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,1338 @@
1
+ /**
2
+ * E2E Tests for OpenTasksTaskBackend
3
+ *
4
+ * Exercises the OpenTasksTaskBackend directly against a real OpenTasks daemon.
5
+ * Validates bidirectional ID mapping, status sync, graph-based blocking,
6
+ * pull model (claim/unclaim), import, subtask hierarchy, and event subscriptions.
7
+ *
8
+ * Requires: opentasks package installed
9
+ *
10
+ * @module task/backend/opentasks/__tests__/opentasks-backend.e2e.test
11
+ */
12
+
13
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ import * as os from "os";
17
+ import {
18
+ createEventStore,
19
+ type EventStore,
20
+ } from "../../../../store/event-store.js";
21
+ import {
22
+ OpenTasksTaskBackend,
23
+ createOpenTasksTaskBackend,
24
+ OpenTasksBackendError,
25
+ } from "../backend.js";
26
+ import { IPCOpenTasksClient } from "../client.js";
27
+ import type { OpenTasksClient } from "../client.js";
28
+ import type { TaskChangeEvent } from "../../types.js";
29
+
30
+ // =============================================================================
31
+ // Helpers
32
+ // =============================================================================
33
+
34
+ const TEST_AGENT = "agent_e2e_test";
35
+ const WORKER_1 = "agent_worker_1";
36
+ const WORKER_2 = "agent_worker_2";
37
+
38
+ /**
39
+ * Wait for a condition to become true with timeout.
40
+ */
41
+ async function waitFor(
42
+ condition: () => boolean | Promise<boolean>,
43
+ timeoutMs = 5000,
44
+ intervalMs = 50
45
+ ): Promise<void> {
46
+ const start = Date.now();
47
+ while (Date.now() - start < timeoutMs) {
48
+ if (await condition()) return;
49
+ await new Promise((r) => setTimeout(r, intervalMs));
50
+ }
51
+ throw new Error(`waitFor timed out after ${timeoutMs}ms`);
52
+ }
53
+
54
+ // =============================================================================
55
+ // Test Suite
56
+ // =============================================================================
57
+
58
+ describe("OpenTasksTaskBackend E2E", () => {
59
+ let tempDir: string;
60
+ let daemon: any;
61
+ let socketPath: string;
62
+ let eventStore: EventStore;
63
+ let client: IPCOpenTasksClient;
64
+ let backend: OpenTasksTaskBackend;
65
+
66
+ beforeAll(async () => {
67
+ // Create temp directory for opentasks data
68
+ tempDir = fs.mkdtempSync(
69
+ path.join(os.tmpdir(), "macro-e2e-opentasks-backend-")
70
+ );
71
+ const locationPath = path.join(tempDir, ".opentasks");
72
+ fs.mkdirSync(locationPath, { recursive: true });
73
+
74
+ const registryPath = path.join(tempDir, "registry.json");
75
+
76
+ // Start a real opentasks daemon
77
+ const opentasks = await import("opentasks");
78
+ daemon = await opentasks.createDaemonWithStore({
79
+ locationPath,
80
+ version: "0.0.3",
81
+ registryPath,
82
+ shutdownTimeoutMs: 2000,
83
+ });
84
+ await daemon.start();
85
+ socketPath = daemon.socketPath;
86
+
87
+ // Create client
88
+ client = new IPCOpenTasksClient({
89
+ socketPath,
90
+ autoConnect: true,
91
+ timeout: 10000,
92
+ });
93
+ await client.connect();
94
+ }, 30000);
95
+
96
+ afterAll(async () => {
97
+ try {
98
+ client?.disconnect();
99
+ } catch {
100
+ /* ignore */
101
+ }
102
+ try {
103
+ await daemon?.stop();
104
+ } catch {
105
+ /* ignore */
106
+ }
107
+ try {
108
+ if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true });
109
+ } catch {
110
+ /* ignore */
111
+ }
112
+ }, 15000);
113
+
114
+ // Fresh EventStore + backend per test to avoid cross-test pollution
115
+ beforeEach(async () => {
116
+ if (eventStore) {
117
+ try {
118
+ await eventStore.close();
119
+ } catch {
120
+ /* ignore */
121
+ }
122
+ }
123
+ eventStore = await createEventStore({ inMemory: true });
124
+ backend = createOpenTasksTaskBackend(eventStore, client, {
125
+ syncStatus: true,
126
+ sourceLabel: "e2e-test",
127
+ });
128
+ });
129
+
130
+ // ─────────────────────────────────────────────────────────────────────────────
131
+ // 1. Lifecycle: Create / Get / Update / Delete
132
+ // ─────────────────────────────────────────────────────────────────────────────
133
+
134
+ describe("lifecycle", () => {
135
+ it("should create a task backed by a real OpenTasks issue", async () => {
136
+ const task = await backend.create({
137
+ description: "E2E create test",
138
+ created_by: TEST_AGENT,
139
+ tags: ["e2e", "lifecycle"],
140
+ });
141
+
142
+ expect(task.id).toMatch(/^task_/);
143
+ expect(task.description).toBe("E2E create test");
144
+ expect(task.status).toBe("pending");
145
+ expect(task.created_by).toBe(TEST_AGENT);
146
+ expect(task.external_id).toBeDefined();
147
+
148
+ // Verify the issue exists in the real daemon
149
+ const issue = await client.getIssue(task.external_id!);
150
+ expect(issue).not.toBeNull();
151
+ expect(issue!.title).toBe("E2E create test");
152
+ expect(issue!.status).toBe("open");
153
+ });
154
+
155
+ it("should retrieve a task by ID", async () => {
156
+ const task = await backend.create({
157
+ description: "E2E get test",
158
+ created_by: TEST_AGENT,
159
+ });
160
+
161
+ const retrieved = await backend.get(task.id);
162
+ expect(retrieved).not.toBeNull();
163
+ expect(retrieved!.id).toBe(task.id);
164
+ expect(retrieved!.description).toBe("E2E get test");
165
+ expect(retrieved!.external_id).toBe(task.external_id);
166
+ });
167
+
168
+ it("should return null for non-existent task", async () => {
169
+ const result = await backend.get("task_nonexistent_xyz");
170
+ expect(result).toBeNull();
171
+ });
172
+
173
+ it("should update task outputs", async () => {
174
+ const task = await backend.create({
175
+ description: "E2E update test",
176
+ created_by: TEST_AGENT,
177
+ });
178
+
179
+ const updated = await backend.update(task.id, {
180
+ outputs: { result: "success", files_changed: 5 },
181
+ });
182
+
183
+ expect(updated.outputs).toEqual(
184
+ expect.objectContaining({ result: "success" })
185
+ );
186
+ });
187
+
188
+ it("should update task description and sync to daemon", async () => {
189
+ const task = await backend.create({
190
+ description: "Original description",
191
+ created_by: TEST_AGENT,
192
+ });
193
+
194
+ await backend.update(task.id, {
195
+ description: "Updated description",
196
+ });
197
+
198
+ // Verify the daemon issue was updated
199
+ const issue = await client.getIssue(task.external_id!);
200
+ expect(issue!.title).toBe("Updated description");
201
+ });
202
+
203
+ it("should delete a task and its backing issue", async () => {
204
+ const task = await backend.create({
205
+ description: "E2E delete test",
206
+ created_by: TEST_AGENT,
207
+ });
208
+
209
+ const issueId = task.external_id!;
210
+ await backend.delete(task.id);
211
+
212
+ // Mapping should be cleared
213
+ expect(backend.getIssueForTask(task.id)).toBeUndefined();
214
+ expect(backend.getTaskForIssue(issueId)).toBeUndefined();
215
+ });
216
+ });
217
+
218
+ // ─────────────────────────────────────────────────────────────────────────────
219
+ // 2. Status Transitions with Daemon Sync
220
+ // ─────────────────────────────────────────────────────────────────────────────
221
+
222
+ describe("status transitions", () => {
223
+ it("should assign a task and sync assignee to daemon", async () => {
224
+ const task = await backend.create({
225
+ description: "E2E assign test",
226
+ created_by: TEST_AGENT,
227
+ });
228
+
229
+ await backend.assign(task.id, WORKER_1);
230
+
231
+ const updated = await backend.get(task.id);
232
+ expect(updated!.status).toBe("assigned");
233
+ expect(updated!.assigned_agent).toBe(WORKER_1);
234
+
235
+ // Verify daemon issue was updated (assignee synced)
236
+ const issue = await client.getIssue(task.external_id!);
237
+ expect(issue).not.toBeNull();
238
+ });
239
+
240
+ it("should unassign a task and clear assignee in daemon", async () => {
241
+ const task = await backend.create({
242
+ description: "E2E unassign test",
243
+ created_by: TEST_AGENT,
244
+ });
245
+
246
+ await backend.assign(task.id, WORKER_1);
247
+ await backend.unassign(task.id);
248
+
249
+ const updated = await backend.get(task.id);
250
+ expect(updated!.assigned_agent).toBeUndefined();
251
+
252
+ // Daemon issue should still exist
253
+ const issue = await client.getIssue(task.external_id!);
254
+ expect(issue).not.toBeNull();
255
+ });
256
+
257
+ it("should throw when unassigning a non-assigned task", async () => {
258
+ const task = await backend.create({
259
+ description: "Not assigned",
260
+ created_by: TEST_AGENT,
261
+ });
262
+
263
+ await expect(backend.unassign(task.id)).rejects.toThrow(
264
+ OpenTasksBackendError
265
+ );
266
+ });
267
+
268
+ it("should start a task and sync in_progress to daemon", async () => {
269
+ const task = await backend.create({
270
+ description: "E2E start test",
271
+ created_by: TEST_AGENT,
272
+ });
273
+
274
+ await backend.start(task.id);
275
+
276
+ const updated = await backend.get(task.id);
277
+ expect(updated!.status).toBe("in_progress");
278
+
279
+ // Daemon should reflect in_progress
280
+ const issue = await client.getIssue(task.external_id!);
281
+ expect(issue!.status).toBe("in_progress");
282
+ });
283
+
284
+ it("should complete a task with outputs and close in daemon", async () => {
285
+ const task = await backend.create({
286
+ description: "E2E complete test",
287
+ created_by: TEST_AGENT,
288
+ });
289
+
290
+ await backend.start(task.id);
291
+ await backend.complete(task.id, {
292
+ summary: "All done",
293
+ data: { lines_changed: 42 },
294
+ });
295
+
296
+ const updated = await backend.get(task.id);
297
+ expect(updated!.status).toBe("completed");
298
+ expect(updated!.outputs).toEqual(
299
+ expect.objectContaining({ summary: "All done", lines_changed: 42 })
300
+ );
301
+
302
+ // Daemon issue should be closed
303
+ const issue = await client.getIssue(task.external_id!);
304
+ expect(issue!.status).toBe("closed");
305
+ });
306
+
307
+ it("should fail a task and close in daemon with error metadata", async () => {
308
+ const task = await backend.create({
309
+ description: "E2E fail test",
310
+ created_by: TEST_AGENT,
311
+ });
312
+
313
+ await backend.start(task.id);
314
+ await backend.fail(task.id, {
315
+ message: "Compilation failed",
316
+ code: "COMPILE_ERROR",
317
+ });
318
+
319
+ const updated = await backend.get(task.id);
320
+ expect(updated!.status).toBe("failed");
321
+
322
+ // Daemon issue should be closed
323
+ const issue = await client.getIssue(task.external_id!);
324
+ expect(issue!.status).toBe("closed");
325
+ });
326
+
327
+ it("should reject invalid status transitions", async () => {
328
+ const task = await backend.create({
329
+ description: "E2E invalid transition test",
330
+ created_by: TEST_AGENT,
331
+ });
332
+
333
+ await backend.start(task.id);
334
+ await backend.complete(task.id);
335
+
336
+ // completed → in_progress is invalid
337
+ await expect(backend.start(task.id)).rejects.toThrow(
338
+ OpenTasksBackendError
339
+ );
340
+
341
+ // completed → completed is invalid
342
+ await expect(backend.complete(task.id)).rejects.toThrow(
343
+ OpenTasksBackendError
344
+ );
345
+ });
346
+
347
+ it("should allow failed → pending recovery", async () => {
348
+ const task = await backend.create({
349
+ description: "E2E recovery test",
350
+ created_by: TEST_AGENT,
351
+ });
352
+
353
+ await backend.start(task.id);
354
+ await backend.fail(task.id, { message: "first attempt failed" });
355
+
356
+ // Retry: failed → pending is valid
357
+ await backend.update(task.id, { status: "pending" });
358
+
359
+ const updated = await backend.get(task.id);
360
+ expect(updated!.status).toBe("pending");
361
+ });
362
+
363
+ it("should track full lifecycle: pending → assigned → in_progress → completed", async () => {
364
+ const task = await backend.create({
365
+ description: "E2E full lifecycle test",
366
+ created_by: TEST_AGENT,
367
+ });
368
+
369
+ expect((await backend.get(task.id))!.status).toBe("pending");
370
+
371
+ await backend.assign(task.id, WORKER_1);
372
+ expect((await backend.get(task.id))!.status).toBe("assigned");
373
+
374
+ await backend.start(task.id);
375
+ expect((await backend.get(task.id))!.status).toBe("in_progress");
376
+
377
+ await backend.complete(task.id, { summary: "Done!" });
378
+ expect((await backend.get(task.id))!.status).toBe("completed");
379
+ });
380
+ });
381
+
382
+ // ─────────────────────────────────────────────────────────────────────────────
383
+ // 3. Bidirectional ID Mapping
384
+ // ─────────────────────────────────────────────────────────────────────────────
385
+
386
+ describe("bidirectional ID mapping", () => {
387
+ it("should maintain consistent task ↔ issue mapping", async () => {
388
+ const task = await backend.create({
389
+ description: "ID mapping test",
390
+ created_by: TEST_AGENT,
391
+ });
392
+
393
+ const issueId = backend.getIssueForTask(task.id);
394
+ expect(issueId).toBeDefined();
395
+
396
+ const taskId = backend.getTaskForIssue(issueId!);
397
+ expect(taskId).toBe(task.id);
398
+ });
399
+
400
+ it("should clear mapping on delete", async () => {
401
+ const task = await backend.create({
402
+ description: "Mapping delete test",
403
+ created_by: TEST_AGENT,
404
+ });
405
+
406
+ const issueId = task.external_id!;
407
+ await backend.delete(task.id);
408
+
409
+ expect(backend.getIssueForTask(task.id)).toBeUndefined();
410
+ expect(backend.getTaskForIssue(issueId)).toBeUndefined();
411
+ });
412
+
413
+ it("should include external_id on created tasks", async () => {
414
+ const task = await backend.create({
415
+ description: "External ID test",
416
+ created_by: TEST_AGENT,
417
+ });
418
+
419
+ // external_id should be set on the task
420
+ expect(task.external_id).toBeDefined();
421
+ expect(task.external_id).toMatch(/^[a-z0-9-]+$/i);
422
+
423
+ // The issue should be retrievable from the daemon
424
+ const issue = await client.getIssue(task.external_id!);
425
+ expect(issue).not.toBeNull();
426
+ expect(issue!.title).toBe("External ID test");
427
+ });
428
+ });
429
+
430
+ // ─────────────────────────────────────────────────────────────────────────────
431
+ // 4. Dependencies (Graph-based Blocking)
432
+ // ─────────────────────────────────────────────────────────────────────────────
433
+
434
+ describe("dependencies via graph edges", () => {
435
+ it("should create blocking relationships in daemon graph", async () => {
436
+ const blocker = await backend.create({
437
+ description: "E2E blocker task",
438
+ created_by: TEST_AGENT,
439
+ });
440
+ const blocked = await backend.create({
441
+ description: "E2E blocked task",
442
+ created_by: TEST_AGENT,
443
+ });
444
+
445
+ await backend.addBlocker(blocked.id, blocker.id);
446
+
447
+ // Verify via backend API
448
+ const blockedTask = await backend.get(blocked.id);
449
+ expect(blockedTask!.isBlocked).toBe(true);
450
+
451
+ // Verify blocker is returned
452
+ const blockers = await backend.getBlockers(blocked.id);
453
+ expect(blockers).toHaveLength(1);
454
+ expect(blockers[0].id).toBe(blocker.id);
455
+ });
456
+
457
+ it("should return tasks blocked by a given task", async () => {
458
+ const blocker = await backend.create({
459
+ description: "Blocker",
460
+ created_by: TEST_AGENT,
461
+ });
462
+ const blocked1 = await backend.create({
463
+ description: "Blocked 1",
464
+ created_by: TEST_AGENT,
465
+ });
466
+ const blocked2 = await backend.create({
467
+ description: "Blocked 2",
468
+ created_by: TEST_AGENT,
469
+ });
470
+
471
+ await backend.addBlocker(blocked1.id, blocker.id);
472
+ await backend.addBlocker(blocked2.id, blocker.id);
473
+
474
+ const blocking = await backend.getBlocking(blocker.id);
475
+ expect(blocking).toHaveLength(2);
476
+ const blockingIds = blocking.map((t) => t.id);
477
+ expect(blockingIds).toContain(blocked1.id);
478
+ expect(blockingIds).toContain(blocked2.id);
479
+ });
480
+
481
+ it("should remove blocking relationships", async () => {
482
+ const blocker = await backend.create({
483
+ description: "Removable blocker",
484
+ created_by: TEST_AGENT,
485
+ });
486
+ const blocked = await backend.create({
487
+ description: "Will be unblocked",
488
+ created_by: TEST_AGENT,
489
+ });
490
+
491
+ await backend.addBlocker(blocked.id, blocker.id);
492
+ expect((await backend.get(blocked.id))!.isBlocked).toBe(true);
493
+
494
+ await backend.removeBlocker(blocked.id, blocker.id);
495
+
496
+ // isBlocked should update via async graph query
497
+ const updatedBlocked = await backend.list({
498
+ includeBlocked: true,
499
+ });
500
+ const task = updatedBlocked.find((t) => t.id === blocked.id);
501
+ expect(task!.isBlocked).toBe(false);
502
+ });
503
+
504
+ it("should unblock when blocker is completed", async () => {
505
+ const blocker = await backend.create({
506
+ description: "Completes to unblock",
507
+ created_by: TEST_AGENT,
508
+ });
509
+ const blocked = await backend.create({
510
+ description: "Waiting for blocker",
511
+ created_by: TEST_AGENT,
512
+ });
513
+
514
+ await backend.addBlocker(blocked.id, blocker.id);
515
+
516
+ // Blocked task should be blocked
517
+ const blockedBefore = await backend.list({ includeBlocked: true });
518
+ expect(blockedBefore.find((t) => t.id === blocked.id)!.isBlocked).toBe(
519
+ true
520
+ );
521
+
522
+ // Complete the blocker
523
+ await backend.start(blocker.id);
524
+ await backend.complete(blocker.id);
525
+
526
+ // After completing blocker, blocked task should be unblocked
527
+ // (the daemon graph edge still exists, but the blocker is closed)
528
+ const all = await backend.list({ includeBlocked: true });
529
+ const blockedAfter = all.find((t) => t.id === blocked.id);
530
+ expect(blockedAfter!.isBlocked).toBe(false);
531
+ });
532
+
533
+ it("should handle chain dependencies: A blocks B blocks C", async () => {
534
+ const a = await backend.create({
535
+ description: "Chain A",
536
+ created_by: TEST_AGENT,
537
+ });
538
+ const b = await backend.create({
539
+ description: "Chain B",
540
+ created_by: TEST_AGENT,
541
+ });
542
+ const c = await backend.create({
543
+ description: "Chain C",
544
+ created_by: TEST_AGENT,
545
+ });
546
+
547
+ await backend.addBlocker(b.id, a.id);
548
+ await backend.addBlocker(c.id, b.id);
549
+
550
+ // B is blocked by A, C is blocked by B
551
+ const allTasks = await backend.list({ includeBlocked: true });
552
+ expect(allTasks.find((t) => t.id === a.id)!.isBlocked).toBe(false);
553
+ expect(allTasks.find((t) => t.id === b.id)!.isBlocked).toBe(true);
554
+ expect(allTasks.find((t) => t.id === c.id)!.isBlocked).toBe(true);
555
+
556
+ // Complete A → B unblocks but C stays blocked by B
557
+ await backend.start(a.id);
558
+ await backend.complete(a.id);
559
+
560
+ const afterA = await backend.list({ includeBlocked: true });
561
+ expect(afterA.find((t) => t.id === b.id)!.isBlocked).toBe(false);
562
+ expect(afterA.find((t) => t.id === c.id)!.isBlocked).toBe(true);
563
+
564
+ // Complete B → C unblocks
565
+ await backend.start(b.id);
566
+ await backend.complete(b.id);
567
+
568
+ const afterB = await backend.list({ includeBlocked: true });
569
+ expect(afterB.find((t) => t.id === c.id)!.isBlocked).toBe(false);
570
+ });
571
+ });
572
+
573
+ // ─────────────────────────────────────────────────────────────────────────────
574
+ // 5. Queries
575
+ // ─────────────────────────────────────────────────────────────────────────────
576
+
577
+ describe("queries", () => {
578
+ it("should list all tasks excluding blocked by default", async () => {
579
+ const blocker = await backend.create({
580
+ description: "Query blocker",
581
+ created_by: TEST_AGENT,
582
+ });
583
+ const blocked = await backend.create({
584
+ description: "Query blocked",
585
+ created_by: TEST_AGENT,
586
+ });
587
+ await backend.addBlocker(blocked.id, blocker.id);
588
+
589
+ const tasks = await backend.list();
590
+ expect(tasks).toHaveLength(1);
591
+ expect(tasks[0].id).toBe(blocker.id);
592
+ });
593
+
594
+ it("should include blocked tasks when requested", async () => {
595
+ const blocker = await backend.create({
596
+ description: "Query blocker 2",
597
+ created_by: TEST_AGENT,
598
+ });
599
+ const blocked = await backend.create({
600
+ description: "Query blocked 2",
601
+ created_by: TEST_AGENT,
602
+ });
603
+ await backend.addBlocker(blocked.id, blocker.id);
604
+
605
+ const tasks = await backend.list({ includeBlocked: true });
606
+ expect(tasks).toHaveLength(2);
607
+ });
608
+
609
+ it("should filter by status", async () => {
610
+ const t1 = await backend.create({
611
+ description: "Pending task",
612
+ created_by: TEST_AGENT,
613
+ });
614
+ const t2 = await backend.create({
615
+ description: "Started task",
616
+ created_by: TEST_AGENT,
617
+ });
618
+ await backend.start(t2.id);
619
+
620
+ const inProgress = await backend.list({ status: "in_progress" });
621
+ expect(inProgress).toHaveLength(1);
622
+ expect(inProgress[0].id).toBe(t2.id);
623
+ });
624
+
625
+ it("should filter by assigned agent", async () => {
626
+ const t1 = await backend.create({
627
+ description: "Worker 1 task",
628
+ created_by: TEST_AGENT,
629
+ });
630
+ await backend.create({
631
+ description: "Unassigned",
632
+ created_by: TEST_AGENT,
633
+ });
634
+ await backend.assign(t1.id, WORKER_1);
635
+
636
+ const assigned = await backend.list({ assigned_agent: WORKER_1 });
637
+ expect(assigned).toHaveLength(1);
638
+ expect(assigned[0].assigned_agent).toBe(WORKER_1);
639
+ });
640
+
641
+ it("should filter by tags", async () => {
642
+ await backend.create({
643
+ description: "Frontend task",
644
+ created_by: TEST_AGENT,
645
+ tags: ["frontend"],
646
+ });
647
+ await backend.create({
648
+ description: "Backend task",
649
+ created_by: TEST_AGENT,
650
+ tags: ["backend"],
651
+ });
652
+
653
+ const frontendTasks = await backend.list({ tags: ["frontend"] });
654
+ expect(frontendTasks).toHaveLength(1);
655
+ expect(frontendTasks[0].description).toBe("Frontend task");
656
+ });
657
+
658
+ it("should filter by created_by", async () => {
659
+ await backend.create({
660
+ description: "Creator 1 task",
661
+ created_by: WORKER_1,
662
+ });
663
+ await backend.create({
664
+ description: "Creator 2 task",
665
+ created_by: WORKER_2,
666
+ });
667
+
668
+ const worker1Tasks = await backend.list({ created_by: WORKER_1 });
669
+ expect(worker1Tasks).toHaveLength(1);
670
+ expect(worker1Tasks[0].description).toBe("Creator 1 task");
671
+ });
672
+
673
+ it("should filter root tasks only", async () => {
674
+ const parent = await backend.create({
675
+ description: "Root task",
676
+ created_by: TEST_AGENT,
677
+ });
678
+ await backend.create({
679
+ description: "Child task",
680
+ created_by: TEST_AGENT,
681
+ parent_task: parent.id,
682
+ });
683
+
684
+ const rootOnly = await backend.list({ rootTasksOnly: true });
685
+ expect(rootOnly).toHaveLength(1);
686
+ expect(rootOnly[0].id).toBe(parent.id);
687
+ });
688
+
689
+ it("should listReady: only pending/assigned, unblocked tasks", async () => {
690
+ const blocker = await backend.create({
691
+ description: "Ready blocker",
692
+ created_by: TEST_AGENT,
693
+ });
694
+ const blocked = await backend.create({
695
+ description: "Ready blocked",
696
+ created_by: TEST_AGENT,
697
+ });
698
+ const started = await backend.create({
699
+ description: "Already started",
700
+ created_by: TEST_AGENT,
701
+ });
702
+
703
+ await backend.addBlocker(blocked.id, blocker.id);
704
+ await backend.start(started.id);
705
+
706
+ const ready = await backend.listReady();
707
+ expect(ready).toHaveLength(1);
708
+ expect(ready[0].id).toBe(blocker.id);
709
+ });
710
+ });
711
+
712
+ // ─────────────────────────────────────────────────────────────────────────────
713
+ // 6. Subtask Hierarchy
714
+ // ─────────────────────────────────────────────────────────────────────────────
715
+
716
+ describe("subtask hierarchy", () => {
717
+ it("should create subtasks linked to parent", async () => {
718
+ const parent = await backend.create({
719
+ description: "Parent task",
720
+ created_by: TEST_AGENT,
721
+ });
722
+
723
+ const child1 = await backend.createSubtask(parent.id, {
724
+ description: "Child 1",
725
+ created_by: TEST_AGENT,
726
+ });
727
+ const child2 = await backend.createSubtask(parent.id, {
728
+ description: "Child 2",
729
+ created_by: TEST_AGENT,
730
+ });
731
+
732
+ expect(child1.parent_task).toBe(parent.id);
733
+ expect(child2.parent_task).toBe(parent.id);
734
+
735
+ // Verify parent has subtask references
736
+ const updatedParent = await backend.get(parent.id);
737
+ expect(updatedParent!.subtasks).toContain(child1.id);
738
+ expect(updatedParent!.subtasks).toContain(child2.id);
739
+ });
740
+
741
+ it("should getChildren for a parent", async () => {
742
+ const parent = await backend.create({
743
+ description: "Children parent",
744
+ created_by: TEST_AGENT,
745
+ });
746
+
747
+ await backend.createSubtask(parent.id, {
748
+ description: "Sub A",
749
+ created_by: TEST_AGENT,
750
+ });
751
+ await backend.createSubtask(parent.id, {
752
+ description: "Sub B",
753
+ created_by: TEST_AGENT,
754
+ });
755
+ await backend.createSubtask(parent.id, {
756
+ description: "Sub C",
757
+ created_by: TEST_AGENT,
758
+ });
759
+
760
+ const children = await backend.getChildren(parent.id);
761
+ expect(children).toHaveLength(3);
762
+ });
763
+
764
+ it("should compute subtask status aggregates", async () => {
765
+ const parent = await backend.create({
766
+ description: "Status parent",
767
+ created_by: TEST_AGENT,
768
+ });
769
+
770
+ const c1 = await backend.createSubtask(parent.id, {
771
+ description: "Sub done",
772
+ created_by: TEST_AGENT,
773
+ });
774
+ const c2 = await backend.createSubtask(parent.id, {
775
+ description: "Sub in progress",
776
+ created_by: TEST_AGENT,
777
+ });
778
+ await backend.createSubtask(parent.id, {
779
+ description: "Sub pending",
780
+ created_by: TEST_AGENT,
781
+ });
782
+
783
+ await backend.start(c1.id);
784
+ await backend.complete(c1.id);
785
+ await backend.start(c2.id);
786
+
787
+ const status = await backend.getSubtaskStatus(parent.id);
788
+ expect(status.total).toBe(3);
789
+ expect(status.completed).toBe(1);
790
+ expect(status.in_progress).toBe(1);
791
+ expect(status.pending).toBe(1);
792
+ expect(status.allCompleted).toBe(false);
793
+ expect(status.anyFailed).toBe(false);
794
+ });
795
+
796
+ it("should report allCompleted when all subtasks done", async () => {
797
+ const parent = await backend.create({
798
+ description: "All done parent",
799
+ created_by: TEST_AGENT,
800
+ });
801
+
802
+ const c1 = await backend.createSubtask(parent.id, {
803
+ description: "Done 1",
804
+ created_by: TEST_AGENT,
805
+ });
806
+ const c2 = await backend.createSubtask(parent.id, {
807
+ description: "Done 2",
808
+ created_by: TEST_AGENT,
809
+ });
810
+
811
+ await backend.start(c1.id);
812
+ await backend.complete(c1.id);
813
+ await backend.start(c2.id);
814
+ await backend.complete(c2.id);
815
+
816
+ const status = await backend.getSubtaskStatus(parent.id);
817
+ expect(status.allCompleted).toBe(true);
818
+ });
819
+
820
+ it("should throw when creating subtask with non-existent parent", async () => {
821
+ await expect(
822
+ backend.createSubtask("task_nonexistent", {
823
+ description: "Orphan",
824
+ created_by: TEST_AGENT,
825
+ })
826
+ ).rejects.toThrow(OpenTasksBackendError);
827
+ });
828
+ });
829
+
830
+ // ─────────────────────────────────────────────────────────────────────────────
831
+ // 7. Pull Model (Claim / Unclaim / ListClaimable)
832
+ // ─────────────────────────────────────────────────────────────────────────────
833
+
834
+ describe("pull model", () => {
835
+ it("should claim a pending unblocked task", async () => {
836
+ await backend.create({
837
+ description: "Claimable task",
838
+ created_by: TEST_AGENT,
839
+ });
840
+
841
+ const claimed = await backend.claim(WORKER_1);
842
+ expect(claimed).not.toBeNull();
843
+ expect(claimed!.assigned_agent).toBe(WORKER_1);
844
+ expect(claimed!.status).toBe("assigned");
845
+
846
+ // Verify daemon issue still exists
847
+ const issue = await client.getIssue(claimed!.external_id!);
848
+ expect(issue).not.toBeNull();
849
+ });
850
+
851
+ it("should return null when no tasks available to claim", async () => {
852
+ const claimed = await backend.claim(WORKER_1);
853
+ expect(claimed).toBeNull();
854
+ });
855
+
856
+ it("should not claim blocked tasks", async () => {
857
+ const blocker = await backend.create({
858
+ description: "Claim blocker",
859
+ created_by: TEST_AGENT,
860
+ });
861
+ const blocked = await backend.create({
862
+ description: "Claim blocked",
863
+ created_by: TEST_AGENT,
864
+ });
865
+
866
+ await backend.addBlocker(blocked.id, blocker.id);
867
+
868
+ // Only the blocker should be claimable
869
+ const claimed = await backend.claim(WORKER_1);
870
+ expect(claimed).not.toBeNull();
871
+ expect(claimed!.id).toBe(blocker.id);
872
+
873
+ // No more claimable tasks (blocked is still blocked)
874
+ const second = await backend.claim(WORKER_2);
875
+ expect(second).toBeNull();
876
+ });
877
+
878
+ it("should not claim already-assigned tasks", async () => {
879
+ await backend.create({
880
+ description: "Single claim test",
881
+ created_by: TEST_AGENT,
882
+ });
883
+
884
+ const first = await backend.claim(WORKER_1);
885
+ expect(first).not.toBeNull();
886
+
887
+ // Second claim should get null
888
+ const second = await backend.claim(WORKER_2);
889
+ expect(second).toBeNull();
890
+ });
891
+
892
+ it("should unclaim a task and make it available again", async () => {
893
+ await backend.create({
894
+ description: "Unclaim test",
895
+ created_by: TEST_AGENT,
896
+ });
897
+
898
+ const claimed = await backend.claim(WORKER_1);
899
+ await backend.unclaim(claimed!.id);
900
+
901
+ const updated = await backend.get(claimed!.id);
902
+ expect(updated!.assigned_agent).toBeUndefined();
903
+ expect(updated!.status).toBe("pending");
904
+
905
+ // Should be claimable again after unclaim resets to pending
906
+ const reClaimed = await backend.claim(WORKER_2);
907
+ expect(reClaimed).not.toBeNull();
908
+ expect(reClaimed!.assigned_agent).toBe(WORKER_2);
909
+ });
910
+
911
+ it("should throw when unclaiming a non-assigned task", async () => {
912
+ const task = await backend.create({
913
+ description: "Not claimed",
914
+ created_by: TEST_AGENT,
915
+ });
916
+
917
+ await expect(backend.unclaim(task.id)).rejects.toThrow(
918
+ OpenTasksBackendError
919
+ );
920
+ });
921
+
922
+ it("should list claimable tasks with tag filter", async () => {
923
+ await backend.create({
924
+ description: "Frontend work",
925
+ created_by: TEST_AGENT,
926
+ tags: ["frontend"],
927
+ });
928
+ await backend.create({
929
+ description: "Backend work",
930
+ created_by: TEST_AGENT,
931
+ tags: ["backend"],
932
+ });
933
+
934
+ const frontendClaimable = await backend.listClaimable({
935
+ tags: ["frontend"],
936
+ });
937
+ expect(frontendClaimable).toHaveLength(1);
938
+ expect(frontendClaimable[0].description).toBe("Frontend work");
939
+ });
940
+
941
+ it("should list claimable tasks with rootTasksOnly filter", async () => {
942
+ const parent = await backend.create({
943
+ description: "Root claimable",
944
+ created_by: TEST_AGENT,
945
+ });
946
+ await backend.createSubtask(parent.id, {
947
+ description: "Child not root",
948
+ created_by: TEST_AGENT,
949
+ });
950
+
951
+ const rootClaimable = await backend.listClaimable({
952
+ rootTasksOnly: true,
953
+ });
954
+ expect(rootClaimable).toHaveLength(1);
955
+ expect(rootClaimable[0].id).toBe(parent.id);
956
+ });
957
+ });
958
+
959
+ // ─────────────────────────────────────────────────────────────────────────────
960
+ // 8. Import from OpenTasks
961
+ // ─────────────────────────────────────────────────────────────────────────────
962
+
963
+ describe("import", () => {
964
+ it("should import an existing OpenTasks issue as a task", async () => {
965
+ // Create an issue directly in the daemon (not via backend)
966
+ const issue = await client.createIssue({
967
+ title: "Externally created issue",
968
+ status: "open",
969
+ tags: ["imported"],
970
+ metadata: { source: "external" },
971
+ });
972
+
973
+ const task = await backend.importIssue(issue.id, TEST_AGENT);
974
+
975
+ expect(task.id).toMatch(/^task_/);
976
+ expect(task.description).toBe("Externally created issue");
977
+ expect(task.status).toBe("pending");
978
+ expect(task.external_id).toBe(issue.id);
979
+
980
+ // Mapping should be established
981
+ expect(backend.getIssueForTask(task.id)).toBe(issue.id);
982
+ expect(backend.getTaskForIssue(issue.id)).toBe(task.id);
983
+ });
984
+
985
+ it("should import an in_progress issue with correct status", async () => {
986
+ const issue = await client.createIssue({
987
+ title: "Active issue",
988
+ status: "in_progress",
989
+ });
990
+
991
+ const task = await backend.importIssue(issue.id, TEST_AGENT);
992
+ expect(task.status).toBe("in_progress");
993
+ });
994
+
995
+ it("should import a closed issue as completed", async () => {
996
+ const issue = await client.createIssue({
997
+ title: "Closed issue",
998
+ status: "closed",
999
+ });
1000
+
1001
+ const task = await backend.importIssue(issue.id, TEST_AGENT);
1002
+ expect(task.status).toBe("completed");
1003
+ });
1004
+
1005
+ it("should not re-import an already imported issue", async () => {
1006
+ const issue = await client.createIssue({
1007
+ title: "Import once",
1008
+ status: "open",
1009
+ });
1010
+
1011
+ const task1 = await backend.importIssue(issue.id, TEST_AGENT);
1012
+ const task2 = await backend.importIssue(issue.id, TEST_AGENT);
1013
+
1014
+ expect(task1.id).toBe(task2.id);
1015
+ });
1016
+
1017
+ it("should import an issue with assignee", async () => {
1018
+ const issue = await client.createIssue({
1019
+ title: "Assigned import",
1020
+ status: "open",
1021
+ assignee: WORKER_1,
1022
+ });
1023
+
1024
+ const task = await backend.importIssue(issue.id, TEST_AGENT);
1025
+
1026
+ // The import should set the assignee locally
1027
+ const retrieved = await backend.get(task.id);
1028
+ expect(retrieved!.assigned_agent).toBe(WORKER_1);
1029
+ });
1030
+
1031
+ it("should bulk import open issues via importOpenIssues", async () => {
1032
+ // Create several issues directly in daemon
1033
+ await client.createIssue({
1034
+ title: "Bulk import 1",
1035
+ status: "open",
1036
+ });
1037
+ await client.createIssue({
1038
+ title: "Bulk import 2",
1039
+ status: "in_progress",
1040
+ });
1041
+ // This one is closed - should still be imported since listIssues filter
1042
+ // only gets open/in_progress
1043
+ await client.createIssue({
1044
+ title: "Bulk import closed",
1045
+ status: "closed",
1046
+ });
1047
+
1048
+ const imported = await backend.importOpenIssues(TEST_AGENT);
1049
+
1050
+ // Should import at least the open and in_progress ones
1051
+ expect(imported.length).toBeGreaterThanOrEqual(2);
1052
+ expect(imported.some((t) => t.description === "Bulk import 1")).toBe(true);
1053
+ expect(imported.some((t) => t.description === "Bulk import 2")).toBe(true);
1054
+ });
1055
+
1056
+ it("should throw when importing non-existent issue", async () => {
1057
+ await expect(
1058
+ backend.importIssue("nonexistent-issue-id", TEST_AGENT)
1059
+ ).rejects.toThrow(OpenTasksBackendError);
1060
+ });
1061
+ });
1062
+
1063
+ // ─────────────────────────────────────────────────────────────────────────────
1064
+ // 9. Agent History
1065
+ // ─────────────────────────────────────────────────────────────────────────────
1066
+
1067
+ describe("agent history", () => {
1068
+ it("should track assignment history", async () => {
1069
+ const task = await backend.create({
1070
+ description: "History tracking test",
1071
+ created_by: TEST_AGENT,
1072
+ });
1073
+
1074
+ await backend.assign(task.id, WORKER_1);
1075
+ await backend.unassign(task.id);
1076
+ await backend.assign(task.id, WORKER_2);
1077
+
1078
+ const history = await backend.getAgentHistory(task.id);
1079
+ expect(history.length).toBeGreaterThanOrEqual(2);
1080
+
1081
+ // The history should contain both workers
1082
+ const agents = history.map((h) => h.agent_id);
1083
+ expect(agents).toContain(WORKER_1);
1084
+ expect(agents).toContain(WORKER_2);
1085
+ });
1086
+
1087
+ it("should throw for non-existent task", async () => {
1088
+ await expect(
1089
+ backend.getAgentHistory("task_nonexistent")
1090
+ ).rejects.toThrow(OpenTasksBackendError);
1091
+ });
1092
+ });
1093
+
1094
+ // ─────────────────────────────────────────────────────────────────────────────
1095
+ // 10. Event Subscriptions
1096
+ // ─────────────────────────────────────────────────────────────────────────────
1097
+
1098
+ describe("event subscriptions", () => {
1099
+ it("should fire callback on task creation", async () => {
1100
+ const events: TaskChangeEvent[] = [];
1101
+ const unsub = backend.onTaskChange((event) => events.push(event));
1102
+
1103
+ await backend.create({
1104
+ description: "Event creation test",
1105
+ created_by: TEST_AGENT,
1106
+ });
1107
+
1108
+ expect(events.length).toBeGreaterThan(0);
1109
+ expect(events[0].type).toBe("created");
1110
+
1111
+ unsub();
1112
+ });
1113
+
1114
+ it("should fire callback on status changes", async () => {
1115
+ const task = await backend.create({
1116
+ description: "Event status test",
1117
+ created_by: TEST_AGENT,
1118
+ });
1119
+
1120
+ const events: TaskChangeEvent[] = [];
1121
+ const unsub = backend.onTaskChange(task.id, (event) =>
1122
+ events.push(event)
1123
+ );
1124
+
1125
+ await backend.start(task.id);
1126
+ await backend.complete(task.id);
1127
+
1128
+ expect(events.length).toBeGreaterThanOrEqual(2);
1129
+
1130
+ unsub();
1131
+ });
1132
+
1133
+ it("should filter events by taskId", async () => {
1134
+ const t1 = await backend.create({
1135
+ description: "Filtered task 1",
1136
+ created_by: TEST_AGENT,
1137
+ });
1138
+
1139
+ const events: TaskChangeEvent[] = [];
1140
+ const unsub = backend.onTaskChange(t1.id, (event) =>
1141
+ events.push(event)
1142
+ );
1143
+
1144
+ await backend.create({
1145
+ description: "Filtered task 2",
1146
+ created_by: TEST_AGENT,
1147
+ });
1148
+
1149
+ await backend.start(t1.id);
1150
+
1151
+ // All events should be for t1 only
1152
+ expect(events.every((e) => e.taskId === t1.id)).toBe(true);
1153
+
1154
+ unsub();
1155
+ });
1156
+
1157
+ it("should stop receiving events after unsubscribe", async () => {
1158
+ const events: TaskChangeEvent[] = [];
1159
+ const unsub = backend.onTaskChange((event) => events.push(event));
1160
+
1161
+ await backend.create({
1162
+ description: "Before unsub",
1163
+ created_by: TEST_AGENT,
1164
+ });
1165
+
1166
+ const countBefore = events.length;
1167
+ unsub();
1168
+
1169
+ await backend.create({
1170
+ description: "After unsub",
1171
+ created_by: TEST_AGENT,
1172
+ });
1173
+
1174
+ expect(events.length).toBe(countBefore);
1175
+ });
1176
+ });
1177
+
1178
+ // ─────────────────────────────────────────────────────────────────────────────
1179
+ // 11. Sync Configuration
1180
+ // ─────────────────────────────────────────────────────────────────────────────
1181
+
1182
+ describe("sync configuration", () => {
1183
+ it("should not sync status when syncStatus is false", async () => {
1184
+ const noSyncBackend = createOpenTasksTaskBackend(eventStore, client, {
1185
+ syncStatus: false,
1186
+ sourceLabel: "no-sync-test",
1187
+ });
1188
+
1189
+ const task = await noSyncBackend.create({
1190
+ description: "No sync test",
1191
+ created_by: TEST_AGENT,
1192
+ });
1193
+
1194
+ await noSyncBackend.start(task.id);
1195
+
1196
+ // Local status should be in_progress
1197
+ const updated = await noSyncBackend.get(task.id);
1198
+ expect(updated!.status).toBe("in_progress");
1199
+
1200
+ // Daemon issue should still be "open" since sync is disabled
1201
+ const issue = await client.getIssue(task.external_id!);
1202
+ expect(issue!.status).toBe("open");
1203
+ });
1204
+ });
1205
+
1206
+ // ─────────────────────────────────────────────────────────────────────────────
1207
+ // 12. Error Handling
1208
+ // ─────────────────────────────────────────────────────────────────────────────
1209
+
1210
+ describe("error handling", () => {
1211
+ it("should throw TASK_NOT_FOUND for operations on non-existent tasks", async () => {
1212
+ const fakeId = "task_doesnotexist";
1213
+
1214
+ await expect(
1215
+ backend.assign(fakeId, WORKER_1)
1216
+ ).rejects.toThrow("Task not found");
1217
+
1218
+ await expect(
1219
+ backend.start(fakeId)
1220
+ ).rejects.toThrow("Task not found");
1221
+
1222
+ await expect(
1223
+ backend.complete(fakeId)
1224
+ ).rejects.toThrow("Task not found");
1225
+
1226
+ await expect(
1227
+ backend.fail(fakeId, { message: "error" })
1228
+ ).rejects.toThrow("Task not found");
1229
+
1230
+ await expect(
1231
+ backend.addBlocker(fakeId, "task_other")
1232
+ ).rejects.toThrow("Task not found");
1233
+ });
1234
+
1235
+ it("should throw when blocker task does not exist", async () => {
1236
+ const task = await backend.create({
1237
+ description: "Has no blocker",
1238
+ created_by: TEST_AGENT,
1239
+ });
1240
+
1241
+ await expect(
1242
+ backend.addBlocker(task.id, "task_nonexistent_blocker")
1243
+ ).rejects.toThrow("Blocker task not found");
1244
+ });
1245
+
1246
+ it("should throw for update on non-existent task", async () => {
1247
+ await expect(
1248
+ backend.update("task_fake", { description: "new" })
1249
+ ).rejects.toThrow(OpenTasksBackendError);
1250
+ });
1251
+ });
1252
+
1253
+ // ─────────────────────────────────────────────────────────────────────────────
1254
+ // 13. Integration: Full Workflow
1255
+ // ─────────────────────────────────────────────────────────────────────────────
1256
+
1257
+ describe("full workflow integration", () => {
1258
+ it("should handle a complete multi-task workflow with dependencies", async () => {
1259
+ // 1. Create a parent with two children
1260
+ const parent = await backend.create({
1261
+ description: "Refactor authentication module",
1262
+ created_by: TEST_AGENT,
1263
+ tags: ["refactor"],
1264
+ });
1265
+
1266
+ const child1 = await backend.createSubtask(parent.id, {
1267
+ description: "Extract auth middleware",
1268
+ created_by: TEST_AGENT,
1269
+ tags: ["backend"],
1270
+ });
1271
+
1272
+ const child2 = await backend.createSubtask(parent.id, {
1273
+ description: "Update auth tests",
1274
+ created_by: TEST_AGENT,
1275
+ tags: ["testing"],
1276
+ });
1277
+
1278
+ // 2. child2 depends on child1
1279
+ await backend.addBlocker(child2.id, child1.id);
1280
+
1281
+ // 3. Verify initial state
1282
+ let status = await backend.getSubtaskStatus(parent.id);
1283
+ expect(status.total).toBe(2);
1284
+ expect(status.pending).toBe(2);
1285
+
1286
+ // 4. Work on child1 directly (child2 is blocked by it)
1287
+ await backend.assign(child1.id, WORKER_1);
1288
+ await backend.start(child1.id);
1289
+ await backend.complete(child1.id, {
1290
+ summary: "Extracted middleware to separate module",
1291
+ data: { files_changed: 3 },
1292
+ });
1293
+
1294
+ // 5. child2 should now be unblocked
1295
+ const readyTasks = await backend.listReady();
1296
+ const child2Ready = readyTasks.find((t) => t.id === child2.id);
1297
+ expect(child2Ready).toBeDefined();
1298
+
1299
+ // 6. Work on child2
1300
+ await backend.assign(child2.id, WORKER_2);
1301
+ await backend.start(child2.id);
1302
+ await backend.complete(child2.id, {
1303
+ summary: "Updated all test files",
1304
+ });
1305
+
1306
+ // 7. All subtasks completed
1307
+ status = await backend.getSubtaskStatus(parent.id);
1308
+ expect(status.allCompleted).toBe(true);
1309
+ expect(status.anyFailed).toBe(false);
1310
+
1311
+ // 8. Complete parent
1312
+ await backend.start(parent.id);
1313
+ await backend.complete(parent.id, {
1314
+ summary: "Auth module refactored",
1315
+ });
1316
+
1317
+ // 9. Verify everything is completed
1318
+ const finalParent = await backend.get(parent.id);
1319
+ expect(finalParent!.status).toBe("completed");
1320
+
1321
+ const finalChild1 = await backend.get(child1.id);
1322
+ expect(finalChild1!.status).toBe("completed");
1323
+
1324
+ const finalChild2 = await backend.get(child2.id);
1325
+ expect(finalChild2!.status).toBe("completed");
1326
+
1327
+ // 10. Verify all issues are closed in daemon
1328
+ const parentIssue = await client.getIssue(parent.external_id!);
1329
+ expect(parentIssue!.status).toBe("closed");
1330
+
1331
+ const child1Issue = await client.getIssue(child1.external_id!);
1332
+ expect(child1Issue!.status).toBe("closed");
1333
+
1334
+ const child2Issue = await client.getIssue(child2.external_id!);
1335
+ expect(child2Issue!.status).toBe("closed");
1336
+ });
1337
+ });
1338
+ });