@grec0/memory-bank-mcp 0.0.3 → 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 +494 -420
- package/dist/common/chunker.js +166 -534
- package/dist/common/embeddingService.js +39 -51
- package/dist/common/fileScanner.js +123 -58
- package/dist/common/indexManager.js +135 -102
- package/dist/common/projectKnowledgeService.js +627 -0
- package/dist/common/setup.js +49 -0
- package/dist/common/utils.js +215 -0
- package/dist/common/vectorStore.js +80 -67
- package/dist/index.js +77 -9
- package/dist/operations/boardMemberships.js +186 -0
- package/dist/operations/boards.js +268 -0
- package/dist/operations/cards.js +426 -0
- package/dist/operations/comments.js +249 -0
- package/dist/operations/labels.js +258 -0
- package/dist/operations/lists.js +157 -0
- package/dist/operations/projects.js +102 -0
- package/dist/operations/tasks.js +238 -0
- package/dist/tools/analyzeCoverage.js +46 -66
- package/dist/tools/board-summary.js +151 -0
- package/dist/tools/card-details.js +106 -0
- package/dist/tools/create-card-with-tasks.js +81 -0
- package/dist/tools/generateProjectDocs.js +133 -0
- package/dist/tools/getProjectDocs.js +126 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/indexCode.js +0 -1
- package/dist/tools/searchMemory.js +2 -2
- package/dist/tools/workflow-actions.js +145 -0
- package/package.json +2 -2
|
@@ -6,8 +6,6 @@ import * as fs from "fs";
|
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
import { scanFiles, scanSingleFile } from "./fileScanner.js";
|
|
8
8
|
import { chunkCode } from "./chunker.js";
|
|
9
|
-
import { logger } from "./logger.js";
|
|
10
|
-
import * as crypto from "crypto";
|
|
11
9
|
/**
|
|
12
10
|
* Index manager coordinating the entire indexing pipeline
|
|
13
11
|
*/
|
|
@@ -16,21 +14,29 @@ export class IndexManager {
|
|
|
16
14
|
vectorStore;
|
|
17
15
|
metadataPath;
|
|
18
16
|
metadata;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
constructor(embeddingService, vectorStore, storagePath = ".memorybank"
|
|
17
|
+
projectKnowledgeService = null;
|
|
18
|
+
autoUpdateDocs = false;
|
|
19
|
+
constructor(embeddingService, vectorStore, storagePath = ".memorybank") {
|
|
22
20
|
this.embeddingService = embeddingService;
|
|
23
21
|
this.vectorStore = vectorStore;
|
|
24
22
|
this.metadataPath = path.join(storagePath, "index-metadata.json");
|
|
25
|
-
this.projectRoot = projectRoot || process.cwd();
|
|
26
|
-
this.projectId = this.generateProjectId(this.projectRoot);
|
|
27
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";
|
|
28
26
|
}
|
|
29
27
|
/**
|
|
30
|
-
*
|
|
28
|
+
* Sets the Project Knowledge Service for auto-generating docs
|
|
31
29
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
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"}`);
|
|
34
40
|
}
|
|
35
41
|
/**
|
|
36
42
|
* Loads index metadata from disk
|
|
@@ -43,7 +49,7 @@ export class IndexManager {
|
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
catch (error) {
|
|
46
|
-
|
|
52
|
+
console.error(`Warning: Could not load index metadata: ${error}`);
|
|
47
53
|
}
|
|
48
54
|
return {
|
|
49
55
|
version: "1.0",
|
|
@@ -63,7 +69,7 @@ export class IndexManager {
|
|
|
63
69
|
fs.writeFileSync(this.metadataPath, JSON.stringify(this.metadata, null, 2));
|
|
64
70
|
}
|
|
65
71
|
catch (error) {
|
|
66
|
-
|
|
72
|
+
console.error(`Warning: Could not save index metadata: ${error}`);
|
|
67
73
|
}
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
@@ -85,67 +91,64 @@ export class IndexManager {
|
|
|
85
91
|
/**
|
|
86
92
|
* Indexes a single file
|
|
87
93
|
*/
|
|
88
|
-
async indexFile(file, forceReindex = false,
|
|
94
|
+
async indexFile(file, forceReindex = false, projectId = "default") {
|
|
89
95
|
try {
|
|
90
96
|
// Check if file needs reindexing
|
|
91
97
|
if (!this.needsReindexing(file, forceReindex)) {
|
|
92
|
-
|
|
98
|
+
console.error(`Skipping ${file.path} (no changes)`);
|
|
93
99
|
return { chunksCreated: 0 };
|
|
94
100
|
}
|
|
95
|
-
|
|
101
|
+
console.error(`Indexing: ${file.path}`);
|
|
96
102
|
// Read file content
|
|
97
103
|
const content = fs.readFileSync(file.absolutePath, "utf-8");
|
|
98
|
-
// Get
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
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
|
|
102
109
|
const chunks = chunkCode({
|
|
103
110
|
filePath: file.path,
|
|
104
111
|
content,
|
|
105
112
|
language: file.language,
|
|
106
|
-
|
|
107
|
-
|
|
113
|
+
maxTokens,
|
|
114
|
+
chunkOverlapTokens,
|
|
108
115
|
});
|
|
109
116
|
if (chunks.length === 0) {
|
|
110
|
-
|
|
111
|
-
return { chunksCreated: 0 };
|
|
112
|
-
}
|
|
113
|
-
logger.debug(` Created ${chunks.length} chunks`);
|
|
114
|
-
// Filter out invalid chunks (fail-safe)
|
|
115
|
-
const validChunks = chunks.filter(c => c.content && c.content.trim().length > 0 && c.content.trim() !== "}");
|
|
116
|
-
if (validChunks.length === 0) {
|
|
117
|
-
logger.warn(`No valid chunks after filtering for ${file.path}`);
|
|
117
|
+
console.error(`Warning: No chunks created for ${file.path}`);
|
|
118
118
|
return { chunksCreated: 0 };
|
|
119
119
|
}
|
|
120
|
+
console.error(` Created ${chunks.length} chunks`);
|
|
120
121
|
// Generate embeddings
|
|
121
|
-
const embeddingInputs =
|
|
122
|
+
const embeddingInputs = chunks.map((chunk) => ({
|
|
122
123
|
id: chunk.id,
|
|
123
124
|
content: chunk.content,
|
|
124
125
|
}));
|
|
125
126
|
const embeddings = await this.embeddingService.generateBatchEmbeddings(embeddingInputs);
|
|
126
|
-
|
|
127
|
-
// Prepare chunk records for storage
|
|
127
|
+
console.error(` Generated ${embeddings.length} embeddings`);
|
|
128
|
+
// Prepare chunk records for storage (using snake_case for LanceDB)
|
|
129
|
+
// Note: All fields must have non-undefined values for LanceDB Arrow conversion
|
|
128
130
|
const timestamp = Date.now();
|
|
129
|
-
|
|
131
|
+
console.error(` Storing chunks with project_id: '${projectId}'`);
|
|
132
|
+
const chunkRecords = chunks.map((chunk, i) => ({
|
|
130
133
|
id: chunk.id,
|
|
131
134
|
vector: embeddings[i].vector,
|
|
132
|
-
|
|
135
|
+
file_path: chunk.filePath,
|
|
133
136
|
content: chunk.content,
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
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
|
|
138
141
|
language: chunk.language,
|
|
139
|
-
|
|
142
|
+
file_hash: file.hash,
|
|
140
143
|
timestamp,
|
|
141
|
-
context: chunk.context,
|
|
142
|
-
|
|
144
|
+
context: chunk.context || "", // Ensure non-undefined for LanceDB
|
|
145
|
+
project_id: projectId,
|
|
143
146
|
}));
|
|
144
147
|
// Delete old chunks for this file
|
|
145
|
-
await this.vectorStore.deleteChunksByFile(file.path
|
|
148
|
+
await this.vectorStore.deleteChunksByFile(file.path);
|
|
146
149
|
// Insert new chunks
|
|
147
150
|
await this.vectorStore.insertChunks(chunkRecords);
|
|
148
|
-
|
|
151
|
+
console.error(` Stored ${chunkRecords.length} chunks in vector store`);
|
|
149
152
|
// Update metadata
|
|
150
153
|
this.metadata.files[file.path] = {
|
|
151
154
|
hash: file.hash,
|
|
@@ -153,38 +156,59 @@ export class IndexManager {
|
|
|
153
156
|
chunkCount: chunks.length,
|
|
154
157
|
};
|
|
155
158
|
this.metadata.lastIndexed = timestamp;
|
|
156
|
-
|
|
157
|
-
this.saveMetadata();
|
|
158
|
-
}
|
|
159
|
+
this.saveMetadata();
|
|
159
160
|
return { chunksCreated: chunks.length };
|
|
160
161
|
}
|
|
161
162
|
catch (error) {
|
|
162
163
|
const errorMsg = `Error indexing ${file.path}: ${error}`;
|
|
163
|
-
|
|
164
|
+
console.error(errorMsg);
|
|
164
165
|
return { chunksCreated: 0, error: errorMsg };
|
|
165
166
|
}
|
|
166
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
|
+
}
|
|
167
185
|
/**
|
|
168
186
|
* Indexes multiple files or a directory
|
|
169
187
|
*/
|
|
170
188
|
async indexFiles(options) {
|
|
171
189
|
const startTime = Date.now();
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
190
|
+
const projectId = this.deriveProjectId(options.rootPath, options.projectId);
|
|
191
|
+
const shouldAutoUpdateDocs = options.autoUpdateDocs !== undefined
|
|
192
|
+
? options.autoUpdateDocs
|
|
193
|
+
: this.autoUpdateDocs;
|
|
194
|
+
console.error(`\n=== Starting indexing process ===`);
|
|
195
|
+
console.error(`Root path: ${options.rootPath}`);
|
|
196
|
+
console.error(`Project ID: ${projectId}`);
|
|
197
|
+
console.error(`Force reindex: ${options.forceReindex || false}`);
|
|
198
|
+
console.error(`Auto-update docs: ${shouldAutoUpdateDocs}`);
|
|
175
199
|
// Initialize vector store
|
|
176
200
|
await this.vectorStore.initialize();
|
|
177
201
|
// Scan files
|
|
178
|
-
|
|
179
|
-
const files =
|
|
202
|
+
console.error(`\nScanning files...`);
|
|
203
|
+
const files = scanFiles({
|
|
180
204
|
rootPath: options.rootPath,
|
|
181
|
-
projectRoot: options.projectRoot,
|
|
182
205
|
recursive: options.recursive !== undefined ? options.recursive : true,
|
|
183
206
|
});
|
|
184
207
|
if (files.length === 0) {
|
|
185
|
-
|
|
208
|
+
console.error("No files found to index");
|
|
186
209
|
return {
|
|
187
210
|
filesProcessed: 0,
|
|
211
|
+
changedFiles: [],
|
|
188
212
|
chunksCreated: 0,
|
|
189
213
|
errors: [],
|
|
190
214
|
duration: Date.now() - startTime,
|
|
@@ -192,69 +216,76 @@ export class IndexManager {
|
|
|
192
216
|
}
|
|
193
217
|
// Filter files that need reindexing
|
|
194
218
|
const filesToIndex = files.filter((file) => this.needsReindexing(file, options.forceReindex || false));
|
|
195
|
-
|
|
219
|
+
console.error(`\nFound ${files.length} files, ${filesToIndex.length} need indexing`);
|
|
196
220
|
if (filesToIndex.length === 0) {
|
|
197
|
-
|
|
221
|
+
console.error("All files are up to date");
|
|
198
222
|
return {
|
|
199
223
|
filesProcessed: 0,
|
|
224
|
+
changedFiles: [],
|
|
200
225
|
chunksCreated: 0,
|
|
201
226
|
errors: [],
|
|
202
227
|
duration: Date.now() - startTime,
|
|
203
228
|
};
|
|
204
229
|
}
|
|
205
|
-
// Index files
|
|
230
|
+
// Index files
|
|
206
231
|
const errors = [];
|
|
232
|
+
const changedFiles = [];
|
|
207
233
|
let totalChunks = 0;
|
|
208
234
|
let processedFiles = 0;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const batchPromises = batch.map(async (file, index) => {
|
|
216
|
-
logger.debug(`[${i + index + 1}/${filesToIndex.length}] Processing ${file.path}`);
|
|
217
|
-
return this.indexFile(file, options.forceReindex || false, false); // Don't save metadata per file
|
|
218
|
-
});
|
|
219
|
-
const results = await Promise.all(batchPromises);
|
|
220
|
-
// Process results
|
|
221
|
-
for (const result of results) {
|
|
222
|
-
if (result.error) {
|
|
223
|
-
errors.push(result.error);
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
processedFiles++;
|
|
227
|
-
totalChunks += result.chunksCreated;
|
|
228
|
-
}
|
|
235
|
+
for (let i = 0; i < filesToIndex.length; i++) {
|
|
236
|
+
const file = filesToIndex[i];
|
|
237
|
+
console.error(`\n[${i + 1}/${filesToIndex.length}] Processing ${file.path}`);
|
|
238
|
+
const result = await this.indexFile(file, options.forceReindex || false, projectId);
|
|
239
|
+
if (result.error) {
|
|
240
|
+
errors.push(result.error);
|
|
229
241
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
else {
|
|
243
|
+
processedFiles++;
|
|
244
|
+
totalChunks += result.chunksCreated;
|
|
245
|
+
changedFiles.push(file.path);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const indexDuration = Date.now() - startTime;
|
|
249
|
+
console.error(`\n=== Indexing complete ===`);
|
|
250
|
+
console.error(`Files processed: ${processedFiles}`);
|
|
251
|
+
console.error(`Chunks created: ${totalChunks}`);
|
|
252
|
+
console.error(`Errors: ${errors.length}`);
|
|
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}`);
|
|
236
270
|
}
|
|
237
271
|
}
|
|
238
|
-
const
|
|
239
|
-
logger.info(`=== Indexing complete ===`);
|
|
240
|
-
logger.info(`Files processed: ${processedFiles}`);
|
|
241
|
-
logger.info(`Chunks created: ${totalChunks}`);
|
|
242
|
-
logger.info(`Errors: ${errors.length}`);
|
|
243
|
-
logger.info(`Duration: ${(duration / 1000).toFixed(2)}s`);
|
|
272
|
+
const totalDuration = Date.now() - startTime;
|
|
244
273
|
return {
|
|
245
274
|
filesProcessed: processedFiles,
|
|
275
|
+
changedFiles,
|
|
246
276
|
chunksCreated: totalChunks,
|
|
247
277
|
errors,
|
|
248
|
-
duration,
|
|
278
|
+
duration: totalDuration,
|
|
279
|
+
docsGeneration,
|
|
249
280
|
};
|
|
250
281
|
}
|
|
251
282
|
/**
|
|
252
283
|
* Re-indexes a specific file by path
|
|
253
284
|
*/
|
|
254
|
-
async reindexFile(filePath, rootPath,
|
|
285
|
+
async reindexFile(filePath, rootPath, projectId) {
|
|
255
286
|
try {
|
|
256
287
|
// Scan the specific file
|
|
257
|
-
const file =
|
|
288
|
+
const file = scanSingleFile(filePath, rootPath);
|
|
258
289
|
if (!file) {
|
|
259
290
|
return {
|
|
260
291
|
success: false,
|
|
@@ -262,10 +293,12 @@ export class IndexManager {
|
|
|
262
293
|
error: "File not found or not a code file",
|
|
263
294
|
};
|
|
264
295
|
}
|
|
296
|
+
// Derive project ID from root path if not provided
|
|
297
|
+
const resolvedProjectId = this.deriveProjectId(rootPath, projectId);
|
|
265
298
|
// Initialize vector store
|
|
266
299
|
await this.vectorStore.initialize();
|
|
267
300
|
// Index the file
|
|
268
|
-
const result = await this.indexFile(file, true);
|
|
301
|
+
const result = await this.indexFile(file, true, resolvedProjectId);
|
|
269
302
|
if (result.error) {
|
|
270
303
|
return {
|
|
271
304
|
success: false,
|
|
@@ -325,11 +358,11 @@ export class IndexManager {
|
|
|
325
358
|
});
|
|
326
359
|
// Format results
|
|
327
360
|
return results.map((result) => ({
|
|
328
|
-
filePath: result.chunk.
|
|
361
|
+
filePath: result.chunk.file_path,
|
|
329
362
|
content: result.chunk.content,
|
|
330
|
-
startLine: result.chunk.
|
|
331
|
-
endLine: result.chunk.
|
|
332
|
-
chunkType: result.chunk.
|
|
363
|
+
startLine: result.chunk.start_line,
|
|
364
|
+
endLine: result.chunk.end_line,
|
|
365
|
+
chunkType: result.chunk.chunk_type,
|
|
333
366
|
name: result.chunk.name,
|
|
334
367
|
language: result.chunk.language,
|
|
335
368
|
score: result.score,
|
|
@@ -349,23 +382,23 @@ export class IndexManager {
|
|
|
349
382
|
this.saveMetadata();
|
|
350
383
|
// Clear embedding cache
|
|
351
384
|
this.embeddingService.clearCache();
|
|
352
|
-
|
|
385
|
+
console.error("Index cleared");
|
|
353
386
|
}
|
|
354
387
|
/**
|
|
355
388
|
* Removes a file from the index
|
|
356
389
|
*/
|
|
357
390
|
async removeFile(filePath) {
|
|
358
391
|
await this.vectorStore.initialize();
|
|
359
|
-
await this.vectorStore.deleteChunksByFile(filePath
|
|
392
|
+
await this.vectorStore.deleteChunksByFile(filePath);
|
|
360
393
|
delete this.metadata.files[filePath];
|
|
361
394
|
this.saveMetadata();
|
|
362
|
-
|
|
395
|
+
console.error(`Removed ${filePath} from index`);
|
|
363
396
|
}
|
|
364
397
|
}
|
|
365
398
|
/**
|
|
366
399
|
* Creates an index manager from environment variables
|
|
367
400
|
*/
|
|
368
|
-
export function createIndexManager(embeddingService, vectorStore
|
|
401
|
+
export function createIndexManager(embeddingService, vectorStore) {
|
|
369
402
|
const storagePath = process.env.MEMORYBANK_STORAGE_PATH || ".memorybank";
|
|
370
|
-
return new IndexManager(embeddingService, vectorStore, storagePath
|
|
403
|
+
return new IndexManager(embeddingService, vectorStore, storagePath);
|
|
371
404
|
}
|