Ma configuration de développement avec agents IA — une seule source de vérité, partagée entre Claude Code et Codex.
Écrire une règle, une commande ou un agent une fois, et l'avoir partout où c'est possible. C'est tout l'objet de ce repo.
Les règles sont partagées par liens symboliques. Les commandes restent la source de vérité : Claude Code les lit directement et le script d'installation génère les skills Codex correspondants.
AI-rules/me.md ──┬──→ ~/.claude/CLAUDE.md
└──→ ~/.codex/AGENTS.md
AI-rules/commands/ ──┬──→ ~/.claude/commands/
└──→ ~/.codex/skills/<nom>/SKILL.md (généré par install.sh)
AI-rules/agents/ ──────→ ~/.claude/agents/ Claude Code uniquement
AI-rules/hooks/ ──────→ ~/.claude/hooks/ Claude Code uniquement
AI-rules/methods/ ──────→ injecté dans les skills Codex (voir « Déléguer sans casser Codex »)
Conséquence : une nouvelle commande reste écrite une seule fois. Elle est immédiatement
disponible dans Claude Code ; il faut relancer install.sh puis redémarrer Codex pour
regénérer et recharger le skill.
Les agents et les hooks n'ont pas d'équivalent Codex. Une commande qui délègue à un
agent reste donc utilisable dans les deux outils : install.sh injecte la méthode
correspondante dans le skill Codex, qui l'applique lui-même.
C'est la distinction qui structure tout le reste, et elle ne se remplace pas :
- Une commande est un protocole. Une séquence, des points d'arrêt, un format de sortie. Source de vérité unique, partagée Claude Code + Codex.
- Un agent est un exécutant. Un contexte isolé, une frontière d'écriture dure, un rapport de mission. Claude Code uniquement, surcouche pure.
Une commande sans agent reste utilisable. Un agent sans sa commande n'existe pas.
cd AI-rules && ./install.shLe script est idempotent et refuse d'écraser un fichier existant non vide. Il affiche
ensuite le bloc de déclaration du hook à coller dans ~/.claude/settings.json, et la
commande pour vérifier qu'il bloque réellement.
Redémarre Claude Code après : le watcher ne voit pas un répertoire créé après le
démarrage de la session. Vérifie ensuite avec /agents et /hooks.
| Commande | Quand | Ce qu'elle fait |
|---|---|---|
cdc |
Au démarrage d'un projet | Le cahier des charges — le quoi et le pourquoi. POC optionnel |
cdd |
Après le cahier des charges | Le cahier de dev — jalons, étapes, dépendances |
rules |
Une fois par projet | Analyse le code réel et génère le CLAUDE.md + AGENTS.md du projet |
explain |
Code inconnu ou surprenant | Explique un fichier, une fonction ou un extrait, en lecture seule |
spec |
Avant une vraie feature | Cadre l'implémentation, produit ai/specs/<feature>.spec.md |
test-setup |
Une fois par projet | Met en place le runner et le premier filet, après validation |
test-write |
Avant ou après le code d'un module | Conçoit les cas depuis la spec, me les fait valider, puis les écrit |
test-gap |
Avant une mise en production | Audite la couverture réelle, trie les trous par coût de l'erreur |
e2e |
Feature avec un parcours utilisateur | Tests de bout en bout, Playwright |
verify |
Quand l'agent dit « c'est fini » | Lance les vraies gates du projet et rapporte la sortie brute |
review |
Fin d'étape ou de jalon | Relit le diff contre la spec et les conventions. Constate, ne corrige pas |
deps-audit |
Après une installation, avant une prod | CVE, scripts non approuvés, signaux de typosquat |
clean-commit |
Avant de commiter | Nettoyage, secrets, gates, message conventionnel |
context |
Blocage ou changement d'outil | Point de situation dans ai/context/, pour reprendre à froid |
issue-capture |
Une idée surgit pendant autre chose | Crée l'issue en vingt secondes et te renvoie au chantier |
issue-start |
Début ou reprise d'un chantier | Dépendances, écart contrat ↔ code réel, plan de session, branche |
issue-pause |
Fin de session | Poste le point de reprise sur l'issue |
issue-finish |
Le chantier est terminé | Critères un par un, gates, découpage en commits, PR en draft |
Dans Claude Code, une commande s'invoque avec /nom. Dans Codex, le skill
correspondant s'invoque avec $nom ou depuis le sélecteur de skills.
À ne pas confondre — c'est la confusion la plus fréquente et la plus coûteuse :
| Document | Répond à | Ne contient pas |
|---|---|---|
ai/cdc.md |
Où veut-on arriver, et pourquoi | Aucune techno, aucun fichier |
ai/cdd.md |
Dans quel ordre, en combien de jalons, quoi dépend de quoi | Le détail d'implémentation |
ai/specs/*.spec.md |
Comment on construit cette étape | Le code lui-même |
Si une spec se retrouve à décrire l'objectif du projet, ou un cahier des charges à citer des fichiers, on est au mauvais niveau.
/cdc le problème, le périmètre, le HORS périmètre
↓
/cdd jalons, étapes, dépendances — l'état d'avancement vit ici
↓
/rules une fois par projet — les conventions réelles, écrites
/test-setup une fois par projet — de quoi faire échouer une régression
↓
/spec la règle métier, en français, avant le code
↓ sa section « Tests » est la source de vérité de l'étape suivante
/test-write conception des cas → ⏸ je relis 20 lignes → écriture → exécution
↓
[ code ] l'implémentation, en session normale ou déléguée à l'agent dev
↓
/verify typecheck → lint → format → test → build, résultat brut
↓ la gate test compte les tests : une suite vide n'est pas un vert
/review le diff contre son contrat — constate, ne corrige pas
↓
/clean-commit secrets, gates, staging fichier par fichier, message conventionnel
Le point d'arrêt de /test-write est le seul endroit où la relecture humaine est vraiment
rentable : vingt lignes de cas en français plutôt que trois cents lignes de code de test.
Le workflow complet ne se déroule pas pour tout. C'est même la moitié du gain de temps :
| Tâche | Ce qu'on lance |
|---|---|
| Correction, ajustement, petit fix | Rien, puis /clean-commit |
| Feature réelle, refacto structurant | /spec → /test-write → travail → /verify → /clean-commit |
| Feature sans logique métier (UI, contenu) | /spec → travail → /verify → /clean-commit |
| Nouveau projet | /cdc → /cdd, puis /rules et /test-setup |
| Avant une mise en production | /test-gap puis /deps-audit |
| Blocage ou passage Claude ↔ Codex | /context |
| Code repris, hérité ou surprenant | /explain |
| Chantier sur plusieurs sessions | /issue-start → travail → /issue-pause → … → /issue-finish |
/test-write sur un composant d'affichage ou une page statique ne vaut pas son coût :
la commande te le dira elle-même plutôt que de produire des tests de remplissage.
Claude Code uniquement. Un agent travaille dans sa propre fenêtre de contexte : ce qu'il lit ne remonte pas dans la conversation principale, seul son rapport revient.
| Agent | Rôle | Écrit | Mode |
|---|---|---|---|
analyst |
Cadre, challenge, rédige les documents | ai/** (hors rapports) · *.md |
session --agent |
dev |
Implémente une étape d'une spec validée | code source · son rapport | sous-agent |
tester |
Conçoit, audite et écrit les tests | tests, configs de test · son rapport | sous-agent |
reviewer |
Relit un diff | son rapport seul | sous-agent |
Aucun agent ne peut écrire là où un autre écrit, et aucun ne juge ce qu'il produit.
analyst ne code pas ce qu'il spécifie. dev ne teste pas ce qu'il code. tester ne
corrige pas le code qu'il fait échouer. reviewer ne répare pas ce qu'il critique.
Tout ce qui traverse une frontière passe par moi, via un rapport de mission. C'est le seul point de couplage du système, et il est volontaire.
claude --agent analystAskUserQuestion est retiré de tout sous-agent : un sous-agent ne dialogue pas, il part
et rend un rapport. Comme l'intérêt de l'analyst est justement d'échanger — qu'il conteste
l'idée, qu'on en discute — il tourne comme session principale. Il y garde ses frontières
d'écriture, ses hooks et son prompt de challenger.
Sa fonction première est de contredire. S'il se contente de mettre en forme ce qu'on lui dit, il ne sert à rien : on écrit plus vite que lui.
Tout sous-agent s'arrête et rapporte dans quatre cas : mission accomplie ; spec fausse, contradictoire ou muette ; décision qui ne lui appartient pas ; gate en échec hors de son périmètre.
Un rapport « bloqué » est un succès. Une mission terminée en ayant tranché à ma place est un échec, même si le code marche.
Chaque agent écrit dans son propre dossier, jamais dans celui d'un autre :
ai/reports/dev/jalon-3/2026-08-14-1530-etape-3-1.md
ai/reports/tester/jalon-3/2026-08-14-1605-etape-3-1.md
ai/reports/reviewer/jalon-3/2026-08-15-0930-revue.md
Agent → jalon → étape horodatée. L'horodatage vient de date, jamais de l'invention du
modèle. Le tri alphabétique donne le tri chronologique.
Le rapport complet va dans le fichier, un résumé de cinq lignes en sortie de conversation. La boucle de retour devient un chemin plutôt qu'un copier-coller :
claude --agent analyst
# « lis ai/reports/dev/jalon-3/2026-08-14-1530-etape-3-1.md »ai/ est versionné. Contrats et rapports suivent le dépôt d'une machine à l'autre.
Conséquence directe : tout ce qui s'y écrit part dans un commit, donc les agents masquent
les secrets par [REDACTED]. Un secret dans un fichier local se supprime ; dans
l'historique Git, non.
Un jalon livré peut voir son dossier de rapports supprimé : l'information utile a déjà rejoint les commits, les specs et la PR.
Un agent qui déduit quoi tester en lisant l'implémentation encode le comportement actuel,
bugs compris. Il produit des tests verts qui certifient le bug. Sans spec, tester le dit
et marque ses cas [DESCRIPTIF] — filet de régression, pas vérification.
C'est aussi pourquoi tester lit un rapport du dev pour savoir ce qui a été fait,
jamais comment.
Le garde-fou central. Déclaré une seule fois en PreToolUse global, il dispatche sur
le champ agent_type que Claude Code place dans l'entrée du hook :
analyst → docs-only ai/** hors reports, *.md
dev → code-only code source, ai/reports/dev/
tester → tests-only tests, configs de test, package.json, ai/reports/tester/
reviewer → reports-only ai/reports/reviewer/ seulement
(autre) → protect-tests tout sauf les fichiers de test existants
Pourquoi en global plutôt que dans le frontmatter de chaque agent : un agent créé plus tard sans déclaration tombe dans le cas par défaut, donc protégé. Le frontmatter fait l'inverse — l'oubli ouvre. Un harnais dont l'oubli ferme est le seul qui tienne dans le temps.
Il applique aussi deux règles mécaniquement plutôt que par prompt : l'allowlist des
paquets de test (pnpm add -D vitest-dom-helper est refusé) et l'obligation
d'--ignore-scripts sur npm et yarn. Un nom de paquet plausible mais inconnu est le
vecteur principal de compromission d'une chaîne d'approvisionnement : quelqu'un publie
exactement le nom qu'un modèle est susceptible d'halluciner.
Le mode protect-tests reprend le hook historique : mis en boucle sur « fais passer les
tests », un agent finit statistiquement par affaiblir l'assertion plutôt que corriger le
code. Une règle en langage naturel est une incitation ; un hook est une contrainte.
Il couvre Edit, Write, NotebookEdit et Bash — ce dernier par heuristique.
Ce n'est pas une frontière de sécurité : une commande suffisamment tordue passe, un
script indirect aussi. C'est un garde-fou contre la dérive ordinaire.
Le verrou de test se lève à la main, jamais par l'agent :
touch .claude/.allow-test-edits # puis rm quand c'est finiToute commande contenant allow-test-edits est bloquée, pour que l'agent ne puisse pas
lever sa propre contrainte. Le verrou ne desserre que la protection des tests en mode
par défaut : il n'ouvre aucune frontière d'agent.
install.sh pose le fichier mais ne modifie pas ton settings.json : il affiche le bloc à
coller, et la commande pour vérifier que le hook bloque réellement. Un hook de gating qui
semble marcher sans rien bloquer est le pire des deux mondes. Il dépend de jq ; sans lui
il se désactive en le signalant.
Hook SessionStart, lecture seule. Quand la branche courante suit la convention
<type>/<numero>-<slug>, il injecte au démarrage les faits bruts du chantier : le contrat de
l'issue, le dernier checkpoint, l'état du worktree, la PR liée s'il y en a une. À défaut de
branche conventionnelle, il retombe sur l'issue portant le label next.
Il n'analyse rien — c'est le travail de /issue-start. Il évite seulement de recommencer une
session en redécouvrant un sujet déjà cadré, ou pire, en rediscutant une décision déjà prise.
Hors convention, hors dépôt Git, sans gh ou sans jq, il sort en 0 sans rien afficher.
Trois moyens, différents, à utiliser dans cet ordre :
/hooks— liste tous les hooks configurés, par événement, avec le fichier de settings d'où chacun vient. Dit ce qui est configuré.- Le test d'injection — envoyer un faux appel d'outil au script et vérifier
exit=2. Dit si le script fonctionne. - Le test réel — lancer l'agent
devet lui demander d'ajouter un test. Dit si l'ensemble bloque vraiment.
Le troisième est le seul qui compte. Les deux premiers peuvent être verts pendant que rien ne bloque.
Les commandes issue-* remplacent le fil de conversation par les issues GitHub comme mémoire
longue. Le fil disparaît à la fermeture de la fenêtre ; l'issue est encore là dans trois semaines,
et lisible depuis n'importe quel outil.
board « DKP - Works » idée notée à la main, tous produits confondus
↓ Convert to issue l'élément du board devient l'issue #42 d'un dépôt
/issue-start 42 dépendances, contrat, écart contrat ↔ code, plan, branche
↓
[ travail ] /spec, /test-write, /verify, /clean-commit comme d'habitude
↓
/issue-pause point de reprise posté sur l'issue, en fin de session
↓ (autant de fois que de sessions)
/issue-finish critères un par un, gates, commits, PR en draft
Sur un projet piloté par un cahier de dev plutôt que par des issues, c'est ai/cdd.md qui
porte le contrat et l'avancement. Les jalons peuvent être projetés en issues GitHub — issue
parente, étapes en sous-issues via gh issue create --parent — quand un suivi partagé est
utile. Le cahier de dev local reste la source de vérité ; GitHub est une projection, un jalon
à la fois, pour que le board ne devienne pas ingérable.
/issue-start refuse de démarrer une étape dont les prérequis sont encore ouverts, et dit
lesquels.
Hors de ce cycle, /issue-capture sort une idée de la tête sans quitter le chantier en cours :
elle crée l'issue et te renvoie au travail, sans l'analyser ni l'ouvrir.
Aucune commande n'écrit sur le board. Elles ne touchent qu'aux issues — corps, commentaires, labels — et à Git. Le board se remplit par le workflow Auto-add to project de GitHub, qui ajoute toute nouvelle issue des dépôts que tu sélectionnes. Deux raisons : ce que tu ranges à la main ne peut pas être défait par une session, et une issue créée depuis le web ou un autre poste arrive dans le board de la même façon. Un élément déjà présent n'est jamais dupliqué.
À régler une fois dans l'UI du board, il n'y a pas d'API pour ça :
- workflow Auto-add to project, un dépôt par ligne — vérifie la limite de dépôts, elle a bougé selon les versions de Projects ;
- workflows Item added → Status Todo et Item closed → Status Done ;
- vue Next filtrée sur
label:next, vue Inbox filtrée surlabel:inbox.
Deux labels portent l'état, et les commandes les gèrent seules : inbox marque une idée sans
contrat, posé par /issue-capture ; next marque le chantier courant du dépôt, posé par
/issue-start — un seul à la fois, retiré par /issue-finish. Rien à créer à la main, les
commandes créent le label s'il manque.
Les numéros d'issue sont propres à chaque dépôt : il y aura un #42 dans plusieurs de tes projets.
/issue-start 42 travaille sur le dépôt courant, donc lance-la depuis le bon répertoire. Le board
affiche le dépôt à côté de chaque élément.
| Niveau | Fichier | Contenu |
|---|---|---|
| Global | AI-rules/me.md |
Qui je suis, comment je veux qu'on travaille. Stable, vrai partout. |
| Projet | CLAUDE.md à la racine du projet |
Stack, arborescence, vraies commandes, pièges. Généré par /rules. |
Les deux se cumulent automatiquement, et le niveau projet prime.
Le fichier projet ne se symlinke pas vers ce repo : chaque projet a le sien, avec ses spécificités. Côté projet, /rules fait pointer AGENTS.md vers CLAUDE.md pour la même raison qu'ici — un seul fichier à maintenir.
Le harnais repose sur trois niveaux, et il faut savoir lequel fait quoi :
| Niveau | Portée | Ce qu'il règle |
|---|---|---|
permissions.allow / deny |
Session entière | Ce qui passe sans demander, ce qui ne passe jamais |
permissionMode |
Par agent (frontmatter) | Comment l'agent traite une demande de permission |
scope-guard.sh |
Par agent (via agent_type) |
Où l'agent a le droit d'écrire |
Une règle deny prime sur tout allow et sur n'importe quel permissionMode, y compris
bypassPermissions. C'est le seul niveau qui ne se contourne pas : git push,
git reset --hard, la lecture des .env y vivent.
Le critère pour décider ce qu'un agent peut toucher n'est pas la nature du fichier, c'est
la réversibilité : git checkout annule ? Alors l'agent le fait et le déclare. Sinon,
il propose et j'applique. Un tsconfig ou un Dockerfile est du texte versionné ; une
migration lancée sur une base et un volume Docker supprimé ne le sont pas.
- Créer
AI-rules/commands/<nom>.md - Frontmatter minimal, compatible avec les deux outils :
---
description: Ce que fait la commande
argument-hint: [ce qu'on peut passer après]
---
Les instructions. "$ARGUMENTS" reçoit ce qui est tapé après la commande.Elle est immédiatement disponible via /<nom> dans Claude Code. Relancer ensuite
AI-rules/install.sh et redémarrer Codex pour disposer de $nom.
Une commande mérite d'exister si elle encode une checklist qu'on oublierait, impose un format, ou contraint l'agent. Si elle se résume à « fais le travail correctement », c'est un prompt normal.
- Créer
AI-rules/agents/<nom>.mdavecname,description,tools, et lesskillsqu'il précharge. - Ajouter son mode dans
scope-guard.sh. Sans ça il tombe dans le cas par défaut : protégé, mais avec le périmètre d'une session normale. - Redémarrer Claude Code, vérifier avec
/agents.
Un agent mérite d'exister quand une mission entière peut lui être déléguée sans qu'on soit dans la boucle pendant. Si tu dois valider trois choses en cours de route, c'est une commande qu'il te faut, pas un agent.
/agents liste les agents mais n'ouvre plus d'assistant de création depuis la v2.1.198 :
les fichiers s'écrivent directement.
- Commande Claude Code : déclenchement explicite via
/nom. - Skill Codex : déclenchement explicite via
$nom, ou implicite selon la description.
Le contenu métier reste commun, mais les mécanismes d'intégration diffèrent. Le script
convertit le frontmatter Claude Code vers le frontmatter minimal attendu par Codex et
retire le placeholder $ARGUMENTS, qui n'existe pas dans les skills.
Codex n'a pas d'agents. Une commande qui délègue serait donc inutilisable — ou il faudrait dupliquer la méthode dans la commande, et maintenir deux textes.
À la place, un marqueur en fin de commande :
<!-- fallback:methods/test-design.md -->Claude Code n'y voit qu'un commentaire HTML et délègue au vrai agent. install.sh, en
générant le skill Codex, remplace la ligne par le contenu du fichier référencé, sous un
titre d'annexe. Le skill reste autonome, et la méthode n'existe qu'à un seul endroit.
Le fichier référencé est relatif à AI-rules/. S'il est introuvable, la génération le
signale sur stderr au lieu de produire un skill amputé en silence.
dev-with-ai/
├── README.md
├── ai/ # cahier des charges, cahier de dev, specs, rapports — VERSIONNÉ
└── AI-rules/
├── me.md # règles globales
├── commands/ # les commandes, partagées Claude Code + Codex
├── agents/ # les agents, Claude Code uniquement
├── methods/ # méthodes injectées dans les skills Codex par fallback
├── hooks/ # contraintes mécaniques, Claude Code uniquement
├── install.sh # pose les liens et génère les skills Codex
└── ARCHIVES/ # version OpenCode — OBSOLÈTE, ne pas appliquer
me.md décrit mon contexte de travail. Deux choses valent d'y être ajoutées quand
l'occasion se présente :
- ce qui change dans mon contexte — types de projets récurrents, contraintes clients ;
- les corrections que je me surprends à répéter à l'IA. C'est le meilleur indicateur d'une règle manquante.