feat(roadmap): surface wave dependencies and cross-cutting constraints (#2487)

* feat(roadmap): surface wave dependencies and cross-cutting constraints (#2447)

Adds roadmap.annotate-dependencies command that post-processes a phase's
ROADMAP plan list to insert wave dependency notes and surface must_haves.truths
entries shared across 2+ plans as cross-cutting constraints. Operation is
idempotent and purely derived from existing PLAN frontmatter.

Closes #2447

* fix(roadmap): address CodeRabbit review findings on PR #2487

- roadmap.cjs: expand idempotency guard to also check for existing
  cross-cutting constraints header, preventing duplicate injection on
  re-runs; add content equality check before writing to preserve
  true idempotency for single-wave phases
- plan-phase.md: move ROADMAP annotation (13d) before docs commit (13c)
  so annotated ROADMAP.md is included in the commit rather than left dirty;
  include .planning/ROADMAP.md in committed files list
- sdk/src/query/index.ts: add annotate-dependencies aliases to
  QUERY_MUTATION_COMMANDS so the mutation is properly event-wired
- sdk/src/query/roadmap.ts: add timeout (15s) and maxBuffer to spawnSync;
  check result.error before result.status to handle spawn/timeout failures

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-04-20 18:19:21 -04:00
committed by GitHub
parent e8ec42082d
commit bd27d4fabe
6 changed files with 441 additions and 6 deletions

View File

@@ -462,6 +462,53 @@ export const roadmapAnalyze: QueryHandler = async (_args, projectDir) => {
return { data: result };
};
// ─── roadmapAnnotateDependencies ─────────────────────────────────────────
/**
* Annotate the ROADMAP.md plan list with wave dependency notes and
* cross-cutting constraints derived from PLAN frontmatter.
*
* Delegates to gsd-tools.cjs which holds the full annotation logic.
* Returns { updated, phase, waves, cross_cutting_constraints }.
*/
export const roadmapAnnotateDependencies: QueryHandler = async (args, projectDir) => {
const phase = args[0];
if (!phase) {
return { data: { updated: false, reason: 'phase argument required' } };
}
const { spawnSync } = await import('node:child_process');
const { fileURLToPath } = await import('node:url');
const toolsPath = fileURLToPath(
new URL('../../../get-shit-done/bin/gsd-tools.cjs', import.meta.url),
);
const result = spawnSync(process.execPath, [toolsPath, 'roadmap', 'annotate-dependencies', phase], {
cwd: projectDir,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
timeout: 15000,
maxBuffer: 1024 * 1024,
});
if (result.error) {
return { data: { updated: false, reason: result.error.message || 'gsd-tools invocation failed' } };
}
if (result.status !== 0) {
return { data: { updated: false, reason: result.stderr?.trim() || 'gsd-tools error' } };
}
try {
return { data: JSON.parse(result.stdout.trim()) };
} catch {
return { data: { updated: false, reason: 'failed to parse gsd-tools output' } };
}
};
// ─── requirementsMarkComplete ─────────────────────────────────────────────
/**