Skip to main content

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_findings counts code findings only. OSV advisories live in their own metric (dependency_advisories), so running audit --fetch never 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

RhythmCommandQuestion
Every PRreview / gateWhat did this change introduce?
Every merge to mainovecc index (in CI)Keep the snapshot history contiguous
Weekly / per releaseovecc drift --since <last-release>Are we eroding?
Retro / planningovecc history coupling_density, ovecc hotspotsWhere is debt accumulating?
tip

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.