@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
@@ -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 on the draft of a
5
- // ticket, whose title then names these components and whose section « Architecture » says the
6
- // survey; the circuit (scripts/circuit.mjs) runs it again before the testwriter of the first
7
- // component, and stops on a component the title does not name.
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. `--admettre` admits out of the title a component the
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, `cheminsLus` of scripts/enveloppe/enveloppe.mjs) that a
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
- // - The record of a ticket's survey, beside its copy (`.claude/worktrees/<ticket>.composants.json`
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 `.`), nor the answers kept in <fichier>, in the survey's order.
419
- export function manquants(racine, r, titre, fichier = null) {
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 the envelope of a component,
10
- // `GRILLHOUSE_ENVELOPPE`, that component alone), each list line (`- …`) of the `## Consommateurs`
11
- // section of its `docs/INTERFACE.md` in the working tree that the section of HEAD does not hold
12
- // is a changed line (an interface absent from HEAD: all its lines). Out of a branch `agent/…`
13
- // (main, the detached copy of the integration, the CI, the night), there is no lot: nothing is
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, or the regulator's,
18
- // that the circuit writes from its sheet, `estArbitrage`) names its consumer, the first
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
- // The regulator's arbitrage as the circuit writes it at the ticket, from the `arbitrage` of its
39
- // sheet (scripts/circuit.mjs): `Circuit · <composant> · regulateur : arbitrage — <texte>`.
40
- const DU_REGULATEUR = /^Circuit · [^\n]* · regulateur : arbitrage — /;
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 text <text> of a comment is an arbitrage: a « ## Arbitrage… » comment, or the
43
- // regulator's, as the circuit writes it.
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 = DU_REGULATEUR.test(a.text) || /^### Verdict\s*—\s*tranché/m.test(a.text);
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 le référent technique), puis entre dans la section « Consommateurs » de l'interface du fournisseur.\n"
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 { ecrireSarif } from './resultats.mjs';
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
- ecrireSarif({ garde, constats: refus, racine, env: env ?? process.env });
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
- const triee = Object.fromEntries(
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
  }
@@ -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 component (scripts/enveloppe/lancer.sh),
4
- // the reviewer, the regulator, the questionneur and the technical referent out of it
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 questionneur and the regulator, every write of bd,
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>; `lit` names the files the regulator reads.
243
- function reglagesDuRole(role, { racine, copie = racine, ticket, journal, preuves }) {
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, ...lectures], deny },
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
- !['racine', 'copie', 'ticket', 'journal', 'preuves'].includes(cle) ||
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];