archtracker-mcp 0.1.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.
@@ -0,0 +1,920 @@
1
+ // src/mcp/index.ts
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { z as z2 } from "zod";
5
+
6
+ // src/analyzer/analyze.ts
7
+ import { resolve } from "path";
8
+ import { cruise } from "dependency-cruiser";
9
+ var DEFAULT_EXCLUDE = [
10
+ "node_modules",
11
+ "\\.d\\.ts$",
12
+ "dist",
13
+ "build",
14
+ "coverage",
15
+ "\\.archtracker"
16
+ ];
17
+ async function analyzeProject(rootDir, options = {}) {
18
+ const {
19
+ exclude = [],
20
+ maxDepth = 0,
21
+ tsConfigPath,
22
+ includeTypeOnly = true
23
+ } = options;
24
+ const absRootDir = resolve(rootDir);
25
+ const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
26
+ const excludePattern = allExclude.join("|");
27
+ const cruiseOptions = {
28
+ baseDir: absRootDir,
29
+ exclude: { path: excludePattern },
30
+ doNotFollow: { path: "node_modules" },
31
+ maxDepth,
32
+ tsPreCompilationDeps: includeTypeOnly ? true : false,
33
+ combinedDependencies: false
34
+ };
35
+ if (tsConfigPath) {
36
+ cruiseOptions.tsConfig = { fileName: tsConfigPath };
37
+ }
38
+ let result;
39
+ try {
40
+ result = await cruise(["."], cruiseOptions);
41
+ } catch (error) {
42
+ const message = error instanceof Error ? error.message : String(error);
43
+ throw new AnalyzerError(
44
+ `dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${message}`,
45
+ { cause: error }
46
+ );
47
+ }
48
+ if (result.exitCode !== 0 && !result.output) {
49
+ throw new AnalyzerError(
50
+ `\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 ${result.exitCode} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F`
51
+ );
52
+ }
53
+ const cruiseResult = result.output;
54
+ return buildGraph(absRootDir, cruiseResult);
55
+ }
56
+ function buildGraph(rootDir, cruiseResult) {
57
+ const files = {};
58
+ const edges = [];
59
+ const circularSet = /* @__PURE__ */ new Set();
60
+ const circularDependencies = [];
61
+ for (const mod of cruiseResult.modules) {
62
+ if (isExternalModule(mod)) continue;
63
+ files[mod.source] = {
64
+ path: mod.source,
65
+ exists: !mod.couldNotResolve,
66
+ dependencies: [],
67
+ dependents: []
68
+ };
69
+ }
70
+ for (const mod of cruiseResult.modules) {
71
+ for (const dep of mod.dependencies) {
72
+ if (dep.couldNotResolve || dep.coreModule) continue;
73
+ if (!files[mod.source] || isExternalDep(dep)) continue;
74
+ const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
75
+ edges.push({
76
+ source: mod.source,
77
+ target: dep.resolved,
78
+ type: edgeType
79
+ });
80
+ if (files[mod.source]) {
81
+ files[mod.source].dependencies.push(dep.resolved);
82
+ }
83
+ if (files[dep.resolved]) {
84
+ files[dep.resolved].dependents.push(mod.source);
85
+ }
86
+ if (dep.circular && dep.cycle) {
87
+ const cyclePath = dep.cycle.map((c) => c.name);
88
+ const cycleKey = [...cyclePath].sort().join("\u2192");
89
+ if (!circularSet.has(cycleKey)) {
90
+ circularSet.add(cycleKey);
91
+ circularDependencies.push({ cycle: cyclePath });
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return {
97
+ rootDir,
98
+ files,
99
+ edges,
100
+ circularDependencies,
101
+ totalFiles: Object.keys(files).length,
102
+ totalEdges: edges.length
103
+ };
104
+ }
105
+ function isExternalModule(mod) {
106
+ if (mod.coreModule) return true;
107
+ const depTypes = mod.dependencyTypes ?? [];
108
+ if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
109
+ return isExternalPath(mod.source);
110
+ }
111
+ function isExternalDep(dep) {
112
+ if (dep.coreModule) return true;
113
+ if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
114
+ return isExternalPath(dep.resolved);
115
+ }
116
+ function isExternalPath(source) {
117
+ if (source.startsWith("@")) return true;
118
+ if (!source.includes("/") && !source.includes("\\") && !source.includes(".")) return true;
119
+ if (source.startsWith("node:")) return true;
120
+ return false;
121
+ }
122
+ var AnalyzerError = class extends Error {
123
+ constructor(message, options) {
124
+ super(message, options);
125
+ this.name = "AnalyzerError";
126
+ }
127
+ };
128
+
129
+ // src/i18n/index.ts
130
+ var currentLocale = detectLocale();
131
+ function detectLocale() {
132
+ const env = process.env.LC_ALL || process.env.LANG || "";
133
+ if (env.startsWith("ja")) return "ja";
134
+ return "en";
135
+ }
136
+ function t(key, vars) {
137
+ const messages = currentLocale === "ja" ? ja : en;
138
+ let msg = messages[key] ?? en[key] ?? key;
139
+ if (vars) {
140
+ for (const [k, v] of Object.entries(vars)) {
141
+ msg = msg.replaceAll(`{${k}}`, String(v));
142
+ }
143
+ }
144
+ return msg;
145
+ }
146
+ var en = {
147
+ // Analyzer
148
+ "analyzer.failed": "dependency-cruiser failed: {message}",
149
+ "analyzer.exitCode": "Analysis finished with error code {code}",
150
+ // Storage
151
+ "storage.parseFailed": "Failed to parse snapshot.json. File may be corrupted: {path}",
152
+ "storage.readFailed": "Failed to read snapshot.json: {path}",
153
+ "storage.invalidSchema": "snapshot.json schema is invalid. Please regenerate with `archtracker init`:\n{issues}",
154
+ "storage.versionMismatch": "snapshot.json version ({version}) is incompatible with current schema ({expected}). Regenerate with `archtracker init`.",
155
+ // Path guard
156
+ "pathGuard.traversal": 'Path points outside project root: "{input}" \u2192 "{resolved}" (allowed: "{boundary}")',
157
+ // Diff report
158
+ "diff.title": "# Architecture Change Report\n",
159
+ "diff.noChanges": "No changes \u2014 snapshot matches current code.\n",
160
+ "diff.added": "## Added Files ({count})",
161
+ "diff.removed": "## Removed Files ({count})",
162
+ "diff.modified": "## Modified Dependencies ({count})",
163
+ "diff.affected": "## Files Requiring Review ({count})",
164
+ "diff.reasonRemoved": 'Dependency "{file}" was removed',
165
+ "diff.reasonModified": 'Dependency "{file}" had its dependencies changed',
166
+ "diff.reasonAdded": 'New dependency "{file}" was added',
167
+ // Search
168
+ "search.pathMatch": 'Path matches "{pattern}"',
169
+ "search.affected": 'May be affected by changes to "{file}" (via: {via})',
170
+ "search.critical": "{count} files depend on this component",
171
+ "search.orphan": "Orphan file (no dependencies, no dependents)",
172
+ "search.noResults": 'No results: "{query}" (mode: {mode})',
173
+ "search.results": "Results: {count} (mode: {mode})",
174
+ // CLI
175
+ "cli.analyzing": "Analyzing...",
176
+ "cli.snapshotSaved": "Snapshot saved",
177
+ "cli.timestamp": " Timestamp: {ts}",
178
+ "cli.fileCount": " Files: {count}",
179
+ "cli.edgeCount": " Edges: {count}",
180
+ "cli.circularCount": " Circular deps: {count}",
181
+ "cli.keyComponents": "\nKey components:",
182
+ "cli.dependedBy": "{path} ({count} dependents)",
183
+ "cli.noSnapshot": "No snapshot found. Run `archtracker init` first.",
184
+ "cli.ciFailed": "\nCI check failed: {count} file(s) require review",
185
+ "cli.autoGenerating": "No snapshot found, auto-generating...",
186
+ "cli.project": "Project: {path}",
187
+ "cli.validPaths": "\nValid file paths:",
188
+ "cli.snapshot": "Snapshot: {ts}",
189
+ // MCP
190
+ "mcp.analyzeComplete": "Analysis complete: {files} files, {edges} edges",
191
+ "mcp.circularFound": "Circular deps: {count} found",
192
+ "mcp.circularNone": "Circular deps: none",
193
+ "mcp.snapshotSaved": "Snapshot saved",
194
+ "mcp.autoInit": "No snapshot existed. Initial snapshot auto-generated.",
195
+ "mcp.nextCheckEnabled": "Diff checking will be active from the next run.",
196
+ "mcp.queryRequired": '"{mode}" mode requires the query parameter',
197
+ // Analyze report
198
+ "analyze.title": "# Architecture Analysis Report\n",
199
+ "analyze.overview": "## Overview",
200
+ "analyze.totalFiles": " Total files: {count}",
201
+ "analyze.totalEdges": " Total edges: {count}",
202
+ "analyze.totalCircular": " Circular dependencies: {count}",
203
+ "analyze.criticalTitle": "\n## Critical Components (Top {count})",
204
+ "analyze.criticalItem": " {path} ({count} dependents)",
205
+ "analyze.circularTitle": "\n## Circular Dependencies ({count})",
206
+ "analyze.circularItem": " {files}",
207
+ "analyze.orphanTitle": "\n## Orphan Files ({count})",
208
+ "analyze.couplingTitle": "\n## High Coupling (Top {count} by import count)",
209
+ "analyze.couplingItem": " {path} ({count} imports)",
210
+ "analyze.layerTitle": "\n## Directory Breakdown",
211
+ "analyze.layerItem": " {dir}/ \u2014 {count} files",
212
+ "analyze.noIssues": "\nNo architectural issues detected.",
213
+ "analyze.snapshotSaved": "\nSnapshot saved alongside analysis.",
214
+ // CI
215
+ "ci.generated": "GitHub Actions workflow generated: {path}",
216
+ // Web viewer
217
+ "web.starting": "Starting architecture viewer...",
218
+ "web.listening": "Architecture graph available at: http://localhost:{port}",
219
+ "web.stop": "Press Ctrl+C to stop",
220
+ "web.watching": "Watching {dir}/ for changes...",
221
+ "web.reloading": "File change detected, reloading...",
222
+ "web.reloaded": "Graph reloaded",
223
+ // Errors
224
+ "error.analyzer": "[Analysis Error] {message}",
225
+ "error.storage": "[Storage Error] {message}",
226
+ "error.pathTraversal": "[Security Error] {message}",
227
+ "error.generic": "[Error] {message}",
228
+ "error.unexpected": "[Error] Unexpected error: {message}",
229
+ "error.cli.analyzer": "Analysis error: {message}",
230
+ "error.cli.storage": "Storage error: {message}",
231
+ "error.cli.generic": "Error: {message}",
232
+ "error.cli.unexpected": "Unexpected error: {message}"
233
+ };
234
+ var ja = {
235
+ // Analyzer
236
+ "analyzer.failed": "dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
237
+ "analyzer.exitCode": "\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 {code} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F",
238
+ // Storage
239
+ "storage.parseFailed": "snapshot.json \u306E\u30D1\u30FC\u30B9\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u30D5\u30A1\u30A4\u30EB\u304C\u7834\u640D\u3057\u3066\u3044\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059: {path}",
240
+ "storage.readFailed": "snapshot.json \u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {path}",
241
+ "storage.invalidSchema": "snapshot.json \u306E\u30B9\u30AD\u30FC\u30DE\u304C\u4E0D\u6B63\u3067\u3059\u3002archtracker init \u3067\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044:\n{issues}",
242
+ "storage.versionMismatch": "snapshot.json \u306E\u30D0\u30FC\u30B8\u30E7\u30F3 ({version}) \u304C\u73FE\u5728\u306E\u30B9\u30AD\u30FC\u30DE ({expected}) \u3068\u4E92\u63DB\u6027\u304C\u3042\u308A\u307E\u305B\u3093\u3002archtracker init \u3067\u518D\u751F\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
243
+ // Path guard
244
+ "pathGuard.traversal": '\u30D1\u30B9\u304C\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8\u30EB\u30FC\u30C8\u306E\u5916\u90E8\u3092\u6307\u3057\u3066\u3044\u307E\u3059: "{input}" \u2192 "{resolved}" (\u8A31\u53EF\u7BC4\u56F2: "{boundary}")',
245
+ // Diff report
246
+ "diff.title": "# \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u5909\u66F4\u30EC\u30DD\u30FC\u30C8\n",
247
+ "diff.noChanges": "\u5909\u66F4\u306A\u3057 \u2014 \u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3068\u73FE\u5728\u306E\u30B3\u30FC\u30C9\u306F\u4E00\u81F4\u3057\u3066\u3044\u307E\u3059\u3002\n",
248
+ "diff.added": "## \u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
249
+ "diff.removed": "## \u524A\u9664\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
250
+ "diff.modified": "## \u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
251
+ "diff.affected": "## \u78BA\u8A8D\u304C\u5FC5\u8981\u306A\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
252
+ "diff.reasonRemoved": '\u4F9D\u5B58\u5148 "{file}" \u304C\u524A\u9664\u3055\u308C\u307E\u3057\u305F',
253
+ "diff.reasonModified": '\u4F9D\u5B58\u5148 "{file}" \u306E\u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u307E\u3057\u305F',
254
+ "diff.reasonAdded": '\u65B0\u3057\u3044\u4F9D\u5B58\u5148 "{file}" \u304C\u8FFD\u52A0\u3055\u308C\u307E\u3057\u305F',
255
+ // Search
256
+ "search.pathMatch": '\u30D1\u30B9\u304C "{pattern}" \u306B\u30DE\u30C3\u30C1',
257
+ "search.affected": '"{file}" \u306E\u5909\u66F4\u306B\u3088\u308A\u5F71\u97FF\u3092\u53D7\u3051\u308B\u53EF\u80FD\u6027\uFF08\u7D4C\u7531: {via}\uFF09',
258
+ "search.critical": "{count}\u4EF6\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u4F9D\u5B58\u3059\u308B\u91CD\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8",
259
+ "search.orphan": "\u5B64\u7ACB\u30D5\u30A1\u30A4\u30EB\uFF08\u4F9D\u5B58\u306A\u3057\u30FB\u88AB\u4F9D\u5B58\u306A\u3057\uFF09",
260
+ "search.noResults": '\u691C\u7D22\u7D50\u679C\u306A\u3057: "{query}" (\u30E2\u30FC\u30C9: {mode})',
261
+ "search.results": "\u691C\u7D22\u7D50\u679C: {count}\u4EF6 (\u30E2\u30FC\u30C9: {mode})",
262
+ // CLI
263
+ "cli.analyzing": "\u89E3\u6790\u4E2D...",
264
+ "cli.snapshotSaved": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
265
+ "cli.timestamp": " \u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7: {ts}",
266
+ "cli.fileCount": " \u30D5\u30A1\u30A4\u30EB\u6570: {count}",
267
+ "cli.edgeCount": " \u30A8\u30C3\u30B8\u6570: {count}",
268
+ "cli.circularCount": " \u5FAA\u74B0\u53C2\u7167: {count}\u4EF6",
269
+ "cli.keyComponents": "\n\u4E3B\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8:",
270
+ "cli.dependedBy": "{path} ({count}\u4EF6\u304C\u4F9D\u5B58)",
271
+ "cli.noSnapshot": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`archtracker init` \u3092\u5148\u306B\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
272
+ "cli.ciFailed": "\nCI \u30C1\u30A7\u30C3\u30AF\u5931\u6557: {count}\u4EF6\u306E\u8981\u78BA\u8A8D\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u3059",
273
+ "cli.autoGenerating": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u7121\u3044\u305F\u3081\u81EA\u52D5\u751F\u6210\u3057\u307E\u3059...",
274
+ "cli.project": "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8: {path}",
275
+ "cli.validPaths": "\n\u6709\u52B9\u306A\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9:",
276
+ "cli.snapshot": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8: {ts}",
277
+ // MCP
278
+ "mcp.analyzeComplete": "\u89E3\u6790\u5B8C\u4E86: {files}\u30D5\u30A1\u30A4\u30EB, {edges}\u30A8\u30C3\u30B8",
279
+ "mcp.circularFound": "\u5FAA\u74B0\u53C2\u7167: {count}\u4EF6\u691C\u51FA",
280
+ "mcp.circularNone": "\u5FAA\u74B0\u53C2\u7167: \u306A\u3057",
281
+ "mcp.snapshotSaved": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
282
+ "mcp.autoInit": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u5B58\u5728\u3057\u306A\u304B\u3063\u305F\u305F\u3081\u3001\u521D\u671F\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u81EA\u52D5\u751F\u6210\u3057\u307E\u3057\u305F\u3002",
283
+ "mcp.nextCheckEnabled": "\u6B21\u56DE\u306E\u5B9F\u884C\u6642\u304B\u3089\u5DEE\u5206\u30C1\u30A7\u30C3\u30AF\u304C\u6709\u52B9\u306B\u306A\u308A\u307E\u3059\u3002",
284
+ "mcp.queryRequired": '"{mode}" \u30E2\u30FC\u30C9\u3067\u306F query \u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u5FC5\u9808\u3067\u3059',
285
+ // Analyze report
286
+ "analyze.title": "# \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u5206\u6790\u30EC\u30DD\u30FC\u30C8\n",
287
+ "analyze.overview": "## \u6982\u8981",
288
+ "analyze.totalFiles": " \u7DCF\u30D5\u30A1\u30A4\u30EB\u6570: {count}",
289
+ "analyze.totalEdges": " \u7DCF\u30A8\u30C3\u30B8\u6570: {count}",
290
+ "analyze.totalCircular": " \u5FAA\u74B0\u53C2\u7167: {count}\u4EF6",
291
+ "analyze.criticalTitle": "\n## \u91CD\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8 (\u4E0A\u4F4D{count}\u4EF6)",
292
+ "analyze.criticalItem": " {path} ({count}\u4EF6\u304C\u4F9D\u5B58)",
293
+ "analyze.circularTitle": "\n## \u5FAA\u74B0\u53C2\u7167 ({count}\u4EF6)",
294
+ "analyze.circularItem": " {files}",
295
+ "analyze.orphanTitle": "\n## \u5B64\u7ACB\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
296
+ "analyze.couplingTitle": "\n## \u9AD8\u7D50\u5408\u30D5\u30A1\u30A4\u30EB (import\u6570 \u4E0A\u4F4D{count}\u4EF6)",
297
+ "analyze.couplingItem": " {path} ({count}\u4EF6\u3092import)",
298
+ "analyze.layerTitle": "\n## \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u69CB\u6210",
299
+ "analyze.layerItem": " {dir}/ \u2014 {count}\u30D5\u30A1\u30A4\u30EB",
300
+ "analyze.noIssues": "\n\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u4E0A\u306E\u554F\u984C\u306F\u691C\u51FA\u3055\u308C\u307E\u305B\u3093\u3067\u3057\u305F\u3002",
301
+ "analyze.snapshotSaved": "\n\u5206\u6790\u3068\u540C\u6642\u306B\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
302
+ // CI
303
+ "ci.generated": "GitHub Actions \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3092\u751F\u6210\u3057\u307E\u3057\u305F: {path}",
304
+ // Web viewer
305
+ "web.starting": "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u30D3\u30E5\u30FC\u30A2\u30FC\u3092\u8D77\u52D5\u4E2D...",
306
+ "web.listening": "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u30B0\u30E9\u30D5: http://localhost:{port}",
307
+ "web.stop": "Ctrl+C \u3067\u505C\u6B62",
308
+ "web.watching": "{dir}/ \u3092\u76E3\u8996\u4E2D...",
309
+ "web.reloading": "\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u3092\u691C\u51FA\u3001\u30EA\u30ED\u30FC\u30C9\u4E2D...",
310
+ "web.reloaded": "\u30B0\u30E9\u30D5\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
311
+ // Errors
312
+ "error.analyzer": "[\u89E3\u6790\u30A8\u30E9\u30FC] {message}",
313
+ "error.storage": "[\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC] {message}",
314
+ "error.pathTraversal": "[\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30A8\u30E9\u30FC] {message}",
315
+ "error.generic": "[\u30A8\u30E9\u30FC] {message}",
316
+ "error.unexpected": "[\u30A8\u30E9\u30FC] \u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: {message}",
317
+ "error.cli.analyzer": "\u89E3\u6790\u30A8\u30E9\u30FC: {message}",
318
+ "error.cli.storage": "\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC: {message}",
319
+ "error.cli.generic": "\u30A8\u30E9\u30FC: {message}",
320
+ "error.cli.unexpected": "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC: {message}"
321
+ };
322
+
323
+ // src/analyzer/search.ts
324
+ function searchByPath(graph, pattern) {
325
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
326
+ const regex = new RegExp(escaped, "i");
327
+ return Object.values(graph.files).filter((f) => regex.test(f.path)).map((f) => toSearchResult(f, t("search.pathMatch", { pattern })));
328
+ }
329
+ function findAffectedFiles(graph, filePath, maxDepth = 10) {
330
+ const node = findNode(graph, filePath);
331
+ if (!node) return [];
332
+ const visited = /* @__PURE__ */ new Set();
333
+ const results = [];
334
+ function traverse(current, depth, via) {
335
+ if (depth > maxDepth || visited.has(current.path)) return;
336
+ visited.add(current.path);
337
+ if (current.path !== node.path) {
338
+ results.push(
339
+ toSearchResult(
340
+ current,
341
+ t("search.affected", { file: filePath, via })
342
+ )
343
+ );
344
+ }
345
+ for (const depPath of current.dependents) {
346
+ const depNode = graph.files[depPath];
347
+ if (depNode) {
348
+ traverse(depNode, depth + 1, current.path);
349
+ }
350
+ }
351
+ }
352
+ traverse(node, 0, filePath);
353
+ return results;
354
+ }
355
+ function findCriticalFiles(graph, limit = 10) {
356
+ return Object.values(graph.files).filter((f) => f.dependents.length > 0).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, limit).map(
357
+ (f) => toSearchResult(f, t("search.critical", { count: f.dependents.length }))
358
+ );
359
+ }
360
+ function findOrphanFiles(graph) {
361
+ return Object.values(graph.files).filter((f) => f.dependents.length === 0 && f.dependencies.length === 0).map((f) => toSearchResult(f, t("search.orphan")));
362
+ }
363
+ function findNode(graph, filePath) {
364
+ if (graph.files[filePath]) return graph.files[filePath];
365
+ const key = Object.keys(graph.files).find(
366
+ (k) => k.endsWith(filePath) || k.includes(filePath)
367
+ );
368
+ return key ? graph.files[key] : void 0;
369
+ }
370
+ function toSearchResult(node, matchReason) {
371
+ return {
372
+ file: node.path,
373
+ dependents: node.dependents,
374
+ dependencies: node.dependencies,
375
+ dependentCount: node.dependents.length,
376
+ dependencyCount: node.dependencies.length,
377
+ matchReason
378
+ };
379
+ }
380
+
381
+ // src/analyzer/report.ts
382
+ function formatAnalysisReport(graph, options = {}) {
383
+ const topN = options.topN ?? 10;
384
+ const lines = [];
385
+ const files = Object.values(graph.files);
386
+ lines.push(t("analyze.title"));
387
+ lines.push(t("analyze.overview"));
388
+ lines.push(t("analyze.totalFiles", { count: graph.totalFiles }));
389
+ lines.push(t("analyze.totalEdges", { count: graph.totalEdges }));
390
+ lines.push(t("analyze.totalCircular", { count: graph.circularDependencies.length }));
391
+ const critical = files.filter((f) => f.dependents.length > 0).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, topN);
392
+ if (critical.length > 0) {
393
+ lines.push(t("analyze.criticalTitle", { count: critical.length }));
394
+ for (const f of critical) {
395
+ lines.push(t("analyze.criticalItem", { path: f.path, count: f.dependents.length }));
396
+ }
397
+ }
398
+ if (graph.circularDependencies.length > 0) {
399
+ lines.push(t("analyze.circularTitle", { count: graph.circularDependencies.length }));
400
+ for (const c of graph.circularDependencies) {
401
+ lines.push(t("analyze.circularItem", { files: c.cycle.join(" \u2192 ") }));
402
+ }
403
+ }
404
+ const highCoupling = files.filter((f) => f.dependencies.length > 0).sort((a, b) => b.dependencies.length - a.dependencies.length).slice(0, topN);
405
+ if (highCoupling.length > 0) {
406
+ lines.push(t("analyze.couplingTitle", { count: highCoupling.length }));
407
+ for (const f of highCoupling) {
408
+ lines.push(t("analyze.couplingItem", { path: f.path, count: f.dependencies.length }));
409
+ }
410
+ }
411
+ const orphans = files.filter(
412
+ (f) => f.dependents.length === 0 && f.dependencies.length === 0
413
+ );
414
+ if (orphans.length > 0) {
415
+ lines.push(t("analyze.orphanTitle", { count: orphans.length }));
416
+ for (const f of orphans) {
417
+ lines.push(` ${f.path}`);
418
+ }
419
+ }
420
+ const dirCounts = /* @__PURE__ */ new Map();
421
+ for (const f of files) {
422
+ const dir = f.path.includes("/") ? f.path.substring(0, f.path.lastIndexOf("/")) : ".";
423
+ dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
424
+ }
425
+ if (dirCounts.size > 1) {
426
+ lines.push(t("analyze.layerTitle"));
427
+ const sorted = [...dirCounts.entries()].sort((a, b) => b[1] - a[1]);
428
+ for (const [dir, count] of sorted) {
429
+ lines.push(t("analyze.layerItem", { dir, count }));
430
+ }
431
+ }
432
+ if (graph.circularDependencies.length === 0 && orphans.length === 0) {
433
+ lines.push(t("analyze.noIssues"));
434
+ }
435
+ return lines.join("\n");
436
+ }
437
+
438
+ // src/storage/snapshot.ts
439
+ import { mkdir, writeFile, readFile, access } from "fs/promises";
440
+ import { join } from "path";
441
+ import { z } from "zod";
442
+
443
+ // src/types/schema.ts
444
+ var SCHEMA_VERSION = "1.0";
445
+
446
+ // src/storage/snapshot.ts
447
+ var ARCHTRACKER_DIR = ".archtracker";
448
+ var SNAPSHOT_FILE = "snapshot.json";
449
+ var FileNodeSchema = z.object({
450
+ path: z.string(),
451
+ exists: z.boolean(),
452
+ dependencies: z.array(z.string()),
453
+ dependents: z.array(z.string())
454
+ });
455
+ var DependencyGraphSchema = z.object({
456
+ rootDir: z.string(),
457
+ files: z.record(z.string(), FileNodeSchema),
458
+ edges: z.array(z.object({
459
+ source: z.string(),
460
+ target: z.string(),
461
+ type: z.enum(["static", "dynamic", "type-only"])
462
+ })),
463
+ circularDependencies: z.array(z.object({ cycle: z.array(z.string()) })),
464
+ totalFiles: z.number(),
465
+ totalEdges: z.number()
466
+ });
467
+ var SnapshotSchema = z.object({
468
+ version: z.literal(SCHEMA_VERSION),
469
+ timestamp: z.string(),
470
+ rootDir: z.string(),
471
+ graph: DependencyGraphSchema
472
+ });
473
+ async function saveSnapshot(projectRoot, graph) {
474
+ const dirPath = join(projectRoot, ARCHTRACKER_DIR);
475
+ const filePath = join(dirPath, SNAPSHOT_FILE);
476
+ const snapshot = {
477
+ version: SCHEMA_VERSION,
478
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
479
+ rootDir: graph.rootDir,
480
+ graph
481
+ };
482
+ await mkdir(dirPath, { recursive: true });
483
+ await writeFile(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
484
+ return snapshot;
485
+ }
486
+ async function loadSnapshot(projectRoot) {
487
+ const filePath = join(projectRoot, ARCHTRACKER_DIR, SNAPSHOT_FILE);
488
+ let raw;
489
+ try {
490
+ raw = await readFile(filePath, "utf-8");
491
+ } catch (error) {
492
+ if (isNodeError(error) && error.code === "ENOENT") {
493
+ return null;
494
+ }
495
+ throw new StorageError(
496
+ t("storage.readFailed", { path: filePath }),
497
+ { cause: error }
498
+ );
499
+ }
500
+ let parsed;
501
+ try {
502
+ parsed = JSON.parse(raw);
503
+ } catch {
504
+ throw new StorageError(
505
+ t("storage.parseFailed", { path: filePath })
506
+ );
507
+ }
508
+ const result = SnapshotSchema.safeParse(parsed);
509
+ if (!result.success) {
510
+ const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).slice(0, 5).join("\n");
511
+ throw new StorageError(
512
+ t("storage.invalidSchema", { issues })
513
+ );
514
+ }
515
+ return result.data;
516
+ }
517
+ var StorageError = class extends Error {
518
+ constructor(message, options) {
519
+ super(message, options);
520
+ this.name = "StorageError";
521
+ }
522
+ };
523
+ function isNodeError(error) {
524
+ return error instanceof Error && "code" in error;
525
+ }
526
+
527
+ // src/storage/diff.ts
528
+ function computeDiff(oldGraph, newGraph) {
529
+ const oldFiles = new Set(Object.keys(oldGraph.files));
530
+ const newFiles = new Set(Object.keys(newGraph.files));
531
+ const added = [...newFiles].filter((f) => !oldFiles.has(f));
532
+ const removed = [...oldFiles].filter((f) => !newFiles.has(f));
533
+ const modified = [];
534
+ for (const file of newFiles) {
535
+ if (!oldFiles.has(file)) continue;
536
+ const oldDeps = oldGraph.files[file].dependencies.slice().sort();
537
+ const newDeps = newGraph.files[file].dependencies.slice().sort();
538
+ if (!arraysEqual(oldDeps, newDeps)) {
539
+ modified.push(file);
540
+ }
541
+ }
542
+ const removedSet = new Set(removed);
543
+ const changedFiles = /* @__PURE__ */ new Set([...removed, ...modified]);
544
+ const affectedDependents = [];
545
+ const seenAffected = /* @__PURE__ */ new Set();
546
+ for (const changedFile of changedFiles) {
547
+ const graph = removedSet.has(changedFile) ? oldGraph : newGraph;
548
+ const node = graph.files[changedFile];
549
+ if (!node) continue;
550
+ for (const dependent of node.dependents) {
551
+ const key = `${dependent}\u2192${changedFile}`;
552
+ if (seenAffected.has(key)) continue;
553
+ seenAffected.add(key);
554
+ const reason = removedSet.has(changedFile) ? t("diff.reasonRemoved", { file: changedFile }) : t("diff.reasonModified", { file: changedFile });
555
+ affectedDependents.push({
556
+ file: dependent,
557
+ reason,
558
+ dependsOn: changedFile
559
+ });
560
+ }
561
+ }
562
+ for (const addedFile of added) {
563
+ const node = newGraph.files[addedFile];
564
+ if (!node) continue;
565
+ for (const dependent of node.dependents) {
566
+ const key = `${dependent}\u2192${addedFile}`;
567
+ if (seenAffected.has(key)) continue;
568
+ seenAffected.add(key);
569
+ affectedDependents.push({
570
+ file: dependent,
571
+ reason: t("diff.reasonAdded", { file: addedFile }),
572
+ dependsOn: addedFile
573
+ });
574
+ }
575
+ }
576
+ return { added, removed, modified, affectedDependents };
577
+ }
578
+ function formatDiffReport(diff) {
579
+ const lines = [];
580
+ lines.push(t("diff.title"));
581
+ if (diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0) {
582
+ lines.push(t("diff.noChanges"));
583
+ return lines.join("\n");
584
+ }
585
+ if (diff.added.length > 0) {
586
+ lines.push(t("diff.added", { count: diff.added.length }));
587
+ for (const f of diff.added) {
588
+ lines.push(` + ${f}`);
589
+ }
590
+ lines.push("");
591
+ }
592
+ if (diff.removed.length > 0) {
593
+ lines.push(t("diff.removed", { count: diff.removed.length }));
594
+ for (const f of diff.removed) {
595
+ lines.push(` - ${f}`);
596
+ }
597
+ lines.push("");
598
+ }
599
+ if (diff.modified.length > 0) {
600
+ lines.push(t("diff.modified", { count: diff.modified.length }));
601
+ for (const f of diff.modified) {
602
+ lines.push(` ~ ${f}`);
603
+ }
604
+ lines.push("");
605
+ }
606
+ if (diff.affectedDependents.length > 0) {
607
+ lines.push(t("diff.affected", { count: diff.affectedDependents.length }));
608
+ for (const a of diff.affectedDependents) {
609
+ lines.push(` ! ${a.file}`);
610
+ lines.push(` ${a.reason}`);
611
+ }
612
+ lines.push("");
613
+ }
614
+ return lines.join("\n");
615
+ }
616
+ function arraysEqual(a, b) {
617
+ if (a.length !== b.length) return false;
618
+ for (let i = 0; i < a.length; i++) {
619
+ if (a[i] !== b[i]) return false;
620
+ }
621
+ return true;
622
+ }
623
+
624
+ // src/utils/path-guard.ts
625
+ import { resolve as resolve2 } from "path";
626
+ function validatePath(inputPath, boundary) {
627
+ const resolved = resolve2(inputPath);
628
+ const root = boundary ? resolve2(boundary) : process.cwd();
629
+ if (!resolved.startsWith(root)) {
630
+ throw new PathTraversalError(
631
+ t("pathGuard.traversal", { input: inputPath, resolved, boundary: root })
632
+ );
633
+ }
634
+ return resolved;
635
+ }
636
+ var PathTraversalError = class extends Error {
637
+ constructor(message) {
638
+ super(message);
639
+ this.name = "PathTraversalError";
640
+ }
641
+ };
642
+
643
+ // src/mcp/index.ts
644
+ var server = new McpServer({
645
+ name: "archtracker",
646
+ version: "0.1.0"
647
+ });
648
+ server.tool(
649
+ "generate_map",
650
+ "Analyze dependency graph of a directory and return file import/export structure as JSON",
651
+ {
652
+ targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
653
+ exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude (e.g. ['test', 'mock'])"),
654
+ maxDepth: z2.number().int().min(0).optional().describe("Max analysis depth (0 = unlimited)")
655
+ },
656
+ async ({ targetDir, exclude, maxDepth }) => {
657
+ try {
658
+ validatePath(targetDir);
659
+ const graph = await analyzeProject(targetDir, { exclude, maxDepth });
660
+ const summary = [
661
+ t("mcp.analyzeComplete", { files: graph.totalFiles, edges: graph.totalEdges }),
662
+ graph.circularDependencies.length > 0 ? t("mcp.circularFound", { count: graph.circularDependencies.length }) : t("mcp.circularNone")
663
+ ].join("\n");
664
+ return {
665
+ content: [
666
+ { type: "text", text: summary },
667
+ { type: "text", text: JSON.stringify(graph, null, 2) }
668
+ ]
669
+ };
670
+ } catch (error) {
671
+ return errorResponse(error);
672
+ }
673
+ }
674
+ );
675
+ server.tool(
676
+ "analyze_existing_architecture",
677
+ "Comprehensive architecture analysis for existing projects. Shows critical components, circular dependencies, orphan files, coupling hotspots, and directory breakdown.",
678
+ {
679
+ targetDir: z2.string().default("src").describe("Target directory path (default: src)"),
680
+ exclude: z2.array(z2.string()).optional().describe("Array of regex patterns to exclude"),
681
+ topN: z2.number().int().min(1).max(50).optional().describe("Number of top items to show in each section (default: 10)"),
682
+ saveSnapshot: z2.boolean().optional().describe("Also save a snapshot after analysis (default: false)"),
683
+ projectRoot: z2.string().default(".").describe("Project root (needed only when saveSnapshot is true)")
684
+ },
685
+ async ({ targetDir, exclude, topN, saveSnapshot: doSave, projectRoot }) => {
686
+ try {
687
+ validatePath(targetDir);
688
+ const graph = await analyzeProject(targetDir, { exclude });
689
+ const report = formatAnalysisReport(graph, { topN: topN ?? 10 });
690
+ const content = [
691
+ { type: "text", text: report }
692
+ ];
693
+ if (doSave) {
694
+ validatePath(projectRoot);
695
+ await saveSnapshot(projectRoot, graph);
696
+ content.push({ type: "text", text: t("analyze.snapshotSaved") });
697
+ }
698
+ return { content };
699
+ } catch (error) {
700
+ return errorResponse(error);
701
+ }
702
+ }
703
+ );
704
+ server.tool(
705
+ "save_architecture_snapshot",
706
+ "Save the current dependency graph as a snapshot to .archtracker/snapshot.json",
707
+ {
708
+ targetDir: z2.string().default("src").describe("Target directory path"),
709
+ projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)")
710
+ },
711
+ async ({ targetDir, projectRoot }) => {
712
+ try {
713
+ validatePath(targetDir);
714
+ validatePath(projectRoot);
715
+ const graph = await analyzeProject(targetDir);
716
+ const snapshot = await saveSnapshot(projectRoot, graph);
717
+ const keyComponents = Object.values(graph.files).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, 5).map((f) => ` ${t("cli.dependedBy", { path: f.path, count: f.dependents.length })}`);
718
+ const report = [
719
+ t("mcp.snapshotSaved"),
720
+ t("cli.timestamp", { ts: snapshot.timestamp }),
721
+ t("cli.fileCount", { count: graph.totalFiles }),
722
+ t("cli.edgeCount", { count: graph.totalEdges }),
723
+ "",
724
+ t("cli.keyComponents"),
725
+ ...keyComponents
726
+ ].join("\n");
727
+ return { content: [{ type: "text", text: report }] };
728
+ } catch (error) {
729
+ return errorResponse(error);
730
+ }
731
+ }
732
+ );
733
+ server.tool(
734
+ "check_architecture_diff",
735
+ "Compare saved snapshot with current code dependencies and warn about files that may need updates",
736
+ {
737
+ targetDir: z2.string().default("src").describe("Target directory path"),
738
+ projectRoot: z2.string().default(".").describe("Project root (where .archtracker is placed)")
739
+ },
740
+ async ({ targetDir, projectRoot }) => {
741
+ try {
742
+ validatePath(targetDir);
743
+ validatePath(projectRoot);
744
+ const existingSnapshot = await loadSnapshot(projectRoot);
745
+ if (!existingSnapshot) {
746
+ const graph = await analyzeProject(targetDir);
747
+ await saveSnapshot(projectRoot, graph);
748
+ return {
749
+ content: [
750
+ {
751
+ type: "text",
752
+ text: [
753
+ t("mcp.autoInit"),
754
+ t("cli.fileCount", { count: graph.totalFiles }) + ", " + t("cli.edgeCount", { count: graph.totalEdges }),
755
+ t("mcp.nextCheckEnabled")
756
+ ].join("\n")
757
+ }
758
+ ]
759
+ };
760
+ }
761
+ const currentGraph = await analyzeProject(targetDir);
762
+ const diff = computeDiff(existingSnapshot.graph, currentGraph);
763
+ const report = formatDiffReport(diff);
764
+ return { content: [{ type: "text", text: report }] };
765
+ } catch (error) {
766
+ return errorResponse(error);
767
+ }
768
+ }
769
+ );
770
+ server.tool(
771
+ "get_current_context",
772
+ "Get current valid file paths and architecture summary for AI session initialization",
773
+ {
774
+ targetDir: z2.string().default("src").describe("Target directory path"),
775
+ projectRoot: z2.string().default(".").describe("Project root")
776
+ },
777
+ async ({ targetDir, projectRoot }) => {
778
+ try {
779
+ let snapshot = await loadSnapshot(projectRoot);
780
+ if (!snapshot) {
781
+ const graph2 = await analyzeProject(targetDir);
782
+ snapshot = await saveSnapshot(projectRoot, graph2);
783
+ }
784
+ const graph = snapshot.graph;
785
+ const keyComponents = Object.values(graph.files).filter((f) => f.dependents.length > 0 || f.dependencies.length > 0).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, 20).map((f) => ({
786
+ path: f.path,
787
+ dependentCount: f.dependents.length,
788
+ dependencyCount: f.dependencies.length
789
+ }));
790
+ const validPaths = Object.keys(graph.files).sort();
791
+ const summary = [
792
+ t("cli.project", { path: graph.rootDir }),
793
+ t("cli.fileCount", { count: graph.totalFiles }),
794
+ t("cli.edgeCount", { count: graph.totalEdges }),
795
+ t("cli.circularCount", { count: graph.circularDependencies.length }),
796
+ t("cli.snapshot", { ts: snapshot.timestamp }),
797
+ "",
798
+ t("cli.keyComponents"),
799
+ ...keyComponents.map(
800
+ (c) => ` ${t("cli.dependedBy", { path: c.path, count: c.dependentCount })}`
801
+ )
802
+ ].join("\n");
803
+ const context = {
804
+ validPaths,
805
+ summary,
806
+ snapshotExists: true,
807
+ snapshotTimestamp: snapshot.timestamp,
808
+ keyComponents
809
+ };
810
+ return {
811
+ content: [
812
+ { type: "text", text: summary },
813
+ {
814
+ type: "text",
815
+ text: JSON.stringify(context, null, 2)
816
+ }
817
+ ]
818
+ };
819
+ } catch (error) {
820
+ return errorResponse(error);
821
+ }
822
+ }
823
+ );
824
+ server.tool(
825
+ "search_architecture",
826
+ "Search architecture: file path search, impact analysis, critical component detection, orphan file detection",
827
+ {
828
+ query: z2.string().optional().describe("Search query (required for path/affected modes, not needed for critical/orphans)"),
829
+ mode: z2.enum(["path", "affected", "critical", "orphans"]).default("path").describe(
830
+ "Search mode: path=search by path, affected=change impact, critical=key files, orphans=isolated files"
831
+ ),
832
+ targetDir: z2.string().default("src").describe("Target directory path"),
833
+ projectRoot: z2.string().default(".").describe("Project root"),
834
+ limit: z2.number().int().min(1).max(50).optional().describe("Max results (default: 10)")
835
+ },
836
+ async ({ query, mode, targetDir, projectRoot, limit }) => {
837
+ try {
838
+ validatePath(targetDir);
839
+ validatePath(projectRoot);
840
+ let snapshot = await loadSnapshot(projectRoot);
841
+ if (!snapshot) {
842
+ const graph2 = await analyzeProject(targetDir);
843
+ snapshot = await saveSnapshot(projectRoot, graph2);
844
+ }
845
+ const graph = snapshot.graph;
846
+ const maxResults = limit ?? 10;
847
+ let results;
848
+ if ((mode === "path" || mode === "affected") && !query) {
849
+ return {
850
+ content: [{ type: "text", text: t("mcp.queryRequired", { mode }) }],
851
+ isError: true
852
+ };
853
+ }
854
+ switch (mode) {
855
+ case "path":
856
+ results = searchByPath(graph, query);
857
+ break;
858
+ case "affected":
859
+ results = findAffectedFiles(graph, query);
860
+ break;
861
+ case "critical":
862
+ results = findCriticalFiles(graph, maxResults);
863
+ break;
864
+ case "orphans":
865
+ results = findOrphanFiles(graph);
866
+ break;
867
+ }
868
+ if (results.length === 0) {
869
+ return {
870
+ content: [
871
+ { type: "text", text: t("search.noResults", { query: query ?? "", mode }) }
872
+ ]
873
+ };
874
+ }
875
+ const lines = [
876
+ t("search.results", { count: results.length, mode }),
877
+ "",
878
+ ...results.slice(0, maxResults).map((r) => {
879
+ return [
880
+ ` ${r.file}`,
881
+ ` ${r.matchReason}`,
882
+ ` deps: ${r.dependencyCount} -> [${r.dependencies.slice(0, 5).join(", ")}${r.dependencies.length > 5 ? "..." : ""}]`,
883
+ ` dependents: ${r.dependentCount} <- [${r.dependents.slice(0, 5).join(", ")}${r.dependents.length > 5 ? "..." : ""}]`
884
+ ].join("\n");
885
+ })
886
+ ];
887
+ return { content: [{ type: "text", text: lines.join("\n") }] };
888
+ } catch (error) {
889
+ return errorResponse(error);
890
+ }
891
+ }
892
+ );
893
+ function errorResponse(error) {
894
+ let message;
895
+ if (error instanceof PathTraversalError) {
896
+ message = t("error.pathTraversal", { message: error.message });
897
+ } else if (error instanceof AnalyzerError) {
898
+ message = t("error.analyzer", { message: error.message });
899
+ } else if (error instanceof StorageError) {
900
+ message = t("error.storage", { message: error.message });
901
+ } else if (error instanceof Error) {
902
+ message = t("error.generic", { message: error.message });
903
+ } else {
904
+ message = t("error.unexpected", { message: String(error) });
905
+ }
906
+ return {
907
+ content: [{ type: "text", text: message }],
908
+ isError: true
909
+ };
910
+ }
911
+ async function main() {
912
+ const transport = new StdioServerTransport();
913
+ await server.connect(transport);
914
+ console.error("[archtracker] MCP server running on stdio");
915
+ }
916
+ main().catch((error) => {
917
+ console.error("[archtracker] Fatal error:", error);
918
+ process.exit(1);
919
+ });
920
+ //# sourceMappingURL=index.js.map