@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,514 @@
1
+ #!/usr/bin/env node
2
+ // The frame's update: brings what the frame writes in a project to the version of the package
3
+ // installed, as `npx @turbo/codemod migrate` brings a repository to the version of Turborepo.
4
+ //
5
+ // npx grillhouse maj update (npm run grillhouse:maj), after `npm install` of the version wanted
6
+ //
7
+ // - The versions pass by npm: the project installs the version of @roomi-fields/grillhouse it wants
8
+ // (`npm install -D @roomi-fields/grillhouse@<version>`), then the update brings the project to it.
9
+ // What the update reads of the frame is the package's own (scripts/cadre.mjs): the template of a
10
+ // project (`templates/projet/package.json` for the frame's scripts and tools,
11
+ // `templates/turbo/*.json` for a turbo.json). Any argument is refused: the usage is shown (the
12
+ // exit code is non-zero) and nothing is updated, `--help` included.
13
+ // - The migrations first (scripts/migrations.mjs): each migration of the package newer than the
14
+ // version the project recorded (`grillhouse.version` of its package.json; none recorded, every
15
+ // migration), in the order of their versions; each is replayable without effect, and the report
16
+ // names each one that changed something, with what it changed.
17
+ // - The frame copies no file into a project: its scripts are the package's, its skills, agents,
18
+ // hooks of Claude Code, common charter and meta-frame the plugin's, which the package carries
19
+ // (scripts/cadre.mjs): the frame arrives in one piece, at the version npm installs.
20
+ // - The frame's plugin is a setting of the project: in a project whose `.claude/settings.json`
21
+ // exists, `extraKnownMarketplaces` declares the marketplace `roomi-fields` (GitHub,
22
+ // `roomi-fields/claude-plugins`), which names the package npm as the plugin's source, and
23
+ // `enabledPlugins` enables its plugin, `grillhouse@roomi-fields`, each added after the keys the
24
+ // settings hold, which keep their order. What is a project's own (its charter, its settings, its
25
+ // version of the package) lives in the project, never in what is published: the machine knows a
26
+ // marketplace by its name, once for every project. A migration takes off the marketplace of the
27
+ // project's own an earlier update wrote (scripts/migrations/0.23.0-marche-du-projet.mjs).
28
+ // - The status line is a setting of the project, as Husky writes the project's own configuration:
29
+ // in a project whose `.claude/settings.json` exists, `statusLine` becomes the frame's
30
+ // (`"$CLAUDE_PROJECT_DIR/node_modules/.bin/grillhouse" ligne-etat`) when it is absent or names
31
+ // the frame's status line (`ligne-etat`, whatever its former form); a status line of the
32
+ // project's own stays. The settings keep their other keys, in their order.
33
+ // - `package.json`, when there is one: the version of the frame it is brought to
34
+ // (`grillhouse.version`, the package's version), the frame's npm scripts (`tableau`,
35
+ // `grillhouse:maj`), its `prepare` beginning with `grillhouse hooks` (the frame's git hooks at
36
+ // each `npm install`, as Husky's `prepare`; a `prepare` of the project's own follows it,
37
+ // `grillhouse hooks && …`), and `grillhouse:gardes` (scripts/gardes-du-cadre.mjs, the frame's controls), `grillhouse:dette:inscrire` and
38
+ // `grillhouse:dette:elaguer` (the known debt of the guards, scripts/dette.mjs), and `api` (`npm run api`, which writes
39
+ // the API reports and the generated part of each `docs/INTERFACE.md`), each read as it is in the
40
+ // template's `package.json` (a template without one writes none). Turborepo plays `grillhouse:gardes`
41
+ // and the project's own guards, the root script `gardes`, after the build, beside the tests
42
+ // (`turbo.json`, `npm run verifier`): the frame's former wiring goes, a `pretest` that is the
43
+ // frame's call (`npm run --silent grillhouse:gardes`) removed or taken off the head of a longer
44
+ // one (`… && <its pretest>`, the rest kept; a `pretest` of the project's own stays). An
45
+ // `integration:gardes`, the script the former integration played, loses each head that is a
46
+ // former frame command (that call, `npm run --silent pretest`,
47
+ // `node scripts/integration/gardes.mjs`); what remains is the project's own guards, which move to
48
+ // `gardes` when the project has none (the report says so), else stay where nothing plays them
49
+ // (the report names both); nothing remaining, the script goes.
50
+ // - `turbo.json` is the project's own file, as every repository run by Turborepo writes its own. A
51
+ // project with a `package.json` and no `turbo.json` receives the frame's template of its form:
52
+ // `templates/turbo/paquets.json` when its `package.json` declares
53
+ // `workspaces` (the tasks of each package, and the root's guards, `//#grillhouse:gardes` and
54
+ // `//#gardes`; the tests live in the packages, the root has no `test:vitest`; the `build` of each
55
+ // package is set by its own generated `turbo.json`, below), else
56
+ // `templates/turbo/sans-paquets.json` (`build`, then the guards and the tests beside each other).
57
+ // A `turbo.json` present is never written again by the update; only the generator of the
58
+ // packages' turbo.json (below) writes in it the `dependsOn` of the guard tasks, and adds one it
59
+ // lacks.
60
+ // - The tools the frame's checks run as commands (API Extractor, whose TSDoc base the project's
61
+ // `tsdoc.json` extends, dependency-cruiser, knip, Turborepo) are added to the project's
62
+ // `devDependencies` at the version the template's `devDependencies` give; a tool the project
63
+ // already declares in `dependencies`, `devDependencies` or `optionalDependencies` keeps its
64
+ // declaration, one a previous version added stays, and `devDependencies` are written sorted by
65
+ // name. The report names each tool added and `npm install`, which installs them. What the
66
+ // package's scripts import, it declares itself (its `dependencies`, and Vitest and TypeScript as
67
+ // peers). A project without `packageManager` receives the template's (Turborepo requires it), and
68
+ // a `.gitignore` that does not ignore `.turbo/` (Turborepo's runs and cache) receives the line.
69
+ // - A project that runs Vitest (its `test` script names it, or `vitest` is in the `dependencies` or
70
+ // `devDependencies` of its root `package.json` or of one of its packages, the workspaces as npm
71
+ // maps them, read by `paquets` of scripts/paquets.mjs, the frame's one list of the
72
+ // packages: a package a negation takes out is none, and a package out of the frame
73
+ // (`docs/agents/hors-cadre.txt`), which Turborepo never tests, counts for nothing) has its root scripts
74
+ // `test`, `test:vitest`, `test:forcer` and `verifier` become the template's (`npm test`, the tests
75
+ // as Turborepo's tasks with its cache; `npm run test:forcer`, the same played for real;
76
+ // `npm run verifier`, scripts/verifier.mjs, the guards and the tests beside each other, which the
77
+ // integration, the queue, the continuous integration and the night play); the report says so, when the
78
+ // Vitest of the project's root (`npx --no-install vitest`, the one `test:vitest` calls) satisfies
79
+ // the range the template's package.json declares for `vitest`. Where the root resolves no Vitest or
80
+ // another version, the project's scripts stay (the frame's included) and the report names the
81
+ // version found and the range required. A `test` that runs anything else stays. The scripts of
82
+ // the packages of a monorepo are not written.
83
+ // - The frame's base configuration of Vitest is the package's (`@roomi-fields/grillhouse/vitest-config`,
84
+ // scripts/vitest-config/), which each Vitest configuration imports by its name
85
+ // (`import base from '@roomi-fields/grillhouse/vitest-config'`), as the examples of Turborepo
86
+ // reach @repo/eslint-config: a package is reached by its name, never by a path into it. In a
87
+ // project with `workspaces`, each package of the frame that holds a `test:vitest` script
88
+ // declares the frame's package in its `devDependencies`, at the range the root declares (else
89
+ // `^<version>` of the package), a declaration it holds kept; a package out of the frame is not
90
+ // touched. The report names the packages that received it, `npm install`, which links it, and the
91
+ // import; the update writes no Vitest configuration.
92
+ // - The git hooks: both of the frame (commit-msg « a closed ticket refuses a second commit, a reopened ticket takes its corrections », pre-push night branch
93
+ // guard) are placed by scripts/crochets.mjs, the mechanism `grillhouse hooks` (the project's
94
+ // `prepare`) also runs, in the hooks folder git reads for the project (`core.hooksPath`, else the
95
+ // default one; the configuration stays). A hook of the project keeps its lines, which still run;
96
+ // the frame's lines of a former version give way to the current ones.
97
+ // - The plugins: after the files, the command line installs the plugins the project's settings
98
+ // declare, for the project, by scripts/greffons.mjs (the mechanism `grillhouse init` also calls;
99
+ // `mettreAJour` itself never calls claude). A plugin that cannot be installed has its line naming
100
+ // the command to type; the update has finished its files, and the exit code is non-zero.
101
+ // - The generated files: after the package.json, each generator of the frame is run in the project
102
+ // by the project's command (`node_modules/.bin/grillhouse`, its installed package) for the file
103
+ // it writes when the project holds that file (`grillhouse frontieres --ecrire` for
104
+ // `.dependency-cruiser.cjs`, `grillhouse index-interfaces` for
105
+ // `docs/agents/index-des-interfaces.md`; every one of them for a project `grillhouse init`
106
+ // creates), and `grillhouse turbo-des-paquets --ecrire`
107
+ // in a repository with `workspaces` (the `turbo.json` of each of its packages, whose build waits
108
+ // for its runtime dependencies only, and of which a package out of the frame plays no task; the
109
+ // `dependsOn` of the guard tasks of the root's turbo.json, which wait for the builds), so that
110
+ // the frame's checks accept them; a generator
111
+ // that fails, or a project without its command, has its line, which says to run it after
112
+ // `npm install` (the frame's package and tools), and the exit code is non-zero. `interfaces-contre-code.mjs` is not
113
+ // run: it writes after a build.
114
+ // - The report names the version of the frame, « Cadre 0.23.0. », the package's.
115
+ import { spawnSync } from 'node:child_process';
116
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
117
+ import path from 'node:path';
118
+ import { pathToFileURL } from 'node:url';
119
+ import semver from 'semver';
120
+ import { CADRE, VERSION } from './cadre.mjs';
121
+ import { poserCrochets } from './crochets.mjs';
122
+ import { installerGreffons } from './greffons.mjs';
123
+ import { migrer } from './migrations.mjs';
124
+ import { paquets } from './paquets.mjs';
125
+
126
+ const SCRIPTS = {
127
+ tableau: 'grillhouse tableau --texte',
128
+ 'grillhouse:maj': 'grillhouse maj',
129
+ };
130
+ // The command of the project's `prepare` that places the frame's git hooks (Husky's model).
131
+ const PREPARE = 'grillhouse hooks';
132
+ // The template's package.json, where the frame's scripts and tools are read.
133
+ const GABARIT = 'templates/projet/package.json';
134
+ // The frame's package, as a project declares it, and its base configuration of Vitest.
135
+ const PAQUET_DU_CADRE = '@roomi-fields/grillhouse';
136
+ const CONFIG_VITEST = `${PAQUET_DU_CADRE}/vitest-config`;
137
+
138
+ /** The frame's status line, as the settings of a project run it. */
139
+ export const LIGNE_ETAT = '"$CLAUDE_PROJECT_DIR/node_modules/.bin/grillhouse" ligne-etat';
140
+
141
+ // The frame's tools a project runs as commands: their names are the frame's, their versions are
142
+ // read in the template.
143
+ const OUTILS_DU_CADRE = ['@microsoft/api-extractor', 'dependency-cruiser', 'knip', 'turbo'];
144
+ // The root scripts of the tests, written together from the template.
145
+ const SCRIPTS_DES_TESTS = ['test', 'test:vitest', 'test:forcer', 'verifier'];
146
+ const CHAMPS_INSTALLES = ['dependencies', 'devDependencies', 'optionalDependencies'];
147
+
148
+ // The frame's former wiring of the guards: the call its `pretest` held, and the commands its
149
+ // `integration:gardes` held.
150
+ const APPEL_GARDES = 'npm run --silent grillhouse:gardes';
151
+ const ANCIENS_GARDES_D_INTEGRATION = [
152
+ APPEL_GARDES,
153
+ 'npm run --silent pretest',
154
+ 'node scripts/integration/gardes.mjs',
155
+ ];
156
+ // The root script of the project's own guards, which `npm run verifier` plays (turbo.json), and
157
+ // the one the former integration played.
158
+ const GARDES_DU_PROJET = 'gardes';
159
+ const ANCIEN_GARDES_DU_PROJET = 'integration:gardes';
160
+ // The line of `.gitignore` that keeps Turborepo's folder out of git.
161
+ const TURBO_IGNORE = '.turbo/';
162
+ // The frame's templates of `turbo.json`, one per form of repository, read in the source.
163
+ const TURBO_PAQUETS = 'templates/turbo/paquets.json';
164
+ const TURBO_SANS_PAQUETS = 'templates/turbo/sans-paquets.json';
165
+
166
+ // Whether `vitest` is declared in the dependencies of the package.json <p> (read at <dossier>) or of
167
+ // one of its packages of the frame (`paquets`: the workspaces as npm maps them, a package out of
168
+ // the frame left out, which Turborepo never tests).
169
+ const declareVitest = (dossier, p) => {
170
+ const a = pkg => Boolean(pkg?.dependencies?.vitest ?? pkg?.devDependencies?.vitest);
171
+ return a(p) || paquets(dossier).some(({ pkg, hors }) => !hors && a(pkg));
172
+ };
173
+ // The version of the Vitest the project's root resolves, as `test:vitest` calls it
174
+ // (`npx --no-install vitest --version`); null when it answers none.
175
+ const vitestDeLaRacine = projet => {
176
+ const r = spawnSync('npx', ['--no-install', 'vitest', '--version'], {
177
+ cwd: projet,
178
+ encoding: 'utf8',
179
+ });
180
+ return r.status === 0 ? (/vitest\/(\d+\.\d+\.\d+\S*)/.exec(r.stdout)?.[1] ?? null) : null;
181
+ };
182
+ const lireJson = (f, defaut) => {
183
+ try {
184
+ return JSON.parse(readFileSync(f, 'utf8'));
185
+ } catch {
186
+ return defaut;
187
+ }
188
+ };
189
+
190
+ // Adds `.turbo/` to the `.gitignore` of the project at <projet> when no line ignores it.
191
+ function ignorerTurbo(projet) {
192
+ const f = path.join(projet, '.gitignore');
193
+ const texte = existsSync(f) ? readFileSync(f, 'utf8') : '';
194
+ const lignes = texte.split('\n').map(l => l.trim());
195
+ if (lignes.some(l => l === '.turbo' || l === TURBO_IGNORE || l === '/.turbo' || l === '/.turbo/'))
196
+ return;
197
+ const fin = texte === '' || texte.endsWith('\n') ? '' : '\n';
198
+ writeFileSync(f, `${texte}${fin}${TURBO_IGNORE}\n`);
199
+ }
200
+
201
+ // Writes the `turbo.json` of the project at <projet>, when it has a package.json and no turbo.json,
202
+ // from the source's template of its form (`workspaces` declared: a repository of packages).
203
+ // Returns the template written, or null.
204
+ function poserTurbo(projet, source) {
205
+ const f = path.join(projet, 'turbo.json');
206
+ const pkg = path.join(projet, 'package.json');
207
+ if (existsSync(f) || !existsSync(pkg)) return null;
208
+ const gabarit = lireJson(pkg, {}).workspaces !== undefined ? TURBO_PAQUETS : TURBO_SANS_PAQUETS;
209
+ if (!existsSync(path.join(source, gabarit))) return null;
210
+ copyFileSync(path.join(source, gabarit), f);
211
+ return gabarit;
212
+ }
213
+
214
+ // Declares the frame's package, whose base configuration of Vitest each Vitest configuration
215
+ // imports, in the devDependencies of each package of the frame of the project of packages at
216
+ // <projet> (root package.json <p>) whose scripts hold `test:vitest`, at the range the root declares
217
+ // (else `^<version>` of the package), a declaration the package holds kept; a package out of the
218
+ // frame is not touched. Returns { nom, paquets } (paquets: the packages that received the
219
+ // declaration), or null when nothing changes.
220
+ function poserConfigVitest(projet, p) {
221
+ if (p.workspaces === undefined) return null;
222
+ const testes = paquets(projet).filter(
223
+ ({ pkg, hors }) => !hors && typeof pkg.scripts?.['test:vitest'] === 'string'
224
+ );
225
+ const racine = CHAMPS_INSTALLES.map(c => p[c]?.[PAQUET_DU_CADRE]).find(v => v !== undefined);
226
+ const declarants = [];
227
+ for (const { dossier, pkg } of testes) {
228
+ if (CHAMPS_INSTALLES.some(c => pkg[c]?.[PAQUET_DU_CADRE] !== undefined)) continue;
229
+ pkg.devDependencies = Object.fromEntries(
230
+ Object.entries({
231
+ ...(pkg.devDependencies ?? {}),
232
+ [PAQUET_DU_CADRE]: racine ?? `^${VERSION}`,
233
+ }).sort(([a], [b]) => a.localeCompare(b, 'en'))
234
+ );
235
+ writeFileSync(path.join(projet, dossier, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`);
236
+ declarants.push(dossier);
237
+ }
238
+ return declarants.length > 0 ? { nom: PAQUET_DU_CADRE, paquets: declarants } : null;
239
+ }
240
+
241
+ // Writes the frame's status line in the settings of the project at <projet>, when they exist and
242
+ // hold none or the frame's (see the header); their other keys keep their order. Returns whether
243
+ // they changed.
244
+ function reglerLigneEtat(projet) {
245
+ const f = path.join(projet, '.claude', 'settings.json');
246
+ if (!existsSync(f)) return false;
247
+ const reglages = JSON.parse(readFileSync(f, 'utf8'));
248
+ const actuelle = reglages.statusLine;
249
+ const commande = typeof actuelle?.command === 'string' ? actuelle.command : '';
250
+ if (actuelle !== undefined && !commande.includes('ligne-etat')) return false;
251
+ const voulue = { type: 'command', command: LIGNE_ETAT };
252
+ if (JSON.stringify(actuelle) === JSON.stringify(voulue)) return false;
253
+ reglages.statusLine = voulue;
254
+ writeFileSync(f, `${JSON.stringify(reglages, null, 2)}\n`);
255
+ return true;
256
+ }
257
+
258
+ /** The marketplace of the frame's plugin, its source, and the plugin's identifier. */
259
+ export const MARCHE_DU_CADRE = 'roomi-fields';
260
+ export const SOURCE_DU_MARCHE = { source: 'github', repo: 'roomi-fields/claude-plugins' };
261
+ export const GREFFON_DU_CADRE = `grillhouse@${MARCHE_DU_CADRE}`;
262
+
263
+ // Declares in the settings of the project at <projet>, when they exist, the marketplace roomi-fields
264
+ // and enables the frame's plugin (see the header); returns whether they changed.
265
+ function declarerGreffon(projet) {
266
+ const f = path.join(projet, '.claude', 'settings.json');
267
+ if (!existsSync(f)) return false;
268
+ const avant = readFileSync(f, 'utf8');
269
+ const reglages = JSON.parse(avant);
270
+ reglages.extraKnownMarketplaces = {
271
+ ...(reglages.extraKnownMarketplaces ?? {}),
272
+ [MARCHE_DU_CADRE]: { source: SOURCE_DU_MARCHE },
273
+ };
274
+ reglages.enabledPlugins = { ...(reglages.enabledPlugins ?? {}), [GREFFON_DU_CADRE]: true };
275
+ if (JSON.stringify(reglages) === JSON.stringify(JSON.parse(avant))) return false;
276
+ writeFileSync(f, `${JSON.stringify(reglages, null, 2)}\n`);
277
+ return true;
278
+ }
279
+
280
+ /** The version of the frame the project at <projet> recorded (`grillhouse.version`), or null. */
281
+ export function versionDuProjet(projet) {
282
+ const v = lireJson(path.join(projet, 'package.json'), {})?.grillhouse?.version;
283
+ return typeof v === 'string' ? v : null;
284
+ }
285
+
286
+ /**
287
+ * Brings the project at `projet` to the frame's files at `source` (CADRE, scripts/cadre.mjs; a
288
+ * trial's own in the tests). Returns what changed.
289
+ */
290
+ export function mettreAJour(projet, source) {
291
+ const bilan = {
292
+ dependances: [],
293
+ test: null,
294
+ vitest: null,
295
+ turbo: null,
296
+ configVitest: null,
297
+ gardes: null,
298
+ ligneEtat: false,
299
+ greffon: false,
300
+ };
301
+ const pkg = path.join(projet, 'package.json');
302
+ if (existsSync(pkg)) {
303
+ const p = lireJson(pkg, {});
304
+ p.grillhouse = { ...(p.grillhouse ?? {}), version: VERSION };
305
+ const pkgSource = lireJson(path.join(source, GABARIT), {});
306
+ const gardes = pkgSource.scripts?.['grillhouse:gardes'];
307
+ p.scripts = { ...(p.scripts ?? {}), ...SCRIPTS };
308
+ // The project's `prepare`, which npm runs at its install, places the frame's git hooks first.
309
+ const prepare = typeof p.scripts.prepare === 'string' ? p.scripts.prepare.trim() : '';
310
+ if (!prepare.split(/\s*&&\s*/).includes(PREPARE)) {
311
+ p.scripts.prepare = prepare === '' ? PREPARE : `${PREPARE} && ${prepare}`;
312
+ }
313
+ if (typeof pkgSource.scripts?.api === 'string') {
314
+ p.scripts.api = pkgSource.scripts.api;
315
+ }
316
+ // The frame's tests (Turborepo's tasks) replace a project's own test that runs Vitest.
317
+ const testDuCadre = pkgSource.scripts?.test;
318
+ const testDuProjet = p.scripts.test;
319
+ const vitestExige = pkgSource.devDependencies?.vitest ?? pkgSource.dependencies?.vitest;
320
+ const lanceVitest =
321
+ typeof testDuCadre === 'string' &&
322
+ typeof testDuProjet === 'string' &&
323
+ (testDuProjet === testDuCadre || /\bvitest\b/.test(testDuProjet) || declareVitest(projet, p));
324
+ if (lanceVitest) {
325
+ // The root must resolve the Vitest the frame requires (`test:vitest` calls it).
326
+ const trouvee = typeof vitestExige === 'string' ? vitestDeLaRacine(projet) : null;
327
+ const convient =
328
+ typeof vitestExige !== 'string' ||
329
+ (trouvee !== null && semver.satisfies(trouvee, vitestExige));
330
+ if (!convient) {
331
+ bilan.vitest = { trouvee, exigee: vitestExige };
332
+ } else {
333
+ if (testDuProjet !== testDuCadre)
334
+ bilan.test = { ancien: testDuProjet, nouveau: testDuCadre };
335
+ for (const nom of SCRIPTS_DES_TESTS) {
336
+ const script = pkgSource.scripts?.[nom];
337
+ if (typeof script !== 'string') continue;
338
+ // A repository of packages keeps its tests in its packages, as Turborepo does: its root
339
+ // gets no `test:vitest`, and the frame's own leaves.
340
+ if (p.workspaces !== undefined && nom === 'test:vitest') {
341
+ if (p.scripts[nom] === script) delete p.scripts[nom];
342
+ continue;
343
+ }
344
+ p.scripts[nom] = script;
345
+ }
346
+ }
347
+ }
348
+ for (const nom of OUTILS_DU_CADRE) {
349
+ const version = pkgSource.devDependencies?.[nom];
350
+ if (typeof version === 'string' && !CHAMPS_INSTALLES.some(c => p[c]?.[nom] !== undefined)) {
351
+ p.devDependencies = { ...(p.devDependencies ?? {}), [nom]: version };
352
+ bilan.dependances.push(nom);
353
+ }
354
+ }
355
+ if (p.devDependencies) {
356
+ p.devDependencies = Object.fromEntries(
357
+ Object.entries(p.devDependencies).sort(([a], [b]) => a.localeCompare(b, 'en'))
358
+ );
359
+ }
360
+ if (typeof pkgSource.packageManager === 'string' && typeof p.packageManager !== 'string') {
361
+ p.packageManager = pkgSource.packageManager;
362
+ }
363
+ if (typeof gardes === 'string') p.scripts['grillhouse:gardes'] = gardes;
364
+ // The commands of the known debt of the guards (scripts/dette.mjs) are the frame's.
365
+ for (const nom of ['grillhouse:dette:inscrire', 'grillhouse:dette:elaguer']) {
366
+ const script = pkgSource.scripts?.[nom];
367
+ if (typeof script === 'string') p.scripts[nom] = script;
368
+ }
369
+ // The frame's former wiring of the guards goes: Turborepo plays them (`npm run verifier`).
370
+ const pretest = typeof p.scripts.pretest === 'string' ? p.scripts.pretest.trim() : null;
371
+ if (pretest === APPEL_GARDES) {
372
+ delete p.scripts.pretest;
373
+ } else if (pretest?.startsWith(`${APPEL_GARDES} && `)) {
374
+ p.scripts.pretest = pretest.slice(APPEL_GARDES.length + 4);
375
+ }
376
+ // The project's own guards, which the former integration played, move to `gardes`.
377
+ if (typeof p.scripts[ANCIEN_GARDES_DU_PROJET] === 'string') {
378
+ let reste = p.scripts[ANCIEN_GARDES_DU_PROJET].trim();
379
+ for (
380
+ let tete;
381
+ (tete = ANCIENS_GARDES_D_INTEGRATION.find(
382
+ c => reste === c || reste.startsWith(`${c} && `)
383
+ ));
384
+ ) {
385
+ reste = reste.slice(tete.length).replace(/^ && /, '');
386
+ }
387
+ if (reste === '') {
388
+ delete p.scripts[ANCIEN_GARDES_DU_PROJET];
389
+ } else if (typeof p.scripts[GARDES_DU_PROJET] !== 'string') {
390
+ delete p.scripts[ANCIEN_GARDES_DU_PROJET];
391
+ p.scripts[GARDES_DU_PROJET] = reste;
392
+ bilan.gardes = { deplace: true };
393
+ } else {
394
+ bilan.gardes = { deplace: false };
395
+ }
396
+ }
397
+ bilan.configVitest = poserConfigVitest(projet, p);
398
+ writeFileSync(pkg, `${JSON.stringify(p, null, 2)}\n`);
399
+ }
400
+ bilan.turbo = poserTurbo(projet, source);
401
+ bilan.ligneEtat = reglerLigneEtat(projet);
402
+ bilan.greffon = declarerGreffon(projet);
403
+ ignorerTurbo(projet);
404
+ poserCrochets(projet);
405
+ return bilan;
406
+ }
407
+
408
+ // The frame's generators, subcommands of the project's command, and the file each writes: run in
409
+ // the project after its package.json. A generator without `sortie` (the `turbo.json` of each
410
+ // package) runs in every project whose package.json declares `workspaces`.
411
+ const GENERATEURS = [
412
+ { commande: 'frontieres', args: ['--ecrire'], sortie: '.dependency-cruiser.cjs' },
413
+ { commande: 'index-interfaces', args: [], sortie: 'docs/agents/index-des-interfaces.md' },
414
+ { commande: 'turbo-des-paquets', args: ['--ecrire'], sortie: null },
415
+ ];
416
+
417
+ /**
418
+ * Runs each generator whose output the project holds (every one with `tous`, for a project
419
+ * `grillhouse init` creates); returns the lines of failure.
420
+ */
421
+ export function regenerer(projet, { tous = false } = {}) {
422
+ const lignes = [];
423
+ const grillhouse = path.join(projet, 'node_modules', '.bin', 'grillhouse');
424
+ const aPaquets = lireJson(path.join(projet, 'package.json'), {})?.workspaces !== undefined;
425
+ for (const g of GENERATEURS) {
426
+ const absente = g.sortie !== null && !tous && !existsSync(path.join(projet, g.sortie));
427
+ if (g.sortie === null ? !aPaquets : absente) {
428
+ continue;
429
+ }
430
+ if (g.sortie !== null)
431
+ mkdirSync(path.dirname(path.join(projet, g.sortie)), { recursive: true });
432
+ const r = spawnSync(grillhouse, [g.commande, ...g.args], { cwd: projet, encoding: 'utf8' });
433
+ if (r.status !== 0) {
434
+ lignes.push(
435
+ `${g.sortie ?? g.commande} : \`npx grillhouse ${[g.commande, ...g.args].join(' ')}\` échoue ; lance-le après \`npm install\`, qui installe les outils du cadre. ${(r.stderr ?? '').trim()}`.trim()
436
+ );
437
+ }
438
+ }
439
+ return lignes;
440
+ }
441
+
442
+ /** The report of an update, from the bilan <b> of `mettreAJour`: one line per change. */
443
+ export function rapport(b) {
444
+ const lignes = [];
445
+ if (b.dependances.length > 0) {
446
+ lignes.push(
447
+ `Dépendances de développement ajoutées : ${b.dependances.join(', ')}. \`npm install\` les installe.`
448
+ );
449
+ }
450
+ if (b.configVitest) {
451
+ const { nom, paquets: declarants } = b.configVitest;
452
+ lignes.push(
453
+ `Configuration de base de Vitest : ${nom} déclaré dans les devDependencies de ${declarants.join(', ')}. \`npm install\` relie le paquet. La configuration Vitest de chaque paquet l'importe par son nom : \`import base from '${CONFIG_VITEST}'\`.`
454
+ );
455
+ }
456
+ if (b.turbo) {
457
+ lignes.push(
458
+ `turbo.json écrit depuis le gabarit du cadre ${b.turbo} ; il est au projet, la mise à jour n'y touche plus.`
459
+ );
460
+ }
461
+ if (b.ligneEtat) {
462
+ lignes.push(`Ligne d'état : .claude/settings.json lance \`grillhouse ligne-etat\`.`);
463
+ }
464
+ if (b.greffon) {
465
+ lignes.push(
466
+ `Greffon du cadre : .claude/settings.json déclare la place de marché ${MARCHE_DU_CADRE} (${SOURCE_DU_MARCHE.repo}) et active ${GREFFON_DU_CADRE}.`
467
+ );
468
+ }
469
+ if (b.gardes?.deplace) {
470
+ lignes.push(
471
+ `Gardes du projet : le script integration:gardes devient gardes, que \`npm run verifier\` joue après la construction (turbo.json).`
472
+ );
473
+ } else if (b.gardes) {
474
+ lignes.push(
475
+ `Gardes du projet : integration:gardes et gardes coexistent ; rien ne joue plus integration:gardes, \`npm run verifier\` joue gardes. Verse l'un dans l'autre.`
476
+ );
477
+ }
478
+ if (b.vitest) {
479
+ lignes.push(
480
+ `Vitest : la racine résout ${b.vitest.trouvee ?? 'aucune version'}, le cadre exige ${b.vitest.exigee} ; le script test du projet reste tel quel.`
481
+ );
482
+ }
483
+ if (b.test) {
484
+ lignes.push(
485
+ `Script test : \`${b.test.ancien}\` remplacé par le test du cadre \`${b.test.nouveau}\` (Turborepo : construction, puis tests ; une tâche dont rien n'a bougé est relue de son cache ; \`npm run verifier\` joue les gardes et les tests).`
486
+ );
487
+ }
488
+ return lignes.map(l => `${l}\n`).join('');
489
+ }
490
+
491
+ const USAGE =
492
+ 'Usage : npx grillhouse maj met le projet à jour du paquet installé (npm install -D @roomi-fields/grillhouse@<version> d’abord)\n';
493
+
494
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
495
+ if (process.argv.length > 2) {
496
+ process.stderr.write(USAGE);
497
+ process.exitCode = 2;
498
+ } else {
499
+ const projet = process.cwd();
500
+ const migrations = migrer(projet, versionDuProjet(projet));
501
+ const b = mettreAJour(projet, CADRE);
502
+ process.stdout.write(`Cadre ${VERSION}.\n`);
503
+ for (const { version, nom, lignes } of migrations) {
504
+ process.stdout.write(
505
+ `Migration ${version} (${nom}) :\n${lignes.map(l => `- ${l}\n`).join('')}`
506
+ );
507
+ }
508
+ process.stdout.write(rapport(b));
509
+ for (const l of [...regenerer(projet), ...installerGreffons(projet)]) {
510
+ process.stderr.write(`${l}\n`);
511
+ process.exitCode = 1;
512
+ }
513
+ }
514
+ }