The cycle was a clean 7-edge ring:
preferences → preferences-models → uok/auto-runaway-guard →
detectors/periodic-runner → detectors/crash-loop-classifier →
last-green → experimental → preferences
Three targeted cuts, each chosen for being a real architectural smell:
1. experimental → commands-prefs-wizard: the wizard was just
re-routing the same `serializePreferencesToFrontmatter` import
from preferences-serializer. experimental.js now imports from
preferences-serializer directly. Edge removed.
2. crash-loop-classifier → safety/autonomous-rollback: detection
should not directly trigger action — that couples concerns and
creates the runtime cycle. Switched to a lazy `await import()`
inside `crashLoopGate.execute()` (which is already async). The
call site is unchanged from the caller's perspective; the
runtime module-graph edge is gone. Walker skips dynamic
imports.
3. preferences-models → uok/auto-runaway-guard: preferences-models
only needed 6 runaway-threshold CONSTANTS, but pulling them from
auto-runaway-guard dragged the whole detector/preferences/
experimental subsystem into the preferences-models graph.
Extracted those 6 constants to a new leaf module
uok/runaway-defaults.js. Both preferences-models and the guard
import from there. auto-runaway-guard re-exports the constants
so existing call sites keep working without churn.
Net: 2 cycles → 1 cycle. 29/29 tests pass across the 5 touched
modules (autonomous-rollback, experimental-flags, crash-loop-
classifier detector, auto-runaway-guard, preferences-models).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>