Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/AGENTS.md

Raw
Rendered preview

Extension Packages

These rules apply to every package under extensions/*.

New extension checklist

Create:

  1. extensions/<name>/package.json
  2. extensions/<name>/index.ts
  3. extensions/<name>/README.md
  4. extensions/<name>/__tests__/harness.test.ts
  5. extensions/<name>/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-<name>
  • 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 <dependency>/<file> 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/<name>: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:

[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 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 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_<SLUG>_<NAME>; 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/<name>, never the source workspace; the root manifest is intentionally empty. Build with mise run //:build-ext <name> or //:build-all, then /reload in Pi. Verify relocation with mise run //extensions/<name>: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.

# Extension Packages

These rules apply to every package under `extensions/*`.

## New extension checklist

Create:

1. `extensions/<name>/package.json`
2. `extensions/<name>/index.ts`
3. `extensions/<name>/README.md`
4. `extensions/<name>/__tests__/harness.test.ts`
5. `extensions/<name>/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-<name>`
- `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 `<dependency>/<file>` 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/<name>: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_<SLUG>_<NAME>`; 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/<name>`, never the source workspace; the root manifest is intentionally empty.
Build with `mise run //:build-ext <name>` or `//:build-all`, then `/reload` in Pi.
Verify relocation with `mise run //extensions/<name>: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.