one-code-extension 0.4.0 → 0.5.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 (419) hide show
  1. package/README.md +219 -219
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +66 -34
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +123 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +52 -0
  63. package/extensions/background/wakeup.ts +159 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +88 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +19 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -7,20 +7,42 @@
7
7
  * cannot overwrite someone else's change.
8
8
  * - Files that change out of band are reported in a `<system-reminder>` with the
9
9
  * new content around the change, line-numbered.
10
+ * - pi's read, write and edit results read as Claude Code's (`results.ts`).
11
+ * - After a compaction, the files read or written most recently come back as
12
+ * Claude Code brings them back: the contents of a small one, a note to read
13
+ * a large one again (`restore.ts`), and the read state is cleared.
10
14
  *
11
15
  * Tracking is by content, not by tool call, which is what makes it catch writes
12
16
  * that went through bash and never touched an intercepted tool.
13
17
  */
14
18
 
15
- import { existsSync, readFileSync, statSync } from "node:fs";
16
- import { isAbsolute, resolve } from "node:path";
17
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
19
+ import { CHILD_WROTE_CHANNEL, type ChildWrote } from "../lib/child-writes.ts";
20
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
21
+ import { homedir } from "node:os";
22
+ import { join } from "node:path";
23
+ import { createReadToolDefinition, type ExtensionAPI, type ExtensionContext, type SessionCompactEvent } from "@earendil-works/pi-coding-agent";
18
24
  import { pathArgument } from "../auto-mode/paths.ts";
19
- import { REMINDER_CHANNEL } from "../lib/reminders.ts";
20
- import { pathsReadOnBranch } from "./replay.ts";
25
+ import { bashParserReady } from "../lib/bash-parser.ts";
26
+ import { discoverContextFiles, instructionRule } from "../lib/claude-context.ts";
27
+ import { readExternalIncludesApproval } from "../lib/claude-external-includes.ts";
28
+ import { inContextEntries, latestCompaction } from "../lib/compaction-boundary.ts";
29
+ import { autoMemoryEnabled } from "../lib/auto-memory.ts";
30
+ import { projectMemoryDir } from "../lib/memory.ts";
31
+ import { claudeConfigDir, comparablePath, isPathAtOrUnder, tryRealpath } from "../lib/paths.ts";
32
+ import { estimateTextTokens } from "../lib/pi-ai-estimate.ts";
33
+ import { planFileOnBranch } from "../lib/plan-mode-channels.ts";
34
+ import { REMINDER_CHANNEL, type ReminderPayload } from "../lib/reminders.ts";
35
+ import { resolveToolPath } from "../lib/tool-path.ts";
36
+ import { parseRules, ruleMatches } from "../permissions/matcher.ts";
37
+ import { loadPermissionSettings } from "../permissions/settings.ts";
38
+ import { keptReadPaths, keptShellReadPaths, lastTouchesOnBranch, pathsReadOnBranch, shellCallsOnBranch } from "./replay.ts";
39
+ import { type RestoredRead, restoreBlocks, restoreCandidates } from "./restore.ts";
40
+ import { fileToolResultContent } from "./results.ts";
41
+ import { shellReadPaths, shownInFull } from "./shell-reads.ts";
42
+ import { sessionWorkCwd, WORKTREE_CHANNEL, type WorktreeLocation } from "../lib/worktree-channel.ts";
21
43
  import {
22
44
  describeChanges,
23
- EXTERNAL_CHANGE_REMINDER,
45
+ CHILD_CHANGE_REMINDER, EXTERNAL_CHANGE_REMINDER,
24
46
  type FileStamp,
25
47
  FileTracker,
26
48
  STALE_REASON,
@@ -46,6 +68,15 @@ function readIfPresent(path: string): string | undefined {
46
68
  }
47
69
  }
48
70
 
