fix(3266): preserve wave 0 and bucket plans by depends_on DAG in phase-plan-index (#3276)

* fix(3266): preserve wave 0 and bucket plans by depends_on DAG in phase-plan-index

Fixes two cooperating bugs in the phase-plan-index builder:

1. Wave 0 collapse: `parseInt(...) || 1` coerced parsed value `0` to `1` due to
   JS falsy default. Fixed with `Number.isNaN` guard.
2. depends_on ignored: wave-bucketing used only the `wave:` frontmatter field.
   Now replaced with Kahn's topological-level algorithm over `depends_on`:
   source nodes (no in-phase deps) → lowest level; each plan's level = max(deps'
   levels) + 1.  Declared `wave:` that disagrees with computed level emits a
   non-fatal warning on the result.  Cycle detection throws GSDError.

`PlanInfo` gains `depends_on: string[]`. `PhasePlanIndex` gains `warnings?: string[]`.
Both TS (`sdk/src/query/phase.ts`) and CJS twin (`get-shit-done/bin/lib/phase.cjs`)
fixed identically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add changeset for #3276

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(phase): resolve depends_on against canonical plan id (#3276 CR)

Build a secondary `canonicalToId` index alongside `planMap` so that a
dependency declared as '03-01' resolves to a descriptive plan stored
under '03-01-auth-hardening', preventing silent wave-ordering failures.
Applied at both DAG construction sites in phase.cjs and the SDK's
phase.ts (k014 parity). Regression test added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-09 00:25:05 -04:00
committed by GitHub
parent 8bc255c266
commit 8d5f509edf
7 changed files with 576 additions and 79 deletions

View File

@@ -129,7 +129,7 @@ describe('execute-phase docs: user-facing wave flag', () => {
});
describe('phase-plan-index: wave grouping behavior', () => {
test('phase-plan-index groups plans by wave frontmatter field', () => {
test('phase-plan-index groups plans by wave (DAG-bucketing: P002 depends on P001)', () => {
// allow-test-rule: behavioral — calls gsd-tools and asserts structured output
const fs = require('fs');
const path = require('path');
@@ -138,12 +138,13 @@ describe('phase-plan-index: wave grouping behavior', () => {
const phaseDir = path.join(tmpDir, '.planning', 'phases', '01-alpha');
fs.mkdirSync(phaseDir, { recursive: true });
// Wave 1 plan
// Wave 1 plan — no dependencies
fs.writeFileSync(path.join(phaseDir, 'P001-PLAN.md'), [
'---',
'wave: 1',
'objective: First wave task',
'autonomous: true',
'depends_on: []',
'---',
'',
'# Plan 001',
@@ -153,12 +154,14 @@ describe('phase-plan-index: wave grouping behavior', () => {
'<task>Do the thing</task>',
].join('\n'));
// Wave 2 plan
// Wave 2 plan — depends on P001 so DAG places it in level 1 → wave 2
fs.writeFileSync(path.join(phaseDir, 'P002-PLAN.md'), [
'---',
'wave: 2',
'objective: Second wave task',
'autonomous: true',
'depends_on:',
' - P001',
'---',
'',
'# Plan 002',
@@ -185,6 +188,8 @@ describe('phase-plan-index: wave grouping behavior', () => {
assert.ok(p002, 'P002 should be in plans array');
assert.equal(p001.wave, 1, 'P001 should have wave=1');
assert.equal(p002.wave, 2, 'P002 should have wave=2');
// No mismatch warning: declared wave 2 matches topo level 2
assert.strictEqual(data.warnings, undefined, 'no warnings when declared wave matches DAG');
} finally {
cleanup(tmpDir);
}