@soleri/core 2.1.0 → 2.5.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 (377) hide show
  1. package/dist/brain/brain.d.ts +10 -1
  2. package/dist/brain/brain.d.ts.map +1 -1
  3. package/dist/brain/brain.js +116 -13
  4. package/dist/brain/brain.js.map +1 -1
  5. package/dist/brain/intelligence.d.ts +36 -1
  6. package/dist/brain/intelligence.d.ts.map +1 -1
  7. package/dist/brain/intelligence.js +119 -14
  8. package/dist/brain/intelligence.js.map +1 -1
  9. package/dist/brain/types.d.ts +34 -2
  10. package/dist/brain/types.d.ts.map +1 -1
  11. package/dist/cognee/client.d.ts +3 -0
  12. package/dist/cognee/client.d.ts.map +1 -1
  13. package/dist/cognee/client.js +17 -0
  14. package/dist/cognee/client.js.map +1 -1
  15. package/dist/cognee/sync-manager.d.ts +94 -0
  16. package/dist/cognee/sync-manager.d.ts.map +1 -0
  17. package/dist/cognee/sync-manager.js +293 -0
  18. package/dist/cognee/sync-manager.js.map +1 -0
  19. package/dist/control/identity-manager.d.ts +22 -0
  20. package/dist/control/identity-manager.d.ts.map +1 -0
  21. package/dist/control/identity-manager.js +233 -0
  22. package/dist/control/identity-manager.js.map +1 -0
  23. package/dist/control/intent-router.d.ts +32 -0
  24. package/dist/control/intent-router.d.ts.map +1 -0
  25. package/dist/control/intent-router.js +242 -0
  26. package/dist/control/intent-router.js.map +1 -0
  27. package/dist/control/types.d.ts +68 -0
  28. package/dist/control/types.d.ts.map +1 -0
  29. package/dist/control/types.js +9 -0
  30. package/dist/control/types.js.map +1 -0
  31. package/dist/curator/curator.d.ts +37 -1
  32. package/dist/curator/curator.d.ts.map +1 -1
  33. package/dist/curator/curator.js +199 -1
  34. package/dist/curator/curator.js.map +1 -1
  35. package/dist/errors/classify.d.ts +13 -0
  36. package/dist/errors/classify.d.ts.map +1 -0
  37. package/dist/errors/classify.js +97 -0
  38. package/dist/errors/classify.js.map +1 -0
  39. package/dist/errors/index.d.ts +6 -0
  40. package/dist/errors/index.d.ts.map +1 -0
  41. package/dist/errors/index.js +4 -0
  42. package/dist/errors/index.js.map +1 -0
  43. package/dist/errors/retry.d.ts +40 -0
  44. package/dist/errors/retry.d.ts.map +1 -0
  45. package/dist/errors/retry.js +97 -0
  46. package/dist/errors/retry.js.map +1 -0
  47. package/dist/errors/types.d.ts +48 -0
  48. package/dist/errors/types.d.ts.map +1 -0
  49. package/dist/errors/types.js +59 -0
  50. package/dist/errors/types.js.map +1 -0
  51. package/dist/facades/types.d.ts +1 -1
  52. package/dist/governance/governance.d.ts +42 -0
  53. package/dist/governance/governance.d.ts.map +1 -0
  54. package/dist/governance/governance.js +488 -0
  55. package/dist/governance/governance.js.map +1 -0
  56. package/dist/governance/index.d.ts +3 -0
  57. package/dist/governance/index.d.ts.map +1 -0
  58. package/dist/governance/index.js +2 -0
  59. package/dist/governance/index.js.map +1 -0
  60. package/dist/governance/types.d.ts +102 -0
  61. package/dist/governance/types.d.ts.map +1 -0
  62. package/dist/governance/types.js +3 -0
  63. package/dist/governance/types.js.map +1 -0
  64. package/dist/index.d.ts +52 -3
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +47 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/intake/content-classifier.d.ts +14 -0
  69. package/dist/intake/content-classifier.d.ts.map +1 -0
  70. package/dist/intake/content-classifier.js +125 -0
  71. package/dist/intake/content-classifier.js.map +1 -0
  72. package/dist/intake/dedup-gate.d.ts +17 -0
  73. package/dist/intake/dedup-gate.d.ts.map +1 -0
  74. package/dist/intake/dedup-gate.js +66 -0
  75. package/dist/intake/dedup-gate.js.map +1 -0
  76. package/dist/intake/intake-pipeline.d.ts +63 -0
  77. package/dist/intake/intake-pipeline.d.ts.map +1 -0
  78. package/dist/intake/intake-pipeline.js +373 -0
  79. package/dist/intake/intake-pipeline.js.map +1 -0
  80. package/dist/intake/types.d.ts +65 -0
  81. package/dist/intake/types.d.ts.map +1 -0
  82. package/dist/intake/types.js +3 -0
  83. package/dist/intake/types.js.map +1 -0
  84. package/dist/intelligence/loader.js +1 -1
  85. package/dist/intelligence/loader.js.map +1 -1
  86. package/dist/intelligence/types.d.ts +3 -1
  87. package/dist/intelligence/types.d.ts.map +1 -1
  88. package/dist/logging/logger.d.ts +37 -0
  89. package/dist/logging/logger.d.ts.map +1 -0
  90. package/dist/logging/logger.js +145 -0
  91. package/dist/logging/logger.js.map +1 -0
  92. package/dist/logging/types.d.ts +19 -0
  93. package/dist/logging/types.d.ts.map +1 -0
  94. package/dist/logging/types.js +2 -0
  95. package/dist/logging/types.js.map +1 -0
  96. package/dist/loop/loop-manager.d.ts +100 -0
  97. package/dist/loop/loop-manager.d.ts.map +1 -0
  98. package/dist/loop/loop-manager.js +379 -0
  99. package/dist/loop/loop-manager.js.map +1 -0
  100. package/dist/loop/types.d.ts +103 -0
  101. package/dist/loop/types.d.ts.map +1 -0
  102. package/dist/loop/types.js +11 -0
  103. package/dist/loop/types.js.map +1 -0
  104. package/dist/persistence/index.d.ts +3 -0
  105. package/dist/persistence/index.d.ts.map +1 -0
  106. package/dist/persistence/index.js +2 -0
  107. package/dist/persistence/index.js.map +1 -0
  108. package/dist/persistence/sqlite-provider.d.ts +25 -0
  109. package/dist/persistence/sqlite-provider.d.ts.map +1 -0
  110. package/dist/persistence/sqlite-provider.js +59 -0
  111. package/dist/persistence/sqlite-provider.js.map +1 -0
  112. package/dist/persistence/types.d.ts +36 -0
  113. package/dist/persistence/types.d.ts.map +1 -0
  114. package/dist/persistence/types.js +8 -0
  115. package/dist/persistence/types.js.map +1 -0
  116. package/dist/planning/gap-analysis.d.ts +72 -0
  117. package/dist/planning/gap-analysis.d.ts.map +1 -0
  118. package/dist/planning/gap-analysis.js +442 -0
  119. package/dist/planning/gap-analysis.js.map +1 -0
  120. package/dist/planning/gap-types.d.ts +29 -0
  121. package/dist/planning/gap-types.d.ts.map +1 -0
  122. package/dist/planning/gap-types.js +28 -0
  123. package/dist/planning/gap-types.js.map +1 -0
  124. package/dist/planning/planner.d.ts +421 -4
  125. package/dist/planning/planner.d.ts.map +1 -1
  126. package/dist/planning/planner.js +949 -21
  127. package/dist/planning/planner.js.map +1 -1
  128. package/dist/playbooks/generic/brainstorming.d.ts +9 -0
  129. package/dist/playbooks/generic/brainstorming.d.ts.map +1 -0
  130. package/dist/playbooks/generic/brainstorming.js +105 -0
  131. package/dist/playbooks/generic/brainstorming.js.map +1 -0
  132. package/dist/playbooks/generic/code-review.d.ts +11 -0
  133. package/dist/playbooks/generic/code-review.d.ts.map +1 -0
  134. package/dist/playbooks/generic/code-review.js +176 -0
  135. package/dist/playbooks/generic/code-review.js.map +1 -0
  136. package/dist/playbooks/generic/subagent-execution.d.ts +9 -0
  137. package/dist/playbooks/generic/subagent-execution.d.ts.map +1 -0
  138. package/dist/playbooks/generic/subagent-execution.js +68 -0
  139. package/dist/playbooks/generic/subagent-execution.js.map +1 -0
  140. package/dist/playbooks/generic/systematic-debugging.d.ts +9 -0
  141. package/dist/playbooks/generic/systematic-debugging.d.ts.map +1 -0
  142. package/dist/playbooks/generic/systematic-debugging.js +87 -0
  143. package/dist/playbooks/generic/systematic-debugging.js.map +1 -0
  144. package/dist/playbooks/generic/tdd.d.ts +9 -0
  145. package/dist/playbooks/generic/tdd.d.ts.map +1 -0
  146. package/dist/playbooks/generic/tdd.js +70 -0
  147. package/dist/playbooks/generic/tdd.js.map +1 -0
  148. package/dist/playbooks/generic/verification.d.ts +9 -0
  149. package/dist/playbooks/generic/verification.d.ts.map +1 -0
  150. package/dist/playbooks/generic/verification.js +74 -0
  151. package/dist/playbooks/generic/verification.js.map +1 -0
  152. package/dist/playbooks/index.d.ts +4 -0
  153. package/dist/playbooks/index.d.ts.map +1 -0
  154. package/dist/playbooks/index.js +5 -0
  155. package/dist/playbooks/index.js.map +1 -0
  156. package/dist/playbooks/playbook-registry.d.ts +42 -0
  157. package/dist/playbooks/playbook-registry.d.ts.map +1 -0
  158. package/dist/playbooks/playbook-registry.js +227 -0
  159. package/dist/playbooks/playbook-registry.js.map +1 -0
  160. package/dist/playbooks/playbook-seeder.d.ts +47 -0
  161. package/dist/playbooks/playbook-seeder.d.ts.map +1 -0
  162. package/dist/playbooks/playbook-seeder.js +104 -0
  163. package/dist/playbooks/playbook-seeder.js.map +1 -0
  164. package/dist/playbooks/playbook-types.d.ts +132 -0
  165. package/dist/playbooks/playbook-types.d.ts.map +1 -0
  166. package/dist/playbooks/playbook-types.js +12 -0
  167. package/dist/playbooks/playbook-types.js.map +1 -0
  168. package/dist/project/project-registry.d.ts +79 -0
  169. package/dist/project/project-registry.d.ts.map +1 -0
  170. package/dist/project/project-registry.js +274 -0
  171. package/dist/project/project-registry.js.map +1 -0
  172. package/dist/project/types.d.ts +28 -0
  173. package/dist/project/types.d.ts.map +1 -0
  174. package/dist/project/types.js +5 -0
  175. package/dist/project/types.js.map +1 -0
  176. package/dist/prompts/index.d.ts +4 -0
  177. package/dist/prompts/index.d.ts.map +1 -0
  178. package/dist/prompts/index.js +3 -0
  179. package/dist/prompts/index.js.map +1 -0
  180. package/dist/prompts/parser.d.ts +17 -0
  181. package/dist/prompts/parser.d.ts.map +1 -0
  182. package/dist/prompts/parser.js +47 -0
  183. package/dist/prompts/parser.js.map +1 -0
  184. package/dist/prompts/template-manager.d.ts +25 -0
  185. package/dist/prompts/template-manager.d.ts.map +1 -0
  186. package/dist/prompts/template-manager.js +71 -0
  187. package/dist/prompts/template-manager.js.map +1 -0
  188. package/dist/prompts/types.d.ts +26 -0
  189. package/dist/prompts/types.d.ts.map +1 -0
  190. package/dist/prompts/types.js +5 -0
  191. package/dist/prompts/types.js.map +1 -0
  192. package/dist/runtime/admin-extra-ops.d.ts +15 -0
  193. package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
  194. package/dist/runtime/admin-extra-ops.js +595 -0
  195. package/dist/runtime/admin-extra-ops.js.map +1 -0
  196. package/dist/runtime/admin-ops.d.ts +15 -0
  197. package/dist/runtime/admin-ops.d.ts.map +1 -0
  198. package/dist/runtime/admin-ops.js +329 -0
  199. package/dist/runtime/admin-ops.js.map +1 -0
  200. package/dist/runtime/capture-ops.d.ts +15 -0
  201. package/dist/runtime/capture-ops.d.ts.map +1 -0
  202. package/dist/runtime/capture-ops.js +363 -0
  203. package/dist/runtime/capture-ops.js.map +1 -0
  204. package/dist/runtime/cognee-sync-ops.d.ts +12 -0
  205. package/dist/runtime/cognee-sync-ops.d.ts.map +1 -0
  206. package/dist/runtime/cognee-sync-ops.js +55 -0
  207. package/dist/runtime/cognee-sync-ops.js.map +1 -0
  208. package/dist/runtime/core-ops.d.ts +9 -3
  209. package/dist/runtime/core-ops.d.ts.map +1 -1
  210. package/dist/runtime/core-ops.js +693 -10
  211. package/dist/runtime/core-ops.js.map +1 -1
  212. package/dist/runtime/curator-extra-ops.d.ts +9 -0
  213. package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
  214. package/dist/runtime/curator-extra-ops.js +71 -0
  215. package/dist/runtime/curator-extra-ops.js.map +1 -0
  216. package/dist/runtime/domain-ops.d.ts.map +1 -1
  217. package/dist/runtime/domain-ops.js +61 -15
  218. package/dist/runtime/domain-ops.js.map +1 -1
  219. package/dist/runtime/grading-ops.d.ts +14 -0
  220. package/dist/runtime/grading-ops.d.ts.map +1 -0
  221. package/dist/runtime/grading-ops.js +105 -0
  222. package/dist/runtime/grading-ops.js.map +1 -0
  223. package/dist/runtime/intake-ops.d.ts +14 -0
  224. package/dist/runtime/intake-ops.d.ts.map +1 -0
  225. package/dist/runtime/intake-ops.js +110 -0
  226. package/dist/runtime/intake-ops.js.map +1 -0
  227. package/dist/runtime/loop-ops.d.ts +14 -0
  228. package/dist/runtime/loop-ops.d.ts.map +1 -0
  229. package/dist/runtime/loop-ops.js +251 -0
  230. package/dist/runtime/loop-ops.js.map +1 -0
  231. package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
  232. package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
  233. package/dist/runtime/memory-cross-project-ops.js +165 -0
  234. package/dist/runtime/memory-cross-project-ops.js.map +1 -0
  235. package/dist/runtime/memory-extra-ops.d.ts +13 -0
  236. package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
  237. package/dist/runtime/memory-extra-ops.js +173 -0
  238. package/dist/runtime/memory-extra-ops.js.map +1 -0
  239. package/dist/runtime/orchestrate-ops.d.ts +17 -0
  240. package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
  241. package/dist/runtime/orchestrate-ops.js +246 -0
  242. package/dist/runtime/orchestrate-ops.js.map +1 -0
  243. package/dist/runtime/planning-extra-ops.d.ts +25 -0
  244. package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
  245. package/dist/runtime/planning-extra-ops.js +663 -0
  246. package/dist/runtime/planning-extra-ops.js.map +1 -0
  247. package/dist/runtime/playbook-ops.d.ts +14 -0
  248. package/dist/runtime/playbook-ops.d.ts.map +1 -0
  249. package/dist/runtime/playbook-ops.js +141 -0
  250. package/dist/runtime/playbook-ops.js.map +1 -0
  251. package/dist/runtime/project-ops.d.ts +15 -0
  252. package/dist/runtime/project-ops.d.ts.map +1 -0
  253. package/dist/runtime/project-ops.js +186 -0
  254. package/dist/runtime/project-ops.js.map +1 -0
  255. package/dist/runtime/runtime.d.ts.map +1 -1
  256. package/dist/runtime/runtime.js +65 -3
  257. package/dist/runtime/runtime.js.map +1 -1
  258. package/dist/runtime/types.d.ts +29 -0
  259. package/dist/runtime/types.d.ts.map +1 -1
  260. package/dist/runtime/vault-extra-ops.d.ts +10 -0
  261. package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
  262. package/dist/runtime/vault-extra-ops.js +536 -0
  263. package/dist/runtime/vault-extra-ops.js.map +1 -0
  264. package/dist/telemetry/telemetry.d.ts +48 -0
  265. package/dist/telemetry/telemetry.d.ts.map +1 -0
  266. package/dist/telemetry/telemetry.js +87 -0
  267. package/dist/telemetry/telemetry.js.map +1 -0
  268. package/dist/vault/playbook.d.ts +34 -0
  269. package/dist/vault/playbook.d.ts.map +1 -0
  270. package/dist/vault/playbook.js +60 -0
  271. package/dist/vault/playbook.js.map +1 -0
  272. package/dist/vault/vault.d.ts +97 -4
  273. package/dist/vault/vault.d.ts.map +1 -1
  274. package/dist/vault/vault.js +424 -65
  275. package/dist/vault/vault.js.map +1 -1
  276. package/package.json +7 -3
  277. package/src/__tests__/admin-extra-ops.test.ts +467 -0
  278. package/src/__tests__/admin-ops.test.ts +271 -0
  279. package/src/__tests__/brain-intelligence.test.ts +205 -0
  280. package/src/__tests__/brain.test.ts +134 -3
  281. package/src/__tests__/capture-ops.test.ts +509 -0
  282. package/src/__tests__/cognee-integration.test.ts +80 -0
  283. package/src/__tests__/cognee-sync-manager.test.ts +103 -0
  284. package/src/__tests__/core-ops.test.ts +292 -2
  285. package/src/__tests__/curator-extra-ops.test.ts +381 -0
  286. package/src/__tests__/domain-ops.test.ts +66 -0
  287. package/src/__tests__/errors.test.ts +388 -0
  288. package/src/__tests__/governance.test.ts +522 -0
  289. package/src/__tests__/grading-ops.test.ts +361 -0
  290. package/src/__tests__/identity-manager.test.ts +243 -0
  291. package/src/__tests__/intake-pipeline.test.ts +162 -0
  292. package/src/__tests__/intent-router.test.ts +222 -0
  293. package/src/__tests__/logger.test.ts +200 -0
  294. package/src/__tests__/loop-ops.test.ts +469 -0
  295. package/src/__tests__/memory-cross-project-ops.test.ts +248 -0
  296. package/src/__tests__/memory-extra-ops.test.ts +352 -0
  297. package/src/__tests__/orchestrate-ops.test.ts +289 -0
  298. package/src/__tests__/persistence.test.ts +225 -0
  299. package/src/__tests__/planner.test.ts +416 -7
  300. package/src/__tests__/planning-extra-ops.test.ts +706 -0
  301. package/src/__tests__/playbook-registry.test.ts +326 -0
  302. package/src/__tests__/playbook-seeder.test.ts +163 -0
  303. package/src/__tests__/playbook.test.ts +389 -0
  304. package/src/__tests__/project-ops.test.ts +381 -0
  305. package/src/__tests__/template-manager.test.ts +222 -0
  306. package/src/__tests__/vault-extra-ops.test.ts +482 -0
  307. package/src/brain/brain.ts +185 -16
  308. package/src/brain/intelligence.ts +179 -10
  309. package/src/brain/types.ts +40 -2
  310. package/src/cognee/client.ts +18 -0
  311. package/src/cognee/sync-manager.ts +389 -0
  312. package/src/control/identity-manager.ts +354 -0
  313. package/src/control/intent-router.ts +326 -0
  314. package/src/control/types.ts +102 -0
  315. package/src/curator/curator.ts +295 -1
  316. package/src/errors/classify.ts +102 -0
  317. package/src/errors/index.ts +5 -0
  318. package/src/errors/retry.ts +132 -0
  319. package/src/errors/types.ts +81 -0
  320. package/src/governance/governance.ts +698 -0
  321. package/src/governance/index.ts +18 -0
  322. package/src/governance/types.ts +111 -0
  323. package/src/index.ts +213 -2
  324. package/src/intake/content-classifier.ts +146 -0
  325. package/src/intake/dedup-gate.ts +92 -0
  326. package/src/intake/intake-pipeline.ts +503 -0
  327. package/src/intake/types.ts +69 -0
  328. package/src/intelligence/loader.ts +1 -1
  329. package/src/intelligence/types.ts +3 -1
  330. package/src/logging/logger.ts +154 -0
  331. package/src/logging/types.ts +21 -0
  332. package/src/loop/loop-manager.ts +448 -0
  333. package/src/loop/types.ts +115 -0
  334. package/src/persistence/index.ts +7 -0
  335. package/src/persistence/sqlite-provider.ts +62 -0
  336. package/src/persistence/types.ts +44 -0
  337. package/src/planning/gap-analysis.ts +775 -0
  338. package/src/planning/gap-types.ts +61 -0
  339. package/src/planning/planner.ts +1273 -24
  340. package/src/playbooks/generic/brainstorming.ts +110 -0
  341. package/src/playbooks/generic/code-review.ts +181 -0
  342. package/src/playbooks/generic/subagent-execution.ts +74 -0
  343. package/src/playbooks/generic/systematic-debugging.ts +92 -0
  344. package/src/playbooks/generic/tdd.ts +75 -0
  345. package/src/playbooks/generic/verification.ts +79 -0
  346. package/src/playbooks/index.ts +27 -0
  347. package/src/playbooks/playbook-registry.ts +284 -0
  348. package/src/playbooks/playbook-seeder.ts +119 -0
  349. package/src/playbooks/playbook-types.ts +162 -0
  350. package/src/project/project-registry.ts +370 -0
  351. package/src/project/types.ts +31 -0
  352. package/src/prompts/index.ts +3 -0
  353. package/src/prompts/parser.ts +59 -0
  354. package/src/prompts/template-manager.ts +77 -0
  355. package/src/prompts/types.ts +28 -0
  356. package/src/runtime/admin-extra-ops.ts +652 -0
  357. package/src/runtime/admin-ops.ts +340 -0
  358. package/src/runtime/capture-ops.ts +404 -0
  359. package/src/runtime/cognee-sync-ops.ts +63 -0
  360. package/src/runtime/core-ops.ts +787 -9
  361. package/src/runtime/curator-extra-ops.ts +85 -0
  362. package/src/runtime/domain-ops.ts +67 -15
  363. package/src/runtime/grading-ops.ts +130 -0
  364. package/src/runtime/intake-ops.ts +126 -0
  365. package/src/runtime/loop-ops.ts +277 -0
  366. package/src/runtime/memory-cross-project-ops.ts +191 -0
  367. package/src/runtime/memory-extra-ops.ts +186 -0
  368. package/src/runtime/orchestrate-ops.ts +278 -0
  369. package/src/runtime/planning-extra-ops.ts +718 -0
  370. package/src/runtime/playbook-ops.ts +169 -0
  371. package/src/runtime/project-ops.ts +202 -0
  372. package/src/runtime/runtime.ts +77 -3
  373. package/src/runtime/types.ts +29 -0
  374. package/src/runtime/vault-extra-ops.ts +606 -0
  375. package/src/telemetry/telemetry.ts +118 -0
  376. package/src/vault/playbook.ts +87 -0
  377. package/src/vault/vault.ts +575 -98
