trace-mcp 1.2.1 → 1.4.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/dist/cli.js CHANGED
@@ -619,8 +619,8 @@ var require_utils = __commonJS({
619
619
  }
620
620
  return output;
621
621
  };
622
- exports.basename = (path89, { windows } = {}) => {
623
- const segs = path89.split(windows ? /[\\/]/ : "/");
622
+ exports.basename = (path98, { windows } = {}) => {
623
+ const segs = path98.split(windows ? /[\\/]/ : "/");
624
624
  const last = segs[segs.length - 1];
625
625
  if (last === "") {
626
626
  return segs[segs.length - 2];
@@ -2116,8 +2116,8 @@ var require_picomatch2 = __commonJS({
2116
2116
 
2117
2117
  // src/cli.ts
2118
2118
  import { Command as Command10 } from "commander";
2119
- import path88 from "path";
2120
- import fs77 from "fs";
2119
+ import path97 from "path";
2120
+ import fs86 from "fs";
2121
2121
  import { randomUUID } from "crypto";
2122
2122
  import { createRequire as createRequire21 } from "module";
2123
2123
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -3191,14 +3191,14 @@ var Store = class {
3191
3191
  db;
3192
3192
  _stmts;
3193
3193
  // --- Files ---
3194
- insertFile(path89, language, contentHash, byteLength, workspace, mtimeMs) {
3195
- const result = this._stmts.insertFile.run(path89, language, contentHash, byteLength, workspace ?? null, mtimeMs ?? null);
3194
+ insertFile(path98, language, contentHash, byteLength, workspace, mtimeMs) {
3195
+ const result = this._stmts.insertFile.run(path98, language, contentHash, byteLength, workspace ?? null, mtimeMs ?? null);
3196
3196
  const fileId = Number(result.lastInsertRowid);
3197
3197
  this.createNode("file", fileId);
3198
3198
  return fileId;
3199
3199
  }
3200
- getFile(path89) {
3201
- return this._stmts.getFile.get(path89);
3200
+ getFile(path98) {
3201
+ return this._stmts.getFile.get(path98);
3202
3202
  }
3203
3203
  getFileById(id) {
3204
3204
  return this._stmts.getFileById.get(id);
@@ -4735,6 +4735,16 @@ function buildProjectContext(rootPath) {
4735
4735
  } catch {
4736
4736
  }
4737
4737
  }
4738
+ try {
4739
+ const ghWorkflowDir = path2.resolve(rootPath, ".github/workflows");
4740
+ const entries = fs3.readdirSync(ghWorkflowDir);
4741
+ for (const entry of entries) {
4742
+ if (entry.endsWith(".yml") || entry.endsWith(".yaml")) {
4743
+ configFiles.push(`.github/workflows/${entry}`);
4744
+ }
4745
+ }
4746
+ } catch {
4747
+ }
4738
4748
  return {
4739
4749
  rootPath,
4740
4750
  packageJson,
@@ -5491,69 +5501,547 @@ function buildNode(store, comp, filePath, remainingDepth, visited, budget) {
5491
5501
  return node;
5492
5502
  }
5493
5503
 
5504
+ // src/tools/git-analysis.ts
5505
+ import { execFileSync } from "child_process";
5506
+ function isGitRepo(cwd) {
5507
+ try {
5508
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
5509
+ cwd,
5510
+ stdio: "pipe",
5511
+ timeout: 5e3
5512
+ });
5513
+ return true;
5514
+ } catch {
5515
+ return false;
5516
+ }
5517
+ }
5518
+ function getGitFileStats(cwd, sinceDays) {
5519
+ const args = [
5520
+ "log",
5521
+ "--pretty=format:__COMMIT__%H|%aI|%aN",
5522
+ "--name-only",
5523
+ "--no-merges",
5524
+ "--diff-filter=ACDMR"
5525
+ ];
5526
+ if (sinceDays !== void 0) {
5527
+ args.push(`--since=${sinceDays} days ago`);
5528
+ }
5529
+ let output;
5530
+ try {
5531
+ output = execFileSync("git", args, {
5532
+ cwd,
5533
+ stdio: "pipe",
5534
+ maxBuffer: 10 * 1024 * 1024,
5535
+ // 10 MB
5536
+ timeout: 3e4
5537
+ }).toString("utf-8");
5538
+ } catch (e) {
5539
+ logger.warn({ error: e }, "git log failed");
5540
+ return /* @__PURE__ */ new Map();
5541
+ }
5542
+ const fileStats = /* @__PURE__ */ new Map();
5543
+ let currentDate = null;
5544
+ let currentAuthor = null;
5545
+ for (const line of output.split("\n")) {
5546
+ if (line.startsWith("__COMMIT__")) {
5547
+ const parts = line.slice("__COMMIT__".length).split("|");
5548
+ currentDate = new Date(parts[1]);
5549
+ currentAuthor = parts[2];
5550
+ continue;
5551
+ }
5552
+ const trimmed = line.trim();
5553
+ if (!trimmed || !currentDate || !currentAuthor) continue;
5554
+ const existing = fileStats.get(trimmed);
5555
+ if (existing) {
5556
+ existing.commits++;
5557
+ existing.authors.add(currentAuthor);
5558
+ if (currentDate < existing.firstDate) existing.firstDate = currentDate;
5559
+ if (currentDate > existing.lastDate) existing.lastDate = currentDate;
5560
+ } else {
5561
+ fileStats.set(trimmed, {
5562
+ file: trimmed,
5563
+ commits: 1,
5564
+ authors: /* @__PURE__ */ new Set([currentAuthor]),
5565
+ firstDate: currentDate,
5566
+ lastDate: currentDate
5567
+ });
5568
+ }
5569
+ }
5570
+ return fileStats;
5571
+ }
5572
+ function getChurnRate(cwd, options = {}) {
5573
+ const { sinceDays, limit = 50, filePattern } = options;
5574
+ if (!isGitRepo(cwd)) {
5575
+ return [];
5576
+ }
5577
+ const stats = getGitFileStats(cwd, sinceDays);
5578
+ let entries = [];
5579
+ for (const [file, data] of stats) {
5580
+ if (filePattern && !file.includes(filePattern)) continue;
5581
+ const lifespanMs = data.lastDate.getTime() - data.firstDate.getTime();
5582
+ const lifespanWeeks = Math.max(lifespanMs / (7 * 24 * 60 * 60 * 1e3), 1);
5583
+ const churnPerWeek = Math.round(data.commits / lifespanWeeks * 100) / 100;
5584
+ let assessment;
5585
+ if (churnPerWeek <= 1) assessment = "stable";
5586
+ else if (churnPerWeek <= 3) assessment = "active";
5587
+ else assessment = "volatile";
5588
+ entries.push({
5589
+ file,
5590
+ commits: data.commits,
5591
+ unique_authors: data.authors.size,
5592
+ first_seen: data.firstDate.toISOString().split("T")[0],
5593
+ last_modified: data.lastDate.toISOString().split("T")[0],
5594
+ churn_per_week: churnPerWeek,
5595
+ assessment
5596
+ });
5597
+ }
5598
+ entries.sort((a, b) => b.commits - a.commits);
5599
+ return entries.slice(0, limit);
5600
+ }
5601
+ function getHotspots(store, cwd, options = {}) {
5602
+ const { sinceDays = 90, limit = 20, minCyclomatic = 3 } = options;
5603
+ if (!isGitRepo(cwd)) {
5604
+ return getComplexityOnlyHotspots(store, limit, minCyclomatic);
5605
+ }
5606
+ const gitStats = getGitFileStats(cwd, sinceDays);
5607
+ const fileComplexity = getMaxCyclomaticPerFile(store);
5608
+ const entries = [];
5609
+ for (const [file, maxCyclomatic] of fileComplexity) {
5610
+ if (maxCyclomatic < minCyclomatic) continue;
5611
+ const git = gitStats.get(file);
5612
+ const commits = git?.commits ?? 0;
5613
+ const score = Math.round(maxCyclomatic * Math.log(1 + commits) * 100) / 100;
5614
+ if (score <= 0) continue;
5615
+ let assessment;
5616
+ if (score <= 3) assessment = "low";
5617
+ else if (score <= 10) assessment = "medium";
5618
+ else assessment = "high";
5619
+ entries.push({
5620
+ file,
5621
+ max_cyclomatic: maxCyclomatic,
5622
+ commits,
5623
+ score,
5624
+ assessment
5625
+ });
5626
+ }
5627
+ entries.sort((a, b) => b.score - a.score);
5628
+ return entries.slice(0, limit);
5629
+ }
5630
+ function getMaxCyclomaticPerFile(store) {
5631
+ const rows = store.db.prepare(`
5632
+ SELECT f.path, MAX(s.cyclomatic) as max_cyclomatic
5633
+ FROM symbols s
5634
+ JOIN files f ON s.file_id = f.id
5635
+ WHERE s.cyclomatic IS NOT NULL
5636
+ GROUP BY f.path
5637
+ `).all();
5638
+ const result = /* @__PURE__ */ new Map();
5639
+ for (const row of rows) {
5640
+ result.set(row.path, row.max_cyclomatic);
5641
+ }
5642
+ return result;
5643
+ }
5644
+ function getComplexityOnlyHotspots(store, limit, minCyclomatic) {
5645
+ const fileComplexity = getMaxCyclomaticPerFile(store);
5646
+ const entries = [];
5647
+ for (const [file, maxCyclomatic] of fileComplexity) {
5648
+ if (maxCyclomatic < minCyclomatic) continue;
5649
+ entries.push({
5650
+ file,
5651
+ max_cyclomatic: maxCyclomatic,
5652
+ commits: 0,
5653
+ score: maxCyclomatic,
5654
+ // score = complexity alone
5655
+ assessment: maxCyclomatic <= 3 ? "low" : maxCyclomatic <= 10 ? "medium" : "high"
5656
+ });
5657
+ }
5658
+ entries.sort((a, b) => b.score - a.score);
5659
+ return entries.slice(0, limit);
5660
+ }
5661
+
5494
5662
  // src/tools/impact.ts
5495
- function getChangeImpact(store, opts, depth = 3, maxDependents = 200) {
5496
- let startNodeId;
5663
+ import { execSync } from "child_process";
5664
+ function getModule(filePath, depth = 2) {
5665
+ const parts = filePath.split("/");
5666
+ return parts.length <= depth ? parts[0] : parts.slice(0, depth).join("/");
5667
+ }
5668
+ function riskLevel(score) {
5669
+ if (score >= 0.75) return "critical";
5670
+ if (score >= 0.5) return "high";
5671
+ if (score >= 0.25) return "medium";
5672
+ return "low";
5673
+ }
5674
+ function round(v, decimals = 2) {
5675
+ const f = 10 ** decimals;
5676
+ return Math.round(v * f) / f;
5677
+ }
5678
+ function clamp01(v, ceiling) {
5679
+ return Math.min(v / ceiling, 1);
5680
+ }
5681
+ function getTestedFileIds(store) {
5682
+ const rows = store.db.prepare(`
5683
+ SELECT DISTINCT
5684
+ CASE
5685
+ WHEN n.node_type = 'file' THEN n.ref_id
5686
+ WHEN n.node_type = 'symbol' THEN (SELECT file_id FROM symbols WHERE id = n.ref_id)
5687
+ END AS fid
5688
+ FROM edges e
5689
+ JOIN edge_types et ON e.edge_type_id = et.id
5690
+ JOIN nodes n ON e.target_node_id = n.id
5691
+ WHERE et.name = 'test_covers'
5692
+ `).all();
5693
+ const set = /* @__PURE__ */ new Set();
5694
+ for (const r of rows) if (r.fid != null) set.add(r.fid);
5695
+ return set;
5696
+ }
5697
+ function getCoChangesForFile(store, filePath, graphFiles) {
5698
+ try {
5699
+ const rows = store.db.prepare(`
5700
+ SELECT
5701
+ CASE WHEN file_a = ? THEN file_b ELSE file_a END AS co_file,
5702
+ confidence
5703
+ FROM co_changes
5704
+ WHERE (file_a = ? OR file_b = ?)
5705
+ AND confidence >= 0.3
5706
+ AND co_change_count >= 3
5707
+ ORDER BY confidence DESC
5708
+ LIMIT 15
5709
+ `).all(filePath, filePath, filePath);
5710
+ return rows.map((r) => ({
5711
+ file: r.co_file,
5712
+ confidence: round(r.confidence),
5713
+ inGraph: graphFiles.has(r.co_file)
5714
+ }));
5715
+ } catch {
5716
+ return [];
5717
+ }
5718
+ }
5719
+ function findAffectedTests(store, targetPath, dependentPaths) {
5720
+ const seen = /* @__PURE__ */ new Set();
5721
+ const allPaths = [targetPath, ...dependentPaths];
5722
+ for (const p4 of allPaths) {
5723
+ const file = store.getFile(p4);
5724
+ if (!file) continue;
5725
+ const fileNodeId = store.getNodeId("file", file.id);
5726
+ if (fileNodeId != null) {
5727
+ collectTestFiles(store, fileNodeId, seen);
5728
+ }
5729
+ const symbols = store.getSymbolsByFile(file.id);
5730
+ for (const sym of symbols) {
5731
+ const symNodeId = store.getNodeId("symbol", sym.id);
5732
+ if (symNodeId != null) {
5733
+ collectTestFiles(store, symNodeId, seen);
5734
+ }
5735
+ }
5736
+ }
5737
+ const files = [...seen].sort();
5738
+ return { total: files.length, files };
5739
+ }
5740
+ function collectTestFiles(store, nodeId, seen) {
5741
+ const incoming = store.getIncomingEdges(nodeId);
5742
+ for (const edge of incoming) {
5743
+ if (edge.edge_type_name !== "test_covers") continue;
5744
+ const ref = store.getNodeRef(edge.source_node_id);
5745
+ if (!ref) continue;
5746
+ let fileId;
5747
+ if (ref.nodeType === "file") {
5748
+ fileId = ref.refId;
5749
+ } else if (ref.nodeType === "symbol") {
5750
+ const s = store.getSymbolById(ref.refId);
5751
+ if (s) fileId = s.file_id;
5752
+ }
5753
+ if (fileId != null) {
5754
+ const f = store.getFileById(fileId);
5755
+ if (f) seen.add(f.path);
5756
+ }
5757
+ }
5758
+ }
5759
+ function getFileChurn(cwd, filePath, days) {
5760
+ try {
5761
+ const since = new Date(Date.now() - days * 864e5).toISOString().slice(0, 10);
5762
+ const output = execSync(
5763
+ `git log --since="${since}" --oneline -- "${filePath}" | wc -l`,
5764
+ { cwd, encoding: "utf8", timeout: 5e3, stdio: ["pipe", "pipe", "pipe"] }
5765
+ );
5766
+ return parseInt(output.trim(), 10) || 0;
5767
+ } catch {
5768
+ return 0;
5769
+ }
5770
+ }
5771
+ function getChangeImpact(store, opts, depth = 3, maxDependents = 200, cwd) {
5772
+ let startNodeIds = [];
5497
5773
  let targetPath;
5498
5774
  let targetSymbolId;
5499
- if (opts.symbolId) {
5775
+ let targetSymbolName;
5776
+ let targetKind;
5777
+ let scopedToSymbols;
5778
+ if (opts.symbolIds && opts.symbolIds.length > 0) {
5779
+ scopedToSymbols = opts.symbolIds;
5780
+ let firstSym;
5781
+ for (const sid of opts.symbolIds) {
5782
+ const sym = store.getSymbolBySymbolId(sid);
5783
+ if (!sym) continue;
5784
+ if (!firstSym) firstSym = sym;
5785
+ const nid = store.getNodeId("symbol", sym.id);
5786
+ if (nid != null) startNodeIds.push(nid);
5787
+ }
5788
+ if (!firstSym) {
5789
+ return err(notFound(opts.symbolIds[0]));
5790
+ }
5791
+ const file = store.getFileById(firstSym.file_id);
5792
+ targetPath = file?.path ?? "unknown";
5793
+ targetSymbolId = firstSym.symbol_id;
5794
+ targetSymbolName = firstSym.name;
5795
+ targetKind = firstSym.kind;
5796
+ startNodeIds = [...new Set(startNodeIds)];
5797
+ } else if (opts.symbolId) {
5500
5798
  const sym = store.getSymbolBySymbolId(opts.symbolId);
5501
5799
  if (!sym) {
5502
5800
  return err(notFound(opts.symbolId));
5503
5801
  }
5504
- startNodeId = store.getNodeId("symbol", sym.id);
5802
+ const nodeId = store.getNodeId("symbol", sym.id);
5803
+ if (nodeId != null) startNodeIds.push(nodeId);
5505
5804
  const file = store.getFileById(sym.file_id);
5506
5805
  targetPath = file?.path ?? "unknown";
5507
5806
  targetSymbolId = opts.symbolId;
5807
+ targetSymbolName = sym.name;
5808
+ targetKind = sym.kind;
5508
5809
  } else if (opts.filePath) {
5509
5810
  const file = store.getFile(opts.filePath);
5510
5811
  if (!file) {
5511
5812
  return err(notFound(opts.filePath));
5512
5813
  }
5513
5814
  targetPath = file.path;
5815
+ const fileNodeId = store.getNodeId("file", file.id);
5816
+ if (fileNodeId != null) startNodeIds.push(fileNodeId);
5514
5817
  const symbols = store.getSymbolsByFile(file.id);
5515
5818
  const primarySymbol = symbols.find((s) => s.kind === "class") ?? symbols[0];
5516
5819
  if (primarySymbol) {
5517
- startNodeId = store.getNodeId("symbol", primarySymbol.id);
5518
5820
  targetSymbolId = primarySymbol.symbol_id;
5519
- } else {
5520
- startNodeId = store.getNodeId("file", file.id);
5821
+ targetSymbolName = primarySymbol.name;
5822
+ targetKind = primarySymbol.kind;
5521
5823
  }
5824
+ for (const sym of symbols) {
5825
+ const nid = store.getNodeId("symbol", sym.id);
5826
+ if (nid != null) startNodeIds.push(nid);
5827
+ }
5828
+ startNodeIds = [...new Set(startNodeIds)];
5522
5829
  } else {
5523
- return err(notFound("", ["Provide either filePath or symbolId"]));
5830
+ return err(notFound("", ["Provide either filePath, symbolId, or symbolIds"]));
5524
5831
  }
5525
5832
  const pennant = getPennantImpact(store, opts.symbolId ?? opts.filePath ?? "");
5526
- if (startNodeId == null) {
5833
+ if (startNodeIds.length === 0) {
5834
+ const emptySummary = {
5835
+ totalFiles: 0,
5836
+ totalSymbols: 0,
5837
+ maxDepth: 0,
5838
+ crossBoundary: false,
5839
+ publicApiAffected: 0,
5840
+ untestedDependents: 0,
5841
+ highComplexityDependents: 0,
5842
+ sentence: "No dependents found."
5843
+ };
5844
+ const emptyRisk = {
5845
+ score: 0,
5846
+ level: "low",
5847
+ publicApiBreaking: false,
5848
+ untestedRatio: 0,
5849
+ maxComplexity: 0,
5850
+ mitigations: []
5851
+ };
5527
5852
  return ok({
5528
- target: { path: targetPath, symbolId: targetSymbolId },
5853
+ target: { path: targetPath, symbolId: targetSymbolId, symbolName: targetSymbolName, kind: targetKind },
5854
+ summary: emptySummary,
5855
+ risk: emptyRisk,
5529
5856
  dependents: [],
5857
+ affectedTests: { total: 0, files: [] },
5530
5858
  totalAffected: 0
5531
5859
  });
5532
5860
  }
5533
- const dependents = [];
5861
+ const testedFileIds = getTestedFileIds(store);
5862
+ const rawDeps = [];
5534
5863
  const visited = /* @__PURE__ */ new Set();
5535
- visited.add(startNodeId);
5536
- traverseIncoming(store, startNodeId, 1, depth, visited, dependents, maxDependents);
5537
- const truncated = dependents.length >= maxDependents;
5538
- return ok({
5539
- target: { path: targetPath, symbolId: targetSymbolId },
5864
+ for (const nid of startNodeIds) visited.add(nid);
5865
+ traverseIncoming(store, startNodeIds, maxDependents, depth, visited, rawDeps, testedFileIds);
5866
+ const truncated = rawDeps.length >= maxDependents;
5867
+ const dependents = deduplicateByFile(rawDeps);
5868
+ const byEdgeType = {};
5869
+ const byDepth = {};
5870
+ for (const raw of rawDeps) {
5871
+ byEdgeType[raw.edgeType] = (byEdgeType[raw.edgeType] ?? 0) + 1;
5872
+ byDepth[raw.depth] = (byDepth[raw.depth] ?? 0) + 1;
5873
+ }
5874
+ const moduleMap = /* @__PURE__ */ new Map();
5875
+ const graphFiles = /* @__PURE__ */ new Set();
5876
+ const targetModule = getModule(targetPath);
5877
+ let crossBoundary = false;
5878
+ let publicApiAffected = 0;
5879
+ let untestedDependents = 0;
5880
+ let highComplexityDependents = 0;
5881
+ let maxComplexity = 0;
5882
+ let maxDepthSeen = 0;
5883
+ for (const dep of dependents) {
5884
+ const mod = getModule(dep.path);
5885
+ if (mod !== targetModule) crossBoundary = true;
5886
+ let modEntry = moduleMap.get(mod);
5887
+ if (!modEntry) {
5888
+ modEntry = { files: /* @__PURE__ */ new Set(), maxDepth: 0, hasUntested: false };
5889
+ moduleMap.set(mod, modEntry);
5890
+ }
5891
+ modEntry.files.add(dep.path);
5892
+ modEntry.maxDepth = Math.max(modEntry.maxDepth, dep.depth);
5893
+ if (dep.hasTests === false) modEntry.hasUntested = true;
5894
+ graphFiles.add(dep.path);
5895
+ if (dep.hasTests === false) untestedDependents++;
5896
+ if (dep.depth > maxDepthSeen) maxDepthSeen = dep.depth;
5897
+ for (const sym of dep.symbols ?? []) {
5898
+ if (sym.isExported) publicApiAffected++;
5899
+ if ((sym.complexity ?? 0) > 15) highComplexityDependents++;
5900
+ if ((sym.complexity ?? 0) > maxComplexity) maxComplexity = sym.complexity ?? 0;
5901
+ }
5902
+ }
5903
+ const byModule = [...moduleMap.entries()].map(([mod, data]) => ({
5904
+ module: mod,
5905
+ count: data.files.size,
5906
+ files: [...data.files],
5907
+ maxDepth: data.maxDepth,
5908
+ hasUntested: data.hasUntested
5909
+ })).sort((a, b) => b.count - a.count);
5910
+ const affectedTests = findAffectedTests(
5911
+ store,
5912
+ targetPath,
5913
+ dependents.slice(0, 50).map((d) => d.path)
5914
+ );
5915
+ graphFiles.add(targetPath);
5916
+ const coChangeAll = getCoChangesForFile(store, targetPath, graphFiles);
5917
+ const coChangeHidden = coChangeAll.filter((c) => !c.inGraph);
5918
+ const targetFile = store.getFile(targetPath);
5919
+ const targetHasTests = targetFile ? testedFileIds.has(targetFile.id) : false;
5920
+ let churnCommits = 0;
5921
+ if (cwd && isGitRepo(cwd)) {
5922
+ churnCommits = getFileChurn(cwd, targetPath, 180);
5923
+ }
5924
+ const totalFiles = dependents.length || 1;
5925
+ const untestedRatio = round(untestedDependents / totalFiles);
5926
+ const blastScore = clamp01(dependents.length, 50);
5927
+ const complexityScore = clamp01(maxComplexity, 20);
5928
+ const testGapScore = targetHasTests ? 0 : 0.8;
5929
+ const churnScore = clamp01(churnCommits, 30);
5930
+ const publicApiScore = publicApiAffected > 0 ? 0.3 : 0;
5931
+ const riskScore = round(
5932
+ 0.3 * blastScore + 0.2 * complexityScore + 0.2 * testGapScore + 0.15 * churnScore + 0.15 * publicApiScore
5933
+ );
5934
+ const mitigations = [];
5935
+ if (!targetHasTests) mitigations.push("Add test coverage for the target before modifying");
5936
+ if (blastScore > 0.5) mitigations.push(`High blast radius (${dependents.length} files) \u2014 consider incremental rollout`);
5937
+ if (complexityScore > 0.7) mitigations.push("High complexity in dependents \u2014 review carefully for regressions");
5938
+ if (untestedRatio > 0.5) mitigations.push(`${untestedDependents}/${totalFiles} dependents lack tests \u2014 add integration tests`);
5939
+ if (publicApiAffected > 0) mitigations.push(`${publicApiAffected} public API symbol(s) affected \u2014 check for breaking changes`);
5940
+ if (churnCommits > 15) mitigations.push(`High churn (${churnCommits} commits/180d) \u2014 review recent history`);
5941
+ if (coChangeHidden.length > 0) {
5942
+ mitigations.push(`${coChangeHidden.length} hidden coupling(s) via git history: ${coChangeHidden.slice(0, 3).map((c) => c.file).join(", ")}`);
5943
+ }
5944
+ const risk = {
5945
+ score: riskScore,
5946
+ level: riskLevel(riskScore),
5947
+ publicApiBreaking: publicApiAffected > 0,
5948
+ untestedRatio,
5949
+ maxComplexity,
5950
+ mitigations
5951
+ };
5952
+ const modCount = byModule.length;
5953
+ const symCount = dependents.reduce((s, d) => s + (d.symbols?.length ?? 0), 0);
5954
+ const parts = [];
5955
+ parts.push(`${dependents.length} file(s) across ${modCount} module(s)`);
5956
+ if (publicApiAffected > 0) parts.push(`${publicApiAffected} public API`);
5957
+ if (untestedDependents > 0) parts.push(`${untestedDependents} untested`);
5958
+ if (highComplexityDependents > 0) parts.push(`${highComplexityDependents} high-complexity`);
5959
+ if (affectedTests.total > 0) parts.push(`${affectedTests.total} test(s) to run`);
5960
+ if (coChangeHidden.length > 0) parts.push(`${coChangeHidden.length} hidden coupling(s)`);
5961
+ const summary = {
5962
+ totalFiles: dependents.length,
5963
+ totalSymbols: symCount,
5964
+ maxDepth: maxDepthSeen,
5965
+ crossBoundary,
5966
+ publicApiAffected,
5967
+ untestedDependents,
5968
+ highComplexityDependents,
5969
+ sentence: `Impact: ${parts.join(", ")}.${risk.level !== "low" ? ` Risk: ${risk.level}.` : ""}`
5970
+ };
5971
+ const breakingChanges = detectBreakingChanges(store, targetPath, scopedToSymbols);
5972
+ if (breakingChanges.length > 0) {
5973
+ const totalConsumers = breakingChanges.reduce((s, b) => s + b.consumers, 0);
5974
+ mitigations.push(`${breakingChanges.length} exported symbol(s) with ${totalConsumers} consumer(s) \u2014 signature change = breaking`);
5975
+ parts.push(`${breakingChanges.length} breaking risk(s)`);
5976
+ summary.sentence = `Impact: ${parts.join(", ")}.${risk.level !== "low" ? ` Risk: ${risk.level}.` : ""}`;
5977
+ }
5978
+ const result = {
5979
+ target: { path: targetPath, symbolId: targetSymbolId, symbolName: targetSymbolName, kind: targetKind },
5980
+ summary,
5981
+ risk,
5982
+ affectedTests,
5540
5983
  dependents,
5541
- totalAffected: dependents.length,
5542
- ...truncated ? { truncated: true } : {},
5543
- ...pennant ? { pennant } : {}
5544
- });
5984
+ totalAffected: dependents.length
5985
+ };
5986
+ if (breakingChanges.length > 0) result.breakingChanges = breakingChanges;
5987
+ if (byModule.length > 0) result.byModule = byModule;
5988
+ if (Object.keys(byEdgeType).length > 0) result.byEdgeType = byEdgeType;
5989
+ if (Object.keys(byDepth).length > 0) result.byDepth = byDepth;
5990
+ if (coChangeHidden.length > 0) result.coChangeHidden = coChangeHidden;
5991
+ if (truncated) result.truncated = true;
5992
+ if (pennant) result.pennant = pennant;
5993
+ if (scopedToSymbols) result.scopedToSymbols = scopedToSymbols;
5994
+ return ok(result);
5545
5995
  }
5546
- function traverseIncoming(store, startNodeId, _currentDepth, maxDepth, visited, dependents, maxDependents) {
5547
- let frontier = [startNodeId];
5996
+ function deduplicateByFile(rawDeps) {
5997
+ const fileMap = /* @__PURE__ */ new Map();
5998
+ for (const raw of rawDeps) {
5999
+ let entry = fileMap.get(raw.path);
6000
+ if (!entry) {
6001
+ entry = { edgeTypes: /* @__PURE__ */ new Set(), depth: raw.depth, hasTests: raw.hasTests, symbols: [] };
6002
+ fileMap.set(raw.path, entry);
6003
+ }
6004
+ entry.edgeTypes.add(raw.edgeType);
6005
+ entry.depth = Math.min(entry.depth, raw.depth);
6006
+ if (raw.hasTests != null) entry.hasTests = raw.hasTests;
6007
+ if (raw.symbolId && raw.symbolName && raw.symbolKind) {
6008
+ if (!entry.symbols.some((s) => s.symbolId === raw.symbolId)) {
6009
+ const sym = {
6010
+ symbolId: raw.symbolId,
6011
+ symbolName: raw.symbolName,
6012
+ symbolKind: raw.symbolKind
6013
+ };
6014
+ if (raw.complexity != null) sym.complexity = raw.complexity;
6015
+ if (raw.isExported != null) sym.isExported = raw.isExported;
6016
+ entry.symbols.push(sym);
6017
+ }
6018
+ }
6019
+ }
6020
+ const result = [];
6021
+ for (const [path98, entry] of fileMap) {
6022
+ const dep = {
6023
+ path: path98,
6024
+ edgeTypes: [...entry.edgeTypes],
6025
+ depth: entry.depth
6026
+ };
6027
+ if (entry.hasTests != null) dep.hasTests = entry.hasTests;
6028
+ if (entry.symbols.length > 0) dep.symbols = entry.symbols;
6029
+ result.push(dep);
6030
+ }
6031
+ result.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
6032
+ return result;
6033
+ }
6034
+ function traverseIncoming(store, startNodeIds, maxDependents, maxDepth, visited, rawDeps, testedFileIds) {
6035
+ let frontier = startNodeIds;
5548
6036
  for (let depth = 1; depth <= maxDepth; depth++) {
5549
- if (frontier.length === 0 || dependents.length >= maxDependents) break;
6037
+ if (frontier.length === 0 || rawDeps.length >= maxDependents) break;
5550
6038
  const allEdges = store.getEdgesForNodesBatch(frontier);
5551
6039
  const frontierSet = new Set(frontier);
5552
6040
  const newFrontier = [];
5553
6041
  const sourceNodeIds = [];
5554
6042
  const edgeBySource = /* @__PURE__ */ new Map();
5555
6043
  for (const edge of allEdges) {
5556
- if (dependents.length + sourceNodeIds.length >= maxDependents) break;
6044
+ if (rawDeps.length + sourceNodeIds.length >= maxDependents) break;
5557
6045
  if (!frontierSet.has(edge.target_node_id)) continue;
5558
6046
  if (edge.source_node_id === edge.target_node_id) continue;
5559
6047
  const srcId = edge.source_node_id;
@@ -5576,33 +6064,113 @@ function traverseIncoming(store, startNodeId, _currentDepth, maxDepth, visited,
5576
6064
  const symFileIds = /* @__PURE__ */ new Set();
5577
6065
  for (const sym of symbolMap.values()) symFileIds.add(sym.file_id);
5578
6066
  const symFiles = symFileIds.size > 0 ? store.getFilesByIds([...symFileIds]) : /* @__PURE__ */ new Map();
6067
+ const complexityMap = /* @__PURE__ */ new Map();
6068
+ if (symbolIds.length > 0) {
6069
+ const placeholders = symbolIds.map(() => "?").join(",");
6070
+ const rows = store.db.prepare(
6071
+ `SELECT id, cyclomatic FROM symbols WHERE id IN (${placeholders}) AND cyclomatic IS NOT NULL`
6072
+ ).all(...symbolIds);
6073
+ for (const r of rows) complexityMap.set(r.id, r.cyclomatic);
6074
+ }
5579
6075
  for (const srcId of sourceNodeIds) {
5580
- if (dependents.length >= maxDependents) break;
6076
+ if (rawDeps.length >= maxDependents) break;
5581
6077
  const ref = nodeRefs.get(srcId);
5582
6078
  if (!ref) continue;
5583
6079
  let filePath;
5584
6080
  let symbolId;
6081
+ let symbolName;
6082
+ let symbolKind;
6083
+ let complexity;
6084
+ let isExported2;
6085
+ let fileId;
5585
6086
  if (ref.nodeType === "symbol") {
5586
6087
  const sym = symbolMap.get(ref.refId);
5587
6088
  if (sym) {
5588
6089
  symbolId = sym.symbol_id;
6090
+ symbolName = sym.name;
6091
+ symbolKind = sym.kind;
5589
6092
  filePath = symFiles.get(sym.file_id)?.path;
6093
+ fileId = sym.file_id;
6094
+ complexity = complexityMap.get(sym.id);
6095
+ if (sym.metadata) {
6096
+ try {
6097
+ const meta = JSON.parse(sym.metadata);
6098
+ isExported2 = meta.exported === true || meta.exported === 1;
6099
+ } catch {
6100
+ }
6101
+ }
5590
6102
  }
5591
6103
  } else if (ref.nodeType === "file") {
5592
- filePath = fileMap.get(ref.refId)?.path;
6104
+ const f = fileMap.get(ref.refId);
6105
+ filePath = f?.path;
6106
+ fileId = f?.id;
5593
6107
  }
5594
6108
  if (filePath) {
5595
- dependents.push({
6109
+ rawDeps.push({
5596
6110
  path: filePath,
5597
- symbolId,
5598
6111
  edgeType: edgeBySource.get(srcId) ?? "unknown",
5599
- depth
6112
+ depth,
6113
+ symbolId,
6114
+ symbolName,
6115
+ symbolKind,
6116
+ complexity,
6117
+ hasTests: fileId != null ? testedFileIds.has(fileId) : void 0,
6118
+ isExported: isExported2,
6119
+ fileId
5600
6120
  });
5601
6121
  }
5602
6122
  }
5603
6123
  frontier = newFrontier;
5604
6124
  }
5605
6125
  }
6126
+ function detectBreakingChanges(store, targetPath, scopedSymbolIds) {
6127
+ const file = store.getFile(targetPath);
6128
+ if (!file) return [];
6129
+ const symbols = store.getSymbolsByFile(file.id);
6130
+ const breaking = [];
6131
+ for (const sym of symbols) {
6132
+ if (!sym.metadata) continue;
6133
+ let meta;
6134
+ try {
6135
+ meta = JSON.parse(sym.metadata);
6136
+ } catch {
6137
+ continue;
6138
+ }
6139
+ if (meta.exported !== true && meta.exported !== 1) continue;
6140
+ if (scopedSymbolIds && !scopedSymbolIds.includes(sym.symbol_id)) continue;
6141
+ const nodeId = store.getNodeId("symbol", sym.id);
6142
+ if (nodeId == null) continue;
6143
+ const incoming = store.getIncomingEdges(nodeId);
6144
+ const consumerFiles = /* @__PURE__ */ new Set();
6145
+ for (const edge of incoming) {
6146
+ if (edge.edge_type_name === "test_covers") continue;
6147
+ const ref = store.getNodeRef(edge.source_node_id);
6148
+ if (!ref) continue;
6149
+ if (ref.nodeType === "symbol") {
6150
+ const s = store.getSymbolById(ref.refId);
6151
+ if (s) {
6152
+ const f = store.getFileById(s.file_id);
6153
+ if (f && f.path !== targetPath) consumerFiles.add(f.path);
6154
+ }
6155
+ } else if (ref.nodeType === "file") {
6156
+ const f = store.getFileById(ref.refId);
6157
+ if (f && f.path !== targetPath) consumerFiles.add(f.path);
6158
+ }
6159
+ }
6160
+ if (consumerFiles.size > 0) {
6161
+ breaking.push({
6162
+ symbolId: sym.symbol_id,
6163
+ symbolName: sym.name,
6164
+ kind: sym.kind,
6165
+ consumers: consumerFiles.size,
6166
+ consumerFiles: [...consumerFiles].slice(0, 10)
6167
+ // cap for readability
6168
+ });
6169
+ }
6170
+ }
6171
+ breaking.sort((a, b) => b.consumers - a.consumers);
6172
+ return breaking;
6173
+ }
5606
6174
  function getPennantImpact(store, name) {
5607
6175
  if (!name) return null;
5608
6176
  const definedIn = [];
@@ -6462,166 +7030,6 @@ var GitignoreMatcher = class {
6462
7030
 
6463
7031
  // src/tools/history.ts
6464
7032
  import { execFileSync as execFileSync2 } from "child_process";
6465
-
6466
- // src/tools/git-analysis.ts
6467
- import { execFileSync } from "child_process";
6468
- function isGitRepo(cwd) {
6469
- try {
6470
- execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
6471
- cwd,
6472
- stdio: "pipe",
6473
- timeout: 5e3
6474
- });
6475
- return true;
6476
- } catch {
6477
- return false;
6478
- }
6479
- }
6480
- function getGitFileStats(cwd, sinceDays) {
6481
- const args = [
6482
- "log",
6483
- "--pretty=format:__COMMIT__%H|%aI|%aN",
6484
- "--name-only",
6485
- "--no-merges",
6486
- "--diff-filter=ACDMR"
6487
- ];
6488
- if (sinceDays !== void 0) {
6489
- args.push(`--since=${sinceDays} days ago`);
6490
- }
6491
- let output;
6492
- try {
6493
- output = execFileSync("git", args, {
6494
- cwd,
6495
- stdio: "pipe",
6496
- maxBuffer: 10 * 1024 * 1024,
6497
- // 10 MB
6498
- timeout: 3e4
6499
- }).toString("utf-8");
6500
- } catch (e) {
6501
- logger.warn({ error: e }, "git log failed");
6502
- return /* @__PURE__ */ new Map();
6503
- }
6504
- const fileStats = /* @__PURE__ */ new Map();
6505
- let currentDate = null;
6506
- let currentAuthor = null;
6507
- for (const line of output.split("\n")) {
6508
- if (line.startsWith("__COMMIT__")) {
6509
- const parts = line.slice("__COMMIT__".length).split("|");
6510
- currentDate = new Date(parts[1]);
6511
- currentAuthor = parts[2];
6512
- continue;
6513
- }
6514
- const trimmed = line.trim();
6515
- if (!trimmed || !currentDate || !currentAuthor) continue;
6516
- const existing = fileStats.get(trimmed);
6517
- if (existing) {
6518
- existing.commits++;
6519
- existing.authors.add(currentAuthor);
6520
- if (currentDate < existing.firstDate) existing.firstDate = currentDate;
6521
- if (currentDate > existing.lastDate) existing.lastDate = currentDate;
6522
- } else {
6523
- fileStats.set(trimmed, {
6524
- file: trimmed,
6525
- commits: 1,
6526
- authors: /* @__PURE__ */ new Set([currentAuthor]),
6527
- firstDate: currentDate,
6528
- lastDate: currentDate
6529
- });
6530
- }
6531
- }
6532
- return fileStats;
6533
- }
6534
- function getChurnRate(cwd, options = {}) {
6535
- const { sinceDays, limit = 50, filePattern } = options;
6536
- if (!isGitRepo(cwd)) {
6537
- return [];
6538
- }
6539
- const stats = getGitFileStats(cwd, sinceDays);
6540
- let entries = [];
6541
- for (const [file, data] of stats) {
6542
- if (filePattern && !file.includes(filePattern)) continue;
6543
- const lifespanMs = data.lastDate.getTime() - data.firstDate.getTime();
6544
- const lifespanWeeks = Math.max(lifespanMs / (7 * 24 * 60 * 60 * 1e3), 1);
6545
- const churnPerWeek = Math.round(data.commits / lifespanWeeks * 100) / 100;
6546
- let assessment;
6547
- if (churnPerWeek <= 1) assessment = "stable";
6548
- else if (churnPerWeek <= 3) assessment = "active";
6549
- else assessment = "volatile";
6550
- entries.push({
6551
- file,
6552
- commits: data.commits,
6553
- unique_authors: data.authors.size,
6554
- first_seen: data.firstDate.toISOString().split("T")[0],
6555
- last_modified: data.lastDate.toISOString().split("T")[0],
6556
- churn_per_week: churnPerWeek,
6557
- assessment
6558
- });
6559
- }
6560
- entries.sort((a, b) => b.commits - a.commits);
6561
- return entries.slice(0, limit);
6562
- }
6563
- function getHotspots(store, cwd, options = {}) {
6564
- const { sinceDays = 90, limit = 20, minCyclomatic = 3 } = options;
6565
- if (!isGitRepo(cwd)) {
6566
- return getComplexityOnlyHotspots(store, limit, minCyclomatic);
6567
- }
6568
- const gitStats = getGitFileStats(cwd, sinceDays);
6569
- const fileComplexity = getMaxCyclomaticPerFile(store);
6570
- const entries = [];
6571
- for (const [file, maxCyclomatic] of fileComplexity) {
6572
- if (maxCyclomatic < minCyclomatic) continue;
6573
- const git = gitStats.get(file);
6574
- const commits = git?.commits ?? 0;
6575
- const score = Math.round(maxCyclomatic * Math.log(1 + commits) * 100) / 100;
6576
- if (score <= 0) continue;
6577
- let assessment;
6578
- if (score <= 3) assessment = "low";
6579
- else if (score <= 10) assessment = "medium";
6580
- else assessment = "high";
6581
- entries.push({
6582
- file,
6583
- max_cyclomatic: maxCyclomatic,
6584
- commits,
6585
- score,
6586
- assessment
6587
- });
6588
- }
6589
- entries.sort((a, b) => b.score - a.score);
6590
- return entries.slice(0, limit);
6591
- }
6592
- function getMaxCyclomaticPerFile(store) {
6593
- const rows = store.db.prepare(`
6594
- SELECT f.path, MAX(s.cyclomatic) as max_cyclomatic
6595
- FROM symbols s
6596
- JOIN files f ON s.file_id = f.id
6597
- WHERE s.cyclomatic IS NOT NULL
6598
- GROUP BY f.path
6599
- `).all();
6600
- const result = /* @__PURE__ */ new Map();
6601
- for (const row of rows) {
6602
- result.set(row.path, row.max_cyclomatic);
6603
- }
6604
- return result;
6605
- }
6606
- function getComplexityOnlyHotspots(store, limit, minCyclomatic) {
6607
- const fileComplexity = getMaxCyclomaticPerFile(store);
6608
- const entries = [];
6609
- for (const [file, maxCyclomatic] of fileComplexity) {
6610
- if (maxCyclomatic < minCyclomatic) continue;
6611
- entries.push({
6612
- file,
6613
- max_cyclomatic: maxCyclomatic,
6614
- commits: 0,
6615
- score: maxCyclomatic,
6616
- // score = complexity alone
6617
- assessment: maxCyclomatic <= 3 ? "low" : maxCyclomatic <= 10 ? "medium" : "high"
6618
- });
6619
- }
6620
- entries.sort((a, b) => b.score - a.score);
6621
- return entries.slice(0, limit);
6622
- }
6623
-
6624
- // src/tools/history.ts
6625
7033
  function sampleFileCommits(cwd, filePath, sinceDays, count) {
6626
7034
  const args = [
6627
7035
  "log",
@@ -9091,7 +9499,7 @@ function registerAITools(server, ctx) {
9091
9499
  },
9092
9500
  async ({ file_path, diff }) => {
9093
9501
  const impactResult = getChangeImpact(store, { filePath: file_path });
9094
- const blastRadius = impactResult.isOk() ? impactResult.value.dependents.map((d) => `${d.edgeType}: ${d.path}`).join("\n") : "";
9502
+ const blastRadius = impactResult.isOk() ? impactResult.value.dependents.map((d) => `${d.edgeTypes.join(", ")}: ${d.path}`).join("\n") : "";
9095
9503
  const prompt = PROMPTS.review_change.build({
9096
9504
  filePath: file_path,
9097
9505
  diff: diff ?? "",
@@ -10540,7 +10948,14 @@ var CALL_EDGE_TYPES = /* @__PURE__ */ new Set([
10540
10948
  "routes_to",
10541
10949
  "validates_with",
10542
10950
  "nest_injects",
10543
- "graphql_resolves"
10951
+ "graphql_resolves",
10952
+ // Import-based edges (fallback when no call edges exist)
10953
+ "esm_imports",
10954
+ "imports",
10955
+ "uses",
10956
+ // Component/rendering edges
10957
+ "renders_component",
10958
+ "uses_composable"
10544
10959
  ]);
10545
10960
  var MAX_DEPTH = 10;
10546
10961
  function getCallGraph(store, opts, depth = 2) {
@@ -12853,6 +13268,10 @@ async function getTaskContext(store, rootPath, opts, ai) {
12853
13268
  const recency = computeRecency(file.indexed_at, now);
12854
13269
  const typeBonus = getTypeBonus(sym.kind);
12855
13270
  let score = hybridScore({ relevance, pagerank: pr, recency, typeBonus });
13271
+ const NON_CODE_LANGUAGES = /* @__PURE__ */ new Set(["markdown", "yaml", "json", "toml", "xml", "html", "csv", "text", "ini"]);
13272
+ if (file.language && NON_CODE_LANGUAGES.has(file.language.toLowerCase())) {
13273
+ score *= 0.2;
13274
+ }
12856
13275
  if (walkInfo.depth > 0) {
12857
13276
  score *= 1 / (1 + 0.3 * walkInfo.depth);
12858
13277
  }
@@ -13117,7 +13536,7 @@ function rankPercentile(values) {
13117
13536
  function clampNormalize(value, ceiling) {
13118
13537
  return Math.min(1, value / ceiling);
13119
13538
  }
13120
- function riskLevel(score) {
13539
+ function riskLevel2(score) {
13121
13540
  if (score < 0.3) return "low";
13122
13541
  if (score < 0.5) return "medium";
13123
13542
  if (score < 0.75) return "high";
@@ -13130,7 +13549,7 @@ function debtGrade(score) {
13130
13549
  if (score < 0.8) return "D";
13131
13550
  return "F";
13132
13551
  }
13133
- function getModule(filePath, depth) {
13552
+ function getModule2(filePath, depth) {
13134
13553
  const parts = filePath.split("/");
13135
13554
  return parts.slice(0, Math.min(depth, parts.length - 1)).join("/") || filePath;
13136
13555
  }
@@ -13187,14 +13606,14 @@ function predictBugs(store, cwd, options = {}) {
13187
13606
  predictions.push({
13188
13607
  file,
13189
13608
  score: Math.round(score * 1e3) / 1e3,
13190
- risk: riskLevel(score),
13609
+ risk: riskLevel2(score),
13191
13610
  factors: [
13192
- { signal: "churn", raw_value: round(git?.churnPerWeek ?? 0), normalized: round(sChurn), weight: w.churn, contribution: round(w.churn * sChurn) },
13193
- { signal: "fix_ratio", raw_value: round(git?.fixRatio ?? 0), normalized: round(sFixRatio), weight: w.fix_ratio, contribution: round(w.fix_ratio * sFixRatio) },
13194
- { signal: "complexity", raw_value: complexityMap.get(file) ?? 0, normalized: round(sComplexity), weight: w.complexity, contribution: round(w.complexity * sComplexity) },
13195
- { signal: "coupling", raw_value: round(coupling?.instability ?? 0), normalized: round(sCoupling), weight: w.coupling, contribution: round(w.coupling * sCoupling) },
13196
- { signal: "pagerank", raw_value: round(pagerankMap.get(file)?.score ?? 0), normalized: round(sPagerank), weight: w.pagerank, contribution: round(w.pagerank * sPagerank) },
13197
- { signal: "authors", raw_value: git?.authors ?? 0, normalized: round(sAuthors), weight: w.authors, contribution: round(w.authors * sAuthors) }
13611
+ { signal: "churn", raw_value: round2(git?.churnPerWeek ?? 0), normalized: round2(sChurn), weight: w.churn, contribution: round2(w.churn * sChurn) },
13612
+ { signal: "fix_ratio", raw_value: round2(git?.fixRatio ?? 0), normalized: round2(sFixRatio), weight: w.fix_ratio, contribution: round2(w.fix_ratio * sFixRatio) },
13613
+ { signal: "complexity", raw_value: complexityMap.get(file) ?? 0, normalized: round2(sComplexity), weight: w.complexity, contribution: round2(w.complexity * sComplexity) },
13614
+ { signal: "coupling", raw_value: round2(coupling?.instability ?? 0), normalized: round2(sCoupling), weight: w.coupling, contribution: round2(w.coupling * sCoupling) },
13615
+ { signal: "pagerank", raw_value: round2(pagerankMap.get(file)?.score ?? 0), normalized: round2(sPagerank), weight: w.pagerank, contribution: round2(w.pagerank * sPagerank) },
13616
+ { signal: "authors", raw_value: git?.authors ?? 0, normalized: round2(sAuthors), weight: w.authors, contribution: round2(w.authors * sAuthors) }
13198
13617
  ]
13199
13618
  });
13200
13619
  }
@@ -13229,7 +13648,7 @@ function detectDrift(store, cwd, options = {}) {
13229
13648
  for (const file of files) {
13230
13649
  fileCommitCount.set(file, (fileCommitCount.get(file) ?? 0) + 1);
13231
13650
  fileTotalCount.set(file, (fileTotalCount.get(file) ?? 0) + 1);
13232
- modulesTouched.add(getModule(file, moduleDepth));
13651
+ modulesTouched.add(getModule2(file, moduleDepth));
13233
13652
  }
13234
13653
  const isShotgun = modulesTouched.size >= 3;
13235
13654
  if (isShotgun) {
@@ -13250,8 +13669,8 @@ function detectDrift(store, cwd, options = {}) {
13250
13669
  const anomalies = [];
13251
13670
  for (const [key, count] of coChangeCount) {
13252
13671
  const [fileA, fileB] = key.split("|");
13253
- const moduleA = getModule(fileA, moduleDepth);
13254
- const moduleB = getModule(fileB, moduleDepth);
13672
+ const moduleA = getModule2(fileA, moduleDepth);
13673
+ const moduleB = getModule2(fileB, moduleDepth);
13255
13674
  if (moduleA === moduleB) continue;
13256
13675
  const commitsA = fileCommitCount.get(fileA) ?? 0;
13257
13676
  const commitsB = fileCommitCount.get(fileB) ?? 0;
@@ -13263,7 +13682,7 @@ function detectDrift(store, cwd, options = {}) {
13263
13682
  file_a: fileA,
13264
13683
  file_b: fileB,
13265
13684
  co_change_count: count,
13266
- confidence: round(jaccard2),
13685
+ confidence: round2(jaccard2),
13267
13686
  module_a: moduleA,
13268
13687
  module_b: moduleB
13269
13688
  });
@@ -13278,7 +13697,7 @@ function detectDrift(store, cwd, options = {}) {
13278
13697
  file,
13279
13698
  shotgun_commits: shotgunCommits,
13280
13699
  total_commits: total,
13281
- ratio: round(ratio)
13700
+ ratio: round2(ratio)
13282
13701
  });
13283
13702
  }
13284
13703
  }
@@ -13325,7 +13744,7 @@ function getTechDebt(store, cwd, options = {}) {
13325
13744
  const allFiles = store.getAllFiles();
13326
13745
  const moduleFiles = /* @__PURE__ */ new Map();
13327
13746
  for (const f of allFiles) {
13328
- const mod = getModule(f.path, moduleDepth);
13747
+ const mod = getModule2(f.path, moduleDepth);
13329
13748
  if (!moduleFiles.has(mod)) moduleFiles.set(mod, []);
13330
13749
  moduleFiles.get(mod).push(f.path);
13331
13750
  }
@@ -13361,7 +13780,7 @@ function getTechDebt(store, cwd, options = {}) {
13361
13780
  }
13362
13781
  if (sCoupling > 0.7) {
13363
13782
  recommendations.push({
13364
- action: `Reduce coupling: module has high average instability (${round(sCoupling)})`,
13783
+ action: `Reduce coupling: module has high average instability (${round2(sCoupling)})`,
13365
13784
  target: mod,
13366
13785
  priority: "medium"
13367
13786
  });
@@ -13385,13 +13804,13 @@ function getTechDebt(store, cwd, options = {}) {
13385
13804
  }
13386
13805
  modules.push({
13387
13806
  module: mod,
13388
- score: round(score),
13807
+ score: round2(score),
13389
13808
  grade: debtGrade(score),
13390
13809
  breakdown: {
13391
- complexity: round(sComplexity),
13392
- coupling: round(sCoupling),
13393
- test_gap: round(sTestGap),
13394
- churn: round(sChurn)
13810
+ complexity: round2(sComplexity),
13811
+ coupling: round2(sCoupling),
13812
+ test_gap: round2(sTestGap),
13813
+ churn: round2(sChurn)
13395
13814
  },
13396
13815
  file_count: files.length,
13397
13816
  recommendations
@@ -13401,7 +13820,7 @@ function getTechDebt(store, cwd, options = {}) {
13401
13820
  const totalScore = modules.length > 0 ? modules.reduce((s, m) => s + m.score, 0) / modules.length : 0;
13402
13821
  return ok({
13403
13822
  modules: modules.slice(0, 50),
13404
- project_score: round(totalScore),
13823
+ project_score: round2(totalScore),
13405
13824
  project_grade: debtGrade(totalScore)
13406
13825
  });
13407
13826
  }
@@ -13498,15 +13917,15 @@ function assessChangeRisk(store, cwd, opts) {
13498
13917
  if (sChurn > 0.7) mitigations.push("Frequently changed file \u2014 review recent change history for context");
13499
13918
  return ok({
13500
13919
  target: { file: targetFile, symbol_id: targetSymbolId },
13501
- risk_score: round(riskScore),
13502
- risk_level: riskLevel(riskScore),
13503
- confidence: round(confidence),
13920
+ risk_score: round2(riskScore),
13921
+ risk_level: riskLevel2(riskScore),
13922
+ confidence: round2(confidence),
13504
13923
  factors: [
13505
- { signal: "blast_radius", value: round(sBlast), weight: w.blast_radius, contribution: round(w.blast_radius * sBlast), detail: `${blastFiles} files, ${blastSymbols} symbols in blast radius` },
13506
- { signal: "complexity", value: round(sComplexity), weight: w.complexity, contribution: round(w.complexity * sComplexity), detail: `Max cyclomatic: ${complexityRow?.max_cyc ?? 0}` },
13507
- { signal: "churn", value: round(sChurn), weight: w.churn, contribution: round(w.churn * sChurn), detail: gitAvailable ? `Churn percentile: ${round(sChurn * 100)}%` : "Git unavailable" },
13508
- { signal: "test_gap", value: sTestGap, weight: w.test_gap, contribution: round(w.test_gap * sTestGap), detail: hasTestCoverage ? "Has test coverage" : "No test coverage" },
13509
- { signal: "coupling", value: round(sCoupling), weight: w.coupling, contribution: round(w.coupling * sCoupling), detail: `Instability: ${round(sCoupling)}` }
13924
+ { signal: "blast_radius", value: round2(sBlast), weight: w.blast_radius, contribution: round2(w.blast_radius * sBlast), detail: `${blastFiles} files, ${blastSymbols} symbols in blast radius` },
13925
+ { signal: "complexity", value: round2(sComplexity), weight: w.complexity, contribution: round2(w.complexity * sComplexity), detail: `Max cyclomatic: ${complexityRow?.max_cyc ?? 0}` },
13926
+ { signal: "churn", value: round2(sChurn), weight: w.churn, contribution: round2(w.churn * sChurn), detail: gitAvailable ? `Churn percentile: ${round2(sChurn * 100)}%` : "Git unavailable" },
13927
+ { signal: "test_gap", value: sTestGap, weight: w.test_gap, contribution: round2(w.test_gap * sTestGap), detail: hasTestCoverage ? "Has test coverage" : "No test coverage" },
13928
+ { signal: "coupling", value: round2(sCoupling), weight: w.coupling, contribution: round2(w.coupling * sCoupling), detail: `Instability: ${round2(sCoupling)}` }
13510
13929
  ],
13511
13930
  mitigations,
13512
13931
  blast_radius: { files: blastFiles, symbols: blastSymbols }
@@ -13589,7 +14008,7 @@ function getCachedBugPredictions(store, limit, minScore, filePattern, ttlMs = 60
13589
14008
  predictions: rows.map((r) => ({
13590
14009
  file: r.file_path,
13591
14010
  score: r.score,
13592
- risk: riskLevel(r.score),
14011
+ risk: riskLevel2(r.score),
13593
14012
  factors: JSON.parse(r.factors || "[]")
13594
14013
  })),
13595
14014
  total_files_analyzed: snapshotFull?.file_count ?? rows.length,
@@ -13659,7 +14078,7 @@ function saveBugPredictionCache(store, predictions, cwd) {
13659
14078
  return null;
13660
14079
  }
13661
14080
  }
13662
- function round(v, decimals = 3) {
14081
+ function round2(v, decimals = 3) {
13663
14082
  const m = 10 ** decimals;
13664
14083
  return Math.round(v * m) / m;
13665
14084
  }
@@ -14534,16 +14953,16 @@ function findShortestPath(store, startNodeId, endNodeId, maxDepth) {
14534
14953
  parent.set(nodeId, { from, edgeType: edge.edge_type_name });
14535
14954
  nextFrontier.push(nodeId);
14536
14955
  if (nodeId === endNodeId) {
14537
- const path89 = [endNodeId];
14956
+ const path98 = [endNodeId];
14538
14957
  const edgeTypes = [];
14539
14958
  let cur = endNodeId;
14540
14959
  while (cur !== startNodeId) {
14541
14960
  const p4 = parent.get(cur);
14542
- path89.unshift(p4.from);
14961
+ path98.unshift(p4.from);
14543
14962
  edgeTypes.unshift(p4.edgeType);
14544
14963
  cur = p4.from;
14545
14964
  }
14546
- return { path: path89, edgeTypes };
14965
+ return { path: path98, edgeTypes };
14547
14966
  }
14548
14967
  }
14549
14968
  }
@@ -15494,7 +15913,7 @@ function searchText(store, projectRoot, opts) {
15494
15913
  filePattern,
15495
15914
  language,
15496
15915
  maxResults = 50,
15497
- contextLines = 2,
15916
+ contextLines = 0,
15498
15917
  caseSensitive = false
15499
15918
  } = opts;
15500
15919
  if (!query || query.length === 0) {
@@ -15517,7 +15936,7 @@ function searchText(store, projectRoot, opts) {
15517
15936
  files = store.db.prepare("SELECT * FROM files WHERE status != ?").all("error");
15518
15937
  }
15519
15938
  if (filePattern) {
15520
- const isMatch = (0, import_picomatch.default)(filePattern, { matchBase: true });
15939
+ const isMatch = (0, import_picomatch.default)(filePattern, { dot: true });
15521
15940
  files = files.filter((f) => isMatch(f.path));
15522
15941
  }
15523
15942
  const matches = [];
@@ -15631,7 +16050,7 @@ function buildGraphData(store, opts) {
15631
16050
  } else {
15632
16051
  const allFiles = store.getAllFiles();
15633
16052
  if (scope.includes("*")) {
15634
- const isMatch = (0, import_picomatch2.default)(scope, { matchBase: true });
16053
+ const isMatch = (0, import_picomatch2.default)(scope, { dot: true });
15635
16054
  seedFiles = allFiles.filter((f) => isMatch(f.path));
15636
16055
  } else if (scope.endsWith("/") || !scope.includes(".")) {
15637
16056
  seedFiles = allFiles.filter((f) => f.path.startsWith(scope.replace(/\/$/, "")));
@@ -16977,11 +17396,11 @@ function getCrossServiceImpact(topoStore, projectRoot, additionalRepos, opts) {
16977
17396
  });
16978
17397
  }
16979
17398
  }
16980
- const riskLevel2 = affected.length >= 3 ? "high" : affected.length >= 1 ? "medium" : "low";
17399
+ const riskLevel3 = affected.length >= 3 ? "high" : affected.length >= 1 ? "medium" : "low";
16981
17400
  return ok({
16982
17401
  target: { service: opts.service, endpoint: opts.endpoint, event: opts.event },
16983
17402
  affected_services: affected,
16984
- risk_level: riskLevel2
17403
+ risk_level: riskLevel3
16985
17404
  });
16986
17405
  }
16987
17406
  function getApiContract(topoStore, projectRoot, additionalRepos, opts) {
@@ -17575,7 +17994,7 @@ var FederationManager = class {
17575
17994
  }
17576
17995
  }
17577
17996
  const uniqueRepos = new Set(clients.map((c) => c.repo));
17578
- const riskLevel2 = uniqueRepos.size >= 3 ? "critical" : uniqueRepos.size >= 2 ? "high" : clients.length >= 3 ? "medium" : "low";
17997
+ const riskLevel3 = uniqueRepos.size >= 3 ? "critical" : uniqueRepos.size >= 2 ? "high" : clients.length >= 3 ? "medium" : "low";
17579
17998
  const svc = this.topoStore.getAllServices().find((s) => s.id === ep.service_id);
17580
17999
  const repo = svc ? this.topoStore.getFederatedRepo(svc.repo_root) : void 0;
17581
18000
  results.push({
@@ -17586,7 +18005,7 @@ var FederationManager = class {
17586
18005
  repo: repo?.name ?? svc?.repo_root ?? "unknown"
17587
18006
  },
17588
18007
  clients,
17589
- riskLevel: riskLevel2,
18008
+ riskLevel: riskLevel3,
17590
18009
  summary: `${ep.method ?? "*"} ${ep.path} is called by ${clients.length} client(s) in ${uniqueRepos.size} repo(s)`
17591
18010
  });
17592
18011
  }
@@ -18082,6 +18501,34 @@ var hintGenerators = {
18082
18501
  }
18083
18502
  }
18084
18503
  return hints;
18504
+ },
18505
+ batch(r) {
18506
+ const hints = [];
18507
+ const results = arr(dig(r, "batch_results"));
18508
+ if (results.length > 0) {
18509
+ hints.push({ tool: "get_session_stats", args: {}, why: "Check token savings from this batch vs individual calls" });
18510
+ }
18511
+ return hints;
18512
+ },
18513
+ get_optimization_report(r) {
18514
+ const hints = [];
18515
+ const opts = arr(dig(r, "optimizations"));
18516
+ const hasRepeated = opts.some((o) => str(o?.rule) === "repeated-file-read");
18517
+ if (hasRepeated) {
18518
+ hints.push({ tool: "get_outline", args: { path: "<frequently_read_file>" }, why: "Use get_outline + get_symbol instead of repeated full-file reads" });
18519
+ }
18520
+ hints.push({ tool: "get_real_savings", args: { period: "today" }, why: "See actual per-file token savings breakdown" });
18521
+ return hints;
18522
+ },
18523
+ get_real_savings(r) {
18524
+ const hints = [];
18525
+ hints.push({ tool: "get_session_stats", args: {}, why: "See per-tool call counts and savings for this session" });
18526
+ return hints;
18527
+ },
18528
+ get_session_stats(r) {
18529
+ const hints = [];
18530
+ hints.push({ tool: "get_optimization_report", args: { period: "today" }, why: "Find specific waste patterns to fix" });
18531
+ return hints;
18085
18532
  }
18086
18533
  };
18087
18534
  function getHints(toolName, result, max = 3) {
@@ -20557,14 +21004,14 @@ function generatePrTemplate(input, affected, risk) {
20557
21004
  }
20558
21005
 
20559
21006
  // src/tools/co-changes.ts
20560
- import { execSync } from "child_process";
21007
+ import { execSync as execSync2 } from "child_process";
20561
21008
  function collectCoChanges(rootPath, windowDays = 180) {
20562
21009
  const sinceDate = /* @__PURE__ */ new Date();
20563
21010
  sinceDate.setDate(sinceDate.getDate() - windowDays);
20564
21011
  const since = sinceDate.toISOString().split("T")[0];
20565
21012
  let gitOutput;
20566
21013
  try {
20567
- gitOutput = execSync(
21014
+ gitOutput = execSync2(
20568
21015
  `git log --name-only --pretty=format:"COMMIT:%H:%aI" --since="${since}" --diff-filter=AMRD`,
20569
21016
  { cwd: rootPath, maxBuffer: 50 * 1024 * 1024, encoding: "utf-8", timeout: 3e4 }
20570
21017
  );
@@ -20676,12 +21123,12 @@ function getCoChanges(store, opts) {
20676
21123
  }
20677
21124
 
20678
21125
  // src/tools/changed-symbols.ts
20679
- import { execSync as execSync2 } from "child_process";
21126
+ import { execSync as execSync3 } from "child_process";
20680
21127
  function getChangedSymbols(store, rootPath, opts) {
20681
21128
  const until = opts.until ?? "HEAD";
20682
21129
  let diffNameStatus;
20683
21130
  try {
20684
- diffNameStatus = execSync2(
21131
+ diffNameStatus = execSync3(
20685
21132
  `git diff --name-status --diff-filter=AMRD ${opts.since}..${until}`,
20686
21133
  { cwd: rootPath, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, timeout: 15e3 }
20687
21134
  ).trim();
@@ -20707,7 +21154,7 @@ function getChangedSymbols(store, rootPath, opts) {
20707
21154
  }
20708
21155
  let diffUnified = "";
20709
21156
  try {
20710
- diffUnified = execSync2(
21157
+ diffUnified = execSync3(
20711
21158
  `git diff --unified=0 ${opts.since}..${until}`,
20712
21159
  { cwd: rootPath, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024, timeout: 3e4 }
20713
21160
  );
@@ -20828,14 +21275,14 @@ function compareBranches(store, rootPath, opts) {
20828
21275
  const branch = opts.branch;
20829
21276
  let mergeBase;
20830
21277
  try {
20831
- mergeBase = execSync2(
21278
+ mergeBase = execSync3(
20832
21279
  `git merge-base ${base} ${branch}`,
20833
21280
  { cwd: rootPath, encoding: "utf-8", timeout: 1e4 }
20834
21281
  ).trim();
20835
21282
  } catch (e) {
20836
21283
  if (base === "main") {
20837
21284
  try {
20838
- mergeBase = execSync2(
21285
+ mergeBase = execSync3(
20839
21286
  `git merge-base master ${branch}`,
20840
21287
  { cwd: rootPath, encoding: "utf-8", timeout: 1e4 }
20841
21288
  ).trim();
@@ -20848,7 +21295,7 @@ function compareBranches(store, rootPath, opts) {
20848
21295
  }
20849
21296
  let commitCount = 0;
20850
21297
  try {
20851
- const countOutput = execSync2(
21298
+ const countOutput = execSync3(
20852
21299
  `git rev-list --count ${mergeBase}..${branch}`,
20853
21300
  { cwd: rootPath, encoding: "utf-8", timeout: 1e4 }
20854
21301
  ).trim();
@@ -20906,6 +21353,7 @@ var SAVINGS_PATH = path33.join(TRACE_MCP_HOME, "savings.json");
20906
21353
  var RAW_COST_ESTIMATES = {
20907
21354
  get_symbol: 800,
20908
21355
  search: 600,
21356
+ search_text: 3e3,
20909
21357
  get_outline: 1200,
20910
21358
  get_change_impact: 2e3,
20911
21359
  get_feature_context: 4e3,
@@ -21070,8 +21518,8 @@ var SessionJournal = class {
21070
21518
  };
21071
21519
  this.entries.push(entry);
21072
21520
  if (tool === "get_symbol" || tool === "get_outline") {
21073
- const path89 = params.path ?? params.file_path ?? "";
21074
- if (path89) this.filesRead.add(path89);
21521
+ const path98 = params.path ?? params.file_path ?? "";
21522
+ if (path98) this.filesRead.add(path98);
21075
21523
  }
21076
21524
  if (resultCount === 0 && this.isSearchTool(tool)) {
21077
21525
  this.zeroResultQueries.set(hash, summary);
@@ -22299,6 +22747,40 @@ function benchmarkCallGraph(store, symbols, count, rand) {
22299
22747
  });
22300
22748
  return buildScenario("call_graph", "Trace call graph (baseline: read all caller/callee files)", details);
22301
22749
  }
22750
+ function benchmarkTaskContext(store, symbols, files, count, rand) {
22751
+ const sampled = sample(symbols.filter((s) => s.kind === "function" || s.kind === "class"), count, rand);
22752
+ const details = sampled.map((s) => {
22753
+ const nodeRow = store.db.prepare(
22754
+ "SELECT n.id FROM nodes n JOIN symbols sym ON n.ref_id = sym.id AND n.node_type = ? WHERE sym.symbol_id = ?"
22755
+ ).get("symbol", s.symbol_id);
22756
+ let relatedFileBytes = 0;
22757
+ let relatedFileCount = 0;
22758
+ if (nodeRow) {
22759
+ const related = store.db.prepare(`
22760
+ SELECT DISTINCT f.byte_length FROM (
22761
+ SELECT source_node_id AS nid FROM edges WHERE target_node_id = ?
22762
+ UNION
22763
+ SELECT target_node_id AS nid FROM edges WHERE source_node_id = ?
22764
+ ) r
22765
+ JOIN nodes n2 ON r.nid = n2.id AND n2.node_type = 'symbol'
22766
+ JOIN symbols s2 ON n2.ref_id = s2.id
22767
+ JOIN files f ON s2.file_id = f.id
22768
+ LIMIT 10
22769
+ `).all(nodeRow.id, nodeRow.id);
22770
+ relatedFileBytes = related.reduce((sum, d) => sum + (d.byte_length || 0), 0);
22771
+ relatedFileCount = related.length;
22772
+ }
22773
+ const targetFileBytes = s.file_byte_length;
22774
+ const additionalFiles = Math.min(relatedFileCount, 5);
22775
+ const avgRelatedSize = relatedFileCount > 0 ? relatedFileBytes / relatedFileCount : s.file_byte_length;
22776
+ const totalReadBytes = targetFileBytes + additionalFiles * avgRelatedSize;
22777
+ const grepOverhead = 2 * 5 * 20 * 80;
22778
+ const bl = estimateTokens2(totalReadBytes + grepOverhead);
22779
+ const tm = estimateTokens2(Math.round(totalReadBytes * 0.08));
22780
+ return { query: `task: understand ${s.name}`, file: s.file_path, baseline_tokens: bl, trace_mcp_tokens: tm, reduction_pct: reductionPct(bl, tm) };
22781
+ });
22782
+ return buildScenario("composite_task", "NL task \u2192 optimal code context (baseline: search + read 5-8 files + grep)", details);
22783
+ }
22302
22784
  function runBenchmark(store, opts = {}) {
22303
22785
  const n = opts.queries ?? 10;
22304
22786
  const rand = seededRandom(opts.seed ?? 42);
@@ -22309,7 +22791,8 @@ function runBenchmark(store, opts = {}) {
22309
22791
  benchmarkFileExploration(files, n, rand),
22310
22792
  benchmarkSearch(symbols, n, rand),
22311
22793
  benchmarkImpactAnalysis(store, symbols, n, rand),
22312
- benchmarkCallGraph(store, symbols, n, rand)
22794
+ benchmarkCallGraph(store, symbols, n, rand),
22795
+ benchmarkTaskContext(store, symbols, files, n, rand)
22313
22796
  ];
22314
22797
  const totalQueries = scenarios.reduce((s, sc) => s + sc.queries, 0);
22315
22798
  const totalBaseline = scenarios.reduce((s, sc) => s + sc.baseline_tokens, 0);
@@ -23535,8 +24018,8 @@ function registerPrompts(server, ctx) {
23535
24018
  `);
23536
24019
  let changedFiles = [];
23537
24020
  try {
23538
- const { execSync: execSync3 } = await import("child_process");
23539
- const diff = execSync3(`git diff --name-only ${baseRef}...${branch}`, {
24021
+ const { execSync: execSync4 } = await import("child_process");
24022
+ const diff = execSync4(`git diff --name-only ${baseRef}...${branch}`, {
23540
24023
  cwd: projectRoot,
23541
24024
  encoding: "utf-8",
23542
24025
  timeout: 1e4
@@ -23757,8 +24240,8 @@ Analyze this project's architecture health. Identify the most critical issues an
23757
24240
  `);
23758
24241
  let changedFiles = [];
23759
24242
  try {
23760
- const { execSync: execSync3 } = await import("child_process");
23761
- const diff = execSync3(`git diff --name-only ${baseRef}...${branch}`, {
24243
+ const { execSync: execSync4 } = await import("child_process");
24244
+ const diff = execSync4(`git diff --name-only ${baseRef}...${branch}`, {
23762
24245
  cwd: projectRoot,
23763
24246
  encoding: "utf-8",
23764
24247
  timeout: 1e4
@@ -24895,7 +25378,7 @@ var FileWatcher = class {
24895
25378
  var require2 = createRequire(import.meta.url);
24896
25379
  var { version: PKG_VERSION } = require2("../package.json");
24897
25380
  function j2(value) {
24898
- return JSON.stringify(value);
25381
+ return JSON.stringify(value, (_key, val) => val === null || val === void 0 ? void 0 : val);
24899
25382
  }
24900
25383
  function extractResultCount(response) {
24901
25384
  if (response?.isError) return 0;
@@ -24972,6 +25455,15 @@ function createServer2(store, registry, config, rootPath) {
24972
25455
  "- State stores \u2192 `get_state_stores` (Zustand/Redux/Pinia)",
24973
25456
  "- Event graph \u2192 `get_event_graph` (event emitters/listeners)",
24974
25457
  "",
25458
+ "Token optimization (IMPORTANT \u2014 saves 40-85% tokens):",
25459
+ "- **Batch multiple queries** \u2192 `batch` combines up to 10 tool calls into 1 MCP request. Use whenever you need 2+ independent queries:",
25460
+ ' `batch({ calls: [{ tool: "get_outline", args: { path: "a.ts" } }, { tool: "get_outline", args: { path: "b.ts" } }] })`',
25461
+ "- **Bundle symbol + imports** \u2192 `get_context_bundle` returns a symbol's source + its import dependencies in one call (supports batch via `symbol_ids[]`)",
25462
+ "- **Avoid repeated file reads** \u2192 use `get_outline` once to understand structure, then `get_symbol` for specific symbols. NEVER read the same file multiple times.",
25463
+ "- **Use `get_task_context` instead of Agent subagents** \u2192 it returns focused context within a token budget, replacing manual search chains",
25464
+ "- Check token waste \u2192 `get_optimization_report` detects repeated reads, Bash grep, and unused trace-mcp tools",
25465
+ "- Track savings \u2192 `get_session_stats` shows per-tool token savings; `get_real_savings` shows actual vs achievable token usage",
25466
+ "",
24975
25467
  "WHEN TO USE native tools (Read/Grep/Glob):",
24976
25468
  "- Non-code files (.md, .json, .yaml, .toml, config) \u2192 Read/Grep",
24977
25469
  "- Reading a file before editing (Edit needs full content) \u2192 Read",
@@ -24994,6 +25486,7 @@ function createServer2(store, registry, config, rootPath) {
24994
25486
  }
24995
25487
  const _originalTool = server.tool.bind(server);
24996
25488
  const registeredToolNames = [];
25489
+ const toolHandlers = /* @__PURE__ */ new Map();
24997
25490
  const descriptionOverrides = config.tools?.descriptions ?? {};
24998
25491
  const sharedParamOverrides = typeof descriptionOverrides._shared === "object" && descriptionOverrides._shared !== null ? descriptionOverrides._shared : {};
24999
25492
  server.tool = ((...args) => {
@@ -25025,6 +25518,9 @@ function createServer2(store, registry, config, rootPath) {
25025
25518
  const cbIdx = args.length - 1;
25026
25519
  const originalCb = args[cbIdx];
25027
25520
  if (typeof originalCb === "function") {
25521
+ toolHandlers.set(name, async (params) => {
25522
+ return await originalCb(params);
25523
+ });
25028
25524
  args[cbIdx] = async (...cbArgs) => {
25029
25525
  savings.recordCall(name);
25030
25526
  const params = cbArgs[0] && typeof cbArgs[0] === "object" ? cbArgs[0] : {};
@@ -25061,10 +25557,12 @@ function createServer2(store, registry, config, rootPath) {
25061
25557
  process.on("SIGINT", flushSavings);
25062
25558
  process.on("SIGTERM", flushSavings);
25063
25559
  process.on("exit", flushSavings);
25560
+ let budgetWarningShown = false;
25064
25561
  function jh(toolName, value) {
25065
25562
  const hinted = withHints(toolName, value);
25066
25563
  const stats = savings.getSessionStats();
25067
- if (stats.total_calls >= 15) {
25564
+ if (stats.total_calls >= 15 && !budgetWarningShown) {
25565
+ budgetWarningShown = true;
25068
25566
  const obj = hinted !== null && typeof hinted === "object" && !Array.isArray(hinted) ? hinted : { data: hinted };
25069
25567
  obj._budget_warning = `${stats.total_calls} tool calls this session (~${stats.total_raw_tokens} raw tokens). Consider using get_task_context or get_feature_context for consolidated context instead of many small queries.`;
25070
25568
  return j2(obj);
@@ -25276,13 +25774,27 @@ function createServer2(store, registry, config, rootPath) {
25276
25774
  }));
25277
25775
  const response = { items, total: result.total, search_mode: result.search_mode };
25278
25776
  if (items.length === 0) {
25279
- const stats = store.getStats();
25280
- response.evidence = buildNegativeEvidence(
25281
- stats.totalFiles,
25282
- stats.totalSymbols,
25283
- result.search_mode === "fuzzy" || !!fuzzy,
25284
- "search"
25285
- );
25777
+ const textResult = searchText(store, projectRoot, {
25778
+ query,
25779
+ filePattern: file_pattern,
25780
+ language,
25781
+ maxResults: Math.min(limit ?? 20, 10),
25782
+ contextLines: 1
25783
+ });
25784
+ if (textResult.isOk() && textResult.value.matches.length > 0) {
25785
+ const tv = textResult.value;
25786
+ response.fallback_text_matches = tv.matches;
25787
+ response.fallback_total = tv.total_matches;
25788
+ response.search_mode = "symbol_miss_text_fallback";
25789
+ } else {
25790
+ const stats = store.getStats();
25791
+ response.evidence = buildNegativeEvidence(
25792
+ stats.totalFiles,
25793
+ stats.totalSymbols,
25794
+ result.search_mode === "fuzzy" || !!fuzzy,
25795
+ "search"
25796
+ );
25797
+ }
25286
25798
  }
25287
25799
  return { content: [{ type: "text", text: jh("search", response) }] };
25288
25800
  }
@@ -25305,23 +25817,25 @@ function createServer2(store, registry, config, rootPath) {
25305
25817
  );
25306
25818
  server.tool(
25307
25819
  "get_change_impact",
25308
- "Determine what depends on a file or symbol (reverse dependency analysis). Use before making changes to understand blast radius.",
25820
+ "Full change impact report: risk score + mitigations, breaking change detection, enriched dependents (complexity, coverage, exports), module groups, affected tests, co-change hidden couplings. Supports diff-aware mode via symbol_ids to scope analysis to only changed symbols.",
25309
25821
  {
25310
25822
  file_path: z5.string().max(512).optional().describe("Relative file path to analyze"),
25311
25823
  symbol_id: z5.string().max(512).optional().describe("Symbol ID to analyze"),
25824
+ symbol_ids: z5.array(z5.string().max(512)).max(50).optional().describe("Diff-aware: only analyze impact of these specific symbols (e.g. from get_changed_symbols)"),
25312
25825
  depth: z5.number().int().min(1).max(20).optional().describe("Max traversal depth (default 3)"),
25313
25826
  max_dependents: z5.number().int().min(1).max(5e3).optional().describe("Cap on returned dependents (default 200)")
25314
25827
  },
25315
- async ({ file_path, symbol_id, depth, max_dependents }) => {
25828
+ async ({ file_path, symbol_id, symbol_ids, depth, max_dependents }) => {
25316
25829
  if (file_path) {
25317
25830
  const blocked = guardPath(file_path);
25318
25831
  if (blocked) return blocked;
25319
25832
  }
25320
25833
  const result = getChangeImpact(
25321
25834
  store,
25322
- { filePath: file_path, symbolId: symbol_id },
25835
+ { filePath: file_path, symbolId: symbol_id, symbolIds: symbol_ids },
25323
25836
  depth ?? 3,
25324
- max_dependents ?? 200
25837
+ max_dependents ?? 200,
25838
+ projectRoot
25325
25839
  );
25326
25840
  if (result.isErr()) {
25327
25841
  return { content: [{ type: "text", text: j2(formatToolError(result.error)) }], isError: true };
@@ -26653,7 +27167,7 @@ function createServer2(store, registry, config, rootPath) {
26653
27167
  file_pattern: z5.string().max(512).optional().describe('Glob filter, e.g. "src/**/*.ts"'),
26654
27168
  language: z5.string().max(64).optional().describe('Filter by language (e.g. "typescript", "python")'),
26655
27169
  max_results: z5.number().int().min(1).max(200).optional().describe("Max matches to return (default 50)"),
26656
- context_lines: z5.number().int().min(0).max(10).optional().describe("Lines of context before/after each match (default 2)"),
27170
+ context_lines: z5.number().int().min(0).max(10).optional().describe("Lines of context before/after each match (default 0 \u2014 set higher if you need surrounding code)"),
26657
27171
  case_sensitive: z5.boolean().optional().describe("Case-sensitive search (default false)")
26658
27172
  },
26659
27173
  async ({ query, is_regex, file_pattern, language, max_results, context_lines, case_sensitive }) => {
@@ -27259,6 +27773,43 @@ function createServer2(store, registry, config, rootPath) {
27259
27773
  return { content: [{ type: "text", text: j2(summary) }] };
27260
27774
  }
27261
27775
  );
27776
+ _originalTool(
27777
+ "batch",
27778
+ "Execute multiple trace-mcp tools in a single MCP request. Returns results for all calls. Use to reduce round-trips when you need several independent queries (e.g., get_outline for 3 files, or search + get_symbol together).",
27779
+ {
27780
+ calls: z5.array(z5.object({
27781
+ tool: z5.string().describe('Tool name (e.g., "get_outline", "get_symbol", "search")'),
27782
+ args: z5.record(z5.unknown()).describe("Tool arguments")
27783
+ })).min(1).max(10).describe("Array of tool calls to execute (max 10)")
27784
+ },
27785
+ async ({ calls }) => {
27786
+ const results = [];
27787
+ for (const call of calls) {
27788
+ const handler = toolHandlers.get(call.tool);
27789
+ if (!handler) {
27790
+ results.push({ tool: call.tool, error: `Unknown tool: ${call.tool}` });
27791
+ continue;
27792
+ }
27793
+ try {
27794
+ savings.recordCall(call.tool);
27795
+ const response = await handler(call.args);
27796
+ const text = response.content?.[0]?.text;
27797
+ if (text) {
27798
+ try {
27799
+ results.push({ tool: call.tool, result: JSON.parse(text) });
27800
+ } catch {
27801
+ results.push({ tool: call.tool, result: text });
27802
+ }
27803
+ } else {
27804
+ results.push({ tool: call.tool, result: response });
27805
+ }
27806
+ } catch (e) {
27807
+ results.push({ tool: call.tool, error: e instanceof Error ? e.message : String(e) });
27808
+ }
27809
+ }
27810
+ return { content: [{ type: "text", text: j2({ batch_results: results, total: results.length }) }] };
27811
+ }
27812
+ );
27262
27813
  registerPrompts(server, { store, registry, config, projectRoot });
27263
27814
  return server;
27264
27815
  }
@@ -42537,8 +43088,8 @@ var SpringPlugin = class {
42537
43088
  const re = new RegExp(`@${annotation}\\s*(?:\\(\\s*(?:value\\s*=\\s*)?["']([^"']*)["']\\s*\\))?`, "g");
42538
43089
  let m;
42539
43090
  while ((m = re.exec(source)) !== null) {
42540
- const path89 = m[1] ?? "";
42541
- const uri = normalizePath(classPrefix + "/" + path89);
43091
+ const path98 = m[1] ?? "";
43092
+ const uri = normalizePath(classPrefix + "/" + path98);
42542
43093
  result.routes.push({ method, uri, line: source.substring(0, m.index).split("\n").length });
42543
43094
  }
42544
43095
  }
@@ -42598,8 +43149,8 @@ var SpringPlugin = class {
42598
43149
  }
42599
43150
  }
42600
43151
  };
42601
- function normalizePath(path89) {
42602
- return "/" + path89.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
43152
+ function normalizePath(path98) {
43153
+ return "/" + path98.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
42603
43154
  }
42604
43155
 
42605
43156
  // src/indexer/plugins/integration/framework/express/index.ts
@@ -45795,6 +46346,131 @@ function toModelName(varName) {
45795
46346
  return stripped.charAt(0).toUpperCase() + stripped.slice(1);
45796
46347
  }
45797
46348
 
46349
+ // src/indexer/plugins/integration/orm/raw-sql/index.ts
46350
+ import fs46 from "fs";
46351
+ import path58 from "path";
46352
+ var RAW_SQL_PACKAGES = [
46353
+ "better-sqlite3",
46354
+ "sqlite3",
46355
+ "sql.js",
46356
+ "pg",
46357
+ "mysql2",
46358
+ "mysql",
46359
+ "tedious",
46360
+ "oracledb"
46361
+ ];
46362
+ var PYTHON_SQL_PACKAGES = ["sqlite3", "psycopg2", "pymysql", "asyncpg", "aiosqlite"];
46363
+ var SQL_CALL_RE = /\.(?:prepare|exec|execute|query|run|all|get)\(\s*[`'"]([\s\S]{5,500}?)[`'"]/g;
46364
+ var CREATE_TABLE_RE = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["'`]?(\w+)["'`]?/gi;
46365
+ var TABLE_REF_RE = /(?:FROM|INTO|UPDATE|JOIN)\s+["'`]?(\w+)["'`]?/gi;
46366
+ var PRAGMA_RE = /\.pragma\(\s*['"]([^'"]+)['"]/g;
46367
+ var SQL_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:better-sqlite3|sqlite3|sql\.js|Database)\b/;
46368
+ function extractSqlStatements(source) {
46369
+ const results = [];
46370
+ const seen = /* @__PURE__ */ new Set();
46371
+ const callRe = new RegExp(SQL_CALL_RE.source, "g");
46372
+ let m;
46373
+ while ((m = callRe.exec(source)) !== null) {
46374
+ const sql = m[1].trim();
46375
+ if (seen.has(sql)) continue;
46376
+ seen.add(sql);
46377
+ const tables = [];
46378
+ const isDdl = /^\s*(CREATE|ALTER|DROP)\s/i.test(sql);
46379
+ const isPragma = /^\s*pragma\s/i.test(sql);
46380
+ const createRe = new RegExp(CREATE_TABLE_RE.source, "gi");
46381
+ let cm;
46382
+ while ((cm = createRe.exec(sql)) !== null) {
46383
+ if (!tables.includes(cm[1])) tables.push(cm[1]);
46384
+ }
46385
+ const refRe = new RegExp(TABLE_REF_RE.source, "gi");
46386
+ let rm;
46387
+ while ((rm = refRe.exec(sql)) !== null) {
46388
+ const name = rm[1].toLowerCase();
46389
+ if (!["set", "select", "where", "values", "null", "table"].includes(name)) {
46390
+ if (!tables.includes(rm[1])) tables.push(rm[1]);
46391
+ }
46392
+ }
46393
+ results.push({
46394
+ kind: isPragma ? "pragma" : isDdl ? "ddl" : "dml",
46395
+ tables,
46396
+ raw: sql.length > 200 ? sql.slice(0, 200) + "\u2026" : sql
46397
+ });
46398
+ }
46399
+ const pragmaRe = new RegExp(PRAGMA_RE.source, "g");
46400
+ while ((m = pragmaRe.exec(source)) !== null) {
46401
+ results.push({ kind: "pragma", tables: [], raw: m[1] });
46402
+ }
46403
+ return results;
46404
+ }
46405
+ var RawSqlPlugin = class {
46406
+ manifest = {
46407
+ name: "raw-sql",
46408
+ version: "1.0.0",
46409
+ priority: 30,
46410
+ category: "orm",
46411
+ dependencies: []
46412
+ };
46413
+ detect(ctx) {
46414
+ if (ctx.packageJson) {
46415
+ const deps = {
46416
+ ...ctx.packageJson.dependencies,
46417
+ ...ctx.packageJson.devDependencies
46418
+ };
46419
+ for (const pkg of RAW_SQL_PACKAGES) {
46420
+ if (pkg in deps) return true;
46421
+ }
46422
+ }
46423
+ if (ctx.requirementsTxt) {
46424
+ for (const line of ctx.requirementsTxt) {
46425
+ const pkgName = line.split(/[=<>!]/)[0].trim().toLowerCase();
46426
+ if (PYTHON_SQL_PACKAGES.includes(pkgName)) return true;
46427
+ }
46428
+ }
46429
+ try {
46430
+ const pkgPath = path58.join(ctx.rootPath, "package.json");
46431
+ const content = fs46.readFileSync(pkgPath, "utf-8");
46432
+ const pkg = JSON.parse(content);
46433
+ const deps = {
46434
+ ...pkg.dependencies,
46435
+ ...pkg.devDependencies
46436
+ };
46437
+ for (const p4 of RAW_SQL_PACKAGES) {
46438
+ if (p4 in deps) return true;
46439
+ }
46440
+ } catch {
46441
+ }
46442
+ return false;
46443
+ }
46444
+ registerSchema() {
46445
+ return {
46446
+ edgeTypes: [
46447
+ { name: "sql_query", category: "sql", description: "Raw SQL query against a table" },
46448
+ { name: "sql_schema", category: "sql", description: "DDL schema definition" }
46449
+ ]
46450
+ };
46451
+ }
46452
+ extractNodes(filePath, content, language) {
46453
+ if (!["typescript", "javascript", "python"].includes(language)) {
46454
+ return ok({ status: "ok", symbols: [] });
46455
+ }
46456
+ const source = content.toString("utf-8");
46457
+ const result = { status: "ok", symbols: [], edges: [] };
46458
+ const hasImport = SQL_IMPORT_RE.test(source);
46459
+ const statements = extractSqlStatements(source);
46460
+ if (statements.length > 0) {
46461
+ const hasDdl = statements.some((s) => s.kind === "ddl");
46462
+ result.frameworkRole = hasDdl ? "sql_schema" : "sql_queries";
46463
+ } else if (hasImport) {
46464
+ result.frameworkRole = "sql_client";
46465
+ }
46466
+ return ok(result);
46467
+ }
46468
+ resolveEdges(_ctx) {
46469
+ const edges = [];
46470
+ return ok(edges);
46471
+ }
46472
+ };
46473
+
45798
46474
  // src/indexer/plugins/integration/view/react/index.ts
45799
46475
  import { createRequire as createRequire17 } from "module";
45800
46476
  import { ok as ok45, err as err28 } from "neverthrow";
@@ -46048,8 +46724,8 @@ var ReactPlugin = class {
46048
46724
  };
46049
46725
 
46050
46726
  // src/indexer/plugins/integration/view/vue/index.ts
46051
- import fs46 from "fs";
46052
- import path58 from "path";
46727
+ import fs47 from "fs";
46728
+ import path59 from "path";
46053
46729
 
46054
46730
  // src/indexer/plugins/integration/view/vue/resolver.ts
46055
46731
  function toKebabCase(name) {
@@ -46088,8 +46764,8 @@ var VueFrameworkPlugin = class {
46088
46764
  return "vue" in deps;
46089
46765
  }
46090
46766
  try {
46091
- const pkgPath = path58.join(ctx.rootPath, "package.json");
46092
- const content = fs46.readFileSync(pkgPath, "utf-8");
46767
+ const pkgPath = path59.join(ctx.rootPath, "package.json");
46768
+ const content = fs47.readFileSync(pkgPath, "utf-8");
46093
46769
  const pkg = JSON.parse(content);
46094
46770
  const deps = {
46095
46771
  ...pkg.dependencies,
@@ -46193,8 +46869,8 @@ var VueFrameworkPlugin = class {
46193
46869
  };
46194
46870
 
46195
46871
  // src/indexer/plugins/integration/view/react-native/index.ts
46196
- import fs47 from "fs";
46197
- import path59 from "path";
46872
+ import fs48 from "fs";
46873
+ import path60 from "path";
46198
46874
  import { ok as ok46 } from "neverthrow";
46199
46875
  function expoFileToRoute(filePath) {
46200
46876
  const normalized = filePath.replace(/\\/g, "/");
@@ -46232,8 +46908,8 @@ var ReactNativePlugin = class {
46232
46908
  return true;
46233
46909
  }
46234
46910
  try {
46235
- const pkgPath = path59.join(ctx.rootPath, "package.json");
46236
- const content = fs47.readFileSync(pkgPath, "utf-8");
46911
+ const pkgPath = path60.join(ctx.rootPath, "package.json");
46912
+ const content = fs48.readFileSync(pkgPath, "utf-8");
46237
46913
  const json = JSON.parse(content);
46238
46914
  const allDeps = { ...json.dependencies, ...json.devDependencies };
46239
46915
  if ("expo-router" in allDeps) this.hasExpoRouter = true;
@@ -46466,8 +47142,8 @@ function extractExpoNavigationCalls(source) {
46466
47142
  }
46467
47143
  const templateRegex = /router\.(push|replace|navigate)\s*\(\s*`([^`]+)`/g;
46468
47144
  while ((match = templateRegex.exec(source)) !== null) {
46469
- const path89 = match[2].replace(/\$\{[^}]+\}/g, ":param");
46470
- paths.push(path89);
47145
+ const path98 = match[2].replace(/\$\{[^}]+\}/g, ":param");
47146
+ paths.push(path98);
46471
47147
  }
46472
47148
  const linkRegex = /<Link\s+[^>]*href\s*=\s*(?:\{?\s*)?['"]([^'"]+)['"]/g;
46473
47149
  while ((match = linkRegex.exec(source)) !== null) {
@@ -46479,9 +47155,9 @@ function extractExpoNavigationCalls(source) {
46479
47155
  }
46480
47156
  return [...new Set(paths)];
46481
47157
  }
46482
- function matchExpoRoute(path89, routePattern) {
46483
- if (path89 === routePattern) return true;
46484
- const pathParts = path89.split("/").filter(Boolean);
47158
+ function matchExpoRoute(path98, routePattern) {
47159
+ if (path98 === routePattern) return true;
47160
+ const pathParts = path98.split("/").filter(Boolean);
46485
47161
  const routeParts = routePattern.split("/").filter(Boolean);
46486
47162
  if (pathParts.length !== routeParts.length) {
46487
47163
  if (routeParts[routeParts.length - 1] === "*" && pathParts.length >= routeParts.length - 1) {
@@ -46539,8 +47215,8 @@ function extractNativeModuleNames(source) {
46539
47215
  }
46540
47216
 
46541
47217
  // src/indexer/plugins/integration/view/blade/index.ts
46542
- import fs48 from "fs";
46543
- import path60 from "path";
47218
+ import fs49 from "fs";
47219
+ import path61 from "path";
46544
47220
  var EXTENDS_RE = /@extends\(\s*['"]([\w.-]+)['"]\s*\)/g;
46545
47221
  var INCLUDE_RE = /@include(?:If|When|Unless|First)?\(\s*['"]([\w.-]+)['"]/g;
46546
47222
  var COMPONENT_RE = /@component\(\s*['"]([\w.-]+)['"]/g;
@@ -46598,8 +47274,8 @@ var BladePlugin = class {
46598
47274
  };
46599
47275
  detect(ctx) {
46600
47276
  try {
46601
- const viewsDir = path60.join(ctx.rootPath, "resources", "views");
46602
- const stat = fs48.statSync(viewsDir);
47277
+ const viewsDir = path61.join(ctx.rootPath, "resources", "views");
47278
+ const stat = fs49.statSync(viewsDir);
46603
47279
  if (!stat.isDirectory()) return false;
46604
47280
  return this.hasBlade(viewsDir);
46605
47281
  } catch {
@@ -46681,11 +47357,11 @@ var BladePlugin = class {
46681
47357
  }
46682
47358
  hasBlade(dir) {
46683
47359
  try {
46684
- const entries = fs48.readdirSync(dir, { withFileTypes: true });
47360
+ const entries = fs49.readdirSync(dir, { withFileTypes: true });
46685
47361
  for (const entry of entries) {
46686
47362
  if (entry.isFile() && entry.name.endsWith(".blade.php")) return true;
46687
47363
  if (entry.isDirectory()) {
46688
- if (this.hasBlade(path60.join(dir, entry.name))) return true;
47364
+ if (this.hasBlade(path61.join(dir, entry.name))) return true;
46689
47365
  }
46690
47366
  }
46691
47367
  } catch {
@@ -46695,8 +47371,8 @@ var BladePlugin = class {
46695
47371
  };
46696
47372
 
46697
47373
  // src/indexer/plugins/integration/view/inertia/index.ts
46698
- import fs49 from "fs";
46699
- import path61 from "path";
47374
+ import fs50 from "fs";
47375
+ import path62 from "path";
46700
47376
  var INERTIA_RENDER_RE = /(?:Inertia::render|inertia)\(\s*['"]([\w/.-]+)['"]\s*(?:,\s*\[([^\]]*)\])?\s*\)/g;
46701
47377
  var ARRAY_KEY_RE = /['"](\w+)['"]\s*=>/g;
46702
47378
  function extractInertiaRenders(source) {
@@ -46742,16 +47418,16 @@ var InertiaPlugin = class {
46742
47418
  if ("@inertiajs/vue3" in deps || "@inertiajs/react" in deps) return true;
46743
47419
  }
46744
47420
  try {
46745
- const composerPath = path61.join(ctx.rootPath, "composer.json");
46746
- const content = fs49.readFileSync(composerPath, "utf-8");
47421
+ const composerPath = path62.join(ctx.rootPath, "composer.json");
47422
+ const content = fs50.readFileSync(composerPath, "utf-8");
46747
47423
  const json = JSON.parse(content);
46748
47424
  const req = json.require;
46749
47425
  if (req?.["inertiajs/inertia-laravel"]) return true;
46750
47426
  } catch {
46751
47427
  }
46752
47428
  try {
46753
- const pkgPath = path61.join(ctx.rootPath, "package.json");
46754
- const content = fs49.readFileSync(pkgPath, "utf-8");
47429
+ const pkgPath = path62.join(ctx.rootPath, "package.json");
47430
+ const content = fs50.readFileSync(pkgPath, "utf-8");
46755
47431
  const pkg = JSON.parse(content);
46756
47432
  const deps = {
46757
47433
  ...pkg.dependencies,
@@ -46792,7 +47468,7 @@ var InertiaPlugin = class {
46792
47468
  if (file.language !== "php") continue;
46793
47469
  let source;
46794
47470
  try {
46795
- source = fs49.readFileSync(path61.resolve(ctx.rootPath, file.path), "utf-8");
47471
+ source = fs50.readFileSync(path62.resolve(ctx.rootPath, file.path), "utf-8");
46796
47472
  } catch {
46797
47473
  continue;
46798
47474
  }
@@ -46843,8 +47519,8 @@ var InertiaPlugin = class {
46843
47519
  };
46844
47520
 
46845
47521
  // src/indexer/plugins/integration/view/shadcn/index.ts
46846
- import fs50 from "fs";
46847
- import path62 from "path";
47522
+ import fs51 from "fs";
47523
+ import path63 from "path";
46848
47524
  import { ok as ok47 } from "neverthrow";
46849
47525
  var CVA_RE = /(?:export\s+(?:default\s+)?)?(?:const|let)\s+(\w+)\s*=\s*cva\s*\(/g;
46850
47526
  function extractCvaDefinitions(source) {
@@ -47031,7 +47707,7 @@ function extractShadcnComponents(source, filePath) {
47031
47707
  return components;
47032
47708
  }
47033
47709
  function extractShadcnVueComponent(source, filePath) {
47034
- const fileName = path62.basename(filePath, path62.extname(filePath));
47710
+ const fileName = path63.basename(filePath, path63.extname(filePath));
47035
47711
  const name = toPascalCase2(fileName);
47036
47712
  const hasRadixVue = /from\s+['"]radix-vue['"]/.test(source) || /from\s+['"]reka-ui['"]/.test(source);
47037
47713
  const hasCn = /\bcn\s*\(/.test(source);
@@ -47168,26 +47844,26 @@ function scanInstalledComponents(rootPath, config) {
47168
47844
  "app/components/ui"
47169
47845
  ].filter(Boolean);
47170
47846
  for (const relDir of possibleDirs) {
47171
- const absDir = path62.resolve(rootPath, relDir);
47847
+ const absDir = path63.resolve(rootPath, relDir);
47172
47848
  try {
47173
- const entries = fs50.readdirSync(absDir, { withFileTypes: true });
47849
+ const entries = fs51.readdirSync(absDir, { withFileTypes: true });
47174
47850
  for (const entry of entries) {
47175
47851
  if (entry.isFile() && /\.(tsx|vue|ts|jsx)$/.test(entry.name)) {
47176
47852
  const baseName = entry.name.replace(/\.(tsx|vue|ts|jsx)$/, "");
47177
47853
  components.push({
47178
47854
  name: toPascalCase2(baseName),
47179
47855
  fileName: entry.name,
47180
- relativePath: path62.join(relDir, entry.name)
47856
+ relativePath: path63.join(relDir, entry.name)
47181
47857
  });
47182
47858
  } else if (entry.isDirectory()) {
47183
47859
  try {
47184
- const subEntries = fs50.readdirSync(path62.join(absDir, entry.name));
47860
+ const subEntries = fs51.readdirSync(path63.join(absDir, entry.name));
47185
47861
  const indexFile = subEntries.find((f) => /^index\.(tsx|vue|ts|jsx)$/.test(f));
47186
47862
  if (indexFile) {
47187
47863
  components.push({
47188
47864
  name: toPascalCase2(entry.name),
47189
47865
  fileName: indexFile,
47190
- relativePath: path62.join(relDir, entry.name, indexFile)
47866
+ relativePath: path63.join(relDir, entry.name, indexFile)
47191
47867
  });
47192
47868
  }
47193
47869
  for (const sub of subEntries) {
@@ -47196,7 +47872,7 @@ function scanInstalledComponents(rootPath, config) {
47196
47872
  components.push({
47197
47873
  name: toPascalCase2(baseName),
47198
47874
  fileName: sub,
47199
- relativePath: path62.join(relDir, entry.name, sub)
47875
+ relativePath: path63.join(relDir, entry.name, sub)
47200
47876
  });
47201
47877
  }
47202
47878
  }
@@ -47229,8 +47905,8 @@ var ShadcnPlugin = class {
47229
47905
  detect(ctx) {
47230
47906
  this.rootPath = ctx.rootPath;
47231
47907
  try {
47232
- const configPath = path62.join(ctx.rootPath, "components.json");
47233
- const raw = fs50.readFileSync(configPath, "utf-8");
47908
+ const configPath = path63.join(ctx.rootPath, "components.json");
47909
+ const raw = fs51.readFileSync(configPath, "utf-8");
47234
47910
  this.config = JSON.parse(raw);
47235
47911
  this.scanComponents(ctx);
47236
47912
  return true;
@@ -48106,8 +48782,8 @@ var HeadlessUiPlugin = class {
48106
48782
  };
48107
48783
 
48108
48784
  // src/indexer/plugins/integration/view/nuxt-ui/index.ts
48109
- import fs51 from "fs";
48110
- import path63 from "path";
48785
+ import fs52 from "fs";
48786
+ import path64 from "path";
48111
48787
  import { ok as ok51 } from "neverthrow";
48112
48788
  var NUXT_UI_V3_COMPONENTS = /* @__PURE__ */ new Set([
48113
48789
  "UAccordion",
@@ -48363,8 +49039,8 @@ var NuxtUiPlugin = class {
48363
49039
  this.hasPro = "@nuxt/ui-pro" in deps;
48364
49040
  if (!hasNuxtUi) {
48365
49041
  try {
48366
- const configPath = path63.join(ctx.rootPath, "nuxt.config.ts");
48367
- const configContent = fs51.readFileSync(configPath, "utf-8");
49042
+ const configPath = path64.join(ctx.rootPath, "nuxt.config.ts");
49043
+ const configContent = fs52.readFileSync(configPath, "utf-8");
48368
49044
  if (/@nuxt\/ui/.test(configContent)) return true;
48369
49045
  } catch {
48370
49046
  }
@@ -48563,8 +49239,8 @@ function extractBraceBody4(source, pos) {
48563
49239
  }
48564
49240
 
48565
49241
  // src/indexer/plugins/integration/view/angular/index.ts
48566
- import fs52 from "fs";
48567
- import path64 from "path";
49242
+ import fs53 from "fs";
49243
+ import path65 from "path";
48568
49244
  var COMPONENT_RE2 = /@Component\s*\(\s*\{[^}]*selector\s*:\s*['"]([^'"]+)['"]/gs;
48569
49245
  var INJECTABLE_RE2 = /@Injectable\s*\(/g;
48570
49246
  var NGMODULE_RE = /@NgModule\s*\(/g;
@@ -48612,8 +49288,8 @@ var AngularPlugin = class {
48612
49288
  if ("@angular/core" in deps) return true;
48613
49289
  }
48614
49290
  try {
48615
- const pkgPath = path64.join(ctx.rootPath, "package.json");
48616
- const content = fs52.readFileSync(pkgPath, "utf-8");
49291
+ const pkgPath = path65.join(ctx.rootPath, "package.json");
49292
+ const content = fs53.readFileSync(pkgPath, "utf-8");
48617
49293
  const pkg = JSON.parse(content);
48618
49294
  const deps = {
48619
49295
  ...pkg.dependencies,
@@ -48932,8 +49608,8 @@ function isHtmlTag(tag) {
48932
49608
  }
48933
49609
 
48934
49610
  // src/indexer/plugins/integration/view/svelte/index.ts
48935
- import fs53 from "fs";
48936
- import path65 from "path";
49611
+ import fs54 from "fs";
49612
+ import path66 from "path";
48937
49613
  var EXPORT_LET_RE = /export\s+let\s+(\w+)/g;
48938
49614
  var SLOT_RE = /<slot(?:\s+name\s*=\s*['"]([^'"]+)['"])?\s*\/?>/g;
48939
49615
  var DISPATCH_RE2 = /dispatch\s*\(\s*['"]([^'"]+)['"]/g;
@@ -48951,11 +49627,11 @@ function extractTemplate(source) {
48951
49627
  return source.replace(/<script[^>]*>[\s\S]*?<\/script>/g, "").replace(/<style[^>]*>[\s\S]*?<\/style>/g, "");
48952
49628
  }
48953
49629
  function isSvelteKitRouteFile(filePath) {
48954
- const base = path65.basename(filePath);
49630
+ const base = path66.basename(filePath);
48955
49631
  return /^\+(page|layout|server|error)/.test(base);
48956
49632
  }
48957
49633
  function isSvelteKitHooksFile(filePath) {
48958
- const base = path65.basename(filePath);
49634
+ const base = path66.basename(filePath);
48959
49635
  return /^hooks\.(server|client)\.(ts|js)$/.test(base);
48960
49636
  }
48961
49637
  function extractRouteUri(filePath) {
@@ -48963,11 +49639,11 @@ function extractRouteUri(filePath) {
48963
49639
  const routesIdx = normalized.indexOf("/routes/");
48964
49640
  if (routesIdx === -1) return "/";
48965
49641
  const afterRoutes = normalized.substring(routesIdx + "/routes".length);
48966
- const dir = path65.posix.dirname(afterRoutes);
49642
+ const dir = path66.posix.dirname(afterRoutes);
48967
49643
  return dir === "." ? "/" : dir;
48968
49644
  }
48969
49645
  function componentNameFromPath2(filePath) {
48970
- const base = path65.basename(filePath, ".svelte");
49646
+ const base = path66.basename(filePath, ".svelte");
48971
49647
  if (base.startsWith("+")) return base;
48972
49648
  return base;
48973
49649
  }
@@ -48994,8 +49670,8 @@ var SveltePlugin = class {
48994
49670
  if ("svelte" in deps || "@sveltejs/kit" in deps) return true;
48995
49671
  }
48996
49672
  try {
48997
- const pkgPath = path65.join(ctx.rootPath, "package.json");
48998
- const content = fs53.readFileSync(pkgPath, "utf-8");
49673
+ const pkgPath = path66.join(ctx.rootPath, "package.json");
49674
+ const content = fs54.readFileSync(pkgPath, "utf-8");
48999
49675
  const pkg = JSON.parse(content);
49000
49676
  const deps = {
49001
49677
  ...pkg.dependencies,
@@ -49070,7 +49746,7 @@ var SveltePlugin = class {
49070
49746
  const name = componentNameFromPath2(filePath);
49071
49747
  const isRouteFile = isSvelteKitRouteFile(filePath);
49072
49748
  let kind = "component";
49073
- const base = path65.basename(filePath);
49749
+ const base = path66.basename(filePath);
49074
49750
  if (base === "+page.svelte") kind = "page";
49075
49751
  else if (base === "+layout.svelte") kind = "layout";
49076
49752
  else if (base === "+error.svelte") kind = "component";
@@ -49151,7 +49827,7 @@ var SveltePlugin = class {
49151
49827
  }
49152
49828
  }
49153
49829
  extractSvelteKitServerFile(filePath, source, result) {
49154
- const base = path65.basename(filePath);
49830
+ const base = path66.basename(filePath);
49155
49831
  if (!isSvelteKitRouteFile(filePath) && !isSvelteKitHooksFile(filePath)) {
49156
49832
  return;
49157
49833
  }
@@ -49242,8 +49918,8 @@ var SveltePlugin = class {
49242
49918
  };
49243
49919
 
49244
49920
  // src/indexer/plugins/integration/api/trpc/index.ts
49245
- import fs54 from "fs";
49246
- import path66 from "path";
49921
+ import fs55 from "fs";
49922
+ import path67 from "path";
49247
49923
  var PROCEDURE_RE = /(\w+)\s*:\s*\w*[Pp]rocedure[\s\S]{0,500}?\.(query|mutation|subscription)\s*\(/g;
49248
49924
  var ROUTER_RE = /(?:t\.router|router)\s*\(\s*\{/g;
49249
49925
  function extractTrpcProcedures(source) {
@@ -49275,8 +49951,8 @@ var TrpcPlugin = class {
49275
49951
  if ("@trpc/server" in deps) return true;
49276
49952
  }
49277
49953
  try {
49278
- const pkgPath = path66.join(ctx.rootPath, "package.json");
49279
- const content = fs54.readFileSync(pkgPath, "utf-8");
49954
+ const pkgPath = path67.join(ctx.rootPath, "package.json");
49955
+ const content = fs55.readFileSync(pkgPath, "utf-8");
49280
49956
  const pkg = JSON.parse(content);
49281
49957
  const deps = {
49282
49958
  ...pkg.dependencies,
@@ -49323,8 +49999,8 @@ var TrpcPlugin = class {
49323
49999
 
49324
50000
  // src/indexer/plugins/integration/api/drf/index.ts
49325
50001
  import { createRequire as createRequire18 } from "module";
49326
- import fs55 from "fs";
49327
- import path67 from "path";
50002
+ import fs56 from "fs";
50003
+ import path68 from "path";
49328
50004
  import { ok as ok52, err as err29 } from "neverthrow";
49329
50005
  var require19 = createRequire18(import.meta.url);
49330
50006
  var Parser17 = require19("tree-sitter");
@@ -49340,25 +50016,25 @@ function getParser14() {
49340
50016
  function hasPythonDep4(rootPath, depName) {
49341
50017
  for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
49342
50018
  try {
49343
- const content = fs55.readFileSync(path67.join(rootPath, reqFile), "utf-8");
50019
+ const content = fs56.readFileSync(path68.join(rootPath, reqFile), "utf-8");
49344
50020
  if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
49345
50021
  } catch {
49346
50022
  }
49347
50023
  }
49348
50024
  try {
49349
- const content = fs55.readFileSync(path67.join(rootPath, "pyproject.toml"), "utf-8");
50025
+ const content = fs56.readFileSync(path68.join(rootPath, "pyproject.toml"), "utf-8");
49350
50026
  if (content.includes(depName)) return true;
49351
50027
  } catch {
49352
50028
  }
49353
50029
  for (const f of ["setup.py", "setup.cfg"]) {
49354
50030
  try {
49355
- const content = fs55.readFileSync(path67.join(rootPath, f), "utf-8");
50031
+ const content = fs56.readFileSync(path68.join(rootPath, f), "utf-8");
49356
50032
  if (content.includes(depName)) return true;
49357
50033
  } catch {
49358
50034
  }
49359
50035
  }
49360
50036
  try {
49361
- const content = fs55.readFileSync(path67.join(rootPath, "Pipfile"), "utf-8");
50037
+ const content = fs56.readFileSync(path68.join(rootPath, "Pipfile"), "utf-8");
49362
50038
  if (content.includes(depName)) return true;
49363
50039
  } catch {
49364
50040
  }
@@ -49671,9 +50347,103 @@ var DRFPlugin = class {
49671
50347
  }
49672
50348
  };
49673
50349
 
50350
+ // src/indexer/plugins/integration/api/mcp-sdk/index.ts
50351
+ import fs57 from "fs";
50352
+ import path69 from "path";
50353
+ var MCP_SDK_PKG = "@modelcontextprotocol/sdk";
50354
+ var TOOL_RE = /\.tool\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]*)['"]\s*)?/g;
50355
+ var RESOURCE_RE = /\.resource\(\s*['"]([^'"]+)['"]/g;
50356
+ var PROMPT_RE = /\.prompt\(\s*['"]([^'"]+)['"]/g;
50357
+ var MCP_SERVER_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*McpServer.*from\s+['"]@modelcontextprotocol\/sdk/;
50358
+ var MCP_TRANSPORT_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*Transport.*from\s+['"]@modelcontextprotocol\/sdk/;
50359
+ function extractMcpRegistrations(source) {
50360
+ const results = [];
50361
+ let m;
50362
+ const toolRe = new RegExp(TOOL_RE.source, "g");
50363
+ while ((m = toolRe.exec(source)) !== null) {
50364
+ results.push({ kind: "tool", name: m[1], description: m[2] || void 0 });
50365
+ }
50366
+ const resourceRe = new RegExp(RESOURCE_RE.source, "g");
50367
+ while ((m = resourceRe.exec(source)) !== null) {
50368
+ results.push({ kind: "resource", name: m[1] });
50369
+ }
50370
+ const promptRe = new RegExp(PROMPT_RE.source, "g");
50371
+ while ((m = promptRe.exec(source)) !== null) {
50372
+ results.push({ kind: "prompt", name: m[1] });
50373
+ }
50374
+ return results;
50375
+ }
50376
+ var McpSdkPlugin = class {
50377
+ manifest = {
50378
+ name: "mcp-sdk",
50379
+ version: "1.0.0",
50380
+ priority: 25,
50381
+ category: "api",
50382
+ dependencies: []
50383
+ };
50384
+ detect(ctx) {
50385
+ if (ctx.packageJson) {
50386
+ const deps = {
50387
+ ...ctx.packageJson.dependencies,
50388
+ ...ctx.packageJson.devDependencies
50389
+ };
50390
+ if (MCP_SDK_PKG in deps) return true;
50391
+ }
50392
+ try {
50393
+ const pkgPath = path69.join(ctx.rootPath, "package.json");
50394
+ const content = fs57.readFileSync(pkgPath, "utf-8");
50395
+ const pkg = JSON.parse(content);
50396
+ const deps = {
50397
+ ...pkg.dependencies,
50398
+ ...pkg.devDependencies
50399
+ };
50400
+ return MCP_SDK_PKG in deps;
50401
+ } catch {
50402
+ return false;
50403
+ }
50404
+ }
50405
+ registerSchema() {
50406
+ return {
50407
+ edgeTypes: [
50408
+ { name: "mcp_tool", category: "mcp", description: "MCP tool registration" },
50409
+ { name: "mcp_resource", category: "mcp", description: "MCP resource registration" },
50410
+ { name: "mcp_prompt", category: "mcp", description: "MCP prompt registration" }
50411
+ ]
50412
+ };
50413
+ }
50414
+ extractNodes(filePath, content, language) {
50415
+ if (!["typescript", "javascript"].includes(language)) {
50416
+ return ok({ status: "ok", symbols: [] });
50417
+ }
50418
+ const source = content.toString("utf-8");
50419
+ const result = { status: "ok", symbols: [], routes: [], edges: [] };
50420
+ const hasServerImport = MCP_SERVER_IMPORT_RE.test(source);
50421
+ const hasTransportImport = MCP_TRANSPORT_IMPORT_RE.test(source);
50422
+ const registrations = extractMcpRegistrations(source);
50423
+ if (registrations.length > 0) {
50424
+ result.frameworkRole = "mcp_server";
50425
+ for (const reg of registrations) {
50426
+ result.routes.push({
50427
+ method: reg.kind.toUpperCase(),
50428
+ uri: reg.name
50429
+ });
50430
+ }
50431
+ } else if (hasServerImport) {
50432
+ result.frameworkRole = "mcp_server";
50433
+ } else if (hasTransportImport) {
50434
+ result.frameworkRole = "mcp_transport";
50435
+ }
50436
+ return ok(result);
50437
+ }
50438
+ resolveEdges(_ctx) {
50439
+ const edges = [];
50440
+ return ok(edges);
50441
+ }
50442
+ };
50443
+
49674
50444
  // src/indexer/plugins/integration/validation/zod/index.ts
49675
- import fs56 from "fs";
49676
- import path68 from "path";
50445
+ import fs58 from "fs";
50446
+ import path70 from "path";
49677
50447
  var ZOD_OBJECT_RE = /(?:export\s+(?:default\s+)?)?(?:const|let|var)\s+(\w+)\s*=\s*z\.object\s*\(\s*\{([^]*?)\}\s*\)/g;
49678
50448
  var ZOD_FIELD_RE = /(\w+)\s*:\s*z\.(\w+)\s*\(([^)]*)\)([.\w()]*)/g;
49679
50449
  function resolveFieldType(baseType, chain) {
@@ -49728,8 +50498,8 @@ var ZodPlugin = class {
49728
50498
  if ("zod" in deps) return true;
49729
50499
  }
49730
50500
  try {
49731
- const pkgPath = path68.join(ctx.rootPath, "package.json");
49732
- const content = fs56.readFileSync(pkgPath, "utf-8");
50501
+ const pkgPath = path70.join(ctx.rootPath, "package.json");
50502
+ const content = fs58.readFileSync(pkgPath, "utf-8");
49733
50503
  const pkg = JSON.parse(content);
49734
50504
  const deps = {
49735
50505
  ...pkg.dependencies,
@@ -49773,8 +50543,8 @@ var ZodPlugin = class {
49773
50543
 
49774
50544
  // src/indexer/plugins/integration/validation/pydantic/index.ts
49775
50545
  import { createRequire as createRequire19 } from "module";
49776
- import fs57 from "fs";
49777
- import path69 from "path";
50546
+ import fs59 from "fs";
50547
+ import path71 from "path";
49778
50548
  import { ok as ok53, err as err30 } from "neverthrow";
49779
50549
  var require20 = createRequire19(import.meta.url);
49780
50550
  var Parser18 = require20("tree-sitter");
@@ -49790,25 +50560,25 @@ function getParser15() {
49790
50560
  function hasPythonDep5(rootPath, depName) {
49791
50561
  for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
49792
50562
  try {
49793
- const content = fs57.readFileSync(path69.join(rootPath, reqFile), "utf-8");
50563
+ const content = fs59.readFileSync(path71.join(rootPath, reqFile), "utf-8");
49794
50564
  if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
49795
50565
  } catch {
49796
50566
  }
49797
50567
  }
49798
50568
  try {
49799
- const content = fs57.readFileSync(path69.join(rootPath, "pyproject.toml"), "utf-8");
50569
+ const content = fs59.readFileSync(path71.join(rootPath, "pyproject.toml"), "utf-8");
49800
50570
  if (content.includes(depName)) return true;
49801
50571
  } catch {
49802
50572
  }
49803
50573
  for (const f of ["setup.py", "setup.cfg"]) {
49804
50574
  try {
49805
- const content = fs57.readFileSync(path69.join(rootPath, f), "utf-8");
50575
+ const content = fs59.readFileSync(path71.join(rootPath, f), "utf-8");
49806
50576
  if (content.includes(depName)) return true;
49807
50577
  } catch {
49808
50578
  }
49809
50579
  }
49810
50580
  try {
49811
- const content = fs57.readFileSync(path69.join(rootPath, "Pipfile"), "utf-8");
50581
+ const content = fs59.readFileSync(path71.join(rootPath, "Pipfile"), "utf-8");
49812
50582
  if (content.includes(depName)) return true;
49813
50583
  } catch {
49814
50584
  }
@@ -50351,8 +51121,8 @@ function extractBraceBody5(source, pos) {
50351
51121
  }
50352
51122
 
50353
51123
  // src/indexer/plugins/integration/realtime/socketio/index.ts
50354
- import fs58 from "fs";
50355
- import path70 from "path";
51124
+ import fs60 from "fs";
51125
+ import path72 from "path";
50356
51126
  var LISTENER_RE = /(?:socket|io|server|namespace)\s*\.\s*on\s*\(\s*['"`]([^'"`]+)['"`]/g;
50357
51127
  var EMITTER_RE = /(?:socket|io|server|namespace)(?:\.broadcast)?\s*\.\s*emit\s*\(\s*['"`]([^'"`]+)['"`]/g;
50358
51128
  var NAMESPACE_RE6 = /(?:io|server)\s*\.\s*of\s*\(\s*['"`]([^'"`]+)['"`]/g;
@@ -50395,8 +51165,8 @@ var SocketIoPlugin = class {
50395
51165
  if ("socket.io" in deps) return true;
50396
51166
  }
50397
51167
  try {
50398
- const pkgPath = path70.join(ctx.rootPath, "package.json");
50399
- const content = fs58.readFileSync(pkgPath, "utf-8");
51168
+ const pkgPath = path72.join(ctx.rootPath, "package.json");
51169
+ const content = fs60.readFileSync(pkgPath, "utf-8");
50400
51170
  const pkg = JSON.parse(content);
50401
51171
  const deps = {
50402
51172
  ...pkg.dependencies,
@@ -50450,7 +51220,7 @@ var SocketIoPlugin = class {
50450
51220
  };
50451
51221
 
50452
51222
  // src/indexer/plugins/integration/testing/testing/index.ts
50453
- import fs59 from "fs";
51223
+ import fs61 from "fs";
50454
51224
 
50455
51225
  // src/utils/regex.ts
50456
51226
  var globalReCache = /* @__PURE__ */ new WeakMap();
@@ -50465,7 +51235,7 @@ function globalRe(pattern) {
50465
51235
  }
50466
51236
 
50467
51237
  // src/indexer/plugins/integration/testing/testing/index.ts
50468
- import path71 from "path";
51238
+ import path73 from "path";
50469
51239
  var PAGE_GOTO_RE = /page\.goto\s*\(\s*['"`]([^'"`]+)['"`]/g;
50470
51240
  var CY_VISIT_RE = /cy\.visit\s*\(\s*['"`]([^'"`]+)['"`]/g;
50471
51241
  var REQUEST_METHOD_RE = /request\s*\.\s*(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g;
@@ -50572,8 +51342,8 @@ var TestingPlugin = class {
50572
51342
  }
50573
51343
  }
50574
51344
  try {
50575
- const pkgPath = path71.join(ctx.rootPath, "package.json");
50576
- const content = fs59.readFileSync(pkgPath, "utf-8");
51345
+ const pkgPath = path73.join(ctx.rootPath, "package.json");
51346
+ const content = fs61.readFileSync(pkgPath, "utf-8");
50577
51347
  const pkg = JSON.parse(content);
50578
51348
  const deps = {
50579
51349
  ...pkg.dependencies,
@@ -50645,8 +51415,8 @@ var TestingPlugin = class {
50645
51415
 
50646
51416
  // src/indexer/plugins/integration/tooling/celery/index.ts
50647
51417
  import { createRequire as createRequire20 } from "module";
50648
- import fs60 from "fs";
50649
- import path72 from "path";
51418
+ import fs62 from "fs";
51419
+ import path74 from "path";
50650
51420
  import { ok as ok55, err as err31 } from "neverthrow";
50651
51421
  var require21 = createRequire20(import.meta.url);
50652
51422
  var Parser19 = require21("tree-sitter");
@@ -50662,25 +51432,25 @@ function getParser16() {
50662
51432
  function hasPythonDep6(rootPath, depName) {
50663
51433
  for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
50664
51434
  try {
50665
- const content = fs60.readFileSync(path72.join(rootPath, reqFile), "utf-8");
51435
+ const content = fs62.readFileSync(path74.join(rootPath, reqFile), "utf-8");
50666
51436
  if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
50667
51437
  } catch {
50668
51438
  }
50669
51439
  }
50670
51440
  try {
50671
- const content = fs60.readFileSync(path72.join(rootPath, "pyproject.toml"), "utf-8");
51441
+ const content = fs62.readFileSync(path74.join(rootPath, "pyproject.toml"), "utf-8");
50672
51442
  if (content.includes(depName)) return true;
50673
51443
  } catch {
50674
51444
  }
50675
51445
  for (const f of ["setup.py", "setup.cfg"]) {
50676
51446
  try {
50677
- const content = fs60.readFileSync(path72.join(rootPath, f), "utf-8");
51447
+ const content = fs62.readFileSync(path74.join(rootPath, f), "utf-8");
50678
51448
  if (content.includes(depName)) return true;
50679
51449
  } catch {
50680
51450
  }
50681
51451
  }
50682
51452
  try {
50683
- const content = fs60.readFileSync(path72.join(rootPath, "Pipfile"), "utf-8");
51453
+ const content = fs62.readFileSync(path74.join(rootPath, "Pipfile"), "utf-8");
50684
51454
  if (content.includes(depName)) return true;
50685
51455
  } catch {
50686
51456
  }
@@ -50928,8 +51698,8 @@ var CeleryPlugin = class {
50928
51698
  };
50929
51699
 
50930
51700
  // src/indexer/plugins/integration/tooling/n8n/index.ts
50931
- import fs61 from "fs";
50932
- import path73 from "path";
51701
+ import fs63 from "fs";
51702
+ import path75 from "path";
50933
51703
  var CODE_TYPES = /* @__PURE__ */ new Set([
50934
51704
  "n8n-nodes-base.code",
50935
51705
  "n8n-nodes-base.function",
@@ -51557,9 +52327,9 @@ function findDisconnectedNodes(workflow, connections) {
51557
52327
  function collectNodeFiles(dir) {
51558
52328
  const results = [];
51559
52329
  try {
51560
- const entries = fs61.readdirSync(dir, { withFileTypes: true });
52330
+ const entries = fs63.readdirSync(dir, { withFileTypes: true });
51561
52331
  for (const entry of entries) {
51562
- const fullPath = path73.join(dir, entry.name);
52332
+ const fullPath = path75.join(dir, entry.name);
51563
52333
  if (entry.isDirectory()) {
51564
52334
  results.push(...collectNodeFiles(fullPath));
51565
52335
  } else if (entry.name.endsWith(".node.ts")) {
@@ -51819,14 +52589,14 @@ var N8nPlugin = class {
51819
52589
  }
51820
52590
  }
51821
52591
  try {
51822
- if (fs61.existsSync(path73.join(ctx.rootPath, ".n8n"))) return true;
52592
+ if (fs63.existsSync(path75.join(ctx.rootPath, ".n8n"))) return true;
51823
52593
  } catch {
51824
52594
  }
51825
52595
  const nodeDirs = ["nodes", "src/nodes"];
51826
52596
  for (const dir of nodeDirs) {
51827
52597
  try {
51828
- const fullDir = path73.join(ctx.rootPath, dir);
51829
- if (fs61.existsSync(fullDir) && fs61.statSync(fullDir).isDirectory()) {
52598
+ const fullDir = path75.join(ctx.rootPath, dir);
52599
+ if (fs63.existsSync(fullDir) && fs63.statSync(fullDir).isDirectory()) {
51830
52600
  const files = collectNodeFiles(fullDir);
51831
52601
  if (files.length > 0) return true;
51832
52602
  }
@@ -51836,12 +52606,12 @@ var N8nPlugin = class {
51836
52606
  const searchDirs = ["workflows", "n8n", ".n8n", "."];
51837
52607
  for (const dir of searchDirs) {
51838
52608
  try {
51839
- const fullDir = path73.join(ctx.rootPath, dir);
51840
- if (!fs61.existsSync(fullDir) || !fs61.statSync(fullDir).isDirectory()) continue;
51841
- const files = fs61.readdirSync(fullDir).filter((f) => f.endsWith(".json"));
52609
+ const fullDir = path75.join(ctx.rootPath, dir);
52610
+ if (!fs63.existsSync(fullDir) || !fs63.statSync(fullDir).isDirectory()) continue;
52611
+ const files = fs63.readdirSync(fullDir).filter((f) => f.endsWith(".json"));
51842
52612
  for (const file of files.slice(0, 5)) {
51843
52613
  try {
51844
- const content = fs61.readFileSync(path73.join(fullDir, file));
52614
+ const content = fs63.readFileSync(path75.join(fullDir, file));
51845
52615
  if (parseN8nWorkflow(content)) return true;
51846
52616
  } catch {
51847
52617
  }
@@ -51903,7 +52673,7 @@ var N8nPlugin = class {
51903
52673
  routes: [],
51904
52674
  frameworkRole: role,
51905
52675
  metadata: {
51906
- workflowName: workflow.name ?? path73.basename(filePath, ".json"),
52676
+ workflowName: workflow.name ?? path75.basename(filePath, ".json"),
51907
52677
  workflowId: workflow.id,
51908
52678
  active: workflow.active ?? false,
51909
52679
  nodeCount: workflow.nodes.length,
@@ -52237,8 +53007,8 @@ function findNodeByteOffset(source, nodeName) {
52237
53007
  }
52238
53008
 
52239
53009
  // src/indexer/plugins/integration/tooling/data-fetching/index.ts
52240
- import fs62 from "fs";
52241
- import path74 from "path";
53010
+ import fs64 from "fs";
53011
+ import path76 from "path";
52242
53012
  var USE_QUERY_OBJECT_RE = /\b(useQuery|useInfiniteQuery)\s*\(\s*\{[^}]*?queryFn\s*:\s*[^}]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`]/g;
52243
53013
  var USE_QUERY_ARRAY_RE = /\b(useQuery|useInfiniteQuery)\s*\(\s*\[[^\]]*\]\s*,\s*(?:\([^)]*\)\s*=>|function\s*\([^)]*\)\s*\{)[^)]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`]/g;
52244
53014
  var USE_MUTATION_RE = /\b(useMutation)\s*\(\s*\{[^}]*?mutationFn\s*:\s*[^}]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`][^)]*?(?:method\s*:\s*['"`](\w+)['"`])?/g;
@@ -52310,8 +53080,8 @@ var DataFetchingPlugin = class {
52310
53080
  if ("@tanstack/react-query" in deps || "swr" in deps) return true;
52311
53081
  }
52312
53082
  try {
52313
- const pkgPath = path74.join(ctx.rootPath, "package.json");
52314
- const content = fs62.readFileSync(pkgPath, "utf-8");
53083
+ const pkgPath = path76.join(ctx.rootPath, "package.json");
53084
+ const content = fs64.readFileSync(pkgPath, "utf-8");
52315
53085
  const pkg = JSON.parse(content);
52316
53086
  const deps = {
52317
53087
  ...pkg.dependencies,
@@ -52354,6 +53124,713 @@ var DataFetchingPlugin = class {
52354
53124
  }
52355
53125
  };
52356
53126
 
53127
+ // src/indexer/plugins/integration/tooling/commander/index.ts
53128
+ import fs65 from "fs";
53129
+ import path77 from "path";
53130
+ var CLI_PACKAGES = ["commander", "yargs", "@oclif/core", "clipanion", "cac", "citty"];
53131
+ var COMMAND_RE = /\.command\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]*)['"]\s*)?/g;
53132
+ var OPTION_RE = /\.option\(\s*['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]*)['"]\s*)?/g;
53133
+ var NEW_COMMAND_RE = /new\s+Command\(\s*(?:['"]([^'"]+)['"])?\s*\)/g;
53134
+ var CLI_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:commander|Command|yargs)\b/;
53135
+ function extractCliCommands(source) {
53136
+ const commands = [];
53137
+ const cmdRe = new RegExp(COMMAND_RE.source, "g");
53138
+ let m;
53139
+ while ((m = cmdRe.exec(source)) !== null) {
53140
+ const fullCmd = m[1];
53141
+ const name = fullCmd.split(/\s/)[0];
53142
+ commands.push({
53143
+ name,
53144
+ description: m[2] || void 0,
53145
+ options: [],
53146
+ arguments: []
53147
+ });
53148
+ }
53149
+ const newCmdRe = new RegExp(NEW_COMMAND_RE.source, "g");
53150
+ while ((m = newCmdRe.exec(source)) !== null) {
53151
+ if (m[1] && !commands.some((c) => c.name === m[1])) {
53152
+ commands.push({ name: m[1], options: [], arguments: [] });
53153
+ }
53154
+ }
53155
+ return commands;
53156
+ }
53157
+ function extractCliOptions(source) {
53158
+ const options = [];
53159
+ const re = new RegExp(OPTION_RE.source, "g");
53160
+ let m;
53161
+ while ((m = re.exec(source)) !== null) {
53162
+ options.push(m[1]);
53163
+ }
53164
+ return options;
53165
+ }
53166
+ var CommanderPlugin = class {
53167
+ manifest = {
53168
+ name: "commander",
53169
+ version: "1.0.0",
53170
+ priority: 30,
53171
+ category: "tooling",
53172
+ dependencies: []
53173
+ };
53174
+ detect(ctx) {
53175
+ if (ctx.packageJson) {
53176
+ const deps = {
53177
+ ...ctx.packageJson.dependencies,
53178
+ ...ctx.packageJson.devDependencies
53179
+ };
53180
+ for (const pkg of CLI_PACKAGES) {
53181
+ if (pkg in deps) return true;
53182
+ }
53183
+ }
53184
+ try {
53185
+ const pkgPath = path77.join(ctx.rootPath, "package.json");
53186
+ const content = fs65.readFileSync(pkgPath, "utf-8");
53187
+ const pkg = JSON.parse(content);
53188
+ const deps = {
53189
+ ...pkg.dependencies,
53190
+ ...pkg.devDependencies
53191
+ };
53192
+ for (const p4 of CLI_PACKAGES) {
53193
+ if (p4 in deps) return true;
53194
+ }
53195
+ } catch {
53196
+ return false;
53197
+ }
53198
+ return false;
53199
+ }
53200
+ registerSchema() {
53201
+ return {
53202
+ edgeTypes: [
53203
+ { name: "cli_command", category: "cli", description: "CLI command definition" },
53204
+ { name: "cli_subcommand", category: "cli", description: "CLI subcommand relationship" }
53205
+ ]
53206
+ };
53207
+ }
53208
+ extractNodes(filePath, content, language) {
53209
+ if (!["typescript", "javascript"].includes(language)) {
53210
+ return ok({ status: "ok", symbols: [] });
53211
+ }
53212
+ const source = content.toString("utf-8");
53213
+ const result = { status: "ok", symbols: [], routes: [], edges: [] };
53214
+ const hasImport = CLI_IMPORT_RE.test(source);
53215
+ const commands = extractCliCommands(source);
53216
+ const options = extractCliOptions(source);
53217
+ if (commands.length > 0) {
53218
+ result.frameworkRole = "cli_command";
53219
+ for (const cmd of commands) {
53220
+ result.routes.push({
53221
+ method: "CLI",
53222
+ uri: cmd.name
53223
+ });
53224
+ }
53225
+ } else if (options.length > 0 && hasImport) {
53226
+ result.frameworkRole = "cli_options";
53227
+ } else if (hasImport) {
53228
+ result.frameworkRole = "cli_entry";
53229
+ }
53230
+ return ok(result);
53231
+ }
53232
+ resolveEdges(_ctx) {
53233
+ const edges = [];
53234
+ return ok(edges);
53235
+ }
53236
+ };
53237
+
53238
+ // src/indexer/plugins/integration/tooling/tree-sitter/index.ts
53239
+ import fs66 from "fs";
53240
+ import path78 from "path";
53241
+ var TREE_SITTER_PACKAGES = [
53242
+ "tree-sitter",
53243
+ "web-tree-sitter",
53244
+ "tree-sitter-typescript",
53245
+ "tree-sitter-javascript",
53246
+ "tree-sitter-python",
53247
+ "tree-sitter-go",
53248
+ "tree-sitter-rust",
53249
+ "tree-sitter-java",
53250
+ "tree-sitter-php",
53251
+ "tree-sitter-ruby",
53252
+ "tree-sitter-c",
53253
+ "tree-sitter-cpp",
53254
+ "tree-sitter-c-sharp",
53255
+ "tree-sitter-kotlin",
53256
+ "tree-sitter-scala",
53257
+ "tree-sitter-swift",
53258
+ "tree-sitter-dart"
53259
+ ];
53260
+ var PARSER_USAGE_RE = /(?:parser|Parser)\s*\.\s*(?:setLanguage|parse|getLanguage)\s*\(/g;
53261
+ var QUERY_PATTERN_RE = /\(\s*\w+(?:_\w+)*\s+(?:name|value|body):\s*\(\w+\)\s*@\w+/g;
53262
+ var TS_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:tree-sitter|Parser)\b/;
53263
+ var TreeSitterPlugin = class {
53264
+ manifest = {
53265
+ name: "tree-sitter",
53266
+ version: "1.0.0",
53267
+ priority: 35,
53268
+ category: "tooling",
53269
+ dependencies: []
53270
+ };
53271
+ detect(ctx) {
53272
+ if (ctx.packageJson) {
53273
+ const deps = {
53274
+ ...ctx.packageJson.dependencies,
53275
+ ...ctx.packageJson.devDependencies
53276
+ };
53277
+ for (const pkg of TREE_SITTER_PACKAGES) {
53278
+ if (pkg in deps) return true;
53279
+ }
53280
+ }
53281
+ try {
53282
+ const pkgPath = path78.join(ctx.rootPath, "package.json");
53283
+ const content = fs66.readFileSync(pkgPath, "utf-8");
53284
+ const pkg = JSON.parse(content);
53285
+ const deps = {
53286
+ ...pkg.dependencies,
53287
+ ...pkg.devDependencies
53288
+ };
53289
+ for (const p4 of TREE_SITTER_PACKAGES) {
53290
+ if (p4 in deps) return true;
53291
+ }
53292
+ } catch {
53293
+ return false;
53294
+ }
53295
+ return false;
53296
+ }
53297
+ registerSchema() {
53298
+ return {
53299
+ edgeTypes: [
53300
+ { name: "ts_parser_usage", category: "tree-sitter", description: "Tree-sitter parser usage" },
53301
+ { name: "ts_query_pattern", category: "tree-sitter", description: "Tree-sitter query pattern" }
53302
+ ]
53303
+ };
53304
+ }
53305
+ extractNodes(filePath, content, language) {
53306
+ if (!["typescript", "javascript"].includes(language)) {
53307
+ return ok({ status: "ok", symbols: [] });
53308
+ }
53309
+ const source = content.toString("utf-8");
53310
+ const result = { status: "ok", symbols: [], edges: [] };
53311
+ const hasImport = TS_IMPORT_RE.test(source);
53312
+ const hasParserUsage = PARSER_USAGE_RE.test(source);
53313
+ const hasQueryPatterns = QUERY_PATTERN_RE.test(source);
53314
+ if (hasQueryPatterns) {
53315
+ result.frameworkRole = "tree_sitter_queries";
53316
+ } else if (hasParserUsage) {
53317
+ result.frameworkRole = "tree_sitter_parser";
53318
+ } else if (hasImport) {
53319
+ result.frameworkRole = "tree_sitter_client";
53320
+ }
53321
+ return ok(result);
53322
+ }
53323
+ resolveEdges(_ctx) {
53324
+ return ok([]);
53325
+ }
53326
+ };
53327
+
53328
+ // src/indexer/plugins/integration/tooling/build-tools/index.ts
53329
+ import fs67 from "fs";
53330
+ import path79 from "path";
53331
+ var BUILD_PACKAGES = [
53332
+ "tsup",
53333
+ "esbuild",
53334
+ "rollup",
53335
+ "webpack",
53336
+ "@rspack/core",
53337
+ "vite",
53338
+ "turbo",
53339
+ "swc",
53340
+ "@swc/core",
53341
+ "parcel"
53342
+ ];
53343
+ var BUILD_CONFIG_FILES = [
53344
+ "tsup.config.ts",
53345
+ "tsup.config.js",
53346
+ "rollup.config.ts",
53347
+ "rollup.config.js",
53348
+ "rollup.config.mjs",
53349
+ "webpack.config.ts",
53350
+ "webpack.config.js",
53351
+ "vite.config.ts",
53352
+ "vite.config.js",
53353
+ "esbuild.config.ts",
53354
+ "esbuild.config.js",
53355
+ "turbo.json",
53356
+ ".swcrc"
53357
+ ];
53358
+ var ENTRY_RE = /entry\s*:\s*(?:\[([^\]]+)\]|\{([^}]+)\}|['"]([^'"]+)['"])/g;
53359
+ var FORMAT_RE = /format\s*:\s*\[([^\]]+)\]/g;
53360
+ var TARGET_RE = /target\s*:\s*(?:\[([^\]]+)\]|['"]([^'"]+)['"])/g;
53361
+ var EXTERNAL_RE = /external\s*:\s*\[([^\]]+)\]/g;
53362
+ var CONFIG_EXPORT_RE = /(?:defineConfig|export\s+default)\s*(?:\(\s*)?\{/;
53363
+ function extractBuildConfig(source) {
53364
+ const config = { entries: [], formats: [], targets: [], externals: [] };
53365
+ let m;
53366
+ const entryRe = new RegExp(ENTRY_RE.source, "g");
53367
+ while ((m = entryRe.exec(source)) !== null) {
53368
+ const raw = m[1] || m[2] || m[3] || "";
53369
+ const items = raw.match(/['"]([^'"]+)['"]/g);
53370
+ if (items) config.entries.push(...items.map((s) => s.replace(/['"]/g, "")));
53371
+ }
53372
+ const formatRe = new RegExp(FORMAT_RE.source, "g");
53373
+ while ((m = formatRe.exec(source)) !== null) {
53374
+ const items = m[1].match(/['"]([^'"]+)['"]/g);
53375
+ if (items) config.formats.push(...items.map((s) => s.replace(/['"]/g, "")));
53376
+ }
53377
+ const targetRe = new RegExp(TARGET_RE.source, "g");
53378
+ while ((m = targetRe.exec(source)) !== null) {
53379
+ const raw = m[1] || m[2] || "";
53380
+ const items = raw.match(/['"]([^'"]+)['"]/g);
53381
+ if (items) config.targets.push(...items.map((s) => s.replace(/['"]/g, "")));
53382
+ else if (m[2]) config.targets.push(m[2]);
53383
+ }
53384
+ const externalRe = new RegExp(EXTERNAL_RE.source, "g");
53385
+ while ((m = externalRe.exec(source)) !== null) {
53386
+ const items = m[1].match(/['"]([^'"]+)['"]/g);
53387
+ if (items) config.externals.push(...items.map((s) => s.replace(/['"]/g, "")));
53388
+ }
53389
+ return config;
53390
+ }
53391
+ var BuildToolsPlugin = class {
53392
+ manifest = {
53393
+ name: "build-tools",
53394
+ version: "1.0.0",
53395
+ priority: 35,
53396
+ category: "tooling",
53397
+ dependencies: []
53398
+ };
53399
+ detect(ctx) {
53400
+ if (ctx.packageJson) {
53401
+ const deps = {
53402
+ ...ctx.packageJson.dependencies,
53403
+ ...ctx.packageJson.devDependencies
53404
+ };
53405
+ for (const pkg of BUILD_PACKAGES) {
53406
+ if (pkg in deps) return true;
53407
+ }
53408
+ }
53409
+ for (const cf of BUILD_CONFIG_FILES) {
53410
+ if (ctx.configFiles.includes(cf)) return true;
53411
+ }
53412
+ try {
53413
+ const pkgPath = path79.join(ctx.rootPath, "package.json");
53414
+ const content = fs67.readFileSync(pkgPath, "utf-8");
53415
+ const pkg = JSON.parse(content);
53416
+ const deps = {
53417
+ ...pkg.dependencies,
53418
+ ...pkg.devDependencies
53419
+ };
53420
+ for (const p4 of BUILD_PACKAGES) {
53421
+ if (p4 in deps) return true;
53422
+ }
53423
+ } catch {
53424
+ return false;
53425
+ }
53426
+ return false;
53427
+ }
53428
+ registerSchema() {
53429
+ return {
53430
+ edgeTypes: [
53431
+ { name: "build_entry", category: "build", description: "Build entry point" },
53432
+ { name: "build_external", category: "build", description: "Externalized dependency" }
53433
+ ]
53434
+ };
53435
+ }
53436
+ extractNodes(filePath, content, language) {
53437
+ if (!["typescript", "javascript"].includes(language)) {
53438
+ return ok({ status: "ok", symbols: [] });
53439
+ }
53440
+ const source = content.toString("utf-8");
53441
+ const result = { status: "ok", symbols: [], edges: [] };
53442
+ const isConfigFile = BUILD_CONFIG_FILES.some((cf) => filePath.endsWith(cf.replace(/\.(ts|js|mjs)$/, "")));
53443
+ const hasConfigExport = CONFIG_EXPORT_RE.test(source);
53444
+ if (isConfigFile || hasConfigExport) {
53445
+ const config = extractBuildConfig(source);
53446
+ if (config.entries.length > 0 || config.formats.length > 0) {
53447
+ result.frameworkRole = "build_config";
53448
+ }
53449
+ }
53450
+ return ok(result);
53451
+ }
53452
+ resolveEdges(_ctx) {
53453
+ return ok([]);
53454
+ }
53455
+ };
53456
+
53457
+ // src/indexer/plugins/integration/tooling/github-actions/index.ts
53458
+ var WORKFLOW_NAME_RE = /^name:\s*['"]?([^\n'"]+)['"]?/m;
53459
+ var TRIGGER_RE = /^on:\s*(?:\[([^\]]+)\]|(\w+))/m;
53460
+ var JOB_RE = /^\s{2}(\w[\w-]*):\s*$/gm;
53461
+ var USES_RE = /uses:\s*['"]?([^'"\n]+)['"]?/g;
53462
+ function extractGhaWorkflow(source) {
53463
+ const result = { triggers: [], jobs: [], actions: [] };
53464
+ const nameMatch = WORKFLOW_NAME_RE.exec(source);
53465
+ if (nameMatch) result.name = nameMatch[1].trim();
53466
+ const triggerMatch = TRIGGER_RE.exec(source);
53467
+ if (triggerMatch) {
53468
+ if (triggerMatch[1]) {
53469
+ result.triggers = triggerMatch[1].split(",").map((t) => t.trim());
53470
+ } else if (triggerMatch[2]) {
53471
+ result.triggers = [triggerMatch[2]];
53472
+ }
53473
+ }
53474
+ const jobRe = new RegExp(JOB_RE.source, "gm");
53475
+ let m;
53476
+ while ((m = jobRe.exec(source)) !== null) {
53477
+ if (!["name", "on", "env", "permissions", "concurrency", "defaults"].includes(m[1])) {
53478
+ result.jobs.push(m[1]);
53479
+ }
53480
+ }
53481
+ const usesRe = new RegExp(USES_RE.source, "g");
53482
+ while ((m = usesRe.exec(source)) !== null) {
53483
+ const action = m[1].trim();
53484
+ if (!result.actions.includes(action)) result.actions.push(action);
53485
+ }
53486
+ return result;
53487
+ }
53488
+ var GithubActionsPlugin = class {
53489
+ manifest = {
53490
+ name: "github-actions",
53491
+ version: "1.0.0",
53492
+ priority: 35,
53493
+ category: "tooling",
53494
+ dependencies: []
53495
+ };
53496
+ detect(ctx) {
53497
+ return ctx.configFiles.some(
53498
+ (f) => f.startsWith(".github/workflows/") && (f.endsWith(".yml") || f.endsWith(".yaml"))
53499
+ );
53500
+ }
53501
+ registerSchema() {
53502
+ return {
53503
+ edgeTypes: [
53504
+ { name: "gha_job", category: "ci", description: "GitHub Actions job definition" },
53505
+ { name: "gha_uses", category: "ci", description: "GitHub Actions action reference" },
53506
+ { name: "gha_needs", category: "ci", description: "GitHub Actions job dependency" }
53507
+ ]
53508
+ };
53509
+ }
53510
+ extractNodes(filePath, content, language) {
53511
+ if (language !== "yaml" || !filePath.includes(".github/workflows")) {
53512
+ return ok({ status: "ok", symbols: [] });
53513
+ }
53514
+ const source = content.toString("utf-8");
53515
+ const result = { status: "ok", symbols: [], routes: [], edges: [] };
53516
+ const workflow = extractGhaWorkflow(source);
53517
+ if (workflow.jobs.length > 0 || workflow.triggers.length > 0) {
53518
+ result.frameworkRole = "gha_workflow";
53519
+ for (const job of workflow.jobs) {
53520
+ result.routes.push({
53521
+ method: "JOB",
53522
+ uri: job
53523
+ });
53524
+ }
53525
+ }
53526
+ return ok(result);
53527
+ }
53528
+ resolveEdges(_ctx) {
53529
+ return ok([]);
53530
+ }
53531
+ };
53532
+
53533
+ // src/indexer/plugins/integration/tooling/pino/index.ts
53534
+ import fs68 from "fs";
53535
+ import path80 from "path";
53536
+ var LOGGING_PACKAGES = ["pino", "winston", "bunyan", "log4js", "pino-pretty", "pino-http"];
53537
+ var LOGGER_CREATE_RE = /(?:pino|createLogger|getLogger|winston\.createLogger|bunyan\.createLogger)\s*\(/g;
53538
+ var LOG_CALL_RE = /(?:logger|log)\s*\.\s*(trace|debug|info|warn|error|fatal)\s*\(/g;
53539
+ var CHILD_LOGGER_RE = /\.child\s*\(\s*\{/g;
53540
+ var LOGGING_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:pino|winston|bunyan|log4js)\b/;
53541
+ var PinoPlugin = class {
53542
+ manifest = {
53543
+ name: "pino",
53544
+ version: "1.0.0",
53545
+ priority: 40,
53546
+ category: "tooling",
53547
+ dependencies: []
53548
+ };
53549
+ detect(ctx) {
53550
+ if (ctx.packageJson) {
53551
+ const deps = {
53552
+ ...ctx.packageJson.dependencies,
53553
+ ...ctx.packageJson.devDependencies
53554
+ };
53555
+ for (const pkg of LOGGING_PACKAGES) {
53556
+ if (pkg in deps) return true;
53557
+ }
53558
+ }
53559
+ try {
53560
+ const pkgPath = path80.join(ctx.rootPath, "package.json");
53561
+ const content = fs68.readFileSync(pkgPath, "utf-8");
53562
+ const pkg = JSON.parse(content);
53563
+ const deps = {
53564
+ ...pkg.dependencies,
53565
+ ...pkg.devDependencies
53566
+ };
53567
+ for (const p4 of LOGGING_PACKAGES) {
53568
+ if (p4 in deps) return true;
53569
+ }
53570
+ } catch {
53571
+ return false;
53572
+ }
53573
+ return false;
53574
+ }
53575
+ registerSchema() {
53576
+ return {
53577
+ edgeTypes: [
53578
+ { name: "logger_creates", category: "logging", description: "Logger instance creation" },
53579
+ { name: "logger_child", category: "logging", description: "Child logger derivation" }
53580
+ ]
53581
+ };
53582
+ }
53583
+ extractNodes(filePath, content, language) {
53584
+ if (!["typescript", "javascript"].includes(language)) {
53585
+ return ok({ status: "ok", symbols: [] });
53586
+ }
53587
+ const source = content.toString("utf-8");
53588
+ const result = { status: "ok", symbols: [], edges: [] };
53589
+ const hasImport = LOGGING_IMPORT_RE.test(source);
53590
+ const hasCreation = LOGGER_CREATE_RE.test(source);
53591
+ const hasChildLogger = CHILD_LOGGER_RE.test(source);
53592
+ const hasLogCalls = LOG_CALL_RE.test(source);
53593
+ if (hasCreation) {
53594
+ result.frameworkRole = "logger_config";
53595
+ } else if (hasChildLogger) {
53596
+ result.frameworkRole = "logger_child";
53597
+ } else if (hasImport) {
53598
+ result.frameworkRole = "logger_usage";
53599
+ } else if (hasLogCalls) {
53600
+ result.frameworkRole = "logger_usage";
53601
+ }
53602
+ return ok(result);
53603
+ }
53604
+ resolveEdges(_ctx) {
53605
+ return ok([]);
53606
+ }
53607
+ };
53608
+
53609
+ // src/indexer/plugins/integration/tooling/cosmiconfig/index.ts
53610
+ import fs69 from "fs";
53611
+ import path81 from "path";
53612
+ var CONFIG_PACKAGES = ["cosmiconfig", "lilconfig", "rc", "dotenv", "envalid", "env-var"];
53613
+ var EXPLORER_RE = /(?:cosmiconfig|lilconfig|cosmiconfigSync|lilconfigSync)\(\s*['"]([^'"]+)['"]/g;
53614
+ var DOTENV_RE = /(?:dotenv\.config|config)\s*\(\s*(?:\{[^}]*\})?\s*\)/g;
53615
+ var CONFIG_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:cosmiconfig|lilconfig|dotenv)\b/;
53616
+ var CosmiconfigPlugin = class {
53617
+ manifest = {
53618
+ name: "cosmiconfig",
53619
+ version: "1.0.0",
53620
+ priority: 40,
53621
+ category: "tooling",
53622
+ dependencies: []
53623
+ };
53624
+ detect(ctx) {
53625
+ if (ctx.packageJson) {
53626
+ const deps = {
53627
+ ...ctx.packageJson.dependencies,
53628
+ ...ctx.packageJson.devDependencies
53629
+ };
53630
+ for (const pkg of CONFIG_PACKAGES) {
53631
+ if (pkg in deps) return true;
53632
+ }
53633
+ }
53634
+ try {
53635
+ const pkgPath = path81.join(ctx.rootPath, "package.json");
53636
+ const content = fs69.readFileSync(pkgPath, "utf-8");
53637
+ const pkg = JSON.parse(content);
53638
+ const deps = {
53639
+ ...pkg.dependencies,
53640
+ ...pkg.devDependencies
53641
+ };
53642
+ for (const p4 of CONFIG_PACKAGES) {
53643
+ if (p4 in deps) return true;
53644
+ }
53645
+ } catch {
53646
+ return false;
53647
+ }
53648
+ return false;
53649
+ }
53650
+ registerSchema() {
53651
+ return {
53652
+ edgeTypes: [
53653
+ { name: "config_search", category: "config", description: "Config file search/load" }
53654
+ ]
53655
+ };
53656
+ }
53657
+ extractNodes(filePath, content, language) {
53658
+ if (!["typescript", "javascript"].includes(language)) {
53659
+ return ok({ status: "ok", symbols: [] });
53660
+ }
53661
+ const source = content.toString("utf-8");
53662
+ const result = { status: "ok", symbols: [], edges: [] };
53663
+ const hasImport = CONFIG_IMPORT_RE.test(source);
53664
+ const hasExplorer = EXPLORER_RE.test(source);
53665
+ const hasDotenv = DOTENV_RE.test(source);
53666
+ if (hasExplorer) {
53667
+ result.frameworkRole = "config_loader";
53668
+ } else if (hasDotenv) {
53669
+ result.frameworkRole = "env_loader";
53670
+ } else if (hasImport) {
53671
+ result.frameworkRole = "config_usage";
53672
+ }
53673
+ return ok(result);
53674
+ }
53675
+ resolveEdges(_ctx) {
53676
+ return ok([]);
53677
+ }
53678
+ };
53679
+
53680
+ // src/indexer/plugins/integration/tooling/neverthrow/index.ts
53681
+ import fs70 from "fs";
53682
+ import path82 from "path";
53683
+ var RESULT_PACKAGES = ["neverthrow", "ts-results", "oxide.ts", "true-myth", "@badrap/result"];
53684
+ var RESULT_TYPE_RE = /(?:Result|ResultAsync|Ok|Err)\s*<[^>]+>/g;
53685
+ var CHAIN_RE = /\.\s*(?:andThen|map|mapErr|orElse|match|unwrapOr|isOk|isErr)\s*\(/g;
53686
+ var WRAPPER_RE = /\b(?:fromPromise|fromThrowable|safeTry)\s*\(/g;
53687
+ var RESULT_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:neverthrow|ts-results|oxide\.ts|true-myth)\b/;
53688
+ var NeverthrowPlugin = class {
53689
+ manifest = {
53690
+ name: "neverthrow",
53691
+ version: "1.0.0",
53692
+ priority: 40,
53693
+ category: "tooling",
53694
+ dependencies: []
53695
+ };
53696
+ detect(ctx) {
53697
+ if (ctx.packageJson) {
53698
+ const deps = {
53699
+ ...ctx.packageJson.dependencies,
53700
+ ...ctx.packageJson.devDependencies
53701
+ };
53702
+ for (const pkg of RESULT_PACKAGES) {
53703
+ if (pkg in deps) return true;
53704
+ }
53705
+ }
53706
+ try {
53707
+ const pkgPath = path82.join(ctx.rootPath, "package.json");
53708
+ const content = fs70.readFileSync(pkgPath, "utf-8");
53709
+ const pkg = JSON.parse(content);
53710
+ const deps = {
53711
+ ...pkg.dependencies,
53712
+ ...pkg.devDependencies
53713
+ };
53714
+ for (const p4 of RESULT_PACKAGES) {
53715
+ if (p4 in deps) return true;
53716
+ }
53717
+ } catch {
53718
+ return false;
53719
+ }
53720
+ return false;
53721
+ }
53722
+ registerSchema() {
53723
+ return {
53724
+ edgeTypes: [
53725
+ { name: "result_chain", category: "error-handling", description: "Result type chain (andThen/map/mapErr)" },
53726
+ { name: "result_wraps", category: "error-handling", description: "fromPromise/fromThrowable wrapper" }
53727
+ ]
53728
+ };
53729
+ }
53730
+ extractNodes(filePath, content, language) {
53731
+ if (!["typescript", "javascript"].includes(language)) {
53732
+ return ok({ status: "ok", symbols: [] });
53733
+ }
53734
+ const source = content.toString("utf-8");
53735
+ const result = { status: "ok", symbols: [], edges: [] };
53736
+ const hasImport = RESULT_IMPORT_RE.test(source);
53737
+ const hasResultType = RESULT_TYPE_RE.test(source);
53738
+ const hasChain = CHAIN_RE.test(source);
53739
+ const hasWrapper = WRAPPER_RE.test(source);
53740
+ if (hasWrapper && hasImport) {
53741
+ result.frameworkRole = "result_boundary";
53742
+ } else if (hasChain && hasResultType) {
53743
+ result.frameworkRole = "result_chain";
53744
+ } else if (hasResultType || hasImport) {
53745
+ result.frameworkRole = "result_usage";
53746
+ }
53747
+ return ok(result);
53748
+ }
53749
+ resolveEdges(_ctx) {
53750
+ return ok([]);
53751
+ }
53752
+ };
53753
+
53754
+ // src/indexer/plugins/integration/tooling/clack/index.ts
53755
+ import fs71 from "fs";
53756
+ import path83 from "path";
53757
+ var PROMPT_PACKAGES = [
53758
+ "@clack/prompts",
53759
+ "@clack/core",
53760
+ "inquirer",
53761
+ "@inquirer/prompts",
53762
+ "prompts",
53763
+ "enquirer"
53764
+ ];
53765
+ var CLACK_FLOW_RE = /(?:intro|outro|spinner|log\.(?:info|warn|error|success|step))\s*\(/g;
53766
+ var CLACK_PROMPT_RE = /(?:text|select|confirm|multiselect|selectKey|group|password|isCancel)\s*\(\s*\{/g;
53767
+ var INQUIRER_PROMPT_RE = /(?:inquirer\.prompt|prompt)\s*\(\s*[\[{]/g;
53768
+ var PROMPT_IMPORT_RE = /(?:import|require)\s*(?:\(|{)?\s*.*(?:@clack\/prompts|inquirer|enquirer)\b/;
53769
+ var ClackPlugin = class {
53770
+ manifest = {
53771
+ name: "clack",
53772
+ version: "1.0.0",
53773
+ priority: 40,
53774
+ category: "tooling",
53775
+ dependencies: []
53776
+ };
53777
+ detect(ctx) {
53778
+ if (ctx.packageJson) {
53779
+ const deps = {
53780
+ ...ctx.packageJson.dependencies,
53781
+ ...ctx.packageJson.devDependencies
53782
+ };
53783
+ for (const pkg of PROMPT_PACKAGES) {
53784
+ if (pkg in deps) return true;
53785
+ }
53786
+ }
53787
+ try {
53788
+ const pkgPath = path83.join(ctx.rootPath, "package.json");
53789
+ const content = fs71.readFileSync(pkgPath, "utf-8");
53790
+ const pkg = JSON.parse(content);
53791
+ const deps = {
53792
+ ...pkg.dependencies,
53793
+ ...pkg.devDependencies
53794
+ };
53795
+ for (const p4 of PROMPT_PACKAGES) {
53796
+ if (p4 in deps) return true;
53797
+ }
53798
+ } catch {
53799
+ return false;
53800
+ }
53801
+ return false;
53802
+ }
53803
+ registerSchema() {
53804
+ return {
53805
+ edgeTypes: [
53806
+ { name: "prompt_flow", category: "cli", description: "Interactive prompt flow step" }
53807
+ ]
53808
+ };
53809
+ }
53810
+ extractNodes(filePath, content, language) {
53811
+ if (!["typescript", "javascript"].includes(language)) {
53812
+ return ok({ status: "ok", symbols: [] });
53813
+ }
53814
+ const source = content.toString("utf-8");
53815
+ const result = { status: "ok", symbols: [], edges: [] };
53816
+ const hasImport = PROMPT_IMPORT_RE.test(source);
53817
+ const hasClackFlow = CLACK_FLOW_RE.test(source);
53818
+ const hasClackPrompt = CLACK_PROMPT_RE.test(source);
53819
+ const hasInquirerPrompt = INQUIRER_PROMPT_RE.test(source);
53820
+ if (hasClackFlow && hasClackPrompt) {
53821
+ result.frameworkRole = "cli_wizard";
53822
+ } else if (hasClackPrompt || hasInquirerPrompt) {
53823
+ result.frameworkRole = "cli_prompts";
53824
+ } else if (hasImport) {
53825
+ result.frameworkRole = "cli_interactive";
53826
+ }
53827
+ return ok(result);
53828
+ }
53829
+ resolveEdges(_ctx) {
53830
+ return ok([]);
53831
+ }
53832
+ };
53833
+
52357
53834
  // src/indexer/plugins/integration/all.ts
52358
53835
  function createAllIntegrationPlugins() {
52359
53836
  return [
@@ -52379,6 +53856,7 @@ function createAllIntegrationPlugins() {
52379
53856
  new MongoosePlugin(),
52380
53857
  new SQLAlchemyPlugin(),
52381
53858
  new DrizzlePlugin(),
53859
+ new RawSqlPlugin(),
52382
53860
  // view
52383
53861
  new ReactPlugin(),
52384
53862
  new VueFrameworkPlugin(),
@@ -52396,6 +53874,7 @@ function createAllIntegrationPlugins() {
52396
53874
  new GraphQLPlugin(),
52397
53875
  new TrpcPlugin(),
52398
53876
  new DRFPlugin(),
53877
+ new McpSdkPlugin(),
52399
53878
  // validation
52400
53879
  new ZodPlugin(),
52401
53880
  new PydanticPlugin(),
@@ -52408,13 +53887,21 @@ function createAllIntegrationPlugins() {
52408
53887
  // tooling
52409
53888
  new CeleryPlugin(),
52410
53889
  new N8nPlugin(),
52411
- new DataFetchingPlugin()
53890
+ new DataFetchingPlugin(),
53891
+ new CommanderPlugin(),
53892
+ new TreeSitterPlugin(),
53893
+ new BuildToolsPlugin(),
53894
+ new GithubActionsPlugin(),
53895
+ new PinoPlugin(),
53896
+ new CosmiconfigPlugin(),
53897
+ new NeverthrowPlugin(),
53898
+ new ClackPlugin()
52412
53899
  ];
52413
53900
  }
52414
53901
 
52415
53902
  // src/indexer/watcher.ts
52416
53903
  import * as parcelWatcher from "@parcel/watcher";
52417
- import path75 from "path";
53904
+ import path84 from "path";
52418
53905
  var IGNORE_DIRS = [
52419
53906
  "vendor",
52420
53907
  "node_modules",
@@ -52439,7 +53926,7 @@ var FileWatcher2 = class {
52439
53926
  debounceTimer = null;
52440
53927
  pendingPaths = /* @__PURE__ */ new Set();
52441
53928
  async start(rootPath, config, onChanges, debounceMs = DEFAULT_DEBOUNCE_MS, onDeletes) {
52442
- const ignoreDirs = IGNORE_DIRS.map((d) => path75.join(rootPath, d));
53929
+ const ignoreDirs = IGNORE_DIRS.map((d) => path84.join(rootPath, d));
52443
53930
  this.subscription = await parcelWatcher.subscribe(
52444
53931
  rootPath,
52445
53932
  async (err32, events) => {
@@ -52494,13 +53981,13 @@ import http from "http";
52494
53981
 
52495
53982
  // src/cli-init.ts
52496
53983
  import { Command } from "commander";
52497
- import fs72 from "fs";
52498
- import path84 from "path";
53984
+ import fs81 from "fs";
53985
+ import path93 from "path";
52499
53986
  import * as p from "@clack/prompts";
52500
53987
 
52501
53988
  // src/init/mcp-client.ts
52502
- import fs63 from "fs";
52503
- import path76 from "path";
53989
+ import fs72 from "fs";
53990
+ import path85 from "path";
52504
53991
  import os3 from "os";
52505
53992
  var HOME = os3.homedir();
52506
53993
  function configureMcpClients(clientNames, projectRoot, opts) {
@@ -52511,9 +53998,9 @@ function configureMcpClients(clientNames, projectRoot, opts) {
52511
53998
  results.push({ target: name, action: "skipped", detail: "Unknown client" });
52512
53999
  continue;
52513
54000
  }
52514
- if (fs63.existsSync(configPath)) {
54001
+ if (fs72.existsSync(configPath)) {
52515
54002
  try {
52516
- const content = JSON.parse(fs63.readFileSync(configPath, "utf-8"));
54003
+ const content = JSON.parse(fs72.readFileSync(configPath, "utf-8"));
52517
54004
  if (content?.mcpServers?.["trace-mcp"]) {
52518
54005
  results.push({ target: configPath, action: "already_configured", detail: name });
52519
54006
  continue;
@@ -52539,13 +54026,13 @@ function configureMcpClients(clientNames, projectRoot, opts) {
52539
54026
  return results;
52540
54027
  }
52541
54028
  function writeTraceMcpEntry(configPath, entry) {
52542
- const dir = path76.dirname(configPath);
52543
- if (!fs63.existsSync(dir)) fs63.mkdirSync(dir, { recursive: true });
54029
+ const dir = path85.dirname(configPath);
54030
+ if (!fs72.existsSync(dir)) fs72.mkdirSync(dir, { recursive: true });
52544
54031
  let config = {};
52545
54032
  let isNew = true;
52546
- if (fs63.existsSync(configPath)) {
54033
+ if (fs72.existsSync(configPath)) {
52547
54034
  try {
52548
- config = JSON.parse(fs63.readFileSync(configPath, "utf-8"));
54035
+ config = JSON.parse(fs72.readFileSync(configPath, "utf-8"));
52549
54036
  isNew = false;
52550
54037
  } catch {
52551
54038
  }
@@ -52554,31 +54041,31 @@ function writeTraceMcpEntry(configPath, entry) {
52554
54041
  config.mcpServers = {};
52555
54042
  }
52556
54043
  config.mcpServers["trace-mcp"] = entry;
52557
- fs63.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
54044
+ fs72.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
52558
54045
  return isNew ? "created" : "updated";
52559
54046
  }
52560
54047
  function getConfigPath(name, projectRoot, scope) {
52561
54048
  switch (name) {
52562
54049
  case "claude-code":
52563
- return scope === "global" ? path76.join(HOME, ".claude.json") : path76.join(projectRoot, ".mcp.json");
54050
+ return scope === "global" ? path85.join(HOME, ".claude.json") : path85.join(projectRoot, ".mcp.json");
52564
54051
  case "claw-code":
52565
- return scope === "global" ? path76.join(HOME, ".claw", "settings.json") : path76.join(projectRoot, ".claw.json");
54052
+ return scope === "global" ? path85.join(HOME, ".claw", "settings.json") : path85.join(projectRoot, ".claw.json");
52566
54053
  case "claude-desktop":
52567
- return process.platform === "darwin" ? path76.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json") : path76.join(process.env.APPDATA ?? path76.join(HOME, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
54054
+ return process.platform === "darwin" ? path85.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json") : path85.join(process.env.APPDATA ?? path85.join(HOME, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
52568
54055
  case "cursor":
52569
- return scope === "global" ? path76.join(HOME, ".cursor", "mcp.json") : path76.join(projectRoot, ".cursor", "mcp.json");
54056
+ return scope === "global" ? path85.join(HOME, ".cursor", "mcp.json") : path85.join(projectRoot, ".cursor", "mcp.json");
52570
54057
  case "windsurf":
52571
- return scope === "global" ? path76.join(HOME, ".windsurf", "mcp.json") : path76.join(projectRoot, ".windsurf", "mcp.json");
54058
+ return scope === "global" ? path85.join(HOME, ".windsurf", "mcp.json") : path85.join(projectRoot, ".windsurf", "mcp.json");
52572
54059
  case "continue":
52573
- return scope === "global" ? path76.join(HOME, ".continue", "mcpServers", "mcp.json") : path76.join(projectRoot, ".continue", "mcpServers", "mcp.json");
54060
+ return scope === "global" ? path85.join(HOME, ".continue", "mcpServers", "mcp.json") : path85.join(projectRoot, ".continue", "mcpServers", "mcp.json");
52574
54061
  default:
52575
54062
  return null;
52576
54063
  }
52577
54064
  }
52578
54065
 
52579
54066
  // src/init/claude-md.ts
52580
- import fs64 from "fs";
52581
- import path77 from "path";
54067
+ import fs73 from "fs";
54068
+ import path86 from "path";
52582
54069
  var START_MARKER = "<!-- trace-mcp:start -->";
52583
54070
  var END_MARKER = "<!-- trace-mcp:end -->";
52584
54071
  var BLOCK = `${START_MARKER}
@@ -52608,33 +54095,33 @@ Use Read/Grep/Glob ONLY for non-code files (.md, .json, .yaml, config) or before
52608
54095
  Start sessions with \`get_project_map\` (summary_only=true).
52609
54096
  ${END_MARKER}`;
52610
54097
  function updateClaudeMd(projectRoot, opts) {
52611
- const filePath = opts.scope === "global" ? path77.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".claude", "CLAUDE.md") : path77.join(projectRoot, "CLAUDE.md");
54098
+ const filePath = opts.scope === "global" ? path86.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".claude", "CLAUDE.md") : path86.join(projectRoot, "CLAUDE.md");
52612
54099
  if (opts.dryRun) {
52613
- if (!fs64.existsSync(filePath)) {
54100
+ if (!fs73.existsSync(filePath)) {
52614
54101
  return { target: filePath, action: "skipped", detail: "Would create CLAUDE.md" };
52615
54102
  }
52616
- const content2 = fs64.readFileSync(filePath, "utf-8");
54103
+ const content2 = fs73.readFileSync(filePath, "utf-8");
52617
54104
  if (content2.includes(START_MARKER)) {
52618
54105
  return { target: filePath, action: "skipped", detail: "Would update trace-mcp block" };
52619
54106
  }
52620
54107
  return { target: filePath, action: "skipped", detail: "Would append trace-mcp block" };
52621
54108
  }
52622
- if (!fs64.existsSync(filePath)) {
52623
- fs64.writeFileSync(filePath, BLOCK + "\n");
54109
+ if (!fs73.existsSync(filePath)) {
54110
+ fs73.writeFileSync(filePath, BLOCK + "\n");
52624
54111
  return { target: filePath, action: "created" };
52625
54112
  }
52626
- const content = fs64.readFileSync(filePath, "utf-8");
54113
+ const content = fs73.readFileSync(filePath, "utf-8");
52627
54114
  if (content.includes(START_MARKER)) {
52628
54115
  const re = new RegExp(`${escapeRegex4(START_MARKER)}[\\s\\S]*?${escapeRegex4(END_MARKER)}`, "m");
52629
54116
  const updated = content.replace(re, BLOCK);
52630
54117
  if (updated === content) {
52631
54118
  return { target: filePath, action: "already_configured" };
52632
54119
  }
52633
- fs64.writeFileSync(filePath, updated);
54120
+ fs73.writeFileSync(filePath, updated);
52634
54121
  return { target: filePath, action: "updated" };
52635
54122
  }
52636
54123
  const separator = content.endsWith("\n") ? "\n" : "\n\n";
52637
- fs64.writeFileSync(filePath, content + separator + BLOCK + "\n");
54124
+ fs73.writeFileSync(filePath, content + separator + BLOCK + "\n");
52638
54125
  return { target: filePath, action: "updated", detail: "Appended trace-mcp block" };
52639
54126
  }
52640
54127
  function escapeRegex4(s) {
@@ -52642,51 +54129,57 @@ function escapeRegex4(s) {
52642
54129
  }
52643
54130
 
52644
54131
  // src/init/hooks.ts
52645
- import fs65 from "fs";
52646
- import path78 from "path";
54132
+ import fs74 from "fs";
54133
+ import path87 from "path";
52647
54134
  import os4 from "os";
52648
54135
 
52649
54136
  // src/init/types.ts
52650
- var GUARD_HOOK_VERSION = "0.1.0";
54137
+ var GUARD_HOOK_VERSION = "0.2.0";
52651
54138
  var REINDEX_HOOK_VERSION = "0.1.0";
52652
54139
 
52653
54140
  // src/init/hooks.ts
52654
54141
  var HOME2 = os4.homedir();
52655
- var HOOK_DEST = path78.join(HOME2, ".claude", "hooks", "trace-mcp-guard.sh");
52656
- var REINDEX_HOOK_DEST = path78.join(HOME2, ".claude", "hooks", "trace-mcp-reindex.sh");
52657
- var CLAW_HOOK_DEST = path78.join(HOME2, ".claw", "hooks", "trace-mcp-guard.sh");
52658
- var CLAW_REINDEX_HOOK_DEST = path78.join(HOME2, ".claw", "hooks", "trace-mcp-reindex.sh");
54142
+ var IS_WINDOWS = process.platform === "win32";
54143
+ var HOOK_EXT = IS_WINDOWS ? ".cmd" : ".sh";
54144
+ function hookCommand(hookPath) {
54145
+ return IS_WINDOWS ? `cmd /c "set CLAUDE_TOOL_NAME={{tool_name}}&& "${hookPath}""` : `CLAUDE_TOOL_NAME={{tool_name}} ${hookPath}`;
54146
+ }
54147
+ var HOOK_DEST = path87.join(HOME2, ".claude", "hooks", `trace-mcp-guard${HOOK_EXT}`);
54148
+ var REINDEX_HOOK_DEST = path87.join(HOME2, ".claude", "hooks", `trace-mcp-reindex${HOOK_EXT}`);
54149
+ var CLAW_HOOK_DEST = path87.join(HOME2, ".claw", "hooks", `trace-mcp-guard${HOOK_EXT}`);
54150
+ var CLAW_REINDEX_HOOK_DEST = path87.join(HOME2, ".claw", "hooks", `trace-mcp-reindex${HOOK_EXT}`);
52659
54151
  function getHookSourcePath() {
54152
+ const filename = `trace-mcp-guard${HOOK_EXT}`;
52660
54153
  const candidates = [
52661
- path78.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-guard.sh"),
52662
- path78.resolve(process.cwd(), "hooks", "trace-mcp-guard.sh")
54154
+ path87.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", filename),
54155
+ path87.resolve(process.cwd(), "hooks", filename)
52663
54156
  ];
52664
54157
  for (const c of candidates) {
52665
- if (fs65.existsSync(c)) return c;
54158
+ if (fs74.existsSync(c)) return c;
52666
54159
  }
52667
- throw new Error("Could not find hooks/trace-mcp-guard.sh \u2014 trace-mcp installation may be corrupted.");
54160
+ throw new Error(`Could not find hooks/${filename} \u2014 trace-mcp installation may be corrupted.`);
52668
54161
  }
52669
54162
  function installGuardHook(opts) {
52670
- const settingsPath = opts.global ? path78.join(HOME2, ".claude", "settings.json") : path78.resolve(process.cwd(), ".claude", "settings.local.json");
54163
+ const settingsPath = opts.global ? path87.join(HOME2, ".claude", "settings.json") : path87.resolve(process.cwd(), ".claude", "settings.local.json");
52671
54164
  if (opts.dryRun) {
52672
54165
  return { target: HOOK_DEST, action: "skipped", detail: "Would install guard hook" };
52673
54166
  }
52674
54167
  const hookSrc = getHookSourcePath();
52675
- const hookDir = path78.dirname(HOOK_DEST);
52676
- if (!fs65.existsSync(hookDir)) fs65.mkdirSync(hookDir, { recursive: true });
52677
- const isUpdate = fs65.existsSync(HOOK_DEST);
52678
- fs65.copyFileSync(hookSrc, HOOK_DEST);
52679
- fs65.chmodSync(HOOK_DEST, 493);
52680
- const settingsDir = path78.dirname(settingsPath);
52681
- if (!fs65.existsSync(settingsDir)) fs65.mkdirSync(settingsDir, { recursive: true });
52682
- const settings = fs65.existsSync(settingsPath) ? JSON.parse(fs65.readFileSync(settingsPath, "utf-8")) : {};
54168
+ const hookDir = path87.dirname(HOOK_DEST);
54169
+ if (!fs74.existsSync(hookDir)) fs74.mkdirSync(hookDir, { recursive: true });
54170
+ const isUpdate = fs74.existsSync(HOOK_DEST);
54171
+ fs74.copyFileSync(hookSrc, HOOK_DEST);
54172
+ if (!IS_WINDOWS) fs74.chmodSync(HOOK_DEST, 493);
54173
+ const settingsDir = path87.dirname(settingsPath);
54174
+ if (!fs74.existsSync(settingsDir)) fs74.mkdirSync(settingsDir, { recursive: true });
54175
+ const settings = fs74.existsSync(settingsPath) ? JSON.parse(fs74.readFileSync(settingsPath, "utf-8")) : {};
52683
54176
  if (!settings.hooks) settings.hooks = {};
52684
54177
  if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
52685
54178
  const hookEntry = {
52686
54179
  matcher: "Read|Grep|Glob|Bash",
52687
54180
  hooks: [{
52688
54181
  type: "command",
52689
- command: `CLAUDE_TOOL_NAME={{tool_name}} ${HOOK_DEST}`
54182
+ command: hookCommand(HOOK_DEST)
52690
54183
  }]
52691
54184
  };
52692
54185
  const existing = settings.hooks.PreToolUse.find(
@@ -52695,17 +54188,17 @@ function installGuardHook(opts) {
52695
54188
  if (!existing) {
52696
54189
  settings.hooks.PreToolUse.push(hookEntry);
52697
54190
  }
52698
- fs65.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
52699
- const clawHome = path78.join(HOME2, ".claw");
52700
- if (fs65.existsSync(clawHome)) {
52701
- const clawHookDir = path78.dirname(CLAW_HOOK_DEST);
52702
- if (!fs65.existsSync(clawHookDir)) fs65.mkdirSync(clawHookDir, { recursive: true });
52703
- fs65.copyFileSync(hookSrc, CLAW_HOOK_DEST);
52704
- fs65.chmodSync(CLAW_HOOK_DEST, 493);
52705
- const clawSettingsPath = opts.global ? path78.join(clawHome, "settings.json") : path78.resolve(process.cwd(), ".claw", "settings.local.json");
52706
- const clawSettingsDir = path78.dirname(clawSettingsPath);
52707
- if (!fs65.existsSync(clawSettingsDir)) fs65.mkdirSync(clawSettingsDir, { recursive: true });
52708
- const clawSettings = fs65.existsSync(clawSettingsPath) ? JSON.parse(fs65.readFileSync(clawSettingsPath, "utf-8")) : {};
54191
+ fs74.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
54192
+ const clawHome = path87.join(HOME2, ".claw");
54193
+ if (fs74.existsSync(clawHome)) {
54194
+ const clawHookDir = path87.dirname(CLAW_HOOK_DEST);
54195
+ if (!fs74.existsSync(clawHookDir)) fs74.mkdirSync(clawHookDir, { recursive: true });
54196
+ fs74.copyFileSync(hookSrc, CLAW_HOOK_DEST);
54197
+ if (!IS_WINDOWS) fs74.chmodSync(CLAW_HOOK_DEST, 493);
54198
+ const clawSettingsPath = opts.global ? path87.join(clawHome, "settings.json") : path87.resolve(process.cwd(), ".claw", "settings.local.json");
54199
+ const clawSettingsDir = path87.dirname(clawSettingsPath);
54200
+ if (!fs74.existsSync(clawSettingsDir)) fs74.mkdirSync(clawSettingsDir, { recursive: true });
54201
+ const clawSettings = fs74.existsSync(clawSettingsPath) ? JSON.parse(fs74.readFileSync(clawSettingsPath, "utf-8")) : {};
52709
54202
  if (!clawSettings.hooks) clawSettings.hooks = {};
52710
54203
  if (!clawSettings.hooks.PreToolUse) clawSettings.hooks.PreToolUse = [];
52711
54204
  const clawExisting = clawSettings.hooks.PreToolUse.find(
@@ -52714,10 +54207,10 @@ function installGuardHook(opts) {
52714
54207
  if (!clawExisting) {
52715
54208
  clawSettings.hooks.PreToolUse.push({
52716
54209
  matcher: "Read|Grep|Glob|Bash",
52717
- hooks: [{ type: "command", command: `CLAUDE_TOOL_NAME={{tool_name}} ${CLAW_HOOK_DEST}` }]
54210
+ hooks: [{ type: "command", command: hookCommand(CLAW_HOOK_DEST) }]
52718
54211
  });
52719
54212
  }
52720
- fs65.writeFileSync(clawSettingsPath, JSON.stringify(clawSettings, null, 2) + "\n");
54213
+ fs74.writeFileSync(clawSettingsPath, JSON.stringify(clawSettings, null, 2) + "\n");
52721
54214
  }
52722
54215
  return {
52723
54216
  target: HOOK_DEST,
@@ -52726,9 +54219,9 @@ function installGuardHook(opts) {
52726
54219
  };
52727
54220
  }
52728
54221
  function uninstallGuardHook(opts) {
52729
- const settingsPath = opts.global ? path78.join(HOME2, ".claude", "settings.json") : path78.resolve(process.cwd(), ".claude", "settings.local.json");
52730
- if (fs65.existsSync(settingsPath)) {
52731
- const settings = JSON.parse(fs65.readFileSync(settingsPath, "utf-8"));
54222
+ const settingsPath = opts.global ? path87.join(HOME2, ".claude", "settings.json") : path87.resolve(process.cwd(), ".claude", "settings.local.json");
54223
+ if (fs74.existsSync(settingsPath)) {
54224
+ const settings = JSON.parse(fs74.readFileSync(settingsPath, "utf-8"));
52732
54225
  const pre = settings.hooks?.PreToolUse;
52733
54226
  if (Array.isArray(pre)) {
52734
54227
  settings.hooks.PreToolUse = pre.filter(
@@ -52736,13 +54229,13 @@ function uninstallGuardHook(opts) {
52736
54229
  );
52737
54230
  if (settings.hooks.PreToolUse.length === 0) delete settings.hooks.PreToolUse;
52738
54231
  if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
52739
- fs65.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
54232
+ fs74.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
52740
54233
  }
52741
54234
  }
52742
- if (fs65.existsSync(HOOK_DEST)) fs65.unlinkSync(HOOK_DEST);
52743
- const clawSettingsPath = opts.global ? path78.join(HOME2, ".claw", "settings.json") : path78.resolve(process.cwd(), ".claw", "settings.local.json");
52744
- if (fs65.existsSync(clawSettingsPath)) {
52745
- const clawSettings = JSON.parse(fs65.readFileSync(clawSettingsPath, "utf-8"));
54235
+ if (fs74.existsSync(HOOK_DEST)) fs74.unlinkSync(HOOK_DEST);
54236
+ const clawSettingsPath = opts.global ? path87.join(HOME2, ".claw", "settings.json") : path87.resolve(process.cwd(), ".claw", "settings.local.json");
54237
+ if (fs74.existsSync(clawSettingsPath)) {
54238
+ const clawSettings = JSON.parse(fs74.readFileSync(clawSettingsPath, "utf-8"));
52746
54239
  const clawPre = clawSettings.hooks?.PreToolUse;
52747
54240
  if (Array.isArray(clawPre)) {
52748
54241
  clawSettings.hooks.PreToolUse = clawPre.filter(
@@ -52750,10 +54243,10 @@ function uninstallGuardHook(opts) {
52750
54243
  );
52751
54244
  if (clawSettings.hooks.PreToolUse.length === 0) delete clawSettings.hooks.PreToolUse;
52752
54245
  if (clawSettings.hooks && Object.keys(clawSettings.hooks).length === 0) delete clawSettings.hooks;
52753
- fs65.writeFileSync(clawSettingsPath, JSON.stringify(clawSettings, null, 2) + "\n");
54246
+ fs74.writeFileSync(clawSettingsPath, JSON.stringify(clawSettings, null, 2) + "\n");
52754
54247
  }
52755
54248
  }
52756
- if (fs65.existsSync(CLAW_HOOK_DEST)) fs65.unlinkSync(CLAW_HOOK_DEST);
54249
+ if (fs74.existsSync(CLAW_HOOK_DEST)) fs74.unlinkSync(CLAW_HOOK_DEST);
52757
54250
  return { target: HOOK_DEST, action: "updated", detail: "Removed" };
52758
54251
  }
52759
54252
  function isHookOutdated(installedVersion) {
@@ -52761,36 +54254,37 @@ function isHookOutdated(installedVersion) {
52761
54254
  return installedVersion !== GUARD_HOOK_VERSION;
52762
54255
  }
52763
54256
  function getReindexHookSourcePath() {
54257
+ const filename = `trace-mcp-reindex${HOOK_EXT}`;
52764
54258
  const candidates = [
52765
- path78.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-reindex.sh"),
52766
- path78.resolve(process.cwd(), "hooks", "trace-mcp-reindex.sh")
54259
+ path87.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", filename),
54260
+ path87.resolve(process.cwd(), "hooks", filename)
52767
54261
  ];
52768
54262
  for (const c of candidates) {
52769
- if (fs65.existsSync(c)) return c;
54263
+ if (fs74.existsSync(c)) return c;
52770
54264
  }
52771
- throw new Error("Could not find hooks/trace-mcp-reindex.sh \u2014 trace-mcp installation may be corrupted.");
54265
+ throw new Error(`Could not find hooks/${filename} \u2014 trace-mcp installation may be corrupted.`);
52772
54266
  }
52773
54267
  function installReindexHook(opts) {
52774
- const settingsPath = opts.global ? path78.join(HOME2, ".claude", "settings.json") : path78.resolve(process.cwd(), ".claude", "settings.local.json");
54268
+ const settingsPath = opts.global ? path87.join(HOME2, ".claude", "settings.json") : path87.resolve(process.cwd(), ".claude", "settings.local.json");
52775
54269
  if (opts.dryRun) {
52776
54270
  return { target: REINDEX_HOOK_DEST, action: "skipped", detail: "Would install reindex hook" };
52777
54271
  }
52778
54272
  const hookSrc = getReindexHookSourcePath();
52779
- const hookDir = path78.dirname(REINDEX_HOOK_DEST);
52780
- if (!fs65.existsSync(hookDir)) fs65.mkdirSync(hookDir, { recursive: true });
52781
- const isUpdate = fs65.existsSync(REINDEX_HOOK_DEST);
52782
- fs65.copyFileSync(hookSrc, REINDEX_HOOK_DEST);
52783
- fs65.chmodSync(REINDEX_HOOK_DEST, 493);
52784
- const settingsDir = path78.dirname(settingsPath);
52785
- if (!fs65.existsSync(settingsDir)) fs65.mkdirSync(settingsDir, { recursive: true });
52786
- const settings = fs65.existsSync(settingsPath) ? JSON.parse(fs65.readFileSync(settingsPath, "utf-8")) : {};
54273
+ const hookDir = path87.dirname(REINDEX_HOOK_DEST);
54274
+ if (!fs74.existsSync(hookDir)) fs74.mkdirSync(hookDir, { recursive: true });
54275
+ const isUpdate = fs74.existsSync(REINDEX_HOOK_DEST);
54276
+ fs74.copyFileSync(hookSrc, REINDEX_HOOK_DEST);
54277
+ if (!IS_WINDOWS) fs74.chmodSync(REINDEX_HOOK_DEST, 493);
54278
+ const settingsDir = path87.dirname(settingsPath);
54279
+ if (!fs74.existsSync(settingsDir)) fs74.mkdirSync(settingsDir, { recursive: true });
54280
+ const settings = fs74.existsSync(settingsPath) ? JSON.parse(fs74.readFileSync(settingsPath, "utf-8")) : {};
52787
54281
  if (!settings.hooks) settings.hooks = {};
52788
54282
  if (!settings.hooks.PostToolUse) settings.hooks.PostToolUse = [];
52789
54283
  const hookEntry = {
52790
54284
  matcher: "Edit|Write|MultiEdit",
52791
54285
  hooks: [{
52792
54286
  type: "command",
52793
- command: `CLAUDE_TOOL_NAME={{tool_name}} ${REINDEX_HOOK_DEST}`
54287
+ command: hookCommand(REINDEX_HOOK_DEST)
52794
54288
  }]
52795
54289
  };
52796
54290
  const existing = settings.hooks.PostToolUse.find(
@@ -52799,17 +54293,17 @@ function installReindexHook(opts) {
52799
54293
  if (!existing) {
52800
54294
  settings.hooks.PostToolUse.push(hookEntry);
52801
54295
  }
52802
- fs65.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
52803
- const clawHome = path78.join(HOME2, ".claw");
52804
- if (fs65.existsSync(clawHome)) {
52805
- const clawHookDir = path78.dirname(CLAW_REINDEX_HOOK_DEST);
52806
- if (!fs65.existsSync(clawHookDir)) fs65.mkdirSync(clawHookDir, { recursive: true });
52807
- fs65.copyFileSync(hookSrc, CLAW_REINDEX_HOOK_DEST);
52808
- fs65.chmodSync(CLAW_REINDEX_HOOK_DEST, 493);
52809
- const clawSettingsPath = opts.global ? path78.join(clawHome, "settings.json") : path78.resolve(process.cwd(), ".claw", "settings.local.json");
52810
- const clawSettingsDir = path78.dirname(clawSettingsPath);
52811
- if (!fs65.existsSync(clawSettingsDir)) fs65.mkdirSync(clawSettingsDir, { recursive: true });
52812
- const clawSettings = fs65.existsSync(clawSettingsPath) ? JSON.parse(fs65.readFileSync(clawSettingsPath, "utf-8")) : {};
54296
+ fs74.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
54297
+ const clawHome = path87.join(HOME2, ".claw");
54298
+ if (fs74.existsSync(clawHome)) {
54299
+ const clawHookDir = path87.dirname(CLAW_REINDEX_HOOK_DEST);
54300
+ if (!fs74.existsSync(clawHookDir)) fs74.mkdirSync(clawHookDir, { recursive: true });
54301
+ fs74.copyFileSync(hookSrc, CLAW_REINDEX_HOOK_DEST);
54302
+ if (!IS_WINDOWS) fs74.chmodSync(CLAW_REINDEX_HOOK_DEST, 493);
54303
+ const clawSettingsPath = opts.global ? path87.join(clawHome, "settings.json") : path87.resolve(process.cwd(), ".claw", "settings.local.json");
54304
+ const clawSettingsDir = path87.dirname(clawSettingsPath);
54305
+ if (!fs74.existsSync(clawSettingsDir)) fs74.mkdirSync(clawSettingsDir, { recursive: true });
54306
+ const clawSettings = fs74.existsSync(clawSettingsPath) ? JSON.parse(fs74.readFileSync(clawSettingsPath, "utf-8")) : {};
52813
54307
  if (!clawSettings.hooks) clawSettings.hooks = {};
52814
54308
  if (!clawSettings.hooks.PostToolUse) clawSettings.hooks.PostToolUse = [];
52815
54309
  const clawExisting = clawSettings.hooks.PostToolUse.find(
@@ -52818,10 +54312,10 @@ function installReindexHook(opts) {
52818
54312
  if (!clawExisting) {
52819
54313
  clawSettings.hooks.PostToolUse.push({
52820
54314
  matcher: "Edit|Write|MultiEdit",
52821
- hooks: [{ type: "command", command: `CLAUDE_TOOL_NAME={{tool_name}} ${CLAW_REINDEX_HOOK_DEST}` }]
54315
+ hooks: [{ type: "command", command: hookCommand(CLAW_REINDEX_HOOK_DEST) }]
52822
54316
  });
52823
54317
  }
52824
- fs65.writeFileSync(clawSettingsPath, JSON.stringify(clawSettings, null, 2) + "\n");
54318
+ fs74.writeFileSync(clawSettingsPath, JSON.stringify(clawSettings, null, 2) + "\n");
52825
54319
  }
52826
54320
  return {
52827
54321
  target: REINDEX_HOOK_DEST,
@@ -52831,8 +54325,8 @@ function installReindexHook(opts) {
52831
54325
  }
52832
54326
 
52833
54327
  // src/init/ide-rules.ts
52834
- import fs66 from "fs";
52835
- import path79 from "path";
54328
+ import fs75 from "fs";
54329
+ import path88 from "path";
52836
54330
  var START_MARKER2 = "<!-- trace-mcp:start -->";
52837
54331
  var END_MARKER2 = "<!-- trace-mcp:end -->";
52838
54332
  var TOOL_ROUTING_POLICY = `IMPORTANT: For ANY code exploration task, ALWAYS use trace-mcp tools first. NEVER use built-in search/grep/file listing for navigating source code.
@@ -52868,12 +54362,12 @@ alwaysApply: true
52868
54362
  ${TOOL_ROUTING_POLICY}
52869
54363
  `;
52870
54364
  function installCursorRules(projectRoot, opts) {
52871
- const base = opts.global ? path79.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".cursor") : path79.join(projectRoot, ".cursor");
52872
- const rulesDir = path79.join(base, "rules");
52873
- const filePath = path79.join(rulesDir, "trace-mcp.mdc");
54365
+ const base = opts.global ? path88.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".cursor") : path88.join(projectRoot, ".cursor");
54366
+ const rulesDir = path88.join(base, "rules");
54367
+ const filePath = path88.join(rulesDir, "trace-mcp.mdc");
52874
54368
  if (opts.dryRun) {
52875
- if (fs66.existsSync(filePath)) {
52876
- const content = fs66.readFileSync(filePath, "utf-8");
54369
+ if (fs75.existsSync(filePath)) {
54370
+ const content = fs75.readFileSync(filePath, "utf-8");
52877
54371
  if (content === CURSOR_RULE) {
52878
54372
  return { target: filePath, action: "skipped", detail: "Already up to date" };
52879
54373
  }
@@ -52881,16 +54375,16 @@ function installCursorRules(projectRoot, opts) {
52881
54375
  }
52882
54376
  return { target: filePath, action: "skipped", detail: "Would create trace-mcp.mdc" };
52883
54377
  }
52884
- if (fs66.existsSync(filePath)) {
52885
- const content = fs66.readFileSync(filePath, "utf-8");
54378
+ if (fs75.existsSync(filePath)) {
54379
+ const content = fs75.readFileSync(filePath, "utf-8");
52886
54380
  if (content === CURSOR_RULE) {
52887
54381
  return { target: filePath, action: "already_configured" };
52888
54382
  }
52889
- fs66.writeFileSync(filePath, CURSOR_RULE);
54383
+ fs75.writeFileSync(filePath, CURSOR_RULE);
52890
54384
  return { target: filePath, action: "updated" };
52891
54385
  }
52892
- fs66.mkdirSync(rulesDir, { recursive: true });
52893
- fs66.writeFileSync(filePath, CURSOR_RULE);
54386
+ fs75.mkdirSync(rulesDir, { recursive: true });
54387
+ fs75.writeFileSync(filePath, CURSOR_RULE);
52894
54388
  return { target: filePath, action: "created" };
52895
54389
  }
52896
54390
  var WINDSURF_BLOCK = `${START_MARKER2}
@@ -52899,33 +54393,33 @@ var WINDSURF_BLOCK = `${START_MARKER2}
52899
54393
  ${TOOL_ROUTING_POLICY}
52900
54394
  ${END_MARKER2}`;
52901
54395
  function installWindsurfRules(projectRoot, opts) {
52902
- const filePath = opts.global ? path79.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".windsurfrules") : path79.join(projectRoot, ".windsurfrules");
54396
+ const filePath = opts.global ? path88.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".windsurfrules") : path88.join(projectRoot, ".windsurfrules");
52903
54397
  if (opts.dryRun) {
52904
- if (!fs66.existsSync(filePath)) {
54398
+ if (!fs75.existsSync(filePath)) {
52905
54399
  return { target: filePath, action: "skipped", detail: "Would create .windsurfrules" };
52906
54400
  }
52907
- const content2 = fs66.readFileSync(filePath, "utf-8");
54401
+ const content2 = fs75.readFileSync(filePath, "utf-8");
52908
54402
  if (content2.includes(START_MARKER2)) {
52909
54403
  return { target: filePath, action: "skipped", detail: "Would update trace-mcp block" };
52910
54404
  }
52911
54405
  return { target: filePath, action: "skipped", detail: "Would append trace-mcp block" };
52912
54406
  }
52913
- if (!fs66.existsSync(filePath)) {
52914
- fs66.writeFileSync(filePath, WINDSURF_BLOCK + "\n");
54407
+ if (!fs75.existsSync(filePath)) {
54408
+ fs75.writeFileSync(filePath, WINDSURF_BLOCK + "\n");
52915
54409
  return { target: filePath, action: "created" };
52916
54410
  }
52917
- const content = fs66.readFileSync(filePath, "utf-8");
54411
+ const content = fs75.readFileSync(filePath, "utf-8");
52918
54412
  if (content.includes(START_MARKER2)) {
52919
54413
  const re = new RegExp(`${escapeRegex5(START_MARKER2)}[\\s\\S]*?${escapeRegex5(END_MARKER2)}`, "m");
52920
54414
  const updated = content.replace(re, WINDSURF_BLOCK);
52921
54415
  if (updated === content) {
52922
54416
  return { target: filePath, action: "already_configured" };
52923
54417
  }
52924
- fs66.writeFileSync(filePath, updated);
54418
+ fs75.writeFileSync(filePath, updated);
52925
54419
  return { target: filePath, action: "updated" };
52926
54420
  }
52927
54421
  const separator = content.endsWith("\n") ? "\n" : "\n\n";
52928
- fs66.writeFileSync(filePath, content + separator + WINDSURF_BLOCK + "\n");
54422
+ fs75.writeFileSync(filePath, content + separator + WINDSURF_BLOCK + "\n");
52929
54423
  return { target: filePath, action: "updated", detail: "Appended trace-mcp block" };
52930
54424
  }
52931
54425
  function escapeRegex5(s) {
@@ -52933,13 +54427,13 @@ function escapeRegex5(s) {
52933
54427
  }
52934
54428
 
52935
54429
  // src/init/detector.ts
52936
- import fs67 from "fs";
52937
- import path80 from "path";
54430
+ import fs76 from "fs";
54431
+ import path89 from "path";
52938
54432
  import os5 from "os";
52939
54433
  import Database6 from "better-sqlite3";
52940
54434
  var HOME3 = os5.homedir();
52941
54435
  function detectProject(dir) {
52942
- const projectRoot = path80.resolve(dir);
54436
+ const projectRoot = path89.resolve(dir);
52943
54437
  const ctx = buildProjectContext(projectRoot);
52944
54438
  const packageManagers = detectPackageManagers(projectRoot);
52945
54439
  const registry = new PluginRegistry();
@@ -52968,9 +54462,9 @@ function detectProject(dir) {
52968
54462
  const mcpClients = detectMcpClients(projectRoot);
52969
54463
  const existingConfig = detectExistingConfig(projectRoot);
52970
54464
  const existingDb = detectExistingDb(projectRoot);
52971
- const claudeMdPath = path80.join(projectRoot, "CLAUDE.md");
52972
- const hasClaudeMd = fs67.existsSync(claudeMdPath);
52973
- const claudeMdHasTraceMcpBlock = hasClaudeMd && fs67.readFileSync(claudeMdPath, "utf-8").includes("<!-- trace-mcp:start -->");
54465
+ const claudeMdPath = path89.join(projectRoot, "CLAUDE.md");
54466
+ const hasClaudeMd = fs76.existsSync(claudeMdPath);
54467
+ const claudeMdHasTraceMcpBlock = hasClaudeMd && fs76.readFileSync(claudeMdPath, "utf-8").includes("<!-- trace-mcp:start -->");
52974
54468
  const { hasGuardHook, guardHookVersion } = detectGuardHook();
52975
54469
  return {
52976
54470
  projectRoot,
@@ -52989,8 +54483,8 @@ function detectProject(dir) {
52989
54483
  function detectPackageManagers(root) {
52990
54484
  const managers = [];
52991
54485
  const check = (file, type, lockfiles) => {
52992
- if (fs67.existsSync(path80.join(root, file))) {
52993
- const lockfile = lockfiles.find((l) => fs67.existsSync(path80.join(root, l)));
54486
+ if (fs76.existsSync(path89.join(root, file))) {
54487
+ const lockfile = lockfiles.find((l) => fs76.existsSync(path89.join(root, l)));
52994
54488
  managers.push({ type, lockfile });
52995
54489
  }
52996
54490
  };
@@ -53004,7 +54498,7 @@ function detectPackageManagers(root) {
53004
54498
  check("pyproject.toml", "poetry", ["poetry.lock", "uv.lock"]);
53005
54499
  if (managers.length > 0 && managers[managers.length - 1].type === "poetry") {
53006
54500
  if (managers[managers.length - 1].lockfile === "uv.lock") managers[managers.length - 1].type = "uv";
53007
- else if (!managers[managers.length - 1].lockfile && fs67.existsSync(path80.join(root, "requirements.txt"))) {
54501
+ else if (!managers[managers.length - 1].lockfile && fs76.existsSync(path89.join(root, "requirements.txt"))) {
53008
54502
  managers[managers.length - 1].type = "pip";
53009
54503
  }
53010
54504
  }
@@ -53013,7 +54507,7 @@ function detectPackageManagers(root) {
53013
54507
  check("Gemfile", "bundler", ["Gemfile.lock"]);
53014
54508
  check("pom.xml", "maven", []);
53015
54509
  if (!managers.some((m) => m.type === "maven")) {
53016
- if (fs67.existsSync(path80.join(root, "build.gradle")) || fs67.existsSync(path80.join(root, "build.gradle.kts"))) {
54510
+ if (fs76.existsSync(path89.join(root, "build.gradle")) || fs76.existsSync(path89.join(root, "build.gradle.kts"))) {
53017
54511
  managers.push({ type: "gradle", lockfile: void 0 });
53018
54512
  }
53019
54513
  }
@@ -53022,9 +54516,9 @@ function detectPackageManagers(root) {
53022
54516
  function detectMcpClients(projectRoot) {
53023
54517
  const clients = [];
53024
54518
  const checkConfig = (name, configPath) => {
53025
- if (!fs67.existsSync(configPath)) return;
54519
+ if (!fs76.existsSync(configPath)) return;
53026
54520
  try {
53027
- const content = JSON.parse(fs67.readFileSync(configPath, "utf-8"));
54521
+ const content = JSON.parse(fs76.readFileSync(configPath, "utf-8"));
53028
54522
  const hasTraceMcp = !!content?.mcpServers?.["trace-mcp"];
53029
54523
  clients.push({ name, configPath, hasTraceMcp });
53030
54524
  } catch {
@@ -53032,47 +54526,47 @@ function detectMcpClients(projectRoot) {
53032
54526
  }
53033
54527
  };
53034
54528
  if (projectRoot) {
53035
- checkConfig("claude-code", path80.join(projectRoot, ".mcp.json"));
54529
+ checkConfig("claude-code", path89.join(projectRoot, ".mcp.json"));
53036
54530
  }
53037
- checkConfig("claude-code", path80.join(HOME3, ".claude.json"));
53038
- checkConfig("claude-code", path80.join(HOME3, ".claude", "settings.json"));
54531
+ checkConfig("claude-code", path89.join(HOME3, ".claude.json"));
54532
+ checkConfig("claude-code", path89.join(HOME3, ".claude", "settings.json"));
53039
54533
  if (projectRoot) {
53040
- checkConfig("claw-code", path80.join(projectRoot, ".claw.json"));
54534
+ checkConfig("claw-code", path89.join(projectRoot, ".claw.json"));
53041
54535
  }
53042
- checkConfig("claw-code", path80.join(HOME3, ".claw", "settings.json"));
54536
+ checkConfig("claw-code", path89.join(HOME3, ".claw", "settings.json"));
53043
54537
  const platform = os5.platform();
53044
54538
  if (platform === "darwin") {
53045
- checkConfig("claude-desktop", path80.join(HOME3, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
54539
+ checkConfig("claude-desktop", path89.join(HOME3, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
53046
54540
  } else if (platform === "win32") {
53047
- const appData = process.env.APPDATA ?? path80.join(HOME3, "AppData", "Roaming");
53048
- checkConfig("claude-desktop", path80.join(appData, "Claude", "claude_desktop_config.json"));
54541
+ const appData = process.env.APPDATA ?? path89.join(HOME3, "AppData", "Roaming");
54542
+ checkConfig("claude-desktop", path89.join(appData, "Claude", "claude_desktop_config.json"));
53049
54543
  }
53050
- checkConfig("cursor", path80.join(HOME3, ".cursor", "mcp.json"));
54544
+ checkConfig("cursor", path89.join(HOME3, ".cursor", "mcp.json"));
53051
54545
  if (projectRoot && !clients.some((c) => c.name === "cursor")) {
53052
- checkConfig("cursor", path80.join(projectRoot, ".cursor", "mcp.json"));
54546
+ checkConfig("cursor", path89.join(projectRoot, ".cursor", "mcp.json"));
53053
54547
  }
53054
- checkConfig("windsurf", path80.join(HOME3, ".windsurf", "mcp.json"));
54548
+ checkConfig("windsurf", path89.join(HOME3, ".windsurf", "mcp.json"));
53055
54549
  if (projectRoot && !clients.some((c) => c.name === "windsurf")) {
53056
- checkConfig("windsurf", path80.join(projectRoot, ".windsurf", "mcp.json"));
54550
+ checkConfig("windsurf", path89.join(projectRoot, ".windsurf", "mcp.json"));
53057
54551
  }
53058
- checkConfig("continue", path80.join(HOME3, ".continue", "mcpServers", "mcp.json"));
54552
+ checkConfig("continue", path89.join(HOME3, ".continue", "mcpServers", "mcp.json"));
53059
54553
  if (projectRoot && !clients.some((c) => c.name === "continue")) {
53060
- checkConfig("continue", path80.join(projectRoot, ".continue", "mcpServers", "mcp.json"));
54554
+ checkConfig("continue", path89.join(projectRoot, ".continue", "mcpServers", "mcp.json"));
53061
54555
  }
53062
54556
  return clients;
53063
54557
  }
53064
54558
  function detectExistingConfig(root) {
53065
54559
  const candidates = [
53066
- path80.join(root, ".trace-mcp.json"),
53067
- path80.join(root, ".config", "trace-mcp.json")
54560
+ path89.join(root, ".trace-mcp.json"),
54561
+ path89.join(root, ".config", "trace-mcp.json")
53068
54562
  ];
53069
54563
  for (const p4 of candidates) {
53070
- if (fs67.existsSync(p4)) return { path: p4 };
54564
+ if (fs76.existsSync(p4)) return { path: p4 };
53071
54565
  }
53072
- const pkgPath = path80.join(root, "package.json");
53073
- if (fs67.existsSync(pkgPath)) {
54566
+ const pkgPath = path89.join(root, "package.json");
54567
+ if (fs76.existsSync(pkgPath)) {
53074
54568
  try {
53075
- const pkg = JSON.parse(fs67.readFileSync(pkgPath, "utf-8"));
54569
+ const pkg = JSON.parse(fs76.readFileSync(pkgPath, "utf-8"));
53076
54570
  if (pkg["trace-mcp"]) return { path: pkgPath };
53077
54571
  } catch {
53078
54572
  }
@@ -53080,8 +54574,8 @@ function detectExistingConfig(root) {
53080
54574
  return null;
53081
54575
  }
53082
54576
  function detectExistingDb(root, globalDbPath) {
53083
- const candidates = globalDbPath ? [globalDbPath, path80.join(root, ".trace-mcp", "index.db")] : [path80.join(root, ".trace-mcp", "index.db")];
53084
- const dbPath = candidates.find((p4) => fs67.existsSync(p4));
54577
+ const candidates = globalDbPath ? [globalDbPath, path89.join(root, ".trace-mcp", "index.db")] : [path89.join(root, ".trace-mcp", "index.db")];
54578
+ const dbPath = candidates.find((p4) => fs76.existsSync(p4));
53085
54579
  if (!dbPath) return null;
53086
54580
  try {
53087
54581
  const db = new Database6(dbPath, { readonly: true });
@@ -53096,12 +54590,13 @@ function detectExistingDb(root, globalDbPath) {
53096
54590
  }
53097
54591
  }
53098
54592
  function detectGuardHook() {
53099
- const hookPath = path80.join(HOME3, ".claude", "hooks", "trace-mcp-guard.sh");
53100
- const clawHookPath = path80.join(HOME3, ".claw", "hooks", "trace-mcp-guard.sh");
53101
- const existingPath = fs67.existsSync(hookPath) ? hookPath : fs67.existsSync(clawHookPath) ? clawHookPath : null;
54593
+ const ext = process.platform === "win32" ? ".cmd" : ".sh";
54594
+ const hookPath = path89.join(HOME3, ".claude", "hooks", `trace-mcp-guard${ext}`);
54595
+ const clawHookPath = path89.join(HOME3, ".claw", "hooks", `trace-mcp-guard${ext}`);
54596
+ const existingPath = fs76.existsSync(hookPath) ? hookPath : fs76.existsSync(clawHookPath) ? clawHookPath : null;
53102
54597
  if (!existingPath) return { hasGuardHook: false, guardHookVersion: null };
53103
- const content = fs67.readFileSync(existingPath, "utf-8");
53104
- const match = content.match(/^# trace-mcp-guard v(.+)$/m);
54598
+ const content = fs76.readFileSync(existingPath, "utf-8");
54599
+ const match = content.match(/^(?:#|REM) trace-mcp-guard v(.+)$/m);
53105
54600
  return {
53106
54601
  hasGuardHook: true,
53107
54602
  guardHookVersion: match ? match[1] : null
@@ -53109,8 +54604,8 @@ function detectGuardHook() {
53109
54604
  }
53110
54605
 
53111
54606
  // src/init/conflict-detector.ts
53112
- import fs68 from "fs";
53113
- import path81 from "path";
54607
+ import fs77 from "fs";
54608
+ import path90 from "path";
53114
54609
  import os6 from "os";
53115
54610
  var HOME4 = os6.homedir();
53116
54611
  var COMPETING_MCP_SERVERS = {
@@ -53204,9 +54699,9 @@ var COMPETING_PROJECT_FILES = [
53204
54699
  { file: ".greptile.yaml", competitor: "greptile" }
53205
54700
  ];
53206
54701
  var COMPETING_GLOBAL_DIRS = [
53207
- { dir: path81.join(HOME4, ".code-index"), competitor: "jcodemunch-mcp" },
53208
- { dir: path81.join(HOME4, ".repomix"), competitor: "repomix" },
53209
- { dir: path81.join(HOME4, ".aider.tags.cache.v3"), competitor: "aider" }
54702
+ { dir: path90.join(HOME4, ".code-index"), competitor: "jcodemunch-mcp" },
54703
+ { dir: path90.join(HOME4, ".repomix"), competitor: "repomix" },
54704
+ { dir: path90.join(HOME4, ".aider.tags.cache.v3"), competitor: "aider" }
53210
54705
  ];
53211
54706
  function detectConflicts(projectRoot) {
53212
54707
  const conflicts = [];
@@ -53236,10 +54731,10 @@ function scanMcpServerConfigs(projectRoot) {
53236
54731
  const conflicts = [];
53237
54732
  const configs = getMcpConfigPaths(projectRoot);
53238
54733
  for (const { clientName, configPath } of configs) {
53239
- if (!fs68.existsSync(configPath)) continue;
54734
+ if (!fs77.existsSync(configPath)) continue;
53240
54735
  let parsed;
53241
54736
  try {
53242
- parsed = JSON.parse(fs68.readFileSync(configPath, "utf-8"));
54737
+ parsed = JSON.parse(fs77.readFileSync(configPath, "utf-8"));
53243
54738
  } catch {
53244
54739
  continue;
53245
54740
  }
@@ -53265,43 +54760,44 @@ function getMcpConfigPaths(projectRoot) {
53265
54760
  const paths = [];
53266
54761
  const platform = os6.platform();
53267
54762
  if (projectRoot) {
53268
- paths.push({ clientName: "claude-code", configPath: path81.join(projectRoot, ".mcp.json") });
54763
+ paths.push({ clientName: "claude-code", configPath: path90.join(projectRoot, ".mcp.json") });
53269
54764
  }
53270
- paths.push({ clientName: "claude-code", configPath: path81.join(HOME4, ".claude", "settings.json") });
54765
+ paths.push({ clientName: "claude-code", configPath: path90.join(HOME4, ".claude.json") });
54766
+ paths.push({ clientName: "claude-code", configPath: path90.join(HOME4, ".claude", "settings.json") });
53271
54767
  if (projectRoot) {
53272
- paths.push({ clientName: "claw-code", configPath: path81.join(projectRoot, ".claw.json") });
54768
+ paths.push({ clientName: "claw-code", configPath: path90.join(projectRoot, ".claw.json") });
53273
54769
  }
53274
- paths.push({ clientName: "claw-code", configPath: path81.join(HOME4, ".claw", "settings.json") });
54770
+ paths.push({ clientName: "claw-code", configPath: path90.join(HOME4, ".claw", "settings.json") });
53275
54771
  if (platform === "darwin") {
53276
- paths.push({ clientName: "claude-desktop", configPath: path81.join(HOME4, "Library", "Application Support", "Claude", "claude_desktop_config.json") });
54772
+ paths.push({ clientName: "claude-desktop", configPath: path90.join(HOME4, "Library", "Application Support", "Claude", "claude_desktop_config.json") });
53277
54773
  } else if (platform === "win32") {
53278
- const appData = process.env.APPDATA ?? path81.join(HOME4, "AppData", "Roaming");
53279
- paths.push({ clientName: "claude-desktop", configPath: path81.join(appData, "Claude", "claude_desktop_config.json") });
54774
+ const appData = process.env.APPDATA ?? path90.join(HOME4, "AppData", "Roaming");
54775
+ paths.push({ clientName: "claude-desktop", configPath: path90.join(appData, "Claude", "claude_desktop_config.json") });
53280
54776
  }
53281
- paths.push({ clientName: "cursor", configPath: path81.join(HOME4, ".cursor", "mcp.json") });
54777
+ paths.push({ clientName: "cursor", configPath: path90.join(HOME4, ".cursor", "mcp.json") });
53282
54778
  if (projectRoot) {
53283
- paths.push({ clientName: "cursor", configPath: path81.join(projectRoot, ".cursor", "mcp.json") });
54779
+ paths.push({ clientName: "cursor", configPath: path90.join(projectRoot, ".cursor", "mcp.json") });
53284
54780
  }
53285
- paths.push({ clientName: "windsurf", configPath: path81.join(HOME4, ".windsurf", "mcp.json") });
54781
+ paths.push({ clientName: "windsurf", configPath: path90.join(HOME4, ".windsurf", "mcp.json") });
53286
54782
  if (projectRoot) {
53287
- paths.push({ clientName: "windsurf", configPath: path81.join(projectRoot, ".windsurf", "mcp.json") });
54783
+ paths.push({ clientName: "windsurf", configPath: path90.join(projectRoot, ".windsurf", "mcp.json") });
53288
54784
  }
53289
- paths.push({ clientName: "continue", configPath: path81.join(HOME4, ".continue", "mcpServers", "mcp.json") });
54785
+ paths.push({ clientName: "continue", configPath: path90.join(HOME4, ".continue", "mcpServers", "mcp.json") });
53290
54786
  return paths;
53291
54787
  }
53292
54788
  function scanHooksInSettings() {
53293
54789
  const conflicts = [];
53294
54790
  const settingsFiles = [
53295
- path81.join(HOME4, ".claude", "settings.json"),
53296
- path81.join(HOME4, ".claude", "settings.local.json"),
53297
- path81.join(HOME4, ".claw", "settings.json"),
53298
- path81.join(HOME4, ".claw", "settings.local.json")
54791
+ path90.join(HOME4, ".claude", "settings.json"),
54792
+ path90.join(HOME4, ".claude", "settings.local.json"),
54793
+ path90.join(HOME4, ".claw", "settings.json"),
54794
+ path90.join(HOME4, ".claw", "settings.local.json")
53299
54795
  ];
53300
54796
  for (const settingsPath of settingsFiles) {
53301
- if (!fs68.existsSync(settingsPath)) continue;
54797
+ if (!fs77.existsSync(settingsPath)) continue;
53302
54798
  let settings;
53303
54799
  try {
53304
- settings = JSON.parse(fs68.readFileSync(settingsPath, "utf-8"));
54800
+ settings = JSON.parse(fs77.readFileSync(settingsPath, "utf-8"));
53305
54801
  } catch {
53306
54802
  continue;
53307
54803
  }
@@ -53338,14 +54834,14 @@ function scanHooksInSettings() {
53338
54834
  function scanHookScriptFiles() {
53339
54835
  const conflicts = [];
53340
54836
  const hooksDirs = [
53341
- path81.join(HOME4, ".claude", "hooks"),
53342
- path81.join(HOME4, ".claw", "hooks")
54837
+ path90.join(HOME4, ".claude", "hooks"),
54838
+ path90.join(HOME4, ".claw", "hooks")
53343
54839
  ];
53344
54840
  for (const hooksDir of hooksDirs) {
53345
- if (!fs68.existsSync(hooksDir)) continue;
54841
+ if (!fs77.existsSync(hooksDir)) continue;
53346
54842
  let files;
53347
54843
  try {
53348
- files = fs68.readdirSync(hooksDir);
54844
+ files = fs77.readdirSync(hooksDir);
53349
54845
  } catch {
53350
54846
  continue;
53351
54847
  }
@@ -53353,7 +54849,7 @@ function scanHookScriptFiles() {
53353
54849
  if (file.startsWith("trace-mcp")) continue;
53354
54850
  for (const { pattern, competitor } of COMPETING_HOOK_PATTERNS) {
53355
54851
  if (pattern.test(file)) {
53356
- const filePath = path81.join(hooksDir, file);
54852
+ const filePath = path90.join(hooksDir, file);
53357
54853
  conflicts.push({
53358
54854
  id: `hook_script:${file}:${competitor}`,
53359
54855
  category: "hook_script",
@@ -53373,20 +54869,20 @@ function scanHookScriptFiles() {
53373
54869
  function scanClaudeMdFiles(projectRoot) {
53374
54870
  const conflicts = [];
53375
54871
  const files = [
53376
- path81.join(HOME4, ".claude", "CLAUDE.md"),
53377
- path81.join(HOME4, ".claude", "AGENTS.md")
54872
+ path90.join(HOME4, ".claude", "CLAUDE.md"),
54873
+ path90.join(HOME4, ".claude", "AGENTS.md")
53378
54874
  ];
53379
54875
  if (projectRoot) {
53380
54876
  files.push(
53381
- path81.join(projectRoot, "CLAUDE.md"),
53382
- path81.join(projectRoot, "AGENTS.md")
54877
+ path90.join(projectRoot, "CLAUDE.md"),
54878
+ path90.join(projectRoot, "AGENTS.md")
53383
54879
  );
53384
54880
  }
53385
54881
  for (const filePath of files) {
53386
- if (!fs68.existsSync(filePath)) continue;
54882
+ if (!fs77.existsSync(filePath)) continue;
53387
54883
  let content;
53388
54884
  try {
53389
- content = fs68.readFileSync(filePath, "utf-8");
54885
+ content = fs77.readFileSync(filePath, "utf-8");
53390
54886
  } catch {
53391
54887
  continue;
53392
54888
  }
@@ -53413,44 +54909,44 @@ function scanClaudeMdFiles(projectRoot) {
53413
54909
  function scanIdeRuleFiles(projectRoot) {
53414
54910
  const conflicts = [];
53415
54911
  const ruleFiles = [];
53416
- ruleFiles.push({ path: path81.join(HOME4, ".cursorrules"), type: ".cursorrules (global)" });
53417
- ruleFiles.push({ path: path81.join(HOME4, ".windsurfrules"), type: ".windsurfrules (global)" });
54912
+ ruleFiles.push({ path: path90.join(HOME4, ".cursorrules"), type: ".cursorrules (global)" });
54913
+ ruleFiles.push({ path: path90.join(HOME4, ".windsurfrules"), type: ".windsurfrules (global)" });
53418
54914
  if (projectRoot) {
53419
- ruleFiles.push({ path: path81.join(projectRoot, ".cursorrules"), type: ".cursorrules" });
53420
- ruleFiles.push({ path: path81.join(projectRoot, ".windsurfrules"), type: ".windsurfrules" });
53421
- ruleFiles.push({ path: path81.join(projectRoot, ".clinerules"), type: ".clinerules" });
53422
- ruleFiles.push({ path: path81.join(projectRoot, ".continuerules"), type: ".continuerules" });
53423
- ruleFiles.push({ path: path81.join(projectRoot, ".github", "copilot-instructions.md"), type: "copilot-instructions.md" });
53424
- const clineRulesDir = path81.join(projectRoot, ".clinerules");
53425
- if (fs68.existsSync(clineRulesDir)) {
54915
+ ruleFiles.push({ path: path90.join(projectRoot, ".cursorrules"), type: ".cursorrules" });
54916
+ ruleFiles.push({ path: path90.join(projectRoot, ".windsurfrules"), type: ".windsurfrules" });
54917
+ ruleFiles.push({ path: path90.join(projectRoot, ".clinerules"), type: ".clinerules" });
54918
+ ruleFiles.push({ path: path90.join(projectRoot, ".continuerules"), type: ".continuerules" });
54919
+ ruleFiles.push({ path: path90.join(projectRoot, ".github", "copilot-instructions.md"), type: "copilot-instructions.md" });
54920
+ const clineRulesDir = path90.join(projectRoot, ".clinerules");
54921
+ if (fs77.existsSync(clineRulesDir)) {
53426
54922
  try {
53427
- const stat = fs68.statSync(clineRulesDir);
54923
+ const stat = fs77.statSync(clineRulesDir);
53428
54924
  if (stat.isDirectory()) {
53429
- for (const file of fs68.readdirSync(clineRulesDir)) {
53430
- ruleFiles.push({ path: path81.join(clineRulesDir, file), type: `.clinerules/${file}` });
54925
+ for (const file of fs77.readdirSync(clineRulesDir)) {
54926
+ ruleFiles.push({ path: path90.join(clineRulesDir, file), type: `.clinerules/${file}` });
53431
54927
  }
53432
54928
  }
53433
54929
  } catch {
53434
54930
  }
53435
54931
  }
53436
54932
  }
53437
- const cursorRulesDirs = [path81.join(HOME4, ".cursor", "rules")];
53438
- if (projectRoot) cursorRulesDirs.push(path81.join(projectRoot, ".cursor", "rules"));
54933
+ const cursorRulesDirs = [path90.join(HOME4, ".cursor", "rules")];
54934
+ if (projectRoot) cursorRulesDirs.push(path90.join(projectRoot, ".cursor", "rules"));
53439
54935
  for (const rulesDir of cursorRulesDirs) {
53440
- if (!fs68.existsSync(rulesDir)) continue;
54936
+ if (!fs77.existsSync(rulesDir)) continue;
53441
54937
  try {
53442
- for (const file of fs68.readdirSync(rulesDir)) {
54938
+ for (const file of fs77.readdirSync(rulesDir)) {
53443
54939
  if (!file.endsWith(".mdc") || file === "trace-mcp.mdc") continue;
53444
- ruleFiles.push({ path: path81.join(rulesDir, file), type: `.cursor/rules/${file}` });
54940
+ ruleFiles.push({ path: path90.join(rulesDir, file), type: `.cursor/rules/${file}` });
53445
54941
  }
53446
54942
  } catch {
53447
54943
  }
53448
54944
  }
53449
54945
  for (const { path: filePath, type } of ruleFiles) {
53450
- if (!fs68.existsSync(filePath)) continue;
54946
+ if (!fs77.existsSync(filePath)) continue;
53451
54947
  let content;
53452
54948
  try {
53453
- content = fs68.readFileSync(filePath, "utf-8");
54949
+ content = fs77.readFileSync(filePath, "utf-8");
53454
54950
  } catch {
53455
54951
  continue;
53456
54952
  }
@@ -53475,8 +54971,8 @@ function scanIdeRuleFiles(projectRoot) {
53475
54971
  function scanProjectConfigFiles(projectRoot) {
53476
54972
  const conflicts = [];
53477
54973
  for (const { file, competitor } of COMPETING_PROJECT_FILES) {
53478
- const filePath = path81.join(projectRoot, file);
53479
- if (!fs68.existsSync(filePath)) continue;
54974
+ const filePath = path90.join(projectRoot, file);
54975
+ if (!fs77.existsSync(filePath)) continue;
53480
54976
  conflicts.push({
53481
54977
  id: `config:${competitor}:${file}`,
53482
54978
  category: "config_file",
@@ -53499,11 +54995,11 @@ function scanProjectConfigDirs(projectRoot) {
53499
54995
  { dir: ".continue", competitor: "continue.dev" }
53500
54996
  ];
53501
54997
  for (const { dir, competitor } of dirs) {
53502
- const fullPath = path81.join(projectRoot, dir);
53503
- if (!fs68.existsSync(fullPath)) continue;
54998
+ const fullPath = path90.join(projectRoot, dir);
54999
+ if (!fs77.existsSync(fullPath)) continue;
53504
55000
  let stat;
53505
55001
  try {
53506
- stat = fs68.statSync(fullPath);
55002
+ stat = fs77.statSync(fullPath);
53507
55003
  } catch {
53508
55004
  continue;
53509
55005
  }
@@ -53525,33 +55021,33 @@ function scanProjectConfigDirs(projectRoot) {
53525
55021
  function scanContinueConfigs(projectRoot) {
53526
55022
  const conflicts = [];
53527
55023
  const configPaths = [
53528
- path81.join(HOME4, ".continue", "config.yaml"),
53529
- path81.join(HOME4, ".continue", "config.json")
55024
+ path90.join(HOME4, ".continue", "config.yaml"),
55025
+ path90.join(HOME4, ".continue", "config.json")
53530
55026
  ];
53531
55027
  if (projectRoot) {
53532
55028
  configPaths.push(
53533
- path81.join(projectRoot, ".continue", "config.yaml"),
53534
- path81.join(projectRoot, ".continue", "config.json")
55029
+ path90.join(projectRoot, ".continue", "config.yaml"),
55030
+ path90.join(projectRoot, ".continue", "config.json")
53535
55031
  );
53536
55032
  }
53537
- const mcpServersDirs = [path81.join(HOME4, ".continue", "mcpServers")];
55033
+ const mcpServersDirs = [path90.join(HOME4, ".continue", "mcpServers")];
53538
55034
  if (projectRoot) {
53539
- mcpServersDirs.push(path81.join(projectRoot, ".continue", "mcpServers"));
55035
+ mcpServersDirs.push(path90.join(projectRoot, ".continue", "mcpServers"));
53540
55036
  }
53541
55037
  for (const mcpDir of mcpServersDirs) {
53542
- if (!fs68.existsSync(mcpDir)) continue;
55038
+ if (!fs77.existsSync(mcpDir)) continue;
53543
55039
  let files;
53544
55040
  try {
53545
- files = fs68.readdirSync(mcpDir);
55041
+ files = fs77.readdirSync(mcpDir);
53546
55042
  } catch {
53547
55043
  continue;
53548
55044
  }
53549
55045
  for (const file of files) {
53550
55046
  if (!file.endsWith(".json")) continue;
53551
- const filePath = path81.join(mcpDir, file);
55047
+ const filePath = path90.join(mcpDir, file);
53552
55048
  let content;
53553
55049
  try {
53554
- content = fs68.readFileSync(filePath, "utf-8");
55050
+ content = fs77.readFileSync(filePath, "utf-8");
53555
55051
  } catch {
53556
55052
  continue;
53557
55053
  }
@@ -53577,15 +55073,15 @@ function scanContinueConfigs(projectRoot) {
53577
55073
  }
53578
55074
  function scanGitHooks(projectRoot) {
53579
55075
  const conflicts = [];
53580
- const hooksDir = path81.join(projectRoot, ".git", "hooks");
53581
- if (!fs68.existsSync(hooksDir)) return conflicts;
55076
+ const hooksDir = path90.join(projectRoot, ".git", "hooks");
55077
+ if (!fs77.existsSync(hooksDir)) return conflicts;
53582
55078
  const hookFiles = ["pre-commit", "post-commit", "prepare-commit-msg"];
53583
55079
  for (const hookFile of hookFiles) {
53584
- const hookPath = path81.join(hooksDir, hookFile);
53585
- if (!fs68.existsSync(hookPath)) continue;
55080
+ const hookPath = path90.join(hooksDir, hookFile);
55081
+ if (!fs77.existsSync(hookPath)) continue;
53586
55082
  let content;
53587
55083
  try {
53588
- content = fs68.readFileSync(hookPath, "utf-8");
55084
+ content = fs77.readFileSync(hookPath, "utf-8");
53589
55085
  } catch {
53590
55086
  continue;
53591
55087
  }
@@ -53608,10 +55104,10 @@ function scanGitHooks(projectRoot) {
53608
55104
  function scanGlobalArtifacts() {
53609
55105
  const conflicts = [];
53610
55106
  for (const { dir, competitor } of COMPETING_GLOBAL_DIRS) {
53611
- if (!fs68.existsSync(dir)) continue;
55107
+ if (!fs77.existsSync(dir)) continue;
53612
55108
  let size = 0;
53613
55109
  try {
53614
- const files = fs68.readdirSync(dir);
55110
+ const files = fs77.readdirSync(dir);
53615
55111
  size = files.length;
53616
55112
  } catch {
53617
55113
  }
@@ -53637,7 +55133,7 @@ function truncate(s, maxLen) {
53637
55133
  }
53638
55134
 
53639
55135
  // src/init/conflict-resolver.ts
53640
- import fs69 from "fs";
55136
+ import fs78 from "fs";
53641
55137
  function fixConflict(conflict, opts = {}) {
53642
55138
  if (!conflict.fixable) {
53643
55139
  return {
@@ -53671,41 +55167,74 @@ function fixMcpServer(conflict, opts) {
53671
55167
  const configPath = conflict.target;
53672
55168
  const serverName = conflict.id.split(":")[1];
53673
55169
  if (opts.dryRun) {
53674
- return { conflictId: conflict.id, action: "removed", detail: `Would remove "${serverName}" from ${shortPath2(configPath)}`, target: configPath };
55170
+ return { conflictId: conflict.id, action: "disabled", detail: `Would comment out "${serverName}" in ${shortPath2(configPath)}`, target: configPath };
53675
55171
  }
53676
- if (!fs69.existsSync(configPath)) {
55172
+ if (!fs78.existsSync(configPath)) {
53677
55173
  return { conflictId: conflict.id, action: "skipped", detail: "Config file no longer exists", target: configPath };
53678
55174
  }
53679
55175
  try {
53680
- const raw = fs69.readFileSync(configPath, "utf-8");
53681
- const parsed = JSON.parse(raw);
53682
- let removed = false;
53683
- for (const key of ["mcpServers", "servers"]) {
53684
- if (parsed[key] && parsed[key][serverName]) {
53685
- delete parsed[key][serverName];
53686
- removed = true;
53687
- }
55176
+ const raw = fs78.readFileSync(configPath, "utf-8");
55177
+ const result = commentOutJsonKey(raw, serverName);
55178
+ if (!result) {
55179
+ return { conflictId: conflict.id, action: "skipped", detail: `Server "${serverName}" not found (already disabled?)`, target: configPath };
53688
55180
  }
53689
- if (!removed) {
53690
- return { conflictId: conflict.id, action: "skipped", detail: `Server "${serverName}" not found (already removed?)`, target: configPath };
53691
- }
53692
- fs69.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
53693
- return { conflictId: conflict.id, action: "removed", detail: `Removed "${serverName}" from ${shortPath2(configPath)}`, target: configPath };
55181
+ fs78.writeFileSync(configPath, result);
55182
+ return { conflictId: conflict.id, action: "disabled", detail: `Commented out "${serverName}" in ${shortPath2(configPath)}`, target: configPath };
53694
55183
  } catch (err32) {
53695
55184
  return { conflictId: conflict.id, action: "skipped", detail: `Failed to update config: ${err32.message}`, target: configPath };
53696
55185
  }
53697
55186
  }
55187
+ function commentOutJsonKey(raw, key) {
55188
+ const lines = raw.split("\n");
55189
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
55190
+ const keyPattern = new RegExp(`^(\\s*)"${escaped}"\\s*:`);
55191
+ let startLine = -1;
55192
+ for (let i = 0; i < lines.length; i++) {
55193
+ if (/^\s*\/\//.test(lines[i])) continue;
55194
+ if (keyPattern.test(lines[i])) {
55195
+ startLine = i;
55196
+ break;
55197
+ }
55198
+ }
55199
+ if (startLine === -1) return null;
55200
+ let endLine = startLine;
55201
+ let braceDepth = 0;
55202
+ let foundOpen = false;
55203
+ for (let i = startLine; i < lines.length; i++) {
55204
+ const stripped = lines[i].replace(/"(?:[^"\\]|\\.)*"/g, '""');
55205
+ for (const ch of stripped) {
55206
+ if (ch === "{" || ch === "[") {
55207
+ braceDepth++;
55208
+ foundOpen = true;
55209
+ } else if (ch === "}" || ch === "]") {
55210
+ braceDepth--;
55211
+ }
55212
+ }
55213
+ if (foundOpen && braceDepth <= 0) {
55214
+ endLine = i;
55215
+ break;
55216
+ }
55217
+ if (!foundOpen && i === startLine) {
55218
+ endLine = i;
55219
+ break;
55220
+ }
55221
+ }
55222
+ for (let i = startLine; i <= endLine; i++) {
55223
+ lines[i] = "// " + lines[i];
55224
+ }
55225
+ return lines.join("\n");
55226
+ }
53698
55227
  function fixHookInSettings(conflict, opts) {
53699
55228
  const settingsPath = conflict.target;
53700
55229
  const competitor = conflict.competitor;
53701
55230
  if (opts.dryRun) {
53702
55231
  return { conflictId: conflict.id, action: "removed", detail: `Would remove ${competitor} hooks from ${shortPath2(settingsPath)}`, target: settingsPath };
53703
55232
  }
53704
- if (!fs69.existsSync(settingsPath)) {
55233
+ if (!fs78.existsSync(settingsPath)) {
53705
55234
  return { conflictId: conflict.id, action: "skipped", detail: "Settings file no longer exists", target: settingsPath };
53706
55235
  }
53707
55236
  try {
53708
- const settings = JSON.parse(fs69.readFileSync(settingsPath, "utf-8"));
55237
+ const settings = JSON.parse(fs78.readFileSync(settingsPath, "utf-8"));
53709
55238
  const hooks = settings.hooks;
53710
55239
  if (!hooks) {
53711
55240
  return { conflictId: conflict.id, action: "skipped", detail: "No hooks section found", target: settingsPath };
@@ -53733,7 +55262,7 @@ function fixHookInSettings(conflict, opts) {
53733
55262
  if (!modified) {
53734
55263
  return { conflictId: conflict.id, action: "skipped", detail: "Hook entries already removed", target: settingsPath };
53735
55264
  }
53736
- fs69.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
55265
+ fs78.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
53737
55266
  return { conflictId: conflict.id, action: "removed", detail: `Removed ${competitor} hooks from ${shortPath2(settingsPath)}`, target: settingsPath };
53738
55267
  } catch (err32) {
53739
55268
  return { conflictId: conflict.id, action: "skipped", detail: `Failed to update settings: ${err32.message}`, target: settingsPath };
@@ -53744,11 +55273,11 @@ function fixHookScript(conflict, opts) {
53744
55273
  if (opts.dryRun) {
53745
55274
  return { conflictId: conflict.id, action: "removed", detail: `Would delete ${shortPath2(scriptPath)}`, target: scriptPath };
53746
55275
  }
53747
- if (!fs69.existsSync(scriptPath)) {
55276
+ if (!fs78.existsSync(scriptPath)) {
53748
55277
  return { conflictId: conflict.id, action: "skipped", detail: "Script already removed", target: scriptPath };
53749
55278
  }
53750
55279
  try {
53751
- fs69.unlinkSync(scriptPath);
55280
+ fs78.unlinkSync(scriptPath);
53752
55281
  return { conflictId: conflict.id, action: "removed", detail: `Deleted ${shortPath2(scriptPath)}`, target: scriptPath };
53753
55282
  } catch (err32) {
53754
55283
  return { conflictId: conflict.id, action: "skipped", detail: `Failed to delete: ${err32.message}`, target: scriptPath };
@@ -53759,11 +55288,11 @@ function fixClaudeMdBlock(conflict, opts) {
53759
55288
  if (opts.dryRun) {
53760
55289
  return { conflictId: conflict.id, action: "cleaned", detail: `Would remove ${conflict.competitor} block from ${shortPath2(filePath)}`, target: filePath };
53761
55290
  }
53762
- if (!fs69.existsSync(filePath)) {
55291
+ if (!fs78.existsSync(filePath)) {
53763
55292
  return { conflictId: conflict.id, action: "skipped", detail: "File no longer exists", target: filePath };
53764
55293
  }
53765
55294
  try {
53766
- const content = fs69.readFileSync(filePath, "utf-8");
55295
+ const content = fs78.readFileSync(filePath, "utf-8");
53767
55296
  const tools = ["jcodemunch", "code-index", "repomix", "aider", "cline", "cody", "greptile", "sourcegraph", "code-compass", "repo-map"];
53768
55297
  const markerPattern = new RegExp(
53769
55298
  `<!-- ?(${tools.join("|")}):start ?-->[\\s\\S]*?<!-- ?\\1:end ?-->\\n?`,
@@ -53774,7 +55303,7 @@ function fixClaudeMdBlock(conflict, opts) {
53774
55303
  if (updated === content) {
53775
55304
  return { conflictId: conflict.id, action: "skipped", detail: "No marker-delimited blocks found to remove", target: filePath };
53776
55305
  }
53777
- fs69.writeFileSync(filePath, updated);
55306
+ fs78.writeFileSync(filePath, updated);
53778
55307
  return { conflictId: conflict.id, action: "cleaned", detail: `Removed ${conflict.competitor} block from ${shortPath2(filePath)}`, target: filePath };
53779
55308
  } catch (err32) {
53780
55309
  return { conflictId: conflict.id, action: "skipped", detail: `Failed to update: ${err32.message}`, target: filePath };
@@ -53785,15 +55314,15 @@ function fixConfigFile(conflict, opts) {
53785
55314
  if (opts.dryRun) {
53786
55315
  return { conflictId: conflict.id, action: "removed", detail: `Would delete ${shortPath2(filePath)}`, target: filePath };
53787
55316
  }
53788
- if (!fs69.existsSync(filePath)) {
55317
+ if (!fs78.existsSync(filePath)) {
53789
55318
  return { conflictId: conflict.id, action: "skipped", detail: "Already removed", target: filePath };
53790
55319
  }
53791
55320
  try {
53792
- const stat = fs69.statSync(filePath);
55321
+ const stat = fs78.statSync(filePath);
53793
55322
  if (stat.isDirectory()) {
53794
- fs69.rmSync(filePath, { recursive: true, force: true });
55323
+ fs78.rmSync(filePath, { recursive: true, force: true });
53795
55324
  } else {
53796
- fs69.unlinkSync(filePath);
55325
+ fs78.unlinkSync(filePath);
53797
55326
  }
53798
55327
  return { conflictId: conflict.id, action: "removed", detail: `Deleted ${shortPath2(filePath)}`, target: filePath };
53799
55328
  } catch (err32) {
@@ -53805,11 +55334,11 @@ function fixGlobalArtifact(conflict, opts) {
53805
55334
  if (opts.dryRun) {
53806
55335
  return { conflictId: conflict.id, action: "removed", detail: `Would remove ${shortPath2(dirPath)}`, target: dirPath };
53807
55336
  }
53808
- if (!fs69.existsSync(dirPath)) {
55337
+ if (!fs78.existsSync(dirPath)) {
53809
55338
  return { conflictId: conflict.id, action: "skipped", detail: "Directory already removed", target: dirPath };
53810
55339
  }
53811
55340
  try {
53812
- fs69.rmSync(dirPath, { recursive: true, force: true });
55341
+ fs78.rmSync(dirPath, { recursive: true, force: true });
53813
55342
  return { conflictId: conflict.id, action: "removed", detail: `Removed ${shortPath2(dirPath)}`, target: dirPath };
53814
55343
  } catch (err32) {
53815
55344
  return { conflictId: conflict.id, action: "skipped", detail: `Failed to remove: ${err32.message}`, target: dirPath };
@@ -53822,8 +55351,8 @@ function shortPath2(p4) {
53822
55351
  }
53823
55352
 
53824
55353
  // src/project-root.ts
53825
- import fs70 from "fs";
53826
- import path82 from "path";
55354
+ import fs79 from "fs";
55355
+ import path91 from "path";
53827
55356
  var ROOT_MARKERS = [
53828
55357
  ".git",
53829
55358
  "package.json",
@@ -53837,14 +55366,14 @@ var ROOT_MARKERS = [
53837
55366
  "build.gradle.kts"
53838
55367
  ];
53839
55368
  function findProjectRoot(from) {
53840
- let dir = path82.resolve(from ?? process.cwd());
55369
+ let dir = path91.resolve(from ?? process.cwd());
53841
55370
  while (true) {
53842
55371
  for (const marker of ROOT_MARKERS) {
53843
- if (fs70.existsSync(path82.join(dir, marker))) {
55372
+ if (fs79.existsSync(path91.join(dir, marker))) {
53844
55373
  return dir;
53845
55374
  }
53846
55375
  }
53847
- const parent = path82.dirname(dir);
55376
+ const parent = path91.dirname(dir);
53848
55377
  if (parent === dir) {
53849
55378
  throw new Error(
53850
55379
  `Could not find project root from ${from ?? process.cwd()}. Looked for: ${ROOT_MARKERS.join(", ")}`
@@ -54087,15 +55616,15 @@ function generateConfig(detection) {
54087
55616
  }
54088
55617
 
54089
55618
  // src/registry.ts
54090
- import fs71 from "fs";
54091
- import path83 from "path";
55619
+ import fs80 from "fs";
55620
+ import path92 from "path";
54092
55621
  function emptyRegistry() {
54093
55622
  return { version: 1, projects: {} };
54094
55623
  }
54095
55624
  function loadRegistry2() {
54096
- if (!fs71.existsSync(REGISTRY_PATH)) return emptyRegistry();
55625
+ if (!fs80.existsSync(REGISTRY_PATH)) return emptyRegistry();
54097
55626
  try {
54098
- const raw = JSON.parse(fs71.readFileSync(REGISTRY_PATH, "utf-8"));
55627
+ const raw = JSON.parse(fs80.readFileSync(REGISTRY_PATH, "utf-8"));
54099
55628
  if (raw.version === 1 && raw.projects) return raw;
54100
55629
  return emptyRegistry();
54101
55630
  } catch {
@@ -54105,11 +55634,11 @@ function loadRegistry2() {
54105
55634
  function saveRegistry(reg) {
54106
55635
  ensureGlobalDirs();
54107
55636
  const tmp = REGISTRY_PATH + ".tmp." + process.pid;
54108
- fs71.writeFileSync(tmp, JSON.stringify(reg, null, 2) + "\n");
54109
- fs71.renameSync(tmp, REGISTRY_PATH);
55637
+ fs80.writeFileSync(tmp, JSON.stringify(reg, null, 2) + "\n");
55638
+ fs80.renameSync(tmp, REGISTRY_PATH);
54110
55639
  }
54111
55640
  function registerProject(root) {
54112
- const absRoot = path83.resolve(root);
55641
+ const absRoot = path92.resolve(root);
54113
55642
  const reg = loadRegistry2();
54114
55643
  if (reg.projects[absRoot]) {
54115
55644
  return reg.projects[absRoot];
@@ -54126,7 +55655,7 @@ function registerProject(root) {
54126
55655
  return entry;
54127
55656
  }
54128
55657
  function getProject(root) {
54129
- const absRoot = path83.resolve(root);
55658
+ const absRoot = path92.resolve(root);
54130
55659
  const reg = loadRegistry2();
54131
55660
  return reg.projects[absRoot] ?? null;
54132
55661
  }
@@ -54135,7 +55664,7 @@ function listProjects() {
54135
55664
  return Object.values(reg.projects);
54136
55665
  }
54137
55666
  function updateLastIndexed(root) {
54138
- const absRoot = path83.resolve(root);
55667
+ const absRoot = path92.resolve(root);
54139
55668
  const reg = loadRegistry2();
54140
55669
  if (reg.projects[absRoot]) {
54141
55670
  reg.projects[absRoot].lastIndexed = (/* @__PURE__ */ new Date()).toISOString();
@@ -54218,7 +55747,15 @@ var initCommand = new Command("init").description("One-time global setup: config
54218
55747
  } catch {
54219
55748
  }
54220
55749
  const conflictReport = detectConflicts(projectRoot);
54221
- const fixable = conflictReport.conflicts.filter((c) => c.fixable);
55750
+ const clientSet = new Set(selectedClients);
55751
+ const fixable = conflictReport.conflicts.filter((c) => {
55752
+ if (!c.fixable) return false;
55753
+ if (c.category === "mcp_server") {
55754
+ const clientName = c.id.split(":")[2];
55755
+ return clientSet.has(clientName);
55756
+ }
55757
+ return true;
55758
+ });
54222
55759
  if (fixable.length > 0) {
54223
55760
  const critical = fixable.filter((c) => c.severity === "critical");
54224
55761
  const label = critical.length > 0 ? `Found ${fixable.length} conflicting tool${fixable.length > 1 ? "s" : ""} (${critical.length} critical). Fix them? (recommended)` : `Found ${fixable.length} competing tool artifact${fixable.length > 1 ? "s" : ""}. Clean up?`;
@@ -54260,7 +55797,15 @@ var initCommand = new Command("init").description("One-time global setup: config
54260
55797
  } catch {
54261
55798
  }
54262
55799
  const conflictReport = detectConflicts(projectRoot);
54263
- const fixable = conflictReport.conflicts.filter((c) => c.fixable);
55800
+ const clientSet = new Set(selectedClients);
55801
+ const fixable = conflictReport.conflicts.filter((c) => {
55802
+ if (!c.fixable) return false;
55803
+ if (c.category === "mcp_server") {
55804
+ const clientName = c.id.split(":")[2];
55805
+ return clientSet.has(clientName);
55806
+ }
55807
+ return true;
55808
+ });
54264
55809
  if (fixable.length > 0) {
54265
55810
  const results = fixAllConflicts(fixable, { dryRun: opts.dryRun });
54266
55811
  for (const r of results) {
@@ -54294,7 +55839,7 @@ var initCommand = new Command("init").description("One-time global setup: config
54294
55839
  const spin = !nonInteractive ? p.spinner() : null;
54295
55840
  spin?.start("Upgrading registered projects");
54296
55841
  for (const proj of existingProjects) {
54297
- if (!fs72.existsSync(proj.root)) {
55842
+ if (!fs81.existsSync(proj.root)) {
54298
55843
  steps.push({ target: proj.root, action: "skipped", detail: "Directory not found (stale)" });
54299
55844
  continue;
54300
55845
  }
@@ -54397,9 +55942,9 @@ function registerAndIndexProject(dir, opts) {
54397
55942
  const config = generateConfig(detection);
54398
55943
  saveProjectConfig(projectRoot, { root: config.root, include: config.include, exclude: config.exclude });
54399
55944
  const dbPath = getDbPath(projectRoot);
54400
- const oldDbPath = path84.join(projectRoot, ".trace-mcp", "index.db");
54401
- if (fs72.existsSync(oldDbPath) && !fs72.existsSync(dbPath)) {
54402
- fs72.copyFileSync(oldDbPath, dbPath);
55945
+ const oldDbPath = path93.join(projectRoot, ".trace-mcp", "index.db");
55946
+ if (fs81.existsSync(oldDbPath) && !fs81.existsSync(dbPath)) {
55947
+ fs81.copyFileSync(oldDbPath, dbPath);
54403
55948
  }
54404
55949
  const db = initializeDatabase(dbPath);
54405
55950
  db.close();
@@ -54431,12 +55976,12 @@ function shortPath3(p4) {
54431
55976
 
54432
55977
  // src/cli-upgrade.ts
54433
55978
  import { Command as Command2 } from "commander";
54434
- import fs73 from "fs";
54435
- import path85 from "path";
55979
+ import fs82 from "fs";
55980
+ import path94 from "path";
54436
55981
  var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run DB migrations, reindex with latest plugins, update hooks and CLAUDE.md").argument("[dir]", "Project directory (omit to upgrade all registered projects)").option("--skip-hooks", "Do not update guard hooks").option("--skip-reindex", "Do not trigger reindex").option("--skip-claude-md", "Do not update CLAUDE.md block").option("--dry-run", "Show what would be done without writing files").option("--json", "Output results as JSON").action(async (dir, opts) => {
54437
55982
  const projectRoots = [];
54438
55983
  if (dir) {
54439
- projectRoots.push(path85.resolve(dir));
55984
+ projectRoots.push(path94.resolve(dir));
54440
55985
  } else {
54441
55986
  const projects = listProjects();
54442
55987
  if (projects.length === 0) {
@@ -54444,7 +55989,7 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
54444
55989
  process.exit(1);
54445
55990
  }
54446
55991
  for (const p4 of projects) {
54447
- if (fs73.existsSync(p4.root)) {
55992
+ if (fs82.existsSync(p4.root)) {
54448
55993
  projectRoots.push(p4.root);
54449
55994
  } else {
54450
55995
  logger.warn({ root: p4.root }, "Skipping stale project (directory not found)");
@@ -54518,7 +56063,7 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
54518
56063
  console.log(header);
54519
56064
  for (const { projectRoot, steps } of allSteps) {
54520
56065
  console.log(`
54521
- Project: ${path85.basename(projectRoot)} (${projectRoot})`);
56066
+ Project: ${path94.basename(projectRoot)} (${projectRoot})`);
54522
56067
  for (const step of steps) {
54523
56068
  console.log(` ${step.action}: ${step.detail ?? step.target}`);
54524
56069
  }
@@ -54529,12 +56074,12 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
54529
56074
 
54530
56075
  // src/cli-add.ts
54531
56076
  import { Command as Command3 } from "commander";
54532
- import fs74 from "fs";
54533
- import path86 from "path";
56077
+ import fs83 from "fs";
56078
+ import path95 from "path";
54534
56079
  import * as p2 from "@clack/prompts";
54535
56080
  var addCommand = new Command3("add").description("Register a project for indexing: detect root, create DB, add to registry").argument("[dir]", "Project directory (default: current directory)", ".").option("--force", "Re-register even if already registered").option("--json", "Output results as JSON").action(async (dir, opts) => {
54536
- const resolvedDir = path86.resolve(dir);
54537
- if (!fs74.existsSync(resolvedDir)) {
56081
+ const resolvedDir = path95.resolve(dir);
56082
+ if (!fs83.existsSync(resolvedDir)) {
54538
56083
  console.error(`Directory does not exist: ${resolvedDir}`);
54539
56084
  process.exit(1);
54540
56085
  }
@@ -54588,10 +56133,10 @@ DB: ${shortPath4(existing.dbPath)}`, "Existing");
54588
56133
  ensureGlobalDirs();
54589
56134
  saveProjectConfig(projectRoot, configForSave);
54590
56135
  const dbPath = getDbPath(projectRoot);
54591
- const oldDbPath = path86.join(projectRoot, ".trace-mcp", "index.db");
56136
+ const oldDbPath = path95.join(projectRoot, ".trace-mcp", "index.db");
54592
56137
  let migrated = false;
54593
- if (fs74.existsSync(oldDbPath) && !fs74.existsSync(dbPath)) {
54594
- fs74.copyFileSync(oldDbPath, dbPath);
56138
+ if (fs83.existsSync(oldDbPath) && !fs83.existsSync(dbPath)) {
56139
+ fs83.copyFileSync(oldDbPath, dbPath);
54595
56140
  migrated = true;
54596
56141
  }
54597
56142
  const db = initializeDatabase(dbPath);
@@ -54765,8 +56310,8 @@ function shortPath5(p4) {
54765
56310
  // src/cli-ci.ts
54766
56311
  import { Command as Command5 } from "commander";
54767
56312
  import { execFileSync as execFileSync7 } from "child_process";
54768
- import path87 from "path";
54769
- import fs75 from "fs";
56313
+ import path96 from "path";
56314
+ import fs84 from "fs";
54770
56315
 
54771
56316
  // src/ci/report-generator.ts
54772
56317
  init_graph_analysis();
@@ -54824,6 +56369,7 @@ function computeBlastRadius(store, changedFiles) {
54824
56369
  const allEntries = [];
54825
56370
  const seenPaths = new Set(changedFiles);
54826
56371
  let truncated = false;
56372
+ const riskSummary = [];
54827
56373
  for (const filePath of changedFiles) {
54828
56374
  const result = getChangeImpact(store, { filePath }, 2, 100);
54829
56375
  if (result.isErr()) continue;
@@ -54834,17 +56380,25 @@ function computeBlastRadius(store, changedFiles) {
54834
56380
  seenPaths.add(dep.path);
54835
56381
  allEntries.push({
54836
56382
  path: dep.path,
54837
- symbolId: dep.symbolId,
54838
- edgeType: dep.edgeType,
56383
+ symbolId: dep.symbols?.[0]?.symbolId,
56384
+ edgeType: dep.edgeTypes.join(", "),
54839
56385
  depth: dep.depth
54840
56386
  });
54841
56387
  }
56388
+ if (impact.totalAffected > 3) {
56389
+ riskSummary.push({
56390
+ file: filePath,
56391
+ riskLevel: impact.risk.level,
56392
+ sentence: impact.summary.sentence
56393
+ });
56394
+ }
54842
56395
  }
54843
56396
  allEntries.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
54844
56397
  return {
54845
56398
  entries: allEntries,
54846
56399
  totalAffected: allEntries.length,
54847
- truncated
56400
+ truncated,
56401
+ ...riskSummary.length > 0 ? { riskSummary } : {}
54848
56402
  };
54849
56403
  }
54850
56404
  function computeTestCoverageGaps(store, changedFiles, blastEntries) {
@@ -55145,16 +56699,16 @@ function writeOutput(outputPath, content) {
55145
56699
  if (outputPath === "-" || !outputPath) {
55146
56700
  process.stdout.write(content + "\n");
55147
56701
  } else {
55148
- const resolved = path87.resolve(outputPath);
55149
- fs75.mkdirSync(path87.dirname(resolved), { recursive: true });
55150
- fs75.writeFileSync(resolved, content, "utf-8");
56702
+ const resolved = path96.resolve(outputPath);
56703
+ fs84.mkdirSync(path96.dirname(resolved), { recursive: true });
56704
+ fs84.writeFileSync(resolved, content, "utf-8");
55151
56705
  logger.info({ path: resolved }, "CI report written");
55152
56706
  }
55153
56707
  }
55154
56708
 
55155
56709
  // src/cli-check.ts
55156
56710
  import { Command as Command6 } from "commander";
55157
- import fs76 from "fs";
56711
+ import fs85 from "fs";
55158
56712
  function resolveDbPath2(projectRoot) {
55159
56713
  const entry = getProject(projectRoot);
55160
56714
  if (entry) return entry.dbPath;
@@ -55178,7 +56732,7 @@ var checkCommand = new Command6("check").description("Run quality gate checks ag
55178
56732
  let gatesConfig;
55179
56733
  if (opts.config) {
55180
56734
  try {
55181
- const raw = JSON.parse(fs76.readFileSync(opts.config, "utf-8"));
56735
+ const raw = JSON.parse(fs85.readFileSync(opts.config, "utf-8"));
55182
56736
  const parsed = QualityGatesConfigSchema2.safeParse(raw.quality_gates ?? raw);
55183
56737
  if (!parsed.success) {
55184
56738
  console.error(`Invalid quality gates config: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`);
@@ -55981,8 +57535,8 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
55981
57535
  });
55982
57536
  });
55983
57537
  program.command("index").description("Index a project directory").argument("<dir>", "Directory to index").option("-f, --force", "Force reindex all files").action(async (dir, opts) => {
55984
- const resolvedDir = path88.resolve(dir);
55985
- if (!fs77.existsSync(resolvedDir)) {
57538
+ const resolvedDir = path97.resolve(dir);
57539
+ if (!fs86.existsSync(resolvedDir)) {
55986
57540
  logger.error({ dir: resolvedDir }, "Directory does not exist");
55987
57541
  process.exit(1);
55988
57542
  }
@@ -56006,13 +57560,13 @@ program.command("index").description("Index a project directory").argument("<dir
56006
57560
  db.close();
56007
57561
  });
56008
57562
  program.command("index-file").description("Incrementally reindex a single file (called by the PostToolUse auto-reindex hook)").argument("<file>", "Absolute or relative path to the file to reindex").action(async (file) => {
56009
- const resolvedFile = path88.resolve(file);
56010
- if (!fs77.existsSync(resolvedFile)) {
57563
+ const resolvedFile = path97.resolve(file);
57564
+ if (!fs86.existsSync(resolvedFile)) {
56011
57565
  process.exit(0);
56012
57566
  }
56013
57567
  let projectRoot;
56014
57568
  try {
56015
- projectRoot = findProjectRoot(path88.dirname(resolvedFile));
57569
+ projectRoot = findProjectRoot(path97.dirname(resolvedFile));
56016
57570
  } catch {
56017
57571
  process.exit(0);
56018
57572
  }
@@ -56049,7 +57603,7 @@ program.command("list").description("List all registered projects").option("--js
56049
57603
  console.log("Registered projects:\n");
56050
57604
  for (const p4 of projects) {
56051
57605
  const lastIdx = p4.lastIndexed ? new Date(p4.lastIndexed).toLocaleString() : "never";
56052
- const dbExists = fs77.existsSync(p4.dbPath) ? "ok" : "missing";
57606
+ const dbExists = fs86.existsSync(p4.dbPath) ? "ok" : "missing";
56053
57607
  console.log(` ${p4.name}`);
56054
57608
  console.log(` Root: ${p4.root}`);
56055
57609
  console.log(` DB: ${dbExists}`);