archtracker-mcp 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +64 -131
- package/dist/mcp/index.js.map +1 -1
- package/package.json +4 -6
- package/scripts/postinstall.cjs +0 -45
- package/scripts/screenshot.mjs +0 -121
package/dist/mcp/index.js
CHANGED
|
@@ -1,128 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
1
3
|
// src/mcp/index.ts
|
|
2
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
6
|
import { z as z2 } from "zod";
|
|
5
7
|
|
|
6
8
|
// 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
|
-
}
|
|
9
|
+
import { resolve as resolve3 } from "path";
|
|
10
|
+
import { stat as stat3 } from "fs/promises";
|
|
122
11
|
|
|
123
12
|
// src/analyzer/engines/regex-engine.ts
|
|
124
13
|
import { readdir, readFile } from "fs/promises";
|
|
125
|
-
import { join, relative, resolve
|
|
14
|
+
import { join, relative, resolve } from "path";
|
|
126
15
|
|
|
127
16
|
// src/analyzer/engines/cycle.ts
|
|
128
17
|
function detectCycles(edges) {
|
|
@@ -680,7 +569,7 @@ var RegexEngine = class {
|
|
|
680
569
|
this.config = config;
|
|
681
570
|
}
|
|
682
571
|
async analyze(rootDir, options = {}) {
|
|
683
|
-
const absRootDir =
|
|
572
|
+
const absRootDir = resolve(rootDir);
|
|
684
573
|
const excludePatterns = [
|
|
685
574
|
...this.config.defaultExclude ?? [],
|
|
686
575
|
...options.exclude ?? [],
|
|
@@ -927,7 +816,7 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
|
|
|
927
816
|
|
|
928
817
|
// src/analyzer/engines/languages.ts
|
|
929
818
|
import { readFileSync } from "fs";
|
|
930
|
-
import { join as join3, dirname, resolve as
|
|
819
|
+
import { join as join3, dirname, resolve as resolve2 } from "path";
|
|
931
820
|
|
|
932
821
|
// src/analyzer/engines/types.ts
|
|
933
822
|
var LANGUAGE_IDS = [
|
|
@@ -1210,7 +1099,7 @@ var cCpp = {
|
|
|
1210
1099
|
{ regex: /^#include\s+"([^"]+)"/gm }
|
|
1211
1100
|
],
|
|
1212
1101
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1213
|
-
const fromSource =
|
|
1102
|
+
const fromSource = resolve2(dirname(sourceFile), importPath);
|
|
1214
1103
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1215
1104
|
const fromRoot = join3(rootDir, importPath);
|
|
1216
1105
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1234,7 +1123,7 @@ var ruby = {
|
|
|
1234
1123
|
],
|
|
1235
1124
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1236
1125
|
const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
|
|
1237
|
-
const fromSource =
|
|
1126
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1238
1127
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1239
1128
|
const fromRoot = join3(rootDir, withExt);
|
|
1240
1129
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1257,7 +1146,7 @@ var php = {
|
|
|
1257
1146
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1258
1147
|
if (importPath.includes("/") || importPath.endsWith(".php")) {
|
|
1259
1148
|
const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
|
|
1260
|
-
const fromSource =
|
|
1149
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1261
1150
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1262
1151
|
const fromRoot2 = join3(rootDir, withExt);
|
|
1263
1152
|
if (projectFiles.has(fromRoot2)) return fromRoot2;
|
|
@@ -1377,7 +1266,7 @@ var dart = {
|
|
|
1377
1266
|
if (projectFiles.has(full)) return full;
|
|
1378
1267
|
return null;
|
|
1379
1268
|
}
|
|
1380
|
-
const resolved =
|
|
1269
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1381
1270
|
if (projectFiles.has(resolved)) return resolved;
|
|
1382
1271
|
return null;
|
|
1383
1272
|
},
|
|
@@ -1441,9 +1330,47 @@ var scala = {
|
|
|
1441
1330
|
},
|
|
1442
1331
|
defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
|
|
1443
1332
|
};
|
|
1333
|
+
var javascript = {
|
|
1334
|
+
id: "javascript",
|
|
1335
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
1336
|
+
commentStyle: "c-style",
|
|
1337
|
+
importPatterns: [
|
|
1338
|
+
// ES6: import [type] [stuff from] "path"
|
|
1339
|
+
{ regex: /import\s+(?:type\s+)?(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g },
|
|
1340
|
+
// Dynamic: import("path")
|
|
1341
|
+
{ regex: /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g },
|
|
1342
|
+
// Re-export: export [type] { stuff } from "path" / export * from "path"
|
|
1343
|
+
{ regex: /export\s+(?:type\s+)?(?:\{[^}]*\}|\*(?:\s+as\s+\w+)?)\s+from\s+["']([^"']+)["']/g },
|
|
1344
|
+
// CommonJS: require("path")
|
|
1345
|
+
{ regex: /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g }
|
|
1346
|
+
],
|
|
1347
|
+
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1348
|
+
if (importPath.startsWith("node:")) return null;
|
|
1349
|
+
if (!importPath.startsWith(".")) return null;
|
|
1350
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1351
|
+
if (projectFiles.has(resolved)) return resolved;
|
|
1352
|
+
if (resolved.endsWith(".js")) {
|
|
1353
|
+
const tsPath = resolved.slice(0, -3) + ".ts";
|
|
1354
|
+
if (projectFiles.has(tsPath)) return tsPath;
|
|
1355
|
+
const tsxPath = resolved.slice(0, -3) + ".tsx";
|
|
1356
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1357
|
+
}
|
|
1358
|
+
if (resolved.endsWith(".jsx")) {
|
|
1359
|
+
const tsxPath = resolved.slice(0, -4) + ".tsx";
|
|
1360
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1361
|
+
}
|
|
1362
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) {
|
|
1363
|
+
if (projectFiles.has(resolved + ext)) return resolved + ext;
|
|
1364
|
+
}
|
|
1365
|
+
for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
|
|
1366
|
+
if (projectFiles.has(resolved + idx)) return resolved + idx;
|
|
1367
|
+
}
|
|
1368
|
+
return null;
|
|
1369
|
+
},
|
|
1370
|
+
defaultExclude: ["node_modules", "\\.d\\.ts$", "dist", "build", "coverage"]
|
|
1371
|
+
};
|
|
1444
1372
|
var LANGUAGE_CONFIGS = {
|
|
1445
|
-
javascript
|
|
1446
|
-
// handled by DependencyCruiserEngine
|
|
1373
|
+
javascript,
|
|
1447
1374
|
python,
|
|
1448
1375
|
rust,
|
|
1449
1376
|
go,
|
|
@@ -1463,12 +1390,18 @@ function getLanguageConfig(id) {
|
|
|
1463
1390
|
|
|
1464
1391
|
// src/analyzer/analyze.ts
|
|
1465
1392
|
async function analyzeProject(rootDir, options = {}) {
|
|
1466
|
-
const absRootDir =
|
|
1467
|
-
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1393
|
+
const absRootDir = resolve3(rootDir);
|
|
1468
1394
|
try {
|
|
1469
|
-
|
|
1470
|
-
|
|
1395
|
+
const s = await stat3(absRootDir);
|
|
1396
|
+
if (!s.isDirectory()) {
|
|
1397
|
+
throw new AnalyzerError(`Not a directory: ${absRootDir}`);
|
|
1471
1398
|
}
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
if (error instanceof AnalyzerError) throw error;
|
|
1401
|
+
throw new AnalyzerError(`Directory not found: ${absRootDir}`, { cause: error });
|
|
1402
|
+
}
|
|
1403
|
+
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1404
|
+
try {
|
|
1472
1405
|
const config = getLanguageConfig(language);
|
|
1473
1406
|
if (!config) {
|
|
1474
1407
|
throw new AnalyzerError(`No analyzer config for language: ${language}`);
|
|
@@ -1983,10 +1916,10 @@ function arraysEqual(a, b) {
|
|
|
1983
1916
|
}
|
|
1984
1917
|
|
|
1985
1918
|
// src/utils/path-guard.ts
|
|
1986
|
-
import { resolve as
|
|
1919
|
+
import { resolve as resolve4 } from "path";
|
|
1987
1920
|
function validatePath(inputPath, boundary) {
|
|
1988
|
-
const resolved =
|
|
1989
|
-
const root = boundary ?
|
|
1921
|
+
const resolved = resolve4(inputPath);
|
|
1922
|
+
const root = boundary ? resolve4(boundary) : process.cwd();
|
|
1990
1923
|
if (!resolved.startsWith(root)) {
|
|
1991
1924
|
throw new PathTraversalError(
|
|
1992
1925
|
t("pathGuard.traversal", { input: inputPath, resolved, boundary: root })
|