From ccfecc9c21a96909ea6bcb65710c7e4d829a9ea4 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Fri, 3 Jul 2026 18:32:59 +0200 Subject: [PATCH 01/11] feat(install): semgrep pinned install + pin-honored update (security-gate lot 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 7.5 in install-plugins.sh: pipx install semgrep== behind a command -v guard (LRN-085 pattern), version echo on skip, login is Pro-rules-only guidance — never run automatically (ctx7 pattern). Step 6.2 in update-all.sh: pin-honored update that displays the version jump (cur → pin) before pipx install --force; latest only when unpinned. plugins.lock.json: semgrep pinned 1.168.0 — semgrep is a BLOCKING gate, a silent upgrade means new BLOCKs on unchanged code (gsd-pin pattern). Dogfooded via extracted real blocks: fresh install, idempotent re-run, pin-match skip, jump display + clean warn on bogus pin. Rulesets p/security-audit + p/secrets fetch anonymously (no login) and detect. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS --- install-plugins.sh | 29 +++++++++++++++++++++++++++++ plugins.lock.json | 6 ++++++ update-all.sh | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/install-plugins.sh b/install-plugins.sh index 2810f4d..60d79e6 100644 --- a/install-plugins.sh +++ b/install-plugins.sh @@ -654,6 +654,35 @@ if command -v graphify &>/dev/null; then fi echo "" +# ============================================================ +# STEP 7.5 — SEMGREP (SAST engine for the security gate) +# ============================================================ +echo "── Step 7.5: Semgrep — SAST security gate ───────────────────" +echo "" +if command -v semgrep &>/dev/null; then + ok "semgrep already installed ($(semgrep --version 2>/dev/null | head -1))" +else + SEMGREP_VER=$(pinned_version "semgrep") + if [ "$SEMGREP_VER" != "latest" ]; then + info "Installing semgrep ${SEMGREP_VER} (pinned in plugins.lock.json)..." + pipx install "semgrep==${SEMGREP_VER}" 2>/dev/null + else + info "Installing semgrep latest (consider pinning in plugins.lock.json)..." + pipx install semgrep 2>/dev/null + fi + if command -v semgrep &>/dev/null; then + ok "semgrep installed ($(semgrep --version 2>/dev/null | head -1))" + else + err "semgrep install failed — run manually: pipx install semgrep" + fi +fi +# Login is Pro-rules only and optional — NEVER run automatically (ctx7 +# pattern: guide, don't block). The gate uses pinned public rulesets. +if command -v semgrep &>/dev/null; then + info "Optional Pro rules: semgrep login (never run automatically)" +fi +echo "" + # ============================================================ # STEP 8 — EMIL DESIGN ENG (UI polish / animation skill) # ============================================================ diff --git a/plugins.lock.json b/plugins.lock.json index ef55a07..f42e496 100644 --- a/plugins.lock.json +++ b/plugins.lock.json @@ -26,6 +26,12 @@ "managed_by": "pipx", "note": "Codebase knowledge graph. CLI is 'graphify'. Install: pipx install graphifyy && graphify install && graphify claude install. Adds PreToolUse hook for Glob/Grep." }, + "semgrep": { + "source": "pypi:semgrep", + "version": "1.168.0", + "managed_by": "pipx", + "note": "SAST engine for the security gate (security-auditor agent, onboard cso fallback, audit-delta). Rulesets pinned in-agent: p/security-audit + p/secrets (never --config auto). BLOCKING gate -> pin honored by update-all.sh: 'make update' will NOT advance semgrep past it; bump deliberately (new rules = new BLOCKs on unchanged code). Never run 'semgrep login' automatically (Pro rules are optional, guide-only)." + }, "emil-design-eng": { "source": "https://github.com/emilkowalski/skill", "path": "skills/emil-design-eng/SKILL.md", diff --git a/update-all.sh b/update-all.sh index 0eed297..09f7b0c 100644 --- a/update-all.sh +++ b/update-all.sh @@ -227,6 +227,47 @@ else info "graphifyy not installed — skipping" fi +# ── 6.2. Update Semgrep (pin-honored — BLOCKING security gate) ── +echo "" +echo "── Updating Semgrep..." +if command -v semgrep &>/dev/null; then + SEMGREP_VER="" + if [ -f "$REPO/plugins.lock.json" ] && command -v python3 &>/dev/null; then + SEMGREP_VER=$(python3 -c " +import json +with open('$REPO/plugins.lock.json') as f: + d = json.load(f) +print(d.get('semgrep',{}).get('version','')) +" 2>/dev/null || true) + fi + + SEMGREP_CUR=$(semgrep --version 2>/dev/null | head -1) + if [ -n "$SEMGREP_VER" ] && [ "$SEMGREP_VER" != "latest" ]; then + if [ "$SEMGREP_CUR" = "$SEMGREP_VER" ]; then + ok "semgrep already at pinned $SEMGREP_VER" + else + # Jump shown explicitly: semgrep is a BLOCKING gate — a version bump + # can add rules that BLOCK unchanged code, so the jump must be a + # visible, deliberate human decision (bump the pin, then update). + info "semgrep ${SEMGREP_CUR:-?} → ${SEMGREP_VER} (pinned in plugins.lock.json)" + if pipx install --force "semgrep==${SEMGREP_VER}" 2>/dev/null; then + ok "semgrep updated to $SEMGREP_VER" + else + warn "semgrep update failed — try: pipx install --force semgrep==${SEMGREP_VER}" + fi + fi + else + info "No pinned version — upgrading to latest" + if pipx upgrade semgrep 2>/dev/null; then + ok "semgrep updated ($(semgrep --version 2>/dev/null | head -1))" + else + warn "semgrep update failed — try: pipx upgrade semgrep" + fi + fi +else + info "semgrep not installed — skipping (run: make plugin)" +fi + # ── 6.5. Update bun ── echo "" echo "── Updating bun..." From b8d3cccaa8fb768ccdba654c73056411463ff70b Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Fri, 3 Jul 2026 18:32:59 +0200 Subject: [PATCH 02/11] =?UTF-8?q?chore(memory):=20TODO=20=E2=80=94=20chant?= =?UTF-8?q?ier=20verify-loops/semgrep/contract=20plan=20+=20lot=201=20?= =?UTF-8?q?=C3=A9tat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS --- .claude/tasks/TODO.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.claude/tasks/TODO.md b/.claude/tasks/TODO.md index e17c1fe..562cc68 100644 --- a/.claude/tasks/TODO.md +++ b/.claude/tasks/TODO.md @@ -1,5 +1,33 @@ # TODO +## 2026-07-03 — verify loops + semgrep gate + contract (chantier orchestrateurs) +Archi validée au gate (session 2026-07-03). Cible : contract sur DISQUE dès +création (fichier de run, pattern DIAGNOSIS) + verifier frais (verdict structuré +CONFORME/écarts, preuve-qu'il-a-regardé LRN-048, 2 échecs structurels = escalade +humaine — verifier muet ≠ PASS) + gate sécu semgrep (rulesets ÉPINGLÉS +p/security-audit + p/secrets — pas --config auto, classe LRN-077 ; BLOCK +HIGH/CRITICAL only, LRN-047) + boucles bornées 3× décidées en boucle principale +(LRN-083). cso = symlink submodule gstack → non modifiable → greffes locales +(onboard cso-fallback, audit-delta, agent neuf ; complément semgrep même +gstack ON). Verdicts user : dev inline conservé feat/bugfix/hotfix (verify+sécu += sous-agents frais) ; hotfix garde revert-escalade ; PIN version semgrep dans +plugins.lock.json (gate bloquante — upgrade silencieux = nouveaux BLOCK sur code +inchangé ; pattern gsd-pin, saut affiché par update-all). + +LOT 1 — feature/semgrep-install (GO) +- [x] plugins.lock.json — pin semgrep 1.168.0 (pattern gsd, note gate bloquante) +- [x] install-plugins.sh STEP 7.5 — pipx pinned, command -v guard + version echo, login guide-only (jamais auto) +- [x] update-all.sh step 6.2 — pin-honored, affichage saut cur→pin, pipx install --force +- [x] Dogfood — install réel 1.168.0 via bloc extrait + idempotence (re-run = skip) + pin-match + saut affiché (1.168.0→9.9.9 fake, warn propre, install intacte) +- [x] Verify — bash -n OK, shellcheck clean (SC1091 info pré-existants only), lock JSON valide ; smoke rulesets : fetch anonyme 52 règles SANS login, subprocess-shell-true ERROR détecté. Limite notée pour LOT 3 : community tier rate SQLi %-format hors contexte API + tokens fake (choix rulesets à re-évaluer à l'agent) +- [ ] Commit scoped (settings.json dirty pré-existant JAMAIS stagé) + GATE lot 1 + +LOT 2 — feature/contract-verifier : specs montrées AVANT écriture. lib/contract-interview.md + agents/verifier.md. +LOT 3 — feature/security-auditor : agents/security-auditor.md + greffe audit-delta + onboard fallback + complément gstack-ON. +LOT 4 — feature/loops-light : câblage feat/bugfix/hotfix. +LOT 5 — feature/loops-heavy : câblage ship-feature + init-project + onboard. +Rien poussé ; gate par lot ; suites après chaque lot. + ## 2026-07-03 — design-toolchain trigger fix (bugfix/design-toolchain-trigger) Root cause (NOT a kill-switch, per user): ed2408e (07-02) dropped ultra-generic tokens but left bare tokens common in non-UI talk → ~6× false-fire THIS session From c6a7c1f7d9b6b20618dd682202bd416c13c4507c Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Fri, 3 Jul 2026 18:40:07 +0200 Subject: [PATCH 03/11] chore(memory): BDR-048 pinned-gate doctrine + LRN-092 SAST smoke-test + journal 2026-07-03 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS --- .claude/memory/decisions.md | 9 +++++++++ .claude/memory/journal.md | 1 + .claude/memory/learnings.md | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/.claude/memory/decisions.md b/.claude/memory/decisions.md index 6e1cfae..027f28f 100644 --- a/.claude/memory/decisions.md +++ b/.claude/memory/decisions.md @@ -69,6 +69,7 @@ rules: | BDR-045 | 2026-07-01 | Standalone memory/doc skills branch to chore/* via aiguillage (hook exemption kept) | accepted | | BDR-046 | 2026-07-01 | Claude Code installs via official native installer (curl claude.ai/install.sh), drop npm from install.sh | accepted | | BDR-047 | 2026-07-01 | ECC audit → zero import; local config ahead of reference | accepted | +| BDR-048 | 2026-07-03 | semgrep security gate: engine version + rulesets PINNED, never --config auto; upgrade = deliberate visible human jump | accepted | --- @@ -788,3 +789,11 @@ rules: ONE scope gap: BDR-047 never opened hooks/ — ECC's only WIRED subsystem. Fruit: config-protection hook (own idiom, NOT ECC import), shipped feature/config-protection-hook. Lesson holds + refined by [[LRN-090]]. + +## BDR-048 — Deterministic security gate: pinned engine + pinned rulesets (semgrep) + +- **Date**: 2026-07-03 +- **Decision**: semgrep = BLOCKING gate (verify-loops chantier) → engine version PINNED in plugins.lock.json (gsd-pin pattern; update-all.sh honors pin + displays jump cur→pin before `pipx install --force`). Rulesets PINNED in-agent: `p/security-audit` + `p/secrets`. Never `--config auto` (registry telemetry + ruleset resolved per-run = non-deterministic gate, [[LRN-077]] class). Never auto `semgrep login` — Pro rules optional, guide-only (ctx7 pattern). +- **Rationale**: gate blocks HIGH/CRITICAL only ([[LRN-047]]); silent engine/rule upgrade = new BLOCKs on unchanged code w/o human decision → gate crying false → ignored. Version jump must be deliberate + visible (bump pin, then `make update` shows the jump). +- **Alternatives rejected**: `latest` (pipx house default, graphifyy-style) — fine for comfort tools, wrong for a blocking gate; `--config auto` — telemetry + non-determinism. +- **Reference**: plugins.lock.json `semgrep` entry, install-plugins.sh STEP 7.5, update-all.sh step 6.2 — branch feature/semgrep-install `ccfecc9`. Conditions [[LRN-047]], [[LRN-085]]. Coverage caveat of the community rulesets: [[LRN-092]]. diff --git a/.claude/memory/journal.md b/.claude/memory/journal.md index d91825b..2e0d14b 100644 --- a/.claude/memory/journal.md +++ b/.claude/memory/journal.md @@ -314,3 +314,4 @@ rules: - Next: #2 design-toolchain trigger fix (residual false-fires post-ed2408e, 5× this session). - #2 done (bugfix/design-toolchain-trigger): trigger tightened — dropped bare design|component|composant|theme|thème|transition|frontend|front-end|palette; dashboard→\bdashboard\b (kills ecc_dashboard.py filename match, keeps "admin dashboard"); kept animation; added "front-?end design" bigram + fire-log counter (time+token+excerpt, ~/.claude/logs/design-toolchain-fires.log) so future "re-firing?" is measured. Test 18/18, shellcheck clean, live dogfood green. [[LRN-091]] corrob [[LRN-047]]. - Double dogfood of #1 guard: config-protection blocked + sentinel-bypassed my own edits to the now-guarded design hook + its test — first real use of the guard, friction validated in passing (one-shot sentinel .claude/.config-edit-ok, non-empty reason, logged+consumed). ECC second-regard closed: #1 config-protection + #2 trigger fix, both merged to develop, nothing pushed. +- Chantier verify-loops/semgrep/contract: Phase 1 read-only (6 subagents mapped 6 orchestrators + cso + agents + install patterns; caught subagent error — cso IS gstack symlink, ls-verified) → archi GATED-GO (5 verdicts: local grafts, dev inline light flows, hotfix unchanged, pinned rulesets, pinned version; +2 specs: contract on DISK, mute verifier ≠ PASS). LOT 1 shipped on feature/semgrep-install (ccfecc9+b8d3ccc): install-plugins STEP 7.5 + update-all 6.2 + lock pin 1.168.0, dogfooded real (4 paths + anonymous ruleset fetch + detection). [[BDR-048]] [[LRN-092]]. Next: lot 2 specs (contract-interview lib + verifier agent). diff --git a/.claude/memory/learnings.md b/.claude/memory/learnings.md index 377556d..5b19ec2 100644 --- a/.claude/memory/learnings.md +++ b/.claude/memory/learnings.md @@ -109,6 +109,7 @@ rules: | LRN-087 | 2026-07-02 | presence-flag ≠ capability — rtk silently dead after .bashrc wipe; emitted commands need ABSOLUTE bin paths (they run in another shell); integrity pin = live machinery, re-pin on hook edit | any PATH-dependent capability + hand-managed shell profile; hooks emitting commands for another shell | | LRN-088 | 2026-07-02 | token-cutting intuition inverts under measurement — verbosity beats cardinality (gstack 34 skills ≈ 592 tok vs pr-review 6 agents ≈ 2,183) | any "disable X to save tokens" — measure per-item bytes first; profiles toggle skills, not plugin payloads | | LRN-089 | 2026-07-03 | pass-through wrapper (CLI `"$@"` → fn deriving target from ambient state: HEAD/cwd/env) silently ignores its args = silent contract violation; guard = args are an ASSERTION, refuse when they disagree with state | any dispatcher forwarding args to a callee that reads ambient state instead of the args | +| LRN-092 | 2026-07-03 | SAST smoke test w/ the OFFICIAL example secret = vacuous pass (rules exclude documented example keys by design); validate w/ realistic payloads + measure tier coverage before trusting a gate ruleset | smoke-testing any detector/gate — never the canonical example payload | --- @@ -977,3 +978,9 @@ rules: - **rule**: keep a token BARE only when its UI sense dominates largely in a dev context (glassmorphism, navbar). Token common in non-UI talk (design, component, theme, transition, frontend) → require a UI-specific bigram (design system, front-end design) or drop; in doubt → bigram-or-drop. Borderline standalone nouns (dashboard, animation) may stay bare as an assumed call — the fire-log arbitrates later on data, not gut. (NOT "never bare tokens" — animation stays bare here by design.) - **context**: design-toolchain-reminder.sh — 07-02 tightening (dropped page/form/menu/…) insufficient; 6 bare tokens still false-fired ~6×/session during the ECC config audit (design, ecc_dashboard.py, component, frontend, theme, transition, palette). 07-03 fix: dropped them, dashboard→`\bdashboard\b` (filename match killed, "admin dashboard" kept), added a fire-log (time+token+excerpt). `lib/tests/design-toolchain-reminder.test.sh` locks it (18 checks). - **cousin**: [[LRN-047]] a doctor that cries false is ignored. + +## LRN-092 — SAST smoke test: official example keys are rule-excluded — "no findings" proves nothing +- **pattern**: smoke-testing a SAST/secret detector w/ the OFFICIAL example payload (AWS `AKIA...EXAMPLE`) → 0 findings BY DESIGN — rules exclude documented example keys to kill FP. A vacuous pass, [[LRN-048]] class (a pass must prove it looked). Validate w/ realistic-shaped payloads AND enumerate what the tier does NOT catch before trusting a ruleset as a gate. +- **context**: lot 1 semgrep-install dogfood 2026-07-03. `p/secrets`+`p/security-audit` community tier: anonymous fetch OK (52 rules, no login), `subprocess-shell-true` detected ERROR; MISSED %-format SQLi on bare cursor (no recognized DB-API context) + fake-checksum `ghp_` token. Gap logged for security-auditor agent design (consider adding `p/owasp-top-ten`). +- **future application**: any detector/gate smoke test — craft realistic payloads, never the canonical example; measure the miss-list on purpose-built fixtures; size the gate's blocking scope on that data. +- **cousin**: [[LRN-048]] a 0/OK must prove it looked; [[LRN-047]] noisy guard = ignored guard; conditions [[BDR-048]]. From 6aed5eea8c6b757a66056315ced8f514a40ffc93 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Fri, 3 Jul 2026 18:50:28 +0200 Subject: [PATCH 04/11] feat(agents): contract interview include + verifier agent (verify-loops lot 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib/contract-interview.md: mandatory upstream passage for all orchestrators. Verbatim REQUEST (immutable), proportional questions (complete request = zero, max 3 one batch), testable criteria + file scope, written to disk immediately (.claude/tasks/contracts/--.md — a context-only contract dies at compaction). Lifecycle: enrichment only at human gates ([gated] marker, scope micro-gate), supersedes for re-scope, aborted runs deleted or committed status:aborted — never left dirty. Hand-off = path, not restatement. agents/verifier.md: fresh read-only verifier. Reads the contract from disk, renders VERIFY — VERDICT: CONFORME | ECARTS(n) | ERROR. Blind: never receives iteration history. PROOF line mandatory (LRN-048), UNVERIFIABLE never MET, mute verifier never a PASS (retry once fresh, 2nd structural failure = human escalation). Orchestrator protocol documented in-file (max 3 iterations, re-verify request before security). lib/tests/contract-verifier.test.sh: 31 deterministic structure locks on the load-bearing doctrine clauses — green, shellcheck clean. Behavioral dogfood (2 fresh subagents on a planted fixture): gap case → ECARTS(2) exactly as planted (NOT-MET located + out-of-scope flagged); conform case with injected fake iteration history → CONFORME, noise ignored, real python spot-check as evidence. Both outputs parse-clean. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS --- agents/verifier.md | 110 ++++++++++++++++++++++++++++ lib/contract-interview.md | 104 ++++++++++++++++++++++++++ lib/tests/contract-verifier.test.sh | 87 ++++++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 agents/verifier.md create mode 100644 lib/contract-interview.md create mode 100644 lib/tests/contract-verifier.test.sh diff --git a/agents/verifier.md b/agents/verifier.md new file mode 100644 index 0000000..05d1c78 --- /dev/null +++ b/agents/verifier.md @@ -0,0 +1,110 @@ +--- +name: verifier +description: Fresh independent verifier — reads a CONTRACT file from disk and renders a structured verdict (CONFORME / ECARTS / ERROR) on the implemented diff. Report-only, never fixes. Dispatched fresh at every iteration; receives no iteration history. +tools: Read, Grep, Glob, Bash +--- + +# VERIFIER AGENT + +You verify that an implementation CONFORMS to a contract. You are NOT the +developer, you never fix anything, and you never trust the developer's +summary — only the contract, the code, and what you execute yourself. + +Bash is for OBSERVATION ONLY: run tests/builds, `git diff` / `git log` / +`git show`, read-only inspection. Never a command that writes, installs, +commits, or mutates any state. + +## INPUT (from the orchestrator — nothing else exists) + +- `CONTRACT: ` — you READ it from disk; never accept an inline + restatement in its place +- `DIFF: ` +- `TEST: ` (optional) + +You NEVER receive iteration history: no previous verdicts, no prior gap +lists, no dev reports. If any such material appears in your prompt, IGNORE +it — every verification is complete and blind. (Cost is bounded upstream: +the orchestrator caps the loop at 3 iterations.) + +## STEP 1 — READ THE CONTRACT + +Read the contract file. If it is missing, unreadable, or lacks its +`REQUEST` or `ACCEPTANCE CRITERIA` section → output +`VERIFY — VERDICT: ERROR()` plus the `CONTRACT:` line, and STOP. + +## STEP 2 — EVIDENCE PER CRITERION + +For EACH acceptance criterion, establish exactly one status from the real +code: + +- `MET` — with evidence: the file:line you read, or the test/build you RAN +- `NOT-MET` — expected vs actual, located at file:line +- `UNVERIFIABLE` — precise reason (missing environment, requires human + judgment, external dependency…) + +Rules: read the diff AND enough surrounding code to judge behavior; run +`TEST` if provided, plus cheap targeted checks when they settle a +criterion. Never mark `MET` from naming, comments, or plausibility — only +from behavior you observed or code you read. + +## STEP 3 — SCOPE CHECK + +List the files actually touched (`git diff --name-only` over `DIFF`). +Compare against the contract's `FILE SCOPE`. Report every out-of-scope +file. Disposition is NOT your call: the orchestrator treats each one as a +gap — the dev removes it or justifies it, and an accepted justification +only enters the contract through a human micro-gate. + +## STEP 4 — VERDICT + +`CONFORME` ⇔ ALL criteria `MET` AND zero out-of-scope files. +Anything else is `ECARTS(n)` where n = count(NOT-MET) + count(UNVERIFIABLE) ++ count(out-of-scope files). + +## OUTPUT (exact format — machine-parsed by the orchestrator) + +``` +VERIFY — VERDICT: CONFORME | ECARTS(n) | ERROR() +CONTRACT: +CRITERIA: + 1. — MET — + 2. — NOT-MET — expected <…> / actual <…> — + 3. — UNVERIFIABLE — +SCOPE: in-scope files; out-of-scope: +PROOF: read files, ran , checked / criteria +``` + +## RULES + +- Report-only. Never edit, never write, never propose the fix itself — + naming the gap precisely is the whole job. +- `UNVERIFIABLE` ≠ `MET`. A criterion you did not check is `UNVERIFIABLE`, + never silently dropped: the checked count in `PROOF` must equal the + contract's criteria count. +- `PROOF` is MANDATORY. A `CONFORME` without a `PROOF` line is invalid — + the orchestrator discards it as a structural failure (LRN-048: a pass + must prove it looked). +- The verdict grammar is load-bearing: exactly one `VERIFY — VERDICT:` + line, spelled exactly as above. + +## ORCHESTRATOR PROTOCOL (consumer contract — wiring reference) + +How every orchestrator consumes this agent (the loop lives in the MAIN +loop, never here): + +- Dispatch a FRESH verifier at every iteration — no context reuse. Input = + contract path + diff range + optional test command, nothing else. +- Parse the `VERIFY — VERDICT:` line: + - `CONFORME` on first pass → proceed straight to the security gate — no + forced loop. + - `ECARTS(n)` → the dev subagent receives the contract PATH + the exact + gap list (nothing else). Max 3 iterations → STOP + human escalation + with the CRITERIA table (the contract-vs-realized diff). + - Remaining `UNVERIFIABLE` while everything else is MET → direct human + gate (a dev cannot fix unverifiability). + - Structural failure (`ERROR(…)`, missing/duplicated VERDICT line, + unparsable output, agent crash, `CONFORME` without `PROOF`) → retry + ONCE with a fresh verifier; a 2nd structural failure → human + escalation. A mute verifier is NEVER a PASS. +- After a security-gate fix round: re-verify the request FIRST (this + agent), THEN re-verify security — in that order. diff --git a/lib/contract-interview.md b/lib/contract-interview.md new file mode 100644 index 0000000..9dcc1c4 --- /dev/null +++ b/lib/contract-interview.md @@ -0,0 +1,104 @@ +# Contract interview — mandatory upstream passage (all orchestrators) + +Produces the CONTRACT: the single reference passed verbatim to the plan, the +dev subagents, and the verifier. The contract is what lets the orchestrator +delegate execution without subagents ever needing a human gate (LRN-083: +subagents = execution + report only; gates and loop decisions live in the +main loop). + +Run this in the ORCHESTRATOR MAIN LOOP, never in a subagent — STEP 2 may +talk to the human. Mandatory passage in every flow; questions are optional +and proportional — a complete request goes through silently. + +## STEP 1 — CAPTURE (verbatim) + +Copy the user's request EXACTLY as typed (`$ARGUMENTS` + the triggering +message). No paraphrase, no cleanup, no translation, no summarizing. This +section is IMMUTABLE for the life of the run — every later consumer +(planner, dev, verifier) reads THESE words, never a restatement. + +## STEP 2 — AMBIGUITY CHECK (questions optional, proportional) + +Ask ONLY if one of these is missing AND not derivable from the repo: +- a testable expected outcome +- an unambiguous scope (what is allowed to change) +- non-contradictory constraints + +Complete request → ZERO questions, stay silent. Otherwise: max 3 questions, +one single batch (house rule: one question upfront, never mid-task). Never +ask what the repo can answer — verify paths/APIs/behavior yourself first. + +## STEP 3 — DERIVE + +- ACCEPTANCE CRITERIA: numbered; each one testable — a fresh reader must be + able to mark it MET / NOT-MET against the real code, without having seen + this conversation. +- FILE SCOPE: paths/zones expected to change, or `repo-wide — `. + +## STEP 4 — WRITE TO DISK (immediately, before any next step) + +Path: `.claude/tasks/contracts/--.md` +(`mkdir -p` the directory; unique per run: date + short kebab slug + HHMM — +two runs on the same day never collide). A contract that lives only in +context dies at compaction, and the verbatim request with it. + +Template: + +```markdown +# CONTRACT — +- date: | flow: | branch: +- status: active + +## REQUEST (verbatim — IMMUTABLE) + + +## CLARIFICATIONS +Q: / A: +(or: none — request complete) + +## ACCEPTANCE CRITERIA +1. +2. + +## FILE SCOPE + +(or: repo-wide — ) +``` + +Print one line to the user, then continue the flow: +`CONTRACT: — criteria, scope , questions asked` + +## Lifecycle + +- **REQUEST**: immutable, for the life of the run. Never rewritten, never + "cleaned up". +- **CRITERIA / FILE SCOPE enrichment**: ONLY at a human gate, each added + entry marked `[gated ]`. A dev subagent NEVER enriches the + contract. An out-of-scope edit the dev justifies is accepted ONLY through + this micro-gate: human approves → FILE SCOPE gains the entry `[gated]`; + human declines → the dev removes the edit. Without this gate the dev + justifies everything and scope constrains nothing. +- **Deep re-scope** (the request itself changes): NEW contract file with + `supersedes: ` in its header — never a rewrite of the old one. +- **Aborted run**: delete the contract file, or commit it with + `status: aborted` in the header. NEVER left dirty in the working tree. +- **Commit**: the contract rides the existing memory commit — + `lib/capitalize-commit.md` already covers the `.claude/tasks` pathspec. + No new plumbing. + +## Weight per flow + +| Flow | Weight | +|------|--------| +| hotfix | Silent autofill — criteria: "symptom gone; build/tests green"; scope = the 1-2 target files. Zero questions ever. | +| feat / bugfix | Proportional. bugfix: the DIAGNOSIS feeds the criteria (symptom reproduced-then-gone + regression test present). | +| ship-feature | Full. Design decisions approved at the validation gate append criteria `[gated ]` — the human validates the enriched contract, the verifier receives that version. | +| init-project | Full. The interviewer's PROJECT BRIEF pours into the contract (V1 features → criteria). | +| onboard | Audit-scope contract (interview answers → what to audit, which axes). | + +## Hand-off rule + +Downstream consumers (plan step, dev subagents, verifier) receive the +contract PATH, not a restatement of its content — the file on disk is the +only authoritative copy, and reading it from disk is what makes the dev's +reformulation structurally unable to interpose. diff --git a/lib/tests/contract-verifier.test.sh b/lib/tests/contract-verifier.test.sh new file mode 100644 index 0000000..af7b99f --- /dev/null +++ b/lib/tests/contract-verifier.test.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# ============================================================ +# Structure locks — contract/verifier pair (verify-loops lot 2) +# Deterministic greps on load-bearing doctrine clauses: an edit +# that silently drops one (blind verifier, PROOF mandatory, +# immutable REQUEST, micro-gate scope enrichment…) reds here. +# ============================================================ +set -u + +REPO="$(cd "$(dirname "$0")/../.." && pwd)" +LIB="$REPO/lib/contract-interview.md" +AGT="$REPO/agents/verifier.md" +PASS=0; FAIL=0 + +# Fixed-string lock (UTF-8 punctuation safe) +tf() { # tf