@roomi-fields/grillhouse 0.23.1 → 0.24.1

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 (65) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/METACADRE.md +1 -1
  3. package/agents/explorateur.md +4 -2
  4. package/agents/questionneur.md +1 -1
  5. package/bin/grillhouse.mjs +3 -58
  6. package/cadre/templates/projet/CLAUDE.md +5 -1
  7. package/cadre/templates/projet/docs/agents/issue-tracker.md +4 -4
  8. package/charte.md +23 -6
  9. package/docs/ARCHITECTURE.md +130 -1
  10. package/hooks/register.tsx +3 -13
  11. package/package.json +13 -1
  12. package/scripts/bilan.mjs +134 -88
  13. package/scripts/circuit.mjs +1547 -2248
  14. package/scripts/composants-affectes.mjs +27 -108
  15. package/scripts/consommateurs-arbitres.mjs +15 -21
  16. package/scripts/consommateurs.d.ts +8 -0
  17. package/scripts/consommateurs.mjs +5 -1
  18. package/scripts/dette.d.ts +23 -0
  19. package/scripts/dette.mjs +76 -13
  20. package/scripts/droits.mjs +38 -44
  21. package/scripts/elements-du-lot.mjs +246 -0
  22. package/scripts/enveloppe/enveloppe.mjs +105 -232
  23. package/scripts/enveloppe/lancer.sh +79 -49
  24. package/scripts/enveloppe/paquets-du-plan.mjs +49 -0
  25. package/scripts/etapes.mjs +148 -213
  26. package/scripts/frontieres.mjs +4 -2
  27. package/scripts/gardes-du-cadre.mjs +8 -6
  28. package/scripts/grillhouse-maj.mjs +113 -19
  29. package/scripts/index-interfaces.mjs +21 -1
  30. package/scripts/integration/integrer.mjs +2 -2
  31. package/scripts/interfaces-contre-code.mjs +25 -17
  32. package/scripts/migrations/0.23.0-config-vitest.mjs +26 -23
  33. package/scripts/migrations/0.23.2-copies-du-cadre.mjs +201 -0
  34. package/scripts/migrations/0.24.0-circuit-simplifie.mjs +274 -0
  35. package/scripts/migrations.mjs +41 -6
  36. package/scripts/paquets.d.ts +5 -0
  37. package/scripts/paquets.mjs +3 -0
  38. package/scripts/references-par-les-types.mjs +55 -36
  39. package/scripts/registre.mjs +7 -15
  40. package/scripts/resultats.mjs +283 -142
  41. package/scripts/resumes.mjs +1 -1
  42. package/scripts/session-start.sh +1 -2
  43. package/scripts/sources.mjs +28 -0
  44. package/scripts/sous-commandes.mjs +62 -0
  45. package/scripts/tableau.mjs +85 -191
  46. package/scripts/tests.mjs +19 -17
  47. package/scripts/verifier.mjs +5 -4
  48. package/scripts/verrou-de-machine.mjs +131 -24
  49. package/scripts/verrous/verrou.mjs +66 -5
  50. package/skills/developpeur/SKILL.md +60 -50
  51. package/skills/explorateur/SKILL.md +90 -64
  52. package/skills/grill/SKILL.md +1 -1
  53. package/skills/mesure/SKILL.md +2 -1
  54. package/skills/pitmaster/SKILL.md +132 -118
  55. package/skills/pitmaster/references/consigne-agent.md +54 -148
  56. package/skills/questionneur/SKILL.md +18 -18
  57. package/skills/relecteur/SKILL.md +69 -23
  58. package/skills/testwriter/SKILL.md +46 -35
  59. package/types/index.d.ts +1 -3
  60. package/agents/referent-technique.md +0 -9
  61. package/agents/regulateur.md +0 -11
  62. package/scripts/enveloppe/composant.mjs +0 -11
  63. package/scripts/etat-du-circuit.mjs +0 -104
  64. package/skills/referent-technique/SKILL.md +0 -145
  65. package/skills/regulateur/SKILL.md +0 -220
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env bash
2
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.
3
+ # plan, the packages it writes (one session of the testwriter or the developpeur for the whole
4
+ # ticket, `.` as its component and the plan as `--paquets`, grillhouse-3uv.95.6). The supervisor runs it in the background and is told when the session ends.
4
5
  #
