macro-agent 0.1.0 → 0.1.2

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 (660) hide show
  1. package/.claude/settings.local.json +3 -1
  2. package/.sudocode/issues.jsonl +28 -0
  3. package/.sudocode/specs.jsonl +8 -0
  4. package/CLAUDE.md +25 -17
  5. package/README.md +11 -29
  6. package/dist/acp/macro-agent.d.ts +15 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +131 -35
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +32 -1
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/agent/agent-manager.d.ts +65 -1
  14. package/dist/agent/agent-manager.d.ts.map +1 -1
  15. package/dist/agent/agent-manager.js +544 -200
  16. package/dist/agent/agent-manager.js.map +1 -1
  17. package/dist/agent/types.d.ts +8 -1
  18. package/dist/agent/types.d.ts.map +1 -1
  19. package/dist/agent/types.js.map +1 -1
  20. package/dist/api/server.d.ts +8 -1
  21. package/dist/api/server.d.ts.map +1 -1
  22. package/dist/api/server.js +136 -8
  23. package/dist/api/server.js.map +1 -1
  24. package/dist/api/types.d.ts +1 -1
  25. package/dist/api/types.d.ts.map +1 -1
  26. package/dist/auth/index.d.ts +2 -0
  27. package/dist/auth/index.d.ts.map +1 -0
  28. package/dist/auth/index.js +2 -0
  29. package/dist/auth/index.js.map +1 -0
  30. package/dist/auth/token.d.ts +41 -0
  31. package/dist/auth/token.d.ts.map +1 -0
  32. package/dist/auth/token.js +73 -0
  33. package/dist/auth/token.js.map +1 -0
  34. package/dist/cli/acp.d.ts +2 -23
  35. package/dist/cli/acp.d.ts.map +1 -1
  36. package/dist/cli/acp.js +197 -61
  37. package/dist/cli/acp.js.map +1 -1
  38. package/dist/cli/index.js +152 -16
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cli/mcp.d.ts +6 -0
  41. package/dist/cli/mcp.d.ts.map +1 -1
  42. package/dist/cli/mcp.js +279 -173
  43. package/dist/cli/mcp.js.map +1 -1
  44. package/dist/cli/parse-args.d.ts +20 -0
  45. package/dist/cli/parse-args.d.ts.map +1 -0
  46. package/dist/cli/parse-args.js +43 -0
  47. package/dist/cli/parse-args.js.map +1 -0
  48. package/dist/cli/stable-instance-id.d.ts +8 -0
  49. package/dist/cli/stable-instance-id.d.ts.map +1 -0
  50. package/dist/cli/stable-instance-id.js +14 -0
  51. package/dist/cli/stable-instance-id.js.map +1 -0
  52. package/dist/config/project-config.d.ts +85 -7
  53. package/dist/config/project-config.d.ts.map +1 -1
  54. package/dist/config/project-config.js +133 -20
  55. package/dist/config/project-config.js.map +1 -1
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +2 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/lifecycle/handlers/index.d.ts +7 -3
  61. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  62. package/dist/lifecycle/handlers/index.js +25 -8
  63. package/dist/lifecycle/handlers/index.js.map +1 -1
  64. package/dist/lifecycle/types.d.ts +2 -0
  65. package/dist/lifecycle/types.d.ts.map +1 -1
  66. package/dist/lifecycle/types.js.map +1 -1
  67. package/dist/map/adapter/acp-over-map.d.ts +17 -0
  68. package/dist/map/adapter/acp-over-map.d.ts.map +1 -1
  69. package/dist/map/adapter/acp-over-map.js +384 -23
  70. package/dist/map/adapter/acp-over-map.js.map +1 -1
  71. package/dist/map/adapter/connection-manager.d.ts.map +1 -1
  72. package/dist/map/adapter/connection-manager.js +3 -0
  73. package/dist/map/adapter/connection-manager.js.map +1 -1
  74. package/dist/map/adapter/event-log.d.ts +87 -0
  75. package/dist/map/adapter/event-log.d.ts.map +1 -0
  76. package/dist/map/adapter/event-log.js +122 -0
  77. package/dist/map/adapter/event-log.js.map +1 -0
  78. package/dist/map/adapter/event-translator.js +6 -6
  79. package/dist/map/adapter/event-translator.js.map +1 -1
  80. package/dist/map/adapter/extensions/agent-lifecycle.d.ts +82 -0
  81. package/dist/map/adapter/extensions/agent-lifecycle.d.ts.map +1 -0
  82. package/dist/map/adapter/extensions/agent-lifecycle.js +164 -0
  83. package/dist/map/adapter/extensions/agent-lifecycle.js.map +1 -0
  84. package/dist/map/adapter/extensions/index.d.ts +13 -1
  85. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  86. package/dist/map/adapter/extensions/index.js +61 -0
  87. package/dist/map/adapter/extensions/index.js.map +1 -1
  88. package/dist/map/adapter/extensions/mcp-bridge.d.ts +57 -0
  89. package/dist/map/adapter/extensions/mcp-bridge.d.ts.map +1 -0
  90. package/dist/map/adapter/extensions/mcp-bridge.js +745 -0
  91. package/dist/map/adapter/extensions/mcp-bridge.js.map +1 -0
  92. package/dist/map/adapter/extensions/rename.d.ts +29 -0
  93. package/dist/map/adapter/extensions/rename.d.ts.map +1 -0
  94. package/dist/map/adapter/extensions/rename.js +49 -0
  95. package/dist/map/adapter/extensions/rename.js.map +1 -0
  96. package/dist/map/adapter/extensions/streams.d.ts +95 -0
  97. package/dist/map/adapter/extensions/streams.d.ts.map +1 -0
  98. package/dist/map/adapter/extensions/streams.js +515 -0
  99. package/dist/map/adapter/extensions/streams.js.map +1 -0
  100. package/dist/map/adapter/extensions/task.d.ts.map +1 -1
  101. package/dist/map/adapter/extensions/task.js +10 -0
  102. package/dist/map/adapter/extensions/task.js.map +1 -1
  103. package/dist/map/adapter/extensions/update-metadata.d.ts +29 -0
  104. package/dist/map/adapter/extensions/update-metadata.d.ts.map +1 -0
  105. package/dist/map/adapter/extensions/update-metadata.js +67 -0
  106. package/dist/map/adapter/extensions/update-metadata.js.map +1 -0
  107. package/dist/map/adapter/index.d.ts +2 -1
  108. package/dist/map/adapter/index.d.ts.map +1 -1
  109. package/dist/map/adapter/index.js +10 -2
  110. package/dist/map/adapter/index.js.map +1 -1
  111. package/dist/map/adapter/interface.d.ts +2 -0
  112. package/dist/map/adapter/interface.d.ts.map +1 -1
  113. package/dist/map/adapter/map-adapter.d.ts +3 -0
  114. package/dist/map/adapter/map-adapter.d.ts.map +1 -1
  115. package/dist/map/adapter/map-adapter.js +258 -35
  116. package/dist/map/adapter/map-adapter.js.map +1 -1
  117. package/dist/map/adapter/subscription-manager.d.ts.map +1 -1
  118. package/dist/map/adapter/subscription-manager.js +5 -1
  119. package/dist/map/adapter/subscription-manager.js.map +1 -1
  120. package/dist/map/adapter/types.d.ts +3 -1
  121. package/dist/map/adapter/types.d.ts.map +1 -1
  122. package/dist/mcp/map-client.d.ts +39 -0
  123. package/dist/mcp/map-client.d.ts.map +1 -0
  124. package/dist/mcp/map-client.js +129 -0
  125. package/dist/mcp/map-client.js.map +1 -0
  126. package/dist/mcp/mcp-server.d.ts +16 -0
  127. package/dist/mcp/mcp-server.d.ts.map +1 -1
  128. package/dist/mcp/mcp-server.js +125 -88
  129. package/dist/mcp/mcp-server.js.map +1 -1
  130. package/dist/mcp/tools/done.d.ts.map +1 -1
  131. package/dist/mcp/tools/done.js +18 -0
  132. package/dist/mcp/tools/done.js.map +1 -1
  133. package/dist/mcp/types.d.ts +9 -1
  134. package/dist/mcp/types.d.ts.map +1 -1
  135. package/dist/mcp/types.js.map +1 -1
  136. package/dist/metrics/metrics.js +1 -1
  137. package/dist/metrics/metrics.js.map +1 -1
  138. package/dist/roles/builtin/coordinator.d.ts.map +1 -1
  139. package/dist/roles/builtin/coordinator.js +2 -1
  140. package/dist/roles/builtin/coordinator.js.map +1 -1
  141. package/dist/roles/builtin/integrator.d.ts.map +1 -1
  142. package/dist/roles/builtin/integrator.js +2 -1
  143. package/dist/roles/builtin/integrator.js.map +1 -1
  144. package/dist/roles/builtin/worker.d.ts.map +1 -1
  145. package/dist/roles/builtin/worker.js +3 -1
  146. package/dist/roles/builtin/worker.js.map +1 -1
  147. package/dist/roles/capabilities.d.ts +9 -1
  148. package/dist/roles/capabilities.d.ts.map +1 -1
  149. package/dist/roles/capabilities.js +27 -7
  150. package/dist/roles/capabilities.js.map +1 -1
  151. package/dist/roles/config-loader.d.ts +6 -6
  152. package/dist/roles/config-loader.d.ts.map +1 -1
  153. package/dist/roles/config-loader.js +8 -7
  154. package/dist/roles/config-loader.js.map +1 -1
  155. package/dist/roles/registry.d.ts +2 -2
  156. package/dist/roles/registry.js +2 -2
  157. package/dist/roles/types.d.ts +3 -1
  158. package/dist/roles/types.d.ts.map +1 -1
  159. package/dist/server/combined-server.d.ts +28 -1
  160. package/dist/server/combined-server.d.ts.map +1 -1
  161. package/dist/server/combined-server.js +111 -8
  162. package/dist/server/combined-server.js.map +1 -1
  163. package/dist/store/event-store.d.ts +2 -1
  164. package/dist/store/event-store.d.ts.map +1 -1
  165. package/dist/store/event-store.js +80 -24
  166. package/dist/store/event-store.js.map +1 -1
  167. package/dist/store/instance.d.ts +1 -1
  168. package/dist/store/instance.d.ts.map +1 -1
  169. package/dist/store/instance.js +2 -2
  170. package/dist/store/instance.js.map +1 -1
  171. package/dist/store/types/agents.d.ts +23 -0
  172. package/dist/store/types/agents.d.ts.map +1 -1
  173. package/dist/store/types/events.d.ts +1 -1
  174. package/dist/store/types/events.d.ts.map +1 -1
  175. package/dist/task/backend/index.d.ts +47 -29
  176. package/dist/task/backend/index.d.ts.map +1 -1
  177. package/dist/task/backend/index.js +109 -71
  178. package/dist/task/backend/index.js.map +1 -1
  179. package/dist/task/backend/memory.d.ts +1 -0
  180. package/dist/task/backend/memory.d.ts.map +1 -1
  181. package/dist/task/backend/memory.js +3 -0
  182. package/dist/task/backend/memory.js.map +1 -1
  183. package/dist/task/backend/opentasks/backend.d.ts +140 -0
  184. package/dist/task/backend/opentasks/backend.d.ts.map +1 -0
  185. package/dist/task/backend/opentasks/backend.js +1023 -0
  186. package/dist/task/backend/opentasks/backend.js.map +1 -0
  187. package/dist/task/backend/opentasks/client.d.ts +337 -0
  188. package/dist/task/backend/opentasks/client.d.ts.map +1 -0
  189. package/dist/task/backend/opentasks/client.js +225 -0
  190. package/dist/task/backend/opentasks/client.js.map +1 -0
  191. package/dist/task/backend/opentasks/daemon-manager.d.ts +89 -0
  192. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -0
  193. package/dist/task/backend/opentasks/daemon-manager.js +195 -0
  194. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -0
  195. package/dist/task/backend/opentasks/index.d.ts +21 -0
  196. package/dist/task/backend/opentasks/index.d.ts.map +1 -0
  197. package/dist/task/backend/opentasks/index.js +21 -0
  198. package/dist/task/backend/opentasks/index.js.map +1 -0
  199. package/dist/task/backend/opentasks/mapping.d.ts +48 -0
  200. package/dist/task/backend/opentasks/mapping.d.ts.map +1 -0
  201. package/dist/task/backend/opentasks/mapping.js +77 -0
  202. package/dist/task/backend/opentasks/mapping.js.map +1 -0
  203. package/dist/task/backend/types.d.ts +33 -53
  204. package/dist/task/backend/types.d.ts.map +1 -1
  205. package/dist/task/backend/types.js +7 -11
  206. package/dist/task/backend/types.js.map +1 -1
  207. package/dist/task/backend/unified-tool-provider.d.ts +57 -0
  208. package/dist/task/backend/unified-tool-provider.d.ts.map +1 -0
  209. package/dist/task/backend/unified-tool-provider.js +623 -0
  210. package/dist/task/backend/unified-tool-provider.js.map +1 -0
  211. package/dist/teams/index.d.ts +3 -1
  212. package/dist/teams/index.d.ts.map +1 -1
  213. package/dist/teams/index.js +2 -0
  214. package/dist/teams/index.js.map +1 -1
  215. package/dist/teams/seed-defaults.d.ts +20 -0
  216. package/dist/teams/seed-defaults.d.ts.map +1 -0
  217. package/dist/teams/seed-defaults.js +71 -0
  218. package/dist/teams/seed-defaults.js.map +1 -0
  219. package/dist/teams/team-loader.d.ts +7 -3
  220. package/dist/teams/team-loader.d.ts.map +1 -1
  221. package/dist/teams/team-loader.js +156 -164
  222. package/dist/teams/team-loader.js.map +1 -1
  223. package/dist/teams/team-manager.d.ts +112 -0
  224. package/dist/teams/team-manager.d.ts.map +1 -0
  225. package/dist/teams/team-manager.js +305 -0
  226. package/dist/teams/team-manager.js.map +1 -0
  227. package/dist/teams/team-runtime.d.ts +125 -19
  228. package/dist/teams/team-runtime.d.ts.map +1 -1
  229. package/dist/teams/team-runtime.js +529 -119
  230. package/dist/teams/team-runtime.js.map +1 -1
  231. package/dist/teams/types.d.ts +41 -151
  232. package/dist/teams/types.d.ts.map +1 -1
  233. package/dist/teams/types.js +2 -3
  234. package/dist/teams/types.js.map +1 -1
  235. package/docs/architecture.md +7 -6
  236. package/docs/configuration.md +26 -62
  237. package/docs/implementation-details.md +5 -5
  238. package/docs/implementation-summary.md +17 -17
  239. package/docs/plan-self-driving-support.md +4 -4
  240. package/docs/spec-self-driving-support.md +10 -10
  241. package/docs/team-templates.md +2 -2
  242. package/docs/teams.md +76 -3
  243. package/docs/troubleshooting.md +10 -11
  244. package/package.json +7 -4
  245. package/references/minimem/.claude/settings.json +7 -0
  246. package/references/minimem/.sudocode/issues.jsonl +18 -0
  247. package/references/minimem/.sudocode/specs.jsonl +1 -0
  248. package/references/minimem/CLAUDE.md +310 -0
  249. package/references/minimem/README.md +562 -0
  250. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
  251. package/references/minimem/claude-plugin/.mcp.json +7 -0
  252. package/references/minimem/claude-plugin/README.md +158 -0
  253. package/references/minimem/claude-plugin/commands/recall.md +47 -0
  254. package/references/minimem/claude-plugin/commands/remember.md +41 -0
  255. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
  256. package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
  257. package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
  258. package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
  259. package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
  260. package/references/minimem/media/banner.png +0 -0
  261. package/references/minimem/package-lock.json +5373 -0
  262. package/references/minimem/package.json +72 -0
  263. package/references/minimem/scripts/postbuild.js +35 -0
  264. package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
  265. package/references/minimem/src/__tests__/errors.test.ts +265 -0
  266. package/references/minimem/src/__tests__/helpers.ts +199 -0
  267. package/references/minimem/src/__tests__/internal.test.ts +407 -0
  268. package/references/minimem/src/__tests__/knowledge.test.ts +287 -0
  269. package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
  270. package/references/minimem/src/__tests__/session.test.ts +190 -0
  271. package/references/minimem/src/cli/__tests__/commands.test.ts +759 -0
  272. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
  273. package/references/minimem/src/cli/commands/append.ts +76 -0
  274. package/references/minimem/src/cli/commands/config.ts +262 -0
  275. package/references/minimem/src/cli/commands/conflicts.ts +413 -0
  276. package/references/minimem/src/cli/commands/daemon.ts +169 -0
  277. package/references/minimem/src/cli/commands/index.ts +12 -0
  278. package/references/minimem/src/cli/commands/init.ts +88 -0
  279. package/references/minimem/src/cli/commands/mcp.ts +177 -0
  280. package/references/minimem/src/cli/commands/push-pull.ts +213 -0
  281. package/references/minimem/src/cli/commands/search.ts +158 -0
  282. package/references/minimem/src/cli/commands/status.ts +84 -0
  283. package/references/minimem/src/cli/commands/sync-init.ts +290 -0
  284. package/references/minimem/src/cli/commands/sync.ts +70 -0
  285. package/references/minimem/src/cli/commands/upsert.ts +197 -0
  286. package/references/minimem/src/cli/config.ts +584 -0
  287. package/references/minimem/src/cli/index.ts +264 -0
  288. package/references/minimem/src/cli/shared.ts +161 -0
  289. package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
  290. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
  291. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
  292. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
  293. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
  294. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
  295. package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
  296. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
  297. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
  298. package/references/minimem/src/cli/sync/central.ts +292 -0
  299. package/references/minimem/src/cli/sync/conflicts.ts +204 -0
  300. package/references/minimem/src/cli/sync/daemon.ts +407 -0
  301. package/references/minimem/src/cli/sync/detection.ts +138 -0
  302. package/references/minimem/src/cli/sync/index.ts +107 -0
  303. package/references/minimem/src/cli/sync/operations.ts +373 -0
  304. package/references/minimem/src/cli/sync/registry.ts +279 -0
  305. package/references/minimem/src/cli/sync/state.ts +355 -0
  306. package/references/minimem/src/cli/sync/validation.ts +206 -0
  307. package/references/minimem/src/cli/sync/watcher.ts +234 -0
  308. package/references/minimem/src/cli/version.ts +34 -0
  309. package/references/minimem/src/core/index.ts +9 -0
  310. package/references/minimem/src/core/indexer.ts +628 -0
  311. package/references/minimem/src/core/searcher.ts +221 -0
  312. package/references/minimem/src/db/schema.ts +183 -0
  313. package/references/minimem/src/db/sqlite-vec.ts +24 -0
  314. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
  315. package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
  316. package/references/minimem/src/embeddings/batch-openai.ts +409 -0
  317. package/references/minimem/src/embeddings/embeddings.ts +434 -0
  318. package/references/minimem/src/index.ts +109 -0
  319. package/references/minimem/src/internal.ts +299 -0
  320. package/references/minimem/src/minimem.ts +1276 -0
  321. package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
  322. package/references/minimem/src/search/graph.ts +234 -0
  323. package/references/minimem/src/search/hybrid.ts +151 -0
  324. package/references/minimem/src/search/search.ts +256 -0
  325. package/references/minimem/src/server/__tests__/mcp.test.ts +341 -0
  326. package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
  327. package/references/minimem/src/server/mcp.ts +326 -0
  328. package/references/minimem/src/server/tools.ts +720 -0
  329. package/references/minimem/src/session.ts +460 -0
  330. package/references/minimem/tsconfig.json +19 -0
  331. package/references/minimem/tsup.config.ts +26 -0
  332. package/references/minimem/vitest.config.ts +24 -0
  333. package/references/openteams/.claude/settings.json +6 -0
  334. package/references/openteams/README.md +1 -0
  335. package/references/openteams/SKILL.md +341 -0
  336. package/references/openteams/design.md +411 -0
  337. package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +16 -0
  338. package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +5 -0
  339. package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +24 -0
  340. package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +5 -0
  341. package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +25 -0
  342. package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +5 -0
  343. package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +21 -0
  344. package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +5 -0
  345. package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +20 -0
  346. package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +5 -0
  347. package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +17 -0
  348. package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +5 -0
  349. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +23 -0
  350. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +5 -0
  351. package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +27 -0
  352. package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +5 -0
  353. package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +21 -0
  354. package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +5 -0
  355. package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +16 -0
  356. package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +5 -0
  357. package/references/openteams/examples/bmad-method/roles/analyst.yaml +9 -0
  358. package/references/openteams/examples/bmad-method/roles/architect.yaml +9 -0
  359. package/references/openteams/examples/bmad-method/roles/developer.yaml +8 -0
  360. package/references/openteams/examples/bmad-method/roles/master.yaml +8 -0
  361. package/references/openteams/examples/bmad-method/roles/pm.yaml +9 -0
  362. package/references/openteams/examples/bmad-method/roles/qa.yaml +8 -0
  363. package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +8 -0
  364. package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +9 -0
  365. package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +8 -0
  366. package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +8 -0
  367. package/references/openteams/examples/bmad-method/team.yaml +161 -0
  368. package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/ROLE.md +17 -0
  369. package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/SOUL.md +5 -0
  370. package/references/openteams/examples/get-shit-done/prompts/debugger/ROLE.md +25 -0
  371. package/references/openteams/examples/get-shit-done/prompts/debugger/SOUL.md +5 -0
  372. package/references/openteams/examples/get-shit-done/prompts/executor/ROLE.md +34 -0
  373. package/references/openteams/examples/get-shit-done/prompts/executor/SOUL.md +5 -0
  374. package/references/openteams/examples/get-shit-done/prompts/integration-checker/ROLE.md +18 -0
  375. package/references/openteams/examples/get-shit-done/prompts/integration-checker/SOUL.md +3 -0
  376. package/references/openteams/examples/get-shit-done/prompts/orchestrator/ROLE.md +42 -0
  377. package/references/openteams/examples/get-shit-done/prompts/orchestrator/SOUL.md +5 -0
  378. package/references/openteams/examples/get-shit-done/prompts/phase-researcher/ROLE.md +15 -0
  379. package/references/openteams/examples/get-shit-done/prompts/phase-researcher/SOUL.md +3 -0
  380. package/references/openteams/examples/get-shit-done/prompts/plan-checker/ROLE.md +17 -0
  381. package/references/openteams/examples/get-shit-done/prompts/plan-checker/SOUL.md +3 -0
  382. package/references/openteams/examples/get-shit-done/prompts/planner/ROLE.md +28 -0
  383. package/references/openteams/examples/get-shit-done/prompts/planner/SOUL.md +5 -0
  384. package/references/openteams/examples/get-shit-done/prompts/project-researcher/ROLE.md +16 -0
  385. package/references/openteams/examples/get-shit-done/prompts/project-researcher/SOUL.md +3 -0
  386. package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/ROLE.md +13 -0
  387. package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/SOUL.md +3 -0
  388. package/references/openteams/examples/get-shit-done/prompts/roadmapper/ROLE.md +14 -0
  389. package/references/openteams/examples/get-shit-done/prompts/roadmapper/SOUL.md +3 -0
  390. package/references/openteams/examples/get-shit-done/prompts/verifier/ROLE.md +19 -0
  391. package/references/openteams/examples/get-shit-done/prompts/verifier/SOUL.md +5 -0
  392. package/references/openteams/examples/get-shit-done/roles/codebase-mapper.yaml +8 -0
  393. package/references/openteams/examples/get-shit-done/roles/debugger.yaml +8 -0
  394. package/references/openteams/examples/get-shit-done/roles/executor.yaml +8 -0
  395. package/references/openteams/examples/get-shit-done/roles/integration-checker.yaml +8 -0
  396. package/references/openteams/examples/get-shit-done/roles/orchestrator.yaml +9 -0
  397. package/references/openteams/examples/get-shit-done/roles/phase-researcher.yaml +7 -0
  398. package/references/openteams/examples/get-shit-done/roles/plan-checker.yaml +8 -0
  399. package/references/openteams/examples/get-shit-done/roles/planner.yaml +8 -0
  400. package/references/openteams/examples/get-shit-done/roles/project-researcher.yaml +8 -0
  401. package/references/openteams/examples/get-shit-done/roles/research-synthesizer.yaml +7 -0
  402. package/references/openteams/examples/get-shit-done/roles/roadmapper.yaml +7 -0
  403. package/references/openteams/examples/get-shit-done/roles/verifier.yaml +8 -0
  404. package/references/openteams/examples/get-shit-done/team.yaml +154 -0
  405. package/references/openteams/package-lock.json +2181 -0
  406. package/references/openteams/package.json +48 -0
  407. package/references/openteams/schema/role.schema.json +125 -0
  408. package/references/openteams/schema/team.schema.json +284 -0
  409. package/references/openteams/src/cli/agent.ts +104 -0
  410. package/references/openteams/src/cli/cli.test.ts +381 -0
  411. package/references/openteams/src/cli/generate.ts +220 -0
  412. package/references/openteams/src/cli/message.ts +241 -0
  413. package/references/openteams/src/cli/task.ts +154 -0
  414. package/references/openteams/src/cli/team.ts +104 -0
  415. package/references/openteams/src/cli/template.ts +207 -0
  416. package/references/openteams/src/cli.ts +45 -0
  417. package/references/openteams/src/db/database.test.ts +185 -0
  418. package/references/openteams/src/db/database.ts +240 -0
  419. package/references/openteams/src/generators/agent-prompt-generator.test.ts +332 -0
  420. package/references/openteams/src/generators/agent-prompt-generator.ts +521 -0
  421. package/references/openteams/src/generators/package-generator.test.ts +129 -0
  422. package/references/openteams/src/generators/package-generator.ts +102 -0
  423. package/references/openteams/src/generators/skill-generator.test.ts +246 -0
  424. package/references/openteams/src/generators/skill-generator.ts +374 -0
  425. package/references/openteams/src/index.ts +104 -0
  426. package/references/openteams/src/services/agent-service.test.ts +158 -0
  427. package/references/openteams/src/services/agent-service.ts +84 -0
  428. package/references/openteams/src/services/communication-service.test.ts +455 -0
  429. package/references/openteams/src/services/communication-service.ts +371 -0
  430. package/references/openteams/src/services/message-service.test.ts +342 -0
  431. package/references/openteams/src/services/message-service.ts +203 -0
  432. package/references/openteams/src/services/task-service.test.ts +434 -0
  433. package/references/openteams/src/services/task-service.ts +239 -0
  434. package/references/openteams/src/services/team-service.test.ts +181 -0
  435. package/references/openteams/src/services/team-service.ts +139 -0
  436. package/references/openteams/src/services/template-service.test.ts +306 -0
  437. package/references/openteams/src/services/template-service.ts +182 -0
  438. package/references/openteams/src/spawner/acp-factory.ts +96 -0
  439. package/references/openteams/src/spawner/interface.ts +31 -0
  440. package/references/openteams/src/spawner/mock.test.ts +93 -0
  441. package/references/openteams/src/spawner/mock.ts +59 -0
  442. package/references/openteams/src/template/loader.test.ts +1319 -0
  443. package/references/openteams/src/template/loader.ts +698 -0
  444. package/references/openteams/src/template/types.ts +200 -0
  445. package/references/openteams/src/types.ts +205 -0
  446. package/references/openteams/tsconfig.json +18 -0
  447. package/references/openteams/vitest.config.ts +9 -0
  448. package/references/skill-tree/.claude/settings.json +6 -0
  449. package/references/skill-tree/.sudocode/issues.jsonl +11 -0
  450. package/references/skill-tree/.sudocode/specs.jsonl +1 -0
  451. package/references/skill-tree/CLAUDE.md +150 -0
  452. package/references/skill-tree/README.md +324 -0
  453. package/references/skill-tree/docs/GAPS_v1.md +221 -0
  454. package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
  455. package/references/skill-tree/docs/TODOS.md +91 -0
  456. package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
  457. package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
  458. package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
  459. package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
  460. package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
  461. package/references/skill-tree/docs/scraper/README.md +170 -0
  462. package/references/skill-tree/examples/basic-usage.ts +190 -0
  463. package/references/skill-tree/package-lock.json +1509 -0
  464. package/references/skill-tree/package.json +66 -0
  465. package/references/skill-tree/scraper/README.md +123 -0
  466. package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
  467. package/references/skill-tree/scraper/docs/PLAN.md +336 -0
  468. package/references/skill-tree/scraper/drizzle.config.ts +10 -0
  469. package/references/skill-tree/scraper/package-lock.json +6329 -0
  470. package/references/skill-tree/scraper/package.json +68 -0
  471. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
  472. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
  473. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
  474. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
  475. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
  476. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
  477. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
  478. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
  479. package/references/skill-tree/scraper/tsup.config.ts +14 -0
  480. package/references/skill-tree/scraper/vitest.config.ts +17 -0
  481. package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
  482. package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
  483. package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
  484. package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
  485. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
  486. package/references/skill-tree/test/run-all.ts +106 -0
  487. package/references/skill-tree/test/utils.ts +128 -0
  488. package/references/skill-tree/vitest.config.ts +16 -0
  489. package/src/__tests__/e2e/agent-spawn-visibility.e2e.test.ts +761 -0
  490. package/src/__tests__/e2e/full-agent-conflict-resolution.e2e.test.ts +2 -2
  491. package/src/__tests__/e2e/mcp-thin-client-bridge.e2e.test.ts +304 -0
  492. package/src/__tests__/e2e/mcp-tools-available.e2e.test.ts +324 -0
  493. package/src/__tests__/e2e/multi-agent.e2e.test.ts +5 -5
  494. package/src/__tests__/e2e/spawn-session-streaming.e2e.test.ts +563 -0
  495. package/src/acp/__tests__/integration.test.ts +56 -31
  496. package/src/acp/__tests__/macro-agent.test.ts +16 -7
  497. package/src/acp/macro-agent.ts +170 -36
  498. package/src/acp/types.ts +46 -1
  499. package/src/agent/__tests__/agent-manager.test.ts +228 -2
  500. package/src/agent/agent-manager.ts +809 -285
  501. package/src/agent/types.ts +12 -1
  502. package/src/api/__tests__/server.test.ts +203 -4
  503. package/src/api/server.ts +169 -10
  504. package/src/api/types.ts +3 -1
  505. package/src/auth/__tests__/token.test.ts +100 -0
  506. package/src/auth/index.ts +1 -0
  507. package/src/auth/token.ts +82 -0
  508. package/src/cli/__tests__/acp.test.ts +1 -1
  509. package/src/cli/__tests__/stable-instance-id.test.ts +1 -1
  510. package/src/cli/acp.ts +197 -72
  511. package/src/cli/index.ts +125 -15
  512. package/src/cli/mcp.ts +315 -197
  513. package/src/cli/parse-args.ts +54 -0
  514. package/src/cli/stable-instance-id.ts +14 -0
  515. package/src/config/project-config.ts +214 -27
  516. package/src/index.ts +3 -0
  517. package/src/lifecycle/__tests__/cascade-termination.test.ts +1 -1
  518. package/src/lifecycle/__tests__/handlers.test.ts +53 -0
  519. package/src/lifecycle/handlers/index.ts +25 -8
  520. package/src/lifecycle/types.ts +3 -0
  521. package/src/map/adapter/__tests__/acp-over-map-cancel.test.ts +22 -4
  522. package/src/map/adapter/__tests__/acp-over-map-getmodels.test.ts +355 -0
  523. package/src/map/adapter/__tests__/acp-over-map-history.test.ts +263 -0
  524. package/src/map/adapter/__tests__/acp-over-map-persistence.e2e.test.ts +1 -1
  525. package/src/map/adapter/__tests__/event-broadcast.test.ts +420 -0
  526. package/src/map/adapter/__tests__/event-log.test.ts +527 -0
  527. package/src/map/adapter/__tests__/event-translator.test.ts +3 -3
  528. package/src/map/adapter/__tests__/extensions.test.ts +408 -0
  529. package/src/map/adapter/__tests__/map-adapter.test.ts +99 -0
  530. package/src/map/adapter/__tests__/mcp-bridge.test.ts +1187 -0
  531. package/src/map/adapter/__tests__/multi-client-broadcast.test.ts +711 -0
  532. package/src/map/adapter/__tests__/stream-extensions.test.ts +494 -0
  533. package/src/map/adapter/__tests__/websocket-integration.test.ts +218 -0
  534. package/src/map/adapter/acp-over-map.ts +678 -66
  535. package/src/map/adapter/connection-manager.ts +3 -0
  536. package/src/map/adapter/event-log.ts +208 -0
  537. package/src/map/adapter/event-translator.ts +6 -6
  538. package/src/map/adapter/extensions/agent-lifecycle.ts +267 -0
  539. package/src/map/adapter/extensions/index.ts +96 -0
  540. package/src/map/adapter/extensions/mcp-bridge.ts +995 -0
  541. package/src/map/adapter/extensions/streams.ts +839 -0
  542. package/src/map/adapter/extensions/task.ts +11 -0
  543. package/src/map/adapter/extensions/update-metadata.ts +126 -0
  544. package/src/map/adapter/index.ts +33 -0
  545. package/src/map/adapter/interface.ts +2 -0
  546. package/src/map/adapter/map-adapter.ts +312 -47
  547. package/src/map/adapter/subscription-manager.ts +5 -1
  548. package/src/map/adapter/types.ts +10 -1
  549. package/src/mcp/__tests__/map-client.test.ts +386 -0
  550. package/src/mcp/__tests__/mcp-server-thin-client.test.ts +368 -0
  551. package/src/mcp/__tests__/mcp-server.test.ts +100 -1
  552. package/src/mcp/map-client.ts +177 -0
  553. package/src/mcp/mcp-server.ts +205 -103
  554. package/src/mcp/tools/done.ts +19 -0
  555. package/src/mcp/types.ts +6 -1
  556. package/src/metrics/metrics.ts +1 -1
  557. package/src/monitor/__tests__/stale-agent-flow.integration.test.ts +1 -1
  558. package/src/roles/__tests__/config-loader.test.ts +7 -7
  559. package/src/roles/builtin/coordinator.ts +2 -0
  560. package/src/roles/builtin/integrator.ts +2 -0
  561. package/src/roles/builtin/worker.ts +3 -0
  562. package/src/roles/capabilities.ts +28 -7
  563. package/src/roles/config-loader.ts +8 -7
  564. package/src/roles/registry.ts +2 -2
  565. package/src/roles/types.ts +7 -0
  566. package/src/server/__tests__/combined-server.test.ts +94 -21
  567. package/src/server/combined-server.ts +203 -33
  568. package/src/steering/__tests__/steering-integration.test.ts +1 -1
  569. package/src/store/__tests__/event-store-oob.test.ts +109 -0
  570. package/src/store/__tests__/event-store.test.ts +196 -1
  571. package/src/store/__tests__/instance.test.ts +3 -3
  572. package/src/store/event-store.ts +92 -23
  573. package/src/store/instance.ts +2 -2
  574. package/src/store/types/agents.ts +20 -0
  575. package/src/store/types/events.ts +1 -1
  576. package/src/task/backend/__tests__/create-task-backend.test.ts +225 -0
  577. package/src/task/backend/__tests__/e2e/unified-tool-provider-opentasks.e2e.test.ts +524 -0
  578. package/src/task/backend/__tests__/memory-pull-mode.test.ts +153 -0
  579. package/src/task/backend/__tests__/unified-tool-provider.test.ts +579 -0
  580. package/src/task/backend/index.ts +156 -106
  581. package/src/task/backend/memory.ts +4 -0
  582. package/src/task/backend/opentasks/__tests__/backend.test.ts +968 -0
  583. package/src/task/backend/opentasks/__tests__/daemon-manager.test.ts +406 -0
  584. package/src/task/backend/opentasks/__tests__/mapping.test.ts +84 -0
  585. package/src/task/backend/opentasks/__tests__/opentasks-backend.e2e.test.ts +1338 -0
  586. package/src/task/backend/opentasks/backend.ts +1323 -0
  587. package/src/task/backend/opentasks/client.ts +652 -0
  588. package/src/task/backend/opentasks/daemon-manager.ts +256 -0
  589. package/src/task/backend/opentasks/index.ts +69 -0
  590. package/src/task/backend/opentasks/mapping.ts +94 -0
  591. package/src/task/backend/types.ts +42 -66
  592. package/src/task/backend/unified-tool-provider.ts +779 -0
  593. package/src/teams/CLAUDE.md +180 -0
  594. package/src/teams/__tests__/cross-subsystem.integration.test.ts +1 -1
  595. package/src/teams/__tests__/e2e/workspace-isolation.e2e.test.ts +1263 -0
  596. package/src/teams/__tests__/team-manager.test.ts +814 -0
  597. package/src/teams/__tests__/team-system.test.ts +1291 -8
  598. package/src/teams/index.ts +21 -3
  599. package/src/teams/seed-defaults.ts +79 -0
  600. package/src/teams/team-loader.ts +202 -236
  601. package/src/teams/team-manager.ts +387 -0
  602. package/src/teams/team-runtime.ts +592 -121
  603. package/src/teams/types.ts +99 -200
  604. package/test_fixtures/README.md +2 -3
  605. package/test_fixtures/fixtures/index.ts +0 -3
  606. package/test_fixtures/fixtures/projects/project-with-specs.ts +7 -149
  607. package/test_fixtures/fixtures/repos/index.ts +1 -3
  608. package/test_fixtures/fixtures/repos/temp-repo-factory.ts +0 -116
  609. package/test_fixtures/fixtures/repos/types.ts +0 -11
  610. package/test_fixtures/harness/__tests__/fixtures.test.ts +10 -102
  611. package/test_fixtures/harness/__tests__/temp-repo-and-simulator.test.ts +0 -33
  612. package/test_fixtures/harness/simulator/agent-simulator.ts +4 -4
  613. package/vitest.config.ts +1 -1
  614. package/vitest.e2e.config.ts +1 -1
  615. package/vitest.setup.ts +1 -30
  616. package/.macro-agent/teams/self-driving/prompts/grinder.md +0 -27
  617. package/.macro-agent/teams/self-driving/prompts/judge.md +0 -27
  618. package/.macro-agent/teams/self-driving/prompts/planner.md +0 -33
  619. package/.macro-agent/teams/self-driving/roles/grinder.yaml +0 -17
  620. package/.macro-agent/teams/self-driving/roles/judge.yaml +0 -24
  621. package/.macro-agent/teams/self-driving/roles/planner.yaml +0 -18
  622. package/.macro-agent/teams/self-driving/team.yaml +0 -103
  623. package/.macro-agent/teams/structured/prompts/developer.md +0 -26
  624. package/.macro-agent/teams/structured/prompts/lead.md +0 -25
  625. package/.macro-agent/teams/structured/prompts/reviewer.md +0 -24
  626. package/.macro-agent/teams/structured/roles/developer.yaml +0 -12
  627. package/.macro-agent/teams/structured/roles/lead.yaml +0 -11
  628. package/.macro-agent/teams/structured/roles/reviewer.yaml +0 -19
  629. package/.macro-agent/teams/structured/team.yaml +0 -89
  630. package/docs/sudocode-integration.md +0 -383
  631. package/src/task/backend/__tests__/backend-parity.test.ts +0 -451
  632. package/src/task/backend/__tests__/tool-provider-edge-cases.test.ts +0 -430
  633. package/src/task/backend/__tests__/tool-provider.test.ts +0 -983
  634. package/src/task/backend/sudocode/__tests__/backend-edge-cases.test.ts +0 -575
  635. package/src/task/backend/sudocode/__tests__/backend.test.ts +0 -1194
  636. package/src/task/backend/sudocode/__tests__/client-integration.test.ts +0 -418
  637. package/src/task/backend/sudocode/__tests__/client.test.ts +0 -345
  638. package/src/task/backend/sudocode/__tests__/e2e/backend.e2e.test.ts +0 -753
  639. package/src/task/backend/sudocode/__tests__/e2e/server-client.e2e.test.ts +0 -680
  640. package/src/task/backend/sudocode/__tests__/e2e-workflow.test.ts +0 -666
  641. package/src/task/backend/sudocode/__tests__/integration/standalone-client.integration.test.ts +0 -396
  642. package/src/task/backend/sudocode/__tests__/integration/sudocode-cli.integration.test.ts +0 -328
  643. package/src/task/backend/sudocode/__tests__/integration/test-utils.ts +0 -175
  644. package/src/task/backend/sudocode/__tests__/mapping-edge-cases.test.ts +0 -265
  645. package/src/task/backend/sudocode/__tests__/server-client.test.ts +0 -675
  646. package/src/task/backend/sudocode/__tests__/sync-policy-edge-cases.test.ts +0 -521
  647. package/src/task/backend/sudocode/__tests__/sync-policy.test.ts +0 -519
  648. package/src/task/backend/sudocode/__tests__/tools.test.ts +0 -471
  649. package/src/task/backend/sudocode/backend.ts +0 -1237
  650. package/src/task/backend/sudocode/client.ts +0 -515
  651. package/src/task/backend/sudocode/index.ts +0 -120
  652. package/src/task/backend/sudocode/mapping.ts +0 -93
  653. package/src/task/backend/sudocode/server-client.ts +0 -522
  654. package/src/task/backend/sudocode/standalone-client.ts +0 -623
  655. package/src/task/backend/sudocode/sync-policy.ts +0 -387
  656. package/src/task/backend/sudocode/tools.ts +0 -896
  657. package/src/task/backend/tool-provider.ts +0 -506
  658. package/test_fixtures/fixtures/sudocode/index.ts +0 -29
  659. package/test_fixtures/fixtures/sudocode/issues.ts +0 -185
  660. package/test_fixtures/fixtures/sudocode/specs.ts +0 -159
