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/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 resolve4 } from "path";
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 as resolve2 } from "path";
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 = resolve2(rootDir);
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 resolve3 } from "path";
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 = resolve3(dirname(sourceFile), importPath);
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 = resolve3(dirname(sourceFile), withExt);
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 = resolve3(dirname(sourceFile), withExt);
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 = resolve3(dirname(sourceFile), importPath);
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: null,
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 = resolve4(rootDir);
1467
- const language = options.language ?? await detectLanguage(absRootDir);
1391
+ const absRootDir = resolve3(rootDir);
1468
1392
  try {
1469
- if (language === "javascript") {
1470
- return await new DependencyCruiserEngine().analyze(absRootDir, options);
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 resolve5 } from "path";
1917
+ import { resolve as resolve4 } from "path";
1987
1918
  function validatePath(inputPath, boundary) {
1988
- const resolved = resolve5(inputPath);
1989
- const root = boundary ? resolve5(boundary) : process.cwd();
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 })