@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
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# The preparation of a working copy's submodules, called by the three places that create a copy:
|
|
3
|
+
# the integration copy (scripts/integration/integrer.mjs), the agent copy
|
|
4
|
+
# (scripts/enveloppe/lancer.sh) and the night copy (scripts/nuit.sh).
|
|
5
|
+
#
|
|
6
|
+
# npx grillhouse sous-modules pose <copie> every submodule, set or shared
|
|
7
|
+
# npx grillhouse sous-modules garde <copie> refuses a copy whose submodule carries local work
|
|
8
|
+
# npx grillhouse sous-modules monter <copie> --partages <liste> <commande…>
|
|
9
|
+
# plays a command with the shared submodules read-only
|
|
10
|
+
#
|
|
11
|
+
# - `grillhouse.partage` in the package.json of the copy lists the paths of the submodules the
|
|
12
|
+
# project shares (`npm pkg set 'grillhouse.partage[0]=vendor/natif'`). A shared submodule is not
|
|
13
|
+
# set in a copy: the copy reads the one of the main repository at its usual path, through a
|
|
14
|
+
# symbolic link marked « ignored in the copy » (`git update-index --skip-worktree`), so that
|
|
15
|
+
# `git status` of the copy does not report it. The copy therefore reads the shared submodule at
|
|
16
|
+
# the commit checked out in the main repository, not at the commit its own HEAD references, and
|
|
17
|
+
# with the files the main repository holds there, untracked ones included. No repository holds
|
|
18
|
+
# one more copy of it. Without the key, or with an empty list, nothing is shared.
|
|
19
|
+
# - `pose` reads the declaration first and refuses before setting anything (exit 1, its first line
|
|
20
|
+
# on stderr citing `grillhouse.partage` and the faulty value, or naming the shared submodule):
|
|
21
|
+
# a value that is not a list, an element that is not a non-empty relative path inside the
|
|
22
|
+
# repository, a path that is not a submodule of .gitmodules, a shared submodule absent or not
|
|
23
|
+
# initialized in the main repository (never set in the copy instead). Then it puts the link of
|
|
24
|
+
# each shared submodule back (git replaces it by an empty folder when moving a copy forward
|
|
25
|
+
# changes the submodule's commit), sets each other submodule, one by one, at the commit of HEAD,
|
|
26
|
+
# nested ones included (`submodule update --init --recursive --force`), and removes their
|
|
27
|
+
# untracked files: a local modification is overwritten. A submodule that was shared and no longer
|
|
28
|
+
# is gets its own again. A submodule that cannot be set stops it, exit 1, its first line on
|
|
29
|
+
# stderr naming the submodule and its commit, git's output after. A submodule whose address is
|
|
30
|
+
# local (a path or a file:// URL) is set too: the command alone carries
|
|
31
|
+
# `-c protocol.file.allow=always`, the authorization of the file transport; no repository or
|
|
32
|
+
# machine configuration holds it.
|
|
33
|
+
# - The address a submodule is set from is the one the main repository uses: its
|
|
34
|
+
# `submodule.<name>.url` (git's own rule: the local configuration wins), else the url of
|
|
35
|
+
# .gitmodules. A copy shares the configuration of the main repository, so the pose reads it
|
|
36
|
+
# there. The command carries that address (`-c submodule.<name>.url=…`) and, in a copy whose
|
|
37
|
+
# submodule was already set, the `origin` remote of that submodule is moved to it first (git
|
|
38
|
+
# fetches through `origin`, fixed at the first clone). The pose never writes the main
|
|
39
|
+
# repository's configuration. A url relative to the superproject (`./`, `../`), in the
|
|
40
|
+
# configuration or in .gitmodules, is resolved as `git submodule init` resolves it for the main
|
|
41
|
+
# repository (relative_url and chop_last_dir of git), against the url of the remote the current
|
|
42
|
+
# branch of the main repository follows (`branch.<b>.remote`), else its `origin`, else its working
|
|
43
|
+
# tree folder. A remote url that is a local path not absolute is relative to the main repository.
|
|
44
|
+
# Each `../` removes the last component of that url, at its last slash, else at its colon:
|
|
45
|
+
# `host:repo.git` with `../natif.git` gives `host:natif.git`, `git@github.com:org/repo.git` with
|
|
46
|
+
# `../../x.git` gives `git@github.com:x.git`, `upstream.git` with `../natif.git` gives `natif.git`.
|
|
47
|
+
# One final « / » of the url goes. A local path not absolute that results is taken from the main
|
|
48
|
+
# repository's folder, which need not exist (`../up/repo.git` with `../../natif.git` gives
|
|
49
|
+
# `<main repository>/../up/natif.git`). A url git refuses for the main repository stops the pose
|
|
50
|
+
# before the submodule is set — a `../` that finds neither slash nor colon left in a relative
|
|
51
|
+
# remote url (`upstream.git` with `../../natif.git`), or in the `.` an absolute one becomes once
|
|
52
|
+
# emptied of its components: exit 1, its first line on stderr naming the submodule, and the
|
|
53
|
+
# main repository's configuration stays as it is. The resolved address is the one the command
|
|
54
|
+
# carries, so a copy under .claude/worktrees/ never resolves it from its own folder.
|
|
55
|
+
# - `pose` never enters a shared submodule, nor a submodule nested in it: they belong to the main
|
|
56
|
+
# repository, which neither sets nor cleans them.
|
|
57
|
+
# - `garde` reads the copy as it stands: a submodule modified in `git status` (an edited or
|
|
58
|
+
# untracked file, another commit checked out) stops it, exit 1, its first line on stderr naming
|
|
59
|
+
# the submodule. The agent copy calls it before moving forward, as its work stays in place.
|
|
60
|
+
# - `pose` prints on stdout the list it fixes: the target of each link it set (the path of the
|
|
61
|
+
# shared submodule in the main repository), one per line, nothing when nothing is shared.
|
|
62
|
+
# - `monter` plays the command, from the current folder, with each shared submodule of the main
|
|
63
|
+
# repository mounted read-only (bwrap), its working tree and its git repository (nested
|
|
64
|
+
# submodules included): a write into it, a commit, reset, checkout or fetch fails. Its caller
|
|
65
|
+
# hands it the list its pose printed (`--partages`, one path per line): the mount reads nothing
|
|
66
|
+
# the copy or a command of the project rewrites during a lot (neither the declaration, nor
|
|
67
|
+
# .gitmodules, nor the index, nor the main repository's state), only the git file of each shared
|
|
68
|
+
# submodule, read-only under every earlier mount. The list is required: a call without it is
|
|
69
|
+
# malformed. Every command of the project that the frame plays in a copy goes through it with
|
|
70
|
+
# the list of its pose: the integration (installation, build, suites, guards, hooks of its
|
|
71
|
+
# commit), the night, and the installation and build of the agent copy.
|
|
72
|
+
# With an empty list the command plays as it is. With a path and no possible mount (bwrap absent
|
|
73
|
+
# or refused, a shared submodule absent from the main repository or not initialized there),
|
|
74
|
+
# nothing plays: exit 1, its first line on stderr gives that reason.
|
|
75
|
+
# - Without .gitmodules and without a declaration in the copy, `pose` and `garde` do nothing.
|
|
76
|
+
# A malformed call exits 2.
|
|
77
|
+
set -uo pipefail
|
|
78
|
+
usage() {
|
|
79
|
+
echo "usage: sous-modules.sh pose|garde <copie> | monter <copie> --partages <liste> <commande…>" >&2
|
|
80
|
+
exit 2
|
|
81
|
+
}
|
|
82
|
+
[ $# -ge 2 ] || usage
|
|
83
|
+
action=$1 copie=$2
|
|
84
|
+
case "$action" in
|
|
85
|
+
pose | garde) [ $# -eq 2 ] || usage ;;
|
|
86
|
+
monter) [ $# -ge 5 ] && [ "$3" = --partages ] || usage ;;
|
|
87
|
+
*) usage ;;
|
|
88
|
+
esac
|
|
89
|
+
|
|
90
|
+
refuser() {
|
|
91
|
+
echo "$1" >&2
|
|
92
|
+
shift
|
|
93
|
+
[ $# -eq 0 ] || echo "$*" >&2
|
|
94
|
+
exit 1
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
# The declaration of the copy: the paths it shares, one per line in `partages`; a faulty one
|
|
98
|
+
# refuses. A package.json that does not read declares nothing.
|
|
99
|
+
lire_partage() {
|
|
100
|
+
local lecture ligne
|
|
101
|
+
lecture=$(node -e '
|
|
102
|
+
const fs = require("node:fs");
|
|
103
|
+
let pkg;
|
|
104
|
+
try { pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); } catch { process.exit(0); }
|
|
105
|
+
const v = pkg?.grillhouse?.partage;
|
|
106
|
+
if (v === undefined || v === null) process.exit(0);
|
|
107
|
+
const faute = (valeur, raison) => { console.log(`faute\t${JSON.stringify(valeur)}\t${raison}`); process.exit(0); };
|
|
108
|
+
if (!Array.isArray(v)) faute(v, "pas une liste de chemins de sous-modules");
|
|
109
|
+
for (const e of v) {
|
|
110
|
+
if (typeof e !== "string" || !e.trim()) faute(e, "pas un chemin non vide");
|
|
111
|
+
if (e.startsWith("/")) faute(e, "un chemin absolu");
|
|
112
|
+
if (e.split("/").includes("..")) faute(e, "un chemin qui sort du dépôt");
|
|
113
|
+
}
|
|
114
|
+
for (const e of v) console.log(`chemin\t${e}`);
|
|
115
|
+
' "$copie/package.json") || refuser "la clé grillhouse.partage de $copie/package.json ne se lit pas."
|
|
116
|
+
partages=()
|
|
117
|
+
while IFS= read -r ligne; do
|
|
118
|
+
case "$ligne" in
|
|
119
|
+
faute$'\t'*)
|
|
120
|
+
local reste=${ligne#faute$'\t'}
|
|
121
|
+
refuser "la clé grillhouse.partage vaut ${reste%%$'\t'*}, ${reste#*$'\t'} : rien n'est posé." ;;
|
|
122
|
+
chemin$'\t'*) partages+=("${ligne#chemin$'\t'}") ;;
|
|
123
|
+
esac
|
|
124
|
+
done <<< "$lecture"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# The main repository's working tree: the first of the repository's copies.
|
|
128
|
+
dossier_principal() {
|
|
129
|
+
git -C "$copie" worktree list --porcelain | sed -n '1s/^worktree //p'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
est_partage() {
|
|
133
|
+
local p
|
|
134
|
+
for p in "${partages[@]}"; do [ "$p" = "$1" ] && return 0; done
|
|
135
|
+
return 1
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if [ "$action" = monter ]; then
|
|
139
|
+
# The paths to mount: the list the caller holds from the pose, absolute paths only.
|
|
140
|
+
cibles=()
|
|
141
|
+
while IFS= read -r c; do
|
|
142
|
+
[ -n "$c" ] || continue
|
|
143
|
+
case "$c" in /*) cibles+=("$c") ;; *) usage ;; esac
|
|
144
|
+
done <<< "$4"
|
|
145
|
+
shift 4
|
|
146
|
+
[ ${#cibles[@]} -gt 0 ] || exec "$@"
|
|
147
|
+
command -v bwrap > /dev/null || refuser "le montage en lecture seule des sous-modules partagés est impossible : bwrap est absent."
|
|
148
|
+
montage=(bwrap --dev-bind / / --die-with-parent)
|
|
149
|
+
for c in "${cibles[@]}"; do
|
|
150
|
+
[ -e "$c/.git" ] ||
|
|
151
|
+
refuser "le montage en lecture seule du sous-module partagé $c est impossible : il est absent ou non initialisé dans le dépôt principal."
|
|
152
|
+
depot=$(git -C "$c" rev-parse --absolute-git-dir) ||
|
|
153
|
+
refuser "le montage en lecture seule du sous-module partagé $c est impossible : son dépôt git ne se lit pas."
|
|
154
|
+
montage+=(--ro-bind "$c" "$c")
|
|
155
|
+
# Its git repository (nested submodules' ones live under it) is read-only too.
|
|
156
|
+
case "$depot" in "$c"/*) ;; *) montage+=(--ro-bind "$depot" "$depot") ;; esac
|
|
157
|
+
done
|
|
158
|
+
montage+=(--chdir "$PWD")
|
|
159
|
+
sortie=$("${montage[@]}" -- true 2>&1) ||
|
|
160
|
+
refuser "le montage en lecture seule des sous-modules partagés est impossible : bwrap est refusé." "$sortie"
|
|
161
|
+
exec "${montage[@]}" -- "$@"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
lire_partage
|
|
165
|
+
[ -f "$copie/.gitmodules" ] || [ ${#partages[@]} -eq 0 ] || refuser "la clé grillhouse.partage nomme ${partages[0]}, mais la copie n'a pas de .gitmodules : rien n'est posé."
|
|
166
|
+
[ -f "$copie/.gitmodules" ] || exit 0
|
|
167
|
+
# The submodules of .gitmodules: `chemins[i]` is a path, `noms[i]` its name. Read with -z so that a
|
|
168
|
+
# path or a name holding a space stays whole.
|
|
169
|
+
chemins=() noms=()
|
|
170
|
+
while IFS= read -r -d '' entree; do
|
|
171
|
+
cle=${entree%%$'\n'*}
|
|
172
|
+
cle=${cle#submodule.}
|
|
173
|
+
noms+=("${cle%.path}")
|
|
174
|
+
chemins+=("${entree#*$'\n'}")
|
|
175
|
+
done < <(git -C "$copie" config -f .gitmodules -z --get-regexp '^submodule\..*\.path$')
|
|
176
|
+
|
|
177
|
+
if [ "$action" = garde ]; then
|
|
178
|
+
for p in "${chemins[@]}"; do
|
|
179
|
+
etat=$(git -C "$copie" status --porcelain --ignore-submodules=none -- "$p") || exit 1
|
|
180
|
+
if [ -n "$etat" ]; then
|
|
181
|
+
echo "le sous-module $p porte des modifications locales dans $copie : elles restent en place, et la copie s'arrête." >&2
|
|
182
|
+
echo "$etat" >&2
|
|
183
|
+
exit 1
|
|
184
|
+
fi
|
|
185
|
+
done
|
|
186
|
+
exit 0
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# The name of the submodule at path $1, in .gitmodules.
|
|
190
|
+
nom_du_sous_module() {
|
|
191
|
+
local i
|
|
192
|
+
for i in "${!chemins[@]}"; do
|
|
193
|
+
[ "${chemins[$i]}" = "$1" ] && { printf '%s\n' "${noms[$i]}"; return; }
|
|
194
|
+
done
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
# The url of the main repository's remote a relative address resolves against, as git chooses it:
|
|
198
|
+
# the remote its current branch follows (`branch.<b>.remote`), else origin; no url, its folder.
|
|
199
|
+
base_relative() {
|
|
200
|
+
local principal=$1 branche remote base=
|
|
201
|
+
branche=$(git -C "$principal" symbolic-ref -q --short HEAD) || branche=
|
|
202
|
+
remote=
|
|
203
|
+
[ -z "$branche" ] || remote=$(git -C "$principal" config --get "branch.$branche.remote") || remote=
|
|
204
|
+
[ -n "$remote" ] || remote=origin
|
|
205
|
+
base=$(git -C "$principal" config --get "remote.$remote.url") || base=
|
|
206
|
+
[ -n "$base" ] || base=$principal
|
|
207
|
+
printf '%s\n' "$base"
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
# Whether a url is a local path, as git reads it (url_is_local_not_ssh): no colon, or a slash
|
|
211
|
+
# before its first colon.
|
|
212
|
+
est_locale() {
|
|
213
|
+
case "$1" in *:*) ;; *) return 0 ;; esac
|
|
214
|
+
case "${1%%:*}" in */*) return 0 ;; esac
|
|
215
|
+
return 1
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# The url relative to the superproject (`./`, `../`) $1 of the submodule at path $2, resolved as
|
|
219
|
+
# `git submodule init` resolves it for the main repository (relative_url and chop_last_dir of git):
|
|
220
|
+
# the base loses one final « / »; a base that is a local path not absolute is relative to the main
|
|
221
|
+
# repository (`upstream.git` reads `./upstream.git`). Each `../` removes the last component of the
|
|
222
|
+
# base, at its last slash, else at its colon, which then joins the rest with « : »; a base left
|
|
223
|
+
# without either refuses when it is relative or `.`, and becomes `.` otherwise. One final « / » of
|
|
224
|
+
# the url goes. A local path not absolute that results is made absolute from the main repository's
|
|
225
|
+
# folder, without requiring it to exist. A refusal names the submodule, exit 1.
|
|
226
|
+
resoudre_relative() {
|
|
227
|
+
local url=$1 principal base relative=0 deuxpoints=0 resolue
|
|
228
|
+
principal=$(dossier_principal)
|
|
229
|
+
base=$(base_relative "$principal")
|
|
230
|
+
base=${base%/}
|
|
231
|
+
if est_locale "$base" && [ "${base#/}" = "$base" ]; then
|
|
232
|
+
relative=1
|
|
233
|
+
case "$base" in ./* | ../*) ;; *) base=./$base ;; esac
|
|
234
|
+
fi
|
|
235
|
+
while :; do
|
|
236
|
+
case "$url" in
|
|
237
|
+
./*) url=${url#./} ;;
|
|
238
|
+
../*)
|
|
239
|
+
url=${url#../}
|
|
240
|
+
case "$base" in
|
|
241
|
+
*/*) base=${base%/*} ;;
|
|
242
|
+
*:*) base=${base%:*} deuxpoints=1 ;;
|
|
243
|
+
*)
|
|
244
|
+
[ "$relative" = 0 ] && [ "$base" != . ] ||
|
|
245
|
+
refuser "le sous-module $2 ne se pose pas : son adresse relative $1 remonte au-delà de l'adresse $(base_relative "$principal") du dépôt principal, que git refuse de même ; il n'est pas posé, et la configuration du dépôt principal reste intacte."
|
|
246
|
+
base=. ;;
|
|
247
|
+
esac ;;
|
|
248
|
+
*) break ;;
|
|
249
|
+
esac
|
|
250
|
+
done
|
|
251
|
+
if [ "$deuxpoints" = 1 ]; then resolue=$base:$url; else resolue=$base/$url; fi
|
|
252
|
+
case "$url" in */) resolue=${resolue%/} ;; esac
|
|
253
|
+
resolue=${resolue#./}
|
|
254
|
+
if est_locale "$resolue" && [ "${resolue#/}" = "$resolue" ]; then
|
|
255
|
+
resolue=$(realpath -m -s -- "$principal/$resolue") || return 1
|
|
256
|
+
fi
|
|
257
|
+
printf '%s\n' "$resolue"
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
adresse_du_sous_module() {
|
|
261
|
+
local nom url
|
|
262
|
+
nom=$(nom_du_sous_module "$1")
|
|
263
|
+
url=$(git -C "$copie" config --get "submodule.$nom.url") ||
|
|
264
|
+
url=$(git -C "$copie" config -f .gitmodules --get "submodule.$nom.url") || url=
|
|
265
|
+
case "$url" in ./* | ../*) url=$(resoudre_relative "$url" "$1") || return 1 ;; esac
|
|
266
|
+
printf '%s\n' "$url"
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Refuses before setting anything.
|
|
270
|
+
for p in "${partages[@]}"; do
|
|
271
|
+
printf '%s\n' "${chemins[@]}" | grep -qxF -- "$p" ||
|
|
272
|
+
refuser "la clé grillhouse.partage nomme « $p », qui n'est pas un sous-module de .gitmodules : rien n'est posé."
|
|
273
|
+
done
|
|
274
|
+
principal=
|
|
275
|
+
if [ ${#partages[@]} -gt 0 ]; then
|
|
276
|
+
principal=$(dossier_principal)
|
|
277
|
+
for p in "${partages[@]}"; do
|
|
278
|
+
[ -e "$principal/$p/.git" ] ||
|
|
279
|
+
refuser "le sous-module partagé $p est absent ou non initialisé dans le dépôt principal $principal : la copie ne le lit pas, rien n'est posé."
|
|
280
|
+
done
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
# A shared submodule: the link to the main repository's, ignored in the copy.
|
|
284
|
+
for p in "${partages[@]}"; do
|
|
285
|
+
[ -L "$copie/$p" ] || rm -rf -- "${copie:?}/$p"
|
|
286
|
+
ln -sfn -- "$principal/$p" "$copie/$p"
|
|
287
|
+
git -C "$copie" update-index --skip-worktree -- "$p" || exit 1
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
# The others: set in the copy. A link left by an earlier declaration goes first, and the mark with it.
|
|
291
|
+
for p in "${chemins[@]}"; do
|
|
292
|
+
est_partage "$p" && continue
|
|
293
|
+
[ -L "$copie/$p" ] && rm -f -- "${copie:?}/$p"
|
|
294
|
+
git -C "$copie" update-index --no-skip-worktree -- "$p" || exit 1
|
|
295
|
+
adresse=$(adresse_du_sous_module "$p") || exit 1
|
|
296
|
+
options=(-c protocol.file.allow=always)
|
|
297
|
+
if [ -n "$adresse" ]; then
|
|
298
|
+
options+=(-c "submodule.$(nom_du_sous_module "$p").url=$adresse")
|
|
299
|
+
if [ -e "$copie/$p/.git" ] && ! sortie=$(git -C "$copie/$p" remote set-url origin "$adresse" 2>&1); then
|
|
300
|
+
echo "le sous-module $p ne se pose pas : son remote origin ne passe pas à $adresse :" >&2
|
|
301
|
+
echo "$sortie" >&2
|
|
302
|
+
exit 1
|
|
303
|
+
fi
|
|
304
|
+
fi
|
|
305
|
+
if ! sortie=$(git "${options[@]}" -C "$copie" submodule update --init --recursive --force -- "$p" 2>&1); then
|
|
306
|
+
commit=$(git -C "$copie" ls-tree HEAD -- "$p" | cut -d' ' -f3 | cut -f1)
|
|
307
|
+
echo "le sous-module $p ne se pose pas au commit ${commit:0:7} :" >&2
|
|
308
|
+
echo "$sortie" >&2
|
|
309
|
+
exit 1
|
|
310
|
+
fi
|
|
311
|
+
git -C "$copie/$p" clean -q -f -d || exit 1
|
|
312
|
+
git -C "$copie/$p" submodule foreach --quiet --recursive 'git clean -q -f -d' || exit 1
|
|
313
|
+
done
|
|
314
|
+
|
|
315
|
+
# The list the pose fixes: the target of each link, for the caller to hand to `monter`.
|
|
316
|
+
for p in "${partages[@]}"; do printf '%s\n' "$principal/$p"; done
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# UserPromptSubmit hook: a message that asks for a structure decision (split, merge, create or remove
|
|
3
|
+
# a package, a module, an interface, a boundary) reminds the session that such a decision goes through
|
|
4
|
+
# the grill. Read-only; silent on every other message, including one that merely mentions structure,
|
|
5
|
+
# and to a session of a role agent of the frame (`claude --agent <rôle>`: the input names it,
|
|
6
|
+
# `agent_type`, bare or as `grillhouse:<rôle>`, and the frame's plugin defines it,
|
|
7
|
+
# `$CLAUDE_PLUGIN_ROOT/agents/<rôle>.md`), whose instruction is a ticket's.
|
|
8
|
+
entree="$(cat)"
|
|
9
|
+
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{}})')"
|
|
10
|
+
racine="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
11
|
+
role="${role#grillhouse:}"
|
|
12
|
+
[ -n "$role" ] && [ -n "${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "$CLAUDE_PLUGIN_ROOT/agents/$role.md" ] && exit 0
|
|
13
|
+
prompt="$(printf '%s' "$entree" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{try{process.stdout.write(JSON.parse(s).prompt||"")}catch{}})')"
|
|
14
|
+
noun='paquets?|packages?|modules?|interfaces?|fronti[eè]res?|boundar(y|ies)|couches?|(é|e)tages?|layers?|monorepo|architecture'
|
|
15
|
+
decision="faut-il|doit-on|devrait-on|est-ce qu.?il faut|besoin (de|d.)|on (d[ée]coupe|s[ée]pare)|d[ée]couper|s[ée]parer|scinder|fusionner|regrouper|extraire|cr[ée]er (un|une|des)|supprimer (le|la|les|un|une)|should (we|i)|do we need|split|merge"
|
|
16
|
+
if printf '%s' "$prompt" | grep -qiE "($noun)" && printf '%s' "$prompt" | grep -qiE "($decision)"; then
|
|
17
|
+
cat <<'MSG'
|
|
18
|
+
## Décision de structure
|
|
19
|
+
Ce message demande une décision de structure (découpage, paquets, modules, interfaces, frontières).
|
|
20
|
+
Elle se prend par le grill : charge la compétence `grill` (§2 « La règle de l'architecture », §3
|
|
21
|
+
relevé structurel, §4 grill), jamais par une réponse directe. Elle se tranche sur les objectifs, les
|
|
22
|
+
consommateurs, les représentations de données et les axes de changement, avec leurs pièces, et
|
|
23
|
+
s'arbitre par la charte (« Comment on arbitre ») — jamais sur la taille du code ni sur « pas besoin
|
|
24
|
+
pour l'instant ».
|
|
25
|
+
MSG
|
|
26
|
+
fi
|
|
27
|
+
exit 0
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// One supervisor per project, as Zuul has one scheduler: a session of supervision holds the lock
|
|
3
|
+
// of its repository, and a second one reads it, says so, and launches no circuit. Played by the
|
|
4
|
+
// session start (scripts/session-start.sh), which writes what it says to the session.
|
|
5
|
+
//
|
|
6
|
+
// npx grillhouse superviseur <racine>
|
|
7
|
+
//
|
|
8
|
+
// - Out of a git repository, in an agent's copy (a worktree whose git directory is not the common
|
|
9
|
+
// one) and in a session of a role that the circuit launches (BEADS_ACTOR set: the author of its
|
|
10
|
+
// comments at the ticket), nothing is taken and nothing is said.
|
|
11
|
+
// - The lock is `<git common dir>/grillhouse-superviseur.lock`, one for the repository and its
|
|
12
|
+
// copies, taken through scripts/verrou-de-fichier.mjs for the session's own process: the first
|
|
13
|
+
// process above this one that is not a shell (sh, bash, dash, zsh), the claude of the session.
|
|
14
|
+
// It holds as long as that process lives: a lock whose process is gone is taken over.
|
|
15
|
+
// - Free, or held by a process that is gone: taken, nothing said. Held by the session itself (a
|
|
16
|
+
// session reopened in the same process): kept, nothing said. Held by another live session: the
|
|
17
|
+
// lock stays, and the section « ## Un autre superviseur tient ce dépôt » names its process and
|
|
18
|
+
// since when it holds the lock, and asks to say so to the owner and to launch no circuit
|
|
19
|
+
// (skill pitmaster, « Au lancement de la séance »).
|
|
20
|
+
// Exit 0, whatever it finds.
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { pathToFileURL } from 'node:url';
|
|
25
|
+
import { prendreVerrou } from './verrou-de-fichier.mjs';
|
|
26
|
+
import { vivant } from './vitest-config/base-de-passe.mjs';
|
|
27
|
+
|
|
28
|
+
const VERROU_SUPERVISEUR = 'grillhouse-superviseur.lock';
|
|
29
|
+
const COQUILLES = new Set(['sh', 'bash', 'dash', 'zsh']);
|
|
30
|
+
|
|
31
|
+
// The parent and the name of the process <pid>, from /proc; null when it does not read.
|
|
32
|
+
function processus(pid) {
|
|
33
|
+
try {
|
|
34
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
35
|
+
const fin = stat.lastIndexOf(')');
|
|
36
|
+
const nom = stat.slice(stat.indexOf('(') + 1, fin);
|
|
37
|
+
const parent = Number(stat.slice(fin + 2).split(' ')[1]);
|
|
38
|
+
return { nom, parent };
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The session's process: the first process above <depart> that is not a shell.
|
|
45
|
+
function seance(depart = process.ppid) {
|
|
46
|
+
let pid = depart;
|
|
47
|
+
for (let i = 0; i < 32 && pid > 1; i++) {
|
|
48
|
+
const p = processus(pid);
|
|
49
|
+
if (!p) return depart;
|
|
50
|
+
if (!COQUILLES.has(p.nom)) return pid;
|
|
51
|
+
pid = p.parent;
|
|
52
|
+
}
|
|
53
|
+
return depart;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const git = (racine, ...args) =>
|
|
57
|
+
spawnSync('git', ['-C', racine, 'rev-parse', '--path-format=absolute', ...args], {
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The check of the supervisor at the opening of a session in <racine>: what it says (empty when
|
|
63
|
+
* nothing), the lock taken when it is free.
|
|
64
|
+
* @param {string} racine the directory the session opens in
|
|
65
|
+
* @param {{ env?: Record<string, string | undefined> }} options
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function superviseur(racine, { env = process.env } = {}) {
|
|
69
|
+
if (env.BEADS_ACTOR) return '';
|
|
70
|
+
const r = git(racine, '--git-dir', '--git-common-dir');
|
|
71
|
+
if (r.status !== 0) return '';
|
|
72
|
+
const [dossierGit, commun] = r.stdout.trim().split('\n');
|
|
73
|
+
if (path.resolve(dossierGit) !== path.resolve(commun)) return '';
|
|
74
|
+
const verrou = path.join(commun, VERROU_SUPERVISEUR);
|
|
75
|
+
const moi = seance();
|
|
76
|
+
const tenant = () => {
|
|
77
|
+
try {
|
|
78
|
+
return Number(/^(\d+)/.exec(readFileSync(verrou, 'utf8'))?.[1]);
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
if (tenant() === moi) return '';
|
|
84
|
+
if (prendreVerrou(verrou, { attendre: false, pid: moi })) return '';
|
|
85
|
+
const autre = tenant();
|
|
86
|
+
if (!autre || autre === moi || !vivant(autre)) return '';
|
|
87
|
+
let depuis = '';
|
|
88
|
+
try {
|
|
89
|
+
depuis = `, depuis le ${statSync(verrou).mtime.toLocaleString('fr-FR')}`;
|
|
90
|
+
} catch {
|
|
91
|
+
// the lock is gone meanwhile: its date is not said
|
|
92
|
+
}
|
|
93
|
+
return [
|
|
94
|
+
'## Un autre superviseur tient ce dépôt',
|
|
95
|
+
`La séance de supervision du processus ${autre} tient le verrou de ce dépôt (${verrou}${depuis}). Un projet a un seul superviseur, comme Zuul a un seul ordonnanceur : dis-le au responsable dès ta première réponse, et ne lance aucun circuit, ni à la main aucune livraison, tant que cette séance tient le verrou. Il se libère quand elle se ferme ; la séance suivante le reprend.`,
|
|
96
|
+
].join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
100
|
+
const texte = superviseur(process.argv[2] ?? process.cwd());
|
|
101
|
+
if (texte) process.stdout.write(`${texte}\n`);
|
|
102
|
+
}
|