5
- # npx grillhouse lancer [--modele <m>] [--effort <e>] [--seance <id> | --reprise <id>] <ticket> <rôle> <composant> <fichier de consigne>
6
+ # npx grillhouse lancer [--modele <m>] [--effort <e>] [--seance <id> | --reprise <id>] [--paquets <liste>] <ticket> <rôle> <composant> <fichier de consigne>
6
7
  #
7
8
  # - <composant> is the component's path from the root (`packages/<nom>`, or `.` for the whole
8
9
  # repository). It is judged before the copy is prepared, by the envelope's rule
@@ -10,6 +11,11 @@
10
11
  # when it exists, else in the main tree or in the tree of the branch agent/<ticket> when it
11
12
  # 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
13
  # it is the name of a package or of its folder, the refusal names the path it means.
14
+ # - --paquets <liste> is the plan of the session: the paths of the packages it writes, in the form
15
+ # of the envelope's variable (`ecrireLaListe` of paquets-du-plan.mjs, `packages/a:packages/b`),
16
+ # each judged as <composant> is; without it, the plan is <composant> alone. The envelope shows
17
+ # the session the whole copy and opens in writing the packages of the plan alone. It takes a
18
+ # value as --modele does.
13
19
  # - --modele <m> and --effort <e> override, for this session only, the model and the thinking level
14
20
  # of the role's definition (its agent in the frame's plugin, agents/<rôle>.md); they reach claude
15
21
  # as --model and --effort.
@@ -45,21 +51,22 @@
45
51
  # dependencies are installed when `node_modules` is absent or `package-lock.json` is newer than
46
52
  # the last installation (`node_modules/.package-lock.json`, written by npm), and it is built there
47
53
  # 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>...
54
+ # agent finds the builds of the packages out of its plan. As Bazel builds the dependencies of a
55
+ # target before the target: for a plan whose every path is a package of the frame (the circuit's
56
+ # session over the whole ticket, `.` as its component and the plan as `--paquets`, or a component
57
+ # alone), their providers are built first (the packages whose build their builds wait for, read
58
+ # in Turborepo's graph, `turbo run build --filter=<paquet>… --dry=json`, which node reads on its
59
+ # standard input, whatever its size), and a failure stops the launch, CODE_COPIE;
60
+ # then the traces of other packages their tests wait for (`grillhouse.traces`, read in Turborepo's
61
+ # plan of their tests, `turbo run test:vitest --filter=<paquet>… --dry=json`), which the envelope
62
+ # shows read-only, and a failure stops the launch, CODE_COPIE; then the packages, their own
63
+ # traces and what their tests read (`turbo run build <their traces> --filter=<paquet>...…
57
64
  # --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
65
+ # dependencies succeed goes to its end, so what their tests read without depending on them is
66
+ # built whole; the session opens, its instruction ending with a section « La construction de ton
60
67
  # 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
68
+ # provider's lot that breaks its consumers). Their consumers are not built. Any other plan, the
69
+ # whole repository (`.`) included, builds the copy whole (`turbo run build`). The
63
70
  # installation and the build play through `scripts/sous-modules.sh monter`, each shared submodule
64
71
  # of the main repository read-only, by the list the pose printed, whatever the installation
65
72
  # rewrites afterwards; with a shared submodule and no possible mount (bwrap absent or
@@ -69,11 +76,13 @@
69
76
  # - The session runs `claude -p` with the role's agent and, as its prompt, read on its standard
70
77
  # input from a temporary file the launcher writes and removes (an argument may not pass the
71
78
  # 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
79
+ # followed by the section « Ce que les composants d'avant publient », for each package of the
80
+ # plan (one part per package when the plan holds several, grillhouse-3uv.95.6): for each
81
+ # component whose `## Consommateurs` section names the package, its line, the names of the
82
+ # elements declared for the package, and the address of the rubric « Rend » of its frame, the
83
+ # rest of its elements left to the index of the interfaces (`scripts/index-interfaces.mjs
84
+ # --publie <plan>`, read in the prepared copy; a reading that fails stops the launch,
85
+ # CODE_COPIE), then the
77
86
  # section « Ton dossier de travail », which names the session's working directory, where every
