@aiden-ade/sandbox-agent 0.1.58 → 0.1.60

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/__tests__/binding-switch-git.test.d.ts +2 -0
  2. package/dist/__tests__/binding-switch-git.test.d.ts.map +1 -0
  3. package/dist/__tests__/binding-switch-git.test.js +610 -0
  4. package/dist/__tests__/binding-switch-git.test.js.map +1 -0
  5. package/dist/__tests__/cli-ensure-install.test.d.ts +2 -0
  6. package/dist/__tests__/cli-ensure-install.test.d.ts.map +1 -0
  7. package/dist/__tests__/cli-ensure-install.test.js +109 -0
  8. package/dist/__tests__/cli-ensure-install.test.js.map +1 -0
  9. package/dist/__tests__/cli-executable.test.d.ts +2 -0
  10. package/dist/__tests__/cli-executable.test.d.ts.map +1 -0
  11. package/dist/__tests__/cli-executable.test.js +129 -0
  12. package/dist/__tests__/cli-executable.test.js.map +1 -0
  13. package/dist/__tests__/cli-help.test.d.ts +2 -0
  14. package/dist/__tests__/cli-help.test.d.ts.map +1 -0
  15. package/dist/__tests__/cli-help.test.js +34 -0
  16. package/dist/__tests__/cli-help.test.js.map +1 -0
  17. package/dist/__tests__/computer-readiness.test.d.ts +2 -0
  18. package/dist/__tests__/computer-readiness.test.d.ts.map +1 -0
  19. package/dist/__tests__/computer-readiness.test.js +63 -0
  20. package/dist/__tests__/computer-readiness.test.js.map +1 -0
  21. package/dist/__tests__/core-agent-workflow-context.test.d.ts +2 -0
  22. package/dist/__tests__/core-agent-workflow-context.test.d.ts.map +1 -0
  23. package/dist/__tests__/core-agent-workflow-context.test.js +139 -0
  24. package/dist/__tests__/core-agent-workflow-context.test.js.map +1 -0
  25. package/dist/__tests__/daemon-reconcile-ask.test.d.ts +2 -0
  26. package/dist/__tests__/daemon-reconcile-ask.test.d.ts.map +1 -0
  27. package/dist/__tests__/daemon-reconcile-ask.test.js +86 -0
  28. package/dist/__tests__/daemon-reconcile-ask.test.js.map +1 -0
  29. package/dist/__tests__/daemon-socket.integration.test.d.ts +2 -0
  30. package/dist/__tests__/daemon-socket.integration.test.d.ts.map +1 -0
  31. package/dist/__tests__/daemon-socket.integration.test.js +450 -0
  32. package/dist/__tests__/daemon-socket.integration.test.js.map +1 -0
  33. package/dist/__tests__/daemon.test.d.ts +2 -0
  34. package/dist/__tests__/daemon.test.d.ts.map +1 -0
  35. package/dist/__tests__/daemon.test.js +3975 -0
  36. package/dist/__tests__/daemon.test.js.map +1 -0
  37. package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts +2 -0
  38. package/dist/__tests__/ensure-shared-checkout-branch.test.d.ts.map +1 -0
  39. package/dist/__tests__/ensure-shared-checkout-branch.test.js +129 -0
  40. package/dist/__tests__/ensure-shared-checkout-branch.test.js.map +1 -0
  41. package/dist/__tests__/event-outbox-persist-failure.test.d.ts +2 -0
  42. package/dist/__tests__/event-outbox-persist-failure.test.d.ts.map +1 -0
  43. package/dist/__tests__/event-outbox-persist-failure.test.js +84 -0
  44. package/dist/__tests__/event-outbox-persist-failure.test.js.map +1 -0
  45. package/dist/__tests__/event-outbox.test.d.ts +2 -0
  46. package/dist/__tests__/event-outbox.test.d.ts.map +1 -0
  47. package/dist/__tests__/event-outbox.test.js +256 -0
  48. package/dist/__tests__/event-outbox.test.js.map +1 -0
  49. package/dist/__tests__/fake-provider-cli.test.d.ts +2 -0
  50. package/dist/__tests__/fake-provider-cli.test.d.ts.map +1 -0
  51. package/dist/__tests__/fake-provider-cli.test.js +46 -0
  52. package/dist/__tests__/fake-provider-cli.test.js.map +1 -0
  53. package/dist/__tests__/generated-images.test.d.ts +2 -0
  54. package/dist/__tests__/generated-images.test.d.ts.map +1 -0
  55. package/dist/__tests__/generated-images.test.js +132 -0
  56. package/dist/__tests__/generated-images.test.js.map +1 -0
  57. package/dist/__tests__/lifecycle-logger.test.d.ts +2 -0
  58. package/dist/__tests__/lifecycle-logger.test.d.ts.map +1 -0
  59. package/dist/__tests__/lifecycle-logger.test.js +49 -0
  60. package/dist/__tests__/lifecycle-logger.test.js.map +1 -0
  61. package/dist/__tests__/local-skill-scan.test.d.ts +2 -0
  62. package/dist/__tests__/local-skill-scan.test.d.ts.map +1 -0
  63. package/dist/__tests__/local-skill-scan.test.js +91 -0
  64. package/dist/__tests__/local-skill-scan.test.js.map +1 -0
  65. package/dist/__tests__/local-workspace-migration.test.d.ts +2 -0
  66. package/dist/__tests__/local-workspace-migration.test.d.ts.map +1 -0
  67. package/dist/__tests__/local-workspace-migration.test.js +195 -0
  68. package/dist/__tests__/local-workspace-migration.test.js.map +1 -0
  69. package/dist/__tests__/mcp-inspect-timeout.test.d.ts +2 -0
  70. package/dist/__tests__/mcp-inspect-timeout.test.d.ts.map +1 -0
  71. package/dist/__tests__/mcp-inspect-timeout.test.js +97 -0
  72. package/dist/__tests__/mcp-inspect-timeout.test.js.map +1 -0
  73. package/dist/__tests__/mcp-readiness.test.d.ts +2 -0
  74. package/dist/__tests__/mcp-readiness.test.d.ts.map +1 -0
  75. package/dist/__tests__/mcp-readiness.test.js +267 -0
  76. package/dist/__tests__/mcp-readiness.test.js.map +1 -0
  77. package/dist/__tests__/mcp-registration-cli.test.d.ts +2 -0
  78. package/dist/__tests__/mcp-registration-cli.test.d.ts.map +1 -0
  79. package/dist/__tests__/mcp-registration-cli.test.js +71 -0
  80. package/dist/__tests__/mcp-registration-cli.test.js.map +1 -0
  81. package/dist/__tests__/mcp-registration.test.d.ts +2 -0
  82. package/dist/__tests__/mcp-registration.test.d.ts.map +1 -0
  83. package/dist/__tests__/mcp-registration.test.js +426 -0
  84. package/dist/__tests__/mcp-registration.test.js.map +1 -0
  85. package/dist/__tests__/model-scan-runner.test.d.ts +2 -0
  86. package/dist/__tests__/model-scan-runner.test.d.ts.map +1 -0
  87. package/dist/__tests__/model-scan-runner.test.js +67 -0
  88. package/dist/__tests__/model-scan-runner.test.js.map +1 -0
  89. package/dist/__tests__/network-diagnostics.test.d.ts +2 -0
  90. package/dist/__tests__/network-diagnostics.test.d.ts.map +1 -0
  91. package/dist/__tests__/network-diagnostics.test.js +26 -0
  92. package/dist/__tests__/network-diagnostics.test.js.map +1 -0
  93. package/dist/__tests__/observable-map.test.d.ts +2 -0
  94. package/dist/__tests__/observable-map.test.d.ts.map +1 -0
  95. package/dist/__tests__/observable-map.test.js +54 -0
  96. package/dist/__tests__/observable-map.test.js.map +1 -0
  97. package/dist/__tests__/provider-auth-preflight.test.d.ts +2 -0
  98. package/dist/__tests__/provider-auth-preflight.test.d.ts.map +1 -0
  99. package/dist/__tests__/provider-auth-preflight.test.js +123 -0
  100. package/dist/__tests__/provider-auth-preflight.test.js.map +1 -0
  101. package/dist/__tests__/provider-model-discovery.test.d.ts +2 -0
  102. package/dist/__tests__/provider-model-discovery.test.d.ts.map +1 -0
  103. package/dist/__tests__/provider-model-discovery.test.js +472 -0
  104. package/dist/__tests__/provider-model-discovery.test.js.map +1 -0
  105. package/dist/__tests__/remove-worktrees-if-clean.test.d.ts +2 -0
  106. package/dist/__tests__/remove-worktrees-if-clean.test.d.ts.map +1 -0
  107. package/dist/__tests__/remove-worktrees-if-clean.test.js +221 -0
  108. package/dist/__tests__/remove-worktrees-if-clean.test.js.map +1 -0
  109. package/dist/__tests__/resume-fallback-env.test.d.ts +2 -0
  110. package/dist/__tests__/resume-fallback-env.test.d.ts.map +1 -0
  111. package/dist/__tests__/resume-fallback-env.test.js +28 -0
  112. package/dist/__tests__/resume-fallback-env.test.js.map +1 -0
  113. package/dist/__tests__/run-journal.test.d.ts +2 -0
  114. package/dist/__tests__/run-journal.test.d.ts.map +1 -0
  115. package/dist/__tests__/run-journal.test.js +121 -0
  116. package/dist/__tests__/run-journal.test.js.map +1 -0
  117. package/dist/__tests__/run-start-gate.test.d.ts +2 -0
  118. package/dist/__tests__/run-start-gate.test.d.ts.map +1 -0
  119. package/dist/__tests__/run-start-gate.test.js +29 -0
  120. package/dist/__tests__/run-start-gate.test.js.map +1 -0
  121. package/dist/__tests__/runtime-capabilities-sync.test.d.ts +2 -0
  122. package/dist/__tests__/runtime-capabilities-sync.test.d.ts.map +1 -0
  123. package/dist/__tests__/runtime-capabilities-sync.test.js +66 -0
  124. package/dist/__tests__/runtime-capabilities-sync.test.js.map +1 -0
  125. package/dist/__tests__/runtime-config.test.d.ts +2 -0
  126. package/dist/__tests__/runtime-config.test.d.ts.map +1 -0
  127. package/dist/__tests__/runtime-config.test.js +102 -0
  128. package/dist/__tests__/runtime-config.test.js.map +1 -0
  129. package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts +2 -0
  130. package/dist/__tests__/runtime-presenter-heartbeat.test.d.ts.map +1 -0
  131. package/dist/__tests__/runtime-presenter-heartbeat.test.js +159 -0
  132. package/dist/__tests__/runtime-presenter-heartbeat.test.js.map +1 -0
  133. package/dist/__tests__/sandbox-outbox.test.d.ts +2 -0
  134. package/dist/__tests__/sandbox-outbox.test.d.ts.map +1 -0
  135. package/dist/__tests__/sandbox-outbox.test.js +214 -0
  136. package/dist/__tests__/sandbox-outbox.test.js.map +1 -0
  137. package/dist/__tests__/sandbox-workflow-context.test.d.ts +2 -0
  138. package/dist/__tests__/sandbox-workflow-context.test.d.ts.map +1 -0
  139. package/dist/__tests__/sandbox-workflow-context.test.js +396 -0
  140. package/dist/__tests__/sandbox-workflow-context.test.js.map +1 -0
  141. package/dist/__tests__/service-manager.test.d.ts +2 -0
  142. package/dist/__tests__/service-manager.test.d.ts.map +1 -0
  143. package/dist/__tests__/service-manager.test.js +50 -0
  144. package/dist/__tests__/service-manager.test.js.map +1 -0
  145. package/dist/__tests__/sleep-inhibitor.test.d.ts +2 -0
  146. package/dist/__tests__/sleep-inhibitor.test.d.ts.map +1 -0
  147. package/dist/__tests__/sleep-inhibitor.test.js +236 -0
  148. package/dist/__tests__/sleep-inhibitor.test.js.map +1 -0
  149. package/dist/__tests__/status-local-workspace.test.d.ts +2 -0
  150. package/dist/__tests__/status-local-workspace.test.d.ts.map +1 -0
  151. package/dist/__tests__/status-local-workspace.test.js +121 -0
  152. package/dist/__tests__/status-local-workspace.test.js.map +1 -0
  153. package/dist/__tests__/suspension-detector.test.d.ts +2 -0
  154. package/dist/__tests__/suspension-detector.test.d.ts.map +1 -0
  155. package/dist/__tests__/suspension-detector.test.js +112 -0
  156. package/dist/__tests__/suspension-detector.test.js.map +1 -0
  157. package/dist/__tests__/web-presenter-parent-events.test.d.ts +2 -0
  158. package/dist/__tests__/web-presenter-parent-events.test.d.ts.map +1 -0
  159. package/dist/__tests__/web-presenter-parent-events.test.js +130 -0
  160. package/dist/__tests__/web-presenter-parent-events.test.js.map +1 -0
  161. package/dist/__tests__/workspace-relocation.test.d.ts +2 -0
  162. package/dist/__tests__/workspace-relocation.test.d.ts.map +1 -0
  163. package/dist/__tests__/workspace-relocation.test.js +66 -0
  164. package/dist/__tests__/workspace-relocation.test.js.map +1 -0
  165. package/dist/__tests__/workspace-run-lease.test.d.ts +2 -0
  166. package/dist/__tests__/workspace-run-lease.test.d.ts.map +1 -0
  167. package/dist/__tests__/workspace-run-lease.test.js +230 -0
  168. package/dist/__tests__/workspace-run-lease.test.js.map +1 -0
  169. package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts +2 -0
  170. package/dist/__tests__/ws-client-heartbeat-volatile.test.d.ts.map +1 -0
  171. package/dist/__tests__/ws-client-heartbeat-volatile.test.js +61 -0
  172. package/dist/__tests__/ws-client-heartbeat-volatile.test.js.map +1 -0
  173. package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts +2 -0
  174. package/dist/__tests__/ws-client-heartbeat.integration.test.d.ts.map +1 -0
  175. package/dist/__tests__/ws-client-heartbeat.integration.test.js +109 -0
  176. package/dist/__tests__/ws-client-heartbeat.integration.test.js.map +1 -0
  177. package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts +2 -0
  178. package/dist/__tests__/ws-client-user-message-ack.integration.test.d.ts.map +1 -0
  179. package/dist/__tests__/ws-client-user-message-ack.integration.test.js +167 -0
  180. package/dist/__tests__/ws-client-user-message-ack.integration.test.js.map +1 -0
  181. package/dist/aiden-system-prompt.d.ts +6 -0
  182. package/dist/aiden-system-prompt.d.ts.map +1 -0
  183. package/dist/aiden-system-prompt.js +6 -0
  184. package/dist/aiden-system-prompt.js.map +1 -0
  185. package/dist/binding-switch-git.d.ts +26 -0
  186. package/dist/binding-switch-git.d.ts.map +1 -0
  187. package/dist/binding-switch-git.js +973 -0
  188. package/dist/binding-switch-git.js.map +1 -0
  189. package/dist/cli-ensure-install.d.ts +39 -0
  190. package/dist/cli-ensure-install.d.ts.map +1 -0
  191. package/dist/cli-ensure-install.js +133 -0
  192. package/dist/cli-ensure-install.js.map +1 -0
  193. package/dist/cli-executable.d.ts +97 -0
  194. package/dist/cli-executable.d.ts.map +1 -0
  195. package/dist/cli-executable.js +432 -0
  196. package/dist/cli-executable.js.map +1 -0
  197. package/dist/cli-help.d.ts +7 -0
  198. package/dist/cli-help.d.ts.map +1 -0
  199. package/dist/cli-help.js +49 -0
  200. package/dist/cli-help.js.map +1 -0
  201. package/dist/computer-readiness.d.ts +42 -0
  202. package/dist/computer-readiness.d.ts.map +1 -0
  203. package/dist/computer-readiness.js +191 -0
  204. package/dist/computer-readiness.js.map +1 -0
  205. package/dist/core-agent.d.ts +47 -0
  206. package/dist/core-agent.d.ts.map +1 -0
  207. package/dist/core-agent.js +364 -0
  208. package/dist/core-agent.js.map +1 -0
  209. package/dist/daemon.d.ts +392 -0
  210. package/dist/daemon.d.ts.map +1 -0
  211. package/dist/daemon.js +4711 -0
  212. package/dist/daemon.js.map +1 -0
  213. package/dist/event-outbox.d.ts +83 -0
  214. package/dist/event-outbox.d.ts.map +1 -0
  215. package/dist/event-outbox.js +440 -0
  216. package/dist/event-outbox.js.map +1 -0
  217. package/dist/fake-provider-cli.d.ts +13 -0
  218. package/dist/fake-provider-cli.d.ts.map +1 -0
  219. package/dist/fake-provider-cli.js +53 -0
  220. package/dist/fake-provider-cli.js.map +1 -0
  221. package/dist/generated-images.d.ts +2 -0
  222. package/dist/generated-images.d.ts.map +1 -0
  223. package/dist/generated-images.js +2 -0
  224. package/dist/generated-images.js.map +1 -0
  225. package/dist/index.cjs +179 -52
  226. package/dist/index.d.ts +6 -0
  227. package/dist/index.d.ts.map +1 -0
  228. package/dist/index.js +209 -0
  229. package/dist/index.js.map +1 -0
  230. package/dist/lifecycle-logger.d.ts +25 -0
  231. package/dist/lifecycle-logger.d.ts.map +1 -0
  232. package/dist/lifecycle-logger.js +73 -0
  233. package/dist/lifecycle-logger.js.map +1 -0
  234. package/dist/local-skill-scan.d.ts +21 -0
  235. package/dist/local-skill-scan.d.ts.map +1 -0
  236. package/dist/local-skill-scan.js +109 -0
  237. package/dist/local-skill-scan.js.map +1 -0
  238. package/dist/local-workspace-migration.d.ts +68 -0
  239. package/dist/local-workspace-migration.d.ts.map +1 -0
  240. package/dist/local-workspace-migration.js +157 -0
  241. package/dist/local-workspace-migration.js.map +1 -0
  242. package/dist/mcp-readiness.d.ts +59 -0
  243. package/dist/mcp-readiness.d.ts.map +1 -0
  244. package/dist/mcp-readiness.js +202 -0
  245. package/dist/mcp-readiness.js.map +1 -0
  246. package/dist/mcp-registration.d.ts +75 -0
  247. package/dist/mcp-registration.d.ts.map +1 -0
  248. package/dist/mcp-registration.js +563 -0
  249. package/dist/mcp-registration.js.map +1 -0
  250. package/dist/model-scan-runner.d.ts +15 -0
  251. package/dist/model-scan-runner.d.ts.map +1 -0
  252. package/dist/model-scan-runner.js +51 -0
  253. package/dist/model-scan-runner.js.map +1 -0
  254. package/dist/network-diagnostics.d.ts +8 -0
  255. package/dist/network-diagnostics.d.ts.map +1 -0
  256. package/dist/network-diagnostics.js +56 -0
  257. package/dist/network-diagnostics.js.map +1 -0
  258. package/dist/observable-map.d.ts +25 -0
  259. package/dist/observable-map.d.ts.map +1 -0
  260. package/dist/observable-map.js +42 -0
  261. package/dist/observable-map.js.map +1 -0
  262. package/dist/provider-auth-preflight.d.ts +22 -0
  263. package/dist/provider-auth-preflight.d.ts.map +1 -0
  264. package/dist/provider-auth-preflight.js +110 -0
  265. package/dist/provider-auth-preflight.js.map +1 -0
  266. package/dist/provider-model-discovery.d.ts +38 -0
  267. package/dist/provider-model-discovery.d.ts.map +1 -0
  268. package/dist/provider-model-discovery.js +519 -0
  269. package/dist/provider-model-discovery.js.map +1 -0
  270. package/dist/resume-fallback-env.d.ts +20 -0
  271. package/dist/resume-fallback-env.d.ts.map +1 -0
  272. package/dist/resume-fallback-env.js +30 -0
  273. package/dist/resume-fallback-env.js.map +1 -0
  274. package/dist/run-journal.d.ts +53 -0
  275. package/dist/run-journal.d.ts.map +1 -0
  276. package/dist/run-journal.js +193 -0
  277. package/dist/run-journal.js.map +1 -0
  278. package/dist/run-start-gate.d.ts +9 -0
  279. package/dist/run-start-gate.d.ts.map +1 -0
  280. package/dist/run-start-gate.js +24 -0
  281. package/dist/run-start-gate.js.map +1 -0
  282. package/dist/runtime-backends.d.ts +2 -0
  283. package/dist/runtime-backends.d.ts.map +1 -0
  284. package/dist/runtime-backends.js +2 -0
  285. package/dist/runtime-backends.js.map +1 -0
  286. package/dist/runtime-capabilities-sync.d.ts +33 -0
  287. package/dist/runtime-capabilities-sync.d.ts.map +1 -0
  288. package/dist/runtime-capabilities-sync.js +56 -0
  289. package/dist/runtime-capabilities-sync.js.map +1 -0
  290. package/dist/runtime-config.d.ts +24 -0
  291. package/dist/runtime-config.d.ts.map +1 -0
  292. package/dist/runtime-config.js +124 -0
  293. package/dist/runtime-config.js.map +1 -0
  294. package/dist/sandbox-outbox.d.ts +122 -0
  295. package/dist/sandbox-outbox.d.ts.map +1 -0
  296. package/dist/sandbox-outbox.js +180 -0
  297. package/dist/sandbox-outbox.js.map +1 -0
  298. package/dist/sandbox.d.ts +44 -0
  299. package/dist/sandbox.d.ts.map +1 -0
  300. package/dist/sandbox.js +438 -0
  301. package/dist/sandbox.js.map +1 -0
  302. package/dist/service-manager.d.ts +28 -0
  303. package/dist/service-manager.d.ts.map +1 -0
  304. package/dist/service-manager.js +259 -0
  305. package/dist/service-manager.js.map +1 -0
  306. package/dist/sleep-inhibitor.d.ts +111 -0
  307. package/dist/sleep-inhibitor.d.ts.map +1 -0
  308. package/dist/sleep-inhibitor.js +412 -0
  309. package/dist/sleep-inhibitor.js.map +1 -0
  310. package/dist/suspension-detector.d.ts +48 -0
  311. package/dist/suspension-detector.d.ts.map +1 -0
  312. package/dist/suspension-detector.js +89 -0
  313. package/dist/suspension-detector.js.map +1 -0
  314. package/dist/types.d.ts +6 -0
  315. package/dist/types.d.ts.map +1 -0
  316. package/dist/types.js +6 -0
  317. package/dist/types.js.map +1 -0
  318. package/dist/updater.d.ts +15 -0
  319. package/dist/updater.d.ts.map +1 -0
  320. package/dist/updater.js +50 -0
  321. package/dist/updater.js.map +1 -0
  322. package/dist/version.d.ts +2 -0
  323. package/dist/version.d.ts.map +1 -0
  324. package/dist/version.js +2 -0
  325. package/dist/version.js.map +1 -0
  326. package/dist/web-presenter.d.ts +55 -0
  327. package/dist/web-presenter.d.ts.map +1 -0
  328. package/dist/web-presenter.js +265 -0
  329. package/dist/web-presenter.js.map +1 -0
  330. package/dist/workspace-relocation.d.ts +22 -0
  331. package/dist/workspace-relocation.d.ts.map +1 -0
  332. package/dist/workspace-relocation.js +90 -0
  333. package/dist/workspace-relocation.js.map +1 -0
  334. package/dist/workspace-run-lease.d.ts +97 -0
  335. package/dist/workspace-run-lease.d.ts.map +1 -0
  336. package/dist/workspace-run-lease.js +241 -0
  337. package/dist/workspace-run-lease.js.map +1 -0
  338. package/dist/ws-client.d.ts +194 -0
  339. package/dist/ws-client.d.ts.map +1 -0
  340. package/dist/ws-client.js +282 -0
  341. package/dist/ws-client.js.map +1 -0
  342. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=binding-switch-git.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-switch-git.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/binding-switch-git.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,610 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync, } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { basename, dirname, join } from "node:path";
