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.
- package/LICENSE +21 -0
- package/README.md +468 -0
- package/dist/cli/index.js +1853 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +144 -0
- package/dist/index.js +587 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +920 -0
- package/dist/mcp/index.js.map +1 -0
- package/package.json +78 -0
- package/skills/arch-analyze/SKILL.md +28 -0
- package/skills/arch-check/SKILL.md +22 -0
- package/skills/arch-context/SKILL.md +21 -0
- package/skills/arch-search/SKILL.md +24 -0
- package/skills/arch-snapshot/SKILL.md +20 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
// src/types/schema.ts
|
|
2
|
+
var SCHEMA_VERSION = "1.0";
|
|
3
|
+
|
|
4
|
+
// src/analyzer/analyze.ts
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
import { cruise } from "dependency-cruiser";
|
|
7
|
+
var DEFAULT_EXCLUDE = [
|
|
8
|
+
"node_modules",
|
|
9
|
+
"\\.d\\.ts$",
|
|
10
|
+
"dist",
|
|
11
|
+
"build",
|
|
12
|
+
"coverage",
|
|
13
|
+
"\\.archtracker"
|
|
14
|
+
];
|
|
15
|
+
async function analyzeProject(rootDir, options = {}) {
|
|
16
|
+
const {
|
|
17
|
+
exclude = [],
|
|
18
|
+
maxDepth = 0,
|
|
19
|
+
tsConfigPath,
|
|
20
|
+
includeTypeOnly = true
|
|
21
|
+
} = options;
|
|
22
|
+
const absRootDir = resolve(rootDir);
|
|
23
|
+
const allExclude = [...DEFAULT_EXCLUDE, ...exclude];
|
|
24
|
+
const excludePattern = allExclude.join("|");
|
|
25
|
+
const cruiseOptions = {
|
|
26
|
+
baseDir: absRootDir,
|
|
27
|
+
exclude: { path: excludePattern },
|
|
28
|
+
doNotFollow: { path: "node_modules" },
|
|
29
|
+
maxDepth,
|
|
30
|
+
tsPreCompilationDeps: includeTypeOnly ? true : false,
|
|
31
|
+
combinedDependencies: false
|
|
32
|
+
};
|
|
33
|
+
if (tsConfigPath) {
|
|
34
|
+
cruiseOptions.tsConfig = { fileName: tsConfigPath };
|
|
35
|
+
}
|
|
36
|
+
let result;
|
|
37
|
+
try {
|
|
38
|
+
result = await cruise(["."], cruiseOptions);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
throw new AnalyzerError(
|
|
42
|
+
`dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: ${message}`,
|
|
43
|
+
{ cause: error }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (result.exitCode !== 0 && !result.output) {
|
|
47
|
+
throw new AnalyzerError(
|
|
48
|
+
`\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 ${result.exitCode} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const cruiseResult = result.output;
|
|
52
|
+
return buildGraph(absRootDir, cruiseResult);
|
|
53
|
+
}
|
|
54
|
+
function buildGraph(rootDir, cruiseResult) {
|
|
55
|
+
const files = {};
|
|
56
|
+
const edges = [];
|
|
57
|
+
const circularSet = /* @__PURE__ */ new Set();
|
|
58
|
+
const circularDependencies = [];
|
|
59
|
+
for (const mod of cruiseResult.modules) {
|
|
60
|
+
if (isExternalModule(mod)) continue;
|
|
61
|
+
files[mod.source] = {
|
|
62
|
+
path: mod.source,
|
|
63
|
+
exists: !mod.couldNotResolve,
|
|
64
|
+
dependencies: [],
|
|
65
|
+
dependents: []
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
for (const mod of cruiseResult.modules) {
|
|
69
|
+
for (const dep of mod.dependencies) {
|
|
70
|
+
if (dep.couldNotResolve || dep.coreModule) continue;
|
|
71
|
+
if (!files[mod.source] || isExternalDep(dep)) continue;
|
|
72
|
+
const edgeType = dep.typeOnly ? "type-only" : dep.dynamic ? "dynamic" : "static";
|
|
73
|
+
edges.push({
|
|
74
|
+
source: mod.source,
|
|
75
|
+
target: dep.resolved,
|
|
76
|
+
type: edgeType
|
|
77
|
+
});
|
|
78
|
+
if (files[mod.source]) {
|
|
79
|
+
files[mod.source].dependencies.push(dep.resolved);
|
|
80
|
+
}
|
|
81
|
+
if (files[dep.resolved]) {
|
|
82
|
+
files[dep.resolved].dependents.push(mod.source);
|
|
83
|
+
}
|
|
84
|
+
if (dep.circular && dep.cycle) {
|
|
85
|
+
const cyclePath = dep.cycle.map((c) => c.name);
|
|
86
|
+
const cycleKey = [...cyclePath].sort().join("\u2192");
|
|
87
|
+
if (!circularSet.has(cycleKey)) {
|
|
88
|
+
circularSet.add(cycleKey);
|
|
89
|
+
circularDependencies.push({ cycle: cyclePath });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
rootDir,
|
|
96
|
+
files,
|
|
97
|
+
edges,
|
|
98
|
+
circularDependencies,
|
|
99
|
+
totalFiles: Object.keys(files).length,
|
|
100
|
+
totalEdges: edges.length
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function isExternalModule(mod) {
|
|
104
|
+
if (mod.coreModule) return true;
|
|
105
|
+
const depTypes = mod.dependencyTypes ?? [];
|
|
106
|
+
if (depTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
|
|
107
|
+
return isExternalPath(mod.source);
|
|
108
|
+
}
|
|
109
|
+
function isExternalDep(dep) {
|
|
110
|
+
if (dep.coreModule) return true;
|
|
111
|
+
if (dep.dependencyTypes.some((t2) => t2.startsWith("npm") || t2 === "core")) return true;
|
|
112
|
+
return isExternalPath(dep.resolved);
|
|
113
|
+
}
|
|
114
|
+
function isExternalPath(source) {
|
|
115
|
+
if (source.startsWith("@")) return true;
|
|
116
|
+
if (!source.includes("/") && !source.includes("\\") && !source.includes(".")) return true;
|
|
117
|
+
if (source.startsWith("node:")) return true;
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
var AnalyzerError = class extends Error {
|
|
121
|
+
constructor(message, options) {
|
|
122
|
+
super(message, options);
|
|
123
|
+
this.name = "AnalyzerError";
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// src/i18n/index.ts
|
|
128
|
+
var currentLocale = detectLocale();
|
|
129
|
+
function getLocale() {
|
|
130
|
+
return currentLocale;
|
|
131
|
+
}
|
|
132
|
+
function setLocale(locale) {
|
|
133
|
+
currentLocale = locale;
|
|
134
|
+
}
|
|
135
|
+
function detectLocale() {
|
|
136
|
+
const env = process.env.LC_ALL || process.env.LANG || "";
|
|
137
|
+
if (env.startsWith("ja")) return "ja";
|
|
138
|
+
return "en";
|
|
139
|
+
}
|
|
140
|
+
function t(key, vars) {
|
|
141
|
+
const messages = currentLocale === "ja" ? ja : en;
|
|
142
|
+
let msg = messages[key] ?? en[key] ?? key;
|
|
143
|
+
if (vars) {
|
|
144
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
145
|
+
msg = msg.replaceAll(`{${k}}`, String(v));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return msg;
|
|
149
|
+
}
|
|
150
|
+
var en = {
|
|
151
|
+
// Analyzer
|
|
152
|
+
"analyzer.failed": "dependency-cruiser failed: {message}",
|
|
153
|
+
"analyzer.exitCode": "Analysis finished with error code {code}",
|
|
154
|
+
// Storage
|
|
155
|
+
"storage.parseFailed": "Failed to parse snapshot.json. File may be corrupted: {path}",
|
|
156
|
+
"storage.readFailed": "Failed to read snapshot.json: {path}",
|
|
157
|
+
"storage.invalidSchema": "snapshot.json schema is invalid. Please regenerate with `archtracker init`:\n{issues}",
|
|
158
|
+
"storage.versionMismatch": "snapshot.json version ({version}) is incompatible with current schema ({expected}). Regenerate with `archtracker init`.",
|
|
159
|
+
// Path guard
|
|
160
|
+
"pathGuard.traversal": 'Path points outside project root: "{input}" \u2192 "{resolved}" (allowed: "{boundary}")',
|
|
161
|
+
// Diff report
|
|
162
|
+
"diff.title": "# Architecture Change Report\n",
|
|
163
|
+
"diff.noChanges": "No changes \u2014 snapshot matches current code.\n",
|
|
164
|
+
"diff.added": "## Added Files ({count})",
|
|
165
|
+
"diff.removed": "## Removed Files ({count})",
|
|
166
|
+
"diff.modified": "## Modified Dependencies ({count})",
|
|
167
|
+
"diff.affected": "## Files Requiring Review ({count})",
|
|
168
|
+
"diff.reasonRemoved": 'Dependency "{file}" was removed',
|
|
169
|
+
"diff.reasonModified": 'Dependency "{file}" had its dependencies changed',
|
|
170
|
+
"diff.reasonAdded": 'New dependency "{file}" was added',
|
|
171
|
+
// Search
|
|
172
|
+
"search.pathMatch": 'Path matches "{pattern}"',
|
|
173
|
+
"search.affected": 'May be affected by changes to "{file}" (via: {via})',
|
|
174
|
+
"search.critical": "{count} files depend on this component",
|
|
175
|
+
"search.orphan": "Orphan file (no dependencies, no dependents)",
|
|
176
|
+
"search.noResults": 'No results: "{query}" (mode: {mode})',
|
|
177
|
+
"search.results": "Results: {count} (mode: {mode})",
|
|
178
|
+
// CLI
|
|
179
|
+
"cli.analyzing": "Analyzing...",
|
|
180
|
+
"cli.snapshotSaved": "Snapshot saved",
|
|
181
|
+
"cli.timestamp": " Timestamp: {ts}",
|
|
182
|
+
"cli.fileCount": " Files: {count}",
|
|
183
|
+
"cli.edgeCount": " Edges: {count}",
|
|
184
|
+
"cli.circularCount": " Circular deps: {count}",
|
|
185
|
+
"cli.keyComponents": "\nKey components:",
|
|
186
|
+
"cli.dependedBy": "{path} ({count} dependents)",
|
|
187
|
+
"cli.noSnapshot": "No snapshot found. Run `archtracker init` first.",
|
|
188
|
+
"cli.ciFailed": "\nCI check failed: {count} file(s) require review",
|
|
189
|
+
"cli.autoGenerating": "No snapshot found, auto-generating...",
|
|
190
|
+
"cli.project": "Project: {path}",
|
|
191
|
+
"cli.validPaths": "\nValid file paths:",
|
|
192
|
+
"cli.snapshot": "Snapshot: {ts}",
|
|
193
|
+
// MCP
|
|
194
|
+
"mcp.analyzeComplete": "Analysis complete: {files} files, {edges} edges",
|
|
195
|
+
"mcp.circularFound": "Circular deps: {count} found",
|
|
196
|
+
"mcp.circularNone": "Circular deps: none",
|
|
197
|
+
"mcp.snapshotSaved": "Snapshot saved",
|
|
198
|
+
"mcp.autoInit": "No snapshot existed. Initial snapshot auto-generated.",
|
|
199
|
+
"mcp.nextCheckEnabled": "Diff checking will be active from the next run.",
|
|
200
|
+
"mcp.queryRequired": '"{mode}" mode requires the query parameter',
|
|
201
|
+
// Analyze report
|
|
202
|
+
"analyze.title": "# Architecture Analysis Report\n",
|
|
203
|
+
"analyze.overview": "## Overview",
|
|
204
|
+
"analyze.totalFiles": " Total files: {count}",
|
|
205
|
+
"analyze.totalEdges": " Total edges: {count}",
|
|
206
|
+
"analyze.totalCircular": " Circular dependencies: {count}",
|
|
207
|
+
"analyze.criticalTitle": "\n## Critical Components (Top {count})",
|
|
208
|
+
"analyze.criticalItem": " {path} ({count} dependents)",
|
|
209
|
+
"analyze.circularTitle": "\n## Circular Dependencies ({count})",
|
|
210
|
+
"analyze.circularItem": " {files}",
|
|
211
|
+
"analyze.orphanTitle": "\n## Orphan Files ({count})",
|
|
212
|
+
"analyze.couplingTitle": "\n## High Coupling (Top {count} by import count)",
|
|
213
|
+
"analyze.couplingItem": " {path} ({count} imports)",
|
|
214
|
+
"analyze.layerTitle": "\n## Directory Breakdown",
|
|
215
|
+
"analyze.layerItem": " {dir}/ \u2014 {count} files",
|
|
216
|
+
"analyze.noIssues": "\nNo architectural issues detected.",
|
|
217
|
+
"analyze.snapshotSaved": "\nSnapshot saved alongside analysis.",
|
|
218
|
+
// CI
|
|
219
|
+
"ci.generated": "GitHub Actions workflow generated: {path}",
|
|
220
|
+
// Web viewer
|
|
221
|
+
"web.starting": "Starting architecture viewer...",
|
|
222
|
+
"web.listening": "Architecture graph available at: http://localhost:{port}",
|
|
223
|
+
"web.stop": "Press Ctrl+C to stop",
|
|
224
|
+
"web.watching": "Watching {dir}/ for changes...",
|
|
225
|
+
"web.reloading": "File change detected, reloading...",
|
|
226
|
+
"web.reloaded": "Graph reloaded",
|
|
227
|
+
// Errors
|
|
228
|
+
"error.analyzer": "[Analysis Error] {message}",
|
|
229
|
+
"error.storage": "[Storage Error] {message}",
|
|
230
|
+
"error.pathTraversal": "[Security Error] {message}",
|
|
231
|
+
"error.generic": "[Error] {message}",
|
|
232
|
+
"error.unexpected": "[Error] Unexpected error: {message}",
|
|
233
|
+
"error.cli.analyzer": "Analysis error: {message}",
|
|
234
|
+
"error.cli.storage": "Storage error: {message}",
|
|
235
|
+
"error.cli.generic": "Error: {message}",
|
|
236
|
+
"error.cli.unexpected": "Unexpected error: {message}"
|
|
237
|
+
};
|
|
238
|
+
var ja = {
|
|
239
|
+
// Analyzer
|
|
240
|
+
"analyzer.failed": "dependency-cruiser \u306E\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {message}",
|
|
241
|
+
"analyzer.exitCode": "\u89E3\u6790\u304C\u30A8\u30E9\u30FC\u30B3\u30FC\u30C9 {code} \u3067\u7D42\u4E86\u3057\u307E\u3057\u305F",
|
|
242
|
+
// Storage
|
|
243
|
+
"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}",
|
|
244
|
+
"storage.readFailed": "snapshot.json \u306E\u8AAD\u307F\u53D6\u308A\u306B\u5931\u6557\u3057\u307E\u3057\u305F: {path}",
|
|
245
|
+
"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}",
|
|
246
|
+
"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",
|
|
247
|
+
// Path guard
|
|
248
|
+
"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}")',
|
|
249
|
+
// Diff report
|
|
250
|
+
"diff.title": "# \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u5909\u66F4\u30EC\u30DD\u30FC\u30C8\n",
|
|
251
|
+
"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",
|
|
252
|
+
"diff.added": "## \u8FFD\u52A0\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
|
|
253
|
+
"diff.removed": "## \u524A\u9664\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
|
|
254
|
+
"diff.modified": "## \u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u305F\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
|
|
255
|
+
"diff.affected": "## \u78BA\u8A8D\u304C\u5FC5\u8981\u306A\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
|
|
256
|
+
"diff.reasonRemoved": '\u4F9D\u5B58\u5148 "{file}" \u304C\u524A\u9664\u3055\u308C\u307E\u3057\u305F',
|
|
257
|
+
"diff.reasonModified": '\u4F9D\u5B58\u5148 "{file}" \u306E\u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u307E\u3057\u305F',
|
|
258
|
+
"diff.reasonAdded": '\u65B0\u3057\u3044\u4F9D\u5B58\u5148 "{file}" \u304C\u8FFD\u52A0\u3055\u308C\u307E\u3057\u305F',
|
|
259
|
+
// Search
|
|
260
|
+
"search.pathMatch": '\u30D1\u30B9\u304C "{pattern}" \u306B\u30DE\u30C3\u30C1',
|
|
261
|
+
"search.affected": '"{file}" \u306E\u5909\u66F4\u306B\u3088\u308A\u5F71\u97FF\u3092\u53D7\u3051\u308B\u53EF\u80FD\u6027\uFF08\u7D4C\u7531: {via}\uFF09',
|
|
262
|
+
"search.critical": "{count}\u4EF6\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u4F9D\u5B58\u3059\u308B\u91CD\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8",
|
|
263
|
+
"search.orphan": "\u5B64\u7ACB\u30D5\u30A1\u30A4\u30EB\uFF08\u4F9D\u5B58\u306A\u3057\u30FB\u88AB\u4F9D\u5B58\u306A\u3057\uFF09",
|
|
264
|
+
"search.noResults": '\u691C\u7D22\u7D50\u679C\u306A\u3057: "{query}" (\u30E2\u30FC\u30C9: {mode})',
|
|
265
|
+
"search.results": "\u691C\u7D22\u7D50\u679C: {count}\u4EF6 (\u30E2\u30FC\u30C9: {mode})",
|
|
266
|
+
// CLI
|
|
267
|
+
"cli.analyzing": "\u89E3\u6790\u4E2D...",
|
|
268
|
+
"cli.snapshotSaved": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
|
|
269
|
+
"cli.timestamp": " \u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7: {ts}",
|
|
270
|
+
"cli.fileCount": " \u30D5\u30A1\u30A4\u30EB\u6570: {count}",
|
|
271
|
+
"cli.edgeCount": " \u30A8\u30C3\u30B8\u6570: {count}",
|
|
272
|
+
"cli.circularCount": " \u5FAA\u74B0\u53C2\u7167: {count}\u4EF6",
|
|
273
|
+
"cli.keyComponents": "\n\u4E3B\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8:",
|
|
274
|
+
"cli.dependedBy": "{path} ({count}\u4EF6\u304C\u4F9D\u5B58)",
|
|
275
|
+
"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",
|
|
276
|
+
"cli.ciFailed": "\nCI \u30C1\u30A7\u30C3\u30AF\u5931\u6557: {count}\u4EF6\u306E\u8981\u78BA\u8A8D\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u3059",
|
|
277
|
+
"cli.autoGenerating": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u7121\u3044\u305F\u3081\u81EA\u52D5\u751F\u6210\u3057\u307E\u3059...",
|
|
278
|
+
"cli.project": "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8: {path}",
|
|
279
|
+
"cli.validPaths": "\n\u6709\u52B9\u306A\u30D5\u30A1\u30A4\u30EB\u30D1\u30B9:",
|
|
280
|
+
"cli.snapshot": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8: {ts}",
|
|
281
|
+
// MCP
|
|
282
|
+
"mcp.analyzeComplete": "\u89E3\u6790\u5B8C\u4E86: {files}\u30D5\u30A1\u30A4\u30EB, {edges}\u30A8\u30C3\u30B8",
|
|
283
|
+
"mcp.circularFound": "\u5FAA\u74B0\u53C2\u7167: {count}\u4EF6\u691C\u51FA",
|
|
284
|
+
"mcp.circularNone": "\u5FAA\u74B0\u53C2\u7167: \u306A\u3057",
|
|
285
|
+
"mcp.snapshotSaved": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F",
|
|
286
|
+
"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",
|
|
287
|
+
"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",
|
|
288
|
+
"mcp.queryRequired": '"{mode}" \u30E2\u30FC\u30C9\u3067\u306F query \u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u5FC5\u9808\u3067\u3059',
|
|
289
|
+
// Analyze report
|
|
290
|
+
"analyze.title": "# \u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u5206\u6790\u30EC\u30DD\u30FC\u30C8\n",
|
|
291
|
+
"analyze.overview": "## \u6982\u8981",
|
|
292
|
+
"analyze.totalFiles": " \u7DCF\u30D5\u30A1\u30A4\u30EB\u6570: {count}",
|
|
293
|
+
"analyze.totalEdges": " \u7DCF\u30A8\u30C3\u30B8\u6570: {count}",
|
|
294
|
+
"analyze.totalCircular": " \u5FAA\u74B0\u53C2\u7167: {count}\u4EF6",
|
|
295
|
+
"analyze.criticalTitle": "\n## \u91CD\u8981\u30B3\u30F3\u30DD\u30FC\u30CD\u30F3\u30C8 (\u4E0A\u4F4D{count}\u4EF6)",
|
|
296
|
+
"analyze.criticalItem": " {path} ({count}\u4EF6\u304C\u4F9D\u5B58)",
|
|
297
|
+
"analyze.circularTitle": "\n## \u5FAA\u74B0\u53C2\u7167 ({count}\u4EF6)",
|
|
298
|
+
"analyze.circularItem": " {files}",
|
|
299
|
+
"analyze.orphanTitle": "\n## \u5B64\u7ACB\u30D5\u30A1\u30A4\u30EB ({count}\u4EF6)",
|
|
300
|
+
"analyze.couplingTitle": "\n## \u9AD8\u7D50\u5408\u30D5\u30A1\u30A4\u30EB (import\u6570 \u4E0A\u4F4D{count}\u4EF6)",
|
|
301
|
+
"analyze.couplingItem": " {path} ({count}\u4EF6\u3092import)",
|
|
302
|
+
"analyze.layerTitle": "\n## \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u69CB\u6210",
|
|
303
|
+
"analyze.layerItem": " {dir}/ \u2014 {count}\u30D5\u30A1\u30A4\u30EB",
|
|
304
|
+
"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",
|
|
305
|
+
"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",
|
|
306
|
+
// CI
|
|
307
|
+
"ci.generated": "GitHub Actions \u30EF\u30FC\u30AF\u30D5\u30ED\u30FC\u3092\u751F\u6210\u3057\u307E\u3057\u305F: {path}",
|
|
308
|
+
// Web viewer
|
|
309
|
+
"web.starting": "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u30D3\u30E5\u30FC\u30A2\u30FC\u3092\u8D77\u52D5\u4E2D...",
|
|
310
|
+
"web.listening": "\u30A2\u30FC\u30AD\u30C6\u30AF\u30C1\u30E3\u30B0\u30E9\u30D5: http://localhost:{port}",
|
|
311
|
+
"web.stop": "Ctrl+C \u3067\u505C\u6B62",
|
|
312
|
+
"web.watching": "{dir}/ \u3092\u76E3\u8996\u4E2D...",
|
|
313
|
+
"web.reloading": "\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u3092\u691C\u51FA\u3001\u30EA\u30ED\u30FC\u30C9\u4E2D...",
|
|
314
|
+
"web.reloaded": "\u30B0\u30E9\u30D5\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
|
|
315
|
+
// Errors
|
|
316
|
+
"error.analyzer": "[\u89E3\u6790\u30A8\u30E9\u30FC] {message}",
|
|
317
|
+
"error.storage": "[\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC] {message}",
|
|
318
|
+
"error.pathTraversal": "[\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30A8\u30E9\u30FC] {message}",
|
|
319
|
+
"error.generic": "[\u30A8\u30E9\u30FC] {message}",
|
|
320
|
+
"error.unexpected": "[\u30A8\u30E9\u30FC] \u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u307E\u3057\u305F: {message}",
|
|
321
|
+
"error.cli.analyzer": "\u89E3\u6790\u30A8\u30E9\u30FC: {message}",
|
|
322
|
+
"error.cli.storage": "\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC: {message}",
|
|
323
|
+
"error.cli.generic": "\u30A8\u30E9\u30FC: {message}",
|
|
324
|
+
"error.cli.unexpected": "\u4E88\u671F\u3057\u306A\u3044\u30A8\u30E9\u30FC: {message}"
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// src/analyzer/report.ts
|
|
328
|
+
function formatAnalysisReport(graph, options = {}) {
|
|
329
|
+
const topN = options.topN ?? 10;
|
|
330
|
+
const lines = [];
|
|
331
|
+
const files = Object.values(graph.files);
|
|
332
|
+
lines.push(t("analyze.title"));
|
|
333
|
+
lines.push(t("analyze.overview"));
|
|
334
|
+
lines.push(t("analyze.totalFiles", { count: graph.totalFiles }));
|
|
335
|
+
lines.push(t("analyze.totalEdges", { count: graph.totalEdges }));
|
|
336
|
+
lines.push(t("analyze.totalCircular", { count: graph.circularDependencies.length }));
|
|
337
|
+
const critical = files.filter((f) => f.dependents.length > 0).sort((a, b) => b.dependents.length - a.dependents.length).slice(0, topN);
|
|
338
|
+
if (critical.length > 0) {
|
|
339
|
+
lines.push(t("analyze.criticalTitle", { count: critical.length }));
|
|
340
|
+
for (const f of critical) {
|
|
341
|
+
lines.push(t("analyze.criticalItem", { path: f.path, count: f.dependents.length }));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (graph.circularDependencies.length > 0) {
|
|
345
|
+
lines.push(t("analyze.circularTitle", { count: graph.circularDependencies.length }));
|
|
346
|
+
for (const c of graph.circularDependencies) {
|
|
347
|
+
lines.push(t("analyze.circularItem", { files: c.cycle.join(" \u2192 ") }));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const highCoupling = files.filter((f) => f.dependencies.length > 0).sort((a, b) => b.dependencies.length - a.dependencies.length).slice(0, topN);
|
|
351
|
+
if (highCoupling.length > 0) {
|
|
352
|
+
lines.push(t("analyze.couplingTitle", { count: highCoupling.length }));
|
|
353
|
+
for (const f of highCoupling) {
|
|
354
|
+
lines.push(t("analyze.couplingItem", { path: f.path, count: f.dependencies.length }));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const orphans = files.filter(
|
|
358
|
+
(f) => f.dependents.length === 0 && f.dependencies.length === 0
|
|
359
|
+
);
|
|
360
|
+
if (orphans.length > 0) {
|
|
361
|
+
lines.push(t("analyze.orphanTitle", { count: orphans.length }));
|
|
362
|
+
for (const f of orphans) {
|
|
363
|
+
lines.push(` ${f.path}`);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const dirCounts = /* @__PURE__ */ new Map();
|
|
367
|
+
for (const f of files) {
|
|
368
|
+
const dir = f.path.includes("/") ? f.path.substring(0, f.path.lastIndexOf("/")) : ".";
|
|
369
|
+
dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);
|
|
370
|
+
}
|
|
371
|
+
if (dirCounts.size > 1) {
|
|
372
|
+
lines.push(t("analyze.layerTitle"));
|
|
373
|
+
const sorted = [...dirCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
374
|
+
for (const [dir, count] of sorted) {
|
|
375
|
+
lines.push(t("analyze.layerItem", { dir, count }));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (graph.circularDependencies.length === 0 && orphans.length === 0) {
|
|
379
|
+
lines.push(t("analyze.noIssues"));
|
|
380
|
+
}
|
|
381
|
+
return lines.join("\n");
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/storage/snapshot.ts
|
|
385
|
+
import { mkdir, writeFile, readFile, access } from "fs/promises";
|
|
386
|
+
import { join } from "path";
|
|
387
|
+
import { z } from "zod";
|
|
388
|
+
var ARCHTRACKER_DIR = ".archtracker";
|
|
389
|
+
var SNAPSHOT_FILE = "snapshot.json";
|
|
390
|
+
var FileNodeSchema = z.object({
|
|
391
|
+
path: z.string(),
|
|
392
|
+
exists: z.boolean(),
|
|
393
|
+
dependencies: z.array(z.string()),
|
|
394
|
+
dependents: z.array(z.string())
|
|
395
|
+
});
|
|
396
|
+
var DependencyGraphSchema = z.object({
|
|
397
|
+
rootDir: z.string(),
|
|
398
|
+
files: z.record(z.string(), FileNodeSchema),
|
|
399
|
+
edges: z.array(z.object({
|
|
400
|
+
source: z.string(),
|
|
401
|
+
target: z.string(),
|
|
402
|
+
type: z.enum(["static", "dynamic", "type-only"])
|
|
403
|
+
})),
|
|
404
|
+
circularDependencies: z.array(z.object({ cycle: z.array(z.string()) })),
|
|
405
|
+
totalFiles: z.number(),
|
|
406
|
+
totalEdges: z.number()
|
|
407
|
+
});
|
|
408
|
+
var SnapshotSchema = z.object({
|
|
409
|
+
version: z.literal(SCHEMA_VERSION),
|
|
410
|
+
timestamp: z.string(),
|
|
411
|
+
rootDir: z.string(),
|
|
412
|
+
graph: DependencyGraphSchema
|
|
413
|
+
});
|
|
414
|
+
async function saveSnapshot(projectRoot, graph) {
|
|
415
|
+
const dirPath = join(projectRoot, ARCHTRACKER_DIR);
|
|
416
|
+
const filePath = join(dirPath, SNAPSHOT_FILE);
|
|
417
|
+
const snapshot = {
|
|
418
|
+
version: SCHEMA_VERSION,
|
|
419
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
420
|
+
rootDir: graph.rootDir,
|
|
421
|
+
graph
|
|
422
|
+
};
|
|
423
|
+
await mkdir(dirPath, { recursive: true });
|
|
424
|
+
await writeFile(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
425
|
+
return snapshot;
|
|
426
|
+
}
|
|
427
|
+
async function loadSnapshot(projectRoot) {
|
|
428
|
+
const filePath = join(projectRoot, ARCHTRACKER_DIR, SNAPSHOT_FILE);
|
|
429
|
+
let raw;
|
|
430
|
+
try {
|
|
431
|
+
raw = await readFile(filePath, "utf-8");
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
throw new StorageError(
|
|
437
|
+
t("storage.readFailed", { path: filePath }),
|
|
438
|
+
{ cause: error }
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
let parsed;
|
|
442
|
+
try {
|
|
443
|
+
parsed = JSON.parse(raw);
|
|
444
|
+
} catch {
|
|
445
|
+
throw new StorageError(
|
|
446
|
+
t("storage.parseFailed", { path: filePath })
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
const result = SnapshotSchema.safeParse(parsed);
|
|
450
|
+
if (!result.success) {
|
|
451
|
+
const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).slice(0, 5).join("\n");
|
|
452
|
+
throw new StorageError(
|
|
453
|
+
t("storage.invalidSchema", { issues })
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return result.data;
|
|
457
|
+
}
|
|
458
|
+
async function hasArchtrackerDir(projectRoot) {
|
|
459
|
+
try {
|
|
460
|
+
await access(join(projectRoot, ARCHTRACKER_DIR));
|
|
461
|
+
return true;
|
|
462
|
+
} catch {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
var StorageError = class extends Error {
|
|
467
|
+
constructor(message, options) {
|
|
468
|
+
super(message, options);
|
|
469
|
+
this.name = "StorageError";
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
function isNodeError(error) {
|
|
473
|
+
return error instanceof Error && "code" in error;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/storage/diff.ts
|
|
477
|
+
function computeDiff(oldGraph, newGraph) {
|
|
478
|
+
const oldFiles = new Set(Object.keys(oldGraph.files));
|
|
479
|
+
const newFiles = new Set(Object.keys(newGraph.files));
|
|
480
|
+
const added = [...newFiles].filter((f) => !oldFiles.has(f));
|
|
481
|
+
const removed = [...oldFiles].filter((f) => !newFiles.has(f));
|
|
482
|
+
const modified = [];
|
|
483
|
+
for (const file of newFiles) {
|
|
484
|
+
if (!oldFiles.has(file)) continue;
|
|
485
|
+
const oldDeps = oldGraph.files[file].dependencies.slice().sort();
|
|
486
|
+
const newDeps = newGraph.files[file].dependencies.slice().sort();
|
|
487
|
+
if (!arraysEqual(oldDeps, newDeps)) {
|
|
488
|
+
modified.push(file);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const removedSet = new Set(removed);
|
|
492
|
+
const changedFiles = /* @__PURE__ */ new Set([...removed, ...modified]);
|
|
493
|
+
const affectedDependents = [];
|
|
494
|
+
const seenAffected = /* @__PURE__ */ new Set();
|
|
495
|
+
for (const changedFile of changedFiles) {
|
|
496
|
+
const graph = removedSet.has(changedFile) ? oldGraph : newGraph;
|
|
497
|
+
const node = graph.files[changedFile];
|
|
498
|
+
if (!node) continue;
|
|
499
|
+
for (const dependent of node.dependents) {
|
|
500
|
+
const key = `${dependent}\u2192${changedFile}`;
|
|
501
|
+
if (seenAffected.has(key)) continue;
|
|
502
|
+
seenAffected.add(key);
|
|
503
|
+
const reason = removedSet.has(changedFile) ? t("diff.reasonRemoved", { file: changedFile }) : t("diff.reasonModified", { file: changedFile });
|
|
504
|
+
affectedDependents.push({
|
|
505
|
+
file: dependent,
|
|
506
|
+
reason,
|
|
507
|
+
dependsOn: changedFile
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
for (const addedFile of added) {
|
|
512
|
+
const node = newGraph.files[addedFile];
|
|
513
|
+
if (!node) continue;
|
|
514
|
+
for (const dependent of node.dependents) {
|
|
515
|
+
const key = `${dependent}\u2192${addedFile}`;
|
|
516
|
+
if (seenAffected.has(key)) continue;
|
|
517
|
+
seenAffected.add(key);
|
|
518
|
+
affectedDependents.push({
|
|
519
|
+
file: dependent,
|
|
520
|
+
reason: t("diff.reasonAdded", { file: addedFile }),
|
|
521
|
+
dependsOn: addedFile
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return { added, removed, modified, affectedDependents };
|
|
526
|
+
}
|
|
527
|
+
function formatDiffReport(diff) {
|
|
528
|
+
const lines = [];
|
|
529
|
+
lines.push(t("diff.title"));
|
|
530
|
+
if (diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0) {
|
|
531
|
+
lines.push(t("diff.noChanges"));
|
|
532
|
+
return lines.join("\n");
|
|
533
|
+
}
|
|
534
|
+
if (diff.added.length > 0) {
|
|
535
|
+
lines.push(t("diff.added", { count: diff.added.length }));
|
|
536
|
+
for (const f of diff.added) {
|
|
537
|
+
lines.push(` + ${f}`);
|
|
538
|
+
}
|
|
539
|
+
lines.push("");
|
|
540
|
+
}
|
|
541
|
+
if (diff.removed.length > 0) {
|
|
542
|
+
lines.push(t("diff.removed", { count: diff.removed.length }));
|
|
543
|
+
for (const f of diff.removed) {
|
|
544
|
+
lines.push(` - ${f}`);
|
|
545
|
+
}
|
|
546
|
+
lines.push("");
|
|
547
|
+
}
|
|
548
|
+
if (diff.modified.length > 0) {
|
|
549
|
+
lines.push(t("diff.modified", { count: diff.modified.length }));
|
|
550
|
+
for (const f of diff.modified) {
|
|
551
|
+
lines.push(` ~ ${f}`);
|
|
552
|
+
}
|
|
553
|
+
lines.push("");
|
|
554
|
+
}
|
|
555
|
+
if (diff.affectedDependents.length > 0) {
|
|
556
|
+
lines.push(t("diff.affected", { count: diff.affectedDependents.length }));
|
|
557
|
+
for (const a of diff.affectedDependents) {
|
|
558
|
+
lines.push(` ! ${a.file}`);
|
|
559
|
+
lines.push(` ${a.reason}`);
|
|
560
|
+
}
|
|
561
|
+
lines.push("");
|
|
562
|
+
}
|
|
563
|
+
return lines.join("\n");
|
|
564
|
+
}
|
|
565
|
+
function arraysEqual(a, b) {
|
|
566
|
+
if (a.length !== b.length) return false;
|
|
567
|
+
for (let i = 0; i < a.length; i++) {
|
|
568
|
+
if (a[i] !== b[i]) return false;
|
|
569
|
+
}
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
export {
|
|
573
|
+
AnalyzerError,
|
|
574
|
+
SCHEMA_VERSION,
|
|
575
|
+
StorageError,
|
|
576
|
+
analyzeProject,
|
|
577
|
+
computeDiff,
|
|
578
|
+
formatAnalysisReport,
|
|
579
|
+
formatDiffReport,
|
|
580
|
+
getLocale,
|
|
581
|
+
hasArchtrackerDir,
|
|
582
|
+
loadSnapshot,
|
|
583
|
+
saveSnapshot,
|
|
584
|
+
setLocale,
|
|
585
|
+
t
|
|
586
|
+
};
|
|
587
|
+
//# sourceMappingURL=index.js.map
|