@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.
Files changed (64) 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/sources.mjs +28 -0
  43. package/scripts/sous-commandes.mjs +62 -0
  44. package/scripts/tableau.mjs +85 -191
  45. package/scripts/tests.mjs +19 -17
  46. package/scripts/verifier.mjs +5 -4
  47. package/scripts/verrou-de-machine.mjs +131 -24
  48. package/scripts/verrous/verrou.mjs +66 -5
  49. package/skills/developpeur/SKILL.md +60 -50
  50. package/skills/explorateur/SKILL.md +90 -64
  51. package/skills/grill/SKILL.md +1 -1
  52. package/skills/mesure/SKILL.md +2 -1
  53. package/skills/pitmaster/SKILL.md +132 -118
  54. package/skills/pitmaster/references/consigne-agent.md +54 -148
  55. package/skills/questionneur/SKILL.md +18 -18
  56. package/skills/relecteur/SKILL.md +69 -23
  57. package/skills/testwriter/SKILL.md +46 -35
  58. package/types/index.d.ts +1 -3
  59. package/agents/referent-technique.md +0 -9
  60. package/agents/regulateur.md +0 -11
  61. package/scripts/enveloppe/composant.mjs +0 -11
  62. package/scripts/etat-du-circuit.mjs +0 -104
  63. package/skills/referent-technique/SKILL.md +0 -145
  64. package/skills/regulateur/SKILL.md +0 -220
@@ -0,0 +1,28 @@
1
+ // The source files of a project, as the migrations read them (scripts/migrations/): each `.ts`,
2
+ // `.mts`, `.cts`, `.js`, `.mjs`, `.cjs` under its root, as a path from it, the directories of
3
+ // tools and of what is installed or built (node_modules, .git, dist, coverage, .turbo, .claude,
4
+ // .beads, .codegraph, .rtfm) left out.
5
+ import { readdirSync } from 'node:fs';
6
+ import path from 'node:path';
7
+
8
+ const EXTENSIONS = /\.(?:[mc]?ts|[mc]?js)$/;
9
+ const HORS = new Set([
10
+ 'node_modules',
11
+ '.git',
12
+ 'dist',
13
+ 'coverage',
14
+ '.turbo',
15
+ '.claude',
16
+ '.beads',
17
+ '.codegraph',
18
+ '.rtfm',
19
+ ]);
20
+
21
+ /** The source files of the project at <racine>, as paths from it. */
22
+ export function sources(racine, rel = '') {
23
+ return readdirSync(path.join(racine, rel), { withFileTypes: true }).flatMap(e => {
24
+ const r = rel === '' ? e.name : `${rel}/${e.name}`;
25
+ if (e.isDirectory()) return HORS.has(e.name) ? [] : sources(racine, r);
26
+ return e.isFile() && EXTENSIONS.test(e.name) ? [r] : [];
27
+ });
28
+ }
@@ -0,0 +1,62 @@
1
+ // The table of the frame's subcommands, which `grillhouse <sous-commande>` (bin/grillhouse.mjs)
2
+ // runs and the migrations read (a former copy of a script becomes its subcommand): one module the
3
+ // command and the scripts import, so that no script imports the command, which is being evaluated
4
+ // when it runs one.
5
+ //
6
+ // - A subcommand is named after its script, without directory nor extension (`verifier`,
7
+ // `circuit`, `tableau`); `maj` runs `grillhouse-maj.mjs`, whose name already says the frame's;
8
+ // `hooks` (`crochets.mjs`, which places the git hooks) and `hook` (`crochet.mjs`, which runs one)
9
+ // take Husky's names, which a project's `prepare` and its git hooks call.
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const SCRIPTS = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ // Each subcommand and its script, under `scripts/`.
16
+ export const SOUS_COMMANDES = {
17
+ bilan: 'bilan.mjs',
18
+ circuit: 'circuit.mjs',
19
+ 'code-mort': 'code-mort.mjs',
20
+ commande: 'verrous/commande.mjs',
21
+ 'composants-affectes': 'composants-affectes.mjs',
22
+ consommateurs: 'consommateurs.mjs',
23
+ 'consommateurs-arbitres': 'consommateurs-arbitres.mjs',
24
+ droits: 'droits.mjs',
25
+ enveloppe: 'enveloppe/enveloppe.mjs',
26
+ file: 'integration/file.mjs',
27
+ frontieres: 'frontieres.mjs',
28
+ 'gardes-du-cadre': 'gardes-du-cadre.mjs',
29
+ greffons: 'greffons.mjs',
30
+ hook: 'crochet.mjs',
31
+ hooks: 'crochets.mjs',
32
+ 'index-interfaces': 'index-interfaces.mjs',
33
+ init: 'init.mjs',
34
+ integrer: 'integration/integrer.mjs',
35
+ 'interfaces-contre-code': 'interfaces-contre-code.mjs',
36
+ lancer: 'enveloppe/lancer.sh',
37
+ 'ligne-etat': 'ligne-etat/ligne-etat.mjs',
38
+ maj: 'grillhouse-maj.mjs',
39
+ nuit: 'nuit.sh',
40
+ pression: 'vitest-config/pression.mjs',
41
+ registre: 'registre.mjs',
42
+ resultats: 'resultats.mjs',
43
+ resumes: 'resumes.mjs',
44
+ 'retirer-copie': 'retirer-copie.sh',
45
+ 'session-start': 'session-start.sh',
46
+ sortie: 'sortie.mjs',
47
+ 'sous-modules': 'sous-modules.sh',
48
+ 'structure-guard': 'structure-guard.sh',
49
+ superviseur: 'superviseur.mjs',
50
+ tableau: 'tableau.mjs',
51
+ tests: 'tests.mjs',
52
+ ticket: 'ticket.sh',
53
+ 'ticket-du-sous-agent': 'verrous/ticket-du-sous-agent.mjs',
54
+ 'turbo-des-paquets': 'turbo-des-paquets.mjs',
55
+ verifier: 'verifier.mjs',
56
+ verrou: 'verrous/verrou.mjs',
57
+ };
58
+
59
+ // The absolute path of the script of the subcommand <nom>; null for a name the table lacks.
60
+ export function scriptDe(nom) {
61
+ return Object.hasOwn(SOUS_COMMANDES, nom) ? path.join(SCRIPTS, SOUS_COMMANDES[nom]) : null;
62
+ }
@@ -12,23 +12,19 @@
12
12
  // - Global: the work tickets of the project (a ticket without children; a mother is a grouping)
