Septième et dernier article de la série “De la spécification à l’exécution”. L’article précédent montrait la mise en pratique sur un stack React + Go. Je termine ici par la question qui compte vraiment. Comment lancer ce workflow sans le transformer en décoration de plus ?

Je reviens à quatre questions simples.

  1. Comment l’agent sait quel test implémenter ensuite ?
  2. Comment je l’empêche de tricher pendant qu’il code ?
  3. Comment j’automatise la vérification après chaque PR ?
  4. Comment je reprends un repo existant sans tout casser ?

Je réponds à ces quatre points dans l’ordre.

plan.md, le fichier que l’agent suit

Le geste qui transforme le workflow en pratique tient dans un fichier. Kent Beck l’appelle plan.md dans son workflow augmented coding (2025). Je garde le même principe. Le fichier vit à la racine du projet, ou du module que je travaille, et contient quatre choses.

  • La spec, c’est-à-dire l’intention, les exemples et les propriétés.
  • Une liste ordonnée de tests à implémenter.
  • Les décisions d’architecture prises pendant le développement.
  • Les points de suivi sortis du scope.

Je peux le résumer avec un exemple minimal.

# Plan, module billing

## Spec
Calculer le coût d'un appel à partir d'une durée et d'un tarif.

## Tests
- [x] T1, happy path, appel de 120 s vers +33612345678
- [x] T2, durée nulle retourne 0
- [ ] T3, durée négative retourne 0
- [ ] T4, le préfixe le plus spécifique gagne
- [ ] T5, le coût reste monotone avec la durée

L’agent suit ce fichier un test à la fois. Il implémente T1, puis T2, puis s’arrête. Après chaque cycle vert, il coche la case, crée un commit, puis attend mon feu vert avant de passer au test suivant.

Je veux cette pause. Sans elle, l’agent enchaîne trop vite et finit par dériver. Avec elle, je garde un point de contrôle humain à chaque cycle.

Je garde trois avantages très concrets à ce fichier.

  • Il survit aux crashs de session et aux redémarrages d’agent. La continuité est dans le fichier, pas dans la mémoire conversationnelle.
  • Il s’audite avec un simple git diff plan.md. Je vois l’avancement réel, pas une impression d’avancement.
  • Il se réutilise avec plusieurs agents. Si je change d’outil ou de modèle, je reprends le même plan.

Le template complet et le prompt système associé sont dans tdd-skill/plan-template.md du repo public.

Les hooks pre-commit comme garde-fous

Les règles dures du fichier agent-discipline.md deviennent utiles quand je les fais exécuter par des hooks. J’en garde trois.

Hook 1, détecter une modification de tests pendant un cycle green

Je garde un marqueur .tdd-cycle pour mémoriser le test en cours. Le hook refuse toute modification d’autres tests dans le même commit. Cela coupe net le réflexe qui consiste à supprimer ou modifier un test pour faire passer le code.

#!/usr/bin/env bash
MARKER=".tdd-cycle"
[[ -f "$MARKER" ]] || exit 0
CURRENT_TEST="$(cat "$MARKER")"
CHANGED_TESTS=$(git diff --cached --name-only | grep -E 'test')
for f in $CHANGED_TESTS; do
  [[ "$f" == "$CURRENT_TEST" ]] || {
    echo "ERROR: test '$f' modifié hors du cycle ($CURRENT_TEST)"
    exit 1
  }
done

Je crée le marqueur au début du cycle, puis je le retire une fois le commit propre passé.

Hook 2, exiger un vrai rouge avant d’écrire le code

Je veux voir un test qui échoue avant d’autoriser l’écriture du code de production. Si le test passe au premier run, je stoppe. Soit le test ne teste rien, soit la fonctionnalité existe déjà et je ne suis pas dans le bon cycle.

if pytest "$TEST_ID" --tb=short -q > /dev/null 2>&1; then
  echo "ERROR: test passé au premier run. STOP."
  exit 1
fi

Je peux aussi vérifier que l’échec vient de la bonne raison, pas d’une erreur d’import ou d’une faute de syntaxe.

Hook 3, verrouiller les fichiers de test pendant le vert

Je peux aller plus loin et rendre les fichiers de test non modifiables pendant la phase green.

chmod a-w tests/test_billing.py
# code, tests verts
chmod u+w tests/test_billing.py

Ce troisième garde-fou ne remplace pas les deux premiers. Il les complète.

Je garde trois options pour implémenter ces hooks.

  • Des hooks Git en bash pur, sans dépendance, pour démarrer vite.
  • pre-commit, si je veux un cadre plus standard côté Python.
  • lefthook ou husky, si la pile mélange plusieurs langages.

Les versions complètes par langage sont dans tdd-skill/hooks-{python,typescript,go}.md du repo public.

Le pipeline CI multi-niveaux

Je veux que la CI reflète le même découpage.

unit, front et back -> integration, testcontainers -> contract, Pact -> mutation -> E2E

Je garde des règles simples.

  • Les tests unitaires et d’intégration bloquent chaque PR.
  • Le contract testing bloque aussi si les pacts ont changé.
  • Le mutation testing bloque sur les PR, mais seulement sur les fichiers modifiés.
  • Les E2E tournent sur main, pas sur chaque PR.
  • La full suite mutation et les checks de flakiness tournent la nuit.

Je résume la logique dans un extrait GitHub Actions simplifié.

