@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.
@@ -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
- projectRoot;
20
- projectId;
21
- constructor(embeddingService, vectorStore, storagePath = ".memorybank", projectRoot) {
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
- * Generates a unique project ID from the project root path
28
+ * Sets the Project Knowledge Service for auto-generating docs
31
29
  */
32
- generateProjectId(projectRoot) {
33
- return crypto.createHash("sha256").update(projectRoot).digest("hex").substring(0, 16);
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
- logger.warn(`Could not load index metadata: ${error}`);
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
- logger.warn(`Could not save index metadata: ${error}`);
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, saveMetadata = true) {
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
- logger.debug(`Skipping ${file.path} (no changes)`);
98
+ console.error(`Skipping ${file.path} (no changes)`);
93
99
  return { chunksCreated: 0 };
94
100
  }
95
- logger.info(`Indexing: ${file.path}`);
101
+ console.error(`Indexing: ${file.path}`);
96
102
  // Read file content
97
103
  const content = fs.readFileSync(file.absolutePath, "utf-8");
98
- // Get chunk size from environment or use defaults
99
- const maxChunkSize = parseInt(process.env.MEMORYBANK_CHUNK_SIZE || "1000");
100
- const chunkOverlap = parseInt(process.env.MEMORYBANK_CHUNK_OVERLAP || "200");
101
- // Chunk the code
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
- maxChunkSize,
107
- chunkOverlap,
113
+ maxTokens,
114
+ chunkOverlapTokens,
108
115
  });
109
116
  if (chunks.length === 0) {
110
- logger.warn(`No chunks created for ${file.path}`);
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 = validChunks.map((chunk) => ({
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
- logger.debug(` Generated ${embeddings.length} embeddings`);
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
- const chunkRecords = validChunks.map((chunk, i) => ({
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
- filePath: chunk.filePath,
135
+ file_path: chunk.filePath,
133
136
  content: chunk.content,
134
- startLine: chunk.startLine,
135
- endLine: chunk.endLine,
136
- chunkType: chunk.chunkType,
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
- fileHash: file.hash,
142
+ file_hash: file.hash,
140
143
  timestamp,
141
- context: chunk.context,
142
- projectId: this.projectId,
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, this.projectId);
148
+ await this.vectorStore.deleteChunksByFile(file.path);
146
149
  // Insert new chunks
147
150
  await this.vectorStore.insertChunks(chunkRecords);
148
- logger.debug(` Stored ${chunkRecords.length} chunks in vector store`);
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
- if (saveMetadata) {
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
- logger.error(errorMsg);
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
- logger.info(`=== Starting indexing process ===`);
173
- logger.info(`Root path: ${options.rootPath}`);
174
- logger.info(`Force reindex: ${options.forceReindex || false}`);
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
- logger.info(`Scanning files...`);
179
- const files = await scanFiles({
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
- logger.warn("No files found to index");
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
- logger.info(`Found ${files.length} files, ${filesToIndex.length} need indexing`);
219
+ console.error(`\nFound ${files.length} files, ${filesToIndex.length} need indexing`);
196
220
  if (filesToIndex.length === 0) {
197
- logger.info("All files are up to date");
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 in batches
230
+ // Index files
206
231
  const errors = [];
232
+ const changedFiles = [];
207
233
  let totalChunks = 0;
208
234
  let processedFiles = 0;
209
- const batchSize = 5; // Concurrency limit
210
- for (let i = 0; i < filesToIndex.length; i += batchSize) {
211
- const batch = filesToIndex.slice(i, i + batchSize);
212
- const batchNum = Math.floor(i / batchSize) + 1;
213
- const totalBatches = Math.ceil(filesToIndex.length / batchSize);
214
- logger.info(`Processing batch ${batchNum}/${totalBatches} (${batch.length} files)`);
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
- // Save metadata and embedding cache after each batch
231
- this.saveMetadata();
232
- this.embeddingService.saveCache();
233
- // Small delay between batches
234
- if (i + batchSize < filesToIndex.length) {
235
- await new Promise(resolve => setTimeout(resolve, 100));
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 duration = Date.now() - startTime;
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, projectRoot) {
285
+ async reindexFile(filePath, rootPath, projectId) {
255
286
  try {
256
287
  // Scan the specific file
257
- const file = await scanSingleFile(filePath, rootPath, projectRoot);
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.filePath,
361
+ filePath: result.chunk.file_path,
329
362
  content: result.chunk.content,
330
- startLine: result.chunk.startLine,
331
- endLine: result.chunk.endLine,
332
- chunkType: result.chunk.chunkType,
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
- logger.info("Index cleared");
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, this.projectId);
392
+ await this.vectorStore.deleteChunksByFile(filePath);
360
393
  delete this.metadata.files[filePath];
361
394
  this.saveMetadata();
362
- logger.info(`Removed ${filePath} from index`);
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, workspaceRoot) {
401
+ export function createIndexManager(embeddingService, vectorStore) {
369
402
  const storagePath = process.env.MEMORYBANK_STORAGE_PATH || ".memorybank";
370
- return new IndexManager(embeddingService, vectorStore, storagePath, workspaceRoot);
403
+ return new IndexManager(embeddingService, vectorStore, storagePath);
371
404
  }