@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
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Reconsolidation (#384, #392) — the store resolves its own corrections, and
3
+ * THE unified resolution stage.
4
+ *
5
+ * Nightly consolidation stage (runs as Stage 3.8, after supersession, before
6
+ * decay/prune) that detects memories which correct, retract, supersede, or
7
+ * DUPLICATE older ones; REWRITES corrected facts in place (absorbing
8
+ * transition-only trigger memories), MERGES confirmed duplicates via the
9
+ * dedup core (absorbing the loser), and marks everything else. Companions to
10
+ * the stage:
11
+ * - explicit write-time marking (`corrects`/`supersedes` at /ingest +
12
+ * `hicortex_ingest`) — deterministic link + status, zero LLM;
13
+ * - `memory_history` audit + the `hicortex history` CLI (listing + rollback).
14
+ *
15
+ * #392 — one zone system, ONE verdict per pair: below `correctionMinSimilarity`
16
+ * (floor, 0.75) pairs are not candidates; in [floor, `dedupAutoMergeThreshold`)
17
+ * (ceiling, 0.92) each unlinked pair gets ONE verdict call whose action is
18
+ * `merge` | `corrects` | `supersedes` | `none`; at/above the ceiling the
19
+ * deterministic merge zone (dedup.ts runDeterministicMergeZone — LLM-free,
20
+ * budget-free) owns the pair. The merge disposition reuses the dedup core's
21
+ * execution (canonical pick, link re-point, dedup_log, metadata rails); a
22
+ * merge verdict below `correctionRewriteMinConfidence` keeps both memories.
23
+ *
24
+ * Status vocabulary (code-defined, extensible — deliberately NOT config):
25
+ * NULL/'active' default | 'superseded' + 'retracted' demote in ranking |
26
+ * 'corrected' = rewritten, never demotes (demoting it would bury the
27
+ * correction — the exact failure this stage fixes) | 'absorbed' = invisible
28
+ * to recall (no vector row, no FTS row; plain row + link kept as evidence,
29
+ * session lineage, rollback reference). Merge losers share 'absorbed'
30
+ * (storage.absorbMemory is the one primitive) — the only difference is the
31
+ * audit trail: rewrites roll back via memory_history; merges recover via
32
+ * dedup_log + the retained loser row (NOT history-rollback-able).
33
+ *
34
+ * This module deliberately does NOT import consolidate.ts (which imports this
35
+ * module to wire the stage) — the budget is consumed through the structural
36
+ * StageBudget interface below, which BudgetTracker satisfies. dedup.ts is
37
+ * imported (never the reverse) for the merge core.
38
+ */
39
+ import type Database from "better-sqlite3";
40
+ import type { LlmClient, LlmUsage } from "./llm.js";
41
+ import type { ConsolidationReport } from "./types.js";
42
+ import type { EmbedFn } from "./retrieval.js";
43
+ import { acquireCaptureLock } from "./capture.js";
44
+ /** Stage label used for every budget.use()/recordUsage() call (#384). */
45
+ export declare const RECONSOLIDATION_STAGE_LABEL = "reconsolidation";
46
+ /**
47
+ * Default minimum COSINE similarity for a correction candidate pair. Lower
48
+ * than the supersession stage's 0.80 on purpose: a retraction often rides
49
+ * inside an otherwise unrelated memory (the field failure that opened this
50
+ * issue), so the neighborhood gate must be a touch wider while the LLM
51
+ * verdict + confidence gate carry the precision load.
52
+ */
53
+ export declare const DEFAULT_CORRECTION_MIN_SIMILARITY = 0.75;
54
+ /**
55
+ * Default minimum verdict confidence for the REWRITE fork. Below this a
56
+ * `corrects` verdict degrades to mark-only — a weak mark is recoverable, a
57
+ * weak rewrite is corruption.
58
+ */
59
+ export declare const DEFAULT_CORRECTION_REWRITE_MIN_CONFIDENCE = 0.8;
60
+ /** Head of the old content quoted in the provenance footer. */
61
+ export declare const FOOTER_HEAD_MAX_CHARS = 160;
62
+ /** The code-defined status vocabulary (see module doc). Not user-configurable. */
63
+ export type MemoryStatus = "superseded" | "retracted" | "corrected" | "absorbed";
64
+ /**
65
+ * Statuses that demote a memory's ranking score (retrieval.ts findDemotedIds).
66
+ * 'corrected' is deliberately absent — see module doc.
67
+ */
68
+ export declare const DEMOTED_STATUSES: readonly [MemoryStatus, MemoryStatus];
69
+ /** structural subset of consolidate.BudgetTracker (avoids an import cycle). */
70
+ export interface StageBudget {
71
+ readonly exhausted: boolean;
72
+ use(stage: string, count?: number): boolean;
73
+ recordUsage(stage: string, usage: LlmUsage | undefined): void;
74
+ }
75
+ export interface ReconsolidationOptions {
76
+ /** correctionMinSimilarity (config; default 0.75). Invalid → default. */
77
+ minSimilarity?: number;
78
+ /** correctionRewriteMinConfidence (config; default 0.80). Invalid → default. */
79
+ rewriteMinConfidence?: number;
80
+ /**
81
+ * dedupAutoMergeThreshold (config; default 0.92; legacy dedupMergeThreshold
82
+ * honored by nightly.ts when the new key is absent). The deterministic/LLM
83
+ * boundary of the unified resolution pass (#392): pairs at/above it merge
84
+ * via the LLM-free zone, pairs in [floor, ceiling) get the verdict.
85
+ * Invalid → default.
86
+ */
87
+ autoMergeThreshold?: number;
88
+ /**
89
+ * dedupNightlyMaxMerges (config; default 250; 0 disables the merge
90
+ * machinery). Counts merge OPERATIONS per run — zone clusters + judged
91
+ * pair merges against ONE cap. Invalid → default.
92
+ */
93
+ maxMerges?: number;
94
+ /**
95
+ * Capture-lock acquirer override (tests) — the deterministic zone and the
96
+ * judged-merge phase each hold a short lock window. Defaults to the real
97
+ * capture.ts lock. DedupOptions.acquireLock pattern.
98
+ */
99
+ acquireLock?: typeof acquireCaptureLock;
100
+ }
101
+ /** The 14-field stage report (typed once, in ConsolidationReport). */
102
+ export type ReconsolidationStageResult = NonNullable<ConsolidationReport["stages"]["reconsolidation"]>;
103
+ /**
104
+ * True when a memory is REWRITE-ELIGIBLE — a fact-shaped target. The fork is
105
+ * keyed on the existing taxonomy the code already trusts (facts are
106
+ * rewritten; decisions/plans/experiences are history, marked only).
107
+ */
108
+ export declare function isFactShapedTarget(mem: {
109
+ memory_type: string;
110
+ content: string;
111
+ }): boolean;
112
+ /**
113
+ * The unified resolution verdict (#392): ONE call per unlinked pair decides
114
+ * how the newer memory relates to the older — merge (same underlying
115
+ * fact/verdict, differing in wording/qualifiers), corrects, supersedes, or
116
+ * none (related but distinct).
117
+ */
118
+ export type ResolutionAction = "merge" | "corrects" | "supersedes" | "none";
119
+ /** Build the constrained pair-verdict prompt (1500-char truncation, supersession precedent). */
120
+ export declare function buildCorrectionVerdictPrompt(oldContent: string, newContent: string): string;
121
+ export interface CorrectionVerdict {
122
+ action: ResolutionAction;
123
+ confidence: number;
124
+ }
125
+ /**
126
+ * Parse the pair verdict. Null on anything unparseable, unknown action, or an
127
+ * out-of-range/missing confidence — the caller counts skipped_infra and moves
128
+ * on (same discipline as parseSupersessionReply: never mis-judge on ambiguity).
129
+ */
130
+ export declare function parseCorrectionVerdict(reply: string): CorrectionVerdict | null;
131
+ export interface RewriteTriggerDisposition {
132
+ id: string;
133
+ disposition: "absorb" | "keep";
134
+ }
135
+ export interface RewriteContract {
136
+ rewritten: string;
137
+ triggers: RewriteTriggerDisposition[];
138
+ }
139
+ /** Build the constrained rewrite prompt: old content + N trigger contents, nothing else. */
140
+ export declare function buildRewritePrompt(oldContent: string, triggers: Array<{
141
+ id: string;
142
+ content: string;
143
+ }>): string;
144
+ /**
145
+ * Parse + validate the rewrite contract (AC4). Null on ANY failure:
146
+ * - unparseable JSON / wrong shape;
147
+ * - rewritten empty, identical to the old content, or longer than
148
+ * old + 2000 + 500 per additional trigger;
149
+ * - the triggers array not covering every input trigger id exactly once
150
+ * (missing, unknown, or duplicated) or carrying an invalid disposition.
151
+ * A null return degrades the WHOLE group to mark-only (never a partial apply).
152
+ */
153
+ export declare function parseRewriteReply(reply: string, expectedTriggerIds: string[], oldContent: string): RewriteContract | null;
154
+ /**
155
+ * The provenance footer appended to every rewritten memory:
156
+ * `previously believed "<≤160-char head of old content>" until <date>`.
157
+ * date = the ISO DATE (YYYY-MM-DD) derived from the latest trigger's
158
+ * created_at — the same trigger recorded as the history row's evidence_id.
159
+ */
160
+ export declare function buildCorrectionFooter(oldContent: string, dateISO: string): string;
161
+ export type ExplicitMarkKind = "corrects" | "supersedes";
162
+ export interface ExplicitMarkInput {
163
+ kind: ExplicitMarkKind;
164
+ /** Raw id reference (8-char prefix or full UUID) as the client sent it. */
165
+ target: string;
166
+ }
167
+ export type ExplicitMarkCheck = {
168
+ ok: true;
169
+ targetId: string;
170
+ } | {
171
+ ok: false;
172
+ httpStatus: number;
173
+ error: string;
174
+ };
175
+ /**
176
+ * Validate an explicit mark target BEFORE anything is written (AC1: on an
177
+ * unknown/ambiguous id the WHOLE request fails and nothing is stored).
178
+ * httpStatus is the REST code; the MCP tool reuses the message verbatim.
179
+ */
180
+ export declare function checkExplicitMarkTarget(db: Database.Database, input: ExplicitMarkInput): ExplicitMarkCheck;
181
+ /**
182
+ * Apply a validated explicit mark: link old → new + status on the old memory.
183
+ * Deterministic — no LLM. Link strength 1.0: an operator-declared mark, not a
184
+ * measured cosine. `corrects` → `corrected_by` + status `retracted`;
185
+ * `supersedes` → `superseded_by` + status `superseded` (AC1).
186
+ */
187
+ export declare function applyExplicitMark(db: Database.Database, newMemoryId: string, input: ExplicitMarkInput): void;
188
+ /**
189
+ * Drop a trigger's retrieval candidacy: status `absorbed`, vector row deleted,
190
+ * FTS row deleted (direct DELETE — the AFTER UPDATE trigger's `UPDATE … WHERE
191
+ * rowid` is a silent no-op on the missing row, so later column edits cannot
192
+ * resurrect it). Row + links are KEPT (evidence, session lineage, rollback).
193
+ * Must run inside a transaction. Tags/domain deliberately untouched (only the
194
+ * rewritten TARGET gets its tags cleared).
195
+ *
196
+ * #392: the implementation moved to storage.ts (`absorbMemory`) so the dedup
197
+ * merge core shares the ONE primitive without an import cycle; re-exported
198
+ * here under its historical name for the rewrite/rollback paths (nothing
199
+ * external imports it today, but it is the module's documented surface).
200
+ */
201
+ export declare const absorbTrigger: (db: Database.Database, triggerId: string) => void;
202
+ /**
203
+ * Replace a memory's vector (delete + insert) — the /update re-embed pattern.
204
+ * Must run inside a transaction (the caller pre-computes the embedding
205
+ * asynchronously, outside the sync transaction).
206
+ */
207
+ export declare function replaceMemoryVector(db: Database.Database, memoryId: string, embedding: Float32Array): void;
208
+ /**
209
+ * Reverse an absorb: status back to active (NULL), vector re-embedded from the
210
+ * (untouched) content, FTS row re-inserted explicitly (migration v10's rebuild
211
+ * pattern — the AFTER UPDATE trigger cannot recreate a deleted FTS row).
212
+ * Must run inside a transaction; no-op on a memory that is not currently
213
+ * absorbed (never resurrects an already-live row, never duplicates an FTS row).
214
+ */
215
+ export declare function unabsorbTrigger(db: Database.Database, triggerId: string, embedding: Float32Array): void;
216
+ export interface MemoryHistoryRow {
217
+ id: number;
218
+ memory_id: string;
219
+ old_content: string;
220
+ new_content: string;
221
+ prev_status: string | null;
222
+ new_status: string | null;
223
+ triggers_json: string | null;
224
+ evidence_id: string | null;
225
+ confidence: number | null;
226
+ cause: string;
227
+ created_at: string;
228
+ }
229
+ /** History rows for one memory, oldest first. */
230
+ export declare function getMemoryHistory(db: Database.Database, memoryId: string): MemoryHistoryRow[];
231
+ export declare function getHistoryRow(db: Database.Database, historyRowId: number): MemoryHistoryRow | null;
232
+ export interface ResolutionBand {
233
+ /** Band label, e.g. "0.75-0.8" or ">=0.92". */
234
+ label: string;
235
+ /** Inclusive lower edge. */
236
+ lo: number;
237
+ /** Exclusive upper edge (Infinity for the deterministic >= band). */
238
+ hi: number;
239
+ }
240
+ /**
241
+ * Build the verdict-statistic bands from the LIVE floor/ceiling (#392): edges
242
+ * = sorted unique [floor, 0.80, 0.85, 0.90, ceiling]; bands are [e0,e1) …
243
+ * [e(n-1),en) plus the deterministic ">=en" band. Intermediate edges outside
244
+ * (floor, ceiling) are dropped — a band below the floor can never receive a
245
+ * verdict (candidates are >= floor), so a raised floor collapses the lower
246
+ * bands away instead of seeding dead labels. Labels use the numbers as
247
+ * configured ("0.75-0.8" … "0.9-0.92", ">=0.92").
248
+ */
249
+ export declare function buildResolutionBands(floor: number, ceiling: number): ResolutionBand[];
250
+ /**
251
+ * The band a pair's cosine falls into: the [lo, hi) band that contains it,
252
+ * falling through to the final >= band for cosines at/above the last edge.
253
+ * Null only for a cosine below the floor (never a candidate).
254
+ */
255
+ export declare function bandForCosine(bands: ResolutionBand[], cosine: number): ResolutionBand | null;
256
+ /**
257
+ * Nightly reconsolidation stage (#384, #392 — THE unified resolution stage).
258
+ *
259
+ * Phase 0 (#392): the deterministic merge zone (pairs >= the ceiling) runs
260
+ * first — LLM-free, budget-free, own lock/backup/cap.
261
+ *
262
+ * Scan: every memory with rowid > reconsolidationCursor (no shape filter;
263
+ * absorbed candidates are skipped — invisible memories are not re-judged).
264
+ * Each candidate's pairs: incoming explicit marks (verified once, AC7) then
265
+ * up-to-5 older KNN neighbors in [floor, ceiling) (verdict call per unlinked
266
+ * pair, AC2 — pairs at/above the ceiling are counted, never judged). Confirmed
267
+ * `corrects` pairs above the confidence gate on fact-shaped targets group by
268
+ * target into ONE rewrite call each (AC3); confirmed `merge` pairs queue for
269
+ * the merge phase; everything else is mark-only.
270
+ *
271
+ * Merge phase (#392): queued pairs merge through the dedup core under one
272
+ * lock/backup window, capped with the zone by dedupNightlyMaxMerges. A pair
273
+ * that cannot apply keeps both memories and holds the cursor.
274
+ *
275
+ * Cursor discipline mirrors stageSupersession: the cursor advances past a
276
+ * candidate once its neighbor set has been considered, regardless of infra
277
+ * skips — EXCEPT when rewrite groups or confirmed merges could not be applied
278
+ * (budget exhausted / rewrite-call infra error / merge cap or lock): the
279
+ * cursor then holds BELOW the earliest candidate contributing to the
280
+ * un-applied work, so those pairs are re-detected next run (dup-over-loss —
281
+ * an un-marked, un-rewritten, un-merged confirmed resolution must never be
282
+ * silently dropped by the cursor passing it).
283
+ *
284
+ * Dry-run: the zone's discovery + the free idempotency check only — zero LLM
285
+ * calls, zero writes, no cursor or band-stats persistence.
286
+ */
287
+ export declare function stageReconsolidation(db: Database.Database, llm: LlmClient, budget: StageBudget, embedFn: EmbedFn, dryRun: boolean, stateDir: string | undefined, options?: ReconsolidationOptions): Promise<ReconsolidationStageResult>;
288
+ export interface RollbackResult {
289
+ historyRowId: number;
290
+ memoryId: string;
291
+ restoredStatus: string | null;
292
+ unabsorbed: string[];
293
+ newHistoryRowId: number;
294
+ }
295
+ /**
296
+ * Roll back ONE rewrite history row: restore the recorded prior content and
297
+ * prior status via the same mechanics as the rewrite (re-embed, clear tags +
298
+ * domain NULL), reverse every absorb recorded in triggers_json (status
299
+ * restored, vector re-embedded, FTS row re-inserted), and write the rollback's
300
+ * own history row (cause `rollback`).
301
+ *
302
+ * Newest-first discipline: the row must be the NEWEST history entry for its
303
+ * memory (rolling back an older entry under a newer one would clobber — undo
304
+ * the newest first). The rollback row itself can be rolled back (undo the
305
+ * undo), which is what makes the chain navigable.
306
+ */
307
+ export declare function rollbackHistoryRow(db: Database.Database, historyRowId: number, embedFn: EmbedFn): Promise<RollbackResult>;
308
+ export interface HistoryCliOptions {
309
+ /** DB path override (tests / snapshot verification). Defaults to resolveDbPath(). */
310
+ dbPath?: string;
311
+ /** Memory id (8-char prefix or full UUID) whose history to list. */
312
+ memoryId?: string;
313
+ /** memory_history.id to roll back. */
314
+ rollbackId?: number;
315
+ /** Embed fn injection for tests; production lazy-loads the ONNX embedder. */
316
+ embedFn?: EmbedFn;
317
+ }
318
+ /**
319
+ * Runner for `hicortex history <id>` / `hicortex history --rollback <n>`.
320
+ * Returns a process exit code (0 success, 1 failure); throws only on
321
+ * unexpected infra errors (cli.ts prints those).
322
+ */
323
+ export declare function runHistoryCommand(options: HistoryCliOptions): Promise<number>;