feat(pi): add Pi Coding Agent as a generation target#1553
Conversation
…ands, and skills support
There was a problem hiding this comment.
Pull request overview
Adds Pi Coding Agent as a new Rulesync generation/import target (pi), wiring it into the rules/commands/skills processors with global-mode support and updating docs/tests accordingly.
Changes:
- Register new
pitool target across types and feature processors (rules/commands/skills), including global support and TOON rule discovery. - Implement Pi-specific generators/parsers:
PiRule,PiCommand(round-tripsargument-hintviapi:frontmatter namespace), andPiSkill(Agent Skills-style layout under.pi/). - Expand unit + E2E coverage and update documentation + gitignore registry for Pi outputs.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types/tool-targets.ts | Adds pi to the canonical tool target list. |
| src/types/tool-targets.test.ts | Updates tool target snapshot/expectations to include pi. |
| src/features/skills/skills-processor.ts | Registers PiSkill in the skills processor factory map. |
| src/features/skills/skills-processor.test.ts | Updates skills processor target assertions to include pi. |
| src/features/skills/pi-skill.ts | Introduces Pi skill directory implementation targeting .pi/skills (+ global). |
| src/features/skills/pi-skill.test.ts | Adds unit tests for Pi skill pathing, parsing, and conversions. |
| src/features/rules/rules-processor.ts | Registers PiRule with TOON discovery and Pi command/skill conventions. |
| src/features/rules/rules-processor.test.ts | Updates rules processor target assertions and global target counts for pi. |
| src/features/rules/pi-rule.ts | Introduces Pi rule implementation writing AGENTS.md + .agents/memories. |
| src/features/rules/pi-rule.test.ts | Adds unit tests for Pi rule paths, loading, conversion, and global behavior. |
| src/features/commands/pi-command.ts | Introduces Pi command implementation for .pi/prompts (+ global), with argument-hint round-trip. |
| src/features/commands/pi-command.test.ts | Adds unit tests for Pi command parsing, conversion, and round-trip behavior. |
| src/features/commands/commands-processor.ts | Registers PiCommand in the commands processor factory map. |
| src/features/commands/commands-processor.test.ts | Updates commands processor target assertions to include pi. |
| src/e2e/e2e-skills.spec.ts | Adds E2E rows for Pi skills generate/import/global/orphan-delete. |
| src/e2e/e2e-rules.spec.ts | Adds E2E rows for Pi rules generate/import/global. |
| src/e2e/e2e-commands.spec.ts | Adds E2E rows for Pi commands generate/import/global/orphan-delete. |
| src/cli/commands/gitignore-entries.ts | Adds Pi-specific gitignore entries and shares AGENTS.md ignore with agentsmd. |
| skills/rulesync/supported-tools.md | Documents Pi support in the synced supported-tools table. |
| docs/reference/supported-tools.md | Documents Pi support in the reference supported-tools table. |
| README.md | Documents Pi support in the main supported-tools table. |
| ), | ||
| }; | ||
| } | ||
|
|
There was a problem hiding this comment.
Pi skills (Agent Skills standard) typically require the skill directory name to match the frontmatter name. PiSkill.validate() currently only checks schema shape, so it will treat mismatched dirName/frontmatter.name as valid and may generate a layout Pi rejects. Consider adding a validation step that compares parsed name to this.getDirName() (similar to ClineSkill.validate() / RooSkill.validate()).
| const dirName = this.getDirName(); | |
| if (result.data.name !== dirName) { | |
| return { | |
| success: false, | |
| error: new Error( | |
| `Invalid frontmatter in ${this.getDirPath()}: frontmatter name "${result.data.name}" must match directory name "${dirName}"`, | |
| ), | |
| }; | |
| } |
| static fromRulesyncSkill({ | ||
| baseDir = process.cwd(), | ||
| rulesyncSkill, | ||
| validate = true, | ||
| global = false, | ||
| }: ToolSkillFromRulesyncSkillParams): PiSkill { | ||
| const settablePaths = PiSkill.getSettablePaths({ global }); | ||
| const rulesyncFrontmatter = rulesyncSkill.getFrontmatter(); | ||
|
|
||
| const piFrontmatter: PiSkillFrontmatter = { | ||
| name: rulesyncFrontmatter.name, | ||
| description: rulesyncFrontmatter.description, | ||
| }; | ||
|
|
||
| return new PiSkill({ | ||
| baseDir, | ||
| relativeDirPath: settablePaths.relativeDirPath, | ||
| dirName: rulesyncSkill.getDirName(), | ||
| frontmatter: piFrontmatter, | ||
| body: rulesyncSkill.getBody(), | ||
| otherFiles: rulesyncSkill.getOtherFiles(), | ||
| validate, | ||
| global, | ||
| }); |
There was a problem hiding this comment.
PiSkill.fromRulesyncSkill() uses dirName: rulesyncSkill.getDirName() while setting frontmatter.name from the rulesync frontmatter. If those differ, this will emit an on-disk skill directory whose name doesn’t match the skill name, which is invalid for several skill targets and (per Pi’s skill format) can break discovery. Consider using the (validated) frontmatter name as dirName (and/or failing fast when they differ).
…: command namespace Address mid-severity review findings on PR #1553: - Remove additionalConventions for pi in rules-processor (dead config; pi is not simulated so the block never renders, matching opencode). - Document the pi: command frontmatter namespace (argument-hint) in docs/reference/file-formats.md and sync to skills/rulesync/.
| const paths = this.getSettablePaths({ global }); | ||
| const isRoot = relativeFilePath === paths.root.relativeFilePath; | ||
|
|
||
| if (isRoot) { | ||
| const fileContent = await readFileContent( | ||
| join(baseDir, paths.root.relativeDirPath, paths.root.relativeFilePath), | ||
| ); |
There was a problem hiding this comment.
PiRule.fromFile determines isRoot solely by relativeFilePath === "AGENTS.md". When importing non-root rules, RulesProcessor passes relativeFilePath relative to the .agents/memories directory; a memory file named AGENTS.md would be misclassified as root and the wrong file would be read (project-root AGENTS.md instead of the memory file). Consider incorporating relativeDirPath into the root check (e.g., require it to match paths.root.relativeDirPath) or otherwise disambiguate root vs non-root based on both dir and filename.
| const paths = this.getSettablePaths({ global }); | ||
| const isRoot = relativeFilePath === paths.root.relativeFilePath; | ||
|
|
||
| return new PiRule({ | ||
| baseDir, | ||
| relativeDirPath, | ||
| relativeFilePath, | ||
| fileContent: "", | ||
| validate: false, | ||
| root: isRoot, | ||
| }); |
There was a problem hiding this comment.
PiRule.forDeletion sets root based only on relativeFilePath === "AGENTS.md". If a non-root memory file under .agents/memories/ happens to be named AGENTS.md, it will be treated as a root rule, which can lead to incorrect behavior when the rule is later converted/handled as a root. Align this with the AgentsMdRule pattern by considering both relativeDirPath and relativeFilePath when deciding isRoot (and account for global vs project roots).
| const fileContent = stringifyFrontmatter(this.body, rulesyncFrontmatter); | ||
|
|
||
| return new RulesyncCommand({ | ||
| baseDir: ".", | ||
| frontmatter: rulesyncFrontmatter, | ||
| body: this.body, | ||
| relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath, | ||
| relativeFilePath: this.relativeFilePath, | ||
| fileContent, | ||
| validate: true, | ||
| }); |
There was a problem hiding this comment.
PiCommand.toRulesyncCommand hardcodes baseDir: ".". During import, the CommandsProcessor can be constructed with a baseDir that is not the current working directory (e.g., when importing for a configured baseDir), and FeatureProcessor.writeAiFiles uses the RulesyncCommand baseDir to compute output paths. Using "." here can write imported rulesync files to the wrong location. Prefer using this.baseDir (or process.cwd() consistently) so the produced RulesyncCommand writes under the same baseDir it was loaded from.
Summary
Adds Pi Coding Agent (
@mariozechner/pi-coding-agent) as a new generation targetpiwith native support for Rules, Commands, and Skills in both project and global scopes. MCP, Subagents, and Ignore features are intentionally not registered, matching Pi's upstream design that rejects those as framework features.Closes #1552.
Feature × scope matrix
AGENTS.md(root) +.agents/memories/*.md(TOON refs)~/.pi/agent/AGENTS.md.pi/prompts/*.md~/.pi/agent/prompts/*.md.pi/skills/<name>/SKILL.md~/.pi/agent/skills/<name>/SKILL.mdKey changes
PiRule(mirrorsagentsmd-rulewithruleDiscoveryMode: "toon"),PiCommand,PiSkill(reuses the Agent Skills standard).PiCommandround-tripsargument-hintthrough api:namespace in rulesync frontmatter.rules-processor.ts,commands-processor.ts,skills-processor.tswithsupportsGlobal: true. Not registered inmcp-processor.ts,subagents-processor.ts, orignore-processor.ts.gitignore-entries.tsgains**/.agents/memories/,**/.pi/prompts/,**/.pi/skills/, and shares**/AGENTS.mdwithagentsmd.PiRule,PiCommand,PiSkill.pi× (rules, commands, skills) × (project generate, import, global, orphan-delete).README.md,docs/reference/supported-tools.md, and the syncedskills/rulesync/supported-tools.mdlist Pi.Test plan
pnpm cicheck:code— formatter, oxlint, eslint, typecheck, 5204 unit tests passpnpm cicheck:content— skill-docs sync, cspell, secretlint passpnpm test:e2e— 346/346 pass (includes newpirows)rulesync generate --targets piproduces expected project layoutrulesync generate --targets pi --globalproduces expected~/.pi/agent/layoutrulesync import --targets piround-trips back to.rulesync/