one-code-extension 0.4.1 → 0.6.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 (439) 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 +40 -28
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +26 -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 +124 -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 +642 -0
  56. package/extensions/background/index.ts +614 -248
  57. package/extensions/background/loop-fire.ts +173 -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 +56 -0
  63. package/extensions/background/wakeup.ts +178 -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 +66 -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 +250 -78
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/cache-marks/index.ts +42 -0
  80. package/extensions/claude-compat/index.ts +7 -11
  81. package/extensions/claude-context/index.ts +436 -63
  82. package/extensions/compaction/cut.ts +157 -0
  83. package/extensions/compaction/index.ts +219 -75
  84. package/extensions/compaction/kept-thinking.ts +62 -0
  85. package/extensions/compaction/prompt.ts +15 -12
  86. package/extensions/context-budget/budget.ts +16 -2
  87. package/extensions/context-budget/index.ts +35 -15
  88. package/extensions/context-management/index.ts +36 -28
  89. package/extensions/doctor/build.ts +59 -8
  90. package/extensions/doctor/builtins.ts +84 -0
  91. package/extensions/doctor/cli.ts +7 -1
  92. package/extensions/doctor/compat.ts +88 -23
  93. package/extensions/doctor/dependencies.ts +41 -5
  94. package/extensions/doctor/fix-prompt.ts +9 -2
  95. package/extensions/doctor/index.ts +102 -58
  96. package/extensions/doctor/models.ts +102 -63
  97. package/extensions/doctor/presets.ts +42 -27
  98. package/extensions/doctor/report.ts +19 -2
  99. package/extensions/doctor/viewer.ts +49 -27
  100. package/extensions/effort/index.ts +141 -25
  101. package/extensions/effort/settings.ts +21 -0
  102. package/extensions/effort/slider.ts +24 -20
  103. package/extensions/exit/index.ts +32 -1
  104. package/extensions/file-tracker/index.ts +420 -42
  105. package/extensions/file-tracker/replay.ts +137 -24
  106. package/extensions/file-tracker/restore.ts +66 -0
  107. package/extensions/file-tracker/results.ts +66 -0
  108. package/extensions/file-tracker/shell-reads.ts +211 -0
  109. package/extensions/file-tracker/tracker.ts +45 -15
  110. package/extensions/footer/footer-line.ts +45 -24
  111. package/extensions/footer/index.ts +47 -2
  112. package/extensions/footer/pr.ts +34 -5
  113. package/extensions/hooks/compaction.ts +7 -0
  114. package/extensions/hooks/executor.ts +32 -30
  115. package/extensions/hooks/index.ts +209 -58
  116. package/extensions/hooks/matcher.ts +3 -0
  117. package/extensions/hooks/prompt-context.ts +59 -0
  118. package/extensions/hooks/protocol.ts +41 -3
  119. package/extensions/hooks/settings.ts +50 -19
  120. package/extensions/hooks/subagent-bridge.ts +2 -0
  121. package/extensions/hooks/trust.ts +26 -12
  122. package/extensions/idle-compact/index.ts +119 -0
  123. package/extensions/idle-compact/policy.ts +143 -0
  124. package/extensions/init/index.ts +11 -10
  125. package/extensions/init/prompt.ts +25 -32
  126. package/extensions/interrupted/index.ts +13 -1
  127. package/extensions/kept-thinking/index.ts +27 -0
  128. package/extensions/ledger/index.ts +46 -0
  129. package/extensions/ledger/wire.ts +154 -0
  130. package/extensions/lib/agent-cron.ts +146 -0
  131. package/extensions/lib/agent-loader.ts +18 -3
  132. package/extensions/lib/agent-view.ts +85 -0
  133. package/extensions/lib/anthropic-auth.ts +98 -0
  134. package/extensions/lib/anthropic-payload.ts +82 -1
  135. package/extensions/lib/anthropic-server-call.ts +230 -0
  136. package/extensions/lib/anthropic-server-tools.ts +485 -0
  137. package/extensions/lib/app-launch.mjs +94 -0
  138. package/extensions/lib/argument-hints.ts +190 -0
  139. package/extensions/lib/atomic-write.ts +6 -2
  140. package/extensions/lib/auto-memory.ts +75 -0
  141. package/extensions/lib/bash-parser.ts +84 -0
  142. package/extensions/lib/btw-fork.ts +93 -0
  143. package/extensions/lib/child-extensions.ts +52 -0
  144. package/extensions/lib/child-writes.ts +45 -0
  145. package/extensions/lib/claude-context.ts +515 -100
  146. package/extensions/lib/claude-external-includes.ts +68 -0
  147. package/extensions/lib/claude-id.ts +22 -0
  148. package/extensions/lib/claude-rules.ts +323 -0
  149. package/extensions/lib/claude-settings.ts +76 -4
  150. package/extensions/lib/compaction-boundary.ts +36 -0
  151. package/extensions/lib/compaction-keep.mjs +83 -0
  152. package/extensions/lib/config-mode.ts +112 -0
  153. package/extensions/lib/consent-dialogs.ts +63 -0
  154. package/extensions/lib/consent-preview.ts +5 -1
  155. package/extensions/lib/consent-stores.ts +48 -0
  156. package/extensions/lib/context-facts.ts +97 -0
  157. package/extensions/lib/covered-messages.ts +23 -0
  158. package/extensions/lib/deferred.ts +359 -25
  159. package/extensions/lib/dock-rows.ts +60 -0
  160. package/extensions/lib/empty-tool-result.ts +27 -0
  161. package/extensions/lib/environment-block.ts +76 -0
  162. package/extensions/lib/fetch-timeout.ts +24 -6
  163. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  164. package/extensions/lib/git.ts +75 -13
  165. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  166. package/extensions/lib/grammars/README.md +2 -0
  167. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  168. package/extensions/lib/guide-docs.ts +45 -0
  169. package/extensions/lib/headless-output.ts +46 -0
  170. package/extensions/lib/hook-gate.ts +7 -7
  171. package/extensions/lib/interrupt.ts +20 -4
  172. package/extensions/lib/key-input.ts +455 -0
  173. package/extensions/lib/keys.ts +14 -1
  174. package/extensions/lib/ledger/fold-memo.ts +49 -0
  175. package/extensions/lib/ledger/fold.ts +265 -0
  176. package/extensions/lib/ledger/layout.ts +72 -0
  177. package/extensions/lib/ledger/plan.ts +102 -0
  178. package/extensions/lib/ledger/records.ts +276 -0
  179. package/extensions/lib/ledger/state.ts +47 -0
  180. package/extensions/lib/ledger/validate.ts +141 -0
  181. package/extensions/lib/ledger/wire.ts +632 -0
  182. package/extensions/lib/local-command.ts +71 -19
  183. package/extensions/lib/mcp-overrides.ts +1 -1
  184. package/extensions/lib/mcp-share.ts +46 -0
  185. package/extensions/lib/memory-content.ts +146 -0
  186. package/extensions/lib/memory-index.ts +18 -0
  187. package/extensions/lib/memory.ts +134 -123
  188. package/extensions/lib/model-catalog/huggingface.json +178 -0
  189. package/extensions/lib/model-catalog/models-dev.json +18533 -0
  190. package/extensions/lib/model-catalog/openrouter.json +5050 -0
  191. package/extensions/lib/model-catalog-data.ts +513 -0
  192. package/extensions/lib/model-catalog.ts +462 -0
  193. package/extensions/lib/model-choice-warnings.ts +137 -0
  194. package/extensions/lib/model-pairings.ts +152 -0
  195. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  196. package/extensions/lib/model-policy.ts +112 -15
  197. package/extensions/lib/model-tier.ts +283 -355
  198. package/extensions/lib/model-unusable.ts +1 -1
  199. package/extensions/lib/newer-model.ts +100 -0
  200. package/extensions/lib/notices.ts +87 -0
  201. package/extensions/lib/notifications.ts +357 -73
  202. package/extensions/lib/one-code-settings.ts +74 -8
  203. package/extensions/lib/open-browser.ts +63 -0
  204. package/extensions/lib/openrouter-generation.ts +94 -0
  205. package/extensions/lib/operation-deadline.ts +30 -0
  206. package/extensions/lib/original-command.ts +4 -4
  207. package/extensions/lib/package-version.ts +1 -1
  208. package/extensions/lib/panel-focus.ts +134 -0
  209. package/extensions/lib/paths.ts +17 -2
  210. package/extensions/lib/permission-gate.ts +72 -6
  211. package/extensions/lib/persisted-output.ts +60 -8
  212. package/extensions/lib/pi-ai-estimate.ts +19 -3
  213. package/extensions/lib/pi-version.ts +38 -2
  214. package/extensions/lib/plan-mode-channels.ts +42 -5
  215. package/extensions/lib/plugins.ts +26 -17
  216. package/extensions/lib/powershell-parse-script.ts +208 -0
  217. package/extensions/lib/powershell-parser.ts +344 -0
  218. package/extensions/lib/previous-mark.ts +101 -0
  219. package/extensions/lib/process-state.ts +48 -0
  220. package/extensions/lib/process-tree.ts +132 -8
  221. package/extensions/lib/prompt-options.ts +82 -0
  222. package/extensions/lib/queued-delivery.ts +44 -0
  223. package/extensions/lib/reminders.ts +370 -358
  224. package/extensions/lib/replaced-builtins.mjs +199 -0
  225. package/extensions/lib/replay-call.ts +70 -0
  226. package/extensions/lib/request-replay.ts +547 -0
  227. package/extensions/lib/scratchpad.ts +81 -14
  228. package/extensions/lib/send-now-channel.ts +7 -0
  229. package/extensions/lib/session-model-tier.ts +51 -0
  230. package/extensions/lib/session-work.ts +64 -0
  231. package/extensions/lib/settings-channels.ts +4 -4
  232. package/extensions/lib/shell-exit-semantics.ts +170 -0
  233. package/extensions/lib/shell-quote.ts +11 -0
  234. package/extensions/lib/shell-result.ts +70 -0
  235. package/extensions/lib/shell-spawn.ts +70 -6
  236. package/extensions/lib/shell-tool.ts +75 -25
  237. package/extensions/lib/side-call-cache.ts +124 -0
  238. package/extensions/lib/side-call-run.ts +96 -0
  239. package/extensions/lib/side-call-usage.ts +38 -0
  240. package/extensions/lib/side-call.ts +24 -1
  241. package/extensions/lib/single-flight.ts +18 -0
  242. package/extensions/lib/skill-body.ts +42 -0
  243. package/extensions/lib/skill-scan.ts +63 -24
  244. package/extensions/lib/spill-file.ts +140 -0
  245. package/extensions/lib/subagent-handback.ts +112 -0
  246. package/extensions/lib/system-role.ts +156 -0
  247. package/extensions/lib/terminal-text.ts +114 -0
  248. package/extensions/lib/text-width.ts +47 -16
  249. package/extensions/lib/timer-ops.ts +20 -0
  250. package/extensions/lib/tool-additions.ts +129 -0
  251. package/extensions/lib/tool-call-corruption.ts +134 -0
  252. package/extensions/lib/tool-path.ts +66 -0
  253. package/extensions/lib/tool-variants.ts +69 -0
  254. package/extensions/lib/tui-render.ts +255 -27
  255. package/extensions/lib/turn-budget-layout.ts +159 -0
  256. package/extensions/lib/update-check.mjs +15 -2
  257. package/extensions/lib/usage-bus.ts +1 -1
  258. package/extensions/lib/user-denial.ts +38 -0
  259. package/extensions/lib/workspace-channel.ts +16 -0
  260. package/extensions/lib/worktree-channel.ts +31 -3
  261. package/extensions/lib/worktree-isolation.ts +40 -10
  262. package/extensions/lsp/client.ts +8 -9
  263. package/extensions/lsp/index.ts +76 -12
  264. package/extensions/lsp/keep-alive.ts +1 -1
  265. package/extensions/lsp/trust.ts +119 -0
  266. package/extensions/lsp/watcher.ts +1 -1
  267. package/extensions/mcp/announce.ts +90 -43
  268. package/extensions/mcp/client.ts +161 -48
  269. package/extensions/mcp/config.ts +132 -8
  270. package/extensions/mcp/discovery.ts +19 -0
  271. package/extensions/mcp/index.ts +220 -121
  272. package/extensions/mcp/oauth/browser.ts +38 -17
  273. package/extensions/mcp/oauth/flow.ts +25 -17
  274. package/extensions/mcp/oauth/provider.ts +14 -8
  275. package/extensions/mcp/oauth/store.ts +34 -16
  276. package/extensions/mcp/panel/keys.ts +12 -11
  277. package/extensions/mcp/panel/render.ts +9 -6
  278. package/extensions/mcp/schema.ts +104 -5
  279. package/extensions/mcp/trust.ts +164 -73
  280. package/extensions/memory/entries.ts +23 -14
  281. package/extensions/memory/index.ts +134 -28
  282. package/extensions/memory/open-external.ts +33 -18
  283. package/extensions/memory/panel.ts +135 -26
  284. package/extensions/model-default/index.ts +86 -19
  285. package/extensions/newer-model/index.ts +31 -0
  286. package/extensions/notebook/description.ts +18 -0
  287. package/extensions/notebook/index.ts +6 -5
  288. package/extensions/notebook/notebook.ts +1 -1
  289. package/extensions/permissions/index.ts +1426 -398
  290. package/extensions/permissions/matcher.ts +425 -74
  291. package/extensions/permissions/modes.ts +11 -4
  292. package/extensions/permissions/panel/host.ts +130 -0
  293. package/extensions/permissions/panel/keys.ts +48 -0
  294. package/extensions/permissions/panel/render.ts +450 -0
  295. package/extensions/permissions/panel/state.ts +550 -0
  296. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  297. package/extensions/permissions/powershell-paths.ts +197 -0
  298. package/extensions/permissions/powershell-rules.ts +207 -280
  299. package/extensions/permissions/powershell-tree.ts +557 -0
  300. package/extensions/permissions/project-trust.ts +27 -11
  301. package/extensions/permissions/protected-paths.ts +14 -2
  302. package/extensions/permissions/session-grant.ts +6 -0
  303. package/extensions/permissions/settings.ts +166 -31
  304. package/extensions/permissions/subagent-gate.ts +25 -0
  305. package/extensions/permissions/workspace.ts +78 -0
  306. package/extensions/plan-mode/index.ts +276 -92
  307. package/extensions/plan-mode/reminder.ts +89 -21
  308. package/extensions/plan-mode/run-state.ts +26 -0
  309. package/extensions/plan-mode/texts.ts +177 -0
  310. package/extensions/plan-mode/viewer.ts +92 -27
  311. package/extensions/plugins/counts.ts +3 -3
  312. package/extensions/plugins/index.ts +64 -46
  313. package/extensions/plugins/install/install.ts +16 -0
  314. package/extensions/plugins/marketplace/sync.ts +9 -3
  315. package/extensions/plugins/marketplace/types.ts +3 -1
  316. package/extensions/plugins/panel/keys.ts +21 -24
  317. package/extensions/plugins/panel/render.ts +12 -6
  318. package/extensions/plugins/shell-expand.ts +107 -0
  319. package/extensions/plugins/template.ts +32 -15
  320. package/extensions/powershell/child.ts +7 -0
  321. package/extensions/powershell/description.ts +38 -6
  322. package/extensions/powershell/guards.ts +1 -1
  323. package/extensions/powershell/index.ts +14 -7
  324. package/extensions/recap/index.ts +73 -71
  325. package/extensions/recap/prompt.ts +2 -2
  326. package/extensions/recap/scheduler.ts +3 -5
  327. package/extensions/search-tools/grep-merge.ts +39 -0
  328. package/extensions/search-tools/index.ts +0 -0
  329. package/extensions/send-now/chord.ts +62 -0
  330. package/extensions/send-now/index.ts +133 -0
  331. package/extensions/session-title/index.ts +3 -0
  332. package/extensions/session-title/title.ts +4 -1
  333. package/extensions/skill/announce.ts +30 -0
  334. package/extensions/skill/code-review.ts +97 -0
  335. package/extensions/skill/index.ts +469 -132
  336. package/extensions/skill/invoke.ts +118 -75
  337. package/extensions/skill/listing.ts +133 -9
  338. package/extensions/skill/panel/keys.ts +15 -16
  339. package/extensions/skill/panel/render.ts +5 -2
  340. package/extensions/skill/panel/state.ts +12 -1
  341. package/extensions/subagents/agent-args.ts +39 -0
  342. package/extensions/subagents/agent-description.ts +132 -0
  343. package/extensions/subagents/agents.ts +146 -31
  344. package/extensions/subagents/default-model.ts +2 -3
  345. package/extensions/subagents/delegation-steer.ts +4 -4
  346. package/extensions/subagents/fork-cache.ts +82 -0
  347. package/extensions/subagents/guide-agent.ts +189 -0
  348. package/extensions/subagents/hand-back-review.ts +3 -0
  349. package/extensions/subagents/index.ts +1594 -852
  350. package/extensions/subagents/live-runs.ts +26 -6
  351. package/extensions/subagents/model-select.ts +38 -25
  352. package/extensions/subagents/outcome.ts +5 -2
  353. package/extensions/subagents/panel-keys.ts +73 -31
  354. package/extensions/subagents/panel-render.ts +120 -96
  355. package/extensions/subagents/panel-widget.ts +60 -89
  356. package/extensions/subagents/parent-mode.ts +50 -0
  357. package/extensions/subagents/pending-claim.ts +18 -1
  358. package/extensions/subagents/runner.ts +135 -80
  359. package/extensions/subagents/runs.ts +44 -5
  360. package/extensions/subagents/send-to-main-tool.ts +1 -1
  361. package/extensions/subagents/session-turns.ts +32 -8
  362. package/extensions/subagents/shell-panel.ts +36 -178
  363. package/extensions/subagents/tasks-dialog.ts +170 -0
  364. package/extensions/subagents/viewport-mirror.ts +95 -0
  365. package/extensions/subagents/worktree.ts +57 -13
  366. package/extensions/system-prompt/environment.ts +19 -19
  367. package/extensions/system-prompt/idle-turn.ts +65 -0
  368. package/extensions/system-prompt/index.ts +165 -47
  369. package/extensions/system-prompt/template.ts +34 -51
  370. package/extensions/system-prompt/tiers/common.ts +23 -64
  371. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  372. package/extensions/system-prompt/tiers/low.ts +67 -45
  373. package/extensions/system-prompt/tiers/mid.ts +96 -38
  374. package/extensions/system-reminder/index.ts +204 -70
  375. package/extensions/tasks/descriptions.ts +190 -0
  376. package/extensions/tasks/index.ts +120 -41
  377. package/extensions/tasks/store.ts +83 -16
  378. package/extensions/tool-call-corruption/child.ts +7 -0
  379. package/extensions/tool-call-corruption/index.ts +237 -0
  380. package/extensions/tool-search/index.ts +366 -144
  381. package/extensions/tool-style/index.ts +68 -10
  382. package/extensions/tool-style/write-description.ts +40 -0
  383. package/extensions/tool-style/write-render.ts +237 -0
  384. package/extensions/turn-duration/index.ts +7 -3
  385. package/extensions/turn-duration/line.ts +47 -4
  386. package/extensions/web/backends.ts +6 -9
  387. package/extensions/web/description.ts +50 -0
  388. package/extensions/web/index.ts +118 -53
  389. package/extensions/web/native-call.ts +15 -0
  390. package/extensions/web/native-results.ts +41 -0
  391. package/extensions/web-fetch/description.ts +42 -0
  392. package/extensions/web-fetch/extract.ts +53 -6
  393. package/extensions/web-fetch/index.ts +125 -76
  394. package/extensions/web-fetch/page-content.ts +19 -0
  395. package/extensions/web-fetch/response.ts +49 -0
  396. package/extensions/web-fetch/summarize.ts +9 -10
  397. package/extensions/workflow/agent-session.ts +108 -18
  398. package/extensions/workflow/args.ts +24 -0
  399. package/extensions/workflow/description.ts +67 -0
  400. package/extensions/workflow/globals.ts +17 -3
  401. package/extensions/workflow/index.ts +157 -162
  402. package/extensions/workflow/records.ts +1 -0
  403. package/extensions/workflow/run-manager.ts +33 -6
  404. package/extensions/workflow/saved-workflows.ts +3 -2
  405. package/extensions/workflow/types.ts +22 -1
  406. package/extensions/workflow/viewer.ts +54 -24
  407. package/extensions/workflow/widget.ts +20 -0
  408. package/extensions/worktree/descriptions.ts +91 -0
  409. package/extensions/worktree/guards.ts +316 -54
  410. package/extensions/worktree/index.ts +162 -44
  411. package/extensions/worktree/powershell-guards.ts +167 -0
  412. package/extensions/worktree/rewrite.ts +14 -16
  413. package/package.json +21 -7
  414. package/skills/artifact-design/SKILL.md +85 -0
  415. package/skills/code-review/SKILL.md +20 -20
  416. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  417. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  418. package/skills/code-review/cells/high-8-inline.md +122 -0
  419. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  420. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  421. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  422. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  423. package/skills/code-review/cells/minimal-prompt.md +32 -0
  424. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  425. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  426. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  427. package/skills/loop/SKILL.md +11 -0
  428. package/skills/security-review/SKILL.md +2 -5
  429. package/skills/simplify/SKILL.md +7 -5
  430. package/skills/workflow-authoring/SKILL.md +143 -0
  431. package/themes/onecode-light.json +1 -1
  432. package/themes/onecode.json +1 -1
  433. package/types/app.d.ts +58 -0
  434. package/types/community-packages.d.ts +26 -1
  435. package/extensions/auto-mode/recoverability.ts +0 -229
  436. package/extensions/lib/capability-index.ts +0 -466
  437. package/extensions/lib/model-facts.generated.json +0 -2675
  438. package/extensions/lib/model-facts.ts +0 -162
  439. package/extensions/tool-search/announce.ts +0 -42
