@roomi-fields/grillhouse 0.0.0-stage → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/METACADRE.md +41 -0
  3. package/agents/developpeur.md +10 -0
  4. package/agents/explorateur.md +9 -0
  5. package/agents/questionneur.md +11 -0
  6. package/agents/referent-technique.md +9 -0
  7. package/agents/regulateur.md +11 -0
  8. package/agents/relecteur.md +9 -0
  9. package/agents/testwriter.md +10 -0
  10. package/bin/grillhouse.mjs +125 -0
  11. package/cadre/templates/projet/.claude/settings.json +30 -0
  12. package/cadre/templates/projet/.editorconfig +47 -0
  13. package/cadre/templates/projet/.env.example +57 -0
  14. package/cadre/templates/projet/.eslintrc.cjs +56 -0
  15. package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
  16. package/cadre/templates/projet/.mcp.json +13 -0
  17. package/cadre/templates/projet/.prettierrc +11 -0
  18. package/cadre/templates/projet/CHANGELOG.md +61 -0
  19. package/cadre/templates/projet/CLAUDE.md +46 -0
  20. package/cadre/templates/projet/CONTRIBUTING.md +243 -0
  21. package/cadre/templates/projet/CREDITS.md +41 -0
  22. package/cadre/templates/projet/README.md +152 -0
  23. package/cadre/templates/projet/_gitignore +91 -0
  24. package/cadre/templates/projet/_npmignore +46 -0
  25. package/cadre/templates/projet/deployment/README.md +98 -0
  26. package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
  27. package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
  28. package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
  29. package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
  30. package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
  31. package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
  32. package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
  33. package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
  34. package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
  35. package/cadre/templates/projet/ecosystem.config.cjs +73 -0
  36. package/cadre/templates/projet/package.json +90 -0
  37. package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
  38. package/cadre/templates/projet/src/config.ts +68 -0
  39. package/cadre/templates/projet/src/errors.ts +102 -0
  40. package/cadre/templates/projet/src/index.ts +46 -0
  41. package/cadre/templates/projet/src/services/.gitkeep +8 -0
  42. package/cadre/templates/projet/src/types/index.ts +24 -0
  43. package/cadre/templates/projet/src/utils/logger.ts +106 -0
  44. package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
  45. package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
  46. package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
  47. package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
  48. package/cadre/templates/projet/tsconfig.eslint.json +9 -0
  49. package/cadre/templates/projet/tsconfig.json +41 -0
  50. package/cadre/templates/projet/tsdoc.json +6 -0
  51. package/cadre/templates/projet/vitest.config.ts +44 -0
  52. package/cadre/templates/turbo/paquets.json +29 -0
  53. package/cadre/templates/turbo/sans-paquets.json +28 -0
  54. package/charte.md +107 -0
  55. package/docs/ARCHITECTURE.md +90 -0
  56. package/hooks/crochet.mjs +33 -0
  57. package/hooks/hooks.json +58 -0
  58. package/hooks/register.tsx +558 -0
  59. package/package.json +75 -4
  60. package/scripts/avancement.mjs +244 -0
  61. package/scripts/bilan.mjs +523 -0
  62. package/scripts/branche-de-nuit.mjs +59 -0
  63. package/scripts/cadre.mjs +109 -0
  64. package/scripts/circuit.mjs +4375 -0
  65. package/scripts/code-mort.mjs +371 -0
  66. package/scripts/composants-affectes.mjs +598 -0
  67. package/scripts/consommateurs-arbitres.mjs +192 -0
  68. package/scripts/consommateurs.mjs +310 -0
  69. package/scripts/crochet.mjs +24 -0
  70. package/scripts/crochets.mjs +135 -0
  71. package/scripts/dernier-vert.mjs +208 -0
  72. package/scripts/dette.mjs +181 -0
  73. package/scripts/droits.mjs +354 -0
  74. package/scripts/enveloppe/codes.mjs +17 -0
  75. package/scripts/enveloppe/composant.mjs +11 -0
  76. package/scripts/enveloppe/enveloppe.mjs +628 -0
  77. package/scripts/enveloppe/lancer.sh +311 -0
  78. package/scripts/etapes.mjs +323 -0
  79. package/scripts/etat-du-circuit.mjs +104 -0
  80. package/scripts/frontieres.mjs +456 -0
  81. package/scripts/gardes-du-cadre.mjs +72 -0
  82. package/scripts/greffons.mjs +338 -0
  83. package/scripts/grillhouse-maj.mjs +514 -0
  84. package/scripts/index-interfaces.mjs +293 -0
  85. package/scripts/init.mjs +117 -0
  86. package/scripts/integration/file-attente.mjs +61 -0
  87. package/scripts/integration/file.mjs +494 -0
  88. package/scripts/integration/integrer.mjs +381 -0
  89. package/scripts/integration/rejeu.mjs +139 -0
  90. package/scripts/integration/verdict.mjs +13 -0
  91. package/scripts/interfaces-contre-code.mjs +547 -0
  92. package/scripts/ligne-etat/defaut.sh +110 -0
  93. package/scripts/ligne-etat/ligne-etat.mjs +93 -0
  94. package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
  95. package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
  96. package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
  97. package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
  98. package/scripts/migrations.mjs +43 -0
  99. package/scripts/nuit.sh +269 -0
  100. package/scripts/paquets.mjs +79 -0
  101. package/scripts/poussee-cles.mjs +46 -0
  102. package/scripts/preparation.mjs +84 -0
  103. package/scripts/references-par-les-types.mjs +420 -0
  104. package/scripts/registre.mjs +336 -0
  105. package/scripts/resultats.mjs +580 -0
  106. package/scripts/resumes.mjs +57 -0
  107. package/scripts/retirer-copie.sh +127 -0
  108. package/scripts/session-start.sh +203 -0
  109. package/scripts/sortie.mjs +155 -0
  110. package/scripts/sous-modules.sh +316 -0
  111. package/scripts/structure-guard.sh +27 -0
  112. package/scripts/superviseur.mjs +102 -0
  113. package/scripts/tableau.mjs +1886 -0
  114. package/scripts/tests.mjs +236 -0
  115. package/scripts/ticket.sh +23 -0
  116. package/scripts/turbo-des-paquets.mjs +899 -0
  117. package/scripts/un-commit-par-ticket.mjs +212 -0
  118. package/scripts/verifier.mjs +469 -0
  119. package/scripts/verrou-de-fichier.mjs +130 -0
  120. package/scripts/verrou-de-machine.mjs +211 -0
  121. package/scripts/verrous/commande.mjs +180 -0
  122. package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
  123. package/scripts/verrous/verrou.mjs +165 -0
  124. package/scripts/vitest-config/avancement.mjs +39 -0
  125. package/scripts/vitest-config/avancement.reporter.mjs +56 -0
  126. package/scripts/vitest-config/base-de-passe.mjs +128 -0
  127. package/scripts/vitest-config/base.d.ts +6 -0
  128. package/scripts/vitest-config/base.mjs +87 -0
  129. package/scripts/vitest-config/changements.mjs +173 -0
  130. package/scripts/vitest-config/cibles.mjs +207 -0
  131. package/scripts/vitest-config/duree.reporter.mjs +98 -0
  132. package/scripts/vitest-config/machine.setup.mjs +14 -0
  133. package/scripts/vitest-config/mesure.d.ts +6 -0
  134. package/scripts/vitest-config/mesure.mjs +13 -0
  135. package/scripts/vitest-config/mesure.setup.mjs +12 -0
  136. package/scripts/vitest-config/pression.mjs +90 -0
  137. package/scripts/vitest-config/resultats.mjs +23 -0
  138. package/scripts/vitest-config/resultats.setup.mjs +19 -0
  139. package/scripts/vitest.nuit.config.mjs +26 -0
  140. package/skills/developpeur/SKILL.md +175 -0
  141. package/skills/explorateur/SKILL.md +84 -0
  142. package/skills/grill/SKILL.md +238 -0
  143. package/skills/grill/references/releve-structurel.md +38 -0
  144. package/skills/mesure/SKILL.md +168 -0
  145. package/skills/pitmaster/SKILL.md +382 -0
  146. package/skills/pitmaster/references/cadre.md +119 -0
  147. package/skills/pitmaster/references/consigne-agent.md +308 -0
  148. package/skills/pitmaster/references/erreurs.md +25 -0
  149. package/skills/pitmaster/references/tour.md +124 -0
  150. package/skills/questionneur/SKILL.md +64 -0
  151. package/skills/redacteur/SKILL.md +84 -0
  152. package/skills/redacteur/references/documents-d-un-composant.md +105 -0
  153. package/skills/referent-technique/SKILL.md +145 -0
  154. package/skills/regulateur/SKILL.md +220 -0
  155. package/skills/release/SKILL.md +134 -0
  156. package/skills/release/scripts/update-version.cjs +92 -0
  157. package/skills/relecteur/SKILL.md +152 -0
  158. package/skills/relecteur/references/LICENSE-MIT +30 -0
  159. package/skills/relecteur/references/SOURCE.md +8 -0
  160. package/skills/relecteur/references/claims-check.md +14 -0
  161. package/skills/relecteur/references/deletion-check.md +14 -0
  162. package/skills/relecteur/references/edge-case-hunter.md +80 -0
  163. package/skills/relecteur/references/verification-gap.md +113 -0
  164. package/skills/testwriter/SKILL.md +187 -0
  165. package/types/index.d.ts +56 -0
  166. package/README.md +0 -3
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env bash
2
+ # Launches a role agent on one ticket, as its own Claude session inside the envelope of its
3
+ # component. The supervisor runs it in the background and is told when the session ends.
4
+ #
5
+ # npx grillhouse lancer [--modele <m>] [--effort <e>] [--seance <id> | --reprise <id>] <ticket> <rôle> <composant> <fichier de consigne>
6
+ #
7
+ # - <composant> is the component's path from the root (`packages/<nom>`, or `.` for the whole
8
+ # repository). It is judged before the copy is prepared, by the envelope's rule
9
+ # (`jugerComposant` of enveloppe.mjs), as the role and the instruction are, in the ticket's copy
10
+ # when it exists, else in the main tree or in the tree of the branch agent/<ticket> when it
11
+ # exists (`git cat-file -e`; `.` is always admitted): a path that names nothing there is refused on the standard error, CODE_USAGE, nothing prepared; when
12
+ # it is the name of a package or of its folder, the refusal names the path it means.
13
+ # - --modele <m> and --effort <e> override, for this session only, the model and the thinking level
14
+ # of the role's definition (its agent in the frame's plugin, agents/<rôle>.md); they reach claude
15
+ # as --model and --effort.
16
+ # Each is optional and works alone; without them the role decides. The level is one of low,
17
+ # medium, high, xhigh, max; the model is any non-empty text, passed to claude as given. They come
18
+ # first, each as two words (`--modele opus`); `--modele=opus`, `--`, a value starting with `--`,
19
+ # an unknown option or an option after the four arguments is refused, CODE_USAGE, before the copy
20
+ # is prepared. A repeated option keeps its last value; every level given is checked.
21
+ # - --seance <id> opens the session under that id (claude's --session-id), and --reprise <id>
22
+ # resumes the session of that id (claude's --resume), which claude finds among the transcripts
23
+ # of the copy's working directory (`~/.claude/projects/<copy in dashes>/`, which the envelope
24
+ # mounts in writing over the real directory, so that a transcript outlives its session). The
25
+ # circuit gives them, so that a relaunch of an agent on its step resumes its session. The two
26
+ # together are refused, CODE_USAGE; each takes a value as --modele does.
27
+ # A resumed session receives its instruction alone: the session it resumes holds the sections
28
+ # the launcher adds below, save « La construction de ton paquet échoue », which it receives.
29
+ # - The agent's copy is the worktree .claude/worktrees/<ticket> (branch agent/<ticket>), created
30
+ # from the main tree's HEAD when absent and moved forward to it otherwise. When the branch exists
31
+ # without a copy, it is brought back to the main tree's HEAD and the copy is made on it: the lot
32
+ # the branch carried ahead of main (a branch the queue sent back) comes back as local work in
33
+ # the copy, a conflict with main marked in its file; a branch with nothing ahead simply starts
34
+ # again from main. A ticket whose branch is still in the integration queue is not launched:
35
+ # the refusal goes to the log, which names the queue, CODE_COPIE, before any copy is made.
36
+ # Moving forward keeps
37
+ # the work in progress of the copy: git stashes it, fast-forwards, and reapplies it
38
+ # (`--autostash`). A refusal, such as commits of the agent on its branch, goes to the log and
39
+ # stops the launch, CODE_COPIE; so does a reapplication that conflicts: the log names each file
40
+ # in conflict, the work stays in git's stash, and no agent starts on a half-merged copy. Its
41
+ # submodules go through the common preparation (scripts/sous-modules.sh): before moving forward,
42
+ # a submodule carrying local work stops the launch, CODE_COPIE, its log naming it, the work in
43
+ # place; then every submodule is set at the commit of HEAD, before any installation, except one the
44
+ # project shares (`grillhouse.partage`), which the copy reads from the main repository. Its
45
+ # dependencies are installed when `node_modules` is absent or `package-lock.json` is newer than
46
+ # the last installation (`node_modules/.package-lock.json`, written by npm), and it is built there
47
+ # by Turborepo, which restores an unchanged build from its cache, outside the envelope, so the
48
+ # agent finds the published parts of its neighbours. As Bazel builds the dependencies of a target
49
+ # before the target: for a component that is a package of the frame, its providers are built
50
+ # first (the packages whose build its build waits for, read in Turborepo's graph,
51
+ # `turbo run build --filter=<paquet> --dry=json`, which node reads on its standard input, whatever
52
+ # its size), and a failure stops the launch, CODE_COPIE;
53
+ # then the traces of other packages its tests wait for (`grillhouse.traces`, read in Turborepo's
54
+ # plan of its tests, `turbo run test:vitest --filter=<paquet> --dry=json`), which the envelope
55
+ # shows read-only, and a failure stops the launch, CODE_COPIE; then the package, its own traces
56
+ # and what its tests read (`turbo run build <its traces> --filter=<paquet>...
57
+ # --continue=dependencies-successful`), whose failure does not stop it: each task whose
58
+ # dependencies succeed goes to its end, so what its tests read without depending on it is built
59
+ # whole; the session opens, its instruction ending with a section « La construction de ton
60
+ # paquet échoue » with the end of that output, since repairing it is the session's work (a
61
+ # provider's lot that breaks its consumers). Its consumers are not built. Any other component,
62
+ # the whole repository (`.`) included, builds the copy whole (`turbo run build`). The
63
+ # installation and the build play through `scripts/sous-modules.sh monter`, each shared submodule
64
+ # of the main repository read-only, by the list the pose printed, whatever the installation
65
+ # rewrites afterwards; with a shared submodule and no possible mount (bwrap absent or
66
+ # refused), the launch stops, CODE_COPIE, before any command runs. In the envelope the main
67
+ # repository is read-only as a whole.
68
+ # - The envelope receives the role: it decides who writes the package of the shared test material.
69
+ # - The session runs `claude -p` with the role's agent and, as its prompt, read on its standard
70
+ # input from a temporary file the launcher writes and removes (an argument may not pass the
71
+ # 128 KiB Linux allows one, MAX_ARG_STRLEN), the instruction file
72
+ # followed by the section « Ce que les composants d'avant publient »: for each component whose
73
+ # `## Consommateurs` section names the component, its line, the names of the elements declared
74
+ # for the component, and the address of the rubric « Rend » of its frame, the rest of its
75
+ # elements left to the index of the interfaces (`scripts/index-interfaces.mjs --publie`, read in
76
+ # the prepared copy; a reading that fails stops the launch, CODE_COPIE), then the
77
+ # section « Ton dossier de travail », which names the session's working directory, where every
78
+ # temporary file goes (`dossierDeTravail` of enveloppe.mjs, the envelope's TMPDIR), then the
79
+ # section « La construction de ton paquet échoue » when it fails. The prompt
80
+ # carries « TON TICKET : <ticket> », which the role's locks read. The envelope is the sandbox, so
81
+ # the session runs without permission prompts, under the rights and the context of its role
82
+ # (scripts/droits.mjs, given the main tree, the copy and the ticket; a reading that fails stops
83
+ # the launch, CODE_COPIE): the user's own configuration left out, its settings on the command
84
+ # line (its refusals, among them the change of its author at the ticket, AUTEUR_FIXE: its
85
+ # comments carry the author the launch names in BEADS_ACTOR, which the envelope passes on; its
86
+ # locks), its tools and no server of the index.
87
+ # - A role that ends on a sheet (scripts/resultats.mjs: the testwriter, the developpeur) runs with
88
+ # `--output-format json --json-schema <its schema>` (`npx grillhouse resultats --schema
89
+ # <rôle>`): claude's JSON result, its sheet in `structured_output`, goes to
90
+ # .claude/worktrees/<ticket>.resultat.json, which a launch removes first and the circuit reads,
91
+ # then to the log; its errors go to the log.
92
+ # - Its output, and an envelope refusal (CODE_REFUS), are added to .claude/worktrees/<ticket>.log,
93
+ # the log of the ticket, which keeps every session of the ticket: each launch past the checks of
94
+ # its call adds a header line, `=== <time> · <rôle> · <composant> ===`, then its preparation and
95
+ # its session. The agent writes its handover in the ticket. Once the envelope has ended, the
96
+ # launcher writes the session's exit code to .claude/worktrees/<ticket>.fin, which a launch
97
+ # removes first: a launch that stops before its session leaves none, so the circuit
98
+ # (scripts/circuit.mjs) tells a code of the session from CODE_USAGE, CODE_REFUS and CODE_COPIE.
99
+ # It then writes the session's line to the register (scripts/registre.mjs --copie), and exits
100
+ # with the envelope's code.
101
+ # - The exit codes are named in scripts/enveloppe/codes.mjs; a malformed call exits CODE_USAGE.
102
+ # - CLAUDE_BIN replaces `claude` (the tests use it).
103
+ set -euo pipefail
104
+ ici="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
105
+ eval "$(node "$ici/codes.mjs")"
106
+ usage="usage: npx grillhouse lancer [--modele <m>] [--effort <e>] [--seance <id> | --reprise <id>] <ticket> <rôle> <composant> <fichier de consigne>"
107
+ refus_usage() { echo "$usage" >&2; exit "$CODE_USAGE"; }
108
+ modele="" effort="" seance="" reprise="" surcharge=()
109
+ while [ $# -gt 0 ]; do
110
+ case "$1" in
111
+ --modele|--effort|--seance|--reprise)
112
+ [ $# -ge 2 ] && [ -n "$2" ] && [[ "$2" != --* ]] || refus_usage
113
+ if [ "$1" = --modele ]; then
114
+ modele=$2
115
+ elif [ "$1" = --seance ]; then
116
+ seance=$2
117
+ elif [ "$1" = --reprise ]; then
118
+ reprise=$2
119
+ else
120
+ case "$2" in low|medium|high|xhigh|max) effort=$2 ;; *) refus_usage ;; esac
121
+ fi
122
+ shift 2 ;;
123
+ -*) refus_usage ;;
124
+ *) break ;;
125
+ esac
126
+ done
127
+ [ -z "$modele" ] || surcharge+=(--model "$modele")
128
+ [ -z "$effort" ] || surcharge+=(--effort "$effort")
129
+ [ -z "$seance" ] || [ -z "$reprise" ] || refus_usage
130
+ [ -z "$seance" ] || surcharge+=(--session-id "$seance")
131
+ [ -z "$reprise" ] || surcharge+=(--resume "$reprise")
132
+ [ $# -eq 4 ] || refus_usage
133
+ ticket=$1 role=$2 composant=$3 consigne=$4
134
+ # The main tree, from the common git directory: launched from an agent copy, the new copy still
135
+ # lands next to the others, never inside one.
136
+ racine=$(cd "$(git rev-parse --path-format=absolute --git-common-dir)/.." && pwd)
137
+ # A role is one of the circuit's, whose rights scripts/droits.mjs gives (ROLES).
138
+ node --input-type=module -e "import { ROLES } from '$ici/../droits.mjs'; process.exit(Object.hasOwn(ROLES, process.argv[1]) ? 0 : 1);" "$role" || { echo "⛔ Rôle inconnu : $role (aucun rôle du circuit, scripts/droits.mjs)." >&2; exit "$CODE_USAGE"; }
139
+ [ -f "$consigne" ] || { echo "⛔ Consigne introuvable : $consigne" >&2; exit "$CODE_USAGE"; }
140
+ # The component, judged by the envelope's rule before anything is prepared: in the ticket's copy
141
+ # when it exists (a relaunch, whose package an earlier agent may have created), else in the main
142
+ # tree or, when the branch agent/<ticket> exists (one the queue sent back), in its tree, whose lot
143
+ # the copy made again carries.
144
+ juge="$racine" branche=
145
+ if [ -d "$racine/.claude/worktrees/$ticket" ]; then
146
+ juge="$racine/.claude/worktrees/$ticket"
147
+ elif git -C "$racine" rev-parse --verify --quiet "refs/heads/agent/$ticket" > /dev/null; then
148
+ branche="agent/$ticket"
149
+ fi
150
+ refus_composant=$(node --input-type=module -e "import { spawnSync } from 'node:child_process'; import { existsSync } from 'node:fs'; import path from 'node:path'; import { jugerComposant } from '$ici/enveloppe.mjs'; import { paquets } from '$ici/../paquets.mjs'; const [depot, composant, branche] = process.argv.slice(1); const dansBranche = p => { const rel = path.relative(depot, p); return rel === '' || (!rel.startsWith('..') && spawnSync('git', ['-C', depot, 'cat-file', '-e', branche + ':' + rel]).status === 0); }; const existe = p => existsSync(p) || (branche !== '' && dansBranche(p)); process.stdout.write(jugerComposant({ depot, composant, paquets: paquets(depot) }, { existe }) ?? '');" "$juge" "$composant" "$branche") || exit "$CODE_USAGE"
151
+ [ -z "$refus_composant" ] || { echo "⛔ $refus_composant" >&2; exit "$CODE_USAGE"; }
152
+ # The ticket is matched whole: `demo-3160` is not `demo-316`, and its dots are dots.
153
+ motif=$(printf '%s' "$ticket" | sed 's/[.[\*^$]/\\&/g')
154
+ grep -qE "TON TICKET : ${motif}([^A-Za-z0-9_.-]|$)" "$consigne" || { echo "⛔ La consigne ne porte pas « TON TICKET : $ticket »." >&2; exit "$CODE_USAGE"; }
155
+
156
+ copie="$racine/.claude/worktrees/$ticket"
157
+ journal="$copie.log"
158
+ fin="$copie.fin"
159
+ resultat="$copie.resultat.json"
160
+ mkdir -p "$(dirname "$copie")"
161
+ rm -f "$fin" "$resultat"
162
+ printf '=== %s · %s · %s ===\n' "$(date -Iseconds)" "$role" "$composant" >> "$journal"
163
+ # A branch still in the queue is not relaunched: the queue brings it onto main, or sends it back.
164
+ if node --input-type=module -e "import { lireEntrees } from '$ici/../integration/file-attente.mjs'; const [r, b, t] = process.argv.slice(1); process.exit(lireEntrees(r).some(e => e.branche === b || e.ticket === t) ? 0 : 1);" "$racine" "agent/$ticket" "$ticket"; then
165
+ refus="⛔ La branche agent/$ticket est encore en file d'intégration : la file l'amène sur main ou la renvoie ; le ticket ne se relance pas avant."
166
+ echo "$refus" >> "$journal"
167
+ echo "$refus" >&2
168
+ exit "$CODE_COPIE"
169
+ fi
170
+ # The output of the session package's build, when it fails.
171
+ echec=$(mktemp)
172
+ (
173
+ # Each step returns on failure: `set -e` does not act inside a subshell tested by `||`.
174
+ if [ ! -d "$copie" ]; then
175
+ tete=$(git -C "$racine" rev-parse HEAD) || exit 1
176
+ branche="agent/$ticket"
177
+ lot=
178
+ if git -C "$racine" rev-parse --verify -q "refs/heads/$branche" > /dev/null; then
179
+ # A branch already there: the queue sent it back, or it was left without a commit. Its lot,
180
+ # when it carries one, goes back as local work on main; the branch returns to main.
181
+ if [ "$(git -C "$racine" rev-list --count "$tete..$branche")" -gt 0 ]; then
182
+ base=$(git -C "$racine" merge-base "$tete" "$branche") || exit 1
183
+ lot=$(mktemp) || exit 1
184
+ git -C "$racine" diff --binary "$base" "$branche" > "$lot" || exit 1
185
+ fi
186
+ git -C "$racine" branch -q -f "$branche" "$tete" || exit 1
187
+ git -C "$racine" worktree add -q "$copie" "$branche" || exit 1
188
+ else
189
+ git -C "$racine" worktree add -q -b "$branche" "$copie" HEAD || exit 1
190
+ fi
191
+ if [ -n "$lot" ]; then
192
+ # The lot is applied three ways: a conflict is marked in the file, the rest of the lot is
193
+ # there. The index is left as HEAD has it, so that all of it is local work.
194
+ git -C "$copie" apply --3way "$lot" || [ -n "$(git -C "$copie" ls-files -u)" ] || exit 1
195
+ git -C "$copie" reset -q || exit 1
196
+ rm -f "$lot"
197
+ fi
198
+ else
199
+ bash "$ici/../sous-modules.sh" garde "$copie" || exit 1
200
+ git -C "$copie" merge -q --ff-only --autostash "$(git -C "$racine" rev-parse HEAD)" || exit 1
201
+ # A conflicting reapplication exits 0: the unmerged files are what tells it.
202
+ conflits=$(git -C "$copie" diff --name-only --diff-filter=U) || exit 1
203
+ if [ -n "$conflits" ]; then
204
+ echo "⛔ Le travail en cours de la copie entre en conflit avec main ; il reste dans la remise de git (git stash list)."
205
+ echo "Fichiers en conflit :"
206
+ sed 's/^/ /' <<<"$conflits"
207
+ exit 1
208
+ fi
209
+ fi
210
+ # The list the pose fixes is the one every mount gets, whatever the installation rewrites.
211
+ partages=$(bash "$ici/../sous-modules.sh" pose "$copie") || exit 1
212
+ monter() { bash "$ici/../sous-modules.sh" monter "$copie" --partages "$partages" "$@"; }
213
+ monter true || exit 1
214
+ if [ -f "$copie/package.json" ]; then
215
+ # --no-save leaves the lock file as committed, so a clean copy stays clean.
216
+ installe="$copie/node_modules/.package-lock.json"
217
+ if [ ! -d "$copie/node_modules" ] || [ "$copie/package-lock.json" -nt "$installe" ]; then
218
+ monter npm --prefix "$copie" install --no-save --no-audit --no-fund --silent || exit 1
219
+ fi
220
+ # The package of the session, when the component is a package of the frame: its name.
221
+ paquet=$(node --input-type=module -e "import path from 'node:path'; import { paquets } from '$ici/../paquets.mjs'; const [r, d] = process.argv.slice(1); const p = paquets(r).find(p => !p.hors && p.dossier === path.normalize(d).replace(/\/\$/, '')); process.stdout.write(p?.pkg.name ?? '');" "$copie" "$composant") || exit 1
222
+ if [ -z "$paquet" ]; then
223
+ (cd "$copie" && monter npx --no-install turbo run build) || exit 1
224
+ else
225
+ # Its providers: the packages whose build its own build waits for, as Turborepo reads them.
226
+ graphe=$(cd "$copie" && monter npx --no-install turbo run build "--filter=$paquet" --dry=json) || exit 1
227
+ # The graph comes on the standard input: as an argument, it may pass the size Linux allows one.
228
+ fournisseurs=$(node -e 'const [p] = process.argv.slice(1); const t = JSON.parse(require("node:fs").readFileSync(0, "utf8")).tasks.filter(t => t.taskId !== `${p}#build`); console.log([...new Set(t.map(t => `--filter=${t.package}`))].join("\n"));' "$paquet" <<<"$graphe") || exit 1
229
+ if [ -n "$fournisseurs" ]; then
230
+ mapfile -t filtres <<<"$fournisseurs"
231
+ (cd "$copie" && monter npx --no-install turbo run build "${filtres[@]}") || exit 1
232
+ fi
233
+ # The traces its tests wait for, as Turborepo's plan of its tests reads them. Another
234
+ # package's trace is produced here, and a failure stops the launch, as a provider's; its own
235
+ # waits for its own build, and is produced with it below.
236
+ plan=$(cd "$copie" && monter npx --no-install turbo run test:vitest "--filter=$paquet" --dry=json) || exit 1
237
+ traces() { node --input-type=module -e "import { readFileSync } from 'node:fs'; import { tracesDuPlan } from '$ici/../turbo-des-paquets.mjs'; const [p, propres] = process.argv.slice(1); console.log(tracesDuPlan(JSON.parse(readFileSync(0, 'utf8')), t => (t.package === p) === (propres === 'oui')).join('\n'));" "$paquet" "$1" <<<"$plan"; }
238
+ autres=$(traces non) || exit 1
239
+ if [ -n "$autres" ]; then
240
+ mapfile -t ids <<<"$autres"
241
+ (cd "$copie" && monter npx --no-install turbo run "${ids[@]}") || exit 1
242
+ fi
243
+ propres=$(traces oui) || exit 1
244
+ ids=()
245
+ [ -z "$propres" ] || mapfile -t ids <<<"$propres"
246
+ # The package itself, its own traces, and what its tests read (the shared test material),
247
+ # built after its providers: a failure does not stop the launch, it goes into the instruction.
248
+ # Each task whose dependencies succeed goes to its end (--continue=dependencies-successful):
249
+ # the package's failure does not cancel the build of what its tests read without depending
250
+ # on it, which the envelope shows read-only.
251
+ if ! (cd "$copie" && monter npx --no-install turbo run build ${ids[@]+"${ids[@]}"} "--filter=$paquet..." --continue=dependencies-successful) > "$echec" 2>&1; then
252
+ cat "$echec"
253
+ echo "⚠️ La construction de $paquet échoue : la séance s'ouvre, la consigne porte l'erreur."
254
+ else
255
+ : > "$echec"
256
+ fi
257
+ fi
258
+ fi
259
+ ) >> "$journal" 2>&1 || { rm -f "$echec"; echo "⛔ La copie $copie n'a pas pu être préparée : voir $journal." >&2; exit "$CODE_COPIE"; }
260
+ # What the components before publish, read in the prepared copy, follows the instruction.
261
+ publie=$(cd "$copie" && node "$ici/../index-interfaces.mjs" --publie "$composant" 2>> "$journal") \
262
+ || { rm -f "$echec"; echo "⛔ Ce que publient les composants d'avant n'a pas pu se lire : voir $journal." >&2; exit "$CODE_COPIE"; }
263
+ # The session's working directory, the one the envelope gives as TMPDIR, follows them.
264
+ travail=$(node --input-type=module -e "import { dossierDeTravail } from '$ici/enveloppe.mjs'; process.stdout.write(dossierDeTravail(process.argv[1]));" "$copie" 2>> "$journal") \
265
+ || { rm -f "$echec"; echo "⛔ Le dossier de travail de la séance n'a pas pu se lire : voir $journal." >&2; exit "$CODE_COPIE"; }
266
+ dossier=$'\n\n'"## Ton dossier de travail"$'\n\n'"Tout fichier temporaire s'écrit dans \`$travail\`, que \`\$TMPDIR\` nomme aussi."
267
+ construction=
268
+ if [ -s "$echec" ]; then
269
+ construction=$'\n\n'"## La construction de ton paquet échoue"$'\n\n'"Ses fournisseurs et ce que ses tests lisent sans dépendre de lui sont construits ; sa propre construction échoue, et la réparer fait partie de ta tâche. La fin de sa sortie :"$'\n\n```\n'"$(tail -n 100 "$echec")"$'\n```'
270
+ fi
271
+ rm -f "$echec"
272
+ # The prompt, assembled in a file: claude reads it on its standard input, since as an argument it
273
+ # may pass the size Linux allows one (MAX_ARG_STRLEN, 128 KiB).
274
+ # A resumed session holds the sections of its first instruction: only a failed build is news.
275
+ prompt=$(mktemp)
276
+ if [ -n "$reprise" ]; then
277
+ printf '%s' "$(cat "$consigne")$construction" > "$prompt"
278
+ else
279
+ printf '%s' "$(cat "$consigne")"$'\n\n'"$publie$dossier$construction" > "$prompt"
280
+ fi
281
+
282
+ # The rights and the context of the role's session (scripts/droits.mjs), one option per line.
283
+ droits=$(node "$ici/../droits.mjs" "$role" --racine "$racine" --copie "$copie" --ticket "$ticket" 2>> "$journal") \
284
+ || { rm -f "$prompt"; echo "⛔ Les droits de la séance n'ont pas pu se lire : voir $journal." >&2; exit "$CODE_COPIE"; }
285
+ mapfile -t droits_du_role <<<"$droits"
286
+
287
+ # The schema of the role's end-of-session sheet (scripts/resultats.mjs), for a role that has one:
288
+ # the session ends on it, which claude writes in its JSON result, on the standard output.
289
+ fiche=()
290
+ schema=$(node "$ici/../resultats.mjs" --schema "$role" 2> /dev/null) || schema=
291
+ [ -z "$schema" ] || fiche=(--output-format json --json-schema "$schema")
292
+
293
+ # An envelope in error does not stop the launcher (set -e): its code is kept for the exit.
294
+ code=0
295
+ lancer_claude() {
296
+ node "$ici/enveloppe.mjs" "$copie" "$composant" "$role" -- \
297
+ "${CLAUDE_BIN:-claude}" -p --agent "$role" \
298
+ ${surcharge[@]+"${surcharge[@]}"} ${fiche[@]+"${fiche[@]}"} --permission-mode bypassPermissions \
299
+ "${droits_du_role[@]}" < "$prompt"
300
+ }
301
+ if [ -n "$schema" ]; then
302
+ lancer_claude > "$resultat" 2>> "$journal" || code=$?
303
+ { cat "$resultat"; echo; } >> "$journal"
304
+ else
305
+ lancer_claude >> "$journal" 2>&1 || code=$?
306
+ fi
307
+ rm -f "$prompt"
308
+ echo "$code" > "$fin"
309
+ # The envelope ended: the session's line goes to the register from outside it, whatever its end hook did.
310
+ node "$ici/../registre.mjs" --copie "$copie" >> "$journal" 2>&1 || true
311
+ exit "$code"
@@ -0,0 +1,323 @@
1
+ // The steps of a ticket, the tree of its circuit, as a pipeline of GitLab CI shows its stages and
2
+ // their jobs and GitHub Actions its jobs and their steps, and the stops of its circuit: the one
3
+ // list, which the circuit (scripts/circuit.mjs), the board (scripts/tableau.mjs), the bilan
4
+ // (scripts/bilan.mjs) and the texts read.
5
+ //
6
+ // - A step (ETAPES): its number (« 2 », « 2.3 »), its title, who works there (`qui`: the role
7
+ // agents, or AUTO for a step the circuit leads itself; none for a step that only holds its
8
+ // sub-steps), its short form on the board's line where the roles' names do not hold in its
9
+ // width (`quiCourt`, the roles keeping their whole name everywhere else), whether it runs for each component of the ticket's title (`parComposant`), its
10
+ // sub-steps (`sous`), and the step of the circuit's state it is (`etat`: the `etape` of
11
+ // `.claude/worktrees/<ticket>.circuit.json`, the final pass being the checks once `finale`).
12
+ // A step that runs only for a project that declares it names the key of that declaration under
13
+ // `grillhouse` of the project's package.json (`declaration`): the board shows it only there.
14
+ // The opening reads its sub-steps elsewhere in the state: the survey of the components by its
15
+ // start while it runs (`releve`) and by its stop (`arrets`), the questioning of the plan by the questioning under way (`questionnement`)
16
+ // and by its stop. SUITE is the tree in its order, each step before its sub-steps.
17
+ // - A step carries as many bounded loops as it needs (`boucles`): each its key (`cle`, which
18
+ // keys its count in the state's `boucles`), its label, its maximum (`max`, the pass that stops
19
+ // it; null for the components, whose number is the title's) and the sub-steps it spans (`sur`;
20
+ // the step and all its sub-steps without it): the components of the title, the testwriter's
21
+ // sessions after a refused proof, the developpeur's sessions with their checks, the rounds of
22
+ // a questioning, the reviews, the same stop come back after a RELANCE of the regulator. The
23
+ // circuit stops each loop at its maximum and writes its count in its state; the board shows
24
+ // « <label> <n>/<max> » on the ticket's line from the second pass.
25
+ // - The arbitrage of a stop (ARBITRAGE) stands out of the sequence, under the stopped step: the
26
+ // regulator settles the stop, and the questionneur asks its questions of his decision before it
27
+ // is applied, as Amazon's bar raiser and a cockpit's challenge and response; it has no number.
28
+ // - The stops of the circuit, each by its name and its exit code (ARRETS): the circuit ends on one
29
+ // with its code, and its header says when.
30
+ // - The table of the stops (TABLE_DES_ARRETS, below): each kind of stop and its gesture.
31
+ import { A_REPASSER } from './vitest-config/pression.mjs';
32
+
33
+ // Who works at a step the circuit leads itself, with no agent.
34
+ export const AUTO = 'auto';
35
+
36
+ // The rounds of a questioning, of the plan at 1.2 and of a decision at the arbitrage.
37
+ const TOURS = { cle: 'questionnement', libelle: 'tour', max: 4 };
38
+
39
+ export const ETAPES = [
40
+ { numero: '0', titre: 'Exploration', qui: ['explorateur'], etat: null },
41
+ {
42
+ numero: '1',
43
+ titre: 'Ouverture',
44
+ qui: [],
45
+ etat: null,
46
+ sous: [
47
+ {
48
+ numero: '1.1',
49
+ titre: 'Relevé des composants',
50
+ qui: [AUTO],
51
+ etat: null,
52
+ releve: true,
53
+ arrets: ['composants-manquants'],
54
+ },
55
+ {
56
+ numero: '1.2',
57
+ titre: 'Questionnement',
58
+ qui: ['questionneur', 'referent-technique'],
59
+ quiCourt: 'questionneur ⇄ référent',
60
+ etat: null,
61
+ questionnement: 'plan',
62
+ arrets: ['questionnement'],
63
+ boucles: [TOURS],
64
+ },
65
+ ],
66
+ },
67
+ {
68
+ numero: '2',
69
+ titre: 'Composants',
70
+ qui: [],
71
+ parComposant: true,
72
+ etat: null,
73
+ boucles: [
74
+ { cle: 'composant', libelle: 'composant', max: null },
75
+ { cle: 'tests', libelle: 'essai', max: 3, sur: ['2.1', '2.2'] },
76
+ { cle: 'code', libelle: 'essai', max: 3, sur: ['2.3', '2.4'] },
77
+ ],
78
+ sous: [
79
+ {
80
+ numero: '2.1',
81
+ titre: 'Tests',
82
+ qui: ['testwriter'],
83
+ parComposant: true,
84
+ etat: 'testwriter',
85
+ },
86
+ { numero: '2.2', titre: 'Preuve du rouge', qui: [AUTO], parComposant: true, etat: 'preuve' },
87
+ {
88
+ numero: '2.3',
89
+ titre: 'Code',
90
+ qui: ['developpeur'],
91
+ parComposant: true,
92
+ etat: 'developpeur',
93
+ },
94
+ { numero: '2.4', titre: 'Contrôles', qui: [AUTO], parComposant: true, etat: 'controles' },
95
+ ],
96
+ },
97
+ {
98
+ numero: '3',
99
+ titre: 'Passe finale',
100
+ qui: [AUTO],
101
+ etat: 'controles',
102
+ finale: true,
103
+ sous: [
104
+ {
105
+ numero: '3.1',
106
+ titre: 'Régénération des références',
107
+ qui: [AUTO],
108
+ etat: 'references',
109
+ declaration: 'references',
110
+ arrets: ['references'],
111
+ },
112
+ ],
113
+ },
114
+ {
115
+ numero: '4',
116
+ titre: 'Relecture',
117
+ qui: ['relecteur'],
118
+ etat: 'relecture',
119
+ boucles: [{ cle: 'relecture', libelle: 'tour', max: 3 }],
120
+ },
121
+ {
122
+ numero: '5',
123
+ titre: 'Intégration',
124
+ qui: [],
125
+ etat: null,
126
+ sous: [
127
+ { numero: '5.1', titre: 'Commit', qui: [AUTO], etat: 'integration' },
128
+ { numero: '5.2', titre: 'File : main, poussée, CI', qui: [AUTO], etat: 'file' },
129
+ ],
130
+ },
131
+ ];
132
+
133
+ // Every step and sub-step, in the order of the tree.
134
+ export const SUITE = ETAPES.flatMap(e => [e, ...(e.sous ?? [])]);
135
+
136
+ // The arbitrage of a stop, out of the sequence, under the stopped step.
137
+ export const ARBITRAGE = {
138
+ numero: null,
139
+ titre: "Arbitrage de l'arrêt",
140
+ qui: ['regulateur', 'questionneur'],
141
+ etat: null,
142
+ boucles: [{ cle: 'arret', libelle: 'arrêt', max: 3 }, TOURS],
143
+ };
144
+
145
+ // The bounded loop of key <cle>.
146
+ export const boucle = cle =>
147
+ [...SUITE, ARBITRAGE].flatMap(e => e.boucles ?? []).find(b => b.cle === cle) ?? null;
148
+
149
+ // The loops that run at the step <etape>: those of the arbitrage at the arbitrage; else those of
150
+ // each step that spans it, itself or its parent.
151
+ export const bouclesDe = etape => {
152
+ if (etape === ARBITRAGE) return ARBITRAGE.boucles;
153
+ const parent = ETAPES.find(e => e === etape || e.sous?.includes(etape));
154
+ return [...new Set([parent, etape])]
155
+ .flatMap(e => e?.boucles ?? [])
156
+ .filter(b => (b.sur ? b.sur.includes(etape.numero) : true));
157
+ };
158
+
159
+ // The exit code of each stop of the circuit.
160
+ export const ARRETS = {
161
+ lancement: 10,
162
+ bloqué: 11,
163
+ 'sans-fiche': 12,
164
+ preuve: 13,
165
+ controles: 14,
166
+ 'sans-livraison': 15,
167
+ 'relecture-arret': 16,
168
+ integration: 17,
169
+ question: 18,
170
+ 'hors-portee': 19,
171
+ besoin: 20,
172
+ 'composants-manquants': 21,
173
+ questionnement: 22,
174
+ references: 23,
175
+ 'a-repasser': A_REPASSER,
176
+ };
177
+
178
+ // The role that settles a stop of the circuit, and the one that questions a plan or a decision.
179
+ export const REGULATEUR = 'regulateur';
180
+ export const QUESTIONNEUR = 'questionneur';
181
+
182
+ // A role in words.
183
+ const NOMS = {
184
+ 'referent-technique': 'référent technique',
185
+ regulateur: 'régulateur',
186
+ developpeur: 'développeur',
187
+ };
188
+ export const nomDuRole = role => NOMS[role] ?? role;
189
+
190
+ // Who works at the step <e>, in words, « questionneur ⇄ référent »: its short form, else its
191
+ // roles' names.
192
+ export const quiDe = e => e.quiCourt ?? e.qui.map(nomDuRole).join(' ⇄ ');
193
+
194
+ // The step <e> in words, « 2.3 Code ».
195
+ export const libelle = e => `${e.numero} ${e.titre}`;
196
+
197
+ // The step of a circuit's state (`etape`, `finale`, its stop `raison`, its questioning under way
198
+ // `questionnement`, an object or its `objet`, the start of its survey of the components under way
199
+ // `releve`), or null (« integre », an unknown step): the step
200
+ // whose stop or questioning it is, else the step of its `etape`.
201
+ export const etapeDuCircuit = ({ etape, finale, raison, questionnement, releve }) => {
202
+ const objet = questionnement?.objet ?? questionnement ?? null;
203
+ return (
204
+ SUITE.find(
205
+ e =>
206
+ (raison && e.arrets?.includes(raison)) ||
207
+ (objet && e.questionnement === objet) ||
208
+ (!raison && !objet && releve && e.releve)
209
+ ) ??
210
+ SUITE.find(
211
+ e =>
212
+ e.etat !== null &&
213
+ e.etat === etape &&
214
+ Boolean(e.finale) === (etape === 'controles' && finale === true)
215
+ ) ??
216
+ null
217
+ );
218
+ };
219
+
220
+ // The step where the role agent <role> works, the arbitrage for the regulator, or null for any
221
+ // other agent.
222
+ export const etapeDuRole = role =>
223
+ role === AUTO ? null : ([...SUITE, ARBITRAGE].find(e => e.qui.includes(role)) ?? null);
224
+
225
+ // The step where a ticket's plan is questioned.
226
+ const QUESTIONNEMENT_DU_PLAN = SUITE.find(e => e.questionnement === 'plan');
227
+
228
+ // Whether the role agent <role> judges a plan or a stop (the questioning of the plan, the
229
+ // arbitrage), and so delivers nothing to the lot.
230
+ export const juge = role =>
231
+ [QUESTIONNEMENT_DU_PLAN, ARBITRAGE].some(e => role !== AUTO && e.qui.includes(role));
232
+
233
+ // The table of the stops and their gesture: the written policy of the circuit's stops, as Zuul
234
+ // and GitHub's merge queue write their bounded retries and their failure policy, and as the
235
+ // sheriffs of Chromium quarantine a flaky test by a rule, not by a judgement. The circuit
236
+ // (scripts/circuit.mjs) tries its lines in their order on each stop, and on the regulator's
237
+ // decision: a gesture the table names, it applies itself, with no regulator; the regulator is
238
+ // launched for what no line settles, and for the line that sends to him. Each line: `genre`, the
239
+ // kind of stop as the circuit reads it in its structured results and its state; `geste`, what
240
+ // follows; `cas`, the case of BPScript 320.3 (2026-10-20) it would have settled, by its stop (A)
241
+ // or its decision (D) in the analysis of that ticket. The gestures (GESTES):
242
+ // - `superviseur`: the circuit ends on the stop, its reason saying why, for the supervisor, with
243
+ // no regulator, and the board shows it under its step after « ⤷ Arrêt · superviseur »;
244
+ // - `a-repasser`: the circuit ends at 75, which the supervisor plays again at rest;
245
+ // - `regulateur`: a session of the regulator settles the stop;
246
+ // - `developpeur-du-composant`: the red goes to the developpeur of the component that owns it,
247
+ // never to the one under way: it waits for that component's checks, or, at the final pass, the
248
+ // circuit relaunches that developpeur (the checks of scripts/circuit.mjs);
249
+ // - `testwriter`: the circuit plays the component again from its testwriter.
250
+ export const GESTES = [
251
+ 'superviseur',
252
+ 'a-repasser',
253
+ 'regulateur',
254
+ 'developpeur-du-composant',
255
+ 'testwriter',
256
+ ];
257
+
258
+ export const TABLE_DES_ARRETS = [
259
+ {
260
+ // A component the survey of the ticket finds and its title misses (21): the circuit ends on
261
+ // it at 1.1, before any session.
262
+ genre: 'composants-manquants',
263
+ geste: 'superviseur',
264
+ cas: 'A3 : 020-loader mis au titre en cours de route ; A14 : 040-checker, nommé au critère 13 et absent du titre, rouge à la première passe finale',
265
+ },
266
+ {
267
+ // A questioning of the plan that does not converge at 1.2 (22), a disagreement or a fourth
268
+ // round still questioned.
269
+ genre: 'questionnement',
270
+ geste: 'superviseur',
271
+ cas: 'A14 : 040-checker nommé au critère 13 et absent du titre, que le plan questionné aurait relevé ; D15 : une consigne neuve sur le fond, hors du plan',
272
+ },
273
+ {
274
+ // The same stop (its name, step and component) after a RELANCE, the copy and the ticket's
275
+ // description unchanged since that decision.
276
+ genre: 'meme-arret-sans-fait-nouveau',
277
+ geste: 'superviseur',
278
+ cas: 'A8 → A9 : RELANCE --reprendre sans contexte, le même arrêt sur la même copie (D9) ; A11 → A13 : le même rouge de la passe finale trois fois (D13, D14)',
279
+ },
280
+ {
281
+ // Checks whose failures all depend on a duration under load (verifier's 75), the integration
282
+ // at 75, or the branch kept in the queue.
283
+ genre: 'a-repasser',
284
+ geste: 'a-repasser',
285
+ cas: 'A12, A17, A18 : un vrai rouge pris pour la charge du processeur (D14, D20, D21) ; seul le code 75 du vérificateur dit la charge',
286
+ },
287
+ {
288
+ // A red whose findings are all to settle (SARIF `genre` a-arbitrer), which no developpeur
289
+ // settles.
290
+ genre: 'a-arbitrer',
291
+ geste: 'regulateur',
292
+ cas: 'A16 : la garde consommateurs-arbitres attend un arbitrage, que seul le régulateur écrit (D19, juste)',
293
+ },
294
+ {
295
+ // A red that belongs to another component of the title than the one whose checks run.
296
+ genre: 'rouge-d-un-autre-composant',
297
+ geste: 'developpeur-du-composant',
298
+ cas: 'A4, A6, A8–A10 : la garde des mots de 030-binder rendue aux développeurs de 050, 130 et 900 (D5, D7, D9–D11)',
299
+ },
300
+ {
301
+ // A developpeur's sheet whose `renvoi` names the testwriter of a component of the title up to
302
+ // his own.
303
+ genre: 'renvoi-au-testwriter',
304
+ geste: 'testwriter',
305
+ cas: 'A5, A7 : « Agent : testwriter » non lu, les tests périmés de 050 et de 130 (D6, D8) ; A2 (D2)',
306
+ },
307
+ {
308
+ // On the supervisor's answer to a question, a component the title gained since the stop.
309
+ genre: 'composant-ajoute',
310
+ geste: 'testwriter',
311
+ cas: 'A3 : 020-loader mis au titre, puis RELANCE testwriter 020-loader (D4) ; A14 : 040-checker, puis testwriter 040-checker (D17)',
312
+ },
313
+ {
314
+ // The ticket's description changed while the stop was settled, a criterion added: the circuit
315
+ // then admits only a RELANCE from a testwriter.
316
+ genre: 'critere-neuf',
317
+ geste: 'testwriter',
318
+ cas: 'D15 : une consigne neuve sur le fond donnée au développeur de 030-binder sans test (A14), rendue au testwriter de 040-checker (D17)',
319
+ },
320
+ ];
321
+
322
+ // The line of the table for the kind of stop <genre>, or null.
323
+ export const gesteDe = genre => TABLE_DES_ARRETS.find(l => l.genre === genre) ?? null;