wyrm-mcp 3.2.0 → 3.3.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.
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Wyrm Knowledge Graph — Entity & Relationship Management
3
+ *
4
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
5
+ * @license Proprietary - See LICENSE file for details.
6
+ *
7
+ * Manual-first knowledge graph with provenance tracking:
8
+ * - Named entities with types and metadata
9
+ * - Typed, directed relationships between entities
10
+ * - Aliases for deduplication
11
+ * - Graph traversal via recursive CTEs (neighborhood, paths)
12
+ * - FTS5 search on entity names
13
+ */
14
+ const MAX_DEPTH = 5;
15
+ const MAX_RESULTS = 200;
16
+ /**
17
+ * Knowledge graph operations backed by SQLite.
18
+ */
19
+ export class KnowledgeGraph {
20
+ db;
21
+ constructor(db) {
22
+ this.db = db;
23
+ }
24
+ // ==================== ENTITIES ====================
25
+ addEntity(projectId, name, type, metadata, createdBy) {
26
+ const result = this.db.prepare(`
27
+ INSERT INTO entities (project_id, name, type, metadata, created_by)
28
+ VALUES (?, ?, ?, ?, ?)
29
+ `).run(projectId, name.trim(), type.trim(), metadata ?? null, createdBy ?? 'local');
30
+ return this.db.prepare('SELECT * FROM entities WHERE id = ?').get(result.lastInsertRowid);
31
+ }
32
+ getEntity(id) {
33
+ return this.db.prepare('SELECT * FROM entities WHERE id = ?').get(id) ?? null;
34
+ }
35
+ findEntity(projectId, name, type) {
36
+ if (type) {
37
+ return this.db.prepare('SELECT * FROM entities WHERE project_id = ? AND name = ? AND type = ?').get(projectId, name, type) ?? null;
38
+ }
39
+ return this.db.prepare('SELECT * FROM entities WHERE project_id = ? AND name = ?').get(projectId, name) ?? null;
40
+ }
41
+ updateEntity(id, updates) {
42
+ const entity = this.getEntity(id);
43
+ if (!entity)
44
+ return null;
45
+ this.db.prepare(`
46
+ UPDATE entities SET
47
+ name = COALESCE(?, name),
48
+ type = COALESCE(?, type),
49
+ metadata = COALESCE(?, metadata),
50
+ updated_at = datetime('now')
51
+ WHERE id = ?
52
+ `).run(updates.name ?? null, updates.type ?? null, updates.metadata ?? null, id);
53
+ return this.getEntity(id);
54
+ }
55
+ deleteEntity(id) {
56
+ const result = this.db.prepare('DELETE FROM entities WHERE id = ?').run(id);
57
+ return result.changes > 0;
58
+ }
59
+ listEntities(projectId, options) {
60
+ let sql = 'SELECT * FROM entities WHERE project_id = ?';
61
+ const params = [projectId];
62
+ if (options?.type) {
63
+ sql += ' AND type = ?';
64
+ params.push(options.type);
65
+ }
66
+ sql += ' ORDER BY name ASC';
67
+ sql += ` LIMIT ? OFFSET ?`;
68
+ params.push(options?.limit ?? 50, options?.offset ?? 0);
69
+ return this.db.prepare(sql).all(...params);
70
+ }
71
+ searchEntities(projectId, query, limit = 20) {
72
+ // Try FTS first, fall back to LIKE
73
+ try {
74
+ const ftsResults = this.db.prepare(`
75
+ SELECT e.* FROM entities e
76
+ JOIN entities_fts f ON f.rowid = e.id
77
+ WHERE f.entities_fts MATCH ? AND e.project_id = ?
78
+ LIMIT ?
79
+ `).all(query, projectId, limit);
80
+ if (ftsResults.length > 0)
81
+ return ftsResults;
82
+ }
83
+ catch {
84
+ // FTS query syntax error — fall back to LIKE
85
+ }
86
+ return this.db.prepare(`
87
+ SELECT * FROM entities WHERE project_id = ? AND (name LIKE ? OR type LIKE ?)
88
+ ORDER BY name ASC LIMIT ?
89
+ `).all(projectId, `%${query}%`, `%${query}%`, limit);
90
+ }
91
+ // ==================== ALIASES ====================
92
+ addAlias(entityId, alias) {
93
+ this.db.prepare('INSERT OR IGNORE INTO entity_aliases (entity_id, alias) VALUES (?, ?)').run(entityId, alias.trim());
94
+ }
95
+ getAliases(entityId) {
96
+ const rows = this.db.prepare('SELECT alias FROM entity_aliases WHERE entity_id = ?').all(entityId);
97
+ return rows.map(r => r.alias);
98
+ }
99
+ findByAlias(projectId, alias) {
100
+ return this.db.prepare(`
101
+ SELECT e.* FROM entities e
102
+ JOIN entity_aliases a ON a.entity_id = e.id
103
+ WHERE e.project_id = ? AND a.alias = ?
104
+ `).get(projectId, alias) ?? null;
105
+ }
106
+ // ==================== RELATIONSHIPS ====================
107
+ addRelationship(projectId, sourceId, targetId, type, options) {
108
+ const result = this.db.prepare(`
109
+ INSERT INTO relationships (
110
+ project_id, source_entity_id, target_entity_id, relationship_type,
111
+ weight, confidence, source_memory, extraction_method, metadata, created_by
112
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
113
+ `).run(projectId, sourceId, targetId, type.trim(), options?.weight ?? 1.0, options?.confidence ?? 1.0, options?.sourceMemory ?? null, options?.extractionMethod ?? 'manual', options?.metadata ?? null, options?.createdBy ?? 'local');
114
+ return this.db.prepare('SELECT * FROM relationships WHERE id = ?').get(result.lastInsertRowid);
115
+ }
116
+ getRelationships(entityId, direction = 'both') {
117
+ switch (direction) {
118
+ case 'outgoing':
119
+ return this.db.prepare('SELECT * FROM relationships WHERE source_entity_id = ?').all(entityId);
120
+ case 'incoming':
121
+ return this.db.prepare('SELECT * FROM relationships WHERE target_entity_id = ?').all(entityId);
122
+ case 'both':
123
+ return this.db.prepare('SELECT * FROM relationships WHERE source_entity_id = ? OR target_entity_id = ?').all(entityId, entityId);
124
+ }
125
+ }
126
+ deleteRelationship(id) {
127
+ return this.db.prepare('DELETE FROM relationships WHERE id = ?').run(id).changes > 0;
128
+ }
129
+ // ==================== GRAPH QUERIES ====================
130
+ /**
131
+ * Get neighborhood — all nodes within N hops of a given entity.
132
+ * Uses recursive CTE with cycle detection.
133
+ */
134
+ getNeighborhood(entityId, maxDepth = 2) {
135
+ const depth = Math.min(maxDepth, MAX_DEPTH);
136
+ const nodes = this.db.prepare(`
137
+ WITH RECURSIVE neighbors(id, depth, visited) AS (
138
+ SELECT ?, 0, ',' || ? || ','
139
+ UNION ALL
140
+ SELECT
141
+ CASE WHEN r.source_entity_id = n.id THEN r.target_entity_id ELSE r.source_entity_id END,
142
+ n.depth + 1,
143
+ n.visited || CASE WHEN r.source_entity_id = n.id THEN r.target_entity_id ELSE r.source_entity_id END || ','
144
+ FROM neighbors n
145
+ JOIN relationships r ON (r.source_entity_id = n.id OR r.target_entity_id = n.id)
146
+ WHERE n.depth < ?
147
+ AND n.visited NOT LIKE '%,' || CASE WHEN r.source_entity_id = n.id THEN r.target_entity_id ELSE r.source_entity_id END || ',%'
148
+ )
149
+ SELECT DISTINCT e.id, e.name, e.type, e.metadata, MIN(n.depth) as depth
150
+ FROM neighbors n
151
+ JOIN entities e ON e.id = n.id
152
+ GROUP BY e.id
153
+ ORDER BY depth ASC
154
+ LIMIT ?
155
+ `).all(entityId, entityId, depth, MAX_RESULTS);
156
+ // Get edges between all discovered nodes
157
+ const nodeIds = nodes.map(n => n.id);
158
+ if (nodeIds.length === 0)
159
+ return { nodes: [], edges: [] };
160
+ const placeholders = nodeIds.map(() => '?').join(',');
161
+ const edges = this.db.prepare(`
162
+ SELECT r.source_entity_id as source_id, s.name as source_name,
163
+ r.target_entity_id as target_id, t.name as target_name,
164
+ r.relationship_type, r.weight
165
+ FROM relationships r
166
+ JOIN entities s ON s.id = r.source_entity_id
167
+ JOIN entities t ON t.id = r.target_entity_id
168
+ WHERE r.source_entity_id IN (${placeholders})
169
+ AND r.target_entity_id IN (${placeholders})
170
+ `).all(...nodeIds, ...nodeIds);
171
+ return { nodes, edges };
172
+ }
173
+ /**
174
+ * Find path between two entities using BFS with cycle detection.
175
+ * Returns the shortest path as a list of entity IDs, or null if no path exists.
176
+ */
177
+ findPath(sourceId, targetId, maxDepth = 5) {
178
+ const depth = Math.min(maxDepth, MAX_DEPTH);
179
+ const rows = this.db.prepare(`
180
+ WITH RECURSIVE path_finder(id, path, depth) AS (
181
+ SELECT ?, CAST(? AS TEXT), 0
182
+ UNION ALL
183
+ SELECT
184
+ CASE WHEN r.source_entity_id = pf.id THEN r.target_entity_id ELSE r.source_entity_id END,
185
+ pf.path || ',' || CASE WHEN r.source_entity_id = pf.id THEN r.target_entity_id ELSE r.source_entity_id END,
186
+ pf.depth + 1
187
+ FROM path_finder pf
188
+ JOIN relationships r ON (r.source_entity_id = pf.id OR r.target_entity_id = pf.id)
189
+ WHERE pf.depth < ?
190
+ AND pf.path NOT LIKE '%,' || CASE WHEN r.source_entity_id = pf.id THEN r.target_entity_id ELSE r.source_entity_id END || ',%'
191
+ AND INSTR(',' || pf.path || ',', ',' || CASE WHEN r.source_entity_id = pf.id THEN r.target_entity_id ELSE r.source_entity_id END || ',') = 0
192
+ )
193
+ SELECT path FROM path_finder WHERE id = ? LIMIT 1
194
+ `).all(sourceId, sourceId, depth, targetId);
195
+ if (rows.length === 0)
196
+ return null;
197
+ return rows[0].path.split(',').map(Number);
198
+ }
199
+ /**
200
+ * Merge two entities: moves all relationships and aliases from source to target,
201
+ * adds source name as alias on target, then deletes source.
202
+ */
203
+ mergeEntities(sourceId, targetId) {
204
+ const source = this.getEntity(sourceId);
205
+ const target = this.getEntity(targetId);
206
+ if (!source || !target)
207
+ return null;
208
+ const merge = this.db.transaction(() => {
209
+ // Add source name as alias on target
210
+ this.addAlias(targetId, source.name);
211
+ // Move source's aliases to target
212
+ this.db.prepare('UPDATE entity_aliases SET entity_id = ? WHERE entity_id = ?').run(targetId, sourceId);
213
+ // Redirect relationships from source to target
214
+ this.db.prepare('UPDATE relationships SET source_entity_id = ? WHERE source_entity_id = ?').run(targetId, sourceId);
215
+ this.db.prepare('UPDATE relationships SET target_entity_id = ? WHERE target_entity_id = ?').run(targetId, sourceId);
216
+ // Remove self-loops created by merge
217
+ this.db.prepare('DELETE FROM relationships WHERE source_entity_id = target_entity_id').run();
218
+ // Delete source entity
219
+ this.deleteEntity(sourceId);
220
+ });
221
+ merge();
222
+ return this.getEntity(targetId);
223
+ }
224
+ // ==================== STATS ====================
225
+ getStats(projectId) {
226
+ const entities = this.db.prepare('SELECT COUNT(*) as c FROM entities WHERE project_id = ?').get(projectId).c;
227
+ const relationships = this.db.prepare('SELECT COUNT(*) as c FROM relationships WHERE project_id = ?').get(projectId).c;
228
+ const typeRows = this.db.prepare('SELECT type, COUNT(*) as cnt FROM entities WHERE project_id = ? GROUP BY type').all(projectId);
229
+ const entityTypes = {};
230
+ for (const r of typeRows)
231
+ entityTypes[r.type] = r.cnt;
232
+ return { entities, relationships, entityTypes };
233
+ }
234
+ }
235
+ //# sourceMappingURL=knowledge-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge-graph.js","sourceRoot":"","sources":["../src/knowledge-graph.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAoDH,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,MAAM,WAAW,GAAG,GAAG,CAAC;AAExB;;GAEG;AACH,MAAM,OAAO,cAAc;IACjB,EAAE,CAAoB;IAE9B,YAAY,EAAqB;QAC/B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,qDAAqD;IAErD,SAAS,CAAC,SAAiB,EAAE,IAAY,EAAE,IAAY,EAAE,QAAiB,EAAE,SAAkB;QAC5F,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAG9B,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,IAAI,IAAI,EAAE,SAAS,IAAI,OAAO,CAAC,CAAC;QAEpF,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAW,CAAC;IACtG,CAAC;IAED,SAAS,CAAC,EAAU;QAClB,OAAQ,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAY,IAAI,IAAI,CAAC;IAC5F,CAAC;IAED,UAAU,CAAC,SAAiB,EAAE,IAAY,EAAE,IAAa;QACvD,IAAI,IAAI,EAAE,CAAC;YACT,OAAQ,IAAI,CAAC,EAAE,CAAC,OAAO,CACrB,uEAAuE,CACxE,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAY,IAAI,IAAI,CAAC;QAClD,CAAC;QACD,OAAQ,IAAI,CAAC,EAAE,CAAC,OAAO,CACrB,0DAA0D,CAC3D,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAY,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED,YAAY,CAAC,EAAU,EAAE,OAA4D;QACnF,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAClC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEzB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;KAOf,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;QAEjF,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC5B,CAAC;IAED,YAAY,CAAC,EAAU;QACrB,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5E,OAAO,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,YAAY,CAAC,SAAiB,EAAE,OAA4D;QAC1F,IAAI,GAAG,GAAG,6CAA6C,CAAC;QACxD,MAAM,MAAM,GAAwB,CAAC,SAAS,CAAC,CAAC;QAEhD,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;YAClB,GAAG,IAAI,eAAe,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QACD,GAAG,IAAI,oBAAoB,CAAC;QAC5B,GAAG,IAAI,mBAAmB,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;QAExD,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAa,CAAC;IACzD,CAAC;IAED,cAAc,CAAC,SAAiB,EAAE,KAAa,EAAE,QAAgB,EAAE;QACjE,mCAAmC;QACnC,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;OAKlC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAa,CAAC;YAC5C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,UAAU,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,6CAA6C;QAC/C,CAAC;QAED,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAGtB,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,KAAK,CAAa,CAAC;IACnE,CAAC;IAED,oDAAoD;IAEpD,QAAQ,CAAC,QAAgB,EAAE,KAAa;QACtC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACvH,CAAC;IAED,UAAU,CAAC,QAAgB;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,QAAQ,CAA6B,CAAC;QAC/H,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,WAAW,CAAC,SAAiB,EAAE,KAAa;QAC1C,OAAQ,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;KAIvB,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAY,IAAI,IAAI,CAAC;IAC9C,CAAC;IAED,0DAA0D;IAE1D,eAAe,CACb,SAAiB,EACjB,QAAgB,EAChB,QAAgB,EAChB,IAAY,EACZ,OAOC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;KAK9B,CAAC,CAAC,GAAG,CACJ,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,EAC1C,OAAO,EAAE,MAAM,IAAI,GAAG,EACtB,OAAO,EAAE,UAAU,IAAI,GAAG,EAC1B,OAAO,EAAE,YAAY,IAAI,IAAI,EAC7B,OAAO,EAAE,gBAAgB,IAAI,QAAQ,EACrC,OAAO,EAAE,QAAQ,IAAI,IAAI,EACzB,OAAO,EAAE,SAAS,IAAI,OAAO,CAC9B,CAAC;QAEF,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAiB,CAAC;IACjH,CAAC;IAED,gBAAgB,CAAC,QAAgB,EAAE,YAA8C,MAAM;QACrF,QAAQ,SAAS,EAAE,CAAC;YAClB,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAmB,CAAC;YACnH,KAAK,UAAU;gBACb,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,wDAAwD,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAmB,CAAC;YACnH,KAAK,MAAM;gBACT,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,gFAAgF,CACjF,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAmB,CAAC;QAChD,CAAC;IACH,CAAC;IAED,kBAAkB,CAAC,EAAU;QAC3B,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC;IACvF,CAAC;IAED,0DAA0D;IAE1D;;;OAGG;IACH,eAAe,CAAC,QAAgB,EAAE,WAAmB,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAE5C,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;;;;;KAmB7B,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,CAAgB,CAAC;QAE9D,yCAAyC;QACzC,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAE1D,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;qCAOG,YAAY;qCACZ,YAAY;KAC5C,CAAC,CAAC,GAAG,CAAC,GAAG,OAAO,EAAE,GAAG,OAAO,CAAgB,CAAC;QAE9C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1B,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAC,QAAgB,EAAE,QAAgB,EAAE,WAAmB,CAAC;QAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAE5C,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;;;;;;;;KAe5B,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,CAA4B,CAAC;QAEvE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,QAAgB,EAAE,QAAgB;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAEpC,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;YACrC,qCAAqC;YACrC,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAErC,kCAAkC;YAClC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAEvG,+CAA+C;YAC/C,IAAI,CAAC,EAAE,CAAC,OAAO,CACb,0EAA0E,CAC3E,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,CACb,0EAA0E,CAC3E,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAE1B,qCAAqC;YACrC,IAAI,CAAC,EAAE,CAAC,OAAO,CACb,qEAAqE,CACtE,CAAC,GAAG,EAAE,CAAC;YAER,uBAAuB;YACvB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,KAAK,EAAE,CAAC;QAER,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;IAED,kDAAkD;IAElD,QAAQ,CAAC,SAAiB;QACxB,MAAM,QAAQ,GAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CAC/B,yDAAyD,CAC1D,CAAC,GAAG,CAAC,SAAS,CAAmB,CAAC,CAAC,CAAC;QAErC,MAAM,aAAa,GAAI,IAAI,CAAC,EAAE,CAAC,OAAO,CACpC,8DAA8D,CAC/D,CAAC,GAAG,CAAC,SAAS,CAAmB,CAAC,CAAC,CAAC;QAErC,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC9B,+EAA+E,CAChF,CAAC,GAAG,CAAC,SAAS,CAAyC,CAAC;QAEzD,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,KAAK,MAAM,CAAC,IAAI,QAAQ;YAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;QAEtD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Wyrm Memory Artifacts — Distilled knowledge for AI intelligence amplification.
3
+ *
4
+ * Stores problem-solution records, lessons, patterns, and anti-patterns that
5
+ * help AI models recall what worked in the past and apply it to new tasks.
6
+ *
7
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
8
+ * @license Proprietary - See LICENSE file for details.
9
+ */
10
+ import type Database from 'better-sqlite3';
11
+ export type ArtifactKind = 'reasoning_trace' | 'lesson' | 'pattern' | 'anti_pattern' | 'heuristic';
12
+ export type ArtifactOutcome = 'positive' | 'negative' | 'neutral';
13
+ export interface MemoryArtifact {
14
+ id: number;
15
+ project_id: number;
16
+ kind: ArtifactKind;
17
+ problem: string;
18
+ constraints: string | null;
19
+ validated_fix: string | null;
20
+ why_it_worked: string | null;
21
+ outcome: ArtifactOutcome;
22
+ source_session_id: number | null;
23
+ tags: string | null;
24
+ confidence: number;
25
+ last_validated_at: string;
26
+ supersedes_id: number | null;
27
+ needs_review: number;
28
+ reuse_count: number;
29
+ reuse_success_count: number;
30
+ reuse_failure_count: number;
31
+ created_by: string;
32
+ created_at: string;
33
+ updated_at: string;
34
+ }
35
+ export interface AddArtifactOptions {
36
+ kind: ArtifactKind;
37
+ problem: string;
38
+ constraints?: string;
39
+ validatedFix?: string;
40
+ whyItWorked?: string;
41
+ outcome?: ArtifactOutcome;
42
+ sourceSessionId?: number;
43
+ tags?: string[];
44
+ confidence?: number;
45
+ createdBy?: string;
46
+ needsReview?: number;
47
+ }
48
+ export interface RecallResult {
49
+ artifact: MemoryArtifact;
50
+ relevance_score: number;
51
+ match_type: 'fts' | 'tag' | 'both';
52
+ }
53
+ export interface ContextBriefSection {
54
+ heading: string;
55
+ items: string[];
56
+ source: string;
57
+ }
58
+ export declare class MemoryArtifacts {
59
+ private db;
60
+ constructor(db: Database.Database);
61
+ add(projectId: number, opts: AddArtifactOptions): MemoryArtifact;
62
+ get(id: number): MemoryArtifact | null;
63
+ update(id: number, updates: Partial<Pick<MemoryArtifact, 'confidence' | 'validated_fix' | 'why_it_worked' | 'last_validated_at' | 'needs_review'>>): MemoryArtifact | null;
64
+ /** Mark an artifact as superseded by a newer/better one. */
65
+ markSuperseded(oldId: number, newId: number): void;
66
+ /** Record that an artifact was used and whether it helped. */
67
+ recordFeedback(id: number, success: boolean): void;
68
+ /**
69
+ * 2-stage retrieval: FTS candidates → sort by weighted relevance.
70
+ * Excludes superseded artifacts and those below minConfidence.
71
+ */
72
+ recall(projectId: number, query: string, opts?: {
73
+ kind?: ArtifactKind;
74
+ limit?: number;
75
+ minConfidence?: number;
76
+ }): RecallResult[];
77
+ private searchByFts;
78
+ private searchByTags;
79
+ listRecent(projectId: number, limit?: number, kind?: ArtifactKind): MemoryArtifact[];
80
+ /**
81
+ * Assemble an optimized memory brief for injection into an AI model's context.
82
+ * Excludes credentials/sensitive content. Groups by kind. Deduplicates.
83
+ * Returns sections with headings + items, plus raw text for direct injection.
84
+ */
85
+ buildContextBrief(projectId: number, task: string, opts?: {
86
+ maxItems?: number;
87
+ kinds?: ArtifactKind[];
88
+ minConfidence?: number;
89
+ }): {
90
+ sections: ContextBriefSection[];
91
+ text: string;
92
+ sourceIds: number[];
93
+ };
94
+ getStats(projectId: number): {
95
+ total: number;
96
+ byKind: Record<string, number>;
97
+ avgConfidence: number;
98
+ supersededCount: number;
99
+ };
100
+ listAll(projectId: number, opts?: {
101
+ kind?: ArtifactKind;
102
+ includeSuperseded?: boolean;
103
+ limit?: number;
104
+ }): MemoryArtifact[];
105
+ }
106
+ //# sourceMappingURL=memory-artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-artifacts.d.ts","sourceRoot":"","sources":["../src/memory-artifacts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Wyrm Memory Artifacts — Distilled knowledge for AI intelligence amplification.
3
+ *
4
+ * Stores problem-solution records, lessons, patterns, and anti-patterns that
5
+ * help AI models recall what worked in the past and apply it to new tasks.
6
+ *
7
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
8
+ * @license Proprietary - See LICENSE file for details.
9
+ */
10
+ /** Maximum artifacts returned from a recall query (before reranking) */
11
+ const MAX_CANDIDATES = 40;
12
+ /** Maximum artifacts included in a context brief */
13
+ const MAX_BRIEF_ITEMS = 10;
14
+ /** Minimum confidence to include in auto-generated briefs */
15
+ const MIN_BRIEF_CONFIDENCE = 0.3;
16
+ export class MemoryArtifacts {
17
+ db;
18
+ constructor(db) {
19
+ this.db = db;
20
+ }
21
+ // ==================== CRUD ====================
22
+ add(projectId, opts) {
23
+ const tags = opts.tags?.length ? opts.tags.join(',') : null;
24
+ const result = this.db.prepare(`
25
+ INSERT INTO memory_artifacts
26
+ (project_id, kind, problem, constraints, validated_fix, why_it_worked,
27
+ outcome, source_session_id, tags, confidence, needs_review, created_by)
28
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
29
+ `).run(projectId, opts.kind, opts.problem.trim(), opts.constraints?.trim() ?? null, opts.validatedFix?.trim() ?? null, opts.whyItWorked?.trim() ?? null, opts.outcome ?? 'neutral', opts.sourceSessionId ?? null, tags, opts.confidence ?? 1.0, opts.needsReview ?? 0, opts.createdBy ?? 'local');
30
+ return this.get(result.lastInsertRowid);
31
+ }
32
+ get(id) {
33
+ return this.db.prepare('SELECT * FROM memory_artifacts WHERE id = ?').get(id) ?? null;
34
+ }
35
+ update(id, updates) {
36
+ this.db.prepare(`
37
+ UPDATE memory_artifacts SET
38
+ confidence = COALESCE(?, confidence),
39
+ validated_fix = COALESCE(?, validated_fix),
40
+ why_it_worked = COALESCE(?, why_it_worked),
41
+ last_validated_at = COALESCE(?, last_validated_at),
42
+ needs_review = COALESCE(?, needs_review),
43
+ updated_at = datetime('now')
44
+ WHERE id = ?
45
+ `).run(updates.confidence ?? null, updates.validated_fix ?? null, updates.why_it_worked ?? null, updates.last_validated_at ?? null, updates.needs_review ?? null, id);
46
+ return this.get(id);
47
+ }
48
+ /** Mark an artifact as superseded by a newer/better one. */
49
+ markSuperseded(oldId, newId) {
50
+ this.db.prepare('UPDATE memory_artifacts SET supersedes_id = ?, updated_at = datetime(\'now\') WHERE id = ?').run(newId, oldId);
51
+ }
52
+ /** Record that an artifact was used and whether it helped. */
53
+ recordFeedback(id, success) {
54
+ const col = success ? 'reuse_success_count' : 'reuse_failure_count';
55
+ this.db.prepare(`
56
+ UPDATE memory_artifacts SET
57
+ reuse_count = reuse_count + 1,
58
+ ${col} = ${col} + 1,
59
+ confidence = CASE
60
+ WHEN ? THEN MIN(1.0, confidence + 0.05)
61
+ ELSE MAX(0.0, confidence - 0.1)
62
+ END,
63
+ updated_at = datetime('now')
64
+ WHERE id = ?
65
+ `).run(success ? 1 : 0, id);
66
+ }
67
+ // ==================== RETRIEVAL ====================
68
+ /**
69
+ * 2-stage retrieval: FTS candidates → sort by weighted relevance.
70
+ * Excludes superseded artifacts and those below minConfidence.
71
+ */
72
+ recall(projectId, query, opts = {}) {
73
+ const limit = opts.limit ?? 10;
74
+ const minConf = opts.minConfidence ?? 0.0;
75
+ // Stage 1: FTS candidates
76
+ const ftsCandidates = this.searchByFts(projectId, query, MAX_CANDIDATES, opts.kind, minConf);
77
+ // Stage 2: Tag candidates (exact tag matches not covered by FTS)
78
+ const tagCandidates = this.searchByTags(projectId, query, MAX_CANDIDATES, opts.kind, minConf);
79
+ // Merge and dedupe by id
80
+ const seen = new Set();
81
+ const merged = [];
82
+ for (const a of ftsCandidates) {
83
+ seen.add(a.id);
84
+ merged.push({ artifact: a, inFts: true, inTag: false });
85
+ }
86
+ for (const a of tagCandidates) {
87
+ if (!seen.has(a.id)) {
88
+ seen.add(a.id);
89
+ merged.push({ artifact: a, inFts: false, inTag: true });
90
+ }
91
+ else {
92
+ const existing = merged.find(m => m.artifact.id === a.id);
93
+ if (existing)
94
+ existing.inTag = true;
95
+ }
96
+ }
97
+ // Score and rerank — also enforce minConf as a safety net
98
+ const results = merged
99
+ .filter(({ artifact }) => artifact.confidence >= minConf)
100
+ .map(({ artifact, inFts, inTag }) => {
101
+ // Base relevance from FTS match
102
+ let relevance = 0;
103
+ if (inFts && inTag)
104
+ relevance = 1.0;
105
+ else if (inFts)
106
+ relevance = 0.7;
107
+ else
108
+ relevance = 0.4;
109
+ // Boost by confidence
110
+ relevance *= artifact.confidence;
111
+ // Boost positive outcomes slightly
112
+ if (artifact.outcome === 'positive')
113
+ relevance *= 1.1;
114
+ // Freshness: decay slowly over 180 days
115
+ const daysSinceValidated = (Date.now() - new Date(artifact.last_validated_at).getTime()) / (1000 * 60 * 60 * 24);
116
+ const freshnessScore = Math.max(0.5, 1.0 - daysSinceValidated / 180);
117
+ relevance *= freshnessScore;
118
+ return {
119
+ artifact,
120
+ relevance_score: Math.min(1.0, relevance),
121
+ match_type: (inFts && inTag ? 'both' : inFts ? 'fts' : 'tag'),
122
+ };
123
+ });
124
+ return results.sort((a, b) => b.relevance_score - a.relevance_score).slice(0, limit);
125
+ }
126
+ searchByFts(projectId, query, limit, kind, minConf = 0.0) {
127
+ const sanitized = query.replace(/['"*]/g, ' ').trim();
128
+ if (!sanitized)
129
+ return this.listRecent(projectId, limit, kind);
130
+ const kindClause = kind ? 'AND a.kind = ?' : '';
131
+ const params = [sanitized, projectId, minConf];
132
+ if (kind)
133
+ params.push(kind);
134
+ params.push(limit);
135
+ try {
136
+ return this.db.prepare(`
137
+ SELECT a.* FROM memory_artifacts a
138
+ JOIN memory_artifacts_fts fts ON a.id = fts.rowid
139
+ WHERE fts MATCH ?
140
+ AND a.project_id = ?
141
+ AND a.confidence >= ?
142
+ AND a.supersedes_id IS NULL
143
+ AND a.needs_review = 0
144
+ ${kindClause}
145
+ ORDER BY rank, a.confidence DESC
146
+ LIMIT ?
147
+ `).all(...params);
148
+ }
149
+ catch {
150
+ return this.listRecent(projectId, limit, kind);
151
+ }
152
+ }
153
+ searchByTags(projectId, query, limit, kind, minConf = 0.0) {
154
+ // Match any word in the query against tags
155
+ const words = query.toLowerCase().split(/\s+/).filter(w => w.length > 2);
156
+ if (!words.length)
157
+ return [];
158
+ const likeClause = words.map(() => 'LOWER(tags) LIKE ?').join(' OR ');
159
+ const likeParams = words.map(w => `%${w}%`);
160
+ const kindClause = kind ? 'AND kind = ?' : '';
161
+ const params = [projectId, minConf, ...likeParams];
162
+ if (kind)
163
+ params.push(kind);
164
+ params.push(limit);
165
+ return this.db.prepare(`
166
+ SELECT * FROM memory_artifacts
167
+ WHERE project_id = ? AND confidence >= ? AND supersedes_id IS NULL
168
+ AND needs_review = 0
169
+ AND (${likeClause})
170
+ ${kindClause}
171
+ ORDER BY confidence DESC, reuse_success_count DESC
172
+ LIMIT ?
173
+ `).all(...params);
174
+ }
175
+ listRecent(projectId, limit = 20, kind) {
176
+ const kindClause = kind ? 'AND kind = ?' : '';
177
+ const params = [projectId];
178
+ if (kind)
179
+ params.push(kind);
180
+ params.push(limit);
181
+ return this.db.prepare(`
182
+ SELECT * FROM memory_artifacts
183
+ WHERE project_id = ? AND supersedes_id IS NULL AND needs_review = 0
184
+ ${kindClause}
185
+ ORDER BY confidence DESC, created_at DESC
186
+ LIMIT ?
187
+ `).all(...params);
188
+ }
189
+ // ==================== CONTEXT BRIEF ====================
190
+ /**
191
+ * Assemble an optimized memory brief for injection into an AI model's context.
192
+ * Excludes credentials/sensitive content. Groups by kind. Deduplicates.
193
+ * Returns sections with headings + items, plus raw text for direct injection.
194
+ */
195
+ buildContextBrief(projectId, task, opts = {}) {
196
+ const kinds = opts.kinds ?? ['pattern', 'heuristic', 'reasoning_trace', 'lesson', 'anti_pattern'];
197
+ const maxItems = opts.maxItems ?? MAX_BRIEF_ITEMS;
198
+ const minConf = opts.minConfidence ?? MIN_BRIEF_CONFIDENCE;
199
+ // Recall relevant artifacts
200
+ const recalled = this.recall(projectId, task, { limit: maxItems * 2, minConfidence: minConf });
201
+ // Group by kind, pick top items per kind
202
+ const byKind = new Map();
203
+ for (const r of recalled) {
204
+ if (!kinds.includes(r.artifact.kind))
205
+ continue;
206
+ const existing = byKind.get(r.artifact.kind) ?? [];
207
+ existing.push(r);
208
+ byKind.set(r.artifact.kind, existing);
209
+ }
210
+ const KIND_LABELS = {
211
+ pattern: '✅ Proven Patterns',
212
+ heuristic: '💡 Heuristics',
213
+ reasoning_trace: '🧠 Past Reasoning',
214
+ lesson: '📚 Lessons Learned',
215
+ anti_pattern: '⚠️ Anti-Patterns to Avoid',
216
+ };
217
+ // Prioritize: patterns > heuristics > reasoning_traces > lessons > anti_patterns
218
+ const kindOrder = ['pattern', 'heuristic', 'reasoning_trace', 'lesson', 'anti_pattern'];
219
+ const sections = [];
220
+ const sourceIds = [];
221
+ let totalItems = 0;
222
+ for (const kind of kindOrder) {
223
+ if (!kinds.includes(kind))
224
+ continue;
225
+ const items = byKind.get(kind) ?? [];
226
+ if (!items.length)
227
+ continue;
228
+ const sectionItems = [];
229
+ for (const r of items) {
230
+ if (totalItems >= maxItems)
231
+ break;
232
+ const a = r.artifact;
233
+ let line = `**Problem:** ${a.problem}`;
234
+ if (a.constraints)
235
+ line += `\n _Constraints:_ ${a.constraints}`;
236
+ if (a.validated_fix)
237
+ line += `\n _Solution:_ ${a.validated_fix}`;
238
+ if (a.why_it_worked)
239
+ line += `\n _Why it worked:_ ${a.why_it_worked}`;
240
+ if (a.outcome === 'negative')
241
+ line += `\n _Note: This approach failed — avoid it_`;
242
+ sectionItems.push(line);
243
+ sourceIds.push(a.id);
244
+ totalItems++;
245
+ }
246
+ if (sectionItems.length) {
247
+ sections.push({ heading: KIND_LABELS[kind], items: sectionItems, source: kind });
248
+ }
249
+ }
250
+ // Build formatted text
251
+ let text = '';
252
+ if (sections.length > 0) {
253
+ text += '---\n## 🐉 Memory Brief\n_Relevant past knowledge from Wyrm:_\n\n';
254
+ for (const section of sections) {
255
+ text += `### ${section.heading}\n`;
256
+ for (const item of section.items) {
257
+ text += `- ${item}\n`;
258
+ }
259
+ text += '\n';
260
+ }
261
+ text += '---\n';
262
+ }
263
+ return { sections, text, sourceIds };
264
+ }
265
+ // ==================== STATS ====================
266
+ getStats(projectId) {
267
+ const total = this.db.prepare('SELECT COUNT(*) as n FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NULL').get(projectId).n;
268
+ const supersededCount = this.db.prepare('SELECT COUNT(*) as n FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NOT NULL').get(projectId).n;
269
+ const avgConfidence = this.db.prepare('SELECT AVG(confidence) as v FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NULL').get(projectId).v ?? 0;
270
+ const kindRows = this.db.prepare('SELECT kind, COUNT(*) as cnt FROM memory_artifacts WHERE project_id = ? AND supersedes_id IS NULL GROUP BY kind').all(projectId);
271
+ const byKind = {};
272
+ for (const r of kindRows)
273
+ byKind[r.kind] = r.cnt;
274
+ return { total, byKind, avgConfidence: Math.round(avgConfidence * 100) / 100, supersededCount };
275
+ }
276
+ listAll(projectId, opts = {}) {
277
+ const kindClause = opts.kind ? 'AND kind = ?' : '';
278
+ const supersededClause = opts.includeSuperseded ? '' : 'AND supersedes_id IS NULL';
279
+ const params = [projectId];
280
+ if (opts.kind)
281
+ params.push(opts.kind);
282
+ params.push(opts.limit ?? 50);
283
+ return this.db.prepare(`
284
+ SELECT * FROM memory_artifacts
285
+ WHERE project_id = ? ${kindClause} ${supersededClause}
286
+ ORDER BY confidence DESC, created_at DESC
287
+ LIMIT ?
288
+ `).all(...params);
289
+ }
290
+ }
291
+ //# sourceMappingURL=memory-artifacts.js.map