@mrxkun/mcfast-mcp 4.2.0 → 4.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrxkun/mcfast-mcp",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "Ultra-fast code editing with WASM acceleration, fuzzy patching, multi-layer caching, and 8 unified tools. v4.1.12: Implement proper MCP stdio transport lifecycle and cleanup to prevent zombie processes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -607,8 +607,8 @@ export class MemoryEngine {
607
607
  embedding = new Float32Array(cached.embedding.buffer, cached.embedding.byteOffset, cached.dimensions);
608
608
  } else {
609
609
  // Generate embedding
610
- const result = this.embedder.embedCode(chunk.content);
611
- embedding = result.vector;
610
+ const vector = await this.embedder.embedCode(chunk.content);
611
+ embedding = new Float32Array(vector);
612
612
 
613
613
  // Cache it
614
614
  this.memoryDb?.cacheEmbedding?.(
@@ -14,7 +14,7 @@ export class BaseDatabase {
14
14
  this.db = null;
15
15
  this.isInitialized = false;
16
16
  this.options = options;
17
-
17
+
18
18
  // Logger (can be replaced with proper logger)
19
19
  this.logger = options.logger || console;
20
20
  }
@@ -23,13 +23,13 @@ export class BaseDatabase {
23
23
  if (this.isInitialized) return;
24
24
 
25
25
  await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
26
-
26
+
27
27
  this.db = new Database(this.dbPath);
28
28
  this.db.pragma('journal_mode = WAL');
29
-
29
+
30
30
  this.createTables();
31
31
  this.isInitialized = true;
32
-
32
+
33
33
  this._log(`Initialized at: ${this.dbPath}`);
34
34
  }
35
35
 
@@ -79,24 +79,24 @@ export class BaseDatabase {
79
79
  */
80
80
  searchFTS(query, limit = 20) {
81
81
  const startTime = performance.now();
82
-
82
+
83
83
  const stmt = this.db.prepare(`
84
84
  SELECT
85
85
  c.*,
86
86
  rank as bm25_score
87
87
  FROM chunks_fts fts
88
- JOIN chunks c ON fts.rowid = c.id
88
+ JOIN chunks c ON fts.chunk_id = c.id
89
89
  WHERE chunks_fts MATCH ?
90
90
  ORDER BY rank
91
91
  LIMIT ?
92
92
  `);
93
-
93
+
94
94
  const results = stmt.all(query, limit);
95
95
  const duration = performance.now() - startTime;
96
-
96
+
97
97
  // Log search
98
98
  this.logSearch(query, 'fts', results.length, duration);
99
-
99
+
100
100
  return {
101
101
  results: results.map(r => ({
102
102
  ...r,
@@ -19,13 +19,13 @@ export class CodebaseDatabase {
19
19
  if (this.isInitialized) return;
20
20
 
21
21
  await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
22
-
22
+
23
23
  this.db = new Database(this.dbPath);
24
24
  this.db.pragma('journal_mode = WAL');
25
-
25
+
26
26
  this.createTables();
27
27
  this.isInitialized = true;
28
-
28
+
29
29
  console.error(`[CodebaseDatabase] Initialized at: ${this.dbPath}`);
30
30
  }
31
31
 
@@ -88,26 +88,36 @@ export class CodebaseDatabase {
88
88
 
89
89
  // FTS5 for code search
90
90
  this.db.exec(`
91
- CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
91
+ DROP TABLE IF EXISTS chunks_fts;
92
+ CREATE VIRTUAL TABLE chunks_fts USING fts5(
93
+ chunk_id UNINDEXED,
92
94
  content,
93
- content_rowid=id,
94
95
  tokenize='porter'
95
96
  );
96
97
  `);
97
98
 
99
+ // Backfill FTS5 index if chunks exist
100
+ this.db.exec(`
101
+ INSERT INTO chunks_fts(chunk_id, content)
102
+ SELECT id, content FROM chunks;
103
+ `);
104
+
98
105
  // Triggers to sync FTS5
99
106
  this.db.exec(`
100
- CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
101
- INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
107
+ DROP TRIGGER IF EXISTS chunks_ai;
108
+ CREATE TRIGGER chunks_ai AFTER INSERT ON chunks BEGIN
109
+ INSERT INTO chunks_fts(chunk_id, content) VALUES (new.id, new.content);
102
110
  END;
103
111
 
104
- CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
105
- INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES ('delete', old.id, old.content);
112
+ DROP TRIGGER IF EXISTS chunks_ad;
113
+ CREATE TRIGGER chunks_ad AFTER DELETE ON chunks BEGIN
114
+ DELETE FROM chunks_fts WHERE chunk_id = old.id;
106
115
  END;
107
116
 
108
- CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
109
- INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES ('delete', old.id, old.content);
110
- INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
117
+ DROP TRIGGER IF EXISTS chunks_au;
118
+ CREATE TRIGGER chunks_au AFTER UPDATE ON chunks BEGIN
119
+ DELETE FROM chunks_fts WHERE chunk_id = old.id;
120
+ INSERT INTO chunks_fts(chunk_id, content) VALUES (new.id, new.content);
111
121
  END;
112
122
  `);
113
123
 
@@ -202,7 +212,12 @@ export class CodebaseDatabase {
202
212
  (id, file_id, type, name, line_start, line_end, signature, exported, documentation)
203
213
  VALUES ($id, $file_id, $type, $name, $line_start, $line_end, $signature, $exported, $documentation)
204
214
  `);
205
- return stmt.run(fact);
215
+ return stmt.run({
216
+ ...fact,
217
+ exported: fact.exported ? 1 : 0,
218
+ documentation: fact.documentation || null,
219
+ signature: fact.signature || null
220
+ });
206
221
  }
207
222
 
208
223
  deleteFactsByFile(fileId) {
@@ -300,23 +315,23 @@ export class CodebaseDatabase {
300
315
 
301
316
  searchFTS(query, limit = 20) {
302
317
  const startTime = performance.now();
303
-
318
+
304
319
  const stmt = this.db.prepare(`
305
320
  SELECT
306
321
  c.*,
307
322
  f.path as file_path,
308
323
  rank as bm25_score
309
324
  FROM chunks_fts fts
310
- JOIN chunks c ON fts.rowid = c.id
325
+ JOIN chunks c ON fts.chunk_id = c.id
311
326
  JOIN files f ON c.file_id = f.id
312
327
  WHERE chunks_fts MATCH ?
313
328
  ORDER BY rank
314
329
  LIMIT ?
315
330
  `);
316
-
331
+
317
332
  const results = stmt.all(query, limit);
318
333
  const duration = performance.now() - startTime;
319
-
334
+
320
335
  return {
321
336
  results: results.map(r => ({
322
337
  chunk_id: r.id,
@@ -390,7 +405,7 @@ export class CodebaseDatabase {
390
405
  const chunks = this.db.prepare('SELECT COUNT(*) as count FROM chunks').get();
391
406
  const embeddings = this.db.prepare('SELECT COUNT(*) as count FROM embeddings').get();
392
407
  const edits = this.db.prepare('SELECT COUNT(*) as count FROM edit_history').get();
393
-
408
+
394
409
  return {
395
410
  files: files.count,
396
411
  facts: facts.count,
@@ -21,13 +21,13 @@ export class MemoryDatabase {
21
21
 
22
22
  // Create directory if not exists
23
23
  await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
24
-
24
+
25
25
  this.db = new Database(this.dbPath);
26
26
  this.db.pragma('journal_mode = WAL');
27
-
27
+
28
28
  this.createTables();
29
29
  this.isInitialized = true;
30
-
30
+
31
31
  console.error(`[MemoryDatabase] Initialized at: ${this.dbPath}`);
32
32
  }
33
33
 
@@ -53,26 +53,36 @@ export class MemoryDatabase {
53
53
 
54
54
  // FTS5 virtual table for full-text search
55
55
  this.db.exec(`
56
- CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
56
+ DROP TABLE IF EXISTS chunks_fts;
57
+ CREATE VIRTUAL TABLE chunks_fts USING fts5(
58
+ chunk_id UNINDEXED,
57
59
  content,
58
- content_rowid=id,
59
60
  tokenize='porter'
60
61
  );
61
62
  `);
62
63
 
64
+ // Backfill FTS5 index if chunks exist
65
+ this.db.exec(`
66
+ INSERT INTO chunks_fts(chunk_id, content)
67
+ SELECT id, content FROM chunks;
68
+ `);
69
+
63
70
  // Triggers to keep FTS5 in sync
64
71
  this.db.exec(`
65
- CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
66
- INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
72
+ DROP TRIGGER IF EXISTS chunks_ai;
73
+ CREATE TRIGGER chunks_ai AFTER INSERT ON chunks BEGIN
74
+ INSERT INTO chunks_fts(chunk_id, content) VALUES (new.id, new.content);
67
75
  END;
68
76
 
69
- CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
70
- INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES ('delete', old.id, old.content);
77
+ DROP TRIGGER IF EXISTS chunks_ad;
78
+ CREATE TRIGGER chunks_ad AFTER DELETE ON chunks BEGIN
79
+ DELETE FROM chunks_fts WHERE chunk_id = old.id;
71
80
  END;
72
81
 
73
- CREATE TRIGGER IF NOT EXISTS chunks_au AFTER UPDATE ON chunks BEGIN
74
- INSERT INTO chunks_fts(chunks_fts, rowid, content) VALUES ('delete', old.id, old.content);
75
- INSERT INTO chunks_fts(rowid, content) VALUES (new.id, new.content);
82
+ DROP TRIGGER IF EXISTS chunks_au;
83
+ CREATE TRIGGER chunks_au AFTER UPDATE ON chunks BEGIN
84
+ DELETE FROM chunks_fts WHERE chunk_id = old.id;
85
+ INSERT INTO chunks_fts(chunk_id, content) VALUES (new.id, new.content);
76
86
  END;
77
87
  `);
78
88
 
@@ -138,7 +148,7 @@ export class MemoryDatabase {
138
148
  (id, file_path, start_line, end_line, content, content_hash, chunk_type, created_at, updated_at)
139
149
  VALUES ($id, $file_path, $start_line, $end_line, $content, $content_hash, $chunk_type, $created_at, $updated_at)
140
150
  `);
141
-
151
+
142
152
  const now = Date.now();
143
153
  return stmt.run({
144
154
  ...chunk,
@@ -234,25 +244,25 @@ export class MemoryDatabase {
234
244
  */
235
245
  searchFTS(query, limit = 20) {
236
246
  const startTime = performance.now();
237
-
247
+
238
248
  // Use FTS5 rank for scoring (BM25)
239
249
  const stmt = this.db.prepare(`
240
250
  SELECT
241
251
  c.*,
242
252
  rank as bm25_score
243
253
  FROM chunks_fts fts
244
- JOIN chunks c ON fts.rowid = c.id
254
+ JOIN chunks c ON fts.chunk_id = c.id
245
255
  WHERE chunks_fts MATCH ?
246
256
  ORDER BY rank
247
257
  LIMIT ?
248
258
  `);
249
-
259
+
250
260
  const results = stmt.all(query, limit);
251
261
  const duration = performance.now() - startTime;
252
-
262
+
253
263
  // Log search
254
264
  this.logSearch(query, 'fts', results.length, duration);
255
-
265
+
256
266
  return {
257
267
  results: results.map(r => ({
258
268
  ...r,
@@ -275,25 +285,25 @@ export class MemoryDatabase {
275
285
  const startTime = performance.now();
276
286
  const candidateMultiplier = 4;
277
287
  const maxCandidates = limit * candidateMultiplier;
278
-
288
+
279
289
  // Get FTS results
280
290
  const ftsResults = this.searchFTS(query, maxCandidates);
281
-
291
+
282
292
  // Normalize FTS scores to 0-1
283
293
  const ftsMap = new Map();
284
294
  ftsResults.results.forEach(r => {
285
295
  ftsMap.set(r.id, r.score);
286
296
  });
287
-
297
+
288
298
  // Normalize vector scores to 0-1
289
299
  let maxVectorScore = 0;
290
300
  vectorResults.forEach(r => {
291
301
  if (r.similarity > maxVectorScore) maxVectorScore = r.similarity;
292
302
  });
293
-
303
+
294
304
  // Combine results
295
305
  const combined = new Map();
296
-
306
+
297
307
  // Add vector results
298
308
  vectorResults.forEach(r => {
299
309
  const normalizedVectorScore = maxVectorScore > 0 ? r.similarity / maxVectorScore : 0;
@@ -308,7 +318,7 @@ export class MemoryDatabase {
308
318
  sources: ['vector']
309
319
  });
310
320
  });
311
-
321
+
312
322
  // Add FTS results not in vector
313
323
  ftsResults.results.forEach(r => {
314
324
  if (combined.has(r.id)) {
@@ -327,20 +337,20 @@ export class MemoryDatabase {
327
337
  });
328
338
  }
329
339
  });
330
-
340
+
331
341
  // Calculate final scores (0.7 vector + 0.3 text)
332
342
  const results = Array.from(combined.values()).map(r => ({
333
343
  ...r,
334
344
  finalScore: (0.7 * r.vectorScore) + (0.3 * r.textScore)
335
345
  }));
336
-
346
+
337
347
  // Sort by final score and filter
338
348
  results.sort((a, b) => b.finalScore - a.finalScore);
339
349
  const filtered = results.filter(r => r.finalScore >= 0.35).slice(0, limit);
340
-
350
+
341
351
  const duration = performance.now() - startTime;
342
352
  this.logSearch(query, 'hybrid', filtered.length, duration);
343
-
353
+
344
354
  return {
345
355
  results: filtered,
346
356
  metadata: {
@@ -363,9 +373,9 @@ export class MemoryDatabase {
363
373
  INSERT INTO search_history (query, query_hash, method, results_count, duration_ms, timestamp)
364
374
  VALUES (?, ?, ?, ?, ?, ?)
365
375
  `);
366
-
376
+
367
377
  const queryHash = crypto.createHash('md5').update(query).digest('hex');
368
-
378
+
369
379
  stmt.run(query, queryHash, method, resultsCount, Math.round(durationMs), Date.now());
370
380
  } catch (error) {
371
381
  // Silent fail - don't break search for logging
@@ -393,7 +403,7 @@ export class MemoryDatabase {
393
403
  const chunks = this.db.prepare('SELECT COUNT(*) as count FROM chunks').get();
394
404
  const embeddings = this.db.prepare('SELECT COUNT(*) as count FROM embeddings').get();
395
405
  const cache = this.db.prepare('SELECT COUNT(*) as count FROM embedding_cache').get();
396
-
406
+
397
407
  return {
398
408
  files: files.count,
399
409
  chunks: chunks.count,
@@ -27,7 +27,7 @@ export class DashboardClient {
27
27
  */
28
28
  async fetch(path, options = {}) {
29
29
  const url = `${this.baseUrl}/api/v1${path}`;
30
-
30
+
31
31
  try {
32
32
  const response = await fetch(url, {
33
33
  ...options,
@@ -36,12 +36,12 @@ export class DashboardClient {
36
36
  ...options.headers
37
37
  }
38
38
  });
39
-
39
+
40
40
  if (!response.ok) {
41
41
  const error = await response.json().catch(() => ({ error: 'Unknown error' }));
42
42
  throw new Error(error.error || `HTTP ${response.status}`);
43
43
  }
44
-
44
+
45
45
  return response.json();
46
46
  } catch (error) {
47
47
  console.error(`[DashboardClient] API error (${path}):`, error.message);
@@ -55,11 +55,11 @@ export class DashboardClient {
55
55
  async getSearchConfig() {
56
56
  const cacheKey = 'searchConfig';
57
57
  const cached = this.cache.get(cacheKey);
58
-
58
+
59
59
  if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
60
60
  return cached.data;
61
61
  }
62
-
62
+
63
63
  try {
64
64
  const config = await this.fetch('/settings');
65
65
  this.cache.set(cacheKey, { data: config, timestamp: Date.now() });
@@ -70,6 +70,7 @@ export class DashboardClient {
70
70
  return {
71
71
  enable_mercury_rerank: false,
72
72
  smart_routing_mode: 'auto',
73
+ bootstrap_mode: 'hybrid',
73
74
  rerank_threshold: 0.7,
74
75
  monthly_budget: 20,
75
76
  usage_this_month: 0
@@ -93,7 +94,7 @@ export class DashboardClient {
93
94
  }
94
95
  })
95
96
  });
96
-
97
+
97
98
  return result;
98
99
  } catch (error) {
99
100
  console.error('[DashboardClient] Mercury rerank failed:', error.message);
@@ -46,8 +46,8 @@ export const QUERIES = {
46
46
  definitions: `
47
47
  (function_declaration name: (identifier) @name) @function
48
48
  (class_declaration name: (identifier) @name) @class
49
- (method_definition key: (property_identifier) @name) @method
50
- (variable_declarator name: (identifier) @name init: (arrow_function)) @arrow_function
49
+ (method_definition name: (property_identifier) @name) @method
50
+ (variable_declarator name: (identifier) @name value: (arrow_function)) @arrow_function
51
51
  (export_statement
52
52
  (function_declaration name: (identifier) @name)) @export_function
53
53
  (export_statement