@kr78/pi-coding-agent 17.2.10

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 (1593) hide show
  1. package/CHANGELOG.md +14861 -0
  2. package/README.md +35 -0
  3. package/dist/CHANGELOG-chtdxh1z.md +14861 -0
  4. package/dist/cli.js +20483 -0
  5. package/dist/template-c2hyaytt.js +1653 -0
  6. package/dist/template-f8wx9vfn.css +1355 -0
  7. package/dist/template-qat058wr.html +55 -0
  8. package/dist/tool-views.generated-jdfmzwmn.js +35 -0
  9. package/examples/README.md +21 -0
  10. package/examples/custom-tools/README.md +104 -0
  11. package/examples/custom-tools/hello/index.ts +20 -0
  12. package/examples/extensions/README.md +142 -0
  13. package/examples/extensions/api-demo.ts +78 -0
  14. package/examples/extensions/chalk-logger.ts +25 -0
  15. package/examples/extensions/hello.ts +31 -0
  16. package/examples/extensions/pirate.ts +43 -0
  17. package/examples/extensions/plan-mode.ts +549 -0
  18. package/examples/extensions/reload-runtime.ts +38 -0
  19. package/examples/extensions/thinking-note.ts +13 -0
  20. package/examples/extensions/tools.ts +145 -0
  21. package/examples/extensions/with-deps/index.ts +36 -0
  22. package/examples/extensions/with-deps/package-lock.json +31 -0
  23. package/examples/extensions/with-deps/package.json +17 -0
  24. package/examples/hooks/README.md +56 -0
  25. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  26. package/examples/hooks/confirm-destructive.ts +58 -0
  27. package/examples/hooks/custom-compaction.ts +115 -0
  28. package/examples/hooks/dirty-repo-guard.ts +51 -0
  29. package/examples/hooks/file-trigger.ts +40 -0
  30. package/examples/hooks/git-checkpoint.ts +52 -0
  31. package/examples/hooks/handoff.ts +149 -0
  32. package/examples/hooks/permission-gate.ts +33 -0
  33. package/examples/hooks/protected-paths.ts +29 -0
  34. package/examples/hooks/qna.ts +118 -0
  35. package/examples/hooks/status-line.ts +39 -0
  36. package/examples/sdk/01-minimal.ts +21 -0
  37. package/examples/sdk/02-custom-model.ts +49 -0
  38. package/examples/sdk/03-custom-prompt.ts +46 -0
  39. package/examples/sdk/04-skills.ts +43 -0
  40. package/examples/sdk/06-extensions.ts +82 -0
  41. package/examples/sdk/06-hooks.ts +61 -0
  42. package/examples/sdk/07-context-files.ts +35 -0
  43. package/examples/sdk/08-prompt-templates.ts +41 -0
  44. package/examples/sdk/08-slash-commands.ts +46 -0
  45. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  46. package/examples/sdk/11-sessions.ts +47 -0
  47. package/examples/sdk/12-redis-sessions.ts +54 -0
  48. package/examples/sdk/13-sql-sessions.ts +61 -0
  49. package/examples/sdk/README.md +169 -0
  50. package/package.json +587 -0
  51. package/scripts/bench-guard.ts +71 -0
  52. package/scripts/bench-title-models.ts +332 -0
  53. package/scripts/build-binary.ts +117 -0
  54. package/scripts/bundle-dist.ts +123 -0
  55. package/scripts/compile-binary.ts +69 -0
  56. package/scripts/embed-mupdf-wasm.ts +67 -0
  57. package/scripts/format-prompts.ts +68 -0
  58. package/scripts/generate-aria-snapshot.ts +134 -0
  59. package/scripts/generate-docs-index.ts +58 -0
  60. package/scripts/generate-share-viewer.ts +34 -0
  61. package/scripts/legacy-pi-virtual-module.ts +205 -0
  62. package/scripts/measure-prompt-tokens.ts +63 -0
  63. package/scripts/omp +51 -0
  64. package/scripts/omp.ts +19 -0
  65. package/scripts/security-compare.ts +40 -0
  66. package/src/advisor/advise-tool.ts +234 -0
  67. package/src/advisor/config.ts +341 -0
  68. package/src/advisor/emission-guard.ts +172 -0
  69. package/src/advisor/index.ts +6 -0
  70. package/src/advisor/runtime.ts +1412 -0
  71. package/src/advisor/transcript-recorder.ts +215 -0
  72. package/src/advisor/watchdog.ts +135 -0
  73. package/src/async/index.ts +1 -0
  74. package/src/async/job-manager.ts +847 -0
  75. package/src/auto-thinking/classifier.ts +220 -0
  76. package/src/autolearn/controller.ts +152 -0
  77. package/src/autolearn/managed-skills.ts +255 -0
  78. package/src/autoresearch/command-resume.md +14 -0
  79. package/src/autoresearch/dashboard.ts +436 -0
  80. package/src/autoresearch/git.ts +331 -0
  81. package/src/autoresearch/helpers.ts +218 -0
  82. package/src/autoresearch/index.ts +541 -0
  83. package/src/autoresearch/prompt-setup.md +43 -0
  84. package/src/autoresearch/prompt.md +103 -0
  85. package/src/autoresearch/resume-message.md +10 -0
  86. package/src/autoresearch/state.ts +273 -0
  87. package/src/autoresearch/storage.ts +700 -0
  88. package/src/autoresearch/tools/init-experiment.ts +268 -0
  89. package/src/autoresearch/tools/log-experiment.ts +520 -0
  90. package/src/autoresearch/tools/run-experiment.ts +407 -0
  91. package/src/autoresearch/tools/update-notes.ts +109 -0
  92. package/src/autoresearch/types.ts +168 -0
  93. package/src/capability/context-file.ts +44 -0
  94. package/src/capability/extension-module.ts +34 -0
  95. package/src/capability/extension.ts +47 -0
  96. package/src/capability/fs.ts +117 -0
  97. package/src/capability/hook.ts +40 -0
  98. package/src/capability/index.ts +467 -0
  99. package/src/capability/instruction.ts +37 -0
  100. package/src/capability/mcp.ts +115 -0
  101. package/src/capability/prompt.ts +35 -0
  102. package/src/capability/rule-buckets.ts +66 -0
  103. package/src/capability/rule.ts +298 -0
  104. package/src/capability/settings.ts +34 -0
  105. package/src/capability/skill.ts +69 -0
  106. package/src/capability/slash-command.ts +40 -0
  107. package/src/capability/ssh.ts +41 -0
  108. package/src/capability/system-prompt.ts +34 -0
  109. package/src/capability/tool.ts +38 -0
  110. package/src/capability/types.ts +187 -0
  111. package/src/cleanse/agent.ts +226 -0
  112. package/src/cleanse/balance.ts +79 -0
  113. package/src/cleanse/checkers.ts +996 -0
  114. package/src/cleanse/index.ts +190 -0
  115. package/src/cleanse/loop.ts +51 -0
  116. package/src/cleanse/parsers.ts +726 -0
  117. package/src/cleanse/progress.ts +50 -0
  118. package/src/cleanse/prompts/assignment.md +47 -0
  119. package/src/cleanse/types.ts +72 -0
  120. package/src/cli/agents-cli.ts +138 -0
  121. package/src/cli/args.ts +368 -0
  122. package/src/cli/auth-broker-cli.ts +940 -0
  123. package/src/cli/auth-gateway-cli.ts +674 -0
  124. package/src/cli/bench-cli.ts +990 -0
  125. package/src/cli/browser-relay-cli.ts +119 -0
  126. package/src/cli/classify-install-target.ts +76 -0
  127. package/src/cli/claude-trace-cli.ts +795 -0
  128. package/src/cli/command-help.ts +107 -0
  129. package/src/cli/commands/init-xdg.ts +27 -0
  130. package/src/cli/completion-gen.ts +550 -0
  131. package/src/cli/config-cli.ts +459 -0
  132. package/src/cli/dry-balance-cli.ts +864 -0
  133. package/src/cli/extension-flags.ts +48 -0
  134. package/src/cli/file-processor.ts +132 -0
  135. package/src/cli/flag-tables.ts +367 -0
  136. package/src/cli/gallery-cli.ts +272 -0
  137. package/src/cli/gallery-fixtures/agentic.ts +420 -0
  138. package/src/cli/gallery-fixtures/codeintel.ts +187 -0
  139. package/src/cli/gallery-fixtures/edit.ts +254 -0
  140. package/src/cli/gallery-fixtures/fs.ts +245 -0
  141. package/src/cli/gallery-fixtures/index.ts +40 -0
  142. package/src/cli/gallery-fixtures/interaction.ts +46 -0
  143. package/src/cli/gallery-fixtures/memory.ts +81 -0
  144. package/src/cli/gallery-fixtures/misc.ts +177 -0
  145. package/src/cli/gallery-fixtures/search.ts +135 -0
  146. package/src/cli/gallery-fixtures/shell.ts +241 -0
  147. package/src/cli/gallery-fixtures/types.ts +57 -0
  148. package/src/cli/gallery-fixtures/web.ts +158 -0
  149. package/src/cli/gallery-screenshot.ts +279 -0
  150. package/src/cli/gc-cli.ts +1566 -0
  151. package/src/cli/grep-cli.ts +161 -0
  152. package/src/cli/grievances-cli.ts +256 -0
  153. package/src/cli/help-extra.ts +89 -0
  154. package/src/cli/initial-message.ts +58 -0
  155. package/src/cli/models-cli.ts +385 -0
  156. package/src/cli/plugin-cli.ts +996 -0
  157. package/src/cli/profile-alias.ts +369 -0
  158. package/src/cli/profile-bootstrap.ts +233 -0
  159. package/src/cli/read-cli.ts +99 -0
  160. package/src/cli/session-picker.ts +110 -0
  161. package/src/cli/setup-cli.ts +312 -0
  162. package/src/cli/setup-model-picker.ts +43 -0
  163. package/src/cli/shell-cli.ts +176 -0
  164. package/src/cli/ssh-cli.ts +179 -0
  165. package/src/cli/startup-cwd.ts +58 -0
  166. package/src/cli/stats-cli.ts +229 -0
  167. package/src/cli/thinking-levels.ts +7 -0
  168. package/src/cli/tiny-models-cli.ts +153 -0
  169. package/src/cli/ttsr-cli.ts +1013 -0
  170. package/src/cli/update-cli.ts +1184 -0
  171. package/src/cli/usage-cli.ts +1084 -0
  172. package/src/cli/usage-error.ts +7 -0
  173. package/src/cli/web-search-cli.ts +144 -0
  174. package/src/cli/worktree-cli.ts +311 -0
  175. package/src/cli-commands.ts +308 -0
  176. package/src/cli.ts +428 -0
  177. package/src/collab/crypto.ts +63 -0
  178. package/src/collab/display-name.ts +13 -0
  179. package/src/collab/guest.ts +764 -0
  180. package/src/collab/host.ts +692 -0
  181. package/src/collab/protocol.ts +296 -0
  182. package/src/collab/relay-client.ts +282 -0
  183. package/src/collab/replication-shrink.ts +111 -0
  184. package/src/commands/acp.ts +35 -0
  185. package/src/commands/agents.ts +58 -0
  186. package/src/commands/auth-broker.ts +100 -0
  187. package/src/commands/auth-gateway.ts +70 -0
  188. package/src/commands/bench.ts +64 -0
  189. package/src/commands/browser-relay.ts +53 -0
  190. package/src/commands/cleanse.ts +45 -0
  191. package/src/commands/commit.ts +47 -0
  192. package/src/commands/complete.ts +67 -0
  193. package/src/commands/completions.ts +61 -0
  194. package/src/commands/config.ts +52 -0
  195. package/src/commands/dry-balance.ts +43 -0
  196. package/src/commands/gallery.ts +61 -0
  197. package/src/commands/gc.ts +47 -0
  198. package/src/commands/grep.ts +49 -0
  199. package/src/commands/grievances.ts +52 -0
  200. package/src/commands/install.ts +107 -0
  201. package/src/commands/join.ts +40 -0
  202. package/src/commands/launch-help.ts +116 -0
  203. package/src/commands/launch.ts +34 -0
  204. package/src/commands/models.ts +61 -0
  205. package/src/commands/plugin.ts +79 -0
  206. package/src/commands/read.ts +39 -0
  207. package/src/commands/say.ts +146 -0
  208. package/src/commands/setup.ts +68 -0
  209. package/src/commands/share.ts +71 -0
  210. package/src/commands/shell.ts +30 -0
  211. package/src/commands/ssh.ts +61 -0
  212. package/src/commands/stats.ts +30 -0
  213. package/src/commands/tiny-models.ts +36 -0
  214. package/src/commands/token.ts +164 -0
  215. package/src/commands/ttsr.ts +125 -0
  216. package/src/commands/update.ts +34 -0
  217. package/src/commands/usage.ts +55 -0
  218. package/src/commands/web-search.ts +43 -0
  219. package/src/commands/worktree.ts +63 -0
  220. package/src/commit/agentic/agent.ts +322 -0
  221. package/src/commit/agentic/fallback.ts +96 -0
  222. package/src/commit/agentic/index.ts +383 -0
  223. package/src/commit/agentic/lock-files.ts +107 -0
  224. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  225. package/src/commit/agentic/prompts/session-user.md +25 -0
  226. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  227. package/src/commit/agentic/prompts/system.md +38 -0
  228. package/src/commit/agentic/state.ts +60 -0
  229. package/src/commit/agentic/tools/analyze-file.ts +148 -0
  230. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  231. package/src/commit/agentic/tools/git-hunk.ts +52 -0
  232. package/src/commit/agentic/tools/git-overview.ts +62 -0
  233. package/src/commit/agentic/tools/index.ts +54 -0
  234. package/src/commit/agentic/tools/propose-changelog.ts +147 -0
  235. package/src/commit/agentic/tools/propose-commit.ts +109 -0
  236. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  237. package/src/commit/agentic/tools/schemas.ts +11 -0
  238. package/src/commit/agentic/tools/split-commit.ts +241 -0
  239. package/src/commit/agentic/topo-sort.ts +44 -0
  240. package/src/commit/agentic/trivial.ts +51 -0
  241. package/src/commit/agentic/validation.ts +183 -0
  242. package/src/commit/analysis/conventional.ts +64 -0
  243. package/src/commit/analysis/index.ts +4 -0
  244. package/src/commit/analysis/scope.ts +242 -0
  245. package/src/commit/analysis/summary.ts +107 -0
  246. package/src/commit/analysis/validation.ts +66 -0
  247. package/src/commit/changelog/detect.ts +40 -0
  248. package/src/commit/changelog/generate.ts +101 -0
  249. package/src/commit/changelog/index.ts +234 -0
  250. package/src/commit/changelog/parse.ts +44 -0
  251. package/src/commit/cli.ts +85 -0
  252. package/src/commit/git/diff.ts +148 -0
  253. package/src/commit/index.ts +5 -0
  254. package/src/commit/map-reduce/index.ts +69 -0
  255. package/src/commit/map-reduce/map-phase.ts +193 -0
  256. package/src/commit/map-reduce/reduce-phase.ts +49 -0
  257. package/src/commit/map-reduce/utils.ts +9 -0
  258. package/src/commit/message.ts +11 -0
  259. package/src/commit/model-selection.ts +95 -0
  260. package/src/commit/pipeline.ts +244 -0
  261. package/src/commit/prompts/analysis-system.md +148 -0
  262. package/src/commit/prompts/analysis-user.md +38 -0
  263. package/src/commit/prompts/changelog-system.md +50 -0
  264. package/src/commit/prompts/changelog-user.md +18 -0
  265. package/src/commit/prompts/file-observer-system.md +24 -0
  266. package/src/commit/prompts/file-observer-user.md +8 -0
  267. package/src/commit/prompts/reduce-system.md +50 -0
  268. package/src/commit/prompts/reduce-user.md +17 -0
  269. package/src/commit/prompts/summary-retry.md +3 -0
  270. package/src/commit/prompts/summary-system.md +38 -0
  271. package/src/commit/prompts/summary-user.md +13 -0
  272. package/src/commit/prompts/types-description.md +2 -0
  273. package/src/commit/shared-llm.ts +70 -0
  274. package/src/commit/types.ts +118 -0
  275. package/src/commit/utils/exclusions.ts +42 -0
  276. package/src/commit/utils.ts +58 -0
  277. package/src/config/api-key-resolver.ts +81 -0
  278. package/src/config/append-only-context-mode.ts +76 -0
  279. package/src/config/config-file.ts +347 -0
  280. package/src/config/inline-tool-descriptors-mode.ts +26 -0
  281. package/src/config/keybindings.ts +703 -0
  282. package/src/config/mcp-schema.json +252 -0
  283. package/src/config/model-discovery.ts +1077 -0
  284. package/src/config/model-registry.ts +2822 -0
  285. package/src/config/model-resolver.ts +2115 -0
  286. package/src/config/model-roles.ts +113 -0
  287. package/src/config/models-config-schema-bundle.ts +315 -0
  288. package/src/config/models-config-schema.ts +14 -0
  289. package/src/config/models-config.ts +130 -0
  290. package/src/config/prompt-templates.ts +205 -0
  291. package/src/config/provider-globals.ts +25 -0
  292. package/src/config/resolve-config-value.ts +95 -0
  293. package/src/config/service-tier.ts +146 -0
  294. package/src/config/settings-schema.ts +5887 -0
  295. package/src/config/settings.ts +2394 -0
  296. package/src/config.ts +242 -0
  297. package/src/cursor-bridge-tools.ts +81 -0
  298. package/src/cursor.ts +960 -0
  299. package/src/dap/client.ts +1043 -0
  300. package/src/dap/config.ts +480 -0
  301. package/src/dap/defaults.json +212 -0
  302. package/src/dap/index.ts +4 -0
  303. package/src/dap/session.ts +1841 -0
  304. package/src/dap/types.ts +611 -0
  305. package/src/debug/index.ts +584 -0
  306. package/src/debug/log-formatting.ts +58 -0
  307. package/src/debug/log-viewer.ts +966 -0
  308. package/src/debug/profiler.ts +168 -0
  309. package/src/debug/protocol-probe.ts +267 -0
  310. package/src/debug/raw-sse-buffer.ts +421 -0
  311. package/src/debug/raw-sse.ts +312 -0
  312. package/src/debug/remote-debugger.ts +151 -0
  313. package/src/debug/report-bundle.ts +411 -0
  314. package/src/debug/system-info.ts +111 -0
  315. package/src/debug/terminal-info.ts +124 -0
  316. package/src/discovery/agent-plugin-format.ts +551 -0
  317. package/src/discovery/agent-plugins.ts +341 -0
  318. package/src/discovery/agents-md.ts +67 -0
  319. package/src/discovery/agents.ts +300 -0
  320. package/src/discovery/at-imports.ts +273 -0
  321. package/src/discovery/builtin-defaults.ts +39 -0
  322. package/src/discovery/builtin-rules/go-add-cleanup.md +33 -0
  323. package/src/discovery/builtin-rules/go-bench-loop.md +36 -0
  324. package/src/discovery/builtin-rules/go-exp-promoted.md +40 -0
  325. package/src/discovery/builtin-rules/go-ioutil.md +37 -0
  326. package/src/discovery/builtin-rules/go-join-hostport.md +30 -0
  327. package/src/discovery/builtin-rules/go-new-expr.md +44 -0
  328. package/src/discovery/builtin-rules/go-rand-v2.md +41 -0
  329. package/src/discovery/builtin-rules/go-range-int.md +45 -0
  330. package/src/discovery/builtin-rules/index.ts +74 -0
  331. package/src/discovery/builtin-rules/rs-box-leak.md +49 -0
  332. package/src/discovery/builtin-rules/rs-future-prelude.md +24 -0
  333. package/src/discovery/builtin-rules/rs-lazylock.md +52 -0
  334. package/src/discovery/builtin-rules/rs-match-ergonomics.md +68 -0
  335. package/src/discovery/builtin-rules/rs-parking-lot.md +45 -0
  336. package/src/discovery/builtin-rules/rs-result-type.md +20 -0
  337. package/src/discovery/builtin-rules/ts-bare-catch.md +39 -0
  338. package/src/discovery/builtin-rules/ts-import-type.md +43 -0
  339. package/src/discovery/builtin-rules/ts-no-any.md +66 -0
  340. package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +45 -0
  341. package/src/discovery/builtin-rules/ts-no-dynamic-import.md +40 -0
  342. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +56 -0
  343. package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
  344. package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
  345. package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
  346. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
  347. package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +66 -0
  348. package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
  349. package/src/discovery/builtin-rules/ts-set-map.md +28 -0
  350. package/src/discovery/builtin.ts +945 -0
  351. package/src/discovery/claude-plugins.ts +640 -0
  352. package/src/discovery/claude.ts +591 -0
  353. package/src/discovery/cline.ts +83 -0
  354. package/src/discovery/codex.ts +553 -0
  355. package/src/discovery/contained-path.ts +78 -0
  356. package/src/discovery/cursor.ts +223 -0
  357. package/src/discovery/gemini.ts +386 -0
  358. package/src/discovery/github.ts +337 -0
  359. package/src/discovery/helpers.ts +1183 -0
  360. package/src/discovery/index.ts +82 -0
  361. package/src/discovery/mcp-json.ts +182 -0
  362. package/src/discovery/omp-extension-roots.ts +272 -0
  363. package/src/discovery/omp-plugins.ts +409 -0
  364. package/src/discovery/opencode.ts +441 -0
  365. package/src/discovery/plugin-dir-roots.ts +28 -0
  366. package/src/discovery/ssh.ts +153 -0
  367. package/src/discovery/substitute-plugin-root.ts +77 -0
  368. package/src/discovery/vscode.ts +106 -0
  369. package/src/discovery/windsurf.ts +149 -0
  370. package/src/edit/apply-patch/index.ts +87 -0
  371. package/src/edit/apply-patch/parser.ts +174 -0
  372. package/src/edit/diff.ts +1008 -0
  373. package/src/edit/edit-clipboard.ts +23 -0
  374. package/src/edit/file-snapshot-store.ts +148 -0
  375. package/src/edit/hashline/block-resolver.ts +33 -0
  376. package/src/edit/hashline/diff.ts +402 -0
  377. package/src/edit/hashline/execute.ts +313 -0
  378. package/src/edit/hashline/filesystem.ts +247 -0
  379. package/src/edit/hashline/index.ts +5 -0
  380. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  381. package/src/edit/hashline/params.ts +12 -0
  382. package/src/edit/index.ts +699 -0
  383. package/src/edit/modes/apply-patch.lark +19 -0
  384. package/src/edit/modes/apply-patch.ts +53 -0
  385. package/src/edit/modes/patch.ts +1958 -0
  386. package/src/edit/modes/replace.ts +1191 -0
  387. package/src/edit/normalize.ts +345 -0
  388. package/src/edit/notebook.ts +242 -0
  389. package/src/edit/read-file.ts +25 -0
  390. package/src/edit/renderer.ts +998 -0
  391. package/src/edit/snapshot-details.ts +77 -0
  392. package/src/edit/streaming.ts +710 -0
  393. package/src/eval/agent-bridge.ts +224 -0
  394. package/src/eval/backend-helpers.ts +48 -0
  395. package/src/eval/backend.ts +71 -0
  396. package/src/eval/bridge-timeout.ts +65 -0
  397. package/src/eval/budget-bridge.ts +48 -0
  398. package/src/eval/completion-bridge.ts +212 -0
  399. package/src/eval/concurrency-bridge.ts +34 -0
  400. package/src/eval/executor-base.ts +562 -0
  401. package/src/eval/idle-timeout.ts +91 -0
  402. package/src/eval/index.ts +6 -0
  403. package/src/eval/jl/executor.ts +551 -0
  404. package/src/eval/jl/index.ts +54 -0
  405. package/src/eval/jl/kernel.ts +236 -0
  406. package/src/eval/jl/prelude.jl +736 -0
  407. package/src/eval/jl/prelude.ts +3 -0
  408. package/src/eval/jl/runner.jl +666 -0
  409. package/src/eval/jl/runtime.ts +118 -0
  410. package/src/eval/js/context-manager.ts +872 -0
  411. package/src/eval/js/executor.ts +178 -0
  412. package/src/eval/js/index.ts +41 -0
  413. package/src/eval/js/process-entry.ts +31 -0
  414. package/src/eval/js/shared/helpers.ts +170 -0
  415. package/src/eval/js/shared/indirect-eval.ts +30 -0
  416. package/src/eval/js/shared/local-module-loader.ts +364 -0
  417. package/src/eval/js/shared/prelude.ts +2 -0
  418. package/src/eval/js/shared/prelude.txt +294 -0
  419. package/src/eval/js/shared/rewrite-imports.ts +550 -0
  420. package/src/eval/js/shared/runtime.ts +590 -0
  421. package/src/eval/js/shared/types.ts +18 -0
  422. package/src/eval/js/tool-bridge.ts +163 -0
  423. package/src/eval/js/worker-core.ts +380 -0
  424. package/src/eval/js/worker-entry.ts +37 -0
  425. package/src/eval/js/worker-protocol.ts +47 -0
  426. package/src/eval/kernel-base.ts +569 -0
  427. package/src/eval/py/display.ts +71 -0
  428. package/src/eval/py/executor.ts +643 -0
  429. package/src/eval/py/index.ts +57 -0
  430. package/src/eval/py/kernel.ts +235 -0
  431. package/src/eval/py/prelude.py +673 -0
  432. package/src/eval/py/prelude.ts +3 -0
  433. package/src/eval/py/runner.py +1425 -0
  434. package/src/eval/py/runtime.ts +276 -0
  435. package/src/eval/py/spawn-options.ts +134 -0
  436. package/src/eval/py/tool-bridge.ts +201 -0
  437. package/src/eval/rb/executor.ts +511 -0
  438. package/src/eval/rb/index.ts +54 -0
  439. package/src/eval/rb/kernel.ts +231 -0
  440. package/src/eval/rb/prelude.rb +551 -0
  441. package/src/eval/rb/prelude.ts +3 -0
  442. package/src/eval/rb/runner.rb +581 -0
  443. package/src/eval/rb/runtime.ts +132 -0
  444. package/src/eval/runtime-env.ts +104 -0
  445. package/src/eval/session-id.ts +8 -0
  446. package/src/eval/types.ts +48 -0
  447. package/src/exa/index.ts +2 -0
  448. package/src/exa/mcp-client.ts +370 -0
  449. package/src/exa/types.ts +69 -0
  450. package/src/exec/bash-executor.ts +627 -0
  451. package/src/exec/direnv.ts +145 -0
  452. package/src/exec/exec.ts +53 -0
  453. package/src/exec/non-interactive-env.ts +118 -0
  454. package/src/export/custom-share.ts +65 -0
  455. package/src/export/html/args.ts +20 -0
  456. package/src/export/html/index.ts +320 -0
  457. package/src/export/html/share-loader.js +102 -0
  458. package/src/export/html/template.css +1355 -0
  459. package/src/export/html/template.html +55 -0
  460. package/src/export/html/template.js +1653 -0
  461. package/src/export/html/tool-views.generated.js +35 -0
  462. package/src/export/html/vendor/highlight.min.js +1213 -0
  463. package/src/export/html/vendor/marked.min.js +6 -0
  464. package/src/export/html/web-palette.ts +126 -0
  465. package/src/export/share.ts +686 -0
  466. package/src/export/ttsr.ts +590 -0
  467. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
  468. package/src/extensibility/custom-commands/bundled/review/index.ts +698 -0
  469. package/src/extensibility/custom-commands/index.ts +2 -0
  470. package/src/extensibility/custom-commands/loader.ts +242 -0
  471. package/src/extensibility/custom-commands/types.ts +119 -0
  472. package/src/extensibility/custom-tools/index.ts +7 -0
  473. package/src/extensibility/custom-tools/loader.ts +301 -0
  474. package/src/extensibility/custom-tools/types.ts +287 -0
  475. package/src/extensibility/custom-tools/wrapper.ts +50 -0
  476. package/src/extensibility/extensions/compact-handler.ts +40 -0
  477. package/src/extensibility/extensions/get-commands-handler.ts +78 -0
  478. package/src/extensibility/extensions/index.ts +16 -0
  479. package/src/extensibility/extensions/load-errors.ts +13 -0
  480. package/src/extensibility/extensions/loader.ts +663 -0
  481. package/src/extensibility/extensions/managed-timers.ts +83 -0
  482. package/src/extensibility/extensions/model-api.ts +39 -0
  483. package/src/extensibility/extensions/runner.ts +1396 -0
  484. package/src/extensibility/extensions/types.ts +1603 -0
  485. package/src/extensibility/extensions/wrapper.ts +395 -0
  486. package/src/extensibility/hooks/index.ts +6 -0
  487. package/src/extensibility/hooks/loader.ts +243 -0
  488. package/src/extensibility/hooks/runner.ts +425 -0
  489. package/src/extensibility/hooks/tool-wrapper.ts +124 -0
  490. package/src/extensibility/hooks/types.ts +612 -0
  491. package/src/extensibility/legacy-pi-ai-shim.ts +161 -0
  492. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1460 -0
  493. package/src/extensibility/legacy-pi-tui-shim.ts +43 -0
  494. package/src/extensibility/legacy-typebox.ts +101 -0
  495. package/src/extensibility/plugins/bun-git-cache.ts +91 -0
  496. package/src/extensibility/plugins/doctor.ts +65 -0
  497. package/src/extensibility/plugins/git-url.ts +367 -0
  498. package/src/extensibility/plugins/index.ts +9 -0
  499. package/src/extensibility/plugins/installer.ts +201 -0
  500. package/src/extensibility/plugins/legacy-pi-compat.ts +2643 -0
  501. package/src/extensibility/plugins/legacy-pi-virtual-modules.d.ts +4 -0
  502. package/src/extensibility/plugins/loader.ts +535 -0
  503. package/src/extensibility/plugins/manager.ts +1142 -0
  504. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  505. package/src/extensibility/plugins/marketplace/fetcher.ts +316 -0
  506. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  507. package/src/extensibility/plugins/marketplace/manager.ts +927 -0
  508. package/src/extensibility/plugins/marketplace/registry.ts +192 -0
  509. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  510. package/src/extensibility/plugins/marketplace/types.ts +192 -0
  511. package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
  512. package/src/extensibility/plugins/parser.ts +107 -0
  513. package/src/extensibility/plugins/runtime-config.ts +9 -0
  514. package/src/extensibility/plugins/types.ts +194 -0
  515. package/src/extensibility/session-handler-types.ts +21 -0
  516. package/src/extensibility/shared-events.ts +401 -0
  517. package/src/extensibility/skills.ts +531 -0
  518. package/src/extensibility/slash-commands.ts +131 -0
  519. package/src/extensibility/tool-event-input.ts +80 -0
  520. package/src/extensibility/tool-proxy.ts +28 -0
  521. package/src/extensibility/utils.ts +192 -0
  522. package/src/goals/index.ts +3 -0
  523. package/src/goals/runtime.ts +521 -0
  524. package/src/goals/state.ts +37 -0
  525. package/src/goals/tools/goal-tool.ts +251 -0
  526. package/src/hindsight/backend.ts +354 -0
  527. package/src/hindsight/bank.ts +156 -0
  528. package/src/hindsight/client.ts +680 -0
  529. package/src/hindsight/config.ts +193 -0
  530. package/src/hindsight/content.ts +266 -0
  531. package/src/hindsight/index.ts +8 -0
  532. package/src/hindsight/mental-models.ts +429 -0
  533. package/src/hindsight/seeds.json +32 -0
  534. package/src/hindsight/state.ts +530 -0
  535. package/src/hindsight/transcript.ts +71 -0
  536. package/src/index.ts +66 -0
  537. package/src/internal-urls/agent-protocol.ts +180 -0
  538. package/src/internal-urls/artifact-protocol.ts +151 -0
  539. package/src/internal-urls/docs-index.ts +102 -0
  540. package/src/internal-urls/filesystem-resource.ts +34 -0
  541. package/src/internal-urls/history-protocol.ts +198 -0
  542. package/src/internal-urls/index.ts +28 -0
  543. package/src/internal-urls/issue-pr-protocol.ts +594 -0
  544. package/src/internal-urls/json-query.ts +126 -0
  545. package/src/internal-urls/local-protocol.ts +471 -0
  546. package/src/internal-urls/mcp-protocol.ts +168 -0
  547. package/src/internal-urls/memory-protocol.ts +380 -0
  548. package/src/internal-urls/omp-protocol.ts +94 -0
  549. package/src/internal-urls/parse.ts +103 -0
  550. package/src/internal-urls/registry-helpers.ts +132 -0
  551. package/src/internal-urls/router.ts +153 -0
  552. package/src/internal-urls/rule-protocol.ts +45 -0
  553. package/src/internal-urls/security-protocol.ts +261 -0
  554. package/src/internal-urls/skill-protocol.ts +131 -0
  555. package/src/internal-urls/ssh-protocol.ts +368 -0
  556. package/src/internal-urls/types.ts +196 -0
  557. package/src/internal-urls/vault-protocol.ts +940 -0
  558. package/src/internal-urls/xd-protocol.ts +46 -0
  559. package/src/irc/bus.ts +380 -0
  560. package/src/jsonrpc/message-framing.ts +142 -0
  561. package/src/launch/broker.ts +1368 -0
  562. package/src/launch/client.ts +521 -0
  563. package/src/launch/ensure.ts +73 -0
  564. package/src/launch/paths.ts +14 -0
  565. package/src/launch/presence.ts +82 -0
  566. package/src/launch/protocol.ts +448 -0
  567. package/src/launch/spawn-options.ts +17 -0
  568. package/src/launch/terminal-output-worker-client.ts +53 -0
  569. package/src/launch/terminal-output-worker-protocol.ts +11 -0
  570. package/src/launch/terminal-output-worker.ts +23 -0
  571. package/src/launch/terminal-output.ts +46 -0
  572. package/src/lib/xai-http.ts +150 -0
  573. package/src/live/attestation.ts +91 -0
  574. package/src/live/controller.ts +517 -0
  575. package/src/live/prompts/agent-final-message.md +3 -0
  576. package/src/live/prompts/live-instructions.md +23 -0
  577. package/src/live/protocol.ts +233 -0
  578. package/src/live/transport.ts +422 -0
  579. package/src/live/visualizer.ts +221 -0
  580. package/src/live/voices.ts +18 -0
  581. package/src/lsp/client.ts +1465 -0
  582. package/src/lsp/clients/biome-client.ts +263 -0
  583. package/src/lsp/clients/index.ts +50 -0
  584. package/src/lsp/clients/lsp-linter-client.ts +85 -0
  585. package/src/lsp/clients/swiftlint-client.ts +120 -0
  586. package/src/lsp/config.ts +549 -0
  587. package/src/lsp/defaults.json +499 -0
  588. package/src/lsp/deferred-diagnostics.ts +66 -0
  589. package/src/lsp/diagnostics-ledger.ts +51 -0
  590. package/src/lsp/edits.ts +288 -0
  591. package/src/lsp/format-options.ts +119 -0
  592. package/src/lsp/index.ts +2821 -0
  593. package/src/lsp/lspmux.ts +233 -0
  594. package/src/lsp/mux/daemon.ts +348 -0
  595. package/src/lsp/mux/protocol.ts +96 -0
  596. package/src/lsp/mux/server.ts +797 -0
  597. package/src/lsp/render.ts +668 -0
  598. package/src/lsp/startup-events.ts +13 -0
  599. package/src/lsp/types.ts +479 -0
  600. package/src/lsp/utils.ts +747 -0
  601. package/src/main.ts +1799 -0
  602. package/src/markit/NOTICE +32 -0
  603. package/src/markit/converters/docx.ts +56 -0
  604. package/src/markit/converters/epub.ts +136 -0
  605. package/src/markit/converters/pdf/columns.ts +103 -0
  606. package/src/markit/converters/pdf/extract.ts +598 -0
  607. package/src/markit/converters/pdf/grid.ts +780 -0
  608. package/src/markit/converters/pdf/headers.ts +106 -0
  609. package/src/markit/converters/pdf/index.ts +146 -0
  610. package/src/markit/converters/pdf/render.ts +501 -0
  611. package/src/markit/converters/pdf/types.ts +84 -0
  612. package/src/markit/converters/pptx.ts +325 -0
  613. package/src/markit/converters/xlsx.ts +173 -0
  614. package/src/markit/index.ts +2 -0
  615. package/src/markit/registry.ts +59 -0
  616. package/src/markit/types.ts +35 -0
  617. package/src/mcp/client.ts +511 -0
  618. package/src/mcp/config-writer.ts +377 -0
  619. package/src/mcp/config.ts +385 -0
  620. package/src/mcp/index.ts +29 -0
  621. package/src/mcp/json-rpc.ts +122 -0
  622. package/src/mcp/loader.ts +125 -0
  623. package/src/mcp/manager.ts +1524 -0
  624. package/src/mcp/oauth-credentials.ts +104 -0
  625. package/src/mcp/oauth-discovery.ts +587 -0
  626. package/src/mcp/oauth-flow.ts +830 -0
  627. package/src/mcp/render.ts +214 -0
  628. package/src/mcp/request-id.ts +24 -0
  629. package/src/mcp/smithery-auth.ts +108 -0
  630. package/src/mcp/smithery-connect.ts +154 -0
  631. package/src/mcp/smithery-registry.ts +500 -0
  632. package/src/mcp/startup-events.ts +116 -0
  633. package/src/mcp/timeout.ts +59 -0
  634. package/src/mcp/tool-bridge.ts +691 -0
  635. package/src/mcp/tool-cache.ts +117 -0
  636. package/src/mcp/transports/header-policy.ts +95 -0
  637. package/src/mcp/transports/http.ts +508 -0
  638. package/src/mcp/transports/index.ts +7 -0
  639. package/src/mcp/transports/sse.ts +372 -0
  640. package/src/mcp/transports/stdio.ts +905 -0
  641. package/src/mcp/types.ts +467 -0
  642. package/src/memories/index.ts +1434 -0
  643. package/src/memories/storage.ts +578 -0
  644. package/src/memory-backend/index.ts +19 -0
  645. package/src/memory-backend/local-backend.ts +47 -0
  646. package/src/memory-backend/messages.ts +19 -0
  647. package/src/memory-backend/off-backend.ts +25 -0
  648. package/src/memory-backend/resolve.ts +25 -0
  649. package/src/memory-backend/runtime.ts +66 -0
  650. package/src/memory-backend/tool-names.ts +2 -0
  651. package/src/memory-backend/types.ts +166 -0
  652. package/src/mnemopi/backend.ts +629 -0
  653. package/src/mnemopi/config.ts +267 -0
  654. package/src/mnemopi/embed-client.ts +293 -0
  655. package/src/mnemopi/embed-protocol.ts +35 -0
  656. package/src/mnemopi/embed-worker.ts +114 -0
  657. package/src/mnemopi/index.ts +3 -0
  658. package/src/mnemopi/state.ts +940 -0
  659. package/src/modes/acp/acp-agent.ts +2595 -0
  660. package/src/modes/acp/acp-client-bridge.ts +154 -0
  661. package/src/modes/acp/acp-event-mapper.ts +1084 -0
  662. package/src/modes/acp/acp-mode.ts +48 -0
  663. package/src/modes/acp/index.ts +2 -0
  664. package/src/modes/acp/terminal-auth.ts +37 -0
  665. package/src/modes/components/advisor-config.ts +635 -0
  666. package/src/modes/components/advisor-message.ts +109 -0
  667. package/src/modes/components/agent-dashboard.ts +1252 -0
  668. package/src/modes/components/agent-hub-projection.ts +248 -0
  669. package/src/modes/components/agent-hub-renderer.ts +194 -0
  670. package/src/modes/components/agent-hub.ts +1126 -0
  671. package/src/modes/components/agent-transcript-viewer.ts +647 -0
  672. package/src/modes/components/ask-dialog.ts +1017 -0
  673. package/src/modes/components/assistant-message.ts +978 -0
  674. package/src/modes/components/background-tan-message.ts +36 -0
  675. package/src/modes/components/bash-execution.ts +233 -0
  676. package/src/modes/components/bordered-loader.ts +41 -0
  677. package/src/modes/components/btw-panel.ts +161 -0
  678. package/src/modes/components/cache-invalidation-marker.ts +110 -0
  679. package/src/modes/components/chat-block.ts +111 -0
  680. package/src/modes/components/chat-transcript-builder.ts +518 -0
  681. package/src/modes/components/codex-reset-fireworks.ts +369 -0
  682. package/src/modes/components/collab-prompt-message.ts +32 -0
  683. package/src/modes/components/compaction-summary-message.ts +221 -0
  684. package/src/modes/components/copy-selector.ts +218 -0
  685. package/src/modes/components/countdown-timer.ts +75 -0
  686. package/src/modes/components/custom-editor.ts +1036 -0
  687. package/src/modes/components/custom-message.ts +70 -0
  688. package/src/modes/components/diff.ts +254 -0
  689. package/src/modes/components/dynamic-border.ts +37 -0
  690. package/src/modes/components/error-banner.ts +33 -0
  691. package/src/modes/components/eval-execution.ts +169 -0
  692. package/src/modes/components/execution-shared.ts +101 -0
  693. package/src/modes/components/extensions/extension-dashboard.ts +492 -0
  694. package/src/modes/components/extensions/extension-list.ts +507 -0
  695. package/src/modes/components/extensions/index.ts +9 -0
  696. package/src/modes/components/extensions/inspector-panel.ts +326 -0
  697. package/src/modes/components/extensions/state-manager.ts +648 -0
  698. package/src/modes/components/extensions/types.ts +186 -0
  699. package/src/modes/components/footer.ts +274 -0
  700. package/src/modes/components/history-search.ts +268 -0
  701. package/src/modes/components/hook-editor.ts +213 -0
  702. package/src/modes/components/hook-input.ts +87 -0
  703. package/src/modes/components/hook-message.ts +67 -0
  704. package/src/modes/components/hook-selector.ts +691 -0
  705. package/src/modes/components/index.ts +42 -0
  706. package/src/modes/components/keybinding-hints.ts +56 -0
  707. package/src/modes/components/late-diagnostics-message.ts +60 -0
  708. package/src/modes/components/login-dialog.ts +197 -0
  709. package/src/modes/components/logout-account-selector.ts +130 -0
  710. package/src/modes/components/mcp-add-wizard.ts +1413 -0
  711. package/src/modes/components/message-frame.ts +98 -0
  712. package/src/modes/components/model-browser.ts +888 -0
  713. package/src/modes/components/model-hub.ts +2014 -0
  714. package/src/modes/components/model-picker.ts +237 -0
  715. package/src/modes/components/move-overlay.ts +293 -0
  716. package/src/modes/components/oauth-selector.ts +474 -0
  717. package/src/modes/components/omfg-panel.ts +141 -0
  718. package/src/modes/components/overlay-box.ts +109 -0
  719. package/src/modes/components/pause-screen.ts +208 -0
  720. package/src/modes/components/plan-review-overlay.ts +1226 -0
  721. package/src/modes/components/plan-toc.ts +138 -0
  722. package/src/modes/components/plugin-selector.ts +100 -0
  723. package/src/modes/components/plugin-settings.ts +745 -0
  724. package/src/modes/components/queue-mode-selector.ts +61 -0
  725. package/src/modes/components/read-tool-group.ts +856 -0
  726. package/src/modes/components/reset-usage-selector.ts +161 -0
  727. package/src/modes/components/segment-track.ts +89 -0
  728. package/src/modes/components/select-list-mouse-routing.ts +35 -0
  729. package/src/modes/components/selector-helpers.ts +129 -0
  730. package/src/modes/components/session-account-selector.ts +62 -0
  731. package/src/modes/components/session-selector.ts +1035 -0
  732. package/src/modes/components/settings-defs.ts +267 -0
  733. package/src/modes/components/settings-selector.ts +1445 -0
  734. package/src/modes/components/show-images-selector.ts +50 -0
  735. package/src/modes/components/skill-message.ts +110 -0
  736. package/src/modes/components/snapcompact-shape-preview-doc.md +14 -0
  737. package/src/modes/components/snapcompact-shape-preview.ts +192 -0
  738. package/src/modes/components/status-line/component.ts +1843 -0
  739. package/src/modes/components/status-line/context-thresholds.ts +86 -0
  740. package/src/modes/components/status-line/git-utils.ts +42 -0
  741. package/src/modes/components/status-line/index.ts +5 -0
  742. package/src/modes/components/status-line/presets.ts +106 -0
  743. package/src/modes/components/status-line/segments.ts +710 -0
  744. package/src/modes/components/status-line/separators.ts +55 -0
  745. package/src/modes/components/status-line/types.ts +159 -0
  746. package/src/modes/components/stripped-tool-calls-placeholder.ts +35 -0
  747. package/src/modes/components/theme-selector.ts +68 -0
  748. package/src/modes/components/thinking-selector.ts +57 -0
  749. package/src/modes/components/tiny-title-download-progress.ts +90 -0
  750. package/src/modes/components/tips.txt +26 -0
  751. package/src/modes/components/todo-reminder.ts +43 -0
  752. package/src/modes/components/tool-execution.ts +1467 -0
  753. package/src/modes/components/transcript-container.ts +527 -0
  754. package/src/modes/components/tree-selector.ts +1006 -0
  755. package/src/modes/components/ttsr-notification.ts +123 -0
  756. package/src/modes/components/usage-row.ts +52 -0
  757. package/src/modes/components/user-message-selector.ts +227 -0
  758. package/src/modes/components/user-message.ts +167 -0
  759. package/src/modes/components/visual-truncate.ts +63 -0
  760. package/src/modes/components/welcome.ts +578 -0
  761. package/src/modes/controllers/btw-controller.ts +246 -0
  762. package/src/modes/controllers/command-controller-shared.ts +109 -0
  763. package/src/modes/controllers/command-controller.ts +2024 -0
  764. package/src/modes/controllers/event-controller.ts +2173 -0
  765. package/src/modes/controllers/extension-ui-controller.ts +1243 -0
  766. package/src/modes/controllers/input-controller.ts +2037 -0
  767. package/src/modes/controllers/live-command-controller.ts +261 -0
  768. package/src/modes/controllers/mcp-command-controller.ts +2552 -0
  769. package/src/modes/controllers/omfg-controller.ts +287 -0
  770. package/src/modes/controllers/omfg-rule.ts +647 -0
  771. package/src/modes/controllers/selector-controller.ts +2058 -0
  772. package/src/modes/controllers/session-focus-controller.ts +117 -0
  773. package/src/modes/controllers/ssh-command-controller.ts +385 -0
  774. package/src/modes/controllers/streaming-reveal.ts +399 -0
  775. package/src/modes/controllers/tan-command-controller.ts +243 -0
  776. package/src/modes/controllers/todo-command-controller.ts +487 -0
  777. package/src/modes/controllers/tool-args-reveal.ts +591 -0
  778. package/src/modes/data/emojis.json +1 -0
  779. package/src/modes/emoji-autocomplete.ts +285 -0
  780. package/src/modes/github-ref-autocomplete.ts +75 -0
  781. package/src/modes/gradient-highlight.ts +99 -0
  782. package/src/modes/image-references.ts +137 -0
  783. package/src/modes/index.ts +10 -0
  784. package/src/modes/interactive-mode.ts +5055 -0
  785. package/src/modes/internal-url-autocomplete.ts +158 -0
  786. package/src/modes/loop-limit.ts +192 -0
  787. package/src/modes/magic-keyword-boundary.ts +23 -0
  788. package/src/modes/magic-keywords.ts +42 -0
  789. package/src/modes/markdown-prose.ts +247 -0
  790. package/src/modes/oauth-manual-input.ts +69 -0
  791. package/src/modes/orchestrate.ts +43 -0
  792. package/src/modes/print-mode.ts +282 -0
  793. package/src/modes/prompt-action-autocomplete.ts +322 -0
  794. package/src/modes/queue-input.ts +132 -0
  795. package/src/modes/rpc/host-tools.ts +204 -0
  796. package/src/modes/rpc/host-uris.ts +241 -0
  797. package/src/modes/rpc/rpc-client.ts +1213 -0
  798. package/src/modes/rpc/rpc-frame.ts +316 -0
  799. package/src/modes/rpc/rpc-input.ts +38 -0
  800. package/src/modes/rpc/rpc-messages.ts +127 -0
  801. package/src/modes/rpc/rpc-mode.ts +1519 -0
  802. package/src/modes/rpc/rpc-subagents.ts +265 -0
  803. package/src/modes/rpc/rpc-types.ts +544 -0
  804. package/src/modes/running-subagent-badge.ts +13 -0
  805. package/src/modes/runtime-init.ts +144 -0
  806. package/src/modes/session-observer-registry.ts +223 -0
  807. package/src/modes/session-teardown.ts +82 -0
  808. package/src/modes/setup-version.ts +11 -0
  809. package/src/modes/setup-wizard/index.ts +103 -0
  810. package/src/modes/setup-wizard/lazy.ts +16 -0
  811. package/src/modes/setup-wizard/scenes/glyph.ts +103 -0
  812. package/src/modes/setup-wizard/scenes/model.ts +132 -0
  813. package/src/modes/setup-wizard/scenes/outro.ts +35 -0
  814. package/src/modes/setup-wizard/scenes/providers.ts +105 -0
  815. package/src/modes/setup-wizard/scenes/sign-in.ts +312 -0
  816. package/src/modes/setup-wizard/scenes/splash.ts +201 -0
  817. package/src/modes/setup-wizard/scenes/theme.ts +330 -0
  818. package/src/modes/setup-wizard/scenes/types.ts +65 -0
  819. package/src/modes/setup-wizard/scenes/web-search.ts +153 -0
  820. package/src/modes/setup-wizard/startup-splash.ts +107 -0
  821. package/src/modes/setup-wizard/wizard-overlay.ts +335 -0
  822. package/src/modes/shared.ts +49 -0
  823. package/src/modes/skill-command.ts +91 -0
  824. package/src/modes/theme/dark.json +95 -0
  825. package/src/modes/theme/defaults/alabaster.json +93 -0
  826. package/src/modes/theme/defaults/amethyst.json +96 -0
  827. package/src/modes/theme/defaults/anthracite.json +93 -0
  828. package/src/modes/theme/defaults/basalt.json +91 -0
  829. package/src/modes/theme/defaults/birch.json +95 -0
  830. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  831. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  832. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  833. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  834. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  835. package/src/modes/theme/defaults/dark-copper.json +95 -0
  836. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  837. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  838. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  839. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  840. package/src/modes/theme/defaults/dark-ember.json +95 -0
  841. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  842. package/src/modes/theme/defaults/dark-forest.json +96 -0
  843. package/src/modes/theme/defaults/dark-github.json +105 -0
  844. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  845. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  846. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  847. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  848. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  849. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  850. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  851. package/src/modes/theme/defaults/dark-nord.json +97 -0
  852. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  853. package/src/modes/theme/defaults/dark-one.json +100 -0
  854. package/src/modes/theme/defaults/dark-poimandres.json +143 -0
  855. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  856. package/src/modes/theme/defaults/dark-reef.json +91 -0
  857. package/src/modes/theme/defaults/dark-retro.json +92 -0
  858. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  859. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  860. package/src/modes/theme/defaults/dark-slate.json +95 -0
  861. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  862. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  863. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  864. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  865. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  866. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  867. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  868. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  869. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  870. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  871. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  872. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  873. package/src/modes/theme/defaults/graphite.json +92 -0
  874. package/src/modes/theme/defaults/index.ts +199 -0
  875. package/src/modes/theme/defaults/light-arctic.json +107 -0
  876. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  877. package/src/modes/theme/defaults/light-canyon.json +91 -0
  878. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  879. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  880. package/src/modes/theme/defaults/light-coral.json +95 -0
  881. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  882. package/src/modes/theme/defaults/light-dawn.json +90 -0
  883. package/src/modes/theme/defaults/light-dunes.json +91 -0
  884. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  885. package/src/modes/theme/defaults/light-forest.json +100 -0
  886. package/src/modes/theme/defaults/light-frost.json +95 -0
  887. package/src/modes/theme/defaults/light-github.json +115 -0
  888. package/src/modes/theme/defaults/light-glacier.json +91 -0
  889. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  890. package/src/modes/theme/defaults/light-haze.json +90 -0
  891. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  892. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  893. package/src/modes/theme/defaults/light-lavender.json +95 -0
  894. package/src/modes/theme/defaults/light-meadow.json +91 -0
  895. package/src/modes/theme/defaults/light-mint.json +95 -0
  896. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  897. package/src/modes/theme/defaults/light-ocean.json +99 -0
  898. package/src/modes/theme/defaults/light-one.json +99 -0
  899. package/src/modes/theme/defaults/light-opal.json +91 -0
  900. package/src/modes/theme/defaults/light-orchard.json +91 -0
  901. package/src/modes/theme/defaults/light-paper.json +95 -0
  902. package/src/modes/theme/defaults/light-poimandres.json +143 -0
  903. package/src/modes/theme/defaults/light-prism.json +90 -0
  904. package/src/modes/theme/defaults/light-retro.json +98 -0
  905. package/src/modes/theme/defaults/light-sand.json +95 -0
  906. package/src/modes/theme/defaults/light-savanna.json +91 -0
  907. package/src/modes/theme/defaults/light-solarized.json +102 -0
  908. package/src/modes/theme/defaults/light-soleil.json +90 -0
  909. package/src/modes/theme/defaults/light-sunset.json +99 -0
  910. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  911. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  912. package/src/modes/theme/defaults/light-wetland.json +91 -0
  913. package/src/modes/theme/defaults/light-zenith.json +89 -0
  914. package/src/modes/theme/defaults/limestone.json +94 -0
  915. package/src/modes/theme/defaults/mahogany.json +97 -0
  916. package/src/modes/theme/defaults/marble.json +93 -0
  917. package/src/modes/theme/defaults/obsidian.json +91 -0
  918. package/src/modes/theme/defaults/onyx.json +91 -0
  919. package/src/modes/theme/defaults/pearl.json +93 -0
  920. package/src/modes/theme/defaults/porcelain.json +91 -0
  921. package/src/modes/theme/defaults/quartz.json +96 -0
  922. package/src/modes/theme/defaults/sandstone.json +95 -0
  923. package/src/modes/theme/defaults/titanium.json +90 -0
  924. package/src/modes/theme/light.json +93 -0
  925. package/src/modes/theme/mermaid-cache.ts +92 -0
  926. package/src/modes/theme/shimmer.ts +305 -0
  927. package/src/modes/theme/theme-schema.json +463 -0
  928. package/src/modes/theme/theme.ts +3171 -0
  929. package/src/modes/turn-budget.ts +31 -0
  930. package/src/modes/types.ts +494 -0
  931. package/src/modes/ultrathink.ts +42 -0
  932. package/src/modes/utils/context-usage.ts +518 -0
  933. package/src/modes/utils/copy-targets.ts +378 -0
  934. package/src/modes/utils/hotkeys-markdown.ts +65 -0
  935. package/src/modes/utils/interactive-context-helpers.ts +31 -0
  936. package/src/modes/utils/keybinding-matchers.ts +86 -0
  937. package/src/modes/utils/tools-markdown.ts +31 -0
  938. package/src/modes/utils/transcript-render-helpers.ts +253 -0
  939. package/src/modes/utils/ui-helpers.ts +976 -0
  940. package/src/modes/warp-events.ts +232 -0
  941. package/src/modes/workflow.ts +55 -0
  942. package/src/plan-mode/approved-plan.ts +194 -0
  943. package/src/plan-mode/model-transition.ts +51 -0
  944. package/src/plan-mode/plan-files.ts +40 -0
  945. package/src/plan-mode/plan-handoff.ts +37 -0
  946. package/src/plan-mode/plan-protection.ts +31 -0
  947. package/src/plan-mode/state.ts +6 -0
  948. package/src/priority.json +60 -0
  949. package/src/prompts/advisor/active-repo-watchdog.md +6 -0
  950. package/src/prompts/advisor/advise-tool.md +3 -0
  951. package/src/prompts/advisor/context-files.md +8 -0
  952. package/src/prompts/advisor/system.md +98 -0
  953. package/src/prompts/agents/designer.md +74 -0
  954. package/src/prompts/agents/frontmatter.md +12 -0
  955. package/src/prompts/agents/init.md +33 -0
  956. package/src/prompts/agents/librarian.md +119 -0
  957. package/src/prompts/agents/reviewer.md +139 -0
  958. package/src/prompts/agents/scout.md +58 -0
  959. package/src/prompts/agents/security-reviewer.md +75 -0
  960. package/src/prompts/agents/task.md +17 -0
  961. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  962. package/src/prompts/bench/cache-prefix.md +3 -0
  963. package/src/prompts/bench/cache-suffix.md +1 -0
  964. package/src/prompts/bench.md +6 -0
  965. package/src/prompts/ci-green-request.md +36 -0
  966. package/src/prompts/dry-balance-bench.md +8 -0
  967. package/src/prompts/goals/goal-budget-limit.md +16 -0
  968. package/src/prompts/goals/goal-continuation.md +28 -0
  969. package/src/prompts/goals/goal-mode-active.md +23 -0
  970. package/src/prompts/goals/goal-mode-context.md +4 -0
  971. package/src/prompts/goals/goal-todo-context.md +12 -0
  972. package/src/prompts/goals/guided-goal-interview.md +43 -0
  973. package/src/prompts/memories/consolidation.md +30 -0
  974. package/src/prompts/memories/consolidation_system.md +4 -0
  975. package/src/prompts/memories/read-path.md +17 -0
  976. package/src/prompts/memories/stage_one_input.md +6 -0
  977. package/src/prompts/memories/stage_one_system.md +21 -0
  978. package/src/prompts/review-custom-request.md +21 -0
  979. package/src/prompts/review-headless-request.md +16 -0
  980. package/src/prompts/review-request.md +68 -0
  981. package/src/prompts/security/scan-coordinator.md +7 -0
  982. package/src/prompts/security/scan-request.md +21 -0
  983. package/src/prompts/security/validate-request.md +8 -0
  984. package/src/prompts/session/launch-completion.md +1 -0
  985. package/src/prompts/skills/autoload.md +8 -0
  986. package/src/prompts/skills/user-invocation.md +11 -0
  987. package/src/prompts/steering/parent-irc.md +5 -0
  988. package/src/prompts/steering/user-interjection.md +6 -0
  989. package/src/prompts/system/active-repo-context.md +4 -0
  990. package/src/prompts/system/agent-creation-architect.md +50 -0
  991. package/src/prompts/system/agent-creation-user.md +6 -0
  992. package/src/prompts/system/auto-continue.md +1 -0
  993. package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
  994. package/src/prompts/system/auto-thinking-difficulty.md +14 -0
  995. package/src/prompts/system/autolearn-guidance-learn.md +1 -0
  996. package/src/prompts/system/autolearn-guidance.md +7 -0
  997. package/src/prompts/system/autolearn-nudge-autocontinue.md +5 -0
  998. package/src/prompts/system/background-tan-dispatch.md +8 -0
  999. package/src/prompts/system/btw-user.md +8 -0
  1000. package/src/prompts/system/commit-message-system.md +14 -0
  1001. package/src/prompts/system/computer-safety.md +14 -0
  1002. package/src/prompts/system/custom-system-prompt.md +64 -0
  1003. package/src/prompts/system/eager-task.md +7 -0
  1004. package/src/prompts/system/eager-todo.md +18 -0
  1005. package/src/prompts/system/empty-stop-retry.md +4 -0
  1006. package/src/prompts/system/gemini-tool-call-reminder.md +9 -0
  1007. package/src/prompts/system/interrupted-thinking.md +7 -0
  1008. package/src/prompts/system/irc-autoreply.md +6 -0
  1009. package/src/prompts/system/irc-incoming.md +9 -0
  1010. package/src/prompts/system/manual-continue.md +7 -0
  1011. package/src/prompts/system/mcp-xdev-guidance.md +11 -0
  1012. package/src/prompts/system/memory-consolidation-system.md +8 -0
  1013. package/src/prompts/system/memory-extraction-system.md +26 -0
  1014. package/src/prompts/system/mid-run-todo-nudge.md +3 -0
  1015. package/src/prompts/system/omfg-user.md +50 -0
  1016. package/src/prompts/system/orchestrate-notice.md +40 -0
  1017. package/src/prompts/system/personalities/default.md +18 -0
  1018. package/src/prompts/system/personalities/friendly.md +17 -0
  1019. package/src/prompts/system/personalities/pragmatic.md +15 -0
  1020. package/src/prompts/system/plan-mode-active.md +125 -0
  1021. package/src/prompts/system/plan-mode-approved.md +22 -0
  1022. package/src/prompts/system/plan-mode-compact-instructions.md +17 -0
  1023. package/src/prompts/system/plan-mode-reference.md +10 -0
  1024. package/src/prompts/system/plan-mode-subagent.md +33 -0
  1025. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  1026. package/src/prompts/system/plan-yolo-handoff.md +5 -0
  1027. package/src/prompts/system/prewalk-checklist.md +7 -0
  1028. package/src/prompts/system/prewalk-continue.md +1 -0
  1029. package/src/prompts/system/prewalk-plan.md +13 -0
  1030. package/src/prompts/system/project-prompt.md +61 -0
  1031. package/src/prompts/system/recap-user.md +9 -0
  1032. package/src/prompts/system/resolve-device-reminder.md +3 -0
  1033. package/src/prompts/system/rewind-report.md +6 -0
  1034. package/src/prompts/system/side-channel-no-tools.md +3 -0
  1035. package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
  1036. package/src/prompts/system/snapcompact-context-stub.md +1 -0
  1037. package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
  1038. package/src/prompts/system/snapcompact-system-stub.md +1 -0
  1039. package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
  1040. package/src/prompts/system/speech-rewrite.md +15 -0
  1041. package/src/prompts/system/subagent-async-pending.md +6 -0
  1042. package/src/prompts/system/subagent-system-prompt.md +73 -0
  1043. package/src/prompts/system/subagent-user-prompt.md +3 -0
  1044. package/src/prompts/system/subagent-yield-reminder.md +23 -0
  1045. package/src/prompts/system/system-prompt.md +263 -0
  1046. package/src/prompts/system/tan-context-switch.md +17 -0
  1047. package/src/prompts/system/task-label.md +23 -0
  1048. package/src/prompts/system/thinking-loop-redirect.md +10 -0
  1049. package/src/prompts/system/title-marker-instruction.md +1 -0
  1050. package/src/prompts/system/title-system.md +16 -0
  1051. package/src/prompts/system/tool-call-loop-redirect.md +8 -0
  1052. package/src/prompts/system/ttsr-interrupt.md +7 -0
  1053. package/src/prompts/system/ttsr-tool-reminder.md +5 -0
  1054. package/src/prompts/system/ultrathink-notice.md +3 -0
  1055. package/src/prompts/system/unexpected-stop-classifier.md +17 -0
  1056. package/src/prompts/system/unexpected-stop-retry.md +4 -0
  1057. package/src/prompts/system/vibe-mode-active.md +26 -0
  1058. package/src/prompts/system/web-search.md +25 -0
  1059. package/src/prompts/system/workflow-notice.md +112 -0
  1060. package/src/prompts/system/xdev-mount-notice.md +20 -0
  1061. package/src/prompts/tools/apply-patch.md +65 -0
  1062. package/src/prompts/tools/ask.md +22 -0
  1063. package/src/prompts/tools/ast-edit.md +11 -0
  1064. package/src/prompts/tools/ast-grep.md +19 -0
  1065. package/src/prompts/tools/async-result.md +8 -0
  1066. package/src/prompts/tools/bash.md +23 -0
  1067. package/src/prompts/tools/browser.md +29 -0
  1068. package/src/prompts/tools/checkpoint.md +15 -0
  1069. package/src/prompts/tools/computer.md +26 -0
  1070. package/src/prompts/tools/debug.md +3 -0
  1071. package/src/prompts/tools/eval.md +45 -0
  1072. package/src/prompts/tools/github.md +22 -0
  1073. package/src/prompts/tools/glob.md +16 -0
  1074. package/src/prompts/tools/goal.md +11 -0
  1075. package/src/prompts/tools/grep.md +13 -0
  1076. package/src/prompts/tools/hub.md +34 -0
  1077. package/src/prompts/tools/image-attachment-describe-system.md +8 -0
  1078. package/src/prompts/tools/image-attachment-describe.md +10 -0
  1079. package/src/prompts/tools/image-gen.md +7 -0
  1080. package/src/prompts/tools/inspect-image-system.md +20 -0
  1081. package/src/prompts/tools/inspect-image.md +22 -0
  1082. package/src/prompts/tools/learn.md +7 -0
  1083. package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
  1084. package/src/prompts/tools/lsp.md +19 -0
  1085. package/src/prompts/tools/manage-skill.md +9 -0
  1086. package/src/prompts/tools/memory-edit.md +12 -0
  1087. package/src/prompts/tools/patch.md +57 -0
  1088. package/src/prompts/tools/read.md +27 -0
  1089. package/src/prompts/tools/recall.md +7 -0
  1090. package/src/prompts/tools/reflect.md +5 -0
  1091. package/src/prompts/tools/replace.md +30 -0
  1092. package/src/prompts/tools/retain.md +6 -0
  1093. package/src/prompts/tools/rewind.md +14 -0
  1094. package/src/prompts/tools/security-publish.md +1 -0
  1095. package/src/prompts/tools/security-scan.md +1 -0
  1096. package/src/prompts/tools/task-async-contract.md +1 -0
  1097. package/src/prompts/tools/task-summary.md +20 -0
  1098. package/src/prompts/tools/task.md +83 -0
  1099. package/src/prompts/tools/todo.md +42 -0
  1100. package/src/prompts/tools/vibe-kill.md +3 -0
  1101. package/src/prompts/tools/vibe-list.md +3 -0
  1102. package/src/prompts/tools/vibe-send.md +9 -0
  1103. package/src/prompts/tools/vibe-spawn.md +10 -0
  1104. package/src/prompts/tools/vibe-turn-result.md +19 -0
  1105. package/src/prompts/tools/vibe-wait.md +8 -0
  1106. package/src/prompts/tools/web-search.md +8 -0
  1107. package/src/prompts/tools/write.md +14 -0
  1108. package/src/registry/agent-lifecycle.ts +505 -0
  1109. package/src/registry/agent-registry.ts +287 -0
  1110. package/src/registry/persisted-agents.ts +423 -0
  1111. package/src/sdk.ts +3820 -0
  1112. package/src/secrets/index.ts +375 -0
  1113. package/src/secrets/obfuscator.ts +2629 -0
  1114. package/src/secrets/regex.ts +21 -0
  1115. package/src/security/auth.ts +98 -0
  1116. package/src/security/cloud.ts +686 -0
  1117. package/src/security/comparison.ts +255 -0
  1118. package/src/security/contracts/ids.ts +111 -0
  1119. package/src/security/contracts/index.ts +4 -0
  1120. package/src/security/contracts/schemas.ts +219 -0
  1121. package/src/security/contracts/types.ts +254 -0
  1122. package/src/security/contracts/validation.ts +69 -0
  1123. package/src/security/coordinator.ts +708 -0
  1124. package/src/security/importers/codex-security.ts +387 -0
  1125. package/src/security/importers/index.ts +2 -0
  1126. package/src/security/importers/sarif.ts +357 -0
  1127. package/src/security/index.ts +13 -0
  1128. package/src/security/preflight.ts +405 -0
  1129. package/src/security/provenance.ts +106 -0
  1130. package/src/security/publication.ts +326 -0
  1131. package/src/security/remediation.ts +93 -0
  1132. package/src/security/resource-output.ts +50 -0
  1133. package/src/security/sarif.ts +78 -0
  1134. package/src/security/store.ts +430 -0
  1135. package/src/session/acp-permission-gate.ts +165 -0
  1136. package/src/session/agent-session-events.ts +67 -0
  1137. package/src/session/agent-session-types.ts +407 -0
  1138. package/src/session/agent-session.ts +9000 -0
  1139. package/src/session/agent-storage.ts +807 -0
  1140. package/src/session/artifacts.ts +154 -0
  1141. package/src/session/async-job-delivery.ts +82 -0
  1142. package/src/session/auth-broker-config.ts +92 -0
  1143. package/src/session/auth-storage.ts +25 -0
  1144. package/src/session/bash-runner.ts +326 -0
  1145. package/src/session/blob-store.ts +295 -0
  1146. package/src/session/checkpoint-entries.ts +81 -0
  1147. package/src/session/claude-session-store.ts +426 -0
  1148. package/src/session/client-bridge.ts +85 -0
  1149. package/src/session/codex-auto-reset.ts +673 -0
  1150. package/src/session/codex-session-store.ts +673 -0
  1151. package/src/session/compact-modes.ts +105 -0
  1152. package/src/session/credential-pin.ts +93 -0
  1153. package/src/session/eval-runner.ts +219 -0
  1154. package/src/session/exit-diagnostics.ts +310 -0
  1155. package/src/session/foreign-session-import.ts +52 -0
  1156. package/src/session/foreign-session-jsonl.ts +29 -0
  1157. package/src/session/foreign-session-store.ts +26 -0
  1158. package/src/session/history-storage.ts +329 -0
  1159. package/src/session/indexed-session-storage.ts +553 -0
  1160. package/src/session/irc-bridge.ts +203 -0
  1161. package/src/session/launch-completion.ts +37 -0
  1162. package/src/session/messages.ts +1304 -0
  1163. package/src/session/model-controls.ts +757 -0
  1164. package/src/session/prewalk.ts +279 -0
  1165. package/src/session/provider-image-budget.ts +86 -0
  1166. package/src/session/queued-messages.ts +99 -0
  1167. package/src/session/redis-session-storage.ts +257 -0
  1168. package/src/session/retry-fallback-chains.ts +455 -0
  1169. package/src/session/role-models.ts +85 -0
  1170. package/src/session/session-advisors.ts +1896 -0
  1171. package/src/session/session-context.ts +584 -0
  1172. package/src/session/session-dump-format.ts +216 -0
  1173. package/src/session/session-entries.ts +306 -0
  1174. package/src/session/session-handoff.ts +325 -0
  1175. package/src/session/session-history-format.ts +459 -0
  1176. package/src/session/session-listing.ts +715 -0
  1177. package/src/session/session-loader.ts +362 -0
  1178. package/src/session/session-maintenance.ts +3115 -0
  1179. package/src/session/session-manager.ts +2747 -0
  1180. package/src/session/session-memory.ts +222 -0
  1181. package/src/session/session-metadata.ts +53 -0
  1182. package/src/session/session-migrations.ts +78 -0
  1183. package/src/session/session-paths.ts +280 -0
  1184. package/src/session/session-persistence.ts +293 -0
  1185. package/src/session/session-provider-boundary.ts +306 -0
  1186. package/src/session/session-stats.ts +349 -0
  1187. package/src/session/session-storage.ts +774 -0
  1188. package/src/session/session-title-slot.ts +141 -0
  1189. package/src/session/session-tools.ts +1385 -0
  1190. package/src/session/session-workspace.ts +53 -0
  1191. package/src/session/settings-stream-fn.ts +79 -0
  1192. package/src/session/shake-types.ts +43 -0
  1193. package/src/session/snapcompact-inline.ts +545 -0
  1194. package/src/session/snapcompact-savings-journal.ts +113 -0
  1195. package/src/session/sql-session-storage.ts +374 -0
  1196. package/src/session/stream-guards.ts +417 -0
  1197. package/src/session/streaming-output.ts +1459 -0
  1198. package/src/session/todo-tracker.ts +380 -0
  1199. package/src/session/tool-choice-queue.ts +305 -0
  1200. package/src/session/ttsr-coordinator.ts +496 -0
  1201. package/src/session/turn-persistence.ts +142 -0
  1202. package/src/session/turn-recovery.ts +2005 -0
  1203. package/src/session/unexpected-stop-classifier.ts +141 -0
  1204. package/src/session/yield-queue.ts +288 -0
  1205. package/src/slash-commands/acp-builtins.ts +70 -0
  1206. package/src/slash-commands/available-commands.ts +105 -0
  1207. package/src/slash-commands/builtin-registry.ts +3149 -0
  1208. package/src/slash-commands/helpers/active-oauth-account.ts +80 -0
  1209. package/src/slash-commands/helpers/collab-qrcode.ts +28 -0
  1210. package/src/slash-commands/helpers/context-report.ts +66 -0
  1211. package/src/slash-commands/helpers/format.ts +46 -0
  1212. package/src/slash-commands/helpers/logout.ts +108 -0
  1213. package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
  1214. package/src/slash-commands/helpers/mcp.ts +533 -0
  1215. package/src/slash-commands/helpers/parse.ts +85 -0
  1216. package/src/slash-commands/helpers/reset-usage.ts +68 -0
  1217. package/src/slash-commands/helpers/security.ts +451 -0
  1218. package/src/slash-commands/helpers/session-pin.ts +44 -0
  1219. package/src/slash-commands/helpers/ssh.ts +196 -0
  1220. package/src/slash-commands/helpers/stats-dashboard.ts +86 -0
  1221. package/src/slash-commands/helpers/todo.ts +285 -0
  1222. package/src/slash-commands/helpers/usage-report.ts +198 -0
  1223. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  1224. package/src/slash-commands/types.ts +139 -0
  1225. package/src/ssh/config-writer.ts +183 -0
  1226. package/src/ssh/connection-manager.ts +667 -0
  1227. package/src/ssh/file-transfer.ts +214 -0
  1228. package/src/ssh/sshfs-mount.ts +163 -0
  1229. package/src/ssh/utils.ts +51 -0
  1230. package/src/startup-splash.ts +19 -0
  1231. package/src/stt/asr-client.ts +401 -0
  1232. package/src/stt/asr-protocol.ts +65 -0
  1233. package/src/stt/asr-worker.ts +603 -0
  1234. package/src/stt/downloader.ts +142 -0
  1235. package/src/stt/endpointer.ts +259 -0
  1236. package/src/stt/index.ts +6 -0
  1237. package/src/stt/models.ts +150 -0
  1238. package/src/stt/sherpa-runtime.ts +71 -0
  1239. package/src/stt/stt-controller.ts +320 -0
  1240. package/src/stt/submit-trigger.ts +74 -0
  1241. package/src/subprocess/worker-client.ts +463 -0
  1242. package/src/subprocess/worker-runtime.ts +494 -0
  1243. package/src/system-prompt.ts +921 -0
  1244. package/src/task/agents.ts +170 -0
  1245. package/src/task/commands.ts +132 -0
  1246. package/src/task/discovery.ts +145 -0
  1247. package/src/task/executor.ts +3431 -0
  1248. package/src/task/index.ts +1515 -0
  1249. package/src/task/isolation-ownership.ts +106 -0
  1250. package/src/task/isolation-runner.ts +444 -0
  1251. package/src/task/label.ts +40 -0
  1252. package/src/task/name-generator.ts +1577 -0
  1253. package/src/task/omp-command.ts +26 -0
  1254. package/src/task/output-manager.ts +115 -0
  1255. package/src/task/parallel.ts +221 -0
  1256. package/src/task/persisted-revive.ts +173 -0
  1257. package/src/task/prewalk.ts +6 -0
  1258. package/src/task/prompt-policy.ts +8 -0
  1259. package/src/task/provider-concurrency.ts +100 -0
  1260. package/src/task/read-only-policy.ts +27 -0
  1261. package/src/task/render.ts +1830 -0
  1262. package/src/task/renderer.ts +14 -0
  1263. package/src/task/repair-args.ts +118 -0
  1264. package/src/task/spawn-policy.ts +72 -0
  1265. package/src/task/structured-subagent.ts +676 -0
  1266. package/src/task/subprocess-tool-registry.ts +88 -0
  1267. package/src/task/types.ts +555 -0
  1268. package/src/task/worktree.ts +967 -0
  1269. package/src/task/yield-assembly.ts +198 -0
  1270. package/src/telemetry-export.ts +504 -0
  1271. package/src/thinking.ts +377 -0
  1272. package/src/tiny/device.ts +111 -0
  1273. package/src/tiny/dtype.ts +101 -0
  1274. package/src/tiny/message-preproc.ts +155 -0
  1275. package/src/tiny/models.ts +268 -0
  1276. package/src/tiny/text.ts +290 -0
  1277. package/src/tiny/title-client.ts +460 -0
  1278. package/src/tiny/title-protocol.ts +56 -0
  1279. package/src/tiny/worker.ts +354 -0
  1280. package/src/tools/acp-bridge.ts +125 -0
  1281. package/src/tools/approval.ts +245 -0
  1282. package/src/tools/ask.ts +1459 -0
  1283. package/src/tools/ast-edit.ts +718 -0
  1284. package/src/tools/ast-grep.ts +526 -0
  1285. package/src/tools/auto-generated-guard.ts +335 -0
  1286. package/src/tools/bash-interactive.ts +435 -0
  1287. package/src/tools/bash-interceptor.ts +148 -0
  1288. package/src/tools/bash-pty-selection.ts +14 -0
  1289. package/src/tools/bash-skill-urls.ts +350 -0
  1290. package/src/tools/bash.ts +1826 -0
  1291. package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
  1292. package/src/tools/browser/aria/aria-snapshot.ts +131 -0
  1293. package/src/tools/browser/attach.ts +219 -0
  1294. package/src/tools/browser/cmux/cmux-tab.ts +1531 -0
  1295. package/src/tools/browser/cmux/rpc.ts +200 -0
  1296. package/src/tools/browser/cmux/socket-client.ts +445 -0
  1297. package/src/tools/browser/launch.ts +939 -0
  1298. package/src/tools/browser/readable.ts +112 -0
  1299. package/src/tools/browser/registry.ts +447 -0
  1300. package/src/tools/browser/relay/bridge.ts +945 -0
  1301. package/src/tools/browser/relay/daemon.ts +117 -0
  1302. package/src/tools/browser/relay/extension-assets/background.js.txt +242 -0
  1303. package/src/tools/browser/relay/extension-assets/manifest.json.txt +14 -0
  1304. package/src/tools/browser/relay/extension-assets/options.html.txt +53 -0
  1305. package/src/tools/browser/relay/extension-assets/options.js.txt +23 -0
  1306. package/src/tools/browser/relay/kind.ts +41 -0
  1307. package/src/tools/browser/relay/protocol.ts +54 -0
  1308. package/src/tools/browser/relay/server.ts +141 -0
  1309. package/src/tools/browser/render.ts +229 -0
  1310. package/src/tools/browser/run-output.ts +76 -0
  1311. package/src/tools/browser/shared-daemon.ts +139 -0
  1312. package/src/tools/browser/tab-protocol.ts +123 -0
  1313. package/src/tools/browser/tab-supervisor.ts +1077 -0
  1314. package/src/tools/browser/tab-worker-entry.ts +29 -0
  1315. package/src/tools/browser/tab-worker.ts +1982 -0
  1316. package/src/tools/browser.ts +487 -0
  1317. package/src/tools/builtin-names.ts +67 -0
  1318. package/src/tools/checkpoint.ts +137 -0
  1319. package/src/tools/computer/exposure.ts +14 -0
  1320. package/src/tools/computer/protocol.ts +69 -0
  1321. package/src/tools/computer/supervisor.ts +409 -0
  1322. package/src/tools/computer/worker-entry.ts +39 -0
  1323. package/src/tools/computer/worker.ts +745 -0
  1324. package/src/tools/computer-renderer.ts +147 -0
  1325. package/src/tools/computer.ts +222 -0
  1326. package/src/tools/conflict-detect.ts +815 -0
  1327. package/src/tools/context.ts +49 -0
  1328. package/src/tools/debug.ts +1121 -0
  1329. package/src/tools/default-renderer.ts +154 -0
  1330. package/src/tools/essential-tools.ts +46 -0
  1331. package/src/tools/eval-backends.ts +34 -0
  1332. package/src/tools/eval-format/index.ts +24 -0
  1333. package/src/tools/eval-format/javascript.ts +952 -0
  1334. package/src/tools/eval-format/julia.ts +446 -0
  1335. package/src/tools/eval-format/python.ts +544 -0
  1336. package/src/tools/eval-format/ruby.ts +380 -0
  1337. package/src/tools/eval-render.ts +784 -0
  1338. package/src/tools/eval.ts +774 -0
  1339. package/src/tools/fetch.ts +1889 -0
  1340. package/src/tools/file-recorder.ts +35 -0
  1341. package/src/tools/fs-cache-invalidation.ts +28 -0
  1342. package/src/tools/gh-cache-invalidation.ts +175 -0
  1343. package/src/tools/gh-format.ts +12 -0
  1344. package/src/tools/gh-renderer.ts +484 -0
  1345. package/src/tools/gh.ts +3958 -0
  1346. package/src/tools/github-cache.ts +663 -0
  1347. package/src/tools/glob.ts +691 -0
  1348. package/src/tools/grep.ts +1920 -0
  1349. package/src/tools/grouped-file-output.ts +210 -0
  1350. package/src/tools/hub/index.ts +579 -0
  1351. package/src/tools/hub/jobs.ts +714 -0
  1352. package/src/tools/hub/launch.ts +690 -0
  1353. package/src/tools/hub/messaging.ts +735 -0
  1354. package/src/tools/hub/types.ts +117 -0
  1355. package/src/tools/image-gen.ts +1689 -0
  1356. package/src/tools/image-providers.ts +50 -0
  1357. package/src/tools/index.ts +736 -0
  1358. package/src/tools/inspect-image-renderer.ts +133 -0
  1359. package/src/tools/inspect-image.ts +316 -0
  1360. package/src/tools/json-tree.ts +260 -0
  1361. package/src/tools/jtd-to-json-schema.ts +219 -0
  1362. package/src/tools/jtd-to-typescript.ts +136 -0
  1363. package/src/tools/jtd-utils.ts +102 -0
  1364. package/src/tools/learn.ts +141 -0
  1365. package/src/tools/list-limit.ts +40 -0
  1366. package/src/tools/manage-skill.ts +102 -0
  1367. package/src/tools/match-line-format.ts +20 -0
  1368. package/src/tools/memory-edit.ts +61 -0
  1369. package/src/tools/memory-recall.ts +102 -0
  1370. package/src/tools/memory-reflect.ts +88 -0
  1371. package/src/tools/memory-render.ts +211 -0
  1372. package/src/tools/memory-retain.ts +89 -0
  1373. package/src/tools/output-meta.ts +860 -0
  1374. package/src/tools/output-schema-validator.ts +307 -0
  1375. package/src/tools/path-utils.ts +1489 -0
  1376. package/src/tools/plan-mode-guard.ts +155 -0
  1377. package/src/tools/puppeteer/00_stealth_tampering.txt +44 -0
  1378. package/src/tools/puppeteer/01_stealth_activity.txt +80 -0
  1379. package/src/tools/puppeteer/02_stealth_hairline.txt +57 -0
  1380. package/src/tools/puppeteer/03_stealth_botd.txt +380 -0
  1381. package/src/tools/puppeteer/04_stealth_iframe.txt +174 -0
  1382. package/src/tools/puppeteer/05_stealth_webgl.txt +233 -0
  1383. package/src/tools/puppeteer/06_stealth_screen.txt +260 -0
  1384. package/src/tools/puppeteer/07_stealth_fonts.txt +99 -0
  1385. package/src/tools/puppeteer/08_stealth_audio.txt +63 -0
  1386. package/src/tools/puppeteer/09_stealth_locale.txt +51 -0
  1387. package/src/tools/puppeteer/10_stealth_plugins.txt +212 -0
  1388. package/src/tools/puppeteer/11_stealth_hardware.txt +59 -0
  1389. package/src/tools/puppeteer/12_stealth_codecs.txt +42 -0
  1390. package/src/tools/puppeteer/13_stealth_worker.txt +235 -0
  1391. package/src/tools/read.ts +3762 -0
  1392. package/src/tools/render-utils.ts +926 -0
  1393. package/src/tools/renderers.ts +133 -0
  1394. package/src/tools/report-tool-issue.ts +568 -0
  1395. package/src/tools/resolve.ts +423 -0
  1396. package/src/tools/review.ts +103 -0
  1397. package/src/tools/run-scope.ts +417 -0
  1398. package/src/tools/security-scan.ts +287 -0
  1399. package/src/tools/shell-tokenize.ts +213 -0
  1400. package/src/tools/sqlite-reader.ts +884 -0
  1401. package/src/tools/terminal-output.ts +141 -0
  1402. package/src/tools/todo.ts +1226 -0
  1403. package/src/tools/tool-errors.ts +62 -0
  1404. package/src/tools/tool-result.ts +102 -0
  1405. package/src/tools/tool-timeouts.ts +38 -0
  1406. package/src/tools/tts.ts +266 -0
  1407. package/src/tools/vibe.ts +608 -0
  1408. package/src/tools/write.ts +1646 -0
  1409. package/src/tools/xdev.ts +560 -0
  1410. package/src/tools/yield.ts +486 -0
  1411. package/src/tts/downloader.ts +64 -0
  1412. package/src/tts/index.ts +10 -0
  1413. package/src/tts/models.ts +137 -0
  1414. package/src/tts/runtime.ts +21 -0
  1415. package/src/tts/speakable.ts +392 -0
  1416. package/src/tts/speech-enhancer.ts +206 -0
  1417. package/src/tts/streaming-player.ts +120 -0
  1418. package/src/tts/tts-client.ts +475 -0
  1419. package/src/tts/tts-protocol.ts +69 -0
  1420. package/src/tts/tts-worker.ts +434 -0
  1421. package/src/tts/vocalizer.ts +419 -0
  1422. package/src/tts/wav.ts +58 -0
  1423. package/src/tui/code-cell.ts +268 -0
  1424. package/src/tui/file-list.ts +55 -0
  1425. package/src/tui/hyperlink.ts +178 -0
  1426. package/src/tui/index.ts +13 -0
  1427. package/src/tui/output-block.ts +268 -0
  1428. package/src/tui/status-line.ts +54 -0
  1429. package/src/tui/tree-list.ts +172 -0
  1430. package/src/tui/types.ts +15 -0
  1431. package/src/tui/utils.ts +103 -0
  1432. package/src/tui/width-aware-text.ts +58 -0
  1433. package/src/utils/active-repo-context.ts +143 -0
  1434. package/src/utils/block-context.ts +312 -0
  1435. package/src/utils/changelog.ts +373 -0
  1436. package/src/utils/clipboard.ts +361 -0
  1437. package/src/utils/command-args.ts +74 -0
  1438. package/src/utils/commit-message-generator.ts +148 -0
  1439. package/src/utils/cpuprofile.ts +235 -0
  1440. package/src/utils/edit-mode.ts +61 -0
  1441. package/src/utils/enhanced-paste.ts +230 -0
  1442. package/src/utils/event-bus.ts +33 -0
  1443. package/src/utils/external-editor.ts +80 -0
  1444. package/src/utils/fetch-timeout.ts +10 -0
  1445. package/src/utils/file-display-mode.ts +44 -0
  1446. package/src/utils/file-mentions.ts +293 -0
  1447. package/src/utils/git.ts +2472 -0
  1448. package/src/utils/image-loading.ts +236 -0
  1449. package/src/utils/image-resize.ts +420 -0
  1450. package/src/utils/image-vision-fallback.ts +196 -0
  1451. package/src/utils/inspect-image-mode.ts +39 -0
  1452. package/src/utils/ipc.ts +38 -0
  1453. package/src/utils/jj.ts +422 -0
  1454. package/src/utils/lang-from-path.ts +251 -0
  1455. package/src/utils/late-cleanup.ts +17 -0
  1456. package/src/utils/local-date.ts +7 -0
  1457. package/src/utils/mac-file-urls.applescript +37 -0
  1458. package/src/utils/markit-cache.ts +166 -0
  1459. package/src/utils/markit.ts +223 -0
  1460. package/src/utils/mupdf-wasm-embed.ts +12 -0
  1461. package/src/utils/open.ts +126 -0
  1462. package/src/utils/profile-tree.ts +111 -0
  1463. package/src/utils/prompt-path.ts +3 -0
  1464. package/src/utils/qrcode.ts +535 -0
  1465. package/src/utils/sample-profile.ts +437 -0
  1466. package/src/utils/session-color.ts +142 -0
  1467. package/src/utils/shell-snapshot-fn-env.sh +63 -0
  1468. package/src/utils/shell-snapshot.ts +326 -0
  1469. package/src/utils/sixel.ts +69 -0
  1470. package/src/utils/thinking-display.ts +163 -0
  1471. package/src/utils/title-generator.ts +597 -0
  1472. package/src/utils/token-rate.ts +72 -0
  1473. package/src/utils/tool-choice.ts +50 -0
  1474. package/src/utils/tools-manager.ts +411 -0
  1475. package/src/utils/turndown.ts +82 -0
  1476. package/src/utils/zip.ts +1106 -0
  1477. package/src/vibe/runtime.ts +1684 -0
  1478. package/src/vibe/state.ts +4 -0
  1479. package/src/web/kagi.ts +305 -0
  1480. package/src/web/parallel.ts +354 -0
  1481. package/src/web/scrapers/artifacthub.ts +207 -0
  1482. package/src/web/scrapers/arxiv.ts +83 -0
  1483. package/src/web/scrapers/aur.ts +162 -0
  1484. package/src/web/scrapers/biorxiv.ts +133 -0
  1485. package/src/web/scrapers/bluesky.ts +262 -0
  1486. package/src/web/scrapers/brew.ts +172 -0
  1487. package/src/web/scrapers/cheatsh.ts +68 -0
  1488. package/src/web/scrapers/chocolatey.ts +196 -0
  1489. package/src/web/scrapers/choosealicense.ts +95 -0
  1490. package/src/web/scrapers/cisa-kev.ts +87 -0
  1491. package/src/web/scrapers/clojars.ts +154 -0
  1492. package/src/web/scrapers/coingecko.ts +177 -0
  1493. package/src/web/scrapers/crates-io.ts +97 -0
  1494. package/src/web/scrapers/crossref.ts +136 -0
  1495. package/src/web/scrapers/devto.ts +147 -0
  1496. package/src/web/scrapers/discogs.ts +306 -0
  1497. package/src/web/scrapers/discourse.ts +197 -0
  1498. package/src/web/scrapers/dockerhub.ts +138 -0
  1499. package/src/web/scrapers/docs-rs.ts +663 -0
  1500. package/src/web/scrapers/fdroid.ts +134 -0
  1501. package/src/web/scrapers/firefox-addons.ts +191 -0
  1502. package/src/web/scrapers/flathub.ts +223 -0
  1503. package/src/web/scrapers/github-gist.ts +58 -0
  1504. package/src/web/scrapers/github.ts +800 -0
  1505. package/src/web/scrapers/gitlab.ts +401 -0
  1506. package/src/web/scrapers/go-pkg.ts +266 -0
  1507. package/src/web/scrapers/hackage.ts +129 -0
  1508. package/src/web/scrapers/hackernews.ts +189 -0
  1509. package/src/web/scrapers/hex.ts +105 -0
  1510. package/src/web/scrapers/huggingface.ts +321 -0
  1511. package/src/web/scrapers/iacr.ts +89 -0
  1512. package/src/web/scrapers/index.ts +252 -0
  1513. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  1514. package/src/web/scrapers/lemmy.ts +203 -0
  1515. package/src/web/scrapers/lobsters.ts +175 -0
  1516. package/src/web/scrapers/mastodon.ts +292 -0
  1517. package/src/web/scrapers/maven.ts +138 -0
  1518. package/src/web/scrapers/mdn.ts +173 -0
  1519. package/src/web/scrapers/metacpan.ts +222 -0
  1520. package/src/web/scrapers/musicbrainz.ts +250 -0
  1521. package/src/web/scrapers/npm.ts +98 -0
  1522. package/src/web/scrapers/nuget.ts +183 -0
  1523. package/src/web/scrapers/nvd.ts +222 -0
  1524. package/src/web/scrapers/ollama.ts +239 -0
  1525. package/src/web/scrapers/open-vsx.ts +106 -0
  1526. package/src/web/scrapers/opencorporates.ts +292 -0
  1527. package/src/web/scrapers/openlibrary.ts +336 -0
  1528. package/src/web/scrapers/orcid.ts +286 -0
  1529. package/src/web/scrapers/osv.ts +176 -0
  1530. package/src/web/scrapers/packagist.ts +160 -0
  1531. package/src/web/scrapers/pub-dev.ts +143 -0
  1532. package/src/web/scrapers/pubmed.ts +211 -0
  1533. package/src/web/scrapers/pypi.ts +112 -0
  1534. package/src/web/scrapers/rawg.ts +110 -0
  1535. package/src/web/scrapers/readthedocs.ts +121 -0
  1536. package/src/web/scrapers/reddit.ts +95 -0
  1537. package/src/web/scrapers/repology.ts +251 -0
  1538. package/src/web/scrapers/rfc.ts +201 -0
  1539. package/src/web/scrapers/rubygems.ts +103 -0
  1540. package/src/web/scrapers/searchcode.ts +189 -0
  1541. package/src/web/scrapers/sec-edgar.ts +261 -0
  1542. package/src/web/scrapers/semantic-scholar.ts +171 -0
  1543. package/src/web/scrapers/snapcraft.ts +187 -0
  1544. package/src/web/scrapers/sourcegraph.ts +336 -0
  1545. package/src/web/scrapers/spdx.ts +108 -0
  1546. package/src/web/scrapers/spotify.ts +198 -0
  1547. package/src/web/scrapers/stackoverflow.ts +120 -0
  1548. package/src/web/scrapers/terraform.ts +277 -0
  1549. package/src/web/scrapers/tldr.ts +47 -0
  1550. package/src/web/scrapers/twitter.ts +94 -0
  1551. package/src/web/scrapers/types.ts +354 -0
  1552. package/src/web/scrapers/utils.ts +109 -0
  1553. package/src/web/scrapers/vimeo.ts +133 -0
  1554. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  1555. package/src/web/scrapers/w3c.ts +156 -0
  1556. package/src/web/scrapers/wikidata.ts +344 -0
  1557. package/src/web/scrapers/wikipedia.ts +84 -0
  1558. package/src/web/scrapers/youtube.ts +325 -0
  1559. package/src/web/search/index.ts +393 -0
  1560. package/src/web/search/provider.ts +272 -0
  1561. package/src/web/search/providers/anthropic.ts +406 -0
  1562. package/src/web/search/providers/base.ts +112 -0
  1563. package/src/web/search/providers/brave.ts +181 -0
  1564. package/src/web/search/providers/browser-headers.ts +82 -0
  1565. package/src/web/search/providers/browser-page.ts +125 -0
  1566. package/src/web/search/providers/codex.ts +788 -0
  1567. package/src/web/search/providers/duckduckgo.ts +382 -0
  1568. package/src/web/search/providers/ecosia.ts +183 -0
  1569. package/src/web/search/providers/exa.ts +513 -0
  1570. package/src/web/search/providers/firecrawl.ts +213 -0
  1571. package/src/web/search/providers/gemini.ts +634 -0
  1572. package/src/web/search/providers/google.ts +195 -0
  1573. package/src/web/search/providers/jina.ts +144 -0
  1574. package/src/web/search/providers/kagi.ts +98 -0
  1575. package/src/web/search/providers/kimi.ts +221 -0
  1576. package/src/web/search/providers/mojeek.ts +220 -0
  1577. package/src/web/search/providers/parallel.ts +186 -0
  1578. package/src/web/search/providers/perplexity-auth.ts +142 -0
  1579. package/src/web/search/providers/perplexity.ts +997 -0
  1580. package/src/web/search/providers/public.ts +199 -0
  1581. package/src/web/search/providers/searxng.ts +467 -0
  1582. package/src/web/search/providers/startpage.ts +225 -0
  1583. package/src/web/search/providers/synthetic.ts +126 -0
  1584. package/src/web/search/providers/tavily.ts +244 -0
  1585. package/src/web/search/providers/tinyfish.ts +166 -0
  1586. package/src/web/search/providers/utils.ts +130 -0
  1587. package/src/web/search/providers/xai.ts +372 -0
  1588. package/src/web/search/providers/zai.ts +452 -0
  1589. package/src/web/search/query.ts +850 -0
  1590. package/src/web/search/render.ts +262 -0
  1591. package/src/web/search/types.ts +518 -0
  1592. package/src/web/search/utils.ts +17 -0
  1593. package/src/workspace-tree.ts +326 -0
