@code-yeongyu/senpi — primary fork target. The CLI app users actually run (senpi). Highest merge-conflict surface against upstream pi-mono. Always reach for the extension API before touching anything in src/core/.
src/
├── cli.ts # Bun/Node entry — sets process.title, disables undici timeouts, calls main()
├── main.ts # Arg parse → model resolution → mode dispatch
├── index.ts # Public API (AgentSession, AuthStorage, compaction, extension types, tools)
├── config.ts # APP_NAME, VERSION, configDir/cacheDir/sessionDir resolvers
├── migrations.ts # Settings/session schema migrations (incl. `pi → senpi` rename)
├── package-manager-cli.ts # `senpi update senpi`, package commands (install/list/remove)
├── changes.md # Fork tracker (root-level src changes)
├── bun/ # Bun binary entry (cli.ts, register-bedrock.ts, restore-sandbox-env.ts)
├── cli/ # args.ts, file-processor.ts, initial-message.ts, list-models.ts, session-picker.ts, config-selector.ts
├── core/ # 32 files — see below; PRIMARY FORK SURFACE
│ ├── agent-session.ts # 3355-line session lifecycle, event emission, runtime
│ ├── extensions/ # Extension API: types.ts (1636 LOC), loader, runner, builtin/ — see AGENTS.md
│ ├── tools/ # Built-in tools (bash/edit/grep/find/ls/read/write) — see AGENTS.md
│ ├── compaction/ # Plugsuit-style compaction policy — see changes.md
│ ├── dynamic-prompt/ # buildDynamicSystemPrompt() — see changes.md
│ ├── export-html/ # session → HTML transcript renderer
│ ├── auth-{guidance,storage}.ts, sdk.ts, model-{registry,resolver}.ts, settings-manager.ts, …
│ └── changes.md # Core-level fork changes
├── modes/
│ ├── interactive/ # TUI mode — see AGENTS.md (35 components + interactive-mode.ts)
│ ├── rpc/ # JSONL RPC server (rpc-mode.ts, rpc-client.ts, jsonl.ts, rpc-types.ts)
│ └── print-mode.ts # One-shot non-interactive mode
├── utils/ # git, mime, clipboard, image, photon, version-check, …
└── docs/ # User-facing docs (extensions.md is 2262 lines of API ref)
test/
├── suite/
│ ├── harness.ts # MODERN test harness — use this
│ └── regressions/ # `<issue-number>-<slug>.test.ts` for upstream issues
├── test-harness.ts # Legacy harness
└── (~120 standalone .test.ts files)
| Task | First-choice path | Notes |
|---|---|---|
| Add tool | src/core/extensions/builtin/<name>/ |
Use pi.registerTool(). Core tools/ only for upstream-parity edits. |
| Add slash command | builtin extension | pi.registerCommand(). Never edit src/core/slash-commands.ts. |
| Add CLI flag | builtin extension pi.registerFlag() |
Or src/cli/args.ts if it must mirror upstream behavior |
| Modify session lifecycle | src/core/agent-session.ts |
High-conflict; document any change in core/changes.md |
| Replace system prompt | extension before_agent_start |
Or src/core/dynamic-prompt/build.ts (already modified — see changes.md) |
| Custom compaction logic | extension on("session_before_compact") |
Or src/core/compaction/ for policy constants |
| Add TUI component | src/modes/interactive/components/ |
35 existing components — match local style |
| Add regression test | test/suite/regressions/<issue>-<slug>.test.ts |
Use test/suite/harness.ts, never real APIs |
- Discovery: builtin (
builtin/index.ts) +.pi/extensions/,.senpi/extensions/,~/.pi/agent/extensions/,settings.jsonpaths,-eCLI flag. - Loading:
extensions/loader.ts— single sharedjitiimporter (changes.md2026-05-08), aliases@mariozechner/pi-*→ workspace packages. - Factory:
export default function(pi: ExtensionAPI) { … }runs at load time. - Binding:
ExtensionRunner.bindCore()connectspi.*stubs to real implementations. - Events:
session_start→resources_discover→ tool/command/UI events →session_shutdown. - Reload:
session_shutdown→ reload changed files → re-run factories →session_start({ reason: "reload" }).
- Tool shape: TypeBox schema +
execute(input, ctx)+renderCall+renderResult. Matchcore/tools/patterns; seecore/tools/AGENTS.md. - No built-in MCP / permission popups / plan mode / todos in core — pi philosophy. The fork's
permission-system,compaction,prompt-preset, andtodowritefeatures live as builtin extensions, not core. - Keybindings always configurable —
DEFAULT_EDITOR_KEYBINDINGS/DEFAULT_APP_KEYBINDINGSare the source of truth. - Faux provider for tests — never spend a real token in
npm test. Useharness.ts+pi-ai/faux. - Inlined UUIDv7 in
core/session-manager.ts— do NOT re-add theuuidpackage. Documented inchanges.md2026-04-17. - Branding: package name
@code-yeongyu/senpi, app namesenpi, configDir.senpi. Self-update target iscode-yeongyu/senpi.
- Touching
src/core/extensions/types.tswithout anextensions/changes.mdentry — the public extension API is the fork's most-watched contract. - Hardcoding key bindings.
- Real LLM API in tests.
- Adding "would-be-an-extension" features to
core/— bloats merge surface and violates pi's philosophy. - Re-running
prepublishOnlyto "fix" CI — it intentionally rebuilds dist + chmod's binaries; only run during release. - Editing
dist/checked-in stubs (none here, but seepackages/{mom,pods}/).
- The MODERN test harness is
test/suite/harness.ts.test/test-harness.tsis legacy and only kept for already-converted suites. - Test docs:
test/suite/README.md(harness-based suite rules),test/integration/README.md(API-key-gated live tests),test/fixtures/compaction/README.md(per-feature compaction fixtures). docs/extensions.mdis the 2262-line capability reference. Read it before claiming "no extension hook can do X".examples/extensions/ships canonical extension reference implementations (sandbox, custom-provider-anthropic, custom-provider-gitlab-duo, with-deps).- The Bun binary build (
build:binary) compilesdist/bun/cli.jsinto a single executable;copy-binary-assetscopies fonts/themes/templates intodist/.