one-code-extension 0.1.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +200 -172
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +126 -0
- package/docs/guide/configuration.md +171 -0
- package/docs/guide/differences-from-claude-code.md +150 -0
- package/docs/guide/doctor.md +86 -0
- package/docs/guide/hooks.md +109 -0
- package/docs/guide/installation.md +124 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
- package/docs/guide/providers-and-models.md +194 -0
- package/docs/guide/reference.md +224 -0
- package/docs/guide/sessions-and-context.md +190 -0
- package/docs/guide/skills-plugins-and-mcp.md +176 -0
- package/docs/guide/subagents-and-workflows.md +193 -0
- package/docs/guide/tasks-and-background-work.md +111 -0
- package/docs/guide/terminal-interface.md +155 -0
- package/docs/guide/tools.md +175 -0
- package/docs/guide/troubleshooting.md +175 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -202
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- package/extensions/workflow/permission-gate.ts +0 -71
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Branding, themes & startup
|
|
2
|
-
|
|
3
|
-
Part of [Decisions](../decisions.md).
|
|
4
|
-
|
|
5
|
-
## Branding without a fork
|
|
6
|
-
|
|
7
|
-
pi's startup banner ("pi v0.83.0 … Ask it how to use or extend Pi") is built from
|
|
8
|
-
its `piConfig`, which resolves from pi's own installed `package.json` — a
|
|
9
|
-
dependent package cannot change it, as recorded in
|
|
10
|
-
[`distribution.md`](distribution.md#distribution-pi-package-not-a-wrapper-binary).
|
|
11
|
-
But `ctx.ui.setHeader()`
|
|
12
|
-
replaces the header component outright, which reaches the same result:
|
|
13
|
-
`extensions/branding/` renders the package name, version, key hints and current
|
|
14
|
-
model/mode instead, and sets the terminal title. `CC_NO_BANNER=1` restores pi's.
|
|
15
|
-
|
|
16
|
-
Implementation note: a pi-tui `Component` is just `render(width)` plus
|
|
17
|
-
`invalidate()`, so the header is an inline object literal — no dependency on
|
|
18
|
-
pi-tui needed. It only applies when `ctx.mode === "tui"`; print and rpc modes have
|
|
19
|
-
no chrome to replace.
|
|
20
|
-
|
|
21
|
-
Verified rendering with `test/e2e/tui-capture.sh`:
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
pi-claude-code v0.1.0 Claude Code on the pi harness
|
|
25
|
-
escape interrupt · ctrl+c/ctrl+d clear/exit · / commands · ! bash · ctrl+o more
|
|
26
|
-
model claude-sonnet-5 · mode default
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
This is the cosmetic half of what a fork would buy. The other half — the `.pi`
|
|
30
|
-
config directory name — stays as it is; see the integration-shapes section in
|
|
31
|
-
`findings.md` for when that would justify forking.
|
|
32
|
-
|
|
33
|
-
## Themes: authored, not adopted
|
|
34
|
-
|
|
35
|
-
The ecosystem has no Claude Code *theme*. What it has are Claude Code-flavoured UI
|
|
36
|
-
**extensions** (`pi-cc-extensions`, `pi-claude-code-tui`, `pi-cc-header`) — code,
|
|
37
|
-
with the dependency risk already declined in the Phase 7 notes.
|
|
38
|
-
|
|
39
|
-
A pi theme, by contrast, is a JSON file of 51 colour tokens: data, and squarely in
|
|
40
|
-
the "cheaper to own than to depend on" half of the checklist. So `themes/` ships
|
|
41
|
-
two of our own, `claude-code` and `claude-code-light`, declared through
|
|
42
|
-
`pi.themes` in the manifest. The palette is a warm clay accent (`#d97757` dark,
|
|
43
|
-
`#c05f38` light) over neutral surfaces — an approximation of Claude Code's
|
|
44
|
-
terminal look, not values extracted from it.
|
|
45
|
-
|
|
46
|
-
`test/unit/themes.test.ts` validates every bundled theme: all 51 required tokens
|
|
47
|
-
present, no unknown tokens beyond the two optional ones, every `vars` reference
|
|
48
|
-
resolvable, and colours well-formed. pi rejects an incomplete theme at load time
|
|
49
|
-
without saying which token is missing, so this catches typos before a user does.
|
|
50
|
-
|
|
51
|
-
Both were then verified with `test/e2e/tui-capture.sh`, which runs pi inside tmux
|
|
52
|
-
to get a real pty: the startup screen lists `[Themes] claude-code,
|
|
53
|
-
claude-code-light` (so `pi.themes` discovery works for an installed package), and
|
|
54
|
-
with the theme selected the clay accent appears as a 24-bit escape in the rendered
|
|
55
|
-
output.
|
|
56
|
-
|
|
57
|
-
## Rebrand: pincer
|
|
58
|
-
|
|
59
|
-
Anthropic's branding guidelines (published on the Claude Agent SDK docs, seen
|
|
60
|
-
2026-08) prohibit "Claude Code" product names, Claude Code-mimicking ASCII
|
|
61
|
-
art/visual elements, and products that appear to be Claude Code. They formally
|
|
62
|
-
address SDK partners — which this project is not — but the substance is
|
|
63
|
-
ordinary trademark hygiene and applies to anything published.
|
|
64
|
-
|
|
65
|
-
What changed (2026-08-05): package `pi-claude-code` → **`pincer-agent`**
|
|
66
|
-
(brand "pincer" — contains *pi*, means *claw*; plain `pincer` is taken on npm
|
|
67
|
-
by an old static file server). Banner `NAME`, system-prompt identity ("You are
|
|
68
|
-
pincer"), MCP client name, and web_fetch User-Agent follow. The banner mascot
|
|
69
|
-
— previously a deliberate homage to Claude Code's — is redrawn as a pixel π,
|
|
70
|
-
which is ours outright. Themes renamed `claude-code`/`claude-code-light` →
|
|
71
|
-
`pincer`/`pincer-light` (users with `"theme": "claude-code"` in pi settings
|
|
72
|
-
must re-select). Event channels renamed `pi-claude-code:*` → `pincer:*` —
|
|
73
|
-
done now, pre-publish, because the channel names are a documented third-party
|
|
74
|
-
contract that would be painful to change later.
|
|
75
|
-
|
|
76
|
-
What deliberately stays: every *descriptive* reference — "the Claude Code
|
|
77
|
-
experience on the pi harness", "reads your Claude Code settings.json" — is
|
|
78
|
-
nominative use (truthfully naming the thing we are compatible with), which
|
|
79
|
-
trademark law and the guidelines themselves permit. The fidelity references in
|
|
80
|
-
`tools/` and `payload.json` are technical reference data, not branding, and
|
|
81
|
-
are untouched.
|
|
82
|
-
|
|
83
|
-
## Rebrand: One Code
|
|
84
|
-
|
|
85
|
-
What changed (2026-08-09): brand **pincer** → **One Code**; npm package
|
|
86
|
-
`pincer-agent` → **`one-code`**. Reason: plainer, more memorable name. Rejected
|
|
87
|
-
alternatives: `pi-code` (taken on npm), `onecode` (taken); `one-code` was free
|
|
88
|
-
and mirrors `claude-code`'s hyphenated npm style. The banner wordmark was
|
|
89
|
-
redrawn as a stacked ANSI-Shadow "ONE" over "CODE" (narrower than the previous
|
|
90
|
-
mark, so it never shears on standard-width terminals — verified live via
|
|
91
|
-
`test/e2e/tui-capture.sh`).
|
|
92
|
-
|
|
93
|
-
Followed the same surface as the pincer rebrand: banner `NAME`/title/wordmark,
|
|
94
|
-
system-prompt identity ("You are One Code"), MCP client name, `web_fetch`
|
|
95
|
-
User-Agent, themes (`pincer`/`pincer-light` → `one-code`/`one-code-light` —
|
|
96
|
-
users with the old theme selected must re-select), and event channels
|
|
97
|
-
(`pincer:*` → `one-code:*`, locked pre-publish since the channel names are a
|
|
98
|
-
third-party contract). This pass went further than the pincer one and also
|
|
99
|
-
renamed the state directory `~/.pincer` → `~/.one-code`, the env var
|
|
100
|
-
`PINCER_STATE_DIR` → `ONE_CODE_STATE_DIR`, and the internal `pincerStateDir()`
|
|
101
|
-
helper → `oneCodeStateDir()`; existing users lose in-flight plans and hook
|
|
102
|
-
approvals under the old dir, acceptable pre-publish. The GitHub repo was
|
|
103
|
-
renamed `pincer-agent` → `one-code` to match (GitHub keeps a redirect from the
|
|
104
|
-
old path).
|
|
105
|
-
|
|
106
|
-
What deliberately stays: every *descriptive* "the Claude Code experience"
|
|
107
|
-
reference (nominative use), and the historical records above (the plan
|
|
108
|
-
snapshot, the `pi-claude-code → pincer` history) — rewriting them would falsify
|
|
109
|
-
the record.
|
|
110
|
-
|
|
111
|
-
## Startup listing: quietStartup + banner sections
|
|
112
|
-
|
|
113
|
-
pi's startup resource listing has no per-section switch, and its [Extensions]
|
|
114
|
-
section names all twenty of this package's internal modules — noise to an end
|
|
115
|
-
user of the packaged product. The only lever pi offers is `quietStartup: true`
|
|
116
|
-
(global setting), which hides the entire listing but leaves a `setHeader`
|
|
117
|
-
banner alone (verified in tmux). `resourceLoader` is not exposed to
|
|
118
|
-
extensions, so the useful sections cannot be read back; instead the banner
|
|
119
|
-
re-derives compact `context` / `skills` / `themes` lines (the blessed
|
|
120
|
-
re-derive pattern): context files via the git-root walk, skills from the
|
|
121
|
-
Claude Code dirs — `existsSync` on `<dir>/<name>/SKILL.md` rather than
|
|
122
|
-
`isDirectory()`, because skill directories can be symlinks — plus namespaced
|
|
123
|
-
plugin skills from `discoverPlugins` (which pi's own listing misses, since the
|
|
124
|
-
plugins extension exposes those only through the `skill` tool), and themes
|
|
125
|
-
from the package's own directory. The sections render only when
|
|
126
|
-
`quietStartupEnabled()` reads true from pi's settings, so pi's listing and
|
|
127
|
-
ours never both appear.
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Code review remediation
|
|
2
|
-
|
|
3
|
-
The first whole-codebase pass of `/simplify` + `/code-review` (2026-08-10), and
|
|
4
|
-
what came of it. Run report-first over the entire source tree (the code had
|
|
5
|
-
never been reviewed). Seven correctness findings were confirmed by an
|
|
6
|
-
independent verify pass; ~22 quality cleanups were applied and the more invasive
|
|
7
|
-
refactors deferred.
|
|
8
|
-
|
|
9
|
-
## Auto-mode gate hardening: four false "safe" verdicts closed (2026-08-10)
|
|
10
|
-
|
|
11
|
-
The shell pre-gate's contract is that it may **only ever conclude "safe", never
|
|
12
|
-
deny** — a gap costs one classifier call, never a bypass (see
|
|
13
|
-
[`auto-mode.md`](auto-mode.md)). The review found four commands reaching "safe"
|
|
14
|
-
without their real effect being weighed, plus one allow-rule class skipping the
|
|
15
|
-
classifier entirely:
|
|
16
|
-
|
|
17
|
-
- **A bare redirect with no command word** (`> /etc/hosts`) — the analyze loop
|
|
18
|
-
`continue`d on an empty command name before it ever looked at the segment's
|
|
19
|
-
redirects.
|
|
20
|
-
- **A read-only command's redirect** (`git log > outside.txt`) — the git/cd
|
|
21
|
-
branches `continue`d before the write-target check ran.
|
|
22
|
-
- **`git -C <dir>`** whose directory escapes the working tree — the global-flag
|
|
23
|
-
loop skipped `-C` and its value without validating the target.
|
|
24
|
-
- **`sort -o <file>`** — `sort` is fast-pathed as read-only, but `-o` writes an
|
|
25
|
-
arbitrary path.
|
|
26
|
-
- **`Bash(sh -c *)` / `python -c *` / `node -e *`** allow rules — `isBroadExecutionRule`
|
|
27
|
-
only recognised `run|exec|x` as a runner's escape hatch, so interpreter
|
|
28
|
-
inline-code flags matched and returned `allow` without ever reaching the
|
|
29
|
-
classifier.
|
|
30
|
-
|
|
31
|
-
**Fix keeps the contract.** Every change converts a false "safe" into an
|
|
32
|
-
escalation — none adds a deny path. The structural fix for the two redirect
|
|
33
|
-
gaps was to lift redirect checks to run **first for every segment** (a shared
|
|
34
|
-
`checkWriteTarget` closure), so no command-specific `continue` can skip a write
|
|
35
|
-
target — deeper than patching the bare-redirect case alone. `git -C`/`--git-dir`/
|
|
36
|
-
`--work-tree` now escalate when their directory leaves the working tree; `sort -o`
|
|
37
|
-
escalates as a write; interpreter `-c`/`-e` flags are treated as broad (which, if
|
|
38
|
-
it over-flags a runner's unrelated `-c`, only costs a classifier call — the safe
|
|
39
|
-
direction). All five are covered by regression tests
|
|
40
|
-
(`test/unit/auto-mode-shell.test.ts`, `test/unit/matcher.test.ts`).
|
|
41
|
-
|
|
42
|
-
**Live-verified both directions** (2026-08-10, `openai-codex/gpt-5.6-luna` in
|
|
43
|
-
`--permission-mode auto`, `logDecisions` on): plainly-safe commands (`cat`,
|
|
44
|
-
`git status`) resolved on the deterministic pre-gate (`source: pre-gate`), while
|
|
45
|
-
the two newly-covered commands (`sort -o …`, `git -C /etc status`) routed to the
|
|
46
|
-
classifier (`source: classifier`) — i.e. escalated rather than fast-pathed to
|
|
47
|
-
"safe", the fix's whole point. The classifier then allowed both (an in-cwd sort
|
|
48
|
-
and a read-only status are not dangerous); the fix owns the escalation, the
|
|
49
|
-
classifier owns the verdict.
|
|
50
|
-
|
|
51
|
-
## Worktree vs. permission rules — preserve-original, not reorder (2026-08-10)
|
|
52
|
-
|
|
53
|
-
A worktree session rewrites bash commands into `cd '<wt>' && (…)` on the
|
|
54
|
-
`tool_call` hook, and worktree loads before permissions, so the gate saw the
|
|
55
|
-
wrapped command — defeating every configured `Bash(...)` allow/ask/deny rule for
|
|
56
|
-
the whole session.
|
|
57
|
-
|
|
58
|
-
**Chosen: preserve-original.** worktree stashes the model's pre-wrap command on
|
|
59
|
-
`event.input` (`ORIGINAL_COMMAND_KEY`); only rule-matching reads it, while the
|
|
60
|
-
classifier and safety floor keep seeing the wrapped command (whose `cd` makes
|
|
61
|
-
containment resolve inside the worktree).
|
|
62
|
-
|
|
63
|
-
**Rejected: reorder permissions ahead of worktree** — the mechanism the hooks
|
|
64
|
-
decision used for "matchers see the model's original call"
|
|
65
|
-
([`hooks.md`](hooks.md)). It would have shifted worktree-session protected-path
|
|
66
|
-
containment from the actual write location (the worktree) back to the original
|
|
67
|
-
checkout, a semantic change worse than the bug (which fails toward *more*
|
|
68
|
-
prompting/classifier review — the safe direction).
|
|
69
|
-
|
|
70
|
-
## Background subagents now get the holistic review (2026-08-10)
|
|
71
|
-
|
|
72
|
-
Auto mode's third subagent checkpoint — reviewing a finished child's action
|
|
73
|
-
*sequence* as a whole ("read the deploy config, read a token, open a PR") — only
|
|
74
|
-
fired for foreground runs, because it attaches to the spawning call's
|
|
75
|
-
`tool_result`, which a background/resident run has already returned.
|
|
76
|
-
|
|
77
|
-
**Chosen: review-on-receipt.** Background turns now emit their action sequence;
|
|
78
|
-
the gate reviews it on receipt (shared `reviewCompletedRun`, using the last live
|
|
79
|
-
context) and surfaces any concern as its own follow-up notification, alongside
|
|
80
|
-
the completion report. The foreground path is unchanged; both share one helper.
|
|
81
|
-
|
|
82
|
-
## Next-turn reminders no longer lost without a user message (2026-08-10)
|
|
83
|
-
|
|
84
|
-
`reminderQueue.drain()` cleared the next-turn queue unconditionally, but
|
|
85
|
-
`injectReminders` is a no-op when there is no user message to attach to — so a
|
|
86
|
-
`context` event on a user-message-less array silently dropped every pending
|
|
87
|
-
reminder. The gate now only drains once a user message exists, honoring the
|
|
88
|
-
module's own documented re-enqueue contract.
|
|
89
|
-
|
|
90
|
-
## Quality cleanups, and what was deferred
|
|
91
|
-
|
|
92
|
-
~22 reuse/dedup/dead-code cleanups were applied; their rationale is the drift
|
|
93
|
-
they remove. Highlights: shared `lib/` helpers (`isWritingTool` hoisted to the
|
|
94
|
-
lower layer, `projectMemoryDir`, `restoreLatestDetails`, `perCwd`,
|
|
95
|
-
`safeThemePaint`, `tryReadFile`, notification-format constants); dead code
|
|
96
|
-
removed (workflow `AgentCallMeta`/`tokenUsage`/`ReplayCursor.totals()`, the
|
|
97
|
-
misleading `readCapped` size branch); two efficiency wins (MCP connect via
|
|
98
|
-
`Promise.allSettled`, `findRoleProfileModel` no longer double-computes
|
|
99
|
-
containment on the classifier hot path). A behavior fix surfaced by the review:
|
|
100
|
-
**`CLAUDE_CONFIG_DIR` is now honored** for CLAUDE.md/skills/commands discovery
|
|
101
|
-
(`claude-context`, `claude-compat`), matching real Claude Code.
|
|
102
|
-
|
|
103
|
-
**Deferred** as focused follow-ups — internal structure or low value, zero
|
|
104
|
-
correctness impact, high edit-risk to batch here: the `subagents/index.ts`
|
|
105
|
-
`execute()` split and its sibling helpers, background-worktree parallelization,
|
|
106
|
-
the notify-envelope consolidation across five dirs, and a few minor dedups.
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
# Compaction
|
|
2
|
-
|
|
3
|
-
Part of [Decisions](../decisions.md).
|
|
4
|
-
|
|
5
|
-
## Compaction runs Claude Code's prompt, via session_before_compact
|
|
6
|
-
|
|
7
|
-
pi hardcodes its own summarization prompt (settings only expose
|
|
8
|
-
enabled/reserveTokens/keepRecentTokens), but `session_before_compact` lets an
|
|
9
|
-
extension supply the whole `CompactionResult` — so One Code now compacts the
|
|
10
|
-
way Claude Code does. Three design points:
|
|
11
|
-
|
|
12
|
-
- **The prompt**: appended as user text after the conversation; the model
|
|
13
|
-
answers `<analysis>` + `<summary>`, and only the `<summary>` content
|
|
14
|
-
survives. `extractSummary` matches tags at *line starts* and spans to the
|
|
15
|
-
last closing tag; an untagged reply is used whole minus any analysis block —
|
|
16
|
-
losing a compaction to a formatting slip is worse than untidy text. Empty
|
|
17
|
-
means failure. Trap, caught live: the first run's analysis said "wrapped in
|
|
18
|
-
`<analysis>` and `<summary>` tags", a loose regex anchored on that inline
|
|
19
|
-
mention, and the stored summary began with the tail of the analysis — prose
|
|
20
|
-
*about* the format is indistinguishable from the format unless tags are
|
|
21
|
-
line-anchored.
|
|
22
|
-
- **The model — the session model, for the cache**: Claude Code keeps the
|
|
23
|
-
session's system prompt, tool definitions, and message prefix intact,
|
|
24
|
-
appends the instruction, and runs the call on the session's own model. That
|
|
25
|
-
is not thrift on the model choice — it is thrift on the *tokens*: the
|
|
26
|
-
summarization call replays the
|
|
27
|
-
already-cached prefix, so it is mostly cache reads. The extension does the
|
|
28
|
-
same: session model, `ctx.getSystemPrompt()`, the active tool definitions in
|
|
29
|
-
active order (a mismatch there costs cache hits, never correctness), and — the
|
|
30
|
-
key to actually hitting the cache — the session's *last real outgoing
|
|
31
|
-
messages*, captured from the `context` event (see the cache section below)
|
|
32
|
-
rather than reconstructed from entries, with the instruction as a
|
|
33
|
-
final user message: one `<system-reminder>` holding the trigger notice
|
|
34
|
-
("the user has triggered a /compact command" vs "the conversation context
|
|
35
|
-
window is running out") directly above the verbatim instruction, preceded by
|
|
36
|
-
any `/compact <instructions>` as a "## Compact Instructions" reminder. The
|
|
37
|
-
stored summary gets a continuation preamble baked in ("This session is being
|
|
38
|
-
continued from a previous conversation that ran out of context…") plus a
|
|
39
|
-
pointer to the session JSONL with a NEVER-read-it-whole warning — details
|
|
40
|
-
the summary lost stay grepable instead of gone, and `read`/`grep` are
|
|
41
|
-
safe-tier tools, so following the pointer never prompts, auto mode included.
|
|
42
|
-
All inside pi's own hardcoded `<summary>` frame. A previous compaction's summary lives
|
|
43
|
-
in the *live* context but is excluded from `messagesToSummarize`
|
|
44
|
-
(prepareCompaction starts after that entry, handing the text over
|
|
45
|
-
separately) — it is reattached as the leading `compactionSummary` message,
|
|
46
|
-
the exact shape the live context carries, so convertToLlm renders the same
|
|
47
|
-
bytes the cached prefix holds and re-compactions stay cache reads too.
|
|
48
|
-
- **Reasoning mirrors the session's thinking level.** The call sends
|
|
49
|
-
`reasoning: ctx.thinkingLevel` (with `off`→none). This began as a deliberate
|
|
50
|
-
omission — `reasoning` fails closed on providers pi's compat data mispredicts,
|
|
51
|
-
the classifier's documented trade — but that reasoning does not apply here and
|
|
52
|
-
omitting it turned out to be the last thing keeping the cache cold (see below).
|
|
53
|
-
Unlike the classifier, compaction runs the *session's own* model, already
|
|
54
|
-
proven to accept the level, and `streamSimple` clamps an unsupported level down
|
|
55
|
-
rather than erroring — so mirroring never fails closed. It also restores the
|
|
56
|
-
extended-thinking behavior for free.
|
|
57
|
-
|
|
58
|
-
All three reasons (manual, threshold, overflow) take this path; any failure —
|
|
59
|
-
no auth, timeout, empty summary — returns nothing and pi's own compaction
|
|
60
|
-
serves: a different summary style, never a broken compaction. `CC_COMPACTION=0`
|
|
61
|
-
opts out entirely.
|
|
62
|
-
|
|
63
|
-
Verified live on a Luna session with the final shape: two /compact runs in one
|
|
64
|
-
session (48,356 then 30,838 tokens) both stored fromHook entries — preamble
|
|
65
|
-
first, transcript pointer with the warning, all nine sections, no tag bleed
|
|
66
|
-
(inline backtick *mentions* of the tags in the second summary are content, and
|
|
67
|
-
line-anchored extraction correctly ignored them).
|
|
68
|
-
|
|
69
|
-
## The cache miss, and the two-part fix (2026-08-07)
|
|
70
|
-
|
|
71
|
-
For a while the summarization call read `cacheRead: 0` on openai-codex even
|
|
72
|
-
though the surrounding session sat at ~78–96% cache hits. Passing
|
|
73
|
-
`options.sessionId` (pi's prompt_cache_key / session-affinity carrier, which
|
|
74
|
-
completeSimple otherwise omits) was necessary but not sufficient. Two things
|
|
75
|
-
were wrong, both now fixed:
|
|
76
|
-
|
|
77
|
-
1. **Reconstruction diverged from the cached prefix at message one.** The
|
|
78
|
-
request was rebuilt from session *entries*, but per-request `<system-reminder>`
|
|
79
|
-
injections (the memory index, the every-turn reminders) are added at request
|
|
80
|
-
time and never become entries — so no reconstruction can reproduce them. The
|
|
81
|
-
fix is *capture, not reconstruct*: a `context`-event listener stashes the
|
|
82
|
-
exact `AgentMessage[]` the session last handed the provider (a held reference —
|
|
83
|
-
pi `structuredClone`s the array per turn, and compaction is the last `context`
|
|
84
|
-
handler, so it is the array actually sent). pi's agent loop builds a request as
|
|
85
|
-
`transformContext` then `convertToLlm`; running the *same* `convertToLlm` on
|
|
86
|
-
the captured array makes the message prefix byte-identical, and appending only
|
|
87
|
-
the instruction leaves that whole prefix a cache read. The entry path survives
|
|
88
|
-
as a fallback for when no turn has run yet (e.g. `/compact` first thing in a
|
|
89
|
-
resumed session).
|
|
90
|
-
|
|
91
|
-
2. **The reasoning config is part of the cache identity.** Even with a
|
|
92
|
-
byte-identical prefix, `prompt_cache_key`, tools, and system prompt, the
|
|
93
|
-
request *still* missed — because it omitted `reasoning` while the session's
|
|
94
|
-
normal requests sent it. On the Responses API that is a different request
|
|
95
|
-
configuration and the cached prefix is not reused. Found by dumping the last
|
|
96
|
-
normal payload and the compaction payload and diffing: everything matched
|
|
97
|
-
except `reasoning`. Fixed by mirroring the session thinking level (above).
|
|
98
|
-
|
|
99
|
-
Measured after the fix (thinking high, ~48–56k-token context):
|
|
100
|
-
|
|
101
|
-
| provider / model | before | after |
|
|
102
|
-
|---|---|---|
|
|
103
|
-
| openai-codex `gpt-5.6-luna` | `input 49776, cacheRead 0` | `input 2094, cacheRead 47616` |
|
|
104
|
-
| anthropic `claude-haiku-4-5` | — | `input 10, cacheRead 13042, cacheWrite 44287` |
|
|
105
|
-
| openrouter `deepseek/deepseek-v4-flash` | — | `input 1402, cacheRead 51200` |
|
|
106
|
-
|
|
107
|
-
openai-codex and deepseek get near-total prefix reuse immediately (automatic
|
|
108
|
-
prefix caching keyed by `prompt_cache_key`, no explicit breakpoints). Anthropic
|
|
109
|
-
needed a third fix. At first it reused only ~13k (≈ system + tools) and re-cached
|
|
110
|
-
the ~44k of history. Two dead-end explanations were ruled out by experiment
|
|
111
|
-
before the real one: it is **not** `cache_control` breakpoint placement (adding
|
|
112
|
-
an explicit anchor on the history's last block changed nothing — still 13k) and
|
|
113
|
-
**not** the reasoning param (identical in both). The confirmed cause, found by
|
|
114
|
-
diffing the compaction wire payload against the session's last normal payload and
|
|
115
|
-
by A/B-ing `CC_CLEAR_THINKING`:
|
|
116
|
-
|
|
117
|
-
- The `context-management` extension (default-on for first-party Anthropic) puts
|
|
118
|
-
`context_management: { edits: [clear_thinking_20251015] }` on every agent-loop
|
|
119
|
-
request plus the `context-management-2025-06-27` beta. So the session's cached
|
|
120
|
-
**message** prefix is stored in the thinking-cleared form.
|
|
121
|
-
- `completeSimple` bypasses `before_provider_headers`/`before_provider_request`,
|
|
122
|
-
so the compaction request omitted both — sending full thinking blocks with no
|
|
123
|
-
`clear_thinking`. On Anthropic that mismatch invalidates the *message* cache
|
|
124
|
-
(system+tools are unaffected, hence they still read), so the whole history
|
|
125
|
-
re-caches: cacheRead ~13k, cacheWrite ~44k.
|
|
126
|
-
- Fix: when `clearThinkingEnabled(...)` is true, the compaction call replays the
|
|
127
|
-
same beta header and `clear_thinking` body edit (reusing context-management's
|
|
128
|
-
exported helpers), so its message prefix matches the session's cache. Measured
|
|
129
|
-
after: `input 10, cacheRead 56032, cacheWrite 1460` on `claude-haiku-4-5:high`
|
|
130
|
-
— near-total reuse, matching the other providers.
|
|
131
|
-
|
|
132
|
-
So the compaction request must be a faithful replay of the session's request in
|
|
133
|
-
*three* dimensions the naive shape got wrong: the request-time reminder
|
|
134
|
-
injections (capture, not reconstruct), the reasoning config (Responses-API cache
|
|
135
|
-
identity), and — on Anthropic — the context-management beta + `clear_thinking`
|
|
136
|
-
edit. All three providers now produce valid fromHook summaries with near-total
|
|
137
|
-
cache reuse, and none fail-closed. (Any provider-specific request mutation an
|
|
138
|
-
extension adds via `before_provider_*` is invisible to `completeSimple`; a future
|
|
139
|
-
compaction refactor that routed through the same request builder would get these
|
|
140
|
-
for free — a point for the upstream pi PR.)
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
# Distribution & dependencies
|
|
2
|
-
|
|
3
|
-
Part of [Decisions](../decisions.md).
|
|
4
|
-
|
|
5
|
-
## Two artifacts from one codebase: the app and the package (2026-08-10)
|
|
6
|
-
|
|
7
|
-
One Code ships two npm packages built from this single repo — **both live on
|
|
8
|
-
npm since 2026-08-10 at 0.1.2** (GitHub release `v0.1.2`):
|
|
9
|
-
|
|
10
|
-
| npm name | What it is | pi relationship |
|
|
11
|
-
|---|---|---|
|
|
12
|
-
| **`@one-ai/one-code`** (`app/`) | the bundled app — a `one-code` bin | **exact-pins** pi (`0.84.1`) as a dependency |
|
|
13
|
-
| **`one-code-extension`** (repo root) | the pi package | **peerDependency** range; runs on the user's own pi |
|
|
14
|
-
|
|
15
|
-
The split exists because the two installs need opposite dependency stances
|
|
16
|
-
(reproducible pin vs. ride-the-user's-pi), and nothing else: the app is a thin
|
|
17
|
-
launcher over the same extension set. `one-code-extension` was chosen over
|
|
18
|
-
`one-code-pi`, which reads as "one-code *with* pi bundled" — the opposite of
|
|
19
|
-
what it is. The app is scoped under the **`one-ai`** npm org because npm's
|
|
20
|
-
typosquat guard rejects unscoped `one-code` (an existing `onecode` package
|
|
21
|
-
blocks every punctuation variant — discovered only at publish time; a 404 on
|
|
22
|
-
`npm view` does not mean a name is claimable). The *command* the app installs
|
|
23
|
-
is still `one-code`; the extension package stays unscoped so
|
|
24
|
-
`pi install npm:one-code-extension` reads clean, and is attached to the org
|
|
25
|
-
via team access rather than a scope. The npm org (`one-ai`) and the planned
|
|
26
|
-
GitHub org (`oneai-hq` — `one-ai` was taken on GitHub) deliberately differ:
|
|
27
|
-
registry namespaces are independent, install commands only ever cite the npm
|
|
28
|
-
scope, and `repository` fields point at URLs, so the mismatch costs nothing
|
|
29
|
-
beyond a profile cross-link. A Homebrew tap (`brew install IsuruMaduranga/one-code/one-code`)
|
|
30
|
-
delivers the same app and solves the Node ≥22.19 requirement via
|
|
31
|
-
`depends_on "node"`; a single compiled binary is not viable (pi-tui ships
|
|
32
|
-
per-platform native modules).
|
|
33
|
-
|
|
34
|
-
**How the app loads the extensions — package registration, not
|
|
35
|
-
`extensionFactories`.** The bin sets `PI_CODING_AGENT_DIR=~/.one-code/agent`
|
|
36
|
-
(pi's env override for its config dir) and idempotently registers the
|
|
37
|
-
`one-code-extension` directory from its own `node_modules` as a local-path
|
|
38
|
-
package in the isolated `settings.json`, then calls pi's public `main(argv)`.
|
|
39
|
-
pi resolves local-path package sources **in place** (no copying), so the
|
|
40
|
-
manifest drives everything exactly as a `pi install` would: extensions in
|
|
41
|
-
their load-bearing order, `pi.themes`, and the bundled agents (which resolve
|
|
42
|
-
relative to the extension files). The alternative — passing our factories via
|
|
43
|
-
`main(argv, { extensionFactories })` — was prototyped and rejected: it loads
|
|
44
|
-
no themes without extra plumbing, and our extensions are TypeScript that only
|
|
45
|
-
pi's jiti loader handles; a plain Node bin cannot import them. Verified live
|
|
46
|
-
from packed tarballs: themes, agents, permission prompts, session resume, and
|
|
47
|
-
full state isolation (`~/.one-code` created, `~/.pi` untouched, and a stale
|
|
48
|
-
`packages` entry in a user's pi settings cannot double-load us because the
|
|
49
|
-
isolated dir never reads them).
|
|
50
|
-
|
|
51
|
-
**Version drift is guarded at runtime, not by the peer range.** pi renames
|
|
52
|
-
settings/flags between minors (0.83→0.84: `uiMode`→`tuiMode`, and 0.84 rejects
|
|
53
|
-
the old flag), so the package variant warns at startup when the running pi is
|
|
54
|
-
outside the tested range (`extensions/lib/pi-version.ts`, wired in branding's
|
|
55
|
-
`session_start`; fail-silent on unparseable versions). The app never warns —
|
|
56
|
-
its pin is inside the range by construction. The peer range stays loose
|
|
57
|
-
(`>=0.83.0`); a hard range would make `pi install` fail instead of warn.
|
|
58
|
-
|
|
59
|
-
**Branding: a surgical stdout rewrite in the bin.** pi prints its own command
|
|
60
|
-
name from `APP_NAME` (fork-gated, resolved from pi's *own* package.json) in
|
|
61
|
-
two plain-stdout places: the post-TUI resume hint and `--help`. Under
|
|
62
|
-
isolation the resume hint `pi --session <id>` is not just mis-branded but
|
|
63
|
-
broken (stock pi cannot see `~/.one-code` sessions), so the bin rewrites
|
|
64
|
-
` pi --session ` → ` one-code --session ` on that line (matched on the label,
|
|
65
|
-
which carries ANSI dim codes) and standalone-word `pi` in help output only.
|
|
66
|
-
Nothing else is touched — TUI escape streams pass through byte-identical.
|
|
67
|
-
Verified: the rewritten resume command actually resumes the session. The real
|
|
68
|
-
fix is an upstream `PI_APP_NAME` env override PR; the rewrite shrinks then.
|
|
69
|
-
|
|
70
|
-
**Updates: pi's check suppressed, ours added.** The bin sets
|
|
71
|
-
`PI_SKIP_VERSION_CHECK=1` (pi's endpoint is hardcoded to its own registry) and
|
|
72
|
-
injects one inline JS extension (`app/update-check.mjs`) via
|
|
73
|
-
`extensionFactories`: a non-blocking, fail-silent fetch of
|
|
74
|
-
`registry.npmjs.org/one-code/latest` on session start, notifying with an
|
|
75
|
-
install-aware hint (`brew upgrade one-code` when the bin resolves under a
|
|
76
|
-
Homebrew prefix, else `npm i -g @one-ai/one-code`). pi's semver helpers are not
|
|
77
|
-
exported from the package root, so the dotted-numeric compare lives in the
|
|
78
|
-
app. `one-code --version` reports the app version with the pinned pi in
|
|
79
|
-
parentheses. Both paths live-verified (drift warning and update notice each
|
|
80
|
-
fired against a real TUI).
|
|
81
|
-
|
|
82
|
-
**Versioning is lockstep.** Both packages share one version per release, even
|
|
83
|
-
when only one changed, and the app pins the extension at exactly its own
|
|
84
|
-
version — so one git tag (`vX.Y.Z`) names the whole release, GitHub Releases
|
|
85
|
-
map one-to-one, and a pin/version mismatch is an instantly visible release
|
|
86
|
-
mistake. Adopted at 0.1.2 (the app skipped 0.1.1 to converge); the cost is an
|
|
87
|
-
occasional republish of an unchanged package. The Homebrew tap is
|
|
88
|
-
`IsuruMaduranga/homebrew-one-ai` — named for the brand umbrella so future
|
|
89
|
-
tools join the same tap — giving `brew install isurumaduranga/one-ai/one-code`
|
|
90
|
-
(two-segment installs are not brew grammar; verified empirically). Two
|
|
91
|
-
release-process facts learned shipping 0.1.2: the formula's `url` + `sha256`
|
|
92
|
-
must be bumped by hand each release until a release workflow automates it,
|
|
93
|
-
and Homebrew's `std_npm_args` passes npm's supply-chain guard
|
|
94
|
-
`--min-release-age=1`, so **the brew install of a release only works ~24h
|
|
95
|
-
after its npm publish** — day-one users get "No matching version found …
|
|
96
|
-
with a date before <now−1d>"; either schedule the formula bump a day after
|
|
97
|
-
the npm publish or accept the gap.
|
|
98
|
-
|
|
99
|
-
Registering the package also seeds first-run settings (`theme: "one-code"`,
|
|
100
|
-
`quietStartup: true`, and — from 0.1.3 — `tuiMode: "fullscreen"`, the
|
|
101
|
-
alt-screen One Code look per the fullscreen decision in `tui.md`), which
|
|
102
|
-
doubles as skipping pi's stock first-time theme picker — the branded first
|
|
103
|
-
run. Seeded only when no settings file exists, so a user's later changes
|
|
104
|
-
stick. Three hardcoded `~/.pi` paths found during
|
|
105
|
-
this work (quiet-startup detection, the skills listing, the Anthropic-OAuth
|
|
106
|
-
check) now resolve through pi's `getAgentDir()`, which honours the isolation
|
|
107
|
-
env var.
|
|
108
|
-
|
|
109
|
-
## Distribution: pi package, not a wrapper binary
|
|
110
|
-
|
|
111
|
-
*(Partially superseded 2026-08-10 by the two-artifact model above: a wrapper
|
|
112
|
-
**app** now exists alongside the package. What still stands: never fork, and
|
|
113
|
-
the package variant remains first-class.)*
|
|
114
|
-
|
|
115
|
-
pi's `piConfig` rebranding (app name, config dir) resolves from pi's *own*
|
|
116
|
-
installed `package.json`, so a dependent package cannot rebrand it. Shipping as
|
|
117
|
-
a pi package (`pi install npm:pi-claude-code`) costs nothing extra and keeps
|
|
118
|
-
upstream pi upgrades a version bump away. All Claude-Code-shaped paths
|
|
119
|
-
(`.claude/settings.json`, `.claude/commands`, `.claude/agents`) are discovered
|
|
120
|
-
by our own code rather than by changing pi's `.pi` namespace.
|
|
121
|
-
|
|
122
|
-
## Community packages: adopted where they work
|
|
123
|
-
|
|
124
|
-
Per project directive, prefer ecosystem packages over new code.
|
|
125
|
-
|
|
126
|
-
- **Adopted:** `pi-ask-user` for the AskUserQuestion role (option lists,
|
|
127
|
-
multi-select, freeform, headless fallback). Bundled as a dependency and
|
|
128
|
-
re-exported from `extensions/ask-user`, so users get it automatically.
|
|
129
|
-
- **Rejected: `pi-subagents` (0.40.0).** Its child processes were SIGKILLed by
|
|
130
|
-
the parent ~29 ms after spawn in this environment (macOS, Node 26,
|
|
131
|
-
pi 0.83.0), in both print and RPC mode, with our extensions absent and the
|
|
132
|
-
same failure when spawning through an explicit `PI_SUBAGENT_PI_BINARY`
|
|
133
|
-
wrapper. The identical child command line runs fine standalone, so the fault
|
|
134
|
-
is in the package's parent-side lifecycle management, not our integration.
|
|
135
|
-
`extensions/subagents` is therefore our own implementation, modeled on pi's
|
|
136
|
-
official `examples/extensions/subagent`: spawn `pi --mode json -p`, parse the
|
|
137
|
-
event stream, return the child's final text. Worth re-evaluating the package
|
|
138
|
-
on a future release.
|
|
139
|
-
|
|
140
|
-
## Publish readiness (Phase 7)
|
|
141
|
-
|
|
142
|
-
*(Historical — the release shipped 2026-08-10 as described in the two-artifact
|
|
143
|
-
entry above. The tarball/install checks below were the single-package-era
|
|
144
|
-
method and remain the template for pre-publish verification.)*
|
|
145
|
-
|
|
146
|
-
`npm pack` produces a 69 kB / 47-file tarball: `extensions/`, `agents/`, `types/`,
|
|
147
|
-
README, LICENSE and `docs/decisions.md`. Every path in the `pi.extensions`
|
|
148
|
-
manifest is checked to be present in the tarball.
|
|
149
|
-
|
|
150
|
-
**A path install does not fetch dependencies.** Verified by installing the packed
|
|
151
|
-
copy from a directory: pi registered it, then every extension importing a
|
|
152
|
-
dependency failed to load (`Cannot find module '@modelcontextprotocol/sdk/...'`).
|
|
153
|
-
`pi install npm:<name>` is fine, because npm resolves the dependency tree — so
|
|
154
|
-
the README tells anyone installing from a checkout to run `npm install` first.
|
|
155
|
-
|
|
156
|
-
The published path was verified by simulating its layout: `npm install <tarball>`
|
|
157
|
-
into a scratch project (348 packages resolved), then `pi install
|
|
158
|
-
<scratch>/node_modules/pi-claude-code`. A fresh run listed all bundled and plugin
|
|
159
|
-
agents correctly.
|
|
160
|
-
|
|
161
|
-
**Portability**: `web_fetch` through `tool_search` was exercised on
|
|
162
|
-
`gpt-5.4`, `gpt-5.5` and `gpt-5.6-terra` — a pre-native-deferred-loading model,
|
|
163
|
-
a native one, and a newer family — all correct. Anthropic's `defer_loading` path
|
|
164
|
-
and the non-native fallback remain unexercised for lack of a credential.
|
|
165
|
-
|
|
166
|
-
**Declined:** the CC-style UI packages (`pi-cc-header`, `pi-cc-extensions`). They
|
|
167
|
-
are cosmetic and would add a dependency for appearance only, which fails our own
|
|
168
|
-
adopt-vs-build checklist.
|
package/docs/decisions/hooks.md
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# Hooks
|
|
2
|
-
|
|
3
|
-
Part of [Decisions](../decisions.md).
|
|
4
|
-
|
|
5
|
-
## Hooks: own the mechanism, port the best of three references
|
|
6
|
-
|
|
7
|
-
Claude Code command hooks were the biggest remaining compat gap — real setups
|
|
8
|
-
carry `PreToolUse`/`PostToolUse`/`UserPromptSubmit` hooks in settings.json and
|
|
9
|
-
plugins ship `hooks/hooks.json`. Three MIT community implementations were
|
|
10
|
-
studied before building (`extensions/hooks/`); none survived as a dependency,
|
|
11
|
-
all three contributed:
|
|
12
|
-
|
|
13
|
-
- **`pi-code`** (ilovepixelart): the executor hardening — absolute `/bin/sh`,
|
|
14
|
-
`detached: true` + SIGKILL to the negative pid so grandchildren holding the
|
|
15
|
-
stdio pipes die with the timeout, utf8 stream decoding, 1MB output caps,
|
|
16
|
-
`CLAUDE_PROJECT_DIR`, the Node timer-overflow clamp — plus reading real
|
|
17
|
-
`.claude/settings.json` and the deliberate **ask→block** fail-closed mapping
|
|
18
|
-
(pi's tool_call is allow-or-block; ask-as-allow is the unsafe reading).
|
|
19
|
-
- **`@hsingjui/pi-hooks`**: the CC JSON envelope parsing, hidden-custom-message
|
|
20
|
-
context injection, and the Stop-hook loop re-trigger
|
|
21
|
-
(`deliverAs: "followUp", triggerTurn: true` + a `stop_hook_active`
|
|
22
|
-
re-entrancy flag). Not adopted as a dependency because it reads
|
|
23
|
-
`.pi/settings.json` rather than `.claude`, and its bugs were ported *around*:
|
|
24
|
-
snake_case event aliases, `if` conditions silently disabling non-tool hooks,
|
|
25
|
-
compact trigger hardcoded to "manual".
|
|
26
|
-
- **`pi-fairy-tales`**: the mtime-keyed config reload (both others only reload
|
|
27
|
-
at session_start).
|
|
28
|
-
|
|
29
|
-
Decisions that shaped the wiring:
|
|
30
|
-
|
|
31
|
-
- **Position in `pi.extensions` is the mechanism** for CC's "hook updatedInput
|
|
32
|
-
applies before permission evaluation": hooks sits right after
|
|
33
|
-
system-reminder, ahead of worktree (which rewrites `input.command` into a
|
|
34
|
-
`cd … && (…)` block — matchers must see the model's original call),
|
|
35
|
-
file-tracker, and permissions. pi passes the same `event.input` object to
|
|
36
|
-
every handler, so an in-place mutation is exactly what the gate, safety
|
|
37
|
-
floor, and classifier evaluate.
|
|
38
|
-
- **A hook can never pre-approve**: `permissionDecision: "allow"` is parsed and
|
|
39
|
-
ignored. Deny direction is honoured everywhere; timeouts fail closed for
|
|
40
|
-
PreToolUse/UserPromptSubmit only.
|
|
41
|
-
- **Trust**: project/local hooks are arbitrary code execution, so they run
|
|
42
|
-
after a once-per-config consent (sha256 of the canonicalised project hook
|
|
43
|
-
config, persisted in `~/.one-code/hooks/project-approvals.json`; a decline
|
|
44
|
-
sticks for the session only). pi's own project-trust store was deliberately
|
|
45
|
-
not reused — it never triggers for repos that have only `.claude/*` files.
|
|
46
|
-
Plugin hooks are user scope: installing the plugin was the consent.
|
|
47
|
-
- **No loader exemption** (user decision): a hook may block `tool_search`/
|
|
48
|
-
`skill`/`structured_output`, full CC fidelity — see the foot-gun note in
|
|
49
|
-
findings.
|
|
50
|
-
- **additionalContext** is a hidden `pi.sendMessage` custom message (persisted,
|
|
51
|
-
survives resume), not a reminder-queue entry (transient) and not deferred to
|
|
52
|
-
`before_agent_start` (fires once per outer turn — mid-loop context would
|
|
53
|
-
arrive a turn late). Mid-loop events use `deliverAs: "steer"`; prompt-time
|
|
54
|
-
context is stashed and injected at `before_agent_start` so it lands on the
|
|
55
|
-
same turn as the prompt.
|
|
56
|
-
- **Notification is unsupported**: pi has no event carrying that concept;
|
|
57
|
-
documented rather than stubbed. `http`/`prompt`/`agent` hook types are
|
|
58
|
-
skipped with a diagnostic.
|
|
59
|
-
|
|
60
|
-
Verified live (rpc e2e, no --dangerously-skip-permissions): consent prompt
|
|
61
|
-
shown once; approved run executes the hook, blocks the bash call with the
|
|
62
|
-
hook's stderr as reason, and no permission prompt fires for it (the hook
|
|
63
|
-
short-circuits ahead of the gate); declined run never executes the hook and
|
|
64
|
-
the same call flows to the ordinary permission prompt.
|