@treedy/lsp-mcp 0.2.7 → 0.2.8

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/README.md CHANGED
@@ -137,6 +137,7 @@ Strict semantic error shape (example):
137
137
  - `doctor` includes `workspaceDependencyChecks.python_bundled_runtime` in bundled mode, with optional executable probe results when `probe_backends=true`.
138
138
  - `doctor` includes `benchmarkInsights` from the latest benchmark report (`.tmp/benchmark-latest.json` by default) for runtime budget recommendations.
139
139
  - If baseline exists (`.tmp/benchmark-baseline.json` by default), `benchmarkInsights.trend` includes regression/improvement deltas.
140
+ - If benchmark cases include `*_cold` and `*_warm` pairs, `benchmarkInsights.warmup` summarizes warm-start deltas.
140
141
  - `doctor` includes `llmSemanticDefaults` with ready-to-use parameter presets for:
141
142
  - `semantic_navigate` (`mode`, `strategy`, `page_size`, `reference_preview`, `hint_max_lines`)
142
143
  - `diagnostics_delta` (`page_size`, `preview_limit`, `hotspot_limit`)
package/dist/index.js CHANGED
@@ -22465,16 +22465,41 @@ function normalizeRecoveryPlan(value, installCommands, nextStep) {
22465
22465
  return buildRecoveryPlan(installCommands, nextStep);
22466
22466
  }
22467
22467
  function inferResponseResultSize(payload) {
22468
+ if (typeof payload.result_size === "number" && Number.isFinite(payload.result_size))
22469
+ return payload.result_size;
22468
22470
  if (typeof payload.count === "number" && Number.isFinite(payload.count))
22469
22471
  return payload.count;
22470
- const arrayKeys = ["matches", "references", "diagnostics", "lines", "symbols", "hints"];
22472
+ const arrayKeys = [
22473
+ "matches",
22474
+ "references",
22475
+ "diagnostics",
22476
+ "lines",
22477
+ "symbols",
22478
+ "hints",
22479
+ "ranges",
22480
+ "tokens",
22481
+ "links",
22482
+ "actions",
22483
+ "items",
22484
+ "changes",
22485
+ "results",
22486
+ "completions"
22487
+ ];
22471
22488
  for (const key of arrayKeys) {
22472
22489
  if (Array.isArray(payload[key]))
22473
22490
  return payload[key].length;
22474
22491
  }
22475
- if (typeof payload.result === "string")
22476
- return payload.result.length;
22477
- return 0;
22492
+ const stringKeys = ["result", "contents", "message", "identifier", "label"];
22493
+ for (const key of stringKeys) {
22494
+ const value = payload[key];
22495
+ if (typeof value === "string")
22496
+ return value.length;
22497
+ }
22498
+ try {
22499
+ return Buffer.byteLength(JSON.stringify(payload), "utf8");
22500
+ } catch {
22501
+ return 0;
22502
+ }
22478
22503
  }
22479
22504
  function withStandardCostFields(payload) {
22480
22505
  const page = payload.page && typeof payload.page === "object" ? payload.page : null;
@@ -23458,7 +23483,6 @@ server.registerTool("semantic_navigate", {
23458
23483
  next_step: nextStep,
23459
23484
  recovery_plan: recoveryPlan,
23460
23485
  latency_ms: Date.now() - startedAt,
23461
- result_size: referenceCount,
23462
23486
  cursor_available: !!referencesPayload?.next?.arguments?.cursor,
23463
23487
  truncated: !!referencesPayload?.page?.has_more
23464
23488
  }));
@@ -23738,7 +23762,6 @@ server.registerTool("diagnostics_delta", {
23738
23762
  fallback_used: false,
23739
23763
  approximate: false,
23740
23764
  latency_ms: Date.now() - startedAt,
23741
- result_size: diagnostics.length,
23742
23765
  cursor_available: !!deltaCursor,
23743
23766
  truncated: deltaChanges.length > limit,
23744
23767
  next: deltaCursor ? { tool: "diagnostics_delta", arguments: { cursor: deltaCursor, page_size: pageSize } } : null
@@ -23855,6 +23878,46 @@ server.registerTool("doctor", {
23855
23878
  const regressions = trendPairs.filter((p) => p.delta_pct > 20 && p.delta_ms > 50).sort((a, b) => b.delta_pct - a.delta_pct);
23856
23879
  const improvements = trendPairs.filter((p) => p.delta_pct < -20 && p.delta_ms < -50).sort((a, b) => a.delta_pct - b.delta_pct);
23857
23880
  const slowCases = [...cases].filter((c) => typeof c.latency_ms === "number" && c.latency_ms >= 1200).sort((a, b) => b.latency_ms - a.latency_ms).slice(0, 5);
23881
+ const warmupPairs = (() => {
23882
+ const byBase = new Map;
23883
+ for (const c of cases) {
23884
+ const isCold = c.id.endsWith("_cold") || c.phase === "cold";
23885
+ const isWarm = c.id.endsWith("_warm") || c.phase === "warm";
23886
+ if (!isCold && !isWarm)
23887
+ continue;
23888
+ const baseId = c.id.replace(/_(cold|warm)$/, "");
23889
+ const row = byBase.get(baseId) || { tool: c.tool };
23890
+ if (isCold)
23891
+ row.cold = c;
23892
+ if (isWarm)
23893
+ row.warm = c;
23894
+ byBase.set(baseId, row);
23895
+ }
23896
+ const pairs = Array.from(byBase.entries()).map(([id, row]) => ({ id, tool: row.tool, cold: row.cold, warm: row.warm })).filter((row) => !!row.cold && !!row.warm).filter((row) => row.cold.ok && row.warm.ok && row.cold.latency_ms > 0 && row.warm.latency_ms > 0);
23897
+ const deltas = pairs.map((row) => {
23898
+ const deltaMs = row.cold.latency_ms - row.warm.latency_ms;
23899
+ const deltaPct = deltaMs / row.cold.latency_ms * 100;
23900
+ return {
23901
+ id: row.id,
23902
+ tool: row.tool,
23903
+ cold_latency_ms: row.cold.latency_ms,
23904
+ warm_latency_ms: row.warm.latency_ms,
23905
+ warmup_delta_ms: Math.round(deltaMs),
23906
+ warmup_delta_pct: Math.round(deltaPct * 10) / 10
23907
+ };
23908
+ });
23909
+ const improved = deltas.filter((d) => d.warmup_delta_ms > 0);
23910
+ const regressed = deltas.filter((d) => d.warmup_delta_ms < 0);
23911
+ const avgDelta = deltas.length > 0 ? deltas.reduce((sum, d) => sum + d.warmup_delta_ms, 0) / deltas.length : 0;
23912
+ return {
23913
+ paired_cases: deltas.length,
23914
+ improved_count: improved.length,
23915
+ regressed_count: regressed.length,
23916
+ average_warmup_delta_ms: Math.round(avgDelta * 10) / 10,
23917
+ top_improvements: [...improved].sort((a, b) => b.warmup_delta_ms - a.warmup_delta_ms).slice(0, 5),
23918
+ top_regressions: [...regressed].sort((a, b) => a.warmup_delta_ms - b.warmup_delta_ms).slice(0, 5)
23919
+ };
23920
+ })();
23858
23921
  const errorCases = cases.filter((c) => !c.ok);
23859
23922
  const tokenHeavyCases = cases.filter((c) => c.truncated || c.cursor_available || c.result_size > 400);
23860
23923
  const totalLatency = report.summary?.total_latency_ms ?? cases.reduce((sum, c) => sum + (Number(c.latency_ms) || 0), 0);
@@ -23878,6 +23941,7 @@ server.registerTool("doctor", {
23878
23941
  improvements: improvements.slice(0, 5)
23879
23942
  },
23880
23943
  slow_cases: slowCases,
23944
+ warmup: warmupPairs,
23881
23945
  token_heavy_cases: tokenHeavyCases.slice(0, 5),
23882
23946
  recommended_mode: totalLatency > 6000 ? "semantic_navigate(mode='fast')" : "semantic_navigate(mode='deep')",
23883
23947
  next_step: regressions.length > 0 ? "Benchmark regressed vs baseline; review regressions before changing LLM defaults." : errorCases.length > 0 ? "Investigate failed benchmark cases before trusting semantic automation." : "Use slow_cases and token_heavy_cases to set default mode/strategy for LLM workflows."
@@ -24402,6 +24466,9 @@ server.registerTool("doctor", {
24402
24466
  } else if (benchmarkInsights.budget_status === "high_latency") {
24403
24467
  recommendations.push("Latest benchmark latency is high; prefer semantic_navigate(mode='fast') and narrower page_size defaults.");
24404
24468
  }
24469
+ if ((benchmarkInsights.warmup?.regressed_count || 0) > 0) {
24470
+ recommendations.push("Benchmark warmup regression detected in cold/warm pairs. Check benchmarkInsights.warmup.top_regressions.");
24471
+ }
24405
24472
  }
24406
24473
  if (config2.languages.vue?.enabled && activeWorkspacePath) {
24407
24474
  const missing = (vueChecks?.projects || []).filter((p) => !p.ok);
@@ -24758,6 +24825,18 @@ function extractSearchLikeItems(parsed) {
24758
24825
  function extractSearchLikeCount(parsed, items) {
24759
24826
  return typeof parsed?.count === "number" ? parsed.count : items.length;
24760
24827
  }
24828
+ function buildSearchLikeDedupeKey(item, toolName) {
24829
+ const file = typeof item?.file === "string" ? item.file : "";
24830
+ const line = Number(item?.line ?? item?.range?.start?.line ?? -1);
24831
+ const column = Number(item?.column ?? item?.range?.start?.character ?? -1);
24832
+ const text = typeof item?.text === "string" ? item.text : "";
24833
+ if (toolName === "search") {
24834
+ return `${file}:${line}:${column}:${text}`;
24835
+ }
24836
+ const name = typeof item?.name === "string" ? item.name : "";
24837
+ const kind = typeof item?.kind === "string" ? item.kind : "";
24838
+ return `${file}:${line}:${column}:${text}:${name}:${kind}`;
24839
+ }
24761
24840
  function extractReferencesItems(parsed) {
24762
24841
  if (Array.isArray(parsed))
24763
24842
  return parsed;
@@ -26342,13 +26421,23 @@ ${results.join(`
26342
26421
  const preferred = requestedWorkspacePath ? inferLanguageFromPath(requestedWorkspacePath, config2) : null;
26343
26422
  const fallback = enabledLanguages.length === 1 ? enabledLanguages[0] : null;
26344
26423
  const candidate = preferred || fallback;
26345
- if (candidate) {
26424
+ const requestTargetsDirectory = typeof requestedWorkspacePath === "string" && fs3.existsSync(requestedWorkspacePath) && fs3.statSync(requestedWorkspacePath).isDirectory();
26425
+ const shouldStartAllSearchBackends = tool.name === "search" && enabledLanguages.length > 1 && (!candidate || requestTargetsDirectory);
26426
+ if (shouldStartAllSearchBackends) {
26427
+ for (const lang of enabledLanguages) {
26428
+ try {
26429
+ await startAndSyncBackend(lang);
26430
+ } catch {}
26431
+ }
26432
+ }
26433
+ if (candidate && !shouldStartAllSearchBackends) {
26346
26434
  try {
26347
26435
  await startAndSyncBackend(candidate);
26348
26436
  } catch (e) {}
26349
26437
  }
26350
26438
  }
26351
26439
  const results = [];
26440
+ const seenSearchLikeKeys = new Set;
26352
26441
  let totalCount = 0;
26353
26442
  for (const lang of enabledLanguages) {
26354
26443
  const lock = await getSingletonLock(lang);
@@ -26381,9 +26470,15 @@ ${results.join(`
26381
26470
  const parsedCount = extractSearchLikeCount(parsed, items);
26382
26471
  totalCount += parsedCount;
26383
26472
  if (items.length > 0) {
26384
- const remaining = Math.max(pageSize - results.length, 0);
26385
- if (remaining > 0) {
26386
- results.push(...items.slice(0, remaining).map((i) => ({ ...i, language: lang })));
26473
+ for (const item of items) {
26474
+ const inferredLanguage = inferLanguageFromPath(typeof item?.file === "string" ? item.file : "", config2);
26475
+ const itemLanguage = typeof item?.language === "string" && item.language.trim().length > 0 ? item.language : inferredLanguage || lang;
26476
+ const normalizedItem = { ...item, language: itemLanguage };
26477
+ const dedupeKey = buildSearchLikeDedupeKey(normalizedItem, tool.name);
26478
+ if (seenSearchLikeKeys.has(dedupeKey))
26479
+ continue;
26480
+ seenSearchLikeKeys.add(dedupeKey);
26481
+ results.push(normalizedItem);
26387
26482
  }
26388
26483
  }
26389
26484
  } catch (e) {}
@@ -27370,4 +27465,4 @@ main().catch((error2) => {
27370
27465
  process.exit(1);
27371
27466
  });
27372
27467
 
27373
- //# debugId=15CB193D47835A7764756E2164756E21
27468
+ //# debugId=2EDAD27987CF16C564756E2164756E21