78
87
  # temporary file goes (`dossierDeTravail` of enveloppe.mjs, the envelope's TMPDIR), then the
79
88
  # section « La construction de ton paquet échoue » when it fails. The prompt
@@ -83,7 +92,7 @@
83
92
  # the launch, CODE_COPIE): the user's own configuration left out, its settings on the command
84
93
  # line (its refusals, among them the change of its author at the ticket, AUTEUR_FIXE: its
85
94
  # 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.
95
+ # locks), its tools and the servers of the index, for the reading of the whole copy.
87
96
  # - A role that ends on a sheet (scripts/resultats.mjs: the testwriter, the developpeur) runs with
88
97
  # `--output-format json --json-schema <its schema>` (`npx grillhouse resultats --schema
89
98
  # <rôle>`): claude's JSON result, its sheet in `structured_output`, goes to
@@ -96,19 +105,22 @@
96
105
  # launcher writes the session's exit code to .claude/worktrees/<ticket>.fin, which a launch
97
106
  # removes first: a launch that stops before its session leaves none, so the circuit
98
107
  # (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.
108
+ # A session its lock stopped on a write out of the plan (scripts/verrous/verrou.mjs) left the
109
+ # reason in its working directory (`hors-plan`, removed before the session): the launcher moves
110
+ # it to .claude/worktrees/<ticket>.hors-plan, which a launch removes first and the circuit reads,
111
+ # and adds it to the log. It then writes the session's line to the register
112
+ # (scripts/registre.mjs --copie), and exits with the envelope's code.
101
113
  # - The exit codes are named in scripts/enveloppe/codes.mjs; a malformed call exits CODE_USAGE.
102
114
  # - CLAUDE_BIN replaces `claude` (the tests use it).
103
115
  set -euo pipefail
104
116
  ici="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
105
117
  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>"
118
+ usage="usage: npx grillhouse lancer [--modele <m>] [--effort <e>] [--seance <id> | --reprise <id>] [--paquets <liste>] <ticket> <rôle> <composant> <fichier de consigne>"
107
119
  refus_usage() { echo "$usage" >&2; exit "$CODE_USAGE"; }
108
- modele="" effort="" seance="" reprise="" surcharge=()
120
+ modele="" effort="" seance="" reprise="" liste="" surcharge=()
109
121
  while [ $# -gt 0 ]; do
110
122
  case "$1" in
111
- --modele|--effort|--seance|--reprise)
123
+ --modele|--effort|--seance|--reprise|--paquets)
112
124
  [ $# -ge 2 ] && [ -n "$2" ] && [[ "$2" != --* ]] || refus_usage
113
125
  if [ "$1" = --modele ]; then
114
126
  modele=$2
@@ -116,6 +128,8 @@ while [ $# -gt 0 ]; do
116
128
  seance=$2
117
129
  elif [ "$1" = --reprise ]; then
118
130
  reprise=$2
131
+ elif [ "$1" = --paquets ]; then
132
+ liste=$2
119
133
  else
120
134
  case "$2" in low|medium|high|xhigh|max) effort=$2 ;; *) refus_usage ;; esac
121
135
  fi
@@ -131,6 +145,8 @@ done
131
145
  [ -z "$reprise" ] || surcharge+=(--resume "$reprise")
