mcpField
L
ln-644-dependency-topology-auditor

ln-644-dependency-topology-auditor

github.com/levnikolaevichテスト・品質58474 回インストール2 日前に更新
リポジトリ

ln-644-dependency-topology-auditor について

モジュール依存関係を可視化し、アーキテクチャ境界を監査するスキルです。循環依存や依存ルール、安定性を検査し、結合度指標の算出と段階導入向けのベースラインに対応します。

主な機能

  1. 01内部依存グラフの構築
  2. 02直接・推移的な循環の検出
  3. 03アーキテクチャ境界ルールの検査
  4. 04結合度・安定性指標の算出
  5. 05安定依存の原則の検証
  6. 06違反ベースラインへの対応

活用シーン

  1. 01モジュール間の循環インポートの発見
  2. 02依存関係とアーキテクチャルールの照合
  3. 03レガシーコードの新規違反の追跡
  4. 04モジュールの結合度と安定性の確認

MCP 対応

循環や結合度指標の確認には hex-graph、ローカルコードと設定の読み取りには hex-line を利用できます。MCP ツールが使えない、または不十分な場合はリポジトリ走査に切り替えます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/levnikolaevich/claude-code-skills --skill ln-644-dependency-topology-auditor

インストール後、Claude Code で/ln-644-dependency-topology-auditorを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
ai_ready_architecture.md
audit_scoring.md
audit_summary_contract.md
audit_worker_core_contract.md
dependency_rules.md
graph_metrics.md
import_patterns.md
mcp_integration_patterns.md
mcp_tool_preferences.md
two_layer_detection.md
references/templates/
audit_worker_report_template.md

詳細

パス: ファイルパス(references/、../ln-*)は、このスキルディレクトリからの相対パスです。

タイプ: L3 ワーカー カテゴリ: 6XX 監査

依存関係トポロジー監査

アーキテクチャの境界を適用するために、モジュールの依存関係グラフを構築して分析する L3 ワーカーです。

