- name
- ripwire-quality-bar
- description
- Code QUALITY of what YOU just wrote, before you commit or say 'done', or verifying a cleanup: --quality-delta lists what got WORSE in 10 kinds and exits 2 only when pre-existing code got materially worse; which restructuring a measured shape (humps/deep, a tangle) calls for. Merge safety → change-check. Even a single-line leaf fix runs it.
- allowed-tools
- Bash, Read
# The quality-bar convergence loop
> Routing:
> • PR-submission readiness — tests to run, blast radius, "safe to merge?" → **ripwire-change-check** — run
> `--quality-delta` FIRST, then its `--test-gate`: clean code that runs the wrong tests still regresses.
> • Reusing before you write the code in the first place → **ripwire-reuse-first**.
> • Wide-angle "where does this still look rotten" read across a whole file/subsystem (not a before/after
> delta) → the panel below, or **ripwire-fresh-eyes** for the full six-family breakdown.
> • **You have the measurement and need the FIX** — for your own diff or for a subsystem **ripwire-fresh-eyes**
> just measured → the shape → refactor playbook and the closed fix loop are both on this page, below.
> • **The regression is a MEMORY-layout hypothesis, not one of the 10 quality kinds** — `--lint`'s built-in
> cache-\* pack (8 static data-layout checks) is a normal part of a lint pass; `--field-affinity[=STRUCT]`
> is the deeper struct-level lens once a profile implicates a specific aggregate → **ripwire-perf-target**.
> • Not sure which skill? → **ripwire-router**.
Don't eyeball quality — **measure the delta your change introduced**, with a deterministic oracle, in a
bounded loop. A file that was already complex is not your regression.
Apply the "non-trivial work" trigger to the LOOP, not the check. `--quality-delta` warm-runs in well under a
second — cheaper than deciding by eye whether a fix "counts" as trivial, and that eyeball judgment is
precisely where a leaf-looking edit that quietly changed a signature or added a branch slips through
unmeasured. Run it. What a single-line leaf fix that preserves the signature and adds no branch, symbol,
dependency, or abstraction gets to skip is everything AFTER a clean run: the drill-down table, `--dmm`,
acking, a second round. This skill (and this file) earns its cost when the one-shot delta actually reports
something — a clean `gating="0"` run on a leaf fix is confirmation, not ceremony you were right to skip.
## Before you converge: the wide-angle read — `--quality-panel`
`ripwire <dir> --quality-panel[=strict|default|lenient] --legend=compact` is THE SINGLE COMMAND for "does what I just
touched still look rotten" — one ranked report over **six** evidence families (the four `--ensemble`
joins — `structural`, `lexical`, `confusion`, `historical` — plus `colocation` and `state`; the full
per-family breakdown lives in **ripwire-fresh-eyes**). Point it at the file or symbol you just edited for
a multi-angle second opinion the single `--quality-delta` number can't give you on its own.
**Read it correctly: it is a lens, never a gate.** `--help` says so in the flag's own text and the
contract is enforced in code — `--quality-panel` exits 0 unconditionally, on every preset, on every repo.
It does not compare against a baseline and it cannot fail a commit. The gate for "did MY change make this
WORSE" is Step 3 below (`--quality-delta`) — that is the only pass in this skill (or in ripwire) with an
exit code that means something. Run `--quality-panel` for the wide-angle read, converge with
`--quality-delta`, never the other way round.
Pick the preset by what "rotten" needs to mean right now: `lenient` (all six families, 1 must agree) is a
reading order, roughly a third of any tree; `default` (all six, 2 must agree) is a review list; `strict`
(only the four families measured stable enough to stand behind repeatedly — `historical` and `colocation`
are fixed-size worst-40 cuts over a ranking whose population moves, so both re-shuffle release to release
on code that never changed) is the rung closest to something CI-shaped, but it is still a lens — nothing
here plugs into an exit code the way `--quality-delta` does.
### Read the structural row as a PROFILE — `nest=` alone is a max, and misleads solo
`nest=` reports the single deepest line in a function. One line at depth 9 and a thousand lines at depth 9
report the same number, so `nest=9` cannot tell a **tangled** body from a long **blocked-sequential** one
whose max was set by one inner loop nobody has to hold in their head. Acting on `nest=` alone is how an
agent guard-clauses a dispatch table. `--metrics` (and the structural family's `why=` string in
`--quality-panel` / `--ensemble`) now carry the profile beside the max:
```
<e f="structural" counted="1" why="ccx=724 loc=1244 nest=9 humps=30 deep=308 rrank=1"/>
```
**Every concrete number on this page is ILLUSTRATIVE OF A SHAPE, never a value to expect.** The counters
themselves are under active calibration — an else-clause over-count fix in flight moves `humps=` down by a
large fraction, and `ccx=`/`nest=` with it, on else-heavy functions. What is durable is what you *do* with
the row: `humps=1` vs many, the two ratios below, and the semantics (regions vs lines, absence below the
bar, `deep < humps` legal). Read the row in front of you; never carry a remembered number to it.
- **`humps=`** — how many *maximal control-nesting regions* reach the nesting bar (`bar_nest=` on the panel
root; CodeScene's "bumpy road": a rise above the threshold then a fall). One deep tangle is `1`; repeated
missing abstractions are many. EXACT, not a floor.
- **`deep=`** — how many **LINES** lie inside those regions, read against the `loc=` already on the row. A
disclosed FLOOR (`deep_floor="1"`).
- Both are **absent exactly when `nest <` the bar** — not-deep, never a hidden `0`.
- **`deep` below `humps` is legal output, not a defect.** `deep` counts lines and `humps` counts regions, and
two regions can share a line: a one-line `if(c){x;}else{y;}` at the bar is 2 regions on 1 line. Three
reviewers have read that shape as a bug; it isn't.
Two ratios do the actual discriminating, and you compute them yourself from the row:
| Ratio | High says | Low says |
|---|---|---|
| **`deep/loc`** | **tangled** — the body *sustains* depth, so most of what you read is nested | **blocked-sequential** — a long run of shallow steps (a dispatch table, a switch, a setup block); the max is one inner loop |
| **`deep/humps`** | **few giant tangles** — one region holds depth for a long stretch; the expensive fix | **many tiny touches** — repeated missing abstractions, each hump its own cheap extraction |
Three shapes off this repo's own source — read the *pattern*, not the digits, which move with calibration:
a ~1000-line function at a **low** `deep/loc` (`main`, roughly a tenth) sits beside one at **~2.5×** that
fraction (`buildGraph`) once `loc`/`nest` have declared them equivalent; and a function far too small for
any size bar to fire can carry the **highest** `deep/loc` in the table (`ur_walkTree`, `loc=87`, near half
its body deep in a single hump). The first is blocked-sequential, the second tangled, the third dense —
three different fixes, one indistinguishable `nest=`.
**`locals=`** rides the same row: the count of local-variable declarations, a FLOOR (`locals_floor="1"`),
**C/C++ only** and **absent — never a bare `0`** — for every other language. It measures the working set a
reader must hold at once, which is the thing extraction is actually supposed to shrink; a "split" that leaves
`locals` where it was mostly moved braces.
**`join="deep+untested"`** on a `--quality-panel` row is a **conjunction of two facts the report already
holds** — this row carries `deep=` *and* no indexed test reaches it — annotated, not a seventh family. It
changes nothing: not `fam=`, not `of=`, not the ordering, not which rows appear. It is the pair where a
refactor is most wanted and least safe, so it routes straight to **test first, refactor second** in the
playbook below. It is **suppressed on every row when `tested_scope="0"`**, because on a corpus whose tests
were never crawled "untested" would be a fact about the crawl, not about the code — read `tested_scope=` on
the root before you read the absence of the annotation as good news. `deep_untested=` on the root counts them
across the WHOLE row set, which the `limit=` window does not change.
**The per-file churn caveat.** The `historical` family's `churn=` and `hrank=` are **FILE facts, inherited
verbatim by every symbol in the file** — a symbol in a churny file collects that family without any property
of its own. Discount it accordingly: on a row whose other evidence is thin, `historical` may be saying only
"this file is busy", not "this function is." (`hrank=` is also a *relative* decile cut over this corpus, so
something always fires.)
## The loop
1. **Zero-setup path:** just make your change, then run `ripwire <dir> --quality-delta --legend=compact`
before you call it done — add `--legend=compact` every time you run this in a loop: on a CLEAN report the
legend is nearly the whole payload (2,776 B to 454 B measured on a small fixture, 15,601 B to 8,775 B on
a mid-change repo), the rows are byte-identical either way, and you have already read the dictionary — in a git repo it auto-compares the working tree vs `git HEAD` (`<quality-delta
baseline="git-HEAD">` confirms it), no start-of-task action needed. **Tighter loop on a long change:** run
`ripwire <dir> --quality-baseline` FIRST — **on a clean tree** — to pin an explicit floor (takes
precedence over HEAD) so each edit deltas against the original start, not the last commit. On a tree that
already differs from HEAD the pin **refuses**, naming the gating findings it would have swallowed into the
floor: commit first, or pass `--allow-dirty` to pin anyway, which stamps the absorbed count so every later
report carries `baseline_absorbed="N"` and a green exit beside it reads "clean *since the pin*".
2. **Make your change.**
3. **Measure the delta** — `ripwire <dir> --quality-delta --legend=compact` → only the regressions you
introduced, across the
10 kinds in the table below. Each emits `<r kind="…" sym=… was=… now=…>` (`members=` for duplication).
Test-fixture dirs are exempt from `dead-code`; `short-horizon-churn` ignores your own current edit and
exempts brand-new symbols/markdown/fixtures. Two exemptions are DISCLOSED on the report rather than
silent, and both change how you read a zero: a symbol defined by a self-registering test/benchmark
macro (doctest `TEST_CASE`, gtest `TEST`/`TEST_F`/`TEST_P`, Catch2, Google Benchmark, plus anything in
`.ripwire_config`'s `register_macros`) is never `dead-code` — a static initializer is invisible to a
name-based call graph — and the header's `register-macro-excluded="N"` counts how many were dropped
that way, printed even at 0. A `duplication` row whose members share only a recognized *idiom* — a
scalar threshold ladder, an enum-to-string switch table, a builder chain — and share no domain
identifiers across different scopes is demoted to `sev="minor"` with `idiom="…"` and stops gating.
**The idiom name is there so you can overrule it by reading**: a demotion is a judgement the tool is
showing its work on, not a row it hid. **Watch `verbosity` hardest** — LOC growth is the single
most-measured agent failure mode, the one most likely to hide in an otherwise-clean diff.
**Read the exit code correctly — it is narrower than it looks.** Findings are sorted on three
independent axes, and only one combination gates:
- **acked** — suppressed entirely (counted honestly in `acked="N"`).
- **ORIGIN** — a symbol that EXISTED at the baseline and got worse is *preexisting-worse* (**no**
`origin=` attribute on the row); one that exists only because the code is NEW carries
`origin="new-symbol"`.
- **MATERIALITY** — a small numeric delta is additionally `sev="minor"`.
**`--quality-delta` exits 2 ONLY on preexisting-worse AND major AND unacked** — exactly the
`gating="N"` count in the header. Read `gating=`, not `regressions=`. A real header looks like:
```
<quality-delta baseline="git-HEAD" regressions="0" minor="0" acked="0"
preexisting-worse="0" new-symbol="0" gating="0" at="f0a45e43d">
```
**`origin="new-symbol"` rows are PRINTED but NEVER gate.** They are the debt you are adding — read
them; nothing else will make you. And `--help` is explicit that **exit 0 means "nothing that already
existed got worse", NOT "clean"**: a change that is entirely new code can add unbounded new-symbol
debt and still exit 0. Never report "quality-delta passed" as "no new debt" — open the rows.
Two more contract details worth knowing: clone kinds classify by member set (a group is new-symbol
only if EVERY member is new), and `short-horizon-churn` is preexisting by construction.
**LIMIT:** origin is canonId (`path::scope::name`) identity, so a **RENAMED or MOVED symbol reads as
new** — a genuine regression carried in with a move classifies `new-symbol` and will not gate. If your
diff moves code, the exit code is especially weak evidence; read the rows.
4. **Fix the REAL ones, re-run, converge.** Which fix a row calls for is the **shape → refactor playbook**
below; proving the fix landed is the **closed fix loop** below that. Repeat until clean or the remainder
are conscious trade-offs.
**Record a trade-off instead of re-reading it forever:** `ripwire <dir> --quality-ack="why it's accepted"`
writes the currently-visible findings into `.ripwire_quality_acks` (committable) — later runs suppress
them honestly (`acked="N"`) and a finding REAPPEARS the moment it worsens past its acked size.
**Ack a SUBSET, never the screen.** Bare `--quality-ack` accepts *every* finding currently visible, so
using it to accept one deliberate change silently accepts the rest too — that is how a ratchet turns into
a rubber stamp. Narrow it with `--ack-only=SUBSTR[,SUBSTR]`, which matches a finding's kind, its canonical
id, or its **facet**:
```bash
ripwire <dir> --quality-delta --ack-only=contract-change --quality-ack="arity change required by <fix>"
```
Prefer the facet over the kind when one exists: `api-surface` also covers the never-gating `new-symbol`
rows, so acking by kind can sweep in dozens of findings to accept a handful. `--ack-only=gating` selects
exactly what would exit 2. A pattern matching nothing refuses (exit 1) rather than acking everything.
Whatever you leave unacked stays visible — that is the point; an exit 2 you have explained in a commit
message is worth more than an exit 0 you bought with a blanket ack.
**Sharing the working tree with other sessions? Then `--ack-only` is not enough — add `--scope=`.**
`--quality-delta` compares the *working tree* against HEAD, so in a checkout several agents are editing
at once, every sibling's uncommitted rows land in **your** report. Bare `--quality-ack` there accepts
the whole screen, which writes another session's debt into a committed ledger under *your* reason
string: that is how the ratchet becomes a rubber stamp, and no amount of care reading the report
prevents it. `--scope=GLOB[,GLOB...]` files each finding by its `p=` path:
```bash
ripwire <dir> --quality-delta --legend=compact --scope=src/render,src/render_gl.h # gate on MY subtree only
ripwire <dir> --quality-delta --scope=src/render --quality-ack="deliberate" # …and ack only my rows
```
Rows outside the scope are **still printed**, under an `<out-of-scope>` element with a do-not-ack
banner, and never gate; the header's `scoped-out-gating=` says how many of them *would* have gated, so
a scoped exit 0 means "nothing of mine is broken", never "the tree is clean". The ack cannot write an
out-of-scope row at all, and an `--ack-only=` that names one refuses (exit 1). Each row you write
records `by=<scope>`, so a later run can flag an ack that suppressed a path its author never owned
(`foreign-acks=`). A pattern with no wildcard is a root-anchored path prefix; `*`/`?` match the whole
path, with `*` spanning `/`. A scope that names nothing indexed refuses rather than reporting you clean.
One reserved word: `--scope=diff` is whatever the working tree changed vs the baseline, expanded to
one path per changed indexed file. It is sugar for the **single-writer** case — in the shared tree
this flag exists for, a sibling's edits are "changed" too, so name your own paths there.
5. **Want ONE number instead of a list — `ripwire <dir> --dmm --legend=compact`.** `--quality-delta` says *which* kinds got
worse; it has no scale, so "is this change better than my last one?" has no answer. `--dmm` is that scale:
the Delta Maintainability Model (di Biase, Rastogi, Bruntink & van Deursen, TechDebt 2019; thresholds and
arithmetic from PyDriller's reference implementation) scores the share of the volume your change moved
that landed in — or freed from — risky units.
```
<dmm base="2edbb46c…" target="working-tree" available="1" combine="pooled" size_metric="physical-loc"
dmm="0.436" good="462" bad="597" base_units="4759" target_units="4780">
<p k="size" dmm="0.184" good="65" bad="288" d_low="65" d_high="288"/>…
```
A unit (a function/method definition with a body) is **low risk** iff `loc<=15` (size), `cyclomatic<=5`
(complexity), `params<=2` (interfacing). `good` = low-risk volume **added** plus high-risk volume
**removed**; `bad` = the reverse; `dmm = good/(good+bad)`. **Deleting a god function scores 1.000;
growing one scores 0.000.** The three sub-scores are separately actionable — a low `size` with a healthy
`interfacing` says *split the function*, not *change the signature*.
Three things to carry. **It is a DELTA, never a level:** editing bad code without changing its size,
complexity or parameter count contributes *nothing* — you are not punished for touching a mess, which is
deliberate. **`dmm="UNAVAILABLE"` is not a score of 1.0 or 0.0** — it means `good+bad` was 0 (a rename, a
Ver no GitHub