5
+ import { afterEach, describe, expect, it, vi } from "vitest";
6
+ import { executeBindingSwitchGitRpc } from "../binding-switch-git.js";
7
+ const tempDirs = [];
8
+ vi.setConfig({ testTimeout: 20_000 });
9
+ function git(cwd, ...args) {
10
+ return execFileSync("git", args, { cwd, encoding: "utf8" }).trim();
11
+ }
12
+ function makeTempDir() {
13
+ const path = mkdtempSync(join(tmpdir(), "alan-binding-switch-git-"));
14
+ tempDirs.push(path);
15
+ return path;
16
+ }
17
+ function configureIdentity(repoPath) {
18
+ git(repoPath, "config", "user.name", "Alan Test");
19
+ git(repoPath, "config", "user.email", "alan-test@example.com");
20
+ }
21
+ function createRepository(root, name) {
22
+ const repoPath = join(root, name);
23
+ mkdirSync(repoPath);
24
+ git(repoPath, "init", "-b", "main");
25
+ configureIdentity(repoPath);
26
+ writeFileSync(join(repoPath, "tracked.txt"), "initial\n");
27
+ git(repoPath, "add", "tracked.txt");
28
+ git(repoPath, "commit", "-m", "Initial commit");
29
+ return repoPath;
30
+ }
31
+ function createRemoteFixture(root) {
32
+ const remotePath = join(root, "remote.git");
33
+ mkdirSync(remotePath);
34
+ git(remotePath, "init", "--bare");
35
+ git(remotePath, "symbolic-ref", "HEAD", "refs/heads/main");
36
+ const seedPath = createRepository(root, "seed");
37
+ git(seedPath, "remote", "add", "origin", remotePath);
38
+ git(seedPath, "push", "-u", "origin", "main");
39
+ const mainSha = git(seedPath, "rev-parse", "HEAD");
40
+ const sourcePath = join(root, "source");
41
+ const targetPath = join(root, "target");
42
+ git(root, "clone", remotePath, sourcePath);
43
+ git(root, "clone", remotePath, targetPath);
44
+ configureIdentity(sourcePath);
45
+ configureIdentity(targetPath);
46
+ return { remotePath, sourcePath, targetPath, mainSha };
47
+ }
48
+ function common(repoPath, suffix = "one") {
49
+ let expectedRemoteUrl;
50
+ try {
51
+ expectedRemoteUrl = git(repoPath, "config", "--get", "remote.origin.url");
52
+ }
53
+ catch {
54
+ expectedRemoteUrl = undefined;
55
+ }
56
+ return {
57
+ switchId: `switch-${suffix}`,
58
+ repoKey: `repo-${suffix}`,
59
+ repoPath,
60
+ projectPath: repoPath,
61
+ expectedRepoRoot: repoPath,
62
+ expectedRemoteUrl,
63
+ expectedBranch: git(repoPath, "branch", "--show-current"),
64
+ };
65
+ }
66
+ async function waitForPath(path, timeoutMs = 5_000) {
67
+ const deadline = Date.now() + timeoutMs;
68
+ while (!existsSync(path)) {
69
+ if (Date.now() >= deadline)
70
+ throw new Error(`Timed out waiting for ${path}`);
71
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 20));
72
+ }
73
+ }
74
+ afterEach(() => {
75
+ for (const path of tempDirs.splice(0))
76
+ rmSync(path, { recursive: true, force: true });
77
+ });
78
+ describe("binding-switch git RPC", () => {
79
+ it("reports and verifies the repository fetch remote during status inspection", async () => {
80
+ const root = makeTempDir();
81
+ const { remotePath, sourcePath } = createRemoteFixture(root);
82
+ await expect(executeBindingSwitchGitRpc("git.status", common(sourcePath))).resolves.toMatchObject({
83
+ repoRoot: sourcePath,
84
+ remoteUrl: remotePath,
85
+ branch: "main",
86
+ dirty: false,
87
+ });
88
+ });
89
+ it("rejects a mismatched expected remote during status inspection", async () => {
90
+ const root = makeTempDir();
91
+ const { sourcePath } = createRemoteFixture(root);
92
+ const otherRemote = join(root, "other.git");
93
+ mkdirSync(otherRemote);
94
+ git(otherRemote, "init", "--bare");
95
+ await expect(executeBindingSwitchGitRpc("git.status", {
96
+ ...common(sourcePath, "status-remote-mismatch"),
97
+ expectedRemoteUrl: otherRemote,
98
+ })).rejects.toMatchObject({ code: "remote_url_mismatch" });
99
+ });
100
+ it("reports a missing remote without weakening repository path authorization", async () => {
101
+ const root = makeTempDir();
102
+ const repoPath = createRepository(root, "repo-without-remote");
103
+ const { expectedRemoteUrl: _expectedRemoteUrl, ...request } = common(repoPath, "status-no-remote");
104
+ await expect(executeBindingSwitchGitRpc("git.status", request)).resolves.toMatchObject({
105
+ repoRoot: repoPath,
106
+ remoteUrl: null,
107
+ });
108
+ });
109
+ it("rejects credential-bearing remotes during status inspection", async () => {
110
+ const root = makeTempDir();
111
+ const repoPath = createRepository(root, "repo-with-credential-remote");
112
+ git(repoPath, "remote", "add", "origin", "https://user:secret@example.invalid/owner/repo.git");
113
+ const { expectedRemoteUrl: _expectedRemoteUrl, ...request } = common(repoPath, "status-credential-remote");
114
+ await expect(executeBindingSwitchGitRpc("git.status", request)).rejects.toMatchObject({
115
+ code: "remote_url_credentials_rejected",
116
+ });
117
+ });
118
+ it("creates one guarded checkpoint and replays it by switch/repository trailers", async () => {
119
+ const root = makeTempDir();
120
+ const repoPath = createRepository(root, "repo");
121
+ const originalHead = git(repoPath, "rev-parse", "HEAD");
122
+ writeFileSync(join(repoPath, "tracked.txt"), "checkpointed\n");
123
+ writeFileSync(join(repoPath, "untracked.txt"), "new\n");
124
+ const status = (await executeBindingSwitchGitRpc("git.status", common(repoPath)));
125
+ expect(status).toMatchObject({
126
+ headSha: originalHead,
127
+ dirty: true,
128
+ filesChanged: 2,
129
+ });
130
+ expect(status.statusFingerprint).toMatch(/^[0-9a-f]{64}$/);
131
+ const request = {
132
+ ...common(repoPath),
133
+ expectedHeadSha: originalHead,
134
+ expectedStatusFingerprint: status.statusFingerprint,
135
+ expectedProposedTreeSha: status.proposedTreeSha,
136
+ commitMessage: "chore: checkpoint work before environment switch\n\nCo-Authored-By: Alan <agent@tryalan.ai>",
137
+ authorName: "Siddharth Talesara",
138
+ authorEmail: "siddharth@example.com",
139
+ };
140
+ const first = (await executeBindingSwitchGitRpc("git.checkpoint", request));
141
+ expect(first.replayed).toBe(false);
142
+ expect(first.sha).not.toBe(originalHead);
143
+ expect(git(repoPath, "show", "-s", "--format=%B", "HEAD")).toContain("Alan-Switch-Id: switch-one");
144
+ expect(git(repoPath, "show", "-s", "--format=%B", "HEAD")).toContain("Alan-Repository-Id: repo-one");
145
+ expect(git(repoPath, "show", "-s", "--format=%B", "HEAD")).toContain("Co-Authored-By: Alan <agent@tryalan.ai>");
146
+ await expect(executeBindingSwitchGitRpc("git.checkpoint", request)).resolves.toMatchObject({
147
+ sha: first.sha,
148
+ replayed: true,
149
+ });
150
+ writeFileSync(join(repoPath, "tracked.txt"), "changed after checkpoint\n");
151
+ await expect(executeBindingSwitchGitRpc("git.checkpoint", request)).resolves.toMatchObject({
152
+ sha: first.sha,
153
+ replayed: true,
154
+ });
155
+ expect(git(repoPath, "status", "--porcelain")).toContain("tracked.txt");
156
+ });
157
+ it("invalidates approval when bytes change at the same tracked or untracked path", async () => {
158
+ const root = makeTempDir();
159
+ const repoPath = createRepository(root, "repo");
160
+ writeFileSync(join(repoPath, "tracked.txt"), "first tracked bytes\n");
161
+ writeFileSync(join(repoPath, "untracked.txt"), "first untracked bytes\n");
162
+ const first = (await executeBindingSwitchGitRpc("git.status", common(repoPath)));
163
+ writeFileSync(join(repoPath, "tracked.txt"), "second tracked bytes\n");
164
+ writeFileSync(join(repoPath, "untracked.txt"), "second untracked bytes\n");
165
+ const second = (await executeBindingSwitchGitRpc("git.status", common(repoPath)));
166
+ expect(second.statusFingerprint).not.toBe(first.statusFingerprint);
167
+ await expect(executeBindingSwitchGitRpc("git.checkpoint", {
168
+ ...common(repoPath),
169
+ expectedHeadSha: first.headSha,
170
+ expectedStatusFingerprint: first.statusFingerprint,
171
+ expectedProposedTreeSha: first.proposedTreeSha,
172
+ commitMessage: "checkpoint",
173
+ authorName: "Alan",
174
+ authorEmail: "agent@tryalan.ai",
175
+ })).rejects.toMatchObject({ code: "source_changed_since_approval" });
176
+ });
177
+ it("commits only the approved proposed tree when bytes change during preparation", async () => {
178
+ const root = makeTempDir();
179
+ const repoPath = createRepository(root, "checkpoint-race");
180
+ writeFileSync(join(repoPath, "tracked.txt"), "approved checkpoint bytes\n");
181
+ const inspected = (await executeBindingSwitchGitRpc("git.status", common(repoPath, "checkpoint-race")));
182
+ const barrierDir = join(root, "checkpoint-barrier");
183
+ const previousBarrier = process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR;
184
+ process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR = barrierDir;
185
+ try {
186
+ const checkpointPromise = executeBindingSwitchGitRpc("git.checkpoint", {
187
+ ...common(repoPath, "checkpoint-race"),
188
+ expectedHeadSha: inspected.headSha,
189
+ expectedStatusFingerprint: inspected.statusFingerprint,
190
+ expectedProposedTreeSha: inspected.proposedTreeSha,
191
+ commitMessage: "approved checkpoint",
192
+ authorName: "Alan",
193
+ authorEmail: "agent@tryalan.ai",
194
+ });
195
+ await waitForPath(join(barrierDir, "ready"));
196
+ writeFileSync(join(repoPath, "tracked.txt"), "post-approval bytes\n");
197
+ writeFileSync(join(barrierDir, "release"), "release\n");
198
+ const result = (await checkpointPromise);
199
+ expect(result.replayed).toBe(false);
200
+ expect(git(repoPath, "rev-parse", `${result.sha}^{tree}`)).toBe(inspected.proposedTreeSha);
201
+ expect(git(repoPath, "show", `${result.sha}:tracked.txt`)).toBe("approved checkpoint bytes");
202
+ expect(git(repoPath, "status", "--porcelain")).toContain("tracked.txt");
203
+ }
204
+ finally {
205
+ mkdirSync(barrierDir, { recursive: true });
206
+ writeFileSync(join(barrierDir, "release"), "release\n");
207
+ if (previousBarrier === undefined) {
208
+ delete process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR;
209
+ }
210
+ else {
211
+ process.env.ALAN_TEST_GIT_CHECKPOINT_BARRIER_DIR = previousBarrier;
212
+ }
213
+ }
214
+ });
215
+ it("leaves both branches and the new branch index intact when HEAD changes during finalization", async () => {
216
+ const root = makeTempDir();
217
+ const repoPath = createRepository(root, "checkpoint-branch-race");
218
+ const originalHead = git(repoPath, "rev-parse", "HEAD");
219
+ git(repoPath, "branch", "other-checkpoint", originalHead);
220
+ writeFileSync(join(repoPath, "tracked.txt"), "approved checkpoint bytes\n");
221
+ const inspected = (await executeBindingSwitchGitRpc("git.status", common(repoPath, "checkpoint-branch-race")));
222
+ const barrierDir = join(root, "checkpoint-finalize-barrier");
223
+ const previousBarrier = process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR;
224
+ process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR = barrierDir;
225
+ try {
226
+ const checkpointPromise = executeBindingSwitchGitRpc("git.checkpoint", {
227
+ ...common(repoPath, "checkpoint-branch-race"),
228
+ expectedHeadSha: inspected.headSha,
229
+ expectedStatusFingerprint: inspected.statusFingerprint,
230
+ expectedProposedTreeSha: inspected.proposedTreeSha,
231
+ commitMessage: "approved checkpoint",
232
+ authorName: "Alan",
233
+ authorEmail: "agent@tryalan.ai",
234
+ });
235
+ await waitForPath(join(barrierDir, "ready"));
236
+ git(repoPath, "switch", "other-checkpoint");
237
+ writeFileSync(join(barrierDir, "release"), "release\n");
238
+ await expect(checkpointPromise).rejects.toMatchObject({
239
+ code: "source_changed_since_approval",
240
+ });
241
+ expect(git(repoPath, "rev-parse", "refs/heads/main")).toBe(originalHead);
242
+ expect(git(repoPath, "rev-parse", "refs/heads/other-checkpoint")).toBe(originalHead);
243
+ expect(git(repoPath, "branch", "--show-current")).toBe("other-checkpoint");
244
+ expect(git(repoPath, "rev-parse", "HEAD")).toBe(originalHead);
245
+ expect(git(repoPath, "diff", "--cached", "--name-only")).toBe("");
246
+ expect(git(repoPath, "status", "--porcelain")).toBe("M tracked.txt");
247
+ expect(readFileSync(join(repoPath, "tracked.txt"), "utf8")).toBe("approved checkpoint bytes\n");
248
+ }
249
+ finally {
250
+ mkdirSync(barrierDir, { recursive: true });
251
+ writeFileSync(join(barrierDir, "release"), "release\n");
252
+ if (previousBarrier === undefined) {
253
+ delete process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR;
254
+ }
255
+ else {
256
+ process.env.ALAN_TEST_GIT_CHECKPOINT_FINALIZE_BARRIER_DIR = previousBarrier;
257
+ }
258
+ }
259
+ });
260
+ it("rejects checkpoint and push when the branch changes at the same HEAD", async () => {
261
+ const root = makeTempDir();
262
+ const checkpointRepo = createRepository(root, "checkpoint");
263
+ writeFileSync(join(checkpointRepo, "tracked.txt"), "dirty on main\n");
264
+ const inspected = (await executeBindingSwitchGitRpc("git.status", common(checkpointRepo, "same-sha-checkpoint")));
265
+ git(checkpointRepo, "switch", "-c", "other-checkpoint");
266
+ expect(git(checkpointRepo, "rev-parse", "HEAD")).toBe(inspected.headSha);
267
+ await expect(executeBindingSwitchGitRpc("git.checkpoint", {
268
+ ...common(checkpointRepo, "same-sha-checkpoint"),
269
+ expectedBranch: "main",
270
+ expectedHeadSha: inspected.headSha,
271
+ expectedStatusFingerprint: inspected.statusFingerprint,
272
+ expectedProposedTreeSha: inspected.proposedTreeSha,
273
+ commitMessage: "must not commit another branch",
274
+ authorName: "Alan",
275
+ authorEmail: "agent@tryalan.ai",
276
+ })).rejects.toMatchObject({ code: "source_changed_since_approval" });
277
+ const { sourcePath } = createRemoteFixture(root);
278
+ const approvedHead = git(sourcePath, "rev-parse", "HEAD");
279
+ git(sourcePath, "switch", "-c", "other-push");
280
+ expect(git(sourcePath, "rev-parse", "HEAD")).toBe(approvedHead);
281
+ await expect(executeBindingSwitchGitRpc("git.push", {
282
+ ...common(sourcePath, "same-sha-push"),
283
+ expectedBranch: "main",
284
+ expectedHeadSha: approvedHead,
285
+ branch: "main",
286
+ })).rejects.toMatchObject({ code: "source_changed_since_approval" });
287
+ });
288
+ it("publishes the source branch and lands a different bootstrap checkout at the exact SHA", async () => {
289
+ const root = makeTempDir();
290
+ const { remotePath, sourcePath, targetPath } = createRemoteFixture(root);
291
+ git(sourcePath, "switch", "-c", "task/migrate");
292
+ writeFileSync(join(sourcePath, "tracked.txt"), "source task work\n");
293
+ git(sourcePath, "commit", "-am", "Task work");
294
+ const pushedSha = git(sourcePath, "rev-parse", "HEAD");
295
+ expect(await executeBindingSwitchGitRpc("git.push", {
296
+ ...common(sourcePath),
297
+ expectedHeadSha: pushedSha,
298
+ branch: "task/migrate",
299
+ expectedRemoteUrl: `file://${remotePath}`,
300
+ })).toMatchObject({
301
+ pushedSha,
302
+ branch: "task/migrate",
303
+ replayed: false,
304
+ });
305
+ expect(git(targetPath, "branch", "--show-current")).toBe("main");
306
+ const prepared = await executeBindingSwitchGitRpc("git.prepare_checkout", {
307
+ ...common(targetPath),
308
+ branch: "task/migrate",
309
+ targetSha: pushedSha,
310
+ });
311
+ expect(prepared).toMatchObject({
312
+ path: targetPath,
313
+ branch: "task/migrate",
314
+ headSha: pushedSha,
315
+ replayed: false,
316
+ });
317
+ expect(git(targetPath, "branch", "--show-current")).toBe("task/migrate");
318
+ expect(git(targetPath, "rev-parse", "HEAD")).toBe(pushedSha);
319
+ expect(await executeBindingSwitchGitRpc("git.prepare_checkout", {
320
+ ...common(targetPath),
321
+ branch: "task/migrate",
322
+ targetSha: pushedSha,
323
+ })).toMatchObject({ headSha: pushedSha, replayed: true });
324
+ expect(await executeBindingSwitchGitRpc("git.verify_head", {
325
+ ...common(targetPath),
326
+ expectedSha: pushedSha,
327
+ })).toMatchObject({ headSha: pushedSha, verified: true });
328
+ });
329
+ it("rejects verification of a dirty target even when HEAD is exact", async () => {
330
+ const root = makeTempDir();
331
+ const repoPath = createRepository(root, "dirty-verify");
332
+ const expectedSha = git(repoPath, "rev-parse", "HEAD");
333
+ writeFileSync(join(repoPath, "late-target-edit.txt"), "must block verification\n");
334
+ await expect(executeBindingSwitchGitRpc("git.verify_head", {
335
+ ...common(repoPath, "dirty-verify"),
336
+ expectedSha,
337
+ })).rejects.toMatchObject({ code: "target_checkout_dirty" });
338
+ }, 60_000);
339
+ it("fetches and creates an Alan-managed worktree at the published SHA", async () => {
340
+ const root = makeTempDir();
341
+ const { sourcePath, targetPath } = createRemoteFixture(root);
342
+ git(sourcePath, "switch", "-c", "task/worktree");
343
+ writeFileSync(join(sourcePath, "tracked.txt"), "worktree work\n");
344
+ git(sourcePath, "commit", "-am", "Worktree task");
345
+ const pushedSha = git(sourcePath, "rev-parse", "HEAD");
346
+ await executeBindingSwitchGitRpc("git.push", {
347
+ ...common(sourcePath, "worktree"),
348
+ expectedHeadSha: pushedSha,
349
+ branch: "task/worktree",
350
+ });
351
+ const managedRoot = join(dirname(targetPath), `${basename(targetPath)}-alan-worktrees`);
352
+ const worktreePath = join(managedRoot, "switch-worktree", "repo");
353
+ expect(await executeBindingSwitchGitRpc("git.prepare_worktree", {
354
+ ...common(targetPath, "worktree"),
355
+ worktreePath,
356
+ branch: "task/worktree",
357
+ targetSha: pushedSha,
358
+ })).toMatchObject({
359
+ path: worktreePath,
360
+ branch: "task/worktree",
361
+ headSha: pushedSha,
362
+ replayed: false,
363
+ });
364
+ expect(git(worktreePath, "rev-parse", "HEAD")).toBe(pushedSha);
365
+ expect(await executeBindingSwitchGitRpc("git.prepare_worktree", {
366
+ ...common(targetPath, "worktree"),
367
+ worktreePath,
368
+ branch: "task/worktree",
369
+ targetSha: pushedSha,
370
+ })).toMatchObject({ headSha: pushedSha, replayed: true });
371
+ });
372
+ it("rejects an arbitrary repository root even when its path is otherwise valid", async () => {
373
+ const root = makeTempDir();
374
+ const trustedProject = createRepository(root, "trusted");
375
+ const arbitraryRepo = createRepository(root, "arbitrary");
376
+ writeFileSync(join(arbitraryRepo, "tracked.txt"), "arbitrary dirt\n");
377
+ const status = (await executeBindingSwitchGitRpc("git.status", common(arbitraryRepo, "arbitrary")));
378
+ await expect(executeBindingSwitchGitRpc("git.checkpoint", {
379
+ ...common(arbitraryRepo, "arbitrary"),
380
+ projectPath: trustedProject,
381
+ expectedRepoRoot: arbitraryRepo,
382
+ expectedHeadSha: status.headSha,
383
+ expectedStatusFingerprint: status.statusFingerprint,
384
+ expectedProposedTreeSha: status.proposedTreeSha,
385
+ commitMessage: "must not commit",
386
+ authorName: "Alan",
387
+ authorEmail: "agent@tryalan.ai",
388
+ })).rejects.toMatchObject({ code: "repository_path_not_authorized" });
389
+ expect(git(arbitraryRepo, "status", "--porcelain")).not.toBe("");
390
+ });
391
+ it("rejects a mismatched remote before contacting either repository", async () => {
392
+ const root = makeTempDir();
393
+ const { sourcePath, remotePath } = createRemoteFixture(root);
394
+ const otherRemote = join(root, "other.git");
395
+ mkdirSync(otherRemote);
396
+ git(otherRemote, "init", "--bare");
397
+ git(sourcePath, "switch", "-c", "task/remote-guard");
398
+ writeFileSync(join(sourcePath, "tracked.txt"), "guarded work\n");
399
+ git(sourcePath, "commit", "-am", "Guarded work");
400
+ const expectedHeadSha = git(sourcePath, "rev-parse", "HEAD");
401
+ await expect(executeBindingSwitchGitRpc("git.push", {
402
+ ...common(sourcePath, "remote-guard"),
403
+ expectedHeadSha,
404
+ branch: "task/remote-guard",
405
+ expectedRemoteUrl: otherRemote,
406
+ })).rejects.toMatchObject({ code: "remote_url_mismatch" });
407
+ expect(git(remotePath, "for-each-ref", "--format=%(refname)", "refs/heads/task/remote-guard")).toBe("");
408
+ expect(git(otherRemote, "for-each-ref", "--format=%(refname)", "refs/heads/task/remote-guard")).toBe("");
409
+ });
410
+ it("rejects an Alan worktree path whose symlinked ancestor escapes the managed root", async () => {
411
+ const root = makeTempDir();
412
+ const { sourcePath, targetPath } = createRemoteFixture(root);
413
+ git(sourcePath, "switch", "-c", "task/symlink");
414
+ writeFileSync(join(sourcePath, "tracked.txt"), "symlink guarded\n");
415
+ git(sourcePath, "commit", "-am", "Symlink work");
416
+ const pushedSha = git(sourcePath, "rev-parse", "HEAD");
417
+ await executeBindingSwitchGitRpc("git.push", {
418
+ ...common(sourcePath, "symlink"),
419
+ expectedHeadSha: pushedSha,
420
+ branch: "task/symlink",
421
+ });
422
+ const managedRoot = join(dirname(targetPath), `${basename(targetPath)}-alan-worktrees`);
423
+ const outsideRoot = join(root, "outside");
424
+ mkdirSync(managedRoot);
425
+ mkdirSync(outsideRoot);
426
+ symlinkSync(outsideRoot, join(managedRoot, "escape"));
427
+ await expect(executeBindingSwitchGitRpc("git.prepare_worktree", {
428
+ ...common(targetPath, "symlink"),
429
+ worktreePath: join(managedRoot, "escape", "task"),
430
+ branch: "task/symlink",
431
+ targetSha: pushedSha,
432
+ })).rejects.toMatchObject({ code: "worktree_path_not_authorized" });
433
+ expect(existsSync(join(outsideRoot, ".git"))).toBe(false);
434
+ rmSync(managedRoot, { recursive: true });
435
+ symlinkSync(outsideRoot, managedRoot);
436
+ await expect(executeBindingSwitchGitRpc("git.prepare_worktree", {
437
+ ...common(targetPath, "symlink"),
438
+ worktreePath: join(managedRoot, "task"),
439
+ branch: "task/symlink",
440
+ targetSha: pushedSha,
441
+ })).rejects.toMatchObject({ code: "worktree_path_not_authorized" });
442
+ expect(existsSync(join(outsideRoot, ".git"))).toBe(false);
443
+ });
444
+ it("cancels a hung network operation without letting its child finish later", async () => {
445
+ const root = makeTempDir();
446
+ const repoPath = createRepository(root, "repo");
447
+ git(repoPath, "switch", "-c", "task/timeout");
448
+ const markerPath = join(root, "network-finished");
449
+ const fakeBin = join(root, "fake-bin");
450
+ mkdirSync(fakeBin);
451
+ const sshScript = join(fakeBin, "ssh");
452
+ writeFileSync(sshScript, `#!/bin/sh\nsleep 2\nprintf finished > '${markerPath}'\nexit 1\n`);
453
+ chmodSync(sshScript, 0o755);
454
+ const remoteUrl = "ssh://git@example.invalid/owner/repo.git";
455
+ git(repoPath, "remote", "add", "origin", remoteUrl);
456
+ const previousSshCommand = process.env.ALAN_TEST_GIT_SSH_COMMAND;
457
+ const previousTimeout = process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS;
458
+ process.env.ALAN_TEST_GIT_SSH_COMMAND = sshScript;
459
+ process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS = "100";
460
+ try {
461
+ await expect(executeBindingSwitchGitRpc("git.push", {
462
+ ...common(repoPath, "timeout"),
463
+ expectedHeadSha: git(repoPath, "rev-parse", "HEAD"),
464
+ branch: "task/timeout",
465
+ expectedRemoteUrl: "https://example.invalid/owner/repo.git",
466
+ })).rejects.toMatchObject({ code: "git_network_timeout" });
467
+ await new Promise((resolvePromise) => setTimeout(resolvePromise, 500));
468
+ expect(existsSync(markerPath)).toBe(false);
469
+ }
470
+ finally {
471
+ if (previousSshCommand === undefined)
472
+ delete process.env.ALAN_TEST_GIT_SSH_COMMAND;
473
+ else
474
+ process.env.ALAN_TEST_GIT_SSH_COMMAND = previousSshCommand;
475
+ if (previousTimeout === undefined)
476
+ delete process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS;
477
+ else
478
+ process.env.ALAN_TEST_GIT_NETWORK_TIMEOUT_MS = previousTimeout;
479
+ }
480
+ });
481
+ it("preserves published SHA evidence when the source changes during push", async () => {
482
+ const root = makeTempDir();
483
+ const { remotePath, sourcePath } = createRemoteFixture(root);
484
+ git(sourcePath, "switch", "-c", "task/push-race");
485
+ writeFileSync(join(sourcePath, "tracked.txt"), "approved work\n");
486
+ git(sourcePath, "commit", "-am", "Approved work");
487
+ const approvedSha = git(sourcePath, "rev-parse", "HEAD");
488
+ const readyPath = join(root, "push-ready");
489
+ const releasePath = join(root, "push-release");
490
+ const sshScript = join(root, "blocking-ssh.sh");
491
+ writeFileSync(sshScript, [
492
+ "#!/bin/sh",
493
+ 'if [ "$1" = "-G" ]; then exit 0; fi',
494
+ 'command=""',
495
+ 'for argument in "$@"; do command="$argument"; done',
496
+ `case "$command" in *git-receive-pack*) touch '${readyPath}'; while [ ! -f '${releasePath}' ]; do sleep 0.02; done ;; esac`,
497
+ 'exec /bin/sh -c "$command"',
498
+ "",
499
+ ].join("\n"));
500
+ chmodSync(sshScript, 0o755);
501
+ const remoteUrl = `ssh://git@localhost${remotePath}`;
502
+ git(sourcePath, "remote", "set-url", "origin", remoteUrl);
503
+ const previousSshCommand = process.env.ALAN_TEST_GIT_SSH_COMMAND;
504
+ process.env.ALAN_TEST_GIT_SSH_COMMAND = sshScript;
505
+ try {
506
+ const pushPromise = executeBindingSwitchGitRpc("git.push", {
507
+ ...common(sourcePath, "push-race"),
508
+ expectedHeadSha: approvedSha,
509
+ expectedBranch: "task/push-race",
510
+ branch: "task/push-race",
511
+ expectedRemoteUrl: remoteUrl,
512
+ });
513
+ await waitForPath(readyPath);
514
+ writeFileSync(join(sourcePath, "tracked.txt"), "committed work changed during push\n");
515
+ git(sourcePath, "commit", "-am", "Late local commit");
516
+ const lateSha = git(sourcePath, "rev-parse", "HEAD");
517
+ expect(lateSha).not.toBe(approvedSha);
518
+ writeFileSync(releasePath, "release\n");
519
+ await expect(pushPromise).rejects.toMatchObject({
520
+ code: "source_changed_since_approval",
521
+ details: {
522
+ published: {
523
+ pushedSha: approvedSha,
524
+ branch: "task/push-race",
525
+ remote: "origin",
526
+ replayed: false,
527
+ },
528
+ },
529
+ });
530
+ expect(git(remotePath, "rev-parse", "refs/heads/task/push-race")).toBe(approvedSha);
531
+ expect(git(sourcePath, "rev-parse", "HEAD")).toBe(lateSha);
532
+ expect(git(sourcePath, "status", "--porcelain")).toBe("");
533
+ }
534
+ finally {
535
+ writeFileSync(releasePath, "release\n");
536
+ if (previousSshCommand === undefined)
537
+ delete process.env.ALAN_TEST_GIT_SSH_COMMAND;
538
+ else
539
+ process.env.ALAN_TEST_GIT_SSH_COMMAND = previousSshCommand;
540
+ }
541
+ });
542
+ it("rejects stale status approval and a fetched branch at the wrong SHA", async () => {
543
+ const root = makeTempDir();
544
+ const { sourcePath, targetPath, mainSha } = createRemoteFixture(root);
545
+ git(sourcePath, "switch", "-c", "task/stale");
546
+ writeFileSync(join(sourcePath, "tracked.txt"), "dirty\n");
547
+ const status = (await executeBindingSwitchGitRpc("git.status", common(sourcePath, "stale")));
548
+ writeFileSync(join(sourcePath, "another.txt"), "raced\n");
549
+ await expect(executeBindingSwitchGitRpc("git.checkpoint", {
550
+ ...common(sourcePath, "stale"),
551
+ expectedHeadSha: status.headSha,
552
+ expectedStatusFingerprint: status.statusFingerprint,
553
+ expectedProposedTreeSha: status.proposedTreeSha,
554
+ commitMessage: "checkpoint",
555
+ authorName: "Alan",
556
+ authorEmail: "agent@tryalan.ai",
557
+ })).rejects.toThrowError(expect.objectContaining({
558
+ code: "source_changed_since_approval",
559
+ }));
560
+ git(sourcePath, "add", "--all");
561
+ git(sourcePath, "commit", "-m", "Published task work");
562
+ const taskSha = git(sourcePath, "rev-parse", "HEAD");
563
+ await executeBindingSwitchGitRpc("git.push", {
564
+ ...common(sourcePath, "stale"),
565
+ expectedHeadSha: taskSha,
566
+ branch: "task/stale",
567
+ });
568
+ await expect(executeBindingSwitchGitRpc("git.fetch", {
569
+ ...common(targetPath, "stale"),
570
+ branch: "task/stale",
571
+ expectedSha: mainSha,
572
+ })).rejects.toThrowError(expect.objectContaining({
573
+ code: "target_sha_mismatch",
574
+ }));
575
+ });
576
+ it("never fast-forwards a dirty or divergent shared checkout", async () => {
577
+ const root = makeTempDir();
578
+ const { sourcePath, targetPath } = createRemoteFixture(root);
579
+ git(sourcePath, "switch", "-c", "task/safe");
580
+ writeFileSync(join(sourcePath, "tracked.txt"), "remote task work\n");
581
+ git(sourcePath, "commit", "-am", "Remote task work");
582
+ const pushedSha = git(sourcePath, "rev-parse", "HEAD");
583
+ await executeBindingSwitchGitRpc("git.push", {
584
+ ...common(sourcePath, "safe"),
585
+ expectedHeadSha: pushedSha,
586
+ branch: "task/safe",
587
+ });
588
+ writeFileSync(join(targetPath, "dirty.txt"), "local dirt\n");
589
+ await expect(executeBindingSwitchGitRpc("git.prepare_checkout", {
590
+ ...common(targetPath, "safe"),
591
+ branch: "task/safe",
592
+ targetSha: pushedSha,
593
+ })).rejects.toThrowError(expect.objectContaining({
594
+ code: "target_checkout_dirty",
595
+ }));
596
+ rmSync(join(targetPath, "dirty.txt"));
597
+ git(targetPath, "switch", "-c", "task/safe");
598
+ writeFileSync(join(targetPath, "local.txt"), "divergent\n");
599
+ git(targetPath, "add", "local.txt");
600
+ git(targetPath, "commit", "-m", "Local divergent work");
601
+ await expect(executeBindingSwitchGitRpc("git.prepare_checkout", {
602
+ ...common(targetPath, "safe"),
603
+ branch: "task/safe",
604
+ targetSha: pushedSha,
605
+ })).rejects.toThrowError(expect.objectContaining({
606
+ code: "target_branch_diverged",
607
+ }));
608
+ });
609
+ });
610
+ //# sourceMappingURL=binding-switch-git.test.js.map