@@ -1,6 +1,5 @@
1
- import Database from 'better-sqlite3';
2
- import { mkdirSync } from 'node:fs';
3
- import { dirname } from 'node:path';
1
+ import type { PersistenceProvider } from '../persistence/types.js';
2
+ import { SQLitePersistenceProvider } from '../persistence/sqlite-provider.js';
4
3
  import type { IntelligenceEntry } from '../intelligence/types.js';
5
4
 
6
5
  export interface SearchResult {
@@ -39,21 +38,43 @@ export interface MemoryStats {
39
38
  }
40
39
 
41
40
  export class Vault {
42
- private db: Database.Database;
41
+ private provider: PersistenceProvider;
42
+ private sqliteProvider: SQLitePersistenceProvider | null;
43
+ private syncManager: import('../cognee/sync-manager.js').CogneeSyncManager | null = null;
43
44
 
44
- constructor(dbPath: string = ':memory:') {
45
- if (dbPath !== ':memory:') mkdirSync(dirname(dbPath), { recursive: true });
46
- this.db = new Database(dbPath);
47
- this.db.pragma('journal_mode = WAL');
48
- this.db.pragma('foreign_keys = ON');
45
+ /**
46
+ * Create a Vault with a PersistenceProvider or a SQLite path (backward compat).
47
+ */
48
+ constructor(providerOrPath: PersistenceProvider | string = ':memory:') {
49
+ if (typeof providerOrPath === 'string') {
50
+ const sqlite = new SQLitePersistenceProvider(providerOrPath);
51
+ this.provider = sqlite;
52
+ this.sqliteProvider = sqlite;
53
+ // SQLite-specific pragmas
54
+ this.provider.run('PRAGMA journal_mode = WAL');
55
+ this.provider.run('PRAGMA foreign_keys = ON');
56
+ } else {
57
+ this.provider = providerOrPath;
58
+ this.sqliteProvider =
59
+ providerOrPath instanceof SQLitePersistenceProvider ? providerOrPath : null;
60
+ }
49
61
  this.initialize();
50
62
  }
51
63
 
64
+ setSyncManager(mgr: import('../cognee/sync-manager.js').CogneeSyncManager): void {
65
+ this.syncManager = mgr;
66
+ }
67
+
68
+ /** Backward-compatible factory. */
69
+ static createWithSQLite(dbPath: string = ':memory:'): Vault {
70
+ return new Vault(dbPath);
71
+ }
72
+
52
73
  private initialize(): void {
53
- this.db.exec(`
74
+ this.provider.execSql(`
54
75
  CREATE TABLE IF NOT EXISTS entries (
55
76
  id TEXT PRIMARY KEY,
56
- type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule')),
77
+ type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule', 'playbook')),
57
78
  domain TEXT NOT NULL,
58
79
  title TEXT NOT NULL,
59
80
  severity TEXT NOT NULL CHECK(severity IN ('critical', 'warning', 'suggestion')),
@@ -121,25 +142,87 @@ export class Vault {
121
142
  id INTEGER PRIMARY KEY AUTOINCREMENT,
122
143
  query TEXT NOT NULL,
123
144
  entry_id TEXT NOT NULL,
124
- action TEXT NOT NULL CHECK(action IN ('accepted', 'dismissed')),
145
+ action TEXT NOT NULL CHECK(action IN ('accepted', 'dismissed', 'modified', 'failed')),
146
+ source TEXT NOT NULL DEFAULT 'search',
147
+ confidence REAL NOT NULL DEFAULT 0.6,
148
+ duration INTEGER,
149
+ context TEXT NOT NULL DEFAULT '{}',
150
+ reason TEXT,
125
151
  created_at INTEGER NOT NULL DEFAULT (unixepoch())
126
152
  );
127
153
  CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query);
128
154
  `);
155
+ this.migrateBrainSchema();
156
+ this.migrateTemporalSchema();
157
+ }
158
+
159
+ private migrateTemporalSchema(): void {
160
+ try {
161
+ this.provider.run('ALTER TABLE entries ADD COLUMN valid_from INTEGER');
162
+ } catch {
163
+ // Column already exists
164
+ }
165
+ try {
166
+ this.provider.run('ALTER TABLE entries ADD COLUMN valid_until INTEGER');
167
+ } catch {
168
+ // Column already exists
169
+ }
170
+ }
171
+
172
+ private migrateBrainSchema(): void {
173
+ const columns = this.provider.all<{ name: string }>('PRAGMA table_info(brain_feedback)');
174
+ const hasSource = columns.some((c) => c.name === 'source');
175
+
176
+ if (!hasSource && columns.length > 0) {
177
+ this.provider.transaction(() => {
178
+ this.provider.run(`
179
+ CREATE TABLE brain_feedback_new (
180
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
181
+ query TEXT NOT NULL,
182
+ entry_id TEXT NOT NULL,
183
+ action TEXT NOT NULL CHECK(action IN ('accepted', 'dismissed', 'modified', 'failed')),
184
+ source TEXT NOT NULL DEFAULT 'search',
185
+ confidence REAL NOT NULL DEFAULT 0.6,
186
+ duration INTEGER,
187
+ context TEXT NOT NULL DEFAULT '{}',
188
+ reason TEXT,
189
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
190
+ )
191
+ `);
192
+ this.provider.run(`
193
+ INSERT INTO brain_feedback_new (id, query, entry_id, action, created_at)
194
+ SELECT id, query, entry_id, action, created_at FROM brain_feedback
195
+ `);
196
+ this.provider.run('DROP TABLE brain_feedback');
197
+ this.provider.run('ALTER TABLE brain_feedback_new RENAME TO brain_feedback');
198
+ this.provider.run(
199
+ 'CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query)',
200
+ );
201
+ });
202
+ }
203
+
204
+ try {
205
+ const sessionCols = this.provider.all<{ name: string }>('PRAGMA table_info(brain_sessions)');
206
+ if (sessionCols.length > 0 && !sessionCols.some((c) => c.name === 'extracted_at')) {
207
+ this.provider.run('ALTER TABLE brain_sessions ADD COLUMN extracted_at TEXT');
208
+ }
209
+ } catch {
210
+ // brain_sessions table doesn't exist yet — BrainIntelligence will create it
211
+ }
129
212
  }
130
213
 
131
214
  seed(entries: IntelligenceEntry[]): number {
132
- const upsert = this.db.prepare(`
133
- INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to)
134
- VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo)
215
+ const sql = `
216
+ INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to,valid_from,valid_until)
217
+ VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo,@validFrom,@validUntil)
135
218
  ON CONFLICT(id) DO UPDATE SET type=excluded.type,domain=excluded.domain,title=excluded.title,severity=excluded.severity,
136
219
  description=excluded.description,context=excluded.context,example=excluded.example,counter_example=excluded.counter_example,
137
- why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,updated_at=unixepoch()
138
- `);
139
- const tx = this.db.transaction((items: IntelligenceEntry[]) => {
220
+ why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,valid_from=excluded.valid_from,valid_until=excluded.valid_until,updated_at=unixepoch()
221
+ `;
222
+ return this.provider.transaction(() => {
140
223
  let count = 0;
141
- for (const entry of items) {
142
- upsert.run({
224
+ for (const entry of entries) {
225
+ this.provider.run(sql, {
143
226
  id: entry.id,
144
227
  type: entry.type,
145
228
  domain: entry.domain,
@@ -152,21 +235,31 @@ export class Vault {
152
235
  why: entry.why ?? null,
153
236
  tags: JSON.stringify(entry.tags),
154
237
  appliesTo: JSON.stringify(entry.appliesTo ?? []),
238
+ validFrom: entry.validFrom ?? null,
239
+ validUntil: entry.validUntil ?? null,
155
240
  });
156
241
  count++;
242
+ if (this.syncManager) {
243
+ this.syncManager.enqueue('ingest', entry.id, entry);
244
+ }
157
245
  }
158
246
  return count;
159
247
  });
160
- return tx(entries);
161
248
  }
162
249
 
163
250
  search(
164
251
  query: string,
165
- options?: { domain?: string; type?: string; severity?: string; limit?: number },
252
+ options?: {
253
+ domain?: string;
254
+ type?: string;
255
+ severity?: string;
256
+ limit?: number;
257
+ includeExpired?: boolean;
258
+ },
166
259
  ): SearchResult[] {
167
260
  const limit = options?.limit ?? 10;
168
261
  const filters: string[] = [];
169
- const fp: Record<string, string> = {};
262
+ const fp: Record<string, unknown> = {};
170
263
  if (options?.domain) {
171
264
  filters.push('e.domain = @domain');
172
265
  fp.domain = options.domain;
@@ -179,13 +272,18 @@ export class Vault {
179
272
  filters.push('e.severity = @severity');
180
273
  fp.severity = options.severity;
181
274
  }
275
+ if (!options?.includeExpired) {
276
+ const now = Math.floor(Date.now() / 1000);
277
+ filters.push('(e.valid_until IS NULL OR e.valid_until > @now)');
278
+ filters.push('(e.valid_from IS NULL OR e.valid_from <= @now)');
279
+ fp.now = now;
280
+ }
182
281
  const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
183
282
  try {
184
- const rows = this.db
185
- .prepare(
186
- `SELECT e.*, -rank as score FROM entries_fts fts JOIN entries e ON e.rowid = fts.rowid WHERE entries_fts MATCH @query ${wc} ORDER BY score DESC LIMIT @limit`,
187
- )
188
- .all({ query, limit, ...fp }) as Array<Record<string, unknown>>;
283
+ const rows = this.provider.all<Record<string, unknown>>(
284
+ `SELECT e.*, -rank as score FROM entries_fts fts JOIN entries e ON e.rowid = fts.rowid WHERE entries_fts MATCH @query ${wc} ORDER BY score DESC LIMIT @limit`,
285
+ { query, limit, ...fp },
286
+ );
189
287
  return rows.map(rowToSearchResult);
190
288
  } catch {
191
289
  return [];
@@ -193,9 +291,9 @@ export class Vault {
193
291
  }
194
292
 
195
293
  get(id: string): IntelligenceEntry | null {
196
- const row = this.db.prepare('SELECT * FROM entries WHERE id = ?').get(id) as
197
- | Record<string, unknown>
198
- | undefined;
294
+ const row = this.provider.get<Record<string, unknown>>('SELECT * FROM entries WHERE id = ?', [
295
+ id,
296
+ ]);
199
297
  return row ? rowToEntry(row) : null;
200
298
  }
201
299
 
@@ -206,6 +304,7 @@ export class Vault {
206
304
  tags?: string[];
207
305
  limit?: number;
208
306
  offset?: number;
307
+ includeExpired?: boolean;
209
308
  }): IntelligenceEntry[] {
210
309
  const filters: string[] = [];
211
310
  const params: Record<string, unknown> = {};
@@ -228,26 +327,29 @@ export class Vault {
228
327
  });
229
328
  filters.push(`(${c.join(' OR ')})`);
230
329
  }
330
+ if (!options?.includeExpired) {
331
+ const now = Math.floor(Date.now() / 1000);
332
+ filters.push('(valid_until IS NULL OR valid_until > @now)');
333
+ filters.push('(valid_from IS NULL OR valid_from <= @now)');
334
+ params.now = now;
335
+ }
231
336
  const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
232
- const rows = this.db
233
- .prepare(
234
- `SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`,
235
- )
236
- .all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 }) as Array<
237
- Record<string, unknown>
238
- >;
337
+ const rows = this.provider.all<Record<string, unknown>>(
338
+ `SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`,
339
+ { ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 },
340
+ );
239
341
  return rows.map(rowToEntry);
240
342
  }
241
343
 
242
344
  stats(): VaultStats {
243
- const total = (
244
- this.db.prepare('SELECT COUNT(*) as count FROM entries').get() as { count: number }
245
- ).count;
345
+ const total = this.provider.get<{ count: number }>(
346
+ 'SELECT COUNT(*) as count FROM entries',
347
+ )!.count;
246
348
  return {
247
349
  totalEntries: total,
248
- byType: gc(this.db, 'type'),
249
- byDomain: gc(this.db, 'domain'),
250
- bySeverity: gc(this.db, 'severity'),
350
+ byType: gc(this.provider, 'type'),
351
+ byDomain: gc(this.provider, 'domain'),
352
+ bySeverity: gc(this.provider, 'severity'),
251
353
  };
252
354
  }
253
355
 
@@ -255,28 +357,186 @@ export class Vault {
255
357
  this.seed([entry]);
256
358
  }
257
359
  remove(id: string): boolean {
258
- return this.db.prepare('DELETE FROM entries WHERE id = ?').run(id).changes > 0;
360
+ const deleted = this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes > 0;
361
+ if (deleted && this.syncManager) {
362
+ this.syncManager.enqueue('delete', id);
363
+ }
364
+ return deleted;
365
+ }
366
+
367
+ update(
368
+ id: string,
369
+ fields: Partial<
370
+ Pick<
371
+ IntelligenceEntry,
372
+ | 'title'
373
+ | 'description'
374
+ | 'context'
375
+ | 'example'
376
+ | 'counterExample'
377
+ | 'why'
378
+ | 'tags'
379
+ | 'appliesTo'
380
+ | 'severity'
381
+ | 'type'
382
+ | 'domain'
383
+ | 'validFrom'
384
+ | 'validUntil'
385
+ >
386
+ >,
387
+ ): IntelligenceEntry | null {
388
+ const existing = this.get(id);
389
+ if (!existing) return null;
390
+ const merged: IntelligenceEntry = { ...existing, ...fields };
391
+ this.seed([merged]);
392
+ return this.get(id);
393
+ }
394
+
395
+ setTemporal(id: string, validFrom?: number, validUntil?: number): boolean {
396
+ const sets: string[] = [];
397
+ const params: Record<string, unknown> = { id };
398
+ if (validFrom !== undefined) {
399
+ sets.push('valid_from = @validFrom');
400
+ params.validFrom = validFrom;
401
+ }
402
+ if (validUntil !== undefined) {
403
+ sets.push('valid_until = @validUntil');
404
+ params.validUntil = validUntil;
405
+ }
406
+ if (sets.length === 0) return false;
407
+ sets.push('updated_at = unixepoch()');
408
+ return (
409
+ this.provider.run(`UPDATE entries SET ${sets.join(', ')} WHERE id = @id`, params).changes > 0
410
+ );
411
+ }
412
+
413
+ findExpiring(withinDays: number): IntelligenceEntry[] {
414
+ const now = Math.floor(Date.now() / 1000);
415
+ const cutoff = now + withinDays * 86400;
416
+ const rows = this.provider.all<Record<string, unknown>>(
417
+ 'SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until > @now AND valid_until <= @cutoff ORDER BY valid_until ASC',
418
+ { now, cutoff },
419
+ );
420
+ return rows.map(rowToEntry);
421
+ }
422
+
423
+ findExpired(limit: number = 50): IntelligenceEntry[] {
424
+ const now = Math.floor(Date.now() / 1000);
425
+ const rows = this.provider.all<Record<string, unknown>>(
426
+ 'SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until <= @now ORDER BY valid_until DESC LIMIT @limit',
427
+ { now, limit },
428
+ );
429
+ return rows.map(rowToEntry);
430
+ }
431
+
432
+ bulkRemove(ids: string[]): number {
433
+ return this.provider.transaction(() => {
434
+ let count = 0;
435
+ for (const id of ids) {
436
+ count += this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes;
437
+ if (this.syncManager) {
438
+ this.syncManager.enqueue('delete', id);
439
+ }
440
+ }
441
+ return count;
442
+ });
443
+ }
444
+
445
+ getTags(): Array<{ tag: string; count: number }> {
446
+ const rows = this.provider.all<{ tags: string }>('SELECT tags FROM entries');
447
+ const counts = new Map<string, number>();
448
+ for (const row of rows) {
449
+ const tags: string[] = JSON.parse(row.tags || '[]');
450
+ for (const tag of tags) {
451
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
452
+ }
453
+ }
454
+ return Array.from(counts.entries())
455
+ .map(([tag, count]) => ({ tag, count }))
456
+ .sort((a, b) => b.count - a.count);
457
+ }
458
+
459
+ getDomains(): Array<{ domain: string; count: number }> {
460
+ return this.provider.all<{ domain: string; count: number }>(
461
+ 'SELECT domain, COUNT(*) as count FROM entries GROUP BY domain ORDER BY count DESC',
462
+ );
463
+ }
464
+
465
+ getRecent(limit: number = 20): IntelligenceEntry[] {
466
+ const rows = this.provider.all<Record<string, unknown>>(
467
+ 'SELECT * FROM entries ORDER BY updated_at DESC LIMIT ?',
468
+ [limit],
469
+ );
470
+ return rows.map(rowToEntry);
471
+ }
472
+
473
+ exportAll(): { entries: IntelligenceEntry[]; exportedAt: number; count: number } {
474
+ const rows = this.provider.all<Record<string, unknown>>(
475
+ 'SELECT * FROM entries ORDER BY domain, title',
476
+ );
477
+ const entries = rows.map(rowToEntry);
478
+ return { entries, exportedAt: Math.floor(Date.now() / 1000), count: entries.length };
479
+ }
480
+
481
+ getAgeReport(): {
482
+ total: number;
483
+ buckets: Array<{ label: string; count: number; minDays: number; maxDays: number }>;
484
+ oldestTimestamp: number | null;
485
+ newestTimestamp: number | null;
486
+ } {
487
+ const rows = this.provider.all<{ created_at: number; updated_at: number }>(
488
+ 'SELECT created_at, updated_at FROM entries',
489
+ );
490
+ const now = Math.floor(Date.now() / 1000);
491
+ const bucketDefs = [
492
+ { label: 'today', minDays: 0, maxDays: 1 },
493
+ { label: 'this_week', minDays: 1, maxDays: 7 },
494
+ { label: 'this_month', minDays: 7, maxDays: 30 },
495
+ { label: 'this_quarter', minDays: 30, maxDays: 90 },
496
+ { label: 'older', minDays: 90, maxDays: Infinity },
497
+ ];
498
+ const counts = new Array(bucketDefs.length).fill(0) as number[];
499
+ let oldest: number | null = null;
500
+ let newest: number | null = null;
501
+ for (const row of rows) {
502
+ const ts = row.created_at;
503
+ if (oldest === null || ts < oldest) oldest = ts;
504
+ if (newest === null || ts > newest) newest = ts;
505
+ const ageDays = (now - ts) / 86400;
506
+ for (let i = 0; i < bucketDefs.length; i++) {
507
+ if (ageDays >= bucketDefs[i].minDays && ageDays < bucketDefs[i].maxDays) {
508
+ counts[i]++;
509
+ break;
510
+ }
511
+ }
512
+ }
513
+ return {
514
+ total: rows.length,
515
+ buckets: bucketDefs.map((b, i) => ({ ...b, count: counts[i] })),
516
+ oldestTimestamp: oldest,
517
+ newestTimestamp: newest,
518
+ };
259
519
  }
260
520
 
261
521
  registerProject(path: string, name?: string): ProjectInfo {
262
522
  const projectName = name ?? path.replace(/\/$/, '').split('/').pop() ?? path;
263
523
  const existing = this.getProject(path);
264
524
  if (existing) {
265
- this.db
266
- .prepare(
267
- 'UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?',
268
- )
269
- .run(path);
525
+ this.provider.run(
526
+ 'UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?',
527
+ [path],
528
+ );
270
529
  return this.getProject(path)!;
271
530
  }
272
- this.db.prepare('INSERT INTO projects (path, name) VALUES (?, ?)').run(path, projectName);
531
+ this.provider.run('INSERT INTO projects (path, name) VALUES (?, ?)', [path, projectName]);
273
532
  return this.getProject(path)!;
274
533
  }
275
534
 
276
535
  getProject(path: string): ProjectInfo | null {
277
- const row = this.db.prepare('SELECT * FROM projects WHERE path = ?').get(path) as
278
- | Record<string, unknown>
279
- | undefined;
536
+ const row = this.provider.get<Record<string, unknown>>(
537
+ 'SELECT * FROM projects WHERE path = ?',
538
+ [path],
539
+ );
280
540
  if (!row) return null;
281
541
  return {
282
542
  path: row.path as string,
@@ -288,9 +548,9 @@ export class Vault {
288
548
  }
289
549
 
290
550
  listProjects(): ProjectInfo[] {
291
- const rows = this.db
292
- .prepare('SELECT * FROM projects ORDER BY last_seen_at DESC')
293
- .all() as Array<Record<string, unknown>>;
551
+ const rows = this.provider.all<Record<string, unknown>>(
552
+ 'SELECT * FROM projects ORDER BY last_seen_at DESC',
553
+ );
294
554
  return rows.map((row) => ({
295
555
  path: row.path as string,
296
556
  name: row.name as string,
@@ -302,11 +562,9 @@ export class Vault {
302
562
 
303
563
  captureMemory(memory: Omit<Memory, 'id' | 'createdAt' | 'archivedAt'>): Memory {
304
564
  const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
305
- this.db
306
- .prepare(
307
- `INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`,
308
- )
309
- .run({
565
+ this.provider.run(
566
+ `INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`,
567
+ {
310
568
  id,
311
569
  projectPath: memory.projectPath,
312
570
  type: memory.type,
@@ -315,7 +573,8 @@ export class Vault {
315
573
  topics: JSON.stringify(memory.topics),
316
574
  filesModified: JSON.stringify(memory.filesModified),
317
575
  toolsUsed: JSON.stringify(memory.toolsUsed),
318
- });
576
+ },
577
+ );
319
578
  return this.getMemory(id)!;
320
579
  }
321
580
 
@@ -336,11 +595,10 @@ export class Vault {
336
595
  }
337
596
  const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
338
597
  try {
339
- const rows = this.db
340
- .prepare(
341
- `SELECT m.* FROM memories_fts fts JOIN memories m ON m.rowid = fts.rowid WHERE memories_fts MATCH @query ${wc} ORDER BY rank LIMIT @limit`,
342
- )
343
- .all({ query, limit, ...fp }) as Array<Record<string, unknown>>;
598
+ const rows = this.provider.all<Record<string, unknown>>(
599
+ `SELECT m.* FROM memories_fts fts JOIN memories m ON m.rowid = fts.rowid WHERE memories_fts MATCH @query ${wc} ORDER BY rank LIMIT @limit`,
600
+ { query, limit, ...fp },
601
+ );
344
602
  return rows.map(rowToMemory);
345
603
  } catch {
346
604
  return [];
@@ -364,30 +622,23 @@ export class Vault {
364
622
  params.projectPath = options.projectPath;
365
623
  }
366
624
  const wc = `WHERE ${filters.join(' AND ')}`;
367
- const rows = this.db
368
- .prepare(`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`)
369
- .all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 }) as Array<
370
- Record<string, unknown>
371
- >;
625
+ const rows = this.provider.all<Record<string, unknown>>(
626
+ `SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`,
627
+ { ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 },
628
+ );
372
629
  return rows.map(rowToMemory);
373
630
  }
374
631
 
375
632
  memoryStats(): MemoryStats {
376
- const total = (
377
- this.db.prepare('SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL').get() as {
378
- count: number;
379
- }
380
- ).count;
381
- const byTypeRows = this.db
382
- .prepare(
383
- 'SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type',
384
- )
385
- .all() as Array<{ key: string; count: number }>;
386
- const byProjectRows = this.db
387
- .prepare(
388
- 'SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path',
389
- )
390
- .all() as Array<{ key: string; count: number }>;
633
+ const total = this.provider.get<{ count: number }>(
634
+ 'SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL',
635
+ )!.count;
636
+ const byTypeRows = this.provider.all<{ key: string; count: number }>(
637
+ 'SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type',
638
+ );
639
+ const byProjectRows = this.provider.all<{ key: string; count: number }>(
640
+ 'SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path',
641
+ );
391
642
  return {
392
643
  total,
393
644
  byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
@@ -396,25 +647,249 @@ export class Vault {
396
647
  }
397
648
 
398
649
  getMemory(id: string): Memory | null {
399
- const row = this.db.prepare('SELECT * FROM memories WHERE id = ?').get(id) as
400
- | Record<string, unknown>
401
- | undefined;
650
+ const row = this.provider.get<Record<string, unknown>>('SELECT * FROM memories WHERE id = ?', [
651
+ id,
652
+ ]);
402
653
  return row ? rowToMemory(row) : null;
403
654
  }
404
655
 
405
- getDb(): Database.Database {
406
- return this.db;
656
+ deleteMemory(id: string): boolean {
657
+ return this.provider.run('DELETE FROM memories WHERE id = ?', [id]).changes > 0;
658
+ }
659
+
660
+ memoryStatsDetailed(options?: {
661
+ projectPath?: string;
662
+ fromDate?: number;
663
+ toDate?: number;
664
+ }): MemoryStats & { oldest: number | null; newest: number | null; archivedCount: number } {
665
+ const filters: string[] = [];
666
+ const params: Record<string, unknown> = {};
667
+ if (options?.projectPath) {
668
+ filters.push('project_path = @projectPath');
669
+ params.projectPath = options.projectPath;
670
+ }
671
+ if (options?.fromDate) {
672
+ filters.push('created_at >= @fromDate');
673
+ params.fromDate = options.fromDate;
674
+ }
675
+ if (options?.toDate) {
676
+ filters.push('created_at <= @toDate');
677
+ params.toDate = options.toDate;
678
+ }
679
+ const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
680
+
681
+ const total = this.provider.get<{ count: number }>(
682
+ `SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
683
+ params,
684
+ )!.count;
685
+
686
+ const archivedCount = this.provider.get<{ count: number }>(
687
+ `SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NOT NULL`,
688
+ params,
689
+ )!.count;
690
+
691
+ const byTypeRows = this.provider.all<{ key: string; count: number }>(
692
+ `SELECT type as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY type`,
693
+ params,
694
+ );
695
+
696
+ const byProjectRows = this.provider.all<{ key: string; count: number }>(
697
+ `SELECT project_path as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY project_path`,
698
+ params,
699
+ );
700
+
701
+ const dateRange = this.provider.get<{ oldest: number | null; newest: number | null }>(
702
+ `SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`,
703
+ params,
704
+ )!;
705
+
706
+ return {
707
+ total,
708
+ byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
709
+ byProject: Object.fromEntries(byProjectRows.map((r) => [r.key, r.count])),
710
+ oldest: dateRange.oldest,
711
+ newest: dateRange.newest,
712
+ archivedCount,
713
+ };
714
+ }
715
+
716
+ exportMemories(options?: {
717
+ projectPath?: string;
718
+ type?: string;
719
+ includeArchived?: boolean;
720
+ }): Memory[] {
721
+ const filters: string[] = [];
722
+ const params: Record<string, unknown> = {};
723
+ if (!options?.includeArchived) {
724
+ filters.push('archived_at IS NULL');
725
+ }
726
+ if (options?.projectPath) {
727
+ filters.push('project_path = @projectPath');
728
+ params.projectPath = options.projectPath;
729
+ }
730
+ if (options?.type) {
731
+ filters.push('type = @type');
732
+ params.type = options.type;
733
+ }
734
+ const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
735
+ const rows = this.provider.all<Record<string, unknown>>(
736
+ `SELECT * FROM memories ${wc} ORDER BY created_at ASC`,
737
+ Object.keys(params).length > 0 ? params : undefined,
738
+ );
739
+ return rows.map(rowToMemory);
740
+ }
741
+
742
+ importMemories(memories: Memory[]): { imported: number; skipped: number } {
743
+ const sql = `
744
+ INSERT OR IGNORE INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used, created_at, archived_at)
745
+ VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed, @createdAt, @archivedAt)
746
+ `;
747
+ let imported = 0;
748
+ let skipped = 0;
749
+ this.provider.transaction(() => {
750
+ for (const m of memories) {
751
+ const result = this.provider.run(sql, {
752
+ id: m.id,
753
+ projectPath: m.projectPath,
754
+ type: m.type,
755
+ context: m.context,
756
+ summary: m.summary,
757
+ topics: JSON.stringify(m.topics),
758
+ filesModified: JSON.stringify(m.filesModified),
759
+ toolsUsed: JSON.stringify(m.toolsUsed),
760
+ createdAt: m.createdAt,
761
+ archivedAt: m.archivedAt,
762
+ });
763
+ if (result.changes > 0) imported++;
764
+ else skipped++;
765
+ }
766
+ });
767
+ return { imported, skipped };
768
+ }
769
+
770
+ pruneMemories(olderThanDays: number): { pruned: number } {
771
+ const cutoff = Math.floor(Date.now() / 1000) - olderThanDays * 86400;
772
+ const result = this.provider.run(
773
+ 'DELETE FROM memories WHERE created_at < ? AND archived_at IS NULL',
774
+ [cutoff],
775
+ );
776
+ return { pruned: result.changes };
777
+ }
778
+
779
+ deduplicateMemories(): { removed: number; groups: Array<{ kept: string; removed: string[] }> } {
780
+ const dupeRows = this.provider.all<{ id1: string; id2: string }>(`
781
+ SELECT m1.id as id1, m2.id as id2
782
+ FROM memories m1
783
+ JOIN memories m2 ON m1.summary = m2.summary
784
+ AND m1.project_path = m2.project_path
785
+ AND m1.type = m2.type
786
+ AND m1.id < m2.id
787
+ AND m1.archived_at IS NULL
788
+ AND m2.archived_at IS NULL
789
+ `);
790
+
791
+ const groupMap = new Map<string, Set<string>>();
792
+ for (const row of dupeRows) {
793
+ if (!groupMap.has(row.id1)) groupMap.set(row.id1, new Set());
794
+ groupMap.get(row.id1)!.add(row.id2);
795
+ }
796
+
797
+ const groups: Array<{ kept: string; removed: string[] }> = [];
798
+ const toRemove = new Set<string>();
799
+ for (const [kept, removedSet] of groupMap) {
800
+ const removed = [...removedSet].filter((id) => !toRemove.has(id));
801
+ if (removed.length > 0) {
802
+ groups.push({ kept, removed });
803
+ for (const id of removed) toRemove.add(id);
804
+ }
805
+ }
806
+
807
+ if (toRemove.size > 0) {
808
+ this.provider.transaction(() => {
809
+ for (const id of toRemove) {
810
+ this.provider.run('DELETE FROM memories WHERE id = ?', [id]);
811
+ }
812
+ });
813
+ }
814
+
815
+ return { removed: toRemove.size, groups };
816
+ }
817
+
818
+ memoryTopics(): Array<{ topic: string; count: number }> {
819
+ const rows = this.provider.all<{ topics: string }>(
820
+ 'SELECT topics FROM memories WHERE archived_at IS NULL',
821
+ );
822
+
823
+ const topicCounts = new Map<string, number>();
824
+ for (const row of rows) {
825
+ const topics: string[] = JSON.parse(row.topics || '[]');
826
+ for (const topic of topics) {
827
+ topicCounts.set(topic, (topicCounts.get(topic) ?? 0) + 1);
828
+ }
829
+ }
830
+
831
+ return [...topicCounts.entries()]
832
+ .map(([topic, count]) => ({ topic, count }))
833
+ .sort((a, b) => b.count - a.count);
834
+ }
835
+
836
+ memoriesByProject(): Array<{ project: string; count: number; memories: Memory[] }> {
837
+ const rows = this.provider.all<{ project: string; count: number }>(
838
+ 'SELECT project_path as project, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path ORDER BY count DESC',
839
+ );
840
+
841
+ return rows.map((row) => {
842
+ const memories = this.provider.all<Record<string, unknown>>(
843
+ 'SELECT * FROM memories WHERE project_path = ? AND archived_at IS NULL ORDER BY created_at DESC',
844
+ [row.project],
845
+ );
846
+ return {
847
+ project: row.project,
848
+ count: row.count,
849
+ memories: memories.map(rowToMemory),
850
+ };
851
+ });
852
+ }
853
+
854
+ /**
855
+ * Rebuild the FTS5 index for the entries table.
856
+ * Useful after bulk operations or if the index gets out of sync.
857
+ */
858
+ rebuildFtsIndex(): void {
859
+ try {
860
+ this.provider.run("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')");
861
+ } catch {
862
+ // Graceful degradation — FTS rebuild failed (e.g. table doesn't exist yet)
863
+ }
864
+ }
865
+
866
+ /**
867
+ * Get the underlying persistence provider.
868
+ */
869
+ getProvider(): PersistenceProvider {
870
+ return this.provider;
871
+ }
872
+
873
+ /**
874
+ * Get the raw better-sqlite3 Database (backward compat).
875
+ * Throws if the provider is not SQLite.
876
+ */
877
+ getDb(): import('better-sqlite3').Database {
878
+ if (this.sqliteProvider) {
879
+ return this.sqliteProvider.getDatabase();
880
+ }
881
+ throw new Error('getDb() is only available with SQLite provider');
407
882
  }
408
883
 
409
884
  close(): void {
410
- this.db.close();
885
+ this.provider.close();
411
886
  }
412
887
  }
413
888
 
414
- function gc(db: Database.Database, col: string): Record<string, number> {
415
- const rows = db
416
- .prepare(`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`)
417
- .all() as Array<{ key: string; count: number }>;
889
+ function gc(provider: PersistenceProvider, col: string): Record<string, number> {
890
+ const rows = provider.all<{ key: string; count: number }>(
891
+ `SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`,
892
+ );
418
893
  return Object.fromEntries(rows.map((r) => [r.key, r.count]));
419
894
  }
420
895
 
@@ -432,6 +907,8 @@ function rowToEntry(row: Record<string, unknown>): IntelligenceEntry {
432
907
  why: (row.why as string) ?? undefined,
433
908
  tags: JSON.parse((row.tags as string) || '[]'),
434
909
  appliesTo: JSON.parse((row.applies_to as string) || '[]'),
910
+ validFrom: (row.valid_from as number) ?? undefined,
911
+ validUntil: (row.valid_until as number) ?? undefined,
435
912
  };
436
913
  }
437
914