fix(gsd): merge enhanced context sections into standard template, clean up stale gate patterns

- Add Architectural Decisions, Error Handling Strategy, Testing
  Requirements, and Acceptance Criteria sections to context.md so
  discussion investigation output persists for downstream phases
- Remove layer1-4 gate patterns from write-gate.ts (only depth_verification
  remains — the 4-layer gates were only in the deleted discuss-prepared.md)
- Update write-gate tests to use depth_verification fixtures

Follows up on #3934
This commit is contained in:
Jeremy 2026-04-10 11:44:39 -05:00
parent cc5157e534
commit a9fc396043
3 changed files with 47 additions and 22 deletions

View file

@ -50,10 +50,6 @@ let pendingGateId: string | null = null;
* These appear in discuss.md (depth/requirements/roadmap).
*/
const GATE_QUESTION_PATTERNS = [
"layer1_scope_gate",
"layer2_architecture_gate",
"layer3_error_gate",
"layer4_quality_gate",
"depth_verification",
] as const;

View file

@ -38,6 +38,28 @@ To call this milestone complete, we must prove:
- {{one real end-to-end scenario}}
- {{what cannot be simulated if this milestone is to be considered truly done}}
## Architectural Decisions
### {{decisionTitle}}
**Decision:** {{decisionStatement}}
**Rationale:** {{rationale}}
**Alternatives Considered:**
- {{alternative}} — {{whyNotChosen}}
---
> Add additional decisions as separate `### Decision Title` blocks following the same structure above.
> See `.gsd/DECISIONS.md` for the full append-only register of all project decisions.
## Error Handling Strategy
{{errorHandlingStrategy}}
> Describe the approach for handling failures, edge cases, and error propagation. Include retry policies, fallback behaviors, and user-facing error messages where relevant.
## Risks and Unknowns
- {{riskOrUnknown}} — {{whyItMatters}}
@ -47,8 +69,6 @@ To call this milestone complete, we must prove:
- `{{fileOrModule}}` — {{howItRelates}}
- `{{fileOrModule}}` — {{howItRelates}}
> See `.gsd/DECISIONS.md` for all architectural and pattern decisions — it is an append-only register; read it during planning, append to it during execution.
## Relevant Requirements
- {{requirementId}} — {{howThisMilestoneAdvancesIt}}
@ -71,6 +91,18 @@ To call this milestone complete, we must prove:
- {{systemOrService}} — {{howThisMilestoneInteractsWithIt}}
## Testing Requirements
{{testingRequirements}}
> Specify test types (unit, integration, e2e), coverage expectations, and specific test scenarios that must pass.
## Acceptance Criteria
{{acceptanceCriteria}}
> Per-slice acceptance criteria gathered during discussion. Each slice should have clear, testable criteria.
## Open Questions
- {{question}} — {{currentThinking}}

View file

