@roomi-fields/grillhouse 0.23.0 → 0.24.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 (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 +127 -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/gardes-du-cadre.mjs +8 -6
  27. package/scripts/grillhouse-maj.mjs +31 -9
  28. package/scripts/index-interfaces.mjs +21 -1
  29. package/scripts/integration/integrer.mjs +2 -2
  30. package/scripts/interfaces-contre-code.mjs +25 -17
  31. package/scripts/migrations/0.23.0-config-vitest.mjs +26 -23
  32. package/scripts/migrations/0.23.2-copies-du-cadre.mjs +201 -0
  33. package/scripts/migrations/0.24.0-circuit-simplifie.mjs +231 -0
  34. package/scripts/migrations.mjs +41 -6
  35. package/scripts/paquets.d.ts +5 -0
  36. package/scripts/paquets.mjs +3 -0
  37. package/scripts/references-par-les-types.mjs +55 -36
  38. package/scripts/registre.mjs +7 -15
  39. package/scripts/resultats.mjs +283 -142
  40. package/scripts/resumes.mjs +1 -1
  41. package/scripts/session-start.sh +1 -2
  42. package/scripts/sortie.mjs +1 -1
  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
@@ -3,121 +3,81 @@
3
3
  // list, which the circuit (scripts/circuit.mjs), the board (scripts/tableau.mjs), the bilan
4
4
  // (scripts/bilan.mjs) and the texts read.
5
5
  //
6
- // - A step (ETAPES): its number (« 2 », « 2.3 »), its title, who works there (`qui`: the role
6
+ // - A step (ETAPES): its number (« 2 », « 4.1 »), its title, who works there (`qui`: the role
7
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.
8
+ // sub-steps), its sub-steps (`sous`), and the step of the circuit's state it is (`etat`: the
9
+ // `etape` of `.claude/worktrees/<ticket>.circuit.json`, the final pass being the checks once
10
+ // `finale`). Every step runs once for the whole ticket, as a merge request has one author of
11
+ // its tests and one of its code, one CI on the whole and one review: one session of the
12
+ // testwriter (2) and one of the developpeur (3) carry the whole plan.
13
+ // The plan (1) is read elsewhere in the state: by the work on the plan under way
14
+ // (`questionnement`, the explorateur's or the questionneur's session) and by its stop (`arrets`).
15
+ // SUITE is the tree in its order, each step before its sub-steps.
16
+ // - A step carries as many bounded loops as it needs (`boucles`), each spanning the step and its
17
+ // sub-steps: each its key (`cle`, which keys its count in the state's `boucles`), its label and
18
+ // its maximum (`max`, the pass that stops it): the testwriter's sessions after a refused proof,
19
+ // the developpeur's sessions with their checks, the reviews. The circuit stops each loop at its
20
+ // maximum and writes its count in its state; the board shows « <label> <n>/<max> » on the
21
+ // ticket's line from the second pass.
28
22
  // - The stops of the circuit, each by its name and its exit code (ARRETS): the circuit ends on one
29
23
  // 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.
24
+ // - The gestures of the circuit (GESTES, below): what it does itself on a stop a rule settles,
25
+ // with no session.
26
+ // - The question of each other stop (MONTEES, below): what goes up to the supervisor, and who
27
+ // must answer it.
31
28
  import { A_REPASSER } from './vitest-config/pression.mjs';
32
29
 
33
30
  // Who works at a step the circuit leads itself, with no agent.
34
31
  export const AUTO = 'auto';
35
32
 
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
33
  export const ETAPES = [
40
- { numero: '0', titre: 'Exploration', qui: ['explorateur'], etat: null },
41
34
  {
42
35
  numero: '1',
43
- titre: 'Ouverture',
44
- qui: [],
36
+ titre: 'Plan',
37
+ qui: ['explorateur', 'questionneur'],
45
38
  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
- ],
39
+ questionnement: 'plan',
40
+ arrets: ['questionnement'],
66
41
  },
67
42
  {
68
43
  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
- ],
44
+ titre: 'Tests',
45
+ qui: ['testwriter'],
46
+ etat: 'testwriter',
47
+ boucles: [{ cle: 'tests', libelle: 'essai', max: 3 }],
48
+ sous: [{ numero: '2.1', titre: 'Preuve du rouge', qui: [AUTO], etat: 'preuve' }],
96
49
  },
