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,1319 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+ import { TemplateLoader } from "./loader";
6
+
7
+ describe("TemplateLoader", () => {
8
+ let tmpDir: string;
9
+
10
+ beforeEach(() => {
11
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "openteams-test-"));
12
+ });
13
+
14
+ afterEach(() => {
15
+ fs.rmSync(tmpDir, { recursive: true, force: true });
16
+ });
17
+
18
+ function writeYaml(relPath: string, content: string): void {
19
+ const full = path.join(tmpDir, relPath);
20
+ fs.mkdirSync(path.dirname(full), { recursive: true });
21
+ fs.writeFileSync(full, content, "utf-8");
22
+ }
23
+
24
+ describe("load", () => {
25
+ it("loads a minimal valid template", () => {
26
+ writeYaml(
27
+ "team.yaml",
28
+ `
29
+ name: test-team
30
+ version: 1
31
+ roles:
32
+ - worker
33
+ topology:
34
+ root:
35
+ role: worker
36
+ `
37
+ );
38
+
39
+ const template = TemplateLoader.load(tmpDir);
40
+ expect(template.manifest.name).toBe("test-team");
41
+ expect(template.manifest.version).toBe(1);
42
+ expect(template.manifest.roles).toEqual(["worker"]);
43
+ expect(template.manifest.topology.root.role).toBe("worker");
44
+ expect(template.roles.size).toBe(1);
45
+ expect(template.roles.get("worker")!.name).toBe("worker");
46
+ });
47
+
48
+ it("loads a full self-driving template", () => {
49
+ writeYaml(
50
+ "team.yaml",
51
+ `
52
+ name: self-driving
53
+ description: "Autonomous codebase development"
54
+ version: 1
55
+ roles:
56
+ - planner
57
+ - grinder
58
+ - judge
59
+ topology:
60
+ root:
61
+ role: planner
62
+ prompt: prompts/planner.md
63
+ config:
64
+ model: sonnet
65
+ companions:
66
+ - role: judge
67
+ prompt: prompts/judge.md
68
+ config:
69
+ model: haiku
70
+ spawn_rules:
71
+ planner: [grinder, planner]
72
+ judge: []
73
+ grinder: []
74
+ communication:
75
+ channels:
76
+ task_updates:
77
+ description: "Task lifecycle events"
78
+ signals: [TASK_CREATED, TASK_COMPLETED, TASK_FAILED]
79
+ work_coordination:
80
+ description: "Work assignment"
81
+ signals: [WORK_ASSIGNED, WORKER_DONE]
82
+ subscriptions:
83
+ planner:
84
+ - channel: task_updates
85
+ - channel: work_coordination
86
+ signals: [WORKER_DONE]
87
+ judge:
88
+ - channel: task_updates
89
+ signals: [TASK_FAILED]
90
+ grinder:
91
+ - channel: work_coordination
92
+ signals: [WORK_ASSIGNED]
93
+ emissions:
94
+ planner: [TASK_CREATED, WORK_ASSIGNED]
95
+ judge: [FIXUP_CREATED]
96
+ grinder: [WORKER_DONE]
97
+ routing:
98
+ status: upstream
99
+ peers:
100
+ - from: judge
101
+ to: planner
102
+ via: direct
103
+ signals: [FIXUP_CREATED]
104
+ macro_agent:
105
+ task_assignment:
106
+ mode: pull
107
+ `
108
+ );
109
+
110
+ writeYaml("prompts/planner.md", "# Planner\nYou are the planner.");
111
+ writeYaml("prompts/judge.md", "# Judge\nYou evaluate quality.");
112
+
113
+ const template = TemplateLoader.load(tmpDir);
114
+ expect(template.manifest.name).toBe("self-driving");
115
+ expect(template.manifest.roles).toHaveLength(3);
116
+ expect(template.manifest.topology.companions).toHaveLength(1);
117
+ expect(template.manifest.topology.spawn_rules!.planner).toEqual([
118
+ "grinder",
119
+ "planner",
120
+ ]);
121
+ expect(template.manifest.communication!.channels!.task_updates.signals).toEqual([
122
+ "TASK_CREATED",
123
+ "TASK_COMPLETED",
124
+ "TASK_FAILED",
125
+ ]);
126
+ expect(template.prompts.get("planner")!.primary).toContain("You are the planner");
127
+ expect(template.prompts.get("judge")!.primary).toContain("evaluate quality");
128
+ expect(template.manifest.macro_agent).toEqual({
129
+ task_assignment: { mode: "pull" },
130
+ });
131
+ });
132
+
133
+ it("loads role definitions from roles/ directory", () => {
134
+ writeYaml(
135
+ "team.yaml",
136
+ `
137
+ name: with-roles
138
+ version: 1
139
+ roles:
140
+ - coder
141
+ topology:
142
+ root:
143
+ role: coder
144
+ `
145
+ );
146
+
147
+ writeYaml(
148
+ "roles/coder.yaml",
149
+ `
150
+ name: coder
151
+ extends: worker
152
+ display_name: "Code Writer"
153
+ description: "Writes and tests code"
154
+ capabilities:
155
+ add:
156
+ - file.write
157
+ - exec.test
158
+ remove:
159
+ - agent.spawn.worker
160
+ macro_agent:
161
+ workspace:
162
+ type: own
163
+ `
164
+ );
165
+
166
+ const template = TemplateLoader.load(tmpDir);
167
+ const coder = template.roles.get("coder")!;
168
+ expect(coder.displayName).toBe("Code Writer");
169
+ expect(coder.description).toBe("Writes and tests code");
170
+ expect(coder.extends).toBe("worker");
171
+ expect(coder.capabilities).toEqual(["file.write", "exec.test"]);
172
+ expect(coder.raw.macro_agent).toEqual({ workspace: { type: "own" } });
173
+ });
174
+
175
+ it("loads role with full capability list", () => {
176
+ writeYaml(
177
+ "team.yaml",
178
+ `
179
+ name: simple
180
+ version: 1
181
+ roles:
182
+ - runner
183
+ topology:
184
+ root:
185
+ role: runner
186
+ `
187
+ );
188
+
189
+ writeYaml(
190
+ "roles/runner.yaml",
191
+ `
192
+ name: runner
193
+ description: "Task runner"
194
+ capabilities:
195
+ - exec.build
196
+ - exec.test
197
+ - exec.lint
198
+ `
199
+ );
200
+
201
+ const template = TemplateLoader.load(tmpDir);
202
+ expect(template.roles.get("runner")!.capabilities).toEqual([
203
+ "exec.build",
204
+ "exec.test",
205
+ "exec.lint",
206
+ ]);
207
+ });
208
+
209
+ it("loads prompts by convention (prompts/<role>.md)", () => {
210
+ writeYaml(
211
+ "team.yaml",
212
+ `
213
+ name: conv
214
+ version: 1
215
+ roles:
216
+ - worker
217
+ topology:
218
+ root:
219
+ role: worker
220
+ `
221
+ );
222
+
223
+ writeYaml("prompts/worker.md", "# Worker\nDo work.");
224
+
225
+ const template = TemplateLoader.load(tmpDir);
226
+ expect(template.prompts.get("worker")!.primary).toContain("Do work");
227
+ expect(template.prompts.get("worker")!.additional).toEqual([]);
228
+ });
229
+
230
+ it("loads prompt directory with ROLE.md as primary", () => {
231
+ writeYaml(
232
+ "team.yaml",
233
+ `
234
+ name: dir-test
235
+ version: 1
236
+ roles:
237
+ - developer
238
+ topology:
239
+ root:
240
+ role: developer
241
+ `
242
+ );
243
+
244
+ writeYaml("prompts/developer/ROLE.md", "# Developer\nImplement features.");
245
+ writeYaml("prompts/developer/SOUL.md", "You are a pragmatic craftsman.");
246
+ writeYaml("prompts/developer/RULES.md", "Follow TDD. Write tests first.");
247
+
248
+ const template = TemplateLoader.load(tmpDir);
249
+ const prompts = template.prompts.get("developer")!;
250
+ expect(prompts.primary).toContain("Implement features");
251
+ expect(prompts.additional).toHaveLength(2);
252
+ // SOUL.md is always first among additional files
253
+ expect(prompts.additional[0].name).toBe("soul");
254
+ expect(prompts.additional[0].content).toContain("pragmatic craftsman");
255
+ expect(prompts.additional[1].name).toBe("RULES");
256
+ });
257
+
258
+ it("orders SOUL.md before other additional files", () => {
259
+ writeYaml(
260
+ "team.yaml",
261
+ `
262
+ name: soul-order-test
263
+ version: 1
264
+ roles:
265
+ - dev
266
+ topology:
267
+ root:
268
+ role: dev
269
+ `
270
+ );
271
+
272
+ writeYaml("prompts/dev/ROLE.md", "Build things.");
273
+ writeYaml("prompts/dev/SOUL.md", "You are creative.");
274
+ writeYaml("prompts/dev/aaa-first-alphabetically.md", "Coding standards.");
275
+
276
+ const template = TemplateLoader.load(tmpDir);
277
+ const prompts = template.prompts.get("dev")!;
278
+ expect(prompts.primary).toContain("Build things");
279
+ // SOUL.md should come first despite aaa sorting earlier alphabetically
280
+ expect(prompts.additional[0].name).toBe("soul");
281
+ expect(prompts.additional[1].name).toBe("aaa-first-alphabetically");
282
+ });
283
+
284
+ it("falls back to prompt.md when ROLE.md is absent", () => {
285
+ writeYaml(
286
+ "team.yaml",
287
+ `
288
+ name: fallback-test
289
+ version: 1
290
+ roles:
291
+ - tester
292
+ topology:
293
+ root:
294
+ role: tester
295
+ `
296
+ );
297
+
298
+ writeYaml("prompts/tester/prompt.md", "Run all tests.");
299
+ writeYaml("prompts/tester/SOUL.md", "Break things on purpose.");
300
+
301
+ const template = TemplateLoader.load(tmpDir);
302
+ const prompts = template.prompts.get("tester")!;
303
+ expect(prompts.primary).toContain("Run all tests");
304
+ expect(prompts.additional).toHaveLength(1);
305
+ expect(prompts.additional[0].name).toBe("soul");
306
+ });
307
+
308
+ it("uses first file alphabetically when neither ROLE.md nor prompt.md exist", () => {
309
+ writeYaml(
310
+ "team.yaml",
311
+ `
312
+ name: alpha-test
313
+ version: 1
314
+ roles:
315
+ - tester
316
+ topology:
317
+ root:
318
+ role: tester
319
+ `
320
+ );
321
+
322
+ writeYaml("prompts/tester/instructions.md", "Run all tests.");
323
+ writeYaml("prompts/tester/SOUL.md", "Break things on purpose.");
324
+
325
+ const template = TemplateLoader.load(tmpDir);
326
+ const prompts = template.prompts.get("tester")!;
327
+ // SOUL.md sorts before instructions.md (uppercase < lowercase in ASCII)
328
+ // so SOUL.md is picked as primary in the alphabetical fallback
329
+ expect(prompts.primary).toContain("Break things on purpose");
330
+ expect(prompts.additional).toHaveLength(1);
331
+ expect(prompts.additional[0].name).toBe("instructions");
332
+ });
333
+
334
+ it("respects explicit prompts ordering from role YAML", () => {
335
+ writeYaml(
336
+ "team.yaml",
337
+ `
338
+ name: ordered-test
339
+ version: 1
340
+ roles:
341
+ - coder
342
+ topology:
343
+ root:
344
+ role: coder
345
+ `
346
+ );
347
+
348
+ writeYaml(
349
+ "roles/coder.yaml",
350
+ `
351
+ name: coder
352
+ description: "A coder"
353
+ prompts:
354
+ - SOUL.md
355
+ - ROLE.md
356
+ - RULES.md
357
+ `
358
+ );
359
+
360
+ writeYaml("prompts/coder/ROLE.md", "Write code.");
361
+ writeYaml("prompts/coder/SOUL.md", "You are meticulous.");
362
+ writeYaml("prompts/coder/RULES.md", "Use TypeScript.");
363
+
364
+ const template = TemplateLoader.load(tmpDir);
365
+ const prompts = template.prompts.get("coder")!;
366
+ // SOUL.md is first in the list, so it becomes primary
367
+ expect(prompts.primary).toContain("meticulous");
368
+ expect(prompts.additional).toHaveLength(2);
369
+ expect(prompts.additional[0].name).toBe("ROLE");
370
+ expect(prompts.additional[1].name).toBe("RULES");
371
+ });
372
+
373
+ it("prefers prompt directory over single file", () => {
374
+ writeYaml(
375
+ "team.yaml",
376
+ `
377
+ name: priority-test
378
+ version: 1
379
+ roles:
380
+ - worker
381
+ topology:
382
+ root:
383
+ role: worker
384
+ `
385
+ );
386
+
387
+ // Both exist — directory should win
388
+ writeYaml("prompts/worker.md", "Single file prompt.");
389
+ writeYaml("prompts/worker/ROLE.md", "Directory prompt.");
390
+ writeYaml("prompts/worker/SOUL.md", "Directory soul.");
391
+
392
+ const template = TemplateLoader.load(tmpDir);
393
+ const prompts = template.prompts.get("worker")!;
394
+ expect(prompts.primary).toContain("Directory prompt");
395
+ expect(prompts.additional).toHaveLength(1);
396
+ expect(prompts.additional[0].name).toBe("soul");
397
+ });
398
+ });
399
+
400
+ describe("validation", () => {
401
+ it("throws when team.yaml is missing", () => {
402
+ expect(() => TemplateLoader.load(tmpDir)).toThrow("team.yaml not found");
403
+ });
404
+
405
+ it("throws when name is missing", () => {
406
+ writeYaml(
407
+ "team.yaml",
408
+ `
409
+ version: 1
410
+ roles:
411
+ - worker
412
+ topology:
413
+ root:
414
+ role: worker
415
+ `
416
+ );
417
+
418
+ expect(() => TemplateLoader.load(tmpDir)).toThrow("missing required field: name");
419
+ });
420
+
421
+ it("throws when roles list is empty", () => {
422
+ writeYaml(
423
+ "team.yaml",
424
+ `
425
+ name: bad
426
+ version: 1
427
+ roles: []
428
+ topology:
429
+ root:
430
+ role: worker
431
+ `
432
+ );
433
+
434
+ expect(() => TemplateLoader.load(tmpDir)).toThrow("at least one role");
435
+ });
436
+
437
+ it("throws when topology.root.role is not in roles", () => {
438
+ writeYaml(
439
+ "team.yaml",
440
+ `
441
+ name: bad
442
+ version: 1
443
+ roles:
444
+ - worker
445
+ topology:
446
+ root:
447
+ role: unknown
448
+ `
449
+ );
450
+
451
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
452
+ 'topology.root.role "unknown" is not in the roles list'
453
+ );
454
+ });
455
+
456
+ it("throws when companion role is not in roles", () => {
457
+ writeYaml(
458
+ "team.yaml",
459
+ `
460
+ name: bad
461
+ version: 1
462
+ roles:
463
+ - worker
464
+ topology:
465
+ root:
466
+ role: worker
467
+ companions:
468
+ - role: ghost
469
+ `
470
+ );
471
+
472
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
473
+ 'topology.companions role "ghost" is not in the roles list'
474
+ );
475
+ });
476
+
477
+ it("throws when spawn_rules reference unknown role", () => {
478
+ writeYaml(
479
+ "team.yaml",
480
+ `
481
+ name: bad
482
+ version: 1
483
+ roles:
484
+ - worker
485
+ topology:
486
+ root:
487
+ role: worker
488
+ spawn_rules:
489
+ worker: [ghost]
490
+ `
491
+ );
492
+
493
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
494
+ 'spawn_rules "worker" references unknown role "ghost"'
495
+ );
496
+ });
497
+
498
+ it("throws when subscription references unknown channel", () => {
499
+ writeYaml(
500
+ "team.yaml",
501
+ `
502
+ name: bad
503
+ version: 1
504
+ roles:
505
+ - worker
506
+ topology:
507
+ root:
508
+ role: worker
509
+ communication:
510
+ channels: {}
511
+ subscriptions:
512
+ worker:
513
+ - channel: nonexistent
514
+ `
515
+ );
516
+
517
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
518
+ 'references unknown channel "nonexistent"'
519
+ );
520
+ });
521
+
522
+ it("throws when subscription role is not in roles", () => {
523
+ writeYaml(
524
+ "team.yaml",
525
+ `
526
+ name: bad
527
+ version: 1
528
+ roles:
529
+ - worker
530
+ topology:
531
+ root:
532
+ role: worker
533
+ communication:
534
+ channels:
535
+ ch1:
536
+ signals: [SIG]
537
+ subscriptions:
538
+ ghost:
539
+ - channel: ch1
540
+ `
541
+ );
542
+
543
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
544
+ 'subscriptions key "ghost" is not in the roles list'
545
+ );
546
+ });
547
+
548
+ it("throws when peer route references unknown role", () => {
549
+ writeYaml(
550
+ "team.yaml",
551
+ `
552
+ name: bad
553
+ version: 1
554
+ roles:
555
+ - worker
556
+ topology:
557
+ root:
558
+ role: worker
559
+ communication:
560
+ routing:
561
+ peers:
562
+ - from: worker
563
+ to: ghost
564
+ via: direct
565
+ `
566
+ );
567
+
568
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
569
+ 'routing.peers.to "ghost" is not in the roles list'
570
+ );
571
+ });
572
+ });
573
+
574
+ describe("role inheritance", () => {
575
+ it("resolves single-level inheritance with add/remove", () => {
576
+ writeYaml(
577
+ "team.yaml",
578
+ `
579
+ name: inherit-test
580
+ version: 1
581
+ roles:
582
+ - senior
583
+ - junior
584
+ topology:
585
+ root:
586
+ role: senior
587
+ `
588
+ );
589
+
590
+ writeYaml(
591
+ "roles/senior.yaml",
592
+ `
593
+ name: senior
594
+ capabilities:
595
+ - code
596
+ - review
597
+ - deploy
598
+ `
599
+ );
600
+
601
+ writeYaml(
602
+ "roles/junior.yaml",
603
+ `
604
+ name: junior
605
+ extends: senior
606
+ capabilities:
607
+ add:
608
+ - code
609
+ - debug
610
+ remove:
611
+ - deploy
612
+ `
613
+ );
614
+
615
+ const template = TemplateLoader.load(tmpDir);
616
+ const junior = template.roles.get("junior")!;
617
+ expect(junior.capabilities.sort()).toEqual(["code", "debug", "review"]);
618
+ });
619
+
620
+ it("resolves multi-level inheritance (A extends B extends C)", () => {
621
+ writeYaml(
622
+ "team.yaml",
623
+ `
624
+ name: multi-inherit
625
+ version: 1
626
+ roles:
627
+ - base
628
+ - mid
629
+ - leaf
630
+ topology:
631
+ root:
632
+ role: base
633
+ `
634
+ );
635
+
636
+ writeYaml(
637
+ "roles/base.yaml",
638
+ `
639
+ name: base
640
+ capabilities:
641
+ - read
642
+ - write
643
+ - admin
644
+ `
645
+ );
646
+
647
+ writeYaml(
648
+ "roles/mid.yaml",
649
+ `
650
+ name: mid
651
+ extends: base
652
+ capabilities:
653
+ add:
654
+ - build
655
+ remove:
656
+ - admin
657
+ `
658
+ );
659
+
660
+ writeYaml(
661
+ "roles/leaf.yaml",
662
+ `
663
+ name: leaf
664
+ extends: mid
665
+ capabilities:
666
+ add:
667
+ - deploy
668
+ remove:
669
+ - write
670
+ `
671
+ );
672
+
673
+ const template = TemplateLoader.load(tmpDir);
674
+ // base: [read, write, admin]
675
+ // mid: base + build - admin = [read, write, build]
676
+ // leaf: mid + deploy - write = [read, build, deploy]
677
+ expect(template.roles.get("mid")!.capabilities.sort()).toEqual(
678
+ ["build", "read", "write"]
679
+ );
680
+ expect(template.roles.get("leaf")!.capabilities.sort()).toEqual(
681
+ ["build", "deploy", "read"]
682
+ );
683
+ });
684
+
685
+ it("keeps explicit capability array as override (no merge)", () => {
686
+ writeYaml(
687
+ "team.yaml",
688
+ `
689
+ name: override-test
690
+ version: 1
691
+ roles:
692
+ - parent
693
+ - child
694
+ topology:
695
+ root:
696
+ role: parent
697
+ `
698
+ );
699
+
700
+ writeYaml(
701
+ "roles/parent.yaml",
702
+ `
703
+ name: parent
704
+ capabilities:
705
+ - a
706
+ - b
707
+ - c
708
+ `
709
+ );
710
+
711
+ writeYaml(
712
+ "roles/child.yaml",
713
+ `
714
+ name: child
715
+ extends: parent
716
+ capabilities:
717
+ - x
718
+ - y
719
+ `
720
+ );
721
+
722
+ const template = TemplateLoader.load(tmpDir);
723
+ // Plain array = explicit override, not merged with parent
724
+ expect(template.roles.get("child")!.capabilities).toEqual(["x", "y"]);
725
+ });
726
+
727
+ it("detects circular inheritance", () => {
728
+ writeYaml(
729
+ "team.yaml",
730
+ `
731
+ name: cycle-test
732
+ version: 1
733
+ roles:
734
+ - alpha
735
+ - beta
736
+ topology:
737
+ root:
738
+ role: alpha
739
+ `
740
+ );
741
+
742
+ writeYaml(
743
+ "roles/alpha.yaml",
744
+ `
745
+ name: alpha
746
+ extends: beta
747
+ capabilities:
748
+ add:
749
+ - a
750
+ `
751
+ );
752
+
753
+ writeYaml(
754
+ "roles/beta.yaml",
755
+ `
756
+ name: beta
757
+ extends: alpha
758
+ capabilities:
759
+ add:
760
+ - b
761
+ `
762
+ );
763
+
764
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
765
+ "Circular role inheritance detected"
766
+ );
767
+ });
768
+
769
+ it("resolves flat capabilities_add/capabilities_remove syntax", () => {
770
+ writeYaml(
771
+ "team.yaml",
772
+ `
773
+ name: flat-syntax
774
+ version: 1
775
+ roles:
776
+ - base
777
+ - child
778
+ topology:
779
+ root:
780
+ role: base
781
+ `
782
+ );
783
+
784
+ writeYaml(
785
+ "roles/base.yaml",
786
+ `
787
+ name: base
788
+ capabilities:
789
+ - read
790
+ - write
791
+ - admin
792
+ `
793
+ );
794
+
795
+ writeYaml(
796
+ "roles/child.yaml",
797
+ `
798
+ name: child
799
+ extends: base
800
+ capabilities_add:
801
+ - debug
802
+ - test
803
+ capabilities_remove:
804
+ - admin
805
+ `
806
+ );
807
+
808
+ const template = TemplateLoader.load(tmpDir);
809
+ const child = template.roles.get("child")!;
810
+ expect(child.capabilities.sort()).toEqual(["debug", "read", "test", "write"]);
811
+ });
812
+
813
+ it("resolves flat capabilities_add only (no remove)", () => {
814
+ writeYaml(
815
+ "team.yaml",
816
+ `
817
+ name: add-only
818
+ version: 1
819
+ roles:
820
+ - parent
821
+ - child
822
+ topology:
823
+ root:
824
+ role: parent
825
+ `
826
+ );
827
+
828
+ writeYaml(
829
+ "roles/parent.yaml",
830
+ `
831
+ name: parent
832
+ capabilities:
833
+ - a
834
+ - b
835
+ `
836
+ );
837
+
838
+ writeYaml(
839
+ "roles/child.yaml",
840
+ `
841
+ name: child
842
+ extends: parent
843
+ capabilities_add:
844
+ - c
845
+ `
846
+ );
847
+
848
+ const template = TemplateLoader.load(tmpDir);
849
+ expect(template.roles.get("child")!.capabilities.sort()).toEqual(["a", "b", "c"]);
850
+ });
851
+
852
+ it("resolves flat capabilities_remove only (no add)", () => {
853
+ writeYaml(
854
+ "team.yaml",
855
+ `
856
+ name: remove-only
857
+ version: 1
858
+ roles:
859
+ - parent
860
+ - child
861
+ topology:
862
+ root:
863
+ role: parent
864
+ `
865
+ );
866
+
867
+ writeYaml(
868
+ "roles/parent.yaml",
869
+ `
870
+ name: parent
871
+ capabilities:
872
+ - a
873
+ - b
874
+ - c
875
+ `
876
+ );
877
+
878
+ writeYaml(
879
+ "roles/child.yaml",
880
+ `
881
+ name: child
882
+ extends: parent
883
+ capabilities_remove:
884
+ - c
885
+ `
886
+ );
887
+
888
+ const template = TemplateLoader.load(tmpDir);
889
+ expect(template.roles.get("child")!.capabilities.sort()).toEqual(["a", "b"]);
890
+ });
891
+
892
+ it("errors when both CapabilityComposition and flat fields are used", () => {
893
+ writeYaml(
894
+ "team.yaml",
895
+ `
896
+ name: conflict
897
+ version: 1
898
+ roles:
899
+ - bad
900
+ topology:
901
+ root:
902
+ role: bad
903
+ `
904
+ );
905
+
906
+ writeYaml(
907
+ "roles/bad.yaml",
908
+ `
909
+ name: bad
910
+ capabilities:
911
+ add:
912
+ - x
913
+ remove:
914
+ - y
915
+ capabilities_add:
916
+ - z
917
+ `
918
+ );
919
+
920
+ expect(() => TemplateLoader.load(tmpDir)).toThrow(
921
+ 'uses both CapabilityComposition'
922
+ );
923
+ });
924
+
925
+ it("ignores extends when parent is not in roles map", () => {
926
+ writeYaml(
927
+ "team.yaml",
928
+ `
929
+ name: external-parent
930
+ version: 1
931
+ roles:
932
+ - child
933
+ topology:
934
+ root:
935
+ role: child
936
+ `
937
+ );
938
+
939
+ writeYaml(
940
+ "roles/child.yaml",
941
+ `
942
+ name: child
943
+ extends: nonexistent-parent
944
+ capabilities:
945
+ add:
946
+ - foo
947
+ - bar
948
+ `
949
+ );
950
+
951
+ // Parent not in roles map → just use add list as-is
952
+ const template = TemplateLoader.load(tmpDir);
953
+ expect(template.roles.get("child")!.capabilities).toEqual(["foo", "bar"]);
954
+ expect(template.roles.get("child")!.extends).toBe("nonexistent-parent");
955
+ });
956
+ });
957
+
958
+ describe("load with options (hooks)", () => {
959
+ it("resolves external parent via resolveExternalRole hook", () => {
960
+ writeYaml(
961
+ "team.yaml",
962
+ `
963
+ name: hook-test
964
+ version: 1
965
+ roles:
966
+ - child
967
+ topology:
968
+ root:
969
+ role: child
970
+ `
971
+ );
972
+
973
+ writeYaml(
974
+ "roles/child.yaml",
975
+ `
976
+ name: child
977
+ extends: external-worker
978
+ capabilities:
979
+ add:
980
+ - debug
981
+ remove:
982
+ - git.push
983
+ `
984
+ );
985
+
986
+ const template = TemplateLoader.load(tmpDir, {
987
+ resolveExternalRole: (name) => {
988
+ if (name === "external-worker") {
989
+ return {
990
+ name: "external-worker",
991
+ displayName: "Worker",
992
+ description: "External worker role",
993
+ capabilities: ["file.read", "file.write", "git.push"],
994
+ raw: { name: "external-worker" },
995
+ };
996
+ }
997
+ return null;
998
+ },
999
+ });
1000
+
1001
+ const child = template.roles.get("child")!;
1002
+ // parent: [file.read, file.write, git.push] + debug - git.push
1003
+ expect(child.capabilities.sort()).toEqual(["debug", "file.read", "file.write"]);
1004
+ });
1005
+
1006
+ it("external resolver returning null falls back to add-only", () => {
1007
+ writeYaml(
1008
+ "team.yaml",
1009
+ `
1010
+ name: null-resolver
1011
+ version: 1
1012
+ roles:
1013
+ - orphan
1014
+ topology:
1015
+ root:
1016
+ role: orphan
1017
+ `
1018
+ );
1019
+
1020
+ writeYaml(
1021
+ "roles/orphan.yaml",
1022
+ `
1023
+ name: orphan
1024
+ extends: nonexistent
1025
+ capabilities:
1026
+ add:
1027
+ - foo
1028
+ `
1029
+ );
1030
+
1031
+ const template = TemplateLoader.load(tmpDir, {
1032
+ resolveExternalRole: () => null,
1033
+ });
1034
+
1035
+ // No parent found → just the add list
1036
+ expect(template.roles.get("orphan")!.capabilities).toEqual(["foo"]);
1037
+ });
1038
+
1039
+ it("postProcessRole transforms each role", () => {
1040
+ writeYaml(
1041
+ "team.yaml",
1042
+ `
1043
+ name: post-process
1044
+ version: 1
1045
+ roles:
1046
+ - worker
1047
+ topology:
1048
+ root:
1049
+ role: worker
1050
+ `
1051
+ );
1052
+
1053
+ writeYaml(
1054
+ "roles/worker.yaml",
1055
+ `
1056
+ name: worker
1057
+ capabilities:
1058
+ - build
1059
+ `
1060
+ );
1061
+
1062
+ const template = TemplateLoader.load(tmpDir, {
1063
+ postProcessRole: (role) => ({
1064
+ ...role,
1065
+ capabilities: [...role.capabilities, "injected.cap"],
1066
+ }),
1067
+ });
1068
+
1069
+ expect(template.roles.get("worker")!.capabilities).toEqual([
1070
+ "build",
1071
+ "injected.cap",
1072
+ ]);
1073
+ });
1074
+
1075
+ it("postProcess transforms the entire template", () => {
1076
+ writeYaml(
1077
+ "team.yaml",
1078
+ `
1079
+ name: full-post
1080
+ version: 1
1081
+ roles:
1082
+ - worker
1083
+ topology:
1084
+ root:
1085
+ role: worker
1086
+ `
1087
+ );
1088
+
1089
+ const template = TemplateLoader.load(tmpDir, {
1090
+ postProcess: (t) => ({
1091
+ ...t,
1092
+ sourcePath: "/overridden",
1093
+ }),
1094
+ });
1095
+
1096
+ expect(template.sourcePath).toBe("/overridden");
1097
+ });
1098
+ });
1099
+
1100
+ describe("loadAsync", () => {
1101
+ it("loads template with async external resolver", async () => {
1102
+ writeYaml(
1103
+ "team.yaml",
1104
+ `
1105
+ name: async-test
1106
+ version: 1
1107
+ roles:
1108
+ - child
1109
+ topology:
1110
+ root:
1111
+ role: child
1112
+ `
1113
+ );
1114
+
1115
+ writeYaml(
1116
+ "roles/child.yaml",
1117
+ `
1118
+ name: child
1119
+ extends: async-parent
1120
+ capabilities:
1121
+ add:
1122
+ - test
1123
+ `
1124
+ );
1125
+
1126
+ const template = await TemplateLoader.loadAsync(tmpDir, {
1127
+ resolveExternalRole: async (name) => {
1128
+ // Simulate async lookup
1129
+ await new Promise((r) => setTimeout(r, 1));
1130
+ if (name === "async-parent") {
1131
+ return {
1132
+ name: "async-parent",
1133
+ displayName: "Async Parent",
1134
+ description: "Resolved asynchronously",
1135
+ capabilities: ["read", "write"],
1136
+ raw: { name: "async-parent" },
1137
+ };
1138
+ }
1139
+ return null;
1140
+ },
1141
+ });
1142
+
1143
+ const child = template.roles.get("child")!;
1144
+ expect(child.capabilities.sort()).toEqual(["read", "test", "write"]);
1145
+ });
1146
+
1147
+ it("loads without options (same as sync)", async () => {
1148
+ writeYaml(
1149
+ "team.yaml",
1150
+ `
1151
+ name: async-basic
1152
+ version: 1
1153
+ roles:
1154
+ - worker
1155
+ topology:
1156
+ root:
1157
+ role: worker
1158
+ `
1159
+ );
1160
+
1161
+ const template = await TemplateLoader.loadAsync(tmpDir);
1162
+ expect(template.manifest.name).toBe("async-basic");
1163
+ expect(template.roles.get("worker")!.name).toBe("worker");
1164
+ });
1165
+
1166
+ it("supports async postProcessRole", async () => {
1167
+ writeYaml(
1168
+ "team.yaml",
1169
+ `
1170
+ name: async-post
1171
+ version: 1
1172
+ roles:
1173
+ - dev
1174
+ topology:
1175
+ root:
1176
+ role: dev
1177
+ `
1178
+ );
1179
+
1180
+ const template = await TemplateLoader.loadAsync(tmpDir, {
1181
+ postProcessRole: async (role) => {
1182
+ await new Promise((r) => setTimeout(r, 1));
1183
+ return { ...role, capabilities: [...role.capabilities, "async.cap"] };
1184
+ },
1185
+ });
1186
+
1187
+ expect(template.roles.get("dev")!.capabilities).toContain("async.cap");
1188
+ });
1189
+
1190
+ it("supports async postProcess", async () => {
1191
+ writeYaml(
1192
+ "team.yaml",
1193
+ `
1194
+ name: async-post-full
1195
+ version: 1
1196
+ roles:
1197
+ - dev
1198
+ topology:
1199
+ root:
1200
+ role: dev
1201
+ `
1202
+ );
1203
+
1204
+ const template = await TemplateLoader.loadAsync(tmpDir, {
1205
+ postProcess: async (t) => {
1206
+ await new Promise((r) => setTimeout(r, 1));
1207
+ return { ...t, sourcePath: "/async-overridden" };
1208
+ },
1209
+ });
1210
+
1211
+ expect(template.sourcePath).toBe("/async-overridden");
1212
+ });
1213
+ });
1214
+
1215
+ describe("MCP server loading", () => {
1216
+ it("loads tools/mcp-servers.json per role", () => {
1217
+ writeYaml(
1218
+ "team.yaml",
1219
+ `
1220
+ name: mcp-test
1221
+ version: 1
1222
+ roles:
1223
+ - planner
1224
+ - worker
1225
+ topology:
1226
+ root:
1227
+ role: planner
1228
+ `
1229
+ );
1230
+
1231
+ const mcpConfig = {
1232
+ planner: {
1233
+ servers: [
1234
+ { name: "sudocode", command: "npx", args: ["sudocode-mcp"] },
1235
+ ],
1236
+ },
1237
+ worker: {
1238
+ servers: [
1239
+ { name: "linter", command: "eslint-mcp", env: { CI: "true" } },
1240
+ { name: "tester", command: "vitest-mcp" },
1241
+ ],
1242
+ },
1243
+ };
1244
+ const toolsDir = path.join(tmpDir, "tools");
1245
+ fs.mkdirSync(toolsDir, { recursive: true });
1246
+ fs.writeFileSync(
1247
+ path.join(toolsDir, "mcp-servers.json"),
1248
+ JSON.stringify(mcpConfig),
1249
+ "utf-8"
1250
+ );
1251
+
1252
+ const template = TemplateLoader.load(tmpDir);
1253
+ expect(template.mcpServers.size).toBe(2);
1254
+ expect(template.mcpServers.get("planner")).toHaveLength(1);
1255
+ expect(template.mcpServers.get("planner")![0].name).toBe("sudocode");
1256
+ expect(template.mcpServers.get("worker")).toHaveLength(2);
1257
+ expect(template.mcpServers.get("worker")![0].env).toEqual({ CI: "true" });
1258
+ });
1259
+
1260
+ it("returns empty map when tools/ does not exist", () => {
1261
+ writeYaml(
1262
+ "team.yaml",
1263
+ `
1264
+ name: no-tools
1265
+ version: 1
1266
+ roles:
1267
+ - worker
1268
+ topology:
1269
+ root:
1270
+ role: worker
1271
+ `
1272
+ );
1273
+
1274
+ const template = TemplateLoader.load(tmpDir);
1275
+ expect(template.mcpServers.size).toBe(0);
1276
+ });
1277
+
1278
+ it("throws on malformed JSON", () => {
1279
+ writeYaml(
1280
+ "team.yaml",
1281
+ `
1282
+ name: bad-json
1283
+ version: 1
1284
+ roles:
1285
+ - worker
1286
+ topology:
1287
+ root:
1288
+ role: worker
1289
+ `
1290
+ );
1291
+
1292
+ const toolsDir = path.join(tmpDir, "tools");
1293
+ fs.mkdirSync(toolsDir, { recursive: true });
1294
+ fs.writeFileSync(
1295
+ path.join(toolsDir, "mcp-servers.json"),
1296
+ "{ invalid json",
1297
+ "utf-8"
1298
+ );
1299
+
1300
+ expect(() => TemplateLoader.load(tmpDir)).toThrow("Failed to parse");
1301
+ });
1302
+ });
1303
+
1304
+ describe("loadFromManifest", () => {
1305
+ it("loads from an inline manifest", () => {
1306
+ const template = TemplateLoader.loadFromManifest({
1307
+ name: "inline",
1308
+ version: 1,
1309
+ roles: ["worker"],
1310
+ topology: { root: { role: "worker" } },
1311
+ });
1312
+
1313
+ expect(template.manifest.name).toBe("inline");
1314
+ expect(template.roles.get("worker")!.name).toBe("worker");
1315
+ expect(template.mcpServers.size).toBe(0);
1316
+ expect(template.sourcePath).toBe("");
1317
+ });
1318
+ });
1319
+ });