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
@@ -11,24 +11,27 @@
11
11
  * whose repository is decided at runtime (`xargs`/`parallel`/`find -exec`
12
12
  * feeding it arguments, a `-C "$DIR"` computed from a variable, a `cd`
13
13
  * whose destination cannot be resolved), or that points into the shared
14
- * checkout or a sibling worktree (`git -C`, `--git-dir`, `--work-tree`, a
15
- * preceding `cd`), is refused with the fix named. Git against an unrelated
14
+ * checkout or a sibling worktree (`git -C`, `--git-dir`, `--work-tree`,
15
+ * `-c core.worktree=`, a `GIT_DIR`/`GIT_WORK_TREE`/`GIT_COMMON_DIR`
16
+ * assignment before git or exported earlier in the line, a preceding
17
+ * `cd`), is refused with the fix named. Git against an unrelated
16
18
  * repository elsewhere on disk is left alone — the permission gate and
17
19
  * auto-mode still apply to it.
18
20
  * 2. The stash stack is shared by the main checkout and every worktree of the
19
21
  * repository, and parallel sessions can interleave on it. Stash forms that
20
22
  * collide with a concurrent session (untagged push, `pop`, `clear`,
21
- * ref-less `drop`) are refused with the tag + apply-by-SHA recipe.
23
+ * ref-less `drop` and `branch`) are refused with the tag + apply-by-SHA recipe.
22
24
  *
23
- * Steering, not security: only bash is inspected here, and an unparseable
24
- * command is refused only when it visibly involves git (fail closed on the
25
- * invariant, fail open on everything else).
25
+ * Steering, not security: bash (and `monitor`, which runs bash) is inspected
26
+ * here, PowerShell in powershell-guards.ts, and an unparseable command is
27
+ * refused only when it visibly involves git (fail closed on the invariant,
28
+ * fail open on everything else).
26
29
  */
27
30
 
28
31
  import { homedir } from "node:os";
29
32
  import { isAbsolute } from "node:path";
30
- import { isWithin, toAbsoluteBash } from "../auto-mode/paths.ts";
31
- import { gitSubcommand, leadTokens, parseCommand, resolvePayload, type Token } from "../auto-mode/shell-analysis.ts";
33
+ import { isWithin, resolveForContainment, toAbsoluteBash } from "../auto-mode/paths.ts";
34
+ import { gitSubcommand, INLINE_SCRIPT_SHELLS, isUnknownTilde, leadTokens, LOOPS, movesDirectory, parseCommand, resolvePayload, scopedTracker, type Token } from "../auto-mode/shell-analysis.ts";
32
35
 
