@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.
@@ -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 chunk size from environment or use defaults
87
- const maxChunkSize = parseInt(process.env.MEMORYBANK_CHUNK_SIZE || "1000");
88
- const chunkOverlap = parseInt(process.env.MEMORYBANK_CHUNK_OVERLAP || "200");
89
- // 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
90
109
  const chunks = chunkCode({
91
110
  filePath: file.path,
92
111
  content,
93
112
  language: file.language,
94
- maxChunkSize,
95
- chunkOverlap,
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
- filePath: chunk.filePath,
135
+ file_path: chunk.filePath,
115
136
  content: chunk.content,
116
- startLine: chunk.startLine,
117
- endLine: chunk.endLine,
118
- chunkType: chunk.chunkType,
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
- fileHash: file.hash,
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 duration = Date.now() - startTime;
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: ${(duration / 1000).toFixed(2)}s`);
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.filePath,
361
+ filePath: result.chunk.file_path,
290
362
  content: result.chunk.content,
291
- startLine: result.chunk.startLine,
292
- endLine: result.chunk.endLine,
293
- chunkType: result.chunk.chunkType,
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();