Développement piloté par les contraintes
Vue d'ensemble
Les autres compétences de ce pack décrivent ce à quoi ressemble un travail de qualité. code-review-and-quality vous donne cinq axes. test-driven-development vous donne un cycle. security-and-hardening vous donne une liste de menaces. Tout cela existe dans un texte que l'agent lit et peut ou non suivre, et rien ne survit à la fin de la session.
Cette compétence produit quelque chose de différent : un enregistrement écrit du niveau de qualité de ce projet, avec des chiffres, qui survit à la conversation et peut être vérifié mécaniquement.
La raison est importante. Quand vous écriviez le code, le lire vous disait s'il était bon. Un agent écrit plus en un après-midi que vous n'en lirez cette semaine, donc le jugement sort de votre tête et entre dans des vérifications qui tournent en boucle. Ces vérifications doivent exister, elles doivent avoir des chiffres que vous avez réellement choisis, et elles doivent se déclencher suffisamment près du travail pour que l'agent corrige sa propre sortie.
Le développement piloté par les spécifications dit quoi construire. Le développement piloté par les tests prouve que ça marche. Le développement piloté par les contraintes définit ce que signifie « assez bon pour être livré », avant que quiconque ne se dispute à ce sujet dans une pull request.
Quand l'utiliser
Appliquez cette compétence quand :
- Vous démarrez un projet ou une fonctionnalité importante et aucun niveau de qualité n'est écrit
- L'utilisateur demande à « définir des contraintes », « ajouter des portes de qualité », « définir nos standards » ou « empêcher l'agent de livrer de la camelote »
- Un agent produit un volume que personne ne lit ligne par ligne
- L'IC a des vérifications mais personne ne peut dire lesquelles bloquent une fusion et lesquelles sont décoratives
- Les chiffres de couverture, de performance ou d'accessibilité sont débattus par PR au lieu d'être décidés une fois pour toutes
- Vous êtes sur le point d'exécuter
/build autoou toute boucle autonome, et la seule chose entre elle et la branche principale est une suite de tests que l'agent a également écrite
Quand NE PAS l'utiliser :
- Le projet a déjà un
CONSTRAINTS.mdet l'utilisateur ne le modifie pas — lisez-le et suivez-le plutôt - Scripts ponctuels, prototypes jetables, expérimentations
- L'utilisateur veut une revue de code immédiate (
code-review-and-quality) ou une pipeline CI construite (ci-cd-and-automation) - Code pré- adéquation produit-marché avec une durée de vie attendue de deux semaines — le socle minimum en vaut toujours la peine, le reste non
Chargement des contraintes
L'entretien a besoin d'un utilisateur en direct. Ne l'exécutez pas dans des contextes non interactifs (CI, /loop, exécutions autonomes). Si les contraintes sont manquantes et que vous êtes dans l'un de ces contextes, appliquez le socle ci-dessous, notez que vous l'avez fait, et signalez le reste à un humain.
Le processus
Étape 1 : Détecter avant de demander
Ne demandez jamais ce que vous pouvez lire. Avant la première question, rassemblez :
| Quoi | Où chercher |
|---|---|
| Langage et stack | package.json, pyproject.toml, go.mod, Cargo.toml |
| Lanceur de tests | dépendances de développement, script test, fichiers de test existants |
| Linters existants | eslint.config.*, biome.json, .ruff.toml |
| Couverture actuelle | sortie coverage/, ou exécutez la suite une fois |
| CI | .github/workflows/, .gitlab-ci.yml |
| Infrastructure d'agent | .claude/, .codex/, AGENTS.md |
Signalez ce que vous avez trouvé en deux lignes, puis demandez uniquement ce qui reste.
Étape 2 : Quatre questions, chacune avec une valeur par défaut
Suivez la discipline d'une question à la fois de interview-me, avec un changement : chaque question ici a une valeur par défaut, donc « Je ne sais pas » est une réponse complète qui produit toujours une configuration fonctionnelle.
Q1 : Au-delà du socle, lesquelles de ces dimensions voulez-vous voir appliquées ?
(a) Couverture de test sur le nouveau code
(b) Scan de sécurité
(c) Budgets de performance
(d) Accessibilité
(e) Limites architecturales
ESTIMATION : (a) et (b) — vous avez déjà un lanceur de tests et vous gérez des entrées utilisateur.
PAR DÉFAUT si incertain : (a) et (b).
Dites ce que chaque choix coûte : (c) et (d) nécessitent une URL en cours d'exécution, (e) nécessite un fichier de règles écrit.
Q2 : Quand une vérification échoue pendant que l'agent est en train de travailler, doit-elle bloquer ou avertir ?
ESTIMATION : Bloquer. Vous exécutez des agents sans surveillance et un avertissement que personne ne lit est un avertissement inutile.
PAR DÉFAUT si incertain : Bloquer sur le socle, avertir sur tout le reste pendant les deux premières semaines.
Q3 : Avez-vous des chiffres cibles en tête, ou dois-je mesurer où vous en êtes aujourd'hui et maintenir cette ligne ?
ESTIMATION : Mesurer. La plupart des équipes n'ont pas de chiffre, et un chiffre inventé est ignoré.
PAR DÉFAUT si incertain : Mesurer et maintenir. Voir « Cliquets » ci-dessous.
Q4 : Quelle est la vérification la plus lente que vous tolérerez avant que l'agent ne rende le travail ?
ESTIMATION : Environ 90 secondes. Plus long et vous arrêterez de l'exécuter.
PAR DÉFAUT si incertain : 90 secondes à la fin de la tâche, illimité en CI.
Arrêtez-vous à quatre. Un questionnaire de douze questions produit une configuration que personne ne comprend et un utilisateur qui regrette d'avoir commencé.
Étape 3 : Écrire CONSTRAINTS.md
Un fichier à la racine du dépôt. N'importe quel agent sur n'importe quelle infrastructure peut le lire, et une modification apparaît dans la revue là où elle doit être.
# Contraintes
Dernière révision : 2026-08-08 par @mukhon-core
## Socle (toujours appliqué, aucune configuration requise)
- Aucun nouveau commentaire de suppression : `@ts-ignore`, `eslint-disable`, `# noqa`, `# type: ignore`
- Aucune ébauche non implémentée : `throw new Error("Not implemented")`, `catch {}` vide
- Aucun test ignoré ou supprimé sans raison dans le message de commit
- Aucun secret dans le code source
- Ce fichier n'est pas affaibli pour faire passer une modification
## Appliqué avec des chiffres
| Dimension | Règle | Vérifié par | S'exécute à |
|-----------|-------|-------------|-------------|
| Types | Zéro erreur de type | `tsc --noEmit` | chaque édition |
| Linting | Zéro erreur de notre config | `biome check` | chaque édition |
| Secrets | Aucun secret dans le code source | `gitleaks detect --redact` | chaque édition |
| Couverture | Lignes modifiées ≥ 80 % couvertes | `vitest run --coverage` + git diff | fin de tâche, CI |
| Sécurité : code | Aucune constatation élevée | `semgrep scan --config p/default` | CI |
| Sécurité : dépendances | Rien à haut niveau ou au-dessus | `osv-scanner scan source -r .` | CI |
| Accessibilité | Zéro critique ou sérieux | `axe $PREVIEW_URL --tags wcag2a,wcag2aa,wcag21aa` | déploiement de prévisualisation |
| Performance | LCP ≤ 2500ms, CLS ≤ 0.1 | `lighthouse $PREVIEW_URL --output=json` | déploiement de prévisualisation |
Chaque ligne nomme la commande qui produit le verdict. Une dimension avec un
chiffre et aucune commande dans cette colonne est une aspiration, pas une contrainte.
## Mesuré, pas encore appliqué
| Métrique | Aujourd'hui | Direction |
|----------|-------------|-----------|
| Couverture du projet | 62.4% | ne doit pas baisser |
| Taille du bundle (principal) | 184 kB | ne doit pas augmenter |
## Exceptions
| ID | Règle | Chemin | Raison | Propriétaire | Expire |
|----|-------|--------|--------|--------------|--------|
| W1 | `no-explicit-any` | `src/legacy/**` | Réécriture suivie dans ENG-441 | @mukhon-core | 2026-11-01 |
Ajoutez ensuite une ligne à AGENTS.md et CLAUDE.md : Lisez CONSTRAINTS.md avant d'écrire du code. Ne l'affaiblissez pas pour faire passer une modification.
Étape 4 : Installer ce dont chaque dimension a besoin
Choisir une dimension signifie installer quelque chose. Ne laissez pas l'utilisateur avec un chiffre et aucun mécanisme, et n'inventez pas votre propre vérificateur quand un outil de facto existe — ces outils sont listés parce que leurs formats de règles et leurs seuils sont ce que tout le reste de l'écosystème cible, donc la configuration existante de l'équipe continue de fonctionner.
| Dimension | Outil | Installation | Exécution | Porte sur |
|---|---|---|---|---|
| Types (TS) | tsc | déjà présent | tsc --noEmit | toute erreur |
| Types (Python) | mypy | pip install mypy | mypy . | toute erreur |
| Linting | votre config existante | déjà présent | eslint . / biome check / ruff check | toute erreur |
| Couverture (JS) | votre lanceur de tests | déjà présent | vitest run --coverage (ou jest --coverage) | couverture des lignes modifiées |
| Couverture (Python) | pytest-cov | pip install pytest-cov | pytest --cov --cov-report=lcov | idem |
| Sécurité : code | Semgrep | pipx install semgrep | semgrep scan --config p/default --config p/owasp-top-ten | toute constatation élevée |
| Sécurité : secrets | gitleaks | brew install gitleaks | gitleaks detect --redact --no-banner | toute constatation |
| Sécurité : dépendances | osv-scanner | brew install osv-scanner | osv-scanner scan source -r . | haut niveau ou plus |
| Performance : page | Lighthouse | npm i -D lighthouse | lighthouse $URL --output=json --quiet | LCP, CLS, score de performance |
| Performance : bundle | size-limit | npm i -D size-limit | size-limit --json | budget d'octets par entrée |
| Accessibilité | axe-core | npm i -D @axe-core/cli | axe $URL --tags wcag2a,wcag2aa,wcag21aa | zéro critique ou sérieux |
| Architecture | dependency-cruiser | npm i -D dependency-cruiser | depcruise --validate src | toute violation |
| Qualité des assertions | Stryker | npm i -D @stryker-mutator/core | stryker run --mutate <fichiers modifiés> | score de mutation |
Cinq choses qui vous mordront si vous les sautez :
--redactsur gitleaks n'est pas optionnel. Sans lui, le secret correspondant atterrit dans le journal de l'agent, ce qui est ainsi qu'une clé divulguée se retrouve dans un journal, un résumé ou un message de commit. Signalez la règle et l'emplacement, jamais la valeur.- Lighthouse et axe ont besoin d'une URL. Ils ne fonctionnent que contre une application en cours d'exécution, donc ils appartiennent à l'étape d'exécution contre un déploiement de prévisualisation ou un serveur local que vous démarrez d'abord. Si le projet n'a pas d'URL à atteindre — un CLI, une bibliothèque, une application de bureau — dites-le et abandonnez la dimension plutôt que d'inventer une vérification qui ne peut pas fonctionner.
- Limitez les vérifications coûteuses au diff.
stryker run --mutatesur l'ensemble du dépôt prend des heures et est désactivé ; sur les fichiers qu'un changement a touchés, cela prend moins d'une minute. Idem pour Semgrep, qui accepte une liste de chemins. - La couverture n'a pas besoin d'une deuxième exécution de test. Lisez le lcov que votre suite écrit déjà et croisez-le avec
git diff. Exécuter la suite deux fois pour obtenir un nombre est le moyen le plus rapide de faire détester cette fonctionnalité. - Les règles de registre de Semgrep sont gratuites à exécuter ; vérifiez la licence avant de les redistribuer.
opengrepest un fork de remplacement avec le même format de règles et la même sortie JSON si cela compte pour votre équipe juridique.
Ajoutez chacun à son propre script dans le projet pour qu'il soit reproductible sans agent :
{
"scripts": {
"check:fast": "tsc --noEmit && eslint . && gitleaks detect --redact --no-banner",
"check:task": "npm run check:fast && vitest run --coverage",
"check:full": "npm run check:task && semgrep scan --config p/default && osv-scanner scan source -r ."
}
}
Ce mappage importe plus que les outils. check:fast est ce qui s'exécute après une édition, check:task quand l'agent pense avoir terminé, check:full en CI.
Les commandes vivent maintenant à deux endroits — la colonne Vérifié par dans CONSTRAINTS.md et ces scripts. CONSTRAINTS.md est la source canonique : elle porte la raison à côté de chaque commande et elle apparaît dans la revue. Les scripts sont des wrappers de commodité qui doivent la refléter, pas une deuxième source de vérité ; s'ils dévient, le fichier gagne.
Étape 5 : Câbler au cycle de vie
La plus grosse erreur est d'exécuter partout tout. Une vérification qui bloque l'agent est désactivée, et une porte que les gens ont désactivée est pire que pas de porte, car la barre semble toujours exister.
| Phase | Commande | Ce qui s'exécute | Budget |
|---|---|---|---|
| BUILD | /build | Types, linting, secrets, le socle | moins de 5s, fichier modifié uniquement |
| VERIFY | /test | Tests associés, couverture sur les lignes modifiées | moins de 90s |
| REVIEW | /review | Tout, plus les gardes ci-dessous | minutes |
| SHIP | /ship | Vérifications de direction, aucune régression | CI |
Deux règles qui rendent cela tolérable :
- Limitez au diff. Vérifiez les lignes que ce changement a touchées, pas l'ensemble du dépôt. La couverture des lignes modifiées est un nombre que l'agent peut faire bouger ; la couverture du projet est un nombre qu'il a hérité.
- Le coût décide du placement. Tout ce qui dépasse quelques secondes sort de la boucle d'édition. Les tests de mutation sur un dépôt entier prennent des heures ; sur les fichiers qu'un changement a touchés, cela prend moins d'une minute, ce qui est la différence entre une vérification que les gens exécutent et une qu'ils n'exécutent pas.
Étape 6 : Garder la barre elle-même
Quelqu'un fera remarquer que si l'agent écrit le code et les vérifications, les vérifications ne prouvent rien. À moitié vrai, et cela vaut la peine d'être conçu pour contourner ce problème.
Les agents ne créent pas de failles intelligentes. Ils rencontrent une vérification rouge et prennent le chemin le moins coûteux vers le vert. Surveillez ces cinq mouvements dans le diff, au moment de la revue :
- Le seuil a bougé. Un budget réduit, une sévérité abaissée, une vérification retirée de l'étape rapide. Comparez
CONSTRAINTS.mdavec son état au point de branchement. - Un test est devenu plus facile.
.skipajouté, un fichier de test supprimé, des assertions retirées de tests qui sont restés. - Un vérificateur a été réduit au silence. Nouveau
@ts-ignoreoueslint-disable. Quatre suppressions méritent une attention particulière car elles désactivent une vérification sur laquelle vous comptez :istanbul ignoreretire du code de la couverture au lieu de le tester,Stryker disablecache un mutant survivant,nosemgrepetgitleaks:allowle font pour les constatations de sécurité. - Le travail est inachevé. Une ébauche qui lève une exception, un
catchvide qui transforme un échec en silence, unTODOqui se tient à la place de l'implémentation. - Une exception est apparue. Une nouvelle ligne dans le tableau des Exceptions dont personne n'a parlé.
Rien de tout cela n'a besoin d'outillage au-delà de git diff. Resserrer la barre doit être silencieux ; la desserrer doit être bruyant.
Contrairement aux dimensions numérotées, le socle n'a pas d'outil de facto propre, donc un agent chargé de l'appliquer a tendance à écrire un vérificateur à partir de zéro, et deux agents en écrivent deux différents. Une implémentation de référence de ces cinq vérifications est livrée avec cette compétence dans references/floor-guard.md (limitée au diff, sortie 0/1/2, motifs adaptables par écosystème). Adaptez cela plutôt que de le réinventer, pour la même raison que chaque dimension nomme un outil de facto : afin que le mécanisme soit le même d'une exécution à l'autre et d'une pile à l'autre.
Toutes les vérifications ne sont pas également circulaires. Classez-les selon une question : l'agent peut-il faire passer cette vérification en écrivant du code qui ne marche pas ?
- Externe — axe-core encode WCAG,
osv-scannerlit une base de données de vulnérabilités, Lighthouse mesure un vrai navigateur. L'agent ne peut pas discuter avec cela. - Projet — vos règles de linting, vos limites de couches. Un humain possède le fichier.
- Suite — vos propres tests. Les plus utiles, et les seuls véritablement circulaires.
Comment rendre la suite moins circulaire :