@remnic/core 1.1.12 → 1.1.13

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 (1324) hide show
  1. package/dist/access-cli.d.ts +2 -1
  2. package/dist/access-cli.js +263 -82
  3. package/dist/access-cli.js.map +1 -1
  4. package/dist/access-http.d.ts +26 -60
  5. package/dist/access-http.js +43 -29
  6. package/dist/access-mcp.d.ts +24 -6
  7. package/dist/access-mcp.js +35 -28
  8. package/dist/access-schema.d.ts +9 -6
  9. package/dist/access-schema.js +7 -5
  10. package/dist/access-service-DcCDmNYC.d.ts +1542 -0
  11. package/dist/access-service.d.ts +25 -7
  12. package/dist/access-service.js +33 -26
  13. package/dist/active-memory-bridge.js +2 -2
  14. package/dist/active-recall.js +11 -3
  15. package/dist/active-recall.js.map +1 -1
  16. package/dist/adapters/claude-code.d.ts +24 -0
  17. package/dist/adapters/claude-code.js +9 -0
  18. package/dist/adapters/codex.d.ts +25 -0
  19. package/dist/adapters/codex.js +9 -0
  20. package/dist/adapters/hermes.d.ts +35 -0
  21. package/dist/adapters/hermes.js +9 -0
  22. package/dist/adapters/index.d.ts +6 -0
  23. package/dist/adapters/index.js +26 -0
  24. package/dist/adapters/registry.d.ts +20 -0
  25. package/dist/adapters/registry.js +13 -0
  26. package/dist/adapters/replit.d.ts +28 -0
  27. package/dist/adapters/replit.js +9 -0
  28. package/dist/adapters/types.d.ts +43 -0
  29. package/dist/adapters/types.js +8 -0
  30. package/dist/bootstrap.d.ts +20 -5
  31. package/dist/boxes.d.ts +7 -0
  32. package/dist/boxes.js +1 -1
  33. package/dist/briefing.d.ts +5 -3
  34. package/dist/briefing.js +9 -6
  35. package/dist/buffer-surprise-report.js +1 -1
  36. package/dist/buffer.d.ts +18 -4
  37. package/dist/buffer.js +1 -1
  38. package/dist/calibration.js +4 -4
  39. package/dist/capsule-cli.d.ts +4 -4
  40. package/dist/capsule-cli.js +1 -1
  41. package/dist/capsule-crypto-5CYAGVC5.js +18 -0
  42. package/dist/capsule-merge-4MGKE7C5.js +189 -0
  43. package/dist/causal-behavior.d.ts +8 -28
  44. package/dist/causal-behavior.js +6 -3
  45. package/dist/causal-behavior.js.map +1 -1
  46. package/dist/causal-chain.js +3 -2
  47. package/dist/causal-consolidation.d.ts +1 -1
  48. package/dist/causal-consolidation.js +24 -13
  49. package/dist/causal-consolidation.js.map +1 -1
  50. package/dist/causal-retrieval.js +3 -3
  51. package/dist/causal-trajectory.js +1 -1
  52. package/dist/chunk-25MQ7IHJ.js +427 -0
  53. package/dist/chunk-25MQ7IHJ.js.map +1 -0
  54. package/dist/chunk-2F2W355T.js +256 -0
  55. package/dist/chunk-2F2W355T.js.map +1 -0
  56. package/dist/chunk-2KI4QFHU.js +228 -0
  57. package/dist/chunk-2KI4QFHU.js.map +1 -0
  58. package/dist/chunk-2PRQG7PV.js +86 -0
  59. package/dist/chunk-2PRQG7PV.js.map +1 -0
  60. package/dist/chunk-2QR3XXIC.js +2272 -0
  61. package/dist/chunk-2QR3XXIC.js.map +1 -0
  62. package/dist/chunk-2WWLHTZY.js +121 -0
  63. package/dist/chunk-326G7DJK.js +2185 -0
  64. package/dist/chunk-326G7DJK.js.map +1 -0
  65. package/dist/chunk-34DQE4KF.js +174 -0
  66. package/dist/chunk-34DQE4KF.js.map +1 -0
  67. package/dist/chunk-3APJ5EVB.js +601 -0
  68. package/dist/chunk-3APJ5EVB.js.map +1 -0
  69. package/dist/chunk-3HPAPHUK.js +51 -0
  70. package/dist/chunk-3HPAPHUK.js.map +1 -0
  71. package/dist/chunk-3JXBXXM2.js +69 -0
  72. package/dist/chunk-3JXBXXM2.js.map +1 -0
  73. package/dist/chunk-3KW65B36.js +681 -0
  74. package/dist/chunk-3KW65B36.js.map +1 -0
  75. package/dist/chunk-3UXOZBHV.js +20 -0
  76. package/dist/chunk-3UXOZBHV.js.map +1 -0
  77. package/dist/chunk-3VAL7ZL2.js +266 -0
  78. package/dist/chunk-3VAL7ZL2.js.map +1 -0
  79. package/dist/chunk-3Y4P7RXM.js +31 -0
  80. package/dist/chunk-3Y4P7RXM.js.map +1 -0
  81. package/dist/chunk-47VWKCAF.js +273 -0
  82. package/dist/chunk-47VWKCAF.js.map +1 -0
  83. package/dist/chunk-4CRG46BG.js +271 -0
  84. package/dist/chunk-5375UYTQ.js +914 -0
  85. package/dist/chunk-5375UYTQ.js.map +1 -0
  86. package/dist/chunk-56K5QLHX.js +506 -0
  87. package/dist/chunk-56K5QLHX.js.map +1 -0
  88. package/dist/chunk-5RGLBDQF.js +596 -0
  89. package/dist/chunk-5RGLBDQF.js.map +1 -0
  90. package/dist/chunk-5UZXUTVO.js +9 -0
  91. package/dist/chunk-5UZXUTVO.js.map +1 -0
  92. package/dist/chunk-65PG43EQ.js +105 -0
  93. package/dist/chunk-65PG43EQ.js.map +1 -0
  94. package/dist/chunk-66DHUKLO.js +57 -0
  95. package/dist/chunk-66DHUKLO.js.map +1 -0
  96. package/dist/chunk-6FC5EGNV.js +46 -0
  97. package/dist/chunk-6FC5EGNV.js.map +1 -0
  98. package/dist/chunk-6H2TESSP.js +62 -0
  99. package/dist/chunk-6H2TESSP.js.map +1 -0
  100. package/dist/chunk-6LVVDPJ4.js +32 -0
  101. package/dist/chunk-6LVVDPJ4.js.map +1 -0
  102. package/dist/chunk-6RVI47ZR.js +159 -0
  103. package/dist/chunk-6RVI47ZR.js.map +1 -0
  104. package/dist/chunk-7AAT6G4Q.js +5117 -0
  105. package/dist/chunk-7AAT6G4Q.js.map +1 -0
  106. package/dist/chunk-7DTASS5T.js +29 -0
  107. package/dist/chunk-7DTASS5T.js.map +1 -0
  108. package/dist/chunk-7IASACLB.js +596 -0
  109. package/dist/chunk-7MNMYOFP.js +32 -0
  110. package/dist/chunk-7MNMYOFP.js.map +1 -0
  111. package/dist/chunk-7N4KAIGN.js +133 -0
  112. package/dist/chunk-7N4KAIGN.js.map +1 -0
  113. package/dist/chunk-7OZ53EXP.js +101 -0
  114. package/dist/chunk-7OZ53EXP.js.map +1 -0
  115. package/dist/chunk-7XYTQGCC.js +134 -0
  116. package/dist/chunk-7XYTQGCC.js.map +1 -0
  117. package/dist/chunk-A2XUIMJ3.js +341 -0
  118. package/dist/chunk-A2XUIMJ3.js.map +1 -0
  119. package/dist/chunk-AGZQD76C.js +201 -0
  120. package/dist/chunk-AGZQD76C.js.map +1 -0
  121. package/dist/chunk-APO3DCMU.js +361 -0
  122. package/dist/chunk-APO3DCMU.js.map +1 -0
  123. package/dist/chunk-BFBF3XEF.js +283 -0
  124. package/dist/chunk-BFBF3XEF.js.map +1 -0
  125. package/dist/chunk-BJ3KMYTB.js +1974 -0
  126. package/dist/chunk-BJ3KMYTB.js.map +1 -0
  127. package/dist/chunk-CHEL3SKB.js +6758 -0
  128. package/dist/chunk-CHEL3SKB.js.map +1 -0
  129. package/dist/chunk-CQZRLNMV.js +1491 -0
  130. package/dist/chunk-CQZRLNMV.js.map +1 -0
  131. package/dist/chunk-D46YSIYX.js +892 -0
  132. package/dist/chunk-D46YSIYX.js.map +1 -0
  133. package/dist/chunk-DINWEURR.js +648 -0
  134. package/dist/chunk-DINWEURR.js.map +1 -0
  135. package/dist/chunk-DK5LDEQM.js +530 -0
  136. package/dist/chunk-DK5LDEQM.js.map +1 -0
  137. package/dist/chunk-DOM4GKSW.js +34 -0
  138. package/dist/chunk-DOM4GKSW.js.map +1 -0
  139. package/dist/chunk-EDTHC6UD.js +1075 -0
  140. package/dist/chunk-EFJ3MQ4V.js +721 -0
  141. package/dist/chunk-EHRTFRWW.js +89 -0
  142. package/dist/chunk-EHRTFRWW.js.map +1 -0
  143. package/dist/chunk-FAJ7FZYM.js +11 -0
  144. package/dist/chunk-FAJ7FZYM.js.map +1 -0
  145. package/dist/chunk-FBYESMQ2.js +570 -0
  146. package/dist/chunk-FDU6HUUL.js +147 -0
  147. package/dist/chunk-FF4KLI5W.js +99 -0
  148. package/dist/chunk-FF4KLI5W.js.map +1 -0
  149. package/dist/chunk-FIT6DMX6.js +310 -0
  150. package/dist/chunk-FIT6DMX6.js.map +1 -0
  151. package/dist/chunk-FJ43PRLT.js +272 -0
  152. package/dist/chunk-FJ43PRLT.js.map +1 -0
  153. package/dist/chunk-FKFMOY3N.js +32 -0
  154. package/dist/chunk-FKFMOY3N.js.map +1 -0
  155. package/dist/chunk-FLTNHQK6.js +262 -0
  156. package/dist/chunk-FLTNHQK6.js.map +1 -0
  157. package/dist/chunk-GA454ALV.js +12436 -0
  158. package/dist/chunk-GA454ALV.js.map +1 -0
  159. package/dist/chunk-GGKRUQOO.js +228 -0
  160. package/dist/chunk-GIF42EW3.js +63 -0
  161. package/dist/chunk-GIF42EW3.js.map +1 -0
  162. package/dist/chunk-GL6I6MEQ.js +647 -0
  163. package/dist/chunk-H3ME6L6D.js +709 -0
  164. package/dist/chunk-H3ME6L6D.js.map +1 -0
  165. package/dist/chunk-HHLLAQGZ.js +1 -0
  166. package/dist/chunk-HXXBL2KD.js +2040 -0
  167. package/dist/chunk-I5V2VDIW.js +219 -0
  168. package/dist/chunk-I5V2VDIW.js.map +1 -0
  169. package/dist/chunk-I6K5FBRQ.js +35 -0
  170. package/dist/chunk-I6K5FBRQ.js.map +1 -0
  171. package/dist/chunk-ICRIXAP2.js +121 -0
  172. package/dist/chunk-ICRIXAP2.js.map +1 -0
  173. package/dist/chunk-J4EB7DNW.js +11 -0
  174. package/dist/chunk-J4EB7DNW.js.map +1 -0
  175. package/dist/chunk-JLFA7DQG.js +62 -0
  176. package/dist/chunk-JLFA7DQG.js.map +1 -0
  177. package/dist/chunk-KJTKLXTH.js +9 -0
  178. package/dist/chunk-KJTKLXTH.js.map +1 -0
  179. package/dist/chunk-KLAO5DGL.js +917 -0
  180. package/dist/chunk-KLAO5DGL.js.map +1 -0
  181. package/dist/chunk-KNKUID7G.js +183 -0
  182. package/dist/chunk-KOSORCJG.js +624 -0
  183. package/dist/chunk-KOSORCJG.js.map +1 -0
  184. package/dist/chunk-KUJVMMZQ.js +1262 -0
  185. package/dist/chunk-KUJVMMZQ.js.map +1 -0
  186. package/dist/chunk-LCR46JY5.js +123 -0
  187. package/dist/chunk-LCR46JY5.js.map +1 -0
  188. package/dist/chunk-LLQ2LLWF.js +148 -0
  189. package/dist/chunk-LLQ2LLWF.js.map +1 -0
  190. package/dist/chunk-LPMVBPA3.js +236 -0
  191. package/dist/chunk-LT3NLYSI.js +50 -0
  192. package/dist/chunk-LT3NLYSI.js.map +1 -0
  193. package/dist/chunk-LUDTDZLK.js +287 -0
  194. package/dist/chunk-LUDTDZLK.js.map +1 -0
  195. package/dist/chunk-M23FSH32.js +3963 -0
  196. package/dist/chunk-M23FSH32.js.map +1 -0
  197. package/dist/chunk-MC26UJIM.js +118 -0
  198. package/dist/chunk-ME6ESPZU.js +119 -0
  199. package/dist/chunk-ME6ESPZU.js.map +1 -0
  200. package/dist/chunk-MGKYQQYF.js +272 -0
  201. package/dist/chunk-MJFNCJXV.js +66 -0
  202. package/dist/chunk-MJFNCJXV.js.map +1 -0
  203. package/dist/chunk-MSWG7JI6.js +237 -0
  204. package/dist/chunk-MSWG7JI6.js.map +1 -0
  205. package/dist/chunk-MT25YHYH.js +141 -0
  206. package/dist/chunk-MT25YHYH.js.map +1 -0
  207. package/dist/chunk-MT4HVDUZ.js +53 -0
  208. package/dist/chunk-MY6TPVXW.js +219 -0
  209. package/dist/chunk-N2D6GXBM.js +267 -0
  210. package/dist/chunk-N2D6GXBM.js.map +1 -0
  211. package/dist/chunk-NJ3MJQZX.js +46 -0
  212. package/dist/chunk-NJ3MJQZX.js.map +1 -0
  213. package/dist/chunk-NMZY542O.js +335 -0
  214. package/dist/chunk-NMZY542O.js.map +1 -0
  215. package/dist/chunk-NNVTUXEB.js +23 -0
  216. package/dist/chunk-NZL6GGQE.js +375 -0
  217. package/dist/chunk-NZL6GGQE.js.map +1 -0
  218. package/dist/chunk-P4NEIHUT.js +108 -0
  219. package/dist/chunk-P7FMDTKL.js +103 -0
  220. package/dist/chunk-P7FMDTKL.js.map +1 -0
  221. package/dist/chunk-PHK3HARR.js +32 -0
  222. package/dist/chunk-PHK3HARR.js.map +1 -0
  223. package/dist/chunk-PIRJPV5T.js +98 -0
  224. package/dist/chunk-PIRJPV5T.js.map +1 -0
  225. package/dist/chunk-PK7H5L6Y.js +159 -0
  226. package/dist/chunk-PK7H5L6Y.js.map +1 -0
  227. package/dist/chunk-PR5FBTFU.js +233 -0
  228. package/dist/chunk-PR5FBTFU.js.map +1 -0
  229. package/dist/chunk-PU63GXWS.js +174 -0
  230. package/dist/chunk-PU63GXWS.js.map +1 -0
  231. package/dist/chunk-PZIAX57I.js +124 -0
  232. package/dist/chunk-PZIAX57I.js.map +1 -0
  233. package/dist/chunk-Q7P4WJDP.js +26 -0
  234. package/dist/chunk-Q7P4WJDP.js.map +1 -0
  235. package/dist/chunk-QQUAB63I.js +63 -0
  236. package/dist/chunk-QQUAB63I.js.map +1 -0
  237. package/dist/chunk-QRNI5JBH.js +18 -0
  238. package/dist/chunk-RHY3HH7P.js +601 -0
  239. package/dist/chunk-RHY3HH7P.js.map +1 -0
  240. package/dist/chunk-RRF5UOBJ.js +91 -0
  241. package/dist/chunk-RXDLTSWT.js +124 -0
  242. package/dist/chunk-RXDLTSWT.js.map +1 -0
  243. package/dist/chunk-RYED3SPJ.js +42 -0
  244. package/dist/chunk-RYED3SPJ.js.map +1 -0
  245. package/dist/chunk-S7KDBTWT.js +106 -0
  246. package/dist/chunk-S7KDBTWT.js.map +1 -0
  247. package/dist/chunk-SEDEKFYQ.js +1 -0
  248. package/dist/chunk-TECVW3JP.js +36 -0
  249. package/dist/chunk-TECVW3JP.js.map +1 -0
  250. package/dist/chunk-TFO23QT4.js +88 -0
  251. package/dist/chunk-TFO23QT4.js.map +1 -0
  252. package/dist/chunk-TK4UEOSK.js +76 -0
  253. package/dist/chunk-TK4UEOSK.js.map +1 -0
  254. package/dist/chunk-TKWGAOLV.js +122 -0
  255. package/dist/chunk-TKWGAOLV.js.map +1 -0
  256. package/dist/chunk-TMM4S4IJ.js +597 -0
  257. package/dist/chunk-TMM4S4IJ.js.map +1 -0
  258. package/dist/chunk-TMQLARTH.js +188 -0
  259. package/dist/chunk-TMQLARTH.js.map +1 -0
  260. package/dist/chunk-TPDBFYEG.js +130 -0
  261. package/dist/chunk-TPDBFYEG.js.map +1 -0
  262. package/dist/chunk-TPMQ3G6Z.js +145 -0
  263. package/dist/chunk-TPMQ3G6Z.js.map +1 -0
  264. package/dist/chunk-TZOLIGIG.js +61 -0
  265. package/dist/chunk-TZOLIGIG.js.map +1 -0
  266. package/dist/chunk-U3PN77QT.js +113 -0
  267. package/dist/chunk-U3WSW6PZ.js +277 -0
  268. package/dist/chunk-U4SCL7B7.js +640 -0
  269. package/dist/chunk-U4SCL7B7.js.map +1 -0
  270. package/dist/chunk-UWK5OXUJ.js +156 -0
  271. package/dist/chunk-UWK5OXUJ.js.map +1 -0
  272. package/dist/chunk-UWVJF25J.js +74 -0
  273. package/dist/chunk-UXHQAFNA.js +1317 -0
  274. package/dist/chunk-UXHQAFNA.js.map +1 -0
  275. package/dist/chunk-V5OCT34X.js +1 -0
  276. package/dist/chunk-VLXA6PI2.js +304 -0
  277. package/dist/chunk-VLXA6PI2.js.map +1 -0
  278. package/dist/chunk-VNO6ZJ35.js +500 -0
  279. package/dist/chunk-VNO6ZJ35.js.map +1 -0
  280. package/dist/chunk-VW676BEI.js +827 -0
  281. package/dist/chunk-VW676BEI.js.map +1 -0
  282. package/dist/chunk-W3LR522O.js +2296 -0
  283. package/dist/chunk-W4L6CZKA.js +96 -0
  284. package/dist/chunk-W4L6CZKA.js.map +1 -0
  285. package/dist/chunk-W4RVMTHR.js +372 -0
  286. package/dist/chunk-W4RVMTHR.js.map +1 -0
  287. package/dist/chunk-WEHSQBFR.js +188 -0
  288. package/dist/chunk-WEHSQBFR.js.map +1 -0
  289. package/dist/chunk-WELDCG6C.js +380 -0
  290. package/dist/chunk-WELDCG6C.js.map +1 -0
  291. package/dist/chunk-WZYKANL3.js +2800 -0
  292. package/dist/chunk-WZYKANL3.js.map +1 -0
  293. package/dist/chunk-XIG5PDM7.js +48 -0
  294. package/dist/chunk-XJNBEDFE.js +193 -0
  295. package/dist/chunk-XJNBEDFE.js.map +1 -0
  296. package/dist/chunk-XVVIG67A.js +291 -0
  297. package/dist/chunk-XVVIG67A.js.map +1 -0
  298. package/dist/chunk-XVZ7B3HG.js +135 -0
  299. package/dist/chunk-YBPYIAA5.js +73 -0
  300. package/dist/chunk-YBPYIAA5.js.map +1 -0
  301. package/dist/chunk-Z734BLO3.js +21 -0
  302. package/dist/chunk-Z734BLO3.js.map +1 -0
  303. package/dist/chunk-ZKSK55RC.js +269 -0
  304. package/dist/chunk-ZKSK55RC.js.map +1 -0
  305. package/dist/chunk-ZTFCYYEZ.js +69 -0
  306. package/dist/chunk-ZTFCYYEZ.js.map +1 -0
  307. package/dist/chunk-ZY2MNJR6.js +329 -0
  308. package/dist/chunk-ZY2MNJR6.js.map +1 -0
  309. package/dist/cli-D3VpkVwB.d.ts +1136 -0
  310. package/dist/cli.d.ts +39 -10
  311. package/dist/cli.js +108 -49
  312. package/dist/commitment-ledger.js +1 -1
  313. package/dist/compat/checks.d.ts +5 -0
  314. package/dist/compat/checks.js +11 -0
  315. package/dist/compat/checks.js.map +1 -0
  316. package/dist/compat/types.d.ts +30 -0
  317. package/dist/compat/types.js +1 -0
  318. package/dist/compat/types.js.map +1 -0
  319. package/dist/compounding/engine.d.ts +221 -0
  320. package/dist/compounding/engine.js +32 -0
  321. package/dist/compounding/engine.js.map +1 -0
  322. package/dist/compounding/preference-consolidator.d.ts +92 -0
  323. package/dist/compounding/preference-consolidator.js +553 -0
  324. package/dist/compounding/preference-consolidator.js.map +1 -0
  325. package/dist/config.d.ts +4 -2
  326. package/dist/config.js +9 -4
  327. package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
  328. package/dist/connectors/codex-materialize-runner.d.ts +64 -0
  329. package/dist/connectors/codex-materialize-runner.js +33 -0
  330. package/dist/connectors/codex-materialize-runner.js.map +1 -0
  331. package/dist/connectors/codex-materialize.d.ts +195 -0
  332. package/dist/connectors/codex-materialize.js +38 -0
  333. package/dist/connectors/codex-materialize.js.map +1 -0
  334. package/dist/connectors/index.d.ts +444 -0
  335. package/dist/connectors/index.js +115 -0
  336. package/dist/connectors/index.js.map +1 -0
  337. package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
  338. package/dist/connectors-cli.d.ts +1 -1
  339. package/dist/consolidation-provenance-check.d.ts +3 -1
  340. package/dist/consolidation-undo.d.ts +3 -1
  341. package/dist/contradiction/index.d.ts +258 -0
  342. package/dist/contradiction/index.js +43 -0
  343. package/dist/contradiction/index.js.map +1 -0
  344. package/dist/contradiction-review-ATP4S6IC.js +30 -0
  345. package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
  346. package/dist/contradiction-scan-5A4IDZV5.js +13 -0
  347. package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
  348. package/dist/conversation-index/backend.d.ts +97 -0
  349. package/dist/conversation-index/backend.js +13 -0
  350. package/dist/conversation-index/backend.js.map +1 -0
  351. package/dist/conversation-index/chunker.d.ts +16 -0
  352. package/dist/conversation-index/chunker.js +8 -0
  353. package/dist/conversation-index/chunker.js.map +1 -0
  354. package/dist/conversation-index/cleanup.d.ts +11 -0
  355. package/dist/conversation-index/cleanup.js +9 -0
  356. package/dist/conversation-index/cleanup.js.map +1 -0
  357. package/dist/conversation-index/faiss-adapter.d.ts +6 -0
  358. package/dist/conversation-index/faiss-adapter.js +16 -0
  359. package/dist/conversation-index/faiss-adapter.js.map +1 -0
  360. package/dist/conversation-index/indexer.d.ts +23 -0
  361. package/dist/conversation-index/indexer.js +15 -0
  362. package/dist/conversation-index/indexer.js.map +1 -0
  363. package/dist/conversation-index/search.d.ts +6 -0
  364. package/dist/conversation-index/search.js +11 -0
  365. package/dist/conversation-index/search.js.map +1 -0
  366. package/dist/embedding-fallback.js +2 -2
  367. package/dist/enrichment/index.d.ts +163 -0
  368. package/dist/enrichment/index.js +18 -0
  369. package/dist/enrichment/index.js.map +1 -0
  370. package/dist/entity-retrieval.d.ts +4 -2
  371. package/dist/entity-retrieval.js +8 -5
  372. package/dist/evals.js +1 -1
  373. package/dist/explicit-capture.d.ts +20 -5
  374. package/dist/explicit-capture.js +2 -2
  375. package/dist/extraction-judge-training.js +1 -1
  376. package/dist/extraction.js +8 -8
  377. package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
  378. package/dist/fallback-llm.d.ts +2 -0
  379. package/dist/fallback-llm.js +4 -4
  380. package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
  381. package/dist/index.d.ts +66 -711
  382. package/dist/index.js +556 -2680
  383. package/dist/index.js.map +1 -1
  384. package/dist/lcm/archive.d.ts +89 -0
  385. package/dist/lcm/archive.js +12 -0
  386. package/dist/lcm/archive.js.map +1 -0
  387. package/dist/lcm/dag.d.ts +48 -0
  388. package/dist/lcm/dag.js +8 -0
  389. package/dist/lcm/dag.js.map +1 -0
  390. package/dist/lcm/engine.d.ts +116 -0
  391. package/dist/lcm/engine.js +20 -0
  392. package/dist/lcm/engine.js.map +1 -0
  393. package/dist/lcm/index.d.ts +12 -0
  394. package/dist/lcm/index.js +44 -0
  395. package/dist/lcm/index.js.map +1 -0
  396. package/dist/lcm/queue.d.ts +62 -0
  397. package/dist/lcm/queue.js +8 -0
  398. package/dist/lcm/queue.js.map +1 -0
  399. package/dist/lcm/recall.d.ts +20 -0
  400. package/dist/lcm/recall.js +8 -0
  401. package/dist/lcm/recall.js.map +1 -0
  402. package/dist/lcm/schema.d.ts +16 -0
  403. package/dist/lcm/schema.js +14 -0
  404. package/dist/lcm/schema.js.map +1 -0
  405. package/dist/lcm/summarizer.d.ts +38 -0
  406. package/dist/lcm/summarizer.js +12 -0
  407. package/dist/lcm/summarizer.js.map +1 -0
  408. package/dist/lcm/tools.d.ts +29 -0
  409. package/dist/lcm/tools.js +8 -0
  410. package/dist/lcm/tools.js.map +1 -0
  411. package/dist/live-connectors-runner.js +5 -5
  412. package/dist/local-llm.js +3 -3
  413. package/dist/maintenance/archive-observations.d.ts +18 -0
  414. package/dist/maintenance/archive-observations.js +8 -0
  415. package/dist/maintenance/archive-observations.js.map +1 -0
  416. package/dist/maintenance/backup-stamp.d.ts +3 -0
  417. package/dist/maintenance/backup-stamp.js +8 -0
  418. package/dist/maintenance/backup-stamp.js.map +1 -0
  419. package/dist/maintenance/memory-governance-cron.d.ts +85 -0
  420. package/dist/maintenance/memory-governance-cron.js +22 -0
  421. package/dist/maintenance/memory-governance-cron.js.map +1 -0
  422. package/dist/maintenance/memory-governance.d.ts +137 -0
  423. package/dist/maintenance/memory-governance.js +40 -0
  424. package/dist/maintenance/memory-governance.js.map +1 -0
  425. package/dist/maintenance/migrate-observations.d.ts +18 -0
  426. package/dist/maintenance/migrate-observations.js +9 -0
  427. package/dist/maintenance/migrate-observations.js.map +1 -0
  428. package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
  429. package/dist/maintenance/observation-ledger-utils.js +10 -0
  430. package/dist/maintenance/observation-ledger-utils.js.map +1 -0
  431. package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
  432. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
  433. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
  434. package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
  435. package/dist/maintenance/rebuild-memory-projection.js +35 -0
  436. package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
  437. package/dist/maintenance/rebuild-observations.d.ts +17 -0
  438. package/dist/maintenance/rebuild-observations.js +9 -0
  439. package/dist/maintenance/rebuild-observations.js.map +1 -0
  440. package/dist/mcp-memory-inspector-app.d.ts +24 -6
  441. package/dist/memory-projection-store.d.ts +108 -3
  442. package/dist/memory-projection-store.js +2 -1
  443. package/dist/memory-worth-outcomes.d.ts +4 -2
  444. package/dist/migrate/from-engram.d.ts +24 -0
  445. package/dist/migrate/from-engram.js +12 -0
  446. package/dist/migrate/from-engram.js.map +1 -0
  447. package/dist/namespaces/migrate.d.ts +50 -0
  448. package/dist/namespaces/migrate.js +50 -0
  449. package/dist/namespaces/migrate.js.map +1 -0
  450. package/dist/namespaces/principal.d.ts +17 -0
  451. package/dist/namespaces/principal.js +16 -0
  452. package/dist/namespaces/principal.js.map +1 -0
  453. package/dist/namespaces/search.d.ts +46 -0
  454. package/dist/namespaces/search.js +28 -0
  455. package/dist/namespaces/search.js.map +1 -0
  456. package/dist/namespaces/storage.d.ts +32 -0
  457. package/dist/namespaces/storage.js +28 -0
  458. package/dist/namespaces/storage.js.map +1 -0
  459. package/dist/network/tailscale.d.ts +41 -0
  460. package/dist/network/tailscale.js +9 -0
  461. package/dist/network/tailscale.js.map +1 -0
  462. package/dist/network/webdav.d.ts +39 -0
  463. package/dist/network/webdav.js +10 -0
  464. package/dist/network/webdav.js.map +1 -0
  465. package/dist/objective-state-writers.js +2 -2
  466. package/dist/operator-toolkit.d.ts +4 -2
  467. package/dist/operator-toolkit.js +32 -14
  468. package/dist/opik-exporter.js +2 -2
  469. package/dist/opik-exporter.js.map +1 -1
  470. package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
  471. package/dist/orchestrator.d.ts +22 -7
  472. package/dist/orchestrator.js +79 -44
  473. package/dist/path-MR5JPYOP.js +9 -0
  474. package/dist/path-MR5JPYOP.js.map +1 -0
  475. package/dist/qmd-recall-cache.d.ts +1 -1
  476. package/dist/qmd.d.ts +102 -3
  477. package/dist/qmd.js +23 -5
  478. package/dist/recall-explain-renderer.js +3 -3
  479. package/dist/recall-xray-cli.js +4 -4
  480. package/dist/recall-xray-renderer.js +3 -3
  481. package/dist/recall-xray.js +2 -2
  482. package/dist/replay/normalizers/chatgpt.d.ts +6 -0
  483. package/dist/replay/normalizers/chatgpt.js +11 -0
  484. package/dist/replay/normalizers/chatgpt.js.map +1 -0
  485. package/dist/replay/normalizers/claude.d.ts +6 -0
  486. package/dist/replay/normalizers/claude.js +11 -0
  487. package/dist/replay/normalizers/claude.js.map +1 -0
  488. package/dist/replay/normalizers/openclaw.d.ts +6 -0
  489. package/dist/replay/normalizers/openclaw.js +11 -0
  490. package/dist/replay/normalizers/openclaw.js.map +1 -0
  491. package/dist/replay/normalizers/shared.d.ts +16 -0
  492. package/dist/replay/normalizers/shared.js +14 -0
  493. package/dist/replay/normalizers/shared.js.map +1 -0
  494. package/dist/replay/runner.d.ts +35 -0
  495. package/dist/replay/runner.js +16 -0
  496. package/dist/replay/runner.js.map +1 -0
  497. package/dist/replay/types.d.ts +57 -0
  498. package/dist/replay/types.js +19 -0
  499. package/dist/replay/types.js.map +1 -0
  500. package/dist/resolution-B7FNQSSP.js +12 -0
  501. package/dist/resolution-B7FNQSSP.js.map +1 -0
  502. package/dist/resolve-provider-secret.js +2 -2
  503. package/dist/resume-bundles.js +8 -6
  504. package/dist/retrieval-agents.d.ts +1 -1
  505. package/dist/routing/engine.d.ts +35 -0
  506. package/dist/routing/engine.js +16 -0
  507. package/dist/routing/engine.js.map +1 -0
  508. package/dist/routing/store.d.ts +27 -0
  509. package/dist/routing/store.js +10 -0
  510. package/dist/routing/store.js.map +1 -0
  511. package/dist/runtime/better-sqlite.d.ts +8 -0
  512. package/dist/runtime/better-sqlite.js +10 -0
  513. package/dist/runtime/better-sqlite.js.map +1 -0
  514. package/dist/runtime/child-process.d.ts +32 -0
  515. package/dist/runtime/child-process.js +10 -0
  516. package/dist/runtime/child-process.js.map +1 -0
  517. package/dist/runtime/env.d.ts +5 -0
  518. package/dist/runtime/env.js +12 -0
  519. package/dist/runtime/env.js.map +1 -0
  520. package/dist/schemas.d.ts +22 -22
  521. package/dist/sdk-compat.js +1 -1
  522. package/dist/search/document-scanner.d.ts +22 -0
  523. package/dist/search/document-scanner.js +8 -0
  524. package/dist/search/document-scanner.js.map +1 -0
  525. package/dist/search/embed-helper.d.ts +35 -0
  526. package/dist/search/embed-helper.js +9 -0
  527. package/dist/search/embed-helper.js.map +1 -0
  528. package/dist/search/factory.d.ts +32 -0
  529. package/dist/search/factory.js +29 -0
  530. package/dist/search/factory.js.map +1 -0
  531. package/dist/search/index.d.ts +15 -0
  532. package/dist/search/index.js +50 -0
  533. package/dist/search/index.js.map +1 -0
  534. package/dist/search/lancedb-backend.d.ts +51 -0
  535. package/dist/search/lancedb-backend.js +10 -0
  536. package/dist/search/lancedb-backend.js.map +1 -0
  537. package/dist/search/meilisearch-backend.d.ts +48 -0
  538. package/dist/search/meilisearch-backend.js +10 -0
  539. package/dist/search/meilisearch-backend.js.map +1 -0
  540. package/dist/search/noop-backend.d.ts +26 -0
  541. package/dist/search/noop-backend.js +8 -0
  542. package/dist/search/noop-backend.js.map +1 -0
  543. package/dist/search/orama-backend.d.ts +53 -0
  544. package/dist/search/orama-backend.js +10 -0
  545. package/dist/search/orama-backend.js.map +1 -0
  546. package/dist/search/port.d.ts +61 -0
  547. package/dist/search/port.js +1 -0
  548. package/dist/search/port.js.map +1 -0
  549. package/dist/search/remote-backend.d.ts +39 -0
  550. package/dist/search/remote-backend.js +9 -0
  551. package/dist/search/remote-backend.js.map +1 -0
  552. package/dist/secure-store/index.d.ts +890 -0
  553. package/dist/secure-store/index.js +156 -0
  554. package/dist/secure-store/index.js.map +1 -0
  555. package/dist/semantic-VwGI14Ok.d.ts +69 -0
  556. package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
  557. package/dist/semantic-consolidation.d.ts +2 -2
  558. package/dist/semantic-consolidation.js +13 -6
  559. package/dist/semantic-rule-promotion.js +8 -5
  560. package/dist/semantic-rule-verifier.js +8 -5
  561. package/dist/shared-context/manager.d.ts +131 -0
  562. package/dist/shared-context/manager.js +15 -0
  563. package/dist/shared-context/manager.js.map +1 -0
  564. package/dist/skills-registry.js +13 -1
  565. package/dist/skills-registry.js.map +1 -1
  566. package/dist/state-store-VZU2IA53.js +16 -0
  567. package/dist/state-store-VZU2IA53.js.map +1 -0
  568. package/dist/storage-paths.d.ts +9 -0
  569. package/dist/storage-paths.js +20 -0
  570. package/dist/storage-paths.js.map +1 -0
  571. package/dist/storage.d.ts +3 -1
  572. package/dist/storage.js +7 -4
  573. package/dist/summarizer.d.ts +5 -0
  574. package/dist/summarizer.js +9 -8
  575. package/dist/summary-snapshot.js +2 -1
  576. package/dist/surfaces/dreams.d.ts +16 -0
  577. package/dist/surfaces/dreams.js +282 -0
  578. package/dist/surfaces/dreams.js.map +1 -0
  579. package/dist/surfaces/heartbeat.d.ts +17 -0
  580. package/dist/surfaces/heartbeat.js +265 -0
  581. package/dist/surfaces/heartbeat.js.map +1 -0
  582. package/dist/temporal-supersession.d.ts +3 -1
  583. package/dist/threading.d.ts +5 -0
  584. package/dist/threading.js +2 -1
  585. package/dist/tier-migration.d.ts +4 -2
  586. package/dist/tokens.js +2 -2
  587. package/dist/transcript.d.ts +15 -1
  588. package/dist/transcript.js +2 -1
  589. package/dist/transfer/autodetect.d.ts +4 -0
  590. package/dist/transfer/autodetect.js +15 -0
  591. package/dist/transfer/autodetect.js.map +1 -0
  592. package/dist/transfer/backup.d.ts +21 -0
  593. package/dist/transfer/backup.js +17 -0
  594. package/dist/transfer/backup.js.map +1 -0
  595. package/dist/transfer/capsule-export.d.ts +113 -0
  596. package/dist/transfer/capsule-export.js +19 -0
  597. package/dist/transfer/capsule-export.js.map +1 -0
  598. package/dist/transfer/capsule-import.d.ts +124 -0
  599. package/dist/transfer/capsule-import.js +16 -0
  600. package/dist/transfer/capsule-import.js.map +1 -0
  601. package/dist/transfer/constants.d.ts +13 -0
  602. package/dist/transfer/constants.js +12 -0
  603. package/dist/transfer/constants.js.map +1 -0
  604. package/dist/transfer/export-json.d.ts +11 -0
  605. package/dist/transfer/export-json.js +11 -0
  606. package/dist/transfer/export-json.js.map +1 -0
  607. package/dist/transfer/export-md.d.ts +10 -0
  608. package/dist/transfer/export-md.js +13 -0
  609. package/dist/transfer/export-md.js.map +1 -0
  610. package/dist/transfer/export-sqlite.d.ts +9 -0
  611. package/dist/transfer/export-sqlite.js +12 -0
  612. package/dist/transfer/export-sqlite.js.map +1 -0
  613. package/dist/transfer/fs-utils.d.ts +61 -0
  614. package/dist/transfer/fs-utils.js +40 -0
  615. package/dist/transfer/fs-utils.js.map +1 -0
  616. package/dist/transfer/import-json.d.ts +16 -0
  617. package/dist/transfer/import-json.js +13 -0
  618. package/dist/transfer/import-json.js.map +1 -0
  619. package/dist/transfer/import-md.d.ts +14 -0
  620. package/dist/transfer/import-md.js +11 -0
  621. package/dist/transfer/import-md.js.map +1 -0
  622. package/dist/transfer/import-sqlite.d.ts +14 -0
  623. package/dist/transfer/import-sqlite.js +12 -0
  624. package/dist/transfer/import-sqlite.js.map +1 -0
  625. package/dist/transfer/sqlite-schema.d.ts +4 -0
  626. package/dist/transfer/sqlite-schema.js +10 -0
  627. package/dist/transfer/sqlite-schema.js.map +1 -0
  628. package/dist/transfer/types.d.ts +916 -0
  629. package/dist/transfer/types.js +30 -0
  630. package/dist/transfer/types.js.map +1 -0
  631. package/dist/types.d.ts +28 -1
  632. package/dist/types.js +1 -1
  633. package/dist/verified-recall.js +9 -6
  634. package/dist/work/board.d.ts +43 -0
  635. package/dist/work/board.js +14 -0
  636. package/dist/work/board.js.map +1 -0
  637. package/dist/work/boundary.d.ts +8 -0
  638. package/dist/work/boundary.js +14 -0
  639. package/dist/work/boundary.js.map +1 -0
  640. package/dist/work/storage.d.ts +39 -0
  641. package/dist/work/storage.js +11 -0
  642. package/dist/work/storage.js.map +1 -0
  643. package/dist/work/types.d.ts +75 -0
  644. package/dist/work/types.js +1 -0
  645. package/dist/work/types.js.map +1 -0
  646. package/package.json +2767 -6
  647. package/scripts/faiss_index.py +816 -0
  648. package/scripts/faiss_requirements.txt +3 -0
  649. package/skills/remnic-entities/SKILL.md +51 -0
  650. package/skills/remnic-memory-workflow/SKILL.md +61 -0
  651. package/skills/remnic-recall/SKILL.md +51 -0
  652. package/skills/remnic-remember/SKILL.md +56 -0
  653. package/skills/remnic-search/SKILL.md +51 -0
  654. package/skills/remnic-status/SKILL.md +51 -0
  655. package/src/abort-error.test.ts +49 -0
  656. package/src/abort-error.ts +46 -0
  657. package/src/abstraction-nodes.ts +162 -0
  658. package/src/access-audit.test.ts +178 -0
  659. package/src/access-audit.ts +125 -0
  660. package/src/access-cli.test.ts +439 -0
  661. package/src/access-cli.ts +438 -0
  662. package/src/access-http.test.ts +225 -0
  663. package/src/access-http.ts +1899 -0
  664. package/src/access-idempotency.ts +232 -0
  665. package/src/access-mcp.test.ts +568 -0
  666. package/src/access-mcp.ts +3056 -0
  667. package/src/access-schema-pi.test.ts +60 -0
  668. package/src/access-schema.ts +522 -0
  669. package/src/access-service-namespace.test.ts +123 -0
  670. package/src/access-service.ts +5629 -0
  671. package/src/action-confidence.test.ts +206 -0
  672. package/src/action-confidence.ts +466 -0
  673. package/src/active-memory-bridge.test.ts +285 -0
  674. package/src/active-memory-bridge.ts +217 -0
  675. package/src/active-recall.test.ts +484 -0
  676. package/src/active-recall.ts +459 -0
  677. package/src/adapters/claude-code.ts +56 -0
  678. package/src/adapters/codex.ts +57 -0
  679. package/src/adapters/hermes.ts +64 -0
  680. package/src/adapters/index.ts +6 -0
  681. package/src/adapters/registry.ts +41 -0
  682. package/src/adapters/replit.ts +55 -0
  683. package/src/adapters/types.ts +51 -0
  684. package/src/behavior-learner.ts +144 -0
  685. package/src/behavior-signals.ts +73 -0
  686. package/src/binary-lifecycle/backend.ts +117 -0
  687. package/src/binary-lifecycle/index.ts +35 -0
  688. package/src/binary-lifecycle/manifest.ts +79 -0
  689. package/src/binary-lifecycle/pipeline.ts +352 -0
  690. package/src/binary-lifecycle/scanner.ts +89 -0
  691. package/src/binary-lifecycle/types.ts +89 -0
  692. package/src/bootstrap.ts +178 -0
  693. package/src/boxes.ts +521 -0
  694. package/src/briefing.test.ts +1535 -0
  695. package/src/briefing.ts +1382 -0
  696. package/src/buffer-session.test.ts +443 -0
  697. package/src/buffer-surprise-report.ts +176 -0
  698. package/src/buffer-surprise-telemetry.test.ts +606 -0
  699. package/src/buffer-surprise-trigger.test.ts +766 -0
  700. package/src/buffer-surprise.test.ts +339 -0
  701. package/src/buffer-surprise.ts +203 -0
  702. package/src/buffer.ts +900 -0
  703. package/src/bulk-import/cli-command.test.ts +204 -0
  704. package/src/bulk-import/index.ts +34 -0
  705. package/src/bulk-import/pipeline.test.ts +445 -0
  706. package/src/bulk-import/pipeline.ts +178 -0
  707. package/src/bulk-import/registry.test.ts +151 -0
  708. package/src/bulk-import/registry.ts +72 -0
  709. package/src/bulk-import/types.test.ts +272 -0
  710. package/src/bulk-import/types.ts +145 -0
  711. package/src/calibration.ts +394 -0
  712. package/src/capsule-cli.test.ts +398 -0
  713. package/src/capsule-cli.ts +565 -0
  714. package/src/causal-behavior.ts +308 -0
  715. package/src/causal-chain.ts +419 -0
  716. package/src/causal-consolidation.ts +370 -0
  717. package/src/causal-retrieval.ts +286 -0
  718. package/src/causal-trajectory-graph.ts +60 -0
  719. package/src/causal-trajectory.ts +303 -0
  720. package/src/chunking.ts +220 -0
  721. package/src/citations.ts +232 -0
  722. package/src/cli.ts +9403 -0
  723. package/src/codex-cli-fallback.ts +162 -0
  724. package/src/codex-thread-key.ts +1 -0
  725. package/src/coding/access-coding-context.test.ts +197 -0
  726. package/src/coding/coding-branch-scope.test.ts +281 -0
  727. package/src/coding/coding-namespace.test.ts +360 -0
  728. package/src/coding/coding-namespace.ts +412 -0
  729. package/src/coding/coding-orchestrator.test.ts +249 -0
  730. package/src/coding/git-context.test.ts +507 -0
  731. package/src/coding/git-context.ts +336 -0
  732. package/src/coding/mcp-set-coding-context.test.ts +174 -0
  733. package/src/coding/review-context.test.ts +316 -0
  734. package/src/coding/review-context.ts +349 -0
  735. package/src/coding/wire-coding-context.test.ts +468 -0
  736. package/src/commitment-ledger.test.ts +78 -0
  737. package/src/commitment-ledger.ts +337 -0
  738. package/src/compat/checks.test.ts +206 -0
  739. package/src/compat/checks.ts +716 -0
  740. package/src/compat/types.ts +33 -0
  741. package/src/compounding/engine.ts +1686 -0
  742. package/src/compounding/preference-consolidator.ts +778 -0
  743. package/src/compression-optimizer.ts +312 -0
  744. package/src/config.test.ts +930 -0
  745. package/src/config.ts +3807 -0
  746. package/src/connectors/codex/instructions.md +160 -0
  747. package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
  748. package/src/connectors/codex-marketplace.ts +500 -0
  749. package/src/connectors/codex-materialize-runner.ts +212 -0
  750. package/src/connectors/codex-materialize.ts +983 -0
  751. package/src/connectors/coerce.ts +62 -0
  752. package/src/connectors/index.test.ts +1570 -0
  753. package/src/connectors/index.ts +3222 -0
  754. package/src/connectors/live/framework.ts +164 -0
  755. package/src/connectors/live/github.test.ts +1218 -0
  756. package/src/connectors/live/github.ts +1068 -0
  757. package/src/connectors/live/gmail.test.ts +1706 -0
  758. package/src/connectors/live/gmail.ts +1293 -0
  759. package/src/connectors/live/google-drive.test.ts +696 -0
  760. package/src/connectors/live/google-drive.ts +724 -0
  761. package/src/connectors/live/index.ts +101 -0
  762. package/src/connectors/live/live-connectors.test.ts +689 -0
  763. package/src/connectors/live/notion.test.ts +1109 -0
  764. package/src/connectors/live/notion.ts +978 -0
  765. package/src/connectors/live/registry.ts +103 -0
  766. package/src/connectors/live/state-store.ts +399 -0
  767. package/src/connectors/live/transient-errors.ts +150 -0
  768. package/src/connectors/weclone-installer.test.ts +850 -0
  769. package/src/connectors-cli.ts +513 -0
  770. package/src/console/state.test.ts +224 -0
  771. package/src/console/state.ts +514 -0
  772. package/src/console/trace.test.ts +813 -0
  773. package/src/console/trace.ts +603 -0
  774. package/src/console/tui.test.ts +582 -0
  775. package/src/console/tui.ts +508 -0
  776. package/src/consolidation-operator.ts +182 -0
  777. package/src/consolidation-provenance-check.ts +551 -0
  778. package/src/consolidation-undo.ts +718 -0
  779. package/src/contradiction/contradiction-judge.test.ts +189 -0
  780. package/src/contradiction/contradiction-judge.ts +333 -0
  781. package/src/contradiction/contradiction-review.ts +574 -0
  782. package/src/contradiction/contradiction-scan.ts +504 -0
  783. package/src/contradiction/contradiction.test.ts +2230 -0
  784. package/src/contradiction/index.ts +37 -0
  785. package/src/contradiction/resolution.ts +383 -0
  786. package/src/conversation-index/backend.ts +323 -0
  787. package/src/conversation-index/chunker.ts +47 -0
  788. package/src/conversation-index/cleanup.ts +53 -0
  789. package/src/conversation-index/faiss-adapter.ts +384 -0
  790. package/src/conversation-index/indexer.test.ts +164 -0
  791. package/src/conversation-index/indexer.ts +192 -0
  792. package/src/conversation-index/search.ts +37 -0
  793. package/src/cross-namespace-budget.test.ts +275 -0
  794. package/src/cross-namespace-budget.ts +365 -0
  795. package/src/cue-anchors.ts +163 -0
  796. package/src/curation/index.ts +544 -0
  797. package/src/dashboard-runtime.ts +337 -0
  798. package/src/day-summary.ts +122 -0
  799. package/src/dedup/index.ts +330 -0
  800. package/src/dedup/semantic.test.ts +1577 -0
  801. package/src/dedup/semantic.ts +148 -0
  802. package/src/delinearize.ts +193 -0
  803. package/src/direct-answer-wiring.test.ts +473 -0
  804. package/src/direct-answer-wiring.ts +180 -0
  805. package/src/direct-answer.test.ts +484 -0
  806. package/src/direct-answer.ts +273 -0
  807. package/src/embedding-fallback.ts +565 -0
  808. package/src/enrichment/audit.ts +89 -0
  809. package/src/enrichment/index.ts +27 -0
  810. package/src/enrichment/pipeline.ts +197 -0
  811. package/src/enrichment/provider-registry.ts +85 -0
  812. package/src/enrichment/types.ts +100 -0
  813. package/src/enrichment/web-search-provider.ts +63 -0
  814. package/src/entity-retrieval.ts +774 -0
  815. package/src/entity-schema.ts +239 -0
  816. package/src/evals.ts +1312 -0
  817. package/src/event-order-recall.test.ts +4164 -0
  818. package/src/event-order-recall.ts +2802 -0
  819. package/src/evidence-pack.test.ts +89 -0
  820. package/src/evidence-pack.ts +388 -0
  821. package/src/explicit-capture.ts +530 -0
  822. package/src/explicit-cue-recall.test.ts +3019 -0
  823. package/src/explicit-cue-recall.ts +5545 -0
  824. package/src/extraction-judge-telemetry.ts +234 -0
  825. package/src/extraction-judge-training.ts +221 -0
  826. package/src/extraction-judge.ts +846 -0
  827. package/src/extraction-timeout.test.ts +265 -0
  828. package/src/extraction.ts +2719 -0
  829. package/src/fallback-llm.test.ts +1060 -0
  830. package/src/fallback-llm.ts +918 -0
  831. package/src/focused-list-recall.test.ts +734 -0
  832. package/src/focused-list-recall.ts +1160 -0
  833. package/src/graph-dashboard-diff.ts +35 -0
  834. package/src/graph-dashboard-key.ts +5 -0
  835. package/src/graph-dashboard-parser.ts +104 -0
  836. package/src/graph-edge-reinforcement.ts +192 -0
  837. package/src/graph-events.ts +151 -0
  838. package/src/graph-recall.test.ts +164 -0
  839. package/src/graph-recall.ts +189 -0
  840. package/src/graph-retrieval.test.ts +809 -0
  841. package/src/graph-retrieval.ts +823 -0
  842. package/src/graph-snapshot.ts +329 -0
  843. package/src/graph.ts +813 -0
  844. package/src/harmonic-retrieval.ts +223 -0
  845. package/src/himem.ts +154 -0
  846. package/src/hygiene.ts +87 -0
  847. package/src/identity-continuity.ts +333 -0
  848. package/src/importance.ts +328 -0
  849. package/src/importers/base.test.ts +294 -0
  850. package/src/importers/base.ts +436 -0
  851. package/src/importers/index.ts +21 -0
  852. package/src/index.ts +1204 -0
  853. package/src/intent.ts +154 -0
  854. package/src/json-extract.ts +85 -0
  855. package/src/json-store.ts +42 -0
  856. package/src/lcm/archive.ts +617 -0
  857. package/src/lcm/dag.ts +199 -0
  858. package/src/lcm/engine.ts +645 -0
  859. package/src/lcm/index.ts +7 -0
  860. package/src/lcm/queue.test.ts +178 -0
  861. package/src/lcm/queue.ts +200 -0
  862. package/src/lcm/recall.ts +117 -0
  863. package/src/lcm/schema.ts +154 -0
  864. package/src/lcm/summarizer.ts +235 -0
  865. package/src/lcm/tools.ts +191 -0
  866. package/src/lcm-engine.test.ts +660 -0
  867. package/src/legacy-hook-compat.test.ts +20 -0
  868. package/src/legacy-hook-compat.ts +45 -0
  869. package/src/lifecycle.ts +289 -0
  870. package/src/live-connectors-runner.ts +385 -0
  871. package/src/local-llm-qos.test.ts +303 -0
  872. package/src/local-llm-thinking.test.ts +292 -0
  873. package/src/local-llm.ts +1464 -0
  874. package/src/logger.ts +49 -0
  875. package/src/maintenance/archive-observations.ts +147 -0
  876. package/src/maintenance/backup-stamp.ts +3 -0
  877. package/src/maintenance/dreams-ledger.ts +516 -0
  878. package/src/maintenance/first-start-migration.ts +362 -0
  879. package/src/maintenance/forget.test.ts +206 -0
  880. package/src/maintenance/forget.ts +126 -0
  881. package/src/maintenance/graph-edge-decay.test.ts +409 -0
  882. package/src/maintenance/graph-edge-decay.ts +394 -0
  883. package/src/maintenance/memory-governance-cron.ts +447 -0
  884. package/src/maintenance/memory-governance.ts +1039 -0
  885. package/src/maintenance/migrate-observations.ts +216 -0
  886. package/src/maintenance/observation-ledger-utils.ts +54 -0
  887. package/src/maintenance/pattern-reinforcement.test.ts +875 -0
  888. package/src/maintenance/pattern-reinforcement.ts +369 -0
  889. package/src/maintenance/purge.ts +334 -0
  890. package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
  891. package/src/maintenance/rebuild-memory-projection.ts +1234 -0
  892. package/src/maintenance/rebuild-observations.ts +178 -0
  893. package/src/maintenance/tier-stats.test.ts +378 -0
  894. package/src/maintenance/tier-stats.ts +222 -0
  895. package/src/mcp-memory-inspector-app.ts +421 -0
  896. package/src/memory-action-policy.ts +80 -0
  897. package/src/memory-cache.ts +208 -0
  898. package/src/memory-extension/claude-code-publisher.ts +51 -0
  899. package/src/memory-extension/codex-publisher.ts +149 -0
  900. package/src/memory-extension/hermes-publisher.ts +51 -0
  901. package/src/memory-extension/index.ts +100 -0
  902. package/src/memory-extension/shared-instructions.ts +133 -0
  903. package/src/memory-extension/types.ts +86 -0
  904. package/src/memory-extension-host/host-discovery.ts +276 -0
  905. package/src/memory-extension-host/index.ts +14 -0
  906. package/src/memory-extension-host/render-extensions-block.ts +73 -0
  907. package/src/memory-extension-host/types.ts +21 -0
  908. package/src/memory-lifecycle-ledger-utils.ts +116 -0
  909. package/src/memory-projection-format.ts +11 -0
  910. package/src/memory-projection-store.ts +951 -0
  911. package/src/memory-provenance.test.ts +196 -0
  912. package/src/memory-provenance.ts +484 -0
  913. package/src/memory-worth-bench.test.ts +71 -0
  914. package/src/memory-worth-bench.ts +265 -0
  915. package/src/memory-worth-filter.test.ts +209 -0
  916. package/src/memory-worth-filter.ts +204 -0
  917. package/src/memory-worth-frontmatter.test.ts +311 -0
  918. package/src/memory-worth-outcomes.test.ts +316 -0
  919. package/src/memory-worth-outcomes.ts +286 -0
  920. package/src/memory-worth.test.ts +317 -0
  921. package/src/memory-worth.ts +215 -0
  922. package/src/message-parts/index.ts +806 -0
  923. package/src/message-parts/message-parts.test.ts +421 -0
  924. package/src/migrate/from-engram.ts +789 -0
  925. package/src/model-registry.ts +313 -0
  926. package/src/models-json.ts +76 -0
  927. package/src/namespaces/migrate.ts +187 -0
  928. package/src/namespaces/path.ts +25 -0
  929. package/src/namespaces/principal.test.ts +195 -0
  930. package/src/namespaces/principal.ts +86 -0
  931. package/src/namespaces/search.test.ts +105 -0
  932. package/src/namespaces/search.ts +233 -0
  933. package/src/namespaces/storage.ts +74 -0
  934. package/src/native-knowledge.ts +1823 -0
  935. package/src/negative.ts +72 -0
  936. package/src/network/tailscale.ts +179 -0
  937. package/src/network/webdav.ts +385 -0
  938. package/src/objective-state-writers.ts +951 -0
  939. package/src/objective-state.ts +320 -0
  940. package/src/onboarding/index.ts +529 -0
  941. package/src/openai-chat-compat.ts +56 -0
  942. package/src/operator-toolkit.ts +2132 -0
  943. package/src/opik-exporter.test.ts +72 -0
  944. package/src/opik-exporter.ts +587 -0
  945. package/src/orchestrator-extraction-queue.test.ts +197 -0
  946. package/src/orchestrator-flush.test.ts +1171 -0
  947. package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
  948. package/src/orchestrator-source-attribution.test.ts +701 -0
  949. package/src/orchestrator.ts +16368 -0
  950. package/src/page-versioning.ts +450 -0
  951. package/src/patterns-cli.ts +574 -0
  952. package/src/peers/index.ts +54 -0
  953. package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
  954. package/src/peers/migrate-from-identity-anchor.ts +350 -0
  955. package/src/peers/peers.test.ts +419 -0
  956. package/src/peers/profile-reasoner.ts +694 -0
  957. package/src/peers/storage.ts +1350 -0
  958. package/src/peers/types.ts +138 -0
  959. package/src/plugin-id.ts +84 -0
  960. package/src/policy-runtime.ts +209 -0
  961. package/src/procedural/procedure-miner.ts +150 -0
  962. package/src/procedural/procedure-recall.ts +93 -0
  963. package/src/procedural/procedure-stats.ts +213 -0
  964. package/src/procedural/procedure-types.ts +132 -0
  965. package/src/procedural/reinforcement-core.test.ts +132 -0
  966. package/src/procedural/reinforcement-core.ts +73 -0
  967. package/src/profiling.test.ts +263 -0
  968. package/src/profiling.ts +435 -0
  969. package/src/projection/index.ts +398 -0
  970. package/src/qmd-recall-cache.test.ts +138 -0
  971. package/src/qmd-recall-cache.ts +111 -0
  972. package/src/qmd.test.ts +257 -0
  973. package/src/qmd.ts +2614 -0
  974. package/src/reasoning-trace-recall.ts +201 -0
  975. package/src/reasoning-trace-types.ts +235 -0
  976. package/src/recall-audit-anomaly.test.ts +246 -0
  977. package/src/recall-audit-anomaly.ts +297 -0
  978. package/src/recall-audit.test.ts +51 -0
  979. package/src/recall-audit.ts +72 -0
  980. package/src/recall-budget-config.test.ts +87 -0
  981. package/src/recall-disclosure-escalation.test.ts +196 -0
  982. package/src/recall-disclosure-escalation.ts +158 -0
  983. package/src/recall-disclosure-shaping.test.ts +146 -0
  984. package/src/recall-disclosure.test.ts +214 -0
  985. package/src/recall-explain-renderer.test.ts +140 -0
  986. package/src/recall-explain-renderer.ts +356 -0
  987. package/src/recall-mmr.test.ts +808 -0
  988. package/src/recall-mmr.ts +607 -0
  989. package/src/recall-qos.test.ts +85 -0
  990. package/src/recall-qos.ts +82 -0
  991. package/src/recall-query-policy.ts +221 -0
  992. package/src/recall-state.test.ts +233 -0
  993. package/src/recall-state.ts +456 -0
  994. package/src/recall-tag-filter.ts +143 -0
  995. package/src/recall-tokenization.ts +35 -0
  996. package/src/recall-xray-cli.test.ts +118 -0
  997. package/src/recall-xray-cli.ts +100 -0
  998. package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
  999. package/src/recall-xray-renderer.test.ts +539 -0
  1000. package/src/recall-xray-renderer.ts +487 -0
  1001. package/src/recall-xray.test.ts +503 -0
  1002. package/src/recall-xray.ts +621 -0
  1003. package/src/reconstruct.ts +41 -0
  1004. package/src/release-changelog.ts +35 -0
  1005. package/src/relevance.ts +67 -0
  1006. package/src/replay/normalizers/chatgpt.ts +133 -0
  1007. package/src/replay/normalizers/claude.ts +102 -0
  1008. package/src/replay/normalizers/openclaw.ts +119 -0
  1009. package/src/replay/normalizers/shared.ts +69 -0
  1010. package/src/replay/runner.ts +197 -0
  1011. package/src/replay/types.ts +143 -0
  1012. package/src/rerank.test.ts +48 -0
  1013. package/src/rerank.ts +176 -0
  1014. package/src/resolve-auth-token.test.ts +226 -0
  1015. package/src/resolve-auth-token.ts +151 -0
  1016. package/src/resolve-provider-secret.test.ts +187 -0
  1017. package/src/resolve-provider-secret.ts +410 -0
  1018. package/src/response-guidance-recall.test.ts +3952 -0
  1019. package/src/response-guidance-recall.ts +4431 -0
  1020. package/src/resume-bundles.ts +415 -0
  1021. package/src/retrieval-agents.ts +623 -0
  1022. package/src/retrieval-tiers.ts +25 -0
  1023. package/src/retrieval.ts +104 -0
  1024. package/src/review/index.test.ts +201 -0
  1025. package/src/review/index.ts +536 -0
  1026. package/src/routing/engine.ts +162 -0
  1027. package/src/routing/store.ts +321 -0
  1028. package/src/runtime/better-sqlite.test.ts +32 -0
  1029. package/src/runtime/better-sqlite.ts +76 -0
  1030. package/src/runtime/child-process.ts +67 -0
  1031. package/src/runtime/env.ts +48 -0
  1032. package/src/sanitize.ts +58 -0
  1033. package/src/schemas.ts +449 -0
  1034. package/src/sdk-compat.ts +87 -0
  1035. package/src/search/document-scanner.ts +96 -0
  1036. package/src/search/embed-helper.ts +142 -0
  1037. package/src/search/factory.ts +189 -0
  1038. package/src/search/index.ts +10 -0
  1039. package/src/search/lancedb-backend.ts +342 -0
  1040. package/src/search/meilisearch-backend.ts +232 -0
  1041. package/src/search/noop-backend.ts +57 -0
  1042. package/src/search/orama-backend.ts +358 -0
  1043. package/src/search/port.ts +86 -0
  1044. package/src/search/remote-backend.ts +124 -0
  1045. package/src/secure-store/cipher.ts +271 -0
  1046. package/src/secure-store/cli-handlers.ts +355 -0
  1047. package/src/secure-store/cli-renderer.ts +131 -0
  1048. package/src/secure-store/header.ts +373 -0
  1049. package/src/secure-store/index.ts +137 -0
  1050. package/src/secure-store/kdf.ts +263 -0
  1051. package/src/secure-store/keyring.ts +106 -0
  1052. package/src/secure-store/metadata.ts +394 -0
  1053. package/src/secure-store/passphrase-reader.ts +252 -0
  1054. package/src/secure-store/secure-fs.ts +571 -0
  1055. package/src/secure-store/secure-store.test.ts +755 -0
  1056. package/src/semantic-chunking.ts +545 -0
  1057. package/src/semantic-consolidation.test.ts +182 -0
  1058. package/src/semantic-consolidation.ts +432 -0
  1059. package/src/semantic-rule-promotion.ts +183 -0
  1060. package/src/semantic-rule-verifier.ts +160 -0
  1061. package/src/session-integrity.ts +569 -0
  1062. package/src/session-observer-bands.ts +11 -0
  1063. package/src/session-observer-state.ts +346 -0
  1064. package/src/session-toggles.test.ts +96 -0
  1065. package/src/session-toggles.ts +159 -0
  1066. package/src/shared-context/manager.ts +810 -0
  1067. package/src/signal.ts +84 -0
  1068. package/src/skills-registry.test.ts +277 -0
  1069. package/src/skills-registry.ts +120 -0
  1070. package/src/source-attribution-roundtrip.test.ts +215 -0
  1071. package/src/source-attribution.test.ts +1425 -0
  1072. package/src/source-attribution.ts +639 -0
  1073. package/src/spaces/index.ts +627 -0
  1074. package/src/storage-paths.ts +117 -0
  1075. package/src/storage.ts +6657 -0
  1076. package/src/store-contract.ts +55 -0
  1077. package/src/summarizer.ts +844 -0
  1078. package/src/summary-snapshot.test.ts +681 -0
  1079. package/src/summary-snapshot.ts +238 -0
  1080. package/src/surfaces/dreams.test.ts +394 -0
  1081. package/src/surfaces/dreams.ts +346 -0
  1082. package/src/surfaces/heartbeat.test.ts +415 -0
  1083. package/src/surfaces/heartbeat.ts +325 -0
  1084. package/src/sync/index.ts +308 -0
  1085. package/src/targeted-fact-recall.test.ts +1694 -0
  1086. package/src/targeted-fact-recall.ts +2905 -0
  1087. package/src/taxonomy/default-taxonomy.ts +87 -0
  1088. package/src/taxonomy/index.ts +26 -0
  1089. package/src/taxonomy/resolver-doc-generator.ts +57 -0
  1090. package/src/taxonomy/resolver.ts +184 -0
  1091. package/src/taxonomy/taxonomy-loader.ts +186 -0
  1092. package/src/taxonomy/types.ts +48 -0
  1093. package/src/telemetry-transcript.ts +70 -0
  1094. package/src/temporal-index.ts +890 -0
  1095. package/src/temporal-supersession.test.ts +2703 -0
  1096. package/src/temporal-supersession.ts +493 -0
  1097. package/src/temporal-validity.test.ts +448 -0
  1098. package/src/temporal-validity.ts +123 -0
  1099. package/src/threading.ts +395 -0
  1100. package/src/tier-migration.ts +124 -0
  1101. package/src/tier-routing.ts +102 -0
  1102. package/src/tmt.ts +462 -0
  1103. package/src/tokens.test.ts +178 -0
  1104. package/src/tokens.ts +279 -0
  1105. package/src/topics.ts +147 -0
  1106. package/src/training-export/cli-date-validation.test.ts +258 -0
  1107. package/src/training-export/converter.test.ts +452 -0
  1108. package/src/training-export/converter.ts +319 -0
  1109. package/src/training-export/date-parse.ts +117 -0
  1110. package/src/training-export/index.ts +26 -0
  1111. package/src/training-export/registry.test.ts +85 -0
  1112. package/src/training-export/registry.ts +57 -0
  1113. package/src/training-export/types.ts +31 -0
  1114. package/src/transcript.ts +1179 -0
  1115. package/src/transfer/autodetect.ts +30 -0
  1116. package/src/transfer/backup.ts +138 -0
  1117. package/src/transfer/capsule-crypto.ts +485 -0
  1118. package/src/transfer/capsule-encrypt.test.ts +690 -0
  1119. package/src/transfer/capsule-export.ts +543 -0
  1120. package/src/transfer/capsule-fork.ts +375 -0
  1121. package/src/transfer/capsule-import.ts +564 -0
  1122. package/src/transfer/capsule-merge.ts +433 -0
  1123. package/src/transfer/conflict-policy.ts +16 -0
  1124. package/src/transfer/constants.ts +13 -0
  1125. package/src/transfer/exclusions.ts +37 -0
  1126. package/src/transfer/export-json.ts +65 -0
  1127. package/src/transfer/export-md.ts +59 -0
  1128. package/src/transfer/export-sqlite.ts +52 -0
  1129. package/src/transfer/fs-utils.ts +269 -0
  1130. package/src/transfer/import-json.ts +108 -0
  1131. package/src/transfer/import-md.ts +84 -0
  1132. package/src/transfer/import-sqlite.ts +100 -0
  1133. package/src/transfer/integrity.ts +71 -0
  1134. package/src/transfer/sqlite-schema.ts +16 -0
  1135. package/src/transfer/types.ts +297 -0
  1136. package/src/trust-zones.ts +1186 -0
  1137. package/src/types.ts +3074 -0
  1138. package/src/user-model.test.ts +124 -0
  1139. package/src/user-model.ts +162 -0
  1140. package/src/utility-learner.ts +353 -0
  1141. package/src/utility-runtime.ts +88 -0
  1142. package/src/utility-telemetry.ts +215 -0
  1143. package/src/utils/category-dir.ts +44 -0
  1144. package/src/utils/errno.ts +6 -0
  1145. package/src/utils/iso-timestamp.test.ts +37 -0
  1146. package/src/utils/iso-timestamp.ts +164 -0
  1147. package/src/utils/path.ts +26 -0
  1148. package/src/verified-recall.ts +138 -0
  1149. package/src/version-utils.test.ts +10 -0
  1150. package/src/version-utils.ts +9 -0
  1151. package/src/whitespace.ts +10 -0
  1152. package/src/work/board.ts +359 -0
  1153. package/src/work/boundary.ts +107 -0
  1154. package/src/work/storage.ts +436 -0
  1155. package/src/work/types.ts +82 -0
  1156. package/src/work-product-ledger.ts +265 -0
  1157. package/dist/access-service-DDjzFALq.d.ts +0 -2088
  1158. package/dist/capsule-crypto-SJS5VVAP.js +0 -18
  1159. package/dist/capsule-export-7QNCBZOQ.js +0 -17
  1160. package/dist/capsule-import-EPBHD2EN.js +0 -16
  1161. package/dist/capsule-merge-DI7PNQ2H.js +0 -189
  1162. package/dist/chunk-23ZZK64Y.js +0 -26
  1163. package/dist/chunk-23ZZK64Y.js.map +0 -1
  1164. package/dist/chunk-242S3I2A.js +0 -647
  1165. package/dist/chunk-2LGMW3DJ.js +0 -111
  1166. package/dist/chunk-3B6KIRBH.js +0 -5213
  1167. package/dist/chunk-3B6KIRBH.js.map +0 -1
  1168. package/dist/chunk-457A4P3L.js +0 -119
  1169. package/dist/chunk-457A4P3L.js.map +0 -1
  1170. package/dist/chunk-4IS4SXIQ.js +0 -2040
  1171. package/dist/chunk-4YM32CRU.js +0 -721
  1172. package/dist/chunk-6TBWYBJ3.js +0 -236
  1173. package/dist/chunk-74EMIVE4.js +0 -329
  1174. package/dist/chunk-74EMIVE4.js.map +0 -1
  1175. package/dist/chunk-767ODGE6.js +0 -183
  1176. package/dist/chunk-7V22HTMD.js +0 -623
  1177. package/dist/chunk-7V22HTMD.js.map +0 -1
  1178. package/dist/chunk-7ZM3BFKK.js +0 -9705
  1179. package/dist/chunk-7ZM3BFKK.js.map +0 -1
  1180. package/dist/chunk-AQJNPMOA.js +0 -643
  1181. package/dist/chunk-AQJNPMOA.js.map +0 -1
  1182. package/dist/chunk-ASAITVLA.js +0 -64
  1183. package/dist/chunk-ASAITVLA.js.map +0 -1
  1184. package/dist/chunk-BBE34QBJ.js +0 -275
  1185. package/dist/chunk-BBE34QBJ.js.map +0 -1
  1186. package/dist/chunk-BZSQEPRW.js +0 -14710
  1187. package/dist/chunk-BZSQEPRW.js.map +0 -1
  1188. package/dist/chunk-CPKTBRS2.js +0 -891
  1189. package/dist/chunk-CPKTBRS2.js.map +0 -1
  1190. package/dist/chunk-D4GAOFF6.js +0 -562
  1191. package/dist/chunk-D4GAOFF6.js.map +0 -1
  1192. package/dist/chunk-D54LZC5L.js +0 -147
  1193. package/dist/chunk-DF3RVK3X.js +0 -119
  1194. package/dist/chunk-DF3RVK3X.js.map +0 -1
  1195. package/dist/chunk-DZZPC36E.js +0 -1451
  1196. package/dist/chunk-DZZPC36E.js.map +0 -1
  1197. package/dist/chunk-E2UCDP5S.js +0 -570
  1198. package/dist/chunk-E6K4NIEU.js +0 -747
  1199. package/dist/chunk-E6K4NIEU.js.map +0 -1
  1200. package/dist/chunk-EEQLFRUM.js +0 -89
  1201. package/dist/chunk-ETOW6ACV.js +0 -158
  1202. package/dist/chunk-ETOW6ACV.js.map +0 -1
  1203. package/dist/chunk-FMEBPEAO.js +0 -347
  1204. package/dist/chunk-FMEBPEAO.js.map +0 -1
  1205. package/dist/chunk-FQDPCE3I.js +0 -1837
  1206. package/dist/chunk-FQDPCE3I.js.map +0 -1
  1207. package/dist/chunk-FYIYMQ5N.js +0 -221
  1208. package/dist/chunk-FYIYMQ5N.js.map +0 -1
  1209. package/dist/chunk-G2WADRQ3.js +0 -219
  1210. package/dist/chunk-G4SK7DSQ.js +0 -121
  1211. package/dist/chunk-GVPWB7EY.js +0 -390
  1212. package/dist/chunk-GVPWB7EY.js.map +0 -1
  1213. package/dist/chunk-HELQZFZO.js +0 -1075
  1214. package/dist/chunk-HL5LRPNA.js +0 -1914
  1215. package/dist/chunk-HL5LRPNA.js.map +0 -1
  1216. package/dist/chunk-HQZVVSVB.js +0 -147
  1217. package/dist/chunk-HQZVVSVB.js.map +0 -1
  1218. package/dist/chunk-HY3L4WKC.js +0 -2195
  1219. package/dist/chunk-HY3L4WKC.js.map +0 -1
  1220. package/dist/chunk-IB3BFHGN.js +0 -228
  1221. package/dist/chunk-IXEJRKCZ.js +0 -18
  1222. package/dist/chunk-JBMSGZEQ.js +0 -441
  1223. package/dist/chunk-JBMSGZEQ.js.map +0 -1
  1224. package/dist/chunk-JESOB2HO.js +0 -108
  1225. package/dist/chunk-JKDVIE52.js +0 -272
  1226. package/dist/chunk-JRNQ3RNA.js +0 -284
  1227. package/dist/chunk-JRNQ3RNA.js.map +0 -1
  1228. package/dist/chunk-K6WK37A6.js +0 -865
  1229. package/dist/chunk-K6WK37A6.js.map +0 -1
  1230. package/dist/chunk-MARWOCVP.js +0 -48
  1231. package/dist/chunk-MNU6ZBWT.js +0 -4454
  1232. package/dist/chunk-MNU6ZBWT.js.map +0 -1
  1233. package/dist/chunk-N5AKDXAI.js +0 -74
  1234. package/dist/chunk-OA3L7BFR.js +0 -183
  1235. package/dist/chunk-OA3L7BFR.js.map +0 -1
  1236. package/dist/chunk-OR64ZGRZ.js +0 -23
  1237. package/dist/chunk-P77UEOU2.js +0 -1521
  1238. package/dist/chunk-P77UEOU2.js.map +0 -1
  1239. package/dist/chunk-PH4C2U43.js +0 -239
  1240. package/dist/chunk-PH4C2U43.js.map +0 -1
  1241. package/dist/chunk-RVPLBATS.js +0 -1586
  1242. package/dist/chunk-RVPLBATS.js.map +0 -1
  1243. package/dist/chunk-U5JMRGKX.js +0 -340
  1244. package/dist/chunk-U5JMRGKX.js.map +0 -1
  1245. package/dist/chunk-URB2WSKZ.js +0 -350
  1246. package/dist/chunk-URB2WSKZ.js.map +0 -1
  1247. package/dist/chunk-UVMUAWVT.js +0 -596
  1248. package/dist/chunk-WEJG4TB5.js +0 -118
  1249. package/dist/chunk-X7HPGUVG.js +0 -271
  1250. package/dist/chunk-XAMBKFQS.js +0 -2777
  1251. package/dist/chunk-XAMBKFQS.js.map +0 -1
  1252. package/dist/chunk-XJKFSSDW.js +0 -726
  1253. package/dist/chunk-XJKFSSDW.js.map +0 -1
  1254. package/dist/chunk-XMHBH5H6.js +0 -283
  1255. package/dist/chunk-XMHBH5H6.js.map +0 -1
  1256. package/dist/chunk-XMVFHBHT.js +0 -277
  1257. package/dist/chunk-Y3VMVTYX.js +0 -53
  1258. package/dist/chunk-YNB73F22.js +0 -137
  1259. package/dist/chunk-YNB73F22.js.map +0 -1
  1260. package/dist/chunk-Z2E7VW55.js +0 -335
  1261. package/dist/chunk-Z2E7VW55.js.map +0 -1
  1262. package/dist/chunk-ZG7PTKBK.js +0 -2296
  1263. package/dist/chunk-ZNQN6ZTA.js +0 -135
  1264. package/dist/chunk-ZVTKDVVM.js +0 -827
  1265. package/dist/chunk-ZVTKDVVM.js.map +0 -1
  1266. package/dist/cli-BR8KpIU0.d.ts +0 -1259
  1267. package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
  1268. package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
  1269. package/dist/contradiction-review-5LTTVDQV.js +0 -22
  1270. package/dist/contradiction-scan-QTXAMBUA.js +0 -414
  1271. package/dist/contradiction-scan-QTXAMBUA.js.map +0 -1
  1272. package/dist/engine-35M5BKQ7.js +0 -28
  1273. package/dist/fs-utils-IRVUFB6G.js +0 -30
  1274. package/dist/graph-edge-decay-PWB63GRE.js +0 -207
  1275. package/dist/memory-governance-IMPQZXFC.js +0 -37
  1276. package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
  1277. package/dist/orchestrator-DDMPqU6R.d.ts +0 -1792
  1278. package/dist/path-RMTY5Y5A.js +0 -9
  1279. package/dist/port-B6VEDIkC.d.ts +0 -53
  1280. package/dist/resolution-YGIBORXI.js +0 -101
  1281. package/dist/resolution-YGIBORXI.js.map +0 -1
  1282. package/dist/secure-store-4R2GSO7S.js +0 -156
  1283. package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
  1284. package/dist/state-store-3EH7HYIN.js +0 -16
  1285. package/dist/types-V3FJ26TF.js +0 -30
  1286. /package/dist/{capsule-crypto-SJS5VVAP.js.map → adapters/claude-code.js.map} +0 -0
  1287. /package/dist/{capsule-export-7QNCBZOQ.js.map → adapters/codex.js.map} +0 -0
  1288. /package/dist/{capsule-import-EPBHD2EN.js.map → adapters/hermes.js.map} +0 -0
  1289. /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/index.js.map} +0 -0
  1290. /package/dist/{engine-35M5BKQ7.js.map → adapters/registry.js.map} +0 -0
  1291. /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/replit.js.map} +0 -0
  1292. /package/dist/{memory-governance-IMPQZXFC.js.map → adapters/types.js.map} +0 -0
  1293. /package/dist/{path-RMTY5Y5A.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
  1294. /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
  1295. /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
  1296. /package/dist/{chunk-X7HPGUVG.js.map → chunk-4CRG46BG.js.map} +0 -0
  1297. /package/dist/{chunk-UVMUAWVT.js.map → chunk-7IASACLB.js.map} +0 -0
  1298. /package/dist/{chunk-HELQZFZO.js.map → chunk-EDTHC6UD.js.map} +0 -0
  1299. /package/dist/{chunk-4YM32CRU.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
  1300. /package/dist/{chunk-E2UCDP5S.js.map → chunk-FBYESMQ2.js.map} +0 -0
  1301. /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
  1302. /package/dist/{chunk-IB3BFHGN.js.map → chunk-GGKRUQOO.js.map} +0 -0
  1303. /package/dist/{chunk-242S3I2A.js.map → chunk-GL6I6MEQ.js.map} +0 -0
  1304. /package/dist/{secure-store-4R2GSO7S.js.map → chunk-HHLLAQGZ.js.map} +0 -0
  1305. /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
  1306. /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
  1307. /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
  1308. /package/dist/{chunk-WEJG4TB5.js.map → chunk-MC26UJIM.js.map} +0 -0
  1309. /package/dist/{chunk-JKDVIE52.js.map → chunk-MGKYQQYF.js.map} +0 -0
  1310. /package/dist/{chunk-Y3VMVTYX.js.map → chunk-MT4HVDUZ.js.map} +0 -0
  1311. /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
  1312. /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
  1313. /package/dist/{chunk-JESOB2HO.js.map → chunk-P4NEIHUT.js.map} +0 -0
  1314. /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
  1315. /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
  1316. /package/dist/{state-store-3EH7HYIN.js.map → chunk-SEDEKFYQ.js.map} +0 -0
  1317. /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
  1318. /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
  1319. /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
  1320. /package/dist/{types-V3FJ26TF.js.map → chunk-V5OCT34X.js.map} +0 -0
  1321. /package/dist/{chunk-ZG7PTKBK.js.map → chunk-W3LR522O.js.map} +0 -0
  1322. /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
  1323. /package/dist/{chunk-ZNQN6ZTA.js.map → chunk-XVZ7B3HG.js.map} +0 -0
  1324. /package/dist/{graph-edge-decay-PWB63GRE.js.map → graph-edge-decay-5DI5GUNL.js.map} +0 -0
@@ -0,0 +1,1577 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import test from "node:test";
6
+
7
+ import { parseConfig } from "../config.js";
8
+ import { chunkContent, type ChunkingConfig } from "../chunking.js";
9
+ import { EmbeddingFallback, EmbeddingTimeoutError } from "../embedding-fallback.js";
10
+ import { ContentHashIndex, StorageManager } from "../storage.js";
11
+ import {
12
+ decideSemanticDedup,
13
+ type SemanticDedupDecision,
14
+ type SemanticDedupHit,
15
+ type SemanticDedupLookup,
16
+ type SemanticDedupOptions,
17
+ } from "./semantic.js";
18
+
19
+ // ── Helpers ───────────────────────────────────────────────────────────────────
20
+
21
+ function makeLookup(hits: SemanticDedupHit[]): SemanticDedupLookup {
22
+ return async () => hits;
23
+ }
24
+
25
+ const DEFAULT_OPTS: SemanticDedupOptions = {
26
+ enabled: true,
27
+ threshold: 0.92,
28
+ candidates: 5,
29
+ };
30
+
31
+ // ── decideSemanticDedup ───────────────────────────────────────────────────────
32
+
33
+ test("semantic dedup: returns keep/disabled when enabled flag is false", async () => {
34
+ const decision = await decideSemanticDedup(
35
+ "hello world",
36
+ makeLookup([{ id: "m1", score: 0.99 }]),
37
+ { ...DEFAULT_OPTS, enabled: false },
38
+ );
39
+ assert.equal(decision.action, "keep");
40
+ assert.equal(decision.reason, "disabled");
41
+ });
42
+
43
+ test("semantic dedup: keeps content when lookup returns no hits (empty index → no_candidates)", async () => {
44
+ const decision = await decideSemanticDedup(
45
+ "some novel statement",
46
+ makeLookup([]),
47
+ DEFAULT_OPTS,
48
+ );
49
+ assert.equal(decision.action, "keep");
50
+ // Provider is available but returned no hits: empty index, not backend failure.
51
+ assert.equal(decision.reason, "no_candidates");
52
+ });
53
+
54
+ test("semantic dedup: keeps content when top score is below threshold", async () => {
55
+ const decision = await decideSemanticDedup(
56
+ "the user prefers tabs over spaces",
57
+ makeLookup([
58
+ { id: "m1", score: 0.82 },
59
+ { id: "m2", score: 0.74 },
60
+ ]),
61
+ DEFAULT_OPTS,
62
+ );
63
+ assert.equal(decision.action, "keep");
64
+ assert.equal(decision.reason, "no_near_duplicate");
65
+ if (decision.action === "keep") {
66
+ assert.equal(decision.topId, "m1");
67
+ assert.equal(decision.topScore, 0.82);
68
+ }
69
+ });
70
+
71
+ test("semantic dedup: skips content when top score meets threshold exactly", async () => {
72
+ const decision = await decideSemanticDedup(
73
+ "the user prefers tabs",
74
+ makeLookup([{ id: "m1", score: 0.92 }]),
75
+ DEFAULT_OPTS,
76
+ );
77
+ assert.equal(decision.action, "skip");
78
+ if (decision.action === "skip") {
79
+ assert.equal(decision.reason, "near_duplicate");
80
+ assert.equal(decision.topId, "m1");
81
+ assert.equal(decision.topScore, 0.92);
82
+ }
83
+ });
84
+
85
+ test("semantic dedup: skips content when top score exceeds threshold", async () => {
86
+ // Simulates a paraphrase that collides with an existing memory.
87
+ const decision = await decideSemanticDedup(
88
+ "tabs are preferred by the user for indentation",
89
+ makeLookup([
90
+ { id: "existing-pref-42", score: 0.96, path: "/tmp/pref.md" },
91
+ { id: "existing-pref-43", score: 0.81 },
92
+ ]),
93
+ DEFAULT_OPTS,
94
+ );
95
+ assert.equal(decision.action, "skip");
96
+ if (decision.action === "skip") {
97
+ assert.equal(decision.topId, "existing-pref-42");
98
+ assert.equal(decision.topPath, "/tmp/pref.md");
99
+ assert.ok(decision.topScore >= 0.92);
100
+ }
101
+ });
102
+
103
+ test("semantic dedup: picks highest-scoring hit even if unsorted", async () => {
104
+ const decision = await decideSemanticDedup(
105
+ "anything",
106
+ makeLookup([
107
+ { id: "m1", score: 0.5 },
108
+ { id: "m2", score: 0.97 },
109
+ { id: "m3", score: 0.6 },
110
+ ]),
111
+ DEFAULT_OPTS,
112
+ );
113
+ assert.equal(decision.action, "skip");
114
+ if (decision.action === "skip") {
115
+ assert.equal(decision.topId, "m2");
116
+ assert.equal(decision.topScore, 0.97);
117
+ }
118
+ });
119
+
120
+ test("semantic dedup: ignores non-finite scores", async () => {
121
+ const decision = await decideSemanticDedup(
122
+ "content",
123
+ makeLookup([
124
+ { id: "m1", score: Number.NaN },
125
+ { id: "m2", score: Number.POSITIVE_INFINITY },
126
+ { id: "m3", score: 0.5 },
127
+ ]),
128
+ DEFAULT_OPTS,
129
+ );
130
+ assert.equal(decision.action, "keep");
131
+ assert.equal(decision.reason, "no_near_duplicate");
132
+ if (decision.action === "keep") {
133
+ assert.equal(decision.topId, "m3");
134
+ }
135
+ });
136
+
137
+ test("semantic dedup: treats lookup throw as fail-open keep", async () => {
138
+ const decision = await decideSemanticDedup(
139
+ "content",
140
+ async () => {
141
+ throw new Error("network down");
142
+ },
143
+ DEFAULT_OPTS,
144
+ );
145
+ assert.equal(decision.action, "keep");
146
+ assert.equal(decision.reason, "backend_unavailable");
147
+ });
148
+
149
+ test("semantic dedup: empty/whitespace content never triggers lookup", async () => {
150
+ let called = 0;
151
+ const decision = await decideSemanticDedup(
152
+ " \n ",
153
+ async () => {
154
+ called++;
155
+ return [{ id: "m1", score: 0.99 }];
156
+ },
157
+ DEFAULT_OPTS,
158
+ );
159
+ assert.equal(called, 0);
160
+ assert.equal(decision.action, "keep");
161
+ });
162
+
163
+ test("semantic dedup: candidates option is forwarded to lookup", async () => {
164
+ let limitSeen = -1;
165
+ await decideSemanticDedup(
166
+ "anything",
167
+ async (_content, limit) => {
168
+ limitSeen = limit;
169
+ return [];
170
+ },
171
+ { ...DEFAULT_OPTS, candidates: 11 },
172
+ );
173
+ assert.equal(limitSeen, 11);
174
+ });
175
+
176
+ test("semantic dedup: candidates=0 short-circuits without calling lookup", async () => {
177
+ let called = 0;
178
+ const decision = await decideSemanticDedup(
179
+ "anything",
180
+ async () => {
181
+ called++;
182
+ return [];
183
+ },
184
+ { ...DEFAULT_OPTS, candidates: 0 },
185
+ );
186
+ assert.equal(called, 0, "lookup must not be called when candidates=0");
187
+ assert.equal(decision.action, "keep");
188
+ assert.equal(decision.reason, "disabled");
189
+ });
190
+
191
+ // ── Config flag parsing ───────────────────────────────────────────────────────
192
+
193
+ test("parseConfig: semantic dedup flags default to enabled/0.92/5", () => {
194
+ const config = parseConfig({});
195
+ assert.equal(config.semanticDedupEnabled, true);
196
+ assert.equal(config.semanticDedupThreshold, 0.92);
197
+ assert.equal(config.semanticDedupCandidates, 5);
198
+ });
199
+
200
+ test("parseConfig: semantic dedup flags respect explicit settings", () => {
201
+ const config = parseConfig({
202
+ semanticDedupEnabled: false,
203
+ semanticDedupThreshold: 0.88,
204
+ semanticDedupCandidates: 10,
205
+ });
206
+ assert.equal(config.semanticDedupEnabled, false);
207
+ assert.equal(config.semanticDedupThreshold, 0.88);
208
+ assert.equal(config.semanticDedupCandidates, 10);
209
+ });
210
+
211
+ test("parseConfig: semantic dedup threshold clamps to [0, 1]", () => {
212
+ const below = parseConfig({ semanticDedupThreshold: -0.5 });
213
+ const above = parseConfig({ semanticDedupThreshold: 5 });
214
+ assert.equal(below.semanticDedupThreshold, 0);
215
+ assert.equal(above.semanticDedupThreshold, 1);
216
+ });
217
+
218
+ test("parseConfig: semanticDedupCandidates=0 is preserved (operator disable signal)", () => {
219
+ const zero = parseConfig({ semanticDedupCandidates: 0 });
220
+ assert.equal(zero.semanticDedupCandidates, 0);
221
+ });
222
+
223
+ test("parseConfig: negative semanticDedupCandidates falls back to default 5", () => {
224
+ const negative = parseConfig({ semanticDedupCandidates: -3 });
225
+ assert.equal(negative.semanticDedupCandidates, 5);
226
+ });
227
+
228
+ test("parseConfig: NaN semanticDedupCandidates falls back to default 5", () => {
229
+ const nan = parseConfig({ semanticDedupCandidates: Number.NaN });
230
+ assert.equal(nan.semanticDedupCandidates, 5);
231
+ });
232
+
233
+ test("parseConfig: NaN semanticDedupThreshold falls back to default 0.92", () => {
234
+ const nan = parseConfig({ semanticDedupThreshold: Number.NaN });
235
+ assert.equal(nan.semanticDedupThreshold, 0.92);
236
+ });
237
+
238
+ test("parseConfig: Infinity semanticDedupThreshold falls back to default 0.92", () => {
239
+ const pos = parseConfig({ semanticDedupThreshold: Number.POSITIVE_INFINITY });
240
+ const neg = parseConfig({ semanticDedupThreshold: Number.NEGATIVE_INFINITY });
241
+ assert.equal(pos.semanticDedupThreshold, 0.92);
242
+ assert.equal(neg.semanticDedupThreshold, 0.92);
243
+ });
244
+
245
+ // ── Regression: semantic skip must NOT register a synthetic content hash ──────
246
+ //
247
+ // Verifies the fix for the bug introduced in PR #399: when the semantic dedup
248
+ // guard decides to skip a fact (near-duplicate of an existing memory), the
249
+ // orchestrator must NOT add the skipped fact's content to contentHashIndex.
250
+ //
251
+ // If it did, archiving the original neighbor memory would leave an orphaned
252
+ // hash that permanently blocks legitimate writes of the same text.
253
+
254
+ test("regression #399: semantic dedup skip does NOT add content hash to index", async () => {
255
+ const stateDir = await mkdtemp(join(tmpdir(), "remnic-test-"));
256
+ try {
257
+ const index = new ContentHashIndex(stateDir);
258
+ await index.load();
259
+
260
+ const FACT_CONTENT = "the user prefers dark mode in their editor";
261
+ const NEIGHBOR_ID = "mem-neighbor-001";
262
+
263
+ // Simulate the orchestrator: run the semantic dedup decision (skip outcome).
264
+ const decision = await decideSemanticDedup(
265
+ FACT_CONTENT,
266
+ makeLookup([{ id: NEIGHBOR_ID, score: 0.97 }]),
267
+ DEFAULT_OPTS,
268
+ );
269
+ assert.equal(decision.action, "skip", "precondition: decision must be skip");
270
+
271
+ // The fixed orchestrator does NOT call index.add() in the skip branch.
272
+ // Simulate that invariant: we do NOT call index.add(FACT_CONTENT) here.
273
+
274
+ // The skipped fact's hash must NOT be present in the index.
275
+ assert.equal(
276
+ index.has(FACT_CONTENT),
277
+ false,
278
+ "skipped fact content must not be registered in contentHashIndex",
279
+ );
280
+
281
+ // Now simulate archiving the neighbor: remove its content from the index.
282
+ // (In the orchestrator this would be index.remove(neighborMemory.content);
283
+ // here the neighbor was never registered, so the index stays empty — which
284
+ // is the desired state.)
285
+ assert.equal(index.size, 0, "index must remain empty after semantic skip");
286
+
287
+ // A subsequent write attempt of the same text must NOT be blocked by the
288
+ // hash gate (because no hash was ever registered for the skipped fact).
289
+ assert.equal(
290
+ index.has(FACT_CONTENT),
291
+ false,
292
+ "third write attempt must not be blocked by a phantom hash",
293
+ );
294
+
295
+ // Confirm that only a genuine persist (index.add) registers the hash.
296
+ index.add(FACT_CONTENT);
297
+ assert.equal(
298
+ index.has(FACT_CONTENT),
299
+ true,
300
+ "explicit add must register the hash",
301
+ );
302
+ assert.equal(index.size, 1);
303
+ } finally {
304
+ await rm(stateDir, { recursive: true, force: true });
305
+ }
306
+ });
307
+
308
+ test("regression #399: after neighbor archive, re-write of skipped content is allowed", async () => {
309
+ // More explicit end-to-end simulation of the full scenario:
310
+ // 1. Seed a "neighbor" memory in the hash index.
311
+ // 2. A second fact is semantically-skipped (no hash added — the fix).
312
+ // 3. The neighbor memory is archived (its hash is removed from the index).
313
+ // 4. A third write of the same content as the skipped fact must pass the gate.
314
+
315
+ const stateDir = await mkdtemp(join(tmpdir(), "remnic-test-"));
316
+ try {
317
+ const index = new ContentHashIndex(stateDir);
318
+ await index.load();
319
+
320
+ const NEIGHBOR_CONTENT = "the user prefers dark mode in their editor";
321
+ const SKIPPED_CONTENT = "the user likes dark editor themes";
322
+
323
+ // Step 1: seed neighbor memory hash (as if a real persist happened).
324
+ index.add(NEIGHBOR_CONTENT);
325
+ assert.equal(index.size, 1, "neighbor hash seeded");
326
+
327
+ // Step 2: semantic dedup decides to skip SKIPPED_CONTENT.
328
+ const decision = await decideSemanticDedup(
329
+ SKIPPED_CONTENT,
330
+ makeLookup([{ id: "mem-neighbor-001", score: 0.95 }]),
331
+ DEFAULT_OPTS,
332
+ );
333
+ assert.equal(decision.action, "skip");
334
+ // Fixed code: do NOT call index.add(SKIPPED_CONTENT).
335
+ // (In the old buggy code this line would have been executed.)
336
+ assert.equal(
337
+ index.has(SKIPPED_CONTENT),
338
+ false,
339
+ "skipped content must not be in index",
340
+ );
341
+
342
+ // Step 3: archive the neighbor — remove its hash.
343
+ index.remove(NEIGHBOR_CONTENT);
344
+ assert.equal(index.size, 0, "index empty after neighbor archived");
345
+
346
+ // Step 4: attempt to write SKIPPED_CONTENT again — must not be blocked.
347
+ assert.equal(
348
+ index.has(SKIPPED_CONTENT),
349
+ false,
350
+ "write of previously-skipped content must not be blocked after neighbor archive",
351
+ );
352
+
353
+ // Confirm a fresh persist now registers the hash correctly.
354
+ index.add(SKIPPED_CONTENT);
355
+ assert.equal(index.has(SKIPPED_CONTENT), true);
356
+ assert.equal(index.size, 1);
357
+ } finally {
358
+ await rm(stateDir, { recursive: true, force: true });
359
+ }
360
+ });
361
+
362
+ // ── Regression: PR #399 P1 — cross-namespace dedup must not suppress writes ──
363
+ //
364
+ // When namespaces are enabled and two namespaces contain near-duplicate
365
+ // content, a write in namespace A must NOT be skipped because the top
366
+ // embedding hit lives in namespace B. The fix scopes the semantic dedup
367
+ // lookup to the target namespace's path prefix.
368
+
369
+ async function seedEmbeddingIndex(
370
+ memoryDir: string,
371
+ entries: Record<string, { vector: number[]; path: string }>,
372
+ ): Promise<void> {
373
+ const stateDir = join(memoryDir, "state");
374
+ await mkdir(stateDir, { recursive: true });
375
+ const indexFile = {
376
+ version: 1 as const,
377
+ provider: "openai" as const,
378
+ model: "text-embedding-3-small",
379
+ entries,
380
+ };
381
+ await writeFile(
382
+ join(stateDir, "embeddings.json"),
383
+ JSON.stringify(indexFile),
384
+ "utf-8",
385
+ );
386
+ }
387
+
388
+ /**
389
+ * Replace global fetch with a stub that returns a fixed embedding vector.
390
+ * Returns a restore function the test should call in its `finally` block.
391
+ */
392
+ function stubEmbedFetch(vector: number[]): () => void {
393
+ const original = globalThis.fetch;
394
+ (globalThis as any).fetch = async (_url: any, _init: any) => {
395
+ return new Response(
396
+ JSON.stringify({ data: [{ embedding: vector }] }),
397
+ { status: 200, headers: { "Content-Type": "application/json" } },
398
+ );
399
+ };
400
+ return () => {
401
+ (globalThis as any).fetch = original;
402
+ };
403
+ }
404
+
405
+ test("regression #399 P1: semantic dedup lookup is scoped to target namespace", async () => {
406
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-ns-dedup-"));
407
+ // Use a unit vector for stability: cosine similarity with itself is ~1.
408
+ const vec = [1, 0, 0, 0];
409
+ const restoreFetch = stubEmbedFetch(vec);
410
+ try {
411
+ // Seed an index with two near-identical entries in two namespaces.
412
+ // Paths mirror what `toMemoryRelativePath` would produce.
413
+ await seedEmbeddingIndex(memoryDir, {
414
+ "mem-a-001": {
415
+ vector: vec,
416
+ path: "namespaces/alpha/facts/a-001.md",
417
+ },
418
+ "mem-b-001": {
419
+ vector: vec,
420
+ path: "namespaces/beta/facts/b-001.md",
421
+ },
422
+ });
423
+
424
+ const config = parseConfig({
425
+ memoryDir,
426
+ namespacesEnabled: true,
427
+ embeddingFallbackEnabled: true,
428
+ embeddingFallbackProvider: "openai",
429
+ // Non-empty key so the provider resolves. The stubbed fetch never
430
+ // validates the header.
431
+ openaiApiKey: "test-key",
432
+ });
433
+ const fallback = new EmbeddingFallback(config);
434
+
435
+ // Unscoped lookup: both namespaces match. Confirms the baseline index
436
+ // and the stubbed fetch plumbing work.
437
+ const unscoped = await fallback.search("the user prefers tabs", 5);
438
+ assert.equal(unscoped.length, 2, "unscoped lookup returns both entries");
439
+
440
+ // Scoped to namespace alpha: only the alpha entry should appear, so
441
+ // a fact being written into alpha cannot be semantically deduped
442
+ // against the beta neighbor.
443
+ const alphaHits = await fallback.search(
444
+ "the user prefers tabs",
445
+ 5,
446
+ { pathPrefix: "namespaces/alpha/" },
447
+ );
448
+ assert.equal(alphaHits.length, 1, "alpha-scoped lookup returns one hit");
449
+ assert.equal(alphaHits[0]?.id, "mem-a-001");
450
+
451
+ // Symmetric check for beta.
452
+ const betaHits = await fallback.search(
453
+ "the user prefers tabs",
454
+ 5,
455
+ { pathPrefix: "namespaces/beta/" },
456
+ );
457
+ assert.equal(betaHits.length, 1, "beta-scoped lookup returns one hit");
458
+ assert.equal(betaHits[0]?.id, "mem-b-001");
459
+
460
+ // End-to-end: feed the scoped lookup into decideSemanticDedup for a
461
+ // hypothetical fact destined for a THIRD namespace with no entries.
462
+ // The lookup must return zero candidates, and the decision must be
463
+ // "keep" — NOT "skip" — even though alpha/beta both contain
464
+ // high-similarity memories. Without the P1 fix, the unfiltered index
465
+ // would have surfaced either alpha or beta and the fact would be
466
+ // dropped.
467
+ const decision = await decideSemanticDedup(
468
+ "the user prefers tabs",
469
+ (content, limit) =>
470
+ fallback
471
+ .search(content, limit, { pathPrefix: "namespaces/gamma/" })
472
+ .then((hits) =>
473
+ hits.map((hit) => ({
474
+ id: hit.id,
475
+ score: hit.score,
476
+ path: hit.path,
477
+ })),
478
+ ),
479
+ DEFAULT_OPTS,
480
+ );
481
+ assert.equal(
482
+ decision.action,
483
+ "keep",
484
+ "cross-namespace dedup must not skip writes in a fresh namespace",
485
+ );
486
+ } finally {
487
+ restoreFetch();
488
+ await rm(memoryDir, { recursive: true, force: true });
489
+ }
490
+ });
491
+
492
+ // ── Finding 3: empty index vs backend unavailable ─────────────────────────────
493
+
494
+ test("finding 3: empty lookup result returns no_candidates, not backend_unavailable", async () => {
495
+ // Provider is reachable (no throw) but the index has no entries.
496
+ const decision = await decideSemanticDedup(
497
+ "brand new fact never seen before",
498
+ makeLookup([]),
499
+ DEFAULT_OPTS,
500
+ );
501
+ assert.equal(decision.action, "keep");
502
+ assert.equal(
503
+ decision.reason,
504
+ "no_candidates",
505
+ "empty index must yield no_candidates, not backend_unavailable",
506
+ );
507
+ });
508
+
509
+ test("finding 3: lookup throw returns backend_unavailable", async () => {
510
+ const decision = await decideSemanticDedup(
511
+ "some fact",
512
+ async () => {
513
+ throw new Error("connection refused");
514
+ },
515
+ DEFAULT_OPTS,
516
+ );
517
+ assert.equal(decision.action, "keep");
518
+ assert.equal(
519
+ decision.reason,
520
+ "backend_unavailable",
521
+ "provider error must yield backend_unavailable",
522
+ );
523
+ });
524
+
525
+ // ── Finding 2: fractional semanticDedupCandidates clamped to 1 ───────────────
526
+
527
+ test("finding 2: parseConfig semanticDedupCandidates=0.5 clamps to 1 (not 0)", () => {
528
+ const config = parseConfig({ semanticDedupCandidates: 0.5 });
529
+ assert.equal(
530
+ config.semanticDedupCandidates,
531
+ 1,
532
+ "fractional positive value must clamp to 1, not floor to 0",
533
+ );
534
+ });
535
+
536
+ test("finding 2: parseConfig semanticDedupCandidates=0.99 clamps to 1", () => {
537
+ const config = parseConfig({ semanticDedupCandidates: 0.99 });
538
+ assert.equal(config.semanticDedupCandidates, 1);
539
+ });
540
+
541
+ test("finding 2: parseConfig semanticDedupCandidates=0 preserved (explicit disable)", () => {
542
+ const config = parseConfig({ semanticDedupCandidates: 0 });
543
+ assert.equal(config.semanticDedupCandidates, 0);
544
+ });
545
+
546
+ test("finding 2: parseConfig semanticDedupCandidates=1.5 floors to 1 (not clamped)", () => {
547
+ // Value > 1 but fractional: floor(1.5) = 1, raw > 0, so clamp is not needed.
548
+ const config = parseConfig({ semanticDedupCandidates: 1.5 });
549
+ assert.equal(config.semanticDedupCandidates, 1);
550
+ });
551
+
552
+ // ── Finding 1: semantic-skip candidate that is also a contradiction ───────────
553
+ //
554
+ // The orchestrator fix (deferred skip) cannot be exercised as a pure unit test
555
+ // here because it lives in the orchestrator's write loop. The pure semantic.ts
556
+ // layer is unchanged in behaviour: it still returns action="skip" for a
557
+ // high-similarity hit. The integration guarantee is:
558
+ // • decideSemanticDedup returns skip (confirmed below — precondition)
559
+ // • orchestrator runs contradiction detection before applying the skip
560
+ // • if contradiction found → write proceeds (supersede path)
561
+ // • if no contradiction → skip is applied (existing behaviour)
562
+ //
563
+ // We verify the precondition that the pure function still returns "skip" for
564
+ // high-similarity, so the orchestrator has the correct input to branch on.
565
+
566
+ test("finding 1: precondition — decideSemanticDedup still returns skip for high-similarity hit", async () => {
567
+ const decision = await decideSemanticDedup(
568
+ "the operator never wants dark mode enabled",
569
+ makeLookup([{ id: "pref-001", score: 0.95 }]),
570
+ DEFAULT_OPTS,
571
+ );
572
+ assert.equal(
573
+ decision.action,
574
+ "skip",
575
+ "high-similarity hit must still produce skip so orchestrator can branch on it",
576
+ );
577
+ if (decision.action === "skip") {
578
+ assert.equal(decision.reason, "near_duplicate");
579
+ assert.equal(decision.topId, "pref-001");
580
+ }
581
+ });
582
+
583
+ test("regression #399 P1: default namespace at root excludes namespaces/* entries", async () => {
584
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-ns-dedup-default-"));
585
+ const vec = [1, 0, 0, 0];
586
+ const restoreFetch = stubEmbedFetch(vec);
587
+ try {
588
+ await seedEmbeddingIndex(memoryDir, {
589
+ "mem-default-001": { vector: vec, path: "facts/default-001.md" },
590
+ "mem-alpha-001": { vector: vec, path: "namespaces/alpha/facts/a-001.md" },
591
+ });
592
+
593
+ const config = parseConfig({
594
+ memoryDir,
595
+ namespacesEnabled: true,
596
+ embeddingFallbackEnabled: true,
597
+ embeddingFallbackProvider: "openai",
598
+ openaiApiKey: "test-key",
599
+ });
600
+ const fallback = new EmbeddingFallback(config);
601
+
602
+ // The orchestrator's scope helper passes `pathExcludePrefixes:
603
+ // ["namespaces/"]` when targeting the default namespace at legacy
604
+ // root. Simulate that filter directly.
605
+ const defaultHits = await fallback.search(
606
+ "content",
607
+ 5,
608
+ { pathExcludePrefixes: ["namespaces/"] },
609
+ );
610
+ assert.equal(defaultHits.length, 1);
611
+ assert.equal(defaultHits[0]?.id, "mem-default-001");
612
+ } finally {
613
+ restoreFetch();
614
+ await rm(memoryDir, { recursive: true, force: true });
615
+ }
616
+ });
617
+
618
+ // ── Regression: PR #399 HIGH — chunking path must honour pendingSemanticSkip ──
619
+ //
620
+ // Before the fix (commit 57d7e7d), the orchestrator's chunking branch executed
621
+ // a `continue` that bypassed the deferred `pendingSemanticSkip` guard entirely.
622
+ // A fact whose content was long enough to trigger chunking would be persisted
623
+ // (and have its hash registered) even when semanticDecision === "skip".
624
+ //
625
+ // The fix moves both contradiction detection and the semantic-skip check to
626
+ // BEFORE the chunking branch, so the chunking branch is only reached when the
627
+ // fact has passed the semantic-dedup gate.
628
+ //
629
+ // These tests validate the two invariants using the pure layer:
630
+ // 1. decideSemanticDedup → skip, NO contradiction → write must be suppressed
631
+ // 2. decideSemanticDedup → skip, WITH contradiction → write must proceed
632
+ //
633
+ // The orchestrator invariant is tested via simulation: we use `chunkContent`
634
+ // with a low threshold to confirm the content *would* have triggered chunking,
635
+ // then assert on the semantic decision and contradiction outcome that the
636
+ // orchestrator sees, proving the pre-chunking guard is now the gating condition.
637
+
638
+ /** Build a synthetic long string that triggers chunking at `minTokens` = 10. */
639
+ function buildLongContent(sentenceCount: number, wordsPerSentence = 15): string {
640
+ const sentences: string[] = [];
641
+ for (let i = 0; i < sentenceCount; i++) {
642
+ const words = Array.from({ length: wordsPerSentence }, (_, w) =>
643
+ `word${i}_${w}`,
644
+ );
645
+ sentences.push(words.join(" ") + ".");
646
+ }
647
+ return sentences.join(" ");
648
+ }
649
+
650
+ /** Low chunking threshold so a ~30-sentence string reliably produces multiple chunks. */
651
+ const LOW_THRESHOLD_CHUNKING: ChunkingConfig = {
652
+ targetTokens: 20,
653
+ minTokens: 10,
654
+ overlapSentences: 1,
655
+ };
656
+
657
+ test("regression #399 HIGH: long content that would chunk is NOT written when semantic-skip has no contradiction", async () => {
658
+ // Build content long enough to trigger chunking at the low threshold.
659
+ const longContent = buildLongContent(30);
660
+
661
+ // Confirm this content would produce multiple chunks (precondition).
662
+ const chunkResult = chunkContent(longContent, LOW_THRESHOLD_CHUNKING);
663
+ assert.ok(
664
+ chunkResult.chunked && chunkResult.chunks.length > 1,
665
+ `precondition: chunkResult.chunked must be true; got ${chunkResult.chunks.length} chunk(s)`,
666
+ );
667
+
668
+ // The semantic dedup lookup returns a high-similarity hit → decision = skip.
669
+ const semanticDecision = await decideSemanticDedup(
670
+ longContent,
671
+ makeLookup([{ id: "neighbor-001", score: 0.97 }]),
672
+ DEFAULT_OPTS,
673
+ );
674
+ assert.equal(
675
+ semanticDecision.action,
676
+ "skip",
677
+ "semantic decision must be skip for high-similarity hit",
678
+ );
679
+
680
+ // No contradiction detected (supersedes is undefined).
681
+ const supersedes: string | undefined = undefined;
682
+
683
+ // Fixed orchestrator gate: if (pendingSemanticSkip && !supersedes) → skip.
684
+ // Before the fix, this check was AFTER the chunking branch's `continue`,
685
+ // so chunking would have written the memory before this guard ran.
686
+ const pendingSemanticSkip =
687
+ semanticDecision.action === "skip" ? semanticDecision : null;
688
+ const gateTriggered = pendingSemanticSkip !== null && !supersedes;
689
+
690
+ assert.ok(
691
+ gateTriggered,
692
+ "semantic-skip gate must fire (suppressing the write) when there is no contradiction",
693
+ );
694
+
695
+ // Verify no hash is registered (the orchestrator skips index.add when gated).
696
+ // Simulate: if gated, we do NOT call index.add(). Confirm the index stays empty.
697
+ const stateDir = await mkdtemp(join(tmpdir(), "remnic-chunk-dedup-1-"));
698
+ try {
699
+ const index = new ContentHashIndex(stateDir);
700
+ await index.load();
701
+ // Gate fires → no write → no hash registration.
702
+ if (!gateTriggered) {
703
+ // Would-be write path (only reached if bug is present).
704
+ index.add(longContent);
705
+ }
706
+ assert.equal(
707
+ index.has(longContent),
708
+ false,
709
+ "content hash must NOT be registered when semantic-skip gate suppresses the chunking write",
710
+ );
711
+ assert.equal(index.size, 0);
712
+ } finally {
713
+ await rm(stateDir, { recursive: true, force: true });
714
+ }
715
+ });
716
+
717
+ test("regression #399 HIGH: long content that would chunk IS written when semantic-skip has a contradiction (supersession path)", async () => {
718
+ // Build content long enough to trigger chunking at the low threshold.
719
+ const longContent = buildLongContent(30);
720
+
721
+ // Confirm this content would produce multiple chunks (precondition).
722
+ const chunkResult = chunkContent(longContent, LOW_THRESHOLD_CHUNKING);
723
+ assert.ok(
724
+ chunkResult.chunked && chunkResult.chunks.length > 1,
725
+ `precondition: chunkResult.chunked must be true; got ${chunkResult.chunks.length} chunk(s)`,
726
+ );
727
+
728
+ // The semantic dedup lookup returns a high-similarity hit → decision = skip.
729
+ const semanticDecision = await decideSemanticDedup(
730
+ longContent,
731
+ makeLookup([{ id: "neighbor-001", score: 0.97 }]),
732
+ DEFAULT_OPTS,
733
+ );
734
+ assert.equal(
735
+ semanticDecision.action,
736
+ "skip",
737
+ "semantic decision must be skip for high-similarity hit",
738
+ );
739
+
740
+ // A contradiction IS detected — this is the supersession path.
741
+ // The orchestrator sets supersedes when checkForContradiction returns a hit.
742
+ const supersedes = "old-memory-abc-123";
743
+
744
+ // Fixed orchestrator gate: if (pendingSemanticSkip && !supersedes) → skip.
745
+ // When supersedes is set, the gate must NOT fire: the write proceeds.
746
+ const pendingSemanticSkip =
747
+ semanticDecision.action === "skip" ? semanticDecision : null;
748
+ const gateTriggered = pendingSemanticSkip !== null && !supersedes;
749
+
750
+ assert.ok(
751
+ !gateTriggered,
752
+ "semantic-skip gate must NOT fire when a contradiction was found (supersedes is set)",
753
+ );
754
+
755
+ // Simulate the write path that the orchestrator takes when the gate does not fire:
756
+ // content IS persisted and its hash IS registered.
757
+ const stateDir = await mkdtemp(join(tmpdir(), "remnic-chunk-dedup-2-"));
758
+ try {
759
+ const index = new ContentHashIndex(stateDir);
760
+ await index.load();
761
+ // Gate did NOT fire → write proceeds → register hash.
762
+ if (!gateTriggered) {
763
+ index.add(longContent);
764
+ }
765
+ assert.equal(
766
+ index.has(longContent),
767
+ true,
768
+ "content hash MUST be registered when supersession path proceeds despite semantic-skip flag",
769
+ );
770
+ assert.equal(index.size, 1);
771
+ } finally {
772
+ await rm(stateDir, { recursive: true, force: true });
773
+ }
774
+ });
775
+
776
+ // ── Round 6 regression tests ───────────────────────────────────────────────────
777
+
778
+ // Finding 1+2 (round 6): chunked parent must carry supersedes + links
779
+ //
780
+ // When contradiction detection runs before the chunking branch (the round 5
781
+ // fix) and finds a conflict, the chunked parent writeMemory() call must pass
782
+ // supersedes and links. Without this fix, deindexMemory() fires on the old
783
+ // memory but the new chunked parent has no supersedes frontmatter field —
784
+ // leaving a dangling deindex reference.
785
+ //
786
+ // This test exercises StorageManager.writeMemory() directly with both
787
+ // supersedes and links to verify the round 6 fix propagates them to the
788
+ // written file's YAML frontmatter.
789
+
790
+ test("round 6: chunked parent writeMemory carries supersedes in frontmatter", async () => {
791
+ const memDir = await mkdtemp(join(tmpdir(), "remnic-chunked-supersedes-"));
792
+ try {
793
+ const storage = new StorageManager(memDir);
794
+ const OLD_ID = "fact-old-abc-123";
795
+
796
+ const newId = await storage.writeMemory("fact", "the user prefers dark mode", {
797
+ confidence: 0.9,
798
+ tags: ["chunked", "preference"],
799
+ supersedes: OLD_ID,
800
+ links: [
801
+ {
802
+ targetId: OLD_ID,
803
+ linkType: "contradicts",
804
+ strength: 0.88,
805
+ reason: "user corrected earlier preference",
806
+ },
807
+ ],
808
+ });
809
+
810
+ // Find the written file and parse its raw YAML to verify the fields.
811
+ const allFiles: string[] = [];
812
+ const factsBase = join(memDir, "facts");
813
+ try {
814
+ for (const dateDir of await readdir(factsBase)) {
815
+ const dir = join(factsBase, dateDir);
816
+ for (const f of await readdir(dir)) {
817
+ if (f.endsWith(".md")) allFiles.push(join(dir, f));
818
+ }
819
+ }
820
+ } catch {
821
+ // factsBase may not exist if today's directory hasn't been created yet
822
+ }
823
+
824
+ assert.equal(allFiles.length, 1, "exactly one memory file must be written");
825
+ const raw = await readFile(allFiles[0]!, "utf-8");
826
+
827
+ // Verify supersedes appears in the YAML block.
828
+ assert.ok(
829
+ raw.includes(`supersedes: ${OLD_ID}`),
830
+ `written file must contain supersedes: ${OLD_ID}\nActual content:\n${raw}`,
831
+ );
832
+
833
+ // Verify links block appears in the YAML block.
834
+ assert.ok(
835
+ raw.includes("contradicts"),
836
+ `written file must contain the contradicts link type\nActual content:\n${raw}`,
837
+ );
838
+ assert.ok(
839
+ raw.includes(OLD_ID),
840
+ `written file must reference the old memory id in links\nActual content:\n${raw}`,
841
+ );
842
+
843
+ // Verify the returned ID is what we'd find in the file.
844
+ assert.ok(newId.startsWith("fact-"), `memory id must start with 'fact-'; got ${newId}`);
845
+ assert.ok(raw.includes(`id: ${newId}`), `file must contain id: ${newId}`);
846
+ } finally {
847
+ StorageManager.clearAllStaticCaches();
848
+ await rm(memDir, { recursive: true, force: true });
849
+ }
850
+ });
851
+
852
+ // Finding 3 (round 6): semanticDedupLookup throws on backend unavailability
853
+ //
854
+ // The round 6 fix changes semanticDedupLookup to THROW when the embedding
855
+ // backend is not configured or is unavailable, rather than returning [].
856
+ // This ensures decideSemanticDedup's catch block fires and sets
857
+ // reason="backend_unavailable" instead of reason="no_candidates".
858
+ //
859
+ // We test the boundary at the pure decideSemanticDedup level: a lookup that
860
+ // throws (simulating the new semanticDedupLookup behaviour) must yield
861
+ // reason="backend_unavailable", not reason="no_candidates".
862
+
863
+ test("round 6: orchestrator lookup throw (backend down) maps to backend_unavailable, not no_candidates", async () => {
864
+ // Simulate the new semanticDedupLookup behaviour: throws when backend unavailable.
865
+ const throwingLookup: SemanticDedupLookup = async () => {
866
+ throw new Error("semantic dedup: embedding backend unavailable");
867
+ };
868
+
869
+ const decision = await decideSemanticDedup("some fact content", throwingLookup, DEFAULT_OPTS);
870
+
871
+ assert.equal(
872
+ decision.action,
873
+ "keep",
874
+ "backend unavailable must keep the fact (fail-open)",
875
+ );
876
+ assert.equal(
877
+ decision.reason,
878
+ "backend_unavailable",
879
+ "reason must be backend_unavailable (not no_candidates) when lookup throws",
880
+ );
881
+ });
882
+
883
+ test("round 6: empty lookup result (index empty, backend OK) still maps to no_candidates", async () => {
884
+ // Simulate the new semanticDedupLookup behaviour when the backend is OK but
885
+ // the index is empty: return [], do NOT throw.
886
+ const emptyLookup: SemanticDedupLookup = async () => [];
887
+
888
+ const decision = await decideSemanticDedup("some fact content", emptyLookup, DEFAULT_OPTS);
889
+
890
+ assert.equal(decision.action, "keep");
891
+ assert.equal(
892
+ decision.reason,
893
+ "no_candidates",
894
+ "empty index (backend reachable, returns []) must yield no_candidates, not backend_unavailable",
895
+ );
896
+ });
897
+
898
+ // ── UUI1: correction category exempt from semantic skip fallback ──────────────
899
+ //
900
+ // When contradiction detection is disabled (or QMD is unavailable), `supersedes`
901
+ // is never set. Without the UUI1 fix, a high-similarity fact in the "correction"
902
+ // category would be silently dropped by the semantic skip gate even though it
903
+ // is a legitimate update. The gate must always let corrections through.
904
+ //
905
+ // This simulates the orchestrator gate logic at the pure layer: the test drives
906
+ // the same `pendingSemanticSkip && !supersedes && !isCorrection` condition that
907
+ // the orchestrator evaluates, asserting:
908
+ // 1. A "correction" write is NOT suppressed (gate does not fire).
909
+ // 2. A "fact" write in the same circumstances IS suppressed (gate still works).
910
+
911
+ test("UUI1: correction category is never suppressed by semantic skip fallback when supersedes is unset", async () => {
912
+ // Both facts have a high-similarity neighbor → decideSemanticDedup returns skip.
913
+ const semanticDecisionCorrection = await decideSemanticDedup(
914
+ "the user now prefers light mode",
915
+ makeLookup([{ id: "pref-old-001", score: 0.96 }]),
916
+ DEFAULT_OPTS,
917
+ );
918
+ assert.equal(
919
+ semanticDecisionCorrection.action,
920
+ "skip",
921
+ "precondition: semantic decision must be skip for high-similarity hit",
922
+ );
923
+
924
+ const semanticDecisionFact = await decideSemanticDedup(
925
+ "the user prefers dark mode",
926
+ makeLookup([{ id: "pref-old-002", score: 0.95 }]),
927
+ DEFAULT_OPTS,
928
+ );
929
+ assert.equal(
930
+ semanticDecisionFact.action,
931
+ "skip",
932
+ "precondition: semantic decision must be skip for high-similarity hit",
933
+ );
934
+
935
+ // Contradiction detection disabled / QMD unavailable → supersedes never set.
936
+ const supersedes: string | undefined = undefined;
937
+
938
+ // --- Correction write: gate must NOT fire ---
939
+ const pendingSkipCorrection =
940
+ semanticDecisionCorrection.action === "skip" ? semanticDecisionCorrection : null;
941
+ const isCorrectionCategory = true; // writeCategory === "correction"
942
+ const correctionGateFires =
943
+ pendingSkipCorrection !== null && !supersedes && !isCorrectionCategory;
944
+ assert.equal(
945
+ correctionGateFires,
946
+ false,
947
+ "semantic skip gate must NOT fire for correction category — correction must be persisted",
948
+ );
949
+
950
+ // --- Normal fact write: gate MUST fire ---
951
+ const pendingSkipFact =
952
+ semanticDecisionFact.action === "skip" ? semanticDecisionFact : null;
953
+ const isFactCategory = false; // writeCategory === "fact", not "correction"
954
+ const factGateFires =
955
+ pendingSkipFact !== null && !supersedes && !isFactCategory;
956
+ assert.equal(
957
+ factGateFires,
958
+ true,
959
+ "semantic skip gate MUST fire for non-correction category (fact) — near-duplicate fact must be suppressed",
960
+ );
961
+ });
962
+
963
+ // ── UUI2: backend-unavailable short-circuit per batch ────────────────────────
964
+ //
965
+ // When the embedding backend is degraded, each fact in a batch should NOT pay
966
+ // a full lookup roundtrip. Once the first lookup returns backend_unavailable,
967
+ // subsequent facts must skip the lookup entirely and proceed directly to write.
968
+ //
969
+ // This simulates the orchestrator's `batchBackendUnavailable` flag logic:
970
+ // the flag is false at batch start, is set when the first lookup signals
971
+ // backend_unavailable, and subsequent iterations skip the lookup call.
972
+ // All N facts must still be written (fail-open behaviour preserved).
973
+
974
+ test("UUI2: batch backend-unavailable flag short-circuits embedding lookups after first failure", async () => {
975
+ let lookupCallCount = 0;
976
+
977
+ const throwingLookup: SemanticDedupLookup = async () => {
978
+ lookupCallCount++;
979
+ throw new Error("embedding backend unavailable");
980
+ };
981
+
982
+ // Simulate the orchestrator's per-batch short-circuit logic for N=5 facts.
983
+ const N = 5;
984
+ let batchBackendUnavailable = false;
985
+ const decisions: SemanticDedupDecision[] = [];
986
+
987
+ for (let i = 0; i < N; i++) {
988
+ let semanticDecision: SemanticDedupDecision;
989
+ if (batchBackendUnavailable) {
990
+ // Short-circuit: no lookup call, treat as backend_unavailable.
991
+ semanticDecision = { action: "keep", reason: "backend_unavailable" };
992
+ } else {
993
+ try {
994
+ semanticDecision = await decideSemanticDedup(
995
+ `synthetic fact content number ${i}`,
996
+ throwingLookup,
997
+ DEFAULT_OPTS,
998
+ );
999
+ } catch {
1000
+ semanticDecision = { action: "keep", reason: "backend_unavailable" };
1001
+ }
1002
+ if (semanticDecision.reason === "backend_unavailable") {
1003
+ batchBackendUnavailable = true;
1004
+ }
1005
+ }
1006
+ decisions.push(semanticDecision);
1007
+ }
1008
+
1009
+ // The underlying lookup must have been called at most 1 time (for the first
1010
+ // fact only). Facts 2–5 must have hit the batchBackendUnavailable branch.
1011
+ assert.ok(
1012
+ lookupCallCount <= 1,
1013
+ `embed lookup must be called ≤1 time for the lookup phase; called ${lookupCallCount} time(s)`,
1014
+ );
1015
+
1016
+ // All 5 facts must have action="keep" (fail-open: writes proceed).
1017
+ assert.equal(decisions.length, N, "all N facts must produce a decision");
1018
+ for (const decision of decisions) {
1019
+ assert.equal(
1020
+ decision.action,
1021
+ "keep",
1022
+ "every fact must be kept (fail-open) when backend is unavailable",
1023
+ );
1024
+ assert.equal(
1025
+ decision.reason,
1026
+ "backend_unavailable",
1027
+ "every decision must carry reason=backend_unavailable",
1028
+ );
1029
+ }
1030
+ });
1031
+
1032
+ // ── Round 9 / Finding UZqB: embedding timeout propagates as backend_unavailable ─
1033
+ //
1034
+ // Previously a timed-out embedding fetch returned null from embed(), which
1035
+ // caused search() to return [] silently. decideSemanticDedup classified that
1036
+ // result as no_candidates instead of backend_unavailable, so the per-batch
1037
+ // batchBackendUnavailable flag never flipped and every fact in a batch paid a
1038
+ // full timeout roundtrip (N × timeout instead of 1 × timeout).
1039
+ //
1040
+ // Round 9 made embed() throw EmbeddingTimeoutError on the lookup path when
1041
+ // AbortSignal fires. Round 10 (Findings Ui1J + Ui1L) scopes that throw:
1042
+ // • search() WITHOUT throwOnTimeout catches EmbeddingTimeoutError and returns
1043
+ // [] — this is the recall-path contract so a slow backend doesn't abort recall.
1044
+ // • search() WITH throwOnTimeout:true re-throws — this is the dedup-path
1045
+ // contract so decideSemanticDedup's catch block can return
1046
+ // reason="backend_unavailable" and activate the per-batch short-circuit.
1047
+ //
1048
+ // These tests cover the TIMEOUT code path and validate all four combinations:
1049
+
1050
+ /**
1051
+ * Replace global fetch with a stub that always throws a DOMException AbortError,
1052
+ * simulating an AbortSignal.timeout() firing. Returns a restore function and a
1053
+ * call counter so tests can assert how many times fetch was attempted.
1054
+ */
1055
+ function stubTimeoutFetch(): { restore: () => void; callCount: () => number } {
1056
+ const original = globalThis.fetch;
1057
+ let count = 0;
1058
+ (globalThis as any).fetch = async (_url: any, init: any) => {
1059
+ count++;
1060
+ // AbortSignal.timeout() raises a DOMException with name "TimeoutError".
1061
+ // Simulate that: throw a DOMException if available, or a plain Error with
1062
+ // the correct name so the isTimeout branch in embed() triggers.
1063
+ const signal: AbortSignal | undefined = init?.signal;
1064
+ if (signal?.aborted) {
1065
+ const err = signal.reason instanceof Error
1066
+ ? signal.reason
1067
+ : Object.assign(new Error("The operation was aborted due to timeout"), { name: "TimeoutError" });
1068
+ throw err;
1069
+ }
1070
+ // Signal not yet aborted — throw as TimeoutError anyway to simulate a
1071
+ // backend that always takes longer than the deadline.
1072
+ const timeout = new Error("The operation timed out");
1073
+ (timeout as any).name = "TimeoutError";
1074
+ throw timeout;
1075
+ };
1076
+ return {
1077
+ restore: () => { (globalThis as any).fetch = original; },
1078
+ callCount: () => count,
1079
+ };
1080
+ }
1081
+
1082
+ // Round 11 helper (Finding Ur_J): simulate a degraded embedding backend that
1083
+ // returns a non-2xx HTTP status (e.g. 503 Service Unavailable) instead of
1084
+ // timing out. The fetch resolves successfully but with `ok=false`.
1085
+ function stubNon200Fetch(status: number): { restore: () => void; callCount: () => number } {
1086
+ const original = globalThis.fetch;
1087
+ let count = 0;
1088
+ (globalThis as any).fetch = async (_url: any, _init: any) => {
1089
+ count++;
1090
+ return new Response(JSON.stringify({ error: { message: "service unavailable" } }), {
1091
+ status,
1092
+ statusText: "Service Unavailable",
1093
+ headers: { "content-type": "application/json" },
1094
+ });
1095
+ };
1096
+ return {
1097
+ restore: () => { (globalThis as any).fetch = original; },
1098
+ callCount: () => count,
1099
+ };
1100
+ }
1101
+
1102
+ // Round 10 regression tests (Findings Ui1J + Ui1L):
1103
+ // search() without throwOnTimeout must return [] on timeout (recall-path contract).
1104
+ // search() with throwOnTimeout:true must throw EmbeddingTimeoutError (dedup-path contract).
1105
+ // Previously search() always propagated EmbeddingTimeoutError, which would
1106
+ // abort recall entirely when the embedding backend was slow.
1107
+
1108
+ test("round 10 Ui1J+Ui1L: search() without throwOnTimeout returns [] on timeout (recall-path fail-open)", async () => {
1109
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-timeout-recall-"));
1110
+ const { restore, callCount } = stubTimeoutFetch();
1111
+ process.env.REMNIC_EMBEDDING_FETCH_TIMEOUT_MS = "1";
1112
+ try {
1113
+ // Seed a non-empty index so search() actually attempts an embed call.
1114
+ await seedEmbeddingIndex(memoryDir, {
1115
+ "mem-t-001": { vector: [1, 0, 0, 0], path: "facts/t-001.md" },
1116
+ });
1117
+ const config = parseConfig({
1118
+ memoryDir,
1119
+ embeddingFallbackEnabled: true,
1120
+ embeddingFallbackProvider: "openai",
1121
+ openaiApiKey: "test-key",
1122
+ });
1123
+ const fallback = new EmbeddingFallback(config);
1124
+
1125
+ // Call WITHOUT throwOnTimeout — simulates the recall path.
1126
+ let threw = false;
1127
+ let result: Array<{ id: string; score: number; path: string }> = [];
1128
+ try {
1129
+ result = await fallback.search("synthetic query for timeout test", 5);
1130
+ } catch (_err) {
1131
+ threw = true;
1132
+ }
1133
+
1134
+ assert.ok(!threw, "search() must NOT throw when throwOnTimeout is omitted (recall-path fail-open)");
1135
+ assert.deepEqual(result, [], "search() must return [] on timeout when throwOnTimeout is false");
1136
+ assert.ok(callCount() >= 1, "fetch must have been called at least once");
1137
+ } finally {
1138
+ delete process.env.REMNIC_EMBEDDING_FETCH_TIMEOUT_MS;
1139
+ restore();
1140
+ await rm(memoryDir, { recursive: true, force: true });
1141
+ }
1142
+ });
1143
+
1144
+ test("round 10 Ui1J+Ui1L: search() with throwOnTimeout:true throws EmbeddingTimeoutError (dedup-path contract)", async () => {
1145
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-timeout-dedup-"));
1146
+ const { restore, callCount } = stubTimeoutFetch();
1147
+ process.env.REMNIC_EMBEDDING_FETCH_TIMEOUT_MS = "1";
1148
+ try {
1149
+ // Seed a non-empty index so search() actually attempts an embed call.
1150
+ await seedEmbeddingIndex(memoryDir, {
1151
+ "mem-t-001": { vector: [1, 0, 0, 0], path: "facts/t-001.md" },
1152
+ });
1153
+ const config = parseConfig({
1154
+ memoryDir,
1155
+ embeddingFallbackEnabled: true,
1156
+ embeddingFallbackProvider: "openai",
1157
+ openaiApiKey: "test-key",
1158
+ });
1159
+ const fallback = new EmbeddingFallback(config);
1160
+
1161
+ // Call WITH throwOnTimeout:true — simulates the semanticDedupLookup path.
1162
+ let threw: unknown;
1163
+ try {
1164
+ await fallback.search("synthetic query for timeout test", 5, { throwOnTimeout: true });
1165
+ } catch (err) {
1166
+ threw = err;
1167
+ }
1168
+
1169
+ assert.ok(threw instanceof EmbeddingTimeoutError,
1170
+ `search() must throw EmbeddingTimeoutError when throwOnTimeout=true; got ${threw}`);
1171
+ assert.ok(callCount() >= 1, "fetch must have been called at least once");
1172
+ } finally {
1173
+ delete process.env.REMNIC_EMBEDDING_FETCH_TIMEOUT_MS;
1174
+ restore();
1175
+ await rm(memoryDir, { recursive: true, force: true });
1176
+ }
1177
+ });
1178
+
1179
+ // Round 11 regression test (Finding Ur_J):
1180
+ // When the embedding backend returns a non-2xx HTTP status on the lookup
1181
+ // path, embed() previously returned null → search() yielded [] → caller
1182
+ // classified the result as "no_candidates" instead of "backend_unavailable".
1183
+ // In a degraded backend (repeated 429/5xx), every fact in a batch would pay
1184
+ // a full HTTP roundtrip instead of tripping the per-batch short-circuit.
1185
+ //
1186
+ // Round 11 fix: embed() now throws EmbeddingTimeoutError on lookup-path !res.ok
1187
+ // (the same path used for genuine timeouts), so search() with throwOnTimeout
1188
+ // propagates to decideSemanticDedup's backend_unavailable branch.
1189
+ test("round 11 Ur_J: search() with throwOnTimeout throws EmbeddingTimeoutError on non-2xx response", async () => {
1190
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-non200-dedup-"));
1191
+ const { restore, callCount } = stubNon200Fetch(503);
1192
+ try {
1193
+ await seedEmbeddingIndex(memoryDir, {
1194
+ "mem-u-001": { vector: [1, 0, 0, 0], path: "facts/u-001.md" },
1195
+ });
1196
+ const config = parseConfig({
1197
+ memoryDir,
1198
+ embeddingFallbackEnabled: true,
1199
+ embeddingFallbackProvider: "openai",
1200
+ openaiApiKey: "test-key",
1201
+ });
1202
+ const fallback = new EmbeddingFallback(config);
1203
+
1204
+ let threw: unknown;
1205
+ try {
1206
+ await fallback.search("synthetic query for non-2xx test", 5, { throwOnTimeout: true });
1207
+ } catch (err) {
1208
+ threw = err;
1209
+ }
1210
+
1211
+ assert.ok(threw instanceof EmbeddingTimeoutError,
1212
+ `search() must throw EmbeddingTimeoutError on non-2xx when throwOnTimeout=true; got ${threw}`);
1213
+ assert.ok(callCount() >= 1, "fetch must have been called at least once");
1214
+ } finally {
1215
+ restore();
1216
+ await rm(memoryDir, { recursive: true, force: true });
1217
+ }
1218
+ });
1219
+
1220
+ test("round 11 Ur_J: search() without throwOnTimeout returns [] on non-2xx response (recall-path fail-open)", async () => {
1221
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-non200-recall-"));
1222
+ const { restore, callCount } = stubNon200Fetch(503);
1223
+ try {
1224
+ await seedEmbeddingIndex(memoryDir, {
1225
+ "mem-u-001": { vector: [1, 0, 0, 0], path: "facts/u-001.md" },
1226
+ });
1227
+ const config = parseConfig({
1228
+ memoryDir,
1229
+ embeddingFallbackEnabled: true,
1230
+ embeddingFallbackProvider: "openai",
1231
+ openaiApiKey: "test-key",
1232
+ });
1233
+ const fallback = new EmbeddingFallback(config);
1234
+
1235
+ let threw = false;
1236
+ let result: Array<{ id: string; score: number; path: string }> = [];
1237
+ try {
1238
+ result = await fallback.search("synthetic query for non-2xx test", 5);
1239
+ } catch (_err) {
1240
+ threw = true;
1241
+ }
1242
+
1243
+ assert.ok(!threw, "search() must NOT throw on non-2xx when throwOnTimeout is omitted (recall-path fail-open)");
1244
+ assert.deepEqual(result, [], "search() must return [] on non-2xx when throwOnTimeout is false");
1245
+ assert.ok(callCount() >= 1, "fetch must have been called at least once");
1246
+ } finally {
1247
+ restore();
1248
+ await rm(memoryDir, { recursive: true, force: true });
1249
+ }
1250
+ });
1251
+
1252
+ test("semantic dedup timeout: batch short-circuits after first timeout", async () => {
1253
+ // This test mirrors UUI2 but uses the REAL EmbeddingFallback with a
1254
+ // timed-out fetch stub, exercising the full propagation path:
1255
+ // AbortError → EmbeddingTimeoutError (from embed) → re-thrown from search()
1256
+ // (because throwOnTimeout:true) → caught by decideSemanticDedup →
1257
+ // reason="backend_unavailable" → batchBackendUnavailable flag flips →
1258
+ // subsequent facts skip fetch.
1259
+ //
1260
+ // The lookup uses throwOnTimeout:true to mirror semanticDedupLookup's actual
1261
+ // call (Round 10 fix, Ui1J+Ui1L). Without the flag, search() would return []
1262
+ // and decideSemanticDedup would classify as no_candidates, never flipping
1263
+ // batchBackendUnavailable.
1264
+
1265
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-timeout-batch-"));
1266
+ const { restore, callCount } = stubTimeoutFetch();
1267
+ process.env.REMNIC_EMBEDDING_FETCH_TIMEOUT_MS = "1";
1268
+
1269
+ try {
1270
+ // Seed a non-empty index so search() proceeds past the early-return guard
1271
+ // (ids.length === 0) and actually calls embed().
1272
+ await seedEmbeddingIndex(memoryDir, {
1273
+ "mem-t-001": { vector: [1, 0, 0, 0], path: "facts/t-001.md" },
1274
+ "mem-t-002": { vector: [0, 1, 0, 0], path: "facts/t-002.md" },
1275
+ });
1276
+
1277
+ const config = parseConfig({
1278
+ memoryDir,
1279
+ embeddingFallbackEnabled: true,
1280
+ embeddingFallbackProvider: "openai",
1281
+ openaiApiKey: "test-key",
1282
+ });
1283
+ const fallback = new EmbeddingFallback(config);
1284
+
1285
+ // Build a lookup that uses the real fallback with throwOnTimeout:true,
1286
+ // mirroring how semanticDedupLookup actually calls search() after Round 10.
1287
+ const realLookup: SemanticDedupLookup = async (content, limit) =>
1288
+ fallback.search(content, limit, { throwOnTimeout: true }).then((hits) =>
1289
+ hits.map((h) => ({ id: h.id, score: h.score, path: h.path })),
1290
+ );
1291
+
1292
+ // Simulate the orchestrator's per-batch short-circuit for N=5 facts.
1293
+ const N = 5;
1294
+ let batchBackendUnavailable = false;
1295
+ const decisions: SemanticDedupDecision[] = [];
1296
+
1297
+ for (let i = 0; i < N; i++) {
1298
+ let semanticDecision: SemanticDedupDecision;
1299
+ if (batchBackendUnavailable) {
1300
+ // Short-circuit: skip the lookup, directly mark as backend_unavailable.
1301
+ semanticDecision = { action: "keep", reason: "backend_unavailable" };
1302
+ } else {
1303
+ semanticDecision = await decideSemanticDedup(
1304
+ `synthetic fact about preference number ${i}`,
1305
+ realLookup,
1306
+ DEFAULT_OPTS,
1307
+ );
1308
+ if (semanticDecision.reason === "backend_unavailable") {
1309
+ batchBackendUnavailable = true;
1310
+ }
1311
+ }
1312
+ decisions.push(semanticDecision);
1313
+ }
1314
+
1315
+ // Fetch must have been called at most 1 time (only for the first fact's
1316
+ // embed attempt). Facts 2-5 must have short-circuited via the flag.
1317
+ assert.ok(
1318
+ callCount() <= 1,
1319
+ `fetch must be called ≤1 time for the lookup phase; called ${callCount()} time(s)`,
1320
+ );
1321
+
1322
+ // All 5 facts must be kept (fail-open behaviour preserved even on timeout).
1323
+ assert.equal(decisions.length, N, "all N facts must produce a decision");
1324
+ for (const decision of decisions) {
1325
+ assert.equal(
1326
+ decision.action,
1327
+ "keep",
1328
+ "every fact must be kept (fail-open) when embedding times out",
1329
+ );
1330
+ assert.equal(
1331
+ decision.reason,
1332
+ "backend_unavailable",
1333
+ "timeout must propagate as backend_unavailable, not no_candidates",
1334
+ );
1335
+ }
1336
+
1337
+ // Explicitly verify the flag did flip (not just all the facts) — ensures
1338
+ // the first decision triggered the short-circuit path.
1339
+ assert.equal(
1340
+ batchBackendUnavailable,
1341
+ true,
1342
+ "batchBackendUnavailable flag must have been set by the first timeout",
1343
+ );
1344
+ } finally {
1345
+ delete process.env.REMNIC_EMBEDDING_FETCH_TIMEOUT_MS;
1346
+ restore();
1347
+ await rm(memoryDir, { recursive: true, force: true });
1348
+ }
1349
+ });
1350
+
1351
+ // ── P2: contradictionAutoResolve=false must not silently drop contradictory facts ─
1352
+ //
1353
+ // Regression for PR #399 review thread PRRT_kwDORJXyws56UxS0:
1354
+ // When contradictionAutoResolve=false, checkForContradiction() previously returned
1355
+ // null even when a contradiction was confirmed (the supersede path was skipped and
1356
+ // the return happened only inside the auto-resolve block). The caller therefore saw
1357
+ // no contradiction, leaving `supersedes` unset, so the semantic-skip guard fired
1358
+ // and silently dropped the contradictory fact. The fix moves the return outside the
1359
+ // auto-resolve block and uses a separate `contradictionDetected` flag in the guard.
1360
+ //
1361
+ // This test models the orchestrator gate logic at the pure layer (like UUI1 above)
1362
+ // and covers three scenarios:
1363
+ // 1. autoResolve=true, contradiction detected → gate must NOT fire (baseline)
1364
+ // 2. autoResolve=false, contradiction detected → gate must NOT fire (the bug)
1365
+ // 3. autoResolve=false, no contradiction → gate MUST fire (true dedup)
1366
+
1367
+ test("P2: semantic-skip gate does not fire when contradiction detected with autoResolve=false", async () => {
1368
+ // All three scenarios share a high-similarity semantic decision.
1369
+ const semanticDecision = await decideSemanticDedup(
1370
+ "the user now prefers light mode",
1371
+ makeLookup([{ id: "pref-old-001", score: 0.96 }]),
1372
+ DEFAULT_OPTS,
1373
+ );
1374
+ assert.equal(
1375
+ semanticDecision.action,
1376
+ "skip",
1377
+ "precondition: semantic decision must be skip for high-similarity hit",
1378
+ );
1379
+ const pendingSkip = semanticDecision.action === "skip" ? semanticDecision : null;
1380
+
1381
+ // Scenario 1: autoResolve=true, contradiction detected → supersedes is set.
1382
+ // Gate condition: pendingSkip && !contradictionDetected && !isCorrection
1383
+ {
1384
+ const contradictionDetected = true; // checkForContradiction returned non-null
1385
+ const isCorrection = false;
1386
+ const gateFires = pendingSkip !== null && !contradictionDetected && !isCorrection;
1387
+ assert.equal(
1388
+ gateFires,
1389
+ false,
1390
+ "scenario 1 (autoResolve=true, contradiction): gate must NOT fire — contradictory update must be written",
1391
+ );
1392
+ }
1393
+
1394
+ // Scenario 2 (the regression): autoResolve=false, contradiction detected.
1395
+ // Before the fix checkForContradiction() returned null → contradictionDetected=false
1396
+ // → gate fired → fact was silently dropped. After the fix contradictionDetected=true.
1397
+ {
1398
+ const contradictionDetected = true; // fixed: checkForContradiction now returns non-null
1399
+ const isCorrection = false;
1400
+ const gateFires = pendingSkip !== null && !contradictionDetected && !isCorrection;
1401
+ assert.equal(
1402
+ gateFires,
1403
+ false,
1404
+ "scenario 2 (autoResolve=false, contradiction): gate must NOT fire — contradictory fact must be preserved for manual review",
1405
+ );
1406
+ }
1407
+
1408
+ // Scenario 3: autoResolve=false, no contradiction (genuine near-duplicate).
1409
+ // The gate must still suppress the write.
1410
+ {
1411
+ const contradictionDetected = false; // no contradiction: true near-duplicate
1412
+ const isCorrection = false;
1413
+ const gateFires = pendingSkip !== null && !contradictionDetected && !isCorrection;
1414
+ assert.equal(
1415
+ gateFires,
1416
+ true,
1417
+ "scenario 3 (autoResolve=false, no contradiction): gate MUST fire — genuine near-duplicate must be deduplicated",
1418
+ );
1419
+ }
1420
+ });
1421
+
1422
+ // ── Round 12 regression tests ──────────────────────────────────────────────────
1423
+ //
1424
+ // Fix #1 (thread PRRT_kwDORJXyws56U6Gi): non-timeout transport failures on the
1425
+ // LOOKUP path must throw EmbeddingTimeoutError so decideSemanticDedup can
1426
+ // classify them as backend_unavailable and activate the per-batch short-circuit.
1427
+ //
1428
+ // Fix #2 (thread PRRT_kwDORJXyws56U6Gj): when targetStorage.dir is outside
1429
+ // memoryDir, the semantic dedup scope must NOT be {} — it must use the absolute
1430
+ // storageDir as pathPrefix so cross-tenant suppression cannot occur.
1431
+
1432
+ test("round 12 fix #1: non-timeout fetch error on lookup path throws EmbeddingTimeoutError", async () => {
1433
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-r12-transport-"));
1434
+ const original = globalThis.fetch;
1435
+ try {
1436
+ // Seed a non-empty index so search() reaches the embed() call.
1437
+ await seedEmbeddingIndex(memoryDir, {
1438
+ "mem-001": { vector: [1, 0, 0, 0], path: "facts/f-001.md" },
1439
+ });
1440
+
1441
+ // Stub fetch to throw a transport error (ECONNREFUSED / DNS failure).
1442
+ const transportErr = Object.assign(new TypeError("fetch failed"), {
1443
+ cause: Object.assign(new Error("connect ECONNREFUSED 127.0.0.1:11434"), { code: "ECONNREFUSED" }),
1444
+ });
1445
+ (globalThis as any).fetch = async () => { throw transportErr; };
1446
+
1447
+ const config = parseConfig({
1448
+ memoryDir,
1449
+ embeddingFallbackEnabled: true,
1450
+ embeddingFallbackProvider: "openai",
1451
+ openaiApiKey: "test-key",
1452
+ });
1453
+ const fallback = new EmbeddingFallback(config);
1454
+
1455
+ // search() with throwOnTimeout:true must re-throw EmbeddingTimeoutError
1456
+ // (not return []) when fetch throws a non-timeout transport error.
1457
+ await assert.rejects(
1458
+ () => fallback.search("query text", 5, { throwOnTimeout: true }),
1459
+ EmbeddingTimeoutError,
1460
+ "non-timeout transport failure on lookup path must throw EmbeddingTimeoutError",
1461
+ );
1462
+
1463
+ // Without throwOnTimeout (recall path), search() must still fail open.
1464
+ // No throw — returns [].
1465
+ const result = await fallback.search("query text", 5);
1466
+ assert.deepEqual(result, [], "recall path must fail open (return []) on transport error");
1467
+
1468
+ // End-to-end: a transport failure must be classified as backend_unavailable,
1469
+ // NOT no_candidates.
1470
+ const decision = await decideSemanticDedup(
1471
+ "some fact content",
1472
+ async (_content, limit) => {
1473
+ await fallback.search(_content, limit, { throwOnTimeout: true });
1474
+ return [];
1475
+ },
1476
+ DEFAULT_OPTS,
1477
+ );
1478
+ assert.equal(decision.action, "keep");
1479
+ assert.equal(
1480
+ decision.reason,
1481
+ "backend_unavailable",
1482
+ "transport error must yield backend_unavailable so batchBackendUnavailable short-circuit activates",
1483
+ );
1484
+ } finally {
1485
+ (globalThis as any).fetch = original;
1486
+ await rm(memoryDir, { recursive: true, force: true });
1487
+ }
1488
+ });
1489
+
1490
+ test("round 12 fix #2: external storage dir scopes lookup to absolute path prefix", async () => {
1491
+ const memoryDir = await mkdtemp(join(tmpdir(), "remnic-r12-scope-mem-"));
1492
+ const externalDir = await mkdtemp(join(tmpdir(), "remnic-r12-scope-ext-"));
1493
+ const original = globalThis.fetch;
1494
+ try {
1495
+ const vec = [1, 0, 0, 0];
1496
+
1497
+ // Seed the index with:
1498
+ // - One entry whose path is inside memoryDir (relative path, as normal).
1499
+ // - One entry whose path is in externalDir (absolute path, as
1500
+ // toMemoryRelativePath() produces for outside-memoryDir files).
1501
+ await seedEmbeddingIndex(memoryDir, {
1502
+ "mem-internal": {
1503
+ vector: vec,
1504
+ path: "facts/internal.md",
1505
+ },
1506
+ "mem-external": {
1507
+ vector: vec,
1508
+ // toMemoryRelativePath returns the absolute path when outside memoryDir.
1509
+ path: join(externalDir, "facts", "external.md").replace(/\\/g, "/"),
1510
+ },
1511
+ });
1512
+
1513
+ // Stub fetch to return the same vector for any query.
1514
+ (globalThis as any).fetch = async () =>
1515
+ new Response(
1516
+ JSON.stringify({ data: [{ embedding: vec }] }),
1517
+ { status: 200, headers: { "Content-Type": "application/json" } },
1518
+ );
1519
+
1520
+ const config = parseConfig({
1521
+ memoryDir,
1522
+ namespacesEnabled: true,
1523
+ embeddingFallbackEnabled: true,
1524
+ embeddingFallbackProvider: "openai",
1525
+ openaiApiKey: "test-key",
1526
+ });
1527
+ const fallback = new EmbeddingFallback(config);
1528
+
1529
+ // Unscoped search must return both entries.
1530
+ const unscoped = await fallback.search("test query", 10);
1531
+ assert.equal(unscoped.length, 2, "unscoped search must return both entries");
1532
+
1533
+ // Scoped to externalDir (absolute prefix): only the external entry must match.
1534
+ const extNorm = externalDir.replace(/\\/g, "/");
1535
+ const extPrefix = extNorm.endsWith("/") ? extNorm : `${extNorm}/`;
1536
+ const externalHits = await fallback.search("test query", 10, { pathPrefix: extPrefix });
1537
+ assert.equal(externalHits.length, 1, "absolute-prefix search must return only the external entry");
1538
+ assert.equal(externalHits[0]?.id, "mem-external");
1539
+
1540
+ // Scoped to a DIFFERENT absolute prefix must return nothing — confirming
1541
+ // that the external entry is not visible in the wrong tenant's scope.
1542
+ const otherDir = join(tmpdir(), "remnic-r12-other-").replace(/\\/g, "/") + "/";
1543
+ const otherHits = await fallback.search("test query", 10, { pathPrefix: otherDir });
1544
+ assert.equal(
1545
+ otherHits.length,
1546
+ 0,
1547
+ "external entry must not appear under a different tenant's absolute prefix",
1548
+ );
1549
+
1550
+ // This is the core invariant: without the round 12 fix, semanticDedupScopeFor()
1551
+ // returned {} for externalDir, letting the high-similarity internal entry
1552
+ // suppress writes destined for externalDir. Confirm that scoping by the
1553
+ // absolute external prefix isolates the lookup correctly.
1554
+ const decision = await decideSemanticDedup(
1555
+ "test query",
1556
+ async (content, limit) => {
1557
+ const hits = await fallback.search(content, limit, { pathPrefix: extPrefix });
1558
+ return hits.map((h) => ({ id: h.id, score: h.score, path: h.path }));
1559
+ },
1560
+ DEFAULT_OPTS,
1561
+ );
1562
+ // The external entry has cosine similarity ≈ 1 (same vector) → skip.
1563
+ // The internal entry must NOT influence this decision.
1564
+ assert.equal(decision.action, "skip");
1565
+ if (decision.action === "skip") {
1566
+ assert.equal(
1567
+ decision.topId,
1568
+ "mem-external",
1569
+ "dedup lookup scoped to external dir must only find the external entry as the near-duplicate",
1570
+ );
1571
+ }
1572
+ } finally {
1573
+ (globalThis as any).fetch = original;
1574
+ await rm(memoryDir, { recursive: true, force: true });
1575
+ await rm(externalDir, { recursive: true, force: true });
1576
+ }
1577
+ });