@vinaes/succ 1.4.0 → 1.5.42

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 (687) hide show
  1. package/README.md +64 -10
  2. package/dist/cli.js +81 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/agents-md.d.ts.map +1 -1
  5. package/dist/commands/agents-md.js +3 -2
  6. package/dist/commands/agents-md.js.map +1 -1
  7. package/dist/commands/analyze-profile.d.ts.map +1 -1
  8. package/dist/commands/analyze-profile.js +32 -8
  9. package/dist/commands/analyze-profile.js.map +1 -1
  10. package/dist/commands/analyze-recursive.d.ts.map +1 -1
  11. package/dist/commands/analyze-recursive.js +6 -2
  12. package/dist/commands/analyze-recursive.js.map +1 -1
  13. package/dist/commands/analyze-utils.d.ts.map +1 -1
  14. package/dist/commands/analyze-utils.js +17 -4
  15. package/dist/commands/analyze-utils.js.map +1 -1
  16. package/dist/commands/benchmark-quality.d.ts.map +1 -1
  17. package/dist/commands/benchmark-quality.js +11 -4
  18. package/dist/commands/benchmark-quality.js.map +1 -1
  19. package/dist/commands/benchmark-sqlite-vec.d.ts.map +1 -1
  20. package/dist/commands/benchmark-sqlite-vec.js +4 -0
  21. package/dist/commands/benchmark-sqlite-vec.js.map +1 -1
  22. package/dist/commands/benchmark.d.ts.map +1 -1
  23. package/dist/commands/benchmark.js +5 -1
  24. package/dist/commands/benchmark.js.map +1 -1
  25. package/dist/commands/codex-chat.d.ts +8 -0
  26. package/dist/commands/codex-chat.d.ts.map +1 -0
  27. package/dist/commands/codex-chat.js +161 -0
  28. package/dist/commands/codex-chat.js.map +1 -0
  29. package/dist/commands/config.d.ts.map +1 -1
  30. package/dist/commands/config.js +32 -4
  31. package/dist/commands/config.js.map +1 -1
  32. package/dist/commands/daemon.d.ts.map +1 -1
  33. package/dist/commands/daemon.js +13 -4
  34. package/dist/commands/daemon.js.map +1 -1
  35. package/dist/commands/index-code.d.ts +4 -0
  36. package/dist/commands/index-code.d.ts.map +1 -1
  37. package/dist/commands/index-code.js +1 -1
  38. package/dist/commands/index-code.js.map +1 -1
  39. package/dist/commands/init.d.ts.map +1 -1
  40. package/dist/commands/init.js +305 -203
  41. package/dist/commands/init.js.map +1 -1
  42. package/dist/commands/memories.d.ts.map +1 -1
  43. package/dist/commands/memories.js +25 -14
  44. package/dist/commands/memories.js.map +1 -1
  45. package/dist/commands/progress.d.ts.map +1 -1
  46. package/dist/commands/progress.js +3 -2
  47. package/dist/commands/progress.js.map +1 -1
  48. package/dist/commands/reindex.d.ts.map +1 -1
  49. package/dist/commands/reindex.js +54 -36
  50. package/dist/commands/reindex.js.map +1 -1
  51. package/dist/commands/retention.d.ts.map +1 -1
  52. package/dist/commands/retention.js +7 -5
  53. package/dist/commands/retention.js.map +1 -1
  54. package/dist/commands/scan-code.d.ts +76 -0
  55. package/dist/commands/scan-code.d.ts.map +1 -0
  56. package/dist/commands/scan-code.js +385 -0
  57. package/dist/commands/scan-code.js.map +1 -0
  58. package/dist/commands/score.d.ts.map +1 -1
  59. package/dist/commands/score.js +3 -2
  60. package/dist/commands/score.js.map +1 -1
  61. package/dist/commands/session.d.ts +33 -0
  62. package/dist/commands/session.d.ts.map +1 -0
  63. package/dist/commands/session.js +163 -0
  64. package/dist/commands/session.js.map +1 -0
  65. package/dist/commands/setup.d.ts.map +1 -1
  66. package/dist/commands/setup.js +254 -15
  67. package/dist/commands/setup.js.map +1 -1
  68. package/dist/commands/soul.js +3 -2
  69. package/dist/commands/soul.js.map +1 -1
  70. package/dist/commands/status.d.ts.map +1 -1
  71. package/dist/commands/status.js +14 -5
  72. package/dist/commands/status.js.map +1 -1
  73. package/dist/commands/watch.d.ts.map +1 -1
  74. package/dist/commands/watch.js +13 -4
  75. package/dist/commands/watch.js.map +1 -1
  76. package/dist/daemon/analyzer.d.ts.map +1 -1
  77. package/dist/daemon/analyzer.js +21 -5
  78. package/dist/daemon/analyzer.js.map +1 -1
  79. package/dist/daemon/client.d.ts.map +1 -1
  80. package/dist/daemon/client.js +32 -8
  81. package/dist/daemon/client.js.map +1 -1
  82. package/dist/daemon/routes/analyzer.d.ts +3 -0
  83. package/dist/daemon/routes/analyzer.d.ts.map +1 -0
  84. package/dist/daemon/routes/analyzer.js +27 -0
  85. package/dist/daemon/routes/analyzer.js.map +1 -0
  86. package/dist/daemon/routes/hooks.d.ts +14 -0
  87. package/dist/daemon/routes/hooks.d.ts.map +1 -0
  88. package/dist/daemon/routes/hooks.js +1212 -0
  89. package/dist/daemon/routes/hooks.js.map +1 -0
  90. package/dist/daemon/routes/memory.d.ts +4 -0
  91. package/dist/daemon/routes/memory.d.ts.map +1 -0
  92. package/dist/daemon/routes/memory.js +71 -0
  93. package/dist/daemon/routes/memory.js.map +1 -0
  94. package/dist/daemon/routes/reflection.d.ts +10 -0
  95. package/dist/daemon/routes/reflection.d.ts.map +1 -0
  96. package/dist/daemon/routes/reflection.js +397 -0
  97. package/dist/daemon/routes/reflection.js.map +1 -0
  98. package/dist/daemon/routes/search.d.ts +5 -0
  99. package/dist/daemon/routes/search.d.ts.map +1 -0
  100. package/dist/daemon/routes/search.js +93 -0
  101. package/dist/daemon/routes/search.js.map +1 -0
  102. package/dist/daemon/routes/sessions.d.ts +3 -0
  103. package/dist/daemon/routes/sessions.d.ts.map +1 -0
  104. package/dist/daemon/routes/sessions.js +160 -0
  105. package/dist/daemon/routes/sessions.js.map +1 -0
  106. package/dist/daemon/routes/skills.d.ts +3 -0
  107. package/dist/daemon/routes/skills.d.ts.map +1 -0
  108. package/dist/daemon/routes/skills.js +36 -0
  109. package/dist/daemon/routes/skills.js.map +1 -0
  110. package/dist/daemon/routes/status.d.ts +3 -0
  111. package/dist/daemon/routes/status.d.ts.map +1 -0
  112. package/dist/daemon/routes/status.js +47 -0
  113. package/dist/daemon/routes/status.js.map +1 -0
  114. package/dist/daemon/routes/types.d.ts +240 -0
  115. package/dist/daemon/routes/types.d.ts.map +1 -0
  116. package/dist/daemon/routes/types.js +97 -0
  117. package/dist/daemon/routes/types.js.map +1 -0
  118. package/dist/daemon/routes/versioning.d.ts +27 -0
  119. package/dist/daemon/routes/versioning.d.ts.map +1 -0
  120. package/dist/daemon/routes/versioning.js +44 -0
  121. package/dist/daemon/routes/versioning.js.map +1 -0
  122. package/dist/daemon/routes/watcher.d.ts +3 -0
  123. package/dist/daemon/routes/watcher.d.ts.map +1 -0
  124. package/dist/daemon/routes/watcher.js +28 -0
  125. package/dist/daemon/routes/watcher.js.map +1 -0
  126. package/dist/daemon/service.d.ts +5 -23
  127. package/dist/daemon/service.d.ts.map +1 -1
  128. package/dist/daemon/service.js +201 -933
  129. package/dist/daemon/service.js.map +1 -1
  130. package/dist/daemon/session-processor.d.ts +4 -8
  131. package/dist/daemon/session-processor.d.ts.map +1 -1
  132. package/dist/daemon/session-processor.js +39 -38
  133. package/dist/daemon/session-processor.js.map +1 -1
  134. package/dist/lib/ai-readiness.d.ts.map +1 -1
  135. package/dist/lib/ai-readiness.js +33 -8
  136. package/dist/lib/ai-readiness.js.map +1 -1
  137. package/dist/lib/analyze-state.d.ts.map +1 -1
  138. package/dist/lib/analyze-state.js +25 -3
  139. package/dist/lib/analyze-state.js.map +1 -1
  140. package/dist/lib/auto-memory/consolidation.d.ts +41 -0
  141. package/dist/lib/auto-memory/consolidation.d.ts.map +1 -0
  142. package/dist/lib/auto-memory/consolidation.js +151 -0
  143. package/dist/lib/auto-memory/consolidation.js.map +1 -0
  144. package/dist/lib/bpe.d.ts.map +1 -1
  145. package/dist/lib/bpe.js +9 -10
  146. package/dist/lib/bpe.js.map +1 -1
  147. package/dist/lib/brain-export.d.ts +65 -0
  148. package/dist/lib/brain-export.d.ts.map +1 -0
  149. package/dist/lib/brain-export.js +413 -0
  150. package/dist/lib/brain-export.js.map +1 -0
  151. package/dist/lib/checkpoint.d.ts.map +1 -1
  152. package/dist/lib/checkpoint.js +22 -6
  153. package/dist/lib/checkpoint.js.map +1 -1
  154. package/dist/lib/chunker.d.ts.map +1 -1
  155. package/dist/lib/chunker.js +6 -1
  156. package/dist/lib/chunker.js.map +1 -1
  157. package/dist/lib/claude-ws-transport.d.ts.map +1 -1
  158. package/dist/lib/claude-ws-transport.js +12 -4
  159. package/dist/lib/claude-ws-transport.js.map +1 -1
  160. package/dist/lib/command-safety.d.ts +64 -0
  161. package/dist/lib/command-safety.d.ts.map +1 -0
  162. package/dist/lib/command-safety.js +625 -0
  163. package/dist/lib/command-safety.js.map +1 -0
  164. package/dist/lib/compact-briefing.d.ts.map +1 -1
  165. package/dist/lib/compact-briefing.js +10 -13
  166. package/dist/lib/compact-briefing.js.map +1 -1
  167. package/dist/lib/config-defaults.d.ts.map +1 -1
  168. package/dist/lib/config-defaults.js +3 -0
  169. package/dist/lib/config-defaults.js.map +1 -1
  170. package/dist/lib/config-display.d.ts +4 -0
  171. package/dist/lib/config-display.d.ts.map +1 -1
  172. package/dist/lib/config-display.js +6 -1
  173. package/dist/lib/config-display.js.map +1 -1
  174. package/dist/lib/config-types.d.ts +156 -0
  175. package/dist/lib/config-types.d.ts.map +1 -1
  176. package/dist/lib/config-validation.d.ts.map +1 -1
  177. package/dist/lib/config-validation.js +5 -0
  178. package/dist/lib/config-validation.js.map +1 -1
  179. package/dist/lib/config.d.ts.map +1 -1
  180. package/dist/lib/config.js +92 -9
  181. package/dist/lib/config.js.map +1 -1
  182. package/dist/lib/consolidate.d.ts.map +1 -1
  183. package/dist/lib/consolidate.js +66 -47
  184. package/dist/lib/consolidate.js.map +1 -1
  185. package/dist/lib/content-sanitizer.d.ts +29 -0
  186. package/dist/lib/content-sanitizer.d.ts.map +1 -0
  187. package/dist/lib/content-sanitizer.js +72 -0
  188. package/dist/lib/content-sanitizer.js.map +1 -0
  189. package/dist/lib/cross-repo.d.ts +44 -0
  190. package/dist/lib/cross-repo.d.ts.map +1 -0
  191. package/dist/lib/cross-repo.js +108 -0
  192. package/dist/lib/cross-repo.js.map +1 -0
  193. package/dist/lib/daemon-port.d.ts +12 -0
  194. package/dist/lib/daemon-port.d.ts.map +1 -0
  195. package/dist/lib/daemon-port.js +20 -0
  196. package/dist/lib/daemon-port.js.map +1 -0
  197. package/dist/lib/db/auto-memory.d.ts +40 -0
  198. package/dist/lib/db/auto-memory.d.ts.map +1 -0
  199. package/dist/lib/db/auto-memory.js +74 -0
  200. package/dist/lib/db/auto-memory.js.map +1 -0
  201. package/dist/lib/db/bm25-indexes.d.ts.map +1 -1
  202. package/dist/lib/db/bm25-indexes.js +16 -4
  203. package/dist/lib/db/bm25-indexes.js.map +1 -1
  204. package/dist/lib/db/documents.d.ts.map +1 -1
  205. package/dist/lib/db/documents.js +4 -1
  206. package/dist/lib/db/documents.js.map +1 -1
  207. package/dist/lib/db/global-memories.d.ts +2 -10
  208. package/dist/lib/db/global-memories.d.ts.map +1 -1
  209. package/dist/lib/db/global-memories.js +13 -6
  210. package/dist/lib/db/global-memories.js.map +1 -1
  211. package/dist/lib/db/graph.d.ts +5 -1
  212. package/dist/lib/db/graph.d.ts.map +1 -1
  213. package/dist/lib/db/graph.js +38 -8
  214. package/dist/lib/db/graph.js.map +1 -1
  215. package/dist/lib/db/hybrid-search.d.ts +4 -2
  216. package/dist/lib/db/hybrid-search.d.ts.map +1 -1
  217. package/dist/lib/db/hybrid-search.js +29 -11
  218. package/dist/lib/db/hybrid-search.js.map +1 -1
  219. package/dist/lib/db/index.d.ts +6 -1
  220. package/dist/lib/db/index.d.ts.map +1 -1
  221. package/dist/lib/db/index.js +5 -1
  222. package/dist/lib/db/index.js.map +1 -1
  223. package/dist/lib/db/memories.d.ts +19 -14
  224. package/dist/lib/db/memories.d.ts.map +1 -1
  225. package/dist/lib/db/memories.js +100 -37
  226. package/dist/lib/db/memories.js.map +1 -1
  227. package/dist/lib/db/parse-helpers.d.ts +14 -0
  228. package/dist/lib/db/parse-helpers.d.ts.map +1 -0
  229. package/dist/lib/db/parse-helpers.js +59 -0
  230. package/dist/lib/db/parse-helpers.js.map +1 -0
  231. package/dist/lib/db/recall-events.d.ts +49 -0
  232. package/dist/lib/db/recall-events.d.ts.map +1 -0
  233. package/dist/lib/db/recall-events.js +196 -0
  234. package/dist/lib/db/recall-events.js.map +1 -0
  235. package/dist/lib/db/retention.d.ts +4 -3
  236. package/dist/lib/db/retention.d.ts.map +1 -1
  237. package/dist/lib/db/retention.js +12 -1
  238. package/dist/lib/db/retention.js.map +1 -1
  239. package/dist/lib/db/schema.d.ts +2 -0
  240. package/dist/lib/db/schema.d.ts.map +1 -1
  241. package/dist/lib/db/schema.js +140 -80
  242. package/dist/lib/db/schema.js.map +1 -1
  243. package/dist/lib/db/skills.d.ts.map +1 -1
  244. package/dist/lib/db/skills.js +10 -6
  245. package/dist/lib/db/skills.js.map +1 -1
  246. package/dist/lib/diff-brain.d.ts +24 -0
  247. package/dist/lib/diff-brain.d.ts.map +1 -0
  248. package/dist/lib/diff-brain.js +114 -0
  249. package/dist/lib/diff-brain.js.map +1 -0
  250. package/dist/lib/diff-parser.d.ts +74 -0
  251. package/dist/lib/diff-parser.d.ts.map +1 -0
  252. package/dist/lib/diff-parser.js +200 -0
  253. package/dist/lib/diff-parser.js.map +1 -0
  254. package/dist/lib/embedding-pool.d.ts.map +1 -1
  255. package/dist/lib/embedding-pool.js +5 -1
  256. package/dist/lib/embedding-pool.js.map +1 -1
  257. package/dist/lib/embeddings.d.ts +12 -0
  258. package/dist/lib/embeddings.d.ts.map +1 -1
  259. package/dist/lib/embeddings.js +77 -19
  260. package/dist/lib/embeddings.js.map +1 -1
  261. package/dist/lib/errors.d.ts +2 -0
  262. package/dist/lib/errors.d.ts.map +1 -1
  263. package/dist/lib/errors.js +4 -0
  264. package/dist/lib/errors.js.map +1 -1
  265. package/dist/lib/fault-logger.d.ts.map +1 -1
  266. package/dist/lib/fault-logger.js +22 -7
  267. package/dist/lib/fault-logger.js.map +1 -1
  268. package/dist/lib/git/co-change.d.ts +39 -0
  269. package/dist/lib/git/co-change.d.ts.map +1 -0
  270. package/dist/lib/git/co-change.js +139 -0
  271. package/dist/lib/git/co-change.js.map +1 -0
  272. package/dist/lib/graph/bridge-edges.d.ts +93 -0
  273. package/dist/lib/graph/bridge-edges.d.ts.map +1 -0
  274. package/dist/lib/graph/bridge-edges.js +276 -0
  275. package/dist/lib/graph/bridge-edges.js.map +1 -0
  276. package/dist/lib/graph/centrality.d.ts +11 -0
  277. package/dist/lib/graph/centrality.d.ts.map +1 -1
  278. package/dist/lib/graph/centrality.js +51 -3
  279. package/dist/lib/graph/centrality.js.map +1 -1
  280. package/dist/lib/graph/cleanup.d.ts.map +1 -1
  281. package/dist/lib/graph/cleanup.js +2 -1
  282. package/dist/lib/graph/cleanup.js.map +1 -1
  283. package/dist/lib/graph/community-detection.d.ts +17 -2
  284. package/dist/lib/graph/community-detection.d.ts.map +1 -1
  285. package/dist/lib/graph/community-detection.js +147 -48
  286. package/dist/lib/graph/community-detection.js.map +1 -1
  287. package/dist/lib/graph/community-summaries.d.ts +26 -0
  288. package/dist/lib/graph/community-summaries.d.ts.map +1 -0
  289. package/dist/lib/graph/community-summaries.js +130 -0
  290. package/dist/lib/graph/community-summaries.js.map +1 -0
  291. package/dist/lib/graph/contextual-proximity.d.ts.map +1 -1
  292. package/dist/lib/graph/contextual-proximity.js +11 -4
  293. package/dist/lib/graph/contextual-proximity.js.map +1 -1
  294. package/dist/lib/graph/graphology-bridge.d.ts +101 -0
  295. package/dist/lib/graph/graphology-bridge.d.ts.map +1 -0
  296. package/dist/lib/graph/graphology-bridge.js +488 -0
  297. package/dist/lib/graph/graphology-bridge.js.map +1 -0
  298. package/dist/lib/graph/llm-relations.d.ts.map +1 -1
  299. package/dist/lib/graph/llm-relations.js +27 -5
  300. package/dist/lib/graph/llm-relations.js.map +1 -1
  301. package/dist/lib/graph-export.d.ts.map +1 -1
  302. package/dist/lib/graph-export.js +2 -2
  303. package/dist/lib/graph-export.js.map +1 -1
  304. package/dist/lib/graph-scheduler.d.ts +0 -5
  305. package/dist/lib/graph-scheduler.d.ts.map +1 -1
  306. package/dist/lib/graph-scheduler.js +5 -1
  307. package/dist/lib/graph-scheduler.js.map +1 -1
  308. package/dist/lib/guardrails.d.ts +50 -0
  309. package/dist/lib/guardrails.d.ts.map +1 -0
  310. package/dist/lib/guardrails.js +502 -0
  311. package/dist/lib/guardrails.js.map +1 -0
  312. package/dist/lib/hook-rules.d.ts +1 -1
  313. package/dist/lib/hook-rules.d.ts.map +1 -1
  314. package/dist/lib/hook-rules.js +8 -2
  315. package/dist/lib/hook-rules.js.map +1 -1
  316. package/dist/lib/ifc/file-labels.d.ts +35 -0
  317. package/dist/lib/ifc/file-labels.d.ts.map +1 -0
  318. package/dist/lib/ifc/file-labels.js +208 -0
  319. package/dist/lib/ifc/file-labels.js.map +1 -0
  320. package/dist/lib/ifc/label.d.ts +38 -0
  321. package/dist/lib/ifc/label.d.ts.map +1 -0
  322. package/dist/lib/ifc/label.js +80 -0
  323. package/dist/lib/ifc/label.js.map +1 -0
  324. package/dist/lib/ifc/session-ifc.d.ts +92 -0
  325. package/dist/lib/ifc/session-ifc.d.ts.map +1 -0
  326. package/dist/lib/ifc/session-ifc.js +222 -0
  327. package/dist/lib/ifc/session-ifc.js.map +1 -0
  328. package/dist/lib/indexer.js +2 -2
  329. package/dist/lib/indexer.js.map +1 -1
  330. package/dist/lib/injection-detector.d.ts +83 -0
  331. package/dist/lib/injection-detector.d.ts.map +1 -0
  332. package/dist/lib/injection-detector.js +586 -0
  333. package/dist/lib/injection-detector.js.map +1 -0
  334. package/dist/lib/injection-semantic.d.ts +31 -0
  335. package/dist/lib/injection-semantic.d.ts.map +1 -0
  336. package/dist/lib/injection-semantic.js +230 -0
  337. package/dist/lib/injection-semantic.js.map +1 -0
  338. package/dist/lib/llm.d.ts.map +1 -1
  339. package/dist/lib/llm.js +19 -4
  340. package/dist/lib/llm.js.map +1 -1
  341. package/dist/lib/lock.d.ts.map +1 -1
  342. package/dist/lib/lock.js +24 -3
  343. package/dist/lib/lock.js.map +1 -1
  344. package/dist/lib/md-fetch.d.ts.map +1 -1
  345. package/dist/lib/md-fetch.js +9 -2
  346. package/dist/lib/md-fetch.js.map +1 -1
  347. package/dist/lib/observability.d.ts +75 -0
  348. package/dist/lib/observability.d.ts.map +1 -0
  349. package/dist/lib/observability.js +201 -0
  350. package/dist/lib/observability.js.map +1 -0
  351. package/dist/lib/ort-session.d.ts +26 -0
  352. package/dist/lib/ort-session.d.ts.map +1 -1
  353. package/dist/lib/ort-session.js +107 -3
  354. package/dist/lib/ort-session.js.map +1 -1
  355. package/dist/lib/prd/codebase-context.d.ts.map +1 -1
  356. package/dist/lib/prd/codebase-context.js +9 -2
  357. package/dist/lib/prd/codebase-context.js.map +1 -1
  358. package/dist/lib/prd/context.d.ts.map +1 -1
  359. package/dist/lib/prd/context.js +11 -3
  360. package/dist/lib/prd/context.js.map +1 -1
  361. package/dist/lib/prd/export.js +1 -1
  362. package/dist/lib/prd/export.js.map +1 -1
  363. package/dist/lib/prd/generate.d.ts.map +1 -1
  364. package/dist/lib/prd/generate.js +17 -4
  365. package/dist/lib/prd/generate.js.map +1 -1
  366. package/dist/lib/prd/parse.d.ts.map +1 -1
  367. package/dist/lib/prd/parse.js +6 -1
  368. package/dist/lib/prd/parse.js.map +1 -1
  369. package/dist/lib/prd/runner.d.ts +1 -2
  370. package/dist/lib/prd/runner.d.ts.map +1 -1
  371. package/dist/lib/prd/runner.js +43 -32
  372. package/dist/lib/prd/runner.js.map +1 -1
  373. package/dist/lib/prd/worktree.d.ts +1 -2
  374. package/dist/lib/prd/worktree.d.ts.map +1 -1
  375. package/dist/lib/prd/worktree.js +62 -70
  376. package/dist/lib/prd/worktree.js.map +1 -1
  377. package/dist/lib/precompute-context.d.ts.map +1 -1
  378. package/dist/lib/precompute-context.js +15 -34
  379. package/dist/lib/precompute-context.js.map +1 -1
  380. package/dist/lib/pricing.d.ts.map +1 -1
  381. package/dist/lib/pricing.js +5 -1
  382. package/dist/lib/pricing.js.map +1 -1
  383. package/dist/lib/process-registry.js +3 -3
  384. package/dist/lib/process-registry.js.map +1 -1
  385. package/dist/lib/public-api.d.ts +41 -1
  386. package/dist/lib/public-api.d.ts.map +1 -1
  387. package/dist/lib/public-api.js +38 -0
  388. package/dist/lib/public-api.js.map +1 -1
  389. package/dist/lib/quality.d.ts.map +1 -1
  390. package/dist/lib/quality.js +15 -6
  391. package/dist/lib/quality.js.map +1 -1
  392. package/dist/lib/query-expansion.d.ts +32 -0
  393. package/dist/lib/query-expansion.d.ts.map +1 -1
  394. package/dist/lib/query-expansion.js +62 -1
  395. package/dist/lib/query-expansion.js.map +1 -1
  396. package/dist/lib/reference-embeddings.d.ts.map +1 -1
  397. package/dist/lib/reference-embeddings.js +17 -4
  398. package/dist/lib/reference-embeddings.js.map +1 -1
  399. package/dist/lib/reflection-synthesizer.d.ts.map +1 -1
  400. package/dist/lib/reflection-synthesizer.js +7 -1
  401. package/dist/lib/reflection-synthesizer.js.map +1 -1
  402. package/dist/lib/reranker.d.ts +41 -0
  403. package/dist/lib/reranker.d.ts.map +1 -0
  404. package/dist/lib/reranker.js +294 -0
  405. package/dist/lib/reranker.js.map +1 -0
  406. package/dist/lib/retrieval-feedback.d.ts +100 -0
  407. package/dist/lib/retrieval-feedback.d.ts.map +1 -0
  408. package/dist/lib/retrieval-feedback.js +174 -0
  409. package/dist/lib/retrieval-feedback.js.map +1 -0
  410. package/dist/lib/review/context-pack.d.ts +58 -0
  411. package/dist/lib/review/context-pack.d.ts.map +1 -0
  412. package/dist/lib/review/context-pack.js +300 -0
  413. package/dist/lib/review/context-pack.js.map +1 -0
  414. package/dist/lib/search/hierarchical-summaries.d.ts +65 -0
  415. package/dist/lib/search/hierarchical-summaries.d.ts.map +1 -0
  416. package/dist/lib/search/hierarchical-summaries.js +423 -0
  417. package/dist/lib/search/hierarchical-summaries.js.map +1 -0
  418. package/dist/lib/search/hyde.d.ts +27 -0
  419. package/dist/lib/search/hyde.d.ts.map +1 -0
  420. package/dist/lib/search/hyde.js +141 -0
  421. package/dist/lib/search/hyde.js.map +1 -0
  422. package/dist/lib/search/late-chunking.d.ts +53 -0
  423. package/dist/lib/search/late-chunking.d.ts.map +1 -0
  424. package/dist/lib/search/late-chunking.js +230 -0
  425. package/dist/lib/search/late-chunking.js.map +1 -0
  426. package/dist/lib/search/ppr-retrieval.d.ts +49 -0
  427. package/dist/lib/search/ppr-retrieval.d.ts.map +1 -0
  428. package/dist/lib/search/ppr-retrieval.js +135 -0
  429. package/dist/lib/search/ppr-retrieval.js.map +1 -0
  430. package/dist/lib/search/repo-map.d.ts +43 -0
  431. package/dist/lib/search/repo-map.d.ts.map +1 -0
  432. package/dist/lib/search/repo-map.js +165 -0
  433. package/dist/lib/search/repo-map.js.map +1 -0
  434. package/dist/lib/session-analyzer.d.ts +90 -0
  435. package/dist/lib/session-analyzer.d.ts.map +1 -0
  436. package/dist/lib/session-analyzer.js +467 -0
  437. package/dist/lib/session-analyzer.js.map +1 -0
  438. package/dist/lib/session-observations.d.ts.map +1 -1
  439. package/dist/lib/session-observations.js +13 -3
  440. package/dist/lib/session-observations.js.map +1 -1
  441. package/dist/lib/session-summary.d.ts.map +1 -1
  442. package/dist/lib/session-summary.js +57 -50
  443. package/dist/lib/session-summary.js.map +1 -1
  444. package/dist/lib/session-surgeon.d.ts +53 -0
  445. package/dist/lib/session-surgeon.d.ts.map +1 -0
  446. package/dist/lib/session-surgeon.js +501 -0
  447. package/dist/lib/session-surgeon.js.map +1 -0
  448. package/dist/lib/similarity-utils.d.ts +26 -0
  449. package/dist/lib/similarity-utils.d.ts.map +1 -0
  450. package/dist/lib/similarity-utils.js +66 -0
  451. package/dist/lib/similarity-utils.js.map +1 -0
  452. package/dist/lib/skills.d.ts.map +1 -1
  453. package/dist/lib/skills.js +11 -11
  454. package/dist/lib/skills.js.map +1 -1
  455. package/dist/lib/storage/backends/interface.d.ts +13 -3
  456. package/dist/lib/storage/backends/interface.d.ts.map +1 -1
  457. package/dist/lib/storage/backends/postgresql.d.ts +52 -3
  458. package/dist/lib/storage/backends/postgresql.d.ts.map +1 -1
  459. package/dist/lib/storage/backends/postgresql.js +694 -49
  460. package/dist/lib/storage/backends/postgresql.js.map +1 -1
  461. package/dist/lib/storage/benchmark.js +2 -2
  462. package/dist/lib/storage/benchmark.js.map +1 -1
  463. package/dist/lib/storage/dispatcher/base.d.ts +114 -0
  464. package/dist/lib/storage/dispatcher/base.d.ts.map +1 -0
  465. package/dist/lib/storage/dispatcher/base.js +160 -0
  466. package/dist/lib/storage/dispatcher/base.js.map +1 -0
  467. package/dist/lib/storage/dispatcher/documents.d.ts +25 -0
  468. package/dist/lib/storage/dispatcher/documents.d.ts.map +1 -0
  469. package/dist/lib/storage/dispatcher/documents.js +194 -0
  470. package/dist/lib/storage/dispatcher/documents.js.map +1 -0
  471. package/dist/lib/storage/dispatcher/embeddings.d.ts +34 -0
  472. package/dist/lib/storage/dispatcher/embeddings.d.ts.map +1 -0
  473. package/dist/lib/storage/dispatcher/embeddings.js +144 -0
  474. package/dist/lib/storage/dispatcher/embeddings.js.map +1 -0
  475. package/dist/lib/storage/dispatcher/export-import.d.ts +139 -0
  476. package/dist/lib/storage/dispatcher/export-import.d.ts.map +1 -0
  477. package/dist/lib/storage/dispatcher/export-import.js +191 -0
  478. package/dist/lib/storage/dispatcher/export-import.js.map +1 -0
  479. package/dist/lib/storage/dispatcher/file-hashes.d.ts +13 -0
  480. package/dist/lib/storage/dispatcher/file-hashes.d.ts.map +1 -0
  481. package/dist/lib/storage/dispatcher/file-hashes.js +36 -0
  482. package/dist/lib/storage/dispatcher/file-hashes.js.map +1 -0
  483. package/dist/lib/storage/dispatcher/global-memories.d.ts +28 -0
  484. package/dist/lib/storage/dispatcher/global-memories.d.ts.map +1 -0
  485. package/dist/lib/storage/dispatcher/global-memories.js +151 -0
  486. package/dist/lib/storage/dispatcher/global-memories.js.map +1 -0
  487. package/dist/lib/storage/dispatcher/graph.d.ts +32 -0
  488. package/dist/lib/storage/dispatcher/graph.d.ts.map +1 -0
  489. package/dist/lib/storage/dispatcher/graph.js +146 -0
  490. package/dist/lib/storage/dispatcher/graph.js.map +1 -0
  491. package/dist/lib/storage/dispatcher/index.d.ts +34 -0
  492. package/dist/lib/storage/dispatcher/index.d.ts.map +1 -0
  493. package/dist/lib/storage/dispatcher/index.js +139 -0
  494. package/dist/lib/storage/dispatcher/index.js.map +1 -0
  495. package/dist/lib/storage/dispatcher/memories.d.ts +65 -0
  496. package/dist/lib/storage/dispatcher/memories.d.ts.map +1 -0
  497. package/dist/lib/storage/dispatcher/memories.js +466 -0
  498. package/dist/lib/storage/dispatcher/memories.js.map +1 -0
  499. package/dist/lib/storage/dispatcher/mixin-helper.d.ts +6 -0
  500. package/dist/lib/storage/dispatcher/mixin-helper.d.ts.map +1 -0
  501. package/dist/lib/storage/dispatcher/mixin-helper.js +10 -0
  502. package/dist/lib/storage/dispatcher/mixin-helper.js.map +1 -0
  503. package/dist/lib/storage/dispatcher/retention.d.ts +20 -0
  504. package/dist/lib/storage/dispatcher/retention.d.ts.map +1 -0
  505. package/dist/lib/storage/dispatcher/retention.js +123 -0
  506. package/dist/lib/storage/dispatcher/retention.js.map +1 -0
  507. package/dist/lib/storage/dispatcher/search.d.ts +34 -0
  508. package/dist/lib/storage/dispatcher/search.d.ts.map +1 -0
  509. package/dist/lib/storage/dispatcher/search.js +222 -0
  510. package/dist/lib/storage/dispatcher/search.js.map +1 -0
  511. package/dist/lib/storage/dispatcher/skills.d.ts +53 -0
  512. package/dist/lib/storage/dispatcher/skills.d.ts.map +1 -0
  513. package/dist/lib/storage/dispatcher/skills.js +98 -0
  514. package/dist/lib/storage/dispatcher/skills.js.map +1 -0
  515. package/dist/lib/storage/dispatcher/token-stats.d.ts +23 -0
  516. package/dist/lib/storage/dispatcher/token-stats.d.ts.map +1 -0
  517. package/dist/lib/storage/dispatcher/token-stats.js +92 -0
  518. package/dist/lib/storage/dispatcher/token-stats.js.map +1 -0
  519. package/dist/lib/storage/dispatcher/web-search.d.ts +10 -0
  520. package/dist/lib/storage/dispatcher/web-search.d.ts.map +1 -0
  521. package/dist/lib/storage/dispatcher/web-search.js +39 -0
  522. package/dist/lib/storage/dispatcher/web-search.js.map +1 -0
  523. package/dist/lib/storage/dispatcher-export.d.ts.map +1 -1
  524. package/dist/lib/storage/dispatcher-export.js +48 -39
  525. package/dist/lib/storage/dispatcher-export.js.map +1 -1
  526. package/dist/lib/storage/dispatcher.d.ts +1 -468
  527. package/dist/lib/storage/dispatcher.d.ts.map +1 -1
  528. package/dist/lib/storage/dispatcher.js +1 -1931
  529. package/dist/lib/storage/dispatcher.js.map +1 -1
  530. package/dist/lib/storage/index.d.ts +20 -5
  531. package/dist/lib/storage/index.d.ts.map +1 -1
  532. package/dist/lib/storage/index.js +36 -7
  533. package/dist/lib/storage/index.js.map +1 -1
  534. package/dist/lib/storage/migration/export-import.d.ts.map +1 -1
  535. package/dist/lib/storage/migration/export-import.js +9 -2
  536. package/dist/lib/storage/migration/export-import.js.map +1 -1
  537. package/dist/lib/storage/types.d.ts +152 -10
  538. package/dist/lib/storage/types.d.ts.map +1 -1
  539. package/dist/lib/storage/types.js +13 -0
  540. package/dist/lib/storage/types.js.map +1 -1
  541. package/dist/lib/storage/vector/interface.d.ts +4 -0
  542. package/dist/lib/storage/vector/interface.d.ts.map +1 -1
  543. package/dist/lib/storage/vector/qdrant.d.ts +13 -2
  544. package/dist/lib/storage/vector/qdrant.d.ts.map +1 -1
  545. package/dist/lib/storage/vector/qdrant.js +147 -61
  546. package/dist/lib/storage/vector/qdrant.js.map +1 -1
  547. package/dist/lib/token-budget.d.ts.map +1 -1
  548. package/dist/lib/token-budget.js +9 -2
  549. package/dist/lib/token-budget.js.map +1 -1
  550. package/dist/lib/transcript-utils.d.ts +60 -0
  551. package/dist/lib/transcript-utils.d.ts.map +1 -0
  552. package/dist/lib/transcript-utils.js +69 -0
  553. package/dist/lib/transcript-utils.js.map +1 -0
  554. package/dist/lib/tree-sitter/extractor.d.ts +1 -1
  555. package/dist/lib/tree-sitter/extractor.d.ts.map +1 -1
  556. package/dist/lib/tree-sitter/extractor.js +34 -9
  557. package/dist/lib/tree-sitter/extractor.js.map +1 -1
  558. package/dist/lib/tree-sitter/parser.d.ts.map +1 -1
  559. package/dist/lib/tree-sitter/parser.js +45 -11
  560. package/dist/lib/tree-sitter/parser.js.map +1 -1
  561. package/dist/lib/tree-sitter/public.d.ts +12 -0
  562. package/dist/lib/tree-sitter/public.d.ts.map +1 -1
  563. package/dist/lib/tree-sitter/public.js +33 -1
  564. package/dist/lib/tree-sitter/public.js.map +1 -1
  565. package/dist/lib/tree-sitter/queries.d.ts.map +1 -1
  566. package/dist/lib/tree-sitter/queries.js +8 -0
  567. package/dist/lib/tree-sitter/queries.js.map +1 -1
  568. package/dist/lib/version-check.d.ts +29 -0
  569. package/dist/lib/version-check.d.ts.map +1 -0
  570. package/dist/lib/version-check.js +187 -0
  571. package/dist/lib/version-check.js.map +1 -0
  572. package/dist/lib/working-memory-pipeline.d.ts.map +1 -1
  573. package/dist/lib/working-memory-pipeline.js +12 -3
  574. package/dist/lib/working-memory-pipeline.js.map +1 -1
  575. package/dist/lib/worktree-detect.d.ts +43 -0
  576. package/dist/lib/worktree-detect.d.ts.map +1 -0
  577. package/dist/lib/worktree-detect.js +154 -0
  578. package/dist/lib/worktree-detect.js.map +1 -0
  579. package/dist/lsp/client.d.ts +96 -0
  580. package/dist/lsp/client.d.ts.map +1 -0
  581. package/dist/lsp/client.js +435 -0
  582. package/dist/lsp/client.js.map +1 -0
  583. package/dist/lsp/installer.d.ts +39 -0
  584. package/dist/lsp/installer.d.ts.map +1 -0
  585. package/dist/lsp/installer.js +275 -0
  586. package/dist/lsp/installer.js.map +1 -0
  587. package/dist/lsp/manager.d.ts +62 -0
  588. package/dist/lsp/manager.d.ts.map +1 -0
  589. package/dist/lsp/manager.js +234 -0
  590. package/dist/lsp/manager.js.map +1 -0
  591. package/dist/lsp/servers.d.ts +52 -0
  592. package/dist/lsp/servers.d.ts.map +1 -0
  593. package/dist/lsp/servers.js +162 -0
  594. package/dist/lsp/servers.js.map +1 -0
  595. package/dist/mcp/helpers.d.ts.map +1 -1
  596. package/dist/mcp/helpers.js +8 -2
  597. package/dist/mcp/helpers.js.map +1 -1
  598. package/dist/mcp/profile.js +1 -1
  599. package/dist/mcp/server.d.ts +3 -2
  600. package/dist/mcp/server.d.ts.map +1 -1
  601. package/dist/mcp/server.js +19 -7
  602. package/dist/mcp/server.js.map +1 -1
  603. package/dist/mcp/tools/config.d.ts.map +1 -1
  604. package/dist/mcp/tools/config.js +28 -118
  605. package/dist/mcp/tools/config.js.map +1 -1
  606. package/dist/mcp/tools/dead-end.d.ts.map +1 -1
  607. package/dist/mcp/tools/dead-end.js +4 -3
  608. package/dist/mcp/tools/dead-end.js.map +1 -1
  609. package/dist/mcp/tools/debug.d.ts.map +1 -1
  610. package/dist/mcp/tools/debug.js +27 -112
  611. package/dist/mcp/tools/debug.js.map +1 -1
  612. package/dist/mcp/tools/graph.d.ts.map +1 -1
  613. package/dist/mcp/tools/graph.js +164 -176
  614. package/dist/mcp/tools/graph.js.map +1 -1
  615. package/dist/mcp/tools/indexing.d.ts +1 -1
  616. package/dist/mcp/tools/indexing.d.ts.map +1 -1
  617. package/dist/mcp/tools/indexing.js +63 -164
  618. package/dist/mcp/tools/indexing.js.map +1 -1
  619. package/dist/mcp/tools/memory/forget.d.ts +3 -0
  620. package/dist/mcp/tools/memory/forget.d.ts.map +1 -0
  621. package/dist/mcp/tools/memory/forget.js +175 -0
  622. package/dist/mcp/tools/memory/forget.js.map +1 -0
  623. package/dist/mcp/tools/memory/memory-helpers.d.ts +45 -0
  624. package/dist/mcp/tools/memory/memory-helpers.d.ts.map +1 -0
  625. package/dist/mcp/tools/memory/memory-helpers.js +291 -0
  626. package/dist/mcp/tools/memory/memory-helpers.js.map +1 -0
  627. package/dist/mcp/tools/memory/recall.d.ts +3 -0
  628. package/dist/mcp/tools/memory/recall.d.ts.map +1 -0
  629. package/dist/mcp/tools/memory/recall.js +495 -0
  630. package/dist/mcp/tools/memory/recall.js.map +1 -0
  631. package/dist/mcp/tools/memory/remember.d.ts +3 -0
  632. package/dist/mcp/tools/memory/remember.d.ts.map +1 -0
  633. package/dist/mcp/tools/memory/remember.js +256 -0
  634. package/dist/mcp/tools/memory/remember.js.map +1 -0
  635. package/dist/mcp/tools/memory/temporal-query.d.ts +8 -0
  636. package/dist/mcp/tools/memory/temporal-query.d.ts.map +1 -0
  637. package/dist/mcp/tools/memory/temporal-query.js +68 -0
  638. package/dist/mcp/tools/memory/temporal-query.js.map +1 -0
  639. package/dist/mcp/tools/memory.d.ts +0 -11
  640. package/dist/mcp/tools/memory.d.ts.map +1 -1
  641. package/dist/mcp/tools/memory.js +6 -1228
  642. package/dist/mcp/tools/memory.js.map +1 -1
  643. package/dist/mcp/tools/prd.d.ts.map +1 -1
  644. package/dist/mcp/tools/prd.js +19 -70
  645. package/dist/mcp/tools/prd.js.map +1 -1
  646. package/dist/mcp/tools/review.d.ts +8 -0
  647. package/dist/mcp/tools/review.d.ts.map +1 -0
  648. package/dist/mcp/tools/review.js +133 -0
  649. package/dist/mcp/tools/review.js.map +1 -0
  650. package/dist/mcp/tools/search.d.ts.map +1 -1
  651. package/dist/mcp/tools/search.js +79 -8
  652. package/dist/mcp/tools/search.js.map +1 -1
  653. package/dist/mcp/tools/status.d.ts.map +1 -1
  654. package/dist/mcp/tools/status.js +50 -75
  655. package/dist/mcp/tools/status.js.map +1 -1
  656. package/dist/mcp/tools/web-fetch.d.ts.map +1 -1
  657. package/dist/mcp/tools/web-fetch.js +5 -1
  658. package/dist/mcp/tools/web-fetch.js.map +1 -1
  659. package/dist/mcp/tools/web-search.d.ts.map +1 -1
  660. package/dist/mcp/tools/web-search.js +25 -103
  661. package/dist/mcp/tools/web-search.js.map +1 -1
  662. package/dist/prompts/guardrails.d.ts +14 -0
  663. package/dist/prompts/guardrails.d.ts.map +1 -0
  664. package/dist/prompts/guardrails.js +115 -0
  665. package/dist/prompts/guardrails.js.map +1 -0
  666. package/dist/prompts/index.d.ts +2 -1
  667. package/dist/prompts/index.d.ts.map +1 -1
  668. package/dist/prompts/index.js +3 -1
  669. package/dist/prompts/index.js.map +1 -1
  670. package/dist/prompts/prd.d.ts +0 -2
  671. package/dist/prompts/prd.d.ts.map +1 -1
  672. package/dist/prompts/prd.js +0 -2
  673. package/dist/prompts/prd.js.map +1 -1
  674. package/hooks/core/__tests__/adapter.test.cjs +340 -0
  675. package/hooks/core/adapter.cjs +463 -0
  676. package/hooks/core/config.cjs +83 -0
  677. package/hooks/core/daemon-boot.cjs +140 -0
  678. package/hooks/core/log.cjs +41 -0
  679. package/hooks/core/worktree.cjs +119 -0
  680. package/hooks/succ-post-tool.cjs +198 -134
  681. package/hooks/succ-pre-compact.cjs +262 -0
  682. package/hooks/succ-pre-tool.cjs +526 -182
  683. package/hooks/succ-session-end.cjs +40 -64
  684. package/hooks/succ-session-start.cjs +528 -427
  685. package/hooks/succ-stop-reflection.cjs +36 -62
  686. package/hooks/succ-user-prompt.cjs +137 -180
  687. package/package.json +17 -6
