Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .claude/skills/coordinate-adjoint/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,18 +93,23 @@ La proposition `act_kind` est donc **mise en attente mesurée**, pas ajoutée au

- **Dérive de schéma** : cette section disait `--ledger pr-actions` + `kind` + `audited_head_sha` — drift silencieux. Le code aurait rejeté à chaque cycle avec `unknown_field`. Corrigé par lecture firsthand de `debt_ledger.py` ligne par ligne.
- **Journalisation manquée** : 33 `[ADJOINT CLOSE]` publiés sur GitHub n'ont **pas** été traduits en observations ledger valides (le CLI était absent de main, et la skill disait du faux). Backfill = geste ultérieur, après merge phase A — pas un skip silencieux.
- **`spool` n'est pas une sous-commande** : le CLI en déclare **trois** (`init`, `append`, `reduce`). Le *spool* est le **répertoire** désigné par le flag `append --out-dir` (défaut local `%LOCALAPPDATA%\CoursIA\debt-ledgers\<ledger>\spool\`). Une demande de « `spool --status` » se traite donc par un **audit du répertoire**, pas par un refus : compter les enveloppes, et recouper chacune contre les surfaces du dashboard.
- **Un dashboard a TROIS surfaces, et « absent de l'intercom vivant » ne dit rien des deux autres** : l'intercom vivant, l'**archive de condensation**, et le fichier Drive. Mesuré le 2026-09-26 : une enveloppe spoolée (`obs-5e28f86031aaae2e`) que j'avais déclarée « jamais postée » était en fait **postée puis archivée** par la condensation de 02:36:41Z — l'archive la portait, entière, avec son `observation_id`. Avant de conclure « jamais postée », lire l'archive (`action:"read_archive"`). Même classe que « un `find` qui ne rend rien n'est pas une absence » : un instrument muet ne mesure pas une absence, il mesure son propre silence.
- **Ne pas récupérer par acquittement ce qu'on n'a pas vérifié** : la conclusion « 1 enveloppe manquante » a produit un append de récupération qui s'est avéré un **doublon** d'un message déjà archivé (et qui a levé un `[FORK SUSPECTÉ]` en pure perte). Le contrôle qui l'aurait évitée coûtait un appel : `read_archive` **avant** de re-poster — la règle de l'organe lui-même (« relire le canonique avant tout retry ») nomme l'intercom, mais le canonique inclut l'archive.

## Émission de dossiers — garde-fous obligatoires

