Files
eb49702651 feat: thin Pi adapter mounting ECC's canonical skills and commands (#2759)
* feat: add thin Pi adapter mounting ECC's canonical skills and commands

Adds first-class Pi (@earendil-works/pi-coding-agent) support as a thin
adapter layer, following the maintainer review on #2352. ECC's canonical
assets stay the single source of truth: nothing is copied or generated
under .pi/.

The `pi` manifest in package.json points Pi directly at `skills/` and
`commands/`. No transformation is needed — ECC's SKILL.md files already
follow the Agent Skills standard Pi implements, and ECC's command
frontmatter is already Pi's prompt-template format.

.pi/extensions/index.ts is the only adapter logic. It:

- uses Pi's documented `pi.on(...)` lifecycle, not an undocumented event bus
- resolves hook scripts from the installed package via `__dirname`, never
  `process.cwd()`, so global installs work from any project directory
- runs hooks with `execFile(process.execPath, [...])` and no shell, so paths
  containing spaces or shell metacharacters are safe
- invokes hooks through ECC's own `run-with-flags.js`, so `ECC_HOOK_PROFILE`
  and `ECC_DISABLED_HOOKS` keep gating hooks under Pi
- runs hooks in the user's project directory so project detection stays
  correct, while resolving the scripts themselves package-relative
- injects the SessionStart hook's `additionalContext` into the system prompt
  on the next `before_agent_start`
- isolates hook failures behind a timeout and an output limit
- registers `/ecc-doctor` for install diagnostics

Registers `.pi` in the platform-configs install module and adds a Pi row to
the harness adapter compliance matrix.

Verified against Pi 0.84.1: a global `pi install` exposes 285 skills and 94
commands resolved from `skills/` and `commands/`, plus `/ecc-doctor`, with
no generated copies.

Scope deliberately excludes subagents, chains, approval gates, todos,
profiles, and MCP; ECC works in Pi without any companion package.

* fix: address review findings on the Pi adapter

Bot review on #2759 surfaced two real runtime defects and several
hardening gaps.

Runtime fixes:

- Attach an `error` listener to the hook child's stdin. `stdin.end()`
  writes asynchronously, so a hook that exits, short-circuits, or is
  killed by the timeout before reading the payload raises EPIPE as an
  `error` event that the surrounding try/catch cannot see. Unhandled,
  that event would terminate the Pi session and break the isolation
  guarantee the adapter documents.
- Clear `pendingContext` at the top of the `session_start` handler. Pi
  can start a new session (/new, /resume, /fork) before
  `before_agent_start` consumes the previous value; if the newer hook
  then failed, the next agent start received context describing a
  different session's project state.
- Replace `require.resolve` companion detection with a read of Pi's own
  `packages` list, honoring `PI_CODING_AGENT_DIR`. Pi installs packages
  under its config directory, which is not on Node's module resolution
  path from the extension, so the previous check reported every
  companion as missing no matter what was installed.

Compliance matrix: remove internal semicolons and a trailing period from
the Pi record's list entries. The renderer joins entries with "; ", so
those characters split one entry into several in the rendered cell.

Tests: run profile gating against the temp skeleton instead of the real
checkout so it cannot leave marker artifacts behind; count files under
.pi/ by walking disk rather than git, so untracked copies cannot bypass
the regression guard; allow negated phrasing in the README heuristic;
pin the adapter's real parser guards with source assertions so the local
mirrors cannot silently diverge; add coverage for EPIPE isolation, stale
context clearing, and companion detection.

* docs: point users at existing companion Pi packages instead of bundling them

Every capability listed as out of scope is already provided by a maintained
community Pi package: pi-subagents, @juicesharp/rpiv-ask-user-question,
@juicesharp/rpiv-todo, and pi-mcp-adapter for MCP.

Pi supports pulling other pi packages in via dependencies plus
bundledDependencies, but this adapter deliberately does not. Bundling would
ship third-party code that executes with full user permissions in every ECC
install, turn optional capabilities into mandatory ones, and add four
fast-moving pins to maintain.

