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