diff --git a/src/core/command-generation/adapters/pi.ts b/src/core/command-generation/adapters/pi.ts index cb8d2b331..fa11d9d8e 100644 --- a/src/core/command-generation/adapters/pi.ts +++ b/src/core/command-generation/adapters/pi.ts @@ -7,6 +7,20 @@ import path from 'path'; import type { CommandContent, ToolCommandAdapter } from '../types.js'; +import { transformToHyphenCommands } from '../../../utils/command-references.js'; + +const PI_INPUT_HEADING = /^\*\*Input\*\*:[^\n]*$/m; + +function injectPiArgs(body: string): string { + if (body.includes('$@') || body.includes('$ARGUMENTS')) { + return body; + } + + return body.replace( + PI_INPUT_HEADING, + (heading) => `${heading}\n**Provided arguments**: $@` + ); +} /** * Escapes a string value for safe YAML output. @@ -27,6 +41,10 @@ function escapeYamlValue(value: string): string { * Pi adapter for prompt template generation. * File path: .pi/prompts/opsx-.md * Frontmatter: description + * + * Pi uses the filename (minus .md) as the slash command name, so + * opsx-propose.md → /opsx-propose. Command references in the body + * are transformed from /opsx: to /opsx- for consistency. */ export const piAdapter: ToolCommandAdapter = { toolId: 'pi', @@ -36,11 +54,14 @@ export const piAdapter: ToolCommandAdapter = { }, formatFile(content: CommandContent): string { + // Transform /opsx: references to /opsx- and inject $@ for template args + const transformedBody = transformToHyphenCommands(content.body); + return `--- description: ${escapeYamlValue(content.description)} --- -${content.body} +${injectPiArgs(transformedBody)} `; }, }; diff --git a/src/core/init.ts b/src/core/init.ts index 95728dc7e..aa38408f2 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -537,8 +537,8 @@ 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 OpenCode - const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined; + // Use hyphen-based command references for tools where filename = command name + const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined; const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer); // Write the skill file diff --git a/src/core/update.ts b/src/core/update.ts index 62db8a08f..de922a5ff 100644 --- a/src/core/update.ts +++ b/src/core/update.ts @@ -195,7 +195,7 @@ export class UpdateCommand { const skillFile = path.join(skillDir, 'SKILL.md'); // Use hyphen-based command references for OpenCode - const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined; + const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined; const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer); await FileSystemUtils.writeFile(skillFile, skillContent); } @@ -666,7 +666,7 @@ export class UpdateCommand { const skillFile = path.join(skillDir, 'SKILL.md'); // Use hyphen-based command references for OpenCode - const transformer = tool.value === 'opencode' ? transformToHyphenCommands : undefined; + const transformer = (tool.value === 'opencode' || tool.value === 'pi') ? transformToHyphenCommands : undefined; const skillContent = generateSkillContent(template, OPENSPEC_VERSION, transformer); await FileSystemUtils.writeFile(skillFile, skillContent); } diff --git a/test/core/command-generation/adapters.test.ts b/test/core/command-generation/adapters.test.ts index dab19bf3d..1f502a34c 100644 --- a/test/core/command-generation/adapters.test.ts +++ b/test/core/command-generation/adapters.test.ts @@ -547,6 +547,28 @@ describe('command-generation/adapters', () => { expect(output).toContain('This is the command body.'); }); + it('should transform command references from colon to hyphen format', () => { + const contentWithRefs: CommandContent = { + ...sampleContent, + body: 'Run /opsx:apply to implement. Then /opsx:archive when done.', + }; + + const output = piAdapter.formatFile(contentWithRefs); + expect(output).toContain('/opsx-apply'); + expect(output).toContain('/opsx-archive'); + expect(output).not.toContain('/opsx:apply'); + }); + + it('should inject template arguments into the input section', () => { + const contentWithInput: CommandContent = { + ...sampleContent, + body: '**Input**: The argument after `/opsx:explore` is the topic.\n\n**Steps**\n1. Think.', + }; + + const output = piAdapter.formatFile(contentWithInput); + expect(output).toContain('**Provided arguments**: $@'); + }); + it('should escape YAML special characters in description', () => { const contentWithSpecialChars: CommandContent = { ...sampleContent, diff --git a/test/core/update.test.ts b/test/core/update.test.ts index f3d393d93..6eeae843f 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -250,6 +250,7 @@ Old instructions content expect(exists).toBe(false); } }); + }); describe('multi-tool support', () => {