Instead /ecc-doctor now prints the exact `pi install npm:<name>` command for
each companion it does not find, so adopting one stays a deliberate user
choice.

Also corrects the MCP claim: Pi core has no MCP surface by design, but the
community pi-mcp-adapter package adds one. This adapter neither installs nor
verifies it, and ECC's MCP reference configs are not known to be compatible.

* docs: ECC's MCP configs work in Pi through pi-mcp-adapter, verbatim

Tested rather than assumed. The community pi-mcp-adapter package reads the
standard mcpServers format from .mcp.json and ~/.config/mcp/mcp.json, which
is exactly the format ECC already uses in .mcp.json and
mcp-configs/mcp-servers.json.

Verified against pi-mcp-adapter 2.21.2 in an isolated PI_CODING_AGENT_DIR:
copying mcp-configs/mcp-servers.json to a project's .mcp.json registers Pi's
`mcp` tool and `/mcp` command with all 35 ECC servers discovered, coexisting
with this adapter's /ecc-doctor. No translation layer and no ECC change are
needed, so this stops being a limitation and becomes documentation.

Recorded caveats: the adapter's first run against a new config performs
initialization that blocks in non-interactive mode, and only discovery was
verified, not live tool invocation.

ECC still neither installs nor depends on the package.

* feat: inject ECC's canonical engineering rules into Pi's system prompt

ECC's rules were the one durable asset the adapter did not deliver: skills
and commands reached Pi in full, but the 122 rule files that carry ECC's
coding style, testing, security, git workflow, and code-review standards
did not, so ECC in Pi was a library of skills rather than a set of
enforced standards.

Rules are read at runtime from the canonical rules/common/ directory of
the installed package and appended to the system prompt inside an
<ecc-engineering-rules> block. Nothing is copied or generated under .pi/,
which keeps the single-source-of-truth constraint this PR exists to
satisfy. Injection reuses the before_agent_start path already built for
session context, so no new lifecycle mapping is introduced.

Rules are re-applied every turn because they are standing policy, while
the session context stays one-shot and is consumed on first use.

agents.md, hooks.md, and performance.md are excluded: they describe Claude
Code primitives Pi does not have (Task/TodoWrite delegation, Claude hook
event types, thinking-budget toggles), so injecting them would point the
model at tools that are not there. A test asserts they stay excluded, and
a leakage test asserts none of those primitives appear in the injected
text. Language-specific rules under rules/<language>/ are out of scope for
this first adapter.

Injection is bounded by MAX_RULES_BYTES and can be disabled with
ECC_PI_RULES, following ECC's existing off-switch convention. /ecc-doctor
reports the state and injected size.

Measured on this repo: 7 files, 12,361 characters, roughly 3k tokens.

Also replaces a Function() call in the test helper with direct arithmetic,
and repins a stale assertion that pinned one spelling of the context
handoff rather than the guarantee (read before clear, clear before return).

* fix: /ecc-doctor misreported filtered packages and partial rule installs

Two reporting defects in /ecc-doctor, the command whose whole job is telling
a user what is actually installed.

Pi's settings accept a `packages` entry in two shapes: the bare source string
("npm:pi-subagents") and an object carrying that source alongside resource
filters ({ source: "npm:pi-subagents", skills: [] }). normalizePiPackageName
only recognized the string, so a user who narrowed which resources a companion
contributes was told the companion was not installed, along with an install
command for something already present. The source type still decides whether a
name is comparable, so an object wrapping a git source or a path stays
unrecognized exactly as before.

loadPortableRules drops rule files it cannot read, drops empty ones, and stops
at MAX_RULES_BYTES, but describeRulesStatus reported PORTABLE_RULE_FILES.length
regardless. A partial install that loaded 3 of 7 files reported "7 rule file(s)"
to the one command a user runs to find a partial install. The loaded count is
now tracked next to the cache and reported as a ratio, with the shortfall named.

