Skip to content
Back to skills

Verify Repair Loop

ASecurity

Use to iterate a change until tests/quality checks pass — bounded run→revise→re-run gated by a numeric threshold, then a judge confirms. Triggers 'iterate to green', 'keep fixing until tests pass'.

  • 10 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 2, 2026
ai-agentspythonrustgophpbashtestinggitapi

Works with

  • terminal
  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 20, 2026

npx -y skills add event4u-app/agent-config --skill verify-repair-loop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Verify Repair Loop?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Verify Repair Loop
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/event4u-app-verify-repair-loop/badge)](https://www.skillsdirectory.com/skills/event4u-app-verify-repair-loop)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
model_tier: inherit
name: verify-repair-loop
description: "Use to iterate a change until tests/quality checks pass — bounded run→revise→re-run gated by a numeric threshold, then a judge confirms. Triggers 'iterate to green', 'keep fixing until tests pass'."
domain: process
workspaces:
  - agent-config-maintainer
packs:
  - meta
---

# verify-repair-loop

> A bounded **generate → run → revise → re-run** cycle whose pass signal is the
> **executed verdict** — the project's real test/quality output, scored against a
> numeric threshold — escalated to a **judge** for qualitative confirmation only
> *after* the numeric gate passes (staged escalation). The loop is the
> conversation, not a process: this loop introduces no resident process of its
> own and no persistent cross-session state. Use
> it to drive a change to green when the verdict source is *runtime execution*,
> not a judge reading a diff.

## When to use

* A change must converge against the project's **test runner / quality tools**
  and you want the agent to iterate until checks pass before asking for review.
* Test/quality coverage is the authoritative signal (high coverage, actionable
  failures) — not subjective craft.
* You want bounded auto-repair with a hard stop, not an open-ended fix loop.

Do NOT use when:

* The verdict is **subjective craft** (naming, architecture, style) or coverage
  is incomplete → use `do-and-judge` in
  [`subagent-orchestration`](../subagent-orchestration/SKILL.md) (judge reads the
  diff and is sovereign).
* You only need a **one-shot** multi-judge review of a finished diff → use
  [`/review-changes`](../../commands/review/changes.md).
* The verdict must come from a **live app** (Playwright against running services)
  → **deferred** (see Scope below); use test/quality verdicts until that trigger
  fires.

## The Iron Law

```
THE NUMERIC VERDICT IS NEVER SOVEREIGN.
TESTS DECIDE WHEN TO ESCALATE — A JUDGE DECIDES WHETHER TO APPLY.
A REVISION THAT BREAKS A PREVIOUSLY-GREEN CHECK STOPS THE LOOP,
EVEN IF THE OVERALL SCORE ROSE.
GENERATOR NEVER APPROVES ITS OWN CHANGE.
```

A loop that optimizes for `pass_count ≥ N` can game the metric — delete tests,
skip assertions, weaken checks — and still hit the threshold. The numeric gate
only decides **when to escalate**; a judge with veto confirms the change is real.

## Procedure

### 1. Freeze the contract (spec fingerprint)

Before the first run, capture what "done" means and snapshot it:

* the task's acceptance criteria / requirement,
* the set of checks that define the verdict (test files + quality commands),
* a **spec fingerprint** = a hash of (requirement text + the check set).

Record the **baseline green set** — which checks pass *before* any edit — so
regressions are detectable. Pick the verdict commands per the project's
[`toolchain-resolver`](../../contexts/execution/toolchain-resolver.md) (PHP / JS-TS
/ Python / Go / Rust), not a hardcoded runner.

### 2. Set the gate

| Knob | Default | Meaning |
|---|---|---|
| `threshold` | all targeted checks green | numeric pass bar, **absolute** (fraction of total), not "± N tests" |
| `allow_regressions` | `false` | a revision breaking a baseline-green check stops the loop |
| `max_attempts` | **3** | hard cap (test feedback is more actionable than diff critique → one more than `do-and-judge`'s 2; bounded by [`autonomous-execution`](../../rules/autonomous-execution.md) N=3) |
| `plateau_window` | 3 | stop if the last *window* scores sit within `tolerance` |
| `tolerance` | small absolute fraction | flake jitter that does **not** count as improvement |

### 3. The loop (multi-turn — the conversation IS the loop)

Each iteration is agent turns, never executing control-flow code:

1. **Read the failure, then revise** — before changing a line, inspect what the
   previous run actually reported: which checks failed, the assertion text, and
   whether this failure signature matches the last one. Analyze the existing
   code path the failing check exercises rather than pattern-matching the error
   string. A revision written without reading the failure is the blind retry
   both the plateau rule below and
   [`autonomous-execution`](../../rules/autonomous-execution.md) exist to stop —
   and on attempt 1 there is no previous run, so this is the baseline green set
   from step 1. Then make the smallest change toward the contract.
2. **Run** — execute the verdict commands; parse the **structured** result
   (counts green/total, which checks failed). This is one tool call, read in
   context.
3. **Score + regression check** — compute the numeric score; compare failures
   against the baseline green set.
   * **Regression** (a baseline-green check now fails) → STOP, hand back with the
     regression named, even if the score rose. No whack-a-mole.
   * **Plateau** (last `plateau_window` scores within `tolerance`) → STOP; surface
     suspected flakiness or a stuck point. Do not thrash.
   * **`attempts == max_attempts`** → STOP, hand back the best envelope.
4. **Tamper check (deterministic, loop-local)** — before the numeric gate is
   allowed to say anything, run the detector over the diff this attempt
   produced and over the recorded verdict:

   ```bash
   ./scripts-run src/scripts/detect_verification_tampering \
       --diff <this attempt's diff> --message <this attempt's message> \
       --verdict <the runner's recorded verdict>
   ```

   It is **fail-closed inside the loop**: outcome `violations` or
   `stale_verdict` STOPS the loop and hands back with the finding's
   `file:line`, whatever the score says. `clean` changes nothing — an
   ordinary passing attempt is unaffected, because the detector reports only
   on shapes the diff introduced.

   The check answers exactly the gap the Iron Law names: `assertion-removed`,
   `assertion-weakened`, `verification-skipped` (warn), `expectation-rewritten`,
   `early-exit-injected`, `test-file-deleted`, `subject-mocked-away`, and
   `stale_verdict` for a runner that finished before the last edit. A change
   that legitimately deletes an obsolete test or relaxes an over-tight
   assertion declares it in the attempt's message —
   `tamper-allow: <detector-id> — <reason>` — scoped to one id, never to all.

   It is **not** a CI gate and does not become one here. The tree's measured
   lesson is that reach, not content, is the scarce thing; a new always-on
   gate is the opposite of that lesson. Promotion owes a measured
   false-positive rate first.

5. **Numeric gate** — score `< threshold` and attempts remain → back to step 1
   (attempts++). Score `≥ threshold`, no regression, tamper check clean →
   **escalate** (step 4 of the stage).

### 4. Judge escalation (only after the numeric gate passes)

Dispatch the judge as a **subagent with fresh context** (the `judge-*` cluster via
[`subagent-orchestration`](../subagent-orchestration/SKILL.md)) that sees **only the
diff + the executed results** — never the generator's reasoning. This is the real
generator ≠ judge separation; single-agent persona-switching is theater.

* judge `apply` → DONE.
* judge `revise` → back to step 3.1 (attempts++; still bounded).
* judge `reject` → STOP, hand back; the approach must change.

### 5. Mid-loop invalidation (user-interrupt-priority)

Re-check the spec fingerprint each iteration. If the user changes the
requirement mid-loop (a new instruction in the conversation), the fingerprint
changes → **abort and hand back** per
[`user-interrupt-priority`](../../rules/user-interrupt-priority.md). Never keep
iterating against a stale contract.

## Scope — what runs, what is deferred

| Verify context | In scope | Why |
|---|---|---|
| Unit / integration tests | ✅ | fast, deterministic, sandboxed — runtime-free |
| Static quality (lint, type-check, format) | ✅ | fast, deterministic, no side effects (auto-fixable failures may bypass the revise step) |
| Live-app Playwright / E2E | ⛔ **deferred** | needs running services (DB, API) = a runtime; trigger = a UI-observable mission output + Playwright wired into a consumer CI |

State lives in the conversation, optionally mirrored to a re-read working file —
**never** a daemon or persistent cross-session store (honors
[`resident-process-governance`](../../docs/contracts/resident-process-governance.md)).

## Validation

Before finalizing, confirm:

1. The numeric gate **escalated to a judge** — it did not apply on its own.
2. No baseline-green check regressed (or the loop stopped and said so).
3. The tamper check ran on the final attempt and returned `clean` — or the loop
   stopped on its finding. A green verdict recorded over a `violations` or
   `stale_verdict` outcome is not a green verdict.
4. The loop stopped at `max_attempts`, a plateau, or a judge `apply` — never
   ran unbounded.
5. The judge saw only the diff + results, dispatched in fresh context.
6. No **unsupervised** resident process introduced — and none of the four
   governance conditions in `resident-process-governance.md` left unmet if a
   supervised one is. (Amended 2026-08-27, ADR-249: this read "No daemon /
   persistent runtime introduced" when a daemon was prohibited outright. The
   check still fires; what it tests moved from existence to governance.)

## Output format

```
verify-repair-loop
Contract:   <one-line requirement> · threshold <X> · allow_regressions false
Attempts:   <k>/<max>  scores: [s1, s2, …]   stop: threshold|plateau|cap|regression|reject
Verdict:    DONE | DONE_WITH_CONCERNS | BLOCKED
run_terminal: success | blocked | exhausted | stagnated
Judge:      <judge skill> → apply|revise|reject
Evidence:   <green/total> · regressions: none | <named checks>
```

Required fields (ordered):

1. **Contract** — the frozen requirement + threshold + `allow_regressions`.
2. **Attempts** — `k/max`, the score history, and the exact stop reason
   (`threshold` / `plateau` / `cap` / `regression` / `reject`).
3. **Verdict** — `DONE`, `DONE_WITH_CONCERNS`, or `BLOCKED`.
4. **run_terminal** — this loop's stop in the vocabulary every other loop in
   this tree reports in. The verdict above is local to this skill; this field is
   the one a reader can aggregate across the continuation hook, the self-fix
   lanes and this loop without a per-surface translation table.

   | stop reason | `run_terminal` | why |
   |---|---|---|
   | `threshold` (judge `apply`) | `success` | the contract is met |
   | `cap` | `exhausted` | the attempt budget ran out — a bigger budget might help |
   | `plateau` | `stagnated` | scores stopped moving — a bigger budget will not help |
   | `regression` | `blocked` | a baseline-green check broke; the loop must not proceed |
   | `reject` | `blocked` | the judge refused the work |

   The value set is `RunTerminalState`, whose members are `success`,
   `clean-no-op`, `blocked`, `approval-required`, `exhausted`, `stagnated` and
   `premise-invalidated`. It is declared once, at
   `src/scripts/_lib/outcome_vocabularies.ts` (`RUN_TERMINAL_STATES`), and
   described in [`terminal-states`](../../agent-src/contexts/execution/terminal-states.md).
   Never emit a value outside that set: a sixth stop reason maps onto an
   existing member or it is not a terminal state.

   `exhausted` and `stagnated` are deliberately distinct. Both mean "stopped
   without a green", and they call for opposite responses — raise the budget
   against the first, change approach against the second. Collapsing them loses
   the only part of the stop a reader can act on.
5. **Judge** — the dispatched judge skill and its `apply` / `revise` /
   `reject` verdict (omit only when the loop stopped before escalation).
6. **Evidence** — final `green/total` and any regressed checks by name.

## Examples

Good — staged, bounded, judge confirms:
```
iter1 84/100 (<90) → revise
iter2 93/100 (≥90, no regression) → escalate → judge apply → DONE
```

Bad — numeric verdict treated as sovereign (rejected by the Iron Law):
```
iter1 91/100 ≥ threshold → "PASS, done"   ✗ no judge escalation; metric may be gamed
iter2 fixes A, breaks B, 92% → "PASS"      ✗ regression ignored
```

## Gotcha

* **Metric gaming** — the agent "passes" by deleting a failing test or
  weakening an assertion. The *numeric* gate still cannot catch this, and that
  is why loop step 3.4 exists: `detect_verification_tampering` decides the
  mechanical shapes deterministically, and the fresh-context judge remains the
  safeguard for everything shape cannot decide. If the diff *removes* checks,
  that is a `reject`, not a `pass`.
* **Phantom verification** — the loop records a green verdict it never re-ran,
  so the exit code describes a tree that no longer exists. The verdict carries
  the runner's own timestamp; one that predates the last edit is
  `stale_verdict`, its own outcome, never a pass.
* **Flake mistaken for a plateau** — a single non-deterministic test flips
  the score and the window-comparison reads it as "no improvement". Re-run
  the suspected check before declaring a plateau; surface flakiness instead
  of thrashing.
* **Score rose, but a regression hid inside it** — fixing 6 checks while
  breaking 2 still raises the total. Without the regression guard the loop
  ships a regression. Always diff failures against the baseline green set,
  not just the aggregate score.
* **Persona-switch theater** — asking the same agent to "now judge what you
  wrote" is not separation. Dispatch the judge as a fresh-context subagent
  that never saw the generator's reasoning.
* **Stale contract** — iterating against a requirement the user changed
  mid-loop. Re-check the spec fingerprint each turn; a change aborts.

## Do NOT

* NEVER let the numeric score apply a change without judge confirmation.
* NEVER record a green verdict over a tamper finding or a stale runner
  timestamp — inside the loop the detector is fail-closed.
* NEVER widen a `tamper-allow` declaration past the one id it names.
* NEVER continue past a regression because the overall score improved.
* NEVER run the live-app Playwright path here — it is deferred (needs a runtime).
* NEVER judge with the generator's own context — dispatch a fresh-context judge.
* NEVER loop past `max_attempts` or a detected plateau.

## See also

* [`subagent-orchestration`](../subagent-orchestration/SKILL.md) — `do-and-judge`
  (judge-sovereign, diff-read) is the sibling; this skill is test-verdict-gated.
* [`/review-changes`](../../commands/review/changes.md) — one-shot multi-judge
  review; wires this loop as an opt-in step.
* [`playwright-testing`](../playwright-testing/SKILL.md),
  [`quality-tools`](../quality-tools/SKILL.md) — verdict sources.
* `src/scripts/detect_verification_tampering.ts` — the loop-local tamper and
  phantom-verification check bound at step 3.4, with its fixture corpus in
  `tests/fixtures/tamper-corpus/` and its polarity proven both ways by
  `--self-test`.
* [`autonomous-execution`](../../rules/autonomous-execution.md) — the N=3 cap.
* [`resident-process-governance`](../../docs/contracts/resident-process-governance.md) — the
  runtime-free constraint this loop honors.
* Optional impact pre-step (when a code-graph is present, ADR-124): run
  `agent-config code-graph affected --since <ref>` to scope which symbols a
  change touches before choosing verdict sources — cited, not duplicated; the
  loop itself is unchanged when no graph exists.

Files in this skill

  • SKILL.md10.5 KB
  • evals/output-schema.yml667 B
  • evals/triggers.json1.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…