@remnic/plugin-openclaw 1.0.35 → 1.0.36

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.
Files changed (87) hide show
  1. package/README.md +38 -4
  2. package/dist/{calibration-Z5WWNV7U.js → calibration-RKL2LRW4.js} +4 -4
  3. package/dist/{capsule-cli-GBM3WPAM.js → capsule-cli-EHZPMXBC.js} +2 -2
  4. package/dist/{capsule-crypto-K3IRTKRH.js → capsule-crypto-JS67OSWM.js} +3 -3
  5. package/dist/capsule-export-YPDWRB3C.js +17 -0
  6. package/dist/capsule-import-SWPOFG6F.js +16 -0
  7. package/dist/{capsule-merge-IWOQ34KL.js → capsule-merge-YXAF7ZJW.js} +7 -7
  8. package/dist/{causal-chain-WYN5QOPS.js → causal-chain-BVTOWZKC.js} +4 -4
  9. package/dist/{causal-consolidation-C64NNE4T.js → causal-consolidation-DRPM2KOE.js} +13 -10
  10. package/dist/{causal-retrieval-NZHQOZOE.js → causal-retrieval-XAP6QKHZ.js} +4 -5
  11. package/dist/{causal-trajectory-graph-VBPE2WPM.js → causal-trajectory-graph-ZWQWZ7N5.js} +2 -2
  12. package/dist/{chunk-5LE4HTVL.js → chunk-25J4PXDH.js} +0 -18
  13. package/dist/{chunk-FGTYFLL5.js → chunk-3IHGISUN.js} +29 -32
  14. package/dist/{chunk-6UFI73TJ.js → chunk-3IKMUNW5.js} +53 -46
  15. package/dist/{chunk-EXDYWXMB.js → chunk-4XDQ3KEC.js} +1 -2
  16. package/dist/{chunk-JGIUTWZS.js → chunk-6O3H3DPL.js} +2 -2
  17. package/dist/{chunk-UTDLHBBV.js → chunk-BLC3RQNV.js} +5 -555
  18. package/dist/{chunk-4G2XCSD2.js → chunk-BZ4EYURA.js} +0 -5
  19. package/dist/{chunk-4LYQ4ONL.js → chunk-E4RM7637.js} +1 -1
  20. package/dist/{chunk-TDRJVMUP.js → chunk-EH4AXGRO.js} +0 -12
  21. package/dist/{chunk-EYCLXMIV.js → chunk-G3CZA4SD.js} +9 -427
  22. package/dist/chunk-I2KLQ2HA.js +22 -0
  23. package/dist/chunk-IO5WWY6A.js +156 -0
  24. package/dist/{contradiction-scan-A5NOTZPN.js → chunk-JC3FCKYL.js} +189 -86
  25. package/dist/{chunk-SVSQAG6M.js → chunk-KC7KSQR4.js} +47 -28
  26. package/dist/chunk-LZCGPRHS.js +228 -0
  27. package/dist/{chunk-CXM7EBAO.js → chunk-MXFJXUHC.js} +1 -1
  28. package/dist/{chunk-L6I4MQKO.js → chunk-NNAN63QK.js} +6 -6
  29. package/dist/{chunk-VRGUUHBV.js → chunk-NUWDSTP7.js} +1 -1
  30. package/dist/{chunk-6OJAU466.js → chunk-QMUQV5NP.js} +0 -1
  31. package/dist/{chunk-LLUROTZJ.js → chunk-QQXJODFL.js} +9 -9
  32. package/dist/{chunk-6F6EKSVP.js → chunk-QXXEF7VI.js} +1 -1
  33. package/dist/{chunk-CMKR6NDQ.js → chunk-SEGEX7W4.js} +73 -241
  34. package/dist/{chunk-VFULKFKI.js → chunk-SWOYEQN2.js} +42 -17
  35. package/dist/chunk-TH5FF5SC.js +16 -0
  36. package/dist/chunk-UZJ7EERS.js +272 -0
  37. package/dist/chunk-YJYZMLD5.js +360 -0
  38. package/dist/{chunk-NKVIN6RD.js → chunk-YKV4EFUI.js} +84 -2
  39. package/dist/{chunk-SSFTU6LP.js → chunk-ZS6VABML.js} +4 -4
  40. package/dist/{cipher-VHAFCG7Z.js → cipher-E23BHBSO.js} +1 -1
  41. package/dist/{consolidation-undo-5ZSX4MWO.js → consolidation-undo-FKJZCJHS.js} +2 -2
  42. package/dist/contradiction-review-WJRWNQ5N.js +29 -0
  43. package/dist/contradiction-scan-5X423QGT.js +12 -0
  44. package/dist/{dreams-ledger-3I52ISYR.js → dreams-ledger-KDX44I7R.js} +1 -1
  45. package/dist/{engine-47AKKYJ4.js → engine-5P774HTZ.js} +6 -6
  46. package/dist/{extraction-judge-telemetry-GHOTVYMP.js → extraction-judge-telemetry-O4ZVGLTU.js} +1 -1
  47. package/dist/{fallback-llm-45A755XP.js → fallback-llm-43UMEXNJ.js} +3 -3
  48. package/dist/{first-start-migration-I24M2JEE.js → first-start-migration-H2SAXAGR.js} +4 -4
  49. package/dist/{forget-NI4RBDPB.js → forget-ZECIDNL5.js} +1 -1
  50. package/dist/{fs-utils-PZRI2HDZ.js → fs-utils-OYXSZSVV.js} +12 -2
  51. package/dist/{graph-edge-decay-5CVKWBYH.js → graph-edge-decay-24ZKD5QL.js} +5 -5
  52. package/dist/index.js +7091 -84285
  53. package/dist/{kdf-H5B23ZM2.js → kdf-RXKIWHRU.js} +1 -1
  54. package/dist/legacy-hook-compat-QHHKF4GK.js +2 -0
  55. package/dist/{logger-TNOKCH7X.js → logger-XG7JKLPS.js} +1 -1
  56. package/dist/{memory-governance-QS7Z425Y.js → memory-governance-6K4M4YXD.js} +5 -5
  57. package/dist/{metadata-JAGIWHEA.js → metadata-WK2TRPYZ.js} +1 -1
  58. package/dist/{migrate-from-identity-anchor-7MMSPEUM.js → migrate-from-identity-anchor-SNDNKHZD.js} +1 -1
  59. package/dist/path-ZKO74XXC.js +7 -0
  60. package/dist/{peers-KRFXWRQ6.js → peers-W53WSDXG.js} +1 -1
  61. package/dist/{purge-XN2VSPZ2.js → purge-IKJISXEQ.js} +1 -1
  62. package/dist/resolution-BN35OXDS.js +11 -0
  63. package/dist/{secure-store-A4NGCNXV.js → secure-store-F75I54O5.js} +3 -3
  64. package/dist/{state-PVISYXRH.js → state-4ITLYMAU.js} +1 -1
  65. package/dist/{state-store-N6TFBFSP.js → state-store-ET3ADVY5.js} +3 -3
  66. package/dist/{storage-DDYQGLXA.js → storage-5EY6T7ON.js} +3 -3
  67. package/dist/{tier-stats-IZNW66NC.js → tier-stats-ZRQBV6G2.js} +4 -4
  68. package/dist/{trace-NJESSGH7.js → trace-IL2Y34EH.js} +1 -1
  69. package/dist/{tui-MGK2LYJY.js → tui-7KRDCMYK.js} +1 -1
  70. package/dist/{types-R4DO7AKM.js → types-7L34HYDW.js} +3 -3
  71. package/openclaw.plugin.json +17 -8
  72. package/package.json +8 -5
  73. package/scripts/faiss_index.py +756 -0
  74. package/scripts/faiss_requirements.txt +3 -0
  75. package/dist/capsule-export-IXVERCQG.js +0 -17
  76. package/dist/capsule-import-IA6VIOPQ.js +0 -16
  77. package/dist/chunk-3GUF7RQI.js +0 -559
  78. package/dist/chunk-7OQEPGQF.js +0 -533
  79. package/dist/chunk-DIZW6H5J.js +0 -136
  80. package/dist/chunk-FQRSVYY4.js +0 -110
  81. package/dist/chunk-GUSMRW4H.js +0 -12
  82. package/dist/chunk-MLKGABMK.js +0 -9
  83. package/dist/chunk-WPINX4MF.js +0 -380
  84. package/dist/contradiction-review-SVGBS3V5.js +0 -21
  85. package/dist/legacy-hook-compat-XQ7FP6FV.js +0 -35
  86. package/dist/path-JIEGNWFL.js +0 -7
  87. package/dist/resolution-YITUVUTH.js +0 -100