Also reconciles the Notes bullet in .pi/README.md, which still called MCP out of
scope after the MCP section landed documenting that ECC's configs load in Pi
through pi-mcp-adapter.

Both defects were reported by CodeRabbit and verified against Pi's own
packages.md before fixing. Adapter tests go from 24 to 26; the two source
contracts that pinned the previous spellings now pin the new guards, so the
object-form unwrapping and the loaded-count reporting cannot be silently
reverted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 18:41:33 -04:00
..

.pi — Pi Coding Agent Integration

This directory contains the Pi adapter for ECC — a thin extension that connects the @earendil-works/pi-coding-agent terminal coding agent to ECC's canonical skills, prompts, and lifecycle hooks.

Design Principle

ECC's canonical assets—skills, agents, commands, and hooks—remain the single source of truth. This adapter contains only the integration logic. No copies, no duplication.

What This Provides

  • ECC's skills from ./skills/ — available in Pi as /skill:<name>
  • ECC's commands from ./commands/ — available in Pi as /<name>
  • ECC's engineering rules from ./rules/common/ — injected into Pi's system prompt on every turn, so coding style, testing, security, git workflow, and code-review standards apply in Pi as they do in other harnesses
  • Session lifecycle hooks — ECC's SessionStart and SessionEnd hooks, run through ECC's own run-with-flags.js, so ECC_HOOK_PROFILE and ECC_DISABLED_HOOKS keep working under Pi
  • Session context injection — whatever ECC's SessionStart hook returns as additionalContext is folded into Pi's system prompt for the next turn
  • /ecc-doctor — diagnostic command to verify the integration

Verified against Pi 0.84.1: a global install exposes 285 skills and 94 commands, resolved directly from skills/ and commands/, with no generated copies.

Installation

# Install ECC as a Pi package
pi install git:github.com/affaan-m/ECC

# Or from a local checkout
pi install /path/to/ECC

# Or project-local only
pi install -l /path/to/ECC

# Verify
pi list

Then inside Pi, run /ecc-doctor to confirm skills, commands, and hooks are available.

To uninstall:

pi remove git:github.com/affaan-m/ECC

Option 2: Zero-Install (Existing Claude Code Users)

If you already have ECC installed for Claude Code, point Pi at the same canonical directories from ~/.pi/agent/settings.json:

{
  "skills": ["~/.claude/skills"],
  "prompts": ["~/.claude/commands"]
}

This gives you skills and commands directly. It does not include the lifecycle hook adapter or /ecc-doctor — use Option 1 for the full integration.

How It Works

The extensions/index.ts file handles:

  1. Skill and command mounting — Pi reads ./skills and ./commands directly via the pi key in package.json. No transformation is needed: ECC's SKILL.md files already follow the Agent Skills standard Pi implements, and ECC's command frontmatter (description, argument-hint) is already Pi's prompt-template format
  2. Lifecycle hooks — Maps Pi's session_start to ECC's session:start hook (scripts/hooks/session-start.js) and Pi's session_shutdown to ECC's session:end:marker hook (scripts/hooks/session-end-marker.js), both invoked through scripts/hooks/run-with-flags.js so ECC's profile and disable flags are honored
  3. Rule injection — Reads ECC's portable engineering rules from the canonical rules/common/ directory at runtime and appends them to the system prompt inside an <ecc-engineering-rules> block on every turn. Nothing is copied into .pi/. agents.md, hooks.md, and performance.md are excluded on purpose: they describe Claude Code primitives Pi does not have (Task/TodoWrite delegation, Claude hook event types, thinking-budget toggles), so injecting them would point the model at tools that are not there. Language-specific rules under rules/<language>/ are not injected in this first adapter. Set ECC_PI_RULES to 0, false, off, none, or disabled to turn injection off; /ecc-doctor reports the current state and the injected size
  4. Context injection — Parses hookSpecificOutput.additionalContext from the SessionStart hook and appends it to the system prompt on the next before_agent_start, wrapped in an <ecc-session-context> block. Non-JSON hook output is tolerated, not treated as an error
  5. Hook isolation — Failing, missing, or slow hooks degrade to a warning and never terminate the Pi session. Hook execution is bounded by a timeout and an output limit
  6. Package resolution — Resolves hook scripts from the installed package via __dirname, never from process.cwd(), so a global install works from any project directory. Hooks still run in the user's project directory, so project detection stays correct

