@obsidicore/cascade-engine 0.2.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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cascade/checkpoints.d.ts +55 -0
  3. package/dist/cascade/checkpoints.js +123 -0
  4. package/dist/cascade/checkpoints.js.map +1 -0
  5. package/dist/cascade/engine.d.ts +72 -0
  6. package/dist/cascade/engine.js +170 -0
  7. package/dist/cascade/engine.js.map +1 -0
  8. package/dist/cascade/gates.d.ts +46 -0
  9. package/dist/cascade/gates.js +199 -0
  10. package/dist/cascade/gates.js.map +1 -0
  11. package/dist/cascade/research.d.ts +50 -0
  12. package/dist/cascade/research.js +127 -0
  13. package/dist/cascade/research.js.map +1 -0
  14. package/dist/cli.d.ts +19 -0
  15. package/dist/cli.js +165 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/control/kalman.d.ts +53 -0
  18. package/dist/control/kalman.js +83 -0
  19. package/dist/control/kalman.js.map +1 -0
  20. package/dist/control/pid.d.ts +57 -0
  21. package/dist/control/pid.js +95 -0
  22. package/dist/control/pid.js.map +1 -0
  23. package/dist/control/stability.d.ts +42 -0
  24. package/dist/control/stability.js +117 -0
  25. package/dist/control/stability.js.map +1 -0
  26. package/dist/db/index.d.ts +26 -0
  27. package/dist/db/index.js +116 -0
  28. package/dist/db/index.js.map +1 -0
  29. package/dist/db/schema.sql +282 -0
  30. package/dist/graph/amem.d.ts +80 -0
  31. package/dist/graph/amem.js +190 -0
  32. package/dist/graph/amem.js.map +1 -0
  33. package/dist/graph/entities.d.ts +66 -0
  34. package/dist/graph/entities.js +187 -0
  35. package/dist/graph/entities.js.map +1 -0
  36. package/dist/graph/queries.d.ts +48 -0
  37. package/dist/graph/queries.js +176 -0
  38. package/dist/graph/queries.js.map +1 -0
  39. package/dist/hitl/dashboard.d.ts +51 -0
  40. package/dist/hitl/dashboard.js +135 -0
  41. package/dist/hitl/dashboard.js.map +1 -0
  42. package/dist/hitl/interventions.d.ts +36 -0
  43. package/dist/hitl/interventions.js +150 -0
  44. package/dist/hitl/interventions.js.map +1 -0
  45. package/dist/hitl/steering.d.ts +37 -0
  46. package/dist/hitl/steering.js +118 -0
  47. package/dist/hitl/steering.js.map +1 -0
  48. package/dist/index.d.ts +12 -0
  49. package/dist/index.js +701 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/memory/consolidation.d.ts +51 -0
  52. package/dist/memory/consolidation.js +122 -0
  53. package/dist/memory/consolidation.js.map +1 -0
  54. package/dist/memory/ncd.d.ts +40 -0
  55. package/dist/memory/ncd.js +90 -0
  56. package/dist/memory/ncd.js.map +1 -0
  57. package/dist/memory/sm2.d.ts +44 -0
  58. package/dist/memory/sm2.js +119 -0
  59. package/dist/memory/sm2.js.map +1 -0
  60. package/dist/memory/tiers.d.ts +49 -0
  61. package/dist/memory/tiers.js +145 -0
  62. package/dist/memory/tiers.js.map +1 -0
  63. package/dist/server.d.ts +6 -0
  64. package/dist/server.js +6 -0
  65. package/dist/server.js.map +1 -0
  66. package/dist/trust/ingestion.d.ts +38 -0
  67. package/dist/trust/ingestion.js +147 -0
  68. package/dist/trust/ingestion.js.map +1 -0
  69. package/dist/trust/patterns.d.ts +26 -0
  70. package/dist/trust/patterns.js +78 -0
  71. package/dist/trust/patterns.js.map +1 -0
  72. package/dist/trust/scoring.d.ts +39 -0
  73. package/dist/trust/scoring.js +206 -0
  74. package/dist/trust/scoring.js.map +1 -0
  75. package/package.json +58 -0
