@aiden-ade/sandbox-agent 0.1.60 → 0.1.62

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 (342) hide show
  1. package/dist/index.cjs +465 -342
  2. package/package.json +1 -1
  3. package/dist/__tests__/binding-switch-git.test.d.ts +0 -2
  4. package/dist/__tests__/binding-switch-git.test.d.ts.map +0 -1
  5. package/dist/__tests__/binding-switch-git.test.js +0 -610
  6. package/dist/__tests__/binding-switch-git.test.js.map +0 -1
  7. package/dist/__tests__/cli-ensure-install.test.d.ts +0 -2
  8. package/dist/__tests__/cli-ensure-install.test.d.ts.map +0 -1
  9. package/dist/__tests__/cli-ensure-install.test.js +0 -109
  10. package/dist/__tests__/cli-ensure-install.test.js.map +0 -1
  11. package/dist/__tests__/cli-executable.test.d.ts +0 -2
  12. package/dist/__tests__/cli-executable.test.d.ts.map +0 -1
  13. package/dist/__tests__/cli-executable.test.js +0 -129
  14. package/dist/__tests__/cli-executable.test.js.map +0 -1
  15. package/dist/__tests__/cli-help.test.d.ts +0 -2
  16. package/dist/__tests__/cli-help.test.d.ts.map +0 -1
  17. package/dist/__tests__/cli-help.test.js +0 -34
  18. package/dist/__tests__/cli-help.test.js.map +0 -1
  19. package/dist/__tests__/computer-readiness.test.d.ts +0 -2
  20. package/dist/__tests__/computer-readiness.test.d.ts.map +0 -1
  21. package/dist/__tests__/computer-readiness.test.js +0 -63
  22. package/dist/__tests__/computer-readiness.test.js.map +0 -1
  23. package/dist/__tests__/core-agent-workflow-context.test.d.ts +0 -2
  24. package/dist/__tests__/core-agent-workflow-context.test.d.ts.map +0 -1
  25. package/dist/__tests__/core-agent-workflow-context.test.js +0 -139
  26. package/dist/__tests__/core-agent-workflow-context.test.js.map +0 -1
  27. package/dist/__tests__/daemon-reconcile-ask.test.d.ts +0 -2
  28. package/dist/__tests__/daemon-reconcile-ask.test.d.ts.map +0 -1
  29. package/dist/__tests__/daemon-reconcile-ask.test.js +0 -86
  30. package/dist/__tests__/daemon-reconcile-ask.test.js.map +0 -1
  31. package/dist/__tests__/daemon-socket.integration.test.d.ts +0 -2
  32. package/dist/__tests__/daemon-socket.integration.test.d.ts.map +0 -1
  33. package/dist/__tests__/daemon-socket.integration.test.js +0 -450
  34. package/dist/__tests__/daemon-socket.integration.test.js.map +0 -1
  35. package/dist/__tests__/daemon.test.d.ts +0 -2
  36. package/dist/__tests__/daemon.test.d.ts.map +0 -1
  37. package/dist/__tests__/daemon.test.js +0 -3975
  38. package/dist/__tests__/daemon.test.js.map +0 -1
  39. package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts +0 -2
  40. package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts.map +0 -1
  41. package/dist/__tests__/ensure-shared-checkout-branch.test.js +0 -129
  42. package/dist/__tests__/ensure-shared-checkout-branch.test.js.map +0 -1
  43. package/dist/__tests__/event-outbox-persist-failure.test.d.ts +0 -2
  44. package/dist/__tests__/event-outbox-persist-failure.test.d.ts.map +0 -1
  45. package/dist/__tests__/event-outbox-persist-failure.test.js +0 -84
  46. package/dist/__tests__/event-outbox-persist-failure.test.js.map +0 -1
  47. package/dist/__tests__/event-outbox.test.d.ts +0 -2
  48. package/dist/__tests__/event-outbox.test.d.ts.map +0 -1
  49. package/dist/__tests__/event-outbox.test.js +0 -256
  50. package/dist/__tests__/event-outbox.test.js.map +0 -1
  51. package/dist/__tests__/fake-provider-cli.test.d.ts +0 -2
  52. package/dist/__tests__/fake-provider-cli.test.d.ts.map +0 -1
  53. package/dist/__tests__/fake-provider-cli.test.js +0 -46
  54. package/dist/__tests__/fake-provider-cli.test.js.map +0 -1
  55. package/dist/__tests__/generated-images.test.d.ts +0 -2
  56. package/dist/__tests__/generated-images.test.d.ts.map +0 -1
  57. package/dist/__tests__/generated-images.test.js +0 -132
  58. package/dist/__tests__/generated-images.test.js.map +0 -1
  59. package/dist/__tests__/lifecycle-logger.test.d.ts +0 -2
  60. package/dist/__tests__/lifecycle-logger.test.d.ts.map +0 -1
  61. package/dist/__tests__/lifecycle-logger.test.js +0 -49
  62. package/dist/__tests__/lifecycle-logger.test.js.map +0 -1
  63. package/dist/__tests__/local-skill-scan.test.d.ts +0 -2
  64. package/dist/__tests__/local-skill-scan.test.d.ts.map +0 -1
  65. package/dist/__tests__/local-skill-scan.test.js +0 -91
  66. package/dist/__tests__/local-skill-scan.test.js.map +0 -1
  67. package/dist/__tests__/local-workspace-migration.test.d.ts +0 -2
  68. package/dist/__tests__/local-workspace-migration.test.d.ts.map +0 -1
  69. package/dist/__tests__/local-workspace-migration.test.js +0 -195
  70. package/dist/__tests__/local-workspace-migration.test.js.map +0 -1
  71. package/dist/__tests__/mcp-inspect-timeout.test.d.ts +0 -2
  72. package/dist/__tests__/mcp-inspect-timeout.test.d.ts.map +0 -1
  73. package/dist/__tests__/mcp-inspect-timeout.test.js +0 -97
  74. package/dist/__tests__/mcp-inspect-timeout.test.js.map +0 -1
  75. package/dist/__tests__/mcp-readiness.test.d.ts +0 -2
  76. package/dist/__tests__/mcp-readiness.test.d.ts.map +0 -1
  77. package/dist/__tests__/mcp-readiness.test.js +0 -267
  78. package/dist/__tests__/mcp-readiness.test.js.map +0 -1
  79. package/dist/__tests__/mcp-registration-cli.test.d.ts +0 -2
  80. package/dist/__tests__/mcp-registration-cli.test.d.ts.map +0 -1
  81. package/dist/__tests__/mcp-registration-cli.test.js +0 -71
  82. package/dist/__tests__/mcp-registration-cli.test.js.map +0 -1
  83. package/dist/__tests__/mcp-registration.test.d.ts +0 -2
  84. package/dist/__tests__/mcp-registration.test.d.ts.map +0 -1
  85. package/dist/__tests__/mcp-registration.test.js +0 -426
  86. package/dist/__tests__/mcp-registration.test.js.map +0 -1
  87. package/dist/__tests__/model-scan-runner.test.d.ts +0 -2
  88. package/dist/__tests__/model-scan-runner.test.d.ts.map +0 -1
  89. package/dist/__tests__/model-scan-runner.test.js +0 -67
  90. package/dist/__tests__/model-scan-runner.test.js.map +0 -1
  91. package/dist/__tests__/network-diagnostics.test.d.ts +0 -2
  92. package/dist/__tests__/network-diagnostics.test.d.ts.map +0 -1
  93. package/dist/__tests__/network-diagnostics.test.js +0 -26
  94. package/dist/__tests__/network-diagnostics.test.js.map +0 -1
  95. package/dist/__tests__/observable-map.test.d.ts +0 -2
  96. package/dist/__tests__/observable-map.test.d.ts.map +0 -1
  97. package/dist/__tests__/observable-map.test.js +0 -54
  98. package/dist/__tests__/observable-map.test.js.map +0 -1
  99. package/dist/__tests__/provider-auth-preflight.test.d.ts +0 -2
  100. package/dist/__tests__/provider-auth-preflight.test.d.ts.map +0 -1
  101. package/dist/__tests__/provider-auth-preflight.test.js +0 -123
  102. package/dist/__tests__/provider-auth-preflight.test.js.map +0 -1
  103. package/dist/__tests__/provider-model-discovery.test.d.ts +0 -2
  104. package/dist/__tests__/provider-model-discovery.test.d.ts.map +0 -1
  105. package/dist/__tests__/provider-model-discovery.test.js +0 -472
  106. package/dist/__tests__/provider-model-discovery.test.js.map +0 -1
  107. package/dist/__tests__/remove-worktrees-if-clean.test.d.ts +0 -2
  108. package/dist/__tests__/remove-worktrees-if-clean.test.d.ts.map +0 -1
  109. package/dist/__tests__/remove-worktrees-if-clean.test.js +0 -221
  110. package/dist/__tests__/remove-worktrees-if-clean.test.js.map +0 -1
  111. package/dist/__tests__/resume-fallback-env.test.d.ts +0 -2
  112. package/dist/__tests__/resume-fallback-env.test.d.ts.map +0 -1
  113. package/dist/__tests__/resume-fallback-env.test.js +0 -28
  114. package/dist/__tests__/resume-fallback-env.test.js.map +0 -1
  115. package/dist/__tests__/run-journal.test.d.ts +0 -2
  116. package/dist/__tests__/run-journal.test.d.ts.map +0 -1
  117. package/dist/__tests__/run-journal.test.js +0 -121
  118. package/dist/__tests__/run-journal.test.js.map +0 -1
  119. package/dist/__tests__/run-start-gate.test.d.ts +0 -2
  120. package/dist/__tests__/run-start-gate.test.d.ts.map +0 -1
  121. package/dist/__tests__/run-start-gate.test.js +0 -29
  122. package/dist/__tests__/run-start-gate.test.js.map +0 -1
  123. package/dist/__tests__/runtime-capabilities-sync.test.d.ts +0 -2
  124. package/dist/__tests__/runtime-capabilities-sync.test.d.ts.map +0 -1
  125. package/dist/__tests__/runtime-capabilities-sync.test.js +0 -66
  126. package/dist/__tests__/runtime-capabilities-sync.test.js.map +0 -1
  127. package/dist/__tests__/runtime-config.test.d.ts +0 -2
  128. package/dist/__tests__/runtime-config.test.d.ts.map +0 -1
  129. package/dist/__tests__/runtime-config.test.js +0 -102
  130. package/dist/__tests__/runtime-config.test.js.map +0 -1
  131. package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts +0 -2
  132. package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts.map +0 -1
  133. package/dist/__tests__/runtime-presenter-heartbeat.test.js +0 -159
  134. package/dist/__tests__/runtime-presenter-heartbeat.test.js.map +0 -1
  135. package/dist/__tests__/sandbox-outbox.test.d.ts +0 -2
  136. package/dist/__tests__/sandbox-outbox.test.d.ts.map +0 -1
  137. package/dist/__tests__/sandbox-outbox.test.js +0 -214
  138. package/dist/__tests__/sandbox-outbox.test.js.map +0 -1
  139. package/dist/__tests__/sandbox-workflow-context.test.d.ts +0 -2
  140. package/dist/__tests__/sandbox-workflow-context.test.d.ts.map +0 -1
  141. package/dist/__tests__/sandbox-workflow-context.test.js +0 -396
  142. package/dist/__tests__/sandbox-workflow-context.test.js.map +0 -1
  143. package/dist/__tests__/service-manager.test.d.ts +0 -2
  144. package/dist/__tests__/service-manager.test.d.ts.map +0 -1
  145. package/dist/__tests__/service-manager.test.js +0 -50
  146. package/dist/__tests__/service-manager.test.js.map +0 -1
  147. package/dist/__tests__/sleep-inhibitor.test.d.ts +0 -2
  148. package/dist/__tests__/sleep-inhibitor.test.d.ts.map +0 -1
  149. package/dist/__tests__/sleep-inhibitor.test.js +0 -236
  150. package/dist/__tests__/sleep-inhibitor.test.js.map +0 -1
  151. package/dist/__tests__/status-local-workspace.test.d.ts +0 -2
  152. package/dist/__tests__/status-local-workspace.test.d.ts.map +0 -1
  153. package/dist/__tests__/status-local-workspace.test.js +0 -121
  154. package/dist/__tests__/status-local-workspace.test.js.map +0 -1
  155. package/dist/__tests__/suspension-detector.test.d.ts +0 -2
  156. package/dist/__tests__/suspension-detector.test.d.ts.map +0 -1
  157. package/dist/__tests__/suspension-detector.test.js +0 -112
  158. package/dist/__tests__/suspension-detector.test.js.map +0 -1
  159. package/dist/__tests__/web-presenter-parent-events.test.d.ts +0 -2
  160. package/dist/__tests__/web-presenter-parent-events.test.d.ts.map +0 -1
  161. package/dist/__tests__/web-presenter-parent-events.test.js +0 -130
  162. package/dist/__tests__/web-presenter-parent-events.test.js.map +0 -1
  163. package/dist/__tests__/workspace-relocation.test.d.ts +0 -2
  164. package/dist/__tests__/workspace-relocation.test.d.ts.map +0 -1
  165. package/dist/__tests__/workspace-relocation.test.js +0 -66
  166. package/dist/__tests__/workspace-relocation.test.js.map +0 -1
  167. package/dist/__tests__/workspace-run-lease.test.d.ts +0 -2
  168. package/dist/__tests__/workspace-run-lease.test.d.ts.map +0 -1
  169. package/dist/__tests__/workspace-run-lease.test.js +0 -230
  170. package/dist/__tests__/workspace-run-lease.test.js.map +0 -1
  171. package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts +0 -2
  172. package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts.map +0 -1
  173. package/dist/__tests__/ws-client-heartbeat-volatile.test.js +0 -61
  174. package/dist/__tests__/ws-client-heartbeat-volatile.test.js.map +0 -1
  175. package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts +0 -2
  176. package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts.map +0 -1
  177. package/dist/__tests__/ws-client-heartbeat.integration.test.js +0 -109
  178. package/dist/__tests__/ws-client-heartbeat.integration.test.js.map +0 -1
  179. package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts +0 -2
  180. package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts.map +0 -1
  181. package/dist/__tests__/ws-client-user-message-ack.integration.test.js +0 -167
  182. package/dist/__tests__/ws-client-user-message-ack.integration.test.js.map +0 -1
  183. package/dist/aiden-system-prompt.d.ts +0 -6
  184. package/dist/aiden-system-prompt.d.ts.map +0 -1
  185. package/dist/aiden-system-prompt.js +0 -6
  186. package/dist/aiden-system-prompt.js.map +0 -1
  187. package/dist/binding-switch-git.d.ts +0 -26
  188. package/dist/binding-switch-git.d.ts.map +0 -1
  189. package/dist/binding-switch-git.js +0 -973
  190. package/dist/binding-switch-git.js.map +0 -1
  191. package/dist/cli-ensure-install.d.ts +0 -39
  192. package/dist/cli-ensure-install.d.ts.map +0 -1
  193. package/dist/cli-ensure-install.js +0 -133
  194. package/dist/cli-ensure-install.js.map +0 -1
  195. package/dist/cli-executable.d.ts +0 -97
  196. package/dist/cli-executable.d.ts.map +0 -1
  197. package/dist/cli-executable.js +0 -432
  198. package/dist/cli-executable.js.map +0 -1
  199. package/dist/cli-help.d.ts +0 -7
  200. package/dist/cli-help.d.ts.map +0 -1
  201. package/dist/cli-help.js +0 -49
  202. package/dist/cli-help.js.map +0 -1
  203. package/dist/computer-readiness.d.ts +0 -42
  204. package/dist/computer-readiness.d.ts.map +0 -1
  205. package/dist/computer-readiness.js +0 -191
  206. package/dist/computer-readiness.js.map +0 -1
  207. package/dist/core-agent.d.ts +0 -47
  208. package/dist/core-agent.d.ts.map +0 -1
  209. package/dist/core-agent.js +0 -364
  210. package/dist/core-agent.js.map +0 -1
  211. package/dist/daemon.d.ts +0 -392
  212. package/dist/daemon.d.ts.map +0 -1
  213. package/dist/daemon.js +0 -4711
  214. package/dist/daemon.js.map +0 -1
  215. package/dist/event-outbox.d.ts +0 -83
  216. package/dist/event-outbox.d.ts.map +0 -1
  217. package/dist/event-outbox.js +0 -440
  218. package/dist/event-outbox.js.map +0 -1
  219. package/dist/fake-provider-cli.d.ts +0 -13
  220. package/dist/fake-provider-cli.d.ts.map +0 -1
  221. package/dist/fake-provider-cli.js +0 -53
  222. package/dist/fake-provider-cli.js.map +0 -1
  223. package/dist/generated-images.d.ts +0 -2
  224. package/dist/generated-images.d.ts.map +0 -1
  225. package/dist/generated-images.js +0 -2
  226. package/dist/generated-images.js.map +0 -1
  227. package/dist/index.d.ts +0 -6
  228. package/dist/index.d.ts.map +0 -1
  229. package/dist/index.js +0 -209
  230. package/dist/index.js.map +0 -1
  231. package/dist/lifecycle-logger.d.ts +0 -25
  232. package/dist/lifecycle-logger.d.ts.map +0 -1
  233. package/dist/lifecycle-logger.js +0 -73
  234. package/dist/lifecycle-logger.js.map +0 -1
  235. package/dist/local-skill-scan.d.ts +0 -21
  236. package/dist/local-skill-scan.d.ts.map +0 -1
  237. package/dist/local-skill-scan.js +0 -109
  238. package/dist/local-skill-scan.js.map +0 -1
  239. package/dist/local-workspace-migration.d.ts +0 -68
  240. package/dist/local-workspace-migration.d.ts.map +0 -1
  241. package/dist/local-workspace-migration.js +0 -157
  242. package/dist/local-workspace-migration.js.map +0 -1
  243. package/dist/mcp-readiness.d.ts +0 -59
  244. package/dist/mcp-readiness.d.ts.map +0 -1
  245. package/dist/mcp-readiness.js +0 -202
  246. package/dist/mcp-readiness.js.map +0 -1
  247. package/dist/mcp-registration.d.ts +0 -75
  248. package/dist/mcp-registration.d.ts.map +0 -1
  249. package/dist/mcp-registration.js +0 -563
  250. package/dist/mcp-registration.js.map +0 -1
  251. package/dist/model-scan-runner.d.ts +0 -15
  252. package/dist/model-scan-runner.d.ts.map +0 -1
  253. package/dist/model-scan-runner.js +0 -51
  254. package/dist/model-scan-runner.js.map +0 -1
  255. package/dist/network-diagnostics.d.ts +0 -8
  256. package/dist/network-diagnostics.d.ts.map +0 -1
  257. package/dist/network-diagnostics.js +0 -56
  258. package/dist/network-diagnostics.js.map +0 -1
  259. package/dist/observable-map.d.ts +0 -25
  260. package/dist/observable-map.d.ts.map +0 -1
  261. package/dist/observable-map.js +0 -42
  262. package/dist/observable-map.js.map +0 -1
  263. package/dist/provider-auth-preflight.d.ts +0 -22
  264. package/dist/provider-auth-preflight.d.ts.map +0 -1
  265. package/dist/provider-auth-preflight.js +0 -110
  266. package/dist/provider-auth-preflight.js.map +0 -1
  267. package/dist/provider-model-discovery.d.ts +0 -38
  268. package/dist/provider-model-discovery.d.ts.map +0 -1
  269. package/dist/provider-model-discovery.js +0 -519
  270. package/dist/provider-model-discovery.js.map +0 -1
  271. package/dist/resume-fallback-env.d.ts +0 -20
  272. package/dist/resume-fallback-env.d.ts.map +0 -1
  273. package/dist/resume-fallback-env.js +0 -30
  274. package/dist/resume-fallback-env.js.map +0 -1
  275. package/dist/run-journal.d.ts +0 -53
  276. package/dist/run-journal.d.ts.map +0 -1
  277. package/dist/run-journal.js +0 -193
  278. package/dist/run-journal.js.map +0 -1
  279. package/dist/run-start-gate.d.ts +0 -9
  280. package/dist/run-start-gate.d.ts.map +0 -1
  281. package/dist/run-start-gate.js +0 -24
  282. package/dist/run-start-gate.js.map +0 -1
  283. package/dist/runtime-backends.d.ts +0 -2
  284. package/dist/runtime-backends.d.ts.map +0 -1
  285. package/dist/runtime-backends.js +0 -2
  286. package/dist/runtime-backends.js.map +0 -1
  287. package/dist/runtime-capabilities-sync.d.ts +0 -33
  288. package/dist/runtime-capabilities-sync.d.ts.map +0 -1
  289. package/dist/runtime-capabilities-sync.js +0 -56
  290. package/dist/runtime-capabilities-sync.js.map +0 -1
  291. package/dist/runtime-config.d.ts +0 -24
  292. package/dist/runtime-config.d.ts.map +0 -1
  293. package/dist/runtime-config.js +0 -124
  294. package/dist/runtime-config.js.map +0 -1
  295. package/dist/sandbox-outbox.d.ts +0 -122
  296. package/dist/sandbox-outbox.d.ts.map +0 -1
  297. package/dist/sandbox-outbox.js +0 -180
  298. package/dist/sandbox-outbox.js.map +0 -1
  299. package/dist/sandbox.d.ts +0 -44
  300. package/dist/sandbox.d.ts.map +0 -1
  301. package/dist/sandbox.js +0 -438
  302. package/dist/sandbox.js.map +0 -1
  303. package/dist/service-manager.d.ts +0 -28
  304. package/dist/service-manager.d.ts.map +0 -1
  305. package/dist/service-manager.js +0 -259
  306. package/dist/service-manager.js.map +0 -1
  307. package/dist/sleep-inhibitor.d.ts +0 -111
  308. package/dist/sleep-inhibitor.d.ts.map +0 -1
  309. package/dist/sleep-inhibitor.js +0 -412
  310. package/dist/sleep-inhibitor.js.map +0 -1
  311. package/dist/suspension-detector.d.ts +0 -48
  312. package/dist/suspension-detector.d.ts.map +0 -1
  313. package/dist/suspension-detector.js +0 -89
  314. package/dist/suspension-detector.js.map +0 -1
  315. package/dist/types.d.ts +0 -6
  316. package/dist/types.d.ts.map +0 -1
  317. package/dist/types.js +0 -6
  318. package/dist/types.js.map +0 -1
  319. package/dist/updater.d.ts +0 -15
  320. package/dist/updater.d.ts.map +0 -1
  321. package/dist/updater.js +0 -50
  322. package/dist/updater.js.map +0 -1
  323. package/dist/version.d.ts +0 -2
  324. package/dist/version.d.ts.map +0 -1
  325. package/dist/version.js +0 -2
  326. package/dist/version.js.map +0 -1
  327. package/dist/web-presenter.d.ts +0 -55
  328. package/dist/web-presenter.d.ts.map +0 -1
  329. package/dist/web-presenter.js +0 -265
  330. package/dist/web-presenter.js.map +0 -1
  331. package/dist/workspace-relocation.d.ts +0 -22
  332. package/dist/workspace-relocation.d.ts.map +0 -1
  333. package/dist/workspace-relocation.js +0 -90
  334. package/dist/workspace-relocation.js.map +0 -1
  335. package/dist/workspace-run-lease.d.ts +0 -97
  336. package/dist/workspace-run-lease.d.ts.map +0 -1
  337. package/dist/workspace-run-lease.js +0 -241
  338. package/dist/workspace-run-lease.js.map +0 -1
  339. package/dist/ws-client.d.ts +0 -194
  340. package/dist/ws-client.d.ts.map +0 -1
  341. package/dist/ws-client.js +0 -282
  342. package/dist/ws-client.js.map +0 -1
