@mrxkun/mcfast-mcp 4.1.10 → 4.1.12
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/package.json +2 -2
- package/src/index.js +297 -21
- package/src/memory/memory-engine.js +232 -25
- package/src/memory/stores/base-database.js +223 -0
- package/src/memory/utils/chunker.js +1 -0
- package/src/memory/utils/indexer.js +110 -4
- package/src/memory/utils/logger.js +162 -0
- package/src/memory/utils/vector-index.js +241 -0
- package/src/memory/watchers/file-watcher.js +257 -103
- package/src/tools/project_analyze.js +491 -0
- package/src/utils/audit-queue.js +1 -0
|
@@ -59,12 +59,12 @@ export class CodeIndexer {
|
|
|
59
59
|
const fileId = this.generateFileId(filePath);
|
|
60
60
|
|
|
61
61
|
try {
|
|
62
|
-
//
|
|
62
|
+
// Try tree-sitter query first (for functions)
|
|
63
63
|
const query = await getQuery(language, 'definitions');
|
|
64
|
-
|
|
65
|
-
if (query) {
|
|
64
|
+
|
|
65
|
+
if (query && ast?.rootNode) {
|
|
66
66
|
const captures = query.captures(ast.rootNode);
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
for (const capture of captures) {
|
|
69
69
|
if (capture.name === 'name' || capture.name === 'function') {
|
|
70
70
|
facts.push({
|
|
@@ -81,6 +81,95 @@ export class CodeIndexer {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
// Fallback/enhancement: Extract more facts using regex patterns
|
|
86
|
+
// This works across languages and captures additional symbols
|
|
87
|
+
const content = ast?.text || '';
|
|
88
|
+
|
|
89
|
+
// Extract classes
|
|
90
|
+
const classMatches = content.matchAll(/class\s+(\w+)(?:\s+extends\s+(\w+))?/g);
|
|
91
|
+
for (const match of classMatches) {
|
|
92
|
+
if (!facts.find(f => f.type === 'class' && f.name === match[1])) {
|
|
93
|
+
facts.push({
|
|
94
|
+
id: this.generateFactId(fileId, match[1], 'class'),
|
|
95
|
+
file_id: fileId,
|
|
96
|
+
type: 'class',
|
|
97
|
+
name: match[1],
|
|
98
|
+
line_start: this.findLineNumber(content, match[0]) || 0,
|
|
99
|
+
line_end: 0,
|
|
100
|
+
signature: match[2] ? `extends ${match[2]}` : '',
|
|
101
|
+
exported: this.isExported(content, match[1]),
|
|
102
|
+
confidence: 0.9
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Extract interfaces (TypeScript)
|
|
108
|
+
if (language === 'typescript') {
|
|
109
|
+
const interfaceMatches = content.matchAll(/interface\s+(\w+)(?:\s+extends\s+([^{]+))?/g);
|
|
110
|
+
for (const match of interfaceMatches) {
|
|
111
|
+
if (!facts.find(f => f.type === 'interface' && f.name === match[1])) {
|
|
112
|
+
facts.push({
|
|
113
|
+
id: this.generateFactId(fileId, match[1], 'interface'),
|
|
114
|
+
file_id: fileId,
|
|
115
|
+
type: 'interface',
|
|
116
|
+
name: match[1],
|
|
117
|
+
line_start: this.findLineNumber(content, match[0]) || 0,
|
|
118
|
+
line_end: 0,
|
|
119
|
+
signature: match[2] ? `extends ${match[2].trim()}` : '',
|
|
120
|
+
exported: this.isExported(content, match[1]),
|
|
121
|
+
confidence: 0.9
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Extract imports
|
|
128
|
+
const importMatches = content.matchAll(/import\s+(?:(\w+)|\{\s*([^}]+)\s*\}|\*\s+as\s+(\w+))\s+from\s+['"]([^'"]+)['"]/g);
|
|
129
|
+
for (const match of importMatches) {
|
|
130
|
+
const imported = match[1] || match[2]?.split(',').map(s => s.trim().split(' ')[0]).join(', ') || match[3];
|
|
131
|
+
facts.push({
|
|
132
|
+
id: this.generateFactId(fileId, imported, 'import'),
|
|
133
|
+
file_id: fileId,
|
|
134
|
+
type: 'import',
|
|
135
|
+
name: imported,
|
|
136
|
+
line_start: this.findLineNumber(content, match[0]) || 0,
|
|
137
|
+
line_end: 0,
|
|
138
|
+
signature: match[4],
|
|
139
|
+
exported: false,
|
|
140
|
+
confidence: 1.0
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Extract exports
|
|
145
|
+
const exportMatches = content.matchAll(/export\s+(?:default\s+)?(?:const|let|var|function|class|interface|type)\s+(\w+)/g);
|
|
146
|
+
for (const match of exportMatches) {
|
|
147
|
+
if (!facts.find(f => f.name === match[1] && f.exported)) {
|
|
148
|
+
const existing = facts.find(f => f.name === match[1]);
|
|
149
|
+
if (existing) {
|
|
150
|
+
existing.exported = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Extract type definitions (TypeScript)
|
|
156
|
+
if (language === 'typescript') {
|
|
157
|
+
const typeMatches = content.matchAll(/type\s+(\w+)\s*=/g);
|
|
158
|
+
for (const match of typeMatches) {
|
|
159
|
+
facts.push({
|
|
160
|
+
id: this.generateFactId(fileId, match[1], 'type'),
|
|
161
|
+
file_id: fileId,
|
|
162
|
+
type: 'type',
|
|
163
|
+
name: match[1],
|
|
164
|
+
line_start: this.findLineNumber(content, match[0]) || 0,
|
|
165
|
+
line_end: 0,
|
|
166
|
+
signature: 'type alias',
|
|
167
|
+
exported: this.isExported(content, match[1]),
|
|
168
|
+
confidence: 0.8
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
84
173
|
} catch (error) {
|
|
85
174
|
console.warn(`[Indexer] Failed to extract facts:`, error.message);
|
|
86
175
|
}
|
|
@@ -88,6 +177,23 @@ export class CodeIndexer {
|
|
|
88
177
|
return facts;
|
|
89
178
|
}
|
|
90
179
|
|
|
180
|
+
// Helper to find line number of a string in content
|
|
181
|
+
findLineNumber(content, searchStr) {
|
|
182
|
+
const lines = content.split('\n');
|
|
183
|
+
for (let i = 0; i < lines.length; i++) {
|
|
184
|
+
if (lines[i].includes(searchStr)) {
|
|
185
|
+
return i + 1;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if a symbol is exported
|
|
192
|
+
isExported(content, symbolName) {
|
|
193
|
+
return new RegExp(`export\\s+.*\\b${symbolName}\\b`).test(content) ||
|
|
194
|
+
new RegExp(`export\\s+default`).test(content);
|
|
195
|
+
}
|
|
196
|
+
|
|
91
197
|
async generateEmbeddings(chunks, language = 'javascript') {
|
|
92
198
|
const embeddings = [];
|
|
93
199
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility for Memory System
|
|
3
|
+
* Provides structured logging with different levels
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Log levels: debug, info, warn, error
|
|
7
|
+
* - Timestamp formatting
|
|
8
|
+
* - Module context
|
|
9
|
+
* - Configurable output
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const LOG_LEVELS = {
|
|
13
|
+
debug: 0,
|
|
14
|
+
info: 1,
|
|
15
|
+
warn: 2,
|
|
16
|
+
error: 3
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
class Logger {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.module = options.module || 'Memory';
|
|
22
|
+
this.level = options.level || 'info';
|
|
23
|
+
this.prefix = options.prefix || '';
|
|
24
|
+
|
|
25
|
+
// Set minimum level
|
|
26
|
+
this.minLevel = LOG_LEVELS[options.minLevel?.toLowerCase()] ?? LOG_LEVELS.info;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set log level
|
|
31
|
+
*/
|
|
32
|
+
setLevel(level) {
|
|
33
|
+
this.minLevel = LOG_LEVELS[level?.toLowerCase()] ?? LOG_LEVELS.info;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format timestamp
|
|
38
|
+
*/
|
|
39
|
+
_formatTime() {
|
|
40
|
+
return new Date().toISOString();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Format log message
|
|
45
|
+
*/
|
|
46
|
+
_format(level, message, data = null) {
|
|
47
|
+
const timestamp = this._formatTime();
|
|
48
|
+
const context = this.prefix ? `[${this.prefix}]` : `[${this.module}]`;
|
|
49
|
+
|
|
50
|
+
let formatted = `${timestamp} ${context} ${level.toUpperCase()}: ${message}`;
|
|
51
|
+
|
|
52
|
+
if (data) {
|
|
53
|
+
if (data instanceof Error) {
|
|
54
|
+
formatted += `\n Stack: ${data.stack}`;
|
|
55
|
+
} else if (typeof data === 'object') {
|
|
56
|
+
formatted += `\n Data: ${JSON.stringify(data, null, 2)}`;
|
|
57
|
+
} else {
|
|
58
|
+
formatted += `\n ${data}`;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return formatted;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Debug level logging
|
|
67
|
+
*/
|
|
68
|
+
debug(message, data = null) {
|
|
69
|
+
if (this.minLevel > LOG_LEVELS.debug) return;
|
|
70
|
+
console.error(this._format('debug', message, data));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Info level logging
|
|
75
|
+
*/
|
|
76
|
+
info(message, data = null) {
|
|
77
|
+
if (this.minLevel > LOG_LEVELS.info) return;
|
|
78
|
+
console.error(this._format('info', message, data));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Warn level logging
|
|
83
|
+
*/
|
|
84
|
+
warn(message, data = null) {
|
|
85
|
+
if (this.minLevel > LOG_LEVELS.warn) return;
|
|
86
|
+
console.error(this._format('warn', message, data));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Error level logging
|
|
91
|
+
*/
|
|
92
|
+
error(message, data = null) {
|
|
93
|
+
if (this.minLevel > LOG_LEVELS.error) return;
|
|
94
|
+
console.error(this._format('error', message, data));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create child logger with additional context
|
|
99
|
+
*/
|
|
100
|
+
child(options = {}) {
|
|
101
|
+
return new Logger({
|
|
102
|
+
...options,
|
|
103
|
+
module: this.module,
|
|
104
|
+
minLevel: Object.keys(LOG_LEVELS).find(key => LOG_LEVELS[key] === this.minLevel)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Timer for performance tracking
|
|
110
|
+
*/
|
|
111
|
+
timer(label = 'operation') {
|
|
112
|
+
return new Timer(this, label);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Timer class for performance tracking
|
|
118
|
+
*/
|
|
119
|
+
class Timer {
|
|
120
|
+
constructor(logger, label) {
|
|
121
|
+
this.logger = logger;
|
|
122
|
+
this.label = label;
|
|
123
|
+
this.start = performance.now();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
end() {
|
|
127
|
+
const duration = performance.now() - this.start;
|
|
128
|
+
this.logger.debug(`${this.label} completed in ${duration.toFixed(2)}ms`);
|
|
129
|
+
return duration;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Pre-configured loggers for common modules
|
|
134
|
+
export const loggers = {
|
|
135
|
+
memory: new Logger({ module: 'MemoryEngine', minLevel: 'info' }),
|
|
136
|
+
watcher: new Logger({ module: 'FileWatcher', minLevel: 'info' }),
|
|
137
|
+
indexer: new Logger({ module: 'Indexer', minLevel: 'info' }),
|
|
138
|
+
database: new Logger({ module: 'Database', minLevel: 'info' }),
|
|
139
|
+
embedder: new Logger({ module: 'Embedder', minLevel: 'info' }),
|
|
140
|
+
intelligence: new Logger({ module: 'Intelligence', minLevel: 'info' })
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get logger for specific module
|
|
145
|
+
*/
|
|
146
|
+
export function getLogger(module, options = {}) {
|
|
147
|
+
if (loggers[module]) {
|
|
148
|
+
return loggers[module].child(options);
|
|
149
|
+
}
|
|
150
|
+
return new Logger({ module, ...options });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set global log level
|
|
155
|
+
*/
|
|
156
|
+
export function setGlobalLevel(level) {
|
|
157
|
+
Object.values(loggers).forEach(logger => {
|
|
158
|
+
logger.setLevel(level);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default Logger;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Index for fast similarity search
|
|
3
|
+
* Implements HNSW-like in-memory index for embeddings
|
|
4
|
+
*
|
|
5
|
+
* This provides O(log n) search instead of O(n) linear scan
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class VectorIndex {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.dimension = options.dimension || 1024;
|
|
11
|
+
this.maxElements = options.maxElements || 10000;
|
|
12
|
+
this.efConstruction = options.efConstruction || 200;
|
|
13
|
+
this.M = options.M || 16;
|
|
14
|
+
|
|
15
|
+
// In-memory storage
|
|
16
|
+
this.vectors = new Map(); // id -> Float32Array
|
|
17
|
+
this.metadata = new Map(); // id -> metadata
|
|
18
|
+
|
|
19
|
+
// Simple HNSW-like structure (layer 0 only for simplicity)
|
|
20
|
+
this.connections = new Map(); // id -> [neighbor_ids]
|
|
21
|
+
|
|
22
|
+
// Entry point for search
|
|
23
|
+
this.entryPoint = null;
|
|
24
|
+
|
|
25
|
+
// Stats
|
|
26
|
+
this.stats = {
|
|
27
|
+
searches: 0,
|
|
28
|
+
totalDuration: 0,
|
|
29
|
+
avgDuration: 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Add vector to index
|
|
35
|
+
*/
|
|
36
|
+
add(id, vector, metadata = {}) {
|
|
37
|
+
if (vector.length !== this.dimension) {
|
|
38
|
+
throw new Error(`Vector dimension ${vector.length} != expected ${this.dimension}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const vec = vector instanceof Float32Array ? vector : new Float32Array(vector);
|
|
42
|
+
this.vectors.set(id, vec);
|
|
43
|
+
this.metadata.set(id, metadata);
|
|
44
|
+
|
|
45
|
+
// Initialize empty connections
|
|
46
|
+
if (!this.connections.has(id)) {
|
|
47
|
+
this.connections.set(id, []);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Set entry point if first element
|
|
51
|
+
if (!this.entryPoint) {
|
|
52
|
+
this.entryPoint = id;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Build connections (simplified - just connect to nearest)
|
|
56
|
+
this._buildConnections(id, vec);
|
|
57
|
+
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build connections to nearest neighbors
|
|
63
|
+
*/
|
|
64
|
+
_buildConnections(id, vector) {
|
|
65
|
+
// Find nearest neighbors
|
|
66
|
+
const neighbors = [];
|
|
67
|
+
|
|
68
|
+
for (const [otherId, otherVec] of this.vectors) {
|
|
69
|
+
if (otherId === id) continue;
|
|
70
|
+
|
|
71
|
+
const dist = this._cosineDistance(vector, otherVec);
|
|
72
|
+
neighbors.push({ id: otherId, dist });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Sort by distance and take M nearest
|
|
76
|
+
neighbors.sort((a, b) => a.dist - b.dist);
|
|
77
|
+
const nearest = neighbors.slice(0, this.M);
|
|
78
|
+
|
|
79
|
+
// Update connections
|
|
80
|
+
const connections = this.connections.get(id) || [];
|
|
81
|
+
nearest.forEach(n => connections.push(n.id));
|
|
82
|
+
this.connections.set(id, connections);
|
|
83
|
+
|
|
84
|
+
// Add reverse connections
|
|
85
|
+
nearest.forEach(n => {
|
|
86
|
+
const reverseConns = this.connections.get(n.id) || [];
|
|
87
|
+
if (!reverseConns.includes(id)) {
|
|
88
|
+
reverseConns.push(id);
|
|
89
|
+
this.connections.set(n.id, reverseConns);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Search for nearest neighbors
|
|
96
|
+
*/
|
|
97
|
+
search(queryVector, k = 5, ef = 10) {
|
|
98
|
+
const startTime = performance.now();
|
|
99
|
+
|
|
100
|
+
if (!this.entryPoint || this.vectors.size === 0) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const query = queryVector instanceof Float32Array
|
|
105
|
+
? queryVector
|
|
106
|
+
: new Float32Array(queryVector);
|
|
107
|
+
|
|
108
|
+
// Greedy search from entry point
|
|
109
|
+
let current = this.entryPoint;
|
|
110
|
+
let bestDist = this._cosineDistance(query, this.vectors.get(current));
|
|
111
|
+
let visited = new Set([current]);
|
|
112
|
+
|
|
113
|
+
// Explore neighbors
|
|
114
|
+
let candidates = [current];
|
|
115
|
+
let results = [{ id: current, dist: bestDist }];
|
|
116
|
+
|
|
117
|
+
while (candidates.length > 0) {
|
|
118
|
+
const candidate = candidates.shift();
|
|
119
|
+
const candidateVec = this.vectors.get(candidate);
|
|
120
|
+
const candidateDist = this._cosineDistance(query, candidateVec);
|
|
121
|
+
|
|
122
|
+
// Update results
|
|
123
|
+
if (results.length < k || candidateDist < results[results.length - 1].dist) {
|
|
124
|
+
results.push({ id: candidate, dist: candidateDist });
|
|
125
|
+
results.sort((a, b) => a.dist - b.dist);
|
|
126
|
+
results = results.slice(0, k);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Get neighbors
|
|
130
|
+
const neighbors = this.connections.get(candidate) || [];
|
|
131
|
+
for (const neighbor of neighbors) {
|
|
132
|
+
if (visited.has(neighbor)) continue;
|
|
133
|
+
visited.add(neighbor);
|
|
134
|
+
|
|
135
|
+
const neighborDist = this._cosineDistance(query, this.vectors.get(neighbor));
|
|
136
|
+
|
|
137
|
+
// Add to candidates if promising
|
|
138
|
+
if (results.length < k || neighborDist < results[results.length - 1].dist) {
|
|
139
|
+
candidates.push(neighbor);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Sort final results
|
|
145
|
+
results.sort((a, b) => a.dist - b.dist);
|
|
146
|
+
|
|
147
|
+
// Convert distance to similarity
|
|
148
|
+
const searchResults = results.slice(0, k).map(r => ({
|
|
149
|
+
id: r.id,
|
|
150
|
+
similarity: 1 - r.dist,
|
|
151
|
+
distance: r.dist,
|
|
152
|
+
metadata: this.metadata.get(r.id)
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
// Update stats
|
|
156
|
+
const duration = performance.now() - startTime;
|
|
157
|
+
this.stats.searches++;
|
|
158
|
+
this.stats.totalDuration += duration;
|
|
159
|
+
this.stats.avgDuration = this.stats.totalDuration / this.stats.searches;
|
|
160
|
+
|
|
161
|
+
return searchResults;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Cosine distance (1 - cosine similarity)
|
|
166
|
+
*/
|
|
167
|
+
_cosineDistance(a, b) {
|
|
168
|
+
let dotProduct = 0;
|
|
169
|
+
let normA = 0;
|
|
170
|
+
let normB = 0;
|
|
171
|
+
|
|
172
|
+
for (let i = 0; i < a.length; i++) {
|
|
173
|
+
dotProduct += a[i] * b[i];
|
|
174
|
+
normA += a[i] * a[i];
|
|
175
|
+
normB += b[i] * b[i];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
179
|
+
return 1 - similarity;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Bulk add vectors
|
|
184
|
+
*/
|
|
185
|
+
bulkAdd(items) {
|
|
186
|
+
for (const item of items) {
|
|
187
|
+
this.add(item.id, item.vector, item.metadata);
|
|
188
|
+
}
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get statistics
|
|
194
|
+
*/
|
|
195
|
+
getStats() {
|
|
196
|
+
return {
|
|
197
|
+
...this.stats,
|
|
198
|
+
size: this.vectors.size,
|
|
199
|
+
dimension: this.dimension,
|
|
200
|
+
maxElements: this.maxElements
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clear index
|
|
206
|
+
*/
|
|
207
|
+
clear() {
|
|
208
|
+
this.vectors.clear();
|
|
209
|
+
this.metadata.clear();
|
|
210
|
+
this.connections.clear();
|
|
211
|
+
this.entryPoint = null;
|
|
212
|
+
this.stats = { searches: 0, totalDuration: 0, avgDuration: 0 };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Serialize to JSON
|
|
217
|
+
*/
|
|
218
|
+
toJSON() {
|
|
219
|
+
const data = {};
|
|
220
|
+
for (const [id, vec] of this.vectors) {
|
|
221
|
+
data[id] = {
|
|
222
|
+
vector: Array.from(vec),
|
|
223
|
+
metadata: this.metadata.get(id)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
return data;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Load from JSON
|
|
231
|
+
*/
|
|
232
|
+
static fromJSON(json, options = {}) {
|
|
233
|
+
const index = new VectorIndex(options);
|
|
234
|
+
for (const [id, item] of Object.entries(json)) {
|
|
235
|
+
index.add(id, item.vector, item.metadata);
|
|
236
|
+
}
|
|
237
|
+
return index;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export default VectorIndex;
|