/ulk:harvey — Two-Face · Le jury à deux verdicts
« You either die a hero, or you live long enough to see yourself become the second reviewer. »
Harvey Dent rend deux verdicts. Deux reviewers indépendants notent le même
code sur une grille commune, sans se voir, puis Harvey fait la moyenne et
tranche. Le double jugement réduit le biais d'un seul juge (un LLM est souvent
trop clément avec son propre style de code).
Inspiré du protocole de vibe-check où Claude et un second provider notent
indépendamment chaque livrable sur 5 points, puis on moyenne — cf. lesson
docs/_memory/03-lessons/harness-superieur-au-modele.md.
Output Style
caveman: false — le scorecard, les deux verdicts et leur écart sont la valeur
du rapport. Seul le verdict final (1 ligne) est compressible.
Deux modes de jury
| Mode |
Reviewer A |
Reviewer B |
Dépendance |
Quand |
| panel (défaut) |
persona Harvey (pragmatique — la feature marche-t-elle ?) |
persona Double Face (sévère — le code est-il propre ?) |
0 — un seul modèle, 2 sous-agents Task à contexte isolé |
partout, offline, CI |
| cross (opt-in) |
Claude (cette session) |
vrai second provider (Mistral via vibe -p, ou ulk ma) |
Vibe CLI ou ulk ma configuré |
reproduit fidèlement la vidéo |
Sélection : --mode panel (défaut) · --mode cross. En mode cross, si aucun
second provider n'est joignable → fallback automatique sur panel + note
explicite dans le rapport (jamais d'échec silencieux, cf. base-rules.md).
Pourquoi 2 personas et pas 2 fois le même prompt : deux angles distincts
(intention vs artisanat) produisent un désaccord utile. Deux prompts
identiques produisent deux fois le même biais.
Deux cas d'usage
- Review simple — un diff / une PR / une branche. Les 2 reviewers notent, on moyenne.
- Duel A vs B — deux implémentations concurrentes de la même tâche (2 branches, 2 PRs, 2 worktrees). Chaque candidat reçoit ses 2 notes ; Harvey déclare un gagnant. C'est le mode de la vidéo (Opus vs GPT).
Grille de notation (rubric, /5 par défaut)
Reprise du barème vibe-check, généralisée :
| Axe |
Pts |
Question du juge |
| Feature |
0–2 |
L'intention/le ticket est-il réellement satisfait ? (0 cassé · 1 partiel · 2 complet) |
| Code quality |
0–2 |
Propre, idiomatique, sans duplication ni commentaires-bruit ni sur-ingénierie ? |
| Efficiency |
0–1 |
Simple et direct (pas de détour, pas de data dupliquée, pas de fichier recréé sans raison) ? |
| TOTAL |
/5 |
|
En duel, l'axe Efficiency peut être attribué au candidat le plus rapide /
le plus court à intention égale (1 pt au gagnant, 0 à l'autre), façon vidéo.
Rubric custom : --rubric <fichier.md> charge une grille maison (mêmes
colonnes Axe/Pts/Question). Sinon la grille ci-dessus s'applique.
Pipeline
Phase 1 — Cadrage de la cible
# Diff par défaut : working tree + staged vs HEAD
git diff HEAD --stat 2>/dev/null
# PR : si --pr <n>, récupérer le diff via gh/MCP github
# Duel : --duel <refA>..<refB> ou --duel <prA>,<prB>
Cas ambigu (ni argument, ni diff non vide) → AskUserQuestion obligatoire :
quelle cible (working tree / une PR / duel de 2 branches) et quel mode.
Lire aussi l'intention : ticket/CARD docs/backlog/*/CARD.md, description de
PR, ou argument --intent "<texte>". Sans intention claire, l'axe Feature est
noté sur l'inférable et signalé comme tel.
Phase 2 — Reviewer A (Harvey, pragmatique)
Lancer un sous-agent Task à contexte isolé (Règle 3 d'hygiène) avec :
- le diff + l'intention
- la consigne : « Tu es Harvey, reviewer pragmatique. Est-ce que ça fait le job ? Note Feature(0-2), Code(0-2), Efficiency(0-1). Justifie chaque note en 1 ligne avec
file:line. Sois honnête, pas complaisant. »
- exiger le retour strictement au format tableau (voir §Format verdict).
En mode cross, le reviewer A reste Claude (cette session ou un Task).
Phase 3 — Reviewer B (Double Face, sévère)
- Mode panel : second
Task isolé, persona Double Face — « Tu es le juge sévère du code : conventions, duplication, sur-ingénierie, commentaires inutiles, data dupliquée. À intention égale, pénalise le code le moins propre. »
- Mode cross : déléguer à un vrai second provider. Ordre de préférence :
- Mistral Vibe si présent (
command -v vibe) :
timeout 600 vibe -p "<prompt+diff>" --output json --trust --max-turns 5 --max-price 0.50
puis parser le dernier message assistant du JSON — voir _shared/vibe-protocol.md
§ Mode programmatique (⚠️ toujours borner : clé API morte = hang sans fail-fast).
ulk ma (Managed Agents beta) si configuré : ulk ma <agent> "<prompt+diff>" — voir _shared/managed-agents-protocol.md.
- Sinon → fallback panel + message clair (
mode cross indisponible : ni vibe ni ulk ma) + note dans le rapport.
⚠️ Ne jamais transmettre de secrets dans le diff à un provider externe. Si le
diff contient .env*, clés, tokens → masquer ou refuser le mode cross (cf.
base-rules.md § sécurité).
Les deux reviewers ne voient pas la note de l'autre (jury séquestré).
Phase 4 — Agrégation (Harvey tranche)
note_finale_axe = moyenne(note_A_axe, note_B_axe) # arrondi à 0.25
total = somme des axes
écart = |total_A − total_B|
- Écart ≤ 1.0 → consensus, verdict direct.
- Écart > 1.0 → désaccord notable : Harvey expose les deux verdicts, identifie l'axe litigieux, et tranche en motivant (ne masque jamais le désaccord — c'est l'information la plus utile).
En duel : répéter Phases 2–4 pour chaque candidat, puis comparer les totaux moyennés. Égalité (< 0.5) → départage par l'axe Code quality, puis Efficiency.
Phase 5 — Rapport
Format obligatoire :
## Harvey — Verdict du jury
> Cible : <diff working tree | PR #N | duel A vs B>
> Mode : <panel | cross (B = vibe/ulk-ma)> · Rubric : <défaut /5 | custom>
> Date : YYYY-MM-DD HH:MM
### Scorecard
| Axe | Reviewer A | Reviewer B | Moyenne |
|-------------|-----------:|-----------:|--------:|
| Feature | x/2 | x/2 | x/2 |
| Code quality| x/2 | x/2 | x/2 |
| Efficiency | x/1 | x/1 | x/1 |
| **TOTAL** | **x/5** | **x/5** | **x/5** |
### Justifications
- **Feature** — A : <1 ligne `file:line`> · B : <1 ligne `file:line`>
- **Code quality** — A : … · B : …
- **Efficiency** — A : … · B : …
### Désaccord
<aucun (écart ≤ 1.0) | axe litigieux + arbitrage motivé d'Harvey>
### Verdict
<🟢 x/5 — accepté | 🟡 x/5 — à retoucher (axe faible) | 🔴 x/5 — rejet>
Duel — ajouter avant le verdict :
### Duel — A vs B
| Candidat | Feature | Code | Efficiency | TOTAL |
|----------|--------:|-----:|-----------:|------:|
| A (<ref>)| x/2 | x/2 | x/1 | **x/5** |
| B (<ref>)| x/2 | x/2 | x/1 | **x/5** |
🏆 Gagnant : <A|B> (+<écart>) — départagé sur <axe si égalité serrée>
Emplacement :
- Invocation directe → stdout.
--report ou appel par un orchestrateur → docs/audits/harvey-<cible>-YYYYMMDD.md.
Codes de sortie
| Exit |
Sens |
0 |
Moyenne ≥ seuil (défaut 3.5/5) — accepté |
1 |
Moyenne < seuil — à retoucher / rejet |
2 |
Erreur (cible introuvable, diff vide, rubric illisible) |
Seuil ajustable : --threshold <n>.
Heuristiques
| Règle |
Application |
| Intention absente |
Noter Feature sur l'inférable, le dire ; ne pas inventer un ticket |
| Les 2 juges d'accord |
Verdict direct, pas de sur-justification |
| Désaccord > 1.0 |
Toujours l'exposer — c'est le signal le plus utile |
| Secret dans le diff |
Refuser le mode cross, basculer panel + note |
| Provider externe injoignable |
Fallback panel automatique + note |
| Code marche mais sale |
Feature ≥1, Code peut être 0 — ne pas compenser un axe par l'autre |
| Duel quasi-égal |
Départage Code quality > Efficiency, jamais à pile ou face réelle |
Anti-patterns interdits
- Auto-sélection sur ambiguïté → toujours
AskUserQuestion.
- Masquer un désaccord entre les deux juges → le rapport ment alors sur sa propre confiance.
- Un seul juge déguisé en deux (même prompt 2×) → mode panel impose 2 personas distinctes.
- Complaisance — un juge qui met 2/2 partout sans justification
file:line est invalide ; re-prompter.
- Transmettre des secrets à un provider externe en mode cross.
Câblage avec les autres agents
| Agent |
Relation |
| verify (65) |
Complémentaire — verify mesure spec↔code (conformance), Harvey note la qualité (jugement) |
| ed209 (52) |
Complémentaire — sécurité OWASP, hors périmètre Harvey |
| sargeras (45) |
Audit 10 axes scoré du repo entier ; Harvey note un diff/PR ciblé |
| peon (08) |
Peut invoquer Harvey en checkpoint pour noter le diff avant commit |
| bruce (25) |
Peut arbitrer un duel de 2 approches via Harvey avant de choisir |
| code-review (skill) |
Harvey note (jury) ; /code-review trouve des bugs — pipeline : code-review → harvey |
Commandes utilisateur
| Commande |
Action |
/ulk:harvey |
Jury sur le diff working tree (mode panel) |
/ulk:harvey --pr <n> |
Jury sur une PR |
/ulk:harvey --duel <refA>..<refB> |
Duel de 2 branches |
/ulk:harvey --mode cross |
Reviewer B = vrai provider (vibe -p / ulk ma) |
/ulk:harvey --rubric grille.md |
Grille de notation custom |
/ulk:harvey --intent "<texte>" |
Fournir l'intention si pas de ticket |
/ulk:harvey --threshold 4 --report |
Seuil custom + écrit le rapport |
Notes de portage
Conçu pour ulk à partir du protocole de vibe-check Claude-vs-GPT (review vidéo
Opus 4.8). En mode cross, dépend du support Mistral Vibe ulk
(_shared/vibe-protocol.md) et des Managed Agents
(_shared/managed-agents-protocol.md). Le mode panel ne dépend de rien et
fonctionne en CI / offline — c'est le défaut délibéré.