@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.
- package/.claude-plugin/plugin.json +9 -0
- package/METACADRE.md +41 -0
- package/agents/developpeur.md +10 -0
- package/agents/explorateur.md +9 -0
- package/agents/questionneur.md +11 -0
- package/agents/referent-technique.md +9 -0
- package/agents/regulateur.md +11 -0
- package/agents/relecteur.md +9 -0
- package/agents/testwriter.md +10 -0
- package/bin/grillhouse.mjs +125 -0
- package/cadre/templates/projet/.claude/settings.json +30 -0
- package/cadre/templates/projet/.editorconfig +47 -0
- package/cadre/templates/projet/.env.example +57 -0
- package/cadre/templates/projet/.eslintrc.cjs +56 -0
- package/cadre/templates/projet/.github/workflows/ci.yml +66 -0
- package/cadre/templates/projet/.mcp.json +13 -0
- package/cadre/templates/projet/.prettierrc +11 -0
- package/cadre/templates/projet/CHANGELOG.md +61 -0
- package/cadre/templates/projet/CLAUDE.md +46 -0
- package/cadre/templates/projet/CONTRIBUTING.md +243 -0
- package/cadre/templates/projet/CREDITS.md +41 -0
- package/cadre/templates/projet/README.md +152 -0
- package/cadre/templates/projet/_gitignore +91 -0
- package/cadre/templates/projet/_npmignore +46 -0
- package/cadre/templates/projet/deployment/README.md +98 -0
- package/cadre/templates/projet/deployment/docs/01-INSTALL.md +145 -0
- package/cadre/templates/projet/deployment/docs/02-CONFIGURATION.md +117 -0
- package/cadre/templates/projet/deployment/docs/03-API.md +264 -0
- package/cadre/templates/projet/deployment/docs/04-TROUBLESHOOTING.md +262 -0
- package/cadre/templates/projet/deployment/scripts/install.ps1 +100 -0
- package/cadre/templates/projet/deployment/scripts/start.ps1 +61 -0
- package/cadre/templates/projet/deployment/scripts/stop.ps1 +37 -0
- package/cadre/templates/projet/deployment/scripts/test.ps1 +118 -0
- package/cadre/templates/projet/docs/agents/issue-tracker.md +44 -0
- package/cadre/templates/projet/ecosystem.config.cjs +73 -0
- package/cadre/templates/projet/package.json +90 -0
- package/cadre/templates/projet/pitmaster/SUIVI.md +22 -0
- package/cadre/templates/projet/src/config.ts +68 -0
- package/cadre/templates/projet/src/errors.ts +102 -0
- package/cadre/templates/projet/src/index.ts +46 -0
- package/cadre/templates/projet/src/services/.gitkeep +8 -0
- package/cadre/templates/projet/src/types/index.ts +24 -0
- package/cadre/templates/projet/src/utils/logger.ts +106 -0
- package/cadre/templates/projet/tests/integration/.gitkeep +7 -0
- package/cadre/templates/projet/tests/unit/config.spec.ts +68 -0
- package/cadre/templates/projet/tests/unit/errors.spec.ts +132 -0
- package/cadre/templates/projet/tests/unit/types.spec.ts +73 -0
- package/cadre/templates/projet/tsconfig.eslint.json +9 -0
- package/cadre/templates/projet/tsconfig.json +41 -0
- package/cadre/templates/projet/tsdoc.json +6 -0
- package/cadre/templates/projet/vitest.config.ts +44 -0
- package/cadre/templates/turbo/paquets.json +29 -0
- package/cadre/templates/turbo/sans-paquets.json +28 -0
- package/charte.md +107 -0
- package/docs/ARCHITECTURE.md +90 -0
- package/hooks/crochet.mjs +33 -0
- package/hooks/hooks.json +58 -0
- package/hooks/register.tsx +558 -0
- package/package.json +75 -4
- package/scripts/avancement.mjs +244 -0
- package/scripts/bilan.mjs +523 -0
- package/scripts/branche-de-nuit.mjs +59 -0
- package/scripts/cadre.mjs +109 -0
- package/scripts/circuit.mjs +4375 -0
- package/scripts/code-mort.mjs +371 -0
- package/scripts/composants-affectes.mjs +598 -0
- package/scripts/consommateurs-arbitres.mjs +192 -0
- package/scripts/consommateurs.mjs +310 -0
- package/scripts/crochet.mjs +24 -0
- package/scripts/crochets.mjs +135 -0
- package/scripts/dernier-vert.mjs +208 -0
- package/scripts/dette.mjs +181 -0
- package/scripts/droits.mjs +354 -0
- package/scripts/enveloppe/codes.mjs +17 -0
- package/scripts/enveloppe/composant.mjs +11 -0
- package/scripts/enveloppe/enveloppe.mjs +628 -0
- package/scripts/enveloppe/lancer.sh +311 -0
- package/scripts/etapes.mjs +323 -0
- package/scripts/etat-du-circuit.mjs +104 -0
- package/scripts/frontieres.mjs +456 -0
- package/scripts/gardes-du-cadre.mjs +72 -0
- package/scripts/greffons.mjs +338 -0
- package/scripts/grillhouse-maj.mjs +514 -0
- package/scripts/index-interfaces.mjs +293 -0
- package/scripts/init.mjs +117 -0
- package/scripts/integration/file-attente.mjs +61 -0
- package/scripts/integration/file.mjs +494 -0
- package/scripts/integration/integrer.mjs +381 -0
- package/scripts/integration/rejeu.mjs +139 -0
- package/scripts/integration/verdict.mjs +13 -0
- package/scripts/interfaces-contre-code.mjs +547 -0
- package/scripts/ligne-etat/defaut.sh +110 -0
- package/scripts/ligne-etat/ligne-etat.mjs +93 -0
- package/scripts/migrations/0.23.0-charte-importee.mjs +47 -0
- package/scripts/migrations/0.23.0-config-vitest.mjs +105 -0
- package/scripts/migrations/0.23.0-crochets-de-seance.mjs +55 -0
- package/scripts/migrations/0.23.0-marche-du-projet.mjs +82 -0
- package/scripts/migrations.mjs +43 -0
- package/scripts/nuit.sh +269 -0
- package/scripts/paquets.mjs +79 -0
- package/scripts/poussee-cles.mjs +46 -0
- package/scripts/preparation.mjs +84 -0
- package/scripts/references-par-les-types.mjs +420 -0
- package/scripts/registre.mjs +336 -0
- package/scripts/resultats.mjs +580 -0
- package/scripts/resumes.mjs +57 -0
- package/scripts/retirer-copie.sh +127 -0
- package/scripts/session-start.sh +203 -0
- package/scripts/sortie.mjs +155 -0
- package/scripts/sous-modules.sh +316 -0
- package/scripts/structure-guard.sh +27 -0
- package/scripts/superviseur.mjs +102 -0
- package/scripts/tableau.mjs +1886 -0
- package/scripts/tests.mjs +236 -0
- package/scripts/ticket.sh +23 -0
- package/scripts/turbo-des-paquets.mjs +899 -0
- package/scripts/un-commit-par-ticket.mjs +212 -0
- package/scripts/verifier.mjs +469 -0
- package/scripts/verrou-de-fichier.mjs +130 -0
- package/scripts/verrou-de-machine.mjs +211 -0
- package/scripts/verrous/commande.mjs +180 -0
- package/scripts/verrous/ticket-du-sous-agent.mjs +39 -0
- package/scripts/verrous/verrou.mjs +165 -0
- package/scripts/vitest-config/avancement.mjs +39 -0
- package/scripts/vitest-config/avancement.reporter.mjs +56 -0
- package/scripts/vitest-config/base-de-passe.mjs +128 -0
- package/scripts/vitest-config/base.d.ts +6 -0
- package/scripts/vitest-config/base.mjs +87 -0
- package/scripts/vitest-config/changements.mjs +173 -0
- package/scripts/vitest-config/cibles.mjs +207 -0
- package/scripts/vitest-config/duree.reporter.mjs +98 -0
- package/scripts/vitest-config/machine.setup.mjs +14 -0
- package/scripts/vitest-config/mesure.d.ts +6 -0
- package/scripts/vitest-config/mesure.mjs +13 -0
- package/scripts/vitest-config/mesure.setup.mjs +12 -0
- package/scripts/vitest-config/pression.mjs +90 -0
- package/scripts/vitest-config/resultats.mjs +23 -0
- package/scripts/vitest-config/resultats.setup.mjs +19 -0
- package/scripts/vitest.nuit.config.mjs +26 -0
- package/skills/developpeur/SKILL.md +175 -0
- package/skills/explorateur/SKILL.md +84 -0
- package/skills/grill/SKILL.md +238 -0
- package/skills/grill/references/releve-structurel.md +38 -0
- package/skills/mesure/SKILL.md +168 -0
- package/skills/pitmaster/SKILL.md +382 -0
- package/skills/pitmaster/references/cadre.md +119 -0
- package/skills/pitmaster/references/consigne-agent.md +308 -0
- package/skills/pitmaster/references/erreurs.md +25 -0
- package/skills/pitmaster/references/tour.md +124 -0
- package/skills/questionneur/SKILL.md +64 -0
- package/skills/redacteur/SKILL.md +84 -0
- package/skills/redacteur/references/documents-d-un-composant.md +105 -0
- package/skills/referent-technique/SKILL.md +145 -0
- package/skills/regulateur/SKILL.md +220 -0
- package/skills/release/SKILL.md +134 -0
- package/skills/release/scripts/update-version.cjs +92 -0
- package/skills/relecteur/SKILL.md +152 -0
- package/skills/relecteur/references/LICENSE-MIT +30 -0
- package/skills/relecteur/references/SOURCE.md +8 -0
- package/skills/relecteur/references/claims-check.md +14 -0
- package/skills/relecteur/references/deletion-check.md +14 -0
- package/skills/relecteur/references/edge-case-hunter.md +80 -0
- package/skills/relecteur/references/verification-gap.md +113 -0
- package/skills/testwriter/SKILL.md +187 -0
- package/types/index.d.ts +56 -0
- package/README.md +0 -3
package/scripts/nuit.sh
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# The night: once a day, the full pass played for real, then what the day never plays: the night
|
|
3
|
+
# tests and the time budget benches. During the day `npm run verifier` (the integration's) replays
|
|
4
|
+
# from Turborepo's cache every task whose inputs are unchanged, and plays the tests Vitest relates
|
|
5
|
+
# to the files changed since the last green (scripts/integration/integrer.mjs,
|
|
6
|
+
# scripts/dernier-vert.mjs).
|
|
7
|
+
#
|
|
8
|
+
# npx grillhouse nuit from the root of the project, by the crontab of the machine, e.g.
|
|
9
|
+
# « 0 3 * * * cd <root> && node_modules/.bin/grillhouse nuit »
|
|
10
|
+
#
|
|
11
|
+
# - It plays main's HEAD in its own clean copy (.claude/worktrees/nuit): detached, every untracked
|
|
12
|
+
# file removed, its submodules set at the commit of HEAD (scripts/sous-modules.sh, a local
|
|
13
|
+
# modification overwritten; one that cannot be set turns the night red; a submodule the project
|
|
14
|
+
# shares, `grillhouse.partage`, is linked to the main repository's instead, not set),
|
|
15
|
+
# dependencies reinstalled (npm ci) when package-lock.json changes, then
|
|
16
|
+
# `npm run verifier -- --force`: Turborepo's build, guards (the frame's and the project's own,
|
|
17
|
+
# its root script `gardes`) and every test of the day, each task
|
|
18
|
+
# played for real (`--force`, no log replayed from the cache, no measure against the last
|
|
19
|
+
# green: the complete pass, scripts/verifier.mjs), its result written to the cache again; green,
|
|
20
|
+
# its tree becomes the last green of the day's passes.
|
|
21
|
+
# A full pass « à repasser » (75, scripts/verifier.mjs: a failure of duration under load, which
|
|
22
|
+
# found no rest in its bound) makes the night « à repasser », below.
|
|
23
|
+
# An existing copy that does not get ready (its move onto HEAD, its cleaning or the setting of
|
|
24
|
+
# its submodules fails) is removed by `scripts/retirer-copie.sh --jetable` (local work does not
|
|
25
|
+
# hold it, a lock does) and created again, once; a copy that does not get ready the second time
|
|
26
|
+
# turns the night red, and so does a removal the script refuses, the log repeating its reason.
|
|
27
|
+
# - Every command of the project (installation, build, tests, night tests, night script) plays
|
|
28
|
+
# through `scripts/sous-modules.sh monter`, each shared submodule of the main repository
|
|
29
|
+
# read-only, its working tree and its git repository (the list the preparation's pose printed,
|
|
30
|
+
# whatever a command rewrites afterwards): a write into it fails. With a shared submodule and no possible mount (bwrap absent
|
|
31
|
+
# or refused), the night turns red, the log saying so, before any command runs.
|
|
32
|
+
# - The night tests: after a green full pass, the files `*.nuit.test.*`, `*.nuit.spec.*`,
|
|
33
|
+
# `*.budget.test.*` and `*.budget.spec.*` of the copy (outside node_modules, dist and .git): the
|
|
34
|
+
# night tests and the time budget benches, which `npm test` and the integration leave out
|
|
35
|
+
# (the package @roomi-fields/grillhouse/vitest-config, scripts/vitest-config/, which every day configuration
|
|
36
|
+
# extends), run by Vitest with the frame's configuration (scripts/vitest.nuit.config.mjs, beside
|
|
37
|
+
# this script), bench or night test alone as well. A red turns the night red, with a log line that says so, and the
|
|
38
|
+
# project's night script does not run. A copy with neither plays none. A run « à repasser » (75:
|
|
39
|
+
# every failure depended on a duration, one under load, scripts/vitest-config/duree.reporter.mjs)
|
|
40
|
+
# waits for rest (`npx grillhouse pression attendre`, bound 15 min) and plays
|
|
41
|
+
# once more; green then, the night goes on; à repasser again, or no rest in the bound, the night
|
|
42
|
+
# is « à repasser ».
|
|
43
|
+
# - « À repasser » (exit 75): neither green nor red. The last green stays, no ticket opens, nothing
|
|
44
|
+
# is pushed, the log says so; the next night plays again.
|
|
45
|
+
# - The project's night script: `grillhouse.nuit` in the package.json of the commit it plays names
|
|
46
|
+
# an npm script (`npm pkg set grillhouse.nuit=<script>`). After the night tests, the night plays
|
|
47
|
+
# `npm run <script>` in its copy; a red script turns the night red, with a log line that names it.
|
|
48
|
+
# The key is read as written, before any work: without it (or with null) the night plays
|
|
49
|
+
# no script of the project; a value that is not a non-blank string, or a name absent from the scripts of
|
|
50
|
+
# that package.json, turns the night red, with a log line that cites it, and nothing runs.
|
|
51
|
+
# - The night push: `grillhouse.poussee.nuit` in the package.json of the commit it plays names a
|
|
52
|
+
# remote branch (`npm pkg set grillhouse.poussee.nuit=<branch>`). After a green night, once the
|
|
53
|
+
# last green is written, the night runs `git push origin <commit>:<branch>` with GRILLHOUSE_NUIT=1
|
|
54
|
+
# set for that push alone, the mark by which the pre-push guard (scripts/branche-de-nuit.mjs)
|
|
55
|
+
# lets it through; any other push to that branch is refused there. Without the key
|
|
56
|
+
# (or with null, as for grillhouse.poussee itself) or after a red night it pushes nothing. The
|
|
57
|
+
# keys are read as written, before any work: a grillhouse.poussee that is neither null nor an
|
|
58
|
+
# object, or a grillhouse.poussee.nuit that is not a non-blank string, turns the night red with
|
|
59
|
+
# a log line that cites it, one line per faulty key. A refused push keeps the last green, opens
|
|
60
|
+
# (or completes) the night ticket « La poussée de la nuit est refusée sur <commit> », and exits
|
|
61
|
+
# 1; a remote already at the commit accepts the push.
|
|
62
|
+
# - It takes no lock of the integration or of the queue (scripts/integration/file.mjs): an
|
|
63
|
+
# integration and the queue run during the night, each in its own copy.
|
|
64
|
+
# - Its log is <git common dir>/nuit/<date>.log. Green: HEAD becomes the last green commit
|
|
65
|
+
# (<git common dir>/nuit/dernier-vert). Red: a ticket labelled `nuit`, with the commits since the
|
|
66
|
+
# last green, where `git bisect` looks for the culprit; an open one gets a comment instead.
|
|
67
|
+
# With a last green, each red task of the full pass (the file GRILLHOUSE_ROUGES names, which
|
|
68
|
+
# scripts/verifier.mjs writes: `<package>#<task>` per line, no task à repasser among them) gets
|
|
69
|
+
# its line, the command that looks for the culprit with that task alone:
|
|
70
|
+
# `git bisect start <head> <last green> && git bisect run npx turbo run <task> --filter=<package>`
|
|
71
|
+
# (`--filter=//` for the root), Turborepo's hash replaying at each step only what changed.
|
|
72
|
+
set -u
|
|
73
|
+
# The project is the working directory; the frame's scripts are this one's neighbours.
|
|
74
|
+
racine="$PWD"
|
|
75
|
+
ici="$(cd "$(dirname "$0")" && pwd)"
|
|
76
|
+
commun="$(git -C "$racine" rev-parse --path-format=absolute --git-common-dir)"
|
|
77
|
+
dossier="$commun/nuit"
|
|
78
|
+
mkdir -p "$dossier"
|
|
79
|
+
journal="$dossier/$(date +%Y-%m-%d).log"
|
|
80
|
+
# The red tasks of the full pass, written by scripts/verifier.mjs.
|
|
81
|
+
rouges="$dossier/rouges"
|
|
82
|
+
rm -f "$rouges"
|
|
83
|
+
|
|
84
|
+
tete="$(git -C "$racine" rev-parse HEAD)"
|
|
85
|
+
copie="$racine/.claude/worktrees/nuit"
|
|
86
|
+
branche=
|
|
87
|
+
# Gets the copy ready on HEAD: detached, cleaned, its submodules set; `partages` holds the list the
|
|
88
|
+
# pose fixes, which every mount gets. Prints the reason on failure.
|
|
89
|
+
partages=
|
|
90
|
+
preparer() {
|
|
91
|
+
if [ -d "$copie" ]; then
|
|
92
|
+
git -C "$copie" checkout -q --detach --force "$tete" || return 1
|
|
93
|
+
git -C "$copie" clean -q -f -d -x -e node_modules || return 1
|
|
94
|
+
else
|
|
95
|
+
git -C "$racine" worktree add -q --detach "$copie" "$tete" || return 1
|
|
96
|
+
fi
|
|
97
|
+
partages=$(bash "$ici/sous-modules.sh" pose "$copie")
|
|
98
|
+
}
|
|
99
|
+
# Plays the copy; its status is the night's verdict. An existing copy that does not get ready is
|
|
100
|
+
# removed (scripts/retirer-copie.sh --jetable) and created again, once.
|
|
101
|
+
jouer() {
|
|
102
|
+
echo "— la nuit du $(date '+%Y-%m-%d %H:%M') sur ${tete:0:7}"
|
|
103
|
+
local existait=0 raison
|
|
104
|
+
[ -d "$copie" ] && existait=1
|
|
105
|
+
if ! preparer; then
|
|
106
|
+
[ "$existait" -eq 1 ] || return 1
|
|
107
|
+
if ! raison=$(cd "$racine" && bash "$ici/retirer-copie.sh" --jetable "$copie" 2>&1); then
|
|
108
|
+
echo "✗ la copie de la nuit ne se prépare pas et ne se retire pas : $raison"
|
|
109
|
+
return 1
|
|
110
|
+
fi
|
|
111
|
+
preparer || return 1
|
|
112
|
+
fi
|
|
113
|
+
# The project's commands play with the shared submodules read-only; no possible mount stops here.
|
|
114
|
+
monter() { bash "$ici/sous-modules.sh" monter "$copie" --partages "$partages" "$@"; }
|
|
115
|
+
if ! raison=$(monter true 2>&1); then
|
|
116
|
+
echo "✗ le montage en lecture seule des sous-modules partagés est impossible : rien n'est joué."
|
|
117
|
+
echo "$raison"
|
|
118
|
+
return 1
|
|
119
|
+
fi
|
|
120
|
+
# The reading gives one line per finding: « script<TAB>name », « pousse<TAB>branch », or a fault
|
|
121
|
+
# « nuit-invalide<TAB>value as JSON », « nuit-absent<TAB>name », « poussee-invalide<TAB>value »,
|
|
122
|
+
# « poussee-nuit-invalide<TAB>value ». Every faulty key is reported before any work.
|
|
123
|
+
local lecture ligne nuit=
|
|
124
|
+
lecture="$(node --input-type=module -e '
|
|
125
|
+
import { readFileSync } from "node:fs";
|
|
126
|
+
import { pathToFileURL } from "node:url";
|
|
127
|
+
const pkg = JSON.parse(readFileSync(process.argv[1], "utf8"));
|
|
128
|
+
const { lirePoussee } = await import(pathToFileURL(process.argv[2]).href);
|
|
129
|
+
const out = (...l) => process.stdout.write(l.join("\t") + "\n");
|
|
130
|
+
const vide = v => typeof v !== "string" || !v.trim();
|
|
131
|
+
const v = pkg.grillhouse?.nuit;
|
|
132
|
+
if (v != null) {
|
|
133
|
+
if (vide(v)) out("nuit-invalide", JSON.stringify(v));
|
|
134
|
+
else if (!Object.hasOwn(pkg.scripts ?? {}, v)) out("nuit-absent", v);
|
|
135
|
+
else out("script", v);
|
|
136
|
+
}
|
|
137
|
+
const { nuit, fautes } = lirePoussee(pkg);
|
|
138
|
+
for (const f of fautes) out(f.cle === "grillhouse.poussee" ? "poussee-invalide" : "poussee-nuit-invalide", f.valeur);
|
|
139
|
+
if (nuit) out("pousse", nuit);
|
|
140
|
+
' "$copie/package.json" "$ici/poussee-cles.mjs" 2>/dev/null)"
|
|
141
|
+
local faute=0
|
|
142
|
+
while IFS= read -r ligne; do
|
|
143
|
+
case "$ligne" in
|
|
144
|
+
script$'\t'*) nuit="${ligne#script$'\t'}" ;;
|
|
145
|
+
pousse$'\t'*) branche="${ligne#pousse$'\t'}" ;;
|
|
146
|
+
nuit-invalide$'\t'*)
|
|
147
|
+
echo "✗ la clé grillhouse.nuit vaut ${ligne#nuit-invalide$'\t'}, pas un nom de script npm : rien n'est joué."
|
|
148
|
+
faute=1 ;;
|
|
149
|
+
nuit-absent$'\t'*)
|
|
150
|
+
echo "✗ la clé grillhouse.nuit nomme « ${ligne#nuit-absent$'\t'} », absent des scripts du package.json : rien n'est joué."
|
|
151
|
+
faute=1 ;;
|
|
152
|
+
poussee-invalide$'\t'*)
|
|
153
|
+
echo "✗ la clé grillhouse.poussee vaut ${ligne#poussee-invalide$'\t'}, pas un objet : rien n'est joué."
|
|
154
|
+
faute=1 ;;
|
|
155
|
+
poussee-nuit-invalide$'\t'*)
|
|
156
|
+
echo "✗ la clé grillhouse.poussee.nuit vaut ${ligne#poussee-nuit-invalide$'\t'}, pas un nom de branche : rien n'est joué."
|
|
157
|
+
faute=1 ;;
|
|
158
|
+
esac
|
|
159
|
+
done <<< "$lecture"
|
|
160
|
+
[ "$faute" -eq 0 ] || return 1
|
|
161
|
+
if [ -f "$copie/package-lock.json" ] && ! cmp -s "$copie/package-lock.json" "$copie/node_modules/.nuit-lock"; then
|
|
162
|
+
monter npm --prefix "$copie" ci --no-audit --no-fund --silent || return 1
|
|
163
|
+
cp "$copie/package-lock.json" "$copie/node_modules/.nuit-lock"
|
|
164
|
+
fi
|
|
165
|
+
# The full pass, played for real: the night replays no log of the cache.
|
|
166
|
+
local code
|
|
167
|
+
GRILLHOUSE_ROUGES="$rouges" monter npm --prefix "$copie" run verifier -- --force
|
|
168
|
+
code=$?
|
|
169
|
+
if [ "$code" -eq 75 ]; then
|
|
170
|
+
echo "⏱ la passe complète est à repasser (code 75) : un échec de durée sous charge, sans repos dans la borne."
|
|
171
|
+
return 75
|
|
172
|
+
fi
|
|
173
|
+
[ "$code" -eq 0 ] || return 1
|
|
174
|
+
if [ -n "$(find "$copie" \( -name node_modules -o -name dist -o -name .git \) -prune -o \
|
|
175
|
+
-type f -regextype posix-extended -regex '.*\.(nuit|budget)\.(test|spec)\.(c|m)?[jt]sx?' -print -quit)" ]; then
|
|
176
|
+
bancs() { (cd "$copie" && monter npx --no-install vitest run --root "$copie" \
|
|
177
|
+
--config "$ici/vitest.nuit.config.mjs"); }
|
|
178
|
+
bancs
|
|
179
|
+
code=$?
|
|
180
|
+
if [ "$code" -eq 75 ]; then
|
|
181
|
+
echo "⏱ les bancs de temps ou les tests de nuit sont à repasser (code 75) : attente du repos, puis une relance."
|
|
182
|
+
if ! node "$ici/vitest-config/pression.mjs" attendre; then
|
|
183
|
+
echo "⏱ à repasser : pas de repos dans la borne."
|
|
184
|
+
return 75
|
|
185
|
+
fi
|
|
186
|
+
bancs
|
|
187
|
+
code=$?
|
|
188
|
+
if [ "$code" -eq 75 ]; then
|
|
189
|
+
echo "⏱ à repasser : les bancs échouent encore sous charge après la relance."
|
|
190
|
+
return 75
|
|
191
|
+
fi
|
|
192
|
+
fi
|
|
193
|
+
if [ "$code" -ne 0 ]; then
|
|
194
|
+
echo "✗ les bancs de temps ou les tests de nuit (*.budget.*, *.nuit.*) ont échoué."
|
|
195
|
+
return 1
|
|
196
|
+
fi
|
|
197
|
+
fi
|
|
198
|
+
[ -n "$nuit" ] || return 0
|
|
199
|
+
if ! monter npm --prefix "$copie" run "$nuit"; then
|
|
200
|
+
echo "✗ le script de nuit du projet \`npm run $nuit\` (grillhouse.nuit) a échoué."
|
|
201
|
+
return 1
|
|
202
|
+
fi
|
|
203
|
+
}
|
|
204
|
+
jouer >> "$journal" 2>&1
|
|
205
|
+
verdict=$?
|
|
206
|
+
|
|
207
|
+
if [ "$verdict" -eq 75 ]; then
|
|
208
|
+
echo "⏱ à repasser : ${tete:0:7} n'est ni vert ni rouge, la machine est restée chargée ; le dernier vert reste, aucun ticket, la nuit suivante rejoue." >> "$journal"
|
|
209
|
+
exit 75
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
# Opens the night ticket (subject « number — subject — parent », the number being the id without the
|
|
213
|
+
# project's prefix as `bd config get issue_prefix` gives it), or comments the open one; prints its id.
|
|
214
|
+
signaler() {
|
|
215
|
+
local sujet="$1" texte="$2" ouvert neuf= prefixe
|
|
216
|
+
ouvert="$(cd "$racine" && bd list --status open --label nuit --json 2>/dev/null | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{const l=JSON.parse(s);process.stdout.write(l[0]?.id??"")}catch{}})')"
|
|
217
|
+
if [ -n "$ouvert" ]; then
|
|
218
|
+
(cd "$racine" && bd comments add "$ouvert" "$texte" >/dev/null)
|
|
219
|
+
else
|
|
220
|
+
neuf="$(cd "$racine" && bd create "$sujet" -t bug -p 1 -l nuit -d "$texte" --silent)"
|
|
221
|
+
prefixe="$(cd "$racine" && bd config get issue_prefix 2>/dev/null)"
|
|
222
|
+
[ -n "$neuf" ] && (cd "$racine" && bd update "$neuf" --title "${neuf#"$prefixe"-} — $sujet — parent" >/dev/null)
|
|
223
|
+
fi
|
|
224
|
+
echo "${ouvert:-$neuf}"
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if [ "$verdict" -eq 0 ]; then
|
|
228
|
+
echo "$tete" > "$dossier/dernier-vert"
|
|
229
|
+
echo "✓ vert : ${tete:0:7} est le dernier vert." >> "$journal"
|
|
230
|
+
if [ -n "$branche" ]; then
|
|
231
|
+
if GRILLHOUSE_NUIT=1 GIT_TERMINAL_PROMPT=0 git -C "$racine" push origin "$tete:$branche" >> "$journal" 2>&1; then
|
|
232
|
+
echo "✓ poussée : ${tete:0:7} est sur origin/$branche." >> "$journal"
|
|
233
|
+
else
|
|
234
|
+
echo "✗ la poussée de ${tete:0:7} vers origin/$branche (grillhouse.poussee.nuit) est refusée." >> "$journal"
|
|
235
|
+
texte="La poussée de la nuit du $(date +%Y-%m-%d) est refusée : ${tete:0:7} vers origin/$branche (grillhouse.poussee.nuit).
|
|
236
|
+
Le dernier vert reste ${tete:0:7}.
|
|
237
|
+
Journal : $journal
|
|
238
|
+
Fin du journal :
|
|
239
|
+
$(tail -40 "$journal")"
|
|
240
|
+
numero="$(signaler "La poussée de la nuit est refusée sur ${tete:0:7}" "$texte")"
|
|
241
|
+
echo "✗ vert, poussée refusée : ticket ${numero:-neuf} (étiquette nuit)." >> "$journal"
|
|
242
|
+
exit 1
|
|
243
|
+
fi
|
|
244
|
+
fi
|
|
245
|
+
exit 0
|
|
246
|
+
fi
|
|
247
|
+
|
|
248
|
+
vert="$(cat "$dossier/dernier-vert" 2>/dev/null || true)"
|
|
249
|
+
intervalle="${vert:+${vert:0:7}..}${tete:0:7}"
|
|
250
|
+
bissections=
|
|
251
|
+
if [ -n "$vert" ] && [ -s "$rouges" ]; then
|
|
252
|
+
bissections="Pour chaque tâche rouge, la commande qui cherche le commit fautif :"
|
|
253
|
+
while IFS= read -r tache; do
|
|
254
|
+
[ -n "$tache" ] || continue
|
|
255
|
+
bissections="$bissections
|
|
256
|
+
- $tache : git bisect start ${tete:0:7} ${vert:0:7} && git bisect run npx turbo run ${tache#*#} --filter=${tache%%#*}"
|
|
257
|
+
done < "$rouges"
|
|
258
|
+
bissections="$bissections
|
|
259
|
+
"
|
|
260
|
+
fi
|
|
261
|
+
texte="La nuit du $(date +%Y-%m-%d) est rouge sur ${tete:0:7}.
|
|
262
|
+
Commits à chercher (git bisect) : $intervalle.
|
|
263
|
+
${bissections}Journal : $journal
|
|
264
|
+
Fin du journal :
|
|
265
|
+
$(tail -40 "$journal")"
|
|
266
|
+
# A night ticket is the mother of its fixes.
|
|
267
|
+
numero="$(signaler "La nuit est rouge depuis la nuit du $(date +%Y-%m-%d)" "$texte")"
|
|
268
|
+
echo "✗ rouge : ticket ${numero:-neuf} (étiquette nuit)." >> "$journal"
|
|
269
|
+
exit 1
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// The packages of a repository: the frame's one list of them, each marked out of the frame or not.
|
|
2
|
+
// The update of the frame (scripts/grillhouse-maj.mjs), the generator of the packages' turbo.json
|
|
3
|
+
// (scripts/turbo-des-paquets.mjs) and the targeted test runs (scripts/tests.mjs) read it.
|
|
4
|
+
//
|
|
5
|
+
// The packages are the directories holding a `package.json` that the `workspaces` of the root
|
|
6
|
+
// `package.json` select (an array, or its `packages` key), mapped as npm maps them
|
|
7
|
+
// (@npmcli/map-workspaces), read with Node's glob: a pattern « ! » takes out what the other
|
|
8
|
+
// patterns select, unless a later pattern it matches puts it back; `node_modules` is never a
|
|
9
|
+
// package. A repository without `workspaces` has none. A package is out of the frame when
|
|
10
|
+
// `docs/agents/hors-cadre.txt` (read by `horsCadre`, below) lists it: a directory under
|
|
11
|
+
// `packages/` or `src/`.
|
|
12
|
+
//
|
|
13
|
+
// This module and the ones it imports use Node alone: the update runs them from a source checkout
|
|
14
|
+
// of the frame that installs no dependency.
|
|
15
|
+
import { existsSync, globSync, readFileSync } from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
// The list of the packages out of the frame: one line per package, « <package> <reason> », its
|
|
19
|
+
// first word the name of a directory under `packages/` or `src/`; a blank line or a line starting
|
|
20
|
+
// with `#` names nothing.
|
|
21
|
+
const HORS_CADRE = 'docs/agents/hors-cadre.txt';
|
|
22
|
+
|
|
23
|
+
// The names listed in <racine>'s `docs/agents/hors-cadre.txt`: a Set, empty without the file.
|
|
24
|
+
export function horsCadre(racine) {
|
|
25
|
+
const liste = path.join(racine, HORS_CADRE);
|
|
26
|
+
if (!existsSync(liste)) return new Set();
|
|
27
|
+
const noms = new Set();
|
|
28
|
+
for (const ligne of readFileSync(liste, 'utf8').split('\n')) {
|
|
29
|
+
const mot = ligne.trim().split(/\s+/)[0];
|
|
30
|
+
if (mot && !mot.startsWith('#')) noms.add(mot);
|
|
31
|
+
}
|
|
32
|
+
return noms;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const lireJson = f => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(readFileSync(f, 'utf8'));
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// The packages of the repository at <racine>: [{ dossier, pkg, hors }], sorted by directory;
|
|
44
|
+
// `hors` whether `docs/agents/hors-cadre.txt` lists it (a directory under `packages/` or `src/`).
|
|
45
|
+
export function paquets(racine) {
|
|
46
|
+
const p = lireJson(path.join(racine, 'package.json'));
|
|
47
|
+
const declares = Array.isArray(p?.workspaces) ? p.workspaces : (p?.workspaces?.packages ?? []);
|
|
48
|
+
const motifs = [];
|
|
49
|
+
let nies = [];
|
|
50
|
+
for (const brut of declares.map(String)) {
|
|
51
|
+
const excl = /^!+/.exec(brut)?.[0] ?? '';
|
|
52
|
+
const motif = brut
|
|
53
|
+
.slice(excl.length)
|
|
54
|
+
.replace(/^\.?\/+/, '')
|
|
55
|
+
.replace(/\/$/, '');
|
|
56
|
+
if (excl.length % 2 === 1) {
|
|
57
|
+
nies.push(motif);
|
|
58
|
+
} else {
|
|
59
|
+
// A pattern puts back what an earlier negation it matches took out.
|
|
60
|
+
nies = nies.filter(n => !path.matchesGlob(motif, n));
|
|
61
|
+
motifs.push(motif);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const dossiers = new Set();
|
|
65
|
+
for (const motif of motifs) {
|
|
66
|
+
for (const f of globSync(`${motif}/package.json`, { cwd: racine })) {
|
|
67
|
+
const dossier = path.dirname(f);
|
|
68
|
+
if (f.split(path.sep).includes('node_modules')) continue;
|
|
69
|
+
if (nies.some(n => path.matchesGlob(dossier, n))) continue;
|
|
70
|
+
dossiers.add(dossier);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const listes = dossiers.size ? horsCadre(racine) : new Set();
|
|
74
|
+
return [...dossiers].sort().map(dossier => ({
|
|
75
|
+
dossier,
|
|
76
|
+
pkg: lireJson(path.join(racine, dossier, 'package.json')) ?? {},
|
|
77
|
+
hors: ['packages', 'src'].includes(path.dirname(dossier)) && listes.has(path.basename(dossier)),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// The reading of the `grillhouse.poussee` keys of a package.json, shared by the night
|
|
2
|
+
// (scripts/nuit.sh), the pre-push guard (scripts/branche-de-nuit.mjs) and the queue
|
|
3
|
+
// (scripts/integration/file.mjs): the keys are read as written. `grillhouse.poussee` is absent,
|
|
4
|
+
// null or an object; `grillhouse.poussee.nuit` and `grillhouse.poussee.commit` are absent, null or
|
|
5
|
+
// a non-blank string.
|
|
6
|
+
|
|
7
|
+
// Returns { nuit, fautes }: `nuit` the night branch, or null when the key is absent or faulty;
|
|
8
|
+
// `fautes` one entry { cle, valeur } per faulty key, `valeur` as JSON.
|
|
9
|
+
export function lirePoussee(pkg) {
|
|
10
|
+
const fautes = [];
|
|
11
|
+
let nuit = null;
|
|
12
|
+
const p = pkg?.grillhouse?.poussee;
|
|
13
|
+
if (p !== undefined && p !== null) {
|
|
14
|
+
if (typeof p !== 'object' || Array.isArray(p)) {
|
|
15
|
+
fautes.push({ cle: 'grillhouse.poussee', valeur: JSON.stringify(p) });
|
|
16
|
+
} else if (p.nuit !== undefined && p.nuit !== null) {
|
|
17
|
+
if (typeof p.nuit !== 'string' || !p.nuit.trim()) {
|
|
18
|
+
fautes.push({ cle: 'grillhouse.poussee.nuit', valeur: JSON.stringify(p.nuit) });
|
|
19
|
+
} else {
|
|
20
|
+
nuit = p.nuit;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { nuit, fautes };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The branch the queue pushes main to: `grillhouse.poussee.commit`, `main` without the key (or with
|
|
28
|
+
// null). Returns { commit, faute }: `faute` is the faulty value as JSON, and `commit` null, when the
|
|
29
|
+
// key is not a non-blank string.
|
|
30
|
+
export function lireCommit(pkg) {
|
|
31
|
+
const p = pkg?.grillhouse?.poussee;
|
|
32
|
+
if (p === undefined || p === null) {
|
|
33
|
+
return { commit: 'main', faute: null };
|
|
34
|
+
}
|
|
35
|
+
if (typeof p !== 'object' || Array.isArray(p)) {
|
|
36
|
+
return { commit: null, faute: JSON.stringify(p) };
|
|
37
|
+
}
|
|
38
|
+
const c = p.commit;
|
|
39
|
+
if (c === undefined || c === null) {
|
|
40
|
+
return { commit: 'main', faute: null };
|
|
41
|
+
}
|
|
42
|
+
if (typeof c !== 'string' || !c.trim()) {
|
|
43
|
+
return { commit: null, faute: JSON.stringify(c) };
|
|
44
|
+
}
|
|
45
|
+
return { commit: c, faute: null };
|
|
46
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// The preparation of a project for its first session, which `grillhouse init` runs on the project
|
|
2
|
+
// it creates (scripts/init.mjs). Idempotent: a second preparation changes nothing.
|
|
3
|
+
//
|
|
4
|
+
// - git: the directory becomes a repository when it is none.
|
|
5
|
+
// - Beads, the ticket store the skills work from: the tool (`bd`, installed by
|
|
6
|
+
// `npm install -g @beads/bd` when absent), then the store, under the prefix given, else the
|
|
7
|
+
// store's own, else the directory's name; the repository's `beads.role` is `maintainer`.
|
|
8
|
+
// - CodeGraph, the code knowledge graph the skills query (the MCP server of `.mcp.json`): the tool
|
|
9
|
+
// (installed by `npm install -g @colbymchenry/codegraph` when absent) and the index of the code.
|
|
10
|
+
// - The folder git reads the hooks from: `core.hooksPath` set to Beads' (`.beads/hooks`) when the
|
|
11
|
+
// repository names none. The frame's git hooks come by the project's `prepare`
|
|
12
|
+
// (`grillhouse hooks`), which the `npm install` below runs.
|
|
13
|
+
// - `npm install`, when the project has a package.json: it installs the frame's package.
|
|
14
|
+
// - Then the frame's plugins its settings declare, installed for the project (scripts/greffons.mjs):
|
|
15
|
+
// a plugin that cannot be installed has its line naming the command to type.
|
|
16
|
+
// A global tool whose installation fails stops the preparation, its message naming the command.
|
|
17
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { installerGreffons } from './greffons.mjs';
|
|
21
|
+
|
|
22
|
+
// The prefix of the tickets: the one given, else the store's own, else the directory's name.
|
|
23
|
+
function prefixeDe(projet, donne) {
|
|
24
|
+
if (donne) return donne;
|
|
25
|
+
if (existsSync(path.join(projet, '.beads'))) {
|
|
26
|
+
const r = spawnSync('bd', ['config', 'get', 'issue_prefix'], {
|
|
27
|
+
cwd: projet,
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
30
|
+
});
|
|
31
|
+
const existant = (r.stdout ?? '').trim();
|
|
32
|
+
if (r.status === 0 && existant !== '' && !existant.includes('(')) return existant;
|
|
33
|
+
}
|
|
34
|
+
return path
|
|
35
|
+
.basename(projet)
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.replace(/[^a-z0-9]/g, '-');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Runs <commande> in <projet>, its output this process's; throws on a failure.
|
|
41
|
+
const lancer = (projet, commande, args) =>
|
|
42
|
+
execFileSync(commande, args, { cwd: projet, stdio: 'inherit' });
|
|
43
|
+
const present = commande => spawnSync('sh', ['-c', `command -v ${commande}`]).status === 0;
|
|
44
|
+
|
|
45
|
+
// A global tool of the preparation, installed by npm when absent.
|
|
46
|
+
function outil(commande, paquet) {
|
|
47
|
+
if (present(commande)) return;
|
|
48
|
+
try {
|
|
49
|
+
execFileSync('npm', ['install', '-g', paquet], { stdio: 'inherit' });
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`L'installation de ${paquet} échoue : lance npm install -g ${paquet}, puis recommence.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Prepares the project at <projet>, its tickets under <prefixe> (see the header). Returns the lines
|
|
59
|
+
* of failure of the plugins (empty when everything stands).
|
|
60
|
+
*/
|
|
61
|
+
export function preparer(projet, prefixe) {
|
|
62
|
+
if (spawnSync('git', ['-C', projet, 'rev-parse', '--git-dir']).status !== 0) {
|
|
63
|
+
lancer(projet, 'git', ['init', '-q']);
|
|
64
|
+
}
|
|
65
|
+
outil('bd', '@beads/bd');
|
|
66
|
+
outil('codegraph', '@colbymchenry/codegraph');
|
|
67
|
+
if (!existsSync(path.join(projet, '.codegraph'))) lancer(projet, 'codegraph', ['init', projet]);
|
|
68
|
+
lancer(projet, 'bd', [
|
|
69
|
+
'init',
|
|
70
|
+
'--init-if-missing',
|
|
71
|
+
'--non-interactive',
|
|
72
|
+
'--skip-agents',
|
|
73
|
+
'--quiet',
|
|
74
|
+
'--prefix',
|
|
75
|
+
prefixeDe(projet, prefixe),
|
|
76
|
+
]);
|
|
77
|
+
lancer(projet, 'git', ['config', 'beads.role', 'maintainer']);
|
|
78
|
+
const r = spawnSync('git', ['-C', projet, 'config', 'core.hooksPath'], { encoding: 'utf8' });
|
|
79
|
+
if ((r.stdout ?? '').trim() === '') {
|
|
80
|
+
lancer(projet, 'git', ['config', 'core.hooksPath', '.beads/hooks']);
|
|
81
|
+
}
|
|
82
|
+
if (existsSync(path.join(projet, 'package.json'))) lancer(projet, 'npm', ['install']);
|
|
83
|
+
return installerGreffons(projet);
|
|
84
|
+
}
|