Drift & trends
Architecture erodes one harmless-looking commit at a time. Because every
ovecc index persists a snapshot with 25+ metrics,
the question "is this codebase getting worse?" has a deterministic answer.
drift: base → head, classified
$ ovecc drift
Drift: snapshot:85ee2372… -> snapshot:5ab78093…
Coupling: +14.29%
Trend: worsening
dependencies: 17 -> 22 (5)
circular_dependencies: 0 -> 1 (1)
coupling_density: 0.23 -> 0.27 (0.03)
boundary_violations: 1 -> 2 (1)
security_findings: 7 -> 13 (6)
max_cyclomatic: 3 -> 24 (21)
high_complexity_functions: 0 -> 1 (1)
unused_exports: 8 -> 10 (2)
...
By default drift compares the previous snapshot to the latest; --since <ref>
compares against the snapshot taken at a Git ref instead:
ovecc drift --since main
ovecc drift --since v1.0.0
Two design details that keep the trend honest:
security_findingscounts code findings only. OSV advisories live in their own metric (dependency_advisories), so runningaudit --fetchnever reads as a code-quality regression.- Only metrics that changed are listed — an empty drift is a genuinely quiet period.
history: one metric, every snapshot
Without arguments, history lists everything trendable (26 metrics — the full list
with definitions is in the metrics reference):
$ ovecc history
Trendable metrics (26):
apis
boundary_violations
circular_dependencies
coupling_density
high_complexity_functions
security_findings
unused_exports
...
Run `ovecc history <metric>` to trend one.
With a metric, you get per-snapshot values, deltas, and a sparkline:
$ ovecc history unused_exports
History: unused_exports over 3 snapshot(s) 8 -> 0 (-8)
▇█▁
2026-07-11T09:35:24… d9416a8 8
2026-07-11T09:35:25… 1a67876 10 +2
2026-07-11T09:36:59… e8fa485 0 -10
Each row shows when the snapshot was taken, the commit, the value, and the delta.
--limit N keeps the most recent N snapshots. JSON output keeps raw values for
machines; text output rounds ratios to three decimals.
diff: the raw structural delta
When you want the mechanics behind a drift — which modules and edges actually appeared or vanished:
$ ovecc diff
Architecture diff: snapshot:85ee2372… -> snapshot:5ab78093…
Added modules: 0
Removed modules: 0
Added dependencies: 4
Removed dependencies: 0
Risk: Medium
New dependencies:
core -> services (../services/billing)
routes -> external:child_process (child_process)
routes -> services (../services/orders)
routes -> services (../services/reports)
diff accepts snapshot ids or Git refs as [base] [head], and --fail-on medium|high|any to gate on the diff risk. Intra-module file wiring is excluded —
only architecture-level changes count.
Suggested cadence
| Rhythm | Command | Question |
|---|---|---|
| Every PR | review / gate | What did this change introduce? |
| Every merge to main | ovecc index (in CI) | Keep the snapshot history contiguous |
| Weekly / per release | ovecc drift --since <last-release> | Are we eroding? |
| Retro / planning | ovecc history coupling_density, ovecc hotspots | Where is debt accumulating? |
Snapshots only exist where you run ovecc index. If you want release-over-release
drift, make indexing part of the merge pipeline — it's incremental, so the steady-state
cost is small.