@gamaze/hicortex 0.20.3 → 0.20.5

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 (44) hide show
  1. package/README.md +20 -1
  2. package/assets/dashboard.html +318 -8
  3. package/assets/identity.html +349 -8
  4. package/assets/viz.html +352 -8
  5. package/dist/backup.d.ts +12 -8
  6. package/dist/backup.js +13 -9
  7. package/dist/claude-desktop.d.ts +138 -0
  8. package/dist/claude-desktop.js +251 -0
  9. package/dist/cli.d.ts +7 -2
  10. package/dist/cli.js +84 -3
  11. package/dist/consolidate.d.ts +8 -1
  12. package/dist/consolidate.js +82 -2
  13. package/dist/dashboard.d.ts +17 -0
  14. package/dist/dashboard.js +32 -0
  15. package/dist/db.js +36 -0
  16. package/dist/dedup.d.ts +157 -25
  17. package/dist/dedup.js +376 -83
  18. package/dist/domain-classify.js +4 -2
  19. package/dist/index.js +7 -7
  20. package/dist/init.d.ts +4 -1
  21. package/dist/init.js +103 -1
  22. package/dist/llm.d.ts +19 -13
  23. package/dist/llm.js +25 -14
  24. package/dist/mcp-server.d.ts +6 -0
  25. package/dist/mcp-server.js +94 -13
  26. package/dist/mcp-stdio.d.ts +138 -0
  27. package/dist/mcp-stdio.js +313 -0
  28. package/dist/memory-instructions.d.ts +18 -0
  29. package/dist/memory-instructions.js +39 -2
  30. package/dist/nightly.js +14 -1
  31. package/dist/reconsolidation.d.ts +323 -0
  32. package/dist/reconsolidation.js +1226 -0
  33. package/dist/retrieval.d.ts +14 -0
  34. package/dist/retrieval.js +41 -3
  35. package/dist/state.d.ts +23 -1
  36. package/dist/storage.d.ts +25 -0
  37. package/dist/storage.js +49 -7
  38. package/dist/type-classify.js +4 -2
  39. package/dist/types.d.ts +188 -0
  40. package/hermes-plugin/hicortex/provider.py +29 -17
  41. package/opencode-plugin/hicortex/index.ts +7 -7
  42. package/package.json +4 -2
  43. package/pi-extension/hicortex/index.ts +7 -7
  44. package/server.json +44 -0
