From d0ab1d8aaa493f88096c73bf85c74026e6d1170f Mon Sep 17 00:00:00 2001 From: Lakshman Turlapati <83905676+LakshmanTurlapati@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:49:58 -0500 Subject: [PATCH] fix(codex): convert /gsd- workflow commands to $gsd- during installation (#1994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The convertSlashCommandsToCodexSkillMentions function only converted colon-style skill invocations (/gsd:command) but not hyphen-style command references (/gsd-command) used in workflow output templates (Next Up blocks, phase completion messages, etc.). This caused Codex users to see /gsd- prefixed commands instead of $gsd- in chat output. - Add regex to convert /gsd-command → $gsd-command with negative lookbehind to exclude file paths (e.g. bin/gsd-tools.cjs) - Strip /clear references in Codex output (no Codex equivalent) - Add 5 regression tests covering command conversion, path preservation, and /clear removal Co-authored-by: Lakshman Co-authored-by: Claude Opus 4.6 (1M context) --- bin/install.js | 15 ++++++- tests/codex-config.test.cjs | 81 +++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) 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', () => {