33
36
  export interface WorktreeGuardContext {
34
37
  command: string;
@@ -36,15 +39,59 @@ export interface WorktreeGuardContext {
36
39
  worktreePath: string;
37
40
  /** Root of the shared checkout the worktree belongs to. */
38
41
  sharedRoot: string;
42
+ /**
43
+ * Every worktree of the repository (`git worktree list`). A linked worktree
44
+ * can live outside the shared root, and git aimed at it is refused too.
45
+ */
46
+ otherWorktrees?: readonly string[];
39
47
  }
40
48
 
49
+ /**
50
+ * Whether a git target lies in a root, judged where both resolve: git lists
51
+ * worktrees by their real paths (`/private/var/…` on macOS) while the session
52
+ * and the model may spell a directory through a link (`/var/…`), and a link
53
+ * inside one worktree may lead anywhere. Lexical only when resolution fails.
54
+ */
55
+ export const containsPath = (root: string, target: string): boolean => {
56
+ const resolvedRoot = resolveForContainment(root);
57
+ const resolvedTarget = resolveForContainment(target);
58
+ return resolvedRoot !== undefined && resolvedTarget !== undefined ? isWithin(resolvedRoot, resolvedTarget) : isWithin(root, target);
59
+ };
60
+
61
+ /** The roots a git command must not target: the shared checkout and every other worktree. */
62
+ export const sharedRepositoryRoots = ({ sharedRoot, otherWorktrees }: Pick<WorktreeGuardContext, "sharedRoot" | "otherWorktrees">): string[] => [sharedRoot, ...(otherWorktrees ?? [])];
63
+
41
64
  /** Expansion syntax the guard cannot resolve statically. */
42
65
  const hasExpansion = (value: string) => /[$`]/.test(value);
43
66
 
44
- const isolated = (worktreePath: string, problem: string, fix: string): string =>
67
+ export const isolated = (worktreePath: string, problem: string, fix: string): string =>
45
68
  `This session is isolated in the worktree ${worktreePath}, but ${problem}. ` +
46
69
  `Refusing to run it — a worktree-isolated session's git operations must target its own worktree. ${fix}`;
47
70
 
71
+ /**
72
+ * Whether a shell command may run git, so its targets need the repository's
73
+ * worktree list: any spelling the parse resolves to git (`g"it"`, `\\git`,
74
+ * `$'\\x67it'`, `env git`), git passed to a runner, git in a script a shell
75
+ * runs, or a line that does not parse. Over-matching costs one `git worktree list`.
76
+ */
77
+ export function namesGit(command: string): boolean {
78
+ if (mentionsGit(command)) return true;
79
+ const { segments, parseFailed } = parseCommand(command);
80
+ if (parseFailed) return true;
81
+ return segments.some((seg) => {
82
+ const { command: cmd, args } = resolvePayload(leadTokens(seg));
83
+ return cmd === "git" || args.some((arg) => arg.value === "git" || mentionsGit(arg.value));
84
+ });
85
+ }
86
+
87
+ /** The worktree list could not be read, so a git command's target cannot be checked: refuse it. */
88
+ export const unlistedWorktreesReason = (worktreePath: string): string =>
89
+ isolated(
90
+ worktreePath,
91
+ "the repository's worktrees could not be listed, so the repository this git command targets cannot be verified",
92
+ `Retry; if it keeps failing, run git from ${worktreePath} without -C, --git-dir or --work-tree.`,
93
+ );
94
+
48
95
  function stashMessage(form: string, hazard: string): string {
49
96
  return (
50
97
  `Blocked: \`${form}\` — the git stash stack is shared between the main checkout and every worktree of this repository, and parallel sessions may push or pop entries concurrently; ${hazard}. ` +
@@ -54,7 +101,7 @@ function stashMessage(form: string, hazard: string): string {
54
101
  );
55
102
  }
56
103
 
57
- function stashReason(rest: Token[]): string | undefined {
104
+ export function stashReason(rest: Pick<Token, "value">[]): string | undefined {
58
105
  const words = rest.map((t) => t.value);
59
106
  const sub = words.find((w) => !w.startsWith("-"));
60
107
  // `-m` may ride in a short-flag bundle (`git stash push -um wip`).
@@ -80,19 +127,98 @@ function stashReason(rest: Token[]): string | undefined {
80
127
  return stashMessage("git stash drop", "a ref-less `drop` deletes whatever is currently stash@{0}, which may be another session's entry");
81
128
  }
82
129
  }
130
+ if (sub === "branch") {
131
+ // `git stash branch <name> [<stash>]`: without the stash ref it pops stash@{0}.
132
+ const operands = words.slice(words.indexOf("branch") + 1).filter((w) => !w.startsWith("-"));
133
+ if (operands.length < 2) {
134
+ return stashMessage("git stash branch", "without a stash ref, `branch` pops whatever is currently stash@{0}, which may be another session's entry");
135
+ }
136
+ }
83
137
  return undefined;
84
138
  }
85
139
 
86
- export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: WorktreeGuardContext): string | undefined {
87
- const { segments, parseFailed } = parseCommand(command);
88
- if (parseFailed || segments.length === 0) {
89
- if (!/\bgit\b/.test(command)) return undefined;
90
- return isolated(
140
+ /** Environment variables that point git at a repository, like `--git-dir` and `--work-tree`. */
141
+ const GIT_REPOSITORY_ENV = /^(GIT_DIR|GIT_WORK_TREE|GIT_COMMON_DIR)=/;
142
+
143
+ /** `declare -x` / `typeset -gx`: the builtins that export like `export`. */
144
+ const exportsLikeExport = (cmd: string, args: Token[]) =>
145
+ cmd === "export" || ((cmd === "declare" || cmd === "typeset") && args.some((arg) => /^-[a-zA-Z]*x/.test(arg.value)));
146
+
147
+ export function worktreeBashGuardReason(context: WorktreeGuardContext): string | undefined {
148
+ return guardScript(context.command, context.worktreePath, sharedRepositoryRoots(context), context.worktreePath, 0).reason;
149
+ }
150
+
151
+ /**
152
+ * Judge one script that starts in `startDir` (undefined: not statically
153
+ * known). Returns the refusal, if any, and whether the script can change the
154
+ * directory of the shell it runs in (what an `eval` of it would do).
155
+ */
156
+ /** Builtins that run a script in this shell. */
157
+ const SCRIPT_RUNNERS: ReadonlySet<string> = new Set(["eval", "source", "."]);
158
+
159
+ /** Shell options that take the next word as their value. */
160
+ const SHELL_VALUE_OPTIONS = new Set(["-o", "+o", "-O", "+O", "--rcfile", "--init-file"]);
161
+
162
+ /**
163
+ * The command lines a shell invocation runs: with `-c` (alone or in a cluster
164
+ * such as `-lc`), the first operand; the words after it are `$0`, `$1`, …
165
+ * (`bash -c 'echo ok' 'git status'` runs no git), unless the script expands
166
+ * them (`bash -c '"$@"' _ git status`), when they are a command line too.
167
+ * Without `-c`, every argument, since which one is a script file is not known.
168
+ */
169
+ function shellScripts(args: Token[]): string[] {
170
+ let command = false;
171
+ let i = 0;
172
+ for (; i < args.length; i++) {
173
+ const value = args[i].value;
174
+ if (value === "--" || value === "-") {
175
+ i++;
176
+ break;
177
+ }
178
+ if (!/^[-+]/.test(value)) break;
179
+ if (SHELL_VALUE_OPTIONS.has(value)) i++;
180
+ else if (/^-[A-Za-z]+$/.test(value) && value.includes("c")) command = true;
181
+ }
182
+ const values = args.map((arg) => arg.value);
183
+ if (!command) return values;
184
+ const [script, ...positional] = values.slice(i);
185
+ if (script === undefined) return [];
186
+ // `$0`…`$9`, `$@`, `$*`, `${1}`, `${@}`: read both from `$0` and from `$1`.
187
+ if (!/\$(?:[0-9@*]|\{[0-9@*])/.test(script) || positional.length === 0) return [script];
188
+ return [script, positional.join(" "), positional.slice(1).join(" ")];
189
+ }
190
+
191
+ /** Whether git appears as a word in `text`, read with quotes and backslashes removed (`gi\t`, `g"it"`). */
192
+ const mentionsGit = (text: string): boolean => /\bgit\b/.test(text.replace(/[\\'"]/g, ""));
193
+
194
+ /**
195
+ * A script the guard cannot follow (it failed to parse, or is nested too deep):
196
+ * refused if git appears anywhere in it, and a `cd` in it leaves the directory
197
+ * unknown.
198
+ */
199
+ function unverifiedScript(command: string, worktreePath: string, mayMove: boolean): { reason?: string; moves?: boolean } {
200
+ if (!mentionsGit(command)) return { moves: mayMove && /\b(?:cd|pushd|popd)\b/.test(command) };
201
+ return {
202
+ reason: isolated(
91
203
  worktreePath,
92
204
  "this command is too complex to verify that its git operations stay inside the worktree",
93
205
  `Break it into plain, separate git commands with literal paths and run them from ${worktreePath}.`,
94
- );
95
- }
206
+ ),
207
+ };
208
+ }
209
+
210
+ /** How many directories a segment may run in before the guard treats its directory as unknown. */
211
+ const MAX_POSSIBLE_DIRS = 8;
212
+
213
+ function guardScript(
214
+ command: string,
215
+ worktreePath: string,
216
+ sharedRoots: readonly string[],
217
+ startDir: string | undefined,
218
+ depth: number,
219
+ ): { reason?: string; moves?: boolean } {
220
+ const { segments, parseFailed } = parseCommand(command);
221
+ if (parseFailed || segments.length === 0) return unverifiedScript(command, worktreePath, parseFailed);
96
222
 
97
223
  // Claude Code's exact refusal for stdin-fed git (one static string for
98
224
  // both mechanisms) — keep it byte-identical to the capture.
@@ -103,38 +229,141 @@ export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: W
103
229
  `Run the equivalent from ${worktreePath} without the redirect.`,
104
230
  );
105
231
 
106
- /** Directory later segments run in; undefined = not statically known. */
107
- let dir: string | undefined = worktreePath;
108
- /** Subshell nesting: a `cd` inside `(...)` must not leak past the `)`. */
109
- let depth = 0;
110
- let dirBeforeSubshell: { dir: string | undefined } | undefined;
232
+ /** Directory the current segment runs in; undefined = not statically known. */
233
+ let dir: string | undefined = startDir;
234
+ /**
235
+ * Repository variables an earlier `export` in this line set, by name:
236
+ * the directory they name, or undefined when it is not statically known.
237
+ * Later git in the line runs with them. Subshell scopes are ignored, which
238
+ * can only refuse more.
239
+ */
240
+ const exportedGitEnv = new Map<string, string | undefined>();
241
+ const envTarget = (value: string, token: Token | undefined): string | undefined =>
242
+ !value || hasExpansion(value) || token?.dynamic || token?.glob || dir === undefined ? undefined : toAbsoluteBash(dir, value, homedir());
243
+ /**
244
+ * The directories a segment may run in, per subshell scope
245
+ * (`Segment.scopes`): a `cd` inside `( … )`, a substitution or a pipeline
246
+ * member does not leak out. A `cd` that may not run (`false && cd x`, an
247
+ * `if` branch) leaves both the old and the new directory possible, and every
248
+ * git command must be safe in each.
249
+ */
250
+ const scopeDirs = scopedTracker<(string | undefined)[]>([startDir]);
251
+
252
+ // A git command as the parse sees it, spelled any way (`gi\t`, `env git`,
253
+ // `xargs git`, `parallel git`), or anywhere in the script a shell or `eval`
254
+ // runs (`bash -c '…'`), read as text with quotes and backslashes removed so
255
+ // a script too complex to parse still counts.
256
+ const runsGit = (seg: (typeof segments)[number]) => {
257
+ const { command: cmd, args } = resolvePayload(leadTokens(seg));
258
+ if (cmd === "git" || (cmd === "parallel" && args.some((arg) => arg.value === "git"))) return true;
259
+ if (!INLINE_SCRIPT_SHELLS.has(cmd) && !SCRIPT_RUNNERS.has(cmd)) return false;
260
+ return args.some((arg) => mentionsGit(arg.value));
261
+ };
262
+ // A loop body runs more than once, so a `cd` in it moves every git command
263
+ // in the loop after the first pass; the segments show one pass only.
264
+ // An `eval` there may `cd` too.
265
+ const movesOrEvals = (seg: (typeof segments)[number]) => movesDirectory(seg) || resolvePayload(leadTokens(seg)).command === "eval";
266
+ const movesInLoop = segments.some((seg) => seg.enclosing.some((construct) => LOOPS.has(construct)) && movesOrEvals(seg));
267
+ if (movesInLoop && segments.some(runsGit)) {
268
+ return {
269
+ reason: isolated(
270
+ worktreePath,
271
+ "this command changes directory inside a loop, so the repository its git commands target cannot be verified",
272
+ `Break it into plain, separate git commands with literal paths and run them from ${worktreePath}.`,
273
+ ),
274
+ };
275
+ }
276
+ // The last member of a pipeline runs in the current shell under `shopt -s
277
+ // lastpipe`, so its `cd` may move every later command.
278
+ // Only a git command after such a `cd` can be moved by it.
279
+ // Inside a substitution too (`$(echo | cd /repo; git status)`), but only for
280
+ // git later in the shell that tail runs in.
281
+ const inShell = (seg: (typeof segments)[number], shell: number[]) => shell.every((scope, depth) => seg.scopes[depth] === scope);
282
+ const pipeMoved = segments.some(
283
+ (cd, index) => cd.pipelineShell && movesDirectory(cd) && segments.slice(index + 1).some((seg) => inShell(seg, cd.pipelineShell!) && runsGit(seg)),
284
+ );
285
+ if (pipeMoved) {
286
+ return {
287
+ reason: isolated(
288
+ worktreePath,
289
+ "this command changes directory in the last command of a pipeline, which can run in the current shell, so the repository its git commands target cannot be verified",
290
+ `Break it into plain, separate git commands with literal paths and run them from ${worktreePath}.`,
291
+ ),
292
+ };
293
+ }
111
294
 
112
295
  const checkSegment = (seg: (typeof segments)[number]): string | undefined => {
113
296
  const tokens = leadTokens(seg);
114
297
  const rawLead = tokens[0]?.value;
115
298
  if (!rawLead) return undefined;
299
+ // The command a wrapper runs (`builtin cd`, `command cd`), not the first word.
300
+ const { command: lead, args: leadArgs } = resolvePayload(tokens);
116
301
 
117
- if (rawLead === "cd") {
302
+ if (lead === "cd") {
118
303
  // Skip cd's own options (-P/-L/-e/-@); `--` ends them; a lone `-`
119
304
  // means the previous directory, which is not statically known.
120
- let j = 1;
121
- while (j < tokens.length && tokens[j].value.startsWith("-") && !["-", "--"].includes(tokens[j].value)) j++;
122
- if (tokens[j]?.value === "--") j++;
123
- const target = tokens[j]?.value;
305
+ let j = 0;
306
+ while (j < leadArgs.length && leadArgs[j].value.startsWith("-") && !["-", "--"].includes(leadArgs[j].value)) j++;
307
+ if (leadArgs[j]?.value === "--") j++;
308
+ const targetToken = leadArgs[j];
309
+ const target = targetToken?.value;
124
310
  if (!target) dir = homedir();
125
- else if (hasExpansion(target) || target === "-") dir = undefined;
311
+ // A glob (`cd /r*po`), an expansion or `~user` lands wherever bash expands it.
312
+ else if (hasExpansion(target) || target === "-" || targetToken.glob || targetToken.dynamic || isUnknownTilde(target)) dir = undefined;
126
313
  // An absolute (or ~) destination re-anchors the tracked directory
127
314
  // even when it was unknown — later git commands become checkable again.
128
315
  else if (isAbsolute(target) || target === "~" || target.startsWith("~/")) dir = toAbsoluteBash("/", target, homedir());
129
316
  else if (dir !== undefined) dir = toAbsoluteBash(dir, target, homedir());
130
317
  return undefined;
131
318
  }
132
- if (rawLead === "pushd" || rawLead === "popd") {
319
+ if (lead === "pushd" || lead === "popd") {
133
320
  dir = undefined;
134
321
  return undefined;
135
322
  }
136
323
 
137
324
  const { command: cmd, args, peeled } = resolvePayload(tokens);
325
+ // Git behind a wrapper the strict reading does not peel (`sudo`, `exec`,
326
+ // `env -C /repo`): the wrapper may have moved it, so its target cannot be
327
+ // read off the command.
328
+ // A shell, `eval` or `source` in command position is judged below instead.
329
+ if (cmd !== "git" && !SCRIPT_RUNNERS.has(cmd) && !INLINE_SCRIPT_SHELLS.has(cmd)) {
330
+ const wide = resolvePayload(tokens, "wide");
331
+ const runsScript = INLINE_SCRIPT_SHELLS.has(wide.command) || SCRIPT_RUNNERS.has(wide.command);
332
+ const shellScript = runsScript && wide.args.some((arg) => mentionsGit(arg.value));
333
+ if (wide.command === "git" || shellScript || wide.scripts.some(mentionsGit)) {
334
+ return isolated(
335
+ worktreePath,
336
+ `this command runs git through ${rawLead}, which can change the repository it targets`,
337
+ `Run git directly, with literal paths inside ${worktreePath}.`,
338
+ );
339
+ }
340
+ }
341
+
342
+ // A script a shell or `eval` runs is judged as commands of its own,
343
+ // starting where this command runs: `bash -c 'cd /repo && git status'`
344
+ // runs git in /repo. A shell's script runs in a child, but `eval` runs
345
+ // in this shell, so a `cd` in it moves the commands after it.
346
+ // Past three levels a script is judged as one the guard cannot follow.
347
+ if (INLINE_SCRIPT_SHELLS.has(cmd) || cmd === "eval") {
348
+ const scripts = cmd === "eval" ? [args.map((arg) => arg.value).join(" ")] : shellScripts(args);
349
+ for (const script of scripts) {
350
+ const nested = depth < 3 ? guardScript(script, worktreePath, sharedRoots, dir, depth + 1) : unverifiedScript(script, worktreePath, true);
351
+ if (nested.reason) return nested.reason;
352
+ if (cmd === "eval" && nested.moves) dir = undefined;
353
+ }
354
+ return undefined;
355
+ }
356
+
357
+ // `source <(…)`, `. "$f"`: the script is only known at runtime, so git in
358
+ // its source text cannot be checked.
359
+ // It runs in this shell, so a `cd` in it would move later git: the
360
+ // directory is unknown after it. A literal file (`source .venv/bin/activate`)
361
+ // keeps it: its text is not the guard's to read.
362
+ if ((cmd === "source" || cmd === ".") && args.some((arg) => arg.dynamic)) {
363
+ const unverified = unverifiedScript(seg.raw, worktreePath, false);
364
+ if (unverified.reason) return unverified.reason;
365
+ dir = undefined;
366
+ }
138
367
 
139
368
  // Git whose arguments are assembled at runtime — the repository it will
140
369
  // target cannot be read off the command.
@@ -152,6 +381,16 @@ export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: W
152
381
  );
153
382
  }
154
383
 
384
+ if (exportsLikeExport(cmd, args)) {
385
+ for (const arg of args) {
386
+ const name = /^(GIT_DIR|GIT_WORK_TREE|GIT_COMMON_DIR)(=|$)/.exec(arg.value);
387
+ if (!name) continue;
388
+ // `export GIT_DIR` (no value) exports whatever it was set to earlier.
389
+ exportedGitEnv.set(name[1], name[2] ? envTarget(arg.value.slice(name[0].length), arg) : undefined);
390
+ }
391
+ return undefined;
392
+ }
393
+
155
394
  if (cmd !== "git") return undefined;
156
395
 
157
396
  if (dir === undefined) {
@@ -165,15 +404,47 @@ export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: W
165
404
  // Effective repository target: the tracked cwd, adjusted by global flags.
166
405
  let effective = dir;
167
406
  const extraTargets: string[] = [];
407
+ // `GIT_DIR=… git`, `env GIT_WORK_TREE=… git`, and an earlier `export`:
408
+ // the environment spelling of `--git-dir` / `--work-tree`.
409
+ const envRefusal = (name: string) =>
410
+ isolated(
411
+ worktreePath,
412
+ `\`${name}\` points git at a repository that is decided at runtime, so it cannot be verified`,
413
+ `Drop the ${name} setting and run git from ${worktreePath}.`,
414
+ );
415
+ const prefix = tokens.slice(0, tokens.length - args.length - 1);
416
+ const assigned = new Map(exportedGitEnv);
417
+ for (const token of prefix) {
418
+ if (!GIT_REPOSITORY_ENV.test(token.value)) continue;
419
+ const eq = token.value.indexOf("=");
420
+ assigned.set(token.value.slice(0, eq), envTarget(token.value.slice(eq + 1), token));
421
+ }
422
+ for (const [name, target] of assigned) {
423
+ if (target === undefined) return envRefusal(name);
424
+ extraTargets.push(target);
425
+ }
168
426
  let i = 0;
169
427
  while (i < args.length) {
170
428
  const value = args[i].value;
171
429
  if (!value.startsWith("-")) break;
430
+ // `-c core.worktree=<dir>` is `--work-tree` spelled as config;
431
+ // `--config-env` takes the value from a variable at runtime.
432
+ if (value === "-c" && /^core\.worktree=/i.test(args[i + 1]?.value ?? "")) {
433
+ const raw = args[i + 1].value.slice(args[i + 1].value.indexOf("=") + 1);
434
+ if (!raw || hasExpansion(raw)) return envRefusal("core.worktree");
435
+ extraTargets.push(toAbsoluteBash(effective, raw, homedir()));
436
+ i += 2;
437
+ continue;
438
+ }
439
+ if ((value === "--config-env" && /^core\.worktree=/i.test(args[i + 1]?.value ?? "")) || /^--config-env=core\.worktree=/i.test(value)) {
440
+ return envRefusal("core.worktree");
441
+ }
172
442
  const pathFlag = value === "-C" || value === "--git-dir" || value === "--work-tree";
173
443
  const inlined = value.startsWith("--git-dir=") || value.startsWith("--work-tree=");
174
444
  if (pathFlag || inlined) {
175
- const raw = inlined ? value.slice(value.indexOf("=") + 1) : args[i + 1]?.value;
176
- if (!raw || hasExpansion(raw)) {
445
+ const pathToken = inlined ? args[i] : args[i + 1];
446
+ const raw = inlined ? value.slice(value.indexOf("=") + 1) : pathToken?.value;
447
+ if (!raw || hasExpansion(raw) || pathToken?.dynamic || pathToken?.glob || isUnknownTilde(raw)) {
177
448
  return isolated(
178
449
  worktreePath,
179
450
  `\`git ${value}\` computes its repository target at runtime, so it cannot be verified`,
@@ -191,8 +462,8 @@ export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: W
191
462
 
192
463
  const targets = [effective, ...extraTargets];
193
464
  for (const target of targets) {
194
- if (isWithin(worktreePath, target)) continue;
195
- if (isWithin(sharedRoot, target)) {
465
+ if (containsPath(worktreePath, target)) continue;
466
+ if (sharedRoots.some((root) => containsPath(root, target))) {
196
467
  return isolated(
197
468
  worktreePath,
198
469
  `this git command targets ${target}, which is the shared checkout or another worktree of the same repository`,
@@ -205,7 +476,7 @@ export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: W
205
476
  // The stash stack is per-repository and shared across its worktrees. Any
206
477
  // shared-root target that is NOT inside the worktree already returned
207
478
  // above, so only worktree-contained targets can reach this check.
208
- if (targets.some((t) => isWithin(worktreePath, t))) {
479
+ if (targets.some((t) => containsPath(worktreePath, t))) {
209
480
  const { sub, rest } = gitSubcommand(args);
210
481
  if (sub === "stash") {
211
482
  const reason = stashReason(rest);
@@ -216,26 +487,17 @@ export function worktreeBashGuardReason({ command, worktreePath, sharedRoot }: W
216
487
  };
217
488
 
218
489
  for (const seg of segments) {
219
- // Subshell bookkeeping reads the RAW tokens — leadTokens strips the very
220
- // parens being counted. A `cd` between `(` and `)` is scoped: the tracked
221
- // directory is restored when the subshell closes.
222
- const first = seg.tokens[0]?.value ?? "";
223
- const opens = (/^\(+/.exec(first)?.[0] ?? "").length;
224
- if (opens > 0 && depth === 0) dirBeforeSubshell = { dir };
225
- depth += opens;
226
-
227
- const reason = checkSegment(seg);
228
- if (reason) return reason;
229
-
230
- const last = seg.tokens[seg.tokens.length - 1]?.value ?? "";
231
- const closes = (/\)+$/.exec(last)?.[0] ?? "").length;
232
- if (closes > 0 && depth > 0) {
233
- depth = Math.max(0, depth - closes);
234
- if (depth === 0 && dirBeforeSubshell) {
235
- dir = dirBeforeSubshell.dir;
236
- dirBeforeSubshell = undefined;
237
- }
490
+ const before = scopeDirs.get(seg);
491
+ const after: (string | undefined)[] = [];
492
+ for (const start of before) {
493
+ dir = start;
494
+ const reason = checkSegment(seg);
495
+ if (reason) return { reason };
496
+ after.push(dir);
238
497
  }
498
+ const possible = [...new Set(seg.conditional ? [...before, ...after] : after)];
499
+ // Past a handful of possibilities the directory is as good as unknown.
500
+ scopeDirs.set(seg, possible.length > MAX_POSSIBLE_DIRS ? [undefined] : possible);
239
501
  }
240
- return undefined;
502
+ return { moves: segments.some(movesDirectory) || segments.some((seg) => resolvePayload(leadTokens(seg)).command === "eval") };
241
503
  }