ln-644-dependency-graph-auditor

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Paths: File paths (
shared/
,
references/
,
../ln-*
) are relative to skills repo root. If not found at CWD, locate this SKILL.md directory and go up one level for repo root.
路径: 文件路径(
shared/
references/
../ln-*
)相对于技能仓库根目录。如果在当前工作目录未找到,请定位到SKILL.md所在目录,再向上一级即为仓库根目录。

Dependency Graph Auditor

依赖图谱审计器

L3 Worker that builds and analyzes the module dependency graph to enforce architectural boundaries.
L3 Worker,用于构建并分析模块依赖图谱,以强制执行架构边界。

Purpose & Scope

目标与范围

  • Worker in ln-640 coordinator pipeline - invoked by ln-640-pattern-evolution-auditor
  • Build module dependency graph from import statements (Python, TS/JS, C#, Java)
  • Detect circular dependencies: pairwise (HIGH) + transitive via DFS (CRITICAL)
  • Validate boundary rules: forbidden, allowed, required (per dependency-cruiser pattern)
  • Calculate Robert C. Martin metrics (Ca, Ce, Instability) + Lakos aggregate (CCD, NCCD)
  • Validate Stable Dependencies Principle (SDP)
  • Support baseline/freeze for incremental legacy adoption (per ArchUnit FreezingArchRule)
  • Adaptive: 3-tier architecture detection — custom rules > docs > auto-detect
Out of Scope (owned by other workers):
  • I/O isolation violations (grep-based) -> ln-642-layer-boundary-auditor
  • API contract violations -> ln-643-api-contract-auditor
  • Code duplication -> ln-623-code-principles-auditor
  • ln-640协调器流水线中的Worker - 由ln-640-pattern-evolution-auditor调用
  • 从导入语句(Python、TS/JS、C#、Java)构建模块依赖图谱
  • 检测循环依赖:两两循环(高优先级)+ 基于DFS的传递性循环(严重优先级)
  • 验证边界规则:禁止、允许、必填(遵循dependency-cruiser模式)
  • 计算Robert C. Martin指标(Ca、Ce、不稳定性)+ Lakos聚合指标(CCD、NCCD)
  • 验证稳定依赖原则(SDP)
  • 支持基线/冻结功能,以便在遗留项目中逐步采用(参考ArchUnit FreezingArchRule)
  • 自适应: 三层架构检测 — 自定义规则 > 文档 > 自动检测
超出范围(由其他Worker负责):
  • I/O隔离违规(基于grep)-> ln-642-layer-boundary-auditor
  • API契约违规 -> ln-643-api-contract-auditor
  • 代码重复 -> ln-623-code-principles-auditor

Input (from ln-640)

输入(来自ln-640)

- architecture_path: string    # Path to docs/architecture.md
- codebase_root: string        # Root directory to scan
- architecture_path: string    # 指向docs/architecture.md的路径
- codebase_root: string        # 待扫描的根目录

Domain-aware (optional, from coordinator)

领域感知(可选,来自协调器)

  • 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)
  • domain_mode: "global" | "domain-aware" # 默认值:"global"
  • current_domain: string # 示例:"users"、"billing"(仅在domain-aware模式下有效)
  • scan_path: string # 示例:"src/users/"(仅在domain-aware模式下有效)

Baseline (optional)

基线(可选)

  • update_baseline: boolean # If true, save current state as baseline

**When domain_mode="domain-aware":** Use `scan_path` instead of `codebase_root` for all Grep/Glob operations. Tag all findings with `domain` field.
  • update_baseline: boolean # 如果为true,将当前状态保存为基线

**当domain_mode="domain-aware"时:** 所有Grep/Glob操作使用`scan_path`而非`codebase_root`。为所有检测结果添加`domain`字段标签。

Workflow

工作流程

Phase 1: Discover Architecture (Adaptive)

阶段1:发现架构(自适应)

MANDATORY READ: Load
references/dependency_rules.md
— use 3-Tier Priority Chain, Architecture Presets, Auto-Detection Heuristics.
Architecture detection uses 3-tier priority — explicit config wins over docs, docs win over auto-detection:
undefined
必读: 加载
references/dependency_rules.md
— 使用三层优先级链、架构预设、自动检测启发式规则。
架构检测采用三层优先级 — 显式配置优先于文档,文档优先于自动检测:
undefined

Priority 1: Explicit project config

优先级1:显式项目配置

IF docs/project/dependency_rules.yaml exists: Load custom rules (modules, forbidden, allowed, required) SKIP preset detection
IF docs/project/dependency_rules.yaml 存在: Load custom rules (modules, forbidden, allowed, required) SKIP preset detection

Priority 2: Architecture documentation

优先级2:架构文档

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
ELSE IF 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

优先级3:从目录结构自动检测

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
undefined
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 ELSE IF 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
undefined

Phase 2: Build Dependency Graph

阶段2:构建依赖图谱

MANDATORY READ: Load
references/import_patterns.md
— use Language Detection, Import Grep Patterns, Module Resolution Algorithm, Exclusion Lists.
scan_root = scan_path IF domain_mode == "domain-aware" ELSE codebase_root
必读: 加载
references/import_patterns.md
— 使用Language Detection, Import Grep Patterns, Module Resolution Algorithm, Exclusion Lists.
scan_root = scan_path IF domain_mode == "domain-aware" ELSE codebase_root

Step 1: Detect primary language

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)
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

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)
undefined
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

ELSE IF 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#

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

Java

ELSE IF 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)
undefined

Phase 3: Detect Cycles (ADP)

阶段3:检测循环依赖(ADP)

Per Robert C. Martin (Clean Architecture Ch14): "Allow no cycles in the component dependency graph."
undefined
根据Robert C. Martin的《Clean Architecture》第14章:"Allow no cycles in the component dependency graph."
undefined

Pairwise cycles (A <-> B)

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) })
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) })

Transitive cycles via DFS (A -> B -> C -> A)

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, [])
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]) ELSE IF 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-level cycles (per dependency-cruiser pattern)

folder_graph = collapse_to_folders(graph) Repeat DFS on folder_graph for folder-level cycles

**Cycle-breaking recommendations** (from Clean Architecture Ch14):
1. **DIP** — extract interface in depended-upon module, implement in depending module
2. **Extract Shared Component** — move shared code to new module both depend on
3. **Domain Events / Message Bus** — for cross-domain cycles, decouple via async communication
folder_graph = collapse_to_folders(graph) Repeat DFS on folder_graph for folder-level cycles

**循环打破建议**(来自《Clean Architecture》第14章):
1. **DIP** — extract interface in depended-upon module, implement in depending module
2. **Extract Shared Component** — move shared code to new module both depend on
3. **Domain Events / Message Bus** — for cross-domain cycles, decouple via async communication

Phase 4: Validate Boundary Rules

阶段4:验证边界规则

undefined
undefined

Load rules from Phase 1 discovery

Load rules from Phase 1 discovery

rules = {forbidden: [], allowed: [], required: []}

rules = {forbidden: [], allowed: [], required: []}

Check FORBIDDEN rules

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 })
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)

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" })
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

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 })
undefined
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 })
undefined

Phase 5: Calculate Graph Metrics

阶段5:计算图谱指标

MANDATORY READ: Load
references/graph_metrics.md
— use Metric Definitions, Thresholds per Layer, SDP Algorithm, Lakos Formulas.
undefined
必读: 加载
references/graph_metrics.md
— 使用Metric Definitions, Thresholds per Layer, SDP Algorithm, Lakos Formulas.
undefined

Per-module metrics (Robert C. Martin)

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}
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)

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" })
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)

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}"})
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

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)"})
undefined
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)"})
undefined

Phase 6: Baseline Support

阶段6:基线支持

Inspired by ArchUnit FreezingArchRule — enables incremental adoption in legacy projects.
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"
Inspired by ArchUnit FreezingArchRule — enables incremental adoption in legacy projects.
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"

Phase 7: Score + Return

阶段7:评分 + 返回

MANDATORY READ: Load
shared/references/audit_scoring.md
for unified scoring formula.
penalty = (critical * 2.0) + (high * 1.0) + (medium * 0.5) + (low * 0.2)
score = max(0, 10 - penalty)
Note: When baseline is active, penalty is calculated from
active_findings
only (new violations), not frozen ones.
json
{
  "category": "Dependency Graph",
  "score": 6.5,
  "total_issues": 8,
  "critical": 1, "high": 3, "medium": 3, "low": 1,
  "architecture": {
    "detected": "hybrid",
    "confidence": "MEDIUM",
    "zones": [
      {"path": "src/core/", "preset": "layered"},
      {"path": "src/features/", "preset": "vertical"}
    ]
  },
  "graph_stats": {
    "modules_analyzed": 12,
    "edges": 34,
    "cycles_detected": 2,
    "ccd": 42,
    "nccd": 1.3
  },
  "cycles": [
    {
      "type": "transitive",
      "path": ["auth", "billing", "notify", "auth"],
      "severity": "CRITICAL",
      "fix": "Apply DIP: extract interface in auth, implement in notify"
    }
  ],
  "boundary_violations": [
    {
      "rule_type": "forbidden",
      "from": "domain",
      "to": "infrastructure",
      "file": "domain/user.py:12",
      "severity": "CRITICAL",
      "reason": "Domain must not depend on infrastructure"
    }
  ],
  "sdp_violations": [
    {
      "from": "domain",
      "to": "utils",
      "I_from": 0.2,
      "I_to": 0.8,
      "severity": "HIGH"
    }
  ],
  "metrics": {
    "users": {"Ca": 3, "Ce": 5, "I": 0.625},
    "billing": {"Ca": 1, "Ce": 7, "I": 0.875}
  },
  "baseline": {"new": 3, "resolved": 1, "frozen": 4},
  "findings": [],
  "domain": "users",
  "scan_path": "src/users/"
}
必读: 加载
shared/references/audit_scoring.md
for unified scoring formula.
penalty = (critical * 2.0) + (high * 1.0) + (medium * 0.5) + (low * 0.2)
score = max(0, 10 - penalty)
Note: When baseline is active, penalty is calculated from
active_findings
only (new violations), not frozen ones.
json
{
  "category": "Dependency Graph",
  "score": 6.5,
  "total_issues": 8,
  "critical": 1, "high": 3, "medium": 3, "low": 1,
  "architecture": {
    "detected": "hybrid",
    "confidence": "MEDIUM",
    "zones": [
      {"path": "src/core/", "preset": "layered"},
      {"path": "src/features/", "preset": "vertical"}
    ]
  },
  "graph_stats": {
    "modules_analyzed": 12,
    "edges": 34,
    "cycles_detected": 2,
    "ccd": 42,
    "nccd": 1.3
  },
  "cycles": [
    {
      "type": "transitive",
      "path": ["auth", "billing", "notify", "auth"],
      "severity": "CRITICAL",
      "fix": "Apply DIP: extract interface in auth, implement in notify"
    }
  ],
  "boundary_violations": [
    {
      "rule_type": "forbidden",
      "from": "domain",
      "to": "infrastructure",
      "file": "domain/user.py:12",
      "severity": "CRITICAL",
      "reason": "Domain must not depend on infrastructure"
    }
  ],
  "sdp_violations": [
    {
      "from": "domain",
      "to": "utils",
      "I_from": 0.2,
      "I_to": 0.8,
      "severity": "HIGH"
    }
  ],
  "metrics": {
    "users": {"Ca": 3, "Ce": 5, "I": 0.625},
    "billing": {"Ca": 1, "Ce": 7, "I": 0.875}
  },
  "baseline": {"new": 3, "resolved": 1, "frozen": 4},
  "findings": [],
  "domain": "users",
  "scan_path": "src/users/"
}

Critical Rules

关键规则

  • Adaptive architecture — never assume one style; detect from project structure or docs
  • 3-tier priority — custom rules > architecture.md > auto-detection
  • Hybrid support — projects mix styles; apply different presets per zone
  • Custom = safe mode — if no pattern detected, only check cycles + metrics (no false boundary violations)
  • Internal only — exclude stdlib, third-party from graph (only project modules)
  • Baseline mode — when baseline exists, report only NEW violations
  • Cycle fixes — always provide actionable recommendation (DIP, Extract Shared, Domain Events)
  • File + line — always provide exact import location for violations
  • Adaptive architecture — never assume one style; detect from project structure or docs
  • 3-tier priority — custom rules > architecture.md > auto-detection
  • Hybrid support — projects mix styles; apply different presets per zone
  • Custom = safe mode — if no pattern detected, only check cycles + metrics (no false boundary violations)
  • Internal only — exclude stdlib, third-party from graph (only project modules)
  • Baseline mode — when baseline exists, report only NEW violations
  • Cycle fixes — always provide actionable recommendation (DIP, Extract Shared, Domain Events)
  • File + line — always provide exact import location for violations

Definition of Done

完成标准

  • Architecture discovered (adaptive 3-tier detection applied)
  • Dependency graph built from import statements (internal modules only)
  • Circular dependencies detected (pairwise + transitive DFS + folder-level)
  • Boundary rules validated (forbidden + allowed + required)
  • Metrics calculated (Ca, Ce, I per module + CCD, NCCD aggregate)
  • SDP validated (stable modules not depending on unstable)
  • Baseline applied if exists (only new violations reported)
  • If domain-aware: all Grep/Glob scoped to scan_path, findings tagged with domain
  • Score calculated per audit_scoring.md
  • Result returned to coordinator
  • Architecture discovered (adaptive 3-tier detection applied)
  • Dependency graph built from import statements (internal modules only)
  • Circular dependencies detected (pairwise + transitive DFS + folder-level)
  • Boundary rules validated (forbidden + allowed + required)
  • Metrics calculated (Ca, Ce, I per module + CCD, NCCD aggregate)
  • SDP validated (stable modules not depending on unstable)
  • Baseline applied if exists (only new violations reported)
  • If domain-aware: all Grep/Glob scoped to scan_path, findings tagged with domain
  • Score calculated per audit_scoring.md
  • Result returned to coordinator

Reference Files

参考文件

  • Boundary rules & presets:
    references/dependency_rules.md
  • Metrics & thresholds:
    references/graph_metrics.md
  • Import patterns:
    references/import_patterns.md
  • Scoring algorithm:
    shared/references/audit_scoring.md

Version: 1.0.0 Last Updated: 2026-02-11
  • Boundary rules & presets:
    references/dependency_rules.md
  • Metrics & thresholds:
    references/graph_metrics.md
  • Import patterns:
    references/import_patterns.md
  • Scoring algorithm:
    shared/references/audit_scoring.md

Version: 1.0.0 Last Updated: 2026-02-11