fix(3583): normalize retired colon-form commands in generated Claude/Qwen/Hermes SKILL.md bodies (#3629)
* Add first-class grok runtime support (maps to ~/.agents); wire installer, runtime-homes.cjs and sync-skills; update Grok Build engine in local ~/.agents to latest; record session progress in discussion doc * Normalize gsd colon references to hyphen in generated Claude SKILL.md bodies using the shared transformer. Fixes #3583. * Refine #3583 implementation after review: cache command names, improve tests, clean up comments * Harden gsd colon-to-hyphen transformer with bidirectional word boundaries and body-only regression guard * Track quick-wins batch status and local session notes for #3583/#3579 handoff * Port installer robustness (hoist copyLibDir + selective Codex hooks) from 3579 to make Codex tests pass on this branch. Fixes ReferenceError and prevents extra hook pollution in Codex installs. * Restore #3583 transformer wiring and Codex .sh GSD_VERSION branch lost in 50ff8f17 port Commit 50ff8f17 ('Port installer robustness from #3579') accidentally reverted: - the top-level require of transformContentToHyphen/readGsdCommandNames - the body normalization inside convertClaudeCommandToClaudeSkill - the Codex hook loop's .sh branch with {{GSD_VERSION}} substitution These were the actual #3583 fix and the Codex half of the #2136 invariant. Failing tests fixed: bug-2808-skill-hyphen-name, claude-skills-migration #3583 case, bug-2136 Codex .sh substitution. * Exempt 'sync-skills' slug from docs-parity check (skill dir name in path references) gsd-sync-skills is an installed Claude skill name and a workflow file but not a registered slash command. The docs-parity regex catches /gsd-sync-skills from filesystem path references like ~/.agents/skills/gsd-sync-skills/ in docs/discussions/grok-build-support-2026-05.md. Adding to INTERNAL_COMPONENT_SLUGS matches the existing exemption pattern for 'statusline', 'workspaces', 'graphify-update', etc. * Restrict hooks/lib/ install to hook-enabled runtimes and managed allowlist Codex/Copilot/Cursor/Windsurf/Trae/Cline already skip the hooks block but were still copying hooks/lib/ helpers, contradicting the downstream Codex comment. Gate the call on the same runtime check and pass GSD_HOOK_LIB_FILES so install scope matches the uninstall/manifest scope.
This commit is contained in:
@@ -1,10 +1,18 @@
|
||||
'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).
|
||||
* One-shot script + library: bidirectional GSD slash-command namespace normalizer.
|
||||
*
|
||||
* 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.
|
||||
* - Default direction (transformContent): retired /gsd-<cmd> → /gsd:<cmd>
|
||||
* (keeps monorepo sources, docs, and workflows in the active colon form).
|
||||
* - Reverse direction (transformContentToHyphen): /gsd:<cmd> / gsd:<cmd> → gsd-<cmd>
|
||||
* (used during skill installation for runtimes that register skills under the
|
||||
* canonical hyphen form established in #2808).
|
||||
*
|
||||
* Both directions only rewrite known commands from `commands/gsd/*.md` (longest-first
|
||||
* matching + word-boundary safety). Non-commands (gsd-sdk, gsd-tools, etc.) are
|
||||
* intentionally left untouched.
|
||||
*
|
||||
* The transforms are pure and exported for use by the installer and tests.
|
||||
*/
|
||||
|
||||
const fs = require('node:fs');
|
||||
@@ -57,6 +65,32 @@ function transformContent(src, cmdNames) {
|
||||
return src.replace(pattern, (_, cmd) => `/gsd:${cmd}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build regex for the reverse direction (colon form → hyphen form).
|
||||
* Matches both "gsd:cmd" and "/gsd:cmd" (the leading / is preserved automatically
|
||||
* because it is not part of the match). Uses longest-first ordering plus
|
||||
* bidirectional word-boundary safety (negative lookbehind on the left, lookahead
|
||||
* on the right) so matches only occur at token boundaries.
|
||||
*/
|
||||
function buildColonPattern(cmdNames) {
|
||||
if (!Array.isArray(cmdNames) || cmdNames.length === 0) return null;
|
||||
const sorted = [...cmdNames].sort((a, b) => b.length - a.length);
|
||||
return new RegExp(`(?<![a-zA-Z0-9_-])gsd:(${sorted.join('|')})(?=[^a-zA-Z0-9_-]|$)`, 'g');
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure transform (reverse): rewrite `/gsd:<cmd>` / `gsd:<cmd>` to hyphen form
|
||||
* for known GSD commands.
|
||||
*
|
||||
* Non-command identifiers (e.g. gsd-sdk, gsd-tools) are left untouched, matching
|
||||
* the safety contract of the forward transform.
|
||||
*/
|
||||
function transformContentToHyphen(src, cmdNames) {
|
||||
const pattern = buildColonPattern(cmdNames);
|
||||
if (!pattern) return src;
|
||||
return src.replace(pattern, (_, cmd) => `gsd-${cmd}`);
|
||||
}
|
||||
|
||||
function readCmdNames() {
|
||||
return fs.readdirSync(COMMANDS_DIR)
|
||||
.filter(f => f.endsWith('.md'))
|
||||
@@ -103,4 +137,11 @@ if (require.main === module) {
|
||||
console.log('Done.');
|
||||
}
|
||||
|
||||
module.exports = { transformContent, buildPattern, SKIP_DIRS };
|
||||
module.exports = {
|
||||
transformContent,
|
||||
transformContentToHyphen,
|
||||
buildPattern,
|
||||
buildColonPattern,
|
||||
readCmdNames,
|
||||
SKIP_DIRS
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user