@stoneforge/smithy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/LICENSE +13 -0
  2. package/README.md +114 -0
  3. package/dist/api/index.d.ts +7 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +7 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/orchestrator-api.d.ts +153 -0
  8. package/dist/api/orchestrator-api.d.ts.map +1 -0
  9. package/dist/api/orchestrator-api.js +374 -0
  10. package/dist/api/orchestrator-api.js.map +1 -0
  11. package/dist/bin/sf.d.ts +3 -0
  12. package/dist/bin/sf.d.ts.map +1 -0
  13. package/dist/bin/sf.js +10 -0
  14. package/dist/bin/sf.js.map +1 -0
  15. package/dist/cli/commands/agent.d.ts +20 -0
  16. package/dist/cli/commands/agent.d.ts.map +1 -0
  17. package/dist/cli/commands/agent.js +861 -0
  18. package/dist/cli/commands/agent.js.map +1 -0
  19. package/dist/cli/commands/daemon.d.ts +14 -0
  20. package/dist/cli/commands/daemon.d.ts.map +1 -0
  21. package/dist/cli/commands/daemon.js +272 -0
  22. package/dist/cli/commands/daemon.js.map +1 -0
  23. package/dist/cli/commands/dispatch.d.ts +9 -0
  24. package/dist/cli/commands/dispatch.d.ts.map +1 -0
  25. package/dist/cli/commands/dispatch.js +128 -0
  26. package/dist/cli/commands/dispatch.js.map +1 -0
  27. package/dist/cli/commands/merge.d.ts +11 -0
  28. package/dist/cli/commands/merge.d.ts.map +1 -0
  29. package/dist/cli/commands/merge.js +246 -0
  30. package/dist/cli/commands/merge.js.map +1 -0
  31. package/dist/cli/commands/pool.d.ts +21 -0
  32. package/dist/cli/commands/pool.d.ts.map +1 -0
  33. package/dist/cli/commands/pool.js +762 -0
  34. package/dist/cli/commands/pool.js.map +1 -0
  35. package/dist/cli/commands/serve.d.ts +54 -0
  36. package/dist/cli/commands/serve.d.ts.map +1 -0
  37. package/dist/cli/commands/serve.js +57 -0
  38. package/dist/cli/commands/serve.js.map +1 -0
  39. package/dist/cli/commands/task.d.ts +36 -0
  40. package/dist/cli/commands/task.d.ts.map +1 -0
  41. package/dist/cli/commands/task.js +889 -0
  42. package/dist/cli/commands/task.js.map +1 -0
  43. package/dist/cli/commands/test-orchestration.d.ts +32 -0
  44. package/dist/cli/commands/test-orchestration.d.ts.map +1 -0
  45. package/dist/cli/commands/test-orchestration.js +392 -0
  46. package/dist/cli/commands/test-orchestration.js.map +1 -0
  47. package/dist/cli/index.d.ts +13 -0
  48. package/dist/cli/index.d.ts.map +1 -0
  49. package/dist/cli/index.js +15 -0
  50. package/dist/cli/index.js.map +1 -0
  51. package/dist/cli/plugin.d.ts +23 -0
  52. package/dist/cli/plugin.d.ts.map +1 -0
  53. package/dist/cli/plugin.js +36 -0
  54. package/dist/cli/plugin.js.map +1 -0
  55. package/dist/git/index.d.ts +10 -0
  56. package/dist/git/index.d.ts.map +1 -0
  57. package/dist/git/index.js +12 -0
  58. package/dist/git/index.js.map +1 -0
  59. package/dist/git/merge.d.ts +79 -0
  60. package/dist/git/merge.d.ts.map +1 -0
  61. package/dist/git/merge.js +254 -0
  62. package/dist/git/merge.js.map +1 -0
  63. package/dist/git/worktree-manager.d.ts +299 -0
  64. package/dist/git/worktree-manager.d.ts.map +1 -0
  65. package/dist/git/worktree-manager.js +744 -0
  66. package/dist/git/worktree-manager.js.map +1 -0
  67. package/dist/index.d.ts +24 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +31 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/prompts/director.md +272 -0
  72. package/dist/prompts/index.d.ts +100 -0
  73. package/dist/prompts/index.d.ts.map +1 -0
  74. package/dist/prompts/index.js +294 -0
  75. package/dist/prompts/index.js.map +1 -0
  76. package/dist/prompts/message-triage.md +50 -0
  77. package/dist/prompts/persistent-worker.md +240 -0
  78. package/dist/prompts/steward-base.md +64 -0
  79. package/dist/prompts/steward-docs.md +118 -0
  80. package/dist/prompts/steward-health.md +39 -0
  81. package/dist/prompts/steward-merge.md +168 -0
  82. package/dist/prompts/steward-ops.md +28 -0
  83. package/dist/prompts/steward-reminder.md +26 -0
  84. package/dist/prompts/worker.md +282 -0
  85. package/dist/providers/claude/headless.d.ts +18 -0
  86. package/dist/providers/claude/headless.d.ts.map +1 -0
  87. package/dist/providers/claude/headless.js +307 -0
  88. package/dist/providers/claude/headless.js.map +1 -0
  89. package/dist/providers/claude/index.d.ts +24 -0
  90. package/dist/providers/claude/index.d.ts.map +1 -0
  91. package/dist/providers/claude/index.js +80 -0
  92. package/dist/providers/claude/index.js.map +1 -0
  93. package/dist/providers/claude/interactive.d.ts +21 -0
  94. package/dist/providers/claude/interactive.d.ts.map +1 -0
  95. package/dist/providers/claude/interactive.js +142 -0
  96. package/dist/providers/claude/interactive.js.map +1 -0
  97. package/dist/providers/codex/event-mapper.d.ts +91 -0
  98. package/dist/providers/codex/event-mapper.d.ts.map +1 -0
  99. package/dist/providers/codex/event-mapper.js +299 -0
  100. package/dist/providers/codex/event-mapper.js.map +1 -0
  101. package/dist/providers/codex/headless.d.ts +20 -0
  102. package/dist/providers/codex/headless.d.ts.map +1 -0
  103. package/dist/providers/codex/headless.js +174 -0
  104. package/dist/providers/codex/headless.js.map +1 -0
  105. package/dist/providers/codex/index.d.ts +30 -0
  106. package/dist/providers/codex/index.d.ts.map +1 -0
  107. package/dist/providers/codex/index.js +55 -0
  108. package/dist/providers/codex/index.js.map +1 -0
  109. package/dist/providers/codex/interactive.d.ts +21 -0
  110. package/dist/providers/codex/interactive.d.ts.map +1 -0
  111. package/dist/providers/codex/interactive.js +141 -0
  112. package/dist/providers/codex/interactive.js.map +1 -0
  113. package/dist/providers/codex/jsonrpc-client.d.ts +52 -0
  114. package/dist/providers/codex/jsonrpc-client.d.ts.map +1 -0
  115. package/dist/providers/codex/jsonrpc-client.js +141 -0
  116. package/dist/providers/codex/jsonrpc-client.js.map +1 -0
  117. package/dist/providers/codex/server-manager.d.ts +100 -0
  118. package/dist/providers/codex/server-manager.d.ts.map +1 -0
  119. package/dist/providers/codex/server-manager.js +153 -0
  120. package/dist/providers/codex/server-manager.js.map +1 -0
  121. package/dist/providers/index.d.ts +15 -0
  122. package/dist/providers/index.d.ts.map +1 -0
  123. package/dist/providers/index.js +19 -0
  124. package/dist/providers/index.js.map +1 -0
  125. package/dist/providers/opencode/async-queue.d.ts +21 -0
  126. package/dist/providers/opencode/async-queue.d.ts.map +1 -0
  127. package/dist/providers/opencode/async-queue.js +51 -0
  128. package/dist/providers/opencode/async-queue.js.map +1 -0
  129. package/dist/providers/opencode/event-mapper.d.ts +132 -0
  130. package/dist/providers/opencode/event-mapper.d.ts.map +1 -0
  131. package/dist/providers/opencode/event-mapper.js +204 -0
  132. package/dist/providers/opencode/event-mapper.js.map +1 -0
  133. package/dist/providers/opencode/headless.d.ts +25 -0
  134. package/dist/providers/opencode/headless.d.ts.map +1 -0
  135. package/dist/providers/opencode/headless.js +190 -0
  136. package/dist/providers/opencode/headless.js.map +1 -0
  137. package/dist/providers/opencode/index.d.ts +33 -0
  138. package/dist/providers/opencode/index.d.ts.map +1 -0
  139. package/dist/providers/opencode/index.js +42 -0
  140. package/dist/providers/opencode/index.js.map +1 -0
  141. package/dist/providers/opencode/interactive.d.ts +21 -0
  142. package/dist/providers/opencode/interactive.d.ts.map +1 -0
  143. package/dist/providers/opencode/interactive.js +135 -0
  144. package/dist/providers/opencode/interactive.js.map +1 -0
  145. package/dist/providers/opencode/server-manager.d.ts +145 -0
  146. package/dist/providers/opencode/server-manager.d.ts.map +1 -0
  147. package/dist/providers/opencode/server-manager.js +163 -0
  148. package/dist/providers/opencode/server-manager.js.map +1 -0
  149. package/dist/providers/registry.d.ts +38 -0
  150. package/dist/providers/registry.d.ts.map +1 -0
  151. package/dist/providers/registry.js +82 -0
  152. package/dist/providers/registry.js.map +1 -0
  153. package/dist/providers/types.d.ts +144 -0
  154. package/dist/providers/types.d.ts.map +1 -0
  155. package/dist/providers/types.js +25 -0
  156. package/dist/providers/types.js.map +1 -0
  157. package/dist/runtime/event-utils.d.ts +8 -0
  158. package/dist/runtime/event-utils.d.ts.map +1 -0
  159. package/dist/runtime/event-utils.js +23 -0
  160. package/dist/runtime/event-utils.js.map +1 -0
  161. package/dist/runtime/handoff.d.ts +195 -0
  162. package/dist/runtime/handoff.d.ts.map +1 -0
  163. package/dist/runtime/handoff.js +332 -0
  164. package/dist/runtime/handoff.js.map +1 -0
  165. package/dist/runtime/index.d.ts +17 -0
  166. package/dist/runtime/index.d.ts.map +1 -0
  167. package/dist/runtime/index.js +60 -0
  168. package/dist/runtime/index.js.map +1 -0
  169. package/dist/runtime/message-mapper.d.ts +99 -0
  170. package/dist/runtime/message-mapper.d.ts.map +1 -0
  171. package/dist/runtime/message-mapper.js +202 -0
  172. package/dist/runtime/message-mapper.js.map +1 -0
  173. package/dist/runtime/predecessor-query.d.ts +212 -0
  174. package/dist/runtime/predecessor-query.d.ts.map +1 -0
  175. package/dist/runtime/predecessor-query.js +283 -0
  176. package/dist/runtime/predecessor-query.js.map +1 -0
  177. package/dist/runtime/session-manager.d.ts +466 -0
  178. package/dist/runtime/session-manager.d.ts.map +1 -0
  179. package/dist/runtime/session-manager.js +986 -0
  180. package/dist/runtime/session-manager.js.map +1 -0
  181. package/dist/runtime/spawner.d.ts +407 -0
  182. package/dist/runtime/spawner.d.ts.map +1 -0
  183. package/dist/runtime/spawner.js +781 -0
  184. package/dist/runtime/spawner.js.map +1 -0
  185. package/dist/server/config.d.ts +22 -0
  186. package/dist/server/config.d.ts.map +1 -0
  187. package/dist/server/config.js +59 -0
  188. package/dist/server/config.js.map +1 -0
  189. package/dist/server/daemon-state.d.ts +50 -0
  190. package/dist/server/daemon-state.d.ts.map +1 -0
  191. package/dist/server/daemon-state.js +100 -0
  192. package/dist/server/daemon-state.js.map +1 -0
  193. package/dist/server/events-websocket.d.ts +32 -0
  194. package/dist/server/events-websocket.d.ts.map +1 -0
  195. package/dist/server/events-websocket.js +96 -0
  196. package/dist/server/events-websocket.js.map +1 -0
  197. package/dist/server/formatters.d.ts +94 -0
  198. package/dist/server/formatters.d.ts.map +1 -0
  199. package/dist/server/formatters.js +142 -0
  200. package/dist/server/formatters.js.map +1 -0
  201. package/dist/server/index.d.ts +17 -0
  202. package/dist/server/index.d.ts.map +1 -0
  203. package/dist/server/index.js +153 -0
  204. package/dist/server/index.js.map +1 -0
  205. package/dist/server/lsp-websocket.d.ts +33 -0
  206. package/dist/server/lsp-websocket.d.ts.map +1 -0
  207. package/dist/server/lsp-websocket.js +161 -0
  208. package/dist/server/lsp-websocket.js.map +1 -0
  209. package/dist/server/routes/agents.d.ts +9 -0
  210. package/dist/server/routes/agents.d.ts.map +1 -0
  211. package/dist/server/routes/agents.js +369 -0
  212. package/dist/server/routes/agents.js.map +1 -0
  213. package/dist/server/routes/daemon.d.ts +13 -0
  214. package/dist/server/routes/daemon.d.ts.map +1 -0
  215. package/dist/server/routes/daemon.js +187 -0
  216. package/dist/server/routes/daemon.js.map +1 -0
  217. package/dist/server/routes/events.d.ts +23 -0
  218. package/dist/server/routes/events.d.ts.map +1 -0
  219. package/dist/server/routes/events.js +282 -0
  220. package/dist/server/routes/events.js.map +1 -0
  221. package/dist/server/routes/extensions.d.ts +9 -0
  222. package/dist/server/routes/extensions.d.ts.map +1 -0
  223. package/dist/server/routes/extensions.js +202 -0
  224. package/dist/server/routes/extensions.js.map +1 -0
  225. package/dist/server/routes/health.d.ts +7 -0
  226. package/dist/server/routes/health.d.ts.map +1 -0
  227. package/dist/server/routes/health.js +33 -0
  228. package/dist/server/routes/health.js.map +1 -0
  229. package/dist/server/routes/index.d.ts +21 -0
  230. package/dist/server/routes/index.d.ts.map +1 -0
  231. package/dist/server/routes/index.js +21 -0
  232. package/dist/server/routes/index.js.map +1 -0
  233. package/dist/server/routes/lsp.d.ts +9 -0
  234. package/dist/server/routes/lsp.d.ts.map +1 -0
  235. package/dist/server/routes/lsp.js +50 -0
  236. package/dist/server/routes/lsp.js.map +1 -0
  237. package/dist/server/routes/plugins.d.ts +9 -0
  238. package/dist/server/routes/plugins.d.ts.map +1 -0
  239. package/dist/server/routes/plugins.js +109 -0
  240. package/dist/server/routes/plugins.js.map +1 -0
  241. package/dist/server/routes/pools.d.ts +9 -0
  242. package/dist/server/routes/pools.d.ts.map +1 -0
  243. package/dist/server/routes/pools.js +189 -0
  244. package/dist/server/routes/pools.js.map +1 -0
  245. package/dist/server/routes/scheduler.d.ts +9 -0
  246. package/dist/server/routes/scheduler.d.ts.map +1 -0
  247. package/dist/server/routes/scheduler.js +162 -0
  248. package/dist/server/routes/scheduler.js.map +1 -0
  249. package/dist/server/routes/sessions.d.ts +27 -0
  250. package/dist/server/routes/sessions.d.ts.map +1 -0
  251. package/dist/server/routes/sessions.js +773 -0
  252. package/dist/server/routes/sessions.js.map +1 -0
  253. package/dist/server/routes/tasks.d.ts +9 -0
  254. package/dist/server/routes/tasks.d.ts.map +1 -0
  255. package/dist/server/routes/tasks.js +954 -0
  256. package/dist/server/routes/tasks.js.map +1 -0
  257. package/dist/server/routes/upload.d.ts +8 -0
  258. package/dist/server/routes/upload.d.ts.map +1 -0
  259. package/dist/server/routes/upload.js +40 -0
  260. package/dist/server/routes/upload.js.map +1 -0
  261. package/dist/server/routes/workflows.d.ts +9 -0
  262. package/dist/server/routes/workflows.d.ts.map +1 -0
  263. package/dist/server/routes/workflows.js +532 -0
  264. package/dist/server/routes/workflows.js.map +1 -0
  265. package/dist/server/routes/workspace-files.d.ts +12 -0
  266. package/dist/server/routes/workspace-files.d.ts.map +1 -0
  267. package/dist/server/routes/workspace-files.js +520 -0
  268. package/dist/server/routes/workspace-files.js.map +1 -0
  269. package/dist/server/routes/worktrees.d.ts +9 -0
  270. package/dist/server/routes/worktrees.d.ts.map +1 -0
  271. package/dist/server/routes/worktrees.js +94 -0
  272. package/dist/server/routes/worktrees.js.map +1 -0
  273. package/dist/server/server.d.ts +14 -0
  274. package/dist/server/server.d.ts.map +1 -0
  275. package/dist/server/server.js +258 -0
  276. package/dist/server/server.js.map +1 -0
  277. package/dist/server/services/lsp-manager.d.ts +93 -0
  278. package/dist/server/services/lsp-manager.d.ts.map +1 -0
  279. package/dist/server/services/lsp-manager.js +291 -0
  280. package/dist/server/services/lsp-manager.js.map +1 -0
  281. package/dist/server/services/session-messages.d.ts +61 -0
  282. package/dist/server/services/session-messages.d.ts.map +1 -0
  283. package/dist/server/services/session-messages.js +101 -0
  284. package/dist/server/services/session-messages.js.map +1 -0
  285. package/dist/server/services.d.ts +35 -0
  286. package/dist/server/services.d.ts.map +1 -0
  287. package/dist/server/services.js +159 -0
  288. package/dist/server/services.js.map +1 -0
  289. package/dist/server/static.d.ts +18 -0
  290. package/dist/server/static.d.ts.map +1 -0
  291. package/dist/server/static.js +71 -0
  292. package/dist/server/static.js.map +1 -0
  293. package/dist/server/types.d.ts +20 -0
  294. package/dist/server/types.d.ts.map +1 -0
  295. package/dist/server/types.js +7 -0
  296. package/dist/server/types.js.map +1 -0
  297. package/dist/server/websocket.d.ts +16 -0
  298. package/dist/server/websocket.d.ts.map +1 -0
  299. package/dist/server/websocket.js +143 -0
  300. package/dist/server/websocket.js.map +1 -0
  301. package/dist/services/agent-pool-service.d.ts +181 -0
  302. package/dist/services/agent-pool-service.d.ts.map +1 -0
  303. package/dist/services/agent-pool-service.js +590 -0
  304. package/dist/services/agent-pool-service.js.map +1 -0
  305. package/dist/services/agent-registry.d.ts +185 -0
  306. package/dist/services/agent-registry.d.ts.map +1 -0
  307. package/dist/services/agent-registry.js +432 -0
  308. package/dist/services/agent-registry.js.map +1 -0
  309. package/dist/services/dispatch-daemon.d.ts +429 -0
  310. package/dist/services/dispatch-daemon.d.ts.map +1 -0
  311. package/dist/services/dispatch-daemon.js +1833 -0
  312. package/dist/services/dispatch-daemon.js.map +1 -0
  313. package/dist/services/dispatch-service.d.ts +148 -0
  314. package/dist/services/dispatch-service.d.ts.map +1 -0
  315. package/dist/services/dispatch-service.js +170 -0
  316. package/dist/services/dispatch-service.js.map +1 -0
  317. package/dist/services/docs-steward-service.d.ts +199 -0
  318. package/dist/services/docs-steward-service.d.ts.map +1 -0
  319. package/dist/services/docs-steward-service.js +599 -0
  320. package/dist/services/docs-steward-service.js.map +1 -0
  321. package/dist/services/health-steward-service.d.ts +446 -0
  322. package/dist/services/health-steward-service.d.ts.map +1 -0
  323. package/dist/services/health-steward-service.js +866 -0
  324. package/dist/services/health-steward-service.js.map +1 -0
  325. package/dist/services/index.d.ts +26 -0
  326. package/dist/services/index.d.ts.map +1 -0
  327. package/dist/services/index.js +111 -0
  328. package/dist/services/index.js.map +1 -0
  329. package/dist/services/merge-request-provider.d.ts +59 -0
  330. package/dist/services/merge-request-provider.d.ts.map +1 -0
  331. package/dist/services/merge-request-provider.js +89 -0
  332. package/dist/services/merge-request-provider.js.map +1 -0
  333. package/dist/services/merge-steward-service.d.ts +268 -0
  334. package/dist/services/merge-steward-service.d.ts.map +1 -0
  335. package/dist/services/merge-steward-service.js +568 -0
  336. package/dist/services/merge-steward-service.js.map +1 -0
  337. package/dist/services/plugin-executor.d.ts +247 -0
  338. package/dist/services/plugin-executor.d.ts.map +1 -0
  339. package/dist/services/plugin-executor.js +451 -0
  340. package/dist/services/plugin-executor.js.map +1 -0
  341. package/dist/services/role-definition-service.d.ts +117 -0
  342. package/dist/services/role-definition-service.d.ts.map +1 -0
  343. package/dist/services/role-definition-service.js +289 -0
  344. package/dist/services/role-definition-service.js.map +1 -0
  345. package/dist/services/steward-scheduler.d.ts +336 -0
  346. package/dist/services/steward-scheduler.d.ts.map +1 -0
  347. package/dist/services/steward-scheduler.js +732 -0
  348. package/dist/services/steward-scheduler.js.map +1 -0
  349. package/dist/services/task-assignment-service.d.ts +291 -0
  350. package/dist/services/task-assignment-service.d.ts.map +1 -0
  351. package/dist/services/task-assignment-service.js +454 -0
  352. package/dist/services/task-assignment-service.js.map +1 -0
  353. package/dist/services/worker-task-service.d.ts +202 -0
  354. package/dist/services/worker-task-service.d.ts.map +1 -0
  355. package/dist/services/worker-task-service.js +228 -0
  356. package/dist/services/worker-task-service.js.map +1 -0
  357. package/dist/testing/index.d.ts +13 -0
  358. package/dist/testing/index.d.ts.map +1 -0
  359. package/dist/testing/index.js +17 -0
  360. package/dist/testing/index.js.map +1 -0
  361. package/dist/testing/orchestration-tests.d.ts +62 -0
  362. package/dist/testing/orchestration-tests.d.ts.map +1 -0
  363. package/dist/testing/orchestration-tests.js +1115 -0
  364. package/dist/testing/orchestration-tests.js.map +1 -0
  365. package/dist/testing/test-context.d.ts +171 -0
  366. package/dist/testing/test-context.d.ts.map +1 -0
  367. package/dist/testing/test-context.js +665 -0
  368. package/dist/testing/test-context.js.map +1 -0
  369. package/dist/testing/test-prompts.d.ts +46 -0
  370. package/dist/testing/test-prompts.d.ts.map +1 -0
  371. package/dist/testing/test-prompts.js +140 -0
  372. package/dist/testing/test-prompts.js.map +1 -0
  373. package/dist/testing/test-utils.d.ts +200 -0
  374. package/dist/testing/test-utils.d.ts.map +1 -0
  375. package/dist/testing/test-utils.js +378 -0
  376. package/dist/testing/test-utils.js.map +1 -0
  377. package/dist/types/agent-pool.d.ts +215 -0
  378. package/dist/types/agent-pool.d.ts.map +1 -0
  379. package/dist/types/agent-pool.js +143 -0
  380. package/dist/types/agent-pool.js.map +1 -0
  381. package/dist/types/agent.d.ts +265 -0
  382. package/dist/types/agent.d.ts.map +1 -0
  383. package/dist/types/agent.js +127 -0
  384. package/dist/types/agent.js.map +1 -0
  385. package/dist/types/index.d.ts +11 -0
  386. package/dist/types/index.d.ts.map +1 -0
  387. package/dist/types/index.js +40 -0
  388. package/dist/types/index.js.map +1 -0
  389. package/dist/types/message-types.d.ts +294 -0
  390. package/dist/types/message-types.d.ts.map +1 -0
  391. package/dist/types/message-types.js +354 -0
  392. package/dist/types/message-types.js.map +1 -0
  393. package/dist/types/role-definition.d.ts +272 -0
  394. package/dist/types/role-definition.d.ts.map +1 -0
  395. package/dist/types/role-definition.js +144 -0
  396. package/dist/types/role-definition.js.map +1 -0
  397. package/dist/types/task-meta.d.ts +248 -0
  398. package/dist/types/task-meta.d.ts.map +1 -0
  399. package/dist/types/task-meta.js +213 -0
  400. package/dist/types/task-meta.js.map +1 -0
  401. package/package.json +120 -0
  402. package/web/assets/abap-BrgZPUOV.js +6 -0
  403. package/web/assets/apex-DyP6w7ZV.js +6 -0
  404. package/web/assets/azcli-BaLxmfj-.js +6 -0
  405. package/web/assets/bat-CFOPXBzS.js +6 -0
  406. package/web/assets/bicep-BfEKNvv3.js +7 -0
  407. package/web/assets/cameligo-BFG1Mk7z.js +6 -0
  408. package/web/assets/clojure-DTECt2xU.js +6 -0
  409. package/web/assets/codicon-DCmgc-ay.ttf +0 -0
  410. package/web/assets/coffee-CDGzqUPQ.js +6 -0
  411. package/web/assets/cpp-CLLBncYj.js +6 -0
  412. package/web/assets/csharp-dUCx_-0o.js +6 -0
  413. package/web/assets/csp-5Rap-vPy.js +6 -0
  414. package/web/assets/css-D3h14YRZ.js +8 -0
  415. package/web/assets/cssMode-DMo-5YLA.js +9 -0
  416. package/web/assets/cypher-DrQuvNYM.js +6 -0
  417. package/web/assets/dart-CFKIUWau.js +6 -0
  418. package/web/assets/dockerfile-Zznr-cwX.js +6 -0
  419. package/web/assets/ecl-Ce3n6wWz.js +6 -0
  420. package/web/assets/elixir-deUWdS0T.js +6 -0
  421. package/web/assets/flow9-i9-g7ZhI.js +6 -0
  422. package/web/assets/freemarker2-D4qgkQzN.js +8 -0
  423. package/web/assets/fsharp-CzKuDChf.js +6 -0
  424. package/web/assets/go-Cphgjts3.js +6 -0
  425. package/web/assets/graphql-Cg7bfA9N.js +6 -0
  426. package/web/assets/handlebars-CXFvNjQC.js +6 -0
  427. package/web/assets/hcl-0cvrggvQ.js +6 -0
  428. package/web/assets/html-oyuB_D-B.js +6 -0
  429. package/web/assets/htmlMode-iWuZ24-r.js +9 -0
  430. package/web/assets/index-DqP-_E4F.css +32 -0
  431. package/web/assets/index-R1cylSgw.js +1665 -0
  432. package/web/assets/ini-Drc7WvVn.js +6 -0
  433. package/web/assets/java-B_fMsGYe.js +6 -0
  434. package/web/assets/javascript-CRIkN2Pg.js +6 -0
  435. package/web/assets/jsonMode-DVDkDgex.js +15 -0
  436. package/web/assets/julia-Bqgm2twL.js +6 -0
  437. package/web/assets/kotlin-BSkB5QuD.js +6 -0
  438. package/web/assets/less-BsTHnhdd.js +7 -0
  439. package/web/assets/lexon-YWi4-JPR.js +6 -0
  440. package/web/assets/liquid-CSfldbB5.js +6 -0
  441. package/web/assets/lua-nf6ki56Z.js +6 -0
  442. package/web/assets/m3-Cpb6xl2v.js +6 -0
  443. package/web/assets/markdown-DSZPf7rp.js +6 -0
  444. package/web/assets/mdx-Dd58iymR.js +6 -0
  445. package/web/assets/mips-B_c3zf-v.js +6 -0
  446. package/web/assets/monaco-editor-B4lwqA13.js +751 -0
  447. package/web/assets/monaco-editor-CQpyCxOA.css +1 -0
  448. package/web/assets/msdax-rUNN04Wq.js +6 -0
  449. package/web/assets/mysql-DDwshQtU.js +6 -0
  450. package/web/assets/objective-c-B5zXfXm9.js +6 -0
  451. package/web/assets/pascal-CXOwvkN_.js +6 -0
  452. package/web/assets/pascaligo-Bc-ZgV77.js +6 -0
  453. package/web/assets/perl-CwNk8-XU.js +6 -0
  454. package/web/assets/pgsql-tGk8EFnU.js +6 -0
  455. package/web/assets/php-CpIb_Oan.js +6 -0
  456. package/web/assets/pla-B03wrqEc.js +6 -0
  457. package/web/assets/postiats-BKlk5iyT.js +6 -0
  458. package/web/assets/powerquery-Bhzvs7bI.js +6 -0
  459. package/web/assets/powershell-Dd3NCNK9.js +6 -0
  460. package/web/assets/protobuf-COyEY5Pt.js +7 -0
  461. package/web/assets/pug-BaJupSGV.js +6 -0
  462. package/web/assets/python-XWrMqdhO.js +6 -0
  463. package/web/assets/qsharp-DXyYeYxl.js +6 -0
  464. package/web/assets/r-CdQndTaG.js +6 -0
  465. package/web/assets/razor-DPlhCpIs.js +6 -0
  466. package/web/assets/redis-CVwtpugi.js +6 -0
  467. package/web/assets/redshift-25W9uPmb.js +6 -0
  468. package/web/assets/restructuredtext-DfzH4Xui.js +6 -0
  469. package/web/assets/router-vendor-DHlGizSU.js +41 -0
  470. package/web/assets/ruby-Cp1zYvxS.js +6 -0
  471. package/web/assets/rust-D5C2fndG.js +6 -0
  472. package/web/assets/sb-CDntyWJ8.js +6 -0
  473. package/web/assets/scala-BoFRg7Ot.js +6 -0
  474. package/web/assets/scheme-Bio4gycK.js +6 -0
  475. package/web/assets/scss-4Ik7cdeQ.js +8 -0
  476. package/web/assets/shell-CX-rkNHf.js +6 -0
  477. package/web/assets/solidity-Tw7wswEv.js +6 -0
  478. package/web/assets/sophia-C5WLch3f.js +6 -0
  479. package/web/assets/sparql-DHaeiCBh.js +6 -0
  480. package/web/assets/sql-CCSDG5nI.js +6 -0
  481. package/web/assets/st-pnP8ivHi.js +6 -0
  482. package/web/assets/swift-DwJ7jVG9.js +8 -0
  483. package/web/assets/systemverilog-B9Xyijhd.js +6 -0
  484. package/web/assets/tcl-DnHyzjbg.js +6 -0
  485. package/web/assets/tsMode-BbA1Jbf3.js +16 -0
  486. package/web/assets/twig-CPajHgWi.js +6 -0
  487. package/web/assets/typescript-DcLHYzvH.js +6 -0
  488. package/web/assets/typespec-D-MeaMDU.js +6 -0
  489. package/web/assets/ui-vendor-BSco96uv.js +51 -0
  490. package/web/assets/utils-vendor-DaJ2Dubl.js +911 -0
  491. package/web/assets/vb-DgyLZaXg.js +6 -0
  492. package/web/assets/wgsl-DYQUnd45.js +303 -0
  493. package/web/assets/xml-xKYS3dO6.js +6 -0
  494. package/web/assets/yaml-CNmlXqzH.js +6 -0
  495. package/web/favicon.ico +0 -0
  496. package/web/index.html +22 -0
  497. package/web/logo.png +0 -0