97
50
  {
98
51
  numero: '3',
99
- titre: 'Passe finale',
100
- qui: [AUTO],
101
- etat: 'controles',
102
- finale: true,
52
+ titre: 'Code',
53
+ qui: ['developpeur'],
54
+ etat: 'developpeur',
55
+ boucles: [{ cle: 'code', libelle: 'essai', max: 3 }],
56
+ sous: [{ numero: '3.1', titre: 'Contrôles', qui: [AUTO], etat: 'controles' }],
57
+ },
58
+ {
59
+ numero: '4',
60
+ titre: 'Passe finale et relecture',
61
+ qui: [],
62
+ etat: null,
103
63
  sous: [
64
+ { numero: '4.1', titre: 'Passe finale', qui: [AUTO], etat: 'controles', finale: true },
104
65
  {
105
- numero: '3.1',
66
+ numero: '4.2',
106
67
  titre: 'Régénération des références',
107
68
  qui: [AUTO],
108
69
  etat: 'references',
109
- declaration: 'references',
110
70
  arrets: ['references'],
111
71
  },
72
+ {
73
+ numero: '4.3',
74
+ titre: 'Relecture',
75
+ qui: ['relecteur'],
76
+ etat: 'relecture',
77
+ boucles: [{ cle: 'relecture', libelle: 'tour', max: 3 }],
78
+ },
112
79
  ],
113
80
  },
114
- {
115
- numero: '4',
116
- titre: 'Relecture',
117
- qui: ['relecteur'],
118
- etat: 'relecture',
119
- boucles: [{ cle: 'relecture', libelle: 'tour', max: 3 }],
120
- },
121
81
  {
122
82
  numero: '5',
123
83
  titre: 'Intégration',
@@ -133,29 +93,19 @@ export const ETAPES = [
133
93
  // Every step and sub-step, in the order of the tree.
134
94
  export const SUITE = ETAPES.flatMap(e => [e, ...(e.sous ?? [])]);
135
95
 
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
96
  // The bounded loop of key <cle>.
146
- export const boucle = cle =>
147
- [...SUITE, ARBITRAGE].flatMap(e => e.boucles ?? []).find(b => b.cle === cle) ?? null;
97
+ export const boucle = cle => SUITE.flatMap(e => e.boucles ?? []).find(b => b.cle === cle) ?? null;
148
98
 
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.
99
+ // The loops that run at the step <etape>: its own and its parent's.
151
100
  export const bouclesDe = etape => {
152
- if (etape === ARBITRAGE) return ARBITRAGE.boucles;
153
101
  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));
102
+ return [...new Set([parent, etape])].flatMap(e => e?.boucles ?? []);
157
103
  };
158
104
 
105
+ // The agents who write the lot, the testwriter its tests and the developpeur its code: the roles a
106
+ // blocking finding of the reviewer names.
107
+ export const AGENTS_DU_LOT = ['testwriter', 'developpeur'];
108
+
159
109
  // The exit code of each stop of the circuit.
