# Extension Packages These rules apply to every package under `extensions/*`. ## New extension checklist Create: 1. `extensions//package.json` 2. `extensions//index.ts` 3. `extensions//README.md` 4. `extensions//__tests__/harness.test.ts` 5. `extensions//mise.toml` Then run `mise run //:lock` and the extension-local `lint`, `test`, and `check` tasks. Do not run `npm install` directly. ## Package manifest contract Every extension package must declare: - name `@bugabinga/pi-ext-` - `private: true` - `type: "module"` - `main: "index.ts"` - `license: "MIT"` - development dependencies on `@marcfargas/pi-test-harness` and Vitest Declare what the generated package needs under `pi-ext`: - `entries`: extra bundled entrypoints such as worker scripts spawned by path - `resources`: files or directories copied verbatim, such as skills, prompts, templates, or executable helpers - `assets`: package-relative destination to `/` for browser assets copied out of a bundled dependency - `externals`: third-party packages kept external, each with its demonstrated bundling incompatibility; they install into the generated package Portable third-party JavaScript is bundled by default; Node built-ins, Pi modules, and TypeBox stay external. Resolve runtime files relative to `import.meta.url` or `import.meta.dirname`; generated packages keep every chunk flat in the package root so those lookups work from any chunk. When a path depends on the file extension, derive it from `import.meta.url` (`.ts` in source, `.js` generated). Do not declare a per-extension version. The root package owns the repository version. Do not declare package scripts. Extension automation belongs in `mise.toml`. Declare only packages imported by the extension, including development and test code. Put Pi packages under `peerDependencies` with range `"*"`. Never depend on another `@bugabinga/pi-ext-*` package. `scripts/extensions.mjs` enforces this contract. ## Mandatory harness test Every extension must include a pi-test-harness test that loads `index.ts` through the real Pi extension runtime. Mocks around helper functions do not replace this test. Run it through `mise run //extensions/:test`. ## Mandatory local tasks Every extension must expose `lint`, `test`, and `check` in its local `mise.toml`. Root tools and environment are inherited, so do not redeclare them locally. Use this baseline, replacing `angel` with the package directory name: ```toml [tasks.lint] description = "Lint angel extension" depends = ["//:install"] run = [ "biome ci --config-path ../.. .", "rumdl check --config ../../.rumdl.toml .", "taplo format --check --config ../../.taplo.toml mise.toml", ] [tasks.test] description = "Test angel extension" depends = ["//:install"] run = [ { task = "//:test", args = ["extensions/angel"] }, ] [tasks.check] description = "Check angel extension" depends = ["//:install"] run = [ { task = "//:build", args = ["angel"] }, ] ``` Keep optional package automation, such as `bench`, `e2e`, or `dev`, in the same local `mise.toml`. Do not replace structured task references with command strings that spawn `mise run`. ## Independence Extensions must remain standalone packages. Never import from another `@bugabinga/pi-ext-*` package or another extension directory. Optional integration must use capabilities or events and degrade cleanly. Important behavior needs a native fallback; decorative integration may no-op. ## Model calls Prefer a parent-linked child `AgentSession` over `ctx.modelRegistry.complete()` for nested model work so Pi session tracking records it. When a direct model call is necessary, use `ctx.modelRegistry`, never Pi AI compatibility dispatchers, so extension-registered providers work. ## Imports | Package | Provides | Placement | | --- | --- | --- | | `@earendil-works/pi-coding-agent` | Extension API, context, and coding-agent types | peer dependency when imported | | `@earendil-works/pi-ai` | model and message APIs | peer dependency when imported | | `@earendil-works/pi-tui` | terminal UI components and rendering helpers | peer dependency when imported | | `typebox` | tool parameter schemas | peer dependency when imported | | `@bugabinga/pi-ext-*` | sibling extension implementation | never | ## Diagnostics Runtime extension code must never write diagnostic text through `console.*` or process stdout/stderr; direct terminal writes corrupt Pi's TUI and machine-readable modes. Every extension must implement the [debug contract](DEBUG.md) with a committed generated `src/pi-ext-debug.ts`, handwritten `src/debug.ts`, lifecycle events, and one extension-local harness check. Generate the local writer from `scripts/codegen/targets.json` with `mise run //:codegen`; change its template rather than editing generated copies. In `src/debug.ts`, call `defineDebug` with literal event names, span bases, and approved numeric, boolean, or finite-enum metadata fields. Import the needed `dbg`, `span`, and `closeDebug` exports from the handwritten file in runtime code; do not import direct logger exports from the generated file. Static checks require the schema binding and a runtime import, and reject runtime imports of the generated module. Never pass credentials, prompts, commands, raw errors, paths, or arbitrary objects to debug code; use safe counts, finite classifications, or presence flags instead. Keep logging off by default; enable it only through user settings or the extension-specific environment override. Each extension uses only its own local writer and remains usable outside the root checkout. Use `ctx.ui.notify` for actionable failures, throw tool failures, and use opt-in file diagnostics for debugging. Do not emit `info` notifications during `/reload`: Pi replaces them with its reload-success status. Preserve actionable `warning` and `error` notifications. Terminal-control sequences, standalone benchmarks, and worker protocols are exempt. ## Settings Configuration must implement the [settings contract](SETTINGS.md) through a committed generated `src/pi-ext-settings.ts`; add the extension to the `pi-ext-settings` codegen target and run `mise run //:codegen`. Never read `getGlobalSettings`, `getProjectSettings`, `registerFlag`, or `getFlag` directly; static checks reject them outside generated files. Keys live under the extension slug; new env variables are `PI__`; flags are for startup toggles only. Pass real project trust, and never let an invalid value fall through to a lower source. Secrets stay in env or Pi's credential store, never in settings. Add settings only for behavior users actually need to change. ## Startup `session_start` must be fast. Treat every Windows process spawn as expensive. Prefer native Node APIs and defer unavoidable process spawns until first use. Keep entry modules thin and lazy-load command or tool implementations. Gate optional domain work with cheap relevance checks before imports. Do not await network access, Git, filesystem scans, or subprocesses during startup. Publish minimal UI immediately, then defer slow work with a background task and generation or stale-result guard. Abort asynchronous startup work on reload and ignore stale results. Check executable presence through native `PATH` access, not version subprocesses. Never block other extensions for telemetry, recovery, decoration, or cache warming. Preserve registration, validation, rendering, and first-use behavior when deferring work. Measure startup performance with the complete extension set on Windows and WSL. ## Tool widgets Tool widgets must match Pi built-ins: compact by default and expanded through `app.tools.expand`. Normal tool rows must rely on Pi-owned keyboard and mouse expansion. Every custom `renderCall` and `renderResult` must honor native expanded state, including progress, errors, and malformed-detail fallbacks. Collapsed results may show status, counts, paths, identifiers, and tiny bounded summaries only. Full content, previews, snippets, verbose logs, structured bodies, and per-result lists require expanded state. When content is hidden, show `keyHint("app.tools.expand", "to expand")`. Do not use the legacy `expandTools` action. Focused overlays and non-tool widgets may handle their own expansion action. ## Cross-platform behavior All extensions must work on Windows, macOS, and Linux. Do not add operating-system-scoped extensions. Prefer native Node APIs over shell helpers such as `which`, `bash`, `sed`, `grep`, or `/tmp`. Check platform-tool availability when the extension loads. Report missing required tools with warning severity and missing optional tools with info severity. Test platform-tool behavior through the real extension or tool path with controlled fake executables on `PATH`, not only mocked helpers. ## Context impact For new extensions or major edits, measure before/after context impact following `scripts/context-startup.md`; report provider-token deltas, clearly distinguish heuristic estimates, and disclose measurement blockers. ## Commands Commands with subcommands must provide argument autocomplete. ## Documentation Keep READMEs terse. Do not duplicate source-of-truth configuration, API details, or implementation documentation. Link to canonical source files, commands, settings, or upstream documentation. A package README should explain purpose, installation or loading, commands and tools, settings, and optional integrations. ## Generated packages Pi loads `dist/`, never the source workspace; the root manifest is intentionally empty. Build with `mise run //:build-ext ` or `//:build-all`, then `/reload` in Pi. Verify relocation with `mise run //extensions/:verify-package`; verify the whole collection with `mise run //:verify-packages --collection`. Print the user settings package list with `mise run //:settings-entries`. ## Publishing Use the root `//:publish` task. Publishing is Git-only; there is no npm registry flow.