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

@@ -53,7 +53,7 @@ import {
} from './init.js';
import { initNewProject, initProgress, initManager } from './init-complex.js';
import { agentSkills } from './skills.js';
import { requirementsMarkComplete } from './roadmap.js';
import { requirementsMarkComplete, roadmapAnnotateDependencies } from './roadmap.js';
import { roadmapUpdatePlanProgress } from './roadmap-update-plan-progress.js';
import { statePlannedPhase } from './state-mutation.js';
import { verifySchemaDrift } from './verify.js';
@@ -141,6 +141,7 @@ export const QUERY_MUTATION_COMMANDS = new Set<string>([
'phase add', 'phase add-batch', 'phase insert', 'phase remove', 'phase complete',
'phase scaffold', 'phases clear', 'phases archive',
'roadmap.update-plan-progress', 'roadmap update-plan-progress',
'roadmap.annotate-dependencies', 'roadmap annotate-dependencies',
'requirements.mark-complete', 'requirements mark-complete',
'todo.complete', 'todo complete',
'milestone.complete', 'milestone complete',
@@ -451,6 +452,8 @@ export function createRegistry(
registry.register('agent-skills', agentSkills);
registry.register('roadmap.update-plan-progress', roadmapUpdatePlanProgress);
registry.register('roadmap update-plan-progress', roadmapUpdatePlanProgress);
registry.register('roadmap.annotate-dependencies', roadmapAnnotateDependencies);
registry.register('roadmap annotate-dependencies', roadmapAnnotateDependencies);
registry.register('requirements.mark-complete', requirementsMarkComplete);
registry.register('requirements mark-complete', requirementsMarkComplete);
registry.register('state.planned-phase', statePlannedPhase);

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 ─────────────────────────────────────────────
/**