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
@@ -0,0 +1,814 @@
1
+ /**
2
+ * TeamManager Tests
3
+ *
4
+ * Tests the central team instance lifecycle manager: starting/stopping teams,
5
+ * composite dispatch of interceptors/filters/validators, and agent-to-team mapping.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach } from "vitest";
9
+ import * as path from "path";
10
+ import { TeamManager } from "../team-manager.js";
11
+ import type { TeamServices } from "../team-runtime.js";
12
+ import { DefaultRoleRegistry } from "../../roles/registry.js";
13
+ import type { AgentManager, SpawnInterceptor } from "../../agent/agent-manager.js";
14
+ import type { MessageRouter } from "../../router/message-router.js";
15
+ import type { EventStore } from "../../store/event-store.js";
16
+ import type { SpawnAgentOptions } from "../../agent/types.js";
17
+ import type { AgentId, Event } from "../../store/types/index.js";
18
+
19
+ // =============================================================================
20
+ // Helpers
21
+ // =============================================================================
22
+
23
+ const PROJECT_ROOT = path.resolve(import.meta.dirname, "../../..");
24
+
25
+ let spawnCounter = 0;
26
+ let capturedInterceptor: SpawnInterceptor | null = null;
27
+ let interceptedSpawnOptions: SpawnAgentOptions[] = [];
28
+ let lifecycleCallbacks: Array<(event: any) => void> = [];
29
+
30
+ function createMockEventStore(): EventStore {
31
+ const events: Event[] = [];
32
+ return {
33
+ emit: vi.fn((input: Record<string, unknown>) => {
34
+ const event = {
35
+ id: `evt_${events.length}`,
36
+ type: input.type,
37
+ timestamp: Date.now(),
38
+ source: input.source,
39
+ target: input.target,
40
+ payload: input.payload,
41
+ } as unknown as Event;
42
+ events.push(event);
43
+ return event;
44
+ }),
45
+ persist: vi.fn().mockResolvedValue(undefined),
46
+ close: vi.fn().mockResolvedValue(undefined),
47
+ query: vi.fn().mockReturnValue([]),
48
+ getAgent: vi.fn().mockReturnValue(null),
49
+ getTask: vi.fn().mockReturnValue(null),
50
+ listAgents: vi.fn().mockReturnValue([]),
51
+ onAgentChange: vi.fn(),
52
+ onTaskChange: vi.fn(),
53
+ updateAgentMetadata: vi.fn(),
54
+ instanceId: "test-instance",
55
+ _events: events,
56
+ } as unknown as EventStore & { _events: Event[] };
57
+ }
58
+
59
+ function createMockMessageRouter(): MessageRouter {
60
+ return {
61
+ sendToAddress: vi.fn().mockResolvedValue({ delivered: true }),
62
+ emitStatus: vi.fn(),
63
+ getMessages: vi.fn().mockReturnValue([]),
64
+ subscribe: vi.fn(),
65
+ unsubscribe: vi.fn(),
66
+ getSubscriptions: vi.fn().mockReturnValue([]),
67
+ setupDefaultSubscriptions: vi.fn(),
68
+ setSignalFilter: vi.fn(),
69
+ setEmissionValidator: vi.fn(),
70
+ } as unknown as MessageRouter;
71
+ }
72
+
73
+ function createMockAgentManager(roleRegistry: DefaultRoleRegistry): AgentManager {
74
+ capturedInterceptor = null;
75
+ spawnCounter = 0;
76
+ interceptedSpawnOptions = [];
77
+ lifecycleCallbacks = [];
78
+
79
+ return {
80
+ spawn: vi.fn(async (options: SpawnAgentOptions) => {
81
+ const opts = capturedInterceptor ? await capturedInterceptor(options) : options;
82
+ interceptedSpawnOptions.push(opts);
83
+ const id = `agent_${spawnCounter++}`;
84
+ return {
85
+ id,
86
+ session_id: `session_${id}`,
87
+ task: opts.task ?? "test",
88
+ state: "running" as const,
89
+ created_at: Date.now(),
90
+ parent: opts.parent ?? null,
91
+ role: opts.role,
92
+ config: opts.config,
93
+ };
94
+ }),
95
+ terminate: vi.fn().mockResolvedValue(undefined),
96
+ get: vi.fn().mockReturnValue(null),
97
+ list: vi.fn().mockReturnValue([]),
98
+ getChildren: vi.fn().mockReturnValue([]),
99
+ getHierarchy: vi.fn().mockReturnValue(null),
100
+ getSession: vi.fn().mockReturnValue(null),
101
+ hasActiveSession: vi.fn().mockReturnValue(false),
102
+ setSpawnInterceptor: vi.fn((interceptor: SpawnInterceptor | null) => {
103
+ capturedInterceptor = interceptor;
104
+ }),
105
+ getRoleRegistry: vi.fn(() => roleRegistry),
106
+ onLifecycleEvent: vi.fn((callback: (event: any) => void) => {
107
+ lifecycleCallbacks.push(callback);
108
+ return vi.fn(); // unsubscribe
109
+ }),
110
+ continueAgent: vi.fn().mockResolvedValue({ id: "continued_0" }),
111
+ close: vi.fn().mockResolvedValue(undefined),
112
+ getOrCreateHeadManager: vi.fn(),
113
+ prompt: vi.fn(),
114
+ isPrompting: vi.fn().mockReturnValue(false),
115
+ } as unknown as AgentManager;
116
+ }
117
+
118
+ // =============================================================================
119
+ // Tests
120
+ // =============================================================================
121
+
122
+ describe("TeamManager", () => {
123
+ let roleRegistry: DefaultRoleRegistry;
124
+ let agentManager: AgentManager;
125
+ let messageRouter: MessageRouter;
126
+ let eventStore: EventStore;
127
+ let services: TeamServices;
128
+
129
+ beforeEach(() => {
130
+ roleRegistry = new DefaultRoleRegistry();
131
+ eventStore = createMockEventStore();
132
+ messageRouter = createMockMessageRouter();
133
+ agentManager = createMockAgentManager(roleRegistry);
134
+ services = { agentManager, messageRouter, eventStore };
135
+ });
136
+
137
+ describe("startTeam()", () => {
138
+ it("loads template, creates runtime, initializes and bootstraps", async () => {
139
+ const manager = new TeamManager(services);
140
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
141
+
142
+ expect(instance.id).toBe("self-driving-1");
143
+ expect(instance.templateName).toBe("self-driving");
144
+ expect(instance.result.rootId).toBeDefined();
145
+ expect(instance.result.companionIds).toHaveLength(1);
146
+
147
+ // Root (planner) + companion (judge) spawned
148
+ expect(agentManager.spawn).toHaveBeenCalledTimes(2);
149
+ });
150
+
151
+ it("emits team_config event during initialization", async () => {
152
+ const manager = new TeamManager(services);
153
+ await manager.startTeam("self-driving", PROJECT_ROOT);
154
+
155
+ expect(eventStore.emit).toHaveBeenCalledWith(
156
+ expect.objectContaining({
157
+ type: "status",
158
+ payload: expect.objectContaining({
159
+ team_config: expect.objectContaining({
160
+ teamName: "self-driving",
161
+ }),
162
+ }),
163
+ })
164
+ );
165
+ });
166
+
167
+ it("allows multiple concurrent teams", async () => {
168
+ const manager = new TeamManager(services);
169
+ const first = await manager.startTeam("self-driving", PROJECT_ROOT);
170
+ const second = await manager.startTeam("structured", PROJECT_ROOT);
171
+
172
+ expect(manager.getInstances()).toHaveLength(2);
173
+ expect(manager.getInstance(first.id)).toBe(first);
174
+ expect(manager.getInstance(second.id)).toBe(second);
175
+ });
176
+
177
+ it("generates sequential instance IDs", async () => {
178
+ const manager = new TeamManager(services);
179
+ const first = await manager.startTeam("self-driving", PROJECT_ROOT);
180
+ expect(first.id).toBe("self-driving-1");
181
+
182
+ const second = await manager.startTeam("structured", PROJECT_ROOT);
183
+ expect(second.id).toBe("structured-2");
184
+ });
185
+
186
+ it("maps bootstrap agents to the team instance", async () => {
187
+ const manager = new TeamManager(services);
188
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
189
+
190
+ const rootTeam = manager.getTeamForAgent(instance.result.rootId);
191
+ expect(rootTeam).toBe(instance);
192
+
193
+ const companionTeam = manager.getTeamForAgent(instance.result.companionIds[0]);
194
+ expect(companionTeam).toBe(instance);
195
+ });
196
+
197
+ it("tags bootstrap agents with team_instance in EventStore", async () => {
198
+ const manager = new TeamManager(services);
199
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
200
+
201
+ // Root + companion agents should be tagged
202
+ const allAgentIds = [instance.result.rootId, ...instance.result.companionIds];
203
+ for (const agentId of allAgentIds) {
204
+ expect(eventStore.updateAgentMetadata).toHaveBeenCalledWith(
205
+ agentId,
206
+ { team_instance: instance.id },
207
+ );
208
+ }
209
+ });
210
+ });
211
+
212
+ describe("stopTeam()", () => {
213
+ it("tears down the runtime and removes from map", async () => {
214
+ const manager = new TeamManager(services);
215
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
216
+
217
+ await manager.stopTeam(instance.id);
218
+
219
+ expect(manager.hasActiveTeam()).toBe(false);
220
+ expect(manager.getInstance(instance.id)).toBeUndefined();
221
+ });
222
+
223
+ it("clears agent-to-team mapping for stopped team", async () => {
224
+ const manager = new TeamManager(services);
225
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
226
+ const rootId = instance.result.rootId;
227
+
228
+ await manager.stopTeam(instance.id);
229
+
230
+ expect(manager.getTeamForAgent(rootId)).toBeUndefined();
231
+ });
232
+
233
+ it("throws for non-existent instance ID", async () => {
234
+ const manager = new TeamManager(services);
235
+
236
+ await expect(
237
+ manager.stopTeam("nonexistent-1")
238
+ ).rejects.toThrow(/No team instance 'nonexistent-1' found/);
239
+ });
240
+
241
+ it("allows starting a new team after stopping", async () => {
242
+ const manager = new TeamManager(services);
243
+ const first = await manager.startTeam("self-driving", PROJECT_ROOT);
244
+ await manager.stopTeam(first.id);
245
+
246
+ // Should not throw
247
+ const second = await manager.startTeam("structured", PROJECT_ROOT);
248
+ expect(second.templateName).toBe("structured");
249
+ });
250
+ });
251
+
252
+ describe("teardownAll()", () => {
253
+ it("stops all running instances", async () => {
254
+ const manager = new TeamManager(services);
255
+ await manager.startTeam("self-driving", PROJECT_ROOT);
256
+
257
+ await manager.teardownAll();
258
+
259
+ expect(manager.hasActiveTeam()).toBe(false);
260
+ expect(manager.getInstances()).toEqual([]);
261
+ });
262
+
263
+ it("is safe to call with no running instances", async () => {
264
+ const manager = new TeamManager(services);
265
+ await manager.teardownAll(); // Should not throw
266
+ });
267
+ });
268
+
269
+ describe("getters", () => {
270
+ it("getInstance() returns instance by ID", async () => {
271
+ const manager = new TeamManager(services);
272
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
273
+
274
+ expect(manager.getInstance(instance.id)).toBe(instance);
275
+ expect(manager.getInstance("nonexistent")).toBeUndefined();
276
+ });
277
+
278
+ it("getInstances() returns all active instances", async () => {
279
+ const manager = new TeamManager(services);
280
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
281
+
282
+ const instances = manager.getInstances();
283
+ expect(instances).toHaveLength(1);
284
+ expect(instances[0]).toBe(instance);
285
+ });
286
+
287
+ it("hasActiveTeam() reflects state correctly", async () => {
288
+ const manager = new TeamManager(services);
289
+ expect(manager.hasActiveTeam()).toBe(false);
290
+
291
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
292
+ expect(manager.hasActiveTeam()).toBe(true);
293
+
294
+ await manager.stopTeam(instance.id);
295
+ expect(manager.hasActiveTeam()).toBe(false);
296
+ });
297
+ });
298
+
299
+ describe("install() — composite interceptor", () => {
300
+ it("installs composite spawn interceptor on agent manager", async () => {
301
+ const manager = new TeamManager(services);
302
+ manager.install();
303
+
304
+ expect(agentManager.setSpawnInterceptor).toHaveBeenCalledWith(
305
+ expect.any(Function)
306
+ );
307
+ });
308
+
309
+ it("interceptor passes through agents with no parent", async () => {
310
+ const manager = new TeamManager(services);
311
+ await manager.startTeam("self-driving", PROJECT_ROOT);
312
+ manager.install();
313
+
314
+ // Spawn with no parent — should pass through unchanged
315
+ const original: SpawnAgentOptions = {
316
+ task: "standalone task",
317
+ role: "grinder",
318
+ parent: null,
319
+ };
320
+
321
+ await agentManager.spawn(original);
322
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
323
+
324
+ // No team context injected (parent is null)
325
+ expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBeUndefined();
326
+ });
327
+
328
+ it("interceptor injects team context for child agents", async () => {
329
+ const manager = new TeamManager(services);
330
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
331
+ manager.install();
332
+
333
+ // Spawn a grinder as child of root (planner)
334
+ await agentManager.spawn({
335
+ task: "grinder task",
336
+ role: "grinder",
337
+ parent: instance.result.rootId,
338
+ });
339
+
340
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
341
+ expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBe("self-driving");
342
+ expect(lastOpts.config?.env?.MACRO_TASK_MODE).toBe("pull");
343
+ expect(lastOpts.topics).toContain("work_coordination");
344
+ });
345
+
346
+ it("interceptor sets team_instance on child agents", async () => {
347
+ const manager = new TeamManager(services);
348
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
349
+ manager.install();
350
+
351
+ await agentManager.spawn({
352
+ task: "grinder task",
353
+ role: "grinder",
354
+ parent: instance.result.rootId,
355
+ });
356
+
357
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
358
+ expect(lastOpts.team_instance).toBe(instance.id);
359
+ });
360
+
361
+ it("interceptor passes through agents with non-team parent", async () => {
362
+ const manager = new TeamManager(services);
363
+ await manager.startTeam("self-driving", PROJECT_ROOT);
364
+ manager.install();
365
+
366
+ // Spawn with a parent that's not in the team
367
+ await agentManager.spawn({
368
+ task: "orphan task",
369
+ role: "worker",
370
+ parent: "non_team_parent",
371
+ });
372
+
373
+ const lastOpts = interceptedSpawnOptions.at(-1)!;
374
+ expect(lastOpts.config?.env?.MACRO_TEAM_NAME).toBeUndefined();
375
+ });
376
+ });
377
+
378
+ describe("install() — composite signal filter", () => {
379
+ it("installs signal filter on message router", async () => {
380
+ const manager = new TeamManager(services);
381
+ manager.install();
382
+
383
+ expect(messageRouter.setSignalFilter).toHaveBeenCalledWith(
384
+ expect.any(Function)
385
+ );
386
+ });
387
+
388
+ it("delegates filtering to correct team instance", async () => {
389
+ const manager = new TeamManager(services);
390
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
391
+ manager.install();
392
+
393
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
394
+ from: string, to: string, signal: string | undefined
395
+ ) => boolean;
396
+
397
+ const rootId = instance.result.rootId;
398
+ const companionId = instance.result.companionIds[0];
399
+
400
+ // judge→planner has peer filter: [FIXUP_CREATED, GREEN_SNAPSHOT]
401
+ expect(filterFn(companionId, rootId, "FIXUP_CREATED")).toBe(true);
402
+ expect(filterFn(companionId, rootId, "WORKER_DONE")).toBe(false);
403
+ });
404
+
405
+ it("allows all signals for non-team agents", async () => {
406
+ const manager = new TeamManager(services);
407
+ await manager.startTeam("self-driving", PROJECT_ROOT);
408
+ manager.install();
409
+
410
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
411
+ from: string, to: string, signal: string | undefined
412
+ ) => boolean;
413
+
414
+ // Non-team agents — should pass through
415
+ expect(filterFn("unknown_from", "unknown_to", "ANY_SIGNAL")).toBe(true);
416
+ });
417
+
418
+ it("applies recipient's team filter for cross-team messages", async () => {
419
+ const manager = new TeamManager(services);
420
+ const teamA = await manager.startTeam("self-driving", PROJECT_ROOT);
421
+ const teamB = await manager.startTeam("structured", PROJECT_ROOT);
422
+ manager.install();
423
+
424
+ // Register a grinder in team A and a developer in team B
425
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
426
+ spawnCallback({
427
+ type: "spawned",
428
+ agent: { id: "grinder_1" as AgentId, parent: teamA.result.rootId as AgentId, role: "grinder", state: "running" },
429
+ });
430
+ spawnCallback({
431
+ type: "spawned",
432
+ agent: { id: "dev_1" as AgentId, parent: teamB.result.rootId as AgentId, role: "developer", state: "running" },
433
+ });
434
+
435
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
436
+ from: string, to: string, signal: string | undefined
437
+ ) => boolean;
438
+
439
+ // grinder (team A) → developer (team B): recipient's team filter applies
440
+ // developer's allowed signals from structured subscriptions: { TASK_ASSIGNED }
441
+ expect(filterFn("grinder_1", "dev_1", "TASK_ASSIGNED")).toBe(true);
442
+ expect(filterFn("grinder_1", "dev_1", "WORK_ASSIGNED")).toBe(false);
443
+ });
444
+
445
+ it("applies sender's team filter when recipient is non-team", async () => {
446
+ const manager = new TeamManager(services);
447
+ const teamA = await manager.startTeam("self-driving", PROJECT_ROOT);
448
+ manager.install();
449
+
450
+ // Register a grinder in team A
451
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
452
+ spawnCallback({
453
+ type: "spawned",
454
+ agent: { id: "grinder_1" as AgentId, parent: teamA.result.rootId as AgentId, role: "grinder", state: "running" },
455
+ });
456
+
457
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
458
+ from: string, to: string, signal: string | undefined
459
+ ) => boolean;
460
+
461
+ // grinder (team A) → non-team agent: sender's team filter is used
462
+ // Non-team recipient has no role in team A's agentRoleMap → no filter → allow
463
+ expect(filterFn("grinder_1", "outsider", "ANYTHING")).toBe(true);
464
+ expect(filterFn("grinder_1", "outsider", "WORK_ASSIGNED")).toBe(true);
465
+ });
466
+
467
+ it("applies recipient's team filter when sender is non-team", async () => {
468
+ const manager = new TeamManager(services);
469
+ const teamB = await manager.startTeam("structured", PROJECT_ROOT);
470
+ manager.install();
471
+
472
+ // Register a developer in team B
473
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
474
+ spawnCallback({
475
+ type: "spawned",
476
+ agent: { id: "dev_1" as AgentId, parent: teamB.result.rootId as AgentId, role: "developer", state: "running" },
477
+ });
478
+
479
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
480
+ from: string, to: string, signal: string | undefined
481
+ ) => boolean;
482
+
483
+ // non-team → developer (team B): recipient's team filter applies
484
+ // developer's allowed signals: { TASK_ASSIGNED }
485
+ expect(filterFn("outsider", "dev_1", "TASK_ASSIGNED")).toBe(true);
486
+ expect(filterFn("outsider", "dev_1", "WORK_ASSIGNED")).toBe(false);
487
+ });
488
+
489
+ it("recipient's team takes precedence over sender's team", async () => {
490
+ const manager = new TeamManager(services);
491
+ const teamA = await manager.startTeam("self-driving", PROJECT_ROOT);
492
+ const teamB = await manager.startTeam("structured", PROJECT_ROOT);
493
+ manager.install();
494
+
495
+ // Register restricted-filter agents in each team
496
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
497
+ spawnCallback({
498
+ type: "spawned",
499
+ agent: { id: "grinder_1" as AgentId, parent: teamA.result.rootId as AgentId, role: "grinder", state: "running" },
500
+ });
501
+ spawnCallback({
502
+ type: "spawned",
503
+ agent: { id: "dev_1" as AgentId, parent: teamB.result.rootId as AgentId, role: "developer", state: "running" },
504
+ });
505
+
506
+ const filterFn = vi.mocked(messageRouter.setSignalFilter).mock.calls.at(-1)![0] as (
507
+ from: string, to: string, signal: string | undefined
508
+ ) => boolean;
509
+
510
+ // grinder → developer: recipient (developer in structured) filter applies
511
+ // developer allows: { TASK_ASSIGNED }, grinder allows: { WORK_ASSIGNED }
512
+ expect(filterFn("grinder_1", "dev_1", "TASK_ASSIGNED")).toBe(true);
513
+ expect(filterFn("grinder_1", "dev_1", "WORK_ASSIGNED")).toBe(false);
514
+
515
+ // Reverse: developer → grinder: recipient (grinder in self-driving) filter applies
516
+ // grinder allows: { WORK_ASSIGNED }
517
+ expect(filterFn("dev_1", "grinder_1", "WORK_ASSIGNED")).toBe(true);
518
+ expect(filterFn("dev_1", "grinder_1", "TASK_ASSIGNED")).toBe(false);
519
+ });
520
+ });
521
+
522
+ describe("install() — composite emission validator", () => {
523
+ it("installs emission validator on message router", async () => {
524
+ const manager = new TeamManager(services);
525
+ manager.install();
526
+
527
+ expect(messageRouter.setEmissionValidator).toHaveBeenCalledWith(
528
+ expect.any(Function)
529
+ );
530
+ });
531
+
532
+ it("delegates validation to correct team instance", async () => {
533
+ const manager = new TeamManager(services);
534
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
535
+ manager.install();
536
+
537
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls.at(-1)![0] as (
538
+ agentId: string, signal: string | undefined
539
+ ) => { action: string; message?: string };
540
+
541
+ const rootId = instance.result.rootId;
542
+
543
+ // planner's allowed emissions: [TASK_CREATED, WORK_ASSIGNED]
544
+ expect(validatorFn(rootId, "TASK_CREATED").action).toBe("allow");
545
+ // Default enforcement is permissive — disallowed signals get "warn"
546
+ expect(validatorFn(rootId, "FORBIDDEN_SIGNAL").action).toBe("warn");
547
+ });
548
+
549
+ it("allows emissions for non-team agents", async () => {
550
+ const manager = new TeamManager(services);
551
+ await manager.startTeam("self-driving", PROJECT_ROOT);
552
+ manager.install();
553
+
554
+ const validatorFn = vi.mocked(messageRouter.setEmissionValidator).mock.calls.at(-1)![0] as (
555
+ agentId: string, signal: string | undefined
556
+ ) => { action: string; message?: string };
557
+
558
+ expect(validatorFn("unknown_agent", "ANYTHING").action).toBe("allow");
559
+ });
560
+ });
561
+
562
+ describe("install() — lifecycle listener", () => {
563
+ it("sets up lifecycle listener for spawn tracking", async () => {
564
+ const manager = new TeamManager(services);
565
+ manager.install();
566
+
567
+ expect(agentManager.onLifecycleEvent).toHaveBeenCalledWith(
568
+ expect.any(Function)
569
+ );
570
+ });
571
+
572
+ it("auto-registers child agents in parent's team", async () => {
573
+ const manager = new TeamManager(services);
574
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
575
+ manager.install();
576
+
577
+ // Simulate a grinder being spawned as child of root
578
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
579
+ spawnCallback({
580
+ type: "spawned",
581
+ agent: {
582
+ id: "grinder_1" as AgentId,
583
+ parent: instance.result.rootId as AgentId,
584
+ role: "grinder",
585
+ state: "running",
586
+ },
587
+ });
588
+
589
+ // grinder should be in the same team as root
590
+ const grinderTeam = manager.getTeamForAgent("grinder_1");
591
+ expect(grinderTeam).toBe(instance);
592
+
593
+ // Also registered in runtime's agent role map
594
+ expect(instance.runtime.hasAgent("grinder_1")).toBe(true);
595
+ });
596
+
597
+ it("ignores spawns with no parent", async () => {
598
+ const manager = new TeamManager(services);
599
+ await manager.startTeam("self-driving", PROJECT_ROOT);
600
+ manager.install();
601
+
602
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
603
+ spawnCallback({
604
+ type: "spawned",
605
+ agent: {
606
+ id: "orphan_1" as AgentId,
607
+ parent: null,
608
+ role: "worker",
609
+ state: "running",
610
+ },
611
+ });
612
+
613
+ expect(manager.getTeamForAgent("orphan_1")).toBeUndefined();
614
+ });
615
+
616
+ it("ignores spawns from non-team parents", async () => {
617
+ const manager = new TeamManager(services);
618
+ await manager.startTeam("self-driving", PROJECT_ROOT);
619
+ manager.install();
620
+
621
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
622
+ spawnCallback({
623
+ type: "spawned",
624
+ agent: {
625
+ id: "child_1" as AgentId,
626
+ parent: "non_team_parent" as AgentId,
627
+ role: "worker",
628
+ state: "running",
629
+ },
630
+ });
631
+
632
+ expect(manager.getTeamForAgent("child_1")).toBeUndefined();
633
+ });
634
+
635
+ it("ignores non-spawn lifecycle events", async () => {
636
+ const manager = new TeamManager(services);
637
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
638
+ manager.install();
639
+
640
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
641
+ spawnCallback({
642
+ type: "stopped",
643
+ agent: {
644
+ id: instance.result.rootId as AgentId,
645
+ parent: null,
646
+ role: "planner",
647
+ state: "stopped",
648
+ },
649
+ reason: "completed",
650
+ });
651
+
652
+ // Should not throw or change mappings
653
+ expect(manager.getTeamForAgent(instance.result.rootId)).toBe(instance);
654
+ });
655
+ });
656
+
657
+ describe("uninstall()", () => {
658
+ it("clears spawn interceptor from agent manager", async () => {
659
+ const manager = new TeamManager(services);
660
+ manager.install();
661
+ manager.uninstall();
662
+
663
+ expect(agentManager.setSpawnInterceptor).toHaveBeenLastCalledWith(null);
664
+ });
665
+
666
+ it("unsubscribes lifecycle listener", async () => {
667
+ const manager = new TeamManager(services);
668
+ manager.install();
669
+
670
+ const unsubscribeFn = vi.mocked(agentManager.onLifecycleEvent).mock.results[0].value;
671
+
672
+ manager.uninstall();
673
+
674
+ expect(unsubscribeFn).toHaveBeenCalled();
675
+ });
676
+ });
677
+
678
+ describe("structured team", () => {
679
+ it("starts structured team with push mode", async () => {
680
+ const manager = new TeamManager(services);
681
+ const instance = await manager.startTeam("structured", PROJECT_ROOT);
682
+
683
+ expect(instance.templateName).toBe("structured");
684
+ expect(instance.runtime.getTaskMode()).toBe("push");
685
+ expect(instance.runtime.getStrategyName()).toBe("queue");
686
+ });
687
+ });
688
+
689
+ describe("install() — spawn rules defense-in-depth", () => {
690
+ it("rejects spawn when child role is not in parent's spawn_rules", async () => {
691
+ const manager = new TeamManager(services);
692
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
693
+ manager.install();
694
+
695
+ // self-driving spawn_rules: planner: [grinder, planner], judge: [], grinder: []
696
+ // judge cannot spawn anything — attempt should throw
697
+ await expect(
698
+ agentManager.spawn({
699
+ task: "disallowed child",
700
+ role: "grinder",
701
+ parent: instance.result.companionIds[0], // judge
702
+ })
703
+ ).rejects.toThrow(/Spawn rules violation: role 'judge' cannot spawn 'grinder'/);
704
+ });
705
+
706
+ it("allows spawn when child role is in parent's spawn_rules", async () => {
707
+ const manager = new TeamManager(services);
708
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
709
+ manager.install();
710
+
711
+ // planner can spawn grinder — should succeed
712
+ await expect(
713
+ agentManager.spawn({
714
+ task: "allowed child",
715
+ role: "grinder",
716
+ parent: instance.result.rootId, // planner
717
+ })
718
+ ).resolves.toBeDefined();
719
+ });
720
+
721
+ it("passes through spawn when parent role has no spawn_rules entry", async () => {
722
+ const manager = new TeamManager(services);
723
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
724
+ manager.install();
725
+
726
+ // Simulate a dynamically spawned agent with a role not in spawn_rules
727
+ const spawnCallback = lifecycleCallbacks.at(-1)!;
728
+ spawnCallback({
729
+ type: "spawned",
730
+ agent: { id: "dynamic_1" as AgentId, parent: instance.result.rootId as AgentId, role: "custom_role", state: "running" },
731
+ });
732
+
733
+ // custom_role is not in spawn_rules — should pass through (no restriction)
734
+ await expect(
735
+ agentManager.spawn({
736
+ task: "dynamic child",
737
+ role: "some_child",
738
+ parent: "dynamic_1",
739
+ })
740
+ ).resolves.toBeDefined();
741
+ });
742
+
743
+ it("passes through spawn when no role is specified on child", async () => {
744
+ const manager = new TeamManager(services);
745
+ const instance = await manager.startTeam("self-driving", PROJECT_ROOT);
746
+ manager.install();
747
+
748
+ // No role specified — spawn rules check is skipped
749
+ await expect(
750
+ agentManager.spawn({
751
+ task: "roleless child",
752
+ parent: instance.result.rootId,
753
+ })
754
+ ).resolves.toBeDefined();
755
+ });
756
+ });
757
+
758
+ describe("role conflict detection", () => {
759
+ it("warns when two teams register the same role name with different capabilities", async () => {
760
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
761
+
762
+ const manager = new TeamManager(services);
763
+ // Both self-driving and structured inherit from built-in roles,
764
+ // but register custom role definitions. Starting the same template twice
765
+ // should not warn (same capabilities). Let's start two different templates
766
+ // that share no custom role names — no warning expected.
767
+ await manager.startTeam("self-driving", PROJECT_ROOT);
768
+ await manager.startTeam("structured", PROJECT_ROOT);
769
+
770
+ const conflictWarnings = warnSpy.mock.calls.filter(
771
+ call => typeof call[0] === "string" && call[0].includes("Role") && call[0].includes("conflict")
772
+ );
773
+ expect(conflictWarnings).toHaveLength(0);
774
+
775
+ warnSpy.mockRestore();
776
+ });
777
+
778
+ it("does not warn when same team is started twice (identical capabilities)", async () => {
779
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
780
+
781
+ const manager = new TeamManager(services);
782
+ await manager.startTeam("self-driving", PROJECT_ROOT);
783
+ await manager.startTeam("self-driving", PROJECT_ROOT);
784
+
785
+ const conflictWarnings = warnSpy.mock.calls.filter(
786
+ call => typeof call[0] === "string" && call[0].includes("conflict")
787
+ );
788
+ expect(conflictWarnings).toHaveLength(0);
789
+
790
+ warnSpy.mockRestore();
791
+ });
792
+
793
+ it("warns when a role is re-registered with different capabilities", async () => {
794
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
795
+
796
+ // Pre-register a role with specific capabilities
797
+ roleRegistry.registerRole({
798
+ name: "grinder",
799
+ capabilities: ["file.read", "file.write"],
800
+ });
801
+
802
+ const manager = new TeamManager(services);
803
+ // self-driving team registers grinder with its own capabilities
804
+ await manager.startTeam("self-driving", PROJECT_ROOT);
805
+
806
+ const conflictWarnings = warnSpy.mock.calls.filter(
807
+ call => typeof call[0] === "string" && call[0].includes("Role 'grinder' conflict")
808
+ );
809
+ expect(conflictWarnings).toHaveLength(1);
810
+
811
+ warnSpy.mockRestore();
812
+ });
813
+ });
814
+ });