@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.
@@ -2,7 +2,6 @@
2
2
  * @fileoverview Embedding service for Memory Bank using OpenAI
3
3
  * Generates vector embeddings for code chunks
4
4
  */
5
- import { logger } from "./logger.js";
6
5
  import OpenAI from "openai";
7
6
  import * as crypto from "crypto";
8
7
  import * as fs from "fs";
@@ -50,7 +49,7 @@ export class EmbeddingService {
50
49
  }
51
50
  }
52
51
  /**
53
- * Saves embedding cache to disk using streams to handle large files
52
+ * Saves embedding cache to disk
54
53
  */
55
54
  saveCache() {
56
55
  try {
@@ -59,16 +58,8 @@ export class EmbeddingService {
59
58
  fs.mkdirSync(dir, { recursive: true });
60
59
  }
61
60
  const entries = Array.from(this.cache.values());
62
- const stream = fs.createWriteStream(this.options.cachePath, { encoding: 'utf8' });
63
- stream.write('[\n');
64
- entries.forEach((entry, index) => {
65
- const isLast = index === entries.length - 1;
66
- const line = JSON.stringify(entry) + (isLast ? '' : ',\n');
67
- stream.write(line);
68
- });
69
- stream.write('\n]');
70
- stream.end();
71
- console.error(`Updated embedding cache (Total: ${entries.length} entries)`);
61
+ fs.writeFileSync(this.options.cachePath, JSON.stringify(entries, null, 2));
62
+ console.error(`Saved ${entries.length} embeddings to cache`);
72
63
  }
73
64
  catch (error) {
74
65
  console.error(`Warning: Could not save embedding cache: ${error}`);
@@ -145,14 +136,14 @@ export class EmbeddingService {
145
136
  // Check if it's a rate limit error
146
137
  if (error?.status === 429 || error?.code === "rate_limit_exceeded") {
147
138
  const backoffMs = Math.pow(2, attempt) * 1000; // Exponential backoff
148
- logger.warn(`Rate limit hit, retrying in ${backoffMs}ms (attempt ${attempt + 1}/${maxRetries})`);
139
+ console.error(`Rate limit hit, retrying in ${backoffMs}ms (attempt ${attempt + 1}/${maxRetries})`);
149
140
  await this.sleep(backoffMs);
150
141
  continue;
151
142
  }
152
143
  // Check if it's a temporary error
153
144
  if (error?.status >= 500 && error?.status < 600) {
154
145
  const backoffMs = Math.pow(2, attempt) * 1000;
155
- logger.warn(`Server error ${error.status}, retrying in ${backoffMs}ms (attempt ${attempt + 1}/${maxRetries})`);
146
+ console.error(`Server error ${error.status}, retrying in ${backoffMs}ms (attempt ${attempt + 1}/${maxRetries})`);
156
147
  await this.sleep(backoffMs);
157
148
  continue;
158
149
  }
@@ -194,10 +185,9 @@ export class EmbeddingService {
194
185
  /**
195
186
  * Generates embeddings for multiple chunks in batches
196
187
  */
197
- async generateBatchEmbeddings(chunks, options = {}) {
188
+ async generateBatchEmbeddings(chunks) {
198
189
  const results = [];
199
190
  const toGenerate = [];
200
- const shouldSave = options.autoSave !== undefined ? options.autoSave : true;
201
191
  // Check cache and collect chunks that need generation
202
192
  for (let i = 0; i < chunks.length; i++) {
203
193
  const chunk = chunks[i];
@@ -214,43 +204,41 @@ export class EmbeddingService {
214
204
  toGenerate.push({ ...chunk, index: i });
215
205
  }
216
206
  }
217
- if (toGenerate.length > 0) {
218
- logger.info(`Generating embeddings: ${toGenerate.length} new, ${chunks.length - toGenerate.length} cached`);
219
- // Process in batches
220
- for (let i = 0; i < toGenerate.length; i += this.options.batchSize) {
221
- const batch = toGenerate.slice(i, i + this.options.batchSize);
222
- const batchTexts = batch.map((item) => item.content);
223
- logger.info(`Processing batch ${Math.floor(i / this.options.batchSize) + 1}/${Math.ceil(toGenerate.length / this.options.batchSize)}`);
224
- try {
225
- const vectors = await this.generateBatchWithRetry(batchTexts);
226
- // Store results and cache
227
- for (let j = 0; j < batch.length; j++) {
228
- const item = batch[j];
229
- const vector = vectors[j];
230
- // Cache the result
231
- this.cacheEmbedding(item.id, item.content, vector);
232
- // Estimate tokens
233
- const tokens = Math.ceil(item.content.length / 4);
234
- results[item.index] = {
235
- chunkId: item.id,
236
- vector,
237
- model: this.options.model,
238
- tokens,
239
- };
240
- }
241
- }
242
- catch (error) {
243
- logger.error(`Error generating batch embeddings:`, error);
244
- throw error;
245
- }
246
- // Small delay between batches to avoid rate limits
247
- if (i + this.options.batchSize < toGenerate.length) {
248
- await this.sleep(100);
207
+ console.error(`Generating embeddings: ${toGenerate.length} new, ${chunks.length - toGenerate.length} cached`);
208
+ // Process in batches
209
+ for (let i = 0; i < toGenerate.length; i += this.options.batchSize) {
210
+ const batch = toGenerate.slice(i, i + this.options.batchSize);
211
+ const batchTexts = batch.map((item) => item.content);
212
+ console.error(`Processing batch ${Math.floor(i / this.options.batchSize) + 1}/${Math.ceil(toGenerate.length / this.options.batchSize)}`);
213
+ try {
214
+ const vectors = await this.generateBatchWithRetry(batchTexts);
215
+ // Store results and cache
216
+ for (let j = 0; j < batch.length; j++) {
217
+ const item = batch[j];
218
+ const vector = vectors[j];
219
+ // Cache the result
220
+ this.cacheEmbedding(item.id, item.content, vector);
221
+ // Estimate tokens
222
+ const tokens = Math.ceil(item.content.length / 4);
223
+ results[item.index] = {
224
+ chunkId: item.id,
225
+ vector,
226
+ model: this.options.model,
227
+ tokens,
228
+ };
249
229
  }
250
230
  }
231
+ catch (error) {
232
+ console.error(`Error generating batch embeddings: ${error}`);
233
+ throw error;
234
+ }
235
+ // Small delay between batches to avoid rate limits
236
+ if (i + this.options.batchSize < toGenerate.length) {
237
+ await this.sleep(100);
238
+ }
251
239
  }
252
240
  // Save cache after batch processing
253
- if (shouldSave && this.options.enableCache && toGenerate.length > 0) {
241
+ if (this.options.enableCache && toGenerate.length > 0) {
254
242
  this.saveCache();
255
243
  }
256
244
  return results;
@@ -268,7 +256,7 @@ export class EmbeddingService {
268
256
  return response.data[0].embedding;
269
257
  }
270
258
  catch (error) {
271
- logger.error(`Error generating query embedding:`, error);
259
+ console.error(`Error generating query embedding: ${error}`);
272
260
  throw error;
273
261
  }
274
262
  }
@@ -280,7 +268,7 @@ export class EmbeddingService {
280
268
  if (fs.existsSync(this.options.cachePath)) {
281
269
  fs.unlinkSync(this.options.cachePath);
282
270
  }
283
- logger.info("Embedding cache cleared");
271
+ console.error("Embedding cache cleared");
284
272
  }
285
273
  /**
286
274
  * Gets cache statistics
@@ -6,53 +6,120 @@ import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import * as crypto from "crypto";
8
8
  import ignoreLib from "ignore";
9
- import { logger } from "./logger.js";
10
9
  // Handle ignore library export
11
10
  const ignore = typeof ignoreLib === 'function' ? ignoreLib : ignoreLib.default;
12
11
  // Language detection by file extension
13
12
  const LANGUAGE_MAP = {
13
+ // TypeScript/JavaScript
14
14
  ".ts": "typescript",
15
15
  ".tsx": "typescript",
16
16
  ".js": "javascript",
17
17
  ".jsx": "javascript",
18
18
  ".mjs": "javascript",
19
19
  ".cjs": "javascript",
20
+ // Python
20
21
  ".py": "python",
22
+ ".pyi": "python",
23
+ ".pyw": "python",
24
+ // JVM Languages
21
25
  ".java": "java",
26
+ ".kt": "kotlin",
27
+ ".kts": "kotlin",
28
+ ".scala": "scala",
29
+ ".groovy": "groovy",
30
+ ".gradle": "groovy",
31
+ // C/C++
22
32
  ".c": "c",
23
33
  ".cpp": "cpp",
24
34
  ".cc": "cpp",
25
35
  ".cxx": "cpp",
26
36
  ".h": "c",
27
37
  ".hpp": "cpp",
38
+ ".hxx": "cpp",
39
+ // .NET
28
40
  ".cs": "csharp",
41
+ ".fs": "fsharp",
42
+ ".vb": "vb",
43
+ // Systems Languages
29
44
  ".go": "go",
30
45
  ".rs": "rust",
46
+ // Scripting Languages
31
47
  ".rb": "ruby",
32
48
  ".php": "php",
33
- ".swift": "swift",
34
- ".kt": "kotlin",
35
- ".kts": "kotlin",
36
- ".scala": "scala",
49
+ ".pl": "perl",
50
+ ".pm": "perl",
51
+ ".lua": "lua",
37
52
  ".r": "r",
38
53
  ".R": "r",
39
- ".sql": "sql",
54
+ // Mobile
55
+ ".swift": "swift",
56
+ ".m": "objectivec",
57
+ ".mm": "objectivec",
58
+ // Shell
40
59
  ".sh": "shell",
41
60
  ".bash": "shell",
42
61
  ".zsh": "shell",
43
62
  ".fish": "shell",
44
- ".md": "markdown",
45
- ".json": "json",
46
- ".yaml": "yaml",
47
- ".yml": "yaml",
48
- ".xml": "xml",
63
+ ".ps1": "powershell",
64
+ ".psm1": "powershell",
65
+ ".bat": "batch",
66
+ ".cmd": "batch",
67
+ // Web
49
68
  ".html": "html",
50
69
  ".htm": "html",
51
70
  ".css": "css",
52
71
  ".scss": "scss",
53
72
  ".sass": "sass",
73
+ ".less": "less",
54
74
  ".vue": "vue",
55
75
  ".svelte": "svelte",
76
+ ".astro": "astro",
77
+ // Data/Config
78
+ ".json": "json",
79
+ ".jsonc": "json",
80
+ ".json5": "json",
81
+ ".yaml": "yaml",
82
+ ".yml": "yaml",
83
+ ".toml": "toml",
84
+ ".xml": "xml",
85
+ ".ini": "ini",
86
+ ".cfg": "ini",
87
+ ".conf": "ini",
88
+ ".properties": "properties",
89
+ ".env": "dotenv",
90
+ ".env.local": "dotenv",
91
+ ".env.example": "dotenv",
92
+ // Documentation
93
+ ".md": "markdown",
94
+ ".mdx": "markdown",
95
+ ".rst": "rst",
96
+ ".txt": "text",
97
+ // Database
98
+ ".sql": "sql",
99
+ ".prisma": "prisma",
100
+ ".graphql": "graphql",
101
+ ".gql": "graphql",
102
+ // Other
103
+ ".dockerfile": "dockerfile",
104
+ ".tf": "terraform",
105
+ ".hcl": "hcl",
106
+ ".proto": "protobuf",
107
+ ".sol": "solidity",
108
+ ".zig": "zig",
109
+ ".nim": "nim",
110
+ ".ex": "elixir",
111
+ ".exs": "elixir",
112
+ ".erl": "erlang",
113
+ ".hrl": "erlang",
114
+ ".clj": "clojure",
115
+ ".cljs": "clojure",
116
+ ".cljc": "clojure",
117
+ ".hs": "haskell",
118
+ ".elm": "elm",
119
+ ".dart": "dart",
120
+ ".v": "v",
121
+ ".asm": "assembly",
122
+ ".s": "assembly",
56
123
  };
57
124
  // Binary file extensions to skip
58
125
  const BINARY_EXTENSIONS = new Set([
@@ -77,10 +144,10 @@ export function loadIgnorePatterns(rootPath) {
77
144
  try {
78
145
  const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
79
146
  ig.add(gitignoreContent);
80
- logger.debug(`Loaded .gitignore patterns from ${gitignorePath}`);
147
+ console.error(`Loaded .gitignore patterns from ${gitignorePath}`);
81
148
  }
82
149
  catch (error) {
83
- logger.warn(`Could not read .gitignore: ${error}`);
150
+ console.error(`Warning: Could not read .gitignore: ${error}`);
84
151
  }
85
152
  }
86
153
  // Load .memoryignore if exists
@@ -89,19 +156,19 @@ export function loadIgnorePatterns(rootPath) {
89
156
  try {
90
157
  const memoryignoreContent = fs.readFileSync(memoryignorePath, "utf-8");
91
158
  ig.add(memoryignoreContent);
92
- logger.debug(`Loaded .memoryignore patterns from ${memoryignorePath}`);
159
+ console.error(`Loaded .memoryignore patterns from ${memoryignorePath}`);
93
160
  }
94
161
  catch (error) {
95
- logger.warn(`Could not read .memoryignore: ${error}`);
162
+ console.error(`Warning: Could not read .memoryignore: ${error}`);
96
163
  }
97
164
  }
98
165
  return ig;
99
166
  }
100
167
  /**
101
- * Calculates SHA-256 hash of file content asynchronously
168
+ * Calculates SHA-256 hash of file content
102
169
  */
103
- export async function calculateFileHash(filePath) {
104
- const content = await fs.promises.readFile(filePath);
170
+ export function calculateFileHash(filePath) {
171
+ const content = fs.readFileSync(filePath);
105
172
  return crypto.createHash("sha256").update(content).digest("hex");
106
173
  }
107
174
  /**
@@ -133,26 +200,39 @@ export function isCodeFile(filePath) {
133
200
  // Additional checks for files without extension or special cases
134
201
  const basename = path.basename(filePath);
135
202
  const codeFileNames = new Set([
203
+ // Build/DevOps
136
204
  "Makefile", "Dockerfile", "Jenkinsfile", "Vagrantfile",
137
205
  "Rakefile", "Gemfile", "Podfile", "Fastfile",
206
+ "CMakeLists.txt", "meson.build", "BUILD", "WORKSPACE",
207
+ // Config files
208
+ ".gitignore", ".gitattributes", ".dockerignore",
209
+ ".editorconfig", ".prettierrc", ".eslintrc",
210
+ ".babelrc", ".browserslistrc",
211
+ "tsconfig.json", "jsconfig.json", "package.json",
212
+ "angular.json", "nest-cli.json", "nx.json",
213
+ "webpack.config.js", "vite.config.js", "rollup.config.js",
214
+ // CI/CD
215
+ ".gitlab-ci.yml", ".travis.yml", "azure-pipelines.yml",
216
+ "bitbucket-pipelines.yml", "cloudbuild.yaml",
217
+ // K8s/Helm
218
+ "Chart.yaml", "values.yaml", "kustomization.yaml",
219
+ // Lock files (optional - might want to skip these)
220
+ // "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
138
221
  ]);
139
222
  return codeFileNames.has(basename);
140
223
  }
141
224
  /**
142
- * Recursively scans directory for code files asynchronously
225
+ * Recursively scans directory for code files
143
226
  */
144
- async function scanDirectoryRecursive(dirPath, rootPath, ig, options, results, concurrencyLimit = 20) {
227
+ function scanDirectoryRecursive(dirPath, rootPath, ig, options, results) {
145
228
  let entries;
146
229
  try {
147
- entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
230
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
148
231
  }
149
232
  catch (error) {
150
- logger.warn(`Could not read directory ${dirPath}: ${error}`);
233
+ console.error(`Warning: Could not read directory ${dirPath}: ${error}`);
151
234
  return;
152
235
  }
153
- // Process subdirectories sequentially to avoid recursion explosion
154
- // but process files in current directory in parallel
155
- const filesToProcess = [];
156
236
  for (const entry of entries) {
157
237
  const fullPath = path.join(dirPath, entry.name);
158
238
  const relativePath = path.relative(rootPath, fullPath);
@@ -167,35 +247,23 @@ async function scanDirectoryRecursive(dirPath, rootPath, ig, options, results, c
167
247
  }
168
248
  if (entry.isDirectory()) {
169
249
  if (options.recursive) {
170
- await scanDirectoryRecursive(fullPath, rootPath, ig, options, results, concurrencyLimit);
250
+ scanDirectoryRecursive(fullPath, rootPath, ig, options, results);
171
251
  }
172
252
  }
173
253
  else if (entry.isFile()) {
174
- filesToProcess.push(entry);
175
- }
176
- }
177
- // Process files in batches
178
- for (let i = 0; i < filesToProcess.length; i += concurrencyLimit) {
179
- const batch = filesToProcess.slice(i, i + concurrencyLimit);
180
- await Promise.all(batch.map(async (entry) => {
181
- const fullPath = path.join(dirPath, entry.name);
182
- // Calculate relative path from PROJECT root (not just scan root)
183
- // And strictly normalize to forward slashes for consistency
184
- const relativePathRaw = path.relative(options.projectRoot, fullPath);
185
- const relativePath = relativePathRaw.split(path.sep).join("/");
186
254
  try {
187
255
  // Check if it's a code file
188
256
  if (!isCodeFile(fullPath)) {
189
- return;
257
+ continue;
190
258
  }
191
- const stats = await fs.promises.stat(fullPath);
259
+ const stats = fs.statSync(fullPath);
192
260
  // Check file size limit
193
261
  if (stats.size > options.maxFileSize) {
194
- logger.warn(`Skipping large file (${stats.size} bytes): ${relativePath}`);
195
- return;
262
+ console.error(`Skipping large file (${stats.size} bytes): ${relativePath}`);
263
+ continue;
196
264
  }
197
265
  // Calculate hash and collect metadata
198
- const hash = await calculateFileHash(fullPath);
266
+ const hash = calculateFileHash(fullPath);
199
267
  const language = detectLanguage(fullPath);
200
268
  const extension = path.extname(fullPath);
201
269
  results.push({
@@ -209,18 +277,17 @@ async function scanDirectoryRecursive(dirPath, rootPath, ig, options, results, c
209
277
  });
210
278
  }
211
279
  catch (error) {
212
- logger.warn(`Could not process file ${fullPath}: ${error}`);
280
+ console.error(`Warning: Could not process file ${fullPath}: ${error}`);
213
281
  }
214
- }));
282
+ }
215
283
  }
216
284
  }
217
285
  /**
218
- * Scans workspace for code files asynchronously
286
+ * Scans workspace for code files
219
287
  */
220
- export async function scanFiles(options) {
288
+ export function scanFiles(options) {
221
289
  const fullOptions = {
222
290
  rootPath: options.rootPath,
223
- projectRoot: options.projectRoot || options.rootPath,
224
291
  recursive: options.recursive !== undefined ? options.recursive : true,
225
292
  includeHidden: options.includeHidden !== undefined ? options.includeHidden : false,
226
293
  maxFileSize: options.maxFileSize || 10 * 1024 * 1024, // 10MB default
@@ -229,23 +296,23 @@ export async function scanFiles(options) {
229
296
  if (!fs.existsSync(fullOptions.rootPath)) {
230
297
  throw new Error(`Root path does not exist: ${fullOptions.rootPath}`);
231
298
  }
232
- const stats = await fs.promises.stat(fullOptions.rootPath);
299
+ const stats = fs.statSync(fullOptions.rootPath);
233
300
  if (!stats.isDirectory()) {
234
301
  throw new Error(`Root path is not a directory: ${fullOptions.rootPath}`);
235
302
  }
236
- logger.info(`Scanning files in: ${fullOptions.rootPath}`);
303
+ console.error(`Scanning files in: ${fullOptions.rootPath}`);
237
304
  // Load ignore patterns
238
305
  const ig = loadIgnorePatterns(fullOptions.rootPath);
239
306
  // Scan files
240
307
  const results = [];
241
- await scanDirectoryRecursive(fullOptions.rootPath, fullOptions.rootPath, ig, fullOptions, results);
242
- logger.info(`Found ${results.length} code files to index`);
308
+ scanDirectoryRecursive(fullOptions.rootPath, fullOptions.rootPath, ig, fullOptions, results);
309
+ console.error(`Found ${results.length} code files to index`);
243
310
  return results;
244
311
  }
245
312
  /**
246
313
  * Scans a single file and returns its metadata
247
314
  */
248
- export async function scanSingleFile(filePath, rootPath, projectRoot) {
315
+ export function scanSingleFile(filePath, rootPath) {
249
316
  try {
250
317
  if (!fs.existsSync(filePath)) {
251
318
  throw new Error(`File does not exist: ${filePath}`);
@@ -257,12 +324,10 @@ export async function scanSingleFile(filePath, rootPath, projectRoot) {
257
324
  if (!isCodeFile(filePath)) {
258
325
  return null;
259
326
  }
260
- const hash = await calculateFileHash(filePath);
327
+ const hash = calculateFileHash(filePath);
261
328
  const language = detectLanguage(filePath);
262
329
  const extension = path.extname(filePath);
263
- // Calculate relative path from PROJECT root
264
- const relativePathRaw = path.relative(projectRoot || rootPath, filePath);
265
- const relativePath = relativePathRaw.split(path.sep).join("/");
330
+ const relativePath = path.relative(rootPath, filePath);
266
331
  return {
267
332
  path: relativePath,
268
333
  absolutePath: filePath,
@@ -274,7 +339,7 @@ export async function scanSingleFile(filePath, rootPath, projectRoot) {
274
339
  };
275
340
  }
276
341
  catch (error) {
277
- logger.error(`Error scanning file ${filePath}:`, error);
342
+ console.error(`Error scanning file ${filePath}: ${error}`);
278
343
  return null;
279
344
  }
280
345
  }