mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-05-13 10:36:38 +02:00
scripts/fix-slash-commands.cjs SEARCH_DIRS did not cover agents/, sdk/src/, or top-level files, so 9 colon-form references survived in 6 files. The hit at agents/gsd-codebase-mapper.md:105 propagated into ~/.claude/agents/ at install time (the fixer is not wired into install) and produced unrunnable /gsd:<cmd> suggestions in agent output on non-Gemini runtimes. This commit includes Pass 1 (the 9 line edits) AND Pass 2 (extending the fixer's SEARCH_DIRS so future regressions are auto-rewritten and caught by the bug-2543 guard, which mirrors that list). The standalone bug-3100 test added in the prior revision is removed in favor of the bug-2543 guard's extended scan, per CONTRIBUTING.md test standards (no source-grep tests on non-.md files). Refs #3100
107 lines
3.8 KiB
JavaScript
107 lines
3.8 KiB
JavaScript
'use strict';
|
|
/**
|
|
* One-shot script: replace retired /gsd:<cmd> with /gsd-<cmd> for known command names.
|
|
* Only replaces when followed by a word boundary (space, newline, quote, backtick, ), end).
|
|
*
|
|
* The transform is exported as a pure function so it can be unit-tested directly
|
|
* (see tests/bug-2543-gsd-slash-namespace.test.cjs) without needing fixture files.
|
|
*/
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const COMMANDS_DIR = path.join(__dirname, '..', 'commands', 'gsd');
|
|
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'),
|
|
path.join(__dirname, '..', 'agents'),
|
|
path.join(__dirname, '..', 'sdk', 'src'),
|
|
];
|
|
|
|
const TOP_LEVEL_FILES = [
|
|
path.join(__dirname, '..', '.clinerules'),
|
|
];
|
|
|
|
const SKIP_DIRS = new Set(['node_modules', 'dist', '.turbo']);
|
|
const EXTENSIONS = new Set(['.md', '.cjs', '.js', '.ts', '.tsx']);
|
|
|
|
// Test files contain intentional fixture strings (e.g. inputs the sanitizer
|
|
// is expected to strip). Rewriting them changes test semantics.
|
|
function isTestFile(name) {
|
|
return /\.test\.(c?js|tsx?)$/.test(name);
|
|
}
|
|
|
|
function buildPattern(cmdNames) {
|
|
// Empty input would compile `/gsd:()(?=[^a-zA-Z0-9_-]|$)/g`, which the regex
|
|
// engine still matches at any `/gsd:` token followed by a non-word boundary
|
|
// (e.g. EOL, whitespace, punctuation) — rewriting it to a stray `/gsd-`.
|
|
// Short-circuit so the caller can no-op on a missing/empty registry rather
|
|
// than perform an unintended broad rewrite.
|
|
if (!Array.isArray(cmdNames) || cmdNames.length === 0) return null;
|
|
const sorted = [...cmdNames].sort((a, b) => b.length - a.length); // longest first to avoid partial matches
|
|
return new RegExp(`/gsd:(${sorted.join('|')})(?=[^a-zA-Z0-9_-]|$)`, 'g');
|
|
}
|
|
|
|
/**
|
|
* Pure transform: rewrite retired `/gsd:<cmd>` to `/gsd-<cmd>` for the given command names.
|
|
* Returns the rewritten string. Identifiers not in `cmdNames` (e.g. `/gsd:sdk`,
|
|
* `/gsd:tools`) are left untouched.
|
|
*/
|
|
function transformContent(src, cmdNames) {
|
|
const pattern = buildPattern(cmdNames);
|
|
if (!pattern) return src;
|
|
return src.replace(pattern, (_, cmd) => `/gsd-${cmd}`);
|
|
}
|
|
|
|
function readCmdNames() {
|
|
return fs.readdirSync(COMMANDS_DIR)
|
|
.filter(f => f.endsWith('.md'))
|
|
.map(f => f.replace(/\.md$/, ''));
|
|
}
|
|
|
|
function processFile(file, cmdNames) {
|
|
const pattern = buildPattern(cmdNames);
|
|
if (!pattern) return;
|
|
let src;
|
|
try { src = fs.readFileSync(file, 'utf-8'); } catch { return; }
|
|
const replaced = transformContent(src, cmdNames);
|
|
if (replaced !== src) {
|
|
fs.writeFileSync(file, replaced, 'utf-8');
|
|
const count = (src.match(pattern) || []).length;
|
|
console.log(` ${count} replacements: ${path.relative(path.join(__dirname, '..'), file)}`);
|
|
}
|
|
}
|
|
|
|
function processDir(dir, cmdNames) {
|
|
const pattern = buildPattern(cmdNames);
|
|
if (!pattern) return;
|
|
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()) {
|
|
if (SKIP_DIRS.has(e.name)) continue;
|
|
processDir(full, cmdNames);
|
|
} else if (EXTENSIONS.has(path.extname(e.name)) && !isTestFile(e.name)) {
|
|
processFile(full, cmdNames);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
const cmdNames = readCmdNames();
|
|
for (const dir of SEARCH_DIRS) {
|
|
processDir(dir, cmdNames);
|
|
}
|
|
for (const file of TOP_LEVEL_FILES) {
|
|
processFile(file, cmdNames);
|
|
}
|
|
console.log('Done.');
|
|
}
|
|
|
|
module.exports = { transformContent, buildPattern, SKIP_DIRS };
|