diff --git a/src/resources/extensions/gsd/tests/note-captures-executed.test.ts b/src/resources/extensions/gsd/tests/note-captures-executed.test.ts new file mode 100644 index 000000000..60c0a7a65 --- /dev/null +++ b/src/resources/extensions/gsd/tests/note-captures-executed.test.ts @@ -0,0 +1,46 @@ +/** + * Regression test for #3578 — note captures marked as executed + * + * Note-classified captures were stuck in "resolved but not executed" limbo + * because executeTriageResolutions only handled inject/replan/defer. The fix + * adds a filter for classification === "note" and calls markCaptureExecuted + * for each matching capture. + * + * Structural verification test — reads source to confirm the note filter + * and markCaptureExecuted call exist. + */ + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const source = readFileSync(join(__dirname, '..', 'triage-resolution.ts'), 'utf-8'); + +describe('note captures executed in triage resolution (#3578)', () => { + test('markCaptureExecuted is imported', () => { + assert.match(source, /markCaptureExecuted/, + 'markCaptureExecuted should be imported'); + }); + + test('note classification filter exists', () => { + assert.match(source, /classification\s*===\s*"note"/, + 'filter should check classification === "note"'); + }); + + test('note filter checks resolved status and not-executed', () => { + assert.match(source, /status\s*===\s*"resolved"\s*&&\s*!c\.executed\s*&&\s*c\.classification\s*===\s*"note"/, + 'filter should check resolved + not-executed + note classification'); + }); + + test('markCaptureExecuted is called for note captures', () => { + // The source should call markCaptureExecuted for note captures + const noteSection = source.slice(source.indexOf('classification === "note"')); + assert.match(noteSection, /markCaptureExecuted\(basePath,\s*cap\.id\)/, + 'markCaptureExecuted should be called for note captures'); + }); +}); diff --git a/src/resources/extensions/gsd/tests/triage-resolution.test.ts b/src/resources/extensions/gsd/tests/triage-resolution.test.ts index deb924347..0decf9e6f 100644 --- a/src/resources/extensions/gsd/tests/triage-resolution.test.ts +++ b/src/resources/extensions/gsd/tests/triage-resolution.test.ts @@ -387,7 +387,8 @@ test("resolution: executeTriageResolutions handles mixed classifications", () => assert.strictEqual(result.injected, 1, "should inject 1 task"); assert.strictEqual(result.replanned, 0); assert.strictEqual(result.quickTasks.length, 1, "should queue 1 quick-task"); - assert.strictEqual(result.actions.length, 2, "should have 2 action entries (note/defer excluded)"); + // inject + quick-task + note acknowledged = 3 actions (defer still excluded) + assert.strictEqual(result.actions.length, 3, "should have 3 action entries (defer excluded, note now included)"); } finally { rmSync(tmp, { recursive: true, force: true }); } diff --git a/src/resources/extensions/gsd/triage-resolution.ts b/src/resources/extensions/gsd/triage-resolution.ts index 256091edf..791b7f1ad 100644 --- a/src/resources/extensions/gsd/triage-resolution.ts +++ b/src/resources/extensions/gsd/triage-resolution.ts @@ -510,6 +510,16 @@ export function executeTriageResolutions( } } + // Mark note captures as executed — they're informational only, no action + // needed. Without this they stay in "resolved but not executed" limbo (#3578). + const notes = loadAllCaptures(basePath).filter( + c => c.status === "resolved" && !c.executed && c.classification === "note", + ); + for (const cap of notes) { + markCaptureExecuted(basePath, cap.id); + result.actions.push(`Note acknowledged: ${cap.id} — "${cap.text}"`); + } + if (actionable.length === 0) return result; for (const capture of actionable) {