13
13
  // by state, the agents' working time and tokens of the day and in all, the alerts.
14
14
  // - Agents: the tree of the steps of the process (scripts/etapes.mjs), one block per step and
15
- // sub-step, with who works there, then the arbitrage of a stop, for the whole project; a step
16
- // that names a declaration (the regeneration of the references, 3.1) has its line only when the
17
- // project's package.json declares it (`grillhouse.references`). A ticket
18
- // stands at the step of every agent that runs on it, or waits in one place: at
19
- // the step of the next role, deduced from the roles of the flow that ran on it and the
20
- // reviewer's last verdict (`ACCEPTÉ` or `RENDU` at the start of a comment). A regulator waits for
21
- // nothing: it comes on a stop of the circuit, which its entry shows.
15
+ // sub-step, with who works there, for the whole project. A ticket stands at the step of every
16
+ // agent that runs on it, or waits in one place: at the step of the next role, deduced from the
17
+ // roles of the flow that ran on it and the reviewer's last verdict (`ACCEPTÉ` or `RENDU` at the
18
+ // start of a comment).
22
19
  // - Circuits: one entry per open ticket whose circuit (scripts/circuit.mjs) runs or stopped, read
23
20
  // in its state `.claude/worktrees/<ticket>.circuit.json`: it runs while the process
24
21
  // `npx grillhouse circuit <ticket>` runs, and stands at the step of its state, with its
25
- // component, its rank among the title's components and its attempt; while it runs a session of
26
- // the regulator on a stop (its state names the regulator's session, `regulateur`), while the
27
- // questionneur questions a decision of its regulator, or once it stopped, it stands at the
28
- // arbitrage of the stopped step; while the questionneur questions its plan (its state names the
29
- // questioning, `questionnement`, of the plan), it stands at the questioning of the plan; its
30
- // card gives the stop (its name and code) and the first line of its reason, or, ended on the
31
- // regulator's question (« question »), that question. A ticket with a circuit waits in no block.
22
+ // attempt, every step one for the whole ticket; while the explorateur or
23
+ // the questionneur works on its plan (its state names that work, `questionnement`, of the
24
+ // plan), it stands at the plan (1); stopped on a question for the supervisor (its state names it,
25
+ // `question`), it stands under the stopped step after a line « ⤷ Question · <qui> », who must
26
+ // answer it, the supervisor or the owner; its card opens on that question, or on the stop (its
27
+ // name and code) and the first line of its reason. A ticket with a circuit waits in no block.
32
28
  // - Bilans: the card of a ticket whose circuit kept its bilan (scripts/bilan.mjs,
33
29
  // `.claude/worktrees/<ticket>.bilan.json`) opens on its key figures: wall time, share lost,
34
30
  // sessions, cost.
@@ -49,8 +45,7 @@
49
45
  // A sub-agent, whatever its role, runs also while its last tool call awaits its answer, its
50
46
  // transcript having moved in the last 30 minutes; a session keeps the 2-minute rule alone. The
51
47
  // board reads no transcript older than 30 minutes: a shell agent whose transcript is older shows
52
- // by its process alone. A sub-agent whose prompt names no ticket takes that of the regulator
53
- // that launched it.
48
+ // by its process alone.
54
49
  // - Time: a transcript's working time is the union of its turns, each bounded by the earliest and
55
50
  // the latest date of its events, whatever their order in the file; a transcript is read where the
56
51
  // board stopped, its reading kept in the cache file. A ticket's working time adds the steps its
@@ -62,10 +57,10 @@
62
57
  // - Progress: the line of a ticket whose circuit runs an automatic step gives the progress of
63
58
  // its pass, read in the progress directory its state names (scripts/avancement.mjs), in
64
59
  // figures, as Bazel's counter of actions: « faits/prévus · <durée> » since the start of the
