forge-server 0.1.0
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/.claude/hooks/worktree-create.sh +64 -0
- package/.claude/hooks/worktree-remove.sh +57 -0
- package/.claude/settings.local.json +29 -0
- package/.forge/knowledge/conventions.yaml +1 -0
- package/.forge/knowledge/decisions.yaml +1 -0
- package/.forge/knowledge/gotchas.yaml +1 -0
- package/.forge/knowledge/patterns.yaml +1 -0
- package/.forge/manifest.yaml +6 -0
- package/CLAUDE.md +144 -0
- package/bin/setup-forge.sh +132 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +553 -0
- package/dist/cli.js.map +1 -0
- package/dist/context/codebase.d.ts +57 -0
- package/dist/context/codebase.d.ts.map +1 -0
- package/dist/context/codebase.js +301 -0
- package/dist/context/codebase.js.map +1 -0
- package/dist/context/injector.d.ts +147 -0
- package/dist/context/injector.d.ts.map +1 -0
- package/dist/context/injector.js +533 -0
- package/dist/context/injector.js.map +1 -0
- package/dist/context/memory.d.ts +32 -0
- package/dist/context/memory.d.ts.map +1 -0
- package/dist/context/memory.js +140 -0
- package/dist/context/memory.js.map +1 -0
- package/dist/context/session-index.d.ts +54 -0
- package/dist/context/session-index.d.ts.map +1 -0
- package/dist/context/session-index.js +265 -0
- package/dist/context/session-index.js.map +1 -0
- package/dist/context/session.d.ts +42 -0
- package/dist/context/session.d.ts.map +1 -0
- package/dist/context/session.js +121 -0
- package/dist/context/session.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/chunker.d.ts +19 -0
- package/dist/ingestion/chunker.d.ts.map +1 -0
- package/dist/ingestion/chunker.js +189 -0
- package/dist/ingestion/chunker.js.map +1 -0
- package/dist/ingestion/embedder.d.ts +45 -0
- package/dist/ingestion/embedder.d.ts.map +1 -0
- package/dist/ingestion/embedder.js +152 -0
- package/dist/ingestion/embedder.js.map +1 -0
- package/dist/ingestion/git-analyzer.d.ts +77 -0
- package/dist/ingestion/git-analyzer.d.ts.map +1 -0
- package/dist/ingestion/git-analyzer.js +437 -0
- package/dist/ingestion/git-analyzer.js.map +1 -0
- package/dist/ingestion/indexer.d.ts +79 -0
- package/dist/ingestion/indexer.d.ts.map +1 -0
- package/dist/ingestion/indexer.js +766 -0
- package/dist/ingestion/indexer.js.map +1 -0
- package/dist/ingestion/markdown-chunker.d.ts +19 -0
- package/dist/ingestion/markdown-chunker.d.ts.map +1 -0
- package/dist/ingestion/markdown-chunker.js +243 -0
- package/dist/ingestion/markdown-chunker.js.map +1 -0
- package/dist/ingestion/markdown-knowledge.d.ts +21 -0
- package/dist/ingestion/markdown-knowledge.d.ts.map +1 -0
- package/dist/ingestion/markdown-knowledge.js +129 -0
- package/dist/ingestion/markdown-knowledge.js.map +1 -0
- package/dist/ingestion/parser.d.ts +20 -0
- package/dist/ingestion/parser.d.ts.map +1 -0
- package/dist/ingestion/parser.js +429 -0
- package/dist/ingestion/parser.js.map +1 -0
- package/dist/ingestion/watcher.d.ts +28 -0
- package/dist/ingestion/watcher.d.ts.map +1 -0
- package/dist/ingestion/watcher.js +147 -0
- package/dist/ingestion/watcher.js.map +1 -0
- package/dist/knowledge/hydrator.d.ts +37 -0
- package/dist/knowledge/hydrator.d.ts.map +1 -0
- package/dist/knowledge/hydrator.js +220 -0
- package/dist/knowledge/hydrator.js.map +1 -0
- package/dist/knowledge/registry.d.ts +129 -0
- package/dist/knowledge/registry.d.ts.map +1 -0
- package/dist/knowledge/registry.js +361 -0
- package/dist/knowledge/registry.js.map +1 -0
- package/dist/knowledge/search.d.ts +114 -0
- package/dist/knowledge/search.d.ts.map +1 -0
- package/dist/knowledge/search.js +428 -0
- package/dist/knowledge/search.js.map +1 -0
- package/dist/knowledge/store.d.ts +76 -0
- package/dist/knowledge/store.d.ts.map +1 -0
- package/dist/knowledge/store.js +230 -0
- package/dist/knowledge/store.js.map +1 -0
- package/dist/learning/confidence.d.ts +30 -0
- package/dist/learning/confidence.d.ts.map +1 -0
- package/dist/learning/confidence.js +165 -0
- package/dist/learning/confidence.js.map +1 -0
- package/dist/learning/patterns.d.ts +52 -0
- package/dist/learning/patterns.d.ts.map +1 -0
- package/dist/learning/patterns.js +290 -0
- package/dist/learning/patterns.js.map +1 -0
- package/dist/learning/trajectory.d.ts +55 -0
- package/dist/learning/trajectory.d.ts.map +1 -0
- package/dist/learning/trajectory.js +200 -0
- package/dist/learning/trajectory.js.map +1 -0
- package/dist/memory/memory-compat.d.ts +100 -0
- package/dist/memory/memory-compat.d.ts.map +1 -0
- package/dist/memory/memory-compat.js +146 -0
- package/dist/memory/memory-compat.js.map +1 -0
- package/dist/memory/observation-store.d.ts +57 -0
- package/dist/memory/observation-store.d.ts.map +1 -0
- package/dist/memory/observation-store.js +154 -0
- package/dist/memory/observation-store.js.map +1 -0
- package/dist/memory/session-tracker.d.ts +81 -0
- package/dist/memory/session-tracker.d.ts.map +1 -0
- package/dist/memory/session-tracker.js +262 -0
- package/dist/memory/session-tracker.js.map +1 -0
- package/dist/pipeline/engine.d.ts +179 -0
- package/dist/pipeline/engine.d.ts.map +1 -0
- package/dist/pipeline/engine.js +691 -0
- package/dist/pipeline/engine.js.map +1 -0
- package/dist/pipeline/events.d.ts +54 -0
- package/dist/pipeline/events.d.ts.map +1 -0
- package/dist/pipeline/events.js +157 -0
- package/dist/pipeline/events.js.map +1 -0
- package/dist/pipeline/parallel.d.ts +83 -0
- package/dist/pipeline/parallel.d.ts.map +1 -0
- package/dist/pipeline/parallel.js +277 -0
- package/dist/pipeline/parallel.js.map +1 -0
- package/dist/pipeline/state-machine.d.ts +65 -0
- package/dist/pipeline/state-machine.d.ts.map +1 -0
- package/dist/pipeline/state-machine.js +176 -0
- package/dist/pipeline/state-machine.js.map +1 -0
- package/dist/query/graph-queries.d.ts +84 -0
- package/dist/query/graph-queries.d.ts.map +1 -0
- package/dist/query/graph-queries.js +216 -0
- package/dist/query/graph-queries.js.map +1 -0
- package/dist/query/hybrid-search.d.ts +34 -0
- package/dist/query/hybrid-search.d.ts.map +1 -0
- package/dist/query/hybrid-search.js +263 -0
- package/dist/query/hybrid-search.js.map +1 -0
- package/dist/query/intent-detector.d.ts +35 -0
- package/dist/query/intent-detector.d.ts.map +1 -0
- package/dist/query/intent-detector.js +115 -0
- package/dist/query/intent-detector.js.map +1 -0
- package/dist/query/ranking.d.ts +57 -0
- package/dist/query/ranking.d.ts.map +1 -0
- package/dist/query/ranking.js +109 -0
- package/dist/query/ranking.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +291 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/falkordb-store.d.ts +73 -0
- package/dist/storage/falkordb-store.d.ts.map +1 -0
- package/dist/storage/falkordb-store.js +346 -0
- package/dist/storage/falkordb-store.js.map +1 -0
- package/dist/storage/file-cache.d.ts +32 -0
- package/dist/storage/file-cache.d.ts.map +1 -0
- package/dist/storage/file-cache.js +115 -0
- package/dist/storage/file-cache.js.map +1 -0
- package/dist/storage/interfaces.d.ts +151 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +7 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/qdrant-store.d.ts +110 -0
- package/dist/storage/qdrant-store.d.ts.map +1 -0
- package/dist/storage/qdrant-store.js +467 -0
- package/dist/storage/qdrant-store.js.map +1 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +136 -0
- package/dist/storage/schema.js.map +1 -0
- package/dist/storage/sqlite.d.ts +35 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +132 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/tools/collaboration-tools.d.ts +111 -0
- package/dist/tools/collaboration-tools.d.ts.map +1 -0
- package/dist/tools/collaboration-tools.js +174 -0
- package/dist/tools/collaboration-tools.js.map +1 -0
- package/dist/tools/context-tools.d.ts +293 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +437 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/graph-tools.d.ts +129 -0
- package/dist/tools/graph-tools.d.ts.map +1 -0
- package/dist/tools/graph-tools.js +237 -0
- package/dist/tools/graph-tools.js.map +1 -0
- package/dist/tools/ingestion-tools.d.ts +96 -0
- package/dist/tools/ingestion-tools.d.ts.map +1 -0
- package/dist/tools/ingestion-tools.js +90 -0
- package/dist/tools/ingestion-tools.js.map +1 -0
- package/dist/tools/learning-tools.d.ts +168 -0
- package/dist/tools/learning-tools.d.ts.map +1 -0
- package/dist/tools/learning-tools.js +158 -0
- package/dist/tools/learning-tools.js.map +1 -0
- package/dist/tools/memory-tools.d.ts +183 -0
- package/dist/tools/memory-tools.d.ts.map +1 -0
- package/dist/tools/memory-tools.js +197 -0
- package/dist/tools/memory-tools.js.map +1 -0
- package/dist/tools/phase-tools.d.ts +954 -0
- package/dist/tools/phase-tools.d.ts.map +1 -0
- package/dist/tools/phase-tools.js +1215 -0
- package/dist/tools/phase-tools.js.map +1 -0
- package/dist/tools/pipeline-tools.d.ts +140 -0
- package/dist/tools/pipeline-tools.d.ts.map +1 -0
- package/dist/tools/pipeline-tools.js +162 -0
- package/dist/tools/pipeline-tools.js.map +1 -0
- package/dist/tools/registration-tools.d.ts +220 -0
- package/dist/tools/registration-tools.d.ts.map +1 -0
- package/dist/tools/registration-tools.js +391 -0
- package/dist/tools/registration-tools.js.map +1 -0
- package/dist/util/circuit-breaker.d.ts +75 -0
- package/dist/util/circuit-breaker.d.ts.map +1 -0
- package/dist/util/circuit-breaker.js +159 -0
- package/dist/util/circuit-breaker.js.map +1 -0
- package/dist/util/config.d.ts +23 -0
- package/dist/util/config.d.ts.map +1 -0
- package/dist/util/config.js +164 -0
- package/dist/util/config.js.map +1 -0
- package/dist/util/logger.d.ts +13 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +45 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/token-counter.d.ts +24 -0
- package/dist/util/token-counter.d.ts.map +1 -0
- package/dist/util/token-counter.js +48 -0
- package/dist/util/token-counter.js.map +1 -0
- package/dist/util/types.d.ts +525 -0
- package/dist/util/types.d.ts.map +1 -0
- package/dist/util/types.js +5 -0
- package/dist/util/types.js.map +1 -0
- package/docker-compose.yml +20 -0
- package/docs/plans/2026-02-27-swarm-coordination/architecture.md +203 -0
- package/docs/plans/2026-02-27-swarm-coordination/vision.md +57 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/architecture.md +1 -0
- package/docs/plans/completed/2026-02-26-forge-plugin-bundling/vision.md +300 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/architecture.md +480 -0
- package/docs/plans/completed/2026-02-27-forge-swarm-learning/verification-checklist.md +462 -0
- package/docs/plans/completed/2026-02-27-git-history-atlassian/git-jira-plan.md +181 -0
- package/package.json +39 -0
- package/plugin/.claude-plugin/plugin.json +8 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/README.md +134 -0
- package/plugin/agents/architect.md +367 -0
- package/plugin/agents/backend-specialist.md +263 -0
- package/plugin/agents/brainstormer.md +122 -0
- package/plugin/agents/data-specialist.md +266 -0
- package/plugin/agents/designer.md +408 -0
- package/plugin/agents/frontend-specialist.md +241 -0
- package/plugin/agents/inspector.md +406 -0
- package/plugin/agents/knowledge-keeper.md +443 -0
- package/plugin/agents/platform-engineer.md +326 -0
- package/plugin/agents/product-manager.md +268 -0
- package/plugin/agents/product-owner.md +438 -0
- package/plugin/agents/pulse-checker.md +73 -0
- package/plugin/agents/qa-strategist.md +500 -0
- package/plugin/agents/self-improver.md +310 -0
- package/plugin/agents/strategist.md +360 -0
- package/plugin/agents/supervisor.md +380 -0
- package/plugin/commands/brainstorm.md +25 -0
- package/plugin/commands/forge.md +88 -0
- package/plugin/docs/atlassian-integration.md +110 -0
- package/plugin/docs/workflow.md +126 -0
- package/plugin/skills/agent-development/.skillfish.json +10 -0
- package/plugin/skills/agent-development/SKILL.md +415 -0
- package/plugin/skills/agent-development/examples/agent-creation-prompt.md +238 -0
- package/plugin/skills/agent-development/examples/complete-agent-examples.md +427 -0
- package/plugin/skills/agent-development/references/agent-creation-system-prompt.md +207 -0
- package/plugin/skills/agent-development/references/system-prompt-design.md +411 -0
- package/plugin/skills/agent-development/references/triggering-examples.md +491 -0
- package/plugin/skills/agent-development/scripts/validate-agent.sh +217 -0
- package/plugin/skills/agent-handoff/SKILL.md +335 -0
- package/plugin/skills/anti-stub/SKILL.md +317 -0
- package/plugin/skills/brainstorm/SKILL.md +31 -0
- package/plugin/skills/debugging/SKILL.md +276 -0
- package/plugin/skills/fix/SKILL.md +62 -0
- package/plugin/skills/frontend-design/.skillfish.json +10 -0
- package/plugin/skills/frontend-design/SKILL.md +42 -0
- package/plugin/skills/gotchas/SKILL.md +61 -0
- package/plugin/skills/graph-orchestrator/SKILL.md +38 -0
- package/plugin/skills/history/SKILL.md +58 -0
- package/plugin/skills/impact/SKILL.md +59 -0
- package/plugin/skills/implementation-execution/SKILL.md +291 -0
- package/plugin/skills/index-repo/SKILL.md +55 -0
- package/plugin/skills/interviewing/SKILL.md +225 -0
- package/plugin/skills/knowledge-curation/SKILL.md +393 -0
- package/plugin/skills/learn/SKILL.md +69 -0
- package/plugin/skills/mcp-integration/.skillfish.json +10 -0
- package/plugin/skills/mcp-integration/SKILL.md +554 -0
- package/plugin/skills/mcp-integration/examples/http-server.json +20 -0
- package/plugin/skills/mcp-integration/examples/sse-server.json +19 -0
- package/plugin/skills/mcp-integration/examples/stdio-server.json +26 -0
- package/plugin/skills/mcp-integration/references/authentication.md +549 -0
- package/plugin/skills/mcp-integration/references/server-types.md +536 -0
- package/plugin/skills/mcp-integration/references/tool-usage.md +538 -0
- package/plugin/skills/nestjs/.skillfish.json +10 -0
- package/plugin/skills/nestjs/SKILL.md +669 -0
- package/plugin/skills/nestjs/drizzle-reference.md +1894 -0
- package/plugin/skills/nestjs/reference.md +1447 -0
- package/plugin/skills/nestjs/workflow-optimization.md +229 -0
- package/plugin/skills/parallel-dispatch/SKILL.md +308 -0
- package/plugin/skills/project-discovery/SKILL.md +304 -0
- package/plugin/skills/search/SKILL.md +56 -0
- package/plugin/skills/security-audit/SKILL.md +362 -0
- package/plugin/skills/skill-development/.skillfish.json +10 -0
- package/plugin/skills/skill-development/SKILL.md +637 -0
- package/plugin/skills/skill-development/references/skill-creator-original.md +209 -0
- package/plugin/skills/tdd/SKILL.md +273 -0
- package/plugin/skills/terminal-presentation/SKILL.md +395 -0
- package/plugin/skills/test-strategy/SKILL.md +365 -0
- package/plugin/skills/verification-protocol/SKILL.md +256 -0
- package/plugin/skills/visual-explainer/CHANGELOG.md +97 -0
- package/plugin/skills/visual-explainer/LICENSE +21 -0
- package/plugin/skills/visual-explainer/README.md +137 -0
- package/plugin/skills/visual-explainer/SKILL.md +352 -0
- package/plugin/skills/visual-explainer/banner.png +0 -0
- package/plugin/skills/visual-explainer/package.json +11 -0
- package/plugin/skills/visual-explainer/prompts/diff-review.md +68 -0
- package/plugin/skills/visual-explainer/prompts/fact-check.md +63 -0
- package/plugin/skills/visual-explainer/prompts/generate-slides.md +18 -0
- package/plugin/skills/visual-explainer/prompts/generate-web-diagram.md +10 -0
- package/plugin/skills/visual-explainer/prompts/plan-review.md +86 -0
- package/plugin/skills/visual-explainer/prompts/project-recap.md +61 -0
- package/plugin/skills/visual-explainer/references/css-patterns.md +1188 -0
- package/plugin/skills/visual-explainer/references/libraries.md +470 -0
- package/plugin/skills/visual-explainer/references/responsive-nav.md +212 -0
- package/plugin/skills/visual-explainer/references/slide-patterns.md +1403 -0
- package/plugin/skills/visual-explainer/templates/architecture.html +596 -0
- package/plugin/skills/visual-explainer/templates/data-table.html +540 -0
- package/plugin/skills/visual-explainer/templates/mermaid-flowchart.html +435 -0
- package/plugin/skills/visual-explainer/templates/slide-deck.html +913 -0
- package/src/cli.ts +655 -0
- package/src/context/.gitkeep +0 -0
- package/src/context/codebase.ts +393 -0
- package/src/context/injector.ts +797 -0
- package/src/context/memory.ts +187 -0
- package/src/context/session-index.ts +327 -0
- package/src/context/session.ts +152 -0
- package/src/index.ts +47 -0
- package/src/ingestion/.gitkeep +0 -0
- package/src/ingestion/chunker.ts +277 -0
- package/src/ingestion/embedder.ts +167 -0
- package/src/ingestion/git-analyzer.ts +545 -0
- package/src/ingestion/indexer.ts +984 -0
- package/src/ingestion/markdown-chunker.ts +337 -0
- package/src/ingestion/markdown-knowledge.ts +175 -0
- package/src/ingestion/parser.ts +475 -0
- package/src/ingestion/watcher.ts +182 -0
- package/src/knowledge/.gitkeep +0 -0
- package/src/knowledge/hydrator.ts +246 -0
- package/src/knowledge/registry.ts +463 -0
- package/src/knowledge/search.ts +565 -0
- package/src/knowledge/store.ts +262 -0
- package/src/learning/.gitkeep +0 -0
- package/src/learning/confidence.ts +193 -0
- package/src/learning/patterns.ts +360 -0
- package/src/learning/trajectory.ts +268 -0
- package/src/memory/.gitkeep +0 -0
- package/src/memory/memory-compat.ts +233 -0
- package/src/memory/observation-store.ts +224 -0
- package/src/memory/session-tracker.ts +332 -0
- package/src/pipeline/.gitkeep +0 -0
- package/src/pipeline/engine.ts +1139 -0
- package/src/pipeline/events.ts +253 -0
- package/src/pipeline/parallel.ts +394 -0
- package/src/pipeline/state-machine.ts +199 -0
- package/src/query/.gitkeep +0 -0
- package/src/query/graph-queries.ts +262 -0
- package/src/query/hybrid-search.ts +337 -0
- package/src/query/intent-detector.ts +131 -0
- package/src/query/ranking.ts +161 -0
- package/src/server.ts +352 -0
- package/src/storage/.gitkeep +0 -0
- package/src/storage/falkordb-store.ts +388 -0
- package/src/storage/file-cache.ts +141 -0
- package/src/storage/interfaces.ts +201 -0
- package/src/storage/qdrant-store.ts +557 -0
- package/src/storage/schema.ts +139 -0
- package/src/storage/sqlite.ts +168 -0
- package/src/tools/.gitkeep +0 -0
- package/src/tools/collaboration-tools.ts +208 -0
- package/src/tools/context-tools.ts +493 -0
- package/src/tools/graph-tools.ts +295 -0
- package/src/tools/ingestion-tools.ts +122 -0
- package/src/tools/learning-tools.ts +181 -0
- package/src/tools/memory-tools.ts +234 -0
- package/src/tools/phase-tools.ts +1452 -0
- package/src/tools/pipeline-tools.ts +188 -0
- package/src/tools/registration-tools.ts +450 -0
- package/src/util/.gitkeep +0 -0
- package/src/util/circuit-breaker.ts +193 -0
- package/src/util/config.ts +177 -0
- package/src/util/logger.ts +53 -0
- package/src/util/token-counter.ts +52 -0
- package/src/util/types.ts +710 -0
- package/tests/context/.gitkeep +0 -0
- package/tests/integration/.gitkeep +0 -0
- package/tests/knowledge/.gitkeep +0 -0
- package/tests/learning/.gitkeep +0 -0
- package/tests/pipeline/.gitkeep +0 -0
- package/tests/tools/.gitkeep +0 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +10 -0
- package/vscode-extension/.vscodeignore +7 -0
- package/vscode-extension/README.md +43 -0
- package/vscode-extension/out/edge-collector.js +274 -0
- package/vscode-extension/out/edge-collector.js.map +1 -0
- package/vscode-extension/out/extension.js +264 -0
- package/vscode-extension/out/extension.js.map +1 -0
- package/vscode-extension/out/forge-client.js +318 -0
- package/vscode-extension/out/forge-client.js.map +1 -0
- package/vscode-extension/package-lock.json +59 -0
- package/vscode-extension/package.json +71 -0
- package/vscode-extension/src/edge-collector.ts +320 -0
- package/vscode-extension/src/extension.ts +269 -0
- package/vscode-extension/src/forge-client.ts +364 -0
- package/vscode-extension/tsconfig.json +19 -0
|
@@ -0,0 +1,1215 @@
|
|
|
1
|
+
// phase-tools.ts — B14
|
|
2
|
+
// MCP tool handlers for all phase lifecycle transitions.
|
|
3
|
+
// Every start_* tool: validates transition -> assembles context -> starts trajectory -> returns enriched context
|
|
4
|
+
// Every submit_* tool: validates phase -> records output -> records trajectory step -> advances state
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { saveObservation } from '../memory/observation-store.js';
|
|
7
|
+
import { getCurrentSessionId } from '../memory/session-tracker.js';
|
|
8
|
+
// Bridge: Project.currentPhase is typed as PipelinePhase (agent-role enum) but
|
|
9
|
+
// the DB stores Phase (workflow-stage) values. Cast explicitly at comparison sites.
|
|
10
|
+
function asPhase(p) {
|
|
11
|
+
return p;
|
|
12
|
+
}
|
|
13
|
+
import { writeFileSync, appendFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, resolve } from 'node:path';
|
|
15
|
+
import { mkdirSync, existsSync } from 'node:fs';
|
|
16
|
+
import { logger } from '../util/logger.js';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helper: write content to disk if file_path provided
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
function writeToDisk(content, filePath) {
|
|
21
|
+
const absPath = resolve(filePath);
|
|
22
|
+
const dir = dirname(absPath);
|
|
23
|
+
if (!existsSync(dir)) {
|
|
24
|
+
mkdirSync(dir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
if (existsSync(absPath)) {
|
|
27
|
+
// Append with a separator so phase outputs don't clobber each other
|
|
28
|
+
const separator = '\n\n---\n\n';
|
|
29
|
+
appendFileSync(absPath, separator + content, 'utf8');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
writeFileSync(absPath, content, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Helper: extract knowledge candidates from text (lightweight keyword NLP)
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function extractKnowledgeCandidates(text) {
|
|
39
|
+
// Simple heuristic: count potential knowledge items based on keywords
|
|
40
|
+
const keywords = [
|
|
41
|
+
/\b(gotcha|pitfall|issue|problem|note|warning|caution|important)\b/gi,
|
|
42
|
+
/\b(pattern|approach|best practice|convention|standard)\b/gi,
|
|
43
|
+
/\b(decided|decision|chose|rationale|because|reason)\b/gi,
|
|
44
|
+
];
|
|
45
|
+
let count = 0;
|
|
46
|
+
for (const re of keywords) {
|
|
47
|
+
const matches = text.match(re);
|
|
48
|
+
count += matches ? matches.length : 0;
|
|
49
|
+
}
|
|
50
|
+
return Math.min(count, 10); // cap at 10 for plausibility
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Input schemas
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
const projectIdSchema = z.string().min(1, 'project_id is required');
|
|
56
|
+
export const startInterviewSchema = z.object({
|
|
57
|
+
project_id: projectIdSchema,
|
|
58
|
+
initial_request: z.string().optional(),
|
|
59
|
+
});
|
|
60
|
+
export const submitVisionSchema = z.object({
|
|
61
|
+
project_id: projectIdSchema,
|
|
62
|
+
vision: z.string().min(1, 'vision content is required'),
|
|
63
|
+
file_path: z.string().optional(),
|
|
64
|
+
});
|
|
65
|
+
export const startArchitectureSchema = z.object({
|
|
66
|
+
project_id: projectIdSchema,
|
|
67
|
+
vision_summary: z.string().optional(),
|
|
68
|
+
});
|
|
69
|
+
export const submitPlanSchema = z.object({
|
|
70
|
+
project_id: projectIdSchema,
|
|
71
|
+
plan: z.string().min(1, 'plan content is required'),
|
|
72
|
+
file_path: z.string().optional(),
|
|
73
|
+
modules: z.array(z.string()).optional(),
|
|
74
|
+
requires_designer: z.boolean().optional(),
|
|
75
|
+
requires_qa: z.boolean().optional(),
|
|
76
|
+
});
|
|
77
|
+
export const startDesignSchema = z.object({
|
|
78
|
+
project_id: projectIdSchema,
|
|
79
|
+
focus: z.string().optional(),
|
|
80
|
+
});
|
|
81
|
+
export const submitDesignSchema = z.object({
|
|
82
|
+
project_id: projectIdSchema,
|
|
83
|
+
design: z.string().min(1, 'design content is required'),
|
|
84
|
+
file_path: z.string().optional(),
|
|
85
|
+
});
|
|
86
|
+
export const startQAStrategySchema = z.object({
|
|
87
|
+
project_id: projectIdSchema,
|
|
88
|
+
});
|
|
89
|
+
export const submitTestPlanSchema = z.object({
|
|
90
|
+
project_id: projectIdSchema,
|
|
91
|
+
test_plan: z.string().min(1, 'test_plan content is required'),
|
|
92
|
+
file_path: z.string().optional(),
|
|
93
|
+
});
|
|
94
|
+
export const startImplementationSchema = z.object({
|
|
95
|
+
project_id: projectIdSchema,
|
|
96
|
+
modules: z.array(z.object({
|
|
97
|
+
name: z.string(),
|
|
98
|
+
agent_type: z.string(),
|
|
99
|
+
description: z.string().optional(),
|
|
100
|
+
})),
|
|
101
|
+
});
|
|
102
|
+
export const submitImplementationSchema = z.object({
|
|
103
|
+
project_id: projectIdSchema,
|
|
104
|
+
module: z.string().min(1, 'module name is required'),
|
|
105
|
+
branch: z.string().optional(),
|
|
106
|
+
summary: z.string().optional(),
|
|
107
|
+
files_changed: z.array(z.string()).optional(),
|
|
108
|
+
});
|
|
109
|
+
export const startInspectionSchema = z.object({
|
|
110
|
+
project_id: projectIdSchema,
|
|
111
|
+
});
|
|
112
|
+
export const submitVerdictSchema = z.object({
|
|
113
|
+
project_id: projectIdSchema,
|
|
114
|
+
verdict: z.enum(['pass', 'pass_with_warnings', 'fail']),
|
|
115
|
+
findings: z.array(z.object({
|
|
116
|
+
severity: z.enum(['critical', 'warning', 'info']),
|
|
117
|
+
description: z.string(),
|
|
118
|
+
file_path: z.string().optional(),
|
|
119
|
+
line_number: z.number().optional(),
|
|
120
|
+
})).optional(),
|
|
121
|
+
summary: z.string().optional(),
|
|
122
|
+
});
|
|
123
|
+
export const collectKnowledgeSchema = z.object({
|
|
124
|
+
project_id: projectIdSchema,
|
|
125
|
+
});
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// No-op GraphStore used when the real graph backend is unavailable.
|
|
128
|
+
// Allows saveObservation() to be called without null checks everywhere.
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
const noOpGraphStore = {
|
|
131
|
+
connect: async () => { },
|
|
132
|
+
disconnect: async () => { },
|
|
133
|
+
isHealthy: async () => false,
|
|
134
|
+
query: async () => ({ nodes: [], edges: [] }),
|
|
135
|
+
upsertNode: async () => { },
|
|
136
|
+
upsertEdge: async () => { },
|
|
137
|
+
deleteFile: async () => { },
|
|
138
|
+
deleteRepo: async () => { },
|
|
139
|
+
getCounts: async () => ({ totalNodes: 0, totalEdges: 0, byLabel: {} }),
|
|
140
|
+
ensureIndexes: async () => { },
|
|
141
|
+
};
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// Fire-and-forget auto-observation helper.
|
|
144
|
+
// Wraps saveObservation() with null-guards and catch-all error handling.
|
|
145
|
+
// NEVER blocks the phase transition — errors are logged and swallowed.
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
function autoObserve(content, opts) {
|
|
148
|
+
if (!opts.vectorStore) {
|
|
149
|
+
logger.debug('autoObserve: vectorStore unavailable, skipping', { phase: opts.phase });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const summary = content.slice(0, 500);
|
|
153
|
+
saveObservation({
|
|
154
|
+
content: summary,
|
|
155
|
+
sessionId: getCurrentSessionId(),
|
|
156
|
+
type: 'procedural',
|
|
157
|
+
category: opts.category,
|
|
158
|
+
importance: opts.importance,
|
|
159
|
+
tags: ['auto_observation', opts.phase, opts.projectId],
|
|
160
|
+
}, opts.vectorStore, opts.graphStore ?? noOpGraphStore).catch((err) => {
|
|
161
|
+
logger.debug('autoObserve: fire-and-forget failed (non-fatal)', {
|
|
162
|
+
phase: opts.phase,
|
|
163
|
+
error: String(err),
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Fire-and-forget knowledge extraction from phase outputs.
|
|
169
|
+
// Scans text for keyword patterns indicating gotchas, patterns, decisions,
|
|
170
|
+
// and constraints. Saves each as a tagged observation for the Knowledge Keeper.
|
|
171
|
+
// NEVER blocks the phase transition — errors are logged and swallowed.
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
function autoExtractKnowledge(content, opts) {
|
|
174
|
+
if (!opts.vectorStore) {
|
|
175
|
+
logger.debug('autoExtractKnowledge: vectorStore unavailable, skipping', { phase: opts.phase });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const lines = content.split('\n');
|
|
179
|
+
const extractions = [];
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const trimmed = line.trim();
|
|
182
|
+
if (!trimmed || trimmed.length < 10)
|
|
183
|
+
continue;
|
|
184
|
+
// Gotcha indicators
|
|
185
|
+
if (/^(CRITICAL|WARNING|GOTCHA|CAUTION|NOTE|IMPORTANT)\s*[:!-]/i.test(trimmed)) {
|
|
186
|
+
extractions.push({ text: trimmed, category: 'gotcha' });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
// Pattern indicators
|
|
190
|
+
if (/\b(pattern|approach|convention|best.?practice|standard)\b/i.test(trimmed) && trimmed.length > 20) {
|
|
191
|
+
extractions.push({ text: trimmed, category: 'pattern' });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
// Decision indicators
|
|
195
|
+
if (/\b(decided|decision|chose|rationale|because we|reason for)\b/i.test(trimmed) && trimmed.length > 20) {
|
|
196
|
+
extractions.push({ text: trimmed, category: 'decision' });
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// Constraint indicators
|
|
200
|
+
if (/\b(constraint|limitation|must not|cannot|forbidden|never)\b/i.test(trimmed) && trimmed.length > 20) {
|
|
201
|
+
extractions.push({ text: trimmed, category: 'constraint' });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Cap at 15 extractions per phase output to avoid flooding
|
|
206
|
+
const capped = extractions.slice(0, 15);
|
|
207
|
+
for (const item of capped) {
|
|
208
|
+
saveObservation({
|
|
209
|
+
content: item.text.slice(0, 500),
|
|
210
|
+
sessionId: getCurrentSessionId(),
|
|
211
|
+
type: 'semantic',
|
|
212
|
+
category: item.category,
|
|
213
|
+
importance: item.category === 'gotcha' ? 0.7 : 0.5,
|
|
214
|
+
tags: ['auto_knowledge', opts.phase, opts.projectId],
|
|
215
|
+
}, opts.vectorStore, opts.graphStore ?? noOpGraphStore).catch((err) => {
|
|
216
|
+
logger.debug('autoExtractKnowledge: save failed (non-fatal)', {
|
|
217
|
+
category: item.category,
|
|
218
|
+
error: String(err),
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (capped.length > 0) {
|
|
223
|
+
logger.debug('autoExtractKnowledge: extracted items', {
|
|
224
|
+
phase: opts.phase,
|
|
225
|
+
count: capped.length,
|
|
226
|
+
categories: capped.map(c => c.category),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Phase tools factory
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
export function createPhaseTools(engine, contextInjector, vectorStore = null, graphStore = null) {
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// forge.start_interview
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
async function startInterview(input) {
|
|
238
|
+
try {
|
|
239
|
+
const result = engine.startPhase(input.project_id, 'interview', 'strategist');
|
|
240
|
+
if ('error' in result) {
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
// Inject context
|
|
244
|
+
let context;
|
|
245
|
+
try {
|
|
246
|
+
context = await contextInjector.injectInterviewContext(input.project_id, input.initial_request);
|
|
247
|
+
}
|
|
248
|
+
catch (ctxErr) {
|
|
249
|
+
logger.warn('start_interview: context injection failed', { error: String(ctxErr) });
|
|
250
|
+
context = {
|
|
251
|
+
similarProjects: [],
|
|
252
|
+
relevantGotchas: [],
|
|
253
|
+
interviewTemplate: DEFAULT_INTERVIEW_TEMPLATE,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
project_id: input.project_id,
|
|
258
|
+
phase: 'interview',
|
|
259
|
+
similar_projects: context.similarProjects,
|
|
260
|
+
relevant_gotchas: context.relevantGotchas.map(g => ({
|
|
261
|
+
title: g.title,
|
|
262
|
+
content: g.content,
|
|
263
|
+
confidence: g.confidence,
|
|
264
|
+
source_repo: g.sourceRepo,
|
|
265
|
+
})),
|
|
266
|
+
interview_template: context.interviewTemplate,
|
|
267
|
+
trajectory_id: result.trajectoryId,
|
|
268
|
+
available_transitions: result.availableTransitions,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
logger.error('start_interview: error', { error: String(err) });
|
|
273
|
+
return { error: 'START_INTERVIEW_FAILED', message: String(err) };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
// forge.submit_vision
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
async function submitVision(input) {
|
|
280
|
+
try {
|
|
281
|
+
const project = engine.getProject(input.project_id);
|
|
282
|
+
if (!project) {
|
|
283
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
284
|
+
}
|
|
285
|
+
if (asPhase(project.currentPhase) !== 'interview') {
|
|
286
|
+
return {
|
|
287
|
+
error: 'INVALID_PHASE',
|
|
288
|
+
current_phase: project.currentPhase,
|
|
289
|
+
required_phase: 'interview',
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Write to disk if requested
|
|
293
|
+
if (input.file_path) {
|
|
294
|
+
try {
|
|
295
|
+
writeToDisk(input.vision, input.file_path);
|
|
296
|
+
}
|
|
297
|
+
catch { }
|
|
298
|
+
}
|
|
299
|
+
// Get active trajectory
|
|
300
|
+
const trajectory = engine.getActiveTrajectory(input.project_id, 'interview');
|
|
301
|
+
const { outputId } = engine.completePhase(input.project_id, 'interview', 'vision', input.vision, {
|
|
302
|
+
agent: 'strategist',
|
|
303
|
+
filePath: input.file_path,
|
|
304
|
+
advanceTo: 'architecture',
|
|
305
|
+
trajectoryId: trajectory?.id,
|
|
306
|
+
});
|
|
307
|
+
const knowledgeExtracted = extractKnowledgeCandidates(input.vision);
|
|
308
|
+
// Fire-and-forget auto-observation
|
|
309
|
+
autoObserve(input.vision, {
|
|
310
|
+
category: 'vision',
|
|
311
|
+
importance: 0.6,
|
|
312
|
+
projectId: input.project_id,
|
|
313
|
+
phase: 'vision',
|
|
314
|
+
vectorStore,
|
|
315
|
+
graphStore,
|
|
316
|
+
});
|
|
317
|
+
autoExtractKnowledge(input.vision, {
|
|
318
|
+
projectId: input.project_id,
|
|
319
|
+
phase: 'interview',
|
|
320
|
+
vectorStore,
|
|
321
|
+
graphStore,
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
project_id: input.project_id,
|
|
325
|
+
phase: 'architecture',
|
|
326
|
+
vision_stored: true,
|
|
327
|
+
knowledge_extracted: knowledgeExtracted,
|
|
328
|
+
trajectory_step_recorded: !!trajectory,
|
|
329
|
+
next_agents: ['architect'],
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
logger.error('submit_vision: error', { error: String(err) });
|
|
334
|
+
return { error: 'SUBMIT_VISION_FAILED', message: String(err) };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
// forge.start_architecture
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
async function startArchitecture(input) {
|
|
341
|
+
try {
|
|
342
|
+
const result = engine.startPhase(input.project_id, 'architecture', 'architect');
|
|
343
|
+
if ('error' in result) {
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
let context;
|
|
347
|
+
try {
|
|
348
|
+
context = await contextInjector.injectArchitectureContext(input.project_id, input.vision_summary);
|
|
349
|
+
}
|
|
350
|
+
catch (ctxErr) {
|
|
351
|
+
logger.warn('start_architecture: context injection failed', { error: String(ctxErr) });
|
|
352
|
+
context = {
|
|
353
|
+
codebaseContext: { modules: [], patterns: [], dependencies: [], fileCount: 0 },
|
|
354
|
+
relevantDecisions: [],
|
|
355
|
+
relevantGotchas: [],
|
|
356
|
+
stackConstraints: [],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
project_id: input.project_id,
|
|
361
|
+
phase: 'architecture',
|
|
362
|
+
codebase_context: {
|
|
363
|
+
modules: context.codebaseContext.modules,
|
|
364
|
+
patterns: context.codebaseContext.patterns,
|
|
365
|
+
dependencies: context.codebaseContext.dependencies,
|
|
366
|
+
file_count: context.codebaseContext.fileCount,
|
|
367
|
+
language_breakdown: {},
|
|
368
|
+
},
|
|
369
|
+
relevant_decisions: context.relevantDecisions.map(d => ({
|
|
370
|
+
title: d.title,
|
|
371
|
+
content: d.content,
|
|
372
|
+
confidence: d.confidence,
|
|
373
|
+
source_repo: d.sourceRepo,
|
|
374
|
+
})),
|
|
375
|
+
relevant_gotchas: context.relevantGotchas.map(g => ({
|
|
376
|
+
title: g.title,
|
|
377
|
+
content: g.content,
|
|
378
|
+
stack_tags: g.stackTags,
|
|
379
|
+
confidence: g.confidence,
|
|
380
|
+
})),
|
|
381
|
+
stack_constraints: context.stackConstraints,
|
|
382
|
+
trajectory_id: result.trajectoryId,
|
|
383
|
+
available_transitions: result.availableTransitions,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
catch (err) {
|
|
387
|
+
logger.error('start_architecture: error', { error: String(err) });
|
|
388
|
+
return { error: 'START_ARCHITECTURE_FAILED', message: String(err) };
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
// forge.submit_plan
|
|
393
|
+
// ---------------------------------------------------------------------------
|
|
394
|
+
async function submitPlan(input) {
|
|
395
|
+
try {
|
|
396
|
+
const project = engine.getProject(input.project_id);
|
|
397
|
+
if (!project) {
|
|
398
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
399
|
+
}
|
|
400
|
+
// Accept submission when current phase is architecture OR design (parallel pair)
|
|
401
|
+
const curPhase = asPhase(project.currentPhase);
|
|
402
|
+
if (curPhase !== 'architecture' && curPhase !== 'design') {
|
|
403
|
+
return {
|
|
404
|
+
error: 'INVALID_PHASE',
|
|
405
|
+
current_phase: project.currentPhase,
|
|
406
|
+
required_phase: 'architecture',
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (input.file_path) {
|
|
410
|
+
try {
|
|
411
|
+
writeToDisk(input.plan, input.file_path);
|
|
412
|
+
}
|
|
413
|
+
catch { }
|
|
414
|
+
}
|
|
415
|
+
const trajectory = engine.getActiveTrajectory(input.project_id, 'architecture');
|
|
416
|
+
// Determine next phase based on flags and whether parallel design is done
|
|
417
|
+
let nextPhase = 'implementation';
|
|
418
|
+
const parallelPhases = [];
|
|
419
|
+
const designOutput = engine.getPhaseOutput(input.project_id, 'design');
|
|
420
|
+
if (input.requires_designer && !designOutput) {
|
|
421
|
+
// Designer hasn't finished yet — advance to design so it can still submit
|
|
422
|
+
nextPhase = 'design';
|
|
423
|
+
parallelPhases.push('design');
|
|
424
|
+
if (input.requires_qa)
|
|
425
|
+
parallelPhases.push('qa_strategy');
|
|
426
|
+
}
|
|
427
|
+
else if (input.requires_qa) {
|
|
428
|
+
nextPhase = 'qa_strategy';
|
|
429
|
+
parallelPhases.push('qa_strategy');
|
|
430
|
+
}
|
|
431
|
+
// else: design already done (or not needed) → advance to implementation
|
|
432
|
+
engine.completePhase(input.project_id, 'architecture', 'architecture', input.plan, {
|
|
433
|
+
agent: 'architect',
|
|
434
|
+
filePath: input.file_path,
|
|
435
|
+
advanceTo: nextPhase,
|
|
436
|
+
trajectoryId: trajectory?.id,
|
|
437
|
+
metadata: {
|
|
438
|
+
modules: input.modules,
|
|
439
|
+
requires_designer: input.requires_designer,
|
|
440
|
+
requires_qa: input.requires_qa,
|
|
441
|
+
},
|
|
442
|
+
});
|
|
443
|
+
const knowledgeExtracted = extractKnowledgeCandidates(input.plan);
|
|
444
|
+
// Fire-and-forget auto-observation
|
|
445
|
+
autoObserve(input.plan, {
|
|
446
|
+
category: 'architecture',
|
|
447
|
+
importance: 0.6,
|
|
448
|
+
projectId: input.project_id,
|
|
449
|
+
phase: 'architecture',
|
|
450
|
+
vectorStore,
|
|
451
|
+
graphStore,
|
|
452
|
+
});
|
|
453
|
+
autoExtractKnowledge(input.plan, {
|
|
454
|
+
projectId: input.project_id,
|
|
455
|
+
phase: 'architecture',
|
|
456
|
+
vectorStore,
|
|
457
|
+
graphStore,
|
|
458
|
+
});
|
|
459
|
+
return {
|
|
460
|
+
project_id: input.project_id,
|
|
461
|
+
phase: nextPhase,
|
|
462
|
+
plan_stored: true,
|
|
463
|
+
knowledge_extracted: knowledgeExtracted,
|
|
464
|
+
parallel_phases: parallelPhases.length > 0 ? parallelPhases : undefined,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
logger.error('submit_plan: error', { error: String(err) });
|
|
469
|
+
return { error: 'SUBMIT_PLAN_FAILED', message: String(err) };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
// ---------------------------------------------------------------------------
|
|
473
|
+
// forge.start_design
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
async function startDesign(input) {
|
|
476
|
+
try {
|
|
477
|
+
const result = engine.startPhase(input.project_id, 'design', 'designer');
|
|
478
|
+
if ('error' in result) {
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
// Search for design patterns
|
|
482
|
+
let relevantPatterns = [];
|
|
483
|
+
try {
|
|
484
|
+
const ctx = await contextInjector.injectPhaseContext(input.project_id, 'design', `design ${input.focus ?? ''}`.trim());
|
|
485
|
+
relevantPatterns = ctx.relevantKnowledge
|
|
486
|
+
.filter(k => k.category === 'pattern' || k.category === 'convention')
|
|
487
|
+
.map(k => ({ title: k.title, content: k.content, source_repo: 'unknown' }));
|
|
488
|
+
}
|
|
489
|
+
catch (ctxErr) {
|
|
490
|
+
logger.warn('start_design: context injection failed', { error: String(ctxErr) });
|
|
491
|
+
}
|
|
492
|
+
// Detect existing design system
|
|
493
|
+
const existingDesignSystem = await detectDesignSystem(input.project_id, engine);
|
|
494
|
+
return {
|
|
495
|
+
project_id: input.project_id,
|
|
496
|
+
phase: 'design',
|
|
497
|
+
existing_design_system: existingDesignSystem,
|
|
498
|
+
relevant_patterns: relevantPatterns,
|
|
499
|
+
trajectory_id: result.trajectoryId,
|
|
500
|
+
available_transitions: result.availableTransitions,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
logger.error('start_design: error', { error: String(err) });
|
|
505
|
+
return { error: 'START_DESIGN_FAILED', message: String(err) };
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
// forge.submit_design
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
async function submitDesign(input) {
|
|
512
|
+
try {
|
|
513
|
+
const project = engine.getProject(input.project_id);
|
|
514
|
+
if (!project) {
|
|
515
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
516
|
+
}
|
|
517
|
+
// Accept submission when current phase is design OR architecture (parallel pair)
|
|
518
|
+
const curPhase = asPhase(project.currentPhase);
|
|
519
|
+
if (curPhase !== 'design' && curPhase !== 'architecture') {
|
|
520
|
+
return {
|
|
521
|
+
error: 'INVALID_PHASE',
|
|
522
|
+
current_phase: project.currentPhase,
|
|
523
|
+
required_phase: 'design',
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (input.file_path) {
|
|
527
|
+
try {
|
|
528
|
+
writeToDisk(input.design, input.file_path);
|
|
529
|
+
}
|
|
530
|
+
catch { }
|
|
531
|
+
}
|
|
532
|
+
const trajectory = engine.getActiveTrajectory(input.project_id, 'design');
|
|
533
|
+
// Only advance past design if the parallel architecture phase is also done.
|
|
534
|
+
// If the architect hasn't submitted yet, persist the output but don't advance —
|
|
535
|
+
// the architect's submit_plan will handle advancement once both are done.
|
|
536
|
+
const archOutput = engine.getPhaseOutput(input.project_id, 'architecture');
|
|
537
|
+
let advanceTo;
|
|
538
|
+
if (archOutput) {
|
|
539
|
+
// Both phases done — determine next phase from architecture metadata
|
|
540
|
+
advanceTo = 'implementation';
|
|
541
|
+
const archMeta = engine.getPhaseMetadata(input.project_id, 'architecture');
|
|
542
|
+
if (archMeta?.requires_qa)
|
|
543
|
+
advanceTo = 'qa_strategy';
|
|
544
|
+
}
|
|
545
|
+
// else: architect hasn't submitted yet — don't advance
|
|
546
|
+
engine.completePhase(input.project_id, 'design', 'xd_plan', input.design, {
|
|
547
|
+
agent: 'designer',
|
|
548
|
+
filePath: input.file_path,
|
|
549
|
+
advanceTo,
|
|
550
|
+
trajectoryId: trajectory?.id,
|
|
551
|
+
});
|
|
552
|
+
const knowledgeExtracted = extractKnowledgeCandidates(input.design);
|
|
553
|
+
// Fire-and-forget auto-observation
|
|
554
|
+
autoObserve(input.design, {
|
|
555
|
+
category: 'design',
|
|
556
|
+
importance: 0.5,
|
|
557
|
+
projectId: input.project_id,
|
|
558
|
+
phase: 'design',
|
|
559
|
+
vectorStore,
|
|
560
|
+
graphStore,
|
|
561
|
+
});
|
|
562
|
+
autoExtractKnowledge(input.design, {
|
|
563
|
+
projectId: input.project_id,
|
|
564
|
+
phase: 'design',
|
|
565
|
+
vectorStore,
|
|
566
|
+
graphStore,
|
|
567
|
+
});
|
|
568
|
+
return {
|
|
569
|
+
project_id: input.project_id,
|
|
570
|
+
phase: advanceTo ?? curPhase,
|
|
571
|
+
design_stored: true,
|
|
572
|
+
waiting_for: archOutput ? undefined : 'architecture',
|
|
573
|
+
knowledge_extracted: knowledgeExtracted,
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
logger.error('submit_design: error', { error: String(err) });
|
|
578
|
+
return { error: 'SUBMIT_DESIGN_FAILED', message: String(err) };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// ---------------------------------------------------------------------------
|
|
582
|
+
// forge.start_qa_strategy
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
async function startQAStrategy(input) {
|
|
585
|
+
try {
|
|
586
|
+
const result = engine.startPhase(input.project_id, 'qa_strategy', 'qa-strategist');
|
|
587
|
+
if ('error' in result) {
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
// Get architecture and design summaries from phase outputs
|
|
591
|
+
const archOutput = engine.getPhaseOutput(input.project_id, 'architecture');
|
|
592
|
+
const designOutput = engine.getPhaseOutput(input.project_id, 'design');
|
|
593
|
+
const architectureSummary = archOutput
|
|
594
|
+
? archOutput.content.slice(0, 1000)
|
|
595
|
+
: 'No architecture plan available.';
|
|
596
|
+
const designSummary = designOutput
|
|
597
|
+
? designOutput.content.slice(0, 500)
|
|
598
|
+
: undefined;
|
|
599
|
+
// Search for past test failures
|
|
600
|
+
let pastTestFailures = [];
|
|
601
|
+
try {
|
|
602
|
+
const ctx = await contextInjector.injectPhaseContext(input.project_id, 'qa_strategy', 'test failures bugs quality issues');
|
|
603
|
+
pastTestFailures = ctx.relevantKnowledge
|
|
604
|
+
.filter(k => k.category === 'gotcha')
|
|
605
|
+
.map(k => ({
|
|
606
|
+
description: k.title,
|
|
607
|
+
resolution: k.content,
|
|
608
|
+
source_repo: 'unknown',
|
|
609
|
+
}));
|
|
610
|
+
}
|
|
611
|
+
catch (ctxErr) {
|
|
612
|
+
logger.warn('start_qa_strategy: context injection failed', { error: String(ctxErr) });
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
project_id: input.project_id,
|
|
616
|
+
phase: 'qa_strategy',
|
|
617
|
+
architecture_summary: architectureSummary,
|
|
618
|
+
design_summary: designSummary,
|
|
619
|
+
past_test_failures: pastTestFailures,
|
|
620
|
+
trajectory_id: result.trajectoryId,
|
|
621
|
+
available_transitions: result.availableTransitions,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
catch (err) {
|
|
625
|
+
logger.error('start_qa_strategy: error', { error: String(err) });
|
|
626
|
+
return { error: 'START_QA_STRATEGY_FAILED', message: String(err) };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// ---------------------------------------------------------------------------
|
|
630
|
+
// forge.submit_test_plan
|
|
631
|
+
// ---------------------------------------------------------------------------
|
|
632
|
+
async function submitTestPlan(input) {
|
|
633
|
+
try {
|
|
634
|
+
const project = engine.getProject(input.project_id);
|
|
635
|
+
if (!project) {
|
|
636
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
637
|
+
}
|
|
638
|
+
if (asPhase(project.currentPhase) !== 'qa_strategy') {
|
|
639
|
+
return {
|
|
640
|
+
error: 'INVALID_PHASE',
|
|
641
|
+
current_phase: project.currentPhase,
|
|
642
|
+
required_phase: 'qa_strategy',
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (input.file_path) {
|
|
646
|
+
try {
|
|
647
|
+
writeToDisk(input.test_plan, input.file_path);
|
|
648
|
+
}
|
|
649
|
+
catch { }
|
|
650
|
+
}
|
|
651
|
+
const trajectory = engine.getActiveTrajectory(input.project_id, 'qa_strategy');
|
|
652
|
+
engine.completePhase(input.project_id, 'qa_strategy', 'test_plan', input.test_plan, {
|
|
653
|
+
agent: 'qa-strategist',
|
|
654
|
+
filePath: input.file_path,
|
|
655
|
+
advanceTo: 'implementation',
|
|
656
|
+
trajectoryId: trajectory?.id,
|
|
657
|
+
});
|
|
658
|
+
const knowledgeExtracted = extractKnowledgeCandidates(input.test_plan);
|
|
659
|
+
// Fire-and-forget auto-observation
|
|
660
|
+
autoObserve(input.test_plan, {
|
|
661
|
+
category: 'test_plan',
|
|
662
|
+
importance: 0.5,
|
|
663
|
+
projectId: input.project_id,
|
|
664
|
+
phase: 'qa_strategy',
|
|
665
|
+
vectorStore,
|
|
666
|
+
graphStore,
|
|
667
|
+
});
|
|
668
|
+
autoExtractKnowledge(input.test_plan, {
|
|
669
|
+
projectId: input.project_id,
|
|
670
|
+
phase: 'qa_strategy',
|
|
671
|
+
vectorStore,
|
|
672
|
+
graphStore,
|
|
673
|
+
});
|
|
674
|
+
return {
|
|
675
|
+
project_id: input.project_id,
|
|
676
|
+
phase: 'implementation',
|
|
677
|
+
test_plan_stored: true,
|
|
678
|
+
knowledge_extracted: knowledgeExtracted,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
logger.error('submit_test_plan: error', { error: String(err) });
|
|
683
|
+
return { error: 'SUBMIT_TEST_PLAN_FAILED', message: String(err) };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// ---------------------------------------------------------------------------
|
|
687
|
+
// forge.start_implementation
|
|
688
|
+
// ---------------------------------------------------------------------------
|
|
689
|
+
async function startImplementation(input) {
|
|
690
|
+
try {
|
|
691
|
+
const result = engine.startPhase(input.project_id, 'implementation', 'supervisor');
|
|
692
|
+
if ('error' in result) {
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
// Create claims for each module
|
|
696
|
+
const claims = input.modules.map(mod => {
|
|
697
|
+
const claim = engine.createClaim(input.project_id, mod.name, mod.agent_type);
|
|
698
|
+
return {
|
|
699
|
+
claim_id: claim.id,
|
|
700
|
+
module: mod.name,
|
|
701
|
+
agent_type: mod.agent_type,
|
|
702
|
+
status: 'claimed',
|
|
703
|
+
};
|
|
704
|
+
});
|
|
705
|
+
// Inject per-module context
|
|
706
|
+
const moduleContexts = {};
|
|
707
|
+
let injectedContexts = {};
|
|
708
|
+
try {
|
|
709
|
+
injectedContexts = await contextInjector.injectImplementationContext(input.project_id, input.modules.map(m => ({ name: m.name, agentType: m.agent_type, description: m.description })));
|
|
710
|
+
}
|
|
711
|
+
catch (ctxErr) {
|
|
712
|
+
logger.warn('start_implementation: context injection failed', { error: String(ctxErr) });
|
|
713
|
+
}
|
|
714
|
+
for (const mod of input.modules) {
|
|
715
|
+
const ctx = injectedContexts[mod.name];
|
|
716
|
+
moduleContexts[mod.name] = {
|
|
717
|
+
relevant_code: ctx?.relevantCode ?? '',
|
|
718
|
+
gotchas: ctx?.gotchas ?? [],
|
|
719
|
+
conventions: ctx?.conventions ?? [],
|
|
720
|
+
recent_broadcasts: ctx?.recentBroadcasts ?? [],
|
|
721
|
+
sibling_modules: ctx?.siblingModules ?? [],
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
return {
|
|
725
|
+
project_id: input.project_id,
|
|
726
|
+
phase: 'implementation',
|
|
727
|
+
claims,
|
|
728
|
+
module_contexts: moduleContexts,
|
|
729
|
+
trajectory_id: result.trajectoryId,
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
catch (err) {
|
|
733
|
+
logger.error('start_implementation: error', { error: String(err) });
|
|
734
|
+
return { error: 'START_IMPLEMENTATION_FAILED', message: String(err) };
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// ---------------------------------------------------------------------------
|
|
738
|
+
// forge.submit_implementation
|
|
739
|
+
// ---------------------------------------------------------------------------
|
|
740
|
+
async function submitImplementation(input) {
|
|
741
|
+
try {
|
|
742
|
+
const project = engine.getProject(input.project_id);
|
|
743
|
+
if (!project) {
|
|
744
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
745
|
+
}
|
|
746
|
+
if (asPhase(project.currentPhase) !== 'implementation') {
|
|
747
|
+
return {
|
|
748
|
+
error: 'INVALID_PHASE',
|
|
749
|
+
current_phase: project.currentPhase,
|
|
750
|
+
required_phase: 'implementation',
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
// Find the claim for this module
|
|
754
|
+
const claim = engine.getClaimByModule(input.project_id, input.module);
|
|
755
|
+
if (!claim) {
|
|
756
|
+
return {
|
|
757
|
+
error: 'CLAIM_NOT_FOUND',
|
|
758
|
+
module: input.module,
|
|
759
|
+
message: 'No active claim found for this module. Call forge.start_implementation first.',
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
const summaryContent = JSON.stringify({
|
|
763
|
+
module: input.module,
|
|
764
|
+
branch: input.branch,
|
|
765
|
+
summary: input.summary,
|
|
766
|
+
files_changed: input.files_changed,
|
|
767
|
+
});
|
|
768
|
+
const { remainingCount, allComplete } = engine.completeClaim(claim.id, summaryContent);
|
|
769
|
+
// Record in phase_outputs
|
|
770
|
+
engine.completePhase(input.project_id, 'implementation', 'code', summaryContent, {
|
|
771
|
+
agent: claim.agent,
|
|
772
|
+
advanceTo: allComplete ? 'inspection' : undefined,
|
|
773
|
+
metadata: {
|
|
774
|
+
module: input.module,
|
|
775
|
+
files_changed: input.files_changed,
|
|
776
|
+
},
|
|
777
|
+
});
|
|
778
|
+
const knowledgeExtracted = extractKnowledgeCandidates(input.summary ?? '');
|
|
779
|
+
// Auto-broadcast module completion so sibling agents can see it via both
|
|
780
|
+
// SQLite (get_broadcasts tool) and Qdrant (getRecentBroadcasts in context injection)
|
|
781
|
+
const filesStr = (input.files_changed ?? []).join(', ');
|
|
782
|
+
const broadcastContent = `MODULE COMPLETED: ${input.module}\nFILES: ${filesStr}\nSUMMARY: ${input.summary ?? 'no summary'}`;
|
|
783
|
+
try {
|
|
784
|
+
engine.recordBroadcast(input.project_id, broadcastContent, 'info');
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
logger.debug('submitImplementation: auto-broadcast (sqlite) failed (non-fatal)', { error: String(err) });
|
|
788
|
+
}
|
|
789
|
+
// Fire-and-forget: save as agent_broadcast observation (Qdrant) so
|
|
790
|
+
// getRecentBroadcasts picks it up for sibling context injection
|
|
791
|
+
autoObserve(broadcastContent, {
|
|
792
|
+
category: 'agent_broadcast',
|
|
793
|
+
importance: 0.5,
|
|
794
|
+
projectId: input.project_id,
|
|
795
|
+
phase: 'implementation',
|
|
796
|
+
vectorStore,
|
|
797
|
+
graphStore,
|
|
798
|
+
});
|
|
799
|
+
autoExtractKnowledge(input.summary ?? '', {
|
|
800
|
+
projectId: input.project_id,
|
|
801
|
+
phase: 'implementation',
|
|
802
|
+
vectorStore,
|
|
803
|
+
graphStore,
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
project_id: input.project_id,
|
|
807
|
+
module: input.module,
|
|
808
|
+
claim_status: 'completed',
|
|
809
|
+
remaining_claims: remainingCount,
|
|
810
|
+
all_complete: allComplete,
|
|
811
|
+
phase: allComplete ? 'inspection' : 'implementation',
|
|
812
|
+
knowledge_extracted: knowledgeExtracted,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
catch (err) {
|
|
816
|
+
logger.error('submit_implementation: error', { error: String(err) });
|
|
817
|
+
return { error: 'SUBMIT_IMPLEMENTATION_FAILED', message: String(err) };
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
// ---------------------------------------------------------------------------
|
|
821
|
+
// forge.start_inspection
|
|
822
|
+
// ---------------------------------------------------------------------------
|
|
823
|
+
async function startInspection(input) {
|
|
824
|
+
try {
|
|
825
|
+
const result = engine.startPhase(input.project_id, 'inspection', 'inspector');
|
|
826
|
+
if ('error' in result) {
|
|
827
|
+
return result;
|
|
828
|
+
}
|
|
829
|
+
const cycleCounts = engine.getCycleCounts(input.project_id);
|
|
830
|
+
const cycleCount = cycleCounts['inspection_to_implementation'] ?? 0;
|
|
831
|
+
const maxCycles = 3;
|
|
832
|
+
// Get arch and test plan for checklist generation
|
|
833
|
+
const archOutput = engine.getPhaseOutput(input.project_id, 'architecture');
|
|
834
|
+
const testPlanOutput = engine.getPhaseOutput(input.project_id, 'qa_strategy');
|
|
835
|
+
const checklist = buildInspectionChecklist(archOutput?.content ?? '', testPlanOutput?.content ?? '');
|
|
836
|
+
// Search for past inspection findings and implementation broadcasts
|
|
837
|
+
let pastFindings = [];
|
|
838
|
+
let implementationBroadcasts = [];
|
|
839
|
+
try {
|
|
840
|
+
const ctx = await contextInjector.injectInspectionContext(input.project_id);
|
|
841
|
+
pastFindings = ctx.pastFindings.map(f => ({
|
|
842
|
+
description: f.description,
|
|
843
|
+
severity: f.severity,
|
|
844
|
+
resolution: f.resolution,
|
|
845
|
+
source_repo: f.sourceRepo,
|
|
846
|
+
}));
|
|
847
|
+
implementationBroadcasts = ctx.broadcasts;
|
|
848
|
+
}
|
|
849
|
+
catch (ctxErr) {
|
|
850
|
+
logger.warn('start_inspection: context injection failed', { error: String(ctxErr) });
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
project_id: input.project_id,
|
|
854
|
+
phase: 'inspection',
|
|
855
|
+
checklist,
|
|
856
|
+
past_findings: pastFindings,
|
|
857
|
+
implementation_broadcasts: implementationBroadcasts,
|
|
858
|
+
anti_stub_patterns: ANTI_STUB_PATTERNS,
|
|
859
|
+
cycle_count: cycleCount,
|
|
860
|
+
max_cycles: maxCycles,
|
|
861
|
+
trajectory_id: result.trajectoryId,
|
|
862
|
+
available_transitions: result.availableTransitions,
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
catch (err) {
|
|
866
|
+
logger.error('start_inspection: error', { error: String(err) });
|
|
867
|
+
return { error: 'START_INSPECTION_FAILED', message: String(err) };
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
// ---------------------------------------------------------------------------
|
|
871
|
+
// forge.submit_verdict
|
|
872
|
+
// ---------------------------------------------------------------------------
|
|
873
|
+
async function submitVerdict(input) {
|
|
874
|
+
try {
|
|
875
|
+
const project = engine.getProject(input.project_id);
|
|
876
|
+
if (!project) {
|
|
877
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
878
|
+
}
|
|
879
|
+
if (asPhase(project.currentPhase) !== 'inspection') {
|
|
880
|
+
return {
|
|
881
|
+
error: 'INVALID_PHASE',
|
|
882
|
+
current_phase: project.currentPhase,
|
|
883
|
+
required_phase: 'inspection',
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const trajectory = engine.getActiveTrajectory(input.project_id, 'inspection');
|
|
887
|
+
const cycleCounts = engine.getCycleCounts(input.project_id);
|
|
888
|
+
const cycleCount = cycleCounts['inspection_to_implementation'] ?? 0;
|
|
889
|
+
const isPassing = input.verdict === 'pass' || input.verdict === 'pass_with_warnings';
|
|
890
|
+
const nextPhase = isPassing ? 'knowledge_collection' : 'implementation';
|
|
891
|
+
const verdictContent = JSON.stringify({
|
|
892
|
+
verdict: input.verdict,
|
|
893
|
+
findings: input.findings ?? [],
|
|
894
|
+
summary: input.summary,
|
|
895
|
+
});
|
|
896
|
+
engine.completePhase(input.project_id, 'inspection', 'verdict', verdictContent, {
|
|
897
|
+
agent: 'inspector',
|
|
898
|
+
advanceTo: nextPhase,
|
|
899
|
+
trajectoryId: trajectory?.id,
|
|
900
|
+
qualityScore: isPassing ? 1.0 : 0.0,
|
|
901
|
+
metadata: {
|
|
902
|
+
verdict: input.verdict,
|
|
903
|
+
findings_count: input.findings?.length ?? 0,
|
|
904
|
+
},
|
|
905
|
+
});
|
|
906
|
+
if (trajectory) {
|
|
907
|
+
engine.completeTrajectory(trajectory.id, isPassing, input.summary);
|
|
908
|
+
}
|
|
909
|
+
const findingsSummary = (input.findings ?? []).map(f => f.description).join('\n');
|
|
910
|
+
const knowledgeExtracted = extractKnowledgeCandidates(findingsSummary);
|
|
911
|
+
// Fire-and-forget auto-observation
|
|
912
|
+
autoObserve(`Verdict: ${input.verdict}. ${input.summary ?? ''}\n${findingsSummary}`.trim(), {
|
|
913
|
+
category: 'inspection',
|
|
914
|
+
importance: 0.7,
|
|
915
|
+
projectId: input.project_id,
|
|
916
|
+
phase: 'inspection',
|
|
917
|
+
vectorStore,
|
|
918
|
+
graphStore,
|
|
919
|
+
});
|
|
920
|
+
autoExtractKnowledge(input.summary ?? '', {
|
|
921
|
+
projectId: input.project_id,
|
|
922
|
+
phase: 'inspection',
|
|
923
|
+
vectorStore,
|
|
924
|
+
graphStore,
|
|
925
|
+
});
|
|
926
|
+
const response = {
|
|
927
|
+
project_id: input.project_id,
|
|
928
|
+
verdict: input.verdict,
|
|
929
|
+
phase: nextPhase,
|
|
930
|
+
cycle_count: cycleCount,
|
|
931
|
+
knowledge_extracted: knowledgeExtracted,
|
|
932
|
+
};
|
|
933
|
+
if (!isPassing && input.findings && input.findings.length > 0) {
|
|
934
|
+
const criticalFindings = input.findings
|
|
935
|
+
.filter(f => f.severity === 'critical')
|
|
936
|
+
.map(f => `- ${f.description}${f.file_path ? ` (${f.file_path})` : ''}`)
|
|
937
|
+
.join('\n');
|
|
938
|
+
response['fix_instructions'] =
|
|
939
|
+
`## Required Fixes (${input.findings.filter(f => f.severity === 'critical').length} critical)\n\n${criticalFindings}`;
|
|
940
|
+
}
|
|
941
|
+
return response;
|
|
942
|
+
}
|
|
943
|
+
catch (err) {
|
|
944
|
+
logger.error('submit_verdict: error', { error: String(err) });
|
|
945
|
+
return { error: 'SUBMIT_VERDICT_FAILED', message: String(err) };
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
// ---------------------------------------------------------------------------
|
|
949
|
+
// forge.collect_knowledge
|
|
950
|
+
// ---------------------------------------------------------------------------
|
|
951
|
+
async function collectKnowledge(input) {
|
|
952
|
+
try {
|
|
953
|
+
const project = engine.getProject(input.project_id);
|
|
954
|
+
if (!project) {
|
|
955
|
+
return { error: 'PROJECT_NOT_FOUND', project_id: input.project_id };
|
|
956
|
+
}
|
|
957
|
+
if (asPhase(project.currentPhase) !== 'knowledge_collection') {
|
|
958
|
+
return {
|
|
959
|
+
error: 'INVALID_PHASE',
|
|
960
|
+
current_phase: project.currentPhase,
|
|
961
|
+
required_phase: 'knowledge_collection',
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
// Collect all phase outputs to extract knowledge
|
|
965
|
+
const allOutputs = engine.getAllPhaseOutputs(input.project_id);
|
|
966
|
+
const { history } = engine.getHistory(input.project_id);
|
|
967
|
+
const knowledgeItems = [];
|
|
968
|
+
// Extract from architecture plan
|
|
969
|
+
const archOutput = allOutputs.find(o => o.outputType === 'architecture');
|
|
970
|
+
if (archOutput) {
|
|
971
|
+
const candidates = extractStructuredKnowledge(archOutput.content, 'architecture');
|
|
972
|
+
knowledgeItems.push(...candidates);
|
|
973
|
+
}
|
|
974
|
+
// Extract from verdict findings
|
|
975
|
+
const verdictOutput = allOutputs.find(o => o.outputType === 'verdict');
|
|
976
|
+
if (verdictOutput) {
|
|
977
|
+
try {
|
|
978
|
+
const verdict = JSON.parse(verdictOutput.content);
|
|
979
|
+
for (const finding of verdict.findings ?? []) {
|
|
980
|
+
knowledgeItems.push({
|
|
981
|
+
category: 'gotcha',
|
|
982
|
+
title: `Inspection finding: ${finding.description.slice(0, 60)}`,
|
|
983
|
+
content: finding.description,
|
|
984
|
+
confidence: finding.severity === 'critical' ? 0.8 : 0.5,
|
|
985
|
+
destination: '.forge/knowledge/gotchas.yaml',
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
catch { }
|
|
990
|
+
}
|
|
991
|
+
// Advance to completed
|
|
992
|
+
const trajectory = engine.getActiveTrajectory(input.project_id, 'knowledge_collection');
|
|
993
|
+
engine.completePhase(input.project_id, 'knowledge_collection', 'knowledge', JSON.stringify(knowledgeItems), {
|
|
994
|
+
agent: 'knowledge-keeper',
|
|
995
|
+
advanceTo: 'completed',
|
|
996
|
+
trajectoryId: trajectory?.id,
|
|
997
|
+
});
|
|
998
|
+
if (trajectory) {
|
|
999
|
+
engine.completeTrajectory(trajectory.id, true);
|
|
1000
|
+
}
|
|
1001
|
+
// Compute trajectory summary
|
|
1002
|
+
const startedAt = project.createdAt;
|
|
1003
|
+
const completedAt = Date.now();
|
|
1004
|
+
const durationMinutes = Math.round((completedAt - startedAt) / 60000);
|
|
1005
|
+
return {
|
|
1006
|
+
project_id: input.project_id,
|
|
1007
|
+
phase: 'completed',
|
|
1008
|
+
knowledge_items: knowledgeItems,
|
|
1009
|
+
trajectory_summary: {
|
|
1010
|
+
total_steps: allOutputs.length,
|
|
1011
|
+
phases_completed: history.filter(h => h.completedAt).length,
|
|
1012
|
+
cycles: engine.getCycleCounts(input.project_id),
|
|
1013
|
+
duration_minutes: durationMinutes,
|
|
1014
|
+
},
|
|
1015
|
+
patterns_promoted: knowledgeItems.filter(k => k.category === 'pattern').length,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
catch (err) {
|
|
1019
|
+
logger.error('collect_knowledge: error', { error: String(err) });
|
|
1020
|
+
return { error: 'COLLECT_KNOWLEDGE_FAILED', message: String(err) };
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
// Return all tools
|
|
1024
|
+
return {
|
|
1025
|
+
'start_interview': {
|
|
1026
|
+
schema: startInterviewSchema,
|
|
1027
|
+
description: 'Start the strategist interview phase for a project',
|
|
1028
|
+
handler: startInterview,
|
|
1029
|
+
},
|
|
1030
|
+
'submit_vision': {
|
|
1031
|
+
schema: submitVisionSchema,
|
|
1032
|
+
description: 'Submit the vision document and advance to architecture phase',
|
|
1033
|
+
handler: submitVision,
|
|
1034
|
+
},
|
|
1035
|
+
'start_architecture': {
|
|
1036
|
+
schema: startArchitectureSchema,
|
|
1037
|
+
description: 'Start the architecture phase with auto-injected codebase context',
|
|
1038
|
+
handler: startArchitecture,
|
|
1039
|
+
},
|
|
1040
|
+
'submit_plan': {
|
|
1041
|
+
schema: submitPlanSchema,
|
|
1042
|
+
description: 'Submit the architecture plan and advance to the next phase',
|
|
1043
|
+
handler: submitPlan,
|
|
1044
|
+
},
|
|
1045
|
+
'start_design': {
|
|
1046
|
+
schema: startDesignSchema,
|
|
1047
|
+
description: 'Start the designer phase for XD/UX planning',
|
|
1048
|
+
handler: startDesign,
|
|
1049
|
+
},
|
|
1050
|
+
'submit_design': {
|
|
1051
|
+
schema: submitDesignSchema,
|
|
1052
|
+
description: 'Submit the design plan and advance to qa_strategy or implementation',
|
|
1053
|
+
handler: submitDesign,
|
|
1054
|
+
},
|
|
1055
|
+
'start_qa_strategy': {
|
|
1056
|
+
schema: startQAStrategySchema,
|
|
1057
|
+
description: 'Start the QA strategy phase to produce a test plan',
|
|
1058
|
+
handler: startQAStrategy,
|
|
1059
|
+
},
|
|
1060
|
+
'submit_test_plan': {
|
|
1061
|
+
schema: submitTestPlanSchema,
|
|
1062
|
+
description: 'Submit the test plan and advance to implementation',
|
|
1063
|
+
handler: submitTestPlan,
|
|
1064
|
+
},
|
|
1065
|
+
'start_implementation': {
|
|
1066
|
+
schema: startImplementationSchema,
|
|
1067
|
+
description: 'Start parallel implementation with per-module claims and context',
|
|
1068
|
+
handler: startImplementation,
|
|
1069
|
+
},
|
|
1070
|
+
'submit_implementation': {
|
|
1071
|
+
schema: submitImplementationSchema,
|
|
1072
|
+
description: 'Submit completion of a single implementation module',
|
|
1073
|
+
handler: submitImplementation,
|
|
1074
|
+
},
|
|
1075
|
+
'start_inspection': {
|
|
1076
|
+
schema: startInspectionSchema,
|
|
1077
|
+
description: 'Start the inspection phase with auto-generated checklist',
|
|
1078
|
+
handler: startInspection,
|
|
1079
|
+
},
|
|
1080
|
+
'submit_verdict': {
|
|
1081
|
+
schema: submitVerdictSchema,
|
|
1082
|
+
description: 'Submit inspection verdict: pass advances to knowledge collection, fail returns to implementation',
|
|
1083
|
+
handler: submitVerdict,
|
|
1084
|
+
},
|
|
1085
|
+
'collect_knowledge': {
|
|
1086
|
+
schema: collectKnowledgeSchema,
|
|
1087
|
+
description: 'Final phase: extract learnings from the entire pipeline run',
|
|
1088
|
+
handler: collectKnowledge,
|
|
1089
|
+
},
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
// ---------------------------------------------------------------------------
|
|
1093
|
+
// Static helpers / templates
|
|
1094
|
+
// ---------------------------------------------------------------------------
|
|
1095
|
+
const DEFAULT_INTERVIEW_TEMPLATE = `# Vision Interview
|
|
1096
|
+
|
|
1097
|
+
## 1. What are we building?
|
|
1098
|
+
Describe the feature or system in plain language.
|
|
1099
|
+
|
|
1100
|
+
## 2. Why does it matter?
|
|
1101
|
+
What problem does this solve for users?
|
|
1102
|
+
|
|
1103
|
+
## 3. Success criteria
|
|
1104
|
+
What does "done" look like? How will we know it works?
|
|
1105
|
+
|
|
1106
|
+
## 4. Constraints and non-goals
|
|
1107
|
+
What are we explicitly NOT doing? What technical constraints apply?
|
|
1108
|
+
|
|
1109
|
+
## 5. Key risks
|
|
1110
|
+
What could go wrong? What are the biggest unknowns?
|
|
1111
|
+
|
|
1112
|
+
## 6. Timeline and scope
|
|
1113
|
+
Is this a quick fix, a medium feature, or a major project?
|
|
1114
|
+
`;
|
|
1115
|
+
const ANTI_STUB_PATTERNS = [
|
|
1116
|
+
'TODO: implement later',
|
|
1117
|
+
'return { success: true } without actual logic',
|
|
1118
|
+
'guard that unconditionally returns true',
|
|
1119
|
+
'service method with empty body',
|
|
1120
|
+
'catch block that silently swallows errors',
|
|
1121
|
+
'hardcoded return values instead of database queries',
|
|
1122
|
+
'unregistered providers in module wiring',
|
|
1123
|
+
'missing imports in module declarations',
|
|
1124
|
+
];
|
|
1125
|
+
function buildInspectionChecklist(archPlan, testPlan) {
|
|
1126
|
+
const sections = [
|
|
1127
|
+
'## Inspection Checklist\n',
|
|
1128
|
+
'### Build & Compilation',
|
|
1129
|
+
'- [ ] `tsc --noEmit` passes with no errors',
|
|
1130
|
+
'- [ ] All imports resolve (no unresolved module errors)',
|
|
1131
|
+
'- [ ] No circular dependency warnings\n',
|
|
1132
|
+
'### Wiring & Registration',
|
|
1133
|
+
'- [ ] All providers registered in their modules',
|
|
1134
|
+
'- [ ] All module imports declared',
|
|
1135
|
+
'- [ ] No unregistered injectable services\n',
|
|
1136
|
+
'### Anti-Stub Verification',
|
|
1137
|
+
'- [ ] No TODO comments in production code paths',
|
|
1138
|
+
'- [ ] All service methods execute real logic (no placeholder returns)',
|
|
1139
|
+
'- [ ] All guards perform real authentication checks',
|
|
1140
|
+
'- [ ] All error cases handled with appropriate HTTP exceptions\n',
|
|
1141
|
+
'### Security',
|
|
1142
|
+
'- [ ] No hardcoded secrets or credentials',
|
|
1143
|
+
'- [ ] Authentication applied to protected endpoints',
|
|
1144
|
+
'- [ ] Input validation present on all endpoints\n',
|
|
1145
|
+
];
|
|
1146
|
+
if (archPlan.length > 0) {
|
|
1147
|
+
sections.push('### Architecture Compliance');
|
|
1148
|
+
sections.push('- [ ] Implementation matches the architecture plan');
|
|
1149
|
+
sections.push('- [ ] Module boundaries respected');
|
|
1150
|
+
sections.push('- [ ] Database schema matches design\n');
|
|
1151
|
+
}
|
|
1152
|
+
if (testPlan.length > 0) {
|
|
1153
|
+
sections.push('### Test Coverage');
|
|
1154
|
+
sections.push('- [ ] Critical paths covered by tests');
|
|
1155
|
+
sections.push('- [ ] Tests pass without mocks for integration paths\n');
|
|
1156
|
+
}
|
|
1157
|
+
return sections.join('\n');
|
|
1158
|
+
}
|
|
1159
|
+
function extractStructuredKnowledge(content, phase) {
|
|
1160
|
+
const items = [];
|
|
1161
|
+
// Extract ADR-style decisions
|
|
1162
|
+
const adrPattern = /### ADR[-\s]?\d+:?\s+([^\n]+)\n([\s\S]*?)(?=###|\Z)/gi;
|
|
1163
|
+
let match;
|
|
1164
|
+
while ((match = adrPattern.exec(content)) !== null) {
|
|
1165
|
+
const title = (match[1] ?? '').trim();
|
|
1166
|
+
const body = (match[2] ?? '').trim().slice(0, 500);
|
|
1167
|
+
if (title && body) {
|
|
1168
|
+
items.push({
|
|
1169
|
+
category: 'decision',
|
|
1170
|
+
title: `[${phase}] ${title}`,
|
|
1171
|
+
content: body,
|
|
1172
|
+
confidence: 0.7,
|
|
1173
|
+
destination: '.forge/knowledge/decisions.yaml',
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return items;
|
|
1178
|
+
}
|
|
1179
|
+
async function detectDesignSystem(_projectId, _engine) {
|
|
1180
|
+
// Look for common design system indicators in codebase
|
|
1181
|
+
// This is a best-effort detection — returns not-found if detection fails
|
|
1182
|
+
try {
|
|
1183
|
+
const commonDesignFiles = [
|
|
1184
|
+
'tailwind.config.js',
|
|
1185
|
+
'tailwind.config.ts',
|
|
1186
|
+
'theme.ts',
|
|
1187
|
+
'theme.js',
|
|
1188
|
+
'tokens.css',
|
|
1189
|
+
'design-system',
|
|
1190
|
+
];
|
|
1191
|
+
// Check if any design system files exist in common locations
|
|
1192
|
+
const { existsSync } = await import('node:fs');
|
|
1193
|
+
const foundFiles = commonDesignFiles.filter(f => {
|
|
1194
|
+
try {
|
|
1195
|
+
return existsSync(f);
|
|
1196
|
+
}
|
|
1197
|
+
catch {
|
|
1198
|
+
return false;
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
if (foundFiles.length > 0) {
|
|
1202
|
+
return {
|
|
1203
|
+
found: true,
|
|
1204
|
+
summary: `Design system files detected: ${foundFiles.join(', ')}`,
|
|
1205
|
+
theme_files: foundFiles,
|
|
1206
|
+
component_count: undefined,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
return { found: false };
|
|
1210
|
+
}
|
|
1211
|
+
catch {
|
|
1212
|
+
return { found: false };
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
//# sourceMappingURL=phase-tools.js.map
|