id: PX-PLAN-RMJ8BIOX
type: plan
title: Built Extension Packages
spec: PX-SPEC-DZLVGRW4
status: approved
Outcome
Pi loads every extension from a self-contained generated package under dist/<extension> produced by Rolldown from the extension's own source.
The repository root stops being a Pi package.
Development builds are on-demand, focused, atomic, and reloadable.
Verified current state: root package.json carries pi.extensions globbing extensions/*/index.ts; user settings list ~/Workspace/pi-ext as a package; Pi loads TypeScript through jiti; no bundler dependency; no dist/.
Behaviors
Order is delivery value; each behavior crosses build, manifest, settings, and verification.
flowchart TB
b1["1. ultra loads from dist/ultra"] --> b2["2. Focused rebuild replaces one package atomically"]
b1 --> b3["3. the-system bundles third-party deps"]
b2 --> b4["4. Whole collection loads from explicit dist entries; root not a package"]
b3 --> b4
b4 --> b5["5. Every package loads relocated with zero unexpected diagnostics"]
Pilot extensions/ultra: Rolldown build emits dist/ultra/ with package.json, entry chunk, package-private lazy chunks for implementation.ts, worker chunk for dynamic-validator-worker.ts, copied prompts/, and a generated pi manifest declaring only the entry and prompts.
User settings gain one explicit local package entry for dist/ultra; the root package entry stays until behavior 4.
Proof: Pi with only dist/ultra configured runs run_workflow and dynamic validation.
Build task //:build-ext <name> (Mise → root npm script → Node build driver) writes into a staging directory, then swaps into dist/<name> only after all chunks and resources exist.
Interrupted builds leave the previous package intact.
Content-addressed chunk names let a loaded extension finish lazy imports across a swap; stale chunks are pruned on the next successful build.
Development build keeps sourcemaps and no minification.
Pilot extensions/the-system: bundle mermaid, unified, rehype-*, remark-*, minisearch, dagre, yaml into package-private chunks; copy skills/; declare pi.skills explicitly; keep Node built-ins, @earendil-works/*, and typebox external.
Any dependency left external requires a recorded incompatibility note in the build config.
Proof: dist/the-system renders /system view and /system book outside the checkout with no node_modules ancestor.
Extend the build driver to all extensions; pi.extensions, pi.skills, pi.prompts, pi.themes removed from root package.json; scripts/extensions.mjs static check inverted to forbid root Pi keys and require per-extension declared resources.
Codegen adds a settings snippet or //:settings-entries task that prints the explicit dist/<name> package list; the human applies it to ~/.pi/agent/settings.json.
Resources: firefox-bidi/skills, pfui/skills, mockup/guidance (internal, not exposed), strata/assets, executable helpers from pi-ext-executable targets.
Relocation gate: copy each dist/<name> to a temp directory without ancestors, load through Pi on Node, assert zero extension errors, working lazy first use, worker start, and manifest-declared resources only.
Run as //:verify-packages; per-extension via //extensions/<name>:verify-package.
Touched: package.json (add rolldown devDependency, remove pi.*), mise.toml (build-ext, build-all, verify-packages), scripts/build/*, scripts/extensions.mjs static check, each extensions/<name>/package.json (declared resources, main stays source for tests), tsconfig.json include for build scripts, .gitignore for dist/.
Constraints
Pi's jiti importer stays; packages are plain ESM JavaScript that jiti passes through.
No lifecycle scripts in generated package.json.
Generated manifest derives from extension package.json declarations; the source manifest remains the single authority.
extensions/*/package.json keep main: index.ts for source tests; generated packages set their own main.
Watcher (optional) calls the same build; no Pi protocol.
Verification
dist/ultra alone runs a workflow with a dynamic extension after relocation.
Interrupting build-ext mid-write leaves the previous dist/<name> loadable.
dist/the-system has no external third-party import and renders Markdown with Mermaid outside the checkout.
Root package.json has no pi key; static check fails if it returns.
verify-packages passes for all extensions and for the full collection with no identity, tool, command, flag, or resource conflicts.
Focused //extensions/<name>:check builds only that extension.
---
id: PX-PLAN-RMJ8BIOX
type: plan
title: Built Extension Packages
spec: PX-SPEC-DZLVGRW4
status: approved
---
## Outcome
Pi loads every extension from a self-contained generated package under `dist/<extension>` produced by Rolldown from the extension's own source.
The repository root stops being a Pi package.
Development builds are on-demand, focused, atomic, and reloadable.
Verified current state: root `package.json` carries `pi.extensions` globbing `extensions/*/index.ts`; user settings list `~/Workspace/pi-ext` as a package; Pi loads TypeScript through jiti; no bundler dependency; no `dist/`.
## Behaviors
Order is delivery value; each behavior crosses build, manifest, settings, and verification.
```mermaid
flowchart TB
b1["1. ultra loads from dist/ultra"] --> b2["2. Focused rebuild replaces one package atomically"]
b1 --> b3["3. the-system bundles third-party deps"]
b2 --> b4["4. Whole collection loads from explicit dist entries; root not a package"]
b3 --> b4
b4 --> b5["5. Every package loads relocated with zero unexpected diagnostics"]
```
1. Pilot `extensions/ultra`: Rolldown build emits `dist/ultra/` with `package.json`, entry chunk, package-private lazy chunks for `implementation.ts`, worker chunk for `dynamic-validator-worker.ts`, copied `prompts/`, and a generated `pi` manifest declaring only the entry and prompts.
User settings gain one explicit local package entry for `dist/ultra`; the root package entry stays until behavior 4.
Proof: Pi with only `dist/ultra` configured runs `run_workflow` and dynamic validation.
2. Build task `//:build-ext <name>` (Mise → root npm script → Node build driver) writes into a staging directory, then swaps into `dist/<name>` only after all chunks and resources exist.
Interrupted builds leave the previous package intact.
Content-addressed chunk names let a loaded extension finish lazy imports across a swap; stale chunks are pruned on the next successful build.
Development build keeps sourcemaps and no minification.
3. Pilot `extensions/the-system`: bundle `mermaid`, `unified`, `rehype-*`, `remark-*`, `minisearch`, `dagre`, `yaml` into package-private chunks; copy `skills/`; declare `pi.skills` explicitly; keep Node built-ins, `@earendil-works/*`, and `typebox` external.
Any dependency left external requires a recorded incompatibility note in the build config.
Proof: `dist/the-system` renders `/system view` and `/system book` outside the checkout with no `node_modules` ancestor.
4. Extend the build driver to all extensions; `pi.extensions`, `pi.skills`, `pi.prompts`, `pi.themes` removed from root `package.json`; `scripts/extensions.mjs` static check inverted to forbid root Pi keys and require per-extension declared resources.
Codegen adds a settings snippet or `//:settings-entries` task that prints the explicit `dist/<name>` package list; the human applies it to `~/.pi/agent/settings.json`.
Resources: `firefox-bidi/skills`, `pfui/skills`, `mockup/guidance` (internal, not exposed), `strata/assets`, executable helpers from `pi-ext-executable` targets.
5. Relocation gate: copy each `dist/<name>` to a temp directory without ancestors, load through Pi on Node, assert zero extension errors, working lazy first use, worker start, and manifest-declared resources only.
Run as `//:verify-packages`; per-extension via `//extensions/<name>:verify-package`.
## Module structure
```mermaid
flowchart LR
src["extensions/name/*.ts + resources"] --> driver["scripts/build/driver (Rolldown API)"]
policy["scripts/build/policy: externals, resources, entries"] --> driver
driver --> staging["dist/.staging/name"] -->|atomic swap| pkg["dist/name"]
pkg --> manifest["dist/name/package.json pi.*"]
pkg --> settings["user settings packages[]"]
pkg --> verify["scripts/verify-packages: relocate + Pi load"]
```
Touched: `package.json` (add `rolldown` devDependency, remove `pi.*`), `mise.toml` (`build-ext`, `build-all`, `verify-packages`), `scripts/build/*`, `scripts/extensions.mjs` static check, each `extensions/<name>/package.json` (declared resources, `main` stays source for tests), `tsconfig.json` include for build scripts, `.gitignore` for `dist/`.
## Constraints
- Pi's jiti importer stays; packages are plain ESM JavaScript that jiti passes through.
- No lifecycle scripts in generated `package.json`.
- Generated manifest derives from extension `package.json` declarations; the source manifest remains the single authority.
- `extensions/*/package.json` keep `main: index.ts` for source tests; generated packages set their own `main`.
- Watcher (optional) calls the same build; no Pi protocol.
## Verification
- [ ] `dist/ultra` alone runs a workflow with a dynamic extension after relocation.
- [ ] Interrupting `build-ext` mid-write leaves the previous `dist/<name>` loadable.
- [ ] `dist/the-system` has no external third-party import and renders Markdown with Mermaid outside the checkout.
- [ ] Root `package.json` has no `pi` key; static check fails if it returns.
- [ ] `verify-packages` passes for all extensions and for the full collection with no identity, tool, command, flag, or resource conflicts.
- [ ] Focused `//extensions/<name>:check` builds only that extension.