archtracker-mcp 0.3.2 → 0.4.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/README.md +8 -4
- package/dist/cli/index.js +105 -140
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -9
- package/dist/index.js +59 -128
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +62 -131
- package/dist/mcp/index.js.map +1 -1
- package/package.json +2 -5
- package/scripts/postinstall.cjs +0 -45
- package/scripts/screenshot.mjs +0 -121
package/dist/mcp/index.js
CHANGED
|
@@ -4,125 +4,12 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { z as z2 } from "zod";
|
|
5
5
|
|
|
6
6
|
// src/analyzer/analyze.ts
|
|
7
|
-
import { resolve as
|
|
8
|
-
|
|
9
|
-
// src/analyzer/engines/dependency-cruiser.ts
|
|
10
|
-
import { resolve } from "path";
|
|
11
|
-
import { cruise } from "dependency-cruiser";
|
|
12
|
-
var DEFAULT_EXCLUDE = [
|
|
13
|
-
"node_modules",
|
|
14
|
-
"\\.d\\.ts$",
|
|
15
|
-
"dist",
|
|
16
|
-
"build",
|
|
17
|
-
"coverage",
|
|
18
|
-
"\\.archtracker"
|
|
19
|
-
];
|
|
20
|
-
var DependencyCruiserEngine = class {
|
|
21
|
-
async analyze(rootDir, options = {}) {
|
|
22
|
-
const {
|
|
23
|
-
exclude = [],
|
|
24
|
-
maxDepth = 0,
|
|
25
|
-
tsConfigPath,
|
|
26
|
-
includeTypeOnly = true
|
|
27
|
-
} = options;
|
|
28
|
-
const absRootDir = resolve(rootDir);
|
|
29
|
-
const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
|
|
30
|
-
const excludePattern = allExclude.join("|");
|
|
31
|
-
const cruiseOptions = {
|
|
32
|
-
baseDir: absRootDir,
|
|
33
|
-
exclude: { path: excludePattern },
|
|
34
|
-
doNotFollow: { path: "node_modules" },
|
|
35
|
-
maxDepth,
|
|
36
|
-
tsPreCompilationDeps: includeTypeOnly ? true : false,
|
|
37
|
-
combinedDependencies: false
|
|
38
|
-
};
|
|
39
|
-
if (tsConfigPath) {
|
|
40
|
-
cruiseOptions.tsConfig = { fileName: tsConfigPath };
|
|
41
|
-
}
|
|
42
|
-
let result;
|
|
43
|
-
try {
|
|
44
|
-
result = await cruise(["."], cruiseOptions);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
-
throw new Error(`dependency-cruiser failed: ${message}`, {
|
|
48
|
-
cause: error
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
if (result.exitCode !== 0 && !result.output) {
|
|
52
|
-
throw new Error(`Analysis exited with code ${result.exitCode}`);
|
|
53
|
-
}
|
|
54
|
-
const cruiseResult = result.output;
|
|
55
|
-
return this.buildGraph(absRootDir, cruiseResult);
|
|
56
|
-
}
|
|
57
|
-
buildGraph(rootDir, cruiseResult) {
|
|
58
|
-
const files = {};
|
|
59
|
-
const edges = [];
|
|
60
|
-
const circularSet = /* @__PURE__ */ new Set();
|
|
61
|
-
const circularDependencies = [];
|
|
62
|
-
for (const mod of cruiseResult.modules) {
|
|
63
|
-
if (this.isExternalModule(mod)) continue;
|
|
64
|
-
files[mod.source] = {
|
|
65
|
-
path: mod.source,
|
|
66
|
-
exists: !mod.couldNotResolve,
|
|
67
|
-
dependencies: [],
|
|
68
|
-
dependents: []
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
for (const mod of cruiseResult.modules) {
|
|
72
|
-
for (const dep of mod.dependencies) {
|
|
73
|
-
if (dep.couldNotResolve || dep.coreModule) continue;
|
|
74
|
-
if (!files[mod.source] || this.isExternalDep(dep)) continue;
|
|
75
|
-
const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
|
|
76
|
-
edges.push({ source: mod.source, target: dep.resolved, type: edgeType });
|
|
77
|
-
if (files[mod.source]) {
|
|
78
|
-
files[mod.source].dependencies.push(dep.resolved);
|
|
79
|
-
}
|
|
80
|
-
if (files[dep.resolved]) {
|
|
81
|
-
files[dep.resolved].dependents.push(mod.source);
|
|
82
|
-
}
|
|
83
|
-
if (dep.circular && dep.cycle) {
|
|
84
|
-
const cyclePath = dep.cycle.map((c) => c.name);
|
|
85
|
-
const cycleKey = [...cyclePath].sort().join("\u2192");
|
|
86
|
-
if (!circularSet.has(cycleKey)) {
|
|
87
|
-
circularSet.add(cycleKey);
|
|
88
|
-
circularDependencies.push({ cycle: cyclePath });
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
rootDir,
|
|
95
|
-
files,
|
|
96
|
-
edges,
|
|
97
|
-
circularDependencies,
|
|
98
|
-
totalFiles: Object.keys(files).length,
|
|
99
|
-
totalEdges: edges.length
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
isExternalModule(mod) {
|
|
103
|
-
if (mod.coreModule) return true;
|
|
104
|
-
const depTypes = mod.dependencyTypes ?? [];
|
|
105
|
-
if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
|
|
106
|
-
return isExternalPath(mod.source);
|
|
107
|
-
}
|
|
108
|
-
isExternalDep(dep) {
|
|
109
|
-
if (dep.coreModule) return true;
|
|
110
|
-
if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core"))
|
|
111
|
-
return true;
|
|
112
|
-
return isExternalPath(dep.resolved);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
function isExternalPath(source) {
|
|
116
|
-
if (source.startsWith("@")) return true;
|
|
117
|
-
if (!source.includes("/") && !source.includes("\\") && !source.includes("."))
|
|
118
|
-
return true;
|
|
119
|
-
if (source.startsWith("node:")) return true;
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
7
|
+
import { resolve as resolve3 } from "path";
|
|
8
|
+
import { stat as stat3 } from "fs/promises";
|
|
122
9
|
|
|
123
10
|
// src/analyzer/engines/regex-engine.ts
|
|
124
11
|
import { readdir, readFile } from "fs/promises";
|
|
125
|
-
import { join, relative, resolve
|
|
12
|
+
import { join, relative, resolve } from "path";
|
|
126
13
|
|
|
127
14
|
// src/analyzer/engines/cycle.ts
|
|
128
15
|
function detectCycles(edges) {
|
|
@@ -680,7 +567,7 @@ var RegexEngine = class {
|
|
|
680
567
|
this.config = config;
|
|
681
568
|
}
|
|
682
569
|
async analyze(rootDir, options = {}) {
|
|
683
|
-
const absRootDir =
|
|
570
|
+
const absRootDir = resolve(rootDir);
|
|
684
571
|
const excludePatterns = [
|
|
685
572
|
...this.config.defaultExclude ?? [],
|
|
686
573
|
...options.exclude ?? [],
|
|
@@ -927,7 +814,7 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
|
|
|
927
814
|
|
|
928
815
|
// src/analyzer/engines/languages.ts
|
|
929
816
|
import { readFileSync } from "fs";
|
|
930
|
-
import { join as join3, dirname, resolve as
|
|
817
|
+
import { join as join3, dirname, resolve as resolve2 } from "path";
|
|
931
818
|
|
|
932
819
|
// src/analyzer/engines/types.ts
|
|
933
820
|
var LANGUAGE_IDS = [
|
|
@@ -1210,7 +1097,7 @@ var cCpp = {
|
|
|
1210
1097
|
{ regex: /^#include\s+"([^"]+)"/gm }
|
|
1211
1098
|
],
|
|
1212
1099
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1213
|
-
const fromSource =
|
|
1100
|
+
const fromSource = resolve2(dirname(sourceFile), importPath);
|
|
1214
1101
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1215
1102
|
const fromRoot = join3(rootDir, importPath);
|
|
1216
1103
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1234,7 +1121,7 @@ var ruby = {
|
|
|
1234
1121
|
],
|
|
1235
1122
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1236
1123
|
const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
|
|
1237
|
-
const fromSource =
|
|
1124
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1238
1125
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1239
1126
|
const fromRoot = join3(rootDir, withExt);
|
|
1240
1127
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1257,7 +1144,7 @@ var php = {
|
|
|
1257
1144
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1258
1145
|
if (importPath.includes("/") || importPath.endsWith(".php")) {
|
|
1259
1146
|
const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
|
|
1260
|
-
const fromSource =
|
|
1147
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1261
1148
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1262
1149
|
const fromRoot2 = join3(rootDir, withExt);
|
|
1263
1150
|
if (projectFiles.has(fromRoot2)) return fromRoot2;
|
|
@@ -1377,7 +1264,7 @@ var dart = {
|
|
|
1377
1264
|
if (projectFiles.has(full)) return full;
|
|
1378
1265
|
return null;
|
|
1379
1266
|
}
|
|
1380
|
-
const resolved =
|
|
1267
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1381
1268
|
if (projectFiles.has(resolved)) return resolved;
|
|
1382
1269
|
return null;
|
|
1383
1270
|
},
|
|
@@ -1441,9 +1328,47 @@ var scala = {
|
|
|
1441
1328
|
},
|
|
1442
1329
|
defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
|
|
1443
1330
|
};
|
|
1331
|
+
var javascript = {
|
|
1332
|
+
id: "javascript",
|
|
1333
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
1334
|
+
commentStyle: "c-style",
|
|
1335
|
+
importPatterns: [
|
|
1336
|
+
// ES6: import [type] [stuff from] "path"
|
|
1337
|
+
{ regex: /import\s+(?:type\s+)?(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g },
|
|
1338
|
+
// Dynamic: import("path")
|
|
1339
|
+
{ regex: /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g },
|
|
1340
|
+
// Re-export: export [type] { stuff } from "path" / export * from "path"
|
|
1341
|
+
{ regex: /export\s+(?:type\s+)?(?:\{[^}]*\}|\*(?:\s+as\s+\w+)?)\s+from\s+["']([^"']+)["']/g },
|
|
1342
|
+
// CommonJS: require("path")
|
|
1343
|
+
{ regex: /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g }
|
|
1344
|
+
],
|
|
1345
|
+
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1346
|
+
if (importPath.startsWith("node:")) return null;
|
|
1347
|
+
if (!importPath.startsWith(".")) return null;
|
|
1348
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1349
|
+
if (projectFiles.has(resolved)) return resolved;
|
|
1350
|
+
if (resolved.endsWith(".js")) {
|
|
1351
|
+
const tsPath = resolved.slice(0, -3) + ".ts";
|
|
1352
|
+
if (projectFiles.has(tsPath)) return tsPath;
|
|
1353
|
+
const tsxPath = resolved.slice(0, -3) + ".tsx";
|
|
1354
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1355
|
+
}
|
|
1356
|
+
if (resolved.endsWith(".jsx")) {
|
|
1357
|
+
const tsxPath = resolved.slice(0, -4) + ".tsx";
|
|
1358
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1359
|
+
}
|
|
1360
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) {
|
|
1361
|
+
if (projectFiles.has(resolved + ext)) return resolved + ext;
|
|
1362
|
+
}
|
|
1363
|
+
for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
|
|
1364
|
+
if (projectFiles.has(resolved + idx)) return resolved + idx;
|
|
1365
|
+
}
|
|
1366
|
+
return null;
|
|
1367
|
+
},
|
|
1368
|
+
defaultExclude: ["node_modules", "\\.d\\.ts$", "dist", "build", "coverage"]
|
|
1369
|
+
};
|
|
1444
1370
|
var LANGUAGE_CONFIGS = {
|
|
1445
|
-
javascript
|
|
1446
|
-
// handled by DependencyCruiserEngine
|
|
1371
|
+
javascript,
|
|
1447
1372
|
python,
|
|
1448
1373
|
rust,
|
|
1449
1374
|
go,
|
|
@@ -1463,12 +1388,18 @@ function getLanguageConfig(id) {
|
|
|
1463
1388
|
|
|
1464
1389
|
// src/analyzer/analyze.ts
|
|
1465
1390
|
async function analyzeProject(rootDir, options = {}) {
|
|
1466
|
-
const absRootDir =
|
|
1467
|
-
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1391
|
+
const absRootDir = resolve3(rootDir);
|
|
1468
1392
|
try {
|
|
1469
|
-
|
|
1470
|
-
|
|
1393
|
+
const s = await stat3(absRootDir);
|
|
1394
|
+
if (!s.isDirectory()) {
|
|
1395
|
+
throw new AnalyzerError(`Not a directory: ${absRootDir}`);
|
|
1471
1396
|
}
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
if (error instanceof AnalyzerError) throw error;
|
|
1399
|
+
throw new AnalyzerError(`Directory not found: ${absRootDir}`, { cause: error });
|
|
1400
|
+
}
|
|
1401
|
+
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1402
|
+
try {
|
|
1472
1403
|
const config = getLanguageConfig(language);
|
|
1473
1404
|
if (!config) {
|
|
1474
1405
|
throw new AnalyzerError(`No analyzer config for language: ${language}`);
|
|
@@ -1983,10 +1914,10 @@ function arraysEqual(a, b) {
|
|
|
1983
1914
|
}
|
|
1984
1915
|
|
|
1985
1916
|
// src/utils/path-guard.ts
|
|
1986
|
-
import { resolve as
|
|
1917
|
+
import { resolve as resolve4 } from "path";
|
|
1987
1918
|
function validatePath(inputPath, boundary) {
|
|
1988
|
-
const resolved =
|
|
1989
|
-
const root = boundary ?
|
|
1919
|
+
const resolved = resolve4(inputPath);
|
|
1920
|
+
const root = boundary ? resolve4(boundary) : process.cwd();
|
|
1990
1921
|
if (!resolved.startsWith(root)) {
|
|
1991
1922
|
throw new PathTraversalError(
|
|
1992
1923
|
t("pathGuard.traversal", { input: inputPath, resolved, boundary: root })
|