/** * Store-level deduplication. Scans for near-duplicate memories by content * Jaccard overlap, keeps the stronger copy (by strength + retrieval count), * removes the rest. * * Extracted from cli.ts in Episode A (v1.11.3) so `api.sleep` can dedupe * during the consolidation pipeline without violating the cli -> api * dependency direction. `cmdDedup` in cli.ts continues to import and use * this function unchanged. * * Survivor selection is a total order as of v1.26.3 * (docs/plans/2026-07-16-dedupe-survivor-determinism.md): strength bucket * desc -> retrieval_count desc -> compareEntryIdentity (content asc -> * layer rank -> tags -> source -> id asc; the metadata keys arrived in * v1.38.1, docs/plans/2026-09-04-dedupe-survivor-metadata.md). Previously * the strength/retrieval-count comparator could tie exactly with no terminal * key, so the survivor fell to load order (arrival-order-dependent); see * `strengthBucket` below for the bucket encoding. As of the tenant-partition * fix (docs/plans/2026-08-15-dedupe-tenant-partition.md) the order is scoped * WITHIN each tenant group; cross-tenant pairs are never compared. Raw and * superseded rows are not candidates at all (v1.38.1). */ export interface DedupPair { kept: string; keptContent: string; keptLayer: string; keptStrength: number; removed: string; removedContent: string; removedLayer: string; removedStrength: number; similarity: number; } /** Result of `deduplicateStore`: how many entries were removed, and the kept/removed pairs. */ export interface DedupResult { removed: number; pairs: DedupPair[]; } /** * Quantize a strength value into an integer "bucket" for tie comparison. * * Encodes the historical 0.01 epsilon transitively: two strengths compare * equal here iff they round to the same multiple of `STRENGTH_TIE_EPSILON`, * which (unlike a raw `Math.abs(a - b) > epsilon` check) is a genuine * equivalence relation — no more "A ties B, B ties C, but A beats C" * (see the file-level history note above). * * Non-finite input (`NaN`, `+/-Infinity`) maps to bucket `0` rather than * propagating: a NaN bucket would make the sort comparator return NaN, * silently reintroducing the non-total-order class this fix exists to kill. * (`null`/`undefined` already default to strength `0` via `?? 0`, same as * before this change.) * * Bucket-edge nuance: two strengths straddling a bucket edge (e.g. 0.0049 vs * 0.0051) now compare as different, where the old raw-epsilon check called * them tied. The flip always favors the not-weaker entry, and the OLD * behavior at such pairs was itself order/engine-dependent (the defect this * fix exists to kill) — so there is no stable prior behavior being broken. */ export declare function strengthBucket(strength: number | null | undefined): number; /** * Scan the store for near-duplicate memories and remove the weaker copy. * Two memories are duplicates if their content has > threshold Jaccard * overlap AND they belong to the same tenant: the scan is partitioned by * tenantId, so byte-identical content in two tenants is never a duplicate * pair (the tenant boundary is an isolation boundary; cross-tenant removal * was the v1.32.0 known-issue data-loss bug). * Keeps the one with higher strength (or more retrievals if tied). */ export declare function deduplicateStore(hippoRoot: string, options?: { threshold?: number; dryRun?: boolean; }): DedupResult; //# sourceMappingURL=dedupe.d.ts.map