@@ -0,0 +1,968 @@
1
+ /**
2
+ * Tests for OpenTasksTaskBackend
3
+ *
4
+ * Uses a mock OpenTasksClient to test the backend without requiring
5
+ * a running OpenTasks daemon.
6
+ *
7
+ * @module task/backend/opentasks/__tests__/backend.test
8
+ */
9
+
10
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
11
+ import { createEventStore, type EventStore } from "../../../../store/event-store.js";
12
+ import {
13
+ OpenTasksTaskBackend,
14
+ OpenTasksBackendError,
15
+ createOpenTasksTaskBackend,
16
+ } from "../backend.js";
17
+ import type { OpenTasksClient, OpenTasksIssue, OpenTasksNodeSummary } from "../client.js";
18
+
19
+ // =============================================================================
20
+ // Mock Client
21
+ // =============================================================================
22
+
23
+ function createMockClient(): OpenTasksClient {
24
+ const issues = new Map<string, OpenTasksIssue>();
25
+ let issueCounter = 0;
26
+
27
+ const edges: Array<{ fromId: string; toId: string; type: string }> = [];
28
+
29
+ return {
30
+ createIssue: vi.fn(async (input) => {
31
+ issueCounter++;
32
+ const id = `i-mock${issueCounter}`;
33
+ const issue: OpenTasksIssue = {
34
+ id,
35
+ uuid: `uuid-${id}`,
36
+ type: "issue",
37
+ title: input.title,
38
+ content: input.content,
39
+ status: input.status ?? "open",
40
+ assignee: input.assignee,
41
+ priority: input.priority,
42
+ tags: input.tags,
43
+ parent_id: input.parent_id,
44
+ created_at: new Date().toISOString(),
45
+ updated_at: new Date().toISOString(),
46
+ metadata: input.metadata,
47
+ };
48
+ issues.set(id, issue);
49
+ return issue;
50
+ }),
51
+
52
+ getIssue: vi.fn(async (id) => {
53
+ return issues.get(id) ?? null;
54
+ }),
55
+
56
+ updateIssue: vi.fn(async (id, updates) => {
57
+ const issue = issues.get(id);
58
+ if (!issue) throw new Error(`Issue not found: ${id}`);
59
+ const updated = { ...issue, ...updates, updated_at: new Date().toISOString() };
60
+ // Merge metadata instead of replacing
61
+ if (updates.metadata && issue.metadata) {
62
+ updated.metadata = { ...issue.metadata, ...updates.metadata };
63
+ }
64
+ issues.set(id, updated);
65
+ return updated;
66
+ }),
67
+
68
+ deleteIssue: vi.fn(async (id) => {
69
+ issues.delete(id);
70
+ }),
71
+
72
+ listIssues: vi.fn(async (filter) => {
73
+ let result = Array.from(issues.values());
74
+ if (filter?.status) {
75
+ const statuses = Array.isArray(filter.status)
76
+ ? filter.status
77
+ : [filter.status];
78
+ result = result.filter((i) => statuses.includes(i.status));
79
+ }
80
+ if (filter?.archived === false) {
81
+ result = result.filter((i) => !i.archived);
82
+ }
83
+ return result;
84
+ }),
85
+
86
+ getReadyIssues: vi.fn(async () => {
87
+ return Array.from(issues.values())
88
+ .filter((i) => i.status === "open" && !i.assignee)
89
+ .map((i) => ({
90
+ id: i.id,
91
+ type: "issue",
92
+ title: i.title,
93
+ status: i.status,
94
+ priority: i.priority,
95
+ archived: false,
96
+ }));
97
+ }),
98
+
99
+ createEdge: vi.fn(async (fromId, toId, type) => {
100
+ edges.push({ fromId, toId, type });
101
+ return {
102
+ id: `x-edge${edges.length}`,
103
+ uuid: `uuid-edge${edges.length}`,
104
+ from_id: fromId,
105
+ to_id: toId,
106
+ type,
107
+ created_at: new Date().toISOString(),
108
+ };
109
+ }),
110
+
111
+ removeEdge: vi.fn(async (fromId, toId, type) => {
112
+ const idx = edges.findIndex(
113
+ (e) => e.fromId === fromId && e.toId === toId && e.type === type
114
+ );
115
+ if (idx >= 0) edges.splice(idx, 1);
116
+ }),
117
+
118
+ getBlockers: vi.fn(async (nodeId) => {
119
+ // Find edges where nodeId is the target (blocked by from)
120
+ const blockerIds = edges
121
+ .filter((e) => e.toId === nodeId && e.type === "blocks")
122
+ .map((e) => e.fromId);
123
+
124
+ return blockerIds
125
+ .map((id) => {
126
+ const issue = issues.get(id);
127
+ if (!issue) return null;
128
+ return {
129
+ id: issue.id,
130
+ type: "issue",
131
+ title: issue.title,
132
+ status: issue.status,
133
+ priority: issue.priority,
134
+ archived: false,
135
+ } as OpenTasksNodeSummary;
136
+ })
137
+ .filter((s): s is OpenTasksNodeSummary => s !== null);
138
+ }),
139
+
140
+ getBlocking: vi.fn(async (nodeId) => {
141
+ const blockedIds = edges
142
+ .filter((e) => e.fromId === nodeId && e.type === "blocks")
143
+ .map((e) => e.toId);
144
+
145
+ return blockedIds
146
+ .map((id) => {
147
+ const issue = issues.get(id);
148
+ if (!issue) return null;
149
+ return {
150
+ id: issue.id,
151
+ type: "issue",
152
+ title: issue.title,
153
+ status: issue.status,
154
+ priority: issue.priority,
155
+ archived: false,
156
+ } as OpenTasksNodeSummary;
157
+ })
158
+ .filter((s): s is OpenTasksNodeSummary => s !== null);
159
+ }),
160
+
161
+ isConnected: vi.fn(() => true),
162
+ connect: vi.fn(async () => {}),
163
+ disconnect: vi.fn(() => {}),
164
+ };
165
+ }
166
+
167
+ // =============================================================================
168
+ // Tests
169
+ // =============================================================================
170
+
171
+ describe("OpenTasksTaskBackend", () => {
172
+ let eventStore: EventStore;
173
+ let client: OpenTasksClient;
174
+ let backend: OpenTasksTaskBackend;
175
+ const testAgentId = "agent_test123";
176
+
177
+ beforeEach(async () => {
178
+ eventStore = await createEventStore({ inMemory: true });
179
+ client = createMockClient();
180
+ backend = createOpenTasksTaskBackend(eventStore, client);
181
+ });
182
+
183
+ afterEach(async () => {
184
+ await eventStore.close();
185
+ });
186
+
187
+ // ─────────────────────────────────────────────────────────────────────────────
188
+ // Lifecycle
189
+ // ─────────────────────────────────────────────────────────────────────────────
190
+
191
+ describe("create", () => {
192
+ it("should create a task and issue in OpenTasks", async () => {
193
+ const task = await backend.create({
194
+ description: "Test task",
195
+ created_by: testAgentId,
196
+ });
197
+
198
+ expect(task.id).toMatch(/^task_/);
199
+ expect(task.description).toBe("Test task");
200
+ expect(task.status).toBe("pending");
201
+ expect(task.created_by).toBe(testAgentId);
202
+ expect(task.external_id).toMatch(/^i-mock/);
203
+
204
+ // Verify client was called
205
+ expect(client.createIssue).toHaveBeenCalledWith(
206
+ expect.objectContaining({
207
+ title: "Test task",
208
+ status: "open",
209
+ metadata: expect.objectContaining({
210
+ macro_agent_task_id: task.id,
211
+ created_by: testAgentId,
212
+ }),
213
+ })
214
+ );
215
+ });
216
+
217
+ it("should create a subtask with parent reference", async () => {
218
+ const parent = await backend.create({
219
+ description: "Parent task",
220
+ created_by: testAgentId,
221
+ });
222
+
223
+ const child = await backend.create({
224
+ description: "Child task",
225
+ created_by: testAgentId,
226
+ parent_task: parent.id,
227
+ });
228
+
229
+ expect(child.parent_task).toBe(parent.id);
230
+
231
+ // Verify parent's subtasks array is updated
232
+ const updatedParent = await backend.get(parent.id);
233
+ expect(updatedParent?.subtasks).toContain(child.id);
234
+ });
235
+
236
+ it("should pass tags to OpenTasks when creating", async () => {
237
+ await backend.create({
238
+ description: "Tagged task",
239
+ created_by: testAgentId,
240
+ tags: ["auth", "backend"],
241
+ });
242
+
243
+ // Tags are passed to OpenTasks (EventStore doesn't persist tags natively)
244
+ expect(client.createIssue).toHaveBeenCalledWith(
245
+ expect.objectContaining({
246
+ tags: ["auth", "backend"],
247
+ })
248
+ );
249
+ });
250
+ });
251
+
252
+ describe("get", () => {
253
+ it("should return task by ID", async () => {
254
+ const task = await backend.create({
255
+ description: "Test task",
256
+ created_by: testAgentId,
257
+ });
258
+
259
+ const retrieved = await backend.get(task.id);
260
+ expect(retrieved).not.toBeNull();
261
+ expect(retrieved!.id).toBe(task.id);
262
+ expect(retrieved!.external_id).toBe(task.external_id);
263
+ });
264
+
265
+ it("should return null for non-existent task", async () => {
266
+ const result = await backend.get("task_nonexistent");
267
+ expect(result).toBeNull();
268
+ });
269
+ });
270
+
271
+ describe("delete", () => {
272
+ it("should delete the task and issue", async () => {
273
+ const task = await backend.create({
274
+ description: "To delete",
275
+ created_by: testAgentId,
276
+ });
277
+
278
+ await backend.delete(task.id);
279
+ expect(client.deleteIssue).toHaveBeenCalledWith(task.external_id);
280
+ });
281
+ });
282
+
283
+ // ─────────────────────────────────────────────────────────────────────────────
284
+ // Status Transitions
285
+ // ─────────────────────────────────────────────────────────────────────────────
286
+
287
+ describe("assign", () => {
288
+ it("should assign task and sync to OpenTasks", async () => {
289
+ const task = await backend.create({
290
+ description: "Assign me",
291
+ created_by: testAgentId,
292
+ });
293
+
294
+ await backend.assign(task.id, "agent_worker1");
295
+
296
+ const updated = await backend.get(task.id);
297
+ expect(updated!.status).toBe("assigned");
298
+ expect(updated!.assigned_agent).toBe("agent_worker1");
299
+
300
+ // Verify OpenTasks was updated
301
+ expect(client.updateIssue).toHaveBeenCalledWith(
302
+ task.external_id,
303
+ expect.objectContaining({
304
+ assignee: "agent_worker1",
305
+ })
306
+ );
307
+ });
308
+ });
309
+
310
+ describe("unassign", () => {
311
+ it("should unassign task and clear in OpenTasks", async () => {
312
+ const task = await backend.create({
313
+ description: "Unassign me",
314
+ created_by: testAgentId,
315
+ });
316
+
317
+ await backend.assign(task.id, "agent_worker1");
318
+ await backend.unassign(task.id);
319
+
320
+ const updated = await backend.get(task.id);
321
+ expect(updated!.assigned_agent).toBeUndefined();
322
+
323
+ // Verify OpenTasks cleared
324
+ expect(client.updateIssue).toHaveBeenCalledWith(
325
+ task.external_id,
326
+ expect.objectContaining({
327
+ assignee: null,
328
+ })
329
+ );
330
+ });
331
+
332
+ it("should throw for non-assigned task", async () => {
333
+ const task = await backend.create({
334
+ description: "Not assigned",
335
+ created_by: testAgentId,
336
+ });
337
+
338
+ await expect(backend.unassign(task.id)).rejects.toThrow(
339
+ OpenTasksBackendError
340
+ );
341
+ });
342
+ });
343
+
344
+ describe("start", () => {
345
+ it("should transition to in_progress and sync", async () => {
346
+ const task = await backend.create({
347
+ description: "Start me",
348
+ created_by: testAgentId,
349
+ });
350
+
351
+ await backend.start(task.id);
352
+
353
+ const updated = await backend.get(task.id);
354
+ expect(updated!.status).toBe("in_progress");
355
+
356
+ expect(client.updateIssue).toHaveBeenCalledWith(
357
+ task.external_id,
358
+ expect.objectContaining({ status: "in_progress" })
359
+ );
360
+ });
361
+
362
+ it("should reject invalid transitions", async () => {
363
+ const task = await backend.create({
364
+ description: "Complete me first",
365
+ created_by: testAgentId,
366
+ });
367
+
368
+ // Move to completed
369
+ await backend.start(task.id);
370
+ await backend.complete(task.id);
371
+
372
+ // Can't start a completed task
373
+ await expect(backend.start(task.id)).rejects.toThrow(
374
+ OpenTasksBackendError
375
+ );
376
+ });
377
+ });
378
+
379
+ describe("complete", () => {
380
+ it("should complete task with outputs and close issue", async () => {
381
+ const task = await backend.create({
382
+ description: "Complete me",
383
+ created_by: testAgentId,
384
+ });
385
+
386
+ await backend.start(task.id);
387
+ await backend.complete(task.id, {
388
+ summary: "Done!",
389
+ data: { files_changed: 3 },
390
+ });
391
+
392
+ const updated = await backend.get(task.id);
393
+ expect(updated!.status).toBe("completed");
394
+ expect(updated!.outputs).toEqual(
395
+ expect.objectContaining({ summary: "Done!" })
396
+ );
397
+
398
+ // Verify issue was closed
399
+ expect(client.updateIssue).toHaveBeenCalledWith(
400
+ task.external_id,
401
+ expect.objectContaining({ status: "closed" })
402
+ );
403
+ });
404
+ });
405
+
406
+ describe("fail", () => {
407
+ it("should fail task and close issue with error metadata", async () => {
408
+ const task = await backend.create({
409
+ description: "Fail me",
410
+ created_by: testAgentId,
411
+ });
412
+
413
+ await backend.start(task.id);
414
+ await backend.fail(task.id, {
415
+ message: "Something went wrong",
416
+ code: "COMPILE_ERROR",
417
+ });
418
+
419
+ const updated = await backend.get(task.id);
420
+ expect(updated!.status).toBe("failed");
421
+
422
+ // Verify issue was closed with error metadata
423
+ expect(client.updateIssue).toHaveBeenCalledWith(
424
+ task.external_id,
425
+ expect.objectContaining({
426
+ status: "closed",
427
+ metadata: expect.objectContaining({
428
+ macro_agent_failed: true,
429
+ macro_agent_error: "Something went wrong",
430
+ }),
431
+ })
432
+ );
433
+ });
434
+ });
435
+
436
+ // ─────────────────────────────────────────────────────────────────────────────
437
+ // Dependencies
438
+ // ─────────────────────────────────────────────────────────────────────────────
439
+
440
+ describe("addBlocker / removeBlocker", () => {
441
+ it("should create a blocks edge in OpenTasks", async () => {
442
+ const blocker = await backend.create({
443
+ description: "Blocker task",
444
+ created_by: testAgentId,
445
+ });
446
+
447
+ const blocked = await backend.create({
448
+ description: "Blocked task",
449
+ created_by: testAgentId,
450
+ });
451
+
452
+ await backend.addBlocker(blocked.id, blocker.id);
453
+
454
+ // Verify edge was created
455
+ expect(client.createEdge).toHaveBeenCalledWith(
456
+ blocker.external_id,
457
+ blocked.external_id,
458
+ "blocks"
459
+ );
460
+
461
+ // Verify task is now blocked
462
+ const updated = await backend.get(blocked.id);
463
+ expect(updated!.isBlocked).toBe(true);
464
+ });
465
+
466
+ it("should remove a blocks edge in OpenTasks", async () => {
467
+ const blocker = await backend.create({
468
+ description: "Blocker task",
469
+ created_by: testAgentId,
470
+ });
471
+
472
+ const blocked = await backend.create({
473
+ description: "Blocked task",
474
+ created_by: testAgentId,
475
+ });
476
+
477
+ await backend.addBlocker(blocked.id, blocker.id);
478
+ await backend.removeBlocker(blocked.id, blocker.id);
479
+
480
+ expect(client.removeEdge).toHaveBeenCalledWith(
481
+ blocker.external_id,
482
+ blocked.external_id,
483
+ "blocks"
484
+ );
485
+ });
486
+ });
487
+
488
+ describe("getBlockers / getBlocking", () => {
489
+ it("should return blockers from OpenTasks graph", async () => {
490
+ const blocker = await backend.create({
491
+ description: "Blocker",
492
+ created_by: testAgentId,
493
+ });
494
+
495
+ const blocked = await backend.create({
496
+ description: "Blocked",
497
+ created_by: testAgentId,
498
+ });
499
+
500
+ await backend.addBlocker(blocked.id, blocker.id);
501
+
502
+ const blockers = await backend.getBlockers(blocked.id);
503
+ expect(blockers).toHaveLength(1);
504
+ expect(blockers[0].id).toBe(blocker.id);
505
+ });
506
+
507
+ it("should return tasks blocked by a given task", async () => {
508
+ const blocker = await backend.create({
509
+ description: "Blocker",
510
+ created_by: testAgentId,
511
+ });
512
+
513
+ const blocked = await backend.create({
514
+ description: "Blocked",
515
+ created_by: testAgentId,
516
+ });
517
+
518
+ await backend.addBlocker(blocked.id, blocker.id);
519
+
520
+ const blocking = await backend.getBlocking(blocker.id);
521
+ expect(blocking).toHaveLength(1);
522
+ expect(blocking[0].id).toBe(blocked.id);
523
+ });
524
+ });
525
+
526
+ // ─────────────────────────────────────────────────────────────────────────────
527
+ // Queries
528
+ // ─────────────────────────────────────────────────────────────────────────────
529
+
530
+ describe("list", () => {
531
+ it("should list all non-blocked tasks", async () => {
532
+ await backend.create({ description: "Task 1", created_by: testAgentId });
533
+ await backend.create({ description: "Task 2", created_by: testAgentId });
534
+
535
+ const tasks = await backend.list();
536
+ expect(tasks).toHaveLength(2);
537
+ });
538
+
539
+ it("should filter by status", async () => {
540
+ const task = await backend.create({
541
+ description: "Task 1",
542
+ created_by: testAgentId,
543
+ });
544
+
545
+ await backend.start(task.id);
546
+
547
+ await backend.create({
548
+ description: "Task 2",
549
+ created_by: testAgentId,
550
+ });
551
+
552
+ const inProgress = await backend.list({ status: "in_progress" });
553
+ expect(inProgress).toHaveLength(1);
554
+ expect(inProgress[0].id).toBe(task.id);
555
+ });
556
+
557
+ it("should filter by assigned agent", async () => {
558
+ const t1 = await backend.create({
559
+ description: "Agent 1 task",
560
+ created_by: testAgentId,
561
+ });
562
+ await backend.create({
563
+ description: "Unassigned task",
564
+ created_by: testAgentId,
565
+ });
566
+
567
+ await backend.assign(t1.id, "agent_worker1");
568
+
569
+ const agentTasks = await backend.list({ assigned_agent: "agent_worker1" });
570
+ expect(agentTasks).toHaveLength(1);
571
+ expect(agentTasks[0].id).toBe(t1.id);
572
+ });
573
+
574
+ it("should exclude blocked tasks by default", async () => {
575
+ const blocker = await backend.create({
576
+ description: "Blocker",
577
+ created_by: testAgentId,
578
+ });
579
+ const blocked = await backend.create({
580
+ description: "Blocked",
581
+ created_by: testAgentId,
582
+ });
583
+
584
+ await backend.addBlocker(blocked.id, blocker.id);
585
+
586
+ const tasks = await backend.list();
587
+ expect(tasks).toHaveLength(1);
588
+ expect(tasks[0].id).toBe(blocker.id);
589
+ });
590
+
591
+ it("should include blocked tasks when requested", async () => {
592
+ const blocker = await backend.create({
593
+ description: "Blocker",
594
+ created_by: testAgentId,
595
+ });
596
+ const blocked = await backend.create({
597
+ description: "Blocked",
598
+ created_by: testAgentId,
599
+ });
600
+
601
+ await backend.addBlocker(blocked.id, blocker.id);
602
+
603
+ const tasks = await backend.list({ includeBlocked: true });
604
+ expect(tasks).toHaveLength(2);
605
+ });
606
+ });
607
+
608
+ describe("listReady", () => {
609
+ it("should return only pending/assigned unblocked tasks", async () => {
610
+ const t1 = await backend.create({
611
+ description: "Ready task",
612
+ created_by: testAgentId,
613
+ });
614
+ const t2 = await backend.create({
615
+ description: "Blocked task",
616
+ created_by: testAgentId,
617
+ });
618
+
619
+ await backend.addBlocker(t2.id, t1.id);
620
+
621
+ const ready = await backend.listReady();
622
+ expect(ready).toHaveLength(1);
623
+ expect(ready[0].id).toBe(t1.id);
624
+ });
625
+ });
626
+
627
+ describe("getChildren / getSubtaskStatus", () => {
628
+ it("should return children of a parent task", async () => {
629
+ const parent = await backend.create({
630
+ description: "Parent",
631
+ created_by: testAgentId,
632
+ });
633
+
634
+ await backend.create({
635
+ description: "Child 1",
636
+ created_by: testAgentId,
637
+ parent_task: parent.id,
638
+ });
639
+ await backend.create({
640
+ description: "Child 2",
641
+ created_by: testAgentId,
642
+ parent_task: parent.id,
643
+ });
644
+
645
+ const children = await backend.getChildren(parent.id);
646
+ expect(children).toHaveLength(2);
647
+ });
648
+
649
+ it("should compute subtask status aggregates", async () => {
650
+ const parent = await backend.create({
651
+ description: "Parent",
652
+ created_by: testAgentId,
653
+ });
654
+
655
+ const c1 = await backend.create({
656
+ description: "Child 1",
657
+ created_by: testAgentId,
658
+ parent_task: parent.id,
659
+ });
660
+ await backend.create({
661
+ description: "Child 2",
662
+ created_by: testAgentId,
663
+ parent_task: parent.id,
664
+ });
665
+
666
+ await backend.start(c1.id);
667
+ await backend.complete(c1.id);
668
+
669
+ const status = await backend.getSubtaskStatus(parent.id);
670
+ expect(status.total).toBe(2);
671
+ expect(status.completed).toBe(1);
672
+ expect(status.pending).toBe(1);
673
+ expect(status.allCompleted).toBe(false);
674
+ });
675
+ });
676
+
677
+ // ─────────────────────────────────────────────────────────────────────────────
678
+ // Pull Model
679
+ // ─────────────────────────────────────────────────────────────────────────────
680
+
681
+ describe("claim / unclaim / listClaimable", () => {
682
+ it("should claim a pending task", async () => {
683
+ await backend.create({
684
+ description: "Claimable task",
685
+ created_by: testAgentId,
686
+ });
687
+
688
+ const claimed = await backend.claim("agent_worker1");
689
+ expect(claimed).not.toBeNull();
690
+ expect(claimed!.assigned_agent).toBe("agent_worker1");
691
+ expect(claimed!.status).toBe("assigned");
692
+
693
+ // Verify OpenTasks was updated
694
+ expect(client.updateIssue).toHaveBeenCalledWith(
695
+ claimed!.external_id,
696
+ expect.objectContaining({
697
+ assignee: "agent_worker1",
698
+ })
699
+ );
700
+ });
701
+
702
+ it("should return null when no tasks available", async () => {
703
+ const claimed = await backend.claim("agent_worker1");
704
+ expect(claimed).toBeNull();
705
+ });
706
+
707
+ it("should not claim blocked tasks", async () => {
708
+ const blocker = await backend.create({
709
+ description: "Blocker",
710
+ created_by: testAgentId,
711
+ });
712
+ const blocked = await backend.create({
713
+ description: "Blocked",
714
+ created_by: testAgentId,
715
+ });
716
+
717
+ await backend.addBlocker(blocked.id, blocker.id);
718
+
719
+ // Claim should pick the blocker, not the blocked task
720
+ const claimed = await backend.claim("agent_worker1");
721
+ expect(claimed).not.toBeNull();
722
+ expect(claimed!.id).toBe(blocker.id);
723
+ });
724
+
725
+ it("should unclaim a task", async () => {
726
+ await backend.create({
727
+ description: "Unclaim me",
728
+ created_by: testAgentId,
729
+ });
730
+
731
+ const claimed = await backend.claim("agent_worker1");
732
+ await backend.unclaim(claimed!.id);
733
+
734
+ const updated = await backend.get(claimed!.id);
735
+ expect(updated!.assigned_agent).toBeUndefined();
736
+
737
+ // Verify OpenTasks cleared
738
+ expect(client.updateIssue).toHaveBeenCalledWith(
739
+ claimed!.external_id,
740
+ expect.objectContaining({
741
+ assignee: null,
742
+ })
743
+ );
744
+ });
745
+
746
+ it("should list claimable tasks", async () => {
747
+ await backend.create({
748
+ description: "Available 1",
749
+ created_by: testAgentId,
750
+ tags: ["auth"],
751
+ });
752
+ await backend.create({
753
+ description: "Available 2",
754
+ created_by: testAgentId,
755
+ tags: ["ui"],
756
+ });
757
+
758
+ // Claim one
759
+ await backend.claim("agent_worker1");
760
+
761
+ const claimable = await backend.listClaimable();
762
+ expect(claimable).toHaveLength(1);
763
+ });
764
+
765
+ it("should filter claimable to root tasks only", async () => {
766
+ const parent = await backend.create({
767
+ description: "Parent task",
768
+ created_by: testAgentId,
769
+ });
770
+ await backend.create({
771
+ description: "Child task",
772
+ created_by: testAgentId,
773
+ parent_task: parent.id,
774
+ });
775
+
776
+ const claimable = await backend.listClaimable({ rootTasksOnly: true });
777
+ expect(claimable).toHaveLength(1);
778
+ expect(claimable[0].description).toBe("Parent task");
779
+ });
780
+ });
781
+
782
+ // ─────────────────────────────────────────────────────────────────────────────
783
+ // Import
784
+ // ─────────────────────────────────────────────────────────────────────────────
785
+
786
+ describe("importIssue", () => {
787
+ it("should import an existing OpenTasks issue as a task", async () => {
788
+ // Create an issue directly in the mock client
789
+ const issue = await client.createIssue({
790
+ title: "External issue",
791
+ status: "open",
792
+ tags: ["imported"],
793
+ });
794
+
795
+ const task = await backend.importIssue(issue.id, testAgentId);
796
+
797
+ expect(task.id).toMatch(/^task_/);
798
+ expect(task.description).toBe("External issue");
799
+ expect(task.status).toBe("pending");
800
+ expect(task.external_id).toBe(issue.id);
801
+
802
+ // Verify ID mapping
803
+ expect(backend.getIssueForTask(task.id)).toBe(issue.id);
804
+ expect(backend.getTaskForIssue(issue.id)).toBe(task.id);
805
+ });
806
+
807
+ it("should import an in_progress issue with correct status", async () => {
808
+ const issue = await client.createIssue({
809
+ title: "Active issue",
810
+ status: "in_progress",
811
+ });
812
+
813
+ // Update the mock to return in_progress
814
+ (client.getIssue as any).mockResolvedValueOnce({
815
+ ...issue,
816
+ status: "in_progress",
817
+ });
818
+
819
+ const task = await backend.importIssue(issue.id, testAgentId);
820
+ expect(task.status).toBe("in_progress");
821
+ });
822
+
823
+ it("should not re-import an already imported issue", async () => {
824
+ const issue = await client.createIssue({
825
+ title: "Already imported",
826
+ status: "open",
827
+ });
828
+
829
+ const task1 = await backend.importIssue(issue.id, testAgentId);
830
+ const task2 = await backend.importIssue(issue.id, testAgentId);
831
+
832
+ expect(task1.id).toBe(task2.id);
833
+ });
834
+ });
835
+
836
+ // ─────────────────────────────────────────────────────────────────────────────
837
+ // ID Mapping
838
+ // ─────────────────────────────────────────────────────────────────────────────
839
+
840
+ describe("ID mapping", () => {
841
+ it("should maintain bidirectional task <-> issue mapping", async () => {
842
+ const task = await backend.create({
843
+ description: "Mapped task",
844
+ created_by: testAgentId,
845
+ });
846
+
847
+ const issueId = backend.getIssueForTask(task.id);
848
+ expect(issueId).toBeDefined();
849
+ expect(issueId).toMatch(/^i-mock/);
850
+
851
+ const taskId = backend.getTaskForIssue(issueId!);
852
+ expect(taskId).toBe(task.id);
853
+ });
854
+ });
855
+
856
+ // ─────────────────────────────────────────────────────────────────────────────
857
+ // Event Subscriptions
858
+ // ─────────────────────────────────────────────────────────────────────────────
859
+
860
+ describe("onTaskChange", () => {
861
+ it("should fire callback on task creation", async () => {
862
+ const events: any[] = [];
863
+ backend.onTaskChange((event) => events.push(event));
864
+
865
+ await backend.create({
866
+ description: "New task",
867
+ created_by: testAgentId,
868
+ });
869
+
870
+ expect(events.length).toBeGreaterThan(0);
871
+ expect(events[0].type).toBe("created");
872
+ });
873
+
874
+ it("should filter by taskId", async () => {
875
+ const task1 = await backend.create({
876
+ description: "Task 1",
877
+ created_by: testAgentId,
878
+ });
879
+
880
+ const events: any[] = [];
881
+ backend.onTaskChange(task1.id, (event) => events.push(event));
882
+
883
+ await backend.create({
884
+ description: "Task 2",
885
+ created_by: testAgentId,
886
+ });
887
+
888
+ await backend.start(task1.id);
889
+
890
+ // Should only have events for task1
891
+ expect(events.every((e) => e.taskId === task1.id)).toBe(true);
892
+ });
893
+ });
894
+
895
+ // ─────────────────────────────────────────────────────────────────────────────
896
+ // close()
897
+ // ─────────────────────────────────────────────────────────────────────────────
898
+
899
+ describe("close()", () => {
900
+ it("should mark backend as closed", async () => {
901
+ await backend.close();
902
+
903
+ // Write operations should throw BACKEND_CLOSED
904
+ await expect(
905
+ backend.create({ description: "after close", created_by: testAgentId })
906
+ ).rejects.toThrow("Backend is closed");
907
+ });
908
+
909
+ it("should throw BACKEND_CLOSED on write operations after close", async () => {
910
+ // Create a task before closing
911
+ const task = await backend.create({
912
+ description: "Test task",
913
+ created_by: testAgentId,
914
+ });
915
+
916
+ await backend.close();
917
+
918
+ // All write methods should throw with BACKEND_CLOSED code
919
+ const expectClosed = async (fn: () => Promise<unknown>) => {
920
+ try {
921
+ await fn();
922
+ throw new Error("Expected to throw");
923
+ } catch (err: any) {
924
+ expect(err.code).toBe("BACKEND_CLOSED");
925
+ expect(err.message).toBe("Backend is closed");
926
+ }
927
+ };
928
+
929
+ await expectClosed(() => backend.create({ description: "x", created_by: testAgentId }));
930
+ await expectClosed(() => backend.update(task.id, { description: "x" }));
931
+ await expectClosed(() => backend.delete(task.id));
932
+ await expectClosed(() => backend.assign(task.id, testAgentId));
933
+ await expectClosed(() => backend.start(task.id));
934
+ await expectClosed(() => backend.complete(task.id));
935
+ await expectClosed(() => backend.fail(task.id, { message: "err" }));
936
+ await expectClosed(() => backend.addBlocker(task.id, task.id));
937
+ await expectClosed(() => backend.removeBlocker(task.id, task.id));
938
+ await expectClosed(() => backend.claim!(testAgentId));
939
+ });
940
+
941
+ it("should still allow read operations after close", async () => {
942
+ // Create a task before closing
943
+ const task = await backend.create({
944
+ description: "readable after close",
945
+ created_by: testAgentId,
946
+ });
947
+
948
+ await backend.close();
949
+
950
+ // Read-only operations should still work
951
+ const fetched = await backend.get(task.id);
952
+ expect(fetched).not.toBeNull();
953
+ expect(fetched!.description).toBe("readable after close");
954
+
955
+ const listed = await backend.list();
956
+ expect(listed.length).toBe(1);
957
+
958
+ const children = await backend.getChildren(task.id);
959
+ expect(children).toEqual([]);
960
+
961
+ const status = await backend.getSubtaskStatus(task.id);
962
+ expect(status.total).toBe(0);
963
+
964
+ const history = await backend.getAgentHistory(task.id);
965
+ expect(history).toEqual([]);
966
+ });
967
+ });
968
+ });