@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.
- package/dist/access-cli.d.ts +2 -1
- package/dist/access-cli.js +263 -82
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +26 -60
- package/dist/access-http.js +43 -29
- package/dist/access-mcp.d.ts +24 -6
- package/dist/access-mcp.js +35 -28
- package/dist/access-schema.d.ts +9 -6
- package/dist/access-schema.js +7 -5
- package/dist/access-service-DcCDmNYC.d.ts +1542 -0
- package/dist/access-service.d.ts +25 -7
- package/dist/access-service.js +33 -26
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.js +11 -3
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/claude-code.d.ts +24 -0
- package/dist/adapters/claude-code.js +9 -0
- package/dist/adapters/codex.d.ts +25 -0
- package/dist/adapters/codex.js +9 -0
- package/dist/adapters/hermes.d.ts +35 -0
- package/dist/adapters/hermes.js +9 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +26 -0
- package/dist/adapters/registry.d.ts +20 -0
- package/dist/adapters/registry.js +13 -0
- package/dist/adapters/replit.d.ts +28 -0
- package/dist/adapters/replit.js +9 -0
- package/dist/adapters/types.d.ts +43 -0
- package/dist/adapters/types.js +8 -0
- package/dist/bootstrap.d.ts +20 -5
- package/dist/boxes.d.ts +7 -0
- package/dist/boxes.js +1 -1
- package/dist/briefing.d.ts +5 -3
- package/dist/briefing.js +9 -6
- package/dist/buffer-surprise-report.js +1 -1
- package/dist/buffer.d.ts +18 -4
- package/dist/buffer.js +1 -1
- package/dist/calibration.js +4 -4
- package/dist/capsule-cli.d.ts +4 -4
- package/dist/capsule-cli.js +1 -1
- package/dist/capsule-crypto-5CYAGVC5.js +18 -0
- package/dist/capsule-merge-4MGKE7C5.js +189 -0
- package/dist/causal-behavior.d.ts +8 -28
- package/dist/causal-behavior.js +6 -3
- package/dist/causal-behavior.js.map +1 -1
- package/dist/causal-chain.js +3 -2
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +24 -13
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-retrieval.js +3 -3
- package/dist/causal-trajectory.js +1 -1
- package/dist/chunk-25MQ7IHJ.js +427 -0
- package/dist/chunk-25MQ7IHJ.js.map +1 -0
- package/dist/chunk-2F2W355T.js +256 -0
- package/dist/chunk-2F2W355T.js.map +1 -0
- package/dist/chunk-2KI4QFHU.js +228 -0
- package/dist/chunk-2KI4QFHU.js.map +1 -0
- package/dist/chunk-2PRQG7PV.js +86 -0
- package/dist/chunk-2PRQG7PV.js.map +1 -0
- package/dist/chunk-2QR3XXIC.js +2272 -0
- package/dist/chunk-2QR3XXIC.js.map +1 -0
- package/dist/chunk-2WWLHTZY.js +121 -0
- package/dist/chunk-326G7DJK.js +2185 -0
- package/dist/chunk-326G7DJK.js.map +1 -0
- package/dist/chunk-34DQE4KF.js +174 -0
- package/dist/chunk-34DQE4KF.js.map +1 -0
- package/dist/chunk-3APJ5EVB.js +601 -0
- package/dist/chunk-3APJ5EVB.js.map +1 -0
- package/dist/chunk-3HPAPHUK.js +51 -0
- package/dist/chunk-3HPAPHUK.js.map +1 -0
- package/dist/chunk-3JXBXXM2.js +69 -0
- package/dist/chunk-3JXBXXM2.js.map +1 -0
- package/dist/chunk-3KW65B36.js +681 -0
- package/dist/chunk-3KW65B36.js.map +1 -0
- package/dist/chunk-3UXOZBHV.js +20 -0
- package/dist/chunk-3UXOZBHV.js.map +1 -0
- package/dist/chunk-3VAL7ZL2.js +266 -0
- package/dist/chunk-3VAL7ZL2.js.map +1 -0
- package/dist/chunk-3Y4P7RXM.js +31 -0
- package/dist/chunk-3Y4P7RXM.js.map +1 -0
- package/dist/chunk-47VWKCAF.js +273 -0
- package/dist/chunk-47VWKCAF.js.map +1 -0
- package/dist/chunk-4CRG46BG.js +271 -0
- package/dist/chunk-5375UYTQ.js +914 -0
- package/dist/chunk-5375UYTQ.js.map +1 -0
- package/dist/chunk-56K5QLHX.js +506 -0
- package/dist/chunk-56K5QLHX.js.map +1 -0
- package/dist/chunk-5RGLBDQF.js +596 -0
- package/dist/chunk-5RGLBDQF.js.map +1 -0
- package/dist/chunk-5UZXUTVO.js +9 -0
- package/dist/chunk-5UZXUTVO.js.map +1 -0
- package/dist/chunk-65PG43EQ.js +105 -0
- package/dist/chunk-65PG43EQ.js.map +1 -0
- package/dist/chunk-66DHUKLO.js +57 -0
- package/dist/chunk-66DHUKLO.js.map +1 -0
- package/dist/chunk-6FC5EGNV.js +46 -0
- package/dist/chunk-6FC5EGNV.js.map +1 -0
- package/dist/chunk-6H2TESSP.js +62 -0
- package/dist/chunk-6H2TESSP.js.map +1 -0
- package/dist/chunk-6LVVDPJ4.js +32 -0
- package/dist/chunk-6LVVDPJ4.js.map +1 -0
- package/dist/chunk-6RVI47ZR.js +159 -0
- package/dist/chunk-6RVI47ZR.js.map +1 -0
- package/dist/chunk-7AAT6G4Q.js +5117 -0
- package/dist/chunk-7AAT6G4Q.js.map +1 -0
- package/dist/chunk-7DTASS5T.js +29 -0
- package/dist/chunk-7DTASS5T.js.map +1 -0
- package/dist/chunk-7IASACLB.js +596 -0
- package/dist/chunk-7MNMYOFP.js +32 -0
- package/dist/chunk-7MNMYOFP.js.map +1 -0
- package/dist/chunk-7N4KAIGN.js +133 -0
- package/dist/chunk-7N4KAIGN.js.map +1 -0
- package/dist/chunk-7OZ53EXP.js +101 -0
- package/dist/chunk-7OZ53EXP.js.map +1 -0
- package/dist/chunk-7XYTQGCC.js +134 -0
- package/dist/chunk-7XYTQGCC.js.map +1 -0
- package/dist/chunk-A2XUIMJ3.js +341 -0
- package/dist/chunk-A2XUIMJ3.js.map +1 -0
- package/dist/chunk-AGZQD76C.js +201 -0
- package/dist/chunk-AGZQD76C.js.map +1 -0
- package/dist/chunk-APO3DCMU.js +361 -0
- package/dist/chunk-APO3DCMU.js.map +1 -0
- package/dist/chunk-BFBF3XEF.js +283 -0
- package/dist/chunk-BFBF3XEF.js.map +1 -0
- package/dist/chunk-BJ3KMYTB.js +1974 -0
- package/dist/chunk-BJ3KMYTB.js.map +1 -0
- package/dist/chunk-CHEL3SKB.js +6758 -0
- package/dist/chunk-CHEL3SKB.js.map +1 -0
- package/dist/chunk-CQZRLNMV.js +1491 -0
- package/dist/chunk-CQZRLNMV.js.map +1 -0
- package/dist/chunk-D46YSIYX.js +892 -0
- package/dist/chunk-D46YSIYX.js.map +1 -0
- package/dist/chunk-DINWEURR.js +648 -0
- package/dist/chunk-DINWEURR.js.map +1 -0
- package/dist/chunk-DK5LDEQM.js +530 -0
- package/dist/chunk-DK5LDEQM.js.map +1 -0
- package/dist/chunk-DOM4GKSW.js +34 -0
- package/dist/chunk-DOM4GKSW.js.map +1 -0
- package/dist/chunk-EDTHC6UD.js +1075 -0
- package/dist/chunk-EFJ3MQ4V.js +721 -0
- package/dist/chunk-EHRTFRWW.js +89 -0
- package/dist/chunk-EHRTFRWW.js.map +1 -0
- package/dist/chunk-FAJ7FZYM.js +11 -0
- package/dist/chunk-FAJ7FZYM.js.map +1 -0
- package/dist/chunk-FBYESMQ2.js +570 -0
- package/dist/chunk-FDU6HUUL.js +147 -0
- package/dist/chunk-FF4KLI5W.js +99 -0
- package/dist/chunk-FF4KLI5W.js.map +1 -0
- package/dist/chunk-FIT6DMX6.js +310 -0
- package/dist/chunk-FIT6DMX6.js.map +1 -0
- package/dist/chunk-FJ43PRLT.js +272 -0
- package/dist/chunk-FJ43PRLT.js.map +1 -0
- package/dist/chunk-FKFMOY3N.js +32 -0
- package/dist/chunk-FKFMOY3N.js.map +1 -0
- package/dist/chunk-FLTNHQK6.js +262 -0
- package/dist/chunk-FLTNHQK6.js.map +1 -0
- package/dist/chunk-GA454ALV.js +12436 -0
- package/dist/chunk-GA454ALV.js.map +1 -0
- package/dist/chunk-GGKRUQOO.js +228 -0
- package/dist/chunk-GIF42EW3.js +63 -0
- package/dist/chunk-GIF42EW3.js.map +1 -0
- package/dist/chunk-GL6I6MEQ.js +647 -0
- package/dist/chunk-H3ME6L6D.js +709 -0
- package/dist/chunk-H3ME6L6D.js.map +1 -0
- package/dist/chunk-HHLLAQGZ.js +1 -0
- package/dist/chunk-HXXBL2KD.js +2040 -0
- package/dist/chunk-I5V2VDIW.js +219 -0
- package/dist/chunk-I5V2VDIW.js.map +1 -0
- package/dist/chunk-I6K5FBRQ.js +35 -0
- package/dist/chunk-I6K5FBRQ.js.map +1 -0
- package/dist/chunk-ICRIXAP2.js +121 -0
- package/dist/chunk-ICRIXAP2.js.map +1 -0
- package/dist/chunk-J4EB7DNW.js +11 -0
- package/dist/chunk-J4EB7DNW.js.map +1 -0
- package/dist/chunk-JLFA7DQG.js +62 -0
- package/dist/chunk-JLFA7DQG.js.map +1 -0
- package/dist/chunk-KJTKLXTH.js +9 -0
- package/dist/chunk-KJTKLXTH.js.map +1 -0
- package/dist/chunk-KLAO5DGL.js +917 -0
- package/dist/chunk-KLAO5DGL.js.map +1 -0
- package/dist/chunk-KNKUID7G.js +183 -0
- package/dist/chunk-KOSORCJG.js +624 -0
- package/dist/chunk-KOSORCJG.js.map +1 -0
- package/dist/chunk-KUJVMMZQ.js +1262 -0
- package/dist/chunk-KUJVMMZQ.js.map +1 -0
- package/dist/chunk-LCR46JY5.js +123 -0
- package/dist/chunk-LCR46JY5.js.map +1 -0
- package/dist/chunk-LLQ2LLWF.js +148 -0
- package/dist/chunk-LLQ2LLWF.js.map +1 -0
- package/dist/chunk-LPMVBPA3.js +236 -0
- package/dist/chunk-LT3NLYSI.js +50 -0
- package/dist/chunk-LT3NLYSI.js.map +1 -0
- package/dist/chunk-LUDTDZLK.js +287 -0
- package/dist/chunk-LUDTDZLK.js.map +1 -0
- package/dist/chunk-M23FSH32.js +3963 -0
- package/dist/chunk-M23FSH32.js.map +1 -0
- package/dist/chunk-MC26UJIM.js +118 -0
- package/dist/chunk-ME6ESPZU.js +119 -0
- package/dist/chunk-ME6ESPZU.js.map +1 -0
- package/dist/chunk-MGKYQQYF.js +272 -0
- package/dist/chunk-MJFNCJXV.js +66 -0
- package/dist/chunk-MJFNCJXV.js.map +1 -0
- package/dist/chunk-MSWG7JI6.js +237 -0
- package/dist/chunk-MSWG7JI6.js.map +1 -0
- package/dist/chunk-MT25YHYH.js +141 -0
- package/dist/chunk-MT25YHYH.js.map +1 -0
- package/dist/chunk-MT4HVDUZ.js +53 -0
- package/dist/chunk-MY6TPVXW.js +219 -0
- package/dist/chunk-N2D6GXBM.js +267 -0
- package/dist/chunk-N2D6GXBM.js.map +1 -0
- package/dist/chunk-NJ3MJQZX.js +46 -0
- package/dist/chunk-NJ3MJQZX.js.map +1 -0
- package/dist/chunk-NMZY542O.js +335 -0
- package/dist/chunk-NMZY542O.js.map +1 -0
- package/dist/chunk-NNVTUXEB.js +23 -0
- package/dist/chunk-NZL6GGQE.js +375 -0
- package/dist/chunk-NZL6GGQE.js.map +1 -0
- package/dist/chunk-P4NEIHUT.js +108 -0
- package/dist/chunk-P7FMDTKL.js +103 -0
- package/dist/chunk-P7FMDTKL.js.map +1 -0
- package/dist/chunk-PHK3HARR.js +32 -0
- package/dist/chunk-PHK3HARR.js.map +1 -0
- package/dist/chunk-PIRJPV5T.js +98 -0
- package/dist/chunk-PIRJPV5T.js.map +1 -0
- package/dist/chunk-PK7H5L6Y.js +159 -0
- package/dist/chunk-PK7H5L6Y.js.map +1 -0
- package/dist/chunk-PR5FBTFU.js +233 -0
- package/dist/chunk-PR5FBTFU.js.map +1 -0
- package/dist/chunk-PU63GXWS.js +174 -0
- package/dist/chunk-PU63GXWS.js.map +1 -0
- package/dist/chunk-PZIAX57I.js +124 -0
- package/dist/chunk-PZIAX57I.js.map +1 -0
- package/dist/chunk-Q7P4WJDP.js +26 -0
- package/dist/chunk-Q7P4WJDP.js.map +1 -0
- package/dist/chunk-QQUAB63I.js +63 -0
- package/dist/chunk-QQUAB63I.js.map +1 -0
- package/dist/chunk-QRNI5JBH.js +18 -0
- package/dist/chunk-RHY3HH7P.js +601 -0
- package/dist/chunk-RHY3HH7P.js.map +1 -0
- package/dist/chunk-RRF5UOBJ.js +91 -0
- package/dist/chunk-RXDLTSWT.js +124 -0
- package/dist/chunk-RXDLTSWT.js.map +1 -0
- package/dist/chunk-RYED3SPJ.js +42 -0
- package/dist/chunk-RYED3SPJ.js.map +1 -0
- package/dist/chunk-S7KDBTWT.js +106 -0
- package/dist/chunk-S7KDBTWT.js.map +1 -0
- package/dist/chunk-SEDEKFYQ.js +1 -0
- package/dist/chunk-TECVW3JP.js +36 -0
- package/dist/chunk-TECVW3JP.js.map +1 -0
- package/dist/chunk-TFO23QT4.js +88 -0
- package/dist/chunk-TFO23QT4.js.map +1 -0
- package/dist/chunk-TK4UEOSK.js +76 -0
- package/dist/chunk-TK4UEOSK.js.map +1 -0
- package/dist/chunk-TKWGAOLV.js +122 -0
- package/dist/chunk-TKWGAOLV.js.map +1 -0
- package/dist/chunk-TMM4S4IJ.js +597 -0
- package/dist/chunk-TMM4S4IJ.js.map +1 -0
- package/dist/chunk-TMQLARTH.js +188 -0
- package/dist/chunk-TMQLARTH.js.map +1 -0
- package/dist/chunk-TPDBFYEG.js +130 -0
- package/dist/chunk-TPDBFYEG.js.map +1 -0
- package/dist/chunk-TPMQ3G6Z.js +145 -0
- package/dist/chunk-TPMQ3G6Z.js.map +1 -0
- package/dist/chunk-TZOLIGIG.js +61 -0
- package/dist/chunk-TZOLIGIG.js.map +1 -0
- package/dist/chunk-U3PN77QT.js +113 -0
- package/dist/chunk-U3WSW6PZ.js +277 -0
- package/dist/chunk-U4SCL7B7.js +640 -0
- package/dist/chunk-U4SCL7B7.js.map +1 -0
- package/dist/chunk-UWK5OXUJ.js +156 -0
- package/dist/chunk-UWK5OXUJ.js.map +1 -0
- package/dist/chunk-UWVJF25J.js +74 -0
- package/dist/chunk-UXHQAFNA.js +1317 -0
- package/dist/chunk-UXHQAFNA.js.map +1 -0
- package/dist/chunk-V5OCT34X.js +1 -0
- package/dist/chunk-VLXA6PI2.js +304 -0
- package/dist/chunk-VLXA6PI2.js.map +1 -0
- package/dist/chunk-VNO6ZJ35.js +500 -0
- package/dist/chunk-VNO6ZJ35.js.map +1 -0
- package/dist/chunk-VW676BEI.js +827 -0
- package/dist/chunk-VW676BEI.js.map +1 -0
- package/dist/chunk-W3LR522O.js +2296 -0
- package/dist/chunk-W4L6CZKA.js +96 -0
- package/dist/chunk-W4L6CZKA.js.map +1 -0
- package/dist/chunk-W4RVMTHR.js +372 -0
- package/dist/chunk-W4RVMTHR.js.map +1 -0
- package/dist/chunk-WEHSQBFR.js +188 -0
- package/dist/chunk-WEHSQBFR.js.map +1 -0
- package/dist/chunk-WELDCG6C.js +380 -0
- package/dist/chunk-WELDCG6C.js.map +1 -0
- package/dist/chunk-WZYKANL3.js +2800 -0
- package/dist/chunk-WZYKANL3.js.map +1 -0
- package/dist/chunk-XIG5PDM7.js +48 -0
- package/dist/chunk-XJNBEDFE.js +193 -0
- package/dist/chunk-XJNBEDFE.js.map +1 -0
- package/dist/chunk-XVVIG67A.js +291 -0
- package/dist/chunk-XVVIG67A.js.map +1 -0
- package/dist/chunk-XVZ7B3HG.js +135 -0
- package/dist/chunk-YBPYIAA5.js +73 -0
- package/dist/chunk-YBPYIAA5.js.map +1 -0
- package/dist/chunk-Z734BLO3.js +21 -0
- package/dist/chunk-Z734BLO3.js.map +1 -0
- package/dist/chunk-ZKSK55RC.js +269 -0
- package/dist/chunk-ZKSK55RC.js.map +1 -0
- package/dist/chunk-ZTFCYYEZ.js +69 -0
- package/dist/chunk-ZTFCYYEZ.js.map +1 -0
- package/dist/chunk-ZY2MNJR6.js +329 -0
- package/dist/chunk-ZY2MNJR6.js.map +1 -0
- package/dist/cli-D3VpkVwB.d.ts +1136 -0
- package/dist/cli.d.ts +39 -10
- package/dist/cli.js +108 -49
- package/dist/commitment-ledger.js +1 -1
- package/dist/compat/checks.d.ts +5 -0
- package/dist/compat/checks.js +11 -0
- package/dist/compat/checks.js.map +1 -0
- package/dist/compat/types.d.ts +30 -0
- package/dist/compat/types.js +1 -0
- package/dist/compat/types.js.map +1 -0
- package/dist/compounding/engine.d.ts +221 -0
- package/dist/compounding/engine.js +32 -0
- package/dist/compounding/engine.js.map +1 -0
- package/dist/compounding/preference-consolidator.d.ts +92 -0
- package/dist/compounding/preference-consolidator.js +553 -0
- package/dist/compounding/preference-consolidator.js.map +1 -0
- package/dist/config.d.ts +4 -2
- package/dist/config.js +9 -4
- package/dist/conflict-policy-DyJ2wd-h.d.ts +4 -0
- package/dist/connectors/codex-materialize-runner.d.ts +64 -0
- package/dist/connectors/codex-materialize-runner.js +33 -0
- package/dist/connectors/codex-materialize-runner.js.map +1 -0
- package/dist/connectors/codex-materialize.d.ts +195 -0
- package/dist/connectors/codex-materialize.js +38 -0
- package/dist/connectors/codex-materialize.js.map +1 -0
- package/dist/connectors/index.d.ts +444 -0
- package/dist/connectors/index.js +115 -0
- package/dist/connectors/index.js.map +1 -0
- package/dist/connectors-cli-CwbyjGR7.d.ts +257 -0
- package/dist/connectors-cli.d.ts +1 -1
- package/dist/consolidation-provenance-check.d.ts +3 -1
- package/dist/consolidation-undo.d.ts +3 -1
- package/dist/contradiction/index.d.ts +258 -0
- package/dist/contradiction/index.js +43 -0
- package/dist/contradiction/index.js.map +1 -0
- package/dist/contradiction-review-ATP4S6IC.js +30 -0
- package/dist/contradiction-review-ATP4S6IC.js.map +1 -0
- package/dist/contradiction-scan-5A4IDZV5.js +13 -0
- package/dist/contradiction-scan-5A4IDZV5.js.map +1 -0
- package/dist/conversation-index/backend.d.ts +97 -0
- package/dist/conversation-index/backend.js +13 -0
- package/dist/conversation-index/backend.js.map +1 -0
- package/dist/conversation-index/chunker.d.ts +16 -0
- package/dist/conversation-index/chunker.js +8 -0
- package/dist/conversation-index/chunker.js.map +1 -0
- package/dist/conversation-index/cleanup.d.ts +11 -0
- package/dist/conversation-index/cleanup.js +9 -0
- package/dist/conversation-index/cleanup.js.map +1 -0
- package/dist/conversation-index/faiss-adapter.d.ts +6 -0
- package/dist/conversation-index/faiss-adapter.js +16 -0
- package/dist/conversation-index/faiss-adapter.js.map +1 -0
- package/dist/conversation-index/indexer.d.ts +23 -0
- package/dist/conversation-index/indexer.js +15 -0
- package/dist/conversation-index/indexer.js.map +1 -0
- package/dist/conversation-index/search.d.ts +6 -0
- package/dist/conversation-index/search.js +11 -0
- package/dist/conversation-index/search.js.map +1 -0
- package/dist/embedding-fallback.js +2 -2
- package/dist/enrichment/index.d.ts +163 -0
- package/dist/enrichment/index.js +18 -0
- package/dist/enrichment/index.js.map +1 -0
- package/dist/entity-retrieval.d.ts +4 -2
- package/dist/entity-retrieval.js +8 -5
- package/dist/evals.js +1 -1
- package/dist/explicit-capture.d.ts +20 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/extraction-judge-training.js +1 -1
- package/dist/extraction.js +8 -8
- package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
- package/dist/fallback-llm.d.ts +2 -0
- package/dist/fallback-llm.js +4 -4
- package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
- package/dist/index.d.ts +66 -711
- package/dist/index.js +556 -2680
- package/dist/index.js.map +1 -1
- package/dist/lcm/archive.d.ts +89 -0
- package/dist/lcm/archive.js +12 -0
- package/dist/lcm/archive.js.map +1 -0
- package/dist/lcm/dag.d.ts +48 -0
- package/dist/lcm/dag.js +8 -0
- package/dist/lcm/dag.js.map +1 -0
- package/dist/lcm/engine.d.ts +116 -0
- package/dist/lcm/engine.js +20 -0
- package/dist/lcm/engine.js.map +1 -0
- package/dist/lcm/index.d.ts +12 -0
- package/dist/lcm/index.js +44 -0
- package/dist/lcm/index.js.map +1 -0
- package/dist/lcm/queue.d.ts +62 -0
- package/dist/lcm/queue.js +8 -0
- package/dist/lcm/queue.js.map +1 -0
- package/dist/lcm/recall.d.ts +20 -0
- package/dist/lcm/recall.js +8 -0
- package/dist/lcm/recall.js.map +1 -0
- package/dist/lcm/schema.d.ts +16 -0
- package/dist/lcm/schema.js +14 -0
- package/dist/lcm/schema.js.map +1 -0
- package/dist/lcm/summarizer.d.ts +38 -0
- package/dist/lcm/summarizer.js +12 -0
- package/dist/lcm/summarizer.js.map +1 -0
- package/dist/lcm/tools.d.ts +29 -0
- package/dist/lcm/tools.js +8 -0
- package/dist/lcm/tools.js.map +1 -0
- package/dist/live-connectors-runner.js +5 -5
- package/dist/local-llm.js +3 -3
- package/dist/maintenance/archive-observations.d.ts +18 -0
- package/dist/maintenance/archive-observations.js +8 -0
- package/dist/maintenance/archive-observations.js.map +1 -0
- package/dist/maintenance/backup-stamp.d.ts +3 -0
- package/dist/maintenance/backup-stamp.js +8 -0
- package/dist/maintenance/backup-stamp.js.map +1 -0
- package/dist/maintenance/memory-governance-cron.d.ts +85 -0
- package/dist/maintenance/memory-governance-cron.js +22 -0
- package/dist/maintenance/memory-governance-cron.js.map +1 -0
- package/dist/maintenance/memory-governance.d.ts +137 -0
- package/dist/maintenance/memory-governance.js +40 -0
- package/dist/maintenance/memory-governance.js.map +1 -0
- package/dist/maintenance/migrate-observations.d.ts +18 -0
- package/dist/maintenance/migrate-observations.js +9 -0
- package/dist/maintenance/migrate-observations.js.map +1 -0
- package/dist/maintenance/observation-ledger-utils.d.ts +10 -0
- package/dist/maintenance/observation-ledger-utils.js +10 -0
- package/dist/maintenance/observation-ledger-utils.js.map +1 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +15 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +28 -0
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js.map +1 -0
- package/dist/maintenance/rebuild-memory-projection.d.ts +77 -0
- package/dist/maintenance/rebuild-memory-projection.js +35 -0
- package/dist/maintenance/rebuild-memory-projection.js.map +1 -0
- package/dist/maintenance/rebuild-observations.d.ts +17 -0
- package/dist/maintenance/rebuild-observations.js +9 -0
- package/dist/maintenance/rebuild-observations.js.map +1 -0
- package/dist/mcp-memory-inspector-app.d.ts +24 -6
- package/dist/memory-projection-store.d.ts +108 -3
- package/dist/memory-projection-store.js +2 -1
- package/dist/memory-worth-outcomes.d.ts +4 -2
- package/dist/migrate/from-engram.d.ts +24 -0
- package/dist/migrate/from-engram.js +12 -0
- package/dist/migrate/from-engram.js.map +1 -0
- package/dist/namespaces/migrate.d.ts +50 -0
- package/dist/namespaces/migrate.js +50 -0
- package/dist/namespaces/migrate.js.map +1 -0
- package/dist/namespaces/principal.d.ts +17 -0
- package/dist/namespaces/principal.js +16 -0
- package/dist/namespaces/principal.js.map +1 -0
- package/dist/namespaces/search.d.ts +46 -0
- package/dist/namespaces/search.js +28 -0
- package/dist/namespaces/search.js.map +1 -0
- package/dist/namespaces/storage.d.ts +32 -0
- package/dist/namespaces/storage.js +28 -0
- package/dist/namespaces/storage.js.map +1 -0
- package/dist/network/tailscale.d.ts +41 -0
- package/dist/network/tailscale.js +9 -0
- package/dist/network/tailscale.js.map +1 -0
- package/dist/network/webdav.d.ts +39 -0
- package/dist/network/webdav.js +10 -0
- package/dist/network/webdav.js.map +1 -0
- package/dist/objective-state-writers.js +2 -2
- package/dist/operator-toolkit.d.ts +4 -2
- package/dist/operator-toolkit.js +32 -14
- package/dist/opik-exporter.js +2 -2
- package/dist/opik-exporter.js.map +1 -1
- package/dist/orchestrator-DuWl9Hwx.d.ts +1244 -0
- package/dist/orchestrator.d.ts +22 -7
- package/dist/orchestrator.js +79 -44
- package/dist/path-MR5JPYOP.js +9 -0
- package/dist/path-MR5JPYOP.js.map +1 -0
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +102 -3
- package/dist/qmd.js +23 -5
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +6 -0
- package/dist/replay/normalizers/chatgpt.js +11 -0
- package/dist/replay/normalizers/chatgpt.js.map +1 -0
- package/dist/replay/normalizers/claude.d.ts +6 -0
- package/dist/replay/normalizers/claude.js +11 -0
- package/dist/replay/normalizers/claude.js.map +1 -0
- package/dist/replay/normalizers/openclaw.d.ts +6 -0
- package/dist/replay/normalizers/openclaw.js +11 -0
- package/dist/replay/normalizers/openclaw.js.map +1 -0
- package/dist/replay/normalizers/shared.d.ts +16 -0
- package/dist/replay/normalizers/shared.js +14 -0
- package/dist/replay/normalizers/shared.js.map +1 -0
- package/dist/replay/runner.d.ts +35 -0
- package/dist/replay/runner.js +16 -0
- package/dist/replay/runner.js.map +1 -0
- package/dist/replay/types.d.ts +57 -0
- package/dist/replay/types.js +19 -0
- package/dist/replay/types.js.map +1 -0
- package/dist/resolution-B7FNQSSP.js +12 -0
- package/dist/resolution-B7FNQSSP.js.map +1 -0
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/resume-bundles.js +8 -6
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/routing/engine.d.ts +35 -0
- package/dist/routing/engine.js +16 -0
- package/dist/routing/engine.js.map +1 -0
- package/dist/routing/store.d.ts +27 -0
- package/dist/routing/store.js +10 -0
- package/dist/routing/store.js.map +1 -0
- package/dist/runtime/better-sqlite.d.ts +8 -0
- package/dist/runtime/better-sqlite.js +10 -0
- package/dist/runtime/better-sqlite.js.map +1 -0
- package/dist/runtime/child-process.d.ts +32 -0
- package/dist/runtime/child-process.js +10 -0
- package/dist/runtime/child-process.js.map +1 -0
- package/dist/runtime/env.d.ts +5 -0
- package/dist/runtime/env.js +12 -0
- package/dist/runtime/env.js.map +1 -0
- package/dist/schemas.d.ts +22 -22
- package/dist/sdk-compat.js +1 -1
- package/dist/search/document-scanner.d.ts +22 -0
- package/dist/search/document-scanner.js +8 -0
- package/dist/search/document-scanner.js.map +1 -0
- package/dist/search/embed-helper.d.ts +35 -0
- package/dist/search/embed-helper.js +9 -0
- package/dist/search/embed-helper.js.map +1 -0
- package/dist/search/factory.d.ts +32 -0
- package/dist/search/factory.js +29 -0
- package/dist/search/factory.js.map +1 -0
- package/dist/search/index.d.ts +15 -0
- package/dist/search/index.js +50 -0
- package/dist/search/index.js.map +1 -0
- package/dist/search/lancedb-backend.d.ts +51 -0
- package/dist/search/lancedb-backend.js +10 -0
- package/dist/search/lancedb-backend.js.map +1 -0
- package/dist/search/meilisearch-backend.d.ts +48 -0
- package/dist/search/meilisearch-backend.js +10 -0
- package/dist/search/meilisearch-backend.js.map +1 -0
- package/dist/search/noop-backend.d.ts +26 -0
- package/dist/search/noop-backend.js +8 -0
- package/dist/search/noop-backend.js.map +1 -0
- package/dist/search/orama-backend.d.ts +53 -0
- package/dist/search/orama-backend.js +10 -0
- package/dist/search/orama-backend.js.map +1 -0
- package/dist/search/port.d.ts +61 -0
- package/dist/search/port.js +1 -0
- package/dist/search/port.js.map +1 -0
- package/dist/search/remote-backend.d.ts +39 -0
- package/dist/search/remote-backend.js +9 -0
- package/dist/search/remote-backend.js.map +1 -0
- package/dist/secure-store/index.d.ts +890 -0
- package/dist/secure-store/index.js +156 -0
- package/dist/secure-store/index.js.map +1 -0
- package/dist/semantic-VwGI14Ok.d.ts +69 -0
- package/dist/semantic-consolidation-4HkHWgeI.d.ts +180 -0
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -6
- package/dist/semantic-rule-promotion.js +8 -5
- package/dist/semantic-rule-verifier.js +8 -5
- package/dist/shared-context/manager.d.ts +131 -0
- package/dist/shared-context/manager.js +15 -0
- package/dist/shared-context/manager.js.map +1 -0
- package/dist/skills-registry.js +13 -1
- package/dist/skills-registry.js.map +1 -1
- package/dist/state-store-VZU2IA53.js +16 -0
- package/dist/state-store-VZU2IA53.js.map +1 -0
- package/dist/storage-paths.d.ts +9 -0
- package/dist/storage-paths.js +20 -0
- package/dist/storage-paths.js.map +1 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +7 -4
- package/dist/summarizer.d.ts +5 -0
- package/dist/summarizer.js +9 -8
- package/dist/summary-snapshot.js +2 -1
- package/dist/surfaces/dreams.d.ts +16 -0
- package/dist/surfaces/dreams.js +282 -0
- package/dist/surfaces/dreams.js.map +1 -0
- package/dist/surfaces/heartbeat.d.ts +17 -0
- package/dist/surfaces/heartbeat.js +265 -0
- package/dist/surfaces/heartbeat.js.map +1 -0
- package/dist/temporal-supersession.d.ts +3 -1
- package/dist/threading.d.ts +5 -0
- package/dist/threading.js +2 -1
- package/dist/tier-migration.d.ts +4 -2
- package/dist/tokens.js +2 -2
- package/dist/transcript.d.ts +15 -1
- package/dist/transcript.js +2 -1
- package/dist/transfer/autodetect.d.ts +4 -0
- package/dist/transfer/autodetect.js +15 -0
- package/dist/transfer/autodetect.js.map +1 -0
- package/dist/transfer/backup.d.ts +21 -0
- package/dist/transfer/backup.js +17 -0
- package/dist/transfer/backup.js.map +1 -0
- package/dist/transfer/capsule-export.d.ts +113 -0
- package/dist/transfer/capsule-export.js +19 -0
- package/dist/transfer/capsule-export.js.map +1 -0
- package/dist/transfer/capsule-import.d.ts +124 -0
- package/dist/transfer/capsule-import.js +16 -0
- package/dist/transfer/capsule-import.js.map +1 -0
- package/dist/transfer/constants.d.ts +13 -0
- package/dist/transfer/constants.js +12 -0
- package/dist/transfer/constants.js.map +1 -0
- package/dist/transfer/export-json.d.ts +11 -0
- package/dist/transfer/export-json.js +11 -0
- package/dist/transfer/export-json.js.map +1 -0
- package/dist/transfer/export-md.d.ts +10 -0
- package/dist/transfer/export-md.js +13 -0
- package/dist/transfer/export-md.js.map +1 -0
- package/dist/transfer/export-sqlite.d.ts +9 -0
- package/dist/transfer/export-sqlite.js +12 -0
- package/dist/transfer/export-sqlite.js.map +1 -0
- package/dist/transfer/fs-utils.d.ts +61 -0
- package/dist/transfer/fs-utils.js +40 -0
- package/dist/transfer/fs-utils.js.map +1 -0
- package/dist/transfer/import-json.d.ts +16 -0
- package/dist/transfer/import-json.js +13 -0
- package/dist/transfer/import-json.js.map +1 -0
- package/dist/transfer/import-md.d.ts +14 -0
- package/dist/transfer/import-md.js +11 -0
- package/dist/transfer/import-md.js.map +1 -0
- package/dist/transfer/import-sqlite.d.ts +14 -0
- package/dist/transfer/import-sqlite.js +12 -0
- package/dist/transfer/import-sqlite.js.map +1 -0
- package/dist/transfer/sqlite-schema.d.ts +4 -0
- package/dist/transfer/sqlite-schema.js +10 -0
- package/dist/transfer/sqlite-schema.js.map +1 -0
- package/dist/transfer/types.d.ts +916 -0
- package/dist/transfer/types.js +30 -0
- package/dist/transfer/types.js.map +1 -0
- package/dist/types.d.ts +28 -1
- package/dist/types.js +1 -1
- package/dist/verified-recall.js +9 -6
- package/dist/work/board.d.ts +43 -0
- package/dist/work/board.js +14 -0
- package/dist/work/board.js.map +1 -0
- package/dist/work/boundary.d.ts +8 -0
- package/dist/work/boundary.js +14 -0
- package/dist/work/boundary.js.map +1 -0
- package/dist/work/storage.d.ts +39 -0
- package/dist/work/storage.js +11 -0
- package/dist/work/storage.js.map +1 -0
- package/dist/work/types.d.ts +75 -0
- package/dist/work/types.js +1 -0
- package/dist/work/types.js.map +1 -0
- package/package.json +2767 -6
- package/scripts/faiss_index.py +816 -0
- package/scripts/faiss_requirements.txt +3 -0
- package/skills/remnic-entities/SKILL.md +51 -0
- package/skills/remnic-memory-workflow/SKILL.md +61 -0
- package/skills/remnic-recall/SKILL.md +51 -0
- package/skills/remnic-remember/SKILL.md +56 -0
- package/skills/remnic-search/SKILL.md +51 -0
- package/skills/remnic-status/SKILL.md +51 -0
- package/src/abort-error.test.ts +49 -0
- package/src/abort-error.ts +46 -0
- package/src/abstraction-nodes.ts +162 -0
- package/src/access-audit.test.ts +178 -0
- package/src/access-audit.ts +125 -0
- package/src/access-cli.test.ts +439 -0
- package/src/access-cli.ts +438 -0
- package/src/access-http.test.ts +225 -0
- package/src/access-http.ts +1899 -0
- package/src/access-idempotency.ts +232 -0
- package/src/access-mcp.test.ts +568 -0
- package/src/access-mcp.ts +3056 -0
- package/src/access-schema-pi.test.ts +60 -0
- package/src/access-schema.ts +522 -0
- package/src/access-service-namespace.test.ts +123 -0
- package/src/access-service.ts +5629 -0
- package/src/action-confidence.test.ts +206 -0
- package/src/action-confidence.ts +466 -0
- package/src/active-memory-bridge.test.ts +285 -0
- package/src/active-memory-bridge.ts +217 -0
- package/src/active-recall.test.ts +484 -0
- package/src/active-recall.ts +459 -0
- package/src/adapters/claude-code.ts +56 -0
- package/src/adapters/codex.ts +57 -0
- package/src/adapters/hermes.ts +64 -0
- package/src/adapters/index.ts +6 -0
- package/src/adapters/registry.ts +41 -0
- package/src/adapters/replit.ts +55 -0
- package/src/adapters/types.ts +51 -0
- package/src/behavior-learner.ts +144 -0
- package/src/behavior-signals.ts +73 -0
- package/src/binary-lifecycle/backend.ts +117 -0
- package/src/binary-lifecycle/index.ts +35 -0
- package/src/binary-lifecycle/manifest.ts +79 -0
- package/src/binary-lifecycle/pipeline.ts +352 -0
- package/src/binary-lifecycle/scanner.ts +89 -0
- package/src/binary-lifecycle/types.ts +89 -0
- package/src/bootstrap.ts +178 -0
- package/src/boxes.ts +521 -0
- package/src/briefing.test.ts +1535 -0
- package/src/briefing.ts +1382 -0
- package/src/buffer-session.test.ts +443 -0
- package/src/buffer-surprise-report.ts +176 -0
- package/src/buffer-surprise-telemetry.test.ts +606 -0
- package/src/buffer-surprise-trigger.test.ts +766 -0
- package/src/buffer-surprise.test.ts +339 -0
- package/src/buffer-surprise.ts +203 -0
- package/src/buffer.ts +900 -0
- package/src/bulk-import/cli-command.test.ts +204 -0
- package/src/bulk-import/index.ts +34 -0
- package/src/bulk-import/pipeline.test.ts +445 -0
- package/src/bulk-import/pipeline.ts +178 -0
- package/src/bulk-import/registry.test.ts +151 -0
- package/src/bulk-import/registry.ts +72 -0
- package/src/bulk-import/types.test.ts +272 -0
- package/src/bulk-import/types.ts +145 -0
- package/src/calibration.ts +394 -0
- package/src/capsule-cli.test.ts +398 -0
- package/src/capsule-cli.ts +565 -0
- package/src/causal-behavior.ts +308 -0
- package/src/causal-chain.ts +419 -0
- package/src/causal-consolidation.ts +370 -0
- package/src/causal-retrieval.ts +286 -0
- package/src/causal-trajectory-graph.ts +60 -0
- package/src/causal-trajectory.ts +303 -0
- package/src/chunking.ts +220 -0
- package/src/citations.ts +232 -0
- package/src/cli.ts +9403 -0
- package/src/codex-cli-fallback.ts +162 -0
- package/src/codex-thread-key.ts +1 -0
- package/src/coding/access-coding-context.test.ts +197 -0
- package/src/coding/coding-branch-scope.test.ts +281 -0
- package/src/coding/coding-namespace.test.ts +360 -0
- package/src/coding/coding-namespace.ts +412 -0
- package/src/coding/coding-orchestrator.test.ts +249 -0
- package/src/coding/git-context.test.ts +507 -0
- package/src/coding/git-context.ts +336 -0
- package/src/coding/mcp-set-coding-context.test.ts +174 -0
- package/src/coding/review-context.test.ts +316 -0
- package/src/coding/review-context.ts +349 -0
- package/src/coding/wire-coding-context.test.ts +468 -0
- package/src/commitment-ledger.test.ts +78 -0
- package/src/commitment-ledger.ts +337 -0
- package/src/compat/checks.test.ts +206 -0
- package/src/compat/checks.ts +716 -0
- package/src/compat/types.ts +33 -0
- package/src/compounding/engine.ts +1686 -0
- package/src/compounding/preference-consolidator.ts +778 -0
- package/src/compression-optimizer.ts +312 -0
- package/src/config.test.ts +930 -0
- package/src/config.ts +3807 -0
- package/src/connectors/codex/instructions.md +160 -0
- package/src/connectors/codex/resources/namespace-cheatsheet.md +48 -0
- package/src/connectors/codex-marketplace.ts +500 -0
- package/src/connectors/codex-materialize-runner.ts +212 -0
- package/src/connectors/codex-materialize.ts +983 -0
- package/src/connectors/coerce.ts +62 -0
- package/src/connectors/index.test.ts +1570 -0
- package/src/connectors/index.ts +3222 -0
- package/src/connectors/live/framework.ts +164 -0
- package/src/connectors/live/github.test.ts +1218 -0
- package/src/connectors/live/github.ts +1068 -0
- package/src/connectors/live/gmail.test.ts +1706 -0
- package/src/connectors/live/gmail.ts +1293 -0
- package/src/connectors/live/google-drive.test.ts +696 -0
- package/src/connectors/live/google-drive.ts +724 -0
- package/src/connectors/live/index.ts +101 -0
- package/src/connectors/live/live-connectors.test.ts +689 -0
- package/src/connectors/live/notion.test.ts +1109 -0
- package/src/connectors/live/notion.ts +978 -0
- package/src/connectors/live/registry.ts +103 -0
- package/src/connectors/live/state-store.ts +399 -0
- package/src/connectors/live/transient-errors.ts +150 -0
- package/src/connectors/weclone-installer.test.ts +850 -0
- package/src/connectors-cli.ts +513 -0
- package/src/console/state.test.ts +224 -0
- package/src/console/state.ts +514 -0
- package/src/console/trace.test.ts +813 -0
- package/src/console/trace.ts +603 -0
- package/src/console/tui.test.ts +582 -0
- package/src/console/tui.ts +508 -0
- package/src/consolidation-operator.ts +182 -0
- package/src/consolidation-provenance-check.ts +551 -0
- package/src/consolidation-undo.ts +718 -0
- package/src/contradiction/contradiction-judge.test.ts +189 -0
- package/src/contradiction/contradiction-judge.ts +333 -0
- package/src/contradiction/contradiction-review.ts +574 -0
- package/src/contradiction/contradiction-scan.ts +504 -0
- package/src/contradiction/contradiction.test.ts +2230 -0
- package/src/contradiction/index.ts +37 -0
- package/src/contradiction/resolution.ts +383 -0
- package/src/conversation-index/backend.ts +323 -0
- package/src/conversation-index/chunker.ts +47 -0
- package/src/conversation-index/cleanup.ts +53 -0
- package/src/conversation-index/faiss-adapter.ts +384 -0
- package/src/conversation-index/indexer.test.ts +164 -0
- package/src/conversation-index/indexer.ts +192 -0
- package/src/conversation-index/search.ts +37 -0
- package/src/cross-namespace-budget.test.ts +275 -0
- package/src/cross-namespace-budget.ts +365 -0
- package/src/cue-anchors.ts +163 -0
- package/src/curation/index.ts +544 -0
- package/src/dashboard-runtime.ts +337 -0
- package/src/day-summary.ts +122 -0
- package/src/dedup/index.ts +330 -0
- package/src/dedup/semantic.test.ts +1577 -0
- package/src/dedup/semantic.ts +148 -0
- package/src/delinearize.ts +193 -0
- package/src/direct-answer-wiring.test.ts +473 -0
- package/src/direct-answer-wiring.ts +180 -0
- package/src/direct-answer.test.ts +484 -0
- package/src/direct-answer.ts +273 -0
- package/src/embedding-fallback.ts +565 -0
- package/src/enrichment/audit.ts +89 -0
- package/src/enrichment/index.ts +27 -0
- package/src/enrichment/pipeline.ts +197 -0
- package/src/enrichment/provider-registry.ts +85 -0
- package/src/enrichment/types.ts +100 -0
- package/src/enrichment/web-search-provider.ts +63 -0
- package/src/entity-retrieval.ts +774 -0
- package/src/entity-schema.ts +239 -0
- package/src/evals.ts +1312 -0
- package/src/event-order-recall.test.ts +4164 -0
- package/src/event-order-recall.ts +2802 -0
- package/src/evidence-pack.test.ts +89 -0
- package/src/evidence-pack.ts +388 -0
- package/src/explicit-capture.ts +530 -0
- package/src/explicit-cue-recall.test.ts +3019 -0
- package/src/explicit-cue-recall.ts +5545 -0
- package/src/extraction-judge-telemetry.ts +234 -0
- package/src/extraction-judge-training.ts +221 -0
- package/src/extraction-judge.ts +846 -0
- package/src/extraction-timeout.test.ts +265 -0
- package/src/extraction.ts +2719 -0
- package/src/fallback-llm.test.ts +1060 -0
- package/src/fallback-llm.ts +918 -0
- package/src/focused-list-recall.test.ts +734 -0
- package/src/focused-list-recall.ts +1160 -0
- package/src/graph-dashboard-diff.ts +35 -0
- package/src/graph-dashboard-key.ts +5 -0
- package/src/graph-dashboard-parser.ts +104 -0
- package/src/graph-edge-reinforcement.ts +192 -0
- package/src/graph-events.ts +151 -0
- package/src/graph-recall.test.ts +164 -0
- package/src/graph-recall.ts +189 -0
- package/src/graph-retrieval.test.ts +809 -0
- package/src/graph-retrieval.ts +823 -0
- package/src/graph-snapshot.ts +329 -0
- package/src/graph.ts +813 -0
- package/src/harmonic-retrieval.ts +223 -0
- package/src/himem.ts +154 -0
- package/src/hygiene.ts +87 -0
- package/src/identity-continuity.ts +333 -0
- package/src/importance.ts +328 -0
- package/src/importers/base.test.ts +294 -0
- package/src/importers/base.ts +436 -0
- package/src/importers/index.ts +21 -0
- package/src/index.ts +1204 -0
- package/src/intent.ts +154 -0
- package/src/json-extract.ts +85 -0
- package/src/json-store.ts +42 -0
- package/src/lcm/archive.ts +617 -0
- package/src/lcm/dag.ts +199 -0
- package/src/lcm/engine.ts +645 -0
- package/src/lcm/index.ts +7 -0
- package/src/lcm/queue.test.ts +178 -0
- package/src/lcm/queue.ts +200 -0
- package/src/lcm/recall.ts +117 -0
- package/src/lcm/schema.ts +154 -0
- package/src/lcm/summarizer.ts +235 -0
- package/src/lcm/tools.ts +191 -0
- package/src/lcm-engine.test.ts +660 -0
- package/src/legacy-hook-compat.test.ts +20 -0
- package/src/legacy-hook-compat.ts +45 -0
- package/src/lifecycle.ts +289 -0
- package/src/live-connectors-runner.ts +385 -0
- package/src/local-llm-qos.test.ts +303 -0
- package/src/local-llm-thinking.test.ts +292 -0
- package/src/local-llm.ts +1464 -0
- package/src/logger.ts +49 -0
- package/src/maintenance/archive-observations.ts +147 -0
- package/src/maintenance/backup-stamp.ts +3 -0
- package/src/maintenance/dreams-ledger.ts +516 -0
- package/src/maintenance/first-start-migration.ts +362 -0
- package/src/maintenance/forget.test.ts +206 -0
- package/src/maintenance/forget.ts +126 -0
- package/src/maintenance/graph-edge-decay.test.ts +409 -0
- package/src/maintenance/graph-edge-decay.ts +394 -0
- package/src/maintenance/memory-governance-cron.ts +447 -0
- package/src/maintenance/memory-governance.ts +1039 -0
- package/src/maintenance/migrate-observations.ts +216 -0
- package/src/maintenance/observation-ledger-utils.ts +54 -0
- package/src/maintenance/pattern-reinforcement.test.ts +875 -0
- package/src/maintenance/pattern-reinforcement.ts +369 -0
- package/src/maintenance/purge.ts +334 -0
- package/src/maintenance/rebuild-memory-lifecycle-ledger.ts +78 -0
- package/src/maintenance/rebuild-memory-projection.ts +1234 -0
- package/src/maintenance/rebuild-observations.ts +178 -0
- package/src/maintenance/tier-stats.test.ts +378 -0
- package/src/maintenance/tier-stats.ts +222 -0
- package/src/mcp-memory-inspector-app.ts +421 -0
- package/src/memory-action-policy.ts +80 -0
- package/src/memory-cache.ts +208 -0
- package/src/memory-extension/claude-code-publisher.ts +51 -0
- package/src/memory-extension/codex-publisher.ts +149 -0
- package/src/memory-extension/hermes-publisher.ts +51 -0
- package/src/memory-extension/index.ts +100 -0
- package/src/memory-extension/shared-instructions.ts +133 -0
- package/src/memory-extension/types.ts +86 -0
- package/src/memory-extension-host/host-discovery.ts +276 -0
- package/src/memory-extension-host/index.ts +14 -0
- package/src/memory-extension-host/render-extensions-block.ts +73 -0
- package/src/memory-extension-host/types.ts +21 -0
- package/src/memory-lifecycle-ledger-utils.ts +116 -0
- package/src/memory-projection-format.ts +11 -0
- package/src/memory-projection-store.ts +951 -0
- package/src/memory-provenance.test.ts +196 -0
- package/src/memory-provenance.ts +484 -0
- package/src/memory-worth-bench.test.ts +71 -0
- package/src/memory-worth-bench.ts +265 -0
- package/src/memory-worth-filter.test.ts +209 -0
- package/src/memory-worth-filter.ts +204 -0
- package/src/memory-worth-frontmatter.test.ts +311 -0
- package/src/memory-worth-outcomes.test.ts +316 -0
- package/src/memory-worth-outcomes.ts +286 -0
- package/src/memory-worth.test.ts +317 -0
- package/src/memory-worth.ts +215 -0
- package/src/message-parts/index.ts +806 -0
- package/src/message-parts/message-parts.test.ts +421 -0
- package/src/migrate/from-engram.ts +789 -0
- package/src/model-registry.ts +313 -0
- package/src/models-json.ts +76 -0
- package/src/namespaces/migrate.ts +187 -0
- package/src/namespaces/path.ts +25 -0
- package/src/namespaces/principal.test.ts +195 -0
- package/src/namespaces/principal.ts +86 -0
- package/src/namespaces/search.test.ts +105 -0
- package/src/namespaces/search.ts +233 -0
- package/src/namespaces/storage.ts +74 -0
- package/src/native-knowledge.ts +1823 -0
- package/src/negative.ts +72 -0
- package/src/network/tailscale.ts +179 -0
- package/src/network/webdav.ts +385 -0
- package/src/objective-state-writers.ts +951 -0
- package/src/objective-state.ts +320 -0
- package/src/onboarding/index.ts +529 -0
- package/src/openai-chat-compat.ts +56 -0
- package/src/operator-toolkit.ts +2132 -0
- package/src/opik-exporter.test.ts +72 -0
- package/src/opik-exporter.ts +587 -0
- package/src/orchestrator-extraction-queue.test.ts +197 -0
- package/src/orchestrator-flush.test.ts +1171 -0
- package/src/orchestrator-pattern-reinforcement.test.ts +128 -0
- package/src/orchestrator-source-attribution.test.ts +701 -0
- package/src/orchestrator.ts +16368 -0
- package/src/page-versioning.ts +450 -0
- package/src/patterns-cli.ts +574 -0
- package/src/peers/index.ts +54 -0
- package/src/peers/migrate-from-identity-anchor.test.ts +291 -0
- package/src/peers/migrate-from-identity-anchor.ts +350 -0
- package/src/peers/peers.test.ts +419 -0
- package/src/peers/profile-reasoner.ts +694 -0
- package/src/peers/storage.ts +1350 -0
- package/src/peers/types.ts +138 -0
- package/src/plugin-id.ts +84 -0
- package/src/policy-runtime.ts +209 -0
- package/src/procedural/procedure-miner.ts +150 -0
- package/src/procedural/procedure-recall.ts +93 -0
- package/src/procedural/procedure-stats.ts +213 -0
- package/src/procedural/procedure-types.ts +132 -0
- package/src/procedural/reinforcement-core.test.ts +132 -0
- package/src/procedural/reinforcement-core.ts +73 -0
- package/src/profiling.test.ts +263 -0
- package/src/profiling.ts +435 -0
- package/src/projection/index.ts +398 -0
- package/src/qmd-recall-cache.test.ts +138 -0
- package/src/qmd-recall-cache.ts +111 -0
- package/src/qmd.test.ts +257 -0
- package/src/qmd.ts +2614 -0
- package/src/reasoning-trace-recall.ts +201 -0
- package/src/reasoning-trace-types.ts +235 -0
- package/src/recall-audit-anomaly.test.ts +246 -0
- package/src/recall-audit-anomaly.ts +297 -0
- package/src/recall-audit.test.ts +51 -0
- package/src/recall-audit.ts +72 -0
- package/src/recall-budget-config.test.ts +87 -0
- package/src/recall-disclosure-escalation.test.ts +196 -0
- package/src/recall-disclosure-escalation.ts +158 -0
- package/src/recall-disclosure-shaping.test.ts +146 -0
- package/src/recall-disclosure.test.ts +214 -0
- package/src/recall-explain-renderer.test.ts +140 -0
- package/src/recall-explain-renderer.ts +356 -0
- package/src/recall-mmr.test.ts +808 -0
- package/src/recall-mmr.ts +607 -0
- package/src/recall-qos.test.ts +85 -0
- package/src/recall-qos.ts +82 -0
- package/src/recall-query-policy.ts +221 -0
- package/src/recall-state.test.ts +233 -0
- package/src/recall-state.ts +456 -0
- package/src/recall-tag-filter.ts +143 -0
- package/src/recall-tokenization.ts +35 -0
- package/src/recall-xray-cli.test.ts +118 -0
- package/src/recall-xray-cli.ts +100 -0
- package/src/recall-xray-disclosure-telemetry.test.ts +183 -0
- package/src/recall-xray-renderer.test.ts +539 -0
- package/src/recall-xray-renderer.ts +487 -0
- package/src/recall-xray.test.ts +503 -0
- package/src/recall-xray.ts +621 -0
- package/src/reconstruct.ts +41 -0
- package/src/release-changelog.ts +35 -0
- package/src/relevance.ts +67 -0
- package/src/replay/normalizers/chatgpt.ts +133 -0
- package/src/replay/normalizers/claude.ts +102 -0
- package/src/replay/normalizers/openclaw.ts +119 -0
- package/src/replay/normalizers/shared.ts +69 -0
- package/src/replay/runner.ts +197 -0
- package/src/replay/types.ts +143 -0
- package/src/rerank.test.ts +48 -0
- package/src/rerank.ts +176 -0
- package/src/resolve-auth-token.test.ts +226 -0
- package/src/resolve-auth-token.ts +151 -0
- package/src/resolve-provider-secret.test.ts +187 -0
- package/src/resolve-provider-secret.ts +410 -0
- package/src/response-guidance-recall.test.ts +3952 -0
- package/src/response-guidance-recall.ts +4431 -0
- package/src/resume-bundles.ts +415 -0
- package/src/retrieval-agents.ts +623 -0
- package/src/retrieval-tiers.ts +25 -0
- package/src/retrieval.ts +104 -0
- package/src/review/index.test.ts +201 -0
- package/src/review/index.ts +536 -0
- package/src/routing/engine.ts +162 -0
- package/src/routing/store.ts +321 -0
- package/src/runtime/better-sqlite.test.ts +32 -0
- package/src/runtime/better-sqlite.ts +76 -0
- package/src/runtime/child-process.ts +67 -0
- package/src/runtime/env.ts +48 -0
- package/src/sanitize.ts +58 -0
- package/src/schemas.ts +449 -0
- package/src/sdk-compat.ts +87 -0
- package/src/search/document-scanner.ts +96 -0
- package/src/search/embed-helper.ts +142 -0
- package/src/search/factory.ts +189 -0
- package/src/search/index.ts +10 -0
- package/src/search/lancedb-backend.ts +342 -0
- package/src/search/meilisearch-backend.ts +232 -0
- package/src/search/noop-backend.ts +57 -0
- package/src/search/orama-backend.ts +358 -0
- package/src/search/port.ts +86 -0
- package/src/search/remote-backend.ts +124 -0
- package/src/secure-store/cipher.ts +271 -0
- package/src/secure-store/cli-handlers.ts +355 -0
- package/src/secure-store/cli-renderer.ts +131 -0
- package/src/secure-store/header.ts +373 -0
- package/src/secure-store/index.ts +137 -0
- package/src/secure-store/kdf.ts +263 -0
- package/src/secure-store/keyring.ts +106 -0
- package/src/secure-store/metadata.ts +394 -0
- package/src/secure-store/passphrase-reader.ts +252 -0
- package/src/secure-store/secure-fs.ts +571 -0
- package/src/secure-store/secure-store.test.ts +755 -0
- package/src/semantic-chunking.ts +545 -0
- package/src/semantic-consolidation.test.ts +182 -0
- package/src/semantic-consolidation.ts +432 -0
- package/src/semantic-rule-promotion.ts +183 -0
- package/src/semantic-rule-verifier.ts +160 -0
- package/src/session-integrity.ts +569 -0
- package/src/session-observer-bands.ts +11 -0
- package/src/session-observer-state.ts +346 -0
- package/src/session-toggles.test.ts +96 -0
- package/src/session-toggles.ts +159 -0
- package/src/shared-context/manager.ts +810 -0
- package/src/signal.ts +84 -0
- package/src/skills-registry.test.ts +277 -0
- package/src/skills-registry.ts +120 -0
- package/src/source-attribution-roundtrip.test.ts +215 -0
- package/src/source-attribution.test.ts +1425 -0
- package/src/source-attribution.ts +639 -0
- package/src/spaces/index.ts +627 -0
- package/src/storage-paths.ts +117 -0
- package/src/storage.ts +6657 -0
- package/src/store-contract.ts +55 -0
- package/src/summarizer.ts +844 -0
- package/src/summary-snapshot.test.ts +681 -0
- package/src/summary-snapshot.ts +238 -0
- package/src/surfaces/dreams.test.ts +394 -0
- package/src/surfaces/dreams.ts +346 -0
- package/src/surfaces/heartbeat.test.ts +415 -0
- package/src/surfaces/heartbeat.ts +325 -0
- package/src/sync/index.ts +308 -0
- package/src/targeted-fact-recall.test.ts +1694 -0
- package/src/targeted-fact-recall.ts +2905 -0
- package/src/taxonomy/default-taxonomy.ts +87 -0
- package/src/taxonomy/index.ts +26 -0
- package/src/taxonomy/resolver-doc-generator.ts +57 -0
- package/src/taxonomy/resolver.ts +184 -0
- package/src/taxonomy/taxonomy-loader.ts +186 -0
- package/src/taxonomy/types.ts +48 -0
- package/src/telemetry-transcript.ts +70 -0
- package/src/temporal-index.ts +890 -0
- package/src/temporal-supersession.test.ts +2703 -0
- package/src/temporal-supersession.ts +493 -0
- package/src/temporal-validity.test.ts +448 -0
- package/src/temporal-validity.ts +123 -0
- package/src/threading.ts +395 -0
- package/src/tier-migration.ts +124 -0
- package/src/tier-routing.ts +102 -0
- package/src/tmt.ts +462 -0
- package/src/tokens.test.ts +178 -0
- package/src/tokens.ts +279 -0
- package/src/topics.ts +147 -0
- package/src/training-export/cli-date-validation.test.ts +258 -0
- package/src/training-export/converter.test.ts +452 -0
- package/src/training-export/converter.ts +319 -0
- package/src/training-export/date-parse.ts +117 -0
- package/src/training-export/index.ts +26 -0
- package/src/training-export/registry.test.ts +85 -0
- package/src/training-export/registry.ts +57 -0
- package/src/training-export/types.ts +31 -0
- package/src/transcript.ts +1179 -0
- package/src/transfer/autodetect.ts +30 -0
- package/src/transfer/backup.ts +138 -0
- package/src/transfer/capsule-crypto.ts +485 -0
- package/src/transfer/capsule-encrypt.test.ts +690 -0
- package/src/transfer/capsule-export.ts +543 -0
- package/src/transfer/capsule-fork.ts +375 -0
- package/src/transfer/capsule-import.ts +564 -0
- package/src/transfer/capsule-merge.ts +433 -0
- package/src/transfer/conflict-policy.ts +16 -0
- package/src/transfer/constants.ts +13 -0
- package/src/transfer/exclusions.ts +37 -0
- package/src/transfer/export-json.ts +65 -0
- package/src/transfer/export-md.ts +59 -0
- package/src/transfer/export-sqlite.ts +52 -0
- package/src/transfer/fs-utils.ts +269 -0
- package/src/transfer/import-json.ts +108 -0
- package/src/transfer/import-md.ts +84 -0
- package/src/transfer/import-sqlite.ts +100 -0
- package/src/transfer/integrity.ts +71 -0
- package/src/transfer/sqlite-schema.ts +16 -0
- package/src/transfer/types.ts +297 -0
- package/src/trust-zones.ts +1186 -0
- package/src/types.ts +3074 -0
- package/src/user-model.test.ts +124 -0
- package/src/user-model.ts +162 -0
- package/src/utility-learner.ts +353 -0
- package/src/utility-runtime.ts +88 -0
- package/src/utility-telemetry.ts +215 -0
- package/src/utils/category-dir.ts +44 -0
- package/src/utils/errno.ts +6 -0
- package/src/utils/iso-timestamp.test.ts +37 -0
- package/src/utils/iso-timestamp.ts +164 -0
- package/src/utils/path.ts +26 -0
- package/src/verified-recall.ts +138 -0
- package/src/version-utils.test.ts +10 -0
- package/src/version-utils.ts +9 -0
- package/src/whitespace.ts +10 -0
- package/src/work/board.ts +359 -0
- package/src/work/boundary.ts +107 -0
- package/src/work/storage.ts +436 -0
- package/src/work/types.ts +82 -0
- package/src/work-product-ledger.ts +265 -0
- package/dist/access-service-DDjzFALq.d.ts +0 -2088
- package/dist/capsule-crypto-SJS5VVAP.js +0 -18
- package/dist/capsule-export-7QNCBZOQ.js +0 -17
- package/dist/capsule-import-EPBHD2EN.js +0 -16
- package/dist/capsule-merge-DI7PNQ2H.js +0 -189
- package/dist/chunk-23ZZK64Y.js +0 -26
- package/dist/chunk-23ZZK64Y.js.map +0 -1
- package/dist/chunk-242S3I2A.js +0 -647
- package/dist/chunk-2LGMW3DJ.js +0 -111
- package/dist/chunk-3B6KIRBH.js +0 -5213
- package/dist/chunk-3B6KIRBH.js.map +0 -1
- package/dist/chunk-457A4P3L.js +0 -119
- package/dist/chunk-457A4P3L.js.map +0 -1
- package/dist/chunk-4IS4SXIQ.js +0 -2040
- package/dist/chunk-4YM32CRU.js +0 -721
- package/dist/chunk-6TBWYBJ3.js +0 -236
- package/dist/chunk-74EMIVE4.js +0 -329
- package/dist/chunk-74EMIVE4.js.map +0 -1
- package/dist/chunk-767ODGE6.js +0 -183
- package/dist/chunk-7V22HTMD.js +0 -623
- package/dist/chunk-7V22HTMD.js.map +0 -1
- package/dist/chunk-7ZM3BFKK.js +0 -9705
- package/dist/chunk-7ZM3BFKK.js.map +0 -1
- package/dist/chunk-AQJNPMOA.js +0 -643
- package/dist/chunk-AQJNPMOA.js.map +0 -1
- package/dist/chunk-ASAITVLA.js +0 -64
- package/dist/chunk-ASAITVLA.js.map +0 -1
- package/dist/chunk-BBE34QBJ.js +0 -275
- package/dist/chunk-BBE34QBJ.js.map +0 -1
- package/dist/chunk-BZSQEPRW.js +0 -14710
- package/dist/chunk-BZSQEPRW.js.map +0 -1
- package/dist/chunk-CPKTBRS2.js +0 -891
- package/dist/chunk-CPKTBRS2.js.map +0 -1
- package/dist/chunk-D4GAOFF6.js +0 -562
- package/dist/chunk-D4GAOFF6.js.map +0 -1
- package/dist/chunk-D54LZC5L.js +0 -147
- package/dist/chunk-DF3RVK3X.js +0 -119
- package/dist/chunk-DF3RVK3X.js.map +0 -1
- package/dist/chunk-DZZPC36E.js +0 -1451
- package/dist/chunk-DZZPC36E.js.map +0 -1
- package/dist/chunk-E2UCDP5S.js +0 -570
- package/dist/chunk-E6K4NIEU.js +0 -747
- package/dist/chunk-E6K4NIEU.js.map +0 -1
- package/dist/chunk-EEQLFRUM.js +0 -89
- package/dist/chunk-ETOW6ACV.js +0 -158
- package/dist/chunk-ETOW6ACV.js.map +0 -1
- package/dist/chunk-FMEBPEAO.js +0 -347
- package/dist/chunk-FMEBPEAO.js.map +0 -1
- package/dist/chunk-FQDPCE3I.js +0 -1837
- package/dist/chunk-FQDPCE3I.js.map +0 -1
- package/dist/chunk-FYIYMQ5N.js +0 -221
- package/dist/chunk-FYIYMQ5N.js.map +0 -1
- package/dist/chunk-G2WADRQ3.js +0 -219
- package/dist/chunk-G4SK7DSQ.js +0 -121
- package/dist/chunk-GVPWB7EY.js +0 -390
- package/dist/chunk-GVPWB7EY.js.map +0 -1
- package/dist/chunk-HELQZFZO.js +0 -1075
- package/dist/chunk-HL5LRPNA.js +0 -1914
- package/dist/chunk-HL5LRPNA.js.map +0 -1
- package/dist/chunk-HQZVVSVB.js +0 -147
- package/dist/chunk-HQZVVSVB.js.map +0 -1
- package/dist/chunk-HY3L4WKC.js +0 -2195
- package/dist/chunk-HY3L4WKC.js.map +0 -1
- package/dist/chunk-IB3BFHGN.js +0 -228
- package/dist/chunk-IXEJRKCZ.js +0 -18
- package/dist/chunk-JBMSGZEQ.js +0 -441
- package/dist/chunk-JBMSGZEQ.js.map +0 -1
- package/dist/chunk-JESOB2HO.js +0 -108
- package/dist/chunk-JKDVIE52.js +0 -272
- package/dist/chunk-JRNQ3RNA.js +0 -284
- package/dist/chunk-JRNQ3RNA.js.map +0 -1
- package/dist/chunk-K6WK37A6.js +0 -865
- package/dist/chunk-K6WK37A6.js.map +0 -1
- package/dist/chunk-MARWOCVP.js +0 -48
- package/dist/chunk-MNU6ZBWT.js +0 -4454
- package/dist/chunk-MNU6ZBWT.js.map +0 -1
- package/dist/chunk-N5AKDXAI.js +0 -74
- package/dist/chunk-OA3L7BFR.js +0 -183
- package/dist/chunk-OA3L7BFR.js.map +0 -1
- package/dist/chunk-OR64ZGRZ.js +0 -23
- package/dist/chunk-P77UEOU2.js +0 -1521
- package/dist/chunk-P77UEOU2.js.map +0 -1
- package/dist/chunk-PH4C2U43.js +0 -239
- package/dist/chunk-PH4C2U43.js.map +0 -1
- package/dist/chunk-RVPLBATS.js +0 -1586
- package/dist/chunk-RVPLBATS.js.map +0 -1
- package/dist/chunk-U5JMRGKX.js +0 -340
- package/dist/chunk-U5JMRGKX.js.map +0 -1
- package/dist/chunk-URB2WSKZ.js +0 -350
- package/dist/chunk-URB2WSKZ.js.map +0 -1
- package/dist/chunk-UVMUAWVT.js +0 -596
- package/dist/chunk-WEJG4TB5.js +0 -118
- package/dist/chunk-X7HPGUVG.js +0 -271
- package/dist/chunk-XAMBKFQS.js +0 -2777
- package/dist/chunk-XAMBKFQS.js.map +0 -1
- package/dist/chunk-XJKFSSDW.js +0 -726
- package/dist/chunk-XJKFSSDW.js.map +0 -1
- package/dist/chunk-XMHBH5H6.js +0 -283
- package/dist/chunk-XMHBH5H6.js.map +0 -1
- package/dist/chunk-XMVFHBHT.js +0 -277
- package/dist/chunk-Y3VMVTYX.js +0 -53
- package/dist/chunk-YNB73F22.js +0 -137
- package/dist/chunk-YNB73F22.js.map +0 -1
- package/dist/chunk-Z2E7VW55.js +0 -335
- package/dist/chunk-Z2E7VW55.js.map +0 -1
- package/dist/chunk-ZG7PTKBK.js +0 -2296
- package/dist/chunk-ZNQN6ZTA.js +0 -135
- package/dist/chunk-ZVTKDVVM.js +0 -827
- package/dist/chunk-ZVTKDVVM.js.map +0 -1
- package/dist/cli-BR8KpIU0.d.ts +0 -1259
- package/dist/codex-materialize-CQlLTzke.d.ts +0 -139
- package/dist/connectors-cli-DFGtY2DB.d.ts +0 -257
- package/dist/contradiction-review-5LTTVDQV.js +0 -22
- package/dist/contradiction-scan-QTXAMBUA.js +0 -414
- package/dist/contradiction-scan-QTXAMBUA.js.map +0 -1
- package/dist/engine-35M5BKQ7.js +0 -28
- package/dist/fs-utils-IRVUFB6G.js +0 -30
- package/dist/graph-edge-decay-PWB63GRE.js +0 -207
- package/dist/memory-governance-IMPQZXFC.js +0 -37
- package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
- package/dist/orchestrator-DDMPqU6R.d.ts +0 -1792
- package/dist/path-RMTY5Y5A.js +0 -9
- package/dist/port-B6VEDIkC.d.ts +0 -53
- package/dist/resolution-YGIBORXI.js +0 -101
- package/dist/resolution-YGIBORXI.js.map +0 -1
- package/dist/secure-store-4R2GSO7S.js +0 -156
- package/dist/semantic-consolidation-ByBXb-sf.d.ts +0 -180
- package/dist/state-store-3EH7HYIN.js +0 -16
- package/dist/types-V3FJ26TF.js +0 -30
- /package/dist/{capsule-crypto-SJS5VVAP.js.map → adapters/claude-code.js.map} +0 -0
- /package/dist/{capsule-export-7QNCBZOQ.js.map → adapters/codex.js.map} +0 -0
- /package/dist/{capsule-import-EPBHD2EN.js.map → adapters/hermes.js.map} +0 -0
- /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/index.js.map} +0 -0
- /package/dist/{engine-35M5BKQ7.js.map → adapters/registry.js.map} +0 -0
- /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/replit.js.map} +0 -0
- /package/dist/{memory-governance-IMPQZXFC.js.map → adapters/types.js.map} +0 -0
- /package/dist/{path-RMTY5Y5A.js.map → capsule-crypto-5CYAGVC5.js.map} +0 -0
- /package/dist/{capsule-merge-DI7PNQ2H.js.map → capsule-merge-4MGKE7C5.js.map} +0 -0
- /package/dist/{chunk-G4SK7DSQ.js.map → chunk-2WWLHTZY.js.map} +0 -0
- /package/dist/{chunk-X7HPGUVG.js.map → chunk-4CRG46BG.js.map} +0 -0
- /package/dist/{chunk-UVMUAWVT.js.map → chunk-7IASACLB.js.map} +0 -0
- /package/dist/{chunk-HELQZFZO.js.map → chunk-EDTHC6UD.js.map} +0 -0
- /package/dist/{chunk-4YM32CRU.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
- /package/dist/{chunk-E2UCDP5S.js.map → chunk-FBYESMQ2.js.map} +0 -0
- /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
- /package/dist/{chunk-IB3BFHGN.js.map → chunk-GGKRUQOO.js.map} +0 -0
- /package/dist/{chunk-242S3I2A.js.map → chunk-GL6I6MEQ.js.map} +0 -0
- /package/dist/{secure-store-4R2GSO7S.js.map → chunk-HHLLAQGZ.js.map} +0 -0
- /package/dist/{chunk-4IS4SXIQ.js.map → chunk-HXXBL2KD.js.map} +0 -0
- /package/dist/{chunk-767ODGE6.js.map → chunk-KNKUID7G.js.map} +0 -0
- /package/dist/{chunk-6TBWYBJ3.js.map → chunk-LPMVBPA3.js.map} +0 -0
- /package/dist/{chunk-WEJG4TB5.js.map → chunk-MC26UJIM.js.map} +0 -0
- /package/dist/{chunk-JKDVIE52.js.map → chunk-MGKYQQYF.js.map} +0 -0
- /package/dist/{chunk-Y3VMVTYX.js.map → chunk-MT4HVDUZ.js.map} +0 -0
- /package/dist/{chunk-G2WADRQ3.js.map → chunk-MY6TPVXW.js.map} +0 -0
- /package/dist/{chunk-OR64ZGRZ.js.map → chunk-NNVTUXEB.js.map} +0 -0
- /package/dist/{chunk-JESOB2HO.js.map → chunk-P4NEIHUT.js.map} +0 -0
- /package/dist/{chunk-IXEJRKCZ.js.map → chunk-QRNI5JBH.js.map} +0 -0
- /package/dist/{chunk-EEQLFRUM.js.map → chunk-RRF5UOBJ.js.map} +0 -0
- /package/dist/{state-store-3EH7HYIN.js.map → chunk-SEDEKFYQ.js.map} +0 -0
- /package/dist/{chunk-2LGMW3DJ.js.map → chunk-U3PN77QT.js.map} +0 -0
- /package/dist/{chunk-XMVFHBHT.js.map → chunk-U3WSW6PZ.js.map} +0 -0
- /package/dist/{chunk-N5AKDXAI.js.map → chunk-UWVJF25J.js.map} +0 -0
- /package/dist/{types-V3FJ26TF.js.map → chunk-V5OCT34X.js.map} +0 -0
- /package/dist/{chunk-ZG7PTKBK.js.map → chunk-W3LR522O.js.map} +0 -0
- /package/dist/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
- /package/dist/{chunk-ZNQN6ZTA.js.map → chunk-XVZ7B3HG.js.map} +0 -0
- /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
|
+
});
|