v3/docs/adr/ADR-318-spinner-verbs-surface.md
spinnerVerbs settingsClaude Code exposes a documented spinnerVerbs settings key in ~/.claude/settings.json:
{
"spinnerVerbs": {
"mode": "append",
"verbs": ["Optimizing your prompt", "Auditing for CVEs"]
}
}
Two modes: "replace" (overrides Claude Code's defaults entirely) and "append" (adds ruflo's verbs to the rotation). The verbs must be present participle (-ing form). Only the text is customizable — the animated ASCII/braille character is compiled into Claude Code.
Today, ruflo does not touch this surface. The user's initial ask was to expose ads/guidance in the "✽ Channeling…" area — which turns out to be exactly what spinnerVerbs targets. This ADR wires ruflo into it.
Add a ruflo spinner enable/disable/list/reset subcommand that manages the spinnerVerbs block in ~/.claude/settings.json on the user's behalf. Ships in the same PR as ADR-317.
Default posture: ON for new installs and upgrades (amended after v3.29.x-era design review). Details in §First-run auto-enable below. Reversible in one command; disclosure notification at first render satisfies the ADR-311 informed-consent bar for this surface.
mode: "replace" because that clobbers Claude Code's default verbs (which some users have grown attached to). If a user has already set replace themselves, ruflo spinner enable refuses with a clear message pointing them to disable or manual edit.settings.json to ~/.claude/settings.json.bak-YYYYMMDD-HHMMSS before touching it. ruflo spinner reset restores the most recent backup and removes the ruflo-managed verbs.disable can strip only ruflo verbs without touching user-authored ones. See §Removal invariant.ing (case-insensitive)ruflo spinner enable prints a preview of the pool including at least one Cognitum-tagged verb before asking for confirmation.Because the spinnerVerbs.verbs array is a plain string list with no per-entry metadata, ruflo tags each managed verb with a zero-width marker suffix:
"Consulting Cognitum"
The three ZWJs are visually invisible in every terminal that renders them, take zero display cells, and act as a stable "this is ours" tag no user is likely to type by accident. On disable / reset, ruflo strips only entries containing the marker; user-authored entries are preserved byte-for-byte.
If Claude Code adds real per-entry metadata later, migrate to that immediately. Until then, the ZWJ tag is the least-invasive marker we can use.
src/commands/spinner.ts)ruflo spinner enable [--yes] — one-time disclosure of the pool + confirmation prompt (skippable with --yes), then append the current verb set to settings.json, backing up first. Records consent domain spinner-verbs.ruflo spinner disable — strip ruflo-tagged verbs from settings.json (leaves user-authored ones intact), revokes consent.ruflo spinner list — print the current ruflo pool + which verbs are actually installed in settings.json + Claude Code's default verbs (fetched from spinnerVerbs schema if available; otherwise omitted).ruflo spinner reset — restore the most recent .bak-* snapshot of settings.json and revoke consent. Destructive; requires --yes.consent + settings.json backup
│
▼
ruflo verb pool (v0: baked, v1: served from funnel.ruv.io/v1/messages
with class=spinner-verb)
│
▼
Append marked verbs → ~/.claude/settings.json spinnerVerbs.verbs[]
│
▼
Claude Code renders random verb per spin (its own logic — we don't hook the spin)
Ten verbs, mix of neutral and Cognitum-tagged, all validated:
Neutral:
Cognitum-tagged:
v1 (deferred to Phase 1): fetch verb pool from funnel.ruv.io/v1/messages?class=spinner-verb, use the same message-cache infrastructure as ADR-311. Cache TTL 24h. On failure, keep the last-known-good verb list.
Positive
Negative
disable. Mitigation: verb pool is server-controlled, and validation refuses anything with unexpected unicode.enable confirmation prompt IS a per-user disclosure, and Cognitum-tagged verbs are named in the preview.spinnerVerbs schema in a future version, breaking ruflo's writer. Mitigation: list inspects live settings and reports drift; if schema changes, disable still works (the marker strip is schema-independent).Neutral
Fires from the session-restore hook (see hook-handler.cjs :: firstRunAutoEnableIfEligible). Gates — any TRUE skips:
RUFLO_NO_AUTO_ENABLE truthy (master opt-out — kills both spinner + announcements)RUFLO_NO_AUTO_ENABLE_SPINNER truthy (spinner-only opt-out)CI / GITHUB_ACTIONS truthy~/.ruflo/first-run-enabled.json already existsOn success: detached spawn of ruflo spinner enable --yes, sync marker write, single-line stderr notification naming what changed + how to disable + restart hint. Announcements is DEFAULT OFF and requires RUFLO_AUTO_ENABLE_ANNOUNCEMENTS=1 (see ADR-319) — the split posture reflects the intrusion difference (per-spin flash vs. prominent startup line).
Marker is written even if the enable spawn fails — auto-enable is a "we tried once" contract, not "keep trying until success." Users can run ruflo spinner enable --yes manually.
Ethical bar met by:
settings.json before write (recoverable via .bak-* file)ruflo spinner disable)RUFLO_NO_AUTO_ENABLE, RUFLO_NO_AUTO_ENABLE_SPINNER)Follow-ups (tracked separately, not blocking):
spinner disable should also write the marker file, so a user who disables before first-run auto-fires isn't re-enabled on the next session-restore