@@ -9,7 +9,10 @@
9
9
  * - vector(N) type from pgvector extension
10
10
  * - ON CONFLICT instead of INSERT OR REPLACE
11
11
  */
12
+ import { SOURCE_TYPES } from '../types.js';
12
13
  import { StorageError, ConfigError } from '../../errors.js';
14
+ import { tokenizeCode, tokenizeCodeWithAST, tokenizeDocs, reciprocalRankFusion, } from '../../bm25.js';
15
+ import { logWarn } from '../../fault-logger.js';
13
16
  // Lazy-load pg to make it optional
14
17
  let pg = null;
15
18
  async function loadPg() {
@@ -19,7 +22,10 @@ async function loadPg() {
19
22
  pg = await import('pg');
20
23
  return pg;
21
24
  }
22
- catch {
25
+ catch (error) {
26
+ logWarn('postgresql', 'Failed to import pg package for PostgreSQL backend', {
27
+ error: error instanceof Error ? error.message : String(error),
28
+ });
23
29
  throw new ConfigError('PostgreSQL support requires the "pg" package. ' + 'Install it with: npm install pg');
24
30
  }
25
31
  }
@@ -105,7 +111,10 @@ export class PostgresBackend {
105
111
  const { getEmbeddingInfo } = await import('../../embeddings.js');
106
112
  return getEmbeddingInfo().dimensions ?? 384;
107
113
  }
108
- catch {
114
+ catch (error) {
115
+ logWarn('postgresql', 'Failed to read embedding dimensions, using default 384', {
116
+ error: error instanceof Error ? error.message : String(error),
117
+ });
109
118
  return 384;
110
119
  }
111
120
  }
@@ -193,6 +202,9 @@ export class PostgresBackend {
193
202
  await pool.query('CREATE INDEX IF NOT EXISTS idx_documents_project_id ON documents(project_id)');
194
203
  await pool.query('CREATE INDEX IF NOT EXISTS idx_documents_file_path ON documents(file_path)');
195
204
  await pool.query('CREATE INDEX IF NOT EXISTS idx_documents_embedding ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100)');
205
+ // Migration: add search_vector column for full-text search (tsvector)
206
+ await pool.query('ALTER TABLE documents ADD COLUMN IF NOT EXISTS search_vector tsvector');
207
+ await pool.query('CREATE INDEX IF NOT EXISTS idx_documents_search_vector ON documents USING GIN(search_vector)');
196
208
  // Metadata table
197
209
  await pool.query(`
198
210
  CREATE TABLE IF NOT EXISTS metadata (
@@ -264,6 +276,9 @@ export class PostgresBackend {
264
276
  await pool.query('CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type)');
265
277
  await pool.query('CREATE INDEX IF NOT EXISTS idx_memories_project_id ON memories(project_id)');
266
278
  await pool.query('CREATE INDEX IF NOT EXISTS idx_memories_embedding ON memories USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100)');
279
+ // Migration: add search_vector column for full-text search (tsvector)
280
+ await pool.query('ALTER TABLE memories ADD COLUMN IF NOT EXISTS search_vector tsvector');
281
+ await pool.query('CREATE INDEX IF NOT EXISTS idx_memories_search_vector ON memories USING GIN(search_vector)');
267
282
  // Memory links table
268
283
  await pool.query(`
269
284
  CREATE TABLE IF NOT EXISTS memory_links (
@@ -282,6 +297,8 @@ export class PostgresBackend {
282
297
  await pool.query('CREATE INDEX IF NOT EXISTS idx_memory_links_target ON memory_links(target_id)');
283
298
  // Migration: add llm_enriched column to memory_links
284
299
  await pool.query('ALTER TABLE memory_links ADD COLUMN IF NOT EXISTS llm_enriched INTEGER DEFAULT 0');
300
+ // Migration: add metadata JSON column to memory_links (for bridge edges)
301
+ await pool.query('ALTER TABLE memory_links ADD COLUMN IF NOT EXISTS metadata JSONB');
285
302
  // Migration: add project_id column to memory_links (for multi-project PG)
286
303
  await pool.query(`
287
304
  DO $$
@@ -295,6 +312,72 @@ export class PostgresBackend {
295
312
  END $$;
296
313
  `);
297
314
  await pool.query('CREATE INDEX IF NOT EXISTS idx_memory_links_project_id ON memory_links(project_id)');
315
+ // Migration: add confidence and source_type columns for memory provenance
316
+ await pool.query('ALTER TABLE memories ADD COLUMN IF NOT EXISTS confidence REAL DEFAULT 0.5');
317
+ await pool.query("ALTER TABLE memories ADD COLUMN IF NOT EXISTS source_type TEXT DEFAULT 'human'");
318
+ // CHECK constraints for provenance columns (defense-in-depth)
319
+ await pool.query(`
320
+ DO $$ BEGIN
321
+ IF NOT EXISTS (
322
+ SELECT 1 FROM pg_constraint
323
+ WHERE conname = 'chk_memories_confidence'
324
+ AND conrelid = 'memories'::regclass
325
+ ) THEN
326
+ UPDATE memories
327
+ SET confidence = 0.5
328
+ WHERE confidence IS NULL
329
+ OR confidence < 0
330
+ OR confidence > 1
331
+ OR confidence <> confidence;
332
+ ALTER TABLE memories
333
+ ADD CONSTRAINT chk_memories_confidence
334
+ CHECK (confidence >= 0 AND confidence <= 1 AND confidence = confidence);
335
+ END IF;
336
+
337
+ IF NOT EXISTS (
338
+ SELECT 1 FROM pg_constraint
339
+ WHERE conname = 'chk_memories_source_type'
340
+ AND conrelid = 'memories'::regclass
341
+ ) THEN
342
+ UPDATE memories
343
+ SET source_type = 'human'
344
+ WHERE source_type IS NULL
345
+ OR source_type NOT IN ('human','agent','canonical_doc','imported','auto_extracted');
346
+ ALTER TABLE memories
347
+ ADD CONSTRAINT chk_memories_source_type
348
+ CHECK (source_type IN ('human','agent','canonical_doc','imported','auto_extracted'));
349
+ END IF;
350
+ END $$;
351
+ `);
352
+ // CHECK constraint for non-negative link weights (required for Dijkstra correctness)
353
+ await pool.query(`
354
+ DO $$ BEGIN
355
+ IF NOT EXISTS (
356
+ SELECT 1 FROM pg_constraint
357
+ WHERE conname = 'chk_memory_links_weight'
358
+ AND conrelid = 'memory_links'::regclass
359
+ ) THEN
360
+ UPDATE memory_links SET weight = GREATEST(weight, 0)
361
+ WHERE weight < 0;
362
+ ALTER TABLE memory_links
363
+ ADD CONSTRAINT chk_memory_links_weight CHECK (weight >= 0);
364
+ END IF;
365
+ END $$;
366
+ `);
367
+ // Retrieval feedback table
368
+ await pool.query(`
369
+ CREATE TABLE IF NOT EXISTS recall_events (
370
+ id SERIAL PRIMARY KEY,
371
+ memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
372
+ query TEXT NOT NULL,
373
+ was_used INTEGER NOT NULL DEFAULT 0,
374
+ rank_position INTEGER,
375
+ similarity_score REAL,
376
+ created_at TIMESTAMPTZ DEFAULT NOW()
377
+ )
378
+ `);
379
+ await pool.query('CREATE INDEX IF NOT EXISTS idx_recall_events_memory ON recall_events(memory_id)');
380
+ await pool.query('CREATE INDEX IF NOT EXISTS idx_recall_events_created ON recall_events(created_at)');
298
381
  // Memory centrality cache table
299
382
  await pool.query(`
300
383
  CREATE TABLE IF NOT EXISTS memory_centrality (
@@ -471,6 +554,20 @@ export class PostgresBackend {
471
554
  await pool.query('CREATE INDEX IF NOT EXISTS idx_wsh_project_id ON web_search_history(project_id)');
472
555
  await pool.query('CREATE INDEX IF NOT EXISTS idx_wsh_created ON web_search_history(created_at)');
473
556
  await pool.query('CREATE INDEX IF NOT EXISTS idx_wsh_tool ON web_search_history(tool_name)');
557
+ // One-time backfill: populate search_vector for rows that existed before tsvector migration
558
+ const migrationKey = 'search_vector_backfill_done';
559
+ const migCheck = await pool.query('SELECT value FROM metadata WHERE key = $1', [migrationKey]);
560
+ if (migCheck.rows.length === 0) {
561
+ try {
562
+ await this.rebuildAllSearchVectors();
563
+ await pool.query(`INSERT INTO metadata (key, value) VALUES ($1, '1') ON CONFLICT(key) DO NOTHING`, [migrationKey]);
564
+ }
565
+ catch (error) {
566
+ logWarn('postgresql', 'search_vector backfill failed, will retry on next init', {
567
+ error,
568
+ });
569
+ }
570
+ }
474
571
  }
475
572
  /**
476
573
  * Close all connections.
@@ -513,7 +610,14 @@ export class PostgresBackend {
513
610
  symbolType ?? null,
514
611
  signature ?? null,
515
612
  ]);
516
- return result.rows[0].id;
613
+ const docId = result.rows[0].id;
614
+ // Compute and store search_vector for full-text search
615
+ const tokens = filePath.startsWith('code:')
616
+ ? tokenizeCodeWithAST(content, signature ? tokenizeCode(signature) : [], symbolName)
617
+ : tokenizeDocs(content);
618
+ const tokenStr = tokens.join(' ');
619
+ await pool.query(`UPDATE documents SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [tokenStr, docId]);
620
+ return docId;
517
621
  }
518
622
  async upsertDocumentsBatch(documents) {
519
623
  if (documents.length === 0)
@@ -550,7 +654,14 @@ export class PostgresBackend {
550
654
  doc.symbolType ?? null,
551
655
  doc.signature ?? null,
552
656
  ]);
553
- ids.push(result.rows[0].id);
657
+ const docId = result.rows[0].id;
658
+ ids.push(docId);
659
+ // Compute and store search_vector for full-text search
660
+ const tokens = doc.filePath.startsWith('code:')
661
+ ? tokenizeCodeWithAST(doc.content, doc.signature ? tokenizeCode(doc.signature) : [], doc.symbolName)
662
+ : tokenizeDocs(doc.content);
663
+ const tokenStr = tokens.join(' ');
664
+ await client.query(`UPDATE documents SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [tokenStr, docId]);
554
665
  }
555
666
  await client.query('COMMIT');
556
667
  }
@@ -599,7 +710,14 @@ export class PostgresBackend {
599
710
  doc.symbolType ?? null,
600
711
  doc.signature ?? null,
601
712
  ]);
602
- ids.push(result.rows[0].id);
713
+ const docId = result.rows[0].id;
714
+ ids.push(docId);
715
+ // Compute and store search_vector for full-text search
716
+ const tokens = doc.filePath.startsWith('code:')
717
+ ? tokenizeCodeWithAST(doc.content, doc.signature ? tokenizeCode(doc.signature) : [], doc.symbolName)
718
+ : tokenizeDocs(doc.content);
719
+ const tokenStr = tokens.join(' ');
720
+ await client.query(`UPDATE documents SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [tokenStr, docId]);
603
721
  if (!processedFiles.has(doc.filePath)) {
604
722
  await client.query(`INSERT INTO file_hashes (project_id, file_path, content_hash, indexed_at)
605
723
  VALUES ($1, $2, $3, NOW())
@@ -683,7 +801,9 @@ export class PostgresBackend {
683
801
  const pool = await this.getPool();
684
802
  let query = `
685
803
  SELECT id, content, tags, source, type, quality_score, quality_factors,
686
- access_count, last_accessed, valid_from, valid_until, created_at
804
+ access_count, last_accessed, correction_count, is_invariant,
805
+ priority_score, valid_from, valid_until, confidence, source_type,
806
+ created_at
687
807
  FROM memories WHERE id = ANY($1)`;
688
808
  const params = [ids];
689
809
  let idx = 2;
@@ -711,7 +831,6 @@ export class PostgresBackend {
711
831
  if (filters?.createdBefore) {
712
832
  query += ` AND created_at <= $${idx}`;
713
833
  params.push(filters.createdBefore.toISOString());
714
- idx++;
715
834
  }
716
835
  const result = await pool.query(query, params);
717
836
  return result.rows.map((row) => ({
@@ -733,6 +852,8 @@ export class PostgresBackend {
733
852
  priority_score: row.priority_score ?? null,
734
853
  valid_from: row.valid_from,
735
854
  valid_until: row.valid_until,
855
+ confidence: row.confidence ?? null,
856
+ source_type: (row.source_type ?? null),
736
857
  created_at: row.created_at,
737
858
  }));
738
859
  }
@@ -774,6 +895,432 @@ export class PostgresBackend {
774
895
  await pool.query("DELETE FROM metadata WHERE key = 'embedding_model'");
775
896
  }
776
897
  // ============================================================================
898
+ // Full-Text Search Vector Updates
899
+ // ============================================================================
900
+ /**
901
+ * Update the search_vector for a single document (called from BM25 dispatcher).
902
+ * tokens: pre-tokenized string joined with spaces, e.g. "auth user login".
903
+ */
904
+ async updateDocumentSearchVector(docId, tokens) {
905
+ const pool = await this.getPool();
906
+ const trimmed = (tokens ?? '').trim();
907
+ await pool.query(`UPDATE documents SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [trimmed, docId]);
908
+ }
909
+ /**
910
+ * Update the search_vector for a single memory (called from BM25 dispatcher).
911
+ */
912
+ async updateMemorySearchVector(memoryId, tokens) {
913
+ const pool = await this.getPool();
914
+ const trimmed = (tokens ?? '').trim();
915
+ await pool.query(`UPDATE memories SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [trimmed, memoryId]);
916
+ }
917
+ /**
918
+ * Rebuild all search_vector values from content.
919
+ * Use after tokenizer logic changes or manual DB edits.
920
+ * Processes in batches to avoid memory issues on large datasets.
921
+ */
922
+ async rebuildAllSearchVectors(batchSize = 500) {
923
+ const pool = await this.getPool();
924
+ let docCount = 0;
925
+ let memCount = 0;
926
+ // Rebuild documents in batches with batch UPDATE
927
+ const docBaseParams = [];
928
+ let docWhere;
929
+ if (this.projectId) {
930
+ docBaseParams.push(this.projectId);
931
+ docWhere = `LOWER(project_id) = $1`;
932
+ }
933
+ else {
934
+ docWhere = `TRUE`;
935
+ }
936
+ const docLimitIdx = docBaseParams.length + 1;
937
+ const docOffsetIdx = docBaseParams.length + 2;
938
+ let offset = 0;
939
+ while (true) {
940
+ const batch = await pool.query(`SELECT id, file_path, content, symbol_name, signature FROM documents
941
+ WHERE ${docWhere} ORDER BY id LIMIT $${docLimitIdx} OFFSET $${docOffsetIdx}`, [...docBaseParams, batchSize, offset]);
942
+ if (batch.rows.length === 0)
943
+ break;
944
+ const ids = [];
945
+ const tokenStrs = [];
946
+ for (const row of batch.rows) {
947
+ const tokens = row.file_path.startsWith('code:')
948
+ ? tokenizeCodeWithAST(row.content, row.signature ? tokenizeCode(row.signature) : [], row.symbol_name ?? undefined)
949
+ : tokenizeDocs(row.content);
950
+ ids.push(row.id);
951
+ tokenStrs.push(tokens.join(' '));
952
+ docCount++;
953
+ }
954
+ if (ids.length > 0) {
955
+ await pool.query(`UPDATE documents SET search_vector = CASE WHEN v.tokens = '' THEN NULL ELSE to_tsvector('simple', v.tokens) END
956
+ FROM unnest($1::int[], $2::text[]) AS v(id, tokens)
957
+ WHERE documents.id = v.id`, [ids, tokenStrs]);
958
+ }
959
+ offset += batch.rows.length;
960
+ }
961
+ // Rebuild memories in batches with batch UPDATE
962
+ const memBaseParams = [];
963
+ let memWhere;
964
+ if (this.projectId) {
965
+ memBaseParams.push(this.projectId);
966
+ memWhere = `(LOWER(project_id) = $1 OR project_id IS NULL)`;
967
+ }
968
+ else {
969
+ memWhere = `project_id IS NULL`;
970
+ }
971
+ const limitIdx = memBaseParams.length + 1;
972
+ const offsetIdx = memBaseParams.length + 2;
973
+ offset = 0;
974
+ while (true) {
975
+ const batch = await pool.query(`SELECT id, content FROM memories WHERE ${memWhere} ORDER BY id LIMIT $${limitIdx} OFFSET $${offsetIdx}`, [...memBaseParams, batchSize, offset]);
976
+ if (batch.rows.length === 0)
977
+ break;
978
+ const ids = [];
979
+ const tokenStrs = [];
980
+ for (const row of batch.rows) {
981
+ const tokens = tokenizeDocs(row.content);
982
+ ids.push(row.id);
983
+ tokenStrs.push(tokens.join(' '));
984
+ memCount++;
985
+ }
986
+ if (ids.length > 0) {
987
+ await pool.query(`UPDATE memories SET search_vector = CASE WHEN v.tokens = '' THEN NULL ELSE to_tsvector('simple', v.tokens) END
988
+ FROM unnest($1::int[], $2::text[]) AS v(id, tokens)
989
+ WHERE memories.id = v.id`, [ids, tokenStrs]);
990
+ }
991
+ offset += batch.rows.length;
992
+ }
993
+ return { documents: docCount, memories: memCount };
994
+ }
995
+ // ============================================================================
996
+ // Hybrid Search (tsvector + pgvector + RRF)
997
+ // ============================================================================
998
+ /**
999
+ * Build a tsquery string from tokenized query terms using OR semantics.
1000
+ * Returns null if no valid tokens (caller should skip text search).
1001
+ */
1002
+ buildTsquery(query, isCode) {
1003
+ const tokens = isCode ? tokenizeCode(query) : tokenizeDocs(query);
1004
+ const valid = tokens.filter((t) => t.length > 0);
1005
+ if (valid.length === 0)
1006
+ return null;
1007
+ // De-duplicate and join with OR operator
1008
+ return [...new Set(valid)].join(' | ');
1009
+ }
1010
+ /**
1011
+ * Hybrid search over documents (code + brain docs).
1012
+ * Combines tsvector full-text search with pgvector cosine similarity via RRF.
1013
+ */
1014
+ async hybridSearchDocuments(query, queryEmbedding, limit, threshold, options) {
1015
+ if (!this.projectId) {
1016
+ throw new StorageError('Project ID must be set before searching documents');
1017
+ }
1018
+ const pool = await this.getPool();
1019
+ const isCode = !!options?.codeOnly;
1020
+ // Build tsquery for text search
1021
+ const tsq = this.buildTsquery(query, isCode);
1022
+ // --- Text search ---
1023
+ const textResults = [];
1024
+ if (tsq) {
1025
+ let textWhere = `WHERE LOWER(project_id) = $2 AND search_vector @@ to_tsquery('simple', $1)`;
1026
+ const textParams = [tsq, this.projectId];
1027
+ if (options?.codeOnly)
1028
+ textWhere += ` AND file_path LIKE 'code:%'`;
1029
+ else if (options?.docsOnly)
1030
+ textWhere += ` AND file_path NOT LIKE 'code:%'`;
1031
+ if (options?.symbolType) {
1032
+ textParams.push(options.symbolType);
1033
+ textWhere += ` AND symbol_type = $${textParams.length}`;
1034
+ }
1035
+ textParams.push(limit * 3); // overfetch for RRF
1036
+ const textQ = `
1037
+ SELECT id, ts_rank_cd(search_vector, to_tsquery('simple', $1)) as score
1038
+ FROM documents
1039
+ ${textWhere}
1040
+ ORDER BY score DESC
1041
+ LIMIT $${textParams.length}
1042
+ `;
1043
+ try {
1044
+ const r = await pool.query(textQ, textParams);
1045
+ for (const row of r.rows) {
1046
+ textResults.push({ docId: row.id, score: parseFloat(String(row.score)) });
1047
+ }
1048
+ }
1049
+ catch (error) {
1050
+ logWarn('postgresql', 'hybridSearchDocuments text search failed, using vector-only', {
1051
+ error: error instanceof Error ? error.message : String(error),
1052
+ });
1053
+ }
1054
+ }
1055
+ // --- Vector search ---
1056
+ const vectorResults = [];
1057
+ {
1058
+ let vecWhere = `WHERE LOWER(project_id) = $2 AND 1 - (embedding <=> $1) >= $3`;
1059
+ const vecParams = [toPgVector(queryEmbedding), this.projectId, threshold];
1060
+ if (options?.codeOnly)
1061
+ vecWhere += ` AND file_path LIKE 'code:%'`;
1062
+ else if (options?.docsOnly)
1063
+ vecWhere += ` AND file_path NOT LIKE 'code:%'`;
1064
+ if (options?.symbolType) {
1065
+ vecParams.push(options.symbolType);
1066
+ vecWhere += ` AND symbol_type = $${vecParams.length}`;
1067
+ }
1068
+ vecParams.push(limit * 3); // overfetch for RRF
1069
+ const vecQ = `
1070
+ SELECT id, 1 - (embedding <=> $1) as score
1071
+ FROM documents
1072
+ ${vecWhere}
1073
+ ORDER BY embedding <=> $1
1074
+ LIMIT $${vecParams.length}
1075
+ `;
1076
+ const r = await pool.query(vecQ, vecParams);
1077
+ for (const row of r.rows) {
1078
+ vectorResults.push({ docId: row.id, score: parseFloat(String(row.score)) });
1079
+ }
1080
+ }
1081
+ // --- RRF fusion ---
1082
+ const fused = reciprocalRankFusion(textResults, vectorResults, 0.5, limit);
1083
+ if (fused.length === 0)
1084
+ return [];
1085
+ const fusedIds = fused.map((r) => r.docId);
1086
+ const fusedScoreMap = new Map(fused.map((r) => [r.docId, r.score]));
1087
+ const textScoreMap = new Map(textResults.map((r) => [r.docId, r.score]));
1088
+ const vecScoreMap = new Map(vectorResults.map((r) => [r.docId, r.score]));
1089
+ // Fetch full rows for fused IDs
1090
+ const rows = await pool.query(`SELECT id, file_path, content, start_line, end_line, symbol_name, symbol_type, signature
1091
+ FROM documents WHERE id = ANY($1)`, [fusedIds]);
1092
+ // Map rows by id for ordering
1093
+ const rowMap = new Map(rows.rows.map((r) => [r.id, r]));
1094
+ const results = [];
1095
+ for (const { docId } of fused) {
1096
+ const row = rowMap.get(docId);
1097
+ if (!row)
1098
+ continue;
1099
+ results.push({
1100
+ file_path: row.file_path,
1101
+ content: row.content,
1102
+ start_line: row.start_line,
1103
+ end_line: row.end_line,
1104
+ similarity: fusedScoreMap.get(docId) ?? 0,
1105
+ bm25Score: textScoreMap.get(docId) ?? 0,
1106
+ vectorScore: vecScoreMap.get(docId) ?? 0,
1107
+ symbol_name: row.symbol_name,
1108
+ symbol_type: row.symbol_type,
1109
+ signature: row.signature,
1110
+ });
1111
+ }
1112
+ return results;
1113
+ }
1114
+ /**
1115
+ * Hybrid search over project memories.
1116
+ */
1117
+ async hybridSearchMemories(query, queryEmbedding, limit, threshold) {
1118
+ const pool = await this.getPool();
1119
+ const tsq = this.buildTsquery(query, false);
1120
+ const now = new Date().toISOString();
1121
+ // --- Text search ---
1122
+ const textResults = [];
1123
+ if (tsq) {
1124
+ const textParams = [tsq];
1125
+ let paramIdx = 2;
1126
+ let whereCond = `WHERE search_vector @@ to_tsquery('simple', $1) AND invalidated_by IS NULL`;
1127
+ whereCond += ` AND (valid_from IS NULL OR valid_from <= $${paramIdx})`;
1128
+ textParams.push(now);
1129
+ paramIdx++;
1130
+ whereCond += ` AND (valid_until IS NULL OR valid_until > $${paramIdx})`;
1131
+ textParams.push(now);
1132
+ paramIdx++;
1133
+ if (this.projectId) {
1134
+ whereCond += ` AND (LOWER(project_id) = $${paramIdx} OR project_id IS NULL)`;
1135
+ textParams.push(this.projectId);
1136
+ }
1137
+ else {
1138
+ whereCond += ` AND project_id IS NULL`;
1139
+ }
1140
+ textParams.push(limit * 3);
1141
+ const textQ = `
1142
+ SELECT id, ts_rank_cd(search_vector, to_tsquery('simple', $1)) as score
1143
+ FROM memories
1144
+ ${whereCond}
1145
+ ORDER BY score DESC
1146
+ LIMIT $${textParams.length}
1147
+ `;
1148
+ try {
1149
+ const r = await pool.query(textQ, textParams);
1150
+ for (const row of r.rows) {
1151
+ textResults.push({ docId: row.id, score: parseFloat(String(row.score)) });
1152
+ }
1153
+ }
1154
+ catch (error) {
1155
+ logWarn('postgresql', 'hybridSearchMemories text search failed, using vector-only', {
1156
+ error: error instanceof Error ? error.message : String(error),
1157
+ });
1158
+ }
1159
+ }
1160
+ // --- Vector search ---
1161
+ const vectorResults = [];
1162
+ {
1163
+ const vecParams = [toPgVector(queryEmbedding), threshold];
1164
+ let paramIdx = 3;
1165
+ let whereVec = `WHERE 1 - (embedding <=> $1) >= $2 AND invalidated_by IS NULL`;
1166
+ whereVec += ` AND (valid_from IS NULL OR valid_from <= $${paramIdx})`;
1167
+ vecParams.push(now);
1168
+ paramIdx++;
1169
+ whereVec += ` AND (valid_until IS NULL OR valid_until > $${paramIdx})`;
1170
+ vecParams.push(now);
1171
+ paramIdx++;
1172
+ if (this.projectId) {
1173
+ whereVec += ` AND (LOWER(project_id) = $${paramIdx} OR project_id IS NULL)`;
1174
+ vecParams.push(this.projectId);
1175
+ }
1176
+ else {
1177
+ whereVec += ` AND project_id IS NULL`;
1178
+ }
1179
+ vecParams.push(limit * 3);
1180
+ const vecQ = `
1181
+ SELECT id, 1 - (embedding <=> $1) as score
1182
+ FROM memories
1183
+ ${whereVec}
1184
+ ORDER BY embedding <=> $1
1185
+ LIMIT $${vecParams.length}
1186
+ `;
1187
+ const r = await pool.query(vecQ, vecParams);
1188
+ for (const row of r.rows) {
1189
+ vectorResults.push({ docId: row.id, score: parseFloat(String(row.score)) });
1190
+ }
1191
+ }
1192
+ // --- RRF fusion ---
1193
+ const fused = reciprocalRankFusion(textResults, vectorResults, 0.5, limit);
1194
+ if (fused.length === 0)
1195
+ return [];
1196
+ const fusedIds = fused.map((r) => r.docId);
1197
+ const fusedScoreMap = new Map(fused.map((r) => [r.docId, r.score]));
1198
+ const textScoreMap = new Map(textResults.map((r) => [r.docId, r.score]));
1199
+ const vecScoreMap = new Map(vectorResults.map((r) => [r.docId, r.score]));
1200
+ // Fetch full rows
1201
+ const rows = await pool.query(`SELECT id, content, tags, source, type, created_at,
1202
+ access_count, last_accessed, valid_from, valid_until
1203
+ FROM memories WHERE id = ANY($1)`, [fusedIds]);
1204
+ const rowMap = new Map(rows.rows.map((r) => [r.id, r]));
1205
+ const results = [];
1206
+ for (const { docId } of fused) {
1207
+ const row = rowMap.get(docId);
1208
+ if (!row)
1209
+ continue;
1210
+ results.push({
1211
+ id: row.id,
1212
+ content: row.content,
1213
+ tags: row.tags ? (typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags) : [],
1214
+ source: row.source,
1215
+ type: (row.type ?? null),
1216
+ created_at: row.created_at,
1217
+ similarity: fusedScoreMap.get(docId) ?? 0,
1218
+ bm25Score: textScoreMap.get(docId) ?? 0,
1219
+ vectorScore: vecScoreMap.get(docId) ?? 0,
1220
+ access_count: row.access_count ?? 0,
1221
+ last_accessed: row.last_accessed,
1222
+ valid_from: row.valid_from,
1223
+ valid_until: row.valid_until,
1224
+ });
1225
+ }
1226
+ return results;
1227
+ }
1228
+ /**
1229
+ * Hybrid search over global memories (project_id IS NULL).
1230
+ */
1231
+ async hybridSearchGlobalMemories(query, queryEmbedding, limit, threshold, tags, since) {
1232
+ const pool = await this.getPool();
1233
+ const tsq = this.buildTsquery(query, false);
1234
+ // --- Text search ---
1235
+ const textResults = [];
1236
+ if (tsq) {
1237
+ const textParams = [tsq];
1238
+ const paramIdx = 2;
1239
+ let whereText = `WHERE search_vector @@ to_tsquery('simple', $1) AND project_id IS NULL AND invalidated_by IS NULL`;
1240
+ if (since) {
1241
+ whereText += ` AND created_at >= $${paramIdx}`;
1242
+ textParams.push(since.toISOString());
1243
+ }
1244
+ textParams.push(limit * 3);
1245
+ const textQ = `
1246
+ SELECT id, ts_rank_cd(search_vector, to_tsquery('simple', $1)) as score
1247
+ FROM memories
1248
+ ${whereText}
1249
+ ORDER BY score DESC
1250
+ LIMIT $${textParams.length}
1251
+ `;
1252
+ try {
1253
+ const r = await pool.query(textQ, textParams);
1254
+ for (const row of r.rows) {
1255
+ textResults.push({ docId: row.id, score: parseFloat(String(row.score)) });
1256
+ }
1257
+ }
1258
+ catch (error) {
1259
+ logWarn('postgresql', 'hybridSearchGlobalMemories text search failed, using vector-only', {
1260
+ error: error instanceof Error ? error.message : String(error),
1261
+ });
1262
+ }
1263
+ }
1264
+ // --- Vector search ---
1265
+ const vectorResults = [];
1266
+ {
1267
+ const vecParams = [toPgVector(queryEmbedding), threshold];
1268
+ const paramIdx = 3;
1269
+ let whereVec = `WHERE 1 - (embedding <=> $1) >= $2 AND project_id IS NULL AND invalidated_by IS NULL`;
1270
+ if (since) {
1271
+ whereVec += ` AND created_at >= $${paramIdx}`;
1272
+ vecParams.push(since.toISOString());
1273
+ }
1274
+ vecParams.push(limit * 3);
1275
+ const vecQ = `
1276
+ SELECT id, 1 - (embedding <=> $1) as score
1277
+ FROM memories
1278
+ ${whereVec}
1279
+ ORDER BY embedding <=> $1
1280
+ LIMIT $${vecParams.length}
1281
+ `;
1282
+ const r = await pool.query(vecQ, vecParams);
1283
+ for (const row of r.rows) {
1284
+ vectorResults.push({ docId: row.id, score: parseFloat(String(row.score)) });
1285
+ }
1286
+ }
1287
+ // --- RRF fusion ---
1288
+ const fused = reciprocalRankFusion(textResults, vectorResults, 0.5, limit);
1289
+ if (fused.length === 0)
1290
+ return [];
1291
+ const fusedIds = fused.map((r) => r.docId);
1292
+ const fusedScoreMap = new Map(fused.map((r) => [r.docId, r.score]));
1293
+ const textScoreMap = new Map(textResults.map((r) => [r.docId, r.score]));
1294
+ const vecScoreMap = new Map(vectorResults.map((r) => [r.docId, r.score]));
1295
+ // Fetch full rows
1296
+ const rows = await pool.query(`SELECT id, project_id, content, tags, source, type, created_at
1297
+ FROM memories WHERE id = ANY($1)`, [fusedIds]);
1298
+ const rowMap = new Map(rows.rows.map((r) => [r.id, r]));
1299
+ let results = [];
1300
+ for (const { docId } of fused) {
1301
+ const row = rowMap.get(docId);
1302
+ if (!row)
1303
+ continue;
1304
+ results.push({
1305
+ id: row.id,
1306
+ content: row.content,
1307
+ tags: row.tags ? (typeof row.tags === 'string' ? JSON.parse(row.tags) : row.tags) : [],
1308
+ source: row.source,
1309
+ project: row.project_id,
1310
+ type: (row.type ?? null),
1311
+ created_at: row.created_at,
1312
+ similarity: fusedScoreMap.get(docId) ?? 0,
1313
+ bm25Score: textScoreMap.get(docId) ?? 0,
1314
+ vectorScore: vecScoreMap.get(docId) ?? 0,
1315
+ });
1316
+ }
1317
+ // Tag filter (post-filter in JS, consistent with searchGlobalMemories)
1318
+ if (tags && tags.length > 0) {
1319
+ results = results.filter((m) => tags.some((t) => m.tags.some((rt) => rt.toLowerCase().includes(t.toLowerCase()))));
1320
+ }
1321
+ return results;
1322
+ }
1323
+ // ============================================================================
777
1324
  // File Hashes
778
1325
  // ============================================================================
779
1326
  async getFileHash(filePath) {
@@ -824,11 +1371,18 @@ export class PostgresBackend {
824
1371
  // ============================================================================
825
1372
  // Memory Operations
826
1373
  // ============================================================================
827
- async saveMemory(content, embedding, tags = [], source, type = 'observation', qualityScore, qualityFactors, validFrom, validUntil, isGlobal = false) {
1374
+ async saveMemory(content, embedding, tags = [], source, type = 'observation', qualityScore, qualityFactors, validFrom, validUntil, isGlobal = false, confidence = 0.5, sourceType = 'human') {
1375
+ // Validate provenance fields
1376
+ if (!Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
1377
+ confidence = 0.5;
1378
+ }
1379
+ if (!SOURCE_TYPES.includes(sourceType)) {
1380
+ sourceType = 'human';
1381
+ }
828
1382
  const pool = await this.getPool();
829
1383
  const projectId = isGlobal ? null : this.projectId;
830
- const result = await pool.query(`INSERT INTO memories (project_id, content, tags, source, type, quality_score, quality_factors, embedding, valid_from, valid_until)
831
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
1384
+ const result = await pool.query(`INSERT INTO memories (project_id, content, tags, source, type, quality_score, quality_factors, embedding, valid_from, valid_until, confidence, source_type)
1385
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
832
1386
  RETURNING id`, [
833
1387
  projectId,
834
1388
  content,
@@ -840,8 +1394,15 @@ export class PostgresBackend {
840
1394
  toPgVector(embedding),
841
1395
  validFrom ?? null,
842
1396
  validUntil ?? null,
1397
+ confidence,
1398
+ sourceType,
843
1399
  ]);
844
- return result.rows[0].id;
1400
+ const memoryId = result.rows[0].id;
1401
+ // Compute and store search_vector for full-text search
1402
+ const tokens = tokenizeDocs(content);
1403
+ const tokenStr = tokens.join(' ');
1404
+ await pool.query(`UPDATE memories SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [tokenStr, memoryId]);
1405
+ return memoryId;
845
1406
  }
846
1407
  async searchMemories(queryEmbedding, limit = 5, threshold = 0.3, tags, since, options) {
847
1408
  const pool = await this.getPool();
@@ -850,8 +1411,9 @@ export class PostgresBackend {
850
1411
  const includeGlobal = options?.includeGlobal ?? true;
851
1412
  let query = `
852
1413
  SELECT id, project_id, content, tags, source, type, quality_score, quality_factors,
853
- access_count, last_accessed, valid_from, valid_until, created_at,
854
- 1 - (embedding <=> $1) as similarity
1414
+ access_count, last_accessed, correction_count, is_invariant,
1415
+ priority_score, valid_from, valid_until, confidence, source_type,
1416
+ created_at, 1 - (embedding <=> $1) as similarity
855
1417
  FROM memories
856
1418
  WHERE 1 - (embedding <=> $1) >= $2
857
1419
  AND invalidated_by IS NULL
@@ -908,6 +1470,8 @@ export class PostgresBackend {
908
1470
  priority_score: row.priority_score ?? null,
909
1471
  valid_from: row.valid_from,
910
1472
  valid_until: row.valid_until,
1473
+ confidence: row.confidence ?? null,
1474
+ source_type: (row.source_type ?? null),
911
1475
  created_at: row.created_at,
912
1476
  similarity: parseFloat(row.similarity),
913
1477
  }));
@@ -921,7 +1485,7 @@ export class PostgresBackend {
921
1485
  const pool = await this.getPool();
922
1486
  const result = await pool.query(`SELECT id, content, tags, source, type, quality_score, quality_factors,
923
1487
  access_count, last_accessed, valid_from, valid_until,
924
- correction_count, is_invariant, priority_score, created_at
1488
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at
925
1489
  FROM memories WHERE id = $1`, [id]);
926
1490
  if (result.rows.length === 0)
927
1491
  return null;
@@ -945,6 +1509,8 @@ export class PostgresBackend {
945
1509
  priority_score: row.priority_score ?? null,
946
1510
  valid_from: row.valid_from,
947
1511
  valid_until: row.valid_until,
1512
+ confidence: row.confidence ?? null,
1513
+ source_type: (row.source_type ?? null),
948
1514
  created_at: row.created_at,
949
1515
  };
950
1516
  }
@@ -952,7 +1518,7 @@ export class PostgresBackend {
952
1518
  const pool = await this.getPool();
953
1519
  const result = await pool.query(`SELECT id, content, tags, source, type, quality_score, quality_factors,
954
1520
  access_count, last_accessed, valid_from, valid_until,
955
- correction_count, is_invariant, priority_score, created_at
1521
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at
956
1522
  FROM memories
957
1523
  WHERE tags::jsonb ? $1
958
1524
  AND invalidated_by IS NULL
@@ -978,6 +1544,8 @@ export class PostgresBackend {
978
1544
  priority_score: row.priority_score ?? null,
979
1545
  valid_from: row.valid_from,
980
1546
  valid_until: row.valid_until,
1547
+ confidence: row.confidence ?? null,
1548
+ source_type: (row.source_type ?? null),
981
1549
  created_at: row.created_at,
982
1550
  }));
983
1551
  }
@@ -1012,7 +1580,7 @@ export class PostgresBackend {
1012
1580
  let query = `
1013
1581
  SELECT id, project_id, content, tags, source, type, quality_score, quality_factors,
1014
1582
  access_count, last_accessed, valid_from, valid_until,
1015
- correction_count, is_invariant, priority_score, created_at
1583
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at
1016
1584
  FROM memories
1017
1585
  `;
1018
1586
  const params = [];
@@ -1053,6 +1621,8 @@ export class PostgresBackend {
1053
1621
  correction_count: row.correction_count ?? 0,
1054
1622
  is_invariant: !!row.is_invariant,
1055
1623
  priority_score: row.priority_score ?? null,
1624
+ confidence: row.confidence ?? null,
1625
+ source_type: (row.source_type ?? null),
1056
1626
  created_at: row.created_at,
1057
1627
  }));
1058
1628
  }
@@ -1078,7 +1648,7 @@ export class PostgresBackend {
1078
1648
  let query = `
1079
1649
  SELECT id, content, tags, source, type, quality_score, quality_factors,
1080
1650
  access_count, last_accessed, valid_from, valid_until,
1081
- correction_count, is_invariant, priority_score, created_at
1651
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at
1082
1652
  FROM memories
1083
1653
  `;
1084
1654
  const params = [];
@@ -1093,7 +1663,6 @@ export class PostgresBackend {
1093
1663
  }
1094
1664
  query += ` AND (correction_count >= $${paramIndex} OR is_invariant = 1)`;
1095
1665
  params.push(threshold);
1096
- paramIndex++;
1097
1666
  query += ` ORDER BY is_invariant DESC, correction_count DESC, quality_score DESC`;
1098
1667
  const result = await pool.query(query, params);
1099
1668
  return result.rows.map((row) => ({
@@ -1115,6 +1684,8 @@ export class PostgresBackend {
1115
1684
  correction_count: row.correction_count ?? 0,
1116
1685
  is_invariant: !!row.is_invariant,
1117
1686
  priority_score: row.priority_score ?? null,
1687
+ confidence: row.confidence ?? null,
1688
+ source_type: (row.source_type ?? null),
1118
1689
  created_at: row.created_at,
1119
1690
  }));
1120
1691
  }
@@ -1128,7 +1699,7 @@ export class PostgresBackend {
1128
1699
  // ============================================================================
1129
1700
  // Memory Links
1130
1701
  // ============================================================================
1131
- async createMemoryLink(sourceId, targetId, relation = 'related', weight = 1.0, validFrom, validUntil) {
1702
+ async createMemoryLink(sourceId, targetId, relation = 'related', weight = 1.0, validFrom, validUntil, metadata) {
1132
1703
  const pool = await this.getPool();
1133
1704
  // Validate that both memories belong to current project (case-insensitive for Windows paths)
1134
1705
  const validation = await pool.query(`SELECT COUNT(*) as count FROM memories
@@ -1136,16 +1707,35 @@ export class PostgresBackend {
1136
1707
  if (parseInt(validation.rows[0].count) !== 2) {
1137
1708
  throw new StorageError('Cannot link memories from different projects');
1138
1709
  }
1139
- const result = await pool.query(`INSERT INTO memory_links (source_id, target_id, relation, weight, valid_from, valid_until)
1140
- VALUES ($1, $2, $3, $4, $5, $6)
1141
- ON CONFLICT (source_id, target_id, relation) DO NOTHING
1142
- RETURNING id`, [sourceId, targetId, relation, weight, validFrom ?? null, validUntil ?? null]);
1143
- if (result.rows.length > 0) {
1144
- return { id: result.rows[0].id, created: true };
1145
- }
1146
- // Link already existed fetch its id
1147
- const existing = await pool.query('SELECT id FROM memory_links WHERE source_id = $1 AND target_id = $2 AND relation = $3', [sourceId, targetId, relation]);
1148
- return { id: existing.rows[0].id, created: false };
1710
+ const metadataJson = metadata ? JSON.stringify(metadata) : null;
1711
+ const result = await pool.query(`INSERT INTO memory_links (source_id, target_id, relation, weight, valid_from, valid_until, metadata)
1712
+ VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb)
1713
+ ON CONFLICT (source_id, target_id, relation) DO UPDATE
1714
+ SET metadata = CASE
1715
+ WHEN memory_links.metadata IS NULL THEN EXCLUDED.metadata
1716
+ WHEN EXCLUDED.metadata IS NULL THEN memory_links.metadata
1717
+ ELSE memory_links.metadata || EXCLUDED.metadata || jsonb_build_object(
1718
+ 'code_paths',
1719
+ COALESCE((SELECT jsonb_agg(DISTINCT p)
1720
+ FROM jsonb_array_elements_text(
1721
+ COALESCE(memory_links.metadata->'code_paths', '[]'::jsonb) ||
1722
+ COALESCE(EXCLUDED.metadata->'code_paths', '[]'::jsonb) ||
1723
+ CASE WHEN memory_links.metadata ? 'code_path'
1724
+ THEN jsonb_build_array(memory_links.metadata->>'code_path')
1725
+ ELSE '[]'::jsonb END ||
1726
+ CASE WHEN EXCLUDED.metadata ? 'code_path'
1727
+ THEN jsonb_build_array(EXCLUDED.metadata->>'code_path')
1728
+ ELSE '[]'::jsonb END
1729
+ ) p), '[]'::jsonb)
1730
+ )
1731
+ END,
1732
+ weight = EXCLUDED.weight,
1733
+ valid_from = COALESCE(EXCLUDED.valid_from, memory_links.valid_from),
1734
+ valid_until = COALESCE(EXCLUDED.valid_until, memory_links.valid_until)
1735
+ RETURNING id, xmax::text`, [sourceId, targetId, relation, weight, validFrom ?? null, validUntil ?? null, metadataJson]);
1736
+ const row = result.rows[0];
1737
+ // xmax = '0' means a fresh insert; non-zero means an update (conflict resolved)
1738
+ return { id: row.id, created: row.xmax === '0' };
1149
1739
  }
1150
1740
  async deleteMemoryLink(sourceId, targetId, relation) {
1151
1741
  const pool = await this.getPool();
@@ -1296,7 +1886,8 @@ export class PostgresBackend {
1296
1886
  const pool = await this.getPool();
1297
1887
  const query = `
1298
1888
  SELECT id, project_id, content, tags, source, type, quality_score, quality_factors,
1299
- access_count, last_accessed, valid_from, valid_until, created_at,
1889
+ access_count, last_accessed, valid_from, valid_until,
1890
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at,
1300
1891
  1 - (embedding <=> $1) as similarity
1301
1892
  FROM memories
1302
1893
  WHERE 1 - (embedding <=> $1) >= $2
@@ -1326,6 +1917,8 @@ export class PostgresBackend {
1326
1917
  priority_score: row.priority_score ?? null,
1327
1918
  valid_from: row.valid_from,
1328
1919
  valid_until: row.valid_until,
1920
+ confidence: row.confidence ?? null,
1921
+ source_type: (row.source_type ?? null),
1329
1922
  created_at: row.created_at,
1330
1923
  similarity: parseFloat(row.similarity),
1331
1924
  }));
@@ -1341,7 +1934,8 @@ export class PostgresBackend {
1341
1934
  async getRecentGlobalMemories(limit = 10) {
1342
1935
  const pool = await this.getPool();
1343
1936
  const result = await pool.query(`SELECT id, project_id, content, tags, source, type, quality_score, quality_factors,
1344
- access_count, last_accessed, valid_from, valid_until, created_at
1937
+ access_count, last_accessed, valid_from, valid_until,
1938
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at
1345
1939
  FROM memories
1346
1940
  WHERE project_id IS NULL
1347
1941
  AND invalidated_by IS NULL
@@ -1366,6 +1960,8 @@ export class PostgresBackend {
1366
1960
  priority_score: row.priority_score ?? null,
1367
1961
  valid_from: row.valid_from,
1368
1962
  valid_until: row.valid_until,
1963
+ confidence: row.confidence ?? null,
1964
+ source_type: (row.source_type ?? null),
1369
1965
  created_at: row.created_at,
1370
1966
  }));
1371
1967
  }
@@ -1651,7 +2247,11 @@ export class PostgresBackend {
1651
2247
  catch (err) {
1652
2248
  // Duplicate key: a link with the target (source_id, target_id, relation) already exists.
1653
2249
  // Delete this link and mark the existing one as enriched instead.
1654
- if (err.code === '23505' && updates.relation) {
2250
+ if (typeof err === 'object' &&
2251
+ err !== null &&
2252
+ 'code' in err &&
2253
+ err.code === '23505' &&
2254
+ updates.relation) {
1655
2255
  const row = await pool.query('SELECT source_id, target_id FROM memory_links WHERE id = $1', [linkId]);
1656
2256
  if (row.rows.length > 0) {
1657
2257
  const { source_id, target_id } = row.rows[0];
@@ -1793,11 +2393,11 @@ export class PostgresBackend {
1793
2393
  `, params);
1794
2394
  return result.rows.map((row) => ({
1795
2395
  event_type: row.event_type,
1796
- query_count: parseInt(row.query_count),
1797
- total_returned_tokens: parseInt(row.total_returned_tokens),
1798
- total_full_source_tokens: parseInt(row.total_full_source_tokens),
1799
- total_savings_tokens: parseInt(row.total_savings_tokens),
1800
- total_estimated_cost: parseFloat(row.total_estimated_cost),
2396
+ query_count: parseInt(String(row.query_count), 10),
2397
+ total_returned_tokens: parseInt(String(row.total_returned_tokens), 10),
2398
+ total_full_source_tokens: parseInt(String(row.total_full_source_tokens), 10),
2399
+ total_savings_tokens: parseInt(String(row.total_savings_tokens), 10),
2400
+ total_estimated_cost: parseFloat(String(row.total_estimated_cost)),
1801
2401
  }));
1802
2402
  }
1803
2403
  async clearTokenStats() {
@@ -1864,16 +2464,16 @@ export class PostgresBackend {
1864
2464
  const result = await pool.query(`SELECT id, tool_name, model, query, prompt_tokens, completion_tokens, estimated_cost_usd, citations_count, has_reasoning, response_length_chars, created_at
1865
2465
  FROM web_search_history ${where} ORDER BY created_at DESC LIMIT $${paramIdx}`, params);
1866
2466
  return result.rows.map((row) => ({
1867
- id: parseInt(row.id),
2467
+ id: parseInt(String(row.id), 10),
1868
2468
  tool_name: row.tool_name,
1869
2469
  model: row.model,
1870
2470
  query: row.query,
1871
- prompt_tokens: parseInt(row.prompt_tokens),
1872
- completion_tokens: parseInt(row.completion_tokens),
1873
- estimated_cost_usd: parseFloat(row.estimated_cost_usd),
1874
- citations_count: parseInt(row.citations_count),
2471
+ prompt_tokens: parseInt(String(row.prompt_tokens), 10),
2472
+ completion_tokens: parseInt(String(row.completion_tokens), 10),
2473
+ estimated_cost_usd: parseFloat(String(row.estimated_cost_usd)),
2474
+ citations_count: parseInt(String(row.citations_count), 10),
1875
2475
  has_reasoning: !!row.has_reasoning,
1876
- response_length_chars: parseInt(row.response_length_chars),
2476
+ response_length_chars: parseInt(String(row.response_length_chars), 10),
1877
2477
  created_at: row.created_at instanceof Date ? row.created_at.toISOString() : row.created_at,
1878
2478
  }));
1879
2479
  }
@@ -2066,8 +2666,16 @@ export class PostgresBackend {
2066
2666
  ? memory.validUntil.toISOString()
2067
2667
  : memory.validUntil
2068
2668
  : null;
2069
- const insertResult = await client.query(`INSERT INTO memories (project_id, content, tags, source, type, quality_score, quality_factors, embedding, valid_from, valid_until)
2070
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
2669
+ // Validate provenance fields mirror saveMemory() behaviour
2670
+ const rawConfidence = memory.confidence ?? 0.5;
2671
+ const confidence = !Number.isFinite(rawConfidence) || rawConfidence < 0 || rawConfidence > 1
2672
+ ? 0.5
2673
+ : rawConfidence;
2674
+ const sourceType = memory.sourceType && SOURCE_TYPES.includes(memory.sourceType)
2675
+ ? memory.sourceType
2676
+ : 'human';
2677
+ const insertResult = await client.query(`INSERT INTO memories (project_id, content, tags, source, type, quality_score, quality_factors, embedding, valid_from, valid_until, confidence, source_type)
2678
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
2071
2679
  RETURNING id`, [
2072
2680
  this.projectId,
2073
2681
  memory.content,
@@ -2079,11 +2687,18 @@ export class PostgresBackend {
2079
2687
  toPgVector(memory.embedding),
2080
2688
  validFromStr,
2081
2689
  validUntilStr,
2690
+ confidence,
2691
+ sourceType,
2082
2692
  ]);
2693
+ const memId = insertResult.rows[0].id;
2694
+ // Compute and store search_vector for full-text search
2695
+ const memTokens = tokenizeDocs(memory.content);
2696
+ const memTokenStr = memTokens.join(' ');
2697
+ await client.query(`UPDATE memories SET search_vector = CASE WHEN $1 = '' THEN NULL ELSE to_tsvector('simple', $1) END WHERE id = $2`, [memTokenStr, memId]);
2083
2698
  results.push({
2084
2699
  index: i,
2085
2700
  isDuplicate: false,
2086
- id: insertResult.rows[0].id,
2701
+ id: memId,
2087
2702
  reason: 'saved',
2088
2703
  });
2089
2704
  saved++;
@@ -2134,6 +2749,29 @@ export class PostgresBackend {
2134
2749
  stale_count: parseInt(stale.rows[0].count),
2135
2750
  };
2136
2751
  }
2752
+ async getMemoryHealth() {
2753
+ const pool = await this.getPool();
2754
+ const scopeCond = this.projectId
2755
+ ? '(LOWER(project_id) = $1 OR project_id IS NULL)'
2756
+ : 'project_id IS NULL';
2757
+ const scopeParams = this.projectId ? [this.projectId] : [];
2758
+ const result = await pool.query(`SELECT
2759
+ COUNT(*) as total,
2760
+ SUM(CASE WHEN access_count = 0 THEN 1 ELSE 0 END) as never_accessed,
2761
+ SUM(CASE WHEN EXTRACT(EPOCH FROM (now() - COALESCE(last_accessed::timestamp, created_at))) / 86400 > 90
2762
+ THEN 1 ELSE 0 END) as stale_unused_90d,
2763
+ AVG(EXTRACT(EPOCH FROM (now() - created_at)) / 86400) as avg_age_days,
2764
+ AVG(access_count) as avg_access
2765
+ FROM memories WHERE ${scopeCond} AND invalidated_by IS NULL`, scopeParams);
2766
+ const row = result.rows[0];
2767
+ return {
2768
+ total: parseInt(row.total) || 0,
2769
+ never_accessed: parseInt(row.never_accessed) || 0,
2770
+ stale_unused_90d: parseInt(row.stale_unused_90d) || 0,
2771
+ avg_age_days: parseFloat(row.avg_age_days ?? '0') || 0,
2772
+ avg_access: parseFloat(row.avg_access ?? '0') || 0,
2773
+ };
2774
+ }
2137
2775
  async deleteMemoriesOlderThan(date) {
2138
2776
  const pool = await this.getPool();
2139
2777
  const result = await pool.query('DELETE FROM memories WHERE created_at < $1', [
@@ -2169,7 +2807,8 @@ export class PostgresBackend {
2169
2807
  : 'project_id IS NULL';
2170
2808
  const scopeParams = this.projectId ? [this.projectId] : [];
2171
2809
  const result = await pool.query(`SELECT id, project_id, content, tags, source, type, quality_score, quality_factors,
2172
- access_count, last_accessed, valid_from, valid_until, created_at,
2810
+ access_count, last_accessed, valid_from, valid_until,
2811
+ correction_count, is_invariant, priority_score, confidence, source_type, created_at,
2173
2812
  1 - (embedding <=> $1) as similarity
2174
2813
  FROM memories
2175
2814
  WHERE created_at <= $2
@@ -2198,8 +2837,10 @@ export class PostgresBackend {
2198
2837
  priority_score: row.priority_score ?? null,
2199
2838
  valid_from: row.valid_from,
2200
2839
  valid_until: row.valid_until,
2840
+ confidence: row.confidence ?? null,
2841
+ source_type: (row.source_type ?? null),
2201
2842
  created_at: row.created_at,
2202
- similarity: parseFloat(row.similarity),
2843
+ similarity: parseFloat(String(row.similarity)),
2203
2844
  }));
2204
2845
  }
2205
2846
  async getConsolidationHistory(limit = 20) {
@@ -2288,7 +2929,11 @@ export class PostgresBackend {
2288
2929
  return result.rows.map((row) => ({
2289
2930
  id: row.id,
2290
2931
  content: row.content,
2291
- tags: row.tags ?? [],
2932
+ tags: Array.isArray(row.tags)
2933
+ ? row.tags
2934
+ : typeof row.tags === 'string'
2935
+ ? JSON.parse(row.tags)
2936
+ : [],
2292
2937
  source: row.source,
2293
2938
  type: row.type,
2294
2939
  projectId: row.project_id,