@@ -12,27 +12,28 @@
12
12
  * case-insensitive. Aliases PowerShell 7 dropped because they collide with
13
13
  * native executables (`sort`, `sc`, `curl`, `wget`) are deliberately absent,
14
14
  * as in CC: mapping `sort` to `Sort-Object` would judge the wrong program.
15
- * - **Statement split.** A command line is split on `|`, `;`, `&&`, `||` and
16
- * unquoted newlines, outside `'…'`, `"…"` and `@'…'@` / `@"…"@` here-strings,
17
- * with backtick escapes honoured. For an allow rule every part must match
18
- * (CC: "every subcommand must match"); a deny/ask rule fires on any part.
19
- * Unbalanced quoting is a parse failure → no allow, the user is asked.
20
- * - **No AST.** Claude Code parses the command; v1 here is textual and
21
- * conservative in the only direction that matters: a wildcard or prefix
22
- * allow never covers a line whose meaning is not on its face — `$(…)`
23
- * subexpressions, backtick escapes, the `&`/`.` call operators,
24
- * `Invoke-Expression`, `-EncodedCommand`, script blocks.
15
+ * - **Statement split.** A command line is split on `|`, `;`, `&&`, `||`, a
16
+ * single (background) `&`, and unquoted line breaks (`\n`, or `\r` on its
17
+ * own), outside `'…'`, `"…"` and `@'…'@` / `@"…"@` here-strings (the
18
+ * typographic quotes included, as pwsh reads them) and comments, with
19
+ * backtick escapes honoured. For an allow rule every part must match (CC:
20
+ * "every subcommand must match"); a deny/ask rule fires on any part, and on
21
+ * a rough split as well. Unbalanced quoting is a parse failure → no allow,
22
+ * the user is asked.
23
+ * - **Textual rule matching.** Rule matching stays on this split, which
24
+ * matches more forms than the tree, never fewer; it is conservative in the
25
+ * only direction that matters: a wildcard or prefix allow never covers a
26
+ * line whose meaning is not on its face — `$(…)` subexpressions, backtick
27
+ * escapes, the `&`/`.` call operators, `Invoke-Expression`,
28
+ * `-EncodedCommand`, script blocks.
25
29
  *