132
146
  [ $# -eq 4 ] || refus_usage
133
147
  ticket=$1 role=$2 composant=$3 consigne=$4
148
+ # The plan of the session: the packages it writes.
149
+ paquets=${liste:-$composant}
134
150
  # The main tree, from the common git directory: launched from an agent copy, the new copy still
135
151
  # lands next to the others, never inside one.
136
152
  racine=$(cd "$(git rev-parse --path-format=absolute --git-common-dir)/.." && pwd)
@@ -147,7 +163,7 @@ if [ -d "$racine/.claude/worktrees/$ticket" ]; then
147
163
  elif git -C "$racine" rev-parse --verify --quiet "refs/heads/agent/$ticket" > /dev/null; then
148
164
  branche="agent/$ticket"
149
165
  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"
166
+ 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 { lireLaListe } from '$ici/paquets-du-plan.mjs'; import { paquets } from '$ici/../paquets.mjs'; const [depot, composant, liste, 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)); const tous = paquets(depot); process.stdout.write([composant, ...lireLaListe(liste)].map(c => jugerComposant({ depot, composant: c, paquets: tous }, { existe })).find(Boolean) ?? '');" "$juge" "$composant" "$paquets" "$branche") || exit "$CODE_USAGE"
151
167
  [ -z "$refus_composant" ] || { echo "⛔ $refus_composant" >&2; exit "$CODE_USAGE"; }
152
168
  # The ticket is matched whole: `demo-3160` is not `demo-316`, and its dots are dots.
153
169
  motif=$(printf '%s' "$ticket" | sed 's/[.[\*^$]/\\&/g')
@@ -157,8 +173,9 @@ copie="$racine/.claude/worktrees/$ticket"
157
173
  journal="$copie.log"
158
174
  fin="$copie.fin"
159
175
  resultat="$copie.resultat.json"
176
+ hors_plan="$copie.hors-plan"
160
177
  mkdir -p "$(dirname "$copie")"
161
- rm -f "$fin" "$resultat"
178
+ rm -f "$fin" "$resultat" "$hors_plan"
162
179
  printf '=== %s · %s · %s ===\n' "$(date -Iseconds)" "$role" "$composant" >> "$journal"
163
180
  # A branch still in the queue is not relaunched: the queue brings it onto main, or sends it back.
164
181
  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
@@ -217,24 +234,28 @@ echec=$(mktemp)
217
234
  if [ ! -d "$copie/node_modules" ] || [ "$copie/package-lock.json" -nt "$installe" ]; then
218
235
  monter npm --prefix "$copie" install --no-save --no-audit --no-fund --silent || exit 1
219
236
  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
237
+ # The packages of the session's plan that are packages of the frame: their names, one per
238
+ # line; none when a path of the plan is no package of the frame (the root, `.`, a folder).
239
+ noms=$(node --input-type=module -e "import path from 'node:path'; import { lireLaListe } from '$ici/paquets-du-plan.mjs'; import { paquets } from '$ici/../paquets.mjs'; const [r, liste] = process.argv.slice(1); const tous = paquets(r).filter(p => !p.hors); const l = lireLaListe(liste).map(d => tous.find(p => p.dossier === path.normalize(d).replace(/\/\$/, ''))?.pkg.name ?? null); process.stdout.write(l.length && l.every(Boolean) ? l.join('\n') : '');" "$copie" "$paquets") || exit 1
240
+ if [ -z "$noms" ]; then
223
241
  (cd "$copie" && monter npx --no-install turbo run build) || exit 1
224
242
  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
243
+ mapfile -t lesNoms <<<"$noms"
244
+ cibles=() lecteurs=()
245
+ for n in "${lesNoms[@]}"; do cibles+=("--filter=$n"); lecteurs+=("--filter=$n..."); done
246
+ # Their providers: the packages whose build their own builds wait for, as Turborepo reads them.
247
+ graphe=$(cd "$copie" && monter npx --no-install turbo run build "${cibles[@]}" --dry=json) || exit 1
227
248
  # 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
249
+ fournisseurs=$(node -e 'const plan = process.argv.slice(1); const t = JSON.parse(require("node:fs").readFileSync(0, "utf8")).tasks.filter(t => !plan.includes(t.package)); console.log([...new Set(t.map(t => `--filter=${t.package}`))].join("\n"));' "${lesNoms[@]}" <<<"$graphe") || exit 1
229
250
  if [ -n "$fournisseurs" ]; then
230
251
  mapfile -t filtres <<<"$fournisseurs"
231
252
  (cd "$copie" && monter npx --no-install turbo run build "${filtres[@]}") || exit 1
232
253
  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"; }
