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:
@@ -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);
|
||||
|
||||
@@ -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 ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user