From 1a70f9daeaeb523924ed8f1bbe1e7ac919fdd322 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 21 Mar 2026 14:38:46 -0400 Subject: [PATCH] fix(hooks): process depth verification in queue mode (#1823) The tool_result handler early-returned when getDiscussionMilestoneId() was null, which is always the case in queue mode. This prevented markDepthVerified() from being called, permanently blocking CONTEXT.md writes when queuePhaseActive was true. The handler now also checks isQueuePhaseActive() before returning early, allowing depth verification to complete in queue mode. A second guard after depth verification processing ensures the discussion log write still requires a valid milestoneId. Fixes #1812 Co-authored-by: Claude Opus 4.6 (1M context) --- .../gsd/bootstrap/register-hooks.ts | 5 +- .../extensions/gsd/tests/write-gate.test.ts | 73 ++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/bootstrap/register-hooks.ts b/src/resources/extensions/gsd/bootstrap/register-hooks.ts index 3a5f361f3..2a381488f 100644 --- a/src/resources/extensions/gsd/bootstrap/register-hooks.ts +++ b/src/resources/extensions/gsd/bootstrap/register-hooks.ts @@ -136,7 +136,8 @@ export function registerHooks(pi: ExtensionAPI): void { pi.on("tool_result", async (event) => { if (event.toolName !== "ask_user_questions") return; const milestoneId = getDiscussionMilestoneId(); - if (!milestoneId) return; + const queueActive = isQueuePhaseActive(); + if (!milestoneId && !queueActive) return; const details = event.details as any; if (details?.cancelled || !details?.response) return; @@ -149,6 +150,8 @@ export function registerHooks(pi: ExtensionAPI): void { } } + if (!milestoneId) return; + const basePath = process.cwd(); const milestoneDir = resolveMilestonePath(basePath, milestoneId); if (!milestoneDir) return; diff --git a/src/resources/extensions/gsd/tests/write-gate.test.ts b/src/resources/extensions/gsd/tests/write-gate.test.ts index 0b7074adc..8ca4ee7b5 100644 --- a/src/resources/extensions/gsd/tests/write-gate.test.ts +++ b/src/resources/extensions/gsd/tests/write-gate.test.ts @@ -11,7 +11,17 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { shouldBlockContextWrite } from '../index.ts'; +import { + shouldBlockContextWrite, + isDepthVerified, + isQueuePhaseActive, + setQueuePhaseActive, +} from '../index.ts'; +import { + markDepthVerified, + clearDiscussionFlowState, + resetWriteGateState, +} from '../bootstrap/write-gate.ts'; // ─── Scenario 1: Blocks CONTEXT.md write during discussion without depth verification (absolute path) ── @@ -120,3 +130,64 @@ test('write-gate: blocked reason contains depth_verification keyword', () => { assert.ok(result.reason!.includes('depth_verification'), 'reason should mention depth_verification question id'); assert.ok(result.reason!.includes('ask_user_questions'), 'reason should mention ask_user_questions tool'); }); + +// ─── Scenario 8: Queue mode blocks CONTEXT.md write without depth verification ── + +test('write-gate: blocks CONTEXT.md write in queue mode without depth verification', () => { + const result = shouldBlockContextWrite( + 'write', + '.gsd/milestones/M001/M001-CONTEXT.md', + null, // no milestoneId in queue mode + false, // not depth-verified + true, // queue phase active + ); + assert.strictEqual(result.block, true, 'should block in queue mode without depth verification'); + assert.ok(result.reason, 'should provide a reason'); +}); + +// ─── Scenario 9: Queue mode allows CONTEXT.md write after depth verification ── + +test('write-gate: allows CONTEXT.md write in queue mode after depth verification', () => { + const result = shouldBlockContextWrite( + 'write', + '.gsd/milestones/M001/M001-CONTEXT.md', + null, // no milestoneId in queue mode + true, // depth-verified + true, // queue phase active + ); + assert.strictEqual(result.block, false, 'should not block in queue mode after depth verification'); +}); + +// ─── Scenario 10: markDepthVerified works in queue-only mode (no milestoneId) ── +// This is the core regression for #1812: in queue mode, the tool_result handler +// must call markDepthVerified() even when getDiscussionMilestoneId() is null. + +test('write-gate: markDepthVerified unblocks queue-mode writes when milestoneId is null', () => { + clearDiscussionFlowState(); + setQueuePhaseActive(true); + + // Before marking: should block + const blocked = shouldBlockContextWrite( + 'write', + '.gsd/milestones/M001/M001-CONTEXT.md', + null, + isDepthVerified(), + isQueuePhaseActive(), + ); + assert.strictEqual(blocked.block, true, 'should block before markDepthVerified'); + + // Simulate what the fixed tool_result handler does + markDepthVerified(); + + // After marking: should pass + const allowed = shouldBlockContextWrite( + 'write', + '.gsd/milestones/M001/M001-CONTEXT.md', + null, + isDepthVerified(), + isQueuePhaseActive(), + ); + assert.strictEqual(allowed.block, false, 'should allow after markDepthVerified in queue mode'); + + clearDiscussionFlowState(); +});