@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
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// The components a realisation ticket touches, surveyed from its text, as the « affected
|
|
3
3
|
// components » of a design document (an RFC of Rust, a design doc of Chromium) and the dependents
|
|
4
|
-
// of a package in Turborepo (`--filter=...<paquet>`). The explorateur runs it
|
|
5
|
-
// ticket
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// of a package in Turborepo (`--filter=...<paquet>`). The explorateur runs it when it writes the
|
|
5
|
+
// plan of a ticket (step 1 of the circuit, scripts/etapes.mjs), or on the draft of an exploration's
|
|
6
|
+
// ticket: the packages of its plan name these components, the readers it changes, and its section
|
|
7
|
+
// « Architecture » says the survey.
|
|
8
8
|
//
|
|
9
9
|
// npx grillhouse composants-affectes <ticket> the ticket `bd show` gives
|
|
10
10
|
// npx grillhouse composants-affectes --description <fichier> a draft of its description
|
|
11
|
-
// npx grillhouse composants-affectes <ticket> --admettre <composant> --raison <texte>
|
|
12
11
|
//
|
|
13
12
|
// prints the survey (`texteDuReleve`), from the repository root; for a ticket, then the components
|
|
14
|
-
// its title misses, exit 1 when there is one.
|
|
15
|
-
// survey finds and the ticket does not change (the explorateur at the creation of the ticket, or
|
|
16
|
-
// the supervisor): kept in the ticket's record, its reason written at the ticket
|
|
17
|
-
// (`Composants · <composant> admis hors du titre : <raison>`); a component the repository does not
|
|
18
|
-
// hold, or no reason, is refused, exit 2.
|
|
13
|
+
// its title misses, exit 1 when there is one.
|
|
19
14
|
//
|
|
20
15
|
// - The text read is the ticket's section « Architecture » and its section of criteria (a `##`
|
|
21
16
|
// heading that begins with « Critères »), each up to the next `##` heading; the rest of the
|
|
@@ -44,7 +39,7 @@
|
|
|
44
39
|
// package it changes. A survey by the types that cannot run says why, and the survey stands on
|
|
45
40
|
// the names; a name the types resolve is no longer set apart.
|
|
46
41
|
// - By the paths read (`lit`): each component whose tests declare they read a path
|
|
47
|
-
// (`grillhouse.lit` of its package.json
|
|
42
|
+
// (`grillhouse.lit` of its package.json and the `lit` of its traces, `cheminsLus`) that a
|
|
48
43
|
// path the ticket changes holds or lies in: a path of the repository the sections write (its
|
|
49
44
|
// `:<line>` left out), or the folder of a component they name.
|
|
50
45
|
// - By the architecture: the items of the « Rend » of the `docs/CADRE.md` of each component that
|
|
@@ -54,11 +49,7 @@
|
|
|
54
49
|
// - The components of the survey go in the title's order, the provider first: each one after
|
|
55
50
|
// every component it reads among them (its imports, the providers of the elements it reads), in
|
|
56
51
|
// the order of their names otherwise.
|
|
57
|
-
|
|
58
|
-
// under the main tree), holds the missing components of the circuit's last check and the
|
|
59
|
-
// components admitted out of the title, by `--admettre` or by the supervisor's answer to a stop
|
|
60
|
-
// (`repondre`): an admitted component is no longer missing.
|
|
61
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
52
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
62
53
|
import path from 'node:path';
|
|
63
54
|
import { pathToFileURL } from 'node:url';
|
|
64
55
|
import { spawnSync } from 'node:child_process';
|
|
@@ -71,11 +62,28 @@ import {
|
|
|
71
62
|
parcourir,
|
|
72
63
|
utilise,
|
|
73
64
|
} from './consommateurs.mjs';
|
|
74
|
-
import { cheminsLus } from './enveloppe/enveloppe.mjs';
|
|
75
65
|
import { lire } from './index-interfaces.mjs';
|
|
76
66
|
import { elementsEcrits, parLesTypes } from './references-par-les-types.mjs';
|
|
77
67
|
|
|
78
68
|
const DOSSIERS_DE_TEST = new Set(['test', 'tests', '__tests__']);
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The paths that the tests of the package at <dir> declare they read: its `grillhouse.lit`, then
|
|
72
|
+
* the `lit` of each trace it declares (`grillhouse.traces`), each path from the root; none without
|
|
73
|
+
* a package.json or a declaration.
|
|
74
|
+
* @param {string} dir the directory of the package
|
|
75
|
+
* @returns {string[]}
|
|
76
|
+
*/
|
|
77
|
+
export function cheminsLus(dir) {
|
|
78
|
+
const manifeste = path.join(dir, 'package.json');
|
|
79
|
+
if (!existsSync(manifeste)) return [];
|
|
80
|
+
const { grillhouse } = JSON.parse(readFileSync(manifeste, 'utf8'));
|
|
81
|
+
const liste = v => (Array.isArray(v) ? v.filter(l => typeof l === 'string') : []);
|
|
82
|
+
return [
|
|
83
|
+
...liste(grillhouse?.lit),
|
|
84
|
+
...Object.values(grillhouse?.traces ?? {}).flatMap(t => liste(t?.lit)),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
79
87
|
const SAUTES = new Set(['node_modules', 'dist', 'build', 'coverage']);
|
|
80
88
|
const SOURCE = /\.[cm]?[jt]sx?$/;
|
|
81
89
|
|
|
@@ -384,42 +392,11 @@ function rendus(c) {
|
|
|
384
392
|
return out;
|
|
385
393
|
}
|
|
386
394
|
|
|
387
|
-
// The record of a ticket's survey beside its copy (<fichier>): `{ manquants, admis, reponses }`,
|
|
388
|
-
// the components the last survey found missing, those the supervisor answered for, and his
|
|
389
|
-
// answers; empty without it.
|
|
390
|
-
function lireGarde(fichier) {
|
|
391
|
-
try {
|
|
392
|
-
const g = JSON.parse(readFileSync(fichier, 'utf8'));
|
|
393
|
-
return { manquants: g.manquants ?? [], admis: g.admis ?? [], reponses: g.reponses ?? [] };
|
|
394
|
-
} catch {
|
|
395
|
-
return { manquants: [], admis: [], reponses: [] };
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
function ecrireGarde(fichier, g) {
|
|
399
|
-
mkdirSync(path.dirname(fichier), { recursive: true });
|
|
400
|
-
writeFileSync(fichier, `${JSON.stringify(g, null, 2)}\n`);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// The components the supervisor answered for, kept in <fichier>.
|
|
404
|
-
const admis = fichier => lireGarde(fichier).admis;
|
|
405
|
-
|
|
406
|
-
// Keeps in <fichier> the components <liste> the supervisor answered for, with his answer.
|
|
407
|
-
export function admettre(fichier, liste, reponse) {
|
|
408
|
-
const g = lireGarde(fichier);
|
|
409
|
-
ecrireGarde(fichier, {
|
|
410
|
-
...g,
|
|
411
|
-
manquants: g.manquants.filter(c => !liste.includes(c)),
|
|
412
|
-
admis: [...new Set([...g.admis, ...liste])],
|
|
413
|
-
reponses: [...g.reponses, { date: new Date().toISOString(), composants: liste, reponse }],
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
|
|
417
395
|
// The components of the survey <r> the title <titre> does not name (its components as written,
|
|
418
|
-
// package names or paths, or `.`),
|
|
419
|
-
export function manquants(racine, r, titre
|
|
396
|
+
// package names or paths, or `.`), in the survey's order.
|
|
397
|
+
export function manquants(racine, r, titre) {
|
|
420
398
|
if (titre.includes('.')) return [];
|
|
421
399
|
const tous = lesComposants(racine);
|
|
422
|
-
const garde = new Set(fichier ? admis(fichier) : []);
|
|
423
400
|
const couvre = c => {
|
|
424
401
|
const rel = path.relative(racine, c.dir).split(path.sep).join('/');
|
|
425
402
|
return titre.some(t => {
|
|
@@ -428,39 +405,11 @@ export function manquants(racine, r, titre, fichier = null) {
|
|
|
428
405
|
});
|
|
429
406
|
};
|
|
430
407
|
return r.composants.filter(nom => {
|
|
431
|
-
if (garde.has(nom)) return false;
|
|
432
408
|
const c = tous.find(x => nomAuTitre(racine, x) === nom);
|
|
433
409
|
return !c || !couvre(c);
|
|
434
410
|
});
|
|
435
411
|
}
|
|
436
412
|
|
|
437
|
-
// The check of the circuit before the testwriter of the first component: the survey of
|
|
438
|
-
// <description> in <racine> against the title's components <titre>, the missing ones kept in
|
|
439
|
-
// <fichier>. `{ manquants, rapport, releve }`, the report opening on the missing components (null
|
|
440
|
-
// without one), the survey as the section « Architecture » says it, which the questionneur reads.
|
|
441
|
-
export function verifierLeTitre({ racine, ticket, description, titre, fichier }) {
|
|
442
|
-
const r = releve(racine, description);
|
|
443
|
-
const liste = manquants(racine, r, titre, fichier);
|
|
444
|
-
ecrireGarde(fichier, { ...lireGarde(fichier), manquants: liste });
|
|
445
|
-
if (!liste.length) return { manquants: [], rapport: null, releve: texteDuReleve(r) };
|
|
446
|
-
const rapport = [
|
|
447
|
-
`Composants manquants au titre : ${liste.join(', ')}.`,
|
|
448
|
-
'',
|
|
449
|
-
texteDuReleve(r),
|
|
450
|
-
'',
|
|
451
|
-
`Le superviseur ajoute chacun au titre, le fournisseur d'abord (\`bd update ${ticket} --title "…"\`), puis relance le circuit ; ou il répond pourquoi il reste dehors : \`npx grillhouse circuit ${ticket} --reponse "<texte>"\`.`,
|
|
452
|
-
].join('\n');
|
|
453
|
-
return { manquants: liste, rapport, releve: texteDuReleve(r) };
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// The supervisor's answer <reponse> to the missing components kept in <fichier>: they pass from
|
|
457
|
-
// then on. The components answered for, in their order.
|
|
458
|
-
export function repondre(fichier, reponse) {
|
|
459
|
-
const { manquants: liste } = lireGarde(fichier);
|
|
460
|
-
admettre(fichier, liste, reponse);
|
|
461
|
-
return liste;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
413
|
// The survey <r> as the section « Architecture » says it, in Markdown.
|
|
465
414
|
export function texteDuReleve(r) {
|
|
466
415
|
const out = [
|
|
@@ -515,34 +464,6 @@ export function texteDuReleve(r) {
|
|
|
515
464
|
return out.join('\n');
|
|
516
465
|
}
|
|
517
466
|
|
|
518
|
-
// The admission of <nom> out of the title of <ticket>, its reason after `--raison` in <args>: its
|
|
519
|
-
// exit code.
|
|
520
|
-
function admettreAuTicket(racine, ticket, nom, args) {
|
|
521
|
-
const r = args.indexOf('--raison');
|
|
522
|
-
const raison = r >= 0 ? args[r + 1]?.trim() : '';
|
|
523
|
-
const c = lesComposants(racine).find(
|
|
524
|
-
x => x.paquet === nom || path.relative(racine, x.dir) === path.normalize(nom ?? '')
|
|
525
|
-
);
|
|
526
|
-
if (!c || !raison) {
|
|
527
|
-
process.stderr.write(
|
|
528
|
-
`⛔ ${!c ? `${nom ?? '(aucun)'} : aucun composant de ce nom` : '--raison manque'}.\n` +
|
|
529
|
-
'usage: npx grillhouse composants-affectes <ticket> --admettre <composant> --raison <texte>\n'
|
|
530
|
-
);
|
|
531
|
-
return 2;
|
|
532
|
-
}
|
|
533
|
-
const titre = nomAuTitre(racine, c);
|
|
534
|
-
admettre(path.join(racine, '.claude', 'worktrees', `${ticket}.composants.json`), [titre], raison);
|
|
535
|
-
const texte = `Composants · ${titre} admis hors du titre : ${raison}`;
|
|
536
|
-
const bd = spawnSync('bd', ['comments', 'add', ticket, texte], {
|
|
537
|
-
cwd: racine,
|
|
538
|
-
encoding: 'utf8',
|
|
539
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
540
|
-
});
|
|
541
|
-
if (bd.status !== 0) process.stderr.write(`⚠️ ligne non écrite au ticket : ${bd.stderr}\n`);
|
|
542
|
-
process.stdout.write(`${texte}\n`);
|
|
543
|
-
return 0;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
467
|
// The command: its exit code.
|
|
547
468
|
function commande(racine, args) {
|
|
548
469
|
const i = args.indexOf('--description');
|
|
@@ -556,8 +477,6 @@ function commande(racine, args) {
|
|
|
556
477
|
return 0;
|
|
557
478
|
}
|
|
558
479
|
const [ticket] = args;
|
|
559
|
-
const a = args.indexOf('--admettre');
|
|
560
|
-
if (ticket && a >= 0) return admettreAuTicket(racine, ticket, args[a + 1], args);
|
|
561
480
|
if (!ticket) {
|
|
562
481
|
process.stderr.write(
|
|
563
482
|
'usage: npx grillhouse composants-affectes <ticket> | --description <fichier>\n'
|
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
// npx grillhouse consommateurs-arbitres run from the repository root (a guard of grillhouse:gardes)
|
|
7
7
|
//
|
|
8
8
|
// - It judges the copy it runs in, on the branch `agent/<ticket>`: for each component the guards
|
|
9
|
-
// judge (`aVerifier` of scripts/interfaces-contre-code.mjs; in
|
|
10
|
-
// `GRILLHOUSE_ENVELOPPE`,
|
|
11
|
-
// section of its `docs/INTERFACE.md` in the working tree that the section of
|
|
12
|
-
// is a changed line (an interface absent from HEAD: all its lines). Out of a
|
|
13
|
-
// (main, the detached copy of the integration, the CI, the night), there is no
|
|
14
|
-
// judged.
|
|
9
|
+
// judge (`aVerifier` of scripts/interfaces-contre-code.mjs; in an envelope,
|
|
10
|
+
// `GRILLHOUSE_ENVELOPPE`, the components of its plan), each list line (`- …`) of the
|
|
11
|
+
// `## Consommateurs` section of its `docs/INTERFACE.md` in the working tree that the section of
|
|
12
|
+
// HEAD does not hold is a changed line (an interface absent from HEAD: all its lines). Out of a
|
|
13
|
+
// branch `agent/…` (main, the detached copy of the integration, the CI, the night), there is no
|
|
14
|
+
// lot: nothing is judged.
|
|
15
15
|
// - With a changed line, the comments of the ticket are read (`bd comments <ticket> --json`, run
|
|
16
16
|
// from the main tree, the parent of git's common directory). A changed line passes when an
|
|
17
|
-
// arbitrage that is settled (`arbitrageTranche`: a « ## Arbitrage… » comment,
|
|
18
|
-
//
|
|
17
|
+
// arbitrage of the supervisor that is settled (`arbitrageTranche`: a « ## Arbitrage… » comment,
|
|
18
|
+
// `estArbitrage`, its verdict « tranché » or the owner's answer after it) names its consumer, the first
|
|
19
19
|
// name between backquotes of the line, found as it is in the comment's text. Without a changed
|
|
20
20
|
// line, bd is not called.
|
|
21
21
|
// - A changed line no arbitrage settles passes too when the section « Architecture » of the
|
|
@@ -35,21 +35,15 @@ import { aVerifier } from './interfaces-contre-code.mjs';
|
|
|
35
35
|
|
|
36
36
|
const REGLE = 'consommateur-sans-arbitrage';
|
|
37
37
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
38
|
+
// True when the text <text> of a comment is an arbitrage: a « ## Arbitrage… » comment, as the
|
|
39
|
+
// supervisor writes it (the skill pitmaster, « Une question de conception se tranche par les
|
|
40
|
+
// règles »).
|
|
41
|
+
export const estArbitrage = text => /^\s*## Arbitrage\b/.test(String(text ?? ''));
|
|
41
42
|
|
|
42
|
-
// True when the
|
|
43
|
-
//
|
|
44
|
-
export const estArbitrage = text =>
|
|
45
|
-
/^\s*## Arbitrage\b/.test(String(text ?? '')) || DU_REGULATEUR.test(String(text ?? ''));
|
|
46
|
-
|
|
47
|
-
// True when the arbitrage <a> of <comments> (Beads' JSON, in their order) is settled: the
|
|
48
|
-
// regulator's (it settles only what the letter of a criterion, a written precedent or a rule of
|
|
49
|
-
// the frame settles), a line `### Verdict — tranché`, or a « ## Réponse du responsable » comment
|
|
50
|
-
// after it.
|
|
43
|
+
// True when the arbitrage <a> of <comments> (Beads' JSON, in their order) is settled: a line
|
|
44
|
+
// `### Verdict — tranché`, or a « ## Réponse du responsable » comment after it.
|
|
51
45
|
export function arbitrageTranche(comments, a) {
|
|
52
|
-
const tranche =
|
|
46
|
+
const tranche = /^### Verdict\s*—\s*tranché/m.test(a.text);
|
|
53
47
|
return (
|
|
54
48
|
tranche ||
|
|
55
49
|
comments
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The types of ./consommateurs.mjs as the entry `@roomi-fields/grillhouse/consommateurs`
|
|
2
|
+
// publishes it, for a project's guard over the shared test material.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The components of `racine` whose `package.json` exports `./test-fixtures`, a package out of the
|
|
6
|
+
* frame never one: the packages of the shared test material.
|
|
7
|
+
*/
|
|
8
|
+
export function matieres(racine: string): { nom: string; dir: string; paquet: string | null }[];
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
// The identity of a component is its name: two directories of the same name, one under `packages/`
|
|
28
28
|
// and one under `src/`, are refused, both paths named (the way npm workspaces, Nx and Cargo refuse
|
|
29
29
|
// two projects of one name). Names differing by case are two names.
|
|
30
|
+
//
|
|
31
|
+
// A project's guard imports `matieres` by the package's entry
|
|
32
|
+
// `@roomi-fields/grillhouse/consommateurs`, its types in ./consommateurs.d.ts (docs/ARCHITECTURE.md,
|
|
33
|
+
// the package's named entries).
|
|
30
34
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
31
35
|
import path from 'node:path';
|
|
32
36
|
import { pathToFileURL } from 'node:url';
|
|
@@ -303,7 +307,7 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
|
303
307
|
for (const l of lignes) process.stderr.write(`${l}\n`);
|
|
304
308
|
if (refus.length) {
|
|
305
309
|
process.stderr.write(
|
|
306
|
-
"Une consommation nouvelle se décide (la section « Architecture » du ticket qui la nomme, sinon
|
|
310
|
+
"Une consommation nouvelle se décide (la section « Architecture » du ticket qui la nomme, sinon l'arbitrage du superviseur), puis entre dans la section « Consommateurs » de l'interface du fournisseur.\n"
|
|
307
311
|
);
|
|
308
312
|
process.exitCode = 1;
|
|
309
313
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// The types of ./dette.mjs as the entry `@roomi-fields/grillhouse/dette` publishes it, for a
|
|
2
|
+
// project's guard that judges its findings against the project's known debt.
|
|
3
|
+
|
|
4
|
+
/** A finding of a guard: inscribed in the debt only with its `fichier` and its `regle`. */
|
|
5
|
+
export type Constat = {
|
|
6
|
+
fichier?: string;
|
|
7
|
+
regle?: string;
|
|
8
|
+
message: string;
|
|
9
|
+
ligne?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Judges the findings of `garde` at `racine` against `grillhouse-dette.json`, in the mode `mode`
|
|
14
|
+
* (`GRILLHOUSE_DETTE` by default): `refus` the findings to print as refusals, `lignes` the lines
|
|
15
|
+
* to print after them.
|
|
16
|
+
*/
|
|
17
|
+
export function trancher(
|
|
18
|
+
racine: string,
|
|
19
|
+
garde: string,
|
|
20
|
+
constats: Constat[],
|
|
21
|
+
mode?: string,
|
|
22
|
+
env?: Record<string, string | undefined>
|
|
23
|
+
): { refus: Constat[]; lignes: string[] };
|
package/scripts/dette.mjs
CHANGED
|
@@ -23,9 +23,13 @@
|
|
|
23
23
|
// drops the keys at zero, never raising a count nor adding a key. Each guard then judges against
|
|
24
24
|
// the file it wrote. The npm scripts `grillhouse:dette:inscrire` and `grillhouse:dette:elaguer`
|
|
25
25
|
// run every guard so.
|
|
26
|
+
//
|
|
27
|
+
// A project's guard imports `trancher` by the package's entry `@roomi-fields/grillhouse/dette`,
|
|
28
|
+
// its types in ./dette.d.ts (docs/ARCHITECTURE.md, the package's named entries).
|
|
26
29
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
27
30
|
import path from 'node:path';
|
|
28
|
-
import {
|
|
31
|
+
import { pathToFileURL } from 'node:url';
|
|
32
|
+
import { A_ELAGUER, ecrireSarif } from './resultats.mjs';
|
|
29
33
|
|
|
30
34
|
export const FICHIER = 'grillhouse-dette.json';
|
|
31
35
|
// The environment variable that names the mode of a run: `inscrire` or `elaguer`.
|
|
@@ -129,13 +133,57 @@ export function juger(dette, garde, constats) {
|
|
|
129
133
|
// (the keys beyond their count, the keys to prune, what the run wrote). An unknown mode, or a file
|
|
130
134
|
// that is not JSON, refuses every finding, behind a refusal that says why. The refusals are the
|
|
131
135
|
// guard's findings in SARIF too, under the results directory <env> names (scripts/resultats.mjs),
|
|
132
|
-
// each with its file, its line when the guard gives one, and its rule
|
|
136
|
+
// each with its file, its line when the guard gives one, and its rule; beside them, each key
|
|
137
|
+
// inscribed above what the guard finds is a note (`a-elaguer`, level `note`), no red, by which the
|
|
138
|
+
// circuit lowers the debt d'office at the final pass (scripts/circuit.mjs, 4.2).
|
|
133
139
|
export function trancher(racine, garde, constats, mode = process.env[MODE] || undefined, env) {
|
|
134
|
-
const { refus, lignes } = juge(racine, garde, constats, mode);
|
|
135
|
-
|
|
140
|
+
const { refus, lignes, corriges } = juge(racine, garde, constats, mode);
|
|
141
|
+
const notes = corriges.map(c => ({
|
|
142
|
+
fichier: c.fichier,
|
|
143
|
+
regle: c.regle,
|
|
144
|
+
niveau: 'note',
|
|
145
|
+
genre: A_ELAGUER,
|
|
146
|
+
trouve: c.trouve,
|
|
147
|
+
message: `${c.trouve} constat(s) pour ${c.inscrit} inscrit(s) dans ${FICHIER} : la dette s'abaisse.`,
|
|
148
|
+
}));
|
|
149
|
+
ecrireSarif({ garde, constats: [...refus, ...notes], racine, env: env ?? process.env });
|
|
136
150
|
return { refus, lignes };
|
|
137
151
|
}
|
|
138
152
|
|
|
153
|
+
// Writes <dette> at <racine>, its guards sorted.
|
|
154
|
+
function ecrire(racine, dette) {
|
|
155
|
+
const triee = Object.fromEntries(
|
|
156
|
+
Object.keys(dette)
|
|
157
|
+
.sort()
|
|
158
|
+
.map(g => [g, dette[g]])
|
|
159
|
+
);
|
|
160
|
+
writeFileSync(path.join(racine, FICHIER), `${JSON.stringify(triee, null, 2)}\n`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// The debt at <racine> lowered by <notes>, each `{ garde, fichier, regle, trouve }` (the notes
|
|
164
|
+
// `a-elaguer` of a pass, scripts/resultats.mjs): each key named comes down to what its guard
|
|
165
|
+
// found, and leaves at zero; a count never rises, no key is added. Written when it changes;
|
|
166
|
+
// returns whether it did.
|
|
167
|
+
function abaisser(racine, notes) {
|
|
168
|
+
const avant = lire(racine);
|
|
169
|
+
const dette = JSON.parse(JSON.stringify(avant));
|
|
170
|
+
for (const { garde, fichier, regle, trouve } of notes) {
|
|
171
|
+
const parRegle = dette[garde]?.[fichier];
|
|
172
|
+
const cle = parRegle?.[regle];
|
|
173
|
+
if (!cle || !Number.isInteger(trouve) || trouve >= cle.count) continue;
|
|
174
|
+
if (trouve > 0) {
|
|
175
|
+
cle.count = trouve;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
delete parRegle[regle];
|
|
179
|
+
if (!Object.keys(parRegle).length) delete dette[garde][fichier];
|
|
180
|
+
if (!Object.keys(dette[garde]).length) delete dette[garde];
|
|
181
|
+
}
|
|
182
|
+
const change = JSON.stringify(dette) !== JSON.stringify(avant);
|
|
183
|
+
if (change) ecrire(racine, dette);
|
|
184
|
+
return change;
|
|
185
|
+
}
|
|
186
|
+
|
|
139
187
|
function juge(racine, garde, constats, mode) {
|
|
140
188
|
let avant;
|
|
141
189
|
try {
|
|
@@ -144,21 +192,15 @@ function juge(racine, garde, constats, mode) {
|
|
|
144
192
|
}
|
|
145
193
|
avant = lire(racine);
|
|
146
194
|
} catch (e) {
|
|
147
|
-
return { refus: [{ message: e.message }, ...constats], lignes: [] };
|
|
195
|
+
return { refus: [{ message: e.message }, ...constats], lignes: [], corriges: [] };
|
|
148
196
|
}
|
|
149
197
|
let dette = avant;
|
|
150
198
|
const lignes = [];
|
|
151
199
|
if (mode) {
|
|
152
200
|
dette =
|
|
153
201
|
mode === 'inscrire' ? inscrire(avant, garde, constats) : elaguer(avant, garde, constats);
|
|
154
|
-
const f = path.join(racine, FICHIER);
|
|
155
202
|
if (JSON.stringify(dette) !== JSON.stringify(avant)) {
|
|
156
|
-
|
|
157
|
-
Object.keys(dette)
|
|
158
|
-
.sort()
|
|
159
|
-
.map(g => [g, dette[g]])
|
|
160
|
-
);
|
|
161
|
-
writeFileSync(f, `${JSON.stringify(triee, null, 2)}\n`);
|
|
203
|
+
ecrire(racine, dette);
|
|
162
204
|
lignes.push(
|
|
163
205
|
`📝 ${FICHIER} : la dette de ${garde} est ${mode === 'inscrire' ? 'inscrite' : 'élaguée'}.`
|
|
164
206
|
);
|
|
@@ -177,5 +219,26 @@ function juge(racine, garde, constats, mode) {
|
|
|
177
219
|
}
|
|
178
220
|
if (tus > 0)
|
|
179
221
|
lignes.push(`ℹ️ ${garde} : ${tus} constat(s) connu(s), inscrit(s) dans ${FICHIER}.`);
|
|
180
|
-
return { refus, lignes };
|
|
222
|
+
return { refus, lignes, corriges };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// `node dette.mjs --abaisser <notes.json>`: the debt of the working directory lowered by the notes
|
|
226
|
+
// of a pass, a JSON list (abaisser), as `prettier --write` writes and judges nothing: 0 once
|
|
227
|
+
// written or left as it is, 1 only on a real failure (a file it cannot read).
|
|
228
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
229
|
+
const [option, notes] = process.argv.slice(2);
|
|
230
|
+
if (option !== '--abaisser' || !notes) {
|
|
231
|
+
process.stderr.write('Usage : node dette.mjs --abaisser <notes.json>\n');
|
|
232
|
+
process.exitCode = 2;
|
|
233
|
+
} else {
|
|
234
|
+
try {
|
|
235
|
+
const fait = abaisser(process.cwd(), JSON.parse(readFileSync(notes, 'utf8')));
|
|
236
|
+
process.stdout.write(
|
|
237
|
+
`${fait ? `📝 ${FICHIER} : la dette est abaissée.` : `${FICHIER} : rien à abaisser.`}\n`
|
|
238
|
+
);
|
|
239
|
+
} catch (e) {
|
|
240
|
+
process.stderr.write(`${e instanceof Error ? e.message : e}\n`);
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
181
244
|
}
|
package/scripts/droits.mjs
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// The rights and the context of each role session the circuit launches, from one source (ROLES):
|
|
3
|
-
// the testwriter and the developpeur in the envelope of their
|
|
4
|
-
// the
|
|
5
|
-
// (scripts/circuit.mjs). As a task of CI receives its
|
|
3
|
+
// the testwriter and the developpeur in the envelope of their plan (scripts/enveloppe/lancer.sh),
|
|
4
|
+
// the explorateur, the questionneur and the reviewer out of it (scripts/circuit.mjs). As a task of CI receives its
|
|
6
5
|
// rights from its steps and runs on an image of its own, not on the workstation of its author
|
|
7
6
|
// (GitHub Actions `permissions:` per job, the hermetic actions of Bazel), a session receives the
|
|
8
7
|
// rights the commands of its skill name, and the context its work reads.
|
|
9
8
|
//
|
|
10
9
|
// npx grillhouse droits <rôle> --racine <arbre principal> --copie <copie> --ticket <ticket>
|
|
11
|
-
// [--journal <fichier> --preuves <motif>]
|
|
12
10
|
//
|
|
13
11
|
// prints the options of claude for that session, one per line (`optionsDuRole`):
|
|
14
12
|
//
|
|
@@ -20,7 +18,7 @@
|
|
|
20
18
|
// change): its permissions (`allow`, the commands its skill names; `deny`, the change of its
|
|
21
19
|
// author at the ticket, AUTEUR_FIXE, for a role that writes the editing by script, the test
|
|
22
20
|
// tool run directly and the reading again of the ticket its instruction carries, and for a role
|
|
23
|
-
// that writes nothing at the ticket, the
|
|
21
|
+
// that writes nothing at the ticket, the explorateur and the questionneur, every write of bd,
|
|
24
22
|
// ECRIRE_AU_TICKET); the
|
|
25
23
|
// automatic memory off; the skills of Claude Code off (`disableBundledSkills`, and the few it keeps,
|
|
26
24
|
// INTEGREES), and the project's own skills (`.claude/skills/` of the copy) off
|
|
@@ -109,6 +107,14 @@ const INTEGREES = ['design', 'doctor', 'plugin-authoring'];
|
|
|
109
107
|
const GREFFON_DU_CADRE = 'grillhouse@';
|
|
110
108
|
// The servers of the index (charter, « L'index d'abord »).
|
|
111
109
|
const INDEX = ['codegraph', 'rtfm'];
|
|
110
|
+
// The reads of the index by a role that writes: codegraph for the calls, rtfm for the search and
|
|
111
|
+
// the reading of a result whole.
|
|
112
|
+
const LIRE_L_INDEX = [
|
|
113
|
+
'Bash(codegraph explore *)',
|
|
114
|
+
'mcp__codegraph__codegraph_explore',
|
|
115
|
+
'mcp__rtfm__rtfm_search',
|
|
116
|
+
'mcp__rtfm__rtfm_expand',
|
|
117
|
+
];
|
|
112
118
|
// The tools of every role, and of a role that writes.
|
|
113
119
|
const LIRE = ['Bash', 'Read', 'Skill'];
|
|
114
120
|
const ECRIRE = ['Edit', 'Write'];
|
|
@@ -117,18 +123,39 @@ const ECRIRE = ['Edit', 'Write'];
|
|
|
117
123
|
// `ecritAuTicket` is false), whether it uses the index, and the commands its skill names (the
|
|
118
124
|
// frame's plugin, `mods/grillhouse/skills/<rôle>/`), as permission rules.
|
|
119
125
|
export const ROLES = {
|
|
126
|
+
// It writes no file and nothing at the ticket: it surveys the components and reads the code by
|
|
127
|
+
// the index, and ends on the plan in its sheet, which the circuit writes at the ticket.
|
|
128
|
+
explorateur: {
|
|
129
|
+
index: true,
|
|
130
|
+
ecritAuTicket: false,
|
|
131
|
+
commandes: [
|
|
132
|
+
'Bash(bd show *)',
|
|
133
|
+
'Bash(bd comments *)',
|
|
134
|
+
'Bash(npx grillhouse composants-affectes *)',
|
|
135
|
+
'Bash(codegraph explore *)',
|
|
136
|
+
'mcp__codegraph__codegraph_explore',
|
|
137
|
+
'mcp__rtfm__rtfm_search',
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
// It writes the tests of the whole ticket, in the envelope of its plan, and reads the whole copy
|
|
141
|
+
// by the index (charter, « L'index d'abord »).
|
|
120
142
|
testwriter: {
|
|
121
143
|
ecrit: true,
|
|
144
|
+
index: true,
|
|
122
145
|
commandes: [
|
|
123
146
|
'Bash(bd update * --claim)',
|
|
124
147
|
'Bash(bd comments add *)',
|
|
125
148
|
'Bash(npm test *)',
|
|
126
149
|
// The removal of a file of its tests, by a script of its temporary directory.
|
|
127
150
|
'Bash(node $TMPDIR/*)',
|
|
151
|
+
...LIRE_L_INDEX,
|
|
128
152
|
],
|
|
129
153
|
},
|
|
154
|
+
// It writes the code of the whole ticket, in the envelope of its plan, and reads the whole copy
|
|
155
|
+
// by the index.
|
|
130
156
|
developpeur: {
|
|
131
157
|
ecrit: true,
|
|
158
|
+
index: true,
|
|
132
159
|
commandes: [
|
|
133
160
|
'Bash(bd update * --claim)',
|
|
134
161
|
'Bash(bd update * --notes*)',
|
|
@@ -137,6 +164,7 @@ export const ROLES = {
|
|
|
137
164
|
'Bash(git -C * diff*)',
|
|
138
165
|
'Bash(npm test *)',
|
|
139
166
|
'Bash(npm run api*)',
|
|
167
|
+
...LIRE_L_INDEX,
|
|
140
168
|
],
|
|
141
169
|
},
|
|
142
170
|
relecteur: {
|
|
@@ -151,19 +179,6 @@ export const ROLES = {
|
|
|
151
179
|
'mcp__rtfm__rtfm_search',
|
|
152
180
|
],
|
|
153
181
|
},
|
|
154
|
-
// It writes nothing at the ticket: the circuit writes its decision, its arbitrage and the
|
|
155
|
-
// criterion it adds, from its sheet. It reads the circuit's state by its command, the ticket's
|
|
156
|
-
// log and its proofs by their paths, a precedent by bd, the copy by git.
|
|
157
|
-
regulateur: {
|
|
158
|
-
lit: ['journal', 'preuves'],
|
|
159
|
-
ecritAuTicket: false,
|
|
160
|
-
commandes: [
|
|
161
|
-
'Bash(npx grillhouse etat-du-circuit *)',
|
|
162
|
-
'Bash(bd search *)',
|
|
163
|
-
'Bash(bd comments *)',
|
|
164
|
-
...LIRE_GIT,
|
|
165
|
-
],
|
|
166
|
-
},
|
|
167
182
|
// It asks, and decides nothing: it reads the proposal its instruction carries and the code by
|
|
168
183
|
// the index; the circuit writes its questions at the ticket.
|
|
169
184
|
questionneur: {
|
|
@@ -175,19 +190,6 @@ export const ROLES = {
|
|
|
175
190
|
'mcp__rtfm__rtfm_search',
|
|
176
191
|
],
|
|
177
192
|
},
|
|
178
|
-
// On the questions to a plan, it answers for the plan; the circuit writes its changes to the
|
|
179
|
-
// ticket and its answers at the ticket.
|
|
180
|
-
'referent-technique': {
|
|
181
|
-
index: true,
|
|
182
|
-
commandes: [
|
|
183
|
-
'Bash(bd show *)',
|
|
184
|
-
'Bash(bd comments add *)',
|
|
185
|
-
'Bash(npx grillhouse ticket *)',
|
|
186
|
-
'Bash(codegraph explore *)',
|
|
187
|
-
'mcp__codegraph__codegraph_explore',
|
|
188
|
-
'mcp__rtfm__rtfm_search',
|
|
189
|
-
],
|
|
190
|
-
},
|
|
191
193
|
};
|
|
192
194
|
|
|
193
195
|
const lireJson = f => {
|
|
@@ -239,13 +241,10 @@ function serveursDeLIndex(racine, home) {
|
|
|
239
241
|
}
|
|
240
242
|
|
|
241
243
|
// The settings of the session of <role>, for the ticket <ticket>, its copy <copie> (where the
|
|
242
|
-
// project's own skills are read) and the main tree <racine
|
|
243
|
-
function reglagesDuRole(role, { racine, copie = racine, ticket
|
|
244
|
+
// project's own skills are read) and the main tree <racine>.
|
|
245
|
+
function reglagesDuRole(role, { racine, copie = racine, ticket }) {
|
|
244
246
|
const r = ROLES[role];
|
|
245
247
|
if (!r) throw new Error(`rôle sans droits : ${role}`);
|
|
246
|
-
const fichiers = { journal, preuves };
|
|
247
|
-
// An absolute path of the machine opens on `//`.
|
|
248
|
-
const lectures = (r.lit ?? []).filter(k => fichiers[k]).map(k => `Read(/${fichiers[k]})`);
|
|
249
248
|
const deny = [...AUTEUR_FIXE, ...(r.ecritAuTicket === false ? ECRIRE_AU_TICKET : [])];
|
|
250
249
|
if (r.ecrit) {
|
|
251
250
|
deny.push(...ECRIRE_PAR_SCRIPT, ...TEST_DIRECT);
|
|
@@ -264,7 +263,7 @@ function reglagesDuRole(role, { racine, copie = racine, ticket, journal, preuves
|
|
|
264
263
|
: []),
|
|
265
264
|
];
|
|
266
265
|
const reglages = {
|
|
267
|
-
permissions: { allow: [...r.commandes
|
|
266
|
+
permissions: { allow: [...r.commandes], deny },
|
|
268
267
|
autoMemoryEnabled: false,
|
|
269
268
|
disableBundledSkills: true,
|
|
270
269
|
skillOverrides: Object.fromEntries(autres.map(s => [s, 'off'])),
|
|
@@ -328,13 +327,8 @@ function commande(argv) {
|
|
|
328
327
|
const o = {};
|
|
329
328
|
for (let i = 0; i < reste.length; i += 2) {
|
|
330
329
|
const cle = reste[i]?.replace(/^--/, '');
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
i + 1 >= reste.length
|
|
334
|
-
) {
|
|
335
|
-
process.stderr.write(
|
|
336
|
-
`usage : droits.mjs <rôle> --racine <r> --copie <c> --ticket <t> [--journal <f> --preuves <m>]\n`
|
|
337
|
-
);
|
|
330
|
+
if (!['racine', 'copie', 'ticket'].includes(cle) || i + 1 >= reste.length) {
|
|
331
|
+
process.stderr.write(`usage : droits.mjs <rôle> --racine <r> --copie <c> --ticket <t>\n`);
|
|
338
332
|
return 2;
|
|
339
333
|
}
|
|
340
334
|
o[cle] = reste[i + 1];
|