254
+ # The traces their tests wait for, as Turborepo's plan of their tests reads them. Another
255
+ # package's trace is produced here, and a failure stops the launch, as a provider's; a trace
256
+ # of the plan waits for its build, and is produced with it below.
257
+ plan=$(cd "$copie" && monter npx --no-install turbo run test:vitest "${cibles[@]}" --dry=json) || exit 1
258
+ traces() { node --input-type=module -e "import { readFileSync } from 'node:fs'; import { tracesDuPlan } from '$ici/../turbo-des-paquets.mjs'; const [propres, ...plan] = process.argv.slice(1); console.log(tracesDuPlan(JSON.parse(readFileSync(0, 'utf8')), t => plan.includes(t.package) === (propres === 'oui')).join('\n'));" "$1" "${lesNoms[@]}" <<<"$plan"; }
238
259
  autres=$(traces non) || exit 1
239
260
  if [ -n "$autres" ]; then
240
261
  mapfile -t ids <<<"$autres"
@@ -243,26 +264,29 @@ echec=$(mktemp)
243
264
  propres=$(traces oui) || exit 1
244
265
  ids=()
245
266
  [ -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
267
+ # The packages of the plan, their own traces, and what their tests read (the shared test
268
+ # material), built after their providers: a failure does not stop the launch, it goes into
269
+ # the instruction. Each task whose dependencies succeed goes to its end
270
+ # (--continue=dependencies-successful): one package's failure does not cancel the build of
271
+ # what the tests read without depending on it, which the envelope shows read-only.
272
+ if ! (cd "$copie" && monter npx --no-install turbo run build ${ids[@]+"${ids[@]}"} "${lecteurs[@]}" --continue=dependencies-successful) > "$echec" 2>&1; then
252
273
  cat "$echec"
253
- echo "⚠️ La construction de $paquet échoue : la séance s'ouvre, la consigne porte l'erreur."
274
+ echo "⚠️ La construction du plan (${lesNoms[*]}) échoue : la séance s'ouvre, la consigne porte l'erreur."
254
275
  else
255
276
  : > "$echec"
256
277
  fi
257
278
  fi
258
279
  fi
259
280
  ) >> "$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") \
281
+ # What the components before each package of the plan publish, read in the prepared copy,
282
+ # follows the instruction.
283
+ publie=$(cd "$copie" && node "$ici/../index-interfaces.mjs" --publie "$paquets" 2>> "$journal") \
262
284
  || { rm -f "$echec"; echo "⛔ Ce que publient les composants d'avant n'a pas pu se lire : voir $journal." >&2; exit "$CODE_COPIE"; }
263
285
  # The session's working directory, the one the envelope gives as TMPDIR, follows them.
264
286
  travail=$(node --input-type=module -e "import { dossierDeTravail } from '$ici/enveloppe.mjs'; process.stdout.write(dossierDeTravail(process.argv[1]));" "$copie" 2>> "$journal") \
265
287
  || { rm -f "$echec"; echo "⛔ Le dossier de travail de la séance n'a pas pu se lire : voir $journal." >&2; exit "$CODE_COPIE"; }
288
+ # The mark a lock leaves there when it stops the session out of the plan: none before the session.
289
+ rm -f "$travail/hors-plan"
266
290
  dossier=$'\n\n'"## Ton dossier de travail"$'\n\n'"Tout fichier temporaire s'écrit dans \`$travail\`, que \`\$TMPDIR\` nomme aussi."
267
291
  construction=
268
292
  if [ -s "$echec" ]; then
@@ -293,7 +317,7 @@ schema=$(node "$ici/../resultats.mjs" --schema "$role" 2> /dev/null) || schema=
293
317
  # An envelope in error does not stop the launcher (set -e): its code is kept for the exit.
294
318
  code=0
