@psiclawops/hypermem 0.6.2 → 0.8.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.
Files changed (98) hide show
  1. package/ARCHITECTURE.md +31 -39
  2. package/README.md +20 -14
  3. package/bin/hypermem-status.mjs +1 -1
  4. package/dist/background-indexer.d.ts +14 -3
  5. package/dist/background-indexer.d.ts.map +1 -1
  6. package/dist/background-indexer.js +135 -27
  7. package/dist/budget-policy.d.ts +22 -0
  8. package/dist/budget-policy.d.ts.map +1 -0
  9. package/dist/budget-policy.js +27 -0
  10. package/dist/cache.d.ts +11 -0
  11. package/dist/cache.d.ts.map +1 -1
  12. package/dist/compositor-utils.d.ts +31 -0
  13. package/dist/compositor-utils.d.ts.map +1 -0
  14. package/dist/compositor-utils.js +47 -0
  15. package/dist/compositor.d.ts +163 -1
  16. package/dist/compositor.d.ts.map +1 -1
  17. package/dist/compositor.js +862 -130
  18. package/dist/content-hash.d.ts +43 -0
  19. package/dist/content-hash.d.ts.map +1 -0
  20. package/dist/content-hash.js +75 -0
  21. package/dist/context-store.d.ts +54 -0
  22. package/dist/context-store.d.ts.map +1 -1
  23. package/dist/context-store.js +102 -0
  24. package/dist/contradiction-audit-store.d.ts +54 -0
  25. package/dist/contradiction-audit-store.d.ts.map +1 -0
  26. package/dist/contradiction-audit-store.js +88 -0
  27. package/dist/contradiction-detector.d.ts +78 -0
  28. package/dist/contradiction-detector.d.ts.map +1 -0
  29. package/dist/contradiction-detector.js +362 -0
  30. package/dist/contradiction-resolution-policy.d.ts +21 -0
  31. package/dist/contradiction-resolution-policy.d.ts.map +1 -0
  32. package/dist/contradiction-resolution-policy.js +17 -0
  33. package/dist/cross-agent.d.ts +1 -1
  34. package/dist/cross-agent.js +17 -17
  35. package/dist/degradation.d.ts +102 -0
  36. package/dist/degradation.d.ts.map +1 -0
  37. package/dist/degradation.js +141 -0
  38. package/dist/dreaming-promoter.d.ts +39 -1
  39. package/dist/dreaming-promoter.d.ts.map +1 -1
  40. package/dist/dreaming-promoter.js +70 -4
  41. package/dist/expertise-store.d.ts +129 -0
  42. package/dist/expertise-store.d.ts.map +1 -0
  43. package/dist/expertise-store.js +342 -0
  44. package/dist/fact-store.d.ts +15 -0
  45. package/dist/fact-store.d.ts.map +1 -1
  46. package/dist/fact-store.js +52 -5
  47. package/dist/index.d.ts +74 -8
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +407 -29
  50. package/dist/knowledge-lint.d.ts +2 -0
  51. package/dist/knowledge-lint.d.ts.map +1 -1
  52. package/dist/knowledge-lint.js +40 -1
  53. package/dist/library-schema.d.ts +7 -2
  54. package/dist/library-schema.d.ts.map +1 -1
  55. package/dist/library-schema.js +307 -2
  56. package/dist/message-store.d.ts +64 -1
  57. package/dist/message-store.d.ts.map +1 -1
  58. package/dist/message-store.js +137 -1
  59. package/dist/proactive-pass.d.ts +2 -2
  60. package/dist/proactive-pass.d.ts.map +1 -1
  61. package/dist/proactive-pass.js +66 -12
  62. package/dist/replay-recovery.d.ts +29 -0
  63. package/dist/replay-recovery.d.ts.map +1 -0
  64. package/dist/replay-recovery.js +82 -0
  65. package/dist/reranker.d.ts +95 -0
  66. package/dist/reranker.d.ts.map +1 -0
  67. package/dist/reranker.js +308 -0
  68. package/dist/schema.d.ts +1 -1
  69. package/dist/schema.d.ts.map +1 -1
  70. package/dist/schema.js +46 -1
  71. package/dist/seed.d.ts +1 -1
  72. package/dist/seed.js +1 -1
  73. package/dist/session-flusher.d.ts +4 -4
  74. package/dist/session-flusher.d.ts.map +1 -1
  75. package/dist/session-flusher.js +3 -3
  76. package/dist/spawn-context.d.ts +1 -1
  77. package/dist/spawn-context.js +1 -1
  78. package/dist/temporal-store.d.ts +1 -0
  79. package/dist/temporal-store.d.ts.map +1 -1
  80. package/dist/tool-artifact-store.d.ts +98 -0
  81. package/dist/tool-artifact-store.d.ts.map +1 -0
  82. package/dist/tool-artifact-store.js +244 -0
  83. package/dist/topic-detector.js +2 -2
  84. package/dist/topic-store.d.ts +6 -0
  85. package/dist/topic-store.d.ts.map +1 -1
  86. package/dist/topic-store.js +39 -0
  87. package/dist/topic-synthesizer.js +1 -1
  88. package/dist/trigger-registry.d.ts +1 -1
  89. package/dist/trigger-registry.js +4 -4
  90. package/dist/types.d.ts +239 -3
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/vector-store.d.ts +2 -1
  93. package/dist/vector-store.d.ts.map +1 -1
  94. package/dist/vector-store.js +3 -0
  95. package/dist/version.d.ts +10 -10
  96. package/dist/version.d.ts.map +1 -1
  97. package/dist/version.js +10 -10
  98. package/package.json +6 -4
