mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
fix(workflow): route offer_next based on CONTEXT.md existence for next phase (#2030)
When a phase completes, the offer_next step now checks whether CONTEXT.md already exists for the next phase before presenting options. - If CONTEXT.md is absent: /gsd-discuss-phase is the recommended first step - If CONTEXT.md exists: /gsd-plan-phase is the recommended first step Adds regression test asserting conditional routing is present. Closes #2002 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
86
tests/bug-2002-offer-next-context.test.cjs
Normal file
86
tests/bug-2002-offer-next-context.test.cjs
Normal file
@@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user