@@ -1,973 +0,0 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import { copyFileSync, existsSync, mkdirSync, mkdtempSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs";
4
- import { tmpdir } from "node:os";
5
- import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
- import { getDaemonCliEnvironment } from "./cli-executable.js";
8
- export const BINDING_SWITCH_GIT_METHODS = [
9
- "git.status",
10
- "git.checkpoint",
11
- "git.push",
12
- "git.fetch",
13
- "git.prepare_checkout",
14
- "git.prepare_worktree",
15
- "git.verify_head",
16
- ];
17
- export function isBindingSwitchGitMethod(method) {
18
- return BINDING_SWITCH_GIT_METHODS.includes(method);
19
- }
20
- export class BindingSwitchGitError extends Error {
21
- code;
22
- details;
23
- constructor(code, message, details) {
24
- super(message);
25
- this.code = code;
26
- this.details = details;
27
- this.name = "BindingSwitchGitError";
28
- }
29
- }
30
- const LOCAL_GIT_TIMEOUT_MS = 10_000;
31
- const NETWORK_GIT_TIMEOUT_MS = 30_000;
32
- const MAX_GIT_OUTPUT_BYTES = 10 * 1024 * 1024;
33
- function runGitResult(cwd, args, options = {}) {
34
- const result = spawnSync("git", args, {
35
- cwd,
36
- env: { ...getDaemonCliEnvironment(), ...options.env },
37
- encoding: "utf8",
38
- input: options.input,
39
- maxBuffer: MAX_GIT_OUTPUT_BYTES,
40
- timeout: LOCAL_GIT_TIMEOUT_MS,
41
- });
42
- if (result.error) {
43
- return { status: 1, stdout: "", stderr: result.error.message };
44
- }
45
- return {
46
- status: result.status ?? 1,
47
- stdout: result.stdout ?? "",
48
- stderr: result.stderr ?? "",
49
- };
50
- }
51
- function networkGitTimeoutMs() {
52
- if (process.env.NODE_ENV !== "test")
53
- return NETWORK_GIT_TIMEOUT_MS;
54
- const override = Number.parseInt(process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS ?? "", 10);
55
- return Number.isFinite(override) && override >= 10
56
- ? Math.min(override, NETWORK_GIT_TIMEOUT_MS)
57
- : NETWORK_GIT_TIMEOUT_MS;
58
- }
59
- async function waitForCheckpointTestBarrier() {
60
- const barrierDir = process.env.NODE_ENV === "test" ? process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR : undefined;
61
- if (!barrierDir)
62
- return;
63
- mkdirSync(barrierDir, { recursive: true });
64
- const readyPath = join(barrierDir, "ready");
65
- const releasePath = join(barrierDir, "release");
66
- writeFileSync(readyPath, "ready\n");
67
- const deadline = Date.now() + 5_000;
68
- while (!existsSync(releasePath)) {
69
- if (Date.now() >= deadline) {
70
- throw new BindingSwitchGitError("checkpoint_prepare_timeout", "Checkpoint preparation test barrier timed out");
71
- }
72
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
73
- }
74
- }
75
- async function waitForCheckpointFinalizeTestBarrier() {
76
- const barrierDir = process.env.NODE_ENV === "test"
77
- ? process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR
78
- : undefined;
79
- if (!barrierDir)
80
- return;
81
- mkdirSync(barrierDir, { recursive: true });
82
- const readyPath = join(barrierDir, "ready");
83
- const releasePath = join(barrierDir, "release");
84
- writeFileSync(readyPath, "ready\n");
85
- const deadline = Date.now() + 5_000;
86
- while (!existsSync(releasePath)) {
87
- if (Date.now() >= deadline) {
88
- throw new BindingSwitchGitError("checkpoint_finalize_timeout", "Checkpoint finalization test barrier timed out");
89
- }
90
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
91
- }
92
- }
93
- async function runNetworkGit(cwd, args, code) {
94
- return new Promise((resolvePromise, rejectPromise) => {
95
- const child = spawn("git", args, {
96
- cwd,
97
- env: {
98
- ...getDaemonCliEnvironment(),
99
- GCM_INTERACTIVE: "Never",
100
- GIT_TERMINAL_PROMPT: "0",
101
- ...(process.env.NODE_ENV === "test" && process.env.ALAN_TEST_GIT_SSH_COMMAND
102
- ? { GIT_SSH_COMMAND: process.env.ALAN_TEST_GIT_SSH_COMMAND }
103
- : {}),
104
- },
105
- detached: process.platform !== "win32",
106
- stdio: ["ignore", "pipe", "pipe"],
107
- });
108
- let stdout = "";
109
- let stderr = "";
110
- let outputExceeded = false;
111
- let timedOut = false;
112
- let settled = false;
113
- const killTree = () => {
114
- if (child.pid && process.platform !== "win32") {
115
- try {
116
- process.kill(-child.pid, "SIGKILL");
117
- return;
118
- }
119
- catch {
120
- // The process may already have exited between the timeout and kill.
121
- }
122
- }
123
- child.kill("SIGKILL");
124
- };
125
- const timer = setTimeout(() => {
126
- timedOut = true;
127
- killTree();
128
- }, networkGitTimeoutMs());
129
- timer.unref?.();
130
- const append = (current, chunk) => {
131
- if (Buffer.byteLength(current) + chunk.byteLength > MAX_GIT_OUTPUT_BYTES) {
132
- outputExceeded = true;
133
- killTree();
134
- return current;
135
- }
136
- return current + chunk.toString("utf8");
137
- };
138
- child.stdout.on("data", (chunk) => {
139
- stdout = append(stdout, chunk);
140
- });
141
- child.stderr.on("data", (chunk) => {
142
- stderr = append(stderr, chunk);
143
- });
144
- child.once("error", (error) => {
145
- if (settled)
146
- return;
147
- settled = true;
148
- clearTimeout(timer);
149
- rejectPromise(new BindingSwitchGitError(code, error.message));
150
- });
151
- child.once("close", (exitCode) => {
152
- if (settled)
153
- return;
154
- settled = true;
155
- clearTimeout(timer);
156
- if (timedOut) {
157
- rejectPromise(new BindingSwitchGitError("git_network_timeout", "The Git network operation timed out and was cancelled"));
158
- return;
159
- }
160
- if (outputExceeded) {
161
- rejectPromise(new BindingSwitchGitError("git_output_limit_exceeded", "The Git network operation produced too much output and was cancelled"));
162
- return;
163
- }
164
- if (exitCode !== 0) {
165
- rejectPromise(new BindingSwitchGitError(code, (stderr || stdout || "Git network command failed").trim()));
166
- return;
167
- }
168
- resolvePromise(stdout.trim());
169
- });
170
- });
171
- }
172
- function runGit(cwd, args, code = "git_operation_failed") {
173
- return runGitWithOptions(cwd, args, {}, code);
174
- }
175
- function runGitWithOptions(cwd, args, options, code = "git_operation_failed") {
176
- const result = runGitResult(cwd, args, options);
177
- if (result.status !== 0) {
178
- throw new BindingSwitchGitError(code, (result.stderr || result.stdout || "Git command failed").trim());
179
- }
180
- return result.stdout.trim();
181
- }
182
- function sourceChangedDuringCheckpoint(message) {
183
- return new BindingSwitchGitError("source_changed_since_approval", message);
184
- }
185
- function acquireCheckpointHeadLock(locks) {
186
- try {
187
- writeFileSync(locks.headLockPath, "", { flag: "wx" });
188
- locks.headLockHeld = true;
189
- }
190
- catch {
191
- throw sourceChangedDuringCheckpoint("Source repository is being changed by another Git operation");
192
- }
193
- }
194
- function releaseCheckpointHeadLock(locks) {
195
- if (!locks.headLockHeld)
196
- return;
197
- rmSync(locks.headLockPath, { force: true });
198
- locks.headLockHeld = false;
199
- }
200
- function acquireCheckpointRepositoryLocks(repoRoot) {
201
- const gitDir = runGit(repoRoot, ["rev-parse", "--absolute-git-dir"], "checkpoint_lock_unavailable");
202
- const indexPath = runGit(repoRoot, ["rev-parse", "--path-format=absolute", "--git-path", "index"], "checkpoint_lock_unavailable");
203
- const locks = {
204
- headLockPath: join(gitDir, "HEAD.lock"),
205
- headLockHeld: false,
206
- indexLockPath: `${indexPath}.lock`,
207
- indexLockHeld: false,
208
- indexPath,
209
- };
210
- try {
211
- writeFileSync(locks.indexLockPath, "", { flag: "wx" });
212
- locks.indexLockHeld = true;
213
- acquireCheckpointHeadLock(locks);
214
- return locks;
215
- }
216
- catch {
217
- if (locks.indexLockHeld) {
218
- rmSync(locks.indexLockPath, { force: true });
219
- locks.indexLockHeld = false;
220
- }
221
- throw sourceChangedDuringCheckpoint("Source repository is being changed by another Git operation");
222
- }
223
- }
224
- function releaseCheckpointRepositoryLocks(locks) {
225
- releaseCheckpointHeadLock(locks);
226
- if (locks.indexLockHeld) {
227
- rmSync(locks.indexLockPath, { force: true });
228
- locks.indexLockHeld = false;
229
- }
230
- }
231
- function assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha) {
232
- const currentRef = runGit(repoRoot, ["symbolic-ref", "-q", "HEAD"], "source_changed_since_approval");
233
- const currentHead = runGit(repoRoot, ["rev-parse", "HEAD"], "source_changed_since_approval");
234
- if (currentRef !== expectedRef || currentHead !== expectedHeadSha) {
235
- throw sourceChangedDuringCheckpoint("Source HEAD or branch changed during checkpoint finalization");
236
- }
237
- }
238
- function createCheckpointIndexBaseline(repoRoot, checkpointSha) {
239
- const tempDir = mkdtempSync(join(tmpdir(), "alan-checkpoint-index-"));
240
- const indexPath = join(tempDir, "index");
241
- try {
242
- runGitWithOptions(repoRoot, ["read-tree", checkpointSha], { env: { GIT_INDEX_FILE: indexPath } }, "checkpoint_index_sync_failed");
243
- return { indexPath, tempDir };
244
- }
245
- catch (error) {
246
- rmSync(tempDir, { recursive: true, force: true });
247
- throw error;
248
- }
249
- }
250
- function installCheckpointIndexBaseline(locks, baselineIndexPath) {
251
- try {
252
- copyFileSync(baselineIndexPath, locks.indexLockPath);
253
- renameSync(locks.indexLockPath, locks.indexPath);
254
- locks.indexLockHeld = false;
255
- }
256
- catch (error) {
257
- throw new BindingSwitchGitError("checkpoint_index_sync_failed", error instanceof Error ? error.message : "The checkpoint index could not be installed");
258
- }
259
- }
260
- function requiredString(params, key) {
261
- const value = params[key];
262
- if (typeof value !== "string" || value.trim() === "") {
263
- throw new BindingSwitchGitError("invalid_git_request", `${key} is required`);
264
- }
265
- if (/[\0\r\n]/.test(value)) {
266
- throw new BindingSwitchGitError("invalid_git_request", `${key} contains unsafe characters`);
267
- }
268
- return value;
269
- }
270
- function requiredCommitMessage(params) {
271
- const value = params.commitMessage;
272
- if (typeof value !== "string" || value.trim() === "") {
273
- throw new BindingSwitchGitError("invalid_git_request", "commitMessage is required");
274
- }
275
- // Commit messages are passed to `git commit-tree` through stdin, not a shell
276
- // argument. Newlines are both safe and required for trailers such as
277
- // Co-Authored-By; only NUL cannot be represented safely in Git commit text.
278
- if (value.includes("\0")) {
279
- throw new BindingSwitchGitError("invalid_git_request", "commitMessage contains unsafe characters");
280
- }
281
- return value;
282
- }
283
- function optionalString(params, key) {
284
- const value = params[key];
285
- if (value === undefined || value === null)
286
- return undefined;
287
- if (typeof value !== "string" || value.trim() === "" || /[\0\r\n]/.test(value)) {
288
- throw new BindingSwitchGitError("invalid_git_request", `${key} is invalid`);
289
- }
290
- return value;
291
- }
292
- function commonRequest(params) {
293
- return {
294
- switchId: requiredString(params, "switchId"),
295
- repoKey: requiredString(params, "repoKey"),
296
- repoPath: resolve(requiredString(params, "repoPath")),
297
- };
298
- }
299
- function assertCommitSha(sha, key) {
300
- if (!/^[0-9a-f]{40,64}$/i.test(sha)) {
301
- throw new BindingSwitchGitError("invalid_git_request", `${key} is not a commit SHA`);
302
- }
303
- }
304
- function assertValidBranch(branch) {
305
- if (branch.startsWith("-")) {
306
- throw new BindingSwitchGitError("invalid_git_request", "branch is not a safe Git ref");
307
- }
308
- const result = spawnSync("git", ["check-ref-format", "--branch", branch], {
309
- env: getDaemonCliEnvironment(),
310
- encoding: "utf8",
311
- });
312
- if (result.status !== 0) {
313
- throw new BindingSwitchGitError("invalid_git_request", "branch is not a valid Git ref");
314
- }
315
- }
316
- function validatedRemote(params) {
317
- const remote = optionalString(params, "remote") ?? "origin";
318
- if (remote.startsWith("-") || remote.includes("/") || remote.includes("\\")) {
319
- throw new BindingSwitchGitError("invalid_git_request", "remote is not a safe remote name");
320
- }
321
- return remote;
322
- }
323
- function canonicalRemoteUrl(repoRoot, value) {
324
- const trimmed = value.trim();
325
- if (!trimmed || /[\0\r\n]/.test(trimmed)) {
326
- throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
327
- }
328
- const scpStyle = trimmed.match(/^([^/@:\s]+)@([^/:\s]+):(.+)$/);
329
- const urlValue = scpStyle ? `ssh://${scpStyle[1]}@${scpStyle[2]}/${scpStyle[3]}` : trimmed;
330
- if (isAbsolute(urlValue) || urlValue.startsWith("./") || urlValue.startsWith("../")) {
331
- const absolute = isAbsolute(urlValue) ? urlValue : resolve(repoRoot, urlValue);
332
- return `file://${canonicalPathWithMissingTail(absolute)
333
- .replace(/\/+$/, "")
334
- .replace(/\.git$/, "")}`;
335
- }
336
- let parsed;
337
- try {
338
- parsed = new URL(urlValue);
339
- }
340
- catch {
341
- throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
342
- }
343
- if (parsed.protocol === "file:") {
344
- return `file://${canonicalPathWithMissingTail(fileURLToPath(parsed))
345
- .replace(/\/+$/, "")
346
- .replace(/\.git$/, "")}`;
347
- }
348
- if (parsed.search || parsed.hash) {
349
- throw new BindingSwitchGitError("invalid_git_request", "Remote URL is invalid");
350
- }
351
- if (!["https:", "http:", "ssh:", "git:"].includes(parsed.protocol)) {
352
- throw new BindingSwitchGitError("invalid_git_request", "Remote URL uses an unsupported protocol");
353
- }
354
- if (parsed.password ||
355
- ((parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.username)) {
356
- throw new BindingSwitchGitError("remote_url_credentials_rejected", "Credential-bearing remote URLs are not allowed for environment migration");
357
- }
358
- const defaultPort = (parsed.protocol === "https:" && parsed.port === "443") ||
359
- (parsed.protocol === "http:" && parsed.port === "80") ||
360
- (parsed.protocol === "ssh:" && parsed.port === "22");
361
- const transport = parsed.protocol === "https:" || parsed.protocol === "ssh:" ? "secure:" : parsed.protocol;
362
- const authority = `${parsed.hostname.toLowerCase()}${parsed.port && !defaultPort ? `:${parsed.port}` : ""}`;
363
- const path = parsed.pathname
364
- .replace(/\/+/g, "/")
365
- .replace(/\/+$/, "")
366
- .replace(/\.git$/, "");
367
- return `${transport}//${authority}${path}`;
368
- }
369
- function verifiedRemote(repoRoot, params, includePushUrls) {
370
- const remote = validatedRemote(params);
371
- const expectedRemoteUrl = canonicalRemoteUrl(repoRoot, requiredString(params, "expectedRemoteUrl"));
372
- const fetchUrls = runGit(repoRoot, ["remote", "get-url", "--all", remote], "git_remote_unavailable")
373
- .split("\n")
374
- .filter(Boolean);
375
- const pushUrls = includePushUrls
376
- ? runGit(repoRoot, ["remote", "get-url", "--push", "--all", remote], "git_remote_unavailable")
377
- .split("\n")
378
- .filter(Boolean)
379
- : [];
380
- const urls = [...fetchUrls, ...pushUrls];
381
- if (urls.length === 0 ||
382
- urls.some((url) => canonicalRemoteUrl(repoRoot, url) !== expectedRemoteUrl)) {
383
- throw new BindingSwitchGitError("remote_url_mismatch", "The configured Git remote does not match the repository selected for migration");
384
- }
385
- return { remote, expectedRemoteUrl, remoteUrl: fetchUrls[0] };
386
- }
387
- function inspectedRemoteUrl(repoRoot, params) {
388
- if (optionalString(params, "expectedRemoteUrl")) {
389
- return verifiedRemote(repoRoot, params, false).remoteUrl;
390
- }
391
- const remote = validatedRemote(params);
392
- const result = runGitResult(repoRoot, ["remote", "get-url", "--all", remote]);
393
- const urls = result.status === 0 ? result.stdout.split("\n").filter(Boolean) : [];
394
- if (urls.length === 0)
395
- return null;
396
- for (const url of urls)
397
- canonicalRemoteUrl(repoRoot, url);
398
- return urls[0];
399
- }
400
- function isPathContained(childPath, parentPath) {
401
- const rel = relative(resolve(parentPath), resolve(childPath));
402
- return rel === "" || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`));
403
- }
404
- function canonicalPathWithMissingTail(path) {
405
- const missingSegments = [];
406
- let cursor = resolve(path);
407
- while (!existsSync(cursor)) {
408
- const parent = dirname(cursor);
409
- if (parent === cursor) {
410
- throw new BindingSwitchGitError("path_canonicalization_failed", "The selected path has no existing canonical ancestor");
411
- }
412
- missingSegments.unshift(basename(cursor));
413
- cursor = parent;
414
- }
415
- return join(realpathSync.native(cursor), ...missingSegments);
416
- }
417
- function assertCanonicalPathContained(childPath, parentPath, code, message) {
418
- if (!isPathContained(canonicalPathWithMissingTail(childPath), canonicalPathWithMissingTail(parentPath))) {
419
- throw new BindingSwitchGitError(code, message);
420
- }
421
- }
422
- function alanWorktreeRoot(projectRoot) {
423
- return join(dirname(projectRoot), `${basename(projectRoot)}-alan-worktrees`);
424
- }
425
- function resolveRepositoryRoot(requestedPath) {
426
- if (!existsSync(requestedPath)) {
427
- throw new BindingSwitchGitError("repository_not_found", `The selected repository path does not exist: ${requestedPath}`);
428
- }
429
- const root = runGitResult(requestedPath, ["rev-parse", "--show-toplevel"]);
430
- if (root.status !== 0 || !root.stdout.trim()) {
431
- throw new BindingSwitchGitError("repository_not_found", "The selected folder is not inside a Git repository");
432
- }
433
- return realpathSync.native(resolve(root.stdout.trim()));
434
- }
435
- function listWorktrees(repoRoot) {
436
- return runGit(repoRoot, ["worktree", "list", "--porcelain"])
437
- .split("\n\n")
438
- .flatMap((entry) => {
439
- const lines = entry.split("\n");
440
- const path = lines.find((line) => line.startsWith("worktree "))?.slice("worktree ".length);
441
- if (!path)
442
- return [];
443
- return [
444
- {
445
- path: canonicalPathWithMissingTail(path),
446
- branch: lines
447
- .find((line) => line.startsWith("branch "))
448
- ?.slice("branch ".length)
449
- .replace("refs/heads/", "") ?? null,
450
- },
451
- ];
452
- });
453
- }
454
- function resolveAuthorizedRepository(params, requireTrustedContext = false) {
455
- const { repoPath } = commonRequest(params);
456
- const repoRoot = resolveRepositoryRoot(repoPath);
457
- const expectedRepoRoot = requireTrustedContext
458
- ? requiredString(params, "expectedRepoRoot")
459
- : optionalString(params, "expectedRepoRoot");
460
- if (expectedRepoRoot && resolveRepositoryRoot(resolve(expectedRepoRoot)) !== repoRoot) {
461
- throw new BindingSwitchGitError("repository_path_changed", "The selected path now resolves to a different Git repository");
462
- }
463
- const projectPath = requireTrustedContext
464
- ? requiredString(params, "projectPath")
465
- : optionalString(params, "projectPath");
466
- if (!projectPath)
467
- return repoRoot;
468
- const projectRoot = resolveRepositoryRoot(resolve(projectPath));
469
- const registrations = listWorktrees(projectRoot);
470
- const registered = registrations.find(({ path }) => path === repoRoot);
471
- if (!registered) {
472
- throw new BindingSwitchGitError("repository_path_not_authorized", "The selected repository is not the Project checkout or one of its registered worktrees");
473
- }
474
- const mainRoot = registrations[0]?.path;
475
- if (mainRoot &&
476
- repoRoot !== mainRoot &&
477
- !isPathContained(repoRoot, canonicalPathWithMissingTail(alanWorktreeRoot(mainRoot)))) {
478
- throw new BindingSwitchGitError("repository_path_not_authorized", "The selected worktree is outside the Alan-managed worktree directory");
479
- }
480
- return repoRoot;
481
- }
482
- function readRawGit(repoRoot, args, code) {
483
- const result = runGitResult(repoRoot, args);
484
- if (result.status !== 0) {
485
- throw new BindingSwitchGitError(code, (result.stderr || result.stdout || "Git inspection failed").trim());
486
- }
487
- return result.stdout;
488
- }
489
- function updateLengthPrefixed(hash, value) {
490
- const bytes = Buffer.from(value, "utf8");
491
- hash.update(String(bytes.byteLength));
492
- hash.update("\0");
493
- hash.update(bytes);
494
- hash.update("\0");
495
- }
496
- function proposedTreeSha(repoRoot, headSha) {
497
- const tempDir = mkdtempSync(join(tmpdir(), "alan-switch-index-"));
498
- const indexPath = join(tempDir, "index");
499
- const options = { env: { GIT_INDEX_FILE: indexPath } };
500
- try {
501
- if (headSha) {
502
- runGitWithOptions(repoRoot, ["read-tree", headSha], options, "git_status_unreadable");
503
- }
504
- else {
505
- runGitWithOptions(repoRoot, ["read-tree", "--empty"], options, "git_status_unreadable");
506
- }
507
- runGitWithOptions(repoRoot, ["add", "--all"], options, "git_status_unreadable");
508
- const treeSha = runGitWithOptions(repoRoot, ["write-tree"], options, "git_status_unreadable");
509
- assertCommitSha(treeSha, "proposedTreeSha");
510
- return treeSha;
511
- }
512
- finally {
513
- rmSync(tempDir, { recursive: true, force: true });
514
- }
515
- }
516
- function statusFingerprint(repoRoot, porcelain, proposedTree) {
517
- const unstagedPatch = readRawGit(repoRoot, ["diff", "--binary", "--full-index", "--no-ext-diff", "--submodule=log"], "git_status_unreadable");
518
- const stagedPatch = readRawGit(repoRoot, ["diff", "--cached", "--binary", "--full-index", "--no-ext-diff", "--submodule=log"], "git_status_unreadable");
519
- const untrackedRaw = readRawGit(repoRoot, ["ls-files", "--others", "--exclude-standard", "-z"], "git_status_unreadable");
520
- const untrackedPaths = untrackedRaw.split("\0").filter(Boolean);
521
- const hash = createHash("sha256");
522
- updateLengthPrefixed(hash, porcelain);
523
- updateLengthPrefixed(hash, proposedTree);
524
- updateLengthPrefixed(hash, unstagedPatch);
525
- updateLengthPrefixed(hash, stagedPatch);
526
- for (let index = 0; index < untrackedPaths.length; index += 128) {
527
- const paths = untrackedPaths.slice(index, index + 128);
528
- const hashes = readRawGit(repoRoot, ["hash-object", "--no-filters", "--", ...paths], "git_status_unreadable");
529
- updateLengthPrefixed(hash, paths.join("\0"));
530
- updateLengthPrefixed(hash, hashes);
531
- }
532
- return hash.digest("hex");
533
- }
534
- function readStatus(repoRoot) {
535
- const headResult = runGitResult(repoRoot, ["rev-parse", "HEAD"]);
536
- const headSha = headResult.status === 0 ? headResult.stdout.trim() || null : null;
537
- const branchResult = runGitResult(repoRoot, ["branch", "--show-current"]);
538
- const branch = branchResult.status === 0 ? branchResult.stdout.trim() || null : null;
539
- const proposedTree = proposedTreeSha(repoRoot, headSha);
540
- const porcelain = runGit(repoRoot, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
541
- const displayStatus = runGit(repoRoot, ["status", "--porcelain=v1", "--untracked-files=all"]);
542
- const upstreamResult = runGitResult(repoRoot, [
543
- "rev-parse",
544
- "--abbrev-ref",
545
- "--symbolic-full-name",
546
- "@{upstream}",
547
- ]);
548
- const upstream = upstreamResult.status === 0 ? upstreamResult.stdout.trim() || null : null;
549
- let ahead = null;
550
- let behind = null;
551
- if (upstream) {
552
- const counts = runGit(repoRoot, [
553
- "rev-list",
554
- "--left-right",
555
- "--count",
556
- `${upstream}...HEAD`,
557
- ]).split(/\s+/);
558
- const parsedBehind = Number.parseInt(counts[0] ?? "", 10);
559
- const parsedAhead = Number.parseInt(counts[1] ?? "", 10);
560
- behind = Number.isNaN(parsedBehind) ? null : parsedBehind;
561
- ahead = Number.isNaN(parsedAhead) ? null : parsedAhead;
562
- }
563
- return {
564
- repoRoot,
565
- remoteUrl: null,
566
- branch,
567
- headSha,
568
- dirty: porcelain.length > 0,
569
- filesChanged: displayStatus ? displayStatus.split("\n").length : 0,
570
- ahead,
571
- behind,
572
- hasUpstream: upstream !== null,
573
- upstream,
574
- statusFingerprint: statusFingerprint(repoRoot, porcelain, proposedTree),
575
- proposedTreeSha: proposedTree,
576
- };
577
- }
578
- function assertExpectedBranch(status, expectedBranch) {
579
- assertValidBranch(expectedBranch);
580
- if (status.branch !== expectedBranch) {
581
- throw new BindingSwitchGitError("source_changed_since_approval", `Source branch changed from ${expectedBranch} to ${status.branch ?? "detached HEAD"}`);
582
- }
583
- }
584
- function assertSourcePreconditions(status, expectedHeadSha, expectedStatusFingerprint, expectedBranch) {
585
- assertCommitSha(expectedHeadSha, "expectedHeadSha");
586
- if (expectedBranch !== undefined)
587
- assertExpectedBranch(status, expectedBranch);
588
- if (status.headSha !== expectedHeadSha) {
589
- throw new BindingSwitchGitError("source_changed_since_approval", `Source HEAD changed from ${expectedHeadSha} to ${status.headSha ?? "unborn"}`);
590
- }
591
- if (expectedStatusFingerprint !== undefined &&
592
- status.statusFingerprint !== expectedStatusFingerprint) {
593
- throw new BindingSwitchGitError("source_changed_since_approval", "Source files changed after the migration inspection");
594
- }
595
- }
596
- function commitHasSwitchTrailers(repoRoot, switchId, repoKey) {
597
- const message = runGitResult(repoRoot, ["show", "-s", "--format=%B", "HEAD"]);
598
- if (message.status !== 0)
599
- return false;
600
- const lines = message.stdout.split(/\r?\n/);
601
- return (lines.includes(`Alan-Switch-Id: ${switchId}`) &&
602
- lines.includes(`Alan-Repository-Id: ${repoKey}`));
603
- }
604
- async function checkpoint(params) {
605
- const { switchId, repoKey } = commonRequest(params);
606
- const repoRoot = resolveAuthorizedRepository(params, true);
607
- const expectedHeadSha = requiredString(params, "expectedHeadSha");
608
- const expectedStatusFingerprint = requiredString(params, "expectedStatusFingerprint");
609
- const expectedProposedTreeSha = requiredString(params, "expectedProposedTreeSha");
610
- assertCommitSha(expectedProposedTreeSha, "expectedProposedTreeSha");
611
- const expectedBranch = requiredString(params, "expectedBranch");
612
- const status = readStatus(repoRoot);
613
- assertExpectedBranch(status, expectedBranch);
614
- if (commitHasSwitchTrailers(repoRoot, switchId, repoKey)) {
615
- if (!status.headSha) {
616
- throw new BindingSwitchGitError("source_head_unreadable", "Checkpoint HEAD is unreadable");
617
- }
618
- const expectedRef = `refs/heads/${expectedBranch}`;
619
- const baseline = createCheckpointIndexBaseline(repoRoot, status.headSha);
620
- let locks = null;
621
- try {
622
- locks = acquireCheckpointRepositoryLocks(repoRoot);
623
- assertCheckpointHead(repoRoot, expectedRef, status.headSha);
624
- installCheckpointIndexBaseline(locks, baseline.indexPath);
625
- return { repoRoot, sha: status.headSha, replayed: true };
626
- }
627
- finally {
628
- if (locks)
629
- releaseCheckpointRepositoryLocks(locks);
630
- rmSync(baseline.tempDir, { recursive: true, force: true });
631
- }
632
- }
633
- assertSourcePreconditions(status, expectedHeadSha, expectedStatusFingerprint, expectedBranch);
634
- if (status.proposedTreeSha !== expectedProposedTreeSha) {
635
- throw new BindingSwitchGitError("source_changed_since_approval", "The proposed checkpoint tree changed after migration approval");
636
- }
637
- if (!status.dirty) {
638
- if (!status.headSha) {
639
- throw new BindingSwitchGitError("source_head_unreadable", "Source HEAD is unreadable");
640
- }
641
- return { repoRoot, sha: status.headSha, replayed: true };
642
- }
643
- const commitMessage = requiredCommitMessage(params).trim();
644
- const authorName = requiredString(params, "authorName");
645
- const authorEmail = requiredString(params, "authorEmail");
646
- const expectedHeadTree = runGit(repoRoot, ["rev-parse", `${expectedHeadSha}^{tree}`], "source_head_unreadable");
647
- if (expectedHeadTree === expectedProposedTreeSha) {
648
- throw new BindingSwitchGitError("checkpoint_empty", "The inspected changes did not produce a checkpoint commit");
649
- }
650
- const proposedTreeObject = runGitResult(repoRoot, [
651
- "cat-file",
652
- "-e",
653
- `${expectedProposedTreeSha}^{tree}`,
654
- ]);
655
- if (proposedTreeObject.status !== 0) {
656
- throw new BindingSwitchGitError("checkpoint_tree_unavailable", "The approved checkpoint tree is no longer available");
657
- }
658
- await waitForCheckpointTestBarrier();
659
- const currentBranch = runGit(repoRoot, ["branch", "--show-current"]);
660
- const currentHead = runGit(repoRoot, ["rev-parse", "HEAD"], "source_head_unreadable");
661
- if (currentBranch !== expectedBranch || currentHead !== expectedHeadSha) {
662
- throw new BindingSwitchGitError("source_changed_since_approval", "Source HEAD or branch changed during checkpoint preparation");
663
- }
664
- const message = `${commitMessage}\n\nAlan-Switch-Id: ${switchId}\nAlan-Repository-Id: ${repoKey}`;
665
- const sha = runGitWithOptions(repoRoot, ["commit-tree", expectedProposedTreeSha, "-p", expectedHeadSha], {
666
- env: {
667
- GIT_AUTHOR_EMAIL: authorEmail,
668
- GIT_AUTHOR_NAME: authorName,
669
- GIT_COMMITTER_EMAIL: authorEmail,
670
- GIT_COMMITTER_NAME: authorName,
671
- },
672
- input: `${message}\n`,
673
- }, "checkpoint_commit_failed");
674
- assertCommitSha(sha, "checkpointSha");
675
- const expectedRef = `refs/heads/${expectedBranch}`;
676
- const currentRef = runGit(repoRoot, ["symbolic-ref", "-q", "HEAD"], "source_changed_since_approval");
677
- if (currentRef !== expectedRef) {
678
- throw new BindingSwitchGitError("source_changed_since_approval", "Source branch changed during checkpoint preparation");
679
- }
680
- await waitForCheckpointFinalizeTestBarrier();
681
- const baseline = createCheckpointIndexBaseline(repoRoot, sha);
682
- let locks = null;
683
- let refAdvanced = false;
684
- let indexInstalled = false;
685
- try {
686
- locks = acquireCheckpointRepositoryLocks(repoRoot);
687
- assertCheckpointHead(repoRoot, expectedRef, expectedHeadSha);
688
- // update-ref maintains the reflog for the currently checked-out branch and
689
- // therefore takes HEAD.lock itself. Keep index.lock held so checkout-like
690
- // operations cannot switch branches while Git briefly owns HEAD.lock.
691
- releaseCheckpointHeadLock(locks);
692
- runGit(repoRoot, ["update-ref", expectedRef, sha, expectedHeadSha], "source_changed_since_approval");
693
- refAdvanced = true;
694
- acquireCheckpointHeadLock(locks);
695
- assertCheckpointHead(repoRoot, expectedRef, sha);
696
- installCheckpointIndexBaseline(locks, baseline.indexPath);
697
- indexInstalled = true;
698
- return { repoRoot, sha, replayed: false };
699
- }
700
- finally {
701
- if (refAdvanced && !indexInstalled) {
702
- if (locks)
703
- releaseCheckpointHeadLock(locks);
704
- runGitResult(repoRoot, ["update-ref", expectedRef, expectedHeadSha, sha]);
705
- }
706
- if (locks)
707
- releaseCheckpointRepositoryLocks(locks);
708
- rmSync(baseline.tempDir, { recursive: true, force: true });
709
- }
710
- }
711
- function resolveRemoteBranch(repoRoot, params) {
712
- const expectedBranch = requiredString(params, "expectedBranch");
713
- const requestedBranch = optionalString(params, "branch");
714
- if (requestedBranch && requestedBranch !== expectedBranch) {
715
- throw new BindingSwitchGitError("invalid_git_request", "branch must match expectedBranch");
716
- }
717
- const currentBranch = runGit(repoRoot, ["branch", "--show-current"]);
718
- const branch = expectedBranch;
719
- assertValidBranch(branch);
720
- if (currentBranch !== expectedBranch) {
721
- throw new BindingSwitchGitError("source_changed_since_approval", `Source branch changed from ${expectedBranch} to ${currentBranch || "detached HEAD"}`);
722
- }
723
- const { remote } = verifiedRemote(repoRoot, params, true);
724
- return { branch, remote };
725
- }
726
- async function push(params) {
727
- const repoRoot = resolveAuthorizedRepository(params, true);
728
- const expectedHeadSha = requiredString(params, "expectedHeadSha");
729
- const expectedBranch = requiredString(params, "expectedBranch");
730
- const status = readStatus(repoRoot);
731
- assertSourcePreconditions(status, expectedHeadSha, undefined, expectedBranch);
732
- if (status.dirty) {
733
- throw new BindingSwitchGitError("source_dirty_after_checkpoint", "The source has uncommitted changes and cannot be published");
734
- }
735
- const { branch, remote } = resolveRemoteBranch(repoRoot, params);
736
- const latest = readStatus(repoRoot);
737
- assertSourcePreconditions(latest, expectedHeadSha, undefined, expectedBranch);
738
- if (latest.dirty) {
739
- throw new BindingSwitchGitError("source_dirty_after_checkpoint", "The source has uncommitted changes and cannot be published");
740
- }
741
- const before = await runNetworkGit(repoRoot, ["ls-remote", "--heads", remote, `refs/heads/${branch}`], "git_remote_unavailable");
742
- const remoteBefore = before.trim().split(/\s+/)[0] || null;
743
- await runNetworkGit(repoRoot, ["push", remote, `${expectedHeadSha}:refs/heads/${expectedBranch}`], "git_push_failed");
744
- const remoteHead = (await runNetworkGit(repoRoot, ["ls-remote", "--heads", remote, `refs/heads/${branch}`], "push_verification_failed"))
745
- .split(/\s+/)[0]
746
- ?.trim();
747
- if (remoteHead !== expectedHeadSha) {
748
- throw new BindingSwitchGitError("push_verification_failed", `Remote branch ${branch} is at ${remoteHead || "an unreadable SHA"}, expected ${expectedHeadSha}`);
749
- }
750
- const afterPush = readStatus(repoRoot);
751
- if (afterPush.headSha !== expectedHeadSha ||
752
- afterPush.branch !== expectedBranch ||
753
- afterPush.dirty ||
754
- afterPush.statusFingerprint !== latest.statusFingerprint) {
755
- throw new BindingSwitchGitError("source_changed_since_approval", "The source changed while the approved commit was being published", {
756
- published: {
757
- pushedSha: expectedHeadSha,
758
- branch,
759
- remote,
760
- replayed: remoteBefore === expectedHeadSha,
761
- },
762
- });
763
- }
764
- return {
765
- repoRoot,
766
- pushedSha: expectedHeadSha,
767
- branch,
768
- remote,
769
- replayed: remoteBefore === expectedHeadSha,
770
- };
771
- }
772
- async function fetchPublishedBranch(repoRoot, branch, expectedSha, params) {
773
- assertValidBranch(branch);
774
- assertCommitSha(expectedSha, "expectedSha");
775
- const { remote } = verifiedRemote(repoRoot, params, false);
776
- await runNetworkGit(repoRoot, ["fetch", "--no-tags", remote, `refs/heads/${branch}`], "git_fetch_failed");
777
- const fetchedSha = runGit(repoRoot, ["rev-parse", "FETCH_HEAD"], "target_sha_unavailable");
778
- if (fetchedSha !== expectedSha) {
779
- throw new BindingSwitchGitError("target_sha_mismatch", `Fetched ${branch} at ${fetchedSha}, expected the published SHA ${expectedSha}`);
780
- }
781
- const commit = runGitResult(repoRoot, ["cat-file", "-e", `${expectedSha}^{commit}`]);
782
- if (commit.status !== 0) {
783
- throw new BindingSwitchGitError("target_sha_unavailable", `The published SHA ${expectedSha} is not available in the destination repository`);
784
- }
785
- return { fetchedSha, remote };
786
- }
787
- async function fetchBranch(params) {
788
- const repoRoot = resolveAuthorizedRepository(params, true);
789
- const branch = requiredString(params, "branch");
790
- const expectedSha = requiredString(params, "expectedSha");
791
- const { fetchedSha, remote } = await fetchPublishedBranch(repoRoot, branch, expectedSha, params);
792
- return { repoRoot, fetchedSha, branch, remote };
793
- }
794
- function isAncestor(repoRoot, ancestor, descendant) {
795
- return runGitResult(repoRoot, ["merge-base", "--is-ancestor", ancestor, descendant]).status === 0;
796
- }
797
- function assertCleanTarget(repoRoot) {
798
- if (readStatus(repoRoot).dirty) {
799
- throw new BindingSwitchGitError("target_checkout_dirty", "The destination checkout has uncommitted changes");
800
- }
801
- }
802
- function assertBranchAvailableAtPath(repoRoot, branch, allowedPath) {
803
- const canonicalAllowedPath = canonicalPathWithMissingTail(allowedPath);
804
- const conflicting = listWorktrees(repoRoot).find((worktree) => worktree.branch === branch && worktree.path !== canonicalAllowedPath);
805
- if (conflicting) {
806
- throw new BindingSwitchGitError("target_branch_in_use", `Branch ${branch} is already checked out at ${conflicting.path}`);
807
- }
808
- }
809
- function advanceBranchAtCheckout(checkoutRoot, branch, targetSha) {
810
- const before = readStatus(checkoutRoot);
811
- if (before.branch === branch && before.headSha === targetSha)
812
- return true;
813
- assertBranchAvailableAtPath(checkoutRoot, branch, checkoutRoot);
814
- const branchExists = runGitResult(checkoutRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`])
815
- .status === 0;
816
- if (branchExists) {
817
- const branchHead = runGit(checkoutRoot, ["rev-parse", `refs/heads/${branch}`]);
818
- if (!isAncestor(checkoutRoot, branchHead, targetSha)) {
819
- throw new BindingSwitchGitError("target_branch_diverged", `Local branch ${branch} cannot fast-forward to the published SHA`);
820
- }
821
- if (before.branch !== branch) {
822
- runGit(checkoutRoot, ["switch", branch], "target_checkout_failed");
823
- }
824
- }
825
- else {
826
- runGit(checkoutRoot, ["switch", "-c", branch, targetSha], "target_checkout_failed");
827
- }
828
- const currentHead = runGit(checkoutRoot, ["rev-parse", "HEAD"]);
829
- if (currentHead !== targetSha) {
830
- if (!isAncestor(checkoutRoot, currentHead, targetSha)) {
831
- throw new BindingSwitchGitError("target_branch_diverged", `Local branch ${branch} cannot fast-forward to the published SHA`);
832
- }
833
- runGit(checkoutRoot, ["merge", "--ff-only", targetSha], "target_fast_forward_failed");
834
- }
835
- const verified = readStatus(checkoutRoot);
836
- if (verified.branch !== branch || verified.headSha !== targetSha) {
837
- throw new BindingSwitchGitError("target_verification_failed", "The destination checkout did not land on the published branch and SHA");
838
- }
839
- return false;
840
- }
841
- async function prepareCheckout(params) {
842
- const repoRoot = resolveAuthorizedRepository(params, true);
843
- const branch = requiredString(params, "branch");
844
- const targetSha = requiredString(params, "targetSha");
845
- await fetchPublishedBranch(repoRoot, branch, targetSha, params);
846
- assertCleanTarget(repoRoot);
847
- const replayed = advanceBranchAtCheckout(repoRoot, branch, targetSha);
848
- return {
849
- repoRoot,
850
- path: repoRoot,
851
- branch,
852
- headSha: targetSha,
853
- replayed,
854
- };
855
- }
856
- async function prepareWorktree(params) {
857
- const repoRoot = resolveAuthorizedRepository(params, true);
858
- const registrations = listWorktrees(repoRoot);
859
- const projectRoot = registrations[0]?.path;
860
- if (!projectRoot || projectRoot !== repoRoot) {
861
- throw new BindingSwitchGitError("project_checkout_required", "Worktree preparation requires the Project's main checkout");
862
- }
863
- const worktreePath = resolve(requiredString(params, "worktreePath"));
864
- const managedRoot = alanWorktreeRoot(projectRoot);
865
- const pathError = "The destination worktree path is outside the Alan-managed worktree directory";
866
- const intendedManagedRoot = join(realpathSync.native(dirname(managedRoot)), basename(managedRoot));
867
- if (canonicalPathWithMissingTail(managedRoot) !== intendedManagedRoot) {
868
- throw new BindingSwitchGitError("worktree_path_not_authorized", pathError);
869
- }
870
- assertCanonicalPathContained(worktreePath, managedRoot, "worktree_path_not_authorized", pathError);
871
- const canonicalWorktreePath = canonicalPathWithMissingTail(worktreePath);
872
- if (canonicalWorktreePath === canonicalPathWithMissingTail(managedRoot)) {
873
- throw new BindingSwitchGitError("worktree_path_not_authorized", pathError);
874
- }
875
- const branch = requiredString(params, "branch");
876
- const targetSha = requiredString(params, "targetSha");
877
- await fetchPublishedBranch(repoRoot, branch, targetSha, params);
878
- const refreshed = listWorktrees(repoRoot);
879
- const registeredAtPath = refreshed.find(({ path }) => path === canonicalWorktreePath);
880
- const registeredForBranch = refreshed.find(({ branch: value }) => value === branch);
881
- if (registeredAtPath) {
882
- if (registeredAtPath.branch !== branch) {
883
- throw new BindingSwitchGitError("worktree_path_conflict", "The destination path is registered to a different branch");
884
- }
885
- assertCleanTarget(worktreePath);
886
- const replayed = advanceBranchAtCheckout(worktreePath, branch, targetSha);
887
- return {
888
- repoRoot: worktreePath,
889
- path: worktreePath,
890
- branch,
891
- headSha: targetSha,
892
- replayed,
893
- };
894
- }
895
- if (registeredForBranch) {
896
- throw new BindingSwitchGitError("target_branch_in_use", `Branch ${branch} is already checked out at ${registeredForBranch.path}`);
897
- }
898
- if (existsSync(worktreePath)) {
899
- throw new BindingSwitchGitError("worktree_path_conflict", "The destination worktree path already exists and is not registered");
900
- }
901
- const branchExists = runGitResult(repoRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]).status ===
902
- 0;
903
- if (branchExists) {
904
- const branchHead = runGit(repoRoot, ["rev-parse", `refs/heads/${branch}`]);
905
- if (!isAncestor(repoRoot, branchHead, targetSha)) {
906
- throw new BindingSwitchGitError("target_branch_diverged", `Local branch ${branch} cannot fast-forward to the published SHA`);
907
- }
908
- }
909
- mkdirSync(dirname(worktreePath), { recursive: true });
910
- if (branchExists) {
911
- runGit(repoRoot, ["worktree", "add", worktreePath, branch], "worktree_prepare_failed");
912
- const currentHead = runGit(worktreePath, ["rev-parse", "HEAD"]);
913
- if (currentHead !== targetSha) {
914
- runGit(worktreePath, ["merge", "--ff-only", targetSha], "target_fast_forward_failed");
915
- }
916
- }
917
- else {
918
- runGit(repoRoot, ["worktree", "add", "-b", branch, worktreePath, targetSha], "worktree_prepare_failed");
919
- }
920
- const createdRoot = resolveRepositoryRoot(worktreePath);
921
- assertCanonicalPathContained(createdRoot, managedRoot, "worktree_path_not_authorized", pathError);
922
- if (createdRoot !== realpathSync.native(worktreePath)) {
923
- throw new BindingSwitchGitError("worktree_path_changed", "The destination worktree resolved to an unexpected path after creation");
924
- }
925
- const verified = readStatus(createdRoot);
926
- if (verified.branch !== branch || verified.headSha !== targetSha) {
927
- throw new BindingSwitchGitError("target_verification_failed", "The destination worktree did not land on the published branch and SHA");
928
- }
929
- return {
930
- repoRoot: worktreePath,
931
- path: worktreePath,
932
- branch,
933
- headSha: targetSha,
934
- replayed: false,
935
- };
936
- }
937
- function verifyHead(params) {
938
- const repoRoot = resolveAuthorizedRepository(params, true);
939
- const expectedSha = requiredString(params, "expectedSha");
940
- assertCommitSha(expectedSha, "expectedSha");
941
- assertCleanTarget(repoRoot);
942
- const headSha = runGit(repoRoot, ["rev-parse", "HEAD"], "target_head_unreadable");
943
- if (headSha !== expectedSha) {
944
- throw new BindingSwitchGitError("verify_head_mismatch", `Destination HEAD is ${headSha}, expected ${expectedSha}`);
945
- }
946
- return { repoRoot, headSha, verified: true };
947
- }
948
- export function statusBindingSwitchGit(params) {
949
- const repoRoot = resolveAuthorizedRepository(params);
950
- return {
951
- ...readStatus(repoRoot),
952
- remoteUrl: inspectedRemoteUrl(repoRoot, params),
953
- };
954
- }
955
- export async function executeBindingSwitchGitRpc(method, params) {
956
- switch (method) {
957
- case "git.status":
958
- return statusBindingSwitchGit(params);
959
- case "git.checkpoint":
960
- return checkpoint(params);
961
- case "git.push":
962
- return push(params);
963
- case "git.fetch":
964
- return fetchBranch(params);
965
- case "git.prepare_checkout":
966
- return prepareCheckout(params);
967
- case "git.prepare_worktree":
968
- return prepareWorktree(params);
969
- case "git.verify_head":
970
- return verifyHead(params);
971
- }
972
- }
973
- //# sourceMappingURL=binding-switch-git.js.map