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,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