repowarden 4.13.0-next.7 → 4.13.1
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/bin/agents +65 -5
- package/bin/garde +208 -27
- package/bin/statut +7 -3
- package/ci/tickets/push.sh +11 -1
- package/hooks/agents/repowarden-garde-v2.js +5 -1
- package/hooks/agents/skill-repowarden.en.md +41 -0
- package/hooks/agents/skill-repowarden.fr.md +41 -0
- package/hooks/lib/agents.sh +38 -2
- package/hooks/lib/i18n/en.sh +14 -1
- package/hooks/lib/i18n/fr.sh +14 -1
- package/install.sh +11 -0
- package/package.json +1 -1
package/bin/agents
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
# Branche le garde-fou repowarden (repowarden garde) dans les agents IA :
|
|
3
3
|
# repowarden agents init [--claude] [--opencode] [--projet | --global]
|
|
4
4
|
# repowarden agents retirer [--claude] [--opencode] [--projet | --global]
|
|
5
|
-
# Claude Code hook PreToolUse dans .claude/settings.json
|
|
5
|
+
# Claude Code hook PreToolUse dans .claude/settings.json + skill repowarden
|
|
6
6
|
# opencode plugins .opencode/plugins/repowarden-garde.js (opencode 1.x)
|
|
7
|
-
# et repowarden-garde-v2.js (opencode 2.x)
|
|
7
|
+
# et repowarden-garde-v2.js (opencode 2.x) + skill repowarden
|
|
8
8
|
# Sans --claude ni --opencode : les deux. --projet (défaut) : fichiers du dépôt,
|
|
9
9
|
# à versionner ; --global : configuration de l'utilisateur, là où chaque outil
|
|
10
10
|
# la lit (CLAUDE_CONFIG_DIR ; OPENCODE_CONFIG_DIR, sinon XDG_CONFIG_HOME).
|
|
11
|
-
# Relançable : fichiers existants complétés ; un garde-fou
|
|
12
|
-
# antérieure de repowarden
|
|
13
|
-
# retirer n'enlève que ce qu'init a écrit.
|
|
11
|
+
# Relançable : fichiers existants complétés ; un garde-fou ou une skill posés
|
|
12
|
+
# par une version antérieure de repowarden sont mis à jour, un fichier
|
|
13
|
+
# personnel jamais écrasé ; retirer n'enlève que ce qu'init a écrit.
|
|
14
14
|
set -euo pipefail
|
|
15
15
|
|
|
16
16
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
@@ -23,6 +23,7 @@ aide() { _tr help.agents; printf '%b\n' "$_T"; }
|
|
|
23
23
|
|
|
24
24
|
COMMANDE_CLAUDE="$AGENTS_COMMANDE_CLAUDE"
|
|
25
25
|
MATCHER_CLAUDE="$AGENTS_MATCHER_CLAUDE"
|
|
26
|
+
MODELE_SKILL="$(agents_modele_skill)"
|
|
26
27
|
|
|
27
28
|
action=""
|
|
28
29
|
case "${1:-}" in
|
|
@@ -51,12 +52,16 @@ if [ "$portee" = projet ]; then
|
|
|
51
52
|
base="$GIT_TOPLEVEL"
|
|
52
53
|
fichier_claude="$base/.claude/settings.json"
|
|
53
54
|
dossier_opencode="$base/.opencode/plugins"
|
|
55
|
+
fichier_skill_claude="$base/.claude/skills/repowarden/SKILL.md"
|
|
56
|
+
fichier_skill_opencode="$base/.opencode/skills/repowarden/SKILL.md"
|
|
54
57
|
else
|
|
55
58
|
# Réglages utilisateur de Claude Code : $CLAUDE_CONFIG_DIR s'il est défini
|
|
56
59
|
# (code.claude.com/docs/en/settings) ; plugins globaux d'opencode :
|
|
57
60
|
# $OPENCODE_CONFIG_DIR, sinon $XDG_CONFIG_HOME/opencode (opencode.ai/docs/config)
|
|
58
61
|
fichier_claude="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"
|
|
59
62
|
dossier_opencode="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/plugins"
|
|
63
|
+
fichier_skill_claude="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/skills/repowarden/SKILL.md"
|
|
64
|
+
fichier_skill_opencode="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/skills/repowarden/SKILL.md"
|
|
60
65
|
fi
|
|
61
66
|
|
|
62
67
|
ecrits=()
|
|
@@ -122,6 +127,28 @@ init_opencode() {
|
|
|
122
127
|
done
|
|
123
128
|
}
|
|
124
129
|
|
|
130
|
+
# Skill repowarden de l'agent $2 au fichier $1, posée selon la langue :
|
|
131
|
+
# remplacée si posée par repowarden et périmée, gardée si personnalisée
|
|
132
|
+
poser_skill() {
|
|
133
|
+
local f="$1" nom="$2"
|
|
134
|
+
if [ -f "$f" ]; then
|
|
135
|
+
if cmp -s "$f" "$MODELE_SKILL"; then
|
|
136
|
+
ok_t agents.skill_present "$nom" "$f"
|
|
137
|
+
elif agents_skill_repowarden "$f"; then
|
|
138
|
+
cp "$MODELE_SKILL" "$f"
|
|
139
|
+
ecrits+=("$f")
|
|
140
|
+
ok_t agents.skill_updated "$nom" "$f"
|
|
141
|
+
else
|
|
142
|
+
attention_t agents.skill_kept "$nom" "$f" "$MODELE_SKILL"
|
|
143
|
+
fi
|
|
144
|
+
return 0
|
|
145
|
+
fi
|
|
146
|
+
mkdir -p "$(dirname "$f")"
|
|
147
|
+
cp "$MODELE_SKILL" "$f"
|
|
148
|
+
ecrits+=("$f")
|
|
149
|
+
ok_t agents.skill_added "$nom" "$f"
|
|
150
|
+
}
|
|
151
|
+
|
|
125
152
|
# Hook PreToolUse : seules les commandes « repowarden garde » retirées ; une
|
|
126
153
|
# entrée vidée disparaît, et le fichier s'il ne reste plus rien
|
|
127
154
|
retirer_claude() {
|
|
@@ -164,14 +191,47 @@ retirer_opencode() {
|
|
|
164
191
|
done
|
|
165
192
|
}
|
|
166
193
|
|
|
194
|
+
# Skill repowarden : retirée si identique au modèle de la langue ; sinon
|
|
195
|
+
# gardée et signalée (peut être modifiée ou dans une autre langue)
|
|
196
|
+
retirer_skill_claude() {
|
|
197
|
+
local f="$fichier_skill_claude" nom="Claude Code"
|
|
198
|
+
if [ ! -f "$f" ]; then
|
|
199
|
+
ok_t agents.skill_absent "$nom" "$f"
|
|
200
|
+
elif cmp -s "$f" "$MODELE_SKILL"; then
|
|
201
|
+
rm -f "$f"
|
|
202
|
+
rmdir "$(dirname "$f")" 2>/dev/null || true
|
|
203
|
+
ok_t agents.skill_removed "$nom" "$f"
|
|
204
|
+
else
|
|
205
|
+
attention_t agents.skill_kept_remove "$nom" "$f"
|
|
206
|
+
fi
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
# Skill repowarden pour opencode : même règle
|
|
210
|
+
retirer_skill_opencode() {
|
|
211
|
+
local f="$fichier_skill_opencode" nom="opencode"
|
|
212
|
+
if [ ! -f "$f" ]; then
|
|
213
|
+
ok_t agents.skill_absent "$nom" "$f"
|
|
214
|
+
elif cmp -s "$f" "$MODELE_SKILL"; then
|
|
215
|
+
rm -f "$f"
|
|
216
|
+
rmdir "$(dirname "$f")" 2>/dev/null || true
|
|
217
|
+
ok_t agents.skill_removed "$nom" "$f"
|
|
218
|
+
else
|
|
219
|
+
attention_t agents.skill_kept_remove "$nom" "$f"
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
167
223
|
statut=0
|
|
168
224
|
if [ "$action" = retirer ]; then
|
|
169
225
|
[ -z "$claude" ] || retirer_claude || statut=1
|
|
170
226
|
[ -z "$opencode" ] || retirer_opencode || statut=1
|
|
227
|
+
[ -z "$claude" ] || retirer_skill_claude || statut=1
|
|
228
|
+
[ -z "$opencode" ] || retirer_skill_opencode || statut=1
|
|
171
229
|
exit "$statut"
|
|
172
230
|
fi
|
|
173
231
|
[ -z "$claude" ] || init_claude || statut=1
|
|
174
232
|
[ -z "$opencode" ] || init_opencode || statut=1
|
|
233
|
+
[ -z "$claude" ] || poser_skill "$fichier_skill_claude" "Claude Code" || statut=1
|
|
234
|
+
[ -z "$opencode" ] || poser_skill "$fichier_skill_opencode" "opencode" || statut=1
|
|
175
235
|
|
|
176
236
|
has repowarden || attention_t agents.no_repowarden
|
|
177
237
|
# Hooks repowarden inactifs : le garde-fou refusera les commits, autant le
|
package/bin/garde
CHANGED
|
@@ -133,8 +133,22 @@ protegee() {
|
|
|
133
133
|
[ -n "$REPLY" ] && [ "$REPLY" = "$1" ]
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
# Vrai si le chemin $1 (absolu), dans le dépôt du dossier courant $2 (son
|
|
137
|
+
# dossier existant le plus proche), est ignoré par git : jamais versionné.
|
|
138
|
+
# Un fichier suivi n'est jamais ignoré (git check-ignore sans --no-index)
|
|
139
|
+
fichier_ignore() {
|
|
140
|
+
local reste="${1#"$2"}" prefixe cdup
|
|
141
|
+
reste="${reste#/}"
|
|
142
|
+
[ -n "$reste" ] || return 1
|
|
143
|
+
# Relatif à la racine par --show-prefix/--show-cdup : ni lien symbolique
|
|
144
|
+
# (/tmp → /private/tmp) ni forme Windows (C:/… contre /c/…) à comparer
|
|
145
|
+
prefixe="$(git rev-parse --show-prefix 2>/dev/null)" || return 1
|
|
146
|
+
cdup="$(git rev-parse --show-cdup 2>/dev/null)" || return 1
|
|
147
|
+
git -C "./$cdup" check-ignore -q -- "$prefixe$reste" 2>/dev/null
|
|
148
|
+
}
|
|
149
|
+
|
|
136
150
|
# Modification du fichier $1 : seulement sur une branche de travail (de ticket,
|
|
137
|
-
# si le dépôt suit ses tickets)
|
|
151
|
+
# si le dépôt suit ses tickets) ; un fichier ignoré par git l'est partout
|
|
138
152
|
verifier_fichier() {
|
|
139
153
|
dossier_r "$1"
|
|
140
154
|
# Sous Windows, jq finit le chemin lu par un CR (retiré) et le chemin peut
|
|
@@ -146,6 +160,8 @@ verifier_fichier() {
|
|
|
146
160
|
depot_repowarden || exit 0
|
|
147
161
|
# Fichiers internes de git (.git/…) : hors du travail de l'agent
|
|
148
162
|
case "$(pwd -P)/" in "$(cd "$(git rev-parse --git-dir)" && pwd -P)"/*) exit 0 ;; esac
|
|
163
|
+
# Fichier ignoré par git : jamais versionné, règle de branche non applicable
|
|
164
|
+
! fichier_ignore "$f" "$d" || exit 0
|
|
149
165
|
if [ -f "$f" ]; then
|
|
150
166
|
FICHIER_CARTE="$(cd "$(dirname "$f")" && pwd -P)/${f##*/}"
|
|
151
167
|
FICHIER_CARTE="${FICHIER_CARTE#"$(cd "$(git rev-parse --show-toplevel)" && pwd -P)"/}"
|
|
@@ -267,11 +283,38 @@ ticket_valide() {
|
|
|
267
283
|
[ "$(head -n 1 <<<"$lu")" = OPEN ] && grep -qxF "$VALIDE" <<<"$lu"
|
|
268
284
|
}
|
|
269
285
|
|
|
286
|
+
# Vrai si le texte $1 vise une étiquette de décision du mainteneur (validé,
|
|
287
|
+
# statut: …, à décider), même encodée dans une URL (valid%C3%A9)
|
|
288
|
+
etiquette_decision() {
|
|
289
|
+
local t
|
|
290
|
+
t="$(printf '%b' "${1//%/\\x}")"
|
|
291
|
+
[[ "$t" == *"$VALIDE"* || "$t" == *"statut:"* || "$t" == *"$A_DECIDER"* ]]
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
# Vrai si l'écriture gh api (méthode $1, chemin $2, mots $3, corps lu d'un
|
|
295
|
+
# fichier si $4) pose, retire ou remplace les étiquettes de décision d'un
|
|
296
|
+
# ticket : refusée même avec un ticket validé (#496). Dans le doute, refuser
|
|
297
|
+
api_etiquettes_decision() {
|
|
298
|
+
local m="${1:-POST}" re_etiq='/issues/[0-9]+/labels(/|\?|$)' re_ticket='/issues/[0-9]+/?(\?|$)'
|
|
299
|
+
if [[ "/$2" =~ $re_etiq ]]; then
|
|
300
|
+
# PUT remplace l'ensemble, DELETE sans nom le vide : validé compris
|
|
301
|
+
[ "$m" != PUT ] || return 0
|
|
302
|
+
[ "$m" != DELETE ] || [[ "$2" == */labels/?* ]] || return 0
|
|
303
|
+
[ -z "$4" ] || return 0
|
|
304
|
+
etiquette_decision "$2 $3"
|
|
305
|
+
elif [[ "/$2" =~ $re_ticket ]]; then
|
|
306
|
+
# PATCH du ticket : un champ labels remplace l'ensemble
|
|
307
|
+
[ -n "$4" ] || [[ "$3" == *labels* ]]
|
|
308
|
+
else
|
|
309
|
+
return 1
|
|
310
|
+
fi
|
|
311
|
+
}
|
|
312
|
+
|
|
270
313
|
# Commande gh (mots $@, sans « gh ») lancée depuis $dir, préfixe
|
|
271
314
|
# REPOWARDEN_TICKET=$ticket_gh : lectures connues permises, écritures du circuit
|
|
272
315
|
# par repowarden, décisions au mainteneur, tout le reste avec un ticket validé
|
|
273
316
|
verifier_gh() {
|
|
274
|
-
local ligne="gh $*" pos=() depot="" methode="" champs="" approuve="" mutation="" ecriture="" hors="" rc
|
|
317
|
+
local ligne="gh $*" mots="$*" pos=() depot="" methode="" champs="" corps="" approuve="" mutation="" ecriture="" hors="" rc
|
|
275
318
|
while [ $# -gt 0 ]; do
|
|
276
319
|
case "$1" in
|
|
277
320
|
-R | --repo) shift; depot="${1:-}" ;;
|
|
@@ -279,7 +322,8 @@ verifier_gh() {
|
|
|
279
322
|
-X | --method) shift; methode="${1:-}" ;;
|
|
280
323
|
-X?*) methode="${1#-X}" ;;
|
|
281
324
|
--method=*) methode="${1#--method=}" ;;
|
|
282
|
-
|
|
325
|
+
--input | --input=*) champs=1 corps=1 ;;
|
|
326
|
+
-f | -F | --field | --raw-field | -f?* | -F?* | --field=* | --raw-field=*) champs=1 ;;
|
|
283
327
|
-a | --approve) approuve=1 ;;
|
|
284
328
|
-h | --help) return 0 ;;
|
|
285
329
|
-*) ;;
|
|
@@ -314,6 +358,7 @@ verifier_gh() {
|
|
|
314
358
|
"api "*)
|
|
315
359
|
[ -n "$ecriture" ] || exit 0
|
|
316
360
|
[[ "${pos[1]:-}" != *pending_deployments* ]] || refuser garde.gh_mainteneur "$ligne"
|
|
361
|
+
! api_etiquettes_decision "$methode" "${pos[1]:-}" "$mots" "$corps" || refuser garde.etiquette_mainteneur "$ligne"
|
|
317
362
|
hors=1
|
|
318
363
|
;;
|
|
319
364
|
# Lectures connues et description de la PR : permises ; tout le
|
|
@@ -335,14 +380,93 @@ verifier_gh() {
|
|
|
335
380
|
) || exit $?
|
|
336
381
|
}
|
|
337
382
|
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
#
|
|
341
|
-
|
|
383
|
+
# repowarden ticket label <n> <label> [--remove] (mots $@ après « repowarden »),
|
|
384
|
+
# lancé depuis $dir : poser ou retirer « validé » ou un statut valide le ticket,
|
|
385
|
+
# décision du mainteneur refusée même avec REPOWARDEN_TICKET (incident #477)
|
|
386
|
+
verifier_ticket_label() {
|
|
387
|
+
local ligne="repowarden $*" mot decision=""
|
|
388
|
+
case "$1" in
|
|
389
|
+
ticket) [ "${2:-}" = label ] || return 0; shift 2 ;;
|
|
390
|
+
*) shift ;;
|
|
391
|
+
esac
|
|
392
|
+
[ $# -ge 2 ] || return 0
|
|
393
|
+
lecture_seule "$@" && return 0
|
|
394
|
+
# Mots après le numéro : une étiquette entre guillemets y est découpée
|
|
395
|
+
# (« statut: en cours » → statut:), dans le doute refuser
|
|
396
|
+
shift
|
|
397
|
+
for mot in "$@"; do
|
|
398
|
+
etiquette_decision "$mot" && decision=1
|
|
399
|
+
done
|
|
400
|
+
# Étiquette de décision en plusieurs mots (« à décider »), découpée par read -a
|
|
401
|
+
etiquette_decision "$*" && decision=1
|
|
402
|
+
[ -n "$decision" ] || return 0
|
|
403
|
+
dossier_r "$dir"
|
|
404
|
+
(
|
|
405
|
+
cd "$REPLY" 2>/dev/null || exit 0
|
|
406
|
+
depot_repowarden || exit 0
|
|
407
|
+
refuser garde.ticket_mainteneur "$ligne"
|
|
408
|
+
) || exit $?
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
# repowarden ticket nouveau/new (mots $@), depuis $dir : créer un ticket est
|
|
412
|
+
# une décision du mainteneur, sauf agentsTickets ; --help reste permis (#496)
|
|
413
|
+
verifier_ticket_nouveau() {
|
|
414
|
+
local ligne="repowarden $*"
|
|
415
|
+
[ "${1:-}" = ticket ] || return 0
|
|
416
|
+
case "${2:-}" in nouveau | new) ;; *) return 0 ;; esac
|
|
417
|
+
lecture_seule "$@" && return 0
|
|
418
|
+
dossier_r "$dir"
|
|
419
|
+
(
|
|
420
|
+
cd "$REPLY" 2>/dev/null || exit 0
|
|
421
|
+
depot_repowarden || exit 0
|
|
422
|
+
cfg_r agentsTickets false
|
|
423
|
+
[ "$REPLY" != true ] || exit 0
|
|
424
|
+
refuser garde.ticket_nouveau "$ligne"
|
|
425
|
+
) || exit $?
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# Découpe $1 en une commande par ligne (REPLY) : « ; », « | », « && », « || » et
|
|
429
|
+
# le « & » séparateur coupent hors guillemets simples et doubles (antislash
|
|
430
|
+
# compris) ; seul le « & » d'une vraie redirection (N>&M, >&2, >&-, &>, &>>, non
|
|
431
|
+
# échappée) reste dans la commande, citée entière. Dans le doute couper : au pire
|
|
432
|
+
# une commande de trop, jamais une masquée (true \>&git push -f lance git).
|
|
433
|
+
# Entre guillemets le retour à la ligne devient une espace : le contenu exact
|
|
434
|
+
# d'une chaîne n'importe pas pour juger la commande.
|
|
342
435
|
decouper_lignes() {
|
|
343
|
-
local s="$1" out="" c i j prec avant nl=$'\n'
|
|
436
|
+
local s="$1" out="" c q="" i j prec avant nl=$'\n'
|
|
344
437
|
for ((i = 0; i < ${#s}; i++)); do
|
|
345
438
|
c="${s:i:1}"
|
|
439
|
+
if [ -n "$q" ]; then
|
|
440
|
+
if [ "$c" = "$nl" ]; then
|
|
441
|
+
out="$out "
|
|
442
|
+
continue
|
|
443
|
+
fi
|
|
444
|
+
out="$out$c"
|
|
445
|
+
if [ "$c" = "$q" ]; then
|
|
446
|
+
q=""
|
|
447
|
+
elif [ "$c" = "\\" ] && [ "$q" = '"' ]; then
|
|
448
|
+
i=$((i + 1))
|
|
449
|
+
out="$out${s:i:1}"
|
|
450
|
+
fi
|
|
451
|
+
continue
|
|
452
|
+
fi
|
|
453
|
+
case "$c" in
|
|
454
|
+
"'" | '"')
|
|
455
|
+
q="$c"
|
|
456
|
+
out="$out$c"
|
|
457
|
+
continue
|
|
458
|
+
;;
|
|
459
|
+
"\\")
|
|
460
|
+
out="$out$c"
|
|
461
|
+
i=$((i + 1))
|
|
462
|
+
out="$out${s:i:1}"
|
|
463
|
+
continue
|
|
464
|
+
;;
|
|
465
|
+
";" | "|")
|
|
466
|
+
out="$out$nl"
|
|
467
|
+
continue
|
|
468
|
+
;;
|
|
469
|
+
esac
|
|
346
470
|
if [ "$c" != "&" ]; then
|
|
347
471
|
out="$out$c"
|
|
348
472
|
continue
|
|
@@ -378,28 +502,72 @@ decouper_lignes() {
|
|
|
378
502
|
REPLY="$out"
|
|
379
503
|
}
|
|
380
504
|
|
|
381
|
-
#
|
|
382
|
-
#
|
|
383
|
-
|
|
505
|
+
# Découpe $1 en mots (MOTS) en respectant guillemets simples et doubles et
|
|
506
|
+
# antislash, sans sous-processus ; guillemet non fermé : MOTS vide, code 1.
|
|
507
|
+
decouper_mots() {
|
|
508
|
+
local s="$1" mot="" q="" c i n
|
|
509
|
+
MOTS=()
|
|
510
|
+
n=${#s}
|
|
511
|
+
for ((i = 0; i < n; i++)); do
|
|
512
|
+
c="${s:i:1}"
|
|
513
|
+
if [ -n "$q" ]; then
|
|
514
|
+
if [ "$c" = "$q" ]; then
|
|
515
|
+
q=""
|
|
516
|
+
elif [ "$c" = "\\" ] && [ "$q" = '"' ]; then
|
|
517
|
+
case "${s:i+1:1}" in
|
|
518
|
+
'"' | "\\" | '$' | '`') i=$((i + 1)); mot="$mot${s:i:1}" ;;
|
|
519
|
+
*) mot="$mot\\" ;;
|
|
520
|
+
esac
|
|
521
|
+
else
|
|
522
|
+
mot="$mot$c"
|
|
523
|
+
fi
|
|
524
|
+
continue
|
|
525
|
+
fi
|
|
526
|
+
case "$c" in
|
|
527
|
+
"'" | '"') q="$c" ;;
|
|
528
|
+
"\\")
|
|
529
|
+
i=$((i + 1))
|
|
530
|
+
mot="$mot${s:i:1}"
|
|
531
|
+
;;
|
|
532
|
+
" " | $'\t')
|
|
533
|
+
[ -z "$mot" ] || { MOTS+=("$mot"); mot=""; }
|
|
534
|
+
;;
|
|
535
|
+
*) mot="$mot$c" ;;
|
|
536
|
+
esac
|
|
537
|
+
done
|
|
538
|
+
if [ -n "$q" ]; then
|
|
539
|
+
MOTS=()
|
|
540
|
+
return 1
|
|
541
|
+
fi
|
|
542
|
+
[ -z "$mot" ] || MOTS+=("$mot")
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
# Ligne de commande shell : chaque commande (« ; », « | », « && », « || » et le
|
|
546
|
+
# « & » séparateur, hors guillemets), « cd » suivi, variables d'environnement en
|
|
547
|
+
# tête ignorées (sauf REPOWARDEN_TICKET, le ticket d'une écriture gh hors du
|
|
548
|
+
# circuit des PR)
|
|
384
549
|
verifier_shell() {
|
|
385
|
-
local commande="$1" segment
|
|
550
|
+
local commande="$1" segment m cmd_cc lignes ticket_gh
|
|
386
551
|
dir="$cwd"
|
|
387
|
-
# Une commande par ligne
|
|
388
|
-
#
|
|
389
|
-
|
|
390
|
-
lignes="${lignes//||/$nl}"
|
|
391
|
-
lignes="${lignes//;/$nl}"
|
|
392
|
-
lignes="${lignes//|/$nl}"
|
|
393
|
-
# Un « & » de redirection (2>&1, >&2, &>log…) n'est pas un séparateur : la
|
|
394
|
-
# commande citée dans le refus et celle proposée restent entières
|
|
395
|
-
decouper_lignes "$lignes"
|
|
552
|
+
# Une commande par ligne, en un seul parcours caractère par caractère qui
|
|
553
|
+
# respecte les guillemets (pas de <( … ) : bash 3.2 le lit mal).
|
|
554
|
+
decouper_lignes "$commande"
|
|
396
555
|
lignes="$REPLY"
|
|
397
556
|
# Plusieurs commandes : un refus vaut pour toute la ligne (dit à l'agent)
|
|
398
557
|
[ "$(grep -c '[^[:space:]]' <<<"$lignes")" -le 1 ] || LIGNE_MULTIPLE=1
|
|
399
558
|
while IFS= read -r segment; do
|
|
400
|
-
#
|
|
401
|
-
|
|
402
|
-
|
|
559
|
+
# Guillemet non fermé : commande illisible, refusée dans un dépôt
|
|
560
|
+
# repowarden (dans le doute, refuser) ; ailleurs, tout est permis
|
|
561
|
+
if ! decouper_mots "$segment"; then
|
|
562
|
+
dossier_r "$dir"
|
|
563
|
+
(
|
|
564
|
+
cd "$REPLY" 2>/dev/null || exit 0
|
|
565
|
+
depot_repowarden || exit 0
|
|
566
|
+
refuser garde.commande_illisible "$segment"
|
|
567
|
+
) || exit $?
|
|
568
|
+
continue
|
|
569
|
+
fi
|
|
570
|
+
set -- ${MOTS[@]+"${MOTS[@]}"}
|
|
403
571
|
ticket_gh=""
|
|
404
572
|
while [ $# -gt 0 ]; do
|
|
405
573
|
case "$1" in
|
|
@@ -409,7 +577,13 @@ verifier_shell() {
|
|
|
409
577
|
esac
|
|
410
578
|
done
|
|
411
579
|
[ $# -gt 0 ] || continue
|
|
580
|
+
# Commande : dernier élément du chemin (/ ou \), sans .exe/.cmd/.bat
|
|
412
581
|
m="${1//[\"\']/}"
|
|
582
|
+
m="${m##*/}"
|
|
583
|
+
m="${m##*\\}"
|
|
584
|
+
case "$m" in
|
|
585
|
+
*.exe | *.EXE | *.cmd | *.CMD | *.bat | *.BAT) m="${m%.*}" ;;
|
|
586
|
+
esac
|
|
413
587
|
case "$m" in
|
|
414
588
|
cd)
|
|
415
589
|
case "${2:-}" in
|
|
@@ -419,21 +593,28 @@ verifier_shell() {
|
|
|
419
593
|
*) dir="$dir/${2//[\"\']/}" ;;
|
|
420
594
|
esac
|
|
421
595
|
;;
|
|
422
|
-
git
|
|
596
|
+
git)
|
|
423
597
|
shift
|
|
424
598
|
verifier_git "${@//[\"\']/}"
|
|
425
599
|
;;
|
|
426
|
-
gh
|
|
600
|
+
gh)
|
|
427
601
|
shift
|
|
428
602
|
verifier_gh "${@//[\"\']/}"
|
|
429
603
|
;;
|
|
430
|
-
repowarden
|
|
604
|
+
repowarden)
|
|
605
|
+
shift
|
|
606
|
+
verifier_ticket_nouveau "${@//[\"\']/}"
|
|
607
|
+
set -- repowarden "$@"
|
|
431
608
|
case "${2//[\"\']/}" in
|
|
432
609
|
cc | commit)
|
|
433
610
|
cmd_cc="repowarden ${2//[\"\']/}"
|
|
434
611
|
shift 2
|
|
435
612
|
lecture_seule "${@//[\"\']/}" || verifier_cc "$cmd_cc"
|
|
436
613
|
;;
|
|
614
|
+
ticket | ticket-label)
|
|
615
|
+
shift
|
|
616
|
+
verifier_ticket_label "${@//[\"\']/}"
|
|
617
|
+
;;
|
|
437
618
|
esac
|
|
438
619
|
;;
|
|
439
620
|
esac
|
package/bin/statut
CHANGED
|
@@ -177,11 +177,15 @@ if agents_sans_garde "$GIT_TOPLEVEL"; then
|
|
|
177
177
|
attention_t st.agents_sans_garde "$cmd_agents"
|
|
178
178
|
suivant="${suivant:-$cmd_agents}"
|
|
179
179
|
fi
|
|
180
|
-
# Garde-fou
|
|
181
|
-
while read -r portee
|
|
180
|
+
# Garde-fou ou skill posés par une version antérieure de repowarden : init le met à jour
|
|
181
|
+
while read -r portee agent fichier; do
|
|
182
182
|
cmd_agents="repowarden agents init"
|
|
183
183
|
[ "$portee" = projet ] || cmd_agents="$cmd_agents --global"
|
|
184
|
-
|
|
184
|
+
if [ "$agent" = skill-claude ] || [ "$agent" = skill-opencode ]; then
|
|
185
|
+
attention_t st.agents_skill_stale "$fichier" "$cmd_agents"
|
|
186
|
+
else
|
|
187
|
+
attention_t st.agents_stale "$fichier" "$cmd_agents"
|
|
188
|
+
fi
|
|
185
189
|
suivant="${suivant:-$cmd_agents}"
|
|
186
190
|
done < <(agents_perimes "$GIT_TOPLEVEL")
|
|
187
191
|
|
package/ci/tickets/push.sh
CHANGED
|
@@ -267,6 +267,10 @@ description_pr_brouillon_r() {
|
|
|
267
267
|
REPLY="$corps${corps:+$'\n\n'}Ticket : #$n"
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
# Numéro de la PR ouverte depuis la branche $1, vide sinon ; relu juste avant
|
|
271
|
+
# la création pour ne jamais ouvrir deux PR pour une même branche
|
|
272
|
+
pr_ouverte() { gh pr list --state open --head "$1" --json number -q '.[0].number // empty'; }
|
|
273
|
+
|
|
270
274
|
# REPLY = compte à assigner à la PR brouillon du ticket $1 : l'auteur du push
|
|
271
275
|
# s'il est une personne (GitHub refuse d'assigner un bot avec un jeton d'App),
|
|
272
276
|
# sinon le premier assigné du ticket, sinon vide
|
|
@@ -296,7 +300,7 @@ cmd_push() {
|
|
|
296
300
|
[ -n "$n" ] || return 0
|
|
297
301
|
# Branche faite hors du ticket (à la main) : inscrite sur le ticket
|
|
298
302
|
inscrire_branche "$n" "$PUSH_BRANCH"
|
|
299
|
-
pr="$(
|
|
303
|
+
pr="$(pr_ouverte "$PUSH_BRANCH")"
|
|
300
304
|
# PR existante : rien à faire si elle n'est plus en brouillon
|
|
301
305
|
[ -z "$pr" ] || [ "$(gh pr view "$pr" --json isDraft -q .isDraft)" = true ] || return 0
|
|
302
306
|
[ "$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$PUSH_BRANCH" -q .ahead_by)" != 0 ] || return 0
|
|
@@ -309,6 +313,12 @@ cmd_push() {
|
|
|
309
313
|
notice_t tk.draft_updated "$pr" "$n"
|
|
310
314
|
return 0
|
|
311
315
|
fi
|
|
316
|
+
# Deux push rapprochés : un autre run a pu ouvrir la PR entre-temps
|
|
317
|
+
pr="$(pr_ouverte "$PUSH_BRANCH")"
|
|
318
|
+
if [ -n "$pr" ]; then
|
|
319
|
+
notice_t tk.draft_exists "$pr" "$n"
|
|
320
|
+
return 0
|
|
321
|
+
fi
|
|
312
322
|
url="$(gh pr create --draft --base "$INTEGRATION" --head "$PUSH_BRANCH" --title "$titre" \
|
|
313
323
|
--body "$corps")"
|
|
314
324
|
# assignation à part : un refus de GitHub ne doit pas faire échouer le suivi
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
// absent ou en panne : rien n'est bloqué. opencode 1.x : repowarden-garde.js,
|
|
5
5
|
// à côté (un module à export par défaut y passerait pour un plugin 2.x).
|
|
6
6
|
// Plugins opencode 2.x : export par défaut { id, setup }, crochet
|
|
7
|
-
// ctx.tool.hook("execute.before") (outils edit, write, shell…)
|
|
7
|
+
// ctx.tool.hook("execute.before") (outils edit, write, shell…). opencode 1.19
|
|
8
|
+
// lit tout export par défaut qui a un id comme un plugin à server() : sans
|
|
9
|
+
// lui, « failed to load plugin » à chaque démarrage ; server() ne rend aucun
|
|
10
|
+
// crochet, le garde-fou 1.x reste celui de repowarden-garde.js
|
|
8
11
|
import { spawnSync } from "node:child_process"
|
|
9
12
|
|
|
10
13
|
// Soumet l'appel d'outil à repowarden garde ; l'erreur levée par le crochet
|
|
@@ -25,4 +28,5 @@ export default {
|
|
|
25
28
|
const dossier = ctx.location?.directory || process.cwd()
|
|
26
29
|
return ctx.tool.hook("execute.before", (appel) => garde(appel.tool, appel.input, dossier))
|
|
27
30
|
},
|
|
31
|
+
server: async () => ({}),
|
|
28
32
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: repowarden
|
|
3
|
+
description: Repowarden skill for AI agents. Triggers on ticket, commit, branch, push, PR, CI, gh, forbidden action or guard refusal.
|
|
4
|
+
---
|
|
5
|
+
<!-- repowarden:skill installed by repowarden agents init; remove this line to customize it -->
|
|
6
|
+
|
|
7
|
+
# Repowarden
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
Follow the repowarden workflow in the repo: no change without a ticket, written design, `repowarden cc`, draft PR opened by the bot, release by the maintainer.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
1. **Ticket before any change** (including GitHub settings). Without a ticket, stop and ask for one: an agent only creates a ticket (`repowarden ticket nouveau "<title>"`) when asked to, and never validates one.
|
|
14
|
+
2. **Design**: write a "Conception retenue" on the ticket (what, files touched, out of scope). Any change of plan must be noted on the ticket before or when it happens.
|
|
15
|
+
3. **Take the ticket**: `repowarden ticket <n>`. Branch `type/<n>-sujet` is created from `develop`.
|
|
16
|
+
4. **Sandbox tests**: use `./.essai/` for disposable repos without a remote. Delete them at the end.
|
|
17
|
+
5. **Code and commit**: `git add <files>` (never `git add .`, `-A` or `--all`). Commit via `repowarden cc -m "<description only>" --type <type> --scope <scope> --body "<why>" --refs "Ticket : #<n>"` (French messages, header ≤ 72 chars, no signature/agent). No `--no-verify`.
|
|
18
|
+
6. **Push**: `git push -u origin HEAD` without force. The bot opens a draft PR to `develop` with `Ticket : #<n>` (never `Closes #<n>`).
|
|
19
|
+
7. **PR**: the maintainer marks it ready and releases; the agent follows `repowarden pr checks <n>` and reports when everything is green. An agent does not merge, does not mark a PR ready, does not close tickets. Only `develop` enters `main`, via the release PR.
|
|
20
|
+
|
|
21
|
+
## Useful commands
|
|
22
|
+
- `repowarden ticket <n>` / `repowarden ticket comment <n> "<text>"`
|
|
23
|
+
- `repowarden cc --dry-run -m "<description>" --type <type> --scope <scope> --body "<why>" --refs "Ticket : #<n>"`
|
|
24
|
+
- `repowarden pr checks <n>`, `repowarden run rerun <id|--failed|--workflow>`
|
|
25
|
+
- `REPOWARDEN_TICKET=<n> gh …`: GitHub write outside the workflow, tied to ticket <n>
|
|
26
|
+
- `opencode run --auto "<instruction>" < /dev/null`: non-interactive opencode (without `--auto` every permission is denied, without `< /dev/null` it waits silently)
|
|
27
|
+
|
|
28
|
+
## Guard
|
|
29
|
+
It refuses in particular:
|
|
30
|
+
- direct or forced push to `main`/`develop`;
|
|
31
|
+
- `git commit` outside `repowarden cc`;
|
|
32
|
+
- modifying a file on a protected branch or outside a ticket branch;
|
|
33
|
+
- `gh` write outside the workflow without a ticket;
|
|
34
|
+
- validating a ticket or changing its status.
|
|
35
|
+
|
|
36
|
+
If `repowarden garde` refuses an action: read the message, run exactly the command indicated, never bypass (`--no-verify`, `--admin`, exception). A quoted string containing a forbidden command must go in a file. If the refusal seems impossible given the ticket design, stop and explain on the ticket instead of guessing.
|
|
37
|
+
|
|
38
|
+
## Rules
|
|
39
|
+
- French commit messages, no signature or co-author from an agent.
|
|
40
|
+
- Bash 3.2 compatible if touching shell scripts.
|
|
41
|
+
- A bug becomes a ticket; if linked to an open ticket, note it as "Ajout" there.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: repowarden
|
|
3
|
+
description: Skill repowarden pour agents IA. Déclenche sur ticket, commit, branche, push, PR, CI, gh, action interdite ou refus du garde-fou.
|
|
4
|
+
---
|
|
5
|
+
<!-- repowarden:skill posée par repowarden agents init ; retirer cette ligne pour la personnaliser -->
|
|
6
|
+
|
|
7
|
+
# Repowarden
|
|
8
|
+
|
|
9
|
+
## But
|
|
10
|
+
Appliquer le circuit repowarden dans le dépôt : pas de changement sans ticket, conception écrite, `repowarden cc`, PR brouillon par le bot, livraison par le mainteneur.
|
|
11
|
+
|
|
12
|
+
## Circuit
|
|
13
|
+
1. **Ticket créé avant tout changement** (même pour un réglage GitHub). Sans ticket, s'arrêter et en demander un : un agent ne crée un ticket (`repowarden ticket nouveau "<titre>"`) que si on le lui demande, et n'en valide jamais.
|
|
14
|
+
2. **Conception** : noter la « Conception retenue » sur le ticket (quoi, fichiers touchés, hors périmètre). Tout écart est noté sur le ticket avant ou au moment.
|
|
15
|
+
3. **Prendre le ticket** : `repowarden ticket <n>`. La branche `type/<n>-sujet` est créée depuis `develop`.
|
|
16
|
+
4. **Cadrer les essais** : utiliser `./.essai/` pour des dépôts jetables sans remote. Les supprimer à la fin.
|
|
17
|
+
5. **Coder et commiter** : `git add <fichiers>` (jamais `git add .`, `-A` ou `--all`). Message via `repowarden cc -m "<description seule>" --type <type> --scope <portée> --body "<pourquoi>" --refs "Ticket : #<n>"` (français, en-tête ≤ 72 caractères, sans signature/agent). Sans `--no-verify`.
|
|
18
|
+
6. **Pousser** : `git push -u origin HEAD` sans forcer. Le bot ouvre une PR brouillon vers `develop` avec `Ticket : #<n>` (jamais `Closes #<n>`).
|
|
19
|
+
7. **PR** : le mainteneur la passe en ready et livre ; l'agent suit `repowarden pr checks <n>` et signale quand tout est vert. Un agent ne merge pas, ne passe pas une PR en ready, ne ferme pas de ticket. Seul `develop` entre dans `main`, par la PR de livraison.
|
|
20
|
+
|
|
21
|
+
## Commandes utiles
|
|
22
|
+
- `repowarden ticket <n>` / `repowarden ticket comment <n> "<texte>"`
|
|
23
|
+
- `repowarden cc --dry-run -m "<description>" --type <type> --scope <portée> --body "<pourquoi>" --refs "Ticket : #<n>"`
|
|
24
|
+
- `repowarden pr checks <n>`, `repowarden run rerun <id|--failed|--workflow>`
|
|
25
|
+
- `REPOWARDEN_TICKET=<n> gh …` : écriture GitHub hors circuit, rattachée au ticket <n>
|
|
26
|
+
- `opencode run --auto "<consigne>" < /dev/null` : opencode non interactif (sans `--auto` chaque permission est refusée, sans `< /dev/null` il attend en silence)
|
|
27
|
+
|
|
28
|
+
## Garde-fou
|
|
29
|
+
Il refuse notamment :
|
|
30
|
+
- push direct ou forcé sur `main`/`develop` ;
|
|
31
|
+
- `git commit` hors `repowarden cc` ;
|
|
32
|
+
- modifier un fichier sur une branche protégée ou hors branche de ticket ;
|
|
33
|
+
- écriture `gh` hors circuit sans ticket ;
|
|
34
|
+
- valider un ticket ou changer son statut.
|
|
35
|
+
|
|
36
|
+
Si `repowarden garde` refuse une action : lire le message, appliquer exactement la commande indiquée, ne jamais contourner (`--no-verify`, `--admin`, exception). Un texte entre guillemets citant une commande interdite doit aller dans un fichier. Si le refus semble impossible au regard de la conception du ticket, s'arrêter et l'expliquer sur le ticket, pas d'invention.
|
|
37
|
+
|
|
38
|
+
## Règles
|
|
39
|
+
- Messages français, sans signature ni co-auteur d'agent.
|
|
40
|
+
- Bash 3.2 compatible si on touche des scripts shell.
|
|
41
|
+
- Un défaut devient un ticket ; s'il est lié à un ticket ouvert, le noter en « Ajout » dessus.
|
package/hooks/lib/agents.sh
CHANGED
|
@@ -11,6 +11,15 @@ AGENTS_PLUGINS_OPENCODE="repowarden-garde.js repowarden-garde-v2.js"
|
|
|
11
11
|
AGENTS_COMMANDE_CLAUDE="repowarden garde --format claude"
|
|
12
12
|
AGENTS_MATCHER_CLAUDE="Edit|Write|MultiEdit|NotebookEdit|Bash"
|
|
13
13
|
|
|
14
|
+
# Modèles de la skill repowarden selon la langue (fr/en)
|
|
15
|
+
agents_modele_skill() {
|
|
16
|
+
local lang="${REPOWARDEN_LANG:-fr}"
|
|
17
|
+
case "$lang" in
|
|
18
|
+
en) echo "$(cd "$(dirname "${BASH_SOURCE[0]}")/../agents" && pwd -P)/skill-repowarden.en.md" ;;
|
|
19
|
+
*) echo "$(cd "$(dirname "${BASH_SOURCE[0]}")/../agents" && pwd -P)/skill-repowarden.fr.md" ;;
|
|
20
|
+
esac
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
# Dossiers de configuration de l'utilisateur, là où chaque outil les lit
|
|
15
24
|
# (CLAUDE_CONFIG_DIR ; OPENCODE_CONFIG_DIR, sinon XDG_CONFIG_HOME)
|
|
16
25
|
agents_dossiers_globaux() {
|
|
@@ -47,13 +56,27 @@ agents_claude_perime() {
|
|
|
47
56
|
# Garde-fous périmés du dépôt $1 (vide hors dépôt) puis de l'utilisateur, une
|
|
48
57
|
# ligne « portée agent fichier » chacun : statut les signale, install les met à jour
|
|
49
58
|
agents_perimes() {
|
|
59
|
+
local modele
|
|
60
|
+
modele="$(agents_modele_skill)"
|
|
50
61
|
agents_dossiers_globaux
|
|
51
62
|
if [ -n "${1:-}" ]; then
|
|
52
63
|
! agents_claude_perime "$1/.claude/settings.json" || echo "projet claude $1/.claude/settings.json"
|
|
53
64
|
agents_opencode_perimes projet "$1/.opencode/plugins"
|
|
65
|
+
if agents_skill_perime "$1/.claude/skills/repowarden/SKILL.md" "$modele"; then
|
|
66
|
+
echo "projet skill-claude $1/.claude/skills/repowarden/SKILL.md"
|
|
67
|
+
fi
|
|
68
|
+
if agents_skill_perime "$1/.opencode/skills/repowarden/SKILL.md" "$modele"; then
|
|
69
|
+
echo "projet skill-opencode $1/.opencode/skills/repowarden/SKILL.md"
|
|
70
|
+
fi
|
|
54
71
|
fi
|
|
55
72
|
! agents_claude_perime "$AGENTS_CLAUDE_DIR/settings.json" || echo "global claude $AGENTS_CLAUDE_DIR/settings.json"
|
|
56
73
|
agents_opencode_perimes global "$AGENTS_OPENCODE_DIR/plugins"
|
|
74
|
+
if agents_skill_perime "$AGENTS_CLAUDE_DIR/skills/repowarden/SKILL.md" "$modele"; then
|
|
75
|
+
echo "global skill-claude $AGENTS_CLAUDE_DIR/skills/repowarden/SKILL.md"
|
|
76
|
+
fi
|
|
77
|
+
if agents_skill_perime "$AGENTS_OPENCODE_DIR/skills/repowarden/SKILL.md" "$modele"; then
|
|
78
|
+
echo "global skill-opencode $AGENTS_OPENCODE_DIR/skills/repowarden/SKILL.md"
|
|
79
|
+
fi
|
|
57
80
|
return 0
|
|
58
81
|
}
|
|
59
82
|
|
|
@@ -77,8 +100,8 @@ agents_sans_garde() {
|
|
|
77
100
|
agents_dossiers_globaux
|
|
78
101
|
command -v claude >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 ||
|
|
79
102
|
[ -d "$AGENTS_CLAUDE_DIR" ] || [ -d "$AGENTS_OPENCODE_DIR" ] || return 1
|
|
80
|
-
[ -z "${1:-}" ] || fichiers=("$1/.claude/settings.json" "$1/.opencode/plugins/repowarden-garde.js")
|
|
81
|
-
fichiers+=("$AGENTS_CLAUDE_DIR/settings.json" "$AGENTS_OPENCODE_DIR/plugins/repowarden-garde.js")
|
|
103
|
+
[ -z "${1:-}" ] || fichiers=("$1/.claude/settings.json" "$1/.opencode/plugins/repowarden-garde.js" "$1/.opencode/plugins/repowarden-garde-v2.js")
|
|
104
|
+
fichiers+=("$AGENTS_CLAUDE_DIR/settings.json" "$AGENTS_OPENCODE_DIR/plugins/repowarden-garde.js" "$AGENTS_OPENCODE_DIR/plugins/repowarden-garde-v2.js")
|
|
82
105
|
# Plugin repowarden d'une version antérieure : branché (périmé, pas absent)
|
|
83
106
|
for f in "${fichiers[@]}"; do
|
|
84
107
|
! grep -qs "repowarden garde" "$f" || return 1
|
|
@@ -86,3 +109,16 @@ agents_sans_garde() {
|
|
|
86
109
|
done
|
|
87
110
|
return 0
|
|
88
111
|
}
|
|
112
|
+
|
|
113
|
+
# Vrai si le fichier skill $1 a été posé par repowarden (repère)
|
|
114
|
+
agents_skill_repowarden() {
|
|
115
|
+
grep -q '<!-- repowarden:skill' "$1" 2>/dev/null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# Vrai si la skill $1 vient de repowarden mais diffère du modèle de la langue
|
|
119
|
+
agents_skill_perime() {
|
|
120
|
+
local modele="$2"
|
|
121
|
+
[ -f "$1" ] || return 1
|
|
122
|
+
agents_skill_repowarden "$1" || return 1
|
|
123
|
+
! cmp -s "$1" "$modele"
|
|
124
|
+
}
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -131,7 +131,7 @@ _msg_en() {
|
|
|
131
131
|
help.migrer) _M="Usage: repowarden migrer [--pr] [--hooks]\n\nMigrates from repogarde to repowarden (docs/migration-v4.md): configs,\nworkflows, renames, lists manual steps left. Safe to rerun.\n --pr on chore/migration-repowarden-vN: commit, push and PR\n --hooks also move leftover lefthook hooks to .git/hooks-lefthook/" ;;
|
|
132
132
|
help.npm-publication) _M="Usage: repowarden npm-publication [--repo owner/name] [--workflow release.yml]\n [--environnement NAME] [--dry-run]\n\nSets up automatic npm publishing per release via OIDC (trusted publishing,\nprovenance). Needs npm logged in, package exists; if interrupted resumes.\nWorkflow: release.yml publishes via npm-publish.yml." ;;
|
|
133
133
|
help.garde) _M="Usage: repowarden garde [--format claude|opencode] (reads the action on standard input)\n\nAI agent guard, called by the agent's hook before each action (repowarden\nagents init wires it): exit code 0 allowed, 2 refused with the command to\nrun. In a repowarden repository: no change on a detached HEAD, on a\nprotected branch, or outside a ticket branch when the repository tracks its\ntickets; branches created by repowarden ticket <n>, commits by repowarden\ncc (repowarden hooks active, off protected branches); no --no-verify, no\ngit add . / -A / --all. Requires jq." ;;
|
|
134
|
-
help.agents) _M="Usage: repowarden agents init [--claude] [--opencode] [--projet | --global]\n repowarden agents retirer [--claude] [--opencode] [--projet | --global]\n\nWires the guard (repowarden garde) into AI agents: Claude Code PreToolUse\nhook (.claude/settings.json), opencode plugins\n(.opencode/plugins/repowarden-garde.js for opencode 1.x,\nrepowarden-garde-v2.js for 2.x). Without --claude or --opencode:\nboth. --projet (default): repository files, to commit; --global: user\nconfiguration (\$CLAUDE_CONFIG_DIR, ~/.claude by default;\n\$OPENCODE_CONFIG_DIR, else \$XDG_CONFIG_HOME/opencode, ~/.config/opencode by\ndefault). Re-runnable: existing files are completed, never overwritten.\nretirer (or remove): removes only what init wrote." ;;
|
|
134
|
+
help.agents) _M="Usage: repowarden agents init [--claude] [--opencode] [--projet | --global]\n repowarden agents retirer [--claude] [--opencode] [--projet | --global]\n\nWires the guard (repowarden garde) into AI agents: Claude Code PreToolUse\nhook (.claude/settings.json), opencode plugins\n(.opencode/plugins/repowarden-garde.js for opencode 1.x,\nrepowarden-garde-v2.js for 2.x). Also places the repowarden skill\n(.claude/skills/repowarden/SKILL.md, .opencode/skills/repowarden/SKILL.md)\nin the repowarden language. Without --claude or --opencode:\nboth. --projet (default): repository files, to commit; --global: user\nconfiguration (\$CLAUDE_CONFIG_DIR, ~/.claude by default;\n\$OPENCODE_CONFIG_DIR, else \$XDG_CONFIG_HOME/opencode, ~/.config/opencode by\ndefault). Re-runnable: existing files are completed, never overwritten.\nretirer (or remove): removes only what init wrote." ;;
|
|
135
135
|
help.app) _M="Usage: repowarden app init [--repo owner/name] [--nom NAME] [--administration]\n repowarden app init --bot\n\nCreates/sets up a GitHub App for repowarden workflows (Actions token doesn't\ntrigger other workflows; App token does). Browser-based manifest, minimal\npermissions, credentials stored securely, installed on repo. --bot for org\nbot (pilot repo). Needs gh and node. Rerunnable." ;;
|
|
136
136
|
# Hooks and language plugins
|
|
137
137
|
hook.pre_commit.1) _M="Commit refused: branch name does not follow the convention." ;;
|
|
@@ -530,6 +530,7 @@ _msg_en() {
|
|
|
530
530
|
st.tools) _M="Tools present: %s · missing: %s (depending on the projects' languages)" ;;
|
|
531
531
|
st.agents_sans_garde) _M="AI agent without the guard (Claude Code, opencode): its actions are only checked at commit time. → %s" ;;
|
|
532
532
|
st.agents_stale) _M="Outdated repowarden guard (%s): wired by an earlier version of repowarden. → %s" ;;
|
|
533
|
+
st.agents_skill_stale) _M="Outdated repowarden skill (%s): placed by an earlier version of repowarden. → %s" ;;
|
|
533
534
|
st.next) _M="Next step:" ;;
|
|
534
535
|
st.carte_absente) _M="No project map (optional, read by agents before changing a file): repowarden carte" ;;
|
|
535
536
|
st.carte_ok) _M="Project map up to date (repowarden carte <file>)" ;;
|
|
@@ -689,6 +690,7 @@ _msg_en() {
|
|
|
689
690
|
tk.branch_kept) _M="Branch \`%s\` kept: %s commit(s) missing from the integration branch, open PR(s): %s. Delete it by hand if the work lives elsewhere." ;;
|
|
690
691
|
tk.draft_opened) _M="Draft PR #%s opened for ticket #%s" ;;
|
|
691
692
|
tk.draft_updated) _M="PR #%s (ticket #%s): title and description taken from the ticket" ;;
|
|
693
|
+
tk.draft_exists) _M="PR #%s already open for ticket #%s (close pushes): no other PR created" ;;
|
|
692
694
|
tk.assign_failed) _M="PR #%s: GitHub refused to assign %s, PR left unassigned" ;;
|
|
693
695
|
tk.pr_additions) _M="Ticket additions" ;;
|
|
694
696
|
tk.pr_design) _M="Design (summary)" ;;
|
|
@@ -743,6 +745,7 @@ _msg_en() {
|
|
|
743
745
|
garde.format) _M="Unknown format \"%s\": claude or opencode." ;;
|
|
744
746
|
garde.no_jq) _M="repowarden garde: jq missing, action not checked (install jq: https://jqlang.org)." ;;
|
|
745
747
|
garde.json) _M="repowarden garde: unreadable JSON input, action not checked." ;;
|
|
748
|
+
garde.commande_illisible) _M="command refused: \"%s\"; unclosed quote, unreadable command.\n → Fix the quotes (open and close them), then run it again." ;;
|
|
746
749
|
garde.detache) _M="change refused: detached HEAD in %s.\n → Switch to a ticket's branch: repowarden ticket <n>" ;;
|
|
747
750
|
garde.protegee) _M="change refused: \"%s\" is a protected branch.\n → Work on a ticket's branch: repowarden ticket <n>" ;;
|
|
748
751
|
garde.protegee_libre) _M="change refused: \"%s\" is a protected branch.\n → Create a work branch: git switch -c <type>/<topic>" ;;
|
|
@@ -762,6 +765,9 @@ _msg_en() {
|
|
|
762
765
|
garde.gh_pr_create) _M="command refused: \"%s\"; the draft PR is opened by the bot on the first push of the ticket's branch.\n → git push -u origin HEAD" ;;
|
|
763
766
|
garde.gh_rerun) _M="command refused: \"%s\"; workflows are re-run through repowarden.\n → repowarden run rerun <run-id|--failed>" ;;
|
|
764
767
|
garde.gh_mainteneur) _M="command refused: \"%s\"; this is the maintainer's decision (leaving draft, merge, approval, deployment).\n → Say the PR is ready (CI green) and let the maintainer decide." ;;
|
|
768
|
+
garde.ticket_mainteneur) _M="command refused: \"%s\"; validating a ticket is the maintainer's decision (\"validé\" or status label, added or removed).\n → Ask the maintainer to validate the ticket, then take it: repowarden ticket <n>" ;;
|
|
769
|
+
garde.etiquette_mainteneur) _M="command refused: \"%s\"; changing a decision label of a ticket (\"validé\", status, \"à décider\"), even through the API, is the maintainer's decision.\n → Tell the maintainer about the request and leave the ticket or PR unchanged." ;;
|
|
770
|
+
garde.ticket_nouveau) _M="command refused: \"%s\"; creating a ticket is the maintainer's decision.\n → Report the defect to the maintainer, who creates the ticket; or note it on the current ticket. Setting repowarden.agentsTickets = true allows it." ;;
|
|
765
771
|
garde.gh_sans_ticket) _M="command refused: \"%s\" changes GitHub outside the PR flow, without a ticket.\n → Create the ticket (repowarden ticket nouveau), get it validated, then run again: REPOWARDEN_TICKET=<n> %s" ;;
|
|
766
772
|
garde.gh_ticket_illisible) _M="command refused: \"%s\"; ticket #%s unreadable (gh issue view): a write without proof of a ticket.\n → Check the number and gh auth status, then run again: REPOWARDEN_TICKET=<n> gh …" ;;
|
|
767
773
|
garde.gh_ticket_non_valide) _M="command refused: \"%s\"; ticket #%s is not open and validated (label \"%s\").\n → Get the ticket validated by the maintainer, then run again." ;;
|
|
@@ -777,6 +783,13 @@ _msg_en() {
|
|
|
777
783
|
agents.removed) _M="%s: guard removed (%s)." ;;
|
|
778
784
|
agents.absent) _M="%s: no guard to remove (%s)." ;;
|
|
779
785
|
agents.kept_remove) _M="%s: %s differs from the template; kept (remove it by hand if no longer wanted)." ;;
|
|
786
|
+
agents.skill_added) _M="%s: repowarden skill added (%s)." ;;
|
|
787
|
+
agents.skill_present) _M="%s: repowarden skill already in place (%s)." ;;
|
|
788
|
+
agents.skill_updated) _M="%s: repowarden skill updated (%s); to customize it, remove its marker line (<!-- repowarden:skill …)." ;;
|
|
789
|
+
agents.skill_kept) _M="%s: repowarden skill (%s) differs from the template; kept as is (template: %s)." ;;
|
|
790
|
+
agents.skill_removed) _M="%s: repowarden skill removed (%s)." ;;
|
|
791
|
+
agents.skill_absent) _M="%s: no repowarden skill to remove (%s)." ;;
|
|
792
|
+
agents.skill_kept_remove) _M="%s: repowarden skill %s differs from the template; kept (remove it by hand if no longer wanted)." ;;
|
|
780
793
|
*) _M="" ;;
|
|
781
794
|
esac
|
|
782
795
|
}
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -254,7 +254,7 @@ _msg_fr() {
|
|
|
254
254
|
help.migrer) _M="Usage : repowarden migrer [--pr] [--hooks]\n\nPassage de repogarde à repowarden dans le projet courant\n(docs/migration-v4.md) : configuration (.repowarden.conf, .repowarden/),\nworkflows (uses: vers le dépôt repowarden et sa version majeure, anciens\nnoms), puis la liste de ce qui reste à faire à la main. Relançable : rien\nn'est fait deux fois.\n --pr sur la branche chore/migration-repowarden-vN : commit, push et PR\n --hooks déplace aussi les hooks lefthook résiduels dans .git/hooks-lefthook/" ;;
|
|
255
255
|
help.npm-publication) _M="Usage : repowarden npm-publication [--repo propriétaire/nom] [--workflow release.yml]\n [--environnement NOM] [--dry-run]\n\nPublication npm automatique mise en place en une commande : chaque release\npublie le paquet (workflow npm-publish.yml), sans jeton (publication de\nconfiance OIDC, provenance jointe) : connexion npm, paquet existant, puis\nrelation de confiance. Interrompue, la commande reprend où elle en était." ;;
|
|
256
256
|
help.garde) _M="Usage : repowarden garde [--format claude|opencode] (lit l'action sur l'entrée standard)\n\nGarde-fou des agents IA, appelé par leur hook avant chaque action\n(repowarden agents init le branche) : code 0 permis, code 2 refusé avec la\ncommande à lancer. Dans un dépôt repowarden : pas de modification en HEAD\ndétaché, sur une branche protégée, ni hors branche de ticket si le dépôt\nsuit ses tickets ; branche créée par repowarden ticket <n>, commit par\nrepowarden cc (hooks repowarden actifs, hors branche protégée) ; ni\n--no-verify ni git add . / -A / --all. Demande jq." ;;
|
|
257
|
-
help.agents) _M="Usage : repowarden agents init [--claude] [--opencode] [--projet | --global]\n repowarden agents retirer [--claude] [--opencode] [--projet | --global]\n\nBranche le garde-fou (repowarden garde) dans les agents IA : hook PreToolUse\nde Claude Code (.claude/settings.json), plugins opencode\n(.opencode/plugins/repowarden-garde.js pour opencode 1.x,\nrepowarden-garde-v2.js pour 2.x). Sans --claude ni --opencode : les
|
|
257
|
+
help.agents) _M="Usage : repowarden agents init [--claude] [--opencode] [--projet | --global]\n repowarden agents retirer [--claude] [--opencode] [--projet | --global]\n\nBranche le garde-fou (repowarden garde) dans les agents IA : hook PreToolUse\nde Claude Code (.claude/settings.json), plugins opencode\n(.opencode/plugins/repowarden-garde.js pour opencode 1.x,\nrepowarden-garde-v2.js pour 2.x). Pose aussi la skill repowarden\n(.claude/skills/repowarden/SKILL.md, .opencode/skills/repowarden/SKILL.md)\ndans la langue de repowarden. Sans --claude ni --opencode : les deux.\n--projet (défaut) : fichiers du dépôt, à versionner ; --global :\nconfiguration de l'utilisateur (\$CLAUDE_CONFIG_DIR, ~/.claude par défaut ;\n\$OPENCODE_CONFIG_DIR, sinon \$XDG_CONFIG_HOME/opencode, ~/.config/opencode par\ndéfaut). Relançable : fichiers existants complétés, jamais écrasés.\nretirer : enlève seulement ce qu'init a écrit." ;;
|
|
258
258
|
help.app) _M="Usage : repowarden app init [--repo propriétaire/nom] [--nom NOM] [--administration]\n repowarden app init --bot\n\nGitHub App du dépôt : identité d'automatisation des workflows repowarden (le\njeton des Actions ne déclenche aucun workflow, un jeton de l'App, si).\nCréation dans le navigateur (flux « manifest »), permissions minimales,\nidentifiants rangés sans être affichés, installation sur le dépôt. --bot :\nApp du bot d'organisation, dans le dépôt de pilotage. Relançable ; demande\ngh et node." ;;
|
|
259
259
|
# CI et protection
|
|
260
260
|
ci.check.2) _M="Pas de base : vérification ignorée." ;;
|
|
@@ -531,6 +531,7 @@ _msg_fr() {
|
|
|
531
531
|
st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
|
|
532
532
|
st.agents_sans_garde) _M="Agent IA sans garde-fou (Claude Code, opencode) : ses actions ne sont vérifiées qu'au commit. → %s" ;;
|
|
533
533
|
st.agents_stale) _M="Garde-fou repowarden périmé (%s) : posé par une version antérieure de repowarden. → %s" ;;
|
|
534
|
+
st.agents_skill_stale) _M="Skill repowarden périmée (%s) : posée par une version antérieure de repowarden. → %s" ;;
|
|
534
535
|
st.next) _M="Étape suivante :" ;;
|
|
535
536
|
st.carte_absente) _M="Carte du projet absente (facultative, lue par les agents avant de modifier un fichier) : repowarden carte" ;;
|
|
536
537
|
st.carte_ok) _M="Carte du projet à jour (repowarden carte <fichier>)" ;;
|
|
@@ -690,6 +691,7 @@ _msg_fr() {
|
|
|
690
691
|
tk.branch_kept) _M="Branche \`%s\` gardée : %s commit(s) absent(s) de la branche d'intégration, PR ouverte(s) : %s. À supprimer à la main si le travail est ailleurs." ;;
|
|
691
692
|
tk.draft_opened) _M="PR #%s ouverte en brouillon pour le ticket #%s" ;;
|
|
692
693
|
tk.draft_updated) _M="PR #%s (ticket #%s) : titre et description repris du ticket" ;;
|
|
694
|
+
tk.draft_exists) _M="PR #%s déjà ouverte pour le ticket #%s (push rapproché) : aucune autre PR créée" ;;
|
|
693
695
|
tk.assign_failed) _M="PR #%s : assignation de %s refusée par GitHub, PR laissée sans assigné" ;;
|
|
694
696
|
tk.pr_additions) _M="Ajouts du ticket" ;;
|
|
695
697
|
tk.pr_design) _M="Conception (résumé)" ;;
|
|
@@ -744,6 +746,7 @@ _msg_fr() {
|
|
|
744
746
|
garde.format) _M="Format inconnu « %s » : claude ou opencode." ;;
|
|
745
747
|
garde.no_jq) _M="repowarden garde : jq absent, action non vérifiée (installer jq : https://jqlang.org)." ;;
|
|
746
748
|
garde.json) _M="repowarden garde : entrée JSON illisible, action non vérifiée." ;;
|
|
749
|
+
garde.commande_illisible) _M="commande refusée : « %s » ; guillemet non fermé, commande illisible.\n → Corriger les guillemets (ouvrir et fermer), puis relancer." ;;
|
|
747
750
|
garde.detache) _M="modification refusée : HEAD détaché dans %s.\n → Se placer sur la branche d'un ticket : repowarden ticket <n>" ;;
|
|
748
751
|
garde.protegee) _M="modification refusée : « %s » est une branche protégée.\n → Travailler sur la branche d'un ticket : repowarden ticket <n>" ;;
|
|
749
752
|
garde.protegee_libre) _M="modification refusée : « %s » est une branche protégée.\n → Créer une branche de travail : git switch -c <type>/<sujet>" ;;
|
|
@@ -763,6 +766,9 @@ _msg_fr() {
|
|
|
763
766
|
garde.gh_pr_create) _M="commande refusée : « %s » ; la PR brouillon est ouverte par le bot au premier push de la branche du ticket.\n → git push -u origin HEAD" ;;
|
|
764
767
|
garde.gh_rerun) _M="commande refusée : « %s » ; un workflow se relance par repowarden.\n → repowarden run rerun <run-id|--failed>" ;;
|
|
765
768
|
garde.gh_mainteneur) _M="commande refusée : « %s » ; c'est une décision du mainteneur (sortie du brouillon, merge, approbation, déploiement).\n → Annoncer que la PR est prête (CI verte) et laisser le mainteneur décider." ;;
|
|
769
|
+
garde.ticket_mainteneur) _M="commande refusée : « %s » ; valider un ticket est une décision du mainteneur (étiquette « validé » ou de statut, ajout comme retrait).\n → Demander au mainteneur de valider le ticket, puis le prendre : repowarden ticket <n>" ;;
|
|
770
|
+
garde.etiquette_mainteneur) _M="commande refusée : « %s » ; toucher une étiquette de décision d'un ticket (« validé », statut, « à décider »), même par l'API, est une décision du mainteneur.\n → Annoncer la demande au mainteneur et laisser le ticket ou la PR inchangés." ;;
|
|
771
|
+
garde.ticket_nouveau) _M="commande refusée : « %s » ; créer un ticket est une décision du mainteneur.\n → Signaler le défaut au mainteneur, qui crée le ticket ; ou le noter sur le ticket en cours. Réglage repowarden.agentsTickets = true pour l'autoriser." ;;
|
|
766
772
|
garde.gh_sans_ticket) _M="commande refusée : « %s » modifie GitHub hors du circuit des PR, sans ticket.\n → Créer le ticket (repowarden ticket nouveau), le faire valider, puis relancer : REPOWARDEN_TICKET=<n> %s" ;;
|
|
767
773
|
garde.gh_ticket_illisible) _M="commande refusée : « %s » ; ticket #%s illisible (gh issue view) : écriture sans preuve de ticket.\n → Vérifier le numéro et gh auth status, puis relancer : REPOWARDEN_TICKET=<n> gh …" ;;
|
|
768
774
|
garde.gh_ticket_non_valide) _M="commande refusée : « %s » ; le ticket #%s n'est pas ouvert et validé (étiquette « %s »).\n → Faire valider le ticket par le mainteneur, puis relancer." ;;
|
|
@@ -778,6 +784,13 @@ _msg_fr() {
|
|
|
778
784
|
agents.removed) _M="%s : garde-fou retiré (%s)." ;;
|
|
779
785
|
agents.absent) _M="%s : pas de garde-fou à retirer (%s)." ;;
|
|
780
786
|
agents.kept_remove) _M="%s : %s diffère du modèle ; gardé (à retirer à la main s'il n'est plus voulu)." ;;
|
|
787
|
+
agents.skill_added) _M="%s : skill repowarden ajoutée (%s)." ;;
|
|
788
|
+
agents.skill_present) _M="%s : skill repowarden déjà en place (%s)." ;;
|
|
789
|
+
agents.skill_updated) _M="%s : skill repowarden mise à jour (%s) ; pour la personnaliser, retirer la ligne repère." ;;
|
|
790
|
+
agents.skill_kept) _M="%s : skill repowarden (%s) diffère du modèle ; gardée telle quelle (modèle : %s)." ;;
|
|
791
|
+
agents.skill_removed) _M="%s : skill repowarden retirée (%s)." ;;
|
|
792
|
+
agents.skill_absent) _M="%s : pas de skill repowarden à retirer (%s)." ;;
|
|
793
|
+
agents.skill_kept_remove) _M="%s : skill repowarden %s diffère du modèle ; gardée (à retirer à la main s'il n'est plus voulu)." ;;
|
|
781
794
|
*) _M="" ;;
|
|
782
795
|
esac
|
|
783
796
|
}
|
package/install.sh
CHANGED
|
@@ -257,6 +257,17 @@ fi
|
|
|
257
257
|
# question (déjà branché par lui), une ligne par agent
|
|
258
258
|
while read -r portee agent fichier; do
|
|
259
259
|
[ "$portee" = global ] || continue
|
|
260
|
+
if [ "$agent" = skill-claude ]; then
|
|
261
|
+
bash "$ROOT/bin/agents" init --global --claude >/dev/null 2>&1 || continue
|
|
262
|
+
t agents.skill_updated "Claude Code" "$fichier"
|
|
263
|
+
ok "$REPLY"
|
|
264
|
+
continue
|
|
265
|
+
elif [ "$agent" = skill-opencode ]; then
|
|
266
|
+
bash "$ROOT/bin/agents" init --global --opencode >/dev/null 2>&1 || continue
|
|
267
|
+
t agents.skill_updated opencode "$fichier"
|
|
268
|
+
ok "$REPLY"
|
|
269
|
+
continue
|
|
270
|
+
fi
|
|
260
271
|
bash "$ROOT/bin/agents" init --global "--$agent" >/dev/null 2>&1 || continue
|
|
261
272
|
nom=opencode
|
|
262
273
|
[ "$agent" != claude ] || nom="Claude Code"
|
package/package.json
CHANGED