finishing-a-development-branch
OBobra/superpowers298k近 7 天新增 2,631 个 star
Code QUALITY of what YOU just wrote, before commit or 'done', or a cleanup: --quality-delta lists what got WORSE in 12 kinds, exit 2 if old code got worse (new-symbol rows never gate, except defect-shape format-arity); the fix a measured shape (humps/deep, tangle) calls for. Merge safety → change-check. Even a single-line leaf fix runs it.
Routing: • PR-submission readiness — tests to run, blast radius, "safe to merge?" → ripwire-change-check — run
--quality-deltaFIRST, 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, the closed fix loop and the debt fix loop are all on this page, below. • The regression is a MEMORY-layout hypothesis, not one of the 12 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.
--quality-panelripwire <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.
nest= alone is a max, and misleads solonest= 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").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.)
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".
Make your change.
Measure the delta — ripwire <dir> --quality-delta --legend=compact → only the regressions you
introduced, across the
12 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="N").origin= attribute on the row); one that exists only because the code is NEW carries
origin="new-symbol".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">
What exits 2 (the gating="N" rows, and nothing else): a finding that is major, unacked AND on code
that already existed (preexisting-worse), plus any defect-shape format-arity row on any origin — a
format/argument mismatch is a defect, not debt, so it gates on new code too. Minor-tier findings, acked
findings and every other origin="new-symbol" row are PRINTED but do not gate. The new-symbol rows 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.
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.
A regression-free answer collapses ack rows that no longer apply to stale= + stale_by_kind=;
--quality-delta --show-stale (its stale_next=) prints those <sa> rows when you prune the ledger.
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:
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:
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.
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
literal edit, a comment reflow), i.e. the change is outside what the model measures; the same token can
appear per property. And it never gates (always exit 0): use it to trend — --dmm=REV scores one
commit against its parent and --dmm=A..B scores a range, so a series of commits is a series of numbers.
Thresholds/definitions are the catalog in quality-metrics.md — this is just drill-down + fix:
| Regression | Drill-down | Fix |
|---|---|---|
complexity | --expand=SYM | split the fn · early-return · lift the nested branch out |
verbosity | --expand=SYM | the #1 agent failure mode (below) — cut boilerplate, don't just reformat |
nesting | --expand=SYM · --metrics for the humps=/deep= profile | guard clauses · invert the condition · extract the nested block — but read the profile first: which of those three it is depends on deep/loc and deep/humps (playbook below) |
params | --expand=SYM | bundle related params into a struct, or split the function |
duplication | --clones | reuse the existing body — Rule of Three; wrong abstraction beats two honest copies |
dead-code | — | delete what you orphaned, or wire the caller you forgot |
api-surface (new public symbol) | --callers=SYM | intentional? keep it. Accidental? narrow it (should've been file-local) |
error-masking (empty catch / bare except: pass / swallowed .catch; a broad handler that only logs and never names the error; a sole handler that re-throws it unchanged) | --expand=SYM | handle it, log the error itself, or drop the try — AI code adds these +47% vs human (GitClear 2026). The two widened shapes gate only in Python (measured precision); elsewhere sev="minor" |
short-horizon-churn | --hotspots · git log -p <file> | rewritten again inside 2 weeks (+15% AI) — is the design unsettled? consolidate |
new-clone-of-reused-helper | --clones · --callers=HELPER | call the existing well-reused helper — reuse is declining in AI code (GitClear) |
placeholder (an added stub or TODO: todo!(), NotImplementedException, a "not implemented" throw/panic, a TODO/FIXME naming no issue) | --expand=SYM | finish it, or name the issue that tracks it — never gates, but do not call the work done over it |
defect-shape (defect=: format-arity — a literal format whose {} fields do not match its arguments; utf8-cut — a byte cap plus an ellipsis with no UTF-8 back-off; dedup-first — std::unique keeping the first of a run over a type with a severity field; vacuous-assert — a test script's absence check read off a command whose failure is unchecked) | --expand=SYM | format-arity gates on any origin: match the fields to the arguments. The other three are sev="minor": back off continuation bytes (or call the utf8 helper), sort worst-first before the unique, check the run's exit status before the absence |
These 12 kinds aren't a generic lint list. Ten target an agent-code degradation mode validated at large N
(verbosity, structural erosion, smell rate, contract drift; passing tests ≠ clean design); placeholder is an
honesty check on your own "done"; defect-shape is four defect shapes code review kept finding by hand. Numbers + why the
loop must be continuous, not a one-time prompt → quality-metrics.md.
Read the Fix column as DIRECTION, not a computed answer. None of these 12 kinds has a corpus-derivable
correct replacement — "split the fn" names a move, not a target function shape, and you still judge it. That
is deliberate: complexity, coupling, and colocation don't have a computable right answer the way a naming
CONVENTION does. The one exception in this whole tool is --naming-consistency (→ ripwire-fresh-eyes),
which proposes an actual propose= value because case-style consistency is Tier A — the corpus's own
majority IS the answer, mechanically recombined from the name's own subtokens, no judgment call involved.
Don't expect that anywhere else, and don't invent a "the fix is X" claim here that this tool doesn't itself
compute.
The table above maps a regression kind to a direction. This maps a measured shape to the named refactor and — the part agents skip — that refactor's precondition. Same doctrine as everything else here: these are facts plus options, never verdicts. The tool measures the shape; which option is right is still your call, and "leave it alone" is always on the menu.
| Measured shape | The named fix | Its precondition — check this FIRST |
|---|---|---|
Many shallow humps — humps high, deep/humps small, deep/loc low | Extract each hump. The bumpy-road fix: every region that rises to the bar and falls back is one missing abstraction with its own name. Cheap, mechanical, one hump at a time. | Nothing structural blocks it — but each extraction is a new symbol, so re-run the loop below: extraction that lands as origin="new-symbol" api-surface debt should be file-local, not public. |
One deep tangle — humps=1 (or few) with high deep/loc | Guard-clause inversion, then state extraction: invert the conditions that hold the depth, return early, and lift the sustained region's working set into a named struct or its own function. Expensive and genuinely risky — a rewrite, not a move. | locals= tells you what you're really moving; a big locals means the region's working set, not just its braces, has to travel. Check --callers=SYM/--impact=SYM before starting, and never do it in the same diff as a behavior change. |
Deciding whether an extract-method is mechanical or a rewrite — check ev= before picking a fix off this table | ev= absent (or ev="1") on a cx= row means every region is single-entry/single-exit: extract-method applies mechanically, anywhere. ev>=2 means a jump gave some region a second exit — the same extraction is now the "one deep tangle" row above, not a cheap lift. | ev_why=tag:count (guard-return, loop-escape, goto, ...) names which jumps raised it — a guard-return-heavy row is visibly not a knot. A FLOOR (ev_floor="1"): noreturn calls and macro-hidden exits can only push the true value higher. |
Small AND dense — small loc, but deep is a large fraction of it (roughly half or more), typically in one hump | Read it before you prescribe anything. Numeric kernels, tree walks, and state machines are legitimately dense: the depth is the algorithm. Often the right fix is a comment or a named constant, not a split. | This row is where a metric-driven agent does the most damage. --expand=SYM first. If the density is the algorithm, ack it (--ack-only=) and move on. |
High fan-in AND untested — big in=/amp=, tested="0", or a --quality-panel row carrying join="deep+untested" | Test first, refactor second. The safety net is the fix's precondition, not its follow-up. → ripwire-write-tests (--seams, the tested= lens, --callers=SYM for the outside contract). | Confirm the annotation is real: join= is suppressed entirely at tested_scope="0", so on an uncrawled-test corpus its absence proves nothing. |
Duplication — a --quality-delta duplication / new-clone-of-reused-helper row, or a --clones group | Consolidate through the repo's own exemplar — ripwire <dir> --exemplar="<what this code does>" --legend=compact names the best-in-class instance to converge on (chosen by ROLE, not text similarity), so the survivor matches house patterns instead of being whichever copy you happened to open. | Rule of Three — extract on the third occurrence, not the second; a wrong abstraction is worse than two honest copies. Check type= on the clone group: type="3" members are gapped near-misses and may differ on purpose. |
Churn-flagged, structurally quiet — historical fires with thin other evidence | Probably nothing here. churn=/hrank= are FILE facts inherited by every symbol in the file. | Confirm at the symbol before acting: git log -p <file> or --hotspots --since= to see whether this function is what keeps moving. |
None of these has a corpus-derivable "correct" answer — see the paragraph above the table. The playbook names a move and the condition that makes the move safe; it does not compute a target shape, and any of these rows can honestly end in "measured, understood, left alone."
Fixing without verifying is how a refactor trades one regression for two. Four steps, in this order; each answers a question the previous one cannot:
# 1. make the fix (playbook above)
ripwire <dir> --quality-delta --legend=compact # 2. did the TARGETED kind improve, and did nothing else regress?
ripwire <dir> --edit-check=SYM --legend=compact # 3. is the CONTRACT intact?
ripwire <dir> --affected=F1,F2 --legend=compact # 4. which tests PROVE it? (then run them)
--quality-delta — the only step with a meaningful exit code, and it is doing two jobs here, not
one: the row you were chasing should be gone, and nothing new should have appeared. A "split the
function" fix that drops complexity while adding api-surface + duplication is a lateral move.
Read gating=, but also read the origin="new-symbol" rows — extraction always creates new symbols, and
those rows do not gate (see What exits 2 above), so exit 0 is not the same as "the fix was free."--edit-check=SYM — unchanged / new-symbol / contract-change for the symbol you just edited:
param count and publicness NOW vs git HEAD, plus its 1-hop callers with any call site provably
incompatible with the new arity flagged. A refactor is supposed to be unchanged here; a
contract-change you did not intend is the finding. Cheap enough (~ms warm) that skipping it is never the
economical choice. It refuses (exit 1) if SYM matches several definition sites — a contract is per
definition, so pass the file:name spelling it lists.--affected=F1,F2 (or --affected=SYM) — the test files that transitively reach what you changed.
Metrics improving is not evidence the code still works; this names what to run, and then you run it.
Mid-task, --situ is the same answer over the whole git diff plus co-change partners; at PR time
--test-gate is the gating form (exit 4 when tests-to-run or the untested blast radius is non-empty).Done means: the targeted kind is gone from --quality-delta, nothing else regressed, --edit-check
reports the contract you intended, and the --affected tests pass. Anything short of all four and the fix is
still a hypothesis.
The loop above converges on debt you just added. When the task is debt that was already there ("clean up this module", "fix the worst of it"), run this instead — bounded, one finding at a time:
gating=, then origin="new-symbol"); otherwise row 1 of
ripwire <dir> --quality-panel=strict --legend=compact pointed at the area you were asked about. The panel orders
by how many independent families agree, not by payoff — no validated value × cost ranking ships yet — so between
near-ties take the cheapest: small, tested, few --callers=SYM.join="deep+untested" on the row, or ripwire <dir> --affected=SYM --legend=compact
naming no <test> row that carries hops= (a partner="1" row without hops= is co-change, not reach), means
the refactor has no safety net. Write one against the UNCHANGED code
(→ ripwire-write-tests), see it pass, commit it alone. A test written after the refactor pins the new
behavior, not the old.--lint --lint-select=magic-number finding (C/C++/ObjC) → a named constant. "Leave it alone" is still on the menu.--quality-delta
(working tree vs git HEAD; re-pin after each commit if you pinned --quality-baseline) covers exactly this fix — after the commit it reads regressions="0" trivially
(head_basis="identity"); to check a fix already committed, run ripwire <dir> --quality-delta=HEAD --legend=compact
(that commit vs its parent). Then regressions= must be 0, not just gating=, and acked= must not rise (a
pre-ack hides a row). The one exemption: a kind="short-horizon-churn" row with churn="self" on a function this
session already fixed is the loop's own commits, not a regression — better, take the next row from a different
function. A fix that trades its target for a row of any other kind — a duplication, a params, a public
api-surface helper that should have been file-local — is a lateral move: revert it, don't ack it.
--quality-delta only lists what got worse, so confirm the target moved by re-running the command that ranked it.--quality-delta exits 2 on exactly the What exits 2 rows above — the gating="N" header count — so
a green hook does not mean the diff added no debt: it means nothing that already existed got worse (and no
format/argument mismatch was added).
Non-zero is the hook contract, no wrapper needed: ripwire <dir> --quality-delta --legend=compact || exit 1. If you want CI
to also block on the debt a change ADDS, exit 2 will not do it for you — parse new-symbol="N" from the
header (--json is supported for this verb) and apply your own policy. Chain the
other deterministic gates in the same hook: det-gate (diff <(ripwire <dir>) <(ripwire <dir>), must be
byte-identical) and ripwire <dir> | xmllint --noout - (valid XML) — any non-zero exit blocks the commit.
ripwire measures STRUCTURE (complexity / duplication / reachability), not data flow — it cedes
use-after-move / taint / type / null errors to the compiler. A high amb= symbol can be a dispatch hub, not
a bug. Thresholds are heuristics: trust coupling/churn hardest, complexity as size-correlated (not
independent), and Martin I/A/D/nccd as descriptive only — never proof. Full catalog (definition · why it
predicts defects · evidence tier · the verb) → quality-metrics.md.
OBobra/superpowers298k近 7 天新增 2,631 个 star
OBobra/superpowers298k近 7 天新增 2,631 个 star
OBobra/superpowers298k近 7 天新增 2,631 个 star
MAmattpocock/skills288k近 7 天新增 10,178 个 star
MAmattpocock/skills288k近 7 天新增 10,178 个 star
MAmattpocock/skills288k近 7 天新增 10,178 个 star