Skip to content
Arxo Arxo

Interpretation

How to interpret cycle detection results, set thresholds, and prioritize fixes. For an overview, see Circular Dependencies.

Circular dependencies create real problems in day-to-day development:

  • Harder to reason about — circular logic is difficult to follow
  • Can’t test in isolation — pulling in one module pulls in the whole cycle
  • Prevents tree-shaking — bundlers can’t eliminate dead code in cycles
  • Blocks incremental builds — changes affect everything in the cycle
  • Increases coupling — all modules in a cycle are tightly bound
  • Slows down builds — compilers can’t parallelize efficiently

Prettier (4,883 files):

# What the team assumed
"Our architecture is pretty clean"
# What Arxo found
scc.max_cycle_size: 56 # 56 files in one giant cycle!
scc.total_nodes_in_cycles: 79 # 79 files stuck in cycles
# The largest cycle
src/standalone.js →
src/cli/prettier-internal.js →
src/cli/utilities.js →
... (52 more files) →
src/index.js →
back to src/standalone.js
# Impact
- 56 files can't be tested independently
- Changes in CLI propagate to standalone and back
- Bundle size is bloated (no tree-shaking possible)
MetricHealthyNeeds attentionWarningCritical
scc.max_cycle_size0 (no cycles)2 (two files importing each other)3–5 files5+ files
scc.total_nodes_in_cycles01–5 files5–10 files10+ files
scc.cycle_count01–2 cycles3–5 cycles5+ cycles
scc.component_countEqual to file count10% below file count20% below30%+ below

Tip: scc.component_count should ideally equal your total module count. If you have 100 modules but scc.component_count = 95, there are 5 cycles merging modules together.

{
"scc": {
"component_count": 1250,
"max_cycle_size": 0,
"cycle_count": 0,
"total_nodes_in_cycles": 0
}
}

Every module has a clear dependency direction. No cycles.

{
"scc": {
"component_count": 980,
"max_cycle_size": 8,
"cycle_count": 5,
"total_nodes_in_cycles": 24
}
}

Multiple cycles present. The largest has 8 files — medium severity. 24 files are affected total.

{
"scc": {
"component_count": 450,
"max_cycle_size": 56,
"cycle_count": 12,
"total_nodes_in_cycles": 150
}
}

Severe problems. A 56-file cycle needs immediate attention.

For each cycle, Arxo shows:

  • Files in the cycle — which modules are involved
  • Internal edges — the dependencies within the cycle
  • Boundary connections — what depends on this cycle, and what it depends on
  • Per-file metrics — centrality, churn, and refactoring priority

When git history is enabled (use_git_history: true), each file in a cycle gets a priority score:

ColumnWhat it meansHow to use it
node_idFile pathIdentifies the file
centralityHow central this file is within the cycle (0–1)High = bottleneck
churnHow often this file changes (total commits)High = frequently modified
hotspot_scorechurn × centralitySort by this — higher = fix first

Example:

{
"node_table": [
{
"node_id": "src/cli/prettier.js",
"centrality": 0.24,
"churn": 87,
"hotspot_score": 20.88 // Fix this first
},
{
"node_id": "src/standalone.js",
"centrality": 0.15,
"churn": 23,
"hotspot_score": 3.45
}
]
}

Arxo suggests which dependency to remove to break each cycle — starting with the cheapest option:

{
"cycle_cut_candidates": [
{
"from": "src/document/builders/join.js",
"to": "src/document/utilities/assert-doc.js",
"call_count": 2,
"note": "Cheapest edge to cut in this cycle"
}
]
}

How to use:

  1. Start with the edge with the lowest call count — it’s the easiest to refactor
  2. The from → to dependency is the one to eliminate
  3. See Fixing Cycles and Refactoring Patterns for how