@roomi-fields/grillhouse 0.0.0-stage → 0.23.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 (166) hide show
  1. package/.claude-plugin/plugin.json +9 -0
  2. package/METACADRE.md +41 -0
  3. package/agents/developpeur.md +10 -0
  4. package/agents/explorateur.md +9 -0
  5. package/agents/questionneur.md +11 -0
  6. package/agents/referent-technique.md +9 -0
  7. package/agents/regulateur.md +11 -0
  8. package/agents/relecteur.md +9 -0
  9. package/agents/testwriter.md +10 -0
  10. package/bin/grillhouse.mjs +125 -0
  11. package/cadre/templates/projet/.claude/settings.json +30 -0
  12. package/cadre/templates/projet/.editorconfig +47 -0
  13. package/cadre/templates/projet/.env.example +57 -0
  14. package/cadre/templates/projet/.eslintrc.cjs +56 -0
  15. package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
  16. package/cadre/templates/projet/.mcp.json +13 -0
  17. package/cadre/templates/projet/.prettierrc +11 -0
  18. package/cadre/templates/projet/CHANGELOG.md +61 -0
  19. package/cadre/templates/projet/CLAUDE.md +46 -0
  20. package/cadre/templates/projet/CONTRIBUTING.md +243 -0
  21. package/cadre/templates/projet/CREDITS.md +41 -0
  22. package/cadre/templates/projet/README.md +152 -0
  23. package/cadre/templates/projet/_gitignore +91 -0
  24. package/cadre/templates/projet/_npmignore +46 -0
  25. package/cadre/templates/projet/deployment/README.md +98 -0
  26. package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
  27. package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
  28. package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
  29. package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
  30. package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
  31. package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
  32. package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
  33. package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
  34. package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
  35. package/cadre/templates/projet/ecosystem.config.cjs +73 -0
  36. package/cadre/templates/projet/package.json +90 -0
  37. package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
  38. package/cadre/templates/projet/src/config.ts +68 -0
  39. package/cadre/templates/projet/src/errors.ts +102 -0
  40. package/cadre/templates/projet/src/index.ts +46 -0
  41. package/cadre/templates/projet/src/services/.gitkeep +8 -0
  42. package/cadre/templates/projet/src/types/index.ts +24 -0
  43. package/cadre/templates/projet/src/utils/logger.ts +106 -0
  44. package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
  45. package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
  46. package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
  47. package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
  48. package/cadre/templates/projet/tsconfig.eslint.json +9 -0
  49. package/cadre/templates/projet/tsconfig.json +41 -0
  50. package/cadre/templates/projet/tsdoc.json +6 -0
  51. package/cadre/templates/projet/vitest.config.ts +44 -0
  52. package/cadre/templates/turbo/paquets.json +29 -0
  53. package/cadre/templates/turbo/sans-paquets.json +28 -0
  54. package/charte.md +107 -0
  55. package/docs/ARCHITECTURE.md +90 -0
  56. package/hooks/crochet.mjs +33 -0
  57. package/hooks/hooks.json +58 -0
  58. package/hooks/register.tsx +558 -0
  59. package/package.json +75 -4
  60. package/scripts/avancement.mjs +244 -0
  61. package/scripts/bilan.mjs +523 -0
  62. package/scripts/branche-de-nuit.mjs +59 -0
  63. package/scripts/cadre.mjs +109 -0
  64. package/scripts/circuit.mjs +4375 -0
  65. package/scripts/code-mort.mjs +371 -0
  66. package/scripts/composants-affectes.mjs +598 -0
  67. package/scripts/consommateurs-arbitres.mjs +192 -0
  68. package/scripts/consommateurs.mjs +310 -0
  69. package/scripts/crochet.mjs +24 -0
  70. package/scripts/crochets.mjs +135 -0
  71. package/scripts/dernier-vert.mjs +208 -0
  72. package/scripts/dette.mjs +181 -0
  73. package/scripts/droits.mjs +354 -0
  74. package/scripts/enveloppe/codes.mjs +17 -0
  75. package/scripts/enveloppe/composant.mjs +11 -0
  76. package/scripts/enveloppe/enveloppe.mjs +628 -0
  77. package/scripts/enveloppe/lancer.sh +311 -0
  78. package/scripts/etapes.mjs +323 -0
  79. package/scripts/etat-du-circuit.mjs +104 -0
  80. package/scripts/frontieres.mjs +456 -0
  81. package/scripts/gardes-du-cadre.mjs +72 -0
  82. package/scripts/greffons.mjs +338 -0
  83. package/scripts/grillhouse-maj.mjs +514 -0
  84. package/scripts/index-interfaces.mjs +293 -0
  85. package/scripts/init.mjs +117 -0
  86. package/scripts/integration/file-attente.mjs +61 -0
  87. package/scripts/integration/file.mjs +494 -0
  88. package/scripts/integration/integrer.mjs +381 -0
  89. package/scripts/integration/rejeu.mjs +139 -0
  90. package/scripts/integration/verdict.mjs +13 -0
  91. package/scripts/interfaces-contre-code.mjs +547 -0
  92. package/scripts/ligne-etat/defaut.sh +110 -0
  93. package/scripts/ligne-etat/ligne-etat.mjs +93 -0
  94. package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
  95. package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
  96. package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
  97. package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
  98. package/scripts/migrations.mjs +43 -0
  99. package/scripts/nuit.sh +269 -0
  100. package/scripts/paquets.mjs +79 -0
  101. package/scripts/poussee-cles.mjs +46 -0
  102. package/scripts/preparation.mjs +84 -0
  103. package/scripts/references-par-les-types.mjs +420 -0
  104. package/scripts/registre.mjs +336 -0
  105. package/scripts/resultats.mjs +580 -0
  106. package/scripts/resumes.mjs +57 -0
  107. package/scripts/retirer-copie.sh +127 -0
  108. package/scripts/session-start.sh +203 -0
  109. package/scripts/sortie.mjs +155 -0
  110. package/scripts/sous-modules.sh +316 -0
  111. package/scripts/structure-guard.sh +27 -0
  112. package/scripts/superviseur.mjs +102 -0
  113. package/scripts/tableau.mjs +1886 -0
  114. package/scripts/tests.mjs +236 -0
  115. package/scripts/ticket.sh +23 -0
  116. package/scripts/turbo-des-paquets.mjs +899 -0
  117. package/scripts/un-commit-par-ticket.mjs +212 -0
  118. package/scripts/verifier.mjs +469 -0
  119. package/scripts/verrou-de-fichier.mjs +130 -0
  120. package/scripts/verrou-de-machine.mjs +211 -0
  121. package/scripts/verrous/commande.mjs +180 -0
  122. package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
  123. package/scripts/verrous/verrou.mjs +165 -0
  124. package/scripts/vitest-config/avancement.mjs +39 -0
  125. package/scripts/vitest-config/avancement.reporter.mjs +56 -0
  126. package/scripts/vitest-config/base-de-passe.mjs +128 -0
  127. package/scripts/vitest-config/base.d.ts +6 -0
  128. package/scripts/vitest-config/base.mjs +87 -0
  129. package/scripts/vitest-config/changements.mjs +173 -0
  130. package/scripts/vitest-config/cibles.mjs +207 -0
  131. package/scripts/vitest-config/duree.reporter.mjs +98 -0
  132. package/scripts/vitest-config/machine.setup.mjs +14 -0
  133. package/scripts/vitest-config/mesure.d.ts +6 -0
  134. package/scripts/vitest-config/mesure.mjs +13 -0
  135. package/scripts/vitest-config/mesure.setup.mjs +12 -0
  136. package/scripts/vitest-config/pression.mjs +90 -0
  137. package/scripts/vitest-config/resultats.mjs +23 -0
  138. package/scripts/vitest-config/resultats.setup.mjs +19 -0
  139. package/scripts/vitest.nuit.config.mjs +26 -0
  140. package/skills/developpeur/SKILL.md +175 -0
  141. package/skills/explorateur/SKILL.md +84 -0
  142. package/skills/grill/SKILL.md +238 -0
  143. package/skills/grill/references/releve-structurel.md +38 -0
  144. package/skills/mesure/SKILL.md +168 -0
  145. package/skills/pitmaster/SKILL.md +382 -0
  146. package/skills/pitmaster/references/cadre.md +119 -0
  147. package/skills/pitmaster/references/consigne-agent.md +308 -0
  148. package/skills/pitmaster/references/erreurs.md +25 -0
  149. package/skills/pitmaster/references/tour.md +124 -0
  150. package/skills/questionneur/SKILL.md +64 -0
  151. package/skills/redacteur/SKILL.md +84 -0
  152. package/skills/redacteur/references/documents-d-un-composant.md +105 -0
  153. package/skills/referent-technique/SKILL.md +145 -0
  154. package/skills/regulateur/SKILL.md +220 -0
  155. package/skills/release/SKILL.md +134 -0
  156. package/skills/release/scripts/update-version.cjs +92 -0
  157. package/skills/relecteur/SKILL.md +152 -0
  158. package/skills/relecteur/references/LICENSE-MIT +30 -0
  159. package/skills/relecteur/references/SOURCE.md +8 -0
  160. package/skills/relecteur/references/claims-check.md +14 -0
  161. package/skills/relecteur/references/deletion-check.md +14 -0
  162. package/skills/relecteur/references/edge-case-hunter.md +80 -0
  163. package/skills/relecteur/references/verification-gap.md +113 -0
  164. package/skills/testwriter/SKILL.md +187 -0
  165. package/types/index.d.ts +56 -0
  166. 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
+ }
@@ -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
+ }