Utiliser quand l'utilisateur veut CRÉER ou définir un nouveau loop auto-cadencé (pas en lancer un existant). Conçoit un loop SUR MESURE (closed-loop discovery→planning→execution→verification→iteration), demande la portée (globale ~/.claude ou per-project <repo>/.claude) et les dimensions (mode closed/open, trigger self-pace/stop-hook, single/fleet) si non précisées, puis scaffolde la skill me:loop:<name> + sa commande /me:loop:<name> (+ hook si stop-hook).
Install
npx skillscat add kbrdn1/dotfiles/me-create-loop Install via the SkillsCat registry.
me:create-loop — Concevoir un loop sur mesure (skill me:loop:<name>)
Ce skill crée un loop auto-cadencé matérialisé comme la skill me:loop:<name> (invocable /me:loop:<name>). Il ne lance rien — il conçoit et scaffolde la définition.
Pour exécuter un loop, c'est
me:run-loop(ou directement/me:loop:<name>).
🎯 Principe : custom, pas un gabarit rigide
Un loop se conçoit en fonction du besoin réel. Les gabarits ci-dessous sont un point de départ, pas un carcan : adapte les phases, omets ce qui n'a pas de sens, ajoute ce qui manque. 🔴 Dès qu'un élément structurant manque ou est ambigu, INTERROGE l'utilisateur — ne devine pas. Mieux vaut deux questions ciblées qu'un loop bancal.
🔁 Modèle conceptuel — closed-loop en 5 phases
Tout loop suit ce cycle (réf. « agent looping ») ; chaque phase est adaptée au besoin :
- Discovery — trouver ce qu'il faut savoir avant d'agir (contexte projet, état courant).
- Planning — découper l'objectif en étapes claires.
- Execution — faire le travail (les
steps). - Verification — l'eval gate :
check_command+ évaluation deexit_whensur sa sortie réelle. - Iteration — corriger les écarts, reboucler ; sinon stop / handback à l'utilisateur.
Un loop simple (ex. test-until-green) condense discovery+planning dans un step ; un loop riche les explicite. À toi de juger.
Dimensions à fixer (demander si non précisé)
| Dimension | Valeurs | Défaut | Quand demander |
|---|---|---|---|
portée <base> |
globale ~/.claude | per-project <repo>/.claude |
— | 🔴 toujours si absent |
| mode | closed (borné) | open (exploratoire) |
closed |
🔴 avant tout open (coût tokens élevé) |
| trigger | self-pace (session) | stop-hook (hook Stop) |
self-pace |
si l'utilisateur veut une relance automatique |
| portée d'exécution | single (1 agent) | fleet (orchestrateur+specialists+subagents) |
single |
si l'objectif est gros/décomposable |
Étape 1 — Discovery du besoin
Recueille (ou infère du message, puis confirme) : le goal, les 4 dimensions, et de quoi remplir les phases. Pose des questions ciblées pour tout champ structurant manquant. Inspecte au besoin le contexte projet (CLAUDE.md, .claude/rules/, stack) pour proposer un check_command réaliste.
Étape 2 — Champs du loop
goal— objectif mesurable.max_iterations— plafond d'itérations (entier > 0).check_command— l'eval gate : commande shell réelle, exécutable telle quelle.exit_when— condition observable dans la sortie decheck_command.steps(Execution) +, si pertinent, notes de Discovery / Planning / Iteration.- si
mode: open→budget(plafond tokens/itérations) + garde-fous. - si
fleet→ décomposition orchestrateur → specialists → subagents.
Étape 3 — Valider avant d'écrire
🟡
check_commandest une vraie commande shell (pas une description). S'appuyer sur le contexte repo (package.json,Makefile,composer.json,cargo…).🔴
exit_whenest observable dans la sortie decheck_command(code de sortie,0 failing,P0P1_COUNT=0,Build succeeded…). Sinon reformuler / changer de check.🔴 Garde anti-faux-propre — si
exit_whense déduit d'un parsing de texte (et non d'un code de sortie), le check DOIT exiger une preuve d'exécution positive avant de rendre un verdict, et sortir en erreur — jamais en zéro — quand elle manque. Sans ça, un outil en échec (quota épuisé, binaire absent, rapport vide) rend une sortie vide que le parser lit « 0 problème » : le loop déclare le succès sans qu'une ligne ait été vérifiée. Le motif de la preuve doit exiger un chiffre (RESUME:[[:space:]]*P0=[0-9]), sinon un gabarit de prompt réémis dans la sortie le satisfait tout seul. Vécu deux fois : #772 (2026-09-01), rejoué à l'identique #1008 (2026-09-02). Gabarit :grep -qE "<motif de preuve avec un chiffre>" "$OUT" || { echo "❌ check NON exécuté — aucune preuve d'exécution dans la sortie" grep -iE "usage limit|rate limit|quota|unauthorized|ERROR:" "$OUT" | head -3 echo " (ne PAS lire ce résultat comme 0 — relancer, ou basculer sur <fallback>)" exit 2 }🔴 Test de mutation du gate — le loop n'est pas livrable tant que son
check_commandn'a pas été VU rouge. La garde anti-faux-propre prouve que le check a tourné. Rien ne prouve qu'il sait échouer. Uncheck_commandcassé qui rend toujours0ressemble exactement à un projet parfait, et rien ne le signale — jamais. Donc, avant de déclarer le loop utilisable, trois observations, dans cet ordre :- vert sur l'état courant (propre),
- rouge après avoir cassé volontairement une chose que le check doit attraper — inverser une assertion, retirer un flag des docs, injecter une violation a11y, ajouter un job CI qui échoue,
- vert de nouveau après le revert.
Si l'étape 2 n'échoue pas, le gate n'est pas branché. Pas de discussion : on corrige le check, on ne livre pas le loop.
Vécu le 2026-09-07 sur
me:loop:docs-sync:set -o pipefail+grep -qfait échouer le pipeline quand le motif est trouvé (grep sort au premier match → SIGPIPE surprintf→ 141). Le check rapportait 216 écarts inexistants. Il n'a été démasqué que parce qu'il criait--help documenté, absent de --help— s'il avait été bloqué à0au lieu de216, il aurait été livré vert à vie. Écrire l'observation des trois états dans le skill du loop, avec les chiffres réels.🟡
max_iterationsentier > 0 ; au moins un step.🔴
mode: open→ exiger unbudgetexplicite + avertir du coût ; sans standard clair, un open loop devient une « slop machine ».🟡
trigger: stop-hook→ prévoir le script + le hook dans le frontmatter de la skill (Étape 4c) ; rappeler que c'est non testé et que le caveat doc s'applique (cf. 4c).🟡
fleet→ exiger une décomposition nette (qui orchestre, quels specialists, quel eval gate global).
Étape 4 — Écrire (sur mesure)
4a — La skill : <base>/skills/me/loop/<name>/SKILL.md
<name> en kebab-case. Si le dossier existe, demander : écraser, renommer, éditer. Pars du gabarit self-pace closed ci-dessous et adapte-le au besoin (ajoute/retire des phases, ajuste le protocole) :
---
name: me:loop:<name>
description: Loop auto-cadencé — <goal>. Relance `<check_command>` jusqu'à « <exit_when> », max <max_iterations> itérations. Déclencheurs : "/me:loop:<name>", "lance le loop <name>", "<goal>".
---
# me:loop:<name>
Loop auto-cadencé. **mode:** <closed|open> · **trigger:** <self-pace|stop-hook> · **exécution:** <single|fleet> · **hardened:** true (bloc anti-triche de `me:run-loop`).
Applique le **PROTOCOLE SELF-PACE** ci-dessous (canonique dans `me:run-loop`).
## Définition
- **goal** : <goal>
- **max_iterations** : <max_iterations>
- **check_command** (eval gate) : `<check_command>`
- **exit_when** : <exit_when>
<- **budget** : <plafond> (si mode: open)>
## Cycle
- **Discovery** : <ce qu'il faut savoir / lire avant d'agir — contexte projet>
- **Planning** : <découpage en étapes>
- **Execution (steps)** :
Step 1: <…>
<Step 2…N>
- **Verification** : lancer `<check_command>`, lire la sortie réelle, évaluer `exit_when`.
- **Iteration** : corriger les écarts pertinents (selon le contexte projet : CLAUDE.md, .claude/rules, conventions) ; reboucler ou stop/handback.
## Protocole self-pace (compteur à 1)
0. Lire les guardrails (`git rev-parse --path-format=absolute --git-path loop-guardrails.md`) — chaque sign est une **contrainte dure**. Ne jamais supprimer ce fichier.
1. Exécuter Discovery+Planning+Execution (les steps).
2. Lancer `<check_command>` et **LIRE sa sortie réelle**. Ne jamais supposer le résultat.
3. Évaluer `exit_when` : si rempli → **STOP**, annoncer le succès (citer la preuve dans la sortie).
4. Sinon incrémenter. Si compteur ≥ <max_iterations> → **STOP**, annoncer la limite sans succès + ce qui bloque.
5. Même échec qu'une passe antérieure → **appender un sign** aux guardrails avant de retenter.
6. Sinon recommencer en respectant tous les signs.
Status à chaque passe : `🔁 Itération N/<max_iterations> — <tenté> → check: <résultat>`.
Garde-fous : ne jamais dépasser `max_iterations` ; jamais de succès sans `exit_when` vu dans la sortie ; **bloc anti-triche `hardened`** de `me:run-loop` (ne pas modifier le check ni l'exit_when, ne pas skipper une validation, ne pas remplacer une assertion réelle par un test toujours vert, corriger le code de production plutôt que rustiner le test).Adaptations selon les dimensions :
- mode: open → ajouter une ligne
budget; dans Iteration, autoriser l'exploration de pistes non spécifiées mais borner par le budget et un standard de qualité explicite (eval gate strict). Avertir l'utilisateur du coût. - fleet → remplacer la section Execution par une orchestration : l'agent courant joue l'orchestrateur, délègue chaque sous-objectif à un
Agent/Task(specialist), qui peut lui-même fan-out via subagents ; lecheck_commandreste l'eval gate global. Chaque specialist applique le même mini-cycle discovery→…→verification.
4b — La commande : <base>/commands/me/loop/<name>.md
---
description: "Loop auto-cadencé — <goal> ; relance `<check_command>` jusqu'à « <exit_when> », max <max_iterations> itérations"
---
Lance le loop **<name>** dans cette session : lis
`<base>/skills/me/loop/<name>/SKILL.md` et applique son **protocole self-pace**
directement (n'appelle PAS le Skill tool sur `me:loop:<name>` — la commande et la
skill partagent ce nom, donc le ré-invoquer reboucle sur ce shim). Respecte les
garde-fous de `me:run-loop`.
Arguments utilisateur : $ARGUMENTS⚠️ Pourquoi pas « Invoke the skill via the Skill tool ». La commande
me:loop:<name>et la skillme:loop:<name>portent le même nom →Skill(me:loop:<name>)
re-résout vers le shim (qui dit « invoque la skill ») → ping-pong infini. Le shim
doit donc pointer vers le chemin du SKILL.md pour application inline, sans dépendre
de la résolution de nom du Skill tool.
4c — (si trigger: stop-hook) Hook Stop déclaré au frontmatter de la skill
On le déclare dans le frontmatter de la skill me:loop:<name> plutôt que dans settings.json global, pour qu'il n'existe pas dans les sessions qui n'invoquent jamais ce loop.
🔴 Mais il ne se désarme PAS tout seul à la fin du loop. La doc est explicite : un hook de skill est enregistré à l'invocation de la skill et Claude Code « keeps running them for the rest of the session, on turns after the skill's own turn as well ». Donc, une fois le loop terminé, le hook continue de lancer check_command à chaque Stop de la session et peut re-bloquer sur un travail sans aucun rapport. → sentinelle d'armement obligatoire : le hook ne fait rien tant qu'un fichier .armed n'existe pas, et il le supprime dès que exit_when est atteinte ou max_iterations dépassé. C'est le Step 0 du loop de l'armer.
(once: true existe mais ne convient pas ici : il retire le hook après sa première exécution réussie, donc dès la première relance — ça tue le loop au lieu de le borner.)
Ajoute au frontmatter :
hooks:
Stop:
- hooks:
- type: command
command: "<chemin_absolu_ou_$CLAUDE_PROJECT_DIR>/skills/me/loop/<name>/stop-hook.sh"Et génère <base>/skills/me/loop/<name>/stop-hook.sh (chmod +x), gabarit :
#!/usr/bin/env bash
STATE="${TMPDIR:-/tmp}/me-loop-<name>"; ARMED="$STATE.armed"; CNT_FILE="$STATE.count"
disarm() { rm -f "$ARMED" "$CNT_FILE"; exit 0; }
# (0) SENTINELLE — le hook survit à la fin du loop (il reste enregistré pour toute la
# session). Sans ce garde, il relancerait check_command à chaque Stop, indéfiniment,
# sur du travail sans rapport. Le Step 0 du loop fait : touch "$ARMED".
[ -f "$ARMED" ] || exit 0
INPUT=$(cat)
# (1) Garde-fou anti-boucle natif : si un blocage Stop est déjà actif, laisser s'arrêter
[ "$(printf '%s' "$INPUT" | jq -r '.stop_hook_active')" = "true" ] && disarm
# (2) Compteur d'itérations (respecte max_iterations en plus du cap natif de blocages)
N=$(cat "$CNT_FILE" 2>/dev/null || echo 0)
[ "$N" -ge <max_iterations> ] && disarm
# (3) Eval gate — porte sa propre garde anti-faux-propre (cf. Étape 3) :
# une sortie qui ne prouve pas que le check a tourné doit DÉSARMER, pas boucler à vide.
OUT=$(<check_command> 2>&1); RC=$?
[ "$RC" = 2 ] && disarm # check inexécutable
printf '%s' "$OUT" | <test exit_when, ex: grep -q 'P0P1_COUNT=0'> && disarm # succès
echo $((N+1)) > "$CNT_FILE"
jq -n --arg r "exit_when non atteint (itération $((N+1))/<max_iterations>). Refais les steps du me:loop:<name> puis laisse le hook revérifier." '{decision:"block", reason:$r}'
exit 0Et le Step 0 du loop, côté skill : mkdir -p "$(dirname "$STATE")" && touch "${TMPDIR:-/tmp}/me-loop-<name>.armed".
⚠️ Ce qui est vérifié et ce qui ne l'est pas (2026-09-07) :
- ✅ vérifié dans la doc : les hooks de frontmatter de skill existent, même format que dans
settings.json, et restent enregistrés pour tout le reste de la session (d'où la sentinelle).decision: "block"+reasonsurStoprelance un tour ; le code de sortie 2 le fait aussi. - ❓ non vérifié : le cap natif de blocages
Stopconsécutifs et sa variable d'environnement ne sont pas documentés. Ne pas compter dessus —max_iterationsdans le script fait foi. - ❓ non testé end-to-end ici : le comportement sur un loop multi-tour réel. En cas de doute,
self-pacereste le défaut sûr.
Étape 5 — Confirmer
Affiche les chemins créés et comment lancer :
Loop créé (portée <globale|per-project>, mode <…>, trigger <…>, <single|fleet>) :
- skill
<base>/skills/me/loop/<name>/SKILL.md(me:loop:<name>)- commande
<base>/commands/me/loop/<name>.md(/me:loop:<name>)
<- hook<base>/skills/me/loop/<name>/stop-hook.sh(si stop-hook)>Pour lancer :
/me:loop:<name>— ou « lance le loop » (le moteurme:run-looproute). Redémarrage de session en général nécessaire pour la découverte.Si portée globale + dotfiles chezmoi → versionner (skill
chezmoi). Si per-project → committer avec le repo.