piagent_
← ~/guides

Contributing to pi

Last updated: Aug 14, 2026

$ pi –contribute

TL;DR. pi’s contribution gate is intentional: every issue and PR from a new contributor is auto-closed. A maintainer must reply with lgtmi (issues only) or lgtm (issues + PRs) at the start or end of a comment before you can land anything. Once approved, run npm run check and ./test.sh from the repo root before pushing; both must pass. The repo follows lockstep versioning (one version across all packages, no major releases), conventional-commit messages, and a release script that bumps every package together. If you are using an AI agent to write the PR, run it from the pi root so it picks up AGENTS.md automatically — agents must follow it.

This guide is for contributors to earendil-works/pi. It pulls the rules from the repo’s CONTRIBUTING.md and AGENTS.md so you can read them in one place and see how they interact. Where the upstream files change, this one will lag behind — always cross-check against the canonical versions in the repo before you open an issue or PR.

The contribution gate

The repository auto-closes every issue and PR from a new contributor by default. That is not a bug; it is the design. The volume of low-signal submissions is high enough that maintainers triage on their own schedule rather than reactively. Two commands reopen the gate, and only a maintainer can issue them:

  • lgtmi — your future issues will not be auto-closed.
  • lgtm — your future issues and PRs will not be auto-closed. Only lgtm grants the right to submit PRs.

The command must be at the very start or the very end of a maintainer’s reply (optionally after one or more @username mentions). Mid-comment occurrences do not count. If you do not see lgtm at the boundary, do not open a PR — it will be auto-closed without review.

Weekend issues (Friday through Sunday) are not guaranteed to be reviewed. Anything urgent belongs on Discord with the short version, a repro, and the relevant logs.

The quality bar for issues

If you open an issue, you must use one of the two GitHub issue templates, keep the body short (one screen or less), and write in your own voice. State the bug or request clearly, explain why it matters, and say whether you want to implement the change yourself. AGENTS.md explicitly notes: do not have an LLM generate the prose, and if you must, follow up with a clearly AI-labeled comment.

A maintainer will reopen the issue or reply with lgtmi / lgtm only if it meets the bar. Issues that do not are closed without discussion, and that silence is intentional — “a reply is maintenance work too.” If you ignore this document twice, or if you submit a large volume of agent-generated issues, your GitHub account will be permanently blocked.

For larger design changes, Earendil uses RFCs — not all of them are public, but the public ones are the right place to start a feature conversation before opening an issue.

Before submitting a PR

Do not open a PR unless a maintainer has already approved it with lgtm. Once you have that approval, the pre-flight is:

npm run check
./test.sh

Both must pass. npm run check is the full output (no tail) — it is the type/lint gate that AGENTS.md requires after every code change, and it does not run tests. ./test.sh runs the non-e2e tests, which is the right scope for almost all contributors. Do not run the full vitest suite directly: it includes e2e tests that activate when endpoint or auth env vars are present, and those require a different harness.

If you are adding a new provider to packages/ai, AGENTS.md specifies a required test file; check that file’s expectations before writing your provider. Put any issue-specific regression test under packages/coding-agent/test/suite/regressions/ named <issue-number>-<short-slug>.test.ts.

AGENTS.md rules for AI-generated work

Using AI to write code is fine. Submitting AI-generated slop without understanding it is not. If you use an agent to draft your PR, run it from the pi repo root so it picks up AGENTS.md automatically — that file is the binding ruleset for any agent working in this repo, and your PR will be judged against it. The headline rules agents must follow:

  • No any unless absolutely necessary. The TypeScript config uses Node strip-only mode (erasable syntax only): no parameter properties, enum, namespace/module, import =, or export =. Use explicit fields with constructor assignments.
  • No inline imports. await import(), import("pkg").Type, dynamic type imports — none of these. Top-level imports only.
  • Read files in full before wide-ranging changes. Search snippets are not enough for broad edits.
  • Never modify packages/ai/src/models.generated.ts directly. Update the generator at packages/ai/scripts/generate-models.ts and regenerate; the resulting models.generated.ts diff is fine to include even if it picks up unrelated upstream model metadata changes.
  • Inline single-line helpers that have only one call site.
  • Check node_modules for external API types rather than guessing signatures.
  • Never remove or downgrade code to fix type errors from outdated deps — upgrade the dep instead.
  • Never hardcode key checks. Add defaults to DEFAULT_EDITOR_KEYBINDINGS or DEFAULT_APP_KEYBINDINGS instead.
  • Ask before removing functionality that appears intentional, and do not preserve backward compatibility unless asked.

These rules come from AGENTS.md and are not negotiable in PR review. If your agent breaks one, the fix is yours, not the maintainers’.

Changelog conventions

