@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.
- 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/sortie.mjs +1 -1
- 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
|
@@ -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
|
+
}
|
package/scripts/tableau.mjs
CHANGED
|
@@ -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,
|
|
16
|
-
// that
|
|
17
|
-
//
|
|
18
|
-
//
|
|
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
|
-
//
|
|
26
|
-
// the
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
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.
|
|
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
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
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
|
|
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> <
|
|
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 ??
|
|
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>`), `
|
|
592
|
-
//
|
|
593
|
-
// `
|
|
594
|
-
//
|
|
595
|
-
//
|
|
596
|
-
//
|
|
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:
|
|
633
|
-
|
|
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) &&
|
|
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
|
|
955
|
-
|
|
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
|
|
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)
|
|
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
|
|
1138
|
-
//
|
|
1139
|
-
//
|
|
1140
|
-
//
|
|
1141
|
-
|
|
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
|
-
// (« ▸
|
|
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
|
|
1278
|
-
//
|
|
1279
|
-
//
|
|
1280
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
// its
|
|
1284
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
1308
|
-
|
|
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) :
|
|
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 =
|
|
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
|
|
1335
|
-
|
|
1336
|
-
|
|
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,
|
|
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:
|
|
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 => !
|
|
1359
|
-
|
|
1360
|
-
const
|
|
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
|
-
|
|
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
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
//
|
|
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,
|
|
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
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
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 =
|
|
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
|
|
1444
|
-
|
|
1445
|
-
|
|
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,
|
|
1632
|
-
//
|
|
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}
|
|
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
|
|
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
|
|
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 &&
|
|
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);
|