feat(agents): wire contract + verify + security into feat/bugfix/hotfix (verify-loops lot 4)

lib/verify-secure-loop.md: shared main-loop include. GATE 1 fresh verifier
(blind, contract from disk) → CONFORME straight to GATE 2, ECARTS loop max
3; GATE 2 fresh security-auditor (MODE gate) → PASS to commit, BLOCK loop
max 3 with re-verify-request-FIRST order invariant. Mute agent never a PASS.

feater.md: STEP 0.7 CONTRACT (proportional, silent on a clear feature) +
STEP 3 VERIFY+SECURE via the include. Nominal = one verifier + one security
dispatch; the loop only costs when it loops.

bugfixer.md: STEP 3.5 CONTRACT fed by the DIAGNOSIS (bug report verbatim +
reproduced-then-gone + regression test criteria) + STEP 5 fresh gates via
the include. Renumbered STEP 5 sub-steps (gates before the commit gate).

hotfixer.md: STEP 1.7 CONTRACT (silent autofill, zero questions) + STEP 3
security gate whose FAILURE REVERTS (git restore to pre-flight SHA + escalate
to /bugfix), never loops — the 1-attempt model preserved. No fresh verifier
at hotfix weight (the smoke-check verifies the trivial contract). Adds the
Agent tool to hotfixer.md + hotfix/SKILL.md for the security dispatch.