@@ -0,0 +1,43 @@
1
+ /**
2
+ * hypermem Content Hash
3
+ *
4
+ * Content-addressed hashing for fact/knowledge deduplication.
5
+ * Uses BLAKE3 (via @noble/hashes) for fast, collision-resistant fingerprints.
6
+ *
7
+ * Dedup semantics are per-agent: the same content stored by two different
8
+ * agents is intentional and should not be merged. Check with:
9
+ * WHERE content_hash = ? AND agent_id = ?
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ /**
13
+ * Compute a BLAKE3 content hash for deduplication.
14
+ * Returns a 64-character hex string (32 bytes).
15
+ *
16
+ * Normalization: collapses whitespace, trims, lowercases
17
+ * before hashing. This catches trivial reformatting differences
18
+ * that shouldn't create duplicate facts.
19
+ */
20
+ export declare function contentHash(text: string): string;
21
+ /**
22
+ * SQL migration statements for adding content_hash columns and indexes.
23
+ *
24
+ * Use non-unique indexes — different agents may legitimately store identical
25
+ * facts. Dedup check is always scoped per agent:
26
+ * WHERE content_hash = ? AND agent_id = ?
27
+ */
28
+ export declare const SCHEMA_MIGRATIONS: {
29
+ addFactsContentHash: string;
30
+ addKnowledgeContentHash: string;
31
+ indexFactsContentHash: string;
32
+ indexKnowledgeContentHash: string;
33
+ };
34
+ /**
35
+ * Backfill content_hash for existing rows that have NULL hashes.
36
+ * Meant to run once after schema migration.
37
+ * Returns count of rows updated.
38
+ *
39
+ * Defensive: returns 0 if the content_hash column does not exist on the
40
+ * target table (e.g. migration hasn't run yet or table is missing).
41
+ */
42
+ export declare function backfillContentHashes(db: DatabaseSync, table: 'facts' | 'knowledge'): number;
43
+ //# sourceMappingURL=content-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../src/content-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB;;;;;CAK7B,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,YAAY,EAChB,KAAK,EAAE,OAAO,GAAG,WAAW,GAC3B,MAAM,CAgCR"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * hypermem Content Hash
3
+ *
4
+ * Content-addressed hashing for fact/knowledge deduplication.
5
+ * Uses BLAKE3 (via @noble/hashes) for fast, collision-resistant fingerprints.
6
+ *
7
+ * Dedup semantics are per-agent: the same content stored by two different
8
+ * agents is intentional and should not be merged. Check with:
9
+ * WHERE content_hash = ? AND agent_id = ?
10
+ */
11
+ import { blake3 } from '@noble/hashes/blake3.js';
12
+ /**
13
+ * Compute a BLAKE3 content hash for deduplication.
14
+ * Returns a 64-character hex string (32 bytes).
15
+ *
16
+ * Normalization: collapses whitespace, trims, lowercases
17
+ * before hashing. This catches trivial reformatting differences
18
+ * that shouldn't create duplicate facts.
19
+ */
20
+ export function contentHash(text) {
21
+ const normalized = text.toLowerCase().replace(/\s+/g, ' ').trim();
22
+ const bytes = new TextEncoder().encode(normalized);
23
+ return Buffer.from(blake3(bytes)).toString('hex');
24
+ }
25
+ /**
26
+ * SQL migration statements for adding content_hash columns and indexes.
27
+ *
28
+ * Use non-unique indexes — different agents may legitimately store identical
29
+ * facts. Dedup check is always scoped per agent:
30
+ * WHERE content_hash = ? AND agent_id = ?
31
+ */
32
+ export const SCHEMA_MIGRATIONS = {
33
+ addFactsContentHash: `ALTER TABLE facts ADD COLUMN content_hash TEXT`,
34
+ addKnowledgeContentHash: `ALTER TABLE knowledge ADD COLUMN content_hash TEXT`,
35
+ indexFactsContentHash: `CREATE INDEX IF NOT EXISTS idx_facts_content_hash ON facts(content_hash)`,
36
+ indexKnowledgeContentHash: `CREATE INDEX IF NOT EXISTS idx_knowledge_content_hash ON knowledge(content_hash)`,
37
+ };
38
+ /**
39
+ * Backfill content_hash for existing rows that have NULL hashes.
40
+ * Meant to run once after schema migration.
41
+ * Returns count of rows updated.
42
+ *
43
+ * Defensive: returns 0 if the content_hash column does not exist on the
44
+ * target table (e.g. migration hasn't run yet or table is missing).
45
+ */
46
+ export function backfillContentHashes(db, table) {
47
+ // Defensive: verify the column exists before querying it.
48
+ const pragmaRows = db
49
+ .prepare(`PRAGMA table_info(${table})`)
50
+ .all();
51
+ const hasColumn = pragmaRows.some((r) => r['name'] === 'content_hash');
52
+ if (!hasColumn)
53
+ return 0;
54
+ const rows = db
55
+ .prepare(`SELECT id, content FROM ${table} WHERE content_hash IS NULL`)
56
+ .all();
57
+ if (rows.length === 0)
58
+ return 0;
59
+ const update = db.prepare(`UPDATE ${table} SET content_hash = ? WHERE id = ?`);
60
+ // Single transaction for the entire batch — much faster than autocommit per row.
61
+ // Node 22 DatabaseSync does not have .transaction(); use manual BEGIN/COMMIT.
62
+ db.exec('BEGIN');
63
+ try {
64
+ for (const row of rows) {
65
+ update.run(contentHash(row.content), row.id);
66
+ }
67
+ db.exec('COMMIT');
68
+ }
69
+ catch (err) {
70
+ db.exec('ROLLBACK');
71
+ throw err;
72
+ }
73
+ return rows.length;
74
+ }
75
+ //# sourceMappingURL=content-hash.js.map
@@ -63,6 +63,60 @@ export declare function updateContextHead(db: DatabaseSync, contextId: number, m
63
63
  * Idempotent — no-op if already archived.
64
64
  */
65
65
  export declare function archiveContext(db: DatabaseSync, contextId: number): void;
66
+ /**
67
+ * Get any context by id, regardless of status.
68
+ * Returns null if not found.
69
+ *
70
+ * @boundary INSPECTION ONLY — not a mining entry point.
71
+ * @policy See specs/DAG_HELPER_POLICY.md for helper classifications.
72
+ * Do not use this function to retrieve messages for active composition or
73
+ * historical mining. Use getArchivedContext + mineArchivedContext for archived
74
+ * mining, and getActiveContext for composition-path access.
75
+ */
76
+ export declare function getContextById(db: DatabaseSync, contextId: number): Context | null;
77
+ /**
78
+ * Get all archived or forked contexts for an agent.
79
+ * Optionally filter by sessionKey and/or limit.
80
+ * Returns in reverse-chronological order (most recently updated first).
81
+ *
82
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
83
+ * archived-context enumeration path.
84
+ */
85
+ export declare function getArchivedContexts(db: DatabaseSync, agentId: string, opts?: {
86
+ sessionKey?: string;
87
+ limit?: number;
88
+ }): Context[];
89
+ /**
90
+ * Get an archived or forked context by id.
91
+ * Returns null if the context does not exist OR if it is active.
92
+ *
93
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
94
+ * single-context lookup path.
95
+ */
96
+ export declare function getArchivedContext(db: DatabaseSync, contextId: number): Context | null;
97
+ /**
98
+ * Walk the parent_context_id chain upward from the given context.
99
+ * Returns contexts in leaf-to-root order (starting context first).
100
+ * Includes the starting context itself.
101
+ * Caps traversal depth at 100 to avoid corrupt loops.
102
+ *
103
+ * @boundary STATUS-CROSSING BY DESIGN — this function traverses across
104
+ * active, archived, and forked contexts without filtering by status.
105
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
106
+ * If you need only archived/forked contexts in the lineage chain, filter
107
+ * the returned array at the call site (e.g. `.filter(c => c.status !== 'active')`).
108
+ */
109
+ export declare function getContextLineage(db: DatabaseSync, contextId: number): Context[];
110
+ /**
111
+ * Get direct fork children of a context (contexts with parent_context_id = parentContextId).
112
+ * Returns in ascending creation order.
113
+ *
114
+ * @boundary STATUS-CROSSING BY DESIGN — returns children regardless of their status
115
+ * (may include active, archived, or forked children).
116
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
117
+ * Filter at the call site if archived-only results are needed.
118
+ */
119
+ export declare function getForkChildren(db: DatabaseSync, parentContextId: number): Context[];
66
120
  /**
67
121
  * Rotate a session's active context: archive the current active context
68
122
  * and create a new one, optionally linking back via parent_context_id.
@@ -1 +1 @@
1
- {"version":3,"file":"context-store.d.ts","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmC1D;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,GAAG,IAAI,CAShB;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAwBT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI,CAeN;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CA2BT"}
1
+ {"version":3,"file":"context-store.d.ts","sourceRoot":"","sources":["../src/context-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAmC1D;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,OAAO,GAAG,IAAI,CAShB;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAwBT;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,IAAI,CAeN;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,IAAI,CAMN;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,GAAG,IAAI,CAOhB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IACL,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,EAAE,CAkBX;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,GAAG,IAAI,CAOhB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,YAAY,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,EAAE,CAqBX;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,YAAY,EAChB,eAAe,EAAE,MAAM,GACtB,OAAO,EAAE,CAMX;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CA2BT"}
@@ -141,6 +141,108 @@ export function archiveContext(db, contextId) {
141
141
  const now = nowIso();
142
142
  db.prepare(`UPDATE contexts SET status = 'archived', updated_at = ? WHERE id = ? AND status != 'archived'`).run(now, contextId);
143
143
  }
144
+ /**
145
+ * Get any context by id, regardless of status.
146
+ * Returns null if not found.
147
+ *
148
+ * @boundary INSPECTION ONLY — not a mining entry point.
149
+ * @policy See specs/DAG_HELPER_POLICY.md for helper classifications.
150
+ * Do not use this function to retrieve messages for active composition or
151
+ * historical mining. Use getArchivedContext + mineArchivedContext for archived
152
+ * mining, and getActiveContext for composition-path access.
153
+ */
154
+ export function getContextById(db, contextId) {
155
+ const row = db
156
+ .prepare('SELECT * FROM contexts WHERE id = ?')
157
+ .get(contextId);
158
+ if (!row)
159
+ return null;
160
+ return parseContextRow(row);
161
+ }
162
+ /**
163
+ * Get all archived or forked contexts for an agent.
164
+ * Optionally filter by sessionKey and/or limit.
165
+ * Returns in reverse-chronological order (most recently updated first).
166
+ *
167
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
168
+ * archived-context enumeration path.
169
+ */
170
+ export function getArchivedContexts(db, agentId, opts) {
171
+ let sql = "SELECT * FROM contexts WHERE agent_id = ? AND status IN ('archived', 'forked')";
172
+ const params = [agentId];
173
+ if (opts?.sessionKey) {
174
+ sql += ' AND session_key = ?';
175
+ params.push(opts.sessionKey);
176
+ }
177
+ sql += ' ORDER BY updated_at DESC';
178
+ if (opts?.limit) {
179
+ sql += ' LIMIT ?';
180
+ params.push(opts.limit);
181
+ }
182
+ const rows = db.prepare(sql).all(...params);
183
+ return rows.map(parseContextRow);
184
+ }
185
+ /**
186
+ * Get an archived or forked context by id.
187
+ * Returns null if the context does not exist OR if it is active.
188
+ *
189
+ * @policy See specs/DAG_HELPER_POLICY.md. This is the operator-safe
190
+ * single-context lookup path.
191
+ */
192
+ export function getArchivedContext(db, contextId) {
193
+ const row = db
194
+ .prepare("SELECT * FROM contexts WHERE id = ? AND status IN ('archived', 'forked')")
195
+ .get(contextId);
196
+ if (!row)
197
+ return null;
198
+ return parseContextRow(row);
199
+ }
200
+ /**
201
+ * Walk the parent_context_id chain upward from the given context.
202
+ * Returns contexts in leaf-to-root order (starting context first).
203
+ * Includes the starting context itself.
204
+ * Caps traversal depth at 100 to avoid corrupt loops.
205
+ *
206
+ * @boundary STATUS-CROSSING BY DESIGN — this function traverses across
207
+ * active, archived, and forked contexts without filtering by status.
208
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
209
+ * If you need only archived/forked contexts in the lineage chain, filter
210
+ * the returned array at the call site (e.g. `.filter(c => c.status !== 'active')`).
211
+ */
212
+ export function getContextLineage(db, contextId) {
213
+ const lineage = [];
214
+ const visited = new Set();
215
+ let currentId = contextId;
216
+ while (currentId !== null && lineage.length < 100) {
217
+ if (visited.has(currentId))
218
+ break; // loop guard
219
+ visited.add(currentId);
220
+ const row = db
221
+ .prepare('SELECT * FROM contexts WHERE id = ?')
222
+ .get(currentId);
223
+ if (!row)
224
+ break;
225
+ const ctx = parseContextRow(row);
226
+ lineage.push(ctx);
227
+ currentId = ctx.parentContextId;
228
+ }
229
+ return lineage;
230
+ }
231
+ /**
232
+ * Get direct fork children of a context (contexts with parent_context_id = parentContextId).
233
+ * Returns in ascending creation order.
234
+ *
235
+ * @boundary STATUS-CROSSING BY DESIGN — returns children regardless of their status
236
+ * (may include active, archived, or forked children).
237
+ * @policy See specs/DAG_HELPER_POLICY.md for the call-site filtering rule.
238
+ * Filter at the call site if archived-only results are needed.
239
+ */
240
+ export function getForkChildren(db, parentContextId) {
241
+ const rows = db
242
+ .prepare('SELECT * FROM contexts WHERE parent_context_id = ? ORDER BY created_at ASC')
243
+ .all(parentContextId);
244
+ return rows.map(parseContextRow);
245
+ }
144
246
  /**
145
247
  * Rotate a session's active context: archive the current active context
146
248
  * and create a new one, optionally linking back via parent_context_id.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * hypermem Contradiction Audit Store
3
+ *
4
+ * Lightweight audit trail for contradiction detections during background indexing.
5
+ * Records when the indexer identifies a new fact candidate that contradicts an
6
+ * existing stored fact, without auto-resolving (autoResolve: false path).
7
+ *
8
+ * Stored in library.db under the contradiction_audits table (created on demand).
9
+ * Used for observability and future contradiction resolution tooling.
10
+ */
11
+ import type { DatabaseSync } from 'node:sqlite';
12
+ import type { ContradictionCandidate } from './contradiction-detector.js';
13
+ export interface ContradictionAuditEntry {
14
+ id: number;
15
+ agentId: string;
16
+ newContent: string;
17
+ newDomain: string | null;
18
+ existingFactId: number;
19
+ existingContent: string;
20
+ similarityScore: number;
21
+ contradictionScore: number;
22
+ reason: string;
23
+ sourceRef: string | null;
24
+ createdAt: string;
25
+ }
26
+ export declare class ContradictionAuditStore {
27
+ private readonly db;
28
+ constructor(db: DatabaseSync);
29
+ private ensureTable;
30
+ /**
31
+ * Record a detected contradiction between a new fact candidate and an existing fact.
32
+ *
33
+ * @param agentId - Agent whose fact was being indexed
34
+ * @param newFact - The incoming fact candidate (not yet stored)
35
+ * @param candidate - The contradiction candidate from ContradictionDetector
36
+ * @param opts - Optional metadata (sourceRef = "msg:<id>" etc.)
37
+ */
38
+ recordFactAudit(agentId: string, newFact: {
39
+ content: string;
40
+ domain?: string | null;
41
+ }, candidate: ContradictionCandidate, opts?: {
42
+ sourceRef?: string;
43
+ status?: string;
44
+ }): void;
45
+ /**
46
+ * Fetch recent audit entries for an agent (most recent first).
47
+ */
48
+ getRecentAudits(agentId: string, limit?: number): ContradictionAuditEntry[];
49
+ /**
50
+ * Count unresolved audits for an agent.
51
+ */
52
+ countAudits(agentId: string): number;
53
+ }
54
+ //# sourceMappingURL=contradiction-audit-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contradiction-audit-store.d.ts","sourceRoot":"","sources":["../src/contradiction-audit-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAE1E,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAI7C,OAAO,CAAC,WAAW;IAiCnB;;;;;;;OAOG;IACH,eAAe,CACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EACpD,SAAS,EAAE,sBAAsB,EACjC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC7C,IAAI;IAqBP;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,uBAAuB,EAAE;IAYvE;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;CAMrC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * hypermem Contradiction Audit Store
3
+ *
4
+ * Lightweight audit trail for contradiction detections during background indexing.
5
+ * Records when the indexer identifies a new fact candidate that contradicts an
6
+ * existing stored fact, without auto-resolving (autoResolve: false path).
7
+ *
8
+ * Stored in library.db under the contradiction_audits table (created on demand).
9
+ * Used for observability and future contradiction resolution tooling.
10
+ */
11
+ export class ContradictionAuditStore {
12
+ db;
13
+ constructor(db) {
14
+ this.db = db;
15
+ this.ensureTable();
16
+ }
17
+ ensureTable() {
18
+ // Synced with library-schema.ts v19 shape so in-memory test DBs match production.
19
+ // In real usage, setupLibraryDb() runs first and this CREATE TABLE is a no-op.
20
+ this.db.exec(`
21
+ CREATE TABLE IF NOT EXISTS contradiction_audits (
22
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
23
+ agent_id TEXT NOT NULL,
24
+ entity_type TEXT NOT NULL CHECK(entity_type IN ('fact')),
25
+ new_content TEXT NOT NULL,
26
+ new_domain TEXT,
27
+ existing_fact_id INTEGER NOT NULL,
28
+ existing_content TEXT NOT NULL,
29
+ similarity_score REAL NOT NULL DEFAULT 0,
30
+ contradiction_score REAL NOT NULL DEFAULT 0,
31
+ reason TEXT NOT NULL,
32
+ detector TEXT NOT NULL DEFAULT 'heuristic_v1',
33
+ suggested_resolution TEXT NOT NULL DEFAULT 'review',
34
+ source_ref TEXT,
35
+ status TEXT NOT NULL DEFAULT 'pending'
36
+ CHECK(status IN ('pending', 'accepted', 'dismissed', 'auto-superseded', 'auto-invalidated')),
37
+ resolution_notes TEXT,
38
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
39
+ resolved_at TEXT
40
+ );
41
+ CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent_status
42
+ ON contradiction_audits (agent_id, status, created_at DESC);
43
+ CREATE INDEX IF NOT EXISTS idx_contradiction_audits_existing_fact
44
+ ON contradiction_audits (existing_fact_id, status);
45
+ CREATE INDEX IF NOT EXISTS idx_contradiction_audits_agent
46
+ ON contradiction_audits (agent_id, created_at DESC);
47
+ `);
48
+ }
49
+ /**
50
+ * Record a detected contradiction between a new fact candidate and an existing fact.
51
+ *
52
+ * @param agentId - Agent whose fact was being indexed
53
+ * @param newFact - The incoming fact candidate (not yet stored)
54
+ * @param candidate - The contradiction candidate from ContradictionDetector
55
+ * @param opts - Optional metadata (sourceRef = "msg:<id>" etc.)
56
+ */
57
+ recordFactAudit(agentId, newFact, candidate, opts) {
58
+ const status = opts?.status ?? 'pending';
59
+ this.db.prepare(`
60
+ INSERT INTO contradiction_audits
61
+ (agent_id, entity_type, new_content, new_domain, existing_fact_id, existing_content,
62
+ similarity_score, contradiction_score, reason, source_ref, status, created_at)
63
+ VALUES (?, 'fact', ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
64
+ `).run(agentId, newFact.content, newFact.domain ?? null, candidate.existingFactId, candidate.existingContent, candidate.similarityScore, candidate.contradictionScore, candidate.reason, opts?.sourceRef ?? null, status);
65
+ }
66
+ /**
67
+ * Fetch recent audit entries for an agent (most recent first).
68
+ */
69
+ getRecentAudits(agentId, limit = 20) {
70
+ return this.db.prepare(`
71
+ SELECT
72
+ id, agent_id, new_content, new_domain, existing_fact_id, existing_content,
73
+ similarity_score, contradiction_score, reason, source_ref, created_at
74
+ FROM contradiction_audits
75
+ WHERE agent_id = ?
76
+ ORDER BY created_at DESC
77
+ LIMIT ?
78
+ `).all(agentId, limit);
79
+ }
80
+ /**
81
+ * Count unresolved audits for an agent.
82
+ */
83
+ countAudits(agentId) {
84
+ const row = this.db.prepare('SELECT COUNT(*) as cnt FROM contradiction_audits WHERE agent_id = ?').get(agentId);
85
+ return row?.cnt ?? 0;
86
+ }
87
+ }
88
+ //# sourceMappingURL=contradiction-audit-store.js.map
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Contradiction Detector — heuristic-based contradiction detection for the fact store.
3
+ *
4
+ * Detects when a newly ingested fact contradicts existing active facts using
5
+ * vector similarity (when available) and FTS candidate retrieval, scored by
6
+ * pattern-based heuristics (negation, numeric conflict, state conflict, temporal).
7
+ *
8
+ * No LLM calls — v1 is purely heuristic. LLM-enhanced scoring is a future item.
9
+ */
10
+ import type { FactStore } from './fact-store.js';
11
+ import type { VectorStore } from './vector-store.js';
12
+ export interface ContradictionCandidate {
13
+ existingFactId: number;
14
+ existingContent: string;
15
+ similarityScore: number;
16
+ contradictionScore: number;
17
+ reason: string;
18
+ }
19
+ export interface ContradictionResult {
20
+ contradictions: ContradictionCandidate[];
21
+ autoResolved: boolean;
22
+ resolvedCount: number;
23
+ }
24
+ export interface ContradictionDetectorConfig {
25
+ /** Minimum similarity to consider as candidate. Default: 0.6 */
26
+ minSimilarity?: number;
27
+ /** Minimum contradiction score for auto-resolution. Default: 0.85 */
28
+ autoResolveThreshold?: number;
29
+ /** Max candidates to evaluate per ingest. Default: 10 */
30
+ maxCandidates?: number;
31
+ /** Enable auto-resolution. Default: true */
32
+ autoResolve?: boolean;
33
+ }
34
+ export declare class ContradictionDetector {
35
+ private readonly factStore;
36
+ private readonly vectorStore?;
37
+ private readonly config;
38
+ constructor(factStore: FactStore, vectorStore?: VectorStore | undefined, config?: ContradictionDetectorConfig);
39
+ /**
40
+ * On fact ingest, check if the new fact contradicts existing active facts.
41
+ * Uses vector similarity (when available) + FTS to find candidates, then
42
+ * scores each candidate with heuristic contradiction checks.
43
+ */
44
+ detectOnIngest(agentId: string, newFact: {
45
+ content: string;
46
+ domain?: string;
47
+ }): Promise<ContradictionResult>;
48
+ /**
49
+ * Resolve a detected contradiction between an existing fact and a new fact.
50
+ */
51
+ resolveContradiction(oldFactId: number, newFactId: number, resolution: 'supersede' | 'keep-both' | 'reject-new'): void;
52
+ /**
53
+ * Auto-resolve high-confidence contradictions: newer supersedes older.
54
+ * Only resolves candidates above the autoResolveThreshold.
55
+ *
56
+ * @param agentId - The agent whose facts are being resolved (for audit trail)
57
+ * @param candidates - Scored contradiction candidates from detectOnIngest
58
+ * @returns Count of auto-resolved contradictions
59
+ */
60
+ autoResolve(_agentId: string, candidates: ContradictionCandidate[]): Promise<number>;
61
+ /**
62
+ * Find candidate facts that might contradict the new fact.
63
+ * Uses vector search (if available) and FTS, deduplicates, and returns
64
+ * up to maxCandidates results above minSimilarity.
65
+ */
66
+ private findCandidates;
67
+ /**
68
+ * Score a candidate fact against the new fact content for contradiction.
69
+ * Returns a ContradictionCandidate if any heuristic fires, null otherwise.
70
+ */
71
+ private scoreContradiction;
72
+ /**
73
+ * Compute Jaccard-like token overlap between two texts.
74
+ * Returns 0-1 where 1 means identical token sets.
75
+ */
76
+ private tokenOverlapSimilarity;
77
+ }
78
+ //# sourceMappingURL=contradiction-detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contradiction-detector.d.ts","sourceRoot":"","sources":["../src/contradiction-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,mBAAmB,CAAC;AAIzE,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,sBAAsB,EAAE,CAAC;IACzC,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAkKD,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAJ/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwC;gBAG5C,SAAS,EAAE,SAAS,EACpB,WAAW,CAAC,EAAE,WAAW,YAAA,EAC1C,MAAM,CAAC,EAAE,2BAA2B;IAKtC;;;;OAIG;IACG,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,mBAAmB,CAAC;IAuB/B;;OAEG;IACH,oBAAoB,CAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,GACnD,IAAI;IAcP;;;;;;;OAOG;IACG,WAAW,CACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,sBAAsB,EAAE,GACnC,OAAO,CAAC,MAAM,CAAC;IAoBlB;;;;OAIG;YACW,cAAc;IA0E5B;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA0D1B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;CAa/B"}