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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/index.ts","../../src/analyzer/analyze.ts","../../src/i18n/index.ts","../../src/analyzer/report.ts","../../src/storage/snapshot.ts","../../src/types/schema.ts","../../src/storage/diff.ts","../../src/web/server.ts","../../src/web/template.ts"],"sourcesContent":["import { Command } from \"commander\";\nimport { watch } from \"node:fs\";\nimport { writeFile, mkdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { analyzeProject, AnalyzerError, formatAnalysisReport } from \"../analyzer/index.js\";\nimport {\n saveSnapshot,\n loadSnapshot,\n computeDiff,\n formatDiffReport,\n StorageError,\n} from \"../storage/index.js\";\nimport { startViewer } from \"../web/server.js\";\nimport { t, setLocale } from \"../i18n/index.js\";\nimport type { Locale } from \"../i18n/index.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"archtracker\")\n .description(\n \"Architecture & Dependency Tracker — Prevent missed architecture changes in AI-driven development\",\n )\n .version(\"0.1.0\")\n .option(\"--lang <locale>\", \"Language (en/ja, auto-detected from LANG env)\")\n .hook(\"preAction\", (thisCommand) => {\n const lang = thisCommand.opts().lang;\n if (lang === \"en\" || lang === \"ja\") {\n setLocale(lang as Locale);\n }\n });\n\n// ─── archtracker init ───────────────────────────────────────────\n\nprogram\n .command(\"init\")\n .description(\"Generate initial snapshot and save to .archtracker/\")\n .option(\"-t, --target <dir>\", \"Target directory\", \"src\")\n .option(\"-r, --root <dir>\", \"Project root\", \".\")\n .option(\n \"-e, --exclude <patterns...>\",\n \"Exclude patterns (regex)\",\n )\n .action(async (opts) => {\n try {\n console.log(t(\"cli.analyzing\"));\n const graph = await analyzeProject(opts.target, {\n exclude: opts.exclude,\n });\n\n const snapshot = await saveSnapshot(opts.root, graph);\n\n console.log(t(\"cli.snapshotSaved\"));\n console.log(t(\"cli.timestamp\", { ts: snapshot.timestamp }));\n console.log(t(\"cli.fileCount\", { count: graph.totalFiles }));\n console.log(t(\"cli.edgeCount\", { count: graph.totalEdges }));\n\n if (graph.circularDependencies.length > 0) {\n console.log(\n t(\"cli.circularCount\", { count: graph.circularDependencies.length }),\n );\n }\n\n // Show top 5 key components\n const top = Object.values(graph.files)\n .sort((a, b) => b.dependents.length - a.dependents.length)\n .slice(0, 5);\n\n if (top.length > 0 && top[0].dependents.length > 0) {\n console.log(t(\"cli.keyComponents\"));\n for (const f of top) {\n if (f.dependents.length === 0) break;\n console.log(` ${t(\"cli.dependedBy\", { path: f.path, count: f.dependents.length })}`);\n }\n }\n } catch (error) {\n handleError(error);\n }\n });\n\n// ─── archtracker analyze ─────────────────────────────────────\n\nprogram\n .command(\"analyze\")\n .description(\n \"Comprehensive architecture analysis for existing projects\",\n )\n .option(\"-t, --target <dir>\", \"Target directory\", \"src\")\n .option(\"-r, --root <dir>\", \"Project root\", \".\")\n .option(\n \"-e, --exclude <patterns...>\",\n \"Exclude patterns (regex)\",\n )\n .option(\"-n, --top <number>\", \"Number of top components to show\", \"10\")\n .option(\"--save\", \"Also save a snapshot after analysis\")\n .action(async (opts) => {\n try {\n console.log(t(\"cli.analyzing\"));\n const graph = await analyzeProject(opts.target, {\n exclude: opts.exclude,\n });\n\n const report = formatAnalysisReport(graph, { topN: parseInt(opts.top, 10) });\n console.log(report);\n\n if (opts.save) {\n await saveSnapshot(opts.root, graph);\n console.log(t(\"analyze.snapshotSaved\"));\n }\n } catch (error) {\n handleError(error);\n }\n });\n\n// ─── archtracker check ─────────────────────────────────────────\n\nprogram\n .command(\"check\")\n .description(\n \"Compare snapshot with current code and report change impacts\",\n )\n .option(\"-t, --target <dir>\", \"Target directory\", \"src\")\n .option(\"-r, --root <dir>\", \"Project root\", \".\")\n .option(\"--ci\", \"CI mode: exit code 1 if affected files exist\")\n .action(async (opts) => {\n try {\n const existingSnapshot = await loadSnapshot(opts.root);\n\n if (!existingSnapshot) {\n console.log(t(\"cli.noSnapshot\"));\n process.exit(1);\n }\n\n console.log(t(\"cli.analyzing\"));\n const currentGraph = await analyzeProject(opts.target);\n const diff = computeDiff(existingSnapshot.graph, currentGraph);\n const report = formatDiffReport(diff);\n\n console.log(report);\n\n // CI mode: exit with error if there are affected dependents\n if (opts.ci && diff.affectedDependents.length > 0) {\n console.log(t(\"cli.ciFailed\", { count: diff.affectedDependents.length }));\n process.exit(1);\n }\n } catch (error) {\n handleError(error);\n }\n });\n\n// ─── archtracker context ────────────────────────────────────────\n\nprogram\n .command(\"context\")\n .description(\n \"Display current architecture context (for AI session initialization)\",\n )\n .option(\"-t, --target <dir>\", \"Target directory\", \"src\")\n .option(\"-r, --root <dir>\", \"Project root\", \".\")\n .option(\"--json\", \"Output in JSON format\")\n .action(async (opts) => {\n try {\n let snapshot = await loadSnapshot(opts.root);\n\n if (!snapshot) {\n console.log(t(\"cli.autoGenerating\"));\n const graph = await analyzeProject(opts.target);\n snapshot = await saveSnapshot(opts.root, graph);\n }\n\n const graph = snapshot.graph;\n\n if (opts.json) {\n const context = {\n validPaths: Object.keys(graph.files).sort(),\n snapshotTimestamp: snapshot.timestamp,\n totalFiles: graph.totalFiles,\n totalEdges: graph.totalEdges,\n circularDependencies: graph.circularDependencies.length,\n keyComponents: Object.values(graph.files)\n .sort((a, b) => b.dependents.length - a.dependents.length)\n .slice(0, 20)\n .map((f) => ({\n path: f.path,\n dependentCount: f.dependents.length,\n dependencyCount: f.dependencies.length,\n })),\n };\n console.log(JSON.stringify(context, null, 2));\n return;\n }\n\n console.log(t(\"cli.project\", { path: graph.rootDir }));\n console.log(t(\"cli.fileCount\", { count: graph.totalFiles }));\n console.log(t(\"cli.edgeCount\", { count: graph.totalEdges }));\n console.log(t(\"cli.circularCount\", { count: graph.circularDependencies.length }));\n console.log(t(\"cli.snapshot\", { ts: snapshot.timestamp }));\n\n console.log(t(\"cli.validPaths\"));\n for (const f of Object.keys(graph.files).sort()) {\n console.log(` ${f}`);\n }\n } catch (error) {\n handleError(error);\n }\n });\n\n// ─── archtracker serve ──────────────────────────────────────────\n\nprogram\n .command(\"serve\")\n .description(\n \"Start interactive architecture graph viewer in browser\",\n )\n .option(\"-t, --target <dir>\", \"Target directory\", \"src\")\n .option(\"-r, --root <dir>\", \"Project root\", \".\")\n .option(\"-p, --port <number>\", \"Port number\", \"3000\")\n .option(\n \"-e, --exclude <patterns...>\",\n \"Exclude patterns (regex)\",\n )\n .option(\"-w, --watch\", \"Watch for file changes and auto-reload\")\n .action(async (opts) => {\n try {\n console.log(t(\"web.starting\"));\n console.log(t(\"cli.analyzing\"));\n\n // Use snapshot if available, otherwise analyze fresh\n let graph;\n let diff = null;\n const snapshot = await loadSnapshot(opts.root);\n if (snapshot) {\n // Compute diff against current code if snapshot exists\n const currentGraph = await analyzeProject(opts.target, { exclude: opts.exclude });\n diff = computeDiff(snapshot.graph, currentGraph);\n graph = currentGraph;\n } else {\n graph = await analyzeProject(opts.target, { exclude: opts.exclude });\n }\n\n const port = parseInt(opts.port, 10);\n const viewer = startViewer(graph, { port, diff });\n\n console.log(t(\"web.listening\", { port }));\n console.log(t(\"web.stop\"));\n\n if (opts.watch) {\n console.log(t(\"web.watching\", { dir: opts.target }));\n let debounce: ReturnType<typeof setTimeout> | null = null;\n watch(opts.target, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(async () => {\n try {\n console.log(t(\"web.reloading\"));\n const newGraph = await analyzeProject(opts.target, { exclude: opts.exclude });\n viewer.close();\n startViewer(newGraph, { port });\n console.log(t(\"web.reloaded\"));\n } catch { /* ignore transient errors during file saves */ }\n }, 500);\n });\n }\n } catch (error) {\n handleError(error);\n }\n });\n\n// ─── archtracker ci-setup ────────────────────────────────────────\n\nprogram\n .command(\"ci-setup\")\n .description(\n \"Generate GitHub Actions workflow for architecture checks on PRs\",\n )\n .option(\"-t, --target <dir>\", \"Target directory\", \"src\")\n .action(async (opts) => {\n const workflow = `name: Architecture Check\n\non:\n pull_request:\n branches: [main, master]\n\njobs:\n arch-check:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '20'\n - run: npm ci\n - run: npx archtracker check --target ${opts.target} --ci\n`;\n try {\n const dir = join(\".github\", \"workflows\");\n await mkdir(dir, { recursive: true });\n const path = join(dir, \"arch-check.yml\");\n await writeFile(path, workflow, \"utf-8\");\n console.log(t(\"ci.generated\", { path }));\n } catch (error) {\n handleError(error);\n }\n });\n\n// ─── Error handling ─────────────────────────────────────────────\n\nfunction handleError(error: unknown): never {\n if (error instanceof AnalyzerError) {\n console.error(t(\"error.cli.analyzer\", { message: error.message }));\n } else if (error instanceof StorageError) {\n console.error(t(\"error.cli.storage\", { message: error.message }));\n } else if (error instanceof Error) {\n console.error(t(\"error.cli.generic\", { message: error.message }));\n } else {\n console.error(t(\"error.cli.unexpected\", { message: String(error) }));\n }\n process.exit(1);\n}\n\nprogram.parse();\n","import { resolve, relative } from \"node:path\";\nimport { cruise } from \"dependency-cruiser\";\nimport type { ICruiseResult } from \"dependency-cruiser\";\nimport type {\n DependencyGraph,\n DependencyEdge,\n FileNode,\n CircularDependency,\n} from \"../types/schema.js\";\n\n/** Options for the dependency analyzer */\nexport interface AnalyzeOptions {\n /** Regex patterns to exclude from analysis (e.g. [\"node_modules\", \"\\\\.test\\\\.ts$\"]) */\n exclude?: string[];\n /** Maximum recursion depth (0 = unlimited) */\n maxDepth?: number;\n /** Path to tsconfig.json (auto-detected if omitted) */\n tsConfigPath?: string;\n /** Include type-only imports (import type {...}) */\n includeTypeOnly?: boolean;\n}\n\nconst DEFAULT_EXCLUDE = [\n \"node_modules\",\n \"\\\\.d\\\\.ts$\",\n \"dist\",\n \"build\",\n \"coverage\",\n \"\\\\.archtracker\",\n];\n\n/**\n * Analyze project dependencies using dependency-cruiser.\n *\n * dependency-cruiser records module paths relative to CWD.\n * We resolve rootDir to an absolute path, then use it as baseDir\n * so that output paths are relative to rootDir.\n *\n * @param rootDir - Directory to analyze (e.g. \"src\" or absolute path)\n * @param options - Analysis configuration\n * @returns DependencyGraph with files, edges, and circular dependency warnings\n */\nexport async function analyzeProject(\n rootDir: string,\n options: AnalyzeOptions = {},\n): Promise<DependencyGraph> {\n const {\n exclude = [],\n maxDepth = 0,\n tsConfigPath,\n includeTypeOnly = true,\n } = options;\n\n const absRootDir = resolve(rootDir);\n const allExclude = [...DEFAULT_EXCLUDE, ...exclude];\n const excludePattern = allExclude.join(\"|\");\n\n // Cruise \".\" relative to absRootDir as baseDir,\n // so all output paths are relative to the target directory\n const cruiseOptions: Record<string, unknown> = {\n baseDir: absRootDir,\n exclude: { path: excludePattern },\n doNotFollow: { path: \"node_modules\" },\n maxDepth,\n tsPreCompilationDeps: includeTypeOnly ? true : false,\n combinedDependencies: false,\n };\n\n if (tsConfigPath) {\n cruiseOptions.tsConfig = { fileName: tsConfigPath };\n }\n\n let result;\n try {\n result = await cruise([\".\"], cruiseOptions);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new AnalyzerError(\n `dependency-cruiser の実行に失敗しました: ${message}`,\n { cause: error },\n );\n }\n\n if (result.exitCode !== 0 && !result.output) {\n throw new AnalyzerError(\n `解析がエラーコード ${result.exitCode} で終了しました`,\n );\n }\n\n const cruiseResult = result.output as ICruiseResult;\n return buildGraph(absRootDir, cruiseResult);\n}\n\n/** Convert dependency-cruiser output to our DependencyGraph format */\nfunction buildGraph(\n rootDir: string,\n cruiseResult: ICruiseResult,\n): DependencyGraph {\n const files: Record<string, FileNode> = {};\n const edges: DependencyEdge[] = [];\n const circularSet = new Set<string>();\n const circularDependencies: CircularDependency[] = [];\n\n // First pass: register only local project files\n // Skip core modules (fs, path), external packages (npm), and unresolvable\n for (const mod of cruiseResult.modules) {\n if (isExternalModule(mod)) continue;\n\n files[mod.source] = {\n path: mod.source,\n exists: !mod.couldNotResolve,\n dependencies: [],\n dependents: [],\n };\n }\n\n // Second pass: build edges and dependency lists\n for (const mod of cruiseResult.modules) {\n for (const dep of mod.dependencies) {\n // Skip unresolvable and core modules\n if (dep.couldNotResolve || dep.coreModule) continue;\n // Skip edges to/from external packages\n if (!files[mod.source] || isExternalDep(dep)) continue;\n\n const edgeType = dep.typeOnly\n ? (\"type-only\" as const)\n : dep.dynamic\n ? (\"dynamic\" as const)\n : (\"static\" as const);\n\n edges.push({\n source: mod.source,\n target: dep.resolved,\n type: edgeType,\n });\n\n // Update dependency/dependent lists\n if (files[mod.source]) {\n files[mod.source].dependencies.push(dep.resolved);\n }\n if (files[dep.resolved]) {\n files[dep.resolved].dependents.push(mod.source);\n }\n\n // Detect circular dependencies\n if (dep.circular && dep.cycle) {\n const cyclePath = dep.cycle.map((c) => c.name);\n const cycleKey = [...cyclePath].sort().join(\"→\");\n if (!circularSet.has(cycleKey)) {\n circularSet.add(cycleKey);\n circularDependencies.push({ cycle: cyclePath });\n }\n }\n }\n }\n\n return {\n rootDir,\n files,\n edges,\n circularDependencies,\n totalFiles: Object.keys(files).length,\n totalEdges: edges.length,\n };\n}\n\n/**\n * Check if a module is external (not a local project file).\n * Uses both dependency-cruiser metadata and path heuristics.\n */\nfunction isExternalModule(mod: { source: string; coreModule?: boolean; dependencyTypes?: string[] }): boolean {\n if (mod.coreModule) return true;\n const depTypes = mod.dependencyTypes ?? [];\n if (depTypes.some((t) => t.startsWith(\"npm\") || t === \"core\")) return true;\n // Path-based fallback: scoped packages (@org/pkg) or bare specifiers without extension\n return isExternalPath(mod.source);\n}\n\n/**\n * Check if a dependency edge points to an external target.\n */\nfunction isExternalDep(dep: { resolved: string; coreModule: boolean; dependencyTypes: string[] }): boolean {\n if (dep.coreModule) return true;\n if (dep.dependencyTypes.some((t) => t.startsWith(\"npm\") || t === \"core\")) return true;\n return isExternalPath(dep.resolved);\n}\n\n/**\n * Path heuristic: detect external modules by path pattern.\n * Local project files start with ./ or are relative paths with file extensions.\n * External: @scope/pkg, bare-name/sub, single-word (fs, path, os).\n */\nfunction isExternalPath(source: string): boolean {\n // Scoped packages: @modelcontextprotocol/sdk/...\n if (source.startsWith(\"@\")) return true;\n // Core node modules: single word without slash (fs, path, os, etc.)\n if (!source.includes(\"/\") && !source.includes(\"\\\\\") && !source.includes(\".\")) return true;\n // node: protocol\n if (source.startsWith(\"node:\")) return true;\n return false;\n}\n\n/** Custom error class for analyzer failures */\nexport class AnalyzerError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"AnalyzerError\";\n }\n}\n","export type Locale = \"en\" | \"ja\";\n\nlet currentLocale: Locale = detectLocale();\n\n/** Get the current locale */\nexport function getLocale(): Locale {\n return currentLocale;\n}\n\n/** Set the locale explicitly */\nexport function setLocale(locale: Locale): void {\n currentLocale = locale;\n}\n\n/** Auto-detect locale from LANG/LC_ALL environment variable */\nfunction detectLocale(): Locale {\n const env = process.env.LC_ALL || process.env.LANG || \"\";\n if (env.startsWith(\"ja\")) return \"ja\";\n return \"en\";\n}\n\n/** Get a translated message by key */\nexport function t(key: string, vars?: Record<string, string | number>): string {\n const messages = currentLocale === \"ja\" ? ja : en;\n let msg = (messages as Record<string, string>)[key] ?? (en as Record<string, string>)[key] ?? key;\n\n if (vars) {\n for (const [k, v] of Object.entries(vars)) {\n msg = msg.replaceAll(`{${k}}`, String(v));\n }\n }\n return msg;\n}\n\n// ─── Message catalogs ───────────────────────────────────────────\n\nconst en = {\n // Analyzer\n \"analyzer.failed\": \"dependency-cruiser failed: {message}\",\n \"analyzer.exitCode\": \"Analysis finished with error code {code}\",\n\n // Storage\n \"storage.parseFailed\": \"Failed to parse snapshot.json. File may be corrupted: {path}\",\n \"storage.readFailed\": \"Failed to read snapshot.json: {path}\",\n \"storage.invalidSchema\": \"snapshot.json schema is invalid. Please regenerate with `archtracker init`:\\n{issues}\",\n \"storage.versionMismatch\": \"snapshot.json version ({version}) is incompatible with current schema ({expected}). Regenerate with `archtracker init`.\",\n\n // Path guard\n \"pathGuard.traversal\": \"Path points outside project root: \\\"{input}\\\" → \\\"{resolved}\\\" (allowed: \\\"{boundary}\\\")\",\n\n // Diff report\n \"diff.title\": \"# Architecture Change Report\\n\",\n \"diff.noChanges\": \"No changes — snapshot matches current code.\\n\",\n \"diff.added\": \"## Added Files ({count})\",\n \"diff.removed\": \"## Removed Files ({count})\",\n \"diff.modified\": \"## Modified Dependencies ({count})\",\n \"diff.affected\": \"## Files Requiring Review ({count})\",\n \"diff.reasonRemoved\": \"Dependency \\\"{file}\\\" was removed\",\n \"diff.reasonModified\": \"Dependency \\\"{file}\\\" had its dependencies changed\",\n \"diff.reasonAdded\": \"New dependency \\\"{file}\\\" was added\",\n\n // Search\n \"search.pathMatch\": \"Path matches \\\"{pattern}\\\"\",\n \"search.affected\": \"May be affected by changes to \\\"{file}\\\" (via: {via})\",\n \"search.critical\": \"{count} files depend on this component\",\n \"search.orphan\": \"Orphan file (no dependencies, no dependents)\",\n \"search.noResults\": \"No results: \\\"{query}\\\" (mode: {mode})\",\n \"search.results\": \"Results: {count} (mode: {mode})\",\n\n // CLI\n \"cli.analyzing\": \"Analyzing...\",\n \"cli.snapshotSaved\": \"Snapshot saved\",\n \"cli.timestamp\": \" Timestamp: {ts}\",\n \"cli.fileCount\": \" Files: {count}\",\n \"cli.edgeCount\": \" Edges: {count}\",\n \"cli.circularCount\": \" Circular deps: {count}\",\n \"cli.keyComponents\": \"\\nKey components:\",\n \"cli.dependedBy\": \"{path} ({count} dependents)\",\n \"cli.noSnapshot\": \"No snapshot found. Run `archtracker init` first.\",\n \"cli.ciFailed\": \"\\nCI check failed: {count} file(s) require review\",\n \"cli.autoGenerating\": \"No snapshot found, auto-generating...\",\n \"cli.project\": \"Project: {path}\",\n \"cli.validPaths\": \"\\nValid file paths:\",\n \"cli.snapshot\": \"Snapshot: {ts}\",\n\n // MCP\n \"mcp.analyzeComplete\": \"Analysis complete: {files} files, {edges} edges\",\n \"mcp.circularFound\": \"Circular deps: {count} found\",\n \"mcp.circularNone\": \"Circular deps: none\",\n \"mcp.snapshotSaved\": \"Snapshot saved\",\n \"mcp.autoInit\": \"No snapshot existed. Initial snapshot auto-generated.\",\n \"mcp.nextCheckEnabled\": \"Diff checking will be active from the next run.\",\n \"mcp.queryRequired\": \"\\\"{mode}\\\" mode requires the query parameter\",\n\n // Analyze report\n \"analyze.title\": \"# Architecture Analysis Report\\n\",\n \"analyze.overview\": \"## Overview\",\n \"analyze.totalFiles\": \" Total files: {count}\",\n \"analyze.totalEdges\": \" Total edges: {count}\",\n \"analyze.totalCircular\": \" Circular dependencies: {count}\",\n \"analyze.criticalTitle\": \"\\n## Critical Components (Top {count})\",\n \"analyze.criticalItem\": \" {path} ({count} dependents)\",\n \"analyze.circularTitle\": \"\\n## Circular Dependencies ({count})\",\n \"analyze.circularItem\": \" {files}\",\n \"analyze.orphanTitle\": \"\\n## Orphan Files ({count})\",\n \"analyze.couplingTitle\": \"\\n## High Coupling (Top {count} by import count)\",\n \"analyze.couplingItem\": \" {path} ({count} imports)\",\n \"analyze.layerTitle\": \"\\n## Directory Breakdown\",\n \"analyze.layerItem\": \" {dir}/ — {count} files\",\n \"analyze.noIssues\": \"\\nNo architectural issues detected.\",\n \"analyze.snapshotSaved\": \"\\nSnapshot saved alongside analysis.\",\n\n // CI\n \"ci.generated\": \"GitHub Actions workflow generated: {path}\",\n\n // Web viewer\n \"web.starting\": \"Starting architecture viewer...\",\n \"web.listening\": \"Architecture graph available at: http://localhost:{port}\",\n \"web.stop\": \"Press Ctrl+C to stop\",\n \"web.watching\": \"Watching {dir}/ for changes...\",\n \"web.reloading\": \"File change detected, reloading...\",\n \"web.reloaded\": \"Graph reloaded\",\n\n // Errors\n \"error.analyzer\": \"[Analysis Error] {message}\",\n \"error.storage\": \"[Storage Error] {message}\",\n \"error.pathTraversal\": \"[Security Error] {message}\",\n \"error.generic\": \"[Error] {message}\",\n \"error.unexpected\": \"[Error] Unexpected error: {message}\",\n \"error.cli.analyzer\": \"Analysis error: {message}\",\n \"error.cli.storage\": \"Storage error: {message}\",\n \"error.cli.generic\": \"Error: {message}\",\n \"error.cli.unexpected\": \"Unexpected error: {message}\",\n} as const;\n\nconst ja = {\n // Analyzer\n \"analyzer.failed\": \"dependency-cruiser の実行に失敗しました: {message}\",\n \"analyzer.exitCode\": \"解析がエラーコード {code} で終了しました\",\n\n // Storage\n \"storage.parseFailed\": \"snapshot.json のパースに失敗しました。ファイルが破損している可能性があります: {path}\",\n \"storage.readFailed\": \"snapshot.json の読み取りに失敗しました: {path}\",\n \"storage.invalidSchema\": \"snapshot.json のスキーマが不正です。archtracker init で再生成してください:\\n{issues}\",\n \"storage.versionMismatch\": \"snapshot.json のバージョン ({version}) が現在のスキーマ ({expected}) と互換性がありません。archtracker init で再生成してください。\",\n\n // Path guard\n \"pathGuard.traversal\": \"パスがプロジェクトルートの外部を指しています: \\\"{input}\\\" → \\\"{resolved}\\\" (許可範囲: \\\"{boundary}\\\")\",\n\n // Diff report\n \"diff.title\": \"# アーキテクチャ変更レポート\\n\",\n \"diff.noChanges\": \"変更なし — スナップショットと現在のコードは一致しています。\\n\",\n \"diff.added\": \"## 追加されたファイル ({count}件)\",\n \"diff.removed\": \"## 削除されたファイル ({count}件)\",\n \"diff.modified\": \"## 依存関係が変更されたファイル ({count}件)\",\n \"diff.affected\": \"## 確認が必要なファイル ({count}件)\",\n \"diff.reasonRemoved\": \"依存先 \\\"{file}\\\" が削除されました\",\n \"diff.reasonModified\": \"依存先 \\\"{file}\\\" の依存関係が変更されました\",\n \"diff.reasonAdded\": \"新しい依存先 \\\"{file}\\\" が追加されました\",\n\n // Search\n \"search.pathMatch\": \"パスが \\\"{pattern}\\\" にマッチ\",\n \"search.affected\": \"\\\"{file}\\\" の変更により影響を受ける可能性(経由: {via})\",\n \"search.critical\": \"{count}件のファイルが依存する重要コンポーネント\",\n \"search.orphan\": \"孤立ファイル(依存なし・被依存なし)\",\n \"search.noResults\": \"検索結果なし: \\\"{query}\\\" (モード: {mode})\",\n \"search.results\": \"検索結果: {count}件 (モード: {mode})\",\n\n // CLI\n \"cli.analyzing\": \"解析中...\",\n \"cli.snapshotSaved\": \"スナップショットを保存しました\",\n \"cli.timestamp\": \" タイムスタンプ: {ts}\",\n \"cli.fileCount\": \" ファイル数: {count}\",\n \"cli.edgeCount\": \" エッジ数: {count}\",\n \"cli.circularCount\": \" 循環参照: {count}件\",\n \"cli.keyComponents\": \"\\n主要コンポーネント:\",\n \"cli.dependedBy\": \"{path} ({count}件が依存)\",\n \"cli.noSnapshot\": \"スナップショットが見つかりません。`archtracker init` を先に実行してください。\",\n \"cli.ciFailed\": \"\\nCI チェック失敗: {count}件の要確認ファイルがあります\",\n \"cli.autoGenerating\": \"スナップショットが無いため自動生成します...\",\n \"cli.project\": \"プロジェクト: {path}\",\n \"cli.validPaths\": \"\\n有効なファイルパス:\",\n \"cli.snapshot\": \"スナップショット: {ts}\",\n\n // MCP\n \"mcp.analyzeComplete\": \"解析完了: {files}ファイル, {edges}エッジ\",\n \"mcp.circularFound\": \"循環参照: {count}件検出\",\n \"mcp.circularNone\": \"循環参照: なし\",\n \"mcp.snapshotSaved\": \"スナップショットを保存しました\",\n \"mcp.autoInit\": \"スナップショットが存在しなかったため、初期スナップショットを自動生成しました。\",\n \"mcp.nextCheckEnabled\": \"次回の実行時から差分チェックが有効になります。\",\n \"mcp.queryRequired\": \"\\\"{mode}\\\" モードでは query パラメータが必須です\",\n\n // Analyze report\n \"analyze.title\": \"# アーキテクチャ分析レポート\\n\",\n \"analyze.overview\": \"## 概要\",\n \"analyze.totalFiles\": \" 総ファイル数: {count}\",\n \"analyze.totalEdges\": \" 総エッジ数: {count}\",\n \"analyze.totalCircular\": \" 循環参照: {count}件\",\n \"analyze.criticalTitle\": \"\\n## 重要コンポーネント (上位{count}件)\",\n \"analyze.criticalItem\": \" {path} ({count}件が依存)\",\n \"analyze.circularTitle\": \"\\n## 循環参照 ({count}件)\",\n \"analyze.circularItem\": \" {files}\",\n \"analyze.orphanTitle\": \"\\n## 孤立ファイル ({count}件)\",\n \"analyze.couplingTitle\": \"\\n## 高結合ファイル (import数 上位{count}件)\",\n \"analyze.couplingItem\": \" {path} ({count}件をimport)\",\n \"analyze.layerTitle\": \"\\n## ディレクトリ構成\",\n \"analyze.layerItem\": \" {dir}/ — {count}ファイル\",\n \"analyze.noIssues\": \"\\nアーキテクチャ上の問題は検出されませんでした。\",\n \"analyze.snapshotSaved\": \"\\n分析と同時にスナップショットを保存しました。\",\n\n // CI\n \"ci.generated\": \"GitHub Actions ワークフローを生成しました: {path}\",\n\n // Web viewer\n \"web.starting\": \"アーキテクチャビューアーを起動中...\",\n \"web.listening\": \"アーキテクチャグラフ: http://localhost:{port}\",\n \"web.stop\": \"Ctrl+C で停止\",\n \"web.watching\": \"{dir}/ を監視中...\",\n \"web.reloading\": \"ファイル変更を検出、リロード中...\",\n \"web.reloaded\": \"グラフを更新しました\",\n\n // Errors\n \"error.analyzer\": \"[解析エラー] {message}\",\n \"error.storage\": \"[ストレージエラー] {message}\",\n \"error.pathTraversal\": \"[セキュリティエラー] {message}\",\n \"error.generic\": \"[エラー] {message}\",\n \"error.unexpected\": \"[エラー] 予期しないエラーが発生しました: {message}\",\n \"error.cli.analyzer\": \"解析エラー: {message}\",\n \"error.cli.storage\": \"ストレージエラー: {message}\",\n \"error.cli.generic\": \"エラー: {message}\",\n \"error.cli.unexpected\": \"予期しないエラー: {message}\",\n} as const;\n","import type { DependencyGraph } from \"../types/schema.js\";\nimport { t } from \"../i18n/index.js\";\n\n/**\n * Generate a comprehensive architecture analysis report.\n *\n * Designed for onboarding into existing projects — provides\n * a full overview of dependencies, critical components, circular\n * references, orphan files, coupling hotspots, and directory breakdown.\n */\nexport function formatAnalysisReport(\n graph: DependencyGraph,\n options: { topN?: number } = {},\n): string {\n const topN = options.topN ?? 10;\n const lines: string[] = [];\n const files = Object.values(graph.files);\n\n // ─── Overview ────────────────────────────────────────────\n lines.push(t(\"analyze.title\"));\n lines.push(t(\"analyze.overview\"));\n lines.push(t(\"analyze.totalFiles\", { count: graph.totalFiles }));\n lines.push(t(\"analyze.totalEdges\", { count: graph.totalEdges }));\n lines.push(t(\"analyze.totalCircular\", { count: graph.circularDependencies.length }));\n\n // ─── Critical Components ─────────────────────────────────\n const critical = files\n .filter((f) => f.dependents.length > 0)\n .sort((a, b) => b.dependents.length - a.dependents.length)\n .slice(0, topN);\n\n if (critical.length > 0) {\n lines.push(t(\"analyze.criticalTitle\", { count: critical.length }));\n for (const f of critical) {\n lines.push(t(\"analyze.criticalItem\", { path: f.path, count: f.dependents.length }));\n }\n }\n\n // ─── Circular Dependencies ──────────────────────────────\n if (graph.circularDependencies.length > 0) {\n lines.push(t(\"analyze.circularTitle\", { count: graph.circularDependencies.length }));\n for (const c of graph.circularDependencies) {\n lines.push(t(\"analyze.circularItem\", { files: c.cycle.join(\" → \") }));\n }\n }\n\n // ─── High Coupling (most imports) ───────────────────────\n const highCoupling = files\n .filter((f) => f.dependencies.length > 0)\n .sort((a, b) => b.dependencies.length - a.dependencies.length)\n .slice(0, topN);\n\n if (highCoupling.length > 0) {\n lines.push(t(\"analyze.couplingTitle\", { count: highCoupling.length }));\n for (const f of highCoupling) {\n lines.push(t(\"analyze.couplingItem\", { path: f.path, count: f.dependencies.length }));\n }\n }\n\n // ─── Orphan Files ───────────────────────────────────────\n const orphans = files.filter(\n (f) => f.dependents.length === 0 && f.dependencies.length === 0,\n );\n\n if (orphans.length > 0) {\n lines.push(t(\"analyze.orphanTitle\", { count: orphans.length }));\n for (const f of orphans) {\n lines.push(` ${f.path}`);\n }\n }\n\n // ─── Directory Breakdown ────────────────────────────────\n const dirCounts = new Map<string, number>();\n for (const f of files) {\n const dir = f.path.includes(\"/\") ? f.path.substring(0, f.path.lastIndexOf(\"/\")) : \".\";\n dirCounts.set(dir, (dirCounts.get(dir) ?? 0) + 1);\n }\n\n if (dirCounts.size > 1) {\n lines.push(t(\"analyze.layerTitle\"));\n const sorted = [...dirCounts.entries()].sort((a, b) => b[1] - a[1]);\n for (const [dir, count] of sorted) {\n lines.push(t(\"analyze.layerItem\", { dir, count }));\n }\n }\n\n // ─── Summary ────────────────────────────────────────────\n if (graph.circularDependencies.length === 0 && orphans.length === 0) {\n lines.push(t(\"analyze.noIssues\"));\n }\n\n return lines.join(\"\\n\");\n}\n","import { mkdir, writeFile, readFile, access } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\nimport type { ArchSnapshot } from \"../types/schema.js\";\nimport { SCHEMA_VERSION } from \"../types/schema.js\";\nimport type { DependencyGraph } from \"../types/schema.js\";\nimport { t } from \"../i18n/index.js\";\n\nconst ARCHTRACKER_DIR = \".archtracker\";\nconst SNAPSHOT_FILE = \"snapshot.json\";\n\n/** Zod schema for runtime validation of loaded snapshots */\nconst FileNodeSchema = z.object({\n path: z.string(),\n exists: z.boolean(),\n dependencies: z.array(z.string()),\n dependents: z.array(z.string()),\n});\n\nconst DependencyGraphSchema = z.object({\n rootDir: z.string(),\n files: z.record(z.string(), FileNodeSchema),\n edges: z.array(z.object({\n source: z.string(),\n target: z.string(),\n type: z.enum([\"static\", \"dynamic\", \"type-only\"]),\n })),\n circularDependencies: z.array(z.object({ cycle: z.array(z.string()) })),\n totalFiles: z.number(),\n totalEdges: z.number(),\n});\n\nconst SnapshotSchema = z.object({\n version: z.literal(SCHEMA_VERSION),\n timestamp: z.string(),\n rootDir: z.string(),\n graph: DependencyGraphSchema,\n});\n\n/**\n * Save a dependency graph as a versioned snapshot.\n *\n * Creates .archtracker/ directory if it doesn't exist.\n * Writes snapshot.json with schema version and timestamp.\n */\nexport async function saveSnapshot(\n projectRoot: string,\n graph: DependencyGraph,\n): Promise<ArchSnapshot> {\n const dirPath = join(projectRoot, ARCHTRACKER_DIR);\n const filePath = join(dirPath, SNAPSHOT_FILE);\n\n const snapshot: ArchSnapshot = {\n version: SCHEMA_VERSION,\n timestamp: new Date().toISOString(),\n rootDir: graph.rootDir,\n graph,\n };\n\n await mkdir(dirPath, { recursive: true });\n await writeFile(filePath, JSON.stringify(snapshot, null, 2), \"utf-8\");\n\n return snapshot;\n}\n\n/**\n * Load the most recent snapshot from .archtracker/snapshot.json.\n *\n * Returns null if no snapshot exists.\n * Validates schema structure with Zod for data integrity.\n */\nexport async function loadSnapshot(\n projectRoot: string,\n): Promise<ArchSnapshot | null> {\n const filePath = join(projectRoot, ARCHTRACKER_DIR, SNAPSHOT_FILE);\n\n // Read file directly — no TOCTOU race with access() check\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf-8\");\n } catch (error: unknown) {\n if (isNodeError(error) && error.code === \"ENOENT\") {\n return null;\n }\n throw new StorageError(\n t(\"storage.readFailed\", { path: filePath }),\n { cause: error },\n );\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new StorageError(\n t(\"storage.parseFailed\", { path: filePath }),\n );\n }\n\n // Validate structure with Zod\n const result = SnapshotSchema.safeParse(parsed);\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` ${i.path.join(\".\")}: ${i.message}`)\n .slice(0, 5)\n .join(\"\\n\");\n throw new StorageError(\n t(\"storage.invalidSchema\", { issues }),\n );\n }\n\n return result.data as ArchSnapshot;\n}\n\n/** Check if .archtracker directory exists */\nexport async function hasArchtrackerDir(\n projectRoot: string,\n): Promise<boolean> {\n try {\n await access(join(projectRoot, ARCHTRACKER_DIR));\n return true;\n } catch {\n return false;\n }\n}\n\n/** Custom error class for storage failures */\nexport class StorageError extends Error {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = \"StorageError\";\n }\n}\n\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && \"code\" in error;\n}\n","/**\n * ArchTracker core schema definitions.\n * Snapshot format is versioned for backward compatibility.\n */\n\nexport const SCHEMA_VERSION = \"1.0\" as const;\n\n/** A single dependency edge: source imports target */\nexport interface DependencyEdge {\n source: string;\n target: string;\n type: \"static\" | \"dynamic\" | \"type-only\";\n}\n\n/** A file node in the architecture graph */\nexport interface FileNode {\n path: string;\n exists: boolean;\n dependencies: string[];\n dependents: string[];\n}\n\n/** A detected circular dependency */\nexport interface CircularDependency {\n cycle: string[];\n}\n\n/** The full dependency graph */\nexport interface DependencyGraph {\n rootDir: string;\n files: Record<string, FileNode>;\n edges: DependencyEdge[];\n circularDependencies: CircularDependency[];\n totalFiles: number;\n totalEdges: number;\n}\n\n/** Persisted snapshot with schema version */\nexport interface ArchSnapshot {\n version: typeof SCHEMA_VERSION;\n timestamp: string;\n rootDir: string;\n graph: DependencyGraph;\n}\n\n/** Diff result between two snapshots */\nexport interface ArchDiff {\n added: string[];\n removed: string[];\n modified: string[];\n affectedDependents: Array<{\n file: string;\n reason: string;\n dependsOn: string;\n }>;\n}\n\n/** Context summary for AI session initialization */\nexport interface ArchContext {\n validPaths: string[];\n summary: string;\n snapshotExists: boolean;\n snapshotTimestamp?: string;\n keyComponents: Array<{\n path: string;\n dependentCount: number;\n dependencyCount: number;\n }>;\n}\n","import type { ArchDiff, DependencyGraph } from \"../types/schema.js\";\nimport { t } from \"../i18n/index.js\";\n\n/**\n * Compute the diff between an old and new dependency graph.\n *\n * Identifies:\n * - Added files (new in the codebase)\n * - Removed files (deleted from the codebase)\n * - Modified files (dependencies changed)\n * - Affected dependents (files that depend on changed/removed files and may need updates)\n */\nexport function computeDiff(\n oldGraph: DependencyGraph,\n newGraph: DependencyGraph,\n): ArchDiff {\n const oldFiles = new Set(Object.keys(oldGraph.files));\n const newFiles = new Set(Object.keys(newGraph.files));\n\n // Files that exist in new but not in old\n const added = [...newFiles].filter((f) => !oldFiles.has(f));\n\n // Files that existed in old but not in new\n const removed = [...oldFiles].filter((f) => !newFiles.has(f));\n\n // Files that exist in both but have different dependency sets\n const modified: string[] = [];\n for (const file of newFiles) {\n if (!oldFiles.has(file)) continue;\n\n const oldDeps = oldGraph.files[file].dependencies.slice().sort();\n const newDeps = newGraph.files[file].dependencies.slice().sort();\n\n if (!arraysEqual(oldDeps, newDeps)) {\n modified.push(file);\n }\n }\n\n // Find all files that depend on changed/removed files\n const removedSet = new Set(removed);\n const changedFiles = new Set([...removed, ...modified]);\n const affectedDependents: ArchDiff[\"affectedDependents\"] = [];\n const seenAffected = new Set<string>();\n\n for (const changedFile of changedFiles) {\n // Look up dependents in the NEW graph (for modified files)\n // and in the OLD graph (for removed files)\n const graph = removedSet.has(changedFile) ? oldGraph : newGraph;\n const node = graph.files[changedFile];\n if (!node) continue;\n\n for (const dependent of node.dependents) {\n const key = `${dependent}→${changedFile}`;\n if (seenAffected.has(key)) continue;\n seenAffected.add(key);\n\n const reason = removedSet.has(changedFile)\n ? t(\"diff.reasonRemoved\", { file: changedFile })\n : t(\"diff.reasonModified\", { file: changedFile });\n\n affectedDependents.push({\n file: dependent,\n reason,\n dependsOn: changedFile,\n });\n }\n }\n\n // Also check for files that depend on added files\n // (new dependencies that might need verification)\n for (const addedFile of added) {\n const node = newGraph.files[addedFile];\n if (!node) continue;\n\n for (const dependent of node.dependents) {\n const key = `${dependent}→${addedFile}`;\n if (seenAffected.has(key)) continue;\n seenAffected.add(key);\n\n affectedDependents.push({\n file: dependent,\n reason: t(\"diff.reasonAdded\", { file: addedFile }),\n dependsOn: addedFile,\n });\n }\n }\n\n return { added, removed, modified, affectedDependents };\n}\n\n/** Generate a human-readable report from an ArchDiff */\nexport function formatDiffReport(diff: ArchDiff): string {\n const lines: string[] = [];\n\n lines.push(t(\"diff.title\"));\n\n if (diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0) {\n lines.push(t(\"diff.noChanges\"));\n return lines.join(\"\\n\");\n }\n\n if (diff.added.length > 0) {\n lines.push(t(\"diff.added\", { count: diff.added.length }));\n for (const f of diff.added) {\n lines.push(` + ${f}`);\n }\n lines.push(\"\");\n }\n\n if (diff.removed.length > 0) {\n lines.push(t(\"diff.removed\", { count: diff.removed.length }));\n for (const f of diff.removed) {\n lines.push(` - ${f}`);\n }\n lines.push(\"\");\n }\n\n if (diff.modified.length > 0) {\n lines.push(t(\"diff.modified\", { count: diff.modified.length }));\n for (const f of diff.modified) {\n lines.push(` ~ ${f}`);\n }\n lines.push(\"\");\n }\n\n if (diff.affectedDependents.length > 0) {\n lines.push(t(\"diff.affected\", { count: diff.affectedDependents.length }));\n for (const a of diff.affectedDependents) {\n lines.push(` ! ${a.file}`);\n lines.push(` ${a.reason}`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction arraysEqual(a: string[], b: string[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n","import { createServer } from \"node:http\";\nimport type { DependencyGraph, ArchDiff } from \"../types/schema.js\";\nimport { buildGraphPage } from \"./template.js\";\nimport { getLocale } from \"../i18n/index.js\";\nimport type { Locale } from \"../i18n/index.js\";\n\n/**\n * Start a local web server to visualize the dependency graph.\n *\n * Serves:\n * - GET / → Interactive graph visualization (single HTML page)\n * - GET /api/graph → Raw graph data as JSON\n */\nexport function startViewer(\n graph: DependencyGraph,\n options: { port?: number; locale?: Locale; diff?: ArchDiff | null } = {},\n): { port: number; close: () => void } {\n const port = options.port ?? 3000;\n const locale = options.locale ?? getLocale();\n\n const html = buildGraphPage(graph, { locale, diff: options.diff });\n const graphJson = JSON.stringify(graph);\n\n const server = createServer((req, res) => {\n if (req.url === \"/api/graph\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(graphJson);\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n res.end(html);\n });\n\n server.listen(port);\n\n return {\n port,\n close: () => server.close(),\n };\n}\n","import type { DependencyGraph } from \"../types/schema.js\";\nimport type { ArchDiff } from \"../types/schema.js\";\nimport type { Locale } from \"../i18n/index.js\";\n\nexport interface ViewerOptions {\n locale?: Locale;\n diff?: ArchDiff | null;\n}\n\n/**\n * Build the complete HTML page for the interactive graph viewer.\n * Two views: Force-directed Graph + Hierarchical Diagram (draw.io style).\n */\nexport function buildGraphPage(graph: DependencyGraph, options: ViewerOptions = {}): string {\n const locale = options.locale ?? \"en\";\n const diff = options.diff ?? null;\n const files = Object.values(graph.files);\n const nodes = files.map((f) => ({\n id: f.path,\n deps: f.dependencies.length,\n dependents: f.dependents.length,\n dependencies: f.dependencies,\n dependentsList: f.dependents,\n isOrphan: f.dependencies.length === 0 && f.dependents.length === 0,\n dir: f.path.includes(\"/\") ? f.path.substring(0, f.path.lastIndexOf(\"/\")) : \".\",\n }));\n\n const links = graph.edges.map((e) => ({\n source: e.source,\n target: e.target,\n type: e.type,\n }));\n\n const circularFiles = new Set<string>();\n for (const c of graph.circularDependencies) {\n for (const f of c.cycle) circularFiles.add(f);\n }\n\n const dirs = [...new Set(nodes.map((n) => n.dir))].sort();\n const projectName = graph.rootDir.split(\"/\").filter(Boolean).pop() || \"Project\";\n const diffData = diff ? JSON.stringify(diff) : \"null\";\n const graphData = JSON.stringify({ nodes, links, circularFiles: [...circularFiles], dirs, projectName });\n\n return /* html */ `<!DOCTYPE html>\n<html lang=\"${locale}\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>${projectName} — Architecture Viewer</title>\n<style>\n:root {\n --bg: #0d1117; --bg-card: #161b22; --bg-hover: #1c2129;\n --border: #30363d; --border-active: #58a6ff;\n --text: #c9d1d9; --text-dim: #8b949e; --text-muted: #484f58;\n --accent: #58a6ff; --green: #3fb950; --red: #f97583; --yellow: #f0e68c;\n --radius: 8px; --font-size: 13px;\n}\n[data-theme=\"light\"] {\n --bg: #ffffff; --bg-card: #f6f8fa; --bg-hover: #eef1f5;\n --border: #d0d7de; --border-active: #0969da;\n --text: #1f2328; --text-dim: #656d76; --text-muted: #8b949e;\n --accent: #0969da; --green: #1a7f37; --red: #cf222e; --yellow: #9a6700;\n}\n* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; font-size: 13px; overflow: hidden; transition: background 0.3s, color 0.3s; }\n\n/* ─── Tab bar ──────────────────────────────── */\n#tab-bar { position: fixed; top: 0; left: 0; right: 0; height: 44px; background: var(--bg-card); border-bottom: 1px solid var(--border); display: flex; align-items: center; z-index: 30; padding: 0 16px; gap: 2px; transition: background 0.3s; }\n#tab-bar .logo { font-weight: 700; font-size: 14px; color: var(--accent); margin-right: 16px; letter-spacing: -0.3px; outline: none; border-bottom: 1px dashed transparent; cursor: text; min-width: 40px; }\n#tab-bar .logo:hover { border-bottom-color: var(--text-muted); }\n#tab-bar .logo:focus { border-bottom-color: var(--accent); }\n.tab { padding: 8px 16px; font-size: 13px; color: var(--text-dim); cursor: pointer; border-radius: 6px 6px 0 0; border: 1px solid transparent; border-bottom: none; transition: all 0.15s; user-select: none; position: relative; top: 1px; }\n.tab:hover { color: var(--text); background: var(--bg-hover); }\n.tab.active { color: var(--text); background: var(--bg); border-color: var(--border); }\n.tab-right { margin-left: auto; display: flex; align-items: center; gap: 12px; }\n.tab-stats { font-size: 12px; color: var(--text-muted); display: flex; gap: 14px; }\n.tab-stats span b { color: var(--text-dim); }\n.settings-btn { background: none; border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; cursor: pointer; color: var(--text-dim); font-size: 16px; transition: all 0.15s; }\n.settings-btn:hover { border-color: var(--accent); color: var(--text); }\n\n/* ─── Views ────────────────────────────────── */\n.view { position: fixed; top: 44px; left: 0; right: 0; bottom: 0; display: none; }\n.view.active { display: block; }\n.view svg { width: 100%; height: 100%; }\n\n/* ─── HUD ─────────────────────────────────── */\n#hud { position: absolute; top: 12px; left: 12px; z-index: 10; display: flex; flex-direction: column; gap: 8px; }\n.hud-panel { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 10px 14px; font-size: 12px; backdrop-filter: blur(8px); transition: background 0.3s; }\n#search-box { display: flex; align-items: center; gap: 8px; }\n#search-box input { background: transparent; border: none; outline: none; color: var(--text); font-size: 13px; width: 180px; }\n#search-box input::placeholder { color: var(--text-muted); }\nkbd { background: #21262d; border: 1px solid var(--border); border-radius: 3px; padding: 1px 5px; font-size: 10px; color: var(--text-muted); font-family: inherit; }\n.legend-item { display: flex; align-items: center; gap: 6px; margin: 3px 0; color: var(--text-dim); }\n.legend-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }\n\n/* ─── Tooltip (interactive — mouse can enter) ─ */\n#tooltip { position: fixed; display: none; background: var(--bg-card); border: 1px solid var(--accent); border-radius: var(--radius); padding: 14px 16px; font-size: 13px; z-index: 40; max-width: 420px; pointer-events: auto; box-shadow: 0 8px 24px rgba(0,0,0,0.5); transition: background 0.3s; }\n#tooltip .tt-name { color: var(--accent); font-size: 14px; font-weight: 600; margin-bottom: 8px; word-break: break-all; }\n#tooltip .tt-badge { display: inline-block; background: var(--bg-hover); border-radius: 10px; padding: 1px 8px; font-size: 11px; margin: 0 2px; }\n#tooltip .tt-section { margin-top: 8px; font-size: 12px; color: var(--text-dim); max-height: 140px; overflow-y: auto; }\n#tooltip .tt-section div { padding: 2px 0; }\n#tooltip .tt-out { color: var(--accent); }\n#tooltip .tt-in { color: var(--green); }\n\n/* ─── Filters ─────────────────────────────── */\n#filters { position: absolute; bottom: 12px; left: 12px; right: 120px; z-index: 10; display: flex; flex-wrap: wrap; gap: 5px; }\n.filter-pill { background: var(--bg-card); border: 1px solid var(--border); border-radius: 14px; padding: 3px 10px; font-size: 11px; cursor: pointer; user-select: none; transition: all 0.15s; display: flex; align-items: center; gap: 5px; }\n.filter-pill:hover { border-color: var(--text-dim); }\n.filter-pill.active { border-color: var(--accent); }\n.filter-pill .pill-dot { width: 6px; height: 6px; border-radius: 50%; }\n.filter-pill .pill-count { color: var(--text-muted); font-size: 10px; }\n\n/* ─── Zoom controls ───────────────────────── */\n#zoom-ctrl { position: absolute; bottom: 52px; right: 12px; z-index: 10; display: flex; flex-direction: column; gap: 2px; }\n#zoom-ctrl button { width: 32px; height: 32px; background: var(--bg-card); border: 1px solid var(--border); color: var(--text-dim); border-radius: 6px; cursor: pointer; font-size: 16px; display: flex; align-items: center; justify-content: center; transition: all 0.1s; }\n#zoom-ctrl button:hover { background: var(--bg-hover); color: var(--text); }\n\n/* ─── Detail panel ────────────────────────── */\n#detail { position: absolute; top: 12px; right: 12px; width: 280px; z-index: 10; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-size: 13px; display: none; max-height: calc(100vh - 100px); overflow-y: auto; transition: background 0.3s; }\n#detail.open { display: block; }\n#detail .detail-name { color: var(--accent); font-weight: 600; font-size: 14px; word-break: break-all; margin-bottom: 8px; }\n#detail .detail-meta { color: var(--text-dim); margin-bottom: 12px; }\n#detail .detail-section { margin-top: 10px; }\n#detail .detail-section h4 { font-size: 11px; text-transform: uppercase; color: var(--text-muted); letter-spacing: 0.5px; margin-bottom: 4px; }\n#detail .detail-list { list-style: none; }\n#detail .detail-list li { padding: 3px 0; font-size: 12px; color: var(--text-dim); cursor: pointer; }\n#detail .detail-list li:hover { color: var(--accent); }\n#detail .close-btn { position: absolute; top: 8px; right: 10px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 16px; }\n\n/* ─── Settings panel ──────────────────────── */\n#settings-panel { position: fixed; top: 44px; right: 0; width: 280px; height: calc(100vh - 44px); background: var(--bg-card); border-left: 1px solid var(--border); z-index: 25; padding: 20px; transform: translateX(100%); transition: transform 0.25s ease, background 0.3s; overflow-y: auto; }\n#settings-panel.open { transform: translateX(0); }\n#settings-panel h3 { font-size: 14px; color: var(--text); margin-bottom: 16px; }\n.setting-group { margin-bottom: 18px; }\n.setting-group label { display: block; font-size: 12px; color: var(--text-dim); margin-bottom: 6px; }\n.setting-group select, .setting-group input[type=range] { width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; color: var(--text); padding: 6px 8px; font-size: 13px; }\n.setting-group input[type=range] { padding: 4px 0; border: none; accent-color: var(--accent); }\n.setting-value { font-size: 11px; color: var(--text-muted); text-align: right; }\n.theme-toggle { display: flex; gap: 6px; }\n.theme-btn { flex: 1; padding: 8px; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; text-align: center; font-size: 12px; color: var(--text-dim); transition: all 0.15s; }\n.theme-btn:hover { border-color: var(--text-dim); }\n.theme-btn.active { border-color: var(--accent); color: var(--accent); }\n\n/* ─── Hierarchy detail panel ──────────────── */\n#hier-detail { position: absolute; top: 12px; right: 12px; width: 280px; z-index: 10; background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-size: 13px; display: none; max-height: calc(100vh - 100px); overflow-y: auto; transition: background 0.3s; }\n#hier-detail.open { display: block; }\n#hier-detail .detail-name { color: var(--accent); font-weight: 600; font-size: 14px; word-break: break-all; margin-bottom: 8px; }\n#hier-detail .detail-meta { color: var(--text-dim); margin-bottom: 12px; }\n#hier-detail .detail-section { margin-top: 10px; }\n#hier-detail .detail-section h4 { font-size: 11px; text-transform: uppercase; color: var(--text-muted); letter-spacing: 0.5px; margin-bottom: 4px; }\n#hier-detail .detail-list { list-style: none; }\n#hier-detail .detail-list li { padding: 3px 0; font-size: 12px; color: var(--text-dim); cursor: pointer; }\n#hier-detail .detail-list li:hover { color: var(--accent); }\n#hier-detail .close-btn { position: absolute; top: 8px; right: 10px; background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 16px; }\n\n/* ─── Hierarchy ───────────────────────────── */\n.hier-node { cursor: pointer; }\n.hier-node rect { rx: 6; ry: 6; stroke-width: 1.5; transition: stroke 0.15s; }\n.hier-node:hover rect { stroke: var(--accent) !important; stroke-width: 2; }\n.hier-node text { fill: var(--text); pointer-events: none; }\n.hier-link { fill: none; stroke: var(--border); stroke-width: 1; }\n.hier-layer-label { fill: var(--text-muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n\n/* ─── Impact mode ─────────────────────────── */\n#impact-btn.active { background: var(--accent) !important; color: #fff !important; border-color: var(--accent) !important; }\n#impact-badge { position: absolute; bottom: 52px; left: 12px; z-index: 10; display: none; background: var(--accent); color: #fff; font-size: 12px; font-weight: 600; padding: 6px 12px; border-radius: var(--radius); }\n\n/* ─── Help bar ─────────────────────────────── */\n#help-bar { position: absolute; bottom: 12px; right: 12px; z-index: 10; font-size: 11px; color: var(--text-muted); background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 6px 10px; transition: background 0.3s; }\n</style>\n</head>\n<body>\n\n<!-- Tab bar -->\n<div id=\"tab-bar\">\n <span class=\"logo\" id=\"project-title\" contenteditable=\"true\" spellcheck=\"false\" title=\"Click to edit project name\"></span>\n <div class=\"tab active\" data-view=\"graph-view\" data-i18n=\"tab.graph\">Graph</div>\n <div class=\"tab\" data-view=\"hier-view\" data-i18n=\"tab.hierarchy\">Hierarchy</div>\n <div class=\"tab\" data-view=\"diff-view\" id=\"diff-tab\" style=\"display:none\" data-i18n=\"tab.diff\">Diff</div>\n <div class=\"tab-right\">\n <div class=\"tab-stats\">\n <span><span data-i18n=\"stats.files\">Files</span> <b id=\"s-files\">0</b></span>\n <span><span data-i18n=\"stats.edges\">Edges</span> <b id=\"s-edges\">0</b></span>\n <span><span data-i18n=\"stats.circular\">Circular</span> <b id=\"s-circular\">0</b></span>\n </div>\n <button class=\"settings-btn\" onclick=\"toggleSettings()\" title=\"Settings\">⚙</button>\n </div>\n</div>\n\n<!-- Settings panel -->\n<div id=\"settings-panel\">\n <h3 data-i18n=\"settings.title\">Settings</h3>\n <div class=\"setting-group\">\n <label data-i18n=\"settings.theme\">Theme</label>\n <div class=\"theme-toggle\">\n <div class=\"theme-btn active\" data-theme-val=\"dark\" onclick=\"setTheme('dark')\">🌙 Dark</div>\n <div class=\"theme-btn\" data-theme-val=\"light\" onclick=\"setTheme('light')\">☀️ Light</div>\n </div>\n </div>\n <div class=\"setting-group\">\n <label data-i18n=\"settings.fontSize\">Font Size</label>\n <input type=\"range\" id=\"font-size-slider\" min=\"10\" max=\"18\" value=\"13\" oninput=\"setFontSize(this.value)\">\n <div class=\"setting-value\"><span id=\"font-size-val\">13</span>px</div>\n </div>\n <div class=\"setting-group\">\n <label data-i18n=\"settings.nodeSize\">Node Size</label>\n <input type=\"range\" id=\"node-size-slider\" min=\"50\" max=\"200\" value=\"100\" oninput=\"setNodeScale(this.value)\">\n <div class=\"setting-value\"><span id=\"node-size-val\">100</span>%</div>\n </div>\n <div class=\"setting-group\">\n <label data-i18n=\"settings.linkOpacity\">Link Opacity</label>\n <input type=\"range\" id=\"link-opacity-slider\" min=\"10\" max=\"100\" value=\"40\" oninput=\"setLinkOpacity(this.value)\">\n <div class=\"setting-value\"><span id=\"link-opacity-val\">40</span>%</div>\n </div>\n <div class=\"setting-group\">\n <label data-i18n=\"settings.gravity\">Gravity</label>\n <input type=\"range\" id=\"gravity-slider\" min=\"10\" max=\"500\" value=\"150\" oninput=\"setGravity(this.value)\">\n <div class=\"setting-value\"><span id=\"gravity-val\">150</span></div>\n </div>\n <div class=\"setting-group\">\n <label data-i18n=\"settings.language\">Language</label>\n <div class=\"theme-toggle\">\n <div class=\"theme-btn lang-btn\" data-lang=\"en\" onclick=\"setLang('en')\">English</div>\n <div class=\"theme-btn lang-btn\" data-lang=\"ja\" onclick=\"setLang('ja')\">日本語</div>\n </div>\n </div>\n <div class=\"setting-group\" style=\"margin-top:12px;padding-top:12px;border-top:1px solid var(--border)\">\n <label data-i18n=\"settings.export\">Export</label>\n <div class=\"theme-toggle\">\n <div class=\"theme-btn\" onclick=\"exportSVG()\">SVG</div>\n <div class=\"theme-btn\" onclick=\"exportPNG()\">PNG</div>\n </div>\n </div>\n</div>\n\n<!-- Graph View -->\n<div id=\"graph-view\" class=\"view active\">\n <svg id=\"graph-svg\"></svg>\n <div id=\"hud\">\n <div class=\"hud-panel\" id=\"search-box\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 16 16\" fill=\"currentColor\" style=\"color:var(--text-muted)\"><path d=\"M11.5 7a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm-.82 4.74a6 6 0 1 1 1.06-1.06l3.04 3.04a.75.75 0 1 1-1.06 1.06l-3.04-3.04Z\"/></svg>\n <input id=\"search\" type=\"text\" data-i18n-placeholder=\"search.placeholder\" placeholder=\"Search files...\" autocomplete=\"off\">\n <kbd>/</kbd>\n </div>\n <div class=\"hud-panel\" id=\"legend-panel\">\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--red)\"></div> <span data-i18n=\"legend.circular\">Circular dep</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--text-muted)\"></div> <span data-i18n=\"legend.orphan\">Orphan</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"border:2px solid var(--yellow);width:6px;height:6px\"></div> <span data-i18n=\"legend.highCoupling\">High coupling</span></div>\n <div class=\"legend-item\" style=\"margin-top:4px;font-size:11px;gap:3px\"><span style=\"color:var(--accent)\">—→</span> <span data-i18n=\"legend.imports\">imports</span> <span style=\"margin-left:6px;color:var(--green)\">←—</span> <span data-i18n=\"legend.importedBy\">imported by</span></div>\n </div>\n </div>\n <div id=\"detail\">\n <button class=\"close-btn\" onclick=\"closeDetail()\">✕</button>\n <div class=\"detail-name\" id=\"d-name\"></div>\n <div class=\"detail-meta\" id=\"d-meta\"></div>\n <div class=\"detail-section\"><h4 data-i18n=\"detail.importedBy\">Imported by</h4><ul class=\"detail-list\" id=\"d-dependents\"></ul></div>\n <div class=\"detail-section\"><h4 data-i18n=\"detail.imports\">Imports</h4><ul class=\"detail-list\" id=\"d-deps\"></ul></div>\n </div>\n <div id=\"filters\"></div>\n <div id=\"zoom-ctrl\">\n <button onclick=\"zoomIn()\" title=\"Zoom in\">+</button>\n <button onclick=\"zoomOut()\" title=\"Zoom out\">−</button>\n <button onclick=\"zoomFit()\" title=\"Fit\">⊡</button>\n <button id=\"impact-btn\" onclick=\"toggleImpactMode()\" title=\"Impact simulation\" style=\"font-size:12px;margin-top:4px\" data-i18n=\"impact.btn\">Impact</button>\n </div>\n <div id=\"impact-badge\"></div>\n <div id=\"help-bar\" data-i18n=\"help.graph\">Scroll: zoom · Drag: pan · Click: select · / search</div>\n</div>\n\n<!-- Hierarchy View -->\n<div id=\"hier-view\" class=\"view\">\n <svg id=\"hier-svg\"></svg>\n <div id=\"hier-hud\" style=\"position:absolute;top:12px;left:12px;z-index:10;display:flex;flex-direction:column;gap:8px;\">\n <div class=\"hud-panel\" id=\"hier-legend\">\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--red)\"></div> <span data-i18n=\"legend.circular\">Circular dep</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--text-muted)\"></div> <span data-i18n=\"legend.orphan\">Orphan</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"border:2px solid var(--yellow);width:6px;height:6px\"></div> <span data-i18n=\"legend.highCoupling\">High coupling</span></div>\n </div>\n </div>\n <div id=\"hier-detail\">\n <button class=\"close-btn\" onclick=\"closeHierDetail()\">✕</button>\n <div class=\"detail-name\" id=\"hd-name\"></div>\n <div class=\"detail-meta\" id=\"hd-meta\"></div>\n <div class=\"detail-section\"><h4 data-i18n=\"detail.importedBy\">Imported by</h4><ul class=\"detail-list\" id=\"hd-dependents\"></ul></div>\n <div class=\"detail-section\"><h4 data-i18n=\"detail.imports\">Imports</h4><ul class=\"detail-list\" id=\"hd-deps\"></ul></div>\n </div>\n <div id=\"hier-filters\" style=\"position:absolute;bottom:42px;left:12px;right:120px;z-index:10;display:flex;flex-wrap:wrap;gap:5px;\"></div>\n <div id=\"help-bar\" style=\"position:absolute\" data-i18n=\"help.hierarchy\">Scroll to navigate · Click to highlight</div>\n</div>\n\n<!-- Diff View -->\n<div id=\"diff-view\" class=\"view\">\n <svg id=\"diff-svg\"></svg>\n <div id=\"diff-legend\" style=\"position:absolute;top:12px;left:12px;z-index:10;\">\n <div class=\"hud-panel\">\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--green)\"></div> <span data-i18n=\"diff.addedLabel\">Added</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--red)\"></div> <span data-i18n=\"diff.removedLabel\">Removed</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--yellow)\"></div> <span data-i18n=\"diff.modifiedLabel\">Modified</span></div>\n <div class=\"legend-item\"><div class=\"legend-dot\" style=\"background:var(--accent)\"></div> <span data-i18n=\"diff.affectedLabel\">Affected</span></div>\n </div>\n </div>\n <div id=\"help-bar\" style=\"position:absolute\" data-i18n=\"help.diff\">Green=added · Red=removed · Yellow=modified · Blue=affected</div>\n</div>\n\n<!-- Tooltip (shared, interactive) -->\n<div id=\"tooltip\">\n <div class=\"tt-name\" id=\"tt-name\"></div>\n <div>\n <span class=\"tt-badge tt-out\" id=\"tt-dep-count\"></span> <span data-i18n=\"tooltip.imports\">imports</span>\n <span class=\"tt-badge tt-in\" id=\"tt-dpt-count\" style=\"margin-left:6px\"></span> <span data-i18n=\"tooltip.importedBy\">imported by</span>\n </div>\n <div class=\"tt-section\" id=\"tt-details\"></div>\n</div>\n\n<script src=\"https://d3js.org/d3.v7.min.js\"></script>\n<script>\n// ═══════════════════════════════════════════════\n// i18n\n// ═══════════════════════════════════════════════\nconst I18N = {\n en: {\n 'tab.graph': 'Graph', 'tab.hierarchy': 'Hierarchy',\n 'stats.files': 'Files', 'stats.edges': 'Edges', 'stats.circular': 'Circular',\n 'settings.title': 'Settings', 'settings.theme': 'Theme', 'settings.fontSize': 'Font Size',\n 'settings.nodeSize': 'Node Size', 'settings.linkOpacity': 'Link Opacity', 'settings.gravity': 'Gravity', 'settings.language': 'Language', 'settings.export': 'Export',\n 'impact.title': 'Impact Simulation', 'impact.btn': 'Impact', 'impact.transitive': 'files affected',\n 'search.placeholder': 'Search files...',\n 'legend.circular': 'Circular dep', 'legend.orphan': 'Orphan', 'legend.highCoupling': 'High coupling',\n 'legend.imports': 'imports', 'legend.importedBy': 'imported by',\n 'detail.importedBy': 'Imported by', 'detail.imports': 'Imports',\n 'detail.none': 'none', 'detail.dir': 'Dir', 'detail.dependencies': 'Dependencies', 'detail.dependents': 'Dependents',\n 'tooltip.imports': 'imports', 'tooltip.importedBy': 'imported by',\n 'help.graph': 'Scroll: zoom · Drag: pan · Click: select · / search',\n 'help.hierarchy': 'Scroll to navigate · Click to highlight',\n 'help.diff': 'Green=added · Red=removed · Yellow=modified · Blue=affected',\n 'tab.diff': 'Diff',\n 'diff.addedLabel': 'Added', 'diff.removedLabel': 'Removed', 'diff.modifiedLabel': 'Modified', 'diff.affectedLabel': 'Affected',\n },\n ja: {\n 'tab.graph': 'グラフ', 'tab.hierarchy': '階層図',\n 'stats.files': 'ファイル', 'stats.edges': 'エッジ', 'stats.circular': '循環参照',\n 'settings.title': '設定', 'settings.theme': 'テーマ', 'settings.fontSize': 'フォントサイズ',\n 'settings.nodeSize': 'ノードサイズ', 'settings.linkOpacity': 'リンク透明度', 'settings.gravity': '重力', 'settings.language': '言語', 'settings.export': 'エクスポート',\n 'impact.title': '影響範囲シミュレーション', 'impact.btn': '影響', 'impact.transitive': 'ファイルに影響',\n 'search.placeholder': 'ファイル検索...',\n 'legend.circular': '循環参照', 'legend.orphan': '孤立', 'legend.highCoupling': '高結合',\n 'legend.imports': 'import先', 'legend.importedBy': 'import元',\n 'detail.importedBy': 'import元', 'detail.imports': 'import先',\n 'detail.none': 'なし', 'detail.dir': 'ディレクトリ', 'detail.dependencies': '依存先', 'detail.dependents': '被依存',\n 'tooltip.imports': 'import先', 'tooltip.importedBy': 'import元',\n 'help.graph': 'スクロール: ズーム · ドラッグ: 移動 · クリック: 選択 · / 検索',\n 'help.hierarchy': 'スクロールで移動 · クリックでハイライト',\n 'help.diff': '緑=追加 · 赤=削除 · 黄=変更 · 青=影響',\n 'tab.diff': '差分',\n 'diff.addedLabel': '追加', 'diff.removedLabel': '削除', 'diff.modifiedLabel': '変更', 'diff.affectedLabel': '影響',\n }\n};\nlet currentLang = '${locale}';\nfunction applyI18n() {\n const msgs = I18N[currentLang] || I18N.en;\n document.querySelectorAll('[data-i18n]').forEach(el => {\n const key = el.getAttribute('data-i18n');\n if (msgs[key]) el.textContent = msgs[key];\n });\n document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {\n const key = el.getAttribute('data-i18n-placeholder');\n if (msgs[key]) el.placeholder = msgs[key];\n });\n document.querySelectorAll('.lang-btn').forEach(b => b.classList.toggle('active', b.dataset.lang === currentLang));\n}\nwindow.setLang = (lang) => { currentLang = lang; applyI18n(); saveSettings(); };\nfunction i(key) { return (I18N[currentLang] || I18N.en)[key] || key; }\n\n// ═══════════════════════════════════════════════\n// SETTINGS (persisted to localStorage)\n// ═══════════════════════════════════════════════\nconst STORAGE_KEY = 'archtracker-settings';\nfunction saveSettings() {\n const s = { theme: document.body.getAttribute('data-theme') || 'dark', fontSize: document.getElementById('font-size-val').textContent, nodeSize: document.getElementById('node-size-val').textContent, linkOpacity: document.getElementById('link-opacity-val').textContent, gravity: document.getElementById('gravity-val').textContent, lang: currentLang, projectTitle: document.getElementById('project-title').textContent };\n try { localStorage.setItem(STORAGE_KEY, JSON.stringify(s)); } catch(e) {}\n}\nfunction loadSettings() {\n try { return JSON.parse(localStorage.getItem(STORAGE_KEY)) || null; } catch(e) { return null; }\n}\n\nlet nodeScale = 1, baseLinkOpacity = 0.4;\nwindow.toggleSettings = () => document.getElementById('settings-panel').classList.toggle('open');\nwindow.setTheme = (theme) => {\n document.body.setAttribute('data-theme', theme === 'light' ? 'light' : '');\n document.querySelectorAll('.theme-btn[data-theme-val]').forEach(b => b.classList.toggle('active', b.dataset.themeVal === theme));\n saveSettings();\n};\nwindow.setFontSize = (v) => {\n document.getElementById('font-size-val').textContent = v;\n const scale = v / 13;\n if (typeof node !== 'undefined') {\n node.select('text').attr('font-size', d => (d.dependents>=3?12:10) * scale);\n }\n saveSettings();\n};\nwindow.setNodeScale = (v) => {\n nodeScale = v / 100;\n document.getElementById('node-size-val').textContent = v;\n if (typeof node !== 'undefined') {\n node.select('circle').attr('r', d => nodeRadius(d) * nodeScale);\n node.select('text').attr('dx', d => nodeRadius(d) * nodeScale + 4);\n simulation.force('collision', d3.forceCollide().radius(d => nodeRadius(d) * nodeScale + 4));\n simulation.alpha(0.3).restart();\n }\n saveSettings();\n};\nwindow.setLinkOpacity = (v) => {\n baseLinkOpacity = v / 100;\n document.getElementById('link-opacity-val').textContent = v;\n if (typeof link !== 'undefined') link.attr('opacity', baseLinkOpacity);\n saveSettings();\n};\nlet gravityStrength = 150;\nwindow.setGravity = (v) => {\n gravityStrength = +v;\n document.getElementById('gravity-val').textContent = v;\n if (typeof simulation !== 'undefined') {\n simulation.force('charge', d3.forceManyBody().strength(-gravityStrength).distanceMax(500));\n simulation.alpha(0.5).restart();\n }\n saveSettings();\n};\n\n// ═══════════════════════════════════════════════\n// EXPORT\n// ═══════════════════════════════════════════════\nwindow.exportSVG = () => {\n const activeView = document.querySelector('.view.active svg');\n if (!activeView) return;\n const clone = activeView.cloneNode(true);\n clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');\n const blob = new Blob([clone.outerHTML], {type: 'image/svg+xml'});\n const a = document.createElement('a');\n a.href = URL.createObjectURL(blob);\n a.download = (document.getElementById('project-title').textContent || 'graph') + '.svg';\n a.click(); URL.revokeObjectURL(a.href);\n};\nwindow.exportPNG = () => {\n const activeView = document.querySelector('.view.active svg');\n if (!activeView) return;\n const clone = activeView.cloneNode(true);\n clone.setAttribute('xmlns', 'http://www.w3.org/2000/svg');\n const svgStr = new XMLSerializer().serializeToString(clone);\n const canvas = document.createElement('canvas');\n const bbox = activeView.getBoundingClientRect();\n canvas.width = bbox.width * 2; canvas.height = bbox.height * 2;\n const ctx = canvas.getContext('2d');\n ctx.scale(2, 2);\n const img = new Image();\n img.onload = () => { ctx.drawImage(img, 0, 0); const a = document.createElement('a'); a.href = canvas.toDataURL('image/png'); a.download = (document.getElementById('project-title').textContent || 'graph') + '.png'; a.click(); };\n img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgStr)));\n};\n\n// ═══════════════════════════════════════════════\n// DATA\n// ═══════════════════════════════════════════════\nconst DATA = ${graphData};\nconst W = window.innerWidth, H = window.innerHeight - 44;\nconst circularSet = new Set(DATA.circularFiles);\n\n// Project title (editable)\nconst titleEl = document.getElementById('project-title');\ntitleEl.textContent = DATA.projectName;\ntitleEl.addEventListener('blur', () => { if (!titleEl.textContent.trim()) titleEl.textContent = DATA.projectName; document.title = titleEl.textContent + ' \\u2014 Architecture Viewer'; saveSettings(); });\ntitleEl.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); titleEl.blur(); } });\n\n// Restore saved settings — phase 1: non-graph settings (before graph init)\nconst _savedSettings = loadSettings();\nif (_savedSettings) {\n if (_savedSettings.theme) setTheme(_savedSettings.theme);\n if (_savedSettings.lang) { currentLang = _savedSettings.lang; applyI18n(); }\n if (_savedSettings.projectTitle) { titleEl.textContent = _savedSettings.projectTitle; document.title = _savedSettings.projectTitle + ' \\u2014 Architecture Viewer'; }\n // Set slider positions (visual only — graph not built yet)\n if (_savedSettings.fontSize) { document.getElementById('font-size-slider').value = _savedSettings.fontSize; document.getElementById('font-size-val').textContent = _savedSettings.fontSize; }\n if (_savedSettings.nodeSize) { document.getElementById('node-size-slider').value = _savedSettings.nodeSize; document.getElementById('node-size-val').textContent = _savedSettings.nodeSize; nodeScale = _savedSettings.nodeSize / 100; }\n if (_savedSettings.linkOpacity) { document.getElementById('link-opacity-slider').value = _savedSettings.linkOpacity; document.getElementById('link-opacity-val').textContent = _savedSettings.linkOpacity; baseLinkOpacity = _savedSettings.linkOpacity / 100; }\n if (_savedSettings.gravity) { document.getElementById('gravity-slider').value = _savedSettings.gravity; document.getElementById('gravity-val').textContent = _savedSettings.gravity; gravityStrength = +_savedSettings.gravity; }\n}\n\ndocument.getElementById('s-files').textContent = DATA.nodes.length;\ndocument.getElementById('s-edges').textContent = DATA.links.length;\ndocument.getElementById('s-circular').textContent = DATA.circularFiles.length;\n\nconst dirColor = d3.scaleOrdinal()\n .domain(DATA.dirs)\n .range(['#58a6ff','#3fb950','#d2a8ff','#f0883e','#79c0ff','#56d4dd','#db61a2','#f778ba','#ffa657','#7ee787']);\nfunction nodeColor(d) {\n if (circularSet.has(d.id)) return '#f97583';\n if (d.isOrphan) return '#484f58';\n return dirColor(d.dir);\n}\nfunction nodeRadius(d) { return Math.max(5, Math.min(22, 4 + d.dependents * 1.8)); }\nfunction fileName(id) { return id.split('/').pop(); }\n\n// ═══════════════════════════════════════════════\n// TAB SWITCHING\n// ═══════════════════════════════════════════════\nlet hierBuilt = false;\ndocument.querySelectorAll('.tab').forEach(tab => {\n tab.addEventListener('click', () => {\n document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));\n tab.classList.add('active');\n document.getElementById(tab.dataset.view).classList.add('active');\n if (tab.dataset.view === 'hier-view' && !hierBuilt) { buildHierarchy(); hierBuilt = true; }\n });\n});\n\n// ═══════════════════════════════════════════════\n// TOOLTIP — delayed hide + interactive\n// ═══════════════════════════════════════════════\nconst tooltip = document.getElementById('tooltip');\nlet tooltipHideTimer = null;\nlet tooltipLocked = false;\n\nfunction showTooltip(e, d) {\n clearTimeout(tooltipHideTimer);\n document.getElementById('tt-name').textContent = d.id;\n document.getElementById('tt-dep-count').textContent = d.deps;\n document.getElementById('tt-dpt-count').textContent = d.dependents;\n const out = (d.dependencies||[]).map(x => '<div class=\"tt-out\">→ '+x+'</div>');\n const inc = (d.dependentsList||[]).map(x => '<div class=\"tt-in\">← '+x+'</div>');\n document.getElementById('tt-details').innerHTML = [...out, ...inc].join('');\n tooltip.style.display = 'block';\n positionTooltip(e);\n}\nfunction positionTooltip(e) {\n const gap = 24;\n const tw = 420, th = tooltip.offsetHeight || 200;\n // Prefer placing to the right and above the cursor so it doesn't cover nodes below\n let x = e.clientX + gap;\n let y = e.clientY - th - 12;\n // If no room on the right, flip left\n if (x + tw > window.innerWidth) x = e.clientX - tw - gap;\n // If no room above, place below the cursor with gap\n if (y < 50) y = e.clientY + gap;\n // Final clamp\n if (y + th > window.innerHeight) y = window.innerHeight - th - 8;\n if (x < 8) x = 8;\n tooltip.style.left = x + 'px';\n tooltip.style.top = y + 'px';\n}\nfunction scheduleHideTooltip() {\n clearTimeout(tooltipHideTimer);\n tooltipHideTimer = setTimeout(() => {\n if (!tooltipLocked) {\n tooltip.style.display = 'none';\n if (!pinnedNode) resetGraphHighlight();\n }\n }, 250);\n}\n\n// Keep tooltip visible when mouse enters it\ntooltip.addEventListener('mouseenter', () => {\n clearTimeout(tooltipHideTimer);\n tooltipLocked = true;\n});\ntooltip.addEventListener('mouseleave', () => {\n tooltipLocked = false;\n scheduleHideTooltip();\n});\n\n// ═══════════════════════════════════════════════\n// GRAPH VIEW\n// ═══════════════════════════════════════════════\nconst svg = d3.select('#graph-svg').attr('width', W).attr('height', H);\nconst g = svg.append('g');\nconst zoom = d3.zoom().scaleExtent([0.05, 10]).on('zoom', e => g.attr('transform', e.transform));\nsvg.call(zoom);\nsvg.call(zoom.transform, d3.zoomIdentity.translate(W/2, H/2).scale(0.7));\n\nwindow.zoomIn = () => svg.transition().duration(300).call(zoom.scaleBy, 1.4);\nwindow.zoomOut = () => svg.transition().duration(300).call(zoom.scaleBy, 0.7);\nwindow.zoomFit = () => {\n const b = g.node().getBBox(); if (!b.width) return;\n const s = Math.min(W/(b.width+80), H/(b.height+80))*0.9;\n svg.transition().duration(500).call(zoom.transform, d3.zoomIdentity.translate(W/2-(b.x+b.width/2)*s, H/2-(b.y+b.height/2)*s).scale(s));\n};\n\n// Defs\nconst defs = svg.append('defs');\n['#30363d','#58a6ff','#3fb950'].forEach((c,i) => {\n defs.append('marker').attr('id','arrow-'+i).attr('viewBox','0 -4 8 8')\n .attr('refX',8).attr('refY',0).attr('markerWidth',7).attr('markerHeight',7).attr('orient','auto')\n .append('path').attr('d','M0,-3.5L8,0L0,3.5Z').attr('fill',c);\n});\n\n// Links\nconst link = g.append('g').selectAll('line').data(DATA.links).join('line')\n .attr('stroke', d => d.type==='type-only'?'#1f3d5c':'#30363d')\n .attr('stroke-width',1)\n .attr('stroke-dasharray', d => d.type==='type-only'?'4,3':d.type==='dynamic'?'6,3':null)\n .attr('marker-end','url(#arrow-0)')\n .attr('opacity', baseLinkOpacity);\n\n// Nodes\nconst node = g.append('g').selectAll('g').data(DATA.nodes).join('g')\n .attr('cursor','pointer')\n .call(d3.drag().on('start',dragStart).on('drag',dragging).on('end',dragEnd));\n\nnode.append('circle')\n .attr('r', d => nodeRadius(d) * nodeScale)\n .attr('fill', nodeColor)\n .attr('stroke', d => d.deps>=5?'var(--yellow)':nodeColor(d))\n .attr('stroke-width', d => d.deps>=5?2.5:1.5)\n .attr('stroke-opacity', d => d.deps>=5?0.8:0.3);\n\nnode.append('text')\n .text(d => fileName(d.id).replace(/\\\\.tsx?$/,''))\n .attr('dx', d => nodeRadius(d)*nodeScale+4)\n .attr('dy',3.5)\n .attr('font-size', d => d.dependents>=3?12:10)\n .attr('font-weight', d => d.dependents>=3?600:400)\n .attr('fill', d => d.dependents>=3?'var(--text)':'var(--text-dim)')\n .attr('opacity', d => d.dependents>=1||d.deps>=3?1:0.5)\n .attr('pointer-events','none');\n\n// Simulation\nconst simulation = d3.forceSimulation(DATA.nodes)\n .force('link', d3.forceLink(DATA.links).id(d=>d.id).distance(70).strength(0.25))\n .force('charge', d3.forceManyBody().strength(-gravityStrength).distanceMax(500))\n .force('center', d3.forceCenter(0,0))\n .force('collision', d3.forceCollide().radius(d=>nodeRadius(d)*nodeScale+4))\n .force('x', d3.forceX(0).strength(0.03))\n .force('y', d3.forceY(0).strength(0.03))\n .on('tick', () => {\n link.each(function(d) {\n const dx=d.target.x-d.source.x, dy=d.target.y-d.source.y;\n const dist=Math.sqrt(dx*dx+dy*dy)||1;\n const rT=nodeRadius(d.target)*nodeScale, rS=nodeRadius(d.source)*nodeScale;\n d3.select(this)\n .attr('x1',d.source.x+(dx/dist)*rS).attr('y1',d.source.y+(dy/dist)*rS)\n .attr('x2',d.target.x-(dx/dist)*rT).attr('y2',d.target.y-(dy/dist)*rT);\n });\n node.attr('transform', d=>\\`translate(\\${d.x},\\${d.y})\\`);\n });\n\nsetTimeout(()=>zoomFit(), 1500);\n\n// Restore saved settings — phase 2: apply to graph elements now that they exist\nif (_savedSettings) {\n if (_savedSettings.fontSize) setFontSize(_savedSettings.fontSize);\n}\n\n// ─── Highlight helper ─────────────────────────\nlet pinnedNode = null;\n\nfunction highlightNode(d) {\n const conn = new Set([d.id]);\n DATA.links.forEach(l => { const s=l.source.id??l.source,t=l.target.id??l.target; if(s===d.id)conn.add(t); if(t===d.id)conn.add(s); });\n node.select('circle').transition().duration(150).attr('opacity',n=>conn.has(n.id)?1:0.1);\n node.select('text').transition().duration(150).attr('opacity',n=>conn.has(n.id)?1:0.05);\n link.transition().duration(150)\n .attr('opacity',l=>{const s=l.source.id??l.source,t=l.target.id??l.target;return s===d.id||t===d.id?0.9:0.03;})\n .attr('stroke',l=>{const s=l.source.id??l.source,t=l.target.id??l.target;if(s===d.id)return'#58a6ff';if(t===d.id)return'#3fb950';return l.type==='type-only'?'#1f3d5c':'#30363d';})\n .attr('stroke-width',l=>{const s=l.source.id??l.source,t=l.target.id??l.target;return s===d.id||t===d.id?2:1;})\n .attr('marker-end',l=>{const s=l.source.id??l.source,t=l.target.id??l.target;if(s===d.id)return'url(#arrow-1)';if(t===d.id)return'url(#arrow-2)';return'url(#arrow-0)';});\n}\n\nfunction resetGraphHighlight() {\n pinnedNode = null;\n node.select('circle').transition().duration(200).attr('opacity',1);\n node.select('text').transition().duration(200).attr('opacity',d=>d.dependents>=1||d.deps>=3?1:0.5);\n link.transition().duration(200)\n .attr('opacity',baseLinkOpacity)\n .attr('stroke',d=>d.type==='type-only'?'#1f3d5c':'#30363d')\n .attr('stroke-width',1).attr('marker-end','url(#arrow-0)');\n}\n\n// ─── Hover ───────────────────────────────────\nnode.on('mouseover', (e,d) => {\n showTooltip(e,d);\n if (!pinnedNode) highlightNode(d);\n})\n.on('mousemove', e=>positionTooltip(e))\n.on('mouseout', () => { scheduleHideTooltip(); if (!pinnedNode) { /* highlight resets via scheduleHideTooltip */ } });\n\n// ─── Click: pin highlight + detail panel ─────\nnode.on('click', (e,d) => {\n e.stopPropagation();\n pinnedNode = d;\n highlightNode(d);\n showDetail(d);\n});\nsvg.on('click', () => {\n resetGraphHighlight();\n tooltip.style.display = 'none';\n tooltipLocked = false;\n closeDetail();\n});\n\nfunction showDetail(d) {\n const p=document.getElementById('detail');\n document.getElementById('d-name').textContent=d.id;\n document.getElementById('d-meta').innerHTML=i('detail.dir')+': '+d.dir+'<br>'+i('detail.dependencies')+': '+d.deps+' · '+i('detail.dependents')+': '+d.dependents;\n const deptL=document.getElementById('d-dependents'), depsL=document.getElementById('d-deps');\n deptL.innerHTML=(d.dependentsList||[]).map(x=>'<li onclick=\"focusNode(\\\\''+x+'\\\\')\">← '+x+'</li>').join('')||'<li style=\"color:var(--text-muted)\">'+i('detail.none')+'</li>';\n depsL.innerHTML=(d.dependencies||[]).map(x=>'<li onclick=\"focusNode(\\\\''+x+'\\\\')\">→ '+x+'</li>').join('')||'<li style=\"color:var(--text-muted)\">'+i('detail.none')+'</li>';\n p.classList.add('open');\n}\nwindow.closeDetail=()=>document.getElementById('detail').classList.remove('open');\nwindow.focusNode=(id)=>{\n const n=DATA.nodes.find(x=>x.id===id); if(!n)return; showDetail(n);\n svg.transition().duration(500).call(zoom.transform,d3.zoomIdentity.translate(W/2-n.x*1.5,H/2-n.y*1.5).scale(1.5));\n};\n\n// Drag\nfunction dragStart(e,d){if(!e.active)simulation.alphaTarget(0.3).restart();d.fx=d.x;d.fy=d.y;}\nfunction dragging(e,d){d.fx=e.x;d.fy=e.y;}\nfunction dragEnd(e,d){if(!e.active)simulation.alphaTarget(0);}\n\n// ─── Search ──────────────────────────────────\nconst searchInput=document.getElementById('search');\ndocument.addEventListener('keydown',e=>{\n if(e.key==='/'&&document.activeElement!==searchInput){e.preventDefault();searchInput.focus();}\n if(e.key==='Escape'){searchInput.value='';searchInput.blur();resetGraphHighlight();}\n});\nsearchInput.addEventListener('input',e=>{\n const q=e.target.value.toLowerCase();\n if(!q){resetGraphHighlight();return;}\n node.select('circle').attr('opacity',d=>d.id.toLowerCase().includes(q)?1:0.06);\n node.select('text').attr('opacity',d=>d.id.toLowerCase().includes(q)?1:0.04);\n link.attr('opacity',0.03);\n});\n\n// ─── Filters (click=toggle, hover=highlight nodes) ──\nconst filtersEl=document.getElementById('filters');\nconst activeDirs=new Set(DATA.dirs);\nconst dirCounts={};\nDATA.nodes.forEach(n=>dirCounts[n.dir]=(dirCounts[n.dir]||0)+1);\nDATA.dirs.forEach(dir=>{\n const pill=document.createElement('div');\n pill.className='filter-pill active';\n pill.innerHTML='<div class=\"pill-dot\" style=\"background:'+dirColor(dir)+'\"></div>'+(dir||'.')+' <span class=\"pill-count\">'+dirCounts[dir]+'</span>';\n pill.onclick=()=>{\n if(activeDirs.has(dir)){activeDirs.delete(dir);pill.classList.remove('active');}\n else{activeDirs.add(dir);pill.classList.add('active');}\n applyFilter();\n };\n pill.onmouseenter=()=>{\n if(pinnedNode)return;\n node.select('circle').transition().duration(120).attr('opacity',d=>d.dir===dir?1:0.1);\n node.select('text').transition().duration(120).attr('opacity',d=>d.dir===dir?1:0.05);\n };\n pill.onmouseleave=()=>{\n if(pinnedNode)return;\n node.select('circle').transition().duration(150).attr('opacity',1);\n node.select('text').transition().duration(150).attr('opacity',d=>d.dependents>=1||d.deps>=3?1:0.5);\n };\n filtersEl.appendChild(pill);\n});\nfunction applyFilter(){\n node.attr('display',d=>activeDirs.has(d.dir)?null:'none');\n link.attr('display',l=>{\n const s=l.source.id??l.source,t=l.target.id??l.target;\n const sD=DATA.nodes.find(n=>n.id===s)?.dir,tD=DATA.nodes.find(n=>n.id===t)?.dir;\n return activeDirs.has(sD)&&activeDirs.has(tD)?null:'none';\n });\n}\n\n// ─── Impact simulation mode ──────────────────\nlet impactMode=false;\nconst impactBadge=document.getElementById('impact-badge');\nwindow.toggleImpactMode=()=>{\n impactMode=!impactMode;\n document.getElementById('impact-btn').classList.toggle('active',impactMode);\n if(!impactMode){impactBadge.style.display='none';resetGraphHighlight();}\n};\nfunction getTransitiveDependents(startId){\n const result=new Set();const queue=[startId];\n const revMap={};\n DATA.links.forEach(l=>{const s=l.source.id??l.source,t=l.target.id??l.target;if(!revMap[t])revMap[t]=[];revMap[t].push(s);});\n while(queue.length){const id=queue.shift();if(result.has(id))continue;result.add(id);(revMap[id]||[]).forEach(x=>queue.push(x));}\n return result;\n}\n// Override click in impact mode\nconst origClick=node.on('click');\nnode.on('click',(e,d)=>{\n if(!impactMode){e.stopPropagation();pinnedNode=d;highlightNode(d);showDetail(d);return;}\n e.stopPropagation();\n const affected=getTransitiveDependents(d.id);\n node.select('circle').transition().duration(200).attr('opacity',n=>affected.has(n.id)?1:0.06)\n .attr('stroke',n=>affected.has(n.id)&&n.id!==d.id?'var(--red)':n.deps>=5?'var(--yellow)':nodeColor(n))\n .attr('stroke-width',n=>affected.has(n.id)?3:1.5);\n node.select('text').transition().duration(200).attr('opacity',n=>affected.has(n.id)?1:0.04);\n link.transition().duration(200).attr('opacity',l=>{\n const s=l.source.id??l.source,t=l.target.id??l.target;\n return affected.has(s)&&affected.has(t)?0.8:0.03;\n }).attr('stroke',l=>{\n const s=l.source.id??l.source,t=l.target.id??l.target;\n return affected.has(s)&&affected.has(t)?'var(--red)':l.type==='type-only'?'#1f3d5c':'#30363d';\n });\n impactBadge.textContent=d.id.split('/').pop()+' → '+(affected.size-1)+' '+i('impact.transitive');\n impactBadge.style.display='block';\n});\n\nwindow.addEventListener('resize',()=>{\n const w=window.innerWidth,h=window.innerHeight-44;\n svg.attr('width',w).attr('height',h);\n});\n\n// ═══════════════════════════════════════════════\n// HIERARCHY VIEW\n// ═══════════════════════════════════════════════\nfunction buildHierarchy(){\n const hSvg=d3.select('#hier-svg');\n const hG=hSvg.append('g');\n const hZoom=d3.zoom().scaleExtent([0.1,4]).on('zoom',e=>hG.attr('transform',e.transform));\n hSvg.call(hZoom);\n\n const nodeMap={}; DATA.nodes.forEach(n=>nodeMap[n.id]=n);\n const importsMap={}; DATA.links.forEach(l=>{const s=l.source.id??l.source,t=l.target.id??l.target;if(!importsMap[s])importsMap[s]=[];importsMap[s].push(t);});\n\n const entryPoints=DATA.nodes.filter(n=>n.dependents===0).map(n=>n.id);\n const layers={};const visited=new Set();\n const queue=entryPoints.map(id=>({id,layer:0}));\n DATA.nodes.forEach(n=>{if(n.isOrphan)layers[n.id]=0;});\n\n while(queue.length>0){\n const{id,layer}=queue.shift();\n if(visited.has(id)&&(layers[id]??-1)>=layer)continue;\n layers[id]=Math.max(layers[id]??0,layer);visited.add(id);\n (importsMap[id]||[]).forEach(t=>queue.push({id:t,layer:layer+1}));\n }\n DATA.nodes.forEach(n=>{if(!(n.id in layers))layers[n.id]=0;});\n\n const maxLayer=Math.max(0,...Object.values(layers));\n const layerGroups={};\n for(let i=0;i<=maxLayer;i++)layerGroups[i]=[];\n Object.entries(layers).forEach(([id,l])=>layerGroups[l].push(id));\n Object.values(layerGroups).forEach(arr=>arr.sort((a,b)=>(nodeMap[a]?.dir||'').localeCompare(nodeMap[b]?.dir||'')||a.localeCompare(b)));\n\n const boxW=200,boxH=30,gapX=24,gapY=70,padY=60,padX=40;\n const positions={};let maxRowWidth=0;\n for(let layer=0;layer<=maxLayer;layer++){const items=layerGroups[layer];maxRowWidth=Math.max(maxRowWidth,items.length*(boxW+gapX)-gapX);}\n for(let layer=0;layer<=maxLayer;layer++){\n const items=layerGroups[layer],rowWidth=items.length*(boxW+gapX)-gapX,startX=padX+(maxRowWidth-rowWidth)/2;\n items.forEach((id,i)=>{positions[id]={x:startX+i*(boxW+gapX),y:padY+layer*(boxH+gapY)};});\n }\n\n const totalW=maxRowWidth+padX*2,totalH=padY*2+(maxLayer+1)*(boxH+gapY);\n hSvg.attr('width',Math.max(totalW,W)).attr('height',Math.max(totalH,H));\n\n const linkG=hG.append('g');\n DATA.links.forEach(l=>{\n const sId=l.source.id??l.source,tId=l.target.id??l.target;\n const s=positions[sId],t=positions[tId]; if(!s||!t)return;\n const x1=s.x+boxW/2,y1=s.y+boxH,x2=t.x+boxW/2,y2=t.y,midY=(y1+y2)/2;\n linkG.append('path').attr('class','hier-link')\n .attr('d',\\`M\\${x1},\\${y1} C\\${x1},\\${midY} \\${x2},\\${midY} \\${x2},\\${y2}\\`)\n .attr('stroke',l.type==='type-only'?'#1f3d5c':'var(--border)')\n .attr('stroke-dasharray',l.type==='type-only'?'4,3':null)\n .attr('data-source',sId).attr('data-target',tId);\n });\n\n hSvg.append('defs').append('marker').attr('id','harrow').attr('viewBox','0 -3 6 6')\n .attr('refX',6).attr('refY',0).attr('markerWidth',6).attr('markerHeight',6).attr('orient','auto')\n .append('path').attr('d','M0,-3L6,0L0,3Z').attr('fill','var(--border)');\n linkG.selectAll('path').attr('marker-end','url(#harrow)');\n\n for(let layer=0;layer<=maxLayer;layer++){\n if(!layerGroups[layer].length)continue;\n hG.append('text').attr('class','hier-layer-label').attr('font-size',11)\n .attr('x',12).attr('y',padY+layer*(boxH+gapY)+boxH/2+4).text('L'+layer);\n }\n\n const nodeG=hG.append('g');\n DATA.nodes.forEach(n=>{\n const pos=positions[n.id]; if(!pos)return;\n const gn=nodeG.append('g').attr('class','hier-node').attr('transform',\\`translate(\\${pos.x},\\${pos.y})\\`);\n gn.append('rect').attr('width',boxW).attr('height',boxH)\n .attr('fill','var(--bg-card)').attr('stroke',nodeColor(n))\n .attr('stroke-width',circularSet.has(n.id)?2:1.5);\n gn.append('text').attr('x',8).attr('y',boxH/2+4).attr('font-size',11)\n .text(fileName(n.id).length>24?fileName(n.id).slice(0,22)+'…':fileName(n.id));\n gn.append('text').attr('x',boxW-8).attr('y',boxH/2+4)\n .attr('text-anchor','end').attr('font-size',10).attr('fill','var(--text-muted)')\n .text(n.dependents>0?'↑'+n.dependents:'');\n gn.append('text').attr('x',8).attr('y',-4).attr('font-size',9)\n .attr('fill',dirColor(n.dir)).attr('opacity',0.7).text(n.dir);\n\n gn.node().__data_id=n.id;\n gn.on('mouseover',e=>{\n showTooltip(e,n);\n if (!hierPinned) hierHighlight(n.id);\n })\n .on('mousemove',e=>positionTooltip(e))\n .on('mouseout',()=>{\n scheduleHideTooltip();\n if (!hierPinned) hierResetHighlight();\n })\n .on('click',(e)=>{\n e.stopPropagation();\n hierPinned=n.id;\n hierHighlight(n.id);\n showHierDetail(n);\n });\n });\n\n // Hierarchy highlight helpers\n let hierPinned=null;\n function hierHighlight(nId){\n linkG.selectAll('path')\n .attr('stroke',function(){const s=this.getAttribute('data-source'),t=this.getAttribute('data-target');if(s===nId)return'#58a6ff';if(t===nId)return'#3fb950';return this.getAttribute('stroke-dasharray')?'#1f3d5c':'var(--border)';})\n .attr('stroke-width',function(){const s=this.getAttribute('data-source'),t=this.getAttribute('data-target');return(s===nId||t===nId)?2.5:1;})\n .attr('opacity',function(){const s=this.getAttribute('data-source'),t=this.getAttribute('data-target');return(s===nId||t===nId)?1:0.15;});\n nodeG.selectAll('.hier-node').attr('opacity',function(){\n const id=this.__data_id; if(id===nId)return 1;\n const connected=DATA.links.some(l=>{const s=l.source.id??l.source,t=l.target.id??l.target;return(s===nId&&t===id)||(t===nId&&s===id);});\n return connected?1:0.3;\n });\n }\n function hierResetHighlight(){\n hierPinned=null;\n linkG.selectAll('path')\n .attr('stroke',function(){return this.getAttribute('stroke-dasharray')?'#1f3d5c':'var(--border)';})\n .attr('stroke-width',1).attr('opacity',1);\n nodeG.selectAll('.hier-node').attr('opacity',1);\n }\n function showHierDetail(n){\n const p=document.getElementById('hier-detail');\n document.getElementById('hd-name').textContent=n.id;\n document.getElementById('hd-meta').innerHTML=i('detail.dir')+': '+n.dir+'<br>'+i('detail.dependencies')+': '+n.deps+' \\u00b7 '+i('detail.dependents')+': '+n.dependents;\n document.getElementById('hd-dependents').innerHTML=(n.dependentsList||[]).map(x=>'<li>\\u2190 '+x+'</li>').join('')||'<li style=\"color:var(--text-muted)\">'+i('detail.none')+'</li>';\n document.getElementById('hd-deps').innerHTML=(n.dependencies||[]).map(x=>'<li>\\u2192 '+x+'</li>').join('')||'<li style=\"color:var(--text-muted)\">'+i('detail.none')+'</li>';\n p.classList.add('open');\n }\n window.closeHierDetail=()=>{document.getElementById('hier-detail').classList.remove('open');hierResetHighlight();tooltip.style.display='none';tooltipLocked=false;};\n\n // Click on empty space to deselect\n hSvg.on('click',()=>{closeHierDetail();});\n\n // Hierarchy dir filters\n const hFiltersEl=document.getElementById('hier-filters');\n const hActiveDirs=new Set(DATA.dirs);\n DATA.dirs.forEach(dir=>{\n const pill=document.createElement('div');\n pill.className='filter-pill active';\n pill.innerHTML='<div class=\"pill-dot\" style=\"background:'+dirColor(dir)+'\"></div>'+(dir||'.')+' <span class=\"pill-count\">'+(dirCounts[dir]||0)+'</span>';\n pill.onclick=()=>{\n if(hActiveDirs.has(dir)){hActiveDirs.delete(dir);pill.classList.remove('active');}\n else{hActiveDirs.add(dir);pill.classList.add('active');}\n nodeG.selectAll('.hier-node').attr('opacity',function(){const nId=this.__data_id;return hActiveDirs.has(nodeMap[nId]?.dir)?1:0.1;});\n };\n pill.onmouseenter=()=>{\n nodeG.selectAll('.hier-node').attr('opacity',function(){return this.__data_id&&nodeMap[this.__data_id]?.dir===dir?1:0.1;});\n };\n pill.onmouseleave=()=>{\n nodeG.selectAll('.hier-node').attr('opacity',1);\n };\n hFiltersEl.appendChild(pill);\n });\n\n hSvg.call(hZoom.transform,d3.zoomIdentity.translate(\n Math.max(0,(W-totalW)/2),20\n ).scale(Math.min(1,W/(totalW+40),H/(totalH+40))));\n}\n\n// ═══════════════════════════════════════════════\n// DIFF VIEW\n// ═══════════════════════════════════════════════\nconst DIFF = ${diffData};\nif (DIFF) {\n document.getElementById('diff-tab').style.display = '';\n const addedSet = new Set(DIFF.added||[]);\n const removedSet = new Set(DIFF.removed||[]);\n const modifiedSet = new Set(DIFF.modified||[]);\n const affectedSet = new Set((DIFF.affectedDependents||[]).map(a=>a.file));\n\n let diffBuilt = false;\n function buildDiffView() {\n const dSvg = d3.select('#diff-svg').attr('width', W).attr('height', H);\n const dG = dSvg.append('g');\n const dZoom = d3.zoom().scaleExtent([0.05,10]).on('zoom', e=>dG.attr('transform',e.transform));\n dSvg.call(dZoom);\n\n function diffColor(d) {\n if (addedSet.has(d.id)) return 'var(--green)';\n if (removedSet.has(d.id)) return 'var(--red)';\n if (modifiedSet.has(d.id)) return 'var(--yellow)';\n if (affectedSet.has(d.id)) return 'var(--accent)';\n return '#30363d';\n }\n\n const dDefs = dSvg.append('defs');\n dDefs.append('marker').attr('id','darrow').attr('viewBox','0 -4 8 8')\n .attr('refX',8).attr('refY',0).attr('markerWidth',7).attr('markerHeight',7).attr('orient','auto')\n .append('path').attr('d','M0,-3.5L8,0L0,3.5Z').attr('fill','#30363d');\n\n const dLink = dG.append('g').selectAll('line').data(DATA.links).join('line')\n .attr('stroke','#30363d').attr('stroke-width',1).attr('marker-end','url(#darrow)').attr('opacity',0.3);\n\n const simNodes = DATA.nodes.map(d=>({...d}));\n const simLinks = DATA.links.map(d=>({source:d.source.id??d.source,target:d.target.id??d.target,type:d.type}));\n\n const dNode = dG.append('g').selectAll('g').data(simNodes).join('g').attr('cursor','pointer');\n dNode.append('circle')\n .attr('r', d=>nodeRadius(d)*nodeScale)\n .attr('fill', diffColor)\n .attr('stroke', diffColor).attr('stroke-width', d=>(addedSet.has(d.id)||removedSet.has(d.id)||modifiedSet.has(d.id)||affectedSet.has(d.id))?3:1)\n .attr('opacity', d=>(addedSet.has(d.id)||removedSet.has(d.id)||modifiedSet.has(d.id)||affectedSet.has(d.id))?1:0.2);\n dNode.append('text')\n .text(d=>fileName(d.id).replace(/\\\\.tsx?$/,''))\n .attr('dx', d=>nodeRadius(d)*nodeScale+4).attr('dy',3.5).attr('font-size',11)\n .attr('fill', d=>(addedSet.has(d.id)||removedSet.has(d.id)||modifiedSet.has(d.id)||affectedSet.has(d.id))?'var(--text)':'var(--text-muted)')\n .attr('opacity', d=>(addedSet.has(d.id)||removedSet.has(d.id)||modifiedSet.has(d.id)||affectedSet.has(d.id))?1:0.2)\n .attr('pointer-events','none');\n\n const dSim = d3.forceSimulation(simNodes)\n .force('link', d3.forceLink(simLinks).id(d=>d.id).distance(70).strength(0.25))\n .force('charge', d3.forceManyBody().strength(-150).distanceMax(500))\n .force('center', d3.forceCenter(0,0))\n .force('collision', d3.forceCollide().radius(d=>nodeRadius(d)*nodeScale+4))\n .on('tick', ()=>{\n dLink.each(function(d){\n const dx=d.target.x-d.source.x,dy=d.target.y-d.source.y,dist=Math.sqrt(dx*dx+dy*dy)||1;\n const rT=nodeRadius(d.target)*nodeScale,rS=nodeRadius(d.source)*nodeScale;\n d3.select(this).attr('x1',d.source.x+(dx/dist)*rS).attr('y1',d.source.y+(dy/dist)*rS)\n .attr('x2',d.target.x-(dx/dist)*rT).attr('y2',d.target.y-(dy/dist)*rT);\n });\n dNode.attr('transform', d=>\\`translate(\\${d.x},\\${d.y})\\`);\n });\n\n dNode.on('mouseover',(e,d)=>showTooltip(e,d)).on('mousemove',e=>positionTooltip(e)).on('mouseout',()=>scheduleHideTooltip());\n\n setTimeout(()=>{\n const b=dG.node().getBBox();if(!b.width)return;\n const s=Math.min(W/(b.width+80),H/(b.height+80))*0.9;\n dSvg.call(dZoom.transform,d3.zoomIdentity.translate(W/2-(b.x+b.width/2)*s,H/2-(b.y+b.height/2)*s).scale(s));\n },1500);\n }\n\n // Hook into tab switching\n const origTabHandler = document.querySelectorAll('.tab');\n origTabHandler.forEach(tab=>{\n tab.addEventListener('click',()=>{\n if(tab.dataset.view==='diff-view'&&!diffBuilt){buildDiffView();diffBuilt=true;}\n });\n });\n}\n\n// ═══════════════════════════════════════════════\n// INIT\n// ═══════════════════════════════════════════════\napplyI18n();\n</script>\n</body>\n</html>`;\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAqTC,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAwGZ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["writeFile","mkdir","join","t","graph","join","mkdir","writeFile"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ArchTracker core schema definitions.
|
|
3
|
+
* Snapshot format is versioned for backward compatibility.
|
|
4
|
+
*/
|
|
5
|
+
declare const SCHEMA_VERSION: "1.0";
|
|
6
|
+
/** A single dependency edge: source imports target */
|
|
7
|
+
interface DependencyEdge {
|
|
8
|
+
source: string;
|
|
9
|
+
target: string;
|
|
10
|
+
type: "static" | "dynamic" | "type-only";
|
|
11
|
+
}
|
|
12
|
+
/** A file node in the architecture graph */
|
|
13
|
+
interface FileNode {
|
|
14
|
+
path: string;
|
|
15
|
+
exists: boolean;
|
|
16
|
+
dependencies: string[];
|
|
17
|
+
dependents: string[];
|
|
18
|
+
}
|
|
19
|
+
/** A detected circular dependency */
|
|
20
|
+
interface CircularDependency {
|
|
21
|
+
cycle: string[];
|
|
22
|
+
}
|
|
23
|
+
/** The full dependency graph */
|
|
24
|
+
interface DependencyGraph {
|
|
25
|
+
rootDir: string;
|
|
26
|
+
files: Record<string, FileNode>;
|
|
27
|
+
edges: DependencyEdge[];
|
|
28
|
+
circularDependencies: CircularDependency[];
|
|
29
|
+
totalFiles: number;
|
|
30
|
+
totalEdges: number;
|
|
31
|
+
}
|
|
32
|
+
/** Persisted snapshot with schema version */
|
|
33
|
+
interface ArchSnapshot {
|
|
34
|
+
version: typeof SCHEMA_VERSION;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
rootDir: string;
|
|
37
|
+
graph: DependencyGraph;
|
|
38
|
+
}
|
|
39
|
+
/** Diff result between two snapshots */
|
|
40
|
+
interface ArchDiff {
|
|
41
|
+
added: string[];
|
|
42
|
+
removed: string[];
|
|
43
|
+
modified: string[];
|
|
44
|
+
affectedDependents: Array<{
|
|
45
|
+
file: string;
|
|
46
|
+
reason: string;
|
|
47
|
+
dependsOn: string;
|
|
48
|
+
}>;
|
|
49
|
+
}
|
|
50
|
+
/** Context summary for AI session initialization */
|
|
51
|
+
interface ArchContext {
|
|
52
|
+
validPaths: string[];
|
|
53
|
+
summary: string;
|
|
54
|
+
snapshotExists: boolean;
|
|
55
|
+
snapshotTimestamp?: string;
|
|
56
|
+
keyComponents: Array<{
|
|
57
|
+
path: string;
|
|
58
|
+
dependentCount: number;
|
|
59
|
+
dependencyCount: number;
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Options for the dependency analyzer */
|
|
64
|
+
interface AnalyzeOptions {
|
|
65
|
+
/** Regex patterns to exclude from analysis (e.g. ["node_modules", "\\.test\\.ts$"]) */
|
|
66
|
+
exclude?: string[];
|
|
67
|
+
/** Maximum recursion depth (0 = unlimited) */
|
|
68
|
+
maxDepth?: number;
|
|
69
|
+
/** Path to tsconfig.json (auto-detected if omitted) */
|
|
70
|
+
tsConfigPath?: string;
|
|
71
|
+
/** Include type-only imports (import type {...}) */
|
|
72
|
+
includeTypeOnly?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Analyze project dependencies using dependency-cruiser.
|
|
76
|
+
*
|
|
77
|
+
* dependency-cruiser records module paths relative to CWD.
|
|
78
|
+
* We resolve rootDir to an absolute path, then use it as baseDir
|
|
79
|
+
* so that output paths are relative to rootDir.
|
|
80
|
+
*
|
|
81
|
+
* @param rootDir - Directory to analyze (e.g. "src" or absolute path)
|
|
82
|
+
* @param options - Analysis configuration
|
|
83
|
+
* @returns DependencyGraph with files, edges, and circular dependency warnings
|
|
84
|
+
*/
|
|
85
|
+
declare function analyzeProject(rootDir: string, options?: AnalyzeOptions): Promise<DependencyGraph>;
|
|
86
|
+
/** Custom error class for analyzer failures */
|
|
87
|
+
declare class AnalyzerError extends Error {
|
|
88
|
+
constructor(message: string, options?: ErrorOptions);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate a comprehensive architecture analysis report.
|
|
93
|
+
*
|
|
94
|
+
* Designed for onboarding into existing projects — provides
|
|
95
|
+
* a full overview of dependencies, critical components, circular
|
|
96
|
+
* references, orphan files, coupling hotspots, and directory breakdown.
|
|
97
|
+
*/
|
|
98
|
+
declare function formatAnalysisReport(graph: DependencyGraph, options?: {
|
|
99
|
+
topN?: number;
|
|
100
|
+
}): string;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Save a dependency graph as a versioned snapshot.
|
|
104
|
+
*
|
|
105
|
+
* Creates .archtracker/ directory if it doesn't exist.
|
|
106
|
+
* Writes snapshot.json with schema version and timestamp.
|
|
107
|
+
*/
|
|
108
|
+
declare function saveSnapshot(projectRoot: string, graph: DependencyGraph): Promise<ArchSnapshot>;
|
|
109
|
+
/**
|
|
110
|
+
* Load the most recent snapshot from .archtracker/snapshot.json.
|
|
111
|
+
*
|
|
112
|
+
* Returns null if no snapshot exists.
|
|
113
|
+
* Validates schema structure with Zod for data integrity.
|
|
114
|
+
*/
|
|
115
|
+
declare function loadSnapshot(projectRoot: string): Promise<ArchSnapshot | null>;
|
|
116
|
+
/** Check if .archtracker directory exists */
|
|
117
|
+
declare function hasArchtrackerDir(projectRoot: string): Promise<boolean>;
|
|
118
|
+
/** Custom error class for storage failures */
|
|
119
|
+
declare class StorageError extends Error {
|
|
120
|
+
constructor(message: string, options?: ErrorOptions);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Compute the diff between an old and new dependency graph.
|
|
125
|
+
*
|
|
126
|
+
* Identifies:
|
|
127
|
+
* - Added files (new in the codebase)
|
|
128
|
+
* - Removed files (deleted from the codebase)
|
|
129
|
+
* - Modified files (dependencies changed)
|
|
130
|
+
* - Affected dependents (files that depend on changed/removed files and may need updates)
|
|
131
|
+
*/
|
|
132
|
+
declare function computeDiff(oldGraph: DependencyGraph, newGraph: DependencyGraph): ArchDiff;
|
|
133
|
+
/** Generate a human-readable report from an ArchDiff */
|
|
134
|
+
declare function formatDiffReport(diff: ArchDiff): string;
|
|
135
|
+
|
|
136
|
+
type Locale = "en" | "ja";
|
|
137
|
+
/** Get the current locale */
|
|
138
|
+
declare function getLocale(): Locale;
|
|
139
|
+
/** Set the locale explicitly */
|
|
140
|
+
declare function setLocale(locale: Locale): void;
|
|
141
|
+
/** Get a translated message by key */
|
|
142
|
+
declare function t(key: string, vars?: Record<string, string | number>): string;
|
|
143
|
+
|
|
144
|
+
export { type AnalyzeOptions, AnalyzerError, type ArchContext, type ArchDiff, type ArchSnapshot, type CircularDependency, type DependencyEdge, type DependencyGraph, type FileNode, type Locale, SCHEMA_VERSION, StorageError, analyzeProject, computeDiff, formatAnalysisReport, formatDiffReport, getLocale, hasArchtrackerDir, loadSnapshot, saveSnapshot, setLocale, t };
|