@@ -0,0 +1,2472 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { $which, hasFsCode, isEisdir, isEnoent, isEnotdir, Snowflake } from "@oh-my-pi/pi-utils";
5
+ import type { Subprocess } from "bun";
6
+ import {
7
+ parseDiffHunks as parseCommitDiffHunks,
8
+ parseFileDiffs,
9
+ parseFileHunks,
10
+ parseNumstat,
11
+ } from "../commit/git/diff";
12
+ import type { FileDiff, FileHunks, NumstatEntry } from "../commit/types";
13
+ import { ToolAbortError, ToolError, throwIfAborted } from "../tools/tool-errors";
14
+
15
+ // ════════════════════════════════════════════════════════════════════════════
16
+ // Types
17
+ // ════════════════════════════════════════════════════════════════════════════
18
+
19
+ export interface GitCommandResult {
20
+ exitCode: number;
21
+ stdout: string;
22
+ stderr: string;
23
+ }
24
+
25
+ export interface GitRepository {
26
+ commonDir: string;
27
+ gitDir: string;
28
+ gitEntryPath: string;
29
+ headPath: string;
30
+ repoRoot: string;
31
+ isReftable?: boolean;
32
+ }
33
+
34
+ export interface GitStatusSummary {
35
+ staged: number;
36
+ unstaged: number;
37
+ untracked: number;
38
+ }
39
+
40
+ export type HunkSelection = {
41
+ path: string;
42
+ hunks: { type: "all" } | { type: "indices"; indices: number[] } | { type: "lines"; start: number; end: number };
43
+ };
44
+
45
+ export interface StageHunksOptions {
46
+ readonly diffCached?: boolean;
47
+ readonly rawDiff?: string;
48
+ readonly signal?: AbortSignal;
49
+ }
50
+ export interface HunkSelectionValidationError {
51
+ readonly path: string;
52
+ readonly message: string;
53
+ }
54
+
55
+ export interface DiffOptions {
56
+ readonly allowFailure?: boolean;
57
+ readonly base?: string;
58
+ readonly binary?: boolean;
59
+ readonly cached?: boolean;
60
+ readonly env?: Record<string, string | undefined>;
61
+ readonly files?: readonly string[];
62
+ readonly head?: string;
63
+ readonly nameOnly?: boolean;
64
+ readonly noIndex?: { left: string; right: string };
65
+ readonly numstat?: boolean;
66
+ readonly signal?: AbortSignal;
67
+ readonly stat?: boolean;
68
+ }
69
+
70
+ export interface StatusOptions {
71
+ readonly pathspecs?: readonly string[];
72
+ readonly porcelainV1?: boolean;
73
+ readonly signal?: AbortSignal;
74
+ readonly untrackedFiles?: "all" | "no" | "normal";
75
+ readonly z?: boolean;
76
+ }
77
+
78
+ export interface CommitAuthor {
79
+ readonly date?: string;
80
+ readonly email: string;
81
+ readonly name: string;
82
+ }
83
+
84
+ export interface CommitDetails {
85
+ readonly author: CommitAuthor;
86
+ readonly message: string;
87
+ }
88
+
89
+ export interface CommitOptions {
90
+ readonly allowEmpty?: boolean;
91
+ readonly author?: CommitAuthor;
92
+ readonly files?: readonly string[];
93
+ readonly signal?: AbortSignal;
94
+ }
95
+
96
+ export interface PushOptions {
97
+ readonly forceWithLease?: boolean;
98
+ readonly refspec?: string;
99
+ readonly remote?: string;
100
+ readonly signal?: AbortSignal;
101
+ }
102
+
103
+ export interface PatchOptions {
104
+ readonly cached?: boolean;
105
+ readonly check?: boolean;
106
+ readonly env?: Record<string, string | undefined>;
107
+ readonly reverse?: boolean;
108
+ readonly threeWay?: boolean;
109
+ readonly signal?: AbortSignal;
110
+ }
111
+
112
+ export interface RestoreOptions {
113
+ readonly files?: readonly string[];
114
+ readonly signal?: AbortSignal;
115
+ readonly source?: string;
116
+ readonly staged?: boolean;
117
+ readonly worktree?: boolean;
118
+ }
119
+
120
+ export interface FetchOptions {
121
+ readonly signal?: AbortSignal;
122
+ /** Deadline for the network transfer. Defaults to {@link GIT_NETWORK_TIMEOUT_MS}. */
123
+ readonly timeoutMs?: number;
124
+ }
125
+
126
+ export interface CloneOptions {
127
+ readonly ref?: string;
128
+ readonly sha?: string;
129
+ readonly signal?: AbortSignal;
130
+ /** Deadline for the network transfer. Defaults to {@link GIT_NETWORK_TIMEOUT_MS}. */
131
+ readonly timeoutMs?: number;
132
+ }
133
+
134
+ interface GitHeadBase extends GitRepository {
135
+ headContent: string;
136
+ }
137
+
138
+ export interface GitRefHead extends GitHeadBase {
139
+ branchName: string | null;
140
+ commit: string | null;
141
+ kind: "ref";
142
+ ref: string;
143
+ }
144
+
145
+ export interface GitDetachedHead extends GitHeadBase {
146
+ commit: string | null;
147
+ kind: "detached";
148
+ }
149
+
150
+ export type GitHeadState = GitRefHead | GitDetachedHead;
151
+
152
+ export interface GitWorktreeEntry {
153
+ branch?: string;
154
+ detached: boolean;
155
+ head?: string;
156
+ path: string;
157
+ }
158
+
159
+ // ════════════════════════════════════════════════════════════════════════════
160
+ // Error
161
+ // ════════════════════════════════════════════════════════════════════════════
162
+
163
+ export class GitCommandError extends Error {
164
+ readonly args: readonly string[];
165
+ readonly result: GitCommandResult;
166
+
167
+ constructor(args: readonly string[], result: GitCommandResult) {
168
+ super(formatCommandFailure(args, result));
169
+ this.name = "GitCommandError";
170
+ this.args = [...args];
171
+ this.result = result;
172
+ }
173
+ }
174
+
175
+ // ════════════════════════════════════════════════════════════════════════════
176
+ // Internal: Core execution
177
+ // ════════════════════════════════════════════════════════════════════════════
178
+
179
+ const NO_OPTIONAL_LOCKS = "--no-optional-locks";
180
+ const HEAD_REF_PREFIX = "ref:";
181
+ const LOCAL_BRANCH_PREFIX = "refs/heads/";
182
+ const DEFAULT_BRANCH_REFS = ["refs/remotes/origin/HEAD", "refs/remotes/upstream/HEAD"] as const;
183
+ const SHORT_LIVED_GIT_CONFIG: readonly (readonly [key: string, value: string])[] = [
184
+ ["core.fsmonitor", "false"],
185
+ ["core.untrackedCache", "false"],
186
+ ];
187
+ const AMBIENT_GIT_ENV = {
188
+ GIT_DIR: undefined,
189
+ GIT_COMMON_DIR: undefined,
190
+ GIT_WORK_TREE: undefined,
191
+ GIT_INDEX_FILE: undefined,
192
+ GIT_OBJECT_DIRECTORY: undefined,
193
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined,
194
+ } satisfies Record<string, undefined>;
195
+
196
+ const GIT_NON_INTERACTIVE_ENV = {
197
+ GIT_ASKPASS: "true",
198
+ GIT_EDITOR: "true",
199
+ GIT_TERMINAL_PROMPT: "0",
200
+ LC_ALL: undefined,
201
+ LC_MESSAGES: "C",
202
+ SSH_ASKPASS: "/usr/bin/false",
203
+ } satisfies Record<string, string | undefined>;
204
+ const GH_NON_INTERACTIVE_ENV = {
205
+ ...GIT_NON_INTERACTIVE_ENV,
206
+ GH_PROMPT_DISABLED: "1",
207
+ } satisfies Record<string, string | undefined>;
208
+
209
+ /** Default deadline for git and gh subprocesses spawned by the coding agent. */
210
+ export const GIT_COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
211
+ /**
212
+ * Default deadline for git subprocesses that perform network transfers
213
+ * (`clone`/`fetch`). Large-repo transfers legitimately outlive
214
+ * {@link GIT_COMMAND_TIMEOUT_MS}, so they get a wider deadline; local plumbing
215
+ * commands keep the short one.
216
+ */
217
+ export const GIT_NETWORK_TIMEOUT_MS = 30 * 60 * 1000;
218
+ /** Maximum captured stdout or stderr bytes retained from git and gh subprocesses. */
219
+ export const GIT_COMMAND_OUTPUT_LIMIT_BYTES = 8 * 1024 * 1024;
220
+ /**
221
+ * Deadline for synchronous git plumbing commands launched via
222
+ * {@link gitSpawnSyncText}. These run on the render path (e.g. reftable HEAD
223
+ * resolution), so the deadline is short: a command that has not exited by then
224
+ * is killed and reported as {@link GIT_COMMAND_TIMEOUT_EXIT_CODE} so the caller
225
+ * degrades instead of freezing the UI indefinitely.
226
+ */
227
+ export const GIT_SPAWN_SYNC_TIMEOUT_MS = 5_000;
228
+
229
+ const GIT_COMMAND_TIMEOUT_EXIT_CODE = 124;
230
+ // Exit code returned when the `git` binary cannot be launched at all (spawn
231
+ // ENOENT). Mirrors the POSIX "command not found" code so read-only callers that
232
+ // degrade on any non-zero exit treat a missing git the same as a failed
233
+ // invocation instead of letting the raw spawn ENOENT escape as an unhandled
234
+ // rejection.
235
+ const GIT_SPAWN_ENOENT_EXIT_CODE = 127;
236
+ const GIT_OUTPUT_TRUNCATED_MARKER = "\n[git subprocess output truncated after 8 MiB]\n";
237
+ const GIT_COMMAND_TERMINATE_GRACE_MS = 5_000;
238
+
239
+ type CommandName = "git" | "gh";
240
+
241
+ function resolveTimeoutMs(timeoutMs: number | undefined, fallback: number = GIT_COMMAND_TIMEOUT_MS): number {
242
+ if (timeoutMs === undefined) return fallback;
243
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) return fallback;
244
+ return Math.trunc(timeoutMs);
245
+ }
246
+
247
+ function resolveOutputLimit(maxOutputBytes: number | undefined): number {
248
+ if (maxOutputBytes === undefined) return GIT_COMMAND_OUTPUT_LIMIT_BYTES;
249
+ if (!Number.isFinite(maxOutputBytes) || maxOutputBytes < 0) return GIT_COMMAND_OUTPUT_LIMIT_BYTES;
250
+ return Math.trunc(maxOutputBytes);
251
+ }
252
+
253
+ function formatCommandLabel(command: CommandName, args: readonly string[]): string {
254
+ return `${command} ${args.join(" ")}`.trim();
255
+ }
256
+
257
+ async function waitForChildExit(child: Subprocess, timeoutMs: number): Promise<boolean> {
258
+ if (timeoutMs <= 0) return false;
259
+ const timeout = Promise.withResolvers<false>();
260
+ const timer = setTimeout(() => timeout.resolve(false), timeoutMs);
261
+ timer.unref?.();
262
+ try {
263
+ return await Promise.race([
264
+ child.exited.then(
265
+ () => true,
266
+ () => true,
267
+ ),
268
+ timeout.promise,
269
+ ]);
270
+ } finally {
271
+ clearTimeout(timer);
272
+ }
273
+ }
274
+
275
+ async function terminateTimedOutChild(child: Subprocess): Promise<void> {
276
+ child.kill("SIGTERM");
277
+ if (await waitForChildExit(child, GIT_COMMAND_TERMINATE_GRACE_MS)) return;
278
+ child.kill("SIGKILL");
279
+ await waitForChildExit(child, GIT_COMMAND_TERMINATE_GRACE_MS);
280
+ }
281
+
282
+ async function waitForExitWithTimeout(
283
+ child: Subprocess,
284
+ commandLabel: string,
285
+ timeoutMs: number,
286
+ ): Promise<{ exitCode: number | null; timedOut: false } | { timedOut: true; stderr: string }> {
287
+ if (timeoutMs === 0) {
288
+ await terminateTimedOutChild(child);
289
+ return { timedOut: true, stderr: `${commandLabel} timed out after 0ms` };
290
+ }
291
+ const timeout = Promise.withResolvers<"timeout">();
292
+ const timer = setTimeout(() => timeout.resolve("timeout"), timeoutMs);
293
+ timer.unref?.();
294
+ try {
295
+ const result = await Promise.race([
296
+ child.exited.then(exitCode => ({ kind: "exit" as const, exitCode })),
297
+ timeout.promise.then(() => ({ kind: "timeout" as const })),
298
+ ]);
299
+ if (result.kind === "exit") {
300
+ return { timedOut: false, exitCode: result.exitCode };
301
+ }
302
+ await terminateTimedOutChild(child);
303
+ return { timedOut: true, stderr: `${commandLabel} timed out after ${timeoutMs}ms` };
304
+ } finally {
305
+ clearTimeout(timer);
306
+ }
307
+ }
308
+
309
+ async function readCappedText(stream: ReadableStream<Uint8Array>, maxBytes: number): Promise<string> {
310
+ const reader = stream.getReader();
311
+ const decoder = new TextDecoder();
312
+ const chunks: string[] = [];
313
+ let remaining = maxBytes;
314
+ let truncated = false;
315
+ try {
316
+ while (true) {
317
+ const { done, value } = await reader.read();
318
+ if (done) break;
319
+ if (!truncated && value.length <= remaining) {
320
+ chunks.push(decoder.decode(value, { stream: true }));
321
+ remaining -= value.length;
322
+ continue;
323
+ }
324
+ if (!truncated && remaining > 0) {
325
+ chunks.push(decoder.decode(value.subarray(0, remaining), { stream: true }));
326
+ remaining = 0;
327
+ }
328
+ truncated = true;
329
+ }
330
+ chunks.push(decoder.decode());
331
+ if (truncated) chunks.push(GIT_OUTPUT_TRUNCATED_MARKER);
332
+ return chunks.join("");
333
+ } finally {
334
+ reader.releaseLock();
335
+ }
336
+ }
337
+
338
+ async function cancelOutput(stream: ReadableStream<Uint8Array>): Promise<void> {
339
+ try {
340
+ await stream.cancel();
341
+ } catch {
342
+ // Best-effort cleanup after a timeout; the subprocess has already been signaled.
343
+ }
344
+ }
345
+
346
+ async function collectSubprocessResult(
347
+ command: CommandName,
348
+ args: readonly string[],
349
+ child: Subprocess,
350
+ options: Pick<CommandOptions, "maxOutputBytes" | "timeoutMs"> = {},
351
+ ): Promise<GitCommandResult> {
352
+ const stdoutStream = child.stdout;
353
+ const stderrStream = child.stderr;
354
+ if (!(stdoutStream instanceof ReadableStream) || !(stderrStream instanceof ReadableStream)) {
355
+ throw new Error(`Failed to capture ${command} command output.`);
356
+ }
357
+ const maxOutputBytes = resolveOutputLimit(options.maxOutputBytes);
358
+ const stdoutPromise = readCappedText(stdoutStream, maxOutputBytes);
359
+ const stderrPromise = readCappedText(stderrStream, maxOutputBytes);
360
+ const exit = await waitForExitWithTimeout(
361
+ child,
362
+ formatCommandLabel(command, args),
363
+ resolveTimeoutMs(options.timeoutMs),
364
+ );
365
+ if (exit.timedOut) {
366
+ void stdoutPromise.catch(() => undefined);
367
+ void stderrPromise.catch(() => undefined);
368
+ await Promise.all([cancelOutput(stdoutStream), cancelOutput(stderrStream)]);
369
+ return { exitCode: GIT_COMMAND_TIMEOUT_EXIT_CODE, stdout: "", stderr: exit.stderr };
370
+ }
371
+ const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
372
+ return { exitCode: exit.exitCode ?? 0, stdout, stderr };
373
+ }
374
+
375
+ interface CommandOptions {
376
+ readonly env?: Record<string, string | undefined>;
377
+ readonly maxOutputBytes?: number;
378
+ readonly readOnly?: boolean;
379
+ readonly signal?: AbortSignal;
380
+ readonly stdin?: string | Uint8Array | ArrayBuffer | SharedArrayBuffer;
381
+ readonly timeoutMs?: number;
382
+ }
383
+
384
+ function normalizeStdin(input: CommandOptions["stdin"]): "ignore" | Uint8Array {
385
+ if (input === undefined) return "ignore";
386
+ if (typeof input === "string") return new TextEncoder().encode(input);
387
+ if (input instanceof Uint8Array) return input;
388
+ return new Uint8Array(input);
389
+ }
390
+
391
+ function buildNonInteractiveEnv(
392
+ env: Record<string, string | undefined>,
393
+ pinnedEnv: Record<string, string | undefined>,
394
+ ): Record<string, string | undefined> {
395
+ const preservedCharacterLocale =
396
+ env.LC_ALL !== undefined && /(?:^|[._-])utf-?8(?:$|[.@_-])/i.test(env.LC_ALL) ? env.LC_ALL : undefined;
397
+ return {
398
+ ...env,
399
+ ...(preservedCharacterLocale === undefined ? {} : { LC_CTYPE: preservedCharacterLocale }),
400
+ ...pinnedEnv,
401
+ };
402
+ }
403
+
404
+ function buildGitEnv(overrides?: Record<string, string | undefined>): Record<string, string | undefined> {
405
+ return buildNonInteractiveEnv(
406
+ {
407
+ ...process.env,
408
+ GIT_OPTIONAL_LOCKS: "0",
409
+ ...AMBIENT_GIT_ENV,
410
+ ...overrides,
411
+ },
412
+ GIT_NON_INTERACTIVE_ENV,
413
+ );
414
+ }
415
+
416
+ function buildGhEnv(): Record<string, string | undefined> {
417
+ return buildNonInteractiveEnv({ ...process.env }, GH_NON_INTERACTIVE_ENV);
418
+ }
419
+
420
+ function ensureAvailable(): void {
421
+ if (!$which("git")) {
422
+ throw new Error("git is not installed.");
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Launch a `git` plumbing command synchronously and decode stdout. Returns the
428
+ * exit code plus trimmed stdout; a missing `git` binary (spawn ENOENT) is
429
+ * reported as {@link GIT_SPAWN_ENOENT_EXIT_CODE} so sync read-only callers
430
+ * degrade to `null` instead of throwing an uncaught error during rendering.
431
+ *
432
+ * A deadline ({@link GIT_SPAWN_SYNC_TIMEOUT_MS}) is enforced so a pathological
433
+ * git invocation (lock contention, NFS stall, …) cannot hang the render path
434
+ * indefinitely: a child killed by the deadline is reported as
435
+ * {@link GIT_COMMAND_TIMEOUT_EXIT_CODE} rather than a successful exit.
436
+ */
437
+ function gitSpawnSyncText(
438
+ cwd: string,
439
+ args: readonly string[],
440
+ timeoutMs: number = GIT_SPAWN_SYNC_TIMEOUT_MS,
441
+ ): { exitCode: number; stdout: string } {
442
+ const commandArgs = withShortLivedGitConfig(withNoOptionalLocks(args));
443
+ try {
444
+ const result = Bun.spawnSync(["git", ...commandArgs], {
445
+ cwd,
446
+ env: buildGitEnv(),
447
+ stdout: "pipe",
448
+ stderr: "pipe",
449
+ windowsHide: true,
450
+ timeout: timeoutMs,
451
+ });
452
+ // Bun's timeout marker is authoritative even when process cleanup reports
453
+ // exit code zero, so render-path callers never trust partial output.
454
+ const exitCode = result.exitedDueToTimeout
455
+ ? GIT_COMMAND_TIMEOUT_EXIT_CODE
456
+ : (result.exitCode ?? GIT_COMMAND_TIMEOUT_EXIT_CODE);
457
+ return { exitCode, stdout: new TextDecoder().decode(result.stdout).trim() };
458
+ } catch (err) {
459
+ if (isEnoent(err)) return { exitCode: GIT_SPAWN_ENOENT_EXIT_CODE, stdout: "" };
460
+ throw err;
461
+ }
462
+ }
463
+
464
+ function formatCommandFailure(
465
+ args: readonly string[],
466
+ result: Pick<GitCommandResult, "exitCode" | "stdout" | "stderr">,
467
+ ): string {
468
+ const stderr = result.stderr.trim();
469
+ if (stderr) return stderr;
470
+ const stdout = result.stdout.trim();
471
+ if (stdout) return stdout;
472
+ return `git ${args.join(" ")} failed with exit code ${result.exitCode}`;
473
+ }
474
+
475
+ async function git(cwd: string, args: readonly string[], options: CommandOptions = {}): Promise<GitCommandResult> {
476
+ const commandArgs = withShortLivedGitConfig(options.readOnly ? withNoOptionalLocks(args) : [...args]);
477
+ let child: Subprocess;
478
+ try {
479
+ child = Bun.spawn(["git", ...commandArgs], {
480
+ cwd,
481
+ env: buildGitEnv(options.env),
482
+ signal: options.signal,
483
+ stdin: normalizeStdin(options.stdin),
484
+ stdout: "pipe",
485
+ stderr: "pipe",
486
+ windowsHide: true,
487
+ });
488
+ } catch (err) {
489
+ if (isEnoent(err)) {
490
+ // A deleted/nonexistent cwd also surfaces as a spawn ENOENT; only blame
491
+ // the binary when the working directory actually exists.
492
+ const stderr = fs.existsSync(cwd) ? "git is not installed." : `working directory does not exist: ${cwd}`;
493
+ return { exitCode: GIT_SPAWN_ENOENT_EXIT_CODE, stdout: "", stderr };
494
+ }
495
+ throw err;
496
+ }
497
+
498
+ return await collectSubprocessResult("git", commandArgs, child, options);
499
+ }
500
+
501
+ function withNoOptionalLocks(args: readonly string[]): string[] {
502
+ if (args.includes(NO_OPTIONAL_LOCKS)) return [...args];
503
+ return [NO_OPTIONAL_LOCKS, ...args];
504
+ }
505
+
506
+ function withShortLivedGitConfig(args: readonly string[]): string[] {
507
+ const prefix: string[] = [];
508
+ for (const [key, value] of SHORT_LIVED_GIT_CONFIG) {
509
+ if (hasGitConfig(args, key, value)) continue;
510
+ prefix.push("-c", `${key}=${value}`);
511
+ }
512
+ return [...prefix, ...args];
513
+ }
514
+
515
+ function hasGitConfig(args: readonly string[], key: string, value: string): boolean {
516
+ const expected = `${key}=${value}`;
517
+ for (let index = 0; index < args.length - 1; index += 1) {
518
+ if (args[index] === "-c" && args[index + 1] === expected) {
519
+ return true;
520
+ }
521
+ }
522
+ return false;
523
+ }
524
+
525
+ async function runChecked(
526
+ cwd: string,
527
+ args: readonly string[],
528
+ options: CommandOptions = {},
529
+ ): Promise<GitCommandResult> {
530
+ ensureAvailable();
531
+ const result = await git(cwd, args, options);
532
+ if (result.exitCode !== 0) {
533
+ throw new GitCommandError(args, result);
534
+ }
535
+ return result;
536
+ }
537
+
538
+ async function runEffect(cwd: string, args: readonly string[], options: CommandOptions = {}): Promise<void> {
539
+ await runChecked(cwd, args, options);
540
+ }
541
+
542
+ async function runText(cwd: string, args: readonly string[], options: CommandOptions = {}): Promise<string> {
543
+ return (await runChecked(cwd, args, options)).stdout;
544
+ }
545
+
546
+ async function tryText(
547
+ cwd: string,
548
+ args: readonly string[],
549
+ options: CommandOptions = {},
550
+ ): Promise<string | undefined> {
551
+ ensureAvailable();
552
+ const result = await git(cwd, args, options);
553
+ if (result.exitCode !== 0) return undefined;
554
+ return result.stdout;
555
+ }
556
+
557
+ // ════════════════════════════════════════════════════════════════════════════
558
+ // Internal: per-repo write serialization
559
+ // ════════════════════════════════════════════════════════════════════════════
560
+
561
+ // Git uses lock files (`.git/config.lock`, commit-graph chain locks,
562
+ // `packed-refs.lock`, …) for many of its mutating operations. Each is created
563
+ // O_EXCL with no waiter, so concurrent in-process git invocations against the
564
+ // same repository fail immediately rather than block. Worktrees share the
565
+ // primary repo's `.git` directory, so racing across worktrees has the same
566
+ // failure mode. We give callers a single per-repo serialization point keyed by
567
+ // the primary repo root: any block that mutates repo state should hold this
568
+ // lock so unrelated callers cannot collide on git's internal locks.
569
+ const repoWriteChain = new Map<string, Promise<unknown>>();
570
+
571
+ /**
572
+ * Serialize an async block that mutates a git repository against other
573
+ * in-process callers operating on the same repository. The lock is keyed by
574
+ * the primary repo root so worktrees of the same repo share a single queue.
575
+ * Failures in one block do not poison the queue for the next caller.
576
+ *
577
+ * Not reentrant: do NOT nest acquisitions for the same repo. Helpers in this
578
+ * module never auto-acquire — callers wrap the critical section themselves.
579
+ */
580
+ export async function withRepoLock<T>(cwd: string, fn: () => Promise<T>, signal?: AbortSignal): Promise<T> {
581
+ const key = (await repo.primaryRoot(cwd, signal)) ?? cwd;
582
+ const prior = repoWriteChain.get(key);
583
+ const run = (async () => {
584
+ if (prior) {
585
+ try {
586
+ await prior;
587
+ } catch {
588
+ // A prior caller failing must not block us from running.
589
+ }
590
+ }
591
+ throwIfAborted(signal);
592
+ return fn();
593
+ })();
594
+ repoWriteChain.set(key, run);
595
+ try {
596
+ return await run;
597
+ } finally {
598
+ if (repoWriteChain.get(key) === run) repoWriteChain.delete(key);
599
+ }
600
+ }
601
+
602
+ function splitLines(text: string): string[] {
603
+ return text
604
+ .split("\n")
605
+ .map(line => line.trim())
606
+ .filter(Boolean);
607
+ }
608
+
609
+ function trimScalar(text: string | undefined): string | undefined {
610
+ const trimmed = text?.trim();
611
+ return trimmed || undefined;
612
+ }
613
+
614
+ // ════════════════════════════════════════════════════════════════════════════
615
+ // Internal: Argument builders
616
+ // ════════════════════════════════════════════════════════════════════════════
617
+
618
+ function buildDiffArgs(options: DiffOptions): string[] {
619
+ const args = ["diff"];
620
+ if (options.binary) args.push("--binary");
621
+ if (options.cached) args.push("--cached");
622
+ if (options.nameOnly) args.push("--name-only");
623
+ if (options.stat) args.push("--stat");
624
+ if (options.numstat) args.push("--numstat");
625
+ if (options.noIndex) {
626
+ args.push("--no-index", options.noIndex.left, options.noIndex.right);
627
+ return args;
628
+ }
629
+ if (options.base) {
630
+ args.push(options.base);
631
+ if (options.head) args.push(options.head);
632
+ }
633
+ if (options.files?.length) args.push("--", ...options.files);
634
+ return args;
635
+ }
636
+
637
+ function buildApplyArgs(patchPath: string, options: PatchOptions): string[] {
638
+ const args = ["apply"];
639
+ if (options.check) args.push("--check");
640
+ if (options.cached) args.push("--cached");
641
+ if (options.reverse) args.push("--reverse");
642
+ if (options.threeWay) args.push("--3way");
643
+ args.push("--binary", patchPath);
644
+ return args;
645
+ }
646
+
647
+ async function writeTempPatch(content: string): Promise<string> {
648
+ const tempPath = path.join(os.tmpdir(), `omp-git-patch-${Snowflake.next()}.patch`);
649
+ await Bun.write(tempPath, content);
650
+ return tempPath;
651
+ }
652
+
653
+ // ════════════════════════════════════════════════════════════════════════════
654
+ // Internal: Repository resolution
655
+ // ════════════════════════════════════════════════════════════════════════════
656
+
657
+ type EntryType = "directory" | "file";
658
+
659
+ function shouldRetry(err: unknown, n: number) {
660
+ if (isEnoent(err) || isEisdir(err) || isEnotdir(err) || hasFsCode(err, "ENFILE") || hasFsCode(err, "EMFILE"))
661
+ return false;
662
+ if (hasFsCode(err, "EINTR")) return n < EINTR_MAX_RETRIES;
663
+ if (n > EINTR_MAX_RETRIES) throw err;
664
+ throw err;
665
+ }
666
+
667
+ /**
668
+ * Bounded retry for synchronous I/O against `EINTR`. POSIX permits short syscalls
669
+ * to be interrupted by signals; when that happens libc traditionally retries.
670
+ * Node's sync wrappers surface the raw `EINTR` so we replicate the retry locally.
671
+ * Any other error (and persistent EINTR after `EINTR_MAX_RETRIES`) is rethrown
672
+ * for the caller's normal "optional metadata" classifier to handle.
673
+ */
674
+ const EINTR_MAX_RETRIES = 3;
675
+ function retryOnEintrSync<T>(op: () => T): T | null {
676
+ for (let attempt = 0; attempt <= EINTR_MAX_RETRIES; attempt += 1) {
677
+ try {
678
+ return op();
679
+ } catch (err) {
680
+ if (shouldRetry(err, attempt)) continue;
681
+ return null;
682
+ }
683
+ }
684
+ throw new Error("retryOnEintrSync: exhausted without resolution");
685
+ }
686
+ async function retryOnEintr<T>(op: () => Promise<T>): Promise<T | null> {
687
+ for (let attempt = 0; attempt <= EINTR_MAX_RETRIES; attempt += 1) {
688
+ try {
689
+ return await op();
690
+ } catch (err) {
691
+ if (shouldRetry(err, attempt)) continue;
692
+ return null;
693
+ }
694
+ }
695
+ throw new Error("retryOnEintr: exhausted without resolution");
696
+ }
697
+
698
+ function getEntryTypeSync(gitEntryPath: string): EntryType | null {
699
+ return retryOnEintrSync(() => {
700
+ const stat = fs.statSync(gitEntryPath);
701
+ if (stat.isDirectory()) return "directory";
702
+ if (stat.isFile()) return "file";
703
+ return null;
704
+ });
705
+ }
706
+
707
+ async function getEntryType(gitEntryPath: string): Promise<EntryType | null> {
708
+ return retryOnEintr(async () => {
709
+ const stat = await fs.promises.stat(gitEntryPath);
710
+ if (stat.isDirectory()) return "directory";
711
+ if (stat.isFile()) return "file";
712
+ return null;
713
+ });
714
+ }
715
+
716
+ function readOptionalTextSync(filePath: string): string | null {
717
+ return retryOnEintrSync(() => fs.readFileSync(filePath, "utf8"));
718
+ }
719
+
720
+ async function readOptionalText(filePath: string): Promise<string | null> {
721
+ return retryOnEintr(async () => await Bun.file(filePath).text());
722
+ }
723
+
724
+ async function readOptionalBytes(filePath: string): Promise<Uint8Array | null> {
725
+ return retryOnEintr(async () => await Bun.file(filePath).bytes());
726
+ }
727
+
728
+ function parseGitDirPointer(content: string): string | null {
729
+ const match = /^gitdir:\s*(.+)\s*$/iu.exec(content.trim());
730
+ return match?.[1] ?? null;
731
+ }
732
+
733
+ function resolveGitDirSync(gitEntryPath: string, entryType: EntryType): string | null {
734
+ if (entryType === "directory") return gitEntryPath;
735
+ const content = readOptionalTextSync(gitEntryPath);
736
+ if (content === null) return null;
737
+ const parsed = parseGitDirPointer(content);
738
+ if (!parsed) return null;
739
+ const gitDir = path.resolve(path.dirname(gitEntryPath), parsed);
740
+ return getEntryTypeSync(gitDir) === "directory" ? gitDir : null;
741
+ }
742
+
743
+ async function resolveGitDir(gitEntryPath: string, entryType: EntryType): Promise<string | null> {
744
+ if (entryType === "directory") return gitEntryPath;
745
+ const content = await readOptionalText(gitEntryPath);
746
+ if (content === null) return null;
747
+ const parsed = parseGitDirPointer(content);
748
+ if (!parsed) return null;
749
+ const gitDir = path.resolve(path.dirname(gitEntryPath), parsed);
750
+ return (await getEntryType(gitDir)) === "directory" ? gitDir : null;
751
+ }
752
+
753
+ function resolveCommonDirSync(gitDir: string): string {
754
+ const content = readOptionalTextSync(path.join(gitDir, "commondir"));
755
+ const relative = content?.trim();
756
+ if (!relative) return gitDir;
757
+ return path.resolve(gitDir, relative);
758
+ }
759
+
760
+ async function resolveCommonDir(gitDir: string): Promise<string> {
761
+ const content = await readOptionalText(path.join(gitDir, "commondir"));
762
+ const relative = content?.trim();
763
+ if (!relative) return gitDir;
764
+ return path.resolve(gitDir, relative);
765
+ }
766
+ function isLinkedWorktree(repository: GitRepository): boolean {
767
+ return (
768
+ repository.gitDir !== repository.commonDir &&
769
+ getEntryTypeSync(path.join(repository.gitDir, "commondir")) === "file"
770
+ );
771
+ }
772
+
773
+ async function isLinkedWorktreeAsync(repository: GitRepository): Promise<boolean> {
774
+ return (
775
+ repository.gitDir !== repository.commonDir &&
776
+ (await getEntryType(path.join(repository.gitDir, "commondir"))) === "file"
777
+ );
778
+ }
779
+
780
+ function primaryRootFromRepositorySync(repository: GitRepository): string {
781
+ if (path.basename(repository.commonDir) === ".git") return path.dirname(repository.commonDir);
782
+ if (isLinkedWorktree(repository)) return repository.commonDir;
783
+ return repository.repoRoot;
784
+ }
785
+
786
+ async function primaryRootFromRepository(repository: GitRepository): Promise<string> {
787
+ if (path.basename(repository.commonDir) === ".git") return path.dirname(repository.commonDir);
788
+ if (await isLinkedWorktreeAsync(repository)) return repository.commonDir;
789
+ return repository.repoRoot;
790
+ }
791
+
792
+ function resolveRepoFromEntrySync(repoRoot: string, gitEntryPath: string, entryType: EntryType): GitRepository | null {
793
+ const gitDir = resolveGitDirSync(gitEntryPath, entryType);
794
+ if (!gitDir) return null;
795
+ return {
796
+ commonDir: resolveCommonDirSync(gitDir),
797
+ gitDir,
798
+ gitEntryPath,
799
+ headPath: path.join(gitDir, "HEAD"),
800
+ repoRoot,
801
+ };
802
+ }
803
+
804
+ async function resolveRepoFromEntry(
805
+ repoRoot: string,
806
+ gitEntryPath: string,
807
+ entryType: EntryType,
808
+ ): Promise<GitRepository | null> {
809
+ const gitDir = await resolveGitDir(gitEntryPath, entryType);
810
+ if (!gitDir) return null;
811
+ return {
812
+ commonDir: await resolveCommonDir(gitDir),
813
+ gitDir,
814
+ gitEntryPath,
815
+ headPath: path.join(gitDir, "HEAD"),
816
+ repoRoot,
817
+ };
818
+ }
819
+
820
+ function resolveRepositorySync(startDir: string): GitRepository | null {
821
+ let current = path.resolve(startDir);
822
+ while (true) {
823
+ const gitEntryPath = path.join(current, ".git");
824
+ const entryType = getEntryTypeSync(gitEntryPath);
825
+ if (entryType) {
826
+ const repository = resolveRepoFromEntrySync(current, gitEntryPath, entryType);
827
+ if (repository) return repository;
828
+ }
829
+ const parent = path.dirname(current);
830
+ if (parent === current) return null;
831
+ current = parent;
832
+ }
833
+ }
834
+
835
+ async function resolveRepository(startDir: string): Promise<GitRepository | null> {
836
+ let current = path.resolve(startDir);
837
+ while (true) {
838
+ const gitEntryPath = path.join(current, ".git");
839
+ const entryType = await getEntryType(gitEntryPath);
840
+ if (entryType) {
841
+ const repository = await resolveRepoFromEntry(current, gitEntryPath, entryType);
842
+ if (repository) return repository;
843
+ }
844
+ const parent = path.dirname(current);
845
+ if (parent === current) return null;
846
+ current = parent;
847
+ }
848
+ }
849
+
850
+ // ════════════════════════════════════════════════════════════════════════════
851
+ // Internal: Ref resolution
852
+ // ════════════════════════════════════════════════════════════════════════════
853
+
854
+ function getRefLookupDirs(repository: GitRepository): string[] {
855
+ if (repository.gitDir === repository.commonDir) return [repository.gitDir];
856
+ return [repository.gitDir, repository.commonDir];
857
+ }
858
+
859
+ function normalizeRefValue(content: string | null): string | null {
860
+ const trimmed = content?.trim() ?? "";
861
+ return trimmed || null;
862
+ }
863
+
864
+ function parsePackedRefs(content: string | null, targetRef: string): string | null {
865
+ if (!content) return null;
866
+ for (const line of content.split("\n")) {
867
+ const trimmed = line.trim();
868
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("^")) continue;
869
+ const [sha, refName] = trimmed.split(" ", 2);
870
+ if (refName === targetRef && sha) return sha;
871
+ }
872
+ return null;
873
+ }
874
+
875
+ function stripGitConfigComments(line: string): string {
876
+ let clean = "";
877
+ let inQuotes = false;
878
+ for (let i = 0; i < line.length; i++) {
879
+ const char = line[i];
880
+ if (char === '"') {
881
+ inQuotes = !inQuotes;
882
+ clean += char;
883
+ } else if (!inQuotes && (char === ";" || char === "#")) {
884
+ break;
885
+ } else {
886
+ clean += char;
887
+ }
888
+ }
889
+ return clean.trim();
890
+ }
891
+
892
+ function parseGitConfigHasReftable(content: string): boolean {
893
+ let inExtensions = false;
894
+ for (const line of content.split("\n")) {
895
+ const trimmed = stripGitConfigComments(line);
896
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
897
+ const section = trimmed.slice(1, -1).trim().toLowerCase();
898
+ inExtensions = section === "extensions";
899
+ } else if (inExtensions) {
900
+ const eqIndex = trimmed.indexOf("=");
901
+ if (eqIndex !== -1) {
902
+ const key = trimmed.slice(0, eqIndex).trim().toLowerCase();
903
+ let value = trimmed.slice(eqIndex + 1).trim();
904
+ if (key === "refstorage") {
905
+ if (value.startsWith('"') && value.endsWith('"')) {
906
+ value = value.slice(1, -1).trim();
907
+ }
908
+ const lowerValue = value.toLowerCase();
909
+ if (lowerValue === "reftable" || lowerValue.startsWith("reftable:")) {
910
+ return true;
911
+ }
912
+ }
913
+ }
914
+ }
915
+ }
916
+ return false;
917
+ }
918
+
919
+ function isReftableRepoSync(repository: GitRepository): boolean {
920
+ if (repository.isReftable !== undefined) return repository.isReftable;
921
+ const configPath = path.join(repository.commonDir, "config");
922
+ const content = readOptionalTextSync(configPath);
923
+ repository.isReftable = content ? parseGitConfigHasReftable(content) : false;
924
+ return repository.isReftable;
925
+ }
926
+
927
+ async function isReftableRepo(repository: GitRepository): Promise<boolean> {
928
+ if (repository.isReftable !== undefined) return repository.isReftable;
929
+ const configPath = path.join(repository.commonDir, "config");
930
+ const content = await readOptionalText(configPath);
931
+ repository.isReftable = content ? parseGitConfigHasReftable(content) : false;
932
+ return repository.isReftable;
933
+ }
934
+
935
+ async function resolveHeadStateReftable(repository: GitRepository, signal?: AbortSignal): Promise<GitHeadState | null> {
936
+ throwIfAborted(signal);
937
+ const symResult = await git(repository.repoRoot, ["symbolic-ref", "HEAD"], { readOnly: true, signal }).catch(err => {
938
+ if (signal?.aborted || (err instanceof Error && (err.name === "AbortError" || err.name === "ToolAbortError"))) {
939
+ throw err;
940
+ }
941
+ return null;
942
+ });
943
+ throwIfAborted(signal);
944
+ const revResult = await git(repository.repoRoot, ["rev-parse", "--verify", "HEAD"], {
945
+ readOnly: true,
946
+ signal,
947
+ }).catch(err => {
948
+ if (signal?.aborted || (err instanceof Error && (err.name === "AbortError" || err.name === "ToolAbortError"))) {
949
+ throw err;
950
+ }
951
+ return null;
952
+ });
953
+ const commit = revResult && revResult.exitCode === 0 ? revResult.stdout.trim() || null : null;
954
+
955
+ if (symResult && symResult.exitCode === 0) {
956
+ const ref = symResult.stdout.trim();
957
+ const branchName = ref.startsWith(LOCAL_BRANCH_PREFIX) ? ref.slice(LOCAL_BRANCH_PREFIX.length) : null;
958
+ return {
959
+ ...repository,
960
+ kind: "ref",
961
+ ref,
962
+ branchName,
963
+ commit,
964
+ headContent: `${HEAD_REF_PREFIX} ${ref}`,
965
+ };
966
+ }
967
+
968
+ return {
969
+ ...repository,
970
+ kind: "detached",
971
+ commit,
972
+ headContent: commit || "",
973
+ };
974
+ }
975
+
976
+ function resolveHeadStateReftableSync(repository: GitRepository): GitHeadState | null {
977
+ const symResult = gitSpawnSyncText(repository.repoRoot, ["symbolic-ref", "HEAD"]);
978
+ const revResult = gitSpawnSyncText(repository.repoRoot, ["rev-parse", "--verify", "HEAD"]);
979
+ const commit = revResult.exitCode === 0 ? revResult.stdout || null : null;
980
+
981
+ if (symResult.exitCode === 0) {
982
+ const ref = symResult.stdout;
983
+ const branchName = ref.startsWith(LOCAL_BRANCH_PREFIX) ? ref.slice(LOCAL_BRANCH_PREFIX.length) : null;
984
+ return {
985
+ ...repository,
986
+ kind: "ref",
987
+ ref,
988
+ branchName,
989
+ commit,
990
+ headContent: `${HEAD_REF_PREFIX} ${ref}`,
991
+ };
992
+ }
993
+
994
+ return {
995
+ ...repository,
996
+ kind: "detached",
997
+ commit,
998
+ headContent: commit || "",
999
+ };
1000
+ }
1001
+
1002
+ function readRefSync(repository: GitRepository, targetRef: string): string | null {
1003
+ if (isReftableRepoSync(repository)) {
1004
+ const symResult = gitSpawnSyncText(repository.repoRoot, ["symbolic-ref", targetRef]);
1005
+ if (symResult.exitCode === 0) {
1006
+ return `${HEAD_REF_PREFIX} ${symResult.stdout}`;
1007
+ }
1008
+ const revResult = gitSpawnSyncText(repository.repoRoot, ["rev-parse", "--verify", targetRef]);
1009
+ if (revResult.exitCode === 0) {
1010
+ return revResult.stdout || null;
1011
+ }
1012
+ return null;
1013
+ }
1014
+
1015
+ for (const dir of getRefLookupDirs(repository)) {
1016
+ const value = normalizeRefValue(readOptionalTextSync(path.join(dir, targetRef)));
1017
+ if (value) return value;
1018
+ }
1019
+ for (const dir of getRefLookupDirs(repository)) {
1020
+ const value = parsePackedRefs(readOptionalTextSync(path.join(dir, "packed-refs")), targetRef);
1021
+ if (value) return value;
1022
+ }
1023
+ return null;
1024
+ }
1025
+
1026
+ async function readRef(repository: GitRepository, targetRef: string, signal?: AbortSignal): Promise<string | null> {
1027
+ if (await isReftableRepo(repository)) {
1028
+ throwIfAborted(signal);
1029
+ const symResult = await git(repository.repoRoot, ["symbolic-ref", targetRef], { readOnly: true, signal }).catch(
1030
+ err => {
1031
+ if (
1032
+ signal?.aborted ||
1033
+ (err instanceof Error && (err.name === "AbortError" || err.name === "ToolAbortError"))
1034
+ ) {
1035
+ throw err;
1036
+ }
1037
+ return null;
1038
+ },
1039
+ );
1040
+ if (symResult && symResult.exitCode === 0) {
1041
+ return `${HEAD_REF_PREFIX} ${symResult.stdout.trim()}`;
1042
+ }
1043
+ throwIfAborted(signal);
1044
+ const revResult = await git(repository.repoRoot, ["rev-parse", "--verify", targetRef], {
1045
+ readOnly: true,
1046
+ signal,
1047
+ }).catch(err => {
1048
+ if (
1049
+ signal?.aborted ||
1050
+ (err instanceof Error && (err.name === "AbortError" || err.name === "ToolAbortError"))
1051
+ ) {
1052
+ throw err;
1053
+ }
1054
+ return null;
1055
+ });
1056
+ if (revResult && revResult.exitCode === 0) {
1057
+ return revResult.stdout.trim() || null;
1058
+ }
1059
+ return null;
1060
+ }
1061
+
1062
+ for (const dir of getRefLookupDirs(repository)) {
1063
+ const value = normalizeRefValue(await readOptionalText(path.join(dir, targetRef)));
1064
+ if (value) return value;
1065
+ }
1066
+ for (const dir of getRefLookupDirs(repository)) {
1067
+ const value = parsePackedRefs(await readOptionalText(path.join(dir, "packed-refs")), targetRef);
1068
+ if (value) return value;
1069
+ }
1070
+ return null;
1071
+ }
1072
+
1073
+ // ════════════════════════════════════════════════════════════════════════════
1074
+ // Internal: Head state parsing
1075
+ // ════════════════════════════════════════════════════════════════════════════
1076
+
1077
+ function parseHeadStateSync(repository: GitRepository, headContent: string): GitHeadState {
1078
+ const trimmed = headContent.trim();
1079
+ if (!trimmed?.startsWith(HEAD_REF_PREFIX)) {
1080
+ return { ...repository, commit: trimmed || null, headContent, kind: "detached" };
1081
+ }
1082
+ const refValue = trimmed.slice(HEAD_REF_PREFIX.length).trim();
1083
+ const branchName = refValue.startsWith(LOCAL_BRANCH_PREFIX) ? refValue.slice(LOCAL_BRANCH_PREFIX.length) : null;
1084
+ return {
1085
+ ...repository,
1086
+ branchName,
1087
+ commit: readRefSync(repository, refValue),
1088
+ headContent,
1089
+ kind: "ref",
1090
+ ref: refValue,
1091
+ };
1092
+ }
1093
+
1094
+ async function parseHeadState(repository: GitRepository, headContent: string): Promise<GitHeadState> {
1095
+ const trimmed = headContent.trim();
1096
+ if (!trimmed?.startsWith(HEAD_REF_PREFIX)) {
1097
+ return { ...repository, commit: trimmed || null, headContent, kind: "detached" };
1098
+ }
1099
+ const refValue = trimmed.slice(HEAD_REF_PREFIX.length).trim();
1100
+ const branchName = refValue.startsWith(LOCAL_BRANCH_PREFIX) ? refValue.slice(LOCAL_BRANCH_PREFIX.length) : null;
1101
+ return {
1102
+ ...repository,
1103
+ branchName,
1104
+ commit: await readRef(repository, refValue),
1105
+ headContent,
1106
+ kind: "ref",
1107
+ ref: refValue,
1108
+ };
1109
+ }
1110
+
1111
+ function parseDefaultBranchRef(refPath: string, target: string | null): string | null {
1112
+ if (!target?.startsWith(HEAD_REF_PREFIX)) return null;
1113
+ const resolvedRef = target.slice(HEAD_REF_PREFIX.length).trim();
1114
+ const remotePrefix = refPath.slice(0, -"HEAD".length);
1115
+ if (!resolvedRef.startsWith(remotePrefix)) return null;
1116
+ return resolvedRef.slice(remotePrefix.length) || null;
1117
+ }
1118
+
1119
+ function stripRemotePrefix(refValue: string): string | null {
1120
+ const slash = refValue.indexOf("/");
1121
+ if (slash < 0) return refValue || null;
1122
+ return refValue.slice(slash + 1) || null;
1123
+ }
1124
+
1125
+ function parseWorktreeList(text: string): GitWorktreeEntry[] {
1126
+ const trimmed = text.trim();
1127
+ if (!trimmed) return [];
1128
+ return trimmed
1129
+ .split(/\n\s*\n/)
1130
+ .map(block => block.trim())
1131
+ .filter(Boolean)
1132
+ .map(block => {
1133
+ const entry: GitWorktreeEntry = { detached: false, path: "" };
1134
+ for (const line of block.split("\n")) {
1135
+ if (line.startsWith("worktree ")) entry.path = line.slice("worktree ".length);
1136
+ else if (line.startsWith("HEAD ")) entry.head = line.slice("HEAD ".length);
1137
+ else if (line.startsWith("branch ")) entry.branch = line.slice("branch ".length);
1138
+ else if (line === "detached") entry.detached = true;
1139
+ }
1140
+ return entry;
1141
+ });
1142
+ }
1143
+
1144
+ // ════════════════════════════════════════════════════════════════════════════
1145
+ // Internal: Hunk selection
1146
+ // ════════════════════════════════════════════════════════════════════════════
1147
+
1148
+ function extractFileHeader(diffText: string): string {
1149
+ const lines = diffText.split("\n");
1150
+ const headerLines: string[] = [];
1151
+ for (const line of lines) {
1152
+ if (line.startsWith("@@")) break;
1153
+ headerLines.push(line);
1154
+ }
1155
+ return headerLines.join("\n");
1156
+ }
1157
+
1158
+ function selectHunks(file: FileHunks, selector: HunkSelection["hunks"]): FileHunks["hunks"] {
1159
+ if (selector.type === "indices") {
1160
+ const wanted = new Set(selector.indices.map(v => Math.max(1, Math.floor(v))));
1161
+ return file.hunks.filter(hunk => wanted.has(hunk.index + 1));
1162
+ }
1163
+ if (selector.type === "lines") {
1164
+ const start = Math.floor(selector.start);
1165
+ const end = Math.floor(selector.end);
1166
+ return file.hunks.filter(hunk => hunk.newStart <= end && hunk.newStart + hunk.newLines - 1 >= start);
1167
+ }
1168
+ return file.hunks;
1169
+ }
1170
+
1171
+ export function createHunkSelectionValidator(
1172
+ rawDiff: string,
1173
+ ): (selections: readonly HunkSelection[]) => HunkSelectionValidationError[] {
1174
+ const fileDiffMap = new Map(parseFileDiffs(rawDiff).map(entry => [entry.filename, entry]));
1175
+ return selections => validateHunkSelectionsFromMap(fileDiffMap, selections);
1176
+ }
1177
+
1178
+ function validateHunkSelectionsFromMap(
1179
+ fileDiffMap: ReadonlyMap<string, FileDiff>,
1180
+ selections: readonly HunkSelection[],
1181
+ ): HunkSelectionValidationError[] {
1182
+ const errors: HunkSelectionValidationError[] = [];
1183
+
1184
+ for (const selection of selections) {
1185
+ const fileDiff = fileDiffMap.get(selection.path);
1186
+ if (!fileDiff) continue;
1187
+ if (selection.hunks.type === "all") continue;
1188
+ if (fileDiff.isBinary) {
1189
+ errors.push({ path: selection.path, message: `Cannot select hunks for binary file ${selection.path}` });
1190
+ continue;
1191
+ }
1192
+ const selected = selectHunks(parseFileHunks(fileDiff), selection.hunks);
1193
+ if (selected.length === 0) {
1194
+ errors.push({ path: selection.path, message: `No hunks selected for ${selection.path}` });
1195
+ }
1196
+ }
1197
+
1198
+ return errors;
1199
+ }
1200
+
1201
+ export function validateHunkSelections(
1202
+ rawDiff: string,
1203
+ selections: readonly HunkSelection[],
1204
+ ): HunkSelectionValidationError[] {
1205
+ return createHunkSelectionValidator(rawDiff)(selections);
1206
+ }
1207
+
1208
+ function parseStatusPorcelain(text: string): GitStatusSummary {
1209
+ let staged = 0;
1210
+ let unstaged = 0;
1211
+ let untracked = 0;
1212
+ for (const line of text.split("\n")) {
1213
+ if (!line) continue;
1214
+ const x = line[0];
1215
+ const y = line[1];
1216
+ if (x === "?" && y === "?") {
1217
+ untracked += 1;
1218
+ continue;
1219
+ }
1220
+ if (x && x !== " " && x !== "?") staged += 1;
1221
+ if (y && y !== " ") unstaged += 1;
1222
+ }
1223
+ return { staged, unstaged, untracked };
1224
+ }
1225
+
1226
+ // ════════════════════════════════════════════════════════════════════════════
1227
+ // API: diff
1228
+ // ════════════════════════════════════════════════════════════════════════════
1229
+
1230
+ /** Run `git diff` with the given options. Returns raw diff text. */
1231
+ export const diff = Object.assign(
1232
+ async function diff(cwd: string, options: DiffOptions = {}): Promise<string> {
1233
+ const args = buildDiffArgs(options);
1234
+ if (options.allowFailure) {
1235
+ return (await git(cwd, args, { env: options.env, readOnly: true, signal: options.signal })).stdout;
1236
+ }
1237
+ return runText(cwd, args, { env: options.env, readOnly: true, signal: options.signal });
1238
+ },
1239
+ {
1240
+ /** List changed file paths. */
1241
+ async changedFiles(
1242
+ cwd: string,
1243
+ options: Pick<DiffOptions, "cached" | "files" | "signal"> = {},
1244
+ ): Promise<string[]> {
1245
+ return splitLines(await diff(cwd, { ...options, nameOnly: true }));
1246
+ },
1247
+ /** Parsed per-file add/remove counts. */
1248
+ async numstat(cwd: string, options: Pick<DiffOptions, "cached" | "signal"> = {}): Promise<NumstatEntry[]> {
1249
+ return parseNumstat(await diff(cwd, { ...options, numstat: true }));
1250
+ },
1251
+ /** Parsed diff hunks for the given files. */
1252
+ async hunks(
1253
+ cwd: string,
1254
+ files: readonly string[],
1255
+ options: { cached?: boolean; signal?: AbortSignal } = {},
1256
+ ): Promise<FileHunks[]> {
1257
+ return parseCommitDiffHunks(
1258
+ await diff(cwd, { cached: options.cached ?? true, files, signal: options.signal }),
1259
+ );
1260
+ },
1261
+ /** Check whether a diff exists (uses `--quiet` for efficiency). */
1262
+ async has(cwd: string, options: Pick<DiffOptions, "cached" | "files" | "signal"> = {}): Promise<boolean> {
1263
+ const args = ["diff"];
1264
+ if (options.cached) args.push("--cached");
1265
+ args.push("--quiet");
1266
+ if (options.files?.length) args.push("--", ...options.files);
1267
+ const result = await git(cwd, args, { readOnly: true, signal: options.signal });
1268
+ if (result.exitCode === 0) return false;
1269
+ if (result.exitCode === 1) return true;
1270
+ throw new GitCommandError(args, result);
1271
+ },
1272
+ /** Diff between two tree-ish objects (`git diff-tree`). */
1273
+ async tree(
1274
+ cwd: string,
1275
+ base: string,
1276
+ headRef: string,
1277
+ options: { binary?: boolean; signal?: AbortSignal; allowFailure?: boolean } = {},
1278
+ ): Promise<string> {
1279
+ const args = ["diff-tree", "-r", "-p"];
1280
+ if (options.binary) args.push("--binary");
1281
+ args.push(base, headRef);
1282
+ if (options.allowFailure) {
1283
+ return (await git(cwd, args, { readOnly: true, signal: options.signal })).stdout;
1284
+ }
1285
+ return runText(cwd, args, { readOnly: true, signal: options.signal });
1286
+ },
1287
+ /** Parse raw diff text into per-file diffs. */
1288
+ parseFiles(text: string): FileDiff[] {
1289
+ return parseFileDiffs(text);
1290
+ },
1291
+ /** Parse raw diff text into per-file hunks. */
1292
+ parseHunks(text: string): FileHunks[] {
1293
+ return parseCommitDiffHunks(text);
1294
+ },
1295
+ },
1296
+ );
1297
+
1298
+ // ════════════════════════════════════════════════════════════════════════════
1299
+ // API: status
1300
+ // ════════════════════════════════════════════════════════════════════════════
1301
+
1302
+ /** Run `git status --porcelain`. Returns raw status text. */
1303
+ export const status = Object.assign(
1304
+ async function status(cwd: string, options: StatusOptions = {}): Promise<string> {
1305
+ const args = ["status"];
1306
+ args.push(options.porcelainV1 ? "--porcelain=v1" : "--porcelain");
1307
+ if (options.z) args.push("-z");
1308
+ if (options.untrackedFiles) args.push(`--untracked-files=${options.untrackedFiles}`);
1309
+ if (options.pathspecs?.length) args.push("--", ...options.pathspecs);
1310
+ return runText(cwd, args, { readOnly: true, signal: options.signal });
1311
+ },
1312
+ {
1313
+ /** Parsed status counts (staged, unstaged, untracked). */
1314
+ async summary(cwd: string, signal?: AbortSignal): Promise<GitStatusSummary | null> {
1315
+ const result = await git(cwd, ["status", "--porcelain"], { readOnly: true, signal });
1316
+ if (result.exitCode !== 0) return null;
1317
+ return parseStatusPorcelain(result.stdout);
1318
+ },
1319
+ /** Parse porcelain status text into counts. */
1320
+ parse: parseStatusPorcelain,
1321
+ },
1322
+ );
1323
+
1324
+ // ════════════════════════════════════════════════════════════════════════════
1325
+ // API: stage
1326
+ // ════════════════════════════════════════════════════════════════════════════
1327
+
1328
+ export const stage = {
1329
+ /** Stage files. Empty array stages all (`git add -A`). */
1330
+ async files(cwd: string, files: readonly string[] = [], signal?: AbortSignal): Promise<void> {
1331
+ const args = files.length === 0 ? ["add", "-A"] : ["add", "--", ...files];
1332
+ await runEffect(cwd, args, { signal });
1333
+ },
1334
+
1335
+ /** Selectively stage hunks from the provided diff or the current working tree diff. */
1336
+ async hunks(cwd: string, selections: HunkSelection[], options: StageHunksOptions = {}): Promise<void> {
1337
+ if (selections.length === 0) return;
1338
+ const rawDiff = options.rawDiff ?? (await diff(cwd, { cached: options.diffCached, signal: options.signal }));
1339
+ const fileDiffs = parseFileDiffs(rawDiff);
1340
+ const fileDiffMap = new Map(fileDiffs.map(entry => [entry.filename, entry]));
1341
+ const patchParts: string[] = [];
1342
+
1343
+ for (const selection of selections) {
1344
+ const fileDiff = fileDiffMap.get(selection.path);
1345
+ if (!fileDiff) throw new Error(`No diff found for ${selection.path}`);
1346
+ if (fileDiff.isBinary) {
1347
+ if (selection.hunks.type !== "all")
1348
+ throw new Error(`Cannot select hunks for binary file ${selection.path}`);
1349
+ patchParts.push(fileDiff.content);
1350
+ continue;
1351
+ }
1352
+ if (selection.hunks.type === "all") {
1353
+ patchParts.push(fileDiff.content);
1354
+ continue;
1355
+ }
1356
+ const fileHunks = parseFileHunks(fileDiff);
1357
+ const selected = selectHunks(fileHunks, selection.hunks);
1358
+ if (selected.length === 0) throw new Error(`No hunks selected for ${selection.path}`);
1359
+ const header = extractFileHeader(fileDiff.content);
1360
+ patchParts.push([header, ...selected.map(h => h.content)].join("\n"));
1361
+ }
1362
+
1363
+ const patchText = patch.join(patchParts);
1364
+ if (!patchText.trim()) return;
1365
+ await patch.applyText(cwd, patchText, { cached: true, signal: options.signal });
1366
+ },
1367
+
1368
+ /** Unstage files. Empty array unstages all (`git reset`). */
1369
+ async reset(cwd: string, files: readonly string[] = [], signal?: AbortSignal): Promise<void> {
1370
+ const args = files.length === 0 ? ["reset"] : ["reset", "--", ...files];
1371
+ await runEffect(cwd, args, { signal });
1372
+ },
1373
+ };
1374
+
1375
+ // ════════════════════════════════════════════════════════════════════════════
1376
+ // API: commit, push, checkout
1377
+ // ════════════════════════════════════════════════════════════════════════════
1378
+
1379
+ /** Create a commit with the given message (passed via stdin). */
1380
+ export async function commit(cwd: string, message: string, options: CommitOptions = {}): Promise<GitCommandResult> {
1381
+ const args = ["commit", "-F", "-"];
1382
+ if (options.author) {
1383
+ args.push(`--author=${options.author.name} <${options.author.email}>`);
1384
+ if (options.author.date) args.push(`--date=${options.author.date}`);
1385
+ }
1386
+ if (options.allowEmpty) args.push("--allow-empty");
1387
+ if (options.files?.length) args.push("--", ...options.files);
1388
+ return runChecked(cwd, args, { signal: options.signal, stdin: message });
1389
+ }
1390
+
1391
+ /** Push the current branch (branch-scoped: never follows tags). */
1392
+ export async function push(cwd: string, options: PushOptions = {}): Promise<void> {
1393
+ // `--no-follow-tags` overrides a user's `push.followTags = true`, which
1394
+ // would otherwise ride every reachable annotated tag along with the
1395
+ // branch — rejected refs ("permission denied") on remotes the user
1396
+ // cannot tag (e.g. PR-head forks), failing the call after the branch
1397
+ // itself already updated. Tool pushes push exactly the named refspec.
1398
+ const args = ["push", "--no-follow-tags"];
1399
+ if (options.forceWithLease) args.push("--force-with-lease");
1400
+ if (options.remote) args.push(options.remote);
1401
+ if (options.refspec) args.push(options.refspec);
1402
+ await runEffect(cwd, args, { signal: options.signal });
1403
+ }
1404
+
1405
+ /** Checkout a ref. */
1406
+ export async function checkout(cwd: string, ref: string, signal?: AbortSignal): Promise<void> {
1407
+ await runEffect(cwd, ["checkout", ref], { signal });
1408
+ }
1409
+
1410
+ /** Fetch a specific refspec from a remote. Network transfer: defaults to the {@link GIT_NETWORK_TIMEOUT_MS} deadline. */
1411
+ export async function fetch(
1412
+ cwd: string,
1413
+ remote: string,
1414
+ source: string,
1415
+ target: string,
1416
+ options: FetchOptions = {},
1417
+ ): Promise<void> {
1418
+ await runEffect(cwd, ["fetch", remote, `+${source}:${target}`], {
1419
+ signal: options.signal,
1420
+ timeoutMs: resolveTimeoutMs(options.timeoutMs, GIT_NETWORK_TIMEOUT_MS),
1421
+ });
1422
+ }
1423
+
1424
+ /** Read a tree-ish into the index. */
1425
+ export async function readTree(
1426
+ cwd: string,
1427
+ treeish: string,
1428
+ options: Pick<CommandOptions, "env" | "signal"> = {},
1429
+ ): Promise<void> {
1430
+ await runEffect(cwd, ["read-tree", treeish], options);
1431
+ }
1432
+
1433
+ /** Write the current index as a tree and return its object id. */
1434
+ export async function writeTree(cwd: string, options: Pick<CommandOptions, "env" | "signal"> = {}): Promise<string> {
1435
+ return (await runText(cwd, ["write-tree"], options)).trim();
1436
+ }
1437
+
1438
+ // ════════════════════════════════════════════════════════════════════════════
1439
+ // API: worktree isolation
1440
+ // ════════════════════════════════════════════════════════════════════════════
1441
+
1442
+ /** Outcome of {@link detachGitDir}. */
1443
+ export type DetachGitDirResult =
1444
+ /** `worktreeRoot` had no `.git`; nothing to detach. */
1445
+ | "no-git"
1446
+ /** `.git` already resolves to an independent object DB — left untouched. */
1447
+ | "independent"
1448
+ /** Detached into a standalone repo borrowing `sourceCommonDir`'s objects. */
1449
+ | "detached";
1450
+
1451
+ /**
1452
+ * Sever a copied/mounted working tree from the git metadata it shares with a
1453
+ * source checkout, turning it into a standalone repository that borrows the
1454
+ * source object database through `objects/info/alternates`.
1455
+ *
1456
+ * Isolation backends (reflink/apfs/btrfs/rcopy…) materialise `merged` by
1457
+ * copying `worktreeRoot` byte-for-byte. When `worktreeRoot` is a **linked git
1458
+ * worktree** its `.git` is a pointer file (`gitdir: …/worktrees/<name>`), so
1459
+ * the copy still resolves HEAD/index/refs through the source repo — a task's
1460
+ * `git checkout`/`commit` inside the isolation then mutates the *parent*
1461
+ * checkout. The rcopy `git worktree add` path leaks the other way: task
1462
+ * branches land in the shared ref namespace and stack on each other.
1463
+ *
1464
+ * After detaching, the working tree keeps its files verbatim while:
1465
+ * - HEAD, refs, and the index are frozen to the snapshot at call time;
1466
+ * - all commits/branches the task creates stay private to the isolation;
1467
+ * - objects resolve against `sourceCommonDir` via alternates, so history reads
1468
+ * and later `git fetch <merged>` object transfer keep working;
1469
+ * - the source checkout's HEAD, branch, index, and working tree are untouched.
1470
+ *
1471
+ * A full-copy `.git` (non-worktree source) already owns its object DB and is
1472
+ * returned as `"independent"` without modification. `worktreeRoot` without a
1473
+ * `.git` yields `"no-git"`.
1474
+ */
1475
+ export async function detachGitDir(worktreeRoot: string, sourceCommonDir: string): Promise<DetachGitDirResult> {
1476
+ ensureAvailable();
1477
+ const gitEntry = path.join(worktreeRoot, ".git");
1478
+ let entryStat: fs.Stats;
1479
+ try {
1480
+ entryStat = await fs.promises.lstat(gitEntry);
1481
+ } catch (err) {
1482
+ if (isEnoent(err)) return "no-git";
1483
+ throw err;
1484
+ }
1485
+ // Canonicalize both sides before comparing: `rev-parse` resolves symlinks
1486
+ // (macOS `/tmp` → `/private/tmp`) while callers derive `sourceCommonDir`
1487
+ // lexically from the session cwd. A lexical mismatch here would silently
1488
+ // classify a shared linked-worktree copy as "independent" and skip the
1489
+ // detach entirely — leaving the parent-mutation leak in place.
1490
+ const parentCommon = await fs.promises.realpath(sourceCommonDir).catch(() => path.resolve(sourceCommonDir));
1491
+ const isoCommonRaw = (
1492
+ await runText(worktreeRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
1493
+ readOnly: true,
1494
+ })
1495
+ ).trim();
1496
+ const isoCommon = await fs.promises.realpath(isoCommonRaw).catch(() => path.resolve(isoCommonRaw));
1497
+ // A full-copy `.git` already resolves to its own object DB — leave it alone.
1498
+ if (isoCommon !== parentCommon) return "independent";
1499
+
1500
+ // Snapshot the state the standalone repo must preserve. HEAD may be a branch
1501
+ // ref (normal checkout), detached, or unborn (a fresh/orphan branch with no
1502
+ // commits — a linked worktree still shares the parent ref namespace, so it
1503
+ // must be severed too). Refs are frozen so `baseSha..branch` ranges and
1504
+ // history reads keep resolving after the source moves on.
1505
+ const headSha = (await tryText(worktreeRoot, ["rev-parse", "HEAD"], { readOnly: true }))?.trim() ?? "";
1506
+ const headRef = (await tryText(worktreeRoot, ["symbolic-ref", "-q", "HEAD"], { readOnly: true }))?.trim() ?? "";
1507
+ const refDump = headSha
1508
+ ? (
1509
+ await runText(worktreeRoot, ["for-each-ref", "--format=%(objectname) %(refname)"], {
1510
+ readOnly: true,
1511
+ })
1512
+ ).trim()
1513
+ : "";
1514
+ const objectFormat =
1515
+ (await tryText(worktreeRoot, ["rev-parse", "--show-object-format"], { readOnly: true }))?.trim() || "sha1";
1516
+ const userName = await config.get(worktreeRoot, "user.name");
1517
+ const userEmail = await config.get(worktreeRoot, "user.email");
1518
+
1519
+ // Preserve the index verbatim rather than round-tripping through
1520
+ // write-tree/read-tree: the raw index carries skip-worktree bits (sparse
1521
+ // checkout), assume-unchanged flags, and exact stage entries. A rebuilt
1522
+ // index drops skip-worktree, so files intentionally absent from a sparse
1523
+ // working tree would read as deletions and delta capture would apply those
1524
+ // deletions back to the parent. Sparse config + patterns are carried too so
1525
+ // later git operations in the isolation keep honouring the sparse view.
1526
+ const indexPath = (
1527
+ await runText(worktreeRoot, ["rev-parse", "--path-format=absolute", "--git-path", "index"], {
1528
+ readOnly: true,
1529
+ })
1530
+ ).trim();
1531
+ const indexBytes = await readOptionalBytes(indexPath);
1532
+ const sparseCheckout = await config.get(worktreeRoot, "core.sparseCheckout");
1533
+ const sparseCone = await config.get(worktreeRoot, "core.sparseCheckoutCone");
1534
+ const sparsePatternPath = (
1535
+ await runText(worktreeRoot, ["rev-parse", "--path-format=absolute", "--git-path", "info/sparse-checkout"], {
1536
+ readOnly: true,
1537
+ })
1538
+ ).trim();
1539
+ const sparsePatterns = await readOptionalText(sparsePatternPath);
1540
+ // Status parity with the source: an explicit core.filemode (e.g. false on
1541
+ // mounts ignoring the executable bit) must carry over, or the re-inited
1542
+ // repo's platform default makes clean files read as mode-changed and delta
1543
+ // capture would apply bogus chmod diffs back to the parent.
1544
+ const fileMode = await config.get(worktreeRoot, "core.fileMode");
1545
+ // A split index references sharedindex.* files beside the source index;
1546
+ // restoring the raw index without them makes every git read fail. Carry the
1547
+ // shared files (and the config) alongside the verbatim index bytes.
1548
+ const splitIndex = await config.get(worktreeRoot, "core.splitIndex");
1549
+ const sharedIndexFiles: Array<{ name: string; bytes: Uint8Array }> = [];
1550
+ if (indexBytes) {
1551
+ const indexDir = path.dirname(indexPath);
1552
+ let entries: string[] = [];
1553
+ try {
1554
+ entries = await fs.promises.readdir(indexDir);
1555
+ } catch {}
1556
+ for (const name of entries) {
1557
+ if (!name.startsWith("sharedindex.")) continue;
1558
+ const bytes = await readOptionalBytes(path.join(indexDir, name));
1559
+ if (bytes) sharedIndexFiles.push({ name, bytes });
1560
+ }
1561
+ }
1562
+ // A shallow source deliberately lacks parents beyond its `shallow` boundary
1563
+ // file; without it, history traversal over the borrowed objects treats the
1564
+ // boundary commit's missing parent as corruption.
1565
+ const shallowBoundary = await readOptionalText(path.join(parentCommon, "shallow"));
1566
+
1567
+ // A pointer `.git` file whose worktree-admin dir back-references this exact
1568
+ // tree is the rcopy `git worktree add` registration. Remove that admin entry
1569
+ // so the source repo's worktree list stops tracking the isolation. A pointer
1570
+ // referencing the *source's* admin (a copied linked-worktree `.git`) is not
1571
+ // ours to delete — only the local pointer file is discarded. Compare via
1572
+ // realpath: git canonicalizes the back-reference (e.g. macOS `/var` →
1573
+ // `/private/var`), so a lexical path comparison would miss the match and
1574
+ // leave a stale registration in the source repo's worktree list.
1575
+ let ownWorktreeAdmin: string | undefined;
1576
+ if (entryStat.isFile()) {
1577
+ const pointer = parseGitDirPointer((await readOptionalText(gitEntry)) ?? "");
1578
+ if (pointer) {
1579
+ const adminDir = path.resolve(path.dirname(gitEntry), pointer);
1580
+ const backRef = (await readOptionalText(path.join(adminDir, "gitdir")))?.trim();
1581
+ if (backRef) {
1582
+ const [realBackRef, realGitEntry] = await Promise.all([
1583
+ fs.promises.realpath(backRef).catch(() => path.resolve(backRef)),
1584
+ fs.promises.realpath(gitEntry).catch(() => path.resolve(gitEntry)),
1585
+ ]);
1586
+ if (realBackRef === realGitEntry) ownWorktreeAdmin = adminDir;
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ await fs.promises.rm(gitEntry, { recursive: true, force: true });
1592
+ if (ownWorktreeAdmin) await fs.promises.rm(ownWorktreeAdmin, { recursive: true, force: true });
1593
+
1594
+ // Preserve the checked-out branch name so an unborn HEAD (fresh/orphan
1595
+ // branch with no commits) keeps its symbolic ref after `init` rather than
1596
+ // snapping to the init default; born HEADs get the ref rewritten below anyway.
1597
+ const initArgs = ["init", "--object-format", objectFormat, "-q"];
1598
+ const initialBranch = headRef.startsWith(LOCAL_BRANCH_PREFIX) ? headRef.slice(LOCAL_BRANCH_PREFIX.length) : "";
1599
+ if (initialBranch) initArgs.push("-b", initialBranch);
1600
+ await runEffect(worktreeRoot, initArgs);
1601
+ const objectsInfo = path.join(gitEntry, "objects", "info");
1602
+ await fs.promises.mkdir(objectsInfo, { recursive: true });
1603
+ const alternates = [path.join(parentCommon, "objects")];
1604
+ const chained = await readOptionalText(path.join(parentCommon, "objects", "info", "alternates"));
1605
+ if (chained) {
1606
+ for (const line of chained.split("\n")) {
1607
+ const entry = line.trim();
1608
+ if (!entry) continue;
1609
+ alternates.push(path.isAbsolute(entry) ? entry : path.resolve(parentCommon, "objects", entry));
1610
+ }
1611
+ }
1612
+ await Bun.write(path.join(objectsInfo, "alternates"), `${alternates.join("\n")}\n`);
1613
+
1614
+ // Freeze refs when HEAD is born. Point HEAD at the raw SHA first so
1615
+ // `update-ref` writes land even for the branch HEAD currently names, then
1616
+ // restore the symbolic HEAD. An unborn HEAD has no refs to freeze; `init -b`
1617
+ // above already set the symbolic HEAD to the unborn branch.
1618
+ if (headSha) {
1619
+ await Bun.write(path.join(gitEntry, "HEAD"), `${headSha}\n`);
1620
+ if (refDump) {
1621
+ const commands = refDump
1622
+ .split("\n")
1623
+ .filter(Boolean)
1624
+ .map(line => {
1625
+ const sep = line.indexOf(" ");
1626
+ return `create ${line.slice(sep + 1)} ${line.slice(0, sep)}`;
1627
+ })
1628
+ .join("\n");
1629
+ await runEffect(worktreeRoot, ["update-ref", "--stdin"], { stdin: `${commands}\n` });
1630
+ }
1631
+ if (headRef) await Bun.write(path.join(gitEntry, "HEAD"), `ref: ${headRef}\n`);
1632
+ } else if (headRef && !initialBranch) {
1633
+ // Unborn detached HEAD (no branch, no commit) — restore the raw ref target.
1634
+ await Bun.write(path.join(gitEntry, "HEAD"), `ref: ${headRef}\n`);
1635
+ }
1636
+
1637
+ // Carry the source identity so isolated commits have an author.
1638
+ if (userName) await config.set(worktreeRoot, "user.name", userName);
1639
+ if (userEmail) await config.set(worktreeRoot, "user.email", userEmail);
1640
+ if (fileMode !== undefined) await config.set(worktreeRoot, "core.fileMode", fileMode);
1641
+ if (splitIndex !== undefined) await config.set(worktreeRoot, "core.splitIndex", splitIndex);
1642
+ // Preserve the shallow boundary so history traversal over the borrowed
1643
+ // object DB stops at the boundary instead of failing on missing parents.
1644
+ if (shallowBoundary !== null) await Bun.write(path.join(gitEntry, "shallow"), shallowBoundary);
1645
+
1646
+ // Restore sparse-checkout state before the index so skip-worktree entries
1647
+ // keep resolving against the carried patterns.
1648
+ if (sparseCheckout) await config.set(worktreeRoot, "core.sparseCheckout", sparseCheckout);
1649
+ if (sparseCone) await config.set(worktreeRoot, "core.sparseCheckoutCone", sparseCone);
1650
+ if (sparsePatterns !== null) {
1651
+ const infoDir = path.join(gitEntry, "info");
1652
+ await fs.promises.mkdir(infoDir, { recursive: true });
1653
+ await Bun.write(path.join(infoDir, "sparse-checkout"), sparsePatterns);
1654
+ }
1655
+
1656
+ // Restore the index verbatim (skip-worktree, assume-unchanged, exact stage
1657
+ // entries) so the working tree's dirty set — including sparse-excluded files
1658
+ // — matches the source. Fall back to rebuilding from HEAD only when the
1659
+ // source had no index (a bare-ish/never-staged checkout).
1660
+ if (indexBytes) {
1661
+ for (const shared of sharedIndexFiles) {
1662
+ await Bun.write(path.join(gitEntry, shared.name), shared.bytes);
1663
+ }
1664
+ await Bun.write(path.join(gitEntry, "index"), indexBytes);
1665
+ } else if (headSha) {
1666
+ await readTree(worktreeRoot, headSha);
1667
+ }
1668
+ return "detached";
1669
+ }
1670
+
1671
+ // ════════════════════════════════════════════════════════════════════════════
1672
+ // API: show
1673
+ // ════════════════════════════════════════════════════════════════════════════
1674
+
1675
+ /** Run `git show` on a revision. */
1676
+ export const show = Object.assign(
1677
+ async function show(
1678
+ cwd: string,
1679
+ revision: string,
1680
+ options: { format?: string; signal?: AbortSignal } = {},
1681
+ ): Promise<string> {
1682
+ return runText(cwd, ["show", `--format=${options.format ?? ""}`, revision], {
1683
+ readOnly: true,
1684
+ signal: options.signal,
1685
+ });
1686
+ },
1687
+ {
1688
+ /** Get the path prefix of the current directory relative to the repo root. */
1689
+ async prefix(cwd: string, signal?: AbortSignal): Promise<string> {
1690
+ return (await runText(cwd, ["rev-parse", "--show-prefix"], { readOnly: true, signal })).trim();
1691
+ },
1692
+ },
1693
+ );
1694
+
1695
+ /** Read commit message and author metadata for replay/rewrite flows. */
1696
+ export async function commitDetails(cwd: string, revision: string, signal?: AbortSignal): Promise<CommitDetails> {
1697
+ const raw = await runText(cwd, ["show", "-s", "--format=%an%x00%ae%x00%aI%x00%B", revision], {
1698
+ readOnly: true,
1699
+ signal,
1700
+ });
1701
+ const [name = "", email = "", date = "", ...messageParts] = raw.split("\0");
1702
+ return {
1703
+ author: { date, email, name },
1704
+ message: messageParts.join("\0").replace(/\n$/, ""),
1705
+ };
1706
+ }
1707
+
1708
+ // ════════════════════════════════════════════════════════════════════════════
1709
+ // API: log
1710
+ // ════════════════════════════════════════════════════════════════════════════
1711
+
1712
+ export const log = {
1713
+ /** Recent commit subjects (one-line each). */
1714
+ async subjects(cwd: string, count: number, signal?: AbortSignal): Promise<string[]> {
1715
+ return splitLines(await runText(cwd, ["log", `-n${count}`, "--pretty=format:%s"], { readOnly: true, signal }));
1716
+ },
1717
+ /** Recent commits as `<short-sha> <subject>` onelines. */
1718
+ async onelines(cwd: string, count: number, signal?: AbortSignal): Promise<string[]> {
1719
+ return splitLines(
1720
+ await runText(cwd, ["log", `-${count}`, "--oneline", "--no-decorate"], { readOnly: true, signal }),
1721
+ );
1722
+ },
1723
+ };
1724
+
1725
+ export const revList = {
1726
+ /** Commits in `base..head`, oldest first. */
1727
+ async range(cwd: string, base: string, head: string, signal?: AbortSignal): Promise<string[]> {
1728
+ return splitLines(await runText(cwd, ["rev-list", "--reverse", `${base}..${head}`], { readOnly: true, signal }));
1729
+ },
1730
+ /** Commits reachable from `ref` that touched `file`, newest first, capped at `limit`. */
1731
+ async touching(cwd: string, ref: string, file: string, limit: number, signal?: AbortSignal): Promise<string[]> {
1732
+ return splitLines(
1733
+ await runText(cwd, ["rev-list", `--max-count=${limit}`, ref, "--", file], { readOnly: true, signal }),
1734
+ );
1735
+ },
1736
+ };
1737
+
1738
+ // ════════════════════════════════════════════════════════════════════════════
1739
+ // API: branch
1740
+ // ════════════════════════════════════════════════════════════════════════════
1741
+
1742
+ export const branch = {
1743
+ /** Current branch name, or null if detached/unavailable. */
1744
+ async current(cwd: string, signal?: AbortSignal): Promise<string | null> {
1745
+ const headState = await resolveHead(cwd);
1746
+ if (headState?.kind === "ref") return headState.branchName ?? headState.ref;
1747
+ const result = await git(cwd, ["symbolic-ref", "--short", "HEAD"], { readOnly: true, signal });
1748
+ if (result.exitCode !== 0) return null;
1749
+ return result.stdout.trim() || null;
1750
+ },
1751
+
1752
+ /** Default branch name (from remote HEAD refs). */
1753
+ async default(cwd: string, signal?: AbortSignal): Promise<string | null> {
1754
+ const repository = await resolveRepository(cwd);
1755
+ if (repository) {
1756
+ for (const refPath of DEFAULT_BRANCH_REFS) {
1757
+ const target = await readRef(repository, refPath, signal);
1758
+ const branchName = parseDefaultBranchRef(refPath, target);
1759
+ if (branchName) return branchName;
1760
+ }
1761
+ }
1762
+ for (const remoteRef of ["origin/HEAD", "upstream/HEAD"]) {
1763
+ const result = await git(cwd, ["rev-parse", "--abbrev-ref", remoteRef], { readOnly: true, signal });
1764
+ if (result.exitCode !== 0) continue;
1765
+ const branchName = stripRemotePrefix(result.stdout.trim());
1766
+ if (branchName) return branchName;
1767
+ }
1768
+ return null;
1769
+ },
1770
+
1771
+ /** Create a new branch at the given start point. */
1772
+ async create(cwd: string, name: string, startPoint = "HEAD", signal?: AbortSignal): Promise<void> {
1773
+ await runEffect(cwd, ["branch", name, startPoint], { signal });
1774
+ },
1775
+
1776
+ /** Force-move a branch to a new start point. */
1777
+ async force(cwd: string, name: string, startPoint: string, signal?: AbortSignal): Promise<void> {
1778
+ await runEffect(cwd, ["branch", "--force", name, startPoint], { signal });
1779
+ },
1780
+
1781
+ /** Delete a branch. Throws on failure. */
1782
+ async delete(cwd: string, name: string, options: { force?: boolean; signal?: AbortSignal } = {}): Promise<void> {
1783
+ await runEffect(cwd, ["branch", options.force === false ? "-d" : "-D", name], { signal: options.signal });
1784
+ },
1785
+
1786
+ /** Delete a branch. Returns false on failure instead of throwing. */
1787
+ async tryDelete(
1788
+ cwd: string,
1789
+ name: string,
1790
+ options: { force?: boolean; signal?: AbortSignal } = {},
1791
+ ): Promise<boolean> {
1792
+ const result = await git(cwd, ["branch", options.force === false ? "-d" : "-D", name], {
1793
+ signal: options.signal,
1794
+ });
1795
+ return result.exitCode === 0;
1796
+ },
1797
+
1798
+ /** Create and checkout a new branch. */
1799
+ async checkoutNew(cwd: string, name: string, signal?: AbortSignal): Promise<void> {
1800
+ await runEffect(cwd, ["checkout", "-b", name], { signal });
1801
+ },
1802
+
1803
+ /** List branches. Pass `{ all: true }` to include remotes. */
1804
+ async list(cwd: string, options: { all?: boolean; signal?: AbortSignal } = {}): Promise<string[]> {
1805
+ const args = ["branch"];
1806
+ if (options.all) args.push("-a");
1807
+ args.push("--format=%(refname:short)");
1808
+ return splitLines(await runText(cwd, args, { readOnly: true, signal: options.signal }));
1809
+ },
1810
+ };
1811
+
1812
+ // ════════════════════════════════════════════════════════════════════════════
1813
+ // API: remote
1814
+ // ════════════════════════════════════════════════════════════════════════════
1815
+
1816
+ export const remote = {
1817
+ /** List remote names. */
1818
+ async list(cwd: string, signal?: AbortSignal): Promise<string[]> {
1819
+ return splitLines(await runText(cwd, ["remote"], { readOnly: true, signal }));
1820
+ },
1821
+
1822
+ /** Get the URL for a remote. */
1823
+ async url(cwd: string, name: string, signal?: AbortSignal): Promise<string | undefined> {
1824
+ return trimScalar(await tryText(cwd, ["remote", "get-url", name], { readOnly: true, signal }));
1825
+ },
1826
+
1827
+ /**
1828
+ * Add a remote pointing at `url`. Idempotent: if a remote named `name`
1829
+ * already exists with the same URL (e.g. an in-process race or a leftover
1830
+ * remote from a previous run), this is treated as success. Throws when the
1831
+ * remote exists with a different URL — that's a real conflict the caller
1832
+ * needs to resolve, not paper over.
1833
+ */
1834
+ async add(cwd: string, name: string, url: string, signal?: AbortSignal): Promise<void> {
1835
+ const result = await git(cwd, ["remote", "add", name, url], { signal });
1836
+ if (result.exitCode === 0) return;
1837
+ const existing = await remote.url(cwd, name, signal);
1838
+ if (existing !== undefined) {
1839
+ if (existing === url) return;
1840
+ throw new ToolError(`remote ${name} already exists with URL ${existing}, expected ${url}`);
1841
+ }
1842
+ throw new GitCommandError(["remote", "add", name, url], result);
1843
+ },
1844
+ };
1845
+
1846
+ // ════════════════════════════════════════════════════════════════════════════
1847
+ // API: ref
1848
+ // ════════════════════════════════════════════════════════════════════════════
1849
+
1850
+ export const ref = {
1851
+ /** Check if a ref exists. */
1852
+ async exists(cwd: string, refName: string, signal?: AbortSignal): Promise<boolean> {
1853
+ if (refName === "HEAD") return (await head.sha(cwd, signal)) !== null;
1854
+ const repository = await resolveRepository(cwd);
1855
+ if (repository && refName.startsWith("refs/")) return (await readRef(repository, refName, signal)) !== null;
1856
+ const result = await git(cwd, ["show-ref", "--verify", "--quiet", refName], { readOnly: true, signal });
1857
+ return result.exitCode === 0;
1858
+ },
1859
+
1860
+ /** Resolve a ref to its commit SHA. */
1861
+ async resolve(cwd: string, refName: string, signal?: AbortSignal): Promise<string | null> {
1862
+ if (refName === "HEAD") return head.sha(cwd, signal);
1863
+ const repository = await resolveRepository(cwd);
1864
+ if (repository && refName.startsWith("refs/")) return readRef(repository, refName, signal);
1865
+ const result = await git(cwd, ["rev-parse", refName], { readOnly: true, signal });
1866
+ if (result.exitCode !== 0) return null;
1867
+ return result.stdout.trim() || null;
1868
+ },
1869
+
1870
+ /** Tags pointing at a ref. */
1871
+ async tags(cwd: string, refName = "HEAD", signal?: AbortSignal): Promise<string[]> {
1872
+ return splitLines(
1873
+ await runText(
1874
+ cwd,
1875
+ [
1876
+ "for-each-ref",
1877
+ "--points-at",
1878
+ refName,
1879
+ "--sort=-version:refname",
1880
+ "--format=%(refname:strip=2)",
1881
+ "refs/tags",
1882
+ ],
1883
+ { readOnly: true, signal },
1884
+ ),
1885
+ );
1886
+ },
1887
+ };
1888
+
1889
+ // ════════════════════════════════════════════════════════════════════════════
1890
+ // API: config
1891
+ // ════════════════════════════════════════════════════════════════════════════
1892
+
1893
+ export const config = {
1894
+ async get(cwd: string, key: string, signal?: AbortSignal): Promise<string | undefined> {
1895
+ return trimScalar(await tryText(cwd, ["config", "--get", key], { readOnly: true, signal }));
1896
+ },
1897
+
1898
+ async set(cwd: string, key: string, value: string, signal?: AbortSignal): Promise<void> {
1899
+ await runEffect(cwd, ["config", key, value], { signal });
1900
+ },
1901
+
1902
+ async getBranch(cwd: string, branchName: string, key: string, signal?: AbortSignal): Promise<string | undefined> {
1903
+ return config.get(cwd, `branch.${branchName}.${key}`, signal);
1904
+ },
1905
+
1906
+ async setBranch(cwd: string, branchName: string, key: string, value: string, signal?: AbortSignal): Promise<void> {
1907
+ return config.set(cwd, `branch.${branchName}.${key}`, value, signal);
1908
+ },
1909
+ };
1910
+
1911
+ // ════════════════════════════════════════════════════════════════════════════
1912
+ // API: worktree
1913
+ // ════════════════════════════════════════════════════════════════════════════
1914
+
1915
+ export const worktree = {
1916
+ async add(
1917
+ cwd: string,
1918
+ worktreePath: string,
1919
+ refName: string,
1920
+ options: { detach?: boolean; signal?: AbortSignal } = {},
1921
+ ): Promise<void> {
1922
+ const args = ["worktree", "add"];
1923
+ if (options.detach) args.push("--detach");
1924
+ args.push(worktreePath, refName);
1925
+ await runEffect(cwd, args, { signal: options.signal });
1926
+ },
1927
+
1928
+ async remove(
1929
+ cwd: string,
1930
+ worktreePath: string,
1931
+ options: { force?: boolean; signal?: AbortSignal } = {},
1932
+ ): Promise<void> {
1933
+ const args = ["worktree", "remove"];
1934
+ if (options.force ?? true) args.push("-f");
1935
+ args.push(worktreePath);
1936
+ await runEffect(cwd, args, { signal: options.signal });
1937
+ },
1938
+
1939
+ async tryRemove(
1940
+ cwd: string,
1941
+ worktreePath: string,
1942
+ options: { force?: boolean; signal?: AbortSignal } = {},
1943
+ ): Promise<boolean> {
1944
+ const args = ["worktree", "remove"];
1945
+ if (options.force ?? true) args.push("-f");
1946
+ args.push(worktreePath);
1947
+ const result = await git(cwd, args, { signal: options.signal });
1948
+ return result.exitCode === 0;
1949
+ },
1950
+
1951
+ async list(cwd: string, signal?: AbortSignal): Promise<GitWorktreeEntry[]> {
1952
+ return parseWorktreeList(await runText(cwd, ["worktree", "list", "--porcelain"], { readOnly: true, signal }));
1953
+ },
1954
+
1955
+ async prune(cwd: string, signal?: AbortSignal): Promise<void> {
1956
+ await runEffect(cwd, ["worktree", "prune"], { signal });
1957
+ },
1958
+ };
1959
+
1960
+ // ════════════════════════════════════════════════════════════════════════════
1961
+ // API: patch
1962
+ // ════════════════════════════════════════════════════════════════════════════
1963
+
1964
+ export const patch = {
1965
+ /** Apply a patch file. */
1966
+ async apply(cwd: string, patchPath: string, options: PatchOptions = {}): Promise<void> {
1967
+ await runEffect(cwd, buildApplyArgs(patchPath, options), { env: options.env, signal: options.signal });
1968
+ },
1969
+
1970
+ /** Apply a patch from a string (writes to a temp file). */
1971
+ async applyText(cwd: string, patchText: string, options: PatchOptions = {}): Promise<void> {
1972
+ if (!patchText.trim()) return;
1973
+ const tempPath = await writeTempPatch(patchText);
1974
+ try {
1975
+ await patch.apply(cwd, tempPath, options);
1976
+ } finally {
1977
+ await fs.promises.rm(tempPath, { force: true });
1978
+ }
1979
+ },
1980
+
1981
+ /** Check if a patch file can be applied cleanly. */
1982
+ async canApply(cwd: string, patchPath: string, options: Omit<PatchOptions, "check"> = {}): Promise<boolean> {
1983
+ const result = await git(cwd, buildApplyArgs(patchPath, { ...options, check: true }), {
1984
+ env: options.env,
1985
+ readOnly: true,
1986
+ signal: options.signal,
1987
+ });
1988
+ return result.exitCode === 0;
1989
+ },
1990
+
1991
+ /** Check if a patch string can be applied cleanly. */
1992
+ async canApplyText(cwd: string, patchText: string, options: Omit<PatchOptions, "check"> = {}): Promise<boolean> {
1993
+ if (!patchText.trim()) return true;
1994
+ const tempPath = await writeTempPatch(patchText);
1995
+ try {
1996
+ return await patch.canApply(cwd, tempPath, options);
1997
+ } finally {
1998
+ await fs.promises.rm(tempPath, { force: true });
1999
+ }
2000
+ },
2001
+
2002
+ /** Join patch parts into a single patch string. */
2003
+ join(parts: string[]): string {
2004
+ return `${parts
2005
+ .map(part => (part.endsWith("\n") ? part : `${part}\n`))
2006
+ .join("\n")
2007
+ .replace(/\n+$/, "")}\n`;
2008
+ },
2009
+ };
2010
+
2011
+ // ════════════════════════════════════════════════════════════════════════════
2012
+ // API: cherryPick
2013
+ // ════════════════════════════════════════════════════════════════════════════
2014
+
2015
+ export const cherryPick = Object.assign(
2016
+ async function cherryPick(cwd: string, revision: string, signal?: AbortSignal): Promise<void> {
2017
+ await runEffect(cwd, ["cherry-pick", revision], { signal });
2018
+ },
2019
+ {
2020
+ async abort(cwd: string, signal?: AbortSignal): Promise<void> {
2021
+ await runEffect(cwd, ["cherry-pick", "--abort"], { signal });
2022
+ },
2023
+ /**
2024
+ * Skip the current commit of an in-progress cherry-pick sequence and
2025
+ * continue with the rest of the range. Use after {@link isEmptyError}
2026
+ * reports the current attempt collapsed to a no-op — the alternative,
2027
+ * `--abort`, throws away every remaining commit in the range.
2028
+ */
2029
+ async skip(cwd: string, signal?: AbortSignal): Promise<void> {
2030
+ await runEffect(cwd, ["cherry-pick", "--skip"], { signal });
2031
+ },
2032
+ /**
2033
+ * True when a cherry-pick failure was caused by the current commit
2034
+ * being empty against HEAD — either redundant with an already-applied
2035
+ * change, or auto-resolved to HEAD by a 3-way merge. Callers should
2036
+ * `--skip` in this case to advance the sequencer rather than aborting
2037
+ * the whole range: an empty commit is not a merge conflict, and any
2038
+ * later commits in the range still deserve to land.
2039
+ */
2040
+ isEmptyError(err: unknown): boolean {
2041
+ return err instanceof GitCommandError && /the previous cherry-pick is now empty/i.test(err.result.stderr);
2042
+ },
2043
+ },
2044
+ );
2045
+
2046
+ // ════════════════════════════════════════════════════════════════════════════
2047
+ // API: stash
2048
+ // ════════════════════════════════════════════════════════════════════════════
2049
+
2050
+ export const stash = {
2051
+ /** Stash working tree + index changes. Returns true when git created a new stash entry. */
2052
+ async push(cwd: string, message?: string): Promise<boolean> {
2053
+ ensureAvailable();
2054
+ const previousStash = await ref.resolve(cwd, "refs/stash");
2055
+ const args = ["stash", "push", "--include-untracked"];
2056
+ if (message) args.push("-m", message);
2057
+ await runEffect(cwd, args);
2058
+ const nextStash = await ref.resolve(cwd, "refs/stash");
2059
+ return nextStash !== null && nextStash !== previousStash;
2060
+ },
2061
+ /** Pop the most recent stash entry, optionally restoring its staged state. */
2062
+ async pop(cwd: string, options?: { index?: boolean }): Promise<void> {
2063
+ const args = ["stash", "pop"];
2064
+ if (options?.index) args.push("--index");
2065
+ await runEffect(cwd, args);
2066
+ },
2067
+ /**
2068
+ * Return the working-tree patch that `stash@{0}` would apply, in a form
2069
+ * that `git apply --check` can consume. Empty string when no stash entry
2070
+ * exists or the stash contains no diffable working-tree changes.
2071
+ */
2072
+ async showPatch(cwd: string): Promise<string> {
2073
+ return (await tryText(cwd, ["stash", "show", "-p", "--binary", "stash@{0}"], { readOnly: true })) ?? "";
2074
+ },
2075
+ /** Return untracked paths stored in the top stash entry. */
2076
+ async untrackedFiles(cwd: string): Promise<string[]> {
2077
+ const output = await tryText(cwd, ["ls-tree", "-r", "-z", "--name-only", "stash@{0}^3"], { readOnly: true });
2078
+ return output?.split("\0").filter(Boolean) ?? [];
2079
+ },
2080
+ /**
2081
+ * Attempt to restore the top stash entry. On success returns `true` and
2082
+ * git drops the stash entry. On conflict returns `false`, leaves the stash
2083
+ * entry preserved for manual resolution, and guarantees the failed restore
2084
+ * leaves no unmerged index entries or partially-restored untracked files.
2085
+ *
2086
+ * The historical raw `pop` catches the failure in a `finally` block and
2087
+ * only logs — it leaves `.git/index` with stage 1/2/3 unmerged entries
2088
+ * that survive indefinitely, corrupting every subsequent overlay-isolated
2089
+ * task that reads through this repo's `.git/`. See issue #4175.
2090
+ */
2091
+ async tryPop(cwd: string, options?: { index?: boolean }): Promise<boolean> {
2092
+ // Preflight: `git stash pop` internally does a 3-way merge, so a plain
2093
+ // `git apply --check` is too strict — it rejects hunks whose context
2094
+ // drifted from HEAD even when 3-way merge would resolve them cleanly.
2095
+ // Match pop's semantics with `--3way --check`, which succeeds iff the
2096
+ // patch either applies directly or merges without conflict against
2097
+ // the patch's `index abc..def` base blobs.
2098
+ const workingPatch = await stash.showPatch(cwd);
2099
+ if (workingPatch.trim() && !(await patch.canApplyText(cwd, workingPatch, { threeWay: true }))) {
2100
+ return false;
2101
+ }
2102
+ const restoredUntracked = await stash.untrackedFiles(cwd);
2103
+ try {
2104
+ await stash.pop(cwd, options);
2105
+ return true;
2106
+ } catch {
2107
+ // Preflight can still miss mode-only or delete/modify conflicts. If
2108
+ // the pop left unmerged entries, wipe them: HEAD holds the merged
2109
+ // state so `reset --hard HEAD` restores a clean index and working
2110
+ // tree without losing the cherry-picked commits. A failed pop can
2111
+ // still restore unrelated untracked files before exiting while
2112
+ // preserving the stash entry, so clean only the untracked paths
2113
+ // recorded in that stash. The user's WIP remains recoverable via
2114
+ // `git stash pop`.
2115
+ try {
2116
+ await reset(cwd, { hard: true });
2117
+ } catch {
2118
+ /* best-effort cleanup — do not mask the primary conflict */
2119
+ }
2120
+ if (restoredUntracked.length > 0) {
2121
+ try {
2122
+ await clean(cwd, { includeIgnored: true, literalPathspecs: true, paths: restoredUntracked });
2123
+ } catch {
2124
+ /* best-effort cleanup — do not mask the primary conflict */
2125
+ }
2126
+ }
2127
+ return false;
2128
+ }
2129
+ },
2130
+ };
2131
+
2132
+ // ════════════════════════════════════════════════════════════════════════════
2133
+ // API: clone, restore, clean
2134
+ // ════════════════════════════════════════════════════════════════════════════
2135
+
2136
+ export async function clone(url: string, targetDir: string, options: CloneOptions = {}): Promise<void> {
2137
+ ensureAvailable();
2138
+ const absoluteTarget = path.resolve(targetDir);
2139
+ await fs.promises.mkdir(path.dirname(absoluteTarget), { recursive: true });
2140
+
2141
+ // `git clone --depth 1 --single-branch` only fetches the tip of the target
2142
+ // branch, so any subsequent `git checkout <sha>` for a non-tip commit fails
2143
+ // with "reference is not a tree". When the caller pinned a specific SHA we
2144
+ // fall back to a full clone so the object is guaranteed to be present.
2145
+ const shallow = !options.sha;
2146
+ const args = ["clone"];
2147
+ if (shallow) args.push("--depth", "1");
2148
+ if (options.ref) args.push("--branch", options.ref, "--single-branch");
2149
+ else if (shallow) args.push("--single-branch");
2150
+ args.push(url, absoluteTarget);
2151
+
2152
+ try {
2153
+ await runEffect(path.dirname(absoluteTarget), args, {
2154
+ signal: options.signal,
2155
+ timeoutMs: resolveTimeoutMs(options.timeoutMs, GIT_NETWORK_TIMEOUT_MS),
2156
+ });
2157
+ if (options.sha) {
2158
+ try {
2159
+ await checkout(absoluteTarget, options.sha, options.signal);
2160
+ } catch {
2161
+ await fs.promises.rm(absoluteTarget, { force: true, recursive: true });
2162
+ throw new Error(`Failed to checkout SHA ${options.sha} in cloned repository ${url}`);
2163
+ }
2164
+ }
2165
+ } catch (err) {
2166
+ await fs.promises.rm(absoluteTarget, { force: true, recursive: true });
2167
+ throw err;
2168
+ }
2169
+ }
2170
+
2171
+ export async function restore(cwd: string, options: RestoreOptions = {}): Promise<void> {
2172
+ const args = ["restore"];
2173
+ if (options.source) args.push(`--source=${options.source}`);
2174
+ if (options.staged) args.push("--staged");
2175
+ if (options.worktree) args.push("--worktree");
2176
+ if (options.files?.length) args.push("--", ...options.files);
2177
+ await runEffect(cwd, args, { signal: options.signal });
2178
+ }
2179
+
2180
+ /**
2181
+ * Run `git reset` with options. Default is a soft reset (no flag); pass `hard: true` for a destructive reset.
2182
+ *
2183
+ * NOTE: stage.reset() handles the per-file unstaging case. This helper exists for tree-wide resets.
2184
+ */
2185
+ export async function reset(
2186
+ cwd: string,
2187
+ options: { hard?: boolean; mixed?: boolean; soft?: boolean; target?: string; signal?: AbortSignal } = {},
2188
+ ): Promise<void> {
2189
+ const args = ["reset"];
2190
+ if (options.hard) args.push("--hard");
2191
+ else if (options.mixed) args.push("--mixed");
2192
+ else if (options.soft) args.push("--soft");
2193
+ if (options.target) args.push(options.target);
2194
+ await runEffect(cwd, args, { signal: options.signal });
2195
+ }
2196
+
2197
+ export async function clean(
2198
+ cwd: string,
2199
+ options: {
2200
+ ignoredOnly?: boolean;
2201
+ includeIgnored?: boolean;
2202
+ literalPathspecs?: boolean;
2203
+ paths?: readonly string[];
2204
+ signal?: AbortSignal;
2205
+ } = {},
2206
+ ): Promise<void> {
2207
+ const args = [options.literalPathspecs ? "--literal-pathspecs" : undefined, "clean"].filter(
2208
+ (arg): arg is string => arg !== undefined,
2209
+ );
2210
+ args.push(options.ignoredOnly ? "-fdX" : options.includeIgnored ? "-fdx" : "-fd");
2211
+ if (options.paths?.length) args.push("--", ...options.paths);
2212
+ await runEffect(cwd, args, { signal: options.signal });
2213
+ }
2214
+
2215
+ // ════════════════════════════════════════════════════════════════════════════
2216
+ // API: ls
2217
+ // ════════════════════════════════════════════════════════════════════════════
2218
+
2219
+ export const ls = {
2220
+ /** List files tracked or untracked by git. */
2221
+ async files(
2222
+ cwd: string,
2223
+ options: { others?: boolean; excludeStandard?: boolean; signal?: AbortSignal } = {},
2224
+ ): Promise<string[]> {
2225
+ const args = ["ls-files"];
2226
+ if (options.others) args.push("--others");
2227
+ if (options.excludeStandard) args.push("--exclude-standard");
2228
+ return splitLines(await runText(cwd, args, { readOnly: true, signal: options.signal }));
2229
+ },
2230
+
2231
+ /** List untracked files (excludes ignored). */
2232
+ async untracked(cwd: string, signal?: AbortSignal): Promise<string[]> {
2233
+ return ls.files(cwd, { others: true, excludeStandard: true, signal });
2234
+ },
2235
+
2236
+ /** List paths present in a ref, optionally filtered to specific paths. */
2237
+ async tree(cwd: string, ref: string, files: readonly string[] = [], signal?: AbortSignal): Promise<string[]> {
2238
+ const args = ["ls-tree", "--name-only", "-r", "-z", ref];
2239
+ if (files.length > 0) args.push("--", ...files);
2240
+ const raw = await runText(cwd, args, { readOnly: true, signal });
2241
+ return raw.split("\0").filter(entry => entry.length > 0);
2242
+ },
2243
+
2244
+ /** List submodule paths (recursive). */
2245
+ async submodules(cwd: string, signal?: AbortSignal): Promise<string[]> {
2246
+ const output = await git(cwd, ["submodule", "--quiet", "foreach", "--recursive", "echo $sm_path"], {
2247
+ readOnly: true,
2248
+ signal,
2249
+ });
2250
+ return splitLines(output.stdout);
2251
+ },
2252
+ };
2253
+
2254
+ // ════════════════════════════════════════════════════════════════════════════
2255
+ // API: head
2256
+ // ════════════════════════════════════════════════════════════════════════════
2257
+
2258
+ export const head = {
2259
+ /** Full HEAD state (branch, commit, repo info). */
2260
+ async resolve(cwd: string, signal?: AbortSignal): Promise<GitHeadState | null> {
2261
+ const repository = await resolveRepository(cwd);
2262
+ if (!repository) return null;
2263
+ if (await isReftableRepo(repository)) {
2264
+ return resolveHeadStateReftable(repository, signal);
2265
+ }
2266
+ const content = await readOptionalText(repository.headPath);
2267
+ if (content === null) return null;
2268
+ return parseHeadState(repository, content);
2269
+ },
2270
+
2271
+ /** Full HEAD state (synchronous). */
2272
+ resolveSync(cwd: string): GitHeadState | null {
2273
+ const repository = resolveRepositorySync(cwd);
2274
+ if (!repository) return null;
2275
+ if (isReftableRepoSync(repository)) {
2276
+ return resolveHeadStateReftableSync(repository);
2277
+ }
2278
+ const content = readOptionalTextSync(repository.headPath);
2279
+ if (content === null) return null;
2280
+ return parseHeadStateSync(repository, content);
2281
+ },
2282
+
2283
+ /** Current HEAD commit SHA. */
2284
+ async sha(cwd: string, signal?: AbortSignal): Promise<string | null> {
2285
+ const headState = await head.resolve(cwd, signal);
2286
+ if (headState?.commit) return headState.commit;
2287
+ const result = await git(cwd, ["rev-parse", "HEAD"], { readOnly: true, signal });
2288
+ if (result.exitCode !== 0) return null;
2289
+ return result.stdout.trim() || null;
2290
+ },
2291
+
2292
+ /** Abbreviated HEAD commit SHA. */
2293
+ async short(cwd: string, length = 7, signal?: AbortSignal): Promise<string | null> {
2294
+ const result = await git(cwd, ["rev-parse", `--short=${length}`, "HEAD"], { readOnly: true, signal });
2295
+ if (result.exitCode !== 0) return null;
2296
+ return result.stdout.trim() || null;
2297
+ },
2298
+ };
2299
+
2300
+ // ════════════════════════════════════════════════════════════════════════════
2301
+ // API: repo
2302
+ // ════════════════════════════════════════════════════════════════════════════
2303
+
2304
+ export const repo = {
2305
+ /** Resolve the repository root (may be a worktree root). */
2306
+ async root(cwd: string, signal?: AbortSignal): Promise<string | null> {
2307
+ const repository = await resolveRepository(cwd);
2308
+ if (repository) return repository.repoRoot;
2309
+ const result = await git(cwd, ["rev-parse", "--show-toplevel"], { readOnly: true, signal });
2310
+ if (result.exitCode !== 0) return null;
2311
+ return result.stdout.trim() || null;
2312
+ },
2313
+
2314
+ /** Resolve the primary checkout root, or the shared common dir for bare-repo worktrees. */
2315
+ async primaryRoot(cwd: string, signal?: AbortSignal): Promise<string | null> {
2316
+ const repository = await resolveRepository(cwd);
2317
+ if (repository) return primaryRootFromRepository(repository);
2318
+ const repoRoot = await repo.root(cwd, signal);
2319
+ if (!repoRoot) return null;
2320
+ const commonDir = await runText(repoRoot, ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
2321
+ readOnly: true,
2322
+ signal,
2323
+ });
2324
+ if (path.basename(commonDir.trim()) === ".git") return path.dirname(commonDir.trim());
2325
+ return repoRoot;
2326
+ },
2327
+
2328
+ /**
2329
+ * Sync sibling of {@link primaryRoot}. Resolves only via on-disk `.git`/
2330
+ * `commondir` walking — no subprocess fallback — so it stays usable from
2331
+ * paths where async I/O is impractical (e.g. `computeBankScope`). Returns
2332
+ * `null` when `cwd` is outside a repository. Bare-repo worktrees resolve to
2333
+ * the shared common dir (`foo.git`) because they have no primary checkout.
2334
+ */
2335
+ primaryRootSync(cwd: string): string | null {
2336
+ const repository = resolveRepositorySync(cwd);
2337
+ if (!repository) return null;
2338
+ return primaryRootFromRepositorySync(repository);
2339
+ },
2340
+
2341
+ /**
2342
+ * Linked-worktree metadata for `cwd`, or `null` when `cwd` is the primary
2343
+ * checkout (or outside a repository). `root` is the worktree's own checkout
2344
+ * root; `primaryRoot` is the shared main checkout that names the project.
2345
+ * Resolves purely via on-disk `.git`/`commondir` walking — no subprocess —
2346
+ * so the status line may call it on every render.
2347
+ */
2348
+ linkedWorktreeSync(cwd: string): { root: string; primaryRoot: string } | null {
2349
+ const repository = resolveRepositorySync(cwd);
2350
+ if (!repository || !isLinkedWorktree(repository)) return null;
2351
+ return { root: repository.repoRoot, primaryRoot: primaryRootFromRepositorySync(repository) };
2352
+ },
2353
+
2354
+ /** Full GitRepository metadata (sync). */
2355
+ resolveSync(cwd: string): GitRepository | null {
2356
+ return resolveRepositorySync(cwd);
2357
+ },
2358
+
2359
+ /** Full GitRepository metadata. */
2360
+ resolve(cwd: string): Promise<GitRepository | null> {
2361
+ return resolveRepository(cwd);
2362
+ },
2363
+
2364
+ /** Check if the repository uses the reftable reference storage format (sync). */
2365
+ isReftableSync(repository: GitRepository): boolean {
2366
+ return isReftableRepoSync(repository);
2367
+ },
2368
+
2369
+ /** Check if the repository uses the reftable reference storage format. */
2370
+ isReftable(repository: GitRepository): Promise<boolean> {
2371
+ return isReftableRepo(repository);
2372
+ },
2373
+ };
2374
+
2375
+ // Helper used during head resolution — defined here to reference `head` namespace.
2376
+ async function resolveHead(cwd: string, signal?: AbortSignal): Promise<GitHeadState | null> {
2377
+ return head.resolve(cwd, signal);
2378
+ }
2379
+
2380
+ // ════════════════════════════════════════════════════════════════════════════
2381
+ // API: github (GitHub CLI)
2382
+ // ════════════════════════════════════════════════════════════════════════════
2383
+
2384
+ export interface GhCommandResult {
2385
+ exitCode: number;
2386
+ stdout: string;
2387
+ stderr: string;
2388
+ }
2389
+
2390
+ export interface GhCommandOptions {
2391
+ repoProvided?: boolean;
2392
+ trimOutput?: boolean;
2393
+ }
2394
+
2395
+ function formatGhFailure(args: readonly string[], stdout: string, stderr: string, options?: GhCommandOptions): string {
2396
+ const message = (stderr || stdout).trim();
2397
+ if (message.includes("gh auth login") || message.includes("not logged into any GitHub hosts")) {
2398
+ return "GitHub CLI is not authenticated. Run `gh auth login`.";
2399
+ }
2400
+ if (
2401
+ !options?.repoProvided &&
2402
+ (message.includes("not a git repository") ||
2403
+ message.includes("no git remotes found") ||
2404
+ message.includes("unable to determine current repository"))
2405
+ ) {
2406
+ return "GitHub repository context is unavailable. Pass `repo` explicitly or run the tool inside a GitHub checkout.";
2407
+ }
2408
+ if (message.length > 0) return message;
2409
+ return `GitHub CLI command failed: gh ${args.join(" ")}`;
2410
+ }
2411
+
2412
+ export const github = {
2413
+ /** Check if `gh` CLI is installed. */
2414
+ available(): boolean {
2415
+ return Boolean($which("gh"));
2416
+ },
2417
+
2418
+ /** Run a raw `gh` CLI command. Does not throw on non-zero exit. */
2419
+ async run(cwd: string, args: string[], signal?: AbortSignal, options?: GhCommandOptions): Promise<GhCommandResult> {
2420
+ throwIfAborted(signal);
2421
+ if (!$which("gh")) {
2422
+ throw new ToolError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/.");
2423
+ }
2424
+ try {
2425
+ const child = Bun.spawn(["gh", ...args], {
2426
+ cwd,
2427
+ env: buildGhEnv(),
2428
+ stdin: "ignore",
2429
+ stdout: "pipe",
2430
+ stderr: "pipe",
2431
+ windowsHide: true,
2432
+ signal,
2433
+ });
2434
+ const { stdout, stderr, exitCode } = await collectSubprocessResult("gh", args, child, {});
2435
+ throwIfAborted(signal);
2436
+ const trim = options?.trimOutput !== false;
2437
+ return {
2438
+ exitCode: exitCode ?? 0,
2439
+ stdout: trim ? stdout.trim() : stdout,
2440
+ stderr: trim ? stderr.trim() : stderr,
2441
+ };
2442
+ } catch (error) {
2443
+ if (signal?.aborted) throw new ToolAbortError();
2444
+ throw error;
2445
+ }
2446
+ },
2447
+
2448
+ /** Run `gh` and parse stdout as JSON. Throws on non-zero exit or invalid JSON. */
2449
+ async json<T>(cwd: string, args: string[], signal?: AbortSignal, options?: GhCommandOptions): Promise<T> {
2450
+ const result = await github.run(cwd, args, signal, options);
2451
+ if (result.exitCode !== 0) {
2452
+ throw new ToolError(formatGhFailure(args, result.stdout, result.stderr, options));
2453
+ }
2454
+ if (!result.stdout) {
2455
+ throw new ToolError("GitHub CLI returned empty output.");
2456
+ }
2457
+ try {
2458
+ return JSON.parse(result.stdout) as T;
2459
+ } catch {
2460
+ throw new ToolError("GitHub CLI returned invalid JSON output.");
2461
+ }
2462
+ },
2463
+
2464
+ /** Run `gh` and return stdout as text. Throws on non-zero exit. */
2465
+ async text(cwd: string, args: string[], signal?: AbortSignal, options?: GhCommandOptions): Promise<string> {
2466
+ const result = await github.run(cwd, args, signal, options);
2467
+ if (result.exitCode !== 0) {
2468
+ throw new ToolError(formatGhFailure(args, result.stdout, result.stderr, options));
2469
+ }
2470
+ return result.stdout;
2471
+ },
2472
+ };