All hook execution is non-shell (execFile without shell interpretation), so paths containing spaces, tabs, or shell metacharacters are safe.

Scope

Intentionally out of scope for this first adapter (to be added independently):

  • Subagent conversion and chains (need the pi-subagents companion package)
  • Structured approval gates (need @juicesharp/rpiv-ask-user-question)
  • Persistent todos (need @juicesharp/rpiv-todo)
  • Profile-based resource filtering
  • MCP translation — see below; no translation turned out to be necessary

ECC works in Pi without any of these. Skills and commands are fully available today.

These capabilities are provided by existing community Pi packages rather than by anything ECC would need to write. This adapter deliberately does not bundle or auto-install them: bundling would ship third-party code that executes with full user permissions in every ECC install, and would make optional capabilities mandatory. Install whichever you want yourself — /ecc-doctor reports which are present and prints the exact pi install command for the ones that are not.

MCP

Pi core has no MCP surface by design. The community pi-mcp-adapter package adds one, and it reads the standard mcpServers format from .mcp.json and ~/.config/mcp/mcp.json — which is exactly the format ECC already uses in .mcp.json and mcp-configs/mcp-servers.json.

Verified against pi-mcp-adapter 2.21.2: copying ECC's mcp-configs/mcp-servers.json to a project's .mcp.json registers Pi's mcp tool and /mcp command with all 35 ECC servers discovered, alongside this adapter's own /ecc-doctor. No translation layer is needed and no ECC change is required.

pi install npm:pi-mcp-adapter
cp mcp-configs/mcp-servers.json /path/to/project/.mcp.json

ECC neither installs nor depends on that package. Two caveats: the adapter's first run against a new config performs initialization that blocks in non-interactive (-p) mode, so run it once interactively before using it headless; and only server discovery was verified, not live tool invocation, which needs real credentials for each server.

Security

  • Pi extensions run with the same OS permissions as the Pi process
  • This adapter does not auto-commit, push, merge, or deploy
  • Hooks are executed without a shell, preventing command injection
  • Hook failures are isolated and cannot silently authorize blocked operations

Troubleshooting

Skills or commands not showing up

Cause: the package's resources are disabled, or a project-local install has not been trusted. Pi asks before trusting a project folder that carries its own .pi/ resources.

Fix: run pi config and confirm the ECC package's skills and prompts are enabled (Tab switches between user and project scope). Then confirm the package itself is registered with pi list.

/ecc-doctor not found or reports missing package root

Cause: Extension not loaded or package installed incorrectly.

Fix:

  1. Run pi list to confirm ECC is registered
  2. Restart Pi: exit and reopen the session
  3. Run /ecc-doctor again

/ecc-doctor prints the resolved package root, the skill and command counts it found, the hook runner path, the active hook profile, and which optional companion packages are present. A NOT FOUND line points at the specific path that failed to resolve.

Hooks not firing

Cause: the extension is not loaded, or the hooks are gated off by an ECC hook profile.

Fix:

  1. Confirm pi list shows ECC and that /ecc-doctor reports the hook runner as found
  2. Check ECC_HOOK_PROFILE and ECC_DISABLED_HOOKS/ecc-doctor prints both. A hook listed in ECC_DISABLED_HOOKS is skipped by design
  3. Restart Pi so the extension reloads

Notes

  • The .pi/extensions/ directory is the only place for adapter code
  • Skills and commands are defined in the repo root (skills/, commands/) and referenced by Pi
  • MCP is not bundled, but ECC's MCP configs load in Pi through the community pi-mcp-adapter — see MCP above
  • This adapter was tested against Pi v0.84.1