trace-mcp 1.0.10 → 1.1.0

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 = (path82, { windows } = {}) => {
623
- const segs = path82.split(windows ? /[\\/]/ : "/");
622
+ exports.basename = (path83, { windows } = {}) => {
623
+ const segs = path83.split(windows ? /[\\/]/ : "/");
624
624
  const last = segs[segs.length - 1];
625
625
  if (last === "") {
626
626
  return segs[segs.length - 2];
@@ -2087,8 +2087,8 @@ var require_picomatch = __commonJS({
2087
2087
  try {
2088
2088
  const opts = options || {};
2089
2089
  return new RegExp(source, opts.flags || (opts.nocase ? "i" : ""));
2090
- } catch (err31) {
2091
- if (options && options.debug === true) throw err31;
2090
+ } catch (err32) {
2091
+ if (options && options.debug === true) throw err32;
2092
2092
  return /$^/;
2093
2093
  }
2094
2094
  };
@@ -2116,7 +2116,7 @@ var require_picomatch2 = __commonJS({
2116
2116
 
2117
2117
  // src/cli.ts
2118
2118
  import { Command as Command7 } from "commander";
2119
- import path81 from "path";
2119
+ import path82 from "path";
2120
2120
  import fs72 from "fs";
2121
2121
  import { createRequire as createRequire21 } from "module";
2122
2122
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -3149,14 +3149,14 @@ var Store = class {
3149
3149
  db;
3150
3150
  _stmts;
3151
3151
  // --- Files ---
3152
- insertFile(path82, language, contentHash, byteLength, workspace) {
3153
- const result = this._stmts.insertFile.run(path82, language, contentHash, byteLength, workspace ?? null);
3152
+ insertFile(path83, language, contentHash, byteLength, workspace) {
3153
+ const result = this._stmts.insertFile.run(path83, language, contentHash, byteLength, workspace ?? null);
3154
3154
  const fileId = Number(result.lastInsertRowid);
3155
3155
  this.createNode("file", fileId);
3156
3156
  return fileId;
3157
3157
  }
3158
- getFile(path82) {
3159
- return this._stmts.getFile.get(path82);
3158
+ getFile(path83) {
3159
+ return this._stmts.getFile.get(path83);
3160
3160
  }
3161
3161
  getFileById(id) {
3162
3162
  return this._stmts.getFileById.get(id);
@@ -6370,6 +6370,564 @@ var GitignoreMatcher = class {
6370
6370
  }
6371
6371
  };
6372
6372
 
6373
+ // src/tools/history.ts
6374
+ import { execFileSync as execFileSync2 } from "child_process";
6375
+
6376
+ // src/tools/git-analysis.ts
6377
+ import { execFileSync } from "child_process";
6378
+ function isGitRepo(cwd) {
6379
+ try {
6380
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
6381
+ cwd,
6382
+ stdio: "pipe",
6383
+ timeout: 5e3
6384
+ });
6385
+ return true;
6386
+ } catch {
6387
+ return false;
6388
+ }
6389
+ }
6390
+ function getGitFileStats(cwd, sinceDays) {
6391
+ const args = [
6392
+ "log",
6393
+ "--pretty=format:__COMMIT__%H|%aI|%aN",
6394
+ "--name-only",
6395
+ "--no-merges",
6396
+ "--diff-filter=ACDMR"
6397
+ ];
6398
+ if (sinceDays !== void 0) {
6399
+ args.push(`--since=${sinceDays} days ago`);
6400
+ }
6401
+ let output;
6402
+ try {
6403
+ output = execFileSync("git", args, {
6404
+ cwd,
6405
+ stdio: "pipe",
6406
+ maxBuffer: 10 * 1024 * 1024,
6407
+ // 10 MB
6408
+ timeout: 3e4
6409
+ }).toString("utf-8");
6410
+ } catch (e) {
6411
+ logger.warn({ error: e }, "git log failed");
6412
+ return /* @__PURE__ */ new Map();
6413
+ }
6414
+ const fileStats = /* @__PURE__ */ new Map();
6415
+ let currentDate = null;
6416
+ let currentAuthor = null;
6417
+ for (const line of output.split("\n")) {
6418
+ if (line.startsWith("__COMMIT__")) {
6419
+ const parts = line.slice("__COMMIT__".length).split("|");
6420
+ currentDate = new Date(parts[1]);
6421
+ currentAuthor = parts[2];
6422
+ continue;
6423
+ }
6424
+ const trimmed = line.trim();
6425
+ if (!trimmed || !currentDate || !currentAuthor) continue;
6426
+ const existing = fileStats.get(trimmed);
6427
+ if (existing) {
6428
+ existing.commits++;
6429
+ existing.authors.add(currentAuthor);
6430
+ if (currentDate < existing.firstDate) existing.firstDate = currentDate;
6431
+ if (currentDate > existing.lastDate) existing.lastDate = currentDate;
6432
+ } else {
6433
+ fileStats.set(trimmed, {
6434
+ file: trimmed,
6435
+ commits: 1,
6436
+ authors: /* @__PURE__ */ new Set([currentAuthor]),
6437
+ firstDate: currentDate,
6438
+ lastDate: currentDate
6439
+ });
6440
+ }
6441
+ }
6442
+ return fileStats;
6443
+ }
6444
+ function getChurnRate(cwd, options = {}) {
6445
+ const { sinceDays, limit = 50, filePattern } = options;
6446
+ if (!isGitRepo(cwd)) {
6447
+ return [];
6448
+ }
6449
+ const stats = getGitFileStats(cwd, sinceDays);
6450
+ let entries = [];
6451
+ for (const [file, data] of stats) {
6452
+ if (filePattern && !file.includes(filePattern)) continue;
6453
+ const lifespanMs = data.lastDate.getTime() - data.firstDate.getTime();
6454
+ const lifespanWeeks = Math.max(lifespanMs / (7 * 24 * 60 * 60 * 1e3), 1);
6455
+ const churnPerWeek = Math.round(data.commits / lifespanWeeks * 100) / 100;
6456
+ let assessment;
6457
+ if (churnPerWeek <= 1) assessment = "stable";
6458
+ else if (churnPerWeek <= 3) assessment = "active";
6459
+ else assessment = "volatile";
6460
+ entries.push({
6461
+ file,
6462
+ commits: data.commits,
6463
+ unique_authors: data.authors.size,
6464
+ first_seen: data.firstDate.toISOString().split("T")[0],
6465
+ last_modified: data.lastDate.toISOString().split("T")[0],
6466
+ churn_per_week: churnPerWeek,
6467
+ assessment
6468
+ });
6469
+ }
6470
+ entries.sort((a, b) => b.commits - a.commits);
6471
+ return entries.slice(0, limit);
6472
+ }
6473
+ function getHotspots(store, cwd, options = {}) {
6474
+ const { sinceDays = 90, limit = 20, minCyclomatic = 3 } = options;
6475
+ if (!isGitRepo(cwd)) {
6476
+ return getComplexityOnlyHotspots(store, limit, minCyclomatic);
6477
+ }
6478
+ const gitStats = getGitFileStats(cwd, sinceDays);
6479
+ const fileComplexity = getMaxCyclomaticPerFile(store);
6480
+ const entries = [];
6481
+ for (const [file, maxCyclomatic] of fileComplexity) {
6482
+ if (maxCyclomatic < minCyclomatic) continue;
6483
+ const git = gitStats.get(file);
6484
+ const commits = git?.commits ?? 0;
6485
+ const score = Math.round(maxCyclomatic * Math.log(1 + commits) * 100) / 100;
6486
+ if (score <= 0) continue;
6487
+ let assessment;
6488
+ if (score <= 3) assessment = "low";
6489
+ else if (score <= 10) assessment = "medium";
6490
+ else assessment = "high";
6491
+ entries.push({
6492
+ file,
6493
+ max_cyclomatic: maxCyclomatic,
6494
+ commits,
6495
+ score,
6496
+ assessment
6497
+ });
6498
+ }
6499
+ entries.sort((a, b) => b.score - a.score);
6500
+ return entries.slice(0, limit);
6501
+ }
6502
+ function getMaxCyclomaticPerFile(store) {
6503
+ const rows = store.db.prepare(`
6504
+ SELECT f.path, MAX(s.cyclomatic) as max_cyclomatic
6505
+ FROM symbols s
6506
+ JOIN files f ON s.file_id = f.id
6507
+ WHERE s.cyclomatic IS NOT NULL
6508
+ GROUP BY f.path
6509
+ `).all();
6510
+ const result = /* @__PURE__ */ new Map();
6511
+ for (const row of rows) {
6512
+ result.set(row.path, row.max_cyclomatic);
6513
+ }
6514
+ return result;
6515
+ }
6516
+ function getComplexityOnlyHotspots(store, limit, minCyclomatic) {
6517
+ const fileComplexity = getMaxCyclomaticPerFile(store);
6518
+ const entries = [];
6519
+ for (const [file, maxCyclomatic] of fileComplexity) {
6520
+ if (maxCyclomatic < minCyclomatic) continue;
6521
+ entries.push({
6522
+ file,
6523
+ max_cyclomatic: maxCyclomatic,
6524
+ commits: 0,
6525
+ score: maxCyclomatic,
6526
+ // score = complexity alone
6527
+ assessment: maxCyclomatic <= 3 ? "low" : maxCyclomatic <= 10 ? "medium" : "high"
6528
+ });
6529
+ }
6530
+ entries.sort((a, b) => b.score - a.score);
6531
+ return entries.slice(0, limit);
6532
+ }
6533
+
6534
+ // src/tools/history.ts
6535
+ function sampleFileCommits(cwd, filePath, sinceDays, count) {
6536
+ const args = [
6537
+ "log",
6538
+ "--pretty=format:%H|%aI",
6539
+ "--follow",
6540
+ "--no-merges",
6541
+ `--max-count=${count * 3}`
6542
+ ];
6543
+ if (sinceDays !== void 0) {
6544
+ args.push(`--since=${sinceDays} days ago`);
6545
+ }
6546
+ args.push("--", filePath);
6547
+ let output;
6548
+ try {
6549
+ output = execFileSync2("git", args, {
6550
+ cwd,
6551
+ stdio: "pipe",
6552
+ timeout: 1e4
6553
+ }).toString("utf-8");
6554
+ } catch {
6555
+ return [];
6556
+ }
6557
+ const all = output.split("\n").filter(Boolean).map((line) => {
6558
+ const [hash, date] = line.split("|");
6559
+ return { hash, date: date.split("T")[0] };
6560
+ });
6561
+ if (all.length <= count) return all;
6562
+ const step = Math.max(1, Math.floor((all.length - 1) / (count - 1)));
6563
+ const sampled = [];
6564
+ for (let i = 0; i < all.length && sampled.length < count; i += step) {
6565
+ sampled.push(all[i]);
6566
+ }
6567
+ if (sampled[sampled.length - 1] !== all[all.length - 1]) {
6568
+ sampled.push(all[all.length - 1]);
6569
+ }
6570
+ return sampled;
6571
+ }
6572
+ function getFileAtCommit(cwd, filePath, commitHash) {
6573
+ try {
6574
+ return execFileSync2("git", ["show", `${commitHash}:${filePath}`], {
6575
+ cwd,
6576
+ stdio: "pipe",
6577
+ timeout: 1e4,
6578
+ maxBuffer: 5 * 1024 * 1024
6579
+ }).toString("utf-8");
6580
+ } catch {
6581
+ return null;
6582
+ }
6583
+ }
6584
+ function countImports(content) {
6585
+ let count = 0;
6586
+ for (const line of content.split("\n")) {
6587
+ const t = line.trim();
6588
+ if (/^import\s+/.test(t) && /['"]/.test(t)) {
6589
+ count++;
6590
+ continue;
6591
+ }
6592
+ if (/\brequire\s*\(\s*['"]/.test(t)) {
6593
+ count++;
6594
+ continue;
6595
+ }
6596
+ if (/^(?:from\s+\S+\s+import|import\s+\S+)/.test(t)) {
6597
+ count++;
6598
+ continue;
6599
+ }
6600
+ if (/^import\s+"/.test(t)) {
6601
+ count++;
6602
+ continue;
6603
+ }
6604
+ if (/^use\s+[A-Z\\]/.test(t)) {
6605
+ count++;
6606
+ continue;
6607
+ }
6608
+ }
6609
+ return count;
6610
+ }
6611
+ function countImportersAtCommit(cwd, filePath, commitHash) {
6612
+ const searchPattern = filePath.replace(/\.[^.]+$/, "").replace(/\/index$/, "");
6613
+ if (searchPattern.length < 8) return 0;
6614
+ try {
6615
+ const output = execFileSync2("git", [
6616
+ "grep",
6617
+ "-l",
6618
+ "--fixed-strings",
6619
+ searchPattern,
6620
+ commitHash,
6621
+ "--"
6622
+ ], {
6623
+ cwd,
6624
+ stdio: "pipe",
6625
+ timeout: 15e3,
6626
+ maxBuffer: 2 * 1024 * 1024
6627
+ }).toString("utf-8");
6628
+ let count = 0;
6629
+ for (const line of output.split("\n")) {
6630
+ if (!line.trim()) continue;
6631
+ const colonIdx = line.indexOf(":");
6632
+ const file = colonIdx >= 0 ? line.slice(colonIdx + 1) : line;
6633
+ if (file !== filePath) count++;
6634
+ }
6635
+ return count;
6636
+ } catch {
6637
+ return 0;
6638
+ }
6639
+ }
6640
+ function getCouplingTrend(store, cwd, filePath, options = {}) {
6641
+ const { sinceDays = 90, snapshots = 6 } = options;
6642
+ if (!isGitRepo(cwd)) return null;
6643
+ const file = store.getFile(filePath);
6644
+ if (!file) return null;
6645
+ const currentCoupling = getCurrentCoupling(store, file.id);
6646
+ const currentSnapshot = {
6647
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
6648
+ commit: "HEAD",
6649
+ ...currentCoupling
6650
+ };
6651
+ const commits = sampleFileCommits(cwd, filePath, sinceDays, snapshots);
6652
+ const historical = [];
6653
+ for (const { hash, date } of commits) {
6654
+ try {
6655
+ const content = getFileAtCommit(cwd, filePath, hash);
6656
+ if (!content) continue;
6657
+ const ce = countImports(content);
6658
+ const ca = countImportersAtCommit(cwd, filePath, hash);
6659
+ const total = ca + ce;
6660
+ const instability = total === 0 ? 0 : Math.round(ce / total * 1e3) / 1e3;
6661
+ historical.push({ date, commit: hash.slice(0, 8), ca, ce, instability });
6662
+ } catch (e) {
6663
+ logger.debug({ file: filePath, commit: hash, error: e }, "Coupling snapshot failed");
6664
+ }
6665
+ }
6666
+ let trend = "stable";
6667
+ let instabilityDelta = 0;
6668
+ let couplingDelta = 0;
6669
+ if (historical.length > 0) {
6670
+ const oldest = historical[historical.length - 1];
6671
+ instabilityDelta = Math.round((currentSnapshot.instability - oldest.instability) * 1e3) / 1e3;
6672
+ couplingDelta = currentSnapshot.ca + currentSnapshot.ce - (oldest.ca + oldest.ce);
6673
+ if (instabilityDelta > 0.1) trend = "destabilizing";
6674
+ else if (instabilityDelta < -0.1) trend = "stabilizing";
6675
+ }
6676
+ return {
6677
+ file: filePath,
6678
+ current: currentSnapshot,
6679
+ historical,
6680
+ trend,
6681
+ instability_delta: instabilityDelta,
6682
+ coupling_delta: couplingDelta
6683
+ };
6684
+ }
6685
+ function getCurrentCoupling(store, fileId) {
6686
+ const row = store.db.prepare(`
6687
+ WITH file_edges AS (
6688
+ SELECT
6689
+ CASE WHEN n1.node_type = 'file' THEN n1.ref_id ELSE s1.file_id END AS src_file,
6690
+ CASE WHEN n2.node_type = 'file' THEN n2.ref_id ELSE s2.file_id END AS tgt_file
6691
+ FROM edges e
6692
+ JOIN edge_types et ON e.edge_type_id = et.id
6693
+ JOIN nodes n1 ON e.source_node_id = n1.id
6694
+ JOIN nodes n2 ON e.target_node_id = n2.id
6695
+ LEFT JOIN symbols s1 ON n1.node_type = 'symbol' AND n1.ref_id = s1.id
6696
+ LEFT JOIN symbols s2 ON n2.node_type = 'symbol' AND n2.ref_id = s2.id
6697
+ WHERE et.name IN ('esm_imports', 'imports', 'py_imports', 'py_reexports')
6698
+ AND (
6699
+ (CASE WHEN n1.node_type = 'file' THEN n1.ref_id ELSE s1.file_id END) = ?
6700
+ OR (CASE WHEN n2.node_type = 'file' THEN n2.ref_id ELSE s2.file_id END) = ?
6701
+ )
6702
+ )
6703
+ SELECT
6704
+ COUNT(DISTINCT CASE WHEN src_file = ? AND tgt_file != ? THEN tgt_file END) AS ce,
6705
+ COUNT(DISTINCT CASE WHEN tgt_file = ? AND src_file != ? THEN src_file END) AS ca
6706
+ FROM file_edges
6707
+ `).get(fileId, fileId, fileId, fileId, fileId, fileId);
6708
+ const ca = row?.ca ?? 0;
6709
+ const ce = row?.ce ?? 0;
6710
+ const total = ca + ce;
6711
+ const instability = total === 0 ? 0 : Math.round(ce / total * 1e3) / 1e3;
6712
+ return { ca, ce, instability };
6713
+ }
6714
+ function stripStringsForBraces(source) {
6715
+ let s = source.replace(/\/\*[\s\S]*?\*\//g, "");
6716
+ s = s.replace(/\/\/.*$/gm, "");
6717
+ s = s.replace(/#.*$/gm, "");
6718
+ s = s.replace(/`[^`]*`/g, '""');
6719
+ s = s.replace(/"(?:[^"\\]|\\.)*"/g, '""');
6720
+ s = s.replace(/'(?:[^'\\]|\\.)*'/g, "''");
6721
+ return s;
6722
+ }
6723
+ function extractSymbolSource(content, symbolName, symbolKind) {
6724
+ const lines = content.split("\n");
6725
+ const patterns = [];
6726
+ const escaped = symbolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6727
+ if (symbolKind === "function" || symbolKind === "method") {
6728
+ patterns.push(
6729
+ new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+${escaped}\\s*[(<]`),
6730
+ new RegExp(`(?:public|private|protected)\\s+(?:async\\s+)?(?:static\\s+)?${escaped}\\s*\\(`),
6731
+ new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:async\\s+)?(?:\\([^)]*\\)|\\w+)\\s*=>`),
6732
+ new RegExp(`\\b${escaped}\\s*:\\s*(?:async\\s+)?function`),
6733
+ new RegExp(`\\bdef\\s+${escaped}\\s*\\(`),
6734
+ new RegExp(`\\bfunc\\s+${escaped}\\s*\\(`)
6735
+ );
6736
+ } else if (symbolKind === "class") {
6737
+ patterns.push(
6738
+ new RegExp(`(?:export\\s+)?(?:abstract\\s+)?class\\s+${escaped}\\b`),
6739
+ new RegExp(`\\bclass\\s+${escaped}\\b`)
6740
+ );
6741
+ } else {
6742
+ patterns.push(new RegExp(`\\b${escaped}\\b`));
6743
+ }
6744
+ let startLine = -1;
6745
+ for (let i = 0; i < lines.length; i++) {
6746
+ if (patterns.some((p4) => p4.test(lines[i]))) {
6747
+ startLine = i;
6748
+ break;
6749
+ }
6750
+ }
6751
+ if (startLine === -1) return null;
6752
+ const cleanLines = stripStringsForBraces(content).split("\n");
6753
+ let depth = 0;
6754
+ let foundOpenBrace = false;
6755
+ let endLine = startLine;
6756
+ for (let i = startLine; i < cleanLines.length; i++) {
6757
+ for (const ch of cleanLines[i]) {
6758
+ if (ch === "{") {
6759
+ depth++;
6760
+ foundOpenBrace = true;
6761
+ } else if (ch === "}") {
6762
+ depth--;
6763
+ }
6764
+ }
6765
+ endLine = i;
6766
+ if (foundOpenBrace && depth <= 0) break;
6767
+ if (!foundOpenBrace && i > startLine && lines[i].trim() !== "") {
6768
+ const startIndent = lines[startLine].match(/^\s*/)?.[0].length ?? 0;
6769
+ const currentIndent = lines[i].match(/^\s*/)?.[0].length ?? 0;
6770
+ if (currentIndent <= startIndent && i > startLine + 1) {
6771
+ endLine = i - 1;
6772
+ break;
6773
+ }
6774
+ }
6775
+ }
6776
+ const source = lines.slice(startLine, endLine + 1).join("\n");
6777
+ const signature = lines[startLine].trim();
6778
+ return { source, signature };
6779
+ }
6780
+ function getSymbolComplexityTrend(store, cwd, symbolId, options = {}) {
6781
+ const { sinceDays, snapshots = 6 } = options;
6782
+ if (!isGitRepo(cwd)) return null;
6783
+ const sym = store.db.prepare(`
6784
+ SELECT s.symbol_id, s.name, s.kind, s.fqn, s.signature,
6785
+ s.cyclomatic, s.max_nesting, s.param_count,
6786
+ s.line_start, s.line_end,
6787
+ f.path
6788
+ FROM symbols s
6789
+ JOIN files f ON s.file_id = f.id
6790
+ WHERE s.symbol_id = ?
6791
+ `).get(symbolId);
6792
+ if (!sym) return null;
6793
+ const currentLines = sym.line_end && sym.line_start ? sym.line_end - sym.line_start + 1 : 0;
6794
+ const currentSnapshot = {
6795
+ date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
6796
+ commit: "HEAD",
6797
+ cyclomatic: sym.cyclomatic ?? 1,
6798
+ max_nesting: sym.max_nesting ?? 0,
6799
+ param_count: sym.param_count ?? 0,
6800
+ lines: currentLines
6801
+ };
6802
+ const commits = sampleFileCommits(cwd, sym.path, sinceDays, snapshots);
6803
+ const historical = [];
6804
+ for (const { hash, date } of commits) {
6805
+ const content = getFileAtCommit(cwd, sym.path, hash);
6806
+ if (!content) continue;
6807
+ try {
6808
+ const extracted = extractSymbolSource(content, sym.name, sym.kind);
6809
+ if (!extracted) continue;
6810
+ const cyclomatic = computeCyclomatic(extracted.source);
6811
+ const maxNesting = computeMaxNesting(extracted.source);
6812
+ const paramCount = computeParamCount(extracted.signature);
6813
+ const lineCount = extracted.source.split("\n").length;
6814
+ historical.push({
6815
+ date,
6816
+ commit: hash.slice(0, 8),
6817
+ cyclomatic,
6818
+ max_nesting: maxNesting,
6819
+ param_count: paramCount,
6820
+ lines: lineCount
6821
+ });
6822
+ } catch (e) {
6823
+ logger.debug({ symbol: symbolId, commit: hash, error: e }, "Symbol complexity snapshot failed");
6824
+ }
6825
+ }
6826
+ let trend = "stable";
6827
+ let cyclomaticDelta = 0;
6828
+ if (historical.length > 0) {
6829
+ const oldest = historical[historical.length - 1];
6830
+ cyclomaticDelta = currentSnapshot.cyclomatic - oldest.cyclomatic;
6831
+ if (cyclomaticDelta >= 2) trend = "degrading";
6832
+ else if (cyclomaticDelta <= -2) trend = "improving";
6833
+ }
6834
+ return {
6835
+ symbol_id: sym.symbol_id,
6836
+ name: sym.name,
6837
+ file: sym.path,
6838
+ current: currentSnapshot,
6839
+ historical,
6840
+ trend,
6841
+ cyclomatic_delta: cyclomaticDelta
6842
+ };
6843
+ }
6844
+ function captureGraphSnapshots(store, cwd) {
6845
+ let commitHash;
6846
+ try {
6847
+ commitHash = execFileSync2("git", ["rev-parse", "--short", "HEAD"], {
6848
+ cwd,
6849
+ stdio: "pipe",
6850
+ timeout: 5e3
6851
+ }).toString("utf-8").trim();
6852
+ } catch {
6853
+ }
6854
+ if (commitHash) {
6855
+ const existing = store.db.prepare(
6856
+ "SELECT id FROM graph_snapshots WHERE commit_hash = ? AND snapshot_type = 'coupling_summary' LIMIT 1"
6857
+ ).get(commitHash);
6858
+ if (existing) return;
6859
+ }
6860
+ const allFiles = store.getAllFiles();
6861
+ if (allFiles.length === 0) return;
6862
+ const allFileIds = allFiles.map((f) => f.id);
6863
+ const fileNodeMap = /* @__PURE__ */ new Map();
6864
+ const CHUNK = 500;
6865
+ for (let i = 0; i < allFileIds.length; i += CHUNK) {
6866
+ const chunk = allFileIds.slice(i, i + CHUNK);
6867
+ for (const [k, v] of store.getNodeIdsBatch("file", chunk)) {
6868
+ fileNodeMap.set(k, v);
6869
+ }
6870
+ }
6871
+ const importsTypeRow = store.db.prepare(
6872
+ "SELECT id FROM edge_types WHERE name = 'imports'"
6873
+ ).get();
6874
+ if (!importsTypeRow) return;
6875
+ const allImportEdges = store.db.prepare(
6876
+ "SELECT source_node_id, target_node_id FROM edges WHERE edge_type_id = ?"
6877
+ ).all(importsTypeRow.id);
6878
+ const nodeToFileId = /* @__PURE__ */ new Map();
6879
+ for (const [fileId, nodeId] of fileNodeMap) {
6880
+ nodeToFileId.set(nodeId, fileId);
6881
+ }
6882
+ const fileCa = /* @__PURE__ */ new Map();
6883
+ const fileCe = /* @__PURE__ */ new Map();
6884
+ for (const edge of allImportEdges) {
6885
+ const srcFileId = nodeToFileId.get(edge.source_node_id);
6886
+ const tgtFileId = nodeToFileId.get(edge.target_node_id);
6887
+ if (srcFileId != null) fileCe.set(srcFileId, (fileCe.get(srcFileId) ?? 0) + 1);
6888
+ if (tgtFileId != null) fileCa.set(tgtFileId, (fileCa.get(tgtFileId) ?? 0) + 1);
6889
+ }
6890
+ const fileIdToPath = new Map(allFiles.map((f) => [f.id, f.path]));
6891
+ const insertStmt = store.db.prepare(
6892
+ "INSERT INTO graph_snapshots (commit_hash, snapshot_type, file_path, data) VALUES (?, ?, ?, ?)"
6893
+ );
6894
+ let count = 0;
6895
+ store.db.transaction(() => {
6896
+ for (const fileId of allFileIds) {
6897
+ const ca = fileCa.get(fileId) ?? 0;
6898
+ const ce = fileCe.get(fileId) ?? 0;
6899
+ if (ca === 0 && ce === 0) continue;
6900
+ const total = ca + ce;
6901
+ const instability = Math.round(ce / total * 1e3) / 1e3;
6902
+ const filePath = fileIdToPath.get(fileId);
6903
+ if (!filePath) continue;
6904
+ insertStmt.run(commitHash ?? null, "coupling", filePath, JSON.stringify({ ca, ce, instability }));
6905
+ count++;
6906
+ }
6907
+ if (count > 0) {
6908
+ const totalInstability = allFileIds.reduce((sum, fid) => {
6909
+ const ca = fileCa.get(fid) ?? 0;
6910
+ const ce = fileCe.get(fid) ?? 0;
6911
+ const total = ca + ce;
6912
+ return sum + (total === 0 ? 0 : ce / total);
6913
+ }, 0);
6914
+ insertStmt.run(
6915
+ commitHash ?? null,
6916
+ "coupling_summary",
6917
+ null,
6918
+ JSON.stringify({
6919
+ total_files: allFiles.length,
6920
+ files_with_edges: count,
6921
+ avg_instability: Math.round(totalInstability / allFiles.length * 1e3) / 1e3
6922
+ })
6923
+ );
6924
+ }
6925
+ })();
6926
+ if (count > 0) {
6927
+ logger.info({ files: count, commit: commitHash }, "Coupling snapshots captured");
6928
+ }
6929
+ }
6930
+
6373
6931
  // src/indexer/pipeline.ts
6374
6932
  var IndexingPipeline = class _IndexingPipeline {
6375
6933
  constructor(store, registry, config, rootPath) {
@@ -6463,9 +7021,9 @@ var IndexingPipeline = class _IndexingPipeline {
6463
7021
  this.registerFrameworkEdgeTypes();
6464
7022
  try {
6465
7023
  {
6466
- const BATCH_SIZE2 = 100;
6467
- for (let i = 0; i < relPaths.length; i += BATCH_SIZE2) {
6468
- const batch = relPaths.slice(i, i + BATCH_SIZE2);
7024
+ const BATCH_SIZE3 = 100;
7025
+ for (let i = 0; i < relPaths.length; i += BATCH_SIZE3) {
7026
+ const batch = relPaths.slice(i, i + BATCH_SIZE3);
6469
7027
  const extractions = [];
6470
7028
  for (const relPath of batch) {
6471
7029
  const ext = await this.extractFile(relPath, force);
@@ -7014,7 +7572,16 @@ var IndexingPipeline = class _IndexingPipeline {
7014
7572
  const absSource = path10.resolve(this.rootPath, file.path);
7015
7573
  const sourceNodeId = fileNodeMap.get(fileId);
7016
7574
  if (sourceNodeId == null) continue;
7575
+ const consolidated = /* @__PURE__ */ new Map();
7017
7576
  for (const { from, specifiers } of imports) {
7577
+ const existing = consolidated.get(from);
7578
+ if (existing) {
7579
+ existing.push(...specifiers);
7580
+ } else {
7581
+ consolidated.set(from, [...specifiers]);
7582
+ }
7583
+ }
7584
+ for (const [from, specifiers] of consolidated) {
7018
7585
  if (!from.startsWith(".") && !from.startsWith("/") && !from.startsWith("@/") && !from.startsWith("~")) continue;
7019
7586
  const resolved = resolver.resolve(from, absSource);
7020
7587
  if (!resolved) continue;
@@ -10248,6 +10815,7 @@ function isTestFile(filePath) {
10248
10815
 
10249
10816
  // src/tools/introspect.ts
10250
10817
  init_graph_analysis();
10818
+ var TEST_FIXTURE_RE = /(?:^|\/)(?:tests?|__tests__|spec)\/fixtures?\//;
10251
10819
  function safeParseMeta(raw) {
10252
10820
  if (!raw) return {};
10253
10821
  try {
@@ -10415,7 +10983,7 @@ function walkDescendants(store, name, result, visited, depth) {
10415
10983
  }
10416
10984
  }
10417
10985
  function getDeadExports(store, filePattern) {
10418
- const exported = store.getExportedSymbols(filePattern);
10986
+ const exported = store.getExportedSymbols(filePattern).filter((s) => !TEST_FIXTURE_RE.test(s.file_path));
10419
10987
  const importedNames = /* @__PURE__ */ new Set();
10420
10988
  const importEdges = store.getEdgesByType("imports");
10421
10989
  for (const edge of importEdges) {
@@ -10490,7 +11058,7 @@ function getDependencyGraph(store, filePath) {
10490
11058
  return { file: filePath, imports, imported_by: importedBy };
10491
11059
  }
10492
11060
  function getUntestedExports(store, filePattern) {
10493
- const exported = store.getExportedSymbols(filePattern).filter((s) => s.kind !== "method");
11061
+ const exported = store.getExportedSymbols(filePattern).filter((s) => s.kind !== "method").filter((s) => !TEST_FIXTURE_RE.test(s.file_path));
10494
11062
  const allFiles = store.getAllFiles();
10495
11063
  const testFiles = allFiles.filter((f) => /\.(test|spec)\.(ts|js|tsx|jsx)$/.test(f.path)).map((f) => f.path.toLowerCase());
10496
11064
  const untested = [];
@@ -10619,164 +11187,6 @@ function selfAudit(store) {
10619
11187
  // src/server.ts
10620
11188
  init_graph_analysis();
10621
11189
 
10622
- // src/tools/git-analysis.ts
10623
- import { execFileSync } from "child_process";
10624
- function isGitRepo(cwd) {
10625
- try {
10626
- execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
10627
- cwd,
10628
- stdio: "pipe",
10629
- timeout: 5e3
10630
- });
10631
- return true;
10632
- } catch {
10633
- return false;
10634
- }
10635
- }
10636
- function getGitFileStats(cwd, sinceDays) {
10637
- const args = [
10638
- "log",
10639
- "--pretty=format:__COMMIT__%H|%aI|%aN",
10640
- "--name-only",
10641
- "--no-merges",
10642
- "--diff-filter=ACDMR"
10643
- ];
10644
- if (sinceDays !== void 0) {
10645
- args.push(`--since=${sinceDays} days ago`);
10646
- }
10647
- let output;
10648
- try {
10649
- output = execFileSync("git", args, {
10650
- cwd,
10651
- stdio: "pipe",
10652
- maxBuffer: 10 * 1024 * 1024,
10653
- // 10 MB
10654
- timeout: 3e4
10655
- }).toString("utf-8");
10656
- } catch (e) {
10657
- logger.warn({ error: e }, "git log failed");
10658
- return /* @__PURE__ */ new Map();
10659
- }
10660
- const fileStats = /* @__PURE__ */ new Map();
10661
- let currentDate = null;
10662
- let currentAuthor = null;
10663
- for (const line of output.split("\n")) {
10664
- if (line.startsWith("__COMMIT__")) {
10665
- const parts = line.slice("__COMMIT__".length).split("|");
10666
- currentDate = new Date(parts[1]);
10667
- currentAuthor = parts[2];
10668
- continue;
10669
- }
10670
- const trimmed = line.trim();
10671
- if (!trimmed || !currentDate || !currentAuthor) continue;
10672
- const existing = fileStats.get(trimmed);
10673
- if (existing) {
10674
- existing.commits++;
10675
- existing.authors.add(currentAuthor);
10676
- if (currentDate < existing.firstDate) existing.firstDate = currentDate;
10677
- if (currentDate > existing.lastDate) existing.lastDate = currentDate;
10678
- } else {
10679
- fileStats.set(trimmed, {
10680
- file: trimmed,
10681
- commits: 1,
10682
- authors: /* @__PURE__ */ new Set([currentAuthor]),
10683
- firstDate: currentDate,
10684
- lastDate: currentDate
10685
- });
10686
- }
10687
- }
10688
- return fileStats;
10689
- }
10690
- function getChurnRate(cwd, options = {}) {
10691
- const { sinceDays, limit = 50, filePattern } = options;
10692
- if (!isGitRepo(cwd)) {
10693
- return [];
10694
- }
10695
- const stats = getGitFileStats(cwd, sinceDays);
10696
- let entries = [];
10697
- for (const [file, data] of stats) {
10698
- if (filePattern && !file.includes(filePattern)) continue;
10699
- const lifespanMs = data.lastDate.getTime() - data.firstDate.getTime();
10700
- const lifespanWeeks = Math.max(lifespanMs / (7 * 24 * 60 * 60 * 1e3), 1);
10701
- const churnPerWeek = Math.round(data.commits / lifespanWeeks * 100) / 100;
10702
- let assessment;
10703
- if (churnPerWeek <= 1) assessment = "stable";
10704
- else if (churnPerWeek <= 3) assessment = "active";
10705
- else assessment = "volatile";
10706
- entries.push({
10707
- file,
10708
- commits: data.commits,
10709
- unique_authors: data.authors.size,
10710
- first_seen: data.firstDate.toISOString().split("T")[0],
10711
- last_modified: data.lastDate.toISOString().split("T")[0],
10712
- churn_per_week: churnPerWeek,
10713
- assessment
10714
- });
10715
- }
10716
- entries.sort((a, b) => b.commits - a.commits);
10717
- return entries.slice(0, limit);
10718
- }
10719
- function getHotspots(store, cwd, options = {}) {
10720
- const { sinceDays = 90, limit = 20, minCyclomatic = 3 } = options;
10721
- if (!isGitRepo(cwd)) {
10722
- return getComplexityOnlyHotspots(store, limit, minCyclomatic);
10723
- }
10724
- const gitStats = getGitFileStats(cwd, sinceDays);
10725
- const fileComplexity = getMaxCyclomaticPerFile(store);
10726
- const entries = [];
10727
- for (const [file, maxCyclomatic] of fileComplexity) {
10728
- if (maxCyclomatic < minCyclomatic) continue;
10729
- const git = gitStats.get(file);
10730
- const commits = git?.commits ?? 0;
10731
- const score = Math.round(maxCyclomatic * Math.log(1 + commits) * 100) / 100;
10732
- if (score <= 0) continue;
10733
- let assessment;
10734
- if (score <= 3) assessment = "low";
10735
- else if (score <= 10) assessment = "medium";
10736
- else assessment = "high";
10737
- entries.push({
10738
- file,
10739
- max_cyclomatic: maxCyclomatic,
10740
- commits,
10741
- score,
10742
- assessment
10743
- });
10744
- }
10745
- entries.sort((a, b) => b.score - a.score);
10746
- return entries.slice(0, limit);
10747
- }
10748
- function getMaxCyclomaticPerFile(store) {
10749
- const rows = store.db.prepare(`
10750
- SELECT f.path, MAX(s.cyclomatic) as max_cyclomatic
10751
- FROM symbols s
10752
- JOIN files f ON s.file_id = f.id
10753
- WHERE s.cyclomatic IS NOT NULL
10754
- GROUP BY f.path
10755
- `).all();
10756
- const result = /* @__PURE__ */ new Map();
10757
- for (const row of rows) {
10758
- result.set(row.path, row.max_cyclomatic);
10759
- }
10760
- return result;
10761
- }
10762
- function getComplexityOnlyHotspots(store, limit, minCyclomatic) {
10763
- const fileComplexity = getMaxCyclomaticPerFile(store);
10764
- const entries = [];
10765
- for (const [file, maxCyclomatic] of fileComplexity) {
10766
- if (maxCyclomatic < minCyclomatic) continue;
10767
- entries.push({
10768
- file,
10769
- max_cyclomatic: maxCyclomatic,
10770
- commits: 0,
10771
- score: maxCyclomatic,
10772
- // score = complexity alone
10773
- assessment: maxCyclomatic <= 3 ? "low" : maxCyclomatic <= 10 ? "medium" : "high"
10774
- });
10775
- }
10776
- entries.sort((a, b) => b.score - a.score);
10777
- return entries.slice(0, limit);
10778
- }
10779
-
10780
11190
  // src/tools/dead-code.ts
10781
11191
  import path17 from "path";
10782
11192
  var BARREL_PATTERNS = [
@@ -10850,7 +11260,8 @@ function buildBarrelExportedNames(store) {
10850
11260
  }
10851
11261
  function getDeadCodeV2(store, options = {}) {
10852
11262
  const { filePattern, threshold = 0.5, limit = 50 } = options;
10853
- const exported = store.getExportedSymbols(filePattern).filter((s) => s.kind !== "method");
11263
+ const TEST_FIXTURE_RE2 = /(?:^|\/)(?:tests?|__tests__|spec)\/fixtures?\//;
11264
+ const exported = store.getExportedSymbols(filePattern).filter((s) => s.kind !== "method").filter((s) => !TEST_FIXTURE_RE2.test(s.file_path));
10854
11265
  const importedNames = buildImportedNamesSet(store);
10855
11266
  const referencedNodeIds = buildReferencedSymbolIds(store);
10856
11267
  const barrelExportedNames = buildBarrelExportedNames(store);
@@ -11484,13 +11895,13 @@ function getIndent(line) {
11484
11895
  init_layer_violations();
11485
11896
 
11486
11897
  // src/tools/git-ownership.ts
11487
- import { execFileSync as execFileSync2 } from "child_process";
11898
+ import { execFileSync as execFileSync3 } from "child_process";
11488
11899
  function getFileOwnership(cwd, filePaths) {
11489
11900
  if (!isGitRepo(cwd)) return [];
11490
11901
  const results = [];
11491
11902
  for (const filePath of filePaths) {
11492
11903
  try {
11493
- const output = execFileSync2("git", [
11904
+ const output = execFileSync3("git", [
11494
11905
  "shortlog",
11495
11906
  "-sn",
11496
11907
  "--no-merges",
@@ -11534,7 +11945,7 @@ function getSymbolOwnership(store, cwd, symbolId) {
11534
11945
  const file = store.getFileById(symbol.file_id);
11535
11946
  if (!file) return null;
11536
11947
  try {
11537
- const output = execFileSync2("git", [
11948
+ const output = execFileSync3("git", [
11538
11949
  "blame",
11539
11950
  "--porcelain",
11540
11951
  `-L${symbol.line_start},${symbol.line_end}`,
@@ -11578,10 +11989,10 @@ function getSymbolOwnership(store, cwd, symbolId) {
11578
11989
  }
11579
11990
 
11580
11991
  // src/tools/complexity-trend.ts
11581
- import { execFileSync as execFileSync3 } from "child_process";
11992
+ import { execFileSync as execFileSync4 } from "child_process";
11582
11993
  function getHistoricalCommits(cwd, filePath, count) {
11583
11994
  try {
11584
- const output = execFileSync3("git", [
11995
+ const output = execFileSync4("git", [
11585
11996
  "log",
11586
11997
  "--pretty=format:%H|%aI",
11587
11998
  "--follow",
@@ -11612,9 +12023,9 @@ function getHistoricalCommits(cwd, filePath, count) {
11612
12023
  return [];
11613
12024
  }
11614
12025
  }
11615
- function getFileAtCommit(cwd, filePath, commitHash) {
12026
+ function getFileAtCommit2(cwd, filePath, commitHash) {
11616
12027
  try {
11617
- return execFileSync3("git", ["show", `${commitHash}:${filePath}`], {
12028
+ return execFileSync4("git", ["show", `${commitHash}:${filePath}`], {
11618
12029
  cwd,
11619
12030
  stdio: "pipe",
11620
12031
  timeout: 1e4
@@ -11695,7 +12106,7 @@ function getComplexityTrend(store, cwd, filePath, options = {}) {
11695
12106
  const commits = getHistoricalCommits(cwd, filePath, snapshots);
11696
12107
  const historical = [];
11697
12108
  for (const { hash, date } of commits) {
11698
- const content = getFileAtCommit(cwd, filePath, hash);
12109
+ const content = getFileAtCommit2(cwd, filePath, hash);
11699
12110
  if (!content) continue;
11700
12111
  try {
11701
12112
  historical.push(computeSnapshot(content, hash, date));
@@ -11720,318 +12131,6 @@ function getComplexityTrend(store, cwd, filePath, options = {}) {
11720
12131
  };
11721
12132
  }
11722
12133
 
11723
- // src/tools/history.ts
11724
- import { execFileSync as execFileSync4 } from "child_process";
11725
- function sampleFileCommits(cwd, filePath, sinceDays, count) {
11726
- const args = [
11727
- "log",
11728
- "--pretty=format:%H|%aI",
11729
- "--follow",
11730
- "--no-merges",
11731
- `--max-count=${count * 3}`
11732
- ];
11733
- if (sinceDays !== void 0) {
11734
- args.push(`--since=${sinceDays} days ago`);
11735
- }
11736
- args.push("--", filePath);
11737
- let output;
11738
- try {
11739
- output = execFileSync4("git", args, {
11740
- cwd,
11741
- stdio: "pipe",
11742
- timeout: 1e4
11743
- }).toString("utf-8");
11744
- } catch {
11745
- return [];
11746
- }
11747
- const all = output.split("\n").filter(Boolean).map((line) => {
11748
- const [hash, date] = line.split("|");
11749
- return { hash, date: date.split("T")[0] };
11750
- });
11751
- if (all.length <= count) return all;
11752
- const step = Math.max(1, Math.floor((all.length - 1) / (count - 1)));
11753
- const sampled = [];
11754
- for (let i = 0; i < all.length && sampled.length < count; i += step) {
11755
- sampled.push(all[i]);
11756
- }
11757
- if (sampled[sampled.length - 1] !== all[all.length - 1]) {
11758
- sampled.push(all[all.length - 1]);
11759
- }
11760
- return sampled;
11761
- }
11762
- function getFileAtCommit2(cwd, filePath, commitHash) {
11763
- try {
11764
- return execFileSync4("git", ["show", `${commitHash}:${filePath}`], {
11765
- cwd,
11766
- stdio: "pipe",
11767
- timeout: 1e4,
11768
- maxBuffer: 5 * 1024 * 1024
11769
- }).toString("utf-8");
11770
- } catch {
11771
- return null;
11772
- }
11773
- }
11774
- function countImports(content) {
11775
- let count = 0;
11776
- for (const line of content.split("\n")) {
11777
- const t = line.trim();
11778
- if (/^import\s+/.test(t) && /['"]/.test(t)) {
11779
- count++;
11780
- continue;
11781
- }
11782
- if (/\brequire\s*\(\s*['"]/.test(t)) {
11783
- count++;
11784
- continue;
11785
- }
11786
- if (/^(?:from\s+\S+\s+import|import\s+\S+)/.test(t)) {
11787
- count++;
11788
- continue;
11789
- }
11790
- if (/^import\s+"/.test(t)) {
11791
- count++;
11792
- continue;
11793
- }
11794
- if (/^use\s+[A-Z\\]/.test(t)) {
11795
- count++;
11796
- continue;
11797
- }
11798
- }
11799
- return count;
11800
- }
11801
- function countImportersAtCommit(cwd, filePath, commitHash) {
11802
- const searchPattern = filePath.replace(/\.[^.]+$/, "").replace(/\/index$/, "");
11803
- if (searchPattern.length < 8) return 0;
11804
- try {
11805
- const output = execFileSync4("git", [
11806
- "grep",
11807
- "-l",
11808
- "--fixed-strings",
11809
- searchPattern,
11810
- commitHash,
11811
- "--"
11812
- ], {
11813
- cwd,
11814
- stdio: "pipe",
11815
- timeout: 15e3,
11816
- maxBuffer: 2 * 1024 * 1024
11817
- }).toString("utf-8");
11818
- let count = 0;
11819
- for (const line of output.split("\n")) {
11820
- if (!line.trim()) continue;
11821
- const colonIdx = line.indexOf(":");
11822
- const file = colonIdx >= 0 ? line.slice(colonIdx + 1) : line;
11823
- if (file !== filePath) count++;
11824
- }
11825
- return count;
11826
- } catch {
11827
- return 0;
11828
- }
11829
- }
11830
- function getCouplingTrend(store, cwd, filePath, options = {}) {
11831
- const { sinceDays = 90, snapshots = 6 } = options;
11832
- if (!isGitRepo(cwd)) return null;
11833
- const file = store.getFile(filePath);
11834
- if (!file) return null;
11835
- const currentCoupling = getCurrentCoupling(store, file.id);
11836
- const currentSnapshot = {
11837
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
11838
- commit: "HEAD",
11839
- ...currentCoupling
11840
- };
11841
- const commits = sampleFileCommits(cwd, filePath, sinceDays, snapshots);
11842
- const historical = [];
11843
- for (const { hash, date } of commits) {
11844
- try {
11845
- const content = getFileAtCommit2(cwd, filePath, hash);
11846
- if (!content) continue;
11847
- const ce = countImports(content);
11848
- const ca = countImportersAtCommit(cwd, filePath, hash);
11849
- const total = ca + ce;
11850
- const instability = total === 0 ? 0 : Math.round(ce / total * 1e3) / 1e3;
11851
- historical.push({ date, commit: hash.slice(0, 8), ca, ce, instability });
11852
- } catch (e) {
11853
- logger.debug({ file: filePath, commit: hash, error: e }, "Coupling snapshot failed");
11854
- }
11855
- }
11856
- let trend = "stable";
11857
- let instabilityDelta = 0;
11858
- let couplingDelta = 0;
11859
- if (historical.length > 0) {
11860
- const oldest = historical[historical.length - 1];
11861
- instabilityDelta = Math.round((currentSnapshot.instability - oldest.instability) * 1e3) / 1e3;
11862
- couplingDelta = currentSnapshot.ca + currentSnapshot.ce - (oldest.ca + oldest.ce);
11863
- if (instabilityDelta > 0.1) trend = "destabilizing";
11864
- else if (instabilityDelta < -0.1) trend = "stabilizing";
11865
- }
11866
- return {
11867
- file: filePath,
11868
- current: currentSnapshot,
11869
- historical,
11870
- trend,
11871
- instability_delta: instabilityDelta,
11872
- coupling_delta: couplingDelta
11873
- };
11874
- }
11875
- function getCurrentCoupling(store, fileId) {
11876
- const row = store.db.prepare(`
11877
- WITH file_edges AS (
11878
- SELECT
11879
- CASE WHEN n1.node_type = 'file' THEN n1.ref_id ELSE s1.file_id END AS src_file,
11880
- CASE WHEN n2.node_type = 'file' THEN n2.ref_id ELSE s2.file_id END AS tgt_file
11881
- FROM edges e
11882
- JOIN edge_types et ON e.edge_type_id = et.id
11883
- JOIN nodes n1 ON e.source_node_id = n1.id
11884
- JOIN nodes n2 ON e.target_node_id = n2.id
11885
- LEFT JOIN symbols s1 ON n1.node_type = 'symbol' AND n1.ref_id = s1.id
11886
- LEFT JOIN symbols s2 ON n2.node_type = 'symbol' AND n2.ref_id = s2.id
11887
- WHERE et.name IN ('esm_imports', 'imports', 'py_imports', 'py_reexports')
11888
- AND (
11889
- (CASE WHEN n1.node_type = 'file' THEN n1.ref_id ELSE s1.file_id END) = ?
11890
- OR (CASE WHEN n2.node_type = 'file' THEN n2.ref_id ELSE s2.file_id END) = ?
11891
- )
11892
- )
11893
- SELECT
11894
- COUNT(DISTINCT CASE WHEN src_file = ? AND tgt_file != ? THEN tgt_file END) AS ce,
11895
- COUNT(DISTINCT CASE WHEN tgt_file = ? AND src_file != ? THEN src_file END) AS ca
11896
- FROM file_edges
11897
- `).get(fileId, fileId, fileId, fileId, fileId, fileId);
11898
- const ca = row?.ca ?? 0;
11899
- const ce = row?.ce ?? 0;
11900
- const total = ca + ce;
11901
- const instability = total === 0 ? 0 : Math.round(ce / total * 1e3) / 1e3;
11902
- return { ca, ce, instability };
11903
- }
11904
- function stripStringsForBraces(source) {
11905
- let s = source.replace(/\/\*[\s\S]*?\*\//g, "");
11906
- s = s.replace(/\/\/.*$/gm, "");
11907
- s = s.replace(/#.*$/gm, "");
11908
- s = s.replace(/`[^`]*`/g, '""');
11909
- s = s.replace(/"(?:[^"\\]|\\.)*"/g, '""');
11910
- s = s.replace(/'(?:[^'\\]|\\.)*'/g, "''");
11911
- return s;
11912
- }
11913
- function extractSymbolSource(content, symbolName, symbolKind) {
11914
- const lines = content.split("\n");
11915
- const patterns = [];
11916
- const escaped = symbolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11917
- if (symbolKind === "function" || symbolKind === "method") {
11918
- patterns.push(
11919
- new RegExp(`(?:export\\s+)?(?:async\\s+)?function\\s+${escaped}\\s*[(<]`),
11920
- new RegExp(`(?:public|private|protected)\\s+(?:async\\s+)?(?:static\\s+)?${escaped}\\s*\\(`),
11921
- new RegExp(`(?:export\\s+)?(?:const|let|var)\\s+${escaped}\\s*=\\s*(?:async\\s+)?(?:\\([^)]*\\)|\\w+)\\s*=>`),
11922
- new RegExp(`\\b${escaped}\\s*:\\s*(?:async\\s+)?function`),
11923
- new RegExp(`\\bdef\\s+${escaped}\\s*\\(`),
11924
- new RegExp(`\\bfunc\\s+${escaped}\\s*\\(`)
11925
- );
11926
- } else if (symbolKind === "class") {
11927
- patterns.push(
11928
- new RegExp(`(?:export\\s+)?(?:abstract\\s+)?class\\s+${escaped}\\b`),
11929
- new RegExp(`\\bclass\\s+${escaped}\\b`)
11930
- );
11931
- } else {
11932
- patterns.push(new RegExp(`\\b${escaped}\\b`));
11933
- }
11934
- let startLine = -1;
11935
- for (let i = 0; i < lines.length; i++) {
11936
- if (patterns.some((p4) => p4.test(lines[i]))) {
11937
- startLine = i;
11938
- break;
11939
- }
11940
- }
11941
- if (startLine === -1) return null;
11942
- const cleanLines = stripStringsForBraces(content).split("\n");
11943
- let depth = 0;
11944
- let foundOpenBrace = false;
11945
- let endLine = startLine;
11946
- for (let i = startLine; i < cleanLines.length; i++) {
11947
- for (const ch of cleanLines[i]) {
11948
- if (ch === "{") {
11949
- depth++;
11950
- foundOpenBrace = true;
11951
- } else if (ch === "}") {
11952
- depth--;
11953
- }
11954
- }
11955
- endLine = i;
11956
- if (foundOpenBrace && depth <= 0) break;
11957
- if (!foundOpenBrace && i > startLine && lines[i].trim() !== "") {
11958
- const startIndent = lines[startLine].match(/^\s*/)?.[0].length ?? 0;
11959
- const currentIndent = lines[i].match(/^\s*/)?.[0].length ?? 0;
11960
- if (currentIndent <= startIndent && i > startLine + 1) {
11961
- endLine = i - 1;
11962
- break;
11963
- }
11964
- }
11965
- }
11966
- const source = lines.slice(startLine, endLine + 1).join("\n");
11967
- const signature = lines[startLine].trim();
11968
- return { source, signature };
11969
- }
11970
- function getSymbolComplexityTrend(store, cwd, symbolId, options = {}) {
11971
- const { sinceDays, snapshots = 6 } = options;
11972
- if (!isGitRepo(cwd)) return null;
11973
- const sym = store.db.prepare(`
11974
- SELECT s.symbol_id, s.name, s.kind, s.fqn, s.signature,
11975
- s.cyclomatic, s.max_nesting, s.param_count,
11976
- s.line_start, s.line_end,
11977
- f.path
11978
- FROM symbols s
11979
- JOIN files f ON s.file_id = f.id
11980
- WHERE s.symbol_id = ?
11981
- `).get(symbolId);
11982
- if (!sym) return null;
11983
- const currentLines = sym.line_end && sym.line_start ? sym.line_end - sym.line_start + 1 : 0;
11984
- const currentSnapshot = {
11985
- date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
11986
- commit: "HEAD",
11987
- cyclomatic: sym.cyclomatic ?? 1,
11988
- max_nesting: sym.max_nesting ?? 0,
11989
- param_count: sym.param_count ?? 0,
11990
- lines: currentLines
11991
- };
11992
- const commits = sampleFileCommits(cwd, sym.path, sinceDays, snapshots);
11993
- const historical = [];
11994
- for (const { hash, date } of commits) {
11995
- const content = getFileAtCommit2(cwd, sym.path, hash);
11996
- if (!content) continue;
11997
- try {
11998
- const extracted = extractSymbolSource(content, sym.name, sym.kind);
11999
- if (!extracted) continue;
12000
- const cyclomatic = computeCyclomatic(extracted.source);
12001
- const maxNesting = computeMaxNesting(extracted.source);
12002
- const paramCount = computeParamCount(extracted.signature);
12003
- const lineCount = extracted.source.split("\n").length;
12004
- historical.push({
12005
- date,
12006
- commit: hash.slice(0, 8),
12007
- cyclomatic,
12008
- max_nesting: maxNesting,
12009
- param_count: paramCount,
12010
- lines: lineCount
12011
- });
12012
- } catch (e) {
12013
- logger.debug({ symbol: symbolId, commit: hash, error: e }, "Symbol complexity snapshot failed");
12014
- }
12015
- }
12016
- let trend = "stable";
12017
- let cyclomaticDelta = 0;
12018
- if (historical.length > 0) {
12019
- const oldest = historical[historical.length - 1];
12020
- cyclomaticDelta = currentSnapshot.cyclomatic - oldest.cyclomatic;
12021
- if (cyclomaticDelta >= 2) trend = "degrading";
12022
- else if (cyclomaticDelta <= -2) trend = "improving";
12023
- }
12024
- return {
12025
- symbol_id: sym.symbol_id,
12026
- name: sym.name,
12027
- file: sym.path,
12028
- current: currentSnapshot,
12029
- historical,
12030
- trend,
12031
- cyclomatic_delta: cyclomaticDelta
12032
- };
12033
- }
12034
-
12035
12134
  // src/tools/suggest.ts
12036
12135
  init_graph_analysis();
12037
12136
  function suggestQueries(store) {
@@ -14305,16 +14404,16 @@ function findShortestPath(store, startNodeId, endNodeId, maxDepth) {
14305
14404
  parent.set(nodeId, { from, edgeType: edge.edge_type_name });
14306
14405
  nextFrontier.push(nodeId);
14307
14406
  if (nodeId === endNodeId) {
14308
- const path82 = [endNodeId];
14407
+ const path83 = [endNodeId];
14309
14408
  const edgeTypes = [];
14310
14409
  let cur = endNodeId;
14311
14410
  while (cur !== startNodeId) {
14312
14411
  const p4 = parent.get(cur);
14313
- path82.unshift(p4.from);
14412
+ path83.unshift(p4.from);
14314
14413
  edgeTypes.unshift(p4.edgeType);
14315
14414
  cur = p4.from;
14316
14415
  }
14317
- return { path: path82, edgeTypes };
14416
+ return { path: path83, edgeTypes };
14318
14417
  }
14319
14418
  }
14320
14419
  }
@@ -14769,9 +14868,9 @@ var OtlpReceiver = class {
14769
14868
  if (this.options.port === 0) return;
14770
14869
  return new Promise((resolve, reject) => {
14771
14870
  this.server = createServer((req, res) => this.handleRequest(req, res));
14772
- this.server.on("error", (err31) => {
14773
- logger.error({ error: err31 }, "OTLP receiver error");
14774
- reject(err31);
14871
+ this.server.on("error", (err32) => {
14872
+ logger.error({ error: err32 }, "OTLP receiver error");
14873
+ reject(err32);
14775
14874
  });
14776
14875
  this.server.listen(this.options.port, this.options.host, () => {
14777
14876
  logger.info(
@@ -17828,6 +17927,20 @@ var hintGenerators = {
17828
17927
  hints.push({ tool: "scan_security", args: { rules: '["all"]' }, why: "Also check for security vulnerabilities" });
17829
17928
  return hints;
17830
17929
  },
17930
+ scan_code_smells(r) {
17931
+ const hints = [];
17932
+ const summary = dig(r, "summary");
17933
+ if (summary?.empty_function) {
17934
+ hints.push({ tool: "get_dead_code", why: "Empty functions may also be dead code \u2014 cross-check with dead code analysis" });
17935
+ }
17936
+ if (summary?.todo_comment) {
17937
+ hints.push({ tool: "get_tech_debt", why: "See overall tech debt score for modules with many TODOs" });
17938
+ }
17939
+ if (summary?.hardcoded_value) {
17940
+ hints.push({ tool: "scan_security", args: { rules: '["hardcoded_secrets"]' }, why: "Some hardcoded values may be security-sensitive" });
17941
+ }
17942
+ return hints;
17943
+ },
17831
17944
  get_page_rank(r) {
17832
17945
  const hints = [];
17833
17946
  const ranked = arr(r);
@@ -18723,9 +18836,321 @@ function detectAntipatterns(store, _projectRoot, opts = {}) {
18723
18836
  });
18724
18837
  }
18725
18838
 
18726
- // src/tools/sbom.ts
18727
- import { readFileSync as readFileSync2, existsSync } from "fs";
18839
+ // src/tools/code-smells.ts
18840
+ import { readFileSync as readFileSync2 } from "fs";
18728
18841
  import path29 from "path";
18842
+ var ALL_CATEGORIES2 = [
18843
+ "todo_comment",
18844
+ "empty_function",
18845
+ "hardcoded_value"
18846
+ ];
18847
+ var TODO_TAGS = [
18848
+ { tag: "FIXME", priority: "high" },
18849
+ { tag: "HACK", priority: "high" },
18850
+ { tag: "XXX", priority: "medium" },
18851
+ { tag: "TODO", priority: "medium" },
18852
+ { tag: "TEMP", priority: "medium" },
18853
+ { tag: "WORKAROUND", priority: "medium" },
18854
+ { tag: "BUG", priority: "high" },
18855
+ { tag: "REFACTOR", priority: "low" },
18856
+ { tag: "OPTIMIZE", priority: "low" },
18857
+ { tag: "NOTE", priority: "low" }
18858
+ ];
18859
+ var TODO_TAG_NAMES = TODO_TAGS.map((t) => t.tag).join("|");
18860
+ var TODO_REGEX = new RegExp(
18861
+ `(?://|#|/\\*|\\*|--|%|;|'|REM\\b)\\s*\\b(${TODO_TAG_NAMES})\\b[:\\s]?(.*)`,
18862
+ "i"
18863
+ );
18864
+ var TODO_TAG_PRIORITY = new Map(
18865
+ TODO_TAGS.map((t) => [t.tag, t.priority])
18866
+ );
18867
+ function detectTodoComments(lines, filePath) {
18868
+ const findings = [];
18869
+ for (let i = 0; i < lines.length; i++) {
18870
+ const line = lines[i];
18871
+ const m = TODO_REGEX.exec(line);
18872
+ if (!m) continue;
18873
+ const tag = m[1].toUpperCase();
18874
+ const message = (m[2] ?? "").trim();
18875
+ const priority = TODO_TAG_PRIORITY.get(tag) ?? "medium";
18876
+ findings.push({
18877
+ category: "todo_comment",
18878
+ priority,
18879
+ tag,
18880
+ file: filePath,
18881
+ line: i + 1,
18882
+ snippet: line.trim().slice(0, 200),
18883
+ description: message || `${tag} comment without description`
18884
+ });
18885
+ }
18886
+ return findings;
18887
+ }
18888
+ var STUB_BODY_PATTERNS = [
18889
+ // completely empty (just whitespace / braces)
18890
+ /^\s*$/,
18891
+ // Python pass
18892
+ /^\s*pass\s*$/,
18893
+ // bare return / return null / return undefined / return nil / return None
18894
+ /^\s*return(?:\s+(?:null|undefined|nil|None|false|0|''))?\s*;?\s*$/,
18895
+ // throw not-implemented
18896
+ /^\s*throw\s+new\s+(?:Error|NotImplementedError|UnsupportedOperationException)\s*\(\s*(['"`].*?['"`])?\s*\)\s*;?\s*$/,
18897
+ // Python raise
18898
+ /^\s*raise\s+(?:NotImplementedError|NotImplemented)\s*(?:\(.*\))?\s*$/,
18899
+ // Single-line TODO/FIXME comment only
18900
+ /^\s*(?:\/\/|#|--|%)\s*(?:TODO|FIXME|HACK|XXX)\b.*$/i,
18901
+ // Ellipsis (Python stub)
18902
+ /^\s*\.\.\.\s*$/
18903
+ ];
18904
+ var CALLABLE_KINDS = /* @__PURE__ */ new Set([
18905
+ "function",
18906
+ "method",
18907
+ "arrow_function",
18908
+ "closure",
18909
+ "constructor",
18910
+ "static_method",
18911
+ "class_method",
18912
+ "generator",
18913
+ "async_function",
18914
+ "async_method"
18915
+ ]);
18916
+ function detectEmptyFunctions(content, lines, symbols, filePath, language) {
18917
+ const findings = [];
18918
+ for (const sym of symbols) {
18919
+ if (!CALLABLE_KINDS.has(sym.kind)) continue;
18920
+ if (sym.line_start == null || sym.line_end == null) continue;
18921
+ const rawBody = content.slice(sym.byte_start, sym.byte_end);
18922
+ let body;
18923
+ if (language === "python") {
18924
+ const bodyLines = rawBody.split("\n");
18925
+ let startIdx = 0;
18926
+ for (let i = 0; i < bodyLines.length; i++) {
18927
+ if (/:\s*(?:#.*)?$/.test(bodyLines[i].trimEnd())) {
18928
+ startIdx = i + 1;
18929
+ break;
18930
+ }
18931
+ }
18932
+ body = bodyLines.slice(startIdx).join("\n");
18933
+ } else {
18934
+ const openBrace = rawBody.indexOf("{");
18935
+ const closeBrace = rawBody.lastIndexOf("}");
18936
+ if (openBrace === -1 || closeBrace === -1 || closeBrace <= openBrace) {
18937
+ continue;
18938
+ }
18939
+ body = rawBody.slice(openBrace + 1, closeBrace);
18940
+ }
18941
+ const strippedLines = body.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).filter((l) => !/^(?:\/\/|#|--|\/\*|\*\/|\*)\s*$/.test(l));
18942
+ const joined = strippedLines.join("\n");
18943
+ const isEmpty = strippedLines.length === 0;
18944
+ const isStub = !isEmpty && strippedLines.length <= 2 && STUB_BODY_PATTERNS.some((p4) => p4.test(joined));
18945
+ if (isEmpty || isStub) {
18946
+ const description = isEmpty ? `Empty ${sym.kind} '${sym.name}' \u2014 no implementation` : `Stub ${sym.kind} '${sym.name}' \u2014 placeholder implementation`;
18947
+ findings.push({
18948
+ category: "empty_function",
18949
+ priority: isEmpty ? "medium" : "low",
18950
+ file: filePath,
18951
+ line: sym.line_start,
18952
+ snippet: (sym.signature ?? lines[sym.line_start - 1]?.trim() ?? sym.name).slice(0, 200),
18953
+ description,
18954
+ symbol: sym.name
18955
+ });
18956
+ }
18957
+ }
18958
+ return findings;
18959
+ }
18960
+ var HARDCODE_PATTERNS = [
18961
+ // Hardcoded IP addresses (not 127.0.0.1 or 0.0.0.0 which are often intentional)
18962
+ {
18963
+ name: "hardcoded_ip",
18964
+ regex: /(?<![.\d])(?!(?:127\.0\.0\.1|0\.0\.0\.0|255\.255\.255\.\d+)\b)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?![\d.])/g,
18965
+ priority: "medium",
18966
+ description: "Hardcoded IP address \u2014 use configuration or environment variable",
18967
+ falsePositives: [
18968
+ /version|semver|v\d/i,
18969
+ /(?:\/\/|#|--|\/\*)\s*(?:example|e\.g\.|i\.e\.|see|cf\.|docs)/i,
18970
+ /(?:test|spec|mock|fixture|seed|sample)/i,
18971
+ /0\.0\.0\.0|127\.0\.0\.1|localhost/
18972
+ ]
18973
+ },
18974
+ // Hardcoded port numbers in connection strings
18975
+ {
18976
+ name: "hardcoded_port",
18977
+ regex: /(?:port\s*[:=]\s*|:\s*)(\d{4,5})\b/g,
18978
+ priority: "low",
18979
+ description: "Hardcoded port number \u2014 use configuration or environment variable",
18980
+ falsePositives: [
18981
+ /(?:test|spec|mock|fixture)/i,
18982
+ /process\.env|os\.environ|getenv|ENV\[|config\./i,
18983
+ /(?:\/\/|#)\s*default/i,
18984
+ /\.env|\.ya?ml|\.toml|\.ini|\.cfg|Dockerfile|docker-compose/
18985
+ ]
18986
+ },
18987
+ // Hardcoded URLs (http/https) — likely should be configurable
18988
+ {
18989
+ name: "hardcoded_url",
18990
+ regex: /(?:['"`])(https?:\/\/(?!(?:localhost|127\.0\.0\.1|example\.com|schemas?\.))[\w.-]+\.\w+[^'"`\s]*?)(?:['"`])/g,
18991
+ priority: "medium",
18992
+ description: "Hardcoded URL \u2014 use configuration or environment variable",
18993
+ falsePositives: [
18994
+ /(?:test|spec|mock|fixture|__tests__)/i,
18995
+ /(?:\/\/|#)\s*(?:see|docs|ref|link|source|from|via|credit)/i,
18996
+ /schema\.org|json-schema|w3\.org|ietf\.org|creativecommons|spdx\.org/i,
18997
+ /swagger|openapi|license|readme|changelog/i,
18998
+ /(?:npmjs?|pypi|rubygems|crates|maven|nuget|packagist)\.(?:org|io|dev)/i,
18999
+ /github\.com|gitlab\.com|bitbucket\.org/i
19000
+ ]
19001
+ },
19002
+ // Magic numbers in comparisons, assignments, or returns
19003
+ {
19004
+ name: "magic_number",
19005
+ regex: /(?:===?\s*|!==?\s*|[<>]=?\s*|return\s+|=\s+)(-?\d{2,}(?:\.\d+)?)\b/g,
19006
+ priority: "low",
19007
+ description: "Magic number \u2014 extract to a named constant for readability",
19008
+ falsePositives: [
19009
+ /(?:test|spec|mock|fixture|__tests__)/i,
19010
+ // Common non-magic values
19011
+ /(?:===?\s*|!==?\s*|return\s+|=\s+)(?:0|1|2|10|100|1000|200|201|204|301|302|400|401|403|404|409|422|429|500|502|503)\b/,
19012
+ /(?:status|code|http|statusCode|response)\s*(?:===?|!==?)/i,
19013
+ /(?:length|size|count|index|offset|width|height|margin|padding)\s*(?:===?|!==?|[<>]=?)/i,
19014
+ /\.(?:status|code|length|size|indexOf|charCodeAt)\s*(?:===?|!==?)/i,
19015
+ /(?:Math\.|parseInt|parseFloat|Number\()/,
19016
+ /(?:timeout|delay|interval|duration|ttl|retry|retries|max_|min_|limit)/i
19017
+ ]
19018
+ },
19019
+ // Hardcoded credentials / secrets patterns (not already caught by security scanner)
19020
+ {
19021
+ name: "hardcoded_credential",
19022
+ regex: /(?:password|passwd|pwd|secret|api_?key|token|auth)\s*[:=]\s*['"`](?![\s'"`)]{0,2}$)[^'"`\n]{3,}['"`]/gi,
19023
+ priority: "high",
19024
+ description: "Hardcoded credential \u2014 use environment variable or secret manager",
19025
+ falsePositives: [
19026
+ /(?:test|spec|mock|fixture|__tests__|example|sample|dummy|placeholder)/i,
19027
+ /process\.env|os\.environ|getenv|ENV\[|config\./i,
19028
+ /TODO|FIXME|CHANGEME|REPLACE|PLACEHOLDER|YOUR_/i,
19029
+ /(?:schema|type|validation|interface|swagger|openapi)/i
19030
+ ]
19031
+ },
19032
+ // Hardcoded feature flags / toggles
19033
+ {
19034
+ name: "hardcoded_feature_flag",
19035
+ regex: /(?:feature|flag|toggle|experiment|beta|enable|disable)[\w_]*\s*[:=]\s*(?:true|false|1|0)\b/gi,
19036
+ priority: "low",
19037
+ description: "Hardcoded feature flag \u2014 use a feature flag service or configuration",
19038
+ falsePositives: [
19039
+ /(?:test|spec|mock|fixture|__tests__)/i,
19040
+ /(?:default|fallback|config|schema|type|interface)/i,
19041
+ /process\.env|os\.environ|getenv|ENV\[/i
19042
+ ]
19043
+ }
19044
+ ];
19045
+ function detectHardcodedValues(lines, filePath) {
19046
+ const findings = [];
19047
+ for (let i = 0; i < lines.length; i++) {
19048
+ const line = lines[i];
19049
+ for (const pattern of HARDCODE_PATTERNS) {
19050
+ const re = new RegExp(pattern.regex.source, pattern.regex.flags);
19051
+ re.lastIndex = 0;
19052
+ const m = re.exec(line);
19053
+ if (!m) continue;
19054
+ let isFP = false;
19055
+ for (const fp of pattern.falsePositives) {
19056
+ if (fp.test(line) || fp.test(filePath)) {
19057
+ isFP = true;
19058
+ break;
19059
+ }
19060
+ }
19061
+ if (isFP) continue;
19062
+ findings.push({
19063
+ category: "hardcoded_value",
19064
+ priority: pattern.priority,
19065
+ tag: pattern.name,
19066
+ file: filePath,
19067
+ line: i + 1,
19068
+ snippet: line.trim().slice(0, 200),
19069
+ description: pattern.description
19070
+ });
19071
+ }
19072
+ }
19073
+ return findings;
19074
+ }
19075
+ var PRIORITY_ORDER = { high: 0, medium: 1, low: 2 };
19076
+ function priorityRank(p4) {
19077
+ return PRIORITY_ORDER[p4];
19078
+ }
19079
+ var BATCH_SIZE2 = 100;
19080
+ var MAX_FILE_SIZE2 = 512 * 1024;
19081
+ function scanCodeSmells(store, projectRoot, opts = {}) {
19082
+ const categories = new Set(opts.category ?? ALL_CATEGORIES2);
19083
+ const thresholdRank = priorityRank(opts.priority_threshold ?? "low");
19084
+ const includeTests = opts.include_tests ?? false;
19085
+ const tagFilter = opts.tags ? new Set(opts.tags.map((t) => t.toUpperCase())) : null;
19086
+ const limit = opts.limit ?? 200;
19087
+ const scope = opts.scope?.replace(/\/+$/, "");
19088
+ const files = scope ? store.db.prepare("SELECT id, path, language FROM files WHERE path LIKE ? AND (status = 'ok' OR status IS NULL)").all(`${scope}%`) : store.db.prepare("SELECT id, path, language FROM files WHERE status = 'ok' OR status IS NULL").all();
19089
+ const allFindings = [];
19090
+ let scanned = 0;
19091
+ const needsEmptyFn = categories.has("empty_function");
19092
+ for (let i = 0; i < files.length; i += BATCH_SIZE2) {
19093
+ const batch = files.slice(i, i + BATCH_SIZE2);
19094
+ for (const file of batch) {
19095
+ if (!includeTests && /\.(?:test|spec)\.|__tests__|\/tests?\//i.test(file.path)) continue;
19096
+ const absPath = path29.resolve(projectRoot, file.path);
19097
+ let content;
19098
+ try {
19099
+ const buf = readFileSync2(absPath);
19100
+ if (buf.length > MAX_FILE_SIZE2) continue;
19101
+ content = buf.toString("utf-8");
19102
+ } catch {
19103
+ continue;
19104
+ }
19105
+ scanned++;
19106
+ const lines = content.split("\n");
19107
+ if (categories.has("todo_comment")) {
19108
+ const todos = detectTodoComments(lines, file.path);
19109
+ for (const f of todos) {
19110
+ if (priorityRank(f.priority) > thresholdRank) continue;
19111
+ if (tagFilter && f.tag && !tagFilter.has(f.tag)) continue;
19112
+ allFindings.push(f);
19113
+ }
19114
+ }
19115
+ if (needsEmptyFn) {
19116
+ const symbols = store.getSymbolsByFile(file.id);
19117
+ const empties = detectEmptyFunctions(content, lines, symbols, file.path, file.language ?? "");
19118
+ for (const f of empties) {
19119
+ if (priorityRank(f.priority) > thresholdRank) continue;
19120
+ allFindings.push(f);
19121
+ }
19122
+ }
19123
+ if (categories.has("hardcoded_value")) {
19124
+ const hardcoded = detectHardcodedValues(lines, file.path);
19125
+ for (const f of hardcoded) {
19126
+ if (priorityRank(f.priority) > thresholdRank) continue;
19127
+ allFindings.push(f);
19128
+ }
19129
+ }
19130
+ }
19131
+ }
19132
+ allFindings.sort(
19133
+ (a, b) => priorityRank(a.priority) - priorityRank(b.priority) || a.file.localeCompare(b.file) || a.line - b.line
19134
+ );
19135
+ const summary = {
19136
+ todo_comment: 0,
19137
+ empty_function: 0,
19138
+ hardcoded_value: 0
19139
+ };
19140
+ for (const f of allFindings) {
19141
+ summary[f.category]++;
19142
+ }
19143
+ return ok({
19144
+ files_scanned: scanned,
19145
+ findings: allFindings.slice(0, limit),
19146
+ summary,
19147
+ total: allFindings.length
19148
+ });
19149
+ }
19150
+
19151
+ // src/tools/sbom.ts
19152
+ import { readFileSync as readFileSync3, existsSync } from "fs";
19153
+ import path30 from "path";
18729
19154
  var COPYLEFT_LICENSES = /* @__PURE__ */ new Set([
18730
19155
  "GPL-2.0",
18731
19156
  "GPL-2.0-only",
@@ -18760,21 +19185,21 @@ function checkLicenseWarning(name, version, license) {
18760
19185
  }
18761
19186
  function readJson(filePath) {
18762
19187
  try {
18763
- return JSON.parse(readFileSync2(filePath, "utf-8"));
19188
+ return JSON.parse(readFileSync3(filePath, "utf-8"));
18764
19189
  } catch {
18765
19190
  return null;
18766
19191
  }
18767
19192
  }
18768
19193
  function readLines2(filePath) {
18769
19194
  try {
18770
- return readFileSync2(filePath, "utf-8").split("\n");
19195
+ return readFileSync3(filePath, "utf-8").split("\n");
18771
19196
  } catch {
18772
19197
  return [];
18773
19198
  }
18774
19199
  }
18775
19200
  function parseNpm(root, includeDev, includeTransitive) {
18776
- const pkgPath = path29.join(root, "package.json");
18777
- const lockPath = path29.join(root, "package-lock.json");
19201
+ const pkgPath = path30.join(root, "package.json");
19202
+ const lockPath = path30.join(root, "package-lock.json");
18778
19203
  const pkg = readJson(pkgPath);
18779
19204
  if (!pkg) return [];
18780
19205
  const deps = pkg.dependencies ?? {};
@@ -18814,8 +19239,8 @@ function parseNpm(root, includeDev, includeTransitive) {
18814
19239
  return components;
18815
19240
  }
18816
19241
  function parseComposer(root, includeDev, includeTransitive) {
18817
- const lockPath = path29.join(root, "composer.lock");
18818
- const manifestPath = path29.join(root, "composer.json");
19242
+ const lockPath = path30.join(root, "composer.lock");
19243
+ const manifestPath = path30.join(root, "composer.json");
18819
19244
  const manifest = readJson(manifestPath);
18820
19245
  const directNames = /* @__PURE__ */ new Set();
18821
19246
  if (manifest) {
@@ -18858,7 +19283,7 @@ function parseComposer(root, includeDev, includeTransitive) {
18858
19283
  }
18859
19284
  function parsePip(root, includeDev, includeTransitive) {
18860
19285
  const components = [];
18861
- const reqPath = path29.join(root, "requirements.txt");
19286
+ const reqPath = path30.join(root, "requirements.txt");
18862
19287
  if (existsSync(reqPath)) {
18863
19288
  for (const line of readLines2(reqPath)) {
18864
19289
  const trimmed = line.trim();
@@ -18874,9 +19299,9 @@ function parsePip(root, includeDev, includeTransitive) {
18874
19299
  }
18875
19300
  }
18876
19301
  }
18877
- const poetryLock = path29.join(root, "poetry.lock");
19302
+ const poetryLock = path30.join(root, "poetry.lock");
18878
19303
  if (existsSync(poetryLock) && includeTransitive) {
18879
- const content = readFileSync2(poetryLock, "utf-8");
19304
+ const content = readFileSync3(poetryLock, "utf-8");
18880
19305
  const directNames = new Set(components.map((c) => c.name.toLowerCase()));
18881
19306
  let currentName = "";
18882
19307
  let currentVersion = "";
@@ -18905,7 +19330,7 @@ function parsePip(root, includeDev, includeTransitive) {
18905
19330
  return components;
18906
19331
  }
18907
19332
  function parseGo(root, _includeDev, includeTransitive) {
18908
- const modPath = path29.join(root, "go.mod");
19333
+ const modPath = path30.join(root, "go.mod");
18909
19334
  if (!existsSync(modPath)) return [];
18910
19335
  const components = [];
18911
19336
  const directNames = /* @__PURE__ */ new Set();
@@ -18938,9 +19363,9 @@ function parseGo(root, _includeDev, includeTransitive) {
18938
19363
  return components;
18939
19364
  }
18940
19365
  function parseCargo(root, _includeDev, includeTransitive) {
18941
- const lockPath = path29.join(root, "Cargo.lock");
19366
+ const lockPath = path30.join(root, "Cargo.lock");
18942
19367
  if (!existsSync(lockPath)) {
18943
- const tomlPath = path29.join(root, "Cargo.toml");
19368
+ const tomlPath = path30.join(root, "Cargo.toml");
18944
19369
  if (!existsSync(tomlPath)) return [];
18945
19370
  const components2 = [];
18946
19371
  let inDeps = false;
@@ -19005,7 +19430,7 @@ function parseCargo(root, _includeDev, includeTransitive) {
19005
19430
  });
19006
19431
  }
19007
19432
  if (!includeTransitive) {
19008
- const tomlPath = path29.join(root, "Cargo.toml");
19433
+ const tomlPath = path30.join(root, "Cargo.toml");
19009
19434
  if (existsSync(tomlPath)) {
19010
19435
  const directNames = /* @__PURE__ */ new Set();
19011
19436
  let inDeps = false;
@@ -19030,7 +19455,7 @@ function parseCargo(root, _includeDev, includeTransitive) {
19030
19455
  return components;
19031
19456
  }
19032
19457
  function parseBundler(root, _includeDev, includeTransitive) {
19033
- const lockPath = path29.join(root, "Gemfile.lock");
19458
+ const lockPath = path30.join(root, "Gemfile.lock");
19034
19459
  if (!existsSync(lockPath)) return [];
19035
19460
  const components = [];
19036
19461
  let inSpecs = false;
@@ -19069,9 +19494,9 @@ function parseBundler(root, _includeDev, includeTransitive) {
19069
19494
  return components;
19070
19495
  }
19071
19496
  function parseMaven(root, _includeDev, _includeTransitive) {
19072
- const pomPath = path29.join(root, "pom.xml");
19497
+ const pomPath = path30.join(root, "pom.xml");
19073
19498
  if (!existsSync(pomPath)) return [];
19074
- const content = readFileSync2(pomPath, "utf-8");
19499
+ const content = readFileSync3(pomPath, "utf-8");
19075
19500
  const components = [];
19076
19501
  const depRegex = /<dependency>\s*<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>\s*(?:<version>([^<]+)<\/version>)?/gs;
19077
19502
  let match;
@@ -19376,8 +19801,8 @@ function getArtifacts(store, opts) {
19376
19801
 
19377
19802
  // src/tools/zero-index.ts
19378
19803
  import { execFileSync as execFileSync6 } from "child_process";
19379
- import { readFileSync as readFileSync3, readdirSync, statSync } from "fs";
19380
- import path30 from "path";
19804
+ import { readFileSync as readFileSync4, readdirSync, statSync } from "fs";
19805
+ import path31 from "path";
19381
19806
  var RG_ARGS_BASE = [
19382
19807
  "--json",
19383
19808
  "--max-count=200",
@@ -19408,7 +19833,7 @@ function searchWithRipgrep(projectRoot, query, opts) {
19408
19833
  const parsed = JSON.parse(line);
19409
19834
  if (parsed.type !== "match") continue;
19410
19835
  const data = parsed.data;
19411
- const relPath = path30.relative(projectRoot, data.path.text);
19836
+ const relPath = path31.relative(projectRoot, data.path.text);
19412
19837
  matches.push({
19413
19838
  file: relPath,
19414
19839
  line: data.line_number,
@@ -19444,7 +19869,7 @@ function manualSearch(projectRoot, query, opts) {
19444
19869
  for (const entry of entries) {
19445
19870
  if (matches.length >= limit) return;
19446
19871
  if (skipDirs.has(entry)) continue;
19447
- const full = path30.join(dir, entry);
19872
+ const full = path31.join(dir, entry);
19448
19873
  let stat;
19449
19874
  try {
19450
19875
  stat = statSync(full);
@@ -19455,14 +19880,14 @@ function manualSearch(projectRoot, query, opts) {
19455
19880
  walk2(full);
19456
19881
  } else if (stat.isFile() && stat.size < 512 * 1024) {
19457
19882
  try {
19458
- const content = readFileSync3(full, "utf-8");
19883
+ const content = readFileSync4(full, "utf-8");
19459
19884
  const lines = content.split("\n");
19460
19885
  for (let i = 0; i < lines.length; i++) {
19461
19886
  regex.lastIndex = 0;
19462
19887
  const m = regex.exec(lines[i]);
19463
19888
  if (m) {
19464
19889
  matches.push({
19465
- file: path30.relative(projectRoot, full),
19890
+ file: path31.relative(projectRoot, full),
19466
19891
  line: i + 1,
19467
19892
  column: m.index + 1,
19468
19893
  text: lines[i].trimEnd()
@@ -19529,7 +19954,7 @@ SYMBOL_PATTERNS["tsx"] = SYMBOL_PATTERNS["typescript"];
19529
19954
  SYMBOL_PATTERNS["jsx"] = SYMBOL_PATTERNS["javascript"];
19530
19955
  SYMBOL_PATTERNS["kotlin"] = SYMBOL_PATTERNS["java"];
19531
19956
  function detectLanguage2(filePath) {
19532
- const ext = path30.extname(filePath).toLowerCase();
19957
+ const ext = path31.extname(filePath).toLowerCase();
19533
19958
  const map = {
19534
19959
  ".ts": "typescript",
19535
19960
  ".tsx": "typescript",
@@ -19580,8 +20005,8 @@ function fallbackSearch(projectRoot, query, opts = {}) {
19580
20005
  };
19581
20006
  }
19582
20007
  function fallbackOutline(projectRoot, filePath) {
19583
- const absPath = path30.resolve(projectRoot, filePath);
19584
- const content = readFileSync3(absPath, "utf-8");
20008
+ const absPath = path31.resolve(projectRoot, filePath);
20009
+ const content = readFileSync4(absPath, "utf-8");
19585
20010
  const language = detectLanguage2(filePath);
19586
20011
  const symbols = extractSymbolsFromContent(content, language);
19587
20012
  return {
@@ -20062,8 +20487,8 @@ function compareBranches(store, rootPath, opts) {
20062
20487
 
20063
20488
  // src/savings.ts
20064
20489
  import fs21 from "fs";
20065
- import path31 from "path";
20066
- var SAVINGS_PATH = path31.join(TRACE_MCP_HOME, "savings.json");
20490
+ import path32 from "path";
20491
+ var SAVINGS_PATH = path32.join(TRACE_MCP_HOME, "savings.json");
20067
20492
  var RAW_COST_ESTIMATES = {
20068
20493
  get_symbol: 800,
20069
20494
  search: 600,
@@ -20195,7 +20620,7 @@ function savePersistentSavings(data) {
20195
20620
 
20196
20621
  // src/tools/audit-config.ts
20197
20622
  import fs22 from "fs";
20198
- import path32 from "path";
20623
+ import path33 from "path";
20199
20624
  var CONFIG_PATTERNS = [
20200
20625
  "CLAUDE.md",
20201
20626
  ".claude/CLAUDE.md",
@@ -20232,7 +20657,7 @@ function auditConfig(store, projectRoot, options = {}) {
20232
20657
  const pathMatches = line.match(/(?:src|lib|app|routes|tests?|components?|pages?)\/[\w/.-]+\.\w+/g);
20233
20658
  if (pathMatches) {
20234
20659
  for (const ref of pathMatches) {
20235
- const refPath = path32.join(projectRoot, ref);
20660
+ const refPath = path33.join(projectRoot, ref);
20236
20661
  if (!fs22.existsSync(refPath) && !store.getFile(ref)) {
20237
20662
  issues.push({
20238
20663
  file,
@@ -20345,7 +20770,7 @@ function checkSymbol(store, name, file, line, fixSuggestions, issues) {
20345
20770
  function findConfigFiles(projectRoot) {
20346
20771
  const found = [];
20347
20772
  for (const pattern of CONFIG_PATTERNS) {
20348
- const absPath = path32.join(projectRoot, pattern);
20773
+ const absPath = path33.join(projectRoot, pattern);
20349
20774
  if (fs22.existsSync(absPath)) found.push(pattern);
20350
20775
  }
20351
20776
  for (const pattern of GLOBAL_CONFIG_PATTERNS) {
@@ -20356,8 +20781,8 @@ function findConfigFiles(projectRoot) {
20356
20781
  }
20357
20782
  function resolveConfigPath(file, projectRoot) {
20358
20783
  if (file.startsWith("~")) return file.replace("~", process.env.HOME ?? "");
20359
- if (path32.isAbsolute(file)) return file;
20360
- return path32.join(projectRoot, file);
20784
+ if (path33.isAbsolute(file)) return file;
20785
+ return path33.join(projectRoot, file);
20361
20786
  }
20362
20787
  function isGlobalConfig(file) {
20363
20788
  return file.startsWith("~") || file.includes(".claude/CLAUDE.md");
@@ -20946,7 +21371,7 @@ Review this pre-merge checklist. Flag any risks and recommend whether it's safe
20946
21371
  // src/tools/pack-context.ts
20947
21372
  init_graph_analysis();
20948
21373
  import fs23 from "fs";
20949
- import path33 from "path";
21374
+ import path34 from "path";
20950
21375
  function estimateTokens2(text) {
20951
21376
  return Math.ceil(text.length / 4);
20952
21377
  }
@@ -21023,7 +21448,7 @@ ${outlineLines.join("\n")}
21023
21448
  const budgetForSource = Math.floor((maxTokens - tokenCount) * 0.9);
21024
21449
  let sourceTokens = 0;
21025
21450
  for (const f of files) {
21026
- const absPath = path33.join(projectRoot, f.path);
21451
+ const absPath = path34.join(projectRoot, f.path);
21027
21452
  if (!fs23.existsSync(absPath)) continue;
21028
21453
  let content;
21029
21454
  try {
@@ -21184,7 +21609,7 @@ function buildFileTree(paths) {
21184
21609
  for (const p4 of sorted.slice(0, 200)) {
21185
21610
  const depth = p4.split("/").length - 1;
21186
21611
  const indent = " ".repeat(Math.min(depth, 6));
21187
- lines.push(`${indent}${path33.basename(p4)}`);
21612
+ lines.push(`${indent}${path34.basename(p4)}`);
21188
21613
  }
21189
21614
  if (sorted.length > 200) {
21190
21615
  lines.push(` ... and ${sorted.length - 200} more files`);
@@ -21457,7 +21882,7 @@ function escapeHtml(s) {
21457
21882
 
21458
21883
  // src/tools/package-deps.ts
21459
21884
  import fs24 from "fs";
21460
- import path34 from "path";
21885
+ import path35 from "path";
21461
21886
  function loadRegistry() {
21462
21887
  if (!fs24.existsSync(REGISTRY_PATH)) return {};
21463
21888
  try {
@@ -21469,7 +21894,7 @@ function loadRegistry() {
21469
21894
  }
21470
21895
  function readManifest(repoPath) {
21471
21896
  const result = { name: void 0, deps: /* @__PURE__ */ new Map(), publishes: [] };
21472
- const pkgJsonPath = path34.join(repoPath, "package.json");
21897
+ const pkgJsonPath = path35.join(repoPath, "package.json");
21473
21898
  if (fs24.existsSync(pkgJsonPath)) {
21474
21899
  try {
21475
21900
  const pkg = JSON.parse(fs24.readFileSync(pkgJsonPath, "utf-8"));
@@ -21484,7 +21909,7 @@ function readManifest(repoPath) {
21484
21909
  } catch {
21485
21910
  }
21486
21911
  }
21487
- const composerPath = path34.join(repoPath, "composer.json");
21912
+ const composerPath = path35.join(repoPath, "composer.json");
21488
21913
  if (fs24.existsSync(composerPath)) {
21489
21914
  try {
21490
21915
  const composer = JSON.parse(fs24.readFileSync(composerPath, "utf-8"));
@@ -21502,7 +21927,7 @@ function readManifest(repoPath) {
21502
21927
  } catch {
21503
21928
  }
21504
21929
  }
21505
- const pyprojectPath = path34.join(repoPath, "pyproject.toml");
21930
+ const pyprojectPath = path35.join(repoPath, "pyproject.toml");
21506
21931
  if (fs24.existsSync(pyprojectPath)) {
21507
21932
  try {
21508
21933
  const content = fs24.readFileSync(pyprojectPath, "utf-8");
@@ -21535,7 +21960,7 @@ function getPackageDeps(options) {
21535
21960
  repoManifests.set(repoPath, manifest);
21536
21961
  const allPublishes = [...entry.publishes ?? [], ...manifest.publishes];
21537
21962
  for (const p4 of new Set(allPublishes)) {
21538
- publishMap.set(p4, { repo: entry.name ?? path34.basename(repoPath), repoPath });
21963
+ publishMap.set(p4, { repo: entry.name ?? path35.basename(repoPath), repoPath });
21539
21964
  }
21540
21965
  }
21541
21966
  const results = [];
@@ -21545,7 +21970,7 @@ function getPackageDeps(options) {
21545
21970
  } else if (project) {
21546
21971
  for (const [repoPath, manifest] of repoManifests) {
21547
21972
  const entry = registry[repoPath];
21548
- if (entry?.name === project || path34.basename(repoPath) === project) {
21973
+ if (entry?.name === project || path35.basename(repoPath) === project) {
21549
21974
  for (const p4 of manifest.publishes) {
21550
21975
  targetPackages.add(p4);
21551
21976
  }
@@ -21564,7 +21989,7 @@ function getPackageDeps(options) {
21564
21989
  if (direction === "dependents" || direction === "both") {
21565
21990
  for (const [repoPath, manifest] of repoManifests) {
21566
21991
  const entry = registry[repoPath];
21567
- const repoName = entry?.name ?? path34.basename(repoPath);
21992
+ const repoName = entry?.name ?? path35.basename(repoPath);
21568
21993
  for (const targetPkg of targetPackages) {
21569
21994
  const dep = manifest.deps.get(targetPkg);
21570
21995
  if (dep) {
@@ -21583,7 +22008,7 @@ function getPackageDeps(options) {
21583
22008
  if (direction === "dependencies" || direction === "both") {
21584
22009
  for (const [repoPath, manifest] of repoManifests) {
21585
22010
  const entry = registry[repoPath];
21586
- const repoName = entry?.name ?? path34.basename(repoPath);
22011
+ const repoName = entry?.name ?? path35.basename(repoPath);
21587
22012
  const isTarget = manifest.publishes.some((p4) => targetPackages.has(p4));
21588
22013
  if (!isTarget && targetPackages.size > 0) continue;
21589
22014
  for (const [dep, info] of manifest.deps) {
@@ -21839,7 +22264,7 @@ function cfgToAscii(cfg) {
21839
22264
 
21840
22265
  // src/tools/control-flow.ts
21841
22266
  import fs25 from "fs";
21842
- import path35 from "path";
22267
+ import path36 from "path";
21843
22268
  function getControlFlow(store, projectRoot, options) {
21844
22269
  const { symbolId, fqn, format, simplify } = options;
21845
22270
  let symbol = null;
@@ -21862,7 +22287,7 @@ function getControlFlow(store, projectRoot, options) {
21862
22287
  if (!file) {
21863
22288
  return err(notFound(`file for symbol`));
21864
22289
  }
21865
- const absPath = path35.isAbsolute(file.path) ? file.path : path35.join(projectRoot, file.path);
22290
+ const absPath = path36.isAbsolute(file.path) ? file.path : path36.join(projectRoot, file.path);
21866
22291
  if (!fs25.existsSync(absPath)) {
21867
22292
  return err(validationError(`File not on disk: ${file.path}`));
21868
22293
  }
@@ -21948,19 +22373,39 @@ function createServer2(store, registry, config, rootPath) {
21948
22373
  instructions: [
21949
22374
  `trace-mcp is a framework-aware code intelligence server for this project. Detected frameworks: ${detectedFrameworks}.`,
21950
22375
  "",
21951
- "WHEN TO USE trace-mcp tools (instead of Read/Grep/Glob):",
21952
- "- Finding a function/class/method \u2192 `search` (understands symbol kinds, FQNs, language filters)",
21953
- "- Understanding a file before editing \u2192 `outline_file` (signatures only \u2014 cheaper than Read)",
22376
+ "IMPORTANT: For ANY code exploration task, ALWAYS use trace-mcp tools first. NEVER fall back to Read/Grep/Glob/Bash(ls,find) for navigating source code \u2014 trace-mcp gives semantic, structured results that are cheaper in tokens and more accurate.",
22377
+ "",
22378
+ "WHEN TO USE trace-mcp tools:",
22379
+ "",
22380
+ "Navigation & search:",
22381
+ "- Finding a function/class/method \u2192 `search` (understands symbol kinds, FQNs, language filters; use `implements`/`extends` to filter by interface)",
22382
+ "- Understanding a file before editing \u2192 `get_outline` (signatures only \u2014 cheaper than Read)",
21954
22383
  "- Reading one symbol's source \u2192 `get_symbol` (returns only the symbol, not the whole file)",
22384
+ "- Quick keyword context \u2192 `get_feature_context` (NL query \u2192 relevant symbols + source)",
22385
+ "- Starting work on a task \u2192 `get_task_context` (NL task \u2192 full execution context with tests)",
22386
+ "",
22387
+ "Relationships & impact:",
21955
22388
  "- What breaks if I change X \u2192 `get_change_impact` (reverse dependency graph)",
21956
22389
  "- Who calls this / what does it call \u2192 `get_call_graph` (bidirectional)",
21957
- "- All usages of a symbol \u2192 `trace_usages` (semantic: imports, calls, renders, dispatches)",
21958
- "- Starting work on a task \u2192 `get_task_context` (NL task \u2192 full execution context with tests, adapts to bugfix/feature/refactor)",
21959
- "- Quick keyword context \u2192 `get_feature_context` (NL query \u2192 relevant symbols + source)",
22390
+ "- All usages of a symbol \u2192 `find_usages` (semantic: imports, calls, renders, dispatches)",
21960
22391
  "- Tests for a symbol/file \u2192 `get_tests_for` (understands test-to-source mapping)",
22392
+ "",
22393
+ "Architecture & meta-analysis:",
22394
+ "- All implementations of an interface \u2192 `get_type_hierarchy` (walks extends/implements tree)",
22395
+ "- All classes implementing X \u2192 `search` with `implements` or `extends` filter",
22396
+ "- Project health / coverage gaps \u2192 `self_audit` (dead exports, untested code, hotspots)",
22397
+ "- Module dependency graph \u2192 `get_module_graph` (NestJS) or `get_import_graph`",
22398
+ "- Dead code / dead exports \u2192 `get_dead_code` / `get_dead_exports`",
22399
+ "- Circular dependencies \u2192 `get_circular_imports`",
22400
+ "- Coupling analysis \u2192 `get_coupling`",
22401
+ "",
22402
+ "Framework-specific:",
21961
22403
  "- HTTP request flow \u2192 `get_request_flow` (route \u2192 middleware \u2192 controller \u2192 service)",
21962
22404
  "- DB model details \u2192 `get_model_context` (relationships, schema, metadata)",
21963
22405
  "- Database schema \u2192 `get_schema` (from migrations/ORM definitions)",
22406
+ "- Component tree \u2192 `get_component_tree` (React/Vue/Angular)",
22407
+ "- State stores \u2192 `get_state_stores` (Zustand/Redux/Pinia)",
22408
+ "- Event graph \u2192 `get_event_graph` (event emitters/listeners)",
21964
22409
  "",
21965
22410
  "WHEN TO USE native tools (Read/Grep/Glob):",
21966
22411
  "- Non-code files (.md, .json, .yaml, .toml, config) \u2192 Read/Grep",
@@ -22865,6 +23310,40 @@ function createServer2(store, registry, config, rootPath) {
22865
23310
  return { content: [{ type: "text", text: jh("detect_antipatterns", result.value) }] };
22866
23311
  }
22867
23312
  );
23313
+ server.tool(
23314
+ "scan_code_smells",
23315
+ "Find deferred work and shortcuts: TODO/FIXME/HACK/XXX comments, empty functions & stubs, hardcoded values (IPs, URLs, credentials, magic numbers, feature flags). Surfaces technical debt that grep alone misses by combining comment scanning, symbol body analysis, and context-aware false-positive filtering.",
23316
+ {
23317
+ category: z4.array(z4.enum([
23318
+ "todo_comment",
23319
+ "empty_function",
23320
+ "hardcoded_value"
23321
+ ])).optional().describe("Categories to scan (default: all)"),
23322
+ scope: z4.string().max(512).optional().describe("Directory to scan (default: whole project)"),
23323
+ priority_threshold: z4.enum(["high", "medium", "low"]).optional().describe("Minimum priority to report (default: low)"),
23324
+ include_tests: z4.boolean().optional().describe("Include test files in scan (default: false)"),
23325
+ tags: z4.array(z4.string().max(64)).optional().describe('Filter TODO comments by tag (e.g. ["FIXME","HACK"]). Only applies to todo_comment category'),
23326
+ limit: z4.number().int().min(1).max(1e3).optional().describe("Max findings to return (default: 200)")
23327
+ },
23328
+ async ({ category, scope, priority_threshold, include_tests, tags, limit }) => {
23329
+ if (scope) {
23330
+ const blocked = guardPath(scope);
23331
+ if (blocked) return blocked;
23332
+ }
23333
+ const result = scanCodeSmells(store, projectRoot, {
23334
+ category,
23335
+ scope,
23336
+ priority_threshold,
23337
+ include_tests,
23338
+ tags,
23339
+ limit
23340
+ });
23341
+ if (result.isErr()) {
23342
+ return { content: [{ type: "text", text: j2(formatToolError(result.error)) }], isError: true };
23343
+ }
23344
+ return { content: [{ type: "text", text: jh("scan_code_smells", result.value) }] };
23345
+ }
23346
+ );
22868
23347
  server.tool(
22869
23348
  "generate_sbom",
22870
23349
  "Generate a Software Bill of Materials (SBOM) from package manifests and lockfiles. Supports npm, Composer, pip, Go, Cargo, Bundler, Maven. Outputs CycloneDX, SPDX, or plain JSON. Includes license compliance warnings for copyleft licenses.",
@@ -23663,28 +24142,6 @@ function createServer2(store, registry, config, rootPath) {
23663
24142
  };
23664
24143
  }
23665
24144
  );
23666
- server.tool(
23667
- "plan_batch_change",
23668
- "Analyze the impact of updating a dependency across the codebase. Shows affected files, import sites, and line references. Generates a PR description template with risk level, breaking changes checklist, and affected file list.",
23669
- {
23670
- package: z4.string().min(1).max(256).describe('Package name being updated (e.g. "@myorg/auth-lib", "lodash")'),
23671
- from_version: z4.string().max(64).optional().describe("Current version"),
23672
- to_version: z4.string().max(64).optional().describe("Target version"),
23673
- breaking_changes: z4.array(z4.string().max(500)).max(20).optional().describe('Known breaking changes (e.g. "login() now returns Promise<AuthResult>")')
23674
- },
23675
- async ({ package: pkg, from_version, to_version, breaking_changes }) => {
23676
- const result = planBatchChange(store, {
23677
- package: pkg,
23678
- fromVersion: from_version,
23679
- toVersion: to_version,
23680
- breakingChanges: breaking_changes
23681
- });
23682
- if (result.isErr()) {
23683
- return { content: [{ type: "text", text: j2(formatToolError(result.error)) }], isError: true };
23684
- }
23685
- return { content: [{ type: "text", text: j2(result.value) }] };
23686
- }
23687
- );
23688
24145
  server.resource(
23689
24146
  "project-map",
23690
24147
  "project://map",
@@ -23900,7 +24357,7 @@ function createServer2(store, registry, config, rootPath) {
23900
24357
 
23901
24358
  // src/indexer/plugins/language/php/index.ts
23902
24359
  import { createRequire as createRequire2 } from "module";
23903
- import { ok as ok9, err as err11 } from "neverthrow";
24360
+ import { ok as ok9, err as err12 } from "neverthrow";
23904
24361
 
23905
24362
  // src/indexer/plugins/language/php/helpers.ts
23906
24363
  function extractNamespace(rootNode) {
@@ -24216,7 +24673,7 @@ var PhpLanguagePlugin = class {
24216
24673
  });
24217
24674
  } catch (e) {
24218
24675
  const msg = e instanceof Error ? e.message : String(e);
24219
- return err11(parseError(filePath, `PHP parse failed: ${msg}`));
24676
+ return err12(parseError(filePath, `PHP parse failed: ${msg}`));
24220
24677
  }
24221
24678
  }
24222
24679
  walkTopLevel(root, filePath, namespace, symbols) {
@@ -24422,7 +24879,7 @@ var PhpLanguagePlugin = class {
24422
24879
 
24423
24880
  // src/indexer/plugins/language/typescript/index.ts
24424
24881
  import { createRequire as createRequire3 } from "module";
24425
- import { ok as ok10, err as err12 } from "neverthrow";
24882
+ import { ok as ok10, err as err13 } from "neverthrow";
24426
24883
 
24427
24884
  // src/indexer/plugins/language/typescript/helpers.ts
24428
24885
  function makeSymbolId2(relativePath, name, kind, parentName) {
@@ -24805,7 +25262,7 @@ var TypeScriptLanguagePlugin = class {
24805
25262
  });
24806
25263
  } catch (e) {
24807
25264
  const msg = e instanceof Error ? e.message : String(e);
24808
- return err12(parseError(filePath, `TypeScript parse failed: ${msg}`));
25265
+ return err13(parseError(filePath, `TypeScript parse failed: ${msg}`));
24809
25266
  }
24810
25267
  }
24811
25268
  walkTopLevel(root, filePath, symbols) {
@@ -25029,7 +25486,7 @@ var TypeScriptLanguagePlugin = class {
25029
25486
  // src/indexer/plugins/language/vue/index.ts
25030
25487
  import { createRequire as createRequire4 } from "module";
25031
25488
  import { parse as parseSFC } from "@vue/compiler-sfc";
25032
- import { ok as ok11, err as err13 } from "neverthrow";
25489
+ import { ok as ok11, err as err14 } from "neverthrow";
25033
25490
 
25034
25491
  // src/indexer/plugins/language/vue/helpers.ts
25035
25492
  var HTML_ELEMENTS = /* @__PURE__ */ new Set([
@@ -25425,7 +25882,7 @@ var VueLanguagePlugin = class {
25425
25882
  });
25426
25883
  } catch (e) {
25427
25884
  const msg = e instanceof Error ? e.message : String(e);
25428
- return err13(parseError(filePath, `Vue SFC parse failed: ${msg}`));
25885
+ return err14(parseError(filePath, `Vue SFC parse failed: ${msg}`));
25429
25886
  }
25430
25887
  }
25431
25888
  /**
@@ -25508,7 +25965,7 @@ var VueLanguagePlugin = class {
25508
25965
 
25509
25966
  // src/indexer/plugins/language/python/index.ts
25510
25967
  import { createRequire as createRequire5 } from "module";
25511
- import { ok as ok12, err as err14 } from "neverthrow";
25968
+ import { ok as ok12, err as err15 } from "neverthrow";
25512
25969
 
25513
25970
  // src/indexer/plugins/language/python/helpers.ts
25514
25971
  function collectNodeTypes3(node) {
@@ -25899,7 +26356,7 @@ var PythonLanguagePlugin = class {
25899
26356
  });
25900
26357
  } catch (e) {
25901
26358
  const msg = e instanceof Error ? e.message : String(e);
25902
- return err14(parseError(filePath, `Python parse failed: ${msg}`));
26359
+ return err15(parseError(filePath, `Python parse failed: ${msg}`));
25903
26360
  }
25904
26361
  }
25905
26362
  walkTopLevel(root, filePath, modulePath, symbols) {
@@ -26149,7 +26606,7 @@ var PythonLanguagePlugin = class {
26149
26606
 
26150
26607
  // src/indexer/plugins/language/java/index.ts
26151
26608
  import { createRequire as createRequire6 } from "module";
26152
- import { ok as ok13, err as err15 } from "neverthrow";
26609
+ import { ok as ok13, err as err16 } from "neverthrow";
26153
26610
 
26154
26611
  // src/indexer/plugins/language/java/version-features.ts
26155
26612
  var JAVA_SOURCE_PATTERNS = [
@@ -26496,7 +26953,7 @@ var JavaLanguagePlugin = class {
26496
26953
  });
26497
26954
  } catch (e) {
26498
26955
  const msg = e instanceof Error ? e.message : String(e);
26499
- return err15(parseError(filePath, `Java parse failed: ${msg}`));
26956
+ return err16(parseError(filePath, `Java parse failed: ${msg}`));
26500
26957
  }
26501
26958
  }
26502
26959
  walkTopLevel(root, filePath, packageName, symbols) {
@@ -26623,7 +27080,7 @@ var JavaLanguagePlugin = class {
26623
27080
  };
26624
27081
 
26625
27082
  // src/indexer/plugins/language/kotlin/index.ts
26626
- import { ok as ok14, err as err16 } from "neverthrow";
27083
+ import { ok as ok14, err as err17 } from "neverthrow";
26627
27084
 
26628
27085
  // src/indexer/plugins/language/kotlin/version-features.ts
26629
27086
  var KOTLIN_SOURCE_PATTERNS = [
@@ -26806,14 +27263,14 @@ var KotlinLanguagePlugin = class {
26806
27263
  });
26807
27264
  } catch (e) {
26808
27265
  const msg = e instanceof Error ? e.message : String(e);
26809
- return err16(parseError(filePath, `Kotlin parse failed: ${msg}`));
27266
+ return err17(parseError(filePath, `Kotlin parse failed: ${msg}`));
26810
27267
  }
26811
27268
  }
26812
27269
  };
26813
27270
 
26814
27271
  // src/indexer/plugins/language/ruby/index.ts
26815
27272
  import { createRequire as createRequire7 } from "module";
26816
- import { ok as ok15, err as err17 } from "neverthrow";
27273
+ import { ok as ok15, err as err18 } from "neverthrow";
26817
27274
 
26818
27275
  // src/indexer/plugins/language/ruby/version-features.ts
26819
27276
  var RUBY_SOURCE_PATTERNS = [
@@ -27109,7 +27566,7 @@ var RubyLanguagePlugin = class {
27109
27566
  });
27110
27567
  } catch (e) {
27111
27568
  const msg = e instanceof Error ? e.message : String(e);
27112
- return err17(parseError(filePath, `Ruby parse failed: ${msg}`));
27569
+ return err18(parseError(filePath, `Ruby parse failed: ${msg}`));
27113
27570
  }
27114
27571
  }
27115
27572
  walkNode(node, filePath, namespaceParts, symbols) {
@@ -27222,7 +27679,7 @@ var RubyLanguagePlugin = class {
27222
27679
 
27223
27680
  // src/indexer/plugins/language/go/index.ts
27224
27681
  import { createRequire as createRequire8 } from "module";
27225
- import { ok as ok16, err as err18 } from "neverthrow";
27682
+ import { ok as ok16, err as err19 } from "neverthrow";
27226
27683
 
27227
27684
  // src/indexer/plugins/language/go/version-features.ts
27228
27685
  var GO_SOURCE_PATTERNS = [
@@ -27457,7 +27914,7 @@ var GoLanguagePlugin = class {
27457
27914
  });
27458
27915
  } catch (e) {
27459
27916
  const msg = e instanceof Error ? e.message : String(e);
27460
- return err18(parseError(filePath, `Go parse failed: ${msg}`));
27917
+ return err19(parseError(filePath, `Go parse failed: ${msg}`));
27461
27918
  }
27462
27919
  }
27463
27920
  extractFunction(node, filePath, pkg, symbols) {
@@ -27964,7 +28421,7 @@ function lineAt2(source, offset) {
27964
28421
 
27965
28422
  // src/indexer/plugins/language/rust/index.ts
27966
28423
  import { createRequire as createRequire9 } from "module";
27967
- import { ok as ok19, err as err19 } from "neverthrow";
28424
+ import { ok as ok19, err as err20 } from "neverthrow";
27968
28425
 
27969
28426
  // src/indexer/plugins/language/rust/helpers.ts
27970
28427
  function makeSymbolId7(filePath, name, kind, parentName) {
@@ -28208,7 +28665,7 @@ var RustLanguagePlugin = class {
28208
28665
  });
28209
28666
  } catch (e) {
28210
28667
  const msg = e instanceof Error ? e.message : String(e);
28211
- return err19(parseError(filePath, `Rust parse failed: ${msg}`));
28668
+ return err20(parseError(filePath, `Rust parse failed: ${msg}`));
28212
28669
  }
28213
28670
  }
28214
28671
  walkTopLevel(root, filePath, symbols) {
@@ -28470,7 +28927,7 @@ var RustLanguagePlugin = class {
28470
28927
 
28471
28928
  // src/indexer/plugins/language/c/index.ts
28472
28929
  import { createRequire as createRequire10 } from "module";
28473
- import { ok as ok20, err as err20 } from "neverthrow";
28930
+ import { ok as ok20, err as err21 } from "neverthrow";
28474
28931
 
28475
28932
  // src/indexer/plugins/language/c/helpers.ts
28476
28933
  function makeSymbolId8(filePath, name, kind, parentName) {
@@ -28641,7 +29098,7 @@ var CLanguagePlugin = class {
28641
29098
  });
28642
29099
  } catch (e) {
28643
29100
  const msg = e instanceof Error ? e.message : String(e);
28644
- return err20(parseError(filePath, `C parse failed: ${msg}`));
29101
+ return err21(parseError(filePath, `C parse failed: ${msg}`));
28645
29102
  }
28646
29103
  }
28647
29104
  walkTopLevel(root, filePath, symbols) {
@@ -28888,7 +29345,7 @@ var CLanguagePlugin = class {
28888
29345
 
28889
29346
  // src/indexer/plugins/language/cpp/index.ts
28890
29347
  import { createRequire as createRequire11 } from "module";
28891
- import { ok as ok21, err as err21 } from "neverthrow";
29348
+ import { ok as ok21, err as err22 } from "neverthrow";
28892
29349
 
28893
29350
  // src/indexer/plugins/language/cpp/helpers.ts
28894
29351
  function makeSymbolId9(filePath, name, kind, parentName) {
@@ -29110,7 +29567,7 @@ var CppLanguagePlugin = class {
29110
29567
  });
29111
29568
  } catch (e) {
29112
29569
  const msg = e instanceof Error ? e.message : String(e);
29113
- return err21(parseError(filePath, `C++ parse failed: ${msg}`));
29570
+ return err22(parseError(filePath, `C++ parse failed: ${msg}`));
29114
29571
  }
29115
29572
  }
29116
29573
  /**
@@ -29507,7 +29964,7 @@ var CppLanguagePlugin = class {
29507
29964
 
29508
29965
  // src/indexer/plugins/language/csharp/index.ts
29509
29966
  import { createRequire as createRequire12 } from "module";
29510
- import { ok as ok22, err as err22 } from "neverthrow";
29967
+ import { ok as ok22, err as err23 } from "neverthrow";
29511
29968
 
29512
29969
  // src/indexer/plugins/language/csharp/helpers.ts
29513
29970
  function makeSymbolId10(relativePath, name, kind, parentName) {
@@ -29912,7 +30369,7 @@ var CSharpLanguagePlugin = class {
29912
30369
  });
29913
30370
  } catch (e) {
29914
30371
  const msg = e instanceof Error ? e.message : String(e);
29915
- return err22(parseError(filePath, `C# parse failed: ${msg}`));
30372
+ return err23(parseError(filePath, `C# parse failed: ${msg}`));
29916
30373
  }
29917
30374
  }
29918
30375
  /** Walk children of a node, dispatching to extraction methods by node type. */
@@ -30516,7 +30973,7 @@ var DartLanguagePlugin = class {
30516
30973
 
30517
30974
  // src/indexer/plugins/language/scala/index.ts
30518
30975
  import { createRequire as createRequire13 } from "module";
30519
- import { ok as ok25, err as err23 } from "neverthrow";
30976
+ import { ok as ok25, err as err24 } from "neverthrow";
30520
30977
 
30521
30978
  // src/indexer/plugins/language/scala/helpers.ts
30522
30979
  function makeSymbolId12(filePath, name, kind, parentName) {
@@ -30943,7 +31400,7 @@ var ScalaLanguagePlugin = class {
30943
31400
  });
30944
31401
  } catch (e) {
30945
31402
  const msg = e instanceof Error ? e.message : String(e);
30946
- return err23(parseError(filePath, `Scala parse failed: ${msg}`));
31403
+ return err24(parseError(filePath, `Scala parse failed: ${msg}`));
30947
31404
  }
30948
31405
  }
30949
31406
  walkTopLevel(node, filePath, fqnParts, symbols) {
@@ -32531,7 +32988,7 @@ var XmlLanguagePlugin = class {
32531
32988
 
32532
32989
  // src/indexer/plugins/integration/orm/prisma/index.ts
32533
32990
  import fs26 from "fs";
32534
- import path36 from "path";
32991
+ import path37 from "path";
32535
32992
  import { ok as ok28 } from "neverthrow";
32536
32993
  var PrismaLanguagePlugin = class {
32537
32994
  manifest = {
@@ -32565,8 +33022,8 @@ var PrismaPlugin = class {
32565
33022
  if ("@prisma/client" in deps || "prisma" in deps) return true;
32566
33023
  try {
32567
33024
  const candidates = [
32568
- path36.join(ctx.rootPath, "prisma", "schema.prisma"),
32569
- path36.join(ctx.rootPath, "schema.prisma")
33025
+ path37.join(ctx.rootPath, "prisma", "schema.prisma"),
33026
+ path37.join(ctx.rootPath, "schema.prisma")
32570
33027
  ];
32571
33028
  return candidates.some((p4) => fs26.existsSync(p4));
32572
33029
  } catch {
@@ -35498,7 +35955,7 @@ function createAllLanguagePlugins() {
35498
35955
 
35499
35956
  // src/indexer/plugins/integration/framework/laravel/index.ts
35500
35957
  import fs28 from "fs";
35501
- import path37 from "path";
35958
+ import path38 from "path";
35502
35959
  import { ok as ok34 } from "neverthrow";
35503
35960
 
35504
35961
  // src/indexer/plugins/integration/framework/laravel/routes.ts
@@ -37300,7 +37757,7 @@ var LaravelPlugin = class {
37300
37757
  deps = ctx.composerJson.require;
37301
37758
  } else {
37302
37759
  try {
37303
- const composerPath = path37.join(ctx.rootPath, "composer.json");
37760
+ const composerPath = path38.join(ctx.rootPath, "composer.json");
37304
37761
  const content = fs28.readFileSync(composerPath, "utf-8");
37305
37762
  const json = JSON.parse(content);
37306
37763
  deps = json.require;
@@ -37974,7 +38431,7 @@ var LaravelPlugin = class {
37974
38431
 
37975
38432
  // src/indexer/plugins/integration/framework/django/index.ts
37976
38433
  import fs29 from "fs";
37977
- import path38 from "path";
38434
+ import path39 from "path";
37978
38435
  import { ok as ok35 } from "neverthrow";
37979
38436
 
37980
38437
  // src/indexer/plugins/integration/framework/django/models.ts
@@ -38477,16 +38934,16 @@ var DjangoPlugin = class {
38477
38934
  dependencies: []
38478
38935
  };
38479
38936
  detect(ctx) {
38480
- if (ctx.configFiles.some((f) => path38.basename(f) === "manage.py")) {
38937
+ if (ctx.configFiles.some((f) => path39.basename(f) === "manage.py")) {
38481
38938
  return true;
38482
38939
  }
38483
38940
  try {
38484
- fs29.accessSync(path38.join(ctx.rootPath, "manage.py"), fs29.constants.F_OK);
38941
+ fs29.accessSync(path39.join(ctx.rootPath, "manage.py"), fs29.constants.F_OK);
38485
38942
  return true;
38486
38943
  } catch {
38487
38944
  }
38488
38945
  try {
38489
- const pyprojectPath = path38.join(ctx.rootPath, "pyproject.toml");
38946
+ const pyprojectPath = path39.join(ctx.rootPath, "pyproject.toml");
38490
38947
  const content = fs29.readFileSync(pyprojectPath, "utf-8");
38491
38948
  if (/django/i.test(content) && /dependencies|requires/i.test(content)) {
38492
38949
  return true;
@@ -38494,7 +38951,7 @@ var DjangoPlugin = class {
38494
38951
  } catch {
38495
38952
  }
38496
38953
  try {
38497
- const reqPath = path38.join(ctx.rootPath, "requirements.txt");
38954
+ const reqPath = path39.join(ctx.rootPath, "requirements.txt");
38498
38955
  const content = fs29.readFileSync(reqPath, "utf-8");
38499
38956
  if (/^django(?:==|>=|<=|~=|!=|\[|\s|$)/im.test(content)) {
38500
38957
  return true;
@@ -38502,7 +38959,7 @@ var DjangoPlugin = class {
38502
38959
  } catch {
38503
38960
  }
38504
38961
  try {
38505
- const setupPath = path38.join(ctx.rootPath, "setup.py");
38962
+ const setupPath = path39.join(ctx.rootPath, "setup.py");
38506
38963
  const content = fs29.readFileSync(setupPath, "utf-8");
38507
38964
  if (/['"]django['"]/i.test(content)) {
38508
38965
  return true;
@@ -38578,7 +39035,7 @@ var DjangoPlugin = class {
38578
39035
  let source;
38579
39036
  try {
38580
39037
  source = fs29.readFileSync(
38581
- path38.resolve(ctx.rootPath, file.path),
39038
+ path39.resolve(ctx.rootPath, file.path),
38582
39039
  "utf-8"
38583
39040
  );
38584
39041
  } catch {
@@ -39172,8 +39629,8 @@ var SpringPlugin = class {
39172
39629
  const re = new RegExp(`@${annotation}\\s*(?:\\(\\s*(?:value\\s*=\\s*)?["']([^"']*)["']\\s*\\))?`, "g");
39173
39630
  let m;
39174
39631
  while ((m = re.exec(source)) !== null) {
39175
- const path82 = m[1] ?? "";
39176
- const uri = normalizePath(classPrefix + "/" + path82);
39632
+ const path83 = m[1] ?? "";
39633
+ const uri = normalizePath(classPrefix + "/" + path83);
39177
39634
  result.routes.push({ method, uri, line: source.substring(0, m.index).split("\n").length });
39178
39635
  }
39179
39636
  }
@@ -39233,13 +39690,13 @@ var SpringPlugin = class {
39233
39690
  }
39234
39691
  }
39235
39692
  };
39236
- function normalizePath(path82) {
39237
- return "/" + path82.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
39693
+ function normalizePath(path83) {
39694
+ return "/" + path83.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
39238
39695
  }
39239
39696
 
39240
39697
  // src/indexer/plugins/integration/framework/express/index.ts
39241
39698
  import fs30 from "fs";
39242
- import path39 from "path";
39699
+ import path40 from "path";
39243
39700
  var ROUTE_RE = /(?:app|router)\s*\.\s*(get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g;
39244
39701
  var MIDDLEWARE_RE = /(?:app|router)\s*\.\s*use\s*\(\s*['"`]([^'"`]+)['"`]/g;
39245
39702
  var GLOBAL_MIDDLEWARE_RE = /(?:app|router)\s*\.\s*use\s*\(\s*([A-Za-z][\w.]*(?:\s*\(\s*[^)]*\))?)\s*[,)]/g;
@@ -39309,7 +39766,7 @@ var ExpressPlugin = class {
39309
39766
  if ("express" in deps) return true;
39310
39767
  }
39311
39768
  try {
39312
- const pkgPath = path39.join(ctx.rootPath, "package.json");
39769
+ const pkgPath = path40.join(ctx.rootPath, "package.json");
39313
39770
  const content = fs30.readFileSync(pkgPath, "utf-8");
39314
39771
  const pkg = JSON.parse(content);
39315
39772
  const deps = {
@@ -39371,8 +39828,8 @@ var ExpressPlugin = class {
39371
39828
  // src/indexer/plugins/integration/framework/fastapi/index.ts
39372
39829
  import { createRequire as createRequire14 } from "module";
39373
39830
  import fs31 from "fs";
39374
- import path40 from "path";
39375
- import { ok as ok38, err as err24 } from "neverthrow";
39831
+ import path41 from "path";
39832
+ import { ok as ok38, err as err25 } from "neverthrow";
39376
39833
  var require15 = createRequire14(import.meta.url);
39377
39834
  var Parser13 = require15("tree-sitter");
39378
39835
  var PythonGrammar2 = require15("tree-sitter-python");
@@ -39385,14 +39842,14 @@ function hasPythonDep(ctx, pkg) {
39385
39842
  }
39386
39843
  if (ctx.requirementsTxt?.includes(lowerPkg)) return true;
39387
39844
  try {
39388
- const pyprojectPath = path40.join(ctx.rootPath, "pyproject.toml");
39845
+ const pyprojectPath = path41.join(ctx.rootPath, "pyproject.toml");
39389
39846
  const content = fs31.readFileSync(pyprojectPath, "utf-8");
39390
39847
  const re = new RegExp(`["']${escapeRegExp(pkg)}[>=<\\[!~\\s"']`, "i");
39391
39848
  if (re.test(content)) return true;
39392
39849
  } catch {
39393
39850
  }
39394
39851
  try {
39395
- const reqPath = path40.join(ctx.rootPath, "requirements.txt");
39852
+ const reqPath = path41.join(ctx.rootPath, "requirements.txt");
39396
39853
  const content = fs31.readFileSync(reqPath, "utf-8");
39397
39854
  const re = new RegExp(`^${escapeRegExp(pkg)}\\b`, "im");
39398
39855
  if (re.test(content)) return true;
@@ -39445,7 +39902,7 @@ var FastAPIPlugin = class {
39445
39902
  this.extractRoutes(root, source, filePath, result);
39446
39903
  this.extractRouterMounts(root, source, filePath, result);
39447
39904
  } catch (e) {
39448
- return err24(parseError(filePath, `FastAPI parse error: ${e instanceof Error ? e.message : String(e)}`));
39905
+ return err25(parseError(filePath, `FastAPI parse error: ${e instanceof Error ? e.message : String(e)}`));
39449
39906
  }
39450
39907
  if (result.routes.length > 0 || result.edges.length > 0) {
39451
39908
  result.frameworkRole = "fastapi_routes";
@@ -39688,8 +40145,8 @@ var FastAPIPlugin = class {
39688
40145
  // src/indexer/plugins/integration/framework/flask/index.ts
39689
40146
  import { createRequire as createRequire15 } from "module";
39690
40147
  import fs32 from "fs";
39691
- import path41 from "path";
39692
- import { ok as ok39, err as err25 } from "neverthrow";
40148
+ import path42 from "path";
40149
+ import { ok as ok39, err as err26 } from "neverthrow";
39693
40150
  var require16 = createRequire15(import.meta.url);
39694
40151
  var Parser14 = require16("tree-sitter");
39695
40152
  var PythonGrammar3 = require16("tree-sitter-python");
@@ -39702,14 +40159,14 @@ function hasPythonDep2(ctx, pkg) {
39702
40159
  }
39703
40160
  if (ctx.requirementsTxt?.includes(lowerPkg)) return true;
39704
40161
  try {
39705
- const pyprojectPath = path41.join(ctx.rootPath, "pyproject.toml");
40162
+ const pyprojectPath = path42.join(ctx.rootPath, "pyproject.toml");
39706
40163
  const content = fs32.readFileSync(pyprojectPath, "utf-8");
39707
40164
  const re = new RegExp(`["']${escapeRegExp(pkg)}[>=<\\[!~\\s"']`, "i");
39708
40165
  if (re.test(content)) return true;
39709
40166
  } catch {
39710
40167
  }
39711
40168
  try {
39712
- const reqPath = path41.join(ctx.rootPath, "requirements.txt");
40169
+ const reqPath = path42.join(ctx.rootPath, "requirements.txt");
39713
40170
  const content = fs32.readFileSync(reqPath, "utf-8");
39714
40171
  const re = new RegExp(`^${escapeRegExp(pkg)}\\b`, "im");
39715
40172
  if (re.test(content)) return true;
@@ -39763,7 +40220,7 @@ var FlaskPlugin = class {
39763
40220
  this.extractBeforeRequestHooks(root, source, filePath, result);
39764
40221
  this.extractErrorHandlers(root, source, filePath, result);
39765
40222
  } catch (e) {
39766
- return err25(parseError(filePath, `Flask parse error: ${e instanceof Error ? e.message : String(e)}`));
40223
+ return err26(parseError(filePath, `Flask parse error: ${e instanceof Error ? e.message : String(e)}`));
39767
40224
  }
39768
40225
  if (result.routes.length > 0 || result.edges.length > 0) {
39769
40226
  result.frameworkRole = "flask_routes";
@@ -39987,7 +40444,7 @@ var FlaskPlugin = class {
39987
40444
 
39988
40445
  // src/indexer/plugins/integration/framework/hono/index.ts
39989
40446
  import fs33 from "fs";
39990
- import path42 from "path";
40447
+ import path43 from "path";
39991
40448
  var ROUTE_RE2 = /(?:app|router|hono)\s*\.\s*(get|post|put|delete|patch|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/g;
39992
40449
  var ON_RE = /(?:app|router|hono)\s*\.\s*on\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/g;
39993
40450
  var ROUTE_GROUP_RE = /(?:app|router|hono)\s*\.\s*route\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*([A-Za-z][\w]*)/g;
@@ -40057,7 +40514,7 @@ var HonoPlugin = class {
40057
40514
  if ("hono" in deps) return true;
40058
40515
  }
40059
40516
  try {
40060
- const pkgPath = path42.join(ctx.rootPath, "package.json");
40517
+ const pkgPath = path43.join(ctx.rootPath, "package.json");
40061
40518
  const content = fs33.readFileSync(pkgPath, "utf-8");
40062
40519
  const pkg = JSON.parse(content);
40063
40520
  const deps = {
@@ -40113,7 +40570,7 @@ var HonoPlugin = class {
40113
40570
 
40114
40571
  // src/indexer/plugins/integration/framework/fastify/index.ts
40115
40572
  import fs34 from "fs";
40116
- import path43 from "path";
40573
+ import path44 from "path";
40117
40574
  var SHORTHAND_ROUTE_RE = /(?:fastify|app|server|instance)\s*\.\s*(get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g;
40118
40575
  var ROUTE_OBJECT_RE = /\.route\s*\(\s*\{[^}]*?method\s*:\s*['"`]([^'"`]+)['"`][^}]*?url\s*:\s*['"`]([^'"`]+)['"`]/g;
40119
40576
  var HOOK_RE = /\.addHook\s*\(\s*['"`]([^'"`]+)['"`]/g;
@@ -40172,7 +40629,7 @@ var FastifyPlugin = class {
40172
40629
  if ("fastify" in deps) return true;
40173
40630
  }
40174
40631
  try {
40175
- const pkgPath = path43.join(ctx.rootPath, "package.json");
40632
+ const pkgPath = path44.join(ctx.rootPath, "package.json");
40176
40633
  const content = fs34.readFileSync(pkgPath, "utf-8");
40177
40634
  const pkg = JSON.parse(content);
40178
40635
  const deps = {
@@ -40227,7 +40684,7 @@ var FastifyPlugin = class {
40227
40684
 
40228
40685
  // src/indexer/plugins/integration/framework/nuxt/index.ts
40229
40686
  import fs35 from "fs";
40230
- import path44 from "path";
40687
+ import path45 from "path";
40231
40688
  function filePathToRoute(filePath, srcDir = ".") {
40232
40689
  const pagesPrefix = srcDir === "." ? "pages/" : `${srcDir}/pages/`;
40233
40690
  let route = filePath.replace(new RegExp(`^${pagesPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").replace(/\.vue$/, "");
@@ -40294,7 +40751,7 @@ var NuxtPlugin = class {
40294
40751
  }
40295
40752
  }
40296
40753
  try {
40297
- const configPath = path44.join(ctx.rootPath, "nuxt.config.ts");
40754
+ const configPath = path45.join(ctx.rootPath, "nuxt.config.ts");
40298
40755
  const configContent = fs35.readFileSync(configPath, "utf-8");
40299
40756
  if (/compatibilityVersion\s*:\s*4/.test(configContent)) {
40300
40757
  return true;
@@ -40302,7 +40759,7 @@ var NuxtPlugin = class {
40302
40759
  } catch {
40303
40760
  }
40304
40761
  try {
40305
- const appPagesDir = path44.join(ctx.rootPath, "app", "pages");
40762
+ const appPagesDir = path45.join(ctx.rootPath, "app", "pages");
40306
40763
  fs35.accessSync(appPagesDir);
40307
40764
  return true;
40308
40765
  } catch {
@@ -40326,7 +40783,7 @@ var NuxtPlugin = class {
40326
40783
  }
40327
40784
  }
40328
40785
  try {
40329
- const configPath = path44.join(ctx.rootPath, "nuxt.config.ts");
40786
+ const configPath = path45.join(ctx.rootPath, "nuxt.config.ts");
40330
40787
  fs35.accessSync(configPath);
40331
40788
  this.nuxt4 = this.isNuxt4(ctx);
40332
40789
  this.srcDir = this.nuxt4 ? "app" : ".";
@@ -40334,7 +40791,7 @@ var NuxtPlugin = class {
40334
40791
  } catch {
40335
40792
  }
40336
40793
  try {
40337
- const pkgPath = path44.join(ctx.rootPath, "package.json");
40794
+ const pkgPath = path45.join(ctx.rootPath, "package.json");
40338
40795
  const content = fs35.readFileSync(pkgPath, "utf-8");
40339
40796
  const pkg = JSON.parse(content);
40340
40797
  const deps = {
@@ -40436,7 +40893,7 @@ var NuxtPlugin = class {
40436
40893
  for (const file of vueFiles) {
40437
40894
  let source;
40438
40895
  try {
40439
- source = fs35.readFileSync(path44.resolve(ctx.rootPath, file.path), "utf-8");
40896
+ source = fs35.readFileSync(path45.resolve(ctx.rootPath, file.path), "utf-8");
40440
40897
  } catch {
40441
40898
  continue;
40442
40899
  }
@@ -40474,7 +40931,7 @@ var NuxtPlugin = class {
40474
40931
 
40475
40932
  // src/indexer/plugins/integration/framework/nextjs/index.ts
40476
40933
  import fs36 from "fs";
40477
- import path45 from "path";
40934
+ import path46 from "path";
40478
40935
  var PAGE_EXTENSIONS = /\.(tsx|ts|jsx|js)$/;
40479
40936
  var APP_ROUTER_FILES = ["page", "layout", "loading", "error", "not-found", "route", "template", "default"];
40480
40937
  var API_ROUTE_EXPORTS_RE = /export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/g;
@@ -40513,7 +40970,7 @@ function pagesRouterPathToRoute(filePath) {
40513
40970
  return "/" + routeSegments.join("/");
40514
40971
  }
40515
40972
  function getAppRouterFileType(filePath) {
40516
- const basename = path45.basename(filePath).replace(PAGE_EXTENSIONS, "");
40973
+ const basename = path46.basename(filePath).replace(PAGE_EXTENSIONS, "");
40517
40974
  if (APP_ROUTER_FILES.includes(basename)) {
40518
40975
  return basename;
40519
40976
  }
@@ -40578,7 +41035,7 @@ var NextJSPlugin = class {
40578
41035
  if ("next" in deps) return true;
40579
41036
  }
40580
41037
  try {
40581
- const pkgPath = path45.join(ctx.rootPath, "package.json");
41038
+ const pkgPath = path46.join(ctx.rootPath, "package.json");
40582
41039
  const content = fs36.readFileSync(pkgPath, "utf-8");
40583
41040
  const pkg = JSON.parse(content);
40584
41041
  const deps = {
@@ -40685,15 +41142,15 @@ var NextJSPlugin = class {
40685
41142
  const edges = [];
40686
41143
  const allFiles = ctx.getAllFiles();
40687
41144
  const layouts = allFiles.filter((f) => {
40688
- const basename = path45.basename(f.path).replace(PAGE_EXTENSIONS, "");
41145
+ const basename = path46.basename(f.path).replace(PAGE_EXTENSIONS, "");
40689
41146
  return f.path.startsWith("app/") && basename === "layout";
40690
41147
  });
40691
41148
  const pages = allFiles.filter((f) => {
40692
- const basename = path45.basename(f.path).replace(PAGE_EXTENSIONS, "");
41149
+ const basename = path46.basename(f.path).replace(PAGE_EXTENSIONS, "");
40693
41150
  return f.path.startsWith("app/") && basename === "page";
40694
41151
  });
40695
41152
  for (const layout of layouts) {
40696
- const layoutDir = path45.dirname(layout.path);
41153
+ const layoutDir = path46.dirname(layout.path);
40697
41154
  const layoutSymbols = ctx.getSymbolsByFile(layout.id);
40698
41155
  const layoutSym = layoutSymbols.find((s) => s.kind === "function" || s.kind === "class");
40699
41156
  if (!layoutSym) continue;
@@ -40723,7 +41180,7 @@ var NextJSPlugin = class {
40723
41180
  if (slotIdx < 1) continue;
40724
41181
  const parentDir = segments.slice(0, slotIdx).join("/");
40725
41182
  const parentLayout = allFiles.find(
40726
- (f) => f.path.startsWith(parentDir + "/") && !f.path.includes("@") && /layout\.(tsx|ts|jsx|js)$/.test(path45.basename(f.path))
41183
+ (f) => f.path.startsWith(parentDir + "/") && !f.path.includes("@") && /layout\.(tsx|ts|jsx|js)$/.test(path46.basename(f.path))
40727
41184
  );
40728
41185
  if (parentLayout) {
40729
41186
  const layoutSymbols = ctx.getSymbolsByFile(parentLayout.id);
@@ -40775,11 +41232,11 @@ var NextJSPlugin = class {
40775
41232
  }
40776
41233
  }
40777
41234
  const templates = allFiles.filter((f) => {
40778
- const basename = path45.basename(f.path).replace(PAGE_EXTENSIONS, "");
41235
+ const basename = path46.basename(f.path).replace(PAGE_EXTENSIONS, "");
40779
41236
  return f.path.startsWith("app/") && basename === "template";
40780
41237
  });
40781
41238
  for (const template of templates) {
40782
- const templateDir = path45.dirname(template.path);
41239
+ const templateDir = path46.dirname(template.path);
40783
41240
  const templateSymbols = ctx.getSymbolsByFile(template.id);
40784
41241
  const templateSym = templateSymbols.find((s) => s.kind === "function" || s.kind === "class");
40785
41242
  if (!templateSym) continue;
@@ -40804,7 +41261,7 @@ var NextJSPlugin = class {
40804
41261
  for (const file of pagesRouterFiles) {
40805
41262
  let source;
40806
41263
  try {
40807
- source = fs36.readFileSync(path45.resolve(ctx.rootPath, file.path), "utf-8");
41264
+ source = fs36.readFileSync(path46.resolve(ctx.rootPath, file.path), "utf-8");
40808
41265
  } catch {
40809
41266
  continue;
40810
41267
  }
@@ -40842,7 +41299,7 @@ var NextJSPlugin = class {
40842
41299
 
40843
41300
  // src/indexer/plugins/integration/framework/gin/index.ts
40844
41301
  import fs37 from "fs";
40845
- import path46 from "path";
41302
+ import path47 from "path";
40846
41303
  var ROUTE_RE3 = /\b(\w+)\s*\.\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|Any)\s*\(\s*"([^"]+)"/g;
40847
41304
  var GROUP_RE = /\b(\w+)\s*\.\s*Group\s*\(\s*"([^"]+)"/g;
40848
41305
  var MIDDLEWARE_RE2 = /\b(\w+)\s*\.\s*Use\s*\(\s*(\w[\w.]*)/g;
@@ -40898,13 +41355,13 @@ var GinPlugin = class {
40898
41355
  };
40899
41356
  detect(ctx) {
40900
41357
  try {
40901
- const goModPath = path46.join(ctx.rootPath, "go.mod");
41358
+ const goModPath = path47.join(ctx.rootPath, "go.mod");
40902
41359
  const content = fs37.readFileSync(goModPath, "utf-8");
40903
41360
  if (content.includes("github.com/gin-gonic/gin")) return true;
40904
41361
  } catch {
40905
41362
  }
40906
41363
  try {
40907
- const goSumPath = path46.join(ctx.rootPath, "go.sum");
41364
+ const goSumPath = path47.join(ctx.rootPath, "go.sum");
40908
41365
  const content = fs37.readFileSync(goSumPath, "utf-8");
40909
41366
  return content.includes("github.com/gin-gonic/gin");
40910
41367
  } catch {
@@ -40967,7 +41424,7 @@ var GinPlugin = class {
40967
41424
 
40968
41425
  // src/indexer/plugins/integration/framework/echo/index.ts
40969
41426
  import fs38 from "fs";
40970
- import path47 from "path";
41427
+ import path48 from "path";
40971
41428
  var ROUTE_RE4 = /\b(\w+)\s*\.\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE)\s*\(\s*"([^"]+)"/g;
40972
41429
  var GROUP_RE2 = /\b(\w+)\s*\.\s*Group\s*\(\s*"([^"]+)"/g;
40973
41430
  var MIDDLEWARE_RE3 = /\b(\w+)\s*\.\s*(?:Use|Pre)\s*\(\s*(\w[\w.]*)/g;
@@ -41023,13 +41480,13 @@ var EchoPlugin = class {
41023
41480
  };
41024
41481
  detect(ctx) {
41025
41482
  try {
41026
- const goModPath = path47.join(ctx.rootPath, "go.mod");
41483
+ const goModPath = path48.join(ctx.rootPath, "go.mod");
41027
41484
  const content = fs38.readFileSync(goModPath, "utf-8");
41028
41485
  if (content.includes("github.com/labstack/echo")) return true;
41029
41486
  } catch {
41030
41487
  }
41031
41488
  try {
41032
- const goSumPath = path47.join(ctx.rootPath, "go.sum");
41489
+ const goSumPath = path48.join(ctx.rootPath, "go.sum");
41033
41490
  const content = fs38.readFileSync(goSumPath, "utf-8");
41034
41491
  return content.includes("github.com/labstack/echo");
41035
41492
  } catch {
@@ -41198,7 +41655,7 @@ function extractTypeORMEntity(source, filePath) {
41198
41655
 
41199
41656
  // src/indexer/plugins/integration/orm/sequelize/index.ts
41200
41657
  import fs39 from "fs";
41201
- import path48 from "path";
41658
+ import path49 from "path";
41202
41659
  import { ok as ok41 } from "neverthrow";
41203
41660
  var SequelizePlugin = class {
41204
41661
  manifest = {
@@ -41215,7 +41672,7 @@ var SequelizePlugin = class {
41215
41672
  };
41216
41673
  if ("sequelize" in deps || "sequelize-typescript" in deps) return true;
41217
41674
  try {
41218
- const pkgPath = path48.join(ctx.rootPath, "package.json");
41675
+ const pkgPath = path49.join(ctx.rootPath, "package.json");
41219
41676
  const content = fs39.readFileSync(pkgPath, "utf-8");
41220
41677
  const json = JSON.parse(content);
41221
41678
  const allDeps = { ...json.dependencies, ...json.devDependencies };
@@ -41268,7 +41725,7 @@ var SequelizePlugin = class {
41268
41725
  return ok41([]);
41269
41726
  }
41270
41727
  isMigrationFile(filePath) {
41271
- return /migrations?\//.test(filePath) && /\d/.test(path48.basename(filePath));
41728
+ return /migrations?\//.test(filePath) && /\d/.test(path49.basename(filePath));
41272
41729
  }
41273
41730
  };
41274
41731
  function extractSequelizeModel(source, filePath) {
@@ -41537,7 +41994,7 @@ function extractScopes2(source, className) {
41537
41994
 
41538
41995
  // src/indexer/plugins/integration/orm/mongoose/index.ts
41539
41996
  import fs40 from "fs";
41540
- import path49 from "path";
41997
+ import path50 from "path";
41541
41998
  import { ok as ok42 } from "neverthrow";
41542
41999
  var MongoosePlugin = class {
41543
42000
  manifest = {
@@ -41554,7 +42011,7 @@ var MongoosePlugin = class {
41554
42011
  };
41555
42012
  if ("mongoose" in deps) return true;
41556
42013
  try {
41557
- const pkgPath = path49.join(ctx.rootPath, "package.json");
42014
+ const pkgPath = path50.join(ctx.rootPath, "package.json");
41558
42015
  const content = fs40.readFileSync(pkgPath, "utf-8");
41559
42016
  const json = JSON.parse(content);
41560
42017
  const allDeps = { ...json.dependencies, ...json.devDependencies };
@@ -41846,8 +42303,8 @@ function extractRefs(fields, sourceModelName) {
41846
42303
  // src/indexer/plugins/integration/orm/sqlalchemy/index.ts
41847
42304
  import { createRequire as createRequire16 } from "module";
41848
42305
  import fs41 from "fs";
41849
- import path50 from "path";
41850
- import { ok as ok43, err as err26 } from "neverthrow";
42306
+ import path51 from "path";
42307
+ import { ok as ok43, err as err27 } from "neverthrow";
41851
42308
  var require17 = createRequire16(import.meta.url);
41852
42309
  var Parser15 = require17("tree-sitter");
41853
42310
  var PythonGrammar4 = require17("tree-sitter-python");
@@ -41866,14 +42323,14 @@ function hasPythonDep3(ctx, pkg) {
41866
42323
  }
41867
42324
  if (ctx.requirementsTxt?.includes(lowerPkg)) return true;
41868
42325
  try {
41869
- const pyprojectPath = path50.join(ctx.rootPath, "pyproject.toml");
42326
+ const pyprojectPath = path51.join(ctx.rootPath, "pyproject.toml");
41870
42327
  const content = fs41.readFileSync(pyprojectPath, "utf-8");
41871
42328
  const re = new RegExp(`["']${escapeRegExp(pkg)}[>=<\\[!~\\s"']`, "i");
41872
42329
  if (re.test(content)) return true;
41873
42330
  } catch {
41874
42331
  }
41875
42332
  try {
41876
- const reqPath = path50.join(ctx.rootPath, "requirements.txt");
42333
+ const reqPath = path51.join(ctx.rootPath, "requirements.txt");
41877
42334
  const content = fs41.readFileSync(reqPath, "utf-8");
41878
42335
  const re = new RegExp(`^${escapeRegExp(pkg)}\\b`, "im");
41879
42336
  if (re.test(content)) return true;
@@ -41924,7 +42381,7 @@ var SQLAlchemyPlugin = class {
41924
42381
  const tree = parser.parse(source);
41925
42382
  this.extractAlembicMigrations(tree.rootNode, source, filePath, result);
41926
42383
  } catch (e) {
41927
- return err26(parseError(filePath, `SQLAlchemy migration parse error: ${e instanceof Error ? e.message : String(e)}`));
42384
+ return err27(parseError(filePath, `SQLAlchemy migration parse error: ${e instanceof Error ? e.message : String(e)}`));
41928
42385
  }
41929
42386
  result.frameworkRole = "alembic_migration";
41930
42387
  return ok43(result);
@@ -41941,7 +42398,7 @@ var SQLAlchemyPlugin = class {
41941
42398
  const root = tree.rootNode;
41942
42399
  this.extractModels(root, source, filePath, result);
41943
42400
  } catch (e) {
41944
- return err26(parseError(filePath, `SQLAlchemy parse error: ${e instanceof Error ? e.message : String(e)}`));
42401
+ return err27(parseError(filePath, `SQLAlchemy parse error: ${e instanceof Error ? e.message : String(e)}`));
41945
42402
  }
41946
42403
  return ok43(result);
41947
42404
  }
@@ -42031,7 +42488,7 @@ var SQLAlchemyPlugin = class {
42031
42488
  */
42032
42489
  extractAlembicMigrations(root, source, filePath, result) {
42033
42490
  const calls = this.findAllByType(root, "call");
42034
- const fileBaseName = path50.basename(filePath);
42491
+ const fileBaseName = path51.basename(filePath);
42035
42492
  const timestampMatch = fileBaseName.match(/^(\d+)_/);
42036
42493
  const timestamp = timestampMatch ? timestampMatch[1] : void 0;
42037
42494
  for (const call of calls) {
@@ -42432,7 +42889,7 @@ function toModelName(varName) {
42432
42889
 
42433
42890
  // src/indexer/plugins/integration/view/react/index.ts
42434
42891
  import { createRequire as createRequire17 } from "module";
42435
- import { ok as ok45, err as err27 } from "neverthrow";
42892
+ import { ok as ok45, err as err28 } from "neverthrow";
42436
42893
  var require18 = createRequire17(import.meta.url);
42437
42894
  var Parser16 = require18("tree-sitter");
42438
42895
  var TsGrammar3 = require18("tree-sitter-typescript");
@@ -42570,7 +43027,7 @@ var ReactPlugin = class {
42570
43027
  }
42571
43028
  }
42572
43029
  } catch (e) {
42573
- return err27(parseError(filePath, `tree-sitter parse failed: ${e}`));
43030
+ return err28(parseError(filePath, `tree-sitter parse failed: ${e}`));
42574
43031
  }
42575
43032
  return ok45(result);
42576
43033
  }
@@ -42684,7 +43141,7 @@ var ReactPlugin = class {
42684
43141
 
42685
43142
  // src/indexer/plugins/integration/view/vue/index.ts
42686
43143
  import fs42 from "fs";
42687
- import path51 from "path";
43144
+ import path52 from "path";
42688
43145
 
42689
43146
  // src/indexer/plugins/integration/view/vue/resolver.ts
42690
43147
  function toKebabCase(name) {
@@ -42723,7 +43180,7 @@ var VueFrameworkPlugin = class {
42723
43180
  return "vue" in deps;
42724
43181
  }
42725
43182
  try {
42726
- const pkgPath = path51.join(ctx.rootPath, "package.json");
43183
+ const pkgPath = path52.join(ctx.rootPath, "package.json");
42727
43184
  const content = fs42.readFileSync(pkgPath, "utf-8");
42728
43185
  const pkg = JSON.parse(content);
42729
43186
  const deps = {
@@ -42829,7 +43286,7 @@ var VueFrameworkPlugin = class {
42829
43286
 
42830
43287
  // src/indexer/plugins/integration/view/react-native/index.ts
42831
43288
  import fs43 from "fs";
42832
- import path52 from "path";
43289
+ import path53 from "path";
42833
43290
  import { ok as ok46 } from "neverthrow";
42834
43291
  function expoFileToRoute(filePath) {
42835
43292
  const normalized = filePath.replace(/\\/g, "/");
@@ -42867,7 +43324,7 @@ var ReactNativePlugin = class {
42867
43324
  return true;
42868
43325
  }
42869
43326
  try {
42870
- const pkgPath = path52.join(ctx.rootPath, "package.json");
43327
+ const pkgPath = path53.join(ctx.rootPath, "package.json");
42871
43328
  const content = fs43.readFileSync(pkgPath, "utf-8");
42872
43329
  const json = JSON.parse(content);
42873
43330
  const allDeps = { ...json.dependencies, ...json.devDependencies };
@@ -43101,8 +43558,8 @@ function extractExpoNavigationCalls(source) {
43101
43558
  }
43102
43559
  const templateRegex = /router\.(push|replace|navigate)\s*\(\s*`([^`]+)`/g;
43103
43560
  while ((match = templateRegex.exec(source)) !== null) {
43104
- const path82 = match[2].replace(/\$\{[^}]+\}/g, ":param");
43105
- paths.push(path82);
43561
+ const path83 = match[2].replace(/\$\{[^}]+\}/g, ":param");
43562
+ paths.push(path83);
43106
43563
  }
43107
43564
  const linkRegex = /<Link\s+[^>]*href\s*=\s*(?:\{?\s*)?['"]([^'"]+)['"]/g;
43108
43565
  while ((match = linkRegex.exec(source)) !== null) {
@@ -43114,9 +43571,9 @@ function extractExpoNavigationCalls(source) {
43114
43571
  }
43115
43572
  return [...new Set(paths)];
43116
43573
  }
43117
- function matchExpoRoute(path82, routePattern) {
43118
- if (path82 === routePattern) return true;
43119
- const pathParts = path82.split("/").filter(Boolean);
43574
+ function matchExpoRoute(path83, routePattern) {
43575
+ if (path83 === routePattern) return true;
43576
+ const pathParts = path83.split("/").filter(Boolean);
43120
43577
  const routeParts = routePattern.split("/").filter(Boolean);
43121
43578
  if (pathParts.length !== routeParts.length) {
43122
43579
  if (routeParts[routeParts.length - 1] === "*" && pathParts.length >= routeParts.length - 1) {
@@ -43175,7 +43632,7 @@ function extractNativeModuleNames(source) {
43175
43632
 
43176
43633
  // src/indexer/plugins/integration/view/blade/index.ts
43177
43634
  import fs44 from "fs";
43178
- import path53 from "path";
43635
+ import path54 from "path";
43179
43636
  var EXTENDS_RE = /@extends\(\s*['"]([\w.-]+)['"]\s*\)/g;
43180
43637
  var INCLUDE_RE = /@include(?:If|When|Unless|First)?\(\s*['"]([\w.-]+)['"]/g;
43181
43638
  var COMPONENT_RE = /@component\(\s*['"]([\w.-]+)['"]/g;
@@ -43233,7 +43690,7 @@ var BladePlugin = class {
43233
43690
  };
43234
43691
  detect(ctx) {
43235
43692
  try {
43236
- const viewsDir = path53.join(ctx.rootPath, "resources", "views");
43693
+ const viewsDir = path54.join(ctx.rootPath, "resources", "views");
43237
43694
  const stat = fs44.statSync(viewsDir);
43238
43695
  if (!stat.isDirectory()) return false;
43239
43696
  return this.hasBlade(viewsDir);
@@ -43320,7 +43777,7 @@ var BladePlugin = class {
43320
43777
  for (const entry of entries) {
43321
43778
  if (entry.isFile() && entry.name.endsWith(".blade.php")) return true;
43322
43779
  if (entry.isDirectory()) {
43323
- if (this.hasBlade(path53.join(dir, entry.name))) return true;
43780
+ if (this.hasBlade(path54.join(dir, entry.name))) return true;
43324
43781
  }
43325
43782
  }
43326
43783
  } catch {
@@ -43331,7 +43788,7 @@ var BladePlugin = class {
43331
43788
 
43332
43789
  // src/indexer/plugins/integration/view/inertia/index.ts
43333
43790
  import fs45 from "fs";
43334
- import path54 from "path";
43791
+ import path55 from "path";
43335
43792
  var INERTIA_RENDER_RE = /(?:Inertia::render|inertia)\(\s*['"]([\w/.-]+)['"]\s*(?:,\s*\[([^\]]*)\])?\s*\)/g;
43336
43793
  var ARRAY_KEY_RE = /['"](\w+)['"]\s*=>/g;
43337
43794
  function extractInertiaRenders(source) {
@@ -43377,7 +43834,7 @@ var InertiaPlugin = class {
43377
43834
  if ("@inertiajs/vue3" in deps || "@inertiajs/react" in deps) return true;
43378
43835
  }
43379
43836
  try {
43380
- const composerPath = path54.join(ctx.rootPath, "composer.json");
43837
+ const composerPath = path55.join(ctx.rootPath, "composer.json");
43381
43838
  const content = fs45.readFileSync(composerPath, "utf-8");
43382
43839
  const json = JSON.parse(content);
43383
43840
  const req = json.require;
@@ -43385,7 +43842,7 @@ var InertiaPlugin = class {
43385
43842
  } catch {
43386
43843
  }
43387
43844
  try {
43388
- const pkgPath = path54.join(ctx.rootPath, "package.json");
43845
+ const pkgPath = path55.join(ctx.rootPath, "package.json");
43389
43846
  const content = fs45.readFileSync(pkgPath, "utf-8");
43390
43847
  const pkg = JSON.parse(content);
43391
43848
  const deps = {
@@ -43427,7 +43884,7 @@ var InertiaPlugin = class {
43427
43884
  if (file.language !== "php") continue;
43428
43885
  let source;
43429
43886
  try {
43430
- source = fs45.readFileSync(path54.resolve(ctx.rootPath, file.path), "utf-8");
43887
+ source = fs45.readFileSync(path55.resolve(ctx.rootPath, file.path), "utf-8");
43431
43888
  } catch {
43432
43889
  continue;
43433
43890
  }
@@ -43479,7 +43936,7 @@ var InertiaPlugin = class {
43479
43936
 
43480
43937
  // src/indexer/plugins/integration/view/shadcn/index.ts
43481
43938
  import fs46 from "fs";
43482
- import path55 from "path";
43939
+ import path56 from "path";
43483
43940
  import { ok as ok47 } from "neverthrow";
43484
43941
  var CVA_RE = /(?:export\s+(?:default\s+)?)?(?:const|let)\s+(\w+)\s*=\s*cva\s*\(/g;
43485
43942
  function extractCvaDefinitions(source) {
@@ -43666,7 +44123,7 @@ function extractShadcnComponents(source, filePath) {
43666
44123
  return components;
43667
44124
  }
43668
44125
  function extractShadcnVueComponent(source, filePath) {
43669
- const fileName = path55.basename(filePath, path55.extname(filePath));
44126
+ const fileName = path56.basename(filePath, path56.extname(filePath));
43670
44127
  const name = toPascalCase2(fileName);
43671
44128
  const hasRadixVue = /from\s+['"]radix-vue['"]/.test(source) || /from\s+['"]reka-ui['"]/.test(source);
43672
44129
  const hasCn = /\bcn\s*\(/.test(source);
@@ -43803,7 +44260,7 @@ function scanInstalledComponents(rootPath, config) {
43803
44260
  "app/components/ui"
43804
44261
  ].filter(Boolean);
43805
44262
  for (const relDir of possibleDirs) {
43806
- const absDir = path55.resolve(rootPath, relDir);
44263
+ const absDir = path56.resolve(rootPath, relDir);
43807
44264
  try {
43808
44265
  const entries = fs46.readdirSync(absDir, { withFileTypes: true });
43809
44266
  for (const entry of entries) {
@@ -43812,17 +44269,17 @@ function scanInstalledComponents(rootPath, config) {
43812
44269
  components.push({
43813
44270
  name: toPascalCase2(baseName),
43814
44271
  fileName: entry.name,
43815
- relativePath: path55.join(relDir, entry.name)
44272
+ relativePath: path56.join(relDir, entry.name)
43816
44273
  });
43817
44274
  } else if (entry.isDirectory()) {
43818
44275
  try {
43819
- const subEntries = fs46.readdirSync(path55.join(absDir, entry.name));
44276
+ const subEntries = fs46.readdirSync(path56.join(absDir, entry.name));
43820
44277
  const indexFile = subEntries.find((f) => /^index\.(tsx|vue|ts|jsx)$/.test(f));
43821
44278
  if (indexFile) {
43822
44279
  components.push({
43823
44280
  name: toPascalCase2(entry.name),
43824
44281
  fileName: indexFile,
43825
- relativePath: path55.join(relDir, entry.name, indexFile)
44282
+ relativePath: path56.join(relDir, entry.name, indexFile)
43826
44283
  });
43827
44284
  }
43828
44285
  for (const sub of subEntries) {
@@ -43831,7 +44288,7 @@ function scanInstalledComponents(rootPath, config) {
43831
44288
  components.push({
43832
44289
  name: toPascalCase2(baseName),
43833
44290
  fileName: sub,
43834
- relativePath: path55.join(relDir, entry.name, sub)
44291
+ relativePath: path56.join(relDir, entry.name, sub)
43835
44292
  });
43836
44293
  }
43837
44294
  }
@@ -43864,7 +44321,7 @@ var ShadcnPlugin = class {
43864
44321
  detect(ctx) {
43865
44322
  this.rootPath = ctx.rootPath;
43866
44323
  try {
43867
- const configPath = path55.join(ctx.rootPath, "components.json");
44324
+ const configPath = path56.join(ctx.rootPath, "components.json");
43868
44325
  const raw = fs46.readFileSync(configPath, "utf-8");
43869
44326
  this.config = JSON.parse(raw);
43870
44327
  this.scanComponents(ctx);
@@ -44742,7 +45199,7 @@ var HeadlessUiPlugin = class {
44742
45199
 
44743
45200
  // src/indexer/plugins/integration/view/nuxt-ui/index.ts
44744
45201
  import fs47 from "fs";
44745
- import path56 from "path";
45202
+ import path57 from "path";
44746
45203
  import { ok as ok51 } from "neverthrow";
44747
45204
  var NUXT_UI_V3_COMPONENTS = /* @__PURE__ */ new Set([
44748
45205
  "UAccordion",
@@ -44998,7 +45455,7 @@ var NuxtUiPlugin = class {
44998
45455
  this.hasPro = "@nuxt/ui-pro" in deps;
44999
45456
  if (!hasNuxtUi) {
45000
45457
  try {
45001
- const configPath = path56.join(ctx.rootPath, "nuxt.config.ts");
45458
+ const configPath = path57.join(ctx.rootPath, "nuxt.config.ts");
45002
45459
  const configContent = fs47.readFileSync(configPath, "utf-8");
45003
45460
  if (/@nuxt\/ui/.test(configContent)) return true;
45004
45461
  } catch {
@@ -45199,7 +45656,7 @@ function extractBraceBody4(source, pos) {
45199
45656
 
45200
45657
  // src/indexer/plugins/integration/view/angular/index.ts
45201
45658
  import fs48 from "fs";
45202
- import path57 from "path";
45659
+ import path58 from "path";
45203
45660
  var COMPONENT_RE2 = /@Component\s*\(\s*\{[^}]*selector\s*:\s*['"]([^'"]+)['"]/gs;
45204
45661
  var INJECTABLE_RE2 = /@Injectable\s*\(/g;
45205
45662
  var NGMODULE_RE = /@NgModule\s*\(/g;
@@ -45247,7 +45704,7 @@ var AngularPlugin = class {
45247
45704
  if ("@angular/core" in deps) return true;
45248
45705
  }
45249
45706
  try {
45250
- const pkgPath = path57.join(ctx.rootPath, "package.json");
45707
+ const pkgPath = path58.join(ctx.rootPath, "package.json");
45251
45708
  const content = fs48.readFileSync(pkgPath, "utf-8");
45252
45709
  const pkg = JSON.parse(content);
45253
45710
  const deps = {
@@ -45568,7 +46025,7 @@ function isHtmlTag(tag) {
45568
46025
 
45569
46026
  // src/indexer/plugins/integration/view/svelte/index.ts
45570
46027
  import fs49 from "fs";
45571
- import path58 from "path";
46028
+ import path59 from "path";
45572
46029
  var EXPORT_LET_RE = /export\s+let\s+(\w+)/g;
45573
46030
  var SLOT_RE = /<slot(?:\s+name\s*=\s*['"]([^'"]+)['"])?\s*\/?>/g;
45574
46031
  var DISPATCH_RE2 = /dispatch\s*\(\s*['"]([^'"]+)['"]/g;
@@ -45586,11 +46043,11 @@ function extractTemplate(source) {
45586
46043
  return source.replace(/<script[^>]*>[\s\S]*?<\/script>/g, "").replace(/<style[^>]*>[\s\S]*?<\/style>/g, "");
45587
46044
  }
45588
46045
  function isSvelteKitRouteFile(filePath) {
45589
- const base = path58.basename(filePath);
46046
+ const base = path59.basename(filePath);
45590
46047
  return /^\+(page|layout|server|error)/.test(base);
45591
46048
  }
45592
46049
  function isSvelteKitHooksFile(filePath) {
45593
- const base = path58.basename(filePath);
46050
+ const base = path59.basename(filePath);
45594
46051
  return /^hooks\.(server|client)\.(ts|js)$/.test(base);
45595
46052
  }
45596
46053
  function extractRouteUri(filePath) {
@@ -45598,11 +46055,11 @@ function extractRouteUri(filePath) {
45598
46055
  const routesIdx = normalized.indexOf("/routes/");
45599
46056
  if (routesIdx === -1) return "/";
45600
46057
  const afterRoutes = normalized.substring(routesIdx + "/routes".length);
45601
- const dir = path58.posix.dirname(afterRoutes);
46058
+ const dir = path59.posix.dirname(afterRoutes);
45602
46059
  return dir === "." ? "/" : dir;
45603
46060
  }
45604
46061
  function componentNameFromPath2(filePath) {
45605
- const base = path58.basename(filePath, ".svelte");
46062
+ const base = path59.basename(filePath, ".svelte");
45606
46063
  if (base.startsWith("+")) return base;
45607
46064
  return base;
45608
46065
  }
@@ -45629,7 +46086,7 @@ var SveltePlugin = class {
45629
46086
  if ("svelte" in deps || "@sveltejs/kit" in deps) return true;
45630
46087
  }
45631
46088
  try {
45632
- const pkgPath = path58.join(ctx.rootPath, "package.json");
46089
+ const pkgPath = path59.join(ctx.rootPath, "package.json");
45633
46090
  const content = fs49.readFileSync(pkgPath, "utf-8");
45634
46091
  const pkg = JSON.parse(content);
45635
46092
  const deps = {
@@ -45705,7 +46162,7 @@ var SveltePlugin = class {
45705
46162
  const name = componentNameFromPath2(filePath);
45706
46163
  const isRouteFile = isSvelteKitRouteFile(filePath);
45707
46164
  let kind = "component";
45708
- const base = path58.basename(filePath);
46165
+ const base = path59.basename(filePath);
45709
46166
  if (base === "+page.svelte") kind = "page";
45710
46167
  else if (base === "+layout.svelte") kind = "layout";
45711
46168
  else if (base === "+error.svelte") kind = "component";
@@ -45786,7 +46243,7 @@ var SveltePlugin = class {
45786
46243
  }
45787
46244
  }
45788
46245
  extractSvelteKitServerFile(filePath, source, result) {
45789
- const base = path58.basename(filePath);
46246
+ const base = path59.basename(filePath);
45790
46247
  if (!isSvelteKitRouteFile(filePath) && !isSvelteKitHooksFile(filePath)) {
45791
46248
  return;
45792
46249
  }
@@ -45878,7 +46335,7 @@ var SveltePlugin = class {
45878
46335
 
45879
46336
  // src/indexer/plugins/integration/api/trpc/index.ts
45880
46337
  import fs50 from "fs";
45881
- import path59 from "path";
46338
+ import path60 from "path";
45882
46339
  var PROCEDURE_RE = /(\w+)\s*:\s*\w*[Pp]rocedure[\s\S]{0,500}?\.(query|mutation|subscription)\s*\(/g;
45883
46340
  var ROUTER_RE = /(?:t\.router|router)\s*\(\s*\{/g;
45884
46341
  function extractTrpcProcedures(source) {
@@ -45910,7 +46367,7 @@ var TrpcPlugin = class {
45910
46367
  if ("@trpc/server" in deps) return true;
45911
46368
  }
45912
46369
  try {
45913
- const pkgPath = path59.join(ctx.rootPath, "package.json");
46370
+ const pkgPath = path60.join(ctx.rootPath, "package.json");
45914
46371
  const content = fs50.readFileSync(pkgPath, "utf-8");
45915
46372
  const pkg = JSON.parse(content);
45916
46373
  const deps = {
@@ -45959,8 +46416,8 @@ var TrpcPlugin = class {
45959
46416
  // src/indexer/plugins/integration/api/drf/index.ts
45960
46417
  import { createRequire as createRequire18 } from "module";
45961
46418
  import fs51 from "fs";
45962
- import path60 from "path";
45963
- import { ok as ok52, err as err28 } from "neverthrow";
46419
+ import path61 from "path";
46420
+ import { ok as ok52, err as err29 } from "neverthrow";
45964
46421
  var require19 = createRequire18(import.meta.url);
45965
46422
  var Parser17 = require19("tree-sitter");
45966
46423
  var PythonGrammar5 = require19("tree-sitter-python");
@@ -45975,25 +46432,25 @@ function getParser14() {
45975
46432
  function hasPythonDep4(rootPath, depName) {
45976
46433
  for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
45977
46434
  try {
45978
- const content = fs51.readFileSync(path60.join(rootPath, reqFile), "utf-8");
46435
+ const content = fs51.readFileSync(path61.join(rootPath, reqFile), "utf-8");
45979
46436
  if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
45980
46437
  } catch {
45981
46438
  }
45982
46439
  }
45983
46440
  try {
45984
- const content = fs51.readFileSync(path60.join(rootPath, "pyproject.toml"), "utf-8");
46441
+ const content = fs51.readFileSync(path61.join(rootPath, "pyproject.toml"), "utf-8");
45985
46442
  if (content.includes(depName)) return true;
45986
46443
  } catch {
45987
46444
  }
45988
46445
  for (const f of ["setup.py", "setup.cfg"]) {
45989
46446
  try {
45990
- const content = fs51.readFileSync(path60.join(rootPath, f), "utf-8");
46447
+ const content = fs51.readFileSync(path61.join(rootPath, f), "utf-8");
45991
46448
  if (content.includes(depName)) return true;
45992
46449
  } catch {
45993
46450
  }
45994
46451
  }
45995
46452
  try {
45996
- const content = fs51.readFileSync(path60.join(rootPath, "Pipfile"), "utf-8");
46453
+ const content = fs51.readFileSync(path61.join(rootPath, "Pipfile"), "utf-8");
45997
46454
  if (content.includes(depName)) return true;
45998
46455
  } catch {
45999
46456
  }
@@ -46241,7 +46698,7 @@ var DRFPlugin = class {
46241
46698
  const parser = getParser14();
46242
46699
  tree = parser.parse(source);
46243
46700
  } catch (e) {
46244
- return err28(parseError(filePath, `tree-sitter parse failed: ${e}`));
46701
+ return err29(parseError(filePath, `tree-sitter parse failed: ${e}`));
46245
46702
  }
46246
46703
  const root = tree.rootNode;
46247
46704
  const serializers = extractSerializers(root);
@@ -46308,7 +46765,7 @@ var DRFPlugin = class {
46308
46765
 
46309
46766
  // src/indexer/plugins/integration/validation/zod/index.ts
46310
46767
  import fs52 from "fs";
46311
- import path61 from "path";
46768
+ import path62 from "path";
46312
46769
  var ZOD_OBJECT_RE = /(?:export\s+(?:default\s+)?)?(?:const|let|var)\s+(\w+)\s*=\s*z\.object\s*\(\s*\{([^]*?)\}\s*\)/g;
46313
46770
  var ZOD_FIELD_RE = /(\w+)\s*:\s*z\.(\w+)\s*\(([^)]*)\)([.\w()]*)/g;
46314
46771
  function resolveFieldType(baseType, chain) {
@@ -46363,7 +46820,7 @@ var ZodPlugin = class {
46363
46820
  if ("zod" in deps) return true;
46364
46821
  }
46365
46822
  try {
46366
- const pkgPath = path61.join(ctx.rootPath, "package.json");
46823
+ const pkgPath = path62.join(ctx.rootPath, "package.json");
46367
46824
  const content = fs52.readFileSync(pkgPath, "utf-8");
46368
46825
  const pkg = JSON.parse(content);
46369
46826
  const deps = {
@@ -46409,8 +46866,8 @@ var ZodPlugin = class {
46409
46866
  // src/indexer/plugins/integration/validation/pydantic/index.ts
46410
46867
  import { createRequire as createRequire19 } from "module";
46411
46868
  import fs53 from "fs";
46412
- import path62 from "path";
46413
- import { ok as ok53, err as err29 } from "neverthrow";
46869
+ import path63 from "path";
46870
+ import { ok as ok53, err as err30 } from "neverthrow";
46414
46871
  var require20 = createRequire19(import.meta.url);
46415
46872
  var Parser18 = require20("tree-sitter");
46416
46873
  var PythonGrammar6 = require20("tree-sitter-python");
@@ -46425,25 +46882,25 @@ function getParser15() {
46425
46882
  function hasPythonDep5(rootPath, depName) {
46426
46883
  for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
46427
46884
  try {
46428
- const content = fs53.readFileSync(path62.join(rootPath, reqFile), "utf-8");
46885
+ const content = fs53.readFileSync(path63.join(rootPath, reqFile), "utf-8");
46429
46886
  if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
46430
46887
  } catch {
46431
46888
  }
46432
46889
  }
46433
46890
  try {
46434
- const content = fs53.readFileSync(path62.join(rootPath, "pyproject.toml"), "utf-8");
46891
+ const content = fs53.readFileSync(path63.join(rootPath, "pyproject.toml"), "utf-8");
46435
46892
  if (content.includes(depName)) return true;
46436
46893
  } catch {
46437
46894
  }
46438
46895
  for (const f of ["setup.py", "setup.cfg"]) {
46439
46896
  try {
46440
- const content = fs53.readFileSync(path62.join(rootPath, f), "utf-8");
46897
+ const content = fs53.readFileSync(path63.join(rootPath, f), "utf-8");
46441
46898
  if (content.includes(depName)) return true;
46442
46899
  } catch {
46443
46900
  }
46444
46901
  }
46445
46902
  try {
46446
- const content = fs53.readFileSync(path62.join(rootPath, "Pipfile"), "utf-8");
46903
+ const content = fs53.readFileSync(path63.join(rootPath, "Pipfile"), "utf-8");
46447
46904
  if (content.includes(depName)) return true;
46448
46905
  } catch {
46449
46906
  }
@@ -46756,7 +47213,7 @@ var PydanticPlugin = class {
46756
47213
  const parser = getParser15();
46757
47214
  tree = parser.parse(source);
46758
47215
  } catch (e) {
46759
- return err29(parseError(filePath, `tree-sitter parse failed: ${e}`));
47216
+ return err30(parseError(filePath, `tree-sitter parse failed: ${e}`));
46760
47217
  }
46761
47218
  const root = tree.rootNode;
46762
47219
  const models = extractPydanticModels(root);
@@ -46987,7 +47444,7 @@ function extractBraceBody5(source, pos) {
46987
47444
 
46988
47445
  // src/indexer/plugins/integration/realtime/socketio/index.ts
46989
47446
  import fs54 from "fs";
46990
- import path63 from "path";
47447
+ import path64 from "path";
46991
47448
  var LISTENER_RE = /(?:socket|io|server|namespace)\s*\.\s*on\s*\(\s*['"`]([^'"`]+)['"`]/g;
46992
47449
  var EMITTER_RE = /(?:socket|io|server|namespace)(?:\.broadcast)?\s*\.\s*emit\s*\(\s*['"`]([^'"`]+)['"`]/g;
46993
47450
  var NAMESPACE_RE6 = /(?:io|server)\s*\.\s*of\s*\(\s*['"`]([^'"`]+)['"`]/g;
@@ -47030,7 +47487,7 @@ var SocketIoPlugin = class {
47030
47487
  if ("socket.io" in deps) return true;
47031
47488
  }
47032
47489
  try {
47033
- const pkgPath = path63.join(ctx.rootPath, "package.json");
47490
+ const pkgPath = path64.join(ctx.rootPath, "package.json");
47034
47491
  const content = fs54.readFileSync(pkgPath, "utf-8");
47035
47492
  const pkg = JSON.parse(content);
47036
47493
  const deps = {
@@ -47086,7 +47543,7 @@ var SocketIoPlugin = class {
47086
47543
 
47087
47544
  // src/indexer/plugins/integration/testing/testing/index.ts
47088
47545
  import fs55 from "fs";
47089
- import path64 from "path";
47546
+ import path65 from "path";
47090
47547
  var PAGE_GOTO_RE = /page\.goto\s*\(\s*['"`]([^'"`]+)['"`]/g;
47091
47548
  var CY_VISIT_RE = /cy\.visit\s*\(\s*['"`]([^'"`]+)['"`]/g;
47092
47549
  var REQUEST_METHOD_RE = /request\s*\.\s*(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g;
@@ -47193,7 +47650,7 @@ var TestingPlugin = class {
47193
47650
  }
47194
47651
  }
47195
47652
  try {
47196
- const pkgPath = path64.join(ctx.rootPath, "package.json");
47653
+ const pkgPath = path65.join(ctx.rootPath, "package.json");
47197
47654
  const content = fs55.readFileSync(pkgPath, "utf-8");
47198
47655
  const pkg = JSON.parse(content);
47199
47656
  const deps = {
@@ -47267,8 +47724,8 @@ var TestingPlugin = class {
47267
47724
  // src/indexer/plugins/integration/tooling/celery/index.ts
47268
47725
  import { createRequire as createRequire20 } from "module";
47269
47726
  import fs56 from "fs";
47270
- import path65 from "path";
47271
- import { ok as ok55, err as err30 } from "neverthrow";
47727
+ import path66 from "path";
47728
+ import { ok as ok55, err as err31 } from "neverthrow";
47272
47729
  var require21 = createRequire20(import.meta.url);
47273
47730
  var Parser19 = require21("tree-sitter");
47274
47731
  var PythonGrammar7 = require21("tree-sitter-python");
@@ -47283,25 +47740,25 @@ function getParser16() {
47283
47740
  function hasPythonDep6(rootPath, depName) {
47284
47741
  for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
47285
47742
  try {
47286
- const content = fs56.readFileSync(path65.join(rootPath, reqFile), "utf-8");
47743
+ const content = fs56.readFileSync(path66.join(rootPath, reqFile), "utf-8");
47287
47744
  if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
47288
47745
  } catch {
47289
47746
  }
47290
47747
  }
47291
47748
  try {
47292
- const content = fs56.readFileSync(path65.join(rootPath, "pyproject.toml"), "utf-8");
47749
+ const content = fs56.readFileSync(path66.join(rootPath, "pyproject.toml"), "utf-8");
47293
47750
  if (content.includes(depName)) return true;
47294
47751
  } catch {
47295
47752
  }
47296
47753
  for (const f of ["setup.py", "setup.cfg"]) {
47297
47754
  try {
47298
- const content = fs56.readFileSync(path65.join(rootPath, f), "utf-8");
47755
+ const content = fs56.readFileSync(path66.join(rootPath, f), "utf-8");
47299
47756
  if (content.includes(depName)) return true;
47300
47757
  } catch {
47301
47758
  }
47302
47759
  }
47303
47760
  try {
47304
- const content = fs56.readFileSync(path65.join(rootPath, "Pipfile"), "utf-8");
47761
+ const content = fs56.readFileSync(path66.join(rootPath, "Pipfile"), "utf-8");
47305
47762
  if (content.includes(depName)) return true;
47306
47763
  } catch {
47307
47764
  }
@@ -47473,7 +47930,7 @@ var CeleryPlugin = class {
47473
47930
  const parser = getParser16();
47474
47931
  tree = parser.parse(source);
47475
47932
  } catch (e) {
47476
- return err30(parseError(filePath, `tree-sitter parse failed: ${e}`));
47933
+ return err31(parseError(filePath, `tree-sitter parse failed: ${e}`));
47477
47934
  }
47478
47935
  const root = tree.rootNode;
47479
47936
  const tasks = extractCeleryTasks(root);
@@ -47550,7 +48007,7 @@ var CeleryPlugin = class {
47550
48007
 
47551
48008
  // src/indexer/plugins/integration/tooling/n8n/index.ts
47552
48009
  import fs57 from "fs";
47553
- import path66 from "path";
48010
+ import path67 from "path";
47554
48011
  var CODE_TYPES = /* @__PURE__ */ new Set([
47555
48012
  "n8n-nodes-base.code",
47556
48013
  "n8n-nodes-base.function",
@@ -48180,7 +48637,7 @@ function collectNodeFiles(dir) {
48180
48637
  try {
48181
48638
  const entries = fs57.readdirSync(dir, { withFileTypes: true });
48182
48639
  for (const entry of entries) {
48183
- const fullPath = path66.join(dir, entry.name);
48640
+ const fullPath = path67.join(dir, entry.name);
48184
48641
  if (entry.isDirectory()) {
48185
48642
  results.push(...collectNodeFiles(fullPath));
48186
48643
  } else if (entry.name.endsWith(".node.ts")) {
@@ -48440,13 +48897,13 @@ var N8nPlugin = class {
48440
48897
  }
48441
48898
  }
48442
48899
  try {
48443
- if (fs57.existsSync(path66.join(ctx.rootPath, ".n8n"))) return true;
48900
+ if (fs57.existsSync(path67.join(ctx.rootPath, ".n8n"))) return true;
48444
48901
  } catch {
48445
48902
  }
48446
48903
  const nodeDirs = ["nodes", "src/nodes"];
48447
48904
  for (const dir of nodeDirs) {
48448
48905
  try {
48449
- const fullDir = path66.join(ctx.rootPath, dir);
48906
+ const fullDir = path67.join(ctx.rootPath, dir);
48450
48907
  if (fs57.existsSync(fullDir) && fs57.statSync(fullDir).isDirectory()) {
48451
48908
  const files = collectNodeFiles(fullDir);
48452
48909
  if (files.length > 0) return true;
@@ -48457,12 +48914,12 @@ var N8nPlugin = class {
48457
48914
  const searchDirs = ["workflows", "n8n", ".n8n", "."];
48458
48915
  for (const dir of searchDirs) {
48459
48916
  try {
48460
- const fullDir = path66.join(ctx.rootPath, dir);
48917
+ const fullDir = path67.join(ctx.rootPath, dir);
48461
48918
  if (!fs57.existsSync(fullDir) || !fs57.statSync(fullDir).isDirectory()) continue;
48462
48919
  const files = fs57.readdirSync(fullDir).filter((f) => f.endsWith(".json"));
48463
48920
  for (const file of files.slice(0, 5)) {
48464
48921
  try {
48465
- const content = fs57.readFileSync(path66.join(fullDir, file));
48922
+ const content = fs57.readFileSync(path67.join(fullDir, file));
48466
48923
  if (parseN8nWorkflow(content)) return true;
48467
48924
  } catch {
48468
48925
  }
@@ -48524,7 +48981,7 @@ var N8nPlugin = class {
48524
48981
  routes: [],
48525
48982
  frameworkRole: role,
48526
48983
  metadata: {
48527
- workflowName: workflow.name ?? path66.basename(filePath, ".json"),
48984
+ workflowName: workflow.name ?? path67.basename(filePath, ".json"),
48528
48985
  workflowId: workflow.id,
48529
48986
  active: workflow.active ?? false,
48530
48987
  nodeCount: workflow.nodes.length,
@@ -48859,7 +49316,7 @@ function findNodeByteOffset(source, nodeName) {
48859
49316
 
48860
49317
  // src/indexer/plugins/integration/tooling/data-fetching/index.ts
48861
49318
  import fs58 from "fs";
48862
- import path67 from "path";
49319
+ import path68 from "path";
48863
49320
  var USE_QUERY_OBJECT_RE = /\b(useQuery|useInfiniteQuery)\s*\(\s*\{[^}]*?queryFn\s*:\s*[^}]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`]/g;
48864
49321
  var USE_QUERY_ARRAY_RE = /\b(useQuery|useInfiniteQuery)\s*\(\s*\[[^\]]*\]\s*,\s*(?:\([^)]*\)\s*=>|function\s*\([^)]*\)\s*\{)[^)]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`]/g;
48865
49322
  var USE_MUTATION_RE = /\b(useMutation)\s*\(\s*\{[^}]*?mutationFn\s*:\s*[^}]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`][^)]*?(?:method\s*:\s*['"`](\w+)['"`])?/g;
@@ -48931,7 +49388,7 @@ var DataFetchingPlugin = class {
48931
49388
  if ("@tanstack/react-query" in deps || "swr" in deps) return true;
48932
49389
  }
48933
49390
  try {
48934
- const pkgPath = path67.join(ctx.rootPath, "package.json");
49391
+ const pkgPath = path68.join(ctx.rootPath, "package.json");
48935
49392
  const content = fs58.readFileSync(pkgPath, "utf-8");
48936
49393
  const pkg = JSON.parse(content);
48937
49394
  const deps = {
@@ -49035,7 +49492,7 @@ function createAllIntegrationPlugins() {
49035
49492
 
49036
49493
  // src/indexer/watcher.ts
49037
49494
  import * as parcelWatcher from "@parcel/watcher";
49038
- import path68 from "path";
49495
+ import path69 from "path";
49039
49496
  var IGNORE_DIRS = [
49040
49497
  "vendor",
49041
49498
  "node_modules",
@@ -49060,12 +49517,12 @@ var FileWatcher = class {
49060
49517
  debounceTimer = null;
49061
49518
  pendingPaths = /* @__PURE__ */ new Set();
49062
49519
  async start(rootPath, config, onChanges, debounceMs = DEFAULT_DEBOUNCE_MS, onDeletes) {
49063
- const ignoreDirs = IGNORE_DIRS.map((d) => path68.join(rootPath, d));
49520
+ const ignoreDirs = IGNORE_DIRS.map((d) => path69.join(rootPath, d));
49064
49521
  this.subscription = await parcelWatcher.subscribe(
49065
49522
  rootPath,
49066
- async (err31, events) => {
49067
- if (err31) {
49068
- logger.error({ error: err31 }, "Watcher error");
49523
+ async (err32, events) => {
49524
+ if (err32) {
49525
+ logger.error({ error: err32 }, "Watcher error");
49069
49526
  return;
49070
49527
  }
49071
49528
  const notIgnored = (p4) => !ignoreDirs.some((d) => p4.startsWith(d));
@@ -49116,12 +49573,12 @@ import http from "http";
49116
49573
  // src/cli-init.ts
49117
49574
  import { Command } from "commander";
49118
49575
  import fs68 from "fs";
49119
- import path77 from "path";
49576
+ import path78 from "path";
49120
49577
  import * as p from "@clack/prompts";
49121
49578
 
49122
49579
  // src/init/mcp-client.ts
49123
49580
  import fs59 from "fs";
49124
- import path69 from "path";
49581
+ import path70 from "path";
49125
49582
  import os2 from "os";
49126
49583
  var HOME = os2.homedir();
49127
49584
  function configureMcpClients(clientNames, projectRoot, opts) {
@@ -49153,14 +49610,14 @@ function configureMcpClients(clientNames, projectRoot, opts) {
49153
49610
  try {
49154
49611
  const action = writeTraceMcpEntry(configPath, entry);
49155
49612
  results.push({ target: configPath, action, detail: `${name} (${opts.scope})` });
49156
- } catch (err31) {
49157
- results.push({ target: configPath, action: "skipped", detail: `Error: ${err31.message}` });
49613
+ } catch (err32) {
49614
+ results.push({ target: configPath, action: "skipped", detail: `Error: ${err32.message}` });
49158
49615
  }
49159
49616
  }
49160
49617
  return results;
49161
49618
  }
49162
49619
  function writeTraceMcpEntry(configPath, entry) {
49163
- const dir = path69.dirname(configPath);
49620
+ const dir = path70.dirname(configPath);
49164
49621
  if (!fs59.existsSync(dir)) fs59.mkdirSync(dir, { recursive: true });
49165
49622
  let config = {};
49166
49623
  let isNew = true;
@@ -49181,15 +49638,15 @@ function writeTraceMcpEntry(configPath, entry) {
49181
49638
  function getConfigPath(name, projectRoot, scope) {
49182
49639
  switch (name) {
49183
49640
  case "claude-code":
49184
- return scope === "global" ? path69.join(HOME, ".claude", "settings.json") : path69.join(projectRoot, ".mcp.json");
49641
+ return scope === "global" ? path70.join(HOME, ".claude.json") : path70.join(projectRoot, ".mcp.json");
49185
49642
  case "claude-desktop":
49186
- return process.platform === "darwin" ? path69.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json") : path69.join(process.env.APPDATA ?? path69.join(HOME, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
49643
+ return process.platform === "darwin" ? path70.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json") : path70.join(process.env.APPDATA ?? path70.join(HOME, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
49187
49644
  case "cursor":
49188
- return scope === "global" ? path69.join(HOME, ".cursor", "mcp.json") : path69.join(projectRoot, ".cursor", "mcp.json");
49645
+ return scope === "global" ? path70.join(HOME, ".cursor", "mcp.json") : path70.join(projectRoot, ".cursor", "mcp.json");
49189
49646
  case "windsurf":
49190
- return scope === "global" ? path69.join(HOME, ".windsurf", "mcp.json") : path69.join(projectRoot, ".windsurf", "mcp.json");
49647
+ return scope === "global" ? path70.join(HOME, ".windsurf", "mcp.json") : path70.join(projectRoot, ".windsurf", "mcp.json");
49191
49648
  case "continue":
49192
- return scope === "global" ? path69.join(HOME, ".continue", "mcpServers", "mcp.json") : path69.join(projectRoot, ".continue", "mcpServers", "mcp.json");
49649
+ return scope === "global" ? path70.join(HOME, ".continue", "mcpServers", "mcp.json") : path70.join(projectRoot, ".continue", "mcpServers", "mcp.json");
49193
49650
  default:
49194
49651
  return null;
49195
49652
  }
@@ -49197,13 +49654,13 @@ function getConfigPath(name, projectRoot, scope) {
49197
49654
 
49198
49655
  // src/init/claude-md.ts
49199
49656
  import fs60 from "fs";
49200
- import path70 from "path";
49657
+ import path71 from "path";
49201
49658
  var START_MARKER = "<!-- trace-mcp:start -->";
49202
49659
  var END_MARKER = "<!-- trace-mcp:end -->";
49203
49660
  var BLOCK = `${START_MARKER}
49204
49661
  ## trace-mcp Tool Routing
49205
49662
 
49206
- Use trace-mcp tools for code intelligence \u2014 they understand framework relationships, not just text.
49663
+ IMPORTANT: For ANY code exploration task, ALWAYS use trace-mcp tools first. NEVER use Read/Grep/Glob/Bash(ls,find) for navigating source code.
49207
49664
 
49208
49665
  | Task | trace-mcp tool | Instead of |
49209
49666
  |------|---------------|------------|
@@ -49212,16 +49669,22 @@ Use trace-mcp tools for code intelligence \u2014 they understand framework relat
49212
49669
  | Read one symbol's source | \`get_symbol\` | Read (full file) |
49213
49670
  | What breaks if I change X | \`get_change_impact\` | guessing |
49214
49671
  | All usages of a symbol | \`find_usages\` | Grep |
49672
+ | All implementations of an interface | \`get_type_hierarchy\` | ls/find on directories |
49673
+ | All classes implementing X | \`search\` with \`implements\` filter | Grep |
49674
+ | Project health / coverage gaps | \`self_audit\` | manual inspection |
49675
+ | Dead code / dead exports | \`get_dead_code\` / \`get_dead_exports\` | Grep for unused |
49215
49676
  | Context for a task | \`get_feature_context\` | reading 15 files |
49216
49677
  | Tests for a symbol | \`get_tests_for\` | Glob + Grep |
49217
49678
  | HTTP request flow | \`get_request_flow\` | reading route files |
49218
49679
  | DB model relationships | \`get_model_context\` | reading model + migrations |
49680
+ | Component tree | \`get_component_tree\` | reading component files |
49681
+ | Circular dependencies | \`get_circular_imports\` | manual tracing |
49219
49682
 
49220
- Use Read/Grep/Glob for non-code files (.md, .json, .yaml, config).
49683
+ Use Read/Grep/Glob ONLY for non-code files (.md, .json, .yaml, config) or before Edit.
49221
49684
  Start sessions with \`get_project_map\` (summary_only=true).
49222
49685
  ${END_MARKER}`;
49223
49686
  function updateClaudeMd(projectRoot, opts) {
49224
- const filePath = opts.scope === "global" ? path70.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".claude", "CLAUDE.md") : path70.join(projectRoot, "CLAUDE.md");
49687
+ const filePath = opts.scope === "global" ? path71.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".claude", "CLAUDE.md") : path71.join(projectRoot, "CLAUDE.md");
49225
49688
  if (opts.dryRun) {
49226
49689
  if (!fs60.existsSync(filePath)) {
49227
49690
  return { target: filePath, action: "skipped", detail: "Would create CLAUDE.md" };
@@ -49256,7 +49719,7 @@ function escapeRegex4(s) {
49256
49719
 
49257
49720
  // src/init/hooks.ts
49258
49721
  import fs61 from "fs";
49259
- import path71 from "path";
49722
+ import path72 from "path";
49260
49723
  import os3 from "os";
49261
49724
 
49262
49725
  // src/init/types.ts
@@ -49265,12 +49728,12 @@ var REINDEX_HOOK_VERSION = "0.1.0";
49265
49728
 
49266
49729
  // src/init/hooks.ts
49267
49730
  var HOME2 = os3.homedir();
49268
- var HOOK_DEST = path71.join(HOME2, ".claude", "hooks", "trace-mcp-guard.sh");
49269
- var REINDEX_HOOK_DEST = path71.join(HOME2, ".claude", "hooks", "trace-mcp-reindex.sh");
49731
+ var HOOK_DEST = path72.join(HOME2, ".claude", "hooks", "trace-mcp-guard.sh");
49732
+ var REINDEX_HOOK_DEST = path72.join(HOME2, ".claude", "hooks", "trace-mcp-reindex.sh");
49270
49733
  function getHookSourcePath() {
49271
49734
  const candidates = [
49272
- path71.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-guard.sh"),
49273
- path71.resolve(process.cwd(), "hooks", "trace-mcp-guard.sh")
49735
+ path72.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-guard.sh"),
49736
+ path72.resolve(process.cwd(), "hooks", "trace-mcp-guard.sh")
49274
49737
  ];
49275
49738
  for (const c of candidates) {
49276
49739
  if (fs61.existsSync(c)) return c;
@@ -49278,17 +49741,17 @@ function getHookSourcePath() {
49278
49741
  throw new Error("Could not find hooks/trace-mcp-guard.sh \u2014 trace-mcp installation may be corrupted.");
49279
49742
  }
49280
49743
  function installGuardHook(opts) {
49281
- const settingsPath = opts.global ? path71.join(HOME2, ".claude", "settings.json") : path71.resolve(process.cwd(), ".claude", "settings.local.json");
49744
+ const settingsPath = opts.global ? path72.join(HOME2, ".claude", "settings.json") : path72.resolve(process.cwd(), ".claude", "settings.local.json");
49282
49745
  if (opts.dryRun) {
49283
49746
  return { target: HOOK_DEST, action: "skipped", detail: "Would install guard hook" };
49284
49747
  }
49285
49748
  const hookSrc = getHookSourcePath();
49286
- const hookDir = path71.dirname(HOOK_DEST);
49749
+ const hookDir = path72.dirname(HOOK_DEST);
49287
49750
  if (!fs61.existsSync(hookDir)) fs61.mkdirSync(hookDir, { recursive: true });
49288
49751
  const isUpdate = fs61.existsSync(HOOK_DEST);
49289
49752
  fs61.copyFileSync(hookSrc, HOOK_DEST);
49290
49753
  fs61.chmodSync(HOOK_DEST, 493);
49291
- const settingsDir = path71.dirname(settingsPath);
49754
+ const settingsDir = path72.dirname(settingsPath);
49292
49755
  if (!fs61.existsSync(settingsDir)) fs61.mkdirSync(settingsDir, { recursive: true });
49293
49756
  const settings = fs61.existsSync(settingsPath) ? JSON.parse(fs61.readFileSync(settingsPath, "utf-8")) : {};
49294
49757
  if (!settings.hooks) settings.hooks = {};
@@ -49314,7 +49777,7 @@ function installGuardHook(opts) {
49314
49777
  };
49315
49778
  }
49316
49779
  function uninstallGuardHook(opts) {
49317
- const settingsPath = opts.global ? path71.join(HOME2, ".claude", "settings.json") : path71.resolve(process.cwd(), ".claude", "settings.local.json");
49780
+ const settingsPath = opts.global ? path72.join(HOME2, ".claude", "settings.json") : path72.resolve(process.cwd(), ".claude", "settings.local.json");
49318
49781
  if (fs61.existsSync(settingsPath)) {
49319
49782
  const settings = JSON.parse(fs61.readFileSync(settingsPath, "utf-8"));
49320
49783
  const pre = settings.hooks?.PreToolUse;
@@ -49336,8 +49799,8 @@ function isHookOutdated(installedVersion) {
49336
49799
  }
49337
49800
  function getReindexHookSourcePath() {
49338
49801
  const candidates = [
49339
- path71.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-reindex.sh"),
49340
- path71.resolve(process.cwd(), "hooks", "trace-mcp-reindex.sh")
49802
+ path72.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-reindex.sh"),
49803
+ path72.resolve(process.cwd(), "hooks", "trace-mcp-reindex.sh")
49341
49804
  ];
49342
49805
  for (const c of candidates) {
49343
49806
  if (fs61.existsSync(c)) return c;
@@ -49345,17 +49808,17 @@ function getReindexHookSourcePath() {
49345
49808
  throw new Error("Could not find hooks/trace-mcp-reindex.sh \u2014 trace-mcp installation may be corrupted.");
49346
49809
  }
49347
49810
  function installReindexHook(opts) {
49348
- const settingsPath = opts.global ? path71.join(HOME2, ".claude", "settings.json") : path71.resolve(process.cwd(), ".claude", "settings.local.json");
49811
+ const settingsPath = opts.global ? path72.join(HOME2, ".claude", "settings.json") : path72.resolve(process.cwd(), ".claude", "settings.local.json");
49349
49812
  if (opts.dryRun) {
49350
49813
  return { target: REINDEX_HOOK_DEST, action: "skipped", detail: "Would install reindex hook" };
49351
49814
  }
49352
49815
  const hookSrc = getReindexHookSourcePath();
49353
- const hookDir = path71.dirname(REINDEX_HOOK_DEST);
49816
+ const hookDir = path72.dirname(REINDEX_HOOK_DEST);
49354
49817
  if (!fs61.existsSync(hookDir)) fs61.mkdirSync(hookDir, { recursive: true });
49355
49818
  const isUpdate = fs61.existsSync(REINDEX_HOOK_DEST);
49356
49819
  fs61.copyFileSync(hookSrc, REINDEX_HOOK_DEST);
49357
49820
  fs61.chmodSync(REINDEX_HOOK_DEST, 493);
49358
- const settingsDir = path71.dirname(settingsPath);
49821
+ const settingsDir = path72.dirname(settingsPath);
49359
49822
  if (!fs61.existsSync(settingsDir)) fs61.mkdirSync(settingsDir, { recursive: true });
49360
49823
  const settings = fs61.existsSync(settingsPath) ? JSON.parse(fs61.readFileSync(settingsPath, "utf-8")) : {};
49361
49824
  if (!settings.hooks) settings.hooks = {};
@@ -49383,10 +49846,10 @@ function installReindexHook(opts) {
49383
49846
 
49384
49847
  // src/init/ide-rules.ts
49385
49848
  import fs62 from "fs";
49386
- import path72 from "path";
49849
+ import path73 from "path";
49387
49850
  var START_MARKER2 = "<!-- trace-mcp:start -->";
49388
49851
  var END_MARKER2 = "<!-- trace-mcp:end -->";
49389
- var TOOL_ROUTING_POLICY = `Use trace-mcp MCP tools for all code intelligence tasks \u2014 they understand framework relationships, not just text.
49852
+ 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.
49390
49853
 
49391
49854
  ## Tool Routing
49392
49855
 
@@ -49397,13 +49860,19 @@ var TOOL_ROUTING_POLICY = `Use trace-mcp MCP tools for all code intelligence tas
49397
49860
  | Read one symbol's source | \`get_symbol\` | reading full file |
49398
49861
  | What breaks if I change X | \`get_change_impact\` | guessing |
49399
49862
  | All usages of a symbol | \`find_usages\` | grep / find references |
49863
+ | All implementations of an interface | \`get_type_hierarchy\` | listing directories |
49864
+ | All classes implementing X | \`search\` with \`implements\` filter | grep |
49865
+ | Project health / coverage gaps | \`self_audit\` | manual inspection |
49866
+ | Dead code / dead exports | \`get_dead_code\` / \`get_dead_exports\` | grep for unused |
49400
49867
  | Context for a task | \`get_feature_context\` | reading many files |
49401
49868
  | Tests for a symbol | \`get_tests_for\` | searching test files |
49402
49869
  | HTTP request flow | \`get_request_flow\` | reading route files |
49403
49870
  | DB model relationships | \`get_model_context\` | reading model + migration files |
49871
+ | Component tree | \`get_component_tree\` | reading component files |
49872
+ | Circular dependencies | \`get_circular_imports\` | manual tracing |
49404
49873
 
49405
49874
  Start sessions with \`get_project_map\` (summary_only=true) to get project overview.
49406
- Use built-in file reading only for non-code files (.md, .json, .yaml, config).`;
49875
+ Use built-in file reading ONLY for non-code files (.md, .json, .yaml, config) or before editing.`;
49407
49876
  var CURSOR_RULE = `---
49408
49877
  description: trace-mcp tool routing \u2014 prefer trace-mcp MCP tools over built-in search for code intelligence
49409
49878
  globs:
@@ -49413,9 +49882,9 @@ alwaysApply: true
49413
49882
  ${TOOL_ROUTING_POLICY}
49414
49883
  `;
49415
49884
  function installCursorRules(projectRoot, opts) {
49416
- const base = opts.global ? path72.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".cursor") : path72.join(projectRoot, ".cursor");
49417
- const rulesDir = path72.join(base, "rules");
49418
- const filePath = path72.join(rulesDir, "trace-mcp.mdc");
49885
+ const base = opts.global ? path73.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".cursor") : path73.join(projectRoot, ".cursor");
49886
+ const rulesDir = path73.join(base, "rules");
49887
+ const filePath = path73.join(rulesDir, "trace-mcp.mdc");
49419
49888
  if (opts.dryRun) {
49420
49889
  if (fs62.existsSync(filePath)) {
49421
49890
  const content = fs62.readFileSync(filePath, "utf-8");
@@ -49444,7 +49913,7 @@ var WINDSURF_BLOCK = `${START_MARKER2}
49444
49913
  ${TOOL_ROUTING_POLICY}
49445
49914
  ${END_MARKER2}`;
49446
49915
  function installWindsurfRules(projectRoot, opts) {
49447
- const filePath = opts.global ? path72.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".windsurfrules") : path72.join(projectRoot, ".windsurfrules");
49916
+ const filePath = opts.global ? path73.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".windsurfrules") : path73.join(projectRoot, ".windsurfrules");
49448
49917
  if (opts.dryRun) {
49449
49918
  if (!fs62.existsSync(filePath)) {
49450
49919
  return { target: filePath, action: "skipped", detail: "Would create .windsurfrules" };
@@ -49479,12 +49948,12 @@ function escapeRegex5(s) {
49479
49948
 
49480
49949
  // src/init/detector.ts
49481
49950
  import fs63 from "fs";
49482
- import path73 from "path";
49951
+ import path74 from "path";
49483
49952
  import os4 from "os";
49484
49953
  import Database4 from "better-sqlite3";
49485
49954
  var HOME3 = os4.homedir();
49486
49955
  function detectProject(dir) {
49487
- const projectRoot = path73.resolve(dir);
49956
+ const projectRoot = path74.resolve(dir);
49488
49957
  const ctx = buildProjectContext(projectRoot);
49489
49958
  const packageManagers = detectPackageManagers(projectRoot);
49490
49959
  const registry = new PluginRegistry();
@@ -49513,7 +49982,7 @@ function detectProject(dir) {
49513
49982
  const mcpClients = detectMcpClients(projectRoot);
49514
49983
  const existingConfig = detectExistingConfig(projectRoot);
49515
49984
  const existingDb = detectExistingDb(projectRoot);
49516
- const claudeMdPath = path73.join(projectRoot, "CLAUDE.md");
49985
+ const claudeMdPath = path74.join(projectRoot, "CLAUDE.md");
49517
49986
  const hasClaudeMd = fs63.existsSync(claudeMdPath);
49518
49987
  const claudeMdHasTraceMcpBlock = hasClaudeMd && fs63.readFileSync(claudeMdPath, "utf-8").includes("<!-- trace-mcp:start -->");
49519
49988
  const { hasGuardHook, guardHookVersion } = detectGuardHook();
@@ -49534,8 +50003,8 @@ function detectProject(dir) {
49534
50003
  function detectPackageManagers(root) {
49535
50004
  const managers = [];
49536
50005
  const check = (file, type, lockfiles) => {
49537
- if (fs63.existsSync(path73.join(root, file))) {
49538
- const lockfile = lockfiles.find((l) => fs63.existsSync(path73.join(root, l)));
50006
+ if (fs63.existsSync(path74.join(root, file))) {
50007
+ const lockfile = lockfiles.find((l) => fs63.existsSync(path74.join(root, l)));
49539
50008
  managers.push({ type, lockfile });
49540
50009
  }
49541
50010
  };
@@ -49549,7 +50018,7 @@ function detectPackageManagers(root) {
49549
50018
  check("pyproject.toml", "poetry", ["poetry.lock", "uv.lock"]);
49550
50019
  if (managers.length > 0 && managers[managers.length - 1].type === "poetry") {
49551
50020
  if (managers[managers.length - 1].lockfile === "uv.lock") managers[managers.length - 1].type = "uv";
49552
- else if (!managers[managers.length - 1].lockfile && fs63.existsSync(path73.join(root, "requirements.txt"))) {
50021
+ else if (!managers[managers.length - 1].lockfile && fs63.existsSync(path74.join(root, "requirements.txt"))) {
49553
50022
  managers[managers.length - 1].type = "pip";
49554
50023
  }
49555
50024
  }
@@ -49558,7 +50027,7 @@ function detectPackageManagers(root) {
49558
50027
  check("Gemfile", "bundler", ["Gemfile.lock"]);
49559
50028
  check("pom.xml", "maven", []);
49560
50029
  if (!managers.some((m) => m.type === "maven")) {
49561
- if (fs63.existsSync(path73.join(root, "build.gradle")) || fs63.existsSync(path73.join(root, "build.gradle.kts"))) {
50030
+ if (fs63.existsSync(path74.join(root, "build.gradle")) || fs63.existsSync(path74.join(root, "build.gradle.kts"))) {
49562
50031
  managers.push({ type: "gradle", lockfile: void 0 });
49563
50032
  }
49564
50033
  }
@@ -49577,39 +50046,40 @@ function detectMcpClients(projectRoot) {
49577
50046
  }
49578
50047
  };
49579
50048
  if (projectRoot) {
49580
- checkConfig("claude-code", path73.join(projectRoot, ".mcp.json"));
50049
+ checkConfig("claude-code", path74.join(projectRoot, ".mcp.json"));
49581
50050
  }
49582
- checkConfig("claude-code", path73.join(HOME3, ".claude", "settings.json"));
50051
+ checkConfig("claude-code", path74.join(HOME3, ".claude.json"));
50052
+ checkConfig("claude-code", path74.join(HOME3, ".claude", "settings.json"));
49583
50053
  const platform = os4.platform();
49584
50054
  if (platform === "darwin") {
49585
- checkConfig("claude-desktop", path73.join(HOME3, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
50055
+ checkConfig("claude-desktop", path74.join(HOME3, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
49586
50056
  } else if (platform === "win32") {
49587
- const appData = process.env.APPDATA ?? path73.join(HOME3, "AppData", "Roaming");
49588
- checkConfig("claude-desktop", path73.join(appData, "Claude", "claude_desktop_config.json"));
50057
+ const appData = process.env.APPDATA ?? path74.join(HOME3, "AppData", "Roaming");
50058
+ checkConfig("claude-desktop", path74.join(appData, "Claude", "claude_desktop_config.json"));
49589
50059
  }
49590
- checkConfig("cursor", path73.join(HOME3, ".cursor", "mcp.json"));
50060
+ checkConfig("cursor", path74.join(HOME3, ".cursor", "mcp.json"));
49591
50061
  if (projectRoot && !clients.some((c) => c.name === "cursor")) {
49592
- checkConfig("cursor", path73.join(projectRoot, ".cursor", "mcp.json"));
50062
+ checkConfig("cursor", path74.join(projectRoot, ".cursor", "mcp.json"));
49593
50063
  }
49594
- checkConfig("windsurf", path73.join(HOME3, ".windsurf", "mcp.json"));
50064
+ checkConfig("windsurf", path74.join(HOME3, ".windsurf", "mcp.json"));
49595
50065
  if (projectRoot && !clients.some((c) => c.name === "windsurf")) {
49596
- checkConfig("windsurf", path73.join(projectRoot, ".windsurf", "mcp.json"));
50066
+ checkConfig("windsurf", path74.join(projectRoot, ".windsurf", "mcp.json"));
49597
50067
  }
49598
- checkConfig("continue", path73.join(HOME3, ".continue", "mcpServers", "mcp.json"));
50068
+ checkConfig("continue", path74.join(HOME3, ".continue", "mcpServers", "mcp.json"));
49599
50069
  if (projectRoot && !clients.some((c) => c.name === "continue")) {
49600
- checkConfig("continue", path73.join(projectRoot, ".continue", "mcpServers", "mcp.json"));
50070
+ checkConfig("continue", path74.join(projectRoot, ".continue", "mcpServers", "mcp.json"));
49601
50071
  }
49602
50072
  return clients;
49603
50073
  }
49604
50074
  function detectExistingConfig(root) {
49605
50075
  const candidates = [
49606
- path73.join(root, ".trace-mcp.json"),
49607
- path73.join(root, ".config", "trace-mcp.json")
50076
+ path74.join(root, ".trace-mcp.json"),
50077
+ path74.join(root, ".config", "trace-mcp.json")
49608
50078
  ];
49609
50079
  for (const p4 of candidates) {
49610
50080
  if (fs63.existsSync(p4)) return { path: p4 };
49611
50081
  }
49612
- const pkgPath = path73.join(root, "package.json");
50082
+ const pkgPath = path74.join(root, "package.json");
49613
50083
  if (fs63.existsSync(pkgPath)) {
49614
50084
  try {
49615
50085
  const pkg = JSON.parse(fs63.readFileSync(pkgPath, "utf-8"));
@@ -49620,7 +50090,7 @@ function detectExistingConfig(root) {
49620
50090
  return null;
49621
50091
  }
49622
50092
  function detectExistingDb(root, globalDbPath) {
49623
- const candidates = globalDbPath ? [globalDbPath, path73.join(root, ".trace-mcp", "index.db")] : [path73.join(root, ".trace-mcp", "index.db")];
50093
+ const candidates = globalDbPath ? [globalDbPath, path74.join(root, ".trace-mcp", "index.db")] : [path74.join(root, ".trace-mcp", "index.db")];
49624
50094
  const dbPath = candidates.find((p4) => fs63.existsSync(p4));
49625
50095
  if (!dbPath) return null;
49626
50096
  try {
@@ -49636,7 +50106,7 @@ function detectExistingDb(root, globalDbPath) {
49636
50106
  }
49637
50107
  }
49638
50108
  function detectGuardHook() {
49639
- const hookPath = path73.join(HOME3, ".claude", "hooks", "trace-mcp-guard.sh");
50109
+ const hookPath = path74.join(HOME3, ".claude", "hooks", "trace-mcp-guard.sh");
49640
50110
  if (!fs63.existsSync(hookPath)) return { hasGuardHook: false, guardHookVersion: null };
49641
50111
  const content = fs63.readFileSync(hookPath, "utf-8");
49642
50112
  const match = content.match(/^# trace-mcp-guard v(.+)$/m);
@@ -49648,7 +50118,7 @@ function detectGuardHook() {
49648
50118
 
49649
50119
  // src/init/conflict-detector.ts
49650
50120
  import fs64 from "fs";
49651
- import path74 from "path";
50121
+ import path75 from "path";
49652
50122
  import os5 from "os";
49653
50123
  var HOME4 = os5.homedir();
49654
50124
  var COMPETING_MCP_SERVERS = {
@@ -49742,9 +50212,9 @@ var COMPETING_PROJECT_FILES = [
49742
50212
  { file: ".greptile.yaml", competitor: "greptile" }
49743
50213
  ];
49744
50214
  var COMPETING_GLOBAL_DIRS = [
49745
- { dir: path74.join(HOME4, ".code-index"), competitor: "jcodemunch-mcp" },
49746
- { dir: path74.join(HOME4, ".repomix"), competitor: "repomix" },
49747
- { dir: path74.join(HOME4, ".aider.tags.cache.v3"), competitor: "aider" }
50215
+ { dir: path75.join(HOME4, ".code-index"), competitor: "jcodemunch-mcp" },
50216
+ { dir: path75.join(HOME4, ".repomix"), competitor: "repomix" },
50217
+ { dir: path75.join(HOME4, ".aider.tags.cache.v3"), competitor: "aider" }
49748
50218
  ];
49749
50219
  function detectConflicts(projectRoot) {
49750
50220
  const conflicts = [];
@@ -49803,31 +50273,31 @@ function getMcpConfigPaths(projectRoot) {
49803
50273
  const paths = [];
49804
50274
  const platform = os5.platform();
49805
50275
  if (projectRoot) {
49806
- paths.push({ clientName: "claude-code", configPath: path74.join(projectRoot, ".mcp.json") });
50276
+ paths.push({ clientName: "claude-code", configPath: path75.join(projectRoot, ".mcp.json") });
49807
50277
  }
49808
- paths.push({ clientName: "claude-code", configPath: path74.join(HOME4, ".claude", "settings.json") });
50278
+ paths.push({ clientName: "claude-code", configPath: path75.join(HOME4, ".claude", "settings.json") });
49809
50279
  if (platform === "darwin") {
49810
- paths.push({ clientName: "claude-desktop", configPath: path74.join(HOME4, "Library", "Application Support", "Claude", "claude_desktop_config.json") });
50280
+ paths.push({ clientName: "claude-desktop", configPath: path75.join(HOME4, "Library", "Application Support", "Claude", "claude_desktop_config.json") });
49811
50281
  } else if (platform === "win32") {
49812
- const appData = process.env.APPDATA ?? path74.join(HOME4, "AppData", "Roaming");
49813
- paths.push({ clientName: "claude-desktop", configPath: path74.join(appData, "Claude", "claude_desktop_config.json") });
50282
+ const appData = process.env.APPDATA ?? path75.join(HOME4, "AppData", "Roaming");
50283
+ paths.push({ clientName: "claude-desktop", configPath: path75.join(appData, "Claude", "claude_desktop_config.json") });
49814
50284
  }
49815
- paths.push({ clientName: "cursor", configPath: path74.join(HOME4, ".cursor", "mcp.json") });
50285
+ paths.push({ clientName: "cursor", configPath: path75.join(HOME4, ".cursor", "mcp.json") });
49816
50286
  if (projectRoot) {
49817
- paths.push({ clientName: "cursor", configPath: path74.join(projectRoot, ".cursor", "mcp.json") });
50287
+ paths.push({ clientName: "cursor", configPath: path75.join(projectRoot, ".cursor", "mcp.json") });
49818
50288
  }
49819
- paths.push({ clientName: "windsurf", configPath: path74.join(HOME4, ".windsurf", "mcp.json") });
50289
+ paths.push({ clientName: "windsurf", configPath: path75.join(HOME4, ".windsurf", "mcp.json") });
49820
50290
  if (projectRoot) {
49821
- paths.push({ clientName: "windsurf", configPath: path74.join(projectRoot, ".windsurf", "mcp.json") });
50291
+ paths.push({ clientName: "windsurf", configPath: path75.join(projectRoot, ".windsurf", "mcp.json") });
49822
50292
  }
49823
- paths.push({ clientName: "continue", configPath: path74.join(HOME4, ".continue", "mcpServers", "mcp.json") });
50293
+ paths.push({ clientName: "continue", configPath: path75.join(HOME4, ".continue", "mcpServers", "mcp.json") });
49824
50294
  return paths;
49825
50295
  }
49826
50296
  function scanHooksInSettings() {
49827
50297
  const conflicts = [];
49828
50298
  const settingsFiles = [
49829
- path74.join(HOME4, ".claude", "settings.json"),
49830
- path74.join(HOME4, ".claude", "settings.local.json")
50299
+ path75.join(HOME4, ".claude", "settings.json"),
50300
+ path75.join(HOME4, ".claude", "settings.local.json")
49831
50301
  ];
49832
50302
  for (const settingsPath of settingsFiles) {
49833
50303
  if (!fs64.existsSync(settingsPath)) continue;
@@ -49869,7 +50339,7 @@ function scanHooksInSettings() {
49869
50339
  }
49870
50340
  function scanHookScriptFiles() {
49871
50341
  const conflicts = [];
49872
- const hooksDir = path74.join(HOME4, ".claude", "hooks");
50342
+ const hooksDir = path75.join(HOME4, ".claude", "hooks");
49873
50343
  if (!fs64.existsSync(hooksDir)) return conflicts;
49874
50344
  let files;
49875
50345
  try {
@@ -49881,7 +50351,7 @@ function scanHookScriptFiles() {
49881
50351
  if (file.startsWith("trace-mcp")) continue;
49882
50352
  for (const { pattern, competitor } of COMPETING_HOOK_PATTERNS) {
49883
50353
  if (pattern.test(file)) {
49884
- const filePath = path74.join(hooksDir, file);
50354
+ const filePath = path75.join(hooksDir, file);
49885
50355
  conflicts.push({
49886
50356
  id: `hook_script:${file}:${competitor}`,
49887
50357
  category: "hook_script",
@@ -49900,13 +50370,13 @@ function scanHookScriptFiles() {
49900
50370
  function scanClaudeMdFiles(projectRoot) {
49901
50371
  const conflicts = [];
49902
50372
  const files = [
49903
- path74.join(HOME4, ".claude", "CLAUDE.md"),
49904
- path74.join(HOME4, ".claude", "AGENTS.md")
50373
+ path75.join(HOME4, ".claude", "CLAUDE.md"),
50374
+ path75.join(HOME4, ".claude", "AGENTS.md")
49905
50375
  ];
49906
50376
  if (projectRoot) {
49907
50377
  files.push(
49908
- path74.join(projectRoot, "CLAUDE.md"),
49909
- path74.join(projectRoot, "AGENTS.md")
50378
+ path75.join(projectRoot, "CLAUDE.md"),
50379
+ path75.join(projectRoot, "AGENTS.md")
49910
50380
  );
49911
50381
  }
49912
50382
  for (const filePath of files) {
@@ -49940,35 +50410,35 @@ function scanClaudeMdFiles(projectRoot) {
49940
50410
  function scanIdeRuleFiles(projectRoot) {
49941
50411
  const conflicts = [];
49942
50412
  const ruleFiles = [];
49943
- ruleFiles.push({ path: path74.join(HOME4, ".cursorrules"), type: ".cursorrules (global)" });
49944
- ruleFiles.push({ path: path74.join(HOME4, ".windsurfrules"), type: ".windsurfrules (global)" });
50413
+ ruleFiles.push({ path: path75.join(HOME4, ".cursorrules"), type: ".cursorrules (global)" });
50414
+ ruleFiles.push({ path: path75.join(HOME4, ".windsurfrules"), type: ".windsurfrules (global)" });
49945
50415
  if (projectRoot) {
49946
- ruleFiles.push({ path: path74.join(projectRoot, ".cursorrules"), type: ".cursorrules" });
49947
- ruleFiles.push({ path: path74.join(projectRoot, ".windsurfrules"), type: ".windsurfrules" });
49948
- ruleFiles.push({ path: path74.join(projectRoot, ".clinerules"), type: ".clinerules" });
49949
- ruleFiles.push({ path: path74.join(projectRoot, ".continuerules"), type: ".continuerules" });
49950
- ruleFiles.push({ path: path74.join(projectRoot, ".github", "copilot-instructions.md"), type: "copilot-instructions.md" });
49951
- const clineRulesDir = path74.join(projectRoot, ".clinerules");
50416
+ ruleFiles.push({ path: path75.join(projectRoot, ".cursorrules"), type: ".cursorrules" });
50417
+ ruleFiles.push({ path: path75.join(projectRoot, ".windsurfrules"), type: ".windsurfrules" });
50418
+ ruleFiles.push({ path: path75.join(projectRoot, ".clinerules"), type: ".clinerules" });
50419
+ ruleFiles.push({ path: path75.join(projectRoot, ".continuerules"), type: ".continuerules" });
50420
+ ruleFiles.push({ path: path75.join(projectRoot, ".github", "copilot-instructions.md"), type: "copilot-instructions.md" });
50421
+ const clineRulesDir = path75.join(projectRoot, ".clinerules");
49952
50422
  if (fs64.existsSync(clineRulesDir)) {
49953
50423
  try {
49954
50424
  const stat = fs64.statSync(clineRulesDir);
49955
50425
  if (stat.isDirectory()) {
49956
50426
  for (const file of fs64.readdirSync(clineRulesDir)) {
49957
- ruleFiles.push({ path: path74.join(clineRulesDir, file), type: `.clinerules/${file}` });
50427
+ ruleFiles.push({ path: path75.join(clineRulesDir, file), type: `.clinerules/${file}` });
49958
50428
  }
49959
50429
  }
49960
50430
  } catch {
49961
50431
  }
49962
50432
  }
49963
50433
  }
49964
- const cursorRulesDirs = [path74.join(HOME4, ".cursor", "rules")];
49965
- if (projectRoot) cursorRulesDirs.push(path74.join(projectRoot, ".cursor", "rules"));
50434
+ const cursorRulesDirs = [path75.join(HOME4, ".cursor", "rules")];
50435
+ if (projectRoot) cursorRulesDirs.push(path75.join(projectRoot, ".cursor", "rules"));
49966
50436
  for (const rulesDir of cursorRulesDirs) {
49967
50437
  if (!fs64.existsSync(rulesDir)) continue;
49968
50438
  try {
49969
50439
  for (const file of fs64.readdirSync(rulesDir)) {
49970
50440
  if (!file.endsWith(".mdc") || file === "trace-mcp.mdc") continue;
49971
- ruleFiles.push({ path: path74.join(rulesDir, file), type: `.cursor/rules/${file}` });
50441
+ ruleFiles.push({ path: path75.join(rulesDir, file), type: `.cursor/rules/${file}` });
49972
50442
  }
49973
50443
  } catch {
49974
50444
  }
@@ -50002,7 +50472,7 @@ function scanIdeRuleFiles(projectRoot) {
50002
50472
  function scanProjectConfigFiles(projectRoot) {
50003
50473
  const conflicts = [];
50004
50474
  for (const { file, competitor } of COMPETING_PROJECT_FILES) {
50005
- const filePath = path74.join(projectRoot, file);
50475
+ const filePath = path75.join(projectRoot, file);
50006
50476
  if (!fs64.existsSync(filePath)) continue;
50007
50477
  conflicts.push({
50008
50478
  id: `config:${competitor}:${file}`,
@@ -50026,7 +50496,7 @@ function scanProjectConfigDirs(projectRoot) {
50026
50496
  { dir: ".continue", competitor: "continue.dev" }
50027
50497
  ];
50028
50498
  for (const { dir, competitor } of dirs) {
50029
- const fullPath = path74.join(projectRoot, dir);
50499
+ const fullPath = path75.join(projectRoot, dir);
50030
50500
  if (!fs64.existsSync(fullPath)) continue;
50031
50501
  let stat;
50032
50502
  try {
@@ -50052,18 +50522,18 @@ function scanProjectConfigDirs(projectRoot) {
50052
50522
  function scanContinueConfigs(projectRoot) {
50053
50523
  const conflicts = [];
50054
50524
  const configPaths = [
50055
- path74.join(HOME4, ".continue", "config.yaml"),
50056
- path74.join(HOME4, ".continue", "config.json")
50525
+ path75.join(HOME4, ".continue", "config.yaml"),
50526
+ path75.join(HOME4, ".continue", "config.json")
50057
50527
  ];
50058
50528
  if (projectRoot) {
50059
50529
  configPaths.push(
50060
- path74.join(projectRoot, ".continue", "config.yaml"),
50061
- path74.join(projectRoot, ".continue", "config.json")
50530
+ path75.join(projectRoot, ".continue", "config.yaml"),
50531
+ path75.join(projectRoot, ".continue", "config.json")
50062
50532
  );
50063
50533
  }
50064
- const mcpServersDirs = [path74.join(HOME4, ".continue", "mcpServers")];
50534
+ const mcpServersDirs = [path75.join(HOME4, ".continue", "mcpServers")];
50065
50535
  if (projectRoot) {
50066
- mcpServersDirs.push(path74.join(projectRoot, ".continue", "mcpServers"));
50536
+ mcpServersDirs.push(path75.join(projectRoot, ".continue", "mcpServers"));
50067
50537
  }
50068
50538
  for (const mcpDir of mcpServersDirs) {
50069
50539
  if (!fs64.existsSync(mcpDir)) continue;
@@ -50075,7 +50545,7 @@ function scanContinueConfigs(projectRoot) {
50075
50545
  }
50076
50546
  for (const file of files) {
50077
50547
  if (!file.endsWith(".json")) continue;
50078
- const filePath = path74.join(mcpDir, file);
50548
+ const filePath = path75.join(mcpDir, file);
50079
50549
  let content;
50080
50550
  try {
50081
50551
  content = fs64.readFileSync(filePath, "utf-8");
@@ -50104,11 +50574,11 @@ function scanContinueConfigs(projectRoot) {
50104
50574
  }
50105
50575
  function scanGitHooks(projectRoot) {
50106
50576
  const conflicts = [];
50107
- const hooksDir = path74.join(projectRoot, ".git", "hooks");
50577
+ const hooksDir = path75.join(projectRoot, ".git", "hooks");
50108
50578
  if (!fs64.existsSync(hooksDir)) return conflicts;
50109
50579
  const hookFiles = ["pre-commit", "post-commit", "prepare-commit-msg"];
50110
50580
  for (const hookFile of hookFiles) {
50111
- const hookPath = path74.join(hooksDir, hookFile);
50581
+ const hookPath = path75.join(hooksDir, hookFile);
50112
50582
  if (!fs64.existsSync(hookPath)) continue;
50113
50583
  let content;
50114
50584
  try {
@@ -50218,8 +50688,8 @@ function fixMcpServer(conflict, opts) {
50218
50688
  }
50219
50689
  fs65.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
50220
50690
  return { conflictId: conflict.id, action: "removed", detail: `Removed "${serverName}" from ${shortPath2(configPath)}`, target: configPath };
50221
- } catch (err31) {
50222
- return { conflictId: conflict.id, action: "skipped", detail: `Failed to update config: ${err31.message}`, target: configPath };
50691
+ } catch (err32) {
50692
+ return { conflictId: conflict.id, action: "skipped", detail: `Failed to update config: ${err32.message}`, target: configPath };
50223
50693
  }
50224
50694
  }
50225
50695
  function fixHookInSettings(conflict, opts) {
@@ -50262,8 +50732,8 @@ function fixHookInSettings(conflict, opts) {
50262
50732
  }
50263
50733
  fs65.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
50264
50734
  return { conflictId: conflict.id, action: "removed", detail: `Removed ${competitor} hooks from ${shortPath2(settingsPath)}`, target: settingsPath };
50265
- } catch (err31) {
50266
- return { conflictId: conflict.id, action: "skipped", detail: `Failed to update settings: ${err31.message}`, target: settingsPath };
50735
+ } catch (err32) {
50736
+ return { conflictId: conflict.id, action: "skipped", detail: `Failed to update settings: ${err32.message}`, target: settingsPath };
50267
50737
  }
50268
50738
  }
50269
50739
  function fixHookScript(conflict, opts) {
@@ -50277,8 +50747,8 @@ function fixHookScript(conflict, opts) {
50277
50747
  try {
50278
50748
  fs65.unlinkSync(scriptPath);
50279
50749
  return { conflictId: conflict.id, action: "removed", detail: `Deleted ${shortPath2(scriptPath)}`, target: scriptPath };
50280
- } catch (err31) {
50281
- return { conflictId: conflict.id, action: "skipped", detail: `Failed to delete: ${err31.message}`, target: scriptPath };
50750
+ } catch (err32) {
50751
+ return { conflictId: conflict.id, action: "skipped", detail: `Failed to delete: ${err32.message}`, target: scriptPath };
50282
50752
  }
50283
50753
  }
50284
50754
  function fixClaudeMdBlock(conflict, opts) {
@@ -50291,30 +50761,20 @@ function fixClaudeMdBlock(conflict, opts) {
50291
50761
  }
50292
50762
  try {
50293
50763
  const content = fs65.readFileSync(filePath, "utf-8");
50294
- const markerPatterns = [
50295
- /<!-- ?jcodemunch:start ?-->[\s\S]*?<!-- ?jcodemunch:end ?-->\n?/gi,
50296
- /<!-- ?code-index:start ?-->[\s\S]*?<!-- ?code-index:end ?-->\n?/gi,
50297
- /<!-- ?repomix:start ?-->[\s\S]*?<!-- ?repomix:end ?-->\n?/gi,
50298
- /<!-- ?aider:start ?-->[\s\S]*?<!-- ?aider:end ?-->\n?/gi,
50299
- /<!-- ?cline:start ?-->[\s\S]*?<!-- ?cline:end ?-->\n?/gi,
50300
- /<!-- ?cody:start ?-->[\s\S]*?<!-- ?cody:end ?-->\n?/gi,
50301
- /<!-- ?greptile:start ?-->[\s\S]*?<!-- ?greptile:end ?-->\n?/gi,
50302
- /<!-- ?sourcegraph:start ?-->[\s\S]*?<!-- ?sourcegraph:end ?-->\n?/gi,
50303
- /<!-- ?code-compass:start ?-->[\s\S]*?<!-- ?code-compass:end ?-->\n?/gi,
50304
- /<!-- ?repo-map:start ?-->[\s\S]*?<!-- ?repo-map:end ?-->\n?/gi
50305
- ];
50306
- let updated = content;
50307
- for (const pattern of markerPatterns) {
50308
- updated = updated.replace(pattern, "");
50309
- }
50764
+ const tools = ["jcodemunch", "code-index", "repomix", "aider", "cline", "cody", "greptile", "sourcegraph", "code-compass", "repo-map"];
50765
+ const markerPattern = new RegExp(
50766
+ `<!-- ?(${tools.join("|")}):start ?-->[\\s\\S]*?<!-- ?\\1:end ?-->\\n?`,
50767
+ "gi"
50768
+ );
50769
+ let updated = content.replace(markerPattern, "");
50310
50770
  updated = updated.replace(/\n{3,}/g, "\n\n").trim() + "\n";
50311
50771
  if (updated === content) {
50312
50772
  return { conflictId: conflict.id, action: "skipped", detail: "No marker-delimited blocks found to remove", target: filePath };
50313
50773
  }
50314
50774
  fs65.writeFileSync(filePath, updated);
50315
50775
  return { conflictId: conflict.id, action: "cleaned", detail: `Removed ${conflict.competitor} block from ${shortPath2(filePath)}`, target: filePath };
50316
- } catch (err31) {
50317
- return { conflictId: conflict.id, action: "skipped", detail: `Failed to update: ${err31.message}`, target: filePath };
50776
+ } catch (err32) {
50777
+ return { conflictId: conflict.id, action: "skipped", detail: `Failed to update: ${err32.message}`, target: filePath };
50318
50778
  }
50319
50779
  }
50320
50780
  function fixConfigFile(conflict, opts) {
@@ -50333,8 +50793,8 @@ function fixConfigFile(conflict, opts) {
50333
50793
  fs65.unlinkSync(filePath);
50334
50794
  }
50335
50795
  return { conflictId: conflict.id, action: "removed", detail: `Deleted ${shortPath2(filePath)}`, target: filePath };
50336
- } catch (err31) {
50337
- return { conflictId: conflict.id, action: "skipped", detail: `Failed to delete: ${err31.message}`, target: filePath };
50796
+ } catch (err32) {
50797
+ return { conflictId: conflict.id, action: "skipped", detail: `Failed to delete: ${err32.message}`, target: filePath };
50338
50798
  }
50339
50799
  }
50340
50800
  function fixGlobalArtifact(conflict, opts) {
@@ -50348,8 +50808,8 @@ function fixGlobalArtifact(conflict, opts) {
50348
50808
  try {
50349
50809
  fs65.rmSync(dirPath, { recursive: true, force: true });
50350
50810
  return { conflictId: conflict.id, action: "removed", detail: `Removed ${shortPath2(dirPath)}`, target: dirPath };
50351
- } catch (err31) {
50352
- return { conflictId: conflict.id, action: "skipped", detail: `Failed to remove: ${err31.message}`, target: dirPath };
50811
+ } catch (err32) {
50812
+ return { conflictId: conflict.id, action: "skipped", detail: `Failed to remove: ${err32.message}`, target: dirPath };
50353
50813
  }
50354
50814
  }
50355
50815
  function shortPath2(p4) {
@@ -50360,7 +50820,7 @@ function shortPath2(p4) {
50360
50820
 
50361
50821
  // src/project-root.ts
50362
50822
  import fs66 from "fs";
50363
- import path75 from "path";
50823
+ import path76 from "path";
50364
50824
  var ROOT_MARKERS = [
50365
50825
  ".git",
50366
50826
  "package.json",
@@ -50374,14 +50834,14 @@ var ROOT_MARKERS = [
50374
50834
  "build.gradle.kts"
50375
50835
  ];
50376
50836
  function findProjectRoot(from) {
50377
- let dir = path75.resolve(from ?? process.cwd());
50837
+ let dir = path76.resolve(from ?? process.cwd());
50378
50838
  while (true) {
50379
50839
  for (const marker of ROOT_MARKERS) {
50380
- if (fs66.existsSync(path75.join(dir, marker))) {
50840
+ if (fs66.existsSync(path76.join(dir, marker))) {
50381
50841
  return dir;
50382
50842
  }
50383
50843
  }
50384
- const parent = path75.dirname(dir);
50844
+ const parent = path76.dirname(dir);
50385
50845
  if (parent === dir) {
50386
50846
  throw new Error(
50387
50847
  `Could not find project root from ${from ?? process.cwd()}. Looked for: ${ROOT_MARKERS.join(", ")}`
@@ -50625,7 +51085,7 @@ function generateConfig(detection) {
50625
51085
 
50626
51086
  // src/registry.ts
50627
51087
  import fs67 from "fs";
50628
- import path76 from "path";
51088
+ import path77 from "path";
50629
51089
  function emptyRegistry() {
50630
51090
  return { version: 1, projects: {} };
50631
51091
  }
@@ -50646,7 +51106,7 @@ function saveRegistry(reg) {
50646
51106
  fs67.renameSync(tmp, REGISTRY_PATH);
50647
51107
  }
50648
51108
  function registerProject(root) {
50649
- const absRoot = path76.resolve(root);
51109
+ const absRoot = path77.resolve(root);
50650
51110
  const reg = loadRegistry2();
50651
51111
  if (reg.projects[absRoot]) {
50652
51112
  return reg.projects[absRoot];
@@ -50663,7 +51123,7 @@ function registerProject(root) {
50663
51123
  return entry;
50664
51124
  }
50665
51125
  function getProject(root) {
50666
- const absRoot = path76.resolve(root);
51126
+ const absRoot = path77.resolve(root);
50667
51127
  const reg = loadRegistry2();
50668
51128
  return reg.projects[absRoot] ?? null;
50669
51129
  }
@@ -50672,7 +51132,7 @@ function listProjects() {
50672
51132
  return Object.values(reg.projects);
50673
51133
  }
50674
51134
  function updateLastIndexed(root) {
50675
- const absRoot = path76.resolve(root);
51135
+ const absRoot = path77.resolve(root);
50676
51136
  const reg = loadRegistry2();
50677
51137
  if (reg.projects[absRoot]) {
50678
51138
  reg.projects[absRoot].lastIndexed = (/* @__PURE__ */ new Date()).toISOString();
@@ -50882,7 +51342,7 @@ function registerAndIndexProject(dir, opts) {
50882
51342
  const config = generateConfig(detection);
50883
51343
  saveProjectConfig(projectRoot, { root: config.root, include: config.include, exclude: config.exclude });
50884
51344
  const dbPath = getDbPath(projectRoot);
50885
- const oldDbPath = path77.join(projectRoot, ".trace-mcp", "index.db");
51345
+ const oldDbPath = path78.join(projectRoot, ".trace-mcp", "index.db");
50886
51346
  if (fs68.existsSync(oldDbPath) && !fs68.existsSync(dbPath)) {
50887
51347
  fs68.copyFileSync(oldDbPath, dbPath);
50888
51348
  }
@@ -50916,11 +51376,11 @@ function shortPath3(p4) {
50916
51376
  // src/cli-upgrade.ts
50917
51377
  import { Command as Command2 } from "commander";
50918
51378
  import fs69 from "fs";
50919
- import path78 from "path";
51379
+ import path79 from "path";
50920
51380
  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) => {
50921
51381
  const projectRoots = [];
50922
51382
  if (dir) {
50923
- projectRoots.push(path78.resolve(dir));
51383
+ projectRoots.push(path79.resolve(dir));
50924
51384
  } else {
50925
51385
  const projects = listProjects();
50926
51386
  if (projects.length === 0) {
@@ -51002,7 +51462,7 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
51002
51462
  console.log(header);
51003
51463
  for (const { projectRoot, steps } of allSteps) {
51004
51464
  console.log(`
51005
- Project: ${path78.basename(projectRoot)} (${projectRoot})`);
51465
+ Project: ${path79.basename(projectRoot)} (${projectRoot})`);
51006
51466
  for (const step of steps) {
51007
51467
  console.log(` ${step.action}: ${step.detail ?? step.target}`);
51008
51468
  }
@@ -51014,10 +51474,10 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
51014
51474
  // src/cli-add.ts
51015
51475
  import { Command as Command3 } from "commander";
51016
51476
  import fs70 from "fs";
51017
- import path79 from "path";
51477
+ import path80 from "path";
51018
51478
  import * as p2 from "@clack/prompts";
51019
51479
  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) => {
51020
- const resolvedDir = path79.resolve(dir);
51480
+ const resolvedDir = path80.resolve(dir);
51021
51481
  if (!fs70.existsSync(resolvedDir)) {
51022
51482
  console.error(`Directory does not exist: ${resolvedDir}`);
51023
51483
  process.exit(1);
@@ -51072,7 +51532,7 @@ DB: ${shortPath4(existing.dbPath)}`, "Existing");
51072
51532
  ensureGlobalDirs();
51073
51533
  saveProjectConfig(projectRoot, configForSave);
51074
51534
  const dbPath = getDbPath(projectRoot);
51075
- const oldDbPath = path79.join(projectRoot, ".trace-mcp", "index.db");
51535
+ const oldDbPath = path80.join(projectRoot, ".trace-mcp", "index.db");
51076
51536
  let migrated = false;
51077
51537
  if (fs70.existsSync(oldDbPath) && !fs70.existsSync(dbPath)) {
51078
51538
  fs70.copyFileSync(oldDbPath, dbPath);
@@ -51249,7 +51709,7 @@ function shortPath5(p4) {
51249
51709
  // src/cli-ci.ts
51250
51710
  import { Command as Command5 } from "commander";
51251
51711
  import { execFileSync as execFileSync7 } from "child_process";
51252
- import path80 from "path";
51712
+ import path81 from "path";
51253
51713
  import fs71 from "fs";
51254
51714
 
51255
51715
  // src/ci/report-generator.ts
@@ -51629,8 +52089,8 @@ function writeOutput(outputPath, content) {
51629
52089
  if (outputPath === "-" || !outputPath) {
51630
52090
  process.stdout.write(content + "\n");
51631
52091
  } else {
51632
- const resolved = path80.resolve(outputPath);
51633
- fs71.mkdirSync(path80.dirname(resolved), { recursive: true });
52092
+ const resolved = path81.resolve(outputPath);
52093
+ fs71.mkdirSync(path81.dirname(resolved), { recursive: true });
51634
52094
  fs71.writeFileSync(resolved, content, "utf-8");
51635
52095
  logger.info({ path: resolved }, "CI report written");
51636
52096
  }
@@ -51866,22 +52326,22 @@ program.command("serve").description("Start MCP server (stdio transport)").actio
51866
52326
  ) : null;
51867
52327
  const runEmbeddings = () => {
51868
52328
  if (!embeddingPipeline) return;
51869
- embeddingPipeline.indexUnembedded().catch((err31) => {
51870
- logger.error({ error: err31 }, "Embedding indexing failed");
52329
+ embeddingPipeline.indexUnembedded().catch((err32) => {
52330
+ logger.error({ error: err32 }, "Embedding indexing failed");
51871
52331
  });
51872
52332
  };
51873
52333
  const runSummarization = () => {
51874
52334
  if (!summarizationPipeline) return;
51875
- summarizationPipeline.summarizeUnsummarized().catch((err31) => {
51876
- logger.error({ error: err31 }, "Summarization failed");
52335
+ summarizationPipeline.summarizeUnsummarized().catch((err32) => {
52336
+ logger.error({ error: err32 }, "Summarization failed");
51877
52337
  });
51878
52338
  };
51879
52339
  pipeline.indexAll().then(() => {
51880
52340
  runSummarization();
51881
52341
  runEmbeddings();
51882
52342
  runFederationAutoSync(projectRoot, config);
51883
- }).catch((err31) => {
51884
- logger.error({ error: err31 }, "Initial indexing failed");
52343
+ }).catch((err32) => {
52344
+ logger.error({ error: err32 }, "Initial indexing failed");
51885
52345
  });
51886
52346
  await watcher.start(projectRoot, config, async (paths) => {
51887
52347
  await pipeline.indexFiles(paths);
@@ -51934,22 +52394,22 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
51934
52394
  ) : null;
51935
52395
  const runEmbeddings = () => {
51936
52396
  if (!embeddingPipeline) return;
51937
- embeddingPipeline.indexUnembedded().catch((err31) => {
51938
- logger.error({ error: err31 }, "Embedding indexing failed");
52397
+ embeddingPipeline.indexUnembedded().catch((err32) => {
52398
+ logger.error({ error: err32 }, "Embedding indexing failed");
51939
52399
  });
51940
52400
  };
51941
52401
  const runSummarization2 = () => {
51942
52402
  if (!summarizationPipeline2) return;
51943
- summarizationPipeline2.summarizeUnsummarized().catch((err31) => {
51944
- logger.error({ error: err31 }, "Summarization failed");
52403
+ summarizationPipeline2.summarizeUnsummarized().catch((err32) => {
52404
+ logger.error({ error: err32 }, "Summarization failed");
51945
52405
  });
51946
52406
  };
51947
52407
  pipeline.indexAll().then(() => {
51948
52408
  runSummarization2();
51949
52409
  runEmbeddings();
51950
52410
  runFederationAutoSync(projectRoot, config);
51951
- }).catch((err31) => {
51952
- logger.error({ error: err31 }, "Initial indexing failed");
52411
+ }).catch((err32) => {
52412
+ logger.error({ error: err32 }, "Initial indexing failed");
51953
52413
  });
51954
52414
  await watcher.start(projectRoot, config, async (paths) => {
51955
52415
  await pipeline.indexFiles(paths);
@@ -52052,7 +52512,7 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
52052
52512
  });
52053
52513
  });
52054
52514
  program.command("index").description("Index a project directory").argument("<dir>", "Directory to index").option("-f, --force", "Force reindex all files").action(async (dir, opts) => {
52055
- const resolvedDir = path81.resolve(dir);
52515
+ const resolvedDir = path82.resolve(dir);
52056
52516
  if (!fs72.existsSync(resolvedDir)) {
52057
52517
  logger.error({ dir: resolvedDir }, "Directory does not exist");
52058
52518
  process.exit(1);
@@ -52077,13 +52537,13 @@ program.command("index").description("Index a project directory").argument("<dir
52077
52537
  db.close();
52078
52538
  });
52079
52539
  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) => {
52080
- const resolvedFile = path81.resolve(file);
52540
+ const resolvedFile = path82.resolve(file);
52081
52541
  if (!fs72.existsSync(resolvedFile)) {
52082
52542
  process.exit(0);
52083
52543
  }
52084
52544
  let projectRoot;
52085
52545
  try {
52086
- projectRoot = findProjectRoot(path81.dirname(resolvedFile));
52546
+ projectRoot = findProjectRoot(path82.dirname(resolvedFile));
52087
52547
  } catch {
52088
52548
  process.exit(0);
52089
52549
  }