Breaking Cycles
Breaking Cycles
Section titled “Breaking Cycles”Circular dependencies are one of the fastest ways to accumulate structural debt. This guide shows a practical workflow to reduce cycles using SCC outputs.
For SCC metric details, see Circular Dependencies.
Step 1: Measure Current State
Section titled “Step 1: Measure Current State”arxo analyze --metric scc --format json > report.jsonInspect top module-level signals:
jq '.results[] | select(.id=="scc") | .data[] | select(.key|test("^scc\\.(cycle_count|max_cycle_size|total_nodes_in_cycles)$"))' report.jsonPrioritize if any of these are non-zero.
Step 2: Locate Specific Cycles
Section titled “Step 2: Locate Specific Cycles”Use cycle microscope payload:
jq '.ui_schemas.scc.scc_details[] | {cycle_id, size:(.nodes|length), nodes, witness_path, risk_score}' report.jsonStart with either:
- largest cycle
- highest
risk_score - cycle containing high-churn files (if
use_git_history: true)
Step 3: Choose a Cut Edge
Section titled “Step 3: Choose a Cut Edge”List module-level cut candidates:
jq '.results[] | select(.id=="scc") | .data[] | select(.key=="scc.cut_candidates")' report.jsonFor function-level call cycles, inspect:
jq '.ui_schemas.scc.issues.categories.cycle_cut_candidates.critical' report.jsonPick lower-risk candidates first (lower call usage or simpler dependency to invert).
Step 4: Apply a Refactoring Pattern
Section titled “Step 4: Apply a Refactoring Pattern”Use one of these patterns:
- Extract shared code into a third module.
- Introduce an interface/abstraction layer.
- Use dependency injection instead of direct construction.
- Move shared type contracts to a separate definitions module.
See Refactoring Patterns for concrete examples.
Step 5: Verify and Gate
Section titled “Step 5: Verify and Gate”Re-run SCC and compare:
arxo analyze --metric sccThen enforce a policy gate so cycles do not regress:
metrics: - id: scc
policy: invariants: - metric: scc.cycle_count op: "==" value: 0 - metric: scc.max_cycle_size op: "==" value: 0For legacy codebases, gate on non-regression first, then tighten thresholds over time.
CI-Friendly Progressive Gate
Section titled “CI-Friendly Progressive Gate”policy: baseline: mode: git ref: origin/main invariants: - metric: scc.max_cycle_size op: "<=" baseline: true - metric: scc.total_nodes_in_cycles op: "<=" baseline: trueThis prevents new cycle debt while existing debt is being paid down incrementally.
Common Pitfalls
Section titled “Common Pitfalls”- Fixing only one edge in a large cycle without re-running analysis.
- Breaking imports but keeping circular call paths.
- Enabling strict zero-cycle policy too early on legacy repos.
- Ignoring high-churn files in cycle triage.