@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,21 +1,39 @@
1
- import Database from 'better-sqlite3';
2
- import { mkdirSync } from 'node:fs';
3
- import { dirname } from 'node:path';
1
+ import { SQLitePersistenceProvider } from '../persistence/sqlite-provider.js';
4
2
  export class Vault {
5
- db;
6
- constructor(dbPath = ':memory:') {
7
- if (dbPath !== ':memory:')
8
- mkdirSync(dirname(dbPath), { recursive: true });
9
- this.db = new Database(dbPath);
10
- this.db.pragma('journal_mode = WAL');
11
- this.db.pragma('foreign_keys = ON');
3
+ provider;
4
+ sqliteProvider;
5
+ syncManager = null;
6
+ /**
7
+ * Create a Vault with a PersistenceProvider or a SQLite path (backward compat).
8
+ */
9
+ constructor(providerOrPath = ':memory:') {
10
+ if (typeof providerOrPath === 'string') {
11
+ const sqlite = new SQLitePersistenceProvider(providerOrPath);
12
+ this.provider = sqlite;
13
+ this.sqliteProvider = sqlite;
14
+ // SQLite-specific pragmas
15
+ this.provider.run('PRAGMA journal_mode = WAL');
16
+ this.provider.run('PRAGMA foreign_keys = ON');
17
+ }
18
+ else {
19
+ this.provider = providerOrPath;
20
+ this.sqliteProvider =
21
+ providerOrPath instanceof SQLitePersistenceProvider ? providerOrPath : null;
22
+ }
12
23
  this.initialize();
13
24
  }
25
+ setSyncManager(mgr) {
26
+ this.syncManager = mgr;
27
+ }
28
+ /** Backward-compatible factory. */
29
+ static createWithSQLite(dbPath = ':memory:') {
30
+ return new Vault(dbPath);
31
+ }
14
32
  initialize() {
15
- this.db.exec(`
33
+ this.provider.execSql(`
16
34
  CREATE TABLE IF NOT EXISTS entries (
17
35
  id TEXT PRIMARY KEY,
18
- type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule')),
36
+ type TEXT NOT NULL CHECK(type IN ('pattern', 'anti-pattern', 'rule', 'playbook')),
19
37
  domain TEXT NOT NULL,
20
38
  title TEXT NOT NULL,
21
39
  severity TEXT NOT NULL CHECK(severity IN ('critical', 'warning', 'suggestion')),
@@ -83,24 +101,83 @@ export class Vault {
83
101
  id INTEGER PRIMARY KEY AUTOINCREMENT,
84
102
  query TEXT NOT NULL,
85
103
  entry_id TEXT NOT NULL,
86
- action TEXT NOT NULL CHECK(action IN ('accepted', 'dismissed')),
104
+ action TEXT NOT NULL CHECK(action IN ('accepted', 'dismissed', 'modified', 'failed')),
105
+ source TEXT NOT NULL DEFAULT 'search',
106
+ confidence REAL NOT NULL DEFAULT 0.6,
107
+ duration INTEGER,
108
+ context TEXT NOT NULL DEFAULT '{}',
109
+ reason TEXT,
87
110
  created_at INTEGER NOT NULL DEFAULT (unixepoch())
88
111
  );
89
112
  CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query);
90
113
  `);
114
+ this.migrateBrainSchema();
115
+ this.migrateTemporalSchema();
116
+ }
117
+ migrateTemporalSchema() {
118
+ try {
119
+ this.provider.run('ALTER TABLE entries ADD COLUMN valid_from INTEGER');
120
+ }
121
+ catch {
122
+ // Column already exists
123
+ }
124
+ try {
125
+ this.provider.run('ALTER TABLE entries ADD COLUMN valid_until INTEGER');
126
+ }
127
+ catch {
128
+ // Column already exists
129
+ }
130
+ }
131
+ migrateBrainSchema() {
132
+ const columns = this.provider.all('PRAGMA table_info(brain_feedback)');
133
+ const hasSource = columns.some((c) => c.name === 'source');
134
+ if (!hasSource && columns.length > 0) {
135
+ this.provider.transaction(() => {
136
+ this.provider.run(`
137
+ CREATE TABLE brain_feedback_new (
138
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
139
+ query TEXT NOT NULL,
140
+ entry_id TEXT NOT NULL,
141
+ action TEXT NOT NULL CHECK(action IN ('accepted', 'dismissed', 'modified', 'failed')),
142
+ source TEXT NOT NULL DEFAULT 'search',
143
+ confidence REAL NOT NULL DEFAULT 0.6,
144
+ duration INTEGER,
145
+ context TEXT NOT NULL DEFAULT '{}',
146
+ reason TEXT,
147
+ created_at INTEGER NOT NULL DEFAULT (unixepoch())
148
+ )
149
+ `);
150
+ this.provider.run(`
151
+ INSERT INTO brain_feedback_new (id, query, entry_id, action, created_at)
152
+ SELECT id, query, entry_id, action, created_at FROM brain_feedback
153
+ `);
154
+ this.provider.run('DROP TABLE brain_feedback');
155
+ this.provider.run('ALTER TABLE brain_feedback_new RENAME TO brain_feedback');
156
+ this.provider.run('CREATE INDEX IF NOT EXISTS idx_brain_feedback_query ON brain_feedback(query)');
157
+ });
158
+ }
159
+ try {
160
+ const sessionCols = this.provider.all('PRAGMA table_info(brain_sessions)');
161
+ if (sessionCols.length > 0 && !sessionCols.some((c) => c.name === 'extracted_at')) {
162
+ this.provider.run('ALTER TABLE brain_sessions ADD COLUMN extracted_at TEXT');
163
+ }
164
+ }
165
+ catch {
166
+ // brain_sessions table doesn't exist yet — BrainIntelligence will create it
167
+ }
91
168
  }
92
169
  seed(entries) {
93
- const upsert = this.db.prepare(`
94
- INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to)
95
- VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo)
170
+ const sql = `
171
+ INSERT INTO entries (id,type,domain,title,severity,description,context,example,counter_example,why,tags,applies_to,valid_from,valid_until)
172
+ VALUES (@id,@type,@domain,@title,@severity,@description,@context,@example,@counterExample,@why,@tags,@appliesTo,@validFrom,@validUntil)
96
173
  ON CONFLICT(id) DO UPDATE SET type=excluded.type,domain=excluded.domain,title=excluded.title,severity=excluded.severity,
97
174
  description=excluded.description,context=excluded.context,example=excluded.example,counter_example=excluded.counter_example,
98
- why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,updated_at=unixepoch()
99
- `);
100
- const tx = this.db.transaction((items) => {
175
+ why=excluded.why,tags=excluded.tags,applies_to=excluded.applies_to,valid_from=excluded.valid_from,valid_until=excluded.valid_until,updated_at=unixepoch()
176
+ `;
177
+ return this.provider.transaction(() => {
101
178
  let count = 0;
102
- for (const entry of items) {
103
- upsert.run({
179
+ for (const entry of entries) {
180
+ this.provider.run(sql, {
104
181
  id: entry.id,
105
182
  type: entry.type,
106
183
  domain: entry.domain,
@@ -113,12 +190,16 @@ export class Vault {
113
190
  why: entry.why ?? null,
114
191
  tags: JSON.stringify(entry.tags),
115
192
  appliesTo: JSON.stringify(entry.appliesTo ?? []),
193
+ validFrom: entry.validFrom ?? null,
194
+ validUntil: entry.validUntil ?? null,
116
195
  });
117
196
  count++;
197
+ if (this.syncManager) {
198
+ this.syncManager.enqueue('ingest', entry.id, entry);
199
+ }
118
200
  }
119
201
  return count;
120
202
  });
121
- return tx(entries);
122
203
  }
123
204
  search(query, options) {
124
205
  const limit = options?.limit ?? 10;
@@ -136,11 +217,15 @@ export class Vault {
136
217
  filters.push('e.severity = @severity');
137
218
  fp.severity = options.severity;
138
219
  }
220
+ if (!options?.includeExpired) {
221
+ const now = Math.floor(Date.now() / 1000);
222
+ filters.push('(e.valid_until IS NULL OR e.valid_until > @now)');
223
+ filters.push('(e.valid_from IS NULL OR e.valid_from <= @now)');
224
+ fp.now = now;
225
+ }
139
226
  const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
140
227
  try {
141
- const rows = this.db
142
- .prepare(`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`)
143
- .all({ query, limit, ...fp });
228
+ const rows = this.provider.all(`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`, { query, limit, ...fp });
144
229
  return rows.map(rowToSearchResult);
145
230
  }
146
231
  catch {
@@ -148,7 +233,9 @@ export class Vault {
148
233
  }
149
234
  }
150
235
  get(id) {
151
- const row = this.db.prepare('SELECT * FROM entries WHERE id = ?').get(id);
236
+ const row = this.provider.get('SELECT * FROM entries WHERE id = ?', [
237
+ id,
238
+ ]);
152
239
  return row ? rowToEntry(row) : null;
153
240
  }
154
241
  list(options) {
@@ -173,41 +260,153 @@ export class Vault {
173
260
  });
174
261
  filters.push(`(${c.join(' OR ')})`);
175
262
  }
263
+ if (!options?.includeExpired) {
264
+ const now = Math.floor(Date.now() / 1000);
265
+ filters.push('(valid_until IS NULL OR valid_until > @now)');
266
+ filters.push('(valid_from IS NULL OR valid_from <= @now)');
267
+ params.now = now;
268
+ }
176
269
  const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
177
- const rows = this.db
178
- .prepare(`SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`)
179
- .all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
270
+ const rows = this.provider.all(`SELECT * FROM entries ${wc} ORDER BY severity, domain, title LIMIT @limit OFFSET @offset`, { ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
180
271
  return rows.map(rowToEntry);
181
272
  }
182
273
  stats() {
183
- const total = this.db.prepare('SELECT COUNT(*) as count FROM entries').get().count;
274
+ const total = this.provider.get('SELECT COUNT(*) as count FROM entries').count;
184
275
  return {
185
276
  totalEntries: total,
186
- byType: gc(this.db, 'type'),
187
- byDomain: gc(this.db, 'domain'),
188
- bySeverity: gc(this.db, 'severity'),
277
+ byType: gc(this.provider, 'type'),
278
+ byDomain: gc(this.provider, 'domain'),
279
+ bySeverity: gc(this.provider, 'severity'),
189
280
  };
190
281
  }
191
282
  add(entry) {
192
283
  this.seed([entry]);
193
284
  }
194
285
  remove(id) {
195
- return this.db.prepare('DELETE FROM entries WHERE id = ?').run(id).changes > 0;
286
+ const deleted = this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes > 0;
287
+ if (deleted && this.syncManager) {
288
+ this.syncManager.enqueue('delete', id);
289
+ }
290
+ return deleted;
291
+ }
292
+ update(id, fields) {
293
+ const existing = this.get(id);
294
+ if (!existing)
295
+ return null;
296
+ const merged = { ...existing, ...fields };
297
+ this.seed([merged]);
298
+ return this.get(id);
299
+ }
300
+ setTemporal(id, validFrom, validUntil) {
301
+ const sets = [];
302
+ const params = { id };
303
+ if (validFrom !== undefined) {
304
+ sets.push('valid_from = @validFrom');
305
+ params.validFrom = validFrom;
306
+ }
307
+ if (validUntil !== undefined) {
308
+ sets.push('valid_until = @validUntil');
309
+ params.validUntil = validUntil;
310
+ }
311
+ if (sets.length === 0)
312
+ return false;
313
+ sets.push('updated_at = unixepoch()');
314
+ return (this.provider.run(`UPDATE entries SET ${sets.join(', ')} WHERE id = @id`, params).changes > 0);
315
+ }
316
+ findExpiring(withinDays) {
317
+ const now = Math.floor(Date.now() / 1000);
318
+ const cutoff = now + withinDays * 86400;
319
+ const rows = this.provider.all('SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until > @now AND valid_until <= @cutoff ORDER BY valid_until ASC', { now, cutoff });
320
+ return rows.map(rowToEntry);
321
+ }
322
+ findExpired(limit = 50) {
323
+ const now = Math.floor(Date.now() / 1000);
324
+ const rows = this.provider.all('SELECT * FROM entries WHERE valid_until IS NOT NULL AND valid_until <= @now ORDER BY valid_until DESC LIMIT @limit', { now, limit });
325
+ return rows.map(rowToEntry);
326
+ }
327
+ bulkRemove(ids) {
328
+ return this.provider.transaction(() => {
329
+ let count = 0;
330
+ for (const id of ids) {
331
+ count += this.provider.run('DELETE FROM entries WHERE id = ?', [id]).changes;
332
+ if (this.syncManager) {
333
+ this.syncManager.enqueue('delete', id);
334
+ }
335
+ }
336
+ return count;
337
+ });
338
+ }
339
+ getTags() {
340
+ const rows = this.provider.all('SELECT tags FROM entries');
341
+ const counts = new Map();
342
+ for (const row of rows) {
343
+ const tags = JSON.parse(row.tags || '[]');
344
+ for (const tag of tags) {
345
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
346
+ }
347
+ }
348
+ return Array.from(counts.entries())
349
+ .map(([tag, count]) => ({ tag, count }))
350
+ .sort((a, b) => b.count - a.count);
351
+ }
352
+ getDomains() {
353
+ return this.provider.all('SELECT domain, COUNT(*) as count FROM entries GROUP BY domain ORDER BY count DESC');
354
+ }
355
+ getRecent(limit = 20) {
356
+ const rows = this.provider.all('SELECT * FROM entries ORDER BY updated_at DESC LIMIT ?', [limit]);
357
+ return rows.map(rowToEntry);
358
+ }
359
+ exportAll() {
360
+ const rows = this.provider.all('SELECT * FROM entries ORDER BY domain, title');
361
+ const entries = rows.map(rowToEntry);
362
+ return { entries, exportedAt: Math.floor(Date.now() / 1000), count: entries.length };
363
+ }
364
+ getAgeReport() {
365
+ const rows = this.provider.all('SELECT created_at, updated_at FROM entries');
366
+ const now = Math.floor(Date.now() / 1000);
367
+ const bucketDefs = [
368
+ { label: 'today', minDays: 0, maxDays: 1 },
369
+ { label: 'this_week', minDays: 1, maxDays: 7 },
370
+ { label: 'this_month', minDays: 7, maxDays: 30 },
371
+ { label: 'this_quarter', minDays: 30, maxDays: 90 },
372
+ { label: 'older', minDays: 90, maxDays: Infinity },
373
+ ];
374
+ const counts = new Array(bucketDefs.length).fill(0);
375
+ let oldest = null;
376
+ let newest = null;
377
+ for (const row of rows) {
378
+ const ts = row.created_at;
379
+ if (oldest === null || ts < oldest)
380
+ oldest = ts;
381
+ if (newest === null || ts > newest)
382
+ newest = ts;
383
+ const ageDays = (now - ts) / 86400;
384
+ for (let i = 0; i < bucketDefs.length; i++) {
385
+ if (ageDays >= bucketDefs[i].minDays && ageDays < bucketDefs[i].maxDays) {
386
+ counts[i]++;
387
+ break;
388
+ }
389
+ }
390
+ }
391
+ return {
392
+ total: rows.length,
393
+ buckets: bucketDefs.map((b, i) => ({ ...b, count: counts[i] })),
394
+ oldestTimestamp: oldest,
395
+ newestTimestamp: newest,
396
+ };
196
397
  }
197
398
  registerProject(path, name) {
198
399
  const projectName = name ?? path.replace(/\/$/, '').split('/').pop() ?? path;
199
400
  const existing = this.getProject(path);
200
401
  if (existing) {
201
- this.db
202
- .prepare('UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?')
203
- .run(path);
402
+ this.provider.run('UPDATE projects SET last_seen_at = unixepoch(), session_count = session_count + 1 WHERE path = ?', [path]);
204
403
  return this.getProject(path);
205
404
  }
206
- this.db.prepare('INSERT INTO projects (path, name) VALUES (?, ?)').run(path, projectName);
405
+ this.provider.run('INSERT INTO projects (path, name) VALUES (?, ?)', [path, projectName]);
207
406
  return this.getProject(path);
208
407
  }
209
408
  getProject(path) {
210
- const row = this.db.prepare('SELECT * FROM projects WHERE path = ?').get(path);
409
+ const row = this.provider.get('SELECT * FROM projects WHERE path = ?', [path]);
211
410
  if (!row)
212
411
  return null;
213
412
  return {
@@ -219,9 +418,7 @@ export class Vault {
219
418
  };
220
419
  }
221
420
  listProjects() {
222
- const rows = this.db
223
- .prepare('SELECT * FROM projects ORDER BY last_seen_at DESC')
224
- .all();
421
+ const rows = this.provider.all('SELECT * FROM projects ORDER BY last_seen_at DESC');
225
422
  return rows.map((row) => ({
226
423
  path: row.path,
227
424
  name: row.name,
@@ -232,9 +429,7 @@ export class Vault {
232
429
  }
233
430
  captureMemory(memory) {
234
431
  const id = `mem-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
235
- this.db
236
- .prepare(`INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`)
237
- .run({
432
+ this.provider.run(`INSERT INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used) VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed)`, {
238
433
  id,
239
434
  projectPath: memory.projectPath,
240
435
  type: memory.type,
@@ -260,9 +455,7 @@ export class Vault {
260
455
  }
261
456
  const wc = filters.length > 0 ? `AND ${filters.join(' AND ')}` : '';
262
457
  try {
263
- const rows = this.db
264
- .prepare(`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`)
265
- .all({ query, limit, ...fp });
458
+ const rows = this.provider.all(`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`, { query, limit, ...fp });
266
459
  return rows.map(rowToMemory);
267
460
  }
268
461
  catch {
@@ -281,19 +474,13 @@ export class Vault {
281
474
  params.projectPath = options.projectPath;
282
475
  }
283
476
  const wc = `WHERE ${filters.join(' AND ')}`;
284
- const rows = this.db
285
- .prepare(`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`)
286
- .all({ ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
477
+ const rows = this.provider.all(`SELECT * FROM memories ${wc} ORDER BY created_at DESC LIMIT @limit OFFSET @offset`, { ...params, limit: options?.limit ?? 50, offset: options?.offset ?? 0 });
287
478
  return rows.map(rowToMemory);
288
479
  }
289
480
  memoryStats() {
290
- const total = this.db.prepare('SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL').get().count;
291
- const byTypeRows = this.db
292
- .prepare('SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type')
293
- .all();
294
- const byProjectRows = this.db
295
- .prepare('SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path')
296
- .all();
481
+ const total = this.provider.get('SELECT COUNT(*) as count FROM memories WHERE archived_at IS NULL').count;
482
+ const byTypeRows = this.provider.all('SELECT type as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY type');
483
+ const byProjectRows = this.provider.all('SELECT project_path as key, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path');
297
484
  return {
298
485
  total,
299
486
  byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
@@ -301,20 +488,190 @@ export class Vault {
301
488
  };
302
489
  }
303
490
  getMemory(id) {
304
- const row = this.db.prepare('SELECT * FROM memories WHERE id = ?').get(id);
491
+ const row = this.provider.get('SELECT * FROM memories WHERE id = ?', [
492
+ id,
493
+ ]);
305
494
  return row ? rowToMemory(row) : null;
306
495
  }
496
+ deleteMemory(id) {
497
+ return this.provider.run('DELETE FROM memories WHERE id = ?', [id]).changes > 0;
498
+ }
499
+ memoryStatsDetailed(options) {
500
+ const filters = [];
501
+ const params = {};
502
+ if (options?.projectPath) {
503
+ filters.push('project_path = @projectPath');
504
+ params.projectPath = options.projectPath;
505
+ }
506
+ if (options?.fromDate) {
507
+ filters.push('created_at >= @fromDate');
508
+ params.fromDate = options.fromDate;
509
+ }
510
+ if (options?.toDate) {
511
+ filters.push('created_at <= @toDate');
512
+ params.toDate = options.toDate;
513
+ }
514
+ const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
515
+ const total = this.provider.get(`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`, params).count;
516
+ const archivedCount = this.provider.get(`SELECT COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NOT NULL`, params).count;
517
+ const byTypeRows = this.provider.all(`SELECT type as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY type`, params);
518
+ const byProjectRows = this.provider.all(`SELECT project_path as key, COUNT(*) as count FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL GROUP BY project_path`, params);
519
+ const dateRange = this.provider.get(`SELECT MIN(created_at) as oldest, MAX(created_at) as newest FROM memories ${wc}${wc ? ' AND' : ' WHERE'} archived_at IS NULL`, params);
520
+ return {
521
+ total,
522
+ byType: Object.fromEntries(byTypeRows.map((r) => [r.key, r.count])),
523
+ byProject: Object.fromEntries(byProjectRows.map((r) => [r.key, r.count])),
524
+ oldest: dateRange.oldest,
525
+ newest: dateRange.newest,
526
+ archivedCount,
527
+ };
528
+ }
529
+ exportMemories(options) {
530
+ const filters = [];
531
+ const params = {};
532
+ if (!options?.includeArchived) {
533
+ filters.push('archived_at IS NULL');
534
+ }
535
+ if (options?.projectPath) {
536
+ filters.push('project_path = @projectPath');
537
+ params.projectPath = options.projectPath;
538
+ }
539
+ if (options?.type) {
540
+ filters.push('type = @type');
541
+ params.type = options.type;
542
+ }
543
+ const wc = filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '';
544
+ const rows = this.provider.all(`SELECT * FROM memories ${wc} ORDER BY created_at ASC`, Object.keys(params).length > 0 ? params : undefined);
545
+ return rows.map(rowToMemory);
546
+ }
547
+ importMemories(memories) {
548
+ const sql = `
549
+ INSERT OR IGNORE INTO memories (id, project_path, type, context, summary, topics, files_modified, tools_used, created_at, archived_at)
550
+ VALUES (@id, @projectPath, @type, @context, @summary, @topics, @filesModified, @toolsUsed, @createdAt, @archivedAt)
551
+ `;
552
+ let imported = 0;
553
+ let skipped = 0;
554
+ this.provider.transaction(() => {
555
+ for (const m of memories) {
556
+ const result = this.provider.run(sql, {
557
+ id: m.id,
558
+ projectPath: m.projectPath,
559
+ type: m.type,
560
+ context: m.context,
561
+ summary: m.summary,
562
+ topics: JSON.stringify(m.topics),
563
+ filesModified: JSON.stringify(m.filesModified),
564
+ toolsUsed: JSON.stringify(m.toolsUsed),
565
+ createdAt: m.createdAt,
566
+ archivedAt: m.archivedAt,
567
+ });
568
+ if (result.changes > 0)
569
+ imported++;
570
+ else
571
+ skipped++;
572
+ }
573
+ });
574
+ return { imported, skipped };
575
+ }
576
+ pruneMemories(olderThanDays) {
577
+ const cutoff = Math.floor(Date.now() / 1000) - olderThanDays * 86400;
578
+ const result = this.provider.run('DELETE FROM memories WHERE created_at < ? AND archived_at IS NULL', [cutoff]);
579
+ return { pruned: result.changes };
580
+ }
581
+ deduplicateMemories() {
582
+ const dupeRows = this.provider.all(`
583
+ SELECT m1.id as id1, m2.id as id2
584
+ FROM memories m1
585
+ JOIN memories m2 ON m1.summary = m2.summary
586
+ AND m1.project_path = m2.project_path
587
+ AND m1.type = m2.type
588
+ AND m1.id < m2.id
589
+ AND m1.archived_at IS NULL
590
+ AND m2.archived_at IS NULL
591
+ `);
592
+ const groupMap = new Map();
593
+ for (const row of dupeRows) {
594
+ if (!groupMap.has(row.id1))
595
+ groupMap.set(row.id1, new Set());
596
+ groupMap.get(row.id1).add(row.id2);
597
+ }
598
+ const groups = [];
599
+ const toRemove = new Set();
600
+ for (const [kept, removedSet] of groupMap) {
601
+ const removed = [...removedSet].filter((id) => !toRemove.has(id));
602
+ if (removed.length > 0) {
603
+ groups.push({ kept, removed });
604
+ for (const id of removed)
605
+ toRemove.add(id);
606
+ }
607
+ }
608
+ if (toRemove.size > 0) {
609
+ this.provider.transaction(() => {
610
+ for (const id of toRemove) {
611
+ this.provider.run('DELETE FROM memories WHERE id = ?', [id]);
612
+ }
613
+ });
614
+ }
615
+ return { removed: toRemove.size, groups };
616
+ }
617
+ memoryTopics() {
618
+ const rows = this.provider.all('SELECT topics FROM memories WHERE archived_at IS NULL');
619
+ const topicCounts = new Map();
620
+ for (const row of rows) {
621
+ const topics = JSON.parse(row.topics || '[]');
622
+ for (const topic of topics) {
623
+ topicCounts.set(topic, (topicCounts.get(topic) ?? 0) + 1);
624
+ }
625
+ }
626
+ return [...topicCounts.entries()]
627
+ .map(([topic, count]) => ({ topic, count }))
628
+ .sort((a, b) => b.count - a.count);
629
+ }
630
+ memoriesByProject() {
631
+ const rows = this.provider.all('SELECT project_path as project, COUNT(*) as count FROM memories WHERE archived_at IS NULL GROUP BY project_path ORDER BY count DESC');
632
+ return rows.map((row) => {
633
+ const memories = this.provider.all('SELECT * FROM memories WHERE project_path = ? AND archived_at IS NULL ORDER BY created_at DESC', [row.project]);
634
+ return {
635
+ project: row.project,
636
+ count: row.count,
637
+ memories: memories.map(rowToMemory),
638
+ };
639
+ });
640
+ }
641
+ /**
642
+ * Rebuild the FTS5 index for the entries table.
643
+ * Useful after bulk operations or if the index gets out of sync.
644
+ */
645
+ rebuildFtsIndex() {
646
+ try {
647
+ this.provider.run("INSERT INTO entries_fts(entries_fts) VALUES('rebuild')");
648
+ }
649
+ catch {
650
+ // Graceful degradation — FTS rebuild failed (e.g. table doesn't exist yet)
651
+ }
652
+ }
653
+ /**
654
+ * Get the underlying persistence provider.
655
+ */
656
+ getProvider() {
657
+ return this.provider;
658
+ }
659
+ /**
660
+ * Get the raw better-sqlite3 Database (backward compat).
661
+ * Throws if the provider is not SQLite.
662
+ */
307
663
  getDb() {
308
- return this.db;
664
+ if (this.sqliteProvider) {
665
+ return this.sqliteProvider.getDatabase();
666
+ }
667
+ throw new Error('getDb() is only available with SQLite provider');
309
668
  }
310
669
  close() {
311
- this.db.close();
670
+ this.provider.close();
312
671
  }
313
672
  }
314
- function gc(db, col) {
315
- const rows = db
316
- .prepare(`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`)
317
- .all();
673
+ function gc(provider, col) {
674
+ const rows = provider.all(`SELECT ${col} as key, COUNT(*) as count FROM entries GROUP BY ${col}`);
318
675
  return Object.fromEntries(rows.map((r) => [r.key, r.count]));
319
676
  }
320
677
  function rowToEntry(row) {
@@ -331,6 +688,8 @@ function rowToEntry(row) {
331
688
  why: row.why ?? undefined,
332
689
  tags: JSON.parse(row.tags || '[]'),
333
690
  appliesTo: JSON.parse(row.applies_to || '[]'),
691
+ validFrom: row.valid_from ?? undefined,
692
+ validUntil: row.valid_until ?? undefined,
334
693
  };
335
694
  }
336
695
  function rowToSearchResult(row) {