65
- // pass, after the counters of its loops, or in their place when room lacks (the subject or the
66
- // component is cut, never the figures); the survey of the components (1.1), the time since its
67
- // start; the queue (5.2), the steps of its CI. Past FIGEE without a sign,
68
- // « ⚠ figée <durée> · faits/prévus », the time since its last sign, in the tone of an alert.
60
+ // pass, after the counters of its loops, or in their place when room lacks (the subject is cut,
61
+ // never the figures); the queue (5.2), the steps of its CI. Past FIGEE
62
+ // without a sign, « ⚠ figée <durée> · faits/prévus », the time since its last sign, in the
63
+ // tone of an alert.
69
64
  // A measure out of a ticket that holds the lock of the machine (a supervisor's or an
70
65
  // integration's `npm run verifier`) stands at the head of AGENTS:
71
66
  // « ⏳ Mesure · <dépôt> · faits/prévus · <durée> », since its start,
@@ -89,18 +84,16 @@ import os from 'node:os';
89
84
  import path from 'node:path';
90
85
  import { pathToFileURL } from 'node:url';
91
86
  import {
92
- ARBITRAGE,
93
87
  AUTO,
94
88
  bouclesDe,
95
89
  ETAPES,
96
90
  etapeDuCircuit,
97
91
  etapeDuRole,
98
- gesteDe,
99
- juge,
100
92
  libelle,
93
+ QUESTIONNEUR,
101
94
  quiDe,
102
- REGULATEUR,
103
95
  SUITE,
96
+ SUPERVISEUR,
104
97
  } from './etapes.mjs';
105
98
  import { bilansGardes, enBref } from './bilan.mjs';
106
99
  import { verdictDe } from './integration/verdict.mjs';
@@ -114,7 +107,7 @@ export const LARGEUR = 52;
114
107
  // An automatic pass without a sign for this long is frozen: its line says so, in the tone of an
115
108
  // alert.
116
109
  export const FIGEE = 5 * MINUTE;
117
- // The room a circuit's line keeps for its subject or its component beside the counters of its
110
+ // The room a circuit's line keeps for its subject beside the counters of its
118
111
  // loops and the progress of its pass: below it, the progress takes the counters' place.
119
112
  const PLACE_DU_SUJET = 10;
120
113
  // The role of a register line that a step of a circuit writes (scripts/circuit.mjs): no agent,
@@ -476,34 +469,10 @@ const metaDe = f => {
476
469
  }
477
470
  };
478
471
 
479
- // The ticket a sub-agent takes from the regulator that launched it: the ticket its prompt names,
480
- // read from the regulator's transcript in the same folder; null when its parent is not a regulator
481
- // (by its spawned role) or names no ticket. With `depuis`, a regulator whose transcript has not
482
- // moved since that time (ms) is not read. `lire` reads a transcript (the board passes its cached
483
- // reader).
484
- export function ticketDuRegulateur(f, depuis = -Infinity, lire = lecteur({})) {
485
- const parent = metaDe(f).parentAgentId;
486
- if (!parent) {
487
- return null;
488
- }
489
- const g = path.join(path.dirname(f), `agent-${parent}.jsonl`);
490
- if (roleDuMeta(metaDe(g)) !== REGULATEUR) {
491
- return null;
492
- }
493
- try {
494
- if (statSync(g).mtimeMs < depuis) {
495
- return null;
496
- }
497
- return lire(g)?.ticket ?? null;
498
- } catch {
499
- return null;
500
- }
501
- }
502
-
503
472
  // The agents running now, each with its measures so far. `ps` is the process list
504
473
  // (`pid etimes args` per line); `cache` keeps the readings of unchanged transcripts. A role agent
505
474
  // launched in a shell shows by the envelope lancer.sh starts for it,
506
- // `enveloppe.mjs <racine>/.claude/worktrees/<ticket> <composant> <rôle> --`: its copy gives the
475
+ // `enveloppe.mjs <racine>/.claude/worktrees/<ticket> <paquets> <rôle> --`: its copy gives the
507
476
  // ticket, its third argument the role (the prompt goes on the standard input, absent from the line).
