Skip to content

fix: normalize skill names and avoid subagent tool conflicts in pi#288

Open
dragosdm wants to merge 2 commits intoEveryInc:mainfrom
dragosdm:fix/pi-skill-name-normalization
Open

fix: normalize skill names and avoid subagent tool conflicts in pi#288
dragosdm wants to merge 2 commits intoEveryInc:mainfrom
dragosdm:fix/pi-skill-name-normalization

Conversation

@dragosdm
Copy link

Summary

Fix Pi compatibility for Compound Engineering by:

  1. normalizing Pi skill names during install and sync
  2. avoiding tool conflicts with pi-subagents by using a dedicated CE tool name

What changed

Pi skill-name normalization

  • normalize Pi skill names to lowercase hyphenated form
  • rewrite copied SKILL.md frontmatter so name: matches the normalized target directory
  • keep Pi install parity with the Compound Engineering plugin source
  • keep Pi sync parity with Claude home input while avoiding invalid Pi skill names

Examples:

  • ce:plan -> ce-plan
  • generate_command -> generate-command
  • resolve_parallel -> resolve-parallel

Pi subagent conflict avoidance

  • rename the CE Pi compat tool from subagent to ce_subagent
  • rewrite Pi-installed CE content so Task-style delegation uses ce_subagent
  • keep generic subagent available for pi-subagents
  • update Pi compatibility notes accordingly

This allows CE Pi installs to coexist with pi-subagents in the same runtime without tool-name conflicts.

Implementation notes

  • Pi-only sync behavior now lives in a dedicated Pi path instead of leaking into shared sync helpers
  • Pi prompt normalization was aligned with Pi skill/body rewrite behavior
  • Pi sync now cleanly replaces existing symlink targets when materialization is needed

Validation

Automated

Ran the full test suite locally

Result:

  • 329 pass
  • 0 fail

Manual smoke tests

Verified locally that:

  • install ./plugins/compound-engineering --to pi produces no invalid Pi skill names
  • CE workflow skills are installed with Pi-safe names
  • CE-installed Pi content references ce_subagent
  • ce_subagent works at runtime
  • subagent from pi-subagents still works at runtime
  • both tools can coexist in the same Pi session

Why this matters

Before this change, Pi installs/syncs could produce invalid skill names such as:

  • ce:plan
  • generate_command

and CE’s Pi compatibility extension conflicted with pi-subagents because both registered subagent.

After this change:

  • Pi output is valid
  • CE install/sync parity is preserved
  • CE and pi-subagents can coexist cleanly

@dragosdm dragosdm force-pushed the fix/pi-skill-name-normalization branch from 993150a to 5570e1f Compare March 17, 2026 11:29
@tmchow
Copy link
Collaborator

tmchow commented Mar 17, 2026

@kieranklaassen this is interesting suggestion because it also addresses cross platform compat with directory and skill names issues with colons not working with windows for example if we ever want folders to match skill names directly.

Using a hyphen is likely a better way to go so we don't overload the usage of what : means. E..g Claude Code already uses a colon to scope skills within a given plugin but we are using it a bit hacky in skill names themselves.

Long term probably makes sense for us to to rename the skills but not worth blocking this now so Pi can get unblocked with the fix

@tmchow tmchow requested a review from Copilot March 18, 2026 03:41
@tmchow tmchow changed the title fix(pi): normalize skill names and avoid subagent tool conflicts fix: normalize skill names and avoid subagent tool conflicts in pi Mar 18, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the Pi target compatibility layer for Compound Engineering by ensuring Pi-safe skill/prompt naming and avoiding tool-name collisions with other Pi extensions (notably pi-subagents).

Changes:

  • Normalize Pi skill/prompt/agent names into lowercase hyphenated form, with collision-safe suffixing.
  • Materialize Pi skills (copy + rewrite) when name/body/frontmatter rewrites are required; otherwise keep symlink parity.
  • Rename the Pi compat tool from subagent to ce_subagent and rewrite Task-style delegation accordingly.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/sync-pi.test.ts Adds coverage for Pi materialization behavior (invalid names, body rewrites, symlink replacement).
tests/pi-writer.test.ts Verifies copied skill frontmatter name is rewritten to match Pi-safe directory and body rewrites use ce_subagent.
tests/pi-converter.test.ts Updates expectations for normalized names and ce_subagent; adds collision normalization test.
src/utils/pi-skills.ts New utilities for Pi name normalization, body rewrites, skill copying, and frontmatter rewriting.
src/templates/pi/compat-extension.ts Renames registered Pi tool to ce_subagent and updates metadata.
src/targets/pi.ts Writes Pi bundles using Pi-aware skill copying/rewriting and updates compatibility notes.
src/sync/pi.ts Switches Pi sync to use a Pi-specific skills sync path.
src/sync/pi-skills.ts New Pi-specific skills sync: normalize names, decide symlink vs materialize, handle collisions.
src/converters/claude-to-pi.ts Uses shared Pi normalization/rewrites, normalizes copied skill names, and avoids name collisions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

