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 Worker **类别:**6XX 审计

依赖拓扑审计器

L3 Worker,用于构建和分析模块依赖图,以强制执行架构边界。

目的与范围

  • 根据导入语句构建模块依赖拓扑(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 操作均使用 scan_path 替代 codebase_root。在所有发现中添加 domain 字段。

工作流

**必须阅读:**加载 references/mcp_tool_preferences.md。

检测策略:采用两层检测(候选扫描,然后上下文验证);仅当验证方法不明确时,才加载 references/two_layer_detection.md。 工具策略:你可能会作为独立子 Agent 运行,此时宿主的 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——使用语言检测、导入 Grep 模式、模块解析算法和排除列表。

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,以即时检测循环。这些循环和耦合指标基于工作区模块级别,因此单包代码库可能会折叠为一个模块。当图输出过于粗略,无法进行包内分析时,可回退到基于 grep 的 DFS 或符号/文件级跟踪。

根据 Robert C. Martin(《Clean Architecture》第 14 章):“组件依赖图中不允许存在循环。”

# 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》第 14 章):

  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" 数组。严重程度:高(4+),中(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。

  • 自适应架构——绝不假定只有一种风格;根据项目结构或文档进行检测
  • 三级优先级——自定义规则 > architecture.md > 自动检测
  • 混合支持——项目可混合多种风格;针对不同区域应用不同预设
  • 自定义 = 安全模式——如果未检测到任何模式,仅检查循环 + 指标(不产生错误的边界违规)
  • 仅限内部——从图中排除标准库和第三方模块(仅包含项目模块)
  • 基线模式——存在基线时,仅报告新的违规
  • 循环修复——始终提供可执行的建议(DIP、提取共享模块、领域事件)
  • 文件 + 行号——始终提供违规的确切导入位置
  • 独特范围: 仅审计依赖拓扑。不审计层级归属、服务契约、包健康状况、通用代码质量或运行时操作。
  • 要求采取行动: 每项发现都使用 BREAK_CYCLE、ENFORCE_RULE 或 REDUCE_COUPLING。

完成定义

应用已加载的 references/audit_worker_core_contract.md。

  • 已发现架构(已应用自适应三级检测)
  • 已根据导入语句构建依赖图(仅包含内部模块)
  • 已检测循环依赖(成对 + 传递 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

评论

支持 Markdown