@remnic/core 9.3.701 → 9.3.702
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-boundary.d.ts +7 -7
- package/dist/access-boundary.js +15 -12
- package/dist/access-cli.js +36 -33
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +7 -7
- package/dist/access-http.js +18 -15
- package/dist/access-mcp.d.ts +7 -7
- package/dist/access-mcp.js +17 -14
- package/dist/access-operations.d.ts +7 -7
- package/dist/access-operations.js +16 -13
- package/dist/{access-service-CGVWK6lZ.d.ts → access-service-COCzhgEL.d.ts} +397 -19
- package/dist/access-service.d.ts +6 -6
- package/dist/access-service.js +14 -11
- package/dist/access-surface-catalog.d.ts +7 -7
- package/dist/action-confidence.d.ts +1 -1
- 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 +7 -6
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +5 -5
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +6 -5
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-DBIghceA.d.ts → catalog-DxCzhjE6.d.ts} +2 -2
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +7 -6
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-YXLT4EMM.js → chunk-4463LUIE.js} +12 -2
- package/dist/chunk-4463LUIE.js.map +1 -0
- package/dist/{chunk-R5DB26G6.js → chunk-4SBW47WK.js} +9 -23
- package/dist/chunk-4SBW47WK.js.map +1 -0
- package/dist/{chunk-KF4TXW7Z.js → chunk-5VXVXJH6.js} +149 -6
- package/dist/{chunk-KF4TXW7Z.js.map → chunk-5VXVXJH6.js.map} +1 -1
- package/dist/{chunk-ODTWHSY2.js → chunk-64XXOQRW.js} +2 -2
- package/dist/{chunk-ZT7B64BE.js → chunk-6TAETM63.js} +2 -2
- package/dist/{chunk-HF4N43Q7.js → chunk-6VVP6NK7.js} +2 -2
- package/dist/{chunk-FN2SM5SN.js → chunk-ABCGDW3A.js} +75 -12
- package/dist/chunk-ABCGDW3A.js.map +1 -0
- package/dist/{chunk-7TAQEPLE.js → chunk-AMNLZ6SF.js} +2 -2
- package/dist/{chunk-KS7WQ4BZ.js → chunk-CZJ6QSKG.js} +3 -3
- package/dist/{chunk-27LQPUMZ.js → chunk-EOOVJK2U.js} +3 -3
- package/dist/{chunk-QP37KL5H.js → chunk-EYJD6KIO.js} +2 -2
- package/dist/{chunk-WFEZUGU5.js → chunk-EZR35XHX.js} +2 -2
- package/dist/{chunk-UU6MVCJ6.js → chunk-FIOYURII.js} +32 -25
- package/dist/chunk-FIOYURII.js.map +1 -0
- package/dist/{chunk-JO3E5VGS.js → chunk-FVI5B7DE.js} +2 -2
- package/dist/{chunk-SFMRLXIV.js → chunk-GFIARMA7.js} +15 -7
- package/dist/chunk-GFIARMA7.js.map +1 -0
- package/dist/{chunk-PONNZ54D.js → chunk-GY3SKOS4.js} +4 -4
- package/dist/{chunk-U33LWTQQ.js → chunk-HV57RHMD.js} +4 -4
- package/dist/{chunk-IKNQAGBV.js → chunk-IX3UQT4H.js} +1 -1
- package/dist/{chunk-IKNQAGBV.js.map → chunk-IX3UQT4H.js.map} +1 -1
- package/dist/{chunk-IYOPIG3E.js → chunk-IX72AAMZ.js} +2 -2
- package/dist/{chunk-JKOKX3PS.js → chunk-JMA4RYRN.js} +2 -2
- package/dist/{chunk-DEDQXIDL.js → chunk-JTKFZMZ7.js} +2 -2
- package/dist/{chunk-OLOYQZFB.js → chunk-KC6TCAWV.js} +5 -5
- package/dist/{chunk-ROZJACKP.js → chunk-KKK7YTYN.js} +4 -1
- package/dist/chunk-KKK7YTYN.js.map +1 -0
- package/dist/{chunk-EC2AYKRX.js → chunk-L6W77GWW.js} +10 -24
- package/dist/chunk-L6W77GWW.js.map +1 -0
- package/dist/{chunk-ED35D32I.js → chunk-LQ4J7ELC.js} +2 -2
- package/dist/chunk-LUPVCGYK.js +63 -0
- package/dist/chunk-LUPVCGYK.js.map +1 -0
- package/dist/{chunk-TFVVONWD.js → chunk-MBUM2Y3L.js} +2 -2
- package/dist/{chunk-T5QAZIBO.js → chunk-MOXFPLD6.js} +3 -3
- package/dist/{chunk-XTIRCSIH.js → chunk-MXEWQKM7.js} +2 -2
- package/dist/{chunk-ZYNMX6IU.js → chunk-NUIJEGVD.js} +6 -6
- package/dist/{chunk-FUCJAZ25.js → chunk-OXEAMU42.js} +529 -9
- package/dist/chunk-OXEAMU42.js.map +1 -0
- package/dist/{chunk-YO4MBK3I.js → chunk-QGJAGC2J.js} +2 -2
- package/dist/{chunk-ISLJ5WIM.js → chunk-QIMFOCSH.js} +2 -2
- package/dist/{chunk-IJEZMWKA.js → chunk-SFOAQQDJ.js} +3 -3
- package/dist/{chunk-HRUULBBV.js → chunk-SHRRWOVY.js} +81 -4
- package/dist/chunk-SHRRWOVY.js.map +1 -0
- package/dist/{chunk-NINRTFSV.js → chunk-SINGJCUR.js} +5 -5
- package/dist/{chunk-ZPQVJEVQ.js → chunk-SK2CR6MW.js} +146 -2
- package/dist/chunk-SK2CR6MW.js.map +1 -0
- package/dist/{chunk-6W2D6FGG.js → chunk-TGAHHCB6.js} +2 -2
- package/dist/{chunk-D75JXBV4.js → chunk-WN4GHSDH.js} +2 -2
- package/dist/{chunk-K4DWSPMW.js → chunk-WRGPE6AW.js} +2 -2
- package/dist/{chunk-4HIAWLA2.js → chunk-XGMCUY5P.js} +60 -114
- package/dist/chunk-XGMCUY5P.js.map +1 -0
- package/dist/{chunk-SDPDU2PM.js → chunk-YTMDF6S7.js} +2 -2
- package/dist/{chunk-MNU5G4TK.js → chunk-Z7XEIAV4.js} +2 -2
- package/dist/{chunk-HXHKLVAS.js → chunk-ZCEI242W.js} +23 -23
- package/dist/{cli-D3XeenwN.d.ts → cli-BM4xQPp4.d.ts} +3 -3
- package/dist/cli.d.ts +7 -7
- package/dist/cli.js +33 -32
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +6 -5
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +6 -5
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -6
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +6 -5
- package/dist/entity-schema.d.ts +1 -1
- package/dist/event-order-recall.js +2 -1
- package/dist/explicit-capture.d.ts +5 -5
- package/dist/explicit-capture.js +2 -2
- package/dist/explicit-cue-recall.js +2 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +14 -1
- package/dist/extraction.js +5 -3
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +123 -123
- package/dist/index.js +53 -52
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +6 -5
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +6 -5
- package/dist/maintenance/rebuild-memory-projection.js +7 -6
- package/dist/mcp-memory-inspector-app.d.ts +7 -7
- 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 +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +7 -6
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +6 -5
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +12 -11
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +8 -7
- package/dist/{orchestrator-BzMCZlKn.d.ts → orchestrator-C9CDWAm6.d.ts} +4 -4
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +29 -27
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +36 -2
- package/dist/provenance.js +5 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-pipeline-stages.d.ts +15 -0
- package/dist/recall-pipeline-stages.js +3 -56
- package/dist/recall-pipeline-stages.js.map +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/response-guidance-recall.js +2 -1
- package/dist/resume-bundles.js +7 -5
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +19 -0
- package/dist/schemas.js +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-DJR8_DMQ.d.ts → semantic-SLAa_prH.d.ts} +1 -1
- package/dist/{semantic-consolidation-BtUfv-AL.d.ts → semantic-consolidation-DgFXyALl.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +7 -6
- package/dist/semantic-rule-promotion.js +6 -5
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +6 -5
- 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 +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +13 -1
- package/dist/storage.js +5 -4
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/targeted-fact-recall.js +2 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transcript.js +2 -2
- package/dist/{types-PuiPZ9iE.d.ts → types-MWKPZnM0.d.ts} +25 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +6 -5
- package/package.json +2 -2
- package/src/access-http.ts +162 -0
- package/src/access-service.ts +215 -0
- package/src/admin/admin-surfaces.test.ts +458 -0
- package/src/admin/admin-surfaces.ts +819 -0
- package/src/event-order-recall.test.ts +53 -0
- package/src/event-order-recall.ts +61 -37
- package/src/explicit-cue-recall.ts +21 -1
- package/src/extraction.ts +91 -6
- package/src/orchestrator.ts +46 -5
- package/src/provenance-extraction.test.ts +838 -0
- package/src/provenance.ts +413 -0
- package/src/recall-pipeline-parity.test.ts +288 -0
- package/src/recall-pipeline-stages.test.ts +43 -0
- package/src/recall-pipeline-stages.ts +22 -0
- package/src/response-guidance-recall.ts +32 -36
- package/src/schemas.ts +7 -0
- package/src/storage.ts +23 -0
- package/src/targeted-fact-recall.ts +25 -32
- package/src/types.ts +24 -0
- package/dist/chunk-4HIAWLA2.js.map +0 -1
- package/dist/chunk-EC2AYKRX.js.map +0 -1
- package/dist/chunk-FN2SM5SN.js.map +0 -1
- package/dist/chunk-FUCJAZ25.js.map +0 -1
- package/dist/chunk-HRUULBBV.js.map +0 -1
- package/dist/chunk-R5DB26G6.js.map +0 -1
- package/dist/chunk-ROZJACKP.js.map +0 -1
- package/dist/chunk-SFMRLXIV.js.map +0 -1
- package/dist/chunk-UU6MVCJ6.js.map +0 -1
- package/dist/chunk-YXLT4EMM.js.map +0 -1
- package/dist/chunk-ZPQVJEVQ.js.map +0 -1
- /package/dist/{chunk-ODTWHSY2.js.map → chunk-64XXOQRW.js.map} +0 -0
- /package/dist/{chunk-ZT7B64BE.js.map → chunk-6TAETM63.js.map} +0 -0
- /package/dist/{chunk-HF4N43Q7.js.map → chunk-6VVP6NK7.js.map} +0 -0
- /package/dist/{chunk-7TAQEPLE.js.map → chunk-AMNLZ6SF.js.map} +0 -0
- /package/dist/{chunk-KS7WQ4BZ.js.map → chunk-CZJ6QSKG.js.map} +0 -0
- /package/dist/{chunk-27LQPUMZ.js.map → chunk-EOOVJK2U.js.map} +0 -0
- /package/dist/{chunk-QP37KL5H.js.map → chunk-EYJD6KIO.js.map} +0 -0
- /package/dist/{chunk-WFEZUGU5.js.map → chunk-EZR35XHX.js.map} +0 -0
- /package/dist/{chunk-JO3E5VGS.js.map → chunk-FVI5B7DE.js.map} +0 -0
- /package/dist/{chunk-PONNZ54D.js.map → chunk-GY3SKOS4.js.map} +0 -0
- /package/dist/{chunk-U33LWTQQ.js.map → chunk-HV57RHMD.js.map} +0 -0
- /package/dist/{chunk-IYOPIG3E.js.map → chunk-IX72AAMZ.js.map} +0 -0
- /package/dist/{chunk-JKOKX3PS.js.map → chunk-JMA4RYRN.js.map} +0 -0
- /package/dist/{chunk-DEDQXIDL.js.map → chunk-JTKFZMZ7.js.map} +0 -0
- /package/dist/{chunk-OLOYQZFB.js.map → chunk-KC6TCAWV.js.map} +0 -0
- /package/dist/{chunk-ED35D32I.js.map → chunk-LQ4J7ELC.js.map} +0 -0
- /package/dist/{chunk-TFVVONWD.js.map → chunk-MBUM2Y3L.js.map} +0 -0
- /package/dist/{chunk-T5QAZIBO.js.map → chunk-MOXFPLD6.js.map} +0 -0
- /package/dist/{chunk-XTIRCSIH.js.map → chunk-MXEWQKM7.js.map} +0 -0
- /package/dist/{chunk-ZYNMX6IU.js.map → chunk-NUIJEGVD.js.map} +0 -0
- /package/dist/{chunk-YO4MBK3I.js.map → chunk-QGJAGC2J.js.map} +0 -0
- /package/dist/{chunk-ISLJ5WIM.js.map → chunk-QIMFOCSH.js.map} +0 -0
- /package/dist/{chunk-IJEZMWKA.js.map → chunk-SFOAQQDJ.js.map} +0 -0
- /package/dist/{chunk-NINRTFSV.js.map → chunk-SINGJCUR.js.map} +0 -0
- /package/dist/{chunk-6W2D6FGG.js.map → chunk-TGAHHCB6.js.map} +0 -0
- /package/dist/{chunk-D75JXBV4.js.map → chunk-WN4GHSDH.js.map} +0 -0
- /package/dist/{chunk-K4DWSPMW.js.map → chunk-WRGPE6AW.js.map} +0 -0
- /package/dist/{chunk-SDPDU2PM.js.map → chunk-YTMDF6S7.js.map} +0 -0
- /package/dist/{chunk-MNU5G4TK.js.map → chunk-Z7XEIAV4.js.map} +0 -0
- /package/dist/{chunk-HXHKLVAS.js.map → chunk-ZCEI242W.js.map} +0 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// Issue #1539 PRs 3–6 — migration parity guards.
|
|
2
|
+
//
|
|
3
|
+
// Each recall pipeline (targeted-fact, response-guidance, explicit-cue,
|
|
4
|
+
// event-order) was migrated from an inline dedup/score/threshold/sort block to
|
|
5
|
+
// a thin config object handed to `unifiedDedupeAndRank` (the spine). The
|
|
6
|
+
// per-tier test files (targeted-fact-recall.test.ts etc.) are the end-to-end
|
|
7
|
+
// characterization — they exercise buildXxxRecallSection on full fixtures. This
|
|
8
|
+
// file is the SEAM guard: it pins, per pipeline, that the declared config shape
|
|
9
|
+
// routes through the spine with the correct divergence (sort direction,
|
|
10
|
+
// content-dedup on/off, rank threshold, transform-applied-to-output-but-not-
|
|
11
|
+
// score). If a future refactor flips one of these silently, the matching test
|
|
12
|
+
// here fails BEFORE the end-to-end suites have a chance to mask it.
|
|
13
|
+
//
|
|
14
|
+
// These tests do NOT re-implement the per-tier scorers — those are genuinely
|
|
15
|
+
// per-tier policy and stay in the per-tier test files. Here we use small
|
|
16
|
+
// representative scorers that exercise the divergence dimension each pipeline
|
|
17
|
+
// declares on the spine.
|
|
18
|
+
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
import test from "node:test";
|
|
21
|
+
|
|
22
|
+
import type { EvidencePackItem } from "./evidence-pack.js";
|
|
23
|
+
import { unifiedDedupeAndRank } from "./recall-pipeline-stages.js";
|
|
24
|
+
|
|
25
|
+
function item(
|
|
26
|
+
turnIndex: number,
|
|
27
|
+
content: string,
|
|
28
|
+
overrides: Partial<EvidencePackItem> = {},
|
|
29
|
+
): EvidencePackItem {
|
|
30
|
+
return {
|
|
31
|
+
id: `s1:${turnIndex}`,
|
|
32
|
+
sessionId: "s1",
|
|
33
|
+
turnIndex,
|
|
34
|
+
role: "user",
|
|
35
|
+
content,
|
|
36
|
+
...overrides,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// PR3 — targeted-fact shape: DESC, content-dedup, transform-applied-to-output,
|
|
42
|
+
// score-on-original. No rank threshold.
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
test("parity targeted-fact: DESC ordering on score ties (latest turnIndex first)", () => {
|
|
46
|
+
const items = [
|
|
47
|
+
item(1, "early turn"),
|
|
48
|
+
item(5, "late turn"),
|
|
49
|
+
item(3, "middle turn"),
|
|
50
|
+
];
|
|
51
|
+
// Constant scorer forces score ties so turnIndex DESC is the deciding key.
|
|
52
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
53
|
+
query: "",
|
|
54
|
+
intents: [],
|
|
55
|
+
scoreEvidence: () => 10,
|
|
56
|
+
});
|
|
57
|
+
assert.equal(ranked[0].turnIndex, 5);
|
|
58
|
+
assert.equal(ranked[1].turnIndex, 3);
|
|
59
|
+
assert.equal(ranked[2].turnIndex, 1);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("parity targeted-fact: content transform applied to output, NOT to scorer input", () => {
|
|
63
|
+
const items = [item(2, "Revenue was $500")];
|
|
64
|
+
// A scorer that reads ORIGINAL content must NOT see the appended cue suffix.
|
|
65
|
+
let scorerObserved = "";
|
|
66
|
+
unifiedDedupeAndRank(items, {
|
|
67
|
+
query: "",
|
|
68
|
+
intents: [],
|
|
69
|
+
scoreEvidence: (original) => {
|
|
70
|
+
scorerObserved = original.content;
|
|
71
|
+
return 1;
|
|
72
|
+
},
|
|
73
|
+
transformContent: (content) => `${content} [CUE]`,
|
|
74
|
+
});
|
|
75
|
+
assert.equal(scorerObserved, "Revenue was $500");
|
|
76
|
+
// ...and the output content carries the transform:
|
|
77
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
78
|
+
query: "",
|
|
79
|
+
intents: [],
|
|
80
|
+
scoreEvidence: () => 1,
|
|
81
|
+
transformContent: (content) => `${content} [CUE]`,
|
|
82
|
+
});
|
|
83
|
+
assert.equal(ranked[0].content, "Revenue was $500 [CUE]");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("parity targeted-fact: duplicate normalized content collapses (default dedupByContent)", () => {
|
|
87
|
+
const items = [
|
|
88
|
+
item(1, "Same content"),
|
|
89
|
+
item(2, "same content"), // whitespace/case differences normalize equal
|
|
90
|
+
item(3, "Distinct"),
|
|
91
|
+
];
|
|
92
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
93
|
+
query: "",
|
|
94
|
+
intents: [],
|
|
95
|
+
scoreEvidence: () => 10,
|
|
96
|
+
});
|
|
97
|
+
assert.equal(ranked.length, 2);
|
|
98
|
+
// First-seen wins: turnIndex 1 is kept, turnIndex 2 (same normalized content)
|
|
99
|
+
// is dropped. Output is then sorted DESC by turnIndex on the score tie.
|
|
100
|
+
assert.deepEqual(
|
|
101
|
+
ranked.map((r) => r.turnIndex),
|
|
102
|
+
[3, 1],
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// PR4 — response-guidance shape: same as targeted-fact (DESC + content-dedup +
|
|
108
|
+
// transform + score-on-original) but with guidance-cue transform. Pin the shape
|
|
109
|
+
// matches; the guidance-specific transform is exercised end-to-end in
|
|
110
|
+
// response-guidance-recall.test.ts.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
test("parity response-guidance: DESC ordering on score ties", () => {
|
|
114
|
+
const items = [
|
|
115
|
+
item(2, "guidance b"),
|
|
116
|
+
item(7, "guidance a"),
|
|
117
|
+
];
|
|
118
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
119
|
+
query: "",
|
|
120
|
+
intents: [],
|
|
121
|
+
scoreEvidence: () => 5,
|
|
122
|
+
});
|
|
123
|
+
assert.equal(ranked[0].turnIndex, 7);
|
|
124
|
+
assert.equal(ranked[1].turnIndex, 2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("parity response-guidance: rank primary key always DESC regardless of turnIndex", () => {
|
|
128
|
+
const items = [
|
|
129
|
+
item(1, "low rank high turnIndex"),
|
|
130
|
+
item(9, "high rank low turnIndex"),
|
|
131
|
+
];
|
|
132
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
133
|
+
query: "",
|
|
134
|
+
intents: [],
|
|
135
|
+
scoreEvidence: (original) => (original.content.includes("high rank") ? 20 : 1),
|
|
136
|
+
});
|
|
137
|
+
assert.equal(ranked[0].turnIndex, 9);
|
|
138
|
+
assert.equal(ranked[1].turnIndex, 1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// PR5 — explicit-cue shape: the pipeline that OPTS OUT of scoring AND sorting.
|
|
143
|
+
// Its deliberate value ordering (turn references -> content cues -> lexical
|
|
144
|
+
// cues, by evidence type then read-key priority) is the highest-value design
|
|
145
|
+
// choice — preserved verbatim via preserveInsertionOrder + constant scorer +
|
|
146
|
+
// id-only dedup (the spine call is behavior-idempotent; items are already
|
|
147
|
+
// deduped by seenTurns during collection).
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
test("parity explicit-cue: preserveInsertionOrder keeps collection order intact", () => {
|
|
151
|
+
// Insertion order is 2, 4, 1. A default sort would reorder to 4, 2, 1.
|
|
152
|
+
// explicit-cue must NOT reorder — the collection order IS the value order.
|
|
153
|
+
const items = [
|
|
154
|
+
item(2, "explicit cue b", { id: "s:2" }),
|
|
155
|
+
item(4, "explicit cue a", { id: "s:4" }),
|
|
156
|
+
item(1, "explicit cue c", { id: "s:1" }),
|
|
157
|
+
];
|
|
158
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
159
|
+
query: "",
|
|
160
|
+
intents: [],
|
|
161
|
+
scoreEvidence: () => 0,
|
|
162
|
+
dedupByContent: false,
|
|
163
|
+
preserveInsertionOrder: true,
|
|
164
|
+
});
|
|
165
|
+
assert.deepEqual(
|
|
166
|
+
ranked.map((r) => r.turnIndex),
|
|
167
|
+
[2, 4, 1],
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("parity explicit-cue: id-only dedup (dedupByContent false) keeps distinct turns", () => {
|
|
172
|
+
const items = [
|
|
173
|
+
item(1, "same body", { id: "s:1" }),
|
|
174
|
+
item(2, "same body", { id: "s:2" }),
|
|
175
|
+
];
|
|
176
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
177
|
+
query: "",
|
|
178
|
+
intents: [],
|
|
179
|
+
scoreEvidence: () => 0,
|
|
180
|
+
dedupByContent: false,
|
|
181
|
+
preserveInsertionOrder: true,
|
|
182
|
+
});
|
|
183
|
+
assert.equal(ranked.length, 2);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// PR6 — event-order shape: ASC ordering, NO content-dedup (dedupByContent:false
|
|
188
|
+
// keeps distinct turns even when cue-appended bodies match), declared
|
|
189
|
+
// rankThreshold of 6, transform applied to output.
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
test("parity event-order: ASC ordering on rank ties (earliest turnIndex first)", () => {
|
|
193
|
+
const items = [
|
|
194
|
+
item(5, "turn five"),
|
|
195
|
+
item(1, "turn one"),
|
|
196
|
+
item(3, "turn three"),
|
|
197
|
+
];
|
|
198
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
199
|
+
query: "",
|
|
200
|
+
intents: [],
|
|
201
|
+
scoreEvidence: () => 10,
|
|
202
|
+
turnIndexSortDirection: "asc",
|
|
203
|
+
});
|
|
204
|
+
assert.deepEqual(
|
|
205
|
+
ranked.map((r) => r.turnIndex),
|
|
206
|
+
[1, 3, 5],
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("parity event-order: dedupByContent false keeps distinct turns with identical bodies", () => {
|
|
211
|
+
// Two distinct turns (different ids/turnIndex) but identical cue-appended body.
|
|
212
|
+
// Event-order MUST keep both — they are legitimate repeated turns in a
|
|
213
|
+
// chronological transcript. This is the cursor-bugbot a4299851 regression guard.
|
|
214
|
+
const items = [
|
|
215
|
+
item(1, "the user asked again"),
|
|
216
|
+
item(4, "the user asked again"),
|
|
217
|
+
];
|
|
218
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
219
|
+
query: "",
|
|
220
|
+
intents: [],
|
|
221
|
+
scoreEvidence: () => 10,
|
|
222
|
+
dedupByContent: false,
|
|
223
|
+
turnIndexSortDirection: "asc",
|
|
224
|
+
});
|
|
225
|
+
assert.equal(ranked.length, 2);
|
|
226
|
+
assert.deepEqual(
|
|
227
|
+
ranked.map((r) => r.turnIndex),
|
|
228
|
+
[1, 4],
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("parity event-order: declared rankThreshold (6) drops below-threshold items", () => {
|
|
233
|
+
const items = [
|
|
234
|
+
item(1, "strong", { id: "s:1" }),
|
|
235
|
+
item(2, "weak", { id: "s:2" }),
|
|
236
|
+
item(3, "borderline", { id: "s:3" }),
|
|
237
|
+
];
|
|
238
|
+
const ranked = unifiedDedupeAndRank(items, {
|
|
239
|
+
query: "",
|
|
240
|
+
intents: [],
|
|
241
|
+
scoreEvidence: (original) => {
|
|
242
|
+
if (original.content === "strong") return 10;
|
|
243
|
+
if (original.content === "borderline") return 6; // exactly threshold -> kept (>=)
|
|
244
|
+
return 5; // weak -> dropped
|
|
245
|
+
},
|
|
246
|
+
rankThreshold: 6,
|
|
247
|
+
turnIndexSortDirection: "asc",
|
|
248
|
+
});
|
|
249
|
+
// rank is ALWAYS DESC primary: strong (10) outranks borderline (6).
|
|
250
|
+
// turnIndex ASC only breaks ties within the same rank.
|
|
251
|
+
assert.deepEqual(
|
|
252
|
+
ranked.map((r) => r.content),
|
|
253
|
+
["strong", "borderline"],
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Cross-pipeline divergence guard: the SAME corpus produces DIFFERENT orderings
|
|
259
|
+
// under DESC vs ASC config. This is the core #1539 defect class — copy-pasting
|
|
260
|
+
// the comparator into a chronological pipeline silently inverts ordering.
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
test("parity divergence: DESC vs ASC configs on the same corpus invert turnIndex ordering", () => {
|
|
264
|
+
const corpus = [
|
|
265
|
+
item(1, "a", { id: "x:1" }),
|
|
266
|
+
item(2, "b", { id: "x:2" }),
|
|
267
|
+
item(3, "c", { id: "x:3" }),
|
|
268
|
+
];
|
|
269
|
+
const desc = unifiedDedupeAndRank(corpus, {
|
|
270
|
+
query: "",
|
|
271
|
+
intents: [],
|
|
272
|
+
scoreEvidence: () => 10,
|
|
273
|
+
});
|
|
274
|
+
const asc = unifiedDedupeAndRank(corpus, {
|
|
275
|
+
query: "",
|
|
276
|
+
intents: [],
|
|
277
|
+
scoreEvidence: () => 10,
|
|
278
|
+
turnIndexSortDirection: "asc",
|
|
279
|
+
});
|
|
280
|
+
assert.deepEqual(
|
|
281
|
+
desc.map((r) => r.turnIndex),
|
|
282
|
+
[3, 2, 1],
|
|
283
|
+
);
|
|
284
|
+
assert.deepEqual(
|
|
285
|
+
asc.map((r) => r.turnIndex),
|
|
286
|
+
[1, 2, 3],
|
|
287
|
+
);
|
|
288
|
+
});
|
|
@@ -267,3 +267,46 @@ test("unifiedDedupeAndRank: dedupByContent true (default) still collapses identi
|
|
|
267
267
|
});
|
|
268
268
|
assert.equal(resultExplicit.length, 1, "explicit dedupByContent: true collapses identical content");
|
|
269
269
|
});
|
|
270
|
+
|
|
271
|
+
test("unifiedDedupeAndRank: preserveInsertionOrder skips the sort stage (explicit-cue shape)", () => {
|
|
272
|
+
// Insertion order is turn-index 3, 1, 2. A DESC sort would reorder to 3, 2, 1.
|
|
273
|
+
// preserveInsertionOrder must keep the original (post-dedup) order: 3, 1, 2.
|
|
274
|
+
const items: EvidencePackItem[] = [
|
|
275
|
+
item(3, "turn ref", { id: "s:3" }),
|
|
276
|
+
item(1, "content cue", { id: "s:1" }),
|
|
277
|
+
item(2, "lexical hit", { id: "s:2" }),
|
|
278
|
+
];
|
|
279
|
+
const result = unifiedDedupeAndRank(items, {
|
|
280
|
+
query: "q",
|
|
281
|
+
intents: NO_INTENTS,
|
|
282
|
+
scoreEvidence: () => 0, // explicit-cue does not score
|
|
283
|
+
dedupByContent: false, // explicit-cue dedupes by id only (seenTurns)
|
|
284
|
+
preserveInsertionOrder: true,
|
|
285
|
+
});
|
|
286
|
+
assert.deepEqual(
|
|
287
|
+
result.map((r) => r.id),
|
|
288
|
+
["s:3", "s:1", "s:2"],
|
|
289
|
+
"insertion order is preserved — no sort applied",
|
|
290
|
+
);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test("unifiedDedupeAndRank: preserveInsertionOrder still applies threshold filter", () => {
|
|
294
|
+
const items: EvidencePackItem[] = [
|
|
295
|
+
item(1, "strong", { id: "s:1" }),
|
|
296
|
+
item(2, "weak", { id: "s:2" }),
|
|
297
|
+
item(3, "medium", { id: "s:3" }),
|
|
298
|
+
];
|
|
299
|
+
const result = unifiedDedupeAndRank(items, {
|
|
300
|
+
query: "q",
|
|
301
|
+
intents: NO_INTENTS,
|
|
302
|
+
scoreEvidence: (original) => (original.content === "strong" ? 10 : original.content === "medium" ? 5 : 1),
|
|
303
|
+
rankThreshold: 5,
|
|
304
|
+
dedupByContent: false,
|
|
305
|
+
preserveInsertionOrder: true,
|
|
306
|
+
});
|
|
307
|
+
// threshold drops "weak" (rank 1); insertion order of survivors preserved.
|
|
308
|
+
assert.deepEqual(
|
|
309
|
+
result.map((r) => r.id),
|
|
310
|
+
["s:1", "s:3"],
|
|
311
|
+
);
|
|
312
|
+
});
|
|
@@ -125,6 +125,21 @@ export interface UnifiedRankConfig<TIntent> {
|
|
|
125
125
|
* The rank primary key is ALWAYS DESC regardless of this setting.
|
|
126
126
|
*/
|
|
127
127
|
turnIndexSortDirection?: TurnIndexSortDirection;
|
|
128
|
+
/**
|
|
129
|
+
* When `true`, skip the sort stage entirely and return items in their
|
|
130
|
+
* post-dedup insertion order (first-seen first). Used by explicit-cue,
|
|
131
|
+
* whose deliberate value ordering — turn references → content cues →
|
|
132
|
+
* lexical cues, gathered by evidence TYPE then read-key priority — is the
|
|
133
|
+
* pipeline's highest-value design choice. A rank-based sort would invert
|
|
134
|
+
* it (turn references are deliberately UNSCORED while lexical hits carry
|
|
135
|
+
* numeric scores; sorting by score would demote turn references below
|
|
136
|
+
* weak lexical hits — cursor[bot] / codex P2 on #1505).
|
|
137
|
+
*
|
|
138
|
+
* Score and threshold-filter still apply when declared; only the sort is
|
|
139
|
+
* skipped. The constant scorer (`scoreEvidence: () => 0`) makes the
|
|
140
|
+
* no-scoring policy explicit alongside this flag.
|
|
141
|
+
*/
|
|
142
|
+
preserveInsertionOrder?: boolean;
|
|
128
143
|
}
|
|
129
144
|
|
|
130
145
|
/**
|
|
@@ -232,6 +247,13 @@ export function unifiedDedupeAndRank<TIntent>(
|
|
|
232
247
|
// turnIndex direction is configurable: DESC (relevance) or ASC (chronology).
|
|
233
248
|
// The tertiary tiebreaker follows the direction:
|
|
234
249
|
// DESC → score DESC; ASC → content.localeCompare.
|
|
250
|
+
//
|
|
251
|
+
// explicit-cue opts out of sorting entirely (preserveInsertionOrder) — its
|
|
252
|
+
// deliberate value ordering is the collection order (turn references →
|
|
253
|
+
// content cues → lexical cues), and a rank-based sort would invert it.
|
|
254
|
+
if (config.preserveInsertionOrder) {
|
|
255
|
+
return filtered;
|
|
256
|
+
}
|
|
235
257
|
return filtered.sort(makeComparator(direction));
|
|
236
258
|
}
|
|
237
259
|
|
|
@@ -5,6 +5,11 @@ import {
|
|
|
5
5
|
resolveLcmReadSessionIds,
|
|
6
6
|
} from "./lcm-fallback-read.js";
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
unifiedDedupeAndRank,
|
|
10
|
+
type RankedEvidenceItem,
|
|
11
|
+
} from "./recall-pipeline-stages.js";
|
|
12
|
+
|
|
8
13
|
export interface ResponseGuidanceRecallOptions {
|
|
9
14
|
engine: ExplicitCueRecallEngine | null | undefined;
|
|
10
15
|
sessionId?: string;
|
|
@@ -24,10 +29,6 @@ export interface ResponseGuidanceRecallOptions {
|
|
|
24
29
|
forceGeneric?: boolean;
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
interface RankedGuidanceItem extends EvidencePackItem {
|
|
28
|
-
rank: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
32
|
type GuidanceIntent =
|
|
32
33
|
| "dates"
|
|
33
34
|
| "editing"
|
|
@@ -341,42 +342,37 @@ async function collectGuidanceScanItems(
|
|
|
341
342
|
return items;
|
|
342
343
|
}
|
|
343
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Issue #1539 PR4: response-guidance now routes dedup/score/threshold/sort through
|
|
347
|
+
* the unified spine (`unifiedDedupeAndRank`). The divergence that lived inline
|
|
348
|
+
* here — append-guidance-cues content transform (two-arg: content + intents),
|
|
349
|
+
* relevance scoring on ORIGINAL content, default DESC turn-index ordering with
|
|
350
|
+
* -1 sentinel for missing turns — is now declared config on the spine. The score
|
|
351
|
+
* and appendGuidanceCues helpers stay in this module because they are genuinely
|
|
352
|
+
* per-tier policy; only the duplicated dedup/sort/threshold machinery moved out.
|
|
353
|
+
*
|
|
354
|
+
* Behavior is byte-for-byte identical to the inline implementation it replaces
|
|
355
|
+
* (the spine was extracted to match it exactly; see recall-pipeline-stages.ts).
|
|
356
|
+
* The response-guidance-recall.test.ts suite is the characterization.
|
|
357
|
+
*
|
|
358
|
+
* Note: response-guidance (like the pre-migration code) dedups on the TRANSFORMED
|
|
359
|
+
* (cue-appended) content, not the original — this matches the spine's default
|
|
360
|
+
* (transform-then-dedup) exactly. No divergence from prior behavior.
|
|
361
|
+
*/
|
|
344
362
|
function rankAndDedupeGuidanceItems(
|
|
345
363
|
items: EvidencePackItem[],
|
|
346
364
|
query: string,
|
|
347
365
|
intents: readonly GuidanceIntent[],
|
|
348
|
-
):
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
);
|
|
359
|
-
if (id && seenIds.has(id)) continue;
|
|
360
|
-
|
|
361
|
-
const enhancedContent = appendGuidanceCues(item.content, intents);
|
|
362
|
-
const contentKey = enhancedContent.toLowerCase().replace(/\s+/g, " ").trim();
|
|
363
|
-
if (seenContent.has(contentKey)) continue;
|
|
364
|
-
|
|
365
|
-
if (id) seenIds.add(id);
|
|
366
|
-
seenContent.add(contentKey);
|
|
367
|
-
ranked.push({
|
|
368
|
-
...item,
|
|
369
|
-
content: enhancedContent,
|
|
370
|
-
rank: scoreGuidanceEvidence(item, query, intents),
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return ranked.sort((left, right) => {
|
|
375
|
-
if (right.rank !== left.rank) return right.rank - left.rank;
|
|
376
|
-
const leftTurn = typeof left.turnIndex === "number" ? left.turnIndex : -1;
|
|
377
|
-
const rightTurn = typeof right.turnIndex === "number" ? right.turnIndex : -1;
|
|
378
|
-
if (rightTurn !== leftTurn) return rightTurn - leftTurn;
|
|
379
|
-
return (right.score ?? 0) - (left.score ?? 0);
|
|
366
|
+
): RankedEvidenceItem[] {
|
|
367
|
+
// The spine's config.intents is typed TIntent[] (readability contract — it is
|
|
368
|
+
// never mutated). Cast the readonly array through; safe because the spine only
|
|
369
|
+
// forwards intents to the score/transform callbacks.
|
|
370
|
+
const mutableIntents = intents as GuidanceIntent[];
|
|
371
|
+
return unifiedDedupeAndRank(items, {
|
|
372
|
+
query,
|
|
373
|
+
intents: mutableIntents,
|
|
374
|
+
scoreEvidence: (item, q, intentArr) => scoreGuidanceEvidence(item, q, intentArr),
|
|
375
|
+
transformContent: (content, intentArr) => appendGuidanceCues(content, intentArr),
|
|
380
376
|
});
|
|
381
377
|
}
|
|
382
378
|
|
package/src/schemas.ts
CHANGED
|
@@ -93,6 +93,13 @@ export const ExtractedFactSchema = z.object({
|
|
|
93
93
|
.describe(
|
|
94
94
|
'Scope classification: "global" for cross-project knowledge (framework bugs, library behavior, API patterns, user preferences, tool configs, general coding patterns); "project" for project-specific knowledge (file paths, env configs, deployment details, project workarounds). Defaults to "project" when a coding context is active.',
|
|
95
95
|
),
|
|
96
|
+
quote: z
|
|
97
|
+
.string()
|
|
98
|
+
.optional()
|
|
99
|
+
.nullable()
|
|
100
|
+
.describe(
|
|
101
|
+
"The EXACT verbatim words from the conversation that support this fact. Copy a contiguous span from a single speaker turn (not a paraphrase). Cap at ~300 characters. This is the grounding evidence for downstream faithfulness verification (issue #1575).",
|
|
102
|
+
),
|
|
96
103
|
structuredAttributes: z
|
|
97
104
|
.record(z.string(), z.string())
|
|
98
105
|
.optional()
|
package/src/storage.ts
CHANGED
|
@@ -88,6 +88,7 @@ import type {
|
|
|
88
88
|
ScoredEntity,
|
|
89
89
|
TopicScore,
|
|
90
90
|
FileHygieneConfig,
|
|
91
|
+
ProvenanceSource,
|
|
91
92
|
} from "./types.js";
|
|
92
93
|
import { confidenceTier, SPECULATIVE_TTL_DAYS } from "./types.js";
|
|
93
94
|
import {
|
|
@@ -3608,6 +3609,14 @@ export class StorageManager {
|
|
|
3608
3609
|
* can consume it. Absent = gate was off or fact predates #1576.
|
|
3609
3610
|
*/
|
|
3610
3611
|
faithfulness?: import("./types.js").FaithfulnessFrontmatter;
|
|
3612
|
+
/**
|
|
3613
|
+
* Claim-level provenance spans (issue #1575 PR 2). When provided,
|
|
3614
|
+
* persisted to frontmatter so downstream readers (memory_get, x-ray,
|
|
3615
|
+
* faithfulness gate #1576) can consume them. Absent = fact predates
|
|
3616
|
+
* #1575 or provenance was disabled.
|
|
3617
|
+
*/
|
|
3618
|
+
sources?: ProvenanceSource[];
|
|
3619
|
+
provenance?: "verified" | "unverified" | "none";
|
|
3611
3620
|
} = {},
|
|
3612
3621
|
): Promise<string> {
|
|
3613
3622
|
await this.ensureDirectories();
|
|
@@ -3681,6 +3690,14 @@ export class StorageManager {
|
|
|
3681
3690
|
if (options.faithfulness !== undefined) {
|
|
3682
3691
|
fm.faithfulness = options.faithfulness;
|
|
3683
3692
|
}
|
|
3693
|
+
// Claim-level provenance (issue #1575 PR 2). Wire through to frontmatter
|
|
3694
|
+
// so verified spans survive extraction → storage → memory_get end-to-end.
|
|
3695
|
+
if (options.sources !== undefined) {
|
|
3696
|
+
fm.sources = options.sources;
|
|
3697
|
+
}
|
|
3698
|
+
if (options.provenance !== undefined) {
|
|
3699
|
+
fm.provenance = options.provenance;
|
|
3700
|
+
}
|
|
3684
3701
|
|
|
3685
3702
|
// Append structured attributes as searchable suffix so QMD indexes them.
|
|
3686
3703
|
// normalizeAttributePairs sorts and lowercases keys so the enriched content
|
|
@@ -7338,6 +7355,10 @@ export class StorageManager {
|
|
|
7338
7355
|
status?: import("./types.js").MemoryStatus;
|
|
7339
7356
|
/** Faithfulness gate verdict (issue #1576), propagated from the parent fact. */
|
|
7340
7357
|
faithfulness?: import("./types.js").FaithfulnessFrontmatter;
|
|
7358
|
+
/** Claim-level provenance spans (issue #1575 PR 2), propagated from the parent fact so a chunk surfaced independently (memory_get/x-ray) preserves them (chatgpt-codex-connector thread Ocvmo). */
|
|
7359
|
+
sources?: ProvenanceSource[];
|
|
7360
|
+
/** Coarse provenance tag (issue #1575 PR 2), propagated from the parent. */
|
|
7361
|
+
provenance?: "verified" | "unverified" | "none";
|
|
7341
7362
|
} = {},
|
|
7342
7363
|
): Promise<string> {
|
|
7343
7364
|
await this.ensureDirectories();
|
|
@@ -7369,6 +7390,8 @@ export class StorageManager {
|
|
|
7369
7390
|
valid_at: validAt,
|
|
7370
7391
|
...(options.status ? { status: options.status } : {}),
|
|
7371
7392
|
...(options.faithfulness ? { faithfulness: options.faithfulness } : {}),
|
|
7393
|
+
...(options.sources ? { sources: options.sources } : {}),
|
|
7394
|
+
...(options.provenance ? { provenance: options.provenance } : {}),
|
|
7372
7395
|
};
|
|
7373
7396
|
|
|
7374
7397
|
const sanitized = sanitizeMemoryContent(content);
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
resolveLcmReadSessionIds,
|
|
10
10
|
} from "./lcm-fallback-read.js";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
unifiedDedupeAndRank,
|
|
14
|
+
type RankedEvidenceItem,
|
|
15
|
+
} from "./recall-pipeline-stages.js";
|
|
16
|
+
|
|
12
17
|
export interface TargetedFactRecallOptions {
|
|
13
18
|
engine: ExplicitCueRecallEngine | null | undefined;
|
|
14
19
|
sessionId?: string;
|
|
@@ -27,10 +32,6 @@ export interface TargetedFactRecallOptions {
|
|
|
27
32
|
title?: string;
|
|
28
33
|
}
|
|
29
34
|
|
|
30
|
-
interface RankedEvidenceItem extends EvidencePackItem {
|
|
31
|
-
rank: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
35
|
const DEFAULT_MAX_SEARCH_RESULTS = 48;
|
|
35
36
|
const DEFAULT_SCAN_WINDOW_TURNS = 8;
|
|
36
37
|
const DEFAULT_SCAN_WINDOW_TOKENS = 12_000;
|
|
@@ -210,38 +211,30 @@ async function collectTargetedFactScanItems(
|
|
|
210
211
|
return items;
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Issue #1539 PR3: targeted-fact now routes dedup/score/threshold/sort through
|
|
216
|
+
* the unified spine (`unifiedDedupeAndRank`). The divergence that lived inline
|
|
217
|
+
* here — append-normalized-numeric-cues content transform, relevance scoring on
|
|
218
|
+
* ORIGINAL content, default DESC turn-index ordering with -1 sentinel for
|
|
219
|
+
* missing turns — is now declared config on the spine. The score and
|
|
220
|
+
* appendNormalizedNumericCues helpers stay in this module because they are
|
|
221
|
+
* genuinely per-tier policy; only the duplicated dedup/sort/threshold machinery
|
|
222
|
+
* moved out.
|
|
223
|
+
*
|
|
224
|
+
* Behavior is byte-for-byte identical to the inline implementation it replaces
|
|
225
|
+
* (the spine was extracted to match it exactly; see recall-pipeline-stages.ts).
|
|
226
|
+
* The targeted-fact-recall.test.ts suite (42 cases, incl. the search/scan
|
|
227
|
+
* ordering and summary insertion tests) is the characterization.
|
|
228
|
+
*/
|
|
213
229
|
function rankAndDedupeTargetedFactItems(
|
|
214
230
|
items: EvidencePackItem[],
|
|
215
231
|
query: string,
|
|
216
232
|
): RankedEvidenceItem[] {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const id = item.id ?? (
|
|
223
|
-
item.sessionId && typeof item.turnIndex === "number"
|
|
224
|
-
? `${item.sessionId}:${item.turnIndex}`
|
|
225
|
-
: undefined
|
|
226
|
-
);
|
|
227
|
-
if (id && seenIds.has(id)) continue;
|
|
228
|
-
const contentKey = item.content.toLowerCase().replace(/\s+/g, " ").trim();
|
|
229
|
-
if (seenContent.has(contentKey)) continue;
|
|
230
|
-
if (id) seenIds.add(id);
|
|
231
|
-
seenContent.add(contentKey);
|
|
232
|
-
ranked.push({
|
|
233
|
-
...item,
|
|
234
|
-
content: appendNormalizedNumericCues(item.content),
|
|
235
|
-
rank: scoreTargetedFactEvidence(item, query),
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return ranked.sort((left, right) => {
|
|
240
|
-
if (right.rank !== left.rank) return right.rank - left.rank;
|
|
241
|
-
const leftTurn = typeof left.turnIndex === "number" ? left.turnIndex : -1;
|
|
242
|
-
const rightTurn = typeof right.turnIndex === "number" ? right.turnIndex : -1;
|
|
243
|
-
if (rightTurn !== leftTurn) return rightTurn - leftTurn;
|
|
244
|
-
return (right.score ?? 0) - (left.score ?? 0);
|
|
233
|
+
return unifiedDedupeAndRank(items, {
|
|
234
|
+
query,
|
|
235
|
+
intents: [],
|
|
236
|
+
scoreEvidence: (item, q) => scoreTargetedFactEvidence(item, q),
|
|
237
|
+
transformContent: (content) => appendNormalizedNumericCues(content),
|
|
245
238
|
});
|
|
246
239
|
}
|
|
247
240
|
|
package/src/types.ts
CHANGED
|
@@ -2852,6 +2852,30 @@ export interface ExtractedFact {
|
|
|
2852
2852
|
* "no verified span" and skip gracefully.
|
|
2853
2853
|
*/
|
|
2854
2854
|
sources?: ProvenanceSource[];
|
|
2855
|
+
/**
|
|
2856
|
+
* Coarse provenance strength tag (issue #1575 PR 2). Set by the extraction
|
|
2857
|
+
* validator: "verified" when the quote was located in source turns,
|
|
2858
|
+
* "unverified" when the quote survived but no offsets were recoverable.
|
|
2859
|
+
* Absent when `provenance.enabled` is false or no quote was provided —
|
|
2860
|
+
* readers treat absent as "none" (rule 34 spirit).
|
|
2861
|
+
*/
|
|
2862
|
+
provenance?: "verified" | "unverified" | "none";
|
|
2863
|
+
/**
|
|
2864
|
+
* Transient LLM-provided supporting quote (issue #1575 PR 2). Consumed by
|
|
2865
|
+
* the post-parse validator to build `sources`, then stripped before the
|
|
2866
|
+
* fact reaches persistence. NEVER included in content-hash dedup (rule 23 /
|
|
2867
|
+
* checklist §13). Absent on facts that have already passed the validator.
|
|
2868
|
+
*/
|
|
2869
|
+
quote?: string;
|
|
2870
|
+
/**
|
|
2871
|
+
* Transient requireSpans signal (issue #1575 PR 2). Set by the provenance
|
|
2872
|
+
* validator (`ProvenanceBuildResult.requireSpansPending`) when
|
|
2873
|
+
* `provenance.requireSpans` is enabled and the quote could not be located
|
|
2874
|
+
* in any source turn. Read by the persist path to route the fact to
|
|
2875
|
+
* `pending_review` instead of `active`. Stripped before persistence —
|
|
2876
|
+
* it never reaches frontmatter (chatgpt-codex-connector thread 4xB).
|
|
2877
|
+
*/
|
|
2878
|
+
requireSpansPending?: boolean;
|
|
2855
2879
|
promptedByQuestion?: string;
|
|
2856
2880
|
/**
|
|
2857
2881
|
* Whether this fact is project-scoped or globally applicable.
|