@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,127 @@
1
+ #!/usr/bin/env bash
2
+ # The removal of a working copy, called by the three places that end one: the agent copy (by
3
+ # scripts/integration/integrer.mjs once its lot is committed, and by the supervisor for a ticket
4
+ # closed without integration), the integration copy (by the same script) and the night copy
5
+ # (scripts/nuit.sh), the last two when their existing copy does not get ready.
6
+ #
7
+ # npx grillhouse retirer-copie [--jetable] [--garder-travail] <copie>
8
+ #
9
+ # - The copy is a path read from the current directory, as git reads it, and must be a copy that
10
+ # `git worktree list` names for the repository of the current directory: the main working tree,
11
+ # an absent path, a plain folder or a submodule's folder is refused, nothing touched.
12
+ # - A copy that carries a submodule is removed too: `git worktree remove` refuses it, so the
13
+ # script reads the copy first, then removes it with `git worktree remove --force`. Only the named
14
+ # copy goes, with the link to a shared submodule (`grillhouse.partage`); the main repository and
15
+ # its submodules (.git/modules) stay as they are.
16
+ # - Without `--jetable`, the reading stands in for what `--force` would erase: a copy that carries
17
+ # local work (a modified tracked file, an untracked file outside the ignored ones, a submodule
18
+ # modified or at another commit, read by `git status --ignore-submodules=none`, whatever the
19
+ # project's `submodule.<name>.ignore` or `diff.ignoreSubmodules`), or whose state does not read,
20
+ # is refused, and stays in place intact. Local work that has entered the branch `main` loses
21
+ # nothing and does not hold the copy: each file of the work (its deletion, or its submodule at a
22
+ # commit with no work in it) holds the content and the mode that a commit of `main` after the
23
+ # copy's base gave that path; a content `main` held before the base is not work that entered. The
24
+ # refusal names the work that has not entered. The agent copy's removal passes no option.
25
+ # - With `--jetable`, passed only by the integration and the night for their own copy, local work
26
+ # and an unreadable state do not hold the removal.
27
+ # - A locked copy (`git worktree lock`) is refused in every case.
28
+ # - The copy removed, the working directory of its ticket's circuit, `<copie>.travail` beside it
29
+ # (scripts/circuit.mjs), goes with it, as the workspace of a pipeline goes at its end: a ticket
30
+ # closed without integration (a final stop, an abandon) loses both by this one command. With
31
+ # `--garder-travail`, passed only by the integration, it stays: the circuit still runs, and the
32
+ # queue removes it at the ticket's closure (scripts/integration/file.mjs). A copy refused keeps
33
+ # it.
34
+ # - Exit 0 removed, 1 refused (the first line on stderr names the copy and what holds it), 2 a
35
+ # malformed call (the usage on stderr).
36
+ set -uo pipefail
37
+ jetable=0
38
+ garder=0
39
+ while [ $# -gt 0 ]; do
40
+ case $1 in
41
+ --jetable) jetable=1 ;;
42
+ --garder-travail) garder=1 ;;
43
+ *) break ;;
44
+ esac
45
+ shift
46
+ done
47
+ [ $# -eq 1 ] && [ "${1#-}" = "$1" ] || {
48
+ echo "usage: retirer-copie.sh [--jetable] [--garder-travail] <copie>" >&2
49
+ exit 2
50
+ }
51
+ copie=$1
52
+
53
+ refuser() {
54
+ echo "$1" >&2
55
+ shift
56
+ [ $# -eq 0 ] || echo "$*" >&2
57
+ exit 1
58
+ }
59
+
60
+ [ -n "$copie" ] && [ -d "$copie" ] || refuser "la copie $copie n'existe pas : rien n'est retiré."
61
+ chemin=$(cd "$copie" && pwd -P) || refuser "la copie $copie ne se lit pas : rien n'est retiré."
62
+ liste=$(git worktree list --porcelain 2>&1) ||
63
+ refuser "la liste des copies ne se lit pas pour retirer $copie : rien n'est retiré." "$liste"
64
+
65
+ # The list is one block per copy, separated by a blank line: the first block is the main working
66
+ # tree, never removable; a `locked` line marks a locked copy. `trouvee` is the verdict of the block
67
+ # of the copy, printed once (`dit`: `exit` runs the END rule, which must not read the same block
68
+ # again).
69
+ trouvee=$(printf '%s\n' "$liste" | awk -v cible="$chemin" '
70
+ function fin() {
71
+ if (n > 1 && dossier != "") {
72
+ cmd = "cd \"" dossier "\" 2>/dev/null && pwd -P"
73
+ cmd | getline reel; close(cmd)
74
+ if (reel == cible) { print (verrou ? "verrouillee" : "libre"); dit = 1; exit }
75
+ }
76
+ }
77
+ /^worktree / { fin(); n++; dossier = substr($0, 10); verrou = 0; reel = ""; next }
78
+ /^locked/ { verrou = 1 }
79
+ END { if (!dit) fin() }
80
+ ')
81
+ [ -n "$trouvee" ] || refuser "$copie n'est pas une copie inscrite de ce dépôt (git worktree list) : rien n'est retiré."
82
+ [ "$trouvee" != verrouillee ] || refuser "la copie $copie est verrouillée (git worktree lock) : elle n'est pas retirée."
83
+
84
+ # Whether the work at path $1 of the copy has entered `main`: what the copy holds there (a file's
85
+ # blob and mode, a submodule's commit once its own state is clean, zeros for a deleted path) is
86
+ # what a commit of `main` after the copy's base (`HEAD..refs/heads/main` in the copy) gave that
87
+ # path, mode included, as git judges a branch already merged.
88
+ est_entre() {
89
+ local p=$1 id mode
90
+ if [ -e "$chemin/$p/.git" ]; then
91
+ [ -z "$(git -C "$chemin/$p" status --porcelain --ignore-submodules=none 2>/dev/null)" ] ||
92
+ return 1
93
+ id=$(git -C "$chemin/$p" rev-parse HEAD 2>/dev/null) || return 1
94
+ mode=160000
95
+ elif [ -L "$chemin/$p" ]; then
96
+ id=$(git -C "$chemin" hash-object --no-filters -- "$p" 2>/dev/null) || return 1
97
+ mode=120000
98
+ elif [ -e "$chemin/$p" ]; then
99
+ id=$(git -C "$chemin" hash-object -- "$p" 2>/dev/null) || return 1
100
+ if [ -x "$chemin/$p" ]; then mode=100755; else mode=100644; fi
101
+ else
102
+ id=0
103
+ mode=000000
104
+ fi
105
+ git -C "$chemin" log HEAD..refs/heads/main --no-renames --format= --raw --no-abbrev -- "$p" \
106
+ 2>/dev/null |
107
+ awk -v id="$id" -v mode="$mode" '
108
+ $2 == mode && ($4 == id || (id == 0 && $4 ~ /^0+$/)) { ok = 1 }
109
+ END { exit !ok }'
110
+ }
111
+
112
+ if [ "$jetable" -eq 0 ]; then
113
+ etat=$(git -C "$chemin" status --porcelain -z -uall --no-renames --ignore-submodules=none 2>&1 |
114
+ tr '\0' '\n') ||
115
+ refuser "l'état de la copie $copie ne se lit pas : elle n'est pas retirée." "$etat"
116
+ restant=
117
+ while IFS= read -r ligne; do
118
+ [ -n "$ligne" ] || continue
119
+ est_entre "${ligne:3}" || restant+="${restant:+$'\n'}$ligne"
120
+ done <<<"$etat"
121
+ [ -z "$restant" ] ||
122
+ refuser "la copie $copie porte du travail local : elle n'est pas retirée." "$restant"
123
+ fi
124
+
125
+ sortie=$(git worktree remove --force "$chemin" 2>&1) ||
126
+ refuser "la copie $copie ne se retire pas :" "$sortie"
127
+ [ "$garder" -eq 1 ] || rm -rf -- "$chemin.travail"
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env bash
2
+ # SessionStart hook: tells the session what the project still lacks before work can start.
3
+ # Silent once the project is set up and initialised. It changes nothing, save the lock of the
4
+ # supervisor below.
5
+ #
6
+ # The project's own check: `grillhouse.demarrage` in the package.json of the root names an npm
7
+ # script (`npm pkg set grillhouse.demarrage=<script>`). The session start plays
8
+ # `npm run --silent <script>` at the root; each non-blank line it writes joins the list « Pas encore
9
+ # renseignés », as an item to deal with. The script has 10 s: past them its whole process group is
10
+ # stopped. A failure (non-zero code) or a stop adds a line that says so, after the lines already
11
+ # written; the session start goes on. Without the key (or with null), nothing runs; a value that is
12
+ # not a non-blank string runs nothing and adds a line that says so. An agent's copy does not play it.
13
+ #
14
+ # The frame's plugins: scripts/greffons.mjs --absents reads `.claude/settings.json` and
15
+ # `claude plugin list --json` (each call stopped after 5 s). Each declared plugin not installed for
16
+ # the project joins « Manque » as « le greffon <id> », the missing claude command as « la commande
17
+ # claude » (the line gives `npx grillhouse maj`, which installs them), and a reading that fails
18
+ # or is stopped as « la lecture des greffons … ». The same reading names the frame's mod installed
19
+ # for the project at another version than the package @roomi-fields/grillhouse, both released under
20
+ # one version, with the command that aligns the one behind. An agent's copy does not read them.
21
+ #
22
+ # One supervisor per project (scripts/superviseur.mjs): the session takes the lock of its
23
+ # repository; a second session, while the first one lives, receives first the section « Un autre
24
+ # superviseur tient ce dépôt », which asks it to say so to the owner and to launch no circuit. An
25
+ # agent's copy and a session of a role the circuit launches take nothing and receive nothing.
26
+ #
27
+ # A session of a role agent of the frame (`claude --agent <rôle>`, the circuit's sessions: the
28
+ # hook's input names it, `agent_type`, bare or as `grillhouse:<rôle>`, and the frame's plugin
29
+ # defines it, `$CLAUDE_PLUGIN_ROOT/agents/<rôle>.md`, the plugin whose hook runs this script) receives nothing:
30
+ # what the project lacks is the supervisor's to deal with, and in an envelope the documents of the
31
+ # hidden packages read as absent.
32
+ set -uo pipefail
33
+
34
+ # The project is Claude Code's, else the working directory; the frame's scripts are this one's
35
+ # neighbours.
36
+ root="${CLAUDE_PROJECT_DIR:-$PWD}"
37
+ ici="$(cd "$(dirname "$0")" && pwd)"
38
+ entree=
39
+ [ -t 0 ] || entree="$(cat)"
40
+ role="$(printf '%s' "$entree" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const a=JSON.parse(s).agent_type;if(typeof a==="string"&&/^[A-Za-z0-9_:-]+$/.test(a))process.stdout.write(a)}catch{}})' 2>/dev/null)"
41
+ role="${role#grillhouse:}"
42
+ [ -n "$role" ] && [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "$CLAUDE_PLUGIN_ROOT/agents/$role.md" ] && exit 0
43
+ if [ -f "$root/.claude/template" ]; then
44
+ echo "## Gabarit Roomi's Grillhouse"
45
+ echo "Ce dossier est le gabarit du framework, pas un projet : il ne s'initialise pas. Un projet neuf se crée par \`npx @roomi-fields/grillhouse init <chemin> [préfixe]\` ; sa première séance propose le grill d'initialisation."
46
+ exit 0
47
+ fi
48
+
49
+ # One supervisor per project: the lock of the repository, or the section that says who holds it.
50
+ superviseur="$(node "$ici/superviseur.mjs" "$root" 2>/dev/null)"
51
+ [ -z "$superviseur" ] || echo "$superviseur"
52
+
53
+ missing=()
54
+
55
+ command -v bd >/dev/null || missing+=("l'outil de tickets bd (Beads : \`npm install -g @beads/bd\`)")
56
+ [ -d "$root/.beads" ] || missing+=("le magasin de tickets (Beads : \`bd init\`)")
57
+ # The index of CodeGraph is for the roles that see the whole repository: an agent's copy (a git
58
+ # worktree under .claude/worktrees/) is asked for neither the tool nor the index.
59
+ copie_agent=
60
+ case "$root" in
61
+ */.claude/worktrees/*)
62
+ [ "$(git -C "$root" rev-parse --git-dir 2>/dev/null)" != "$(git -C "$root" rev-parse --git-common-dir 2>/dev/null)" ] && copie_agent=1
63
+ ;;
64
+ esac
65
+ if [ -z "$copie_agent" ]; then
66
+ command -v codegraph >/dev/null || missing+=("l'outil CodeGraph (\`npm install -g @colbymchenry/codegraph\`)")
67
+ [ -d "$root/.codegraph" ] || missing+=("l'index CodeGraph du code (\`codegraph init .\`)")
68
+ fi
69
+ [ ! -f "$root/package.json" ] || [ -d "$root/node_modules" ] || missing+=("les dépendances npm (\`npm install\`)")
70
+ # The frame's plugins not installed for the project (scripts/greffons.mjs): the plugins are the
71
+ # machine's installation for the project, so an agent's copy does not read them.
72
+ if [ -z "$copie_agent" ]; then
73
+ while IFS= read -r ligne; do
74
+ case "$ligne" in
75
+ claude) missing+=("la commande claude") ;;
76
+ "greffon "*) missing+=("le greffon ${ligne#greffon }") ;;
77
+ "lecture "*) missing+=("la lecture des greffons : ${ligne#lecture }") ;;
78
+ "ecart "*)
79
+ read -r _ e_id e_greffon e_paquet e_commande <<< "$ligne"
80
+ ecart_versions="Le greffon $e_id est en $e_greffon, le paquet @roomi-fields/grillhouse en $e_paquet : les deux sortent ensemble, sous une même version. Aligne-les : \`$e_commande\`."
81
+ ;;
82
+ esac
83
+ done < <(timeout -k 1 8 node "$ici/greffons.mjs" --absents "$root" 2>/dev/null)
84
+ fi
85
+ # The frame's git hooks absent from the folder git reads (scripts/crochets.mjs), each named.
86
+ while IFS= read -r crochet; do
87
+ [ -n "$crochet" ] && missing+=("le crochet git $crochet du cadre")
88
+ done < <(node "$ici/crochets.mjs" --absents "$root" 2>/dev/null)
89
+ command -v bwrap >/dev/null || missing+=("bubblewrap (bwrap), l'enveloppe des agents : sudo apt install bubblewrap")
90
+
91
+ # The key elements the skills read; each one still empty is proposed for a grill.
92
+ pending=()
93
+ charter="$root/CLAUDE.md"
94
+ if [ ! -f "$charter" ]; then
95
+ pending+=("la charte (CLAUDE.md)")
96
+ else
97
+ # an unfilled field of the charter template: <…> outside code spans
98
+ node -e 'const t=require("fs").readFileSync(process.argv[1],"utf8").replace(/`[^`]*`/g,"");process.exit(/<[^<>\n]+>/.test(t)?0:1)' "$charter" \
99
+ && pending+=("les champs <…> encore vides de la charte (CLAUDE.md)")
100
+ # the arbitration criteria: a heading about arbitration in the charter or a reference document
101
+ grep -qiE '^#+ .*(arbitr)' "$charter" "$root"/docs/*.md 2>/dev/null \
102
+ || pending+=("les critères d'arbitrage (section « Comment on arbitre » de CLAUDE.md : la référence mature du domaine, l'existant à reprendre, les exigences du domaine)")
103
+ fi
104
+ # The packages out of the frame: docs/agents/hors-cadre.txt ("<package> <reason>", e.g. a frozen
105
+ # version), read by scripts/paquets.mjs as every check reads it; Turborepo plays none of
106
+ # its tasks (scripts/turbo-des-paquets.mjs). Line 1: the names listed;
107
+ # line 2: the names that no directory under packages/ or src/ bears.
108
+ { read -r hors; read -r inconnus; } < <(node --input-type=module -e '
109
+ const { pathToFileURL } = await import("node:url");
110
+ const { horsCadre } = await import(pathToFileURL(process.argv[1]).href);
111
+ const { horsCadreInconnus } = await import(pathToFileURL(process.argv[2]).href);
112
+ process.stdout.write(`${[...horsCadre(process.argv[3])].join(" ")}\n${horsCadreInconnus(process.argv[3]).join(" ")}\n`);
113
+ ' "$ici/paquets.mjs" "$ici/consommateurs.mjs" "$root" 2>/dev/null)
114
+ # One package: docs/ at the root. Several: each package under packages/ has its own three documents,
115
+ # unless it is out of the frame.
116
+ if ls -d "$root"/packages/*/ >/dev/null 2>&1; then
117
+ for pkg in "$root"/packages/*/; do
118
+ name="$(basename "$pkg")"
119
+ case " ${hors:-} " in *" $name "*) continue ;; esac
120
+ for doc in ARCHITECTURE CADRE INTERFACE; do
121
+ [ -f "$pkg/docs/$doc.md" ] || pending+=("packages/$name/docs/$doc.md")
122
+ done
123
+ done
124
+ else
125
+ for doc in ARCHITECTURE CADRE INTERFACE; do
126
+ [ -f "$root/docs/$doc.md" ] || pending+=("docs/$doc.md")
127
+ done
128
+ fi
129
+ [ -f "$root/CONTEXT.md" ] || pending+=("le lexique du domaine (CONTEXT.md)")
130
+
131
+ # The project's own check (grillhouse.demarrage, described in the header), outside an agent's copy.
132
+ # The reading gives « script<TAB>name », « invalide<TAB>value as JSON », or nothing.
133
+ lecture=
134
+ [ -n "$copie_agent" ] || lecture="$(node -e '
135
+ const v = require(process.argv[1]).grillhouse?.demarrage;
136
+ if (v == null) process.exit(0);
137
+ if (typeof v === "string" && v.trim()) process.stdout.write("script\t" + v);
138
+ else process.stdout.write("invalide\t" + JSON.stringify(v));
139
+ ' "$root/package.json" 2>/dev/null)"
140
+ demarrage=
141
+ case "$lecture" in
142
+ script$'\t'*) demarrage="${lecture#script$'\t'}" ;;
143
+ invalide$'\t'*) pending+=("la clé grillhouse.demarrage vaut ${lecture#invalide$'\t'}, pas un nom de script npm : rien n'est lancé") ;;
144
+ esac
145
+ if [ -n "$demarrage" ]; then
146
+ # timeout signals the script's whole process group, so a child that keeps the output open stops too.
147
+ sortie="$(cd "$root" && timeout -k 2 10 npm run --silent "$demarrage" 2>/dev/null)"
148
+ code=$?
149
+ while IFS= read -r ligne; do
150
+ ligne="${ligne%$'\r'}"
151
+ [ -n "${ligne//[[:space:]]/}" ] && pending+=("$ligne")
152
+ done <<< "$sortie"
153
+ if [ "$code" -eq 124 ] || [ "$code" -eq 137 ]; then
154
+ pending+=("le contrôle d'ouverture \`npm run $demarrage\` (grillhouse.demarrage) a été interrompu après 10 s")
155
+ elif [ "$code" -ne 0 ]; then
156
+ pending+=("le contrôle d'ouverture \`npm run $demarrage\` (grillhouse.demarrage) échoue (code $code)")
157
+ fi
158
+ fi
159
+
160
+ # The agents run the tests by `npm test`; the integration, the queue and the night run the guards
161
+ # and the tests by `npm run verifier` (Turborepo): two npm scripts to wire.
162
+ wiring=()
163
+ if [ -f "$root/package.json" ]; then
164
+ for s in test verifier; do
165
+ node -e 'const p=require(process.argv[1]);process.exit(p.scripts&&p.scripts[process.argv[2]]?0:1)' \
166
+ "$root/package.json" "$s" 2>/dev/null || wiring+=("$s")
167
+ done
168
+ fi
169
+
170
+ # Once the architecture is written, the night (the full pass played for real, the night tests and
171
+ # the time budget benches) needs its line in the crontab.
172
+ if [ -f "$root/docs/ARCHITECTURE.md" ] && ! crontab -l 2>/dev/null | grep -qF "cd $root && node_modules/.bin/grillhouse nuit"; then
173
+ nuit="0 3 * * * cd $root && node_modules/.bin/grillhouse nuit"
174
+ fi
175
+
176
+ # An epic is a chantier, at the root: one with a mother is a mother ticket typed by mistake.
177
+ epopees="$(cd "$root" && bd list --type epic --all --json --limit 0 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(JSON.parse(s).filter(t=>t.parent).map(t=>t.id).join(" "))}catch{}})')"
178
+
179
+ [ ${#missing[@]} -eq 0 ] && [ ${#pending[@]} -eq 0 ] && [ ${#wiring[@]} -eq 0 ] && [ -z "${nuit:-}" ] && [ -z "$epopees" ] && [ -z "${inconnus:-}" ] && [ -z "${ecart_versions:-}" ] && exit 0
180
+
181
+ echo "## Éléments du projet à définir (Roomi's Grillhouse)"
182
+ if [ ${#missing[@]} -gt 0 ]; then
183
+ echo "Manque : $(IFS=';'; echo "${missing[*]}" | sed "s/;/, /g"). Installe-les toi-même avant tout autre geste, sans invite, chacun par la commande qu'il nomme ; \`npx grillhouse maj\` installe les greffons et les crochets git du cadre."
184
+ fi
185
+ if [ ${#pending[@]} -gt 0 ]; then
186
+ echo "Pas encore renseignés :"
187
+ for p in "${pending[@]}"; do echo "- $p"; done
188
+ echo "Dès ta première réponse, propose au responsable de les griller, chacun avec ce que tu recommandes pour ce projet (compétence \`grill\`). Il peut accepter, en choisir un, ou reporter ; tant qu'un élément reste vide, cette proposition revient à chaque séance."
189
+ fi
190
+ if [ ${#wiring[@]} -gt 0 ]; then
191
+ echo "À brancher : $(IFS=' '; echo "${wiring[*]}") dans le package.json de la racine. L'intégration (\`npx grillhouse integrer\`, contrat en tête de son script) et la nuit lancent \`npm run verifier\` (Turborepo, \`turbo.json\` : construction, puis gardes et tests), les agents \`npm test\` ; sans eux, l'intégration refuse chaque lot. Un projet testé par Vitest les reçoit de \`npm run grillhouse:maj\` ; sinon, écris-les pour ses tests."
192
+ fi
193
+ if [ -n "${nuit:-}" ]; then
194
+ echo "La nuit n'est pas programmée : elle rejoue pour de vrai la passe complète, puis les tests de nuit et les bancs de budget, que le jour ne joue pas (\`npx grillhouse nuit\`). Propose au responsable d'ajouter à la table des tâches du poste (\`crontab -e\`) : \`$nuit\`."
195
+ fi
196
+ if [ -n "$epopees" ]; then
197
+ echo "Épopées qui ont une mère : $epopees. Une épopée est un chantier, à la racine ; une mère sous un chantier est une tâche (\`bd update <id> -t task\`, docs/agents/issue-tracker.md, « Numéros et titres »)."
198
+ fi
199
+ if [ -n "${inconnus:-}" ]; then
200
+ echo "docs/agents/hors-cadre.txt nomme un dossier absent de packages/ et de src/ : $inconnus. Corrige la ligne (faute de frappe) ou retire-la : un nom sans dossier n'écarte rien."
201
+ fi
202
+ [ -n "${ecart_versions:-}" ] && echo "$ecart_versions"
203
+ exit 0
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ // A release of Grillhouse, its package npm, which carries the frame's plugin: Grillhouse's own
3
+ // script, which no project runs (`grillhouse init` installs the template of a project alone).
4
+ //
5
+ // npx grillhouse sortie <x.y.z> the release's files: the package's package.json, CHANGELOG.md
6
+ // npx grillhouse sortie --etiqueter the annotated tag v<version> on HEAD, the release commit
7
+ // npx grillhouse sortie --verifier-etiquette <v> exits 0 when the tag <v> names the version of the package
8
+ // npx grillhouse sortie --entree-du-marche the entry of the plugin in the marketplace roomi-fields
9
+ //
10
+ // - The version is the `version` of the package's `packages/grillhouse/package.json`, which npm
11
+ // publishes; the plugin the package carries takes it when the package is packed
12
+ // (scripts/cadre.mjs), so the frame has one version. Semantic versioning in 0.x: a change that
13
+ // obliges a project to act raises the minor, a fix the patch.
14
+ // - Preparing writes the version given, above the current one, in the package's package.json, its
15
+ // other fields kept, and dates the changelog: « ## [Unreleased] » becomes « ## [<x.y.z>] - <today> » and an
16
+ // empty « ## [Unreleased] » opens above it. A release with nothing under « [Unreleased] » is
17
+ // refused. Nothing is written when a check fails.
18
+ // - Tagging creates the annotated tag `v<version>` on HEAD, once the release commit is made and
19
+ // the working tree holds no change. Nothing is pushed: the owner validates the push, whose
20
+ // command the script names; `.github/workflows/release.yml` then plays the CI on the tagged
21
+ // commit and, once it is green, checks the tag (`--verifier-etiquette`), publishes the package
22
+ // on the npm registry (the repository's secret NPM_TOKEN) and the release on GitHub.
23
+ // - The marketplace roomi-fields (GitHub, roomi-fields/claude-plugins, a repository of its own)
24
+ // lists the plugin by its source npm, ENTREE_DU_MARCHE: the package, without a subdirectory (a
25
+ // source npm has none: the package's root is the plugin's) and without a version, so the machine
26
+ // installs the version published last. `--entree-du-marche` prints it; the owner sets it in that
27
+ // repository once the package is published, a public gesture this script does not make.
28
+ import { execFileSync } from 'node:child_process';
29
+ import { readFileSync, writeFileSync } from 'node:fs';
30
+ import path from 'node:path';
31
+ import { pathToFileURL } from 'node:url';
32
+
33
+ const PAQUET = 'packages/grillhouse/package.json';
34
+
35
+ /** The entry of the frame's plugin in the marketplace roomi-fields. */
36
+ export const ENTREE_DU_MARCHE = {
37
+ name: 'grillhouse',
38
+ source: { source: 'npm', package: '@roomi-fields/grillhouse' },
39
+ };
40
+ const CHANGELOG = 'CHANGELOG.md';
41
+ const OUVERTE = '## [Unreleased]\n';
42
+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/;
43
+
44
+ const lire = (racine, f) => readFileSync(path.join(racine, f), 'utf8');
45
+
46
+ // The version of Grillhouse at <racine>: its package's.
47
+ export function versionDuCadre(racine) {
48
+ return JSON.parse(lire(racine, PAQUET)).version;
49
+ }
50
+
51
+ // Refuses a tag <etiquette> other than v<version> of the package at <racine>.
52
+ export function verifierEtiquette(racine, etiquette) {
53
+ const v = versionDuCadre(racine);
54
+ if (etiquette !== `v${v}`) {
55
+ throw new Error(`L'étiquette ${etiquette} ne nomme pas la version du paquet, ${v}.`);
56
+ }
57
+ }
58
+
59
+ const rang = v => SEMVER.exec(v)?.slice(1).map(Number) ?? null;
60
+ const audessus = (a, b) => {
61
+ for (let i = 0; i < 3; i++) {
62
+ if (a[i] !== b[i]) {
63
+ return a[i] > b[i];
64
+ }
65
+ }
66
+ return false;
67
+ };
68
+
69
+ // Writes the release <version> of Grillhouse at <racine>, dated <date> (YYYY-MM-DD).
70
+ export function preparerSortie(racine, version, date) {
71
+ const actuelle = versionDuCadre(racine);
72
+ const neuve = rang(version);
73
+ if (!neuve) {
74
+ throw new Error(`« ${version} » n'est pas une version x.y.z.`);
75
+ }
76
+ if (!audessus(neuve, rang(actuelle))) {
77
+ throw new Error(`${version} n'est pas au-dessus de la version actuelle, ${actuelle}.`);
78
+ }
79
+ const journal = lire(racine, CHANGELOG);
80
+ const debut = journal.indexOf(`\n${OUVERTE}`);
81
+ if (debut < 0) {
82
+ throw new Error(`${CHANGELOG} n'a pas de section « [Unreleased] ».`);
83
+ }
84
+ const corps = journal.slice(debut + 1 + OUVERTE.length);
85
+ const fin = corps.search(/^## \[/m);
86
+ if ((fin < 0 ? corps : corps.slice(0, fin)).trim() === '') {
87
+ throw new Error(`La section « [Unreleased] » de ${CHANGELOG} est vide : rien à sortir.`);
88
+ }
89
+ const paquet = JSON.parse(lire(racine, PAQUET));
90
+ paquet.version = version;
91
+ writeFileSync(path.join(racine, PAQUET), `${JSON.stringify(paquet, null, 2)}\n`);
92
+ writeFileSync(
93
+ path.join(racine, CHANGELOG),
94
+ `${journal.slice(0, debut + 1)}${OUVERTE}\n## [${version}] - ${date}\n${corps}`
95
+ );
96
+ }
97
+
98
+ // Creates the annotated tag of the release at <racine> on HEAD, and returns its name.
99
+ export function etiqueter(racine) {
100
+ const git = (...args) => execFileSync('git', ['-C', racine, ...args], { encoding: 'utf8' });
101
+ const etiquette = `v${versionDuCadre(racine)}`;
102
+ if (git('status', '--porcelain').trim() !== '') {
103
+ throw new Error(
104
+ "L'arbre de travail porte des changements : le commit de sortie vient d'abord."
105
+ );
106
+ }
107
+ if (git('tag', '--list', etiquette).trim() !== '') {
108
+ throw new Error(`L'étiquette ${etiquette} existe déjà.`);
109
+ }
110
+ git('tag', '-a', etiquette, '-m', `Roomi's Grillhouse ${etiquette.slice(1)}`);
111
+ return etiquette;
112
+ }
113
+
114
+ const USAGE =
115
+ 'Usage : npx grillhouse sortie <x.y.z> prépare la sortie (package.json du paquet, CHANGELOG.md)\n' +
116
+ " npx grillhouse sortie --etiqueter pose l'étiquette v<version> sur le commit de sortie\n" +
117
+ " npx grillhouse sortie --verifier-etiquette <v> vérifie que l'étiquette <v> nomme la version du paquet\n" +
118
+ ' npx grillhouse sortie --entree-du-marche écrit l’entrée du greffon dans la place de marché roomi-fields\n';
119
+
120
+ // Today, in the local calendar, as YYYY-MM-DD.
121
+ const aujourdhui = () => {
122
+ const d = new Date();
123
+ const n = x => String(x).padStart(2, '0');
124
+ return `${d.getFullYear()}-${n(d.getMonth() + 1)}-${n(d.getDate())}`;
125
+ };
126
+
127
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
128
+ const args = process.argv.slice(2);
129
+ const racine = process.cwd();
130
+ try {
131
+ if (args.length === 1 && args[0] === '--etiqueter') {
132
+ const e = etiqueter(racine);
133
+ process.stdout.write(
134
+ `Étiquette ${e} posée sur HEAD, en local. La poussée, que valide le responsable : git push origin ${e} ` +
135
+ '(la CI joue alors le commit, puis publie le paquet sur npm)\n'
136
+ );
137
+ } else if (args.length === 1 && args[0] === '--entree-du-marche') {
138
+ process.stdout.write(`${JSON.stringify(ENTREE_DU_MARCHE, null, 2)}\n`);
139
+ } else if (args.length === 2 && args[0] === '--verifier-etiquette') {
140
+ verifierEtiquette(racine, args[1]);
141
+ } else if (args.length === 1 && !args[0].startsWith('-')) {
142
+ preparerSortie(racine, args[0], aujourdhui());
143
+ process.stdout.write(
144
+ `Sortie ${args[0]} préparée : ${PAQUET}, ${CHANGELOG}. ` +
145
+ `Le commit de sortie, puis : npx grillhouse sortie --etiqueter\n`
146
+ );
147
+ } else {
148
+ process.stderr.write(USAGE);
149
+ process.exitCode = 2;
150
+ }
151
+ } catch (e) {
152
+ process.stderr.write(`${e.message}\n`);
153
+ process.exitCode = 1;
154
+ }
155
+ }