Files
get-shit-done/scripts/fix-slash-commands.cjs
Tom Boucher 73c1af5168 fix(#2543): replace legacy /gsd-<cmd> syntax with /gsd:<cmd> across all source files (#2595)
Commands are now installed as commands/gsd/<name>.md and invoked as
/gsd:<name> in Claude Code. The old hyphen form /gsd-<name> was still
hardcoded in hundreds of places across workflows, references, templates,
lib modules, and command files — causing "Unknown command" errors
whenever GSD suggested a command to the user.

Replace all /gsd-<cmd> occurrences where <cmd> is a known command name
(derived at runtime from commands/gsd/*.md) using a targeted Node.js
script. Agent names, tool names (gsd-sdk, gsd-tools), directory names,
and path fragments are not touched.

Adds regression test tests/bug-2543-gsd-slash-namespace.test.cjs that
enforces zero legacy occurrences going forward. Removes inverted
tests/stale-colon-refs.test.cjs (bug #1748) which enforced the now-obsolete
hyphen form; the new bug-2543 test supersedes it. Updates 5 assertion
tests that hardcoded the old hyphen form to accept the new colon form.

Closes #2543

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 12:04:25 -04:00

53 lines
1.8 KiB
JavaScript

'use strict';
/**
* One-shot script: replace /gsd-<cmd> with /gsd:<cmd> for known command names.
* Only replaces when followed by a word boundary (space, newline, quote, backtick, ), end).
*/
const fs = require('node:fs');
const path = require('node:path');
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
const cmdNames = fs.readdirSync(COMMANDS_DIR)
.filter(f => f.endsWith('.md'))
.map(f => f.replace(/\.md$/, ''))
.sort((a, b) => b.length - a.length); // longest first to avoid partial matches
// Build regex: /gsd-(cmd1|cmd2|...) followed by non-word-char or end
const pattern = new RegExp(`/gsd-(${cmdNames.join('|')})(?=[^a-zA-Z0-9_-]|$)`, 'g');
const SEARCH_DIRS = [
path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib'),
path.join(__dirname, '..', 'get-shit-done', 'workflows'),
path.join(__dirname, '..', 'get-shit-done', 'references'),
path.join(__dirname, '..', 'get-shit-done', 'templates'),
path.join(__dirname, '..', 'get-shit-done', 'contexts'),
path.join(__dirname, '..', 'commands', 'gsd'),
];
const EXTENSIONS = new Set(['.md', '.cjs', '.js']);
function processDir(dir) {
let entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) {
processDir(full);
} else if (EXTENSIONS.has(path.extname(e.name))) {
const src = fs.readFileSync(full, 'utf-8');
const replaced = src.replace(pattern, (_, cmd) => `/gsd:${cmd}`);
if (replaced !== src) {
fs.writeFileSync(full, replaced, 'utf-8');
const count = (src.match(pattern) || []).length;
console.log(` ${count} replacements: ${path.relative(path.join(__dirname, '..'), full)}`);
}
}
}
}
let totalFiles = 0;
for (const dir of SEARCH_DIRS) {
processDir(dir);
}
console.log('Done.');