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