Skip to content
Merged
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
23 changes: 22 additions & 1 deletion src/core/command-generation/adapters/pi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -27,6 +41,10 @@ function escapeYamlValue(value: string): string {
* Pi adapter for prompt template generation.
* File path: .pi/prompts/opsx-<id>.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',
Expand All @@ -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)}
`;
},
};
4 changes: 2 additions & 2 deletions src/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/core/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down
22 changes: 22 additions & 0 deletions test/core/command-generation/adapters.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions test/core/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ Old instructions content
expect(exists).toBe(false);
}
});

});

describe('multi-tool support', () => {
Expand Down
Loading