@@ -0,0 +1,1115 @@
1
+ /**
2
+ * Orchestration E2E Test Definitions
3
+ *
4
+ * This module defines the orchestration tests that validate each behavior
5
+ * in the orchestration system. Tests run against an isolated test workspace.
6
+ *
7
+ * Each test supports dual-mode execution:
8
+ * - `mock` (default): Uses mock session manager, simulates agent behavior
9
+ * - `real`: Spawns actual Claude processes via SpawnerService
10
+ *
11
+ * @module
12
+ */
13
+ import { TaskStatus } from '@stoneforge/core';
14
+ import { waitFor, waitForTaskStatus, waitForTaskAssignment, waitForSessionStart, waitForSessionEnd, waitForTaskMeta, waitForGitCommit, pass, fail, sleep, uniqueId, } from './test-utils.js';
15
+ import { createTestWorker, createTestDirector, createTestSteward, createTestTask, } from './test-context.js';
16
+ import { buildTestDirectorPrompt, buildTestStewardPrompt, } from './test-prompts.js';
17
+ // ============================================================================
18
+ // Test 1: Director Creates Tasks
19
+ // ============================================================================
20
+ export const directorCreatesTasksTest = {
21
+ id: 'director-creates-tasks',
22
+ name: 'Director creates tasks when prompted',
23
+ description: 'Send a feature request to director, verify task is created',
24
+ timeout: 120000,
25
+ tags: ['director', 'task'],
26
+ async run(ctx) {
27
+ if (ctx.mode === 'mock') {
28
+ return runDirectorCreatesTasksMock(ctx);
29
+ }
30
+ return runDirectorCreatesTasksReal(ctx);
31
+ },
32
+ };
33
+ async function runDirectorCreatesTasksMock(ctx) {
34
+ // 1. Register a director agent
35
+ ctx.log('Registering director agent...');
36
+ const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
37
+ ctx.log(`Registered director: ${director.id}`);
38
+ // 2. Simulate director creating a task
39
+ ctx.log('Creating task (simulating director behavior)...');
40
+ const task = await createTestTask(ctx, 'Add /health endpoint that returns { status: "ok" }', {
41
+ priority: 5,
42
+ tags: ['test', 'feature', 'health'],
43
+ acceptanceCriteria: 'Endpoint returns { status: "ok" } on GET /health',
44
+ });
45
+ if (!task) {
46
+ return fail('Failed to create task');
47
+ }
48
+ // 3. Verify task was created correctly
49
+ ctx.log(`Task created: ${task.id}`);
50
+ const retrieved = await ctx.api.get(task.id);
51
+ if (!retrieved) {
52
+ return fail('Task not found after creation');
53
+ }
54
+ if (!retrieved.title.toLowerCase().includes('health')) {
55
+ return fail(`Task title doesn't contain 'health': ${retrieved.title}`);
56
+ }
57
+ return pass(`Director created task: "${retrieved.title}"`, {
58
+ taskId: task.id,
59
+ taskTitle: task.title,
60
+ });
61
+ }
62
+ async function runDirectorCreatesTasksReal(ctx) {
63
+ // 1. Register a director agent
64
+ ctx.log('Registering director agent...');
65
+ const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
66
+ ctx.log(`Registered director: ${director.id}`);
67
+ // 2. Start director session with a prompt to create a task
68
+ ctx.log('Starting director session...');
69
+ const prompt = buildTestDirectorPrompt('Create a task titled "Add /health endpoint" with acceptance criteria "Endpoint returns { status: ok } on GET /health"');
70
+ const { session } = await ctx.sessionManager.startSession(director.id, {
71
+ workingDirectory: ctx.tempWorkspace,
72
+ initialPrompt: prompt,
73
+ interactive: false,
74
+ });
75
+ ctx.log(`Director session started: ${session.id}`);
76
+ // 3. Wait for a task with 'health' in the title to appear
77
+ ctx.log('Waiting for director to create task...');
78
+ const task = await waitFor(async () => {
79
+ // List all tasks and look for one containing 'health'
80
+ const tasks = await ctx.stoneforgeApi.list({ type: 'task' });
81
+ const healthTask = tasks.find(t => t.title.toLowerCase().includes('health'));
82
+ return healthTask ?? null;
83
+ }, { timeout: 180000, interval: 3000, description: 'director to create health task' });
84
+ // 4. Wait for session to end
85
+ await waitForSessionEnd(ctx.sessionManager, session.id, { timeout: 60000 }).catch(() => { });
86
+ return pass(`Director created task: "${task.title}"`, {
87
+ taskId: task.id,
88
+ taskTitle: task.title,
89
+ });
90
+ }
91
+ // ============================================================================
92
+ // Test 2: Director Creates Plans
93
+ // ============================================================================
94
+ export const directorCreatesPlansTest = {
95
+ id: 'director-creates-plans',
96
+ name: 'Director creates plans for complex goals',
97
+ description: 'Send a complex goal to director, verify plan with tasks is created',
98
+ timeout: 120000,
99
+ tags: ['director', 'plan'],
100
+ async run(ctx) {
101
+ if (ctx.mode === 'mock') {
102
+ return runDirectorCreatesPlansMock(ctx);
103
+ }
104
+ return runDirectorCreatesPlansReal(ctx);
105
+ },
106
+ };
107
+ async function runDirectorCreatesPlansMock(ctx) {
108
+ // 1. Register a director
109
+ const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
110
+ ctx.log(`Registered director: ${director.id}`);
111
+ // 2. Simulate creating a plan with multiple tasks
112
+ ctx.log('Creating plan (simulating director behavior)...');
113
+ const task1 = await createTestTask(ctx, 'Set up API framework', {
114
+ priority: 5,
115
+ tags: ['test', 'api', 'setup'],
116
+ });
117
+ const task2 = await createTestTask(ctx, 'Implement authentication', {
118
+ priority: 4,
119
+ tags: ['test', 'api', 'auth'],
120
+ });
121
+ const task3 = await createTestTask(ctx, 'Add rate limiting', {
122
+ priority: 3,
123
+ tags: ['test', 'api', 'security'],
124
+ });
125
+ const tasks = [task1, task2, task3];
126
+ if (tasks.length < 2) {
127
+ return fail('Failed to create plan with multiple tasks');
128
+ }
129
+ return pass(`Director created plan with ${tasks.length} tasks`, {
130
+ taskCount: tasks.length,
131
+ tasks: tasks.map(t => ({ id: t.id, title: t.title })),
132
+ });
133
+ }
134
+ async function runDirectorCreatesPlansReal(ctx) {
135
+ // 1. Register a director
136
+ const director = await createTestDirector(ctx, `TestDirector-${uniqueId()}`);
137
+ ctx.log(`Registered director: ${director.id}`);
138
+ // 2. Start director session with a complex goal
139
+ ctx.log('Starting director session...');
140
+ const prompt = buildTestDirectorPrompt('Create a plan for "Build a REST API". Create at least 3 tasks: ' +
141
+ '"Set up API framework", "Implement authentication", and "Add rate limiting".');
142
+ const { session } = await ctx.sessionManager.startSession(director.id, {
143
+ workingDirectory: ctx.tempWorkspace,
144
+ initialPrompt: prompt,
145
+ interactive: false,
146
+ });
147
+ ctx.log(`Director session started: ${session.id}`);
148
+ // 3. Wait for 3+ tasks to exist
149
+ ctx.log('Waiting for director to create plan tasks...');
150
+ const tasks = await waitFor(async () => {
151
+ const allTasks = await ctx.stoneforgeApi.list({ type: 'task' });
152
+ return allTasks.length >= 3 ? allTasks : null;
153
+ }, { timeout: 180000, interval: 3000, description: 'director to create 3+ tasks' });
154
+ // 4. Wait for session to end
155
+ await waitForSessionEnd(ctx.sessionManager, session.id, { timeout: 60000 }).catch(() => { });
156
+ return pass(`Director created plan with ${tasks.length} tasks`, {
157
+ taskCount: tasks.length,
158
+ tasks: tasks.map(t => ({ id: t.id, title: t.title })),
159
+ });
160
+ }
161
+ // ============================================================================
162
+ // Test 3: Daemon Dispatches to Worker
163
+ // ============================================================================
164
+ export const daemonDispatchesWorkerTest = {
165
+ id: 'daemon-dispatches-worker',
166
+ name: 'Daemon dispatches unassigned task to available worker',
167
+ description: 'Create unassigned task, verify daemon assigns to worker',
168
+ timeout: 60000,
169
+ tags: ['daemon', 'dispatch', 'worker'],
170
+ async run(ctx) {
171
+ if (ctx.mode === 'mock') {
172
+ return runDaemonDispatchesWorkerMock(ctx);
173
+ }
174
+ return runDaemonDispatchesWorkerReal(ctx);
175
+ },
176
+ };
177
+ async function runDaemonDispatchesWorkerMock(ctx) {
178
+ // 1. Create an ephemeral worker
179
+ ctx.log('Creating worker agent...');
180
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
181
+ ctx.log(`Registered worker: ${worker.id}`);
182
+ // 2. Create an unassigned task
183
+ ctx.log('Creating unassigned task...');
184
+ const task = await createTestTask(ctx, 'Test task for dispatch', {
185
+ priority: 5,
186
+ tags: ['test', 'dispatch'],
187
+ });
188
+ ctx.log(`Created task: ${task.id}`);
189
+ // 3. Manually trigger daemon poll
190
+ ctx.log('Triggering daemon poll...');
191
+ await ctx.daemon.pollWorkerAvailability();
192
+ // 4. Wait for task to be assigned
193
+ ctx.log('Waiting for task assignment...');
194
+ const assigned = await waitFor(async () => {
195
+ const updated = await ctx.api.get(task.id);
196
+ if (!updated)
197
+ return null;
198
+ return updated.assignee ? updated : null;
199
+ }, { timeout: 30000, interval: 1000, description: 'task assignment' }).catch(() => null);
200
+ if (!assigned) {
201
+ return fail('Daemon did not assign task to worker');
202
+ }
203
+ return pass(`Task assigned to worker`, {
204
+ taskId: task.id,
205
+ assignee: assigned.assignee,
206
+ });
207
+ }
208
+ async function runDaemonDispatchesWorkerReal(ctx) {
209
+ // 1. Create an ephemeral worker
210
+ ctx.log('Creating worker agent...');
211
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
212
+ ctx.log(`Registered worker: ${worker.id}`);
213
+ // 2. Create an unassigned task
214
+ ctx.log('Creating unassigned task...');
215
+ const task = await createTestTask(ctx, 'Test task for dispatch', {
216
+ priority: 5,
217
+ tags: ['test', 'dispatch'],
218
+ });
219
+ ctx.log(`Created task: ${task.id}`);
220
+ // 3. Trigger daemon poll to dispatch the task
221
+ ctx.log('Triggering daemon poll...');
222
+ await ctx.daemon.pollWorkerAvailability();
223
+ // 4. Wait for task to be assigned
224
+ ctx.log('Waiting for task assignment...');
225
+ const assigned = await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
226
+ // 5. Wait for a session to start for this worker
227
+ ctx.log('Waiting for session start...');
228
+ const session = await waitForSessionStart(ctx.sessionManager, worker.id, {
229
+ timeout: 60000,
230
+ }).catch(() => null);
231
+ return pass('Task assigned and session started', {
232
+ taskId: task.id,
233
+ assignee: assigned.assignee,
234
+ sessionStarted: !!session,
235
+ });
236
+ }
237
+ // ============================================================================
238
+ // Test 4: Daemon Respects Dependencies
239
+ // ============================================================================
240
+ export const daemonRespectsDependenciesTest = {
241
+ id: 'daemon-respects-dependencies',
242
+ name: 'Daemon respects task dependencies',
243
+ description: 'Blocked task waits until dependency resolves',
244
+ timeout: 60000,
245
+ tags: ['daemon', 'dependencies'],
246
+ async run(ctx) {
247
+ if (ctx.mode === 'mock') {
248
+ return runDaemonRespectsDependenciesMock(ctx);
249
+ }
250
+ return runDaemonRespectsDependenciesReal(ctx);
251
+ },
252
+ };
253
+ async function runDaemonRespectsDependenciesMock(ctx) {
254
+ // 1. Create worker
255
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
256
+ ctx.log(`Registered worker: ${worker.id}`);
257
+ // 2. Create two tasks where task2 depends on task1
258
+ const task1 = await createTestTask(ctx, 'First task', { priority: 5 });
259
+ const task2 = await createTestTask(ctx, 'Dependent task', { priority: 5 });
260
+ // 3. Add dependency: task2 is blocked by task1
261
+ await ctx.api.addDependency({
262
+ blockedId: task2.id,
263
+ blockerId: task1.id,
264
+ type: 'blocks',
265
+ actor: ctx.systemEntityId,
266
+ });
267
+ ctx.log(`Created dependency: ${task1.id} blocks ${task2.id}`);
268
+ // 4. Poll daemon - should only assign task1, not task2
269
+ await ctx.daemon.pollWorkerAvailability();
270
+ await sleep(1000);
271
+ // 5. Check that task2 is still unassigned
272
+ const task2After = await ctx.api.get(task2.id);
273
+ if (task2After?.assignee) {
274
+ return fail('Daemon assigned blocked task before dependency resolved');
275
+ }
276
+ // 6. Complete task1 (simulate) and stop worker session so it becomes available
277
+ await ctx.api.update(task1.id, { status: TaskStatus.CLOSED });
278
+ const activeSession = ctx.sessionManager.getActiveSession(worker.id);
279
+ if (activeSession) {
280
+ await ctx.sessionManager.stopSession(activeSession.id, { graceful: false });
281
+ }
282
+ ctx.log('Completed task1 and freed worker');
283
+ // 7. Now poll again - task2 should be assignable
284
+ await ctx.daemon.pollWorkerAvailability();
285
+ const task2Final = await waitFor(async () => {
286
+ const updated = await ctx.api.get(task2.id);
287
+ return updated?.assignee ? updated : null;
288
+ }, { timeout: 10000, interval: 1000, description: 'blocked task assignment' }).catch(() => null);
289
+ if (!task2Final?.assignee) {
290
+ return fail('Daemon did not assign task after dependency resolved');
291
+ }
292
+ return pass('Daemon correctly respected dependencies', {
293
+ task1Id: task1.id,
294
+ task2Id: task2.id,
295
+ });
296
+ }
297
+ async function runDaemonRespectsDependenciesReal(ctx) {
298
+ // 1. Create worker
299
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
300
+ ctx.log(`Registered worker: ${worker.id}`);
301
+ // 2. Create two tasks where task2 depends on task1
302
+ const task1 = await createTestTask(ctx, 'First task - create hello.txt', { priority: 5 });
303
+ const task2 = await createTestTask(ctx, 'Dependent task - create world.txt', { priority: 5 });
304
+ // 3. Add dependency: task2 is blocked by task1
305
+ await ctx.api.addDependency({
306
+ blockedId: task2.id,
307
+ blockerId: task1.id,
308
+ type: 'blocks',
309
+ actor: ctx.systemEntityId,
310
+ });
311
+ ctx.log(`Created dependency: ${task1.id} blocks ${task2.id}`);
312
+ // 4. Poll daemon — should only assign task1
313
+ await ctx.daemon.pollWorkerAvailability();
314
+ await sleep(3000);
315
+ // 5. Verify task2 is still unassigned
316
+ const task2After = await ctx.api.get(task2.id);
317
+ if (task2After?.assignee) {
318
+ return fail('Daemon assigned blocked task before dependency resolved');
319
+ }
320
+ ctx.log('Verified: blocked task is not assigned');
321
+ // 6. Close task1 and free the worker session to unblock task2
322
+ await ctx.api.update(task1.id, { status: TaskStatus.CLOSED });
323
+ const activeSession = ctx.sessionManager.getActiveSession(worker.id);
324
+ if (activeSession) {
325
+ await ctx.sessionManager.stopSession(activeSession.id, { graceful: false });
326
+ }
327
+ ctx.log('Completed task1 and freed worker');
328
+ // 7. Poll again — task2 should now be assignable
329
+ await ctx.daemon.pollWorkerAvailability();
330
+ const task2Final = await waitForTaskAssignment(ctx.api, task2.id, { timeout: 120000 })
331
+ .catch(() => null);
332
+ if (!task2Final?.assignee) {
333
+ return fail('Daemon did not assign task after dependency resolved');
334
+ }
335
+ return pass('Daemon correctly respected dependencies', {
336
+ task1Id: task1.id,
337
+ task2Id: task2.id,
338
+ });
339
+ }
340
+ // ============================================================================
341
+ // Test 5: Worker Uses Worktree
342
+ // ============================================================================
343
+ export const workerUsesWorktreeTest = {
344
+ id: 'worker-uses-worktree',
345
+ name: 'Worker operates in isolated worktree',
346
+ description: 'Verify worker is spawned in a git worktree directory',
347
+ timeout: 60000,
348
+ tags: ['worker', 'worktree', 'git'],
349
+ async run(ctx) {
350
+ if (ctx.mode === 'mock') {
351
+ return runWorkerUsesWorktreeMock(ctx);
352
+ }
353
+ return runWorkerUsesWorktreeReal(ctx);
354
+ },
355
+ };
356
+ async function runWorkerUsesWorktreeMock(ctx) {
357
+ // 1. Create worker and task
358
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
359
+ const task = await createTestTask(ctx, 'Test worktree usage', { priority: 5 });
360
+ // 2. Create worktree for the task
361
+ ctx.log('Creating worktree...');
362
+ const worktreeResult = await ctx.worktreeManager.createWorktree({
363
+ agentName: worker.name,
364
+ taskId: task.id,
365
+ taskTitle: task.title,
366
+ });
367
+ ctx.log(`Worktree created at: ${worktreeResult.path}`);
368
+ // 3. Verify worktree exists
369
+ const exists = await ctx.worktreeManager.worktreeExists(worktreeResult.path);
370
+ if (!exists) {
371
+ return fail(`Worktree does not exist at: ${worktreeResult.path}`);
372
+ }
373
+ // 4. Verify it's a valid git worktree
374
+ const worktreeInfo = await ctx.worktreeManager.getWorktree(worktreeResult.path);
375
+ if (!worktreeInfo) {
376
+ return fail('Failed to get worktree info');
377
+ }
378
+ // 5. Verify branch naming convention
379
+ const expectedBranchPattern = /^agent\/.+\/.+/;
380
+ if (!expectedBranchPattern.test(worktreeResult.branch)) {
381
+ return fail(`Branch name doesn't match pattern: ${worktreeResult.branch}`);
382
+ }
383
+ // 6. Verify path is in worktrees directory
384
+ if (!worktreeResult.path.includes('.worktrees')) {
385
+ return fail(`Worktree path not in .worktrees directory: ${worktreeResult.path}`);
386
+ }
387
+ return pass(`Worker running in worktree: ${worktreeResult.path}`, {
388
+ worktreePath: worktreeResult.path,
389
+ branch: worktreeResult.branch,
390
+ isGitWorktree: true,
391
+ });
392
+ }
393
+ async function runWorkerUsesWorktreeReal(ctx) {
394
+ // 1. Create worker and task
395
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
396
+ const task = await createTestTask(ctx, 'Test worktree usage', { priority: 5 });
397
+ // 2. Let daemon dispatch the task (creates worktree automatically)
398
+ ctx.log('Triggering daemon poll for dispatch...');
399
+ await ctx.daemon.pollWorkerAvailability();
400
+ // 3. Wait for task to be assigned
401
+ await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
402
+ // 4. Wait for session to start
403
+ const session = await waitForSessionStart(ctx.sessionManager, worker.id, { timeout: 60000 });
404
+ // 5. Verify session working directory is in .worktrees
405
+ if (!session.workingDirectory.includes('.worktrees')) {
406
+ return fail(`Session working directory not in .worktrees: ${session.workingDirectory}`);
407
+ }
408
+ // 6. Verify worktree branch pattern
409
+ if (session.worktree) {
410
+ const worktreeInfo = await ctx.worktreeManager.getWorktree(session.worktree);
411
+ if (worktreeInfo) {
412
+ const expectedBranchPattern = /^agent\/.+\/.+/;
413
+ if (!expectedBranchPattern.test(worktreeInfo.branch)) {
414
+ return fail(`Branch name doesn't match pattern: ${worktreeInfo.branch}`);
415
+ }
416
+ }
417
+ }
418
+ return pass(`Worker running in worktree: ${session.workingDirectory}`, {
419
+ worktreePath: session.workingDirectory,
420
+ worktree: session.worktree,
421
+ sessionId: session.id,
422
+ });
423
+ }
424
+ // ============================================================================
425
+ // Test 6: Worker Commits Work
426
+ // ============================================================================
427
+ export const workerCommitsWorkTest = {
428
+ id: 'worker-commits-work',
429
+ name: 'Worker makes commits in worktree branch',
430
+ description: 'Verify worker can commit changes to its worktree branch',
431
+ timeout: 60000,
432
+ tags: ['worker', 'git', 'commit'],
433
+ async run(ctx) {
434
+ if (ctx.mode === 'mock') {
435
+ return runWorkerCommitsWorkMock(ctx);
436
+ }
437
+ return runWorkerCommitsWorkReal(ctx);
438
+ },
439
+ };
440
+ async function runWorkerCommitsWorkMock(ctx) {
441
+ const { execSync } = await import('node:child_process');
442
+ const { writeFile } = await import('node:fs/promises');
443
+ const { join } = await import('node:path');
444
+ // 1. Create worker and worktree
445
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
446
+ const task = await createTestTask(ctx, 'Test commits', { priority: 5 });
447
+ const worktreeResult = await ctx.worktreeManager.createWorktree({
448
+ agentName: worker.name,
449
+ taskId: task.id,
450
+ taskTitle: task.title,
451
+ });
452
+ ctx.log(`Working in worktree: ${worktreeResult.path}`);
453
+ // 2. Make a change in the worktree
454
+ const testFilePath = join(worktreeResult.path, 'test-file.txt');
455
+ await writeFile(testFilePath, 'Test content from worker\n');
456
+ ctx.log('Created test file');
457
+ // 3. Commit the change
458
+ try {
459
+ execSync('git add -A && git commit -m "Test commit from worker"', {
460
+ cwd: worktreeResult.path,
461
+ stdio: 'pipe',
462
+ });
463
+ ctx.log('Committed changes');
464
+ }
465
+ catch (error) {
466
+ return fail(`Failed to commit: ${error instanceof Error ? error.message : String(error)}`);
467
+ }
468
+ // 4. Verify commit exists
469
+ try {
470
+ const log = execSync('git log --oneline -1', {
471
+ cwd: worktreeResult.path,
472
+ encoding: 'utf8',
473
+ });
474
+ if (!log.includes('Test commit from worker')) {
475
+ return fail(`Commit message not found in log: ${log}`);
476
+ }
477
+ ctx.log(`Commit verified: ${log.trim()}`);
478
+ }
479
+ catch (error) {
480
+ return fail(`Failed to verify commit: ${error instanceof Error ? error.message : String(error)}`);
481
+ }
482
+ return pass('Worker successfully committed changes', {
483
+ worktreePath: worktreeResult.path,
484
+ branch: worktreeResult.branch,
485
+ });
486
+ }
487
+ async function runWorkerCommitsWorkReal(ctx) {
488
+ // 1. Create worker and task that requires creating a file
489
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
490
+ const task = await createTestTask(ctx, 'Create file test-output.txt containing "hello world"', {
491
+ priority: 5,
492
+ tags: ['test', 'commit'],
493
+ acceptanceCriteria: 'File test-output.txt exists and contains "hello world"',
494
+ });
495
+ // 2. Let daemon dispatch
496
+ ctx.log('Triggering daemon poll for dispatch...');
497
+ await ctx.daemon.pollWorkerAvailability();
498
+ // 3. Wait for assignment and session
499
+ await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
500
+ const session = await waitForSessionStart(ctx.sessionManager, worker.id, { timeout: 60000 });
501
+ const worktreePath = session.workingDirectory;
502
+ ctx.log(`Worker running in: ${worktreePath}`);
503
+ // 4. Wait for a git commit in the worktree
504
+ ctx.log('Waiting for worker to commit...');
505
+ const commitHash = await waitForGitCommit(worktreePath, { timeout: 240000 });
506
+ return pass(`Worker committed changes: ${commitHash}`, {
507
+ worktreePath,
508
+ commitHash,
509
+ sessionId: session.id,
510
+ });
511
+ }
512
+ // ============================================================================
513
+ // Test 7: Worker Creates Merge Request
514
+ // ============================================================================
515
+ export const workerCreatesMergeRequestTest = {
516
+ id: 'worker-creates-merge-request',
517
+ name: 'Worker creates merge request on task completion',
518
+ description: 'Worker creates PR/MR when finishing a task',
519
+ timeout: 90000,
520
+ tags: ['worker', 'merge-request', 'git'],
521
+ async run(ctx) {
522
+ if (ctx.mode === 'mock') {
523
+ return runWorkerCreatesMergeRequestMock(ctx);
524
+ }
525
+ return runWorkerCreatesMergeRequestReal(ctx);
526
+ },
527
+ };
528
+ async function runWorkerCreatesMergeRequestMock(ctx) {
529
+ // 1. Create worker and task
530
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
531
+ const task = await createTestTask(ctx, 'Test merge request creation', { priority: 5 });
532
+ // 2. Create worktree and make changes
533
+ const worktreeResult = await ctx.worktreeManager.createWorktree({
534
+ agentName: worker.name,
535
+ taskId: task.id,
536
+ taskTitle: task.title,
537
+ });
538
+ // 3. Simulate worker completing task by updating task metadata with MR info
539
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
540
+ branch: worktreeResult.branch,
541
+ worktree: worktreeResult.path,
542
+ mergeRequestId: 1,
543
+ mergeRequestUrl: `https://github.com/test/repo/pull/1`,
544
+ mergeStatus: 'pending',
545
+ });
546
+ ctx.log('Updated task with MR info');
547
+ // 4. Verify MR info is stored
548
+ const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
549
+ if (!taskMeta?.mergeRequestId) {
550
+ return fail('PR number not set in task metadata');
551
+ }
552
+ if (!taskMeta?.mergeRequestUrl) {
553
+ return fail('PR URL not set in task metadata');
554
+ }
555
+ return pass(`Merge request created: PR #${taskMeta.mergeRequestId}`, {
556
+ mergeRequestId: taskMeta.mergeRequestId,
557
+ mergeRequestUrl: taskMeta.mergeRequestUrl,
558
+ branch: taskMeta.branch,
559
+ });
560
+ }
561
+ async function runWorkerCreatesMergeRequestReal(ctx) {
562
+ const { execSync } = await import('node:child_process');
563
+ // 1. Create worker (needed for dispatch) and task
564
+ await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
565
+ const task = await createTestTask(ctx, 'Create a new file and push branch', {
566
+ priority: 5,
567
+ tags: ['test', 'merge-request'],
568
+ acceptanceCriteria: 'Branch pushed to remote with changes',
569
+ });
570
+ // 2. Let daemon dispatch
571
+ await ctx.daemon.pollWorkerAvailability();
572
+ await waitForTaskAssignment(ctx.api, task.id, { timeout: 120000 });
573
+ // 3. Wait for task metadata to have a branch set
574
+ ctx.log('Waiting for worker to push branch...');
575
+ const meta = await waitForTaskMeta(ctx.api, task.id, (m) => !!m.branch, { timeout: 240000 });
576
+ // 4. Verify the branch was pushed to the bare remote
577
+ try {
578
+ const branches = execSync('git branch -r', {
579
+ cwd: ctx.tempWorkspace,
580
+ encoding: 'utf8',
581
+ });
582
+ const hasBranch = branches.includes(meta.branch);
583
+ ctx.log(`Remote branches include worker branch: ${hasBranch}`);
584
+ }
585
+ catch {
586
+ ctx.log('Could not check remote branches');
587
+ }
588
+ return pass(`Worker set branch metadata: ${meta.branch}`, {
589
+ branch: meta.branch,
590
+ taskId: task.id,
591
+ });
592
+ }
593
+ // ============================================================================
594
+ // Test 8: Worker Marks Task Complete
595
+ // ============================================================================
596
+ export const workerMarksTaskCompleteTest = {
597
+ id: 'worker-marks-task-complete',
598
+ name: 'Worker marks task as complete',
599
+ description: 'Task status changes to closed when worker finishes',
600
+ timeout: 30000,
601
+ tags: ['worker', 'task', 'completion'],
602
+ async run(ctx) {
603
+ if (ctx.mode === 'mock') {
604
+ return runWorkerMarksTaskCompleteMock(ctx);
605
+ }
606
+ return runWorkerMarksTaskCompleteReal(ctx);
607
+ },
608
+ };
609
+ async function runWorkerMarksTaskCompleteMock(ctx) {
610
+ // 1. Create worker and task
611
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
612
+ const task = await createTestTask(ctx, 'Test task completion', { priority: 5 });
613
+ // 2. Assign task to worker
614
+ await ctx.api.assignTaskToAgent(task.id, worker.id, {
615
+ markAsStarted: true,
616
+ });
617
+ ctx.log('Assigned task to worker');
618
+ // 3. Verify task is in progress
619
+ const taskInProgress = await ctx.api.get(task.id);
620
+ if (taskInProgress?.status !== TaskStatus.IN_PROGRESS) {
621
+ return fail(`Expected task in_progress, got: ${taskInProgress?.status}`);
622
+ }
623
+ // 4. Complete the task (simulating worker completion)
624
+ await ctx.taskAssignment.completeTask(task.id, {
625
+ summary: 'Task completed successfully',
626
+ });
627
+ ctx.log('Completed task');
628
+ // 5. Verify task is closed
629
+ const taskClosed = await ctx.api.get(task.id);
630
+ if (taskClosed?.status !== TaskStatus.CLOSED) {
631
+ return fail(`Expected task closed, got: ${taskClosed?.status}`);
632
+ }
633
+ return pass(`Task status is '${taskClosed.status}'`, {
634
+ taskId: task.id,
635
+ status: taskClosed.status,
636
+ });
637
+ }
638
+ async function runWorkerMarksTaskCompleteReal(ctx) {
639
+ // 1. Create worker (needed for dispatch) and a simple task
640
+ await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
641
+ const task = await createTestTask(ctx, 'Add a comment to src/index.ts', {
642
+ priority: 5,
643
+ tags: ['test', 'simple'],
644
+ acceptanceCriteria: 'A comment is added to src/index.ts',
645
+ });
646
+ // 2. Let daemon dispatch
647
+ ctx.log('Triggering daemon poll...');
648
+ await ctx.daemon.pollWorkerAvailability();
649
+ // 3. Wait for task to reach CLOSED status
650
+ ctx.log('Waiting for worker to complete task...');
651
+ const closedTask = await waitForTaskStatus(ctx.api, task.id, TaskStatus.CLOSED, { timeout: 240000 });
652
+ return pass(`Task status is '${closedTask.status}'`, {
653
+ taskId: task.id,
654
+ status: closedTask.status,
655
+ });
656
+ }
657
+ // ============================================================================
658
+ // Test 9: Worker Handoff on Context Fill
659
+ // ============================================================================
660
+ export const workerHandoffOnContextFillTest = {
661
+ id: 'worker-handoff-context',
662
+ name: 'Worker triggers handoff before context exhaustion',
663
+ description: 'Worker creates handoff task when approaching context limits',
664
+ timeout: 60000,
665
+ tags: ['worker', 'handoff', 'context'],
666
+ async run(ctx) {
667
+ if (ctx.mode === 'mock') {
668
+ return runWorkerHandoffOnContextFillMock(ctx);
669
+ }
670
+ return runWorkerHandoffOnContextFillReal(ctx);
671
+ },
672
+ };
673
+ async function runWorkerHandoffOnContextFillMock(ctx) {
674
+ // 1. Create worker and task
675
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
676
+ const task = await createTestTask(ctx, 'Long running task', { priority: 5 });
677
+ // 2. Assign task and create worktree
678
+ const worktreeResult = await ctx.worktreeManager.createWorktree({
679
+ agentName: worker.name,
680
+ taskId: task.id,
681
+ taskTitle: task.title,
682
+ });
683
+ await ctx.api.assignTaskToAgent(task.id, worker.id, {
684
+ branch: worktreeResult.branch,
685
+ worktree: worktreeResult.path,
686
+ markAsStarted: true,
687
+ });
688
+ // 3. Simulate handoff by updating task metadata
689
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
690
+ handoffBranch: worktreeResult.branch,
691
+ handoffWorktree: worktreeResult.path,
692
+ handoffFrom: worker.id,
693
+ handoffAt: new Date().toISOString(),
694
+ handoffHistory: [{
695
+ sessionId: 'sim-session',
696
+ message: 'Context limit approaching, handing off to continue work',
697
+ branch: worktreeResult.branch,
698
+ worktree: worktreeResult.path,
699
+ handoffAt: new Date().toISOString(),
700
+ }],
701
+ });
702
+ // 4. Unassign from current worker (simulating handoff)
703
+ await ctx.api.update(task.id, {
704
+ assignee: undefined,
705
+ status: TaskStatus.OPEN,
706
+ });
707
+ ctx.log('Created handoff');
708
+ // 5. Verify handoff metadata exists
709
+ const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
710
+ if (!taskMeta?.handoffBranch) {
711
+ return fail('Handoff branch not set');
712
+ }
713
+ if (!taskMeta?.handoffHistory || taskMeta.handoffHistory.length === 0) {
714
+ return fail('Handoff history not set');
715
+ }
716
+ return pass('Worker created handoff successfully', {
717
+ handoffBranch: taskMeta.handoffBranch,
718
+ handoffFrom: taskMeta.handoffFrom,
719
+ handoffHistory: taskMeta.handoffHistory,
720
+ });
721
+ }
722
+ async function runWorkerHandoffOnContextFillReal(ctx) {
723
+ // 1. Create worker and task
724
+ const worker = await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
725
+ const task = await createTestTask(ctx, 'Hand off this task', {
726
+ priority: 5,
727
+ tags: ['test', 'handoff'],
728
+ acceptanceCriteria: 'Task is handed off with a note',
729
+ });
730
+ // 2. Assign task to worker (needed so handoff command can find the assignment)
731
+ await ctx.api.assignTaskToAgent(task.id, worker.id, {
732
+ markAsStarted: true,
733
+ });
734
+ // 3. Start worker session with ultra-direct handoff prompt (like steward tests)
735
+ const dbFlag = ctx.dbPath ? ` --db "${ctx.dbPath}"` : '';
736
+ const command = `sf task handoff ${task.id} --message "Handing off to next worker"${dbFlag}`;
737
+ const prompt = `You are a test agent. Execute this one command immediately and stop.
738
+
739
+ COMMAND TO RUN:
740
+ \`\`\`
741
+ ${command}
742
+ \`\`\`
743
+
744
+ RULES:
745
+ - Run ONLY the command above. Nothing else.
746
+ - The \`el\` command is already on PATH. Do not install or locate it.
747
+ - Do not explore the codebase, read files, or run other commands first.
748
+ - Do not ask questions. Just run the command.
749
+ - After running the command, stop immediately.`;
750
+ const { session } = await ctx.sessionManager.startSession(worker.id, {
751
+ workingDirectory: ctx.tempWorkspace,
752
+ initialPrompt: prompt,
753
+ });
754
+ ctx.log(`Worker session started: ${session.id}`);
755
+ // 4. Wait for handoff metadata to appear
756
+ ctx.log('Waiting for handoff...');
757
+ const meta = await waitForTaskMeta(ctx.api, task.id, (m) => Array.isArray(m.handoffHistory) && m.handoffHistory.length > 0, { timeout: 240000 });
758
+ // 5. Verify task was reopened
759
+ const reopened = await ctx.api.get(task.id);
760
+ if (reopened?.status !== TaskStatus.OPEN) {
761
+ ctx.log(`Task status after handoff: ${reopened?.status} (expected OPEN)`);
762
+ }
763
+ return pass('Worker created handoff successfully', {
764
+ handoffHistory: meta.handoffHistory,
765
+ handoffBranch: meta.handoffBranch,
766
+ taskStatus: reopened?.status,
767
+ });
768
+ }
769
+ // ============================================================================
770
+ // Test 10: Daemon Spawns Steward for MR
771
+ // ============================================================================
772
+ export const daemonSpawnsStewardForMRTest = {
773
+ id: 'daemon-spawns-steward-mr',
774
+ name: 'Daemon spawns steward for merge request',
775
+ description: 'Merge request triggers steward spawn for review',
776
+ timeout: 60000,
777
+ tags: ['daemon', 'steward', 'merge-request'],
778
+ async run(ctx) {
779
+ if (ctx.mode === 'mock') {
780
+ return runDaemonSpawnsStewardForMRMock(ctx);
781
+ }
782
+ return runDaemonSpawnsStewardForMRReal(ctx);
783
+ },
784
+ };
785
+ async function runDaemonSpawnsStewardForMRMock(ctx) {
786
+ // 1. Create a merge steward
787
+ const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
788
+ focus: 'merge',
789
+ triggers: [{ type: 'event', event: 'merge_request_created' }],
790
+ });
791
+ ctx.log(`Registered merge steward: ${steward.id}`);
792
+ // 2. Register steward with scheduler
793
+ await ctx.stewardScheduler.registerSteward(steward.id);
794
+ // 3. Start the scheduler
795
+ await ctx.stewardScheduler.start();
796
+ // 4. Create a task with MR info
797
+ const task = await createTestTask(ctx, 'Task with merge request', { priority: 5 });
798
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
799
+ mergeRequestId: 42,
800
+ mergeRequestUrl: 'https://github.com/test/repo/pull/42',
801
+ mergeStatus: 'pending',
802
+ });
803
+ // 5. Publish merge request event
804
+ const triggered = await ctx.stewardScheduler.publishEvent('merge_request_created', {
805
+ taskId: task.id,
806
+ mergeRequestId: 42,
807
+ });
808
+ ctx.log(`Published MR event, triggered ${triggered} steward(s)`);
809
+ // 6. Verify steward was triggered
810
+ if (triggered === 0) {
811
+ return fail('No steward was triggered for merge request');
812
+ }
813
+ // 7. Check steward execution history
814
+ const history = ctx.stewardScheduler.getExecutionHistory({
815
+ stewardId: steward.id,
816
+ limit: 1,
817
+ });
818
+ if (history.length === 0) {
819
+ return fail('Steward execution not recorded');
820
+ }
821
+ return pass('Steward triggered for merge request', {
822
+ stewardId: steward.id,
823
+ triggerCount: triggered,
824
+ });
825
+ }
826
+ async function runDaemonSpawnsStewardForMRReal(ctx) {
827
+ // 1. Create a merge steward
828
+ const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
829
+ focus: 'merge',
830
+ triggers: [{ type: 'event', event: 'merge_request_created' }],
831
+ });
832
+ ctx.log(`Registered merge steward: ${steward.id}`);
833
+ // 2. Register steward with scheduler
834
+ await ctx.stewardScheduler.registerSteward(steward.id);
835
+ await ctx.stewardScheduler.start();
836
+ // 3. Create a task with MR info
837
+ const task = await createTestTask(ctx, 'Task with merge request', { priority: 5 });
838
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
839
+ mergeRequestId: 42,
840
+ mergeRequestUrl: 'https://github.com/test/repo/pull/42',
841
+ mergeStatus: 'pending',
842
+ });
843
+ // 4. Publish merge request event
844
+ const triggered = await ctx.stewardScheduler.publishEvent('merge_request_created', {
845
+ taskId: task.id,
846
+ mergeRequestId: 42,
847
+ });
848
+ if (triggered === 0) {
849
+ return fail('No steward was triggered for merge request');
850
+ }
851
+ // 5. Wait for steward session to start
852
+ ctx.log('Waiting for steward session to start...');
853
+ const session = await waitForSessionStart(ctx.sessionManager, steward.id, { timeout: 120000 }).catch(() => null);
854
+ return pass('Steward triggered for merge request', {
855
+ stewardId: steward.id,
856
+ triggerCount: triggered,
857
+ sessionStarted: !!session,
858
+ });
859
+ }
860
+ // ============================================================================
861
+ // Test 11: Steward Merges Passing MR
862
+ // ============================================================================
863
+ export const stewardMergesPassingMRTest = {
864
+ id: 'steward-merges-passing',
865
+ name: 'Steward reviews and merges passing PR',
866
+ description: 'Steward merges PR when tests pass',
867
+ timeout: 60000,
868
+ tags: ['steward', 'merge', 'tests'],
869
+ async run(ctx) {
870
+ if (ctx.mode === 'mock') {
871
+ return runStewardMergesPassingMRMock(ctx);
872
+ }
873
+ return runStewardMergesPassingMRReal(ctx);
874
+ },
875
+ };
876
+ async function runStewardMergesPassingMRMock(ctx) {
877
+ // 1. Create task with passing MR status
878
+ const task = await createTestTask(ctx, 'Task with passing tests', { priority: 5 });
879
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
880
+ mergeRequestId: 100,
881
+ mergeRequestUrl: 'https://github.com/test/repo/pull/100',
882
+ mergeStatus: 'testing',
883
+ testRunCount: 1,
884
+ lastTestResult: {
885
+ passed: true,
886
+ totalTests: 10,
887
+ passedTests: 10,
888
+ failedTests: 0,
889
+ durationMs: 5000,
890
+ completedAt: new Date().toISOString(),
891
+ },
892
+ });
893
+ ctx.log('Created task with passing test results');
894
+ // 2. Simulate steward merging the MR
895
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
896
+ mergeStatus: 'merged',
897
+ completedAt: new Date().toISOString(),
898
+ });
899
+ // 3. Close the task
900
+ await ctx.api.update(task.id, { status: TaskStatus.CLOSED });
901
+ // 4. Verify merge status
902
+ const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
903
+ if (taskMeta?.mergeStatus !== 'merged') {
904
+ return fail(`Expected merge status 'merged', got: ${taskMeta?.mergeStatus}`);
905
+ }
906
+ const updatedTask = await ctx.api.get(task.id);
907
+ if (updatedTask?.status !== TaskStatus.CLOSED) {
908
+ return fail(`Expected task closed, got: ${updatedTask?.status}`);
909
+ }
910
+ return pass('Steward merged passing PR', {
911
+ taskId: task.id,
912
+ mergeStatus: taskMeta.mergeStatus,
913
+ taskStatus: updatedTask.status,
914
+ });
915
+ }
916
+ async function runStewardMergesPassingMRReal(ctx) {
917
+ // 1. Create steward
918
+ const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
919
+ focus: 'merge',
920
+ });
921
+ // 2. Create task with passing test metadata
922
+ const task = await createTestTask(ctx, 'Task with passing tests', { priority: 5 });
923
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
924
+ mergeRequestId: 100,
925
+ mergeRequestUrl: 'https://github.com/test/repo/pull/100',
926
+ mergeStatus: 'testing',
927
+ testRunCount: 1,
928
+ lastTestResult: {
929
+ passed: true,
930
+ totalTests: 10,
931
+ passedTests: 10,
932
+ failedTests: 0,
933
+ durationMs: 5000,
934
+ completedAt: new Date().toISOString(),
935
+ },
936
+ });
937
+ // 3. Start steward session with review prompt
938
+ const prompt = buildTestStewardPrompt('merge', task.id, { dbPath: ctx.dbPath });
939
+ const { session } = await ctx.sessionManager.startSession(steward.id, {
940
+ workingDirectory: ctx.tempWorkspace,
941
+ initialPrompt: prompt,
942
+ });
943
+ ctx.log(`Steward session started: ${session.id}`);
944
+ // 4. Wait for merge status to become 'merged'
945
+ ctx.log('Waiting for steward to merge...');
946
+ const meta = await waitForTaskMeta(ctx.api, task.id, (m) => m.mergeStatus === 'merged', { timeout: 240000 });
947
+ return pass('Steward merged passing PR', {
948
+ taskId: task.id,
949
+ mergeStatus: meta.mergeStatus,
950
+ });
951
+ }
952
+ // ============================================================================
953
+ // Test 12: Steward Handoff Failing MR
954
+ // ============================================================================
955
+ export const stewardHandoffFailingMRTest = {
956
+ id: 'steward-handoff-failing',
957
+ name: 'Steward creates handoff for failing PR',
958
+ description: 'Steward hands off to worker when tests fail',
959
+ timeout: 60000,
960
+ tags: ['steward', 'handoff', 'tests'],
961
+ async run(ctx) {
962
+ if (ctx.mode === 'mock') {
963
+ return runStewardHandoffFailingMRMock(ctx);
964
+ }
965
+ return runStewardHandoffFailingMRReal(ctx);
966
+ },
967
+ };
968
+ async function runStewardHandoffFailingMRMock(ctx) {
969
+ // 1. Create worker for handoff (needed for reassignment)
970
+ await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
971
+ // 2. Create task with failing MR status
972
+ const task = await createTestTask(ctx, 'Task with failing tests', {
973
+ priority: 5,
974
+ tags: ['test', 'needs-fix'],
975
+ });
976
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
977
+ mergeRequestId: 101,
978
+ mergeRequestUrl: 'https://github.com/test/repo/pull/101',
979
+ mergeStatus: 'testing',
980
+ testRunCount: 1,
981
+ lastTestResult: {
982
+ passed: false,
983
+ totalTests: 10,
984
+ passedTests: 7,
985
+ failedTests: 3,
986
+ durationMs: 5000,
987
+ completedAt: new Date().toISOString(),
988
+ errorMessage: 'Test 1 failed, Test 2 failed, Test 3 failed',
989
+ },
990
+ });
991
+ ctx.log('Created task with failing test results');
992
+ // 3. Simulate steward marking as test_failed and creating handoff
993
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
994
+ mergeStatus: 'test_failed',
995
+ mergeFailureReason: '3 tests failed: Test 1, Test 2, Test 3',
996
+ handoffHistory: [{
997
+ sessionId: 'steward-session',
998
+ message: 'Tests failed, needs worker to fix issues',
999
+ handoffAt: new Date().toISOString(),
1000
+ }],
1001
+ });
1002
+ // 4. Reopen task for assignment
1003
+ await ctx.api.update(task.id, {
1004
+ status: TaskStatus.OPEN,
1005
+ assignee: undefined,
1006
+ });
1007
+ ctx.log('Created handoff for failing tests');
1008
+ // 5. Verify handoff was created
1009
+ const taskMeta = await ctx.api.getTaskOrchestratorMeta(task.id);
1010
+ if (taskMeta?.mergeStatus !== 'test_failed') {
1011
+ return fail(`Expected merge status 'test_failed', got: ${taskMeta?.mergeStatus}`);
1012
+ }
1013
+ if (!taskMeta?.handoffHistory || taskMeta.handoffHistory.length === 0) {
1014
+ return fail('Handoff history not set');
1015
+ }
1016
+ const updatedTask = await ctx.api.get(task.id);
1017
+ if (updatedTask?.status !== TaskStatus.OPEN) {
1018
+ return fail(`Expected task open for reassignment, got: ${updatedTask?.status}`);
1019
+ }
1020
+ return pass('Steward created handoff for failing PR', {
1021
+ taskId: task.id,
1022
+ mergeStatus: taskMeta.mergeStatus,
1023
+ handoffHistory: taskMeta.handoffHistory,
1024
+ taskStatus: updatedTask.status,
1025
+ });
1026
+ }
1027
+ async function runStewardHandoffFailingMRReal(ctx) {
1028
+ // 1. Create steward and worker (worker needed for reassignment)
1029
+ const steward = await createTestSteward(ctx, `MergeSteward-${uniqueId()}`, {
1030
+ focus: 'merge',
1031
+ });
1032
+ await createTestWorker(ctx, `TestWorker-${uniqueId()}`);
1033
+ // 2. Create task with failing test metadata
1034
+ const task = await createTestTask(ctx, 'Task with failing tests', {
1035
+ priority: 5,
1036
+ tags: ['test', 'needs-fix'],
1037
+ });
1038
+ await ctx.api.updateTaskOrchestratorMeta(task.id, {
1039
+ mergeRequestId: 101,
1040
+ mergeRequestUrl: 'https://github.com/test/repo/pull/101',
1041
+ mergeStatus: 'testing',
1042
+ testRunCount: 1,
1043
+ lastTestResult: {
1044
+ passed: false,
1045
+ totalTests: 10,
1046
+ passedTests: 7,
1047
+ failedTests: 3,
1048
+ durationMs: 5000,
1049
+ completedAt: new Date().toISOString(),
1050
+ errorMessage: 'Test 1 failed, Test 2 failed, Test 3 failed',
1051
+ },
1052
+ });
1053
+ // 3. Start steward session with reject/handoff prompt
1054
+ const prompt = buildTestStewardPrompt('reject', task.id, { dbPath: ctx.dbPath });
1055
+ const { session } = await ctx.sessionManager.startSession(steward.id, {
1056
+ workingDirectory: ctx.tempWorkspace,
1057
+ initialPrompt: prompt,
1058
+ });
1059
+ ctx.log(`Steward session started: ${session.id}`);
1060
+ // 4. Wait for merge status to become 'test_failed'
1061
+ ctx.log('Waiting for steward to reject...');
1062
+ const meta = await waitForTaskMeta(ctx.api, task.id, (m) => m.mergeStatus === 'test_failed', { timeout: 240000 });
1063
+ // 5. Verify task is reopened
1064
+ const reopened = await ctx.api.get(task.id);
1065
+ if (reopened?.status !== TaskStatus.OPEN) {
1066
+ ctx.log(`Task status after handoff: ${reopened?.status} (expected OPEN)`);
1067
+ }
1068
+ return pass('Steward created handoff for failing PR', {
1069
+ taskId: task.id,
1070
+ mergeStatus: meta.mergeStatus,
1071
+ handoffHistory: meta.handoffHistory,
1072
+ taskStatus: reopened?.status,
1073
+ });
1074
+ }
1075
+ // ============================================================================
1076
+ // All Tests Collection
1077
+ // ============================================================================
1078
+ /**
1079
+ * All orchestration tests in order of execution
1080
+ */
1081
+ export const allTests = [
1082
+ directorCreatesTasksTest,
1083
+ directorCreatesPlansTest,
1084
+ daemonDispatchesWorkerTest,
1085
+ daemonRespectsDependenciesTest,
1086
+ workerUsesWorktreeTest,
1087
+ workerCommitsWorkTest,
1088
+ workerCreatesMergeRequestTest,
1089
+ workerMarksTaskCompleteTest,
1090
+ workerHandoffOnContextFillTest,
1091
+ daemonSpawnsStewardForMRTest,
1092
+ stewardMergesPassingMRTest,
1093
+ stewardHandoffFailingMRTest,
1094
+ ];
1095
+ /**
1096
+ * Get tests by tag
1097
+ */
1098
+ export function getTestsByTag(tag) {
1099
+ return allTests.filter(t => t.tags?.includes(tag));
1100
+ }
1101
+ /**
1102
+ * Get test by ID
1103
+ */
1104
+ export function getTestById(id) {
1105
+ return allTests.find(t => t.id === id);
1106
+ }
1107
+ /**
1108
+ * Get tests matching a filter string (matches id or name)
1109
+ */
1110
+ export function filterTests(filter) {
1111
+ const lowerFilter = filter.toLowerCase();
1112
+ return allTests.filter(t => t.id.toLowerCase().includes(lowerFilter) ||
1113
+ t.name.toLowerCase().includes(lowerFilter));
1114
+ }
1115
+ //# sourceMappingURL=orchestration-tests.js.map