macro-agent 0.1.0 → 0.1.1

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 (337) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +16 -14
  4. package/README.md +11 -29
  5. package/dist/acp/macro-agent.d.ts +15 -0
  6. package/dist/acp/macro-agent.d.ts.map +1 -1
  7. package/dist/acp/macro-agent.js +131 -35
  8. package/dist/acp/macro-agent.js.map +1 -1
  9. package/dist/acp/types.d.ts +32 -1
  10. package/dist/acp/types.d.ts.map +1 -1
  11. package/dist/acp/types.js.map +1 -1
  12. package/dist/agent/agent-manager.d.ts +65 -1
  13. package/dist/agent/agent-manager.d.ts.map +1 -1
  14. package/dist/agent/agent-manager.js +464 -183
  15. package/dist/agent/agent-manager.js.map +1 -1
  16. package/dist/agent/types.d.ts +1 -1
  17. package/dist/agent/types.d.ts.map +1 -1
  18. package/dist/api/server.d.ts +3 -0
  19. package/dist/api/server.d.ts.map +1 -1
  20. package/dist/api/server.js +37 -6
  21. package/dist/api/server.js.map +1 -1
  22. package/dist/auth/index.d.ts +2 -0
  23. package/dist/auth/index.d.ts.map +1 -0
  24. package/dist/auth/index.js +2 -0
  25. package/dist/auth/index.js.map +1 -0
  26. package/dist/auth/token.d.ts +41 -0
  27. package/dist/auth/token.d.ts.map +1 -0
  28. package/dist/auth/token.js +73 -0
  29. package/dist/auth/token.js.map +1 -0
  30. package/dist/cli/acp.d.ts +2 -23
  31. package/dist/cli/acp.d.ts.map +1 -1
  32. package/dist/cli/acp.js +127 -61
  33. package/dist/cli/acp.js.map +1 -1
  34. package/dist/cli/index.js +147 -15
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/mcp.d.ts +6 -0
  37. package/dist/cli/mcp.d.ts.map +1 -1
  38. package/dist/cli/mcp.js +268 -181
  39. package/dist/cli/mcp.js.map +1 -1
  40. package/dist/cli/parse-args.d.ts +20 -0
  41. package/dist/cli/parse-args.d.ts.map +1 -0
  42. package/dist/cli/parse-args.js +43 -0
  43. package/dist/cli/parse-args.js.map +1 -0
  44. package/dist/cli/stable-instance-id.d.ts +8 -0
  45. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  46. package/dist/cli/stable-instance-id.js +14 -0
  47. package/dist/cli/stable-instance-id.js.map +1 -0
  48. package/dist/config/project-config.d.ts +74 -7
  49. package/dist/config/project-config.d.ts.map +1 -1
  50. package/dist/config/project-config.js +123 -20
  51. package/dist/config/project-config.js.map +1 -1
  52. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  53. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  54. package/dist/map/adapter/acp-over-map.js +384 -23
  55. package/dist/map/adapter/acp-over-map.js.map +1 -1
  56. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  57. package/dist/map/adapter/connection-manager.js +3 -0
  58. package/dist/map/adapter/connection-manager.js.map +1 -1
  59. package/dist/map/adapter/event-log.d.ts +87 -0
  60. package/dist/map/adapter/event-log.d.ts.map +1 -0
  61. package/dist/map/adapter/event-log.js +122 -0
  62. package/dist/map/adapter/event-log.js.map +1 -0
  63. package/dist/map/adapter/event-translator.js +6 -6
  64. package/dist/map/adapter/event-translator.js.map +1 -1
  65. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  66. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  67. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  68. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  69. package/dist/map/adapter/extensions/index.d.ts +10 -1
  70. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  71. package/dist/map/adapter/extensions/index.js +34 -0
  72. package/dist/map/adapter/extensions/index.js.map +1 -1
  73. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  74. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  75. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  76. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  77. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  78. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  79. package/dist/map/adapter/extensions/rename.js +49 -0
  80. package/dist/map/adapter/extensions/rename.js.map +1 -0
  81. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  82. package/dist/map/adapter/extensions/task.js +10 -0
  83. package/dist/map/adapter/extensions/task.js.map +1 -1
  84. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  85. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  86. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  87. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  88. package/dist/map/adapter/index.d.ts +2 -1
  89. package/dist/map/adapter/index.d.ts.map +1 -1
  90. package/dist/map/adapter/index.js +8 -2
  91. package/dist/map/adapter/index.js.map +1 -1
  92. package/dist/map/adapter/interface.d.ts +2 -0
  93. package/dist/map/adapter/interface.d.ts.map +1 -1
  94. package/dist/map/adapter/map-adapter.d.ts +3 -0
  95. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  96. package/dist/map/adapter/map-adapter.js +258 -35
  97. package/dist/map/adapter/map-adapter.js.map +1 -1
  98. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  99. package/dist/map/adapter/subscription-manager.js +5 -1
  100. package/dist/map/adapter/subscription-manager.js.map +1 -1
  101. package/dist/map/adapter/types.d.ts +2 -0
  102. package/dist/map/adapter/types.d.ts.map +1 -1
  103. package/dist/mcp/map-client.d.ts +39 -0
  104. package/dist/mcp/map-client.d.ts.map +1 -0
  105. package/dist/mcp/map-client.js +129 -0
  106. package/dist/mcp/map-client.js.map +1 -0
  107. package/dist/mcp/mcp-server.d.ts +14 -0
  108. package/dist/mcp/mcp-server.d.ts.map +1 -1
  109. package/dist/mcp/mcp-server.js +113 -85
  110. package/dist/mcp/mcp-server.js.map +1 -1
  111. package/dist/mcp/types.d.ts +9 -1
  112. package/dist/mcp/types.d.ts.map +1 -1
  113. package/dist/mcp/types.js.map +1 -1
  114. package/dist/metrics/metrics.js +1 -1
  115. package/dist/metrics/metrics.js.map +1 -1
  116. package/dist/roles/capabilities.d.ts +3 -1
  117. package/dist/roles/capabilities.d.ts.map +1 -1
  118. package/dist/roles/capabilities.js +17 -7
  119. package/dist/roles/capabilities.js.map +1 -1
  120. package/dist/roles/config-loader.d.ts +6 -6
  121. package/dist/roles/config-loader.d.ts.map +1 -1
  122. package/dist/roles/config-loader.js +6 -6
  123. package/dist/roles/config-loader.js.map +1 -1
  124. package/dist/roles/registry.d.ts +2 -2
  125. package/dist/roles/registry.js +2 -2
  126. package/dist/server/combined-server.d.ts +20 -0
  127. package/dist/server/combined-server.d.ts.map +1 -1
  128. package/dist/server/combined-server.js +107 -8
  129. package/dist/server/combined-server.js.map +1 -1
  130. package/dist/store/event-store.d.ts +2 -1
  131. package/dist/store/event-store.d.ts.map +1 -1
  132. package/dist/store/event-store.js +69 -20
  133. package/dist/store/event-store.js.map +1 -1
  134. package/dist/store/types/agents.d.ts +18 -0
  135. package/dist/store/types/agents.d.ts.map +1 -1
  136. package/dist/store/types/events.d.ts +1 -1
  137. package/dist/store/types/events.d.ts.map +1 -1
  138. package/dist/task/backend/index.d.ts +47 -29
  139. package/dist/task/backend/index.d.ts.map +1 -1
  140. package/dist/task/backend/index.js +109 -71
  141. package/dist/task/backend/index.js.map +1 -1
  142. package/dist/task/backend/memory.d.ts +1 -0
  143. package/dist/task/backend/memory.d.ts.map +1 -1
  144. package/dist/task/backend/memory.js +3 -0
  145. package/dist/task/backend/memory.js.map +1 -1
  146. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  147. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  148. package/dist/task/backend/opentasks/backend.js +1023 -0
  149. package/dist/task/backend/opentasks/backend.js.map +1 -0
  150. package/dist/task/backend/opentasks/client.d.ts +337 -0
  151. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  152. package/dist/task/backend/opentasks/client.js +225 -0
  153. package/dist/task/backend/opentasks/client.js.map +1 -0
  154. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  155. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  156. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  157. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  158. package/dist/task/backend/opentasks/index.d.ts +21 -0
  159. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  160. package/dist/task/backend/opentasks/index.js +21 -0
  161. package/dist/task/backend/opentasks/index.js.map +1 -0
  162. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  163. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  164. package/dist/task/backend/opentasks/mapping.js +77 -0
  165. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  166. package/dist/task/backend/types.d.ts +33 -53
  167. package/dist/task/backend/types.d.ts.map +1 -1
  168. package/dist/task/backend/types.js +7 -11
  169. package/dist/task/backend/types.js.map +1 -1
  170. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  171. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  172. package/dist/task/backend/unified-tool-provider.js +623 -0
  173. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  174. package/dist/teams/team-loader.d.ts +2 -2
  175. package/dist/teams/team-loader.js +3 -3
  176. package/dist/teams/team-loader.js.map +1 -1
  177. package/dist/teams/team-runtime.d.ts.map +1 -1
  178. package/dist/teams/team-runtime.js +2 -0
  179. package/dist/teams/team-runtime.js.map +1 -1
  180. package/docs/architecture.md +7 -6
  181. package/docs/configuration.md +26 -62
  182. package/docs/implementation-details.md +5 -5
  183. package/docs/implementation-summary.md +17 -17
  184. package/docs/plan-self-driving-support.md +4 -4
  185. package/docs/spec-self-driving-support.md +10 -10
  186. package/docs/team-templates.md +2 -2
  187. package/docs/teams.md +3 -3
  188. package/docs/troubleshooting.md +10 -11
  189. package/package.json +6 -4
  190. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  191. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  192. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  193. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  194. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  195. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  196. package/src/acp/__tests__/integration.test.ts +56 -31
  197. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  198. package/src/acp/macro-agent.ts +170 -36
  199. package/src/acp/types.ts +46 -1
  200. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  201. package/src/agent/agent-manager.ts +714 -261
  202. package/src/agent/types.ts +3 -1
  203. package/src/api/server.ts +41 -7
  204. package/src/auth/__tests__/token.test.ts +100 -0
  205. package/src/auth/index.ts +1 -0
  206. package/src/auth/token.ts +82 -0
  207. package/src/cli/__tests__/acp.test.ts +1 -1
  208. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  209. package/src/cli/acp.ts +130 -72
  210. package/src/cli/index.ts +120 -14
  211. package/src/cli/mcp.ts +311 -207
  212. package/src/cli/parse-args.ts +54 -0
  213. package/src/cli/stable-instance-id.ts +14 -0
  214. package/src/config/project-config.ts +190 -27
  215. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  216. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  217. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  218. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  219. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  220. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  221. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  222. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  223. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  224. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  225. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  226. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  227. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  228. package/src/map/adapter/acp-over-map.ts +678 -66
  229. package/src/map/adapter/connection-manager.ts +3 -0
  230. package/src/map/adapter/event-log.ts +208 -0
  231. package/src/map/adapter/event-translator.ts +6 -6
  232. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  233. package/src/map/adapter/extensions/index.ts +60 -0
  234. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  235. package/src/map/adapter/extensions/task.ts +11 -0
  236. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  237. package/src/map/adapter/index.ts +28 -0
  238. package/src/map/adapter/interface.ts +2 -0
  239. package/src/map/adapter/map-adapter.ts +312 -47
  240. package/src/map/adapter/subscription-manager.ts +5 -1
  241. package/src/map/adapter/types.ts +2 -0
  242. package/src/mcp/__tests__/map-client.test.ts +386 -0
  243. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  244. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  245. package/src/mcp/map-client.ts +177 -0
  246. package/src/mcp/mcp-server.ts +191 -100
  247. package/src/mcp/types.ts +6 -1
  248. package/src/metrics/metrics.ts +1 -1
  249. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  250. package/src/roles/__tests__/config-loader.test.ts +7 -7
  251. package/src/roles/capabilities.ts +17 -7
  252. package/src/roles/config-loader.ts +6 -6
  253. package/src/roles/registry.ts +2 -2
  254. package/src/server/__tests__/combined-server.test.ts +94 -21
  255. package/src/server/combined-server.ts +189 -33
  256. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  257. package/src/store/__tests__/event-store.test.ts +196 -1
  258. package/src/store/__tests__/instance.test.ts +3 -3
  259. package/src/store/event-store.ts +80 -21
  260. package/src/store/types/agents.ts +15 -0
  261. package/src/store/types/events.ts +1 -1
  262. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  263. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  264. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  265. package/src/task/backend/index.ts +156 -106
  266. package/src/task/backend/memory.ts +4 -0
  267. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  268. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  269. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  270. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  271. package/src/task/backend/opentasks/backend.ts +1323 -0
  272. package/src/task/backend/opentasks/client.ts +652 -0
  273. package/src/task/backend/opentasks/daemon-manager.ts +253 -0
  274. package/src/task/backend/opentasks/index.ts +69 -0
  275. package/src/task/backend/opentasks/mapping.ts +94 -0
  276. package/src/task/backend/types.ts +42 -66
  277. package/src/task/backend/unified-tool-provider.ts +779 -0
  278. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  279. package/src/teams/team-loader.ts +3 -3
  280. package/src/teams/team-runtime.ts +2 -0
  281. package/test_fixtures/README.md +2 -3
  282. package/test_fixtures/fixtures/index.ts +0 -3
  283. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  284. package/test_fixtures/fixtures/repos/index.ts +1 -3
  285. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  286. package/test_fixtures/fixtures/repos/types.ts +0 -11
  287. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  288. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  289. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  290. package/vitest.config.ts +1 -1
  291. package/vitest.e2e.config.ts +1 -1
  292. package/vitest.setup.ts +1 -30
  293. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  294. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  295. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  296. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  297. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  298. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  299. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  300. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  301. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  302. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  303. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  304. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  305. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  306. package/.macro-agent/teams/structured/team.yaml +0 -89
  307. package/docs/sudocode-integration.md +0 -383
  308. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  309. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  310. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  311. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  312. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  313. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  314. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  315. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  316. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  317. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  318. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  319. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  320. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  321. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  322. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  323. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  324. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  325. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  326. package/src/task/backend/sudocode/backend.ts +0 -1237
  327. package/src/task/backend/sudocode/client.ts +0 -515
  328. package/src/task/backend/sudocode/index.ts +0 -120
  329. package/src/task/backend/sudocode/mapping.ts +0 -93
  330. package/src/task/backend/sudocode/server-client.ts +0 -522
  331. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  332. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  333. package/src/task/backend/sudocode/tools.ts +0 -896
  334. package/src/task/backend/tool-provider.ts +0 -506
  335. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  336. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  337. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -1,666 +0,0 @@
