@roomi-fields/grillhouse 0.23.1 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/METACADRE.md +1 -1
- package/agents/explorateur.md +4 -2
- package/agents/questionneur.md +1 -1
- package/bin/grillhouse.mjs +3 -58
- package/cadre/templates/projet/CLAUDE.md +5 -1
- package/cadre/templates/projet/docs/agents/issue-tracker.md +4 -4
- package/charte.md +23 -6
- package/docs/ARCHITECTURE.md +127 -1
- package/hooks/register.tsx +3 -13
- package/package.json +13 -1
- package/scripts/bilan.mjs +134 -88
- package/scripts/circuit.mjs +1547 -2248
- package/scripts/composants-affectes.mjs +27 -108
- package/scripts/consommateurs-arbitres.mjs +15 -21
- package/scripts/consommateurs.d.ts +8 -0
- package/scripts/consommateurs.mjs +5 -1
- package/scripts/dette.d.ts +23 -0
- package/scripts/dette.mjs +76 -13
- package/scripts/droits.mjs +38 -44
- package/scripts/elements-du-lot.mjs +246 -0
- package/scripts/enveloppe/enveloppe.mjs +105 -232
- package/scripts/enveloppe/lancer.sh +79 -49
- package/scripts/enveloppe/paquets-du-plan.mjs +49 -0
- package/scripts/etapes.mjs +148 -213
- package/scripts/gardes-du-cadre.mjs +8 -6
- package/scripts/grillhouse-maj.mjs +31 -9
- package/scripts/index-interfaces.mjs +21 -1
- package/scripts/integration/integrer.mjs +2 -2
- package/scripts/interfaces-contre-code.mjs +25 -17
- package/scripts/migrations/0.23.0-config-vitest.mjs +26 -23
- package/scripts/migrations/0.23.2-copies-du-cadre.mjs +201 -0
- package/scripts/migrations/0.24.0-circuit-simplifie.mjs +231 -0
- package/scripts/migrations.mjs +41 -6
- package/scripts/paquets.d.ts +5 -0
- package/scripts/paquets.mjs +3 -0
- package/scripts/references-par-les-types.mjs +55 -36
- package/scripts/registre.mjs +7 -15
- package/scripts/resultats.mjs +283 -142
- package/scripts/resumes.mjs +1 -1
- package/scripts/session-start.sh +1 -2
- package/scripts/sources.mjs +28 -0
- package/scripts/sous-commandes.mjs +62 -0
- package/scripts/tableau.mjs +85 -191
- package/scripts/tests.mjs +19 -17
- package/scripts/verifier.mjs +5 -4
- package/scripts/verrou-de-machine.mjs +131 -24
- package/scripts/verrous/verrou.mjs +66 -5
- package/skills/developpeur/SKILL.md +60 -50
- package/skills/explorateur/SKILL.md +90 -64
- package/skills/grill/SKILL.md +1 -1
- package/skills/mesure/SKILL.md +2 -1
- package/skills/pitmaster/SKILL.md +132 -118
- package/skills/pitmaster/references/consigne-agent.md +54 -148
- package/skills/questionneur/SKILL.md +18 -18
- package/skills/relecteur/SKILL.md +69 -23
- package/skills/testwriter/SKILL.md +46 -35
- package/types/index.d.ts +1 -3
- package/agents/referent-technique.md +0 -9
- package/agents/regulateur.md +0 -11
- package/scripts/enveloppe/composant.mjs +0 -11
- package/scripts/etat-du-circuit.mjs +0 -104
- package/skills/referent-technique/SKILL.md +0 -145
- package/skills/regulateur/SKILL.md +0 -220
package/scripts/resultats.mjs
CHANGED
|
@@ -5,49 +5,66 @@
|
|
|
5
5
|
//
|
|
6
6
|
// npx grillhouse resultats --schema <rôle> prints the JSON schema of a role's sheet
|
|
7
7
|
//
|
|
8
|
-
// - The sheet of the end of a session (« la fiche ») of the
|
|
9
|
-
//
|
|
8
|
+
// - The sheet of the end of a session (« la fiche ») of the explorateur, the questionneur, the
|
|
9
|
+
// testwriter, the developpeur and the reviewer: the schema of each role (FICHES), which the session receives
|
|
10
10
|
// (`claude -p --output-format json --json-schema <schéma>`) and fills as its last act; claude
|
|
11
11
|
// writes it in the field `structured_output` of its result (Claude Code, « headless »). A field
|
|
12
12
|
// exists only when a reader reads it:
|
|
13
|
-
// - `statut` (FAIT, BLOQUÉ, BESOIN), with `raison` for BLOQUÉ and `question` for BESOIN
|
|
14
|
-
// circuit goes on, or stops at « hors-portee » or « besoin
|
|
13
|
+
// - `statut` (FAIT, BLOQUÉ, BESOIN), with `raison` for BLOQUÉ and `question` for BESOIN, each
|
|
14
|
+
// with its `piece`: the circuit goes on, or stops at « hors-portee » or « besoin », the piece
|
|
15
|
+
// with the stop (testwriter, developpeur);
|
|
15
16
|
// - `verts_attendus` (`fichier` from the root, `test` its full name, or none for every green of a
|
|
16
17
|
// file HEAD holds, `raison`): the proof of the red (testwriter);
|
|
17
|
-
// - `renvoi` (`role` testwriter
|
|
18
|
-
// (developpeur);
|
|
18
|
+
// - `renvoi` (`role` testwriter): the circuit relaunches the testwriter, then the proof and the
|
|
19
|
+
// developpeur (developpeur);
|
|
19
20
|
// - `ecarts` (`sujet`, `texte`): the body of the commit message, written by the circuit, each
|
|
20
21
|
// subject once, its value from the last sheet that carries it (developpeur, reviewer);
|
|
21
22
|
// - `decouvertes` (`sujet`, `corps`): each one a ticket filed closed under the ticket, labelled
|
|
22
23
|
// `decouverte`, created by the circuit (every role);
|
|
23
|
-
// - `verdict` (ACCEPTÉ, RENDU), `constats` (`titre`, `corps`, `genre` bloquant or
|
|
24
|
+
// - `verdict` (ACCEPTÉ, RENDU), `constats` (`titre`, `corps`, `piece`, `genre` bloquant or
|
|
25
|
+
// remarque; a
|
|
24
26
|
// blocking one with `regle`, the criterion of the plan or the rule of the frame it holds, and
|
|
25
|
-
// `role`
|
|
26
|
-
// a comment blocking or not, the circuit plays the blocking findings alone
|
|
27
|
-
// verdict at the ticket for the human, the remarks with
|
|
28
|
-
// an ACCEPTÉ none (reviewer);
|
|
27
|
+
// `role` its recipient, or `critere_en_cause`): as Conventional Comments mark
|
|
28
|
+
// a comment blocking or not, the circuit plays the blocking findings alone, each open until a
|
|
29
|
+
// review marks it lifted, and writes the verdict at the ticket for the human, the remarks with
|
|
30
|
+
// it; a RENDU carries a blocking finding, an ACCEPTÉ none (reviewer);
|
|
31
|
+
// - `conformite` (`element`, `conforme` oui or non, `document` CADRE, INTERFACE or
|
|
32
|
+
// ARCHITECTURE, `reference`, `piece`; a « non » with `role` its recipient):
|
|
33
|
+
// one line per element of the lot (scripts/elements-du-lot.mjs), its rule cited from a
|
|
34
|
+
// document of its package, a new export's the entry of the INTERFACE that names it; a line
|
|
35
|
+
// without its reference is refused; a « non » is a blocking finding (`bloquantsDeLaFiche`),
|
|
36
|
+
// and the circuit refuses a sheet without a line for each element (`conformiteDuLot`)
|
|
37
|
+
// (reviewer);
|
|
29
38
|
// - `suivi` (`titre`, `etat` levé or non levé, `preuve`): after a review sent back, each finding
|
|
30
39
|
// before it judged on the difference of the lot, which the circuit writes at the ticket with
|
|
31
40
|
// the verdict, for the human (reviewer);
|
|
32
|
-
// - `question` (reviewer): what goes up to the owner, a real word of the
|
|
33
|
-
// code; the circuit stops at « besoin » before it commits or
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// - `
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// - `
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// ticket (`bd update`) (technical referent).
|
|
41
|
+
// - `question`, with its `piece` (reviewer): what goes up to the owner, a real word of the
|
|
42
|
+
// language written in the code; the circuit stops at « besoin » before it commits or
|
|
43
|
+
// relaunches anyone;
|
|
44
|
+
// - `criteres`, `paquets`, `questions` (each with its `piece`): the plan, which the circuit
|
|
45
|
+
// writes at the ticket: the criteria it adds after those the ticket holds, which the circuit
|
|
46
|
+
// keeps as they are; the packages in the title as it names its components (a package name or a
|
|
47
|
+
// path), the list the envelope of every later session writes; the open questions, a change or
|
|
48
|
+
// a removal of a criterion written among them, go up to the supervisor (explorateur);
|
|
49
|
+
// - `reponses` (`numero` of the question, `reponse` CHANGE or GARDE, `texte`): the explorateur's
|
|
50
|
+
// one answer to the questionneur, which the circuit writes at the ticket; a question without
|
|
51
|
+
// an answer stays open (explorateur);
|
|
52
|
+
// - `issue` (QUESTIONS with its `questions`, each with what it aims at, `vise`, and its
|
|
53
|
+
// `piece`; AUCUNE; DÉSACCORD
|
|
54
|
+
// with its `desaccord`): the explorateur answers the questions, the disagreement as one, and
|
|
55
|
+
// the circuit writes them at the ticket (questionneur).
|
|
48
56
|
// `lireFiche` reads a session's output: the sheet and the session's id, or the error that
|
|
49
57
|
// names what lacks or does not conform (no `structured_output`, a field out of the schema, a
|
|
50
|
-
// status without its reason, a blocking finding without its criterion or rule
|
|
58
|
+
// status without its reason, a blocking finding without its criterion or rule, a line of
|
|
59
|
+
// conformity without its rule or a « non » without its recipient, a plan without packages, a
|
|
60
|
+
// fact without its piece, a piece in neither form, and, given the copy, a file piece not in it).
|
|
61
|
+
// - The piece of a fact (« la pièce »), one format, as a code review cites the line it judges: a
|
|
62
|
+
// finding, a question or a stop carries it, so the agent who takes the fact up re-reads it
|
|
63
|
+
// before he builds on it (BP 320.9: a line read wrong, copied by five agents up to the owner).
|
|
64
|
+
// Either `<fichier>:<ligne>` or `<fichier>:<ligne>-<fin>`, the file from the root of the copy,
|
|
65
|
+
// the line from 1, then, after a space, what it shows (« packages/a/src/x.ts:3 divise par sa valeur. »); or `$ <commande>` on
|
|
66
|
+
// its first line and its output on the lines after. `lirePiece` reads it; the circuit gives
|
|
67
|
+
// `lireFiche` the copy, where each file piece's file stands and holds its line.
|
|
51
68
|
// - The findings of a guard, in SARIF 2.1.0 (OASIS), beside its output for the human: one run per
|
|
52
69
|
// guard, `tool.driver.name` the guard; each finding a result, `ruleId` the guard, `level`
|
|
53
70
|
// (error by default), `message.text`, its file and line as its location when it has them, and its
|
|
@@ -75,6 +92,7 @@ import {
|
|
|
75
92
|
readFileSync,
|
|
76
93
|
rmdirSync,
|
|
77
94
|
rmSync,
|
|
95
|
+
statSync,
|
|
78
96
|
writeFileSync,
|
|
79
97
|
} from 'node:fs';
|
|
80
98
|
import path from 'node:path';
|
|
@@ -85,18 +103,12 @@ import { RESULTATS, VITEST } from './vitest-config/resultats.mjs';
|
|
|
85
103
|
export { RESULTATS };
|
|
86
104
|
const SARIF = 'sarif';
|
|
87
105
|
const TURBO = 'turbo';
|
|
88
|
-
// The
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
'hors-titre',
|
|
95
|
-
'rouge-de',
|
|
96
|
-
'sans-changement',
|
|
97
|
-
'sous-charge',
|
|
98
|
-
];
|
|
99
|
-
|
|
106
|
+
// The kind of a note of a guard, no red: a key of the known debt inscribed above what the guard
|
|
107
|
+
// finds, which the circuit lowers (scripts/dette.mjs, scripts/circuit.mjs).
|
|
108
|
+
export const A_ELAGUER = 'a-elaguer';
|
|
109
|
+
// The kinds of a guard's finding: to fix (a developpeur's work), to settle (a person's decision),
|
|
110
|
+
// to lower (a note).
|
|
111
|
+
const GENRES = ['a-corriger', 'a-arbitrer', A_ELAGUER];
|
|
100
112
|
const texte = description => ({ type: 'string', description });
|
|
101
113
|
const liste = (items, description) => ({ type: 'array', description, items });
|
|
102
114
|
const objet = (properties, required = [], description) => ({
|
|
@@ -106,10 +118,15 @@ const objet = (properties, required = [], description) => ({
|
|
|
106
118
|
required,
|
|
107
119
|
properties,
|
|
108
120
|
});
|
|
121
|
+
// The description of a piece in a sheet's schema, the format `lirePiece` reads.
|
|
122
|
+
const PIECE =
|
|
123
|
+
'fichier:ligne depuis la racine de la copie, puis ce qu’elle montre ; ou « $ <commande> » et sa sortie aux lignes suivantes';
|
|
124
|
+
const piece = quoi => texte(`${quoi} : ${PIECE}.`);
|
|
109
125
|
const STATUT = {
|
|
110
126
|
statut: { type: 'string', enum: ['FAIT', 'BLOQUÉ', 'BESOIN'] },
|
|
111
127
|
raison: texte('BLOQUÉ : ce qui reste hors de ta portée, et où.'),
|
|
112
128
|
question: texte('BESOIN : la question qui t’arrête, ses options et ta recommandation.'),
|
|
129
|
+
piece: piece('BLOQUÉ ou BESOIN : la pièce de ta raison ou de ta question'),
|
|
113
130
|
};
|
|
114
131
|
const DECOUVERTES = {
|
|
115
132
|
decouvertes: liste(
|
|
@@ -135,23 +152,46 @@ const ECARTS = {
|
|
|
135
152
|
),
|
|
136
153
|
};
|
|
137
154
|
|
|
138
|
-
// The
|
|
139
|
-
const
|
|
140
|
-
reponses: liste(
|
|
141
|
-
objet(
|
|
142
|
-
{
|
|
143
|
-
question: texte('La question, telle que le questionneur l’a posée.'),
|
|
144
|
-
choix: { type: 'string', enum: ['CHANGE', 'GARDE'] },
|
|
145
|
-
texte: texte('CHANGE : ce que ta proposition devient ; GARDE : pourquoi elle reste.'),
|
|
146
|
-
},
|
|
147
|
-
['question', 'choix', 'texte']
|
|
148
|
-
),
|
|
149
|
-
'Ta réponse à chaque question du questionneur.'
|
|
150
|
-
),
|
|
151
|
-
};
|
|
155
|
+
// The documents a line of a reviewer's conformity cites its rule from.
|
|
156
|
+
const DOCUMENTS = ['CADRE', 'INTERFACE', 'ARCHITECTURE'];
|
|
152
157
|
|
|
153
158
|
// The schema of the sheet of each role the circuit leads.
|
|
154
159
|
const FICHES = {
|
|
160
|
+
explorateur: objet(
|
|
161
|
+
{
|
|
162
|
+
criteres: liste(
|
|
163
|
+
texte('Un critère, sans son numéro : un comportement que ses tests mesurent.'),
|
|
164
|
+
'Les critères que le plan ajoute, dans leur ordre ; le circuit garde tels quels ceux que le ticket porte et écrit les tiens après eux. Changer ou retirer un critère écrit est une question ouverte.'
|
|
165
|
+
),
|
|
166
|
+
paquets: liste(
|
|
167
|
+
texte('Un paquet, comme le titre le nomme : le nom de son paquet, ou son chemin.'),
|
|
168
|
+
'Les paquets à toucher, le fournisseur d’abord ; le circuit les écrit au titre, et chaque séance n’écrit qu’eux.'
|
|
169
|
+
),
|
|
170
|
+
questions: liste(
|
|
171
|
+
objet(
|
|
172
|
+
{
|
|
173
|
+
question: texte('La question qui reste ouverte, ses options et ta recommandation.'),
|
|
174
|
+
piece: piece('Sa pièce'),
|
|
175
|
+
},
|
|
176
|
+
['question', 'piece']
|
|
177
|
+
),
|
|
178
|
+
'Les questions ouvertes du plan, que tu ne tranches pas ; elles montent au superviseur.'
|
|
179
|
+
),
|
|
180
|
+
reponses: liste(
|
|
181
|
+
objet(
|
|
182
|
+
{
|
|
183
|
+
numero: texte('Le numéro de la question, « 1 ».'),
|
|
184
|
+
reponse: { type: 'string', enum: ['CHANGE', 'GARDE'] },
|
|
185
|
+
texte: texte('CHANGE : ce que le plan devient ; GARDE : pourquoi il reste tel quel.'),
|
|
186
|
+
},
|
|
187
|
+
['numero', 'reponse', 'texte']
|
|
188
|
+
),
|
|
189
|
+
'Ta réponse, une fois, à chaque question du questionneur.'
|
|
190
|
+
),
|
|
191
|
+
...DECOUVERTES,
|
|
192
|
+
},
|
|
193
|
+
['criteres', 'paquets', 'questions']
|
|
194
|
+
),
|
|
155
195
|
testwriter: objet(
|
|
156
196
|
{
|
|
157
197
|
...STATUT,
|
|
@@ -176,12 +216,9 @@ const FICHES = {
|
|
|
176
216
|
{
|
|
177
217
|
...STATUT,
|
|
178
218
|
renvoi: objet(
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
},
|
|
183
|
-
['role', 'composant'],
|
|
184
|
-
'Le rouge qui reste est dans les tests de ce composant, le tien ou un d’avant.'
|
|
219
|
+
{ role: { type: 'string', enum: ['testwriter'] } },
|
|
220
|
+
['role'],
|
|
221
|
+
'Le rouge qui reste est dans les tests du lot : le circuit relance le testwriter.'
|
|
185
222
|
),
|
|
186
223
|
...ECARTS,
|
|
187
224
|
...DECOUVERTES,
|
|
@@ -191,10 +228,35 @@ const FICHES = {
|
|
|
191
228
|
relecteur: objet(
|
|
192
229
|
{
|
|
193
230
|
verdict: { type: 'string', enum: ['ACCEPTÉ', 'RENDU'] },
|
|
231
|
+
conformite: liste(
|
|
232
|
+
objet(
|
|
233
|
+
{
|
|
234
|
+
element: texte('L’élément, tel que la liste des éléments du lot l’écrit.'),
|
|
235
|
+
conforme: {
|
|
236
|
+
type: 'string',
|
|
237
|
+
enum: ['oui', 'non'],
|
|
238
|
+
description: 'non : il rend le lot, à son destinataire.',
|
|
239
|
+
},
|
|
240
|
+
document: {
|
|
241
|
+
type: 'string',
|
|
242
|
+
enum: DOCUMENTS,
|
|
243
|
+
description:
|
|
244
|
+
'Le document de son paquet dont la règle est citée ; un export neuf, l’INTERFACE.',
|
|
245
|
+
},
|
|
246
|
+
reference: texte(
|
|
247
|
+
'La règle dans ce document (« 000-forms, R2 », « § Les frontières ») ; un export neuf, le nom de son entrée.'
|
|
248
|
+
),
|
|
249
|
+
piece: piece('Sa pièce'),
|
|
250
|
+
role: { type: 'string', enum: ['testwriter', 'developpeur'] },
|
|
251
|
+
},
|
|
252
|
+
['element', 'conforme', 'document', 'reference', 'piece']
|
|
253
|
+
),
|
|
254
|
+
'Une ligne par élément du lot, chacun une fois : sa conformité à l’architecture.'
|
|
255
|
+
),
|
|
194
256
|
constats: liste(
|
|
195
257
|
objet(
|
|
196
258
|
{
|
|
197
|
-
titre: texte('Son
|
|
259
|
+
titre: texte('Son sujet, une ligne.'),
|
|
198
260
|
genre: {
|
|
199
261
|
type: 'string',
|
|
200
262
|
enum: ['bloquant', 'remarque'],
|
|
@@ -204,12 +266,16 @@ const FICHES = {
|
|
|
204
266
|
regle: texte(
|
|
205
267
|
'Bloquant : le critère du plan (« critère 2 ») ou la règle du cadre (son document et sa règle) qu’il tient.'
|
|
206
268
|
),
|
|
207
|
-
role: {
|
|
208
|
-
|
|
269
|
+
role: {
|
|
270
|
+
type: 'string',
|
|
271
|
+
enum: ['testwriter', 'developpeur'],
|
|
272
|
+
description: 'Bloquant : l’agent qui écrit la correction, à qui le circuit le rend.',
|
|
273
|
+
},
|
|
209
274
|
corps: texte('Sa preuve : son exemple et ce qu’il montre.'),
|
|
275
|
+
piece: piece('Sa pièce'),
|
|
210
276
|
critere_en_cause: texte('Le critère lui-même en cause, au lieu d’un destinataire.'),
|
|
211
277
|
},
|
|
212
|
-
['titre', 'corps', 'genre']
|
|
278
|
+
['titre', 'corps', 'genre', 'piece']
|
|
213
279
|
),
|
|
214
280
|
'Chaque constat, bloquant ou remarque ; un verdict RENDU en porte au moins un bloquant, un verdict ACCEPTÉ aucun.'
|
|
215
281
|
),
|
|
@@ -225,50 +291,14 @@ const FICHES = {
|
|
|
225
291
|
'Relecture bornée : chaque constat de la relecture d’avant, levé ou non.'
|
|
226
292
|
),
|
|
227
293
|
question: texte(
|
|
228
|
-
'Ce qui revient au responsable (par exemple un vrai mot du langage écrit en dur) : la question
|
|
294
|
+
'Ce qui revient au responsable (par exemple un vrai mot du langage écrit en dur) : la question et ta recommandation ; le circuit s’arrête avant de commiter ou de relancer.'
|
|
229
295
|
),
|
|
296
|
+
piece: piece('question : sa pièce'),
|
|
230
297
|
...ECARTS,
|
|
231
298
|
...DECOUVERTES,
|
|
232
299
|
},
|
|
233
300
|
['verdict']
|
|
234
301
|
),
|
|
235
|
-
regulateur: objet(
|
|
236
|
-
{
|
|
237
|
-
decision: { type: 'string', enum: ['RELANCE --reprendre', 'RELANCE --depuis', 'MONTE'] },
|
|
238
|
-
depuis: liste(
|
|
239
|
-
objet(
|
|
240
|
-
{
|
|
241
|
-
role: { type: 'string', enum: ['testwriter', 'developpeur'] },
|
|
242
|
-
composant: texte('Un composant du titre.'),
|
|
243
|
-
contexte: texte('Ce que l’agent relancé fait dans ce composant.'),
|
|
244
|
-
},
|
|
245
|
-
['role', 'composant', 'contexte']
|
|
246
|
-
),
|
|
247
|
-
'RELANCE --depuis : chaque composant que le lot reprend, avec l’agent qui le reprend et son contexte ; le circuit les rejoue dans l’ordre du titre, puis une seule passe finale.'
|
|
248
|
-
),
|
|
249
|
-
contexte: texte('RELANCE --reprendre : ce que l’agent repris fait dans son composant.'),
|
|
250
|
-
question: texte('MONTE : la question, ses pièces et ta recommandation.'),
|
|
251
|
-
arbitrage: texte(
|
|
252
|
-
'RELANCE : ce que tu tranches seul et sa source (la lettre d’un critère, un précédent écrit, une règle du cadre), en nommant chaque consommateur qu’il fixe ; le circuit l’écrit au ticket, tranché.'
|
|
253
|
-
),
|
|
254
|
-
critere: texte(
|
|
255
|
-
'RELANCE --depuis, son premier composant repris par son testwriter : le critère que tu ajoutes, sans son numéro ; le circuit l’écrit, numéroté, à la fin de la description.'
|
|
256
|
-
),
|
|
257
|
-
faits: liste(
|
|
258
|
-
objet(
|
|
259
|
-
{
|
|
260
|
-
sujet: { type: 'string', enum: SUJETS_DES_FAITS },
|
|
261
|
-
composant: texte('Le composant ou le paquet du fait ; sans objet pour sous-charge.'),
|
|
262
|
-
},
|
|
263
|
-
['sujet']
|
|
264
|
-
),
|
|
265
|
-
'Chaque fait de l’état sur lequel ta décision s’appuie : au-titre et hors-titre (un composant au titre ou non), rouge-de (un rouge de l’arrêt dans ce composant ou ce paquet), sans-changement (la copie ne change rien de ce composant), sous-charge (une commande de l’arrêt à repasser, sous charge). Le circuit vérifie chacun sur l’état avant d’appliquer ta décision, et refuse une décision qui s’appuie sur un fait faux.'
|
|
266
|
-
),
|
|
267
|
-
...REPONSES,
|
|
268
|
-
...DECOUVERTES,
|
|
269
|
-
},
|
|
270
|
-
['decision']
|
|
271
|
-
),
|
|
272
302
|
questionneur: objet(
|
|
273
303
|
{
|
|
274
304
|
issue: { type: 'string', enum: ['QUESTIONS', 'AUCUNE', 'DÉSACCORD'] },
|
|
@@ -277,26 +307,16 @@ const FICHES = {
|
|
|
277
307
|
{
|
|
278
308
|
question: texte('Ta question, dans les mots de la proposition.'),
|
|
279
309
|
vise: { type: 'string', enum: ['flou', 'intention', 'référence', 'règle', 'critère'] },
|
|
310
|
+
piece: piece('Sa pièce, ce qui dans la proposition ou le dépôt la fait naître'),
|
|
280
311
|
},
|
|
281
|
-
['question', 'vise']
|
|
312
|
+
['question', 'vise', 'piece']
|
|
282
313
|
),
|
|
283
|
-
'QUESTIONS : tes questions
|
|
284
|
-
),
|
|
285
|
-
desaccord: texte(
|
|
286
|
-
'DÉSACCORD : ce qui reste, que la réponse de l’auteur ne lève pas, et pourquoi.'
|
|
314
|
+
'QUESTIONS : tes questions.'
|
|
287
315
|
),
|
|
316
|
+
desaccord: texte('DÉSACCORD : l’écart que la proposition laisse, et pourquoi.'),
|
|
288
317
|
},
|
|
289
318
|
['issue']
|
|
290
319
|
),
|
|
291
|
-
'referent-technique': objet(
|
|
292
|
-
{
|
|
293
|
-
...REPONSES,
|
|
294
|
-
titre: texte('Le titre du ticket que ta réponse change, entier.'),
|
|
295
|
-
description: texte('La description du ticket que ta réponse change, entière.'),
|
|
296
|
-
...DECOUVERTES,
|
|
297
|
-
},
|
|
298
|
-
['reponses']
|
|
299
|
-
),
|
|
300
320
|
};
|
|
301
321
|
|
|
302
322
|
// The schema of the sheet of <role>, or null for a role the circuit does not lead.
|
|
@@ -331,24 +351,134 @@ function erreurs(schema, valeur, ou) {
|
|
|
331
351
|
return [];
|
|
332
352
|
}
|
|
333
353
|
|
|
354
|
+
// The rule a line of conformity <l> cites: its document and its reference.
|
|
355
|
+
export const regleDeLaLigne = l => `${l.document} ${l.reference}`;
|
|
356
|
+
|
|
357
|
+
// The blocking findings of a reviewer's sheet <fiche>, the ones that send the lot back: its
|
|
358
|
+
// findings of `genre` bloquant, then each line of its conformity that says « non », as a finding
|
|
359
|
+
// on its element, its rule, its recipient and its piece.
|
|
360
|
+
export const bloquantsDeLaFiche = fiche => [
|
|
361
|
+
...(fiche.constats ?? []).filter(c => c.genre === 'bloquant'),
|
|
362
|
+
...(fiche.conformite ?? [])
|
|
363
|
+
.filter(l => l.conforme === 'non')
|
|
364
|
+
.map(l => ({
|
|
365
|
+
titre: l.element,
|
|
366
|
+
genre: 'bloquant',
|
|
367
|
+
regle: regleDeLaLigne(l),
|
|
368
|
+
role: l.role,
|
|
369
|
+
corps: `Non conforme : ${l.piece}`,
|
|
370
|
+
})),
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
// What makes the reviewer's sheet <fiche> out of conformity with the elements of its lot
|
|
374
|
+
// <elements> (scripts/elements-du-lot.mjs: `{ id, nom, statut, exporte }`), as a review asks a
|
|
375
|
+
// word on every changed file: an element without its line, a line on an element out of the list,
|
|
376
|
+
// a new export whose rule does not name its entry in the INTERFACE of its package. The fault, or
|
|
377
|
+
// null.
|
|
378
|
+
export function conformiteDuLot(fiche, elements) {
|
|
379
|
+
const lignes = fiche.conformite ?? [];
|
|
380
|
+
const ids = new Set(elements.map(e => e.id));
|
|
381
|
+
const fautes = [];
|
|
382
|
+
const sans = elements.filter(e => !lignes.some(l => l.element === e.id)).map(e => e.id);
|
|
383
|
+
if (sans.length) fautes.push(`conformite : sans ligne pour ${sans.join(', ')}`);
|
|
384
|
+
for (const [i, l] of lignes.entries()) {
|
|
385
|
+
if (!ids.has(l.element)) {
|
|
386
|
+
fautes.push(`conformite[${i}].element : ${l.element} n'est pas un élément du lot`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (const e of elements.filter(x => x.statut === 'neuf' && x.exporte)) {
|
|
390
|
+
for (const l of lignes.filter(x => x.element === e.id)) {
|
|
391
|
+
if (!(l.document === 'INTERFACE' && l.reference.includes(e.nom))) {
|
|
392
|
+
fautes.push(
|
|
393
|
+
`conformite : ${e.id} est un export neuf ; sa règle nomme son entrée « ${e.nom} » dans l'INTERFACE de son paquet`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return fautes.length ? fautes.join(' ; ') : null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// The piece of a fact <texte>: `{ fichier, ligne }` for « <fichier>:<ligne> », `{ fichier, ligne,
|
|
402
|
+
// fin }` for « <fichier>:<ligne>-<fin> » (as SARIF's region has a start line and an optional end
|
|
403
|
+
// line), then what it shows after a space, the place in backquotes or followed by punctuation
|
|
404
|
+
// read the same; `{ commande, sortie }` for « $ <commande> » and its output on the lines after;
|
|
405
|
+
// null for a text in neither form.
|
|
406
|
+
export function lirePiece(texte) {
|
|
407
|
+
const t = String(texte ?? '').trim();
|
|
408
|
+
if (t.startsWith('$ ')) {
|
|
409
|
+
const saut = t.indexOf('\n');
|
|
410
|
+
const commande = saut < 0 ? '' : t.slice(2, saut).trim();
|
|
411
|
+
const sortie = saut < 0 ? '' : t.slice(saut + 1).trim();
|
|
412
|
+
return commande && sortie ? { commande, sortie } : null;
|
|
413
|
+
}
|
|
414
|
+
let lieu = t.split(/\s/)[0];
|
|
415
|
+
while (lieu && '`.,;:)'.includes(lieu.at(-1))) lieu = lieu.slice(0, -1);
|
|
416
|
+
while (lieu.startsWith('`')) lieu = lieu.slice(1);
|
|
417
|
+
const deux = lieu.lastIndexOf(':');
|
|
418
|
+
const [debut, fin, ...reste] = lieu.slice(deux + 1).split('-');
|
|
419
|
+
const nombre = n => (/^\d+$/.test(n ?? '') && Number(n) >= 1 ? Number(n) : null);
|
|
420
|
+
const ligne = nombre(debut);
|
|
421
|
+
if (deux < 1 || !ligne || reste.length) return null;
|
|
422
|
+
if (fin === undefined) return { fichier: lieu.slice(0, deux), ligne };
|
|
423
|
+
if (!nombre(fin) || nombre(fin) < ligne) return null;
|
|
424
|
+
return { fichier: lieu.slice(0, deux), ligne, fin: nombre(fin) };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// The pieces of the sheet <f>, each `[son chemin, son texte]`: of the stop it carries (a status,
|
|
428
|
+
// a reviewer's question), of its findings, of its lines of conformity, of its questions.
|
|
429
|
+
const piecesDe = f => [
|
|
430
|
+
...('piece' in f ? [['piece', f.piece]] : []),
|
|
431
|
+
...['constats', 'conformite', 'questions'].flatMap(k =>
|
|
432
|
+
(f[k] ?? []).map((x, i) => [`${k}[${i}].piece`, x.piece]).filter(([, p]) => p !== undefined)
|
|
433
|
+
),
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
// What makes a file piece of the sheet <f> not found in the copy <copie>: a file absent from it,
|
|
437
|
+
// or out of it, or a line (the end of a range) past its end; each fault, naming its piece.
|
|
438
|
+
function introuvables(f, copie) {
|
|
439
|
+
const out = [];
|
|
440
|
+
const racine = path.resolve(copie);
|
|
441
|
+
for (const [ou, p] of piecesDe(f)) {
|
|
442
|
+
const lu = lirePiece(p);
|
|
443
|
+
if (!lu?.fichier) continue;
|
|
444
|
+
const abs = path.resolve(racine, lu.fichier);
|
|
445
|
+
const lieu = `${lu.fichier}:${lu.ligne}${lu.fin ? `-${lu.fin}` : ''}`;
|
|
446
|
+
if (!abs.startsWith(`${racine}${path.sep}`) || !existsSync(abs) || !statSync(abs).isFile()) {
|
|
447
|
+
out.push(`${ou} : ${lieu} introuvable dans la copie : pas de tel fichier`);
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const contenu = readFileSync(abs, 'utf8');
|
|
451
|
+
const lignes = contenu.split('\n').length - (contenu.endsWith('\n') ? 1 : 0);
|
|
452
|
+
if ((lu.fin ?? lu.ligne) > lignes) {
|
|
453
|
+
out.push(`${ou} : ${lieu} introuvable dans la copie : le fichier a ${lignes} lignes`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
}
|
|
458
|
+
|
|
334
459
|
// What a sheet of <role> needs beyond its schema: the reason of a BLOQUÉ, the question of a
|
|
335
|
-
// BESOIN
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
//
|
|
460
|
+
// BESOIN, the packages of a plan and the number of each answer, the questions of a QUESTIONS, the text of a
|
|
461
|
+
// DÉSACCORD, the criterion or rule of a blocking finding, the reference of the rule each line of
|
|
462
|
+
// a reviewer's conformity cites in its document and the recipient of a « non », and a reviewer's verdict that follows its blocking findings, its « non » among them
|
|
463
|
+
// (RENDU with one, ACCEPTÉ without), the piece of a BLOQUÉ, a BESOIN and a reviewer's question,
|
|
464
|
+
// and each piece in one of its two forms (`lirePiece`).
|
|
339
465
|
function manques(role, f) {
|
|
340
466
|
const out = [];
|
|
341
467
|
const plein = k => typeof f[k] === 'string' && f[k].trim() !== '';
|
|
342
468
|
if (f.statut === 'BLOQUÉ' && !plein('raison')) out.push('raison : manque, pour BLOQUÉ');
|
|
343
469
|
if (f.statut === 'BESOIN' && !plein('question')) out.push('question : manque, pour BESOIN');
|
|
344
|
-
if (
|
|
345
|
-
out.push(
|
|
470
|
+
if (['BLOQUÉ', 'BESOIN'].includes(f.statut) && !('piece' in f)) {
|
|
471
|
+
out.push(`piece : manque, pour ${f.statut}`);
|
|
346
472
|
}
|
|
347
|
-
if (role === '
|
|
348
|
-
out.push('
|
|
473
|
+
if (role === 'relecteur' && 'question' in f && !('piece' in f)) {
|
|
474
|
+
out.push('piece : manque, pour la question');
|
|
349
475
|
}
|
|
350
|
-
|
|
351
|
-
|
|
476
|
+
for (const [ou, p] of piecesDe(f)) {
|
|
477
|
+
if (!lirePiece(p)) {
|
|
478
|
+
out.push(
|
|
479
|
+
`${ou} : « ${p} » n'est pas une pièce (fichier:ligne, ou « $ <commande> » et sa sortie)`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
352
482
|
}
|
|
353
483
|
if (role === 'relecteur') {
|
|
354
484
|
const constats = f.constats ?? [];
|
|
@@ -357,7 +487,17 @@ function manques(role, f) {
|
|
|
357
487
|
out.push(`constats[${i}].regle : manque, pour un constat bloquant`);
|
|
358
488
|
}
|
|
359
489
|
}
|
|
360
|
-
const
|
|
490
|
+
for (const [i, l] of (f.conformite ?? []).entries()) {
|
|
491
|
+
if (!l.reference.trim()) {
|
|
492
|
+
out.push(`conformite[${i}].reference : manque, la règle citée dans le ${l.document}`);
|
|
493
|
+
}
|
|
494
|
+
if (l.conforme === 'non') {
|
|
495
|
+
if (!(typeof l.role === 'string' && l.role.trim())) {
|
|
496
|
+
out.push(`conformite[${i}].role : manque, pour un élément non conforme`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
const bloque = bloquantsDeLaFiche(f).length > 0;
|
|
361
501
|
if (f.verdict === 'RENDU' && !bloque) {
|
|
362
502
|
out.push('verdict : RENDU sans constat bloquant ; un lot sans constat bloquant est ACCEPTÉ');
|
|
363
503
|
}
|
|
@@ -365,9 +505,12 @@ function manques(role, f) {
|
|
|
365
505
|
out.push('verdict : ACCEPTÉ avec un constat bloquant, qui rend le lot');
|
|
366
506
|
}
|
|
367
507
|
}
|
|
368
|
-
|
|
369
|
-
if (
|
|
370
|
-
|
|
508
|
+
if (role === 'explorateur') {
|
|
509
|
+
if (!(f.paquets ?? []).length) out.push('paquets : manque, pour un plan');
|
|
510
|
+
for (const [i, r] of (f.reponses ?? []).entries()) {
|
|
511
|
+
if (!/^[1-9]\d*$/.test(r.numero)) {
|
|
512
|
+
out.push(`reponses[${i}].numero : « ${r.numero} » n'est pas le numéro d'une question`);
|
|
513
|
+
}
|
|
371
514
|
}
|
|
372
515
|
}
|
|
373
516
|
if (role === 'questionneur' && f.issue === 'QUESTIONS' && !(f.questions ?? []).length) {
|
|
@@ -376,20 +519,13 @@ function manques(role, f) {
|
|
|
376
519
|
if (role === 'questionneur' && f.issue === 'DÉSACCORD' && !plein('desaccord')) {
|
|
377
520
|
out.push('desaccord : manque, pour DÉSACCORD');
|
|
378
521
|
}
|
|
379
|
-
if (
|
|
380
|
-
role === 'referent-technique' &&
|
|
381
|
-
(f.reponses ?? []).some(r => r.choix === 'CHANGE') &&
|
|
382
|
-
!plein('titre') &&
|
|
383
|
-
!plein('description')
|
|
384
|
-
) {
|
|
385
|
-
out.push('titre ou description : manque, pour une réponse CHANGE');
|
|
386
|
-
}
|
|
387
522
|
return out;
|
|
388
523
|
}
|
|
389
524
|
|
|
390
525
|
// The sheet of <role> in <sortie>, what claude prints with `--output-format json`: `{ fiche,
|
|
391
|
-
// seance }`, or `{ erreur }` naming what lacks or does not conform
|
|
392
|
-
|
|
526
|
+
// seance }`, or `{ erreur }` naming what lacks or does not conform; with <copie>, the directory
|
|
527
|
+
// of the copy, each file piece of the sheet stands in it, its file and its line.
|
|
528
|
+
export function lireFiche(role, sortie, { copie } = {}) {
|
|
393
529
|
const schema = schemaDeFiche(role);
|
|
394
530
|
if (!schema) return { erreur: `le rôle ${role} n'a pas de fiche` };
|
|
395
531
|
let resultat;
|
|
@@ -404,6 +540,7 @@ export function lireFiche(role, sortie) {
|
|
|
404
540
|
const fiche = resultat.structured_output;
|
|
405
541
|
const fautes = erreurs(schema, fiche, '');
|
|
406
542
|
if (!fautes.length) fautes.push(...manques(role, fiche));
|
|
543
|
+
if (!fautes.length && copie) fautes.push(...introuvables(fiche, copie));
|
|
407
544
|
if (fautes.length) return { erreur: `fiche non conforme : ${fautes.join(' ; ')}` };
|
|
408
545
|
return { fiche, seance: resultat.session_id ?? null };
|
|
409
546
|
}
|
|
@@ -418,7 +555,8 @@ function paquetDe(racine, fichier) {
|
|
|
418
555
|
}
|
|
419
556
|
|
|
420
557
|
// The SARIF 2.1.0 of the findings <constats> of <garde> in <racine>: each `{ fichier, ligne,
|
|
421
|
-
// message, regle, niveau, genre }`, all but the message optional
|
|
558
|
+
// message, regle, niveau, genre, trouve }`, all but the message optional (`trouve`, the count a
|
|
559
|
+
// note of the debt finds).
|
|
422
560
|
export function sarif({ garde, constats, racine = process.cwd() }) {
|
|
423
561
|
return {
|
|
424
562
|
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
@@ -446,6 +584,7 @@ export function sarif({ garde, constats, racine = process.cwd() }) {
|
|
|
446
584
|
...(c.regle ? { regle: c.regle } : {}),
|
|
447
585
|
paquet: paquetDe(racine, c.fichier),
|
|
448
586
|
genre: c.genre ?? GENRES[0],
|
|
587
|
+
...(c.trouve !== undefined ? { trouve: c.trouve } : {}),
|
|
449
588
|
},
|
|
450
589
|
})),
|
|
451
590
|
},
|
|
@@ -482,7 +621,8 @@ const lireJson = f => {
|
|
|
482
621
|
|
|
483
622
|
// The guards that wrote their SARIF under the results directory <dossier>, and their findings:
|
|
484
623
|
// `{ gardes, constats }`, each finding `{ garde, regle, fichier, ligne, niveau, message, paquet,
|
|
485
|
-
// genre }`, `fichier`, `ligne`, `regle` and `paquet` null when the guard gave none
|
|
624
|
+
// genre }`, `fichier`, `ligne`, `regle` and `paquet` null when the guard gave none, and `trouve`,
|
|
625
|
+
// the count a note of the debt finds, when it carries one.
|
|
486
626
|
export function lireSarif(dossier) {
|
|
487
627
|
const gardes = new Set();
|
|
488
628
|
const constats = [];
|
|
@@ -501,6 +641,7 @@ export function lireSarif(dossier) {
|
|
|
501
641
|
message: String(r.message?.text ?? ''),
|
|
502
642
|
paquet: r.properties?.paquet ?? null,
|
|
503
643
|
genre: r.properties?.genre ?? GENRES[0],
|
|
644
|
+
...(r.properties?.trouve !== undefined ? { trouve: r.properties.trouve } : {}),
|
|
504
645
|
});
|
|
505
646
|
}
|
|
506
647
|
}
|
package/scripts/resumes.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// written in the documentation comment of the export, and `npm run api` carries it over.
|
|
16
16
|
// - A refusal inscribed in the project's debt (`grillhouse-dette.json`, see `dette.mjs`) passes,
|
|
17
17
|
// keyed by the interface and `resume-trop-long`.
|
|
18
|
-
// - In
|
|
18
|
+
// - In an envelope (`GRILLHOUSE_ENVELOPPE`), the components of its plan alone are judged.
|
|
19
19
|
import { existsSync, readFileSync } from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
21
|
import { pathToFileURL } from 'node:url';
|
package/scripts/session-start.sh
CHANGED
|
@@ -27,8 +27,7 @@
|
|
|
27
27
|
# A session of a role agent of the frame (`claude --agent <rôle>`, the circuit's sessions: the
|
|
28
28
|
# hook's input names it, `agent_type`, bare or as `grillhouse:<rôle>`, and the frame's plugin
|
|
29
29
|
# defines it, `$CLAUDE_PLUGIN_ROOT/agents/<rôle>.md`, the plugin whose hook runs this script) receives nothing:
|
|
30
|
-
# what the project lacks is the supervisor's to deal with
|
|
31
|
-
# hidden packages read as absent.
|
|
30
|
+
# what the project lacks is the supervisor's to deal with.
|
|
32
31
|
set -uo pipefail
|
|
33
32
|
|
|
34
33
|
# The project is Claude Code's, else the working directory; the frame's scripts are this one's
|