lib/tests/loops-light.test.sh: 27 structure locks green, shellcheck clean.
Behavioral pipeline dogfood on a fixture (feat adding a feature WITH a SQLi):
GATE1 CONFORME (feature present, SQLi not a conformity gap — orthogonal
gates) → GATE2 BLOCK(1) (checklist caught the %-interp SQLi semgrep's taint
rules missed) → [fix to parameterized] → re-verify CONFORME (order invariant,
feature intact) → re-scan PASS. Loop converges to green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
This commit is contained in:
Bastien Chanot
2026-07-03 20:47:07 +02:00
co-authored by Claude Opus 4.8
parent 5aa4216409
commit 0f0162dcae
6 changed files with 224 additions and 20 deletions
+25 -3
View File
@@ -100,6 +100,16 @@ RISK: <low/medium — what could go wrong>
- If the fix is significant (>10 lines, multiple files, - If the fix is significant (>10 lines, multiple files,
behavior change): wait for user approval. behavior change): wait for user approval.
## STEP 3.5 — CONTRACT
Run `$HOME/.claude/lib/contract-interview.md` (main loop). The DIAGNOSIS
feeds it: REQUEST verbatim = the bug report as received; ACCEPTANCE CRITERIA
= the symptom reproduced-then-gone + a regression test present and passing;
FILE SCOPE = the FIX PLAN files. Questions stay proportional (a clear,
reproduced bug → zero). It writes the contract to
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`; keep the path for GATE 1
(STEP 5).
## STEP 4 — FIX ## STEP 4 — FIX
**Gitflow aiguillage (before editing):** follow `$HOME/.claude/lib/gitflow-aiguillage.md` **Gitflow aiguillage (before editing):** follow `$HOME/.claude/lib/gitflow-aiguillage.md`
@@ -131,7 +141,19 @@ Apply the fix following the plan:
``` ```
2. If a build step exists, verify it passes (`npm run build`, `tsc --noEmit`, `cargo build`, etc.). 2. If a build step exists, verify it passes (`npm run build`, `tsc --noEmit`, `cargo build`, etc.).
3. Check for regressions in related functionality. 3. Check for regressions in related functionality.
4. **Pre-commit confirmation gate.** Before running `git commit`, present the diff 4. **Fresh gates (verify + secure), bounded loops.** Steps 1-3 are your
dev-side smoke test, NOT the gate. Run the two fresh gates per
`$HOME/.claude/lib/verify-secure-loop.md` with `CONTRACT` = the STEP 3.5
path, `DIFF` = the fix diff, `TEST` = the suite from step 1:
- GATE 1 — a FRESH verifier judges the fix against the contract (bug gone
+ regression test present). CONFORME → GATE 2. ECARTS → fix, re-verify,
max 3 → escalate.
- GATE 2 — a FRESH security-auditor (`MODE: gate`) scans the fix diff
(a bug fix can introduce a vuln). PASS → commit gate. BLOCK → fix,
re-verify request THEN re-scan, max 3 → escalate.
Nominal = one verifier + one security dispatch. Only then the commit gate.
5. **Pre-commit confirmation gate.** Before running `git commit`, present the diff
summary and the proposed message, then wait for approval: summary and the proposed message, then wait for approval:
``` ```
@@ -152,7 +174,7 @@ Apply the fix following the plan:
- `skip` → leave changes uncommitted, exit cleanly. - `skip` → leave changes uncommitted, exit cleanly.
- `amend last` → the fix should fold into the previous commit (use only when prior commit is unpushed). - `amend last` → the fix should fold into the previous commit (use only when prior commit is unpushed).
5. Commit using conventional format (after approval): 6. Commit using conventional format (after approval):
``` ```
fix(<scope>): <root cause description> fix(<scope>): <root cause description>
@@ -161,7 +183,7 @@ Apply the fix following the plan:
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude <noreply@anthropic.com>
``` ```
6. Print summary: 7. Print summary:
``` ```
BUGFIX COMPLETE BUGFIX COMPLETE
BUG : <symptom> BUG : <symptom>
+28 -12
View File
@@ -65,6 +65,17 @@ already constrain or forbid the approach; an LRN may name a gotcha to apply. Emi
MEMORY; feed STEP 1 MINI-PLAN. Inline consumption — reader = planner, no injection. MEMORY; feed STEP 1 MINI-PLAN. Inline consumption — reader = planner, no injection.
`.claude/memory/` absent → guarded no-op (zero overhead on a memory-less repo). `.claude/memory/` absent → guarded no-op (zero overhead on a memory-less repo).
## STEP 0.7 — CONTRACT
Run `$HOME/.claude/lib/contract-interview.md` (main loop — you are it). It
captures the request verbatim, asks 0-3 questions PROPORTIONAL to ambiguity
(a complete request → zero questions, silent), derives testable acceptance
criteria + file scope, and writes the contract to
`.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`. Keep the path — GATE 1
(STEP 3) hands it to a fresh verifier. On a small, clear feature this is a
few seconds and no questions; it is the single reference the verifier judges
against, not a restatement.
## STEP 1 — MINI-PLAN ## STEP 1 — MINI-PLAN
Quick mental model, not a formal plan document: Quick mental model, not a formal plan document:
@@ -101,19 +112,24 @@ Work through the plan:
- Follow existing patterns in the codebase. - Follow existing patterns in the codebase.
- Run tests incrementally as you go. - Run tests incrementally as you go.
## STEP 3 — VERIFY ## STEP 3 — VERIFY + SECURE (fresh gates, bounded loops)
1. Run the full relevant test suite: First, your own pre-check (dev-side, fast): run the relevant test suite /
```bash lint / type-check, and if a dev server is relevant note what to check
# detect and run tests, lint, type-check visually. This is your smoke test, NOT the gate.
```
2. If a dev server is relevant, mention what the user should Then run the two fresh gates per `$HOME/.claude/lib/verify-secure-loop.md`
check visually. with `CONTRACT` = the STEP 0.7 path, `DIFF` = your working-tree diff, `TEST`
3. Quick self-review: scan your diff for obvious issues: = the suite you just ran:
```bash
git diff --stat - GATE 1 — a FRESH verifier judges the diff against the contract (blind, no
git diff self-score of yours counts). CONFORME on the first pass → straight to GATE
``` 2, no loop. ECARTS → fix the named gaps, re-verify, max 3 → escalate.
- GATE 2 — a FRESH security-auditor (`MODE: gate`) scans the diff. PASS →
commit. BLOCK → fix, re-verify the request THEN re-scan, max 3 → escalate.
Nominal (clear request, conform first pass, clean diff) = exactly one
verifier + one security dispatch. The loop only costs when it loops.
## STEP 4 — COMMIT ## STEP 4 — COMMIT
+33 -5
View File
@@ -1,13 +1,15 @@
--- ---
name: hotfixer name: hotfixer
description: Quick fix for superficial bugs (typos, CSS issues, config errors, off-by-one, wrong variable name, missing import, broken link). Max 2 files, obvious root cause only. description: Quick fix for superficial bugs (typos, CSS issues, config errors, off-by-one, wrong variable name, missing import, broken link). Max 2 files, obvious root cause only.
tools: Read, Edit, Write, Bash, Grep, Glob tools: Read, Edit, Write, Bash, Grep, Glob, Agent
--- ---
# HOTFIX — Quick Superficial Fix # HOTFIX — Quick Superficial Fix
Fast-track fix for obvious bugs. No planning overhead, no plugin Fast-track fix for obvious bugs. No planning overhead, no plugin check.
check, no subagents. Get in, fix, verify, get out. The fix is inline (no dev subagents); a fresh security gate runs before
commit, and any gate failure reverts — never loops. Get in, fix, gate,
get out.
## REQUEST ## REQUEST
$ARGUMENTS $ARGUMENTS
@@ -39,6 +41,18 @@ skip). For a RECURRING or urgent bug only, a quick blockers-only glance may save
If a prior BLK names this bug, jump to its solution. Not mandatory; no RELATED MEMORY If a prior BLK names this bug, jump to its solution. Not mandatory; no RELATED MEMORY
disposition required at hotfix weight. disposition required at hotfix weight.
## STEP 1.7 — CONTRACT (silent autofill)
Run `$HOME/.claude/lib/contract-interview.md` at hotfix weight: **zero
questions ever** (a hotfix is an obvious fix by definition). Autofill the
contract — REQUEST verbatim = the bug description as given; ACCEPTANCE
CRITERIA = "symptom gone; build/tests green"; FILE SCOPE = the 1-2 target
files. It writes `.claude/tasks/contracts/<date>-<slug>-<HHMM>.md`. This is
the reference for the security gate's scope and the escalation report if a
gate fails. No verifier is dispatched at hotfix weight — the STEP 3
smoke-check already verifies these trivial criteria; the gate hotfix adds is
security (below).
## STEP 1.5 — DESIGN GATE ## STEP 1.5 — DESIGN GATE
Follow `$HOME/.claude/lib/design-gate.md`: Follow `$HOME/.claude/lib/design-gate.md`:
@@ -102,18 +116,32 @@ Apply the minimal change that fixes the bug:
(Files were not yet staged — restore is safe.) (Files were not yet staged — restore is safe.)
- STOP and tell user: `"Hotfix introduced a regression. Reverted. Escalate to /bugfix or /analyze for deeper investigation."` - STOP and tell user: `"Hotfix introduced a regression. Reverted. Escalate to /bugfix or /analyze for deeper investigation."`
- Do NOT commit a broken fix. - Do NOT commit a broken fix.
3. Commit using conventional format (only after verify passes): 3. **Security gate (fresh auditor) — failure REVERTS, never loops.** Dispatch
a FRESH security-auditor (`subagent_type: security-auditor`, or load
`agents/security-auditor.md`) with `MODE: gate`, `SCOPE:` the working-tree
diff vs the pre-flight SHA. Parse its `SECURITY — VERDICT:` line:
- `PASS` (or `DEGRADED` with no BLOCK) → proceed to commit.
- `BLOCK(n)` → this is hotfix: do NOT loop. Run `git restore .` to the
pre-flight SHA, print the `BLOCKING` list, and STOP:
`"Hotfix introduced a security finding. Reverted. Escalate to /bugfix
for a fix under the full verify+security loop."` The hotfix model is
one attempt; any gate failure (smoke OR security) reverts and escalates.
- Structural failure (mute / unparsable / no VERDICT line) → treat as a
failed gate: retry ONCE fresh; a 2nd structural failure → revert +
escalate. A mute auditor is never a PASS.
4. Commit using conventional format (only after verify AND security pass):
``` ```
fix(<scope>): <what was wrong> fix(<scope>): <what was wrong>
Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Claude <noreply@anthropic.com>
``` ```
4. Print summary: 5. Print summary:
``` ```
HOTFIX APPLIED HOTFIX APPLIED
FILE(S) : <changed files> FILE(S) : <changed files>
FIX : <one-line description> FIX : <one-line description>
VERIFIED: <test name or smoke check that passed> VERIFIED: <test name or smoke check that passed>
SECURITY: <PASS | DEGRADED (checklist only)>
``` ```
## STEP 4 — DOC SYNC (automatic) ## STEP 4 — DOC SYNC (automatic)
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# ============================================================
# Structure locks — light-flow wiring (verify-loops lot 4)
# feat/bugfix get contract + fresh verifier + security gate
# (bounded 3x); hotfix gets contract + security gate whose
# FAILURE REVERTS (never loops). Locks the load-bearing clauses.
# ============================================================
set -u
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
INC="$REPO/lib/verify-secure-loop.md"
FEA="$REPO/agents/feater.md"
BUG="$REPO/agents/bugfixer.md"
HOT="$REPO/agents/hotfixer.md"
HSK="$REPO/skills/hotfix/SKILL.md"
PASS=0; FAIL=0
tf() { # tf <label> <file> <fixed-string>
if grep -qF -- "$3" "$2" 2>/dev/null; then
echo " PASS $1"; PASS=$((PASS+1))
else
echo " FAIL $1 — missing: $3"; FAIL=$((FAIL+1))
fi
}
tr_() { # tr_ <label> <file> <ERE>
if grep -qE -- "$3" "$2" 2>/dev/null; then
echo " PASS $1"; PASS=$((PASS+1))
else
echo " FAIL $1 — no match: $3"; FAIL=$((FAIL+1))
fi
}
echo "── verify-secure-loop.md (shared include) ──"
if [ -f "$INC" ]; then echo " PASS include exists"; PASS=$((PASS+1)); else echo " FAIL include missing"; FAIL=$((FAIL+1)); fi
tf "gate1 fresh verifier" "$INC" "GATE 1 — REQUEST CONFORMITY (fresh verifier)"
tf "gate2 fresh auditor" "$INC" "GATE 2 — SECURITY (fresh security-auditor)"
tf "blind — no dev summary" "$INC" "Never pass the dev's summary"
tf "conforme first pass no loop" "$INC" "First-pass conforme = no loop"
tf "conformity max 3" "$INC" "Max 3 conformity iterations"
tf "security max 3" "$INC" "Max 3 security iterations"
tf "reverify request first" "$INC" "re-verify the REQUEST first"
tf "order invariant" "$INC" "always re-checked BEFORE security"
tf "mute never a pass (verify)" "$INC" "NEVER a PASS"
tf "nominal cheap stated" "$INC" "one verifier dispatch + one security dispatch"
echo "── feater.md (feat wiring) ──"
tf "feat contract step" "$FEA" "STEP 0.7 — CONTRACT"
tf "feat contract-interview" "$FEA" "lib/contract-interview.md"
tf "feat verify+secure step" "$FEA" "STEP 3 — VERIFY + SECURE"
tf "feat uses shared include" "$FEA" "lib/verify-secure-loop.md"
tf "feat nominal 1+1 dispatch" "$FEA" "verifier + one security dispatch"
echo "── bugfixer.md (bugfix wiring) ──"
tf "bug contract step" "$BUG" "STEP 3.5 — CONTRACT"
tf "bug diagnosis feeds it" "$BUG" "feeds it: REQUEST verbatim"
tf "bug fresh gates" "$BUG" "Fresh gates (verify + secure)"
tf "bug uses shared include" "$BUG" "lib/verify-secure-loop.md"
echo "── hotfixer.md (hotfix wiring — revert, not loop) ──"
tr_ "hotfix has Agent tool" "$HOT" "^tools:.*Agent"
tf "hotfix silent contract" "$HOT" "STEP 1.7 — CONTRACT (silent autofill)"
tf "hotfix zero questions" "$HOT" "questions ever"
tf "hotfix security gate" "$HOT" "Security gate (fresh auditor)"
tf "hotfix block reverts" "$HOT" "failure REVERTS, never loops"
tf "hotfix no verifier" "$HOT" "No verifier is dispatched at hotfix weight"
tf "hotfix skill has Agent" "$HSK" " - Agent"
echo ""
echo "loops-light structure locks: $PASS pass, $FAIL fail"
[ "$FAIL" -eq 0 ]
+67
View File
@@ -0,0 +1,67 @@
# Verify + secure loop — shared orchestrator include (feat, bugfix)
Runs in the ORCHESTRATOR MAIN LOOP after the dev step completes. Turns a
finished diff into a verified, security-cleared change through two fresh
gates and bounded loops. The dev stays inline (LRN-083: subagents =
execution + report; loop decisions live here, in the main loop).
Inputs the caller must have ready:
- `CONTRACT`: path to the contract file written by `contract-interview.md`.
- `DIFF`: the range/file-list the dev just produced (e.g. `HEAD` vs the
pre-dev SHA, or the working-tree diff before commit).
- `TEST`: the project test command, if known.
Nominal path is cheap: one verifier dispatch + one security dispatch, done.
The loop only costs more when it actually loops.
## GATE 1 — REQUEST CONFORMITY (fresh verifier)
Dispatch a FRESH verifier subagent (`subagent_type: verifier`, or load
`agents/verifier.md`). Pass ONLY: the `CONTRACT` path, the `DIFF` range, the
`TEST` command. Never pass the dev's summary, never pass a prior iteration's
gaps — the verifier reads the contract from disk and judges blind.
Parse its single `VERIFY — VERDICT:` line:
- `CONFORME` → go to GATE 2. (First-pass conforme = no loop.)
- `ECARTS(n)` → hand the dev the CONTRACT path + the exact `CRITERIA` gap
lines (NOT-MET / out-of-scope), nothing else. Dev fixes inline, then
re-dispatch a FRESH verifier. Repeat. **Max 3 conformity iterations** →
STOP + human escalation with the CRITERIA table (the contract-vs-realized
diff).
- Remaining `UNVERIFIABLE` while all else MET → direct human gate (a dev
cannot fix unverifiability); do not spend a loop on it.
- Out-of-scope files: a dev justification is accepted ONLY through the human
micro-gate that appends `[gated <date>]` to the contract's FILE SCOPE;
otherwise the dev removes the file.
- Structural failure (`ERROR(…)`, missing/duplicated VERDICT line,
unparsable, crash, `CONFORME` without `PROOF`) → retry ONCE with a fresh
verifier; a 2nd structural failure → human escalation. A mute verifier is
NEVER a PASS.
## GATE 2 — SECURITY (fresh security-auditor)
Only after GATE 1 is `CONFORME`. Dispatch a FRESH security-auditor
(`subagent_type: security-auditor`, or load `agents/security-auditor.md`)
with `MODE: gate`, `SCOPE: <DIFF>`. No report path (gate mode is
stdout-only, no Write).
Parse its single `SECURITY — VERDICT:` line:
- `PASS` → done, proceed to commit.
- `BLOCK(n)` → hand the dev the `BLOCKING` list + the CONTRACT path. Dev
fixes inline. Then **re-verify the REQUEST first** (GATE 1, fresh
verifier) — a security fix can drift the behavior — **then re-run GATE 2**
(fresh auditor), in that order. **Max 3 security iterations** → STOP +
human escalation with the BLOCKING table.
- `DEGRADED` (semgrep absent) → does NOT block on the tool's absence; surface
the checklist result + recommend `make plugin`. A DEGRADED run that still
BLOCKs (grep-caught secret/injection) blocks like any other.
- Structural failure → retry ONCE fresh; 2nd → human escalation. A mute
auditor is NEVER a PASS.
## Order invariant
REQUEST conformity is always re-checked BEFORE security on any re-loop — a
security fix that breaks the feature must not slip through because only the
security gate re-ran. Never the reverse order.
+1
View File
@@ -15,6 +15,7 @@ allowed-tools:
- Bash - Bash
- Grep - Grep
- Glob - Glob
- Agent
--- ---
Load and follow strictly: Load and follow strictly: