@vibgrate/cli 2026.704.1 → 2026.704.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.
@@ -0,0 +1,6 @@
1
+ export { baselineCommand, runBaseline } from './chunk-WHQRV66O.js';
2
+ import './chunk-YN5OGRWU.js';
3
+ import './chunk-RXP66R2E.js';
4
+ import './chunk-GI6W53LM.js';
5
+ //# sourceMappingURL=baseline-64EUPWUH.js.map
6
+ //# sourceMappingURL=baseline-64EUPWUH.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"baseline-FESLZK5O.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"baseline-64EUPWUH.js"}
@@ -1,5 +1,5 @@
1
- import { langById, shortId, canonicalize, grammarSetVersion, hashString, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
2
- import { writeTextFile, pathExists, readJsonFile, VERSION, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem } from './chunk-RJHYTD62.js';
1
+ import { hashString, langById, shortId, canonicalize, grammarSetVersion, hashBytes, langForExtension, setGrammarsOverride, parseSource } from './chunk-X5YT263H.js';
2
+ import { writeTextFile, pathExists, readJsonFile, computeUpgradeImpact, getChangelogSignals, gitHistoryAvailable, buildVersionTimelines, findPackageTimeline, severityRank, projectTypeToVulnEcosystem, VERSION } from './chunk-YN5OGRWU.js';
3
3
  import * as fs18 from 'fs';
4
4
  import { execFileSync } from 'child_process';
5
5
  import * as path18 from 'path';
@@ -5409,33 +5409,144 @@ function boundList(items, max) {
5409
5409
  return { items: items.slice(0, max), total: items.length };
5410
5410
  }
5411
5411
  var NODE_EDGE_CAP = 50;
5412
+ var IGNORE_DIRS = /* @__PURE__ */ new Set([".git", ".vibgrate", "node_modules", "dist", "build", "out", "target", "vendor", "__pycache__"]);
5413
+ var MAX_FILE_BYTES = 1e6;
5414
+ var MAX_FILES_SCANNED = 2e4;
5415
+ var PREVIEW_CHARS = 120;
5416
+ function searchSymbols(graph, root, query, limit) {
5417
+ const q2 = query.trim();
5418
+ if (!q2) return { matches: [], moreAvailable: false, hint: "query is required" };
5419
+ const nodes = findNodes(graph, q2).filter((n) => n.kind !== "file");
5420
+ const symbolHits = nodes.slice(0, limit).map((n, i) => ({
5421
+ kind: n.kind,
5422
+ name: n.qualifiedName,
5423
+ file: n.file,
5424
+ line: n.span.start,
5425
+ score: Math.round((1 - i / Math.max(nodes.length, 1)) * 100) / 100
5426
+ }));
5427
+ const spare = limit - symbolHits.length;
5428
+ const textHits = [];
5429
+ let truncatedScan = false;
5430
+ if (spare > 0) {
5431
+ const seen = new Set(symbolHits.map((h) => `${h.file}:${h.line}`));
5432
+ truncatedScan = scanFiles(root, q2, spare, seen, textHits);
5433
+ }
5434
+ const matches = [...symbolHits, ...textHits];
5435
+ if (matches.length === 0) {
5436
+ return {
5437
+ matches,
5438
+ moreAvailable: false,
5439
+ hint: "no symbol or text match \u2014 for meaning-level questions (symptoms, relationships, what-breaks-if) use query_graph"
5440
+ };
5441
+ }
5442
+ return { matches, moreAvailable: nodes.length > symbolHits.length || truncatedScan };
5443
+ }
5444
+ function scanFiles(root, needle, budget, seen, out) {
5445
+ const lower = needle.toLowerCase();
5446
+ let scanned = 0;
5447
+ const stack = [root];
5448
+ while (stack.length) {
5449
+ const dir = stack.pop();
5450
+ let entries;
5451
+ try {
5452
+ entries = fs18.readdirSync(dir, { withFileTypes: true });
5453
+ } catch {
5454
+ continue;
5455
+ }
5456
+ entries.sort((a, b) => a.name.localeCompare(b.name));
5457
+ for (const e of entries) {
5458
+ if (e.name.startsWith(".") || IGNORE_DIRS.has(e.name)) continue;
5459
+ const abs = path18.join(dir, e.name);
5460
+ if (e.isDirectory()) {
5461
+ stack.push(abs);
5462
+ continue;
5463
+ }
5464
+ if (++scanned > MAX_FILES_SCANNED) return true;
5465
+ let text;
5466
+ try {
5467
+ if (fs18.statSync(abs).size > MAX_FILE_BYTES) continue;
5468
+ text = fs18.readFileSync(abs, "utf8");
5469
+ } catch {
5470
+ continue;
5471
+ }
5472
+ if (!text.toLowerCase().includes(lower)) continue;
5473
+ const rel2 = path18.relative(root, abs);
5474
+ const lines = text.split("\n");
5475
+ for (let i = 0; i < lines.length; i++) {
5476
+ if (!lines[i].toLowerCase().includes(lower)) continue;
5477
+ const key = `${rel2}:${i + 1}`;
5478
+ if (seen.has(key)) continue;
5479
+ seen.add(key);
5480
+ out.push({ kind: "text", file: rel2, line: i + 1, preview: lines[i].trim().slice(0, PREVIEW_CHARS) });
5481
+ if (out.length >= budget) return true;
5482
+ }
5483
+ }
5484
+ }
5485
+ return false;
5486
+ }
5412
5487
 
5413
5488
  // src/mcp/tools.ts
5414
- var embedderPromise;
5415
- function sharedEmbedder(local) {
5416
- if (!embedderPromise) embedderPromise = loadEmbedder({ local });
5417
- return embedderPromise;
5489
+ var warmEmbedder = null;
5490
+ var bgWarmStarted = false;
5491
+ function warmEmbedderInBackground(local) {
5492
+ if (local || bgWarmStarted || isModelReady()) return;
5493
+ bgWarmStarted = true;
5494
+ loadEmbedder({}).then((e) => {
5495
+ if (e) warmEmbedder = Promise.resolve(e);
5496
+ }).catch(() => {
5497
+ bgWarmStarted = false;
5498
+ });
5499
+ }
5500
+ function readyEmbedder(local) {
5501
+ if (warmEmbedder) return warmEmbedder;
5502
+ if (local) return warmEmbedder = loadEmbedder({ local, noDownload: true });
5503
+ if (isModelReady()) return warmEmbedder = loadEmbedder({ noDownload: true });
5504
+ warmEmbedderInBackground(local);
5505
+ return Promise.resolve(null);
5418
5506
  }
5507
+ async function retrieve(graph, question, budget, ctx) {
5508
+ let mode = "lexical";
5509
+ try {
5510
+ const q2 = await withTimeout(
5511
+ (async () => {
5512
+ const embedder = await readyEmbedder(ctx.local);
5513
+ if (!embedder) return null;
5514
+ const nodeVectors = await getNodeEmbeddings(graph, embedder, ctx.root);
5515
+ const r = await queryGraphSemantic(graph, question, { budget, embedder, nodeVectors });
5516
+ mode = `semantic (${embedder.id})`;
5517
+ return r;
5518
+ })(),
5519
+ SEMANTIC_BUDGET_MS,
5520
+ "semantic path over budget"
5521
+ );
5522
+ if (q2) return { q: q2, mode };
5523
+ } catch {
5524
+ }
5525
+ return { q: queryGraph(graph, question, { budget }), mode: "lexical" };
5526
+ }
5527
+ var SEMANTIC_BUDGET_MS = (() => {
5528
+ const n = Number(process.env.VG_SEMANTIC_BUDGET_MS);
5529
+ return Number.isFinite(n) && n > 0 ? n : 4e3;
5530
+ })();
5419
5531
  var obj = (properties, required = []) => ({
5420
5532
  type: "object",
5421
5533
  properties,
5422
- required,
5534
+ ...required.length ? { required } : {},
5423
5535
  additionalProperties: false
5424
5536
  });
5537
+ var RESPONSE_FORMAT = { type: "string", enum: ["concise", "detailed"] };
5538
+ var isDetailed = (args) => args.response_format === "detailed";
5539
+ var CONCISE_MATCHES = 5;
5425
5540
  var TOOLS = [
5426
- {
5427
- name: "get_graph_summary",
5428
- description: "High-level summary of the code map: counts, languages, clustering, top areas and hubs.",
5429
- inputSchema: obj({}),
5430
- handler: (graph) => summarize(graph)
5431
- },
5432
5541
  {
5433
5542
  name: "orient",
5434
- description: "ONE-SHOT orientation before changing code: the map summary + the most relevant nodes for your question + the blast radius of the top hit, in a single call. Prefer this over separate get_graph_summary + query_graph + impact_of \u2014 same answer, far fewer round-trips (each round-trip re-bills the whole conversation).",
5543
+ description: "Start here: one call returns the map overview, ranked matches for your question, and the top hit\u2019s blast radius \u2014 replaces summary+query+impact round-trips.",
5435
5544
  inputSchema: obj(
5436
5545
  {
5437
- question: { type: "string", description: "what you are about to do or look for" },
5438
- budget: { type: "number", description: "approx token budget for the context block (default 1500)" }
5546
+ question: { type: "string", maxLength: 300 },
5547
+ scope: { type: "string", description: "path prefix to orient within" },
5548
+ budget: { type: "number", description: "token budget in detailed mode (default 1500)" },
5549
+ response_format: RESPONSE_FORMAT
5439
5550
  },
5440
5551
  ["question"]
5441
5552
  ),
@@ -5443,67 +5554,93 @@ var TOOLS = [
5443
5554
  const question = String(args.question ?? "");
5444
5555
  if (!question) return { error: "bad_request", message: "question is required" };
5445
5556
  const budget = numOr(args.budget, 1500);
5446
- const embedder = await sharedEmbedder(ctx.local);
5447
- let q2;
5448
- let mode = "lexical";
5449
- if (embedder) {
5450
- const nodeVectors = await getNodeEmbeddings(graph, embedder, ctx.root);
5451
- q2 = await queryGraphSemantic(graph, question, { budget, embedder, nodeVectors });
5452
- mode = `semantic (${embedder.id})`;
5453
- } else {
5454
- q2 = queryGraph(graph, question, { budget });
5455
- }
5557
+ const { q: q2, mode } = await retrieve(graph, question, budget, ctx);
5558
+ const rawScope = String(args.scope ?? "").trim();
5559
+ const scope = rawScope === "." || rawScope === "./" ? "" : rawScope.replace(/^\.\//, "");
5560
+ const scoped = scope ? q2.matches.filter((m) => m.node.file.startsWith(scope)) : q2.matches;
5561
+ const detailed = isDetailed(args);
5456
5562
  let topImpact = null;
5457
- const top = q2.matches[0]?.node;
5458
- if (top) {
5563
+ const top = scoped[0]?.node;
5564
+ if (detailed && top) {
5459
5565
  const r = impactOf(graph, top.id, { depth: 3 });
5460
5566
  topImpact = { node: top.qualifiedName, direct: r.direct, transitive: r.transitive, affected: r.affected.slice(0, 10).map(stripId) };
5461
5567
  }
5568
+ const shown = detailed ? scoped : scoped.slice(0, CONCISE_MATCHES);
5462
5569
  return {
5463
- summary: summarize(graph),
5570
+ summary: summarize(graph, detailed ? 10 : CONCISE_MATCHES),
5464
5571
  mode,
5465
- context: q2.context,
5466
- matches: q2.matches.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score })),
5467
- topImpact
5572
+ // The fact-annotated context block is detail: concise callers navigate
5573
+ // by the ranked matches and fetch nodes on demand (plan P2).
5574
+ ...detailed ? { context: q2.context } : {},
5575
+ matches: shown.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score })),
5576
+ // A neutral availability flag, not a directive to keep querying: a
5577
+ // retrieval tool that coaches "narrow the question / see more" nudges the
5578
+ // over-navigation that re-bills the whole context on every extra step.
5579
+ ...scoped.length > shown.length ? { moreAvailable: true } : {},
5580
+ ...detailed ? { topImpact } : {}
5468
5581
  };
5469
5582
  }
5470
5583
  },
5584
+ {
5585
+ name: "search_symbols",
5586
+ description: "Find a known name or string fast: ranked symbol lookup with literal file-search fallthrough. Use first for most discovery; use query_graph for meaning.",
5587
+ inputSchema: obj(
5588
+ {
5589
+ query: { type: "string", maxLength: 120 },
5590
+ limit: { type: "number", description: "default 8, max 20" }
5591
+ },
5592
+ ["query"]
5593
+ ),
5594
+ handler: (graph, args, ctx) => {
5595
+ const limit = Math.min(20, numOr(args.limit, 8));
5596
+ return searchSymbols(graph, ctx.root, String(args.query ?? ""), limit);
5597
+ }
5598
+ },
5471
5599
  {
5472
5600
  name: "query_graph",
5473
- description: "Ask the map a natural-language question; returns a budget-bounded, fact-annotated context block plus ranked matches.",
5601
+ description: "Find code by meaning when you don\u2019t know the name: symptoms, relationships, what-breaks-if. For a known name or literal string use search_symbols.",
5474
5602
  inputSchema: obj(
5475
5603
  {
5476
- question: { type: "string", description: "the question" },
5477
- budget: { type: "number", description: "approx token budget (default 2000)" }
5604
+ question: { type: "string", maxLength: 300 },
5605
+ limit: { type: "number", description: "ranked matches to return (default 5)" },
5606
+ offset: { type: "number" },
5607
+ budget: { type: "number", description: "context-block token budget in detailed mode (default 2000)" },
5608
+ response_format: RESPONSE_FORMAT
5478
5609
  },
5479
5610
  ["question"]
5480
5611
  ),
5481
5612
  handler: async (graph, args, ctx) => {
5482
5613
  const question = String(args.question ?? "");
5483
5614
  const budget = numOr(args.budget, 2e3);
5484
- let r;
5485
- let mode = "lexical";
5486
- const embedder = await sharedEmbedder(ctx.local);
5487
- if (embedder) {
5488
- const nodeVectors = await getNodeEmbeddings(graph, embedder, ctx.root);
5489
- r = await queryGraphSemantic(graph, question, { budget, embedder, nodeVectors });
5490
- mode = `semantic (${embedder.id})`;
5491
- } else {
5492
- r = queryGraph(graph, question, { budget });
5615
+ const { q: r, mode } = await retrieve(graph, question, budget, ctx);
5616
+ if (r.matches.length === 0) {
5617
+ return { mode, matches: [], hint: "no semantic match \u2014 for a known name or literal string use search_symbols; otherwise rephrase around the behaviour you observe" };
5493
5618
  }
5619
+ const limit = numOr(args.limit, CONCISE_MATCHES);
5620
+ const offset = Math.max(0, numOrU(args.offset) ?? 0);
5621
+ const page = r.matches.slice(offset, offset + limit);
5494
5622
  return {
5495
5623
  mode,
5496
- context: r.context,
5497
- tokensEstimate: r.tokensEstimate,
5498
- matches: r.matches.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score }))
5624
+ summary: `${r.matches.length} match${r.matches.length === 1 ? "" : "es"}; top: ${r.matches[0].node.qualifiedName}`,
5625
+ // The fact-annotated context block is detail (plan P2): ranked matches
5626
+ // first, fetch nodes on demand.
5627
+ ...isDetailed(args) ? { context: r.context, tokensEstimate: r.tokensEstimate } : {},
5628
+ matches: page.map((m) => ({ name: m.node.qualifiedName, kind: m.node.kind, file: m.node.file, line: m.node.span.start, score: m.score })),
5629
+ // Neutral pagination fact (nextOffset if the model genuinely needs more),
5630
+ // not a "narrow the question" nudge that invites another discovery round.
5631
+ ...offset + page.length < r.matches.length ? { moreAvailable: true, nextOffset: offset + page.length } : {}
5499
5632
  };
