magector 2.16.1 → 2.16.3

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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/src/mcp-server.js +96 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magector",
3
- "version": "2.16.1",
3
+ "version": "2.16.3",
4
4
  "description": "Semantic code search for Magento 2 — index, search, MCP server",
5
5
  "type": "module",
6
6
  "main": "src/mcp-server.js",
@@ -33,10 +33,10 @@
33
33
  "ruvector": "^0.1.96"
34
34
  },
35
35
  "optionalDependencies": {
36
- "@magector/cli-darwin-arm64": "2.16.1",
37
- "@magector/cli-linux-x64": "2.16.1",
38
- "@magector/cli-linux-arm64": "2.16.1",
39
- "@magector/cli-win32-x64": "2.16.1"
36
+ "@magector/cli-darwin-arm64": "2.16.3",
37
+ "@magector/cli-linux-x64": "2.16.3",
38
+ "@magector/cli-linux-arm64": "2.16.3",
39
+ "@magector/cli-win32-x64": "2.16.3"
40
40
  },
41
41
  "keywords": [
42
42
  "magento",
package/src/mcp-server.js CHANGED
@@ -423,6 +423,13 @@ let reindexInProgress = false;
423
423
  let reindexProcess = null;
424
424
  let warmupInProgress = true; // true until checkDbFormat + serve process ready
425
425
 
426
+ // Re-index progress tracking (updated from INDEX log lines)
427
+ let reindexStartTime = null;
428
+ let reindexPhase = 0; // 0=init, 1=AST, 2=embeddings, 3=HNSW
429
+ let reindexTotalFiles = 0;
430
+ let reindexItemsToEmbed = 0;
431
+ let reindexPhase2Start = null;
432
+
426
433
  /**
427
434
  * Check if the database file is compatible with the current binary.
428
435
  * Uses a cached result to avoid running stats (30-60s) on every startup.
@@ -557,6 +564,11 @@ function startBackgroundReindex() {
557
564
  }
558
565
 
559
566
  reindexInProgress = true;
567
+ reindexStartTime = Date.now();
568
+ reindexPhase = 0;
569
+ reindexTotalFiles = 0;
570
+ reindexItemsToEmbed = 0;
571
+ reindexPhase2Start = null;
560
572
 
561
573
  const hadExistingDb = existsSync(config.dbPath);
562
574
  logToFile('WARN', `Starting background re-index to temp path. Old DB ${hadExistingDb ? 'preserved for queries' : 'not found'}.`);
@@ -590,18 +602,31 @@ function startBackgroundReindex() {
590
602
  // entries arrive in large chunks instead of in real time.
591
603
  const indexStdout = createInterface({ input: reindexProcess.stdout });
592
604
  const indexStderr = createInterface({ input: reindexProcess.stderr });
605
+ const parseIndexProgress = (text) => {
606
+ const m = text.match(/Found (\d[\d,]+) files to index/);
607
+ if (m) reindexTotalFiles = parseInt(m[1].replace(/,/g, ''), 10);
608
+ if (text.includes('PHASE 1') || text.includes('AST analyzer')) reindexPhase = 1;
609
+ if (text.includes('PHASE 2') || text.includes('semantic embedding') || text.includes('Generating semantic')) {
610
+ if (reindexPhase < 2) { reindexPhase = 2; reindexPhase2Start = Date.now(); }
611
+ }
612
+ if (text.includes('PHASE 3') || text.includes('Building HNSW') || text.includes('HNSW')) reindexPhase = 3;
613
+ const em = text.match(/Items to embed: (\d[\d,]+)/);
614
+ if (em) reindexItemsToEmbed = parseInt(em[1].replace(/,/g, ''), 10);
615
+ };
593
616
  indexStdout.on('line', (line) => {
594
617
  const text = line.replace(/\x1b\[[0-9;]*m/g, '').trim();
595
- if (text) logToFile('INDEX', text);
618
+ if (text) { logToFile('INDEX', text); parseIndexProgress(text); }
596
619
  });
597
620
  indexStderr.on('line', (line) => {
598
621
  const text = line.replace(/\x1b\[[0-9;]*m/g, '').trim();
599
- if (text) logToFile('INDEX', text);
622
+ if (text) { logToFile('INDEX', text); parseIndexProgress(text); }
600
623
  });
601
624
 
602
625
  reindexProcess.on('exit', (code) => {
603
626
  reindexInProgress = false;
604
627
  reindexProcess = null;
628
+ reindexStartTime = null;
629
+ reindexPhase = 0;
605
630
  removeReindexPidFile();
606
631
  if (code === 0) {
607
632
  // Atomic swap: old → .bak, new → current
@@ -792,6 +817,8 @@ function startServeProcess() {
792
817
  // First line is ready signal
793
818
  if (parsed.ready) {
794
819
  serveReady = true;
820
+ // Clear stale cache entries — they may contain [] from when index was unavailable
821
+ searchCache.clear();
795
822
  logToFile('INFO', `Serve process ready (PID ${proc.pid})`);
796
823
  if (serveReadyResolve) { serveReadyResolve(true); serveReadyResolve = null; }
797
824
  // Now that serve is up, persist primary lock state to data.db
@@ -927,6 +954,7 @@ function tryConnectSocket() {
927
954
 
928
955
  isSocketClient = true;
929
956
  serveReady = true;
957
+ searchCache.clear(); // clear stale [] entries from before socket was available
930
958
  logToFile('INFO', 'Connected to existing serve process via socket proxy');
931
959
  resolve(true);
932
960
  });
@@ -4462,7 +4490,62 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
4462
4490
  ]
4463
4491
  }));
4464
4492
 
4465
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
4493
+ /**
4494
+ * Build a reindex warning string for tool responses during background re-index.
4495
+ * Returns null when not re-indexing.
4496
+ */
4497
+ function getReindexWarning() {
4498
+ // Primary instance: use in-memory state (accurate phase + ETA)
4499
+ if (reindexInProgress && reindexStartTime) {
4500
+ const elapsedSec = Math.round((Date.now() - reindexStartTime) / 1000);
4501
+ const elapsedStr = elapsedSec >= 60
4502
+ ? `${Math.floor(elapsedSec / 60)}m ${elapsedSec % 60}s`
4503
+ : `${elapsedSec}s`;
4504
+
4505
+ let phaseStr, etaStr;
4506
+ if (reindexPhase <= 0) {
4507
+ phaseStr = 'initializing';
4508
+ etaStr = 'est. ~40–70 min total';
4509
+ } else if (reindexPhase === 1) {
4510
+ const filesStr = reindexTotalFiles > 0 ? ` (${reindexTotalFiles.toLocaleString('en')} files)` : '';
4511
+ phaseStr = `phase 1/3: AST parsing${filesStr}`;
4512
+ etaStr = 'est. ~1–3 min for this phase, then ~40–60 min for embeddings';
4513
+ } else if (reindexPhase === 2) {
4514
+ const itemsStr = reindexItemsToEmbed > 0 ? ` (${reindexItemsToEmbed.toLocaleString('en')} items)` : '';
4515
+ phaseStr = `phase 2/3: generating embeddings${itemsStr}`;
4516
+ if (reindexPhase2Start && reindexItemsToEmbed > 0) {
4517
+ // Empirical rate: ~87k items ≈ 45 min on 8-core ONNX. Scale linearly.
4518
+ const estimatedTotalSec = Math.round((reindexItemsToEmbed / 87000) * 45 * 60);
4519
+ const phase2Elapsed = (Date.now() - reindexPhase2Start) / 1000;
4520
+ const remainingSec = Math.max(estimatedTotalSec - phase2Elapsed, 0);
4521
+ etaStr = remainingSec > 60
4522
+ ? `est. ~${Math.round(remainingSec / 60)} min remaining`
4523
+ : 'almost done with embeddings';
4524
+ } else {
4525
+ etaStr = 'est. 30–60 min for this phase';
4526
+ }
4527
+ } else {
4528
+ phaseStr = 'phase 3/3: building HNSW vector index';
4529
+ etaStr = 'est. ~5–10 min remaining';
4530
+ }
4531
+
4532
+ return `> ⏳ **Re-indexing in progress** — ${elapsedStr} elapsed, ${phaseStr}. ${etaStr}.\n` +
4533
+ `> Results below use the **previous index** — valid, but may miss recently added files.\n\n`;
4534
+ }
4535
+
4536
+ // Secondary instance: detect via PID file (no phase info available)
4537
+ try {
4538
+ const externalPid = getRunningReindexPid();
4539
+ if (externalPid) {
4540
+ return `> ⏳ **Re-indexing in progress** (PID ${externalPid}) — semantic search may return empty results until complete.\n` +
4541
+ `> Grep and filesystem-based tools work normally. Check \`.magector/magector.log\` for progress.\n\n`;
4542
+ }
4543
+ } catch { /* ignore */ }
4544
+
4545
+ return null;
4546
+ }
4547
+
4548
+ const _callToolHandler = async (request) => {
4466
4549
  const { name, arguments: args } = request.params;
4467
4550
  const reqStart = Date.now();
4468
4551
  logToFile('REQ', `${name}(${JSON.stringify(args || {})})`);
@@ -6928,6 +7011,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6928
7011
  serveQuery('feedback', { signals }).catch((err) => logToFile('WARN', `Feedback signal send failed: ${err.message}`));
6929
7012
  }
6930
7013
  }
7014
+ };
7015
+
7016
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
7017
+ const result = await _callToolHandler(request);
7018
+ // Append re-index warning to non-error responses during background re-index
7019
+ if (reindexInProgress && !result?.isError && result?.content?.[0]?.type === 'text') {
7020
+ const warning = getReindexWarning();
7021
+ if (warning) result.content[0].text = warning + result.content[0].text;
7022
+ }
7023
+ return result;
6931
7024
  });
6932
7025
 
6933
7026
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({