26
- * `powershellReadOnly` ports the read-only allowlist Claude Code uses to skip
27
- * approval — the cmdlet sets from the 2.1.276 binary — and adds path
28
- * containment by shape: a read of an absolute, `~`, drive-lettered, UNC or
29
- * `..` path is not auto-cleared (UNC paths always ask in CC). Anything the
30
- * check cannot vouch for is simply "not read-only", which costs a classifier
31
- * call or a prompt, never a bypass — docs/decisions/windows.md.
30
+ * `powershellReadOnly`, the read-only verdict the gate skips approval on, is
31
+ * judged from PowerShell's own parse instead (`powershell-tree.ts`,
32
+ * decisions/windows.md "The PowerShell pre-gate reads PowerShell's own parse").
32
33
  */
33
34
 
34
- import { isAbsolute } from "node:path";
35
- import { isWithin, resolveForContainment, toAbsolute } from "../auto-mode/paths.ts";
35
+ import type { PowerShellParse } from "../lib/powershell-parser.ts";
36
+ import { type PowerShellTreeOptions, powershellTreeReadOnly } from "./powershell-tree.ts";
36
37
 
37
38
  /** Claude Code's `COMMON_ALIASES` (utils/powershell/parser.ts), alias → canonical cmdlet. */
38
39
  export const POWERSHELL_ALIASES: Readonly<Record<string, string>> = Object.freeze({
@@ -160,11 +161,12 @@ export function canonicalizeStatement(statement: string): string {
160
161
 
161
162
  /**
162
163
  * The separately executing statements of a PowerShell line — what `|`, `;`,
163
- * `&&`, `||` and unquoted newlines separate — or undefined when quoting is
164
- * unbalanced. Single quotes, double quotes, here-strings (`@'…'@`, `@"…"@`,
165
- * whose closer must start a line) and backtick escapes are honoured; a `|`
166
- * inside `Where-Object { $_ -match 'a|b' }` still splits, conservatively, so
167
- * an allow rule over a script block has to cover the pieces.
164
+ * `&&`, `||`, a single `&` and unquoted line breaks separate — or undefined
165
+ * when quoting is unbalanced. Single quotes, double quotes, here-strings
166
+ * (`@'…'@`, `@"…"@`, whose closer must start a line), comments and backtick
167
+ * escapes are honoured (`lexPowerShell`); a `|` inside `Where-Object { $_
168
+ * -match 'a|b' }` still splits, conservatively, so an allow rule over a
169
+ * script block has to cover the pieces.
168
170
  */
169
171
  export function powershellStatements(command: string): string[] | undefined {
170
172
  // One decision parses the same line from several places (read-only check,
@@ -177,103 +179,183 @@ export function powershellStatements(command: string): string[] | undefined {
177
179
  }
178
180
  let lastSplit: { command: string; statements: string[] | undefined } | undefined;
179
181
 
180
- function splitStatements(command: string): string[] | undefined {
181
- const parts: string[] = [];
182
- let current = "";
182
+ /**
183
+ * PowerShell's quote characters. The tokenizer takes the typographic quotes as
184
+ * quotes too, interchangeably with the ASCII ones: `'x’ ; Remove-Item y ; ‘z'`
185
+ * is three statements, not one string. Checked against pwsh 7.6's parser.
186
+ */
187
+ const SINGLE_QUOTES = new Set(["'", "‘", "’", "‚", "‛"]);
188
+ const DOUBLE_QUOTES = new Set(['"', "“", "”", "„"]);
189
+
190
+ /**
191
+ * How the lexer sees one character of a line: unquoted code, where separators
192
+ * and operators mean what they say; a literal (inside a string or
193
+ * here-string, or escaped by a backtick); or a comment, which never runs.
194
+ */
195
+ const CODE = 0;
196
+ const LITERAL = 1;
197
+ const COMMENT = 2;
198
+ type Lex = typeof CODE | typeof LITERAL | typeof COMMENT;
199
+
200
+ /** A line break as PowerShell reads one: `\n`, and a carriage return on its own too. */
201
+ function isLineBreak(ch: string | undefined): boolean {
202
+ return ch === "\n" || ch === "\r";
203
+ }
204
+
205
+ /**
206
+ * Whether position `i` starts a token, where `#` and `<#` open a comment. In
207
+ * `x#(Set-Content f y)` the `#` is part of the word, and the parenthesis after
208
+ * it still runs.
209
+ */
210
+ function atTokenStart(text: string, i: number): boolean {
211
+ return i === 0 || /[\s;|&(){}]/.test(text[i - 1]);
212
+ }
213
+
214
+ /**
215
+ * Classify every character of a PowerShell line, or undefined when a
216
+ * string, here-string or block comment never closes. The one lexer the
217
+ * statement split and the grouping check share, so they cannot disagree about
218
+ * what is quoted. It follows pwsh's tokenizer on the points that decide where
219
+ * a statement ends: both quote families, doubled quotes, backtick escapes
220
+ * (outside quotes and inside double quotes), here-strings whose closer starts
221
+ * a line (after `\n` or a lone `\r`), and line and block comments at a token
222
+ * start. It does not follow a `$(…)` inside a double-quoted string; every
223
+ * consumer refuses or rough-splits a line with `$(` anyway.
224
+ */
225
+ function lexPowerShell(text: string): Lex[] | undefined {
226
+ const kinds: Lex[] = new Array(text.length);
183
227
  let i = 0;
184
- const text = command;
185
- type Quote = "'" | '"' | "@'" | '@"' | undefined;
186
- let quote: Quote;
187
- const push = () => {
188
- const trimmed = current.trim();
189
- if (trimmed) parts.push(trimmed);
190
- current = "";
228
+ const mark = (from: number, to: number, kind: Lex) => {
229
+ for (let k = from; k < to; k++) kinds[k] = kind;
191
230
  };
192
231
  while (i < text.length) {
193
232
  const ch = text[i];
194
233
  const next = text[i + 1];
195
- if (quote === "@'" || quote === '@"') {
196
- // A here-string closes only with its terminator at the start of a line.
197
- const closer = quote === "@'" ? "'@" : '"@';
198
- if ((i === 0 || text[i - 1] === "\n") && text.startsWith(closer, i)) {
199
- current += closer;
200
- i += 2;
201
- quote = undefined;
202
- continue;
203
- }
204
- current += ch;
205
- i++;
234
+ if (ch === "`" && i + 1 < text.length) {
235
+ kinds[i] = CODE;
236
+ kinds[i + 1] = LITERAL;
237
+ i += 2;
206
238
  continue;
207
239
  }
208
- if (quote === "'") {
209
- current += ch;
210
- i++;
211
- if (ch === "'") {
212
- if (next === "'") {
213
- current += next;
214
- i++; // doubled quote is a literal quote
215
- } else quote = undefined;
216
- }
240
+ if (ch === "#" && atTokenStart(text, i)) {
241
+ let end = i;
242
+ while (end < text.length && !isLineBreak(text[end])) end++;
243
+ mark(i, end, COMMENT);
244
+ i = end;
217
245
  continue;
218
246
  }
219
- if (quote === '"') {
220
- current += ch;
221
- i++;
222
- if (ch === "`" && i < text.length) {
223
- current += text[i];
224
- i++;
225
- continue;
226
- }
227
- if (ch === '"') {
228
- if (next === '"') {
229
- current += next;
230
- i++;
231
- } else quote = undefined;
232
- }
247
+ if (ch === "<" && next === "#" && atTokenStart(text, i)) {
248
+ const close = text.indexOf("#>", i + 2);
249
+ if (close === -1) return undefined;
250
+ mark(i, close + 2, COMMENT);
251
+ i = close + 2;
233
252
  continue;
234
253
  }
235
- // Unquoted.
236
- if (ch === "`" && i + 1 < text.length) {
237
- current += ch + text[i + 1];
238
- i += 2;
254
+ if (ch === "@" && next !== undefined && (SINGLE_QUOTES.has(next) || DOUBLE_QUOTES.has(next))) {
255
+ // A here-string closes only with a quote of its family and `@` at the start of a line.
256
+ const family = SINGLE_QUOTES.has(next) ? SINGLE_QUOTES : DOUBLE_QUOTES;
257
+ let end = i + 2;
258
+ while (end < text.length && !(isLineBreak(text[end - 1]) && family.has(text[end]) && text[end + 1] === "@")) end++;
259
+ if (end >= text.length) return undefined;
260
+ mark(i, end + 2, LITERAL);
261
+ i = end + 2;
239
262
  continue;
240
263
  }
241
- if (ch === "#") {
242
- // Line comment runs to end of line.
243
- const eol = text.indexOf("\n", i);
244
- i = eol === -1 ? text.length : eol;
264
+ if (SINGLE_QUOTES.has(ch)) {
265
+ let end = i + 1;
266
+ for (;;) {
267
+ if (end >= text.length) return undefined;
268
+ if (SINGLE_QUOTES.has(text[end])) {
269
+ if (SINGLE_QUOTES.has(text[end + 1] ?? "")) end += 2; // a doubled quote is a literal quote
270
+ else break;
271
+ } else end++;
272
+ }
273
+ mark(i, end + 1, LITERAL);
274
+ i = end + 1;
245
275
  continue;
246
276
  }
247
- if (ch === "@" && (next === "'" || next === '"')) {
248
- quote = next === "'" ? "@'" : '@"';
249
- current += ch + next;
250
- i += 2;
277
+ if (DOUBLE_QUOTES.has(ch)) {
278
+ let end = i + 1;
279
+ for (;;) {
280
+ if (end >= text.length) return undefined;
281
+ if (text[end] === "`") end += 2;
282
+ else if (DOUBLE_QUOTES.has(text[end])) {
283
+ if (DOUBLE_QUOTES.has(text[end + 1] ?? "")) end += 2;
284
+ else break;
285
+ } else end++;
286
+ }
287
+ mark(i, end + 1, LITERAL);
288
+ i = end + 1;
251
289
  continue;
252
290
  }
253
- if (ch === "'" || ch === '"') {
254
- quote = ch;
291
+ kinds[i] = CODE;
292
+ i++;
293
+ }
294
+ return kinds;
295
+ }
296
+
297
+ /**
298
+ * Split on the statement separators PowerShell 7 runs: `&&`, `||`, `|`, `;`,
299
+ * a line break (`\n`, or `\r` on its own), and a single `&`. A single `&`
300
+ * that starts a statement is the call operator and stays in it; after `>` it
301
+ * is part of a redirection (`2>&1`); anywhere else it is the background
302
+ * operator, and the next statement runs at once (`Get-ChildItem & Remove-Item
303
+ * x` runs both). Comments are dropped.
304
+ */
305
+ function splitStatements(command: string): string[] | undefined {
306
+ const kinds = lexPowerShell(command);
307
+ if (!kinds) return undefined;
308
+ const parts: string[] = [];
309
+ let current = "";
310
+ const push = () => {
311
+ const trimmed = current.trim();
312
+ if (trimmed) parts.push(trimmed);
313
+ current = "";
314
+ };
315
+ for (let i = 0; i < command.length; i++) {
316
+ const ch = command[i];
317
+ const kind = kinds[i];
318
+ if (kind === COMMENT) continue;
319
+ if (kind === LITERAL) {
255
320
  current += ch;
256
- i++;
257
321
  continue;
258
322
  }
323
+ const next = kinds[i + 1] === CODE ? command[i + 1] : undefined;
259
324
  if ((ch === "&" && next === "&") || (ch === "|" && next === "|")) {
260
325
  push();
261
- i += 2;
326
+ i++;
262
327
  continue;
263
328
  }
264
- if (ch === "|" || ch === ";" || ch === "\n") {
329
+ if (ch === "|" || ch === ";" || isLineBreak(ch)) {
330
+ push();
331
+ continue;
332
+ }
333
+ if (ch === "&" && current.trim() !== "" && command[i - 1] !== ">") {
265
334
  push();
266
- i++;
267
335
  continue;
268
336
  }
269
337
  current += ch;
270
- i++;
271
338
  }
272
- if (quote !== undefined) return undefined;
273
339
  push();
274
340
  return parts;
275
341
  }
276
342
 
343
+ /**
344
+ * Whether the line has a `(` outside quotes and comments. PowerShell
345
+ * evaluates a grouping expression in argument position before calling the
346
+ * command, so `Write-Output (Set-Content f x)` writes `f` behind a read-only
347
+ * cmdlet (AUTO-MODE-SECURITY-REVIEW-2026-09-24 H2); Claude Code's parser
348
+ * marks the same node a subexpression. A quoted `(` is a literal (`"Program
349
+ * Files (x86)"`), and a backtick-escaped one is too. An unbalanced line
350
+ * counts as having one: nothing can vouch for it.
351
+ */
352
+ function hasGroupingExpression(command: string): boolean {
353
+ const kinds = lexPowerShell(command);
354
+ if (!kinds) return true;
355
+ for (let i = 0; i < command.length; i++) if (kinds[i] === CODE && command[i] === "(") return true;
356
+ return false;
357
+ }
358
+
277
359
  /** `& cmd` (call operator) or `. script.ps1` (dot-sourcing) at the head of a statement. */
278
360
  function startsWithCallOperator(statement: string): boolean {
279
361
  const trimmed = statement.trim();
@@ -286,15 +368,26 @@ export function statementCommand(statement: string): string {
286
368
  return canonicalCommandName(word).toLowerCase();
287
369
  }
288
370
 
371
+ /**
372
+ * A control character the lexer does not model: C0 other than tab, line feed
373
+ * and carriage return, DEL, C1 (NEL included), and the Unicode line and
374
+ * paragraph separators. pwsh reads some as whitespace and some as part of a
375
+ * word; the split here honours neither, so a line with one is never covered
376
+ * by a wildcard or prefix allow rule.
377
+ */
378
+ const UNRECOGNISED_CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u2028\u2029]/;
379
+
289
380
  /**
290
381
  * Why a wildcard or prefix allow rule must not cover this line: constructs
291
382
  * whose effect is not what the words say. Exact rules (the literal string the
292
383
  * user approved) are unaffected. Returns undefined when none is present.
293
384
  */
294
385
  export function powershellInjectionSyntax(command: string): string | undefined {
386
+ if (UNRECOGNISED_CONTROL.test(command)) return "a control character";
295
387
  if (/\$\(/.test(command)) return "a $(…) subexpression";
296
388
  if (/`/.test(command)) return "a backtick escape";
297
389
  if (/\{/.test(command)) return "a script block";
390
+ if (hasGroupingExpression(command)) return "a (…) grouping expression";
298
391
  if (/-e(nc|ncoded|ncodedcommand)?\b/i.test(command) && /\b(pwsh|powershell)(\.exe)?\b/i.test(command)) return "an encoded command";
299
392
  const statements = powershellStatements(command);
300
393
  for (const statement of statements ?? []) {
@@ -338,148 +431,28 @@ export function powershellMatchForms(command: string, depth = 0): string[] {
338
431
  const nested = nestedShellScript(statement);
339
432
  if (nested) for (const form of powershellMatchForms(nested, depth + 1)) forms.add(form);
340
433
  }
341
- return [...forms];
342
- }
343
-
344
- // ---------------------------------------------------------------------------
345
- // Read-only allowlist
346
-
347
- /**
348
- * The read-only cmdlet sets in the 2.1.276 binary (findings §22), plus the
349
- * neutral output pair, plus — One Code's addition (2026-09-19, user decision,
350
- * docs/decisions/windows.md) — the pure in-process pipeline cmdlets a
351
- * read-only line is piped through: they shape objects already in memory and
352
- * touch neither disk nor network. `Where-Object`/`ForEach-Object` are NOT
353
- * here: they take script blocks, which the check refuses anyway. Claude Code's
354
- * captured list has no pipeline cmdlets at all, so `Select-String … |
355
- * Measure-Object` (the /doctor transcript scan) always went to the classifier.
356
- */
357
- export const READ_ONLY_POWERSHELL_COMMANDS = new Set<string>([
358
- // search
359
- "select-string",
360
- "get-childitem",
361
- "findstr",
362
- "where.exe",
363
- // read
364
- "get-content",
365
- "get-item",
366
- "test-path",
367
- "resolve-path",
368
- "get-process",
369
- "get-service",
370
- "get-location",
371
- "get-filehash",
372
- "get-acl",
373
- "format-hex",
374
- // neutral output
375
- "write-output",
376
- "write-host",
377
- // pure pipeline transforms (One Code's addition)
378
- "select-object",
379
- "sort-object",
380
- "measure-object",
381
- "group-object",
382
- "convertfrom-json",
383
- "out-string",
384
- "format-table",
385
- "format-list",
386
- "format-wide",
387
- "format-custom",
388
- ]);
389
-
390
- /** Parameters that turn a read-only cmdlet into a write. */
391
- const WRITING_PARAMETERS = /^-(outfile|filepath|destination)\b/i;
392
-
393
- /**
394
- * Whether a token names a path this check will not vouch for by shape: UNC
395
- * (`\\server`), home (`~`), a PSDrive outside the filesystem (`HKLM:`,
396
- * `env:`), or one that climbs (`..`). An absolute or drive-lettered path is
397
- * judged by `pathOutsideRoots` when roots are known, and refused otherwise.
398
- */
399
- function pathUnvouchable(value: string): boolean {
400
- if (!value) return false;
401
- if (value.startsWith("\\\\") || value.startsWith("//")) return true;
402
- if (/^[A-Za-z][A-Za-z0-9]+:/.test(value)) return true; // HKLM:, env:, cert: (a drive is one letter)
403
- // Drive-relative (`C:foo.txt`, no separator after the colon) means "foo.txt
404
- // relative to PowerShell's current directory ON C:", which need not be the
405
- // tool's cwd; `path.isAbsolute` does not call it absolute, so `toAbsolute`
406
- // would join it onto the cwd and vouch for the wrong file. Refuse by shape.
407
- if (/^[A-Za-z]:(?![\\/])/.test(value)) return true;
408
- if (value.startsWith("~")) return true;
409
- if (/(^|[\\/])\.\.([\\/]|$)/.test(value)) return true;
410
- return false;
411
- }
412
-
413
- /**
414
- * PowerShell's array syntax splits an argument on commas — outside quotes.
415
- * `"a,b.txt"` is one path with a comma in its name; `a,"b,c"` is two.
416
- */
417
- export function splitPowerShellList(token: string): string[] {
418
- const parts: string[] = [];
419
- let current = "";
420
- let quote: string | undefined;
421
- for (const ch of token) {
422
- if (quote) {
423
- if (ch === quote) quote = undefined;
424
- else current += ch;
425
- } else if (ch === '"' || ch === "'") {
426
- quote = ch;
427
- } else if (ch === ",") {
428
- parts.push(current);
429
- current = "";
430
- } else {
431
- current += ch;
432
- }
434
+ // A rough split on every separator and grouping character, quotes ignored:
435
+ // it catches a statement inside `$(…)`, `(…)` or `{…}`, and every
436
+ // statement of a line the lexer could not read (bash's counterpart adds
437
+ // the same rough split for an unparseable line).
438
+ for (const piece of trimmed.split(ROUGH_SEPARATORS)) {
439
+ const part = piece.trim();
440
+ if (!part) continue;
441
+ forms.add(part);
442
+ forms.add(canonicalizeStatement(part));
433
443
  }
434
- parts.push(current);
435
- return parts;
444
+ return [...forms];
436
445
  }
437
446
 
438
- /** An absolute path by Windows or POSIX spelling: `C:\x`, `C:/x`, `/x`, `\x`. */
439
- function isAbsoluteSpelling(value: string): boolean {
440
- return /^[A-Za-z]:/.test(value) || value.startsWith("/") || value.startsWith("\\");
441
- }
447
+ /** Everything that can end or open a statement, for the deny/ask rough split. */
448
+ const ROUGH_SEPARATORS = /[;|&\r\n(){}]+/;
442
449
 
443
- export interface PowerShellReadOnlyOptions {
444
- /** The working directory the command runs in. */
445
- cwd: string;
446
- /** The user's home, for `toAbsolute`. */
447
- home: string;
448
- /**
449
- * REALPATH-resolved directories an absolute path may point into besides the
450
- * working directory: the harness's own session dirs (memory, scratchpad,
451
- * persisted results, this project's transcripts — matcher.ts `DecideInput`),
452
- * resolved once per session by the caller.
453
- */
454
- readableRoots?: string[];
455
- }
450
+ // ---------------------------------------------------------------------------
451
+ // Read-only
456
452
 
457
- /**
458
- * Whether a path token lies outside every root. Each comma-separated part
459
- * (PowerShell's array syntax, `-Path a,b`; commas inside quotes are part of
460
- * the name) is judged on its own: refused by
461
- * shape when unvouchable; a relative part is inside by construction; an
462
- * absolute part is resolved through `resolveForContainment` — the nearest
463
- * existing ancestor's realpath, so a glob leaf (`C:\src\play\*.ts`) is
464
- * judged by its directory and a symlink planted inside the project that
465
- * points out of it is outside — and must lie inside a root. With no roots
466
- * (no options) an absolute part is refused, the pre-2026-09-19 behaviour.
467
- */
468
- function pathOutsideRoots(value: string, opts: PowerShellReadOnlyOptions | undefined, roots: string[]): boolean {
469
- for (const part of splitPowerShellList(value)) {
470
- const trimmed = part.trim();
471
- if (!trimmed) continue;
472
- if (pathUnvouchable(trimmed)) return true;
473
- if (!isAbsoluteSpelling(trimmed)) continue;
474
- if (!opts) return true;
475
- // Resolve only what THIS platform's path module calls absolute: on a POSIX
476
- // host `C:\x` is not, and `toAbsolute` would join it onto the cwd and
477
- // vouch for a file the shell would never touch. Refuse it by shape there.
478
- if (!isAbsolute(trimmed)) return true;
479
- const resolved = resolveForContainment(toAbsolute(opts.cwd, trimmed, opts.home));
480
- if (resolved === undefined || !roots.some((root) => isWithin(root, resolved))) return true;
481
- }
482
- return false;
453
+ export interface PowerShellReadOnlyOptions extends PowerShellTreeOptions {
454
+ /** PowerShell's own parse of the line (lib/powershell-parser.ts); without one nothing is read-only. */
455
+ parse?: PowerShellParse;
483
456
  }
484
457
 
485
458
  export interface PowerShellReadOnlyVerdict {
@@ -489,61 +462,15 @@ export interface PowerShellReadOnlyVerdict {
489
462
  }
490
463
 
491
464
  /**
492
- * Whether every statement of the line is a read-only cmdlet (or `where.exe`)
493
- * with arguments the check can see through: no redirection, no variables or
494
- * subexpressions, no script blocks, no writing parameters, no path outside
495
- * the working directory by shape. Positive proof only; anything else is not
496
- * read-only and takes the ordinary route (classifier in auto mode, a prompt
497
- * elsewhere).
465
+ * Whether every statement of the line provably only reads inside the working
466
+ * directory and the readable roots, judged from PowerShell's own parse
467
+ * (`powershell-tree.ts`). Positive proof only; without a parse, or with
468
+ * anything the tree check does not model, the line is not read-only and takes
469
+ * the ordinary route (the classifier in auto mode, a prompt elsewhere) —
470
+ * decisions/windows.md "The PowerShell pre-gate reads PowerShell's own parse".
498
471
  */
499
- export function powershellReadOnly(command: string, opts?: PowerShellReadOnlyOptions): PowerShellReadOnlyVerdict {
500
- const statements = powershellStatements(command);
501
- if (statements === undefined) return { readOnly: false, reason: "unbalanced quoting" };
502
- if (statements.length === 0) return { readOnly: false, reason: "empty command" };
503
- if (/[<>]/.test(command)) return { readOnly: false, reason: "redirection" };
504
- if (/\$/.test(command)) return { readOnly: false, reason: "a variable or subexpression" };
505
- if (/[`{}]/.test(command)) return { readOnly: false, reason: "an escape or script block" };
506
- // The cwd's realpath once per command (the roots arrive resolved), not once per token.
507
- const roots = opts ? [resolveForContainment(opts.cwd) ?? opts.cwd, ...(opts.readableRoots ?? [])] : [];
508
- for (const statement of statements) {
509
- if (startsWithCallOperator(statement)) return { readOnly: false, reason: "the call operator" };
510
- const cmd = statementCommand(statement);
511
- if (!READ_ONLY_POWERSHELL_COMMANDS.has(cmd)) return { readOnly: false, reason: `\`${cmd}\` is not a read-only cmdlet` };
512
- const tokens = statement.trim().split(/\s+/).slice(1);
513
- for (const token of tokens) {
514
- if (WRITING_PARAMETERS.test(token)) return { readOnly: false, reason: `${token.split(":")[0]} writes or forwards` };
515
- if (token.startsWith("-")) continue;
516
- // By shape (UNC, ~, PSDrive, ..) a token is never vouched for. An absolute
517
- // path is read-only only when it resolves inside the working directory or
518
- // a harness session dir (2026-09-19: /doctor's transcript scan on Windows
519
- // spelled `<agentDir>\sessions\…` and was classified —
520
- // docs/decisions/auto-mode.md); a comma list is judged part by part.
521
- if (pathOutsideRoots(token, opts, roots)) return { readOnly: false, reason: "a path outside the working directory" };
522
- }
523
- }
524
- return { readOnly: true };
525
- }
526
-
527
- /** git global flags that take a value and can precede the subcommand (`git -C x status`). */
528
- const GIT_GLOBAL_VALUE_FLAGS = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env"]);
529
-
530
- /**
531
- * Whether the statement is a bare `git status` (global flags before it and
532
- * plain flags after it allowed, no other statements) — the call after which
533
- * Claude Code's classifier transcript carries a
534
- * `{"meta":{"gitStatus":{"clean":…}}}` ground-truth line (findings §22).
535
- * Works for bash and PowerShell spellings alike. Global value-taking flags
536
- * (`-C <dir>`, `-c <key=val>`, …) are skipped so `git -C <dir> status` is
537
- * still recognised instead of mistaking the flag's value for the subcommand.
538
- */
539
- export function isGitStatusCommand(command: string): boolean {
540
- const statements = powershellStatements(command);
541
- if (!statements || statements.length !== 1) return false;
542
- if (statementCommand(statements[0]) !== "git") return false;
543
- const words = statements[0].trim().split(/\s+/).slice(1);
544
- let i = 0;
545
- while (i < words.length && words[i].startsWith("-")) {
546
- i += GIT_GLOBAL_VALUE_FLAGS.has(words[i]) ? 2 : 1;
547
- }
548
- return words[i] === "status" && words.slice(i + 1).every((w) => w.startsWith("-"));
472
+ export function powershellReadOnly(opts: PowerShellReadOnlyOptions): PowerShellReadOnlyVerdict {
473
+ if (!opts.parse) return { readOnly: false, reason: "PowerShell's own parse of the line is not available" };
474
+ const verdict = powershellTreeReadOnly(opts.parse, opts);
475
+ return verdict.ok ? { readOnly: true } : { readOnly: false, reason: verdict.reason };
549
476
  }