Measuring C++ Architecture: Coupling, Cycles, LCOM4 RAAG, a platform that quantifies source code dependency graphs to scope AI-assisted refactoring, analyzed two C++ libraries, nlohmann/json and fmt, finding that only 26.4% of 3,354 imports resolved to internal files, and flagged 9 files for instability and one dependency cycle in fmt. The tool uses Tarjan's algorithm for cycle detection and gates instability checks on minimum afferent coupling to avoid false positives. I ‘ve been building RAAG, a platform that turns source code into a quantified dependency graph and uses it to scope AI-assisted refactoring. Last post covered the C++ extraction layer — parsing 579 files in parallel with a std::jthreadpool. This one is about what happens once you have the graph: turning it into an actual judgement about the code's architecture. I ran it against two real, widely-used C++ libraries: nlohmann/json and fmt. Here's the headline output: Files are nodes. An edge runs from the dependent to the dependency — if a.cpp includes b.hpp, the edge is a → b. That direction is the entire basis of everything below it. Reverse it and every metric inverts while the numbers still look plausible. Every import gets checked against the file set RAAG actually parsed: 3,354 imports checked 546 resolved → matched a file in the repository 340 ambiguous → matched several files; closest path wins2,468 external → standard library, third-party, outside the repo 26.4% internal . That number matters more than it looks. RAAG has no compiler include path and no sys.path — it matches import text against the files it parsed, with tie-break rules for the ambiguous cases. Reporting that ratio on every run is the honest thing to do: a graph built from 26% resolved imports and one built from 90% resolved imports describe very different amounts of truth, and a tool that hides the difference is overstating its own accuracy. Two numbers per file: Combined into Instability: I = Ce / Ca + Ce Here’s the part that took real thought to get right. A high instability score is not automatically a problem. An adapter class, a plugin, a CLI entry point — these should score near 1.0. Depending outward while nothing depends on them is exactly their job. If I flagged every unstable file, the report would be mostly noise from code that’s working exactly as designed. Look at the top of the raw instability table from this run: Ca Ce I File-- -- ---- ------------------------------ 1 8 0.89 detail/input/parser.hpp 1 8 0.89 detail/output/serializer.hpp 1 7 0.88 detail/iterators/iter impl.hpp Ca of 1. Almost nothing depends on these. Their instability is high, but it’s irrelevant — nothing breaks if they change, because barely anything relies on them. Compare to what actually triggered an error: ERROR instability detail/input/input adapters.hpp 4 modules depend on this file, but it depends on 5 others I=0.56, limit 0.40 . Ca of 4. Real modules rely on this one, and it’s simultaneously standing on 5 other dependencies. That’s the actual risk — a foundation that shifts under its own weight. So the check is gated on a minimum afferent coupling before instability is even judged. Below that threshold, a high I score isn’t a violation — it’s just an unstable leaf doing its job. 9 files cleared both bars on this run. The worst: input adapters.hpp at I=0.56, four dependents, five dependencies. ERROR dependency-cycle fmt/include/fmt/base.h 3 files depend on each other in a cycle. Three files in fmt mutually depend on one another. None of them can be understood, tested, or changed in isolation — touch one and you're implicitly touching all three. RAAG finds these using strongly connected components rather than enumerating individual cycles. Cycle enumeration is exponential in the worst case, and a densely tangled header set is precisely the worst case — the analysis would hang on exactly the input it's most needed for. Tarjan's algorithm finds every SCC in linear time, and a component of more than one file already tells you everything actionable: this group is tangled, full stop. This is the finding I didn't expect. LCOM4 Methods Class55 61 size padding fmt/format.h LCOM4 — Hitz & Montazeri's cohesion metric — builds a graph where methods are nodes, joined when they share a field or when one calls the other. The score is the number of connected components. A score of 1 means the class is one coherent thing. A score of n means it splits cleanly into n independent groups. size padding scored 55. Sixty-one methods, and the tool's own words for it: This class may be doing 55 separate jobs. The important part isn't the number — it's that LCOM4 names the groups: {