repowarden 4.12.1-next.3 → 4.13.0-next.18
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 +88 -23
- package/bin/carte +69 -0
- package/bin/garde +224 -30
- package/bin/repowarden +3 -0
- package/bin/statut +34 -3
- package/ci/tickets/commun.sh +3 -0
- package/ci/tickets/push.sh +106 -24
- package/hooks/agents/repowarden-garde-v2.js +32 -0
- package/hooks/agents/skill-repowarden.en.md +41 -0
- package/hooks/agents/skill-repowarden.fr.md +41 -0
- package/hooks/lib/agents.sh +59 -10
- package/hooks/lib/carte.sh +339 -0
- package/hooks/lib/commentaires.sh +45 -20
- package/hooks/lib/i18n/en.sh +39 -2
- package/hooks/lib/i18n/fr.sh +39 -2
- package/hooks/post-merge +5 -0
- package/install.sh +11 -0
- package/package.json +1 -1
package/bin/agents
CHANGED
|
@@ -2,14 +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
|
|
6
|
-
# opencode
|
|
5
|
+
# Claude Code hook PreToolUse dans .claude/settings.json + skill repowarden
|
|
6
|
+
# opencode plugins .opencode/plugins/repowarden-garde.js (opencode 1.x)
|
|
7
|
+
# et repowarden-garde-v2.js (opencode 2.x) + skill repowarden
|
|
7
8
|
# Sans --claude ni --opencode : les deux. --projet (défaut) : fichiers du dépôt,
|
|
8
9
|
# à versionner ; --global : configuration de l'utilisateur, là où chaque outil
|
|
9
10
|
# la lit (CLAUDE_CONFIG_DIR ; OPENCODE_CONFIG_DIR, sinon XDG_CONFIG_HOME).
|
|
10
|
-
# Relançable : fichiers existants complétés ; un garde-fou
|
|
11
|
-
# antérieure de repowarden
|
|
12
|
-
# 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.
|
|
13
14
|
set -euo pipefail
|
|
14
15
|
|
|
15
16
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
@@ -20,9 +21,9 @@ source "$ROOT/hooks/lib/agents.sh"
|
|
|
20
21
|
|
|
21
22
|
aide() { _tr help.agents; printf '%b\n' "$_T"; }
|
|
22
23
|
|
|
23
|
-
MODELE_OPENCODE="$AGENTS_MODELE_OPENCODE"
|
|
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
|
|
@@ -50,13 +51,17 @@ if [ "$portee" = projet ]; then
|
|
|
50
51
|
[ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
|
|
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=()
|
|
@@ -99,26 +104,49 @@ init_claude() {
|
|
|
99
104
|
ok_t agents.added "Claude Code" "$f"
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
#
|
|
103
|
-
# repowarden est remplacé, un fichier personnel gardé et signalé
|
|
107
|
+
# Plugins opencode (1.x et 2.x) : copie du modèle ; celui d'une version
|
|
108
|
+
# antérieure de repowarden est remplacé, un fichier personnel gardé et signalé
|
|
104
109
|
init_opencode() {
|
|
105
|
-
local f
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
local p f modele
|
|
111
|
+
for p in $AGENTS_PLUGINS_OPENCODE; do
|
|
112
|
+
f="$dossier_opencode/$p" modele="$AGENTS_MODELES/$p"
|
|
113
|
+
if [ ! -f "$f" ]; then
|
|
114
|
+
mkdir -p "$dossier_opencode"
|
|
115
|
+
cp "$modele" "$f"
|
|
116
|
+
ecrits+=("$f")
|
|
117
|
+
ok_t agents.added opencode "$f"
|
|
118
|
+
elif cmp -s "$f" "$modele"; then
|
|
108
119
|
ok_t agents.present opencode "$f"
|
|
109
120
|
elif agents_plugin_repowarden "$f"; then
|
|
110
|
-
cp "$
|
|
121
|
+
cp "$modele" "$f"
|
|
111
122
|
ecrits+=("$f")
|
|
112
123
|
ok_t agents.updated opencode "$f"
|
|
113
124
|
else
|
|
114
|
-
attention_t agents.kept opencode "$f" "$
|
|
125
|
+
attention_t agents.kept opencode "$f" "$modele"
|
|
126
|
+
fi
|
|
127
|
+
done
|
|
128
|
+
}
|
|
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"
|
|
115
143
|
fi
|
|
116
144
|
return 0
|
|
117
145
|
fi
|
|
118
146
|
mkdir -p "$(dirname "$f")"
|
|
119
|
-
cp "$
|
|
147
|
+
cp "$MODELE_SKILL" "$f"
|
|
120
148
|
ecrits+=("$f")
|
|
121
|
-
ok_t agents.
|
|
149
|
+
ok_t agents.skill_added "$nom" "$f"
|
|
122
150
|
}
|
|
123
151
|
|
|
124
152
|
# Hook PreToolUse : seules les commandes « repowarden garde » retirées ; une
|
|
@@ -146,16 +174,49 @@ retirer_claude() {
|
|
|
146
174
|
ok_t agents.removed "Claude Code" "$f"
|
|
147
175
|
}
|
|
148
176
|
|
|
149
|
-
#
|
|
177
|
+
# Plugins opencode : chacun supprimé s'il est celui du modèle ; modifié, gardé
|
|
178
|
+
# et signalé
|
|
150
179
|
retirer_opencode() {
|
|
151
|
-
local f
|
|
180
|
+
local p f
|
|
181
|
+
for p in $AGENTS_PLUGINS_OPENCODE; do
|
|
182
|
+
f="$dossier_opencode/$p"
|
|
183
|
+
if [ ! -f "$f" ]; then
|
|
184
|
+
ok_t agents.absent opencode "$f"
|
|
185
|
+
elif cmp -s "$f" "$AGENTS_MODELES/$p"; then
|
|
186
|
+
rm -f "$f"
|
|
187
|
+
ok_t agents.removed opencode "$f"
|
|
188
|
+
else
|
|
189
|
+
attention_t agents.kept_remove opencode "$f"
|
|
190
|
+
fi
|
|
191
|
+
done
|
|
192
|
+
}
|
|
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"
|
|
152
212
|
if [ ! -f "$f" ]; then
|
|
153
|
-
ok_t agents.
|
|
154
|
-
elif cmp -s "$f" "$
|
|
213
|
+
ok_t agents.skill_absent "$nom" "$f"
|
|
214
|
+
elif cmp -s "$f" "$MODELE_SKILL"; then
|
|
155
215
|
rm -f "$f"
|
|
156
|
-
|
|
216
|
+
rmdir "$(dirname "$f")" 2>/dev/null || true
|
|
217
|
+
ok_t agents.skill_removed "$nom" "$f"
|
|
157
218
|
else
|
|
158
|
-
attention_t agents.
|
|
219
|
+
attention_t agents.skill_kept_remove "$nom" "$f"
|
|
159
220
|
fi
|
|
160
221
|
}
|
|
161
222
|
|
|
@@ -163,10 +224,14 @@ statut=0
|
|
|
163
224
|
if [ "$action" = retirer ]; then
|
|
164
225
|
[ -z "$claude" ] || retirer_claude || statut=1
|
|
165
226
|
[ -z "$opencode" ] || retirer_opencode || statut=1
|
|
227
|
+
[ -z "$claude" ] || retirer_skill_claude || statut=1
|
|
228
|
+
[ -z "$opencode" ] || retirer_skill_opencode || statut=1
|
|
166
229
|
exit "$statut"
|
|
167
230
|
fi
|
|
168
231
|
[ -z "$claude" ] || init_claude || statut=1
|
|
169
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
|
|
170
235
|
|
|
171
236
|
has repowarden || attention_t agents.no_repowarden
|
|
172
237
|
# Hooks repowarden inactifs : le garde-fou refusera les commits, autant le
|
package/bin/carte
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Usage : repowarden carte (re)génère la carte
|
|
3
|
+
# repowarden carte <fichier|fonction|'#n'> [--json] extrait utile
|
|
4
|
+
#
|
|
5
|
+
# Carte du projet, sans LLM (gh seulement pour la conception des tickets) : chaque fonction des scripts shell,
|
|
6
|
+
# son fichier, sa ligne et son « pourquoi » (le commentaire juste au-dessus),
|
|
7
|
+
# dans .repowarden/carte/carte.json (dossier ignoré par git). Interrogée,
|
|
8
|
+
# elle est d'abord régénérée si elle est absente ou périmée. Voir docs/carte.md.
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
12
|
+
# shellcheck source=../hooks/lib/common.sh
|
|
13
|
+
source "$ROOT/hooks/lib/common.sh"
|
|
14
|
+
# shellcheck source=../hooks/lib/lang.sh
|
|
15
|
+
source "$ROOT/hooks/lib/lang.sh"
|
|
16
|
+
# shellcheck source=../hooks/lib/carte.sh
|
|
17
|
+
source "$ROOT/hooks/lib/carte.sh"
|
|
18
|
+
|
|
19
|
+
# Arguments : une cible (fichier ou fonction), --json
|
|
20
|
+
cible="" json=""
|
|
21
|
+
for a in "$@"; do
|
|
22
|
+
case "$a" in
|
|
23
|
+
--json) json=json ;;
|
|
24
|
+
-*) inconnu="$a" ;;
|
|
25
|
+
*) if [ -z "$cible" ]; then cible="$a"; else inconnu="$a"; fi ;;
|
|
26
|
+
esac
|
|
27
|
+
if [ -n "${inconnu:-}" ]; then
|
|
28
|
+
err_t install.unknown_arg "$inconnu"
|
|
29
|
+
_tr help.carte
|
|
30
|
+
printf '%b\n' "$_T" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
if [ -z "$GIT_TOPLEVEL" ]; then
|
|
35
|
+
err_t carte.no_repo
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
if ! has jq; then
|
|
39
|
+
err_t carte.no_jq
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
# Fichier existant : chemin depuis la racine du dépôt, quel que soit le dossier courant
|
|
43
|
+
chemin=""
|
|
44
|
+
if [ -n "$cible" ] && [ -e "$cible" ]; then
|
|
45
|
+
chemin="$(cd "$(dirname "$cible")" && pwd -P)/${cible##*/}"
|
|
46
|
+
chemin="${chemin#"$(cd "$GIT_TOPLEVEL" && pwd -P)"/}"
|
|
47
|
+
chemin="${chemin%/.}"
|
|
48
|
+
fi
|
|
49
|
+
cd "$GIT_TOPLEVEL"
|
|
50
|
+
|
|
51
|
+
if [ -z "$cible" ] && [ -z "$json" ]; then
|
|
52
|
+
carte_generer
|
|
53
|
+
# shellcheck disable=SC2016 # programme jq, rien à développer
|
|
54
|
+
read -r fichiers fonctions sans < <(cjq -r '[.fichiers[].fonctions[]?] as $f
|
|
55
|
+
| "\(.fichiers | length) \($f | length) \([$f[] | select(.pourquoi == "")] | length)"' "$CARTE_JSON")
|
|
56
|
+
ok_t carte.done "$fichiers" "$fonctions" "$sans" "$CARTE_JSON"
|
|
57
|
+
exit 0
|
|
58
|
+
fi
|
|
59
|
+
# Interrogation : carte absente ou périmée, régénérée d'abord
|
|
60
|
+
carte_a_jour || carte_generer
|
|
61
|
+
if [ -z "$cible" ]; then
|
|
62
|
+
cat "$CARTE_JSON"
|
|
63
|
+
elif [[ "$cible" =~ ^#([0-9]+)$ ]]; then
|
|
64
|
+
carte_ticket "${BASH_REMATCH[1]}" "$json"
|
|
65
|
+
elif [ -n "$chemin" ] || jq -e --arg f "$cible" '.fichiers | has($f)' "$CARTE_JSON" >/dev/null; then
|
|
66
|
+
carte_fichier "${chemin:-$cible}" "$json"
|
|
67
|
+
else
|
|
68
|
+
carte_fonction "$cible" "$json"
|
|
69
|
+
fi
|
package/bin/garde
CHANGED
|
@@ -43,10 +43,14 @@ args="$(jq -c ".$a // {}" <<<"$entree")"
|
|
|
43
43
|
# Refus : raison et commande à lancer, puis code 2. Ligne shell de plusieurs
|
|
44
44
|
# commandes (LIGNE_MULTIPLE) : refusée en bloc, ce que l'agent doit savoir
|
|
45
45
|
LIGNE_MULTIPLE=""
|
|
46
|
+
# Fichier existant dont la modification est vérifiée (chemin depuis la racine) :
|
|
47
|
+
# un refus rappelle de lire sa carte avant de le modifier
|
|
48
|
+
FICHIER_CARTE=""
|
|
46
49
|
refuser() {
|
|
47
50
|
_tr "$@"
|
|
48
51
|
printf '✖ repowarden : %b\n' "$_T" >&2
|
|
49
52
|
[ -z "$LIGNE_MULTIPLE" ] || { _tr garde.ligne_entiere; printf '%b\n' "$_T" >&2; }
|
|
53
|
+
[ -z "$FICHIER_CARTE" ] || { _tr garde.carte "$FICHIER_CARTE" "$FICHIER_CARTE"; printf '%b\n' "$_T" >&2; }
|
|
50
54
|
exit 2
|
|
51
55
|
}
|
|
52
56
|
|
|
@@ -129,16 +133,39 @@ protegee() {
|
|
|
129
133
|
[ -n "$REPLY" ] && [ "$REPLY" = "$1" ]
|
|
130
134
|
}
|
|
131
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
|
+
|
|
132
150
|
# Modification du fichier $1 : seulement sur une branche de travail (de ticket,
|
|
133
|
-
# 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
|
|
134
152
|
verifier_fichier() {
|
|
135
153
|
dossier_r "$1"
|
|
136
|
-
|
|
154
|
+
# Sous Windows, jq finit le chemin lu par un CR (retiré) et le chemin peut
|
|
155
|
+
# être en forme Windows (D:/…), absolu lui aussi
|
|
156
|
+
local d="$REPLY" f="${1%$'\r'}"
|
|
157
|
+
case "$f" in /* | [A-Za-z]:[/\\]*) ;; *) f="$cwd/$f" ;; esac
|
|
137
158
|
(
|
|
138
159
|
cd "$d" 2>/dev/null || exit 0
|
|
139
160
|
depot_repowarden || exit 0
|
|
140
161
|
# Fichiers internes de git (.git/…) : hors du travail de l'agent
|
|
141
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
|
|
165
|
+
if [ -f "$f" ]; then
|
|
166
|
+
FICHIER_CARTE="$(cd "$(dirname "$f")" && pwd -P)/${f##*/}"
|
|
167
|
+
FICHIER_CARTE="${FICHIER_CARTE#"$(cd "$(git rev-parse --show-toplevel)" && pwd -P)"/}"
|
|
168
|
+
fi
|
|
142
169
|
branche="$(git symbolic-ref -q --short HEAD 2>/dev/null || true)"
|
|
143
170
|
[ -n "$branche" ] || refuser garde.detache "$GIT_TOPLEVEL"
|
|
144
171
|
if protegee "$branche"; then
|
|
@@ -256,11 +283,38 @@ ticket_valide() {
|
|
|
256
283
|
[ "$(head -n 1 <<<"$lu")" = OPEN ] && grep -qxF "$VALIDE" <<<"$lu"
|
|
257
284
|
}
|
|
258
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
|
+
|
|
259
313
|
# Commande gh (mots $@, sans « gh ») lancée depuis $dir, préfixe
|
|
260
314
|
# REPOWARDEN_TICKET=$ticket_gh : lectures connues permises, écritures du circuit
|
|
261
315
|
# par repowarden, décisions au mainteneur, tout le reste avec un ticket validé
|
|
262
316
|
verifier_gh() {
|
|
263
|
-
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
|
|
264
318
|
while [ $# -gt 0 ]; do
|
|
265
319
|
case "$1" in
|
|
266
320
|
-R | --repo) shift; depot="${1:-}" ;;
|
|
@@ -268,7 +322,8 @@ verifier_gh() {
|
|
|
268
322
|
-X | --method) shift; methode="${1:-}" ;;
|
|
269
323
|
-X?*) methode="${1#-X}" ;;
|
|
270
324
|
--method=*) methode="${1#--method=}" ;;
|
|
271
|
-
|
|
325
|
+
--input | --input=*) champs=1 corps=1 ;;
|
|
326
|
+
-f | -F | --field | --raw-field | -f?* | -F?* | --field=* | --raw-field=*) champs=1 ;;
|
|
272
327
|
-a | --approve) approuve=1 ;;
|
|
273
328
|
-h | --help) return 0 ;;
|
|
274
329
|
-*) ;;
|
|
@@ -303,6 +358,7 @@ verifier_gh() {
|
|
|
303
358
|
"api "*)
|
|
304
359
|
[ -n "$ecriture" ] || exit 0
|
|
305
360
|
[[ "${pos[1]:-}" != *pending_deployments* ]] || refuser garde.gh_mainteneur "$ligne"
|
|
361
|
+
! api_etiquettes_decision "$methode" "${pos[1]:-}" "$mots" "$corps" || refuser garde.etiquette_mainteneur "$ligne"
|
|
306
362
|
hors=1
|
|
307
363
|
;;
|
|
308
364
|
# Lectures connues et description de la PR : permises ; tout le
|
|
@@ -324,14 +380,93 @@ verifier_gh() {
|
|
|
324
380
|
) || exit $?
|
|
325
381
|
}
|
|
326
382
|
|
|
327
|
-
#
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
|
|
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.
|
|
331
435
|
decouper_lignes() {
|
|
332
|
-
local s="$1" out="" c i j prec avant nl=$'\n'
|
|
436
|
+
local s="$1" out="" c q="" i j prec avant nl=$'\n'
|
|
333
437
|
for ((i = 0; i < ${#s}; i++)); do
|
|
334
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
|
|
335
470
|
if [ "$c" != "&" ]; then
|
|
336
471
|
out="$out$c"
|
|
337
472
|
continue
|
|
@@ -367,28 +502,72 @@ decouper_lignes() {
|
|
|
367
502
|
REPLY="$out"
|
|
368
503
|
}
|
|
369
504
|
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
|
|
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)
|
|
373
549
|
verifier_shell() {
|
|
374
|
-
local commande="$1" segment
|
|
550
|
+
local commande="$1" segment m cmd_cc lignes ticket_gh
|
|
375
551
|
dir="$cwd"
|
|
376
|
-
# Une commande par ligne
|
|
377
|
-
#
|
|
378
|
-
|
|
379
|
-
lignes="${lignes//||/$nl}"
|
|
380
|
-
lignes="${lignes//;/$nl}"
|
|
381
|
-
lignes="${lignes//|/$nl}"
|
|
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"
|
|
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"
|
|
385
555
|
lignes="$REPLY"
|
|
386
556
|
# Plusieurs commandes : un refus vaut pour toute la ligne (dit à l'agent)
|
|
387
557
|
[ "$(grep -c '[^[:space:]]' <<<"$lignes")" -le 1 ] || LIGNE_MULTIPLE=1
|
|
388
558
|
while IFS= read -r segment; do
|
|
389
|
-
#
|
|
390
|
-
|
|
391
|
-
|
|
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[@]}"}
|
|
392
571
|
ticket_gh=""
|
|
393
572
|
while [ $# -gt 0 ]; do
|
|
394
573
|
case "$1" in
|
|
@@ -398,7 +577,13 @@ verifier_shell() {
|
|
|
398
577
|
esac
|
|
399
578
|
done
|
|
400
579
|
[ $# -gt 0 ] || continue
|
|
580
|
+
# Commande : dernier élément du chemin (/ ou \), sans .exe/.cmd/.bat
|
|
401
581
|
m="${1//[\"\']/}"
|
|
582
|
+
m="${m##*/}"
|
|
583
|
+
m="${m##*\\}"
|
|
584
|
+
case "$m" in
|
|
585
|
+
*.exe | *.EXE | *.cmd | *.CMD | *.bat | *.BAT) m="${m%.*}" ;;
|
|
586
|
+
esac
|
|
402
587
|
case "$m" in
|
|
403
588
|
cd)
|
|
404
589
|
case "${2:-}" in
|
|
@@ -408,21 +593,28 @@ verifier_shell() {
|
|
|
408
593
|
*) dir="$dir/${2//[\"\']/}" ;;
|
|
409
594
|
esac
|
|
410
595
|
;;
|
|
411
|
-
git
|
|
596
|
+
git)
|
|
412
597
|
shift
|
|
413
598
|
verifier_git "${@//[\"\']/}"
|
|
414
599
|
;;
|
|
415
|
-
gh
|
|
600
|
+
gh)
|
|
416
601
|
shift
|
|
417
602
|
verifier_gh "${@//[\"\']/}"
|
|
418
603
|
;;
|
|
419
|
-
repowarden
|
|
604
|
+
repowarden)
|
|
605
|
+
shift
|
|
606
|
+
verifier_ticket_nouveau "${@//[\"\']/}"
|
|
607
|
+
set -- repowarden "$@"
|
|
420
608
|
case "${2//[\"\']/}" in
|
|
421
609
|
cc | commit)
|
|
422
610
|
cmd_cc="repowarden ${2//[\"\']/}"
|
|
423
611
|
shift 2
|
|
424
612
|
lecture_seule "${@//[\"\']/}" || verifier_cc "$cmd_cc"
|
|
425
613
|
;;
|
|
614
|
+
ticket | ticket-label)
|
|
615
|
+
shift
|
|
616
|
+
verifier_ticket_label "${@//[\"\']/}"
|
|
617
|
+
;;
|
|
426
618
|
esac
|
|
427
619
|
;;
|
|
428
620
|
esac
|
|
@@ -431,7 +623,8 @@ verifier_shell() {
|
|
|
431
623
|
|
|
432
624
|
case "$outil" in
|
|
433
625
|
Edit | Write | MultiEdit | NotebookEdit | edit | write | multiedit)
|
|
434
|
-
|
|
626
|
+
# path : outils edit et write d'opencode 2.x
|
|
627
|
+
chemin="$(jq -r '.file_path // .filePath // .notebook_path // .path // empty' <<<"$args" 2>/dev/null)"
|
|
435
628
|
[ -z "$chemin" ] || verifier_fichier "$chemin"
|
|
436
629
|
;;
|
|
437
630
|
apply_patch)
|
|
@@ -440,7 +633,8 @@ case "$outil" in
|
|
|
440
633
|
done < <(jq -r '.patchText // empty' <<<"$args" 2>/dev/null |
|
|
441
634
|
sed -nE -e 's/^\*\*\* (Add|Update|Delete) File: //p' -e 's/^\*\*\* Move to: //p')
|
|
442
635
|
;;
|
|
443
|
-
|
|
636
|
+
# shell : nom de l'outil bash dans opencode 2.x
|
|
637
|
+
Bash | bash | shell)
|
|
444
638
|
verifier_shell "$(jq -r '.command // empty' <<<"$args" 2>/dev/null)"
|
|
445
639
|
;;
|
|
446
640
|
esac
|
package/bin/repowarden
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
# repowarden uninstall [--global] [--purge] les retire (avant npm uninstall -g)
|
|
8
8
|
# repowarden cc assistant de commit
|
|
9
9
|
# repowarden code-mort nouveau code mort de la branche
|
|
10
|
+
# repowarden carte carte du projet : fonctions et leur pourquoi
|
|
10
11
|
# repowarden ticket <n> branche du ticket (créée, liée, si besoin)
|
|
11
12
|
# repowarden ticket nouveau "<titre>" nouveau ticket
|
|
12
13
|
# repowarden ticket comment <n> "<texte>" commentaire sur un ticket
|
|
@@ -52,6 +53,7 @@ help_key_r() {
|
|
|
52
53
|
garde | guard) REPLY=help.garde ;;
|
|
53
54
|
cc | commit) REPLY=help.cc ;;
|
|
54
55
|
code-mort | deadcode) REPLY=help.code-mort ;;
|
|
56
|
+
carte | map) REPLY=help.carte ;;
|
|
55
57
|
ticket) REPLY=help.ticket ;;
|
|
56
58
|
tickets) REPLY=help.tickets ;;
|
|
57
59
|
pr) REPLY=help.pr ;;
|
|
@@ -102,6 +104,7 @@ case "$cmd" in
|
|
|
102
104
|
uninstall) exec bash "$ROOT/install.sh" --uninstall "$@" ;;
|
|
103
105
|
cc | commit) exec bash "$ROOT/bin/commit" "$@" ;;
|
|
104
106
|
code-mort | deadcode) exec bash "$ROOT/bin/code-mort" "$@" ;;
|
|
107
|
+
carte | map) exec bash "$ROOT/bin/carte" "$@" ;;
|
|
105
108
|
tickets) exec bash "$ROOT/bin/tickets" "$@" ;;
|
|
106
109
|
ticket) exec bash "$ROOT/bin/ticket" "$@" ;;
|
|
107
110
|
ticket-comment) exec bash "$ROOT/bin/ticket-comment" "$@" ;;
|