repowarden 4.12.1 → 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/statut
CHANGED
|
@@ -13,6 +13,8 @@ source "$ROOT/hooks/lib/common.sh"
|
|
|
13
13
|
source "$ROOT/hooks/lib/secrets.sh"
|
|
14
14
|
# shellcheck source=../hooks/lib/agents.sh
|
|
15
15
|
source "$ROOT/hooks/lib/agents.sh"
|
|
16
|
+
# shellcheck source=../hooks/lib/carte.sh
|
|
17
|
+
source "$ROOT/hooks/lib/carte.sh"
|
|
16
18
|
exec 2>&1 # une seule sortie, dans l'ordre (avertissements compris)
|
|
17
19
|
# shellcheck disable=SC2034 # lu par check_version (common.sh)
|
|
18
20
|
HOOKS_DIR="$ROOT/hooks"
|
|
@@ -75,6 +77,21 @@ if [ -n "$GIT_TOPLEVEL" ]; then
|
|
|
75
77
|
attention_t st.repo_lefthook "$f"
|
|
76
78
|
break
|
|
77
79
|
done
|
|
80
|
+
# Projet pre-commit : avec les hooks repowarden actifs, « pre-commit install »
|
|
81
|
+
# refuse ; ses hooks ne tournent que dans .git/hooks ou .repowarden, lancés par repowarden
|
|
82
|
+
if [ -f "$GIT_TOPLEVEL/.pre-commit-config.yaml" ] &&
|
|
83
|
+
[ "$(reel "$(git config --get core.hooksPath 2>/dev/null || true)")" = "$ROOT/hooks" ]; then
|
|
84
|
+
commun="$(cd "$GIT_TOPLEVEL" && reel "$(git rev-parse --git-common-dir)")"
|
|
85
|
+
lance=""
|
|
86
|
+
for h in "$commun/hooks/pre-commit" "$GIT_TOPLEVEL/.repowarden/pre-commit"; do
|
|
87
|
+
[ -x "$h" ] && grep -qsE 'File generated by pre-commit|pre-commit +(run|hook-impl)' "$h" && lance=1
|
|
88
|
+
done
|
|
89
|
+
if [ -z "$lance" ]; then
|
|
90
|
+
marche="GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.hooksPath GIT_CONFIG_VALUE_0= pre-commit install"
|
|
91
|
+
attention_t st.repo_precommit "$marche"
|
|
92
|
+
suivant="${suivant:-$marche}"
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
78
95
|
# Anciens noms (configuration, workflows de l'ancien dépôt, que GitHub ne
|
|
79
96
|
# redirige plus) : la migration les remplace
|
|
80
97
|
if [ -e "$GIT_TOPLEVEL/.repogarde.conf" ] || [ -d "$GIT_TOPLEVEL/.repogarde" ] ||
|
|
@@ -138,6 +155,16 @@ if [ -n "$GIT_TOPLEVEL" ]; then
|
|
|
138
155
|
sensitive_files_r
|
|
139
156
|
[ -z "$REPLY" ] || sensitive_files_warn "$REPLY"
|
|
140
157
|
fi
|
|
158
|
+
# Carte du projet (repowarden carte) : facultative ; périmée, elle ne dit
|
|
159
|
+
# plus ce que fait le code, et les agents la lisent avant de modifier
|
|
160
|
+
if [ ! -f "$GIT_TOPLEVEL/$CARTE_JSON" ]; then
|
|
161
|
+
info_t st.carte_absente
|
|
162
|
+
elif (cd "$GIT_TOPLEVEL" && carte_a_jour); then
|
|
163
|
+
ok_t st.carte_ok
|
|
164
|
+
else
|
|
165
|
+
attention_t st.carte_perimee
|
|
166
|
+
suivant="${suivant:-repowarden carte}"
|
|
167
|
+
fi
|
|
141
168
|
info_t st.repo_forge "$FORGE"
|
|
142
169
|
[ "$FORGE" != github ] || dim_t st.repo_rules
|
|
143
170
|
fi
|
|
@@ -150,11 +177,15 @@ if agents_sans_garde "$GIT_TOPLEVEL"; then
|
|
|
150
177
|
attention_t st.agents_sans_garde "$cmd_agents"
|
|
151
178
|
suivant="${suivant:-$cmd_agents}"
|
|
152
179
|
fi
|
|
153
|
-
# Garde-fou
|
|
154
|
-
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
|
|
155
182
|
cmd_agents="repowarden agents init"
|
|
156
183
|
[ "$portee" = projet ] || cmd_agents="$cmd_agents --global"
|
|
157
|
-
|
|
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
|
|
158
189
|
suivant="${suivant:-$cmd_agents}"
|
|
159
190
|
done < <(agents_perimes "$GIT_TOPLEVEL")
|
|
160
191
|
|
package/ci/tickets/commun.sh
CHANGED
|
@@ -113,4 +113,7 @@ notice() { echo "::notice title=repowarden::$*"; }
|
|
|
113
113
|
|
|
114
114
|
notice_t() { _tr "$@"; notice "$_T"; }
|
|
115
115
|
|
|
116
|
+
# Avertissement traduit dans le journal du run : signale sans faire échouer
|
|
117
|
+
avertir_t() { _tr "$@"; echo "::warning title=repowarden::$_T"; }
|
|
118
|
+
|
|
116
119
|
# Chargé par les tests (source) : fonctions seulement
|
package/ci/tickets/push.sh
CHANGED
|
@@ -69,12 +69,13 @@ titre_pr_brouillon_r() {
|
|
|
69
69
|
suggest_pr_title_r "$branche"
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
# $1 = corps d'un ticket → REPLY :
|
|
72
|
+
# $1 = corps d'un ticket → REPLY : ses sections de critères d'acceptation
|
|
73
73
|
# (« Terminé quand… », « Critères d'acceptation ») en cases à cocher « - [ ] »,
|
|
74
|
-
# le reste
|
|
74
|
+
# RESTE_TICKET : le reste (constat, proposition), que la PR replie
|
|
75
75
|
criteres_coches_r() {
|
|
76
|
-
local corps="$1" ligne dedans=0
|
|
76
|
+
local corps="$1" ligne dedans=0 criteres="" reste=""
|
|
77
77
|
while IFS= read -r ligne; do
|
|
78
|
+
ligne="${ligne%$'\r'}"
|
|
78
79
|
if [[ "$ligne" =~ ^#{1,6}[[:space:]] ]]; then
|
|
79
80
|
dedans=0
|
|
80
81
|
case "$ligne" in
|
|
@@ -85,9 +86,16 @@ criteres_coches_r() {
|
|
|
85
86
|
ligne="$(printf '%s' "$ligne" |
|
|
86
87
|
sed -E 's/^([[:space:]]*[-*][[:space:]]+)(\[[xX ]\][[:space:]]+)?/\1[ ] /')"
|
|
87
88
|
fi
|
|
88
|
-
|
|
89
|
+
if [ "$dedans" = 1 ]; then
|
|
90
|
+
criteres="$criteres$ligne"$'\n'
|
|
91
|
+
else
|
|
92
|
+
reste="$reste$ligne"$'\n'
|
|
93
|
+
fi
|
|
89
94
|
done <<<"$corps"
|
|
90
|
-
|
|
95
|
+
while [[ "$reste" == $'\n'* ]]; do reste="${reste#$'\n'}"; done
|
|
96
|
+
sans_fin_vide_r "$reste"
|
|
97
|
+
RESTE_TICKET="$REPLY"
|
|
98
|
+
sans_fin_vide_r "$criteres"
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
# Fin de chaque commentaire lu d'un bloc (séparateur ASCII) : un texte écrit à
|
|
@@ -189,41 +197,102 @@ section_realise_r() {
|
|
|
189
197
|
sans_fin_vide_r "$section"
|
|
190
198
|
}
|
|
191
199
|
|
|
200
|
+
# Ligne de pied d'un commit (`repowarden cc --refs`), faite seulement de
|
|
201
|
+
# références : retirée, le pied de la PR porte déjà le ticket
|
|
202
|
+
PIED_COMMIT_RE='^[[:space:]]*(Tickets?|Refs?|Closes|Fixes|Resolves)[[:space:]]*:?[[:space:]]*#[0-9]+([[:space:],]+#[0-9]+)*[[:space:]]*$'
|
|
203
|
+
|
|
204
|
+
# $1 = commits lus par l'API (« parents sha message », chacun suivi d'une ligne
|
|
205
|
+
# SEP_COMMENTAIRE) → REPLY : pour chaque commit hors fusion, « ### en-tête (sha) »
|
|
206
|
+
# puis son corps, le pourquoi que le relecteur cherche avant le ticket
|
|
207
|
+
ce_que_fait_r() {
|
|
208
|
+
local ligne debut=1 fusion=0 entete="" bloc="" sortie="" reste
|
|
209
|
+
while IFS= read -r ligne; do
|
|
210
|
+
ligne="${ligne%$'\r'}"
|
|
211
|
+
if [ "$ligne" = "$SEP_COMMENTAIRE" ]; then
|
|
212
|
+
if [ -n "$entete" ] && [ "$fusion" = 0 ]; then
|
|
213
|
+
sans_fin_vide_r "$bloc"
|
|
214
|
+
sortie="$sortie${sortie:+$'\n\n'}$entete${REPLY:+$'\n'}$REPLY"
|
|
215
|
+
fi
|
|
216
|
+
debut=1 fusion=0 entete="" bloc=""
|
|
217
|
+
continue
|
|
218
|
+
fi
|
|
219
|
+
if [ "$debut" = 1 ]; then
|
|
220
|
+
[ -n "$ligne" ] || continue
|
|
221
|
+
debut=0
|
|
222
|
+
# plus d'un parent : commit de fusion, sans rien à dire à la revue
|
|
223
|
+
[ "${ligne%% *}" -le 1 ] 2>/dev/null || fusion=1
|
|
224
|
+
reste="${ligne#* }"
|
|
225
|
+
entete="### ${reste#* } (${reste%% *})"
|
|
226
|
+
continue
|
|
227
|
+
fi
|
|
228
|
+
[ -n "$bloc" ] || [ -n "${ligne//[[:space:]]/}" ] || continue
|
|
229
|
+
[[ ! "$ligne" =~ $PIED_COMMIT_RE ]] || continue
|
|
230
|
+
ligne_reprise_r "$ligne"
|
|
231
|
+
bloc="$bloc$REPLY"$'\n'
|
|
232
|
+
done <<<"$1"$'\n'"$SEP_COMMENTAIRE"
|
|
233
|
+
REPLY="$sortie"
|
|
234
|
+
}
|
|
235
|
+
|
|
192
236
|
# REPLY = description de la PR brouillon du ticket $1 (branche $2, PR $3 si
|
|
193
|
-
# elle existe) :
|
|
194
|
-
# commentaire, le résumé de sa conception,
|
|
195
|
-
#
|
|
237
|
+
# elle existe) : ce que fait la PR (corps des commits), les critères du ticket
|
|
238
|
+
# en cases à cocher, ses ajouts en commentaire, le résumé de sa conception, le
|
|
239
|
+
# reste du ticket replié, la section « Réalisé » de l'auteur gardée, puis
|
|
240
|
+
# « Ticket : #N ». Le bot ne régénère que ses sections.
|
|
196
241
|
description_pr_brouillon_r() {
|
|
197
|
-
local n="$1" branche="$2" pr="${3:-}" corps
|
|
242
|
+
local n="$1" branche="$2" pr="${3:-}" corps="" reste commentaires
|
|
243
|
+
ce_que_fait_r "$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$branche" \
|
|
244
|
+
-q '.commits[] | "\(.parents | length) \(.sha[0:7]) \(.commit.message)\n\u001e"' 2>/dev/null || true)"
|
|
245
|
+
_tr tk.pr_does
|
|
246
|
+
[ -z "$REPLY" ] || corps="## $_T"$'\n'"$REPLY"
|
|
198
247
|
criteres_coches_r "$(gh issue view "$n" --json body -q .body)"
|
|
199
|
-
|
|
248
|
+
reste="$RESTE_TICKET"
|
|
249
|
+
[ -z "$REPLY" ] || corps="$corps${corps:+$'\n\n'}$REPLY"
|
|
200
250
|
commentaires_mainteneur_r "$n"
|
|
201
251
|
commentaires="$REPLY"
|
|
202
252
|
ajouts_ticket_r "$commentaires"
|
|
203
253
|
_tr tk.pr_additions
|
|
204
|
-
[ -z "$REPLY" ] || corps="$corps
|
|
254
|
+
[ -z "$REPLY" ] || corps="$corps${corps:+$'\n\n'}## $_T"$'\n'"$REPLY"
|
|
205
255
|
resume_conception_r "$commentaires"
|
|
206
256
|
_tr tk.pr_design
|
|
207
|
-
[ -z "$REPLY" ] || corps="$corps
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
257
|
+
[ -z "$REPLY" ] || corps="$corps${corps:+$'\n\n'}## $_T"$'\n'"$REPLY"
|
|
258
|
+
if [ -n "$reste" ]; then
|
|
259
|
+
# lignes vides autour du texte : sinon GitHub ne rend pas son Markdown
|
|
260
|
+
_tr tk.pr_ticket "$n"
|
|
261
|
+
corps="$corps${corps:+$'\n\n'}<details>"$'\n'"<summary>$_T</summary>"$'\n\n'"$reste"$'\n\n</details>'
|
|
262
|
+
fi
|
|
211
263
|
if [ -n "$pr" ]; then
|
|
212
264
|
section_realise_r "$(gh pr view "$pr" --json body -q .body 2>/dev/null || true)"
|
|
213
|
-
[ -z "$REPLY" ] || corps="$corps
|
|
265
|
+
[ -z "$REPLY" ] || corps="$corps${corps:+$'\n\n'}$REPLY"
|
|
214
266
|
fi
|
|
215
|
-
REPLY="$corps
|
|
267
|
+
REPLY="$corps${corps:+$'\n\n'}Ticket : #$n"
|
|
268
|
+
}
|
|
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
|
+
|
|
274
|
+
# REPLY = compte à assigner à la PR brouillon du ticket $1 : l'auteur du push
|
|
275
|
+
# s'il est une personne (GitHub refuse d'assigner un bot avec un jeton d'App),
|
|
276
|
+
# sinon le premier assigné du ticket, sinon vide
|
|
277
|
+
assigne_pr_r() {
|
|
278
|
+
REPLY=""
|
|
279
|
+
case "${PUSHER:-}" in
|
|
280
|
+
"" | *"[bot]") ;;
|
|
281
|
+
*) REPLY="$PUSHER"; return 0 ;;
|
|
282
|
+
esac
|
|
283
|
+
REPLY="$(assignes "$1")"
|
|
284
|
+
REPLY="${REPLY%%$'\n'*}"
|
|
216
285
|
}
|
|
217
286
|
|
|
218
287
|
# Premier push sur la branche d'un ticket : PR brouillon vers la branche
|
|
219
288
|
# d'intégration, titrée par le premier commit (repli : le ticket), à la
|
|
220
|
-
# description reprise du ticket, citant le
|
|
221
|
-
#
|
|
222
|
-
# des Actions, elle ne lance pas la CI : elle tourne au push suivant, ou au
|
|
289
|
+
# description reprise du ticket, citant le ticket, assignée à l'auteur du
|
|
290
|
+
# push (une personne, jamais un bot ; repli : l'assigné du ticket ; un refus
|
|
291
|
+
# n'est qu'un avertissement) ; ticket en cours. Ouverte par le jeton des Actions, elle ne lance pas la CI : elle tourne au push suivant, ou au
|
|
223
292
|
# passage « prête pour revue ». Tant qu'elle est en brouillon, chaque push met
|
|
224
293
|
# à jour son titre et sa description ; une PR prête n'est plus touchée.
|
|
225
294
|
cmd_push() {
|
|
226
|
-
local n pr titre corps url
|
|
295
|
+
local n pr titre corps url qui
|
|
227
296
|
: "${PUSH_BRANCH:?}" "${SHA:?}"
|
|
228
297
|
[ "${DELETED:-false}" != true ] || return 0
|
|
229
298
|
branch_ticket_r "$PUSH_BRANCH"
|
|
@@ -231,7 +300,7 @@ cmd_push() {
|
|
|
231
300
|
[ -n "$n" ] || return 0
|
|
232
301
|
# Branche faite hors du ticket (à la main) : inscrite sur le ticket
|
|
233
302
|
inscrire_branche "$n" "$PUSH_BRANCH"
|
|
234
|
-
pr="$(
|
|
303
|
+
pr="$(pr_ouverte "$PUSH_BRANCH")"
|
|
235
304
|
# PR existante : rien à faire si elle n'est plus en brouillon
|
|
236
305
|
[ -z "$pr" ] || [ "$(gh pr view "$pr" --json isDraft -q .isDraft)" = true ] || return 0
|
|
237
306
|
[ "$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$PUSH_BRANCH" -q .ahead_by)" != 0 ] || return 0
|
|
@@ -244,9 +313,22 @@ cmd_push() {
|
|
|
244
313
|
notice_t tk.draft_updated "$pr" "$n"
|
|
245
314
|
return 0
|
|
246
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
|
|
247
322
|
url="$(gh pr create --draft --base "$INTEGRATION" --head "$PUSH_BRANCH" --title "$titre" \
|
|
248
|
-
--body "$corps"
|
|
249
|
-
|
|
323
|
+
--body "$corps")"
|
|
324
|
+
# assignation à part : un refus de GitHub ne doit pas faire échouer le suivi
|
|
325
|
+
assigne_pr_r "$n"
|
|
326
|
+
qui="$REPLY"
|
|
327
|
+
if [ -n "$qui" ]; then
|
|
328
|
+
gh pr edit "${url##*/}" --add-assignee "$qui" >/dev/null 2>&1 ||
|
|
329
|
+
avertir_t tk.assign_failed "${url##*/}" "$qui"
|
|
330
|
+
[ "$qui" != "${PUSHER:-}" ] || gh issue edit "$n" --add-assignee "$qui" >/dev/null 2>&1 || true
|
|
331
|
+
fi
|
|
250
332
|
est_valide "$n" && statut "$n" "$S_ENCOURS"
|
|
251
333
|
notice_t tk.draft_opened "${url##*/}" "$n"
|
|
252
334
|
verifier "${url##*/}" "$SHA" "$n" "$PUSH_BRANCH" || true
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Garde-fou repowarden pour opencode 2.x (écrit par repowarden agents init) :
|
|
2
|
+
// chaque outil est soumis à « repowarden garde » avant de s'exécuter. Refus
|
|
3
|
+
// (code 2) → erreur rendue à l'agent, avec la commande à lancer. repowarden
|
|
4
|
+
// absent ou en panne : rien n'est bloqué. opencode 1.x : repowarden-garde.js,
|
|
5
|
+
// à côté (un module à export par défaut y passerait pour un plugin 2.x).
|
|
6
|
+
// Plugins opencode 2.x : export par défaut { id, setup }, crochet
|
|
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
|
|
11
|
+
import { spawnSync } from "node:child_process"
|
|
12
|
+
|
|
13
|
+
// Soumet l'appel d'outil à repowarden garde ; l'erreur levée par le crochet
|
|
14
|
+
// empêche l'outil de s'exécuter et son message est rendu à l'agent
|
|
15
|
+
function garde(tool, args, cwd) {
|
|
16
|
+
const r = spawnSync("repowarden", ["garde"], {
|
|
17
|
+
input: JSON.stringify({ tool, args, cwd }),
|
|
18
|
+
encoding: "utf8",
|
|
19
|
+
})
|
|
20
|
+
if (r.status === 2) throw new Error((r.stderr || "").trim())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default {
|
|
24
|
+
id: "repowarden-garde",
|
|
25
|
+
// Sans ctx.tool.hook (opencode 1.x charge aussi ce fichier) : rien
|
|
26
|
+
setup: (ctx) => {
|
|
27
|
+
if (typeof ctx?.tool?.hook !== "function") return
|
|
28
|
+
const dossier = ctx.location?.directory || process.cwd()
|
|
29
|
+
return ctx.tool.hook("execute.before", (appel) => garde(appel.tool, appel.input, dossier))
|
|
30
|
+
},
|
|
31
|
+
server: async () => ({}),
|
|
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
|
@@ -4,10 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
# Forme actuelle du garde-fou : un garde-fou posé par repowarden qui en diffère
|
|
6
6
|
# date d'une version antérieure et est mis à jour (#470)
|
|
7
|
-
|
|
7
|
+
AGENTS_MODELES="$(cd "$(dirname "${BASH_SOURCE[0]}")/../agents" && pwd -P)"
|
|
8
|
+
# Plugins opencode, un par API : 1.x lit l'export nommé, 2.x l'export par
|
|
9
|
+
# défaut, et 1.x ignore l'export nommé d'un module qui a les deux (#469)
|
|
10
|
+
AGENTS_PLUGINS_OPENCODE="repowarden-garde.js repowarden-garde-v2.js"
|
|
8
11
|
AGENTS_COMMANDE_CLAUDE="repowarden garde --format claude"
|
|
9
12
|
AGENTS_MATCHER_CLAUDE="Edit|Write|MultiEdit|NotebookEdit|Bash"
|
|
10
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
|
+
|
|
11
23
|
# Dossiers de configuration de l'utilisateur, là où chaque outil les lit
|
|
12
24
|
# (CLAUDE_CONFIG_DIR ; OPENCODE_CONFIG_DIR, sinon XDG_CONFIG_HOME)
|
|
13
25
|
agents_dossiers_globaux() {
|
|
@@ -25,10 +37,10 @@ agents_plugin_repowarden() {
|
|
|
25
37
|
return 1
|
|
26
38
|
}
|
|
27
39
|
|
|
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
|
|
40
|
+
# Vrai si le plugin $1 vient de repowarden mais diffère du modèle actuel (même
|
|
41
|
+
# nom) : posé par une version antérieure, il n'a pas suivi les mises à jour
|
|
30
42
|
agents_plugin_perime() {
|
|
31
|
-
agents_plugin_repowarden "$1" && ! cmp -s "$1" "$
|
|
43
|
+
agents_plugin_repowarden "$1" && ! cmp -s "$1" "$AGENTS_MODELES/$(basename "$1")"
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
# Vrai si le settings.json $1 a un hook « repowarden garde… » d'une autre forme
|
|
@@ -44,18 +56,42 @@ agents_claude_perime() {
|
|
|
44
56
|
# Garde-fous périmés du dépôt $1 (vide hors dépôt) puis de l'utilisateur, une
|
|
45
57
|
# ligne « portée agent fichier » chacun : statut les signale, install les met à jour
|
|
46
58
|
agents_perimes() {
|
|
59
|
+
local modele
|
|
60
|
+
modele="$(agents_modele_skill)"
|
|
47
61
|
agents_dossiers_globaux
|
|
48
62
|
if [ -n "${1:-}" ]; then
|
|
49
63
|
! agents_claude_perime "$1/.claude/settings.json" || echo "projet claude $1/.claude/settings.json"
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
52
71
|
fi
|
|
53
72
|
! agents_claude_perime "$AGENTS_CLAUDE_DIR/settings.json" || echo "global claude $AGENTS_CLAUDE_DIR/settings.json"
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
56
80
|
return 0
|
|
57
81
|
}
|
|
58
82
|
|
|
83
|
+
# Plugins opencode périmés du dossier $2 (portée $1) : différents du modèle, ou
|
|
84
|
+
# absents à côté d'un plugin repowarden (posé avant le plugin 2.x, #469)
|
|
85
|
+
agents_opencode_perimes() {
|
|
86
|
+
local p
|
|
87
|
+
for p in $AGENTS_PLUGINS_OPENCODE; do
|
|
88
|
+
if agents_plugin_perime "$2/$p" ||
|
|
89
|
+
{ [ ! -e "$2/$p" ] && agents_plugin_repowarden "$2/repowarden-garde.js"; }; then
|
|
90
|
+
echo "$1 opencode $2/$p"
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
}
|
|
94
|
+
|
|
59
95
|
# Vrai si Claude Code ou opencode est sur ce poste (commande, ou dossier de
|
|
60
96
|
# configuration) sans garde-fou branché, ni dans le dépôt $1 (racine, vide hors
|
|
61
97
|
# dépôt) ni pour l'utilisateur : ses actions ne seraient vues qu'au commit
|
|
@@ -64,8 +100,8 @@ agents_sans_garde() {
|
|
|
64
100
|
agents_dossiers_globaux
|
|
65
101
|
command -v claude >/dev/null 2>&1 || command -v opencode >/dev/null 2>&1 ||
|
|
66
102
|
[ -d "$AGENTS_CLAUDE_DIR" ] || [ -d "$AGENTS_OPENCODE_DIR" ] || return 1
|
|
67
|
-
[ -z "${1:-}" ] || fichiers=("$1/.claude/settings.json" "$1/.opencode/plugins/repowarden-garde.js")
|
|
68
|
-
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")
|
|
69
105
|
# Plugin repowarden d'une version antérieure : branché (périmé, pas absent)
|
|
70
106
|
for f in "${fichiers[@]}"; do
|
|
71
107
|
! grep -qs "repowarden garde" "$f" || return 1
|
|
@@ -73,3 +109,16 @@ agents_sans_garde() {
|
|
|
73
109
|
done
|
|
74
110
|
return 0
|
|
75
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
|
+
}
|