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/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,26 @@ 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";
818
+
819
+ // src/analyzer/engines/types.ts
820
+ var LANGUAGE_IDS = [
821
+ "javascript",
822
+ "python",
823
+ "rust",
824
+ "go",
825
+ "java",
826
+ "c-cpp",
827
+ "c-sharp",
828
+ "ruby",
829
+ "php",
830
+ "swift",
831
+ "kotlin",
832
+ "dart",
833
+ "scala"
834
+ ];
835
+
836
+ // src/analyzer/engines/languages.ts
931
837
  var python = {
932
838
  id: "python",
933
839
  extensions: [".py"],
@@ -1191,7 +1097,7 @@ var cCpp = {
1191
1097
  { regex: /^#include\s+"([^"]+)"/gm }
1192
1098
  ],
1193
1099
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1194
- const fromSource = resolve3(dirname(sourceFile), importPath);
1100
+ const fromSource = resolve2(dirname(sourceFile), importPath);
1195
1101
  if (projectFiles.has(fromSource)) return fromSource;
1196
1102
  const fromRoot = join3(rootDir, importPath);
1197
1103
  if (projectFiles.has(fromRoot)) return fromRoot;
@@ -1215,7 +1121,7 @@ var ruby = {
1215
1121
  ],
1216
1122
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1217
1123
  const withExt = importPath.endsWith(".rb") ? importPath : importPath + ".rb";
1218
- const fromSource = resolve3(dirname(sourceFile), withExt);
1124
+ const fromSource = resolve2(dirname(sourceFile), withExt);
1219
1125
  if (projectFiles.has(fromSource)) return fromSource;
1220
1126
  const fromRoot = join3(rootDir, withExt);
1221
1127
  if (projectFiles.has(fromRoot)) return fromRoot;
@@ -1238,7 +1144,7 @@ var php = {
1238
1144
  resolveImport(importPath, sourceFile, rootDir, projectFiles) {
1239
1145
  if (importPath.includes("/") || importPath.endsWith(".php")) {
1240
1146
  const withExt = importPath.endsWith(".php") ? importPath : importPath + ".php";
1241
- const fromSource = resolve3(dirname(sourceFile), withExt);
1147
+ const fromSource = resolve2(dirname(sourceFile), withExt);
1242
1148
  if (projectFiles.has(fromSource)) return fromSource;
1243
1149
  const fromRoot2 = join3(rootDir, withExt);
1244
1150
  if (projectFiles.has(fromRoot2)) return fromRoot2;
@@ -1358,7 +1264,7 @@ var dart = {
1358
1264
  if (projectFiles.has(full)) return full;
1359
1265
  return null;
1360
1266
  }
1361
- const resolved = resolve3(dirname(sourceFile), importPath);
1267
+ const resolved = resolve2(dirname(sourceFile), importPath);
1362
1268
  if (projectFiles.has(resolved)) return resolved;
1363
1269
  return null;
1364
1270
  },
@@ -1422,9 +1328,47 @@ var scala = {
1422
1328
  },
1423
1329
  defaultExclude: ["target", "\\.bsp", "\\.metals", "\\.bloop"]
1424
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
+ };
1425
1370
  var LANGUAGE_CONFIGS = {
1426
- javascript: null,
1427
- // handled by DependencyCruiserEngine
1371
+ javascript,
1428
1372
  python,
1429
1373
  rust,
1430
1374
  go,
@@ -1444,12 +1388,18 @@ function getLanguageConfig(id) {
1444
1388
 
1445
1389
  // src/analyzer/analyze.ts
1446
1390
  async function analyzeProject(rootDir, options = {}) {
1447
- const absRootDir = resolve4(rootDir);
1448
- const language = options.language ?? await detectLanguage(absRootDir);
1391
+ const absRootDir = resolve3(rootDir);
1449
1392
  try {
1450
- if (language === "javascript") {
1451
- 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}`);
1452
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 {
1453
1403
  const config = getLanguageConfig(language);
1454
1404
  if (!config) {
1455
1405
  throw new AnalyzerError(`No analyzer config for language: ${language}`);
@@ -1964,10 +1914,10 @@ function arraysEqual(a, b) {
1964
1914
  }
1965
1915
 
1966
1916
  // src/utils/path-guard.ts
1967
- import { resolve as resolve5 } from "path";
1917
+ import { resolve as resolve4 } from "path";
1968
1918
  function validatePath(inputPath, boundary) {
1969
- const resolved = resolve5(inputPath);
1970
- const root = boundary ? resolve5(boundary) : process.cwd();
1919
+ const resolved = resolve4(inputPath);
1920
+ const root = boundary ? resolve4(boundary) : process.cwd();
1971
1921
  if (!resolved.startsWith(root)) {
1972
1922
  throw new PathTraversalError(
1973
1923
  t("pathGuard.traversal", { input: inputPath, resolved, boundary: root })
@@ -1982,19 +1932,44 @@ var PathTraversalError = class extends Error {
1982
1932
  }
1983
1933
  };
1984
1934
 
1935
+ // src/utils/version.ts
1936
+ import { readFileSync as readFileSync2 } from "fs";
1937
+ import { join as join5, dirname as dirname2 } from "path";
1938
+ import { fileURLToPath } from "url";
1939
+ function loadVersion() {
1940
+ let dir = dirname2(fileURLToPath(import.meta.url));
1941
+ for (let i = 0; i < 5; i++) {
1942
+ try {
1943
+ const pkg = JSON.parse(readFileSync2(join5(dir, "package.json"), "utf-8"));
1944
+ return pkg.version;
1945
+ } catch {
1946
+ dir = dirname2(dir);
1947
+ }
1948
+ }
1949
+ return "0.0.0";
1950
+ }
1951
+ var VERSION = loadVersion();
1952
+
1985
1953
  // src/mcp/index.ts
1986
1954
  var server = new McpServer({
1987
1955
  name: "archtracker",
1988
- version: "0.3.1"
1956
+ version: VERSION
1989
1957
  });
1958
+ var languageEnum = z2.enum(LANGUAGE_IDS);
1959
+ var LANG_DISPLAY = {
1960
+ javascript: "JS/TS",
1961
+ "c-cpp": "C/C++",
1962
+ "c-sharp": "C#"
1963
+ };
1964
+ var languageList = LANGUAGE_IDS.map((id) => LANG_DISPLAY[id] ?? id.charAt(0).toUpperCase() + id.slice(1)).join(", ");
1990
1965
  server.tool(
1991
1966
  "generate_map",
1992
- "Analyze dependency graph of a directory and return file import/export structure as JSON. Supports JS/TS, Python, Rust, Go, Java, C/C++, C#, Ruby, PHP, Swift, Kotlin, Dart, Scala.",
1967
+ `Analyze dependency graph of a directory and return file import/export structure as JSON. Supports ${languageList}.`,
1993
1968
  {
1994
1969
  targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
1995
1970
  exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude (e.g. ['test', 'mock'])"),
1996
1971
  maxDepth: z2.number().int().min(0).optional().describe("Max analysis depth (0 = unlimited)"),
1997
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
1972
+ language: languageEnum.optional().describe("Target language (auto-detected if omitted)")
1998
1973
  },
1999
1974
  async ({ targetDir, exclude, maxDepth, language }) => {
2000
1975
  try {
@@ -2017,14 +1992,14 @@ server.tool(
2017
1992
  );
2018
1993
  server.tool(
2019
1994
  "analyze_existing_architecture",
2020
- "Comprehensive architecture analysis for existing projects. Shows critical components, circular dependencies, orphan files, coupling hotspots, and directory breakdown. Supports 13 languages.",
1995
+ `Comprehensive architecture analysis for existing projects. Shows critical components, circular dependencies, orphan files, coupling hotspots, and directory breakdown. Supports ${LANGUAGE_IDS.length} languages.`,
2021
1996
  {
2022
1997
  targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
2023
1998
  exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude"),
2024
1999
  topN: z2.number().int().min(1).max(50).optional().describe("Number of top items to show in each section (default: 10)"),
2025
2000
  saveSnapshot: z2.boolean().optional().describe("Also save a snapshot after analysis (default: false)"),
2026
2001
  projectRoot: z2.string().default(".").describe("Project root (needed only when saveSnapshot is true)"),
2027
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
2002
+ language: languageEnum.optional().describe("Target language (auto-detected if omitted)")
2028
2003
  },
2029
2004
  async ({ targetDir, exclude, topN, saveSnapshot: doSave, projectRoot, language }) => {
2030
2005
  try {
@@ -2051,7 +2026,7 @@ server.tool(
2051
2026
  {
2052
2027
  targetDir: z2.string().default("src").describe("Target directory path"),
2053
2028
  projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)"),
2054
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
2029
+ language: languageEnum.optional().describe("Target language (auto-detected if omitted)")
2055
2030
  },
2056
2031
  async ({ targetDir, projectRoot, language }) => {
2057
2032
  try {
@@ -2081,7 +2056,7 @@ server.tool(
2081
2056
  {
2082
2057
  targetDir: z2.string().default("src").describe("Target directory path"),
2083
2058
  projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)"),
2084
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
2059
+ language: languageEnum.optional().describe("Target language (auto-detected if omitted)")
2085
2060
  },
2086
2061
  async ({ targetDir, projectRoot, language }) => {
2087
2062
  try {
@@ -2119,7 +2094,7 @@ server.tool(
2119
2094
  {
2120
2095
  targetDir: z2.string().default("src").describe("Target directory path"),
2121
2096
  projectRoot: z2.string().default(".").describe("Project root"),
2122
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
2097
+ language: languageEnum.optional().describe("Target language (auto-detected if omitted)")
2123
2098
  },
2124
2099
  async ({ targetDir, projectRoot, language }) => {
2125
2100
  try {
@@ -2179,7 +2154,7 @@ server.tool(
2179
2154
  targetDir: z2.string().default("src").describe("Target directory path"),
2180
2155
  projectRoot: z2.string().default(".").describe("Project root"),
2181
2156
  limit: z2.number().int().min(1).max(50).optional().describe("Max results (default: 10)"),
2182
- language: z2.enum(["javascript", "python", "rust", "go", "java", "c-cpp", "c-sharp", "ruby", "php", "swift", "kotlin", "dart", "scala"]).optional().describe("Target language (auto-detected if omitted)")
2157
+ language: languageEnum.optional().describe("Target language (auto-detected if omitted)")
2183
2158
  },
2184
2159
  async ({ query, mode, targetDir, projectRoot, limit, language }) => {
2185
2160
  try {