@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,236 @@
1
+ #!/usr/bin/env node
2
+ // The day's tests, as Turborepo's tasks (`npm test`, `npm run test:forcer`): the build, then the
3
+ // `test:vitest` of each package, an unchanged task replayed from Turborepo's cache.
4
+ //
5
+ // npx grillhouse tests [--force] [<path>…] [--<option>=<value>…]
6
+ //
7
+ // - Without a path, every package plays its tests (`turbo run test:vitest`); `--force` plays each
8
+ // task for real, the builds included (`npm run test:forcer`).
9
+ // - A path is written from the root of the repository: a test file or a directory. In a repository
10
+ // of packages, each path plays in the package that holds it: Turborepo runs the `test:vitest` of
11
+ // those packages only (`--filter=<name>`), and their Vitest receives each path made absolute,
12
+ // which it matches against its own test files (a path of another package matches none of them).
13
+ // Turborepo filters packages, not files, and Vitest resolves a relative path from the directory
14
+ // of the package that runs it, so the frame translates each path into its package; a Vitest at the
15
+ // root (its projects) would run each package's tests under the root's version, not the one the
16
+ // package declares. A path out of every package, in a package out of the frame
17
+ // (`docs/agents/hors-cadre.txt`), or in a package without a `test:vitest` script is refused,
18
+ // named, and nothing plays. In a repository without packages, the paths go to the root's Vitest
19
+ // as they are.
20
+ // - The paths and the options reach Vitest in the variable `GRILLHOUSE_TESTS`
21
+ // (scripts/vitest-config/cibles.mjs), which the `env` of the task `test:vitest` names, never
22
+ // after `--`: Turborepo passes the arguments after `--` to the tasks the command names only, and
23
+ // counts them in the hash of every task of the run (its documentation, « Caching »: « including
24
+ // dependencies of `build` that did not receive `--arg=value` »), where a variable of a task's
25
+ // `env` enters that task's hash alone. So a run with paths is one Turborepo run of the tests with
26
+ // what they depend on (`turbo run test:vitest --filter=<name>`): the builds and the traces they
27
+ // wait for play first, from the cache when nothing moved, whatever paths are named, and the hash
28
+ // of the tests holds theirs, as Bazel's key of a target holds its transitive inputs; a change to
29
+ // a package the tests import, or to what they read, plays them again. Every other run of
30
+ // Turborepo plays with the variable unset.
31
+ // - Each run of Turborepo plays every task whose dependencies succeed
32
+ // (`--continue=dependencies-successful`), as Bazel's `--keep_going` and `npm run verifier` do:
33
+ // each build that fails is named on Turborepo's line « Failed: », a test that waits for a failed
34
+ // build does not play, and the exit code stays red. A build that fails first does not hide another
35
+ // one, which the run would otherwise cancel unreported.
36
+ // - `--force` with paths plays the tests named for real, their builds replayed from the cache:
37
+ // Turborepo's `--force` re-executes every task of the run, so the variable carries the moment of
38
+ // the run instead (`rejouer`), a value no cached run of the tests has. A test suspected unstable
39
+ // is played again, and a build, which the same inputs give again, is not.
40
+ // - In the envelope of an agent (`GRILLHOUSE_ENVELOPPE`, scripts/enveloppe/composant.mjs), the
41
+ // neighbours and their traces arrive built by the launcher and read-only, as in a sandbox of
42
+ // Bazel: the build is the named packages' alone, with the traces of those packages their tests
43
+ // wait for, which live in the package the agent writes (`turbo run build <name>#trace:<t>…
44
+ // --only --filter=<name>`, their task ids read in Turborepo's plan of the tests,
45
+ // `turbo run test:vitest --filter=<name> --dry=json`), never their dependencies nor a
46
+ // neighbour's trace; then the tests alone (`turbo run test:vitest --only --filter=<name>`), the
47
+ // paths in the variable. A named package that the envelope shows read-only (the shared test
48
+ // material, for every role but the testwriter) is not built, nor are its traces: it arrives built,
49
+ // as a read-only input of Bazel, and its tests play as it is; the frame knows it read-only by
50
+ // testing its directory for writing (`inscriptible`), where the envelope mounts it, and the logs
51
+ // of its tasks go to the envelope's directories proper to the session (`sortiesDeTest`,
52
+ // scripts/enveloppe/enveloppe.mjs), never into its source. There the hash of the tests sees
53
+ // neither the code of the neighbours nor their builds: the verdict that counts is the reviewer's
54
+ // and the integration's, out of the envelope.
55
+ // - An argument that begins with « - » is an option of Vitest, written `--<name>=<value>`, carried
56
+ // in the variable beside the paths.
57
+ // - A night test or a time budget bench, which the day leaves out, plays when a path names its own
58
+ // file, under the measure of time of the day's configuration (scripts/vitest-config/cibles.mjs);
59
+ // a directory or a package named leaves them out.
60
+ // - A run that plays no test fails: the frame's base configuration of Vitest keeps Vitest's
61
+ // `passWithNoTests: false` (scripts/vitest-config/base.mjs).
62
+ // - Under the circuit's mark (`GRILLHOUSE_DEPUIS_LE_VERT`, its proof and its checks,
63
+ // scripts/circuit.mjs), a run measures itself against the last green, as `npm run verifier`
64
+ // does (scripts/dernier-vert.mjs): in a package or a directory named, the tests Vitest relates to
65
+ // the files changed since it, every test when a change lies out of Vitest's graph; a file named
66
+ // itself plays, changed or not; `--force` plays them all. Without the mark, every test named
67
+ // plays.
68
+ // - While the environment names a results directory (`GRILLHOUSE_RESULTATS`,
69
+ // scripts/resultats.mjs), each run of Turborepo writes its summary (`--summarize`), moved there,
70
+ // and Vitest its JSON report there: what the circuit reads of a run.
71
+ // - While the environment names the progress directory of a pass (`GRILLHOUSE_AVANCEMENT`,
72
+ // scripts/avancement.mjs: the circuit's proof and checks), each run of the tests plans its
73
+ // tasks first, and Vitest writes there the files done and planned of each.
74
+ // - The run has a base of its own (scripts/vitest-config/base-de-passe.mjs), which it sweeps first
75
+ // of the bases a killed run left: it names it in TMPDIR to every run of Turborepo, which the
76
+ // root's `globalPassThroughEnv` passes on to every task, and removes it at its end.
77
+ // The exit code is Turborepo's; 2 for a path refused.
78
+ import { spawnSync } from 'node:child_process';
79
+ import { accessSync, constants, existsSync, readdirSync, statSync } from 'node:fs';
80
+ import path from 'node:path';
81
+ import { planifier } from './avancement.mjs';
82
+ import { composantDeLEnveloppe } from './enveloppe/composant.mjs';
83
+ import { paquets } from './paquets.mjs';
84
+ import { garderLesResumes, RESULTATS } from './resultats.mjs';
85
+ import { tracesDuPlan } from './turbo-des-paquets.mjs';
86
+ import { AVANCEMENT } from './vitest-config/avancement.mjs';
87
+ import { VARIABLE } from './vitest-config/cibles.mjs';
88
+ import { CHANGEMENTS } from './vitest-config/changements.mjs';
89
+ import { DEPUIS_LE_VERT, mesure } from './dernier-vert.mjs';
90
+ import { ouvrirBase } from './vitest-config/base-de-passe.mjs';
91
+
92
+ const racine = process.cwd();
93
+ // The base of the run (scripts/vitest-config/base-de-passe.mjs): this process and every run of
94
+ // Turborepo write in it (TMPDIR), and it goes at the end of the run.
95
+ const base = ouvrirBase('tests');
96
+ process.env.TMPDIR = base.dossier;
97
+ const enveloppe = composantDeLEnveloppe() !== null;
98
+ const args = process.argv.slice(2);
99
+ const force = args.includes('--force');
100
+ const options = args.filter(a => a.startsWith('-') && a !== '--force');
101
+ const chemins = args.filter(a => !a.startsWith('-'));
102
+ // Under the circuit's mark, the tests measured against the last green; `--force` plays them all.
103
+ const depuis = process.env[DEPUIS_LE_VERT] ? mesure(racine, { tout: force }) : null;
104
+ if (depuis) process.stdout.write(`${depuis.ligne}\n`);
105
+
106
+ // One Turborepo run of <args>, VARIABLE set to <tests> (unset without), each task whose
107
+ // dependencies succeed played to its end; its exit code. While the run names a results directory
108
+ // (RESULTATS), Turborepo writes its summary, which goes there.
109
+ const turbo = (args, tests) => {
110
+ const env = { ...process.env };
111
+ delete env[VARIABLE];
112
+ delete env[CHANGEMENTS];
113
+ Object.assign(env, depuis?.env ?? {});
114
+ if (tests) env[VARIABLE] = JSON.stringify(tests);
115
+ const resume = env[RESULTATS] ? ['--summarize'] : [];
116
+ const runs = path.join(racine, '.turbo', 'runs');
117
+ const avant = new Set(existsSync(runs) ? readdirSync(runs) : []);
118
+ // A run of the tests under a progress directory plans its tasks (scripts/avancement.mjs).
119
+ if (args[0] === 'test:vitest') {
120
+ planifier(env[AVANCEMENT], {
121
+ racine,
122
+ args: [...args, '--continue=dependencies-successful'],
123
+ env,
124
+ });
125
+ }
126
+ const code =
127
+ spawnSync('turbo', ['run', ...args, '--continue=dependencies-successful', ...resume], {
128
+ cwd: racine,
129
+ stdio: 'inherit',
130
+ env,
131
+ }).status ?? 1;
132
+ if (resume.length) garderLesResumes(racine, avant, env);
133
+ return code;
134
+ };
135
+
136
+ // The trace tasks of the packages <noms> their tests wait for, read in Turborepo's plan of the
137
+ // tests <filtres> select: `{ ids }`; a plan that fails gives `{ code }`, its output written.
138
+ const traces = (filtres, noms) => {
139
+ const plan = spawnSync('turbo', ['run', 'test:vitest', ...filtres, '--dry=json'], {
140
+ cwd: racine,
141
+ encoding: 'utf8',
142
+ maxBuffer: 256 * 1024 * 1024,
143
+ });
144
+ if (plan.status !== 0) {
145
+ process.stderr.write(`${plan.stdout ?? ''}${plan.stderr ?? ''}`);
146
+ return { code: plan.status ?? 1 };
147
+ }
148
+ return { ids: tracesDuPlan(JSON.parse(plan.stdout), t => noms.has(t.package)) };
149
+ };
150
+
151
+ // The tests <filtres> select, which receive <passes> in VARIABLE: one run with what they depend on;
152
+ // in an envelope, the build of the packages they name with their own traces, then the tests alone.
153
+ // Its exit code: the first that is not 0, else the tests'.
154
+ const cibles = (filtres, passes) => {
155
+ const tests = { arguments: passes, ...(force ? { rejouer: new Date().toISOString() } : {}) };
156
+ if (!enveloppe) return turbo(['test:vitest', ...filtres], tests);
157
+ // A package the envelope shows read-only arrives built: only the packages it shows in writing
158
+ // build, as the envelope mounts them (`inscriptible`).
159
+ const construits = filtres.filter(f => inscriptible(dossiers.get(f.slice('--filter='.length))));
160
+ if (filtres.length === 0 || construits.length > 0) {
161
+ const noms = new Set(construits.map(f => f.slice('--filter='.length)));
162
+ const propres = construits.length ? traces(construits, noms) : { ids: [] };
163
+ if (propres.code !== undefined) return propres.code;
164
+ const construction = turbo(['build', ...propres.ids, '--only', ...construits]);
165
+ if (construction !== 0) return construction;
166
+ }
167
+ return turbo(['test:vitest', '--only', ...filtres], tests);
168
+ };
169
+
170
+ // True when this process may write the directory <dir>: false under a read-only mount.
171
+ const inscriptible = dir => {
172
+ try {
173
+ accessSync(dir, constants.W_OK);
174
+ return true;
175
+ } catch {
176
+ return false;
177
+ }
178
+ };
179
+
180
+ const liste = paquets(racine);
181
+ // The directory of each package of the frame, by its name.
182
+ const dossiers = new Map(liste.map(p => [p.pkg.name, path.join(racine, p.dossier)]));
183
+
184
+ // The run the command asks for: its exit code.
185
+ const lancer = () => {
186
+ if (chemins.length === 0) {
187
+ return turbo(
188
+ ['test:vitest', ...(force ? ['--force'] : [])],
189
+ options.length ? { arguments: options } : undefined
190
+ );
191
+ }
192
+ if (liste.length === 0) return cibles([], [...chemins, ...options]);
193
+
194
+ // The package of the frame that holds each path, the paths made absolute; the refusals.
195
+ const refus = [];
196
+ const noms = new Set();
197
+ const absolus = [];
198
+ for (const chemin of chemins) {
199
+ const abs = path.resolve(racine, chemin);
200
+ const p = liste.find(({ dossier }) =>
201
+ `${abs}${path.sep}`.startsWith(path.join(racine, dossier, path.sep))
202
+ );
203
+ if (!p) {
204
+ refus.push(
205
+ `⛔ ${chemin} : hors de tout paquet ; un chemin de test s'écrit depuis la racine, dans un paquet.`
206
+ );
207
+ } else if (p.hors) {
208
+ refus.push(
209
+ `⛔ ${chemin} : ${p.dossier} est hors cadre (docs/agents/hors-cadre.txt) ; Turborepo n'en joue aucune tâche.`
210
+ );
211
+ } else if (typeof p.pkg.scripts?.['test:vitest'] !== 'string') {
212
+ refus.push(`⛔ ${chemin} : ${p.dossier} n'a pas de script test:vitest.`);
213
+ } else {
214
+ noms.add(p.pkg.name);
215
+ // A directory ends with « / »: Vitest matches the start of a path, and packages/a is the start
216
+ // of packages/ab.
217
+ const dossier = existsSync(abs) && statSync(abs).isDirectory();
218
+ absolus.push(dossier ? `${abs}${path.sep}` : abs);
219
+ }
220
+ }
221
+ if (refus.length) {
222
+ for (const l of refus) process.stderr.write(`${l}\n`);
223
+ return 2;
224
+ }
225
+ return cibles(
226
+ [...noms].sort().map(n => `--filter=${n}`),
227
+ [...absolus, ...options]
228
+ );
229
+ };
230
+
231
+ try {
232
+ process.exitCode = lancer();
233
+ } finally {
234
+ depuis?.retirer();
235
+ base.fermer();
236
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ # Crée un ticket sous sa mère, titré « <numéro> — <sujet> — <composants> », et affiche son id.
3
+ # ticket.sh <mère> <sujet> <composants> <corps> [étiquette]
4
+ # Les composants tiennent en un argument, séparés par des virgules, le fournisseur d'abord :
5
+ # « 020-loader, 050-emitter ».
6
+ # Un corps @<fichier> se lit dans ce fichier. L'étiquette vaut a-valider par défaut ;
7
+ # decouverte crée le ticket fermé (« découverte consignée »).
8
+ # Le numéro est l'id sans le préfixe entier du projet (bd config get issue_prefix).
9
+ set -eu
10
+ if [ $# -lt 4 ]; then
11
+ sed -n '2,9p' "$0" >&2
12
+ exit 2
13
+ fi
14
+ mere=$1 sujet=$2 composants=$3 corps=$4 etiquette=${5:-a-valider}
15
+
16
+ case "$corps" in
17
+ @*) source_corps=(--body-file "${corps#@}") ;;
18
+ *) source_corps=(-d "$corps") ;;
19
+ esac
20
+ n=$(bd create "$sujet" --parent "$mere" --no-inherit-labels -t task -l "$etiquette" "${source_corps[@]}" --silent)
21
+ bd update "$n" --title "${n#"$(bd config get issue_prefix)"-} — $sujet — $composants" >/dev/null
22
+ [ "$etiquette" = decouverte ] && bd close "$n" -r "découverte consignée" >/dev/null
23
+ echo "$n"