36 heures
La plus longue session en autonomie de Claude le week-end dernier.
Les yeux fermés,
ce n'est pas en aveugle.
Pompier des plateformes critiques
spécialisé en archi distribués & fronts exigeants.
Vous
L'agent · Claude Code
Le dossier
≈ ¾ d'un mot · le modèle ne voit que ces morceaux, et chacun se paie.
≈ 75 000 lignes de code.
Haiku · le volume
Le plus rapide, le moins cher (0,25 / 1,25 $). Je le lâche sur tout ce qui se compte en masse.
Sonnet · le quotidien
Le cheval de trait (3 / 15 $). La plupart des cycles tournent dessus.
Opus · le lourd
Le plus capable (15 / 75 $). Réservé aux problèmes complexes, à la planification
Fable 5 · l'impossible
Le tout dernier, le plus capable.
Le contexte · volatile
La session · semi-durable
La mémoire déportée · durable
# Règles permanentes du projet - Toujours un test rouge avant un fix. - Jamais de natif : le design system. - Secrets : gitleaks bloque le commit.
La charte · les règles permanentes
~180 lignes d'impératifs, non négociables. C'est le contrat lu à chaque réveil.
- Toujours un test rouge avant un fix. - Jamais de natif : le design system. - Secrets : gitleaks bloque le commit.
L'index · où vit quoi
Dans le CLAUDE.md : seulement les règles communes à tout. Tout le reste est splitté en fichiers dédiés · l'index ne fait que pointer.
Le déporté · le savoir-faire
≈ 33 fichiers, chargés à la demande : jamais tout en tête, jamais halluciné.
Par dossier · les CLAUDE.md imbriqués
Chaque sous-dossier peut avoir le sien. backend/CLAUDE.md se charge quand l'agent lit un fichier de backend/, pas avant.
# SKILL.md · le mode d'emploi name: audit-security when: auditer TLS, secrets, auth… # puis le protocole, étape par étape, # + scripts du dossier à appeler.
Backend · feature-implementation-rust-axum
Le skill sait quand s'invoquer : il porte sa propre frontière.
## Quand MOI et pas X - MOI : ticket ready-to-dev + fichiers backend/ - vue-nuxt si : modifs exclusivement frontend/
Frontend · feature-implementation-vue-nuxt
Types → store Pinia → composable → page → composant. Réutiliser @nuxt/ui avant de créer.
Qualité · les protocoles transverses
Pas liés à un langage : ils cadrent comment on vérifie.
les tickets ready-to-dev, par priorité
un worktree isolé par ticket
plan → execute → review, fusionné quand c'est vert
des capacités en plus, branchées une fois
améliorent le fonctionnement de Claude · il les déclenche seul
le bornent fichier par fichier · toujours actives
des raccourcis déterministes · du code, pas un prompt
place au doute
Acte II · le doute> fais-moi un module de facturation
invoice.ts | 312 ++++++++++ tax.ts | 208 ++++++++ pdf.ts | 176 ++++++-- webhooks.ts | 143 +++++- ... + 43 autres fichiers 47 fichiers, +1 240 −287
Ça marche, à côté de la demande. Devant ce pavé, la review ne voit rien.
moi > « aligne mieux ce bloc » agent ✓ marges revues, bloc recentré moi > « non, toujours décalé » agent ✓ autre jeu de marges moi > « pas encore ça… » … et ainsi de suite, encore et encore
Tout est vert : tests passés, gates franchies. Le vrai besoin métier, lui, n'est pas cohérent.
Le doute est posé → voici la méthode
Acte III · la démarcheChaque maillon laisse une trace écrite, pas de l'oral qui s'évapore.
brainstorming
le fonctionnel posé, prêt à spécifier
Forcé, pas espéré
Plutôt que de croiser les doigts, on contrôle le résultat.
Plan mode · on réfléchit
Explore, propose, questionne une par une, sans toucher au code, selon le type de tâche :
writing-plans · le plan écrit
Un fichier daté, versionné, découpé en tâches :
# Plan · 2026-06-03 ## T1 · schéma de la règle ## T2 · validations (dépend de T1) ## T3 · tests (dépend de T1) ## T4 · job CI (dépend de T2, T3)
Tasks DAG · le graphe
Un graphe de dépendances : sans dépendance → parallèle, sinon en attente.
Contexte · un check en alerte spamme l'équipe pendant une maintenance connue.
Scénario: Mettre un check en sourdine Étant donné un check en alerte Quand je le mets en sourdine 2 h Alors il n'émet plus jusqu'à expiration
# PERF · Lighthouse CI bloquant + budget bundle Étant donné un changement qui dégrade le FCP au-delà de 1.5s ou le TBT au-delà de 100ms Quand le job frontend:lighthouse tourne Alors le job échoue, score et métriques à l'appui
FCP · premier affichage · l'écran commence à se remplir TBT · temps figé · la page ne répond pas aux clics
« toute info portée par la couleur DOIT l'être aussi autrement »
axe wcag2a · 0 use-of-color« le scan hebdomadaire confirme une fuite : le pipeline échoue »
gitleaks-full bloquant« zéro chaîne anglaise en dur dans le rapport »
i18n-check · fail si manquant« chaque <select : remplacé par UiDropdown, ou justifié »
design-system-auditune MR thématique, pas cinq atomiques · ×4 moins de pipelines · sauf la migration, toujours séparée.
cycle/plan.tstant que l'humain n'a pas signé le plan, aucune ligne de code n'est écrite.
six rôles dans .claude/agents/, chacun son prompt et ses rules.
L'orchestration : /autopilot, délégation explicite, un daemon. Pas un agent qui appelle tout le monde.
un skill sait quand s'invoquer · c'est ça qu'on met dans son header :
MOI · ticket ready-to-dev + fichiers dans backend/ brainstorming · si l'archi Rust n'est pas définie vue-nuxt · si ça touche frontend/ uniquement
un répertoire de travail dédié, par tâche
dev/worktree.tschacun la sienne, aucun ne touche celle d'à côté
à titre d'exemple, 98 worktrees en 10 jours, sans un seul conflit
vue-lsp Volar, html-lsp, css-lsp.
un hook PostToolUse, pas le LSP.
un agent juge indépendant ne voit que le Gherkin · sort VALIDATED / FAILED
cycle/postdeploy-validation.tsun agent relit le fond, comme un reviewer humain
ci/mr-reviewer.tsADR · une décision tracée
Le choix, daté · les alternatives rejetées, chacune argumentée.
# ADR · Audit log org via record_org_audit explicite Statut : Accepté (#294, MR !127). Baseline pragmatique. ## Décision Un helper unique record_org_audit(…), appelé explicitement dans les 12 handlers mutants. Chaque appel porte : actor_id, org_id, AuditActionType typé, target (type, id), summary JSON. Visible en diff → un relecteur repère l'appel manquant. ## Ce qu'on a explicitement rejeté Middleware Tower (POST/PUT/DELETE) · perd le métier : voit l'URL, pas « Create check » vs « Force re-run ». Triggers DB · perdent l'acteur (DB user = service account partout) et le verbe d'action. Bus d'événements + listener · idiomatique DDD mais ~3× le code ; aucun autre contexte ne publie d'events. Macro / code-gen sur les handlers · ajoute une dépendance de build, obscurcit le call site. → optimisé pour la revue, pas les keystrokes. ## Conséquence assumée (négatif) Invasif : 12 handlers modifiés, aucune contrainte compile-time. Tout nouveau handler mutant doit penser à appeler le recorder → risque de drift. ## Quand on revisite Refactor (middleware Tower lisant un AuditHint) quand 2 signaux sur 4 s'allument : 5e contexte audité, un audit manqué en prod, events émis ailleurs, ou > 5 valeurs AuditActionType::Other(...).
MR · le rapport de livraison
Le résumé, le périmètre préservé, le test plan coché.
# refactor(dashboard): unifier le widget checks # via CheckItem partagé (Closes #1034) ## Summary Remplace le markup inline maison du widget Checks (sparkline dots + badges manuels) par le composant partagé CheckItem avec toCheckItemData(). Rendu aligné sur Health Checks : bar-chart 24h (CheckUptimeBar), uptime %, latency, status dot. Suppression de getCheckStatusLabel devenue orpheline. ## Groupement préservé LANDING PAGE / MAILS / CHECKS ORPHELINS conservé tel quel · seul le rendu des items individuels change. ## Test plan [x] Typecheck Nuxt : OK [x] ESLint : OK [x] 2648 tests unitaires : OK (270 fichiers, 0 échec) [x] validate.sh : OK [ ] Visual check : agent-bypass (mr-reviewer auto-merge)
Postmortem · le déclencheur
Sévérité, timeline, cause racine, correctif, suivi. De là naissent l'ADR et le gate anti-rechute.
# Postmortem · Crash-loop API prod · clés JWT EC Sévérité P1 · 503 intermittents ~24h · 1424 restarts ## Timeline 06-01 21:38 release v0.124.0 (validation stricte) 06-03 ~07:37 1er boot prod → exit 1, crash-loop 06-04 08:30 signalement user (503 sur les checks) 06-04 09:40 clés EC provisionnées → 2 containers healthy 06-04 09:55 effet de bord : conflit router traefik 06-04 10:00 service partagé + healthcheck → API restaurée ## Cause racine 1. La v0.124.0 exige des clés EC en prod, jamais provisionnées dans les stacks Portainer → exit 1. 2. GRANIT_ENVIRONMENT=standby échappe à la validation : le standby boote avec la clé dev du repo → masque la panne ET forge de tokens possible. 3. verify-deployed-sha interroge /health servi par le standby → déploiement déclaré « vérifié ». ## Fix Paire EC P-256 générée hors repo · env Portainer 44/45 · failover traefik réel (service partagé + healthcheck actif /health/live) · sessions invalidées (re-login). ## Actions de suivi #781 ✅ alerte restart-loop · #827 verify sur prod #828 validation stricte en standby · #830 checklist release : tout paramètre obligatoire = provisionné.
Dette · assumée
Nommée plutôt qu'enfouie : labellisée tech-debt P2-medium blocked, pas un secret.
# [Frontend/Mobile] Balayage responsive exhaustif # des 87 écrans (harnais Playwright · viewport 375px) ## Déjà corrigé à la racine (2 transverses) SVG GSparkline largeur fixe 600/800px qui débordait → responsive (bba3eb64) Header GCard non-wrappant, action clippée sur mobile → flex-wrap (f464594a) 9 écrans validés : login, register, onboarding, dashboard, projects/new, checks (liste + détail)… ## Reste à faire · ~78 écrans prioritaires : settings/*, alerts (channels, rules), audits, components, incidents, service-map, slos, admin/*. ## Problème résiduel connu 1 card à slot header custom reste clippée (cosmétique, pas de scroll doc). À traiter.
3+ postmortems sur le même sujet → un méta-postmortem qui juge l'approche, pas le bug.
La démarche est posée → on la borne
Acte IV · les rempartsBorner sa course
Limiter son geste
Fail-open : s'il plante, il laisse passer.
Un garde-fou buggé ne bloque jamais le travail.
L'incident se transforme en garde-fou qui empêche la récidive. rust-job-tx-without-bypass-rls.yml
Un navigateur entier pour vérifier une addition. Un test unitaire suffisait.
Un test vert qui ne tient pas, c'est pire que pas de test : il rassure à tort.
La CI prolonge les gates locaux. Tout est bloquant (sauf nightly).
La PII ne va jamais dans les logs. C'est codifié : granit-validate.ts bloque email=% dans le tracing.
Le Spectre des 503, revu par cet œil granit-production-diagnostic.md · MCP grafana·infra
Détection : de 50 minutes à la main à <15 minutes en auto.
7 auditeurs · seuil 70
sous 70 · l'auditeur bloque → ticket
Rien ne passe sans franchir chaque maillon.
Les remparts tiennent → on accélère
Acte V · la vitesse> corrige la pagination de l'écran checks
$ claude -p "corrige le ticket #412" \ --output-format stream-json
hook PostToolUse -> métrique cron 5 min -> daemon.ts dépile
Avant, j'appuyais sur Entrée. Maintenant, l'événement appuie à ma place.
C'est le workflow qui appelle l'agent. Pas l'inverse.
daemon.ts tient la boucle · run.ts enchaîne les 4 étapes.
Tous les signaux qu'il capte
Les gestes qu'on répète sans arrêt (7 j)
Un geste répété 178 fois, c'est du temps perdu et du contexte rechargé à chaque tour. Sur une semaine, ça crève les yeux.
Chaque soir, il relit ses traces
C'est /daily-improve : il change les frictions en une liste d'actions.
scripts/dev/daily-improve.ts retro-fixes.ts quality-debt-sweep.ts
Ce qu'il a corrigé tout seul, ces jours-ci
Toil + retry = la friction · invisible un par un, énorme au cumul.
Pas de filet, pas de vitesse · on accélère la zone sécurisée, jamais le code.
Ça tourne tout seul → maintenant, le verdict
Acte VI · le bilanLa machine surveille la machine : reste qui tranche quand ça casse ? Un humain.
# .claude/settings.json "PreToolUse": [{ "matcher": "Bash", "command": "trim-output --max 4k" }]
Un cat de 30 Ko n'injecte que 4 Ko dans le contexte. Le reste, l'agent le relit s'il en a vraiment besoin.
# en pleine session, le contexte enfle > /compact garde les décisions, jette le bruit contexte 180 K -> ~90 K (~50 %)
Je replie quand la fenêtre se remplit. Les choix d'archi restent, les tâtonnements partent.
system: [{
type: "text", text: CLAUDE_MD,
cache_control: { type: "ephemeral" }
}]
// -90 % sur tout le préambule, tour après tour
Le gros préambule (CLAUDE.md, l'arbo) est lu une fois, puis rejoué quasi gratuitement.
Task(subagent: "trouve où vit la règle X") # 50 K de grep / lectures … -> restent DANS la bulle du sous-agent -> le principal ne reçoit que la réponse
Les 50 K de fouille ne polluent jamais mon contexte. Je récupère la conclusion, pas le dump.
On croit que les hooks ne font que dire non. En vrai, ce sont aussi eux qui me font économiser.
## Quand MOI et pas X - MOI quand : code existant sans tests suffisants · objectif = couvrir du code déjà écrit - test-sweep : audit global de couverture sur tout le projet - feature-implementation-vue-nuxt / feature-implementation-rust-axum : feature pas encore implémentée (les tests font partie de l'implémentation) > Règle rapide : code existe, tests manquent = MOI. --- # Write Tests - Expert ISTQB Testing Skill Expert ISTQB Foundation v4.0. `$ARGUMENTS` = le sujet, fichier ou feature à tester. ## 1. GOLDEN DATASET Avant tout test E2E, vérifier que le dataset est chargé : ```bash psql "$DATABASE_URL" -c "SELECT count(*) FROM checks WHERE id LIKE 'cccccccc%'" ``` Si count = 0, charger : ```bash psql "$DATABASE_URL" -f test-data/golden-dataset.sql ``` ### IDs de référence (figés · JAMAIS de données magiques) | Entité | ID | État | Description | |--------|-----|------|-------------| | Org | `f32c735c-...` | - | Granit Labs | | User | `bb5562b9-...` | - | test@granit.local / Password123 | | Project Prod | `a1b2c3d4-1234-5678-9abc-def012345678` | production | Production API | | Project Staging | `b2c3d4e5-2345-6789-abcd-ef0123456789` | staging | Staging Environment | | Check Google | `d4e5f6a7-4567-89ab-cdef-012345678901` | passing | Google Homepage | | Check GitHub | `e5f6a7b8-5678-9abc-def0-123456789012` | passing | GitHub API | | Check Payment | `cccccccc-0001-0001-0001-000000000001` | failing | Payment API (12 failures consécutifs) | | Check Staging DB | `cccccccc-0001-0001-0001-000000000002` | failing | Staging DB (5 failures) | | Check CDN | `cccccccc-0001-0001-0001-000000000003` | warning | CDN Edge (lent) | | Check Legacy | `cccccccc-0001-0001-0001-000000000004` | snoozé | Legacy API (snoozé 2h) | | Alerts `eeeeeeee-...-0000000000` | `01` firing (Payment), `02` firing (Staging DB), `03` acknowledged (CDN), `04` resolved (Google 3j), `05` resolved (GitHub 7j), `06` resolved (Payment 30j) | | | | Channels `ffffffff-0002-...-1/2/3` | actifs : Email, Slack, Webhook | | | | Rules `ffffffff-0001-...-1/2/3` | actifs sur Payment, Staging DB, CDN | | | --- ## 3. NIVEAUX DE TEST (ISTQB v4.0) | Niveau | Scope | Qui | Défauts typiques | Approche | |--------|-------|-----|-----------------|----------| | Unit | Fonction/classe isolée | Dev | Logique, boundaries | White-box, stubs | | Intégration | Interfaces entre composants | Dev/QA | Mismatch API, mapping data | API contracts | | Système (E2E) | Système complet | QA | Flux cassés, cross-fonctionnel | Black-box, golden dataset | | Acceptation | Validation business | PO/User | Workflow gaps, compliance | UAT, Gherkin | --- ## 4. TECHNIQUES DE CONCEPTION (ISTQB) Black-box : Equivalence Partitioning (classes d'inputs : <0 invalide / 0-100 valide / >100 invalide) · Boundary Value Analysis (frontières : seuil 100ms → 99, 100, 101) · Decision Table (conditions combinées) · State Transition (Alert: firing → acknowledged → resolved) · Use Case Testing (flux utilisateur). White-box : Statement Coverage ≥80% · Branch Coverage ≥80% (recommandé ISTQB) · Path Coverage (code critique uniquement). Experience-based : Error Guessing (null, vide, négatif, SQL injection, XSS) · Exploratory (sessions 60-120min avec charter) · Checklist-based (régression par feature). --- ## 5. PRINCIPES FIRST | Principe | Règle | |----------|-------| | Fast | Unit < 1ms, suite < 10s · pas de DB, pas de réseau | | Isolated | Pas d'état partagé mutable · setup/teardown indépendant par test | | Repeatable | Même résultat partout · pas de dépendance horloge/réseau/random | | Self-validating | Pass ou Fail · assertions claires, pas "check les logs" | | Timely | Écrire avec le code (TDD ou test-alongside), pas après | --- ## 6. RÈGLES ABSOLUES (projet Granit Golem) 1. `data-testid` : TOUJOURS pour les locators E2E. JAMAIS `text=`, CSS class, XPath 2. Page Object Pattern : séparer locators / actions / assertions 3. ZERO `waitForTimeout` : attendre un état concret (`waitFor({ state: 'visible' })`, URL, response) 4. Scoped locators : `parent.getByTestId('child')` jamais `page.getByTestId('child')` global 5. Golden Dataset : utiliser les IDs de référence, JAMAIS de données magiques 6. Pas de `test.skip()` conditionnel : le golden dataset garantit les données 7. AAA Pattern : Arrange → Act → Assert, un seul `act` par test 8. Un concept par test : si "and" dans le nom, split --- ## 7. ANTI-PATTERNS - waitForTimeout → `await element.waitFor({ state: 'visible' })` - Flaky → waits explicites, isolation, mock external - Test coupling (B fail si A absent) → setup indépendant par test - Over-mocking (tests verts, prod casse) → mock aux frontières, pas les internes - Tester l'implémentation (refacto casse sans changer le comportement) → tester le comportement public - The Liar (test sans assertion) → chaque test DOIT pouvoir échouer - Giant Test / Copy-paste → un concept par test, page objects/helpers/fixtures --- ## 8. EXEMPLES MINIMAUX E2E · locators scopés + wait sur état (Page Object) : ```typescript const locators = { heading: page.getByTestId('feature-heading'), item: page.getByTestId('feature-item'), // SCOPED : dropdown DANS un item itemAction: page.getByTestId('feature-item').getByTestId('dropdown-trigger'), } async goto() { await page.goto('/dashboard/feature') await locators.heading.waitFor({ state: 'visible', timeout: 10000 }) } ``` E2E · spec AAA, un seul act : ```typescript test('should display items from golden dataset', async ({ page }) => { const featurePage = createFeaturePage(page) // Arrange await featurePage.goto() // Act : aucun (vérif d'affichage) expect(await featurePage.getItemCount()).toBeGreaterThan(0) // Assert }) ``` Login partagé via `test.beforeEach` (`createLoginPage` + `testConfig.seedUser`). Intégration API : ```typescript test('GET /feature returns items', async ({ request }) => { const ctx = await request.newContext({ baseURL: 'http://localhost:3000/api/v1' }) const response = await ctx.get('/feature') // Act expect(response.status()).toBe(200) // Assert expect((await response.json()).data.length).toBeGreaterThan(0) }) ``` Unitaire Rust (cas valide + cas d'erreur séparés) : ```rust #[test] fn should_reject_inverted_thresholds() { let result = validate_thresholds(1000, 500); // Arrange + Act assert!(result.is_err()); // Assert assert_eq!(result.unwrap_err().to_string(), "Warning threshold must be less than critical"); } ``` --- ## 9. EXÉCUTION DU SKILL 1. Analyser le fichier/feature cible dans `$ARGUMENTS` 2. Classifier le type (E2E / intégration / unitaire) 3. Concevoir avec les techniques ISTQB (EP, BVA, State Transition...) 4. Préparer : ajouter les `data-testid` dans les composants Vue si besoin 5. Implémenter : page object dans `e2e/pages/` + test spec 6. Vérifier : lancer le test, corriger jusqu'à vert --- ## 10. CONDITION DE SORTIE Terminé SEULEMENT quand : - [ ] Tous les tests écrits passent (`npx playwright test e2e/XX-nom.spec.ts` / `vitest run` / `cargo test`) - [ ] `data-testid` ajoutés, page object créé/à jour dans `e2e/pages/` - [ ] Locators scopés au bon contexte, golden dataset référencé (pas de données magiques) - [ ] Pattern AAA respecté, un concept par test - [ ] ZERO `waitForTimeout`, ZERO sélecteur `text=` ou CSS class fragile NE PAS déclarer terminé si un test est skipped, flaky ou commenté "à corriger plus tard".
Frontières et valeurs limites : on teste les bords, jamais le hasard.
Des IDs de référence figés, jamais de données magiques.
text= et waitForTimeout refusés net au pre-commit.
un CLAUDE.md qui décrit le projet, et le réflexe /clear · /compact entre les tâches.
vous packagez vos skills, vous posez des hooks qui bornent, vous rendez les gates bloquantes en CI.
un agent juge relit à votre place, des worktrees tournent en parallèle, et le daemon orchestre tout ça.
Chaque étage est utile seul. On ne déploie pas le daemon avant d'avoir un CLAUDE.md qui tient.
Outillage de l'agent
Sûreté & mesure
Le métier ne disparaît pas : il se déplace vers l'ingénierie du cadre.