diff --git a/bin/install.js b/bin/install.js index 076fd8c5b..80ad67354 100755 --- a/bin/install.js +++ b/bin/install.js @@ -1545,16 +1545,29 @@ function convertClaudeAgentToCodebuddyAgent(content) { } function convertSlashCommandsToCodexSkillMentions(content) { + // Convert colon-style skill invocations to Codex $ prefix let converted = content.replace(/\/gsd:([a-z0-9-]+)/gi, (_, commandName) => { return `$gsd-${String(commandName).toLowerCase()}`; }); - converted = converted.replace(/\/gsd-help\b/g, '$gsd-help'); + // Convert hyphen-style command references (workflow output) to Codex $ prefix. + // Negative lookbehind excludes file paths like bin/gsd-tools.cjs where + // the slash is preceded by a word char, dot, or another slash. + converted = converted.replace(/(? { + return `$gsd-${String(commandName).toLowerCase()}`; + }); return converted; } function convertClaudeToCodexMarkdown(content) { let converted = convertSlashCommandsToCodexSkillMentions(content); converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}'); + // Remove /clear references — Codex has no equivalent command + // Handle backtick-wrapped: `\/clear` then: → (removed) + converted = converted.replace(/`\/clear`\s*,?\s*then:?\s*\n?/gi, ''); + // Handle bare: /clear then: → (removed) + converted = converted.replace(/\/clear\s*,?\s*then:?\s*\n?/gi, ''); + // Handle standalone /clear on its own line + converted = converted.replace(/^\s*`?\/clear`?\s*$/gm, ''); // Path replacement: .claude → .codex (#1430) converted = converted.replace(/\$HOME\/\.claude\//g, '$HOME/.codex/'); converted = converted.replace(/~\/\.claude\//g, '~/.codex/'); diff --git a/tests/codex-config.test.cjs b/tests/codex-config.test.cjs index ab95c1a94..b51037428 100644 --- a/tests/codex-config.test.cjs +++ b/tests/codex-config.test.cjs @@ -17,6 +17,7 @@ const os = require('os'); const { getCodexSkillAdapterHeader, convertClaudeAgentToCodexAgent, + convertClaudeCommandToCodexSkill, generateCodexAgentToml, generateCodexConfigBlock, stripGsdFromCodexConfig, @@ -186,6 +187,86 @@ node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" commit "docs: resolve"`; }); }); +// ─── Codex command prefix conversion ──────────────────────────────────────────── + +describe('Codex hyphen-style command prefix conversion', () => { + test('converts /gsd-command in workflow output to $gsd-command', () => { + const input = `--- +name: gsd-test +description: Test +tools: Read +--- + +/gsd-discuss-phase 1 — gather context +/gsd-plan-phase 2 — create plan +/gsd-execute-phase 3 — run it`; + + const result = convertClaudeCommandToCodexSkill(input, 'gsd-test'); + assert.ok(result.includes('$gsd-discuss-phase'), 'converts /gsd-discuss-phase'); + assert.ok(result.includes('$gsd-plan-phase'), 'converts /gsd-plan-phase'); + assert.ok(result.includes('$gsd-execute-phase'), 'converts /gsd-execute-phase'); + assert.ok(!result.includes('/gsd-discuss-phase'), 'no /gsd-discuss-phase remains'); + }); + + test('converts backtick-wrapped /gsd- commands', () => { + const input = `--- +name: gsd-test +description: Test +tools: Read +--- + +Run \`/gsd-plan-phase 1\` to plan.`; + + const result = convertClaudeCommandToCodexSkill(input, 'gsd-test'); + assert.ok(result.includes('$gsd-plan-phase'), 'converts backtick-wrapped command'); + }); + + test('does not convert /gsd- in file paths', () => { + const input = `--- +name: gsd-test +description: Test +tools: Read +--- + +node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init`; + + const result = convertClaudeCommandToCodexSkill(input, 'gsd-test'); + assert.ok(result.includes('gsd-tools.cjs'), 'gsd-tools.cjs preserved in path'); + assert.ok(!result.includes('$gsd-tools'), 'no $gsd-tools in file path'); + }); + + test('removes /clear then: for Codex', () => { + const input = `--- +name: gsd-test +description: Test +tools: Read +--- + +\`/clear\` then: + +\`$gsd-plan-phase 1\``; + + const result = convertClaudeCommandToCodexSkill(input, 'gsd-test'); + assert.ok(!result.includes('/clear'), 'no /clear remains'); + assert.ok(result.includes('$gsd-plan-phase'), 'command preserved after /clear removal'); + }); + + test('removes bare /clear then: for Codex', () => { + const input = `--- +name: gsd-test +description: Test +tools: Read +--- + +/clear then: +/gsd-execute-phase 2`; + + const result = convertClaudeCommandToCodexSkill(input, 'gsd-test'); + assert.ok(!result.includes('/clear'), 'no /clear remains'); + assert.ok(result.includes('$gsd-execute-phase'), 'command converted'); + }); +}); + // ─── generateCodexAgentToml ───────────────────────────────────────────────────── describe('generateCodexAgentToml', () => {