@roomi-fields/grillhouse 0.0.0-stage → 0.23.1
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 +9 -0
- package/METACADRE.md +41 -0
- package/agents/developpeur.md +10 -0
- package/agents/explorateur.md +9 -0
- package/agents/questionneur.md +11 -0
- package/agents/referent-technique.md +9 -0
- package/agents/regulateur.md +11 -0
- package/agents/relecteur.md +9 -0
- package/agents/testwriter.md +10 -0
- package/bin/grillhouse.mjs +125 -0
- package/cadre/templates/projet/.claude/settings.json +30 -0
- package/cadre/templates/projet/.editorconfig +47 -0
- package/cadre/templates/projet/.env.example +57 -0
- package/cadre/templates/projet/.eslintrc.cjs +56 -0
- package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
- package/cadre/templates/projet/.mcp.json +13 -0
- package/cadre/templates/projet/.prettierrc +11 -0
- package/cadre/templates/projet/CHANGELOG.md +61 -0
- package/cadre/templates/projet/CLAUDE.md +46 -0
- package/cadre/templates/projet/CONTRIBUTING.md +243 -0
- package/cadre/templates/projet/CREDITS.md +41 -0
- package/cadre/templates/projet/README.md +152 -0
- package/cadre/templates/projet/_gitignore +91 -0
- package/cadre/templates/projet/_npmignore +46 -0
- package/cadre/templates/projet/deployment/README.md +98 -0
- package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
- package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
- package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
- package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
- package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
- package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
- package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
- package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
- package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
- package/cadre/templates/projet/ecosystem.config.cjs +73 -0
- package/cadre/templates/projet/package.json +90 -0
- package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
- package/cadre/templates/projet/src/config.ts +68 -0
- package/cadre/templates/projet/src/errors.ts +102 -0
- package/cadre/templates/projet/src/index.ts +46 -0
- package/cadre/templates/projet/src/services/.gitkeep +8 -0
- package/cadre/templates/projet/src/types/index.ts +24 -0
- package/cadre/templates/projet/src/utils/logger.ts +106 -0
- package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
- package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
- package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
- package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
- package/cadre/templates/projet/tsconfig.eslint.json +9 -0
- package/cadre/templates/projet/tsconfig.json +41 -0
- package/cadre/templates/projet/tsdoc.json +6 -0
- package/cadre/templates/projet/vitest.config.ts +44 -0
- package/cadre/templates/turbo/paquets.json +29 -0
- package/cadre/templates/turbo/sans-paquets.json +28 -0
- package/charte.md +107 -0
- package/docs/ARCHITECTURE.md +90 -0
- package/hooks/crochet.mjs +33 -0
- package/hooks/hooks.json +58 -0
- package/hooks/register.tsx +558 -0
- package/package.json +75 -4
- package/scripts/avancement.mjs +244 -0
- package/scripts/bilan.mjs +523 -0
- package/scripts/branche-de-nuit.mjs +59 -0
- package/scripts/cadre.mjs +109 -0
- package/scripts/circuit.mjs +4375 -0
- package/scripts/code-mort.mjs +371 -0
- package/scripts/composants-affectes.mjs +598 -0
- package/scripts/consommateurs-arbitres.mjs +192 -0
- package/scripts/consommateurs.mjs +310 -0
- package/scripts/crochet.mjs +24 -0
- package/scripts/crochets.mjs +135 -0
- package/scripts/dernier-vert.mjs +208 -0
- package/scripts/dette.mjs +181 -0
- package/scripts/droits.mjs +354 -0
- package/scripts/enveloppe/codes.mjs +17 -0
- package/scripts/enveloppe/composant.mjs +11 -0
- package/scripts/enveloppe/enveloppe.mjs +628 -0
- package/scripts/enveloppe/lancer.sh +311 -0
- package/scripts/etapes.mjs +323 -0
- package/scripts/etat-du-circuit.mjs +104 -0
- package/scripts/frontieres.mjs +456 -0
- package/scripts/gardes-du-cadre.mjs +72 -0
- package/scripts/greffons.mjs +338 -0
- package/scripts/grillhouse-maj.mjs +514 -0
- package/scripts/index-interfaces.mjs +293 -0
- package/scripts/init.mjs +117 -0
- package/scripts/integration/file-attente.mjs +61 -0
- package/scripts/integration/file.mjs +494 -0
- package/scripts/integration/integrer.mjs +381 -0
- package/scripts/integration/rejeu.mjs +139 -0
- package/scripts/integration/verdict.mjs +13 -0
- package/scripts/interfaces-contre-code.mjs +547 -0
- package/scripts/ligne-etat/defaut.sh +110 -0
- package/scripts/ligne-etat/ligne-etat.mjs +93 -0
- package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
- package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
- package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
- package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
- package/scripts/migrations.mjs +43 -0
- package/scripts/nuit.sh +269 -0
- package/scripts/paquets.mjs +79 -0
- package/scripts/poussee-cles.mjs +46 -0
- package/scripts/preparation.mjs +84 -0
- package/scripts/references-par-les-types.mjs +420 -0
- package/scripts/registre.mjs +336 -0
- package/scripts/resultats.mjs +580 -0
- package/scripts/resumes.mjs +57 -0
- package/scripts/retirer-copie.sh +127 -0
- package/scripts/session-start.sh +203 -0
- package/scripts/sortie.mjs +155 -0
- package/scripts/sous-modules.sh +316 -0
- package/scripts/structure-guard.sh +27 -0
- package/scripts/superviseur.mjs +102 -0
- package/scripts/tableau.mjs +1886 -0
- package/scripts/tests.mjs +236 -0
- package/scripts/ticket.sh +23 -0
- package/scripts/turbo-des-paquets.mjs +899 -0
- package/scripts/un-commit-par-ticket.mjs +212 -0
- package/scripts/verifier.mjs +469 -0
- package/scripts/verrou-de-fichier.mjs +130 -0
- package/scripts/verrou-de-machine.mjs +211 -0
- package/scripts/verrous/commande.mjs +180 -0
- package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
- package/scripts/verrous/verrou.mjs +165 -0
- package/scripts/vitest-config/avancement.mjs +39 -0
- package/scripts/vitest-config/avancement.reporter.mjs +56 -0
- package/scripts/vitest-config/base-de-passe.mjs +128 -0
- package/scripts/vitest-config/base.d.ts +6 -0
- package/scripts/vitest-config/base.mjs +87 -0
- package/scripts/vitest-config/changements.mjs +173 -0
- package/scripts/vitest-config/cibles.mjs +207 -0
- package/scripts/vitest-config/duree.reporter.mjs +98 -0
- package/scripts/vitest-config/machine.setup.mjs +14 -0
- package/scripts/vitest-config/mesure.d.ts +6 -0
- package/scripts/vitest-config/mesure.mjs +13 -0
- package/scripts/vitest-config/mesure.setup.mjs +12 -0
- package/scripts/vitest-config/pression.mjs +90 -0
- package/scripts/vitest-config/resultats.mjs +23 -0
- package/scripts/vitest-config/resultats.setup.mjs +19 -0
- package/scripts/vitest.nuit.config.mjs +26 -0
- package/skills/developpeur/SKILL.md +175 -0
- package/skills/explorateur/SKILL.md +84 -0
- package/skills/grill/SKILL.md +238 -0
- package/skills/grill/references/releve-structurel.md +38 -0
- package/skills/mesure/SKILL.md +168 -0
- package/skills/pitmaster/SKILL.md +382 -0
- package/skills/pitmaster/references/cadre.md +119 -0
- package/skills/pitmaster/references/consigne-agent.md +308 -0
- package/skills/pitmaster/references/erreurs.md +25 -0
- package/skills/pitmaster/references/tour.md +124 -0
- package/skills/questionneur/SKILL.md +64 -0
- package/skills/redacteur/SKILL.md +84 -0
- package/skills/redacteur/references/documents-d-un-composant.md +105 -0
- package/skills/referent-technique/SKILL.md +145 -0
- package/skills/regulateur/SKILL.md +220 -0
- package/skills/release/SKILL.md +134 -0
- package/skills/release/scripts/update-version.cjs +92 -0
- package/skills/relecteur/SKILL.md +152 -0
- package/skills/relecteur/references/LICENSE-MIT +30 -0
- package/skills/relecteur/references/SOURCE.md +8 -0
- package/skills/relecteur/references/claims-check.md +14 -0
- package/skills/relecteur/references/deletion-check.md +14 -0
- package/skills/relecteur/references/edge-case-hunter.md +80 -0
- package/skills/relecteur/references/verification-gap.md +113 -0
- package/skills/testwriter/SKILL.md +187 -0
- package/types/index.d.ts +56 -0
- package/README.md +0 -3
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The index of the interfaces: one line per component, then one line per element it provides,
|
|
3
|
+
// generated from the components' `docs/INTERFACE.md`. An agent loads it at start and opens an
|
|
4
|
+
// interface, then one of its sections, only when the index names it.
|
|
5
|
+
//
|
|
6
|
+
// npx grillhouse index-interfaces writes docs/agents/index-des-interfaces.md
|
|
7
|
+
// npx grillhouse index-interfaces --verifier refuses (exit 1) when that file is out of date,
|
|
8
|
+
// its finding in SARIF too (scripts/resultats.mjs)
|
|
9
|
+
// npx grillhouse index-interfaces --publie <composant>
|
|
10
|
+
// prints what the providers of <composant> publish
|
|
11
|
+
//
|
|
12
|
+
// The providers of a component are the components whose `## Consommateurs` section names it: the
|
|
13
|
+
// frame's one written and checked record of who uses whom (scripts/consommateurs.mjs). `--publie`
|
|
14
|
+
// is the first level of a progressive disclosure, as the skills of Claude Code, llms.txt or
|
|
15
|
+
// Aider's repository map: for each provider, its line, the names of the elements declared for the
|
|
16
|
+
// component (the only ones the check of the consumers lets it use) and the address of the rubric
|
|
17
|
+
// « Rend » of its `docs/CADRE.md`; the index of the interfaces is the second level, every element
|
|
18
|
+
// with its sentence; the interface is the third, the section of each element. The launcher
|
|
19
|
+
// (scripts/enveloppe/lancer.sh) writes the section `--publie` prints after the instruction of a role
|
|
20
|
+
// agent; the relecteur prints it by the same command.
|
|
21
|
+
//
|
|
22
|
+
// An interface is found at `packages/<x>/docs/INTERFACE.md` and `src/<x>/docs/INTERFACE.md` for
|
|
23
|
+
// each component of `composants` (a package out of the frame has none), and at
|
|
24
|
+
// `docs/INTERFACE.md`. Its title and first sentence give the component's line, headed by the title
|
|
25
|
+
// alone when the title already begins with the component's folder name (exact case, followed by a
|
|
26
|
+
// blank or ending the title), else by `<component> — <title>`; the root interface is always
|
|
27
|
+
// headed `(racine) — <title>`. Each `##` and `###` heading of the generated part (between the
|
|
28
|
+
// marks `<!-- généré: début -->` and `<!-- généré: fin -->`, written by `npm run api`), with the
|
|
29
|
+
// first sentence of its summary (the paragraph under it, before its signature and its rubrics
|
|
30
|
+
// `**Paramètres**`, `**Retour**`, `**Fautes**`, `**Membres**`, which the index never carries),
|
|
31
|
+
// gives an element's line; a heading outside the generated part gives
|
|
32
|
+
// none, and an interface without marks (the root one) gives no element line. Code blocks (``` or
|
|
33
|
+
// ~~~, indented or not) are skipped whole. The `## Consommateurs` section gives one line that
|
|
34
|
+
// names each consumer with the elements it uses.
|
|
35
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
36
|
+
import path from 'node:path';
|
|
37
|
+
import { pathToFileURL } from 'node:url';
|
|
38
|
+
import { composants, declares } from './consommateurs.mjs';
|
|
39
|
+
import { DEBUT, FIN, marques } from './interfaces-contre-code.mjs';
|
|
40
|
+
import { ecrireSarif } from './resultats.mjs';
|
|
41
|
+
|
|
42
|
+
export const SORTIE = 'docs/agents/index-des-interfaces.md';
|
|
43
|
+
|
|
44
|
+
// The interface files of the repository at <racine>, as paths relative to it, in a stable order:
|
|
45
|
+
// the interface of each component that `composants` gives, and the root interface.
|
|
46
|
+
export function interfaces(racine) {
|
|
47
|
+
const found = composants(racine)
|
|
48
|
+
.map(c => path.relative(racine, path.join(c.dir, 'docs', 'INTERFACE.md')))
|
|
49
|
+
.filter(rel => existsSync(path.join(racine, rel)));
|
|
50
|
+
if (existsSync(path.join(racine, 'docs', 'INTERFACE.md'))) found.push('docs/INTERFACE.md');
|
|
51
|
+
return found.sort();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// The heading of a component's line: the title as it is when it already begins with the component's
|
|
55
|
+
// name, exact case, followed by a blank or ending the title; else the name, a dash, the title.
|
|
56
|
+
const tetiere = (composant, titre) =>
|
|
57
|
+
composant !== '(racine)' &&
|
|
58
|
+
titre.startsWith(composant) &&
|
|
59
|
+
(titre.length === composant.length || /\s/.test(titre[composant.length]))
|
|
60
|
+
? titre
|
|
61
|
+
: `${composant} — ${titre}`;
|
|
62
|
+
|
|
63
|
+
const firstSentence = lines => {
|
|
64
|
+
const text = lines.join(' ').replace(/\s+/g, ' ').trim();
|
|
65
|
+
const m = /^(.+?[.!?])(\s|$)/.exec(text);
|
|
66
|
+
return m ? m[1] : text;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// One interface read once: its component, its heading, its first sentence, and its parts in their
|
|
70
|
+
// order, each an element of its generated part `{ titre, phrase }` or its declared consumers
|
|
71
|
+
// `{ consommateurs }` (Map consumer → Set of element names).
|
|
72
|
+
export function lire(rel, contenu) {
|
|
73
|
+
const lignes = contenu.split('\n');
|
|
74
|
+
const composant = rel === 'docs/INTERFACE.md' ? '(racine)' : rel.split('/')[1];
|
|
75
|
+
const titre = (lignes.find(l => l.startsWith('# ')) ?? `# ${composant}`).slice(2).trim();
|
|
76
|
+
const sections = [];
|
|
77
|
+
let courante = { niveau: 1, titre, corps: [] };
|
|
78
|
+
const fermer = () => sections.push(courante);
|
|
79
|
+
let cloture = null;
|
|
80
|
+
// The generated part is read where the check finds its marks; invalid marks give none.
|
|
81
|
+
const m0 = marques(contenu);
|
|
82
|
+
let generee = false;
|
|
83
|
+
for (const [i, l] of lignes.entries()) {
|
|
84
|
+
// A code block is skipped whole: a `## …` written inside it is code, not a heading.
|
|
85
|
+
const f = /^\s*(```|~~~)/.exec(l);
|
|
86
|
+
if (cloture) {
|
|
87
|
+
if (f && f[1] === cloture) cloture = null;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (f) {
|
|
91
|
+
cloture = f[1];
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (l.trim() === DEBUT || l.trim() === FIN) {
|
|
95
|
+
fermer();
|
|
96
|
+
generee = m0.etat === 'ok' && i === m0.debut;
|
|
97
|
+
courante = { niveau: 2, titre: '', corps: [], generee, ignoree: true };
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const m = /^(#{1,3}) (.+)$/.exec(l);
|
|
101
|
+
if (m) {
|
|
102
|
+
if (m[1].length === 1) continue;
|
|
103
|
+
fermer();
|
|
104
|
+
courante = { niveau: m[1].length, titre: m[2].trim(), corps: [], generee };
|
|
105
|
+
} else if (courante.generee && (courante.fermee || /^\*\*[^*]+\*\*/.test(l))) {
|
|
106
|
+
// An element of the generated part gives its summary only: its rubrics (`**Membres**`,
|
|
107
|
+
// `**Paramètres**`…) are read at its address.
|
|
108
|
+
courante.fermee = true;
|
|
109
|
+
} else if (!l.trim()) {
|
|
110
|
+
if (courante.generee && courante.corps.length) courante.fermee = true;
|
|
111
|
+
} else if (!l.startsWith('|')) {
|
|
112
|
+
if (courante.corps.length < 6 || courante.titre === 'Consommateurs')
|
|
113
|
+
courante.corps.push(l.replace(/^[-*]\s+/, ''));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
fermer();
|
|
117
|
+
const [tete, ...reste] = sections;
|
|
118
|
+
const parties = [];
|
|
119
|
+
for (const s of reste) {
|
|
120
|
+
if (s.ignoree) continue;
|
|
121
|
+
if (s.niveau === 2 && s.titre === 'Consommateurs') {
|
|
122
|
+
parties.push({
|
|
123
|
+
consommateurs: declares(`## Consommateurs\n${s.corps.map(l => `- ${l}`).join('\n')}`),
|
|
124
|
+
});
|
|
125
|
+
} else if (s.generee) {
|
|
126
|
+
parties.push({ titre: s.titre, phrase: firstSentence(s.corps) });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
rel,
|
|
131
|
+
composant,
|
|
132
|
+
tetiere: tetiere(composant, tete.titre),
|
|
133
|
+
phrase: firstSentence(tete.corps),
|
|
134
|
+
parties,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The index lines of one interface: its component line, then one line per heading.
|
|
139
|
+
export function entrees(rel, contenu) {
|
|
140
|
+
const { tetiere: t, phrase, parties } = lire(rel, contenu);
|
|
141
|
+
const out = [`## ${t}`, '', `${phrase} (\`${rel}\`)`, ''];
|
|
142
|
+
for (const p of parties) {
|
|
143
|
+
if (p.consommateurs) {
|
|
144
|
+
const liste = [...p.consommateurs]
|
|
145
|
+
.map(([qui, noms]) => `${qui} (${[...noms].join(', ')})`)
|
|
146
|
+
.join(' ; ');
|
|
147
|
+
out.push(`- **Consommateurs** — ${liste || 'aucun'}`);
|
|
148
|
+
} else {
|
|
149
|
+
out.push(`- **${p.titre}**${p.phrase ? ` — ${p.phrase}` : ''}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
out.push('');
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const TITRE_PUBLIE = "## Ce que les composants d'avant publient";
|
|
157
|
+
|
|
158
|
+
// The rubric « Rend » of a component's frame (`docs/CADRE.md`, next to its interface), read once:
|
|
159
|
+
// `{ titre }`, its `## Rend` heading, numbered or not (`## 3. Rend`), outside a code block;
|
|
160
|
+
// `{ absent: true }` without the file, null without the rubric.
|
|
161
|
+
function rend(racine, rel) {
|
|
162
|
+
const fichier = path.join(racine, rel);
|
|
163
|
+
if (!existsSync(fichier)) return { absent: true };
|
|
164
|
+
let cloture = null;
|
|
165
|
+
for (const l of readFileSync(fichier, 'utf8').split('\n')) {
|
|
166
|
+
const f = /^\s*(```|~~~)/.exec(l);
|
|
167
|
+
if (cloture) {
|
|
168
|
+
if (f && f[1] === cloture) cloture = null;
|
|
169
|
+
} else if (f) {
|
|
170
|
+
cloture = f[1];
|
|
171
|
+
} else {
|
|
172
|
+
const m = /^## ((?:\d+\.\s*)?Rend)\s*$/.exec(l);
|
|
173
|
+
if (m) return { titre: m[1] };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// What the components before <composant> publish, as the section the launcher writes after the
|
|
180
|
+
// instruction of a role agent (scripts/enveloppe/lancer.sh). <composant> is the component's path
|
|
181
|
+
// in the repository at <racine> (`packages/a`, `src/parser`), or `.` for a copy of the whole
|
|
182
|
+
// repository, which the section points to the index of the interfaces. Its providers are the
|
|
183
|
+
// components whose `## Consommateurs` section names it; for each, its line in the index of the
|
|
184
|
+
// interfaces (its heading, its first sentence, its interface), the names of the elements its
|
|
185
|
+
// `## Consommateurs` line declares for the component, never their sentence, which the index of the
|
|
186
|
+
// interfaces holds, and the address of the rubric « Rend » of its frame (`<cadre> § <heading>`),
|
|
187
|
+
// or one line saying the frame or the rubric is missing. A declaration of `*`, an import of the
|
|
188
|
+
// whole component, names no element: its line says the elements are in the index. Each interface
|
|
189
|
+
// and each frame is read once.
|
|
190
|
+
export function publie(racine, composant) {
|
|
191
|
+
if (composant === '.') {
|
|
192
|
+
return [
|
|
193
|
+
TITRE_PUBLIE,
|
|
194
|
+
'',
|
|
195
|
+
`Ta copie montre tout le dépôt : \`${SORTIE}\` porte ce que publie chaque composant.`,
|
|
196
|
+
].join('\n');
|
|
197
|
+
}
|
|
198
|
+
const nom = path.basename(composant);
|
|
199
|
+
const fournisseurs = interfaces(racine)
|
|
200
|
+
.map(rel => lire(rel, readFileSync(path.join(racine, rel), 'utf8')))
|
|
201
|
+
.flatMap(i => {
|
|
202
|
+
const declare = i.parties.find(p => p.consommateurs?.has(nom));
|
|
203
|
+
return declare && i.composant !== nom
|
|
204
|
+
? [{ ...i, declare: declare.consommateurs.get(nom) }]
|
|
205
|
+
: [];
|
|
206
|
+
});
|
|
207
|
+
if (fournisseurs.length === 0) {
|
|
208
|
+
return [
|
|
209
|
+
TITRE_PUBLIE,
|
|
210
|
+
'',
|
|
211
|
+
`\`${nom}\` ne dépend d’aucun composant : aucune section « Consommateurs » ne le nomme.`,
|
|
212
|
+
].join('\n');
|
|
213
|
+
}
|
|
214
|
+
const out = [
|
|
215
|
+
TITRE_PUBLIE,
|
|
216
|
+
'',
|
|
217
|
+
`\`${nom}\` dépend de ces composants, et n’utilise d’eux que les éléments déclarés pour lui. ` +
|
|
218
|
+
`Tous les éléments d’un composant, chacun avec sa phrase, sont dans \`${SORTIE}\` ; ` +
|
|
219
|
+
'un élément se lit en entier dans l’interface de son composant, à `<interface> § <son nom>`.',
|
|
220
|
+
];
|
|
221
|
+
const dansIndex = `ses éléments sont dans \`${SORTIE}\``;
|
|
222
|
+
for (const f of fournisseurs) {
|
|
223
|
+
const noms = [...f.declare].filter(n => n !== '*').join(', ');
|
|
224
|
+
let declare = `${noms}.`;
|
|
225
|
+
if (f.declare.has('*')) {
|
|
226
|
+
declare = noms
|
|
227
|
+
? `${noms}, et \`*\`, un import de tout le composant : ${dansIndex}.`
|
|
228
|
+
: `\`*\`, un import de tout le composant, sans élément nommé : ${dansIndex}.`;
|
|
229
|
+
}
|
|
230
|
+
out.push('', `### ${f.tetiere}`, '', `${f.phrase} (\`${f.rel}\`)`);
|
|
231
|
+
out.push(`Déclaré pour \`${nom}\` : ${declare}`);
|
|
232
|
+
if (!f.parties.some(p => !p.consommateurs)) {
|
|
233
|
+
out.push('Son interface n’a pas de part générée : elle dit elle-même ce qu’elle publie.');
|
|
234
|
+
}
|
|
235
|
+
const cadre = path.join(path.dirname(f.rel), 'CADRE.md');
|
|
236
|
+
const r = rend(racine, cadre);
|
|
237
|
+
if (r?.absent) out.push(`Son cadre manque : \`${cadre}\` n’existe pas.`);
|
|
238
|
+
else if (!r) out.push(`Son cadre n’a pas de rubrique « Rend » (\`${cadre}\`).`);
|
|
239
|
+
else out.push(`Son cadre rend : \`${cadre}\` § ${r.titre}.`);
|
|
240
|
+
}
|
|
241
|
+
return out.join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function index(racine) {
|
|
245
|
+
const corps = interfaces(racine).flatMap(rel =>
|
|
246
|
+
entrees(rel, readFileSync(path.join(racine, rel), 'utf8'))
|
|
247
|
+
);
|
|
248
|
+
return [
|
|
249
|
+
'# Index des interfaces',
|
|
250
|
+
'',
|
|
251
|
+
'Généré par `npx grillhouse index-interfaces` depuis les `INTERFACE.md` ; ne pas éditer.',
|
|
252
|
+
"Une ligne par composant, puis une par élément qu'il fournit ; l'interface donne le détail.",
|
|
253
|
+
'',
|
|
254
|
+
...corps,
|
|
255
|
+
].join('\n');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// The command: its exit code. Its output is written before it returns, and Node drains it.
|
|
259
|
+
function commande(racine, args) {
|
|
260
|
+
const i = args.indexOf('--publie');
|
|
261
|
+
if (i >= 0) {
|
|
262
|
+
const composant = args[i + 1];
|
|
263
|
+
if (!composant) {
|
|
264
|
+
process.stderr.write('usage: npx grillhouse index-interfaces --publie <composant>\n');
|
|
265
|
+
return 2;
|
|
266
|
+
}
|
|
267
|
+
process.stdout.write(`${publie(racine, composant)}\n`);
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
const attendu = index(racine);
|
|
271
|
+
const cible = path.join(racine, SORTIE);
|
|
272
|
+
if (!args.includes('--verifier')) {
|
|
273
|
+
writeFileSync(cible, attendu);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
const present = existsSync(cible) ? readFileSync(cible, 'utf8') : '';
|
|
277
|
+
const refus =
|
|
278
|
+
present === attendu
|
|
279
|
+
? []
|
|
280
|
+
: [
|
|
281
|
+
{
|
|
282
|
+
fichier: SORTIE,
|
|
283
|
+
message: `${SORTIE} n'est pas à jour avec les interfaces : npx grillhouse index-interfaces le régénère.`,
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
for (const r of refus) process.stderr.write(`⛔ ${r.message}\n`);
|
|
287
|
+
ecrireSarif({ garde: 'index-interfaces', racine, constats: refus });
|
|
288
|
+
return refus.length ? 1 : 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
292
|
+
process.exitCode = commande(process.cwd(), process.argv.slice(2));
|
|
293
|
+
}
|
package/scripts/init.mjs
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// A new project, created from the frame's template of a project, as create-turbo creates one from
|
|
3
|
+
// its example. The first Claude session opened in it proposes the initialisation grill.
|
|
4
|
+
//
|
|
5
|
+
// npx @roomi-fields/grillhouse init <dossier> [<préfixe des tickets>]
|
|
6
|
+
//
|
|
7
|
+
// - The directory is created, or must be empty: a directory that holds a file is refused (exit 1)
|
|
8
|
+
// and nothing is written in it.
|
|
9
|
+
// - The project receives each file of the template (`templates/projet/` of the frame's files,
|
|
10
|
+
// scripts/cadre.mjs) at its own path; `_gitignore` and `_npmignore` become `.gitignore` and
|
|
11
|
+
// `.npmignore`, the names npm neither publishes nor reads in a package, as create-vite names its
|
|
12
|
+
// templates' files.
|
|
13
|
+
// - Its package.json declares the frame's package, `@roomi-fields/grillhouse` at `^<version>` of
|
|
14
|
+
// this package, in its devDependencies.
|
|
15
|
+
// - The preparation (scripts/preparation.mjs): git, Beads and its ticket store under the prefix
|
|
16
|
+
// given, else the directory's name, CodeGraph and its index, the frame's git hooks, `npm install`,
|
|
17
|
+
// then the plugins its settings declare: the template's declare the frame's,
|
|
18
|
+
// `grillhouse@roomi-fields`, whose marketplace names the package npm.
|
|
19
|
+
// - Then what the frame writes in a project, as `grillhouse maj` writes it (scripts/grillhouse-maj.mjs):
|
|
20
|
+
// its turbo.json, the status line of its settings, the version of the frame; and the generated
|
|
21
|
+
// files (the boundaries' configuration, the interfaces index, run by the project's command). A
|
|
22
|
+
// plugin or a generator that fails has its line, and the exit code is non-zero once the project
|
|
23
|
+
// is created.
|
|
24
|
+
// - The project ends on one commit, « chore: initial commit from Roomi's Grillhouse ».
|
|
25
|
+
import { execFileSync } from 'node:child_process';
|
|
26
|
+
import {
|
|
27
|
+
cpSync,
|
|
28
|
+
existsSync,
|
|
29
|
+
mkdirSync,
|
|
30
|
+
readdirSync,
|
|
31
|
+
readFileSync,
|
|
32
|
+
renameSync,
|
|
33
|
+
writeFileSync,
|
|
34
|
+
} from 'node:fs';
|
|
35
|
+
import path from 'node:path';
|
|
36
|
+
import { pathToFileURL } from 'node:url';
|
|
37
|
+
import { CADRE, VERSION } from './cadre.mjs';
|
|
38
|
+
import { mettreAJour, regenerer } from './grillhouse-maj.mjs';
|
|
39
|
+
import { preparer } from './preparation.mjs';
|
|
40
|
+
|
|
41
|
+
/** The frame's package, as a project declares it. */
|
|
42
|
+
export const PAQUET_DU_CADRE = '@roomi-fields/grillhouse';
|
|
43
|
+
// The template's files whose name the project receives changed.
|
|
44
|
+
const RENOMMES = { _gitignore: '.gitignore', _npmignore: '.npmignore' };
|
|
45
|
+
|
|
46
|
+
// Runs <commande> in <projet>, its output this process's; throws on a failure.
|
|
47
|
+
const lancer = (projet, commande, args) =>
|
|
48
|
+
execFileSync(commande, args, { cwd: projet, stdio: 'inherit' });
|
|
49
|
+
|
|
50
|
+
// Copies the template of a project into <projet>, its files renamed (RENOMMES).
|
|
51
|
+
function copierLeGabarit(projet) {
|
|
52
|
+
const gabarit = path.join(CADRE, 'templates', 'projet');
|
|
53
|
+
cpSync(gabarit, projet, { recursive: true });
|
|
54
|
+
const renommer = dossier => {
|
|
55
|
+
for (const e of readdirSync(dossier, { withFileTypes: true })) {
|
|
56
|
+
const f = path.join(dossier, e.name);
|
|
57
|
+
if (e.isDirectory()) renommer(f);
|
|
58
|
+
else if (Object.hasOwn(RENOMMES, e.name)) renameSync(f, path.join(dossier, RENOMMES[e.name]));
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
renommer(projet);
|
|
62
|
+
const fichier = path.join(projet, 'package.json');
|
|
63
|
+
const p = JSON.parse(readFileSync(fichier, 'utf8'));
|
|
64
|
+
p.devDependencies = Object.fromEntries(
|
|
65
|
+
Object.entries({ ...(p.devDependencies ?? {}), [PAQUET_DU_CADRE]: `^${VERSION}` }).sort(
|
|
66
|
+
([a], [b]) => a.localeCompare(b, 'en')
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
writeFileSync(fichier, `${JSON.stringify(p, null, 2)}\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Creates the project at <dossier>; returns the lines of failure once it is created. */
|
|
73
|
+
export function initialiser(dossier, prefixe) {
|
|
74
|
+
const projet = path.resolve(dossier);
|
|
75
|
+
if (existsSync(projet) && readdirSync(projet).length > 0) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`${dossier} n'est pas vide : grillhouse init crée un projet neuf. Un projet existant adopte le cadre par npm install -D ${PAQUET_DU_CADRE}, puis npx grillhouse maj.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
mkdirSync(projet, { recursive: true });
|
|
81
|
+
copierLeGabarit(projet);
|
|
82
|
+
const greffons = preparer(projet, prefixe);
|
|
83
|
+
mettreAJour(projet, CADRE);
|
|
84
|
+
const lignes = [...greffons, ...regenerer(projet, { tous: true })];
|
|
85
|
+
lancer(projet, 'git', ['add', '-A']);
|
|
86
|
+
// The initial commit carries no ticket: the frame's commit-msg hook has nothing to judge.
|
|
87
|
+
lancer(projet, 'git', [
|
|
88
|
+
'commit',
|
|
89
|
+
'-q',
|
|
90
|
+
'--no-verify',
|
|
91
|
+
'-m',
|
|
92
|
+
"chore: initial commit from Roomi's Grillhouse",
|
|
93
|
+
]);
|
|
94
|
+
return lignes;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
98
|
+
const [dossier, prefixe, ...reste] = process.argv.slice(2);
|
|
99
|
+
if (!dossier || dossier.startsWith('-') || reste.length > 0) {
|
|
100
|
+
process.stderr.write(
|
|
101
|
+
'Usage : npx @roomi-fields/grillhouse init <dossier> [<préfixe des tickets>]\n'
|
|
102
|
+
);
|
|
103
|
+
process.exitCode = 2;
|
|
104
|
+
} else {
|
|
105
|
+
try {
|
|
106
|
+
const lignes = initialiser(dossier, prefixe);
|
|
107
|
+
for (const l of lignes) process.stderr.write(`${l}\n`);
|
|
108
|
+
process.stdout.write(
|
|
109
|
+
`Projet créé dans ${dossier}. Ouvre Claude Code dans ce dossier : la première séance propose le grill d'initialisation.\n`
|
|
110
|
+
);
|
|
111
|
+
if (lignes.length > 0) process.exitCode = 1;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
process.stderr.write(`⛔ ${e.message}\n`);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The waiting side of the integration, kept in git's common directory: the queue's entries, and
|
|
2
|
+
// the git directory the integration's locks live in (taken through scripts/verrou-de-fichier.mjs).
|
|
3
|
+
// The integration writes an entry when it commits a ticket on its branch; the queue
|
|
4
|
+
// (scripts/integration/file.mjs) reads them in their order of arrival.
|
|
5
|
+
//
|
|
6
|
+
// - An entry is one JSON file `<arrival>-<process>.json` under `file-integration/` holding the
|
|
7
|
+
// branch and its one ticket; the name sorts in the order of arrival. One branch has one
|
|
8
|
+
// entry: writing it again replaces the earlier one.
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
|
|
13
|
+
// The git common directory of the repository at <racine>, absolute.
|
|
14
|
+
export function dossierCommun(racine) {
|
|
15
|
+
return spawnSync(
|
|
16
|
+
'git',
|
|
17
|
+
['-C', racine, 'rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
18
|
+
{ encoding: 'utf8' }
|
|
19
|
+
).stdout.trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const dossierEntrees = racine => path.join(dossierCommun(racine), 'file-integration');
|
|
23
|
+
|
|
24
|
+
// The entries waiting, in their order of arrival: { fichier, branche, ticket }.
|
|
25
|
+
export function lireEntrees(racine) {
|
|
26
|
+
const dossier = dossierEntrees(racine);
|
|
27
|
+
let noms = [];
|
|
28
|
+
try {
|
|
29
|
+
noms = readdirSync(dossier)
|
|
30
|
+
.filter(n => n.endsWith('.json'))
|
|
31
|
+
.sort();
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const entrees = [];
|
|
36
|
+
for (const n of noms) {
|
|
37
|
+
try {
|
|
38
|
+
const e = JSON.parse(readFileSync(path.join(dossier, n), 'utf8'));
|
|
39
|
+
entrees.push({ fichier: path.join(dossier, n), branche: e.branche, ticket: e.ticket });
|
|
40
|
+
} catch {
|
|
41
|
+
// an entry being written is read at the next turn
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return entrees;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Puts the branch at the end of the queue, replacing its earlier entry.
|
|
48
|
+
export function inscrireEntree(racine, branche, ticket) {
|
|
49
|
+
for (const e of lireEntrees(racine)) {
|
|
50
|
+
if (e.branche === branche) rmSync(e.fichier, { force: true });
|
|
51
|
+
}
|
|
52
|
+
const dossier = dossierEntrees(racine);
|
|
53
|
+
mkdirSync(dossier, { recursive: true });
|
|
54
|
+
const arrivee = String(Date.now()).padStart(15, '0');
|
|
55
|
+
const fichier = path.join(dossier, `${arrivee}-${String(process.pid).padStart(8, '0')}.json`);
|
|
56
|
+
writeFileSync(fichier, JSON.stringify({ branche, ticket }), { flag: 'wx' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function retirerEntree(entree) {
|
|
60
|
+
rmSync(entree.fichier, { force: true });
|
|
61
|
+
}
|