@remnic/core 9.52.2 → 9.53.0
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.d.ts +5 -5
- package/dist/access-admin-ops-surface.js +22 -22
- package/dist/access-authorization-probe.d.ts +5 -5
- package/dist/access-authorization-probe.js +23 -23
- package/dist/access-boundary.d.ts +5 -5
- package/dist/access-boundary.js +22 -22
- package/dist/access-cli.js +54 -54
- package/dist/access-extraction-force-flush.d.ts +5 -5
- package/dist/access-extraction-force-flush.js +22 -22
- package/dist/access-http-lcm-compaction.d.ts +5 -5
- package/dist/access-http-lifecycle-flush.d.ts +5 -5
- package/dist/access-http-offline-stream.d.ts +5 -5
- package/dist/access-http-query.js +22 -22
- package/dist/access-http.d.ts +5 -5
- package/dist/access-http.js +29 -29
- package/dist/access-identity-continuity-surface.d.ts +4 -4
- package/dist/access-identity-continuity-surface.js +22 -22
- package/dist/access-lcm-surface.d.ts +5 -5
- package/dist/access-lcm-surface.js +22 -22
- package/dist/access-mcp.d.ts +5 -5
- package/dist/access-mcp.js +27 -27
- package/dist/access-namespace-preflight.js +22 -22
- package/dist/access-observe-write-surface.d.ts +5 -5
- package/dist/access-observe-write-surface.js +22 -22
- package/dist/access-offline-manifest.d.ts +4 -4
- package/dist/access-offline-manifest.js +8 -4
- package/dist/access-operations-batch.js +24 -24
- package/dist/access-operations.d.ts +8 -8
- package/dist/access-operations.js +26 -26
- package/dist/access-recall-concurrency.d.ts +5 -5
- package/dist/access-recall-concurrency.js +22 -22
- package/dist/access-recall-response.d.ts +5 -5
- package/dist/access-recall-response.js +22 -22
- package/dist/access-recall-surface.d.ts +5 -5
- package/dist/access-recall-surface.js +22 -22
- package/dist/access-schema.d.ts +88 -88
- package/dist/{access-service-Cbk3bNbD.d.ts → access-service-CgCjvbQu.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +5 -5
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +22 -22
- package/dist/access-surface-catalog.d.ts +5 -5
- package/dist/access-wearables-meetings-surface.d.ts +2 -2
- package/dist/active-recall.js +4 -4
- package/dist/{auto-sync-QFQGOEO2.js → auto-sync-JXEW7444.js} +6 -6
- package/dist/bootstrap.d.ts +4 -4
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +12 -12
- package/dist/buffer.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -13
- package/dist/{chunk-MLXZD4WM.js → chunk-2GFK22OZ.js} +2 -2
- package/dist/{chunk-D5YOZ66N.js → chunk-3DWTDC54.js} +20 -20
- package/dist/{chunk-RANKJI5T.js → chunk-3JLPGJ43.js} +2 -2
- package/dist/chunk-5FVVY2EB.js +27 -0
- package/dist/chunk-5FVVY2EB.js.map +1 -0
- package/dist/{chunk-GXSOUIA4.js → chunk-5WQMORU5.js} +4 -4
- package/dist/{chunk-J272WKX2.js → chunk-6HPHEFXT.js} +2 -2
- package/dist/{chunk-B6LZ4IBH.js → chunk-6LAPLZOS.js} +2 -2
- package/dist/{chunk-WD5B5AIE.js → chunk-6R4G7N4S.js} +118 -173
- package/dist/chunk-6R4G7N4S.js.map +1 -0
- package/dist/{chunk-VXCNQYTC.js → chunk-APPFNGX6.js} +2 -2
- package/dist/{chunk-RMHMUUBW.js → chunk-AY43LBE4.js} +104 -48
- package/dist/{chunk-RMHMUUBW.js.map → chunk-AY43LBE4.js.map} +1 -1
- package/dist/{chunk-GXFSQ6SD.js → chunk-C4ZNGVK3.js} +2 -2
- package/dist/{chunk-JSEJH2YQ.js → chunk-CGVHPCP2.js} +2 -2
- package/dist/{chunk-BQIFZ2MP.js → chunk-CX5SW2EW.js} +41 -24
- package/dist/chunk-CX5SW2EW.js.map +1 -0
- package/dist/{chunk-TEMSWHQD.js → chunk-DZD576ZU.js} +5 -5
- package/dist/{chunk-AMTEYNYK.js → chunk-EVWWT3KE.js} +4 -4
- package/dist/{chunk-XAPMEU3K.js → chunk-FHFERRYE.js} +2 -2
- package/dist/{chunk-P2KBDJJ6.js → chunk-G2LUMAUF.js} +4 -3
- package/dist/{chunk-P2KBDJJ6.js.map → chunk-G2LUMAUF.js.map} +1 -1
- package/dist/{chunk-AXIDQG5A.js → chunk-G4T7L557.js} +5 -5
- package/dist/{chunk-7M6NYQWB.js → chunk-G7Z6EENO.js} +4 -4
- package/dist/{chunk-YXKVLYXU.js → chunk-HWRD23DW.js} +2 -2
- package/dist/{chunk-SOEN6KCF.js → chunk-IBKR2TFR.js} +2 -2
- package/dist/{chunk-R3DJVOSM.js → chunk-JDSVLVCG.js} +8 -1
- package/dist/chunk-JDSVLVCG.js.map +1 -0
- package/dist/{chunk-F4EU6VUC.js → chunk-JDZPNCMA.js} +2 -2
- package/dist/{chunk-DLG5V6ND.js → chunk-K4QS7LXU.js} +4 -7
- package/dist/chunk-K4QS7LXU.js.map +1 -0
- package/dist/{chunk-BAKXNQ3C.js → chunk-KRTY3ENI.js} +2 -2
- package/dist/{chunk-NK5RGJ65.js → chunk-L4DLLKPN.js} +2 -2
- package/dist/{chunk-VFSAWRMZ.js → chunk-LJS7KUPV.js} +2 -2
- package/dist/{chunk-JJRVPWFY.js → chunk-MLPTFODV.js} +166 -37
- package/dist/chunk-MLPTFODV.js.map +1 -0
- package/dist/{chunk-NOEMGE56.js → chunk-NH5IR2WM.js} +2 -2
- package/dist/{chunk-5V6IXHPI.js → chunk-NSVDUN76.js} +2 -2
- package/dist/{chunk-IARXXZU4.js → chunk-P7XI2AQ3.js} +2 -2
- package/dist/{chunk-W7Y5JDVB.js → chunk-PDDTI7IC.js} +4 -4
- package/dist/{chunk-QINJITDS.js → chunk-PX2LSHYM.js} +2 -2
- package/dist/{chunk-O5FFBO2M.js → chunk-QAVH6X2A.js} +2 -2
- package/dist/{chunk-HDIUYMSB.js → chunk-QWGE2PUH.js} +6 -6
- package/dist/{chunk-X57M2Q4I.js → chunk-RDJRFDJY.js} +3 -3
- package/dist/{chunk-RYLEDKQ2.js → chunk-RUMNY5DT.js} +3 -3
- package/dist/{chunk-BKIFTXHR.js → chunk-RZ4XGKOJ.js} +2 -2
- package/dist/{chunk-66SE3BNH.js → chunk-SUPEYLH2.js} +2 -2
- package/dist/{chunk-BF6SXNQJ.js → chunk-T2JHZWH6.js} +20 -20
- package/dist/{chunk-VHFXP27N.js → chunk-THPPCISA.js} +60 -23
- package/dist/chunk-THPPCISA.js.map +1 -0
- package/dist/{chunk-QPOHMO6M.js → chunk-UES4BHA2.js} +2 -2
- package/dist/{chunk-X7XEDOSL.js → chunk-VLL43QEQ.js} +8 -8
- package/dist/{chunk-QBSFOKY6.js → chunk-VOELBUS4.js} +4 -4
- package/dist/{chunk-2LY6GIIE.js → chunk-WDBX6VER.js} +2 -2
- package/dist/{chunk-BMEYMKWX.js → chunk-WM65LU67.js} +2 -2
- package/dist/{chunk-WLRD2TXF.js → chunk-WOPA3SGI.js} +4 -4
- package/dist/{chunk-5UZ6UCOZ.js → chunk-XUAMCLY4.js} +3 -3
- package/dist/{chunk-7HXZJAID.js → chunk-YGLY6QFV.js} +3 -3
- package/dist/{chunk-2OIQDTDC.js → chunk-ZPJ4QSGP.js} +2 -2
- package/dist/{chunk-3OY7KXHM.js → chunk-ZYLC7NFO.js} +3 -3
- package/dist/{cli-DMAFI0qt.d.ts → cli-D-HiEYhl.d.ts} +3 -3
- package/dist/cli.d.ts +6 -6
- package/dist/cli.js +41 -41
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +13 -13
- package/dist/config.js +4 -4
- package/dist/connectors/codex-materialize-runner.js +12 -12
- package/dist/connectors/index.js +12 -12
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/content-hash.d.ts +12 -2
- package/dist/content-hash.js +9 -3
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/contradiction/index.js +3 -3
- package/dist/convergence-refresh.d.ts +1 -1
- package/dist/convergence-refresh.js +14 -14
- package/dist/corpus-watermark.js +14 -14
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +12 -12
- package/dist/explicit-capture.d.ts +4 -4
- package/dist/explicit-capture.js +8 -8
- package/dist/external-wiki-access.d.ts +5 -5
- package/dist/external-wiki-access.js +23 -23
- package/dist/external-wiki-mcp-tools.d.ts +5 -5
- package/dist/extraction-normalization.js +2 -2
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.js +16 -16
- package/dist/{forget-HDS4N6CG.js → forget-EHGGJZLX.js} +3 -2
- package/dist/{forget-HDS4N6CG.js.map → forget-EHGGJZLX.js.map} +1 -1
- package/dist/harmonic-construction.js +2 -2
- package/dist/harmonic-retrieval.js +4 -4
- package/dist/index.d.ts +684 -684
- package/dist/index.js +66 -66
- package/dist/maintenance/memory-governance.js +12 -12
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -12
- package/dist/maintenance/rebuild-memory-projection.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-projection.js +13 -13
- package/dist/{maintenance-kBII2qUI.d.ts → maintenance-CN_Ct-Hr.d.ts} +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +13 -13
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +12 -12
- package/dist/operator-doctor-corpus.js +15 -15
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +20 -20
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +17 -17
- package/dist/{orchestrator-DHy5LtTy.d.ts → orchestrator-B3Xe8qu0.d.ts} +3 -3
- package/dist/orchestrator.d.ts +4 -4
- package/dist/orchestrator.js +54 -54
- package/dist/provenance.js +2 -2
- package/dist/reconcile/manifest.d.ts +3 -1
- package/dist/reconcile/manifest.js +7 -3
- package/dist/{resolution-MDPNOEG6.js → resolution-MMOD5WC4.js} +4 -4
- package/dist/resume-bundles.js +5 -5
- package/dist/salvage-envelope.js +3 -3
- package/dist/schemas.d.ts +116 -116
- package/dist/semantic-consolidation.js +13 -13
- package/dist/semantic-rule-promotion.js +12 -12
- package/dist/semantic-rule-verifier.js +12 -12
- package/dist/{service-PT2_z7xm.d.ts → service-CQGEqL1g.d.ts} +1 -1
- package/dist/shared-context/manager.d.ts +8 -8
- package/dist/source-agent-qualifier.js +14 -14
- package/dist/{storage-BmVudhk4.d.ts → storage-DmkCjo0l.d.ts} +36 -32
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +14 -14
- package/dist/structured-attributes.d.ts +3 -1
- package/dist/structured-attributes.js +3 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/transfer/types.d.ts +66 -66
- package/dist/verified-recall.js +12 -12
- package/dist/write-envelope.js +2 -2
- package/package.json +2 -2
- package/src/access-offline-manifest.ts +1 -0
- package/src/access-service-offline-file-content.test.ts +1 -0
- package/src/content-hash.ts +34 -1
- package/src/dedup/index.test.ts +56 -38
- package/src/dedup/index.ts +85 -38
- package/src/lifecycle/tombstones.test.ts +763 -16
- package/src/lifecycle/tombstones.ts +217 -50
- package/src/reconcile/manifest.test.ts +158 -1
- package/src/reconcile/manifest.ts +76 -22
- package/src/storage/content-hash-index.ts +56 -40
- package/src/storage/entity-ref-repair.ts +3 -3
- package/src/storage/memory-read-store.ts +9 -0
- package/src/storage/tombstone-blocked-capture-index.ts +4 -9
- package/src/storage-contract/content-hash.test.ts +13 -0
- package/src/storage-fact-hash-index-defer.test.ts +1 -1
- package/src/storage.ts +107 -93
- package/src/structured-attributes.ts +11 -0
- package/dist/chunk-BQIFZ2MP.js.map +0 -1
- package/dist/chunk-DLG5V6ND.js.map +0 -1
- package/dist/chunk-JJRVPWFY.js.map +0 -1
- package/dist/chunk-R3DJVOSM.js.map +0 -1
- package/dist/chunk-VHFXP27N.js.map +0 -1
- package/dist/chunk-W5H6ZRPT.js +0 -14
- package/dist/chunk-W5H6ZRPT.js.map +0 -1
- package/dist/chunk-WD5B5AIE.js.map +0 -1
- /package/dist/{auto-sync-QFQGOEO2.js.map → auto-sync-JXEW7444.js.map} +0 -0
- /package/dist/{chunk-MLXZD4WM.js.map → chunk-2GFK22OZ.js.map} +0 -0
- /package/dist/{chunk-D5YOZ66N.js.map → chunk-3DWTDC54.js.map} +0 -0
- /package/dist/{chunk-RANKJI5T.js.map → chunk-3JLPGJ43.js.map} +0 -0
- /package/dist/{chunk-GXSOUIA4.js.map → chunk-5WQMORU5.js.map} +0 -0
- /package/dist/{chunk-J272WKX2.js.map → chunk-6HPHEFXT.js.map} +0 -0
- /package/dist/{chunk-B6LZ4IBH.js.map → chunk-6LAPLZOS.js.map} +0 -0
- /package/dist/{chunk-VXCNQYTC.js.map → chunk-APPFNGX6.js.map} +0 -0
- /package/dist/{chunk-GXFSQ6SD.js.map → chunk-C4ZNGVK3.js.map} +0 -0
- /package/dist/{chunk-JSEJH2YQ.js.map → chunk-CGVHPCP2.js.map} +0 -0
- /package/dist/{chunk-TEMSWHQD.js.map → chunk-DZD576ZU.js.map} +0 -0
- /package/dist/{chunk-AMTEYNYK.js.map → chunk-EVWWT3KE.js.map} +0 -0
- /package/dist/{chunk-XAPMEU3K.js.map → chunk-FHFERRYE.js.map} +0 -0
- /package/dist/{chunk-AXIDQG5A.js.map → chunk-G4T7L557.js.map} +0 -0
- /package/dist/{chunk-7M6NYQWB.js.map → chunk-G7Z6EENO.js.map} +0 -0
- /package/dist/{chunk-YXKVLYXU.js.map → chunk-HWRD23DW.js.map} +0 -0
- /package/dist/{chunk-SOEN6KCF.js.map → chunk-IBKR2TFR.js.map} +0 -0
- /package/dist/{chunk-F4EU6VUC.js.map → chunk-JDZPNCMA.js.map} +0 -0
- /package/dist/{chunk-BAKXNQ3C.js.map → chunk-KRTY3ENI.js.map} +0 -0
- /package/dist/{chunk-NK5RGJ65.js.map → chunk-L4DLLKPN.js.map} +0 -0
- /package/dist/{chunk-VFSAWRMZ.js.map → chunk-LJS7KUPV.js.map} +0 -0
- /package/dist/{chunk-NOEMGE56.js.map → chunk-NH5IR2WM.js.map} +0 -0
- /package/dist/{chunk-5V6IXHPI.js.map → chunk-NSVDUN76.js.map} +0 -0
- /package/dist/{chunk-IARXXZU4.js.map → chunk-P7XI2AQ3.js.map} +0 -0
- /package/dist/{chunk-W7Y5JDVB.js.map → chunk-PDDTI7IC.js.map} +0 -0
- /package/dist/{chunk-QINJITDS.js.map → chunk-PX2LSHYM.js.map} +0 -0
- /package/dist/{chunk-O5FFBO2M.js.map → chunk-QAVH6X2A.js.map} +0 -0
- /package/dist/{chunk-HDIUYMSB.js.map → chunk-QWGE2PUH.js.map} +0 -0
- /package/dist/{chunk-X57M2Q4I.js.map → chunk-RDJRFDJY.js.map} +0 -0
- /package/dist/{chunk-RYLEDKQ2.js.map → chunk-RUMNY5DT.js.map} +0 -0
- /package/dist/{chunk-BKIFTXHR.js.map → chunk-RZ4XGKOJ.js.map} +0 -0
- /package/dist/{chunk-66SE3BNH.js.map → chunk-SUPEYLH2.js.map} +0 -0
- /package/dist/{chunk-BF6SXNQJ.js.map → chunk-T2JHZWH6.js.map} +0 -0
- /package/dist/{chunk-QPOHMO6M.js.map → chunk-UES4BHA2.js.map} +0 -0
- /package/dist/{chunk-X7XEDOSL.js.map → chunk-VLL43QEQ.js.map} +0 -0
- /package/dist/{chunk-QBSFOKY6.js.map → chunk-VOELBUS4.js.map} +0 -0
- /package/dist/{chunk-2LY6GIIE.js.map → chunk-WDBX6VER.js.map} +0 -0
- /package/dist/{chunk-BMEYMKWX.js.map → chunk-WM65LU67.js.map} +0 -0
- /package/dist/{chunk-WLRD2TXF.js.map → chunk-WOPA3SGI.js.map} +0 -0
- /package/dist/{chunk-5UZ6UCOZ.js.map → chunk-XUAMCLY4.js.map} +0 -0
- /package/dist/{chunk-7HXZJAID.js.map → chunk-YGLY6QFV.js.map} +0 -0
- /package/dist/{chunk-2OIQDTDC.js.map → chunk-ZPJ4QSGP.js.map} +0 -0
- /package/dist/{chunk-3OY7KXHM.js.map → chunk-ZYLC7NFO.js.map} +0 -0
- /package/dist/{resolution-MDPNOEG6.js.map → resolution-MMOD5WC4.js.map} +0 -0
package/src/dedup/index.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
2
|
+
import { mkdir, mkdtemp, rm, symlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
@@ -14,25 +14,13 @@ test("findContradictions detects can versus cannot statements", async () => {
|
|
|
14
14
|
await mkdir(factsDir);
|
|
15
15
|
await writeFile(
|
|
16
16
|
path.join(factsDir, "can.md"),
|
|
17
|
-
[
|
|
18
|
-
|
|
19
|
-
"id: mem-can",
|
|
20
|
-
"category: fact",
|
|
21
|
-
"---",
|
|
22
|
-
"The user can access production.",
|
|
23
|
-
].join("\n"),
|
|
24
|
-
"utf8",
|
|
17
|
+
["---", "id: mem-can", "category: fact", "---", "The user can access production."].join("\n"),
|
|
18
|
+
"utf8"
|
|
25
19
|
);
|
|
26
20
|
await writeFile(
|
|
27
21
|
path.join(factsDir, "cannot.md"),
|
|
28
|
-
[
|
|
29
|
-
|
|
30
|
-
"id: mem-cannot",
|
|
31
|
-
"category: fact",
|
|
32
|
-
"---",
|
|
33
|
-
"The user cannot access production.",
|
|
34
|
-
].join("\n"),
|
|
35
|
-
"utf8",
|
|
22
|
+
["---", "id: mem-cannot", "category: fact", "---", "The user cannot access production."].join("\n"),
|
|
23
|
+
"utf8"
|
|
36
24
|
);
|
|
37
25
|
|
|
38
26
|
const result = findContradictions({ memoryDir, categories: ["facts"] });
|
|
@@ -58,14 +46,8 @@ test("dedup public numeric options are normalized before scanning", async () =>
|
|
|
58
46
|
] as const) {
|
|
59
47
|
await writeFile(
|
|
60
48
|
path.join(factsDir, fileName),
|
|
61
|
-
[
|
|
62
|
-
|
|
63
|
-
`id: ${id}`,
|
|
64
|
-
"category: fact",
|
|
65
|
-
"---",
|
|
66
|
-
content,
|
|
67
|
-
].join("\n"),
|
|
68
|
-
"utf8",
|
|
49
|
+
["---", `id: ${id}`, "category: fact", "---", content].join("\n"),
|
|
50
|
+
"utf8"
|
|
69
51
|
);
|
|
70
52
|
}
|
|
71
53
|
|
|
@@ -95,6 +77,51 @@ test("dedup public numeric options are normalized before scanning", async () =>
|
|
|
95
77
|
}
|
|
96
78
|
});
|
|
97
79
|
|
|
80
|
+
test("dedup keeps duplicate and distinct pairs across Unicode scripts", async () => {
|
|
81
|
+
const memoryDir = await mkdtemp(path.join(tmpdir(), "remnic-dedup-unicode-"));
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const factsDir = path.join(memoryDir, "facts");
|
|
85
|
+
await mkdir(factsDir);
|
|
86
|
+
const entries = [
|
|
87
|
+
["english-a.md", "mem-english-a", "The user prefers tea."],
|
|
88
|
+
["english-b.md", "mem-english-b", "The user prefers tea"],
|
|
89
|
+
["english-c.md", "mem-english-c", "The user prefers coffee."],
|
|
90
|
+
["japanese-a.md", "mem-japanese-a", "利用者は紅茶を好む。"],
|
|
91
|
+
["japanese-b.md", "mem-japanese-b", "利用者は紅茶を好む。"],
|
|
92
|
+
["japanese-c.md", "mem-japanese-c", "利用者は珈琲を好む。"],
|
|
93
|
+
["korean-a.md", "mem-korean-a", "사용자는홍차를좋아한다"],
|
|
94
|
+
["korean-b.md", "mem-korean-b", "사용자는홍차를좋아한다"],
|
|
95
|
+
["korean-c.md", "mem-korean-c", "사용자는커피를좋아한다"],
|
|
96
|
+
["korean-punctuation.md", "mem-korean-punctuation", "홍차・커피"],
|
|
97
|
+
["korean-hyphen.md", "mem-korean-hyphen", "홍차-커피"],
|
|
98
|
+
["korean-joined.md", "mem-korean-joined", "홍차커피"],
|
|
99
|
+
["japanese-boundary-a.md", "mem-japanese-boundary-a", "甲・乙丙"],
|
|
100
|
+
["japanese-boundary-b.md", "mem-japanese-boundary-b", "甲乙・丙"],
|
|
101
|
+
] as const;
|
|
102
|
+
|
|
103
|
+
for (const [fileName, id, content] of entries) {
|
|
104
|
+
await writeFile(
|
|
105
|
+
path.join(factsDir, fileName),
|
|
106
|
+
["---", `id: ${id}`, "category: fact", "---", content].join("\n"),
|
|
107
|
+
"utf8"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const result = findDuplicates({ memoryDir, categories: ["facts"] });
|
|
112
|
+
|
|
113
|
+
assert.equal(result.scanned, entries.length);
|
|
114
|
+
assert.equal(result.duplicates.length, 3);
|
|
115
|
+
assert.deepEqual(result.duplicates.map(({ left, right }) => [left.id, right.id]).sort(), [
|
|
116
|
+
["mem-english-a", "mem-english-b"],
|
|
117
|
+
["mem-japanese-a", "mem-japanese-b"],
|
|
118
|
+
["mem-korean-a", "mem-korean-b"],
|
|
119
|
+
]);
|
|
120
|
+
} finally {
|
|
121
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
98
125
|
test("dedup scans reject symlinked category directories outside memoryDir", async (t) => {
|
|
99
126
|
if (process.platform === "win32") {
|
|
100
127
|
t.skip("directory symlink setup is platform-specific");
|
|
@@ -107,24 +134,15 @@ test("dedup scans reject symlinked category directories outside memoryDir", asyn
|
|
|
107
134
|
try {
|
|
108
135
|
await writeFile(
|
|
109
136
|
path.join(outsideDir, "outside.md"),
|
|
110
|
-
[
|
|
111
|
-
|
|
112
|
-
"id: outside",
|
|
113
|
-
"category: fact",
|
|
114
|
-
"---",
|
|
115
|
-
"Outside memory should not be scanned.",
|
|
116
|
-
].join("\n"),
|
|
117
|
-
"utf8",
|
|
137
|
+
["---", "id: outside", "category: fact", "---", "Outside memory should not be scanned."].join("\n"),
|
|
138
|
+
"utf8"
|
|
118
139
|
);
|
|
119
140
|
await symlink(outsideDir, path.join(memoryDir, "facts"), "dir");
|
|
120
141
|
|
|
121
|
-
assert.throws(
|
|
122
|
-
() => findDuplicates({ memoryDir, categories: ["facts"] }),
|
|
123
|
-
/symlinked memory category directory/,
|
|
124
|
-
);
|
|
142
|
+
assert.throws(() => findDuplicates({ memoryDir, categories: ["facts"] }), /symlinked memory category directory/);
|
|
125
143
|
assert.throws(
|
|
126
144
|
() => findContradictions({ memoryDir, categories: ["facts"] }),
|
|
127
|
-
/symlinked memory category directory
|
|
145
|
+
/symlinked memory category directory/
|
|
128
146
|
);
|
|
129
147
|
} finally {
|
|
130
148
|
await rm(memoryDir, { recursive: true, force: true });
|
package/src/dedup/index.ts
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* against existing memories. Can be used standalone or via curation.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import crypto from "node:crypto";
|
|
8
9
|
import fs from "node:fs";
|
|
9
10
|
import path from "node:path";
|
|
10
|
-
import
|
|
11
|
+
import { normalizeRecallTokenSet } from "../recall-tokenization.js";
|
|
11
12
|
import { ALL_CATEGORY_DIRS } from "../utils/category-dir.js";
|
|
12
13
|
|
|
13
14
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -89,20 +90,11 @@ const DEFAULT_DEDUP_THRESHOLD = 0.85;
|
|
|
89
90
|
const DEFAULT_MAX_LOAD = 10000;
|
|
90
91
|
|
|
91
92
|
function normalizeThreshold(value: number | undefined, defaultValue: number): number {
|
|
92
|
-
return typeof value === "number" &&
|
|
93
|
-
Number.isFinite(value) &&
|
|
94
|
-
value >= 0 &&
|
|
95
|
-
value <= 1
|
|
96
|
-
? value
|
|
97
|
-
: defaultValue;
|
|
93
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1 ? value : defaultValue;
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
function normalizeMaxLoad(value: number | undefined, defaultValue: number): number {
|
|
101
|
-
return typeof value === "number" &&
|
|
102
|
-
Number.isInteger(value) &&
|
|
103
|
-
value >= 0
|
|
104
|
-
? value
|
|
105
|
-
: defaultValue;
|
|
97
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : defaultValue;
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
export function findDuplicates(options: DedupOptions): DedupResult {
|
|
@@ -163,26 +155,25 @@ export function findContradictions(options: ContradictionOptions): Contradiction
|
|
|
163
155
|
// ── Similarity computation ───────────────────────────────────────────────────
|
|
164
156
|
|
|
165
157
|
function computeSimilarity(a: string, b: string): number {
|
|
166
|
-
// Normalize
|
|
167
158
|
const normA = normalize(a);
|
|
168
159
|
const normB = normalize(b);
|
|
160
|
+
const punctuationBoundariesA = embeddedPunctuationSignature(a);
|
|
161
|
+
const punctuationBoundariesB = embeddedPunctuationSignature(b);
|
|
162
|
+
|
|
163
|
+
// Compare punctuation and positions before normalization's exact/hash paths.
|
|
164
|
+
if (punctuationBoundariesA !== punctuationBoundariesB) return 0;
|
|
169
165
|
|
|
170
|
-
// Exact match
|
|
171
166
|
if (normA === normB) return 1;
|
|
172
167
|
|
|
173
|
-
// Hash-based exact match
|
|
174
168
|
if (hashContent(normA) === hashContent(normB)) return 0.99;
|
|
175
|
-
|
|
176
|
-
// Substring containment
|
|
177
169
|
if (normA.length > 50 && normB.length > 50) {
|
|
178
170
|
if (normA.includes(normB.slice(0, 40)) || normB.includes(normA.slice(0, 40))) {
|
|
179
171
|
return 0.9;
|
|
180
172
|
}
|
|
181
173
|
}
|
|
182
174
|
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
const wordsB = new Set(normB.split(/\s+/));
|
|
175
|
+
const wordsA = tokenizeForSimilarity(normA);
|
|
176
|
+
const wordsB = tokenizeForSimilarity(normB);
|
|
186
177
|
const intersection = new Set([...wordsA].filter((w) => wordsB.has(w)));
|
|
187
178
|
const union = new Set([...wordsA, ...wordsB]);
|
|
188
179
|
|
|
@@ -190,25 +181,89 @@ function computeSimilarity(a: string, b: string): number {
|
|
|
190
181
|
return intersection.size / union.size;
|
|
191
182
|
}
|
|
192
183
|
|
|
184
|
+
function embeddedPunctuationSignature(text: string): string {
|
|
185
|
+
const chars = [...text.normalize("NFC")];
|
|
186
|
+
const significant = /[\p{L}\p{M}\p{N}]/u;
|
|
187
|
+
const ignored = /[\s']/u;
|
|
188
|
+
const boundaries: string[] = [];
|
|
189
|
+
let position = 0;
|
|
190
|
+
for (let index = 0; index < chars.length;) {
|
|
191
|
+
const char = chars[index];
|
|
192
|
+
if (char === undefined) break;
|
|
193
|
+
if (significant.test(char)) {
|
|
194
|
+
position++;
|
|
195
|
+
index++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (ignored.test(char)) {
|
|
199
|
+
index++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const start = index;
|
|
203
|
+
while (index < chars.length) {
|
|
204
|
+
const next = chars[index];
|
|
205
|
+
if (next === undefined || significant.test(next) || ignored.test(next)) break;
|
|
206
|
+
index++;
|
|
207
|
+
}
|
|
208
|
+
const before = chars[start - 1];
|
|
209
|
+
const after = chars[index];
|
|
210
|
+
if (before && after && significant.test(before) && significant.test(after)) {
|
|
211
|
+
boundaries.push(`${position}:${chars.slice(start, index).join("")}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return boundaries.join("\u0000");
|
|
215
|
+
}
|
|
216
|
+
|
|
193
217
|
function normalize(text: string): string {
|
|
194
218
|
return text
|
|
219
|
+
.normalize("NFC")
|
|
195
220
|
.toLowerCase()
|
|
196
|
-
.replace(/[^\
|
|
221
|
+
.replace(/[^\p{ASCII}\p{L}\p{M}\p{N}\s']/gu, " ")
|
|
222
|
+
.replace(/[^\p{L}\p{M}\p{N}\s']/gu, "")
|
|
197
223
|
.replace(/\s+/g, " ")
|
|
198
224
|
.trim();
|
|
199
225
|
}
|
|
200
226
|
|
|
227
|
+
function tokenizeForSimilarity(text: string): Set<string> {
|
|
228
|
+
if (/\p{Script=Hangul}/u.test(text)) {
|
|
229
|
+
const tokens = normalizeRecallTokenSet(text);
|
|
230
|
+
for (const segment of text.match(/\p{Script=Hangul}+/gu) ?? []) {
|
|
231
|
+
const chars = [...segment];
|
|
232
|
+
for (const size of [1, 2, 3, 4]) {
|
|
233
|
+
for (let index = 0; index <= chars.length - size; index += 1) {
|
|
234
|
+
tokens.add(chars.slice(index, index + size).join(""));
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return tokens;
|
|
239
|
+
}
|
|
240
|
+
if (/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u.test(text)) {
|
|
241
|
+
return normalizeRecallTokenSet(text);
|
|
242
|
+
}
|
|
243
|
+
return text.length === 0 ? new Set() : new Set(text.split(/\s+/));
|
|
244
|
+
}
|
|
245
|
+
|
|
201
246
|
// ── Contradiction detection ──────────────────────────────────────────────────
|
|
202
247
|
|
|
203
248
|
const NEGATION_WORDS = new Set([
|
|
204
|
-
"not",
|
|
205
|
-
"
|
|
249
|
+
"not",
|
|
250
|
+
"don't",
|
|
251
|
+
"doesn't",
|
|
252
|
+
"isn't",
|
|
253
|
+
"aren't",
|
|
254
|
+
"won't",
|
|
255
|
+
"can't",
|
|
256
|
+
"cannot",
|
|
257
|
+
"never",
|
|
258
|
+
"no",
|
|
259
|
+
"none",
|
|
260
|
+
"neither",
|
|
261
|
+
"nor",
|
|
262
|
+
"nothing",
|
|
263
|
+
"nowhere",
|
|
206
264
|
]);
|
|
207
265
|
|
|
208
|
-
function detectContradiction(
|
|
209
|
-
a: MemoryEntry,
|
|
210
|
-
b: MemoryEntry,
|
|
211
|
-
): ContradictionPair | null {
|
|
266
|
+
function detectContradiction(a: MemoryEntry, b: MemoryEntry): ContradictionPair | null {
|
|
212
267
|
const normA = normalize(a.content);
|
|
213
268
|
const normB = normalize(b.content);
|
|
214
269
|
|
|
@@ -271,11 +326,7 @@ function stripNegation(text: string): string {
|
|
|
271
326
|
|
|
272
327
|
// ── Memory loading ───────────────────────────────────────────────────────────
|
|
273
328
|
|
|
274
|
-
function loadMemories(
|
|
275
|
-
memoryDir: string,
|
|
276
|
-
categories?: string[],
|
|
277
|
-
maxLoad = 10000,
|
|
278
|
-
): MemoryEntry[] {
|
|
329
|
+
function loadMemories(memoryDir: string, categories?: string[], maxLoad = 10000): MemoryEntry[] {
|
|
279
330
|
const result: MemoryEntry[] = [];
|
|
280
331
|
const allCategories = categories ?? ALL_CATEGORY_DIRS;
|
|
281
332
|
if (!fs.existsSync(memoryDir)) return result;
|
|
@@ -322,11 +373,7 @@ function hashContent(content: string): string {
|
|
|
322
373
|
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
323
374
|
}
|
|
324
375
|
|
|
325
|
-
function readFileSafe(
|
|
326
|
-
filePath: string,
|
|
327
|
-
memoryRootReal: string,
|
|
328
|
-
categoryRootReal: string,
|
|
329
|
-
): string | null {
|
|
376
|
+
function readFileSafe(filePath: string, memoryRootReal: string, categoryRootReal: string): string | null {
|
|
330
377
|
try {
|
|
331
378
|
const fileStat = fs.lstatSync(filePath);
|
|
332
379
|
if (fileStat.isSymbolicLink()) {
|
|
@@ -373,7 +420,7 @@ function walkMdFiles(
|
|
|
373
420
|
dir: string,
|
|
374
421
|
memoryRootReal: string,
|
|
375
422
|
categoryRootReal: string,
|
|
376
|
-
callback: (filePath: string) => void
|
|
423
|
+
callback: (filePath: string) => void
|
|
377
424
|
): void {
|
|
378
425
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
379
426
|
const fullPath = path.join(dir, entry.name);
|