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
@@ -0,0 +1,68 @@
1
+ /** Claude Code's project-scoped external instruction consent; pure fs/text, no pi imports. */
2
+ import { readJsonFile } from "./atomic-write.ts";
3
+ import { oneCodeProjectRoot, oneCodeProjectSettingsPathFor, readSettingsForWrite, writeSettings } from "./one-code-settings.ts";
4
+ import { comparablePath, tildify } from "./paths.ts";
5
+ import { escapeControlText } from "./terminal-text.ts";
6
+
7
+ export const EXTERNAL_INCLUDES_TITLE = "Allow external CLAUDE.md file imports?";
8
+ export const EXTERNAL_INCLUDES_YES = "Yes, allow external imports";
9
+ export const EXTERNAL_INCLUDES_NO = "No, disable external imports";
10
+ export const EXTERNAL_INCLUDES_SETTING = "External CLAUDE.md includes";
11
+ /**
12
+ * `/config` with nothing to show. Claude Code's /config is a full settings
13
+ * panel and hides this row when there are no external includes; One Code's
14
+ * holds only this row so far (decisions/tools.md), so the empty case names
15
+ * where the other settings live instead of ending the command.
16
+ */
17
+ export const EXTERNAL_INCLUDES_NONE =
18
+ "No external CLAUDE.md includes in this project, so /config has nothing to change. Other settings: /settings (pi's settings), /model, /permissions, /memory.";
19
+
20
+ export interface ExternalIncludesApproval {
21
+ approved: boolean;
22
+ warningShown: boolean;
23
+ }
24
+
25
+ /**
26
+ * The project root an answer was given for. The settings file's slug is lossy
27
+ * (`my_app` and `my-app` share one file), so the answer counts only for this
28
+ * exact root; an answer recorded without one is asked again.
29
+ */
30
+ const APPROVAL_ROOT_KEY = "claudeMdExternalIncludesRoot";
31
+
32
+ /** Only One Code's out-of-checkout project state grants consent, never merged project settings. */
33
+ export function readExternalIncludesApproval(cwd: string, home: string): ExternalIncludesApproval {
34
+ const root = oneCodeProjectRoot(cwd);
35
+ const file = readJsonFile<Record<string, unknown>>(oneCodeProjectSettingsPathFor(root, home));
36
+ const recorded = file?.[APPROVAL_ROOT_KEY];
37
+ if (!file || typeof recorded !== "string" || comparablePath(recorded) !== comparablePath(root)) return { approved: false, warningShown: false };
38
+ return {
39
+ approved: file.hasClaudeMdExternalIncludesApproved === true,
40
+ warningShown: file.hasClaudeMdExternalIncludesWarningShown === true,
41
+ };
42
+ }
43
+
44
+ /** Both answers are remembered, and cover future imports, not just the files in the preview. */
45
+ export function persistExternalIncludesApproval(cwd: string, home: string, approved: boolean): void {
46
+ const root = oneCodeProjectRoot(cwd);
47
+ const path = oneCodeProjectSettingsPathFor(root, home);
48
+ writeSettings(path, {
49
+ ...readSettingsForWrite(path),
50
+ hasClaudeMdExternalIncludesApproved: approved,
51
+ hasClaudeMdExternalIncludesWarningShown: true,
52
+ [APPROVAL_ROOT_KEY]: root,
53
+ });
54
+ }
55
+
56
+ /** The dialog text; pi's plain select carries the body in its multiline title, including over RPC. */
57
+ export function externalIncludesDialog(paths: string[], home: string): string {
58
+ const shown = paths.length <= 8 ? paths : paths.slice(0, 6);
59
+ const hidden = paths.length - shown.length;
60
+ const preview = shown.map((path) => ` ${escapeControlText(tildify(path, home))}`);
61
+ if (hidden) preview.push(` … +${hidden} ${hidden === 1 ? "import" : "imports"} not shown.`, " Yes covers those too, plus any this project adds later.");
62
+ return [
63
+ EXTERNAL_INCLUDES_TITLE,
64
+ "This project's CLAUDE.md or .claude/rules imports files outside the current working directory. Never allow this for third-party repositories.",
65
+ ...(paths.length ? [`External imports:\n${preview.join("\n")}`] : []),
66
+ "Important: Only use One Code with files you trust. Accessing untrusted files may pose security risks.",
67
+ ].join("\n\n");
68
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Claude model ids in any provider's spelling (pure, no imports), for the
3
+ * gates that must not pull in the tier module's catalog graph.
4
+ */
5
+
6
+ /**
7
+ * A Claude model id in any provider's spelling (`claude-opus-5-5`,
8
+ * `claude-haiku-4-5-20251001`, `anthropic/claude-sonnet-5.5`, Bedrock's
9
+ * `us.anthropic.claude-fable-5-1-v1`): family, major, and minor when the id
10
+ * has one. A dated suffix is never read as the minor version. Undefined for
11
+ * anything else. `parseClaudeVersion` (model-tier.ts) stays first-party only, for the gates.
12
+ */
13
+ export function parseClaudeId(id: string): { family: "opus" | "sonnet" | "haiku" | "fable"; major: number; minor?: number } | undefined {
14
+ const match = id.match(/(?:^|[/.])claude-(opus|sonnet|haiku|fable)-(\d+)(?:[-.](\d{1,2}))?(?=$|[-@:[])/);
15
+ if (!match) return undefined;
16
+ return { family: match[1] as "opus" | "sonnet" | "haiku" | "fable", major: Number(match[2]), ...(match[3] ? { minor: Number(match[3]) } : {}) };
17
+ }
18
+
19
+ /** The Claude family of a model id in any provider's spelling. */
20
+ export function claudeFamily(id: string): "opus" | "sonnet" | "haiku" | "fable" | undefined {
21
+ return parseClaudeId(id)?.family;
22
+ }
@@ -0,0 +1,323 @@
1
+ /** Claude Code 2.1.289 rule parsing/discovery. Pure filesystem/text helpers; no pi state. */
2
+ import { lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
3
+ import { basename, dirname, extname, isAbsolute, join, relative } from "node:path";
4
+ import ignore, { type Ignore } from "ignore";
5
+ import { Lexer, type Token } from "marked";
6
+ import { parse as parseYaml } from "yaml";
7
+ import { absoluteFrom, comparablePath, expandTilde, forwardSlashes, isPathAtOrUnder, isRelativeInside, tryRealpath } from "./paths.ts";
8
+
9
+ /** pO/Oee: instruction files larger than 4 MiB are skipped, never truncated. */
10
+ export const MAX_INSTRUCTION_BYTES = 4_194_304;
11
+
12
+ export interface RuleFile {
13
+ path: string;
14
+ key: string;
15
+ content: string;
16
+ globs?: string[];
17
+ /** Import/link provenance for the startup external-includes warning. */
18
+ parent?: string;
19
+ linkedFrom?: string;
20
+ }
21
+
22
+ /** f_t/H in the binary: comma lists and brace alternatives, not numeric brace ranges. */
23
+ export function splitRulePaths(value: unknown): string[] {
24
+ const budget = { results: 1000, bytes: 4_194_304 };
25
+ const expand = (pattern: string): string[] => {
26
+ if (!pattern.includes("{")) return [pattern];
27
+ const result: string[] = [];
28
+ const pending = [pattern];
29
+ for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
30
+ const match = next.match(/^([^{]*)\{([^}]+)\}(.*)$/);
31
+ if (!match) { result.push(next); continue; }
32
+ const alternatives = match[2].split(",").map((part) => part.trim());
33
+ budget.bytes -= next.length;
34
+ const count = result.length + pending.length + alternatives.length;
35
+ if (budget.bytes < 0 || count > budget.results || count * pattern.length > budget.bytes) return [pattern];
36
+ for (let i = alternatives.length - 1; i >= 0; i--) pending.push(match[1] + alternatives[i] + match[3]);
37
+ }
38
+ budget.results -= result.length;
39
+ budget.bytes -= result.length * pattern.length;
40
+ return result;
41
+ };
42
+ const split = (input: unknown): string[] => {
43
+ if (Array.isArray(input)) return input.flatMap(split);
44
+ if (typeof input !== "string") return [];
45
+ const parts: string[] = [];
46
+ let part = "", depth = 0;
47
+ for (const char of input) {
48
+ if (char === "{") depth++;
49
+ if (char === "}") depth--;
50
+ if (char === "," && depth === 0) { if (part.trim()) parts.push(part.trim()); part = ""; }
51
+ else part += char;
52
+ }
53
+ if (part.trim()) parts.push(part.trim());
54
+ return parts.flatMap(expand);
55
+ };
56
+ return split(value);
57
+ }
58
+
59
+ /** Fs retries hand-written YAML after quoting special scalars and expanding leading tabs. */
60
+ function frontmatterValue(yaml: string): Record<string, unknown> {
61
+ const object = (value: unknown): Record<string, unknown> =>
62
+ value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
63
+ try { return object(parseYaml(yaml)); } catch { /* retry below */ }
64
+ const repaired = yaml.split("\n").map((line) => {
65
+ const match = line.match(/^([a-zA-Z_-]+):\s+(\S.*)$/);
66
+ if (!match) return line;
67
+ const [, key, value] = match;
68
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) return line;
69
+ if (value.startsWith("[") && value.endsWith("]")) {
70
+ try { if (Array.isArray(parseYaml(value))) return line; } catch { /* quote it below */ }
71
+ }
72
+ const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
73
+ return /[{}[\]*&#!|>%@`]|: /.test(value) ? `${key}: "${escaped}"` : line;
74
+ }).join("\n").replace(/^\t+/gm, (tabs) => " ".repeat(tabs.length));
75
+ try { return object(parseYaml(repaired)); } catch { return {}; }
76
+ }
77
+
78
+ /** W1n/tgt/Sxe: remove frontmatter and block HTML comments, retain raw body whitespace. */
79
+ export function parseRule(content: string): { content: string; globs?: string[] } {
80
+ const withoutBom = content.replace(/^\uFEFF/, "");
81
+ const match = withoutBom.match(/^---\s*\n([\s\S]*?)---\s*\n?/);
82
+ const metadata = match ? frontmatterValue(match[1]) : {};
83
+ let body = match ? withoutBom.slice(match[0].length) : content;
84
+ if (body.includes("<!--")) {
85
+ body = new Lexer({ gfm: false }).lex(body).map((token) => {
86
+ if (token.type !== "html" || !token.raw.trimStart().startsWith("<!--") || !token.raw.includes("-->")) return token.raw;
87
+ const residue = token.raw.replace(/<!--[\s\S]*?-->/g, "");
88
+ return residue.trim() ? residue : "";
89
+ }).join("");
90
+ }
91
+ const paths = splitRulePaths(metadata.paths).map((path) => path.endsWith("/**") ? path.slice(0, -3) : path).filter(Boolean);
92
+ return { content: body, ...(paths.length && !paths.every((path) => path === "**") ? { globs: paths } : {}) };
93
+ }
94
+
95
+ /** $ee/lje: ignore's gitignore semantics (including negation), never a glob-library approximation. */
96
+ export function ruleMatches(globs: string[] | undefined, filePath: string, base: string): boolean {
97
+ if (!globs?.length) return false;
98
+ let candidate = isAbsolute(filePath) ? relative(base, filePath) : filePath;
99
+ if (isAbsolute(filePath) && !isRelativeInside(candidate)) {
100
+ // Both sides resolved: a cwd reached through a symlink (or a Windows 8.3
101
+ // short name) spells the base differently from the file's real directory.
102
+ const parent = tryRealpath(dirname(filePath));
103
+ if (parent) candidate = relative(tryRealpath(base) ?? base, join(parent, basename(filePath)));
104
+ }
105
+ if (!isRelativeInside(candidate)) return false;
106
+ return globMatcher(globs).ignores(forwardSlashes(candidate));
107
+ }
108
+
109
+ /** Compiled matchers by glob list: a read checks every conditional rule, so each list compiles once. */
110
+ const matchers = new Map<string, Ignore>();
111
+
112
+ function globMatcher(globs: string[]): Ignore {
113
+ const key = JSON.stringify(globs);
114
+ let matcher = matchers.get(key);
115
+ if (!matcher) {
116
+ const valid = globs.filter((glob) => {
117
+ try { ignore().add(glob).test("probe"); return true; } catch { return false; }
118
+ });
119
+ if (matchers.size >= 1024) matchers.clear();
120
+ matchers.set(key, (matcher = ignore().add(valid)));
121
+ }
122
+ return matcher;
123
+ }
124
+
125
+ /**
126
+ * The paths a walk being cached has looked at, each with its stat signature
127
+ * from before it was read: a change while the walk runs makes the next call
128
+ * walk again rather than keep what it read.
129
+ */
130
+ let touched: Map<string, string> | undefined;
131
+
132
+ function touch(path: string): void {
133
+ if (touched && !touched.has(path)) touched.set(path, signature(path));
134
+ }
135
+
136
+ /** What a stat can see of a path (through a link too); "-" when it is missing. */
137
+ function signature(path: string): string {
138
+ try {
139
+ const link = lstatSync(path);
140
+ const stat = link.isSymbolicLink() ? statSync(path) : link;
141
+ return `${link.ino}:${stat.ino}:${stat.mtimeMs}:${stat.size}:${stat.nlink}`;
142
+ } catch {
143
+ return "-";
144
+ }
145
+ }
146
+
147
+ function isLink(path: string): boolean {
148
+ touch(path);
149
+ try { return lstatSync(path).isSymbolicLink(); } catch { return false; }
150
+ }
151
+
152
+ /**
153
+ * One Code addition, matching the inline expander (claude-context.ts
154
+ * `readImportTarget`): an import ending a sentence (`@docs/x.md.`) names
155
+ * `docs/x.md` when only that is a file. Claude Code reads the literal path,
156
+ * which wins whenever it exists.
157
+ */
158
+ function withoutTrailingPunctuation(target: string): string {
159
+ for (let candidate = target; ; candidate = candidate.slice(0, -1)) {
160
+ touch(candidate);
161
+ try { if (statSync(candidate).isFile()) return candidate; } catch { /* not a file: peel */ }
162
+ if (candidate.length <= 1 || !/[.,;:!?)\]]$/.test(candidate)) return target;
163
+ }
164
+ }
165
+
166
+ /** Y1n: imports are separate instruction files, not substitutions into the rule's body. */
167
+ function ruleImports(content: string, path: string, home: string): string[] {
168
+ const found = new Set<string>();
169
+ const text = (body: string) => {
170
+ for (const match of body.matchAll(/(?:^|\s)@((?:[^\s\\]|\\ )+)/g)) {
171
+ const ref = match[1].split("#")[0].replaceAll("\\ ", " ");
172
+ if (!ref || !(ref.startsWith("./") || ref.startsWith("~/") || (ref.startsWith("/") && ref !== "/") || /^[a-zA-Z0-9._-]/.test(ref))) continue;
173
+ found.add(withoutTrailingPunctuation(absoluteFrom(dirname(path), expandTilde(ref, home))));
174
+ }
175
+ };
176
+ const walk = (tokens: Token[]) => {
177
+ for (const token of tokens) {
178
+ if (token.type === "code" || token.type === "codespan") continue;
179
+ if (token.type === "html") {
180
+ if (token.raw.trimStart().startsWith("<!--") && token.raw.includes("-->")) text(token.raw.replace(/<!--[\s\S]*?-->/g, ""));
181
+ continue;
182
+ }
183
+ if (token.type === "text") text(token.text);
184
+ if ("tokens" in token && token.tokens) walk(token.tokens);
185
+ if ("items" in token) walk(token.items);
186
+ }
187
+ };
188
+ walk(new Lexer({ gfm: false }).lex(content));
189
+ return [...found];
190
+ }
191
+
192
+ const TEXT_EXTENSIONS = new Set([
193
+ ".md", ".txt", ".text", ".json", ".yaml", ".yml", ".toml", ".xml", ".csv", ".html", ".htm", ".css", ".scss", ".sass", ".less",
194
+ ".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".py", ".pyi", ".pyw", ".rb", ".erb", ".rake", ".go", ".rs",
195
+ ".java", ".kt", ".kts", ".scala", ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx", ".cs", ".swift", ".sh", ".bash", ".zsh",
196
+ ".fish", ".ps1", ".bat", ".cmd", ".env", ".ini", ".cfg", ".conf", ".config", ".properties", ".sql", ".graphql", ".gql", ".proto",
197
+ ".vue", ".svelte", ".astro", ".ejs", ".hbs", ".pug", ".jade", ".php", ".pl", ".pm", ".lua", ".r", ".dart", ".ex", ".exs",
198
+ ".erl", ".hrl", ".clj", ".cljs", ".cljc", ".edn", ".hs", ".lhs", ".elm", ".ml", ".mli", ".f", ".f90", ".f95", ".for",
199
+ ".cmake", ".make", ".makefile", ".gradle", ".sbt", ".rst", ".adoc", ".asciidoc", ".org", ".tex", ".latex", ".lock", ".log", ".diff", ".patch",
200
+ ]);
201
+
202
+ export interface RuleOptions {
203
+ rulesDir: string;
204
+ cwd: string;
205
+ home: string;
206
+ scope: "Project" | "User" | "Managed";
207
+ /** Undefined selects unconditional files; a target selects only matching conditional files. */
208
+ filePath?: string;
209
+ /** Select every conditional file, whatever its globs (the auto-mode classifier's instructions). */
210
+ allConditional?: boolean;
211
+ processed?: Set<string>;
212
+ /** Normal CLI user rules allow external includes; project/managed rules need approval. */
213
+ includeExternal?: boolean;
214
+ /** Independent mode excludes Claude locations before reading or following their imports. */
215
+ allowPath?: (path: string) => boolean;
216
+ /**
217
+ * The directory a project file belongs to (`<dir>` for `<dir>/.claude/CLAUDE.md`).
218
+ * A file linked out of both it and the cwd is an external include, like an import.
219
+ */
220
+ ownerDir?: string;
221
+ }
222
+
223
+ /** A text extension, or none: checked for the name read and, through a link, the file it points to. */
224
+ function textExtension(path: string): boolean {
225
+ const ext = extname(path).toLowerCase();
226
+ return !ext || TEXT_EXTENSIONS.has(ext);
227
+ }
228
+
229
+ /** lJ: parent first, separate parsed imports, canonical dedupe, depths zero through four. */
230
+ export function readRuleInstructions(path: string, opts: Pick<RuleOptions, "cwd" | "home" | "scope" | "processed" | "includeExternal" | "allowPath" | "ownerDir">): RuleFile[] {
231
+ const includeExternal = opts.includeExternal ?? opts.scope === "User";
232
+ const processed = opts.processed ?? new Set<string>();
233
+ const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
234
+ const inside = (target: string) => isPathAtOrUnder(target, realCwd);
235
+ const realOwner = opts.ownerDir === undefined ? undefined : tryRealpath(opts.ownerDir) ?? opts.ownerDir;
236
+ const load = (path: string, depth = 0, parent?: string): RuleFile[] => {
237
+ touch(path);
238
+ const key = tryRealpath(path);
239
+ if (key) touch(key);
240
+ if (!key || depth >= 5 || processed.has(comparablePath(key)) || (opts.allowPath && (!opts.allowPath(path) || !opts.allowPath(key))) || (depth > 0 && !includeExternal && !inside(key))) return [];
241
+ // One Code addition: a project file linked out of the project is an include the user must approve.
242
+ const linkedOut = depth === 0 && realOwner !== undefined && !inside(key) && !isPathAtOrUnder(key, realOwner);
243
+ if (linkedOut && !includeExternal) return [];
244
+ try {
245
+ const stat = statSync(key);
246
+ if (!stat.isFile() || stat.size > MAX_INSTRUCTION_BYTES || (opts.scope === "User" && !includeExternal && (stat.nlink > 1 || (depth === 0 && isLink(path))))) return [];
247
+ processed.add(comparablePath(key));
248
+ if (!textExtension(path) || !textExtension(key)) return [];
249
+ const parsed = parseRule(readFileSync(key, "utf8"));
250
+ if (!parsed.content.trim()) return [];
251
+ return [{ path, key, ...parsed, ...(parent ? { parent } : {}), ...(linkedOut ? { linkedFrom: path } : {}) }, ...ruleImports(parsed.content, key, opts.home).flatMap((ref) => load(ref, depth + 1, key))];
252
+ } catch { return []; }
253
+ };
254
+ return load(path);
255
+ }
256
+
257
+ /**
258
+ * Parsed walks by rules directory, scope, consent, cwd and home. Every `read`
259
+ * attaches rules, so a walk (readdir, realpath, read, lex and YAML per file) is
260
+ * reused while a stat of every path it looked at, imports and missing paths
261
+ * included, still matches. Consent is part of the key: a walk made without it
262
+ * is never served with it, or the other way round.
263
+ */
264
+ const walks = new Map<string, { stamps: Map<string, string>; files: RuleFile[] }>();
265
+
266
+ /** o$e: readdir order, depth-first, lowercase .md only; canonical identities stop link cycles. */
267
+ export function discoverRules(opts: RuleOptions): RuleFile[] {
268
+ const base = opts.scope === "Project" ? dirname(dirname(opts.rulesDir)) : opts.cwd;
269
+ const select = (files: RuleFile[]) =>
270
+ files
271
+ .filter((file) => (opts.allConditional ? Boolean(file.globs) : opts.filePath === undefined ? !file.globs : ruleMatches(file.globs, opts.filePath, base)))
272
+ .map((file) => ({ ...file }));
273
+ // A shared dedupe set or a path filter makes the walk depend on its caller: never cached.
274
+ if (opts.processed || opts.allowPath || touched) return select(walkRules(opts));
275
+ const key = JSON.stringify([opts.rulesDir, opts.scope, opts.includeExternal ?? opts.scope === "User", opts.cwd, opts.home]);
276
+ const cached = walks.get(key);
277
+ if (cached && [...cached.stamps].every(([path, stamp]) => signature(path) === stamp)) return select(cached.files);
278
+ const stamps = new Map<string, string>();
279
+ touched = stamps;
280
+ let files: RuleFile[];
281
+ try { files = walkRules(opts); } finally { touched = undefined; }
282
+ if (walks.size >= 256) walks.clear();
283
+ walks.set(key, { stamps, files });
284
+ return select(files);
285
+ }
286
+
287
+ function walkRules(opts: RuleOptions): RuleFile[] {
288
+ const includeExternal = opts.includeExternal ?? opts.scope === "User";
289
+ const visited = new Set<string>();
290
+ const processed = opts.processed ?? new Set<string>();
291
+ const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
292
+ const inside = (path: string) => isPathAtOrUnder(path, realCwd);
293
+ const walk = (dir: string, linkedFrom?: string): RuleFile[] => {
294
+ touch(dir);
295
+ const real = tryRealpath(dir);
296
+ if (real) touch(real);
297
+ if (!real || visited.has(comparablePath(real))) return [];
298
+ // o2n/Tgt allow external user rules. o$e's J1n gate checks a linked
299
+ // .claude parent only on the cwd/ancestor walk, not on nested directories.
300
+ const linkedProjectParent = opts.scope === "Project" && isPathAtOrUnder(opts.cwd, dirname(dirname(dir))) && isLink(dirname(dir));
301
+ if (!includeExternal && !inside(real) && (isLink(dir) || linkedProjectParent)) return [];
302
+ visited.add(comparablePath(real));
303
+ const files: RuleFile[] = [];
304
+ try {
305
+ for (const entry of readdirSync(real, { withFileTypes: true })) {
306
+ const path = join(real, entry.name);
307
+ touch(path);
308
+ const key = tryRealpath(path);
309
+ if (!key || (!includeExternal && key !== path && !inside(key))) continue;
310
+ const stat = entry.isSymbolicLink() ? statSync(key) : entry;
311
+ const source = linkedFrom ? join(linkedFrom, entry.name) : join(dir, entry.name);
312
+ if (stat.isDirectory()) files.push(...walk(key, source !== key ? source : undefined));
313
+ else if (stat.isFile() && entry.name.endsWith(".md")) {
314
+ files.push(...readRuleInstructions(key, { ...opts, processed }).map((file) =>
315
+ file.key === key && source !== key ? { ...file, linkedFrom: source } : file,
316
+ ));
317
+ }
318
+ }
319
+ return files;
320
+ } catch { return []; }
321
+ };
322
+ return walk(opts.rulesDir);
323
+ }
@@ -12,8 +12,10 @@
12
12
  */
