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,187 @@
1
+ // MemoryBridge — B8
2
+ //
3
+ // Wraps the Qdrant vector store's observation collection to provide
4
+ // memory search and save operations for the context injection layer.
5
+ //
6
+ // Uses the QdrantVectorStore directly (not the VectorStore interface) because
7
+ // we need the searchObservations + upsertObservation methods that are Qdrant-specific.
8
+ // When vectorStore is null or unhealthy, all methods degrade gracefully.
9
+
10
+ import { randomUUID } from 'node:crypto';
11
+ import type { QdrantVectorStore } from '../storage/qdrant-store.js';
12
+ import type { ObservationPayload } from '../util/types.js';
13
+ import { embedText } from '../ingestion/embedder.js';
14
+ import { logger } from '../util/logger.js';
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Public types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export interface MemorySearchResult {
21
+ content: string;
22
+ createdAt: number;
23
+ relevanceScore: number;
24
+ sessionId?: string;
25
+ }
26
+
27
+ export interface MemorySearchOptions {
28
+ repoId?: string;
29
+ sessionId?: string;
30
+ limit?: number;
31
+ scoreThreshold?: number;
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // MemoryBridge
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export class MemoryBridge {
39
+ constructor(private readonly vectorStore: QdrantVectorStore | null) {}
40
+
41
+ /**
42
+ * Search observations by semantic similarity.
43
+ * Optionally filters by repoId and/or sessionId.
44
+ * Returns [] gracefully when Qdrant is unavailable.
45
+ */
46
+ async searchMemories(
47
+ query: string,
48
+ options: MemorySearchOptions = {},
49
+ ): Promise<MemorySearchResult[]> {
50
+ if (!this.vectorStore) {
51
+ return [];
52
+ }
53
+
54
+ const healthy = await this.vectorStore.isHealthy();
55
+ if (!healthy) {
56
+ logger.debug('MemoryBridge: Qdrant unavailable — returning empty memories');
57
+ return [];
58
+ }
59
+
60
+ const {
61
+ repoId,
62
+ sessionId,
63
+ limit = 10,
64
+ scoreThreshold = 0.3,
65
+ } = options;
66
+
67
+ let queryVector: number[];
68
+ try {
69
+ queryVector = await embedText(query);
70
+ } catch (err) {
71
+ logger.warn('MemoryBridge: embedding failed', { error: String(err) });
72
+ return [];
73
+ }
74
+
75
+ // Build filter — only include conditions for fields that are specified
76
+ const mustConditions: Record<string, unknown>[] = [];
77
+ if (repoId) {
78
+ mustConditions.push({ key: 'repo_id', match: { value: repoId } });
79
+ }
80
+ if (sessionId) {
81
+ mustConditions.push({ key: 'session_id', match: { value: sessionId } });
82
+ }
83
+
84
+ // Exclude session markers from memory results
85
+ const mustNotConditions: Record<string, unknown>[] = [
86
+ { key: 'tags', match: { value: 'session_marker' } },
87
+ ];
88
+
89
+ const filter: Record<string, unknown> =
90
+ mustConditions.length > 0
91
+ ? { must: mustConditions, must_not: mustNotConditions }
92
+ : { must_not: mustNotConditions };
93
+
94
+ try {
95
+ const results = await this.vectorStore.searchObservations(queryVector, {
96
+ limit,
97
+ scoreThreshold,
98
+ filter,
99
+ });
100
+
101
+ return results
102
+ .map((r) => {
103
+ const payload = r.payload as ObservationPayload;
104
+ return {
105
+ content: payload.content,
106
+ createdAt: payload.created_at,
107
+ relevanceScore: Math.round(r.score * 1000) / 1000,
108
+ sessionId: payload.session_id || undefined,
109
+ };
110
+ })
111
+ .filter((m) => m.content && m.content.trim().length > 0);
112
+ } catch (err) {
113
+ logger.warn('MemoryBridge: search failed', { error: String(err) });
114
+ return [];
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Save a memory (observation) to the vector store.
120
+ * Returns the UUID of the stored observation.
121
+ * Throws when storage fails so the caller can decide whether to propagate.
122
+ */
123
+ async saveMemory(
124
+ content: string,
125
+ repoId: string,
126
+ sessionId?: string,
127
+ ): Promise<string> {
128
+ if (!this.vectorStore) {
129
+ throw new Error('MemoryBridge: vector store not configured');
130
+ }
131
+
132
+ const healthy = await this.vectorStore.isHealthy();
133
+ if (!healthy) {
134
+ throw new Error('MemoryBridge: Qdrant is unavailable');
135
+ }
136
+
137
+ const id = randomUUID();
138
+ const now = Date.now();
139
+ const effectiveSessionId = sessionId ?? `session-${now}`;
140
+
141
+ let vector: number[];
142
+ try {
143
+ vector = await embedText(content);
144
+ } catch (err) {
145
+ logger.warn('MemoryBridge: embedding failed, using zero vector', {
146
+ error: String(err),
147
+ });
148
+ vector = new Array(384).fill(0);
149
+ }
150
+
151
+ const payload: ObservationPayload = {
152
+ id,
153
+ repo_id: repoId,
154
+ session_id: effectiveSessionId,
155
+ content,
156
+ type: 'semantic',
157
+ category: null,
158
+ tags: [],
159
+ importance: 0.5,
160
+ created_at: now,
161
+ accessed_at: now,
162
+ access_count: 0,
163
+ is_stale: false,
164
+ linked_symbols: [],
165
+ source: 'forge',
166
+ metadata: {},
167
+ key: null,
168
+ namespace: 'default',
169
+ };
170
+
171
+ await this.vectorStore.upsertObservation(id, vector, payload);
172
+
173
+ logger.debug('MemoryBridge: memory saved', {
174
+ id,
175
+ repoId,
176
+ sessionId: effectiveSessionId,
177
+ contentLength: content.length,
178
+ });
179
+
180
+ return id;
181
+ }
182
+
183
+ /** True when the vector store is configured (does not perform a health check). */
184
+ isAvailable(): boolean {
185
+ return this.vectorStore !== null;
186
+ }
187
+ }
@@ -0,0 +1,327 @@
1
+ // session-index.ts
2
+ //
3
+ // Session-scoped context index — virtual memory for LLM context windows.
4
+ // Instead of pruning old context (losing it), offloads to Qdrant for
5
+ // semantic retrieval. The injector searches this before assembling
6
+ // context, retrieving only what's relevant to the current task.
7
+ //
8
+ // Uses existing forge_observations collection with type='working' to
9
+ // avoid creating new infrastructure.
10
+
11
+ import type { QdrantVectorStore } from '../storage/qdrant-store.js';
12
+ import type { GraphStore } from '../storage/interfaces.js';
13
+ import { saveObservation } from '../memory/observation-store.js';
14
+ import { embedText } from '../ingestion/embedder.js';
15
+ import { logger } from '../util/logger.js';
16
+
17
+ // No-op graph store for saveObservation calls (graph linking not needed for cache)
18
+ const noOpGraph: GraphStore = {
19
+ connect: async () => {},
20
+ disconnect: async () => {},
21
+ isHealthy: async () => false,
22
+ query: async () => ({ nodes: [], edges: [] }),
23
+ upsertNode: async () => {},
24
+ upsertEdge: async () => {},
25
+ deleteFile: async () => {},
26
+ deleteRepo: async () => {},
27
+ getCounts: async () => ({ totalNodes: 0, totalEdges: 0, byLabel: {} }),
28
+ ensureIndexes: async () => {},
29
+ };
30
+
31
+ // Phase names for tag extraction
32
+ const KNOWN_PHASES = [
33
+ 'interview',
34
+ 'requirements',
35
+ 'architecture',
36
+ 'design',
37
+ 'qa_strategy',
38
+ 'implementation',
39
+ 'inspection',
40
+ 'knowledge_collection',
41
+ ] as const;
42
+
43
+ // Event types for tag extraction
44
+ const KNOWN_EVENT_TYPES = [
45
+ 'phase_output',
46
+ 'broadcast',
47
+ 'tool_result',
48
+ 'agent_summary',
49
+ ] as const;
50
+
51
+ export interface SessionEvent {
52
+ type: 'phase_output' | 'broadcast' | 'tool_result' | 'agent_summary';
53
+ phase: string;
54
+ agent: string;
55
+ projectId: string;
56
+ content: string;
57
+ created_at: number;
58
+ }
59
+
60
+ export interface TieredContext {
61
+ /** Tier 0: critical directives, contracts, blockers — always injected */
62
+ alwaysInject: string[];
63
+ /** Tier 1: top-K semantic matches for this module — smart-injected */
64
+ smartInjected: string[];
65
+ /** Tier 2: count of remaining searchable items (for the agent's awareness) */
66
+ searchableCount: number;
67
+ }
68
+
69
+ export class SessionContextIndex {
70
+ constructor(
71
+ private readonly vectorStore: QdrantVectorStore,
72
+ private readonly sessionId: string,
73
+ ) {}
74
+
75
+ /**
76
+ * Index a session event for later retrieval.
77
+ * Fire-and-forget — never blocks the caller.
78
+ */
79
+ async index(event: SessionEvent): Promise<void> {
80
+ try {
81
+ await saveObservation(
82
+ {
83
+ content: event.content.slice(0, 2000),
84
+ sessionId: this.sessionId,
85
+ type: 'working',
86
+ category: `session_${event.type}`,
87
+ importance: event.type === 'broadcast' ? 0.7 : 0.5,
88
+ tags: [
89
+ 'session_cache',
90
+ this.sessionId,
91
+ event.projectId,
92
+ event.phase,
93
+ event.agent,
94
+ event.type,
95
+ ],
96
+ },
97
+ this.vectorStore,
98
+ noOpGraph,
99
+ );
100
+ } catch (err) {
101
+ logger.debug('SessionContextIndex.index: failed (non-fatal)', {
102
+ type: event.type,
103
+ error: String(err),
104
+ });
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Semantic search scoped to this session + project.
110
+ * Returns the most relevant cached items for a query.
111
+ */
112
+ async retrieve(
113
+ query: string,
114
+ projectId: string,
115
+ limit: number = 5,
116
+ ): Promise<SessionEvent[]> {
117
+ try {
118
+ // Attempt semantic search first — requires embedding the query
119
+ let queryVector: number[] | null = null;
120
+ try {
121
+ queryVector = await embedText(query);
122
+ } catch {
123
+ // Embedding unavailable — fall back to filter-only below
124
+ }
125
+
126
+ if (queryVector) {
127
+ // Semantic search: ranked by vector similarity
128
+ const results = await this.vectorStore.searchObservations(queryVector, {
129
+ limit,
130
+ scoreThreshold: 0.2,
131
+ filter: {
132
+ must: [
133
+ { key: 'tags', match: { value: 'session_cache' } },
134
+ { key: 'tags', match: { value: this.sessionId } },
135
+ { key: 'tags', match: { value: projectId } },
136
+ ],
137
+ },
138
+ });
139
+ return results.map((r) => this.resultToSessionEvent(r.payload as unknown as Record<string, unknown>, projectId));
140
+ }
141
+
142
+ // Fallback: filter-only retrieval (no semantic ranking)
143
+ const filter = {
144
+ must: [
145
+ { key: 'tags', match: { value: 'session_cache' } },
146
+ { key: 'tags', match: { value: this.sessionId } },
147
+ { key: 'tags', match: { value: projectId } },
148
+ ],
149
+ };
150
+ const results = await this.vectorStore.filterObservations(filter, limit);
151
+ return results.map((r) => this.resultToSessionEvent(r.payload as unknown as Record<string, unknown>, projectId));
152
+ } catch (err) {
153
+ logger.debug('SessionContextIndex.retrieve: failed (non-fatal)', { error: String(err) });
154
+ return [];
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Build tiered context for a module using semantic search.
160
+ *
161
+ * Tier 0: Items tagged as critical/directive (importance >= 0.8) — always injected
162
+ * Tier 1: Top-K semantic matches for the module description — smart-injected
163
+ * Tier 2: Count of remaining items (awareness only)
164
+ */
165
+ async buildTieredContext(
166
+ moduleDescription: string,
167
+ projectId: string,
168
+ ): Promise<TieredContext> {
169
+ const defaultCtx: TieredContext = {
170
+ alwaysInject: [],
171
+ smartInjected: [],
172
+ searchableCount: 0,
173
+ };
174
+
175
+ try {
176
+ // Tier 0: All session cache items for this project (we filter high-importance in memory)
177
+ const allFilter = {
178
+ must: [
179
+ { key: 'tags', match: { value: 'session_cache' } },
180
+ { key: 'tags', match: { value: this.sessionId } },
181
+ { key: 'tags', match: { value: projectId } },
182
+ ],
183
+ };
184
+ const allResults = await this.vectorStore.filterObservations(allFilter, 50);
185
+
186
+ // Extract high-importance items for Tier 0
187
+ const criticals = allResults.filter((r) => {
188
+ const payload = r.payload as unknown as Record<string, unknown>;
189
+ return (payload['importance'] as number) >= 0.8;
190
+ });
191
+ defaultCtx.alwaysInject = criticals.map((r) => {
192
+ const payload = r.payload as unknown as Record<string, unknown>;
193
+ return (payload['content'] as string) ?? '';
194
+ });
195
+
196
+ // Tier 1: Semantic search for this module's description
197
+ // Requires embedding the module description into a query vector
198
+ let smartResults: string[] = [];
199
+ if (moduleDescription) {
200
+ let queryVector: number[] | null = null;
201
+ try {
202
+ queryVector = await embedText(moduleDescription);
203
+ } catch {
204
+ // Embedding unavailable — skip semantic search
205
+ }
206
+
207
+ if (queryVector) {
208
+ const searchResults = await this.vectorStore.searchObservations(
209
+ queryVector,
210
+ {
211
+ limit: 5,
212
+ scoreThreshold: 0.3,
213
+ filter: {
214
+ must: [
215
+ { key: 'tags', match: { value: 'session_cache' } },
216
+ { key: 'tags', match: { value: this.sessionId } },
217
+ { key: 'tags', match: { value: projectId } },
218
+ ],
219
+ },
220
+ },
221
+ );
222
+ smartResults = searchResults.map((r) => {
223
+ const payload = r.payload as unknown as Record<string, unknown>;
224
+ return (payload['content'] as string) ?? '';
225
+ });
226
+ } else {
227
+ // Fallback: use the non-critical filter results as Tier 1
228
+ const nonCriticalItems = allResults.filter((r) => {
229
+ const payload = r.payload as unknown as Record<string, unknown>;
230
+ return (payload['importance'] as number) < 0.8;
231
+ });
232
+ smartResults = nonCriticalItems.slice(0, 5).map((r) => {
233
+ const payload = r.payload as unknown as Record<string, unknown>;
234
+ return (payload['content'] as string) ?? '';
235
+ });
236
+ }
237
+ }
238
+ defaultCtx.smartInjected = smartResults;
239
+
240
+ // Tier 2: Total count minus what we already injected
241
+ defaultCtx.searchableCount = Math.max(
242
+ 0,
243
+ allResults.length - defaultCtx.alwaysInject.length - defaultCtx.smartInjected.length,
244
+ );
245
+
246
+ return defaultCtx;
247
+ } catch (err) {
248
+ logger.debug('SessionContextIndex.buildTieredContext: failed (non-fatal)', { error: String(err) });
249
+ return defaultCtx;
250
+ }
251
+ }
252
+
253
+ /**
254
+ * End-of-session cleanup: promote high-value items to permanent
255
+ * observations, let the rest expire naturally.
256
+ */
257
+ async promoteAndExpire(): Promise<{ promoted: number; expired: number }> {
258
+ try {
259
+ const filter = {
260
+ must: [
261
+ { key: 'tags', match: { value: 'session_cache' } },
262
+ { key: 'tags', match: { value: this.sessionId } },
263
+ ],
264
+ };
265
+ const all = await this.vectorStore.filterObservations(filter, 100);
266
+
267
+ let promoted = 0;
268
+ for (const item of all) {
269
+ const payload = item.payload as unknown as Record<string, unknown>;
270
+ const importance = (payload['importance'] as number) ?? 0;
271
+ // Promote items with high importance to permanent episodic memory
272
+ if (importance >= 0.7) {
273
+ await saveObservation(
274
+ {
275
+ content: (payload['content'] as string) ?? '',
276
+ sessionId: this.sessionId,
277
+ type: 'episodic',
278
+ category: (payload['category'] as string) ?? 'session_promoted',
279
+ importance,
280
+ tags: ((payload['tags'] as string[]) ?? []).filter(t => t !== 'session_cache'),
281
+ },
282
+ this.vectorStore,
283
+ noOpGraph,
284
+ );
285
+ promoted++;
286
+ }
287
+ }
288
+
289
+ return { promoted, expired: all.length - promoted };
290
+ } catch (err) {
291
+ logger.debug('SessionContextIndex.promoteAndExpire: failed (non-fatal)', { error: String(err) });
292
+ return { promoted: 0, expired: 0 };
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Convert a raw Qdrant payload into a SessionEvent.
298
+ * Extracts type, phase, and agent from the tags array.
299
+ */
300
+ private resultToSessionEvent(
301
+ payload: Record<string, unknown>,
302
+ projectId: string,
303
+ ): SessionEvent {
304
+ const tags = (payload['tags'] as string[]) ?? [];
305
+
306
+ const eventType = tags.find((t): t is SessionEvent['type'] =>
307
+ (KNOWN_EVENT_TYPES as readonly string[]).includes(t),
308
+ ) ?? 'phase_output';
309
+
310
+ const phase = tags.find((t) =>
311
+ (KNOWN_PHASES as readonly string[]).includes(t),
312
+ ) ?? 'unknown';
313
+
314
+ // Agent is stored at index 4 in the tags array:
315
+ // ['session_cache', sessionId, projectId, phase, agent, type]
316
+ const agent = tags[4] ?? 'unknown';
317
+
318
+ return {
319
+ type: eventType,
320
+ phase,
321
+ agent,
322
+ projectId,
323
+ content: (payload['content'] as string) ?? '',
324
+ created_at: (payload['created_at'] as number) ?? 0,
325
+ };
326
+ }
327
+ }
@@ -0,0 +1,152 @@
1
+ // SessionManager — B9
2
+ //
3
+ // Manages session lifecycle against the SQLite sessions table.
4
+ // Sessions are lightweight records that tie memory observations and
5
+ // phase transitions to a chronological window so the context injector can
6
+ // fetch "what was learned in this session" when assembling phase context.
7
+ //
8
+ // Design:
9
+ // - All operations are synchronous SQLite reads/writes (better-sqlite3).
10
+ // - Session IDs follow the format "session-{timestamp}-{random4}".
11
+ // - State is stored as a JSON blob in the `state` column; callers can
12
+ // stash arbitrary resumption data there.
13
+ // - observation_count is incremented via a single UPDATE statement so
14
+ // concurrent callers in the same process do not race.
15
+
16
+ import { randomUUID } from 'node:crypto';
17
+ import type { PipelineDB } from '../storage/sqlite.js';
18
+ import type { Session, SessionRow, SessionState } from '../util/types.js';
19
+ import { logger } from '../util/logger.js';
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Row deserialization helper
23
+ // ---------------------------------------------------------------------------
24
+
25
+ function rowToSession(row: SessionRow): Session {
26
+ let state: SessionState = {};
27
+ try {
28
+ state = JSON.parse(row.state) as SessionState;
29
+ } catch {
30
+ state = {};
31
+ }
32
+
33
+ return {
34
+ id: row.id,
35
+ projectId: row.project_id ?? null,
36
+ repoId: row.repo_id ?? null,
37
+ startedAt: row.started_at,
38
+ endedAt: row.ended_at ?? null,
39
+ state,
40
+ observationCount: row.observation_count,
41
+ };
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // SessionManager
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export class SessionManager {
49
+ constructor(private readonly db: PipelineDB) {}
50
+
51
+ /**
52
+ * Create and persist a new session.
53
+ * Returns the new session ID.
54
+ */
55
+ startSession(repoId?: string, projectId?: string): string {
56
+ const id = `session-${Date.now()}-${randomUUID().slice(0, 4)}`;
57
+ const now = Date.now();
58
+
59
+ this.db.run(
60
+ `INSERT INTO sessions (id, project_id, repo_id, started_at, ended_at, state, observation_count)
61
+ VALUES (?, ?, ?, ?, NULL, '{}', 0)`,
62
+ [id, projectId ?? null, repoId ?? null, now],
63
+ );
64
+
65
+ logger.debug('SessionManager: session started', { id, repoId, projectId });
66
+ return id;
67
+ }
68
+
69
+ /**
70
+ * Mark a session as ended. Idempotent — silently succeeds if already ended.
71
+ */
72
+ endSession(sessionId: string): void {
73
+ this.db.run(
74
+ `UPDATE sessions SET ended_at = ? WHERE id = ? AND ended_at IS NULL`,
75
+ [Date.now(), sessionId],
76
+ );
77
+ logger.debug('SessionManager: session ended', { sessionId });
78
+ }
79
+
80
+ /**
81
+ * Retrieve a session by ID.
82
+ * Returns null if no session with that ID exists.
83
+ */
84
+ getSession(sessionId: string): Session | null {
85
+ const row = this.db.get<SessionRow>(
86
+ `SELECT * FROM sessions WHERE id = ?`,
87
+ [sessionId],
88
+ );
89
+ if (!row) return null;
90
+ return rowToSession(row);
91
+ }
92
+
93
+ /**
94
+ * List sessions, optionally filtered by repoId.
95
+ * Results are ordered most-recent first.
96
+ */
97
+ listSessions(repoId?: string): Session[] {
98
+ let rows: SessionRow[];
99
+ if (repoId) {
100
+ rows = this.db.all<SessionRow>(
101
+ `SELECT * FROM sessions WHERE repo_id = ? ORDER BY started_at DESC`,
102
+ [repoId],
103
+ );
104
+ } else {
105
+ rows = this.db.all<SessionRow>(
106
+ `SELECT * FROM sessions ORDER BY started_at DESC`,
107
+ );
108
+ }
109
+ return rows.map(rowToSession);
110
+ }
111
+
112
+ /**
113
+ * Overwrite the JSON state blob for a session.
114
+ * Callers use this to persist resumption context (current task, agent
115
+ * outputs, phase position) so the supervisor can restore a paused session.
116
+ */
117
+ saveState(sessionId: string, state: Record<string, unknown>): void {
118
+ this.db.run(
119
+ `UPDATE sessions SET state = ? WHERE id = ?`,
120
+ [JSON.stringify(state), sessionId],
121
+ );
122
+ logger.debug('SessionManager: state saved', { sessionId });
123
+ }
124
+
125
+ /**
126
+ * Read the JSON state blob for a session.
127
+ * Returns null if the session does not exist.
128
+ */
129
+ restoreState(sessionId: string): Record<string, unknown> | null {
130
+ const row = this.db.get<{ state: string }>(
131
+ `SELECT state FROM sessions WHERE id = ?`,
132
+ [sessionId],
133
+ );
134
+ if (!row) return null;
135
+ try {
136
+ return JSON.parse(row.state) as Record<string, unknown>;
137
+ } catch {
138
+ return {};
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Increment the observation_count for a session by 1.
144
+ * Called by the observation save path each time a memory is stored.
145
+ */
146
+ incrementObservations(sessionId: string): void {
147
+ this.db.run(
148
+ `UPDATE sessions SET observation_count = observation_count + 1 WHERE id = ?`,
149
+ [sessionId],
150
+ );
151
+ }
152
+ }