160
110
  export const ARRETS = {
161
111
  lancement: 10,
@@ -166,46 +116,36 @@ export const ARRETS = {
166
116
  'sans-livraison': 15,
167
117
  'relecture-arret': 16,
168
118
  integration: 17,
169
- question: 18,
170
119
  'hors-portee': 19,
171
120
  besoin: 20,
172
- 'composants-manquants': 21,
173
121
  questionnement: 22,
174
122
  references: 23,
123
+ 'hors-plan': 24,
175
124
  'a-repasser': A_REPASSER,
176
125
  };
177
126
 
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';
127
+ // The role that writes the plan and answers its questions once, and the role that questions it.
128
+ export const EXPLORATEUR = 'explorateur';
180
129
  export const QUESTIONNEUR = 'questionneur';
181
130
 
182
131
  // A role in words.
183
- const NOMS = {
184
- 'referent-technique': 'référent technique',
185
- regulateur: 'régulateur',
186
- developpeur: 'développeur',
187
- };
132
+ const NOMS = { developpeur: 'développeur' };
188
133
  export const nomDuRole = role => NOMS[role] ?? role;
189
134
 
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(' ⇄ ');
135
+ // Who works at the step <e>, in words: its roles' names.
136
+ export const quiDe = e => e.qui.map(nomDuRole).join(' ⇄ ');
193
137
 
194
- // The step <e> in words, « 2.3 Code ».
138
+ // The step <e> in words, « 3 Code ».
195
139
  export const libelle = e => `${e.numero} ${e.titre}`;
196
140
 
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 }) => {
141
+ // The step of a circuit's state (`etape`, `finale`, its stop `raison`, the work on its plan under
142
+ // way `questionnement`, an object or its `objet`), or null (« integre », an unknown step): the step
143
+ // whose stop or work on the plan it is, else the step of its `etape`.
144
+ export const etapeDuCircuit = ({ etape, finale, raison, questionnement }) => {
202
145
  const objet = questionnement?.objet ?? questionnement ?? null;
203
146
  return (
204
147
  SUITE.find(
205
- e =>
206
- (raison && e.arrets?.includes(raison)) ||
207
- (objet && e.questionnement === objet) ||
208
- (!raison && !objet && releve && e.releve)
148
+ e => (raison && e.arrets?.includes(raison)) || (objet && e.questionnement === objet)
209
149
  ) ??
210
150
  SUITE.find(
211
151
  e =>
@@ -217,107 +157,102 @@ export const etapeDuCircuit = ({ etape, finale, raison, questionnement, releve }
217
157
  );
218
158
  };
219
159
 
220
- // The step where the role agent <role> works, the arbitrage for the regulator, or null for any
221
- // other agent.
160
+ // The step where the role agent <role> works, or null for any other agent.
222
161
  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');
162
+ role === AUTO ? null : (SUITE.find(e => e.qui.includes(role)) ?? null);
227
163
 
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.
164
+ // The gestures of the circuit: what it does itself on a stop a rule settles, as a CI applies its
165
+ // rules (retry, regenerate, skip a job without object) without asking anyone, and as the sheriffs
166
+ // of Chromium quarantine a flaky test by a rule, not by a judgement. Each gesture: `genre`, its
167
+ // name as the circuit writes it at the ticket and in the log; `geste`, what the circuit does;
168
+ // `regle`, the rule that settles it; `cas`, the case of BPScript it settles (the analysis of 320.3,
169
+ // its stop A or its decision D, or that of 320.9). On each one, the circuit writes at the ticket
170
+ // `Circuit · geste : <genre> → <geste> (règle : <regle>)`, and the same line in the ticket's log.
250
171
  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
172
  {
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
173
  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',
174
+ geste: 'le circuit finit à 75, que le superviseur rejoue au repos par --reprendre',
175
+ regle: 'seul le code 75 du vérificateur dit la charge de la machine',
176
+ cas: 'A12, A17, A18 : un vrai rouge pris pour la charge du processeur (D14, D20, D21)',
286
177
  },
287
178
  {
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)',
179
+ genre: 'rejeu',
180
+ geste: 'la commande joue une seconde fois',
181
+ regle:
182
+ 'une commande des contrôles hors de la passe finale, ou l’intégration, à 75 rejoue une fois avant l’arrêt',
183
+ cas: 'A12 : un contrôle à 75, rejoué avant de conclure à la charge',
293
184
  },
294
185
  {
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
186
  genre: 'renvoi-au-testwriter',
304
- geste: 'testwriter',
187
+ geste: 'au testwriter, que la fiche du développeur nomme, puis la preuve et le développeur',
188
+ regle: 'le rouge qui reste est dans les tests du lot (`renvoi` de sa fiche)',
305
189
  cas: 'A5, A7 : « Agent : testwriter » non lu, les tests périmés de 050 et de 130 (D6, D8) ; A2 (D2)',
306
190
  },
307
191
  {
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)',
192
+ genre: 'regeneration',
193
+ geste:
194
+ 'à la régénération des références (4.2), puis la passe finale restreinte à ce qu’elle change',
195
+ regle:
196
+ 'un rouge de la passe finale est celui d’une référence, même mêlé à d’autres : une que le projet déclare (`grillhouse.references`), ou un fichier que le cadre engendre (l’index des interfaces, les turbo.json, les rapports d’API) ; ou la passe abaisse la dette des gardes (`dette.mjs --abaisser`, depuis ses notes)',
197
+ cas: 'BP 320.9 : 292 min autour des références, dont 163 min d’attente pour régénérer à la main l’index des interfaces, le plafond et l’empreinte, des rouges de références mêlés à d’autres',
312
198
  },
313
199
  {
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)',
200
+ genre: 'rien-a-livrer',
201
+ geste: 'aux contrôles, qui jugent le lot',
202
+ regle:
203
+ 'un développeur qui ne change aucun fichier du plan n’a rien à livrer quand les contrôles sont verts ; rouges, l’arrêt monte (sans-livraison)',
204
+ cas: 'BP 320.9 : 995-tools n’a rien à livrer, 4 montées et 49 min (B5)',
319
205
  },