Do not edit CHANGELOG.md. Changelog entries are added by maintainers after the PR lands. The changelog lives at packages/*/CHANGELOG.md (one per package), and every new entry goes under ## [Unreleased] — read that section first and append to existing subsections rather than duplicating them. Released version sections are immutable.

Attribution format expected in changelog entries:

  • Internal fixes (from issues): Fixed foo bar ([#123](https://github.com/earendil-works/pi-mono/issues/123))
  • External contributions: Added feature X ([#456](https://github.com/earendil-works/pi-mono/pull/456) by [@username](https://github.com/username))

Commit message format

The repo uses a tight conventional-commit subset. Messages take the form:

{feat,fix,docs}[(ai,tui,agent,coding-agent)]: <commit message>

Pick the scope only when your change is genuinely local to one package; leave it off otherwise. The commit message itself is informative and concise — no emojis, no cheerful filler, technical prose only. If you used an AI agent to draft the commit, follow AGENTS.md and the contribution conventions; “Thanks @user” is fine, “Thanks so much @user!” is not.

Multiple pi sessions may be running in the same working directory simultaneously. Stage explicit paths (git add <path1> <path2>); never git add -A or git add .. Run git status before committing and verify you are only staging your files. packages/ai/src/models.generated.ts may always be included alongside yours.

Dependencies and lockfile rules

Treat npm dependency and lockfile changes as reviewed code. Direct external deps stay pinned to exact versions. Hydrate locally with npm install --ignore-scripts; do CI-style installs with npm ci --ignore-scripts. Do not run lifecycle scripts unless the user asks. When updating undici, you must read its changelog and evaluate the impact before applying the version bump.

If packages/coding-agent/npm-shrinkwrap.json needs to be regenerated, run node scripts/generate-coding-agent-shrinkwrap.mjs (verify with --check or npm run check). New dependencies with lifecycle scripts require an explicit allowlist entry in that script — never add one silently. A pre-commit hook blocks lockfile commits unless you set PI_ALLOW_LOCKFILE_CHANGE=1. Do not bypass it unless you intend to commit the lockfile change.

Releasing (for maintainers)

pi uses lockstep versioning: all packages share one version and every release updates all of them together. patch = fixes + additions, minor = breaking changes. There are no major releases.

The flow is:

  1. Audit changelogs. Ask the user whether they ran the /cl prompt on the latest commit on main. If not, they must run /cl first to update each package’s [Unreleased] section before releasing.

  2. Local smoke test. Build an unpublished release into /tmp and run the binaries against a real prompt:

    npm run release:local -- --out /tmp/pi-local-release --force
    /tmp/pi-local-release/node/pi --version
    /tmp/pi-local-release/node/pi --list-models
    /tmp/pi-local-release/node/pi -p "Say exactly: ok"

    Repeat for the Bun binary. Both startup, model/account listing, interactive startup, and at least one real prompt with the intended default provider must succeed before continuing.

  3. Run the release script. Use PI_ALLOW_LOCKFILE_CHANGE=1 npm_config_min_release_age=0 only for the release command itself:

    PI_ALLOW_LOCKFILE_CHANGE=1 npm_config_min_release_age=0 npm run release:patch    # fixes + additions
    PI_ALLOW_LOCKFILE_CHANGE=1 npm_config_min_release_age=0 npm run release:minor    # breaking changes

    The script bumps all package versions, updates changelogs, regenerates release artifacts, runs npm run check, commits Release vX.Y.Z, tags vX.Y.Z, adds fresh [Unreleased] sections, then pushes main and the tag. Do not rerun it after a tag is pushed.

  4. CI verifies and announces. Pushing the tag triggers .github/workflows/build-binaries.yml. The publish-npm job uses npm Trusted Publishing through GitHub Actions OIDC (environment npm-publish) — no local npm publish, OTP, or WebAuthn is required. After publishing, announce-pi-dev-release verifies every public workspace package resolves at the exact release version and writes the marker to R2; pi.dev/api/latest-version reads that marker, so the announcement only fires after that job succeeds.

  5. If CI fails. The publish helper is idempotent — it skips package versions already present on npm — and the announcement job rechecks availability before updating the R2 marker. Rerun the failed job or workflow after fixing CI or transient npm issues; do not rerun npm run release:patch for the same version.

A pre-PR checklist

Before opening the PR:

  • A maintainer has approved the change with lgtm (boundary position).
  • npm run check passes with full output (no tail).
  • ./test.sh passes.
  • Issue-specific regressions live under packages/coding-agent/test/suite/regressions/<issue>-<slug>.test.ts.
  • No edits to CHANGELOG.md — that is the maintainers’ job.
  • Commit message matches {feat,fix,docs}[(scope)]: <message> with no emojis or filler.
  • git status shows only your files (no git add -A or git add .).
  • If you used an AI agent, run it from the repo root so it picked up AGENTS.md, and confirm it followed the rules above.
  • Lockfile and package.json deps are intentional; pre-commit hook did not need to be bypassed.

Further reading

  • CONTRIBUTING.md — the canonical contribution gate and quality bar
  • AGENTS.md — binding rules for AI agents and humans working in the repo
  • Earendil RFCs — public design discussions for larger changes
  • Discord — for urgent issues and pre-issue design conversation
  • Releases — the published artifacts release:patch and release:minor produce