@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,338 @@
1
+ #!/usr/bin/env node
2
+ // The frame's plugins, installed for the project by one mechanism: `scripts/preparation.mjs` (the
3
+ // preparation of `grillhouse init`) and `scripts/grillhouse-maj.mjs` (update, its command line) both
4
+ // call it, and the session start
5
+ // asks it which are absent.
6
+ //
7
+ // npx grillhouse greffons [<project>] installs the declared plugins (the current directory by default)
8
+ // npx grillhouse greffons --absents [<project>] prints what the project lacks, one line each (see below)
9
+ //
10
+ // - The list is `enabledPlugins` of the project's `.claude/settings.json` (a plugin at `true`; one
11
+ // at `false` or unlisted is left alone) and `extraKnownMarketplaces` for the marketplaces. A
12
+ // missing file declares nothing (code 0); an unreadable one is a failure.
13
+ // - Installation: each marketplace of `extraKnownMarketplaces` unknown to `claude plugin
14
+ // marketplace list --json` is added by `claude plugin marketplace add <source>` (the user's scope,
15
+ // the project's settings stay untouched; the source is `repo` for github, `url` for git and url,
16
+ // `path` for directory and file, any other type is a failure); then each declared plugin not
17
+ // installed for the project is installed by `claude plugin install <id> --scope project`, run
18
+ // from the project's root. `claude` rewrites `.claude/settings.json` in its own key order; once
19
+ // the installs are done, the file holds the keys in the order it had before them (the keys
20
+ // claude added after), and its exact text when the JSON value is the same.
21
+ // - A plugin is installed for the project when `claude plugin list --json` has an entry for it of
22
+ // scope user, or of scope project or local whose `projectPath` is the project's root (both paths
23
+ // resolved); the entry's state (enabled, errors) is not read.
24
+ // - A failure has its line on the error output, naming the plugin and the command that installs it
25
+ // (a failed add: the marketplace's command, then the plugin's); the others are still tried and the
26
+ // code is 1. A state reading that fails installs nothing. Without `claude`, each plugin has its line.
27
+ // - `--absents` prints `claude` (the command is missing), `greffon <id>` per declared plugin not
28
+ // installed for the project, or `lecture <reason>` when the state cannot be read; each claude
29
+ // call stops after 5 s. It also prints `ecart <id> <mod's version> <package's version>
30
+ // <command>` when the frame's mod (`grillhouse@…`), installed for the project, carries another
31
+ // version than the package `@roomi-fields/grillhouse` this script belongs to: the package carries
32
+ // the plugin, whose manifest receives the package's version when it is packed (scripts/cadre.mjs),
33
+ // and the marketplace roomi-fields names the package, so a project's package and the plugin the
34
+ // machine installed for it can drift apart. The entry read is the project's at scope local, else
35
+ // project, else user; the command aligns the one behind (`claude plugin update <id> --scope <its
36
+ // scope>`, or `npm install -D @roomi-fields/grillhouse@<mod's version>`). A version that is not
37
+ // x.y.z (a plugin whose manifest carries none) and a script without its package's package.json
38
+ // beside it compare nothing.
39
+ import { spawnSync } from 'node:child_process';
40
+ import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';
41
+ import path from 'node:path';
42
+ import { isDeepStrictEqual } from 'node:util';
43
+ import { pathToFileURL } from 'node:url';
44
+
45
+ const DELAI_LECTURE_MS = 5000;
46
+
47
+ // The path resolved through links, else normalised.
48
+ function resolu(p) {
49
+ try {
50
+ return realpathSync(p);
51
+ } catch {
52
+ return path.resolve(p);
53
+ }
54
+ }
55
+
56
+ // Runs claude; `absent` is true when the command does not exist, `delai` when it was stopped.
57
+ function claude(args, options = {}) {
58
+ const r = spawnSync('claude', args, { encoding: 'utf8', ...options });
59
+ return {
60
+ ok: r.status === 0 && !r.error,
61
+ absent: r.error?.code === 'ENOENT',
62
+ delai: r.error?.code === 'ETIMEDOUT',
63
+ stdout: r.stdout ?? '',
64
+ };
65
+ }
66
+
67
+ // The declaration of the project: `{ plugins: [id], marches: {name: {source}} }`, or `{ erreur }`.
68
+ function declaration(projet) {
69
+ const f = path.join(projet, '.claude', 'settings.json');
70
+ if (!existsSync(f)) {
71
+ return { plugins: [], marches: {} };
72
+ }
73
+ try {
74
+ const r = JSON.parse(readFileSync(f, 'utf8'));
75
+ return {
76
+ plugins: Object.entries(r.enabledPlugins ?? {})
77
+ .filter(([, actif]) => actif === true)
78
+ .map(([id]) => id),
79
+ marches: r.extraKnownMarketplaces ?? {},
80
+ };
81
+ } catch {
82
+ return { erreur: `.claude/settings.json illisible (${f})` };
83
+ }
84
+ }
85
+
86
+ // The JSON array `claude <args> --json` answers, or `{ erreur, delai }`.
87
+ function lireListe(args, delai) {
88
+ const r = claude([...args, '--json'], { timeout: delai });
89
+ if (r.absent) {
90
+ return { absent: true };
91
+ }
92
+ if (!r.ok) {
93
+ return {
94
+ erreur: `\`claude ${args.join(' ')} --json\` ${r.delai ? 'interrompu' : 'échoue'}`,
95
+ delai: r.delai,
96
+ };
97
+ }
98
+ try {
99
+ const v = JSON.parse(r.stdout);
100
+ if (Array.isArray(v)) {
101
+ return { liste: v };
102
+ }
103
+ } catch {
104
+ // unreadable, below
105
+ }
106
+ return { erreur: `\`claude ${args.join(' ')} --json\` rend un JSON illisible` };
107
+ }
108
+
109
+ // True when the entries hold the plugin for the project (scope user, or project/local at its root).
110
+ function installePourLeProjet(entrees, id, racine) {
111
+ return entrees.some(
112
+ e =>
113
+ e.id === id &&
114
+ (e.scope === 'user' ||
115
+ ((e.scope === 'project' || e.scope === 'local') &&
116
+ typeof e.projectPath === 'string' &&
117
+ resolu(e.projectPath) === racine))
118
+ );
119
+ }
120
+
121
+ // The source `marketplace add` receives for an entry of `extraKnownMarketplaces`, or null.
122
+ function sourceDuMarche(entree) {
123
+ const s = entree?.source ?? {};
124
+ const champ = { github: 'repo', git: 'url', url: 'url', directory: 'path', file: 'path' }[
125
+ s.source
126
+ ];
127
+ return champ && typeof s[champ] === 'string' ? s[champ] : null;
128
+ }
129
+
130
+ // The value <nouveau> with the object keys of <ancien> first, in its order, the others after;
131
+ // applied at each depth (arrays and scalars are kept as they are).
132
+ function dansLOrdre(nouveau, ancien) {
133
+ const objet = v => v !== null && typeof v === 'object' && !Array.isArray(v);
134
+ if (!objet(nouveau) || !objet(ancien)) {
135
+ return nouveau;
136
+ }
137
+ const cles = [
138
+ ...Object.keys(ancien).filter(k => k in nouveau),
139
+ ...Object.keys(nouveau).filter(k => !(k in ancien)),
140
+ ];
141
+ return Object.fromEntries(cles.map(k => [k, dansLOrdre(nouveau[k], ancien[k])]));
142
+ }
143
+
144
+ // Puts `.claude/settings.json` back in the key order (and text, for an equal value) it had in
145
+ // <avant>, the text read before the installs; a file that is gone or unreadable stays as it is.
146
+ function restaurerReglages(fichier, avant) {
147
+ if (avant === null) {
148
+ return;
149
+ }
150
+ try {
151
+ const apres = readFileSync(fichier, 'utf8');
152
+ if (apres === avant) {
153
+ return;
154
+ }
155
+ const ancien = JSON.parse(avant);
156
+ const range = dansLOrdre(JSON.parse(apres), ancien);
157
+ writeFileSync(
158
+ fichier,
159
+ isDeepStrictEqual(range, ancien) ? avant : `${JSON.stringify(range, null, 2)}\n`
160
+ );
161
+ } catch {
162
+ // unreadable: left as claude wrote it
163
+ }
164
+ }
165
+
166
+ const commandeGreffon = id => `claude plugin install ${id} --scope project`;
167
+ const marcheDe = id => id.slice(id.indexOf('@') + 1);
168
+
169
+ /**
170
+ * Installs the plugins the project declares, for the project. Returns the lines of failure
171
+ * (empty when everything stands).
172
+ */
173
+ export function installerGreffons(projet) {
174
+ const fichier = path.join(resolu(projet), '.claude', 'settings.json');
175
+ const avant = existsSync(fichier) ? readFileSync(fichier, 'utf8') : null;
176
+ try {
177
+ return installer(projet);
178
+ } finally {
179
+ restaurerReglages(fichier, avant);
180
+ }
181
+ }
182
+
183
+ function installer(projet) {
184
+ const racine = resolu(projet);
185
+ const d = declaration(racine);
186
+ if (d.erreur) {
187
+ return [d.erreur];
188
+ }
189
+ if (d.plugins.length === 0 && Object.keys(d.marches).length === 0) {
190
+ return [];
191
+ }
192
+ const lignes = [];
193
+ const lecturePlugins = lireListe(['plugin', 'list'], undefined);
194
+ if (lecturePlugins.absent) {
195
+ return d.plugins.map(
196
+ id =>
197
+ `${id} : la commande claude manque ; installe-la, puis lance \`${commandeGreffon(id)}\`.`
198
+ );
199
+ }
200
+ if (lecturePlugins.erreur) {
201
+ return [`${lecturePlugins.erreur} : aucun greffon n'est installé.`];
202
+ }
203
+ const lectureMarches = lireListe(['plugin', 'marketplace', 'list'], undefined);
204
+ if (lectureMarches.erreur) {
205
+ return [`${lectureMarches.erreur} : aucun marketplace n'est ajouté, aucun greffon installé.`];
206
+ }
207
+ const connus = new Set(lectureMarches.liste.map(m => m.name));
208
+ // marketplace name -> the line-part of its failed add (command or reason)
209
+ const echecs = new Map();
210
+ for (const [nom, entree] of Object.entries(d.marches)) {
211
+ if (connus.has(nom)) {
212
+ continue;
213
+ }
214
+ const src = sourceDuMarche(entree);
215
+ if (src === null) {
216
+ echecs.set(
217
+ nom,
218
+ `le marketplace ${nom} a une source (${entree?.source?.source}) que \`claude plugin marketplace add\` n'accepte pas`
219
+ );
220
+ lignes.push(`${echecs.get(nom)}.`);
221
+ } else if (!claude(['plugin', 'marketplace', 'add', src], { cwd: racine }).ok) {
222
+ echecs.set(nom, `\`claude plugin marketplace add ${src}\``);
223
+ if (!d.plugins.some(id => marcheDe(id) === nom)) {
224
+ lignes.push(`le marketplace ${nom} : l'ajout échoue, lance ${echecs.get(nom)}.`);
225
+ }
226
+ }
227
+ }
228
+ for (const id of d.plugins) {
229
+ if (installePourLeProjet(lecturePlugins.liste, id, racine)) {
230
+ continue;
231
+ }
232
+ const marche = marcheDe(id);
233
+ if (echecs.has(marche)) {
234
+ lignes.push(
235
+ `${id} : le marketplace ${marche} manque (${echecs.get(marche)}), puis \`${commandeGreffon(id)}\`.`
236
+ );
237
+ } else if (!claude(['plugin', 'install', id, '--scope', 'project'], { cwd: racine }).ok) {
238
+ lignes.push(`${id} : l'installation échoue ; lance \`${commandeGreffon(id)}\`.`);
239
+ }
240
+ }
241
+ return lignes;
242
+ }
243
+
244
+ /**
245
+ * What the project lacks of its declared plugins: `claude`, `greffon <id>` or `lecture <reason>`
246
+ * lines, and the `ecart …` line of a mod and a package of different versions. Reads only; each
247
+ * claude call stops after 5 s.
248
+ */
249
+ export function greffonsAbsents(projet) {
250
+ const racine = resolu(projet);
251
+ const d = declaration(racine);
252
+ if (d.erreur) {
253
+ return [`lecture ${d.erreur}`];
254
+ }
255
+ if (d.plugins.length === 0) {
256
+ return [];
257
+ }
258
+ const lecture = lireListe(['plugin', 'list'], DELAI_LECTURE_MS);
259
+ if (lecture.absent) {
260
+ return ['claude'];
261
+ }
262
+ if (lecture.erreur) {
263
+ return [`lecture ${lecture.erreur}`];
264
+ }
265
+ const absents = d.plugins.filter(id => !installePourLeProjet(lecture.liste, id, racine));
266
+ const ecart = ecartDeVersions(lecture.liste, d.plugins, racine);
267
+ return [...absents.map(id => `greffon ${id}`), ...(ecart ? [ecart] : [])];
268
+ }
269
+
270
+ // The frame's mod and its package: one version, the `version` of the package.json beside this
271
+ // script's directory (the package's, wherever it is installed), which the plugin's manifest
272
+ // receives when the package is packed.
273
+ const GREFFON_DU_CADRE = 'grillhouse';
274
+ const NOM_DU_PAQUET = '@roomi-fields/grillhouse';
275
+ const X_Y_Z = /^\d+\.\d+\.\d+$/;
276
+ function versionDuPaquet() {
277
+ try {
278
+ const p = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
279
+ return p.name === NOM_DU_PAQUET && typeof p.version === 'string' ? p.version : null;
280
+ } catch {
281
+ return null;
282
+ }
283
+ }
284
+
285
+ // The line `ecart <id> <mod's version> <package's version> <command>` when the frame's mod,
286
+ // declared and installed for the project (its entry of scope local, else project, else user, as
287
+ // Claude Code applies them), carries another version than the package; null otherwise. The command
288
+ // aligns the one behind: the mod's update at its scope, or the package's install at the mod's
289
+ // version.
290
+ function ecartDeVersions(entrees, declares, racine) {
291
+ const paquet = versionDuPaquet();
292
+ const id = declares.find(x => x.slice(0, x.indexOf('@')) === GREFFON_DU_CADRE);
293
+ if (!paquet || !id) {
294
+ return null;
295
+ }
296
+ const pourLeProjet = entrees.filter(e => installePourLeProjet([e], id, racine));
297
+ const entree = ['local', 'project', 'user']
298
+ .map(s => pourLeProjet.find(e => e.scope === s))
299
+ .find(Boolean);
300
+ const greffon = entree?.version;
301
+ if (typeof greffon !== 'string' || !X_Y_Z.test(greffon) || greffon === paquet) {
302
+ return null;
303
+ }
304
+ const commande = plusAncienne(greffon, paquet)
305
+ ? `claude plugin update ${id} --scope ${entree.scope}`
306
+ : `npm install -D ${NOM_DU_PAQUET}@${greffon}`;
307
+ return `ecart ${id} ${greffon} ${paquet} ${commande}`;
308
+ }
309
+
310
+ // True when version <a> comes before version <b> (x.y.z, numbers compared in order).
311
+ function plusAncienne(a, b) {
312
+ const x = a.split('.').map(Number);
313
+ const y = b.split('.').map(Number);
314
+ for (let i = 0; i < 3; i++) {
315
+ if ((x[i] ?? 0) !== (y[i] ?? 0)) {
316
+ return (x[i] ?? 0) < (y[i] ?? 0);
317
+ }
318
+ }
319
+ return false;
320
+ }
321
+
322
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolu(process.argv[1])).href) {
323
+ const args = process.argv.slice(2);
324
+ const absents = args[0] === '--absents';
325
+ const projet = path.resolve((absents ? args[1] : args[0]) ?? '.');
326
+ if (absents) {
327
+ const lignes = greffonsAbsents(projet);
328
+ if (lignes.length > 0) {
329
+ process.stdout.write(`${lignes.join('\n')}\n`);
330
+ }
331
+ } else {
332
+ const lignes = installerGreffons(projet);
333
+ for (const l of lignes) {
334
+ process.stderr.write(`${l}\n`);
335
+ }
336
+ process.exitCode = lignes.length > 0 ? 1 : 0;
337
+ }
338
+ }