cozo-memory 1.2.0 → 1.2.2
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/dist/emotional-salience.js +295 -0
- package/dist/index.js +726 -9
- package/dist/memory-activation.js +64 -30
- package/dist/memory-service.js +68 -0
- package/dist/pre-storage-reasoning.js +351 -0
- package/dist/temporal-conflict-resolution.js +10 -6
- package/dist/test-activation-mcp.js +118 -0
- package/dist/test-advanced-search-mcp.js +204 -0
- package/dist/test-conflicts-mcp.js +173 -0
- package/dist/test-emotional-salience.js +177 -0
- package/dist/test-hierarchical-mcp.js +135 -0
- package/dist/test-logical-edges-mcp.js +215 -0
- package/dist/test-metadata-check.js +69 -0
- package/dist/test-metadata-update.js +92 -0
- package/dist/test-pre-storage-reasoning.js +149 -0
- package/dist/test-salience-mcp.js +94 -0
- package/dist/test-spreading-mcp.js +155 -0
- package/dist/test-suggest-connections-mcp.js +172 -0
- package/dist/test-zettelkasten-evolution.js +255 -0
- package/dist/test-zettelkasten-fixed.js +74 -0
- package/dist/test-zettelkasten-live.js +117 -0
- package/dist/test-zettelkasten-mcp.js +96 -0
- package/dist/zettelkasten-evolution.js +342 -0
- package/package.json +1 -1
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZettelkastenEvolutionService = void 0;
|
|
4
|
+
class ZettelkastenEvolutionService {
|
|
5
|
+
db;
|
|
6
|
+
embeddings;
|
|
7
|
+
config;
|
|
8
|
+
constructor(db, embeddings, config = {}) {
|
|
9
|
+
this.db = db;
|
|
10
|
+
this.embeddings = embeddings;
|
|
11
|
+
this.config = {
|
|
12
|
+
enableEvolution: true,
|
|
13
|
+
similarityThreshold: 0.7,
|
|
14
|
+
maxRelatedNotes: 5,
|
|
15
|
+
minKeywordFrequency: 2,
|
|
16
|
+
autoExtractKeywords: true,
|
|
17
|
+
autoBidirectionalLinks: true,
|
|
18
|
+
enrichmentDepth: 'shallow',
|
|
19
|
+
...config
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
extractKeywords(text, minLength = 4) {
|
|
23
|
+
const stopWords = new Set([
|
|
24
|
+
'the', 'is', 'at', 'which', 'on', 'a', 'an', 'and', 'or', 'but',
|
|
25
|
+
'in', 'with', 'to', 'for', 'of', 'as', 'by', 'this', 'that', 'it',
|
|
26
|
+
'from', 'are', 'was', 'were', 'been', 'be', 'have', 'has', 'had',
|
|
27
|
+
'do', 'does', 'did', 'will', 'would', 'should', 'could', 'can',
|
|
28
|
+
'may', 'might', 'must', 'shall'
|
|
29
|
+
]);
|
|
30
|
+
const words = text
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^\w\s]/g, ' ')
|
|
33
|
+
.split(/\s+/)
|
|
34
|
+
.filter(word => word.length >= minLength &&
|
|
35
|
+
!stopWords.has(word) &&
|
|
36
|
+
!/^\d+$/.test(word));
|
|
37
|
+
const frequencies = new Map();
|
|
38
|
+
for (const word of words) {
|
|
39
|
+
frequencies.set(word, (frequencies.get(word) || 0) + 1);
|
|
40
|
+
}
|
|
41
|
+
return Array.from(frequencies.entries())
|
|
42
|
+
.filter(([_, count]) => count >= this.config.minKeywordFrequency)
|
|
43
|
+
.sort((a, b) => b[1] - a[1])
|
|
44
|
+
.map(([word, _]) => word)
|
|
45
|
+
.slice(0, 10);
|
|
46
|
+
}
|
|
47
|
+
extractTags(text) {
|
|
48
|
+
const tags = [];
|
|
49
|
+
const hashtagMatches = text.match(/#\w+/g);
|
|
50
|
+
if (hashtagMatches) {
|
|
51
|
+
tags.push(...hashtagMatches.map(tag => tag.substring(1).toLowerCase()));
|
|
52
|
+
}
|
|
53
|
+
const patternMatches = text.match(/(?:category|type|topic|subject):\s*(\w+)/gi);
|
|
54
|
+
if (patternMatches) {
|
|
55
|
+
tags.push(...patternMatches.map(match => {
|
|
56
|
+
const parts = match.split(':');
|
|
57
|
+
return parts[1].trim().toLowerCase();
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
return [...new Set(tags)];
|
|
61
|
+
}
|
|
62
|
+
async findRelatedNotes(observationId, observationText, observationEmbedding) {
|
|
63
|
+
try {
|
|
64
|
+
const result = await this.db.run(`
|
|
65
|
+
?[id, entity_id, text, similarity] :=
|
|
66
|
+
~observation:semantic{id |
|
|
67
|
+
query: vec($embedding),
|
|
68
|
+
k: $k,
|
|
69
|
+
ef: 100,
|
|
70
|
+
bind_distance: dist
|
|
71
|
+
},
|
|
72
|
+
*observation{id, entity_id, text, @ "NOW"},
|
|
73
|
+
id != $exclude_id,
|
|
74
|
+
similarity = 1.0 - dist
|
|
75
|
+
`, {
|
|
76
|
+
embedding: observationEmbedding,
|
|
77
|
+
k: this.config.maxRelatedNotes + 5,
|
|
78
|
+
exclude_id: observationId
|
|
79
|
+
});
|
|
80
|
+
const relatedNotes = [];
|
|
81
|
+
const observationKeywords = this.extractKeywords(observationText);
|
|
82
|
+
for (const [id, entity_id, text, similarity] of result.rows) {
|
|
83
|
+
const sim = similarity;
|
|
84
|
+
if (sim < this.config.similarityThreshold) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const noteKeywords = this.extractKeywords(text);
|
|
88
|
+
const sharedKeywords = observationKeywords.filter(k => noteKeywords.includes(k));
|
|
89
|
+
let connectionType = 'semantic';
|
|
90
|
+
let reason = `High semantic similarity (${sim.toFixed(3)})`;
|
|
91
|
+
if (sharedKeywords.length >= 2) {
|
|
92
|
+
connectionType = 'keyword';
|
|
93
|
+
reason = `Shared keywords: ${sharedKeywords.slice(0, 3).join(', ')}`;
|
|
94
|
+
}
|
|
95
|
+
relatedNotes.push({
|
|
96
|
+
observationId: id,
|
|
97
|
+
entityId: entity_id,
|
|
98
|
+
text: text,
|
|
99
|
+
similarity: sim,
|
|
100
|
+
sharedKeywords,
|
|
101
|
+
connectionType,
|
|
102
|
+
reason
|
|
103
|
+
});
|
|
104
|
+
if (relatedNotes.length >= this.config.maxRelatedNotes) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return relatedNotes;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error('[ZettelkastenEvolution] Error finding related notes:', error);
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async enrichObservation(observationId, observationText, observationEmbedding, entityId) {
|
|
116
|
+
if (!this.config.enableEvolution) {
|
|
117
|
+
return {
|
|
118
|
+
observationId,
|
|
119
|
+
relatedNotes: [],
|
|
120
|
+
extractedKeywords: [],
|
|
121
|
+
addedTags: [],
|
|
122
|
+
createdLinks: 0,
|
|
123
|
+
updatedMetadata: {},
|
|
124
|
+
evolutionSummary: 'Evolution disabled'
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const extractedKeywords = this.config.autoExtractKeywords
|
|
129
|
+
? this.extractKeywords(observationText)
|
|
130
|
+
: [];
|
|
131
|
+
const addedTags = this.extractTags(observationText);
|
|
132
|
+
const relatedNotes = await this.findRelatedNotes(observationId, observationText, observationEmbedding);
|
|
133
|
+
let createdLinks = 0;
|
|
134
|
+
if (this.config.autoBidirectionalLinks) {
|
|
135
|
+
const now = Date.now() * 1000; // CozoDB Validity uses microseconds
|
|
136
|
+
for (const related of relatedNotes) {
|
|
137
|
+
try {
|
|
138
|
+
await this.db.run(`
|
|
139
|
+
?[from_id, to_id, relation_type, created_at, strength, metadata] <- [[
|
|
140
|
+
$from_id,
|
|
141
|
+
$to_id,
|
|
142
|
+
'zettelkasten_link',
|
|
143
|
+
[${now}, true],
|
|
144
|
+
$strength,
|
|
145
|
+
{
|
|
146
|
+
"connection_type": $connection_type,
|
|
147
|
+
"shared_keywords": $shared_keywords,
|
|
148
|
+
"reason": $reason,
|
|
149
|
+
"auto_generated": true
|
|
150
|
+
}
|
|
151
|
+
]]
|
|
152
|
+
:put relationship {from_id, to_id, relation_type, created_at => strength, metadata}
|
|
153
|
+
`, {
|
|
154
|
+
from_id: observationId,
|
|
155
|
+
to_id: related.observationId,
|
|
156
|
+
strength: related.similarity,
|
|
157
|
+
connection_type: related.connectionType,
|
|
158
|
+
shared_keywords: JSON.stringify(related.sharedKeywords),
|
|
159
|
+
reason: related.reason
|
|
160
|
+
});
|
|
161
|
+
await this.db.run(`
|
|
162
|
+
?[from_id, to_id, relation_type, created_at, strength, metadata] <- [[
|
|
163
|
+
$from_id,
|
|
164
|
+
$to_id,
|
|
165
|
+
'zettelkasten_link',
|
|
166
|
+
[${now}, true],
|
|
167
|
+
$strength,
|
|
168
|
+
{
|
|
169
|
+
"connection_type": $connection_type,
|
|
170
|
+
"shared_keywords": $shared_keywords,
|
|
171
|
+
"reason": $reason,
|
|
172
|
+
"auto_generated": true,
|
|
173
|
+
"bidirectional": true
|
|
174
|
+
}
|
|
175
|
+
]]
|
|
176
|
+
:put relationship {from_id, to_id, relation_type, created_at => strength, metadata}
|
|
177
|
+
`, {
|
|
178
|
+
from_id: related.observationId,
|
|
179
|
+
to_id: observationId,
|
|
180
|
+
strength: related.similarity,
|
|
181
|
+
connection_type: related.connectionType,
|
|
182
|
+
shared_keywords: JSON.stringify(related.sharedKeywords),
|
|
183
|
+
reason: related.reason
|
|
184
|
+
});
|
|
185
|
+
createdLinks += 2;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error(`[ZettelkastenEvolution] Error creating link:`, error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Update metadata of related observations
|
|
193
|
+
for (const related of relatedNotes) {
|
|
194
|
+
try {
|
|
195
|
+
const metaResult = await this.db.run(`
|
|
196
|
+
?[id, metadata] := *observation{id, metadata, @ "NOW"}, id = $id
|
|
197
|
+
`, { id: related.observationId });
|
|
198
|
+
if (metaResult.rows.length > 0) {
|
|
199
|
+
const currentMetadata = metaResult.rows[0][1] || {};
|
|
200
|
+
const existingKeywords = currentMetadata.zettelkasten_keywords || [];
|
|
201
|
+
const enrichedKeywords = [...new Set([...existingKeywords, ...extractedKeywords])];
|
|
202
|
+
const existingTags = currentMetadata.zettelkasten_tags || [];
|
|
203
|
+
const enrichedTags = [...new Set([...existingTags, ...addedTags])];
|
|
204
|
+
const existingRelated = currentMetadata.zettelkasten_related || [];
|
|
205
|
+
const enrichedRelated = [...new Set([...existingRelated, observationId])];
|
|
206
|
+
const mergedMetadata = {
|
|
207
|
+
...currentMetadata,
|
|
208
|
+
zettelkasten_keywords: enrichedKeywords,
|
|
209
|
+
zettelkasten_tags: enrichedTags,
|
|
210
|
+
zettelkasten_related: enrichedRelated,
|
|
211
|
+
zettelkasten_last_enriched: Date.now()
|
|
212
|
+
};
|
|
213
|
+
await this.db.run(`
|
|
214
|
+
?[id, created_at, entity_id, session_id, task_id, text, embedding, metadata] :=
|
|
215
|
+
*observation{id, created_at, entity_id, session_id, task_id, text, embedding, @ "NOW"},
|
|
216
|
+
id = $id,
|
|
217
|
+
metadata = $new_metadata
|
|
218
|
+
|
|
219
|
+
:put observation {id, created_at => entity_id, session_id, task_id, text, embedding, metadata}
|
|
220
|
+
`, {
|
|
221
|
+
id: related.observationId,
|
|
222
|
+
new_metadata: mergedMetadata
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
console.error(`[ZettelkastenEvolution] Error enriching related note:`, error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const updatedMetadata = {
|
|
231
|
+
zettelkasten_keywords: extractedKeywords,
|
|
232
|
+
zettelkasten_tags: addedTags,
|
|
233
|
+
zettelkasten_related: relatedNotes.map(n => n.observationId),
|
|
234
|
+
zettelkasten_enriched: true,
|
|
235
|
+
zettelkasten_timestamp: Date.now()
|
|
236
|
+
};
|
|
237
|
+
// Fetch current metadata and merge with new metadata
|
|
238
|
+
const currentMetaResult = await this.db.run(`
|
|
239
|
+
?[metadata] := *observation{id, metadata, @ "NOW"}, id = $id
|
|
240
|
+
`, { id: observationId });
|
|
241
|
+
const existingMetadata = currentMetaResult.rows[0]?.[0] || {};
|
|
242
|
+
const mergedMetadata = {
|
|
243
|
+
...existingMetadata,
|
|
244
|
+
zettelkasten_keywords: extractedKeywords,
|
|
245
|
+
zettelkasten_tags: addedTags,
|
|
246
|
+
zettelkasten_related: relatedNotes.map(n => n.observationId),
|
|
247
|
+
zettelkasten_enriched: true,
|
|
248
|
+
zettelkasten_timestamp: Date.now()
|
|
249
|
+
};
|
|
250
|
+
await this.db.run(`
|
|
251
|
+
?[id, created_at, entity_id, session_id, task_id, text, embedding, metadata] :=
|
|
252
|
+
*observation{id, created_at, entity_id, session_id, task_id, text, embedding, @ "NOW"},
|
|
253
|
+
id = $id,
|
|
254
|
+
metadata = $new_metadata
|
|
255
|
+
|
|
256
|
+
:put observation {id, created_at => entity_id, session_id, task_id, text, embedding, metadata}
|
|
257
|
+
`, {
|
|
258
|
+
id: observationId,
|
|
259
|
+
new_metadata: mergedMetadata
|
|
260
|
+
});
|
|
261
|
+
const evolutionSummary = `Enriched with ${extractedKeywords.length} keywords, ${addedTags.length} tags, ` +
|
|
262
|
+
`${relatedNotes.length} related notes, ${createdLinks} bidirectional links`;
|
|
263
|
+
return {
|
|
264
|
+
observationId,
|
|
265
|
+
relatedNotes,
|
|
266
|
+
extractedKeywords,
|
|
267
|
+
addedTags,
|
|
268
|
+
createdLinks,
|
|
269
|
+
updatedMetadata,
|
|
270
|
+
evolutionSummary
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
console.error('[ZettelkastenEvolution] Error enriching observation:', error);
|
|
275
|
+
return {
|
|
276
|
+
observationId,
|
|
277
|
+
relatedNotes: [],
|
|
278
|
+
extractedKeywords: [],
|
|
279
|
+
addedTags: [],
|
|
280
|
+
createdLinks: 0,
|
|
281
|
+
updatedMetadata: {},
|
|
282
|
+
evolutionSummary: 'Enrichment failed'
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async getEvolutionStats() {
|
|
287
|
+
try {
|
|
288
|
+
const totalResult = await this.db.run(`
|
|
289
|
+
?[count(id)] := *observation{id, @ "NOW"}
|
|
290
|
+
`);
|
|
291
|
+
const totalObservations = totalResult.rows[0]?.[0] || 0;
|
|
292
|
+
const enrichedResult = await this.db.run(`
|
|
293
|
+
?[count(id)] :=
|
|
294
|
+
*observation{id, metadata, @ "NOW"},
|
|
295
|
+
metadata != null,
|
|
296
|
+
metadata->'zettelkasten_enriched' == true
|
|
297
|
+
`);
|
|
298
|
+
const enrichedObservations = enrichedResult.rows[0]?.[0] || 0;
|
|
299
|
+
const linksResult = await this.db.run(`
|
|
300
|
+
?[count(from_id)] :=
|
|
301
|
+
*relationship{from_id, to_id, relation_type, @ "NOW"},
|
|
302
|
+
relation_type == 'zettelkasten_link'
|
|
303
|
+
`);
|
|
304
|
+
const totalLinks = linksResult.rows[0]?.[0] || 0;
|
|
305
|
+
const averageLinksPerNote = enrichedObservations > 0
|
|
306
|
+
? totalLinks / enrichedObservations
|
|
307
|
+
: 0;
|
|
308
|
+
return {
|
|
309
|
+
totalObservations,
|
|
310
|
+
enrichedObservations,
|
|
311
|
+
totalLinks,
|
|
312
|
+
averageLinksPerNote,
|
|
313
|
+
topKeywords: [],
|
|
314
|
+
topTags: [],
|
|
315
|
+
connectionTypes: {
|
|
316
|
+
semantic: 0,
|
|
317
|
+
keyword: 0,
|
|
318
|
+
entity: 0,
|
|
319
|
+
temporal: 0
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
console.error('[ZettelkastenEvolution] Error getting stats:', error);
|
|
325
|
+
return {
|
|
326
|
+
totalObservations: 0,
|
|
327
|
+
enrichedObservations: 0,
|
|
328
|
+
totalLinks: 0,
|
|
329
|
+
averageLinksPerNote: 0,
|
|
330
|
+
topKeywords: [],
|
|
331
|
+
topTags: [],
|
|
332
|
+
connectionTypes: {
|
|
333
|
+
semantic: 0,
|
|
334
|
+
keyword: 0,
|
|
335
|
+
entity: 0,
|
|
336
|
+
temporal: 0
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
exports.ZettelkastenEvolutionService = ZettelkastenEvolutionService;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cozo-memory",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"mcpName": "io.github.tobs-code/cozo-memory",
|
|
5
5
|
"description": "Local-first persistent memory system for AI agents with hybrid search, graph reasoning, and MCP integration",
|
|
6
6
|
"main": "dist/index.js",
|