71
+ /** Whether `path` is a regular file (symlinks followed): never a device, FIFO or directory. */
72
+ function isRegularFile(path: string): boolean {
73
+ try {
74
+ return statSync(path).isFile();
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
49
80
  /** The file's disk stamp, or undefined when it is gone (or not a plain file we could stat). */
50
81
  function statIfPresent(path: string): FileStamp | undefined {
51
82
  try {
@@ -56,12 +87,84 @@ function statIfPresent(path: string): FileStamp | undefined {
56
87
  }
57
88
  }
58
89
 
59
- /** Observe a file's current content together with the stamp it was read at. */
60
- function observeFromDisk(tracker: FileTracker, path: string): void {
90
+ function commandOf(input: unknown): string {
91
+ const command = (input as { command?: unknown } | undefined)?.command;
92
+ return typeof command === "string" ? command : "";
93
+ }
94
+
95
+ function textOf(content: unknown): string {
96
+ if (!Array.isArray(content)) return "";
97
+ return content.map((block) => (block as { type?: string; text?: string })?.type === "text" ? ((block as { text?: string }).text ?? "") : "").join("\n");
98
+ }
99
+
100
+ /**
101
+ * A file's identity and version, from metadata only: `ctimeMs` changes on any
102
+ * write, rename or metadata change, and cannot be set back by the writer.
103
+ */
104
+ function versionOf(path: string): string | undefined {
105
+ try {
106
+ const stat = statSync(path);
107
+ return stat.isFile() ? `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}` : undefined;
108
+ } catch {
109
+ return undefined;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * The files a shell command may print, with each one's version as the command
115
+ * starts (`shell-reads.ts` paths, resolved as the shell resolves them, from
116
+ * the directory a literal `cd` earlier in the command moved to). Taken before
117
+ * the command runs, so a file the command itself rewrites (`cat x; cp y x`)
118
+ * is not certified with its new content (found by a GPT-6 Sol review,
119
+ * 2026-10-04). Metadata only: this runs before the permission gate decides
120
+ * the call, so a file the user then denies is never opened.
121
+ */
122
+ function shellReadSnapshot(command: string, cwd: string): Map<string, string> {
123
+ const snapshot = new Map<string, string>();
124
+ if (!command) return snapshot;
125
+ for (const path of shellReadPaths(command, cwd, homedir(), (dir) => readdirSync(dir))) {
126
+ // Regular files only: a device or FIFO (`head -c 1 /dev/zero`) reports size 0
127
+ // and a read of it never ends (found by a GPT-6 Astra review).
128
+ if (!isRegularFile(path)) continue;
129
+ const stamp = statIfPresent(path);
130
+ if (!stamp || stamp.size > MAX_SHELL_READ_BYTES) continue;
131
+ const version = versionOf(path);
132
+ if (version !== undefined) snapshot.set(path, version);
133
+ }
134
+ return snapshot;
135
+ }
136
+
137
+ /**
138
+ * Count as read each file the command printed in full and did not change:
139
+ * its version before and after the command is the same, and the output holds
140
+ * all of it. Observed, not touched: the post-compaction restore follows the
141
+ * file tools, as Claude Code's does. Returns the files it observed.
142
+ */
143
+ function observeShellReads(tracker: FileTracker, before: Map<string, string>, output: string): string[] {
144
+ const observed: string[] = [];
145
+ if (!output) return observed;
146
+ for (const [path, version] of before) {
147
+ const stamp = statIfPresent(path);
148
+ if (!stamp || !isRegularFile(path) || versionOf(path) !== version) continue;
149
+ const content = readIfPresent(path);
150
+ if (content === undefined || versionOf(path) !== version) continue;
151
+ if (!shownInFull(output, content)) continue;
152
+ tracker.observe(path, content, Date.now(), stamp);
153
+ observed.push(path);
154
+ }
155
+ return observed;
156
+ }
157
+
158
+ /** Above this a shell read's output is persisted, not shown, so the file cannot have been seen whole. */
159
+ const MAX_SHELL_READ_BYTES = 256 * 1024;
160
+
161
+ /** Observe a file's current content together with the stamp it was read at, and return that stamp. */
162
+ function observeFromDisk(tracker: FileTracker, path: string): FileStamp | undefined {
61
163
  const stamp = statIfPresent(path);
62
164
  const current = readIfPresent(path);
63
165
  if (current === undefined) tracker.forget(path);
64
166
  else tracker.observe(path, current, Date.now(), stamp);
167
+ return stamp;
65
168
  }
66
169
 
67
170
  function pathOf(input: unknown, cwd: string): string | undefined {
@@ -71,40 +174,165 @@ function pathOf(input: unknown, cwd: string): string | undefined {
71
174
  // read-before-write guard (code-review).
72
175
  const raw = pathArgument(input as Record<string, unknown> | undefined);
73
176
  if (typeof raw !== "string" || !raw.trim()) return undefined;
74
- return isAbsolute(raw) ? raw : resolve(cwd, raw);
177
+ // pi's own resolution (`~`, `@`, `file://`, …): the guard must check the
178
+ // file the tool will actually touch (lib/tool-path.ts).
179
+ return resolveToolPath(raw, cwd);
180
+ }
181
+
182
+ /**
183
+ * The files the context still shows after a compaction, as comparable paths:
184
+ * the context stack (the CLAUDE.md family as claude-context discovers it, what
185
+ * they `@`-import, and the auto-memory index), the plan file, the files a
186
+ * `read` in the kept turns returned (`kept`, as written) and the files a kept
187
+ * bash call showed in full (`keptShell`, absolute). Claude Code's restore
188
+ * skips them; they stay read.
189
+ */
190
+ function inContextFiles(ctx: ExtensionContext, branch: readonly { type: string; customType?: string; data?: unknown }[], kept: string[], keptShell: string[]): Set<string> {
191
+ const home = homedir();
192
+ // Use the context loader's import confinement and approval: a literal
193
+ // unapproved @import is not evidence that its file is still in context.
194
+ const stack = discoverContextFiles({ cwd: ctx.cwd, home, homeClaudeDir: claudeConfigDir(), rule: instructionRule(home),
195
+ includeExternal: readExternalIncludesApproval(ctx.cwd, home).approved,
196
+ }).flatMap((file) => [file.path, ...(file.imported ?? [])]);
197
+ return new Set(
198
+ [...stack, autoMemoryEnabled(ctx.cwd) ? join(projectMemoryDir(ctx.cwd, home), "MEMORY.md") : undefined, planFileOnBranch(branch), ...kept.map((path) => resolveToolPath(path, ctx.cwd)), ...keptShell]
199
+ .filter((path): path is string => typeof path === "string")
200
+ .map(comparablePath),
201
+ );
202
+ }
203
+
204
+ /**
205
+ * Whether a file's contents may come back without the permission gate that a
206
+ * `read` call passes: only a file that resolves (symlinks followed) inside the
207
+ * working directory and that no read deny or ask rule covers now. Any other
208
+ * file, one read outside the project after a question, say, is not restored;
209
+ * the model reads it again through the gate if it needs it. Nothing passes
210
+ * when the settings cannot be read (the gate reports them).
211
+ */
212
+ function restoreAllowed(ctx: ExtensionContext): (path: string) => boolean {
213
+ try {
214
+ const settings = loadPermissionSettings(ctx.cwd, homedir());
215
+ const rules = parseRules([...settings.deny, ...settings.ask]);
216
+ const root = tryRealpath(ctx.cwd) ?? ctx.cwd;
217
+ return (path) => {
218
+ const real = tryRealpath(path);
219
+ if (!real || !isPathAtOrUnder(real, root)) return false;
220
+ return !rules.some((rule) => ruleMatches(rule, "read", path, ctx.cwd) || ruleMatches(rule, "read", real, ctx.cwd));
221
+ };
222
+ } catch {
223
+ return () => false;
224
+ }
225
+ }
226
+
227
+ /** A file read again through pi's own read tool, as the model would see it; undefined when gone, unreadable or an image. */
228
+ async function readAgain(read: ReturnType<typeof createReadToolDefinition>, path: string, ctx: ExtensionContext): Promise<string | undefined> {
229
+ try {
230
+ const result = await read.execute("restore", { path }, undefined, undefined, ctx as never);
231
+ if (result.content.some((block) => block.type !== "text")) return undefined;
232
+ return result.content.map((block) => (block as { text: string }).text).join("\n");
233
+ } catch {
234
+ return undefined;
235
+ }
75
236
  }
76
237
 
77
238
  export default function fileTrackerExtension(pi: ExtensionAPI) {
78
239
  let tracker = new FileTracker();
240
+ /**
241
+ * The files each bash call showed in full, by call id, decided where the
242
+ * call ran: a compaction keeps the reads of the calls it keeps from this,
243
+ * since re-parsing a command later resolves it from today's directory.
244
+ */
245
+ const shellReadsByCall = new Map<string, string[]>();
246
+ const recordShellReads = (callId: string, paths: string[]) => {
247
+ if (paths.length > 0) shellReadsByCall.set(callId, paths);
248
+ };
249
+ /** The entered worktree, where the shell runs while one is active (lib/worktree-channel.ts). */
250
+ let entered: WorktreeLocation | undefined;
251
+ pi.events.on(WORKTREE_CHANNEL, (data) => {
252
+ const location = data as WorktreeLocation | null | undefined;
253
+ entered = location?.path ? location : undefined;
254
+ });
79
255
  let sessionId: string | undefined;
256
+ /**
257
+ * Every file a read or write touched on this branch, with its modification
258
+ * time as stat'ed then (the transcript time for one replayed after a resume):
259
+ * the post-compaction restore's order.
260
+ */
261
+ let touched = new Map<string, number>();
262
+ /**
263
+ * Files this session's own child agents changed since the parent last saw
264
+ * them (lib/child-writes.ts). Every way the parent sees a file clears its
265
+ * entry: a file tool, a shell read, a rebuild from disk, the change scan.
266
+ */
267
+ const childWrites = new Map<string, string | undefined>();
268
+ pi.events.on(CHILD_WROTE_CHANNEL, (data) => {
269
+ const wrote = data as ChildWrote | undefined;
270
+ if (wrote?.path) childWrites.set(wrote.path, wrote.agent);
271
+ });
272
+ /** Observe a file the model read or wrote, and record it as touched at its current modification time. */
273
+ const touch = (path: string) => {
274
+ childWrites.delete(path);
275
+ const stamp = observeFromDisk(tracker, path);
276
+ if (stamp) touched.set(path, stamp.mtimeMs);
277
+ };
80
278
 
81
279
  // A resumed session (or a session_tree switch) carries reads the model did
82
- // earlier in the transcript; rebuild the tracker from the branch so post-
83
- // resume edits are not refused as "never read" (replay.ts). A new session id
84
- // starts from an empty tracker.
85
- const reconstruct = (ctx: ExtensionContext) => {
280
+ // earlier in the transcript; rebuild the tracker from the part of the branch
281
+ // still in context (since the latest compaction's kept tail) so post-resume
282
+ // edits are not refused as "never read" (replay.ts). A new session id starts
283
+ // from an empty tracker.
284
+ const reconstruct = (ctx: ExtensionContext, fresh = false) => {
86
285
  const id = ctx.sessionManager.getSessionId?.() ?? undefined;
87
- if (id !== sessionId) {
286
+ if (fresh || id !== sessionId) {
88
287
  sessionId = id;
89
288
  tracker = new FileTracker();
90
289
  }
290
+ // Tracked files are re-read from disk below: what a child wrote is now the baseline.
291
+ childWrites.clear();
91
292
  let entries: unknown[] = [];
92
293
  try {
93
- entries = ctx.sessionManager.getBranch() as unknown[];
294
+ entries = inContextEntries(ctx.sessionManager.getBranch() as unknown[]);
94
295
  } catch {
95
296
  return;
96
297
  }
97
298
  for (const raw of pathsReadOnBranch(entries)) {
98
- observeFromDisk(tracker, isAbsolute(raw) ? raw : resolve(ctx.cwd, raw));
299
+ observeFromDisk(tracker, resolveToolPath(raw, ctx.cwd));
99
300
  }
301
+ // Shell reads need the bash grammar, which may still be loading this early.
302
+ const target = tracker;
303
+ shellReadsByCall.clear();
304
+ const shellCalls = [...shellCallsOnBranch(entries)];
305
+ if (shellCalls.length > 0) {
306
+ void bashParserReady().then(() => {
307
+ if (target !== tracker) return;
308
+ // A replayed result has no before-state: the files as they are now stand in for it.
309
+ for (const call of shellCalls) recordShellReads(call.id, observeShellReads(target, shellReadSnapshot(call.command, sessionWorkCwd(entered, ctx.cwd)), call.output));
310
+ });
311
+ }
312
+ touched = new Map([...lastTouchesOnBranch(entries)].map(([raw, at]) => [resolveToolPath(raw, ctx.cwd), at]));
100
313
  };
101
314
  pi.on("session_start", (_event, ctx) => reconstruct(ctx));
102
- pi.on("session_tree", (_event, ctx) => reconstruct(ctx));
315
+ // A branch switch keeps the session id, but the reads of the branch left
316
+ // behind are not in context any more: start from an empty tracker, as for a
317
+ // new session (found by an adversarial GPT-6 Astra run, 2026-10-04).
318
+ pi.on("session_tree", (_event, ctx) => reconstruct(ctx, true));
319
+
320
+ /** Whether a write's file existed when the call was made, by call id: its result says "created" or "updated". */
321
+ const writeTargetExisted = new Map<string, boolean>();
103
322
 
104
- pi.on("tool_call", (event, ctx) => {
323
+ /** Each running bash call's candidate files as they were when it started (`shellReadSnapshot`). */
324
+ const shellSnapshots = new Map<string, Map<string, string>>();
325
+ pi.on("tool_call", async (event, ctx) => {
326
+ if (event.toolName === "bash") {
327
+ await bashParserReady();
328
+ // Relative paths in the command resolve where the shell runs: the entered worktree, if any.
329
+ shellSnapshots.set(event.toolCallId, shellReadSnapshot(commandOf(event.input), sessionWorkCwd(entered, ctx.cwd)));
330
+ return undefined;
331
+ }
105
332
  if (!GUARDED_TOOLS.has(event.toolName)) return undefined;
106
333
  const path = pathOf(event.input, ctx.cwd);
107
334
  if (!path) return undefined;
335
+ if (event.toolName === "write") writeTargetExisted.set(event.toolCallId, existsSync(path));
108
336
 
109
337
  const current = readIfPresent(path);
110
338
  const status = tracker.status(path, current);
@@ -115,8 +343,19 @@ export default function fileTrackerExtension(pi: ExtensionAPI) {
115
343
  return { block: true, reason: STALE_REASON(path) };
116
344
  });
117
345
 
118
- pi.on("tool_result", (event, ctx) => {
119
- if (event.isError) return undefined;
346
+ pi.on("tool_result", async (event, ctx) => {
347
+ const existedBefore = writeTargetExisted.get(event.toolCallId);
348
+ writeTargetExisted.delete(event.toolCallId);
349
+ if (event.toolName === "bash") {
350
+ const before = shellSnapshots.get(event.toolCallId);
351
+ shellSnapshots.delete(event.toolCallId);
352
+ if (before) {
353
+ const observed = observeShellReads(tracker, before, textOf(event.content));
354
+ recordShellReads(event.toolCallId, observed);
355
+ for (const path of observed) childWrites.delete(path);
356
+ }
357
+ return undefined;
358
+ }
120
359
  if (!READ_TOOLS.has(event.toolName) && !GUARDED_TOOLS.has(event.toolName)) return undefined;
121
360
 
122
361
  const path = pathOf(event.input, ctx.cwd);
@@ -124,8 +363,69 @@ export default function fileTrackerExtension(pi: ExtensionAPI) {
124
363
 
125
364
  // After a read we know the file; after our own write we know it again, so a
126
365
  // successful edit does not make the file look stale to the next edit.
127
- observeFromDisk(tracker, path);
128
- return undefined;
366
+ if (!event.isError) touch(path);
367
+ const content = fileToolResultContent({
368
+ toolName: event.toolName,
369
+ isError: event.isError,
370
+ content: event.content,
371
+ path,
372
+ cwd: ctx.cwd,
373
+ existedBefore,
374
+ isEmptyFile: () => statIfPresent(path)?.size === 0,
375
+ });
376
+ return content ? { content } : undefined;
377
+ });
378
+
379
+ /**
380
+ * Claude Code's post-compaction file restore (`restore.ts`). The
381
+ * blocks open the next user message (`user-prepend`); an overflow compaction
382
+ * that retries the turn has no such message, so they ride the request's tail
383
+ * instead.
384
+ *
385
+ * Like Claude Code, the compaction also clears what the session has read:
386
+ * the summary no longer holds those files, so an edit needs a fresh read.
387
+ * A file the context still shows stays read (a kept turn's read or whole-file
388
+ * shell read, the context stack, the plan file), and a restored file counts
389
+ * as read and as touched for the next compaction's restore; a note never
390
+ * marks its file read. Contents come back only for a file the gate would
391
+ * let through without a question (`restoreAllowed`).
392
+ */
393
+ pi.on("session_compact", async (event: SessionCompactEvent, ctx) => {
394
+ const before = touched;
395
+ try {
396
+ const branch = ctx.sessionManager.getBranch();
397
+ const compaction = latestCompaction(branch);
398
+ const keptTurns = compaction ? branch.slice(compaction.keptStart, compaction.index) : [];
399
+ // The kept shell reads: what each kept bash call certified as it ran.
400
+ const keptShell = keptShellReadPaths(keptTurns, (id) => shellReadsByCall.get(id), (path) => tracker.lastSeen(path));
401
+ // A call the summary took never comes back: only the kept ones need their record.
402
+ const keptCalls = new Set([...shellCallsOnBranch(keptTurns)].map((call) => call.id));
403
+ for (const id of shellReadsByCall.keys()) if (!keptCalls.has(id)) shellReadsByCall.delete(id);
404
+ const inContext = inContextFiles(ctx, branch, keptReadPaths(keptTurns), keptShell);
405
+ // The loader lists imports by real path; a tracked path may be spelled through a symlink.
406
+ const shown = (path: string) => inContext.has(comparablePath(path)) || inContext.has(comparablePath(tryRealpath(path) ?? path));
407
+ // A file the context still shows keeps its read state (what the model
408
+ // saw, not what is on disk now, so the stale-edit guard still holds)
409
+ // and its touch time, for the next compaction's restore.
410
+ for (const path of tracker.tracked) if (!shown(path)) tracker.forget(path);
411
+ touched = new Map([...before].filter(([path]) => shown(path)));
412
+ // Opting out of One Code's restore does not disable pi's compaction:
413
+ // reads discarded by either summarizer still need a fresh read.
414
+ if (process.env.CC_COMPACTION === "0" || before.size === 0) return;
415
+ const allowed = restoreAllowed(ctx);
416
+ const candidates = restoreCandidates(before, (path) => shown(path) || !allowed(path));
417
+ const read = createReadToolDefinition(ctx.cwd);
418
+ const reads: RestoredRead[] = await Promise.all(candidates.map(async (path) => ({ path, text: await readAgain(read, path, ctx) })));
419
+ const { blocks, restored } = restoreBlocks(reads, estimateTextTokens);
420
+ const placement = event.willRetry ? "last-append" : "user-prepend";
421
+ for (const text of blocks) pi.events.emit(REMINDER_CHANNEL, { text, placement } satisfies ReminderPayload);
422
+ for (const path of restored) touch(path);
423
+ } catch {
424
+ // The restore is best effort: nothing comes back, and an edit needs a fresh read.
425
+ tracker = new FileTracker();
426
+ touched = new Map();
427
+ shellReadsByCall.clear();
428
+ }
129
429
  });
130
430
 
131
431
  /**
@@ -166,14 +466,22 @@ export default function fileTrackerExtension(pi: ExtensionAPI) {
166
466
  const detailed: string[] = [];
167
467
  const overflow: string[] = [];
168
468
  for (const path of tracker.tracked) {
469
+ // Undefined for a file tracked without content (oversized); an empty
470
+ // file is compared like any other.
169
471
  const previous = tracker.lastSeen(path);
170
- if (previous === undefined || previous === "") continue;
472
+ if (previous === undefined) continue;
171
473
  const stamp = statIfPresent(path);
172
474
  if (stamp && tracker.unchangedOnDisk(path, stamp)) continue;
475
+ // This scan accounts for the child's write whatever it finds (same
476
+ // content, already notified, overflow), so a later change by someone
477
+ // else is never credited to that agent.
478
+ const byChild = childWrites.has(path);
479
+ const childAgent = childWrites.get(path);
480
+ childWrites.delete(path);
173
481
  const current = stamp ? readIfPresent(path) : undefined;
174
482
  if (stamp === undefined || current === undefined) {
175
483
  tracker.forget(path);
176
- detailed.push(`${path} no longer exists; it was deleted or moved after you read it.`);
484
+ detailed.push(`${path} no longer exists; it was deleted or moved after you last read or wrote it.`);
177
485
  continue;
178
486
  }
179
487
  tracker.recordStamp(path, stamp);
@@ -183,7 +491,7 @@ export default function fileTrackerExtension(pi: ExtensionAPI) {
183
491
 
184
492
  if (detailed.length < DETAILED_CHANGE_REMINDERS_PER_TURN) {
185
493
  const excerpt = describeChanges(previous, current);
186
- if (excerpt) detailed.push(EXTERNAL_CHANGE_REMINDER(path, excerpt));
494
+ if (excerpt) detailed.push(byChild ? CHILD_CHANGE_REMINDER(path, childAgent, excerpt) : EXTERNAL_CHANGE_REMINDER(path, excerpt));
187
495
  } else {
188
496
  overflow.push(path);
189
497
  }
@@ -212,8 +520,11 @@ export default function fileTrackerExtension(pi: ExtensionAPI) {
212
520
  pi.on("agent_start", () => {
213
521
  // A turn aborted mid-batch can leave ids behind; each turn starts from a
214
522
  // clean set so the mid-turn scan cannot be wedged off for the rest of the
215
- // session.
523
+ // session. A write blocked before it ran left its existence entry behind,
524
+ // and a blocked or aborted bash call its snapshot of file versions.
216
525
  executing.clear();
526
+ writeTargetExisted.clear();
527
+ shellSnapshots.clear();
217
528
  reportExternalChanges();
218
529
  return undefined;
219
530
  });
@@ -11,6 +11,9 @@
11
11
  * guard still catches anything that changes from this point on.
12
12
  */
13
13
 
14
+ import { pathArgument } from "../auto-mode/paths.ts";
15
+ import { shownInFull } from "./shell-reads.ts";
16
+
14
17
  const READ_TOOLS = new Set(["read", "notebook_edit"]);
15
18
  const WRITE_TOOLS = new Set(["edit", "write", "notebook_edit"]);
16
19
 
@@ -21,26 +24,109 @@ interface ToolCallBlock {
21
24
  arguments?: Record<string, unknown>;
22
25
  }
23
26
 
24
- /** Paths (as written in the tool input) of every successful read/edit/write on the branch, in order, deduplicated. */
25
- export function pathsReadOnBranch(entries: unknown[]): string[] {
27
+ type BranchMessage = { role?: string; content?: unknown; toolCallId?: string; isError?: boolean; timestamp?: number };
28
+
29
+ /**
30
+ * Every tool call on the branch that names a path (`path`, `file_path` or
31
+ * `notebook_path`, as the gates read it) and got a successful result, in
32
+ * result order, with the result message's time.
33
+ */
34
+ export function* successfulPathCalls(entries: readonly unknown[]): Generator<{ name: string; path: string; timestamp: number | undefined }> {
26
35
  const calls = new Map<string, { name: string; path: string }>();
27
- const seen: string[] = [];
28
36
  for (const entry of entries) {
29
- const e = entry as { type?: string; message?: { role?: string; content?: unknown; toolCallId?: string; toolName?: string; isError?: boolean } };
37
+ const e = entry as { type?: string; message?: BranchMessage };
30
38
  if (e?.type !== "message" || !e.message) continue;
31
39
  const m = e.message;
32
40
  if (m.role === "assistant" && Array.isArray(m.content)) {
33
41
  for (const block of m.content as ToolCallBlock[]) {
34
42
  if (block?.type !== "toolCall" || !block.id) continue;
35
- const path = block.arguments?.path;
36
- if (typeof path === "string" && path.trim()) calls.set(block.id, { name: block.name, path });
43
+ const path = pathArgument(block.arguments);
44
+ if (path?.trim()) calls.set(block.id, { name: block.name, path });
37
45
  }
38
46
  } else if (m.role === "toolResult" && m.toolCallId && !m.isError) {
39
47
  const call = calls.get(m.toolCallId);
40
- if (!call) continue;
41
- if (!READ_TOOLS.has(call.name) && !WRITE_TOOLS.has(call.name)) continue;
42
- if (!seen.includes(call.path)) seen.push(call.path);
48
+ if (call) yield { ...call, timestamp: m.timestamp };
49
+ }
50
+ }
51
+ }
52
+
53
+ const touchesFile = (name: string) => READ_TOOLS.has(name) || WRITE_TOOLS.has(name);
54
+
55
+ /** Paths (as written in the tool input) of every successful read/edit/write on the branch, in order, deduplicated. */
56
+ export function pathsReadOnBranch(entries: readonly unknown[]): string[] {
57
+ const seen = new Set<string>();
58
+ for (const call of successfulPathCalls(entries)) if (touchesFile(call.name)) seen.add(call.path);
59
+ return [...seen];
60
+ }
61
+
62
+ /**
63
+ * Each path (as written in the tool input) a successful read/edit/write on the
64
+ * branch touched, with the time of its last such result message: the order
65
+ * the post-compaction file restore uses after a resume, when the modification
66
+ * times stat'ed at those reads are gone (Claude Code uses the transcript time
67
+ * the same way).
68
+ */
69
+ export function lastTouchesOnBranch(entries: readonly unknown[]): Map<string, number> {
70
+ const touches = new Map<string, number>();
71
+ for (const call of successfulPathCalls(entries)) {
72
+ if (touchesFile(call.name) && call.timestamp !== undefined) touches.set(call.path, call.timestamp);
73
+ }
74
+ return touches;
75
+ }
76
+
77
+ /**
78
+ * The paths (as written in the tool input) that a successful `read` in the
79
+ * kept turns returned: those files are in context already, so the
80
+ * post-compaction restore skips them.
81
+ */
82
+ export function keptReadPaths(kept: readonly unknown[]): string[] {
83
+ return [...successfulPathCalls(kept)].filter((call) => call.name === "read").map((call) => call.path);
84
+ }
85
+
86
+ /**
87
+ * The tracked files that a bash call in the kept turns showed in full: those
88
+ * the call certified when it ran (`readsOf`, by call id: the directory it ran
89
+ * in decided, never today's) whose content as the tracker last saw it (`seen`)
90
+ * is still all in its kept output. Like a kept `read`, they are in context
91
+ * still, so they stay read; a file changed since is then stale, not unread. A
92
+ * call needs its result in the kept turns too, and one with no record
93
+ * certifies nothing (a miss costs one re-read).
94
+ */
95
+ export function keptShellReadPaths(kept: readonly unknown[], readsOf: (callId: string) => readonly string[] | undefined, seen: (path: string) => string | undefined): string[] {
96
+ const shown = new Set<string>();
97
+ for (const { id, output } of shellCallsOnBranch(kept)) {
98
+ for (const path of readsOf(id) ?? []) {
99
+ if (shown.has(path)) continue;
100
+ const content = seen(path);
101
+ if (content !== undefined && shownInFull(output, content)) shown.add(path);
102
+ }
103
+ }
104
+ return [...shown];
105
+ }
106
+
107
+ /**
108
+ * Every bash call on the branch with its command and the text of its result,
109
+ * in result order: a resumed session re-checks which files those outputs
110
+ * showed in full (`shell-reads.ts`), against the files' current content.
111
+ */
112
+ export function* shellCallsOnBranch(entries: readonly unknown[]): Generator<{ id: string; command: string; output: string }> {
113
+ const commands = new Map<string, string>();
114
+ for (const entry of entries) {
115
+ const e = entry as { type?: string; message?: BranchMessage };
116
+ if (e?.type !== "message" || !e.message) continue;
117
+ const m = e.message;
118
+ if (m.role === "assistant" && Array.isArray(m.content)) {
119
+ for (const block of m.content as ToolCallBlock[]) {
120
+ const command = block?.type === "toolCall" && block.name === "bash" ? block.arguments?.command : undefined;
121
+ if (typeof command === "string" && block.id) commands.set(block.id, command);
122
+ }
123
+ } else if (m.role === "toolResult" && m.toolCallId) {
124
+ const command = commands.get(m.toolCallId);
125
+ if (command === undefined || !Array.isArray(m.content)) continue;
126
+ const output = (m.content as { type?: string; text?: string }[])
127
+ .map((block) => (block?.type === "text" ? (block.text ?? "") : ""))
128
+ .join("\n");
129
+ yield { id: m.toolCallId, command, output };
43
130
  }
44
131
  }
45
- return seen;
46
132
  }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The files that come back after a compaction, and how. Pure: no pi imports,
3
+ * no I/O.
4
+ *
5
+ * As in Claude Code: the five files the session read, edited or wrote with
6
+ * the newest modification time (as stat'ed at that read or write), minus the
7
+ * files the context still shows, are read again. A file within 5,000 tokens
8
+ * comes back as a read call and its result, a larger one as a note to read it
9
+ * again, while the total stays within 50,000 tokens (findings §47). Each is
10
+ * its own `<system-reminder>` block at the start of the next user message,
11
+ * worded as Claude Code words them with One Code's tool name and argument
12
+ * (`read`, `path`).
13
+ */
14
+
15
+ export const RESTORE_MAX_FILES = 5;
16
+ export const RESTORE_FILE_MAX_TOKENS = 5_000;
17
+ export const RESTORE_TOTAL_MAX_TOKENS = 50_000;
18
+
19
+ /**
20
+ * The files to restore, newest first: the touched files (path to modification
21
+ * time) minus `skip`, at most five. A file that cannot be read again is
22
+ * dropped afterwards, not replaced, as in Claude Code.
23
+ */
24
+ export function restoreCandidates(touched: ReadonlyMap<string, number>, skip: (path: string) => boolean): string[] {
25
+ const candidates: string[] = [];
26
+ for (const [path] of [...touched].sort((a, b) => b[1] - a[1])) {
27
+ if (candidates.length === RESTORE_MAX_FILES) break;
28
+ if (!skip(path)) candidates.push(path);
29
+ }
30
+ return candidates;
31
+ }
32
+
33
+ /** What reading a candidate again gave: its text as the read tool returns it, or nothing (gone, unreadable, an image). */
34
+ export type RestoredRead = { path: string; text: string | undefined };
35
+
36
+ /**
37
+ * The reminder texts (one per `<system-reminder>` block) for the reads, in
38
+ * order: two blocks for a file within the per-file cap, one note for a larger
39
+ * one, nothing for a file that could not be read. A file's blocks are kept only
40
+ * while the running total stays within the overall cap. `restored` lists the
41
+ * files whose contents went back into context.
42
+ */
43
+ export function restoreBlocks(reads: RestoredRead[], estimateTokens: (text: string) => number): { blocks: string[]; restored: string[] } {
44
+ const blocks: string[] = [];
45
+ const restored: string[] = [];
46
+ let total = 0;
47
+ for (const { path, text } of reads) {
48
+ if (text === undefined) continue;
49
+ const fits = estimateTokens(text) <= RESTORE_FILE_MAX_TOKENS;
50
+ const fileBlocks = fits ? [readCallBlock(path), readResultBlock(text)] : [tooLargeNote(path)];
51
+ const tokens = fileBlocks.reduce((sum, block) => sum + estimateTokens(block), 0);
52
+ if (total + tokens > RESTORE_TOTAL_MAX_TOKENS) continue;
53
+ total += tokens;
54
+ blocks.push(...fileBlocks);
55
+ if (fits) restored.push(path);
56
+ }
57
+ return { blocks, restored };
58
+ }
59
+
60
+ export const readCallBlock = (path: string) => `Called the read tool with the following input: ${JSON.stringify({ path })}`;
61
+ export const readResultBlock = (text: string) => `Result of calling the read tool:\n${text}`;
62
+ export const tooLargeNote = (path: string) =>
63
+ `Note: ${path} was read before the last conversation was summarized, but the contents are too large to include. Use read tool if you need to access it.`;