one-code-extension 0.1.4 → 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.
Files changed (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +200 -172
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +126 -0
  8. package/docs/guide/configuration.md +171 -0
  9. package/docs/guide/differences-from-claude-code.md +150 -0
  10. package/docs/guide/doctor.md +86 -0
  11. package/docs/guide/hooks.md +109 -0
  12. package/docs/guide/installation.md +124 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
  14. package/docs/guide/providers-and-models.md +194 -0
  15. package/docs/guide/reference.md +224 -0
  16. package/docs/guide/sessions-and-context.md +190 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +176 -0
  18. package/docs/guide/subagents-and-workflows.md +193 -0
  19. package/docs/guide/tasks-and-background-work.md +111 -0
  20. package/docs/guide/terminal-interface.md +155 -0
  21. package/docs/guide/tools.md +175 -0
  22. package/docs/guide/troubleshooting.md +175 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -217
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. 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.
@@ -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.