@ -230,16 +230,13 @@ import {
// ─── Scenario 19: isGateQuestionId recognizes all gate patterns ──
test('write-gate: isGateQuestionId recognizes all gate patterns', () => {
assert.strictEqual(isGateQuestionId('layer1_scope_gate'), true);
assert.strictEqual(isGateQuestionId('layer2_architecture_gate'), true);
assert.strictEqual(isGateQuestionId('layer3_error_gate'), true);
assert.strictEqual(isGateQuestionId('layer4_quality_gate'), true);
assert.strictEqual(isGateQuestionId('depth_verification'), true);
assert.strictEqual(isGateQuestionId('depth_verification_M002'), true);
assert.strictEqual(isGateQuestionId('my_layer1_scope_gate_question'), true);
assert.strictEqual(isGateQuestionId('depth_verification_confirm'), true);
// Non-gate question IDs
assert.strictEqual(isGateQuestionId('project_intent'), false);
assert.strictEqual(isGateQuestionId('feature_priority'), false);
assert.strictEqual(isGateQuestionId('layer1_scope_gate'), false);
assert.strictEqual(isGateQuestionId(''), false);
});
@ -249,14 +246,14 @@ test('write-gate: pending gate lifecycle (set, get, clear)', () => {
clearDiscussionFlowState();
assert.strictEqual(getPendingGate(), null, 'starts null');
setPendingGate('layer1_scope_gate');
assert.strictEqual(getPendingGate(), 'layer1_scope_gate', 'set correctly');
setPendingGate('depth_verification');
assert.strictEqual(getPendingGate(), 'depth_verification', 'set correctly');
clearPendingGate();
assert.strictEqual(getPendingGate(), null, 'cleared correctly');
// clearDiscussionFlowState also clears pending gate
setPendingGate('layer2_architecture_gate');
setPendingGate('depth_verification_M002');
clearDiscussionFlowState();
assert.strictEqual(getPendingGate(), null, 'clearDiscussionFlowState clears pending gate');
});
@ -265,12 +262,12 @@ test('write-gate: pending gate lifecycle (set, get, clear)', () => {
test('write-gate: shouldBlockPendingGate blocks write/edit during pending gate', () => {
clearDiscussionFlowState();
setPendingGate('layer1_scope_gate');
setPendingGate('depth_verification');
// write should be blocked during discussion
const writeResult = shouldBlockPendingGate('write', 'M001', false);
assert.strictEqual(writeResult.block, true, 'write should be blocked');
assert.ok(writeResult.reason!.includes('layer1_scope_gate'), 'reason mentions the gate');
assert.ok(writeResult.reason!.includes('depth_verification'), 'reason mentions the gate');
// edit should be blocked
const editResult = shouldBlockPendingGate('edit', 'M001', false);
@ -287,7 +284,7 @@ test('write-gate: shouldBlockPendingGate blocks write/edit during pending gate',
test('write-gate: shouldBlockPendingGate allows read-only and ask_user_questions during pending gate', () => {
clearDiscussionFlowState();
setPendingGate('layer1_scope_gate');
setPendingGate('depth_verification');
// ask_user_questions is always safe (model needs to re-ask)
assert.strictEqual(shouldBlockPendingGate('ask_user_questions', 'M001').block, false);
@ -304,7 +301,7 @@ test('write-gate: shouldBlockPendingGate allows read-only and ask_user_questions
test('write-gate: shouldBlockPendingGate blocks outside discussion when a gate is pending', () => {
clearDiscussionFlowState();
setPendingGate('layer1_scope_gate');
setPendingGate('depth_verification');
// No milestoneId and no queue phase — still block because the gate is pending
const result = shouldBlockPendingGate('write', null, false);
@ -330,7 +327,7 @@ test('write-gate: shouldBlockPendingGate blocks in queue mode when gate is pendi
test('write-gate: shouldBlockPendingGateBash allows read-only commands during pending gate', () => {
clearDiscussionFlowState();
setPendingGate('layer2_architecture_gate');
setPendingGate('depth_verification');
assert.strictEqual(shouldBlockPendingGateBash('cat file.txt', 'M001').block, false);
assert.strictEqual(shouldBlockPendingGateBash('git log --oneline', 'M001').block, false);
@ -344,11 +341,11 @@ test('write-gate: shouldBlockPendingGateBash allows read-only commands during pe
test('write-gate: shouldBlockPendingGateBash blocks mutating commands during pending gate', () => {
clearDiscussionFlowState();
setPendingGate('layer2_architecture_gate');
setPendingGate('depth_verification');
const result = shouldBlockPendingGateBash('npm run build', 'M001');
assert.strictEqual(result.block, true, 'mutating bash should be blocked');
assert.ok(result.reason!.includes('layer2_architecture_gate'));
assert.ok(result.reason!.includes('depth_verification'));
clearDiscussionFlowState();
});
@ -365,7 +362,7 @@ test('write-gate: no pending gate means no blocking', () => {
// ─── Scenario 28: resetWriteGateState clears pending gate ──
test('write-gate: resetWriteGateState clears pending gate', () => {
setPendingGate('layer3_error_gate');
setPendingGate('depth_verification');
resetWriteGateState();
assert.strictEqual(getPendingGate(), null);
});