@grec0/memory-bank-mcp 0.0.2 → 0.0.4
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 +74 -5
- package/dist/common/chunker.js +168 -24
- package/dist/common/fileScanner.js +94 -10
- package/dist/common/indexManager.js +97 -25
- package/dist/common/logger.js +54 -0
- package/dist/common/projectKnowledgeService.js +627 -0
- package/dist/common/vectorStore.js +77 -21
- package/dist/index.js +76 -8
- package/dist/tools/analyzeCoverage.js +1 -1
- package/dist/tools/generateProjectDocs.js +133 -0
- package/dist/tools/getProjectDocs.js +126 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/searchMemory.js +2 -2
- package/package.json +2 -1
|
@@ -14,11 +14,29 @@ export class IndexManager {
|
|
|
14
14
|
vectorStore;
|
|
15
15
|
metadataPath;
|
|
16
16
|
metadata;
|
|
17
|
+
projectKnowledgeService = null;
|
|
18
|
+
autoUpdateDocs = false;
|
|
17
19
|
constructor(embeddingService, vectorStore, storagePath = ".memorybank") {
|
|
18
20
|
this.embeddingService = embeddingService;
|
|
19
21
|
this.vectorStore = vectorStore;
|
|
20
22
|
this.metadataPath = path.join(storagePath, "index-metadata.json");
|
|
21
23
|
this.metadata = this.loadMetadata();
|
|
24
|
+
// Check if auto-update docs is enabled via environment variable
|
|
25
|
+
this.autoUpdateDocs = process.env.MEMORYBANK_AUTO_UPDATE_DOCS === "true";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Sets the Project Knowledge Service for auto-generating docs
|
|
29
|
+
*/
|
|
30
|
+
setProjectKnowledgeService(service) {
|
|
31
|
+
this.projectKnowledgeService = service;
|
|
32
|
+
console.error("Project Knowledge Service attached to Index Manager");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Enables or disables auto-update of project docs after indexing
|
|
36
|
+
*/
|
|
37
|
+
setAutoUpdateDocs(enabled) {
|
|
38
|
+
this.autoUpdateDocs = enabled;
|
|
39
|
+
console.error(`Auto-update project docs: ${enabled ? "enabled" : "disabled"}`);
|
|
22
40
|
}
|
|
23
41
|
/**
|
|
24
42
|
* Loads index metadata from disk
|
|
@@ -73,7 +91,7 @@ export class IndexManager {
|
|
|
73
91
|
/**
|
|
74
92
|
* Indexes a single file
|
|
75
93
|
*/
|
|
76
|
-
async indexFile(file, forceReindex = false) {
|
|
94
|
+
async indexFile(file, forceReindex = false, projectId = "default") {
|
|
77
95
|
try {
|
|
78
96
|
// Check if file needs reindexing
|
|
79
97
|
if (!this.needsReindexing(file, forceReindex)) {
|
|
@@ -83,16 +101,17 @@ export class IndexManager {
|
|
|
83
101
|
console.error(`Indexing: ${file.path}`);
|
|
84
102
|
// Read file content
|
|
85
103
|
const content = fs.readFileSync(file.absolutePath, "utf-8");
|
|
86
|
-
// Get
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
104
|
+
// Get token limits from environment or use defaults
|
|
105
|
+
// text-embedding-3-small has 8192 token limit, default to 7500 for safety
|
|
106
|
+
const maxTokens = parseInt(process.env.MEMORYBANK_MAX_TOKENS || "7500");
|
|
107
|
+
const chunkOverlapTokens = parseInt(process.env.MEMORYBANK_CHUNK_OVERLAP_TOKENS || "200");
|
|
108
|
+
// Chunk the code using token-based chunking
|
|
90
109
|
const chunks = chunkCode({
|
|
91
110
|
filePath: file.path,
|
|
92
111
|
content,
|
|
93
112
|
language: file.language,
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
maxTokens,
|
|
114
|
+
chunkOverlapTokens,
|
|
96
115
|
});
|
|
97
116
|
if (chunks.length === 0) {
|
|
98
117
|
console.error(`Warning: No chunks created for ${file.path}`);
|
|
@@ -106,21 +125,24 @@ export class IndexManager {
|
|
|
106
125
|
}));
|
|
107
126
|
const embeddings = await this.embeddingService.generateBatchEmbeddings(embeddingInputs);
|
|
108
127
|
console.error(` Generated ${embeddings.length} embeddings`);
|
|
109
|
-
// Prepare chunk records for storage
|
|
128
|
+
// Prepare chunk records for storage (using snake_case for LanceDB)
|
|
129
|
+
// Note: All fields must have non-undefined values for LanceDB Arrow conversion
|
|
110
130
|
const timestamp = Date.now();
|
|
131
|
+
console.error(` Storing chunks with project_id: '${projectId}'`);
|
|
111
132
|
const chunkRecords = chunks.map((chunk, i) => ({
|
|
112
133
|
id: chunk.id,
|
|
113
134
|
vector: embeddings[i].vector,
|
|
114
|
-
|
|
135
|
+
file_path: chunk.filePath,
|
|
115
136
|
content: chunk.content,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
name: chunk.name,
|
|
137
|
+
start_line: chunk.startLine,
|
|
138
|
+
end_line: chunk.endLine,
|
|
139
|
+
chunk_type: chunk.chunkType,
|
|
140
|
+
name: chunk.name || "", // Ensure non-undefined for LanceDB
|
|
120
141
|
language: chunk.language,
|
|
121
|
-
|
|
142
|
+
file_hash: file.hash,
|
|
122
143
|
timestamp,
|
|
123
|
-
context: chunk.context,
|
|
144
|
+
context: chunk.context || "", // Ensure non-undefined for LanceDB
|
|
145
|
+
project_id: projectId,
|
|
124
146
|
}));
|
|
125
147
|
// Delete old chunks for this file
|
|
126
148
|
await this.vectorStore.deleteChunksByFile(file.path);
|
|
@@ -143,14 +165,37 @@ export class IndexManager {
|
|
|
143
165
|
return { chunksCreated: 0, error: errorMsg };
|
|
144
166
|
}
|
|
145
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Derives a project ID from the root path if not provided
|
|
170
|
+
*/
|
|
171
|
+
deriveProjectId(rootPath, providedId) {
|
|
172
|
+
if (providedId) {
|
|
173
|
+
return providedId;
|
|
174
|
+
}
|
|
175
|
+
// Use the directory name as project ID
|
|
176
|
+
const dirName = path.basename(path.resolve(rootPath));
|
|
177
|
+
// Sanitize: remove special chars, lowercase, replace spaces with dashes
|
|
178
|
+
const sanitized = dirName
|
|
179
|
+
.toLowerCase()
|
|
180
|
+
.replace(/[^a-z0-9-_]/g, "-")
|
|
181
|
+
.replace(/-+/g, "-")
|
|
182
|
+
.replace(/^-|-$/g, "");
|
|
183
|
+
return sanitized || "default";
|
|
184
|
+
}
|
|
146
185
|
/**
|
|
147
186
|
* Indexes multiple files or a directory
|
|
148
187
|
*/
|
|
149
188
|
async indexFiles(options) {
|
|
150
189
|
const startTime = Date.now();
|
|
190
|
+
const projectId = this.deriveProjectId(options.rootPath, options.projectId);
|
|
191
|
+
const shouldAutoUpdateDocs = options.autoUpdateDocs !== undefined
|
|
192
|
+
? options.autoUpdateDocs
|
|
193
|
+
: this.autoUpdateDocs;
|
|
151
194
|
console.error(`\n=== Starting indexing process ===`);
|
|
152
195
|
console.error(`Root path: ${options.rootPath}`);
|
|
196
|
+
console.error(`Project ID: ${projectId}`);
|
|
153
197
|
console.error(`Force reindex: ${options.forceReindex || false}`);
|
|
198
|
+
console.error(`Auto-update docs: ${shouldAutoUpdateDocs}`);
|
|
154
199
|
// Initialize vector store
|
|
155
200
|
await this.vectorStore.initialize();
|
|
156
201
|
// Scan files
|
|
@@ -163,6 +208,7 @@ export class IndexManager {
|
|
|
163
208
|
console.error("No files found to index");
|
|
164
209
|
return {
|
|
165
210
|
filesProcessed: 0,
|
|
211
|
+
changedFiles: [],
|
|
166
212
|
chunksCreated: 0,
|
|
167
213
|
errors: [],
|
|
168
214
|
duration: Date.now() - startTime,
|
|
@@ -175,6 +221,7 @@ export class IndexManager {
|
|
|
175
221
|
console.error("All files are up to date");
|
|
176
222
|
return {
|
|
177
223
|
filesProcessed: 0,
|
|
224
|
+
changedFiles: [],
|
|
178
225
|
chunksCreated: 0,
|
|
179
226
|
errors: [],
|
|
180
227
|
duration: Date.now() - startTime,
|
|
@@ -182,37 +229,60 @@ export class IndexManager {
|
|
|
182
229
|
}
|
|
183
230
|
// Index files
|
|
184
231
|
const errors = [];
|
|
232
|
+
const changedFiles = [];
|
|
185
233
|
let totalChunks = 0;
|
|
186
234
|
let processedFiles = 0;
|
|
187
235
|
for (let i = 0; i < filesToIndex.length; i++) {
|
|
188
236
|
const file = filesToIndex[i];
|
|
189
237
|
console.error(`\n[${i + 1}/${filesToIndex.length}] Processing ${file.path}`);
|
|
190
|
-
const result = await this.indexFile(file, options.forceReindex || false);
|
|
238
|
+
const result = await this.indexFile(file, options.forceReindex || false, projectId);
|
|
191
239
|
if (result.error) {
|
|
192
240
|
errors.push(result.error);
|
|
193
241
|
}
|
|
194
242
|
else {
|
|
195
243
|
processedFiles++;
|
|
196
244
|
totalChunks += result.chunksCreated;
|
|
245
|
+
changedFiles.push(file.path);
|
|
197
246
|
}
|
|
198
247
|
}
|
|
199
|
-
const
|
|
248
|
+
const indexDuration = Date.now() - startTime;
|
|
200
249
|
console.error(`\n=== Indexing complete ===`);
|
|
201
250
|
console.error(`Files processed: ${processedFiles}`);
|
|
202
251
|
console.error(`Chunks created: ${totalChunks}`);
|
|
203
252
|
console.error(`Errors: ${errors.length}`);
|
|
204
|
-
console.error(`Duration: ${(
|
|
253
|
+
console.error(`Duration: ${(indexDuration / 1000).toFixed(2)}s`);
|
|
254
|
+
// Run post-indexing hook to update project documentation
|
|
255
|
+
let docsGeneration;
|
|
256
|
+
if (shouldAutoUpdateDocs && this.projectKnowledgeService && changedFiles.length > 0) {
|
|
257
|
+
console.error(`\n=== Updating project documentation ===`);
|
|
258
|
+
try {
|
|
259
|
+
// Get all chunks for the project
|
|
260
|
+
const allChunks = await this.vectorStore.getAllChunks(projectId);
|
|
261
|
+
// Update docs incrementally based on changed files
|
|
262
|
+
docsGeneration = await this.projectKnowledgeService.updateDocuments(allChunks, changedFiles);
|
|
263
|
+
console.error(`Docs updated: ${docsGeneration.documentsUpdated.length}`);
|
|
264
|
+
console.error(`Docs generated: ${docsGeneration.documentsGenerated.length}`);
|
|
265
|
+
console.error(`Reasoning tokens: ${docsGeneration.totalReasoningTokens}`);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error(`Warning: Failed to update project docs: ${error.message}`);
|
|
269
|
+
errors.push(`Project docs update failed: ${error.message}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const totalDuration = Date.now() - startTime;
|
|
205
273
|
return {
|
|
206
274
|
filesProcessed: processedFiles,
|
|
275
|
+
changedFiles,
|
|
207
276
|
chunksCreated: totalChunks,
|
|
208
277
|
errors,
|
|
209
|
-
duration,
|
|
278
|
+
duration: totalDuration,
|
|
279
|
+
docsGeneration,
|
|
210
280
|
};
|
|
211
281
|
}
|
|
212
282
|
/**
|
|
213
283
|
* Re-indexes a specific file by path
|
|
214
284
|
*/
|
|
215
|
-
async reindexFile(filePath, rootPath) {
|
|
285
|
+
async reindexFile(filePath, rootPath, projectId) {
|
|
216
286
|
try {
|
|
217
287
|
// Scan the specific file
|
|
218
288
|
const file = scanSingleFile(filePath, rootPath);
|
|
@@ -223,10 +293,12 @@ export class IndexManager {
|
|
|
223
293
|
error: "File not found or not a code file",
|
|
224
294
|
};
|
|
225
295
|
}
|
|
296
|
+
// Derive project ID from root path if not provided
|
|
297
|
+
const resolvedProjectId = this.deriveProjectId(rootPath, projectId);
|
|
226
298
|
// Initialize vector store
|
|
227
299
|
await this.vectorStore.initialize();
|
|
228
300
|
// Index the file
|
|
229
|
-
const result = await this.indexFile(file, true);
|
|
301
|
+
const result = await this.indexFile(file, true, resolvedProjectId);
|
|
230
302
|
if (result.error) {
|
|
231
303
|
return {
|
|
232
304
|
success: false,
|
|
@@ -286,11 +358,11 @@ export class IndexManager {
|
|
|
286
358
|
});
|
|
287
359
|
// Format results
|
|
288
360
|
return results.map((result) => ({
|
|
289
|
-
filePath: result.chunk.
|
|
361
|
+
filePath: result.chunk.file_path,
|
|
290
362
|
content: result.chunk.content,
|
|
291
|
-
startLine: result.chunk.
|
|
292
|
-
endLine: result.chunk.
|
|
293
|
-
chunkType: result.chunk.
|
|
363
|
+
startLine: result.chunk.start_line,
|
|
364
|
+
endLine: result.chunk.end_line,
|
|
365
|
+
chunkType: result.chunk.chunk_type,
|
|
294
366
|
name: result.chunk.name,
|
|
295
367
|
language: result.chunk.language,
|
|
296
368
|
score: result.score,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Structured logger for Memory Bank MCP
|
|
3
|
+
* Ensures all logs are written to stderr to avoid breaking JSON-RPC on stdout
|
|
4
|
+
*/
|
|
5
|
+
export var LogLevel;
|
|
6
|
+
(function (LogLevel) {
|
|
7
|
+
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
|
|
8
|
+
LogLevel[LogLevel["INFO"] = 1] = "INFO";
|
|
9
|
+
LogLevel[LogLevel["WARN"] = 2] = "WARN";
|
|
10
|
+
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
|
|
11
|
+
})(LogLevel || (LogLevel = {}));
|
|
12
|
+
export class Logger {
|
|
13
|
+
static instance;
|
|
14
|
+
level = LogLevel.INFO;
|
|
15
|
+
constructor() { }
|
|
16
|
+
static getInstance() {
|
|
17
|
+
if (!Logger.instance) {
|
|
18
|
+
Logger.instance = new Logger();
|
|
19
|
+
}
|
|
20
|
+
return Logger.instance;
|
|
21
|
+
}
|
|
22
|
+
setLevel(level) {
|
|
23
|
+
this.level = level;
|
|
24
|
+
}
|
|
25
|
+
formatMessage(level, message) {
|
|
26
|
+
const timestamp = new Date().toISOString();
|
|
27
|
+
return `[${timestamp}] [${level}] ${message}`;
|
|
28
|
+
}
|
|
29
|
+
debug(message) {
|
|
30
|
+
if (this.level <= LogLevel.DEBUG) {
|
|
31
|
+
console.error(this.formatMessage("DEBUG", message));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
info(message) {
|
|
35
|
+
if (this.level <= LogLevel.INFO) {
|
|
36
|
+
console.error(this.formatMessage("INFO", message));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
warn(message) {
|
|
40
|
+
if (this.level <= LogLevel.WARN) {
|
|
41
|
+
console.error(this.formatMessage("WARN", message));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
error(message, error) {
|
|
45
|
+
if (this.level <= LogLevel.ERROR) {
|
|
46
|
+
const errorMsg = error ? ` ${error instanceof Error ? error.message : String(error)}` : "";
|
|
47
|
+
console.error(this.formatMessage("ERROR", message + errorMsg));
|
|
48
|
+
if (error instanceof Error && error.stack) {
|
|
49
|
+
console.error(error.stack);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export const logger = Logger.getInstance();
|