@@ -0,0 +1,282 @@
1
+ -- Research Cascade Knowledge Database Schema
2
+ -- Version: 1.0
3
+ -- Requires: SQLite >= 3.51.3 (WAL-reset corruption fix)
4
+
5
+ -- ============================================================
6
+ -- TIER 1: CASCADE STATE
7
+ -- ============================================================
8
+
9
+ CREATE TABLE IF NOT EXISTS cascades (
10
+ id TEXT PRIMARY KEY,
11
+ question TEXT NOT NULL,
12
+ status TEXT NOT NULL DEFAULT 'planning'
13
+ CHECK(status IN('planning','investigating','validating','synthesizing','complete','stalled')),
14
+ plan_json TEXT CHECK(plan_json IS NULL OR json_valid(plan_json)),
15
+ pid_state_json TEXT,
16
+ max_rounds INTEGER DEFAULT 5,
17
+ current_round INTEGER DEFAULT 0,
18
+ token_budget INTEGER DEFAULT 500000,
19
+ tokens_used INTEGER DEFAULT 0,
20
+ exploration_budget REAL DEFAULT 0.8,
21
+ created_at TEXT DEFAULT(datetime('now')),
22
+ updated_at TEXT DEFAULT(datetime('now'))
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS threads (
26
+ id TEXT PRIMARY KEY,
27
+ cascade_id TEXT NOT NULL REFERENCES cascades(id) ON DELETE CASCADE,
28
+ question TEXT NOT NULL,
29
+ type TEXT NOT NULL CHECK(type IN('technical','discovery','classification','validation')),
30
+ status TEXT DEFAULT 'pending' CHECK(status IN('pending','active','done','failed')),
31
+ agent_name TEXT,
32
+ model_used TEXT,
33
+ ucb_value REAL DEFAULT 0.0,
34
+ ucb_visits INTEGER DEFAULT 0,
35
+ ucb_reward REAL DEFAULT 0.0,
36
+ started_at TEXT,
37
+ completed_at TEXT
38
+ );
39
+ CREATE INDEX IF NOT EXISTS idx_threads_cascade ON threads(cascade_id);
40
+
41
+ CREATE TABLE IF NOT EXISTS findings (
42
+ id TEXT PRIMARY KEY,
43
+ thread_id TEXT REFERENCES threads(id) ON DELETE CASCADE,
44
+ cascade_id TEXT REFERENCES cascades(id) ON DELETE CASCADE,
45
+ claim TEXT NOT NULL,
46
+ evidence TEXT,
47
+ source_url TEXT,
48
+ source_type TEXT CHECK(source_type IN('primary','secondary','tertiary')),
49
+ confidence REAL DEFAULT 0.5,
50
+ confidence_uncertainty REAL DEFAULT 1.0,
51
+ trust_composite REAL DEFAULT 0.5,
52
+ trust_signals_json TEXT,
53
+ grade_level TEXT CHECK(grade_level IS NULL OR grade_level IN('high','moderate','low','very_low')),
54
+ quarantined INTEGER DEFAULT 0,
55
+ human_reviewed INTEGER DEFAULT 0,
56
+ retrieval_weight REAL DEFAULT 1.0,
57
+ cd47_protected INTEGER DEFAULT 0,
58
+ cascade_round INTEGER NOT NULL,
59
+ ncd_cluster_id TEXT,
60
+ created_at TEXT DEFAULT(datetime('now'))
61
+ );
62
+ CREATE INDEX IF NOT EXISTS idx_findings_cascade ON findings(cascade_id);
63
+ CREATE INDEX IF NOT EXISTS idx_findings_quarantined ON findings(quarantined) WHERE quarantined = 1;
64
+
65
+ CREATE VIRTUAL TABLE IF NOT EXISTS findings_fts USING fts5(
66
+ claim, evidence, source_url, content=findings, content_rowid=rowid
67
+ );
68
+
69
+ -- Triggers to keep FTS in sync
70
+ CREATE TRIGGER IF NOT EXISTS findings_ai AFTER INSERT ON findings BEGIN
71
+ INSERT INTO findings_fts(rowid, claim, evidence, source_url)
72
+ VALUES (new.rowid, new.claim, new.evidence, new.source_url);
73
+ END;
74
+
75
+ CREATE TRIGGER IF NOT EXISTS findings_ad AFTER DELETE ON findings BEGIN
76
+ INSERT INTO findings_fts(findings_fts, rowid, claim, evidence, source_url)
77
+ VALUES ('delete', old.rowid, old.claim, old.evidence, old.source_url);
78
+ END;
79
+
80
+ CREATE TRIGGER IF NOT EXISTS findings_au AFTER UPDATE ON findings BEGIN
81
+ INSERT INTO findings_fts(findings_fts, rowid, claim, evidence, source_url)
82
+ VALUES ('delete', old.rowid, old.claim, old.evidence, old.source_url);
83
+ INSERT INTO findings_fts(rowid, claim, evidence, source_url)
84
+ VALUES (new.rowid, new.claim, new.evidence, new.source_url);
85
+ END;
86
+
87
+ CREATE TABLE IF NOT EXISTS cascade_checkpoints (
88
+ task_id TEXT NOT NULL,
89
+ round_index INTEGER NOT NULL,
90
+ step_index INTEGER NOT NULL,
91
+ step_name TEXT NOT NULL,
92
+ status TEXT NOT NULL DEFAULT 'pending'
93
+ CHECK(status IN('pending','running','done','failed','skipped')),
94
+ state_snapshot TEXT,
95
+ idempotency_key TEXT UNIQUE,
96
+ error_message TEXT,
97
+ created_at TEXT DEFAULT(datetime('now')),
98
+ completed_at TEXT,
99
+ PRIMARY KEY (task_id, round_index, step_index)
100
+ );
101
+
102
+ CREATE TABLE IF NOT EXISTS idempotency_cache (
103
+ key TEXT PRIMARY KEY,
104
+ result_json TEXT NOT NULL,
105
+ created_at TEXT DEFAULT(datetime('now')),
106
+ expires_at TEXT
107
+ );
108
+
109
+ -- ============================================================
110
+ -- TIER 2: KNOWLEDGE GRAPH
111
+ -- ============================================================
112
+
113
+ CREATE TABLE IF NOT EXISTS kg_entities (
114
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
115
+ name TEXT NOT NULL,
116
+ entity_type TEXT NOT NULL,
117
+ properties TEXT DEFAULT '{}',
118
+ community_id INTEGER,
119
+ betweenness REAL DEFAULT 0.0,
120
+ tier TEXT DEFAULT 'working' CHECK(tier IN('peripheral','working','core')),
121
+ access_count INTEGER DEFAULT 0,
122
+ importance REAL DEFAULT 0.5,
123
+ last_accessed TEXT DEFAULT(datetime('now')),
124
+ created_at TEXT DEFAULT(datetime('now')),
125
+ UNIQUE(name, entity_type)
126
+ );
127
+ CREATE INDEX IF NOT EXISTS idx_entities_tier ON kg_entities(tier);
128
+ CREATE INDEX IF NOT EXISTS idx_entities_community ON kg_entities(community_id);
129
+
130
+ CREATE TABLE IF NOT EXISTS kg_edges (
131
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
132
+ source_id INTEGER NOT NULL REFERENCES kg_entities(id) ON DELETE CASCADE,
133
+ target_id INTEGER NOT NULL REFERENCES kg_entities(id) ON DELETE CASCADE,
134
+ relation_type TEXT NOT NULL,
135
+ weight REAL DEFAULT 1.0,
136
+ properties TEXT DEFAULT '{}',
137
+ activation_count INTEGER DEFAULT 0,
138
+ last_activated TEXT DEFAULT(datetime('now')),
139
+ created_at TEXT DEFAULT(datetime('now')),
140
+ UNIQUE(source_id, target_id, relation_type)
141
+ );
142
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON kg_edges(source_id);
143
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON kg_edges(target_id);
144
+
145
+ CREATE TABLE IF NOT EXISTS kg_entity_chunks (
146
+ entity_id INTEGER REFERENCES kg_entities(id) ON DELETE CASCADE,
147
+ chunk_id TEXT NOT NULL,
148
+ relevance REAL DEFAULT 1.0,
149
+ PRIMARY KEY (entity_id, chunk_id)
150
+ );
151
+
152
+ CREATE TABLE IF NOT EXISTS hypotheses (
153
+ id TEXT PRIMARY KEY,
154
+ cascade_id TEXT REFERENCES cascades(id) ON DELETE CASCADE,
155
+ statement TEXT NOT NULL,
156
+ parent_id TEXT,
157
+ affinity REAL DEFAULT 0.5,
158
+ generation INTEGER DEFAULT 0,
159
+ status TEXT DEFAULT 'proposed'
160
+ CHECK(status IN('proposed','testing','supported','refuted','uncertain','archived')),
161
+ supporting TEXT DEFAULT '[]',
162
+ contradicting TEXT DEFAULT '[]',
163
+ mutation_history TEXT DEFAULT '[]',
164
+ cd47_protected INTEGER DEFAULT 0,
165
+ created_at TEXT DEFAULT(datetime('now')),
166
+ updated_at TEXT DEFAULT(datetime('now'))
167
+ );
168
+ CREATE INDEX IF NOT EXISTS idx_hypotheses_cascade ON hypotheses(cascade_id);
169
+ CREATE INDEX IF NOT EXISTS idx_hypotheses_status ON hypotheses(status);
170
+
171
+ -- ============================================================
172
+ -- TIER 2b: A-MEM ZETTELKASTEN LAYER
173
+ -- ============================================================
174
+
175
+ CREATE TABLE IF NOT EXISTS atomic_notes (
176
+ id TEXT PRIMARY KEY,
177
+ content TEXT NOT NULL,
178
+ note_type TEXT NOT NULL DEFAULT 'insight'
179
+ CHECK(note_type IN('insight','connection','question','contradiction','synthesis')),
180
+ source_finding_id TEXT REFERENCES findings(id),
181
+ source_entity_id INTEGER REFERENCES kg_entities(id),
182
+ cascade_id TEXT REFERENCES cascades(id) ON DELETE CASCADE,
183
+ cascade_round INTEGER,
184
+ keywords TEXT DEFAULT '[]',
185
+ maturity TEXT DEFAULT 'isolated_fact'
186
+ CHECK(maturity IN('isolated_fact','connected_fact','principle','mental_model')),
187
+ access_count INTEGER DEFAULT 0,
188
+ last_accessed TEXT DEFAULT(datetime('now')),
189
+ created_at TEXT DEFAULT(datetime('now'))
190
+ );
191
+
192
+ CREATE TABLE IF NOT EXISTS note_links (
193
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
194
+ source_note_id TEXT NOT NULL REFERENCES atomic_notes(id) ON DELETE CASCADE,
195
+ target_note_id TEXT NOT NULL REFERENCES atomic_notes(id) ON DELETE CASCADE,
196
+ link_type TEXT NOT NULL DEFAULT 'relates_to'
197
+ CHECK(link_type IN('relates_to','supports','contradicts','refines','generalizes','exemplifies')),
198
+ strength REAL DEFAULT 1.0,
199
+ bidirectional INTEGER DEFAULT 1,
200
+ created_at TEXT DEFAULT(datetime('now')),
201
+ UNIQUE(source_note_id, target_note_id, link_type)
202
+ );
203
+ CREATE INDEX IF NOT EXISTS idx_note_links_source ON note_links(source_note_id);
204
+ CREATE INDEX IF NOT EXISTS idx_note_links_target ON note_links(target_note_id);
205
+
206
+ -- ============================================================
207
+ -- TIER 3: TRUST & ANALYTICS
208
+ -- ============================================================
209
+
210
+ CREATE TABLE IF NOT EXISTS source_reputation (
211
+ domain TEXT PRIMARY KEY,
212
+ reputation_score REAL DEFAULT 0.5,
213
+ total_entries INTEGER DEFAULT 0,
214
+ admitted_entries INTEGER DEFAULT 0,
215
+ flagged_entries INTEGER DEFAULT 0,
216
+ rejected_entries INTEGER DEFAULT 0,
217
+ last_updated TEXT DEFAULT(datetime('now'))
218
+ );
219
+
220
+ CREATE TABLE IF NOT EXISTS ingestion_audit_log (
221
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
222
+ finding_id TEXT,
223
+ action TEXT CHECK(action IN('admitted','quarantined','rejected')),
224
+ trust_composite REAL,
225
+ signals_json TEXT,
226
+ reason TEXT,
227
+ decided_at TEXT DEFAULT(datetime('now')),
228
+ human_override INTEGER DEFAULT 0
229
+ );
230
+
231
+ CREATE TABLE IF NOT EXISTS metrics (
232
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
233
+ cascade_id TEXT REFERENCES cascades(id) ON DELETE CASCADE,
234
+ round_index INTEGER,
235
+ metric_name TEXT NOT NULL,
236
+ metric_value REAL NOT NULL,
237
+ recorded_at TEXT DEFAULT(datetime('now'))
238
+ );
239
+ CREATE INDEX IF NOT EXISTS idx_metrics_cascade ON metrics(cascade_id, metric_name);
240
+
241
+ -- ============================================================
242
+ -- TIER 4: MEMORY MANAGEMENT (SM-2 + CONSOLIDATION)
243
+ -- ============================================================
244
+
245
+ CREATE TABLE IF NOT EXISTS sm2_schedule (
246
+ item_id TEXT PRIMARY KEY,
247
+ item_type TEXT NOT NULL CHECK(item_type IN('finding','entity','hypothesis')),
248
+ ease_factor REAL DEFAULT 2.5,
249
+ interval_days REAL DEFAULT 1.0,
250
+ repetitions INTEGER DEFAULT 0,
251
+ next_review TEXT DEFAULT(datetime('now')),
252
+ last_reviewed TEXT,
253
+ retrieval_success_rate REAL DEFAULT 0.0
254
+ );
255
+
256
+ CREATE TABLE IF NOT EXISTS consolidation_log (
257
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
258
+ cascade_id TEXT,
259
+ trigger_type TEXT CHECK(trigger_type IN('round_boundary','context_saturation','manual')),
260
+ items_processed INTEGER DEFAULT 0,
261
+ items_promoted INTEGER DEFAULT 0,
262
+ items_demoted INTEGER DEFAULT 0,
263
+ items_pruned INTEGER DEFAULT 0,
264
+ duration_ms INTEGER,
265
+ created_at TEXT DEFAULT(datetime('now'))
266
+ );
267
+
268
+ -- ============================================================
269
+ -- TIER 5: STEER EVENTS & HITL
270
+ -- ============================================================
271
+
272
+ CREATE TABLE IF NOT EXISTS steer_events (
273
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
274
+ cascade_id TEXT NOT NULL REFERENCES cascades(id) ON DELETE CASCADE,
275
+ event_type TEXT NOT NULL
276
+ CHECK(event_type IN('redirect','narrow','broaden','add_question','drop_hypothesis','approve','reject')),
277
+ instruction TEXT NOT NULL,
278
+ target_id TEXT,
279
+ applied INTEGER DEFAULT 0,
280
+ created_at TEXT DEFAULT(datetime('now'))
281
+ );
282
+ CREATE INDEX IF NOT EXISTS idx_steer_cascade ON steer_events(cascade_id, applied);
@@ -0,0 +1,80 @@
1
+ /**
2
+ * A-MEM — Zettelkasten Atomic Notes Layer
3
+ *
4
+ * Each insight becomes an atomic note. Notes are bidirectionally linked
5
+ * based on content relationships. Notes mature over time:
6
+ * isolated_fact → connected_fact → principle → mental_model
7
+ *
8
+ * Built on top of the entity/edge KG — notes reference entities and findings
9
+ * but form their own link structure optimized for emergent understanding.
10
+ */
11
+ export interface AtomicNote {
12
+ id: string;
13
+ content: string;
14
+ noteType: 'insight' | 'connection' | 'question' | 'contradiction' | 'synthesis';
15
+ sourceFindingId: string | null;
16
+ sourceEntityId: number | null;
17
+ cascadeId: string | null;
18
+ cascadeRound: number | null;
19
+ keywords: string[];
20
+ maturity: 'isolated_fact' | 'connected_fact' | 'principle' | 'mental_model';
21
+ accessCount: number;
22
+ lastAccessed: string;
23
+ createdAt: string;
24
+ }
25
+ export interface NoteLink {
26
+ id: number;
27
+ sourceNoteId: string;
28
+ targetNoteId: string;
29
+ linkType: 'relates_to' | 'supports' | 'contradicts' | 'refines' | 'generalizes' | 'exemplifies';
30
+ strength: number;
31
+ bidirectional: boolean;
32
+ createdAt: string;
33
+ }
34
+ /**
35
+ * Create an atomic note from an insight.
36
+ * Content-addressable: same content = same note (idempotent).
37
+ */
38
+ export declare function createNote(content: string, noteType?: AtomicNote['noteType'], keywords?: string[], sourceFindingId?: string, sourceEntityId?: number, cascadeId?: string, cascadeRound?: number): string;
39
+ /**
40
+ * Link two notes bidirectionally.
41
+ */
42
+ export declare function linkNotes(sourceId: string, targetId: string, linkType?: NoteLink['linkType'], strength?: number, bidirectional?: boolean): void;
43
+ /**
44
+ * Get a note and its connections.
45
+ */
46
+ export declare function getNote(noteId: string): {
47
+ note: AtomicNote;
48
+ links: any[];
49
+ } | null;
50
+ /**
51
+ * Find notes by keyword.
52
+ */
53
+ export declare function searchNotes(keyword: string, limit?: number): AtomicNote[];
54
+ /**
55
+ * Auto-extract notes from a finding.
56
+ * Creates an atomic note from the claim and links it to related entities.
57
+ */
58
+ export declare function extractNotesFromFinding(findingId: string, cascadeId: string, cascadeRound: number): string[];
59
+ /**
60
+ * Update note maturity based on connection count.
61
+ * Dreyfus progression: isolated_fact → connected_fact → principle → mental_model
62
+ */
63
+ export declare function updateMaturity(): {
64
+ promoted: number;
65
+ };
66
+ /**
67
+ * Find orphan notes (no links) — candidates for linking or pruning.
68
+ */
69
+ export declare function findOrphanNotes(): AtomicNote[];
70
+ /**
71
+ * Get note graph statistics.
72
+ */
73
+ export declare function getNoteStats(): {
74
+ totalNotes: number;
75
+ maturityCounts: Record<string, number>;
76
+ typeCounts: Record<string, number>;
77
+ totalLinks: number;
78
+ orphanCount: number;
79
+ };
80
+ //# sourceMappingURL=amem.d.ts.map
@@ -0,0 +1,190 @@
1
+ /**
2
+ * A-MEM — Zettelkasten Atomic Notes Layer
3
+ *
4
+ * Each insight becomes an atomic note. Notes are bidirectionally linked
5
+ * based on content relationships. Notes mature over time:
6
+ * isolated_fact → connected_fact → principle → mental_model
7
+ *
8
+ * Built on top of the entity/edge KG — notes reference entities and findings
9
+ * but form their own link structure optimized for emergent understanding.
10
+ */
11
+ import { getDb, contentHash } from '../db/index.js';
12
+ // --- Note CRUD ---
13
+ /**
14
+ * Create an atomic note from an insight.
15
+ * Content-addressable: same content = same note (idempotent).
16
+ */
17
+ export function createNote(content, noteType = 'insight', keywords = [], sourceFindingId, sourceEntityId, cascadeId, cascadeRound) {
18
+ const db = getDb();
19
+ const id = contentHash(content);
20
+ db.prepare(`INSERT INTO atomic_notes
21
+ (id, content, note_type, source_finding_id, source_entity_id, cascade_id, cascade_round, keywords)
22
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
23
+ ON CONFLICT(id) DO UPDATE SET
24
+ access_count = access_count + 1,
25
+ last_accessed = datetime('now'),
26
+ keywords = json_patch(keywords, excluded.keywords)`)
27
+ .run(id, content, noteType, sourceFindingId ?? null, sourceEntityId ?? null, cascadeId ?? null, cascadeRound ?? null, JSON.stringify(keywords));
28
+ return id;
29
+ }
30
+ /**
31
+ * Link two notes bidirectionally.
32
+ */
33
+ export function linkNotes(sourceId, targetId, linkType = 'relates_to', strength = 1.0, bidirectional = true) {
34
+ const db = getDb();
35
+ db.prepare(`INSERT INTO note_links (source_note_id, target_note_id, link_type, strength, bidirectional)
36
+ VALUES (?, ?, ?, ?, ?)
37
+ ON CONFLICT(source_note_id, target_note_id, link_type) DO UPDATE SET
38
+ strength = MAX(strength, excluded.strength)`)
39
+ .run(sourceId, targetId, linkType, strength, bidirectional ? 1 : 0);
40
+ // If bidirectional, create the reverse link too
41
+ if (bidirectional) {
42
+ db.prepare(`INSERT INTO note_links (source_note_id, target_note_id, link_type, strength, bidirectional)
43
+ VALUES (?, ?, ?, ?, ?)
44
+ ON CONFLICT(source_note_id, target_note_id, link_type) DO UPDATE SET
45
+ strength = MAX(strength, excluded.strength)`)
46
+ .run(targetId, sourceId, linkType, strength, 1);
47
+ }
48
+ }
49
+ /**
50
+ * Get a note and its connections.
51
+ */
52
+ export function getNote(noteId) {
53
+ const db = getDb();
54
+ const row = db.prepare('SELECT * FROM atomic_notes WHERE id = ?').get(noteId);
55
+ if (!row)
56
+ return null;
57
+ // Touch access count
58
+ db.prepare('UPDATE atomic_notes SET access_count = access_count + 1, last_accessed = datetime(\'now\') WHERE id = ?')
59
+ .run(noteId);
60
+ const links = db.prepare(`SELECT nl.*, an.content as linked_content, an.note_type as linked_type
61
+ FROM note_links nl
62
+ JOIN atomic_notes an ON an.id = nl.target_note_id
63
+ WHERE nl.source_note_id = ?
64
+ ORDER BY nl.strength DESC`)
65
+ .all(noteId);
66
+ return { note: rowToNote(row), links };
67
+ }
68
+ /**
69
+ * Find notes by keyword.
70
+ */
71
+ export function searchNotes(keyword, limit = 20) {
72
+ const db = getDb();
73
+ // Search in keywords JSON array and content
74
+ const rows = db.prepare(`SELECT * FROM atomic_notes
75
+ WHERE keywords LIKE ? OR content LIKE ?
76
+ ORDER BY access_count DESC, created_at DESC LIMIT ?`)
77
+ .all(`%${keyword}%`, `%${keyword}%`, limit);
78
+ return rows.map(rowToNote);
79
+ }
80
+ /**
81
+ * Auto-extract notes from a finding.
82
+ * Creates an atomic note from the claim and links it to related entities.
83
+ */
84
+ export function extractNotesFromFinding(findingId, cascadeId, cascadeRound) {
85
+ const db = getDb();
86
+ const finding = db.prepare('SELECT * FROM findings WHERE id = ?').get(findingId);
87
+ if (!finding)
88
+ return [];
89
+ const noteIds = [];
90
+ // Create a note from the claim
91
+ const noteId = createNote(finding.claim, 'insight', [], // Keywords could be extracted by LLM
92
+ findingId, undefined, cascadeId, cascadeRound);
93
+ noteIds.push(noteId);
94
+ // Link to any existing notes with overlapping content
95
+ const existing = db.prepare(`SELECT id, content FROM atomic_notes
96
+ WHERE id != ? AND cascade_id = ?
97
+ ORDER BY created_at DESC LIMIT 50`)
98
+ .all(noteId, cascadeId);
99
+ for (const other of existing) {
100
+ // Simple keyword overlap check for auto-linking
101
+ const words1 = new Set(finding.claim.toLowerCase().split(/\s+/).filter((w) => w.length > 5));
102
+ const words2 = new Set(other.content.toLowerCase().split(/\s+/).filter((w) => w.length > 5));
103
+ let overlap = 0;
104
+ for (const w of words1) {
105
+ if (words2.has(w))
106
+ overlap++;
107
+ }
108
+ if (overlap >= 2) {
109
+ linkNotes(noteId, other.id, 'relates_to', Math.min(1.0, overlap * 0.2));
110
+ }
111
+ }
112
+ return noteIds;
113
+ }
114
+ /**
115
+ * Update note maturity based on connection count.
116
+ * Dreyfus progression: isolated_fact → connected_fact → principle → mental_model
117
+ */
118
+ export function updateMaturity() {
119
+ const db = getDb();
120
+ let promoted = 0;
121
+ // Connected fact: ≥2 links
122
+ const r1 = db.prepare(`UPDATE atomic_notes SET maturity = 'connected_fact'
123
+ WHERE maturity = 'isolated_fact'
124
+ AND (SELECT COUNT(*) FROM note_links WHERE source_note_id = id OR target_note_id = id) >= 2`).run();
125
+ promoted += r1.changes;
126
+ // Principle: ≥5 links AND accessed ≥3 times
127
+ const r2 = db.prepare(`UPDATE atomic_notes SET maturity = 'principle'
128
+ WHERE maturity = 'connected_fact'
129
+ AND (SELECT COUNT(*) FROM note_links WHERE source_note_id = id OR target_note_id = id) >= 5
130
+ AND access_count >= 3`).run();
131
+ promoted += r2.changes;
132
+ // Mental model: ≥10 links AND accessed ≥10 times AND note_type = 'synthesis'
133
+ const r3 = db.prepare(`UPDATE atomic_notes SET maturity = 'mental_model'
134
+ WHERE maturity = 'principle'
135
+ AND (SELECT COUNT(*) FROM note_links WHERE source_note_id = id OR target_note_id = id) >= 10
136
+ AND access_count >= 10
137
+ AND note_type = 'synthesis'`).run();
138
+ promoted += r3.changes;
139
+ return { promoted };
140
+ }
141
+ /**
142
+ * Find orphan notes (no links) — candidates for linking or pruning.
143
+ */
144
+ export function findOrphanNotes() {
145
+ const db = getDb();
146
+ const rows = db.prepare(`SELECT * FROM atomic_notes
147
+ WHERE id NOT IN (SELECT source_note_id FROM note_links)
148
+ AND id NOT IN (SELECT target_note_id FROM note_links)
149
+ ORDER BY created_at DESC`)
150
+ .all();
151
+ return rows.map(rowToNote);
152
+ }
153
+ /**
154
+ * Get note graph statistics.
155
+ */
156
+ export function getNoteStats() {
157
+ const db = getDb();
158
+ const totalNotes = db.prepare('SELECT COUNT(*) as n FROM atomic_notes').get().n;
159
+ const totalLinks = db.prepare('SELECT COUNT(*) as n FROM note_links').get().n;
160
+ const orphanCount = db.prepare(`SELECT COUNT(*) as n FROM atomic_notes
161
+ WHERE id NOT IN (SELECT source_note_id FROM note_links)
162
+ AND id NOT IN (SELECT target_note_id FROM note_links)`).get().n;
163
+ const maturityRows = db.prepare('SELECT maturity, COUNT(*) as n FROM atomic_notes GROUP BY maturity').all();
164
+ const maturityCounts = {};
165
+ for (const r of maturityRows)
166
+ maturityCounts[r.maturity] = r.n;
167
+ const typeRows = db.prepare('SELECT note_type, COUNT(*) as n FROM atomic_notes GROUP BY note_type').all();
168
+ const typeCounts = {};
169
+ for (const r of typeRows)
170
+ typeCounts[r.note_type] = r.n;
171
+ return { totalNotes, maturityCounts, typeCounts, totalLinks, orphanCount };
172
+ }
173
+ // --- Helpers ---
174
+ function rowToNote(row) {
175
+ return {
176
+ id: row.id,
177
+ content: row.content,
178
+ noteType: row.note_type,
179
+ sourceFindingId: row.source_finding_id,
180
+ sourceEntityId: row.source_entity_id,
181
+ cascadeId: row.cascade_id,
182
+ cascadeRound: row.cascade_round,
183
+ keywords: JSON.parse(row.keywords || '[]'),
184
+ maturity: row.maturity,
185
+ accessCount: row.access_count,
186
+ lastAccessed: row.last_accessed,
187
+ createdAt: row.created_at,
188
+ };
189
+ }
190
+ //# sourceMappingURL=amem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amem.js","sourceRoot":"","sources":["../../src/graph/amem.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Knowledge Graph — Entity CRUD + Recursive CTE Traversal
3
+ *
4
+ * Edge-table pattern with SQLite recursive CTEs.
5
+ * Performance: 10K edges <50ms, 100K 50-500ms.
6
+ * ALWAYS ≤3 hop depth limit.
7
+ */
8
+ export interface Entity {
9
+ id: number;
10
+ name: string;
11
+ entityType: string;
12
+ properties: Record<string, any>;
13
+ communityId: number | null;
14
+ betweenness: number;
15
+ tier: 'peripheral' | 'working' | 'core';
16
+ accessCount: number;
17
+ importance: number;
18
+ lastAccessed: string;
19
+ createdAt: string;
20
+ }
21
+ export interface Edge {
22
+ id: number;
23
+ sourceId: number;
24
+ targetId: number;
25
+ relationType: string;
26
+ weight: number;
27
+ properties: Record<string, any>;
28
+ activationCount: number;
29
+ lastActivated: string;
30
+ createdAt: string;
31
+ }
32
+ export interface TraversalResult {
33
+ entity: Entity;
34
+ depth: number;
35
+ path: string[];
36
+ edgeType: string;
37
+ }
38
+ export declare function upsertEntity(name: string, entityType: string, properties?: Record<string, any>, tier?: Entity['tier'], importance?: number): number;
39
+ export declare function getEntity(name: string, entityType: string): Entity | null;
40
+ export declare function getEntityById(id: number): Entity | null;
41
+ export declare function searchEntities(query: string, limit?: number): Entity[];
42
+ export declare function getEntitiesByTier(tier: Entity['tier']): Entity[];
43
+ export declare function upsertEdge(sourceId: number, targetId: number, relationType: string, weight?: number, properties?: Record<string, any>): number;
44
+ export declare function getEdgesFrom(entityId: number): Edge[];
45
+ export declare function getEdgesTo(entityId: number): Edge[];
46
+ /**
47
+ * Traverse the graph from a starting entity using recursive CTE.
48
+ * ALWAYS bounded to maxHops ≤ 3.
49
+ */
50
+ export declare function traverse(startId: number, maxHops?: number, minWeight?: number, relationFilter?: string): TraversalResult[];
51
+ /**
52
+ * Find orphan entities (no incoming or outgoing edges).
53
+ */
54
+ export declare function findOrphans(): Entity[];
55
+ /**
56
+ * Get graph statistics.
57
+ */
58
+ export declare function getGraphStats(): {
59
+ entityCount: number;
60
+ edgeCount: number;
61
+ avgDegree: number;
62
+ tierCounts: Record<string, number>;
63
+ communityCounts: number;
64
+ orphanCount: number;
65
+ };
66
+ //# sourceMappingURL=entities.d.ts.map