one-code-extension 0.4.1 → 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 +16 -5
  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 +58 -31
  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 +18 -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
@@ -1,13 +1,14 @@
1
1
  /**
2
- * Claude Code's `# claudeMd` context block — the single `<system-reminder>` that
3
- * carries the CLAUDE.md files, the MEMORY.md index, the user's email, and the
4
- * date, prepended to the first user message (see extensions/claude-context for
5
- * the wiring, and lib/reminders.ts for placement).
2
+ * Claude Code's three first-message context reminders: the instructions block
3
+ * (the CLAUDE.md files and the MEMORY.md index), the context block (the user's
4
+ * email and the git snapshot) and the date, each its own `<system-reminder>`
5
+ * (see extensions/claude-context for the wiring, and lib/reminders.ts for
6
+ * placement; on a model that takes a mid-conversation system message the date
7
+ * moves into it, lib/system-role.ts).
6
8
  *
7
- * This module is pure (no pi imports) and byte-exact against real Claude Code
8
- * captures (opus-4-8.json / latest-haiku.json). The block is identical across
9
- * model tiers — only the `# Memory` *spec* in the system prompt varies by tier
10
- * (handled in lib/memory.ts), never this block.
9
+ * This module is pure (no pi imports) and byte-exact with Claude Code. The
10
+ * blocks are identical across model tiers — only the `# Memory` *spec* in the
11
+ * system prompt varies by tier (handled in lib/memory.ts), never these blocks.
11
12
  *
12
13
  * Discovery mirrors Claude Code, not pi's own loader: global `~/.claude/CLAUDE.md`
13
14
  * first, then project `CLAUDE.md` / `CLAUDE.local.md` from the farthest ancestor
@@ -26,24 +27,31 @@
26
27
  * block so they take precedence over CLAUDE.md — keeping `# claudeMd` itself
27
28
  * byte-exact with Claude Code.
28
29
  *
29
- * What is NOT yet replicated: enterprise-policy files and nested subtree
30
- * CLAUDE.md loaded on-demand when a file under them is read (a follow-up needing
31
- * file-tracker integration; absent from every turn-1 capture we have).
30
+ * Nested subtree instruction files come later, when a file under them is read
31
+ * (`nestedInstructionFiles`, attached by the claude-context extension). Rules
32
+ * parsing, recursive discovery and path matching live in claude-rules.ts.
32
33
  */
33
34
 
34
35
  import { existsSync, readdirSync, statSync } from "node:fs";
35
- import { dirname, isAbsolute, join } from "node:path";
36
+ import { basename, dirname, join, relative } from "node:path";
37
+ import { type InstructionFiles, readInstructionFiles } from "./claude-settings.ts";
38
+ import { claudeSourcesOn } from "./config-mode.ts";
39
+ import { findGitRoot } from "./git.ts";
36
40
  import { tryReadFile } from "./plugins.ts";
37
- import { absoluteFrom, expandTilde } from "./paths.ts";
41
+ import { absoluteFrom, claudeManagedDir, claudeUserDir, expandTilde, isPathAtOrUnder, oneCodeStateDir, tryRealpath } from "./paths.ts";
42
+ import { discoverRules, MAX_INSTRUCTION_BYTES, readRuleInstructions, type RuleFile, type RuleOptions } from "./claude-rules.ts";
38
43
 
39
- /** One CLAUDE.md-family file as it appears in the block. `content` is raw (untrimmed). */
44
+ /** One instruction section. Legacy content stays raw; new parsed files carry CC's trimmed startup body. */
40
45
  export interface ContextFile {
41
46
  path: string;
42
47
  content: string;
43
48
  descriptor: string;
49
+ /** Actual inline imports; parsed imports are separate sections and use [] to prevent raw @token inference. */
50
+ imported?: string[];
44
51
  }
45
52
 
46
53
  export const GLOBAL_DESCRIPTOR = "user's private global instructions for all projects";
54
+ export const MANAGED_DESCRIPTOR = "organization-managed policy instructions";
47
55
  export const PROJECT_DESCRIPTOR = "project instructions, checked into the codebase";
48
56
  export const LOCAL_DESCRIPTOR = "user's private project instructions, not checked in";
49
57
  export const MEMORY_DESCRIPTOR = "user's auto-memory, persists across conversations";
@@ -58,18 +66,36 @@ const ONECODE_NAMES = ["ONECODE.md", "onecode.md", "One Code.md"] as const;
58
66
  const MAX_IMPORT_DEPTH = 5;
59
67
 
60
68
  const PREAMBLE =
61
- "As you answer the user's questions, you can use the following context:\n" +
62
- "# claudeMd\n" +
63
69
  "Codebase and user instructions are shown below. Be sure to adhere to these instructions. " +
64
70
  "IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.";
65
71
 
66
- const TRAILER =
67
- " IMPORTANT: this context may or may not be relevant to your tasks. " +
68
- "You should not respond to this context unless it is highly relevant to your task.";
72
+ const CONTEXT_PREAMBLE = "As you answer the user's questions, you can use the following context:";
73
+
74
+ /** Claude Code's sentence after the email, with One Code named as the harness that attached it. */
75
+ const EMAIL_USE =
76
+ "Use it only to identify the user, such as for authorship, attribution, or filtering their own work. " +
77
+ "Never send it to an unrelated service, such as in a request header, URL, or payload, unless the user explicitly asks.";
78
+
79
+ const CONTEXT_FOOTER =
80
+ "One Code attached this context automatically; it isn't part of the user's message. " +
81
+ "It describes the user's own account and workspace, so they don't need it reported back.";
82
+
83
+ /** Independent mode excludes Claude locations even when reached through aliases or imports. */
84
+ export function isClaudeLocation(path: string, userDir: string): boolean {
85
+ return [path, tryRealpath(path)].some((candidate) => candidate !== undefined && (
86
+ /(?:^|[/\\])\.claude(?:[/\\]|$)/i.test(candidate) ||
87
+ /(?:^|[/\\])CLAUDE(?:\.local)?\.md$/i.test(candidate) ||
88
+ isPathAtOrUnder(candidate, tryRealpath(userDir) ?? userDir) ||
89
+ isPathAtOrUnder(candidate, claudeManagedDir())
90
+ ));
91
+ }
69
92
 
