archtracker-mcp 0.3.1 → 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 +127 -144
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +5 -10
- package/dist/index.js +59 -128
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +115 -140
- 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/index.d.ts
CHANGED
|
@@ -60,7 +60,9 @@ interface ArchContext {
|
|
|
60
60
|
}>;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
/** Single source of truth for supported language IDs */
|
|
64
|
+
declare const LANGUAGE_IDS: readonly ["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"];
|
|
65
|
+
type LanguageId = (typeof LANGUAGE_IDS)[number];
|
|
64
66
|
|
|
65
67
|
/** Options for the dependency analyzer */
|
|
66
68
|
interface AnalyzeOptions {
|
|
@@ -68,19 +70,12 @@ interface AnalyzeOptions {
|
|
|
68
70
|
exclude?: string[];
|
|
69
71
|
/** Maximum recursion depth (0 = unlimited) */
|
|
70
72
|
maxDepth?: number;
|
|
71
|
-
/** Path to tsconfig.json (auto-detected if omitted) */
|
|
72
|
-
tsConfigPath?: string;
|
|
73
|
-
/** Include type-only imports (import type {...}) — JS/TS only */
|
|
74
|
-
includeTypeOnly?: boolean;
|
|
75
73
|
/** Target language (auto-detected if omitted) */
|
|
76
74
|
language?: LanguageId;
|
|
77
75
|
}
|
|
78
76
|
/**
|
|
79
|
-
* Analyze project dependencies.
|
|
80
|
-
*
|
|
81
|
-
* For JavaScript/TypeScript: uses dependency-cruiser (AST-based).
|
|
82
|
-
* For other languages: uses regex-based import extraction.
|
|
83
|
-
* Language is auto-detected from project marker files if not specified.
|
|
77
|
+
* Analyze project dependencies using regex-based import extraction.
|
|
78
|
+
* Supports all 13 languages. Language is auto-detected if not specified.
|
|
84
79
|
*/
|
|
85
80
|
declare function analyzeProject(rootDir: string, options?: AnalyzeOptions): Promise<DependencyGraph>;
|
|
86
81
|
/** Custom error class for analyzer failures */
|
package/dist/index.js
CHANGED
|
@@ -2,125 +2,12 @@
|
|
|
2
2
|
var SCHEMA_VERSION = "1.0";
|
|
3
3
|
|
|
4
4
|
// src/analyzer/analyze.ts
|
|
5
|
-
import { resolve as
|
|
6
|
-
|
|
7
|
-
// src/analyzer/engines/dependency-cruiser.ts
|
|
8
|
-
import { resolve } from "path";
|
|
9
|
-
import { cruise } from "dependency-cruiser";
|
|
10
|
-
var DEFAULT_EXCLUDE = [
|
|
11
|
-
"node_modules",
|
|
12
|
-
"\\.d\\.ts$",
|
|
13
|
-
"dist",
|
|
14
|
-
"build",
|
|
15
|
-
"coverage",
|
|
16
|
-
"\\.archtracker"
|
|
17
|
-
];
|
|
18
|
-
var DependencyCruiserEngine = class {
|
|
19
|
-
async analyze(rootDir, options = {}) {
|
|
20
|
-
const {
|
|
21
|
-
exclude = [],
|
|
22
|
-
maxDepth = 0,
|
|
23
|
-
tsConfigPath,
|
|
24
|
-
includeTypeOnly = true
|
|
25
|
-
} = options;
|
|
26
|
-
const absRootDir = resolve(rootDir);
|
|
27
|
-
const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
|
|
28
|
-
const excludePattern = allExclude.join("|");
|
|
29
|
-
const cruiseOptions = {
|
|
30
|
-
baseDir: absRootDir,
|
|
31
|
-
exclude: { path: excludePattern },
|
|
32
|
-
doNotFollow: { path: "node_modules" },
|
|
33
|
-
maxDepth,
|
|
34
|
-
tsPreCompilationDeps: includeTypeOnly ? true : false,
|
|
35
|
-
combinedDependencies: false
|
|
36
|
-
};
|
|
37
|
-
if (tsConfigPath) {
|
|
38
|
-
cruiseOptions.tsConfig = { fileName: tsConfigPath };
|
|
39
|
-
}
|
|
40
|
-
let result;
|
|
41
|
-
try {
|
|
42
|
-
result = await cruise(["."], cruiseOptions);
|
|
43
|
-
} catch (error) {
|
|
44
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
-
throw new Error(`dependency-cruiser failed: ${message}`, {
|
|
46
|
-
cause: error
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
if (result.exitCode !== 0 && !result.output) {
|
|
50
|
-
throw new Error(`Analysis exited with code ${result.exitCode}`);
|
|
51
|
-
}
|
|
52
|
-
const cruiseResult = result.output;
|
|
53
|
-
return this.buildGraph(absRootDir, cruiseResult);
|
|
54
|
-
}
|
|
55
|
-
buildGraph(rootDir, cruiseResult) {
|
|
56
|
-
const files = {};
|
|
57
|
-
const edges = [];
|
|
58
|
-
const circularSet = /* @__PURE__ */ new Set();
|
|
59
|
-
const circularDependencies = [];
|
|
60
|
-
for (const mod of cruiseResult.modules) {
|
|
61
|
-
if (this.isExternalModule(mod)) continue;
|
|
62
|
-
files[mod.source] = {
|
|
63
|
-
path: mod.source,
|
|
64
|
-
exists: !mod.couldNotResolve,
|
|
65
|
-
dependencies: [],
|
|
66
|
-
dependents: []
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
for (const mod of cruiseResult.modules) {
|
|
70
|
-
for (const dep of mod.dependencies) {
|
|
71
|
-
if (dep.couldNotResolve || dep.coreModule) continue;
|
|
72
|
-
if (!files[mod.source] || this.isExternalDep(dep)) continue;
|
|
73
|
-
const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
|
|
74
|
-
edges.push({ source: mod.source, target: dep.resolved, type: edgeType });
|
|
75
|
-
if (files[mod.source]) {
|
|
76
|
-
files[mod.source].dependencies.push(dep.resolved);
|
|
77
|
-
}
|
|
78
|
-
if (files[dep.resolved]) {
|
|
79
|
-
files[dep.resolved].dependents.push(mod.source);
|
|
80
|
-
}
|
|
81
|
-
if (dep.circular && dep.cycle) {
|
|
82
|
-
const cyclePath = dep.cycle.map((c) => c.name);
|
|
83
|
-
const cycleKey = [...cyclePath].sort().join("\u2192");
|
|
84
|
-
if (!circularSet.has(cycleKey)) {
|
|
85
|
-
circularSet.add(cycleKey);
|
|
86
|
-
circularDependencies.push({ cycle: cyclePath });
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return {
|
|
92
|
-
rootDir,
|
|
93
|
-
files,
|
|
94
|
-
edges,
|
|
95
|
-
circularDependencies,
|
|
96
|
-
totalFiles: Object.keys(files).length,
|
|
97
|
-
totalEdges: edges.length
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
isExternalModule(mod) {
|
|
101
|
-
if (mod.coreModule) return true;
|
|
102
|
-
const depTypes = mod.dependencyTypes ?? [];
|
|
103
|
-
if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
|
|
104
|
-
return isExternalPath(mod.source);
|
|
105
|
-
}
|
|
106
|
-
isExternalDep(dep) {
|
|
107
|
-
if (dep.coreModule) return true;
|
|
108
|
-
if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core"))
|
|
109
|
-
return true;
|
|
110
|
-
return isExternalPath(dep.resolved);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
function isExternalPath(source) {
|
|
114
|
-
if (source.startsWith("@")) return true;
|
|
115
|
-
if (!source.includes("/") && !source.includes("\\") && !source.includes("."))
|
|
116
|
-
return true;
|
|
117
|
-
if (source.startsWith("node:")) return true;
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
5
|
+
import { resolve as resolve3 } from "path";
|
|
6
|
+
import { stat as stat3 } from "fs/promises";
|
|
120
7
|
|
|
121
8
|
// src/analyzer/engines/regex-engine.ts
|
|
122
9
|
import { readdir, readFile } from "fs/promises";
|
|
123
|
-
import { join, relative, resolve
|
|
10
|
+
import { join, relative, resolve } from "path";
|
|
124
11
|
|
|
125
12
|
// src/analyzer/engines/cycle.ts
|
|
126
13
|
function detectCycles(edges) {
|
|
@@ -678,7 +565,7 @@ var RegexEngine = class {
|
|
|
678
565
|
this.config = config;
|
|
679
566
|
}
|
|
680
567
|
async analyze(rootDir, options = {}) {
|
|
681
|
-
const absRootDir =
|
|
568
|
+
const absRootDir = resolve(rootDir);
|
|
682
569
|
const excludePatterns = [
|
|
683
570
|
...this.config.defaultExclude ?? [],
|
|
684
571
|
...options.exclude ?? [],
|
|
@@ -925,7 +812,7 @@ async function scanExtensions(dir, counts, maxDepth, currentDepth) {
|
|
|
925
812
|
|
|
926
813
|
// src/analyzer/engines/languages.ts
|
|
927
814
|
import { readFileSync } from "fs";
|
|
928
|
-
import { join as join3, dirname, resolve as
|
|
815
|
+
import { join as join3, dirname, resolve as resolve2 } from "path";
|
|
929
816
|
var python = {
|
|
930
817
|
id: "python",
|
|
931
818
|
extensions: [".py"],
|
|
@@ -1189,7 +1076,7 @@ var cCpp = {
|
|
|
1189
1076
|
{ regex: /^#include\s+"([^"]+)"/gm }
|
|
1190
1077
|
],
|
|
1191
1078
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1192
|
-
const fromSource =
|
|
1079
|
+
const fromSource = resolve2(dirname(sourceFile), importPath);
|
|
1193
1080
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1194
1081
|
const fromRoot = join3(rootDir, importPath);
|
|
1195
1082
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1213,7 +1100,7 @@ var ruby = {
|
|
|
1213
1100
|
],
|
|
1214
1101
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1215
1102
|
const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
|
|
1216
|
-
const fromSource =
|
|
1103
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1217
1104
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1218
1105
|
const fromRoot = join3(rootDir, withExt);
|
|
1219
1106
|
if (projectFiles.has(fromRoot)) return fromRoot;
|
|
@@ -1236,7 +1123,7 @@ var php = {
|
|
|
1236
1123
|
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1237
1124
|
if (importPath.includes("/") || importPath.endsWith(".php")) {
|
|
1238
1125
|
const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
|
|
1239
|
-
const fromSource =
|
|
1126
|
+
const fromSource = resolve2(dirname(sourceFile), withExt);
|
|
1240
1127
|
if (projectFiles.has(fromSource)) return fromSource;
|
|
1241
1128
|
const fromRoot2 = join3(rootDir, withExt);
|
|
1242
1129
|
if (projectFiles.has(fromRoot2)) return fromRoot2;
|
|
@@ -1356,7 +1243,7 @@ var dart = {
|
|
|
1356
1243
|
if (projectFiles.has(full)) return full;
|
|
1357
1244
|
return null;
|
|
1358
1245
|
}
|
|
1359
|
-
const resolved =
|
|
1246
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1360
1247
|
if (projectFiles.has(resolved)) return resolved;
|
|
1361
1248
|
return null;
|
|
1362
1249
|
},
|
|
@@ -1420,9 +1307,47 @@ var scala = {
|
|
|
1420
1307
|
},
|
|
1421
1308
|
defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
|
|
1422
1309
|
};
|
|
1310
|
+
var javascript = {
|
|
1311
|
+
id: "javascript",
|
|
1312
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
|
1313
|
+
commentStyle: "c-style",
|
|
1314
|
+
importPatterns: [
|
|
1315
|
+
// ES6: import [type] [stuff from] "path"
|
|
1316
|
+
{ regex: /import\s+(?:type\s+)?(?:[\w*{}\s,]+\s+from\s+)?["']([^"']+)["']/g },
|
|
1317
|
+
// Dynamic: import("path")
|
|
1318
|
+
{ regex: /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g },
|
|
1319
|
+
// Re-export: export [type] { stuff } from "path" / export * from "path"
|
|
1320
|
+
{ regex: /export\s+(?:type\s+)?(?:\{[^}]*\}|\*(?:\s+as\s+\w+)?)\s+from\s+["']([^"']+)["']/g },
|
|
1321
|
+
// CommonJS: require("path")
|
|
1322
|
+
{ regex: /\brequire\s*\(\s*["']([^"']+)["']\s*\)/g }
|
|
1323
|
+
],
|
|
1324
|
+
resolveImport(importPath, sourceFile, rootDir, projectFiles) {
|
|
1325
|
+
if (importPath.startsWith("node:")) return null;
|
|
1326
|
+
if (!importPath.startsWith(".")) return null;
|
|
1327
|
+
const resolved = resolve2(dirname(sourceFile), importPath);
|
|
1328
|
+
if (projectFiles.has(resolved)) return resolved;
|
|
1329
|
+
if (resolved.endsWith(".js")) {
|
|
1330
|
+
const tsPath = resolved.slice(0, -3) + ".ts";
|
|
1331
|
+
if (projectFiles.has(tsPath)) return tsPath;
|
|
1332
|
+
const tsxPath = resolved.slice(0, -3) + ".tsx";
|
|
1333
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1334
|
+
}
|
|
1335
|
+
if (resolved.endsWith(".jsx")) {
|
|
1336
|
+
const tsxPath = resolved.slice(0, -4) + ".tsx";
|
|
1337
|
+
if (projectFiles.has(tsxPath)) return tsxPath;
|
|
1338
|
+
}
|
|
1339
|
+
for (const ext of [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]) {
|
|
1340
|
+
if (projectFiles.has(resolved + ext)) return resolved + ext;
|
|
1341
|
+
}
|
|
1342
|
+
for (const idx of ["/index.ts", "/index.tsx", "/index.js", "/index.jsx"]) {
|
|
1343
|
+
if (projectFiles.has(resolved + idx)) return resolved + idx;
|
|
1344
|
+
}
|
|
1345
|
+
return null;
|
|
1346
|
+
},
|
|
1347
|
+
defaultExclude: ["node_modules", "\\.d\\.ts$", "dist", "build", "coverage"]
|
|
1348
|
+
};
|
|
1423
1349
|
var LANGUAGE_CONFIGS = {
|
|
1424
|
-
javascript
|
|
1425
|
-
// handled by DependencyCruiserEngine
|
|
1350
|
+
javascript,
|
|
1426
1351
|
python,
|
|
1427
1352
|
rust,
|
|
1428
1353
|
go,
|
|
@@ -1442,12 +1367,18 @@ function getLanguageConfig(id) {
|
|
|
1442
1367
|
|
|
1443
1368
|
// src/analyzer/analyze.ts
|
|
1444
1369
|
async function analyzeProject(rootDir, options = {}) {
|
|
1445
|
-
const absRootDir =
|
|
1446
|
-
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1370
|
+
const absRootDir = resolve3(rootDir);
|
|
1447
1371
|
try {
|
|
1448
|
-
|
|
1449
|
-
|
|
1372
|
+
const s = await stat3(absRootDir);
|
|
1373
|
+
if (!s.isDirectory()) {
|
|
1374
|
+
throw new AnalyzerError(`Not a directory: ${absRootDir}`);
|
|
1450
1375
|
}
|
|
1376
|
+
} catch (error) {
|
|
1377
|
+
if (error instanceof AnalyzerError) throw error;
|
|
1378
|
+
throw new AnalyzerError(`Directory not found: ${absRootDir}`, { cause: error });
|
|
1379
|
+
}
|
|
1380
|
+
const language = options.language ?? await detectLanguage(absRootDir);
|
|
1381
|
+
try {
|
|
1451
1382
|
const config = getLanguageConfig(language);
|
|
1452
1383
|
if (!config) {
|
|
1453
1384
|
throw new AnalyzerError(`No analyzer config for language: ${language}`);
|