- **Instrument de mesure des checks (arbitrage ai-01 2026-09-21 + RECTIF 12:09Z)** : lire `commits/<sha>/check-runs` — **jamais** `actions/runs` (un `attempt=2` y garde l'ancien id plus petit : organe `dedupe_latest`, `scripts/pr_gate.py` l.537, mesure #11416). Dédup **obligatoire** (17 noms dupliqués mesurés sur #16263) par clé canonique `(started_at, id)` dans cet ordre — jamais `created_at`, jamais `id` seul — et **paginer** (`--paginate` : total_count 101 > per_page 100 mesuré sur #16263).
- **DWELL = minuteur, pas un défaut de contenu** : un rouge `PR gate: DWELL -- ... ecoule a <HH:MM>Z. Rien a corriger dans le code` ne se répare PAS par push (chaque push ré-arme le plancher 120 min depuis la nouvelle tête) ; un dossier BLOCKED qui le nomme est un livrable valide, le merge suit l'échéance. `gh pr update-branch` ne ré-arme PAS le plancher depuis #16149. Corollaire : `statusCheckRollup` ment sur ~20 % des candidates (mesuré ai-01 2026-09-21) — ne jamais en faire un verdict.
- **DWELL = minuteur, pas un défaut de contenu** : un rouge `PR gate: DWELL -- ... ecoule a <HH:MM>Z. Rien a corriger dans le code` ne se répare PAS par push (chaque push ré-arme le plancher 120 min depuis la nouvelle tête) ; un dossier BLOCKED qui le nomme est un livrable valide, le merge suit l'échéance. `gh pr update-branch` ne ré-arme PAS le plancher depuis #16149 ; en revanche une **résolution de conflit** (merge de `main` dont l'arbre diffère de l'auto-merge des parents) le **ré-arme** — mesuré le 2026-09-26 sur #17800, où la résolution d'ai-01 a repoussé la stabilisation de `PR gate` de 2 h. C'est ce qui produit la forme la plus déroutante : `PR gate` **reste en vol longtemps après que tout le reste est vert**, parce qu'il attend l'échéance *puis* juge — son `started_at` récent n'est pas un signe de lenteur de la PR. Corollaire : `statusCheckRollup` ment sur ~20 % des candidates (mesuré ai-01 2026-09-21) — ne jamais en faire un verdict.
- **Un pliage partiel n'est pas un `latest-wins-green`** : le fold est une photo de ce qui **existe**, pas de ce que la CI **produira**. Avant tout verdict vert, exiger **0 jambe non-`completed`** *et* un compte stable entre deux relevés. Mesuré le 2026-09-26 sur #17800 : 30 noms, **19 verts et 5 en vol** — dont le seul composite `PR gate` — et `residual_reds: []` sur ce relevé ne disait rien. Un `residual_reds` vide est un plancher, jamais un acquittement.
- **Le gate en échec imprime sur STDOUT** : sans `--json`, l'échec de `check_adjoint_prevalidation.py` rend `UNKNOWN -- {erreur}` sur stdout — une redirection `--template > file.md` capture cette ligne comme template. Avant tout post : (1) rc=0 du template, (2) `head -1` du fichier = `[ADJOINT PREFLIGHT]`, (3) placeholders `REPLACE_WITH` présents dans le template source. `grep -c REPLACE_WITH = 0` est un **faux-OK** sur une ligne d'erreur.
- **Le gate ne lit pas l'état de merge** : un dossier READY exige la vérification `mergeable` côté attestant (CONFLICTING → BLOCKED conflit ; UNKNOWN → HOLD re-mesure).
- **Fenêtre rate-limited** : après un refus GraphQL (`rate limit already exceeded` avec buckets pleins = limite secondaire), le fallback REST `gh api repos/.../issues/N/comments --input payload.json` passe (payload `{"body": "..."}` construit hors shell — la forme `-f body=` est interdite, `gh-posting-hygiene` HARD 1) — mais le template doit être **régénéré après** la fenêtre, jamais réutilisé.
- **Dossier posé EN DERNIER** : toute prose postée après le dossier le périmé (surfaces-sha256).
- **Auto-attestation refusée (HARD)** : le gate refuse un dossier dont la `lane` est **celle de la PR porteuse**, lue dans son tag `Grain:` — une lane ne peut pas attester la PR qu'elle porte. Contre-exemple mesuré : #17422 accepté (porteuse tierce) vs #17051 refusé (porteuse = la mienne), même cycle. Conséquence d'action : une PR verte sans dossier dont la lane porteuse est la mienne **n'est prévalidable que par ai-01** — la nommer pour lui, ne pas la contourner.
- **Un dossier tiers à la même tête rend le mien inutile** : avant de poster, vérifier le **dernier commentaire**. Si un `[ADJOINT PREFLIGHT]` intact d'une autre lane porte déjà la tête vive, poster le mien le périme sans rien apporter (mesuré sur #17835) — vérifier que le gate rend déjà 0, et passer le relais à ai-01.
- **`NO-DOSSIER` a deux lectures** : « aucun dossier » (il en faut un) et « dossier existant devenu invalide » (head ≠ tête vive, ou surfaces modifiées). Un dossier dont la tête n'est plus vive compte comme **nit non levé** — la sortie est de **ré-émettre**, pas d'argumenter (§B.0). Lire `errors[]`, jamais `head -1`.
- **`NO-DOSSIER` a trois lectures** : « aucun dossier » (il en faut un) ; « dossier existant devenu invalide » (head ≠ tête vive, ou surfaces modifiées) ; et — mesuré le 2026-09-26 — un dossier **valide** (tête vive, surfaces intactes, `errors[]` vide) dont le **champ bloquant déclaré** n'est plus soutenu par la mesure. Un dossier dont la tête n'est plus vive compte comme **nit non levé** — la sortie est de **ré-émettre**, pas d'argumenter (§B.0). Lire `errors[]`, jamais `head -1`. Le troisième cas se ré-émet **à la même tête** : le gate rend alors 0. Cas type : `checks: blocked` alors que la jambe bloquante a terminé **verte à tête inchangée** (mesuré sur #17831 — `PR gate` success à 03:52:41Z, 80 min après le dossier de 02:32Z ; aucun commit poussé entre les deux).
- **Le paquet READY fait QUATRE organes, et `domain` est le quatrième** — cette skill ne décrivait pas le champ que le contrat de dossier exige (mesuré le 2026-09-26 : `grep -c domain` rendait **0** sur le fichier). Les quatre : `checks` (pliage *latest-wins*, 0 jambe en vol) · `b0` (`check_unaddressed_nits.py`, rc) · `perimeter` (`check_pr_perimeter.py` — **le gate ne le calcule pas**, c'est le seul organe qui attrape un desserrement de baseline sans `--baseline-justified`) · `domain` (crible de **contenu**). `domain` ne se recopie **jamais** de `checks` : recopier des checks verts est exactement le claim non mesuré qui a produit un READY faux (#16953 — identifiants accentués et ré-exécution sans clé passés sous READY). Trois valeurs : `pass` (cribles verts) · **`not-applicable`** (le diff ne touche **aucun carnet** — les cribles mesurent la prose et les sorties de carnets, ils n'ont pas d'objet : le dire est la réponse exacte) · `fail` (un crible trouve un défaut réel). Un `fail` sur un carnet **nouveau** est invisible à la CI : sans jumeau, l'advisory de désaccentuation ne juge rien (#17623) — mesuré le 2026-09-26 sur #17825, où `domain: fail` portait sur 2 caractères accentués sur 5620 contre 305 sur 12054 au contrôle de la même famille, toutes les jambes vertes par ailleurs.
- **Ledger : poster `d["content"]` avec `messageId = observation_id`** — l'idempotence du CLI est portée par cet id ; un id dérivé à la main crée un doublon silencieux.
- **Une note postée APRÈS le dossier lui est invisible** : `_strip_adjoint_dossier` coupe le corps **jusqu'à sa fin**, donc un commentaire — ou un `CHANGES_REQUESTED` — collé après un bloc dossier ne compte pas comme réserve et l'organe rend `rc=0`. Le pire des deux mondes : perdue pour le gate, lue par l'humain. Une observation qui ne doit ni lever ni réserver se poste dans un commentaire **séparé, AVANT** le dossier.

Expand All @@ -122,7 +127,7 @@ La proposition `act_kind` est donc **mise en attente mesurée**, pas ajoutée au

## Amélioration continue (mandat user 2026-09-21)

« Gardez sous le coude l'amélioration continue, et mettez à jour vos skills régulièrement. » Chaque tell fondateur mesuré en cycle (garde-fou manquant, anti-pattern, instrument faux) est consigné sur le dashboard **puis** reporté dans cette skill par PR dédiée — pas d'édition directe de `main`. Trois défauts muets à chercher en priorité : un instrument qui réimplémente un organe existant (`git grep` le geste dans `scripts/` avant d'écrire du jq de verdict), une absence observée sur un échantillon prise pour une propriété de l'API, une forme d'appel gh non canonique (`-f body=` interdit, `gh-posting-hygiene` HARD 1).
« Gardez sous le coude l'amélioration continue, et mettez à jour vos skills régulièrement. » Chaque tell fondateur mesuré en cycle (garde-fou manquant, anti-pattern, instrument faux) est consigné sur le dashboard **puis** reporté dans cette skill par PR dédiée — pas d'édition directe de `main`. Quatre défauts muets à chercher en priorité : un instrument qui réimplémente un organe existant (`git grep` le geste dans `scripts/` avant d'écrire du jq de verdict), une absence observée sur un échantillon prise pour une propriété de l'API, une forme d'appel gh non canonique (`-f body=` interdit, `gh-posting-hygiene` HARD 1), et une **condition de reprise qu'aucun instrument ne mesure** — elle ne peut jamais être constatée atteinte, donc elle diffère indéfiniment contre la volonté de qui l'a posée. Cas mesuré le 2026-09-26 : une lane attendait un « arbitrage d'ordre de merge » d'ai-01 pendant qu'un `gh pr view <N> --json state,mergedAt` aurait montré que la PR d'en face était mergée depuis ~23 h. Rendre la condition **observable** est plus petit que le travail différé, et le débloque entièrement.

## Cron

Expand Down
Loading