320
206
  ];
321
207
 
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;
208
+ // The gesture of name <genre>, or null.
209
+ export const gesteDe = genre => GESTES.find(g => g.genre === genre) ?? null;
210
+
211
+ // Who answers a question of the circuit: the supervisor, or the owner, whose question the
212
+ // supervisor carries.
213
+ export const SUPERVISEUR = 'superviseur';
214
+ export const RESPONSABLE = 'responsable';
215
+
216
+ // The commands by which the supervisor answers a question (scripts/circuit.mjs): resume, extend
217
+ // the plan, stop.
218
+ const REPONSES =
219
+ 'Le superviseur répond par une commande du circuit : `--reprendre` (avec `--contexte`) ou `--depuis <testwriter|developpeur>` (avec `--contexte`), `--etendre <paquet>` (le plan gagne ce paquet, puis l’étape en cours reprend), ou `--arreter`.';
220
+
221
+ // The question of each stop no gesture settles, as a CI calls a person only for what no rule
222
+ // settles (the approval of a review): every stop of ARRETS but « a-repasser », a gesture. The
223
+ // circuit ends on the stop, its state carrying the question (`question`: `{ arret, fait, piece,
224
+ // question, qui }`: the fact, its piece, file:line or command and output, the question and who must
225
+ // answer it), for the supervisor, who answers by a command of the circuit and carries to the owner
226
+ // what is his (`qui` responsable: the reviewer's question on a real word of the language).
227
+ export const MONTEES = Object.fromEntries(
228
+ Object.entries({
229
+ lancement:
230
+ 'Le lanceur s’arrête avant la séance : la machine se répare-t-elle, pour reprendre ?',
231
+ bloqué:
232
+ 'La séance n’a plus bougé, et le circuit l’a arrêtée : reprendre l’étape, ou arrêter le circuit ?',
233
+ 'sans-fiche':
234
+ 'La séance finit deux fois sans fiche conforme : reprendre l’étape, ou arrêter le circuit ?',
235
+ preuve:
236
+ 'La troisième preuve du rouge refuse le lot de tests : quel contexte donner au testwriter, ou le plan doit-il changer ?',
237
+ controles:
238
+ 'Un rouge reste, que le circuit ne rend pas au développeur (hors du plan, sans fichier, à arbitrer), ou que trois séances n’ont pas levé : avec quel contexte le lever, ou le plan doit-il s’étendre ?',
239
+ 'sans-livraison':
240
+ 'Le développeur ne change rien et le rouge reste : avec quel contexte le lever, ou le plan doit-il changer ?',
241
+ 'relecture-arret':
242
+ 'La relecture ne rend pas le lot à un agent (un constat sans destinataire valide, un critère en cause, ou la troisième relecture rendue) : que devient le plan, et qui reprend ?',
243
+ integration:
244
+ 'L’intégration refuse le lot, ou la file rouvre le ticket : que corrige le lot, et qui le reprend ?',
245
+ 'hors-portee':
246
+ 'L’agent se dit bloqué hors de sa portée : le plan s’étend-il au paquet qu’il nomme, ou un autre agent lève-t-il le reste ?',
247
+ besoin: 'Une réponse manque pour avancer : laquelle ?',
248
+ questionnement:
249
+ 'Des questions du plan restent ouvertes après la réponse de l’explorateur : le plan change-t-il (`bd update`), ou reste-t-il tel quel ? `--reprendre` le fait passer tel qu’il est alors.',
250
+ references:
251
+ 'La régénération des références change une référence du projet que le plan ne nomme pas, un fichier qu’aucune référence n’écrit, ou sa commande échoue : le plan les nomme-t-il, ou qui corrige le lot ?',
252
+ 'hors-plan':
253
+ 'La séance écrit hors du plan du ticket : le plan s’étend-il à ce paquet (`--etendre`), ou l’écriture est-elle refusée (`--reprendre` avec son contexte) ?',
254
+ }).map(([arret, question]) => [arret, { question: `${question} ${REPONSES}`, qui: SUPERVISEUR }])
255
+ );
256
+
257
+ // The question of the stop <arret>, `{ question, qui }`, or null for a gesture.
258
+ export const monteeDe = arret => MONTEES[arret] ?? null;
@@ -9,14 +9,14 @@
9
9
  // - Each red guard is named with its code at the end; the exit code is 1 when one guard is red,