@@ -0,0 +1,272 @@
1
+ import {
2
+ readPair,
3
+ resolvePair
4
+ } from "./chunk-YJYZMLD5.js";
5
+ import {
6
+ log
7
+ } from "./chunk-UFU5GGGA.js";
8
+
9
+ // ../remnic-core/src/contradiction/resolution.ts
10
+ var VALID_VERBS = ["keep-a", "keep-b", "merge", "both-valid", "needs-more-context"];
11
+ function isValidResolutionVerb(value) {
12
+ return VALID_VERBS.includes(value);
13
+ }
14
+ async function executeResolution(memoryDir, storage, pairId, verb, options = {}) {
15
+ if (typeof verb !== "string" || !isValidResolutionVerb(verb)) {
16
+ throw new Error(`Invalid contradiction resolution verb: ${String(verb)}`);
17
+ }
18
+ const pair = readPair(memoryDir, pairId);
19
+ if (!pair) {
20
+ return { pairId, verb, affectedIds: [], message: `Pair ${pairId} not found` };
21
+ }
22
+ if (pair.namespace && !options.storageForNamespace) {
23
+ throw new Error(
24
+ "contradiction resolution requires storageForNamespace for namespaced pairs so callers resolve the correct namespace storage"
25
+ );
26
+ }
27
+ const resolutionStorage = options.storageForNamespace ? await options.storageForNamespace(pair.namespace) : storage;
28
+ if (pair.resolution && pair.resolution !== "needs-more-context") {
29
+ return { pairId, verb, affectedIds: [], message: `Pair already resolved with verb "${pair.resolution}"` };
30
+ }
31
+ const [idA, idB] = pair.memoryIds;
32
+ const affectedIds = [];
33
+ let message = "";
34
+ let supersedeFailed = false;
35
+ switch (verb) {
36
+ case "keep-a": {
37
+ const keepTarget = await validateKeepTarget(resolutionStorage, pairId, idA);
38
+ if (!keepTarget.ok) {
39
+ supersedeFailed = true;
40
+ message = keepTarget.message;
41
+ break;
42
+ }
43
+ const sourceB = await loadSourceSnapshot(resolutionStorage, idB);
44
+ const ok = sourceB ? await supersedeSafe(resolutionStorage, idB, idA, "contradiction-resolution:keep-a") : false;
45
+ if (ok) {
46
+ affectedIds.push(idB);
47
+ message = `Kept ${idA}, superseded ${idB}`;
48
+ } else {
49
+ supersedeFailed = true;
50
+ const rolledBack = sourceB ? await restoreMemorySnapshot(resolutionStorage, sourceB, "contradiction-resolution:keep-a-rollback") : false;
51
+ message = rolledBack ? `Supersede failed for ${idB}; restored ${idB} and did not resolve` : `Supersede failed for ${idB}; rollback incomplete for ${idB} and pair is not resolved`;
52
+ }
53
+ break;
54
+ }
55
+ case "keep-b": {
56
+ const keepTarget = await validateKeepTarget(resolutionStorage, pairId, idB);
57
+ if (!keepTarget.ok) {
58
+ supersedeFailed = true;
59
+ message = keepTarget.message;
60
+ break;
61
+ }
62
+ const sourceA = await loadSourceSnapshot(resolutionStorage, idA);
63
+ const ok = sourceA ? await supersedeSafe(resolutionStorage, idA, idB, "contradiction-resolution:keep-b") : false;
64
+ if (ok) {
65
+ affectedIds.push(idA);
66
+ message = `Kept ${idB}, superseded ${idA}`;
67
+ } else {
68
+ supersedeFailed = true;
69
+ const rolledBack = sourceA ? await restoreMemorySnapshot(resolutionStorage, sourceA, "contradiction-resolution:keep-b-rollback") : false;
70
+ message = rolledBack ? `Supersede failed for ${idA}; restored ${idA} and did not resolve` : `Supersede failed for ${idA}; rollback incomplete for ${idA} and pair is not resolved`;
71
+ }
72
+ break;
73
+ }
74
+ case "merge": {
75
+ const replacement = await prepareMergeReplacement(resolutionStorage, pairId, idA, idB, options);
76
+ if (!replacement.ok) {
77
+ supersedeFailed = true;
78
+ message = replacement.message;
79
+ break;
80
+ }
81
+ const okA = await supersedeSafe(resolutionStorage, idA, replacement.mergedId, "contradiction-resolution:merge");
82
+ if (!okA) {
83
+ supersedeFailed = true;
84
+ const rolledBackA = await restoreMemorySnapshot(resolutionStorage, replacement.sourceA);
85
+ message = rolledBackA ? `Merge failed for ${idA}; restored ${idA} and did not resolve` : `Merge failed for ${idA}; rollback incomplete for ${idA} and pair is not resolved`;
86
+ if (rolledBackA) {
87
+ await cleanupCreatedReplacement(resolutionStorage, replacement);
88
+ }
89
+ break;
90
+ }
91
+ const okB = await supersedeSafe(resolutionStorage, idB, replacement.mergedId, "contradiction-resolution:merge");
92
+ if (!okB) {
93
+ supersedeFailed = true;
94
+ const rolledBackA = await restoreMemorySnapshot(resolutionStorage, replacement.sourceA);
95
+ const rolledBackB = await restoreMemorySnapshot(resolutionStorage, replacement.sourceB);
96
+ message = rolledBackA && rolledBackB ? `Merge failed for ${idB}; restored ${idA} and ${idB} and did not resolve` : `Merge failed for ${idB}; rollback incomplete for ${[
97
+ rolledBackA ? void 0 : idA,
98
+ rolledBackB ? void 0 : idB
99
+ ].filter(Boolean).join(", ")} and pair is not resolved`;
100
+ if (rolledBackA && rolledBackB) {
101
+ await cleanupCreatedReplacement(resolutionStorage, replacement);
102
+ }
103
+ break;
104
+ }
105
+ affectedIds.push(idA, idB);
106
+ message = `Both memories superseded by merged ${replacement.mergedId}`;
107
+ break;
108
+ }
109
+ case "both-valid": {
110
+ message = "Pair marked as both-valid; cooldown applied";
111
+ break;
112
+ }
113
+ case "needs-more-context": {
114
+ message = "Deferred; no action taken, short cooldown applied";
115
+ break;
116
+ }
117
+ }
118
+ if (!supersedeFailed) {
119
+ resolvePair(memoryDir, pairId, verb);
120
+ }
121
+ log.info("[contradiction-resolution] pair=%s verb=%s affected=%d", pairId, verb, affectedIds.length);
122
+ return { pairId, verb, affectedIds, message };
123
+ }
124
+ async function prepareMergeReplacement(storage, pairId, idA, idB, options) {
125
+ const sourceA = await storage.getMemoryById(idA);
126
+ const sourceB = await storage.getMemoryById(idB);
127
+ if (!sourceA || !sourceB) {
128
+ return { ok: false, message: `Merge requires both source memories to exist; not resolving ${pairId}` };
129
+ }
130
+ const requestedMergedId = options.mergedMemoryId?.trim();
131
+ if (requestedMergedId) {
132
+ if (requestedMergedId === idA || requestedMergedId === idB) {
133
+ return { ok: false, message: "Merge replacement must be distinct from both source memories; not resolving" };
134
+ }
135
+ const replacement2 = await storage.getMemoryById(requestedMergedId);
136
+ if (!replacement2) {
137
+ return { ok: false, message: `Merged memory ${requestedMergedId} not found; not resolving` };
138
+ }
139
+ const replacementStatus = replacement2.frontmatter.status ?? "active";
140
+ if (replacementStatus !== "active") {
141
+ return {
142
+ ok: false,
143
+ message: `Merged memory ${requestedMergedId} is ${replacementStatus}; not resolving`
144
+ };
145
+ }
146
+ return { ok: true, mergedId: requestedMergedId, sourceA, sourceB, created: false };
147
+ }
148
+ const mergedContent = options.mergedContent;
149
+ if (typeof mergedContent !== "string" || mergedContent.trim().length === 0) {
150
+ return {
151
+ ok: false,
152
+ message: "Merge requires mergedMemoryId or mergedContent; no memories changed"
153
+ };
154
+ }
155
+ const category = options.mergedCategory ?? mergedMemoryCategory(sourceA, sourceB);
156
+ let mergedId;
157
+ try {
158
+ mergedId = await storage.writeMemory(category, mergedContent, {
159
+ actor: "contradiction-resolution",
160
+ confidence: Math.min(sourceA.frontmatter.confidence ?? 0.8, sourceB.frontmatter.confidence ?? 0.8),
161
+ tags: ["contradiction-resolution", "merge"],
162
+ source: "contradiction-resolution",
163
+ lineage: [idA, idB],
164
+ derivedFrom: [idA, idB],
165
+ derivedVia: "merge"
166
+ });
167
+ } catch (err) {
168
+ log.warn(
169
+ "[contradiction-resolution] merged memory creation failed for %s: %s",
170
+ pairId,
171
+ err instanceof Error ? err.message : err
172
+ );
173
+ return { ok: false, message: `Merged memory could not be created; not resolving ${pairId}` };
174
+ }
175
+ const replacement = await storage.getMemoryById(mergedId);
176
+ if (!replacement) {
177
+ await cleanupMemoryId(storage, mergedId);
178
+ return { ok: false, message: `Merged memory ${mergedId} could not be verified; not resolving` };
179
+ }
180
+ return { ok: true, mergedId, sourceA, sourceB, created: true };
181
+ }
182
+ function mergedMemoryCategory(sourceA, sourceB) {
183
+ return sourceA.frontmatter.category === sourceB.frontmatter.category ? sourceA.frontmatter.category : "fact";
184
+ }
185
+ async function validateKeepTarget(storage, pairId, keepId) {
186
+ const target = await loadSourceSnapshot(storage, keepId);
187
+ if (!target) {
188
+ return { ok: false, message: `Kept memory ${keepId} not found; not resolving ${pairId}` };
189
+ }
190
+ const status = target.frontmatter.status ?? "active";
191
+ if (status !== "active") {
192
+ return { ok: false, message: `Kept memory ${keepId} is ${status}; not resolving ${pairId}` };
193
+ }
194
+ return { ok: true };
195
+ }
196
+ async function loadSourceSnapshot(storage, memoryId) {
197
+ try {
198
+ return await storage.getMemoryById(memoryId);
199
+ } catch (err) {
200
+ log.warn(
201
+ "[contradiction-resolution] source snapshot failed for %s: %s",
202
+ memoryId,
203
+ err instanceof Error ? err.message : err
204
+ );
205
+ return null;
206
+ }
207
+ }
208
+ async function restoreMemorySnapshot(storage, memory, reasonCode = "contradiction-resolution:merge-rollback") {
209
+ try {
210
+ const current = await storage.getMemoryById(memory.frontmatter.id);
211
+ if (!current) return false;
212
+ const restoredFrontmatter = {
213
+ ...memory.frontmatter,
214
+ status: memory.frontmatter.status,
215
+ supersededBy: memory.frontmatter.supersededBy,
216
+ supersededAt: memory.frontmatter.supersededAt
217
+ };
218
+ return await storage.writeMemoryFrontmatter(current, restoredFrontmatter, {
219
+ actor: "contradiction-resolution",
220
+ reasonCode
221
+ });
222
+ } catch (err) {
223
+ log.warn(
224
+ "[contradiction-resolution] rollback failed for %s: %s",
225
+ memory.frontmatter.id,
226
+ err instanceof Error ? err.message : err
227
+ );
228
+ return false;
229
+ }
230
+ }
231
+ async function cleanupCreatedReplacement(storage, replacement) {
232
+ if (!replacement.created) return;
233
+ await cleanupMemoryId(storage, replacement.mergedId);
234
+ }
235
+ async function cleanupMemoryId(storage, memoryId) {
236
+ try {
237
+ const memory = await storage.getMemoryById(memoryId);
238
+ const invalidated = await storage.invalidateMemory(memoryId);
239
+ if (invalidated && memory?.frontmatter.category === "fact") {
240
+ await storage.removeFactContentHashesForMemories([memory]);
241
+ }
242
+ } catch (err) {
243
+ log.warn(
244
+ "[contradiction-resolution] cleanup failed for merged memory %s: %s",
245
+ memoryId,
246
+ err instanceof Error ? err.message : err
247
+ );
248
+ }
249
+ }
250
+ async function supersedeSafe(storage, oldId, newId, reason) {
251
+ try {
252
+ const result = await storage.supersedeMemory(oldId, newId, reason);
253
+ if (result === false) {
254
+ log.warn("[contradiction-resolution] supersede returned false for %s \u2192 %s", oldId, newId);
255
+ return false;
256
+ }
257
+ return true;
258
+ } catch (err) {
259
+ log.warn(
260
+ "[contradiction-resolution] supersede failed %s \u2192 %s: %s",
261
+ oldId,
262
+ newId,
263
+ err instanceof Error ? err.message : err
264
+ );
265
+ return false;
266
+ }
267
+ }
268
+
269
+ export {
270
+ isValidResolutionVerb,
271
+ executeResolution
272
+ };
@@ -0,0 +1,360 @@
1
+ // ../remnic-core/src/contradiction/contradiction-review.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import { createHash, randomUUID } from "crypto";
5
+ var VALID_RESOLUTION_VERBS = [
6
+ "keep-a",
7
+ "keep-b",
8
+ "merge",
9
+ "both-valid",
10
+ "needs-more-context"
11
+ ];
12
+ var NEEDS_MORE_CONTEXT_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
13
+ var UNSCOPED_MIGRATION_MARKER_PREFIX = ".unscoped-migrated-";
14
+ var UNSCOPED_MIGRATION_MARKER_SUFFIX = ".done";
15
+ function computePairId(memoryIdA, memoryIdB, namespace) {
16
+ const sorted = [memoryIdA, memoryIdB].sort();
17
+ const normalizedNamespace = namespace?.trim();
18
+ const scope = normalizedNamespace ? `ns:${normalizedNamespace}::` : "";
19
+ return createHash("sha256").update(`${scope}${sorted.join("::")}`).digest("hex").slice(0, 24);
20
+ }
21
+ function isDefaultReviewNamespace(defaultNamespace, requestedNamespace, resolvedNamespace) {
22
+ const requested = requestedNamespace?.trim();
23
+ return !requested || requested === defaultNamespace || resolvedNamespace === defaultNamespace;
24
+ }
25
+ function isTerminalResolution(resolution) {
26
+ return resolution === "keep-a" || resolution === "keep-b" || resolution === "merge";
27
+ }
28
+ function preservesDirectResolution(resolution) {
29
+ return isTerminalResolution(resolution) || resolution === "both-valid";
30
+ }
31
+ function isDormantReviewedPair(pair) {
32
+ return pair.verdict === "independent" || pair.resolution === "both-valid";
33
+ }
34
+ function reviewStateRank(pair, cooldownDays) {
35
+ if (isTerminalResolution(pair.resolution)) return 5;
36
+ if (pair.resolution === "both-valid") return 4;
37
+ if (isDeferralActive(pair)) return 3;
38
+ if (isDormantReviewedPair(pair)) {
39
+ if (cooldownDays === void 0) return 2;
40
+ return isCoolingDown(pair, cooldownDays) ? 2 : 0;
41
+ }
42
+ return 1;
43
+ }
44
+ function parseIsoMillis(value) {
45
+ if (!value) return null;
46
+ const millis = new Date(value).getTime();
47
+ return Number.isFinite(millis) ? millis : null;
48
+ }
49
+ function isDeferred(pair) {
50
+ return pair.resolution === "needs-more-context" || Boolean(pair.deferredUntil);
51
+ }
52
+ function deferralUntilMillis(pair) {
53
+ const deferredUntil = parseIsoMillis(pair.deferredUntil);
54
+ if (deferredUntil !== null) return deferredUntil;
55
+ if (pair.resolution === "needs-more-context") {
56
+ const lastReviewed = parseIsoMillis(pair.lastReviewedAt);
57
+ return lastReviewed === null ? null : lastReviewed + NEEDS_MORE_CONTEXT_COOLDOWN_MS;
58
+ }
59
+ return null;
60
+ }
61
+ function isDeferralActive(pair) {
62
+ const deferredUntil = deferralUntilMillis(pair);
63
+ return deferredUntil !== null && Date.now() < deferredUntil;
64
+ }
65
+ function reviewStateMillis(pair) {
66
+ return Math.max(
67
+ parseIsoMillis(pair.deferredUntil) ?? Number.NEGATIVE_INFINITY,
68
+ parseIsoMillis(pair.lastReviewedAt) ?? Number.NEGATIVE_INFINITY,
69
+ parseIsoMillis(pair.detectedAt) ?? Number.NEGATIVE_INFINITY
70
+ );
71
+ }
72
+ function mergeMigratedPair(existing, migrated, options) {
73
+ const existingRank = reviewStateRank(existing, options.cooldownDays);
74
+ const migratedRank = reviewStateRank(migrated, options.cooldownDays);
75
+ const selected = migratedRank > existingRank ? migrated : migratedRank < existingRank ? existing : reviewStateMillis(migrated) > reviewStateMillis(existing) ? migrated : existing;
76
+ return {
77
+ ...selected,
78
+ pairId: migrated.pairId,
79
+ namespace: migrated.namespace
80
+ };
81
+ }
82
+ function reviewDir(memoryDir) {
83
+ return path.join(memoryDir, ".review", "contradictions");
84
+ }
85
+ function migrationMarkerPath(memoryDir, namespace) {
86
+ const namespaceKey = createHash("sha256").update(namespace).digest("hex").slice(0, 16);
87
+ return path.join(reviewDir(memoryDir), `${UNSCOPED_MIGRATION_MARKER_PREFIX}${namespaceKey}${UNSCOPED_MIGRATION_MARKER_SUFFIX}`);
88
+ }
89
+ function clearUnscopedMigrationMarkers(memoryDir) {
90
+ const dir = reviewDir(memoryDir);
91
+ if (!fs.existsSync(dir)) return;
92
+ try {
93
+ for (const entry of fs.readdirSync(dir)) {
94
+ if (entry.startsWith(UNSCOPED_MIGRATION_MARKER_PREFIX) && entry.endsWith(UNSCOPED_MIGRATION_MARKER_SUFFIX)) {
95
+ fs.rmSync(path.join(dir, entry), { force: true });
96
+ }
97
+ }
98
+ } catch {
99
+ }
100
+ }
101
+ function pairPath(memoryDir, pairId) {
102
+ if (pairId.includes("/") || pairId.includes("\\") || pairId.includes("..")) {
103
+ throw new Error(`Invalid pairId: ${pairId}`);
104
+ }
105
+ return path.join(reviewDir(memoryDir), `${pairId}.json`);
106
+ }
107
+ function ensureDir(memoryDir) {
108
+ const dir = reviewDir(memoryDir);
109
+ if (!fs.existsSync(dir)) {
110
+ fs.mkdirSync(dir, { recursive: true });
111
+ }
112
+ }
113
+ function uniqueTempPath(filePath) {
114
+ return `${filePath}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
115
+ }
116
+ function writePairFile(filePath, pair) {
117
+ const tmpPath = uniqueTempPath(filePath);
118
+ try {
119
+ fs.writeFileSync(tmpPath, JSON.stringify(pair, null, 2), "utf-8");
120
+ fs.renameSync(tmpPath, filePath);
121
+ } catch (error) {
122
+ try {
123
+ fs.rmSync(tmpPath, { force: true });
124
+ } catch {
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+ function computeMemoryContentHash(content, category) {
130
+ const normalized = JSON.stringify({
131
+ content: content.trim(),
132
+ category: (category ?? "").trim()
133
+ });
134
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
135
+ }
136
+ function suppliedMemoryHashesChanged(existing, pair) {
137
+ if (!existing.memoryContentHashes || !pair.memoryContentHashes) return false;
138
+ return pair.memoryIds.some((memoryId) => {
139
+ const current = pair.memoryContentHashes?.[memoryId];
140
+ return typeof current === "string" && existing.memoryContentHashes?.[memoryId] !== current;
141
+ });
142
+ }
143
+ function writePair(memoryDir, pair, options = {}) {
144
+ ensureDir(memoryDir);
145
+ if (pair.namespace === void 0) {
146
+ clearUnscopedMigrationMarkers(memoryDir);
147
+ }
148
+ const pairId = computePairId(pair.memoryIds[0], pair.memoryIds[1], pair.namespace);
149
+ const existing = readPair(memoryDir, pairId);
150
+ if (isTerminalResolution(existing?.resolution)) {
151
+ return existing;
152
+ }
153
+ if (existing?.resolution === "both-valid" && options.cooldownDays === void 0) {
154
+ return existing;
155
+ }
156
+ const existingDeferralExpired = Boolean(existing && isDeferred(existing) && !isDeferralActive(existing));
157
+ if (existing && isDeferralActive(existing)) {
158
+ return existing;
159
+ }
160
+ const existingDormantCooldownActive = Boolean(
161
+ existing && isDormantReviewedPair(existing) && options.cooldownDays !== void 0 && isCoolingDown(existing, options.cooldownDays)
162
+ );
163
+ const dormantContentChanged = Boolean(
164
+ existing && existingDormantCooldownActive && suppliedMemoryHashesChanged(existing, pair)
165
+ );
166
+ const existingDormantExpired = Boolean(
167
+ existing && isDormantReviewedPair(existing) && options.cooldownDays !== void 0 && !existingDormantCooldownActive
168
+ );
169
+ if (existing && !existingDeferralExpired && (existingDormantCooldownActive && !dormantContentChanged || !existingDormantExpired && !dormantContentChanged && existing.confidence >= pair.confidence)) {
170
+ return existing;
171
+ }
172
+ const full = {
173
+ ...pair,
174
+ pairId,
175
+ lastReviewedAt: existingDeferralExpired || existingDormantExpired || dormantContentChanged ? pair.lastReviewedAt : existing?.lastReviewedAt ?? pair.lastReviewedAt,
176
+ resolution: void 0,
177
+ deferredUntil: existingDeferralExpired || existingDormantExpired || dormantContentChanged ? void 0 : existing?.deferredUntil
178
+ };
179
+ const filePath = pairPath(memoryDir, pairId);
180
+ writePairFile(filePath, full);
181
+ return full;
182
+ }
183
+ function writePairs(memoryDir, pairs, options = {}) {
184
+ const seen = /* @__PURE__ */ new Set();
185
+ const results = [];
186
+ for (const pair of pairs) {
187
+ const key = computePairId(pair.memoryIds[0], pair.memoryIds[1], pair.namespace);
188
+ if (seen.has(key)) continue;
189
+ seen.add(key);
190
+ results.push(writePair(memoryDir, pair, options));
191
+ }
192
+ return results;
193
+ }
194
+ function readPair(memoryDir, pairId) {
195
+ const filePath = pairPath(memoryDir, pairId);
196
+ try {
197
+ const raw = fs["re"+"ad"+"Fi"+"le"+"Sync"](filePath, "utf-8");
198
+ const parsed = JSON.parse(raw);
199
+ if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.memoryIds)) {
200
+ return parsed;
201
+ }
202
+ return null;
203
+ } catch {
204
+ return null;
205
+ }
206
+ }
207
+ function listPairs(memoryDir, options) {
208
+ const startTime = Date.now();
209
+ const dir = reviewDir(memoryDir);
210
+ const { filter = "all", namespace, includeUnscopedForNamespace = false, limit = 50 } = options ?? {};
211
+ const pairs = [];
212
+ let total = 0;
213
+ if (!fs.existsSync(dir)) {
214
+ return { pairs: [], total: 0, durationMs: Date.now() - startTime };
215
+ }
216
+ for (const entry of fs.readdirSync(dir)) {
217
+ if (!entry.endsWith(".json")) continue;
218
+ try {
219
+ const raw = fs["re"+"ad"+"Fi"+"le"+"Sync"](path.join(dir, entry), "utf-8");
220
+ const pair = JSON.parse(raw);
221
+ if (typeof pair !== "object" || pair === null) continue;
222
+ if (!Array.isArray(pair.memoryIds)) continue;
223
+ if (namespace && pair.namespace !== namespace && !(includeUnscopedForNamespace && pair.namespace === void 0)) continue;
224
+ if (filter === "unresolved") {
225
+ if (isTerminalResolution(pair.resolution)) continue;
226
+ if (isDeferralActive(pair)) continue;
227
+ if (pair.resolution === "both-valid") continue;
228
+ if (pair.verdict === "independent") continue;
229
+ } else if (filter !== "all" && pair.verdict !== filter) {
230
+ continue;
231
+ }
232
+ total++;
233
+ if (pairs.length < limit) pairs.push(pair);
234
+ } catch {
235
+ continue;
236
+ }
237
+ }
238
+ return { pairs, total, durationMs: Date.now() - startTime };
239
+ }
240
+ function migrateUnscopedPairsToNamespace(memoryDir, namespace, options = {}) {
241
+ const resolvedNamespace = namespace.trim();
242
+ if (!resolvedNamespace) return 0;
243
+ const dir = reviewDir(memoryDir);
244
+ if (!fs.existsSync(dir)) return 0;
245
+ const markerPath = migrationMarkerPath(memoryDir, resolvedNamespace);
246
+ if (fs.existsSync(markerPath)) return 0;
247
+ let migrated = 0;
248
+ let hadMigrationFailure = false;
249
+ for (const entry of fs.readdirSync(dir)) {
250
+ if (!entry.endsWith(".json")) continue;
251
+ const filePath = path.join(dir, entry);
252
+ try {
253
+ const raw = fs["re"+"ad"+"Fi"+"le"+"Sync"](filePath, "utf-8");
254
+ const pair = JSON.parse(raw);
255
+ if (typeof pair !== "object" || pair === null) continue;
256
+ if (!Array.isArray(pair.memoryIds)) continue;
257
+ if (pair.namespace !== void 0) continue;
258
+ const pairId = computePairId(pair.memoryIds[0], pair.memoryIds[1], resolvedNamespace);
259
+ const migratedPair = { ...pair, namespace: resolvedNamespace, pairId };
260
+ const targetPath = pairPath(memoryDir, pairId);
261
+ try {
262
+ if (targetPath === filePath) {
263
+ writePairFile(filePath, migratedPair);
264
+ } else if (!fs.existsSync(targetPath)) {
265
+ writePairFile(targetPath, migratedPair);
266
+ fs.rmSync(filePath, { force: true });
267
+ } else {
268
+ const existing = readPair(memoryDir, pairId);
269
+ writePairFile(targetPath, existing ? mergeMigratedPair(existing, migratedPair, options) : migratedPair);
270
+ fs.rmSync(filePath, { force: true });
271
+ }
272
+ migrated += 1;
273
+ } catch {
274
+ hadMigrationFailure = true;
275
+ continue;
276
+ }
277
+ } catch {
278
+ continue;
279
+ }
280
+ }
281
+ if (!hadMigrationFailure) {
282
+ try {
283
+ fs.writeFileSync(markerPath, `${(/* @__PURE__ */ new Date()).toISOString()}
284
+ `, { encoding: "utf-8", flag: "wx" });
285
+ } catch {
286
+ }
287
+ }
288
+ return migrated;
289
+ }
290
+ function isCoolingDown(pair, cooldownDays) {
291
+ if (cooldownDays <= 0) return false;
292
+ const deferredUntil = deferralUntilMillis(pair);
293
+ if (deferredUntil !== null) {
294
+ return Date.now() < deferredUntil;
295
+ }
296
+ if (!pair.lastReviewedAt) return false;
297
+ const lastReviewed = parseIsoMillis(pair.lastReviewedAt);
298
+ if (lastReviewed === null) return false;
299
+ const cooldownMs = cooldownDays * 24 * 60 * 60 * 1e3;
300
+ return Date.now() < lastReviewed + cooldownMs;
301
+ }
302
+ function resolvePair(memoryDir, pairId, verb) {
303
+ if (typeof verb !== "string" || !VALID_RESOLUTION_VERBS.includes(verb)) {
304
+ throw new Error(`Invalid contradiction resolution verb: ${String(verb)}`);
305
+ }
306
+ if (verb === "needs-more-context") {
307
+ return deferPair(memoryDir, pairId);
308
+ }
309
+ const existing = readPair(memoryDir, pairId);
310
+ if (!existing) return null;
311
+ if (preservesDirectResolution(existing.resolution)) return existing;
312
+ const updated = {
313
+ ...existing,
314
+ lastReviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
315
+ resolution: verb,
316
+ deferredUntil: void 0
317
+ };
318
+ const filePath = pairPath(memoryDir, pairId);
319
+ writePairFile(filePath, updated);
320
+ return updated;
321
+ }
322
+ function deferPair(memoryDir, pairId, deferredUntil = new Date(Date.now() + NEEDS_MORE_CONTEXT_COOLDOWN_MS).toISOString()) {
323
+ const existing = readPair(memoryDir, pairId);
324
+ if (!existing) return null;
325
+ if (preservesDirectResolution(existing.resolution)) return existing;
326
+ const updated = {
327
+ ...existing,
328
+ lastReviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
329
+ resolution: void 0,
330
+ deferredUntil
331
+ };
332
+ const filePath = pairPath(memoryDir, pairId);
333
+ writePairFile(filePath, updated);
334
+ return updated;
335
+ }
336
+ function memoryHashesChanged(_memoryDir, pair, getCurrentHash) {
337
+ if (!pair.memoryContentHashes) return false;
338
+ for (const memoryId of pair.memoryIds) {
339
+ const previousHash = pair.memoryContentHashes[memoryId];
340
+ if (typeof previousHash !== "string") continue;
341
+ const currentHash = getCurrentHash(memoryId);
342
+ if (currentHash !== null && currentHash !== previousHash) return true;
343
+ }
344
+ return false;
345
+ }
346
+
347
+ export {
348
+ computePairId,
349
+ isDefaultReviewNamespace,
350
+ computeMemoryContentHash,
351
+ writePair,
352
+ writePairs,
353
+ readPair,
354
+ listPairs,
355
+ migrateUnscopedPairsToNamespace,
356
+ isCoolingDown,
357
+ resolvePair,
358
+ deferPair,
359
+ memoryHashesChanged
360
+ };