repowarden 4.12.0-next.3 → 4.12.1-next.3
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 +28 -7
- package/bin/garde +47 -1
- package/bin/statut +7 -0
- package/ci/tickets/commun.sh +4 -3
- package/hooks/lib/agents.sh +62 -7
- package/hooks/lib/i18n/en.sh +2 -0
- package/hooks/lib/i18n/fr.sh +2 -0
- package/install.sh +11 -0
- package/package.json +1 -1
package/bin/agents
CHANGED
|
@@ -7,19 +7,22 @@
|
|
|
7
7
|
# Sans --claude ni --opencode : les deux. --projet (défaut) : fichiers du dépôt,
|
|
8
8
|
# à versionner ; --global : configuration de l'utilisateur, là où chaque outil
|
|
9
9
|
# la lit (CLAUDE_CONFIG_DIR ; OPENCODE_CONFIG_DIR, sinon XDG_CONFIG_HOME).
|
|
10
|
-
# Relançable : fichiers existants complétés
|
|
11
|
-
#
|
|
10
|
+
# Relançable : fichiers existants complétés ; un garde-fou posé par une version
|
|
11
|
+
# antérieure de repowarden est mis à jour, un fichier personnel jamais écrasé ;
|
|
12
|
+
# retirer n'enlève que ce qu'init a écrit.
|
|
12
13
|
set -euo pipefail
|
|
13
14
|
|
|
14
15
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
15
16
|
# shellcheck source=../hooks/lib/common.sh
|
|
16
17
|
source "$ROOT/hooks/lib/common.sh"
|
|
18
|
+
# shellcheck source=../hooks/lib/agents.sh
|
|
19
|
+
source "$ROOT/hooks/lib/agents.sh"
|
|
17
20
|
|
|
18
21
|
aide() { _tr help.agents; printf '%b\n' "$_T"; }
|
|
19
22
|
|
|
20
|
-
MODELE_OPENCODE="$
|
|
21
|
-
COMMANDE_CLAUDE="
|
|
22
|
-
MATCHER_CLAUDE="
|
|
23
|
+
MODELE_OPENCODE="$AGENTS_MODELE_OPENCODE"
|
|
24
|
+
COMMANDE_CLAUDE="$AGENTS_COMMANDE_CLAUDE"
|
|
25
|
+
MATCHER_CLAUDE="$AGENTS_MATCHER_CLAUDE"
|
|
23
26
|
|
|
24
27
|
action=""
|
|
25
28
|
case "${1:-}" in
|
|
@@ -59,7 +62,8 @@ fi
|
|
|
59
62
|
ecrits=()
|
|
60
63
|
|
|
61
64
|
# Hook PreToolUse de Claude Code : ajouté au settings.json (créé au besoin),
|
|
62
|
-
# rien si une commande « repowarden garde » y est déjà
|
|
65
|
+
# rien si une commande « repowarden garde » y est déjà ; d'une ancienne forme,
|
|
66
|
+
# remplacé (les autres hooks restent)
|
|
63
67
|
init_claude() {
|
|
64
68
|
local f="$fichier_claude" entree tmp
|
|
65
69
|
has jq || { err_t agents.no_jq "$f"; return 1; }
|
|
@@ -67,6 +71,18 @@ init_claude() {
|
|
|
67
71
|
'{matcher: $m, hooks: [{type: "command", command: $c}]}')"
|
|
68
72
|
if [ -f "$f" ]; then
|
|
69
73
|
jq -e . "$f" >/dev/null 2>&1 || { err_t agents.bad_json "Claude Code" "$f"; return 1; }
|
|
74
|
+
if agents_claude_perime "$f"; then
|
|
75
|
+
tmp="$(mktemp)"
|
|
76
|
+
jq --argjson e "$entree" 'def rw: (.command // "") | startswith("repowarden garde");
|
|
77
|
+
.hooks.PreToolUse = ([.hooks.PreToolUse[]
|
|
78
|
+
| if any(.hooks[]?; rw) then (.hooks |= map(select(rw | not))) | select(.hooks | length > 0) else . end]
|
|
79
|
+
+ [$e])' "$f" >"$tmp"
|
|
80
|
+
cat "$tmp" >"$f"
|
|
81
|
+
rm -f "$tmp"
|
|
82
|
+
ecrits+=("$f")
|
|
83
|
+
ok_t agents.updated "Claude Code" "$f"
|
|
84
|
+
return 0
|
|
85
|
+
fi
|
|
70
86
|
if jq -e '[.hooks.PreToolUse[]?.hooks[]?.command // empty | select(test("repowarden garde"))] | length > 0' "$f" >/dev/null; then
|
|
71
87
|
ok_t agents.present "Claude Code" "$f"
|
|
72
88
|
return 0
|
|
@@ -83,12 +99,17 @@ init_claude() {
|
|
|
83
99
|
ok_t agents.added "Claude Code" "$f"
|
|
84
100
|
}
|
|
85
101
|
|
|
86
|
-
# Plugin opencode : copie du modèle ;
|
|
102
|
+
# Plugin opencode : copie du modèle ; celui d'une version antérieure de
|
|
103
|
+
# repowarden est remplacé, un fichier personnel gardé et signalé
|
|
87
104
|
init_opencode() {
|
|
88
105
|
local f="$fichier_opencode"
|
|
89
106
|
if [ -f "$f" ]; then
|
|
90
107
|
if cmp -s "$f" "$MODELE_OPENCODE"; then
|
|
91
108
|
ok_t agents.present opencode "$f"
|
|
109
|
+
elif agents_plugin_repowarden "$f"; then
|
|
110
|
+
cp "$MODELE_OPENCODE" "$f"
|
|
111
|
+
ecrits+=("$f")
|
|
112
|
+
ok_t agents.updated opencode "$f"
|
|
92
113
|
else
|
|
93
114
|
attention_t agents.kept opencode "$f" "$MODELE_OPENCODE"
|
|
94
115
|
fi
|
package/bin/garde
CHANGED
|
@@ -324,6 +324,49 @@ verifier_gh() {
|
|
|
324
324
|
) || exit $?
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
# Découpe $1 en une commande par ligne (REPLY) : seul le « & » d'une vraie
|
|
328
|
+
# redirection (N>&M, >&2, >&-, &>, &>>, non échappée) reste dans la commande,
|
|
329
|
+
# citée entière ; partout ailleurs il sépare. Dans le doute couper : au pire une
|
|
330
|
+
# commande de trop, jamais une masquée (true \>&git push -f lance git).
|
|
331
|
+
decouper_lignes() {
|
|
332
|
+
local s="$1" out="" c i j prec avant nl=$'\n'
|
|
333
|
+
for ((i = 0; i < ${#s}; i++)); do
|
|
334
|
+
c="${s:i:1}"
|
|
335
|
+
if [ "$c" != "&" ]; then
|
|
336
|
+
out="$out$c"
|
|
337
|
+
continue
|
|
338
|
+
fi
|
|
339
|
+
prec="" avant=""
|
|
340
|
+
[ "$i" -lt 1 ] || prec="${s:i-1:1}"
|
|
341
|
+
[ "$i" -lt 2 ] || avant="${s:i-2:1}"
|
|
342
|
+
# &> et &>> : le « & » lui-même non échappé
|
|
343
|
+
if [ "${s:i+1:1}" = ">" ] && [ "$prec" != "\\" ]; then
|
|
344
|
+
out="$out&"
|
|
345
|
+
continue
|
|
346
|
+
fi
|
|
347
|
+
# >&N, <&N, >&- : « > » ou « < » non échappé avant, un descripteur ou
|
|
348
|
+
# « - » après, puis une fin de mot
|
|
349
|
+
if { [ "$prec" = ">" ] || [ "$prec" = "<" ]; } && [ "$avant" != "\\" ]; then
|
|
350
|
+
j=$((i + 1))
|
|
351
|
+
if [ "${s:j:1}" = "-" ]; then
|
|
352
|
+
j=$((j + 1))
|
|
353
|
+
else
|
|
354
|
+
while [[ "${s:j:1}" == [0-9] ]]; do j=$((j + 1)); done
|
|
355
|
+
fi
|
|
356
|
+
if [ "$j" -gt $((i + 1)) ]; then
|
|
357
|
+
case "${s:j:1}" in
|
|
358
|
+
"" | " " | $'\t' | "$nl" | "&" | ";" | "|" | ")")
|
|
359
|
+
out="$out&"
|
|
360
|
+
continue
|
|
361
|
+
;;
|
|
362
|
+
esac
|
|
363
|
+
fi
|
|
364
|
+
fi
|
|
365
|
+
out="$out$nl"
|
|
366
|
+
done
|
|
367
|
+
REPLY="$out"
|
|
368
|
+
}
|
|
369
|
+
|
|
327
370
|
# Ligne de commande shell : chaque commande (&&, ||, ;, |, retour à la ligne),
|
|
328
371
|
# « cd » suivi, variables d'environnement en tête ignorées (sauf
|
|
329
372
|
# REPOWARDEN_TICKET, le ticket d'une écriture gh hors du circuit des PR)
|
|
@@ -336,7 +379,10 @@ verifier_shell() {
|
|
|
336
379
|
lignes="${lignes//||/$nl}"
|
|
337
380
|
lignes="${lignes//;/$nl}"
|
|
338
381
|
lignes="${lignes//|/$nl}"
|
|
339
|
-
|
|
382
|
+
# Un « & » de redirection (2>&1, >&2, &>log…) n'est pas un séparateur : la
|
|
383
|
+
# commande citée dans le refus et celle proposée restent entières
|
|
384
|
+
decouper_lignes "$lignes"
|
|
385
|
+
lignes="$REPLY"
|
|
340
386
|
# Plusieurs commandes : un refus vaut pour toute la ligne (dit à l'agent)
|
|
341
387
|
[ "$(grep -c '[^[:space:]]' <<<"$lignes")" -le 1 ] || LIGNE_MULTIPLE=1
|
|
342
388
|
while IFS= read -r segment; do
|
package/bin/statut
CHANGED
|
@@ -150,6 +150,13 @@ if agents_sans_garde "$GIT_TOPLEVEL"; then
|
|
|
150
150
|
attention_t st.agents_sans_garde "$cmd_agents"
|
|
151
151
|
suivant="${suivant:-$cmd_agents}"
|
|
152
152
|
fi
|
|
153
|
+
# Garde-fou posé par une version antérieure de repowarden : init le met à jour
|
|
154
|
+
while read -r portee _agent fichier; do
|
|
155
|
+
cmd_agents="repowarden agents init"
|
|
156
|
+
[ "$portee" = projet ] || cmd_agents="$cmd_agents --global"
|
|
157
|
+
attention_t st.agents_stale "$fichier" "$cmd_agents"
|
|
158
|
+
suivant="${suivant:-$cmd_agents}"
|
|
159
|
+
done < <(agents_perimes "$GIT_TOPLEVEL")
|
|
153
160
|
|
|
154
161
|
# Outils : secrets, code mort (selon les langages)
|
|
155
162
|
presents="" absents=""
|
package/ci/tickets/commun.sh
CHANGED
|
@@ -63,13 +63,14 @@ statut() {
|
|
|
63
63
|
PIED="<!-- repowarden:pied -->"
|
|
64
64
|
|
|
65
65
|
# REPLY = texte $1 terminé par le pied (---, puis une phrase en Markdown
|
|
66
|
-
# normal : <sub> affiche les liens en brut)
|
|
67
|
-
#
|
|
66
|
+
# normal : <sub> affiche les liens en brut). Le repère est seul sur sa ligne
|
|
67
|
+
# pour que GitHub rende les liens du pied en HTML (bloc HTML brut sinon) ;
|
|
68
|
+
# sans slug (jeton des Actions, github-actions[bot]) ou déjà signé, texte inchangé
|
|
68
69
|
pied_r() {
|
|
69
70
|
REPLY="$1"
|
|
70
71
|
[ -n "${APP_SLUG:-}" ] && [[ "$REPLY" != *"$PIED"* ]] || return 0
|
|
71
72
|
_tr tk.footer "$APP_SLUG"
|
|
72
|
-
REPLY="$REPLY"$'\n\n---\n\n'"$PIED$_T"
|
|
73
|
+
REPLY="$REPLY"$'\n\n---\n\n'"$PIED"$'\n\n'"$_T"
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
# Commentaire $2 du bot sur le ticket $1, avec son pied
|
package/hooks/lib/agents.sh
CHANGED
|
@@ -1,20 +1,75 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Agents IA du poste et garde-fou repowarden (repowarden statut,
|
|
3
|
-
# Compatible bash 3.2.
|
|
2
|
+
# Agents IA du poste et garde-fou repowarden (repowarden agents, statut,
|
|
3
|
+
# install.sh). Compatible bash 3.2.
|
|
4
|
+
|
|
5
|
+
# Forme actuelle du garde-fou : un garde-fou posé par repowarden qui en diffère
|
|
6
|
+
# date d'une version antérieure et est mis à jour (#470)
|
|
7
|
+
AGENTS_MODELE_OPENCODE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../agents" && pwd -P)/repowarden-garde.js"
|
|
8
|
+
AGENTS_COMMANDE_CLAUDE="repowarden garde --format claude"
|
|
9
|
+
AGENTS_MATCHER_CLAUDE="Edit|Write|MultiEdit|NotebookEdit|Bash"
|
|
10
|
+
|
|
11
|
+
# Dossiers de configuration de l'utilisateur, là où chaque outil les lit
|
|
12
|
+
# (CLAUDE_CONFIG_DIR ; OPENCODE_CONFIG_DIR, sinon XDG_CONFIG_HOME)
|
|
13
|
+
agents_dossiers_globaux() {
|
|
14
|
+
AGENTS_CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
|
|
15
|
+
AGENTS_OPENCODE_DIR="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Vrai si le plugin opencode $1 a été posé par repowarden (en-tête du modèle,
|
|
19
|
+
# ancien plugin autonome compris) : lui seul peut être remplacé sans perte
|
|
20
|
+
agents_plugin_repowarden() {
|
|
21
|
+
local ligne=""
|
|
22
|
+
[ -f "$1" ] || return 1
|
|
23
|
+
IFS= read -r ligne <"$1" || true
|
|
24
|
+
case "$ligne" in "// Garde-fou repowarden pour opencode"*) return 0 ;; esac
|
|
25
|
+
return 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Vrai si le plugin $1 vient de repowarden mais diffère du modèle actuel :
|
|
29
|
+
# posé par une version antérieure, il n'a pas suivi les mises à jour
|
|
30
|
+
agents_plugin_perime() {
|
|
31
|
+
agents_plugin_repowarden "$1" && ! cmp -s "$1" "$AGENTS_MODELE_OPENCODE"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Vrai si le settings.json $1 a un hook « repowarden garde… » d'une autre forme
|
|
35
|
+
# que l'actuelle (commande ou matcher) : posé par une version antérieure
|
|
36
|
+
agents_claude_perime() {
|
|
37
|
+
[ -f "$1" ] && command -v jq >/dev/null 2>&1 || return 1
|
|
38
|
+
jq -e --arg c "$AGENTS_COMMANDE_CLAUDE" --arg m "$AGENTS_MATCHER_CLAUDE" '
|
|
39
|
+
[.hooks.PreToolUse[]? | . as $e | .hooks[]?
|
|
40
|
+
| select((.command // "") | startswith("repowarden garde"))
|
|
41
|
+
| select(.command != $c or $e.matcher != $m)] | length > 0' "$1" >/dev/null 2>&1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Garde-fous périmés du dépôt $1 (vide hors dépôt) puis de l'utilisateur, une
|
|
45
|
+
# ligne « portée agent fichier » chacun : statut les signale, install les met à jour
|
|
46
|
+
agents_perimes() {
|
|
47
|
+
agents_dossiers_globaux
|
|
48
|
+
if [ -n "${1:-}" ]; then
|
|
49
|
+
! agents_claude_perime "$1/.claude/settings.json" || echo "projet claude $1/.claude/settings.json"
|
|
50
|
+
! agents_plugin_perime "$1/.opencode/plugins/repowarden-garde.js" ||
|
|
51
|
+
echo "projet opencode $1/.opencode/plugins/repowarden-garde.js"
|
|
52
|
+
fi
|
|
53
|
+
! agents_claude_perime "$AGENTS_CLAUDE_DIR/settings.json" || echo "global claude $AGENTS_CLAUDE_DIR/settings.json"
|
|
54
|
+
! agents_plugin_perime "$AGENTS_OPENCODE_DIR/plugins/repowarden-garde.js" ||
|
|
55
|
+
echo "global opencode $AGENTS_OPENCODE_DIR/plugins/repowarden-garde.js"
|
|
56
|
+
return 0
|
|
57
|
+
}
|
|
4
58
|
|
|
5
59
|
# Vrai si Claude Code ou opencode est sur ce poste (commande, ou dossier de
|
|
6
60
|
# configuration) sans garde-fou branché, ni dans le dépôt $1 (racine, vide hors
|
|
7
61
|
# dépôt) ni pour l'utilisateur : ses actions ne seraient vues qu'au commit
|
|
8
62
|
agents_sans_garde() {
|
|
9
|
-
local
|
|
10
|
-
|
|
11
|
-
opencode_dir="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}"
|
|
63
|
+
local f fichiers=()
|
|
64
|
+
agents_dossiers_globaux
|
|
12
65
|
command -v claude >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 ||
|
|
13
|
-
[ -d "$
|
|
66
|
+
[ -d "$AGENTS_CLAUDE_DIR" ] || [ -d "$AGENTS_OPENCODE_DIR" ] || return 1
|
|
14
67
|
[ -z "${1:-}" ] || fichiers=("$1/.claude/settings.json" "$1/.opencode/plugins/repowarden-garde.js")
|
|
15
|
-
fichiers+=("$
|
|
68
|
+
fichiers+=("$AGENTS_CLAUDE_DIR/settings.json" "$AGENTS_OPENCODE_DIR/plugins/repowarden-garde.js")
|
|
69
|
+
# Plugin repowarden d'une version antérieure : branché (périmé, pas absent)
|
|
16
70
|
for f in "${fichiers[@]}"; do
|
|
17
71
|
! grep -qs "repowarden garde" "$f" || return 1
|
|
72
|
+
! agents_plugin_repowarden "$f" || return 1
|
|
18
73
|
done
|
|
19
74
|
return 0
|
|
20
75
|
}
|
package/hooks/lib/i18n/en.sh
CHANGED
|
@@ -512,6 +512,7 @@ _msg_en() {
|
|
|
512
512
|
st.wf_fix) _M="fix the uses: lines reported above" ;;
|
|
513
513
|
st.tools) _M="Tools present: %s · missing: %s (depending on the projects' languages)" ;;
|
|
514
514
|
st.agents_sans_garde) _M="AI agent without the guard (Claude Code, opencode): its actions are only checked at commit time. → %s" ;;
|
|
515
|
+
st.agents_stale) _M="Outdated repowarden guard (%s): wired by an earlier version of repowarden. → %s" ;;
|
|
515
516
|
st.next) _M="Next step:" ;;
|
|
516
517
|
st.ready) _M="All set: git cc to commit, repowarden code-mort on a branch." ;;
|
|
517
518
|
st.help) _M="Every command: repowarden --help" ;;
|
|
@@ -742,6 +743,7 @@ _msg_en() {
|
|
|
742
743
|
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." ;;
|
|
743
744
|
agents.added) _M="%s: guard added (%s)." ;;
|
|
744
745
|
agents.present) _M="%s: guard already in place (%s)." ;;
|
|
746
|
+
agents.updated) _M="%s: guard updated (%s)." ;;
|
|
745
747
|
agents.kept) _M="%s: %s exists and differs from the template; kept as is (template: %s)." ;;
|
|
746
748
|
agents.bad_json) _M="%s: %s is not valid JSON; nothing was changed." ;;
|
|
747
749
|
agents.no_jq) _M="jq is required to update %s (https://jqlang.org)." ;;
|
package/hooks/lib/i18n/fr.sh
CHANGED
|
@@ -513,6 +513,7 @@ _msg_fr() {
|
|
|
513
513
|
st.wf_fix) _M="corriger les lignes uses: signalées ci-dessus" ;;
|
|
514
514
|
st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
|
|
515
515
|
st.agents_sans_garde) _M="Agent IA sans garde-fou (Claude Code, opencode) : ses actions ne sont vérifiées qu'au commit. → %s" ;;
|
|
516
|
+
st.agents_stale) _M="Garde-fou repowarden périmé (%s) : posé par une version antérieure de repowarden. → %s" ;;
|
|
516
517
|
st.next) _M="Étape suivante :" ;;
|
|
517
518
|
st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
|
|
518
519
|
st.help) _M="Toutes les commandes : repowarden --help" ;;
|
|
@@ -743,6 +744,7 @@ _msg_fr() {
|
|
|
743
744
|
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." ;;
|
|
744
745
|
agents.added) _M="%s : garde-fou ajouté (%s)." ;;
|
|
745
746
|
agents.present) _M="%s : garde-fou déjà en place (%s)." ;;
|
|
747
|
+
agents.updated) _M="%s : garde-fou mis à jour (%s)." ;;
|
|
746
748
|
agents.kept) _M="%s : %s existe et diffère du modèle ; gardé tel quel (modèle : %s)." ;;
|
|
747
749
|
agents.bad_json) _M="%s : %s n'est pas un JSON valide ; rien n'a été modifié." ;;
|
|
748
750
|
agents.no_jq) _M="jq est requis pour compléter %s (https://jqlang.org)." ;;
|
package/install.sh
CHANGED
|
@@ -253,6 +253,17 @@ if [ "$SCOPE" = --global ]; then
|
|
|
253
253
|
info "$REPLY"
|
|
254
254
|
fi
|
|
255
255
|
|
|
256
|
+
# Garde-fou de l'utilisateur posé par une version antérieure : mis à jour sans
|
|
257
|
+
# question (déjà branché par lui), une ligne par agent
|
|
258
|
+
while read -r portee agent fichier; do
|
|
259
|
+
[ "$portee" = global ] || continue
|
|
260
|
+
bash "$ROOT/bin/agents" init --global "--$agent" >/dev/null 2>&1 || continue
|
|
261
|
+
nom=opencode
|
|
262
|
+
[ "$agent" != claude ] || nom="Claude Code"
|
|
263
|
+
t agents.updated "$nom" "$fichier"
|
|
264
|
+
ok "$REPLY"
|
|
265
|
+
done < <(agents_perimes "")
|
|
266
|
+
|
|
256
267
|
# Agent IA sans garde-fou (Claude Code, opencode) : branchement proposé dans un
|
|
257
268
|
# terminal, jamais fait sans accord ; sinon, la commande sur une ligne
|
|
258
269
|
RACINE="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
package/package.json
CHANGED