70
- function readFileIfPresent(path: string): string | null {
93
+ /** A regular file within the instruction size limit (never a FIFO, which would block), else null. */
94
+ export function readFileIfPresent(path: string): string | null {
71
95
  try {
72
- if (!existsSync(path) || !statSync(path).isFile()) return null;
96
+ if (!existsSync(path)) return null;
97
+ const stat = statSync(path);
98
+ if (!stat.isFile() || stat.size > MAX_INSTRUCTION_BYTES) return null;
73
99
  } catch {
74
100
  return null;
75
101
  }
@@ -101,7 +127,7 @@ function isPresentFile(path: string): boolean {
101
127
  * costs a few stats and never lists the directory. Only when a probe hits do we
102
128
  * read the directory once to recover the real casing.
103
129
  */
104
- function firstOneCodeFile(dir: string): string | null {
130
+ export function firstOneCodeFile(dir: string): string | null {
105
131
  if (!ONECODE_NAMES.some((n) => isPresentFile(join(dir, n)))) return null;
106
132
  let entries: string[];
107
133
  try {
@@ -266,55 +292,128 @@ export function ancestorDirs(cwd: string): string[] {
266
292
  }
267
293
 
268
294
  /**
269
- * The ordered CLAUDE.md-family paths that exist, WITHOUT reading their contents:
270
- * global `~/.claude/CLAUDE.md` first, then project `CLAUDE.md`/`CLAUDE.local.md`
271
- * from the farthest ancestor down to cwd (`CLAUDE.md` before `CLAUDE.local.md`
272
- * within a directory). `discoverContextFiles` reads content on top of this;
273
- * callers that only need paths/descriptors (e.g. `/memory`'s picker) use it
274
- * directly to avoid loading files they will discard.
295
+ * Which instruction files load: Claude Code's `instructionFiles` values
296
+ * (findings §57) in Claude-compatible mode, or `agents-md` in independent mode
297
+ * (lib/config-mode.ts), where no CLAUDE.md-family file is read and every
298
+ * directory's `AGENTS.md` loads.
299
+ */
300
+ export type InstructionRule = InstructionFiles | "agents-md";
301
+
302
+ /** The rule One Code runs under: independent mode's own, else Claude Code's `instructionFiles`. */
303
+ export function instructionRule(home: string): InstructionRule {
304
+ return claudeSourcesOn() ? readInstructionFiles(home) : "agents-md";
305
+ }
306
+
307
+ /**
308
+ * Whether `dir` holds a CLAUDE.md that puts Claude's instructions in play for
309
+ * `claude-md-or-agents-md`: CLAUDE.md, .claude/CLAUDE.md (never at the
310
+ * filesystem root) or CLAUDE.local.md.
311
+ */
312
+ function dirHasClaudeMd(dir: string, present: (path: string) => boolean = isPresentFile): boolean {
313
+ return present(join(dir, "CLAUDE.md")) || (dirname(dir) !== dir && present(join(dir, ".claude", "CLAUDE.md"))) || present(join(dir, "CLAUDE.local.md"));
314
+ }
315
+
316
+ /**
317
+ * The ordered instruction-file paths that exist, WITHOUT reading their
318
+ * contents: global `~/.claude/CLAUDE.md` first, then per directory from the
319
+ * farthest ancestor down to cwd `CLAUDE.md`, the AGENTS.md files at its
320
+ * position, and `CLAUDE.local.md`. `discoverContextFiles` reads content on top
321
+ * of this; callers that only need paths/descriptors (e.g. `/memory`'s picker)
322
+ * use it directly to avoid loading files they will discard.
323
+ *
324
+ * `rule` (default `claude-md`) decides the AGENTS.md files, as Claude Code's
325
+ * `instructionFiles` does (findings §57): `claude-md-or-agents-md` loads every
326
+ * ancestor `AGENTS.md` and `.claude/AGENTS.md` only when the project has no
327
+ * `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` anywhere on that path (a per-project
328
+ * decision, so `# claudeMd` stays byte-exact with CC whenever CLAUDE.md is
329
+ * present); `claude-md-and-agents-md` loads them beside CLAUDE.md;
330
+ * `managed-only` drops the user's and the project's files, retaining managed
331
+ * instructions; `agents-md` reads only `AGENTS.md`, with no Claude Code
332
+ * file at all.
275
333
  *
276
334
  * When `homeOneCodeDir` is given, One Code's own `ONECODE.md` files join the
277
335
  * list: the global one from `~/.onecode` right after the global CLAUDE.md, and
278
- * each directory's after its CLAUDE.md/CLAUDE.local.md. The `/memory` picker passes
279
- * it (so ONECODE.md is an editable target); the model-facing `# claudeMd` block
280
- * does NOT — there ONECODE.md rides its own `# oneCodeMd` block via
281
- * `discoverOneCodeFiles` instead, so `# claudeMd` stays byte-exact with CC.
282
- *
283
- * When `agentsFallback` is set, a directory with no `CLAUDE.md` falls back to its
284
- * `AGENTS.md` (CLAUDE.md > AGENTS.md — the first-match Codex/opencode do, but
285
- * CLAUDE-preferred for our CC-compat audience). A directory that has a CLAUDE.md
286
- * ignores its AGENTS.md, so `# claudeMd` stays byte-exact with CC whenever
287
- * CLAUDE.md is present.
336
+ * each directory's last. The `/memory` picker passes it (so ONECODE.md is an
337
+ * editable target); the model-facing `# claudeMd` block does NOT — there
338
+ * ONECODE.md rides its own `# oneCodeMd` block via `discoverOneCodeFiles`
339
+ * instead, so `# claudeMd` stays byte-exact with CC.
288
340
  */
289
- export function discoverContextFilePaths(opts: {
341
+ interface DiscoveryOptions {
290
342
  cwd: string;
291
343
  homeClaudeDir: string;
292
344
  homeOneCodeDir?: string;
293
- agentsFallback?: boolean;
294
- }): ContextFilePath[] {
295
- const paths: ContextFilePath[] = [];
345
+ managedDir?: string;
346
+ rule?: InstructionRule;
347
+ /** User consent for this project's external instruction imports. Never sourced from project files. */
348
+ includeExternal?: boolean;
349
+ }
350
+
351
+ export function discoverContextFilePaths(opts: DiscoveryOptions): ContextFilePath[] {
352
+ return discoverInstructionEntries(opts);
353
+ }
354
+
355
+ interface InstructionEntry extends ContextFilePath {
356
+ ruleFile?: RuleFile;
357
+ }
358
+
359
+ function discoverInstructionEntries(
360
+ opts: DiscoveryOptions,
361
+ rules: (dir: string, scope: RuleOptions["scope"], descriptor: string) => InstructionEntry[] = () => [],
362
+ ): InstructionEntry[] {
363
+ const paths: InstructionEntry[] = [];
296
364
  const seen = new Set<string>();
297
365
  const includeOneCode = opts.homeOneCodeDir !== undefined;
298
-
299
- /** Adds the path if present; returns whether it was added. */
300
- const push = (path: string, descriptor: string): boolean => {
301
- if (seen.has(path) || !isPresentFile(path)) return false;
366
+ const rule = opts.rule ?? "claude-md";
367
+ const claudeFiles = rule !== "agents-md" && rule !== "managed-only";
368
+ const dirs = ancestorDirs(opts.cwd);
369
+ // Each path is checked once per call: the per-project test and the walk share it.
370
+ const presence = new Map<string, boolean>();
371
+ const present = (path: string): boolean => {
372
+ let found = presence.get(path);
373
+ if (found === undefined) presence.set(path, (found = isPresentFile(path)));
374
+ return found;
375
+ };
376
+ const projectHasClaude = () => dirs.some((d) => dirHasClaudeMd(d, present));
377
+ const agentsFiles =
378
+ rule === "agents-md" || rule === "claude-md-and-agents-md" || (rule === "claude-md-or-agents-md" && !projectHasClaude());
379
+
380
+ /** Adds the path if present. */
381
+ const push = (path: string, descriptor: string): void => {
382
+ if (!present(path) || (rule === "agents-md" && isClaudeLocation(path, opts.homeClaudeDir))) return;
383
+ const key = tryRealpath(path) ?? path;
384
+ if (seen.has(key)) return;
302
385
  paths.push({ path, descriptor });
303
- seen.add(path);
304
- return true;
386
+ seen.add(key);
305
387
  };
306
388
 
307
- push(join(opts.homeClaudeDir, "CLAUDE.md"), GLOBAL_DESCRIPTOR);
389
+ if (rule !== "agents-md") {
390
+ const managed = opts.managedDir ?? claudeManagedDir();
391
+ push(join(managed, "CLAUDE.md"), MANAGED_DESCRIPTOR);
392
+ paths.push(...rules(join(managed, ".claude", "rules"), "Managed", MANAGED_DESCRIPTOR));
393
+ }
394
+ if (claudeFiles) {
395
+ push(join(opts.homeClaudeDir, "CLAUDE.md"), GLOBAL_DESCRIPTOR);
396
+ paths.push(...rules(join(opts.homeClaudeDir, "rules"), "User", GLOBAL_DESCRIPTOR));
397
+ }
308
398
  if (includeOneCode && opts.homeOneCodeDir) {
309
399
  const globalOneCode = firstOneCodeFile(opts.homeOneCodeDir);
310
400
  if (globalOneCode) push(globalOneCode, ONECODE_GLOBAL_DESCRIPTOR);
311
401
  }
312
402
 
313
- for (const d of ancestorDirs(opts.cwd)) {
314
- const hasClaude = push(join(d, "CLAUDE.md"), PROJECT_DESCRIPTOR);
315
- // AGENTS.md stands in for a missing CLAUDE.md in this directory.
316
- if (!hasClaude && opts.agentsFallback) push(join(d, "AGENTS.md"), AGENTS_DESCRIPTOR);
317
- push(join(d, "CLAUDE.local.md"), LOCAL_DESCRIPTOR);
403
+ for (const d of dirs) {
404
+ if (claudeFiles) {
405
+ push(join(d, "CLAUDE.md"), PROJECT_DESCRIPTOR);
406
+ if (dirname(d) !== d) push(join(d, ".claude", "CLAUDE.md"), PROJECT_DESCRIPTOR);
407
+ }
408
+ if (agentsFiles) {
409
+ push(join(d, "AGENTS.md"), AGENTS_DESCRIPTOR);
410
+ // `.claude/AGENTS.md` is a Claude Code location; independent mode reads none.
411
+ if (claudeFiles) push(join(d, ".claude", "AGENTS.md"), AGENTS_DESCRIPTOR);
412
+ }
413
+ if (claudeFiles) {
414
+ if (dirname(d) !== d) paths.push(...rules(join(d, ".claude", "rules"), "Project", PROJECT_DESCRIPTOR));
415
+ push(join(d, "CLAUDE.local.md"), LOCAL_DESCRIPTOR);
416
+ }
318
417
  if (includeOneCode) {
319
418
  const oneCode = firstOneCodeFile(d);
320
419
  if (oneCode) push(oneCode, ONECODE_DESCRIPTOR);
@@ -324,25 +423,269 @@ export function discoverContextFilePaths(opts: {
324
423
  return paths;
325
424
  }
326
425
 
327
- export function discoverContextFiles(opts: {
426
+ /**
427
+ * The project's own instruction files in play under the running rule
428
+ * (`instructionRule`), from cwd up to the git root (or just cwd outside a
429
+ * repo), nearest directory first; with `homeOneCodeDir`, its ONECODE.md files
430
+ * too. The startup banner lists these.
431
+ */
432
+ export function projectInstructionFiles(opts: { cwd: string; home: string; homeOneCodeDir?: string }): string[] {
433
+ const stop = findGitRoot(opts.cwd) ?? opts.cwd;
434
+ const files = discoverContextFilePaths({ cwd: opts.cwd, homeClaudeDir: claudeUserDir(opts.home), homeOneCodeDir: opts.homeOneCodeDir, rule: instructionRule(opts.home) });
435
+ // The directory a file belongs to: `.claude/AGENTS.md` counts as its parent's.
436
+ const owner = (path: string) => (basename(dirname(path)) === ".claude" ? dirname(dirname(path)) : dirname(path));
437
+ return files
438
+ .filter(({ path, descriptor }) => descriptor !== GLOBAL_DESCRIPTOR && descriptor !== ONECODE_GLOBAL_DESCRIPTOR && isPathAtOrUnder(path, stop))
439
+ .map(({ path }) => path)
440
+ .sort((a, b) => owner(b).length - owner(a).length);
441
+ }
442
+
443
+ /**
444
+ * The instruction files Claude Code attaches when the model reads a file below
445
+ * the working directory (its `nested_memory` attachment): for each directory
446
+ * from just below cwd down to the file's own, `CLAUDE.md`, `.claude/CLAUDE.md`
447
+ * and `CLAUDE.local.md`, under the same rule as the startup block (AGENTS.md
448
+ * per `rule`, decided for the whole project as at startup; none of the
449
+ * CLAUDE.md family and no `.claude` directory in independent mode), plus One
450
+ * Code's ONECODE.md. Directories at or above cwd are left out: the startup
451
+ * block already carries them. Rules attach in Claude Code's order: managed/user
452
+ * conditional, nested files/rules, then ancestor conditional rules.
453
+ *
454
+ * Named nested files retain One Code's real-path confinement to the project;
455
+ * rules use claude-rules.ts's source-specific link/include policy. A symlink
456
+ * and its target are one file (`key`), and the file being read is never its
457
+ * own attachment. `imported` lists the real paths the legacy inline expander
458
+ * pulled in, so the caller can treat them as shown; parsed imports instead
459
+ * appear as their own files.
460
+ */
461
+ export function nestedInstructionFiles(opts: {
462
+ filePath: string;
328
463
  cwd: string;
329
- homeClaudeDir: string;
330
- homeOneCodeDir?: string;
331
- agentsFallback?: boolean;
464
+ rule: InstructionRule;
465
+ home: string;
466
+ homeClaudeDir?: string;
467
+ managedDir?: string;
468
+ includeExternal?: boolean;
469
+ }): { path: string; key: string; content: string; imported: string[] }[] {
470
+ const target = absoluteFrom(opts.cwd, opts.filePath);
471
+ if (target === absoluteFrom(opts.cwd, ".")) return [];
472
+ const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
473
+ const inProject = (path: string | undefined): path is string => path !== undefined && isPathAtOrUnder(path, realCwd);
474
+ // Named nested files stay confined to the project. An authorized read of a
475
+ // sibling can still match an ancestor's rule, whose base is that ancestor.
476
+ const realParent = tryRealpath(dirname(target));
477
+ let nestedParent = dirname(target);
478
+ if (!isPathAtOrUnder(nestedParent, opts.cwd) && inProject(realParent)) nestedParent = join(opts.cwd, relative(realCwd, realParent));
479
+ const walkNested = isPathAtOrUnder(nestedParent, opts.cwd) && inProject(realParent);
480
+ const realTarget = tryRealpath(target);
481
+ const claudeFiles = opts.rule !== "agents-md" && opts.rule !== "managed-only";
482
+ const dirs: string[] = [];
483
+ for (let dir = nestedParent; walkNested && isPathAtOrUnder(dir, opts.cwd) && !isPathAtOrUnder(opts.cwd, dir); dir = dirname(dir)) {
484
+ dirs.unshift(dir);
485
+ if (dirname(dir) === dir) break;
486
+ }
487
+ // Independent mode reads no Claude Code location, so it never walks into a `.claude` directory.
488
+ const userDir = opts.homeClaudeDir ?? claudeUserDir(opts.home);
489
+ const allowedLocation = (path: string) => opts.rule !== "agents-md" || !isClaudeLocation(path, userDir);
490
+ const walked = dirs.filter(allowedLocation);
491
+ // claude-md-or-agents-md is decided per project, as at startup: AGENTS.md only where no CLAUDE.md is in play.
492
+ const projectHasClaude = () =>
493
+ [...ancestorDirs(opts.cwd), ...walked].some((dir) => dirHasClaudeMd(dir));
494
+ const agents = opts.rule === "agents-md" || opts.rule === "claude-md-and-agents-md" || (opts.rule === "claude-md-or-agents-md" && !projectHasClaude());
495
+ // Approved external chains use the reference parser below; the legacy fallback stays confined.
496
+ const readAllowed = (path: string) => (allowedLocation(path) ? readFileIfPresent(path) : null);
497
+ const readInProject = (path: string) => (inProject(tryRealpath(path)) ? readAllowed(path) : null);
498
+ const seen = new Set<string>(realTarget ? [realTarget] : []);
499
+ const files: { path: string; key: string; content: string; imported: string[] }[] = [];
500
+ const addRules = (rulesDir: string, scope: RuleOptions["scope"], conditional: boolean) => {
501
+ for (const file of discoverRules({ rulesDir, scope, cwd: opts.cwd, home: opts.home, ...(scope !== "User" ? { includeExternal: opts.includeExternal } : {}), ...(conditional ? { filePath: target } : {}) })) {
502
+ if (seen.has(file.key)) continue;
503
+ seen.add(file.key);
504
+ files.push({ ...file, imported: [] });
505
+ }
506
+ };
507
+ if (opts.rule !== "agents-md") addRules(join(opts.managedDir ?? claudeManagedDir(), ".claude", "rules"), "Managed", true);
508
+ if (claudeFiles) addRules(join(opts.homeClaudeDir ?? claudeUserDir(opts.home), "rules"), "User", true);
509
+ for (const dir of walked) {
510
+ const candidates: string[] = [];
511
+ if (claudeFiles) candidates.push(join(dir, "CLAUDE.md"), join(dir, ".claude", "CLAUDE.md"));
512
+ if (agents) {
513
+ candidates.push(join(dir, "AGENTS.md"));
514
+ if (claudeFiles) candidates.push(join(dir, ".claude", "AGENTS.md"));
515
+ }
516
+ if (claudeFiles) candidates.push(join(dir, "CLAUDE.local.md"));
517
+ const oneCode = firstOneCodeFile(dir);
518
+ if (oneCode) candidates.push(oneCode);
519
+ for (const path of candidates) {
520
+ const key = tryRealpath(path);
521
+ if (!inProject(key) || seen.has(key) || !allowedLocation(path)) continue;
522
+ if (basename(dirname(path)) === ".claude" && basename(path) === "CLAUDE.md") {
523
+ for (const file of readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: "Project", includeExternal: opts.includeExternal })) {
524
+ if (seen.has(file.key)) continue;
525
+ seen.add(file.key);
526
+ files.push({ ...file, imported: [] });
527
+ }
528
+ continue;
529
+ }
530
+ const external = path !== oneCode ? approvedExternalInstructions(path, opts) : undefined;
531
+ if (external) {
532
+ for (const file of external) {
533
+ if (seen.has(file.key)) continue;
534
+ seen.add(file.key);
535
+ files.push({ ...file, imported: [] });
536
+ }
537
+ continue;
538
+ }
539
+ const content = readFileIfPresent(path);
540
+ if (content === null || content.trim() === "") continue;
541
+ seen.add(key);
542
+ // ONECODE.md keeps its inline expander; approval lifts the confinement, as at startup.
543
+ const read = path === oneCode && opts.includeExternal ? readAllowed : readInProject;
544
+ const imported = [...collectImportedPaths(content, dirname(path), { home: opts.home, read })].map((p) => tryRealpath(p) ?? p);
545
+ for (const p of imported) seen.add(p);
546
+ files.push({ path, key, content: expandImports(content, dirname(path), { home: opts.home, read }), imported });
547
+ }
548
+ if (claudeFiles) {
549
+ addRules(join(dir, ".claude", "rules"), "Project", false);
550
+ addRules(join(dir, ".claude", "rules"), "Project", true);
551
+ }
552
+ }
553
+ if (claudeFiles) for (const dir of ancestorDirs(opts.cwd)) if (dirname(dir) !== dir) addRules(join(dir, ".claude", "rules"), "Project", true);
554
+ return files;
555
+ }
556
+
557
+ /** Claude Code's `nested_memory` rendering (the reminder queue adds the `<system-reminder>` frame). */
558
+ export function nestedInstructionText(file: { path: string; content: string }): string {
559
+ return `Contents of ${file.path}:\n\n${file.content}`;
560
+ }
561
+
562
+ export function discoverContextFiles(opts: DiscoveryOptions & {
332
563
  /** Home directory for resolving `~` in `@path` imports. */
333
564
  home: string;
334
565
  }): ContextFile[] {
335
566
  const files: ContextFile[] = [];
336
- for (const { path, descriptor } of discoverContextFilePaths(opts)) {
567
+ // Under claude-md-and-agents-md an AGENTS.md that a loaded file already
568
+ // imports, or whose content one already carries, is not loaded twice (CC).
569
+ const dedupe = opts.rule === "claude-md-and-agents-md";
570
+ const imported = new Set<string>();
571
+ const contents = new Set<string>();
572
+ const emitted = new Set<string>();
573
+ const parsedShown = new Set<string>();
574
+ const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
575
+ const readAllowedImport = (path: string) => opts.rule !== "agents-md" || !isClaudeLocation(path, opts.homeClaudeDir) ? readFileIfPresent(path) : null;
576
+ const readProjectImport = (path: string) => {
577
+ const key = tryRealpath(path);
578
+ return key && isPathAtOrUnder(key, realCwd) ? readAllowedImport(path) : null;
579
+ };
580
+ const appendParsed = (file: RuleFile, descriptor: string) => {
581
+ if (emitted.has(file.key)) return;
582
+ emitted.add(file.key);
583
+ parsedShown.add(file.key);
584
+ imported.add(file.key);
585
+ contents.add(file.content.trim());
586
+ files.push({ path: file.path, descriptor, content: file.content.trim(), imported: [] });
587
+ };
588
+ const entries = discoverInstructionEntries(opts, (rulesDir, scope, descriptor) =>
589
+ discoverRules({ rulesDir, scope, cwd: opts.cwd, home: opts.home, ...(scope !== "User" ? { includeExternal: opts.includeExternal } : {}) }).map((ruleFile) => ({ path: ruleFile.path, descriptor, ruleFile })),
590
+ );
591
+ for (const { path, descriptor, ruleFile } of entries) {
592
+ const key = tryRealpath(path) ?? path;
593
+ if (ruleFile) {
594
+ appendParsed(ruleFile, descriptor);
595
+ continue;
596
+ }
597
+ // ~/.claude/CLAUDE.md is the pre-existing global surface, not the new
598
+ // project location: leave its legacy bytes alone when no new files exist.
599
+ const dotClaude = descriptor === PROJECT_DESCRIPTOR && basename(dirname(path)) === ".claude" && basename(path) === "CLAUDE.md";
600
+ if (dotClaude || descriptor === MANAGED_DESCRIPTOR) {
601
+ for (const file of readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: descriptor === MANAGED_DESCRIPTOR ? "Managed" : "Project", includeExternal: opts.includeExternal, ...(dotClaude ? { ownerDir: dirname(dirname(path)) } : {}) })) appendParsed(file, descriptor);
602
+ continue;
603
+ }
604
+ if (parsedShown.has(key)) continue;
337
605
  // Re-check the read: a file present at enumeration but unreadable now is
338
606
  // omitted, exactly as before (the block must never carry empty entries).
339
607
  const content = readFileIfPresent(path);
340
608
  if (content === null) continue;
341
- files.push({ path, content: expandImports(content, dirname(path), { home: opts.home }), descriptor });
609
+ if (dedupe) {
610
+ const trimmed = content.trim();
611
+ // Compared by real path, so a `./`-spelled or symlinked import still matches.
612
+ if (descriptor === AGENTS_DESCRIPTOR && (imported.has(tryRealpath(path) ?? path) || (trimmed !== "" && contents.has(trimmed)))) continue;
613
+ contents.add(trimmed);
614
+ }
615
+ // Keep legacy bytes when no external import exists. Approved external
616
+ // chains use the same parser as the warning probe (escaped spaces,
617
+ // fragments, depth/extension limits and separate imported sections).
618
+ const projectSource = descriptor === PROJECT_DESCRIPTOR || descriptor === LOCAL_DESCRIPTOR || descriptor === AGENTS_DESCRIPTOR;
619
+ const external = projectSource ? approvedExternalInstructions(path, opts) : undefined;
620
+ if (external) {
621
+ for (const file of external) appendParsed(file, descriptor);
622
+ continue;
623
+ }
624
+ const importOptions = { home: opts.home, read: projectSource ? readProjectImport : readAllowedImport };
625
+ const included = [...collectImportedPaths(content, dirname(path), importOptions)];
626
+ for (const p of included) {
627
+ const importedKey = tryRealpath(p) ?? p;
628
+ imported.add(importedKey);
629
+ emitted.add(importedKey);
630
+ }
631
+ files.push({ path, content: expandImports(content, dirname(path), importOptions), descriptor, ...(content.includes("@") ? { imported: included } : {}) });
632
+ emitted.add(key);
342
633
  }
343
634
  return files;
344
635
  }
345
636
 
637
+ /** Use CC's parsed records for an approved external chain, without changing legacy no-import bytes. */
638
+ function approvedExternalInstructions(path: string, opts: { cwd: string; home: string; homeClaudeDir?: string; rule?: InstructionRule; includeExternal?: boolean }): RuleFile[] | undefined {
639
+ if (!opts.includeExternal) return;
640
+ const files = readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: "Project", includeExternal: true,
641
+ allowPath: (path) => opts.rule !== "agents-md" || !isClaudeLocation(path, opts.homeClaudeDir ?? claudeUserDir(opts.home)),
642
+ });
643
+ const cwd = tryRealpath(opts.cwd) ?? opts.cwd;
644
+ return files.some((file) => file.parent && !isPathAtOrUnder(file.key, cwd)) ? files : undefined;
645
+ }
646
+
647
+ /**
648
+ * The startup warning probe: load with external includes enabled, then keep
649
+ * non-user imports/links outside cwd. Missing, empty, binary-extension and
650
+ * over-depth targets never become records, so they cannot cause a question.
651
+ * This scan is UI-only; its contents must never enter the reminder queue.
652
+ */
653
+ export function externalInstructionIncludes(opts: DiscoveryOptions & { home: string }): string[] {
654
+ const outside: string[] = [];
655
+ const seen = new Set<string>();
656
+ const cwd = tryRealpath(opts.cwd) ?? opts.cwd;
657
+ const add = (file: RuleFile) => {
658
+ if ((!file.parent && !file.linkedFrom) || isPathAtOrUnder(file.key, cwd) || seen.has(file.key)) return;
659
+ seen.add(file.key);
660
+ outside.push(file.key);
661
+ };
662
+ const entries = discoverInstructionEntries(opts, (rulesDir, scope, descriptor) => {
663
+ if (scope === "User") return [];
664
+ return discoverRules({ rulesDir, scope, cwd: opts.cwd, home: opts.home, includeExternal: true })
665
+ .map((ruleFile) => ({ path: ruleFile.path, descriptor, ruleFile }));
666
+ });
667
+ for (const { path, descriptor, ruleFile } of entries) {
668
+ if (descriptor === GLOBAL_DESCRIPTOR || descriptor === ONECODE_GLOBAL_DESCRIPTOR || descriptor === ONECODE_DESCRIPTOR) continue;
669
+ if (ruleFile) { add(ruleFile); continue; }
670
+ const dotClaude = descriptor === PROJECT_DESCRIPTOR && basename(dirname(path)) === ".claude" && basename(path) === "CLAUDE.md";
671
+ for (const file of readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: descriptor === MANAGED_DESCRIPTOR ? "Managed" : "Project", includeExternal: true,
672
+ allowPath: (path) => opts.rule !== "agents-md" || !isClaudeLocation(path, opts.homeClaudeDir),
673
+ ...(dotClaude ? { ownerDir: dirname(dirname(path)) } : {}),
674
+ })) add(file);
675
+ }
676
+ // A project ONECODE.md's imports, resolved by the expander that would load them.
677
+ for (const source of oneCodeSources({ cwd: opts.cwd, home: opts.home, homeOneCodeDir: opts.homeOneCodeDir ?? oneCodeStateDir(process.env, opts.home) })) {
678
+ if (source.descriptor !== ONECODE_DESCRIPTOR) continue;
679
+ for (const imported of collectImportedPaths(source.content, dirname(source.path), { home: opts.home, read: source.unconfined })) {
680
+ const key = tryRealpath(imported) ?? imported;
681
+ if (isPathAtOrUnder(key, cwd) || seen.has(key)) continue;
682
+ seen.add(key);
683
+ outside.push(key);
684
+ }
685
+ }
686
+ return outside;
687
+ }
688
+
346
689
  /**
347
690
  * The ONECODE.md files that exist, ordered global-first then farthest ancestor
348
691
  * down to cwd (nearer wins). These ride in their own `# oneCodeMd` block, not the
@@ -352,31 +695,66 @@ function discoverOneCodeFilePaths(opts: { cwd: string; homeOneCodeDir: string })
352
695
  const paths: ContextFilePath[] = [];
353
696
  const seen = new Set<string>();
354
697
  const push = (path: string | null, descriptor: string) => {
355
- if (!path || seen.has(path)) return;
698
+ if (!path) return;
699
+ const key = tryRealpath(path) ?? path;
700
+ if (seen.has(key)) return;
356
701
  paths.push({ path, descriptor });
357
- seen.add(path);
702
+ seen.add(key);
358
703
  };
359
704
  push(firstOneCodeFile(opts.homeOneCodeDir), ONECODE_GLOBAL_DESCRIPTOR);
360
705
  for (const d of ancestorDirs(opts.cwd)) push(firstOneCodeFile(d), ONECODE_DESCRIPTOR);
361
706
  return paths;
362
707
  }
363
708
 
364
- /** ONECODE.md files with `@import`s expanded, for the `# oneCodeMd` block. */
365
- export function discoverOneCodeFiles(opts: { cwd: string; homeOneCodeDir: string; home: string }): ContextFile[] {
366
- const files: ContextFile[] = [];
367
- for (const { path, descriptor } of discoverOneCodeFilePaths(opts)) {
368
- const content = readFileIfPresent(path);
369
- if (content === null) continue;
370
- files.push({ path, content: expandImports(content, dirname(path), { home: opts.home }), descriptor });
371
- }
372
- return files;
709
+ /**
710
+ * The ONECODE.md files present, each with the readers for its `@import`s:
711
+ * `confined` is what loads without consent (anywhere for the user's global
712
+ * file, which the user wrote; at or under the cwd's real path for a project
713
+ * file, like CLAUDE.md), `unconfined` what loads once the user approved.
714
+ */
715
+ function oneCodeSources(opts: { cwd: string; homeOneCodeDir: string; home: string }): {
716
+ path: string;
717
+ descriptor: string;
718
+ content: string;
719
+ confined: (path: string) => string | null;
720
+ unconfined: (path: string) => string | null;
721
+ }[] {
722
+ const allowedLocation = (path: string) => claudeSourcesOn() || !isClaudeLocation(path, claudeUserDir(opts.home));
723
+ const unconfined = (path: string) => allowedLocation(path) ? readFileIfPresent(path) : null;
724
+ const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
725
+ const inProject = (path: string) => {
726
+ const key = tryRealpath(path);
727
+ return key && isPathAtOrUnder(key, realCwd) ? unconfined(path) : null;
728
+ };
729
+ return discoverOneCodeFilePaths(opts).flatMap(({ path, descriptor }) => {
730
+ const content = unconfined(path);
731
+ return content === null ? [] : [{ path, descriptor, content, unconfined, confined: descriptor === ONECODE_DESCRIPTOR ? inProject : unconfined }];
732
+ });
373
733
  }
374
734
 
375
- const ONECODE_PREAMBLE =
376
- "# oneCodeMd\n" +
377
- "The following are One Code-specific instructions, read only by One Code and not by other tools. " +
378
- "IMPORTANT: they take precedence over the CLAUDE.md instructions above and over any default behavior — " +
379
- "where they conflict with CLAUDE.md, follow these. Follow them exactly as written.";
735
+ /**
736
+ * ONECODE.md files with `@import`s expanded, for the `# oneCodeMd` block. A
737
+ * project file's imports outside the cwd load only with `includeExternal`
738
+ * (the user's consent, as for CLAUDE.md), which `externalInstructionIncludes` asks for.
739
+ */
740
+ export function discoverOneCodeFiles(opts: { cwd: string; homeOneCodeDir: string; home: string; includeExternal?: boolean }): ContextFile[] {
741
+ return oneCodeSources(opts).map(({ path, descriptor, content, confined, unconfined }) => {
742
+ const importOptions = { home: opts.home, read: opts.includeExternal ? unconfined : confined };
743
+ const imported = [...collectImportedPaths(content, dirname(path), importOptions)];
744
+ return { path, content: expandImports(content, dirname(path), importOptions), descriptor, ...(content.includes("@") ? { imported } : {}) };
745
+ });
746
+ }
747
+
748
+ /** The block's preamble, naming the files above it: AGENTS.md in independent mode (lib/config-mode.ts). */
749
+ function oneCodePreamble(): string {
750
+ const above = claudeSourcesOn() ? "CLAUDE.md" : "AGENTS.md";
751
+ return (
752
+ "# oneCodeMd\n" +
753
+ "The following are One Code-specific instructions, read only by One Code and not by other tools. " +
754
+ `IMPORTANT: they take precedence over the ${above} instructions above and over any default behavior — ` +
755
+ `where they conflict with ${above}, follow these. Follow them exactly as written.`
756
+ );
757
+ }
380
758
 
381
759
  /**
382
760
  * Assemble the `# oneCodeMd` block's inner text (wrapper added by lib/reminders.ts):
@@ -386,35 +764,62 @@ const ONECODE_PREAMBLE =
386
764
  */
387
765
  export function buildOneCodeBlock(files: ContextFile[]): string | null {
388
766
  if (files.length === 0) return null;
389
- const sections = files.map((s) => `Contents of ${s.path} (${s.descriptor}):\n\n${s.content}`).join("\n");
390
- return `${ONECODE_PREAMBLE}\n\n${sections}`;
767
+ return withoutFinalNewline(`${oneCodePreamble()}\n\n${files.map(section).join("\n")}`);
391
768
  }
392
769
 
393
770
  export const AGENTS_DESCRIPTOR = "cross-tool agent instructions, AGENTS.md standard";
394
771
 
395
772
  /**
396
- * Assemble the block's inner text (the `<system-reminder>` wrapper is added by
397
- * lib/reminders.ts). Byte-exact rule reverse-engineered from opus-4-8.json:
773
+ * One file's section. Its content is raw, but always ends in a newline: the
774
+ * join rule relies on it, and a file without one would glue the next section,
775
+ * or the next section's header, onto its last line.
776
+ */
777
+ function section(file: ContextFile): string {
778
+ const content = file.content.endsWith("\n") ? file.content : `${file.content}\n`;
779
+ return `Contents of ${file.path} (${file.descriptor}):\n\n${content}`;
780
+ }
781
+
782
+ /**
783
+ * The date reminder's value: the user's LOCAL calendar date as YYYY-MM-DD, the
784
+ * date their own clock shows (a UTC date is a day off every evening west of
785
+ * UTC and every early morning east of it).
786
+ */
787
+ export function localDate(now: Date = new Date()): string {
788
+ const pad = (n: number) => String(n).padStart(2, "0");
789
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
790
+ }
791
+
792
+ /**
793
+ * Claude Code's notice when the local date moves on mid-session. The block
794
+ * carrying the date is frozen after the first request, so the new date
795
+ * rides a one-shot where the model reads next, the way Claude Code does.
796
+ */
797
+ export function dateChangeReminder(date: string): string {
798
+ return `The date has changed. Today's date is now ${date}. No need to announce the new date \u2014 the user's own clock shows it.`;
799
+ }
800
+
801
+ /** The text without one trailing newline: the reminder frame supplies the newline before its closing tag. */
802
+ function withoutFinalNewline(text: string): string {
803
+ return text.endsWith("\n") ? text.slice(0, -1) : text;
804
+ }
805
+
806
+ /**
807
+ * The instructions block's inner text (the `<system-reminder>` wrapper is added
808
+ * by lib/reminders.ts):
398
809
  *
399
810
  * {PREAMBLE}\n\n
400
- * {section}\n{section}\n… ← sections joined by a single "\n"
401
- * # userEmail\nThe user's email address is {email}.\n
402
- * # currentDate\nToday's date is {date}.\n
403
- * \n{TRAILER}
811
+ * {section}\n{section}\n…{last section without its final newline}
404
812
  *
405
813
  * where each section is `Contents of {path} ({descriptor}):\n\n{content}` and
406
814
  * `content` keeps its own trailing newline (files are read raw, never trimmed —
407
- * a file ending in "\n" plus the join "\n" is the "\n\n" seen between sections;
408
- * the last section's own trailing "\n" is the single "\n" before `# userEmail`).
409
- *
410
- * `memoryIndex`, when present, is appended as a final context section with the
411
- * memory descriptor. Returns null when there is nothing at all to inject.
815
+ * a file ending in "\n" plus the join "\n" is the "\n\n" seen between
816
+ * sections). A file that does not end in "\n" gets one, so no header lands on
817
+ * its last line. `memoryIndex`, when present, is appended as a final section
818
+ * with the memory descriptor. Returns null when there is nothing to inject.
412
819
  */
413
820
  export function buildClaudeMdBlock(opts: {
414
821
  contextFiles: ContextFile[];
415
822
  memoryIndex?: { path: string; content: string } | null;
416
- email?: string | null;
417
- date: string;
418
823
  }): string | null {
419
824
  const sections = [...opts.contextFiles];
420
825
  if (opts.memoryIndex && opts.memoryIndex.content.trim()) {
@@ -424,15 +829,25 @@ export function buildClaudeMdBlock(opts: {
424
829
  descriptor: MEMORY_DESCRIPTOR,
425
830
  });
426
831
  }
832
+ if (sections.length === 0) return null;
833
+ return withoutFinalNewline(`${PREAMBLE}\n\n${sections.map(section).join("\n")}`);
834
+ }
427
835
 
428
- if (sections.length === 0 && !opts.email) return null;
836
+ /**
837
+ * The context block's inner text: the preamble, `# userEmail` with Claude
838
+ * Code's use-only-to-identify sentence, the `# gitStatus` snapshot
839
+ * (lib/git-status.ts), then a blank line and the footer. Null with neither an
840
+ * email nor a snapshot.
841
+ */
842
+ export function buildContextBlock(opts: { email?: string | null; gitStatus?: string | null }): string | null {
843
+ const parts: string[] = [];
844
+ if (opts.email) parts.push(`# userEmail\nThe user's email address is ${opts.email}. ${EMAIL_USE}`);
845
+ if (opts.gitStatus) parts.push(opts.gitStatus);
846
+ if (parts.length === 0) return null;
847
+ return `${CONTEXT_PREAMBLE}\n${parts.join("\n")}\n\n${CONTEXT_FOOTER}`;
848
+ }
429
849
 
430
- let inner = `${PREAMBLE}\n\n`;
431
- inner += sections.map((s) => `Contents of ${s.path} (${s.descriptor}):\n\n${s.content}`).join("\n");
432
- if (opts.email) {
433
- inner += `# userEmail\nThe user's email address is ${opts.email}.\n`;
434
- }
435
- inner += `# currentDate\nToday's date is ${opts.date}.\n`;
436
- inner += `\n${TRAILER}`;
437
- return inner;
850
+ /** The date reminder's inner text, sent whether or not any CLAUDE.md exists. */
851
+ export function dateBlock(date: string): string {
852
+ return `Today's date is ${date}.`;
438
853
  }