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,519 +0,0 @@
1
- /**
2
- * SyncPolicyEngine Tests
3
- *
4
- * Tests for the sync policy engine implementation.
5
- */
6
-
7
- import { describe, it, expect, vi, beforeEach } from "vitest";
8
- import {
9
- SyncPolicyEngine,
10
- SyncPolicy,
11
- defaultSyncPolicy,
12
- createSyncPolicyEngine,
13
- SyncEvent,
14
- SyncableTaskBackend,
15
- } from "../sync-policy.js";
16
- import type { IssueChangeEvent } from "../client.js";
17
-
18
- // Mock backend for testing
19
- function createMockBackend(): SyncableTaskBackend & {
20
- _setTasks: (tasks: Map<string, { status: string }>) => void;
21
- _setTasksByIssue: (issueId: string, taskIds: string[]) => void;
22
- } {
23
- const tasks = new Map<string, { status: string }>();
24
- const tasksByIssue = new Map<string, string[]>();
25
-
26
- return {
27
- getTasksByIssue: vi.fn((issueId: string) => tasksByIssue.get(issueId) ?? []),
28
- get: vi.fn(async (taskId: string) => tasks.get(taskId) ?? null),
29
- complete: vi.fn(async () => {}),
30
- fail: vi.fn(async () => {}),
31
- update: vi.fn(async () => ({})),
32
-
33
- // Test helpers
34
- _setTasks: (newTasks: Map<string, { status: string }>) => {
35
- tasks.clear();
36
- for (const [id, task] of newTasks) {
37
- tasks.set(id, task);
38
- }
39
- },
40
- _setTasksByIssue: (issueId: string, taskIds: string[]) => {
41
- tasksByIssue.set(issueId, taskIds);
42
- },
43
- };
44
- }
45
-
46
- describe("SyncPolicyEngine", () => {
47
- let backend: ReturnType<typeof createMockBackend>;
48
- let engine: SyncPolicyEngine;
49
-
50
- beforeEach(() => {
51
- backend = createMockBackend();
52
- });
53
-
54
- describe("defaultSyncPolicy", () => {
55
- it("should have correct default values", () => {
56
- expect(defaultSyncPolicy).toEqual({
57
- onIssueClosed: "notify_only",
58
- onDescriptionChanged: "snapshot",
59
- onBlockerChanged: "update_blocked",
60
- updateIssueOnStart: true,
61
- updateIssueOnComplete: "never",
62
- });
63
- });
64
- });
65
-
66
- describe("createSyncPolicyEngine", () => {
67
- it("should create engine with default policy", () => {
68
- const engine = createSyncPolicyEngine({}, backend);
69
- expect(engine.getPolicy()).toEqual(defaultSyncPolicy);
70
- });
71
-
72
- it("should merge partial policy with defaults", () => {
73
- const engine = createSyncPolicyEngine(
74
- { onIssueClosed: "complete_task" },
75
- backend
76
- );
77
- expect(engine.getPolicy().onIssueClosed).toBe("complete_task");
78
- expect(engine.getPolicy().onDescriptionChanged).toBe("snapshot");
79
- });
80
- });
81
-
82
- describe("onIssueClosed policy", () => {
83
- it("should complete task when policy is complete_task", async () => {
84
- const policy: SyncPolicy = {
85
- ...defaultSyncPolicy,
86
- onIssueClosed: "complete_task",
87
- };
88
- engine = new SyncPolicyEngine(policy, backend);
89
-
90
- // Set up a task bound to an issue
91
- backend._setTasks(
92
- new Map([["task-1", { status: "in_progress" }]])
93
- );
94
- backend._setTasksByIssue("i-test", ["task-1"]);
95
-
96
- const event: IssueChangeEvent = {
97
- type: "status_changed",
98
- issueId: "i-test",
99
- issue: {
100
- id: "i-test",
101
- uuid: "uuid-test",
102
- title: "Test",
103
- content: "",
104
- status: "closed",
105
- priority: 1,
106
- created_at: "2024-01-01",
107
- updated_at: "2024-01-01",
108
- },
109
- };
110
-
111
- await engine.handleIssueChange(event);
112
-
113
- expect(backend.complete).toHaveBeenCalledWith("task-1", {
114
- summary: "Issue closed externally",
115
- });
116
- });
117
-
118
- it("should fail task when policy is fail_task", async () => {
119
- const policy: SyncPolicy = {
120
- ...defaultSyncPolicy,
121
- onIssueClosed: "fail_task",
122
- };
123
- engine = new SyncPolicyEngine(policy, backend);
124
-
125
- backend._setTasks(
126
- new Map([["task-1", { status: "in_progress" }]])
127
- );
128
- backend._setTasksByIssue("i-test", ["task-1"]);
129
-
130
- const event: IssueChangeEvent = {
131
- type: "status_changed",
132
- issueId: "i-test",
133
- issue: {
134
- id: "i-test",
135
- uuid: "uuid-test",
136
- title: "Test",
137
- content: "",
138
- status: "closed",
139
- priority: 1,
140
- created_at: "2024-01-01",
141
- updated_at: "2024-01-01",
142
- },
143
- };
144
-
145
- await engine.handleIssueChange(event);
146
-
147
- expect(backend.fail).toHaveBeenCalledWith("task-1", {
148
- code: "ISSUE_CLOSED",
149
- message: "Bound issue was closed externally",
150
- });
151
- });
152
-
153
- it("should emit event when policy is notify_only", async () => {
154
- const policy: SyncPolicy = {
155
- ...defaultSyncPolicy,
156
- onIssueClosed: "notify_only",
157
- };
158
- engine = new SyncPolicyEngine(policy, backend);
159
-
160
- backend._setTasks(
161
- new Map([["task-1", { status: "in_progress" }]])
162
- );
163
- backend._setTasksByIssue("i-test", ["task-1"]);
164
-
165
- const events: SyncEvent[] = [];
166
- engine.onSyncEvent((event) => events.push(event));
167
-
168
- const event: IssueChangeEvent = {
169
- type: "status_changed",
170
- issueId: "i-test",
171
- issue: {
172
- id: "i-test",
173
- uuid: "uuid-test",
174
- title: "Test",
175
- content: "",
176
- status: "closed",
177
- priority: 1,
178
- created_at: "2024-01-01",
179
- updated_at: "2024-01-01",
180
- },
181
- };
182
-
183
- await engine.handleIssueChange(event);
184
-
185
- expect(backend.complete).not.toHaveBeenCalled();
186
- expect(backend.fail).not.toHaveBeenCalled();
187
- expect(events).toHaveLength(1);
188
- expect(events[0].type).toBe("issue_closed");
189
- });
190
-
191
- it("should not affect completed tasks", async () => {
192
- const policy: SyncPolicy = {
193
- ...defaultSyncPolicy,
194
- onIssueClosed: "complete_task",
195
- };
196
- engine = new SyncPolicyEngine(policy, backend);
197
-
198
- backend._setTasks(
199
- new Map([["task-1", { status: "completed" }]])
200
- );
201
- backend._setTasksByIssue("i-test", ["task-1"]);
202
-
203
- const event: IssueChangeEvent = {
204
- type: "status_changed",
205
- issueId: "i-test",
206
- issue: {
207
- id: "i-test",
208
- uuid: "uuid-test",
209
- title: "Test",
210
- content: "",
211
- status: "closed",
212
- priority: 1,
213
- created_at: "2024-01-01",
214
- updated_at: "2024-01-01",
215
- },
216
- };
217
-
218
- await engine.handleIssueChange(event);
219
-
220
- expect(backend.complete).not.toHaveBeenCalled();
221
- });
222
- });
223
-
224
- describe("issue deleted", () => {
225
- it("should always fail orphaned tasks", async () => {
226
- engine = createSyncPolicyEngine({}, backend);
227
-
228
- backend._setTasks(
229
- new Map([
230
- ["task-1", { status: "in_progress" }],
231
- ["task-2", { status: "pending" }],
232
- ])
233
- );
234
- backend._setTasksByIssue("i-test", ["task-1", "task-2"]);
235
-
236
- const events: SyncEvent[] = [];
237
- engine.onSyncEvent((event) => events.push(event));
238
-
239
- const event: IssueChangeEvent = {
240
- type: "deleted",
241
- issueId: "i-test",
242
- };
243
-
244
- await engine.handleIssueChange(event);
245
-
246
- expect(backend.fail).toHaveBeenCalledTimes(2);
247
- expect(backend.fail).toHaveBeenCalledWith("task-1", {
248
- code: "ISSUE_DELETED",
249
- message: "Bound issue i-test was deleted",
250
- });
251
- expect(backend.fail).toHaveBeenCalledWith("task-2", {
252
- code: "ISSUE_DELETED",
253
- message: "Bound issue i-test was deleted",
254
- });
255
-
256
- expect(events).toHaveLength(2);
257
- expect(events[0].type).toBe("issue_deleted");
258
- });
259
-
260
- it("should not fail completed or failed tasks", async () => {
261
- engine = createSyncPolicyEngine({}, backend);
262
-
263
- backend._setTasks(
264
- new Map([
265
- ["task-1", { status: "completed" }],
266
- ["task-2", { status: "failed" }],
267
- ])
268
- );
269
- backend._setTasksByIssue("i-test", ["task-1", "task-2"]);
270
-
271
- const event: IssueChangeEvent = {
272
- type: "deleted",
273
- issueId: "i-test",
274
- };
275
-
276
- await engine.handleIssueChange(event);
277
-
278
- expect(backend.fail).not.toHaveBeenCalled();
279
- });
280
- });
281
-
282
- describe("blocker events", () => {
283
- it("should emit blocker_added event when update_blocked policy", async () => {
284
- const policy: SyncPolicy = {
285
- ...defaultSyncPolicy,
286
- onBlockerChanged: "update_blocked",
287
- };
288
- engine = new SyncPolicyEngine(policy, backend);
289
-
290
- backend._setTasks(
291
- new Map([["task-1", { status: "pending" }]])
292
- );
293
- backend._setTasksByIssue("i-test", ["task-1"]);
294
-
295
- const events: SyncEvent[] = [];
296
- engine.onSyncEvent((event) => events.push(event));
297
-
298
- const event: IssueChangeEvent = {
299
- type: "blocked",
300
- issueId: "i-test",
301
- };
302
-
303
- await engine.handleIssueChange(event);
304
-
305
- expect(events).toHaveLength(1);
306
- expect(events[0].type).toBe("blocker_added");
307
- });
308
-
309
- it("should emit blocker_removed event", async () => {
310
- engine = createSyncPolicyEngine({}, backend);
311
-
312
- backend._setTasks(
313
- new Map([["task-1", { status: "pending" }]])
314
- );
315
- backend._setTasksByIssue("i-test", ["task-1"]);
316
-
317
- const events: SyncEvent[] = [];
318
- engine.onSyncEvent((event) => events.push(event));
319
-
320
- const event: IssueChangeEvent = {
321
- type: "unblocked",
322
- issueId: "i-test",
323
- };
324
-
325
- await engine.handleIssueChange(event);
326
-
327
- expect(events).toHaveLength(1);
328
- expect(events[0].type).toBe("blocker_removed");
329
- });
330
- });
331
-
332
- describe("description changed", () => {
333
- it("should propagate description when policy is propagate", async () => {
334
- const policy: SyncPolicy = {
335
- ...defaultSyncPolicy,
336
- onDescriptionChanged: "propagate",
337
- };
338
- engine = new SyncPolicyEngine(policy, backend);
339
-
340
- backend._setTasks(
341
- new Map([["task-1", { status: "pending" }]])
342
- );
343
- backend._setTasksByIssue("i-test", ["task-1"]);
344
-
345
- const events: SyncEvent[] = [];
346
- engine.onSyncEvent((event) => events.push(event));
347
-
348
- const event: IssueChangeEvent = {
349
- type: "updated",
350
- issueId: "i-test",
351
- issue: {
352
- id: "i-test",
353
- uuid: "uuid-test",
354
- title: "Test",
355
- content: "New description",
356
- status: "open",
357
- priority: 1,
358
- created_at: "2024-01-01",
359
- updated_at: "2024-01-01",
360
- },
361
- previousIssue: {
362
- id: "i-test",
363
- uuid: "uuid-test",
364
- title: "Test",
365
- content: "Old description",
366
- status: "open",
367
- priority: 1,
368
- created_at: "2024-01-01",
369
- updated_at: "2024-01-01",
370
- },
371
- };
372
-
373
- await engine.handleIssueChange(event);
374
-
375
- expect(backend.update).toHaveBeenCalledWith("task-1", {
376
- description: "New description",
377
- });
378
- expect(events).toHaveLength(1);
379
- expect(events[0].type).toBe("description_changed");
380
- });
381
-
382
- it("should not propagate description when policy is snapshot", async () => {
383
- const policy: SyncPolicy = {
384
- ...defaultSyncPolicy,
385
- onDescriptionChanged: "snapshot",
386
- };
387
- engine = new SyncPolicyEngine(policy, backend);
388
-
389
- backend._setTasks(
390
- new Map([["task-1", { status: "pending" }]])
391
- );
392
- backend._setTasksByIssue("i-test", ["task-1"]);
393
-
394
- const events: SyncEvent[] = [];
395
- engine.onSyncEvent((event) => events.push(event));
396
-
397
- const event: IssueChangeEvent = {
398
- type: "updated",
399
- issueId: "i-test",
400
- issue: {
401
- id: "i-test",
402
- uuid: "uuid-test",
403
- title: "Test",
404
- content: "New description",
405
- status: "open",
406
- priority: 1,
407
- created_at: "2024-01-01",
408
- updated_at: "2024-01-01",
409
- },
410
- previousIssue: {
411
- id: "i-test",
412
- uuid: "uuid-test",
413
- title: "Test",
414
- content: "Old description",
415
- status: "open",
416
- priority: 1,
417
- created_at: "2024-01-01",
418
- updated_at: "2024-01-01",
419
- },
420
- };
421
-
422
- await engine.handleIssueChange(event);
423
-
424
- expect(backend.update).not.toHaveBeenCalled();
425
- // Event is still emitted for tracking
426
- expect(events).toHaveLength(1);
427
- });
428
- });
429
-
430
- describe("event subscription", () => {
431
- it("should allow unsubscribing from events", async () => {
432
- engine = createSyncPolicyEngine({}, backend);
433
-
434
- backend._setTasks(
435
- new Map([["task-1", { status: "pending" }]])
436
- );
437
- backend._setTasksByIssue("i-test", ["task-1"]);
438
-
439
- const events: SyncEvent[] = [];
440
- const unsubscribe = engine.onSyncEvent((event) => events.push(event));
441
-
442
- // First event
443
- await engine.handleIssueChange({
444
- type: "blocked",
445
- issueId: "i-test",
446
- });
447
-
448
- expect(events).toHaveLength(1);
449
-
450
- // Unsubscribe
451
- unsubscribe();
452
-
453
- // Second event should not be received
454
- await engine.handleIssueChange({
455
- type: "unblocked",
456
- issueId: "i-test",
457
- });
458
-
459
- expect(events).toHaveLength(1);
460
- });
461
-
462
- it("should handle callback errors gracefully", async () => {
463
- engine = createSyncPolicyEngine({}, backend);
464
-
465
- backend._setTasks(
466
- new Map([["task-1", { status: "pending" }]])
467
- );
468
- backend._setTasksByIssue("i-test", ["task-1"]);
469
-
470
- const events: SyncEvent[] = [];
471
- engine.onSyncEvent(() => {
472
- throw new Error("Callback error");
473
- });
474
- engine.onSyncEvent((event) => events.push(event));
475
-
476
- // Should not throw
477
- await engine.handleIssueChange({
478
- type: "blocked",
479
- issueId: "i-test",
480
- });
481
-
482
- // Second callback should still receive events
483
- expect(events).toHaveLength(1);
484
- });
485
- });
486
-
487
- describe("no bound tasks", () => {
488
- it("should do nothing when no tasks are bound to the issue", async () => {
489
- engine = createSyncPolicyEngine(
490
- { onIssueClosed: "complete_task" },
491
- backend
492
- );
493
-
494
- // No tasks set up for this issue
495
- backend._setTasksByIssue("i-test", []);
496
-
497
- const events: SyncEvent[] = [];
498
- engine.onSyncEvent((event) => events.push(event));
499
-
500
- await engine.handleIssueChange({
501
- type: "status_changed",
502
- issueId: "i-test",
503
- issue: {
504
- id: "i-test",
505
- uuid: "uuid-test",
506
- title: "Test",
507
- content: "",
508
- status: "closed",
509
- priority: 1,
510
- created_at: "2024-01-01",
511
- updated_at: "2024-01-01",
512
- },
513
- });
514
-
515
- expect(backend.complete).not.toHaveBeenCalled();
516
- expect(events).toHaveLength(0);
517
- });
518
- });
519
- });