diff --git a/get-shit-done/workflows/execute-phase.md b/get-shit-done/workflows/execute-phase.md index ad7ad361..e1416917 100644 --- a/get-shit-done/workflows/execute-phase.md +++ b/get-shit-done/workflows/execute-phase.md @@ -1315,13 +1315,32 @@ Read and follow `~/.claude/get-shit-done/workflows/transition.md`, passing throu **IMPORTANT: There is NO `/gsd-transition` command. Never suggest it. The transition workflow is internal only.** +Check whether CONTEXT.md already exists for the next phase: + +```bash +ls .planning/phases/*{next}*/{next}-CONTEXT.md 2>/dev/null || echo "no-context" +``` + +If CONTEXT.md does **not** exist for the next phase, present: + ``` ## ✓ Phase {X}: {Name} Complete /gsd-progress ${GSD_WS} — see updated roadmap -/gsd-discuss-phase {next} ${GSD_WS} — discuss next phase before planning -/gsd-plan-phase {next} ${GSD_WS} — plan next phase -/gsd-execute-phase {next} ${GSD_WS} — execute next phase +/gsd-discuss-phase {next} ${GSD_WS} — start here: discuss next phase before planning ← recommended +/gsd-plan-phase {next} ${GSD_WS} — plan next phase (skip discuss) +/gsd-execute-phase {next} ${GSD_WS} — execute next phase (skip discuss and plan) +``` + +If CONTEXT.md **exists** for the next phase, present: + +``` +## ✓ Phase {X}: {Name} Complete + +/gsd-progress ${GSD_WS} — see updated roadmap +/gsd-plan-phase {next} ${GSD_WS} — start here: plan next phase (CONTEXT.md already present) ← recommended +/gsd-discuss-phase {next} ${GSD_WS} — re-discuss next phase +/gsd-execute-phase {next} ${GSD_WS} — execute next phase (skip planning) ``` Only suggest the commands listed above. Do not invent or hallucinate command names. diff --git a/tests/bug-2002-offer-next-context.test.cjs b/tests/bug-2002-offer-next-context.test.cjs new file mode 100644 index 00000000..4144664c --- /dev/null +++ b/tests/bug-2002-offer-next-context.test.cjs @@ -0,0 +1,86 @@ +/** + * Regression tests for bug #2002 + * + * offer_next in execute-phase.md must present conditional next steps + * based on whether CONTEXT.md already exists for the next phase. + * The previous flat list offered all options equally with no primary + * recommendation, leaving agents without guidance on the correct first step. + * + * Fixed: offer_next now checks for {next}-CONTEXT.md in the phase directory. + * - If CONTEXT.md is missing: primary suggestion is /gsd-discuss-phase + * - If CONTEXT.md exists: primary suggestion is /gsd-plan-phase + */ + +'use strict'; + +const { describe, test } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const workflowPath = path.resolve( + __dirname, '..', 'get-shit-done', 'workflows', 'execute-phase.md' +); + +describe('bug #2002: offer_next checks CONTEXT.md before suggesting next step', () => { + let content; + + // Read once — all tests share the same file content + test('setup: workflow file is readable', () => { + content = fs.readFileSync(workflowPath, 'utf-8'); + assert.ok(content.length > 0, 'execute-phase.md must not be empty'); + }); + + test('offer_next section checks for CONTEXT.md existence', () => { + content = content || fs.readFileSync(workflowPath, 'utf-8'); + // The workflow must check for CONTEXT.md in the next phase directory + assert.ok( + content.includes('CONTEXT.md'), + 'offer_next must reference CONTEXT.md to determine primary next step' + ); + }); + + test('offer_next presents /gsd-discuss-phase when CONTEXT.md does not exist', () => { + content = content || fs.readFileSync(workflowPath, 'utf-8'); + // Must have a conditional path where discuss-phase is the primary step + // when CONTEXT.md is missing — look for proximity of "not exist"/"missing"/ + // "does not exist" and "gsd-discuss-phase" in the offer_next step + const offerNextIdx = content.indexOf('offer_next'); + assert.ok(offerNextIdx !== -1, 'offer_next step must exist'); + + // Use 5000-char window — the step is ~60 lines of prose before the conditionals + const offerNextSection = content.slice(offerNextIdx, offerNextIdx + 5000); + assert.ok( + /CONTEXT\.md.*does not exist|CONTEXT\.md.*not.*exist|If CONTEXT\.md does/i.test(offerNextSection) || + /gsd-discuss-phase.*recommended|recommended.*gsd-discuss-phase/i.test(offerNextSection), + 'offer_next must present /gsd-discuss-phase as primary when CONTEXT.md does not exist' + ); + }); + + test('offer_next presents /gsd-plan-phase when CONTEXT.md exists', () => { + content = content || fs.readFileSync(workflowPath, 'utf-8'); + const offerNextIdx = content.indexOf('offer_next'); + assert.ok(offerNextIdx !== -1, 'offer_next step must exist'); + + const offerNextSection = content.slice(offerNextIdx, offerNextIdx + 5000); + assert.ok( + /CONTEXT\.md.*exists|exists.*CONTEXT\.md|If CONTEXT\.md/i.test(offerNextSection), + 'offer_next must present /gsd-plan-phase as primary when CONTEXT.md exists' + ); + }); + + test('offer_next section contains at least one conditional guard before listing commands', () => { + content = content || fs.readFileSync(workflowPath, 'utf-8'); + const offerNextIdx = content.indexOf('offer_next'); + assert.ok(offerNextIdx !== -1, 'offer_next step must exist'); + + const offerNextSection = content.slice(offerNextIdx, offerNextIdx + 5000); + + // The fixed version must contain at least one "If CONTEXT.md" conditional + // guard before presenting command options. The old flat list had no guard. + assert.ok( + /If CONTEXT\.md/i.test(offerNextSection), + 'offer_next must contain at least one "If CONTEXT.md" conditional guard' + ); + }); +});