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.
- package/.sudocode/issues.jsonl +28 -0
- package/.sudocode/specs.jsonl +4 -0
- package/CLAUDE.md +9 -3
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js +111 -48
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/types.d.ts +7 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/server.d.ts +5 -1
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +100 -3
- package/dist/api/server.js.map +1 -1
- package/dist/api/types.d.ts +1 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +71 -1
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.js +27 -8
- package/dist/cli/mcp.js.map +1 -1
- package/dist/config/project-config.d.ts +13 -2
- package/dist/config/project-config.d.ts.map +1 -1
- package/dist/config/project-config.js +12 -2
- package/dist/config/project-config.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/handlers/index.d.ts +7 -3
- package/dist/lifecycle/handlers/index.d.ts.map +1 -1
- package/dist/lifecycle/handlers/index.js +25 -8
- package/dist/lifecycle/handlers/index.js.map +1 -1
- package/dist/lifecycle/types.d.ts +2 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/map/adapter/extensions/index.d.ts +4 -1
- package/dist/map/adapter/extensions/index.d.ts.map +1 -1
- package/dist/map/adapter/extensions/index.js +27 -0
- package/dist/map/adapter/extensions/index.js.map +1 -1
- package/dist/map/adapter/extensions/streams.d.ts +95 -0
- package/dist/map/adapter/extensions/streams.d.ts.map +1 -0
- package/dist/map/adapter/extensions/streams.js +515 -0
- package/dist/map/adapter/extensions/streams.js.map +1 -0
- package/dist/map/adapter/index.d.ts +1 -1
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +3 -1
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/types.d.ts +1 -1
- package/dist/map/adapter/types.d.ts.map +1 -1
- package/dist/mcp/mcp-server.d.ts +2 -0
- package/dist/mcp/mcp-server.d.ts.map +1 -1
- package/dist/mcp/mcp-server.js +12 -3
- package/dist/mcp/mcp-server.js.map +1 -1
- package/dist/mcp/tools/done.d.ts.map +1 -1
- package/dist/mcp/tools/done.js +18 -0
- package/dist/mcp/tools/done.js.map +1 -1
- package/dist/roles/builtin/coordinator.d.ts.map +1 -1
- package/dist/roles/builtin/coordinator.js +2 -1
- package/dist/roles/builtin/coordinator.js.map +1 -1
- package/dist/roles/builtin/integrator.d.ts.map +1 -1
- package/dist/roles/builtin/integrator.js +2 -1
- package/dist/roles/builtin/integrator.js.map +1 -1
- package/dist/roles/builtin/worker.d.ts.map +1 -1
- package/dist/roles/builtin/worker.js +3 -1
- package/dist/roles/builtin/worker.js.map +1 -1
- package/dist/roles/capabilities.d.ts +6 -0
- package/dist/roles/capabilities.d.ts.map +1 -1
- package/dist/roles/capabilities.js +10 -0
- package/dist/roles/capabilities.js.map +1 -1
- package/dist/roles/config-loader.d.ts +1 -1
- package/dist/roles/config-loader.d.ts.map +1 -1
- package/dist/roles/config-loader.js +3 -2
- package/dist/roles/config-loader.js.map +1 -1
- package/dist/roles/types.d.ts +3 -1
- package/dist/roles/types.d.ts.map +1 -1
- package/dist/server/combined-server.d.ts +8 -1
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +6 -2
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.d.ts.map +1 -1
- package/dist/store/event-store.js +12 -5
- package/dist/store/event-store.js.map +1 -1
- package/dist/store/instance.d.ts +1 -1
- package/dist/store/instance.d.ts.map +1 -1
- package/dist/store/instance.js +2 -2
- package/dist/store/instance.js.map +1 -1
- package/dist/store/types/agents.d.ts +5 -0
- package/dist/store/types/agents.d.ts.map +1 -1
- package/dist/task/backend/opentasks/daemon-manager.d.ts.map +1 -1
- package/dist/task/backend/opentasks/daemon-manager.js +1 -1
- package/dist/task/backend/opentasks/daemon-manager.js.map +1 -1
- package/dist/teams/index.d.ts +3 -1
- package/dist/teams/index.d.ts.map +1 -1
- package/dist/teams/index.js +2 -0
- package/dist/teams/index.js.map +1 -1
- package/dist/teams/seed-defaults.d.ts +20 -0
- package/dist/teams/seed-defaults.d.ts.map +1 -0
- package/dist/teams/seed-defaults.js +71 -0
- package/dist/teams/seed-defaults.js.map +1 -0
- package/dist/teams/team-loader.d.ts +6 -2
- package/dist/teams/team-loader.d.ts.map +1 -1
- package/dist/teams/team-loader.js +154 -162
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-manager.d.ts +112 -0
- package/dist/teams/team-manager.d.ts.map +1 -0
- package/dist/teams/team-manager.js +305 -0
- package/dist/teams/team-manager.js.map +1 -0
- package/dist/teams/team-runtime.d.ts +125 -19
- package/dist/teams/team-runtime.d.ts.map +1 -1
- package/dist/teams/team-runtime.js +527 -119
- package/dist/teams/team-runtime.js.map +1 -1
- package/dist/teams/types.d.ts +41 -151
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/teams/types.js +2 -3
- package/dist/teams/types.js.map +1 -1
- package/docs/teams.md +73 -0
- package/package.json +2 -1
- package/references/minimem/.claude/settings.json +7 -0
- package/references/minimem/.sudocode/issues.jsonl +18 -0
- package/references/minimem/.sudocode/specs.jsonl +1 -0
- package/references/minimem/CLAUDE.md +310 -0
- package/references/minimem/README.md +562 -0
- package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
- package/references/minimem/claude-plugin/.mcp.json +7 -0
- package/references/minimem/claude-plugin/README.md +158 -0
- package/references/minimem/claude-plugin/commands/recall.md +47 -0
- package/references/minimem/claude-plugin/commands/remember.md +41 -0
- package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
- package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
- package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
- package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
- package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
- package/references/minimem/media/banner.png +0 -0
- package/references/minimem/package-lock.json +5373 -0
- package/references/minimem/package.json +72 -0
- package/references/minimem/scripts/postbuild.js +35 -0
- package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
- package/references/minimem/src/__tests__/errors.test.ts +265 -0
- package/references/minimem/src/__tests__/helpers.ts +199 -0
- package/references/minimem/src/__tests__/internal.test.ts +407 -0
- package/references/minimem/src/__tests__/knowledge.test.ts +287 -0
- package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
- package/references/minimem/src/__tests__/session.test.ts +190 -0
- package/references/minimem/src/cli/__tests__/commands.test.ts +759 -0
- package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
- package/references/minimem/src/cli/commands/append.ts +76 -0
- package/references/minimem/src/cli/commands/config.ts +262 -0
- package/references/minimem/src/cli/commands/conflicts.ts +413 -0
- package/references/minimem/src/cli/commands/daemon.ts +169 -0
- package/references/minimem/src/cli/commands/index.ts +12 -0
- package/references/minimem/src/cli/commands/init.ts +88 -0
- package/references/minimem/src/cli/commands/mcp.ts +177 -0
- package/references/minimem/src/cli/commands/push-pull.ts +213 -0
- package/references/minimem/src/cli/commands/search.ts +158 -0
- package/references/minimem/src/cli/commands/status.ts +84 -0
- package/references/minimem/src/cli/commands/sync-init.ts +290 -0
- package/references/minimem/src/cli/commands/sync.ts +70 -0
- package/references/minimem/src/cli/commands/upsert.ts +197 -0
- package/references/minimem/src/cli/config.ts +584 -0
- package/references/minimem/src/cli/index.ts +264 -0
- package/references/minimem/src/cli/shared.ts +161 -0
- package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
- package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
- package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
- package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
- package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
- package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
- package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
- package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
- package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
- package/references/minimem/src/cli/sync/central.ts +292 -0
- package/references/minimem/src/cli/sync/conflicts.ts +204 -0
- package/references/minimem/src/cli/sync/daemon.ts +407 -0
- package/references/minimem/src/cli/sync/detection.ts +138 -0
- package/references/minimem/src/cli/sync/index.ts +107 -0
- package/references/minimem/src/cli/sync/operations.ts +373 -0
- package/references/minimem/src/cli/sync/registry.ts +279 -0
- package/references/minimem/src/cli/sync/state.ts +355 -0
- package/references/minimem/src/cli/sync/validation.ts +206 -0
- package/references/minimem/src/cli/sync/watcher.ts +234 -0
- package/references/minimem/src/cli/version.ts +34 -0
- package/references/minimem/src/core/index.ts +9 -0
- package/references/minimem/src/core/indexer.ts +628 -0
- package/references/minimem/src/core/searcher.ts +221 -0
- package/references/minimem/src/db/schema.ts +183 -0
- package/references/minimem/src/db/sqlite-vec.ts +24 -0
- package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
- package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
- package/references/minimem/src/embeddings/batch-openai.ts +409 -0
- package/references/minimem/src/embeddings/embeddings.ts +434 -0
- package/references/minimem/src/index.ts +109 -0
- package/references/minimem/src/internal.ts +299 -0
- package/references/minimem/src/minimem.ts +1276 -0
- package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
- package/references/minimem/src/search/graph.ts +234 -0
- package/references/minimem/src/search/hybrid.ts +151 -0
- package/references/minimem/src/search/search.ts +256 -0
- package/references/minimem/src/server/__tests__/mcp.test.ts +341 -0
- package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
- package/references/minimem/src/server/mcp.ts +326 -0
- package/references/minimem/src/server/tools.ts +720 -0
- package/references/minimem/src/session.ts +460 -0
- package/references/minimem/tsconfig.json +19 -0
- package/references/minimem/tsup.config.ts +26 -0
- package/references/minimem/vitest.config.ts +24 -0
- package/references/openteams/.claude/settings.json +6 -0
- package/references/openteams/README.md +1 -0
- package/references/openteams/SKILL.md +341 -0
- package/references/openteams/design.md +411 -0
- package/references/openteams/examples/bmad-method/prompts/analyst/ROLE.md +16 -0
- package/references/openteams/examples/bmad-method/prompts/analyst/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/architect/ROLE.md +24 -0
- package/references/openteams/examples/bmad-method/prompts/architect/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/developer/ROLE.md +25 -0
- package/references/openteams/examples/bmad-method/prompts/developer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/master/ROLE.md +21 -0
- package/references/openteams/examples/bmad-method/prompts/master/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/pm/ROLE.md +20 -0
- package/references/openteams/examples/bmad-method/prompts/pm/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/qa/ROLE.md +17 -0
- package/references/openteams/examples/bmad-method/prompts/qa/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/ROLE.md +23 -0
- package/references/openteams/examples/bmad-method/prompts/quick-flow-dev/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/scrum-master/ROLE.md +27 -0
- package/references/openteams/examples/bmad-method/prompts/scrum-master/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/tech-writer/ROLE.md +21 -0
- package/references/openteams/examples/bmad-method/prompts/tech-writer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/prompts/ux-designer/ROLE.md +16 -0
- package/references/openteams/examples/bmad-method/prompts/ux-designer/SOUL.md +5 -0
- package/references/openteams/examples/bmad-method/roles/analyst.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/architect.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/developer.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/master.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/pm.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/qa.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/quick-flow-dev.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/scrum-master.yaml +9 -0
- package/references/openteams/examples/bmad-method/roles/tech-writer.yaml +8 -0
- package/references/openteams/examples/bmad-method/roles/ux-designer.yaml +8 -0
- package/references/openteams/examples/bmad-method/team.yaml +161 -0
- package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/ROLE.md +17 -0
- package/references/openteams/examples/get-shit-done/prompts/codebase-mapper/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/debugger/ROLE.md +25 -0
- package/references/openteams/examples/get-shit-done/prompts/debugger/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/executor/ROLE.md +34 -0
- package/references/openteams/examples/get-shit-done/prompts/executor/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/integration-checker/ROLE.md +18 -0
- package/references/openteams/examples/get-shit-done/prompts/integration-checker/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/orchestrator/ROLE.md +42 -0
- package/references/openteams/examples/get-shit-done/prompts/orchestrator/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/phase-researcher/ROLE.md +15 -0
- package/references/openteams/examples/get-shit-done/prompts/phase-researcher/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/plan-checker/ROLE.md +17 -0
- package/references/openteams/examples/get-shit-done/prompts/plan-checker/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/planner/ROLE.md +28 -0
- package/references/openteams/examples/get-shit-done/prompts/planner/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/prompts/project-researcher/ROLE.md +16 -0
- package/references/openteams/examples/get-shit-done/prompts/project-researcher/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/ROLE.md +13 -0
- package/references/openteams/examples/get-shit-done/prompts/research-synthesizer/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/roadmapper/ROLE.md +14 -0
- package/references/openteams/examples/get-shit-done/prompts/roadmapper/SOUL.md +3 -0
- package/references/openteams/examples/get-shit-done/prompts/verifier/ROLE.md +19 -0
- package/references/openteams/examples/get-shit-done/prompts/verifier/SOUL.md +5 -0
- package/references/openteams/examples/get-shit-done/roles/codebase-mapper.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/debugger.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/executor.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/integration-checker.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/orchestrator.yaml +9 -0
- package/references/openteams/examples/get-shit-done/roles/phase-researcher.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/plan-checker.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/planner.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/project-researcher.yaml +8 -0
- package/references/openteams/examples/get-shit-done/roles/research-synthesizer.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/roadmapper.yaml +7 -0
- package/references/openteams/examples/get-shit-done/roles/verifier.yaml +8 -0
- package/references/openteams/examples/get-shit-done/team.yaml +154 -0
- package/references/openteams/package-lock.json +2181 -0
- package/references/openteams/package.json +48 -0
- package/references/openteams/schema/role.schema.json +125 -0
- package/references/openteams/schema/team.schema.json +284 -0
- package/references/openteams/src/cli/agent.ts +104 -0
- package/references/openteams/src/cli/cli.test.ts +381 -0
- package/references/openteams/src/cli/generate.ts +220 -0
- package/references/openteams/src/cli/message.ts +241 -0
- package/references/openteams/src/cli/task.ts +154 -0
- package/references/openteams/src/cli/team.ts +104 -0
- package/references/openteams/src/cli/template.ts +207 -0
- package/references/openteams/src/cli.ts +45 -0
- package/references/openteams/src/db/database.test.ts +185 -0
- package/references/openteams/src/db/database.ts +240 -0
- package/references/openteams/src/generators/agent-prompt-generator.test.ts +332 -0
- package/references/openteams/src/generators/agent-prompt-generator.ts +521 -0
- package/references/openteams/src/generators/package-generator.test.ts +129 -0
- package/references/openteams/src/generators/package-generator.ts +102 -0
- package/references/openteams/src/generators/skill-generator.test.ts +246 -0
- package/references/openteams/src/generators/skill-generator.ts +374 -0
- package/references/openteams/src/index.ts +104 -0
- package/references/openteams/src/services/agent-service.test.ts +158 -0
- package/references/openteams/src/services/agent-service.ts +84 -0
- package/references/openteams/src/services/communication-service.test.ts +455 -0
- package/references/openteams/src/services/communication-service.ts +371 -0
- package/references/openteams/src/services/message-service.test.ts +342 -0
- package/references/openteams/src/services/message-service.ts +203 -0
- package/references/openteams/src/services/task-service.test.ts +434 -0
- package/references/openteams/src/services/task-service.ts +239 -0
- package/references/openteams/src/services/team-service.test.ts +181 -0
- package/references/openteams/src/services/team-service.ts +139 -0
- package/references/openteams/src/services/template-service.test.ts +306 -0
- package/references/openteams/src/services/template-service.ts +182 -0
- package/references/openteams/src/spawner/acp-factory.ts +96 -0
- package/references/openteams/src/spawner/interface.ts +31 -0
- package/references/openteams/src/spawner/mock.test.ts +93 -0
- package/references/openteams/src/spawner/mock.ts +59 -0
- package/references/openteams/src/template/loader.test.ts +1319 -0
- package/references/openteams/src/template/loader.ts +698 -0
- package/references/openteams/src/template/types.ts +200 -0
- package/references/openteams/src/types.ts +205 -0
- package/references/openteams/tsconfig.json +18 -0
- package/references/openteams/vitest.config.ts +9 -0
- package/references/skill-tree/.claude/settings.json +6 -0
- package/references/skill-tree/.sudocode/issues.jsonl +11 -0
- package/references/skill-tree/.sudocode/specs.jsonl +1 -0
- package/references/skill-tree/CLAUDE.md +150 -0
- package/references/skill-tree/README.md +324 -0
- package/references/skill-tree/docs/GAPS_v1.md +221 -0
- package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
- package/references/skill-tree/docs/TODOS.md +91 -0
- package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
- package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
- package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
- package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
- package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
- package/references/skill-tree/docs/scraper/README.md +170 -0
- package/references/skill-tree/examples/basic-usage.ts +190 -0
- package/references/skill-tree/package-lock.json +1509 -0
- package/references/skill-tree/package.json +66 -0
- package/references/skill-tree/scraper/README.md +123 -0
- package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
- package/references/skill-tree/scraper/docs/PLAN.md +336 -0
- package/references/skill-tree/scraper/drizzle.config.ts +10 -0
- package/references/skill-tree/scraper/package-lock.json +6329 -0
- package/references/skill-tree/scraper/package.json +68 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
- package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
- package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
- package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
- package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
- package/references/skill-tree/scraper/tsup.config.ts +14 -0
- package/references/skill-tree/scraper/vitest.config.ts +17 -0
- package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
- package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
- package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
- package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
- package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
- package/references/skill-tree/test/run-all.ts +106 -0
- package/references/skill-tree/test/utils.ts +128 -0
- package/references/skill-tree/vitest.config.ts +16 -0
- package/src/agent/agent-manager.ts +143 -72
- package/src/agent/types.ts +9 -0
- package/src/api/__tests__/server.test.ts +203 -4
- package/src/api/server.ts +130 -5
- package/src/api/types.ts +3 -1
- package/src/cli/acp.ts +68 -1
- package/src/cli/index.ts +5 -1
- package/src/cli/mcp.ts +27 -13
- package/src/config/project-config.ts +27 -3
- package/src/index.ts +3 -0
- package/src/lifecycle/__tests__/handlers.test.ts +53 -0
- package/src/lifecycle/handlers/index.ts +25 -8
- package/src/lifecycle/types.ts +3 -0
- package/src/map/adapter/__tests__/stream-extensions.test.ts +494 -0
- package/src/map/adapter/extensions/index.ts +36 -0
- package/src/map/adapter/extensions/streams.ts +839 -0
- package/src/map/adapter/index.ts +5 -0
- package/src/map/adapter/types.ts +8 -1
- package/src/mcp/mcp-server.ts +14 -3
- package/src/mcp/tools/done.ts +19 -0
- package/src/roles/builtin/coordinator.ts +2 -0
- package/src/roles/builtin/integrator.ts +2 -0
- package/src/roles/builtin/worker.ts +3 -0
- package/src/roles/capabilities.ts +11 -0
- package/src/roles/config-loader.ts +3 -2
- package/src/roles/types.ts +7 -0
- package/src/server/combined-server.ts +15 -1
- package/src/store/__tests__/event-store-oob.test.ts +109 -0
- package/src/store/event-store.ts +13 -3
- package/src/store/instance.ts +2 -2
- package/src/store/types/agents.ts +5 -0
- package/src/task/backend/__tests__/memory-pull-mode.test.ts +153 -0
- package/src/task/backend/opentasks/daemon-manager.ts +4 -1
- package/src/teams/CLAUDE.md +180 -0
- package/src/teams/__tests__/e2e/workspace-isolation.e2e.test.ts +1263 -0
- package/src/teams/__tests__/team-manager.test.ts +814 -0
- package/src/teams/__tests__/team-system.test.ts +1291 -8
- package/src/teams/index.ts +21 -3
- package/src/teams/seed-defaults.ts +79 -0
- package/src/teams/team-loader.ts +200 -234
- package/src/teams/team-manager.ts +387 -0
- package/src/teams/team-runtime.ts +590 -121
- package/src/teams/types.ts +99 -200
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import type {
|
|
5
|
+
TeamManifest,
|
|
6
|
+
RoleDefinition,
|
|
7
|
+
ResolvedTemplate,
|
|
8
|
+
ResolvedRole,
|
|
9
|
+
ResolvedPrompts,
|
|
10
|
+
PromptSection,
|
|
11
|
+
CapabilityComposition,
|
|
12
|
+
McpServerEntry,
|
|
13
|
+
LoadOptions,
|
|
14
|
+
AsyncLoadOptions,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
export class TemplateLoader {
|
|
18
|
+
/**
|
|
19
|
+
* Load a team template from a directory.
|
|
20
|
+
* Expects: team.yaml, optional roles/*.yaml, optional prompts/*.md
|
|
21
|
+
*
|
|
22
|
+
* @param templateDir - Absolute or relative path to the template directory
|
|
23
|
+
* @param options - Optional hooks for external role resolution and post-processing
|
|
24
|
+
*/
|
|
25
|
+
static load(templateDir: string, options?: LoadOptions): ResolvedTemplate {
|
|
26
|
+
const { manifest, roles, prompts, mcpServers, absDir } =
|
|
27
|
+
TemplateLoader.loadCore(templateDir);
|
|
28
|
+
|
|
29
|
+
// Resolve role inheritance chains (with optional external resolution)
|
|
30
|
+
TemplateLoader.resolveInheritance(roles, options?.resolveExternalRole);
|
|
31
|
+
|
|
32
|
+
// Post-process each role if hook provided
|
|
33
|
+
if (options?.postProcessRole) {
|
|
34
|
+
for (const [name, role] of roles) {
|
|
35
|
+
roles.set(name, options.postProcessRole(role, manifest));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Load prompts
|
|
40
|
+
for (const roleName of manifest.roles) {
|
|
41
|
+
const resolved = TemplateLoader.loadPromptsForRole(
|
|
42
|
+
absDir,
|
|
43
|
+
roleName,
|
|
44
|
+
manifest,
|
|
45
|
+
roles.get(roleName)
|
|
46
|
+
);
|
|
47
|
+
if (resolved) {
|
|
48
|
+
prompts.set(roleName, resolved);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let result: ResolvedTemplate = {
|
|
53
|
+
manifest,
|
|
54
|
+
roles,
|
|
55
|
+
prompts,
|
|
56
|
+
mcpServers,
|
|
57
|
+
sourcePath: absDir,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (options?.postProcess) {
|
|
61
|
+
result = options.postProcess(result);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Async variant of load(). Identical file I/O but hooks may return Promises.
|
|
69
|
+
*
|
|
70
|
+
* @param templateDir - Absolute or relative path to the template directory
|
|
71
|
+
* @param options - Optional async hooks for external role resolution and post-processing
|
|
72
|
+
*/
|
|
73
|
+
static async loadAsync(
|
|
74
|
+
templateDir: string,
|
|
75
|
+
options?: AsyncLoadOptions
|
|
76
|
+
): Promise<ResolvedTemplate> {
|
|
77
|
+
const { manifest, roles, prompts, mcpServers, absDir } =
|
|
78
|
+
TemplateLoader.loadCore(templateDir);
|
|
79
|
+
|
|
80
|
+
// Resolve role inheritance chains (with optional async external resolution)
|
|
81
|
+
await TemplateLoader.resolveInheritanceAsync(
|
|
82
|
+
roles,
|
|
83
|
+
options?.resolveExternalRole
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Post-process each role if hook provided
|
|
87
|
+
if (options?.postProcessRole) {
|
|
88
|
+
for (const [name, role] of roles) {
|
|
89
|
+
roles.set(name, await options.postProcessRole(role, manifest));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Load prompts
|
|
94
|
+
for (const roleName of manifest.roles) {
|
|
95
|
+
const resolved = TemplateLoader.loadPromptsForRole(
|
|
96
|
+
absDir,
|
|
97
|
+
roleName,
|
|
98
|
+
manifest,
|
|
99
|
+
roles.get(roleName)
|
|
100
|
+
);
|
|
101
|
+
if (resolved) {
|
|
102
|
+
prompts.set(roleName, resolved);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let result: ResolvedTemplate = {
|
|
107
|
+
manifest,
|
|
108
|
+
roles,
|
|
109
|
+
prompts,
|
|
110
|
+
mcpServers,
|
|
111
|
+
sourcePath: absDir,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (options?.postProcess) {
|
|
115
|
+
result = await options.postProcess(result);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Core loading logic shared by load() and loadAsync().
|
|
123
|
+
* Reads filesystem and parses YAML — no hooks called.
|
|
124
|
+
*/
|
|
125
|
+
private static loadCore(templateDir: string): {
|
|
126
|
+
manifest: TeamManifest;
|
|
127
|
+
roles: Map<string, ResolvedRole>;
|
|
128
|
+
prompts: Map<string, ResolvedPrompts>;
|
|
129
|
+
mcpServers: Map<string, McpServerEntry[]>;
|
|
130
|
+
absDir: string;
|
|
131
|
+
} {
|
|
132
|
+
const absDir = path.resolve(templateDir);
|
|
133
|
+
|
|
134
|
+
if (!fs.existsSync(absDir)) {
|
|
135
|
+
throw new Error(`Template directory not found: ${absDir}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const manifestPath = path.join(absDir, "team.yaml");
|
|
139
|
+
if (!fs.existsSync(manifestPath)) {
|
|
140
|
+
throw new Error(`team.yaml not found in ${absDir}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const manifestContent = fs.readFileSync(manifestPath, "utf-8");
|
|
144
|
+
const manifest = yaml.load(manifestContent) as TeamManifest;
|
|
145
|
+
|
|
146
|
+
TemplateLoader.validateManifest(manifest);
|
|
147
|
+
|
|
148
|
+
const roles = new Map<string, ResolvedRole>();
|
|
149
|
+
const prompts = new Map<string, ResolvedPrompts>();
|
|
150
|
+
|
|
151
|
+
// Load role definitions
|
|
152
|
+
for (const roleName of manifest.roles) {
|
|
153
|
+
const rolePath = path.join(absDir, "roles", `${roleName}.yaml`);
|
|
154
|
+
if (fs.existsSync(rolePath)) {
|
|
155
|
+
const roleContent = fs.readFileSync(rolePath, "utf-8");
|
|
156
|
+
const roleDef = yaml.load(roleContent) as RoleDefinition;
|
|
157
|
+
roles.set(roleName, TemplateLoader.resolveRole(roleDef));
|
|
158
|
+
} else {
|
|
159
|
+
// Implicit role — just the name, no definition file
|
|
160
|
+
roles.set(roleName, {
|
|
161
|
+
name: roleName,
|
|
162
|
+
displayName: roleName,
|
|
163
|
+
description: `Role: ${roleName}`,
|
|
164
|
+
capabilities: [],
|
|
165
|
+
raw: { name: roleName },
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Load MCP server configs
|
|
171
|
+
const mcpServers = TemplateLoader.loadMcpServers(absDir);
|
|
172
|
+
|
|
173
|
+
return { manifest, roles, prompts, mcpServers, absDir };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Load a template from an inline manifest object (no filesystem).
|
|
178
|
+
* Used for programmatic/test scenarios.
|
|
179
|
+
*/
|
|
180
|
+
static loadFromManifest(manifest: TeamManifest): ResolvedTemplate {
|
|
181
|
+
TemplateLoader.validateManifest(manifest);
|
|
182
|
+
|
|
183
|
+
const roles = new Map<string, ResolvedRole>();
|
|
184
|
+
for (const roleName of manifest.roles) {
|
|
185
|
+
roles.set(roleName, {
|
|
186
|
+
name: roleName,
|
|
187
|
+
displayName: roleName,
|
|
188
|
+
description: `Role: ${roleName}`,
|
|
189
|
+
capabilities: [],
|
|
190
|
+
raw: { name: roleName },
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
manifest,
|
|
196
|
+
roles,
|
|
197
|
+
prompts: new Map(),
|
|
198
|
+
mcpServers: new Map(),
|
|
199
|
+
sourcePath: "",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private static validateManifest(manifest: TeamManifest): void {
|
|
204
|
+
if (!manifest.name) {
|
|
205
|
+
throw new Error("Template manifest missing required field: name");
|
|
206
|
+
}
|
|
207
|
+
if (!manifest.version) {
|
|
208
|
+
throw new Error("Template manifest missing required field: version");
|
|
209
|
+
}
|
|
210
|
+
if (!manifest.roles || !Array.isArray(manifest.roles) || manifest.roles.length === 0) {
|
|
211
|
+
throw new Error("Template manifest must define at least one role");
|
|
212
|
+
}
|
|
213
|
+
if (!manifest.topology?.root?.role) {
|
|
214
|
+
throw new Error("Template manifest must define topology.root.role");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Validate all topology references point to declared roles
|
|
218
|
+
const declaredRoles = new Set(manifest.roles);
|
|
219
|
+
|
|
220
|
+
if (!declaredRoles.has(manifest.topology.root.role)) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`topology.root.role "${manifest.topology.root.role}" is not in the roles list`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (manifest.topology.companions) {
|
|
227
|
+
for (const comp of manifest.topology.companions) {
|
|
228
|
+
if (!declaredRoles.has(comp.role)) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`topology.companions role "${comp.role}" is not in the roles list`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (manifest.topology.spawn_rules) {
|
|
237
|
+
for (const [from, targets] of Object.entries(manifest.topology.spawn_rules)) {
|
|
238
|
+
if (!declaredRoles.has(from)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`spawn_rules key "${from}" is not in the roles list`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
for (const target of targets) {
|
|
244
|
+
if (!declaredRoles.has(target)) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`spawn_rules "${from}" references unknown role "${target}"`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Validate communication references
|
|
254
|
+
if (manifest.communication) {
|
|
255
|
+
const channels = manifest.communication.channels
|
|
256
|
+
? new Set(Object.keys(manifest.communication.channels))
|
|
257
|
+
: new Set<string>();
|
|
258
|
+
|
|
259
|
+
if (manifest.communication.subscriptions) {
|
|
260
|
+
for (const [role, subs] of Object.entries(manifest.communication.subscriptions)) {
|
|
261
|
+
if (!declaredRoles.has(role)) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`communication.subscriptions key "${role}" is not in the roles list`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
for (const sub of subs) {
|
|
267
|
+
if (!channels.has(sub.channel)) {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`subscription for "${role}" references unknown channel "${sub.channel}"`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (manifest.communication.emissions) {
|
|
277
|
+
for (const role of Object.keys(manifest.communication.emissions)) {
|
|
278
|
+
if (!declaredRoles.has(role)) {
|
|
279
|
+
throw new Error(
|
|
280
|
+
`communication.emissions key "${role}" is not in the roles list`
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (manifest.communication.routing?.peers) {
|
|
287
|
+
for (const peer of manifest.communication.routing.peers) {
|
|
288
|
+
if (!declaredRoles.has(peer.from)) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`routing.peers.from "${peer.from}" is not in the roles list`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (!declaredRoles.has(peer.to)) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`routing.peers.to "${peer.to}" is not in the roles list`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Merge a child role's capabilities with its parent using CapabilityComposition.
|
|
305
|
+
*/
|
|
306
|
+
private static mergeCapabilities(
|
|
307
|
+
role: ResolvedRole,
|
|
308
|
+
parentRole: ResolvedRole
|
|
309
|
+
): void {
|
|
310
|
+
const raw = role.raw;
|
|
311
|
+
if (raw.capabilities && !Array.isArray(raw.capabilities)) {
|
|
312
|
+
// CapabilityComposition — merge with parent
|
|
313
|
+
const comp = raw.capabilities as CapabilityComposition;
|
|
314
|
+
const parentCaps = [...parentRole.capabilities];
|
|
315
|
+
const toAdd = comp.add ?? [];
|
|
316
|
+
const toRemove = new Set(comp.remove ?? []);
|
|
317
|
+
|
|
318
|
+
// Start with parent caps, add child additions, remove exclusions
|
|
319
|
+
const merged = [...new Set([...parentCaps, ...toAdd])];
|
|
320
|
+
role.capabilities = merged.filter((c) => !toRemove.has(c));
|
|
321
|
+
}
|
|
322
|
+
// If capabilities is a plain array, it's an explicit override — keep as-is
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Resolve external parents: for roles that `extends` a name not in the local map,
|
|
327
|
+
* call the resolver hook to get the parent ResolvedRole.
|
|
328
|
+
* Returns a map of external parent name → ResolvedRole.
|
|
329
|
+
*/
|
|
330
|
+
private static resolveExternalParents(
|
|
331
|
+
roles: Map<string, ResolvedRole>,
|
|
332
|
+
resolver?: (name: string) => ResolvedRole | null
|
|
333
|
+
): Map<string, ResolvedRole> {
|
|
334
|
+
const externals = new Map<string, ResolvedRole>();
|
|
335
|
+
if (!resolver) return externals;
|
|
336
|
+
|
|
337
|
+
for (const [, role] of roles) {
|
|
338
|
+
if (role.extends && !roles.has(role.extends) && !externals.has(role.extends)) {
|
|
339
|
+
const external = resolver(role.extends);
|
|
340
|
+
if (external) {
|
|
341
|
+
externals.set(role.extends, external);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return externals;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Resolve external parents (async variant).
|
|
350
|
+
*/
|
|
351
|
+
private static async resolveExternalParentsAsync(
|
|
352
|
+
roles: Map<string, ResolvedRole>,
|
|
353
|
+
resolver?: (name: string) => Promise<ResolvedRole | null> | ResolvedRole | null
|
|
354
|
+
): Promise<Map<string, ResolvedRole>> {
|
|
355
|
+
const externals = new Map<string, ResolvedRole>();
|
|
356
|
+
if (!resolver) return externals;
|
|
357
|
+
|
|
358
|
+
for (const [, role] of roles) {
|
|
359
|
+
if (role.extends && !roles.has(role.extends) && !externals.has(role.extends)) {
|
|
360
|
+
const external = await resolver(role.extends);
|
|
361
|
+
if (external) {
|
|
362
|
+
externals.set(role.extends, external);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return externals;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Core inheritance resolution logic. Operates on a combined map of
|
|
371
|
+
* local + external parent roles.
|
|
372
|
+
*/
|
|
373
|
+
private static resolveInheritanceCore(
|
|
374
|
+
roles: Map<string, ResolvedRole>,
|
|
375
|
+
allRoles: Map<string, ResolvedRole>
|
|
376
|
+
): void {
|
|
377
|
+
// Build dependency map: child -> parent (only for parents resolvable in allRoles)
|
|
378
|
+
const extendsMap = new Map<string, string>();
|
|
379
|
+
for (const [name, role] of roles) {
|
|
380
|
+
if (role.extends && allRoles.has(role.extends)) {
|
|
381
|
+
extendsMap.set(name, role.extends);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if (extendsMap.size === 0) return;
|
|
386
|
+
|
|
387
|
+
// Detect cycles by following chains (only through local roles)
|
|
388
|
+
for (const startName of extendsMap.keys()) {
|
|
389
|
+
const chain: string[] = [];
|
|
390
|
+
let current: string | undefined = startName;
|
|
391
|
+
while (current) {
|
|
392
|
+
if (chain.includes(current)) {
|
|
393
|
+
const cycleStart = chain.indexOf(current);
|
|
394
|
+
const cyclePath = [...chain.slice(cycleStart), current].join(" -> ");
|
|
395
|
+
throw new Error(`Circular role inheritance detected: ${cyclePath}`);
|
|
396
|
+
}
|
|
397
|
+
chain.push(current);
|
|
398
|
+
current = extendsMap.get(current);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Resolve in topological order (parents before children)
|
|
403
|
+
const resolved = new Set<string>();
|
|
404
|
+
|
|
405
|
+
const resolve = (name: string): void => {
|
|
406
|
+
if (resolved.has(name)) return;
|
|
407
|
+
|
|
408
|
+
const parent = extendsMap.get(name);
|
|
409
|
+
if (parent && roles.has(parent)) {
|
|
410
|
+
resolve(parent);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const role = roles.get(name)!;
|
|
414
|
+
if (parent) {
|
|
415
|
+
const parentRole = allRoles.get(parent)!;
|
|
416
|
+
TemplateLoader.mergeCapabilities(role, parentRole);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
resolved.add(name);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
for (const name of extendsMap.keys()) {
|
|
423
|
+
resolve(name);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Resolve role inheritance chains. For each role with `extends` pointing
|
|
429
|
+
* to another role in the map (or resolvable via external resolver),
|
|
430
|
+
* merge parent capabilities with the child's add/remove composition.
|
|
431
|
+
* Detects circular inheritance.
|
|
432
|
+
*
|
|
433
|
+
* @param resolveExternalRole - Optional hook to resolve roles not in the local map
|
|
434
|
+
*/
|
|
435
|
+
private static resolveInheritance(
|
|
436
|
+
roles: Map<string, ResolvedRole>,
|
|
437
|
+
resolveExternalRole?: (name: string) => ResolvedRole | null
|
|
438
|
+
): void {
|
|
439
|
+
const externals = TemplateLoader.resolveExternalParents(roles, resolveExternalRole);
|
|
440
|
+
const allRoles = new Map([...roles, ...externals]);
|
|
441
|
+
TemplateLoader.resolveInheritanceCore(roles, allRoles);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Async variant of resolveInheritance.
|
|
446
|
+
*/
|
|
447
|
+
private static async resolveInheritanceAsync(
|
|
448
|
+
roles: Map<string, ResolvedRole>,
|
|
449
|
+
resolveExternalRole?: (name: string) => Promise<ResolvedRole | null> | ResolvedRole | null
|
|
450
|
+
): Promise<void> {
|
|
451
|
+
const externals = await TemplateLoader.resolveExternalParentsAsync(roles, resolveExternalRole);
|
|
452
|
+
const allRoles = new Map([...roles, ...externals]);
|
|
453
|
+
TemplateLoader.resolveInheritanceCore(roles, allRoles);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Normalize a role definition's capability fields into a canonical form.
|
|
458
|
+
*
|
|
459
|
+
* Supports two syntaxes for capability composition:
|
|
460
|
+
* 1. `capabilities: { add: [...], remove: [...] }` (CapabilityComposition)
|
|
461
|
+
* 2. `capabilities_add: [...]` / `capabilities_remove: [...]` (flat fields)
|
|
462
|
+
*
|
|
463
|
+
* Both are normalized into CapabilityComposition on `raw.capabilities` so that
|
|
464
|
+
* `resolveInheritance()` has a single code path.
|
|
465
|
+
*
|
|
466
|
+
* Validation: errors if both syntaxes are used simultaneously.
|
|
467
|
+
*/
|
|
468
|
+
private static normalizeRoleDefinition(def: RoleDefinition): RoleDefinition {
|
|
469
|
+
const hasComposition = def.capabilities && !Array.isArray(def.capabilities);
|
|
470
|
+
const hasFlatFields = def.capabilities_add !== undefined || def.capabilities_remove !== undefined;
|
|
471
|
+
|
|
472
|
+
if (hasComposition && hasFlatFields) {
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Role "${def.name}" uses both CapabilityComposition in "capabilities" and flat ` +
|
|
475
|
+
`"capabilities_add"/"capabilities_remove" fields. Use one syntax or the other.`
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Normalize flat fields into CapabilityComposition on the capabilities field
|
|
480
|
+
if (hasFlatFields) {
|
|
481
|
+
const normalized = { ...def };
|
|
482
|
+
normalized.capabilities = {
|
|
483
|
+
add: def.capabilities_add,
|
|
484
|
+
remove: def.capabilities_remove,
|
|
485
|
+
} as CapabilityComposition;
|
|
486
|
+
delete normalized.capabilities_add;
|
|
487
|
+
delete normalized.capabilities_remove;
|
|
488
|
+
return normalized;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return def;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
private static resolveRole(def: RoleDefinition): ResolvedRole {
|
|
495
|
+
// Normalize flat capabilities_add/remove into CapabilityComposition
|
|
496
|
+
const normalized = TemplateLoader.normalizeRoleDefinition(def);
|
|
497
|
+
let capabilities: string[] = [];
|
|
498
|
+
|
|
499
|
+
if (normalized.capabilities) {
|
|
500
|
+
if (Array.isArray(normalized.capabilities)) {
|
|
501
|
+
capabilities = normalized.capabilities;
|
|
502
|
+
} else {
|
|
503
|
+
// CapabilityComposition — resolve against parent later if extends is used.
|
|
504
|
+
// For now, just collect the add list.
|
|
505
|
+
const comp = normalized.capabilities as CapabilityComposition;
|
|
506
|
+
capabilities = comp.add ?? [];
|
|
507
|
+
// remove is applied when composing with parent; tracked in raw for later
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
name: normalized.name,
|
|
513
|
+
extends: normalized.extends,
|
|
514
|
+
displayName: normalized.display_name ?? normalized.name,
|
|
515
|
+
description: normalized.description ?? `Role: ${normalized.name}`,
|
|
516
|
+
capabilities,
|
|
517
|
+
promptFile: normalized.prompt,
|
|
518
|
+
promptFiles: normalized.prompts,
|
|
519
|
+
raw: normalized,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Load tools/mcp-servers.json if present.
|
|
525
|
+
* Returns a map of role name → MCP server entries.
|
|
526
|
+
*/
|
|
527
|
+
private static loadMcpServers(
|
|
528
|
+
absDir: string
|
|
529
|
+
): Map<string, McpServerEntry[]> {
|
|
530
|
+
const result = new Map<string, McpServerEntry[]>();
|
|
531
|
+
const mcpPath = path.join(absDir, "tools", "mcp-servers.json");
|
|
532
|
+
|
|
533
|
+
if (!fs.existsSync(mcpPath)) {
|
|
534
|
+
return result;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
let raw: string;
|
|
538
|
+
try {
|
|
539
|
+
raw = fs.readFileSync(mcpPath, "utf-8");
|
|
540
|
+
} catch (err) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`Failed to read ${mcpPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
let parsed: Record<string, { servers: McpServerEntry[] }>;
|
|
547
|
+
try {
|
|
548
|
+
parsed = JSON.parse(raw);
|
|
549
|
+
} catch (err) {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`Failed to parse ${mcpPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
for (const [roleName, config] of Object.entries(parsed)) {
|
|
556
|
+
if (config.servers && Array.isArray(config.servers)) {
|
|
557
|
+
result.set(roleName, config.servers);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return result;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Load prompts for a role. Supports two layouts:
|
|
566
|
+
*
|
|
567
|
+
* 1. Single file: prompts/<role>.md (backward compatible)
|
|
568
|
+
* 2. Directory: prompts/<role>/prompt.md + additional .md files
|
|
569
|
+
*
|
|
570
|
+
* When a directory exists, prompt.md is the primary prompt and all
|
|
571
|
+
* other .md files become additional sections. The role YAML can
|
|
572
|
+
* specify an explicit file list via `prompts:` to control ordering.
|
|
573
|
+
*/
|
|
574
|
+
private static loadPromptsForRole(
|
|
575
|
+
absDir: string,
|
|
576
|
+
roleName: string,
|
|
577
|
+
manifest: TeamManifest,
|
|
578
|
+
role?: ResolvedRole
|
|
579
|
+
): ResolvedPrompts | null {
|
|
580
|
+
// Priority 1: topology node prompt path (single file, backward compat)
|
|
581
|
+
if (manifest.topology.root.role === roleName && manifest.topology.root.prompt) {
|
|
582
|
+
const p = path.join(absDir, manifest.topology.root.prompt);
|
|
583
|
+
if (fs.existsSync(p)) {
|
|
584
|
+
return { primary: fs.readFileSync(p, "utf-8"), additional: [] };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (manifest.topology.companions) {
|
|
589
|
+
for (const comp of manifest.topology.companions) {
|
|
590
|
+
if (comp.role === roleName && comp.prompt) {
|
|
591
|
+
const p = path.join(absDir, comp.prompt);
|
|
592
|
+
if (fs.existsSync(p)) {
|
|
593
|
+
return { primary: fs.readFileSync(p, "utf-8"), additional: [] };
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Priority 2: role definition prompt field (single file)
|
|
600
|
+
if (role?.promptFile && !role.promptFiles) {
|
|
601
|
+
const p = path.join(absDir, role.promptFile);
|
|
602
|
+
if (fs.existsSync(p)) {
|
|
603
|
+
return { primary: fs.readFileSync(p, "utf-8"), additional: [] };
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Priority 3: prompt directory — prompts/<roleName>/
|
|
608
|
+
const promptDirPath = path.join(absDir, "prompts", roleName);
|
|
609
|
+
if (fs.existsSync(promptDirPath) && fs.statSync(promptDirPath).isDirectory()) {
|
|
610
|
+
return TemplateLoader.loadPromptDirectory(promptDirPath, role);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Priority 4: single file convention — prompts/<roleName>.md
|
|
614
|
+
const conventionPath = path.join(absDir, "prompts", `${roleName}.md`);
|
|
615
|
+
if (fs.existsSync(conventionPath)) {
|
|
616
|
+
return { primary: fs.readFileSync(conventionPath, "utf-8"), additional: [] };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Load a prompt directory into a ResolvedPrompts.
|
|
624
|
+
*
|
|
625
|
+
* If the role YAML declares `prompts:` (an ordered list of filenames),
|
|
626
|
+
* those files are loaded in that order. The first file is primary,
|
|
627
|
+
* the rest are additional sections.
|
|
628
|
+
*
|
|
629
|
+
* Otherwise, ROLE.md is the primary and remaining .md files are
|
|
630
|
+
* loaded as additional sections. SOUL.md is always ordered first
|
|
631
|
+
* among additional files so personality/values precede other materials.
|
|
632
|
+
*/
|
|
633
|
+
private static loadPromptDirectory(
|
|
634
|
+
dirPath: string,
|
|
635
|
+
role?: ResolvedRole
|
|
636
|
+
): ResolvedPrompts | null {
|
|
637
|
+
// Explicit ordering from role YAML
|
|
638
|
+
if (role?.promptFiles && role.promptFiles.length > 0) {
|
|
639
|
+
const files = role.promptFiles;
|
|
640
|
+
let primary: string | null = null;
|
|
641
|
+
const additional: PromptSection[] = [];
|
|
642
|
+
|
|
643
|
+
for (const file of files) {
|
|
644
|
+
const filePath = path.join(dirPath, file);
|
|
645
|
+
if (!fs.existsSync(filePath)) continue;
|
|
646
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
647
|
+
if (primary === null) {
|
|
648
|
+
primary = content;
|
|
649
|
+
} else {
|
|
650
|
+
const stem = path.basename(file, path.extname(file));
|
|
651
|
+
additional.push({ name: stem, content });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (primary === null) return null;
|
|
656
|
+
return { primary, additional };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Convention: ROLE.md is primary, SOUL.md is first additional,
|
|
660
|
+
// remaining .md files sorted alphabetically.
|
|
661
|
+
// Falls back to prompt.md / first-alphabetical for backward compat.
|
|
662
|
+
const allFiles = fs.readdirSync(dirPath)
|
|
663
|
+
.filter((f: string) => f.endsWith(".md"))
|
|
664
|
+
.sort();
|
|
665
|
+
|
|
666
|
+
if (allFiles.length === 0) return null;
|
|
667
|
+
|
|
668
|
+
// Determine primary file: ROLE.md > prompt.md > first alphabetically
|
|
669
|
+
let primaryFile: string;
|
|
670
|
+
if (allFiles.includes("ROLE.md")) {
|
|
671
|
+
primaryFile = "ROLE.md";
|
|
672
|
+
} else if (allFiles.includes("prompt.md")) {
|
|
673
|
+
primaryFile = "prompt.md";
|
|
674
|
+
} else {
|
|
675
|
+
primaryFile = allFiles[0];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const primary = fs.readFileSync(path.join(dirPath, primaryFile), "utf-8");
|
|
679
|
+
|
|
680
|
+
// Build additional list: SOUL.md first, then the rest alphabetically
|
|
681
|
+
const additional: PromptSection[] = [];
|
|
682
|
+
const soulFile = allFiles.find((f: string) => f === "SOUL.md" || f === "soul.md");
|
|
683
|
+
if (soulFile && soulFile !== primaryFile) {
|
|
684
|
+
const content = fs.readFileSync(path.join(dirPath, soulFile), "utf-8");
|
|
685
|
+
additional.push({ name: "soul", content });
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
for (const file of allFiles) {
|
|
689
|
+
if (file === primaryFile) continue;
|
|
690
|
+
if (file === soulFile) continue; // already added above
|
|
691
|
+
const stem = path.basename(file, ".md");
|
|
692
|
+
const content = fs.readFileSync(path.join(dirPath, file), "utf-8");
|
|
693
|
+
additional.push({ name: stem, content });
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
return { primary, additional };
|
|
697
|
+
}
|
|
698
|
+
}
|