1
- /**
2
- * End-to-End Workflow Tests
3
- *
4
- * Tests for complete task lifecycle with sudocode integration.
5
- * These tests verify the interaction between all components.
6
- *
7
- * @module task/backend/sudocode/__tests__/e2e-workflow.test
8
- * @see s-8472 Pluggable Task Backend Integration
9
- * @see s-1zcx Multi-Agent Orchestration Testing Strategy
10
- */
11
-
12
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
13
- import { createEventStore, type EventStore } from "../../../../store/event-store.js";
14
- import {
15
- SudocodeTaskBackend,
16
- createSudocodeTaskBackend,
17
- } from "../backend.js";
18
- import type { SudocodeClient, Issue, IssueChangeCallback } from "../client.js";
19
- import type { TaskChangeEvent, SyncEvent } from "../sync-policy.js";
20
-
21
- // Create a realistic mock client
22
- function createRealisticMockClient(): SudocodeClient & {
23
- _issues: Map<string, Issue>;
24
- _blockers: Map<string, Issue[]>;
25
- _blocking: Map<string, Issue[]>;
26
- _callbacks: IssueChangeCallback[];
27
- _triggerIssueChange: (event: Parameters<IssueChangeCallback>[0]) => void;
28
- _createIssue: (issue: Issue) => void;
29
- } {
30
- const issues = new Map<string, Issue>();
31
- const blockers = new Map<string, Issue[]>();
32
- const blocking = new Map<string, Issue[]>();
33
- const callbacks: IssueChangeCallback[] = [];
34
-
35
- return {
36
- _issues: issues,
37
- _blockers: blockers,
38
- _blocking: blocking,
39
- _callbacks: callbacks,
40
- _triggerIssueChange: (event) => {
41
- for (const cb of callbacks) {
42
- cb(event);
43
- }
44
- },
45
- _createIssue: (issue) => {
46
- issues.set(issue.id, issue);
47
- },
48
-
49
- getIssue: vi.fn(async (id: string) => issues.get(id) ?? null),
50
- listIssues: vi.fn(async () => Array.from(issues.values())),
51
- createIssue: vi.fn(async (data: Partial<Issue>) => {
52
- const id = data.id ?? `i-${Date.now()}`;
53
- const issue: Issue = {
54
- id,
55
- uuid: `uuid-${id}`,
56
- title: data.title ?? "New Issue",
57
- status: data.status ?? "open",
58
- priority: data.priority ?? 2,
59
- created_at: new Date().toISOString(),
60
- updated_at: new Date().toISOString(),
61
- ...data,
62
- } as Issue;
63
- issues.set(id, issue);
64
- return issue;
65
- }),
66
- updateIssue: vi.fn(async (id: string, updates: Partial<Issue>) => {
67
- const issue = issues.get(id);
68
- if (!issue) throw new Error(`Issue not found: ${id}`);
69
- const previousStatus = issue.status;
70
- Object.assign(issue, updates);
71
- issue.updated_at = new Date().toISOString();
72
-
73
- // Trigger status_changed if status changed
74
- if (updates.status && updates.status !== previousStatus) {
75
- for (const cb of callbacks) {
76
- cb({
77
- type: "status_changed",
78
- issueId: id,
79
- issue,
80
- previousIssue: { ...issue, status: previousStatus },
81
- });
82
- }
83
- }
84
-
85
- return issue;
86
- }),
87
- getReadyIssues: vi.fn(async () => {
88
- return Array.from(issues.values()).filter((i) => {
89
- const issueBlockers = blockers.get(i.id) ?? [];
90
- return (
91
- issueBlockers.every((b) => b.status === "closed") &&
92
- i.status !== "closed"
93
- );
94
- });
95
- }),
96
- getBlockers: vi.fn(async (id: string) => blockers.get(id) ?? []),
97
- getBlocking: vi.fn(async (id: string) => blocking.get(id) ?? []),
98
- createLink: vi.fn(async (from: string, to: string, type: string) => {
99
- if (type === "blocks") {
100
- const fromIssue = issues.get(from);
101
- if (!fromIssue) return;
102
- const existing = blockers.get(to) ?? [];
103
- if (!existing.some((b) => b.id === from)) {
104
- blockers.set(to, [...existing, fromIssue]);
105
- }
106
- const blockingList = blocking.get(from) ?? [];
107
- const toIssue = issues.get(to);
108
- if (toIssue && !blockingList.some((b) => b.id === to)) {
109
- blocking.set(from, [...blockingList, toIssue]);
110
- }
111
- }
112
- }),
113
- removeLink: vi.fn(async (from: string, to: string, type: string) => {
114
- if (type === "blocks") {
115
- const existing = blockers.get(to) ?? [];
116
- blockers.set(to, existing.filter((b) => b.id !== from));
117
- const blockingList = blocking.get(from) ?? [];
118
- blocking.set(from, blockingList.filter((b) => b.id !== to));
119
- }
120
- }),
121
- getSpec: vi.fn(async () => null),
122
- listSpecs: vi.fn(async () => []),
123
- addFeedback: vi.fn(async () => {}),
124
- onIssueChange: vi.fn((callback: IssueChangeCallback) => {
125
- callbacks.push(callback);
126
- return () => {
127
- const idx = callbacks.indexOf(callback);
128
- if (idx >= 0) callbacks.splice(idx, 1);
129
- };
130
- }),
131
- close: vi.fn(),
132
- isReady: vi.fn(() => true),
133
- } as unknown as SudocodeClient & {
134
- _issues: Map<string, Issue>;
135
- _blockers: Map<string, Issue[]>;
136
- _blocking: Map<string, Issue[]>;
137
- _callbacks: IssueChangeCallback[];
138
- _triggerIssueChange: (event: Parameters<IssueChangeCallback>[0]) => void;
139
- _createIssue: (issue: Issue) => void;
140
- };
141
- }
142
-
143
- describe("E2E Workflow", () => {
144
- let eventStore: EventStore;
145
- let backend: SudocodeTaskBackend;
146
- let mockClient: ReturnType<typeof createRealisticMockClient>;
147
- const testAgentId = "agent_test";
148
-
149
- beforeEach(async () => {
150
- eventStore = await createEventStore({ inMemory: true });
151
- mockClient = createRealisticMockClient();
152
- });
153
-
154
- afterEach(async () => {
155
- backend?.close();
156
- await eventStore.close();
157
- });
158
-
159
- describe("Task creation and issue binding", () => {
160
- beforeEach(() => {
161
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
162
- syncStatus: true,
163
- });
164
- });
165
-
166
- it("should create a task bound to an existing issue", async () => {
167
- // Create an issue first
168
- mockClient._createIssue({
169
- id: "i-feature1",
170
- uuid: "uuid-1",
171
- title: "Implement feature 1",
172
- status: "open",
173
- priority: 1,
174
- created_at: new Date().toISOString(),
175
- updated_at: new Date().toISOString(),
176
- });
177
-
178
- // Create task bound to the issue
179
- const task = await backend.create({
180
- description: "Implement feature 1",
181
- created_by: testAgentId,
182
- external_id: "i-feature1",
183
- });
184
-
185
- expect(task.id).toBeDefined();
186
- expect(task.external_id).toBe("i-feature1");
187
- expect(backend.getIssueForTask(task.id)).toBe("i-feature1");
188
- expect(backend.getTasksByIssue("i-feature1")).toContain(task.id);
189
- });
190
-
191
- it("should fail to create task bound to non-existent issue", async () => {
192
- await expect(
193
- backend.create({
194
- description: "Test",
195
- created_by: testAgentId,
196
- external_id: "i-nonexistent",
197
- })
198
- ).rejects.toThrow("Issue not found");
199
- });
200
- });
201
-
202
- describe("Complete workflow: create -> assign -> start -> complete", () => {
203
- beforeEach(() => {
204
- mockClient._createIssue({
205
- id: "i-workflow",
206
- uuid: "uuid-workflow",
207
- title: "Workflow test issue",
208
- status: "open",
209
- priority: 2,
210
- created_at: new Date().toISOString(),
211
- updated_at: new Date().toISOString(),
212
- });
213
-
214
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
215
- syncStatus: true,
216
- autoCloseIssues: true,
217
- });
218
- });
219
-
220
- it("should complete full task lifecycle", async () => {
221
- // Create task
222
- const task = await backend.create({
223
- description: "Complete workflow test",
224
- created_by: testAgentId,
225
- external_id: "i-workflow",
226
- });
227
- expect(task.status).toBe("pending");
228
-
229
- // Assign task
230
- await backend.assign(task.id, "worker-1");
231
- const afterAssign = await backend.get(task.id);
232
- expect(afterAssign?.status).toBe("assigned");
233
- expect(afterAssign?.assigned_agent).toBe("worker-1");
234
-
235
- // Start task
236
- await backend.start(task.id);
237
- const afterStart = await backend.get(task.id);
238
- expect(afterStart?.status).toBe("in_progress");
239
-
240
- // Complete task
241
- await backend.complete(task.id, {
242
- summary: "Work completed successfully",
243
- data: { result: "success" },
244
- });
245
- const afterComplete = await backend.get(task.id);
246
- expect(afterComplete?.status).toBe("completed");
247
-
248
- // Issue should have been closed (autoCloseIssues: true)
249
- expect(mockClient.updateIssue).toHaveBeenCalledWith("i-workflow", {
250
- status: "closed",
251
- });
252
- });
253
-
254
- it("should track status changes correctly", async () => {
255
- const statusHistory: string[] = [];
256
- backend.onTaskChange((event) => {
257
- if (event.task.status) {
258
- statusHistory.push(event.task.status);
259
- }
260
- });
261
-
262
- const task = await backend.create({
263
- description: "Track status",
264
- created_by: testAgentId,
265
- });
266
-
267
- await backend.assign(task.id, "worker-1");
268
- await backend.start(task.id);
269
- await backend.complete(task.id);
270
-
271
- // Should have recorded: pending -> assigned -> in_progress -> completed
272
- expect(statusHistory).toContain("pending");
273
- expect(statusHistory).toContain("assigned");
274
- expect(statusHistory).toContain("in_progress");
275
- expect(statusHistory).toContain("completed");
276
- });
277
- });
278
-
279
- describe("Blocker workflow", () => {
280
- beforeEach(() => {
281
- mockClient._createIssue({
282
- id: "i-blocker",
283
- uuid: "uuid-blocker",
284
- title: "Blocker issue",
285
- status: "open",
286
- priority: 1,
287
- created_at: new Date().toISOString(),
288
- updated_at: new Date().toISOString(),
289
- });
290
- mockClient._createIssue({
291
- id: "i-blocked",
292
- uuid: "uuid-blocked",
293
- title: "Blocked issue",
294
- status: "open",
295
- priority: 2,
296
- created_at: new Date().toISOString(),
297
- updated_at: new Date().toISOString(),
298
- });
299
-
300
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
301
- syncStatus: true,
302
- });
303
- });
304
-
305
- it("should handle task blocked by another task", async () => {
306
- const blocker = await backend.create({
307
- description: "Blocker task",
308
- created_by: testAgentId,
309
- external_id: "i-blocker",
310
- });
311
- const blocked = await backend.create({
312
- description: "Blocked task",
313
- created_by: testAgentId,
314
- external_id: "i-blocked",
315
- });
316
-
317
- // Add blocker
318
- await backend.addBlocker(blocked.id, blocker.id);
319
-
320
- // Blocked task should be blocked
321
- const blockedTask = await backend.get(blocked.id);
322
- expect(blockedTask?.isBlocked).toBe(true);
323
-
324
- // Should not appear in ready list
325
- const ready = await backend.listReady();
326
- expect(ready.find((t) => t.id === blocked.id)).toBeUndefined();
327
- expect(ready.find((t) => t.id === blocker.id)).toBeDefined();
328
-
329
- // Complete the blocker
330
- await backend.start(blocker.id);
331
- await backend.complete(blocker.id);
332
-
333
- // Blocked task should now be unblocked
334
- const afterComplete = await backend.get(blocked.id);
335
- expect(afterComplete?.isBlocked).toBe(false);
336
-
337
- // Should now appear in ready list
338
- const readyAfter = await backend.listReady();
339
- expect(readyAfter.find((t) => t.id === blocked.id)).toBeDefined();
340
- });
341
-
342
- it("should sync blocker relationships to sudocode", async () => {
343
- const blocker = await backend.create({
344
- description: "Blocker",
345
- created_by: testAgentId,
346
- external_id: "i-blocker",
347
- });
348
- const blocked = await backend.create({
349
- description: "Blocked",
350
- created_by: testAgentId,
351
- external_id: "i-blocked",
352
- });
353
-
354
- await backend.addBlocker(blocked.id, blocker.id);
355
-
356
- // Should have created link in sudocode
357
- expect(mockClient.createLink).toHaveBeenCalledWith(
358
- "i-blocker",
359
- "i-blocked",
360
- "blocks"
361
- );
362
- });
363
- });
364
-
365
- describe("Subtask workflow", () => {
366
- beforeEach(() => {
367
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
368
- syncStatus: false,
369
- });
370
- });
371
-
372
- it("should create and track subtasks", async () => {
373
- const parent = await backend.create({
374
- description: "Parent task",
375
- created_by: testAgentId,
376
- });
377
-
378
- const child1 = await backend.createSubtask(parent.id, {
379
- description: "Child 1",
380
- created_by: testAgentId,
381
- });
382
- const child2 = await backend.createSubtask(parent.id, {
383
- description: "Child 2",
384
- created_by: testAgentId,
385
- });
386
-
387
- expect(child1.parent_task).toBe(parent.id);
388
- expect(child2.parent_task).toBe(parent.id);
389
-
390
- // Get children
391
- const children = await backend.getChildren(parent.id);
392
- expect(children).toHaveLength(2);
393
-
394
- // Get subtask status
395
- const status = await backend.getSubtaskStatus(parent.id);
396
- expect(status.total).toBe(2);
397
- expect(status.pending).toBe(2);
398
- expect(status.completed).toBe(0);
399
- expect(status.allCompleted).toBe(false);
400
-
401
- // Complete one child
402
- await backend.start(child1.id);
403
- await backend.complete(child1.id);
404
-
405
- const afterOne = await backend.getSubtaskStatus(parent.id);
406
- expect(afterOne.completed).toBe(1);
407
- expect(afterOne.pending).toBe(1);
408
- expect(afterOne.allCompleted).toBe(false);
409
-
410
- // Complete second child
411
- await backend.start(child2.id);
412
- await backend.complete(child2.id);
413
-
414
- const afterAll = await backend.getSubtaskStatus(parent.id);
415
- expect(afterAll.completed).toBe(2);
416
- expect(afterAll.allCompleted).toBe(true);
417
- });
418
- });
419
-
420
- describe("Issue change propagation", () => {
421
- beforeEach(() => {
422
- mockClient._createIssue({
423
- id: "i-sync",
424
- uuid: "uuid-sync",
425
- title: "Sync test issue",
426
- status: "open",
427
- priority: 2,
428
- created_at: new Date().toISOString(),
429
- updated_at: new Date().toISOString(),
430
- });
431
-
432
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
433
- syncStatus: true,
434
- });
435
- });
436
-
437
- it("should update task when issue status changes", async () => {
438
- const task = await backend.create({
439
- description: "Sync test",
440
- created_by: testAgentId,
441
- external_id: "i-sync",
442
- });
443
-
444
- // Initially pending
445
- expect((await backend.get(task.id))?.status).toBe("pending");
446
-
447
- // Simulate issue status change to in_progress
448
- mockClient._issues.get("i-sync")!.status = "in_progress";
449
- mockClient._triggerIssueChange({
450
- type: "status_changed",
451
- issueId: "i-sync",
452
- issue: mockClient._issues.get("i-sync")!,
453
- });
454
-
455
- // Wait for async handler
456
- await new Promise((resolve) => setTimeout(resolve, 10));
457
-
458
- // Task should be in_progress
459
- expect((await backend.get(task.id))?.status).toBe("in_progress");
460
- });
461
-
462
- it("should not overwrite completed task status", async () => {
463
- const task = await backend.create({
464
- description: "Completed task",
465
- created_by: testAgentId,
466
- external_id: "i-sync",
467
- });
468
-
469
- await backend.start(task.id);
470
- await backend.complete(task.id);
471
- expect((await backend.get(task.id))?.status).toBe("completed");
472
-
473
- // Simulate issue reopened
474
- mockClient._issues.get("i-sync")!.status = "open";
475
- mockClient._triggerIssueChange({
476
- type: "status_changed",
477
- issueId: "i-sync",
478
- issue: mockClient._issues.get("i-sync")!,
479
- });
480
-
481
- await new Promise((resolve) => setTimeout(resolve, 10));
482
-
483
- // Task should remain completed
484
- expect((await backend.get(task.id))?.status).toBe("completed");
485
- });
486
- });
487
-
488
- describe("Error handling in workflows", () => {
489
- beforeEach(() => {
490
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
491
- syncStatus: false,
492
- });
493
- });
494
-
495
- it("should handle task failure correctly", async () => {
496
- const task = await backend.create({
497
- description: "Will fail",
498
- created_by: testAgentId,
499
- });
500
-
501
- await backend.start(task.id);
502
- await backend.fail(task.id, {
503
- code: "TEST_ERROR",
504
- message: "Test failure",
505
- details: { reason: "testing" },
506
- });
507
-
508
- const failed = await backend.get(task.id);
509
- expect(failed?.status).toBe("failed");
510
- expect((failed?.outputs as any)?.error?.code).toBe("TEST_ERROR");
511
- });
512
-
513
- it("should reject invalid status transitions", async () => {
514
- const task = await backend.create({
515
- description: "Test",
516
- created_by: testAgentId,
517
- });
518
-
519
- // Can't complete pending task directly
520
- await expect(backend.complete(task.id)).rejects.toThrow(
521
- "Invalid status transition"
522
- );
523
-
524
- // Start first
525
- await backend.start(task.id);
526
- await backend.complete(task.id);
527
-
528
- // Can't start completed task
529
- await expect(backend.start(task.id)).rejects.toThrow(
530
- "Invalid status transition"
531
- );
532
- });
533
- });
534
-
535
- describe("Multiple tasks per issue", () => {
536
- beforeEach(() => {
537
- mockClient._createIssue({
538
- id: "i-multi",
539
- uuid: "uuid-multi",
540
- title: "Multi-task issue",
541
- status: "open",
542
- priority: 2,
543
- created_at: new Date().toISOString(),
544
- updated_at: new Date().toISOString(),
545
- });
546
-
547
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
548
- syncStatus: true,
549
- });
550
- });
551
-
552
- it("should handle multiple tasks bound to same issue", async () => {
553
- const task1 = await backend.create({
554
- description: "Task 1",
555
- created_by: testAgentId,
556
- external_id: "i-multi",
557
- });
558
- const task2 = await backend.create({
559
- description: "Task 2",
560
- created_by: testAgentId,
561
- external_id: "i-multi",
562
- });
563
-
564
- const tasksByIssue = backend.getTasksByIssue("i-multi");
565
- expect(tasksByIssue).toHaveLength(2);
566
- expect(tasksByIssue).toContain(task1.id);
567
- expect(tasksByIssue).toContain(task2.id);
568
- });
569
-
570
- it("should update all tasks when issue status changes", async () => {
571
- const task1 = await backend.create({
572
- description: "Task 1",
573
- created_by: testAgentId,
574
- external_id: "i-multi",
575
- });
576
- const task2 = await backend.create({
577
- description: "Task 2",
578
- created_by: testAgentId,
579
- external_id: "i-multi",
580
- });
581
-
582
- // Simulate issue status change
583
- mockClient._issues.get("i-multi")!.status = "in_progress";
584
- mockClient._triggerIssueChange({
585
- type: "status_changed",
586
- issueId: "i-multi",
587
- issue: mockClient._issues.get("i-multi")!,
588
- });
589
-
590
- await new Promise((resolve) => setTimeout(resolve, 10));
591
-
592
- expect((await backend.get(task1.id))?.status).toBe("in_progress");
593
- expect((await backend.get(task2.id))?.status).toBe("in_progress");
594
- });
595
- });
596
-
597
- describe("listReady with mixed tasks", () => {
598
- beforeEach(() => {
599
- mockClient._createIssue({
600
- id: "i-ready",
601
- uuid: "uuid-ready",
602
- title: "Ready issue",
603
- status: "open",
604
- priority: 2,
605
- created_at: new Date().toISOString(),
606
- updated_at: new Date().toISOString(),
607
- });
608
- mockClient._createIssue({
609
- id: "i-blocked-ext",
610
- uuid: "uuid-blocked-ext",
611
- title: "Externally blocked issue",
612
- status: "blocked",
613
- priority: 2,
614
- created_at: new Date().toISOString(),
615
- updated_at: new Date().toISOString(),
616
- });
617
-
618
- backend = createSudocodeTaskBackend(eventStore, mockClient, {
619
- syncStatus: false,
620
- });
621
- });
622
-
623
- it("should correctly filter ready tasks", async () => {
624
- // Unbound task (always ready if not blocked locally)
625
- const unboundReady = await backend.create({
626
- description: "Unbound ready",
627
- created_by: testAgentId,
628
- });
629
-
630
- // Bound to ready issue
631
- const boundReady = await backend.create({
632
- description: "Bound to ready issue",
633
- created_by: testAgentId,
634
- external_id: "i-ready",
635
- });
636
-
637
- // Bound to blocked issue
638
- const boundBlocked = await backend.create({
639
- description: "Bound to blocked issue",
640
- created_by: testAgentId,
641
- external_id: "i-blocked-ext",
642
- });
643
-
644
- // Locally blocked (by another task)
645
- const locallyBlocked = await backend.create({
646
- description: "Locally blocked",
647
- created_by: testAgentId,
648
- });
649
- const localBlocker = await backend.create({
650
- description: "Local blocker",
651
- created_by: testAgentId,
652
- });
653
- await backend.addBlocker(locallyBlocked.id, localBlocker.id);
654
-
655
- const ready = await backend.listReady();
656
-
657
- // Should include: unboundReady, boundReady, localBlocker
658
- // Should exclude: boundBlocked (blocked issue), locallyBlocked (blocked by task)
659
- expect(ready.find((t) => t.id === unboundReady.id)).toBeDefined();
660
- expect(ready.find((t) => t.id === boundReady.id)).toBeDefined();
661
- expect(ready.find((t) => t.id === localBlocker.id)).toBeDefined();
662
- expect(ready.find((t) => t.id === boundBlocked.id)).toBeUndefined();
663
- expect(ready.find((t) => t.id === locallyBlocked.id)).toBeUndefined();
664
- });
665
- });
666
- });