508
477
  export function lireVivant({ racine, home, prefix, maintenant, ps, cache = {} }) {
509
478
  const lire = lecteur(cache);
@@ -571,7 +540,7 @@ export function lireVivant({ racine, home, prefix, maintenant, ps, cache = {} })
571
540
  const meta = metaDe(f);
572
541
  vivants.push({
573
542
  agent: path.basename(f, '.jsonl').replace(/^agent-/, ''),
574
- ticket: lu.ticket ?? ticketDuRegulateur(f, limite, lire),
543
+ ticket: lu.ticket ?? null,
575
544
  role: roleDuMeta(meta),
576
545
  ligne: false,
577
546
  depuis: lu.premier ?? maintenant,
@@ -588,15 +557,12 @@ export function lireVivant({ racine, home, prefix, maintenant, ps, cache = {} })
588
557
  // The circuits of the repository at <racine>: each state `.claude/worktrees/<ticket>.circuit.json`
589
558
  // scripts/circuit.mjs writes, but those at « integre », with `vivant` when `ps` (`pid etimes args`
590
559
  // per line) shows its process, `grillhouse circuit <ticket>` (the command the supervisor
591
- // launches, `npx grillhouse circuit <ticket>`), `regulateur` when the state names a
592
- // session of the regulator, `questionnement` the object of the questioning under way (`plan` or
593
- // `decision`), null without one, `question` the
594
- // regulator's whole question for a circuit ended on it,
595
- // the counters of its bounded loops (`boucles`, by their key), and the rank of its component among the title's (`rang`, from 1) and their number (`total`),
596
- // the progress of the automatic pass under way (`avancement`, read in the directory its state
597
- // names, scripts/avancement.mjs: `{ faits, prevus, dernier }`, null without one) and the start of
598
- // the survey of the components under way (`releve`, ms, null without one). A state that does not
599
- // parse is left out.
560
+ // launches, `npx grillhouse circuit <ticket>`), `questionnement` the object of the work on the plan
561
+ // under way (`plan`), null without one, `question` the whole question of a circuit ended on one
562
+ // and `qui` who must answer it (`superviseur` or `responsable`), null without one,
563
+ // the counters of its bounded loops (`boucles`, by their key), the progress of the automatic pass under way (`avancement`, read in the directory its state
564
+ // names, scripts/avancement.mjs: `{ faits, prevus, dernier }`, null without one). A state that
565
+ // does not parse is left out.
600
566
  export function lireCircuits({ racine, prefix, ps }) {
601
567
  const dossier = path.join(racine, '.claude', 'worktrees');
602
568
  const motif = new RegExp(`grillhouse circuit (${prefix}-[A-Za-z0-9]+(?:\\.[0-9]+)*)(?:\\s|$)`);
@@ -623,37 +589,24 @@ export function lireCircuits({ racine, prefix, ps }) {
623
589
  out.push({
624
590
  ticket,
625
591
  etape: e.etape ?? null,
626
- composant: e.composant ?? null,
627
592
  tentative: e.tentative ?? null,
628
593
  finale: e.finale === true,
629
594
  code: e.code ?? null,
630
595
  arret: e.raison ?? null,
631
596
  raison: premiereLigne(e.rapport),
632
- question: e.raison === 'question' ? String(e.rapport ?? '').trim() || null : null,
633
- regulateur: Boolean(e.regulateur),
597
+ question: String(e.question?.question ?? '').trim() || null,
598
+ qui: e.question ? String(e.question.qui ?? SUPERVISEUR) : null,
634
599
  questionnement: e.questionnement?.objet ?? null,
635
- ...rangDe(e),
636
600
  boucles: e.boucles && typeof e.boucles === 'object' ? e.boucles : {},
637
601
  mene:
638
602
  typeof e.mene === 'string' && !Number.isNaN(Date.parse(e.mene)) ? Date.parse(e.mene) : null,
639
603
  avancement:
640
604
  typeof e.avancement?.dossier === 'string' ? lireAvancement(e.avancement.dossier) : null,
641
- releve:
642
- typeof e.releve === 'string' && !Number.isNaN(Date.parse(e.releve))
643
- ? Date.parse(e.releve)
644
- : null,
645
605
  vivant: enVie.has(ticket),
646
606
  });
647
607
  }
648
608
  return out;
649
609
  }
650
- // The rank of the state <e>'s component among the title's components, from 1, and their number;
651
- // both null when the state does not name them.
652
- const rangDe = e => {
653
- const l = Array.isArray(e.composants) ? e.composants : [];
654
- const i = l.indexOf(e.composant);
655
- return i < 0 ? { rang: null, total: null } : { rang: i + 1, total: l.length };
656
- };
657
610
  // The first line of a report that is not blank, or null.
658
611
  const premiereLigne = r =>
659
612
  String(r ?? '')
@@ -680,7 +633,7 @@ export function verdict(commentaires) {
680
633
  // and the reviewer's last verdict; null when it waits for none: an accepted lot waits for no
681
634
  // agent, the circuit integrates it (scripts/circuit.mjs).
682
635
  export function etape(passages, v) {
683
- const faits = passages.filter(p => etapeDuRole(p.role) && !juge(p.role));
636
+ const faits = passages.filter(p => etapeDuRole(p.role) && p.role !== QUESTIONNEUR);
684
637
  const dernier = faits[faits.length - 1];
685
638
  if (!dernier) {
686
639
  return 'testwriter';
@@ -730,7 +683,6 @@ export function assembler({
730
683
  maintenant,
731
684
  jour,
732
685
  horsTicket = null,
733
- declarations: lesDeclarations = [],
734
686
  }) {
735
687
  // A circuit's lines are its steps, no agent's passage.
736
688
  const etapesFinies = lignesDuRegistre.filter(r => r.role === ROLE_DU_CIRCUIT);
@@ -951,9 +903,8 @@ export function assembler({
951
903
  (passages.get(r.ticket) ?? passages.set(r.ticket, []).get(r.ticket)).push(r);
952
904
  }
953
905
  }
954
- // One block per step and sub-step, in the order of the steps, with who works there, then the
955
- // arbitrage's.
956
- const blocs = [...SUITE, ARBITRAGE].map(e => ({
906
+ // One block per step and sub-step, in the order of the steps, with who works there.
907
+ const blocs = SUITE.map(e => ({
957
908
  numero: e.numero,
958
909
  titre: e.titre,
959
910
  roles: e.qui.filter(r => r !== AUTO),
@@ -966,14 +917,10 @@ export function assembler({
966
917
  return e ? blocs.find(b => b.numero === e.numero) : undefined;
967
918
  };
968
919
  const places = new Set();
969
- // One line per ticket: a ticket on which a role agent runs stands at its step, and at the
970
- // regulator's only while none of its other agents runs.
971
- const auTravail = new Set(
972
- enCoursMesures.filter(v => v.role !== REGULATEUR && bloc(v.role)).map(v => v.ticket)
973
- );
920
+ // One line per ticket: a ticket on which a role agent runs stands at its step.
974
921
  for (const v of enCoursMesures) {
975
922
  const t = v.ticket && parId.get(v.ticket);
976
- if (!t || !bloc(v.role) || (v.role === REGULATEUR && auTravail.has(v.ticket))) {
923
+ if (!t || !bloc(v.role)) {
977
924
  continue;
978
925
  }
979
926
  places.add(t.id);
@@ -1101,18 +1048,9 @@ export function assembler({
1101
1048
  faitsDuJour,
1102
1049
  etats,
1103
1050
  parTicket,
1104
- declarations: lesDeclarations,
1105
1051
  };
1106
1052
  }
1107
1053
 
1108
- // The declarations of a project, the keys of `grillhouse` of its package.json <pkg> that hold
1109
- // something (a non-empty list, a value): a step that names one (`declaration`, scripts/etapes.mjs)
1110
- // shows only on a board that holds it.
1111
- export const declarations = pkg =>
1112
- Object.entries(pkg?.grillhouse ?? {})
1113
- .filter(([, v]) => (Array.isArray(v) ? v.length > 0 : Boolean(v)))
1114
- .map(([cle]) => cle);
1115
-
1116
1054
  export const k = n =>
1117
1055
  n >= 1e6
1118
1056
  ? `${(n / 1e6).toFixed(1).replace('.', ',')} M`
@@ -1134,12 +1072,11 @@ const jourEtMois = t => {
1134
1072
  // A time since a sign, « 8 s » under a minute, else as `duree`.
1135
1073
  const ilYa = ms => (ms < MINUTE ? `${Math.max(0, Math.round(ms / 1000))} s` : duree(ms));
1136
1074
 
1137
- // The witness of the automatic step <etape> of the running circuit <c> at <maintenant>: the
1138
- // progress of its pass (` · faits/prévus · <durée>`, the figures once a task wrote, the time since
1139
- // the start of the pass, as Bazel's counter), or the time since the start of its survey of the
1140
- // components (1.1); past FIGEE without a sign, ` · ⚠ figée <durée> · faits/prévus`, the time since
1141
- // the last sign, `fige`. Null without either.
1142
- function temoinDe(c, etape, maintenant) {
1075
+ // The witness of the automatic step of the running circuit <c> at <maintenant>: the progress of
1076
+ // its pass (` · faits/prévus · <durée>`, the figures once a task wrote, the time since the start
1077
+ // of the pass, as Bazel's counter); past FIGEE without a sign, ` · ⚠ figée <durée> ·
1078
+ // faits/prévus`, the time since the last sign, `fige`. Null without a progress.
1079
+ function temoinDe(c, maintenant) {
1143
1080
  let age;
1144
1081
  let ecoule;
1145
1082
  let compte = '';
@@ -1150,9 +1087,6 @@ function temoinDe(c, etape, maintenant) {
1150
1087
  if (faits || prevus) {
1151
1088
  compte = `${faits}/${prevus}`;
1152
1089
  }
1153
- } else if (etape.releve && c.releve !== null && c.releve !== undefined) {
1154
- age = maintenant - c.releve;
1155
- ecoule = age;
1156
1090
  } else {
1157
1091
  return null;
1158
1092
  }
@@ -1270,23 +1204,18 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1270
1204
  }
1271
1205
  // The tree of the steps (scripts/etapes.mjs), in its order, as a pipeline of GitLab CI shows its
1272
1206
  // stages and folds the jobs of a stage that has nothing: a step with sub-steps is folded
1273
- // (« ▸ 1 · Ouverture ») while none of them holds a ticket, unfolded (« ▾ ») once one does, all
1207
+ // (« ▸ 2 · Composants ») while none of them holds a ticket, unfolded (« ▾ ») once one does, all
1274
1208
  // its sub-steps then shown, indented, the empty ones in the discreet tone; a step without
1275
1209
  // sub-steps stands on one line « number · title · who », discreet when empty, a title with its
1276
1210
  // tickets under it. A circuit's ticket stands on one line, at the step of its state, and nowhere
1277
- // else in AGENTS: the questioning of the plan at its step; stopped, or while a session of the
1278
- // regulator runs on its stop or the questionneur questions his decision, under its step after a
1279
- // line « ⤷ <the arbitrage> · régulateur ⇄ questionneur », which unfolds the step: the
1280
- // arbitrage is out of the sequence, tied to the stopped step; a stop whose kind the table of the
1281
- // stops sends to the supervisor (gesture `superviseur`, scripts/etapes.mjs), which no regulator
1282
- // settles, stands there after a line « ⤷ Arrêt · superviseur » instead, its counters those of
1283
- // its step. A regulator out of any circuit
1284
- // stands under that line after the steps. A circuit's line gives its subject (`texte`) and, in
1285
- // turn (`alterne`, which the pane shows every other 5 s), the component under way, with its rank
1286
- // among the title's components when they are several; then the counters of the bounded loops of
1287
- // its step from their second pass (`boucles` of the step, scripts/etapes.mjs, their counts in
1288
- // the state's `boucles`), whole; then its ticket's working time and tokens. Its card says where
1289
- // it stopped and why, or the regulator's question. The tickets' numbers line up in one column.
1211
+ // else in AGENTS: the work on the plan at its step; stopped on a question for the
1212
+ // supervisor (its state's `question`), under its step after a line « ⤷ Question · <qui> », who
1213
+ // must answer it, the supervisor or the owner, which unfolds the step, its counters those of its
1214
+ // step; a stopped circuit at no step of the list stands under that line after the steps. A
1215
+ // circuit's line gives its subject (`texte`), then the counters of the bounded loops of its step
1216
+ // from their second pass (`boucles` of the step, scripts/etapes.mjs, their counts in the state's
1217
+ // `boucles`), whole; then its ticket's working time and tokens. Its card opens on
1218
+ // its question, or says where it stopped and why. The tickets' numbers line up in one column.
1290
1219
  const enCircuit = new Set((t.circuits ?? []).map(c => c.id));
1291
1220
  // The block of the step <e>, an empty one when the board holds none.
1292
1221
  const vides = new Map();
@@ -1302,18 +1231,14 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1302
1231
  attend: [],
1303
1232
  })
1304
1233
  .get(e.numero);
1305
- const arbitrage = blocDe(ARBITRAGE);
1234
+ // The block of the circuits at no step of the list, after the steps.
1235
+ const ailleurs = { numero: null, titre: '', qui: '', enCours: [], attend: [] };
1306
1236
  const etapeDe = c => etapeDuCircuit({ ...c, raison: c.arret });
1307
- const auSuperviseur = c =>
1308
- !c.vivant &&
1309
- !c.regulateur &&
1310
- c.questionnement !== 'decision' &&
1311
- gesteDe(c.arret)?.geste === 'superviseur';
1312
- const aLArbitrage = c =>
1313
- !auSuperviseur(c) && (!c.vivant || c.regulateur || c.questionnement === 'decision');
1237
+ // A circuit stopped on a question: who must answer it.
1238
+ const enQuestion = c => (!c.vivant && c.question ? (c.qui ?? SUPERVISEUR) : null);
1314
1239
  const blocDuCircuit = c => {
1315
1240
  const e = etapeDe(c);
1316
- return e ? blocDe(e) : aLArbitrage(c) ? arbitrage : undefined;
1241
+ return e ? blocDe(e) : !c.vivant ? ailleurs : undefined;
1317
1242
  };
1318
1243
  const montres = t.agents.flatMap(b =>
1319
1244
  [...b.enCours, ...b.attend].filter(x => !enCircuit.has(x.id))
@@ -1325,29 +1250,23 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1325
1250
  const numero = n => String(n).padEnd(colonne);
1326
1251
  const ligneDuCircuit = c => {
1327
1252
  const droite = conso(t.parTicket?.[c.id]?.travail ?? 0, t.parTicket?.[c.id]?.jetons ?? 0);
1328
- const etape = aLArbitrage(c) ? ARBITRAGE : etapeDe(c);
1253
+ const etape = etapeDe(c);
1329
1254
  const boucles = (etape ? bouclesDe(etape) : [])
1330
1255
  .filter(b => b.max && c.boucles?.[b.cle] >= 2)
1331
1256
  .map(b => ` · ${b.libelle} ${c.boucles[b.cle]}/${b.max}`)
1332
1257
  .join('');
1333
1258
  const tete = `${c.vivant ? '▶' : '■'} ${numero(c.numero)} `;
1334
- const composant = c.composant
1335
- ? `${c.composant === '.' ? '.' : path.basename(c.composant)}${c.total > 1 ? ` ${c.rang}/${c.total}` : ''}`
1336
- : null;
1337
- const temoin = c.vivant && etape?.qui.includes(AUTO) ? temoinDe(c, etape, maintenant) : null;
1338
- // The progress after the counters of the loops while the component (else the subject) keeps
1339
- // its room beside them, in their place otherwise.
1340
- const quoiCourt = composant ?? c.sujet;
1259
+ const temoin = c.vivant && etape?.qui.includes(AUTO) ? temoinDe(c, maintenant) : null;
1260
+ // The progress after the counters of the loops while the subject keeps its room beside them,
1261
+ // in their place otherwise.
1341
1262
  const reste = avec => largeur - (droite ? droite.length + 1 : 0) - tete.length - avec.length;
1342
1263
  const suite = !temoin
1343
1264
  ? boucles
1344
- : reste(`${boucles}${temoin.texte}`) >= Math.min(PLACE_DU_SUJET, quoiCourt.length)
1265
+ : reste(`${boucles}${temoin.texte}`) >= Math.min(PLACE_DU_SUJET, c.sujet.length)
1345
1266
  ? `${boucles}${temoin.texte}`
1346
1267
  : temoin.texte;
1347
- const texte = quoi => couperAvant(`${tete}${quoi}`, suite, droite, largeur);
1348
1268
  poser({
1349
- texte: texte(c.sujet),
1350
- ...(composant ? { alterne: texte(composant) } : {}),
1269
+ texte: couperAvant(`${tete}${c.sujet}`, suite, droite, largeur),
1351
1270
  ton: temoin?.fige ? 'alerte' : c.vivant ? 'actif' : 'attention',
1352
1271
  ticket: c.id,
1353
1272
  ...(temoin ? { avancement: true } : {}),
@@ -1355,28 +1274,28 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1355
1274
  };
1356
1275
  const contenu = b => {
1357
1276
  const tous = (t.circuits ?? []).filter(c => blocDuCircuit(c) === b);
1358
- const circuits = tous.filter(c => !aLArbitrage(c) && !auSuperviseur(c));
1359
- const arretes = tous.filter(aLArbitrage);
1360
- const auxSuperviseur = tous.filter(auSuperviseur);
1277
+ const circuits = tous.filter(c => !enQuestion(c));
1278
+ // The circuits on a question, by who must answer it, the supervisor first.
1279
+ const enQuestions = [SUPERVISEUR, ...new Set(tous.map(enQuestion).filter(Boolean))]
1280
+ .filter((qui, i, l) => l.indexOf(qui) === i)
1281
+ .map(qui => [qui, tous.filter(c => enQuestion(c) === qui)])
1282
+ .filter(([, l]) => l.length);
1361
1283
  const enCours = b.enCours.filter(x => !enCircuit.has(x.id));
1362
1284
  const attend = b.attend.filter(x => !enCircuit.has(x.id));
1363
1285
  return {
1364
1286
  circuits,
1365
- arretes,
1366
- auxSuperviseur,
1287
+ enQuestions,
1367
1288
  enCours,
1368
1289
  attend,
1369
1290
  vide: !tous.length && !enCours.length && !attend.length,
1370
1291
  };
1371
1292
  };
1372
- const contenus = new Map([...SUITE, ARBITRAGE].map(blocDe).map(b => [b, contenu(b)]));
1373
- const ligneDArbitrage = retrait =>
1374
- L(cadre(`${retrait}⤷ ${arbitrage.titre} · ${arbitrage.qui}`, '', largeur), 'titre');
1375
- // The tickets of the block <b>: its circuits that run, its agents, its waits, then the line of
1376
- // the arbitrage, indented by <retrait> (none when <retrait> is null), and its stopped circuits,
1377
- // then the line of the stops for the supervisor and theirs.
1293
+ const contenus = new Map([...SUITE.map(blocDe), ailleurs].map(b => [b, contenu(b)]));
1294
+ // The tickets of the block <b>: its circuits without a question, its agents, its waits, then
1295
+ // for each one who must answer, the line « ⤷ Question · <qui> », indented by <retrait>, and its
1296
+ // circuits.
1378
1297
  const tickets = (b, retrait) => {
1379
- const { circuits, arretes, auxSuperviseur, enCours, attend } = contenus.get(b);
1298
+ const { circuits, enQuestions, enCours, attend } = contenus.get(b);
1380
1299
  for (const c of circuits) {
1381
1300
  ligneDuCircuit(c);
1382
1301
  }
@@ -1397,17 +1316,9 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1397
1316
  ticket: x.id,
1398
1317
  });
1399
1318
  poser(...attend.map(attente));
1400
- if (arretes.length) {
1401
- if (retrait !== null) {
1402
- ligneDArbitrage(retrait);
1403
- }
1404
- for (const c of arretes) {
1405
- ligneDuCircuit(c);
1406
- }
1407
- }
1408
- if (auxSuperviseur.length) {
1409
- L(cadre(`${retrait ?? ''}⤷ Arrêt · superviseur`, '', largeur), 'titre');
1410
- for (const c of auxSuperviseur) {
1319
+ for (const [qui, l] of enQuestions) {
1320
+ L(cadre(`${retrait}⤷ Question · ${qui}`, '', largeur), 'titre');
1321
+ for (const c of l) {
1411
1322
  ligneDuCircuit(c);
1412
1323
  }
1413
1324
  }
@@ -1417,11 +1328,9 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1417
1328
  cadre(`${retrait}${b.numero} · ${b.titre}${b.qui ? ` · ${b.qui}` : ''}`, '', largeur),
1418
1329
  plein ? 'titre' : 'discret'
1419
1330
  );
1420
- // A sub-step that names a declaration the project does not hold has no line.
1421
- const declare = e => !e.declaration || (t.declarations ?? []).includes(e.declaration);
1422
1331
  for (const e of ETAPES) {
1423
1332
  const b = blocDe(e);
1424
- const lesSous = (e.sous ?? []).filter(declare);
1333
+ const lesSous = e.sous ?? [];
1425
1334
  if (!lesSous.length) {
1426
1335
  // Its number stands under those of the steps a fold mark opens.
1427
1336
  ligneDEtape(b, ' ');
@@ -1434,17 +1343,15 @@ export function lignes(t, nom, largeur = LARGEUR, groupes = {}) {
1434
1343
  if (!ouverte) {
1435
1344
  continue;
1436
1345
  }
1437
- tickets(b, '');
1346
+ tickets(b, ' ');
1438
1347
  for (const x of sous) {
1439
1348
  ligneDEtape(x, ' ');
1440
1349
  tickets(x, ' ');
1441
1350
  }
1442
1351
  }
1443
- // A regulator out of any circuit, or a stopped circuit at no step of the list: under the line of
1444
- // the arbitrage, after the steps.
1445
- if (!contenus.get(arbitrage).vide) {
1446
- ligneDArbitrage('');
1447
- tickets(arbitrage, null);
1352
+ // A stopped circuit at no step of the list, after the steps.
1353
+ if (!contenus.get(ailleurs).vide) {
1354
+ tickets(ailleurs, '');
1448
1355
  }
1449
1356
 
1450
1357
  section = 'chantier';
@@ -1628,19 +1535,15 @@ export function lignesEtats(t, largeur = LARGEUR) {
1628
1535
  return out;
1629
1536
  }
1630
1537
 
1631
- // Where the stopped circuit <c> is: its step by number and title, its attempt, the last part of its
1632
- // component's path (`.` for the root), and its stop (« interrompu » for a circuit dead without a
1633
- // stop written).
1538
+ // Where the stopped circuit <c> is: its step by number and title, its attempt, and its stop
1539
+ // (« interrompu » for a circuit dead without a stop written).
1634
1540
  const ouEnEst = c => {
1635
1541
  const e = etapeDuCircuit({ ...c, raison: c.arret });
1636
1542
  const ou = [e ? libelle(e) : c.etape, c.tentative ?? null]
1637
1543
  .filter(x => x !== null && x !== undefined)
1638
1544
  .join(' ');
1639
- const composant = c.composant
1640
- ? ` · ${c.composant === '.' ? '.' : path.basename(c.composant)}`
1641
- : '';
1642
1545
  const arret = c.code === null ? 'interrompu' : `arrêt ${c.arret ?? '?'} (${c.code})`;
1643
- return `${ou}${composant}, ${arret}`;
1546
+ return `${ou}, ${arret}`;
1644
1547
  };
1645
1548
 
1646
1549
  // The card of each ticket a line names: its number, whole title, component, state, its agents'
@@ -1668,10 +1571,11 @@ export function fiches(
1668
1571
  .map(x => x.trim())
1669
1572
  .filter(x => x && !x.startsWith('#'))
1670
1573
  .join(' ');
1671
- // A stopped circuit's card opens on the regulator's question, or on where it stopped and why.
1574
+ // A stopped circuit's card opens on its question and who must answer it, or on where it
1575
+ // stopped and why.
1672
1576
  const a = arretes.get(t.id);
1673
1577
  const tete = a?.question
1674
- ? `Question du régulateur : ${a.question.replace(/\s+/g, ' ')} `
1578
+ ? `Question · ${a.qui ?? SUPERVISEUR} : ${a.question.replace(/\s+/g, ' ')} `
1675
1579
  : a
1676
1580
  ? `Circuit arrêté en ${ouEnEst(a)} : ${a.raison ?? 'le circuit ne tourne plus'} `
1677
1581
  : '';
@@ -1745,15 +1649,6 @@ const bdJson = (racine, ...args) => {
1745
1649
  }
1746
1650
  };
1747
1651
 
1748
- // The package.json at the root of <racine>, or null.
1749
- const lirePaquet = racine => {
1750
- try {
1751
- return JSON.parse(readFileSync(path.join(racine, 'package.json'), 'utf8'));
1752
- } catch {
1753
- return null;
1754
- }
1755
- };
1756
-
1757
1652
  // The board of the repository at <racine>.
1758
1653
  export function tableau(racine, { home = os.homedir(), maintenant = Date.now() } = {}) {
1759
1654
  let prefix = '';
@@ -1781,7 +1676,7 @@ export function tableau(racine, { home = os.homedir(), maintenant = Date.now() }
1781
1676
  // The reviewer's verdict matters once a developer or a reviewer ran: only those tickets are read.
1782
1677
  const dernier = {};
1783
1678
  for (const r of [...registre].sort((x, y) => Date.parse(x.fin) - Date.parse(y.fin))) {
1784
- if (r.ticket && !juge(r.role) && r.role !== ROLE_DU_CIRCUIT) {
1679
+ if (r.ticket && r.role !== QUESTIONNEUR && r.role !== ROLE_DU_CIRCUIT) {
1785
1680
  dernier[r.ticket] = r.role;
1786
1681
  }
1787
1682
  }
@@ -1843,7 +1738,6 @@ export function tableau(racine, { home = os.homedir(), maintenant = Date.now() }
1843
1738
  maintenant,
1844
1739
  jour: minuit.getTime(),
1845
1740
  horsTicket: lireLaMesure(process.env),
1846
- declarations: declarations(lirePaquet(racine)),
1847
1741
  });
1848
1742
  // The project is named by its repository's folder, as a shell prompt names it.
1849
1743
  const nom = path.basename(racine);