@remnic/core 1.1.11 → 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/README.md +3 -3
- package/dist/access-cli.d.ts +2 -1
- package/dist/access-cli.js +293 -104
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +31 -62
- package/dist/access-http.js +53 -35
- package/dist/access-mcp.d.ts +31 -8
- package/dist/access-mcp.js +45 -34
- package/dist/access-schema.d.ts +197 -14
- package/dist/access-schema.js +16 -5
- package/dist/access-service-DcCDmNYC.d.ts +1542 -0
- package/dist/access-service.d.ts +30 -9
- package/dist/access-service.js +42 -32
- package/dist/action-confidence.d.ts +83 -0
- package/dist/action-confidence.js +22 -0
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.d.ts +1 -1
- 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/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +23 -6
- package/dist/boxes.d.ts +7 -0
- package/dist/boxes.js +1 -1
- package/dist/briefing.d.ts +5 -3
- package/dist/briefing.js +10 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-surprise-report.js +1 -1
- package/dist/buffer.d.ts +18 -4
- package/dist/buffer.js +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/calibration.js +6 -6
- 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 +9 -29
- 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 +2 -2
- package/dist/causal-consolidation.js +28 -17
- 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-4RA3C3EV.js +60 -0
- package/dist/chunk-4RA3C3EV.js.map +1 -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-5NXIJZFX.js +180 -0
- package/dist/chunk-5NXIJZFX.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-6NKAQ74D.js +2237 -0
- package/dist/chunk-6NKAQ74D.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-AC5LO7IU.js +308 -0
- package/dist/chunk-AC5LO7IU.js.map +1 -0
- package/dist/chunk-AGZQD76C.js +201 -0
- package/dist/chunk-AGZQD76C.js.map +1 -0
- package/dist/chunk-AH2JUU6X.js +336 -0
- package/dist/chunk-AH2JUU6X.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-C5BCH4ZS.js +317 -0
- package/dist/chunk-C5BCH4ZS.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-DB5A3NHS.js +906 -0
- package/dist/chunk-DB5A3NHS.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-EDTHC6UD.js.map +1 -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-FBYESMQ2.js.map +1 -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-MGKYQQYF.js.map +1 -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-OAZ5MFUB.js +4124 -0
- package/dist/chunk-OAZ5MFUB.js.map +1 -0
- package/dist/chunk-OIGNEXKZ.js +237 -0
- package/dist/chunk-OIGNEXKZ.js.map +1 -0
- package/dist/chunk-OZKZ2TRP.js +3729 -0
- package/dist/chunk-OZKZ2TRP.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-PD6O7AXF.js +110 -0
- package/dist/chunk-PD6O7AXF.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-PYPOFEMK.js +294 -0
- package/dist/chunk-PYPOFEMK.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-QDZ2RLEC.js +908 -0
- package/dist/chunk-QDZ2RLEC.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-RK6F44Y6.js +84 -0
- package/dist/chunk-RK6F44Y6.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-SOAU2OE2.js +125 -0
- package/dist/chunk-SOAU2OE2.js.map +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-V5OCT34X.js.map +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-VWT3F4IV.js +2161 -0
- package/dist/chunk-VWT3F4IV.js.map +1 -0
- package/dist/chunk-W3LR522O.js +2296 -0
- package/dist/chunk-W3LR522O.js.map +1 -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 +42 -10
- package/dist/cli.js +121 -58
- package/dist/codex-cli-fallback.d.ts +1 -0
- package/dist/codex-cli-fallback.js +1 -1
- 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/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +5 -3
- 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 +4 -2
- package/dist/consolidation-undo.d.ts +4 -2
- 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/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer-wiring.js +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- 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 +9 -6
- package/dist/entity-schema.d.ts +1 -1
- package/dist/evals.js +1 -1
- package/dist/event-order-recall.d.ts +17 -0
- package/dist/event-order-recall.js +11 -0
- package/dist/event-order-recall.js.map +1 -0
- package/dist/evidence-pack.d.ts +3 -1
- package/dist/evidence-pack.js +5 -3
- package/dist/explicit-capture.d.ts +23 -6
- package/dist/explicit-capture.js +2 -2
- package/dist/explicit-cue-recall.d.ts +4 -1
- package/dist/explicit-cue-recall.js +4 -2
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge-training.js +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/extraction.js +11 -10
- package/dist/faiss-adapter-CzPghc4C.d.ts +70 -0
- package/dist/fallback-llm.d.ts +4 -1
- package/dist/fallback-llm.js +6 -6
- package/dist/focused-list-recall.d.ts +17 -0
- package/dist/focused-list-recall.js +11 -0
- package/dist/focused-list-recall.js.map +1 -0
- package/dist/graph-edge-decay-5DI5GUNL.js +207 -0
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index-DJ9QWMw-.d.ts +35 -0
- package/dist/index.d.ts +107 -715
- package/dist/index.js +657 -2611
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/intent.js +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/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/live-connectors-runner.js +5 -5
- package/dist/local-llm.d.ts +8 -4
- 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 +124 -0
- package/dist/mcp-memory-inspector-app.js +20 -0
- package/dist/mcp-memory-inspector-app.js.map +1 -0
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +108 -3
- package/dist/memory-projection-store.js +2 -1
- package/dist/memory-provenance.d.ts +57 -0
- package/dist/memory-provenance.js +13 -0
- package/dist/memory-provenance.js.map +1 -0
- 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/models-json.d.ts +1 -1
- 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/native-knowledge.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/objective-state-writers.js +2 -2
- package/dist/operator-toolkit.d.ts +4 -2
- package/dist/operator-toolkit.js +35 -17
- 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 +24 -7
- package/dist/orchestrator.js +107 -65
- package/dist/path-MR5JPYOP.js +9 -0
- package/dist/path-MR5JPYOP.js.map +1 -0
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +2 -2
- package/dist/qmd.d.ts +103 -4
- package/dist/qmd.js +23 -5
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +3 -1
- package/dist/recall-explain-renderer.js +5 -3
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +3 -1
- package/dist/recall-xray-cli.d.ts +3 -1
- package/dist/recall-xray-cli.js +6 -4
- package/dist/recall-xray-renderer.d.ts +3 -1
- package/dist/recall-xray-renderer.js +5 -3
- package/dist/recall-xray.d.ts +8 -1
- package/dist/recall-xray.js +4 -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-auth-token.d.ts +1 -1
- package/dist/resolve-provider-secret.js +2 -2
- package/dist/response-guidance-recall.d.ts +18 -0
- package/dist/response-guidance-recall.js +11 -0
- package/dist/response-guidance-recall.js.map +1 -0
- package/dist/resume-bundles.js +7 -5
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.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/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 +3 -3
- package/dist/semantic-consolidation.js +15 -8
- package/dist/semantic-rule-promotion.js +9 -6
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +10 -7
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- 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/signal.d.ts +1 -1
- 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 +6 -2
- package/dist/storage.js +8 -5
- package/dist/summarizer.d.ts +6 -1
- package/dist/summarizer.js +11 -10
- package/dist/summary-snapshot.d.ts +1 -1
- 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/targeted-fact-recall.d.ts +17 -0
- package/dist/targeted-fact-recall.js +11 -0
- package/dist/targeted-fact-recall.js.map +1 -0
- package/dist/telemetry-transcript.d.ts +7 -0
- package/dist/telemetry-transcript.js +16 -0
- package/dist/telemetry-transcript.js.map +1 -0
- package/dist/temporal-supersession.d.ts +4 -2
- package/dist/temporal-supersession.js +2 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +6 -1
- package/dist/threading.js +2 -1
- package/dist/tier-migration.d.ts +5 -3
- package/dist/tier-routing.d.ts +1 -1
- package/dist/tokens.js +2 -2
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +16 -2
- 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/trust-zones.d.ts +3 -2
- package/dist/trust-zones.js +1 -1
- package/dist/types.d.ts +88 -3
- package/dist/types.js +1 -1
- package/dist/user-model.d.ts +37 -0
- package/dist/user-model.js +28 -0
- package/dist/user-model.js.map +1 -0
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +11 -8
- 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-BkXt3di1.d.ts +0 -2039
- package/dist/capsule-crypto-SJS5VVAP.js +0 -18
- package/dist/capsule-export-LLEVB2RG.js +0 -17
- package/dist/capsule-import-UW45R2MZ.js +0 -16
- package/dist/capsule-merge-DI7PNQ2H.js +0 -189
- package/dist/chunk-2LGMW3DJ.js +0 -111
- package/dist/chunk-2YMTO4ZJ.js +0 -265
- package/dist/chunk-2YMTO4ZJ.js.map +0 -1
- package/dist/chunk-363MWCD3.js +0 -9683
- package/dist/chunk-363MWCD3.js.map +0 -1
- package/dist/chunk-36CTNQY7.js +0 -1554
- package/dist/chunk-36CTNQY7.js.map +0 -1
- package/dist/chunk-457A4P3L.js +0 -119
- package/dist/chunk-457A4P3L.js.map +0 -1
- package/dist/chunk-4DXC6HQQ.js +0 -1837
- package/dist/chunk-4DXC6HQQ.js.map +0 -1
- package/dist/chunk-4IS4SXIQ.js +0 -2040
- package/dist/chunk-57QNCUEZ.js +0 -1914
- package/dist/chunk-57QNCUEZ.js.map +0 -1
- package/dist/chunk-6AUUAZEX.js +0 -150
- package/dist/chunk-6AUUAZEX.js.map +0 -1
- package/dist/chunk-6TBWYBJ3.js +0 -236
- package/dist/chunk-6XA7UN4Z.js +0 -135
- package/dist/chunk-6Z6UH6TK.js +0 -2129
- package/dist/chunk-6Z6UH6TK.js.map +0 -1
- package/dist/chunk-74EMIVE4.js +0 -329
- package/dist/chunk-74EMIVE4.js.map +0 -1
- package/dist/chunk-74WWN7ZW.js +0 -82
- package/dist/chunk-74WWN7ZW.js.map +0 -1
- package/dist/chunk-767ODGE6.js +0 -183
- package/dist/chunk-A4ACKWIW.js +0 -289
- package/dist/chunk-A4ACKWIW.js.map +0 -1
- package/dist/chunk-ASAITVLA.js +0 -64
- package/dist/chunk-ASAITVLA.js.map +0 -1
- package/dist/chunk-C5HUWVH2.js +0 -891
- package/dist/chunk-C5HUWVH2.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-E6K4NIEU.js +0 -747
- package/dist/chunk-E6K4NIEU.js.map +0 -1
- package/dist/chunk-EEQLFRUM.js +0 -89
- package/dist/chunk-EQINRHYR.js +0 -672
- package/dist/chunk-EQINRHYR.js.map +0 -1
- package/dist/chunk-ETOW6ACV.js +0 -158
- package/dist/chunk-ETOW6ACV.js.map +0 -1
- package/dist/chunk-EYNQTST2.js +0 -721
- 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-GGD5W7TB.js +0 -105
- package/dist/chunk-GGD5W7TB.js.map +0 -1
- package/dist/chunk-GVPWB7EY.js +0 -390
- package/dist/chunk-GVPWB7EY.js.map +0 -1
- package/dist/chunk-HJYHRE4S.js +0 -647
- package/dist/chunk-I6BQZSML.js +0 -1451
- package/dist/chunk-I6BQZSML.js.map +0 -1
- package/dist/chunk-IBX3VFOM.js +0 -446
- package/dist/chunk-IBX3VFOM.js.map +0 -1
- 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-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-KBYWQWSB.js +0 -271
- package/dist/chunk-KUHRUM6B.js +0 -14397
- package/dist/chunk-KUHRUM6B.js.map +0 -1
- package/dist/chunk-KWBPHZUU.js +0 -83
- package/dist/chunk-KWBPHZUU.js.map +0 -1
- package/dist/chunk-LIO5X3CM.js +0 -596
- package/dist/chunk-MARWOCVP.js +0 -48
- package/dist/chunk-MCC6KDQF.js +0 -5095
- package/dist/chunk-MCC6KDQF.js.map +0 -1
- package/dist/chunk-N5AKDXAI.js +0 -74
- package/dist/chunk-NN3LPQ5D.js +0 -936
- package/dist/chunk-NN3LPQ5D.js.map +0 -1
- package/dist/chunk-O4XJUPSF.js +0 -533
- package/dist/chunk-O4XJUPSF.js.map +0 -1
- 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-P73JTV34.js +0 -275
- package/dist/chunk-P73JTV34.js.map +0 -1
- package/dist/chunk-P77UEOU2.js +0 -1521
- package/dist/chunk-P77UEOU2.js.map +0 -1
- package/dist/chunk-PB5KW5PL.js +0 -118
- package/dist/chunk-PHNGXFQ6.js +0 -623
- package/dist/chunk-PHNGXFQ6.js.map +0 -1
- package/dist/chunk-QIGOEM65.js +0 -228
- package/dist/chunk-RXTFCYQF.js +0 -108
- package/dist/chunk-S2JJBLJG.js +0 -2101
- package/dist/chunk-S2JJBLJG.js.map +0 -1
- package/dist/chunk-S3IP6R6K.js +0 -219
- package/dist/chunk-S3IP6R6K.js.map +0 -1
- package/dist/chunk-SRBJUAMP.js +0 -403
- package/dist/chunk-SRBJUAMP.js.map +0 -1
- package/dist/chunk-URB2WSKZ.js +0 -350
- package/dist/chunk-URB2WSKZ.js.map +0 -1
- package/dist/chunk-VQXK37XA.js +0 -26
- package/dist/chunk-VQXK37XA.js.map +0 -1
- package/dist/chunk-VTU2B4VF.js +0 -146
- package/dist/chunk-VTU2B4VF.js.map +0 -1
- package/dist/chunk-VX2IUQFE.js +0 -613
- package/dist/chunk-VX2IUQFE.js.map +0 -1
- package/dist/chunk-WGK4VHGP.js +0 -4292
- package/dist/chunk-WGK4VHGP.js.map +0 -1
- package/dist/chunk-WTFWLUSX.js +0 -827
- package/dist/chunk-WTFWLUSX.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-Y5KDIOKF.js +0 -2403
- package/dist/chunk-Y5KDIOKF.js.map +0 -1
- 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-Z5S5HNGY.js +0 -2280
- package/dist/chunk-Z5S5HNGY.js.map +0 -1
- package/dist/chunk-ZL4S7ARC.js +0 -53
- package/dist/chunk-ZTSE2ZJ6.js +0 -190
- package/dist/chunk-ZTSE2ZJ6.js.map +0 -1
- package/dist/cli-Cvy2SNhF.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-3Z6YW7YA.js +0 -413
- package/dist/contradiction-scan-3Z6YW7YA.js.map +0 -1
- package/dist/engine-FOC3IJLA.js +0 -28
- package/dist/fs-utils-IRVUFB6G.js +0 -30
- package/dist/graph-edge-decay-PWB63GRE.js +0 -207
- package/dist/index-1qIcnbG1.d.ts +0 -34
- package/dist/memory-governance-F3QOJGEY.js +0 -37
- package/dist/memory-projection-store-CY8TU40w.d.ts +0 -222
- package/dist/orchestrator-AOQMo7QI.d.ts +0 -1784
- 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 → action-confidence.js.map} +0 -0
- /package/dist/{capsule-export-LLEVB2RG.js.map → adapters/claude-code.js.map} +0 -0
- /package/dist/{capsule-import-UW45R2MZ.js.map → adapters/codex.js.map} +0 -0
- /package/dist/{contradiction-review-5LTTVDQV.js.map → adapters/hermes.js.map} +0 -0
- /package/dist/{engine-FOC3IJLA.js.map → adapters/index.js.map} +0 -0
- /package/dist/{fs-utils-IRVUFB6G.js.map → adapters/registry.js.map} +0 -0
- /package/dist/{memory-governance-F3QOJGEY.js.map → adapters/replit.js.map} +0 -0
- /package/dist/{path-RMTY5Y5A.js.map → adapters/types.js.map} +0 -0
- /package/dist/{secure-store-4R2GSO7S.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-KBYWQWSB.js.map → chunk-4CRG46BG.js.map} +0 -0
- /package/dist/{chunk-LIO5X3CM.js.map → chunk-7IASACLB.js.map} +0 -0
- /package/dist/{chunk-EYNQTST2.js.map → chunk-EFJ3MQ4V.js.map} +0 -0
- /package/dist/{chunk-D54LZC5L.js.map → chunk-FDU6HUUL.js.map} +0 -0
- /package/dist/{chunk-QIGOEM65.js.map → chunk-GGKRUQOO.js.map} +0 -0
- /package/dist/{chunk-HJYHRE4S.js.map → chunk-GL6I6MEQ.js.map} +0 -0
- /package/dist/{state-store-3EH7HYIN.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-PB5KW5PL.js.map → chunk-MC26UJIM.js.map} +0 -0
- /package/dist/{chunk-ZL4S7ARC.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-RXTFCYQF.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/{types-V3FJ26TF.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/{chunk-MARWOCVP.js.map → chunk-XIG5PDM7.js.map} +0 -0
- /package/dist/{chunk-6XA7UN4Z.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,1425 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { parseConfig } from "./config.js";
|
|
8
|
+
import { ContentHashIndex, StorageManager } from "./storage.js";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_CITATION_FORMAT,
|
|
11
|
+
attachCitation,
|
|
12
|
+
deriveSessionId,
|
|
13
|
+
formatCitation,
|
|
14
|
+
hasCitation,
|
|
15
|
+
hasCitationForTemplate,
|
|
16
|
+
parseAllCitations,
|
|
17
|
+
parseCitation,
|
|
18
|
+
stripCitation,
|
|
19
|
+
} from "./source-attribution.js";
|
|
20
|
+
|
|
21
|
+
test("formatCitation emits the default template with provided fields", () => {
|
|
22
|
+
const out = formatCitation({
|
|
23
|
+
agent: "planner",
|
|
24
|
+
session: "agent:planner:main",
|
|
25
|
+
ts: "2026-04-10T14:25:07Z",
|
|
26
|
+
});
|
|
27
|
+
assert.equal(
|
|
28
|
+
out,
|
|
29
|
+
"[Source: agent=planner, session=main, ts=2026-04-10T14:25:07Z]",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("formatCitation falls back to 'unknown' for missing fields", () => {
|
|
34
|
+
const out = formatCitation({});
|
|
35
|
+
assert.equal(out, "[Source: agent=unknown, session=unknown, ts=unknown]");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("formatCitation supports custom templates with all placeholders", () => {
|
|
39
|
+
const template = "[src:{agent}/{session}@{date}]";
|
|
40
|
+
const out = formatCitation(
|
|
41
|
+
{
|
|
42
|
+
agent: "scout",
|
|
43
|
+
session: "agent:scout:alpha",
|
|
44
|
+
ts: "2026-04-10T14:25:07Z",
|
|
45
|
+
},
|
|
46
|
+
template,
|
|
47
|
+
);
|
|
48
|
+
// session placeholder uses the full colon-delimited session key.
|
|
49
|
+
assert.equal(out, "[src:scout/agent:scout:alpha@2026-04-10]");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("deriveSessionId returns the trailing component of a colon-delimited key", () => {
|
|
53
|
+
assert.equal(deriveSessionId("agent:planner:main"), "main");
|
|
54
|
+
assert.equal(deriveSessionId("single"), "single");
|
|
55
|
+
assert.equal(deriveSessionId(undefined), undefined);
|
|
56
|
+
assert.equal(deriveSessionId(""), undefined);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("attachCitation appends a marker when none is present", () => {
|
|
60
|
+
const text = "The foo service uses Redis for rate limiting.";
|
|
61
|
+
const out = attachCitation(text, {
|
|
62
|
+
agent: "planner",
|
|
63
|
+
session: "agent:planner:main",
|
|
64
|
+
ts: "2026-04-10T14:25:07Z",
|
|
65
|
+
});
|
|
66
|
+
assert.ok(out.startsWith(text));
|
|
67
|
+
assert.ok(
|
|
68
|
+
out.includes("[Source: agent=planner, session=main, ts=2026-04-10T14:25:07Z]"),
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("attachCitation is a no-op when the text already carries a citation", () => {
|
|
73
|
+
const text =
|
|
74
|
+
"Already tagged. [Source: agent=foo, session=bar, ts=2026-01-01T00:00:00Z]";
|
|
75
|
+
const out = attachCitation(text, {
|
|
76
|
+
agent: "other",
|
|
77
|
+
session: "other:session",
|
|
78
|
+
ts: "2026-04-10T14:25:07Z",
|
|
79
|
+
});
|
|
80
|
+
assert.equal(out, text);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("attachCitation preserves trailing newlines for markdown rendering", () => {
|
|
84
|
+
const text = "Fact body.\n";
|
|
85
|
+
const out = attachCitation(text, {
|
|
86
|
+
agent: "a",
|
|
87
|
+
session: "s:1",
|
|
88
|
+
ts: "2026-04-10T00:00:00Z",
|
|
89
|
+
});
|
|
90
|
+
assert.ok(out.endsWith("\n"));
|
|
91
|
+
assert.ok(out.includes("[Source: agent=a"));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("parseCitation extracts agent, session, and timestamp", () => {
|
|
95
|
+
const text =
|
|
96
|
+
"Body of the fact. [Source: agent=planner, session=abc123, ts=2026-04-10T14:25:07Z]";
|
|
97
|
+
const parsed = parseCitation(text);
|
|
98
|
+
assert.ok(parsed);
|
|
99
|
+
assert.equal(parsed!.agent, "planner");
|
|
100
|
+
assert.equal(parsed!.session, "abc123");
|
|
101
|
+
assert.equal(parsed!.ts, "2026-04-10T14:25:07Z");
|
|
102
|
+
assert.ok(parsed!.raw.startsWith("[Source:"));
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("parseCitation returns null when no citation is present", () => {
|
|
106
|
+
assert.equal(parseCitation("no citation here"), null);
|
|
107
|
+
assert.equal(parseCitation(""), null);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("parseCitation tolerates malformed fields without throwing", () => {
|
|
111
|
+
const parsed = parseCitation("[Source: agent=bob, broken-field, ts=]");
|
|
112
|
+
assert.ok(parsed);
|
|
113
|
+
assert.equal(parsed!.agent, "bob");
|
|
114
|
+
assert.equal(parsed!.session, undefined);
|
|
115
|
+
assert.equal(parsed!.ts, undefined);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("parseAllCitations returns every citation in order", () => {
|
|
119
|
+
const text =
|
|
120
|
+
"First [Source: agent=a, session=s1, ts=2026-04-10T00:00:00Z] and " +
|
|
121
|
+
"second [Source: agent=b, session=s2, ts=2026-04-11T00:00:00Z]";
|
|
122
|
+
const all = parseAllCitations(text);
|
|
123
|
+
assert.equal(all.length, 2);
|
|
124
|
+
assert.equal(all[0]!.agent, "a");
|
|
125
|
+
assert.equal(all[1]!.agent, "b");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("hasCitation returns true only when a marker is present", () => {
|
|
129
|
+
assert.equal(hasCitation("nothing tagged"), false);
|
|
130
|
+
assert.equal(hasCitation(""), false);
|
|
131
|
+
assert.equal(
|
|
132
|
+
hasCitation("Tagged. [Source: agent=x, session=y, ts=z]"),
|
|
133
|
+
true,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("stripCitation removes inline markers cleanly", () => {
|
|
138
|
+
const text =
|
|
139
|
+
"Body of the fact. [Source: agent=planner, session=abc123, ts=2026-04-10T14:25:07Z]";
|
|
140
|
+
assert.equal(stripCitation(text), "Body of the fact.");
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("attach → strip is idempotent for well-formed fact text", () => {
|
|
144
|
+
const original = "The foo service uses Redis for rate limiting.";
|
|
145
|
+
const attached = attachCitation(original, {
|
|
146
|
+
agent: "planner",
|
|
147
|
+
session: "agent:planner:main",
|
|
148
|
+
ts: "2026-04-10T14:25:07Z",
|
|
149
|
+
});
|
|
150
|
+
assert.ok(hasCitation(attached));
|
|
151
|
+
assert.equal(stripCitation(attached), original);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("stripCitation leaves plain text untouched", () => {
|
|
155
|
+
assert.equal(stripCitation("no markers"), "no markers");
|
|
156
|
+
assert.equal(stripCitation(""), "");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("DEFAULT_CITATION_FORMAT matches issue #369 proposal", () => {
|
|
160
|
+
assert.equal(
|
|
161
|
+
DEFAULT_CITATION_FORMAT,
|
|
162
|
+
"[Source: agent={agent}, session={sessionId}, ts={ts}]",
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("parseConfig disables inline source attribution by default", () => {
|
|
167
|
+
const cfg = parseConfig({});
|
|
168
|
+
assert.equal(cfg.inlineSourceAttributionEnabled, false);
|
|
169
|
+
assert.equal(
|
|
170
|
+
cfg.inlineSourceAttributionFormat,
|
|
171
|
+
"[Source: agent={agent}, session={sessionId}, ts={ts}]",
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("parseConfig honors explicit inline source attribution overrides", () => {
|
|
176
|
+
const cfg = parseConfig({
|
|
177
|
+
inlineSourceAttributionEnabled: true,
|
|
178
|
+
inlineSourceAttributionFormat: "[src:{agent}/{sessionId}@{date}]",
|
|
179
|
+
});
|
|
180
|
+
assert.equal(cfg.inlineSourceAttributionEnabled, true);
|
|
181
|
+
assert.equal(
|
|
182
|
+
cfg.inlineSourceAttributionFormat,
|
|
183
|
+
"[src:{agent}/{sessionId}@{date}]",
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("parseConfig falls back to default format when override is empty", () => {
|
|
188
|
+
const cfg = parseConfig({
|
|
189
|
+
inlineSourceAttributionEnabled: true,
|
|
190
|
+
inlineSourceAttributionFormat: " ",
|
|
191
|
+
});
|
|
192
|
+
assert.equal(
|
|
193
|
+
cfg.inlineSourceAttributionFormat,
|
|
194
|
+
"[Source: agent={agent}, session={sessionId}, ts={ts}]",
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ── Finding 1 regression: custom citation template dedup detection ────────────
|
|
199
|
+
|
|
200
|
+
test("hasCitationForTemplate detects default [Source:...] marker regardless of template", () => {
|
|
201
|
+
const text = "Fact body. [Source: agent=planner, session=main, ts=2026-04-10T00:00:00Z]";
|
|
202
|
+
// Default template
|
|
203
|
+
assert.equal(hasCitationForTemplate(text, DEFAULT_CITATION_FORMAT), true);
|
|
204
|
+
// Custom template — should still detect the default marker as a fallback
|
|
205
|
+
assert.equal(hasCitationForTemplate(text, "[src:{agent}/{sessionId}@{date}]"), true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("hasCitationForTemplate detects a custom-format citation", () => {
|
|
209
|
+
const customTemplate = "[src:{agent}/{sessionId}@{date}]";
|
|
210
|
+
const text = "Fact body. [src:planner/main@2026-04-10]";
|
|
211
|
+
assert.equal(hasCitationForTemplate(text, customTemplate), true);
|
|
212
|
+
// Should not falsely match plain text
|
|
213
|
+
assert.equal(hasCitationForTemplate("Fact body.", customTemplate), false);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("hasCitationForTemplate returns false for empty / non-string inputs", () => {
|
|
217
|
+
assert.equal(hasCitationForTemplate("", DEFAULT_CITATION_FORMAT), false);
|
|
218
|
+
assert.equal(hasCitationForTemplate("no citation here", DEFAULT_CITATION_FORMAT), false);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("attachCitation with custom template is a no-op when text already carries that custom marker (Finding 1)", () => {
|
|
222
|
+
const customTemplate = "[src:{agent}/{sessionId}@{date}]";
|
|
223
|
+
const ctx = { agent: "planner", session: "agent:planner:main", ts: "2026-04-10T14:25:07Z" };
|
|
224
|
+
// Pre-tag the fact with a custom citation
|
|
225
|
+
const priorCitation = "[src:other/alpha@2026-01-01]";
|
|
226
|
+
const text = `Existing fact. ${priorCitation}`;
|
|
227
|
+
const result = attachCitation(text, ctx, customTemplate);
|
|
228
|
+
// Must be unchanged — no second citation appended
|
|
229
|
+
assert.equal(result, text);
|
|
230
|
+
// Confirm only one citation marker is present
|
|
231
|
+
const markerCount = (result.match(/\[src:/g) ?? []).length;
|
|
232
|
+
assert.equal(markerCount, 1);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("attachCitation with custom template tags untagged text exactly once (Finding 1 positive path)", () => {
|
|
236
|
+
const customTemplate = "[src:{agent}/{sessionId}@{date}]";
|
|
237
|
+
const ctx = { agent: "scout", session: "agent:scout:beta", ts: "2026-04-11T00:00:00Z" };
|
|
238
|
+
const text = "The service uses Redis for caching.";
|
|
239
|
+
const result = attachCitation(text, ctx, customTemplate);
|
|
240
|
+
// Should end with one custom citation
|
|
241
|
+
assert.ok(result.includes("[src:scout/beta@2026-04-11]"), `expected custom citation in: ${result}`);
|
|
242
|
+
// Applying again must be idempotent
|
|
243
|
+
const again = attachCitation(result, ctx, customTemplate);
|
|
244
|
+
assert.equal(again, result);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ── Finding 2 regression: placeholder-bounded template matcher ─────────────────
|
|
248
|
+
|
|
249
|
+
test("hasCitationForTemplate with placeholder-bounded template requires bracket-wrapped match", () => {
|
|
250
|
+
// Template starts AND ends with a placeholder — prefix and suffix are both "".
|
|
251
|
+
// The new strategy requires the match to be wrapped in a bracket/paren/angle
|
|
252
|
+
// pair, so ordinary prose that merely contains the middle literal must NOT
|
|
253
|
+
// be classified as a citation.
|
|
254
|
+
const template = "{source}: {content}";
|
|
255
|
+
// Bracket-wrapped form that a real citation would use: matches.
|
|
256
|
+
assert.equal(hasCitationForTemplate("Fact body. [planner: some-note]", template), true);
|
|
257
|
+
// Prose containing a ": " separator must NOT match — no bracket wrapping.
|
|
258
|
+
assert.equal(hasCitationForTemplate("planner: The service uses Redis", template), false);
|
|
259
|
+
// Random text without the middle literal must NOT match.
|
|
260
|
+
assert.equal(hasCitationForTemplate("random text without separator", template), false);
|
|
261
|
+
assert.equal(hasCitationForTemplate("no separator here at all", template), false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("hasCitationForTemplate with fully placeholder-only template returns false (cannot anchor)", () => {
|
|
265
|
+
// Template has no literal segments at all — templateMatcher returns null.
|
|
266
|
+
// All-placeholder templates cannot be reliably detected without sentinel
|
|
267
|
+
// markers; hasCitationForTemplate returns false for this shape.
|
|
268
|
+
const template = "{source}{content}";
|
|
269
|
+
assert.equal(hasCitationForTemplate("anything goes here", template), false);
|
|
270
|
+
// Even a text that literally contains the raw template syntax is not
|
|
271
|
+
// recognised — the function deliberately refuses to fall back to a naive
|
|
272
|
+
// text.includes check, because that check would never match a rendered
|
|
273
|
+
// citation body anyway (placeholders have been substituted).
|
|
274
|
+
assert.equal(hasCitationForTemplate("{source}{content}", template), false);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("hasCitationForTemplate preserves normal behaviour for well-formed templates (Finding 2 non-regression)", () => {
|
|
278
|
+
// Well-formed template with non-empty prefix and suffix — existing behaviour.
|
|
279
|
+
const template = "Source: {source} — Content: {content}";
|
|
280
|
+
assert.equal(hasCitationForTemplate("Source: planner — Content: some fact", template), true);
|
|
281
|
+
assert.equal(hasCitationForTemplate("random unrelated text", template), false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test("hasCitationForTemplate: placeholder-bounded template does not falsely tag plain text (Finding 2 negative)", () => {
|
|
285
|
+
const template = "{source}: {content}";
|
|
286
|
+
// Plain text with no colon-space separator must return false.
|
|
287
|
+
assert.equal(hasCitationForTemplate("just a plain statement", template), false);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("hasCitationForTemplate: {agent}:{sessionId} template rejects embedded URL colons", () => {
|
|
291
|
+
// Regression for Cursor High: the previous implementation anchored on the
|
|
292
|
+
// first non-empty middle literal alone — for this template that's just ":",
|
|
293
|
+
// which false-positives on any text containing a colon (URLs, paths, any
|
|
294
|
+
// other prose). The stricter reconstruction requires identifier-shaped
|
|
295
|
+
// tokens on both sides of the literal, bounded by clean delimiters, which
|
|
296
|
+
// in particular rejects the `http://host:80` shape.
|
|
297
|
+
const template = "{agent}:{sessionId}";
|
|
298
|
+
assert.equal(
|
|
299
|
+
hasCitationForTemplate("URL uses http://host:80", template),
|
|
300
|
+
false,
|
|
301
|
+
"a colon inside a URL must not be classified as a citation",
|
|
302
|
+
);
|
|
303
|
+
assert.equal(
|
|
304
|
+
hasCitationForTemplate("plain statement without a colon", template),
|
|
305
|
+
false,
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("hasCitationForTemplate: {agent}:{sessionId} template accepts a real citation-shaped token", () => {
|
|
310
|
+
// Positive case — a bracket-wrapped agent:sessionId token looks like an
|
|
311
|
+
// inline citation and should be detected so attachCitation stays idempotent.
|
|
312
|
+
const template = "{agent}:{sessionId}";
|
|
313
|
+
assert.equal(
|
|
314
|
+
hasCitationForTemplate("[backend-agent:abc123] some text", template),
|
|
315
|
+
true,
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ── Finding 1 dedup regression: same raw content, different timestamps ─────────
|
|
320
|
+
|
|
321
|
+
// ── Finding A regression: $ special patterns in replacement strings ───────────
|
|
322
|
+
|
|
323
|
+
test("formatCitation: agent value containing $& is not expanded by replace", () => {
|
|
324
|
+
// $& is the JS replacement special pattern that inserts the matched substring.
|
|
325
|
+
// With the replacer-function form it must be treated as a literal string.
|
|
326
|
+
const out = formatCitation(
|
|
327
|
+
{ agent: "agent-with-$&-literal", session: "sess:abc", ts: "2026-04-11T00:00:00Z" },
|
|
328
|
+
);
|
|
329
|
+
assert.ok(
|
|
330
|
+
out.includes("agent-with-$&-literal"),
|
|
331
|
+
`expected literal $& in output, got: ${out}`,
|
|
332
|
+
);
|
|
333
|
+
// Verify the placeholder was not expanded to the matched regex text either.
|
|
334
|
+
assert.ok(!out.includes("{agent}"), "placeholder must be replaced");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("formatCitation: session value containing $` (backtick) is not corrupted", () => {
|
|
338
|
+
// $` inserts the string before the match. Must be literal here.
|
|
339
|
+
const session = "sess:$`backtick";
|
|
340
|
+
const out = formatCitation(
|
|
341
|
+
{ agent: "planner", session, ts: "2026-04-11T00:00:00Z" },
|
|
342
|
+
);
|
|
343
|
+
// The full session key doesn't appear in the default template (sessionId is used),
|
|
344
|
+
// so test via a custom template that includes {session}.
|
|
345
|
+
const tmpl = "[S: agent={agent}, session={session}, ts={ts}]";
|
|
346
|
+
const out2 = formatCitation({ agent: "planner", session, ts: "2026-04-11T00:00:00Z" }, tmpl);
|
|
347
|
+
assert.ok(
|
|
348
|
+
out2.includes("sess:$`backtick"),
|
|
349
|
+
`expected literal session with $\` in output, got: ${out2}`,
|
|
350
|
+
);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("formatCitation: agent value $1$2 stays literal (not resolved to empty groups)", () => {
|
|
354
|
+
// $1 / $2 are capturing-group back-references in replace(). They must not be
|
|
355
|
+
// resolved when the replacer-function form is used (the regex has no groups anyway,
|
|
356
|
+
// but with a string replacement they still produce empty strings on some engines).
|
|
357
|
+
const out = formatCitation(
|
|
358
|
+
{ agent: "$1$2", session: "sess:main", ts: "2026-04-11T00:00:00Z" },
|
|
359
|
+
);
|
|
360
|
+
assert.ok(
|
|
361
|
+
out.includes("$1$2"),
|
|
362
|
+
`expected literal $1$2 in output, got: ${out}`,
|
|
363
|
+
);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("attachCitation is idempotent across different timestamps for the same raw content (Finding 1 dedup)", () => {
|
|
367
|
+
// Simulates the dedup scenario: the same raw fact content is presented twice
|
|
368
|
+
// to applyInlineCitation with different "now" values (different timestamps).
|
|
369
|
+
// The CITED content varies each call, but the RAW content is the same.
|
|
370
|
+
// This test verifies that hasCitationForTemplate correctly sees already-cited
|
|
371
|
+
// text as tagged regardless of the exact timestamp in the marker.
|
|
372
|
+
const rawContent = "The database uses PostgreSQL for persistent storage.";
|
|
373
|
+
const template = DEFAULT_CITATION_FORMAT;
|
|
374
|
+
|
|
375
|
+
const ctx1 = { agent: "planner", session: "agent:planner:main", ts: "2026-04-11T10:00:00Z" };
|
|
376
|
+
const ctx2 = { agent: "planner", session: "agent:planner:main", ts: "2026-04-11T10:05:00Z" };
|
|
377
|
+
|
|
378
|
+
const cited1 = attachCitation(rawContent, ctx1, template);
|
|
379
|
+
// cited1 includes ts=2026-04-11T10:00:00Z
|
|
380
|
+
assert.ok(cited1.includes("2026-04-11T10:00:00Z"), "first citation should include first timestamp");
|
|
381
|
+
|
|
382
|
+
// A second attachCitation call on already-cited text (different ts) must be a no-op.
|
|
383
|
+
const cited2 = attachCitation(cited1, ctx2, template);
|
|
384
|
+
assert.equal(cited2, cited1, "second attachCitation must not append a second marker");
|
|
385
|
+
|
|
386
|
+
// hasCitationForTemplate must return true for cited1 regardless of template/ts.
|
|
387
|
+
assert.equal(hasCitationForTemplate(cited1, template), true);
|
|
388
|
+
|
|
389
|
+
// The raw content itself should NOT be seen as already-cited.
|
|
390
|
+
assert.equal(hasCitationForTemplate(rawContent, template), false);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// ── Finding B regression: shared-store dedup indexes raw content hash ─────────
|
|
394
|
+
|
|
395
|
+
test("ContentHashIndex.add indexes raw content; has() returns true for the same raw string", async () => {
|
|
396
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-hash-idx-"));
|
|
397
|
+
try {
|
|
398
|
+
const idx = new ContentHashIndex(dir);
|
|
399
|
+
await idx.load();
|
|
400
|
+
const rawContent = "The database uses PostgreSQL for persistent storage.";
|
|
401
|
+
idx.add(rawContent);
|
|
402
|
+
assert.ok(idx.has(rawContent), "has() must return true for a string just added");
|
|
403
|
+
// Simulate what would happen if we indexed the cited variant instead
|
|
404
|
+
const citedContent = `${rawContent} [Source: agent=planner, session=main, ts=2026-04-11T10:00:00Z]`;
|
|
405
|
+
assert.ok(!idx.has(citedContent), "has() must return false for the cited variant when only raw was added");
|
|
406
|
+
} finally {
|
|
407
|
+
await rm(dir, { recursive: true, force: true });
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("StorageManager.writeMemory with contentHashSource registers raw-content hash (Finding B)", async () => {
|
|
412
|
+
// This test verifies that writing with contentHashSource=rawContent persists the
|
|
413
|
+
// RAW content hash to the on-disk fact-hashes.txt index. A new StorageManager
|
|
414
|
+
// instance (simulating a subsequent extraction session) should find the raw fact
|
|
415
|
+
// via hasFactContentHash(rawContent) because the persisted hash index carries the
|
|
416
|
+
// raw hash — not the cited hash. Without the fix, only the cited hash would be
|
|
417
|
+
// persisted, and cross-session dedup of the same raw fact would fail.
|
|
418
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-shared-dedup-"));
|
|
419
|
+
try {
|
|
420
|
+
const rawContent = "The service caches reads with Redis for low-latency access.";
|
|
421
|
+
const citedContent = `${rawContent} [Source: agent=planner, session=main, ts=2026-04-11T10:00:00Z]`;
|
|
422
|
+
|
|
423
|
+
// Session 1: write the cited variant, register the RAW content hash for dedup.
|
|
424
|
+
{
|
|
425
|
+
const storage1 = new StorageManager(dir);
|
|
426
|
+
await storage1.writeMemory("fact", citedContent, {
|
|
427
|
+
source: "extraction",
|
|
428
|
+
contentHashSource: rawContent,
|
|
429
|
+
});
|
|
430
|
+
// Same-session: raw content hash must be present in the in-memory index.
|
|
431
|
+
const foundByRaw = await storage1.hasFactContentHash(rawContent);
|
|
432
|
+
assert.ok(foundByRaw, "hasFactContentHash(rawContent) must be true in the same session");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Session 2: new StorageManager instance simulating a subsequent extraction run.
|
|
436
|
+
// The fact-hashes.txt on disk should contain the raw content hash so that
|
|
437
|
+
// hasFactContentHash(rawContent) returns true without seeing the raw fact body.
|
|
438
|
+
{
|
|
439
|
+
const storage2 = new StorageManager(dir);
|
|
440
|
+
// A different timestamp would produce a different citedContent, so the
|
|
441
|
+
// cross-session dedup must rely on the persisted rawContent hash.
|
|
442
|
+
const foundByRawCrossSession = await storage2.hasFactContentHash(rawContent);
|
|
443
|
+
assert.ok(
|
|
444
|
+
foundByRawCrossSession,
|
|
445
|
+
"hasFactContentHash(rawContent) must be true in a new session via persisted hash index",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
} finally {
|
|
449
|
+
await rm(dir, { recursive: true, force: true });
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test("StorageManager.writeMemory contentHashSource prevents duplicate promotion cross-session (Finding B dedup regression)", async () => {
|
|
454
|
+
// Simulates two extraction sessions with the same raw fact but different timestamps.
|
|
455
|
+
// Session 1 promotes the fact (cited1). Session 2 (new StorageManager) checks
|
|
456
|
+
// hasFactContentHash(rawFact) — it must return true so the promotion is skipped.
|
|
457
|
+
//
|
|
458
|
+
// Without the fix: session 1 would persist the citedContent hash. Session 2
|
|
459
|
+
// backfills from disk (adds citedContent hash), but hasFactContentHash(rawFact)
|
|
460
|
+
// would only match if rawFact hash was also persisted — which it was not.
|
|
461
|
+
// With the fix: session 1 persists the rawFact hash via contentHashSource.
|
|
462
|
+
// Session 2 loads it from fact-hashes.txt and hasFactContentHash(rawFact) is true.
|
|
463
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-dedup-regression-"));
|
|
464
|
+
try {
|
|
465
|
+
const rawFact = "PostgreSQL is used for durable persistent storage of user profiles.";
|
|
466
|
+
const cited1 = `${rawFact} [Source: agent=planner, session=main, ts=2026-04-11T10:00:00Z]`;
|
|
467
|
+
|
|
468
|
+
// Session 1: first promotion — write cited body but index raw hash.
|
|
469
|
+
{
|
|
470
|
+
const storage1 = new StorageManager(dir);
|
|
471
|
+
await storage1.writeMemory("fact", cited1, {
|
|
472
|
+
source: "extraction-shared-promotion",
|
|
473
|
+
tags: ["shared-promotion"],
|
|
474
|
+
contentHashSource: rawFact,
|
|
475
|
+
});
|
|
476
|
+
// Confirm same-session dedup gate works.
|
|
477
|
+
assert.ok(
|
|
478
|
+
await storage1.hasFactContentHash(rawFact),
|
|
479
|
+
"Session 1: hasFactContentHash(rawFact) must be true after first promotion",
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Session 2: new StorageManager (fresh process, no in-memory state).
|
|
484
|
+
// The on-disk fact-hashes.txt must carry rawFact hash so dedup blocks re-promotion.
|
|
485
|
+
{
|
|
486
|
+
const storage2 = new StorageManager(dir);
|
|
487
|
+
// Second extraction produces cited2 with a later timestamp. Before writing,
|
|
488
|
+
// the caller checks hasFactContentHash(rawFact) — must return true to skip.
|
|
489
|
+
const wouldDeduplicate = await storage2.hasFactContentHash(rawFact);
|
|
490
|
+
assert.ok(
|
|
491
|
+
wouldDeduplicate,
|
|
492
|
+
"Session 2: hasFactContentHash(rawFact) must return true to prevent re-promotion",
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
} finally {
|
|
496
|
+
await rm(dir, { recursive: true, force: true });
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// ── PR #401 review findings: stricter template matcher + single-pass format ───
|
|
501
|
+
|
|
502
|
+
test("hasCitationForTemplate: '{agent} {sessionId}' rejects plain two-word prose", () => {
|
|
503
|
+
// Regression: the previous reconstruction used `[\w.-]+ [\w.-]+` with soft
|
|
504
|
+
// whitespace/start anchors, so `"The service responded"` matched — two
|
|
505
|
+
// English words separated by a space. The bracket-delimited anchor in the
|
|
506
|
+
// new strategy rejects prose unambiguously.
|
|
507
|
+
const template = "{agent} {sessionId}";
|
|
508
|
+
assert.equal(
|
|
509
|
+
hasCitationForTemplate("The service responded", template),
|
|
510
|
+
false,
|
|
511
|
+
"two-word English prose must never be classified as a citation",
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
test("hasCitationForTemplate: '{agent} {sessionId}' accepts a bracket-wrapped token", () => {
|
|
516
|
+
// Positive case — the exact shape that attachCitation would emit when the
|
|
517
|
+
// fact body already ends with a bracketed citation token.
|
|
518
|
+
const template = "{agent} {sessionId}";
|
|
519
|
+
assert.equal(
|
|
520
|
+
hasCitationForTemplate("[backend-agent session-abc123]", template),
|
|
521
|
+
true,
|
|
522
|
+
"bracket-wrapped identifier pair must be recognised as a citation",
|
|
523
|
+
);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test("hasCitationForTemplate: '{agent}{sessionId}' all-placeholder template returns false", () => {
|
|
527
|
+
// Regression for PR #401: the previous implementation fell through to
|
|
528
|
+
// text.includes(template), which compared the substituted citation against
|
|
529
|
+
// the raw placeholder syntax — always false — causing attachCitation to
|
|
530
|
+
// append a fresh citation on every reprocessing pass. The new behaviour
|
|
531
|
+
// explicitly returns false for all-placeholder templates and documents the
|
|
532
|
+
// limitation so callers cannot rely on (unreliable) dedup for this shape.
|
|
533
|
+
const template = "{agent}{sessionId}";
|
|
534
|
+
assert.equal(
|
|
535
|
+
hasCitationForTemplate("alphaomega", template),
|
|
536
|
+
false,
|
|
537
|
+
"no sentinel / no literal — must return false",
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("formatCitation: substituted values containing placeholder syntax are not re-interpreted", () => {
|
|
542
|
+
// Regression for PR #401 (low-severity): the previous implementation chained
|
|
543
|
+
// `.replace(/\{agent\}/g, ...)`, `.replace(/\{ts\}/g, ...)` etc. If the agent
|
|
544
|
+
// value was literally `"{ts}"`, step 1 substituted `{agent}` with the string
|
|
545
|
+
// `{ts}`, and the later {ts} pass replaced THAT into the actual timestamp —
|
|
546
|
+
// producing the timestamp in the agent slot.
|
|
547
|
+
//
|
|
548
|
+
// The single-pass implementation scans the template once and each matched
|
|
549
|
+
// `{name}` token is replaced by exactly one lookup, so substituted values
|
|
550
|
+
// are terminal and cannot re-trigger replacement.
|
|
551
|
+
const out = formatCitation(
|
|
552
|
+
{ agent: "{ts}", sessionId: "s1", ts: "T" },
|
|
553
|
+
"{agent}:{sessionId}:{ts}",
|
|
554
|
+
);
|
|
555
|
+
assert.equal(
|
|
556
|
+
out,
|
|
557
|
+
"{ts}:s1:T",
|
|
558
|
+
"agent slot must carry the literal string '{ts}', not the timestamp",
|
|
559
|
+
);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
test("formatCitation: single-pass substitution handles nested placeholder syntax in session value", () => {
|
|
563
|
+
// Complementary case for session / sessionId values that look like
|
|
564
|
+
// placeholders. Every slot must be filled from its own lookup, never from a
|
|
565
|
+
// prior substitution.
|
|
566
|
+
const out = formatCitation(
|
|
567
|
+
{ agent: "a1", session: "sess:{date}", ts: "2026-04-11T00:00:00Z" },
|
|
568
|
+
"[S: agent={agent}, session={session}, date={date}]",
|
|
569
|
+
);
|
|
570
|
+
// The `{date}` inside the session value must remain literal; the `{date}`
|
|
571
|
+
// placeholder at the end of the template must resolve to 2026-04-11.
|
|
572
|
+
assert.equal(
|
|
573
|
+
out,
|
|
574
|
+
"[S: agent=a1, session=sess:{date}, date=2026-04-11]",
|
|
575
|
+
);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
// ── PR #401 P2 finding (PRRT_kwDORJXyws56UCB6): separator-only placeholder templates ──
|
|
579
|
+
|
|
580
|
+
test("formatCitation: '{agent}:{sessionId}' produces compact token without brackets", () => {
|
|
581
|
+
// Baseline: confirm that formatCitation never adds brackets for this template.
|
|
582
|
+
// The P2 finding was that hasCitationForTemplate could not detect the
|
|
583
|
+
// already-emitted 'planner:main' token, causing attachCitation to re-append.
|
|
584
|
+
const out = formatCitation(
|
|
585
|
+
{ agent: "planner", session: "agent:planner:main", ts: "2026-04-10T14:25:07Z" },
|
|
586
|
+
"{agent}:{sessionId}",
|
|
587
|
+
);
|
|
588
|
+
assert.equal(out, "planner:main");
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test("hasCitationForTemplate: '{agent}:{sessionId}' detects standalone compact token", () => {
|
|
592
|
+
// Core fix: the pattern must match 'planner:main' when it appears at the
|
|
593
|
+
// start of the string (no preceding context).
|
|
594
|
+
assert.equal(
|
|
595
|
+
hasCitationForTemplate("planner:main", "{agent}:{sessionId}"),
|
|
596
|
+
true,
|
|
597
|
+
"compact citation at start of string must be detected",
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
test("hasCitationForTemplate: '{agent}:{sessionId}' detects token at end of string", () => {
|
|
602
|
+
// Finding 1 fix: compact tokens are only accepted when they appear at the
|
|
603
|
+
// very end of the string (or inside brackets). Since `attachCitation` always
|
|
604
|
+
// appends the citation at the trimmed tail of the fact body, a real emitted
|
|
605
|
+
// citation will always satisfy this condition.
|
|
606
|
+
assert.equal(
|
|
607
|
+
hasCitationForTemplate("Fact body. planner:main", "{agent}:{sessionId}"),
|
|
608
|
+
true,
|
|
609
|
+
"compact citation at end of string must be detected",
|
|
610
|
+
);
|
|
611
|
+
// A compact token embedded in the MIDDLE of prose is no longer accepted.
|
|
612
|
+
// This is the deliberate tightening that prevents false positives on prose
|
|
613
|
+
// like "long-term" or "host:port" embedded in a fact body.
|
|
614
|
+
assert.equal(
|
|
615
|
+
hasCitationForTemplate("The service planner:main is done.", "{agent}:{sessionId}"),
|
|
616
|
+
false,
|
|
617
|
+
"compact citation surrounded by prose must NOT be classified as a citation (Finding 1 fix)",
|
|
618
|
+
);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("hasCitationForTemplate: '{agent}:{sessionId}' rejects URL-embedded colons", () => {
|
|
622
|
+
// Regression guard: 'host:80' inside 'http://host:80' must NOT match.
|
|
623
|
+
// Trace: trying 'host:80' — the char before 'h' is '/' (non-whitespace,
|
|
624
|
+
// non-bracket), so both (?<=[\[\(\<]) and (?<!\S) fail. Trying 'http:...'
|
|
625
|
+
// — after 'http:' the next chars are '//' which are not [\w.-]+, so the
|
|
626
|
+
// second id-token group fails. Neither attempt matches.
|
|
627
|
+
assert.equal(
|
|
628
|
+
hasCitationForTemplate("URL uses http://host:80", "{agent}:{sessionId}"),
|
|
629
|
+
false,
|
|
630
|
+
"colon inside a URL must not be classified as a citation",
|
|
631
|
+
);
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
test("hasCitationForTemplate: '{agent}:{sessionId}' rejects plain text without colon", () => {
|
|
635
|
+
assert.equal(
|
|
636
|
+
hasCitationForTemplate("Plain text with no colon", "{agent}:{sessionId}"),
|
|
637
|
+
false,
|
|
638
|
+
);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
test("hasCitationForTemplate: '{agent}:{sessionId}' accepts bracket-wrapped token", () => {
|
|
642
|
+
// A bracket-wrapped form also counts as a valid citation marker.
|
|
643
|
+
assert.equal(
|
|
644
|
+
hasCitationForTemplate("[backend-agent:abc123] some text", "{agent}:{sessionId}"),
|
|
645
|
+
true,
|
|
646
|
+
"bracket-wrapped compact token must be recognised",
|
|
647
|
+
);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test("attachCitation idempotency: '{agent}:{sessionId}' template appends citation exactly once", () => {
|
|
651
|
+
// Regression for the P2 finding: without the fix, each reprocess pass would
|
|
652
|
+
// call hasCitationForTemplate → false → append another citation, producing
|
|
653
|
+
// 'planner:main planner:main planner:main...' on repeated passes.
|
|
654
|
+
const template = "{agent}:{sessionId}";
|
|
655
|
+
const ctx = { agent: "planner", session: "agent:planner:main", ts: "2026-04-10T14:25:07Z" };
|
|
656
|
+
const rawFact = "The service caches with Redis.";
|
|
657
|
+
|
|
658
|
+
const pass1 = attachCitation(rawFact, ctx, template);
|
|
659
|
+
assert.ok(pass1.endsWith("planner:main"), `pass1 should end with citation, got: ${pass1}`);
|
|
660
|
+
|
|
661
|
+
// Second pass on already-cited text must be a no-op.
|
|
662
|
+
const pass2 = attachCitation(pass1, ctx, template);
|
|
663
|
+
assert.equal(pass2, pass1, "second attachCitation pass must not append another citation");
|
|
664
|
+
|
|
665
|
+
// Third pass too.
|
|
666
|
+
const pass3 = attachCitation(pass2, ctx, template);
|
|
667
|
+
assert.equal(pass3, pass1, "third attachCitation pass must not append another citation");
|
|
668
|
+
|
|
669
|
+
// Confirm only one occurrence of the compact token.
|
|
670
|
+
const tokenCount = (pass3.match(/planner:main/g) ?? []).length;
|
|
671
|
+
assert.equal(tokenCount, 1, "citation token must appear exactly once");
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
test("hasCitationForTemplate: '{agent}/{sessionId}' slash separator — compact token detected", () => {
|
|
675
|
+
// Ensure the separator-only path works for slash separators too.
|
|
676
|
+
const template = "{agent}/{sessionId}";
|
|
677
|
+
assert.equal(hasCitationForTemplate("planner/main", template), true);
|
|
678
|
+
assert.equal(hasCitationForTemplate("Fact. planner/main", template), true);
|
|
679
|
+
// URL path segments must not false-positive.
|
|
680
|
+
assert.equal(hasCitationForTemplate("see https://example.com/planner/main for docs", template), false);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
test("hasCitationForTemplate: '{agent}-{sessionId}' dash separator — compact token detected", () => {
|
|
684
|
+
// Hyphen-separated compact tokens should also work.
|
|
685
|
+
const template = "{agent}-{sessionId}";
|
|
686
|
+
assert.equal(hasCitationForTemplate("planner-main", template), true);
|
|
687
|
+
// A citation appended at end of prose (by attachCitation) is at the tail.
|
|
688
|
+
assert.equal(hasCitationForTemplate("Fact. planner-main", template), true);
|
|
689
|
+
// Finding 1 fix: a compact token in the MIDDLE of prose must not match,
|
|
690
|
+
// because `attachCitation` always appends at the end. This is what prevents
|
|
691
|
+
// hyphenated English words like "long-term" from being falsely detected.
|
|
692
|
+
assert.equal(hasCitationForTemplate("Fact planner-main rest", template), false);
|
|
693
|
+
// Plain text with no separator must return false.
|
|
694
|
+
assert.equal(hasCitationForTemplate("no separator here", template), false);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// ── PR #401 P2 findings: Finding 1 (PRRT_kwDORJXyws56UH4M) + Finding 2 (PRRT_kwDORJXyws56UH4O) ──
|
|
698
|
+
|
|
699
|
+
// Finding 1: compact-template false positives on prose
|
|
700
|
+
test("hasCitationForTemplate (Finding 1): hyphenated prose 'long-term' must not match {agent}-{sessionId}", () => {
|
|
701
|
+
// Core regression: 'long-term' looks like `[\w.-]+-[\w.-]+` but is ordinary
|
|
702
|
+
// English prose. The tightened trail anchor (end-of-string or bracket only)
|
|
703
|
+
// rejects it because 'long-term' is followed by ' solution', not end-of-string.
|
|
704
|
+
assert.equal(
|
|
705
|
+
hasCitationForTemplate("long-term solution", "{agent}-{sessionId}"),
|
|
706
|
+
false,
|
|
707
|
+
"'long-term' embedded in prose must not be classified as a citation",
|
|
708
|
+
);
|
|
709
|
+
// A real citation appended by attachCitation WOULD be at end of string.
|
|
710
|
+
assert.equal(
|
|
711
|
+
hasCitationForTemplate("This is a long-term solution. planner-main", "{agent}-{sessionId}"),
|
|
712
|
+
true,
|
|
713
|
+
"citation token at end of fact body (after prose containing hyphen) must be detected",
|
|
714
|
+
);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
test("hasCitationForTemplate (Finding 1): slashed path 'docs/setup' in prose must not match {agent}/{sessionId}", () => {
|
|
718
|
+
// Regression for slash separator: 'docs/setup' appears in the middle of a
|
|
719
|
+
// sentence, not at the end, so the end-of-string anchor rejects it.
|
|
720
|
+
assert.equal(
|
|
721
|
+
hasCitationForTemplate("see the docs/setup guide for details", "{agent}/{sessionId}"),
|
|
722
|
+
false,
|
|
723
|
+
"'docs/setup' inside a sentence must not be classified as a citation",
|
|
724
|
+
);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test("hasCitationForTemplate (Finding 1): bracket-wrapped compact template is accepted", () => {
|
|
728
|
+
// Bracket-wrapped form (e.g. from a caller that wraps citations) should
|
|
729
|
+
// still be detected regardless of position in the string.
|
|
730
|
+
assert.equal(
|
|
731
|
+
hasCitationForTemplate("[agent-abc123] some trailing text", "{agent}-{sessionId}"),
|
|
732
|
+
true,
|
|
733
|
+
"bracket-wrapped compact token must be recognised as a citation",
|
|
734
|
+
);
|
|
735
|
+
assert.equal(
|
|
736
|
+
hasCitationForTemplate("Fact body. [agent-abc123]", "{agent}-{sessionId}"),
|
|
737
|
+
true,
|
|
738
|
+
"bracket-wrapped compact token at end of fact must be recognised",
|
|
739
|
+
);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// Finding 2: stripCitation must be a no-op on uncited input
|
|
743
|
+
test("stripCitation (Finding 2): returns uncited input byte-for-byte unchanged", () => {
|
|
744
|
+
// Core regression: the old implementation normalised all repeated whitespace
|
|
745
|
+
// even when no citation was present. This broke markdown hard-break spacing,
|
|
746
|
+
// aligned text, and code-like snippets in fact bodies.
|
|
747
|
+
const withDoubleSpaces = "plain prose with double spaces";
|
|
748
|
+
assert.equal(
|
|
749
|
+
stripCitation(withDoubleSpaces),
|
|
750
|
+
withDoubleSpaces,
|
|
751
|
+
"double spaces must be preserved when no citation is present",
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
// Tab characters in uncited text must also survive unchanged.
|
|
755
|
+
const withTabs = "col1\t\tcol2\t\tcol3";
|
|
756
|
+
assert.equal(
|
|
757
|
+
stripCitation(withTabs),
|
|
758
|
+
withTabs,
|
|
759
|
+
"tab characters must be preserved when no citation is present",
|
|
760
|
+
);
|
|
761
|
+
|
|
762
|
+
// Markdown hard-break: two trailing spaces before a newline.
|
|
763
|
+
const hardBreak = "line one \nline two";
|
|
764
|
+
assert.equal(
|
|
765
|
+
stripCitation(hardBreak),
|
|
766
|
+
hardBreak,
|
|
767
|
+
"markdown hard-break (two trailing spaces before newline) must be preserved",
|
|
768
|
+
);
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("stripCitation (Finding 2): removes citation and normalises only the join seam", () => {
|
|
772
|
+
// Positive case: a citation IS present, so stripping should work as before.
|
|
773
|
+
const cited = "The service uses Redis. [Source: agent=planner, session=main, ts=2026-04-10T00:00:00Z]";
|
|
774
|
+
const stripped = stripCitation(cited);
|
|
775
|
+
assert.equal(stripped, "The service uses Redis.");
|
|
776
|
+
|
|
777
|
+
// Whitespace BEFORE the citation (at the seam) is collapsed, but whitespace
|
|
778
|
+
// elsewhere in the body is not touched.
|
|
779
|
+
const citedWithInternalSpaces =
|
|
780
|
+
"col1 col2 [Source: agent=a, session=s, ts=2026-04-10T00:00:00Z]";
|
|
781
|
+
const strippedWithInternalSpaces = stripCitation(citedWithInternalSpaces);
|
|
782
|
+
// The double space inside the fact body must be preserved; only trailing
|
|
783
|
+
// whitespace at the seam is normalised.
|
|
784
|
+
assert.ok(
|
|
785
|
+
strippedWithInternalSpaces.includes("col1 col2"),
|
|
786
|
+
`internal double space must be preserved, got: ${JSON.stringify(strippedWithInternalSpaces)}`,
|
|
787
|
+
);
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
test("stripCitation (Finding 2): attach → strip round-trip with uncited double-space body", () => {
|
|
791
|
+
// Verifies that a fact body containing double spaces survives attach + strip
|
|
792
|
+
// without the double spaces being collapsed.
|
|
793
|
+
const body = "col one col two col three";
|
|
794
|
+
const ctx = { agent: "a", session: "s:1", ts: "2026-04-11T00:00:00Z" };
|
|
795
|
+
const attached = attachCitation(body, ctx);
|
|
796
|
+
assert.ok(hasCitation(attached), "citation must be present after attach");
|
|
797
|
+
const stripped = stripCitation(attached);
|
|
798
|
+
assert.equal(stripped, body, "strip must restore original body including double spaces");
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// ── P2 finding UM3U regression: hash-index key consistent across write and archive ──
|
|
802
|
+
|
|
803
|
+
test(
|
|
804
|
+
"UM3U: ContentHashIndex.remove via stripCitation(citedContent) removes the raw-content hash (inlineSourceAttributionEnabled=true)",
|
|
805
|
+
async () => {
|
|
806
|
+
// Scenario:
|
|
807
|
+
// 1. inlineSourceAttributionEnabled=true — facts are stored as
|
|
808
|
+
// "raw content [Source: agent=X, session=Y, ts=Z]" on disk.
|
|
809
|
+
// 2. At write time, contentHashSource=rawContent is passed so the
|
|
810
|
+
// dedup index stores hash(rawContent), not hash(citedContent).
|
|
811
|
+
// 3. At archive/consolidation time the code previously called
|
|
812
|
+
// contentHashIndex.remove(memory.content) where memory.content is
|
|
813
|
+
// the post-citation string — hash(citedContent) ≠ hash(rawContent),
|
|
814
|
+
// so the remove silently failed and left a stale entry.
|
|
815
|
+
// 4. The fix: call contentHashIndex.remove(stripCitation(memory.content))
|
|
816
|
+
// which recovers the raw text and produces the matching hash.
|
|
817
|
+
//
|
|
818
|
+
// This test exercises the fix at the ContentHashIndex level (unit) rather
|
|
819
|
+
// than through the full orchestrator, keeping it fast and dependency-free.
|
|
820
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-um3u-"));
|
|
821
|
+
try {
|
|
822
|
+
const rawContent = "user prefers dark mode in all applications";
|
|
823
|
+
const citedContent =
|
|
824
|
+
`${rawContent} [Source: agent=planner, session=chat, ts=2026-04-11T10:00:00Z]`;
|
|
825
|
+
|
|
826
|
+
// Simulate write-time: index the RAW content (as contentHashSource does).
|
|
827
|
+
const idx = new ContentHashIndex(dir);
|
|
828
|
+
await idx.load();
|
|
829
|
+
idx.add(rawContent);
|
|
830
|
+
await idx.save();
|
|
831
|
+
|
|
832
|
+
// Verify the raw hash is present.
|
|
833
|
+
assert.ok(idx.has(rawContent), "raw content hash must be present after add");
|
|
834
|
+
// The cited variant must NOT collide with the raw hash (validates the premise).
|
|
835
|
+
assert.ok(!idx.has(citedContent), "cited content must hash differently from raw");
|
|
836
|
+
|
|
837
|
+
// Simulate the BROKEN archive path: remove using the cited content directly.
|
|
838
|
+
// This must leave the raw hash intact (demonstrating the bug).
|
|
839
|
+
const idxBroken = new ContentHashIndex(dir);
|
|
840
|
+
await idxBroken.load();
|
|
841
|
+
idxBroken.remove(citedContent); // broken: hash mismatch, no-op
|
|
842
|
+
assert.ok(
|
|
843
|
+
idxBroken.has(rawContent),
|
|
844
|
+
"broken path: stale hash must remain because citedContent hash does not match",
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
// Simulate the FIXED archive path: strip citation before calling remove.
|
|
848
|
+
// stripCitation(citedContent) === rawContent (same hash), so remove succeeds.
|
|
849
|
+
const idxFixed = new ContentHashIndex(dir);
|
|
850
|
+
await idxFixed.load();
|
|
851
|
+
idxFixed.remove(stripCitation(citedContent)); // fixed: hash matches
|
|
852
|
+
assert.ok(
|
|
853
|
+
!idxFixed.has(rawContent),
|
|
854
|
+
"fixed path: raw content hash must be removed after stripCitation → remove",
|
|
855
|
+
);
|
|
856
|
+
|
|
857
|
+
// Verify the index is now empty (no stale entries remain).
|
|
858
|
+
assert.equal(idxFixed.size, 0, "hash index must be empty after correct removal");
|
|
859
|
+
} finally {
|
|
860
|
+
await rm(dir, { recursive: true, force: true });
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
test(
|
|
866
|
+
"UM3U: re-extraction of same raw fact is NOT false-deduped after correct archive removal",
|
|
867
|
+
async () => {
|
|
868
|
+
// After a fact is archived with the FIXED remove path (stripCitation before
|
|
869
|
+
// remove), the hash index no longer contains the raw-content hash. A
|
|
870
|
+
// subsequent extraction of the same underlying raw fact must therefore pass
|
|
871
|
+
// the dedup gate (has() returns false) and be allowed to be re-written.
|
|
872
|
+
//
|
|
873
|
+
// Without the fix: remove(citedContent) is a no-op → has(rawContent) stays
|
|
874
|
+
// true → new extraction is incorrectly blocked as a duplicate.
|
|
875
|
+
// With the fix: remove(stripCitation(citedContent)) succeeds → has(rawContent)
|
|
876
|
+
// returns false → re-extraction can proceed.
|
|
877
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-um3u-reextract-"));
|
|
878
|
+
try {
|
|
879
|
+
const rawContent = "user likes coffee in the morning";
|
|
880
|
+
const citedContent =
|
|
881
|
+
`${rawContent} [Source: agent=scout, session=chat, ts=2026-04-11T09:00:00Z]`;
|
|
882
|
+
|
|
883
|
+
// Write phase: add raw hash to index.
|
|
884
|
+
const writeIdx = new ContentHashIndex(dir);
|
|
885
|
+
await writeIdx.load();
|
|
886
|
+
writeIdx.add(rawContent);
|
|
887
|
+
await writeIdx.save();
|
|
888
|
+
|
|
889
|
+
// Archive phase (FIXED): use stripCitation before remove.
|
|
890
|
+
const archiveIdx = new ContentHashIndex(dir);
|
|
891
|
+
await archiveIdx.load();
|
|
892
|
+
archiveIdx.remove(stripCitation(citedContent));
|
|
893
|
+
await archiveIdx.save();
|
|
894
|
+
|
|
895
|
+
// Re-extraction phase: load a fresh index (new session / process).
|
|
896
|
+
const reextractIdx = new ContentHashIndex(dir);
|
|
897
|
+
await reextractIdx.load();
|
|
898
|
+
|
|
899
|
+
// The re-extracted raw fact must NOT be blocked as a duplicate.
|
|
900
|
+
assert.ok(
|
|
901
|
+
!reextractIdx.has(rawContent),
|
|
902
|
+
"after correct archive removal, re-extraction of the same raw fact must not be false-deduped",
|
|
903
|
+
);
|
|
904
|
+
} finally {
|
|
905
|
+
await rm(dir, { recursive: true, force: true });
|
|
906
|
+
}
|
|
907
|
+
},
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
// ── Option A (contentHash on frontmatter): format-agnostic archive cleanup ────
|
|
911
|
+
|
|
912
|
+
test(
|
|
913
|
+
"Option A — Test A: custom unbracketed inline citation: archive removes raw-content hash via stored contentHash",
|
|
914
|
+
async () => {
|
|
915
|
+
// Configure an unbracketed template: "{agent} {sessionId}"
|
|
916
|
+
// formatCitation produces something like "user prefers tea planner main"
|
|
917
|
+
// stripCitation cannot strip it (no [Source: ...] bracket), but the stored
|
|
918
|
+
// frontmatter.contentHash must still let us remove the correct hash.
|
|
919
|
+
//
|
|
920
|
+
// Verifies Option 1 fix: removeByHash(frontmatter.contentHash) correctly
|
|
921
|
+
// deletes the entry without double-hashing.
|
|
922
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-option-a-test-a-"));
|
|
923
|
+
try {
|
|
924
|
+
const rawContent = "user prefers tea";
|
|
925
|
+
const template = "{agent} {sessionId}";
|
|
926
|
+
const citedContent = attachCitation(
|
|
927
|
+
rawContent,
|
|
928
|
+
{ agent: "planner", session: "agent:planner:main" },
|
|
929
|
+
template,
|
|
930
|
+
);
|
|
931
|
+
// citedContent = "user prefers tea planner main" (no brackets — stripCitation is a no-op)
|
|
932
|
+
assert.ok(!rawContent.includes("["), "raw content must have no brackets");
|
|
933
|
+
|
|
934
|
+
// Write with contentHashSource so the raw-content hash lands in the index
|
|
935
|
+
// and on frontmatter.contentHash.
|
|
936
|
+
const storage = new StorageManager(dir);
|
|
937
|
+
await storage.writeMemory("fact", citedContent, {
|
|
938
|
+
source: "extraction",
|
|
939
|
+
contentHashSource: rawContent,
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// Confirm raw-content hash is indexed.
|
|
943
|
+
assert.ok(
|
|
944
|
+
await storage.hasFactContentHash(rawContent),
|
|
945
|
+
"raw-content hash must be in the index after write",
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
// Load the written memory from disk to retrieve frontmatter.contentHash.
|
|
949
|
+
const allMemories = await storage.readAllMemories();
|
|
950
|
+
const written = allMemories.find((m) => m.content.includes("planner main") || (m.content.includes("planner") && m.content.includes("tea")));
|
|
951
|
+
assert.ok(written, "written memory must be findable");
|
|
952
|
+
assert.ok(
|
|
953
|
+
written!.frontmatter.contentHash,
|
|
954
|
+
"frontmatter.contentHash must be persisted on disk",
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
// Confirm frontmatter.contentHash is a 64-char hex string (pre-computed hash).
|
|
958
|
+
assert.match(
|
|
959
|
+
written!.frontmatter.contentHash!,
|
|
960
|
+
/^[a-f0-9]{64}$/,
|
|
961
|
+
"frontmatter.contentHash must be a 64-char SHA-256 hex string",
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
// Simulate archive via Option 1 fix: use removeByHash so we skip hashing
|
|
965
|
+
// the already-hashed value. StorageManager stores fact-hashes.txt under
|
|
966
|
+
// the `state/` subdir — use that same path for the ContentHashIndex.
|
|
967
|
+
const stateDir = path.join(dir, "state");
|
|
968
|
+
const idx = new ContentHashIndex(stateDir);
|
|
969
|
+
await idx.load();
|
|
970
|
+
// Verify the raw-content hash IS present before archive (proves we loaded the right file).
|
|
971
|
+
assert.ok(idx.has(rawContent), "raw-content hash must be present in the state index before archive");
|
|
972
|
+
idx.removeByHash(written!.frontmatter.contentHash!);
|
|
973
|
+
await idx.save();
|
|
974
|
+
|
|
975
|
+
// Reload index and verify hash is gone.
|
|
976
|
+
const idxAfter = new ContentHashIndex(stateDir);
|
|
977
|
+
await idxAfter.load();
|
|
978
|
+
assert.ok(
|
|
979
|
+
!idxAfter.has(rawContent),
|
|
980
|
+
"after archive using removeByHash(frontmatter.contentHash), raw-content hash must be removed from index",
|
|
981
|
+
);
|
|
982
|
+
|
|
983
|
+
// Confirm that the buggy approach (double-hash via remove()) would have failed.
|
|
984
|
+
// Re-add the raw hash and then remove() the already-hashed value — must be a no-op.
|
|
985
|
+
const idxDoubleHashCheck = new ContentHashIndex(stateDir);
|
|
986
|
+
await idxDoubleHashCheck.load();
|
|
987
|
+
idxDoubleHashCheck.add(rawContent);
|
|
988
|
+
await idxDoubleHashCheck.save();
|
|
989
|
+
const idxBuggy = new ContentHashIndex(stateDir);
|
|
990
|
+
await idxBuggy.load();
|
|
991
|
+
idxBuggy.remove(written!.frontmatter.contentHash!); // double-hash: no-op
|
|
992
|
+
assert.ok(
|
|
993
|
+
idxBuggy.has(rawContent),
|
|
994
|
+
"buggy remove(alreadyHashedValue) must be a no-op — stale hash remains",
|
|
995
|
+
);
|
|
996
|
+
} finally {
|
|
997
|
+
await rm(dir, { recursive: true, force: true });
|
|
998
|
+
}
|
|
999
|
+
},
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
test(
|
|
1003
|
+
"Option A — Test B: custom bracketed inline citation: archive removes raw-content hash via stored contentHash",
|
|
1004
|
+
async () => {
|
|
1005
|
+
// Configure a bracketed custom template: "[src:{agent}/{sessionId}@{date}]"
|
|
1006
|
+
// The stored body is "user prefers tea [src:planner/main@2026-04-11]"
|
|
1007
|
+
// stripCitation only strips [Source: ...] shape, so this format is a no-op.
|
|
1008
|
+
//
|
|
1009
|
+
// Verifies Option 1 fix: removeByHash(frontmatter.contentHash) correctly
|
|
1010
|
+
// deletes the entry without double-hashing.
|
|
1011
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-option-a-test-b-"));
|
|
1012
|
+
try {
|
|
1013
|
+
const rawContent = "user prefers tea";
|
|
1014
|
+
const template = "[src:{agent}/{sessionId}@{date}]";
|
|
1015
|
+
const citedContent = attachCitation(
|
|
1016
|
+
rawContent,
|
|
1017
|
+
{ agent: "planner", session: "agent:planner:main", ts: "2026-04-11T00:00:00Z" },
|
|
1018
|
+
template,
|
|
1019
|
+
);
|
|
1020
|
+
// citedContent = "user prefers tea [src:planner/main@2026-04-11]"
|
|
1021
|
+
assert.ok(citedContent.includes("[src:"), "cited content must use custom bracket format");
|
|
1022
|
+
assert.ok(!citedContent.includes("[Source:"), "must NOT use default [Source: ...] format");
|
|
1023
|
+
|
|
1024
|
+
// Write fact with raw-content as contentHashSource.
|
|
1025
|
+
const storage = new StorageManager(dir);
|
|
1026
|
+
await storage.writeMemory("fact", citedContent, {
|
|
1027
|
+
source: "extraction",
|
|
1028
|
+
contentHashSource: rawContent,
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
// Confirm raw-content hash is indexed.
|
|
1032
|
+
assert.ok(
|
|
1033
|
+
await storage.hasFactContentHash(rawContent),
|
|
1034
|
+
"raw-content hash must be in the index after write",
|
|
1035
|
+
);
|
|
1036
|
+
|
|
1037
|
+
// Load from disk and check frontmatter.contentHash.
|
|
1038
|
+
const allMemories = await storage.readAllMemories();
|
|
1039
|
+
const written = allMemories.find((m) => m.content.includes("[src:planner/main@"));
|
|
1040
|
+
assert.ok(written, "written memory must be findable by custom citation");
|
|
1041
|
+
assert.ok(
|
|
1042
|
+
written!.frontmatter.contentHash,
|
|
1043
|
+
"frontmatter.contentHash must be persisted on disk",
|
|
1044
|
+
);
|
|
1045
|
+
|
|
1046
|
+
// Confirm frontmatter.contentHash is a 64-char hex string (pre-computed hash).
|
|
1047
|
+
assert.match(
|
|
1048
|
+
written!.frontmatter.contentHash!,
|
|
1049
|
+
/^[a-f0-9]{64}$/,
|
|
1050
|
+
"frontmatter.contentHash must be a 64-char SHA-256 hex string",
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
// Simulate archive via Option 1 fix: use removeByHash with the correct
|
|
1054
|
+
// state/ subdir path that StorageManager uses.
|
|
1055
|
+
const stateDir = path.join(dir, "state");
|
|
1056
|
+
const idx = new ContentHashIndex(stateDir);
|
|
1057
|
+
await idx.load();
|
|
1058
|
+
// Verify the raw-content hash IS present before archive.
|
|
1059
|
+
assert.ok(idx.has(rawContent), "raw-content hash must be present in the state index before archive");
|
|
1060
|
+
idx.removeByHash(written!.frontmatter.contentHash!);
|
|
1061
|
+
await idx.save();
|
|
1062
|
+
|
|
1063
|
+
// Reload and verify hash is gone.
|
|
1064
|
+
const idxAfter = new ContentHashIndex(stateDir);
|
|
1065
|
+
await idxAfter.load();
|
|
1066
|
+
assert.ok(
|
|
1067
|
+
!idxAfter.has(rawContent),
|
|
1068
|
+
"after archive using removeByHash(frontmatter.contentHash), raw-content hash must be removed from index",
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
// Confirm that the buggy approach (double-hash via remove()) would have failed.
|
|
1072
|
+
const idxDoubleHashCheck = new ContentHashIndex(stateDir);
|
|
1073
|
+
await idxDoubleHashCheck.load();
|
|
1074
|
+
idxDoubleHashCheck.add(rawContent);
|
|
1075
|
+
await idxDoubleHashCheck.save();
|
|
1076
|
+
const idxBuggy = new ContentHashIndex(stateDir);
|
|
1077
|
+
await idxBuggy.load();
|
|
1078
|
+
idxBuggy.remove(written!.frontmatter.contentHash!); // double-hash: no-op
|
|
1079
|
+
assert.ok(
|
|
1080
|
+
idxBuggy.has(rawContent),
|
|
1081
|
+
"buggy remove(alreadyHashedValue) must be a no-op — stale hash remains",
|
|
1082
|
+
);
|
|
1083
|
+
} finally {
|
|
1084
|
+
await rm(dir, { recursive: true, force: true });
|
|
1085
|
+
}
|
|
1086
|
+
},
|
|
1087
|
+
);
|
|
1088
|
+
|
|
1089
|
+
test(
|
|
1090
|
+
"Option A — Test C (legacy, round-4 fix): memory without frontmatter.contentHash — archive removes hash via content fallback",
|
|
1091
|
+
async () => {
|
|
1092
|
+
// Verifies the round-4 (P2) fix: when a legacy memory has no contentHash
|
|
1093
|
+
// frontmatter, the archive path must call remove(memory.content) to clear
|
|
1094
|
+
// the stale dedup entry. Pre-#369 facts were stored without inline
|
|
1095
|
+
// citations, so memory.content is the raw fact text and hashing it via
|
|
1096
|
+
// remove() will hit the correct index entry.
|
|
1097
|
+
//
|
|
1098
|
+
// This replaces the earlier "skip" behaviour (Finding 2 — Urgw) which left
|
|
1099
|
+
// stale entries in the index and caused false-dedup suppression of
|
|
1100
|
+
// re-extracted facts in upgraded deployments.
|
|
1101
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-option-a-test-c-"));
|
|
1102
|
+
try {
|
|
1103
|
+
const rawContent = "user prefers coffee";
|
|
1104
|
+
|
|
1105
|
+
// Populate the index with the raw-content hash (simulates what writeMemory
|
|
1106
|
+
// did in pre-#369 builds when contentHashSource was not yet supported).
|
|
1107
|
+
const idx = new ContentHashIndex(dir);
|
|
1108
|
+
await idx.load();
|
|
1109
|
+
idx.add(rawContent);
|
|
1110
|
+
await idx.save();
|
|
1111
|
+
|
|
1112
|
+
const idxCheck = new ContentHashIndex(dir);
|
|
1113
|
+
await idxCheck.load();
|
|
1114
|
+
assert.ok(idxCheck.has(rawContent), "raw-content hash must be present before archive");
|
|
1115
|
+
|
|
1116
|
+
// Simulate the legacy archive path: frontmatter has NO contentHash, and
|
|
1117
|
+
// memory.content is the raw (un-cited) fact text (pre-#369 write).
|
|
1118
|
+
const legacyMemory = {
|
|
1119
|
+
frontmatter: { contentHash: undefined as string | undefined },
|
|
1120
|
+
content: rawContent,
|
|
1121
|
+
} as unknown as import("./types.js").MemoryFile;
|
|
1122
|
+
|
|
1123
|
+
// The updated archive path: use removeByHash when contentHash present,
|
|
1124
|
+
// else fall back to remove(memory.content) for legacy memories.
|
|
1125
|
+
if (legacyMemory.frontmatter.contentHash) {
|
|
1126
|
+
idxCheck.removeByHash(legacyMemory.frontmatter.contentHash);
|
|
1127
|
+
} else {
|
|
1128
|
+
idxCheck.remove(legacyMemory.content);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
await idxCheck.save();
|
|
1132
|
+
|
|
1133
|
+
// The raw-content hash MUST now be gone — stale entry cleared.
|
|
1134
|
+
const idxAfter = new ContentHashIndex(dir);
|
|
1135
|
+
await idxAfter.load();
|
|
1136
|
+
assert.ok(
|
|
1137
|
+
!idxAfter.has(rawContent),
|
|
1138
|
+
"raw-content hash must be removed from the index after legacy archive content-fallback removal",
|
|
1139
|
+
);
|
|
1140
|
+
} finally {
|
|
1141
|
+
await rm(dir, { recursive: true, force: true });
|
|
1142
|
+
}
|
|
1143
|
+
},
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1146
|
+
// ── Test D: round 8 double-hash regression (Option 1 fix verification) ────────
|
|
1147
|
+
|
|
1148
|
+
test(
|
|
1149
|
+
"Test D: archive removes hash via frontmatter.contentHash — no double hashing (round 8 regression)",
|
|
1150
|
+
async () => {
|
|
1151
|
+
// Regression test for the round 8 double-hash bug:
|
|
1152
|
+
// Round 8 changed archive call sites to:
|
|
1153
|
+
// const hashKey = memory.frontmatter.contentHash ?? stripCitation(memory.content);
|
|
1154
|
+
// contentHashIndex.remove(hashKey);
|
|
1155
|
+
// But ContentHashIndex.remove() internally calls computeHash(hashKey).
|
|
1156
|
+
// When hashKey is already a SHA-256 hex string, we compute hash(hash(rawContent))
|
|
1157
|
+
// which never matches the index entry — silent no-op, stale hash leaks.
|
|
1158
|
+
//
|
|
1159
|
+
// Option 1 fix: removeByHash(hash) deletes the hash directly without re-hashing.
|
|
1160
|
+
// This test verifies:
|
|
1161
|
+
// 1. After write, has(rawContent) is true.
|
|
1162
|
+
// 2. After removeByHash(frontmatter.contentHash), has(rawContent) is false.
|
|
1163
|
+
// 3. After the buggy remove(frontmatter.contentHash), has(rawContent) is STILL true.
|
|
1164
|
+
// 4. Re-extraction after correct removal is NOT false-deduped.
|
|
1165
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "engram-test-d-double-hash-"));
|
|
1166
|
+
try {
|
|
1167
|
+
const rawContent = "user prefers tea";
|
|
1168
|
+
const template = DEFAULT_CITATION_FORMAT;
|
|
1169
|
+
const citedContent = attachCitation(
|
|
1170
|
+
rawContent,
|
|
1171
|
+
{ agent: "planner", session: "agent:planner:main", ts: "2026-04-11T10:00:00Z" },
|
|
1172
|
+
template,
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
// Write the fact with contentHashSource so both the index and frontmatter.contentHash
|
|
1176
|
+
// store hash(rawContent).
|
|
1177
|
+
const storage = new StorageManager(dir);
|
|
1178
|
+
await storage.writeMemory("fact", citedContent, {
|
|
1179
|
+
source: "extraction",
|
|
1180
|
+
contentHashSource: rawContent,
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
// Step 1: verify has(rawContent) is true immediately after write.
|
|
1184
|
+
assert.ok(
|
|
1185
|
+
await storage.hasFactContentHash(rawContent),
|
|
1186
|
+
"Step 1: has(rawContent) must be true after write",
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// Load the written memory to retrieve frontmatter.contentHash.
|
|
1190
|
+
const allMemories = await storage.readAllMemories();
|
|
1191
|
+
const written = allMemories.find((m) => m.content.includes("[Source: agent=planner"));
|
|
1192
|
+
assert.ok(written, "written memory must be findable");
|
|
1193
|
+
const storedHash = written!.frontmatter.contentHash;
|
|
1194
|
+
assert.ok(storedHash, "frontmatter.contentHash must be present");
|
|
1195
|
+
assert.match(storedHash!, /^[a-f0-9]{64}$/, "frontmatter.contentHash must be a 64-char SHA-256 hex");
|
|
1196
|
+
|
|
1197
|
+
// Verify storedHash equals computeHash(rawContent) — proves it's a pre-computed hash.
|
|
1198
|
+
assert.equal(
|
|
1199
|
+
storedHash,
|
|
1200
|
+
ContentHashIndex.computeHash(rawContent),
|
|
1201
|
+
"frontmatter.contentHash must equal computeHash(rawContent)",
|
|
1202
|
+
);
|
|
1203
|
+
|
|
1204
|
+
// Step 2a: demonstrate the BUGGY path — remove(alreadyHashedValue) is a no-op.
|
|
1205
|
+
// This is the double-hash bug from round 8.
|
|
1206
|
+
const stateDir = path.join(dir, "state");
|
|
1207
|
+
const idxBuggy = new ContentHashIndex(stateDir);
|
|
1208
|
+
await idxBuggy.load();
|
|
1209
|
+
assert.ok(idxBuggy.has(rawContent), "Step 2a-pre: hash must be present before buggy remove");
|
|
1210
|
+
idxBuggy.remove(storedHash!); // double-hash: hash(hash(rawContent)) — no match
|
|
1211
|
+
// The hash must STILL be present (remove was a no-op).
|
|
1212
|
+
assert.ok(
|
|
1213
|
+
idxBuggy.has(rawContent),
|
|
1214
|
+
"Step 2a: buggy remove(alreadyHashedValue) must leave the hash intact — stale hash leaks",
|
|
1215
|
+
);
|
|
1216
|
+
|
|
1217
|
+
// Step 2b: demonstrate the FIXED path — removeByHash(alreadyHashedValue) removes directly.
|
|
1218
|
+
const idxFixed = new ContentHashIndex(stateDir);
|
|
1219
|
+
await idxFixed.load();
|
|
1220
|
+
assert.ok(idxFixed.has(rawContent), "Step 2b-pre: hash must be present before fixed remove");
|
|
1221
|
+
idxFixed.removeByHash(storedHash!); // correct: no re-hashing
|
|
1222
|
+
assert.ok(
|
|
1223
|
+
!idxFixed.has(rawContent),
|
|
1224
|
+
"Step 2b: removeByHash(frontmatter.contentHash) must remove the raw-content hash",
|
|
1225
|
+
);
|
|
1226
|
+
await idxFixed.save();
|
|
1227
|
+
|
|
1228
|
+
// Step 3: re-extraction after correct removal must NOT be false-deduped.
|
|
1229
|
+
// Load a fresh index (simulating a new extraction session).
|
|
1230
|
+
const idxReextract = new ContentHashIndex(stateDir);
|
|
1231
|
+
await idxReextract.load();
|
|
1232
|
+
assert.ok(
|
|
1233
|
+
!idxReextract.has(rawContent),
|
|
1234
|
+
"Step 3: after archive, re-extraction of the same raw fact must not be false-deduped (hash is gone)",
|
|
1235
|
+
);
|
|
1236
|
+
} finally {
|
|
1237
|
+
await rm(dir, { recursive: true, force: true });
|
|
1238
|
+
}
|
|
1239
|
+
},
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
// ── Finding 3 (Uru3): templateMatcher — tighten normal-case placeholder regex ─
|
|
1243
|
+
|
|
1244
|
+
test(
|
|
1245
|
+
"Finding 3 (Uru3): hasCitationForTemplate rejects user content that shares outer delimiters but wrong separators",
|
|
1246
|
+
() => {
|
|
1247
|
+
// Template [src:{agent}:{sessionId}_{date}] uses ':' and '_' as separators.
|
|
1248
|
+
// User content that happens to use different separators ('/' and '@') must
|
|
1249
|
+
// NOT be classified as a citation.
|
|
1250
|
+
const template = "[src:{agent}:{sessionId}_{date}]";
|
|
1251
|
+
|
|
1252
|
+
// Real citation produced by this template — MUST be detected.
|
|
1253
|
+
const realCitation = "The cache was cleared. [src:planner:main_2026-04-11]";
|
|
1254
|
+
assert.equal(
|
|
1255
|
+
hasCitationForTemplate(realCitation, template),
|
|
1256
|
+
true,
|
|
1257
|
+
"real citation matching the template separators must be detected",
|
|
1258
|
+
);
|
|
1259
|
+
|
|
1260
|
+
// User content with wrong separators ('/' and '@' instead of ':' and '_').
|
|
1261
|
+
// Before the fix, `[^\n]*?` between prefix/suffix matched this.
|
|
1262
|
+
const wrongSepContent = "See docs at [src:some-agent/abc123@today] for details.";
|
|
1263
|
+
assert.equal(
|
|
1264
|
+
hasCitationForTemplate(wrongSepContent, template),
|
|
1265
|
+
false,
|
|
1266
|
+
"user content with different separators must NOT be mis-flagged as a citation (Finding 3 — Uru3)",
|
|
1267
|
+
);
|
|
1268
|
+
|
|
1269
|
+
// Edge case: content with the same outer delimiters but more tokens.
|
|
1270
|
+
const extraTokenContent = "check [src:agent:session:extra_date] now";
|
|
1271
|
+
assert.equal(
|
|
1272
|
+
hasCitationForTemplate(extraTokenContent, template),
|
|
1273
|
+
false,
|
|
1274
|
+
"content with extra tokens inside the template delimiters must NOT match",
|
|
1275
|
+
);
|
|
1276
|
+
},
|
|
1277
|
+
);
|
|
1278
|
+
|
|
1279
|
+
test(
|
|
1280
|
+
"Finding 3 (Uru3): hasCitationForTemplate correctly detects default-format citations after tightening",
|
|
1281
|
+
() => {
|
|
1282
|
+
// The default format [Source: agent={agent}, session={sessionId}, ts={ts}]
|
|
1283
|
+
// has rich inner separators (', session=' and ', ts='). The tightened
|
|
1284
|
+
// normal-case logic must still match real citations produced by formatCitation.
|
|
1285
|
+
const realCitation =
|
|
1286
|
+
"User prefers dark mode. [Source: agent=planner, session=main, ts=2026-04-11T10:00:00Z]";
|
|
1287
|
+
assert.equal(
|
|
1288
|
+
hasCitationForTemplate(realCitation, DEFAULT_CITATION_FORMAT),
|
|
1289
|
+
true,
|
|
1290
|
+
"default-format citation must be detected after templateMatcher tightening",
|
|
1291
|
+
);
|
|
1292
|
+
|
|
1293
|
+
// Also verify an 'unknown' variant (fields populated with CITATION_UNKNOWN).
|
|
1294
|
+
const unknownCitation =
|
|
1295
|
+
"Some fact. [Source: agent=unknown, session=unknown, ts=unknown]";
|
|
1296
|
+
assert.equal(
|
|
1297
|
+
hasCitationForTemplate(unknownCitation, DEFAULT_CITATION_FORMAT),
|
|
1298
|
+
true,
|
|
1299
|
+
"default-format citation with 'unknown' fields must still be detected",
|
|
1300
|
+
);
|
|
1301
|
+
},
|
|
1302
|
+
);
|
|
1303
|
+
|
|
1304
|
+
test(
|
|
1305
|
+
"Finding 3 (Uru3): templateMatcher normal case rejects content that crosses placeholder boundaries",
|
|
1306
|
+
() => {
|
|
1307
|
+
// Template [src:{agent}/{sessionId}@{date}] with inner seps '/' and '@'.
|
|
1308
|
+
// Content where a single value spans BOTH separators must be rejected.
|
|
1309
|
+
const template = "[src:{agent}/{sessionId}@{date}]";
|
|
1310
|
+
|
|
1311
|
+
// Content where 'agent' portion alone spans the '/' separator — not a
|
|
1312
|
+
// valid emission of the template.
|
|
1313
|
+
const crossBoundaryCitation = "check [src:foo/bar/extra@2026-04-11] now";
|
|
1314
|
+
assert.equal(
|
|
1315
|
+
hasCitationForTemplate(crossBoundaryCitation, template),
|
|
1316
|
+
false,
|
|
1317
|
+
"content where a value token crosses a separator boundary must not match",
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
// Valid citation — exactly three tokens in the right positions.
|
|
1321
|
+
const validCitation = "Fact. [src:planner/main@2026-04-11]";
|
|
1322
|
+
assert.equal(
|
|
1323
|
+
hasCitationForTemplate(validCitation, template),
|
|
1324
|
+
true,
|
|
1325
|
+
"valid citation matching template separators must be detected",
|
|
1326
|
+
);
|
|
1327
|
+
},
|
|
1328
|
+
);
|
|
1329
|
+
|
|
1330
|
+
// ── Fix #3 regression: verbatim artifact must share citation timestamp with memory ──
|
|
1331
|
+
|
|
1332
|
+
test(
|
|
1333
|
+
"Fix #3 (UzK9): verbatim artifact and memory write share the same citation timestamp",
|
|
1334
|
+
() => {
|
|
1335
|
+
// Regression test for the duplicate-citation bug: when applyInlineCitation
|
|
1336
|
+
// is called twice on the same raw content (once for writeMemory and once for
|
|
1337
|
+
// writeArtifact), each invocation generates a fresh new Date().toISOString()
|
|
1338
|
+
// timestamp, producing two distinct citations for the same logical fact.
|
|
1339
|
+
//
|
|
1340
|
+
// The fix: compute applyInlineCitation(fact.content) ONCE and reuse the
|
|
1341
|
+
// result for both writeMemory and writeArtifact. This test verifies that
|
|
1342
|
+
// calling attachCitation on the SAME already-cited string is idempotent
|
|
1343
|
+
// (hasCitationForTemplate returns true, so attachCitation is a no-op),
|
|
1344
|
+
// and that calling it on the raw content twice produces different citations.
|
|
1345
|
+
const rawContent = "user prefers dark mode";
|
|
1346
|
+
const ts1 = "2026-04-11T10:00:00.000Z";
|
|
1347
|
+
const ts2 = "2026-04-11T10:00:00.001Z";
|
|
1348
|
+
|
|
1349
|
+
const cited1 = attachCitation(rawContent, { agent: "planner", session: "s1", ts: ts1 });
|
|
1350
|
+
const cited2 = attachCitation(rawContent, { agent: "planner", session: "s1", ts: ts2 });
|
|
1351
|
+
|
|
1352
|
+
// Two separate calls on raw content produce different citations (different ts).
|
|
1353
|
+
assert.notEqual(
|
|
1354
|
+
cited1,
|
|
1355
|
+
cited2,
|
|
1356
|
+
"two separate attachCitation calls on raw content produce different timestamps",
|
|
1357
|
+
);
|
|
1358
|
+
|
|
1359
|
+
// The fix: the second call receives the ALREADY-CITED string from the first call.
|
|
1360
|
+
// attachCitation must be idempotent — it should not append a second citation.
|
|
1361
|
+
const citedAgain = attachCitation(cited1, { agent: "planner", session: "s1", ts: ts2 });
|
|
1362
|
+
assert.equal(
|
|
1363
|
+
citedAgain,
|
|
1364
|
+
cited1,
|
|
1365
|
+
"attachCitation on already-cited content must be idempotent — no duplicate citation appended",
|
|
1366
|
+
);
|
|
1367
|
+
|
|
1368
|
+
// Verify hasCitationForTemplate detects the citation on cited1.
|
|
1369
|
+
assert.equal(
|
|
1370
|
+
hasCitationForTemplate(cited1, DEFAULT_CITATION_FORMAT),
|
|
1371
|
+
true,
|
|
1372
|
+
"already-cited content must be detected as having a citation",
|
|
1373
|
+
);
|
|
1374
|
+
},
|
|
1375
|
+
);
|
|
1376
|
+
|
|
1377
|
+
// ── Separator-in-placeholder-value regression (round-5 review thread) ─────────
|
|
1378
|
+
|
|
1379
|
+
test("hasCitationForTemplate: placeholder value containing inner separator char is detected (colon in ts)", () => {
|
|
1380
|
+
// Template `[src:{agent}:{ts}]` uses `:` as the separator between {agent}
|
|
1381
|
+
// and {ts}. When the `{ts}` value itself contains `:` (ISO-8601 timestamp),
|
|
1382
|
+
// the old single shared tokenPattern `[^\n\s:]+?` incorrectly rejects the
|
|
1383
|
+
// citation, causing attachCitation to append a duplicate on reprocessing.
|
|
1384
|
+
//
|
|
1385
|
+
// The fix builds per-placeholder patterns: only the FIRST token (before the
|
|
1386
|
+
// `:` separator) must exclude `:`. The LAST token is terminated by the
|
|
1387
|
+
// suffix `]` anchor, so it does not need to exclude `:`.
|
|
1388
|
+
const template = "[src:{agent}:{ts}]";
|
|
1389
|
+
const citedText = "Fact body. [src:planner:2026-04-10T14:25:07Z]";
|
|
1390
|
+
assert.equal(
|
|
1391
|
+
hasCitationForTemplate(citedText, template),
|
|
1392
|
+
true,
|
|
1393
|
+
"citation containing a colon in the ts value must be detected",
|
|
1394
|
+
);
|
|
1395
|
+
// attachCitation must be idempotent: no second citation appended.
|
|
1396
|
+
const ctx = { agent: "planner", session: "agent:planner:main", ts: "2026-04-11T00:00:00Z" };
|
|
1397
|
+
const again = attachCitation(citedText, ctx, template);
|
|
1398
|
+
assert.equal(again, citedText, "attachCitation must not append a duplicate when citation is already present");
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
test("hasCitationForTemplate: colon-separated template still rejects text with wrong structure", () => {
|
|
1402
|
+
// The relaxed last-token pattern must not cause false positives — the prefix
|
|
1403
|
+
// and suffix anchors (`[src:` and `]`) should still filter out arbitrary text.
|
|
1404
|
+
const template = "[src:{agent}:{ts}]";
|
|
1405
|
+
assert.equal(hasCitationForTemplate("plain text without citation", template), false);
|
|
1406
|
+
assert.equal(hasCitationForTemplate("no bracket prefix here planner:2026", template), false);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
test("hasCitationForTemplate: multi-separator template detects citation when intermediate value contains sep char", () => {
|
|
1410
|
+
// Template `[src:{agent}:{sessionId}:{ts}]` — three placeholders, two `:` separators.
|
|
1411
|
+
// The {sessionId} value `scout:alpha` contains `:` which is the separator between
|
|
1412
|
+
// placeholder 0 and 1. Per-placeholder patterns must only exclude `:` from
|
|
1413
|
+
// the token that immediately precedes each separator. The last token ({ts})
|
|
1414
|
+
// must not be restricted by `:` at all.
|
|
1415
|
+
const template = "[src:{agent}:{sessionId}:{ts}]";
|
|
1416
|
+
const citation = "[src:planner:scout:alpha:2026-04-10T14:25:07Z]";
|
|
1417
|
+
// This is ambiguous by design — the matcher allows it because idempotency is
|
|
1418
|
+
// more important than strict inter-placeholder boundary enforcement when values
|
|
1419
|
+
// contain the separator.
|
|
1420
|
+
assert.equal(
|
|
1421
|
+
hasCitationForTemplate(citation, template),
|
|
1422
|
+
true,
|
|
1423
|
+
"multi-colon citation where intermediate value contains the separator should be detected",
|
|
1424
|
+
);
|
|
1425
|
+
});
|