Merge #607: guard spec/plan/todo artifact paths on every command surface

This commit is contained in:
Addy Osmani authored and GitHub committed 2026-09-25 21:18:15 -07:00
commit b2de133cc8
2 files changed
+80

No files matched your search

+63
View File
@@ -114,3 +114,66 @@ test('skips guarded files that do not exist', () => {
assert.equal(result.status, 0, result.stdout + result.stderr);
assert.match(result.stdout, /1 files checked — 0 error\(s\) — PASSED/);
});
// ─── Every command surface is guarded, not only the Claude Code copy ────────
//
// The Gemini and root TOML command sets carry their own prompt bodies, which
// validate-commands.js deliberately does not compare. A producer can therefore
// drift on one surface while `.claude/commands/` stays correct, and before
// these files were guarded that drift merged silently.
test('fails when a Gemini command drifts while the Claude Code copy stays canonical', () => {
const root = makeSandbox();
writeFile(root, '.claude/commands/spec.md', 'Save the spec as `SPEC.md` in the project root.\n');
writeFile(
root,
'.gemini/commands/spec.toml',
'description = "Start spec-driven development"\n\nprompt = """\nSave the spec to `docs/features/[feature-name]/spec.md`.\n"""\n',
);
const result = run(root);
assert.equal(result.status, 1, result.stdout + result.stderr);
assert.match(result.stdout, /\.gemini\/commands\/spec\.toml/);
assert.match(result.stdout, /L4: docs\/features\/\[feature-name\]\/spec\.md/);
assert.match(result.stdout, /1 error\(s\) — FAILED/);
});
test('fails when a root TOML consumer drifts away from the producers', () => {
const root = makeSandbox();
writeFile(root, 'commands/planning.toml', 'prompt = """\nSave the plan to `tasks/plan.md` and the task list to `tasks/todo.md`.\n"""\n');
writeFile(root, 'commands/build.toml', 'prompt = """\nRead the plan from `planning/plan.md` before starting.\n"""\n');
const result = run(root);
assert.equal(result.status, 1, result.stdout + result.stderr);
assert.match(result.stdout, /commands\/build\.toml/);
assert.match(result.stdout, /planning\/plan\.md/);
});
test('passes when all three command surfaces agree on the canonical paths', () => {
const root = makeSandbox();
for (const dir of ['.claude/commands', '.gemini/commands', 'commands']) {
const ext = dir === '.claude/commands' ? 'md' : 'toml';
const planStem = dir === '.claude/commands' ? 'plan' : 'planning';
writeFile(root, `${dir}/spec.${ext}`, 'Save the spec as `SPEC.md`.\n');
writeFile(root, `${dir}/${planStem}.${ext}`, 'Save to `tasks/plan.md` and `tasks/todo.md`.\n');
writeFile(root, `${dir}/build.${ext}`, 'Require `SPEC.md` or `docs/SPEC.md`, and `tasks/plan.md`.\n');
}
const result = run(root);
assert.equal(result.status, 0, result.stdout + result.stderr);
assert.match(result.stdout, /9 files checked — 0 error\(s\) — PASSED/);
});
test('guards the Copilot setup doc, whose prompt-file aliases name the artifacts', () => {
const root = makeSandbox();
writeFile(root, 'docs/copilot-setup.md', 'Read `tasks/plan.md`, then write the spec to `specs/[name]/spec.md`.\n');
const result = run(root);
assert.equal(result.status, 1, result.stdout + result.stderr);
assert.match(result.stdout, /docs\/copilot-setup\.md/);
assert.match(result.stdout, /specs\/\[name\]\/spec\.md/);
});
+17
View File
@@ -41,14 +41,31 @@ const ARTIFACT_ALLOWLIST = new Set([
// The files that make up the spec -> plan -> build pipeline. Absent files are
// skipped, not failed: this validator checks path consistency, not presence.
//
// The command bodies exist once per host surface (validate-commands.js checks
// that the three sets stay in step on descriptions, but their prompt bodies
// are allowed to differ), so a producer or consumer can drift on one surface
// while the Claude Code copy stays correct. Every surface is guarded here.
const GUARDED_FILES = [
// Claude Code commands
'.claude/commands/spec.md',
'.claude/commands/plan.md',
'.claude/commands/build.md',
// Gemini CLI commands
'.gemini/commands/spec.toml',
'.gemini/commands/planning.toml',
'.gemini/commands/build.toml',
// Root command set (Antigravity, Codex, and other TOML-based hosts)
'commands/spec.toml',
'commands/planning.toml',
'commands/build.toml',
// Skills the commands invoke
'skills/spec-driven-development/SKILL.md',
'skills/planning-and-task-breakdown/SKILL.md',
// Docs that tell users where the artifacts live
'docs/getting-started.md',
'docs/adoption-guide.md',
'docs/copilot-setup.md', // its prompt-file aliases name the artifacts directly
];
// Matches a path-like token ending in a spec/plan/todo artifact filename,