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,1139 @@
1
+ // PipelineEngine — the core orchestration layer for Forge projects.
2
+ //
3
+ // The engine owns all project + phase lifecycle operations:
4
+ // - Creating projects
5
+ // - Phase transition validation and persistence (atomic: event + project update)
6
+ // - Trajectory recording (start/step/complete)
7
+ // - Phase output persistence
8
+ // - Claim lifecycle for parallel implementation dispatch
9
+ // - History reconstruction from event log
10
+ // - Cycle count computation from event log
11
+ //
12
+ // All database writes are synchronous (better-sqlite3). External operations
13
+ // (embedding, Qdrant, knowledge extraction) are the responsibility of MCP tool
14
+ // handlers — this engine knows nothing about them.
15
+
16
+ import { randomUUID } from 'node:crypto';
17
+ import type { PipelineDB } from '../storage/sqlite.js';
18
+ import type {
19
+ Phase,
20
+ PipelinePhase,
21
+ PipelineTier,
22
+ Project,
23
+ ProjectMetadata,
24
+ ProjectRow,
25
+ Trajectory,
26
+ TrajectoryStepMetadata,
27
+ PhaseOutput,
28
+ Claim,
29
+ ClaimStatus,
30
+ ClaimRow,
31
+ PipelineEvent,
32
+ } from '../util/types.js';
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Phase type bridge
36
+ // ---------------------------------------------------------------------------
37
+
38
+ // The storage layer uses PipelinePhase (agent-role names) in the TypeScript
39
+ // interface types, but the actual DB values are Phase (workflow-stage names).
40
+ // These cast helpers make the boundary explicit without scattering `as unknown`.
41
+
42
+ /** Cast a workflow-stage Phase to the PipelinePhase expected by event factories. */
43
+ function pp(phase: Phase): PipelinePhase {
44
+ return phase as unknown as PipelinePhase;
45
+ }
46
+
47
+ /** Cast a PipelinePhase DB value back to a workflow-stage Phase. */
48
+ function wp(phase: PipelinePhase): Phase {
49
+ return phase as unknown as Phase;
50
+ }
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // Parallel phase pairs
54
+ // ---------------------------------------------------------------------------
55
+ // Phases that can run concurrently. When starting a phase whose parallel
56
+ // sibling is (or leads to) the current phase, we allow the start without
57
+ // advancing currentPhase, so both agents can work and submit independently.
58
+
59
+ const PARALLEL_PAIRS: ReadonlyMap<Phase, Phase> = new Map([
60
+ ['architecture', 'design'],
61
+ ['design', 'architecture'],
62
+ ]);
63
+
64
+ /**
65
+ * Check if `toPhase` can be started as a parallel sibling of the current phase.
66
+ * Returns true when:
67
+ * - `toPhase` has a parallel sibling, AND
68
+ * - `fromPhase` is the sibling itself, OR `fromPhase` can validly transition
69
+ * to the sibling (so both can start from the same predecessor).
70
+ */
71
+ function isParallelStart(fromPhase: Phase, toPhase: Phase, tier: PipelineTier): boolean {
72
+ const sibling = PARALLEL_PAIRS.get(toPhase);
73
+ if (!sibling) return false;
74
+ return fromPhase === sibling || isValidTransition(fromPhase, sibling, tier);
75
+ }
76
+ import {
77
+ isValidTransition,
78
+ getAvailableTransitions,
79
+ isCycleTransition,
80
+ getCycleKey,
81
+ canCycle,
82
+ getForcedAdvancePhase,
83
+ } from './state-machine.js';
84
+ import {
85
+ phaseTransitionEvent,
86
+ agentStartedEvent,
87
+ agentCompletedEvent,
88
+ projectCreatedEvent,
89
+ projectCompletedEvent,
90
+ claimCreatedEvent,
91
+ claimCompletedEvent,
92
+ claimFailedEvent,
93
+ eventToParams,
94
+ rowToEvent,
95
+ } from './events.js';
96
+ import { logger } from '../util/logger.js';
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // Public interfaces
100
+ // ---------------------------------------------------------------------------
101
+
102
+ export interface CreateProjectOptions {
103
+ name: string;
104
+ description?: string;
105
+ repoId: string;
106
+ tier?: PipelineTier;
107
+ metadata?: ProjectMetadata;
108
+ }
109
+
110
+ export interface TransitionResult {
111
+ projectId: string;
112
+ fromPhase: Phase;
113
+ toPhase: Phase;
114
+ trajectoryId: string;
115
+ cycleCounts: Record<string, number>;
116
+ availableTransitions: Phase[];
117
+ }
118
+
119
+ /** Full project state snapshot — returned by getState(). */
120
+ export interface EngineState {
121
+ project: Project;
122
+ history: PipelineEvent[];
123
+ activeClaims: Claim[];
124
+ cycleCounts: Record<string, number>;
125
+ availableTransitions: Phase[];
126
+ }
127
+
128
+ export interface ProjectState {
129
+ project: Project;
130
+ activeTrajectory: Trajectory | null;
131
+ cycleCounts: Record<string, number>;
132
+ availableTransitions: Phase[];
133
+ activeClaims: Claim[];
134
+ }
135
+
136
+ export interface PhaseHistory {
137
+ phase: Phase;
138
+ agent: string;
139
+ startedAt: string;
140
+ completedAt?: string;
141
+ success?: boolean;
142
+ }
143
+
144
+ export interface ProjectHistory {
145
+ projectId: string;
146
+ events: PipelineEvent[];
147
+ outputs: PhaseOutput[];
148
+ history: PhaseHistory[];
149
+ }
150
+
151
+ // Typed error class so callers can distinguish engine errors from generic ones.
152
+ export type PipelineEngineErrorCode =
153
+ | 'PROJECT_NOT_FOUND'
154
+ | 'PROJECT_NOT_ACTIVE'
155
+ | 'INVALID_TRANSITION'
156
+ | 'CYCLE_LIMIT_EXCEEDED';
157
+
158
+ export class PipelineEngineError extends Error {
159
+ readonly code: PipelineEngineErrorCode;
160
+ readonly details: Record<string, unknown>;
161
+
162
+ constructor(
163
+ code: PipelineEngineErrorCode,
164
+ message: string,
165
+ details: Record<string, unknown> = {},
166
+ ) {
167
+ super(message);
168
+ this.name = 'PipelineEngineError';
169
+ this.code = code;
170
+ this.details = details;
171
+ }
172
+ }
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // PipelineEngine
176
+ // ---------------------------------------------------------------------------
177
+
178
+ export class PipelineEngine {
179
+ constructor(private readonly db: PipelineDB) {}
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // Project lifecycle
183
+ // ---------------------------------------------------------------------------
184
+
185
+ createProject(opts: CreateProjectOptions): Project {
186
+ const id = randomUUID();
187
+ const now = Date.now();
188
+ const tier: PipelineTier = opts.tier ?? 'full';
189
+
190
+ this.db.run(
191
+ `INSERT INTO projects (id, repo_id, name, description, current_phase, tier, status, created_at, updated_at, metadata)
192
+ VALUES (?, ?, ?, ?, 'idle', ?, 'active', ?, ?, ?)`,
193
+ [
194
+ id,
195
+ opts.repoId,
196
+ opts.name,
197
+ opts.description ?? null,
198
+ tier,
199
+ now,
200
+ now,
201
+ JSON.stringify(opts.metadata ?? {}),
202
+ ],
203
+ );
204
+
205
+ // Emit project_created event
206
+ const event = projectCreatedEvent(id, {
207
+ name: opts.name,
208
+ tier,
209
+ repo_id: opts.repoId,
210
+ });
211
+ this.db.run(
212
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
214
+ Object.values(eventToParams(event)),
215
+ );
216
+
217
+ logger.info('PipelineEngine: project created', { id, name: opts.name, tier });
218
+
219
+ return this.getProject(id)!;
220
+ }
221
+
222
+ getProject(projectId: string): Project | null {
223
+ const row = this.db.get<ProjectRow>(
224
+ `SELECT * FROM projects WHERE id = ?`,
225
+ [projectId],
226
+ );
227
+ if (!row) return null;
228
+ return this._rowToProject(row);
229
+ }
230
+
231
+ /**
232
+ * Lists projects with optional repo and status filters.
233
+ * Ordered by updated_at DESC (most recently modified first).
234
+ */
235
+ listProjects(repoId?: string, status?: string): Project[] {
236
+ const conditions: string[] = [];
237
+ const params: unknown[] = [];
238
+
239
+ if (repoId !== undefined) {
240
+ conditions.push('repo_id = ?');
241
+ params.push(repoId);
242
+ }
243
+ if (status !== undefined) {
244
+ conditions.push('status = ?');
245
+ params.push(status);
246
+ }
247
+
248
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
249
+ const rows = this.db.all<ProjectRow>(
250
+ `SELECT * FROM projects ${where} ORDER BY updated_at DESC`,
251
+ params,
252
+ );
253
+ return rows.map((r) => this._rowToProject(r));
254
+ }
255
+
256
+ /**
257
+ * Returns the most recently updated active project for a repo, or null when
258
+ * there is no active project for that repo.
259
+ */
260
+ getActiveProject(repoId: string): Project | null {
261
+ const row = this.db.get<ProjectRow>(
262
+ `SELECT * FROM projects WHERE repo_id = ? AND status = 'active' ORDER BY updated_at DESC LIMIT 1`,
263
+ [repoId],
264
+ );
265
+ return row ? this._rowToProject(row) : null;
266
+ }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Task-spec core API
270
+ // ---------------------------------------------------------------------------
271
+
272
+ /**
273
+ * Returns the full current state of a project: project data, full event
274
+ * history, active claims, derived cycle counts, and available transitions.
275
+ *
276
+ * Throws PipelineEngineError with code PROJECT_NOT_FOUND when the project
277
+ * does not exist.
278
+ */
279
+ getState(projectId: string): EngineState {
280
+ const project = this._requireProject(projectId);
281
+ const history = this.getEventHistory(projectId);
282
+ const activeClaims = this.getActiveClaims(projectId);
283
+ const cycleCounts = this._getCycleCounts(projectId);
284
+ const currentPhase = wp(project.currentPhase);
285
+ const availableTransitions = getAvailableTransitions(currentPhase, project.tier);
286
+ return { project, history, activeClaims, cycleCounts, availableTransitions };
287
+ }
288
+
289
+ /**
290
+ * Atomically advances a project to `toPhase` in a single SQLite transaction:
291
+ * 1. Validates the transition (throws on illegal transitions).
292
+ * 2. Checks cycle limits (throws CYCLE_LIMIT_EXCEEDED; details include the
293
+ * forced advance phase so callers can decide how to proceed).
294
+ * 3. Inserts a phase_transition event.
295
+ * 4. Updates projects.current_phase and updated_at.
296
+ * 5. If toPhase === 'completed', marks the project status 'complete' and
297
+ * emits an additional project_completed event.
298
+ *
299
+ * Throws PipelineEngineError for invalid transitions and cycle limit exceeded.
300
+ * All writes are in a single better-sqlite3 transaction.
301
+ */
302
+ advancePhase(
303
+ projectId: string,
304
+ toPhase: Phase,
305
+ agent?: string,
306
+ payload?: Record<string, unknown>,
307
+ ): void {
308
+ const project = this._requireProject(projectId);
309
+
310
+ if (project.status !== 'active') {
311
+ throw new PipelineEngineError(
312
+ 'PROJECT_NOT_ACTIVE',
313
+ `Project '${projectId}' has status '${project.status}' and cannot be advanced`,
314
+ { currentPhase: project.currentPhase, status: project.status },
315
+ );
316
+ }
317
+
318
+ const fromPhase = wp(project.currentPhase);
319
+
320
+ if (!isValidTransition(fromPhase, toPhase, project.tier)) {
321
+ const available = getAvailableTransitions(fromPhase, project.tier);
322
+ throw new PipelineEngineError(
323
+ 'INVALID_TRANSITION',
324
+ `Cannot transition from '${fromPhase}' to '${toPhase}' for tier '${project.tier}'`,
325
+ { currentPhase: fromPhase, attempted: toPhase, legalTransitions: available },
326
+ );
327
+ }
328
+
329
+ // Cycle limit check for backward edges.
330
+ if (isCycleTransition(fromPhase, toPhase)) {
331
+ const key = getCycleKey(fromPhase, toPhase)!;
332
+ const cycleCounts = this._getCycleCounts(projectId);
333
+ const count = cycleCounts[key] ?? 0;
334
+ if (!canCycle(fromPhase, toPhase, count)) {
335
+ const forcedPhase = getForcedAdvancePhase(fromPhase, toPhase);
336
+ throw new PipelineEngineError(
337
+ 'CYCLE_LIMIT_EXCEEDED',
338
+ `Cycle '${key}' has reached the maximum of ${count} iterations. Force advance to '${forcedPhase ?? 'next'}'`,
339
+ { cycleKey: key, cycleCount: count, forcedAdvanceTo: forcedPhase },
340
+ );
341
+ }
342
+ }
343
+
344
+ const now = Date.now();
345
+ const isTerminal = toPhase === 'completed';
346
+ const newStatus: Project['status'] = isTerminal ? 'complete' : 'active';
347
+
348
+ const transEvent = phaseTransitionEvent(projectId, pp(fromPhase), pp(toPhase), agent ?? null, payload ?? {});
349
+
350
+ this.db.db.transaction(() => {
351
+ // 1. Insert phase_transition event
352
+ this.db.run(
353
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
354
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
355
+ Object.values(eventToParams(transEvent)),
356
+ );
357
+
358
+ // 2. Update project
359
+ this.db.run(
360
+ `UPDATE projects SET current_phase = ?, updated_at = ?, status = ? WHERE id = ?`,
361
+ [toPhase, now, newStatus, projectId],
362
+ );
363
+
364
+ // 3. Emit project_completed if terminal
365
+ if (isTerminal) {
366
+ const completedEvent = projectCompletedEvent(projectId, { agent: agent ?? null });
367
+ this.db.run(
368
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
369
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
370
+ Object.values(eventToParams(completedEvent)),
371
+ );
372
+ }
373
+ })();
374
+
375
+ logger.info('PipelineEngine: advancePhase', { projectId, from: fromPhase, to: toPhase, agent });
376
+ }
377
+
378
+ /**
379
+ * Persists a phase output to phase_outputs. Multiple outputs per phase are
380
+ * allowed (e.g., vision doc, refined vision doc, etc.).
381
+ *
382
+ * Throws PipelineEngineError with code PROJECT_NOT_FOUND when the project
383
+ * does not exist.
384
+ */
385
+ recordOutput(
386
+ projectId: string,
387
+ phase: Phase,
388
+ outputType: string,
389
+ content: string,
390
+ filePath?: string,
391
+ ): void {
392
+ this._requireProject(projectId);
393
+ this.db.run(
394
+ `INSERT INTO phase_outputs (project_id, phase, output_type, content, file_path, created_at)
395
+ VALUES (?, ?, ?, ?, ?, ?)`,
396
+ [projectId, phase, outputType, content, filePath ?? null, Date.now()],
397
+ );
398
+ }
399
+
400
+ /**
401
+ * Returns phase outputs for a project, optionally filtered to a specific
402
+ * phase. Ordered by created_at ASC.
403
+ */
404
+ getOutputs(projectId: string, phase?: Phase): PhaseOutput[] {
405
+ this._requireProject(projectId);
406
+ if (phase !== undefined) {
407
+ return this.db.all<{
408
+ id: number;
409
+ project_id: string;
410
+ phase: string;
411
+ output_type: string;
412
+ content: string;
413
+ file_path: string | null;
414
+ created_at: number;
415
+ }>(
416
+ `SELECT * FROM phase_outputs WHERE project_id = ? AND phase = ? ORDER BY created_at ASC`,
417
+ [projectId, phase],
418
+ ).map((r) => this._rowToPhaseOutput(r));
419
+ }
420
+ return this.getAllPhaseOutputs(projectId);
421
+ }
422
+
423
+ /**
424
+ * Returns all events for a project ordered by created_at ASC.
425
+ * This is the raw event sourcing log used for history reconstruction.
426
+ */
427
+ getEventHistory(projectId: string): PipelineEvent[] {
428
+ return this.db.all<{
429
+ id: number;
430
+ project_id: string;
431
+ event_type: string;
432
+ from_phase: string | null;
433
+ to_phase: string | null;
434
+ agent: string | null;
435
+ payload: string;
436
+ created_at: number;
437
+ }>(
438
+ `SELECT * FROM pipeline_events WHERE project_id = ? ORDER BY created_at ASC`,
439
+ [projectId],
440
+ ).map(rowToEvent);
441
+ }
442
+
443
+ /**
444
+ * Marks a project as 'archived'. Emits a project_completed event with an
445
+ * 'abandoned' flag. Idempotent if already archived.
446
+ *
447
+ * Throws PipelineEngineError with code PROJECT_NOT_FOUND when the project
448
+ * does not exist.
449
+ */
450
+ abandonProject(projectId: string): void {
451
+ const project = this._requireProject(projectId);
452
+
453
+ if (project.status === 'archived') {
454
+ return; // Idempotent.
455
+ }
456
+
457
+ const now = Date.now();
458
+ const completedEvent = projectCompletedEvent(projectId, { abandoned: true });
459
+
460
+ this.db.db.transaction(() => {
461
+ this.db.run(
462
+ `UPDATE projects SET status = 'archived', updated_at = ? WHERE id = ?`,
463
+ [now, projectId],
464
+ );
465
+ this.db.run(
466
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
467
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
468
+ Object.values(eventToParams(completedEvent)),
469
+ );
470
+ })();
471
+
472
+ logger.info('PipelineEngine: project abandoned', { projectId });
473
+ }
474
+
475
+ // ---------------------------------------------------------------------------
476
+ // Phase transitions (extended API — used by MCP tools)
477
+ // ---------------------------------------------------------------------------
478
+
479
+ /**
480
+ * Transition a project to a new phase.
481
+ * Validates the transition against the state machine, emits events,
482
+ * starts a trajectory, and returns the result.
483
+ */
484
+ startPhase(
485
+ projectId: string,
486
+ toPhase: Phase,
487
+ agent: string,
488
+ extra?: Record<string, unknown>,
489
+ ): TransitionResult | { error: string; current_phase: Phase; legal_transitions: Phase[] } {
490
+ const project = this.getProject(projectId);
491
+ if (!project) {
492
+ return { error: 'PROJECT_NOT_FOUND', current_phase: 'idle', legal_transitions: [] };
493
+ }
494
+
495
+ // Cast from PipelinePhase (domain type) to Phase (state-machine type) — the
496
+ // DB stores Phase values in the current_phase column; the Project interface
497
+ // types it as PipelinePhase for legacy compatibility.
498
+ const fromPhase = project.currentPhase as unknown as Phase;
499
+ const tier = project.tier;
500
+ const cycleCounts = this._getCycleCounts(projectId);
501
+
502
+ // Validate transition
503
+ const validTransition = isValidTransition(fromPhase, toPhase, tier);
504
+ const parallelStart = !validTransition && isParallelStart(fromPhase, toPhase, tier);
505
+
506
+ if (!validTransition && !parallelStart) {
507
+ const legal = getAvailableTransitions(fromPhase, tier);
508
+ return {
509
+ error: 'INVALID_TRANSITION',
510
+ current_phase: fromPhase,
511
+ legal_transitions: legal,
512
+ };
513
+ }
514
+
515
+ // For parallel siblings (architecture + design), don't change currentPhase —
516
+ // both agents run concurrently and the submit handlers coordinate advancement.
517
+ // Skip only when the current phase IS the sibling (both are running from the
518
+ // same starting point) or when we allowed a parallelStart.
519
+ const sibling = PARALLEL_PAIRS.get(toPhase);
520
+ const skipPhaseUpdate = parallelStart || (validTransition && sibling !== undefined && fromPhase === sibling);
521
+
522
+ // Check cycle limits (only for non-parallel forward transitions)
523
+ if (!skipPhaseUpdate && isCycleTransition(fromPhase, toPhase)) {
524
+ const key = getCycleKey(fromPhase, toPhase)!;
525
+ const count = cycleCounts[key] ?? 0;
526
+ if (!canCycle(fromPhase, toPhase, count)) {
527
+ const forcedPhase = getForcedAdvancePhase(fromPhase, toPhase);
528
+ logger.warn('PipelineEngine: cycle limit exceeded, forcing advance', {
529
+ projectId,
530
+ from: fromPhase,
531
+ to: toPhase,
532
+ forced: forcedPhase,
533
+ count,
534
+ });
535
+ // Force advance to the prescribed phase instead
536
+ if (forcedPhase) {
537
+ return this.startPhase(projectId, forcedPhase, agent, {
538
+ ...extra,
539
+ forced: true,
540
+ reason: 'cycle_limit_exceeded',
541
+ });
542
+ }
543
+ }
544
+ // Increment cycle counter
545
+ cycleCounts[key] = (cycleCounts[key] ?? 0) + 1;
546
+ }
547
+
548
+ const now = Date.now();
549
+
550
+ // Update project phase (skip for parallel siblings)
551
+ if (!skipPhaseUpdate) {
552
+ this.db.run(
553
+ `UPDATE projects SET current_phase = ?, updated_at = ? WHERE id = ?`,
554
+ [toPhase, now, projectId],
555
+ );
556
+ }
557
+
558
+ // Emit transition event (bridge Phase -> PipelinePhase for event factory)
559
+ const transEvent = phaseTransitionEvent(projectId, pp(fromPhase), pp(toPhase), agent, extra ?? {});
560
+ this.db.run(
561
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
562
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
563
+ Object.values(eventToParams(transEvent)),
564
+ );
565
+
566
+ // Emit agent_started event
567
+ const startedEvent = agentStartedEvent(projectId, pp(toPhase), agent, extra ?? {});
568
+ this.db.run(
569
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
570
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
571
+ Object.values(eventToParams(startedEvent)),
572
+ );
573
+
574
+ // Start trajectory
575
+ const trajectoryId = randomUUID();
576
+ this.db.run(
577
+ `INSERT INTO trajectories (id, project_id, phase, agent, status, started_at)
578
+ VALUES (?, ?, ?, ?, 'active', ?)`,
579
+ [trajectoryId, projectId, toPhase, agent, now],
580
+ );
581
+
582
+ logger.info('PipelineEngine: phase started', { projectId, from: fromPhase, to: toPhase, agent, parallel: skipPhaseUpdate });
583
+
584
+ return {
585
+ projectId,
586
+ fromPhase,
587
+ toPhase,
588
+ trajectoryId,
589
+ cycleCounts,
590
+ availableTransitions: getAvailableTransitions(toPhase, tier),
591
+ };
592
+ }
593
+
594
+ /**
595
+ * Record completion of a phase: persist output, complete the trajectory,
596
+ * and optionally advance the state machine.
597
+ */
598
+ completePhase(
599
+ projectId: string,
600
+ phase: Phase,
601
+ outputType: string,
602
+ content: string,
603
+ opts: {
604
+ agent: string;
605
+ filePath?: string;
606
+ advanceTo?: Phase;
607
+ trajectoryId?: string;
608
+ qualityScore?: number;
609
+ metadata?: Record<string, unknown>;
610
+ },
611
+ ): { outputId: number; nextPhase: Phase | null } {
612
+ const project = this.getProject(projectId);
613
+ if (!project) {
614
+ throw new Error(`Project not found: ${projectId}`);
615
+ }
616
+
617
+ const now = Date.now();
618
+
619
+ // Persist phase output
620
+ const result = this.db.run(
621
+ `INSERT INTO phase_outputs (project_id, phase, output_type, content, file_path, created_at)
622
+ VALUES (?, ?, ?, ?, ?, ?)`,
623
+ [projectId, phase, outputType, content, opts.filePath ?? null, now],
624
+ );
625
+
626
+ // Complete active trajectory for this phase
627
+ if (opts.trajectoryId) {
628
+ this.db.run(
629
+ `UPDATE trajectories SET status = 'complete', completed_at = ?, success = 1 WHERE id = ?`,
630
+ [now, opts.trajectoryId],
631
+ );
632
+
633
+ // Record trajectory step for the output
634
+ this.db.run(
635
+ `INSERT INTO trajectory_steps (trajectory_id, action, result, quality_score, metadata, created_at)
636
+ VALUES (?, ?, ?, ?, ?, ?)`,
637
+ [
638
+ opts.trajectoryId,
639
+ `submit_${phase}`,
640
+ content.slice(0, 500),
641
+ opts.qualityScore ?? null,
642
+ JSON.stringify(opts.metadata ?? {}),
643
+ now,
644
+ ],
645
+ );
646
+ }
647
+
648
+ // Emit agent_completed event
649
+ const completedEvent = agentCompletedEvent(projectId, pp(phase), opts.agent, {
650
+ output_type: outputType,
651
+ content_length: content.length,
652
+ ...opts.metadata,
653
+ });
654
+ this.db.run(
655
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
656
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
657
+ Object.values(eventToParams(completedEvent)),
658
+ );
659
+
660
+ // Advance state if requested
661
+ if (opts.advanceTo) {
662
+ this.db.run(
663
+ `UPDATE projects SET current_phase = ?, updated_at = ? WHERE id = ?`,
664
+ [opts.advanceTo, now, projectId],
665
+ );
666
+
667
+ const transEvent = phaseTransitionEvent(projectId, pp(phase), pp(opts.advanceTo), opts.agent, {});
668
+ this.db.run(
669
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
670
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
671
+ Object.values(eventToParams(transEvent)),
672
+ );
673
+
674
+ logger.info('PipelineEngine: phase completed, advanced', {
675
+ projectId,
676
+ from: phase,
677
+ to: opts.advanceTo,
678
+ });
679
+ }
680
+
681
+ return {
682
+ outputId: Number(result.lastInsertRowid),
683
+ nextPhase: opts.advanceTo ?? null,
684
+ };
685
+ }
686
+
687
+ // ---------------------------------------------------------------------------
688
+ // Trajectory helpers
689
+ // ---------------------------------------------------------------------------
690
+
691
+ startTrajectory(projectId: string, phase: Phase, agent: string): string {
692
+ const id = randomUUID();
693
+ this.db.run(
694
+ `INSERT INTO trajectories (id, project_id, phase, agent, status, started_at)
695
+ VALUES (?, ?, ?, ?, 'active', ?)`,
696
+ [id, projectId, phase, agent, Date.now()],
697
+ );
698
+ return id;
699
+ }
700
+
701
+ recordTrajectoryStep(
702
+ trajectoryId: string,
703
+ action: string,
704
+ result?: string,
705
+ qualityScore?: number,
706
+ metadata?: TrajectoryStepMetadata,
707
+ ): void {
708
+ this.db.run(
709
+ `INSERT INTO trajectory_steps (trajectory_id, action, result, quality_score, metadata, created_at)
710
+ VALUES (?, ?, ?, ?, ?, ?)`,
711
+ [
712
+ trajectoryId,
713
+ action,
714
+ result ?? null,
715
+ qualityScore ?? null,
716
+ JSON.stringify(metadata ?? {}),
717
+ Date.now(),
718
+ ],
719
+ );
720
+ }
721
+
722
+ completeTrajectory(
723
+ trajectoryId: string,
724
+ success: boolean,
725
+ feedback?: string,
726
+ ): void {
727
+ this.db.run(
728
+ `UPDATE trajectories SET status = ?, completed_at = ?, success = ?, feedback = ? WHERE id = ?`,
729
+ [success ? 'complete' : 'failed', Date.now(), success ? 1 : 0, feedback ?? null, trajectoryId],
730
+ );
731
+ }
732
+
733
+ // ---------------------------------------------------------------------------
734
+ // Claims (parallel implementation dispatch)
735
+ // ---------------------------------------------------------------------------
736
+
737
+ createClaim(projectId: string, moduleName: string, agent: string): Claim {
738
+ const id = randomUUID();
739
+ const now = Date.now();
740
+ this.db.run(
741
+ `INSERT INTO claims (id, project_id, module_name, agent, status, claimed_at)
742
+ VALUES (?, ?, ?, ?, 'claimed', ?)`,
743
+ [id, projectId, moduleName, agent, now],
744
+ );
745
+
746
+ const event = claimCreatedEvent(projectId, id, moduleName, agent);
747
+ this.db.run(
748
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
749
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
750
+ Object.values(eventToParams(event)),
751
+ );
752
+
753
+ return {
754
+ id,
755
+ projectId,
756
+ moduleName,
757
+ agent,
758
+ branch: null,
759
+ status: 'claimed',
760
+ claimedAt: now,
761
+ completedAt: null,
762
+ result: null,
763
+ };
764
+ }
765
+
766
+ completeClaim(
767
+ claimId: string,
768
+ result?: string,
769
+ ): { remainingCount: number; allComplete: boolean } {
770
+ const row = this.db.get<ClaimRow>(`SELECT * FROM claims WHERE id = ?`, [claimId]);
771
+ if (!row) throw new Error(`Claim not found: ${claimId}`);
772
+
773
+ const now = Date.now();
774
+ this.db.run(
775
+ `UPDATE claims SET status = 'complete', completed_at = ?, result = ? WHERE id = ?`,
776
+ [now, result ?? null, claimId],
777
+ );
778
+
779
+ const event = claimCompletedEvent(row.project_id, claimId, row.module_name, row.agent, result);
780
+ this.db.run(
781
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
782
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
783
+ Object.values(eventToParams(event)),
784
+ );
785
+
786
+ const remaining = this.db.all<{ id: string }>(
787
+ `SELECT id FROM claims WHERE project_id = ? AND status NOT IN ('complete', 'failed')`,
788
+ [row.project_id],
789
+ );
790
+
791
+ return {
792
+ remainingCount: remaining.length,
793
+ allComplete: remaining.length === 0,
794
+ };
795
+ }
796
+
797
+ failClaim(claimId: string, reason: string): void {
798
+ const row = this.db.get<ClaimRow>(`SELECT * FROM claims WHERE id = ?`, [claimId]);
799
+ if (!row) throw new Error(`Claim not found: ${claimId}`);
800
+
801
+ this.db.run(
802
+ `UPDATE claims SET status = 'failed', completed_at = ? WHERE id = ?`,
803
+ [Date.now(), claimId],
804
+ );
805
+
806
+ const event = claimFailedEvent(row.project_id, claimId, row.module_name, row.agent, reason);
807
+ this.db.run(
808
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
809
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
810
+ Object.values(eventToParams(event)),
811
+ );
812
+ }
813
+
814
+ getActiveClaims(projectId: string): Claim[] {
815
+ const rows = this.db.all<ClaimRow>(
816
+ `SELECT * FROM claims WHERE project_id = ? AND status NOT IN ('complete', 'failed')`,
817
+ [projectId],
818
+ );
819
+ return rows.map((r) => this._rowToClaim(r));
820
+ }
821
+
822
+ getClaims(projectId: string, status?: ClaimStatus): Claim[] {
823
+ if (status !== undefined) {
824
+ const rows = this.db.all<ClaimRow>(
825
+ `SELECT * FROM claims WHERE project_id = ? AND status = ? ORDER BY claimed_at ASC`,
826
+ [projectId, status],
827
+ );
828
+ return rows.map((r) => this._rowToClaim(r));
829
+ }
830
+ const rows = this.db.all<ClaimRow>(
831
+ `SELECT * FROM claims WHERE project_id = ? ORDER BY claimed_at ASC`,
832
+ [projectId],
833
+ );
834
+ return rows.map((r) => this._rowToClaim(r));
835
+ }
836
+
837
+ getClaim(claimId: string): Claim | null {
838
+ const row = this.db.get<ClaimRow>(`SELECT * FROM claims WHERE id = ?`, [claimId]);
839
+ return row ? this._rowToClaim(row) : null;
840
+ }
841
+
842
+ getClaimByModule(projectId: string, moduleName: string): Claim | null {
843
+ const row = this.db.get<ClaimRow>(
844
+ `SELECT * FROM claims WHERE project_id = ? AND module_name = ? ORDER BY claimed_at DESC LIMIT 1`,
845
+ [projectId, moduleName],
846
+ );
847
+ return row ? this._rowToClaim(row) : null;
848
+ }
849
+
850
+ // ---------------------------------------------------------------------------
851
+ // Phase outputs
852
+ // ---------------------------------------------------------------------------
853
+
854
+ getPhaseOutput(projectId: string, phase: Phase, outputType?: string): PhaseOutput | null {
855
+ const sql = outputType
856
+ ? `SELECT * FROM phase_outputs WHERE project_id = ? AND phase = ? AND output_type = ? ORDER BY created_at DESC LIMIT 1`
857
+ : `SELECT * FROM phase_outputs WHERE project_id = ? AND phase = ? ORDER BY created_at DESC LIMIT 1`;
858
+ const params = outputType ? [projectId, phase, outputType] : [projectId, phase];
859
+ const row = this.db.get<{
860
+ id: number;
861
+ project_id: string;
862
+ phase: string;
863
+ output_type: string;
864
+ content: string;
865
+ file_path: string | null;
866
+ created_at: number;
867
+ }>(sql, params);
868
+ if (!row) return null;
869
+ return this._rowToPhaseOutput(row);
870
+ }
871
+
872
+ /**
873
+ * Retrieve metadata stored in the agent_completed event for a phase.
874
+ * This includes flags like requires_qa, requires_designer, modules, etc.
875
+ * that are passed via opts.metadata in completePhase().
876
+ */
877
+ getPhaseMetadata(projectId: string, phase: Phase): Record<string, unknown> | null {
878
+ const row = this.db.get<{ payload: string }>(
879
+ `SELECT payload FROM pipeline_events
880
+ WHERE project_id = ? AND event_type = 'agent_completed' AND from_phase = ?
881
+ ORDER BY created_at DESC LIMIT 1`,
882
+ [projectId, phase],
883
+ );
884
+ if (!row) return null;
885
+ try {
886
+ return JSON.parse(row.payload) as Record<string, unknown>;
887
+ } catch {
888
+ return null;
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Record a broadcast event in the pipeline_events table.
894
+ * Used by phase tools to auto-broadcast module completions for swarm coordination.
895
+ */
896
+ recordBroadcast(
897
+ projectId: string,
898
+ content: string,
899
+ severity: 'info' | 'warning' | 'critical' = 'info',
900
+ ): void {
901
+ this.db.run(
902
+ `INSERT INTO pipeline_events (project_id, event_type, from_phase, to_phase, agent, payload, created_at)
903
+ VALUES (?, ?, NULL, NULL, NULL, ?, ?)`,
904
+ [
905
+ projectId,
906
+ 'agent_broadcast',
907
+ JSON.stringify({ content, severity }),
908
+ Date.now(),
909
+ ],
910
+ );
911
+ }
912
+
913
+ getAllPhaseOutputs(projectId: string): PhaseOutput[] {
914
+ const rows = this.db.all<{
915
+ id: number;
916
+ project_id: string;
917
+ phase: string;
918
+ output_type: string;
919
+ content: string;
920
+ file_path: string | null;
921
+ created_at: number;
922
+ }>(`SELECT * FROM phase_outputs WHERE project_id = ? ORDER BY created_at ASC`, [projectId]);
923
+ return rows.map((r) => this._rowToPhaseOutput(r));
924
+ }
925
+
926
+ // ---------------------------------------------------------------------------
927
+ // History
928
+ // ---------------------------------------------------------------------------
929
+
930
+ getHistory(projectId: string): ProjectHistory {
931
+ const events = this.db.all<{
932
+ id: number;
933
+ project_id: string;
934
+ event_type: string;
935
+ from_phase: string | null;
936
+ to_phase: string | null;
937
+ agent: string | null;
938
+ payload: string;
939
+ created_at: number;
940
+ }>(`SELECT * FROM pipeline_events WHERE project_id = ? ORDER BY created_at ASC`, [projectId]);
941
+
942
+ const outputs = this.getAllPhaseOutputs(projectId);
943
+
944
+ // Build phase history from agent_started / agent_completed pairs.
945
+ // ev.fromPhase is PipelinePhase (domain type), PhaseHistory.phase is Phase.
946
+ const history: PhaseHistory[] = [];
947
+ const startedMap = new Map<string, { phase: Phase; agent: string; startedAt: number }>();
948
+
949
+ for (const row of events) {
950
+ const ev = rowToEvent(row);
951
+ if (ev.eventType === 'agent_started' && ev.fromPhase) {
952
+ startedMap.set(`${ev.fromPhase}-${ev.agent}`, {
953
+ phase: wp(ev.fromPhase),
954
+ agent: ev.agent ?? 'unknown',
955
+ startedAt: ev.createdAt,
956
+ });
957
+ } else if (ev.eventType === 'agent_completed' && ev.fromPhase) {
958
+ const key = `${ev.fromPhase}-${ev.agent}`;
959
+ const started = startedMap.get(key);
960
+ if (started) {
961
+ history.push({
962
+ phase: started.phase,
963
+ agent: started.agent,
964
+ startedAt: new Date(started.startedAt).toISOString(),
965
+ completedAt: new Date(ev.createdAt).toISOString(),
966
+ success: true,
967
+ });
968
+ startedMap.delete(key);
969
+ }
970
+ }
971
+ }
972
+
973
+ // Flush any in-progress phases
974
+ for (const [, started] of startedMap) {
975
+ history.push({
976
+ phase: started.phase,
977
+ agent: started.agent,
978
+ startedAt: new Date(started.startedAt).toISOString(),
979
+ });
980
+ }
981
+
982
+ return {
983
+ projectId,
984
+ events: events.map(rowToEvent),
985
+ outputs,
986
+ history,
987
+ };
988
+ }
989
+
990
+ // ---------------------------------------------------------------------------
991
+ // Cycle tracking
992
+ // ---------------------------------------------------------------------------
993
+
994
+ getCycleCounts(projectId: string): Record<string, number> {
995
+ return this._getCycleCounts(projectId);
996
+ }
997
+
998
+ private _getCycleCounts(projectId: string): Record<string, number> {
999
+ const rows = this.db.all<{
1000
+ from_phase: string;
1001
+ to_phase: string;
1002
+ count: number;
1003
+ }>(
1004
+ `SELECT from_phase, to_phase, COUNT(*) as count
1005
+ FROM pipeline_events
1006
+ WHERE project_id = ? AND event_type = 'phase_transition' AND from_phase IS NOT NULL AND to_phase IS NOT NULL
1007
+ GROUP BY from_phase, to_phase`,
1008
+ [projectId],
1009
+ );
1010
+
1011
+ const counts: Record<string, number> = {};
1012
+ for (const row of rows) {
1013
+ const key = `${row.from_phase}_to_${row.to_phase}`;
1014
+ // Only include recognised cycle keys
1015
+ if (getCycleKey(row.from_phase as Phase, row.to_phase as Phase)) {
1016
+ counts[key] = row.count;
1017
+ }
1018
+ }
1019
+ return counts;
1020
+ }
1021
+
1022
+ // ---------------------------------------------------------------------------
1023
+ // Internal helpers
1024
+ // ---------------------------------------------------------------------------
1025
+
1026
+ /**
1027
+ * Retrieves a project by id and throws a typed PipelineEngineError when not
1028
+ * found. Used by every method that requires an existing project.
1029
+ */
1030
+ private _requireProject(projectId: string): Project {
1031
+ const row = this.db.get<ProjectRow>(
1032
+ `SELECT * FROM projects WHERE id = ?`,
1033
+ [projectId],
1034
+ );
1035
+ if (!row) {
1036
+ throw new PipelineEngineError(
1037
+ 'PROJECT_NOT_FOUND',
1038
+ `Project '${projectId}' does not exist`,
1039
+ { projectId },
1040
+ );
1041
+ }
1042
+ return this._rowToProject(row);
1043
+ }
1044
+
1045
+ // ---------------------------------------------------------------------------
1046
+ // Row mappers
1047
+ // ---------------------------------------------------------------------------
1048
+
1049
+ private _rowToProject(row: ProjectRow): Project {
1050
+ let metadata: ProjectMetadata;
1051
+ try {
1052
+ metadata = JSON.parse(row.metadata) as ProjectMetadata;
1053
+ } catch {
1054
+ metadata = {};
1055
+ }
1056
+
1057
+ return {
1058
+ id: row.id,
1059
+ repoId: row.repo_id,
1060
+ name: row.name,
1061
+ description: row.description,
1062
+ // The DB stores Phase (workflow-stage) values in current_phase, but
1063
+ // Project.currentPhase is typed as PipelinePhase. We bridge here so
1064
+ // callers use wp() to get back to Phase when needed.
1065
+ currentPhase: row.current_phase as unknown as PipelinePhase,
1066
+ tier: row.tier,
1067
+ status: row.status,
1068
+ createdAt: row.created_at,
1069
+ updatedAt: row.updated_at,
1070
+ metadata,
1071
+ };
1072
+ }
1073
+
1074
+ private _rowToClaim(row: ClaimRow): Claim {
1075
+ return {
1076
+ id: row.id,
1077
+ projectId: row.project_id,
1078
+ moduleName: row.module_name,
1079
+ agent: row.agent,
1080
+ branch: row.branch,
1081
+ status: row.status,
1082
+ claimedAt: row.claimed_at,
1083
+ completedAt: row.completed_at,
1084
+ result: row.result,
1085
+ };
1086
+ }
1087
+
1088
+ private _rowToPhaseOutput(row: {
1089
+ id: number;
1090
+ project_id: string;
1091
+ phase: string;
1092
+ output_type: string;
1093
+ content: string;
1094
+ file_path: string | null;
1095
+ created_at: number;
1096
+ }): PhaseOutput {
1097
+ return {
1098
+ id: row.id,
1099
+ projectId: row.project_id,
1100
+ // Bridge: Phase values stored in DB, PhaseOutput.phase typed as PipelinePhase.
1101
+ phase: row.phase as unknown as PipelinePhase,
1102
+ outputType: row.output_type,
1103
+ content: row.content,
1104
+ filePath: row.file_path,
1105
+ createdAt: row.created_at,
1106
+ };
1107
+ }
1108
+
1109
+ // Expose getTrajectory for phase tools
1110
+ getActiveTrajectory(projectId: string, phase: Phase): Trajectory | null {
1111
+ const row = this.db.get<{
1112
+ id: string;
1113
+ project_id: string;
1114
+ phase: string;
1115
+ agent: string;
1116
+ status: string;
1117
+ started_at: number;
1118
+ completed_at: number | null;
1119
+ success: number | null;
1120
+ feedback: string | null;
1121
+ }>(
1122
+ `SELECT * FROM trajectories WHERE project_id = ? AND phase = ? AND status = 'active' ORDER BY started_at DESC LIMIT 1`,
1123
+ [projectId, phase],
1124
+ );
1125
+ if (!row) return null;
1126
+ return {
1127
+ id: row.id,
1128
+ projectId: row.project_id,
1129
+ // Bridge: Phase stored in DB, Trajectory.phase typed as PipelinePhase.
1130
+ phase: row.phase as unknown as PipelinePhase,
1131
+ agent: row.agent,
1132
+ status: row.status as 'active' | 'complete' | 'failed' | 'abandoned',
1133
+ startedAt: row.started_at,
1134
+ completedAt: row.completed_at,
1135
+ success: row.success === null ? null : row.success === 1,
1136
+ feedback: row.feedback,
1137
+ };
1138
+ }
1139
+ }