13
13
 
14
14
  import { existsSync, readFileSync } from "node:fs";
15
- import { join } from "node:path";
16
- import { claudeUserDir } from "./paths.ts";
15
+ import { join, posix, win32 } from "node:path";
16
+ import { claudeSourcesOn, userConfigDir } from "./config-mode.ts";
17
+ import { oneCodeProjectSettingsPath, oneCodeSettingsPath } from "./one-code-settings.ts";
18
+ import { claudeManagedDir, claudeUserDir } from "./paths.ts";
17
19
 
18
20
  export interface ClaudeSettingsFile {
19
21
  enabledPlugins?: Record<string, unknown>;
@@ -29,6 +31,75 @@ export function claudeUserSettingsPath(home: string): string {
29
31
  return join(claudeUserDir(home), "settings.json");
30
32
  }
31
33
 
34
+ /** Managed-settings locations, highest authority: `managed-settings.json` in Claude Code's managed root. */
35
+ export function managedSettingsPaths(platform: NodeJS.Platform = process.platform): string[] {
36
+ const { join: joinFor } = platform === "win32" ? win32 : posix;
37
+ return [joinFor(claudeManagedDir(platform), "managed-settings.json")];
38
+ }
39
+
40
+ /** Claude Code's `instructionFiles` values (findings §57); the default is `claude-md-or-agents-md`. */
41
+ export type InstructionFiles = "claude-md" | "claude-md-or-agents-md" | "claude-md-and-agents-md" | "managed-only";
42
+ const INSTRUCTION_FILES: readonly InstructionFiles[] = ["claude-md", "claude-md-or-agents-md", "claude-md-and-agents-md", "managed-only"];
43
+ const LEGACY_PROJECT_INSTRUCTIONS: Record<string, InstructionFiles> = {
44
+ none: "managed-only",
45
+ claude: "claude-md",
46
+ "agents-fallback": "claude-md-or-agents-md",
47
+ both: "claude-md-and-agents-md",
48
+ };
49
+ /** The AGENTS.md plugin's `pluginConfigs` keys: the id is inferred, so the bare name counts too. */
50
+ const AGENTS_MD_PLUGIN_KEYS = ["cc-plugin-agents-md@builtin", "cc-plugin-agents-md"];
51
+
52
+ /**
53
+ * Claude Code's `instructionFiles` (findings §57): the AGENTS.md plugin's option
54
+ * under `pluginConfigs[<id>].options`, from the user and managed settings
55
+ * (Claude Code reads no project file for it), managed winning. The legacy
56
+ * top-level `projectInstructions` applies only while `instructionFiles` is unset,
57
+ * mapped as Claude Code maps it (an unknown string reads as `claude-md`).
58
+ */
59
+ export function readInstructionFiles(home: string): InstructionFiles {
60
+ let instructionFiles: InstructionFiles | undefined;
61
+ let legacy: InstructionFiles | undefined;
62
+ for (const path of [claudeUserSettingsPath(home), ...managedSettingsPaths()]) {
63
+ const file = readSettingsFile(path);
64
+ if (!file) continue;
65
+ const configs = file.pluginConfigs as Record<string, { options?: Record<string, unknown> }> | undefined;
66
+ for (const key of AGENTS_MD_PLUGIN_KEYS) {
67
+ const value = configs?.[key]?.options?.instructionFiles;
68
+ if (INSTRUCTION_FILES.includes(value as InstructionFiles)) instructionFiles = value as InstructionFiles;
69
+ }
70
+ if (typeof file.projectInstructions === "string") legacy = LEGACY_PROJECT_INSTRUCTIONS[file.projectInstructions] ?? "claude-md";
71
+ }
72
+ return instructionFiles ?? legacy ?? "claude-md-or-agents-md";
73
+ }
74
+
75
+ /** Where a settings file sits: Claude Code's ladder, One Code's two files, or managed policy. */
76
+ export type SettingsSource = "claude-user" | "onecode-user" | "project" | "project-local" | "onecode-project" | "managed";
77
+
78
+ /**
79
+ * Every settings file One Code reads Claude Code's keys from, lowest
80
+ * precedence first, with its source. Managed settings last: Claude Code's
81
+ * organisation policy outranks every user/project file (review P14).
82
+ * Independent mode (lib/config-mode.ts) reads One Code's own two files only,
83
+ * managed settings included in what it drops (decisions/memory-state.md).
84
+ * Each consumer keeps the sources its keys may come from.
85
+ */
86
+ export function settingsSources(cwd: string, home: string): Array<[SettingsSource, string]> {
87
+ const oneCode: Array<[SettingsSource, string]> = [
88
+ ["onecode-user", oneCodeSettingsPath(home)],
89
+ ["onecode-project", oneCodeProjectSettingsPath(cwd, home)],
90
+ ];
91
+ if (!claudeSourcesOn()) return oneCode;
92
+ const paths = settingsPaths(cwd, home);
93
+ return [
94
+ ["claude-user", paths.user],
95
+ oneCode[0],
96
+ ["project", paths.project],
97
+ ["project-local", paths.local],
98
+ oneCode[1],
99
+ ...managedSettingsPaths().map((path): [SettingsSource, string] => ["managed", path]),
100
+ ];
101
+ }
102
+
32
103
  export function settingsPaths(cwd: string, home: string): { user: string; project: string; local: string } {
33
104
  return {
34
105
  user: claudeUserSettingsPath(home),
@@ -56,7 +127,8 @@ export function readSettingsFile(path: string): ClaudeSettingsFile | undefined {
56
127
  * its project-trust prompt).
57
128
  */
58
129
  export function readSettingsEnv(home: string): Record<string, string> {
59
- const env = readSettingsFile(claudeUserSettingsPath(home))?.env;
130
+ // The mode's user file: One Code's own in independent mode (lib/config-mode.ts).
131
+ const env = readSettingsFile(join(userConfigDir(home), "settings.json"))?.env;
60
132
  if (!env || typeof env !== "object" || Array.isArray(env)) return {};
61
133
  const out: Record<string, string> = {};
62
134
  for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
@@ -70,8 +142,8 @@ export function readSettingsEnv(home: string): Record<string, string> {
70
142
  * merged across the three settings files — later files win per key.
71
143
  */
72
144
  export function readEnabledPlugins(cwd: string, home: string): Record<string, boolean> {
73
- const paths = settingsPaths(cwd, home);
74
145
  const merged: Record<string, boolean> = {};
146
+ const paths = settingsPaths(cwd, home);
75
147
  for (const path of [paths.user, paths.project, paths.local]) {
76
148
  const enabled = readSettingsFile(path)?.enabledPlugins;
77
149
  if (!enabled || typeof enabled !== "object") continue;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The latest compaction on a session branch, and where the entries still in
3
+ * context start. Pure: no pi imports; pi's `SessionEntry` fits the shape.
4
+ *
5
+ * pi builds the context after a compaction as the summary, then the kept
6
+ * entries from `firstKeptEntryId` (which lie before the compaction entry on the
7
+ * branch), then everything after the compaction (`buildContextEntries`). A
8
+ * first kept entry not found before the compaction keeps nothing.
9
+ */
10
+
11
+ type BranchEntry = { id?: string; type?: string; timestamp?: string; firstKeptEntryId?: string | null };
12
+
13
+ export interface CompactionBoundary {
14
+ /** Index of the latest compaction entry on the branch. */
15
+ index: number;
16
+ /** Index of its first kept entry, or `index` when it keeps nothing. */
17
+ keptStart: number;
18
+ /** The compaction's time in ms: every message produced since is newer, every kept one older. */
19
+ time: number;
20
+ }
21
+
22
+ /** The latest compaction on the branch, or undefined when there is none. */
23
+ export function latestCompaction(entries: readonly unknown[]): CompactionBoundary | undefined {
24
+ for (let index = entries.length - 1; index >= 0; index--) {
25
+ const entry = entries[index] as BranchEntry | undefined;
26
+ if (entry?.type !== "compaction") continue;
27
+ const kept = entries.findIndex((candidate) => (candidate as BranchEntry | undefined)?.id === entry.firstKeptEntryId);
28
+ return { index, keptStart: kept >= 0 && kept < index ? kept : index, time: Date.parse(entry.timestamp ?? "") };
29
+ }
30
+ return undefined;
31
+ }
32
+
33
+ /** The branch entries still in context: from the latest compaction's kept tail on, else all of them. */
34
+ export function inContextEntries<E>(entries: readonly E[]): E[] {
35
+ return entries.slice(latestCompaction(entries)?.keptStart ?? 0);
36
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * pi's compaction keep window (`compaction.keepRecentTokens`), set to 0 for
3
+ * One Code.
4
+ *
5
+ * One Code's compaction keeps only the last assistant reply, as Claude Code
6
+ * does (`compaction/cut.ts`), so pi's window of recent tokens kept verbatim
7
+ * (20,000 by default) no longer decides what stays. It still decides whether
8
+ * a compaction can happen: pi's `prepareCompaction` gives up with "Nothing to
9
+ * compact (session too small)" when the whole session fits in the window,
10
+ * before any extension sees the request, so `/compact` on a short session
11
+ * failed. At 0, pi cuts at the last reply itself and any session with one
12
+ * finished turn compacts (findings §8).
13
+ *
14
+ * The bundled app writes 0 into its own settings unless the user set a value;
15
+ * on the user's own pi, One Code asks once (`branding/compaction-keep.ts`) and
16
+ * `/doctor` shows the setting.
17
+ *
18
+ * Plain JS with no pi imports, like replaced-builtins.mjs: app/bin.mjs loads it
19
+ * on bare Node before pi does.
20
+ */
21
+
22
+ /** The keep window One Code wants. */
23
+ export const ONE_CODE_KEEP_RECENT_TOKENS = 0;
24
+
25
+ /** pi's default when no settings file sets one. */
26
+ const PI_DEFAULT_KEEP_RECENT_TOKENS = 20000;
27
+
28
+ /**
29
+ * Settings with the keep window set to One Code's when the user has not set
30
+ * one; `changed` says whether anything was written. An explicit value, pi's
31
+ * default included, is the user's and stays.
32
+ */
33
+ export function withCompactionKeepBackfilled(settings) {
34
+ const compaction = settings?.compaction;
35
+ if (compaction !== undefined && (typeof compaction !== "object" || compaction === null || Array.isArray(compaction))) return { settings, changed: false };
36
+ if (compaction?.keepRecentTokens !== undefined) return { settings, changed: false };
37
+ return { settings: { ...settings, compaction: { ...compaction, keepRecentTokens: ONE_CODE_KEEP_RECENT_TOKENS } }, changed: true };
38
+ }
39
+
40
+ /**
41
+ * The keep window pi will use for `modelKey` (`provider/id`), and where it
42
+ * comes from, as pi resolves it: the model's `compaction.modelOverrides` entry
43
+ * first, then the ordinary setting; the project's settings win over the
44
+ * user's in each; `default` when nothing sets it. `model` names the override
45
+ * that supplied the value.
46
+ */
47
+ export function compactionKeepView(userSettings, projectSettings, modelKey) {
48
+ // Set means defined, as for the backfill; pi itself rejects a non-integer.
49
+ const read = (value) => (value === undefined ? undefined : Number(value));
50
+ const scopes = [
51
+ ["project", projectSettings],
52
+ ["user", userSettings],
53
+ ];
54
+ if (modelKey !== undefined) {
55
+ for (const [source, settings] of scopes) {
56
+ const value = read(settings?.compaction?.modelOverrides?.[modelKey]?.keepRecentTokens);
57
+ if (value !== undefined) return { value, source, model: modelKey };
58
+ }
59
+ }
60
+ for (const [source, settings] of scopes) {
61
+ const value = read(settings?.compaction?.keepRecentTokens);
62
+ if (value !== undefined) return { value, source };
63
+ }
64
+ return { value: PI_DEFAULT_KEEP_RECENT_TOKENS, source: "default" };
65
+ }
66
+
67
+ export const KEEP_YES = "Yes, set it to 0";
68
+ export const KEEP_NO = "No, leave the setting";
69
+
70
+ /** The startup question on the user's own pi. */
71
+ export function keepWindowPrompt(settingsPath) {
72
+ return [
73
+ `One Code compacts like Claude Code: it keeps only the last reply after the summary.`,
74
+ `pi's compaction.keepRecentTokens (${PI_DEFAULT_KEEP_RECENT_TOKENS} by default) no longer changes what is kept; it only makes /compact refuse a session shorter than that.`,
75
+ `Set it to 0 in ${settingsPath}? It takes effect from the next start.`,
76
+ ].join("\n");
77
+ }
78
+
79
+ /** The fix /doctor names for a keep window above 0, in the settings file (and model override) that sets it. */
80
+ export function keepWindowFix(settingsPath, model) {
81
+ const key = model === undefined ? `"keepRecentTokens": 0` : `"modelOverrides": { "${model}": { "keepRecentTokens": 0 } }`;
82
+ return `Set "compaction": { ${key} } in ${settingsPath}, then restart.`;
83
+ }