trace-mcp 1.0.11 → 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 -890
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1206 -727
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -619,8 +619,8 @@ var require_utils = __commonJS({
|
|
|
619
619
|
}
|
|
620
620
|
return output;
|
|
621
621
|
};
|
|
622
|
-
exports.basename = (
|
|
623
|
-
const segs =
|
|
622
|
+
exports.basename = (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.",
|
|
@@ -23878,7 +24357,7 @@ function createServer2(store, registry, config, rootPath) {
|
|
|
23878
24357
|
|
|
23879
24358
|
// src/indexer/plugins/language/php/index.ts
|
|
23880
24359
|
import { createRequire as createRequire2 } from "module";
|
|
23881
|
-
import { ok as ok9, err as
|
|
24360
|
+
import { ok as ok9, err as err12 } from "neverthrow";
|
|
23882
24361
|
|
|
23883
24362
|
// src/indexer/plugins/language/php/helpers.ts
|
|
23884
24363
|
function extractNamespace(rootNode) {
|
|
@@ -24194,7 +24673,7 @@ var PhpLanguagePlugin = class {
|
|
|
24194
24673
|
});
|
|
24195
24674
|
} catch (e) {
|
|
24196
24675
|
const msg = e instanceof Error ? e.message : String(e);
|
|
24197
|
-
return
|
|
24676
|
+
return err12(parseError(filePath, `PHP parse failed: ${msg}`));
|
|
24198
24677
|
}
|
|
24199
24678
|
}
|
|
24200
24679
|
walkTopLevel(root, filePath, namespace, symbols) {
|
|
@@ -24400,7 +24879,7 @@ var PhpLanguagePlugin = class {
|
|
|
24400
24879
|
|
|
24401
24880
|
// src/indexer/plugins/language/typescript/index.ts
|
|
24402
24881
|
import { createRequire as createRequire3 } from "module";
|
|
24403
|
-
import { ok as ok10, err as
|
|
24882
|
+
import { ok as ok10, err as err13 } from "neverthrow";
|
|
24404
24883
|
|
|
24405
24884
|
// src/indexer/plugins/language/typescript/helpers.ts
|
|
24406
24885
|
function makeSymbolId2(relativePath, name, kind, parentName) {
|
|
@@ -24783,7 +25262,7 @@ var TypeScriptLanguagePlugin = class {
|
|
|
24783
25262
|
});
|
|
24784
25263
|
} catch (e) {
|
|
24785
25264
|
const msg = e instanceof Error ? e.message : String(e);
|
|
24786
|
-
return
|
|
25265
|
+
return err13(parseError(filePath, `TypeScript parse failed: ${msg}`));
|
|
24787
25266
|
}
|
|
24788
25267
|
}
|
|
24789
25268
|
walkTopLevel(root, filePath, symbols) {
|
|
@@ -25007,7 +25486,7 @@ var TypeScriptLanguagePlugin = class {
|
|
|
25007
25486
|
// src/indexer/plugins/language/vue/index.ts
|
|
25008
25487
|
import { createRequire as createRequire4 } from "module";
|
|
25009
25488
|
import { parse as parseSFC } from "@vue/compiler-sfc";
|
|
25010
|
-
import { ok as ok11, err as
|
|
25489
|
+
import { ok as ok11, err as err14 } from "neverthrow";
|
|
25011
25490
|
|
|
25012
25491
|
// src/indexer/plugins/language/vue/helpers.ts
|
|
25013
25492
|
var HTML_ELEMENTS = /* @__PURE__ */ new Set([
|
|
@@ -25403,7 +25882,7 @@ var VueLanguagePlugin = class {
|
|
|
25403
25882
|
});
|
|
25404
25883
|
} catch (e) {
|
|
25405
25884
|
const msg = e instanceof Error ? e.message : String(e);
|
|
25406
|
-
return
|
|
25885
|
+
return err14(parseError(filePath, `Vue SFC parse failed: ${msg}`));
|
|
25407
25886
|
}
|
|
25408
25887
|
}
|
|
25409
25888
|
/**
|
|
@@ -25486,7 +25965,7 @@ var VueLanguagePlugin = class {
|
|
|
25486
25965
|
|
|
25487
25966
|
// src/indexer/plugins/language/python/index.ts
|
|
25488
25967
|
import { createRequire as createRequire5 } from "module";
|
|
25489
|
-
import { ok as ok12, err as
|
|
25968
|
+
import { ok as ok12, err as err15 } from "neverthrow";
|
|
25490
25969
|
|
|
25491
25970
|
// src/indexer/plugins/language/python/helpers.ts
|
|
25492
25971
|
function collectNodeTypes3(node) {
|
|
@@ -25877,7 +26356,7 @@ var PythonLanguagePlugin = class {
|
|
|
25877
26356
|
});
|
|
25878
26357
|
} catch (e) {
|
|
25879
26358
|
const msg = e instanceof Error ? e.message : String(e);
|
|
25880
|
-
return
|
|
26359
|
+
return err15(parseError(filePath, `Python parse failed: ${msg}`));
|
|
25881
26360
|
}
|
|
25882
26361
|
}
|
|
25883
26362
|
walkTopLevel(root, filePath, modulePath, symbols) {
|
|
@@ -26127,7 +26606,7 @@ var PythonLanguagePlugin = class {
|
|
|
26127
26606
|
|
|
26128
26607
|
// src/indexer/plugins/language/java/index.ts
|
|
26129
26608
|
import { createRequire as createRequire6 } from "module";
|
|
26130
|
-
import { ok as ok13, err as
|
|
26609
|
+
import { ok as ok13, err as err16 } from "neverthrow";
|
|
26131
26610
|
|
|
26132
26611
|
// src/indexer/plugins/language/java/version-features.ts
|
|
26133
26612
|
var JAVA_SOURCE_PATTERNS = [
|
|
@@ -26474,7 +26953,7 @@ var JavaLanguagePlugin = class {
|
|
|
26474
26953
|
});
|
|
26475
26954
|
} catch (e) {
|
|
26476
26955
|
const msg = e instanceof Error ? e.message : String(e);
|
|
26477
|
-
return
|
|
26956
|
+
return err16(parseError(filePath, `Java parse failed: ${msg}`));
|
|
26478
26957
|
}
|
|
26479
26958
|
}
|
|
26480
26959
|
walkTopLevel(root, filePath, packageName, symbols) {
|
|
@@ -26601,7 +27080,7 @@ var JavaLanguagePlugin = class {
|
|
|
26601
27080
|
};
|
|
26602
27081
|
|
|
26603
27082
|
// src/indexer/plugins/language/kotlin/index.ts
|
|
26604
|
-
import { ok as ok14, err as
|
|
27083
|
+
import { ok as ok14, err as err17 } from "neverthrow";
|
|
26605
27084
|
|
|
26606
27085
|
// src/indexer/plugins/language/kotlin/version-features.ts
|
|
26607
27086
|
var KOTLIN_SOURCE_PATTERNS = [
|
|
@@ -26784,14 +27263,14 @@ var KotlinLanguagePlugin = class {
|
|
|
26784
27263
|
});
|
|
26785
27264
|
} catch (e) {
|
|
26786
27265
|
const msg = e instanceof Error ? e.message : String(e);
|
|
26787
|
-
return
|
|
27266
|
+
return err17(parseError(filePath, `Kotlin parse failed: ${msg}`));
|
|
26788
27267
|
}
|
|
26789
27268
|
}
|
|
26790
27269
|
};
|
|
26791
27270
|
|
|
26792
27271
|
// src/indexer/plugins/language/ruby/index.ts
|
|
26793
27272
|
import { createRequire as createRequire7 } from "module";
|
|
26794
|
-
import { ok as ok15, err as
|
|
27273
|
+
import { ok as ok15, err as err18 } from "neverthrow";
|
|
26795
27274
|
|
|
26796
27275
|
// src/indexer/plugins/language/ruby/version-features.ts
|
|
26797
27276
|
var RUBY_SOURCE_PATTERNS = [
|
|
@@ -27087,7 +27566,7 @@ var RubyLanguagePlugin = class {
|
|
|
27087
27566
|
});
|
|
27088
27567
|
} catch (e) {
|
|
27089
27568
|
const msg = e instanceof Error ? e.message : String(e);
|
|
27090
|
-
return
|
|
27569
|
+
return err18(parseError(filePath, `Ruby parse failed: ${msg}`));
|
|
27091
27570
|
}
|
|
27092
27571
|
}
|
|
27093
27572
|
walkNode(node, filePath, namespaceParts, symbols) {
|
|
@@ -27200,7 +27679,7 @@ var RubyLanguagePlugin = class {
|
|
|
27200
27679
|
|
|
27201
27680
|
// src/indexer/plugins/language/go/index.ts
|
|
27202
27681
|
import { createRequire as createRequire8 } from "module";
|
|
27203
|
-
import { ok as ok16, err as
|
|
27682
|
+
import { ok as ok16, err as err19 } from "neverthrow";
|
|
27204
27683
|
|
|
27205
27684
|
// src/indexer/plugins/language/go/version-features.ts
|
|
27206
27685
|
var GO_SOURCE_PATTERNS = [
|
|
@@ -27435,7 +27914,7 @@ var GoLanguagePlugin = class {
|
|
|
27435
27914
|
});
|
|
27436
27915
|
} catch (e) {
|
|
27437
27916
|
const msg = e instanceof Error ? e.message : String(e);
|
|
27438
|
-
return
|
|
27917
|
+
return err19(parseError(filePath, `Go parse failed: ${msg}`));
|
|
27439
27918
|
}
|
|
27440
27919
|
}
|
|
27441
27920
|
extractFunction(node, filePath, pkg, symbols) {
|
|
@@ -27942,7 +28421,7 @@ function lineAt2(source, offset) {
|
|
|
27942
28421
|
|
|
27943
28422
|
// src/indexer/plugins/language/rust/index.ts
|
|
27944
28423
|
import { createRequire as createRequire9 } from "module";
|
|
27945
|
-
import { ok as ok19, err as
|
|
28424
|
+
import { ok as ok19, err as err20 } from "neverthrow";
|
|
27946
28425
|
|
|
27947
28426
|
// src/indexer/plugins/language/rust/helpers.ts
|
|
27948
28427
|
function makeSymbolId7(filePath, name, kind, parentName) {
|
|
@@ -28186,7 +28665,7 @@ var RustLanguagePlugin = class {
|
|
|
28186
28665
|
});
|
|
28187
28666
|
} catch (e) {
|
|
28188
28667
|
const msg = e instanceof Error ? e.message : String(e);
|
|
28189
|
-
return
|
|
28668
|
+
return err20(parseError(filePath, `Rust parse failed: ${msg}`));
|
|
28190
28669
|
}
|
|
28191
28670
|
}
|
|
28192
28671
|
walkTopLevel(root, filePath, symbols) {
|
|
@@ -28448,7 +28927,7 @@ var RustLanguagePlugin = class {
|
|
|
28448
28927
|
|
|
28449
28928
|
// src/indexer/plugins/language/c/index.ts
|
|
28450
28929
|
import { createRequire as createRequire10 } from "module";
|
|
28451
|
-
import { ok as ok20, err as
|
|
28930
|
+
import { ok as ok20, err as err21 } from "neverthrow";
|
|
28452
28931
|
|
|
28453
28932
|
// src/indexer/plugins/language/c/helpers.ts
|
|
28454
28933
|
function makeSymbolId8(filePath, name, kind, parentName) {
|
|
@@ -28619,7 +29098,7 @@ var CLanguagePlugin = class {
|
|
|
28619
29098
|
});
|
|
28620
29099
|
} catch (e) {
|
|
28621
29100
|
const msg = e instanceof Error ? e.message : String(e);
|
|
28622
|
-
return
|
|
29101
|
+
return err21(parseError(filePath, `C parse failed: ${msg}`));
|
|
28623
29102
|
}
|
|
28624
29103
|
}
|
|
28625
29104
|
walkTopLevel(root, filePath, symbols) {
|
|
@@ -28866,7 +29345,7 @@ var CLanguagePlugin = class {
|
|
|
28866
29345
|
|
|
28867
29346
|
// src/indexer/plugins/language/cpp/index.ts
|
|
28868
29347
|
import { createRequire as createRequire11 } from "module";
|
|
28869
|
-
import { ok as ok21, err as
|
|
29348
|
+
import { ok as ok21, err as err22 } from "neverthrow";
|
|
28870
29349
|
|
|
28871
29350
|
// src/indexer/plugins/language/cpp/helpers.ts
|
|
28872
29351
|
function makeSymbolId9(filePath, name, kind, parentName) {
|
|
@@ -29088,7 +29567,7 @@ var CppLanguagePlugin = class {
|
|
|
29088
29567
|
});
|
|
29089
29568
|
} catch (e) {
|
|
29090
29569
|
const msg = e instanceof Error ? e.message : String(e);
|
|
29091
|
-
return
|
|
29570
|
+
return err22(parseError(filePath, `C++ parse failed: ${msg}`));
|
|
29092
29571
|
}
|
|
29093
29572
|
}
|
|
29094
29573
|
/**
|
|
@@ -29485,7 +29964,7 @@ var CppLanguagePlugin = class {
|
|
|
29485
29964
|
|
|
29486
29965
|
// src/indexer/plugins/language/csharp/index.ts
|
|
29487
29966
|
import { createRequire as createRequire12 } from "module";
|
|
29488
|
-
import { ok as ok22, err as
|
|
29967
|
+
import { ok as ok22, err as err23 } from "neverthrow";
|
|
29489
29968
|
|
|
29490
29969
|
// src/indexer/plugins/language/csharp/helpers.ts
|
|
29491
29970
|
function makeSymbolId10(relativePath, name, kind, parentName) {
|
|
@@ -29890,7 +30369,7 @@ var CSharpLanguagePlugin = class {
|
|
|
29890
30369
|
});
|
|
29891
30370
|
} catch (e) {
|
|
29892
30371
|
const msg = e instanceof Error ? e.message : String(e);
|
|
29893
|
-
return
|
|
30372
|
+
return err23(parseError(filePath, `C# parse failed: ${msg}`));
|
|
29894
30373
|
}
|
|
29895
30374
|
}
|
|
29896
30375
|
/** Walk children of a node, dispatching to extraction methods by node type. */
|
|
@@ -30494,7 +30973,7 @@ var DartLanguagePlugin = class {
|
|
|
30494
30973
|
|
|
30495
30974
|
// src/indexer/plugins/language/scala/index.ts
|
|
30496
30975
|
import { createRequire as createRequire13 } from "module";
|
|
30497
|
-
import { ok as ok25, err as
|
|
30976
|
+
import { ok as ok25, err as err24 } from "neverthrow";
|
|
30498
30977
|
|
|
30499
30978
|
// src/indexer/plugins/language/scala/helpers.ts
|
|
30500
30979
|
function makeSymbolId12(filePath, name, kind, parentName) {
|
|
@@ -30921,7 +31400,7 @@ var ScalaLanguagePlugin = class {
|
|
|
30921
31400
|
});
|
|
30922
31401
|
} catch (e) {
|
|
30923
31402
|
const msg = e instanceof Error ? e.message : String(e);
|
|
30924
|
-
return
|
|
31403
|
+
return err24(parseError(filePath, `Scala parse failed: ${msg}`));
|
|
30925
31404
|
}
|
|
30926
31405
|
}
|
|
30927
31406
|
walkTopLevel(node, filePath, fqnParts, symbols) {
|
|
@@ -32509,7 +32988,7 @@ var XmlLanguagePlugin = class {
|
|
|
32509
32988
|
|
|
32510
32989
|
// src/indexer/plugins/integration/orm/prisma/index.ts
|
|
32511
32990
|
import fs26 from "fs";
|
|
32512
|
-
import
|
|
32991
|
+
import path37 from "path";
|
|
32513
32992
|
import { ok as ok28 } from "neverthrow";
|
|
32514
32993
|
var PrismaLanguagePlugin = class {
|
|
32515
32994
|
manifest = {
|
|
@@ -32543,8 +33022,8 @@ var PrismaPlugin = class {
|
|
|
32543
33022
|
if ("@prisma/client" in deps || "prisma" in deps) return true;
|
|
32544
33023
|
try {
|
|
32545
33024
|
const candidates = [
|
|
32546
|
-
|
|
32547
|
-
|
|
33025
|
+
path37.join(ctx.rootPath, "prisma", "schema.prisma"),
|
|
33026
|
+
path37.join(ctx.rootPath, "schema.prisma")
|
|
32548
33027
|
];
|
|
32549
33028
|
return candidates.some((p4) => fs26.existsSync(p4));
|
|
32550
33029
|
} catch {
|
|
@@ -35476,7 +35955,7 @@ function createAllLanguagePlugins() {
|
|
|
35476
35955
|
|
|
35477
35956
|
// src/indexer/plugins/integration/framework/laravel/index.ts
|
|
35478
35957
|
import fs28 from "fs";
|
|
35479
|
-
import
|
|
35958
|
+
import path38 from "path";
|
|
35480
35959
|
import { ok as ok34 } from "neverthrow";
|
|
35481
35960
|
|
|
35482
35961
|
// src/indexer/plugins/integration/framework/laravel/routes.ts
|
|
@@ -37278,7 +37757,7 @@ var LaravelPlugin = class {
|
|
|
37278
37757
|
deps = ctx.composerJson.require;
|
|
37279
37758
|
} else {
|
|
37280
37759
|
try {
|
|
37281
|
-
const composerPath =
|
|
37760
|
+
const composerPath = path38.join(ctx.rootPath, "composer.json");
|
|
37282
37761
|
const content = fs28.readFileSync(composerPath, "utf-8");
|
|
37283
37762
|
const json = JSON.parse(content);
|
|
37284
37763
|
deps = json.require;
|
|
@@ -37952,7 +38431,7 @@ var LaravelPlugin = class {
|
|
|
37952
38431
|
|
|
37953
38432
|
// src/indexer/plugins/integration/framework/django/index.ts
|
|
37954
38433
|
import fs29 from "fs";
|
|
37955
|
-
import
|
|
38434
|
+
import path39 from "path";
|
|
37956
38435
|
import { ok as ok35 } from "neverthrow";
|
|
37957
38436
|
|
|
37958
38437
|
// src/indexer/plugins/integration/framework/django/models.ts
|
|
@@ -38455,16 +38934,16 @@ var DjangoPlugin = class {
|
|
|
38455
38934
|
dependencies: []
|
|
38456
38935
|
};
|
|
38457
38936
|
detect(ctx) {
|
|
38458
|
-
if (ctx.configFiles.some((f) =>
|
|
38937
|
+
if (ctx.configFiles.some((f) => path39.basename(f) === "manage.py")) {
|
|
38459
38938
|
return true;
|
|
38460
38939
|
}
|
|
38461
38940
|
try {
|
|
38462
|
-
fs29.accessSync(
|
|
38941
|
+
fs29.accessSync(path39.join(ctx.rootPath, "manage.py"), fs29.constants.F_OK);
|
|
38463
38942
|
return true;
|
|
38464
38943
|
} catch {
|
|
38465
38944
|
}
|
|
38466
38945
|
try {
|
|
38467
|
-
const pyprojectPath =
|
|
38946
|
+
const pyprojectPath = path39.join(ctx.rootPath, "pyproject.toml");
|
|
38468
38947
|
const content = fs29.readFileSync(pyprojectPath, "utf-8");
|
|
38469
38948
|
if (/django/i.test(content) && /dependencies|requires/i.test(content)) {
|
|
38470
38949
|
return true;
|
|
@@ -38472,7 +38951,7 @@ var DjangoPlugin = class {
|
|
|
38472
38951
|
} catch {
|
|
38473
38952
|
}
|
|
38474
38953
|
try {
|
|
38475
|
-
const reqPath =
|
|
38954
|
+
const reqPath = path39.join(ctx.rootPath, "requirements.txt");
|
|
38476
38955
|
const content = fs29.readFileSync(reqPath, "utf-8");
|
|
38477
38956
|
if (/^django(?:==|>=|<=|~=|!=|\[|\s|$)/im.test(content)) {
|
|
38478
38957
|
return true;
|
|
@@ -38480,7 +38959,7 @@ var DjangoPlugin = class {
|
|
|
38480
38959
|
} catch {
|
|
38481
38960
|
}
|
|
38482
38961
|
try {
|
|
38483
|
-
const setupPath =
|
|
38962
|
+
const setupPath = path39.join(ctx.rootPath, "setup.py");
|
|
38484
38963
|
const content = fs29.readFileSync(setupPath, "utf-8");
|
|
38485
38964
|
if (/['"]django['"]/i.test(content)) {
|
|
38486
38965
|
return true;
|
|
@@ -38556,7 +39035,7 @@ var DjangoPlugin = class {
|
|
|
38556
39035
|
let source;
|
|
38557
39036
|
try {
|
|
38558
39037
|
source = fs29.readFileSync(
|
|
38559
|
-
|
|
39038
|
+
path39.resolve(ctx.rootPath, file.path),
|
|
38560
39039
|
"utf-8"
|
|
38561
39040
|
);
|
|
38562
39041
|
} catch {
|
|
@@ -39150,8 +39629,8 @@ var SpringPlugin = class {
|
|
|
39150
39629
|
const re = new RegExp(`@${annotation}\\s*(?:\\(\\s*(?:value\\s*=\\s*)?["']([^"']*)["']\\s*\\))?`, "g");
|
|
39151
39630
|
let m;
|
|
39152
39631
|
while ((m = re.exec(source)) !== null) {
|
|
39153
|
-
const
|
|
39154
|
-
const uri = normalizePath(classPrefix + "/" +
|
|
39632
|
+
const path83 = m[1] ?? "";
|
|
39633
|
+
const uri = normalizePath(classPrefix + "/" + path83);
|
|
39155
39634
|
result.routes.push({ method, uri, line: source.substring(0, m.index).split("\n").length });
|
|
39156
39635
|
}
|
|
39157
39636
|
}
|
|
@@ -39211,13 +39690,13 @@ var SpringPlugin = class {
|
|
|
39211
39690
|
}
|
|
39212
39691
|
}
|
|
39213
39692
|
};
|
|
39214
|
-
function normalizePath(
|
|
39215
|
-
return "/" +
|
|
39693
|
+
function normalizePath(path83) {
|
|
39694
|
+
return "/" + path83.replace(/\/+/g, "/").replace(/^\/|\/$/g, "");
|
|
39216
39695
|
}
|
|
39217
39696
|
|
|
39218
39697
|
// src/indexer/plugins/integration/framework/express/index.ts
|
|
39219
39698
|
import fs30 from "fs";
|
|
39220
|
-
import
|
|
39699
|
+
import path40 from "path";
|
|
39221
39700
|
var ROUTE_RE = /(?:app|router)\s*\.\s*(get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
39222
39701
|
var MIDDLEWARE_RE = /(?:app|router)\s*\.\s*use\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
39223
39702
|
var GLOBAL_MIDDLEWARE_RE = /(?:app|router)\s*\.\s*use\s*\(\s*([A-Za-z][\w.]*(?:\s*\(\s*[^)]*\))?)\s*[,)]/g;
|
|
@@ -39287,7 +39766,7 @@ var ExpressPlugin = class {
|
|
|
39287
39766
|
if ("express" in deps) return true;
|
|
39288
39767
|
}
|
|
39289
39768
|
try {
|
|
39290
|
-
const pkgPath =
|
|
39769
|
+
const pkgPath = path40.join(ctx.rootPath, "package.json");
|
|
39291
39770
|
const content = fs30.readFileSync(pkgPath, "utf-8");
|
|
39292
39771
|
const pkg = JSON.parse(content);
|
|
39293
39772
|
const deps = {
|
|
@@ -39349,8 +39828,8 @@ var ExpressPlugin = class {
|
|
|
39349
39828
|
// src/indexer/plugins/integration/framework/fastapi/index.ts
|
|
39350
39829
|
import { createRequire as createRequire14 } from "module";
|
|
39351
39830
|
import fs31 from "fs";
|
|
39352
|
-
import
|
|
39353
|
-
import { ok as ok38, err as
|
|
39831
|
+
import path41 from "path";
|
|
39832
|
+
import { ok as ok38, err as err25 } from "neverthrow";
|
|
39354
39833
|
var require15 = createRequire14(import.meta.url);
|
|
39355
39834
|
var Parser13 = require15("tree-sitter");
|
|
39356
39835
|
var PythonGrammar2 = require15("tree-sitter-python");
|
|
@@ -39363,14 +39842,14 @@ function hasPythonDep(ctx, pkg) {
|
|
|
39363
39842
|
}
|
|
39364
39843
|
if (ctx.requirementsTxt?.includes(lowerPkg)) return true;
|
|
39365
39844
|
try {
|
|
39366
|
-
const pyprojectPath =
|
|
39845
|
+
const pyprojectPath = path41.join(ctx.rootPath, "pyproject.toml");
|
|
39367
39846
|
const content = fs31.readFileSync(pyprojectPath, "utf-8");
|
|
39368
39847
|
const re = new RegExp(`["']${escapeRegExp(pkg)}[>=<\\[!~\\s"']`, "i");
|
|
39369
39848
|
if (re.test(content)) return true;
|
|
39370
39849
|
} catch {
|
|
39371
39850
|
}
|
|
39372
39851
|
try {
|
|
39373
|
-
const reqPath =
|
|
39852
|
+
const reqPath = path41.join(ctx.rootPath, "requirements.txt");
|
|
39374
39853
|
const content = fs31.readFileSync(reqPath, "utf-8");
|
|
39375
39854
|
const re = new RegExp(`^${escapeRegExp(pkg)}\\b`, "im");
|
|
39376
39855
|
if (re.test(content)) return true;
|
|
@@ -39423,7 +39902,7 @@ var FastAPIPlugin = class {
|
|
|
39423
39902
|
this.extractRoutes(root, source, filePath, result);
|
|
39424
39903
|
this.extractRouterMounts(root, source, filePath, result);
|
|
39425
39904
|
} catch (e) {
|
|
39426
|
-
return
|
|
39905
|
+
return err25(parseError(filePath, `FastAPI parse error: ${e instanceof Error ? e.message : String(e)}`));
|
|
39427
39906
|
}
|
|
39428
39907
|
if (result.routes.length > 0 || result.edges.length > 0) {
|
|
39429
39908
|
result.frameworkRole = "fastapi_routes";
|
|
@@ -39666,8 +40145,8 @@ var FastAPIPlugin = class {
|
|
|
39666
40145
|
// src/indexer/plugins/integration/framework/flask/index.ts
|
|
39667
40146
|
import { createRequire as createRequire15 } from "module";
|
|
39668
40147
|
import fs32 from "fs";
|
|
39669
|
-
import
|
|
39670
|
-
import { ok as ok39, err as
|
|
40148
|
+
import path42 from "path";
|
|
40149
|
+
import { ok as ok39, err as err26 } from "neverthrow";
|
|
39671
40150
|
var require16 = createRequire15(import.meta.url);
|
|
39672
40151
|
var Parser14 = require16("tree-sitter");
|
|
39673
40152
|
var PythonGrammar3 = require16("tree-sitter-python");
|
|
@@ -39680,14 +40159,14 @@ function hasPythonDep2(ctx, pkg) {
|
|
|
39680
40159
|
}
|
|
39681
40160
|
if (ctx.requirementsTxt?.includes(lowerPkg)) return true;
|
|
39682
40161
|
try {
|
|
39683
|
-
const pyprojectPath =
|
|
40162
|
+
const pyprojectPath = path42.join(ctx.rootPath, "pyproject.toml");
|
|
39684
40163
|
const content = fs32.readFileSync(pyprojectPath, "utf-8");
|
|
39685
40164
|
const re = new RegExp(`["']${escapeRegExp(pkg)}[>=<\\[!~\\s"']`, "i");
|
|
39686
40165
|
if (re.test(content)) return true;
|
|
39687
40166
|
} catch {
|
|
39688
40167
|
}
|
|
39689
40168
|
try {
|
|
39690
|
-
const reqPath =
|
|
40169
|
+
const reqPath = path42.join(ctx.rootPath, "requirements.txt");
|
|
39691
40170
|
const content = fs32.readFileSync(reqPath, "utf-8");
|
|
39692
40171
|
const re = new RegExp(`^${escapeRegExp(pkg)}\\b`, "im");
|
|
39693
40172
|
if (re.test(content)) return true;
|
|
@@ -39741,7 +40220,7 @@ var FlaskPlugin = class {
|
|
|
39741
40220
|
this.extractBeforeRequestHooks(root, source, filePath, result);
|
|
39742
40221
|
this.extractErrorHandlers(root, source, filePath, result);
|
|
39743
40222
|
} catch (e) {
|
|
39744
|
-
return
|
|
40223
|
+
return err26(parseError(filePath, `Flask parse error: ${e instanceof Error ? e.message : String(e)}`));
|
|
39745
40224
|
}
|
|
39746
40225
|
if (result.routes.length > 0 || result.edges.length > 0) {
|
|
39747
40226
|
result.frameworkRole = "flask_routes";
|
|
@@ -39965,7 +40444,7 @@ var FlaskPlugin = class {
|
|
|
39965
40444
|
|
|
39966
40445
|
// src/indexer/plugins/integration/framework/hono/index.ts
|
|
39967
40446
|
import fs33 from "fs";
|
|
39968
|
-
import
|
|
40447
|
+
import path43 from "path";
|
|
39969
40448
|
var ROUTE_RE2 = /(?:app|router|hono)\s*\.\s*(get|post|put|delete|patch|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
39970
40449
|
var ON_RE = /(?:app|router|hono)\s*\.\s*on\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*['"`]([^'"`]+)['"`]/g;
|
|
39971
40450
|
var ROUTE_GROUP_RE = /(?:app|router|hono)\s*\.\s*route\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*([A-Za-z][\w]*)/g;
|
|
@@ -40035,7 +40514,7 @@ var HonoPlugin = class {
|
|
|
40035
40514
|
if ("hono" in deps) return true;
|
|
40036
40515
|
}
|
|
40037
40516
|
try {
|
|
40038
|
-
const pkgPath =
|
|
40517
|
+
const pkgPath = path43.join(ctx.rootPath, "package.json");
|
|
40039
40518
|
const content = fs33.readFileSync(pkgPath, "utf-8");
|
|
40040
40519
|
const pkg = JSON.parse(content);
|
|
40041
40520
|
const deps = {
|
|
@@ -40091,7 +40570,7 @@ var HonoPlugin = class {
|
|
|
40091
40570
|
|
|
40092
40571
|
// src/indexer/plugins/integration/framework/fastify/index.ts
|
|
40093
40572
|
import fs34 from "fs";
|
|
40094
|
-
import
|
|
40573
|
+
import path44 from "path";
|
|
40095
40574
|
var SHORTHAND_ROUTE_RE = /(?:fastify|app|server|instance)\s*\.\s*(get|post|put|delete|patch|head|options)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
40096
40575
|
var ROUTE_OBJECT_RE = /\.route\s*\(\s*\{[^}]*?method\s*:\s*['"`]([^'"`]+)['"`][^}]*?url\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
40097
40576
|
var HOOK_RE = /\.addHook\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -40150,7 +40629,7 @@ var FastifyPlugin = class {
|
|
|
40150
40629
|
if ("fastify" in deps) return true;
|
|
40151
40630
|
}
|
|
40152
40631
|
try {
|
|
40153
|
-
const pkgPath =
|
|
40632
|
+
const pkgPath = path44.join(ctx.rootPath, "package.json");
|
|
40154
40633
|
const content = fs34.readFileSync(pkgPath, "utf-8");
|
|
40155
40634
|
const pkg = JSON.parse(content);
|
|
40156
40635
|
const deps = {
|
|
@@ -40205,7 +40684,7 @@ var FastifyPlugin = class {
|
|
|
40205
40684
|
|
|
40206
40685
|
// src/indexer/plugins/integration/framework/nuxt/index.ts
|
|
40207
40686
|
import fs35 from "fs";
|
|
40208
|
-
import
|
|
40687
|
+
import path45 from "path";
|
|
40209
40688
|
function filePathToRoute(filePath, srcDir = ".") {
|
|
40210
40689
|
const pagesPrefix = srcDir === "." ? "pages/" : `${srcDir}/pages/`;
|
|
40211
40690
|
let route = filePath.replace(new RegExp(`^${pagesPrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").replace(/\.vue$/, "");
|
|
@@ -40272,7 +40751,7 @@ var NuxtPlugin = class {
|
|
|
40272
40751
|
}
|
|
40273
40752
|
}
|
|
40274
40753
|
try {
|
|
40275
|
-
const configPath =
|
|
40754
|
+
const configPath = path45.join(ctx.rootPath, "nuxt.config.ts");
|
|
40276
40755
|
const configContent = fs35.readFileSync(configPath, "utf-8");
|
|
40277
40756
|
if (/compatibilityVersion\s*:\s*4/.test(configContent)) {
|
|
40278
40757
|
return true;
|
|
@@ -40280,7 +40759,7 @@ var NuxtPlugin = class {
|
|
|
40280
40759
|
} catch {
|
|
40281
40760
|
}
|
|
40282
40761
|
try {
|
|
40283
|
-
const appPagesDir =
|
|
40762
|
+
const appPagesDir = path45.join(ctx.rootPath, "app", "pages");
|
|
40284
40763
|
fs35.accessSync(appPagesDir);
|
|
40285
40764
|
return true;
|
|
40286
40765
|
} catch {
|
|
@@ -40304,7 +40783,7 @@ var NuxtPlugin = class {
|
|
|
40304
40783
|
}
|
|
40305
40784
|
}
|
|
40306
40785
|
try {
|
|
40307
|
-
const configPath =
|
|
40786
|
+
const configPath = path45.join(ctx.rootPath, "nuxt.config.ts");
|
|
40308
40787
|
fs35.accessSync(configPath);
|
|
40309
40788
|
this.nuxt4 = this.isNuxt4(ctx);
|
|
40310
40789
|
this.srcDir = this.nuxt4 ? "app" : ".";
|
|
@@ -40312,7 +40791,7 @@ var NuxtPlugin = class {
|
|
|
40312
40791
|
} catch {
|
|
40313
40792
|
}
|
|
40314
40793
|
try {
|
|
40315
|
-
const pkgPath =
|
|
40794
|
+
const pkgPath = path45.join(ctx.rootPath, "package.json");
|
|
40316
40795
|
const content = fs35.readFileSync(pkgPath, "utf-8");
|
|
40317
40796
|
const pkg = JSON.parse(content);
|
|
40318
40797
|
const deps = {
|
|
@@ -40414,7 +40893,7 @@ var NuxtPlugin = class {
|
|
|
40414
40893
|
for (const file of vueFiles) {
|
|
40415
40894
|
let source;
|
|
40416
40895
|
try {
|
|
40417
|
-
source = fs35.readFileSync(
|
|
40896
|
+
source = fs35.readFileSync(path45.resolve(ctx.rootPath, file.path), "utf-8");
|
|
40418
40897
|
} catch {
|
|
40419
40898
|
continue;
|
|
40420
40899
|
}
|
|
@@ -40452,7 +40931,7 @@ var NuxtPlugin = class {
|
|
|
40452
40931
|
|
|
40453
40932
|
// src/indexer/plugins/integration/framework/nextjs/index.ts
|
|
40454
40933
|
import fs36 from "fs";
|
|
40455
|
-
import
|
|
40934
|
+
import path46 from "path";
|
|
40456
40935
|
var PAGE_EXTENSIONS = /\.(tsx|ts|jsx|js)$/;
|
|
40457
40936
|
var APP_ROUTER_FILES = ["page", "layout", "loading", "error", "not-found", "route", "template", "default"];
|
|
40458
40937
|
var API_ROUTE_EXPORTS_RE = /export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/g;
|
|
@@ -40491,7 +40970,7 @@ function pagesRouterPathToRoute(filePath) {
|
|
|
40491
40970
|
return "/" + routeSegments.join("/");
|
|
40492
40971
|
}
|
|
40493
40972
|
function getAppRouterFileType(filePath) {
|
|
40494
|
-
const basename =
|
|
40973
|
+
const basename = path46.basename(filePath).replace(PAGE_EXTENSIONS, "");
|
|
40495
40974
|
if (APP_ROUTER_FILES.includes(basename)) {
|
|
40496
40975
|
return basename;
|
|
40497
40976
|
}
|
|
@@ -40556,7 +41035,7 @@ var NextJSPlugin = class {
|
|
|
40556
41035
|
if ("next" in deps) return true;
|
|
40557
41036
|
}
|
|
40558
41037
|
try {
|
|
40559
|
-
const pkgPath =
|
|
41038
|
+
const pkgPath = path46.join(ctx.rootPath, "package.json");
|
|
40560
41039
|
const content = fs36.readFileSync(pkgPath, "utf-8");
|
|
40561
41040
|
const pkg = JSON.parse(content);
|
|
40562
41041
|
const deps = {
|
|
@@ -40663,15 +41142,15 @@ var NextJSPlugin = class {
|
|
|
40663
41142
|
const edges = [];
|
|
40664
41143
|
const allFiles = ctx.getAllFiles();
|
|
40665
41144
|
const layouts = allFiles.filter((f) => {
|
|
40666
|
-
const basename =
|
|
41145
|
+
const basename = path46.basename(f.path).replace(PAGE_EXTENSIONS, "");
|
|
40667
41146
|
return f.path.startsWith("app/") && basename === "layout";
|
|
40668
41147
|
});
|
|
40669
41148
|
const pages = allFiles.filter((f) => {
|
|
40670
|
-
const basename =
|
|
41149
|
+
const basename = path46.basename(f.path).replace(PAGE_EXTENSIONS, "");
|
|
40671
41150
|
return f.path.startsWith("app/") && basename === "page";
|
|
40672
41151
|
});
|
|
40673
41152
|
for (const layout of layouts) {
|
|
40674
|
-
const layoutDir =
|
|
41153
|
+
const layoutDir = path46.dirname(layout.path);
|
|
40675
41154
|
const layoutSymbols = ctx.getSymbolsByFile(layout.id);
|
|
40676
41155
|
const layoutSym = layoutSymbols.find((s) => s.kind === "function" || s.kind === "class");
|
|
40677
41156
|
if (!layoutSym) continue;
|
|
@@ -40701,7 +41180,7 @@ var NextJSPlugin = class {
|
|
|
40701
41180
|
if (slotIdx < 1) continue;
|
|
40702
41181
|
const parentDir = segments.slice(0, slotIdx).join("/");
|
|
40703
41182
|
const parentLayout = allFiles.find(
|
|
40704
|
-
(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))
|
|
40705
41184
|
);
|
|
40706
41185
|
if (parentLayout) {
|
|
40707
41186
|
const layoutSymbols = ctx.getSymbolsByFile(parentLayout.id);
|
|
@@ -40753,11 +41232,11 @@ var NextJSPlugin = class {
|
|
|
40753
41232
|
}
|
|
40754
41233
|
}
|
|
40755
41234
|
const templates = allFiles.filter((f) => {
|
|
40756
|
-
const basename =
|
|
41235
|
+
const basename = path46.basename(f.path).replace(PAGE_EXTENSIONS, "");
|
|
40757
41236
|
return f.path.startsWith("app/") && basename === "template";
|
|
40758
41237
|
});
|
|
40759
41238
|
for (const template of templates) {
|
|
40760
|
-
const templateDir =
|
|
41239
|
+
const templateDir = path46.dirname(template.path);
|
|
40761
41240
|
const templateSymbols = ctx.getSymbolsByFile(template.id);
|
|
40762
41241
|
const templateSym = templateSymbols.find((s) => s.kind === "function" || s.kind === "class");
|
|
40763
41242
|
if (!templateSym) continue;
|
|
@@ -40782,7 +41261,7 @@ var NextJSPlugin = class {
|
|
|
40782
41261
|
for (const file of pagesRouterFiles) {
|
|
40783
41262
|
let source;
|
|
40784
41263
|
try {
|
|
40785
|
-
source = fs36.readFileSync(
|
|
41264
|
+
source = fs36.readFileSync(path46.resolve(ctx.rootPath, file.path), "utf-8");
|
|
40786
41265
|
} catch {
|
|
40787
41266
|
continue;
|
|
40788
41267
|
}
|
|
@@ -40820,7 +41299,7 @@ var NextJSPlugin = class {
|
|
|
40820
41299
|
|
|
40821
41300
|
// src/indexer/plugins/integration/framework/gin/index.ts
|
|
40822
41301
|
import fs37 from "fs";
|
|
40823
|
-
import
|
|
41302
|
+
import path47 from "path";
|
|
40824
41303
|
var ROUTE_RE3 = /\b(\w+)\s*\.\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|Any)\s*\(\s*"([^"]+)"/g;
|
|
40825
41304
|
var GROUP_RE = /\b(\w+)\s*\.\s*Group\s*\(\s*"([^"]+)"/g;
|
|
40826
41305
|
var MIDDLEWARE_RE2 = /\b(\w+)\s*\.\s*Use\s*\(\s*(\w[\w.]*)/g;
|
|
@@ -40876,13 +41355,13 @@ var GinPlugin = class {
|
|
|
40876
41355
|
};
|
|
40877
41356
|
detect(ctx) {
|
|
40878
41357
|
try {
|
|
40879
|
-
const goModPath =
|
|
41358
|
+
const goModPath = path47.join(ctx.rootPath, "go.mod");
|
|
40880
41359
|
const content = fs37.readFileSync(goModPath, "utf-8");
|
|
40881
41360
|
if (content.includes("github.com/gin-gonic/gin")) return true;
|
|
40882
41361
|
} catch {
|
|
40883
41362
|
}
|
|
40884
41363
|
try {
|
|
40885
|
-
const goSumPath =
|
|
41364
|
+
const goSumPath = path47.join(ctx.rootPath, "go.sum");
|
|
40886
41365
|
const content = fs37.readFileSync(goSumPath, "utf-8");
|
|
40887
41366
|
return content.includes("github.com/gin-gonic/gin");
|
|
40888
41367
|
} catch {
|
|
@@ -40945,7 +41424,7 @@ var GinPlugin = class {
|
|
|
40945
41424
|
|
|
40946
41425
|
// src/indexer/plugins/integration/framework/echo/index.ts
|
|
40947
41426
|
import fs38 from "fs";
|
|
40948
|
-
import
|
|
41427
|
+
import path48 from "path";
|
|
40949
41428
|
var ROUTE_RE4 = /\b(\w+)\s*\.\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE)\s*\(\s*"([^"]+)"/g;
|
|
40950
41429
|
var GROUP_RE2 = /\b(\w+)\s*\.\s*Group\s*\(\s*"([^"]+)"/g;
|
|
40951
41430
|
var MIDDLEWARE_RE3 = /\b(\w+)\s*\.\s*(?:Use|Pre)\s*\(\s*(\w[\w.]*)/g;
|
|
@@ -41001,13 +41480,13 @@ var EchoPlugin = class {
|
|
|
41001
41480
|
};
|
|
41002
41481
|
detect(ctx) {
|
|
41003
41482
|
try {
|
|
41004
|
-
const goModPath =
|
|
41483
|
+
const goModPath = path48.join(ctx.rootPath, "go.mod");
|
|
41005
41484
|
const content = fs38.readFileSync(goModPath, "utf-8");
|
|
41006
41485
|
if (content.includes("github.com/labstack/echo")) return true;
|
|
41007
41486
|
} catch {
|
|
41008
41487
|
}
|
|
41009
41488
|
try {
|
|
41010
|
-
const goSumPath =
|
|
41489
|
+
const goSumPath = path48.join(ctx.rootPath, "go.sum");
|
|
41011
41490
|
const content = fs38.readFileSync(goSumPath, "utf-8");
|
|
41012
41491
|
return content.includes("github.com/labstack/echo");
|
|
41013
41492
|
} catch {
|
|
@@ -41176,7 +41655,7 @@ function extractTypeORMEntity(source, filePath) {
|
|
|
41176
41655
|
|
|
41177
41656
|
// src/indexer/plugins/integration/orm/sequelize/index.ts
|
|
41178
41657
|
import fs39 from "fs";
|
|
41179
|
-
import
|
|
41658
|
+
import path49 from "path";
|
|
41180
41659
|
import { ok as ok41 } from "neverthrow";
|
|
41181
41660
|
var SequelizePlugin = class {
|
|
41182
41661
|
manifest = {
|
|
@@ -41193,7 +41672,7 @@ var SequelizePlugin = class {
|
|
|
41193
41672
|
};
|
|
41194
41673
|
if ("sequelize" in deps || "sequelize-typescript" in deps) return true;
|
|
41195
41674
|
try {
|
|
41196
|
-
const pkgPath =
|
|
41675
|
+
const pkgPath = path49.join(ctx.rootPath, "package.json");
|
|
41197
41676
|
const content = fs39.readFileSync(pkgPath, "utf-8");
|
|
41198
41677
|
const json = JSON.parse(content);
|
|
41199
41678
|
const allDeps = { ...json.dependencies, ...json.devDependencies };
|
|
@@ -41246,7 +41725,7 @@ var SequelizePlugin = class {
|
|
|
41246
41725
|
return ok41([]);
|
|
41247
41726
|
}
|
|
41248
41727
|
isMigrationFile(filePath) {
|
|
41249
|
-
return /migrations?\//.test(filePath) && /\d/.test(
|
|
41728
|
+
return /migrations?\//.test(filePath) && /\d/.test(path49.basename(filePath));
|
|
41250
41729
|
}
|
|
41251
41730
|
};
|
|
41252
41731
|
function extractSequelizeModel(source, filePath) {
|
|
@@ -41515,7 +41994,7 @@ function extractScopes2(source, className) {
|
|
|
41515
41994
|
|
|
41516
41995
|
// src/indexer/plugins/integration/orm/mongoose/index.ts
|
|
41517
41996
|
import fs40 from "fs";
|
|
41518
|
-
import
|
|
41997
|
+
import path50 from "path";
|
|
41519
41998
|
import { ok as ok42 } from "neverthrow";
|
|
41520
41999
|
var MongoosePlugin = class {
|
|
41521
42000
|
manifest = {
|
|
@@ -41532,7 +42011,7 @@ var MongoosePlugin = class {
|
|
|
41532
42011
|
};
|
|
41533
42012
|
if ("mongoose" in deps) return true;
|
|
41534
42013
|
try {
|
|
41535
|
-
const pkgPath =
|
|
42014
|
+
const pkgPath = path50.join(ctx.rootPath, "package.json");
|
|
41536
42015
|
const content = fs40.readFileSync(pkgPath, "utf-8");
|
|
41537
42016
|
const json = JSON.parse(content);
|
|
41538
42017
|
const allDeps = { ...json.dependencies, ...json.devDependencies };
|
|
@@ -41824,8 +42303,8 @@ function extractRefs(fields, sourceModelName) {
|
|
|
41824
42303
|
// src/indexer/plugins/integration/orm/sqlalchemy/index.ts
|
|
41825
42304
|
import { createRequire as createRequire16 } from "module";
|
|
41826
42305
|
import fs41 from "fs";
|
|
41827
|
-
import
|
|
41828
|
-
import { ok as ok43, err as
|
|
42306
|
+
import path51 from "path";
|
|
42307
|
+
import { ok as ok43, err as err27 } from "neverthrow";
|
|
41829
42308
|
var require17 = createRequire16(import.meta.url);
|
|
41830
42309
|
var Parser15 = require17("tree-sitter");
|
|
41831
42310
|
var PythonGrammar4 = require17("tree-sitter-python");
|
|
@@ -41844,14 +42323,14 @@ function hasPythonDep3(ctx, pkg) {
|
|
|
41844
42323
|
}
|
|
41845
42324
|
if (ctx.requirementsTxt?.includes(lowerPkg)) return true;
|
|
41846
42325
|
try {
|
|
41847
|
-
const pyprojectPath =
|
|
42326
|
+
const pyprojectPath = path51.join(ctx.rootPath, "pyproject.toml");
|
|
41848
42327
|
const content = fs41.readFileSync(pyprojectPath, "utf-8");
|
|
41849
42328
|
const re = new RegExp(`["']${escapeRegExp(pkg)}[>=<\\[!~\\s"']`, "i");
|
|
41850
42329
|
if (re.test(content)) return true;
|
|
41851
42330
|
} catch {
|
|
41852
42331
|
}
|
|
41853
42332
|
try {
|
|
41854
|
-
const reqPath =
|
|
42333
|
+
const reqPath = path51.join(ctx.rootPath, "requirements.txt");
|
|
41855
42334
|
const content = fs41.readFileSync(reqPath, "utf-8");
|
|
41856
42335
|
const re = new RegExp(`^${escapeRegExp(pkg)}\\b`, "im");
|
|
41857
42336
|
if (re.test(content)) return true;
|
|
@@ -41902,7 +42381,7 @@ var SQLAlchemyPlugin = class {
|
|
|
41902
42381
|
const tree = parser.parse(source);
|
|
41903
42382
|
this.extractAlembicMigrations(tree.rootNode, source, filePath, result);
|
|
41904
42383
|
} catch (e) {
|
|
41905
|
-
return
|
|
42384
|
+
return err27(parseError(filePath, `SQLAlchemy migration parse error: ${e instanceof Error ? e.message : String(e)}`));
|
|
41906
42385
|
}
|
|
41907
42386
|
result.frameworkRole = "alembic_migration";
|
|
41908
42387
|
return ok43(result);
|
|
@@ -41919,7 +42398,7 @@ var SQLAlchemyPlugin = class {
|
|
|
41919
42398
|
const root = tree.rootNode;
|
|
41920
42399
|
this.extractModels(root, source, filePath, result);
|
|
41921
42400
|
} catch (e) {
|
|
41922
|
-
return
|
|
42401
|
+
return err27(parseError(filePath, `SQLAlchemy parse error: ${e instanceof Error ? e.message : String(e)}`));
|
|
41923
42402
|
}
|
|
41924
42403
|
return ok43(result);
|
|
41925
42404
|
}
|
|
@@ -42009,7 +42488,7 @@ var SQLAlchemyPlugin = class {
|
|
|
42009
42488
|
*/
|
|
42010
42489
|
extractAlembicMigrations(root, source, filePath, result) {
|
|
42011
42490
|
const calls = this.findAllByType(root, "call");
|
|
42012
|
-
const fileBaseName =
|
|
42491
|
+
const fileBaseName = path51.basename(filePath);
|
|
42013
42492
|
const timestampMatch = fileBaseName.match(/^(\d+)_/);
|
|
42014
42493
|
const timestamp = timestampMatch ? timestampMatch[1] : void 0;
|
|
42015
42494
|
for (const call of calls) {
|
|
@@ -42410,7 +42889,7 @@ function toModelName(varName) {
|
|
|
42410
42889
|
|
|
42411
42890
|
// src/indexer/plugins/integration/view/react/index.ts
|
|
42412
42891
|
import { createRequire as createRequire17 } from "module";
|
|
42413
|
-
import { ok as ok45, err as
|
|
42892
|
+
import { ok as ok45, err as err28 } from "neverthrow";
|
|
42414
42893
|
var require18 = createRequire17(import.meta.url);
|
|
42415
42894
|
var Parser16 = require18("tree-sitter");
|
|
42416
42895
|
var TsGrammar3 = require18("tree-sitter-typescript");
|
|
@@ -42548,7 +43027,7 @@ var ReactPlugin = class {
|
|
|
42548
43027
|
}
|
|
42549
43028
|
}
|
|
42550
43029
|
} catch (e) {
|
|
42551
|
-
return
|
|
43030
|
+
return err28(parseError(filePath, `tree-sitter parse failed: ${e}`));
|
|
42552
43031
|
}
|
|
42553
43032
|
return ok45(result);
|
|
42554
43033
|
}
|
|
@@ -42662,7 +43141,7 @@ var ReactPlugin = class {
|
|
|
42662
43141
|
|
|
42663
43142
|
// src/indexer/plugins/integration/view/vue/index.ts
|
|
42664
43143
|
import fs42 from "fs";
|
|
42665
|
-
import
|
|
43144
|
+
import path52 from "path";
|
|
42666
43145
|
|
|
42667
43146
|
// src/indexer/plugins/integration/view/vue/resolver.ts
|
|
42668
43147
|
function toKebabCase(name) {
|
|
@@ -42701,7 +43180,7 @@ var VueFrameworkPlugin = class {
|
|
|
42701
43180
|
return "vue" in deps;
|
|
42702
43181
|
}
|
|
42703
43182
|
try {
|
|
42704
|
-
const pkgPath =
|
|
43183
|
+
const pkgPath = path52.join(ctx.rootPath, "package.json");
|
|
42705
43184
|
const content = fs42.readFileSync(pkgPath, "utf-8");
|
|
42706
43185
|
const pkg = JSON.parse(content);
|
|
42707
43186
|
const deps = {
|
|
@@ -42807,7 +43286,7 @@ var VueFrameworkPlugin = class {
|
|
|
42807
43286
|
|
|
42808
43287
|
// src/indexer/plugins/integration/view/react-native/index.ts
|
|
42809
43288
|
import fs43 from "fs";
|
|
42810
|
-
import
|
|
43289
|
+
import path53 from "path";
|
|
42811
43290
|
import { ok as ok46 } from "neverthrow";
|
|
42812
43291
|
function expoFileToRoute(filePath) {
|
|
42813
43292
|
const normalized = filePath.replace(/\\/g, "/");
|
|
@@ -42845,7 +43324,7 @@ var ReactNativePlugin = class {
|
|
|
42845
43324
|
return true;
|
|
42846
43325
|
}
|
|
42847
43326
|
try {
|
|
42848
|
-
const pkgPath =
|
|
43327
|
+
const pkgPath = path53.join(ctx.rootPath, "package.json");
|
|
42849
43328
|
const content = fs43.readFileSync(pkgPath, "utf-8");
|
|
42850
43329
|
const json = JSON.parse(content);
|
|
42851
43330
|
const allDeps = { ...json.dependencies, ...json.devDependencies };
|
|
@@ -43079,8 +43558,8 @@ function extractExpoNavigationCalls(source) {
|
|
|
43079
43558
|
}
|
|
43080
43559
|
const templateRegex = /router\.(push|replace|navigate)\s*\(\s*`([^`]+)`/g;
|
|
43081
43560
|
while ((match = templateRegex.exec(source)) !== null) {
|
|
43082
|
-
const
|
|
43083
|
-
paths.push(
|
|
43561
|
+
const path83 = match[2].replace(/\$\{[^}]+\}/g, ":param");
|
|
43562
|
+
paths.push(path83);
|
|
43084
43563
|
}
|
|
43085
43564
|
const linkRegex = /<Link\s+[^>]*href\s*=\s*(?:\{?\s*)?['"]([^'"]+)['"]/g;
|
|
43086
43565
|
while ((match = linkRegex.exec(source)) !== null) {
|
|
@@ -43092,9 +43571,9 @@ function extractExpoNavigationCalls(source) {
|
|
|
43092
43571
|
}
|
|
43093
43572
|
return [...new Set(paths)];
|
|
43094
43573
|
}
|
|
43095
|
-
function matchExpoRoute(
|
|
43096
|
-
if (
|
|
43097
|
-
const pathParts =
|
|
43574
|
+
function matchExpoRoute(path83, routePattern) {
|
|
43575
|
+
if (path83 === routePattern) return true;
|
|
43576
|
+
const pathParts = path83.split("/").filter(Boolean);
|
|
43098
43577
|
const routeParts = routePattern.split("/").filter(Boolean);
|
|
43099
43578
|
if (pathParts.length !== routeParts.length) {
|
|
43100
43579
|
if (routeParts[routeParts.length - 1] === "*" && pathParts.length >= routeParts.length - 1) {
|
|
@@ -43153,7 +43632,7 @@ function extractNativeModuleNames(source) {
|
|
|
43153
43632
|
|
|
43154
43633
|
// src/indexer/plugins/integration/view/blade/index.ts
|
|
43155
43634
|
import fs44 from "fs";
|
|
43156
|
-
import
|
|
43635
|
+
import path54 from "path";
|
|
43157
43636
|
var EXTENDS_RE = /@extends\(\s*['"]([\w.-]+)['"]\s*\)/g;
|
|
43158
43637
|
var INCLUDE_RE = /@include(?:If|When|Unless|First)?\(\s*['"]([\w.-]+)['"]/g;
|
|
43159
43638
|
var COMPONENT_RE = /@component\(\s*['"]([\w.-]+)['"]/g;
|
|
@@ -43211,7 +43690,7 @@ var BladePlugin = class {
|
|
|
43211
43690
|
};
|
|
43212
43691
|
detect(ctx) {
|
|
43213
43692
|
try {
|
|
43214
|
-
const viewsDir =
|
|
43693
|
+
const viewsDir = path54.join(ctx.rootPath, "resources", "views");
|
|
43215
43694
|
const stat = fs44.statSync(viewsDir);
|
|
43216
43695
|
if (!stat.isDirectory()) return false;
|
|
43217
43696
|
return this.hasBlade(viewsDir);
|
|
@@ -43298,7 +43777,7 @@ var BladePlugin = class {
|
|
|
43298
43777
|
for (const entry of entries) {
|
|
43299
43778
|
if (entry.isFile() && entry.name.endsWith(".blade.php")) return true;
|
|
43300
43779
|
if (entry.isDirectory()) {
|
|
43301
|
-
if (this.hasBlade(
|
|
43780
|
+
if (this.hasBlade(path54.join(dir, entry.name))) return true;
|
|
43302
43781
|
}
|
|
43303
43782
|
}
|
|
43304
43783
|
} catch {
|
|
@@ -43309,7 +43788,7 @@ var BladePlugin = class {
|
|
|
43309
43788
|
|
|
43310
43789
|
// src/indexer/plugins/integration/view/inertia/index.ts
|
|
43311
43790
|
import fs45 from "fs";
|
|
43312
|
-
import
|
|
43791
|
+
import path55 from "path";
|
|
43313
43792
|
var INERTIA_RENDER_RE = /(?:Inertia::render|inertia)\(\s*['"]([\w/.-]+)['"]\s*(?:,\s*\[([^\]]*)\])?\s*\)/g;
|
|
43314
43793
|
var ARRAY_KEY_RE = /['"](\w+)['"]\s*=>/g;
|
|
43315
43794
|
function extractInertiaRenders(source) {
|
|
@@ -43355,7 +43834,7 @@ var InertiaPlugin = class {
|
|
|
43355
43834
|
if ("@inertiajs/vue3" in deps || "@inertiajs/react" in deps) return true;
|
|
43356
43835
|
}
|
|
43357
43836
|
try {
|
|
43358
|
-
const composerPath =
|
|
43837
|
+
const composerPath = path55.join(ctx.rootPath, "composer.json");
|
|
43359
43838
|
const content = fs45.readFileSync(composerPath, "utf-8");
|
|
43360
43839
|
const json = JSON.parse(content);
|
|
43361
43840
|
const req = json.require;
|
|
@@ -43363,7 +43842,7 @@ var InertiaPlugin = class {
|
|
|
43363
43842
|
} catch {
|
|
43364
43843
|
}
|
|
43365
43844
|
try {
|
|
43366
|
-
const pkgPath =
|
|
43845
|
+
const pkgPath = path55.join(ctx.rootPath, "package.json");
|
|
43367
43846
|
const content = fs45.readFileSync(pkgPath, "utf-8");
|
|
43368
43847
|
const pkg = JSON.parse(content);
|
|
43369
43848
|
const deps = {
|
|
@@ -43405,7 +43884,7 @@ var InertiaPlugin = class {
|
|
|
43405
43884
|
if (file.language !== "php") continue;
|
|
43406
43885
|
let source;
|
|
43407
43886
|
try {
|
|
43408
|
-
source = fs45.readFileSync(
|
|
43887
|
+
source = fs45.readFileSync(path55.resolve(ctx.rootPath, file.path), "utf-8");
|
|
43409
43888
|
} catch {
|
|
43410
43889
|
continue;
|
|
43411
43890
|
}
|
|
@@ -43457,7 +43936,7 @@ var InertiaPlugin = class {
|
|
|
43457
43936
|
|
|
43458
43937
|
// src/indexer/plugins/integration/view/shadcn/index.ts
|
|
43459
43938
|
import fs46 from "fs";
|
|
43460
|
-
import
|
|
43939
|
+
import path56 from "path";
|
|
43461
43940
|
import { ok as ok47 } from "neverthrow";
|
|
43462
43941
|
var CVA_RE = /(?:export\s+(?:default\s+)?)?(?:const|let)\s+(\w+)\s*=\s*cva\s*\(/g;
|
|
43463
43942
|
function extractCvaDefinitions(source) {
|
|
@@ -43644,7 +44123,7 @@ function extractShadcnComponents(source, filePath) {
|
|
|
43644
44123
|
return components;
|
|
43645
44124
|
}
|
|
43646
44125
|
function extractShadcnVueComponent(source, filePath) {
|
|
43647
|
-
const fileName =
|
|
44126
|
+
const fileName = path56.basename(filePath, path56.extname(filePath));
|
|
43648
44127
|
const name = toPascalCase2(fileName);
|
|
43649
44128
|
const hasRadixVue = /from\s+['"]radix-vue['"]/.test(source) || /from\s+['"]reka-ui['"]/.test(source);
|
|
43650
44129
|
const hasCn = /\bcn\s*\(/.test(source);
|
|
@@ -43781,7 +44260,7 @@ function scanInstalledComponents(rootPath, config) {
|
|
|
43781
44260
|
"app/components/ui"
|
|
43782
44261
|
].filter(Boolean);
|
|
43783
44262
|
for (const relDir of possibleDirs) {
|
|
43784
|
-
const absDir =
|
|
44263
|
+
const absDir = path56.resolve(rootPath, relDir);
|
|
43785
44264
|
try {
|
|
43786
44265
|
const entries = fs46.readdirSync(absDir, { withFileTypes: true });
|
|
43787
44266
|
for (const entry of entries) {
|
|
@@ -43790,17 +44269,17 @@ function scanInstalledComponents(rootPath, config) {
|
|
|
43790
44269
|
components.push({
|
|
43791
44270
|
name: toPascalCase2(baseName),
|
|
43792
44271
|
fileName: entry.name,
|
|
43793
|
-
relativePath:
|
|
44272
|
+
relativePath: path56.join(relDir, entry.name)
|
|
43794
44273
|
});
|
|
43795
44274
|
} else if (entry.isDirectory()) {
|
|
43796
44275
|
try {
|
|
43797
|
-
const subEntries = fs46.readdirSync(
|
|
44276
|
+
const subEntries = fs46.readdirSync(path56.join(absDir, entry.name));
|
|
43798
44277
|
const indexFile = subEntries.find((f) => /^index\.(tsx|vue|ts|jsx)$/.test(f));
|
|
43799
44278
|
if (indexFile) {
|
|
43800
44279
|
components.push({
|
|
43801
44280
|
name: toPascalCase2(entry.name),
|
|
43802
44281
|
fileName: indexFile,
|
|
43803
|
-
relativePath:
|
|
44282
|
+
relativePath: path56.join(relDir, entry.name, indexFile)
|
|
43804
44283
|
});
|
|
43805
44284
|
}
|
|
43806
44285
|
for (const sub of subEntries) {
|
|
@@ -43809,7 +44288,7 @@ function scanInstalledComponents(rootPath, config) {
|
|
|
43809
44288
|
components.push({
|
|
43810
44289
|
name: toPascalCase2(baseName),
|
|
43811
44290
|
fileName: sub,
|
|
43812
|
-
relativePath:
|
|
44291
|
+
relativePath: path56.join(relDir, entry.name, sub)
|
|
43813
44292
|
});
|
|
43814
44293
|
}
|
|
43815
44294
|
}
|
|
@@ -43842,7 +44321,7 @@ var ShadcnPlugin = class {
|
|
|
43842
44321
|
detect(ctx) {
|
|
43843
44322
|
this.rootPath = ctx.rootPath;
|
|
43844
44323
|
try {
|
|
43845
|
-
const configPath =
|
|
44324
|
+
const configPath = path56.join(ctx.rootPath, "components.json");
|
|
43846
44325
|
const raw = fs46.readFileSync(configPath, "utf-8");
|
|
43847
44326
|
this.config = JSON.parse(raw);
|
|
43848
44327
|
this.scanComponents(ctx);
|
|
@@ -44720,7 +45199,7 @@ var HeadlessUiPlugin = class {
|
|
|
44720
45199
|
|
|
44721
45200
|
// src/indexer/plugins/integration/view/nuxt-ui/index.ts
|
|
44722
45201
|
import fs47 from "fs";
|
|
44723
|
-
import
|
|
45202
|
+
import path57 from "path";
|
|
44724
45203
|
import { ok as ok51 } from "neverthrow";
|
|
44725
45204
|
var NUXT_UI_V3_COMPONENTS = /* @__PURE__ */ new Set([
|
|
44726
45205
|
"UAccordion",
|
|
@@ -44976,7 +45455,7 @@ var NuxtUiPlugin = class {
|
|
|
44976
45455
|
this.hasPro = "@nuxt/ui-pro" in deps;
|
|
44977
45456
|
if (!hasNuxtUi) {
|
|
44978
45457
|
try {
|
|
44979
|
-
const configPath =
|
|
45458
|
+
const configPath = path57.join(ctx.rootPath, "nuxt.config.ts");
|
|
44980
45459
|
const configContent = fs47.readFileSync(configPath, "utf-8");
|
|
44981
45460
|
if (/@nuxt\/ui/.test(configContent)) return true;
|
|
44982
45461
|
} catch {
|
|
@@ -45177,7 +45656,7 @@ function extractBraceBody4(source, pos) {
|
|
|
45177
45656
|
|
|
45178
45657
|
// src/indexer/plugins/integration/view/angular/index.ts
|
|
45179
45658
|
import fs48 from "fs";
|
|
45180
|
-
import
|
|
45659
|
+
import path58 from "path";
|
|
45181
45660
|
var COMPONENT_RE2 = /@Component\s*\(\s*\{[^}]*selector\s*:\s*['"]([^'"]+)['"]/gs;
|
|
45182
45661
|
var INJECTABLE_RE2 = /@Injectable\s*\(/g;
|
|
45183
45662
|
var NGMODULE_RE = /@NgModule\s*\(/g;
|
|
@@ -45225,7 +45704,7 @@ var AngularPlugin = class {
|
|
|
45225
45704
|
if ("@angular/core" in deps) return true;
|
|
45226
45705
|
}
|
|
45227
45706
|
try {
|
|
45228
|
-
const pkgPath =
|
|
45707
|
+
const pkgPath = path58.join(ctx.rootPath, "package.json");
|
|
45229
45708
|
const content = fs48.readFileSync(pkgPath, "utf-8");
|
|
45230
45709
|
const pkg = JSON.parse(content);
|
|
45231
45710
|
const deps = {
|
|
@@ -45546,7 +46025,7 @@ function isHtmlTag(tag) {
|
|
|
45546
46025
|
|
|
45547
46026
|
// src/indexer/plugins/integration/view/svelte/index.ts
|
|
45548
46027
|
import fs49 from "fs";
|
|
45549
|
-
import
|
|
46028
|
+
import path59 from "path";
|
|
45550
46029
|
var EXPORT_LET_RE = /export\s+let\s+(\w+)/g;
|
|
45551
46030
|
var SLOT_RE = /<slot(?:\s+name\s*=\s*['"]([^'"]+)['"])?\s*\/?>/g;
|
|
45552
46031
|
var DISPATCH_RE2 = /dispatch\s*\(\s*['"]([^'"]+)['"]/g;
|
|
@@ -45564,11 +46043,11 @@ function extractTemplate(source) {
|
|
|
45564
46043
|
return source.replace(/<script[^>]*>[\s\S]*?<\/script>/g, "").replace(/<style[^>]*>[\s\S]*?<\/style>/g, "");
|
|
45565
46044
|
}
|
|
45566
46045
|
function isSvelteKitRouteFile(filePath) {
|
|
45567
|
-
const base =
|
|
46046
|
+
const base = path59.basename(filePath);
|
|
45568
46047
|
return /^\+(page|layout|server|error)/.test(base);
|
|
45569
46048
|
}
|
|
45570
46049
|
function isSvelteKitHooksFile(filePath) {
|
|
45571
|
-
const base =
|
|
46050
|
+
const base = path59.basename(filePath);
|
|
45572
46051
|
return /^hooks\.(server|client)\.(ts|js)$/.test(base);
|
|
45573
46052
|
}
|
|
45574
46053
|
function extractRouteUri(filePath) {
|
|
@@ -45576,11 +46055,11 @@ function extractRouteUri(filePath) {
|
|
|
45576
46055
|
const routesIdx = normalized.indexOf("/routes/");
|
|
45577
46056
|
if (routesIdx === -1) return "/";
|
|
45578
46057
|
const afterRoutes = normalized.substring(routesIdx + "/routes".length);
|
|
45579
|
-
const dir =
|
|
46058
|
+
const dir = path59.posix.dirname(afterRoutes);
|
|
45580
46059
|
return dir === "." ? "/" : dir;
|
|
45581
46060
|
}
|
|
45582
46061
|
function componentNameFromPath2(filePath) {
|
|
45583
|
-
const base =
|
|
46062
|
+
const base = path59.basename(filePath, ".svelte");
|
|
45584
46063
|
if (base.startsWith("+")) return base;
|
|
45585
46064
|
return base;
|
|
45586
46065
|
}
|
|
@@ -45607,7 +46086,7 @@ var SveltePlugin = class {
|
|
|
45607
46086
|
if ("svelte" in deps || "@sveltejs/kit" in deps) return true;
|
|
45608
46087
|
}
|
|
45609
46088
|
try {
|
|
45610
|
-
const pkgPath =
|
|
46089
|
+
const pkgPath = path59.join(ctx.rootPath, "package.json");
|
|
45611
46090
|
const content = fs49.readFileSync(pkgPath, "utf-8");
|
|
45612
46091
|
const pkg = JSON.parse(content);
|
|
45613
46092
|
const deps = {
|
|
@@ -45683,7 +46162,7 @@ var SveltePlugin = class {
|
|
|
45683
46162
|
const name = componentNameFromPath2(filePath);
|
|
45684
46163
|
const isRouteFile = isSvelteKitRouteFile(filePath);
|
|
45685
46164
|
let kind = "component";
|
|
45686
|
-
const base =
|
|
46165
|
+
const base = path59.basename(filePath);
|
|
45687
46166
|
if (base === "+page.svelte") kind = "page";
|
|
45688
46167
|
else if (base === "+layout.svelte") kind = "layout";
|
|
45689
46168
|
else if (base === "+error.svelte") kind = "component";
|
|
@@ -45764,7 +46243,7 @@ var SveltePlugin = class {
|
|
|
45764
46243
|
}
|
|
45765
46244
|
}
|
|
45766
46245
|
extractSvelteKitServerFile(filePath, source, result) {
|
|
45767
|
-
const base =
|
|
46246
|
+
const base = path59.basename(filePath);
|
|
45768
46247
|
if (!isSvelteKitRouteFile(filePath) && !isSvelteKitHooksFile(filePath)) {
|
|
45769
46248
|
return;
|
|
45770
46249
|
}
|
|
@@ -45856,7 +46335,7 @@ var SveltePlugin = class {
|
|
|
45856
46335
|
|
|
45857
46336
|
// src/indexer/plugins/integration/api/trpc/index.ts
|
|
45858
46337
|
import fs50 from "fs";
|
|
45859
|
-
import
|
|
46338
|
+
import path60 from "path";
|
|
45860
46339
|
var PROCEDURE_RE = /(\w+)\s*:\s*\w*[Pp]rocedure[\s\S]{0,500}?\.(query|mutation|subscription)\s*\(/g;
|
|
45861
46340
|
var ROUTER_RE = /(?:t\.router|router)\s*\(\s*\{/g;
|
|
45862
46341
|
function extractTrpcProcedures(source) {
|
|
@@ -45888,7 +46367,7 @@ var TrpcPlugin = class {
|
|
|
45888
46367
|
if ("@trpc/server" in deps) return true;
|
|
45889
46368
|
}
|
|
45890
46369
|
try {
|
|
45891
|
-
const pkgPath =
|
|
46370
|
+
const pkgPath = path60.join(ctx.rootPath, "package.json");
|
|
45892
46371
|
const content = fs50.readFileSync(pkgPath, "utf-8");
|
|
45893
46372
|
const pkg = JSON.parse(content);
|
|
45894
46373
|
const deps = {
|
|
@@ -45937,8 +46416,8 @@ var TrpcPlugin = class {
|
|
|
45937
46416
|
// src/indexer/plugins/integration/api/drf/index.ts
|
|
45938
46417
|
import { createRequire as createRequire18 } from "module";
|
|
45939
46418
|
import fs51 from "fs";
|
|
45940
|
-
import
|
|
45941
|
-
import { ok as ok52, err as
|
|
46419
|
+
import path61 from "path";
|
|
46420
|
+
import { ok as ok52, err as err29 } from "neverthrow";
|
|
45942
46421
|
var require19 = createRequire18(import.meta.url);
|
|
45943
46422
|
var Parser17 = require19("tree-sitter");
|
|
45944
46423
|
var PythonGrammar5 = require19("tree-sitter-python");
|
|
@@ -45953,25 +46432,25 @@ function getParser14() {
|
|
|
45953
46432
|
function hasPythonDep4(rootPath, depName) {
|
|
45954
46433
|
for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
45955
46434
|
try {
|
|
45956
|
-
const content = fs51.readFileSync(
|
|
46435
|
+
const content = fs51.readFileSync(path61.join(rootPath, reqFile), "utf-8");
|
|
45957
46436
|
if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
|
|
45958
46437
|
} catch {
|
|
45959
46438
|
}
|
|
45960
46439
|
}
|
|
45961
46440
|
try {
|
|
45962
|
-
const content = fs51.readFileSync(
|
|
46441
|
+
const content = fs51.readFileSync(path61.join(rootPath, "pyproject.toml"), "utf-8");
|
|
45963
46442
|
if (content.includes(depName)) return true;
|
|
45964
46443
|
} catch {
|
|
45965
46444
|
}
|
|
45966
46445
|
for (const f of ["setup.py", "setup.cfg"]) {
|
|
45967
46446
|
try {
|
|
45968
|
-
const content = fs51.readFileSync(
|
|
46447
|
+
const content = fs51.readFileSync(path61.join(rootPath, f), "utf-8");
|
|
45969
46448
|
if (content.includes(depName)) return true;
|
|
45970
46449
|
} catch {
|
|
45971
46450
|
}
|
|
45972
46451
|
}
|
|
45973
46452
|
try {
|
|
45974
|
-
const content = fs51.readFileSync(
|
|
46453
|
+
const content = fs51.readFileSync(path61.join(rootPath, "Pipfile"), "utf-8");
|
|
45975
46454
|
if (content.includes(depName)) return true;
|
|
45976
46455
|
} catch {
|
|
45977
46456
|
}
|
|
@@ -46219,7 +46698,7 @@ var DRFPlugin = class {
|
|
|
46219
46698
|
const parser = getParser14();
|
|
46220
46699
|
tree = parser.parse(source);
|
|
46221
46700
|
} catch (e) {
|
|
46222
|
-
return
|
|
46701
|
+
return err29(parseError(filePath, `tree-sitter parse failed: ${e}`));
|
|
46223
46702
|
}
|
|
46224
46703
|
const root = tree.rootNode;
|
|
46225
46704
|
const serializers = extractSerializers(root);
|
|
@@ -46286,7 +46765,7 @@ var DRFPlugin = class {
|
|
|
46286
46765
|
|
|
46287
46766
|
// src/indexer/plugins/integration/validation/zod/index.ts
|
|
46288
46767
|
import fs52 from "fs";
|
|
46289
|
-
import
|
|
46768
|
+
import path62 from "path";
|
|
46290
46769
|
var ZOD_OBJECT_RE = /(?:export\s+(?:default\s+)?)?(?:const|let|var)\s+(\w+)\s*=\s*z\.object\s*\(\s*\{([^]*?)\}\s*\)/g;
|
|
46291
46770
|
var ZOD_FIELD_RE = /(\w+)\s*:\s*z\.(\w+)\s*\(([^)]*)\)([.\w()]*)/g;
|
|
46292
46771
|
function resolveFieldType(baseType, chain) {
|
|
@@ -46341,7 +46820,7 @@ var ZodPlugin = class {
|
|
|
46341
46820
|
if ("zod" in deps) return true;
|
|
46342
46821
|
}
|
|
46343
46822
|
try {
|
|
46344
|
-
const pkgPath =
|
|
46823
|
+
const pkgPath = path62.join(ctx.rootPath, "package.json");
|
|
46345
46824
|
const content = fs52.readFileSync(pkgPath, "utf-8");
|
|
46346
46825
|
const pkg = JSON.parse(content);
|
|
46347
46826
|
const deps = {
|
|
@@ -46387,8 +46866,8 @@ var ZodPlugin = class {
|
|
|
46387
46866
|
// src/indexer/plugins/integration/validation/pydantic/index.ts
|
|
46388
46867
|
import { createRequire as createRequire19 } from "module";
|
|
46389
46868
|
import fs53 from "fs";
|
|
46390
|
-
import
|
|
46391
|
-
import { ok as ok53, err as
|
|
46869
|
+
import path63 from "path";
|
|
46870
|
+
import { ok as ok53, err as err30 } from "neverthrow";
|
|
46392
46871
|
var require20 = createRequire19(import.meta.url);
|
|
46393
46872
|
var Parser18 = require20("tree-sitter");
|
|
46394
46873
|
var PythonGrammar6 = require20("tree-sitter-python");
|
|
@@ -46403,25 +46882,25 @@ function getParser15() {
|
|
|
46403
46882
|
function hasPythonDep5(rootPath, depName) {
|
|
46404
46883
|
for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
46405
46884
|
try {
|
|
46406
|
-
const content = fs53.readFileSync(
|
|
46885
|
+
const content = fs53.readFileSync(path63.join(rootPath, reqFile), "utf-8");
|
|
46407
46886
|
if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
|
|
46408
46887
|
} catch {
|
|
46409
46888
|
}
|
|
46410
46889
|
}
|
|
46411
46890
|
try {
|
|
46412
|
-
const content = fs53.readFileSync(
|
|
46891
|
+
const content = fs53.readFileSync(path63.join(rootPath, "pyproject.toml"), "utf-8");
|
|
46413
46892
|
if (content.includes(depName)) return true;
|
|
46414
46893
|
} catch {
|
|
46415
46894
|
}
|
|
46416
46895
|
for (const f of ["setup.py", "setup.cfg"]) {
|
|
46417
46896
|
try {
|
|
46418
|
-
const content = fs53.readFileSync(
|
|
46897
|
+
const content = fs53.readFileSync(path63.join(rootPath, f), "utf-8");
|
|
46419
46898
|
if (content.includes(depName)) return true;
|
|
46420
46899
|
} catch {
|
|
46421
46900
|
}
|
|
46422
46901
|
}
|
|
46423
46902
|
try {
|
|
46424
|
-
const content = fs53.readFileSync(
|
|
46903
|
+
const content = fs53.readFileSync(path63.join(rootPath, "Pipfile"), "utf-8");
|
|
46425
46904
|
if (content.includes(depName)) return true;
|
|
46426
46905
|
} catch {
|
|
46427
46906
|
}
|
|
@@ -46734,7 +47213,7 @@ var PydanticPlugin = class {
|
|
|
46734
47213
|
const parser = getParser15();
|
|
46735
47214
|
tree = parser.parse(source);
|
|
46736
47215
|
} catch (e) {
|
|
46737
|
-
return
|
|
47216
|
+
return err30(parseError(filePath, `tree-sitter parse failed: ${e}`));
|
|
46738
47217
|
}
|
|
46739
47218
|
const root = tree.rootNode;
|
|
46740
47219
|
const models = extractPydanticModels(root);
|
|
@@ -46965,7 +47444,7 @@ function extractBraceBody5(source, pos) {
|
|
|
46965
47444
|
|
|
46966
47445
|
// src/indexer/plugins/integration/realtime/socketio/index.ts
|
|
46967
47446
|
import fs54 from "fs";
|
|
46968
|
-
import
|
|
47447
|
+
import path64 from "path";
|
|
46969
47448
|
var LISTENER_RE = /(?:socket|io|server|namespace)\s*\.\s*on\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
46970
47449
|
var EMITTER_RE = /(?:socket|io|server|namespace)(?:\.broadcast)?\s*\.\s*emit\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
46971
47450
|
var NAMESPACE_RE6 = /(?:io|server)\s*\.\s*of\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -47008,7 +47487,7 @@ var SocketIoPlugin = class {
|
|
|
47008
47487
|
if ("socket.io" in deps) return true;
|
|
47009
47488
|
}
|
|
47010
47489
|
try {
|
|
47011
|
-
const pkgPath =
|
|
47490
|
+
const pkgPath = path64.join(ctx.rootPath, "package.json");
|
|
47012
47491
|
const content = fs54.readFileSync(pkgPath, "utf-8");
|
|
47013
47492
|
const pkg = JSON.parse(content);
|
|
47014
47493
|
const deps = {
|
|
@@ -47064,7 +47543,7 @@ var SocketIoPlugin = class {
|
|
|
47064
47543
|
|
|
47065
47544
|
// src/indexer/plugins/integration/testing/testing/index.ts
|
|
47066
47545
|
import fs55 from "fs";
|
|
47067
|
-
import
|
|
47546
|
+
import path65 from "path";
|
|
47068
47547
|
var PAGE_GOTO_RE = /page\.goto\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
47069
47548
|
var CY_VISIT_RE = /cy\.visit\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
47070
47549
|
var REQUEST_METHOD_RE = /request\s*\.\s*(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -47171,7 +47650,7 @@ var TestingPlugin = class {
|
|
|
47171
47650
|
}
|
|
47172
47651
|
}
|
|
47173
47652
|
try {
|
|
47174
|
-
const pkgPath =
|
|
47653
|
+
const pkgPath = path65.join(ctx.rootPath, "package.json");
|
|
47175
47654
|
const content = fs55.readFileSync(pkgPath, "utf-8");
|
|
47176
47655
|
const pkg = JSON.parse(content);
|
|
47177
47656
|
const deps = {
|
|
@@ -47245,8 +47724,8 @@ var TestingPlugin = class {
|
|
|
47245
47724
|
// src/indexer/plugins/integration/tooling/celery/index.ts
|
|
47246
47725
|
import { createRequire as createRequire20 } from "module";
|
|
47247
47726
|
import fs56 from "fs";
|
|
47248
|
-
import
|
|
47249
|
-
import { ok as ok55, err as
|
|
47727
|
+
import path66 from "path";
|
|
47728
|
+
import { ok as ok55, err as err31 } from "neverthrow";
|
|
47250
47729
|
var require21 = createRequire20(import.meta.url);
|
|
47251
47730
|
var Parser19 = require21("tree-sitter");
|
|
47252
47731
|
var PythonGrammar7 = require21("tree-sitter-python");
|
|
@@ -47261,25 +47740,25 @@ function getParser16() {
|
|
|
47261
47740
|
function hasPythonDep6(rootPath, depName) {
|
|
47262
47741
|
for (const reqFile of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
47263
47742
|
try {
|
|
47264
|
-
const content = fs56.readFileSync(
|
|
47743
|
+
const content = fs56.readFileSync(path66.join(rootPath, reqFile), "utf-8");
|
|
47265
47744
|
if (new RegExp(`^${escapeRegExp(depName)}\\b`, "m").test(content)) return true;
|
|
47266
47745
|
} catch {
|
|
47267
47746
|
}
|
|
47268
47747
|
}
|
|
47269
47748
|
try {
|
|
47270
|
-
const content = fs56.readFileSync(
|
|
47749
|
+
const content = fs56.readFileSync(path66.join(rootPath, "pyproject.toml"), "utf-8");
|
|
47271
47750
|
if (content.includes(depName)) return true;
|
|
47272
47751
|
} catch {
|
|
47273
47752
|
}
|
|
47274
47753
|
for (const f of ["setup.py", "setup.cfg"]) {
|
|
47275
47754
|
try {
|
|
47276
|
-
const content = fs56.readFileSync(
|
|
47755
|
+
const content = fs56.readFileSync(path66.join(rootPath, f), "utf-8");
|
|
47277
47756
|
if (content.includes(depName)) return true;
|
|
47278
47757
|
} catch {
|
|
47279
47758
|
}
|
|
47280
47759
|
}
|
|
47281
47760
|
try {
|
|
47282
|
-
const content = fs56.readFileSync(
|
|
47761
|
+
const content = fs56.readFileSync(path66.join(rootPath, "Pipfile"), "utf-8");
|
|
47283
47762
|
if (content.includes(depName)) return true;
|
|
47284
47763
|
} catch {
|
|
47285
47764
|
}
|
|
@@ -47451,7 +47930,7 @@ var CeleryPlugin = class {
|
|
|
47451
47930
|
const parser = getParser16();
|
|
47452
47931
|
tree = parser.parse(source);
|
|
47453
47932
|
} catch (e) {
|
|
47454
|
-
return
|
|
47933
|
+
return err31(parseError(filePath, `tree-sitter parse failed: ${e}`));
|
|
47455
47934
|
}
|
|
47456
47935
|
const root = tree.rootNode;
|
|
47457
47936
|
const tasks = extractCeleryTasks(root);
|
|
@@ -47528,7 +48007,7 @@ var CeleryPlugin = class {
|
|
|
47528
48007
|
|
|
47529
48008
|
// src/indexer/plugins/integration/tooling/n8n/index.ts
|
|
47530
48009
|
import fs57 from "fs";
|
|
47531
|
-
import
|
|
48010
|
+
import path67 from "path";
|
|
47532
48011
|
var CODE_TYPES = /* @__PURE__ */ new Set([
|
|
47533
48012
|
"n8n-nodes-base.code",
|
|
47534
48013
|
"n8n-nodes-base.function",
|
|
@@ -48158,7 +48637,7 @@ function collectNodeFiles(dir) {
|
|
|
48158
48637
|
try {
|
|
48159
48638
|
const entries = fs57.readdirSync(dir, { withFileTypes: true });
|
|
48160
48639
|
for (const entry of entries) {
|
|
48161
|
-
const fullPath =
|
|
48640
|
+
const fullPath = path67.join(dir, entry.name);
|
|
48162
48641
|
if (entry.isDirectory()) {
|
|
48163
48642
|
results.push(...collectNodeFiles(fullPath));
|
|
48164
48643
|
} else if (entry.name.endsWith(".node.ts")) {
|
|
@@ -48418,13 +48897,13 @@ var N8nPlugin = class {
|
|
|
48418
48897
|
}
|
|
48419
48898
|
}
|
|
48420
48899
|
try {
|
|
48421
|
-
if (fs57.existsSync(
|
|
48900
|
+
if (fs57.existsSync(path67.join(ctx.rootPath, ".n8n"))) return true;
|
|
48422
48901
|
} catch {
|
|
48423
48902
|
}
|
|
48424
48903
|
const nodeDirs = ["nodes", "src/nodes"];
|
|
48425
48904
|
for (const dir of nodeDirs) {
|
|
48426
48905
|
try {
|
|
48427
|
-
const fullDir =
|
|
48906
|
+
const fullDir = path67.join(ctx.rootPath, dir);
|
|
48428
48907
|
if (fs57.existsSync(fullDir) && fs57.statSync(fullDir).isDirectory()) {
|
|
48429
48908
|
const files = collectNodeFiles(fullDir);
|
|
48430
48909
|
if (files.length > 0) return true;
|
|
@@ -48435,12 +48914,12 @@ var N8nPlugin = class {
|
|
|
48435
48914
|
const searchDirs = ["workflows", "n8n", ".n8n", "."];
|
|
48436
48915
|
for (const dir of searchDirs) {
|
|
48437
48916
|
try {
|
|
48438
|
-
const fullDir =
|
|
48917
|
+
const fullDir = path67.join(ctx.rootPath, dir);
|
|
48439
48918
|
if (!fs57.existsSync(fullDir) || !fs57.statSync(fullDir).isDirectory()) continue;
|
|
48440
48919
|
const files = fs57.readdirSync(fullDir).filter((f) => f.endsWith(".json"));
|
|
48441
48920
|
for (const file of files.slice(0, 5)) {
|
|
48442
48921
|
try {
|
|
48443
|
-
const content = fs57.readFileSync(
|
|
48922
|
+
const content = fs57.readFileSync(path67.join(fullDir, file));
|
|
48444
48923
|
if (parseN8nWorkflow(content)) return true;
|
|
48445
48924
|
} catch {
|
|
48446
48925
|
}
|
|
@@ -48502,7 +48981,7 @@ var N8nPlugin = class {
|
|
|
48502
48981
|
routes: [],
|
|
48503
48982
|
frameworkRole: role,
|
|
48504
48983
|
metadata: {
|
|
48505
|
-
workflowName: workflow.name ??
|
|
48984
|
+
workflowName: workflow.name ?? path67.basename(filePath, ".json"),
|
|
48506
48985
|
workflowId: workflow.id,
|
|
48507
48986
|
active: workflow.active ?? false,
|
|
48508
48987
|
nodeCount: workflow.nodes.length,
|
|
@@ -48837,7 +49316,7 @@ function findNodeByteOffset(source, nodeName) {
|
|
|
48837
49316
|
|
|
48838
49317
|
// src/indexer/plugins/integration/tooling/data-fetching/index.ts
|
|
48839
49318
|
import fs58 from "fs";
|
|
48840
|
-
import
|
|
49319
|
+
import path68 from "path";
|
|
48841
49320
|
var USE_QUERY_OBJECT_RE = /\b(useQuery|useInfiniteQuery)\s*\(\s*\{[^}]*?queryFn\s*:\s*[^}]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`]/g;
|
|
48842
49321
|
var USE_QUERY_ARRAY_RE = /\b(useQuery|useInfiniteQuery)\s*\(\s*\[[^\]]*\]\s*,\s*(?:\([^)]*\)\s*=>|function\s*\([^)]*\)\s*\{)[^)]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`]/g;
|
|
48843
49322
|
var USE_MUTATION_RE = /\b(useMutation)\s*\(\s*\{[^}]*?mutationFn\s*:\s*[^}]*?fetch\s*\(\s*[`'"](\/[^'"`$]*?)['"`][^)]*?(?:method\s*:\s*['"`](\w+)['"`])?/g;
|
|
@@ -48909,7 +49388,7 @@ var DataFetchingPlugin = class {
|
|
|
48909
49388
|
if ("@tanstack/react-query" in deps || "swr" in deps) return true;
|
|
48910
49389
|
}
|
|
48911
49390
|
try {
|
|
48912
|
-
const pkgPath =
|
|
49391
|
+
const pkgPath = path68.join(ctx.rootPath, "package.json");
|
|
48913
49392
|
const content = fs58.readFileSync(pkgPath, "utf-8");
|
|
48914
49393
|
const pkg = JSON.parse(content);
|
|
48915
49394
|
const deps = {
|
|
@@ -49013,7 +49492,7 @@ function createAllIntegrationPlugins() {
|
|
|
49013
49492
|
|
|
49014
49493
|
// src/indexer/watcher.ts
|
|
49015
49494
|
import * as parcelWatcher from "@parcel/watcher";
|
|
49016
|
-
import
|
|
49495
|
+
import path69 from "path";
|
|
49017
49496
|
var IGNORE_DIRS = [
|
|
49018
49497
|
"vendor",
|
|
49019
49498
|
"node_modules",
|
|
@@ -49038,12 +49517,12 @@ var FileWatcher = class {
|
|
|
49038
49517
|
debounceTimer = null;
|
|
49039
49518
|
pendingPaths = /* @__PURE__ */ new Set();
|
|
49040
49519
|
async start(rootPath, config, onChanges, debounceMs = DEFAULT_DEBOUNCE_MS, onDeletes) {
|
|
49041
|
-
const ignoreDirs = IGNORE_DIRS.map((d) =>
|
|
49520
|
+
const ignoreDirs = IGNORE_DIRS.map((d) => path69.join(rootPath, d));
|
|
49042
49521
|
this.subscription = await parcelWatcher.subscribe(
|
|
49043
49522
|
rootPath,
|
|
49044
|
-
async (
|
|
49045
|
-
if (
|
|
49046
|
-
logger.error({ error:
|
|
49523
|
+
async (err32, events) => {
|
|
49524
|
+
if (err32) {
|
|
49525
|
+
logger.error({ error: err32 }, "Watcher error");
|
|
49047
49526
|
return;
|
|
49048
49527
|
}
|
|
49049
49528
|
const notIgnored = (p4) => !ignoreDirs.some((d) => p4.startsWith(d));
|
|
@@ -49094,12 +49573,12 @@ import http from "http";
|
|
|
49094
49573
|
// src/cli-init.ts
|
|
49095
49574
|
import { Command } from "commander";
|
|
49096
49575
|
import fs68 from "fs";
|
|
49097
|
-
import
|
|
49576
|
+
import path78 from "path";
|
|
49098
49577
|
import * as p from "@clack/prompts";
|
|
49099
49578
|
|
|
49100
49579
|
// src/init/mcp-client.ts
|
|
49101
49580
|
import fs59 from "fs";
|
|
49102
|
-
import
|
|
49581
|
+
import path70 from "path";
|
|
49103
49582
|
import os2 from "os";
|
|
49104
49583
|
var HOME = os2.homedir();
|
|
49105
49584
|
function configureMcpClients(clientNames, projectRoot, opts) {
|
|
@@ -49131,14 +49610,14 @@ function configureMcpClients(clientNames, projectRoot, opts) {
|
|
|
49131
49610
|
try {
|
|
49132
49611
|
const action = writeTraceMcpEntry(configPath, entry);
|
|
49133
49612
|
results.push({ target: configPath, action, detail: `${name} (${opts.scope})` });
|
|
49134
|
-
} catch (
|
|
49135
|
-
results.push({ target: configPath, action: "skipped", detail: `Error: ${
|
|
49613
|
+
} catch (err32) {
|
|
49614
|
+
results.push({ target: configPath, action: "skipped", detail: `Error: ${err32.message}` });
|
|
49136
49615
|
}
|
|
49137
49616
|
}
|
|
49138
49617
|
return results;
|
|
49139
49618
|
}
|
|
49140
49619
|
function writeTraceMcpEntry(configPath, entry) {
|
|
49141
|
-
const dir =
|
|
49620
|
+
const dir = path70.dirname(configPath);
|
|
49142
49621
|
if (!fs59.existsSync(dir)) fs59.mkdirSync(dir, { recursive: true });
|
|
49143
49622
|
let config = {};
|
|
49144
49623
|
let isNew = true;
|
|
@@ -49159,15 +49638,15 @@ function writeTraceMcpEntry(configPath, entry) {
|
|
|
49159
49638
|
function getConfigPath(name, projectRoot, scope) {
|
|
49160
49639
|
switch (name) {
|
|
49161
49640
|
case "claude-code":
|
|
49162
|
-
return scope === "global" ?
|
|
49641
|
+
return scope === "global" ? path70.join(HOME, ".claude.json") : path70.join(projectRoot, ".mcp.json");
|
|
49163
49642
|
case "claude-desktop":
|
|
49164
|
-
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");
|
|
49165
49644
|
case "cursor":
|
|
49166
|
-
return scope === "global" ?
|
|
49645
|
+
return scope === "global" ? path70.join(HOME, ".cursor", "mcp.json") : path70.join(projectRoot, ".cursor", "mcp.json");
|
|
49167
49646
|
case "windsurf":
|
|
49168
|
-
return scope === "global" ?
|
|
49647
|
+
return scope === "global" ? path70.join(HOME, ".windsurf", "mcp.json") : path70.join(projectRoot, ".windsurf", "mcp.json");
|
|
49169
49648
|
case "continue":
|
|
49170
|
-
return scope === "global" ?
|
|
49649
|
+
return scope === "global" ? path70.join(HOME, ".continue", "mcpServers", "mcp.json") : path70.join(projectRoot, ".continue", "mcpServers", "mcp.json");
|
|
49171
49650
|
default:
|
|
49172
49651
|
return null;
|
|
49173
49652
|
}
|
|
@@ -49175,13 +49654,13 @@ function getConfigPath(name, projectRoot, scope) {
|
|
|
49175
49654
|
|
|
49176
49655
|
// src/init/claude-md.ts
|
|
49177
49656
|
import fs60 from "fs";
|
|
49178
|
-
import
|
|
49657
|
+
import path71 from "path";
|
|
49179
49658
|
var START_MARKER = "<!-- trace-mcp:start -->";
|
|
49180
49659
|
var END_MARKER = "<!-- trace-mcp:end -->";
|
|
49181
49660
|
var BLOCK = `${START_MARKER}
|
|
49182
49661
|
## trace-mcp Tool Routing
|
|
49183
49662
|
|
|
49184
|
-
|
|
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.
|
|
49185
49664
|
|
|
49186
49665
|
| Task | trace-mcp tool | Instead of |
|
|
49187
49666
|
|------|---------------|------------|
|
|
@@ -49190,16 +49669,22 @@ Use trace-mcp tools for code intelligence \u2014 they understand framework relat
|
|
|
49190
49669
|
| Read one symbol's source | \`get_symbol\` | Read (full file) |
|
|
49191
49670
|
| What breaks if I change X | \`get_change_impact\` | guessing |
|
|
49192
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 |
|
|
49193
49676
|
| Context for a task | \`get_feature_context\` | reading 15 files |
|
|
49194
49677
|
| Tests for a symbol | \`get_tests_for\` | Glob + Grep |
|
|
49195
49678
|
| HTTP request flow | \`get_request_flow\` | reading route files |
|
|
49196
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 |
|
|
49197
49682
|
|
|
49198
|
-
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.
|
|
49199
49684
|
Start sessions with \`get_project_map\` (summary_only=true).
|
|
49200
49685
|
${END_MARKER}`;
|
|
49201
49686
|
function updateClaudeMd(projectRoot, opts) {
|
|
49202
|
-
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");
|
|
49203
49688
|
if (opts.dryRun) {
|
|
49204
49689
|
if (!fs60.existsSync(filePath)) {
|
|
49205
49690
|
return { target: filePath, action: "skipped", detail: "Would create CLAUDE.md" };
|
|
@@ -49234,7 +49719,7 @@ function escapeRegex4(s) {
|
|
|
49234
49719
|
|
|
49235
49720
|
// src/init/hooks.ts
|
|
49236
49721
|
import fs61 from "fs";
|
|
49237
|
-
import
|
|
49722
|
+
import path72 from "path";
|
|
49238
49723
|
import os3 from "os";
|
|
49239
49724
|
|
|
49240
49725
|
// src/init/types.ts
|
|
@@ -49243,12 +49728,12 @@ var REINDEX_HOOK_VERSION = "0.1.0";
|
|
|
49243
49728
|
|
|
49244
49729
|
// src/init/hooks.ts
|
|
49245
49730
|
var HOME2 = os3.homedir();
|
|
49246
|
-
var HOOK_DEST =
|
|
49247
|
-
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");
|
|
49248
49733
|
function getHookSourcePath() {
|
|
49249
49734
|
const candidates = [
|
|
49250
|
-
|
|
49251
|
-
|
|
49735
|
+
path72.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-guard.sh"),
|
|
49736
|
+
path72.resolve(process.cwd(), "hooks", "trace-mcp-guard.sh")
|
|
49252
49737
|
];
|
|
49253
49738
|
for (const c of candidates) {
|
|
49254
49739
|
if (fs61.existsSync(c)) return c;
|
|
@@ -49256,17 +49741,17 @@ function getHookSourcePath() {
|
|
|
49256
49741
|
throw new Error("Could not find hooks/trace-mcp-guard.sh \u2014 trace-mcp installation may be corrupted.");
|
|
49257
49742
|
}
|
|
49258
49743
|
function installGuardHook(opts) {
|
|
49259
|
-
const settingsPath = opts.global ?
|
|
49744
|
+
const settingsPath = opts.global ? path72.join(HOME2, ".claude", "settings.json") : path72.resolve(process.cwd(), ".claude", "settings.local.json");
|
|
49260
49745
|
if (opts.dryRun) {
|
|
49261
49746
|
return { target: HOOK_DEST, action: "skipped", detail: "Would install guard hook" };
|
|
49262
49747
|
}
|
|
49263
49748
|
const hookSrc = getHookSourcePath();
|
|
49264
|
-
const hookDir =
|
|
49749
|
+
const hookDir = path72.dirname(HOOK_DEST);
|
|
49265
49750
|
if (!fs61.existsSync(hookDir)) fs61.mkdirSync(hookDir, { recursive: true });
|
|
49266
49751
|
const isUpdate = fs61.existsSync(HOOK_DEST);
|
|
49267
49752
|
fs61.copyFileSync(hookSrc, HOOK_DEST);
|
|
49268
49753
|
fs61.chmodSync(HOOK_DEST, 493);
|
|
49269
|
-
const settingsDir =
|
|
49754
|
+
const settingsDir = path72.dirname(settingsPath);
|
|
49270
49755
|
if (!fs61.existsSync(settingsDir)) fs61.mkdirSync(settingsDir, { recursive: true });
|
|
49271
49756
|
const settings = fs61.existsSync(settingsPath) ? JSON.parse(fs61.readFileSync(settingsPath, "utf-8")) : {};
|
|
49272
49757
|
if (!settings.hooks) settings.hooks = {};
|
|
@@ -49292,7 +49777,7 @@ function installGuardHook(opts) {
|
|
|
49292
49777
|
};
|
|
49293
49778
|
}
|
|
49294
49779
|
function uninstallGuardHook(opts) {
|
|
49295
|
-
const settingsPath = opts.global ?
|
|
49780
|
+
const settingsPath = opts.global ? path72.join(HOME2, ".claude", "settings.json") : path72.resolve(process.cwd(), ".claude", "settings.local.json");
|
|
49296
49781
|
if (fs61.existsSync(settingsPath)) {
|
|
49297
49782
|
const settings = JSON.parse(fs61.readFileSync(settingsPath, "utf-8"));
|
|
49298
49783
|
const pre = settings.hooks?.PreToolUse;
|
|
@@ -49314,8 +49799,8 @@ function isHookOutdated(installedVersion) {
|
|
|
49314
49799
|
}
|
|
49315
49800
|
function getReindexHookSourcePath() {
|
|
49316
49801
|
const candidates = [
|
|
49317
|
-
|
|
49318
|
-
|
|
49802
|
+
path72.resolve(import.meta.dirname ?? ".", "..", "..", "hooks", "trace-mcp-reindex.sh"),
|
|
49803
|
+
path72.resolve(process.cwd(), "hooks", "trace-mcp-reindex.sh")
|
|
49319
49804
|
];
|
|
49320
49805
|
for (const c of candidates) {
|
|
49321
49806
|
if (fs61.existsSync(c)) return c;
|
|
@@ -49323,17 +49808,17 @@ function getReindexHookSourcePath() {
|
|
|
49323
49808
|
throw new Error("Could not find hooks/trace-mcp-reindex.sh \u2014 trace-mcp installation may be corrupted.");
|
|
49324
49809
|
}
|
|
49325
49810
|
function installReindexHook(opts) {
|
|
49326
|
-
const settingsPath = opts.global ?
|
|
49811
|
+
const settingsPath = opts.global ? path72.join(HOME2, ".claude", "settings.json") : path72.resolve(process.cwd(), ".claude", "settings.local.json");
|
|
49327
49812
|
if (opts.dryRun) {
|
|
49328
49813
|
return { target: REINDEX_HOOK_DEST, action: "skipped", detail: "Would install reindex hook" };
|
|
49329
49814
|
}
|
|
49330
49815
|
const hookSrc = getReindexHookSourcePath();
|
|
49331
|
-
const hookDir =
|
|
49816
|
+
const hookDir = path72.dirname(REINDEX_HOOK_DEST);
|
|
49332
49817
|
if (!fs61.existsSync(hookDir)) fs61.mkdirSync(hookDir, { recursive: true });
|
|
49333
49818
|
const isUpdate = fs61.existsSync(REINDEX_HOOK_DEST);
|
|
49334
49819
|
fs61.copyFileSync(hookSrc, REINDEX_HOOK_DEST);
|
|
49335
49820
|
fs61.chmodSync(REINDEX_HOOK_DEST, 493);
|
|
49336
|
-
const settingsDir =
|
|
49821
|
+
const settingsDir = path72.dirname(settingsPath);
|
|
49337
49822
|
if (!fs61.existsSync(settingsDir)) fs61.mkdirSync(settingsDir, { recursive: true });
|
|
49338
49823
|
const settings = fs61.existsSync(settingsPath) ? JSON.parse(fs61.readFileSync(settingsPath, "utf-8")) : {};
|
|
49339
49824
|
if (!settings.hooks) settings.hooks = {};
|
|
@@ -49361,10 +49846,10 @@ function installReindexHook(opts) {
|
|
|
49361
49846
|
|
|
49362
49847
|
// src/init/ide-rules.ts
|
|
49363
49848
|
import fs62 from "fs";
|
|
49364
|
-
import
|
|
49849
|
+
import path73 from "path";
|
|
49365
49850
|
var START_MARKER2 = "<!-- trace-mcp:start -->";
|
|
49366
49851
|
var END_MARKER2 = "<!-- trace-mcp:end -->";
|
|
49367
|
-
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.
|
|
49368
49853
|
|
|
49369
49854
|
## Tool Routing
|
|
49370
49855
|
|
|
@@ -49375,13 +49860,19 @@ var TOOL_ROUTING_POLICY = `Use trace-mcp MCP tools for all code intelligence tas
|
|
|
49375
49860
|
| Read one symbol's source | \`get_symbol\` | reading full file |
|
|
49376
49861
|
| What breaks if I change X | \`get_change_impact\` | guessing |
|
|
49377
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 |
|
|
49378
49867
|
| Context for a task | \`get_feature_context\` | reading many files |
|
|
49379
49868
|
| Tests for a symbol | \`get_tests_for\` | searching test files |
|
|
49380
49869
|
| HTTP request flow | \`get_request_flow\` | reading route files |
|
|
49381
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 |
|
|
49382
49873
|
|
|
49383
49874
|
Start sessions with \`get_project_map\` (summary_only=true) to get project overview.
|
|
49384
|
-
Use built-in file reading
|
|
49875
|
+
Use built-in file reading ONLY for non-code files (.md, .json, .yaml, config) or before editing.`;
|
|
49385
49876
|
var CURSOR_RULE = `---
|
|
49386
49877
|
description: trace-mcp tool routing \u2014 prefer trace-mcp MCP tools over built-in search for code intelligence
|
|
49387
49878
|
globs:
|
|
@@ -49391,9 +49882,9 @@ alwaysApply: true
|
|
|
49391
49882
|
${TOOL_ROUTING_POLICY}
|
|
49392
49883
|
`;
|
|
49393
49884
|
function installCursorRules(projectRoot, opts) {
|
|
49394
|
-
const base = opts.global ?
|
|
49395
|
-
const rulesDir =
|
|
49396
|
-
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");
|
|
49397
49888
|
if (opts.dryRun) {
|
|
49398
49889
|
if (fs62.existsSync(filePath)) {
|
|
49399
49890
|
const content = fs62.readFileSync(filePath, "utf-8");
|
|
@@ -49422,7 +49913,7 @@ var WINDSURF_BLOCK = `${START_MARKER2}
|
|
|
49422
49913
|
${TOOL_ROUTING_POLICY}
|
|
49423
49914
|
${END_MARKER2}`;
|
|
49424
49915
|
function installWindsurfRules(projectRoot, opts) {
|
|
49425
|
-
const filePath = opts.global ?
|
|
49916
|
+
const filePath = opts.global ? path73.join(process.env.HOME ?? process.env.USERPROFILE ?? "", ".windsurfrules") : path73.join(projectRoot, ".windsurfrules");
|
|
49426
49917
|
if (opts.dryRun) {
|
|
49427
49918
|
if (!fs62.existsSync(filePath)) {
|
|
49428
49919
|
return { target: filePath, action: "skipped", detail: "Would create .windsurfrules" };
|
|
@@ -49457,12 +49948,12 @@ function escapeRegex5(s) {
|
|
|
49457
49948
|
|
|
49458
49949
|
// src/init/detector.ts
|
|
49459
49950
|
import fs63 from "fs";
|
|
49460
|
-
import
|
|
49951
|
+
import path74 from "path";
|
|
49461
49952
|
import os4 from "os";
|
|
49462
49953
|
import Database4 from "better-sqlite3";
|
|
49463
49954
|
var HOME3 = os4.homedir();
|
|
49464
49955
|
function detectProject(dir) {
|
|
49465
|
-
const projectRoot =
|
|
49956
|
+
const projectRoot = path74.resolve(dir);
|
|
49466
49957
|
const ctx = buildProjectContext(projectRoot);
|
|
49467
49958
|
const packageManagers = detectPackageManagers(projectRoot);
|
|
49468
49959
|
const registry = new PluginRegistry();
|
|
@@ -49491,7 +49982,7 @@ function detectProject(dir) {
|
|
|
49491
49982
|
const mcpClients = detectMcpClients(projectRoot);
|
|
49492
49983
|
const existingConfig = detectExistingConfig(projectRoot);
|
|
49493
49984
|
const existingDb = detectExistingDb(projectRoot);
|
|
49494
|
-
const claudeMdPath =
|
|
49985
|
+
const claudeMdPath = path74.join(projectRoot, "CLAUDE.md");
|
|
49495
49986
|
const hasClaudeMd = fs63.existsSync(claudeMdPath);
|
|
49496
49987
|
const claudeMdHasTraceMcpBlock = hasClaudeMd && fs63.readFileSync(claudeMdPath, "utf-8").includes("<!-- trace-mcp:start -->");
|
|
49497
49988
|
const { hasGuardHook, guardHookVersion } = detectGuardHook();
|
|
@@ -49512,8 +50003,8 @@ function detectProject(dir) {
|
|
|
49512
50003
|
function detectPackageManagers(root) {
|
|
49513
50004
|
const managers = [];
|
|
49514
50005
|
const check = (file, type, lockfiles) => {
|
|
49515
|
-
if (fs63.existsSync(
|
|
49516
|
-
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)));
|
|
49517
50008
|
managers.push({ type, lockfile });
|
|
49518
50009
|
}
|
|
49519
50010
|
};
|
|
@@ -49527,7 +50018,7 @@ function detectPackageManagers(root) {
|
|
|
49527
50018
|
check("pyproject.toml", "poetry", ["poetry.lock", "uv.lock"]);
|
|
49528
50019
|
if (managers.length > 0 && managers[managers.length - 1].type === "poetry") {
|
|
49529
50020
|
if (managers[managers.length - 1].lockfile === "uv.lock") managers[managers.length - 1].type = "uv";
|
|
49530
|
-
else if (!managers[managers.length - 1].lockfile && fs63.existsSync(
|
|
50021
|
+
else if (!managers[managers.length - 1].lockfile && fs63.existsSync(path74.join(root, "requirements.txt"))) {
|
|
49531
50022
|
managers[managers.length - 1].type = "pip";
|
|
49532
50023
|
}
|
|
49533
50024
|
}
|
|
@@ -49536,7 +50027,7 @@ function detectPackageManagers(root) {
|
|
|
49536
50027
|
check("Gemfile", "bundler", ["Gemfile.lock"]);
|
|
49537
50028
|
check("pom.xml", "maven", []);
|
|
49538
50029
|
if (!managers.some((m) => m.type === "maven")) {
|
|
49539
|
-
if (fs63.existsSync(
|
|
50030
|
+
if (fs63.existsSync(path74.join(root, "build.gradle")) || fs63.existsSync(path74.join(root, "build.gradle.kts"))) {
|
|
49540
50031
|
managers.push({ type: "gradle", lockfile: void 0 });
|
|
49541
50032
|
}
|
|
49542
50033
|
}
|
|
@@ -49555,40 +50046,40 @@ function detectMcpClients(projectRoot) {
|
|
|
49555
50046
|
}
|
|
49556
50047
|
};
|
|
49557
50048
|
if (projectRoot) {
|
|
49558
|
-
checkConfig("claude-code",
|
|
50049
|
+
checkConfig("claude-code", path74.join(projectRoot, ".mcp.json"));
|
|
49559
50050
|
}
|
|
49560
|
-
checkConfig("claude-code",
|
|
49561
|
-
checkConfig("claude-code",
|
|
50051
|
+
checkConfig("claude-code", path74.join(HOME3, ".claude.json"));
|
|
50052
|
+
checkConfig("claude-code", path74.join(HOME3, ".claude", "settings.json"));
|
|
49562
50053
|
const platform = os4.platform();
|
|
49563
50054
|
if (platform === "darwin") {
|
|
49564
|
-
checkConfig("claude-desktop",
|
|
50055
|
+
checkConfig("claude-desktop", path74.join(HOME3, "Library", "Application Support", "Claude", "claude_desktop_config.json"));
|
|
49565
50056
|
} else if (platform === "win32") {
|
|
49566
|
-
const appData = process.env.APPDATA ??
|
|
49567
|
-
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"));
|
|
49568
50059
|
}
|
|
49569
|
-
checkConfig("cursor",
|
|
50060
|
+
checkConfig("cursor", path74.join(HOME3, ".cursor", "mcp.json"));
|
|
49570
50061
|
if (projectRoot && !clients.some((c) => c.name === "cursor")) {
|
|
49571
|
-
checkConfig("cursor",
|
|
50062
|
+
checkConfig("cursor", path74.join(projectRoot, ".cursor", "mcp.json"));
|
|
49572
50063
|
}
|
|
49573
|
-
checkConfig("windsurf",
|
|
50064
|
+
checkConfig("windsurf", path74.join(HOME3, ".windsurf", "mcp.json"));
|
|
49574
50065
|
if (projectRoot && !clients.some((c) => c.name === "windsurf")) {
|
|
49575
|
-
checkConfig("windsurf",
|
|
50066
|
+
checkConfig("windsurf", path74.join(projectRoot, ".windsurf", "mcp.json"));
|
|
49576
50067
|
}
|
|
49577
|
-
checkConfig("continue",
|
|
50068
|
+
checkConfig("continue", path74.join(HOME3, ".continue", "mcpServers", "mcp.json"));
|
|
49578
50069
|
if (projectRoot && !clients.some((c) => c.name === "continue")) {
|
|
49579
|
-
checkConfig("continue",
|
|
50070
|
+
checkConfig("continue", path74.join(projectRoot, ".continue", "mcpServers", "mcp.json"));
|
|
49580
50071
|
}
|
|
49581
50072
|
return clients;
|
|
49582
50073
|
}
|
|
49583
50074
|
function detectExistingConfig(root) {
|
|
49584
50075
|
const candidates = [
|
|
49585
|
-
|
|
49586
|
-
|
|
50076
|
+
path74.join(root, ".trace-mcp.json"),
|
|
50077
|
+
path74.join(root, ".config", "trace-mcp.json")
|
|
49587
50078
|
];
|
|
49588
50079
|
for (const p4 of candidates) {
|
|
49589
50080
|
if (fs63.existsSync(p4)) return { path: p4 };
|
|
49590
50081
|
}
|
|
49591
|
-
const pkgPath =
|
|
50082
|
+
const pkgPath = path74.join(root, "package.json");
|
|
49592
50083
|
if (fs63.existsSync(pkgPath)) {
|
|
49593
50084
|
try {
|
|
49594
50085
|
const pkg = JSON.parse(fs63.readFileSync(pkgPath, "utf-8"));
|
|
@@ -49599,7 +50090,7 @@ function detectExistingConfig(root) {
|
|
|
49599
50090
|
return null;
|
|
49600
50091
|
}
|
|
49601
50092
|
function detectExistingDb(root, globalDbPath) {
|
|
49602
|
-
const candidates = globalDbPath ? [globalDbPath,
|
|
50093
|
+
const candidates = globalDbPath ? [globalDbPath, path74.join(root, ".trace-mcp", "index.db")] : [path74.join(root, ".trace-mcp", "index.db")];
|
|
49603
50094
|
const dbPath = candidates.find((p4) => fs63.existsSync(p4));
|
|
49604
50095
|
if (!dbPath) return null;
|
|
49605
50096
|
try {
|
|
@@ -49615,7 +50106,7 @@ function detectExistingDb(root, globalDbPath) {
|
|
|
49615
50106
|
}
|
|
49616
50107
|
}
|
|
49617
50108
|
function detectGuardHook() {
|
|
49618
|
-
const hookPath =
|
|
50109
|
+
const hookPath = path74.join(HOME3, ".claude", "hooks", "trace-mcp-guard.sh");
|
|
49619
50110
|
if (!fs63.existsSync(hookPath)) return { hasGuardHook: false, guardHookVersion: null };
|
|
49620
50111
|
const content = fs63.readFileSync(hookPath, "utf-8");
|
|
49621
50112
|
const match = content.match(/^# trace-mcp-guard v(.+)$/m);
|
|
@@ -49627,7 +50118,7 @@ function detectGuardHook() {
|
|
|
49627
50118
|
|
|
49628
50119
|
// src/init/conflict-detector.ts
|
|
49629
50120
|
import fs64 from "fs";
|
|
49630
|
-
import
|
|
50121
|
+
import path75 from "path";
|
|
49631
50122
|
import os5 from "os";
|
|
49632
50123
|
var HOME4 = os5.homedir();
|
|
49633
50124
|
var COMPETING_MCP_SERVERS = {
|
|
@@ -49721,9 +50212,9 @@ var COMPETING_PROJECT_FILES = [
|
|
|
49721
50212
|
{ file: ".greptile.yaml", competitor: "greptile" }
|
|
49722
50213
|
];
|
|
49723
50214
|
var COMPETING_GLOBAL_DIRS = [
|
|
49724
|
-
{ dir:
|
|
49725
|
-
{ dir:
|
|
49726
|
-
{ 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" }
|
|
49727
50218
|
];
|
|
49728
50219
|
function detectConflicts(projectRoot) {
|
|
49729
50220
|
const conflicts = [];
|
|
@@ -49782,31 +50273,31 @@ function getMcpConfigPaths(projectRoot) {
|
|
|
49782
50273
|
const paths = [];
|
|
49783
50274
|
const platform = os5.platform();
|
|
49784
50275
|
if (projectRoot) {
|
|
49785
|
-
paths.push({ clientName: "claude-code", configPath:
|
|
50276
|
+
paths.push({ clientName: "claude-code", configPath: path75.join(projectRoot, ".mcp.json") });
|
|
49786
50277
|
}
|
|
49787
|
-
paths.push({ clientName: "claude-code", configPath:
|
|
50278
|
+
paths.push({ clientName: "claude-code", configPath: path75.join(HOME4, ".claude", "settings.json") });
|
|
49788
50279
|
if (platform === "darwin") {
|
|
49789
|
-
paths.push({ clientName: "claude-desktop", configPath:
|
|
50280
|
+
paths.push({ clientName: "claude-desktop", configPath: path75.join(HOME4, "Library", "Application Support", "Claude", "claude_desktop_config.json") });
|
|
49790
50281
|
} else if (platform === "win32") {
|
|
49791
|
-
const appData = process.env.APPDATA ??
|
|
49792
|
-
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") });
|
|
49793
50284
|
}
|
|
49794
|
-
paths.push({ clientName: "cursor", configPath:
|
|
50285
|
+
paths.push({ clientName: "cursor", configPath: path75.join(HOME4, ".cursor", "mcp.json") });
|
|
49795
50286
|
if (projectRoot) {
|
|
49796
|
-
paths.push({ clientName: "cursor", configPath:
|
|
50287
|
+
paths.push({ clientName: "cursor", configPath: path75.join(projectRoot, ".cursor", "mcp.json") });
|
|
49797
50288
|
}
|
|
49798
|
-
paths.push({ clientName: "windsurf", configPath:
|
|
50289
|
+
paths.push({ clientName: "windsurf", configPath: path75.join(HOME4, ".windsurf", "mcp.json") });
|
|
49799
50290
|
if (projectRoot) {
|
|
49800
|
-
paths.push({ clientName: "windsurf", configPath:
|
|
50291
|
+
paths.push({ clientName: "windsurf", configPath: path75.join(projectRoot, ".windsurf", "mcp.json") });
|
|
49801
50292
|
}
|
|
49802
|
-
paths.push({ clientName: "continue", configPath:
|
|
50293
|
+
paths.push({ clientName: "continue", configPath: path75.join(HOME4, ".continue", "mcpServers", "mcp.json") });
|
|
49803
50294
|
return paths;
|
|
49804
50295
|
}
|
|
49805
50296
|
function scanHooksInSettings() {
|
|
49806
50297
|
const conflicts = [];
|
|
49807
50298
|
const settingsFiles = [
|
|
49808
|
-
|
|
49809
|
-
|
|
50299
|
+
path75.join(HOME4, ".claude", "settings.json"),
|
|
50300
|
+
path75.join(HOME4, ".claude", "settings.local.json")
|
|
49810
50301
|
];
|
|
49811
50302
|
for (const settingsPath of settingsFiles) {
|
|
49812
50303
|
if (!fs64.existsSync(settingsPath)) continue;
|
|
@@ -49848,7 +50339,7 @@ function scanHooksInSettings() {
|
|
|
49848
50339
|
}
|
|
49849
50340
|
function scanHookScriptFiles() {
|
|
49850
50341
|
const conflicts = [];
|
|
49851
|
-
const hooksDir =
|
|
50342
|
+
const hooksDir = path75.join(HOME4, ".claude", "hooks");
|
|
49852
50343
|
if (!fs64.existsSync(hooksDir)) return conflicts;
|
|
49853
50344
|
let files;
|
|
49854
50345
|
try {
|
|
@@ -49860,7 +50351,7 @@ function scanHookScriptFiles() {
|
|
|
49860
50351
|
if (file.startsWith("trace-mcp")) continue;
|
|
49861
50352
|
for (const { pattern, competitor } of COMPETING_HOOK_PATTERNS) {
|
|
49862
50353
|
if (pattern.test(file)) {
|
|
49863
|
-
const filePath =
|
|
50354
|
+
const filePath = path75.join(hooksDir, file);
|
|
49864
50355
|
conflicts.push({
|
|
49865
50356
|
id: `hook_script:${file}:${competitor}`,
|
|
49866
50357
|
category: "hook_script",
|
|
@@ -49879,13 +50370,13 @@ function scanHookScriptFiles() {
|
|
|
49879
50370
|
function scanClaudeMdFiles(projectRoot) {
|
|
49880
50371
|
const conflicts = [];
|
|
49881
50372
|
const files = [
|
|
49882
|
-
|
|
49883
|
-
|
|
50373
|
+
path75.join(HOME4, ".claude", "CLAUDE.md"),
|
|
50374
|
+
path75.join(HOME4, ".claude", "AGENTS.md")
|
|
49884
50375
|
];
|
|
49885
50376
|
if (projectRoot) {
|
|
49886
50377
|
files.push(
|
|
49887
|
-
|
|
49888
|
-
|
|
50378
|
+
path75.join(projectRoot, "CLAUDE.md"),
|
|
50379
|
+
path75.join(projectRoot, "AGENTS.md")
|
|
49889
50380
|
);
|
|
49890
50381
|
}
|
|
49891
50382
|
for (const filePath of files) {
|
|
@@ -49919,35 +50410,35 @@ function scanClaudeMdFiles(projectRoot) {
|
|
|
49919
50410
|
function scanIdeRuleFiles(projectRoot) {
|
|
49920
50411
|
const conflicts = [];
|
|
49921
50412
|
const ruleFiles = [];
|
|
49922
|
-
ruleFiles.push({ path:
|
|
49923
|
-
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)" });
|
|
49924
50415
|
if (projectRoot) {
|
|
49925
|
-
ruleFiles.push({ path:
|
|
49926
|
-
ruleFiles.push({ path:
|
|
49927
|
-
ruleFiles.push({ path:
|
|
49928
|
-
ruleFiles.push({ path:
|
|
49929
|
-
ruleFiles.push({ path:
|
|
49930
|
-
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");
|
|
49931
50422
|
if (fs64.existsSync(clineRulesDir)) {
|
|
49932
50423
|
try {
|
|
49933
50424
|
const stat = fs64.statSync(clineRulesDir);
|
|
49934
50425
|
if (stat.isDirectory()) {
|
|
49935
50426
|
for (const file of fs64.readdirSync(clineRulesDir)) {
|
|
49936
|
-
ruleFiles.push({ path:
|
|
50427
|
+
ruleFiles.push({ path: path75.join(clineRulesDir, file), type: `.clinerules/${file}` });
|
|
49937
50428
|
}
|
|
49938
50429
|
}
|
|
49939
50430
|
} catch {
|
|
49940
50431
|
}
|
|
49941
50432
|
}
|
|
49942
50433
|
}
|
|
49943
|
-
const cursorRulesDirs = [
|
|
49944
|
-
if (projectRoot) cursorRulesDirs.push(
|
|
50434
|
+
const cursorRulesDirs = [path75.join(HOME4, ".cursor", "rules")];
|
|
50435
|
+
if (projectRoot) cursorRulesDirs.push(path75.join(projectRoot, ".cursor", "rules"));
|
|
49945
50436
|
for (const rulesDir of cursorRulesDirs) {
|
|
49946
50437
|
if (!fs64.existsSync(rulesDir)) continue;
|
|
49947
50438
|
try {
|
|
49948
50439
|
for (const file of fs64.readdirSync(rulesDir)) {
|
|
49949
50440
|
if (!file.endsWith(".mdc") || file === "trace-mcp.mdc") continue;
|
|
49950
|
-
ruleFiles.push({ path:
|
|
50441
|
+
ruleFiles.push({ path: path75.join(rulesDir, file), type: `.cursor/rules/${file}` });
|
|
49951
50442
|
}
|
|
49952
50443
|
} catch {
|
|
49953
50444
|
}
|
|
@@ -49981,7 +50472,7 @@ function scanIdeRuleFiles(projectRoot) {
|
|
|
49981
50472
|
function scanProjectConfigFiles(projectRoot) {
|
|
49982
50473
|
const conflicts = [];
|
|
49983
50474
|
for (const { file, competitor } of COMPETING_PROJECT_FILES) {
|
|
49984
|
-
const filePath =
|
|
50475
|
+
const filePath = path75.join(projectRoot, file);
|
|
49985
50476
|
if (!fs64.existsSync(filePath)) continue;
|
|
49986
50477
|
conflicts.push({
|
|
49987
50478
|
id: `config:${competitor}:${file}`,
|
|
@@ -50005,7 +50496,7 @@ function scanProjectConfigDirs(projectRoot) {
|
|
|
50005
50496
|
{ dir: ".continue", competitor: "continue.dev" }
|
|
50006
50497
|
];
|
|
50007
50498
|
for (const { dir, competitor } of dirs) {
|
|
50008
|
-
const fullPath =
|
|
50499
|
+
const fullPath = path75.join(projectRoot, dir);
|
|
50009
50500
|
if (!fs64.existsSync(fullPath)) continue;
|
|
50010
50501
|
let stat;
|
|
50011
50502
|
try {
|
|
@@ -50031,18 +50522,18 @@ function scanProjectConfigDirs(projectRoot) {
|
|
|
50031
50522
|
function scanContinueConfigs(projectRoot) {
|
|
50032
50523
|
const conflicts = [];
|
|
50033
50524
|
const configPaths = [
|
|
50034
|
-
|
|
50035
|
-
|
|
50525
|
+
path75.join(HOME4, ".continue", "config.yaml"),
|
|
50526
|
+
path75.join(HOME4, ".continue", "config.json")
|
|
50036
50527
|
];
|
|
50037
50528
|
if (projectRoot) {
|
|
50038
50529
|
configPaths.push(
|
|
50039
|
-
|
|
50040
|
-
|
|
50530
|
+
path75.join(projectRoot, ".continue", "config.yaml"),
|
|
50531
|
+
path75.join(projectRoot, ".continue", "config.json")
|
|
50041
50532
|
);
|
|
50042
50533
|
}
|
|
50043
|
-
const mcpServersDirs = [
|
|
50534
|
+
const mcpServersDirs = [path75.join(HOME4, ".continue", "mcpServers")];
|
|
50044
50535
|
if (projectRoot) {
|
|
50045
|
-
mcpServersDirs.push(
|
|
50536
|
+
mcpServersDirs.push(path75.join(projectRoot, ".continue", "mcpServers"));
|
|
50046
50537
|
}
|
|
50047
50538
|
for (const mcpDir of mcpServersDirs) {
|
|
50048
50539
|
if (!fs64.existsSync(mcpDir)) continue;
|
|
@@ -50054,7 +50545,7 @@ function scanContinueConfigs(projectRoot) {
|
|
|
50054
50545
|
}
|
|
50055
50546
|
for (const file of files) {
|
|
50056
50547
|
if (!file.endsWith(".json")) continue;
|
|
50057
|
-
const filePath =
|
|
50548
|
+
const filePath = path75.join(mcpDir, file);
|
|
50058
50549
|
let content;
|
|
50059
50550
|
try {
|
|
50060
50551
|
content = fs64.readFileSync(filePath, "utf-8");
|
|
@@ -50083,11 +50574,11 @@ function scanContinueConfigs(projectRoot) {
|
|
|
50083
50574
|
}
|
|
50084
50575
|
function scanGitHooks(projectRoot) {
|
|
50085
50576
|
const conflicts = [];
|
|
50086
|
-
const hooksDir =
|
|
50577
|
+
const hooksDir = path75.join(projectRoot, ".git", "hooks");
|
|
50087
50578
|
if (!fs64.existsSync(hooksDir)) return conflicts;
|
|
50088
50579
|
const hookFiles = ["pre-commit", "post-commit", "prepare-commit-msg"];
|
|
50089
50580
|
for (const hookFile of hookFiles) {
|
|
50090
|
-
const hookPath =
|
|
50581
|
+
const hookPath = path75.join(hooksDir, hookFile);
|
|
50091
50582
|
if (!fs64.existsSync(hookPath)) continue;
|
|
50092
50583
|
let content;
|
|
50093
50584
|
try {
|
|
@@ -50197,8 +50688,8 @@ function fixMcpServer(conflict, opts) {
|
|
|
50197
50688
|
}
|
|
50198
50689
|
fs65.writeFileSync(configPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
50199
50690
|
return { conflictId: conflict.id, action: "removed", detail: `Removed "${serverName}" from ${shortPath2(configPath)}`, target: configPath };
|
|
50200
|
-
} catch (
|
|
50201
|
-
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 };
|
|
50202
50693
|
}
|
|
50203
50694
|
}
|
|
50204
50695
|
function fixHookInSettings(conflict, opts) {
|
|
@@ -50241,8 +50732,8 @@ function fixHookInSettings(conflict, opts) {
|
|
|
50241
50732
|
}
|
|
50242
50733
|
fs65.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
50243
50734
|
return { conflictId: conflict.id, action: "removed", detail: `Removed ${competitor} hooks from ${shortPath2(settingsPath)}`, target: settingsPath };
|
|
50244
|
-
} catch (
|
|
50245
|
-
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 };
|
|
50246
50737
|
}
|
|
50247
50738
|
}
|
|
50248
50739
|
function fixHookScript(conflict, opts) {
|
|
@@ -50256,8 +50747,8 @@ function fixHookScript(conflict, opts) {
|
|
|
50256
50747
|
try {
|
|
50257
50748
|
fs65.unlinkSync(scriptPath);
|
|
50258
50749
|
return { conflictId: conflict.id, action: "removed", detail: `Deleted ${shortPath2(scriptPath)}`, target: scriptPath };
|
|
50259
|
-
} catch (
|
|
50260
|
-
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 };
|
|
50261
50752
|
}
|
|
50262
50753
|
}
|
|
50263
50754
|
function fixClaudeMdBlock(conflict, opts) {
|
|
@@ -50270,30 +50761,20 @@ function fixClaudeMdBlock(conflict, opts) {
|
|
|
50270
50761
|
}
|
|
50271
50762
|
try {
|
|
50272
50763
|
const content = fs65.readFileSync(filePath, "utf-8");
|
|
50273
|
-
const
|
|
50274
|
-
|
|
50275
|
-
|
|
50276
|
-
|
|
50277
|
-
|
|
50278
|
-
|
|
50279
|
-
/<!-- ?cody:start ?-->[\s\S]*?<!-- ?cody:end ?-->\n?/gi,
|
|
50280
|
-
/<!-- ?greptile:start ?-->[\s\S]*?<!-- ?greptile:end ?-->\n?/gi,
|
|
50281
|
-
/<!-- ?sourcegraph:start ?-->[\s\S]*?<!-- ?sourcegraph:end ?-->\n?/gi,
|
|
50282
|
-
/<!-- ?code-compass:start ?-->[\s\S]*?<!-- ?code-compass:end ?-->\n?/gi,
|
|
50283
|
-
/<!-- ?repo-map:start ?-->[\s\S]*?<!-- ?repo-map:end ?-->\n?/gi
|
|
50284
|
-
];
|
|
50285
|
-
let updated = content;
|
|
50286
|
-
for (const pattern of markerPatterns) {
|
|
50287
|
-
updated = updated.replace(pattern, "");
|
|
50288
|
-
}
|
|
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, "");
|
|
50289
50770
|
updated = updated.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
50290
50771
|
if (updated === content) {
|
|
50291
50772
|
return { conflictId: conflict.id, action: "skipped", detail: "No marker-delimited blocks found to remove", target: filePath };
|
|
50292
50773
|
}
|
|
50293
50774
|
fs65.writeFileSync(filePath, updated);
|
|
50294
50775
|
return { conflictId: conflict.id, action: "cleaned", detail: `Removed ${conflict.competitor} block from ${shortPath2(filePath)}`, target: filePath };
|
|
50295
|
-
} catch (
|
|
50296
|
-
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 };
|
|
50297
50778
|
}
|
|
50298
50779
|
}
|
|
50299
50780
|
function fixConfigFile(conflict, opts) {
|
|
@@ -50312,8 +50793,8 @@ function fixConfigFile(conflict, opts) {
|
|
|
50312
50793
|
fs65.unlinkSync(filePath);
|
|
50313
50794
|
}
|
|
50314
50795
|
return { conflictId: conflict.id, action: "removed", detail: `Deleted ${shortPath2(filePath)}`, target: filePath };
|
|
50315
|
-
} catch (
|
|
50316
|
-
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 };
|
|
50317
50798
|
}
|
|
50318
50799
|
}
|
|
50319
50800
|
function fixGlobalArtifact(conflict, opts) {
|
|
@@ -50327,8 +50808,8 @@ function fixGlobalArtifact(conflict, opts) {
|
|
|
50327
50808
|
try {
|
|
50328
50809
|
fs65.rmSync(dirPath, { recursive: true, force: true });
|
|
50329
50810
|
return { conflictId: conflict.id, action: "removed", detail: `Removed ${shortPath2(dirPath)}`, target: dirPath };
|
|
50330
|
-
} catch (
|
|
50331
|
-
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 };
|
|
50332
50813
|
}
|
|
50333
50814
|
}
|
|
50334
50815
|
function shortPath2(p4) {
|
|
@@ -50339,7 +50820,7 @@ function shortPath2(p4) {
|
|
|
50339
50820
|
|
|
50340
50821
|
// src/project-root.ts
|
|
50341
50822
|
import fs66 from "fs";
|
|
50342
|
-
import
|
|
50823
|
+
import path76 from "path";
|
|
50343
50824
|
var ROOT_MARKERS = [
|
|
50344
50825
|
".git",
|
|
50345
50826
|
"package.json",
|
|
@@ -50353,14 +50834,14 @@ var ROOT_MARKERS = [
|
|
|
50353
50834
|
"build.gradle.kts"
|
|
50354
50835
|
];
|
|
50355
50836
|
function findProjectRoot(from) {
|
|
50356
|
-
let dir =
|
|
50837
|
+
let dir = path76.resolve(from ?? process.cwd());
|
|
50357
50838
|
while (true) {
|
|
50358
50839
|
for (const marker of ROOT_MARKERS) {
|
|
50359
|
-
if (fs66.existsSync(
|
|
50840
|
+
if (fs66.existsSync(path76.join(dir, marker))) {
|
|
50360
50841
|
return dir;
|
|
50361
50842
|
}
|
|
50362
50843
|
}
|
|
50363
|
-
const parent =
|
|
50844
|
+
const parent = path76.dirname(dir);
|
|
50364
50845
|
if (parent === dir) {
|
|
50365
50846
|
throw new Error(
|
|
50366
50847
|
`Could not find project root from ${from ?? process.cwd()}. Looked for: ${ROOT_MARKERS.join(", ")}`
|
|
@@ -50604,7 +51085,7 @@ function generateConfig(detection) {
|
|
|
50604
51085
|
|
|
50605
51086
|
// src/registry.ts
|
|
50606
51087
|
import fs67 from "fs";
|
|
50607
|
-
import
|
|
51088
|
+
import path77 from "path";
|
|
50608
51089
|
function emptyRegistry() {
|
|
50609
51090
|
return { version: 1, projects: {} };
|
|
50610
51091
|
}
|
|
@@ -50625,7 +51106,7 @@ function saveRegistry(reg) {
|
|
|
50625
51106
|
fs67.renameSync(tmp, REGISTRY_PATH);
|
|
50626
51107
|
}
|
|
50627
51108
|
function registerProject(root) {
|
|
50628
|
-
const absRoot =
|
|
51109
|
+
const absRoot = path77.resolve(root);
|
|
50629
51110
|
const reg = loadRegistry2();
|
|
50630
51111
|
if (reg.projects[absRoot]) {
|
|
50631
51112
|
return reg.projects[absRoot];
|
|
@@ -50642,7 +51123,7 @@ function registerProject(root) {
|
|
|
50642
51123
|
return entry;
|
|
50643
51124
|
}
|
|
50644
51125
|
function getProject(root) {
|
|
50645
|
-
const absRoot =
|
|
51126
|
+
const absRoot = path77.resolve(root);
|
|
50646
51127
|
const reg = loadRegistry2();
|
|
50647
51128
|
return reg.projects[absRoot] ?? null;
|
|
50648
51129
|
}
|
|
@@ -50651,7 +51132,7 @@ function listProjects() {
|
|
|
50651
51132
|
return Object.values(reg.projects);
|
|
50652
51133
|
}
|
|
50653
51134
|
function updateLastIndexed(root) {
|
|
50654
|
-
const absRoot =
|
|
51135
|
+
const absRoot = path77.resolve(root);
|
|
50655
51136
|
const reg = loadRegistry2();
|
|
50656
51137
|
if (reg.projects[absRoot]) {
|
|
50657
51138
|
reg.projects[absRoot].lastIndexed = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -50861,7 +51342,7 @@ function registerAndIndexProject(dir, opts) {
|
|
|
50861
51342
|
const config = generateConfig(detection);
|
|
50862
51343
|
saveProjectConfig(projectRoot, { root: config.root, include: config.include, exclude: config.exclude });
|
|
50863
51344
|
const dbPath = getDbPath(projectRoot);
|
|
50864
|
-
const oldDbPath =
|
|
51345
|
+
const oldDbPath = path78.join(projectRoot, ".trace-mcp", "index.db");
|
|
50865
51346
|
if (fs68.existsSync(oldDbPath) && !fs68.existsSync(dbPath)) {
|
|
50866
51347
|
fs68.copyFileSync(oldDbPath, dbPath);
|
|
50867
51348
|
}
|
|
@@ -50895,11 +51376,11 @@ function shortPath3(p4) {
|
|
|
50895
51376
|
// src/cli-upgrade.ts
|
|
50896
51377
|
import { Command as Command2 } from "commander";
|
|
50897
51378
|
import fs69 from "fs";
|
|
50898
|
-
import
|
|
51379
|
+
import path79 from "path";
|
|
50899
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) => {
|
|
50900
51381
|
const projectRoots = [];
|
|
50901
51382
|
if (dir) {
|
|
50902
|
-
projectRoots.push(
|
|
51383
|
+
projectRoots.push(path79.resolve(dir));
|
|
50903
51384
|
} else {
|
|
50904
51385
|
const projects = listProjects();
|
|
50905
51386
|
if (projects.length === 0) {
|
|
@@ -50981,7 +51462,7 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
|
|
|
50981
51462
|
console.log(header);
|
|
50982
51463
|
for (const { projectRoot, steps } of allSteps) {
|
|
50983
51464
|
console.log(`
|
|
50984
|
-
Project: ${
|
|
51465
|
+
Project: ${path79.basename(projectRoot)} (${projectRoot})`);
|
|
50985
51466
|
for (const step of steps) {
|
|
50986
51467
|
console.log(` ${step.action}: ${step.detail ?? step.target}`);
|
|
50987
51468
|
}
|
|
@@ -50993,10 +51474,10 @@ var upgradeCommand = new Command2("upgrade").description("Upgrade trace-mcp: run
|
|
|
50993
51474
|
// src/cli-add.ts
|
|
50994
51475
|
import { Command as Command3 } from "commander";
|
|
50995
51476
|
import fs70 from "fs";
|
|
50996
|
-
import
|
|
51477
|
+
import path80 from "path";
|
|
50997
51478
|
import * as p2 from "@clack/prompts";
|
|
50998
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) => {
|
|
50999
|
-
const resolvedDir =
|
|
51480
|
+
const resolvedDir = path80.resolve(dir);
|
|
51000
51481
|
if (!fs70.existsSync(resolvedDir)) {
|
|
51001
51482
|
console.error(`Directory does not exist: ${resolvedDir}`);
|
|
51002
51483
|
process.exit(1);
|
|
@@ -51051,7 +51532,7 @@ DB: ${shortPath4(existing.dbPath)}`, "Existing");
|
|
|
51051
51532
|
ensureGlobalDirs();
|
|
51052
51533
|
saveProjectConfig(projectRoot, configForSave);
|
|
51053
51534
|
const dbPath = getDbPath(projectRoot);
|
|
51054
|
-
const oldDbPath =
|
|
51535
|
+
const oldDbPath = path80.join(projectRoot, ".trace-mcp", "index.db");
|
|
51055
51536
|
let migrated = false;
|
|
51056
51537
|
if (fs70.existsSync(oldDbPath) && !fs70.existsSync(dbPath)) {
|
|
51057
51538
|
fs70.copyFileSync(oldDbPath, dbPath);
|
|
@@ -51228,7 +51709,7 @@ function shortPath5(p4) {
|
|
|
51228
51709
|
// src/cli-ci.ts
|
|
51229
51710
|
import { Command as Command5 } from "commander";
|
|
51230
51711
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
51231
|
-
import
|
|
51712
|
+
import path81 from "path";
|
|
51232
51713
|
import fs71 from "fs";
|
|
51233
51714
|
|
|
51234
51715
|
// src/ci/report-generator.ts
|
|
@@ -51608,8 +52089,8 @@ function writeOutput(outputPath, content) {
|
|
|
51608
52089
|
if (outputPath === "-" || !outputPath) {
|
|
51609
52090
|
process.stdout.write(content + "\n");
|
|
51610
52091
|
} else {
|
|
51611
|
-
const resolved =
|
|
51612
|
-
fs71.mkdirSync(
|
|
52092
|
+
const resolved = path81.resolve(outputPath);
|
|
52093
|
+
fs71.mkdirSync(path81.dirname(resolved), { recursive: true });
|
|
51613
52094
|
fs71.writeFileSync(resolved, content, "utf-8");
|
|
51614
52095
|
logger.info({ path: resolved }, "CI report written");
|
|
51615
52096
|
}
|
|
@@ -51845,22 +52326,22 @@ program.command("serve").description("Start MCP server (stdio transport)").actio
|
|
|
51845
52326
|
) : null;
|
|
51846
52327
|
const runEmbeddings = () => {
|
|
51847
52328
|
if (!embeddingPipeline) return;
|
|
51848
|
-
embeddingPipeline.indexUnembedded().catch((
|
|
51849
|
-
logger.error({ error:
|
|
52329
|
+
embeddingPipeline.indexUnembedded().catch((err32) => {
|
|
52330
|
+
logger.error({ error: err32 }, "Embedding indexing failed");
|
|
51850
52331
|
});
|
|
51851
52332
|
};
|
|
51852
52333
|
const runSummarization = () => {
|
|
51853
52334
|
if (!summarizationPipeline) return;
|
|
51854
|
-
summarizationPipeline.summarizeUnsummarized().catch((
|
|
51855
|
-
logger.error({ error:
|
|
52335
|
+
summarizationPipeline.summarizeUnsummarized().catch((err32) => {
|
|
52336
|
+
logger.error({ error: err32 }, "Summarization failed");
|
|
51856
52337
|
});
|
|
51857
52338
|
};
|
|
51858
52339
|
pipeline.indexAll().then(() => {
|
|
51859
52340
|
runSummarization();
|
|
51860
52341
|
runEmbeddings();
|
|
51861
52342
|
runFederationAutoSync(projectRoot, config);
|
|
51862
|
-
}).catch((
|
|
51863
|
-
logger.error({ error:
|
|
52343
|
+
}).catch((err32) => {
|
|
52344
|
+
logger.error({ error: err32 }, "Initial indexing failed");
|
|
51864
52345
|
});
|
|
51865
52346
|
await watcher.start(projectRoot, config, async (paths) => {
|
|
51866
52347
|
await pipeline.indexFiles(paths);
|
|
@@ -51913,22 +52394,22 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
|
|
|
51913
52394
|
) : null;
|
|
51914
52395
|
const runEmbeddings = () => {
|
|
51915
52396
|
if (!embeddingPipeline) return;
|
|
51916
|
-
embeddingPipeline.indexUnembedded().catch((
|
|
51917
|
-
logger.error({ error:
|
|
52397
|
+
embeddingPipeline.indexUnembedded().catch((err32) => {
|
|
52398
|
+
logger.error({ error: err32 }, "Embedding indexing failed");
|
|
51918
52399
|
});
|
|
51919
52400
|
};
|
|
51920
52401
|
const runSummarization2 = () => {
|
|
51921
52402
|
if (!summarizationPipeline2) return;
|
|
51922
|
-
summarizationPipeline2.summarizeUnsummarized().catch((
|
|
51923
|
-
logger.error({ error:
|
|
52403
|
+
summarizationPipeline2.summarizeUnsummarized().catch((err32) => {
|
|
52404
|
+
logger.error({ error: err32 }, "Summarization failed");
|
|
51924
52405
|
});
|
|
51925
52406
|
};
|
|
51926
52407
|
pipeline.indexAll().then(() => {
|
|
51927
52408
|
runSummarization2();
|
|
51928
52409
|
runEmbeddings();
|
|
51929
52410
|
runFederationAutoSync(projectRoot, config);
|
|
51930
|
-
}).catch((
|
|
51931
|
-
logger.error({ error:
|
|
52411
|
+
}).catch((err32) => {
|
|
52412
|
+
logger.error({ error: err32 }, "Initial indexing failed");
|
|
51932
52413
|
});
|
|
51933
52414
|
await watcher.start(projectRoot, config, async (paths) => {
|
|
51934
52415
|
await pipeline.indexFiles(paths);
|
|
@@ -52031,7 +52512,7 @@ program.command("serve-http").description("Start MCP server (HTTP/SSE transport)
|
|
|
52031
52512
|
});
|
|
52032
52513
|
});
|
|
52033
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) => {
|
|
52034
|
-
const resolvedDir =
|
|
52515
|
+
const resolvedDir = path82.resolve(dir);
|
|
52035
52516
|
if (!fs72.existsSync(resolvedDir)) {
|
|
52036
52517
|
logger.error({ dir: resolvedDir }, "Directory does not exist");
|
|
52037
52518
|
process.exit(1);
|
|
@@ -52056,13 +52537,13 @@ program.command("index").description("Index a project directory").argument("<dir
|
|
|
52056
52537
|
db.close();
|
|
52057
52538
|
});
|
|
52058
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) => {
|
|
52059
|
-
const resolvedFile =
|
|
52540
|
+
const resolvedFile = path82.resolve(file);
|
|
52060
52541
|
if (!fs72.existsSync(resolvedFile)) {
|
|
52061
52542
|
process.exit(0);
|
|
52062
52543
|
}
|
|
52063
52544
|
let projectRoot;
|
|
52064
52545
|
try {
|
|
52065
|
-
projectRoot = findProjectRoot(
|
|
52546
|
+
projectRoot = findProjectRoot(path82.dirname(resolvedFile));
|
|
52066
52547
|
} catch {
|
|
52067
52548
|
process.exit(0);
|
|
52068
52549
|
}
|