@remnic/core 9.69.50 → 9.69.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.js +30 -30
- package/dist/access-audit.d.ts +1 -0
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.js +31 -31
- package/dist/access-boundary.js +30 -30
- package/dist/access-cli.js +84 -83
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.js +30 -30
- package/dist/access-http-briefing.js +31 -31
- package/dist/access-http-query.js +32 -32
- package/dist/access-http.js +50 -50
- package/dist/access-identity-continuity-surface.js +30 -30
- package/dist/access-lcm-surface.js +30 -30
- package/dist/access-mcp-output-schemas.js +1 -1
- package/dist/access-mcp.js +41 -41
- package/dist/access-namespace-preflight.js +30 -30
- package/dist/access-observe-write-category.js +5 -5
- package/dist/access-observe-write-surface.js +30 -30
- package/dist/access-operations-batch.js +32 -32
- package/dist/access-operations.js +39 -39
- package/dist/access-recall-concurrency.js +30 -30
- package/dist/access-recall-response.js +30 -30
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +30 -30
- package/dist/access-schema.js +4 -4
- package/dist/access-service.js +30 -30
- package/dist/access-surface-catalog.js +32 -32
- package/dist/active-recall.js +2 -2
- package/dist/briefing.js +5 -5
- package/dist/{capsule-crypto-7FJQINUR.js → capsule-crypto-YO5QJ6L3.js} +2 -2
- package/dist/causal-consolidation.js +6 -6
- package/dist/{chunk-BHI5ILU5.js → chunk-25HSEIEM.js} +2 -2
- package/dist/{chunk-3RZB7TYL.js → chunk-2EICZYIG.js} +2 -2
- package/dist/{chunk-2IVHOQRY.js → chunk-2ERLN25A.js} +2 -2
- package/dist/{chunk-OQZYHCWM.js → chunk-2G2CVN2T.js} +27 -26
- package/dist/{chunk-OQZYHCWM.js.map → chunk-2G2CVN2T.js.map} +1 -1
- package/dist/{chunk-PPNFKQ6D.js → chunk-2SZBCM2S.js} +2 -2
- package/dist/{chunk-E73SVGJA.js → chunk-33SX6K3N.js} +2 -2
- package/dist/{chunk-D6WX5FEF.js → chunk-3QZRSEAM.js} +2 -2
- package/dist/{chunk-LBLXEFWK.js → chunk-4MN27537.js} +1 -1
- package/dist/chunk-4MN27537.js.map +1 -0
- package/dist/{chunk-LR7KUQ2W.js → chunk-6HCJWCA4.js} +5 -5
- package/dist/chunk-6VITOSPL.js +69 -0
- package/dist/chunk-6VITOSPL.js.map +1 -0
- package/dist/{chunk-EZV43XBD.js → chunk-6Z4OLQUO.js} +2 -2
- package/dist/{chunk-JTPAXDSX.js → chunk-6ZMIASYT.js} +2 -2
- package/dist/{chunk-UD6W67JM.js → chunk-72AV2H3R.js} +76 -25
- package/dist/chunk-72AV2H3R.js.map +1 -0
- package/dist/{chunk-DNO6QWCB.js → chunk-7TTOX6NW.js} +2 -2
- package/dist/{chunk-OP5IBPHP.js → chunk-A33DLYNY.js} +2 -2
- package/dist/{chunk-3MIZ2EKQ.js → chunk-AH3OIMS7.js} +2 -2
- package/dist/{chunk-TZ44E732.js → chunk-AIVRD66I.js} +2 -2
- package/dist/{chunk-IST6MBYC.js → chunk-BMAYH7F4.js} +473 -329
- package/dist/chunk-BMAYH7F4.js.map +1 -0
- package/dist/{chunk-XTMABIFW.js → chunk-BULK6DGB.js} +2 -2
- package/dist/{chunk-2NLLXCJG.js → chunk-BXLOS5AJ.js} +2 -2
- package/dist/{chunk-PMLFPVYJ.js → chunk-CKBA2MWS.js} +2 -2
- package/dist/{chunk-TVVEYCNW.js → chunk-DAOMWGO2.js} +2 -2
- package/dist/{chunk-E64HBW7M.js → chunk-DLXBMXIB.js} +2 -2
- package/dist/{chunk-X7Y7WX73.js → chunk-DQEMWVMT.js} +1 -1
- package/dist/{chunk-MQMZO23A.js → chunk-DQSVGTKB.js} +2 -2
- package/dist/{chunk-X6B2QWXW.js → chunk-DX63RFDJ.js} +2 -2
- package/dist/{chunk-OLJYDGGP.js → chunk-EBJL6HGR.js} +4 -4
- package/dist/{chunk-KOEDSRF5.js → chunk-ETNC6TT6.js} +2 -2
- package/dist/{chunk-SRMBMMTK.js → chunk-HEKWNWNL.js} +3 -3
- package/dist/{chunk-UI6R2YUB.js → chunk-IGDEKECC.js} +1 -1
- package/dist/{chunk-MECKA6G2.js → chunk-ISA5U5AP.js} +2 -2
- package/dist/{chunk-ZUNYFDI4.js → chunk-JGPG5UHM.js} +2 -2
- package/dist/{chunk-C3PKRFZ4.js → chunk-JXSOB3SA.js} +2 -2
- package/dist/{chunk-56C7OO3N.js → chunk-KAPYPTPS.js} +15 -15
- package/dist/{chunk-W6V7JN2E.js → chunk-KBHDV7P3.js} +3 -3
- package/dist/{chunk-F5V7KRQS.js → chunk-LIAKZGOY.js} +2 -2
- package/dist/{chunk-A7GQBJP5.js → chunk-LVFI5LG7.js} +7 -7
- package/dist/{chunk-QSP4Y73Q.js → chunk-LX7KIJQF.js} +2 -2
- package/dist/{chunk-ECLY4NZA.js → chunk-LZI6FWZW.js} +7 -1
- package/dist/chunk-LZI6FWZW.js.map +1 -0
- package/dist/{chunk-4PXFLDMJ.js → chunk-MNF6PTNL.js} +2 -2
- package/dist/{chunk-BOTSR5S3.js → chunk-MYDA2GA2.js} +2 -2
- package/dist/{chunk-4SDO74N2.js → chunk-NROI5XMG.js} +2 -2
- package/dist/{chunk-IYHHCSF6.js → chunk-NVW5ISEJ.js} +32 -32
- package/dist/{chunk-AL7OLBL7.js → chunk-NXSNFPYI.js} +2 -2
- package/dist/{chunk-D7QX26FF.js → chunk-O4JFBEUL.js} +2 -2
- package/dist/{chunk-PA5EOTRR.js → chunk-OIK3YL2P.js} +3 -3
- package/dist/{chunk-JOP7W24J.js → chunk-PLJ2MQFT.js} +4 -4
- package/dist/{chunk-HRCWDO6S.js → chunk-PYTSGD5M.js} +2 -2
- package/dist/{chunk-VBRS7Z3O.js → chunk-Q6OC4UUT.js} +2 -2
- package/dist/{chunk-4FWNMN5R.js → chunk-QRSC37NC.js} +5 -5
- package/dist/{chunk-D2XUSZ24.js → chunk-RIP2AOZW.js} +2 -2
- package/dist/{chunk-ZBAQMSEY.js → chunk-SCM4VYJW.js} +2 -2
- package/dist/{chunk-6525YIYL.js → chunk-SMI2G3MA.js} +3 -3
- package/dist/{chunk-DH536WT2.js → chunk-ST7FDKZT.js} +12 -2
- package/dist/chunk-ST7FDKZT.js.map +1 -0
- package/dist/{chunk-AYGWJ2UK.js → chunk-SZ75IB2T.js} +2 -2
- package/dist/{chunk-42BV46XS.js → chunk-T73ZC7C5.js} +2 -2
- package/dist/{chunk-3SI3FFTW.js → chunk-TNFDWJRS.js} +2 -2
- package/dist/{chunk-3UQIS2GC.js → chunk-TTFJQ5B5.js} +2 -2
- package/dist/{chunk-QZDUNK6V.js → chunk-UP3226JC.js} +6 -6
- package/dist/{chunk-IPLYGWQF.js → chunk-VDX2J7OX.js} +6 -6
- package/dist/{chunk-QXQCPNKA.js → chunk-VLR4S5GF.js} +7 -7
- package/dist/{chunk-ZHXSOMYH.js → chunk-VVX5PZFS.js} +2 -2
- package/dist/{chunk-HS75MCQF.js → chunk-WUUGE7QC.js} +4 -4
- package/dist/{chunk-MVMNEU65.js → chunk-X25NTN3W.js} +2 -2
- package/dist/{chunk-IC3PM3YO.js → chunk-XFXVIXDI.js} +2 -2
- package/dist/{chunk-ML5FW2RO.js → chunk-XHDQQ2TT.js} +2 -2
- package/dist/{chunk-XUKUMBP3.js → chunk-XRUOO7OT.js} +2 -2
- package/dist/{chunk-WVI43D54.js → chunk-YEIKOBOW.js} +15 -2
- package/dist/chunk-YEIKOBOW.js.map +1 -0
- package/dist/{chunk-AYP2FZFA.js → chunk-YIIW2OH2.js} +2 -2
- package/dist/{chunk-FO6IK4IF.js → chunk-YJJMJH6J.js} +8 -8
- package/dist/cli.js +68 -68
- package/dist/coding/export-okf-codegraph.js +4 -4
- package/dist/compounding/engine.js +5 -5
- package/dist/config.js +2 -2
- package/dist/connectors/codex-materialize-runner.js +5 -5
- package/dist/connectors/index.js +5 -5
- package/dist/convergence-refresh.js +7 -7
- package/dist/corpus-watermark.js +7 -7
- package/dist/deep-recall-http-glue.js +5 -5
- package/dist/deep-recall.js +2 -2
- package/dist/directory-sidecars.d.ts +28 -2
- package/dist/directory-sidecars.js +9 -1
- package/dist/entity-retrieval.js +5 -5
- package/dist/external-wiki-access.js +31 -31
- package/dist/extraction.js +5 -5
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +98 -88
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +8 -8
- package/dist/location/index.js +7 -7
- package/dist/maintenance/memory-governance.js +5 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +5 -5
- package/dist/maintenance/rebuild-memory-projection.js +6 -6
- package/dist/memory-browse-operations.js +31 -31
- package/dist/memory-browse.js +3 -3
- package/dist/namespaces/migrate.js +6 -6
- package/dist/namespaces/search.js +3 -3
- package/dist/namespaces/storage.js +5 -5
- package/dist/operator-doctor-corpus.js +8 -8
- package/dist/operator-toolkit.js +15 -15
- package/dist/orchestration/maintenance.js +13 -13
- package/dist/orchestrator.js +84 -83
- package/dist/recall-audit-anomaly.d.ts +1 -0
- package/dist/recall-audit.d.ts +4 -0
- package/dist/recall-audit.js +1 -1
- package/dist/recall-composition-decision.d.ts +41 -0
- package/dist/recall-composition-decision.js +17 -0
- package/dist/recall-composition-decision.js.map +1 -0
- package/dist/recall-explain-renderer.d.ts +1 -0
- package/dist/recall-explain-renderer.js +2 -2
- package/dist/recall-navigation-operations.js +31 -31
- package/dist/recall-navigation-renderer.d.ts +1 -0
- package/dist/recall-navigation.d.ts +1 -0
- package/dist/recall-navigation.js +30 -30
- package/dist/recall-state-view-admission.js +3 -3
- package/dist/recall-tag-filter.d.ts +1 -0
- package/dist/recall-xray-cli.d.ts +1 -0
- package/dist/recall-xray-cli.js +3 -3
- package/dist/recall-xray-renderer.d.ts +1 -0
- package/dist/recall-xray-renderer.js +2 -2
- package/dist/recall-xray.d.ts +9 -0
- package/dist/recall-xray.js +1 -1
- package/dist/resume-bundles.js +3 -3
- package/dist/search/factory.js +2 -2
- package/dist/search/index.js +4 -4
- package/dist/semantic-consolidation.js +6 -6
- package/dist/semantic-rule-promotion.js +5 -5
- package/dist/semantic-rule-verifier.js +5 -5
- package/dist/source-agent-qualifier.js +5 -5
- package/dist/standup/build.js +5 -5
- package/dist/storage.js +4 -4
- package/dist/support-passport/index.js +33 -33
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/export-okf.js +5 -5
- package/dist/verified-recall.js +5 -5
- package/dist/who-knows-cli.js +6 -6
- package/dist/who-knows.js +5 -5
- package/package.json +2 -2
- package/src/access-mcp-output-schemas.ts +14 -1
- package/src/access-recall-response.ts +3 -0
- package/src/directory-sidecars.test.ts +119 -0
- package/src/directory-sidecars.ts +110 -25
- package/src/experience/session-experience-recall.test.ts +228 -0
- package/src/experience/session-experience-recall.ts +60 -0
- package/src/index.ts +1 -0
- package/src/orchestration/recall-entry.ts +16 -2
- package/src/orchestration/recall-internal.ts +21 -18
- package/src/orchestration/recall-recognition-search.test.ts +290 -0
- package/src/orchestration/recall-recognition-search.ts +160 -0
- package/src/procedural/procedure-recall.ts +14 -3
- package/src/recall-audit.test.ts +34 -0
- package/src/recall-audit.ts +4 -0
- package/src/recall-composition-decision.test.ts +134 -0
- package/src/recall-composition-decision.ts +113 -0
- package/src/recall-xray-renderer.test.ts +17 -0
- package/src/recall-xray-renderer.ts +10 -0
- package/src/recall-xray.test.ts +28 -0
- package/src/recall-xray.ts +15 -0
- package/src/testing/subjects/recall-recognition-tier.test.ts +106 -0
- package/dist/chunk-DH536WT2.js.map +0 -1
- package/dist/chunk-ECLY4NZA.js.map +0 -1
- package/dist/chunk-IST6MBYC.js.map +0 -1
- package/dist/chunk-LBLXEFWK.js.map +0 -1
- package/dist/chunk-UD6W67JM.js.map +0 -1
- package/dist/chunk-WVI43D54.js.map +0 -1
- /package/dist/{capsule-crypto-7FJQINUR.js.map → capsule-crypto-YO5QJ6L3.js.map} +0 -0
- /package/dist/{chunk-BHI5ILU5.js.map → chunk-25HSEIEM.js.map} +0 -0
- /package/dist/{chunk-3RZB7TYL.js.map → chunk-2EICZYIG.js.map} +0 -0
- /package/dist/{chunk-2IVHOQRY.js.map → chunk-2ERLN25A.js.map} +0 -0
- /package/dist/{chunk-PPNFKQ6D.js.map → chunk-2SZBCM2S.js.map} +0 -0
- /package/dist/{chunk-E73SVGJA.js.map → chunk-33SX6K3N.js.map} +0 -0
- /package/dist/{chunk-D6WX5FEF.js.map → chunk-3QZRSEAM.js.map} +0 -0
- /package/dist/{chunk-LR7KUQ2W.js.map → chunk-6HCJWCA4.js.map} +0 -0
- /package/dist/{chunk-EZV43XBD.js.map → chunk-6Z4OLQUO.js.map} +0 -0
- /package/dist/{chunk-JTPAXDSX.js.map → chunk-6ZMIASYT.js.map} +0 -0
- /package/dist/{chunk-DNO6QWCB.js.map → chunk-7TTOX6NW.js.map} +0 -0
- /package/dist/{chunk-OP5IBPHP.js.map → chunk-A33DLYNY.js.map} +0 -0
- /package/dist/{chunk-3MIZ2EKQ.js.map → chunk-AH3OIMS7.js.map} +0 -0
- /package/dist/{chunk-TZ44E732.js.map → chunk-AIVRD66I.js.map} +0 -0
- /package/dist/{chunk-XTMABIFW.js.map → chunk-BULK6DGB.js.map} +0 -0
- /package/dist/{chunk-2NLLXCJG.js.map → chunk-BXLOS5AJ.js.map} +0 -0
- /package/dist/{chunk-PMLFPVYJ.js.map → chunk-CKBA2MWS.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-DAOMWGO2.js.map} +0 -0
- /package/dist/{chunk-E64HBW7M.js.map → chunk-DLXBMXIB.js.map} +0 -0
- /package/dist/{chunk-X7Y7WX73.js.map → chunk-DQEMWVMT.js.map} +0 -0
- /package/dist/{chunk-MQMZO23A.js.map → chunk-DQSVGTKB.js.map} +0 -0
- /package/dist/{chunk-X6B2QWXW.js.map → chunk-DX63RFDJ.js.map} +0 -0
- /package/dist/{chunk-OLJYDGGP.js.map → chunk-EBJL6HGR.js.map} +0 -0
- /package/dist/{chunk-KOEDSRF5.js.map → chunk-ETNC6TT6.js.map} +0 -0
- /package/dist/{chunk-SRMBMMTK.js.map → chunk-HEKWNWNL.js.map} +0 -0
- /package/dist/{chunk-UI6R2YUB.js.map → chunk-IGDEKECC.js.map} +0 -0
- /package/dist/{chunk-MECKA6G2.js.map → chunk-ISA5U5AP.js.map} +0 -0
- /package/dist/{chunk-ZUNYFDI4.js.map → chunk-JGPG5UHM.js.map} +0 -0
- /package/dist/{chunk-C3PKRFZ4.js.map → chunk-JXSOB3SA.js.map} +0 -0
- /package/dist/{chunk-56C7OO3N.js.map → chunk-KAPYPTPS.js.map} +0 -0
- /package/dist/{chunk-W6V7JN2E.js.map → chunk-KBHDV7P3.js.map} +0 -0
- /package/dist/{chunk-F5V7KRQS.js.map → chunk-LIAKZGOY.js.map} +0 -0
- /package/dist/{chunk-A7GQBJP5.js.map → chunk-LVFI5LG7.js.map} +0 -0
- /package/dist/{chunk-QSP4Y73Q.js.map → chunk-LX7KIJQF.js.map} +0 -0
- /package/dist/{chunk-4PXFLDMJ.js.map → chunk-MNF6PTNL.js.map} +0 -0
- /package/dist/{chunk-BOTSR5S3.js.map → chunk-MYDA2GA2.js.map} +0 -0
- /package/dist/{chunk-4SDO74N2.js.map → chunk-NROI5XMG.js.map} +0 -0
- /package/dist/{chunk-IYHHCSF6.js.map → chunk-NVW5ISEJ.js.map} +0 -0
- /package/dist/{chunk-AL7OLBL7.js.map → chunk-NXSNFPYI.js.map} +0 -0
- /package/dist/{chunk-D7QX26FF.js.map → chunk-O4JFBEUL.js.map} +0 -0
- /package/dist/{chunk-PA5EOTRR.js.map → chunk-OIK3YL2P.js.map} +0 -0
- /package/dist/{chunk-JOP7W24J.js.map → chunk-PLJ2MQFT.js.map} +0 -0
- /package/dist/{chunk-HRCWDO6S.js.map → chunk-PYTSGD5M.js.map} +0 -0
- /package/dist/{chunk-VBRS7Z3O.js.map → chunk-Q6OC4UUT.js.map} +0 -0
- /package/dist/{chunk-4FWNMN5R.js.map → chunk-QRSC37NC.js.map} +0 -0
- /package/dist/{chunk-D2XUSZ24.js.map → chunk-RIP2AOZW.js.map} +0 -0
- /package/dist/{chunk-ZBAQMSEY.js.map → chunk-SCM4VYJW.js.map} +0 -0
- /package/dist/{chunk-6525YIYL.js.map → chunk-SMI2G3MA.js.map} +0 -0
- /package/dist/{chunk-AYGWJ2UK.js.map → chunk-SZ75IB2T.js.map} +0 -0
- /package/dist/{chunk-42BV46XS.js.map → chunk-T73ZC7C5.js.map} +0 -0
- /package/dist/{chunk-3SI3FFTW.js.map → chunk-TNFDWJRS.js.map} +0 -0
- /package/dist/{chunk-3UQIS2GC.js.map → chunk-TTFJQ5B5.js.map} +0 -0
- /package/dist/{chunk-QZDUNK6V.js.map → chunk-UP3226JC.js.map} +0 -0
- /package/dist/{chunk-IPLYGWQF.js.map → chunk-VDX2J7OX.js.map} +0 -0
- /package/dist/{chunk-QXQCPNKA.js.map → chunk-VLR4S5GF.js.map} +0 -0
- /package/dist/{chunk-ZHXSOMYH.js.map → chunk-VVX5PZFS.js.map} +0 -0
- /package/dist/{chunk-HS75MCQF.js.map → chunk-WUUGE7QC.js.map} +0 -0
- /package/dist/{chunk-MVMNEU65.js.map → chunk-X25NTN3W.js.map} +0 -0
- /package/dist/{chunk-IC3PM3YO.js.map → chunk-XFXVIXDI.js.map} +0 -0
- /package/dist/{chunk-ML5FW2RO.js.map → chunk-XHDQQ2TT.js.map} +0 -0
- /package/dist/{chunk-XUKUMBP3.js.map → chunk-XRUOO7OT.js.map} +0 -0
- /package/dist/{chunk-AYP2FZFA.js.map → chunk-YIIW2OH2.js.map} +0 -0
- /package/dist/{chunk-FO6IK4IF.js.map → chunk-YJJMJH6J.js.map} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { abortError } from "./abort-error.js";
|
|
5
|
+
import {
|
|
6
|
+
compactRecallContextFromBuckets,
|
|
7
|
+
decideRecallContextComposition,
|
|
8
|
+
notifyContextComposition,
|
|
9
|
+
recallFailureComposition,
|
|
10
|
+
} from "./recall-composition-decision.js";
|
|
11
|
+
|
|
12
|
+
test("#2972 healthy recall composition carries no degradation marker", () => {
|
|
13
|
+
const { composition, truncated } = decideRecallContextComposition({
|
|
14
|
+
context: "A remembered deployment decision.",
|
|
15
|
+
maxChars: 512,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
assert.equal("degradation" in composition, false);
|
|
19
|
+
assert.equal(truncated, false);
|
|
20
|
+
assert.ok(!JSON.stringify(composition).includes("degradation"));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("#2972 empty recall stays marker-free", () => {
|
|
24
|
+
const { composition, context } = decideRecallContextComposition({
|
|
25
|
+
context: "",
|
|
26
|
+
maxChars: 512,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
assert.equal(context, "");
|
|
30
|
+
assert.equal("degradation" in composition, false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("#2972 compact form from section buckets is preferred over clipping", () => {
|
|
34
|
+
const longOne = "full-form entry one with a long body ".repeat(6).trim();
|
|
35
|
+
const longTwo = "full-form entry two with a long body ".repeat(6).trim();
|
|
36
|
+
const buckets = new Map<string, Array<string | { content: string }>>([
|
|
37
|
+
[
|
|
38
|
+
"memories",
|
|
39
|
+
[
|
|
40
|
+
"## Relevant Memories",
|
|
41
|
+
{ content: `[1] notes/one.md (score: 0.900)\n${longOne}` },
|
|
42
|
+
{ content: `[2] notes/two.md (score: 0.800)\n${longTwo}` },
|
|
43
|
+
],
|
|
44
|
+
],
|
|
45
|
+
]);
|
|
46
|
+
const compactContext = compactRecallContextFromBuckets(buckets);
|
|
47
|
+
assert.equal(
|
|
48
|
+
compactContext,
|
|
49
|
+
"## Relevant Memories\n[1] notes/one.md (score: 0.900)\n[2] notes/two.md (score: 0.800)",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const fullContext = `[1] notes/one.md (score: 0.900)\n${longOne}\n\n[2] notes/two.md (score: 0.800)\n${longTwo}`;
|
|
53
|
+
assert.ok(fullContext.length > compactContext.length);
|
|
54
|
+
assert.ok(compactContext.length <= 90);
|
|
55
|
+
|
|
56
|
+
const { composition, truncated } = decideRecallContextComposition({
|
|
57
|
+
context: fullContext,
|
|
58
|
+
compactContext,
|
|
59
|
+
maxChars: 90,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
assert.equal(composition.context, compactContext);
|
|
63
|
+
assert.equal(composition.degradation?.state, "degraded");
|
|
64
|
+
assert.equal(composition.degradation?.reason, "budget-compacted");
|
|
65
|
+
assert.equal(truncated, true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("#2972 whitespace-only context stays marker-free", () => {
|
|
69
|
+
const { composition, context, truncated } = decideRecallContextComposition({
|
|
70
|
+
context: " ",
|
|
71
|
+
maxChars: 512,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
assert.equal(context, "");
|
|
75
|
+
assert.equal("degradation" in composition, false);
|
|
76
|
+
assert.equal(truncated, false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("#2972 recall failure composition is missing, abort stays silent", () => {
|
|
80
|
+
const timeout = recallFailureComposition(new Error("recall timeout"));
|
|
81
|
+
assert.equal(timeout?.degradation?.state, "missing");
|
|
82
|
+
assert.equal(timeout?.degradation?.reason, "backend-unavailable");
|
|
83
|
+
assert.equal(timeout?.degradation?.detail, "timeout");
|
|
84
|
+
assert.match(timeout?.context ?? "", /memory context unavailable/i);
|
|
85
|
+
|
|
86
|
+
const failed = recallFailureComposition(new Error("qmd exploded"));
|
|
87
|
+
assert.equal(failed?.degradation?.detail, "recall_failed");
|
|
88
|
+
|
|
89
|
+
assert.equal(recallFailureComposition(abortError("recall aborted")), null);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("#2972 composition observer is fail-open", async () => {
|
|
93
|
+
const seen: unknown[] = [];
|
|
94
|
+
notifyContextComposition(
|
|
95
|
+
(composition) => {
|
|
96
|
+
seen.push(composition.context);
|
|
97
|
+
},
|
|
98
|
+
{ context: "ok" },
|
|
99
|
+
() => {
|
|
100
|
+
throw new Error("onError must not run on success");
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
assert.deepEqual(seen, ["ok"]);
|
|
104
|
+
|
|
105
|
+
const errors: unknown[] = [];
|
|
106
|
+
notifyContextComposition(
|
|
107
|
+
() => {
|
|
108
|
+
throw new Error("observer boom");
|
|
109
|
+
},
|
|
110
|
+
{ context: "ok" },
|
|
111
|
+
(err) => {
|
|
112
|
+
errors.push(err);
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
assert.equal(errors.length, 1);
|
|
116
|
+
|
|
117
|
+
await new Promise<void>((resolve, reject) => {
|
|
118
|
+
notifyContextComposition(
|
|
119
|
+
async () => {
|
|
120
|
+
throw new Error("async boom");
|
|
121
|
+
},
|
|
122
|
+
{ context: "ok" },
|
|
123
|
+
(err) => {
|
|
124
|
+
try {
|
|
125
|
+
assert.equal((err as Error).message, "async boom");
|
|
126
|
+
resolve();
|
|
127
|
+
} catch (caught) {
|
|
128
|
+
reject(caught);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall composition decision (#2972 layer 2).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from recall-internal so that file stays under its ratchet
|
|
5
|
+
* ceiling. Classifies injected memory context as complete / degraded /
|
|
6
|
+
* missing, builds the compact (first-line) form from section buckets,
|
|
7
|
+
* and turns outer-recall failures into the missing-note instead of "".
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isAbortError } from "./abort-error.js";
|
|
11
|
+
import {
|
|
12
|
+
boundRecallContextComposition,
|
|
13
|
+
composeMissingMemoryContext,
|
|
14
|
+
composeRecallContext,
|
|
15
|
+
RECALL_CONTEXT_SEPARATOR,
|
|
16
|
+
type RecallContextComposition,
|
|
17
|
+
} from "./recall-context-composition.js";
|
|
18
|
+
|
|
19
|
+
export type RecallCompositionChunk = string | { readonly content: string };
|
|
20
|
+
|
|
21
|
+
export interface DecideRecallContextCompositionInput {
|
|
22
|
+
context: string;
|
|
23
|
+
compactContext?: string;
|
|
24
|
+
footer?: string;
|
|
25
|
+
maxChars: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface DecidedRecallContextComposition {
|
|
29
|
+
composition: RecallContextComposition;
|
|
30
|
+
context: string;
|
|
31
|
+
truncated: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function firstLine(content: string): string {
|
|
35
|
+
const trimmed = content.trim();
|
|
36
|
+
if (trimmed.length === 0) return "";
|
|
37
|
+
const newline = trimmed.indexOf("\n");
|
|
38
|
+
return newline === -1 ? trimmed : trimmed.slice(0, newline).trimEnd();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function chunkContent(chunk: RecallCompositionChunk): string {
|
|
42
|
+
return typeof chunk === "string" ? chunk : chunk.content;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Shallow form of every section chunk: the first line of each entry,
|
|
47
|
+
* so a budget miss prefers breadth-complete-but-shallow over a clipped
|
|
48
|
+
* tail. Empty chunks contribute nothing.
|
|
49
|
+
*/
|
|
50
|
+
export function compactRecallContextFromBuckets(
|
|
51
|
+
buckets: Iterable<readonly [unknown, readonly RecallCompositionChunk[]]>,
|
|
52
|
+
): string {
|
|
53
|
+
const sections: string[] = [];
|
|
54
|
+
for (const [, chunks] of buckets) {
|
|
55
|
+
const lines: string[] = [];
|
|
56
|
+
for (const chunk of chunks) {
|
|
57
|
+
const line = firstLine(chunkContent(chunk));
|
|
58
|
+
if (line.length > 0) lines.push(line);
|
|
59
|
+
}
|
|
60
|
+
if (lines.length > 0) sections.push(lines.join("\n"));
|
|
61
|
+
}
|
|
62
|
+
return sections.join(RECALL_CONTEXT_SEPARATOR);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function decideRecallContextComposition(
|
|
66
|
+
input: DecideRecallContextCompositionInput,
|
|
67
|
+
): DecidedRecallContextComposition {
|
|
68
|
+
const composition = boundRecallContextComposition({
|
|
69
|
+
context: input.context,
|
|
70
|
+
footer: input.footer,
|
|
71
|
+
maxChars: input.maxChars,
|
|
72
|
+
compactContext: input.compactContext,
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
composition,
|
|
76
|
+
context: composeRecallContext(composition),
|
|
77
|
+
truncated:
|
|
78
|
+
composition.degradation?.reason === "budget-clipped" ||
|
|
79
|
+
composition.degradation?.reason === "budget-compacted",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function notifyContextComposition(
|
|
84
|
+
onContextComposition:
|
|
85
|
+
| ((composition: RecallContextComposition) => void | PromiseLike<void>)
|
|
86
|
+
| undefined,
|
|
87
|
+
composition: RecallContextComposition,
|
|
88
|
+
onError: (err: unknown) => void,
|
|
89
|
+
): void {
|
|
90
|
+
if (!onContextComposition) return;
|
|
91
|
+
try {
|
|
92
|
+
const result = onContextComposition(composition);
|
|
93
|
+
if (result && typeof result.then === "function") {
|
|
94
|
+
void Promise.resolve(result).catch(onError);
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
onError(err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Outer-recall failure: abort stays silent (empty string). Timeout and
|
|
103
|
+
* other errors become the missing-note so a failed query is never
|
|
104
|
+
* indistinguishable from "no matches".
|
|
105
|
+
*/
|
|
106
|
+
export function recallFailureComposition(err: unknown): RecallContextComposition | null {
|
|
107
|
+
if (isAbortError(err)) return null;
|
|
108
|
+
const detail =
|
|
109
|
+
err instanceof Error && err.message === "recall timeout"
|
|
110
|
+
? "timeout"
|
|
111
|
+
: "recall_failed";
|
|
112
|
+
return composeMissingMemoryContext({ detail });
|
|
113
|
+
}
|
|
@@ -615,3 +615,20 @@ test("renderXrayText tier-explain block matches shared helper output", async ()
|
|
|
615
615
|
assert.ok(nullText.includes(line));
|
|
616
616
|
}
|
|
617
617
|
});
|
|
618
|
+
|
|
619
|
+
test("#2972 xray text and markdown surface degradation only when present", () => {
|
|
620
|
+
const healthy = minimalSnapshot();
|
|
621
|
+
const degraded = {
|
|
622
|
+
...minimalSnapshot(),
|
|
623
|
+
degradation: {
|
|
624
|
+
state: "degraded" as const,
|
|
625
|
+
reason: "budget-clipped" as const,
|
|
626
|
+
},
|
|
627
|
+
};
|
|
628
|
+
const healthyText = renderXrayText(healthy);
|
|
629
|
+
assert.equal(healthyText.includes("degradation:"), false);
|
|
630
|
+
assert.match(renderXrayText(degraded), /degradation: degraded\/budget-clipped/);
|
|
631
|
+
assert.equal(renderXrayMarkdown(healthy).includes("| Degradation |"), false);
|
|
632
|
+
assert.match(renderXrayMarkdown(degraded), /\| Degradation \| degraded\/budget-clipped \|/);
|
|
633
|
+
});
|
|
634
|
+
|
|
@@ -108,6 +108,11 @@ export function renderXrayText(snapshot: RecallXraySnapshot | null): string {
|
|
|
108
108
|
lines.push(
|
|
109
109
|
`budget: ${snapshot.budget.used} / ${snapshot.budget.chars} chars`,
|
|
110
110
|
);
|
|
111
|
+
if (snapshot.degradation) {
|
|
112
|
+
lines.push(
|
|
113
|
+
`degradation: ${snapshot.degradation.state}/${snapshot.degradation.reason}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
111
116
|
|
|
112
117
|
lines.push("");
|
|
113
118
|
lines.push("--- filters ---");
|
|
@@ -247,6 +252,11 @@ export function renderXrayMarkdown(
|
|
|
247
252
|
lines.push(
|
|
248
253
|
`| Budget | ${snapshot.budget.used} / ${snapshot.budget.chars} chars |`,
|
|
249
254
|
);
|
|
255
|
+
if (snapshot.degradation) {
|
|
256
|
+
lines.push(
|
|
257
|
+
`| Degradation | ${snapshot.degradation.state}/${snapshot.degradation.reason} |`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
250
260
|
|
|
251
261
|
lines.push("");
|
|
252
262
|
lines.push("## Filters");
|
package/src/recall-xray.test.ts
CHANGED
|
@@ -650,3 +650,31 @@ test("RecallXrayBuilder clones the trust projection so the snapshot surfaces it
|
|
|
650
650
|
"quarantine reason preserved so exclusion never looks like 'no result' (rule 34)",
|
|
651
651
|
);
|
|
652
652
|
});
|
|
653
|
+
|
|
654
|
+
test("#2972 healthy xray snapshot omits the degradation marker", () => {
|
|
655
|
+
const snapshot = buildXraySnapshot({
|
|
656
|
+
query: "q",
|
|
657
|
+
now: fixedNow,
|
|
658
|
+
snapshotIdGenerator: idGen(),
|
|
659
|
+
});
|
|
660
|
+
assert.equal("degradation" in snapshot, false);
|
|
661
|
+
assert.ok(!JSON.stringify(snapshot).includes("degradation"));
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
test("#2972 xray snapshot copies degradation and isolates caller mutation", () => {
|
|
665
|
+
const degradation = {
|
|
666
|
+
state: "degraded" as const,
|
|
667
|
+
reason: "budget-clipped" as const,
|
|
668
|
+
budget: { contextBudget: 20, fullChars: 100, deliveredChars: 20 },
|
|
669
|
+
};
|
|
670
|
+
const snapshot = buildXraySnapshot({
|
|
671
|
+
query: "q",
|
|
672
|
+
degradation,
|
|
673
|
+
now: fixedNow,
|
|
674
|
+
snapshotIdGenerator: idGen(),
|
|
675
|
+
});
|
|
676
|
+
assert.deepEqual(snapshot.degradation, degradation);
|
|
677
|
+
degradation.budget.deliveredChars = 0;
|
|
678
|
+
assert.equal(snapshot.degradation?.budget?.deliveredChars, 20);
|
|
679
|
+
});
|
|
680
|
+
|
package/src/recall-xray.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { estimateTokenCount } from "./token-estimate.js";
|
|
|
24
24
|
import type { RecallDisclosure, RecallTierExplain } from "./types.js";
|
|
25
25
|
import type { StateLabel } from "./recall-state-view.js";
|
|
26
26
|
import { isRecallDisclosure } from "./types.js";
|
|
27
|
+
import type { RecallContextDegradation } from "./recall-context-composition.js";
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Estimate token cost of a payload for X-ray disclosure accounting.
|
|
@@ -282,6 +283,12 @@ export interface RecallXraySnapshot {
|
|
|
282
283
|
* absent) means no peer profile was injected.
|
|
283
284
|
*/
|
|
284
285
|
peerProfileInjection?: RecallXrayPeerProfileInjection | null;
|
|
286
|
+
/**
|
|
287
|
+
* Injected-context degradation (#2972). Absent on a healthy recall so
|
|
288
|
+
* the snapshot stays byte-stable. Budget warnings ride here, never
|
|
289
|
+
* in the injected text.
|
|
290
|
+
*/
|
|
291
|
+
degradation?: RecallContextDegradation;
|
|
285
292
|
}
|
|
286
293
|
|
|
287
294
|
// ─── Builder ──────────────────────────────────────────────────────────────
|
|
@@ -300,6 +307,8 @@ export interface BuildXraySnapshotInput {
|
|
|
300
307
|
traceId?: string;
|
|
301
308
|
/** Peer-profile injection metadata (issue #679 completion). */
|
|
302
309
|
peerProfileInjection?: RecallXrayPeerProfileInjection | null;
|
|
310
|
+
/** Injected-context degradation (#2972). Omit on a healthy recall. */
|
|
311
|
+
degradation?: RecallContextDegradation;
|
|
303
312
|
/** Optional injected timestamp for deterministic tests. */
|
|
304
313
|
now?: () => number;
|
|
305
314
|
/** Optional injected id generator for deterministic tests. */
|
|
@@ -375,6 +384,12 @@ export function buildXraySnapshot(
|
|
|
375
384
|
if (peerProfileInjection !== undefined) {
|
|
376
385
|
out.peerProfileInjection = peerProfileInjection;
|
|
377
386
|
}
|
|
387
|
+
if (input.degradation && typeof input.degradation === "object") {
|
|
388
|
+
out.degradation = { ...input.degradation };
|
|
389
|
+
if (input.degradation.budget) {
|
|
390
|
+
out.degradation.budget = { ...input.degradation.budget };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
378
393
|
return out;
|
|
379
394
|
}
|
|
380
395
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recognition-tier swap subject for the scenario-matrix harness (issue #2975).
|
|
3
|
+
*
|
|
4
|
+
* Every canonical row runs the real fetchQmdMemoryResultsWithRecognitionSwap
|
|
5
|
+
* seam twice: gate off (original fetch only, zero index I/O, zero recognizer
|
|
6
|
+
* calls) and gate on with an under-threshold index (one recognizer call, the
|
|
7
|
+
* namespace never reaches vector search). The scenario dimensions are
|
|
8
|
+
* orthogonal to this routing decision, so the same invariant holds per row.
|
|
9
|
+
*/
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
fetchQmdMemoryResultsWithRecognitionSwap,
|
|
17
|
+
type RecognitionSearchDeps,
|
|
18
|
+
} from "../../orchestration/recall-recognition-search.js";
|
|
19
|
+
import { saveRecognitionIndex } from "../../recall-recognition-tier.js";
|
|
20
|
+
import type { QmdSearchResult } from "../../types.js";
|
|
21
|
+
import { type LifecycleSubject, type MatrixRow, runLifecycleMatrix } from "../lifecycle-matrix.js";
|
|
22
|
+
interface SwapState {
|
|
23
|
+
rowId: string;
|
|
24
|
+
dir: string;
|
|
25
|
+
deps: (enabled: boolean) => RecognitionSearchDeps;
|
|
26
|
+
fetchCalls: number;
|
|
27
|
+
recognizeCalls: number;
|
|
28
|
+
storageForCalls: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const subject: LifecycleSubject<SwapState> = {
|
|
32
|
+
async setup(row: MatrixRow): Promise<SwapState> {
|
|
33
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-recognition-subject-"));
|
|
34
|
+
await saveRecognitionIndex(dir, {
|
|
35
|
+
version: 1,
|
|
36
|
+
entries: [
|
|
37
|
+
{ id: `m-${row.id}-a`, description: `trigger for ${row.id}-a` },
|
|
38
|
+
{ id: `m-${row.id}-b`, description: `trigger for ${row.id}-b` },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
const state: SwapState = { rowId: String(row.id), dir, deps: undefined as never, fetchCalls: 0, recognizeCalls: 0, storageForCalls: 0 };
|
|
42
|
+
state.deps = (enabled) =>
|
|
43
|
+
({
|
|
44
|
+
config: { recallRecognitionTier: enabled, recognitionIndexMaxEntries: 500 },
|
|
45
|
+
storageRouter: {
|
|
46
|
+
storageFor: async () => {
|
|
47
|
+
state.storageForCalls += 1;
|
|
48
|
+
return {
|
|
49
|
+
dir,
|
|
50
|
+
findExistingMemoryPaths: async (ids: string[]) =>
|
|
51
|
+
new Map(ids.map((id) => [id, [path.join(dir, `facts/${id}.md`)]])),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
fastLlmForRerank: {
|
|
56
|
+
chatCompletion: async () => {
|
|
57
|
+
state.recognizeCalls += 1;
|
|
58
|
+
return { content: JSON.stringify([`m-${row.id}-a`, `m-${row.id}-b`]) };
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
fetchQmdMemoryResultsWithArtifactTopUp: async (): Promise<QmdSearchResult[]> => {
|
|
62
|
+
state.fetchCalls += 1;
|
|
63
|
+
return [{ docid: `default:vector.md`, namespace: "default", path: "vector.md", score: 1, snippet: "vector" }];
|
|
64
|
+
},
|
|
65
|
+
}) as unknown as RecognitionSearchDeps;
|
|
66
|
+
return state;
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async exercise(state): Promise<void> {
|
|
70
|
+
const options = {
|
|
71
|
+
namespacesEnabled: true,
|
|
72
|
+
recallNamespaces: ["default"],
|
|
73
|
+
resolveNamespace: () => "default",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const off = await fetchQmdMemoryResultsWithRecognitionSwap(state.deps(false), "q", 5, 7, options);
|
|
77
|
+
assert.equal(off.length, 1);
|
|
78
|
+
assert.equal(state.fetchCalls, 1);
|
|
79
|
+
assert.equal(state.recognizeCalls, 0);
|
|
80
|
+
assert.equal(state.storageForCalls, 0);
|
|
81
|
+
|
|
82
|
+
const on = await fetchQmdMemoryResultsWithRecognitionSwap(state.deps(true), "q", 5, 7, options);
|
|
83
|
+
assert.equal(state.recognizeCalls, 1);
|
|
84
|
+
assert.equal(state.fetchCalls, 1, "recognition namespace must not reach vector fetch");
|
|
85
|
+
assert.deepEqual(on.map((r) => r.path).sort(), [`facts/m-${state.rowId}-a.md`, `facts/m-${state.rowId}-b.md`].sort());
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async invariants(state): Promise<void> {
|
|
89
|
+
// Off-path again after the on-path run: still a pure passthrough.
|
|
90
|
+
const before = { fetch: state.fetchCalls, recognize: state.recognizeCalls, storage: state.storageForCalls };
|
|
91
|
+
await fetchQmdMemoryResultsWithRecognitionSwap(state.deps(false), "q", 5, 7, {
|
|
92
|
+
namespacesEnabled: true,
|
|
93
|
+
recallNamespaces: ["default"],
|
|
94
|
+
resolveNamespace: () => "default",
|
|
95
|
+
});
|
|
96
|
+
assert.equal(state.fetchCalls, before.fetch + 1);
|
|
97
|
+
assert.equal(state.recognizeCalls, before.recognize);
|
|
98
|
+
assert.equal(state.storageForCalls, before.storage);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async teardown(state): Promise<void> {
|
|
102
|
+
await rm(state.dir, { recursive: true, force: true });
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
runLifecycleMatrix("recall-recognition-tier", subject);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recall-explain-renderer.ts","../src/recall-xray-renderer.ts"],"sourcesContent":["/**\n * Renderers for RecallTierExplain (issue #518).\n *\n * Pure functions that format a `LastRecallSnapshot` and its\n * optional `tierExplain` field for human text and machine JSON\n * consumption. CLI / HTTP / MCP surfaces consume these — they do\n * not format explain output themselves, so rendering is tested in\n * one place.\n */\n\nimport type { LastRecallSnapshot } from \"./recall-state.js\";\nimport type { RecallTierExplain } from \"./types.js\";\nimport { isRetrievalTier } from \"./retrieval-tiers.js\";\nimport type {\n RecallXraySnapshot,\n RecallFilterTrace,\n RecallXrayResult,\n} from \"./recall-xray.js\";\nimport { renderXrayMarkdown } from \"./recall-xray-renderer.js\";\n\nfunction sanitizeString(v: unknown): string | null {\n return typeof v === \"string\" && v.length > 0 ? v : null;\n}\n\nfunction sanitizeFiniteNumber(v: unknown): number | null {\n return typeof v === \"number\" && Number.isFinite(v) ? v : null;\n}\n\n/**\n * `text` and `json` are the original formats (backwards-compatible\n * since issue #518). `markdown` was added in issue #570 PR 7 and\n * delegates to the shared X-ray renderer so the three observability\n * surfaces stay in lock-step (CLAUDE.md rule 22).\n */\nexport type RecallExplainFormat = \"text\" | \"json\" | \"markdown\";\n\nexport interface RecallExplainJsonPayload {\n hasExplain: boolean;\n snapshotFound: boolean;\n sessionKey: string | null;\n recordedAt: string | null;\n namespace: string | null;\n memoryIds: string[];\n source: string | null;\n sourcesUsed: string[] | null;\n latencyMs: number | null;\n tierExplain: RecallTierExplain | null;\n}\n\nfunction normalizeTierExplain(value: unknown): RecallTierExplain | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n const raw = value as Record<string, unknown>;\n const filteredBy = Array.isArray(raw.filteredBy)\n ? raw.filteredBy.filter((x): x is string => typeof x === \"string\")\n : [];\n const sourceAnchors = Array.isArray(raw.sourceAnchors)\n ? raw.sourceAnchors\n .filter(\n (a): a is { path: string; lineRange?: unknown } =>\n !!a && typeof a === \"object\" && typeof (a as { path?: unknown }).path === \"string\",\n )\n .map((a) => {\n const lr = (a as { lineRange?: unknown }).lineRange;\n const lineRange =\n Array.isArray(lr) &&\n lr.length === 2 &&\n Number.isFinite(lr[0]) &&\n Number.isFinite(lr[1])\n ? ([lr[0] as number, lr[1] as number] as [number, number])\n : undefined;\n return lineRange\n ? { path: (a as { path: string }).path, lineRange }\n : { path: (a as { path: string }).path };\n })\n : undefined;\n return {\n tier: isRetrievalTier(raw.tier) ? raw.tier : \"hybrid\",\n tierReason: typeof raw.tierReason === \"string\" ? raw.tierReason : \"\",\n filteredBy,\n candidatesConsidered: sanitizeFiniteNumber(raw.candidatesConsidered) ?? 0,\n latencyMs: sanitizeFiniteNumber(raw.latencyMs) ?? 0,\n ...(sourceAnchors !== undefined ? { sourceAnchors } : {}),\n };\n}\n\nexport function toRecallExplainJson(\n snapshot: LastRecallSnapshot | null,\n): RecallExplainJsonPayload {\n if (!snapshot) {\n return {\n hasExplain: false,\n snapshotFound: false,\n sessionKey: null,\n recordedAt: null,\n namespace: null,\n memoryIds: [],\n source: null,\n sourcesUsed: null,\n latencyMs: null,\n tierExplain: null,\n };\n }\n const normalizedExplain = normalizeTierExplain(snapshot.tierExplain);\n return {\n hasExplain: normalizedExplain !== null,\n snapshotFound: true,\n sessionKey: sanitizeString(snapshot.sessionKey),\n recordedAt: sanitizeString(snapshot.recordedAt),\n namespace: sanitizeString(snapshot.namespace),\n memoryIds: Array.isArray(snapshot.memoryIds)\n ? snapshot.memoryIds.filter((x): x is string => typeof x === \"string\")\n : [],\n source: sanitizeString(snapshot.source),\n sourcesUsed: Array.isArray(snapshot.sourcesUsed)\n ? snapshot.sourcesUsed.filter((x): x is string => typeof x === \"string\")\n : null,\n latencyMs: sanitizeFiniteNumber(snapshot.latencyMs),\n tierExplain: normalizedExplain,\n };\n}\n\n/**\n * Render the shared \"--- tier explain ---\" text block used by both the\n * recall-explain surface and the Recall X-ray surface. Callers provide\n * the normalized `RecallTierExplain` (or `null` for the\n * not-populated/disabled case) so the block stays character-for-character\n * identical across surfaces (CLAUDE.md rule 22). The returned strings do\n * NOT include leading blank lines or headers — callers own that framing.\n */\nexport function renderTierExplainTextLines(\n tierExplain: RecallTierExplain | null,\n): string[] {\n const lines: string[] = [];\n if (!tierExplain) {\n lines.push(\n \"(not populated — direct-answer tier disabled or did not fire)\",\n );\n return lines;\n }\n lines.push(`tier: ${tierExplain.tier}`);\n lines.push(`reason: ${tierExplain.tierReason}`);\n lines.push(`candidates-considered: ${tierExplain.candidatesConsidered}`);\n lines.push(`latency-ms: ${tierExplain.latencyMs}`);\n if (tierExplain.filteredBy.length > 0) {\n lines.push(`filtered-by: ${tierExplain.filteredBy.join(\", \")}`);\n } else {\n lines.push(\"filtered-by: (none)\");\n }\n if (tierExplain.sourceAnchors && tierExplain.sourceAnchors.length > 0) {\n lines.push(\"source-anchors:\");\n for (const anchor of tierExplain.sourceAnchors) {\n const range = anchor.lineRange\n ? `:${anchor.lineRange[0]}-${anchor.lineRange[1]}`\n : \"\";\n lines.push(` - ${anchor.path}${range}`);\n }\n }\n return lines;\n}\n\nexport function toRecallExplainText(\n snapshot: LastRecallSnapshot | null,\n): string {\n const lines: string[] = [\"=== Recall Explain ===\"];\n\n if (!snapshot) {\n lines.push(\"No recall snapshot recorded yet.\");\n return lines.join(\"\\n\");\n }\n\n const sessionKey = sanitizeString(snapshot.sessionKey);\n const recordedAt = sanitizeString(snapshot.recordedAt);\n const namespace = sanitizeString(snapshot.namespace);\n const source = sanitizeString(snapshot.source);\n lines.push(`session: ${sessionKey ?? \"(unknown)\"}`);\n lines.push(`recorded: ${recordedAt ?? \"(unknown)\"}`);\n if (namespace) lines.push(`namespace: ${namespace}`);\n if (source) lines.push(`source: ${source}`);\n const sourcesUsed = Array.isArray(snapshot.sourcesUsed)\n ? snapshot.sourcesUsed.filter((x): x is string => typeof x === \"string\")\n : [];\n if (sourcesUsed.length > 0) {\n lines.push(`sources-used: ${sourcesUsed.join(\", \")}`);\n }\n const latencyMs = sanitizeFiniteNumber(snapshot.latencyMs);\n if (latencyMs !== null) {\n lines.push(`latency-ms: ${latencyMs}`);\n }\n const memoryIds = Array.isArray(snapshot.memoryIds)\n ? snapshot.memoryIds.filter((x): x is string => typeof x === \"string\")\n : [];\n if (memoryIds.length > 0) {\n lines.push(`memories: ${memoryIds.join(\", \")}`);\n }\n\n const ex = normalizeTierExplain(snapshot.tierExplain);\n if (!ex) {\n lines.push(\"\");\n lines.push(\n \"tier-explain: (not populated — direct-answer tier disabled or did not fire)\",\n );\n return lines.join(\"\\n\");\n }\n\n lines.push(\"\");\n lines.push(\"--- tier explain ---\");\n for (const line of renderTierExplainTextLines(ex)) {\n lines.push(line);\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * Adapter: convert a `LastRecallSnapshot` into a best-effort\n * `RecallXraySnapshot` so the markdown renderer can produce a\n * consistent, richly-formatted document for callers that have asked\n * for `markdown` format. The LastRecallSnapshot and the X-ray\n * snapshot share session/namespace/memoryIds; additional X-ray-only\n * fields (filters, score decomposition, graph path, audit id) are\n * left empty because the legacy snapshot doesn't carry them. The\n * renderer handles missing fields gracefully.\n */\n/**\n * Strip backticks, pipes, and newlines from a host-provided value so it\n * cannot escape its enclosing markdown code span, break the surrounding\n * table row, or inject extra rows when it lands in\n * `renderXrayMarkdown`. Applied at the adapter boundary because\n * `LastRecallSnapshot` is hydrated from on-disk JSON without schema\n * validation (codex P2 review on #605).\n *\n * Accepts `unknown` so non-string truthy values (numbers, objects,\n * booleans, arrays) coming from a corrupted snapshot are coerced to\n * the empty string rather than crashing on `.replace(...)`. Callers\n * should treat an empty return as \"drop this field.\"\n */\nfunction sanitizeForMarkdownInline(value: unknown): string {\n if (typeof value !== \"string\") return \"\";\n return value.replace(/[`|\\r\\n]/g, \" \").trim();\n}\n\n/**\n * Map the legacy `LastRecallSnapshot.source` field to the\n * `RecallXrayServedBy` union used by the unified x-ray renderer.\n * Mirrors the `mapRecallSourceToXrayServedBy` helper inside\n * `orchestrator.ts` (which is private). Keep the two in sync when a\n * new source lands — unknown values collapse to `\"hybrid\"` to preserve\n * backwards compatibility with older on-disk snapshots.\n */\nfunction mapLegacySourceToServedBy(\n source: unknown,\n): \"hybrid\" | \"recent-scan\" {\n if (source === \"recent_scan\") return \"recent-scan\";\n return \"hybrid\";\n}\n\nexport function toRecallXraySnapshotFromLegacy(\n snapshot: LastRecallSnapshot | null,\n): RecallXraySnapshot | null {\n if (!snapshot) return null;\n const capturedAt = (() => {\n if (typeof snapshot.recordedAt !== \"string\") return 0;\n const ms = Date.parse(snapshot.recordedAt);\n return Number.isFinite(ms) && ms >= 0 ? ms : 0;\n })();\n const memoryIds = Array.isArray(snapshot.memoryIds)\n ? snapshot.memoryIds.filter((x): x is string => typeof x === \"string\")\n : [];\n // Codex P2 + Cursor Medium on #605: every converted result used to\n // be stamped with `servedBy: \"hybrid\"`, misattributing legacy\n // snapshots that came from `recent_scan` (or any future source).\n // Propagate the recorded `source` so markdown `recall-explain`\n // output matches the `served-by=` string the native x-ray capture\n // would emit for the same recall.\n const servedBy = mapLegacySourceToServedBy(snapshot.source);\n const results: RecallXrayResult[] = memoryIds.map((memoryId) => ({\n memoryId,\n path: \"\",\n servedBy,\n scoreDecomposition: { final: 0 },\n admittedBy: [],\n }));\n const filters: RecallFilterTrace[] = [];\n return {\n schemaVersion: \"1\",\n // `LastRecallSnapshot` does not preserve the original query text;\n // synthesize a placeholder so the renderer has a non-empty\n // string to print. `queryHash` + `queryLen` stay in the JSON\n // payload via `toRecallExplainJson` for callers that need them.\n query:\n snapshot.queryHash\n ? `(legacy explain; queryHash=${snapshot.queryHash})`\n : \"(legacy explain)\",\n // `snapshotId` is synthesized here; `sessionKey` is already\n // sanitized before it reaches the ID because we re-use the\n // sanitized string below.\n snapshotId: `legacy-${sanitizeForMarkdownInline(snapshot.sessionKey ?? \"unknown\") || \"unknown\"}-${capturedAt}`,\n capturedAt,\n // Run the raw on-disk value through the same normalizer the text\n // and JSON paths use so the markdown adapter cannot render\n // unvalidated tier-explain payloads (cursor / codex review on\n // #605). A malformed tierExplain is dropped to null, matching the\n // behavior of the non-markdown surfaces.\n tierExplain: normalizeTierExplain(snapshot.tierExplain) ?? null,\n results,\n appliedResultLimit: 0,\n appliedResults: [],\n headroomResults: [],\n filters,\n budget: { chars: 0, used: 0 },\n // Sanitize legacy session metadata at the adapter boundary so a\n // malformed on-disk value (containing backticks, pipes, or\n // newlines) cannot break the enclosing markdown table when\n // `renderXrayMarkdown` prints it in a raw code-span cell (codex P2\n // review on #605).\n ...(snapshot.sessionKey\n ? (() => {\n const clean = sanitizeForMarkdownInline(snapshot.sessionKey);\n return clean ? { sessionKey: clean } : {};\n })()\n : {}),\n ...(snapshot.namespace\n ? (() => {\n const clean = sanitizeForMarkdownInline(snapshot.namespace);\n return clean ? { namespace: clean } : {};\n })()\n : {}),\n };\n}\n\nexport function renderRecallExplain(\n snapshot: LastRecallSnapshot | null,\n format: RecallExplainFormat,\n): string {\n if (format === \"json\") {\n return JSON.stringify(toRecallExplainJson(snapshot), null, 2);\n }\n if (format === \"markdown\") {\n // Delegate to the shared X-ray renderer so CLI / HTTP / MCP\n // markdown output all share one implementation (CLAUDE.md rule\n // 22). The JSON and text paths remain byte-for-byte\n // backwards-compatible with pre-#570 behavior.\n return renderXrayMarkdown(toRecallXraySnapshotFromLegacy(snapshot));\n }\n return toRecallExplainText(snapshot);\n}\n\nexport function parseRecallExplainFormat(value: unknown): RecallExplainFormat {\n if (value === undefined || value === null) return \"text\";\n if (typeof value !== \"string\") {\n throw new Error(\n `--format expects \"text\", \"json\", or \"markdown\", got ${typeof value}`,\n );\n }\n const v = value.trim().toLowerCase();\n if (v === \"text\" || v === \"json\" || v === \"markdown\") return v;\n throw new Error(\n `--format expects \"text\", \"json\", or \"markdown\", got ${JSON.stringify(value)}`,\n );\n}\n","/**\n * Unified Recall X-ray renderer (issue #570, PR 2).\n *\n * Pure functions that format a `RecallXraySnapshot` for human text,\n * GitHub-flavored markdown, and machine JSON consumption. CLI / HTTP\n * / MCP surfaces all call into this module — they do NOT format X-ray\n * output themselves, so rendering is tested in one place (CLAUDE.md\n * rule 22).\n *\n * Scope for PR 2 (this slice):\n * - Pure rendering. No IO, no transport, no capture.\n * - `renderXray(snapshot, format)` with format ∈\n * `{\"json\", \"text\", \"markdown\"}`.\n * - `parseXrayFormat(value)` — input validator that rejects unknown\n * formats with a listed-options error (CLAUDE.md rule 51).\n * - Golden-file-style tests in `recall-xray-renderer.test.ts`.\n */\n\nimport type {\n RecallFilterTrace,\n RecallXrayResult,\n RecallXraySnapshot,\n RecallXrayServedBy,\n} from \"./recall-xray.js\";\nimport { summarizeDisclosureTokens } from \"./recall-xray.js\";\nimport { renderTierExplainTextLines } from \"./recall-explain-renderer.js\";\nimport { summarizeRetrievedMemoryProvenance } from \"./memory-provenance.js\";\n\nexport type RecallXrayFormat = \"json\" | \"text\" | \"markdown\";\n\nexport const RECALL_XRAY_FORMATS: readonly RecallXrayFormat[] = [\n \"json\",\n \"text\",\n \"markdown\",\n] as const;\n\n/**\n * Validate and coerce a user-provided `--format` / `format` argument to\n * `RecallXrayFormat`. Unknown values throw an error listing valid\n * options (CLAUDE.md rule 51). `undefined`/`null` defaults to `\"text\"`.\n */\nexport function parseXrayFormat(value: unknown): RecallXrayFormat {\n if (value === undefined || value === null) return \"text\";\n if (typeof value !== \"string\") {\n throw new Error(\n `--format expects one of ${RECALL_XRAY_FORMATS.join(\", \")}; got ${typeof value}`,\n );\n }\n const v = value.trim().toLowerCase();\n if (v === \"json\" || v === \"text\" || v === \"markdown\") return v;\n throw new Error(\n `--format expects one of ${RECALL_XRAY_FORMATS.join(\", \")}; got ${JSON.stringify(value)}`,\n );\n}\n\n/**\n * Top-level dispatcher. CLI / HTTP / MCP callers should always route\n * through this function so the three formats stay in lock-step.\n */\nexport function renderXray(\n snapshot: RecallXraySnapshot | null,\n format: RecallXrayFormat,\n): string {\n if (format === \"json\") return renderXrayJson(snapshot);\n if (format === \"markdown\") return renderXrayMarkdown(snapshot);\n return renderXrayText(snapshot);\n}\n\n// ─── JSON ─────────────────────────────────────────────────────────────────\n\n/**\n * Deterministic JSON encoding of an X-ray snapshot. Returns a stable\n * v1 envelope when the snapshot is absent so consumers can pattern-match\n * on `snapshotFound` rather than distinguishing `null` vs `{}`.\n */\nexport function renderXrayJson(snapshot: RecallXraySnapshot | null): string {\n if (!snapshot) {\n return JSON.stringify(\n { schemaVersion: \"1\", snapshotFound: false },\n null,\n 2,\n );\n }\n // `snapshotFound` is injected *before* the rest so downstream JSON\n // consumers see it near the top of the document.\n return JSON.stringify(\n { snapshotFound: true, ...snapshot },\n null,\n 2,\n );\n}\n\n// ─── Text ─────────────────────────────────────────────────────────────────\n\nexport function renderXrayText(snapshot: RecallXraySnapshot | null): string {\n const lines: string[] = [\"=== Recall X-ray ===\"];\n if (!snapshot) {\n lines.push(\"No X-ray snapshot captured.\");\n return lines.join(\"\\n\");\n }\n\n lines.push(`query: ${snapshot.query}`);\n lines.push(`snapshot-id: ${snapshot.snapshotId}`);\n lines.push(`captured-at: ${formatCapturedAt(snapshot.capturedAt)}`);\n if (snapshot.sessionKey) lines.push(`session: ${snapshot.sessionKey}`);\n if (snapshot.namespace) lines.push(`namespace: ${snapshot.namespace}`);\n if (snapshot.traceId) lines.push(`trace-id: ${snapshot.traceId}`);\n lines.push(\n `budget: ${snapshot.budget.used} / ${snapshot.budget.chars} chars`,\n );\n\n lines.push(\"\");\n lines.push(\"--- filters ---\");\n if (snapshot.filters.length === 0) {\n lines.push(\"(no filter traces recorded)\");\n } else {\n for (const f of snapshot.filters) {\n lines.push(renderFilterTextLine(f));\n }\n }\n\n lines.push(\"\");\n lines.push(\"--- results ---\");\n if (snapshot.results.length === 0) {\n lines.push(\"(no results admitted)\");\n } else {\n snapshot.results.forEach((result, idx) => {\n for (const line of renderResultTextLines(result, idx + 1)) {\n lines.push(line);\n }\n });\n }\n\n lines.push(\"\");\n lines.push(\"--- tier explain ---\");\n for (const line of renderTierExplainTextLines(snapshot.tierExplain ?? null)) {\n lines.push(line);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction renderFilterTextLine(f: RecallFilterTrace): string {\n const base = `- ${f.name}: ${f.admitted}/${f.considered} admitted`;\n return f.reason ? `${base} (${f.reason})` : base;\n}\n\nfunction renderResultTextLines(\n result: RecallXrayResult,\n rank: number,\n): string[] {\n const lines: string[] = [];\n lines.push(`[${rank}] ${result.memoryId} — ${servedByLabel(result.servedBy)}`);\n if (result.path) lines.push(` path: ${result.path}`);\n if (result.stateView) lines.push(` state-view: ${result.stateView}`);\n lines.push(` score: ${renderScoreDecomposition(result)}`);\n if (result.provenance) {\n lines.push(\n ` provenance: ${summarizeRetrievedMemoryProvenance(result.provenance)}`,\n );\n lines.push(` retrieval-reason: ${result.provenance.retrievalReason}`);\n if (result.provenance.userContextScopes.length > 0) {\n lines.push(\n ` context-scopes: ${result.provenance.userContextScopes.join(\", \")}`,\n );\n }\n if (\n result.provenance.safety !== \"safe\" ||\n result.provenance.safetyReasons.length > 0\n ) {\n const reasonSuffix =\n result.provenance.safetyReasons.length > 0\n ? ` (${result.provenance.safetyReasons.join(\", \")})`\n : \"\";\n lines.push(` safety: ${result.provenance.safety}${reasonSuffix}`);\n }\n }\n if (result.sourceSpan) {\n const q = truncateForXray(result.sourceSpan.quote, 120);\n lines.push(\n ` source: \"${q}\" (observed ${result.sourceSpan.observedAt}) [${result.sourceSpan.provenance}]`,\n );\n }\n if (result.admittedBy.length > 0) {\n lines.push(` admitted-by: ${result.admittedBy.join(\", \")}`);\n }\n if (result.rejectedBy) {\n lines.push(` rejected-by: ${result.rejectedBy}`);\n }\n if (result.graphPath && result.graphPath.length > 0) {\n lines.push(` graph-path: ${result.graphPath.join(\" -> \")}`);\n // Issue #681 PR 3/3 — surface per-edge confidence inline so\n // operators can attribute floor-pruning / PageRank ranking\n // decisions to specific edges. Skipped when no confidences were\n // recorded (legacy snapshot or single-node path).\n if (\n result.graphEdgeConfidences &&\n result.graphEdgeConfidences.length > 0\n ) {\n lines.push(\n ` edge-confidences: ${result.graphEdgeConfidences\n .map((c) => c.toFixed(2))\n .join(\", \")}`,\n );\n }\n }\n if (result.pathPenaltyApplied !== undefined) {\n lines.push(` path-penalty: ${result.pathPenaltyApplied ? \"yes\" : \"no\"}`);\n }\n if (result.pathNodeIds && result.pathNodeIds.length > 0) {\n lines.push(` path-node-ids: ${result.pathNodeIds.join(\" -> \")}`);\n }\n if (result.auditEntryId) {\n lines.push(` audit-entry: ${result.auditEntryId}`);\n }\n return lines;\n}\n\n// ─── Markdown ─────────────────────────────────────────────────────────────\n\nexport function renderXrayMarkdown(\n snapshot: RecallXraySnapshot | null,\n): string {\n const lines: string[] = [\"# Recall X-ray\"];\n if (!snapshot) {\n lines.push(\"\");\n lines.push(\"_No X-ray snapshot captured._\");\n return lines.join(\"\\n\");\n }\n\n lines.push(\"\");\n lines.push(`**Query:** ${mdInlineCode(snapshot.query)}`);\n lines.push(\"\");\n lines.push(\"| Field | Value |\");\n lines.push(\"| --- | --- |\");\n lines.push(`| Snapshot ID | \\`${snapshot.snapshotId}\\` |`);\n lines.push(`| Captured at | ${formatCapturedAt(snapshot.capturedAt)} |`);\n if (snapshot.sessionKey) {\n lines.push(`| Session | \\`${snapshot.sessionKey}\\` |`);\n }\n if (snapshot.namespace) {\n lines.push(`| Namespace | \\`${snapshot.namespace}\\` |`);\n }\n if (snapshot.traceId) {\n lines.push(`| Trace ID | \\`${snapshot.traceId}\\` |`);\n }\n lines.push(\n `| Budget | ${snapshot.budget.used} / ${snapshot.budget.chars} chars |`,\n );\n\n lines.push(\"\");\n lines.push(\"## Filters\");\n if (snapshot.filters.length === 0) {\n lines.push(\"\");\n lines.push(\"_No filter traces recorded._\");\n } else {\n lines.push(\"\");\n lines.push(\"| Filter | Considered | Admitted | Reason |\");\n lines.push(\"| --- | ---: | ---: | --- |\");\n for (const f of snapshot.filters) {\n const reason = f.reason ? mdEscape(f.reason) : \"\";\n lines.push(`| ${mdEscape(f.name)} | ${f.considered} | ${f.admitted} | ${reason} |`);\n }\n }\n\n lines.push(\"\");\n lines.push(\"## Results\");\n if (snapshot.results.length === 0) {\n lines.push(\"\");\n lines.push(\"_No results admitted._\");\n } else {\n snapshot.results.forEach((result, idx) => {\n for (const line of renderResultMarkdownLines(result, idx + 1)) {\n lines.push(line);\n }\n });\n\n // Per-disclosure token-spend summary (issue #677 PR 3/4). Only\n // emitted when at least one result carries a disclosure level so\n // we don't pollute the snapshot for callers who haven't wired the\n // depth knob through. Counts and tokens default to 0 for buckets\n // with no contributions.\n const summary = summarizeDisclosureTokens(snapshot.results);\n const hasAnyDisclosure =\n summary.chunk.count + summary.section.count + summary.raw.count > 0;\n if (hasAnyDisclosure) {\n lines.push(\"\");\n lines.push(\"### Token spend by disclosure\");\n lines.push(\"\");\n lines.push(\"| Disclosure | Results | Estimated tokens |\");\n lines.push(\"| --- | ---: | ---: |\");\n lines.push(\n `| chunk | ${summary.chunk.count} | ${summary.chunk.estimatedTokens} |`,\n );\n lines.push(\n `| section | ${summary.section.count} | ${summary.section.estimatedTokens} |`,\n );\n lines.push(\n `| raw | ${summary.raw.count} | ${summary.raw.estimatedTokens} |`,\n );\n if (summary.unspecified.count > 0) {\n lines.push(\n `| _(unspecified)_ | ${summary.unspecified.count} | ${summary.unspecified.estimatedTokens} |`,\n );\n }\n }\n }\n\n lines.push(\"\");\n lines.push(\"## Tier Explain\");\n if (!snapshot.tierExplain) {\n lines.push(\"\");\n lines.push(\n \"_Not populated — direct-answer tier disabled or did not fire._\",\n );\n } else {\n const te = snapshot.tierExplain;\n lines.push(\"\");\n lines.push(\"| Field | Value |\");\n lines.push(\"| --- | --- |\");\n lines.push(`| Tier | \\`${te.tier}\\` |`);\n lines.push(`| Reason | ${mdEscape(te.tierReason)} |`);\n lines.push(`| Candidates considered | ${te.candidatesConsidered} |`);\n lines.push(`| Latency (ms) | ${te.latencyMs} |`);\n lines.push(\n `| Filtered by | ${\n te.filteredBy.length > 0\n ? te.filteredBy.map(mdInlineCode).join(\", \")\n : \"_(none)_\"\n } |`,\n );\n if (te.sourceAnchors && te.sourceAnchors.length > 0) {\n lines.push(\"\");\n lines.push(\"**Source anchors:**\");\n for (const anchor of te.sourceAnchors) {\n const range = anchor.lineRange\n ? `:${anchor.lineRange[0]}-${anchor.lineRange[1]}`\n : \"\";\n lines.push(`- \\`${anchor.path}${range}\\``);\n }\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction renderResultMarkdownLines(\n result: RecallXrayResult,\n rank: number,\n): string[] {\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(\n `### ${rank}. \\`${result.memoryId}\\` — ${servedByLabel(result.servedBy)}`,\n );\n if (result.path) {\n lines.push(\"\");\n lines.push(`- **Path:** \\`${result.path}\\``);\n } else {\n lines.push(\"\");\n }\n lines.push(`- **Score:** ${renderScoreDecomposition(result)}`);\n if (result.provenance) {\n lines.push(\n `- **Provenance:** ${mdEscape(summarizeRetrievedMemoryProvenance(result.provenance))}`,\n );\n lines.push(\n `- **Retrieval reason:** ${mdInlineCode(result.provenance.retrievalReason)}`,\n );\n if (result.provenance.userContextScopes.length > 0) {\n lines.push(\n `- **Context scopes:** ${result.provenance.userContextScopes\n .map(mdInlineCode)\n .join(\", \")}`,\n );\n }\n if (\n result.provenance.safety !== \"safe\" ||\n result.provenance.safetyReasons.length > 0\n ) {\n const reasonSuffix =\n result.provenance.safetyReasons.length > 0\n ? ` (${result.provenance.safetyReasons.map(mdInlineCode).join(\", \")})`\n : \"\";\n lines.push(\n `- **Safety:** ${mdInlineCode(result.provenance.safety)}${reasonSuffix}`,\n );\n }\n }\n if (result.sourceSpan) {\n const q = truncateForXray(result.sourceSpan.quote, 120);\n lines.push(\n `- **Source:** \"${mdEscape(q)}\" (${mdInlineCode(result.sourceSpan.observedAt)}) \\u2014 ${result.sourceSpan.provenance}`,\n );\n }\n if (result.admittedBy.length > 0) {\n lines.push(\n `- **Admitted by:** ${result.admittedBy.map(mdInlineCode).join(\", \")}`,\n );\n }\n if (result.stateView) {\n lines.push(`- **State view:** ${mdInlineCode(result.stateView)}`);\n }\n if (result.rejectedBy) {\n lines.push(`- **Rejected by:** ${mdInlineCode(result.rejectedBy)}`);\n }\n if (result.graphPath && result.graphPath.length > 0) {\n lines.push(\n `- **Graph path:** ${result.graphPath\n .map(mdInlineCode)\n .join(\" → \")}`,\n );\n // Issue #681 PR 3/3 — render per-edge confidences as a parallel\n // markdown line so operators can correlate them with the path\n // arrows above. Skipped when no confidences were recorded.\n if (\n result.graphEdgeConfidences &&\n result.graphEdgeConfidences.length > 0\n ) {\n lines.push(\n `- **Edge confidences:** ${result.graphEdgeConfidences\n .map((c) => mdInlineCode(c.toFixed(2)))\n .join(\", \")}`,\n );\n }\n }\n if (result.pathPenaltyApplied !== undefined) {\n lines.push(\n `- **Path penalty applied:** ${mdInlineCode(result.pathPenaltyApplied ? \"yes\" : \"no\")}`,\n );\n }\n if (result.pathNodeIds && result.pathNodeIds.length > 0) {\n lines.push(\n `- **Path node ids:** ${result.pathNodeIds.map(mdInlineCode).join(\" → \")}`,\n );\n }\n if (result.auditEntryId) {\n lines.push(`- **Audit entry:** \\`${result.auditEntryId}\\``);\n }\n if (result.disclosure !== undefined) {\n const tokenLine =\n typeof result.estimatedTokens === \"number\"\n ? ` (~${result.estimatedTokens} tokens)`\n : \"\";\n lines.push(`- **Disclosure:** \\`${result.disclosure}\\`${tokenLine}`);\n } else if (typeof result.estimatedTokens === \"number\") {\n // Disclosure unspecified but tokens recorded — still surface the\n // budget so the operator can attribute spend.\n lines.push(`- **Estimated tokens:** ${result.estimatedTokens}`);\n }\n return lines;\n}\n\n// ─── Shared helpers ───────────────────────────────────────────────────────\n\nfunction servedByLabel(servedBy: RecallXrayServedBy): string {\n return `served-by=${servedBy}`;\n}\n\nfunction renderScoreDecomposition(result: RecallXrayResult): string {\n const parts: string[] = [`final=${formatScore(result.scoreDecomposition.final)}`];\n const s = result.scoreDecomposition;\n if (s.vector !== undefined) parts.push(`vector=${formatScore(s.vector)}`);\n if (s.bm25 !== undefined) parts.push(`bm25=${formatScore(s.bm25)}`);\n if (s.importance !== undefined) {\n parts.push(`importance=${formatScore(s.importance)}`);\n }\n if (s.mmrPenalty !== undefined) {\n parts.push(`mmr_penalty=${formatScore(s.mmrPenalty)}`);\n }\n if (s.tierPrior !== undefined) {\n parts.push(`tier_prior=${formatScore(s.tierPrior)}`);\n }\n if (s.reinforcementBoost !== undefined && s.reinforcementBoost > 0) {\n parts.push(`reinforcement_boost=${formatScore(s.reinforcementBoost)}`);\n }\n return parts.join(\" \");\n}\n\nfunction formatScore(value: number): string {\n // Deterministic 4-decimal formatting keeps golden files stable\n // without printing spurious trailing zeros via toString().\n if (!Number.isFinite(value)) return \"0.0000\";\n return value.toFixed(4);\n}\n\nfunction formatCapturedAt(ts: number): string {\n if (!Number.isFinite(ts) || ts < 0) return \"(unknown)\";\n // `new Date(n).toISOString()` throws a RangeError for finite numbers\n // outside the valid Date range (roughly |n| > 8.64e15). That case\n // can surface when snapshots are corrupted or captured with a\n // custom clock, so coerce it to the same \"(unknown)\" fallback\n // rather than crashing the renderer.\n try {\n return new Date(ts).toISOString();\n } catch {\n return \"(unknown)\";\n }\n}\n\nfunction mdEscape(value: string): string {\n // Pipe is the only character that breaks GFM table rendering; escape\n // backslash first so we do not re-escape the escape character.\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\\|/g, \"\\\\|\");\n}\n\n/**\n * Truncate a quote for X-ray display (issue #1575 PR 3). Collapses internal\n * whitespace runs (including newlines/carriage returns) to single spaces so\n * a multi-line source excerpt cannot split what is meant to be a single X-ray\n * row, then caps at `maxChars` on a word boundary with an ellipsis marker.\n * The JSON \\`sourceSpan.quote\\` stays verbatim — this is display-only.\n */\nfunction truncateForXray(text: string, maxChars: number): string {\n const collapsed = text.replace(/\\s+/g, \" \").trim();\n if (collapsed.length <= maxChars) return collapsed;\n const slice = collapsed.slice(0, maxChars);\n const lastSpace = slice.lastIndexOf(\" \");\n const cut = lastSpace > Math.floor(maxChars * 0.5) ? slice.slice(0, lastSpace) : slice;\n return `${cut}\\u2026`;\n}\n\nfunction mdInlineCode(value: string): string {\n if (value.length === 0) return \"``\";\n // Use exactly enough backticks to unambiguously wrap content that\n // itself contains backticks (GFM rule).\n const longestRun = /`+/g;\n let maxLen = 0;\n for (const match of value.matchAll(longestRun)) {\n if (match[0].length > maxLen) maxLen = match[0].length;\n }\n const fence = \"`\".repeat(maxLen + 1);\n const pad = value.startsWith(\"`\") || value.endsWith(\"`\") ? \" \" : \"\";\n return `${fence}${pad}${value}${pad}${fence}`;\n}\n"],"mappings":"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","names":[]}
|