Skip to content

feat: ship seodraft as a native pi package - #3

Merged
ch0rch merged 1 commit into
mainfrom
feat/pi-package
Aug 31, 2026
Merged

ch0rch merged 1 commit into
mainfrom
feat/pi-package

Conversation

@ch0rch

@ch0rch ch0rch commented Aug 31, 2026

Copy link
Copy Markdown
Owner

What and why

Pi loads a package's resources in place from the pi key in package.json.
Today pi install npm:seodraft installs the package and registers nothing,
because our skill directory is skill/ (singular) and there is no pi
manifest. This adds the manifest, so the skill is loaded straight out of the
installed package — nothing is copied into the user's repo and pi update
keeps it current.

  • pi.skills = ./skill, pi.prompts = ./prompts, plus the pi-package
    gallery keyword.
  • prompts/seodraft.md restores the literal /seodraft <command> invocation
    that Pi would otherwise only expose as /skill:seodraft.
  • pi joins the installer's provider registry (.pi/skills/) for parity.
  • New tests/pi-package.test.mjs is a distribution contract: manifest roots
    exist, ship in files, resolve to exactly one skill, and templates only use
    argument syntax Pi actually substitutes.
  • scripts/smoke-tarball.mjs now fails if any pi manifest root is missing
    from the published tarball — the failure mode is a package that installs
    cleanly and loads nothing.

Nothing changes for the harnesses already supported. prompts/ is a
package-root resource that the installer never copies, so the payload landing
in .claude, .agents, .cursor, .gemini and .opencode is byte-identical
to 0.1.0. A new test asserts exactly that across every provider.

Checklist

  • pnpm test passes — 81 tests, 10 suites
  • Nothing under skill/scripts/ imports anything but node: builtins and
    relative paths (pnpm check:deps)
  • No rule added or changed — the gate and audit rule sets are untouched
  • Docs updated: README.md (Requirements, Install, new ### Pi section),
    CONTRIBUTING.md, CHANGELOG.md
  • Fixture untouched
  • Conventional commit subject, no AI attribution or co-author trailers

Severity, if this adds a rule

No rules added.

How you verified it

Against Pi's own installed implementation (pi 0.76.0), driving
DefaultPackageManager.resolve(), loadSkillsFromDir() and
expandPromptTemplate() from
@earendil-works/pi-coding-agent/dist/core/* directly:

  1. The published artifact. npm pack, extracted into a fake
    ~/.pi/agent/npm/node_modules/seodraft, registered as a package: resolves
    to exactly skill/SKILL.md (1 skill, name seodraft, 0 warnings) and
    prompts/seodraft.md (1 prompt), with no extensions or themes.
  2. Scripts still run from the client repo while the skill lives outside it:
    node <pkg>/skill/scripts/state.mjs validate from a content repo → exit 0,
    {"ok": true}.
  3. No harm to other harnesses. Ran the real installer into a project with
    every harness folder: diff -rq between the copied trees is identical, and
    no prompts/ directory appears inside any of them.
  4. No phantom skills. .pi/skills/, .agents/skills/ and
    .claude/skills/ layouts each resolve to exactly ['seodraft'] with 0
    warnings — skill/reference/*.md is never picked up, because Pi stops
    recursing at a directory holding SKILL.md.
  5. Template expansion. /seodraft init "focus on pricing pages" expands
    with init and the trailing notes substituted, no tokens left behind. This
    is how the ${1:-status} bug was caught: Pi's docs on main list
    bash-style defaults, but substituteArgs in released builds only handles
    $N, $@, $ARGUMENTS and ${@:N[:L]}, so ${1:-status} reached the
    model verbatim. The template now uses $1 and ${@:2}, and the new
    contract test rejects any other ${...} token.
  6. Collision, documented rather than hidden. Installing by both routes on
    one machine makes Pi emit a collision diagnostic and keep the first skill
    found. README.md says to pick one route.

Pi loads packaged resources in place from the `pi` key in package.json, so
`pi install npm:seodraft` now registers the skill straight out of the
installed package — no copy into the user's repo, and `pi update` keeps it
current.

- declare `pi.skills` (`./skill`) and `pi.prompts` (`./prompts`) plus the
  `pi-package` gallery keyword
- add `prompts/seodraft.md`, which restores the literal `/seodraft <command>`
  invocation pi would otherwise only expose as `/skill:seodraft`. It sticks to
  `$1` / `${@:2}`: released pi builds do not substitute bash-style defaults
  like `${1:-status}` and leak the token into the prompt
- add `pi` to the installer's provider registry (`.pi`) for parity, and a test
  asserting every provider receives a byte-identical payload
- add a distribution contract test over the manifest, tarball `files` and
  template syntax
- fail the tarball smoke test when a `pi` manifest root is missing from the
  published `files`, which would make pi load an empty package

The skill payload other harnesses receive is unchanged: `prompts/` lives at
the package root and is never copied.
@ch0rch
ch0rch merged commit b101e3b into main Aug 31, 2026
4 checks passed
@ch0rch
ch0rch deleted the feat/pi-package branch August 31, 2026 23:04
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.

1 participant