@aiworker/web 1.22.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/README.md +4 -4
  2. package/bin/cli.js +14 -4
  3. package/bin/cli.test.js +165 -73
  4. package/bin/lib/cli-api-target.js +2 -2
  5. package/bin/lib/cli-args.js +119 -119
  6. package/bin/lib/cli-lifecycle.js +9 -9
  7. package/bin/lib/cli-network.js +3 -3
  8. package/bin/lib/cli-paths.js +3 -3
  9. package/bin/lib/cli-ports.js +2 -2
  10. package/bin/lib/cli-process.js +4 -4
  11. package/bin/lib/cli-startup.js +64 -15
  12. package/bin/lib/cli-tunnel-profiles.js +1 -1
  13. package/bin/lib/cli-tunnel-utils.js +2 -2
  14. package/bin/lib/commands-connect-url.js +19 -19
  15. package/bin/lib/commands-lifecycle.js +27 -27
  16. package/bin/lib/commands-logs.js +5 -5
  17. package/bin/lib/commands-models.js +1 -1
  18. package/bin/lib/commands-projects.js +1 -1
  19. package/bin/lib/commands-schedule.js +1 -1
  20. package/bin/lib/commands-serve.js +29 -29
  21. package/bin/lib/commands-session.js +1 -1
  22. package/bin/lib/commands-startup.js +45 -10
  23. package/bin/lib/commands-status.js +1 -1
  24. package/bin/lib/commands-tunnel.js +40 -40
  25. package/bin/lib/commands-update.js +1 -1
  26. package/bin/lib/commands-update.test.js +6 -6
  27. package/dist/assets/{CodeMirrorCommentWidgets-B3u7UIcE.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
  28. package/dist/assets/DiffView-DXjVsEOw.js +4 -0
  29. package/dist/assets/DirtyBranchSwitchDialog-aig6zJjg.js +1 -0
  30. package/dist/assets/FilesView-CJdG0-wk.js +17 -0
  31. package/dist/assets/GitView-BXsS3jG6.js +6 -0
  32. package/dist/assets/{JsonTreeView-B0BdZEHG.js → JsonTreeView-D_hpXBnF.js} +1 -1
  33. package/dist/assets/LinearIssuesView-BoUiLQG1.js +1 -0
  34. package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
  35. package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
  36. package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
  37. package/dist/assets/{PatchDiff-DvfqDql7.js → PatchDiff-8p6nLJaU.js} +1 -1
  38. package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
  39. package/dist/assets/PlanView-CJTmOF6I.js +5 -0
  40. package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
  41. package/dist/assets/{SettingsWindow-i45U1Cy1.js → SettingsWindow-Cpb4hM6q.js} +1 -1
  42. package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  43. package/dist/assets/{ToolOutputDialog-BTwyh5OF.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
  44. package/dist/assets/ToolPartDiffPreview-DtZLzrSK.js +8 -0
  45. package/dist/assets/{VirtualizedFileDiff-DD8lDVej.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
  46. package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
  47. package/dist/assets/{computeVirtualFileMetrics-BMIRgMHv.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
  48. package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
  49. package/dist/assets/de-DCrsQ5zS.js +10 -0
  50. package/dist/assets/es-Dwv4YfH6.js +16 -0
  51. package/dist/assets/fr-BCWg4Cnx.js +14 -0
  52. package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  53. package/dist/assets/index-B68h5S5B.css +1 -0
  54. package/dist/assets/index-DVwMarHI.js +39 -0
  55. package/dist/assets/{index-CODwDsU_.js → index-jXSK5RrB.js} +1 -1
  56. package/dist/assets/{isDiffPlainText-BqayB8d3.js → isDiffPlainText-B_x_rVon.js} +1 -1
  57. package/dist/assets/{isFilePlainText-QfoiKJNY.js → isFilePlainText-DW_xMer1.js} +1 -1
  58. package/dist/assets/ja-Bruw3RYC.js +16 -0
  59. package/dist/assets/ko-idhXGF8j.js +16 -0
  60. package/dist/assets/main-BkyaQhJX.js +930 -0
  61. package/dist/assets/{main-C5DVOxc6.js → main-CzdPuL0s.js} +2 -2
  62. package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
  63. package/dist/assets/{miniChat-DUJ4irZc.js → miniChat-CCkqKOLp.js} +2 -2
  64. package/dist/assets/{mobile-CcaxB0n4.js → mobile-mvxTo055.js} +2 -2
  65. package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
  66. package/dist/assets/pl-CGZWUqZJ.js +16 -0
  67. package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
  68. package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
  69. package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
  70. package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
  71. package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
  72. package/dist/assets/{shikiHighlight-DYOWuY65.js → shikiHighlight-CRl0SUC9.js} +1 -1
  73. package/dist/assets/surface-oMvhsog3.js +1 -0
  74. package/dist/assets/tr-BK_KiO2V.js +7 -0
  75. package/dist/assets/uk-CG74xV8N.js +16 -0
  76. package/dist/assets/useAppFontEffects-CPKeOpUo.js +7284 -0
  77. package/dist/assets/useGitComparison-DH220WmO.js +1 -0
  78. package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
  79. package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
  80. package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
  81. package/dist/assets/{vendor-codemirror-language-data-DFZik9R2.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
  82. package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
  83. package/dist/assets/{web-components-Bhg83tjf.js → web-components-z7hfnS8n.js} +1 -1
  84. package/dist/assets/zh-CN-T4AXofZI.js +16 -0
  85. package/dist/assets/zh-TW-Dte40sxp.js +16 -0
  86. package/dist/index.html +21 -59
  87. package/dist/mini-chat.html +7 -7
  88. package/dist/mobile.html +5 -5
  89. package/package.json +3 -4
  90. package/server/index.d.ts +15 -0
  91. package/server/index.js +158 -89
  92. package/server/lib/agent-memory/actions.js +2 -2
  93. package/server/lib/agent-memory/feature-flag.js +1 -1
  94. package/server/lib/agent-memory/feature-flag.test.js +8 -8
  95. package/server/lib/agent-memory/routes.js +2 -2
  96. package/server/lib/agent-tool/runtime.js +37 -54
  97. package/server/lib/agent-tool/runtime.test.js +59 -59
  98. package/server/lib/browser-control/broker.js +3 -3
  99. package/server/lib/browser-control/broker.test.js +1 -1
  100. package/server/lib/changelog/update-notes.js +82 -0
  101. package/server/lib/changelog/update-notes.test.js +59 -0
  102. package/server/lib/client-auth/remote-clients.test.js +5 -5
  103. package/server/lib/context-obligatory/runtime.js +7 -7
  104. package/server/lib/context-obligatory/runtime.test.js +5 -5
  105. package/server/lib/data-dir-migration.js +40 -0
  106. package/server/lib/data-dir-migration.test.js +59 -0
  107. package/server/lib/dev-servers/parse.js +2 -2
  108. package/server/lib/dev-servers/parse.test.js +2 -2
  109. package/server/lib/dev-servers/routes.js +1 -1
  110. package/server/lib/dev-tunnel/DOCUMENTATION.md +13 -4
  111. package/server/lib/dev-tunnel/client.js +1 -1
  112. package/server/lib/dev-tunnel/runtime.js +5 -5
  113. package/server/lib/dev-tunnel/tunnel.test.js +21 -1
  114. package/server/lib/dictation/local/model-catalog.js +1 -1
  115. package/server/lib/dictation/local/worker-process.js +2 -2
  116. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  117. package/server/lib/event-stream/global-ws-bridge.js +1 -1
  118. package/server/lib/event-stream/protocol.test.js +4 -4
  119. package/server/lib/event-stream/runtime.test.js +18 -18
  120. package/server/lib/fs/DOCUMENTATION.md +5 -0
  121. package/server/lib/fs/routes.js +68 -26
  122. package/server/lib/fs/routes.test.js +208 -22
  123. package/server/lib/git/DOCUMENTATION.md +32 -6
  124. package/server/lib/git/identity-storage.js +1 -1
  125. package/server/lib/git/issue-2746-longpaths.test.js +4 -4
  126. package/server/lib/git/routes.js +41 -5
  127. package/server/lib/git/service.js +300 -224
  128. package/server/lib/git/service.test.js +596 -28
  129. package/server/lib/github/auth.js +8 -8
  130. package/server/lib/github/pr-status.js +1 -1
  131. package/server/lib/github/routes.js +2 -2
  132. package/server/lib/inherited-env.js +17 -10
  133. package/server/lib/inherited-env.test.js +10 -11
  134. package/server/lib/linear/auth.js +8 -8
  135. package/server/lib/linear/auth.test.js +24 -24
  136. package/server/lib/linear/issues.test.js +6 -6
  137. package/server/lib/linear/mapping.test.js +4 -4
  138. package/server/lib/linear/oauth.js +2 -2
  139. package/server/lib/linear/oauth.test.js +14 -14
  140. package/server/lib/linear/routes.js +5 -5
  141. package/server/lib/linear/routes.test.js +16 -16
  142. package/server/lib/linear/status-runtime.test.js +9 -9
  143. package/server/lib/linear/status.js +1 -1
  144. package/server/lib/linear/status.test.js +15 -15
  145. package/server/lib/linear/teams.test.js +4 -4
  146. package/server/lib/magic-prompts/routes.js +2 -2
  147. package/server/lib/mcp-reconnect/DOCUMENTATION.md +55 -0
  148. package/server/lib/mcp-reconnect/runtime.js +122 -0
  149. package/server/lib/mcp-reconnect/runtime.test.js +140 -0
  150. package/server/lib/message-queue/DOCUMENTATION.md +161 -0
  151. package/server/lib/message-queue/runtime.js +870 -0
  152. package/server/lib/message-queue/runtime.test.js +482 -0
  153. package/server/lib/network-defaults.js +23 -0
  154. package/server/lib/network-defaults.test.js +52 -0
  155. package/server/lib/notifications/apns-runtime.js +17 -17
  156. package/server/lib/notifications/apns-runtime.test.js +11 -11
  157. package/server/lib/notifications/emitter-runtime.js +1 -1
  158. package/server/lib/notifications/emitter-runtime.test.js +1 -1
  159. package/server/lib/notifications/push-runtime.js +7 -7
  160. package/server/lib/notifications/routes.js +1 -1
  161. package/server/lib/notifications/runtime.js +1 -1
  162. package/server/lib/openchamber-control/action-resolution.test.js +10 -10
  163. package/server/lib/openchamber-control/actions.js +24 -24
  164. package/server/lib/openchamber-control/error.js +4 -4
  165. package/server/lib/openchamber-control/routes.js +2 -2
  166. package/server/lib/openchamber-control/routes.test.js +5 -5
  167. package/server/lib/openchamber-control/screenshots.js +1 -1
  168. package/server/lib/openchamber-control/screenshots.test.js +1 -1
  169. package/server/lib/openchamber-control/service.js +50 -50
  170. package/server/lib/openchamber-control/service.test.js +5 -5
  171. package/server/lib/openchamber-sessions/routes.js +124 -22
  172. package/server/lib/openchamber-sessions/routes.test.js +99 -11
  173. package/server/lib/opencode/DOCUMENTATION.md +30 -6
  174. package/server/lib/opencode/auth.js +1 -1
  175. package/server/lib/opencode/bootstrap-runtime.js +8 -6
  176. package/server/lib/opencode/cli-options.js +8 -8
  177. package/server/lib/opencode/config-paths.test.js +116 -0
  178. package/server/lib/opencode/core-routes.js +8 -7
  179. package/server/lib/opencode/core-routes.test.js +11 -11
  180. package/server/lib/opencode/env-config.js +5 -5
  181. package/server/lib/opencode/env-config.test.js +7 -7
  182. package/server/lib/opencode/env-runtime.js +10 -10
  183. package/server/lib/opencode/env-runtime.test.js +18 -18
  184. package/server/lib/opencode/feature-routes-runtime.js +19 -12
  185. package/server/lib/opencode/hmr-state-runtime.js +2 -2
  186. package/server/lib/opencode/hmr-state-runtime.test.js +1 -1
  187. package/server/lib/opencode/input-history-scope.js +14 -0
  188. package/server/lib/opencode/lifecycle.js +8 -8
  189. package/server/lib/opencode/lifecycle.test.js +4 -4
  190. package/server/lib/opencode/managed-plugin-config.js +35 -0
  191. package/server/lib/opencode/managed-process-registry.js +3 -3
  192. package/server/lib/opencode/mcp-oauth-callback.test.js +2 -2
  193. package/server/lib/opencode/openchamber-routes.js +79 -19
  194. package/server/lib/opencode/openchamber-routes.test.js +139 -12
  195. package/server/lib/opencode/plugins.js +2 -2
  196. package/server/lib/opencode/plugins.test.js +1 -1
  197. package/server/lib/opencode/project-icon-routes.js +2 -2
  198. package/server/lib/opencode/project-icon-routes.test.js +1 -1
  199. package/server/lib/opencode/providers.js +44 -2
  200. package/server/lib/opencode/providers.test.js +131 -4
  201. package/server/lib/opencode/proxy.js +2 -2
  202. package/server/lib/opencode/pwa-manifest-routes.js +1 -1
  203. package/server/lib/opencode/restart-session-recovery.test.js +1 -1
  204. package/server/lib/opencode/routes-upgrade.test.js +4 -4
  205. package/server/lib/opencode/routes.js +19 -17
  206. package/server/lib/opencode/server-startup-runtime.js +6 -6
  207. package/server/lib/opencode/session-runtime.js +3 -3
  208. package/server/lib/opencode/session-runtime.test.js +6 -6
  209. package/server/lib/opencode/settings-files.js +214 -0
  210. package/server/lib/opencode/settings-helpers.js +116 -44
  211. package/server/lib/opencode/settings-helpers.test.js +354 -12
  212. package/server/lib/opencode/settings-registry.json +747 -0
  213. package/server/lib/opencode/settings-runtime.js +141 -20
  214. package/server/lib/opencode/settings-runtime.test.js +191 -4
  215. package/server/lib/opencode/shared.js +5 -2
  216. package/server/lib/opencode/shared.test.js +7 -7
  217. package/server/lib/opencode/shutdown-runtime.js +2 -0
  218. package/server/lib/opencode/skill-routes.js +2 -1
  219. package/server/lib/opencode/snippets.js +1 -2
  220. package/server/lib/opencode/startup-performance.js +1 -1
  221. package/server/lib/opencode/startup-performance.test.js +6 -6
  222. package/server/lib/opencode/static-routes-runtime.js +8 -8
  223. package/server/lib/opencode/static-routes-runtime.test.js +7 -7
  224. package/server/lib/opencode/upgrade-capability.js +2 -2
  225. package/server/lib/opencode/upgrade-capability.test.js +3 -3
  226. package/server/lib/package-manager.js +17 -42
  227. package/server/lib/package-manager.test.js +19 -19
  228. package/server/lib/permission-auto-accept/runtime.js +1 -1
  229. package/server/lib/project-context/DOCUMENTATION.md +25 -1
  230. package/server/lib/project-context/routes.http.test.js +18 -0
  231. package/server/lib/project-context/routes.js +16 -1
  232. package/server/lib/project-context/runtime.js +248 -23
  233. package/server/lib/project-context/runtime.test.js +106 -3
  234. package/server/lib/projects/DOCUMENTATION.md +76 -0
  235. package/server/lib/projects/project-config.js +144 -11
  236. package/server/lib/projects/project-id.js +17 -0
  237. package/server/lib/projects/project-setup.js +409 -0
  238. package/server/lib/projects/project-setup.test.js +557 -0
  239. package/server/lib/projects/routes.js +59 -0
  240. package/server/lib/quota/DOCUMENTATION.md +40 -2
  241. package/server/lib/quota/credentials/providers.js +4 -0
  242. package/server/lib/quota/credentials/store.js +4 -4
  243. package/server/lib/quota/credentials/store.test.js +8 -8
  244. package/server/lib/quota/index.js +1 -0
  245. package/server/lib/quota/providers/cline-pass.js +125 -0
  246. package/server/lib/quota/providers/cline-pass.test.js +103 -0
  247. package/server/lib/quota/providers/exe-dev.js +75 -0
  248. package/server/lib/quota/providers/exe-dev.test.js +47 -0
  249. package/server/lib/quota/providers/hyper.js +118 -0
  250. package/server/lib/quota/providers/hyper.test.js +126 -0
  251. package/server/lib/quota/providers/index.js +22 -0
  252. package/server/lib/quota/providers/neuralwatt.js +3 -6
  253. package/server/lib/quota/providers/neuralwatt.test.js +7 -7
  254. package/server/lib/quota/providers/ollama-cloud.js +32 -1
  255. package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
  256. package/server/lib/quota/providers/opencode-go.js +3 -2
  257. package/server/lib/quota/providers/opencode-go.test.js +35 -6
  258. package/server/lib/quota/providers/openrouter.js +119 -24
  259. package/server/lib/quota/providers/openrouter.test.js +330 -0
  260. package/server/lib/quota/providers/xai.js +1 -1
  261. package/server/lib/quota/routes.js +2 -0
  262. package/server/lib/quota/utils/auth.js +1 -1
  263. package/server/lib/relay/DOCUMENTATION.md +56 -1
  264. package/server/lib/relay/cross-compat.test.js +26 -0
  265. package/server/lib/relay/downstream-scheduler.js +154 -0
  266. package/server/lib/relay/downstream-scheduler.test.js +162 -0
  267. package/server/lib/relay/e2ee.js +10 -8
  268. package/server/lib/relay/flow-control.test.js +177 -0
  269. package/server/lib/relay/host-client.js +38 -9
  270. package/server/lib/relay/host-client.test.js +9 -1
  271. package/server/lib/relay/host-lock.js +1 -1
  272. package/server/lib/relay/service.js +5 -5
  273. package/server/lib/relay/tunnel-codec.js +9 -0
  274. package/server/lib/relay/tunnel-host.js +69 -18
  275. package/server/lib/relay/tunnel-host.test.js +9 -1
  276. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +2 -2
  277. package/server/lib/scheduled-tasks/routes.js +5 -5
  278. package/server/lib/scheduled-tasks/service.js +25 -25
  279. package/server/lib/scheduled-tasks/service.test.js +1 -1
  280. package/server/lib/security/bind-host.js +4 -4
  281. package/server/lib/security/bind-host.test.js +1 -1
  282. package/server/lib/security/request-security.js +11 -5
  283. package/server/lib/security/request-security.test.js +14 -14
  284. package/server/lib/session-assist/runtime.js +15 -18
  285. package/server/lib/session-folders/routes.js +2 -2
  286. package/server/lib/session-folders/routes.test.js +8 -8
  287. package/server/lib/session-goal/DOCUMENTATION.md +20 -7
  288. package/server/lib/session-goal/create.js +4 -2
  289. package/server/lib/session-goal/create.test.js +1 -1
  290. package/server/lib/session-goal/objectives.js +4 -4
  291. package/server/lib/session-goal/routes.js +1 -1
  292. package/server/lib/session-goal/runtime.js +91 -30
  293. package/server/lib/session-goal/runtime.test.js +428 -2
  294. package/server/lib/session-knowledge/runtime.js +11 -11
  295. package/server/lib/session-knowledge/runtime.test.js +5 -5
  296. package/server/lib/skills-catalog/DOCUMENTATION.md +1 -1
  297. package/server/lib/skills-catalog/cache.test.js +2 -2
  298. package/server/lib/skills-catalog/disk-cache.js +5 -5
  299. package/server/lib/skills-catalog/github-meta.test.js +2 -2
  300. package/server/lib/skills-catalog/install.js +3 -2
  301. package/server/lib/small-model/DOCUMENTATION.md +7 -2
  302. package/server/lib/small-model/call.js +18 -7
  303. package/server/lib/small-model/call.test.js +58 -6
  304. package/server/lib/small-model/index.js +12 -16
  305. package/server/lib/small-model/index.test.js +1 -1
  306. package/server/lib/small-model/resolve.js +1 -1
  307. package/server/lib/small-model/resolve.test.js +1 -1
  308. package/server/lib/small-model/routes.js +2 -1
  309. package/server/lib/small-model/runtime-providers.js +15 -7
  310. package/server/lib/small-model/runtime-providers.test.js +7 -1
  311. package/server/lib/system-prompt/runtime.js +3 -22
  312. package/server/lib/system-prompt/runtime.test.js +3 -3
  313. package/server/lib/terminal/DOCUMENTATION.md +14 -10
  314. package/server/lib/terminal/runtime.js +190 -31
  315. package/server/lib/terminal/runtime.test.js +572 -38
  316. package/server/lib/terminal/shells.js +20 -3
  317. package/server/lib/terminal/shells.test.js +33 -2
  318. package/server/lib/tts/base-url.js +3 -3
  319. package/server/lib/tts/routes.test.js +20 -20
  320. package/server/lib/ui-auth/ui-auth.js +12 -9
  321. package/server/lib/ui-auth/ui-auth.test.js +19 -3
  322. package/server/lib/ui-auth/ui-passkeys.js +7 -7
  323. package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
  324. package/server/lib/walkthrough/index.js +1 -0
  325. package/server/lib/walkthrough/jobs.test.js +1 -1
  326. package/server/lib/walkthrough/language.test.js +1 -1
  327. package/server/lib/walkthrough/model-settings.js +11 -15
  328. package/server/lib/walkthrough/model-settings.test.js +1 -1
  329. package/server/lib/walkthrough/prompt.js +3 -1
  330. package/server/lib/walkthrough/pull-request.test.js +5 -5
  331. package/server/lib/walkthrough/reproduce-2607.test.js +2 -2
  332. package/server/lib/walkthrough/routes.test.js +26 -5
  333. package/server/lib/walkthrough/sources.js +23 -2
  334. package/server/lib/walkthrough/store.js +3 -3
  335. package/server/lib/walkthrough/store.test.js +1 -1
  336. package/server/opencode-proxy.test.js +2 -2
  337. package/server/proxy-headers.js +1 -1
  338. package/server/sse-routes.test.js +4 -4
  339. package/README.md.bak +0 -250
  340. package/bin/lib/DOCUMENTATION.md.bak +0 -163
  341. package/dist/assets/CommitSection-SGBCAW0w.js +0 -1
  342. package/dist/assets/DiffView-DluiRSla.js +0 -4
  343. package/dist/assets/FilesView-Bdq8ppva.js +0 -17
  344. package/dist/assets/GitView-Znn7VJcP.js +0 -6
  345. package/dist/assets/LinearIssuesView-jP-hXyac.js +0 -1
  346. package/dist/assets/MarkdownImageGallery-ClOvuZxC.js +0 -1
  347. package/dist/assets/MarkdownRendererImpl-Dt9mjTRN.js +0 -12
  348. package/dist/assets/OnboardingScreen-D2DG7K6b.js +0 -1
  349. package/dist/assets/PierreDiffViewer-B7m3f0d7.js +0 -157
  350. package/dist/assets/PlanView-B0F3BtPZ.js +0 -5
  351. package/dist/assets/SettingsView-BBW-S-gh.js +0 -28
  352. package/dist/assets/ToolPartDiffPreview-CUNe28U1.js +0 -8
  353. package/dist/assets/WalkthroughView-egQgxel3.js +0 -4
  354. package/dist/assets/contextPanelEmbeddedChat-C3fjDbdg.js +0 -1
  355. package/dist/assets/de-C1P6X5Rs.js +0 -9
  356. package/dist/assets/es-CDgaF1_t.js +0 -15
  357. package/dist/assets/fr-Bgxqvvz_.js +0 -13
  358. package/dist/assets/index-C57jT0Sa.css +0 -1
  359. package/dist/assets/index-DW_E3rnL.js +0 -7
  360. package/dist/assets/ja-BDRrEcNP.js +0 -15
  361. package/dist/assets/ko-Ba8sQV79.js +0 -15
  362. package/dist/assets/main-Bv5Pkngh.js +0 -928
  363. package/dist/assets/markdownCore-CZwzz5gw.js +0 -3
  364. package/dist/assets/patchFileDiff-yNfWXN4E.js +0 -10
  365. package/dist/assets/pl-Dioz29BI.js +0 -15
  366. package/dist/assets/pt-BR-DIs6ElwV.js +0 -15
  367. package/dist/assets/renderElectronMiniChatApp-BcKLHXKe.js +0 -2
  368. package/dist/assets/renderMobileApp-D9yQJy0L.js +0 -25
  369. package/dist/assets/runtimeEndpointReset-CjhvxLH0.js +0 -16
  370. package/dist/assets/scheduledTasksApi-DLW0mkQK.js +0 -1
  371. package/dist/assets/tr-DFnMlPxY.js +0 -6
  372. package/dist/assets/uk-ZrCNjM7Y.js +0 -15
  373. package/dist/assets/useAppFontEffects-CjrntdL0.js +0 -7310
  374. package/dist/assets/useInlineCommentController-CDEuSlPH.js +0 -2
  375. package/dist/assets/useRootScrollLock-BHYPkrLv.js +0 -1
  376. package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
  377. package/dist/assets/vendor-zod-BLfBSgBh.js +0 -39
  378. package/dist/assets/zh-CN-iv_AV4EC.js +0 -15
  379. package/dist/assets/zh-TW-CzEUk38j.js +0 -15
  380. package/server/index.d.ts.bak +0 -39
  381. package/server/lib/agent-tool/DOCUMENTATION.md.bak +0 -119
  382. package/server/lib/browser-control/DOCUMENTATION.md.bak +0 -50
  383. package/server/lib/context-obligatory/DOCUMENTATION.md.bak +0 -19
  384. package/server/lib/dev-tunnel/DOCUMENTATION.md.bak +0 -57
  385. package/server/lib/dictation/DOCUMENTATION.md.bak +0 -100
  386. package/server/lib/event-stream/DOCUMENTATION.md.bak +0 -62
  387. package/server/lib/fs/DOCUMENTATION.md.bak +0 -47
  388. package/server/lib/git/DOCUMENTATION.md.bak +0 -193
  389. package/server/lib/github/DOCUMENTATION.md.bak +0 -183
  390. package/server/lib/linear/DOCUMENTATION.md.bak +0 -97
  391. package/server/lib/linear/OAUTH-BROKER-HANDOFF.md.bak +0 -91
  392. package/server/lib/markdown-image-grants/DOCUMENTATION.md.bak +0 -40
  393. package/server/lib/notifications/APNS.md.bak +0 -135
  394. package/server/lib/notifications/DOCUMENTATION.md.bak +0 -138
  395. package/server/lib/openchamber-control/DOCUMENTATION.md.bak +0 -64
  396. package/server/lib/opencode/DOCUMENTATION.md.bak +0 -431
  397. package/server/lib/opencode/managed-process-registry.d.ts.bak +0 -13
  398. package/server/lib/opencode/provider-env-aliases.d.ts.bak +0 -3
  399. package/server/lib/permission-auto-accept/DOCUMENTATION.md.bak +0 -34
  400. package/server/lib/project-context/DOCUMENTATION.md.bak +0 -160
  401. package/server/lib/quota/DOCUMENTATION.md.bak +0 -123
  402. package/server/lib/relay/DOCUMENTATION.md.bak +0 -100
  403. package/server/lib/scheduled-tasks/DOCUMENTATION.md.bak +0 -182
  404. package/server/lib/session-assist/DOCUMENTATION.md.bak +0 -66
  405. package/server/lib/session-goal/DOCUMENTATION.md.bak +0 -213
  406. package/server/lib/session-knowledge/DOCUMENTATION.md.bak +0 -89
  407. package/server/lib/skills-catalog/DOCUMENTATION.md.bak +0 -151
  408. package/server/lib/small-model/DOCUMENTATION.md.bak +0 -212
  409. package/server/lib/system-prompt/DOCUMENTATION.md.bak +0 -36
  410. package/server/lib/terminal/DOCUMENTATION.md.bak +0 -50
  411. package/server/lib/text/DOCUMENTATION.md.bak +0 -35
  412. package/server/lib/tts/DOCUMENTATION.md.bak +0 -147
  413. package/server/lib/tunnels/DOCUMENTATION.md.bak +0 -21
  414. package/server/lib/ui-auth/DOCUMENTATION.md.bak +0 -44
  415. package/server/lib/walkthrough/DOCUMENTATION.md.bak +0 -411
@@ -200,7 +200,7 @@ const resolveGitBinary = () => {
200
200
  return resolvedGitBinary;
201
201
  }
202
202
 
203
- const explicit = [process.env.GIT_BINARY, process.env.OPENCHAMBER_GIT_BINARY]
203
+ const explicit = [process.env.GIT_BINARY, process.env.AIWORKER_GIT_BINARY]
204
204
  .map((value) => (typeof value === 'string' ? value.trim() : ''))
205
205
  .filter(Boolean);
206
206
  for (const candidate of explicit) {
@@ -352,19 +352,20 @@ const buildGitEnv = async () => {
352
352
  return env;
353
353
  };
354
354
 
355
- const createGit = async (directory, { allowUnsafeSshCommand = false } = {}) => {
355
+ const createGit = async (directory, { allowUnsafeSshCommand = false, allowUnsafeCredentialHelper = false } = {}) => {
356
356
  const env = await buildGitEnv();
357
357
  const spawnOptions = { windowsHide: true };
358
358
  const binary = getGitBinary();
359
359
  const hasCustomBinary = typeof binary === 'string' && binary.trim() && binary !== 'git' && binary !== 'git.exe';
360
- const unsafe = hasCustomBinary || allowUnsafeSshCommand
360
+ const unsafe = hasCustomBinary || allowUnsafeSshCommand || allowUnsafeCredentialHelper
361
361
  ? {
362
- ...(hasCustomBinary && { allowUnsafeCustomBinary: true }),
363
- ...(allowUnsafeSshCommand && { allowUnsafeSshCommand: true }),
364
- }
362
+ ...(hasCustomBinary && { allowUnsafeCustomBinary: true }),
363
+ ...(allowUnsafeSshCommand && { allowUnsafeSshCommand: true }),
364
+ ...(allowUnsafeCredentialHelper && { allowUnsafeCredentialHelper: true }),
365
+ }
365
366
  : undefined;
366
367
  // Always pin simple-git to an explicit working directory. Omitting baseDir
367
- // makes simple-git use process.cwd(), which breaks when the OpenChamber
368
+ // makes simple-git use process.cwd(), which breaks when the AiWorker
368
369
  // server was launched from a neutral directory (e.g. $HOME) and the opened
369
370
  // project lives elsewhere — session/project discovery then sees spurious
370
371
  // "not a git repository" errors and can abort enumeration.
@@ -684,6 +685,15 @@ const parseWorktreePorcelain = (raw) => {
684
685
  const branchRef = line.substring('branch '.length).trim();
685
686
  current.branchRef = branchRef;
686
687
  current.branch = cleanBranchName(branchRef);
688
+ continue;
689
+ }
690
+
691
+ // git marks a worktree whose directory is gone (deleted outside git) as
692
+ // prunable; it stays registered until `git worktree prune`. The sidebar
693
+ // needs that distinction: the directory is missing, but the sessions that
694
+ // lived there are not.
695
+ if (line === 'prunable' || line.startsWith('prunable ')) {
696
+ current.prunable = true;
687
697
  }
688
698
  }
689
699
 
@@ -932,7 +942,7 @@ const runGitCommand = async (cwd, args) => {
932
942
  } catch (error) {
933
943
  return {
934
944
  success: false,
935
- exitCode: typeof error?.code === 'number' ? error.code : 1,
945
+ exitCode: Number.isInteger(error?.code) ? error.code : null,
936
946
  stdout: String(error?.stdout || ''),
937
947
  stderr: String(error?.stderr || ''),
938
948
  message: parseGitErrorText(error),
@@ -991,7 +1001,7 @@ const getFileIdentity = async (filePath) => {
991
1001
  }
992
1002
  };
993
1003
 
994
- // OpenChamber places managed worktrees under a deep data-dir path
1004
+ // AiWorker places managed worktrees under a deep data-dir path
995
1005
  // (`<XDG_DATA_HOME>/opencode/worktree/<40-char project id>/<name>/`). On
996
1006
  // Windows that prefix plus a deeply nested repo file routinely exceeds
997
1007
  // MAX_PATH (260). Git can check those paths out when core.longpaths is
@@ -1009,7 +1019,7 @@ const formatWorktreePopulateError = (message) => {
1009
1019
  return [
1010
1020
  text,
1011
1021
  'The worktree checkout path exceeds this system\'s path-length limit.',
1012
- 'OpenChamber enables Git `core.longpaths` for worktree population; if this still fails on Windows, enable OS long paths (LongPathsEnabled) or open the repository from a shorter absolute path.',
1022
+ 'AiWorker enables Git `core.longpaths` for worktree population; if this still fails on Windows, enable OS long paths (LongPathsEnabled) or open the repository from a shorter absolute path.',
1013
1023
  ].join('\n');
1014
1024
  };
1015
1025
 
@@ -1019,7 +1029,7 @@ export const ensureWorktreeLongpaths = async (directory) => {
1019
1029
  return;
1020
1030
  }
1021
1031
  // Local config is shared across linked worktrees via the common git dir, so
1022
- // subsequent OpenChamber and CLI git operations in this repo also get long
1032
+ // subsequent AiWorker and CLI git operations in this repo also get long
1023
1033
  // path support. Failures here are non-fatal: populate still passes
1024
1034
  // `-c core.longpaths=true` on reset.
1025
1035
  await runGitCommand(directory, ['config', 'core.longpaths', 'true']);
@@ -1309,7 +1319,7 @@ export async function computeIntegratePlan(input = {}) {
1309
1319
  }
1310
1320
 
1311
1321
  const createIntegrateTempWorktree = async (repoRoot, targetBranch) => {
1312
- const tmpParent = path.join(os.homedir(), '.config', 'openchamber', 'tmp');
1322
+ const tmpParent = path.join(os.homedir(), '.config', 'aiworker', 'tmp');
1313
1323
  await fsp.mkdir(tmpParent, { recursive: true });
1314
1324
  const tmpDir = await fsp.mkdtemp(path.join(tmpParent, 'oc-integrate-'));
1315
1325
  try {
@@ -1622,7 +1632,7 @@ const resolveCandidateDirectory = async (worktreeRoot, preferredName, explicitBr
1622
1632
  return { name, directory, branch: explicitBranchName };
1623
1633
  }
1624
1634
 
1625
- const branch = `openchamber/${name}`;
1635
+ const branch = `aiworker/${name}`;
1626
1636
  const branchRef = `refs/heads/${branch}`;
1627
1637
  const branchExists = await runGitCommand(primaryWorktree, ['show-ref', '--verify', '--quiet', branchRef]);
1628
1638
  if (branchExists.success) {
@@ -1742,7 +1752,7 @@ const loadProjectStartCommand = async (projectID) => {
1742
1752
 
1743
1753
  // OpenCode owns its own project/sandbox registry. It records a worktree as a
1744
1754
  // sandbox itself when an instance boots for that directory, and filters entries
1745
- // whose directory no longer exists when reading them back. OpenChamber used to
1755
+ // whose directory no longer exists when reading them back. AiWorker used to
1746
1756
  // write that state directly into OpenCode's storage JSON and SQLite database,
1747
1757
  // behind the back of the running process: the row changed but the server was
1748
1758
  // never told, so a worktree created while OpenCode was running stayed unknown
@@ -2137,7 +2147,7 @@ export async function hasLocalIdentity(directory) {
2137
2147
  }
2138
2148
 
2139
2149
  export async function setLocalIdentity(directory, profile) {
2140
- const git = await createGit(directory, { allowUnsafeSshCommand: true });
2150
+ const git = await createGit(directory, { allowUnsafeSshCommand: true, allowUnsafeCredentialHelper: true });
2141
2151
 
2142
2152
  try {
2143
2153
 
@@ -2455,11 +2465,26 @@ export async function getStatus(directory, options = {}) {
2455
2465
  }
2456
2466
  }
2457
2467
 
2468
+ const getNoIndexDiff = async (repoRoot, repoPath, contextLines) => {
2469
+ const args = ['diff', '--no-color', '--full-index'];
2470
+ if (Number.isFinite(contextLines)) {
2471
+ args.push(`-U${Math.max(0, contextLines)}`);
2472
+ }
2473
+ args.push('--no-index', '--', '/dev/null', repoPath);
2474
+ const result = await runGitCommand(repoRoot, args);
2475
+ // Exit 1 means differences, even when Git also writes warnings to stderr.
2476
+ // Spawn and buffer errors have no numeric exit code and must still fail.
2477
+ if (result.exitCode === 0 || result.exitCode === 1) {
2478
+ return result.stdout;
2479
+ }
2480
+ throw new Error(result.stderr || result.message || 'Failed to get untracked Git diff');
2481
+ };
2482
+
2458
2483
  export async function getDiff(directory, { path: filePath, staged = false, contextLines = 3 } = {}) {
2459
2484
  const { directoryPath, directoryGit, repoRoot, git } = await createRepositoryGitContext(directory);
2460
2485
 
2461
2486
  try {
2462
- const args = ['diff', '--no-color'];
2487
+ const args = ['diff', '--no-color', '--full-index'];
2463
2488
  const fileContext = filePath ? await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot) : null;
2464
2489
 
2465
2490
  if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
@@ -2505,21 +2530,7 @@ export async function getDiff(directory, { path: filePath, staged = false, conte
2505
2530
  ].join('\n');
2506
2531
  }
2507
2532
 
2508
- const noIndexArgs = ['diff', '--no-color'];
2509
- if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
2510
- noIndexArgs.push(`-U${Math.max(0, contextLines)}`);
2511
- }
2512
- noIndexArgs.push('--no-index', '--', '/dev/null', fileContext.repoPath);
2513
- try {
2514
- const noIndexDiff = await git.raw(noIndexArgs);
2515
- return noIndexDiff;
2516
- } catch (noIndexError) {
2517
- // git diff --no-index returns exit code 1 when differences exist (not a real error)
2518
- if (noIndexError.exitCode === 1 && noIndexError.message) {
2519
- return noIndexError.message;
2520
- }
2521
- throw noIndexError;
2522
- }
2533
+ return await getNoIndexDiff(repoRoot, fileContext.repoPath, contextLines);
2523
2534
  }
2524
2535
  } catch (error) {
2525
2536
  console.error('Failed to get Git diff:', error);
@@ -2568,7 +2579,7 @@ export async function getUntrackedDiffs(directory, filePaths = [], { concurrency
2568
2579
  const paths = (Array.isArray(filePaths) ? filePaths : []).filter((value) => typeof value === 'string' && value);
2569
2580
  if (paths.length === 0) return [];
2570
2581
 
2571
- const { directoryPath, directoryGit, repoRoot, git } = await createRepositoryGitContext(directory);
2582
+ const { directoryPath, directoryGit, repoRoot } = await createRepositoryGitContext(directory);
2572
2583
  const results = new Array(paths.length).fill('');
2573
2584
  let cursor = 0;
2574
2585
 
@@ -2577,18 +2588,7 @@ export async function getUntrackedDiffs(directory, filePaths = [], { concurrency
2577
2588
  const index = cursor++;
2578
2589
  try {
2579
2590
  const fileContext = await resolveGitFileContext(directoryPath, directoryGit, paths[index], repoRoot);
2580
- const args = ['diff', '--no-color'];
2581
- if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
2582
- args.push(`-U${Math.max(0, contextLines)}`);
2583
- }
2584
- args.push('--no-index', '--', '/dev/null', fileContext.repoPath);
2585
- try {
2586
- results[index] = await git.raw(args);
2587
- } catch (error) {
2588
- // `git diff --no-index` exits 1 whenever there are differences, which
2589
- // for a new file is always.
2590
- results[index] = error?.exitCode === 1 && error?.message ? error.message : '';
2591
- }
2591
+ results[index] = await getNoIndexDiff(repoRoot, fileContext.repoPath, contextLines);
2592
2592
  } catch {
2593
2593
  results[index] = '';
2594
2594
  }
@@ -2618,7 +2618,51 @@ async function assertRangeRefsResolve(git, refs) {
2618
2618
  }
2619
2619
  }
2620
2620
 
2621
- export async function getRangeDiff(directory, { base, head, path: filePath, contextLines = 3 } = {}) {
2621
+ // A private index lets git include untracked paths in the same tree comparison
2622
+ // as tracked files, including a staged deletion recreated at the same path.
2623
+ // Intent-to-add records only their existence; diff reads current file contents.
2624
+ async function runWorkingTreeRangeDiff(context, baseRef, headRef, args, paths = []) {
2625
+ const { git, repoRoot } = context;
2626
+ const readHead = async () => {
2627
+ const commit = (await git.raw(['rev-parse', '--verify', 'HEAD'])).trim();
2628
+ const ref = (await git.raw(['symbolic-ref', '--quiet', 'HEAD'])).trim();
2629
+ return `${commit}\n${ref}`;
2630
+ };
2631
+ const startingHead = await readHead();
2632
+ const [headCommit, currentRef] = startingHead.split('\n');
2633
+ const requestedRef = (await git.raw(['rev-parse', '--verify', '--symbolic-full-name', '--end-of-options', headRef])).trim();
2634
+ if (requestedRef !== currentRef) {
2635
+ throw new Error('Working-tree comparisons require the checked-out branch. Refresh and try again.');
2636
+ }
2637
+ const mergeBase = (await git.raw(['merge-base', baseRef, headCommit])).trim();
2638
+ const readDiff = async (comparisonGit) => {
2639
+ const diff = await comparisonGit.raw([...args, mergeBase, '--', ...paths]);
2640
+ if (await readHead() !== startingHead) {
2641
+ throw new Error('The checked-out branch changed during comparison. Refresh and try again.');
2642
+ }
2643
+ return diff;
2644
+ };
2645
+ const untracked = await git.raw(['ls-files', '--others', '--exclude-standard', '-z', '--', ...paths]);
2646
+ if (!untracked) return readDiff(git);
2647
+
2648
+ const temporaryDirectory = await fsp.mkdtemp(path.join(os.tmpdir(), 'openchamber-branch-diff-'));
2649
+ try {
2650
+ const indexPath = (await git.raw(['rev-parse', '--git-path', 'index'])).trim();
2651
+ const temporaryIndex = path.join(temporaryDirectory, 'index');
2652
+ await fsp.copyFile(path.resolve(repoRoot, indexPath), temporaryIndex);
2653
+ const pathspecFile = path.join(temporaryDirectory, 'paths');
2654
+ await fsp.writeFile(pathspecFile, untracked);
2655
+ const comparisonGit = await createGit(repoRoot);
2656
+ comparisonGit.env('GIT_INDEX_FILE', temporaryIndex);
2657
+ comparisonGit.env('GIT_LITERAL_PATHSPECS', '1');
2658
+ await comparisonGit.raw(['add', '--intent-to-add', '--pathspec-from-file=' + pathspecFile, '--pathspec-file-nul']);
2659
+ return await readDiff(comparisonGit);
2660
+ } finally {
2661
+ await fsp.rm(temporaryDirectory, { recursive: true, force: true });
2662
+ }
2663
+ }
2664
+
2665
+ export async function getRangeDiff(directory, { base, head, path: filePath, contextLines = 3, includeWorkingTree = false } = {}) {
2622
2666
  const { directoryPath, directoryGit, repoRoot, git } = await createRepositoryGitContext(directory);
2623
2667
  const baseRef = typeof base === 'string' ? base.trim() : '';
2624
2668
  const headRef = typeof head === 'string' ? head.trim() : '';
@@ -2626,51 +2670,39 @@ export async function getRangeDiff(directory, { base, head, path: filePath, cont
2626
2670
  throw new Error('base and head are required');
2627
2671
  }
2628
2672
 
2629
- // Prefer remote-tracking base ref so merged commits don't reappear
2630
- // when local base branch is stale (common when user stays on feature branch).
2631
- let resolvedBase = baseRef;
2632
- const originCandidate = `refs/remotes/origin/${baseRef}`;
2633
- try {
2634
- const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
2635
- if (verified && verified.trim()) {
2636
- resolvedBase = `origin/${baseRef}`;
2637
- }
2638
- } catch {
2639
- // ignore
2640
- }
2641
-
2642
- // Not every repository has an `origin`. When the base names a branch that
2643
- // exists only on another remote, a bare name does not resolve — git looks in
2644
- // refs/heads, not across remotes — and the diff fails with "ambiguous
2645
- // argument". Fall back to whichever remote actually carries it.
2646
- if (resolvedBase === baseRef && !/[*?[\]^~:\\]/.test(baseRef)) {
2647
- const resolvesLocally = await git
2648
- .raw(['rev-parse', '--verify', `refs/heads/${baseRef}`])
2649
- .then((value) => Boolean(String(value || '').trim()))
2650
- .catch(() => false);
2651
-
2652
- if (!resolvesLocally) {
2653
- const remoteMatch = await git
2654
- .raw(['for-each-ref', '--count=1', '--format=%(refname:short)', `refs/remotes/*/${baseRef}`])
2655
- .then((value) => String(value || '').trim())
2656
- .catch(() => '');
2657
- if (remoteMatch) {
2658
- resolvedBase = remoteMatch;
2659
- }
2660
- }
2661
- }
2662
-
2663
- await assertRangeRefsResolve(git, [resolvedBase, headRef]);
2673
+ await assertRangeRefsResolve(git, [baseRef, headRef]);
2664
2674
 
2665
2675
  const args = ['diff', '--no-color'];
2666
2676
  if (typeof contextLines === 'number' && !Number.isNaN(contextLines)) {
2667
2677
  args.push(`-U${Math.max(0, contextLines)}`);
2668
2678
  }
2669
- args.push(`${resolvedBase}...${headRef}`);
2679
+ const paths = [];
2670
2680
  if (filePath) {
2671
- const fileContext = await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot);
2672
- args.push('--', fileContext.repoPath);
2681
+ try {
2682
+ const fileContext = await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot);
2683
+ paths.push(fileContext.repoPath);
2684
+ } catch (error) {
2685
+ if (error.message !== 'Invalid file path') throw error;
2686
+ // A committed deletion is absent from HEAD, the index, and the working
2687
+ // tree. It is still a valid range path when it exists at the merge base.
2688
+ const mergeBase = (await git.raw(['merge-base', baseRef, headRef])).trim();
2689
+ for (const root of new Set([repoRoot, directoryPath])) {
2690
+ const target = path.resolve(root, filePath);
2691
+ if (!isInsideOrSameDirectory(repoRoot, target)) continue;
2692
+ const repoPath = toGitPath(path.relative(repoRoot, target));
2693
+ const exists = await git.raw(['cat-file', '-e', `${mergeBase}:${repoPath}`]).then(() => true).catch(() => false);
2694
+ if (exists) {
2695
+ paths.push(repoPath);
2696
+ break;
2697
+ }
2698
+ }
2699
+ if (paths.length === 0) throw error;
2700
+ }
2701
+ }
2702
+ if (includeWorkingTree) {
2703
+ return runWorkingTreeRangeDiff({ git, repoRoot }, baseRef, headRef, args, paths);
2673
2704
  }
2705
+ args.push(`${baseRef}...${headRef}`, '--', ...paths);
2674
2706
  const diff = await git.raw(args);
2675
2707
  return diff;
2676
2708
  }
@@ -2692,6 +2724,9 @@ export function parseBranchCreationSource(reflogText) {
2692
2724
  .split('\n')
2693
2725
  .map((line) => line.trim())
2694
2726
  .filter(Boolean);
2727
+ // Rebase records its destination as a commit, not a parent branch. The
2728
+ // creation ref is no longer evidence of the current base after restacking.
2729
+ if (lines.some((line) => /^rebase(?:\s|\()/.test(line))) return null;
2695
2730
  // Reflog lists newest entries first; the creation entry is the oldest one.
2696
2731
  for (let index = lines.length - 1; index >= 0; index -= 1) {
2697
2732
  const match = lines[index].match(BRANCH_CREATION_SOURCE_RE);
@@ -2743,31 +2778,23 @@ export async function getBranchBase(directory, branch) {
2743
2778
  return { base: source };
2744
2779
  }
2745
2780
 
2746
- export async function getRangeFiles(directory, { base, head } = {}) {
2747
- const { git } = await createRepositoryGitContext(directory);
2781
+ export async function getRangeFiles(directory, { base, head, includeWorkingTree = false } = {}) {
2782
+ const { git, repoRoot } = await createRepositoryGitContext(directory);
2748
2783
  const baseRef = typeof base === 'string' ? base.trim() : '';
2749
2784
  const headRef = typeof head === 'string' ? head.trim() : '';
2750
2785
  if (!baseRef || !headRef) {
2751
2786
  throw new Error('base and head are required');
2752
2787
  }
2753
2788
 
2754
- let resolvedBase = baseRef;
2755
- const originCandidate = `refs/remotes/origin/${baseRef}`;
2756
- try {
2757
- const verified = await git.raw(['rev-parse', '--verify', originCandidate]);
2758
- if (verified && verified.trim()) {
2759
- resolvedBase = `origin/${baseRef}`;
2760
- }
2761
- } catch {
2762
- // ignore
2763
- }
2764
-
2765
- await assertRangeRefsResolve(git, [resolvedBase, headRef]);
2789
+ await assertRangeRefsResolve(git, [baseRef, headRef]);
2766
2790
 
2767
2791
  // `-C` (copy detection among changed files only, so cheap) makes copies
2768
2792
  // surface as C entries instead of plain additions; rename detection is on
2769
2793
  // by default.
2770
- const raw = await git.raw(['diff', '--name-status', '-z', '-C', `${resolvedBase}...${headRef}`]);
2794
+ const args = ['diff', '--name-status', '-z', '-C'];
2795
+ const raw = includeWorkingTree
2796
+ ? await runWorkingTreeRangeDiff({ git, repoRoot }, baseRef, headRef, args)
2797
+ : await git.raw([...args, `${baseRef}...${headRef}`, '--']);
2771
2798
  // -z format: STATUS\0PATH\0[ORIG\0] repeated. For rename/copy entries
2772
2799
  // (`R100`, `C75`) the first path token is the ORIGINAL path and the second
2773
2800
  // is the DESTINATION — the diff (and the UI) must address the destination.
@@ -2777,7 +2804,7 @@ export async function getRangeFiles(directory, { base, head } = {}) {
2777
2804
  const status = (tokens[index] || '').trim();
2778
2805
  if (!status) continue;
2779
2806
  const isRenameOrCopy = status.startsWith('R') || status.startsWith('C');
2780
- const path = isRenameOrCopy ? (tokens[index + 2] || '').trim() : (tokens[index + 1] || '').trim();
2807
+ const path = isRenameOrCopy ? (tokens[index + 2] || '') : (tokens[index + 1] || '');
2781
2808
  index += isRenameOrCopy ? 2 : 1;
2782
2809
  if (path) {
2783
2810
  files.push({ path, status: status.charAt(0) });
@@ -2823,27 +2850,6 @@ const parseIsBinaryFromNumstat = (raw) => {
2823
2850
  return added === '-' || deleted === '-';
2824
2851
  };
2825
2852
 
2826
- const extractGitStatusPath = (status, pathPart) => {
2827
- if ((status === 'R' || status === 'C') && pathPart.includes('\t')) {
2828
- return pathPart.split('\t').pop() || pathPart;
2829
- }
2830
- return pathPart;
2831
- };
2832
-
2833
- const extractGitNumstatDestinationPath = (filePath) => {
2834
- if (!filePath.includes(' => ')) {
2835
- return filePath;
2836
- }
2837
-
2838
- const braceMatch = filePath.match(/^(.*)\{([^{}]*)\s=>\s([^{}]*)\}(.*)$/);
2839
- if (braceMatch) {
2840
- const [, prefix, , destination, suffix] = braceMatch;
2841
- return `${prefix}${destination}${suffix}`.replace(/\/+/g, '/');
2842
- }
2843
-
2844
- return filePath.split(' => ').pop()?.trim() || filePath;
2845
- };
2846
-
2847
2853
  const looksBinaryBySniff = async (absolutePath) => {
2848
2854
  try {
2849
2855
  const handle = await fsp.open(absolutePath, 'r');
@@ -3093,11 +3099,13 @@ const normalizePatchTargetPath = (value) => {
3093
3099
  };
3094
3100
 
3095
3101
  const extractPatchTargetPath = (patch) => {
3096
- const matches = [...patch.matchAll(/^(?:-{3}|\+{3})\s+.+$/gm)];
3102
+ const firstHunk = patch.search(/^@@\s/m);
3103
+ const header = firstHunk < 0 ? patch : patch.slice(0, firstHunk);
3104
+ const matches = [...header.matchAll(/^(?:-{3}|\+{3})\s+.+$/gm)];
3097
3105
  const realTargets = matches
3098
3106
  .map((match) => normalizePatchTargetPath(parsePatchPathToken(match[0])))
3099
3107
  .filter(Boolean);
3100
- return realTargets[0] || null;
3108
+ return realTargets.at(-1) || null;
3101
3109
  };
3102
3110
 
3103
3111
  const writeTempPatchFile = async (patch) => {
@@ -3125,9 +3133,21 @@ export async function applyHunk(directory, filePath, options = {}) {
3125
3133
  const fileContext = await resolveGitFileContext(directoryPath, directoryGit, filePath, repoRoot);
3126
3134
  validateRepositoryFilePaths(repoRoot, [fileContext.repoPath]);
3127
3135
 
3128
- const targetPath = extractPatchTargetPath(patch);
3129
- if (targetPath && targetPath !== fileContext.repoPath && targetPath !== filePath) {
3130
- throw new Error('patch target path does not match the requested file');
3136
+ // Applicability alone is insufficient: a previously staged or committed
3137
+ // hunk may still reverse cleanly against the working tree. Accept only a
3138
+ // canonical hunk from this file's current working/index diff.
3139
+ const current = await getDiff(directory, { path: filePath, staged: action === 'unstage', contextLines: 3 });
3140
+ const starts = [...current.matchAll(/^@@\s/gm)].map((match) => match.index);
3141
+ const header = current.slice(0, starts[0] ?? 0);
3142
+ const isCurrentHunk = starts.some((start, index) => (
3143
+ header + current.slice(start, starts[index + 1] ?? current.length) === patch
3144
+ ));
3145
+ if (!isCurrentHunk) {
3146
+ const targetPath = extractPatchTargetPath(patch);
3147
+ if (targetPath && targetPath !== fileContext.repoPath && targetPath !== filePath) {
3148
+ throw new Error('patch target path does not match the requested file');
3149
+ }
3150
+ throw new Error('Hunk no longer applies — refresh and try again.');
3131
3151
  }
3132
3152
 
3133
3153
  const flags = HUNK_ACTION_FLAGS[action];
@@ -3248,7 +3268,7 @@ export async function stashPush(directory, options = {}) {
3248
3268
  const { git } = await createRepositoryGitContext(directory);
3249
3269
  const message = typeof options.message === 'string' && options.message.trim()
3250
3270
  ? options.message.trim()
3251
- : `OpenChamber stash ${new Date().toISOString()}`;
3271
+ : `AiWorker stash ${new Date().toISOString()}`;
3252
3272
  const output = await git.raw(['stash', 'push', '--include-untracked', '-m', message]);
3253
3273
  return {
3254
3274
  success: true,
@@ -3687,6 +3707,35 @@ export async function getBranches(directory) {
3687
3707
  }
3688
3708
  }
3689
3709
 
3710
+ /**
3711
+ * Counts locally unpushed commits for a small caller-supplied set of local
3712
+ * branches. This deliberately reads only local refs: the branch picker calls
3713
+ * it when opened, never polls, and never fetches a remote behind the user's
3714
+ * back. Unknown, remote, and upstream-less branches are omitted.
3715
+ */
3716
+ export async function getUnpushedBranchCounts(directory, branchNames) {
3717
+ const { git } = await createRepositoryGitContext(directory);
3718
+ const requested = [...new Set(Array.isArray(branchNames) ? branchNames : [])]
3719
+ .filter((name) => typeof name === 'string' && name.length > 0)
3720
+ .slice(0, 5);
3721
+ if (requested.length === 0) return { counts: {} };
3722
+
3723
+ const local = new Set((await git.branchLocal()).all);
3724
+ const counts = {};
3725
+ await Promise.all(requested.map(async (branch) => {
3726
+ if (!local.has(branch)) return;
3727
+ const upstream = await git.raw(['rev-parse', '--abbrev-ref', '--symbolic-full-name', `${branch}@{upstream}`])
3728
+ .then((value) => value.trim())
3729
+ .catch(() => '');
3730
+ if (!upstream) return;
3731
+ const count = await git.raw(['rev-list', '--count', `${upstream}..${branch}`])
3732
+ .then((value) => Number.parseInt(value.trim(), 10))
3733
+ .catch(() => 0);
3734
+ if (Number.isFinite(count) && count > 0) counts[branch] = count;
3735
+ }));
3736
+ return { counts };
3737
+ }
3738
+
3690
3739
  async function getRemoteDefaultBranches(git) {
3691
3740
  let defaults = {};
3692
3741
 
@@ -4007,6 +4056,7 @@ export async function getWorktrees(directory) {
4007
4056
  name: path.basename(entry.worktree || ''),
4008
4057
  branch: entry.branch || '',
4009
4058
  path: entry.worktree,
4059
+ prunable: entry.prunable === true,
4010
4060
  }));
4011
4061
  } catch (error) {
4012
4062
  // Worktrees are an optional feature. When the caller passes a directory
@@ -4266,6 +4316,7 @@ async function attachGitWorktreeToCandidate(context, candidate, input = {}) {
4266
4316
 
4267
4317
  const parsedRemoteStartRef = await resolveRemoteBranchRef(context.primaryWorktree, startRef);
4268
4318
  if (parsedRemoteStartRef) {
4319
+ worktreeAddArgs.splice(2, 0, '--no-track');
4269
4320
  inferredUpstream = {
4270
4321
  remote: parsedRemoteStartRef.remote,
4271
4322
  branch: parsedRemoteStartRef.branch,
@@ -4273,24 +4324,17 @@ async function attachGitWorktreeToCandidate(context, candidate, input = {}) {
4273
4324
  }
4274
4325
  }
4275
4326
 
4276
- if (ensureRemoteName && ensureRemoteUrl) {
4327
+ if (mode === 'existing' && ensureRemoteName && ensureRemoteUrl) {
4277
4328
  await ensureRemoteWithUrl(context.primaryWorktree, ensureRemoteName, ensureRemoteUrl);
4278
4329
  }
4279
4330
 
4280
- if (mode === 'new') {
4281
- const parsedRemoteStartRef = await resolveRemoteBranchRef(context.primaryWorktree, startRef);
4282
- if (parsedRemoteStartRef) {
4283
- await fetchRemoteBranchRef(context.primaryWorktree, parsedRemoteStartRef.remote, parsedRemoteStartRef.branch);
4284
- }
4285
- }
4286
-
4287
4331
  await runGitCommandOrThrow(context.primaryWorktree, worktreeAddArgs, 'Failed to create git worktree');
4288
4332
 
4289
4333
  const upstreamRemote = shouldSetUpstream
4290
- ? String(inferredUpstream?.remote || input?.upstreamRemote || '').trim()
4334
+ ? String(input?.upstreamRemote || inferredUpstream?.remote || '').trim()
4291
4335
  : '';
4292
4336
  const upstreamBranch = shouldSetUpstream
4293
- ? String(inferredUpstream?.branch || input?.upstreamBranch || '').trim()
4337
+ ? String(input?.upstreamBranch || inferredUpstream?.branch || '').trim()
4294
4338
  : '';
4295
4339
 
4296
4340
  const bootstrapStatus = setWorktreeBootstrapState(
@@ -4325,6 +4369,50 @@ async function attachGitWorktreeToCandidate(context, candidate, input = {}) {
4325
4369
  };
4326
4370
  }
4327
4371
 
4372
+ const prepareWorktreeCreateSource = async (context, input = {}) => {
4373
+ if (input?.mode === 'existing') {
4374
+ return { input, sourceFetchFailed: false };
4375
+ }
4376
+
4377
+ const startRef = normalizeStartRef(input?.startRef);
4378
+ const remoteStartRef = await resolveRemoteBranchRef(context.primaryWorktree, startRef);
4379
+ if (!remoteStartRef) {
4380
+ return { input, sourceFetchFailed: false };
4381
+ }
4382
+
4383
+ const status = await getStatus(context.primaryWorktree, { mode: 'light' }).catch(() => null);
4384
+ const trackingRef = status?.tracking
4385
+ ? await resolveRemoteBranchRef(context.primaryWorktree, status.tracking)
4386
+ : null;
4387
+ const canFallbackToLocal = Boolean(
4388
+ status?.current
4389
+ && status.ahead === 0
4390
+ && trackingRef?.fullRef === remoteStartRef.fullRef
4391
+ );
4392
+
4393
+ try {
4394
+ await fetchRemoteBranchRef(context.primaryWorktree, remoteStartRef.remote, remoteStartRef.branch);
4395
+ return { input, sourceFetchFailed: false };
4396
+ } catch (error) {
4397
+ if (canFallbackToLocal) {
4398
+ return {
4399
+ input: { ...input, startRef: status.current },
4400
+ sourceFetchFailed: true,
4401
+ };
4402
+ }
4403
+
4404
+ const refExists = await runGitCommand(
4405
+ context.primaryWorktree,
4406
+ ['show-ref', '--verify', '--quiet', remoteStartRef.fullRef]
4407
+ );
4408
+ if (!refExists.success) {
4409
+ throw error;
4410
+ }
4411
+ console.warn(`Worktree create: failed to refresh ${remoteStartRef.remote}/${remoteStartRef.branch}, proceeding with the existing remote-tracking ref`);
4412
+ return { input, sourceFetchFailed: false };
4413
+ }
4414
+ };
4415
+
4328
4416
  export async function createWorktree(directory, input = {}) {
4329
4417
  const mode = input?.mode === 'existing' ? 'existing' : 'new';
4330
4418
  const context = await resolveWorktreeProjectContext(directory);
@@ -4333,10 +4421,18 @@ export async function createWorktree(directory, input = {}) {
4333
4421
  await assertWorktreeCreatePreflight(directory, input);
4334
4422
  }
4335
4423
 
4424
+ const ensureRemoteName = String(input?.ensureRemoteName || '').trim();
4425
+ const ensureRemoteUrl = String(input?.ensureRemoteUrl || '').trim();
4426
+ if (ensureRemoteName && ensureRemoteUrl) {
4427
+ await ensureRemoteWithUrl(context.primaryWorktree, ensureRemoteName, ensureRemoteUrl);
4428
+ }
4429
+ const prepared = await prepareWorktreeCreateSource(context, input);
4430
+ const preparedInput = prepared.input;
4431
+
4336
4432
  await fsp.mkdir(context.worktreeRoot, { recursive: true });
4337
4433
 
4338
- const preferredName = String(input?.worktreeName || input?.name || '').trim();
4339
- const preferredBranchName = cleanBranchName(String(input?.branchName || '').trim());
4434
+ const preferredName = String(preparedInput?.worktreeName || preparedInput?.name || '').trim();
4435
+ const preferredBranchName = cleanBranchName(String(preparedInput?.branchName || '').trim());
4340
4436
 
4341
4437
  const candidate = await resolveCandidateDirectory(
4342
4438
  context.worktreeRoot,
@@ -4345,7 +4441,7 @@ export async function createWorktree(directory, input = {}) {
4345
4441
  context.primaryWorktree
4346
4442
  );
4347
4443
 
4348
- if (input?.returnAfterDirectoryCreated === true) {
4444
+ if (preparedInput?.returnAfterDirectoryCreated === true) {
4349
4445
  await fsp.mkdir(candidate.directory, { recursive: false });
4350
4446
 
4351
4447
  const bootstrapStatus = setWorktreeBootstrapState(
@@ -4354,10 +4450,10 @@ export async function createWorktree(directory, input = {}) {
4354
4450
  WORKTREE_BOOTSTRAP_PHASE_DIRECTORY_CREATED
4355
4451
  );
4356
4452
  const localBranch = mode === 'existing'
4357
- ? cleanBranchName(String(input?.branchName || input?.existingBranch || candidate.branch || '').trim())
4453
+ ? cleanBranchName(String(preparedInput?.branchName || preparedInput?.existingBranch || candidate.branch || '').trim())
4358
4454
  : candidate.branch;
4359
4455
 
4360
- const task = attachGitWorktreeToCandidate(context, candidate, input).catch(async (error) => {
4456
+ const task = attachGitWorktreeToCandidate(context, candidate, preparedInput).catch(async (error) => {
4361
4457
  setWorktreeBootstrapState(
4362
4458
  candidate.directory,
4363
4459
  WORKTREE_BOOTSTRAP_FAILED,
@@ -4369,7 +4465,7 @@ export async function createWorktree(directory, input = {}) {
4369
4465
  });
4370
4466
  trackWorktreeBootstrapTask(candidate.directory, task);
4371
4467
 
4372
- return {
4468
+ const result = {
4373
4469
  head: '',
4374
4470
  name: candidate.name,
4375
4471
  branch: localBranch,
@@ -4377,9 +4473,14 @@ export async function createWorktree(directory, input = {}) {
4377
4473
  directoryCreated: true,
4378
4474
  bootstrapStatus,
4379
4475
  };
4476
+ if (prepared.sourceFetchFailed) {
4477
+ result.sourceFetchFailed = true;
4478
+ }
4479
+ return result;
4380
4480
  }
4381
4481
 
4382
- return attachGitWorktreeToCandidate(context, candidate, input);
4482
+ const result = await attachGitWorktreeToCandidate(context, candidate, preparedInput);
4483
+ return prepared.sourceFetchFailed ? { ...result, sourceFetchFailed: true } : result;
4383
4484
  }
4384
4485
 
4385
4486
  export async function getWorktreeBootstrapStatus(directory) {
@@ -4588,12 +4689,11 @@ export async function getLog(directory, options = {}) {
4588
4689
  };
4589
4690
  const resolvedFrom = await resolveBaseRefForLog(options.from, checkRef);
4590
4691
 
4591
- const baseLog = await git.log({
4592
- maxCount,
4593
- from: resolvedFrom,
4594
- to: options.to,
4595
- file: filePath
4596
- });
4692
+ // simple-git's `to` alone means HEAD..to, which is empty for the current
4693
+ // branch. A single requested ref means its reachable history instead.
4694
+ const baseLog = options.to && !resolvedFrom
4695
+ ? await git.log([`--max-count=${maxCount}`, options.to, ...(filePath ? ['--', filePath] : [])])
4696
+ : await git.log({ maxCount, from: resolvedFrom, to: options.to, file: filePath });
4597
4697
 
4598
4698
  const logArgs = [
4599
4699
  'log',
@@ -4837,85 +4937,61 @@ export async function canonicalizeWorktreeState(directory) {
4837
4937
  };
4838
4938
  }
4839
4939
 
4840
- export async function getCommitFiles(directory, commitHash) {
4841
- const { git } = await createRepositoryGitContext(directory);
4842
-
4843
- try {
4844
-
4845
- const numstatRaw = await git.raw([
4846
- 'show',
4847
- '--numstat',
4848
- '--format=',
4849
- commitHash
4850
- ]);
4851
-
4852
- const files = [];
4853
- const lines = numstatRaw.trim().split('\n').filter(Boolean);
4854
-
4855
- for (const line of lines) {
4856
- const parts = line.split('\t');
4857
- if (parts.length < 3) continue;
4858
-
4859
- const [insertionsRaw, deletionsRaw, ...pathParts] = parts;
4860
- const filePath = pathParts.join('\t');
4861
- if (!filePath) continue;
4862
-
4863
- const insertions = insertionsRaw === '-' ? 0 : parseInt(insertionsRaw, 10) || 0;
4864
- const deletions = deletionsRaw === '-' ? 0 : parseInt(deletionsRaw, 10) || 0;
4865
- const isBinary = insertionsRaw === '-' && deletionsRaw === '-';
4866
-
4867
- let changeType = 'M';
4868
- let displayPath = filePath;
4869
-
4870
- if (filePath.includes(' => ')) {
4871
- changeType = 'R';
4872
-
4873
- const match = filePath.match(/(?:\{[^}]*\s=>\s[^}]*\}|.*\s=>\s.*)/);
4874
- if (match) {
4875
- displayPath = filePath;
4876
- }
4877
- }
4878
-
4879
- files.push({
4880
- path: displayPath,
4881
- insertions,
4882
- deletions,
4883
- isBinary,
4884
- changeType
4885
- });
4886
- }
4887
-
4888
- const nameStatusRaw = await git.raw([
4889
- 'show',
4890
- '--name-status',
4891
- '--format=',
4892
- commitHash
4893
- ]).catch(() => '');
4894
-
4895
- const statusMap = new Map();
4896
- const statusLines = nameStatusRaw.trim().split('\n').filter(Boolean);
4897
- for (const line of statusLines) {
4898
- const match = line.match(/^([AMDRC])\d*\t(.+)$/);
4899
- if (match) {
4900
- const [, status, pathPart] = match;
4901
- statusMap.set(extractGitStatusPath(status, pathPart), status);
4902
- }
4903
- }
4940
+ async function resolveCommitHash(git, hash) {
4941
+ if (!/^[0-9a-f]{7,64}$/i.test(hash)) throw new Error('A commit hash is required');
4942
+ return (await git.raw(['rev-parse', '--verify', '--end-of-options', `${hash}^{commit}`])).trim();
4943
+ }
4904
4944
 
4905
- for (const file of files) {
4906
- const basePath = extractGitNumstatDestinationPath(file.path);
4945
+ const commitShowArgs = (hash) => ['show', '--format=', '--root', '--diff-merges=first-parent', '--find-renames', hash];
4907
4946
 
4908
- const status = statusMap.get(basePath) || statusMap.get(file.path);
4909
- if (status) {
4910
- file.changeType = status;
4911
- }
4912
- }
4947
+ export async function getCommitDiff(directory, { hash, path: filePath, previousPath, contextLines = 3 } = {}) {
4948
+ const { git } = await createRepositoryGitContext(directory);
4949
+ const commit = await resolveCommitHash(git, hash);
4950
+ const paths = [filePath, previousPath].filter(Boolean).map((value) => `:(literal)${value}`);
4951
+ return git.raw([
4952
+ ...commitShowArgs(commit), '--no-color', '--no-ext-diff', `-U${Math.max(0, contextLines)}`,
4953
+ '--', ...paths,
4954
+ ]);
4955
+ }
4913
4956
 
4914
- return { files };
4915
- } catch (error) {
4916
- console.error('Failed to get commit files:', error);
4917
- throw error;
4957
+ export async function getCommitFiles(directory, commitHash) {
4958
+ const { git } = await createRepositoryGitContext(directory);
4959
+ const hash = await resolveCommitHash(git, commitHash);
4960
+ const [numstat, nameStatus] = await Promise.all([
4961
+ git.raw([...commitShowArgs(hash), '--numstat', '-z', '--']),
4962
+ git.raw([...commitShowArgs(hash), '--name-status', '-z', '--']),
4963
+ ]);
4964
+ const stats = new Map();
4965
+ const tokens = numstat.split('\0');
4966
+ for (let index = 0; index < tokens.length; index += 1) {
4967
+ const match = /^(\d+|-)\t(\d+|-)\t([\s\S]*)$/.exec(tokens[index]);
4968
+ if (!match) continue;
4969
+ let destination = match[3];
4970
+ if (!destination) {
4971
+ destination = tokens[index + 2];
4972
+ index += 2;
4973
+ }
4974
+ stats.set(destination, {
4975
+ insertions: Number.parseInt(match[1], 10) || 0,
4976
+ deletions: Number.parseInt(match[2], 10) || 0,
4977
+ isBinary: match[1] === '-',
4978
+ });
4918
4979
  }
4980
+ const files = [];
4981
+ const names = nameStatus.split('\0');
4982
+ for (let index = 0; index < names.length; index += 1) {
4983
+ const changeType = names[index].charAt(0);
4984
+ if (!changeType) continue;
4985
+ const renamed = changeType === 'R' || changeType === 'C';
4986
+ const previousPath = renamed ? names[++index] : undefined;
4987
+ const filePath = names[++index];
4988
+ const fileStats = stats.get(filePath);
4989
+ if (!filePath || !fileStats) throw new Error('Incomplete commit file statistics');
4990
+ const entry = { path: filePath, ...fileStats, changeType };
4991
+ if (previousPath) entry.previousPath = previousPath;
4992
+ files.push(entry);
4993
+ }
4994
+ return { files };
4919
4995
  }
4920
4996
 
4921
4997
  export async function renameBranch(directory, oldName, newName) {