macro-agent 0.1.1 → 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 (406) hide show
  1. package/.sudocode/issues.jsonl +28 -0
  2. package/.sudocode/specs.jsonl +4 -0
  3. package/CLAUDE.md +9 -3
  4. package/dist/agent/agent-manager.d.ts.map +1 -1
  5. package/dist/agent/agent-manager.js +111 -48
  6. package/dist/agent/agent-manager.js.map +1 -1
  7. package/dist/agent/types.d.ts +7 -0
  8. package/dist/agent/types.d.ts.map +1 -1
  9. package/dist/agent/types.js.map +1 -1
  10. package/dist/api/server.d.ts +5 -1
  11. package/dist/api/server.d.ts.map +1 -1
  12. package/dist/api/server.js +100 -3
  13. package/dist/api/server.js.map +1 -1
  14. package/dist/api/types.d.ts +1 -1
  15. package/dist/api/types.d.ts.map +1 -1
  16. package/dist/cli/acp.d.ts.map +1 -1
  17. package/dist/cli/acp.js +71 -1
  18. package/dist/cli/acp.js.map +1 -1
  19. package/dist/cli/index.js +5 -1
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/cli/mcp.js +27 -8
  22. package/dist/cli/mcp.js.map +1 -1
  23. package/dist/config/project-config.d.ts +13 -2
  24. package/dist/config/project-config.d.ts.map +1 -1
  25. package/dist/config/project-config.js +12 -2
  26. package/dist/config/project-config.js.map +1 -1
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/lifecycle/handlers/index.d.ts +7 -3
  32. package/dist/lifecycle/handlers/index.d.ts.map +1 -1
  33. package/dist/lifecycle/handlers/index.js +25 -8
  34. package/dist/lifecycle/handlers/index.js.map +1 -1
  35. package/dist/lifecycle/types.d.ts +2 -0
  36. package/dist/lifecycle/types.d.ts.map +1 -1
  37. package/dist/lifecycle/types.js.map +1 -1
  38. package/dist/map/adapter/extensions/index.d.ts +4 -1
  39. package/dist/map/adapter/extensions/index.d.ts.map +1 -1
  40. package/dist/map/adapter/extensions/index.js +27 -0
  41. package/dist/map/adapter/extensions/index.js.map +1 -1
  42. package/dist/map/adapter/extensions/streams.d.ts +95 -0
  43. package/dist/map/adapter/extensions/streams.d.ts.map +1 -0
  44. package/dist/map/adapter/extensions/streams.js +515 -0
  45. package/dist/map/adapter/extensions/streams.js.map +1 -0
  46. package/dist/map/adapter/index.d.ts +1 -1
  47. package/dist/map/adapter/index.d.ts.map +1 -1
  48. package/dist/map/adapter/index.js +3 -1
  49. package/dist/map/adapter/index.js.map +1 -1
  50. package/dist/map/adapter/types.d.ts +1 -1
  51. package/dist/map/adapter/types.d.ts.map +1 -1
  52. package/dist/mcp/mcp-server.d.ts +2 -0
  53. package/dist/mcp/mcp-server.d.ts.map +1 -1
  54. package/dist/mcp/mcp-server.js +12 -3
  55. package/dist/mcp/mcp-server.js.map +1 -1
  56. package/dist/mcp/tools/done.d.ts.map +1 -1
  57. package/dist/mcp/tools/done.js +18 -0
  58. package/dist/mcp/tools/done.js.map +1 -1
  59. package/dist/roles/builtin/coordinator.d.ts.map +1 -1
  60. package/dist/roles/builtin/coordinator.js +2 -1
  61. package/dist/roles/builtin/coordinator.js.map +1 -1
  62. package/dist/roles/builtin/integrator.d.ts.map +1 -1
  63. package/dist/roles/builtin/integrator.js +2 -1
  64. package/dist/roles/builtin/integrator.js.map +1 -1
  65. package/dist/roles/builtin/worker.d.ts.map +1 -1
  66. package/dist/roles/builtin/worker.js +3 -1
  67. package/dist/roles/builtin/worker.js.map +1 -1
  68. package/dist/roles/capabilities.d.ts +6 -0
  69. package/dist/roles/capabilities.d.ts.map +1 -1
  70. package/dist/roles/capabilities.js +10 -0
  71. package/dist/roles/capabilities.js.map +1 -1
  72. package/dist/roles/config-loader.d.ts +1 -1
  73. package/dist/roles/config-loader.d.ts.map +1 -1
  74. package/dist/roles/config-loader.js +3 -2
  75. package/dist/roles/config-loader.js.map +1 -1
  76. package/dist/roles/types.d.ts +3 -1
  77. package/dist/roles/types.d.ts.map +1 -1
  78. package/dist/server/combined-server.d.ts +8 -1
  79. package/dist/server/combined-server.d.ts.map +1 -1
  80. package/dist/server/combined-server.js +6 -2
  81. package/dist/server/combined-server.js.map +1 -1
  82. package/dist/store/event-store.d.ts.map +1 -1
  83. package/dist/store/event-store.js +12 -5
  84. package/dist/store/event-store.js.map +1 -1
  85. package/dist/store/instance.d.ts +1 -1
  86. package/dist/store/instance.d.ts.map +1 -1
  87. package/dist/store/instance.js +2 -2
  88. package/dist/store/instance.js.map +1 -1
  89. package/dist/store/types/agents.d.ts +5 -0
  90. package/dist/store/types/agents.d.ts.map +1 -1
  91. package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -1
  92. package/dist/task/backend/opentasks/daemon-manager.js +1 -1
  93. package/dist/task/backend/opentasks/daemon-manager.js.map +1 -1
  94. package/dist/teams/index.d.ts +3 -1
  95. package/dist/teams/index.d.ts.map +1 -1
  96. package/dist/teams/index.js +2 -0
  97. package/dist/teams/index.js.map +1 -1
  98. package/dist/teams/seed-defaults.d.ts +20 -0
  99. package/dist/teams/seed-defaults.d.ts.map +1 -0
  100. package/dist/teams/seed-defaults.js +71 -0
  101. package/dist/teams/seed-defaults.js.map +1 -0
  102. package/dist/teams/team-loader.d.ts +6 -2
  103. package/dist/teams/team-loader.d.ts.map +1 -1
  104. package/dist/teams/team-loader.js +154 -162
  105. package/dist/teams/team-loader.js.map +1 -1
  106. package/dist/teams/team-manager.d.ts +112 -0
  107. package/dist/teams/team-manager.d.ts.map +1 -0
  108. package/dist/teams/team-manager.js +305 -0
  109. package/dist/teams/team-manager.js.map +1 -0
  110. package/dist/teams/team-runtime.d.ts +125 -19
  111. package/dist/teams/team-runtime.d.ts.map +1 -1
  112. package/dist/teams/team-runtime.js +527 -119
  113. package/dist/teams/team-runtime.js.map +1 -1
  114. package/dist/teams/types.d.ts +41 -151
  115. package/dist/teams/types.d.ts.map +1 -1
  116. package/dist/teams/types.js +2 -3
  117. package/dist/teams/types.js.map +1 -1
  118. package/docs/teams.md +73 -0
  119. package/package.json +2 -1
  120. package/references/minimem/.claude/settings.json +7 -0
  121. package/references/minimem/.sudocode/issues.jsonl +18 -0
  122. package/references/minimem/.sudocode/specs.jsonl +1 -0
  123. package/references/minimem/CLAUDE.md +310 -0
  124. package/references/minimem/README.md +562 -0
  125. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
  126. package/references/minimem/claude-plugin/.mcp.json +7 -0
  127. package/references/minimem/claude-plugin/README.md +158 -0
  128. package/references/minimem/claude-plugin/commands/recall.md +47 -0
  129. package/references/minimem/claude-plugin/commands/remember.md +41 -0
  130. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
  131. package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
  132. package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
  133. package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
  134. package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
  135. package/references/minimem/media/banner.png +0 -0
  136. package/references/minimem/package-lock.json +5373 -0
  137. package/references/minimem/package.json +72 -0
  138. package/references/minimem/scripts/postbuild.js +35 -0
  139. package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
  140. package/references/minimem/src/__tests__/errors.test.ts +265 -0
  141. package/references/minimem/src/__tests__/helpers.ts +199 -0
  142. package/references/minimem/src/__tests__/internal.test.ts +407 -0
  143. package/references/minimem/src/__tests__/knowledge.test.ts +287 -0
  144. package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
  145. package/references/minimem/src/__tests__/session.test.ts +190 -0
  146. package/references/minimem/src/cli/__tests__/commands.test.ts +759 -0
  147. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
  148. package/references/minimem/src/cli/commands/append.ts +76 -0
  149. package/references/minimem/src/cli/commands/config.ts +262 -0
  150. package/references/minimem/src/cli/commands/conflicts.ts +413 -0
  151. package/references/minimem/src/cli/commands/daemon.ts +169 -0
  152. package/references/minimem/src/cli/commands/index.ts +12 -0
  153. package/references/minimem/src/cli/commands/init.ts +88 -0
  154. package/references/minimem/src/cli/commands/mcp.ts +177 -0
  155. package/references/minimem/src/cli/commands/push-pull.ts +213 -0
  156. package/references/minimem/src/cli/commands/search.ts +158 -0
  157. package/references/minimem/src/cli/commands/status.ts +84 -0
  158. package/references/minimem/src/cli/commands/sync-init.ts +290 -0
  159. package/references/minimem/src/cli/commands/sync.ts +70 -0
  160. package/references/minimem/src/cli/commands/upsert.ts +197 -0
  161. package/references/minimem/src/cli/config.ts +584 -0
  162. package/references/minimem/src/cli/index.ts +264 -0
  163. package/references/minimem/src/cli/shared.ts +161 -0
  164. package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
  165. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
  166. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
  167. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
  168. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
  169. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
  170. package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
  171. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
  172. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
  173. package/references/minimem/src/cli/sync/central.ts +292 -0
  174. package/references/minimem/src/cli/sync/conflicts.ts +204 -0
  175. package/references/minimem/src/cli/sync/daemon.ts +407 -0
  176. package/references/minimem/src/cli/sync/detection.ts +138 -0
  177. package/references/minimem/src/cli/sync/index.ts +107 -0
  178. package/references/minimem/src/cli/sync/operations.ts +373 -0
  179. package/references/minimem/src/cli/sync/registry.ts +279 -0
  180. package/references/minimem/src/cli/sync/state.ts +355 -0
  181. package/references/minimem/src/cli/sync/validation.ts +206 -0
  182. package/references/minimem/src/cli/sync/watcher.ts +234 -0
  183. package/references/minimem/src/cli/version.ts +34 -0
  184. package/references/minimem/src/core/index.ts +9 -0
  185. package/references/minimem/src/core/indexer.ts +628 -0
  186. package/references/minimem/src/core/searcher.ts +221 -0
  187. package/references/minimem/src/db/schema.ts +183 -0
  188. package/references/minimem/src/db/sqlite-vec.ts +24 -0
  189. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
  190. package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
  191. package/references/minimem/src/embeddings/batch-openai.ts +409 -0
  192. package/references/minimem/src/embeddings/embeddings.ts +434 -0
  193. package/references/minimem/src/index.ts +109 -0
  194. package/references/minimem/src/internal.ts +299 -0
  195. package/references/minimem/src/minimem.ts +1276 -0
  196. package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
  197. package/references/minimem/src/search/graph.ts +234 -0
  198. package/references/minimem/src/search/hybrid.ts +151 -0
  199. package/references/minimem/src/search/search.ts +256 -0
  200. package/references/minimem/src/server/__tests__/mcp.test.ts +341 -0
  201. package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
  202. package/references/minimem/src/server/mcp.ts +326 -0
  203. package/references/minimem/src/server/tools.ts +720 -0
  204. package/references/minimem/src/session.ts +460 -0
  205. package/references/minimem/tsconfig.json +19 -0
  206. package/references/minimem/tsup.config.ts +26 -0
  207. package/references/minimem/vitest.config.ts +24 -0
  208. package/references/openteams/.claude/settings.json +6 -0
  209. package/references/openteams/README.md +1 -0
  210. package/references/openteams/SKILL.md +341 -0
  211. package/references/openteams/design.md +411 -0
  212. package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +16 -0
  213. package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +5 -0
  214. package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +24 -0
  215. package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +5 -0
  216. package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +25 -0
  217. package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +5 -0
  218. package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +21 -0
  219. package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +5 -0
  220. package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +20 -0
  221. package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +5 -0
  222. package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +17 -0
  223. package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +5 -0
  224. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +23 -0
  225. package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +5 -0
  226. package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +27 -0
  227. package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +5 -0
  228. package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +21 -0
  229. package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +5 -0
  230. package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +16 -0
  231. package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +5 -0
  232. package/references/openteams/examples/bmad-method/roles/analyst.yaml +9 -0
  233. package/references/openteams/examples/bmad-method/roles/architect.yaml +9 -0
  234. package/references/openteams/examples/bmad-method/roles/developer.yaml +8 -0
  235. package/references/openteams/examples/bmad-method/roles/master.yaml +8 -0
  236. package/references/openteams/examples/bmad-method/roles/pm.yaml +9 -0
  237. package/references/openteams/examples/bmad-method/roles/qa.yaml +8 -0
  238. package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +8 -0
  239. package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +9 -0
  240. package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +8 -0
  241. package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +8 -0
  242. package/references/openteams/examples/bmad-method/team.yaml +161 -0
  243. package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/ROLE.md +17 -0
  244. package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/SOUL.md +5 -0
  245. package/references/openteams/examples/get-shit-done/prompts/debugger/ROLE.md +25 -0
  246. package/references/openteams/examples/get-shit-done/prompts/debugger/SOUL.md +5 -0
  247. package/references/openteams/examples/get-shit-done/prompts/executor/ROLE.md +34 -0
  248. package/references/openteams/examples/get-shit-done/prompts/executor/SOUL.md +5 -0
  249. package/references/openteams/examples/get-shit-done/prompts/integration-checker/ROLE.md +18 -0
  250. package/references/openteams/examples/get-shit-done/prompts/integration-checker/SOUL.md +3 -0
  251. package/references/openteams/examples/get-shit-done/prompts/orchestrator/ROLE.md +42 -0
  252. package/references/openteams/examples/get-shit-done/prompts/orchestrator/SOUL.md +5 -0
  253. package/references/openteams/examples/get-shit-done/prompts/phase-researcher/ROLE.md +15 -0
  254. package/references/openteams/examples/get-shit-done/prompts/phase-researcher/SOUL.md +3 -0
  255. package/references/openteams/examples/get-shit-done/prompts/plan-checker/ROLE.md +17 -0
  256. package/references/openteams/examples/get-shit-done/prompts/plan-checker/SOUL.md +3 -0
  257. package/references/openteams/examples/get-shit-done/prompts/planner/ROLE.md +28 -0
  258. package/references/openteams/examples/get-shit-done/prompts/planner/SOUL.md +5 -0
  259. package/references/openteams/examples/get-shit-done/prompts/project-researcher/ROLE.md +16 -0
  260. package/references/openteams/examples/get-shit-done/prompts/project-researcher/SOUL.md +3 -0
  261. package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/ROLE.md +13 -0
  262. package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/SOUL.md +3 -0
  263. package/references/openteams/examples/get-shit-done/prompts/roadmapper/ROLE.md +14 -0
  264. package/references/openteams/examples/get-shit-done/prompts/roadmapper/SOUL.md +3 -0
  265. package/references/openteams/examples/get-shit-done/prompts/verifier/ROLE.md +19 -0
  266. package/references/openteams/examples/get-shit-done/prompts/verifier/SOUL.md +5 -0
  267. package/references/openteams/examples/get-shit-done/roles/codebase-mapper.yaml +8 -0
  268. package/references/openteams/examples/get-shit-done/roles/debugger.yaml +8 -0
  269. package/references/openteams/examples/get-shit-done/roles/executor.yaml +8 -0
  270. package/references/openteams/examples/get-shit-done/roles/integration-checker.yaml +8 -0
  271. package/references/openteams/examples/get-shit-done/roles/orchestrator.yaml +9 -0
  272. package/references/openteams/examples/get-shit-done/roles/phase-researcher.yaml +7 -0
  273. package/references/openteams/examples/get-shit-done/roles/plan-checker.yaml +8 -0
  274. package/references/openteams/examples/get-shit-done/roles/planner.yaml +8 -0
  275. package/references/openteams/examples/get-shit-done/roles/project-researcher.yaml +8 -0
  276. package/references/openteams/examples/get-shit-done/roles/research-synthesizer.yaml +7 -0
  277. package/references/openteams/examples/get-shit-done/roles/roadmapper.yaml +7 -0
  278. package/references/openteams/examples/get-shit-done/roles/verifier.yaml +8 -0
  279. package/references/openteams/examples/get-shit-done/team.yaml +154 -0
  280. package/references/openteams/package-lock.json +2181 -0
  281. package/references/openteams/package.json +48 -0
  282. package/references/openteams/schema/role.schema.json +125 -0
  283. package/references/openteams/schema/team.schema.json +284 -0
  284. package/references/openteams/src/cli/agent.ts +104 -0
  285. package/references/openteams/src/cli/cli.test.ts +381 -0
  286. package/references/openteams/src/cli/generate.ts +220 -0
  287. package/references/openteams/src/cli/message.ts +241 -0
  288. package/references/openteams/src/cli/task.ts +154 -0
  289. package/references/openteams/src/cli/team.ts +104 -0
  290. package/references/openteams/src/cli/template.ts +207 -0
  291. package/references/openteams/src/cli.ts +45 -0
  292. package/references/openteams/src/db/database.test.ts +185 -0
  293. package/references/openteams/src/db/database.ts +240 -0
  294. package/references/openteams/src/generators/agent-prompt-generator.test.ts +332 -0
  295. package/references/openteams/src/generators/agent-prompt-generator.ts +521 -0
  296. package/references/openteams/src/generators/package-generator.test.ts +129 -0
  297. package/references/openteams/src/generators/package-generator.ts +102 -0
  298. package/references/openteams/src/generators/skill-generator.test.ts +246 -0
  299. package/references/openteams/src/generators/skill-generator.ts +374 -0
  300. package/references/openteams/src/index.ts +104 -0
  301. package/references/openteams/src/services/agent-service.test.ts +158 -0
  302. package/references/openteams/src/services/agent-service.ts +84 -0
  303. package/references/openteams/src/services/communication-service.test.ts +455 -0
  304. package/references/openteams/src/services/communication-service.ts +371 -0
  305. package/references/openteams/src/services/message-service.test.ts +342 -0
  306. package/references/openteams/src/services/message-service.ts +203 -0
  307. package/references/openteams/src/services/task-service.test.ts +434 -0
  308. package/references/openteams/src/services/task-service.ts +239 -0
  309. package/references/openteams/src/services/team-service.test.ts +181 -0
  310. package/references/openteams/src/services/team-service.ts +139 -0
  311. package/references/openteams/src/services/template-service.test.ts +306 -0
  312. package/references/openteams/src/services/template-service.ts +182 -0
  313. package/references/openteams/src/spawner/acp-factory.ts +96 -0
  314. package/references/openteams/src/spawner/interface.ts +31 -0
  315. package/references/openteams/src/spawner/mock.test.ts +93 -0
  316. package/references/openteams/src/spawner/mock.ts +59 -0
  317. package/references/openteams/src/template/loader.test.ts +1319 -0
  318. package/references/openteams/src/template/loader.ts +698 -0
  319. package/references/openteams/src/template/types.ts +200 -0
  320. package/references/openteams/src/types.ts +205 -0
  321. package/references/openteams/tsconfig.json +18 -0
  322. package/references/openteams/vitest.config.ts +9 -0
  323. package/references/skill-tree/.claude/settings.json +6 -0
  324. package/references/skill-tree/.sudocode/issues.jsonl +11 -0
  325. package/references/skill-tree/.sudocode/specs.jsonl +1 -0
  326. package/references/skill-tree/CLAUDE.md +150 -0
  327. package/references/skill-tree/README.md +324 -0
  328. package/references/skill-tree/docs/GAPS_v1.md +221 -0
  329. package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
  330. package/references/skill-tree/docs/TODOS.md +91 -0
  331. package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
  332. package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
  333. package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
  334. package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
  335. package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
  336. package/references/skill-tree/docs/scraper/README.md +170 -0
  337. package/references/skill-tree/examples/basic-usage.ts +190 -0
  338. package/references/skill-tree/package-lock.json +1509 -0
  339. package/references/skill-tree/package.json +66 -0
  340. package/references/skill-tree/scraper/README.md +123 -0
  341. package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
  342. package/references/skill-tree/scraper/docs/PLAN.md +336 -0
  343. package/references/skill-tree/scraper/drizzle.config.ts +10 -0
  344. package/references/skill-tree/scraper/package-lock.json +6329 -0
  345. package/references/skill-tree/scraper/package.json +68 -0
  346. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
  347. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
  348. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
  349. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
  350. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
  351. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
  352. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
  353. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
  354. package/references/skill-tree/scraper/tsup.config.ts +14 -0
  355. package/references/skill-tree/scraper/vitest.config.ts +17 -0
  356. package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
  357. package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
  358. package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
  359. package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
  360. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
  361. package/references/skill-tree/test/run-all.ts +106 -0
  362. package/references/skill-tree/test/utils.ts +128 -0
  363. package/references/skill-tree/vitest.config.ts +16 -0
  364. package/src/agent/agent-manager.ts +143 -72
  365. package/src/agent/types.ts +9 -0
  366. package/src/api/__tests__/server.test.ts +203 -4
  367. package/src/api/server.ts +130 -5
  368. package/src/api/types.ts +3 -1
  369. package/src/cli/acp.ts +68 -1
  370. package/src/cli/index.ts +5 -1
  371. package/src/cli/mcp.ts +27 -13
  372. package/src/config/project-config.ts +27 -3
  373. package/src/index.ts +3 -0
  374. package/src/lifecycle/__tests__/handlers.test.ts +53 -0
  375. package/src/lifecycle/handlers/index.ts +25 -8
  376. package/src/lifecycle/types.ts +3 -0
  377. package/src/map/adapter/__tests__/stream-extensions.test.ts +494 -0
  378. package/src/map/adapter/extensions/index.ts +36 -0
  379. package/src/map/adapter/extensions/streams.ts +839 -0
  380. package/src/map/adapter/index.ts +5 -0
  381. package/src/map/adapter/types.ts +8 -1
  382. package/src/mcp/mcp-server.ts +14 -3
  383. package/src/mcp/tools/done.ts +19 -0
  384. package/src/roles/builtin/coordinator.ts +2 -0
  385. package/src/roles/builtin/integrator.ts +2 -0
  386. package/src/roles/builtin/worker.ts +3 -0
  387. package/src/roles/capabilities.ts +11 -0
  388. package/src/roles/config-loader.ts +3 -2
  389. package/src/roles/types.ts +7 -0
  390. package/src/server/combined-server.ts +15 -1
  391. package/src/store/__tests__/event-store-oob.test.ts +109 -0
  392. package/src/store/event-store.ts +13 -3
  393. package/src/store/instance.ts +2 -2
  394. package/src/store/types/agents.ts +5 -0
  395. package/src/task/backend/__tests__/memory-pull-mode.test.ts +153 -0
  396. package/src/task/backend/opentasks/daemon-manager.ts +4 -1
  397. package/src/teams/CLAUDE.md +180 -0
  398. package/src/teams/__tests__/e2e/workspace-isolation.e2e.test.ts +1263 -0
  399. package/src/teams/__tests__/team-manager.test.ts +814 -0
  400. package/src/teams/__tests__/team-system.test.ts +1291 -8
  401. package/src/teams/index.ts +21 -3
  402. package/src/teams/seed-defaults.ts +79 -0
  403. package/src/teams/team-loader.ts +200 -234
  404. package/src/teams/team-manager.ts +387 -0
  405. package/src/teams/team-runtime.ts +590 -121
  406. package/src/teams/types.ts +99 -200
