From 18ffaa86cc9ae1e719982f134ee4d19a2c657ea4 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 15 May 2026 21:54:03 -0400 Subject: [PATCH] fix(3605): scrub retired slash commands from agents/*.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 (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) --- .changeset/3605-stale-agent-command-refs.md | 5 ++ agents/gsd-executor.md | 2 +- agents/gsd-phase-researcher.md | 2 +- agents/gsd-planner.md | 4 +- agents/gsd-research-synthesizer.md | 2 +- agents/gsd-roadmapper.md | 2 +- ...-research-insert-phase-agent-refs.test.cjs | 87 +++++++++++++++++++ 7 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 .changeset/3605-stale-agent-command-refs.md create mode 100644 tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs diff --git a/.changeset/3605-stale-agent-command-refs.md b/.changeset/3605-stale-agent-command-refs.md new file mode 100644 index 000000000..e627dba5e --- /dev/null +++ b/.changeset/3605-stale-agent-command-refs.md @@ -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 ` 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. diff --git a/agents/gsd-executor.md b/agents/gsd-executor.md index 018d01e0f..70a2bbd2d 100644 --- a/agents/gsd-executor.md +++ b/agents/gsd-executor.md @@ -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 Type "verified" with the correct package name, or "abort" to stop the phase diff --git a/agents/gsd-phase-researcher.md b/agents/gsd-phase-researcher.md index a4482938d..2e1fbf585 100644 --- a/agents/gsd-phase-researcher.md +++ b/agents/gsd-phase-researcher.md @@ -14,7 +14,7 @@ color: cyan 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 ` (standalone). @~/.claude/get-shit-done/references/mandatory-initial-read.md diff --git a/agents/gsd-planner.md b/agents/gsd-planner.md index b0d6d5701..133f55bf2 100644 --- a/agents/gsd-planner.md +++ b/agents/gsd-planner.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 ` before plan-phase. @@ -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 ``` diff --git a/agents/gsd-research-synthesizer.md b/agents/gsd-research-synthesizer.md index a551ebe22..d1c61090b 100644 --- a/agents/gsd-research-synthesizer.md +++ b/agents/gsd-research-synthesizer.md @@ -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 ` during planning? - Which phases have well-documented patterns (skip research)? ## Step 5: Assess Confidence diff --git a/agents/gsd-roadmapper.md b/agents/gsd-roadmapper.md index c6a12e407..c2dfe0997 100644 --- a/agents/gsd-roadmapper.md +++ b/agents/gsd-roadmapper.md @@ -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:** diff --git a/tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs b/tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs new file mode 100644 index 000000000..4508c6f0a --- /dev/null +++ b/tests/bug-3605-stale-research-insert-phase-agent-refs.test.cjs @@ -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 ) 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'), + ); + }); + } +});