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