@@ -17,6 +17,8 @@ import type { MessageRouter } from "../../router/message-router.js";
17
17
  import type { EventStore } from "../../store/event-store.js";
18
18
  import type { SpawnAgentOptions } from "../../agent/types.js";
19
19
  import type { AgentId, Event } from "../../store/types/index.js";
20
+ import { TeamLoadError } from "../types.js";
21
+ import type { MacroResolvedTemplate, ResolvedTeamRole, McpServerEntry } from "../types.js";
20
22
 
21
23
  // =============================================================================
22
24
  // Helpers
@@ -209,7 +211,7 @@ describe("Team Template Loading", () => {
209
211
  const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
210
212
 
211
213
  expect(manifest.name).toBe("structured");
212
- expect(manifest.roles).toEqual(["lead", "developer", "reviewer"]);
214
+ expect(manifest.roles).toEqual(["lead", "developer", "reviewer", "merger"]);
213
215
  expect(manifest.macro_agent.task_assignment?.mode).toBe("push");
214
216
  expect(manifest.macro_agent.integration?.strategy).toBe("queue");
215
217
  });
@@ -283,12 +285,23 @@ describe("TeamRuntime", () => {
283
285
  );
284
286
  });
285
287
 
286
- it("sets spawn interceptor on agent manager", async () => {
288
+ it("does not install spawn interceptor directly (TeamManager responsibility)", async () => {
287
289
  const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
288
290
  const runtime = new TeamRuntime(manifest, services);
289
291
 
290
292
  await runtime.initialize();
291
293
 
294
+ // initialize() no longer installs interceptor — that's TeamManager's job
295
+ expect(agentManager.setSpawnInterceptor).not.toHaveBeenCalled();
296
+ });
297
+
298
+ it("installOnServices() sets spawn interceptor on agent manager", async () => {
299
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
300
+ const runtime = new TeamRuntime(manifest, services);
301
+
302
+ await runtime.initialize();
303
+ runtime.installOnServices();
304
+
292
305
  expect(agentManager.setSpawnInterceptor).toHaveBeenCalledWith(
293
306
  expect.any(Function)
294
307
  );
@@ -401,6 +414,7 @@ describe("TeamRuntime", () => {
401
414
  const runtime = new TeamRuntime(manifest, services);
402
415
 
403
416
  await runtime.initialize();
417
+ runtime.installOnServices();
404
418
  await runtime.bootstrap();
405
419
 
406
420
  // Now spawn a grinder through the interceptor
@@ -422,6 +436,7 @@ describe("TeamRuntime", () => {
422
436
  const runtime = new TeamRuntime(manifest, services);
423
437
 
424
438
  await runtime.initialize();
439
+ runtime.installOnServices();
425
440
  await runtime.bootstrap();
426
441
 
427
442
  // Spawn a grinder
@@ -442,6 +457,7 @@ describe("TeamRuntime", () => {
442
457
  const runtime = new TeamRuntime(manifest, services);
443
458
 
444
459
  await runtime.initialize();
460
+ runtime.installOnServices();
445
461
  await runtime.bootstrap();
446
462
 
447
463
  const customPrompt = "My custom prompt";
@@ -479,14 +495,33 @@ describe("TeamRuntime", () => {
479
495
  });
480
496
 
481
497
  describe("teardown()", () => {
482
- it("clears spawn interceptor", async () => {
498
+ it("does not clear interceptor directly (caller responsibility)", async () => {
483
499
  const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
484
500
  const runtime = new TeamRuntime(manifest, services);
485
501
 
486
502
  await runtime.initialize();
503
+ runtime.installOnServices();
487
504
  await runtime.bootstrap();
505
+
506
+ // Reset mock to track only teardown-related calls
507
+ vi.mocked(agentManager.setSpawnInterceptor).mockClear();
508
+
488
509
  await runtime.teardown();
489
510
 
511
+ // teardown() no longer clears interceptor — that's the caller's responsibility
512
+ expect(agentManager.setSpawnInterceptor).not.toHaveBeenCalled();
513
+ });
514
+
515
+ it("uninstallFromServices() clears spawn interceptor", async () => {
516
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
517
+ const runtime = new TeamRuntime(manifest, services);
518
+
519
+ await runtime.initialize();
520
+ runtime.installOnServices();
521
+ await runtime.bootstrap();
522
+ await runtime.teardown();
523
+ runtime.uninstallFromServices();
524
+
490
525
  expect(agentManager.setSpawnInterceptor).toHaveBeenLastCalledWith(null);
491
526
  });
492
527
  });
@@ -679,13 +714,17 @@ describe("TeamRuntime", () => {
679
714
  });
680
715
 
681
716
  describe("signal filtering", () => {
682
- it("installs signal filter on message router after bootstrap", async () => {
717
+ it("installOnServices() installs signal filter on message router", async () => {
683
718
  const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
684
719
  const runtime = new TeamRuntime(manifest, services);
685
720
 
686
721
  await runtime.initialize();
722
+ // bootstrap/initialize alone should NOT install filter
687
723
  await runtime.bootstrap();
724
+ expect(messageRouter.setSignalFilter).not.toHaveBeenCalled();
688
725
 
726
+ // installOnServices() installs the filter
727
+ runtime.installOnServices();
689
728
  expect(messageRouter.setSignalFilter).toHaveBeenCalledTimes(1);
690
729
  expect(messageRouter.setSignalFilter).toHaveBeenCalledWith(expect.any(Function));
691
730
  });
@@ -696,6 +735,7 @@ describe("TeamRuntime", () => {
696
735
 
697
736
  await runtime.initialize();
698
737
  const result = await runtime.bootstrap();
738
+ runtime.installOnServices();
699
739
 
700
740
  // Extract the installed filter
701
741
  const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
@@ -716,6 +756,7 @@ describe("TeamRuntime", () => {
716
756
 
717
757
  await runtime.initialize();
718
758
  const result = await runtime.bootstrap();
759
+ runtime.installOnServices();
719
760
 
720
761
  const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
721
762
  from: string, to: string, signal: string | undefined
@@ -734,6 +775,7 @@ describe("TeamRuntime", () => {
734
775
 
735
776
  await runtime.initialize();
736
777
  const result = await runtime.bootstrap();
778
+ runtime.installOnServices();
737
779
 
738
780
  const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
739
781
  from: string, to: string, signal: string | undefined
@@ -752,6 +794,7 @@ describe("TeamRuntime", () => {
752
794
 
753
795
  await runtime.initialize();
754
796
  const result = await runtime.bootstrap();
797
+ runtime.installOnServices();
755
798
 
756
799
  const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
757
800
  from: string, to: string, signal: string | undefined
@@ -773,6 +816,7 @@ describe("TeamRuntime", () => {
773
816
  const runtime2 = new TeamRuntime(manifest, services);
774
817
  await runtime2.initialize();
775
818
  const result2 = await runtime2.bootstrap();
819
+ runtime2.installOnServices();
776
820
 
777
821
  // Simulate grinder spawn via lifecycle event
778
822
  const deferredCallback = vi.mocked(agentManager.onLifecycleEvent).mock.calls.at(-2)![0];
@@ -799,6 +843,7 @@ describe("TeamRuntime", () => {
799
843
 
800
844
  await runtime.initialize();
801
845
  const result = await runtime.bootstrap();
846
+ runtime.installOnServices();
802
847
 
803
848
  const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
804
849
  from: string, to: string, signal: string | undefined
@@ -817,6 +862,7 @@ describe("TeamRuntime", () => {
817
862
 
818
863
  await runtime.initialize();
819
864
  const result = await runtime.bootstrap();
865
+ runtime.installOnServices();
820
866
 
821
867
  const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls[0][0] as (
822
868
  from: string, to: string, signal: string | undefined
@@ -833,13 +879,17 @@ describe("TeamRuntime", () => {
833
879
  });
834
880
 
835
881
  describe("emission validation", () => {
836
- it("installs emission validator on message router after bootstrap", async () => {
882
+ it("installOnServices() installs emission validator on message router", async () => {
837
883
  const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
838
884
  const runtime = new TeamRuntime(manifest, services);
839
885
 
840
886
  await runtime.initialize();
841
887
  await runtime.bootstrap();
888
+ // bootstrap alone should NOT install validator
889
+ expect(messageRouter.setEmissionValidator).not.toHaveBeenCalled();
842
890
 
891
+ // installOnServices() installs the validator
892
+ runtime.installOnServices();
843
893
  expect(messageRouter.setEmissionValidator).toHaveBeenCalledTimes(1);
844
894
  expect(messageRouter.setEmissionValidator).toHaveBeenCalledWith(expect.any(Function));
845
895
  });
@@ -850,6 +900,7 @@ describe("TeamRuntime", () => {
850
900
 
851
901
  await runtime.initialize();
852
902
  const result = await runtime.bootstrap();
903
+ runtime.installOnServices();
853
904
 
854
905
  const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
855
906
  agentId: string, signal: string | undefined
@@ -869,6 +920,7 @@ describe("TeamRuntime", () => {
869
920
 
870
921
  await runtime.initialize();
871
922
  const result = await runtime.bootstrap();
923
+ runtime.installOnServices();
872
924
 
873
925
  const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
874
926
  agentId: string, signal: string | undefined
@@ -890,6 +942,7 @@ describe("TeamRuntime", () => {
890
942
 
891
943
  await runtime.initialize();
892
944
  const result = await runtime.bootstrap();
945
+ runtime.installOnServices();
893
946
 
894
947
  const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
895
948
  agentId: string, signal: string | undefined
@@ -909,6 +962,7 @@ describe("TeamRuntime", () => {
909
962
 
910
963
  await runtime.initialize();
911
964
  const result = await runtime.bootstrap();
965
+ runtime.installOnServices();
912
966
 
913
967
  const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
914
968
  agentId: string, signal: string | undefined
@@ -928,6 +982,7 @@ describe("TeamRuntime", () => {
928
982
 
929
983
  await runtime.initialize();
930
984
  const result = await runtime.bootstrap();
985
+ runtime.installOnServices();
931
986
 
932
987
  const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
933
988
  agentId: string, signal: string | undefined
@@ -945,6 +1000,7 @@ describe("TeamRuntime", () => {
945
1000
 
946
1001
  await runtime.initialize();
947
1002
  await runtime.bootstrap();
1003
+ runtime.installOnServices();
948
1004
 
949
1005
  const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls[0][0] as (
950
1006
  agentId: string, signal: string | undefined
@@ -961,7 +1017,9 @@ describe("TeamRuntime", () => {
961
1017
 
962
1018
  await runtime.initialize();
963
1019
  await runtime.bootstrap();
1020
+ runtime.installOnServices();
964
1021
 
1022
+ // createEmissionValidator() returns null when no emissions → setEmissionValidator not called
965
1023
  expect(messageRouter.setEmissionValidator).not.toHaveBeenCalled();
966
1024
  });
967
1025
 
@@ -988,6 +1046,119 @@ describe("TeamRuntime", () => {
988
1046
  });
989
1047
  });
990
1048
 
1049
+ describe("exposed factory methods (for TeamManager)", () => {
1050
+ it("createSpawnInterceptor() returns a function", async () => {
1051
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1052
+ const runtime = new TeamRuntime(manifest, services);
1053
+
1054
+ await runtime.initialize();
1055
+
1056
+ const interceptor = runtime.createSpawnInterceptor();
1057
+ expect(interceptor).toBeInstanceOf(Function);
1058
+ });
1059
+
1060
+ it("createSpawnInterceptor() injects team context", async () => {
1061
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1062
+ const runtime = new TeamRuntime(manifest, services);
1063
+
1064
+ await runtime.initialize();
1065
+
1066
+ const interceptor = runtime.createSpawnInterceptor();
1067
+ const result = interceptor({
1068
+ task: "test",
1069
+ role: "grinder",
1070
+ parent: "agent_0",
1071
+ });
1072
+
1073
+ expect(result.config?.env?.MACRO_TEAM_NAME).toBe("self-driving");
1074
+ expect(result.config?.env?.MACRO_TASK_MODE).toBe("pull");
1075
+ expect(result.topics).toContain("work_coordination");
1076
+ });
1077
+
1078
+ it("createSignalFilter() returns a function", async () => {
1079
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1080
+ const runtime = new TeamRuntime(manifest, services);
1081
+
1082
+ await runtime.initialize();
1083
+ await runtime.bootstrap();
1084
+
1085
+ const filter = runtime.createSignalFilter();
1086
+ expect(filter).toBeInstanceOf(Function);
1087
+ });
1088
+
1089
+ it("createEmissionValidator() returns a function when emissions exist", async () => {
1090
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1091
+ const runtime = new TeamRuntime(manifest, services);
1092
+
1093
+ await runtime.initialize();
1094
+ await runtime.bootstrap();
1095
+
1096
+ const validator = runtime.createEmissionValidator();
1097
+ expect(validator).toBeInstanceOf(Function);
1098
+ });
1099
+
1100
+ it("createEmissionValidator() returns null when no emissions", async () => {
1101
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1102
+ manifest.communication.emissions = undefined;
1103
+ const runtime = new TeamRuntime(manifest, services);
1104
+
1105
+ await runtime.initialize();
1106
+ await runtime.bootstrap();
1107
+
1108
+ const validator = runtime.createEmissionValidator();
1109
+ expect(validator).toBeNull();
1110
+ });
1111
+
1112
+ it("getAgentRoleMap() returns role mappings after bootstrap", async () => {
1113
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1114
+ const runtime = new TeamRuntime(manifest, services);
1115
+
1116
+ await runtime.initialize();
1117
+ const result = await runtime.bootstrap();
1118
+
1119
+ const roleMap = runtime.getAgentRoleMap();
1120
+ expect(roleMap.get(result.rootId as AgentId)).toBe("planner");
1121
+ expect(roleMap.get(result.companionIds[0] as AgentId)).toBe("judge");
1122
+ });
1123
+
1124
+ it("registerAgent() adds agent to role map", async () => {
1125
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1126
+ const runtime = new TeamRuntime(manifest, services);
1127
+
1128
+ await runtime.initialize();
1129
+ await runtime.bootstrap();
1130
+
1131
+ runtime.registerAgent("new_agent" as AgentId, "grinder");
1132
+
1133
+ const roleMap = runtime.getAgentRoleMap();
1134
+ expect(roleMap.get("new_agent" as AgentId)).toBe("grinder");
1135
+ });
1136
+
1137
+ it("hasAgent() returns true for known agents", async () => {
1138
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1139
+ const runtime = new TeamRuntime(manifest, services);
1140
+
1141
+ await runtime.initialize();
1142
+ const result = await runtime.bootstrap();
1143
+
1144
+ expect(runtime.hasAgent(result.rootId)).toBe(true);
1145
+ expect(runtime.hasAgent(result.companionIds[0])).toBe(true);
1146
+ expect(runtime.hasAgent("unknown_agent")).toBe(false);
1147
+ });
1148
+
1149
+ it("hasAgent() includes dynamically registered agents", async () => {
1150
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1151
+ const runtime = new TeamRuntime(manifest, services);
1152
+
1153
+ await runtime.initialize();
1154
+ await runtime.bootstrap();
1155
+
1156
+ expect(runtime.hasAgent("dynamic_agent")).toBe(false);
1157
+ runtime.registerAgent("dynamic_agent" as AgentId, "grinder");
1158
+ expect(runtime.hasAgent("dynamic_agent")).toBe(true);
1159
+ });
1160
+ });
1161
+
991
1162
  describe("monitorContinuations()", () => {
992
1163
  it("auto-continues root agent on unexpected stop", async () => {
993
1164
  const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
@@ -1133,9 +1304,9 @@ describe("TeamRuntime", () => {
1133
1304
  await runtime.initialize();
1134
1305
  const result = await runtime.bootstrap();
1135
1306
 
1136
- // Root (lead) + 1 companion (reviewer)
1137
- expect(agentManager.spawn).toHaveBeenCalledTimes(2);
1138
- expect(result.companionIds).toHaveLength(1);
1307
+ // Root (lead) + 2 companions (reviewer, merger)
1308
+ expect(agentManager.spawn).toHaveBeenCalledTimes(3);
1309
+ expect(result.companionIds).toHaveLength(2);
1139
1310
 
1140
1311
  // Verify no pull-mode interaction patterns injected
1141
1312
  const rootCall = vi.mocked(agentManager.spawn).mock.calls[0][0];
@@ -1278,3 +1449,1115 @@ describe("Metrics Module", () => {
1278
1449
  expect(metrics.recentErrors).toEqual([]);
1279
1450
  });
1280
1451
  });
1452
+
1453
+ // =============================================================================
1454
+ // Tests: openteams Migration — Loader Hooks & Error Mapping
1455
+ // =============================================================================
1456
+
1457
+ describe("openteams Migration: Team Loader", () => {
1458
+ let roleRegistry: DefaultRoleRegistry;
1459
+
1460
+ beforeEach(() => {
1461
+ roleRegistry = new DefaultRoleRegistry();
1462
+ });
1463
+
1464
+ describe("buildResolvedTeamRole — enforcement fields", () => {
1465
+ it("maps macro_agent.workspace from role YAML to RoleDefinition", async () => {
1466
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1467
+
1468
+ // judge.yaml has workspace config
1469
+ const judge = manifest._resolvedRoles.get("judge")!;
1470
+ expect(judge.roleDefinition.workspace).toEqual({
1471
+ type: "own",
1472
+ branchPattern: "judge/{agent-id}",
1473
+ cleanupOnTerminate: true,
1474
+ });
1475
+ });
1476
+
1477
+ it("maps macro_agent.lifecycle from role YAML to RoleDefinition", async () => {
1478
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1479
+
1480
+ // planner.yaml has lifecycle: type: daemon, cascade_terminate: true
1481
+ const planner = manifest._resolvedRoles.get("planner")!;
1482
+ expect(planner.roleDefinition.lifecycle).toEqual({
1483
+ type: "daemon",
1484
+ cascadeTerminate: true,
1485
+ });
1486
+
1487
+ // grinder.yaml has lifecycle: type: ephemeral, task_bound: false, max_duration_ms, self_cleanup
1488
+ const grinder = manifest._resolvedRoles.get("grinder")!;
1489
+ expect(grinder.roleDefinition.lifecycle).toEqual({
1490
+ type: "ephemeral",
1491
+ taskBound: false,
1492
+ maxDurationMs: 3600000,
1493
+ selfCleanup: true,
1494
+ });
1495
+ });
1496
+
1497
+ it("falls back to parent role workspace/lifecycle when no macro_agent override", async () => {
1498
+ const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
1499
+
1500
+ // structured roles don't have macro_agent workspace/lifecycle in their YAML
1501
+ // so they inherit from parent role definitions
1502
+ const developer = manifest._resolvedRoles.get("developer")!;
1503
+ // Worker parent has workspace config
1504
+ const parentWorker = roleRegistry.resolveRole("worker");
1505
+ expect(developer.roleDefinition.workspace).toEqual(parentWorker.workspace);
1506
+ });
1507
+
1508
+ it("preserves parent role tools and protocol", async () => {
1509
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1510
+
1511
+ const grinder = manifest._resolvedRoles.get("grinder")!;
1512
+ const parentWorker = roleRegistry.resolveRole("worker");
1513
+ expect(grinder.roleDefinition.tools).toEqual(parentWorker.tools);
1514
+ expect(grinder.roleDefinition.protocol).toEqual(parentWorker.protocol);
1515
+ });
1516
+
1517
+ it("sets correct baseRole from extends chain", async () => {
1518
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1519
+
1520
+ // All roles should reference their base role
1521
+ expect(manifest._resolvedRoles.get("planner")!.baseRole).toBe("coordinator");
1522
+ expect(manifest._resolvedRoles.get("grinder")!.baseRole).toBe("worker");
1523
+ expect(manifest._resolvedRoles.get("judge")!.baseRole).toBe("monitor");
1524
+ });
1525
+
1526
+ it("stores prompt file path in resolved role", async () => {
1527
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1528
+
1529
+ const planner = manifest._resolvedRoles.get("planner")!;
1530
+ expect(planner.prompt).toBe("prompts/planner.md");
1531
+
1532
+ const grinder = manifest._resolvedRoles.get("grinder")!;
1533
+ expect(grinder.prompt).toBe("prompts/grinder.md");
1534
+ });
1535
+ });
1536
+
1537
+ describe("enrichRoleWithSpawnRules hook", () => {
1538
+ it("adds agent.spawn.* capabilities from spawn_rules", async () => {
1539
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1540
+
1541
+ // planner: [grinder, planner]
1542
+ const planner = manifest._resolvedRoles.get("planner")!;
1543
+ expect(planner.capabilities).toContain("agent.spawn.grinder");
1544
+ expect(planner.capabilities).toContain("agent.spawn.planner");
1545
+ });
1546
+
1547
+ it("does not duplicate existing spawn capabilities", async () => {
1548
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1549
+
1550
+ const planner = manifest._resolvedRoles.get("planner")!;
1551
+ const spawnGrinderCount = planner.capabilities.filter(
1552
+ (c) => c === "agent.spawn.grinder"
1553
+ ).length;
1554
+ expect(spawnGrinderCount).toBe(1);
1555
+ });
1556
+
1557
+ it("does not add spawn capabilities for roles with empty spawn_rules", async () => {
1558
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1559
+
1560
+ // judge: [] in spawn_rules — should not have spawn caps added by enrichRoleWithSpawnRules
1561
+ // (judge extends monitor which has no spawn caps)
1562
+ const judge = manifest._resolvedRoles.get("judge")!;
1563
+ const spawnCaps = judge.capabilities.filter((c) => c.startsWith("agent.spawn."));
1564
+ expect(spawnCaps).toEqual([]);
1565
+
1566
+ // grinder: [] in spawn_rules — grinder extends worker which has agent.spawn.worker
1567
+ // from parent, but enrichRoleWithSpawnRules should NOT add any additional spawn caps
1568
+ const grinder = manifest._resolvedRoles.get("grinder")!;
1569
+ const grinderSpawnCaps = grinder.capabilities.filter((c) => c.startsWith("agent.spawn."));
1570
+ // Only has parent-inherited spawn caps, not new ones from spawn_rules
1571
+ expect(grinderSpawnCaps).not.toContain("agent.spawn.grinder");
1572
+ expect(grinderSpawnCaps).not.toContain("agent.spawn.planner");
1573
+ });
1574
+ });
1575
+
1576
+ describe("mapRegistryRole hook", () => {
1577
+ it("resolves known registry roles for extends chains", async () => {
1578
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1579
+
1580
+ // planner extends coordinator — coordinator should be resolvable
1581
+ const planner = manifest._resolvedRoles.get("planner")!;
1582
+ expect(planner.baseRole).toBe("coordinator");
1583
+
1584
+ // Coordinator capabilities should be in the planner's set (minus removals, plus additions)
1585
+ const coordinatorRole = roleRegistry.resolveRole("coordinator");
1586
+ // planner should have coordinator capabilities minus removed ones
1587
+ for (const cap of coordinatorRole.capabilities) {
1588
+ if (cap !== "agent.spawn.integrator" && cap !== "agent.spawn.monitor") {
1589
+ expect(planner.capabilities).toContain(cap);
1590
+ }
1591
+ }
1592
+ });
1593
+ });
1594
+
1595
+ describe("prompt loading and assembly", () => {
1596
+ it("loads prompts keyed by role promptFile path", async () => {
1597
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1598
+
1599
+ // Prompts should be stored under the role's promptFile key
1600
+ expect(manifest._loadedPrompts.has("prompts/planner.md")).toBe(true);
1601
+ expect(manifest._loadedPrompts.has("prompts/grinder.md")).toBe(true);
1602
+ expect(manifest._loadedPrompts.has("prompts/judge.md")).toBe(true);
1603
+ });
1604
+
1605
+ it("loads prompts keyed by topology node prompt path", async () => {
1606
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1607
+
1608
+ // Root topology node has prompt: prompts/planner.md
1609
+ // It should also be stored under that topology key
1610
+ expect(manifest._loadedPrompts.get("prompts/planner.md")).toBeDefined();
1611
+ expect(manifest._loadedPrompts.get("prompts/planner.md")!.length).toBeGreaterThan(0);
1612
+ });
1613
+
1614
+ it("prompt content matches actual file content", async () => {
1615
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1616
+
1617
+ const plannerPrompt = manifest._loadedPrompts.get("prompts/planner.md")!;
1618
+ // Should contain the role name from the actual prompt file
1619
+ expect(plannerPrompt).toContain("Planner");
1620
+ });
1621
+ });
1622
+
1623
+ describe("MCP server loading", () => {
1624
+ it("provides _mcpServers map (even if empty)", async () => {
1625
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1626
+
1627
+ expect(manifest._mcpServers).toBeDefined();
1628
+ expect(manifest._mcpServers).toBeInstanceOf(Map);
1629
+ });
1630
+ });
1631
+
1632
+ describe("error mapping", () => {
1633
+ it("throws MANIFEST_NOT_FOUND for non-existent team", async () => {
1634
+ await expect(
1635
+ loadTeam("nonexistent-team", roleRegistry, PROJECT_ROOT)
1636
+ ).rejects.toThrow(TeamLoadError);
1637
+
1638
+ try {
1639
+ await loadTeam("nonexistent-team", roleRegistry, PROJECT_ROOT);
1640
+ } catch (e) {
1641
+ const err = e as TeamLoadError;
1642
+ expect(err.code).toBe("MANIFEST_NOT_FOUND");
1643
+ expect(err.teamName).toBe("nonexistent-team");
1644
+ }
1645
+ });
1646
+
1647
+ it("includes team name in error", async () => {
1648
+ try {
1649
+ await loadTeam("does-not-exist", roleRegistry, PROJECT_ROOT);
1650
+ } catch (e) {
1651
+ expect(e).toBeInstanceOf(TeamLoadError);
1652
+ expect((e as TeamLoadError).teamName).toBe("does-not-exist");
1653
+ }
1654
+ });
1655
+ });
1656
+
1657
+ describe("communication validation", () => {
1658
+ it("validates self-driving team communication topology", async () => {
1659
+ // Should not throw — well-formed communication config
1660
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1661
+ expect(manifest.communication).toBeDefined();
1662
+ expect(manifest.communication.channels).toBeDefined();
1663
+ expect(manifest.communication.subscriptions).toBeDefined();
1664
+ expect(manifest.communication.emissions).toBeDefined();
1665
+ expect(manifest.communication.routing).toBeDefined();
1666
+ });
1667
+
1668
+ it("validates structured team communication topology", async () => {
1669
+ const manifest = await loadTeam("structured", roleRegistry, PROJECT_ROOT);
1670
+ expect(manifest.communication.channels).toBeDefined();
1671
+ expect(Object.keys(manifest.communication.channels!)).toContain("task_updates");
1672
+ expect(Object.keys(manifest.communication.channels!)).toContain("merge_flow");
1673
+ expect(Object.keys(manifest.communication.channels!)).toContain("review_flow");
1674
+ });
1675
+ });
1676
+ });
1677
+
1678
+ // =============================================================================
1679
+ // Tests: openteams Migration — TeamRuntime Type Detection
1680
+ // =============================================================================
1681
+
1682
+ describe("openteams Migration: TeamRuntime", () => {
1683
+ let roleRegistry: DefaultRoleRegistry;
1684
+ let agentManager: AgentManager;
1685
+ let messageRouter: MessageRouter;
1686
+ let eventStore: EventStore & { _events: Event[] };
1687
+ let services: TeamServices;
1688
+
1689
+ beforeEach(() => {
1690
+ roleRegistry = new DefaultRoleRegistry();
1691
+ eventStore = createMockEventStore() as EventStore & { _events: Event[] };
1692
+ messageRouter = createMockMessageRouter();
1693
+ agentManager = createMockAgentManager(roleRegistry);
1694
+ services = { agentManager, messageRouter, eventStore };
1695
+ });
1696
+
1697
+ describe("MacroResolvedTemplate input", () => {
1698
+ it("accepts MacroResolvedTemplate directly", async () => {
1699
+ // Build a MacroResolvedTemplate from a loaded manifest
1700
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1701
+
1702
+ const resolved: MacroResolvedTemplate = {
1703
+ template: {
1704
+ manifest: {
1705
+ name: manifest.name,
1706
+ description: manifest.description,
1707
+ version: manifest.version,
1708
+ roles: manifest.roles,
1709
+ topology: manifest.topology,
1710
+ communication: manifest.communication,
1711
+ },
1712
+ roles: new Map(),
1713
+ prompts: new Map(),
1714
+ mcpServers: manifest._mcpServers,
1715
+ sourcePath: "",
1716
+ },
1717
+ resolvedRoles: manifest._resolvedRoles,
1718
+ macroAgent: manifest.macro_agent,
1719
+ };
1720
+
1721
+ // Should not throw
1722
+ const runtime = new TeamRuntime(resolved, services);
1723
+ expect(runtime.getTaskMode()).toBe("pull");
1724
+ expect(runtime.getStrategyName()).toBe("trunk");
1725
+ });
1726
+
1727
+ it("getResolvedTemplate() returns the resolved template", async () => {
1728
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1729
+
1730
+ const resolved: MacroResolvedTemplate = {
1731
+ template: {
1732
+ manifest: {
1733
+ name: manifest.name,
1734
+ description: manifest.description,
1735
+ version: manifest.version,
1736
+ roles: manifest.roles,
1737
+ topology: manifest.topology,
1738
+ communication: manifest.communication,
1739
+ },
1740
+ roles: new Map(),
1741
+ prompts: new Map(),
1742
+ mcpServers: manifest._mcpServers,
1743
+ sourcePath: "",
1744
+ },
1745
+ resolvedRoles: manifest._resolvedRoles,
1746
+ macroAgent: manifest.macro_agent,
1747
+ };
1748
+
1749
+ const runtime = new TeamRuntime(resolved, services);
1750
+ const result = runtime.getResolvedTemplate();
1751
+
1752
+ expect(result).toBe(resolved);
1753
+ expect(result.resolvedRoles).toBe(manifest._resolvedRoles);
1754
+ expect(result.macroAgent).toBe(manifest.macro_agent);
1755
+ });
1756
+
1757
+ it("initializes and bootstraps with MacroResolvedTemplate", async () => {
1758
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1759
+
1760
+ const resolved: MacroResolvedTemplate = {
1761
+ template: {
1762
+ manifest: {
1763
+ name: manifest.name,
1764
+ description: manifest.description,
1765
+ version: manifest.version,
1766
+ roles: manifest.roles,
1767
+ topology: manifest.topology,
1768
+ communication: manifest.communication,
1769
+ },
1770
+ roles: new Map(),
1771
+ prompts: new Map(),
1772
+ mcpServers: manifest._mcpServers,
1773
+ sourcePath: "",
1774
+ },
1775
+ resolvedRoles: manifest._resolvedRoles,
1776
+ macroAgent: manifest.macro_agent,
1777
+ };
1778
+
1779
+ const runtime = new TeamRuntime(resolved, services);
1780
+ await runtime.initialize();
1781
+ const result = await runtime.bootstrap();
1782
+
1783
+ expect(result.rootId).toBeDefined();
1784
+ expect(result.companionIds).toHaveLength(1);
1785
+ expect(agentManager.spawn).toHaveBeenCalledTimes(2);
1786
+ });
1787
+
1788
+ it("emits team_config event with MacroResolvedTemplate", async () => {
1789
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1790
+
1791
+ const resolved: MacroResolvedTemplate = {
1792
+ template: {
1793
+ manifest: {
1794
+ name: manifest.name,
1795
+ description: manifest.description,
1796
+ version: manifest.version,
1797
+ roles: manifest.roles,
1798
+ topology: manifest.topology,
1799
+ communication: manifest.communication,
1800
+ },
1801
+ roles: new Map(),
1802
+ prompts: new Map(),
1803
+ mcpServers: manifest._mcpServers,
1804
+ sourcePath: "",
1805
+ },
1806
+ resolvedRoles: manifest._resolvedRoles,
1807
+ macroAgent: manifest.macro_agent,
1808
+ };
1809
+
1810
+ const runtime = new TeamRuntime(resolved, services);
1811
+ await runtime.initialize();
1812
+
1813
+ expect(eventStore.emit).toHaveBeenCalledWith(
1814
+ expect.objectContaining({
1815
+ type: "status",
1816
+ payload: expect.objectContaining({
1817
+ team_config: expect.objectContaining({
1818
+ teamName: "self-driving",
1819
+ strategy: "trunk",
1820
+ taskMode: "pull",
1821
+ }),
1822
+ }),
1823
+ })
1824
+ );
1825
+ });
1826
+ });
1827
+
1828
+ describe("manifestToResolved conversion", () => {
1829
+ it("converts legacy TeamManifest to MacroResolvedTemplate internally", async () => {
1830
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1831
+
1832
+ // Pass TeamManifest (legacy path)
1833
+ const runtime = new TeamRuntime(manifest, services);
1834
+ const resolved = runtime.getResolvedTemplate();
1835
+
1836
+ // Should have been converted internally
1837
+ expect(resolved.template.manifest.name).toBe("self-driving");
1838
+ expect(resolved.resolvedRoles).toBe(manifest._resolvedRoles);
1839
+ expect(resolved.macroAgent).toBe(manifest.macro_agent);
1840
+ expect(resolved.template.mcpServers).toBe(manifest._mcpServers);
1841
+ });
1842
+
1843
+ it("preserves topology and communication in conversion", async () => {
1844
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1845
+
1846
+ const runtime = new TeamRuntime(manifest, services);
1847
+ const resolved = runtime.getResolvedTemplate();
1848
+
1849
+ expect(resolved.template.manifest.topology).toBe(manifest.topology);
1850
+ expect(resolved.template.manifest.communication).toBe(manifest.communication);
1851
+ });
1852
+ });
1853
+
1854
+ describe("getManifest() backward compatibility", () => {
1855
+ it("reconstructs TeamManifest from MacroResolvedTemplate", async () => {
1856
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1857
+
1858
+ const resolved: MacroResolvedTemplate = {
1859
+ template: {
1860
+ manifest: {
1861
+ name: manifest.name,
1862
+ description: manifest.description,
1863
+ version: manifest.version,
1864
+ roles: manifest.roles,
1865
+ topology: manifest.topology,
1866
+ communication: manifest.communication,
1867
+ },
1868
+ roles: new Map(),
1869
+ prompts: new Map(),
1870
+ mcpServers: manifest._mcpServers,
1871
+ sourcePath: "",
1872
+ },
1873
+ resolvedRoles: manifest._resolvedRoles,
1874
+ macroAgent: manifest.macro_agent,
1875
+ };
1876
+
1877
+ const runtime = new TeamRuntime(resolved, services);
1878
+ const backCompat = runtime.getManifest();
1879
+
1880
+ expect(backCompat.name).toBe("self-driving");
1881
+ expect(backCompat._resolvedRoles).toBe(manifest._resolvedRoles);
1882
+ expect(backCompat._mcpServers).toBe(manifest._mcpServers);
1883
+ expect(backCompat.macro_agent).toBe(manifest.macro_agent);
1884
+ });
1885
+
1886
+ it("reconstructs TeamManifest from legacy TeamManifest input", async () => {
1887
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1888
+
1889
+ const runtime = new TeamRuntime(manifest, services);
1890
+ const backCompat = runtime.getManifest();
1891
+
1892
+ expect(backCompat.name).toBe("self-driving");
1893
+ expect(backCompat._resolvedRoles).toBe(manifest._resolvedRoles);
1894
+ expect(backCompat._loadedPrompts).toBe(manifest._loadedPrompts);
1895
+ });
1896
+ });
1897
+
1898
+ describe("spawn interceptor with MacroResolvedTemplate", () => {
1899
+ it("injects team context when using MacroResolvedTemplate", async () => {
1900
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1901
+
1902
+ const resolved: MacroResolvedTemplate = {
1903
+ template: {
1904
+ manifest: {
1905
+ name: manifest.name,
1906
+ description: manifest.description,
1907
+ version: manifest.version,
1908
+ roles: manifest.roles,
1909
+ topology: manifest.topology,
1910
+ communication: manifest.communication,
1911
+ },
1912
+ roles: new Map(),
1913
+ prompts: new Map(),
1914
+ mcpServers: manifest._mcpServers,
1915
+ sourcePath: "",
1916
+ },
1917
+ resolvedRoles: manifest._resolvedRoles,
1918
+ macroAgent: manifest.macro_agent,
1919
+ };
1920
+
1921
+ const runtime = new TeamRuntime(resolved, services);
1922
+ await runtime.initialize();
1923
+ runtime.installOnServices();
1924
+ await runtime.bootstrap();
1925
+
1926
+ // Spawn a grinder through the interceptor
1927
+ await agentManager.spawn({
1928
+ task: "test grinder task",
1929
+ role: "grinder",
1930
+ parent: "agent_0",
1931
+ });
1932
+
1933
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
1934
+ expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBe("self-driving");
1935
+ expect(lastOpts.config?.env?.MACRO_TASK_MODE).toBe("pull");
1936
+ expect(lastOpts.topics).toContain("work_coordination");
1937
+ });
1938
+
1939
+ it("resolves MCP servers from MacroResolvedTemplate", async () => {
1940
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1941
+
1942
+ // Add a mock MCP server for grinder
1943
+ const mcpServers = new Map<string, McpServerEntry[]>();
1944
+ mcpServers.set("grinder", [{
1945
+ name: "test-server",
1946
+ command: "node",
1947
+ args: ["test.js"],
1948
+ }]);
1949
+
1950
+ const resolved: MacroResolvedTemplate = {
1951
+ template: {
1952
+ manifest: {
1953
+ name: manifest.name,
1954
+ description: manifest.description,
1955
+ version: manifest.version,
1956
+ roles: manifest.roles,
1957
+ topology: manifest.topology,
1958
+ communication: manifest.communication,
1959
+ },
1960
+ roles: new Map(),
1961
+ prompts: new Map(),
1962
+ mcpServers,
1963
+ sourcePath: "",
1964
+ },
1965
+ resolvedRoles: manifest._resolvedRoles,
1966
+ macroAgent: manifest.macro_agent,
1967
+ };
1968
+
1969
+ const runtime = new TeamRuntime(resolved, services);
1970
+ await runtime.initialize();
1971
+ runtime.installOnServices();
1972
+ await runtime.bootstrap();
1973
+
1974
+ // Spawn a grinder
1975
+ await agentManager.spawn({
1976
+ task: "test task",
1977
+ role: "grinder",
1978
+ parent: "agent_0",
1979
+ });
1980
+
1981
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
1982
+ expect(lastOpts.config?.mcpServers).toBeDefined();
1983
+ expect(lastOpts.config?.mcpServers!.length).toBeGreaterThanOrEqual(1);
1984
+ expect(lastOpts.config?.mcpServers!.some((s: any) => s.name === "test-server")).toBe(true);
1985
+ });
1986
+ });
1987
+
1988
+ describe("serialized roles in team_config", () => {
1989
+ it("serializes resolved roles with capabilities", async () => {
1990
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
1991
+ const runtime = new TeamRuntime(manifest, services);
1992
+
1993
+ await runtime.initialize();
1994
+
1995
+ const emitCall = vi.mocked(eventStore.emit).mock.calls[0][0] as any;
1996
+ const serializedRoles = emitCall.payload.team_config.roles;
1997
+
1998
+ expect(serializedRoles.planner).toBeDefined();
1999
+ expect(serializedRoles.planner.name).toBe("planner");
2000
+ expect(serializedRoles.planner.capabilities).toContain("task.claim");
2001
+ expect(serializedRoles.planner.capabilities).toContain("agent.spawn.grinder");
2002
+
2003
+ expect(serializedRoles.grinder).toBeDefined();
2004
+ expect(serializedRoles.grinder.name).toBe("grinder");
2005
+ expect(serializedRoles.grinder.capabilities).toContain("task.claim");
2006
+ expect(serializedRoles.grinder.capabilities).toContain("git.push");
2007
+
2008
+ expect(serializedRoles.judge).toBeDefined();
2009
+ expect(serializedRoles.judge.name).toBe("judge");
2010
+ });
2011
+
2012
+ it("includes lifecycle and description in serialized roles", async () => {
2013
+ const manifest = await loadTeam("self-driving", roleRegistry, PROJECT_ROOT);
2014
+ const runtime = new TeamRuntime(manifest, services);
2015
+
2016
+ await runtime.initialize();
2017
+
2018
+ const emitCall = vi.mocked(eventStore.emit).mock.calls[0][0] as any;
2019
+ const serializedRoles = emitCall.payload.team_config.roles;
2020
+
2021
+ // planner has lifecycle config
2022
+ expect(serializedRoles.planner.lifecycle).toBeDefined();
2023
+
2024
+ // All roles have descriptions
2025
+ expect(serializedRoles.planner.description).toBeDefined();
2026
+ expect(serializedRoles.grinder.description).toBeDefined();
2027
+ expect(serializedRoles.judge.description).toBeDefined();
2028
+ });
2029
+ });
2030
+ });
2031
+
2032
+ // =============================================================================
2033
+ // Tests: Auto-Scaling
2034
+ // =============================================================================
2035
+
2036
+ describe("TeamRuntime auto-scaling", () => {
2037
+ let roleRegistry: DefaultRoleRegistry;
2038
+ let agentManager: AgentManager;
2039
+ let messageRouter: MessageRouter;
2040
+ let eventStore: EventStore;
2041
+
2042
+ beforeEach(() => {
2043
+ roleRegistry = new DefaultRoleRegistry();
2044
+ eventStore = createMockEventStore();
2045
+ messageRouter = createMockMessageRouter();
2046
+ agentManager = createMockAgentManager(roleRegistry);
2047
+ vi.useFakeTimers();
2048
+ });
2049
+
2050
+ afterEach(() => {
2051
+ vi.useRealTimers();
2052
+ });
2053
+
2054
+ function createScalingTemplate(scalingConfig: {
2055
+ min_workers?: number;
2056
+ max_workers?: number;
2057
+ scale_on?: "task_queue_depth" | "manual";
2058
+ idle_drain?: boolean;
2059
+ }): MacroResolvedTemplate {
2060
+ const workerRole: ResolvedTeamRole = {
2061
+ name: "grinder",
2062
+ baseRole: "worker",
2063
+ capabilities: ["file.read", "file.write", "task.claim", "lifecycle.done"],
2064
+ roleDefinition: {
2065
+ name: "grinder",
2066
+ capabilities: ["file.read", "file.write", "task.claim", "lifecycle.done"],
2067
+ },
2068
+ };
2069
+
2070
+ const plannerRole: ResolvedTeamRole = {
2071
+ name: "planner",
2072
+ baseRole: "coordinator",
2073
+ capabilities: ["*"],
2074
+ roleDefinition: {
2075
+ name: "planner",
2076
+ capabilities: ["*"],
2077
+ },
2078
+ };
2079
+
2080
+ return {
2081
+ template: {
2082
+ manifest: {
2083
+ name: "test-scaling",
2084
+ version: 1,
2085
+ roles: ["planner", "grinder"],
2086
+ topology: {
2087
+ root: { role: "planner", prompt: "prompts/planner.md" },
2088
+ spawn_rules: { planner: ["grinder"], grinder: [] },
2089
+ },
2090
+ },
2091
+ roles: new Map(),
2092
+ prompts: new Map(),
2093
+ mcpServers: new Map<string, McpServerEntry[]>(),
2094
+ sourcePath: "",
2095
+ },
2096
+ resolvedRoles: new Map<string, ResolvedTeamRole>([
2097
+ ["planner", plannerRole],
2098
+ ["grinder", workerRole],
2099
+ ]),
2100
+ macroAgent: {
2101
+ task_assignment: { mode: "pull" as const },
2102
+ lifecycle: { scaling: scalingConfig },
2103
+ },
2104
+ };
2105
+ }
2106
+
2107
+ it("spawns worker when task queue depth exceeds active workers", async () => {
2108
+ const mockTaskBackend = {
2109
+ listClaimable: vi.fn().mockResolvedValue([
2110
+ { id: "t1", title: "Task 1" },
2111
+ { id: "t2", title: "Task 2" },
2112
+ ]),
2113
+ };
2114
+
2115
+ // No active workers initially
2116
+ vi.mocked(agentManager.list).mockReturnValue([]);
2117
+
2118
+ const services: TeamServices = {
2119
+ agentManager, messageRouter, eventStore,
2120
+ taskBackend: mockTaskBackend as any,
2121
+ };
2122
+
2123
+ const resolved = createScalingTemplate({
2124
+ max_workers: 5,
2125
+ scale_on: "task_queue_depth",
2126
+ });
2127
+ const runtime = new TeamRuntime(resolved, services);
2128
+ await runtime.initialize();
2129
+ await runtime.bootstrap();
2130
+
2131
+ // Advance timer to trigger scaling check (5s interval)
2132
+ await vi.advanceTimersByTimeAsync(5_100);
2133
+
2134
+ // Should have spawned a grinder as child of root
2135
+ const spawnCalls = vi.mocked(agentManager.spawn).mock.calls;
2136
+ const scalingSpawn = spawnCalls.find(
2137
+ call => call[0].role === "grinder" && call[0].parent !== null && call[0].task?.includes("auto-scaled")
2138
+ );
2139
+ expect(scalingSpawn).toBeDefined();
2140
+
2141
+ await runtime.teardown();
2142
+ });
2143
+
2144
+ it("respects max_workers cap", async () => {
2145
+ const mockTaskBackend = {
2146
+ listClaimable: vi.fn().mockResolvedValue([
2147
+ { id: "t1", title: "Task 1" },
2148
+ { id: "t2", title: "Task 2" },
2149
+ ]),
2150
+ };
2151
+
2152
+ // Already at max workers
2153
+ vi.mocked(agentManager.list).mockReturnValue([
2154
+ { id: "w1", role: "grinder", state: "running" } as any,
2155
+ { id: "w2", role: "grinder", state: "running" } as any,
2156
+ ]);
2157
+
2158
+ const services: TeamServices = {
2159
+ agentManager, messageRouter, eventStore,
2160
+ taskBackend: mockTaskBackend as any,
2161
+ };
2162
+
2163
+ const resolved = createScalingTemplate({
2164
+ max_workers: 2,
2165
+ scale_on: "task_queue_depth",
2166
+ });
2167
+ const runtime = new TeamRuntime(resolved, services);
2168
+ await runtime.initialize();
2169
+ await runtime.bootstrap();
2170
+
2171
+ // Register the workers in the runtime's agentRoleMap
2172
+ runtime.registerAgent("w1" as AgentId, "grinder");
2173
+ runtime.registerAgent("w2" as AgentId, "grinder");
2174
+
2175
+ const spawnCountBefore = vi.mocked(agentManager.spawn).mock.calls.length;
2176
+
2177
+ await vi.advanceTimersByTimeAsync(5_100);
2178
+
2179
+ // No additional spawns — already at max
2180
+ const scalingSpawns = vi.mocked(agentManager.spawn).mock.calls.slice(spawnCountBefore).filter(
2181
+ call => call[0].task?.includes("auto-scaled")
2182
+ );
2183
+ expect(scalingSpawns).toHaveLength(0);
2184
+
2185
+ await runtime.teardown();
2186
+ });
2187
+
2188
+ it("does not spawn when scale_on is not task_queue_depth", async () => {
2189
+ const mockTaskBackend = {
2190
+ listClaimable: vi.fn().mockResolvedValue([
2191
+ { id: "t1", title: "Task 1" },
2192
+ ]),
2193
+ };
2194
+
2195
+ const services: TeamServices = {
2196
+ agentManager, messageRouter, eventStore,
2197
+ taskBackend: mockTaskBackend as any,
2198
+ };
2199
+
2200
+ const resolved = createScalingTemplate({
2201
+ max_workers: 5,
2202
+ scale_on: "manual",
2203
+ });
2204
+ const runtime = new TeamRuntime(resolved, services);
2205
+ await runtime.initialize();
2206
+ await runtime.bootstrap();
2207
+
2208
+ const spawnCountBefore = vi.mocked(agentManager.spawn).mock.calls.length;
2209
+
2210
+ await vi.advanceTimersByTimeAsync(10_000);
2211
+
2212
+ // No scaling spawns
2213
+ const scalingSpawns = vi.mocked(agentManager.spawn).mock.calls.slice(spawnCountBefore).filter(
2214
+ call => call[0].task?.includes("auto-scaled")
2215
+ );
2216
+ expect(scalingSpawns).toHaveLength(0);
2217
+
2218
+ // listClaimable should not have been called
2219
+ expect(mockTaskBackend.listClaimable).not.toHaveBeenCalled();
2220
+
2221
+ await runtime.teardown();
2222
+ });
2223
+
2224
+ it("respects cooldown between scale-up actions", async () => {
2225
+ const mockTaskBackend = {
2226
+ listClaimable: vi.fn().mockResolvedValue([
2227
+ { id: "t1" }, { id: "t2" }, { id: "t3" },
2228
+ ]),
2229
+ };
2230
+
2231
+ vi.mocked(agentManager.list).mockReturnValue([]);
2232
+
2233
+ const services: TeamServices = {
2234
+ agentManager, messageRouter, eventStore,
2235
+ taskBackend: mockTaskBackend as any,
2236
+ };
2237
+
2238
+ const resolved = createScalingTemplate({
2239
+ max_workers: 10,
2240
+ scale_on: "task_queue_depth",
2241
+ });
2242
+ const runtime = new TeamRuntime(resolved, services);
2243
+ await runtime.initialize();
2244
+ await runtime.bootstrap();
2245
+
2246
+ const spawnCountBefore = vi.mocked(agentManager.spawn).mock.calls.length;
2247
+
2248
+ // First tick at 5s — should spawn
2249
+ await vi.advanceTimersByTimeAsync(5_100);
2250
+ const afterFirst = vi.mocked(agentManager.spawn).mock.calls.slice(spawnCountBefore).filter(
2251
+ call => call[0].task?.includes("auto-scaled")
2252
+ );
2253
+ expect(afterFirst).toHaveLength(1);
2254
+
2255
+ // Second tick at 10s — cooldown (10s from first spawn) not elapsed
2256
+ await vi.advanceTimersByTimeAsync(5_000);
2257
+ const afterSecond = vi.mocked(agentManager.spawn).mock.calls.slice(spawnCountBefore).filter(
2258
+ call => call[0].task?.includes("auto-scaled")
2259
+ );
2260
+ expect(afterSecond).toHaveLength(1); // Still just 1
2261
+
2262
+ // Third tick at 15s — cooldown elapsed, should spawn again
2263
+ await vi.advanceTimersByTimeAsync(5_100);
2264
+ const afterThird = vi.mocked(agentManager.spawn).mock.calls.slice(spawnCountBefore).filter(
2265
+ call => call[0].task?.includes("auto-scaled")
2266
+ );
2267
+ expect(afterThird).toHaveLength(2);
2268
+
2269
+ await runtime.teardown();
2270
+ });
2271
+ });
2272
+
2273
+ // =============================================================================
2274
+ // Tests: Workspace Isolation (Capability-Based)
2275
+ // =============================================================================
2276
+
2277
+ describe("Workspace Isolation", () => {
2278
+ let roleRegistry: DefaultRoleRegistry;
2279
+ let agentManager: AgentManager;
2280
+ let messageRouter: MessageRouter;
2281
+ let eventStore: EventStore;
2282
+
2283
+ beforeEach(() => {
2284
+ roleRegistry = new DefaultRoleRegistry();
2285
+ agentManager = createMockAgentManager(roleRegistry);
2286
+ messageRouter = createMockMessageRouter();
2287
+ eventStore = createMockEventStore();
2288
+ });
2289
+
2290
+ function createWorkspaceTemplate(): MacroResolvedTemplate {
2291
+ const resolvedRoles = new Map<string, ResolvedTeamRole>();
2292
+ resolvedRoles.set("lead", {
2293
+ name: "lead",
2294
+ baseRole: "coordinator",
2295
+ capabilities: [
2296
+ "file.read", "file.write", "task.create", "task.assign",
2297
+ "agent.spawn.worker", "agent.terminate", "workspace.stream",
2298
+ ],
2299
+ roleDefinition: roleRegistry.resolveRole("coordinator"),
2300
+ });
2301
+ resolvedRoles.set("developer", {
2302
+ name: "developer",
2303
+ baseRole: "worker",
2304
+ capabilities: [
2305
+ "file.read", "file.write", "git.commit", "lifecycle.done",
2306
+ "workspace.worktree",
2307
+ ],
2308
+ roleDefinition: roleRegistry.resolveRole("worker"),
2309
+ });
2310
+ resolvedRoles.set("merger", {
2311
+ name: "merger",
2312
+ baseRole: "integrator",
2313
+ capabilities: [
2314
+ "file.read", "file.write", "git.merge", "lifecycle.done",
2315
+ "workspace.integrate",
2316
+ ],
2317
+ roleDefinition: roleRegistry.resolveRole("integrator"),
2318
+ });
2319
+ resolvedRoles.set("watcher", {
2320
+ name: "watcher",
2321
+ baseRole: "monitor",
2322
+ capabilities: ["file.read", "msg.send"],
2323
+ roleDefinition: roleRegistry.resolveRole("monitor"),
2324
+ });
2325
+
2326
+ return {
2327
+ template: {
2328
+ manifest: {
2329
+ name: "workspace-test",
2330
+ version: 1,
2331
+ roles: ["lead", "developer", "merger", "watcher"],
2332
+ topology: {
2333
+ root: { role: "lead" },
2334
+ companions: [{ role: "merger" }],
2335
+ },
2336
+ communication: {},
2337
+ },
2338
+ roles: new Map(),
2339
+ prompts: new Map(),
2340
+ mcpServers: new Map<string, McpServerEntry[]>(),
2341
+ sourcePath: "",
2342
+ },
2343
+ resolvedRoles,
2344
+ macroAgent: {
2345
+ integration: { strategy: "queue" },
2346
+ task_assignment: { mode: "push" as const },
2347
+ },
2348
+ };
2349
+ }
2350
+
2351
+ it("spawn interceptor injects streamId for role with workspace.worktree", async () => {
2352
+ const template = createWorkspaceTemplate();
2353
+ const mockWorkspaceManager = {
2354
+ createIntegrationStream: vi.fn().mockReturnValue("stream-1"),
2355
+ getMergeQueue: vi.fn().mockReturnValue(null),
2356
+ getWorkspace: vi.fn(),
2357
+ };
2358
+
2359
+ const services: TeamServices = {
2360
+ agentManager,
2361
+ messageRouter,
2362
+ eventStore,
2363
+ workspaceManager: mockWorkspaceManager as any,
2364
+ };
2365
+
2366
+ const runtime = new TeamRuntime(template, services);
2367
+ await runtime.initialize();
2368
+ await runtime.bootstrap();
2369
+
2370
+ // Now spawn a developer (extends worker) through the interceptor
2371
+ const interceptor = runtime.createSpawnInterceptor();
2372
+ const result = interceptor({
2373
+ task: "implement feature",
2374
+ role: "developer",
2375
+ parent: "agent_0",
2376
+ });
2377
+
2378
+ expect(result.streamId).toBe("stream-1");
2379
+ expect(result.dataplaneTaskId).toBeDefined();
2380
+ expect(result.capabilities).toContain("workspace.worktree");
2381
+
2382
+ await runtime.teardown();
2383
+ });
2384
+
2385
+ it("spawn interceptor injects streamId for role with workspace.integrate", async () => {
2386
+ const template = createWorkspaceTemplate();
2387
+ const mockWorkspaceManager = {
2388
+ createIntegrationStream: vi.fn().mockReturnValue("stream-1"),
2389
+ getMergeQueue: vi.fn().mockReturnValue(null),
2390
+ getWorkspace: vi.fn(),
2391
+ };
2392
+
2393
+ const services: TeamServices = {
2394
+ agentManager,
2395
+ messageRouter,
2396
+ eventStore,
2397
+ workspaceManager: mockWorkspaceManager as any,
2398
+ };
2399
+
2400
+ const runtime = new TeamRuntime(template, services);
2401
+ await runtime.initialize();
2402
+ await runtime.bootstrap();
2403
+
2404
+ const interceptor = runtime.createSpawnInterceptor();
2405
+ const result = interceptor({
2406
+ task: "merge changes",
2407
+ role: "merger",
2408
+ parent: "agent_0",
2409
+ });
2410
+
2411
+ expect(result.streamId).toBe("stream-1");
2412
+ expect(result.dataplaneTaskId).toBeUndefined(); // Integrators don't get dataplaneTaskId
2413
+ expect(result.capabilities).toContain("workspace.integrate");
2414
+
2415
+ await runtime.teardown();
2416
+ });
2417
+
2418
+ it("spawn interceptor does NOT inject workspace fields for role without workspace capability", async () => {
2419
+ const template = createWorkspaceTemplate();
2420
+ const mockWorkspaceManager = {
2421
+ createIntegrationStream: vi.fn().mockReturnValue("stream-1"),
2422
+ getMergeQueue: vi.fn().mockReturnValue(null),
2423
+ getWorkspace: vi.fn(),
2424
+ };
2425
+
2426
+ const services: TeamServices = {
2427
+ agentManager,
2428
+ messageRouter,
2429
+ eventStore,
2430
+ workspaceManager: mockWorkspaceManager as any,
2431
+ };
2432
+
2433
+ const runtime = new TeamRuntime(template, services);
2434
+ await runtime.initialize();
2435
+ await runtime.bootstrap();
2436
+
2437
+ const interceptor = runtime.createSpawnInterceptor();
2438
+ const result = interceptor({
2439
+ task: "monitor health",
2440
+ role: "watcher",
2441
+ parent: "agent_0",
2442
+ });
2443
+
2444
+ // Monitor role has no workspace capabilities
2445
+ expect(result.streamId).toBeUndefined();
2446
+ expect(result.streamConfig).toBeUndefined();
2447
+ expect(result.dataplaneTaskId).toBeUndefined();
2448
+
2449
+ await runtime.teardown();
2450
+ });
2451
+
2452
+ it("spawn interceptor does not overwrite explicit workspace values", async () => {
2453
+ const template = createWorkspaceTemplate();
2454
+ const mockWorkspaceManager = {
2455
+ createIntegrationStream: vi.fn().mockReturnValue("stream-1"),
2456
+ getMergeQueue: vi.fn().mockReturnValue(null),
2457
+ getWorkspace: vi.fn(),
2458
+ };
2459
+
2460
+ const services: TeamServices = {
2461
+ agentManager,
2462
+ messageRouter,
2463
+ eventStore,
2464
+ workspaceManager: mockWorkspaceManager as any,
2465
+ };
2466
+
2467
+ const runtime = new TeamRuntime(template, services);
2468
+ await runtime.initialize();
2469
+ await runtime.bootstrap();
2470
+
2471
+ const interceptor = runtime.createSpawnInterceptor();
2472
+ const result = interceptor({
2473
+ task: "implement feature",
2474
+ role: "developer",
2475
+ parent: "agent_0",
2476
+ streamId: "custom-stream",
2477
+ dataplaneTaskId: "custom-task",
2478
+ });
2479
+
2480
+ // Explicit values should NOT be overwritten
2481
+ expect(result.streamId).toBe("custom-stream");
2482
+ expect(result.dataplaneTaskId).toBe("custom-task");
2483
+
2484
+ await runtime.teardown();
2485
+ });
2486
+
2487
+ it("merge queue mr:submitted event wakes integrator agent", async () => {
2488
+ const template = createWorkspaceTemplate();
2489
+ let mergeQueueCallback: ((event: any) => void) | null = null;
2490
+ const mockMergeQueue = {
2491
+ onEvent: vi.fn((cb: (event: any) => void) => {
2492
+ mergeQueueCallback = cb;
2493
+ return () => { mergeQueueCallback = null; };
2494
+ }),
2495
+ };
2496
+ const mockWorkspaceManager = {
2497
+ createIntegrationStream: vi.fn().mockReturnValue("stream-1"),
2498
+ getMergeQueue: vi.fn().mockReturnValue(mockMergeQueue),
2499
+ getWorkspace: vi.fn(),
2500
+ };
2501
+
2502
+ const services: TeamServices = {
2503
+ agentManager,
2504
+ messageRouter,
2505
+ eventStore,
2506
+ workspaceManager: mockWorkspaceManager as any,
2507
+ };
2508
+
2509
+ const runtime = new TeamRuntime(template, services);
2510
+ await runtime.initialize();
2511
+ await runtime.bootstrap();
2512
+
2513
+ // Verify merge queue subscription was set up
2514
+ expect(mockMergeQueue.onEvent).toHaveBeenCalled();
2515
+ expect(mergeQueueCallback).not.toBeNull();
2516
+
2517
+ // Simulate a merge request submission
2518
+ mergeQueueCallback!({
2519
+ type: "mr:submitted",
2520
+ data: {
2521
+ mrId: "mr-123",
2522
+ workerAgentId: "worker-1",
2523
+ workerBranch: "worker/dev-1/task-1@123",
2524
+ },
2525
+ });
2526
+
2527
+ // Should have prompted the merger agent (companion, agent_1)
2528
+ expect(agentManager.prompt).toHaveBeenCalledWith(
2529
+ "agent_1", // merger is the companion agent
2530
+ expect.stringContaining("mr-123"),
2531
+ );
2532
+
2533
+ await runtime.teardown();
2534
+ });
2535
+
2536
+ it("creates integration stream during bootstrap when workspaceManager is available", async () => {
2537
+ const template = createWorkspaceTemplate();
2538
+ const mockWorkspaceManager = {
2539
+ createIntegrationStream: vi.fn().mockReturnValue("stream-42"),
2540
+ getMergeQueue: vi.fn().mockReturnValue(null),
2541
+ getWorkspace: vi.fn(),
2542
+ };
2543
+
2544
+ const services: TeamServices = {
2545
+ agentManager,
2546
+ messageRouter,
2547
+ eventStore,
2548
+ workspaceManager: mockWorkspaceManager as any,
2549
+ };
2550
+
2551
+ const runtime = new TeamRuntime(template, services);
2552
+ await runtime.initialize();
2553
+ await runtime.bootstrap();
2554
+
2555
+ expect(mockWorkspaceManager.createIntegrationStream).toHaveBeenCalledWith(
2556
+ "agent_0", // root agent ID
2557
+ { name: "workspace-test", forkFrom: "main" },
2558
+ );
2559
+ expect(runtime.getTeamStreamId()).toBe("stream-42");
2560
+
2561
+ await runtime.teardown();
2562
+ });
2563
+ });