5500
5633
  }
5501
5634
  },
5502
5635
  {
5503
5636
  name: "get_node",
5504
- description: "Explain a node: kind, signature, callers, callees, area, importance. Resolves by qualified/short name, file:line, glob, or id.",
5637
+ description: "Inspect one symbol: signature, callers, callees, area. Accepts name, file:line, glob or id.",
5505
5638
  inputSchema: obj(
5506
- { name: { type: "string" }, pick: { type: "number", description: "choose nth candidate when ambiguous" } },
5639
+ {
5640
+ name: { type: "string" },
5641
+ pick: { type: "number", description: "nth candidate if ambiguous" },
5642
+ response_format: RESPONSE_FORMAT
5643
+ },
5507
5644
  ["name"]
5508
5645
  ),
5509
5646
  handler: (graph, args, ctx) => {
@@ -5523,8 +5660,9 @@ var TOOLS = [
5523
5660
  if (ctx?.dedup && ctx.seen?.has(node.id)) {
5524
5661
  return { ...base, repeat: true, callsTotal: uniqueNames(index.callees(node.id).map((x) => x.node.qualifiedName)).length, calledByTotal: uniqueNames(index.callers(node.id).map((x) => x.node.qualifiedName)).length };
5525
5662
  }
5526
- const calls = boundList(uniqueNames(index.callees(node.id).map((x) => x.node.qualifiedName)), NODE_EDGE_CAP);
5527
- const calledBy = boundList(uniqueNames(index.callers(node.id).map((x) => x.node.qualifiedName)), NODE_EDGE_CAP);
5663
+ const edgeCap = isDetailed(args) ? NODE_EDGE_CAP : CONCISE_MATCHES;
5664
+ const calls = boundList(uniqueNames(index.callees(node.id).map((x) => x.node.qualifiedName)), edgeCap);
5665
+ const calledBy = boundList(uniqueNames(index.callers(node.id).map((x) => x.node.qualifiedName)), edgeCap);
5528
5666
  ctx?.seen?.add(node.id);
5529
5667
  return {
5530
5668
  ...base,
@@ -5537,13 +5675,13 @@ var TOOLS = [
5537
5675
  },
5538
5676
  {
5539
5677
  name: "find_path",
5540
- description: "Shortest connection between two nodes (how A reaches B).",
5678
+ description: "Shortest connection from a to b.",
5541
5679
  inputSchema: obj(
5542
5680
  {
5543
5681
  a: { type: "string" },
5544
5682
  b: { type: "string" },
5545
- pick_a: { type: "number", description: "choose nth candidate for a when ambiguous" },
5546
- pick_b: { type: "number", description: "choose nth candidate for b when ambiguous" }
5683
+ pick_a: { type: "number" },
5684
+ pick_b: { type: "number" }
5547
5685
  },
5548
5686
  ["a", "b"]
5549
5687
  ),
@@ -5560,25 +5698,76 @@ var TOOLS = [
5560
5698
  },
5561
5699
  {
5562
5700
  name: "impact_of",
5563
- description: "What breaks if a node changes: the reverse-reachability blast radius with per-result depth and confidence.",
5701
+ description: "Blast radius of a change: what breaks if this symbol changes \u2014 dependents, files, covering tests, risk.",
5564
5702
  inputSchema: obj(
5565
5703
  {
5566
5704
  name: { type: "string" },
5567
- depth: { type: "number", description: "max depth (default 4)" },
5568
- pick: { type: "number", description: "choose nth candidate when ambiguous" }
5705
+ change_type: { type: "string", enum: ["modify", "delete", "rename", "add_dependency"] },
5706
+ depth: { type: "number", description: "default 4" },
5707
+ pick: { type: "number", description: "nth candidate if ambiguous" },
5708
+ response_format: RESPONSE_FORMAT
5569
5709
  },
5570
5710
  ["name"]
5571
5711
  ),
5572
5712
  handler: (graph, args) => {
5573
5713
  const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
5574
5714
  if (!node) return unresolved(candidates);
5715
+ const changeType = ["modify", "delete", "rename", "add_dependency"].includes(String(args.change_type)) ? String(args.change_type) : "modify";
5575
5716
  const r = impactOf(graph, node.id, { depth: numOr(args.depth, 4) });
5576
- return { root: r.root.name, direct: r.direct, transitive: r.transitive, affected: r.affected.slice(0, 100).map(stripId) };
5717
+ const tests = coveringTests(graph, node);
5718
+ const filesAffected = [...new Set(r.affected.map((a) => a.file))];
5719
+ const heavyChange = changeType === "delete" || changeType === "rename";
5720
+ const riskLevel = node.isHub || r.direct >= 10 || r.transitive >= 50 || heavyChange && r.direct >= 3 ? "high" : r.direct >= 3 || r.transitive >= 10 ? "medium" : "low";
5721
+ const summary = `${changeType} ${r.root.name}: ${r.direct} direct dependent${r.direct === 1 ? "" : "s"}, ${r.transitive} transitive across ${filesAffected.length} file${filesAffected.length === 1 ? "" : "s"}; ${tests.length} covering test${tests.length === 1 ? "" : "s"} \u2014 ${riskLevel} risk.`;
5722
+ return {
5723
+ root: r.root.name,
5724
+ changeType,
5725
+ directCallers: r.direct,
5726
+ transitiveCount: r.transitive,
5727
+ filesAffected: filesAffected.slice(0, 20),
5728
+ testsAffected: tests.length,
5729
+ riskLevel,
5730
+ summary,
5731
+ // The full row set is detail; concise callers act on the contract above.
5732
+ ...isDetailed(args) ? { affected: r.affected.slice(0, 100).map(stripId) } : r.affected.length > 0 ? { hint: 'response_format:"detailed" lists the affected nodes' } : {}
5733
+ };
5734
+ }
5735
+ },
5736
+ {
5737
+ name: "tests_for",
5738
+ description: "Which tests cover this symbol \u2014 is a change here tested?",
5739
+ inputSchema: obj(
5740
+ {
5741
+ name: { type: "string" },
5742
+ pick: { type: "number", description: "nth candidate if ambiguous" },
5743
+ response_format: RESPONSE_FORMAT
5744
+ },
5745
+ ["name"]
5746
+ ),
5747
+ handler: (graph, args) => {
5748
+ const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
5749
+ if (!node) return unresolved(candidates);
5750
+ const covers = coveringTests(graph, node);
5751
+ return {
5752
+ node: node.qualifiedName,
5753
+ tested: node.tested,
5754
+ coverage: node.coverage ?? null,
5755
+ testCount: covers.length,
5756
+ covers: isDetailed(args) ? covers : covers.slice(0, 10),
5757
+ ...covers.length > 10 && !isDetailed(args) ? { moreAvailable: true } : {},
5758
+ ...covers.length === 0 ? { hint: "no covering tests found \u2014 a change here lands untested" } : {}
5759
+ };
5577
5760
  }
5578
5761
  },
5762
+ {
5763
+ name: "get_graph_summary",
5764
+ description: "Code map overview: counts, languages, top areas and hubs.",
5765
+ inputSchema: obj({}),
5766
+ handler: (graph) => summarize(graph)
5767
+ },
5579
5768
  {
5580
5769
  name: "list_areas",
5581
- description: "The natural code groupings (communities), each labelled, sized, with cohesion.",
5770
+ description: "Code areas (communities) by size.",
5582
5771
  inputSchema: obj({ limit: { type: "number" } }),
5583
5772
  // Strip `members` (raw content-hash id arrays): a model cannot use them and
5584
5773
  // on a large repo they ballooned this result past 20k tokens.
@@ -5586,23 +5775,13 @@ var TOOLS = [
5586
5775
  },
5587
5776
  {
5588
5777
  name: "list_hubs",
5589
- description: "The most-depended-on code (centrality outliers).",
5778
+ description: "Most-depended-on symbols.",
5590
5779
  inputSchema: obj({ limit: { type: "number" } }),
5591
5780
  handler: (graph, args) => graph.nodes.filter((n) => n.kind !== "file" && n.kind !== "external").sort((a, b) => b.importance - a.importance).slice(0, numOr(args.limit, 20)).map((n) => ({ name: n.qualifiedName, kind: n.kind, file: n.file, line: n.span.start, importance: n.importance, isHub: n.isHub }))
5592
5781
  },
5593
- {
5594
- name: "tests_for",
5595
- description: "Which tests cover a node (static call linkage + runtime coverage), with the linkage basis.",
5596
- inputSchema: obj({ name: { type: "string" }, pick: { type: "number", description: "choose nth candidate when ambiguous" } }, ["name"]),
5597
- handler: (graph, args) => {
5598
- const { node, candidates } = resolveOne(graph, String(args.name ?? ""), numOrU(args.pick));
5599
- if (!node) return unresolved(candidates);
5600
- return { node: node.qualifiedName, tested: node.tested, coverage: node.coverage ?? null, covers: coveringTests(graph, node) };
5601
- }
5602
- },
5603
5782
  {
5604
5783
  name: "get_facts",
5605
- description: "The deterministic open facts for a node (contract/invariant/characterization). Requires a --deep build.",
5784
+ description: "Deterministic facts for a node (contract/invariant); needs a --deep build.",
5606
5785
  inputSchema: obj({ name: { type: "string" } }, ["name"]),
5607
5786
  handler: (graph, args) => {
5608
5787
  const { node, candidates } = resolveOne(graph, String(args.name ?? ""));
@@ -5613,7 +5792,7 @@ var TOOLS = [
5613
5792
  },
5614
5793
  {
5615
5794
  name: "guide_node",
5616
- description: "Cited relevant standards/practices for a node (OWASP/CWE), honest about recommended vs conjectured.",
5795
+ description: "Cited standards/practices for a node (OWASP/CWE).",
5617
5796
  inputSchema: obj({ name: { type: "string" } }, ["name"]),
5618
5797
  handler: (graph, args) => {
5619
5798
  const { node, candidates } = resolveOne(graph, String(args.name ?? ""));
@@ -5627,17 +5806,17 @@ var TOOLS = [
5627
5806
  },
5628
5807
  {
5629
5808
  name: "check_drift",
5630
- description: 'Offline dependency inventory across npm/pypi/go (currency enrichment is the CLI\u2019s --online opt-in). Pass attribute:true to add git "who added this / who set the version" attribution for npm deps.',
5809
+ description: "Offline dependency inventory (npm/pypi/go); attribute:true adds git who-added attribution.",
5631
5810
  inputSchema: obj(
5632
- { attribute: { type: "boolean", description: "enrich npm deps with git introduction attribution (requires git; slower)" } },
5811
+ { attribute: { type: "boolean" } },
5633
5812
  []
5634
5813
  ),
5635
5814
  handler: (_graph, args, ctx) => attributedInventory(ctx.root, { attribute: args.attribute === true })
5636
5815
  },
5637
5816
  {
5638
5817
  name: "vuln_attribution",
5639
- description: "Who introduced each open vulnerability and how long you have been exposed, plus CRA remediation metrics: open exposure windows, SLA breaches, and real remediation time (MTTR) reconstructed from vulnerable versions that were later bumped out or removed in git history. Offline read of the last `vg scan --vulns`.",
5640
- inputSchema: obj({ package: { type: "string", description: "optional: restrict to one package" } }, []),
5818
+ description: "Who introduced each open vulnerability, exposure windows, and CRA remediation metrics (MTTR, SLA breaches). Reads the last `vg scan --vulns`.",
5819
+ inputSchema: obj({ package: { type: "string", description: "restrict to one package" } }, []),
5641
5820
  handler: (_graph, args, ctx) => {
5642
5821
  const data = loadVulnerabilities(ctx.root);
5643
5822
  if (!data) {
@@ -5666,9 +5845,9 @@ var TOOLS = [
5666
5845
  },
5667
5846
  {
5668
5847
  name: "list_vulnerabilities",
5669
- description: "Known vulnerabilities for installed dependencies, from the last `vg scan --vulns`. Offline read of the local scan artifact (no network); reports advisory id/CVE, severity, CVSS, and the fixed version.",
5848
+ description: "Known vulnerabilities from the last `vg scan --vulns`: id/CVE, severity, CVSS, fixed version.",
5670
5849
  inputSchema: obj(
5671
- { severity: { type: "string", enum: ["low", "moderate", "high", "critical"], description: "optional minimum severity to include" } },
5850
+ { severity: { type: "string", enum: ["low", "moderate", "high", "critical"], description: "minimum severity" } },
5672
5851
  []
5673
5852
  ),
5674
5853
  handler: (_graph, args, ctx) => {
@@ -5704,11 +5883,11 @@ var TOOLS = [
5704
5883
  },
5705
5884
  {
5706
5885
  name: "upgrade_impact",
5707
- description: 'A local "what breaks if I upgrade this" brief for a package: major-version distance + interim majors to step through, source blast radius (how many files import it), open vulnerabilities the upgrade would fix, and a recommended posture. Offline; richest after `vg scan`. Pass changelog:true to also fetch breaking-change signals from the package\'s GitHub releases (online; ignored under --local).',
5886
+ description: "What breaks if you upgrade a package: major distance, import blast radius, vulns fixed, recommended posture. changelog:true adds GitHub breaking-change signals (online).",
5708
5887
  inputSchema: obj(
5709
5888
  {
5710
- package: { type: "string", description: "package name to assess" },
5711
- changelog: { type: "boolean", description: "also fetch breaking-change signals from GitHub releases between your version and latest (online; ignored under --local)" }
5889
+ package: { type: "string" },
5890
+ changelog: { type: "boolean" }
5712
5891
  },
5713
5892
  ["package"]
5714
5893
  ),
@@ -5724,19 +5903,19 @@ var TOOLS = [
5724
5903
  },
5725
5904
  {
5726
5905
  name: "list_models",
5727
- description: "The local model fleet discovered on disk (Ollama / LM Studio / gguf). Offline, no launch.",
5906
+ description: "Local models on disk (Ollama / LM Studio / gguf).",
5728
5907
  inputSchema: obj({}),
5729
5908
  handler: () => ({ models: discoverModels() })
5730
5909
  },
5731
5910
  {
5732
5911
  name: "resolve_library",
5733
- description: "Resolve a library name/query to its canonical id + the version for YOUR project (version-correct + drift-annotated).",
5912
+ description: "Resolve a library to its canonical id and the version YOUR project uses (drift-annotated).",
5734
5913
  // Canonical §4: `query` (+ optional `context_project`). `name` kept as a back-compat alias.
5735
5914
  inputSchema: obj(
5736
5915
  {
5737
- query: { type: "string", description: "library name or natural-language query" },
5738
- name: { type: "string", description: "alias for query (back-compat)" },
5739
- context_project: { type: "string", description: "optional lockfile/manifest context (informational; the local server uses the project root)" }
5916
+ query: { type: "string", description: "library name or query" },
5917
+ name: { type: "string" },
5918
+ context_project: { type: "string" }
5740
5919
  },
5741
5920
  []
5742
5921
  ),
@@ -5779,19 +5958,19 @@ var TOOLS = [
5779
5958
  },
5780
5959
  {
5781
5960
  name: "library_docs",
5782
- description: "Version-correct, drift-annotated usage docs for a library \u2014 from the committed catalog or the installed package on disk.",
5961
+ description: "Version-correct usage docs for a library, sliced to a token budget.",
5783
5962
  // Canonical §4: `targetId`/`query`, `verbosity`, `max_tokens`. `name`/`tokens` kept as aliases.
5784
5963
  inputSchema: obj(
5785
5964
  {
5786
- targetId: { type: "string", description: "canonical id from resolve_library" },
5965
+ targetId: { type: "string", description: "id from resolve_library" },
5787
5966
  query: { type: "string", description: "library name or query" },
5788
- name: { type: "string", description: "alias for query (back-compat)" },
5789
- context_project: { type: "string", description: "optional project context (informational)" },
5790
- verbosity: { type: "string", enum: ["concise", "balanced", "exhaustive"], description: "detail level (sets the default token budget)" },
5791
- max_tokens: { type: "number", description: "explicit token budget (overrides verbosity)" },
5792
- tokens: { type: "number", description: "alias for max_tokens (back-compat)" },
5793
- enterprise_strict: { type: "boolean", description: "reserved (enterprise policy enforcement \u2014 hosted surface)" },
5794
- follow_up: { type: "boolean", description: "reserved (next slice \u2014 hosted surface)" }
5967
+ name: { type: "string" },
5968
+ context_project: { type: "string" },
5969
+ verbosity: { type: "string", enum: ["concise", "balanced", "exhaustive"] },
5970
+ max_tokens: { type: "number", description: "token budget" },
5971
+ tokens: { type: "number" },
5972
+ enterprise_strict: { type: "boolean" },
5973
+ follow_up: { type: "boolean" }
5795
5974
  },
5796
5975
  []
5797
5976
  ),
@@ -5886,15 +6065,15 @@ var TOOLS = [
5886
6065
  }
5887
6066
  ];
5888
6067
  var VERBOSITY_BUDGET = { concise: 1500, balanced: 4e3, exhaustive: 12e3 };
5889
- function summarize(graph) {
6068
+ function summarize(graph, top = 10) {
5890
6069
  return {
5891
6070
  counts: graph.meta.counts,
5892
6071
  languages: graph.meta.languages,
5893
6072
  cluster: graph.meta.cluster,
5894
6073
  resolver: graph.provenance.resolver,
5895
6074
  generatedAt: graph.generatedAt,
5896
- topAreas: [...graph.areas].sort((a, b) => b.size - a.size).slice(0, 10).map((a) => ({ id: a.id, label: a.label, size: a.size })),
5897
- topHubs: graph.nodes.filter((n) => n.isHub).sort((a, b) => b.importance - a.importance).slice(0, 10).map((n) => ({ name: n.qualifiedName, file: n.file, importance: n.importance }))
6075
+ topAreas: [...graph.areas].sort((a, b) => b.size - a.size).slice(0, top).map((a) => ({ id: a.id, label: a.label, size: a.size })),
6076
+ topHubs: graph.nodes.filter((n) => n.isHub).sort((a, b) => b.importance - a.importance).slice(0, top).map((n) => ({ name: n.qualifiedName, file: n.file, importance: n.importance }))
5898
6077
  };
5899
6078
  }
5900
6079
  function stripId(item) {
@@ -5925,6 +6104,19 @@ function numOrU(v) {
5925
6104
  function uniqueNames(xs) {
5926
6105
  return [...new Set(xs)];
5927
6106
  }
6107
+ var HOT_TOOLS = ["orient", "search_symbols", "query_graph", "get_node"];
6108
+ function deferredToolNames() {
6109
+ const hot = new Set(HOT_TOOLS);
6110
+ return TOOLS.map((t) => t.name).filter((n) => !hot.has(n));
6111
+ }
6112
+ function navigationToolsetConfig(serverName = "vibgrate") {
6113
+ return {
6114
+ type: "mcp_toolset",
6115
+ mcp_server_name: serverName,
6116
+ default_config: { defer_loading: true },
6117
+ configs: Object.fromEntries(HOT_TOOLS.map((n) => [n, { defer_loading: false }]))
6118
+ };
6119
+ }
5928
6120
  var LEDGER = "savings.jsonl";
5929
6121
  var PER_FILE_TOKENS = 400;
5930
6122
  function ledgerPath(root) {
@@ -6047,7 +6239,13 @@ function createServer(source, opts = {}) {
6047
6239
  const seen = /* @__PURE__ */ new Set();
6048
6240
  const server = new Server(
6049
6241
  { name: "vg", version: VERSION },
6050
- { capabilities: { tools: {} } }
6242
+ {
6243
+ capabilities: { tools: {} },
6244
+ // Routing guidance once at the server level (hosts that surface
6245
+ // `instructions` get it at zero per-step schema cost): the flashlight
6246
+ // vs the map.
6247
+ instructions: 'vg is a code map. Use search_symbols to find a known name or literal string fast. Use orient/query_graph for meaning: symptoms, relationships, and what-breaks-if. Responses are concise by default; pass response_format:"detailed" only when a node proves load-bearing. Navigate as little as possible: one good search/query usually locates the code. As soon as you have the file and line, read that file and make the edit \u2014 do not call further graph tools unless the edit fails or the match was wrong.'
6248
+ }
6051
6249
  );
6052
6250
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
6053
6251
  tools: TOOLS.map((t) => ({
@@ -6086,6 +6284,7 @@ function createServer(source, opts = {}) {
6086
6284
  async function serveStdio(graphPath, opts = {}) {
6087
6285
  const source = new GraphSource(graphPath, opts.refresh !== false);
6088
6286
  const server = createServer(source, opts);
6287
+ warmEmbedderInBackground(opts.local);
6089
6288
  await server.connect(new StdioServerTransport());
6090
6289
  }
6091
6290
  function sleep(ms) {
@@ -6310,6 +6509,18 @@ function writeFileEnsured(file, content) {
6310
6509
  fs18.mkdirSync(path18.dirname(file), { recursive: true });
6311
6510
  fs18.writeFileSync(file, content);
6312
6511
  }
6512
+ function writeNavigationConfig(root) {
6513
+ const rel2 = path18.join(".vibgrate", "mcp-navigation.json");
6514
+ const doc = {
6515
+ _readme: "Deferred-loading config for agents embedding `vg serve` via the Claude API (defer_loading + tool-search). The hot navigation core stays in context; the rest load on demand \u2014 ~350-450 schema tokens/step vs ~1,881 for the full set. Hosts without defer_loading ignore this and serve the whole (already optimized) tool set. See docs/graph/VG-NAVIGATION-PROFILE.md.",
6516
+ hot_core: [...HOT_TOOLS],
6517
+ deferred: deferredToolNames(),
6518
+ toolset: navigationToolsetConfig()
6519
+ };
6520
+ writeFileEnsured(path18.join(root, rel2), `${JSON.stringify(doc, null, 2)}
6521
+ `);
6522
+ return rel2;
6523
+ }
6313
6524
  function upsertMcp(file, target, launch) {
6314
6525
  const config = readJson(file);
6315
6526
  const entry = mcpServerEntry(launch);
@@ -6524,6 +6735,6 @@ function spdx(ctx) {
6524
6735
  return JSON.stringify(doc, null, 2) + "\n";
6525
6736
  }
6526
6737
 
6527
- export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeSnapshot, writeStoredCredentials };
6528
- //# sourceMappingURL=chunk-YODVLV37.js.map
6529
- //# sourceMappingURL=chunk-YODVLV37.js.map
6738
+ export { ASSISTANTS, CliError, ECOSYSTEMS, ExitCode, FREE_PACK, GraphIndex, GraphSource, NPX_INVOCATION, ResourceLimitError, SCHEMA_VERSION, SKIP_DIRS, SKIP_FILES, TOOLS, UsageError, addLibrary, analyze, applyCoverage, applyStaticTestLinkage, assessDocQuality, assistantById, availableRegionIds, buildFacts, buildGraph, buildModuleResolver, cacheDir, catalogPath, clearModelCache, clearStoredCredentials, cosine, countPending, coveringTests, createServer, createWorkspaceDsn, credentialsPath, dashHostForIngestHost, decodeScipIndex, defaultGraphPath, detectRunner, detectServeLaunch, discover, discoverModels, driftCount, driftFor, dsnCommand, embeddingsCached, enrichOnline, ensureGitignored, exportGraph, fetchHostedDocs, findGitRoot, findNodes, formatForExt, getNodeEmbeddings, gitignoreEntryForCredentials, groundGraph, hasDrift, identifierParts, impactOf, installAssistant, inventory, isModelReady, isTestFile, libDir, libId, loadCatalog, loadCoverage, loadEmbedder, loadGraph, loadSnapshot, localApiSurface, localPackageDocs, modelCacheInfo, nodeById, nodeEmbedText, parseDsn, parseGraph, parseJsonc, probeFreshness, publishPrivateLibrary, pushCommand, queryGraph, queryGraphSemantic, readDoc, readSavings, readScanArtifact, recordSaving, refreshIfStale, relativeResolver, renderHtml, renderReport, resolveCliInvocation, resolveDsn, resolveEmbedModel, resolveIngestHost, resolveLib, resolveLimits, resolveOne, resolveVersion, saveCatalog, savingsRecorded, scipEdges, selectForBudget, serializeGraph, serveStdio, shortestPath, stableStringify, symbolsFromApi, testsToRun, unavailableMessage, uninstallAssistant, uploadScanArtifact, usageError, verifyDeterminism, vibgrateDir, writeArtifacts, writeNavigationConfig, writeSnapshot, writeStoredCredentials };
6739
+ //# sourceMappingURL=chunk-JL7FIC6W.js.map
6740
+ //# sourceMappingURL=chunk-JL7FIC6W.js.map