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