@dragosdm
Copy link
Author

@tmchow, how would you like to proceed: accept copilot suggestions or should I fix them?

@tmchow
Copy link
Collaborator

tmchow commented Mar 18, 2026

@tmchow, how would you like to proceed: accept copilot suggestions or should I fix them?

I don’t trust copilot’s solutions but trust it’s diagnosis 😆

do you mind looking looking at the flagged problems and implementing solutions?

@dragosdm
Copy link
Author

@tmchow I've pushed the commit with the fixes:

  • made copySkillDirForPi safer by backing up existing real directories instead of deleting them
  • kept symlink replacement behavior for managed/symlink targets
  • made the MCPorter compatibility note idempotent
  • added tests for both behaviors

Copy link
Collaborator

@tmchow tmchow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work overall — the core logic is correct, fixes real pre-existing bugs (skill names reserved but not normalized in output, agent bodies never transformed for Pi), and has solid test coverage.

A few things to clean up before merging:

1. Dead export: isValidPiSkillName is never imported

src/utils/pi-skills.ts exports isValidPiSkillName but nothing imports it — not the sync path (which uses isValidSkillName from utils/symlink.ts), not the converter, not tests. Either remove it or wire it up where it belongs.

2. Backup directory accumulation

copySkillDirForPi creates timestamped .bak directories on every materialization but never cleans up old ones. Repeated installs/syncs will accumulate stale backups in the user's Pi skills directory indefinitely. Either clean up previous .bak dirs before creating a new one, or cap the number retained.

3. Run subagent with catch-all is too broad

result = result.replace(/\bRun subagent with\b/g, `Run ${PI_CE_SUBAGENT_TOOL} with`)

This could false-positive on prose that coincidentally contains "Run subagent with". Tighten the pattern to match the actual generated format, e.g. Run subagent with agent= instead of just Run subagent with.

@dragosdm
Copy link
Author

  • Dead isValidPiSkillName export: removed
  • Backup accumulation: pruneOldPiSkillBackups now cleans up old .bak dirs before creating a new one. Added two tests: one verifying only the latest backup survives repeated syncs, one verifying pruning skips symlinks and plain files
  • Run subagent with too broad: tightened to Run subagent with agent=. Added positive and negative test cases

Also made two small improvements:

  • Replaced pathExists + lstat two-step with a single lstat try/catch
  • Switched backup pruning to readdir({ withFileTypes: true }) to skip extra lstat calls

Squashed everything into a single commit.
Tests: 335 pass, 0 fail.

@tmchow back to you :)

@tmchow
Copy link
Collaborator

tmchow commented Mar 18, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ce0ea530b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +47 to +50
result = result.replace(taskPattern, (_match, prefix: string, agentName: string, args: string) => {
const normalizedAgent = normalizePiTaskAgentName(agentName)
const trimmedArgs = args.trim().replace(/\s+/g, " ")
return `${prefix}Run ${PI_CE_SUBAGENT_TOOL} with agent="${normalizedAgent}" and task="${trimmedArgs}".`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip outer quotes from Task arguments before emitting ce_subagent

This replacement wraps args in a new task="..." string without handling the very common Task agent("...") form. When Pi rewrites copied skills, existing CE workflows such as plugins/compound-engineering/skills/ce-review/SKILL.md:522/:541 and plugins/compound-engineering/skills/test-xcode/SKILL.md:331 become task=""Run /test-browser ..."", so the delegated prompt is emitted with doubled quotes instead of the original task text after install --to pi or sync --target pi.

Useful? React with 👍 / 👎.

return `${prefix}Run ${PI_CE_SUBAGENT_TOOL} with agent="${normalizedAgent}" and task="${trimmedArgs}".`
})

result = result.replace(/\bRun subagent with\b/g, `Run ${PI_CE_SUBAGENT_TOOL} with`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep generic subagent calls unchanged in copied skills

transformPiBodyContent() now runs over every copied/synced skill, not just CE-generated content, so this unconditional rename silently retargets any user-authored Pi skill that intentionally says Run subagent with ... to use the CE-only ce_subagent wrapper instead. In sync --target pi setups that also install pi-subagents, those skills will start calling the wrong tool and fail unless the referenced agent happens to exist as a CE Pi skill too.

Useful? React with 👍 / 👎.

Comment on lines +139 to +141
function normalizePiTaskAgentName(value: string): string {
const leafName = value.split(":").filter(Boolean).pop() ?? value
return normalizePiSkillName(leafName)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve namespaces when normalizing Task agent names

AGENTS.md explicitly requires fully-qualified agent names to avoid collisions with other installed plugins, but this helper drops everything before the last :. That means Task compound-engineering:review:security-sentinel(...) and Task other-plugin:review:security-sentinel(...) are both rewritten to agent="security-sentinel" during Pi install/sync, so a multi-plugin Pi environment can now dispatch the copied skill to the wrong agent.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants