Summary
The cycle_ratio hygiene metric conflates two structurally different things: cross-module import cycles (a real architectural smell) and intra-module call cycles (classes/functions in one file referencing each other — usually benign cohesion). On flat / one-file-per-concern repos this makes cycle_ratio {max: 0.0} fire on almost every domain, rendering drift useless as a CI gate.
Reproduction (two contrasting real repos)
A. Layered repo (package-per-domain) — cycles are REAL.
A FastAPI backend ingested as multi-file domains. cgis_drift flagged:
app.services cycle_ratio 0.075 > 0 ← genuine cross-module circular import
app.api.dependencies cycle_ratio 0.016 > 0 ← genuine circular import in DI providers
These are true smells worth fixing.
B. Flat repo (one file per concern) — cycles are mostly ARTIFACT.
httpx (2106 nodes / 5848 edges, unresolved 9.5% → healthy), each httpx._<module> bound as a domain:
httpx._config cycle_ratio 1.00 (exception/config classes referencing each other in ONE file)
httpx._exceptions cycle_ratio 1.00
httpx._content cycle_ratio 0.96
httpx._decoders cycle_ratio 0.96
httpx._models cycle_ratio 0.85 (Request/Response/Headers methods calling each other)
httpx._utils cycle_ratio 0.74
httpx is a well-architected library — these are intra-file method/class references (cohesion), not circular module dependencies. Result: 8/10 domains went critical for a non-problem, drowning the one meaningful signal (httpx._client 0.41, no cycle).
Why it matters
cycle_ratio is currently a global hard gate. As-is it only behaves sensibly when domains map to multi-file packages. On flat layouts (or any single-module domain) it produces a wall of false criticals, so drift can't be trusted in CI for those repos.
Proposal
Split the metric by layer / scope:
import_cycle_ratio — cycles in the IMPORTS subgraph, restricted to cross-module/file edges. This is the real smell → keep as a hard hygiene gate.
call_cycle_ratio — cycles in the CALLS subgraph. Intra-module method recursion/mutual reference should be excluded or heavily discounted (optionally configurable: intra_module_calls: ignore|warn).
At minimum: exclude same-file (same-module) edges from the cycle computation, and/or report the two ratios separately so the hygiene gate can target import_cycle_ratio only.
Bonus: when a domain resolves to a single file/module, emit a notice (ties into init-ontology FR #174 — such domains probably shouldn't be cycle-gated at module granularity at all).
Related
Summary
The
cycle_ratiohygiene metric conflates two structurally different things: cross-module import cycles (a real architectural smell) and intra-module call cycles (classes/functions in one file referencing each other — usually benign cohesion). On flat / one-file-per-concern repos this makescycle_ratio {max: 0.0}fire on almost every domain, rendering drift useless as a CI gate.Reproduction (two contrasting real repos)
A. Layered repo (package-per-domain) — cycles are REAL.
A FastAPI backend ingested as multi-file domains.
cgis_driftflagged:These are true smells worth fixing.
B. Flat repo (one file per concern) — cycles are mostly ARTIFACT.
httpx(2106 nodes / 5848 edges, unresolved 9.5% → healthy), eachhttpx._<module>bound as a domain:httpx is a well-architected library — these are intra-file method/class references (cohesion), not circular module dependencies. Result: 8/10 domains went
criticalfor a non-problem, drowning the one meaningful signal (httpx._client0.41, no cycle).Why it matters
cycle_ratiois currently a global hard gate. As-is it only behaves sensibly when domains map to multi-file packages. On flat layouts (or any single-module domain) it produces a wall of false criticals, so drift can't be trusted in CI for those repos.Proposal
Split the metric by layer / scope:
import_cycle_ratio— cycles in the IMPORTS subgraph, restricted to cross-module/file edges. This is the real smell → keep as a hard hygiene gate.call_cycle_ratio— cycles in the CALLS subgraph. Intra-module method recursion/mutual reference should be excluded or heavily discounted (optionally configurable:intra_module_calls: ignore|warn).At minimum: exclude same-file (same-module) edges from the cycle computation, and/or report the two ratios separately so the hygiene gate can target
import_cycle_ratioonly.Bonus: when a domain resolves to a single file/module, emit a notice (ties into init-ontology FR #174 — such domains probably shouldn't be cycle-gated at module granularity at all).
Related