archtracker-mcp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +344 -0
- package/README.md +16 -321
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/cli/index.js +863 -1809
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +12 -2
- package/dist/index.js +171 -109
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.js +629 -189
- package/dist/mcp/index.js.map +1 -1
- package/dist/web/viewer.js +1572 -0
- package/package.json +5 -3
- package/skills/arch-analyze/SKILL.md +2 -0
- package/skills/arch-check/SKILL.md +1 -0
- package/skills/arch-search/SKILL.md +4 -0
- package/skills/arch-snapshot/SKILL.md +1 -0
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,12 @@ interface ArchSnapshot {
|
|
|
56
56
|
graph: DependencyGraph;
|
|
57
57
|
/** Present only when layers.json was used */
|
|
58
58
|
multiLayer?: MultiLayerGraph;
|
|
59
|
+
/** Analysis options used to generate this snapshot (for diff consistency checks) */
|
|
60
|
+
analysisOptions?: {
|
|
61
|
+
targetDir?: string;
|
|
62
|
+
language?: string;
|
|
63
|
+
exclude?: string[];
|
|
64
|
+
};
|
|
59
65
|
}
|
|
60
66
|
/** Diff result between two snapshots */
|
|
61
67
|
interface ArchDiff {
|
|
@@ -156,7 +162,7 @@ declare function formatAnalysisReport(graph: DependencyGraph, options?: {
|
|
|
156
162
|
* Each layer is analyzed independently via analyzeProject().
|
|
157
163
|
* Results are merged into a unified graph with layer-prefixed node paths.
|
|
158
164
|
*/
|
|
159
|
-
declare function analyzeMultiLayer(projectRoot: string, layerDefs: LayerDefinition[]): Promise<MultiLayerGraph>;
|
|
165
|
+
declare function analyzeMultiLayer(projectRoot: string, layerDefs: LayerDefinition[], globalExclude?: string[]): Promise<MultiLayerGraph>;
|
|
160
166
|
/**
|
|
161
167
|
* Auto-detect cross-layer connections by scanning file contents
|
|
162
168
|
* for references to identifiers defined in other layers.
|
|
@@ -180,7 +186,11 @@ declare function detectCrossLayerConnections(layers: Record<string, DependencyGr
|
|
|
180
186
|
* Creates .archtracker/ directory if it doesn't exist.
|
|
181
187
|
* Writes snapshot.json with schema version and timestamp.
|
|
182
188
|
*/
|
|
183
|
-
declare function saveSnapshot(projectRoot: string, graph: DependencyGraph, multiLayer?: MultiLayerGraph
|
|
189
|
+
declare function saveSnapshot(projectRoot: string, graph: DependencyGraph, multiLayer?: MultiLayerGraph, analysisOptions?: {
|
|
190
|
+
targetDir?: string;
|
|
191
|
+
language?: string;
|
|
192
|
+
exclude?: string[];
|
|
193
|
+
}): Promise<ArchSnapshot>;
|
|
184
194
|
/**
|
|
185
195
|
* Load the most recent snapshot from .archtracker/snapshot.json.
|
|
186
196
|
*
|
package/dist/index.js
CHANGED
|
@@ -1564,6 +1564,8 @@ var en = {
|
|
|
1564
1564
|
"diff.reasonRemoved": 'Dependency "{file}" was removed',
|
|
1565
1565
|
"diff.reasonModified": 'Dependency "{file}" had its dependencies changed',
|
|
1566
1566
|
"diff.reasonAdded": 'New dependency "{file}" was added',
|
|
1567
|
+
"diff.testSummary": " ... and {count} test/fixture file(s)",
|
|
1568
|
+
"diff.testAffectedSummary": " ... and {count} test/fixture-related review(s)",
|
|
1567
1569
|
// Search
|
|
1568
1570
|
"search.pathMatch": 'Path matches "{pattern}"',
|
|
1569
1571
|
"search.affected": 'May be affected by changes to "{file}" (via: {via})',
|
|
@@ -1625,6 +1627,12 @@ var en = {
|
|
|
1625
1627
|
"web.watching": "Watching {dir}/ for changes...",
|
|
1626
1628
|
"web.reloading": "File change detected, reloading...",
|
|
1627
1629
|
"web.reloaded": "Graph reloaded",
|
|
1630
|
+
// History
|
|
1631
|
+
"history.title": "# Snapshot History\n",
|
|
1632
|
+
"history.empty": "No snapshots found. Run `archtracker init` to create one.",
|
|
1633
|
+
"history.entry": " {ts} | {files} files, {edges} edges, {circular} circular{layers}",
|
|
1634
|
+
"history.count": "{count} snapshot(s) recorded",
|
|
1635
|
+
"history.snapshotNotFound": "Snapshot not found for timestamp: {ts}",
|
|
1628
1636
|
// Errors
|
|
1629
1637
|
"error.analyzer": "[Analysis Error] {message}",
|
|
1630
1638
|
"error.storage": "[Storage Error] {message}",
|
|
@@ -1657,6 +1665,8 @@ var ja = {
|
|
|
1657
1665
|
"diff.reasonRemoved": '\u4F9D\u5B58\u5148 "{file}" \u304C\u524A\u9664\u3055\u308C\u307E\u3057\u305F',
|
|
1658
1666
|
"diff.reasonModified": '\u4F9D\u5B58\u5148 "{file}" \u306E\u4F9D\u5B58\u95A2\u4FC2\u304C\u5909\u66F4\u3055\u308C\u307E\u3057\u305F',
|
|
1659
1667
|
"diff.reasonAdded": '\u65B0\u3057\u3044\u4F9D\u5B58\u5148 "{file}" \u304C\u8FFD\u52A0\u3055\u308C\u307E\u3057\u305F',
|
|
1668
|
+
"diff.testSummary": " ... \u4ED6 {count}\u4EF6\u306E\u30C6\u30B9\u30C8/\u30D5\u30A3\u30AF\u30B9\u30C1\u30E3\u30D5\u30A1\u30A4\u30EB",
|
|
1669
|
+
"diff.testAffectedSummary": " ... \u4ED6 {count}\u4EF6\u306E\u30C6\u30B9\u30C8/\u30D5\u30A3\u30AF\u30B9\u30C1\u30E3\u95A2\u9023\u306E\u78BA\u8A8D\u9805\u76EE",
|
|
1660
1670
|
// Search
|
|
1661
1671
|
"search.pathMatch": '\u30D1\u30B9\u304C "{pattern}" \u306B\u30DE\u30C3\u30C1',
|
|
1662
1672
|
"search.affected": '"{file}" \u306E\u5909\u66F4\u306B\u3088\u308A\u5F71\u97FF\u3092\u53D7\u3051\u308B\u53EF\u80FD\u6027\uFF08\u7D4C\u7531: {via}\uFF09',
|
|
@@ -1718,6 +1728,12 @@ var ja = {
|
|
|
1718
1728
|
"web.watching": "{dir}/ \u3092\u76E3\u8996\u4E2D...",
|
|
1719
1729
|
"web.reloading": "\u30D5\u30A1\u30A4\u30EB\u5909\u66F4\u3092\u691C\u51FA\u3001\u30EA\u30ED\u30FC\u30C9\u4E2D...",
|
|
1720
1730
|
"web.reloaded": "\u30B0\u30E9\u30D5\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
|
|
1731
|
+
// History
|
|
1732
|
+
"history.title": "# \u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u5C65\u6B74\n",
|
|
1733
|
+
"history.empty": "\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002`archtracker init` \u3067\u4F5C\u6210\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
1734
|
+
"history.entry": " {ts} | {files}\u30D5\u30A1\u30A4\u30EB, {edges}\u30A8\u30C3\u30B8, \u5FAA\u74B0{circular}\u4EF6{layers}",
|
|
1735
|
+
"history.count": "{count}\u4EF6\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u3092\u8A18\u9332",
|
|
1736
|
+
"history.snapshotNotFound": "\u6307\u5B9A\u306E\u30BF\u30A4\u30E0\u30B9\u30BF\u30F3\u30D7\u306E\u30B9\u30CA\u30C3\u30D7\u30B7\u30E7\u30C3\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: {ts}",
|
|
1721
1737
|
// Errors
|
|
1722
1738
|
"error.analyzer": "[\u89E3\u6790\u30A8\u30E9\u30FC] {message}",
|
|
1723
1739
|
"error.storage": "[\u30B9\u30C8\u30EC\u30FC\u30B8\u30A8\u30E9\u30FC] {message}",
|
|
@@ -1802,14 +1818,14 @@ var LAYER_COLORS = [
|
|
|
1802
1818
|
"#ffa657",
|
|
1803
1819
|
"#7ee787"
|
|
1804
1820
|
];
|
|
1805
|
-
async function analyzeMultiLayer(projectRoot, layerDefs) {
|
|
1821
|
+
async function analyzeMultiLayer(projectRoot, layerDefs, globalExclude) {
|
|
1806
1822
|
const layers = {};
|
|
1807
1823
|
const layerMetadata = [];
|
|
1808
1824
|
for (let idx = 0; idx < layerDefs.length; idx++) {
|
|
1809
1825
|
const def = layerDefs[idx];
|
|
1810
1826
|
const targetDir = resolve4(projectRoot, def.targetDir);
|
|
1811
1827
|
const graph = await analyzeProject(targetDir, {
|
|
1812
|
-
exclude: def.exclude,
|
|
1828
|
+
exclude: def.exclude ?? globalExclude,
|
|
1813
1829
|
language: def.language
|
|
1814
1830
|
});
|
|
1815
1831
|
const language = def.language ?? await detectLanguage(targetDir) ?? "javascript";
|
|
@@ -2170,57 +2186,144 @@ function mergeLayerGraphs(projectRoot, layers) {
|
|
|
2170
2186
|
};
|
|
2171
2187
|
}
|
|
2172
2188
|
|
|
2173
|
-
// src/storage/
|
|
2174
|
-
import {
|
|
2189
|
+
// src/storage/layers.ts
|
|
2190
|
+
import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
|
|
2175
2191
|
import { join as join5 } from "path";
|
|
2176
2192
|
import { z } from "zod";
|
|
2177
2193
|
var ARCHTRACKER_DIR = ".archtracker";
|
|
2194
|
+
var LAYERS_FILE = "layers.json";
|
|
2195
|
+
var LayerDefinitionSchema = z.object({
|
|
2196
|
+
name: z.string().min(1).regex(
|
|
2197
|
+
/^[a-zA-Z0-9_-]+$/,
|
|
2198
|
+
"Layer name must be alphanumeric (hyphens/underscores allowed)"
|
|
2199
|
+
),
|
|
2200
|
+
targetDir: z.string().min(1),
|
|
2201
|
+
language: z.enum(LANGUAGE_IDS).optional(),
|
|
2202
|
+
exclude: z.array(z.string()).optional(),
|
|
2203
|
+
color: z.string().optional(),
|
|
2204
|
+
description: z.string().optional()
|
|
2205
|
+
});
|
|
2206
|
+
var CrossLayerConnectionSchema = z.object({
|
|
2207
|
+
fromLayer: z.string(),
|
|
2208
|
+
fromFile: z.string(),
|
|
2209
|
+
toLayer: z.string(),
|
|
2210
|
+
toFile: z.string(),
|
|
2211
|
+
type: z.enum(["api-call", "event", "data-flow", "manual"]),
|
|
2212
|
+
label: z.string().optional()
|
|
2213
|
+
});
|
|
2214
|
+
var LayerConfigSchema = z.object({
|
|
2215
|
+
version: z.literal("1.0"),
|
|
2216
|
+
layers: z.array(LayerDefinitionSchema).min(1).refine(
|
|
2217
|
+
(layers) => {
|
|
2218
|
+
const names = layers.map((l) => l.name);
|
|
2219
|
+
return new Set(names).size === names.length;
|
|
2220
|
+
},
|
|
2221
|
+
{ message: "Layer names must be unique" }
|
|
2222
|
+
),
|
|
2223
|
+
connections: z.array(CrossLayerConnectionSchema).optional()
|
|
2224
|
+
});
|
|
2225
|
+
async function loadLayerConfig(projectRoot) {
|
|
2226
|
+
const filePath = join5(projectRoot, ARCHTRACKER_DIR, LAYERS_FILE);
|
|
2227
|
+
let raw;
|
|
2228
|
+
try {
|
|
2229
|
+
raw = await readFile2(filePath, "utf-8");
|
|
2230
|
+
} catch (error) {
|
|
2231
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
2232
|
+
return null;
|
|
2233
|
+
}
|
|
2234
|
+
throw new Error(`Failed to read ${filePath}`);
|
|
2235
|
+
}
|
|
2236
|
+
let parsed;
|
|
2237
|
+
try {
|
|
2238
|
+
parsed = JSON.parse(raw);
|
|
2239
|
+
} catch {
|
|
2240
|
+
throw new Error(`Invalid JSON in ${filePath}`);
|
|
2241
|
+
}
|
|
2242
|
+
const result = LayerConfigSchema.safeParse(parsed);
|
|
2243
|
+
if (!result.success) {
|
|
2244
|
+
const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).slice(0, 5).join("\n");
|
|
2245
|
+
throw new Error(`layers.json validation failed:
|
|
2246
|
+
${issues}`);
|
|
2247
|
+
}
|
|
2248
|
+
return result.data;
|
|
2249
|
+
}
|
|
2250
|
+
async function saveLayerConfig(projectRoot, config) {
|
|
2251
|
+
const dirPath = join5(projectRoot, ARCHTRACKER_DIR);
|
|
2252
|
+
const filePath = join5(dirPath, LAYERS_FILE);
|
|
2253
|
+
await mkdir(dirPath, { recursive: true });
|
|
2254
|
+
await writeFile(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2255
|
+
}
|
|
2256
|
+
function isNodeError(error) {
|
|
2257
|
+
return error instanceof Error && "code" in error;
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/storage/graph-cache.ts
|
|
2261
|
+
import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2, stat as stat4 } from "fs/promises";
|
|
2262
|
+
import { join as join6, resolve as resolve5 } from "path";
|
|
2263
|
+
import { readdir as readdir3 } from "fs/promises";
|
|
2264
|
+
import { createHash } from "crypto";
|
|
2265
|
+
|
|
2266
|
+
// src/storage/snapshot.ts
|
|
2267
|
+
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile4, readdir as readdir4, access } from "fs/promises";
|
|
2268
|
+
import { join as join7 } from "path";
|
|
2269
|
+
import { z as z2 } from "zod";
|
|
2270
|
+
var ARCHTRACKER_DIR2 = ".archtracker";
|
|
2178
2271
|
var SNAPSHOT_FILE = "snapshot.json";
|
|
2179
|
-
var
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2272
|
+
var HISTORY_DIR = "history";
|
|
2273
|
+
var FileNodeSchema = z2.object({
|
|
2274
|
+
path: z2.string(),
|
|
2275
|
+
exists: z2.boolean(),
|
|
2276
|
+
dependencies: z2.array(z2.string()),
|
|
2277
|
+
dependents: z2.array(z2.string())
|
|
2184
2278
|
});
|
|
2185
|
-
var DependencyGraphSchema =
|
|
2186
|
-
rootDir:
|
|
2187
|
-
files:
|
|
2188
|
-
edges:
|
|
2189
|
-
source:
|
|
2190
|
-
target:
|
|
2191
|
-
type:
|
|
2279
|
+
var DependencyGraphSchema = z2.object({
|
|
2280
|
+
rootDir: z2.string(),
|
|
2281
|
+
files: z2.record(z2.string(), FileNodeSchema),
|
|
2282
|
+
edges: z2.array(z2.object({
|
|
2283
|
+
source: z2.string(),
|
|
2284
|
+
target: z2.string(),
|
|
2285
|
+
type: z2.enum(["static", "dynamic", "type-only"])
|
|
2192
2286
|
})),
|
|
2193
|
-
circularDependencies:
|
|
2194
|
-
totalFiles:
|
|
2195
|
-
totalEdges:
|
|
2287
|
+
circularDependencies: z2.array(z2.object({ cycle: z2.array(z2.string()) })),
|
|
2288
|
+
totalFiles: z2.number(),
|
|
2289
|
+
totalEdges: z2.number()
|
|
2196
2290
|
});
|
|
2197
|
-
var SnapshotSchema =
|
|
2198
|
-
version:
|
|
2199
|
-
timestamp:
|
|
2200
|
-
rootDir:
|
|
2291
|
+
var SnapshotSchema = z2.object({
|
|
2292
|
+
version: z2.enum([SCHEMA_VERSION, "1.0"]),
|
|
2293
|
+
timestamp: z2.string(),
|
|
2294
|
+
rootDir: z2.string(),
|
|
2201
2295
|
graph: DependencyGraphSchema
|
|
2202
2296
|
});
|
|
2203
|
-
async function saveSnapshot(projectRoot, graph, multiLayer) {
|
|
2204
|
-
const dirPath =
|
|
2205
|
-
const filePath =
|
|
2297
|
+
async function saveSnapshot(projectRoot, graph, multiLayer, analysisOptions) {
|
|
2298
|
+
const dirPath = join7(projectRoot, ARCHTRACKER_DIR2);
|
|
2299
|
+
const filePath = join7(dirPath, SNAPSHOT_FILE);
|
|
2206
2300
|
const snapshot = {
|
|
2207
2301
|
version: SCHEMA_VERSION,
|
|
2208
2302
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2209
2303
|
rootDir: graph.rootDir,
|
|
2210
2304
|
graph,
|
|
2211
|
-
...multiLayer ? { multiLayer } : {}
|
|
2305
|
+
...multiLayer ? { multiLayer } : {},
|
|
2306
|
+
...analysisOptions ? { analysisOptions } : {}
|
|
2212
2307
|
};
|
|
2213
|
-
await
|
|
2214
|
-
|
|
2308
|
+
await mkdir3(dirPath, { recursive: true });
|
|
2309
|
+
const json = JSON.stringify(snapshot, null, 2);
|
|
2310
|
+
await writeFile3(filePath, json, "utf-8");
|
|
2311
|
+
try {
|
|
2312
|
+
const historyPath = join7(dirPath, HISTORY_DIR);
|
|
2313
|
+
await mkdir3(historyPath, { recursive: true });
|
|
2314
|
+
const safeTs = snapshot.timestamp.replace(/[:.]/g, "-");
|
|
2315
|
+
await writeFile3(join7(historyPath, `${safeTs}.json`), json, "utf-8");
|
|
2316
|
+
} catch {
|
|
2317
|
+
}
|
|
2215
2318
|
return snapshot;
|
|
2216
2319
|
}
|
|
2217
2320
|
async function loadSnapshot(projectRoot) {
|
|
2218
|
-
const filePath =
|
|
2321
|
+
const filePath = join7(projectRoot, ARCHTRACKER_DIR2, SNAPSHOT_FILE);
|
|
2219
2322
|
let raw;
|
|
2220
2323
|
try {
|
|
2221
|
-
raw = await
|
|
2324
|
+
raw = await readFile4(filePath, "utf-8");
|
|
2222
2325
|
} catch (error) {
|
|
2223
|
-
if (
|
|
2326
|
+
if (isNodeError2(error) && error.code === "ENOENT") {
|
|
2224
2327
|
return null;
|
|
2225
2328
|
}
|
|
2226
2329
|
throw new StorageError(
|
|
@@ -2247,7 +2350,7 @@ async function loadSnapshot(projectRoot) {
|
|
|
2247
2350
|
}
|
|
2248
2351
|
async function hasArchtrackerDir(projectRoot) {
|
|
2249
2352
|
try {
|
|
2250
|
-
await access(
|
|
2353
|
+
await access(join7(projectRoot, ARCHTRACKER_DIR2));
|
|
2251
2354
|
return true;
|
|
2252
2355
|
} catch {
|
|
2253
2356
|
return false;
|
|
@@ -2259,7 +2362,7 @@ var StorageError = class extends Error {
|
|
|
2259
2362
|
this.name = "StorageError";
|
|
2260
2363
|
}
|
|
2261
2364
|
};
|
|
2262
|
-
function
|
|
2365
|
+
function isNodeError2(error) {
|
|
2263
2366
|
return error instanceof Error && "code" in error;
|
|
2264
2367
|
}
|
|
2265
2368
|
|
|
@@ -2314,6 +2417,17 @@ function computeDiff(oldGraph, newGraph) {
|
|
|
2314
2417
|
}
|
|
2315
2418
|
return { added, removed, modified, affectedDependents };
|
|
2316
2419
|
}
|
|
2420
|
+
function isTestOrFixture(path) {
|
|
2421
|
+
return /(__fixtures__|__tests__|__mocks__|\.test\.|\.spec\.|\.e2e\.)/.test(path);
|
|
2422
|
+
}
|
|
2423
|
+
function partition(arr, pred) {
|
|
2424
|
+
const yes = [];
|
|
2425
|
+
const no = [];
|
|
2426
|
+
for (const item of arr) {
|
|
2427
|
+
(pred(item) ? yes : no).push(item);
|
|
2428
|
+
}
|
|
2429
|
+
return [yes, no];
|
|
2430
|
+
}
|
|
2317
2431
|
function formatDiffReport(diff) {
|
|
2318
2432
|
const lines = [];
|
|
2319
2433
|
lines.push(t("diff.title"));
|
|
@@ -2322,32 +2436,51 @@ function formatDiffReport(diff) {
|
|
|
2322
2436
|
return lines.join("\n");
|
|
2323
2437
|
}
|
|
2324
2438
|
if (diff.added.length > 0) {
|
|
2439
|
+
const [testFiles, srcFiles] = partition(diff.added, isTestOrFixture);
|
|
2325
2440
|
lines.push(t("diff.added", { count: diff.added.length }));
|
|
2326
|
-
for (const f of
|
|
2441
|
+
for (const f of srcFiles) {
|
|
2327
2442
|
lines.push(` + ${f}`);
|
|
2328
2443
|
}
|
|
2444
|
+
if (testFiles.length > 0) {
|
|
2445
|
+
lines.push(t("diff.testSummary", { count: testFiles.length }));
|
|
2446
|
+
}
|
|
2329
2447
|
lines.push("");
|
|
2330
2448
|
}
|
|
2331
2449
|
if (diff.removed.length > 0) {
|
|
2450
|
+
const [testFiles, srcFiles] = partition(diff.removed, isTestOrFixture);
|
|
2332
2451
|
lines.push(t("diff.removed", { count: diff.removed.length }));
|
|
2333
|
-
for (const f of
|
|
2452
|
+
for (const f of srcFiles) {
|
|
2334
2453
|
lines.push(` - ${f}`);
|
|
2335
2454
|
}
|
|
2455
|
+
if (testFiles.length > 0) {
|
|
2456
|
+
lines.push(t("diff.testSummary", { count: testFiles.length }));
|
|
2457
|
+
}
|
|
2336
2458
|
lines.push("");
|
|
2337
2459
|
}
|
|
2338
2460
|
if (diff.modified.length > 0) {
|
|
2461
|
+
const [testFiles, srcFiles] = partition(diff.modified, isTestOrFixture);
|
|
2339
2462
|
lines.push(t("diff.modified", { count: diff.modified.length }));
|
|
2340
|
-
for (const f of
|
|
2463
|
+
for (const f of srcFiles) {
|
|
2341
2464
|
lines.push(` ~ ${f}`);
|
|
2342
2465
|
}
|
|
2466
|
+
if (testFiles.length > 0) {
|
|
2467
|
+
lines.push(t("diff.testSummary", { count: testFiles.length }));
|
|
2468
|
+
}
|
|
2343
2469
|
lines.push("");
|
|
2344
2470
|
}
|
|
2345
2471
|
if (diff.affectedDependents.length > 0) {
|
|
2472
|
+
const [testEntries, srcEntries] = partition(
|
|
2473
|
+
diff.affectedDependents,
|
|
2474
|
+
(a) => isTestOrFixture(a.file) || isTestOrFixture(a.dependsOn)
|
|
2475
|
+
);
|
|
2346
2476
|
lines.push(t("diff.affected", { count: diff.affectedDependents.length }));
|
|
2347
|
-
for (const a of
|
|
2477
|
+
for (const a of srcEntries) {
|
|
2348
2478
|
lines.push(` ! ${a.file}`);
|
|
2349
2479
|
lines.push(` ${a.reason}`);
|
|
2350
2480
|
}
|
|
2481
|
+
if (testEntries.length > 0) {
|
|
2482
|
+
lines.push(t("diff.testAffectedSummary", { count: testEntries.length }));
|
|
2483
|
+
}
|
|
2351
2484
|
lines.push("");
|
|
2352
2485
|
}
|
|
2353
2486
|
return lines.join("\n");
|
|
@@ -2359,77 +2492,6 @@ function arraysEqual(a, b) {
|
|
|
2359
2492
|
}
|
|
2360
2493
|
return true;
|
|
2361
2494
|
}
|
|
2362
|
-
|
|
2363
|
-
// src/storage/layers.ts
|
|
2364
|
-
import { readFile as readFile3, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
2365
|
-
import { join as join6 } from "path";
|
|
2366
|
-
import { z as z2 } from "zod";
|
|
2367
|
-
var ARCHTRACKER_DIR2 = ".archtracker";
|
|
2368
|
-
var LAYERS_FILE = "layers.json";
|
|
2369
|
-
var LayerDefinitionSchema = z2.object({
|
|
2370
|
-
name: z2.string().min(1).regex(
|
|
2371
|
-
/^[a-zA-Z0-9_-]+$/,
|
|
2372
|
-
"Layer name must be alphanumeric (hyphens/underscores allowed)"
|
|
2373
|
-
),
|
|
2374
|
-
targetDir: z2.string().min(1),
|
|
2375
|
-
language: z2.enum(LANGUAGE_IDS).optional(),
|
|
2376
|
-
exclude: z2.array(z2.string()).optional(),
|
|
2377
|
-
color: z2.string().optional(),
|
|
2378
|
-
description: z2.string().optional()
|
|
2379
|
-
});
|
|
2380
|
-
var CrossLayerConnectionSchema = z2.object({
|
|
2381
|
-
fromLayer: z2.string(),
|
|
2382
|
-
fromFile: z2.string(),
|
|
2383
|
-
toLayer: z2.string(),
|
|
2384
|
-
toFile: z2.string(),
|
|
2385
|
-
type: z2.enum(["api-call", "event", "data-flow", "manual"]),
|
|
2386
|
-
label: z2.string().optional()
|
|
2387
|
-
});
|
|
2388
|
-
var LayerConfigSchema = z2.object({
|
|
2389
|
-
version: z2.literal("1.0"),
|
|
2390
|
-
layers: z2.array(LayerDefinitionSchema).min(1).refine(
|
|
2391
|
-
(layers) => {
|
|
2392
|
-
const names = layers.map((l) => l.name);
|
|
2393
|
-
return new Set(names).size === names.length;
|
|
2394
|
-
},
|
|
2395
|
-
{ message: "Layer names must be unique" }
|
|
2396
|
-
),
|
|
2397
|
-
connections: z2.array(CrossLayerConnectionSchema).optional()
|
|
2398
|
-
});
|
|
2399
|
-
async function loadLayerConfig(projectRoot) {
|
|
2400
|
-
const filePath = join6(projectRoot, ARCHTRACKER_DIR2, LAYERS_FILE);
|
|
2401
|
-
let raw;
|
|
2402
|
-
try {
|
|
2403
|
-
raw = await readFile3(filePath, "utf-8");
|
|
2404
|
-
} catch (error) {
|
|
2405
|
-
if (isNodeError2(error) && error.code === "ENOENT") {
|
|
2406
|
-
return null;
|
|
2407
|
-
}
|
|
2408
|
-
throw new Error(`Failed to read ${filePath}`);
|
|
2409
|
-
}
|
|
2410
|
-
let parsed;
|
|
2411
|
-
try {
|
|
2412
|
-
parsed = JSON.parse(raw);
|
|
2413
|
-
} catch {
|
|
2414
|
-
throw new Error(`Invalid JSON in ${filePath}`);
|
|
2415
|
-
}
|
|
2416
|
-
const result = LayerConfigSchema.safeParse(parsed);
|
|
2417
|
-
if (!result.success) {
|
|
2418
|
-
const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).slice(0, 5).join("\n");
|
|
2419
|
-
throw new Error(`layers.json validation failed:
|
|
2420
|
-
${issues}`);
|
|
2421
|
-
}
|
|
2422
|
-
return result.data;
|
|
2423
|
-
}
|
|
2424
|
-
async function saveLayerConfig(projectRoot, config) {
|
|
2425
|
-
const dirPath = join6(projectRoot, ARCHTRACKER_DIR2);
|
|
2426
|
-
const filePath = join6(dirPath, LAYERS_FILE);
|
|
2427
|
-
await mkdir2(dirPath, { recursive: true });
|
|
2428
|
-
await writeFile2(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
2429
|
-
}
|
|
2430
|
-
function isNodeError2(error) {
|
|
2431
|
-
return error instanceof Error && "code" in error;
|
|
2432
|
-
}
|
|
2433
2495
|
export {
|
|
2434
2496
|
AnalyzerError,
|
|
2435
2497
|
SCHEMA_VERSION,
|