Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/core/command-generation/adapters/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os from 'os';
import path from 'path';
import type { CommandContent, ToolCommandAdapter } from '../types.js';
import { transformCodexRuntimeToolReferences } from '../../../utils/command-references.js';

/**
* Returns the Codex home directory.
Expand All @@ -33,12 +34,14 @@ export const codexAdapter: ToolCommandAdapter = {
},

formatFile(content: CommandContent): string {
const transformedBody = transformCodexRuntimeToolReferences(content.body);

return `---
description: ${content.description}
argument-hint: command arguments
---

${content.body}
${transformedBody}
`;
},
};
14 changes: 11 additions & 3 deletions src/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import ora from 'ora';
import * as fs from 'fs';
import { createRequire } from 'module';
import { FileSystemUtils } from '../utils/file-system.js';
import { transformToHyphenCommands } from '../utils/command-references.js';
import {
transformCodexRuntimeToolReferences,
transformToHyphenCommands,
} from '../utils/command-references.js';
import {
AI_TOOLS,
OPENSPEC_DIR_NAME,
Expand Down Expand Up @@ -537,8 +540,13 @@ export class InitCommand {
const skillFile = path.join(skillDir, 'SKILL.md');

// Generate SKILL.md content with YAML frontmatter including generatedBy
// Use hyphen-based command references for tools where filename = command name
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
// Apply tool-specific rewrites for generated skill content.
const transformer =
tool.value === 'codex'
? transformCodexRuntimeToolReferences
: tool.value === 'opencode' || tool.value === 'pi'
? transformToHyphenCommands
: undefined;
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);

// Write the skill file
Expand Down
23 changes: 18 additions & 5 deletions src/core/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import ora from 'ora';
import * as fs from 'fs';
import { createRequire } from 'module';
import { FileSystemUtils } from '../utils/file-system.js';
import { transformToHyphenCommands } from '../utils/command-references.js';
import {
transformCodexRuntimeToolReferences,
transformToHyphenCommands,
} from '../utils/command-references.js';
import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
import {
generateCommands,
Expand Down Expand Up @@ -196,8 +199,13 @@ export class UpdateCommand {
const skillDir = path.join(skillsDir, dirName);
const skillFile = path.join(skillDir, 'SKILL.md');

// Use hyphen-based command references for OpenCode
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
// Apply tool-specific rewrites for generated skill content.
const transformer =
tool.value === 'codex'
? transformCodexRuntimeToolReferences
: tool.value === 'opencode' || tool.value === 'pi'
? transformToHyphenCommands
: undefined;
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
await FileSystemUtils.writeFile(skillFile, skillContent);
}
Expand Down Expand Up @@ -690,8 +698,13 @@ export class UpdateCommand {
const skillDir = path.join(skillsDir, dirName);
const skillFile = path.join(skillDir, 'SKILL.md');

// Use hyphen-based command references for OpenCode
const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined;
// Apply tool-specific rewrites for generated skill content.
const transformer =
tool.value === 'codex'
? transformCodexRuntimeToolReferences
: tool.value === 'opencode' || tool.value === 'pi'
? transformToHyphenCommands
: undefined;
const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer);
await FileSystemUtils.writeFile(skillFile, skillContent);
}
Expand Down
17 changes: 14 additions & 3 deletions src/core/workspace/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import * as nodeFs from 'node:fs';
import { createRequire } from 'node:module';

import { FileSystemUtils } from '../../utils/file-system.js';
import { transformToHyphenCommands } from '../../utils/command-references.js';
import {
transformCodexRuntimeToolReferences,
transformToHyphenCommands,
} from '../../utils/command-references.js';
import { AI_TOOLS, type AIToolOption } from '../config.js';
import { getGlobalConfig, type Delivery, type Profile } from '../global-config.js';
import { getProfileWorkflows } from '../profiles.js';
Expand Down Expand Up @@ -354,7 +357,11 @@ export async function generateWorkspaceAgentSkills(
try {
const skillsDir = getWorkspaceSkillDirectoryForTool(workspaceRoot, tool);
const transformer =
tool.value === 'opencode' || tool.value === 'pi' ? transformToHyphenCommands : undefined;
tool.value === 'codex'
? transformCodexRuntimeToolReferences
: tool.value === 'opencode' || tool.value === 'pi'
? transformToHyphenCommands
: undefined;

for (const { template, dirName } of skillTemplates) {
const skillFile = FileSystemUtils.joinPath(skillsDir, dirName, 'SKILL.md');
Expand Down Expand Up @@ -466,7 +473,11 @@ export async function updateWorkspaceAgentSkills(
try {
const skillsDir = getWorkspaceSkillDirectoryForTool(workspaceRoot, tool);
const transformer =
tool.value === 'opencode' || tool.value === 'pi' ? transformToHyphenCommands : undefined;
tool.value === 'codex'
? transformCodexRuntimeToolReferences
: tool.value === 'opencode' || tool.value === 'pi'
? transformToHyphenCommands
: undefined;

for (const { template, dirName } of skillTemplates) {
const skillFile = FileSystemUtils.joinPath(skillsDir, dirName, 'SKILL.md');
Expand Down
60 changes: 60 additions & 0 deletions src/utils/command-references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,63 @@
export function transformToHyphenCommands(text: string): string {
return text.replace(/\/opsx:/g, '/opsx-');
}

/**
* Rewrites generated workflow instructions that name Claude-style tools into
* runtime-neutral instructions Codex can follow without attempting missing tools.
*/
export function transformCodexRuntimeToolReferences(text: string): string {
return text
.replace(
/Use the \*\*AskUserQuestion tool\*\* \(open-ended, no preset options\) to ask:/g,
'Ask the user in chat and wait for their reply:'
)
.replace(
/Use \*\*AskUserQuestion tool\*\* with multi-select to let user choose changes:/g,
'Ask the user in chat to choose one or more changes from a concise numbered list:'
)
.replace(
/Use \*\*AskUserQuestion tool\*\* with a single confirmation:/g,
'Ask the user in chat for a single confirmation:'
)
.replace(
/Use the \*\*TodoWrite tool\*\* to track progress through the artifacts\./g,
"Track progress using Codex's native plan/checklist mechanism if available; otherwise keep a concise visible checklist."
)
.replace(
/[Uu]se \*\*AskUserQuestion tool\*\*/g,
(match) =>
match.startsWith('Use')
? 'Ask the user in chat and wait for their reply'
: 'ask the user in chat and wait for their reply'
)
.replace(
/[Uu]se the \*\*AskUserQuestion tool\*\*/g,
(match) =>
match.startsWith('Use')
? 'Ask the user in chat and wait for their reply'
: 'ask the user in chat and wait for their reply'
)
.replace(
/[Uu]se \*\*TodoWrite tool\*\*/g,
(match) =>
match.startsWith('Use')
? "Use Codex's native plan/checklist mechanism if available"
: "use Codex's native plan/checklist mechanism if available"
)
.replace(
/[Uu]se the \*\*TodoWrite tool\*\*/g,
(match) =>
match.startsWith('Use')
? "Use Codex's native plan/checklist mechanism if available"
: "use Codex's native plan/checklist mechanism if available"
)
.replace(
/use Task tool \(subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'\. Delta spec analysis: <include the analyzed delta spec summary>"\)/g,
"ask the agent to invoke openspec-sync-specs for change '<name>' with the analyzed delta spec summary"
)
.replace(
/Use Task tool \(subagent_type: "general-purpose", prompt: "([^"]+)"\)/g,
'Use the agent native delegation or skill mechanism if available; otherwise perform this step directly: $1'
);
}
13 changes: 13 additions & 0 deletions test/core/command-generation/adapters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,19 @@ describe('command-generation/adapters', () => {
expect(output).toContain('---\n\n');
expect(output).toContain('This is the command body.');
});