目的と範囲

  • インポート文からモジュールの依存関係トポロジーを構築します(Python、TS/JS、C#、Java)
  • 循環依存を検出します:ペア単位(HIGH)+ DFS による推移的な依存(CRITICAL)
  • 境界ルールを検証します:禁止、許可、必須(dependency-cruiser パターンに準拠)
  • Robert C. Martin のメトリクス(Ca、Ce、Instability)と Lakos の集約メトリクス(CCD、NCCD)を算出します
  • 安定依存の原則(SDP)を検証します
  • レガシーコードへの段階的な導入に向けて、ベースライン/凍結をサポートします(ArchUnit FreezingArchRule に準拠)
  • 適応型: 3 層アーキテクチャ検出 — カスタムルール > ドキュメント > 自動検出
  • BREAK_CYCLE、ENFORCE_RULE、または REDUCE_COUPLING を出力します

対象外:

  • I/O 分離違反
  • API コントラクト違反
  • コードの重複

入力

- architecture_path: string    # Path to docs/architecture.md
- codebase_root: string        # Root directory to scan
- output_dir: string           # e.g., ".hex-skills/runtime-artifacts/runs/{run_id}/audit-report"

# Domain-aware (optional)
- domain_mode: "global" | "domain-aware"   # Default: "global"
- current_domain: string                   # e.g., "users", "billing" (only if domain-aware)
- scan_path: string                        # e.g., "src/users/" (only if domain-aware)

# Baseline (optional)
- update_baseline: boolean                 # If true, save current state as baseline

domain_mode="domain-aware" の場合: すべての Grep/Glob 操作で codebase_root の代わりに scan_path を使用します。すべての検出結果に domain フィールドを付与してください。

ワークフロー

必須の読み込み: references/mcp_tool_preferences.md を読み込みます。

検出ポリシー:2 層検出(候補のスキャン、続いてコンテキストの検証)を使用します。検証方法が曖昧な場合に限り、references/two_layer_detection.md を読み込みます。 ツールポリシー:ホストの AGENTS.md が対象範囲外となる、独立したサブエージェントとして実行される場合があります。そのため、ファイルの読み取り、検索、編集には、まず hex-line MCP を使用してください。MCP の動作が不明確な場合に限り、references/mcp_integration_patterns.md を読み込みます。

依存関係のトポロジー、循環、またはアーキテクチャメトリクスが監査の質を大きく高める場合は、まず hex-graph を使用します。利用可能な場合、ローカルのコードや設定の読み取りには、まず hex-line を使用します。MCP が利用できない、未対応、またはインデックス化されていない場合は、組み込みの Read/Grep/Glob/Bash を使用して続行し、レポートにフォールバックしたことを記載します。

フェーズ 1:アーキテクチャの検出(適応型)

必須の読み込み: references/dependency_rules.md を読み込み、3 段階優先チェーン、アーキテクチャプリセット、自動検出ヒューリスティックを使用します。

アーキテクチャの検出では、3 段階の優先順位を使用します。明示的な設定がドキュメントより優先され、ドキュメントは自動検出より優先されます。

# Priority 1: Explicit project config
IF docs/project/dependency_rules.yaml exists:
  Load custom rules (modules, forbidden, allowed, required)
  SKIP preset detection

# Priority 2: Architecture documentation
ELIF docs/architecture.md exists:
  Read Section 4.2 (modules, layers, architecture_type)
  Read Section 6.4 (boundary rules, if defined)
  Map documented layers to presets from dependency_rules.md
  Apply preset rules, override with explicit rules from Section 6.4

# Priority 3: Auto-detection from directory structure
ELSE:
  scan_root = scan_path IF domain_mode == "domain-aware" ELSE codebase_root
  Run structure heuristics:

  signals = {}
  IF Glob("**/domain/**") AND Glob("**/infrastructure/**"):
    signals["clean"] = HIGH
  IF Glob("**/controllers/**") AND Glob("**/services/**") AND Glob("**/repositories/**"):
    signals["layered"] = HIGH
  IF Glob("**/features/*/") with internal structure:
    signals["vertical"] = HIGH
  IF Glob("**/adapters/**") AND Glob("**/ports/**"):
    signals["hexagonal"] = HIGH
  IF Glob("**/views/**") AND Glob("**/models/**"):
    signals["mvc"] = HIGH

  IF len(signals) == 0:
    architecture_mode = "custom"
    confidence = "LOW"
    # Only check cycles + metrics, no boundary presets
  ELIF len(signals) == 1:
    architecture_mode = signals.keys()[0]
    confidence = signals.values()[0]
    Apply matching preset from dependency_rules.md
  ELSE:
    architecture_mode = "hybrid"
    confidence = "MEDIUM"
    # Identify zones, apply different presets per zone (see dependency_rules.md Hybrid section)
    FOR EACH detected_style IN signals:
      zone_path = identify_zone(detected_style)
      zone_preset = load_preset(detected_style)
      zones.append({path: zone_path, preset: zone_preset})
    Add cross-zone rules: inner zones accessible, outer zones forbidden to depend on inner
フェーズ 2:依存関係グラフの構築

必須の読み込み: references/import_patterns.md を読み込み、言語の検出、インポート検索パターン、モジュール解決アルゴリズム、除外リストを使用します。

scan_root = scan_path IF domain_mode == "domain-aware" ELSE codebase_root

# Step 1: Detect primary language
tech_stack = Read(docs/project/tech_stack.md) IF exists
  ELSE detect from file extensions: Glob("**/*.py", "**/*.ts", "**/*.cs", "**/*.java", root=scan_root)

# Step 2: Extract imports per language
FOR EACH source_file IN Glob(language_glob_pattern, root=scan_root):
  imports = []

  # Python
  IF language == "python":
    from_imports = Grep("^from\s+([\w.]+)\s+import", source_file)
    plain_imports = Grep("^import\s+([\w.]+)", source_file)
    imports = from_imports + plain_imports

  # TypeScript / JavaScript
  ELIF language == "typescript" OR language == "javascript":
    es6_imports = Grep("import\s+.*\s+from\s+['\"]([^'\"]+)['\"]", source_file)
    require_imports = Grep("require\(['\"]([^'\"]+)['\"]\)", source_file)
    imports = es6_imports + require_imports

  # C#
  ELIF language == "csharp":
    using_imports = Grep("^using\s+([\w.]+);", source_file)
    imports = using_imports

  # Java
  ELIF language == "java":
    java_imports = Grep("^import\s+([\w.]+);", source_file)
    imports = java_imports

  # Step 3: Filter internal only (per import_patterns.md Exclusion Lists)
  internal_imports = filter_internal(imports, scan_root)

  # Step 4: Resolve to modules
  FOR EACH imp IN internal_imports:
    source_module = resolve_module(source_file, scan_root)
    target_module = resolve_module(imp, scan_root)
    IF source_module != target_module:
      graph[source_module].add(target_module)
フェーズ 3:循環の検出(ADP)

hex-graph による高速化: .hex-skills/codegraph/index.db があるプロジェクトでは、analyze_architecture(verbosity="full") を使用し、返された cycles を調べて、循環をすばやく検出します。これらの循環および結合度のメトリクスはワークスペース内のモジュール単位であるため、単一パッケージのリポジトリでは 1 つのモジュールに集約される場合があります。グラフの出力がパッケージ内の分析には粗すぎる場合は、grep ベースの DFS、またはシンボル/ファイル単位の追跡にフォールバックします。

Robert C. Martin(Clean Architecture Ch14)によると、「コンポーネントの依存関係グラフに循環を許してはならない。」

# Pairwise cycles (A <-> B)
FOR EACH (A, B) WHERE B IN graph[A] AND A IN graph[B]:
  cycles.append({
    type: "pairwise",
    path: [A, B, A],
    severity: "HIGH",
    fix: suggest_cycle_fix(A, B)
  })
  # Layer 2: Test-only dependencies (devDependencies, test imports) -> skip cycle
  # Plugin/extension architecture with documented bidirectional design -> downgrade to LOW

# Transitive cycles via DFS (A -> B -> C -> A)
visited = {}
rec_stack = {}

FUNCTION dfs(node, path):
  visited[node] = true
  rec_stack[node] = true

  FOR EACH neighbor IN graph[node]:
    IF NOT visited[neighbor]:
      dfs(neighbor, path + [node])
    ELIF rec_stack[neighbor]:
      cycle_path = extract_cycle(path + [node], neighbor)
      IF len(cycle_path) > 2:  # Skip pairwise (already detected)
        cycles.append({
          type: "transitive",
          path: cycle_path,
          severity: "CRITICAL",
          fix: suggest_cycle_fix_transitive(cycle_path)
        })

  rec_stack[node] = false

FOR EACH module IN graph:
  IF NOT visited[module]:
    dfs(module, [])

# Folder-level cycles (per dependency-cruiser pattern)
folder_graph = collapse_to_folders(graph)
Repeat DFS on folder_graph for folder-level cycles

循環の解消に向けた推奨事項(Clean Architecture Ch14 より):

  1. DIP — 依存されるモジュールからインターフェースを抽出し、依存するモジュールで実装します
  2. 共有コンポーネントの抽出 — 共有コードを、両方のモジュールが依存する新しいモジュールに移動します
  3. ドメインイベント/メッセージバス — ドメインをまたぐ循環を、非同期通信によって切り離します
フェーズ 4:境界ルールの検証
# Load rules from Phase 1 discovery
# rules = {forbidden: [], allowed: [], required: []}

# Check FORBIDDEN rules
FOR EACH rule IN rules.forbidden:
  FOR EACH edge (source -> target) IN graph:
    IF matches(source, rule.from) AND matches(target, rule.to):
      IF rule.cross AND same_group(source, target):
        CONTINUE  # cross=true means only cross-group violations
      boundary_violations.append({
        rule_type: "forbidden",
        from: source,
        to: target,
        file: get_import_location(source, target),
        severity: rule.severity,
        reason: rule.reason
      })

# Check ALLOWED rules (whitelist mode)
IF rules.allowed.length > 0:
  FOR EACH edge (source -> target) IN graph:
    allowed = false
    FOR EACH rule IN rules.allowed:
      IF matches(source, rule.from) AND matches(target, rule.to):
        allowed = true
        BREAK
    IF NOT allowed:
      boundary_violations.append({
        rule_type: "not_in_allowed",
        from: source,
        to: target,
        file: get_import_location(source, target),
        severity: "MEDIUM",
        reason: "Dependency not in allowed list"
      })

# Check REQUIRED rules
FOR EACH rule IN rules.required:
  FOR EACH module IN graph WHERE matches(module, rule.module):
    has_required = false
    FOR EACH dep IN graph[module]:
      IF matches(dep, rule.must_depend_on):
        has_required = true
        BREAK
    IF NOT has_required:
      boundary_violations.append({
        rule_type: "required_missing",
        module: module,
        missing: rule.must_depend_on,
        severity: "MEDIUM",
        reason: rule.reason
      })
フェーズ 5:グラフ指標の計算

hex-graph による高速化: .hex-skills/codegraph/index.db があるプロジェクトでは、analyze_architecture(verbosity="full") を使用し、返された coupling 指標を調べて Ca/Ce/I を即座に計算します。グラフが利用できない場合は、手動計算に切り替えます。

必ず読み込む: references/graph_metrics.md を読み込みます。Metric Definitions、Thresholds per Layer、SDP Algorithm、Lakos Formulas を使用します。

# Per-module metrics (Robert C. Martin)
FOR EACH module IN graph:
  Ce = len(graph[module])                          # Efferent: outgoing
  Ca = count(m for m in graph if module in graph[m])  # Afferent: incoming
  I = Ce / (Ca + Ce) IF (Ca + Ce) > 0 ELSE 0      # Instability

  metrics[module] = {Ca, Ce, I}

# SDP validation (Stable Dependencies Principle)
FOR EACH edge (A -> B) IN graph:
  IF metrics[A].I < metrics[B].I:
    # Stable module depends on less stable module -- SDP violation
    sdp_violations.append({
      from: A, to: B,
      I_from: metrics[A].I, I_to: metrics[B].I,
      severity: "HIGH"
    })

# Threshold checks (per graph_metrics.md, considering detected layer)
FOR EACH module IN metrics:
  layer = get_layer(module)  # From Phase 1 discovery
  thresholds = get_thresholds(layer)  # From graph_metrics.md

  IF metrics[module].I > thresholds.max_instability:
    findings.append({severity: thresholds.severity, issue: f"{module} instability {I} exceeds {thresholds.max_instability}"})
  IF metrics[module].Ce > thresholds.max_ce:
    findings.append({severity: "MEDIUM", issue: f"{module} efferent coupling {Ce} exceeds {thresholds.max_ce}"})

# Lakos aggregate metrics
CCD = 0
FOR EACH module IN graph:
  DependsOn = count_transitive_deps(module, graph) + 1  # Including self
  CCD += DependsOn

N = len(graph)
CCD_balanced = N * log2(N)  # CCD of balanced binary tree with N nodes
NCCD = CCD / CCD_balanced IF CCD_balanced > 0 ELSE 0

IF NCCD > 1.5:
  findings.append({severity: "MEDIUM", issue: f"Graph complexity (NCCD={NCCD:.2f}) exceeds balanced tree threshold (1.5)"})

カスケードチェーンの拡張: サービスファイル(**/services/**)では、モジュールレベルのグラフを関数レベルまで拡張します。公開関数ごとに副作用チェーンの最長経路を特定します(マーカーは references/ai_ready_architecture.md を参照)。chain_length >= 3 の場合は、cascade_findings に追加します。フェーズ 8 の DATA-EXTENDED JSON では "runtime_cascades" 配列を出力します。重大度:HIGH(4 以上)、MEDIUM(3)。

フェーズ 6:ベースラインのサポート

ArchUnit FreezingArchRule に着想を得ており、レガシープロジェクトで段階的に導入できるようにします。

baseline_path = docs/project/dependency_baseline.json

IF file_exists(baseline_path):
  known = load_json(baseline_path)
  current = serialize_violations(cycles + boundary_violations + sdp_violations)

  new_violations = current - known
  resolved_violations = known - current

  # Report only NEW violations as findings
  active_findings = new_violations
  baseline_info = {new: len(new_violations), resolved: len(resolved_violations), frozen: len(known - resolved_violations)}

  IF input.update_baseline == true:
    save_json(baseline_path, current)

ELSE:
  # First run -- report all
  active_findings = all_violations
  baseline_info = {new: len(all_violations), resolved: 0, frozen: 0}
  # Suggest: output note "Run with update_baseline=true to freeze current violations"
フェーズ 7:スコアの計算

必ず読み込む: references/audit_worker_core_contract.md と references/audit_scoring.md を読み込みます。

penalty = (critical * 2.0) + (high * 1.0) + (medium * 0.5) + (low * 0.2)
score = max(0, 10 - penalty)

注: ベースラインが有効な場合、ペナルティは凍結された違反ではなく、active_findings(新しい違反)のみから計算します。

フェーズ 8:レポートの作成

必ず読み込む: references/templates/audit_worker_report_template.md を読み込みます。

references/audit_summary_contract.md に従って JSON サマリーを書き込みます。マネージドモードでは呼び出し元が runId と summaryArtifactPath の両方を渡します。スタンドアロンモードでは、共有コントラクトに従ってワーカーが実行単位のアーティファクトパスを生成します。

# Build markdown report in memory with:
# - AUDIT-META (standard penalty-based: score, counts)
# - Checks table (cycle_detection, boundary_rules, sdp_validation, metrics_thresholds, baseline_comparison)
# - Findings table (active violations sorted by severity)
# - DATA-EXTENDED: {graph_stats, cycles, boundary_violations, sdp_violations, metrics, baseline}

IF domain_mode == "domain-aware":
  Write to {output_dir}/644-dep-graph-{current_domain}.md
ELSE:
  Write to {output_dir}/644-dep-graph.md
フェーズ 9:サマリーの返却
Report written: .hex-skills/runtime-artifacts/runs/{run_id}/audit-report/644-dep-graph-users.md
Score: 6.5/10 | Issues: 8 (C:1 H:3 M:3 L:1)

重要なルール

すでに読み込まれている references/audit_worker_core_contract.md を適用します。

  • 適応型アーキテクチャ -- 1 つのスタイルを前提にせず、プロジェクトの構成またはドキュメントから検出します
  • 3 段階の優先順位 -- カスタムルール > architecture.md > 自動検出
  • ハイブリッド対応 -- 複数のスタイルが混在するプロジェクトでは、ゾーンごとに異なるプリセットを適用します
  • カスタム=セーフモード -- パターンが検出されない場合は、循環依存と指標のみをチェックします(誤った境界違反は報告しません)
  • 内部のみ -- 標準ライブラリとサードパーティをグラフから除外します(プロジェクトのモジュールのみを対象にします)
  • ベースラインモード -- ベースラインがある場合は、新しい違反のみを報告します
  • 循環依存の修正 -- 常に実行可能な推奨事項を提示します(DIP、共有機能の抽出、ドメインイベント)
  • ファイルと行番号 -- 違反の正確なインポート箇所を必ず提示します
  • 独自の観点: 依存関係のトポロジーのみを監査します。レイヤーの所有権、サービスコントラクト、パッケージの健全性、一般的なコード品質、ランタイム運用は監査しません。
  • 必要な対応: すべての指摘には BREAK_CYCLE、ENFORCE_RULE、または REDUCE_COUPLING を使用します。

完了の定義

すでに読み込まれている references/audit_worker_core_contract.md を適用します。

  • アーキテクチャを特定した(適応型の 3 段階検出を適用)
  • インポート文から依存関係グラフを構築した(内部モジュールのみ)
  • 循環依存を検出した(ペア単位+推移的 DFS+フォルダーレベル)
  • 境界ルールを検証した(禁止+許可+必須)
  • 指標を計算した(モジュールごとの Ca、Ce、I +集計値 CCD、NCCD)
  • SDP を検証した(安定モジュールが不安定なモジュールに依存していない)
  • ベースラインがあれば適用した(新しい違反のみを報告)
  • ドメイン対応の場合:すべての Grep/Glob を scan_path の範囲内に限定し、指摘にドメインのタグを付けた
  • audit_scoring.md に従ってスコアを計算した
  • {output_dir}/644-dep-graph[-{domain}].md にレポートを書き込んだ(単一の Write 呼び出しでアトミックに実行)
  • コントラクトに従ってサマリーを書き込んだ

参照ファイル

  • 境界ルールとプリセット:references/dependency_rules.md
  • 指標としきい値:references/graph_metrics.md
  • インポートパターン:references/import_patterns.md
  • スコアリングアルゴリズム:references/audit_scoring.md

バージョン: 1.0.0 最終更新日: 2026-02-11

コメント