ratchet-ops
Use when a policy-check ratchet row is failing, when triaging a blocking config-check regression, or when an improvement just shipped and needs a matching enforcement check landed in the same change (
SKILL.mdskills/ratchet-ops/SKILL.md
GitHubRatchet Ops
A config-ratchet lane converts "someone remembered to run a check" into "a regression cannot ship silently." This skill covers both directions: remediating a failing row and authoring a new one.
30-Second Model
a policy-check runner --tier 3 # the engine: policy-check rows + hook-contract rows + INFO rows
^ a nightly timer runs it with --json --file-issues
v writes a ratchet-run snapshot file
a session-start hook reads the snapshot -> "[Ratchet] FAIL: <ids>" (silent when green)
--file-issues (timer-only) files ONE deduped low-priority issue per failing check
Three check classes — do not confuse them:
| Class | Fails the blocking tier? | Examples |
|---|---|---|
policy-check row (id|op|threshold|counter-fn|desc) |
YES | model-pin, fleet-rot, deferred-dialect, skill-descriptions-budget |
| hook-contract row (a declared-header walk over hook scripts) | YES | a hook-contract validator checking a continueOnBlock-style declared behavior |
| INFO rows (never fail, signal only) | NO | context-floor, orphan-skill, closure-gate, agent-dispatch-census, guard-installs, zero-invocation counters |
Triage Procedure
- Read the snapshot first — do not re-run blind. Load the JSON snapshot your runner writes
and print every row where
level == "error"before touching anything. - Reproduce one row live (fresh evidence, the snapshot may be a day old): invoke the policy-check runner's blocking tier directly, in quiet mode, and capture its exit code.
- Route via your project's own row runbook, if one exists — a doc mapping each row id to what it asserts, where its counter function lives, the fix pattern, and known traps.
- Classify the failure before editing anything:
- Real regression (a rewrite dropped a key, a reference went stale) -> fix the artifact.
- Pre-existing debt the row was landed against (a documented, expected-non-zero baseline) -> work the backlog down or confirm it matches the documented expected state; do NOT "fix" by narrowing the detector.
- Environment artifact (a stale checkout, a missing local path) -> re-run from the primary checkout before concluding anything.
- Verify: re-run step 2, paste the exit code. A fix without the pasted exit code is not done.
- Close the loop: if the timer filed an issue for the row, close it with the commit ref.
Narrowest-Control Ladder (failing or noisy row)
Applies once step 4 above classifies a row as pre-existing debt or a contested check, not a real regression. Work the ladder top-down; stop at the first rung that actually resolves the row. Each step is preceded by an explain step: read the row's own documented rationale (a checks-log, CONTRIBUTING doc, or comment naming the incident it exists to catch) before touching it — "only after you understand it should you change it."
- Fix the regression — the default. If the row is asserting something real and currently false, make it true.
KNOWN_GAPS-style exemption — a documented, visible carve-out array beside the counter function for a genuine exception the row correctly flags but that a separate, deliberate decision defers. Visible in the row's own output, never hidden.- Demote blocking -> INFO — the row keeps measuring and reporting, it just stops failing the blocking tier. Reserved for a row whose signal is still useful but whose current baseline makes blocking premature.
- Delete the row — last resort. Requires an inverted Ledger-closure rationale: state why the incident class the row was landed against can no longer recur (not just "it's noisy now").
Do not skip rungs — narrowing a detector or deleting a row because it is inconvenient, without working the earlier steps first, is the same anti-pattern the Ledger-closure rule exists to prevent (a row deleted without cause can regress silently, same as a row never landed).
Authoring a New Row (Ledger-Closure Rule)
Any improvement recorded as shipped MUST, in the same change, add an enforcement artifact that would fail if it regressed. Pick the mechanism by what the improvement is:
| Improvement shape | Mechanism |
|---|---|
| A config key a hook depends on | a declared-requirement header in the hook script (e.g. # requires-settings: <key>=<value>) |
| A hook that must actually fire | a declared liveness header (e.g. # liveness: event=<E> matcher=<regex> window=<Nd>), or an inline liveness marker for settings-only hooks |
| Any greppable/countable invariant in the repo | a policy-check row + a matching counter function |
| A count worth watching but not gating (operator-judgment calls) | an INFO row — emits data, never fails |
Counter-function contract (every counter function should follow it):
- Echoes a single number; echoes
0and never aborts on a read failure (a broken counter must not take the lane down). - Exclusions live in a bash array RIGHT BESIDE the function (a
KNOWN_GAPS-style array) — data beside code, never a separate config file. A first-run genuine offender the change cannot fix gets a documented array entry + its own triage issue, not a fix-everything-first blocker. - New rows landing against pre-existing debt are EXPECTED to fail on a fresh tree — say so in the row's documentation paragraph, mirroring the pattern used for other known pre-existing-debt rows.
- Day-one grace: liveness-style checks bound their window to
max(cutoff, artifact mtime)so a just-landed hook reads "no data yet," not FAIL.
Blocking vs INFO decision: gate it only when a failure is unambiguously wrong (a dropped key, a banned token, a budget breach). If the right response is an operator decision (archive-vs-keep, dead-weight-vs-break-glass), it is an INFO row — a blocking row there trains people to silence the lane.
Document the row in your project's checks-documentation file: one paragraph — what it asserts, the root-cause incident, exemptions and why.
NEVER
| Never | Why |
|---|---|
| Narrow a detector to make a row pass | Hides a real baseline; the Ledger-closure rule exists because silent drops have shipped before in practice |
| Delete a load-bearing registration to fix a quality/budget row | It can break a dependent system that hard-fails without it — check for downstream dependents first |
| Run the runner's interactive-file-issues flag interactively | Timer-only mode; interactive runs would double-file issues |
| Trim an auto-triggered check's description/trigger text to meet a budget | The description IS its match surface; trim explicit-only entries instead |
| Trim a vendored-external item's description | A re-vendor operation wipes the trim; exclude vendored content from the blocking row and track it separately |
| Treat INFO rows or a "STALE" ratchet status as failures | STALE means the timer has not run recently — fix the timer, not the rows |
Audit detection scripts by grepping exit 1 |
Lexical audits false-positive on arg-parse and markdown-mode exits; verify by executing under a broken precondition |
Verification
Done means: the policy-check runner's blocking tier invoked directly in quiet mode, with the
exit code pasted as 0 (or the specific row's counter pasted at its documented expected value),
plus the checks-documentation paragraph for any new row.