it('should replace unsupported runtime tool references', () => {
const output = codexAdapter.formatFile({
...sampleContent,
body: 'Use the **AskUserQuestion tool** to clarify.\nUse the **TodoWrite tool** to track progress.\nUse Task tool (subagent_type: "general-purpose", prompt: "sync").',
});

expect(output).not.toContain('AskUserQuestion');
expect(output).not.toContain('TodoWrite');
expect(output).not.toContain('Task tool');
expect(output).not.toContain('subagent_type');
expect(output).toContain('Ask the user in chat and wait for their reply');
});
});

describe('codebuddyAdapter', () => {
Expand Down
23 changes: 22 additions & 1 deletion test/utils/command-references.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, it, expect } from 'vitest';
import { transformToHyphenCommands } from '../../src/utils/command-references.js';
import {
transformCodexRuntimeToolReferences,
transformToHyphenCommands,
} from '../../src/utils/command-references.js';

describe('transformToHyphenCommands', () => {
describe('basic transformations', () => {
Expand Down Expand Up @@ -81,3 +84,21 @@ Finally /opsx-apply to implement`;
}
});
});

describe('transformCodexRuntimeToolReferences', () => {
it('should replace Claude-style tool references with Codex-safe instructions', () => {
const input = `Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
Use the **TodoWrite tool** to track progress through the artifacts.
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.`;

const output = transformCodexRuntimeToolReferences(input);

expect(output).toContain('Ask the user in chat and wait for their reply:');
expect(output).toContain("Track progress using Codex's native plan/checklist mechanism");
expect(output).toContain('invoke openspec-sync-specs');
expect(output).not.toContain('AskUserQuestion');
expect(output).not.toContain('TodoWrite');
expect(output).not.toContain('Task tool');
expect(output).not.toContain('subagent_type');
});
});