package/dist/dedup.d.ts CHANGED
@@ -1,52 +1,101 @@
1
1
  /**
2
- * `hicortex dedup` — cluster + merge near-duplicate memories (issue #100).
2
+ * `hicortex dedup` + the nightly deterministic merge zone (issues #100, #392).
3
3
  *
4
4
  * Corpus-quality companion to `hicortex relink`/`classify-domains`: instead of
5
- * discovering NEW structure, this command collapses memories that are
6
- * near-identical (top-10 KNN cosine >= dedupMergeThreshold, default 0.92,
7
- * union-find clustered — same math as the #191 D1 duplicate-rate audit; see
8
- * cluster.ts). Default is a DRY RUN: report only, zero writes. `--apply`
9
- * executes the merge.
5
+ * discovering NEW structure, this collapses memories that are near-identical
6
+ * (top-10 KNN cosine >= threshold, default 0.92, union-find clustered — same
7
+ * math as the #191 D1 duplicate-rate audit; see cluster.ts). Two surfaces,
8
+ * ONE core:
10
9
  *
11
- * Per cluster:
10
+ * - `runDedup` — the manual CLI. Default is a DRY RUN: report only, zero
11
+ * writes. `--apply` executes the merge. Threshold resolution:
12
+ * `--threshold` > config `dedupAutoMergeThreshold` > legacy config
13
+ * `dedupMergeThreshold` > 0.92.
14
+ * - `runDeterministicMergeZone` (#392) — the nightly's LLM-free merge zone:
15
+ * pairs at/above the ceiling merge deterministically, ZERO LLM calls, under
16
+ * its own pacing cap (`dedupNightlyMaxMerges`). Called from the
17
+ * reconsolidation stage (and from the quiet-night skip path in
18
+ * consolidate.ts) so one stage report covers all resolution work.
19
+ *
20
+ * Per cluster (shared `planDedup`/`mergeCluster` core — no forks):
12
21
  * - Canonical = highest access_count (tie: oldest created_at, then
13
22
  * lexicographically smallest id — fully deterministic for audit).
14
23
  * - Losers' links are re-pointed onto the canonical (a link that would
15
24
  * become a self-link, or one whose (canonical, target) ordered pair
16
25
  * ALREADY holds an edge, is skipped rather than overwritten — see
17
- * planLinkRepoints for why `relationship` cannot be part of that guard).
26
+ * planLinkRepoints for why `relationship` cannot be part of that guard);
27
+ * the losers' own link rows are then deleted (previously cascade-deleted
28
+ * with the row).
18
29
  * - canonical.access_count/shown_count = summed across the cluster;
19
30
  * last_accessed = max; base_strength = max.
20
31
  * - Tags are UNIONED onto the canonical (weights NULL — the next nightly's
21
32
  * reconsolidation pass recomputes weights and the derived primary from
22
- * the merged tag set).
23
- * - A `dedup_log` row is written per loser BEFORE it is deleted — audit
24
- * trail AND the safety net /distill consults (mcp-server.ts) so a
25
- * deleted loser's `source_session` marker still blocks a re-ingest.
26
- * - Losers are deleted via storage.deleteMemory (cascades links/tags/
27
- * vectors/FTS).
33
+ * the merged tag set); the losers' tag rows are cleared and their domain
34
+ * set NULL — a loser must not count in moduleIndex/tag recomputes.
35
+ * - A `dedup_log` row is written per loser — audit trail AND the safety net
36
+ * /distill consults (mcp-server.ts) so an absorbed loser's
37
+ * `source_session` marker still blocks a re-ingest.
38
+ * - Losers are ABSORBED (storage.absorbMemory), not deleted (#392): status
39
+ * 'absorbed', vector + FTS rows dropped, plain row retained — invisible
40
+ * to recall, fetchable by id as evidence. Same vocabulary as the
41
+ * reconsolidation rewrite path. Merges are NOT history-rollback-able —
42
+ * dedup_log (loser_id → canonical_id) + the retained loser row is the
43
+ * record.
28
44
  *
29
- * A cluster whose members disagree on project, privacy, or source_agent is
30
- * SKIPPED entirely and listed for manual review — no --force in this release.
45
+ * A cluster whose members disagree on project or source_agent is SKIPPED
46
+ * entirely and listed for manual review — no --force in this release.
31
47
  *
32
- * Safety rails on --apply:
48
+ * Safety rails when applying (CLI and zone alike):
33
49
  * - A full DB backup (SQLite backup API) is taken FIRST, to
34
- * ~/.hicortex/backups/pre-dedup-<ISO>.db. Abort (no merges attempted) if
35
- * the backup fails.
50
+ * <state>/backups/pre-dedup-<ISO>.db, pruned to `backupRetention` newest
51
+ * (pattern-scoped: full `hicortex-*.tar.gz` artifacts keep their own
52
+ * count). The CLI aborts (no merges attempted) if the backup fails; the
53
+ * nightly zone is fail-soft (backup_failed flag, zero merges).
36
54
  * - The existing single-flight capture lock (capture.ts) is held for the
37
55
  * duration of the merge so a concurrent nightly/capture run can't race
38
- * the dedup_log bookkeeping the merge relies on.
56
+ * the dedup_log bookkeeping the merge relies on. The CLI fails fast on a
57
+ * busy lock; the zone reports lock_busy and merges nothing.
39
58
  *
40
59
  * Server-mode only (needs the local DB), like relink/classify-domains.
41
60
  */
42
61
  import type Database from "better-sqlite3";
43
62
  import { type ClusterMetadataMismatch } from "./cluster.js";
44
63
  import { acquireCaptureLock } from "./capture.js";
64
+ import type { DeterministicMergeZoneReport } from "./types.js";
45
65
  /**
46
66
  * Default merge threshold. Measured on the #191 mechanical audit corpus:
47
67
  * 89 clusters / 110 excess rows at 0.92 (data/audit-20260729/eval-report.md).
68
+ * #392: also the default `dedupAutoMergeThreshold` — the deterministic/LLM
69
+ * boundary of the unified resolution pass.
48
70
  */
49
71
  export declare const DEFAULT_DEDUP_MERGE_THRESHOLD = 0.92;