295
319
  lancer_claude() {
296
- node "$ici/enveloppe.mjs" "$copie" "$composant" "$role" -- \
320
+ node "$ici/enveloppe.mjs" "$copie" "$paquets" "$role" -- \
297
321
  "${CLAUDE_BIN:-claude}" -p --agent "$role" \
298
322
  ${surcharge[@]+"${surcharge[@]}"} ${fiche[@]+"${fiche[@]}"} --permission-mode bypassPermissions \
299
323
  "${droits_du_role[@]}" < "$prompt"
@@ -305,6 +329,12 @@ else
305
329
  lancer_claude >> "$journal" 2>&1 || code=$?
306
330
  fi
307
331
  rm -f "$prompt"
332
+ # A session its lock stopped out of the plan (scripts/verrous/verrou.mjs): its reason goes to the
333
+ # circuit, and to the log.
334
+ if [ -f "$travail/hors-plan" ]; then
335
+ mv "$travail/hors-plan" "$hors_plan"
336
+ { cat "$hors_plan"; echo; } >> "$journal"
337
+ fi
308
338
  echo "$code" > "$fin"
309
339
  # The envelope ended: the session's line goes to the register from outside it, whatever its end hook did.
310
340
  node "$ici/../registre.mjs" --copie "$copie" >> "$journal" 2>&1 || true
@@ -0,0 +1,49 @@
1
+ // The packages of the plan of the envelope a command runs in. The envelope names them to every
2
+ // command of its session, in the variable `GRILLHOUSE_ENVELOPPE`: the list of the paths the plan
3
+ // names inside the copy (`packages/a`, or `src/parser` in a one-package project), joined by the
4
+ // separator of a list of paths (`path.delimiter`, as NODE_PATH and PATH join theirs). As the
5
+ // visibility of Bazel and the module boundaries of Nx refuse a dependency while the code stays
6
+ // readable, the session reads the whole copy and writes these packages only: a command of the
7
+ // frame that reads the list builds, judges and writes them alone.
8
+ //
9
+ // This module uses Node alone, as the modules of scripts/paquets.mjs.
10
+ import path from 'node:path';
11
+
12
+ export const VARIABLE = 'GRILLHOUSE_ENVELOPPE';
13
+ // The file the lock of a session leaves in its working directory when it stops the session on a
14
+ // write out of the plan (scripts/verrous/verrou.mjs); the launcher hands it to the circuit as
15
+ // `<copie>.hors-plan` (scripts/enveloppe/lancer.sh).
16
+ export const MARQUE_HORS_PLAN = 'hors-plan';
17
+
18
+ // The list <paquets> as the variable carries it.
19
+ export const ecrireLaListe = paquets => paquets.join(path.delimiter);
20
+
21
+ // The list the text <texte> of the variable carries.
22
+ export const lireLaListe = texte =>
23
+ String(texte ?? '')
24
+ .split(path.delimiter)
25
+ .filter(Boolean);
26
+
27
+ // The packages of the plan of the envelope named in <env>, or null outside an envelope.
28
+ export const paquetsDeLEnveloppe = (env = process.env) =>
29
+ env[VARIABLE] ? lireLaListe(env[VARIABLE]) : null;
30
+
31
+ // The directory of the package of <paquetsDeLaCopie> (`paquets` of scripts/paquets.mjs, each
32
+ // `dossier` relative to the copy) that holds the path <chemin> of the copy, the deepest; `.`, the
33
+ // root, when none holds it.
34
+ export function paquetQuiTient(chemin, paquetsDeLaCopie) {
35
+ const rel = path.normalize(chemin).replace(/\/+$/, '');
36
+ const tenants = paquetsDeLaCopie
37
+ .map(p => path.normalize(p.dossier))
38
+ .filter(d => rel === d || rel.startsWith(`${d}/`));
39
+ return tenants.sort((a, b) => b.length - a.length)[0] ?? '.';
40
+ }
41
+
42
+ // The directories, relative to the copy, that the plan <paquets> writes, sorted: for each path of
43
+ // the plan, the package that holds it (`paquetQuiTient`). A path that no package holds (a folder of
44
+ // the root, `scripts`; `src/parser` in a one-package project; `.`) writes the package that holds
45
+ // it, the root: the plan then writes the whole copy, `['.']`.
46
+ export function ecritsDuPlan(paquets, paquetsDeLaCopie) {
47
+ const ecrits = [...new Set(paquets.map(p => paquetQuiTient(p, paquetsDeLaCopie)))].sort();
48
+ return ecrits.includes('.') ? ['.'] : ecrits;
49
+ }