fix(#2697): replace retired /gsd: prefix with /gsd- in all user-facing text (#2699)

All workflow, command, reference, template, and tool-output files that
surfaced /gsd:<cmd> as a user-typed slash command have been updated to
use /gsd-<cmd>, matching the Claude Code skill directory name.

Closes #2697

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-04-25 10:59:33 -04:00
committed by GitHub
parent 7c6f8005f3
commit b1a670e662
155 changed files with 881 additions and 863 deletions

View File

@@ -1,17 +1,31 @@
'use strict';
/**
* Bug #2543: GSD emits legacy '/gsd-<cmd>' syntax in 102 places.
* Slash-command namespace invariant (#2543, updated by #2697).
*
* Installed commands are under commands/gsd/<name>.md and invoked as
* /gsd:<name>. All internal references must use the colon form.
* History:
* #2543 switched user-facing references from /gsd-<cmd> (dash) to /gsd:<cmd> (colon)
* because Claude Code's skill frontmatter used `name: gsd:<cmd>`.
* #2697 reversed this: Claude Code slash commands are invoked by skill *directory*
* name (gsd-<cmd>), not frontmatter name. The colon form (/gsd:<cmd>) does not work
* as a user-typed slash command. Other environment installers (OpenCode, Copilot,
* Antigravity) already transform gsd: → gsd- at install time, so changing the source
* to use gsd- makes all environments consistent.
*
* Invariant enforced here:
* No `/gsd:<cmd>` pattern in user-facing source text.
* `Skill(skill="gsd:<cmd>")` calls (no leading slash) are ALLOWED — they use
* frontmatter `name:` resolution internally and are not user-typed commands.
*
* Exceptions:
* - CHANGELOG.md: historical entries document commands under their original names.
* - gsd-sdk / gsd-tools identifiers: never rewritten (not slash commands).
*/
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const { execFileSync } = require('node:child_process');
const ROOT = path.join(__dirname, '..');
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
@@ -43,24 +57,26 @@ const cmdNames = fs.readdirSync(COMMANDS_DIR)
.map(f => f.replace(/\.md$/, ''))
.sort((a, b) => b.length - a.length);
const legacyPattern = new RegExp(`/gsd-(${cmdNames.join('|')})(?=[^a-zA-Z0-9_-]|$)`);
// Matches /gsd:<cmd> — the retired user-facing format.
// Does NOT match Skill(skill="gsd:<cmd>") because those have no leading slash.
const retiredPattern = new RegExp(`/gsd:(${cmdNames.join('|')})(?=[^a-zA-Z0-9_-]|$)`);
const allFiles = SEARCH_DIRS.flatMap(d => collectFiles(d));
describe('slash-command namespace fix (#2543)', () => {
describe('slash-command namespace invariant (#2697)', () => {
test('commands/gsd/ directory contains known command files', () => {
assert.ok(cmdNames.length > 0, 'commands/gsd/ must contain .md files');
assert.ok(cmdNames.includes('plan-phase'), 'plan-phase must be a known command');
assert.ok(cmdNames.includes('execute-phase'), 'execute-phase must be a known command');
});
test('no /gsd-<cmd> legacy syntax remains in source files', () => {
test('no /gsd:<cmd> retired syntax in user-facing source files', () => {
const violations = [];
for (const file of allFiles) {
const src = fs.readFileSync(file, 'utf-8');
const lines = src.split('\n');
for (let i = 0; i < lines.length; i++) {
if (legacyPattern.test(lines[i])) {
if (retiredPattern.test(lines[i])) {
violations.push(`${path.relative(ROOT, file)}:${i + 1}: ${lines[i].trim().slice(0, 80)}`);
}
}
@@ -68,7 +84,7 @@ describe('slash-command namespace fix (#2543)', () => {
assert.strictEqual(
violations.length,
0,
`Found ${violations.length} legacy /gsd-<cmd> reference(s):\n${violations.slice(0, 10).join('\n')}`,
`Found ${violations.length} retired /gsd:<cmd> reference(s) — use /gsd-<cmd> instead:\n${violations.slice(0, 10).join('\n')}`,
);
});
@@ -77,11 +93,11 @@ describe('slash-command namespace fix (#2543)', () => {
const src = fs.readFileSync(file, 'utf-8');
assert.ok(
!src.includes('/gsd:sdk'),
`${path.relative(ROOT, file)} must not contain /gsd:sdk (gsd-sdk was incorrectly renamed)`,
`${path.relative(ROOT, file)} must not contain /gsd:sdk (gsd-sdk is not a slash command)`,
);
assert.ok(
!src.includes('/gsd:tools'),
`${path.relative(ROOT, file)} must not contain /gsd:tools (gsd-tools was incorrectly renamed)`,
`${path.relative(ROOT, file)} must not contain /gsd:tools (gsd-tools is not a slash command)`,
);
}
});