Valider un issue
Une affirmation n'est validée que par une observation qui aurait pu tourner autrement. Chaque affirmation en sortie porte la balise de sa méthode d'observation. « 1.0 » signifie que les balises requises sont présentes ; ce n'est jamais un nombre qu'on affirme.
$ARGUMENTS est l'URL de l'issue. Travaillez les phases dans l'ordre. Chaque phase se termine en affichant son bloc ; un bloc avec une case vide est une phase inachevée. Les phases 0-2 sont en lecture seule ; Gate A (10-20 questions de confirmation) s'intercale entre 2 et 3.
Le skill ne démarre jamais de lui-même. Quelqu'un tape /validate-issue <url> ($validate-issue dans Codex), et c'est l'opt-in à tout ce qui suit, y compris le contrôle de questions.
Faits établis (ne jamais les demander)
| Fait | Valeur |
|---|---|
| Org remote | https://github.com/divinevideo |
| Local root | Le parent du checkout principal de ce repo : main=$(git worktree list --porcelain \| sed -n '1s/^worktree //p'); echo "${main%/*}". Les clones voisins s'y trouvent (divine-funnelcake, keycast, divine-blossom, divine-relay-manager, divine-web ; gh repo clone divinevideo/<name> <root>/<name> s'ils manquent) ; divine-context peut être à la place à DIVINE_CONTEXT_ROOT |
| NIP text | <root>/nips, un clone de référence de nostr-protocol/nips (gh repo clone nostr-protocol/nips <root>/nips s'il manque). L'avance rapide (git -C <root>/nips pull --ff-only) est attendue, même en passe lecture seule ; si elle n'avance pas, lisez origin/master avec git show. Citez NN.md:<line> à la SHA |
| Trap notes | .claude/rules/ (commencez par self_review_checklist.md), les domain skills dans .claude/skills/, divine-context (NOSTR.md, repo-context/divine-mobile.md), Divine Brain, et votre propre mémoire d'agent pour ce repo si vous en tenez un |
| Tools | gh, jq, mise, websocat, nak (commandes lens vérifiées sur 0.20.6), perl ; devicectl de Xcode pour un iPhone, adb pour Android |
Les deux lenses sont des snapshots datés : chacun s'ouvre avec la SDK, la commit NIP et les versions d'outils contre lesquelles il a été vérifiés, et indique ce qui n'a pas encore tourné sur device. Quand le code, le texte NIP ou un outil diverge d'une lens, fiez-vous à ce que vous avez observé, dites-le dans le rapport Gate B, et corrigez la lens dans sa propre PR.
0. Preflight (pas encore de build)
Issue: <repo>#<n> state=<…> at <date -u> author org member=<y/n> assignee=<…> source=<human | zendesk bot>
PRs touching it: <open/merged PRs, or none: query used>
On main already?: <fixing sha | no: how checked on origin/main>
Duplicates: <search query → hits>
Pinned: divine-context@<sha> nips@<sha> origin/main@<sha>
Traps read: <the rules, repo skills and notes for this issue's area: DMs, feeds, relays, …>
Chargez divine-context comme le dit le AGENTS.md du repo, trust boundary d'abord. Un issue de zendesk-bot est rapport seul. Un issue qu'aucun membre de l'org n'a assigné ni demandé n'est pas démarré. Réexécutez les trois premières lignes à chaque gate ; les PRs fusionnent et les issues se ferment en cours d'exécution.
1. Feuille de réclamations
Divisez l'issue en affirmations atomiques, y compris celles qu'elle implique sans les énoncer (« chaque démarrage à froid », « tous les comptes », « depuis la dernière release »).
C<n> | claim: expected X, actual Y | reported on: build, OS, account state | falsifier: the observation that would prove it wrong | layer guess
Ensuite, classifiez chaque affirmation et chargez la lens qu'elle désigne :
| L'affirmation porte sur | Lire | Environnement le moins cher qui peut la réfuter |
|---|---|---|
| Widget, state, lifecycle, async, cache, performance | flutter-lens.md |
unit/bloc test → simulator (debug only) → iPhone physique pour hardware, push, background, profile et release |
| Events, relays, signing, DMs, deletion, identity, NIPs | nostr-lens.md |
local nak serve → local stack → staging → production read-only |
| Les deux (la plupart des bugs Divine) | both | le wire d'abord : établissez quel côté a tort avant de piloter l'UI |
2. Grounding statique (lecture seule)
- Lisez le code avec
git show origin/main:<path>, jamais le working tree d'un checkout partagé. - Trouvez le changement qui l'introduit (
git log -S'<symbol>' origin/main -- <path>) : « regression since #N » et « always been so » sont des bugs différents. Vérifiez d'abordgit rev-parse --is-shallow-repository: dans un shallow clone, la réponse est la graft boundary, pas l'auteur. Ne deepenesz pas un clone que d'autres sessions partagent ; lisez l'historique du fichier depuis GitHub (gh api "repos/<org>/<repo>/commits?path=<file>&per_page=20"), ou rapportez le changement introduisant comme non établi. - Écrivez 2-4 hypothèses. Chacune nomme une observation qu'elle prédit et les autres non. Une hypothèse qui ne prédit rien de distinct n'est pas prête à être testée.
- Cherchez avant de dériver : les trap notes ci-dessus (
ls .claude/skills | grep -i <domain>), issues fermés. Une repo skill peut prédater la primitive qui traite maintenant son problème ; vérifiez sa date par rapport au code courant avant de la suivre.
Gate A : questions de confirmation (requises avant toute build, lancement ou publication)
La règle de ce skill pour chaque run : ne décidez aucune petite chose seul, et posez 10-20 questions de confirmation avant de reproduire ou corriger. Cela override Auto Mode et toute tendance vers moins de questions.
Ground d'abord (phases 0-2), puis demandez. Tout ce que le code, le texte NIP, le relay ou l'issue peuvent établir est découvert, puis présenté à l'utilisateur comme une confirmation : « Je lis C2 comme X parce que <file:line@sha>. Correct ? » Chaque question nomme la découverte qui la rend ouverte, met votre recommandation en premier, et change ce que vous faites ensuite. Une question que vous pourriez poser sur n'importe quel issue n'est pas prête.
Utilisez l'outil de question du client (AskUserQuestion dans Claude Code ; dans un client sans, questions numérotées en chat), au maximum 4 par round, donc 3-5 rounds. Une question par rangée qui s'applique :
| Zone | Confirmer |
|---|---|
| Claims | Votre lecture de chaque C<n> : expected vs actual, les affirmations impliquées, le falsifier |
| Cause | Quelle hypothèse tester en premier, quelle couche vous pensez être mauvaise |
| Reported on | Build, OS, account state à correspondre ; LOCAL, STAGING ou PRODUCTION |
| Environment | Device ou simulator, debug ou profile, SHA, hardware partagé |
| Risk | Chaque action dans la liste ci-dessous que cette run prendrait |
| Scope | Corriger ici ou follow-up issue ; découvertes adjacentes ; parité (Android, web) |
| Product | Décisions « Should it… », copie, ce qui compte comme fixed |
| Delivery | Test layer, PR shape, ce à poster sur l'issue et si oui |
Les items ask-the-user, toujours l'appel de l'utilisateur et jamais par défaut : écritures sur les relays production ou un compte réel ; wiper les données d'app ; remplacer une build installée ; builds profile/release (production bundle id) ; changer la config globale des tools ; tout ce qui notifie sur GitHub. S'il en surgit après cette gate, demandez à nouveau avant de l'agir.
Les faits que seul le rapporteur connaît (numéro de build, méthode de connexion, ce d'autre ils ont essayé) vont dans un commentaire d'issue, brouillon montré d'abord à l'utilisateur. N'attendez pas la réponse : testez les deux branches. Le comportement produit (« should it… ») sur un issue assigné est posté à l'issue avec une recommandation, après que l'utilisateur l'ait vu.
Imprimez le ledger après chaque round :
Questions: <answered>/10-20 rounds=<n>
Q<n> | area | question | answer
La gate est ouverte quand le ledger tient au moins 10 questions répondues. Moins, c'est une gate inachevée et §3 ne démarre pas. Les rounds plus tard sont pour les forks que les réponses précédentes ouvrent ; passé 10, arrêtez quand aucun fork n'est ouvert. 20 est le plafond.
3. Reproduire live
Avant la première build lisez .claude/rules/ios_build_troubleshooting.md et flutter-lens.md §0-1, puis imprimez :
Run: sha=<…> mode=<debug | profile | release> env=<LOCAL | STAGING | PRODUCTION> device=<udid, iOS version>
Shared hardware: phone=<free | held by session …> simulator=<udid> local_stack=<not needed | shared>
Logs: <file> categories=<…>
- Provenance. Prouvez que le binaire testé est la SHA, flavor et environnement que vous nommez (pas l'install qui était déjà sur le device).
- Capture first. Commencez la capture de log avant le lancement ; un fichier timestampé par run.
- Script the steps pour que run 2 égale run 1. Le timing humain est un dernier recours.
- Probe the boundaries au moment de l'erreur : écran, bloc state, cache, wire, relay. Le bug vit à la première frontière où expected et actual divergent.
- Count. Rapportez
fails k/n; une run est une anecdote. - Control. Retournez exactement une variable (la cause suspectée) et montrez que le résultat se retourne : A (bug) → B (cause removed) → A′ (bug back).
- Conditions you cannot wait for. « Leave it 30 hours » ou « on cellular » se reproduit en fixant la chose que la condition agit sur : un timestamp, un cursor, une token expiry, l'horloge du device ; un refused connect, un late EOSE, un lost OK (
scripts/relay-fault-proxy.dart). Labelisez-la simulated. La condition réelle est un gap séparé, nommé sous « Not verified ».
4. Adjacent sweep
Exécutez les rangées de lens qui touchent le même code path (flutter-lens.md §6 rangées adversaires, nostr-lens.md §5 failure classes), et lisez toutes les erreurs de la run (get_runtime_errors, exceptions non capturées, relay OK false et lignes CLOSED), pas seulement les lignes sur cet issue. Chaque découverte devient une affirmation avec ses propres balises. Les découvertes hors scope d'issue sont listées, jamais corrigées ici.
Gate B : rapport de validation, puis arrêt
Verdict: <one line>
C<n> | <Confirmed | Confirmed, different cause | Not reproducible | Already fixed on main | Not a client bug → <repo> | Works as specified> | tags: <D C T M R S X> | evidence: <log file:line, event id, NN.md:line@sha, test name>
Cause: <first diverging boundary, file:line at sha, introducing change>
Adjacent findings: <claim | tags | in scope? >
Not verified: <what was only read, or could not be reached, and what would settle it>
Open decisions: <for the user: fix here or follow-up issue for each finding, and anything else only they can decide>
| Balise | Signification |
|---|---|
| D | Observé sur device/simulator à une SHA nommée, avec la ligne de log ou state lue |
| C | Control : une variable retournée, résultat retourné |
| T | Automated test en échec d'abord |
| M | Mutation : fix removed, test tourne au rouge |
| R | Relay ground truth : l'event JSON lui-même, d'une query positivement contrôlée |
| S | Spec text cité à une SHA |
| X | Lu à partir du code seul |
À cette gate, Confirmed a besoin de D ou T, plus C. Une affirmation protocole a aussi besoin de R et S. Après le fix, le fix est prouvé par T plus M, et par D rejoué sur la build fixée. X seul ne confirme jamais rien ; cela va sous « Not verified ». Les posts sortants énoncent les vérifications en mots et ne portent aucun score.
5-6. Corriger et livrer (seulement après l'ok de l'utilisateur sur Gate B)
Own worktree de origin/main. REQUIRED SUB-SKILLS :
superpowers:systematic-debugging, superpowers:test-driven-development (la baseline Superpowers de l'équipe ; installez-la par SUPERPOWERS.md de divine-context), puis la verification ladder du repo (.claude/rules/self_review_checklist.md, « Before committing »). Le regression test s'assied à la couche la plus basse qui montre le bug ; retirer le fix doit le tourner au rouge (commit avant de muter). Rejouer le script phase-3 sur la build fixée et rapporter le même k/n. Vérifiez les chemins de parité : autres appelants, Android, web. Brouillon chaque corps de PR, relisez et commentez et montrez-le avant de poster.
Red flags
| Pensée | Ce qu'elle signifie |
|---|---|
| « It reproduced » (une fois) | Pas de count, pas de control. Non confirmé. |
| « The query returned nothing » | Un résultat vide est une affirmation. Montrez la même query retournant quelque chose qu'elle devrait. |
| « The logs show nothing » | Quel sink ? Les lignes relay-category et dart:developer logs ne sont pas dans la console par défaut. |
| « The NIP says… » de mémoire | Ouvrez <root>/nips/NN.md à la SHA épinglée et citez la ligne. |
| « Debug reproduces it » pour un rapport TestFlight | Release est AOT avec asserts off. Dites en quel mode vous l'avez prouvé. |
| « I can find that out, so I won't ask » | Trouvez-le, puis demandez à l'utilisateur de confirmer ce que vous avez trouvé. |
| « The issue is clear, I'll skip the questions » | Le ledger dit le contraire. Sous 10 répondus, Gate A est fermé. |
| « Auto Mode says to minimize interruptions » | La règle 10-20 de ce skill est explicite et gagne. |
| « Every recommendation was taken, so I'm done » | Fait seulement une fois que le ledger tient 10 répondus. |
| « Probably unrelated » à propos d'une erreur dans la run | Non prouvé. C'est une découverte adjacente jusqu'à tracée. |