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.
- package/README.md +1 -1
- package/dist/database.d.ts +0 -1
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +9 -205
- package/dist/database.js.map +1 -1
- package/dist/index.js +839 -39
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +84 -0
- package/dist/indexer.d.ts.map +1 -0
- package/dist/indexer.js +160 -0
- package/dist/indexer.js.map +1 -0
- package/dist/intelligence.d.ts +116 -0
- package/dist/intelligence.d.ts.map +1 -0
- package/dist/intelligence.js +278 -0
- package/dist/intelligence.js.map +1 -0
- package/dist/knowledge-graph.d.ts +113 -0
- package/dist/knowledge-graph.d.ts.map +1 -0
- package/dist/knowledge-graph.js +235 -0
- package/dist/knowledge-graph.js.map +1 -0
- package/dist/memory-artifacts.d.ts +106 -0
- package/dist/memory-artifacts.d.ts.map +1 -0
- package/dist/memory-artifacts.js +291 -0
- package/dist/memory-artifacts.js.map +1 -0
- package/dist/migrations.d.ts +31 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +525 -0
- package/dist/migrations.js.map +1 -0
- package/dist/providers/embedding-provider.d.ts +81 -0
- package/dist/providers/embedding-provider.d.ts.map +1 -0
- package/dist/providers/embedding-provider.js +149 -0
- package/dist/providers/embedding-provider.js.map +1 -0
- package/dist/security.js +1 -1
- package/dist/security.js.map +1 -1
- package/dist/vectors.d.ts +32 -48
- package/dist/vectors.d.ts.map +1 -1
- package/dist/vectors.js +119 -225
- package/dist/vectors.js.map +1 -1
- package/package.json +2 -2
|
@@ -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":"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"}
|
|
@@ -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
|