jobs:
  unit:
    runs-on: ubuntu-latest
    steps: [ ..., go test ./..., pnpm test:ci ]
  integration:
    steps: [ ..., go test -tags=integration ./... ]
  mutation:
    if: github.event_name == 'pull_request'
    steps: [ ..., gremlins unleash $(changed_pkgs) ]
  contract:
    steps: [ ..., pnpm test:pact, go test -tags=pact ./pacts/... ]
  e2e:
    if: github.ref == 'refs/heads/main'
    steps: [ docker compose up, playwright test ]

Je ne cherche pas un pipeline spectaculaire. Je cherche un pipeline qui sépare bien les responsabilités.

Chaque niveau couvre une classe de défaillance différente.

  • L’unit ne remplace pas l’intégration.
  • L’intégration ne remplace pas le contract testing.
  • Le mutation testing ne remplace pas le reste.
  • Les E2E ne servent pas à valider chaque détail.

Pour une équipe de moins de trois développeurs, je commence plus simplement, unit, integration et lint. J’ajoute mutation et contract quand le coût d’un bug dépasse le coût de la CI.

Auditer un repo existant en trois niveaux

Quand j’arrive sur un projet déjà en place, je ne commence pas par imposer le workflow. Je regarde où j’en suis.

Niveau 1, statique rapide

En 15 à 30 minutes, je peux déjà voir beaucoup de choses.

Pour Python :

ruff check src tests --select PT,S,B
grep -rn 'mock.patch' tests/
grep -rEn 'except\s*:\s*$' src/

Pour Go :

golangci-lint run --enable=testifylint,thelper,paralleltest,errcheck
grep -rn 'sqlmock'
grep -rEn 't\.Skip\(\s*\)' --include='*_test.go'

Pour TypeScript :

pnpm exec eslint . --max-warnings 0
grep -rEn 'vi\.mock\(["\x27]\\./'
grep -rEn '\.(only|skip)\('

Je cherche surtout trois signaux, les mocks internes, les tests sans assertion claire, et les skip oubliés.

Niveau 2, dynamique

Je passe ensuite sur un audit plus profond, sur une à deux heures.

  • Coverage branch sur tout le projet.
  • Mutation testing sur le module le plus critique.
  • Flakiness check avec dix runs successifs et ordre randomisé.

Je ne m’intéresse pas au pourcentage seul. Je regarde les fichiers à 0 %, les fichiers à très haut coverage mais avec peu d’assertions, et les modules qui survivent à trop de mutations.

Niveau 3, IA assistée

Si je veux aller plus loin, j’utilise un critic-agent qui applique les règles dures de agent-discipline.md. Je lui demande un rapport priorisé, BLOCKER, HIGH, MEDIUM, LOW, avec les violations détectées et les tests PBT manqués.

Je garde le format du rapport pour qu’un manager puisse le lire en quelques minutes, et qu’un dev puisse agir dessus sans réinterpréter le diagnostic.

Un audit sans action ne sert pas à grand-chose. Je le vois comme le point de départ des hooks et du pipeline, pas comme une fin.

Le bouclage

Le workflow se renforce tout seul quand je le laisse respirer.

Plus la spec est précise, plus les tests sont solides. Plus les tests sont solides, plus l’IA peut produire sans dériver. Plus le code est fiable, plus je peux itérer vite. Et plus j’itère, plus je découvre des détails à remonter dans la spec.

Je peux résumer la série en sept phrases.

  1. L’IA n’a pas tué les tests, elle a déplacé le travail vers la spec et le jugement.
  2. La spec exécutable se construit en quatre niveaux, du langage naturel aux contrats.
  3. Le test précède le code et vient d’ailleurs que l’agent qui écrit le code.
  4. La qualité d’une suite de tests se mesure sur quatre axes, coverage, mutation, smells, robustesse.
  5. Le property-based testing est la défense contre les oracles faibles.
  6. Sur un stack React + Go, trois choix structurants gardent la pile fiable, testcontainers-go, MSW strict et Pact.
  7. L’opérationnel tient sur quatre piliers, plan.md, hooks pre-commit, pipeline CI et audit.

Cette série s’arrête ici. Le repo public contient les démos, les hooks par langage et le skill TDD étendu pour Claude Code. Je peux le forker, le casser, l’adapter, puis le remettre à sa place.

L’IA produit du code en volume. Le workflow produit la confiance pour l’utiliser.


Pour aller plus loin

  • Kent Beck, Augmented Coding: Beyond the Vibes (2025) - signals.aktagon.com
  • pre-commit framework - pre-commit.com
  • lefthook - lefthook.dev
  • husky - github.com/typicode/husky
  • Le repo public de la série - github.com/mwolff44/spec-to-tests
    • tdd-skill/plan-template.md, template plan.md et prompts agent.
    • tdd-skill/agent-discipline.md, les règles dures.
    • tdd-skill/hooks-python.md, hooks-typescript.md, hooks-go.md, les hooks par langage.
    • examples/billing-react-go/, la démo React + Go + Pact de l’article 6.
    • examples/pbt-sip/, la démo Hypothesis stateful sur SIP de l’article 5.

Cet article est le septième et dernier d’une série sur le workflow “de la spécification à l’exécution”. Si cette série a été utile, partagez-la et laissez-moi un retour, en commentaire ou via les issues du repo. Bon code.