@danielsimonjr/memory-mcp 0.7.1 → 0.41.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/dist/__tests__/edge-cases/edge-cases.test.js +406 -0
- package/dist/__tests__/file-path.test.js +5 -5
- package/dist/__tests__/integration/workflows.test.js +449 -0
- package/dist/__tests__/knowledge-graph.test.js +8 -3
- package/dist/__tests__/performance/benchmarks.test.js +411 -0
- package/dist/__tests__/unit/core/EntityManager.test.js +334 -0
- package/dist/__tests__/unit/core/GraphStorage.test.js +205 -0
- package/dist/__tests__/unit/core/RelationManager.test.js +274 -0
- package/dist/__tests__/unit/features/CompressionManager.test.js +350 -0
- package/dist/__tests__/unit/search/BasicSearch.test.js +311 -0
- package/dist/__tests__/unit/search/BooleanSearch.test.js +432 -0
- package/dist/__tests__/unit/search/FuzzySearch.test.js +448 -0
- package/dist/__tests__/unit/search/RankedSearch.test.js +379 -0
- package/dist/__tests__/unit/utils/levenshtein.test.js +77 -0
- package/dist/core/EntityManager.js +554 -0
- package/dist/core/GraphStorage.js +172 -0
- package/dist/core/KnowledgeGraphManager.js +400 -0
- package/dist/core/ObservationManager.js +129 -0
- package/dist/core/RelationManager.js +186 -0
- package/dist/core/TransactionManager.js +389 -0
- package/dist/core/index.js +9 -0
- package/dist/features/AnalyticsManager.js +222 -0
- package/dist/features/ArchiveManager.js +74 -0
- package/dist/features/BackupManager.js +311 -0
- package/dist/features/CompressionManager.js +310 -0
- package/dist/features/ExportManager.js +305 -0
- package/dist/features/HierarchyManager.js +219 -0
- package/dist/features/ImportExportManager.js +50 -0
- package/dist/features/ImportManager.js +328 -0
- package/dist/features/TagManager.js +210 -0
- package/dist/features/index.js +12 -0
- package/dist/index.js +13 -997
- package/dist/memory.jsonl +225 -0
- package/dist/search/BasicSearch.js +161 -0
- package/dist/search/BooleanSearch.js +304 -0
- package/dist/search/FuzzySearch.js +115 -0
- package/dist/search/RankedSearch.js +206 -0
- package/dist/search/SavedSearchManager.js +145 -0
- package/dist/search/SearchManager.js +305 -0
- package/dist/search/SearchSuggestions.js +57 -0
- package/dist/search/TFIDFIndexManager.js +217 -0
- package/dist/search/index.js +10 -0
- package/dist/server/MCPServer.js +889 -0
- package/dist/types/analytics.types.js +6 -0
- package/dist/types/entity.types.js +7 -0
- package/dist/types/import-export.types.js +7 -0
- package/dist/types/index.js +12 -0
- package/dist/types/search.types.js +7 -0
- package/dist/types/tag.types.js +6 -0
- package/dist/utils/constants.js +127 -0
- package/dist/utils/dateUtils.js +89 -0
- package/dist/utils/errors.js +121 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/levenshtein.js +62 -0
- package/dist/utils/logger.js +33 -0
- package/dist/utils/pathUtils.js +115 -0
- package/dist/utils/schemas.js +184 -0
- package/dist/utils/searchCache.js +209 -0
- package/dist/utils/tfidf.js +90 -0
- package/dist/utils/validationUtils.js +109 -0
- package/package.json +50 -48
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Suggestions
|
|
3
|
+
*
|
|
4
|
+
* Provides "did you mean?" suggestions using Levenshtein distance.
|
|
5
|
+
*
|
|
6
|
+
* @module search/SearchSuggestions
|
|
7
|
+
*/
|
|
8
|
+
import { levenshteinDistance } from '../utils/levenshtein.js';
|
|
9
|
+
/**
|
|
10
|
+
* Generates search suggestions based on entity names and types.
|
|
11
|
+
*/
|
|
12
|
+
export class SearchSuggestions {
|
|
13
|
+
storage;
|
|
14
|
+
constructor(storage) {
|
|
15
|
+
this.storage = storage;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get "did you mean?" suggestions for a query.
|
|
19
|
+
*
|
|
20
|
+
* Returns similar entity names and types that might be what the user intended.
|
|
21
|
+
* Excludes exact matches (similarity < 1.0) and very dissimilar strings (similarity > 0.5).
|
|
22
|
+
*
|
|
23
|
+
* @param query - The search query
|
|
24
|
+
* @param maxSuggestions - Maximum number of suggestions to return (default 5)
|
|
25
|
+
* @returns Array of suggested entity/type names sorted by similarity
|
|
26
|
+
*/
|
|
27
|
+
async getSearchSuggestions(query, maxSuggestions = 5) {
|
|
28
|
+
const graph = await this.storage.loadGraph();
|
|
29
|
+
const queryLower = query.toLowerCase();
|
|
30
|
+
const suggestions = [];
|
|
31
|
+
// Check entity names
|
|
32
|
+
for (const entity of graph.entities) {
|
|
33
|
+
const distance = levenshteinDistance(queryLower, entity.name.toLowerCase());
|
|
34
|
+
const maxLength = Math.max(queryLower.length, entity.name.length);
|
|
35
|
+
const similarity = 1 - distance / maxLength;
|
|
36
|
+
if (similarity > 0.5 && similarity < 1.0) {
|
|
37
|
+
// Not exact match but similar
|
|
38
|
+
suggestions.push({ text: entity.name, similarity });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Check entity types
|
|
42
|
+
const uniqueTypes = [...new Set(graph.entities.map(e => e.entityType))];
|
|
43
|
+
for (const type of uniqueTypes) {
|
|
44
|
+
const distance = levenshteinDistance(queryLower, type.toLowerCase());
|
|
45
|
+
const maxLength = Math.max(queryLower.length, type.length);
|
|
46
|
+
const similarity = 1 - distance / maxLength;
|
|
47
|
+
if (similarity > 0.5 && similarity < 1.0) {
|
|
48
|
+
suggestions.push({ text: type, similarity });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Sort by similarity and return top suggestions
|
|
52
|
+
return suggestions
|
|
53
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
54
|
+
.slice(0, maxSuggestions)
|
|
55
|
+
.map(s => s.text);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TF-IDF Index Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages pre-calculated TF-IDF indexes for fast ranked search.
|
|
5
|
+
* Handles index building, incremental updates, and persistence.
|
|
6
|
+
*
|
|
7
|
+
* @module search/TFIDFIndexManager
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { calculateIDF, tokenize } from '../utils/tfidf.js';
|
|
12
|
+
const INDEX_VERSION = '1.0';
|
|
13
|
+
const INDEX_FILENAME = 'tfidf-index.json';
|
|
14
|
+
/**
|
|
15
|
+
* Manages TF-IDF index lifecycle: building, updating, and persistence.
|
|
16
|
+
*/
|
|
17
|
+
export class TFIDFIndexManager {
|
|
18
|
+
indexPath;
|
|
19
|
+
index = null;
|
|
20
|
+
constructor(storageDir) {
|
|
21
|
+
this.indexPath = path.join(storageDir, '.indexes', INDEX_FILENAME);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build a complete TF-IDF index from a knowledge graph.
|
|
25
|
+
*
|
|
26
|
+
* @param graph - Knowledge graph to index
|
|
27
|
+
* @returns Newly built TF-IDF index
|
|
28
|
+
*/
|
|
29
|
+
async buildIndex(graph) {
|
|
30
|
+
const documents = new Map();
|
|
31
|
+
const allDocumentTexts = [];
|
|
32
|
+
const allTokens = [];
|
|
33
|
+
// Build document vectors
|
|
34
|
+
for (const entity of graph.entities) {
|
|
35
|
+
const documentText = [
|
|
36
|
+
entity.name,
|
|
37
|
+
entity.entityType,
|
|
38
|
+
...entity.observations,
|
|
39
|
+
].join(' ');
|
|
40
|
+
allDocumentTexts.push(documentText);
|
|
41
|
+
const tokens = tokenize(documentText);
|
|
42
|
+
allTokens.push(tokens);
|
|
43
|
+
// Calculate term frequencies
|
|
44
|
+
const termFreq = {};
|
|
45
|
+
for (const term of tokens) {
|
|
46
|
+
termFreq[term] = (termFreq[term] || 0) + 1;
|
|
47
|
+
}
|
|
48
|
+
documents.set(entity.name, {
|
|
49
|
+
entityName: entity.name,
|
|
50
|
+
terms: termFreq,
|
|
51
|
+
documentText,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
// Calculate IDF for all terms
|
|
55
|
+
const idf = new Map();
|
|
56
|
+
const allTerms = new Set(allTokens.flat());
|
|
57
|
+
for (const term of allTerms) {
|
|
58
|
+
const idfScore = calculateIDF(term, allDocumentTexts);
|
|
59
|
+
idf.set(term, idfScore);
|
|
60
|
+
}
|
|
61
|
+
this.index = {
|
|
62
|
+
version: INDEX_VERSION,
|
|
63
|
+
lastUpdated: new Date().toISOString(),
|
|
64
|
+
documents,
|
|
65
|
+
idf,
|
|
66
|
+
};
|
|
67
|
+
return this.index;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Update the index incrementally when entities change.
|
|
71
|
+
*
|
|
72
|
+
* More efficient than rebuilding the entire index.
|
|
73
|
+
*
|
|
74
|
+
* @param graph - Updated knowledge graph
|
|
75
|
+
* @param changedEntityNames - Names of entities that changed
|
|
76
|
+
*/
|
|
77
|
+
async updateIndex(graph, changedEntityNames) {
|
|
78
|
+
if (!this.index) {
|
|
79
|
+
// No existing index, build from scratch
|
|
80
|
+
return this.buildIndex(graph);
|
|
81
|
+
}
|
|
82
|
+
// Rebuild document vectors for changed entities
|
|
83
|
+
const allDocumentTexts = [];
|
|
84
|
+
const allTokens = [];
|
|
85
|
+
const updatedDocuments = new Map(this.index.documents);
|
|
86
|
+
// Remove deleted entities
|
|
87
|
+
for (const entityName of changedEntityNames) {
|
|
88
|
+
const entity = graph.entities.find(e => e.name === entityName);
|
|
89
|
+
if (!entity) {
|
|
90
|
+
updatedDocuments.delete(entityName);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Update/add changed entities
|
|
94
|
+
for (const entity of graph.entities) {
|
|
95
|
+
const documentText = [
|
|
96
|
+
entity.name,
|
|
97
|
+
entity.entityType,
|
|
98
|
+
...entity.observations,
|
|
99
|
+
].join(' ');
|
|
100
|
+
allDocumentTexts.push(documentText);
|
|
101
|
+
const tokens = tokenize(documentText);
|
|
102
|
+
allTokens.push(tokens);
|
|
103
|
+
if (changedEntityNames.has(entity.name)) {
|
|
104
|
+
// Calculate term frequencies for changed entity
|
|
105
|
+
const termFreq = {};
|
|
106
|
+
for (const term of tokens) {
|
|
107
|
+
termFreq[term] = (termFreq[term] || 0) + 1;
|
|
108
|
+
}
|
|
109
|
+
updatedDocuments.set(entity.name, {
|
|
110
|
+
entityName: entity.name,
|
|
111
|
+
terms: termFreq,
|
|
112
|
+
documentText,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Recalculate IDF (need all documents for accurate IDF)
|
|
117
|
+
const idf = new Map();
|
|
118
|
+
const allTerms = new Set(allTokens.flat());
|
|
119
|
+
for (const term of allTerms) {
|
|
120
|
+
const idfScore = calculateIDF(term, allDocumentTexts);
|
|
121
|
+
idf.set(term, idfScore);
|
|
122
|
+
}
|
|
123
|
+
this.index = {
|
|
124
|
+
version: INDEX_VERSION,
|
|
125
|
+
lastUpdated: new Date().toISOString(),
|
|
126
|
+
documents: updatedDocuments,
|
|
127
|
+
idf,
|
|
128
|
+
};
|
|
129
|
+
return this.index;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Load index from disk.
|
|
133
|
+
*
|
|
134
|
+
* @returns Loaded index or null if not found
|
|
135
|
+
*/
|
|
136
|
+
async loadIndex() {
|
|
137
|
+
try {
|
|
138
|
+
const data = await fs.readFile(this.indexPath, 'utf-8');
|
|
139
|
+
const serialized = JSON.parse(data);
|
|
140
|
+
this.index = {
|
|
141
|
+
version: serialized.version,
|
|
142
|
+
lastUpdated: serialized.lastUpdated,
|
|
143
|
+
documents: new Map(serialized.documents),
|
|
144
|
+
idf: new Map(serialized.idf),
|
|
145
|
+
};
|
|
146
|
+
return this.index;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Index doesn't exist or is invalid
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Save index to disk.
|
|
155
|
+
*
|
|
156
|
+
* @param index - Index to save (uses cached index if not provided)
|
|
157
|
+
*/
|
|
158
|
+
async saveIndex(index) {
|
|
159
|
+
const indexToSave = index || this.index;
|
|
160
|
+
if (!indexToSave) {
|
|
161
|
+
throw new Error('No index to save');
|
|
162
|
+
}
|
|
163
|
+
// Ensure index directory exists
|
|
164
|
+
const indexDir = path.dirname(this.indexPath);
|
|
165
|
+
await fs.mkdir(indexDir, { recursive: true });
|
|
166
|
+
// Serialize Map objects to arrays for JSON
|
|
167
|
+
const serialized = {
|
|
168
|
+
version: indexToSave.version,
|
|
169
|
+
lastUpdated: indexToSave.lastUpdated,
|
|
170
|
+
documents: Array.from(indexToSave.documents.entries()),
|
|
171
|
+
idf: Array.from(indexToSave.idf.entries()),
|
|
172
|
+
};
|
|
173
|
+
await fs.writeFile(this.indexPath, JSON.stringify(serialized, null, 2), 'utf-8');
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get the current cached index.
|
|
177
|
+
*
|
|
178
|
+
* @returns Cached index or null if not loaded
|
|
179
|
+
*/
|
|
180
|
+
getIndex() {
|
|
181
|
+
return this.index;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Clear the cached index and delete from disk.
|
|
185
|
+
*/
|
|
186
|
+
async clearIndex() {
|
|
187
|
+
this.index = null;
|
|
188
|
+
try {
|
|
189
|
+
await fs.unlink(this.indexPath);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Index file doesn't exist, nothing to delete
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if the index needs rebuilding based on graph state.
|
|
197
|
+
*
|
|
198
|
+
* @param graph - Current knowledge graph
|
|
199
|
+
* @returns True if index should be rebuilt
|
|
200
|
+
*/
|
|
201
|
+
needsRebuild(graph) {
|
|
202
|
+
if (!this.index) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
// Check if entity count matches
|
|
206
|
+
if (this.index.documents.size !== graph.entities.length) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
// Check if all entities are in index
|
|
210
|
+
for (const entity of graph.entities) {
|
|
211
|
+
if (!this.index.documents.has(entity.name)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search Module Barrel Export
|
|
3
|
+
*/
|
|
4
|
+
export { BasicSearch } from './BasicSearch.js';
|
|
5
|
+
export { RankedSearch } from './RankedSearch.js';
|
|
6
|
+
export { BooleanSearch } from './BooleanSearch.js';
|
|
7
|
+
export { FuzzySearch } from './FuzzySearch.js';
|
|
8
|
+
export { SearchSuggestions } from './SearchSuggestions.js';
|
|
9
|
+
export { SavedSearchManager } from './SavedSearchManager.js';
|
|
10
|
+
export { SearchManager } from './SearchManager.js';
|