fix(3562): generate Codex skill surface; $gsd-* commands discoverable after install

Closes #3562

Codex CLI 0.130.0 only registers commands from skills/<name>/SKILL.md; it
does NOT auto-discover from get-shit-done/workflows/*.md or agents/*.md.
Prior installer logic (#3427/#3433) removed the gsd-* skill copies under the
assumption that Codex would discover the official skills directly. That
assumption does not hold — users ended up with workflows on disk and zero
$gsd-* entrypoints after restart.

Fix: re-wire copyCommandsAsCodexSkills() (line 5519, already present) into
the Codex install dispatch path (line 8090). Generates one
~/.codex/skills/gsd-<name>/SKILL.md per commands/gsd/*.md — same shape the
Copilot/Antigravity/Cursor/Windsurf/Augment/Trae installs use.

Behaviour change: the pre-existing test in bug-3427-3433-codex-install-shape
asserted "does not regenerate gsd-* skill copies". Updated it to assert
the new behaviour (regenerate gsd-* with refreshed body, preserve non-gsd
user skills).

Tests:
- New tests/bug-3562-codex-install-skill-surface.test.cjs (4 cases):
  - skills/gsd-help/SKILL.md exists
  - SKILL.md has YAML frontmatter with name: gsd-help
  - >= 10 gsd-* skill directories produced (lower-bound, currently 67)
  - Pre-existing custom-user-skill directory preserved
- tests/bug-3427-3433-codex-install-shape.test.cjs: updated to assert
  regeneration + body refresh + unrelated-skill preservation.

Verified by re-running the issue's repro: `node bin/install.js --codex
--global --config-dir <tmp>` now produces 67 gsd-* skills and the target
~/.codex/skills/gsd-help/SKILL.md exists with valid frontmatter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-15 13:31:31 -04:00
parent 1a4e6df6b3
commit 365340596d
4 changed files with 163 additions and 15 deletions

View File

@@ -8088,23 +8088,27 @@ function install(isGlobal, runtime = 'claude', options = {}) {
failures.push('command/gsd-*');
}
} else if (isCodex) {
// Codex CLI (0.130.0 at time of #3562) does NOT auto-discover commands
// from get-shit-done/workflows/*.md or agents/*.md. It only registers
// commands from skills/<name>/SKILL.md. The earlier "Codex discovers
// official skills directly" branch left users with workflows on disk and
// no $gsd-* entrypoints. Regenerate the skill surface the same way the
// other runtimes do — copyCommandsAsCodexSkills() rewrites each
// commands/gsd/*.md as ~/.codex/skills/gsd-<name>/SKILL.md and converts
// Claude-flavored command frontmatter into Codex skill frontmatter.
const skillsDir = path.join(targetDir, 'skills');
// Codex now discovers repo/user/admin/system skills from .agents/skills and
// warns if a layer mixes redundant hook/skill representations. Legacy
// gsd-* copies under ~/.codex/skills are therefore removed and no longer
// regenerated.
let removedLegacyCodexSkills = 0;
const gsdSrc = _stageSkills(_commandsDir);
copyCommandsAsCodexSkills(gsdSrc, skillsDir, 'gsd', pathPrefix, runtime);
if (fs.existsSync(skillsDir)) {
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue;
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
removedLegacyCodexSkills += 1;
const count = fs.readdirSync(skillsDir, { withFileTypes: true })
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length;
if (count > 0) {
console.log(` ${green}✓${reset} Installed ${count} skills to skills/`);
} else {
failures.push('skills/gsd-*');
}
}
if (removedLegacyCodexSkills > 0) {
console.log(` ${green}✓${reset} Removed ${removedLegacyCodexSkills} legacy Codex gsd-* skill copies from skills/`);
} else {
console.log(` ${dim}↳${reset} Skipped Codex skill-copy generation (Codex discovers official skills directly)`);
failures.push('skills/gsd-*');
}
} else if (isCopilot) {
const skillsDir = path.join(targetDir, 'skills');