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) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher 2026-03-21 14:38:46 -04:00 committed by GitHub
parent d2be9f34ce
commit 1a70f9daea
2 changed files with 76 additions and 2 deletions

View file

@ -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;

View file

@ -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();
});