enhancement(workflow): replace consecutive-call counter with prior-phase completeness scan in /gsd-next (#2097)
Removes the .next-call-count counter file guard (which fired on clean usage and missed real incomplete work) and replaces it with a scan of all prior phases for plans without summaries, unoverridden VERIFICATION.md failures, and phases with CONTEXT.md but no plans. When gaps are found, shows a structured report with Continue/Stop/Force options; the Continue path writes a formal 999.x backlog entry and commits it before routing. Clean projects route silently with no interruption. Closes #2089 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,9 @@ No arguments needed — reads STATE.md, ROADMAP.md, and phase directories to det
|
|||||||
|
|
||||||
Designed for rapid multi-project workflows where remembering which phase/step you're on is overhead.
|
Designed for rapid multi-project workflows where remembering which phase/step you're on is overhead.
|
||||||
|
|
||||||
Supports `--force` flag to bypass safety gates (checkpoint, error state, verification failures).
|
Supports `--force` flag to bypass safety gates (checkpoint, error state, verification failures, and prior-phase completeness scan).
|
||||||
|
|
||||||
|
Before routing to the next step, scans all prior phases for incomplete work: plans that ran without producing summaries, verification failures without overrides, and phases where discussion happened but planning never ran. When incomplete work is found, shows a structured report and offers three options: defer the gaps to the backlog and continue, stop and resolve manually, or force advance without recording. When prior phases are clean, routes silently with no interruption.
|
||||||
</objective>
|
</objective>
|
||||||
|
|
||||||
<execution_context>
|
<execution_context>
|
||||||
|
|||||||
@@ -82,12 +82,56 @@ Use `--force` to bypass this check.
|
|||||||
```
|
```
|
||||||
Exit.
|
Exit.
|
||||||
|
|
||||||
**Consecutive-call guard:**
|
**Prior-phase completeness scan:**
|
||||||
After passing all gates, check a counter file `.planning/.next-call-count`:
|
After passing all three hard-stop gates, scan all phases that precede the current phase in ROADMAP.md order for incomplete work. Use the existing `gsd-tools.cjs phase json <N>` output to inspect each prior phase.
|
||||||
- If file exists and count >= 6: prompt "You've called /gsd-next {N} times consecutively. Continue? [y/N]"
|
|
||||||
- If user says no, exit
|
Detect three categories of incomplete work:
|
||||||
- Increment the counter
|
1. **Plans without summaries** — a PLAN.md exists in a prior phase directory but no matching SUMMARY.md exists (execution started but not completed).
|
||||||
- The counter file is deleted by any non-`/gsd-next` command (convention — other workflows don't need to implement this, the note here is sufficient)
|
2. **Verification failures not overridden** — a prior phase has a VERIFICATION.md with `FAIL` items that have no override annotation.
|
||||||
|
3. **CONTEXT.md without plans** — a prior phase directory has a CONTEXT.md but no PLAN.md files (discussion happened, planning never ran).
|
||||||
|
|
||||||
|
If no incomplete prior work is found, continue to `determine_next_action` silently with no interruption.
|
||||||
|
|
||||||
|
If incomplete prior work is found, show a structured completeness report:
|
||||||
|
```
|
||||||
|
⚠ Prior phase has incomplete work
|
||||||
|
|
||||||
|
Phase {N} — "{name}" has unresolved items:
|
||||||
|
• Plan {N}-{M} ({slug}): executed but no SUMMARY.md
|
||||||
|
[... additional items ...]
|
||||||
|
|
||||||
|
Advancing before resolving these may cause:
|
||||||
|
• Verification gaps — future phase verification won't have visibility into what prior phases shipped
|
||||||
|
• Context loss — plans that ran without summaries leave no record for future agents
|
||||||
|
|
||||||
|
Options:
|
||||||
|
[C] Continue and defer these items to backlog
|
||||||
|
[S] Stop and resolve manually (recommended)
|
||||||
|
[F] Force advance without recording deferral
|
||||||
|
|
||||||
|
Choice [S]:
|
||||||
|
```
|
||||||
|
|
||||||
|
**If the user chooses "Stop" (S or Enter/default):** Exit without routing.
|
||||||
|
|
||||||
|
**If the user chooses "Continue and defer" (C):**
|
||||||
|
1. For each incomplete item, create a backlog entry in `ROADMAP.md` under `## Backlog` using the existing `999.x` numbering scheme:
|
||||||
|
```markdown
|
||||||
|
### Phase 999.{N}: Follow-up — Phase {src} incomplete plans (BACKLOG)
|
||||||
|
|
||||||
|
**Goal:** Resolve plans that ran without producing summaries during Phase {src} execution
|
||||||
|
**Source phase:** {src}
|
||||||
|
**Deferred at:** {date} during /gsd-next advancement to Phase {dest}
|
||||||
|
**Plans:**
|
||||||
|
- [ ] {N}-{M}: {slug} (ran, no SUMMARY.md)
|
||||||
|
```
|
||||||
|
2. Commit the deferral record:
|
||||||
|
```bash
|
||||||
|
node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: defer incomplete Phase {src} items to backlog"
|
||||||
|
```
|
||||||
|
3. Continue routing to `determine_next_action` immediately — no second prompt.
|
||||||
|
|
||||||
|
**If the user chooses "Force" (F):** Continue to `determine_next_action` without recording deferral.
|
||||||
</step>
|
</step>
|
||||||
|
|
||||||
<step name="determine_next_action">
|
<step name="determine_next_action">
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* GSD Tools Tests - /gsd-next safety gates and consecutive-call guard
|
* GSD Tools Tests - /gsd-next safety gates and prior-phase completeness scan
|
||||||
*
|
*
|
||||||
* Validates that the next workflow includes three hard-stop safety gates
|
* Validates that the next workflow includes three hard-stop safety gates
|
||||||
* (checkpoint, error state, verification), a consecutive-call budget guard,
|
* (checkpoint, error state, verification), a prior-phase completeness scan
|
||||||
* and a --force bypass flag.
|
* replacing the old consecutive-call counter, and a --force bypass flag.
|
||||||
*
|
*
|
||||||
* Closes: #1732
|
* Closes: #1732, #2089
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { test, describe } = require('node:test');
|
const { test, describe } = require('node:test');
|
||||||
@@ -13,7 +13,7 @@ const assert = require('node:assert/strict');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
describe('/gsd-next safety gates (#1732)', () => {
|
describe('/gsd-next safety gates (#1732, #2089)', () => {
|
||||||
const workflowPath = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'next.md');
|
const workflowPath = path.join(__dirname, '..', 'get-shit-done', 'workflows', 'next.md');
|
||||||
const commandPath = path.join(__dirname, '..', 'commands', 'gsd', 'next.md');
|
const commandPath = path.join(__dirname, '..', 'commands', 'gsd', 'next.md');
|
||||||
|
|
||||||
@@ -79,19 +79,72 @@ describe('/gsd-next safety gates (#1732)', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('consecutive-call budget guard', () => {
|
test('prior-phase completeness scan replaces consecutive-call counter', () => {
|
||||||
const content = fs.readFileSync(workflowPath, 'utf8');
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('.next-call-count'),
|
content.includes('Prior-phase completeness scan'),
|
||||||
'workflow should reference .next-call-count counter file'
|
'workflow should have a prior-phase completeness scan section'
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('6'),
|
!content.includes('.next-call-count'),
|
||||||
'consecutive guard should trigger at count >= 6'
|
'workflow must not reference the old .next-call-count counter file'
|
||||||
);
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('consecutively'),
|
!content.includes('consecutively'),
|
||||||
'guard should mention consecutive calls'
|
'workflow must not reference consecutive call counting'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('completeness scan checks plans without summaries', () => {
|
||||||
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
|
assert.ok(
|
||||||
|
content.includes('Plans without summaries') || content.includes('no SUMMARY.md'),
|
||||||
|
'completeness scan should detect plans that ran without producing summaries'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('completeness scan checks verification failures in prior phases', () => {
|
||||||
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
|
assert.ok(
|
||||||
|
content.includes('Verification failures not overridden') ||
|
||||||
|
content.includes('VERIFICATION.md with `FAIL`'),
|
||||||
|
'completeness scan should detect unoverridden FAIL items in prior phase VERIFICATION.md'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('completeness scan checks CONTEXT.md without plans', () => {
|
||||||
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
|
assert.ok(
|
||||||
|
content.includes('CONTEXT.md without plans') ||
|
||||||
|
content.includes('CONTEXT.md but no PLAN.md'),
|
||||||
|
'completeness scan should detect phases with discussion but no planning'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('completeness scan offers Continue, Stop, and Force options', () => {
|
||||||
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
|
assert.ok(content.includes('[C]'), 'completeness scan should offer [C] Continue option');
|
||||||
|
assert.ok(content.includes('[S]'), 'completeness scan should offer [S] Stop option');
|
||||||
|
assert.ok(content.includes('[F]'), 'completeness scan should offer [F] Force option');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deferral path creates backlog entry using 999.x scheme', () => {
|
||||||
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
|
assert.ok(
|
||||||
|
content.includes('999.'),
|
||||||
|
'deferral should use the 999.x backlog numbering scheme'
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
content.includes('Backlog') || content.includes('BACKLOG'),
|
||||||
|
'deferral should write to the Backlog section of ROADMAP.md'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('clean prior phases route silently with no interruption', () => {
|
||||||
|
const content = fs.readFileSync(workflowPath, 'utf8');
|
||||||
|
assert.ok(
|
||||||
|
content.includes('silently') || content.includes('no interruption'),
|
||||||
|
'workflow should route without interruption when prior phases are clean'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -107,7 +160,7 @@ describe('/gsd-next safety gates (#1732)', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('command definition documents --force flag', () => {
|
test('command definition documents --force flag and completeness scan', () => {
|
||||||
const content = fs.readFileSync(commandPath, 'utf8');
|
const content = fs.readFileSync(commandPath, 'utf8');
|
||||||
assert.ok(
|
assert.ok(
|
||||||
content.includes('--force'),
|
content.includes('--force'),
|
||||||
@@ -117,6 +170,10 @@ describe('/gsd-next safety gates (#1732)', () => {
|
|||||||
content.includes('bypass safety gates'),
|
content.includes('bypass safety gates'),
|
||||||
'command definition should explain that --force bypasses safety gates'
|
'command definition should explain that --force bypasses safety gates'
|
||||||
);
|
);
|
||||||
|
assert.ok(
|
||||||
|
content.includes('completeness'),
|
||||||
|
'command definition should document the prior-phase completeness scan'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('gates exit on first hit', () => {
|
test('gates exit on first hit', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user