10
10
  // 0 when all are green.
11
11
  // - While the run names its results directory (`GRILLHOUSE_RESULTATS`, scripts/resultats.mjs),
12
- // each guard writes its findings there in SARIF; a red guard that wrote no finding has one
13
- // written for it, without location, its name (its script's, without extension) and its code:
14
- // a red no reader can place.
12
+ // each guard writes its findings there in SARIF; a red guard that wrote no finding (a note of
13
+ // the debt, `a-elaguer`, is none) has one written for it, without location, its name (its
14
+ // script's, without extension) and its code: a red no reader can place.
15
15
  // - The build is not among them: Turborepo builds before this task (turbo.json).
16
16
  import { spawnSync } from 'node:child_process';
17
17
  import path from 'node:path';
18
18
  import { fileURLToPath, pathToFileURL } from 'node:url';
19
- import { ecrireSarif, lireSarif, RESULTATS } from './resultats.mjs';
19
+ import { A_ELAGUER, ecrireSarif, lireSarif, RESULTATS } from './resultats.mjs';
20
20
 
21
21
  // A script of the package, beside this one, by its name.
22
22
  const ici = nom => fileURLToPath(new URL(nom, import.meta.url));
@@ -47,13 +47,15 @@ export function jouerLesGardes({
47
47
  dire = l => process.stdout.write(`${l}\n`),
48
48
  }) {
49
49
  const rouges = [];
50
+ // The findings under the results directory, the notes of the debt left out.
51
+ const nombre = () => lireSarif(env[RESULTATS]).constats.filter(k => k.genre !== A_ELAGUER).length;
50
52
  for (const garde of gardes) {
51
- const avant = env[RESULTATS] ? lireSarif(env[RESULTATS]).constats.length : 0;
53
+ const avant = env[RESULTATS] ? nombre() : 0;
52
54
  const r = spawnSync(process.execPath, garde, { cwd: racine, env, stdio: 'inherit' });
53
55
  if (r.status === 0) continue;
54
56
  const code = `code ${r.status ?? r.signal}`;
55
57
  rouges.push(`${libelle(garde)} (${code})`);
56
- if (env[RESULTATS] && lireSarif(env[RESULTATS]).constats.length === avant) {
58
+ if (env[RESULTATS] && nombre() === avant) {
57
59
  ecrireSarif({
58
60
  garde: nomDeGarde(garde),
59
61
  racine,
@@ -13,7 +13,14 @@
13
13
  // - The migrations first (scripts/migrations.mjs): each migration of the package newer than the
14
14
  // version the project recorded (`grillhouse.version` of its package.json; none recorded, every
15
15
  // migration), in the order of their versions; each is replayable without effect, and the report
16
- // names each one that changed something, with what it changed.
16
+ // names each one that changed something, with what it changed. What a migration leaves to the
17
+ // project (a copy of the frame the project modified, scripts/migrations/0.23.2-copies-du-cadre.mjs)
18
+ // is written on the error stream, with the command that does it, and the exit code is non-zero;
19
+ // the project keeps the version it recorded, so that the next update plays that migration again.
20
+ // Last, after the update and the regeneration, what stays to the project (`restes` of a
21
+ // migration played, scripts/migrations.mjs: in 0.24.0, a file that still calls a former copy of
22
+ // the frame) is reported on the standard output, « reste au projet », with no effect on the exit
23
+ // code nor on the version recorded.
17
24
  // - The frame copies no file into a project: its scripts are the package's, its skills, agents,
18
25
  // hooks of Claude Code, common charter and meta-frame the plugin's, which the package carries
19
26
  // (scripts/cadre.mjs): the frame arrives in one piece, at the version npm installs.
@@ -31,7 +38,8 @@
31
38
  // the frame's status line (`ligne-etat`, whatever its former form); a status line of the
32
39
  // project's own stays. The settings keep their other keys, in their order.
33
40
  // - `package.json`, when there is one: the version of the frame it is brought to
34
- // (`grillhouse.version`, the package's version), the frame's npm scripts (`tableau`,
41
+ // (`grillhouse.version`, the package's version once no migration leaves anything to do), the
42
+ // frame's npm scripts (`tableau`,
35
43
  // `grillhouse:maj`), its `prepare` beginning with `grillhouse hooks` (the frame's git hooks at
36
44
  // each `npm install`, as Husky's `prepare`; a `prepare` of the project's own follows it,
37
45
  // `grillhouse hooks && …`), and `grillhouse:gardes` (scripts/gardes-du-cadre.mjs, the frame's controls), `grillhouse:dette:inscrire` and
@@ -120,7 +128,7 @@ import semver from 'semver';
120
128
  import { CADRE, VERSION } from './cadre.mjs';
121
129
  import { poserCrochets } from './crochets.mjs';
122
130
  import { installerGreffons } from './greffons.mjs';
123
- import { migrer } from './migrations.mjs';
131
+ import { migrer, restesAuProjet } from './migrations.mjs';
124
132
  import { paquets } from './paquets.mjs';
125
133
 
126
134
  const SCRIPTS = {
@@ -285,9 +293,10 @@ export function versionDuProjet(projet) {
285
293
 
286
294
  /**
287
295
  * Brings the project at `projet` to the frame's files at `source` (CADRE, scripts/cadre.mjs; a
288
- * trial's own in the tests). Returns what changed.
296
+ * trial's own in the tests); <inscrite> is the version it records (the package's; null records
297
+ * none, the project keeping its own). Returns what changed.
289
298
  */
290
- export function mettreAJour(projet, source) {
299
+ export function mettreAJour(projet, source, { inscrite = VERSION } = {}) {
291
300
  const bilan = {
292
301
  dependances: [],
293
302
  test: null,
@@ -301,7 +310,7 @@ export function mettreAJour(projet, source) {
301
310
  const pkg = path.join(projet, 'package.json');
302
311
  if (existsSync(pkg)) {
303
312
  const p = lireJson(pkg, {});
304
- p.grillhouse = { ...(p.grillhouse ?? {}), version: VERSION };
313
+ if (inscrite !== null) p.grillhouse = { ...(p.grillhouse ?? {}), version: inscrite };
305
314
  const pkgSource = lireJson(path.join(source, GABARIT), {});
306
315
  const gardes = pkgSource.scripts?.['grillhouse:gardes'];
307
316
  p.scripts = { ...(p.scripts ?? {}), ...SCRIPTS };
@@ -497,18 +506,31 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
497
506
  process.exitCode = 2;
498
507
  } else {
499
508
  const projet = process.cwd();
500
- const migrations = migrer(projet, versionDuProjet(projet));
501
- const b = mettreAJour(projet, CADRE);
509
+ const depuis = versionDuProjet(projet);
510
+ const migrations = migrer(projet, depuis);
511
+ const resteAFaire = migrations.some(m => m.aFaire.length > 0);
512
+ const b = mettreAJour(projet, CADRE, resteAFaire ? { inscrite: null } : {});
502
513
  process.stdout.write(`Cadre ${VERSION}.\n`);
503
- for (const { version, nom, lignes } of migrations) {
514
+ for (const { version, nom, lignes, aFaire } of migrations) {
504
515
  process.stdout.write(
505
516
  `Migration ${version} (${nom}) :\n${lignes.map(l => `- ${l}\n`).join('')}`
506
517
  );
518
+ if (aFaire.length > 0) {
519
+ process.stderr.write(
520
+ `Migration ${version} (${nom}), à faire :\n${aFaire.map(l => `- ${l}\n`).join('')}`
521
+ );
522
+ process.exitCode = 1;
523
+ }
507
524
  }
508
525
  process.stdout.write(rapport(b));
509
526
  for (const l of [...regenerer(projet), ...installerGreffons(projet)]) {
510
527
  process.stderr.write(`${l}\n`);
511
528
  process.exitCode = 1;
512
529
  }
530
+ for (const { version, nom, lignes } of restesAuProjet(projet, depuis)) {
531
+ process.stdout.write(
532
+ `Migration ${version} (${nom}), reste au projet :\n${lignes.map(l => `- ${l}\n`).join('')}`
533
+ );
534
+ }
513
535
  }
514
536
  }
@@ -36,6 +36,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
36
36
  import path from 'node:path';
37
37
  import { pathToFileURL } from 'node:url';
38
38
  import { composants, declares } from './consommateurs.mjs';
39
+ import { lireLaListe } from './enveloppe/paquets-du-plan.mjs';
39
40
  import { DEBUT, FIN, marques } from './interfaces-contre-code.mjs';
40
41
  import { ecrireSarif } from './resultats.mjs';
41
42
 
@@ -179,7 +180,10 @@ function rend(racine, rel) {
179
180
  // What the components before <composant> publish, as the section the launcher writes after the
180
181
  // instruction of a role agent (scripts/enveloppe/lancer.sh). <composant> is the component's path
181
182
  // in the repository at <racine> (`packages/a`, `src/parser`), or `.` for a copy of the whole
182
- // repository, which the section points to the index of the interfaces. Its providers are the
183
+ // repository, which the section points to the index of the interfaces; or the plan of a session,
184
+ // several paths in the form of the envelope's list (`packages/a:packages/b`, `lireLaListe`), one
185
+ // part of the section per package of the plan, its providers under it, as one session works the
186
+ // whole plan of a ticket (grillhouse-3uv.95.6). Its providers are the
183
187
  // components whose `## Consommateurs` section names it; for each, its line in the index of the
184
188
  // interfaces (its heading, its first sentence, its interface), the names of the elements its
185
189
  // `## Consommateurs` line declares for the component, never their sentence, which the index of the
@@ -187,7 +191,23 @@ function rend(racine, rel) {
187
191
  // or one line saying the frame or the rubric is missing. A declaration of `*`, an import of the
188
192
  // whole component, names no element: its line says the elements are in the index. Each interface
189
193
  // and each frame is read once.
194
+ /** @param {string} racine @param {string} composant @returns {string} */
190
195
  export function publie(racine, composant) {
196
+ const plan = lireLaListe(composant);
197
+ if (plan.length > 1) {
198
+ const parties = plan.map(c =>
199
+ publie(racine, c)
200
+ .split('\n')
201
+ .slice(2)
202
+ .map(l => (l.startsWith('### ') ? `#${l}` : l))
203
+ );
204
+ return [
205
+ TITRE_PUBLIE,
206
+ '',
207
+ 'Ton plan porte plusieurs paquets : pour chacun, ce que publient les composants dont il dépend.',
208
+ ...plan.flatMap((c, i) => ['', `### Pour \`${path.basename(c)}\``, '', ...parties[i]]),
209
+ ].join('\n');
210
+ }
191
211
  if (composant === '.') {
192
212
  return [
193
213
  TITRE_PUBLIE,