72
+ /**
73
+ * Default pacing cap on merge OPERATIONS per nightly run (#392): the zone's
74
+ * clusters and the stage's judged pair merges count against ONE cap. Bounds a
75
+ * misbehaving-distiller burst; a large backlog drains over a few nights.
76
+ * `0` disables the merge machinery entirely.
77
+ */
78
+ export declare const DEFAULT_DEDUP_NIGHTLY_MAX_MERGES = 250;
79
+ /**
80
+ * Row shape read from `memories` for merge decisions — a superset of the
81
+ * fields the Memory type declares (shown_count isn't on that interface yet).
82
+ * `status` rides along so judged-pair merges can defensively drop rows that
83
+ * were absorbed between verdict and apply.
84
+ */
85
+ interface DedupMemberRow {
86
+ id: string;
87
+ content: string;
88
+ access_count: number;
89
+ shown_count: number | null;
90
+ last_accessed: string | null;
91
+ base_strength: number;
92
+ created_at: string;
93
+ project: string | null;
94
+ privacy: string | null;
95
+ source_agent: string;
96
+ source_session: string | null;
97
+ status: string | null;
98
+ }
50
99
  export interface DedupClusterPlan {
51
100
  size: number;
52
101
  canonicalId: string;
@@ -94,8 +143,8 @@ export interface DedupReport {
94
143
  linksSkippedExisting: number;
95
144
  /** --apply only: clusters actually merged. */
96
145
  merged?: number;
97
- /** --apply only: loser rows deleted. */
98
- losersDeleted?: number;
146
+ /** --apply only: loser rows absorbed (hidden from recall, kept as evidence). */
147
+ losersAbsorbed?: number;
99
148
  /** --apply only: clusters that errored mid-merge (rolled back; left for a re-run). */
100
149
  failedClusters?: number;
101
150
  /** --apply only: path to the pre-merge backup. */
@@ -104,7 +153,7 @@ export interface DedupReport {
104
153
  export interface DedupOptions {
105
154
  /** Execute the merge. Default false = dry run (report only, zero writes). */
106
155
  apply?: boolean;
107
- /** Override config.dedupMergeThreshold for one run. */
156
+ /** Override the configured threshold for one run. */
108
157
  threshold?: number;
109
158
  /** DB path override (tests / manual snapshot verification). Defaults to resolveDbPath(). */
110
159
  dbPath?: string;
@@ -116,7 +165,7 @@ export interface DedupOptions {
116
165
  acquireLock?: typeof acquireCaptureLock;
117
166
  /**
118
167
  * Test-only failure injection: called once per cluster merge, after the
119
- * link/tag/counter writes but before the audit-log + delete step. Throwing
168
+ * link/tag/counter writes but before the audit-log + absorb step. Throwing
120
169
  * here proves a mid-merge error rolls the WHOLE cluster's writes back
121
170
  * (better-sqlite3 transaction semantics) rather than leaving a half-merged
122
171
  * cluster. Never set in production.
@@ -135,10 +184,93 @@ export interface LinkRepointPlan {
135
184
  skippedSelfLink: number;
136
185
  skippedExisting: number;
137
186
  }
187
+ /** One cluster's execution plan from `planDedup` — canonical, losers, members. */
188
+ export interface DedupMergePlan {
189
+ canonical: DedupMemberRow;
190
+ losers: DedupMemberRow[];
191
+ /** All member rows, oldest first (CLI preview lines derive from this). */
192
+ membersOldestFirst: DedupMemberRow[];
193
+ }
194
+ export interface PlanDedupResult {
195
+ /** Every cluster found at the threshold (mergeable + mismatch-skipped). */
196
+ clusterCount: number;
197
+ /** Clusters that passed the metadata rails, in discovery order. */
198
+ mergePlans: DedupMergePlan[];
199
+ mismatchSkipped: DedupMismatchCluster[];
200
+ }
201
+ /**
202
+ * Discovery + merge planning at a cosine threshold (read-only — no writes).
203
+ * The ONE clustering core shared by the manual CLI (`runDedup`) and the
204
+ * nightly deterministic merge zone (`runDeterministicMergeZone`): KNN edges
205
+ * (k=10) → union-find clusters → member load → metadata-rail classification →
206
+ * canonical pick. Never forked.
207
+ */
208
+ export declare function planDedup(db: Database.Database, threshold: number): PlanDedupResult;
209
+ export type MergeMemoryIdsResult = {
210
+ ok: true;
211
+ canonicalId: string;
212
+ loserIds: string[];
213
+ linksRepointed: number;
214
+ } | {
215
+ ok: false;
216
+ reason: "metadata_mismatch" | "no_members";
217
+ };
218
+ /**
219
+ * Merge an explicit set of memories (the judged-pair phase of #392: the
220
+ * reconsolidation stage queues verdict-confirmed pairs and applies them
221
+ * through THIS function so the merge math stays single-definition). Loads the
222
+ * LIVE rows at apply time — members that vanished or were absorbed between
223
+ * verdict and apply are dropped defensively; a metadata disagreement refuses
224
+ * the merge (both memories stay live). One transaction for the whole set.
225
+ */
226
+ export declare function mergeMemoryIds(db: Database.Database, ids: string[]): MergeMemoryIdsResult;
227
+ /**
228
+ * Take a pre-merge DB backup to <stateDir>/backups/pre-dedup-<ISO>.db and
229
+ * prune older pre-dedup backups to `backupRetention` (config, default 7;
230
+ * pattern-scoped so full `hicortex-*.tar.gz` artifacts keep their own,
231
+ * independent retention count). THROWS on failure — the callers own the
232
+ * policy: the CLI aborts, the nightly zone is fail-soft. Returns the path.
233
+ */
234
+ export declare function takePreDedupBackup(db: Database.Database, stateDir: string, config?: Record<string, unknown> | null): Promise<string>;
235
+ export interface DeterministicMergeZoneOptions {
236
+ /** State dir (lock + backup + band-stats persistence). Defaults to ~/.hicortex. */
237
+ stateDir?: string;
238
+ /** Cosine ceiling; validated (0,1] → DEFAULT_DEDUP_MERGE_THRESHOLD. */
239
+ threshold?: number;
240
+ /**
241
+ * Pacing cap on merge operations this run; validated >= 0 →
242
+ * DEFAULT_DEDUP_NIGHTLY_MAX_MERGES. `0` disables the machinery entirely
243
+ * (discovery skipped, zeroed report).
244
+ */
245
+ maxMerges?: number;
246
+ /** Discovery + bounded preview only — zero writes, no lock, no backup. */
247
+ dryRun?: boolean;
248
+ /** Config override (backupRetention) — defaults to reading stateDir/config.json. */
249
+ config?: Record<string, unknown> | null;
250
+ /** Capture-lock acquirer override (tests). Defaults to the real capture.ts lock. */
251
+ acquireLock?: typeof acquireCaptureLock;
252
+ }
253
+ /**
254
+ * The >= dedupAutoMergeThreshold band of the unified resolution pass (#392):
255
+ * planDedup discovery + per-cluster mergeCluster — LLM-free, budget-free, so
256
+ * an LLM-less night still drains duplicates. Its own short capture-lock
257
+ * window and pre-merge backup; fail-soft on a busy lock (lock_busy) and on a
258
+ * backup failure (backup_failed) — zero merges either way, never a throw.
259
+ *
260
+ * Also persists the deterministic band's cumulative statistics to state.json
261
+ * `resolutionBandStats` (label `>=threshold`; losers count as merge verdicts
262
+ * at confidence 1.0, mismatch clusters as metadata_skipped) — skipped
263
+ * entirely on dry-run. Called from the reconsolidation stage (main path) and
264
+ * from runConsolidation's quiet-night skip path — exactly one of the two per
265
+ * run.
266
+ */
267
+ export declare function runDeterministicMergeZone(db: Database.Database, opts?: DeterministicMergeZoneOptions): Promise<DeterministicMergeZoneReport>;
138
268
  /**
139
269
  * Run `hicortex dedup`. Dry run by default (options.apply falsy) — discovery
140
270
  * + merge planning only, zero writes. `options.apply` executes: backup, then
141
- * one transaction per cluster.
271
+ * one transaction per cluster. Fails fast (throws) on a busy capture lock or
272
+ * a failed backup — a deliberate manual command should be retried by the
273
+ * operator, not silently deferred (the nightly zone is the fail-soft twin).
142
274
  */
143
275
  export declare function runDedup(options?: DedupOptions): Promise<DedupReport>;
144
276
  /** Escape SQL LIKE wildcards — session ids (e.g. Hermes) can contain "_"/"%". */