fix(3605): scrub retired slash commands from agents/*.md
Six surviving references to /gsd-research-phase (deleted in #3042) and /gsd-insert-phase (consolidated into /gsd:phase insert in v1.40.0) remained in five agent contracts because every prior scrub pass (#3029, #3044, #3131) limited its SEARCH_DIRS to workflows/, references/, templates/, contexts/, commands/, and hooks/ — agents/ was outside scope. agents/gsd-executor.md:195 is user-facing: the executor surfaces it during a package-install failure recovery checkpoint, so a real user hits "Unknown command" while trying to recover from a stalled phase. Replacements: - /gsd-research-phase -> /gsd:plan-phase --research-phase <N> (agents/gsd-executor.md:195, agents/gsd-phase-researcher.md:17, agents/gsd-planner.md:186, agents/gsd-planner.md:991, agents/gsd-research-synthesizer.md:115) - /gsd-insert-phase -> /gsd:phase insert (agents/gsd-roadmapper.md:205) Adds tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs as the regression guard. It scans agents/*.md for any retired command name (/gsd-research-phase, /gsd-insert-phase, /gsd-add-phase, /gsd-remove-phase, /gsd-analyze-dependencies) with proper word-boundary matching so a future consolidation that misses agents/ fails CI. The guard mirrors tests/bug-2950-stale-command-refs.test.cjs which covers the same anti-pattern for workflows/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
.changeset/3605-stale-agent-command-refs.md
Normal file
5
.changeset/3605-stale-agent-command-refs.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
issue: 3605
|
||||
---
|
||||
**Agent contracts no longer reference retired `/gsd-research-phase` or `/gsd-insert-phase`** — six surviving references in `agents/gsd-executor.md`, `agents/gsd-phase-researcher.md`, `agents/gsd-planner.md`, `agents/gsd-research-synthesizer.md`, and `agents/gsd-roadmapper.md` are replaced with `/gsd:plan-phase --research-phase <N>` and `/gsd:phase insert`. Adds a regression guard (`tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs`) that fails when any retired command name reappears in `agents/*.md` — covers the gap that let #3029, #3044, and #3131 miss the `agents/` directory.
|
||||
@@ -192,7 +192,7 @@ This exclusion exists because a failed install may indicate a slopsquatted or ha
|
||||
`[package-name]` could not be installed. Before proceeding:
|
||||
1. Verify the package exists and is legitimate: https://npmjs.com/package/[package-name]
|
||||
2. Confirm the package name is spelled correctly in PLAN.md
|
||||
3. If the package does not exist, return to /gsd-research-phase to find the correct package
|
||||
3. If the package does not exist, re-run /gsd-plan-phase --research-phase <N> to find the correct package
|
||||
</how-to-verify>
|
||||
<resume-signal>Type "verified" with the correct package name, or "abort" to stop the phase</resume-signal>
|
||||
</task>
|
||||
|
||||
@@ -14,7 +14,7 @@ color: cyan
|
||||
<role>
|
||||
You are a GSD phase researcher. You answer "What do I need to know to PLAN this phase well?" and produce a single RESEARCH.md that the planner consumes.
|
||||
|
||||
Spawned by `/gsd:plan-phase` (integrated) or `/gsd-research-phase` (standalone).
|
||||
Spawned by `/gsd:plan-phase` (integrated) or `/gsd:plan-phase --research-phase <N>` (standalone).
|
||||
|
||||
@~/.claude/get-shit-done/references/mandatory-initial-read.md
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ Discovery is MANDATORY unless you can prove current context exists.
|
||||
- Level 2+: New library not in package.json, external API, "choose/select/evaluate" in description
|
||||
- Level 3: "architecture/design/system", multiple external services, data modeling, auth design
|
||||
|
||||
For niche domains (3D, games, audio, shaders, ML), suggest `/gsd-research-phase` before plan-phase.
|
||||
For niche domains (3D, games, audio, shaders, ML), suggest `/gsd:plan-phase --research-phase <N>` before plan-phase.
|
||||
|
||||
</discovery_levels>
|
||||
|
||||
@@ -988,7 +988,7 @@ Use `phase_dir` from init context (already loaded in load_project_state).
|
||||
|
||||
```bash
|
||||
cat "$phase_dir"/*-CONTEXT.md 2>/dev/null # From /gsd:discuss-phase
|
||||
cat "$phase_dir"/*-RESEARCH.md 2>/dev/null # From /gsd-research-phase
|
||||
cat "$phase_dir"/*-RESEARCH.md 2>/dev/null # From /gsd:plan-phase --research-phase
|
||||
cat "$phase_dir"/*-DISCOVERY.md 2>/dev/null # From mandatory discovery
|
||||
```
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ This is the most important section. Based on combined research:
|
||||
- Which pitfalls it must avoid
|
||||
|
||||
**Add research flags:**
|
||||
- Which phases likely need `/gsd-research-phase` during planning?
|
||||
- Which phases likely need `/gsd:plan-phase --research-phase <N>` during planning?
|
||||
- Which phases have well-documented patterns (skip research)?
|
||||
|
||||
## Step 5: Assess Confidence
|
||||
|
||||
@@ -202,7 +202,7 @@ Track coverage as you go.
|
||||
**Integer phases (1, 2, 3):** Planned milestone work.
|
||||
|
||||
**Decimal phases (2.1, 2.2):** Urgent insertions after planning.
|
||||
- Created via `/gsd-insert-phase`
|
||||
- Created via `/gsd:phase insert`
|
||||
- Execute between integers: 1 → 1.1 → 1.2 → 2
|
||||
|
||||
**Starting number:**
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// allow-test-rule: source-text-is-the-product
|
||||
// agents/*.md text IS the deployed contract — Claude Code, Codex, etc. load these
|
||||
// files at runtime and surface their content to users. Testing for retired slash
|
||||
// commands in this text is testing what real users will see.
|
||||
|
||||
/**
|
||||
* Bug #3605: Stale slash command references in 5 agent files
|
||||
*
|
||||
* After #3042 deleted /gsd-research-phase (replaced by
|
||||
* /gsd-plan-phase --research-phase <N>) and v1.40.0 consolidated /gsd-insert-phase
|
||||
* into /gsd-phase insert, six occurrences survived in agents/*.md because none of
|
||||
* the consolidation passes (#3029, #3044, #3131) included agents/ in their per-name
|
||||
* scrub scope. scripts/fix-slash-commands.cjs lists agents/ in SEARCH_DIRS but only
|
||||
* runs the /gsd- → /gsd: namespace transform, not retired-name replacement.
|
||||
*
|
||||
* This guard fails when any retired command name reappears in agents/*.md.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const { describe, test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const AGENTS_DIR = path.join(__dirname, '..', 'agents');
|
||||
|
||||
const RETIRED_COMMANDS = [
|
||||
'/gsd-research-phase',
|
||||
'/gsd-insert-phase',
|
||||
'/gsd-add-phase',
|
||||
'/gsd-remove-phase',
|
||||
'/gsd-analyze-dependencies',
|
||||
];
|
||||
|
||||
function listAgentFiles() {
|
||||
return fs
|
||||
.readdirSync(AGENTS_DIR)
|
||||
.filter((name) => name.endsWith('.md'))
|
||||
.map((name) => path.join(AGENTS_DIR, name));
|
||||
}
|
||||
|
||||
function scanForRetired(filePath) {
|
||||
const text = fs.readFileSync(filePath, 'utf-8');
|
||||
const lines = text.split('\n');
|
||||
const hits = [];
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
for (const cmd of RETIRED_COMMANDS) {
|
||||
const idx = lines[i].indexOf(cmd);
|
||||
if (idx === -1) continue;
|
||||
const next = lines[i].charCodeAt(idx + cmd.length);
|
||||
// Only count if the match is a real invocation, not a prefix of a longer name.
|
||||
// The next char must be a non-name char (anything outside [A-Za-z0-9-_]).
|
||||
const isWordBoundary =
|
||||
Number.isNaN(next) ||
|
||||
!((next >= 48 && next <= 57) || // 0-9
|
||||
(next >= 65 && next <= 90) || // A-Z
|
||||
(next >= 97 && next <= 122) || // a-z
|
||||
next === 45 || // -
|
||||
next === 95); // _
|
||||
if (!isWordBoundary) continue;
|
||||
hits.push({ line: i + 1, cmd, text: lines[i].trim() });
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
describe('bug #3605: agent contracts must not reference retired slash commands', () => {
|
||||
const agentFiles = listAgentFiles();
|
||||
|
||||
test('at least one agent file is scanned (smoke)', () => {
|
||||
assert.ok(agentFiles.length > 0, 'expected agents/*.md to exist');
|
||||
});
|
||||
|
||||
for (const file of agentFiles) {
|
||||
const rel = path.relative(path.join(__dirname, '..'), file);
|
||||
test(`${rel} contains no retired slash commands`, () => {
|
||||
const hits = scanForRetired(file);
|
||||
assert.deepEqual(
|
||||
hits,
|
||||
[],
|
||||
`${rel} contains retired command references:\n` +
|
||||
hits.map((h) => ` line ${h.line}: ${h.cmd} — ${h.text}`).join('\n'),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user