* fix(workflows,docs): scrub stale /gsd-code-review-fix and /gsd-plan-milestone-gaps refs (#3029, #3034) #2790 consolidated /gsd-code-review-fix into /gsd-code-review --fix and deleted /gsd-plan-milestone-gaps in favor of inline gap planning as part of /gsd-audit-milestone's output. The deletion was propagated through some surfaces (#2950 covered help/do/settings/discuss-phase/etc.) but several user-facing surfaces still emitted the old forms: #3029 — /gsd-code-review-fix references in: - agents/gsd-code-fixer.md (description, "Spawned by", recovery prose) - get-shit-done/workflows/code-review.md (offer text) - get-shit-done/workflows/execute-phase.md (offer text) - get-shit-done/workflows/code-review-fix.md (internal retry hints) - docs/INVENTORY.md (agent + workflow rows) - docs/CONFIGURATION.md (workflow.code_review row) - docs/USER-GUIDE.md (3 occurrences in walkthrough) - docs/AGENTS.md (gsd-code-fixer agent stub) - docs/FEATURES.md (commands list + REQ-REVIEW-04) All replaced with /gsd-code-review --fix. Internal retry hints in the workflow file itself updated to point at the new form. Release notes (docs/RELEASE-*.md) and gsd-ns-review's "absorbed by" deletion note left unchanged — historical/explanatory content. #3034 — /gsd-plan-milestone-gaps references in: - get-shit-done/workflows/audit-milestone.md (<offer_next> blocks for gaps_found and tech_debt: lines 281, 323) - commands/gsd/complete-milestone.md (gaps_found pre-flight: lines 46, 57) Replaced with inline closure path: /gsd-phase --insert <N> "Close gap: <REQ-ID> ..." /gsd-discuss-phase <N> /gsd-plan-phase <N> /gsd-execute-phase <N> Plus a Nyquist-coverage hint pointing at /gsd-validate-phase / /gsd-secure-phase for retroactive audit-chain hygiene gaps. The gsd-ns-project SKILL.md "deleted by #2790" note is preserved (it's the canonical pointer for future readers asking what happened to the command). Tests: - tests/bug-3029-3034-stale-command-routes.test.cjs — parser-based assertions per fixed surface, plus a structural cross-check that gsd-ns-project keeps the deletion note. 15 tests, all green. - 6905/6905 full suite passes. Closes #3029 Closes #3034 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: address CR feedback on PR #3038 — argument order, structural tests, agent count CR findings on PR #3038: 1. **docs/USER-GUIDE.md (Major)** — `--fix` examples used flag-first form (`/gsd-code-review --fix 3`), but the supported CLI grammar is phase-first (`/gsd-code-review 3 --fix`). The original sed-based replacement preserved the position of the `gsd-code-review-fix` token, producing the wrong order. Fixed in USER-GUIDE.md (3 occurrences) and the same drift in the workflow surfaces: - get-shit-done/workflows/code-review-fix.md (2 retry hints) - get-shit-done/workflows/code-review.md (offer text) - get-shit-done/workflows/execute-phase.md (offer text) 2. **docs/AGENTS.md (Minor)** — internal count drift: line 483 said "Ten additional agents" but line 725 said "12 advanced/specialized". Filesystem reality: 33 agents total, 21 primary, 12 specialized (count of `### ` stubs in the Advanced and Specialized section). Updated lines 3, 13, 483 to use 12/33 and added the two missing names (doc-classifier, doc-synthesizer) to the inline list at line 13. 3. **tests:94 (Major refactor suggestion)** — `.includes()` token checks were source-grep style. Refactored to a typed-IR pattern: extract the SET of slash-command tokens via regex, assert membership on the parsed Set instead of substring scanning the raw file text. Added the `allow-test-rule` comment explaining the IR-build vs IR-assertion split per scripts/lint-no-source-grep.cjs convention. 4. **tests:130 (Major)** — replacement-path assertion was file-wide and could false-pass on generic mentions of "inline" elsewhere in the file. Refactored: `extractOfferBlocks(content)` returns the typed list of `<offer_next>` and "Pre-flight" blocks where the deleted command previously lived, and the assertion runs against those blocks specifically. Now requires `/gsd-phase --insert` or inline-audit prose to appear in the same offer block, not just somewhere in the file. 15/15 targeted tests pass. 6905/6905 full suite pass. Lints clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
260 lines
11 KiB
JavaScript
260 lines
11 KiB
JavaScript
/**
|
|
* Bugs #3029 + #3034: stale slash-command references in shipped surfaces.
|
|
*
|
|
* Both bugs are the same regression class as #2950 (cleanup of #2790's
|
|
* command consolidation): user-facing surfaces emit slash commands that
|
|
* no longer exist as registered command stubs.
|
|
*
|
|
* - #3029: `/gsd-code-review-fix` was deleted by #2790 (consolidated into
|
|
* `/gsd-code-review --fix`), but the agent role cards
|
|
* (`agents/gsd-code-fixer.md`), several workflow offer blocks
|
|
* (`code-review.md`, `execute-phase.md`), and the doc surfaces
|
|
* (`USER-GUIDE.md`, `INVENTORY.md`, `AGENTS.md`, `FEATURES.md`,
|
|
* `CONFIGURATION.md`) still reference the deleted command. Users hit
|
|
* `Unknown command` when they follow the orchestrator's offer.
|
|
*
|
|
* - #3034: `/gsd-plan-milestone-gaps` was deleted by #2790 (gap planning
|
|
* now happens inline as part of `/gsd-audit-milestone`'s output).
|
|
* `audit-milestone.md` <offer_next> blocks (lines 281, 323) and the
|
|
* `gsd-complete-milestone` skill (lines 46, 57) still emit it.
|
|
*
|
|
* Test invariants (parser-based, no raw text matching beyond the literal
|
|
* deleted-command tokens, which are themselves typed identifiers):
|
|
*
|
|
* - No user-facing surface contains the deleted slash command tokens.
|
|
* - The replacement form is present on each fixed surface.
|
|
* - bug-2950-stale-command-refs's existing assertions are not
|
|
* regressed.
|
|
*
|
|
* Internal mentions are allowed:
|
|
* - `code-review-fix.md` workflow file: this is the implementation
|
|
* backend that `--fix` calls into. Internal references to the
|
|
* workflow basename (e.g. "code-review-fix workflow", filename
|
|
* literals) are fine; only user-typed slash forms are blocked.
|
|
* - Release notes (`docs/RELEASE-*.md`): historical record, immutable.
|
|
*/
|
|
|
|
// allow-test-rule: structural-IR parser for stale-command scrubs. The
|
|
// helpers below extract typed records (slash-command token sets, named
|
|
// section bodies); assertions run on the parsed IR, not on raw text. The
|
|
// .includes() hit reported by lint-no-source-grep is the IR-build step,
|
|
// not the assertion surface.
|
|
|
|
'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 ROOT = path.join(__dirname, '..');
|
|
|
|
function read(rel) {
|
|
return fs.readFileSync(path.join(ROOT, rel), 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Extract the set of slash-command tokens emitted by a markdown surface.
|
|
* A slash command is a token starting with "/gsd-" followed by hyphenated
|
|
* identifier characters (letters, digits, hyphens). Trailing argument
|
|
* tokens (numbers, --flags, ${VAR} placeholders) are not part of the
|
|
* command identity, so they are not captured by the regex.
|
|
*
|
|
* Returns a Set so callers can do membership checks without raw-text
|
|
* scanning.
|
|
*/
|
|
function extractSlashCommandTokens(content) {
|
|
const re = /\/gsd-[a-z0-9][a-z0-9-]*/g;
|
|
return new Set(content.match(re) || []);
|
|
}
|
|
|
|
/**
|
|
* Locate the body of an `<offer_next>` block in a workflow file (or, for
|
|
* complete-milestone, the `gaps_found` pre-flight code block). Returns
|
|
* the bounded section text so assertions can run against the actual
|
|
* routing surface where the deleted command lived, not against the whole
|
|
* file (which can produce false-passes from generic prose elsewhere).
|
|
*
|
|
* Strategy: split on the workflow's standard separator lines, find every
|
|
* contiguous slice that begins with a "▶" header — that's the offer-block
|
|
* shape used by audit-milestone.md. For stub files (commands/gsd/*),
|
|
* also capture every fenced markdown block whose body mentions
|
|
* "Pre-flight Check" or "gaps_found".
|
|
*/
|
|
function extractOfferBlocks(content) {
|
|
const blocks = [];
|
|
const lines = content.split('\n');
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if (/^##?\s*▶\s+/.test(lines[i])) {
|
|
const start = i;
|
|
let end = lines.length;
|
|
// The block ends at the next ▶ header, the </offer_next> closing
|
|
// tag, or the next top-level horizontal-rule separator.
|
|
for (let j = i + 1; j < lines.length; j++) {
|
|
if (
|
|
/^##?\s*▶\s+/.test(lines[j]) ||
|
|
/<\/offer_next>/.test(lines[j]) ||
|
|
/^---$/.test(lines[j])
|
|
) {
|
|
end = j;
|
|
break;
|
|
}
|
|
}
|
|
blocks.push(lines.slice(start, end).join('\n'));
|
|
}
|
|
}
|
|
// Stub-file path: capture every fenced markdown block (optionally
|
|
// indented inside list items) whose body mentions "Pre-flight" or the
|
|
// gaps_found marker. Used by commands/gsd/complete-milestone.md, which
|
|
// embeds the routing template inside an indented fenced block rather
|
|
// than an <offer_next> tag.
|
|
const fenceRe = /^[ \t]*```markdown\s*$([\s\S]*?)^[ \t]*```\s*$/gm;
|
|
let m;
|
|
while ((m = fenceRe.exec(content)) !== null) {
|
|
if (/Pre-flight|gaps?[ -]found|gaps_found/i.test(m[1])) {
|
|
blocks.push(m[1]);
|
|
}
|
|
}
|
|
// Also include the bullet-list step-0 block in commands/gsd/
|
|
// complete-milestone.md, where the gaps_found recommendation lives in
|
|
// a list item adjacent to the Pre-flight fenced block. Capture every
|
|
// numbered-list step whose body mentions gaps_found.
|
|
const numberedStepRe = /^\d+\.\s+\*\*[^*]+\*\*[\s\S]*?(?=^\d+\.\s+\*\*|\Z)/gm;
|
|
let s;
|
|
while ((s = numberedStepRe.exec(content)) !== null) {
|
|
if (/gaps?[ -]found|gaps_found/i.test(s[0])) {
|
|
blocks.push(s[0]);
|
|
}
|
|
}
|
|
return blocks;
|
|
}
|
|
|
|
// ─── #3029: /gsd-code-review-fix scrub ──────────────────────────────────────
|
|
|
|
const CRF_DELETED = '/gsd-code-review-fix';
|
|
const CRF_REPLACEMENT = '/gsd-code-review --fix';
|
|
|
|
// Surfaces a user can encounter as routing/dispatch text. Each must not
|
|
// emit the deleted slash-command form.
|
|
const CRF_USER_FACING_SURFACES = [
|
|
'agents/gsd-code-fixer.md',
|
|
'get-shit-done/workflows/code-review.md',
|
|
'get-shit-done/workflows/execute-phase.md',
|
|
'docs/INVENTORY.md',
|
|
'docs/CONFIGURATION.md',
|
|
'docs/USER-GUIDE.md',
|
|
'docs/AGENTS.md',
|
|
'docs/FEATURES.md',
|
|
];
|
|
|
|
// Surfaces where at least one explicit replacement form must appear so
|
|
// the documented user path stays discoverable after the scrub.
|
|
const CRF_REPLACEMENT_SURFACES = [
|
|
'docs/USER-GUIDE.md',
|
|
'docs/FEATURES.md',
|
|
];
|
|
|
|
describe('bug #3029: /gsd-code-review-fix scrubbed from user-facing surfaces', () => {
|
|
for (const rel of CRF_USER_FACING_SURFACES) {
|
|
test(`${rel}: deleted "${CRF_DELETED}" not in slash-command token set`, () => {
|
|
const content = read(rel);
|
|
const tokens = extractSlashCommandTokens(content);
|
|
assert.equal(
|
|
tokens.has(CRF_DELETED),
|
|
false,
|
|
`${rel}: parsed slash-command token set still contains "${CRF_DELETED}" — replace with "${CRF_REPLACEMENT}"`
|
|
);
|
|
});
|
|
}
|
|
|
|
for (const rel of CRF_REPLACEMENT_SURFACES) {
|
|
test(`${rel}: replacement "${CRF_REPLACEMENT}" tokens present`, () => {
|
|
const content = read(rel);
|
|
// The replacement is a multi-token form (`/gsd-code-review` + the
|
|
// `--fix` flag). The slash-command token set captures only the
|
|
// root command, so we additionally check that the literal
|
|
// `--fix` flag appears in proximity. "In proximity" = within 50
|
|
// chars of a /gsd-code-review token; that proves the flag belongs
|
|
// to the right command rather than appearing somewhere unrelated.
|
|
const tokens = extractSlashCommandTokens(content);
|
|
assert.equal(
|
|
tokens.has('/gsd-code-review'),
|
|
true,
|
|
`${rel}: must reference root /gsd-code-review token`
|
|
);
|
|
const proximityRe = /\/gsd-code-review[^\n]{0,50}--fix/;
|
|
assert.ok(
|
|
proximityRe.test(content),
|
|
`${rel}: must document the "/gsd-code-review … --fix" form (root token + flag within 50 chars)`
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
// ─── #3034: /gsd-plan-milestone-gaps scrub ──────────────────────────────────
|
|
|
|
const PMG_DELETED = '/gsd-plan-milestone-gaps';
|
|
// The closure path is the user-facing replacement for gap planning. We
|
|
// don't pin the exact prose — the gsd-ns-project SKILL.md describes it
|
|
// as inline gap planning routed through /gsd-phase --insert plus the
|
|
// standard discuss/plan/execute chain. We assert structurally:
|
|
// (a) deleted command is absent, and (b) at minimum /gsd-phase appears
|
|
// in the same offer-next block where the deleted command lived.
|
|
const PMG_FIX_SURFACES = [
|
|
'get-shit-done/workflows/audit-milestone.md',
|
|
'commands/gsd/complete-milestone.md',
|
|
];
|
|
|
|
describe('bug #3034: /gsd-plan-milestone-gaps scrubbed from user-facing surfaces', () => {
|
|
for (const rel of PMG_FIX_SURFACES) {
|
|
test(`${rel}: deleted "${PMG_DELETED}" not in slash-command token set`, () => {
|
|
const content = read(rel);
|
|
const tokens = extractSlashCommandTokens(content);
|
|
assert.equal(
|
|
tokens.has(PMG_DELETED),
|
|
false,
|
|
`${rel}: parsed slash-command token set still contains "${PMG_DELETED}" — gap planning now happens inline; route via /gsd-phase --insert`
|
|
);
|
|
});
|
|
|
|
test(`${rel}: replacement guidance present in the offer/pre-flight block where the deleted command lived`, () => {
|
|
const content = read(rel);
|
|
const blocks = extractOfferBlocks(content);
|
|
assert.ok(
|
|
blocks.length > 0,
|
|
`${rel}: parser found no <offer_next> / pre-flight blocks — the structural shape of this file may have changed`
|
|
);
|
|
// For every block that previously hosted the deleted command, the
|
|
// replacement guidance must now appear in that same block. We
|
|
// accept either the explicit /gsd-phase --insert closure path or
|
|
// explanatory inline-audit prose ("inline", "audit's output",
|
|
// "MILESTONE-AUDIT.md").
|
|
const inlineProseRe = /inline|audit.*output|gap.*planning.*now|MILESTONE-AUDIT\.md/i;
|
|
const blocksWithReplacement = blocks.filter(
|
|
(b) => b.includes('/gsd-phase --insert') || inlineProseRe.test(b)
|
|
);
|
|
assert.ok(
|
|
blocksWithReplacement.length > 0,
|
|
`${rel}: no offer/pre-flight block contains the replacement closure path. ` +
|
|
`Expected at least one block to mention "/gsd-phase --insert" or inline-audit prose ` +
|
|
`(scoped check, not file-wide — see CR #3038)`
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
// ─── Cross-issue invariant: gsd-ns-project still documents the deletion ─────
|
|
|
|
describe('cross-check: gsd-ns-project keeps the deletion note', () => {
|
|
test('commands/gsd/ns-project.md still notes /gsd-plan-milestone-gaps was deleted', () => {
|
|
const content = read('commands/gsd/ns-project.md');
|
|
// gsd-ns-project legitimately mentions the deleted command name in
|
|
// a "deleted by #2790" note for routing context. We assert the
|
|
// explanatory phrase is present so the deletion stays documented.
|
|
assert.ok(
|
|
/gsd-plan-milestone-gaps.*deleted by #2790|deleted by #2790.*gsd-plan-milestone-gaps/s.test(content),
|
|
'gsd-ns-project must keep the "deleted by #2790" note for /gsd-plan-milestone-gaps so future readers understand the inline-audit replacement'
|
|
);
|
|
});
|
|
});
|