@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.
- package/.claude-plugin/plugin.json +9 -0
- package/METACADRE.md +41 -0
- package/agents/developpeur.md +10 -0
- package/agents/explorateur.md +9 -0
- package/agents/questionneur.md +11 -0
- package/agents/referent-technique.md +9 -0
- package/agents/regulateur.md +11 -0
- package/agents/relecteur.md +9 -0
- package/agents/testwriter.md +10 -0
- package/bin/grillhouse.mjs +125 -0
- package/cadre/templates/projet/.claude/settings.json +30 -0
- package/cadre/templates/projet/.editorconfig +47 -0
- package/cadre/templates/projet/.env.example +57 -0
- package/cadre/templates/projet/.eslintrc.cjs +56 -0
- package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
- package/cadre/templates/projet/.mcp.json +13 -0
- package/cadre/templates/projet/.prettierrc +11 -0
- package/cadre/templates/projet/CHANGELOG.md +61 -0
- package/cadre/templates/projet/CLAUDE.md +46 -0
- package/cadre/templates/projet/CONTRIBUTING.md +243 -0
- package/cadre/templates/projet/CREDITS.md +41 -0
- package/cadre/templates/projet/README.md +152 -0
- package/cadre/templates/projet/_gitignore +91 -0
- package/cadre/templates/projet/_npmignore +46 -0
- package/cadre/templates/projet/deployment/README.md +98 -0
- package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
- package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
- package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
- package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
- package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
- package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
- package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
- package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
- package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
- package/cadre/templates/projet/ecosystem.config.cjs +73 -0
- package/cadre/templates/projet/package.json +90 -0
- package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
- package/cadre/templates/projet/src/config.ts +68 -0
- package/cadre/templates/projet/src/errors.ts +102 -0
- package/cadre/templates/projet/src/index.ts +46 -0
- package/cadre/templates/projet/src/services/.gitkeep +8 -0
- package/cadre/templates/projet/src/types/index.ts +24 -0
- package/cadre/templates/projet/src/utils/logger.ts +106 -0
- package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
- package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
- package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
- package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
- package/cadre/templates/projet/tsconfig.eslint.json +9 -0
- package/cadre/templates/projet/tsconfig.json +41 -0
- package/cadre/templates/projet/tsdoc.json +6 -0
- package/cadre/templates/projet/vitest.config.ts +44 -0
- package/cadre/templates/turbo/paquets.json +29 -0
- package/cadre/templates/turbo/sans-paquets.json +28 -0
- package/charte.md +107 -0
- package/docs/ARCHITECTURE.md +90 -0
- package/hooks/crochet.mjs +33 -0
- package/hooks/hooks.json +58 -0
- package/hooks/register.tsx +558 -0
- package/package.json +75 -4
- package/scripts/avancement.mjs +244 -0
- package/scripts/bilan.mjs +523 -0
- package/scripts/branche-de-nuit.mjs +59 -0
- package/scripts/cadre.mjs +109 -0
- package/scripts/circuit.mjs +4375 -0
- package/scripts/code-mort.mjs +371 -0
- package/scripts/composants-affectes.mjs +598 -0
- package/scripts/consommateurs-arbitres.mjs +192 -0
- package/scripts/consommateurs.mjs +310 -0
- package/scripts/crochet.mjs +24 -0
- package/scripts/crochets.mjs +135 -0
- package/scripts/dernier-vert.mjs +208 -0
- package/scripts/dette.mjs +181 -0
- package/scripts/droits.mjs +354 -0
- package/scripts/enveloppe/codes.mjs +17 -0
- package/scripts/enveloppe/composant.mjs +11 -0
- package/scripts/enveloppe/enveloppe.mjs +628 -0
- package/scripts/enveloppe/lancer.sh +311 -0
- package/scripts/etapes.mjs +323 -0
- package/scripts/etat-du-circuit.mjs +104 -0
- package/scripts/frontieres.mjs +456 -0
- package/scripts/gardes-du-cadre.mjs +72 -0
- package/scripts/greffons.mjs +338 -0
- package/scripts/grillhouse-maj.mjs +514 -0
- package/scripts/index-interfaces.mjs +293 -0
- package/scripts/init.mjs +117 -0
- package/scripts/integration/file-attente.mjs +61 -0
- package/scripts/integration/file.mjs +494 -0
- package/scripts/integration/integrer.mjs +381 -0
- package/scripts/integration/rejeu.mjs +139 -0
- package/scripts/integration/verdict.mjs +13 -0
- package/scripts/interfaces-contre-code.mjs +547 -0
- package/scripts/ligne-etat/defaut.sh +110 -0
- package/scripts/ligne-etat/ligne-etat.mjs +93 -0
- package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
- package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
- package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
- package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
- package/scripts/migrations.mjs +43 -0
- package/scripts/nuit.sh +269 -0
- package/scripts/paquets.mjs +79 -0
- package/scripts/poussee-cles.mjs +46 -0
- package/scripts/preparation.mjs +84 -0
- package/scripts/references-par-les-types.mjs +420 -0
- package/scripts/registre.mjs +336 -0
- package/scripts/resultats.mjs +580 -0
- package/scripts/resumes.mjs +57 -0
- package/scripts/retirer-copie.sh +127 -0
- package/scripts/session-start.sh +203 -0
- package/scripts/sortie.mjs +155 -0
- package/scripts/sous-modules.sh +316 -0
- package/scripts/structure-guard.sh +27 -0
- package/scripts/superviseur.mjs +102 -0
- package/scripts/tableau.mjs +1886 -0
- package/scripts/tests.mjs +236 -0
- package/scripts/ticket.sh +23 -0
- package/scripts/turbo-des-paquets.mjs +899 -0
- package/scripts/un-commit-par-ticket.mjs +212 -0
- package/scripts/verifier.mjs +469 -0
- package/scripts/verrou-de-fichier.mjs +130 -0
- package/scripts/verrou-de-machine.mjs +211 -0
- package/scripts/verrous/commande.mjs +180 -0
- package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
- package/scripts/verrous/verrou.mjs +165 -0
- package/scripts/vitest-config/avancement.mjs +39 -0
- package/scripts/vitest-config/avancement.reporter.mjs +56 -0
- package/scripts/vitest-config/base-de-passe.mjs +128 -0
- package/scripts/vitest-config/base.d.ts +6 -0
- package/scripts/vitest-config/base.mjs +87 -0
- package/scripts/vitest-config/changements.mjs +173 -0
- package/scripts/vitest-config/cibles.mjs +207 -0
- package/scripts/vitest-config/duree.reporter.mjs +98 -0
- package/scripts/vitest-config/machine.setup.mjs +14 -0
- package/scripts/vitest-config/mesure.d.ts +6 -0
- package/scripts/vitest-config/mesure.mjs +13 -0
- package/scripts/vitest-config/mesure.setup.mjs +12 -0
- package/scripts/vitest-config/pression.mjs +90 -0
- package/scripts/vitest-config/resultats.mjs +23 -0
- package/scripts/vitest-config/resultats.setup.mjs +19 -0
- package/scripts/vitest.nuit.config.mjs +26 -0
- package/skills/developpeur/SKILL.md +175 -0
- package/skills/explorateur/SKILL.md +84 -0
- package/skills/grill/SKILL.md +238 -0
- package/skills/grill/references/releve-structurel.md +38 -0
- package/skills/mesure/SKILL.md +168 -0
- package/skills/pitmaster/SKILL.md +382 -0
- package/skills/pitmaster/references/cadre.md +119 -0
- package/skills/pitmaster/references/consigne-agent.md +308 -0
- package/skills/pitmaster/references/erreurs.md +25 -0
- package/skills/pitmaster/references/tour.md +124 -0
- package/skills/questionneur/SKILL.md +64 -0
- package/skills/redacteur/SKILL.md +84 -0
- package/skills/redacteur/references/documents-d-un-composant.md +105 -0
- package/skills/referent-technique/SKILL.md +145 -0
- package/skills/regulateur/SKILL.md +220 -0
- package/skills/release/SKILL.md +134 -0
- package/skills/release/scripts/update-version.cjs +92 -0
- package/skills/relecteur/SKILL.md +152 -0
- package/skills/relecteur/references/LICENSE-MIT +30 -0
- package/skills/relecteur/references/SOURCE.md +8 -0
- package/skills/relecteur/references/claims-check.md +14 -0
- package/skills/relecteur/references/deletion-check.md +14 -0
- package/skills/relecteur/references/edge-case-hunter.md +80 -0
- package/skills/relecteur/references/verification-gap.md +113 -0
- package/skills/testwriter/SKILL.md +187 -0
- package/types/index.d.ts +56 -0
- 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;
|