repowarden 4.12.1-next.3 → 4.13.0-next.7

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 CHANGED
@@ -3,7 +3,8 @@
3
3
  # repowarden agents init [--claude] [--opencode] [--projet | --global]
4
4
  # repowarden agents retirer [--claude] [--opencode] [--projet | --global]
5
5
  # Claude Code hook PreToolUse dans .claude/settings.json
6
- # opencode plugin .opencode/plugins/repowarden-garde.js
6
+ # opencode plugins .opencode/plugins/repowarden-garde.js (opencode 1.x)
7
+ # et repowarden-garde-v2.js (opencode 2.x)
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).
@@ -20,7 +21,6 @@ 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
26
 
@@ -50,13 +50,13 @@ if [ "$portee" = projet ]; then
50
50
  [ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
51
51
  base="$GIT_TOPLEVEL"
52
52
  fichier_claude="$base/.claude/settings.json"
53
- fichier_opencode="$base/.opencode/plugins/repowarden-garde.js"
53
+ dossier_opencode="$base/.opencode/plugins"
54
54
  else
55
55
  # Réglages utilisateur de Claude Code : $CLAUDE_CONFIG_DIR s'il est défini
56
56
  # (code.claude.com/docs/en/settings) ; plugins globaux d'opencode :
57
57
  # $OPENCODE_CONFIG_DIR, sinon $XDG_CONFIG_HOME/opencode (opencode.ai/docs/config)
58
58
  fichier_claude="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"
59
- fichier_opencode="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/plugins/repowarden-garde.js"
59
+ dossier_opencode="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/plugins"
60
60
  fi
61
61
 
62
62
  ecrits=()
@@ -99,26 +99,27 @@ init_claude() {
99
99
  ok_t agents.added "Claude Code" "$f"
100
100
  }
101
101
 
102
- # Plugin opencode : copie du modèle ; celui d'une version antérieure de
103
- # repowarden est remplacé, un fichier personnel gardé et signalé
102
+ # Plugins opencode (1.x et 2.x) : copie du modèle ; celui d'une version
103
+ # antérieure de repowarden est remplacé, un fichier personnel gardé et signalé
104
104
  init_opencode() {
105
- local f="$fichier_opencode"
106
- if [ -f "$f" ]; then
107
- if cmp -s "$f" "$MODELE_OPENCODE"; then
105
+ local p f modele
106
+ for p in $AGENTS_PLUGINS_OPENCODE; do
107
+ f="$dossier_opencode/$p" modele="$AGENTS_MODELES/$p"
108
+ if [ ! -f "$f" ]; then
109
+ mkdir -p "$dossier_opencode"
110
+ cp "$modele" "$f"
111
+ ecrits+=("$f")
112
+ ok_t agents.added opencode "$f"
113
+ elif cmp -s "$f" "$modele"; then
108
114
  ok_t agents.present opencode "$f"
109
115
  elif agents_plugin_repowarden "$f"; then
110
- cp "$MODELE_OPENCODE" "$f"
116
+ cp "$modele" "$f"
111
117
  ecrits+=("$f")
112
118
  ok_t agents.updated opencode "$f"
113
119
  else
114
- attention_t agents.kept opencode "$f" "$MODELE_OPENCODE"
120
+ attention_t agents.kept opencode "$f" "$modele"
115
121
  fi
116
- return 0
117
- fi
118
- mkdir -p "$(dirname "$f")"
119
- cp "$MODELE_OPENCODE" "$f"
120
- ecrits+=("$f")
121
- ok_t agents.added opencode "$f"
122
+ done
122
123
  }
123
124
 
124
125
  # Hook PreToolUse : seules les commandes « repowarden garde » retirées ; une
@@ -146,17 +147,21 @@ retirer_claude() {
146
147
  ok_t agents.removed "Claude Code" "$f"
147
148
  }
148
149
 
149
- # Plugin opencode : supprimé s'il est celui du modèle ; modifié, gardé et signalé
150
+ # Plugins opencode : chacun supprimé s'il est celui du modèle ; modifié, gardé
151
+ # et signalé
150
152
  retirer_opencode() {
151
- local f="$fichier_opencode"
152
- if [ ! -f "$f" ]; then
153
- ok_t agents.absent opencode "$f"
154
- elif cmp -s "$f" "$MODELE_OPENCODE"; then
155
- rm -f "$f"
156
- ok_t agents.removed opencode "$f"
157
- else
158
- attention_t agents.kept_remove opencode "$f"
159
- fi
153
+ local p f
154
+ for p in $AGENTS_PLUGINS_OPENCODE; do
155
+ f="$dossier_opencode/$p"
156
+ if [ ! -f "$f" ]; then
157
+ ok_t agents.absent opencode "$f"
158
+ elif cmp -s "$f" "$AGENTS_MODELES/$p"; then
159
+ rm -f "$f"
160
+ ok_t agents.removed opencode "$f"
161
+ else
162
+ attention_t agents.kept_remove opencode "$f"
163
+ fi
164
+ done
160
165
  }
161
166
 
162
167
  statut=0
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
 
@@ -133,12 +137,19 @@ protegee() {
133
137
  # si le dépôt suit ses tickets)
134
138
  verifier_fichier() {
135
139
  dossier_r "$1"
136
- local d="$REPLY"
140
+ # Sous Windows, jq finit le chemin lu par un CR (retiré) et le chemin peut
141
+ # être en forme Windows (D:/…), absolu lui aussi
142
+ local d="$REPLY" f="${1%$'\r'}"
143
+ case "$f" in /* | [A-Za-z]:[/\\]*) ;; *) f="$cwd/$f" ;; esac
137
144
  (
138
145
  cd "$d" 2>/dev/null || exit 0
139
146
  depot_repowarden || exit 0
140
147
  # Fichiers internes de git (.git/…) : hors du travail de l'agent
141
148
  case "$(pwd -P)/" in "$(cd "$(git rev-parse --git-dir)" && pwd -P)"/*) exit 0 ;; esac
149
+ if [ -f "$f" ]; then
150
+ FICHIER_CARTE="$(cd "$(dirname "$f")" && pwd -P)/${f##*/}"
151
+ FICHIER_CARTE="${FICHIER_CARTE#"$(cd "$(git rev-parse --show-toplevel)" && pwd -P)"/}"
152
+ fi
142
153
  branche="$(git symbolic-ref -q --short HEAD 2>/dev/null || true)"
143
154
  [ -n "$branche" ] || refuser garde.detache "$GIT_TOPLEVEL"
144
155
  if protegee "$branche"; then
@@ -431,7 +442,8 @@ verifier_shell() {
431
442
 
432
443
  case "$outil" in
433
444
  Edit | Write | MultiEdit | NotebookEdit | edit | write | multiedit)
434
- chemin="$(jq -r '.file_path // .filePath // .notebook_path // empty' <<<"$args" 2>/dev/null)"
445
+ # path : outils edit et write d'opencode 2.x
446
+ chemin="$(jq -r '.file_path // .filePath // .notebook_path // .path // empty' <<<"$args" 2>/dev/null)"
435
447
  [ -z "$chemin" ] || verifier_fichier "$chemin"
436
448
  ;;
437
449
  apply_patch)
@@ -440,7 +452,8 @@ case "$outil" in
440
452
  done < <(jq -r '.patchText // empty' <<<"$args" 2>/dev/null |
441
453
  sed -nE -e 's/^\*\*\* (Add|Update|Delete) File: //p' -e 's/^\*\*\* Move to: //p')
442
454
  ;;
443
- Bash | bash)
455
+ # shell : nom de l'outil bash dans opencode 2.x
456
+ Bash | bash | shell)
444
457
  verifier_shell "$(jq -r '.command // empty' <<<"$args" 2>/dev/null)"
445
458
  ;;
446
459
  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" "$@" ;;
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
@@ -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
@@ -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 : les puces de ses critères d'acceptation
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 inchangé
74
+ # RESTE_TICKET : le reste (constat, proposition), que la PR replie
75
75
  criteres_coches_r() {
76
- local corps="$1" ligne dedans=0 sortie=""
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
- sortie="$sortie$ligne"$'\n'
89
+ if [ "$dedans" = 1 ]; then
90
+ criteres="$criteres$ligne"$'\n'
91
+ else
92
+ reste="$reste$ligne"$'\n'
93
+ fi
89
94
  done <<<"$corps"
90
- REPLY="${sortie%$'\n'}"
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,98 @@ 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) : le ticket (critères en cases à cocher), ses ajouts en
194
- # commentaire, le résumé de sa conception, les commits, la section « Réalisé »
195
- # de l'auteur gardée, puis « Ticket : #N ». Le bot ne régénère que ses sections.
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 commits commentaires
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
- corps="$REPLY"
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"$'\n\n## '"$_T"$'\n'"$REPLY"
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"$'\n\n## '"$_T"$'\n'"$REPLY"
208
- commits="$(gh api "repos/$GH_REPO/compare/$INTEGRATION...$branche" \
209
- -q '.commits[] | "- \(.sha[0:7]) \(.commit.message | split("\n")[0])"' 2>/dev/null || true)"
210
- [ -z "$commits" ] || corps="$corps"$'\n\n## Commits\n'"$commits"
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"$'\n\n'"$REPLY"
265
+ [ -z "$REPLY" ] || corps="$corps${corps:+$'\n\n'}$REPLY"
214
266
  fi
215
- REPLY="$corps"$'\n\nTicket : #'"$n"
267
+ REPLY="$corps${corps:+$'\n\n'}Ticket : #$n"
268
+ }
269
+
270
+ # REPLY = compte à assigner à la PR brouillon du ticket $1 : l'auteur du push
271
+ # s'il est une personne (GitHub refuse d'assigner un bot avec un jeton d'App),
272
+ # sinon le premier assigné du ticket, sinon vide
273
+ assigne_pr_r() {
274
+ REPLY=""
275
+ case "${PUSHER:-}" in
276
+ "" | *"[bot]") ;;
277
+ *) REPLY="$PUSHER"; return 0 ;;
278
+ esac
279
+ REPLY="$(assignes "$1")"
280
+ REPLY="${REPLY%%$'\n'*}"
216
281
  }
217
282
 
218
283
  # Premier push sur la branche d'un ticket : PR brouillon vers la branche
219
284
  # d'intégration, titrée par le premier commit (repli : le ticket), à la
220
- # description reprise du ticket, citant le
221
- # ticket, assignée à l'auteur du push ; ticket en cours. Ouverte par le jeton
222
- # des Actions, elle ne lance pas la CI : elle tourne au push suivant, ou au
285
+ # description reprise du ticket, citant le ticket, assignée à l'auteur du
286
+ # push (une personne, jamais un bot ; repli : l'assigné du ticket ; un refus
287
+ # 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
288
  # passage « prête pour revue ». Tant qu'elle est en brouillon, chaque push met
224
289
  # à jour son titre et sa description ; une PR prête n'est plus touchée.
225
290
  cmd_push() {
226
- local n pr titre corps url
291
+ local n pr titre corps url qui
227
292
  : "${PUSH_BRANCH:?}" "${SHA:?}"
228
293
  [ "${DELETED:-false}" != true ] || return 0
229
294
  branch_ticket_r "$PUSH_BRANCH"
@@ -245,8 +310,15 @@ cmd_push() {
245
310
  return 0
246
311
  fi
247
312
  url="$(gh pr create --draft --base "$INTEGRATION" --head "$PUSH_BRANCH" --title "$titre" \
248
- --body "$corps" ${PUSHER:+--assignee "$PUSHER"})"
249
- [ -z "${PUSHER:-}" ] || gh issue edit "$n" --add-assignee "$PUSHER" >/dev/null || true
313
+ --body "$corps")"
314
+ # assignation à part : un refus de GitHub ne doit pas faire échouer le suivi
315
+ assigne_pr_r "$n"
316
+ qui="$REPLY"
317
+ if [ -n "$qui" ]; then
318
+ gh pr edit "${url##*/}" --add-assignee "$qui" >/dev/null 2>&1 ||
319
+ avertir_t tk.assign_failed "${url##*/}" "$qui"
320
+ [ "$qui" != "${PUSHER:-}" ] || gh issue edit "$n" --add-assignee "$qui" >/dev/null 2>&1 || true
321
+ fi
250
322
  est_valide "$n" && statut "$n" "$S_ENCOURS"
251
323
  notice_t tk.draft_opened "${url##*/}" "$n"
252
324
  verifier "${url##*/}" "$SHA" "$n" "$PUSH_BRANCH" || true
@@ -0,0 +1,28 @@
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…)
8
+ import { spawnSync } from "node:child_process"
9
+
10
+ // Soumet l'appel d'outil à repowarden garde ; l'erreur levée par le crochet
11
+ // empêche l'outil de s'exécuter et son message est rendu à l'agent
12
+ function garde(tool, args, cwd) {
13
+ const r = spawnSync("repowarden", ["garde"], {
14
+ input: JSON.stringify({ tool, args, cwd }),
15
+ encoding: "utf8",
16
+ })
17
+ if (r.status === 2) throw new Error((r.stderr || "").trim())
18
+ }
19
+
20
+ export default {
21
+ id: "repowarden-garde",
22
+ // Sans ctx.tool.hook (opencode 1.x charge aussi ce fichier) : rien
23
+ setup: (ctx) => {
24
+ if (typeof ctx?.tool?.hook !== "function") return
25
+ const dossier = ctx.location?.directory || process.cwd()
26
+ return ctx.tool.hook("execute.before", (appel) => garde(appel.tool, appel.input, dossier))
27
+ },
28
+ }
@@ -4,7 +4,10 @@
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
- AGENTS_MODELE_OPENCODE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../agents" && pwd -P)/repowarden-garde.js"
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
 
@@ -25,10 +28,10 @@ agents_plugin_repowarden() {
25
28
  return 1
26
29
  }
27
30
 
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
31
+ # Vrai si le plugin $1 vient de repowarden mais diffère du modèle actuel (même
32
+ # nom) : posé par une version antérieure, il n'a pas suivi les mises à jour
30
33
  agents_plugin_perime() {
31
- agents_plugin_repowarden "$1" && ! cmp -s "$1" "$AGENTS_MODELE_OPENCODE"
34
+ agents_plugin_repowarden "$1" && ! cmp -s "$1" "$AGENTS_MODELES/$(basename "$1")"
32
35
  }
33
36
 
34
37
  # Vrai si le settings.json $1 a un hook « repowarden garde… » d'une autre forme
@@ -47,15 +50,25 @@ agents_perimes() {
47
50
  agents_dossiers_globaux
48
51
  if [ -n "${1:-}" ]; then
49
52
  ! 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"
53
+ agents_opencode_perimes projet "$1/.opencode/plugins"
52
54
  fi
53
55
  ! 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
+ agents_opencode_perimes global "$AGENTS_OPENCODE_DIR/plugins"
56
57
  return 0
57
58
  }
58
59
 
60
+ # Plugins opencode périmés du dossier $2 (portée $1) : différents du modèle, ou
61
+ # absents à côté d'un plugin repowarden (posé avant le plugin 2.x, #469)
62
+ agents_opencode_perimes() {
63
+ local p
64
+ for p in $AGENTS_PLUGINS_OPENCODE; do
65
+ if agents_plugin_perime "$2/$p" ||
66
+ { [ ! -e "$2/$p" ] && agents_plugin_repowarden "$2/repowarden-garde.js"; }; then
67
+ echo "$1 opencode $2/$p"
68
+ fi
69
+ done
70
+ }
71
+
59
72
  # Vrai si Claude Code ou opencode est sur ce poste (commande, ou dossier de
60
73
  # configuration) sans garde-fou branché, ni dans le dépôt $1 (racine, vide hors
61
74
  # dépôt) ni pour l'utilisateur : ses actions ne seraient vues qu'au commit
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env bash
2
+ # Carte du projet (repowarden carte) : quoi fait quoi et pourquoi, sans LLM ni
3
+ # réseau, pour qu'un agent (ou une personne) lise l'extrait utile au lieu
4
+ # d'explorer le dépôt. Fichier .repowarden/carte/carte.json, dossier ignoré
5
+ # par git ; clés triées, ni date ni chemin absolu : même dépôt, même fichier.
6
+ # Les fonctions et leur « pourquoi » viennent de la détection du contrôle
7
+ # « fonctions sans commentaire » (commentaires.sh) : pas de second analyseur.
8
+ # Demande commentaires.sh (via lang.sh) et common.sh ; lancé à la racine.
9
+ # La conception des tickets est résumée comme pour les PR brouillon du bot :
10
+ # mêmes lecteurs (ci/tickets/push.sh), pas un second.
11
+ # shellcheck disable=SC2016,SC2013 # programmes jq et awk entre apostrophes ; numéros de tickets, un mot par ligne
12
+ _carte_ci="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../ci/tickets" && pwd -P)"
13
+ # shellcheck source=../../ci/tickets/commun.sh
14
+ source "$_carte_ci/commun.sh"
15
+ # shellcheck source=../../ci/tickets/push.sh
16
+ source "$_carte_ci/push.sh"
17
+
18
+ CARTE_DIR=.repowarden/carte
19
+ CARTE_JSON="$CARTE_DIR/carte.json"
20
+
21
+ # jq sans retour chariot : sous Windows, jq finit ses lignes par CRLF, et un
22
+ # read garderait le CR collé à la dernière valeur lue
23
+ cjq() { jq "$@" | tr -d '\r'; }
24
+
25
+ # Fichiers lus par la carte, dans le dossier $1 : « shell » (fichiers
26
+ # cartographiés), « tous » (fichiers du dépôt, rattachables à des tickets),
27
+ # tous deux hors repowarden.exclude, et « tests » (*.bats, tous)
28
+ carte_fichiers() {
29
+ local f
30
+ : >"$1/shell"
31
+ : >"$1/tests"
32
+ : >"$1/tous"
33
+ while IFS= read -r -d '' f; do
34
+ [ -f "$f" ] || continue
35
+ case "$f" in *.bats) printf '%s\n' "$f" >>"$1/tests" ;; esac
36
+ is_excluded "$f" && continue
37
+ printf '%s\n' "$f" >>"$1/tous"
38
+ case "$f" in *.bats) continue ;; esac
39
+ comments_lang_r "$f" "$f"
40
+ [ "$REPLY" != shell ] || printf '%s\n' "$f" >>"$1/shell"
41
+ done < <(git ls-files -z -co --exclude-standard)
42
+ }
43
+
44
+ # Écrit la carte (remplacement atomique : une lecture concurrente voit
45
+ # l'ancienne ou la nouvelle) ; le dossier s'ignore lui-même pour git
46
+ carte_generer() {
47
+ local d f
48
+ local -a shell=() tests=()
49
+ mkdir -p "$CARTE_DIR"
50
+ [ -f "$CARTE_DIR/.gitignore" ] || printf '*\n' >"$CARTE_DIR/.gitignore"
51
+ d="$(mktemp -d)"
52
+ carte_fichiers "$d"
53
+ while IFS= read -r f; do shell+=("$f"); done <"$d/shell"
54
+ while IFS= read -r f; do tests+=("$f"); done <"$d/tests"
55
+ : >"$d/fonctions"
56
+ : >"$d/liens"
57
+ # « fichier<TAB>ticket » par fichier modifié par un ticket (git seul)
58
+ git -c core.quotePath=false log --no-color --diff-merges=first-parent --name-only \
59
+ --format='%x1e%s%n%b%x1f' 2>/dev/null |
60
+ awk -v D=$'\x1e' -v F=$'\x1f' "$CARTE_HIST_AWK" >"$d/historique" || true
61
+ # Conceptions en cache, « \x1d<n> » puis le résumé, ticket par ticket
62
+ # Hors ligne (post-merge) : seules les conceptions déjà en cache servent
63
+ [ -n "${REPOWARDEN_CARTE_HORS_LIGNE:-}" ] || carte_conceptions "$d/historique"
64
+ : >"$d/conceptions"
65
+ for f in $(cut -f2 "$d/historique" | sort -un); do
66
+ [ -s "$CARTE_DIR/conception/$f.md" ] || continue
67
+ printf '\035%s\n' "$f" >>"$d/conceptions"
68
+ cat "$CARTE_DIR/conception/$f.md" >>"$d/conceptions"
69
+ done
70
+ if [ ${#shell[@]} -gt 0 ]; then
71
+ # « fichier<TAB>ligne<TAB>nom<TAB>pourquoi » par fonction
72
+ awk -v lang=shell -v q="'" "$COMMENTS_AWK_LIB$CARTE_AWK" "${shell[@]}" >"$d/fonctions"
73
+ # « fichier<TAB>test » par test qui le couvre
74
+ [ ${#tests[@]} -eq 0 ] ||
75
+ awk "$CARTE_TESTS_AWK" "$d/shell" "${tests[@]}" >"$d/liens"
76
+ fi
77
+ jq -n -S --rawfile s "$d/shell" --rawfile fo "$d/fonctions" --rawfile li "$d/liens" \
78
+ --rawfile to "$d/tous" --rawfile hi "$d/historique" --rawfile co "$d/conceptions" '
79
+ def lignes($x): $x | split("\n") | map(select(length > 0) | split("\t"));
80
+ (lignes($fo) | group_by(.[0]) | map({key: .[0][0], value:
81
+ map({nom: .[2], ligne: (.[1] | tonumber), pourquoi: (.[3] // "")})
82
+ | sort_by(.ligne)}) | from_entries) as $fn
83
+ | (lignes($li) | group_by(.[0])
84
+ | map({key: .[0][0], value: (map(.[1]) | unique)}) | from_entries) as $te
85
+ | ($co | split("\u001d") | map(select(length > 0) | index("\n") as $i
86
+ | {key: .[:$i], value: (.[$i + 1:] | rtrimstr("\n"))}) | from_entries) as $cn
87
+ | (lignes($to) | map({key: .[0], value: 1}) | from_entries) as $ok
88
+ | (lignes($hi) | map(select($ok[.[0]] != null))) as $h
89
+ | ($h | group_by(.[0])
90
+ | map({key: .[0][0], value: (map(.[1] | tonumber) | unique)}) | from_entries) as $tf
91
+ | {version: 1,
92
+ fichiers: (($tf | map_values({tickets: .})) + (lignes($s) | map(.[0])
93
+ | map({key: ., value: {fonctions: ($fn[.] // []), tests: ($te[.] // []),
94
+ tickets: ($tf[.] // [])}})
95
+ | from_entries)),
96
+ tickets: ($h | group_by(.[1])
97
+ | map({key: .[0][1], value: ({fichiers: (map(.[0]) | unique)}
98
+ + (if $cn[.[0][1]] then {conception: $cn[.[0][1]]} else {} end))})
99
+ | from_entries)}' >"$d/carte.json"
100
+ mv -f "$d/carte.json" "$CARTE_JSON"
101
+ rm -rf "$d"
102
+ }
103
+
104
+ # Vrai si la carte existe et qu'aucun fichier du dépôt (suivi, ou nouveau non
105
+ # ignoré) ni l'index git n'est plus récent : sinon elle ne dit plus le code
106
+ carte_a_jour() {
107
+ local f
108
+ [ -f "$CARTE_JSON" ] || return 1
109
+ f="$(git rev-parse --git-path index)"
110
+ [ ! "$f" -nt "$CARTE_JSON" ] || return 1
111
+ while IFS= read -r -d '' f; do
112
+ [ ! "$f" -nt "$CARTE_JSON" ] || return 1
113
+ done < <(git ls-files -z -co --exclude-standard)
114
+ return 0
115
+ }
116
+
117
+ # Fonctions du fichier $1 (chemin depuis la racine) : une ligne chacune,
118
+ # « ligne nom — pourquoi », puis ses tests et tickets ; JSON si $2 = json
119
+ carte_fichier() {
120
+ local f="$1" ligne nom pourquoi tests tickets n=0
121
+ if [ "${2:-}" = json ]; then
122
+ jq --arg f "$f" '{fichier: $f} + (.fichiers[$f] // {fonctions: []})' "$CARTE_JSON"
123
+ return 0
124
+ fi
125
+ printf '%s%s%s\n' "$UI_B" "$f" "$UI_N"
126
+ while IFS=$'\t' read -r ligne nom pourquoi; do
127
+ n=$((n + 1))
128
+ if [ -n "$pourquoi" ]; then
129
+ printf ' %s %s — %s\n' "$ligne" "$nom" "$pourquoi"
130
+ else
131
+ t carte.no_comment
132
+ printf ' %s %s %s\n' "$ligne" "$nom" "$REPLY"
133
+ fi
134
+ done < <(cjq -r --arg f "$f" '(.fichiers[$f].fonctions // [])[] | "\(.ligne)\t\(.nom)\t\(.pourquoi)"' "$CARTE_JSON")
135
+ [ "$n" -gt 0 ] || info_t carte.file_empty "$f"
136
+ # Tests liés (fichiers cartographiés) : ceux à écrire d'abord, puis à relancer
137
+ if jq -e --arg f "$f" '.fichiers[$f] | has("tests")' "$CARTE_JSON" >/dev/null 2>&1; then
138
+ tests="$(cjq -r --arg f "$f" '.fichiers[$f].tests | join(" ")' "$CARTE_JSON")"
139
+ if [ -n "$tests" ]; then
140
+ echo_t carte.tests "$tests"
141
+ echo_t carte.tests_run "bats $tests"
142
+ else
143
+ info_t carte.no_tests "$f"
144
+ fi
145
+ fi
146
+ # Tickets qui l'ont modifié : le pourquoi de son histoire
147
+ tickets="$(cjq -r --arg f "$f" '(.fichiers[$f].tickets // []) | map("#\(.)") | join(" ")' "$CARTE_JSON")"
148
+ [ -z "$tickets" ] || echo_t carte.tickets "$tickets"
149
+ # Conception de chacun de ces tickets : sa première ligne utile
150
+ cjq -r --arg f "$f" '. as $c | (.fichiers[$f].tickets // [])[] | tostring as $n
151
+ | ($c.tickets[$n].conception // empty) | split("\n")
152
+ | map(select(test("^\\s*(#|$)") | not)) | first // empty
153
+ | " #\($n) — \(sub("^\\s*[-*]\\s*"; ""))"' "$CARTE_JSON"
154
+ }
155
+
156
+ # Met en cache (conception/<n>.md, vide sans conception) le résumé de chaque
157
+ # ticket de l'historique $1 absent du cache : un seul appel gh, jamais bloquant
158
+ carte_conceptions() {
159
+ local n manquants="" tmp ligne num="" texte=""
160
+ mkdir -p "$CARTE_DIR/conception"
161
+ for n in $(cut -f2 "$1" | sort -un); do
162
+ [ -f "$CARTE_DIR/conception/$n.md" ] || manquants="$manquants $n"
163
+ done
164
+ [ -n "$manquants" ] || return 0
165
+ if ! has gh || ! gh auth token >/dev/null 2>&1; then
166
+ attention_t carte.no_gh
167
+ return 0
168
+ fi
169
+ tmp="$(mktemp)"
170
+ if ! gh issue list --state all --limit 1000 --json number,comments >"$tmp" 2>/dev/null; then
171
+ attention_t carte.no_gh
172
+ rm -f "$tmp"
173
+ return 0
174
+ fi
175
+ # Par ticket : « \x1d<n> » puis ses commentaires de mainteneur, chacun
176
+ # suivi de SEP_COMMENTAIRE (forme de commentaires_mainteneur_r) ; une
177
+ # dernière ligne « \x1d » seule clôt le dernier ticket
178
+ while IFS= read -r ligne; do
179
+ if [[ "$ligne" == $'\035'* ]]; then
180
+ if [ -n "$num" ] && [[ " $manquants " == *" $num "* ]]; then
181
+ carte_resume_r "$texte"
182
+ printf '%s' "$REPLY" >"$CARTE_DIR/conception/$num.md"
183
+ fi
184
+ num="${ligne#$'\035'}" texte=""
185
+ [ -n "$num" ] || break
186
+ continue
187
+ fi
188
+ texte="$texte$ligne"$'\n'
189
+ done < <(cjq -r '.[] | "\u001d\(.number)", (.comments[]
190
+ | select(.authorAssociation == "OWNER" or .authorAssociation == "MEMBER" or .authorAssociation == "COLLABORATOR")
191
+ | .body + "\n\u001e")' "$tmp"; printf '\035\n')
192
+ rm -f "$tmp"
193
+ # Tickets absents de la réponse : sans conception, pas redemandés
194
+ for n in $manquants; do
195
+ [ -f "$CARTE_DIR/conception/$n.md" ] || : >"$CARTE_DIR/conception/$n.md"
196
+ done
197
+ }
198
+
199
+ # REPLY = résumé de conception des commentaires $1 : « Ce qui change » et
200
+ # « Hors périmètre » de la dernière conception, puis les ajouts
201
+ carte_resume_r() {
202
+ local conception
203
+ resume_conception_r "$1"
204
+ conception="$REPLY"
205
+ ajouts_ticket_r "$1"
206
+ REPLY="$conception${conception:+${REPLY:+$'\n\n'}}$REPLY"
207
+ [ -z "$REPLY" ] || REPLY="$REPLY"$'\n'
208
+ }
209
+
210
+ # Fichiers modifiés par le ticket $1 (numéro, sans #), puis sa conception ;
211
+ # JSON si $2 = json.
212
+ # Code 1 si l'historique git ne le cite pas
213
+ carte_ticket() {
214
+ local n="$1" f c=0 conception
215
+ if [ "${2:-}" = json ]; then
216
+ jq -e -S --arg n "$n" '.tickets[$n] // empty | {ticket: ($n | tonumber)} + .' "$CARTE_JSON" && return 0
217
+ err_t carte.ticket_unknown "$n" "$n" "$n"
218
+ return 1
219
+ fi
220
+ while IFS= read -r f; do
221
+ c=$((c + 1))
222
+ [ "$c" -gt 1 ] || { t carte.ticket_title "$n"; printf '%s%s%s\n' "$UI_B" "$REPLY" "$UI_N"; }
223
+ printf ' %s\n' "$f"
224
+ done < <(cjq -r --arg n "$n" '(.tickets[$n].fichiers // [])[]' "$CARTE_JSON")
225
+ if [ "$c" -gt 0 ]; then
226
+ conception="$(cjq -r --arg n "$n" '.tickets[$n].conception // empty' "$CARTE_JSON")"
227
+ [ -z "$conception" ] || { echo_t carte.conception; printf '%s\n' "$conception" | sed 's/^/ /'; }
228
+ return 0
229
+ fi
230
+ err_t carte.ticket_unknown "$n" "$n" "$n"
231
+ return 1
232
+ }
233
+
234
+ # Définitions de la fonction $1 : « nom fichier:ligne » puis son pourquoi ;
235
+ # JSON (liste) si $2 = json. Code 1 si la carte ne la connaît pas
236
+ carte_fonction() {
237
+ local nom="$1" f ligne pourquoi n=0
238
+ if [ "${2:-}" = json ]; then
239
+ jq -e --arg n "$nom" '[.fichiers | to_entries[] | .key as $f
240
+ | .value.fonctions[]? | select(.nom == $n) | {fichier: $f} + .]
241
+ | if length > 0 then . else empty end' "$CARTE_JSON" && return 0
242
+ err_t carte.unknown "$nom"
243
+ return 1
244
+ fi
245
+ while IFS=$'\t' read -r f ligne pourquoi; do
246
+ n=$((n + 1))
247
+ printf '%s%s%s %s:%s\n' "$UI_B" "$nom" "$UI_N" "$f" "$ligne"
248
+ if [ -n "$pourquoi" ]; then
249
+ printf ' %s\n' "$pourquoi"
250
+ else
251
+ t carte.no_comment
252
+ printf ' %s\n' "$REPLY"
253
+ fi
254
+ done < <(cjq -r --arg n "$nom" '.fichiers | to_entries[] | .key as $f
255
+ | .value.fonctions[]? | select(.nom == $n) | "\($f)\t\(.ligne)\t\(.pourquoi)"' "$CARTE_JSON")
256
+ [ "$n" -gt 0 ] && return 0
257
+ err_t carte.unknown "$nom"
258
+ return 1
259
+ }
260
+
261
+ # Programme awk de la carte : chaque fonction de chaque fichier lu, avec sa
262
+ # ligne et le texte de son commentaire (vide s'il n'y en a pas)
263
+ # shellcheck disable=SC2016 # programme awk, rien à développer
264
+ CARTE_AWK='
265
+ # Fonctions du fichier f (lignes L[1..N])
266
+ function sortie( i, n) {
267
+ for (i = 1; i <= N; i++) {
268
+ n = nom(L[i])
269
+ if (n != "") printf "%s\t%d\t%s\t%s\n", f, i, n, commentaire(i)
270
+ }
271
+ }
272
+ FNR == 1 { if (NR > 1) sortie(); f = FILENAME; N = 0 }
273
+ # Fichier en CRLF (extraction Windows) : le CR ne fait pas partie du texte
274
+ { sub(/\r$/, ""); L[FNR] = $0; N = FNR }
275
+ END { if (NR > 0) sortie() }'
276
+
277
+
278
+ # Programme awk des tests liés : lit la liste des fichiers cartographiés, puis
279
+ # chaque test ; relie un fichier à un test par le nommage (même nom sans
280
+ # extension), par son chemin cité, ou par « repowarden <commande> » (bin/…)
281
+ # shellcheck disable=SC2016 # programme awk, rien à développer
282
+ CARTE_TESTS_AWK='
283
+ # Nom sans dossier ni extension : bin/garde, test/garde.bats → garde
284
+ function base(p) { sub(/^.*\//, "", p); sub(/\.[^.\/]*$/, "", p); return p }
285
+ # Vrai si le texte x cite le chemin p, sans autre caractère de nom collé
286
+ function cite(x, p, k, av, ap) {
287
+ while ((k = index(x, p)) > 0) {
288
+ av = (k > 1) ? substr(x, k - 1, 1) : ""
289
+ ap = substr(x, k + length(p), 1)
290
+ if (av !~ /[A-Za-z0-9_-]/ && ap !~ /[A-Za-z0-9_.-]/) return 1
291
+ x = substr(x, k + 1)
292
+ }
293
+ return 0
294
+ }
295
+ # Liens du test t (texte complet x) vers les fichiers cartographiés
296
+ function liens( i, bt, r, c, cmd) {
297
+ bt = base(t)
298
+ split("", cmd)
299
+ r = x
300
+ while (match(r, /repowarden[ \t]+[a-z][a-z0-9-]*/)) {
301
+ c = substr(r, RSTART, RLENGTH); sub(/^repowarden[ \t]+/, "", c)
302
+ cmd["bin/" c] = 1
303
+ r = substr(r, RSTART + RLENGTH)
304
+ }
305
+ for (i = 1; i <= nf; i++)
306
+ if (base(F[i]) == bt || (F[i] in cmd) || cite(x, F[i])) print F[i] "\t" t
307
+ }
308
+ FILENAME == ARGV[1] { F[++nf] = $0; next }
309
+ FNR == 1 { if (t != "") liens(); t = FILENAME; x = "" }
310
+ { sub(/\r$/, ""); x = x "\n" $0 }
311
+ END { if (t != "") liens() }'
312
+
313
+ # Programme awk de l'historique : lit « git log --name-only » (en-tête de
314
+ # commit précédé de D, fin du message marquée par F) et écrit
315
+ # « fichier<TAB>ticket » pour chaque ticket du commit (pieds « Ticket : #n »,
316
+ # branche type/<n>-sujet d'une fusion) et chaque fichier modifié
317
+ # shellcheck disable=SC2016 # programme awk, rien à développer
318
+ CARTE_HIST_AWK='
319
+ # Tickets cités par la ligne l, si c est un pied « Ticket(s) : #n »
320
+ function pied(l) {
321
+ if (l !~ /^[ \t]*[Tt][Ii][Cc][Kk][Ee][Tt][Ss]?[ \t]*:[ \t]*#[0-9]/) return
322
+ while (match(l, /#[0-9]+/)) {
323
+ T[substr(l, RSTART + 1, RLENGTH - 1) + 0] = 1
324
+ l = substr(l, RSTART + RLENGTH)
325
+ }
326
+ }
327
+ index($0, D) == 1 {
328
+ split("", T); msg = 1; $0 = substr($0, 2)
329
+ if ($0 ~ /^Merge / && match($0, /[A-Za-z]+\/[0-9]+-/)) {
330
+ b = substr($0, RSTART, RLENGTH); sub(/^[A-Za-z]+\//, "", b); T[b + 0] = 1
331
+ }
332
+ }
333
+ msg {
334
+ k = index($0, F)
335
+ if (k > 0) { pied(substr($0, 1, k - 1)); msg = 0 } else pied($0)
336
+ next
337
+ }
338
+ { sub(/\r$/, "") }
339
+ $0 != "" { for (n in T) print $0 "\t" n }'
@@ -9,27 +9,17 @@
9
9
  # Une ligne « fichier:ligne<TAB>nom » par fonction ajoutée sans commentaire
10
10
  # entre $1 (commit de base) et $2 (commit, ou vide pour le contenu stagé).
11
11
  comments_missing() {
12
- local base="$1" new="$2" st a b f old lang added tmp
12
+ local base="$1" new="$2" a b f old lang added tmp
13
13
  tmp="$(mktemp -d)"
14
- while IFS=$'\t' read -r st a b; do
14
+ while IFS=$'\t' read -r _ a b; do
15
15
  [ -n "$a" ] || continue
16
16
  # Renommage ou copie : l'ancien chemin donne les fonctions existantes
17
17
  if [ -n "$b" ]; then old="$a" f="$b"; else old="$a" f="$a"; fi
18
18
  is_excluded "$f" && continue
19
- case "${f##*/}" in
20
- *.sh | *.bash) lang=shell ;;
21
- *.py) lang=py ;;
22
- *.js | *.jsx | *.mjs | *.cjs | *.ts | *.tsx) lang=js ;;
23
- *.*) continue ;;
24
- *) lang=shebang ;;
25
- esac
26
19
  git show "$new:$f" >"$tmp/new" 2>/dev/null || continue
27
- if [ "$lang" = shebang ]; then
28
- # Script sans extension (bin/…) : seul un shebang sh ou bash compte
29
- IFS= read -r st <"$tmp/new" || true
30
- [[ "$st" =~ ^#!.*[/\ ](ba)?sh([[:space:]]|$) ]] || continue
31
- lang=shell
32
- fi
20
+ comments_lang_r "$f" "$tmp/new"
21
+ lang="$REPLY"
22
+ [ -n "$lang" ] || continue
33
23
  git show "$base:$old" >"$tmp/base" 2>/dev/null || : >"$tmp/base"
34
24
  if [ -n "$new" ]; then
35
25
  git diff -U0 --no-color --no-ext-diff "$base" "$new" -- "$f"
@@ -42,7 +32,7 @@ comments_missing() {
42
32
  for (i = 0; i < n; i++) printf " %d", s + i
43
33
  }' "$tmp/diff") "
44
34
  [ "$added" != " " ] || continue
45
- awk -v lang="$lang" -v added="$added" -v f="$f" -v q="'" "$COMMENTS_AWK" "$tmp/base" "$tmp/new"
35
+ awk -v lang="$lang" -v added="$added" -v f="$f" -v q="'" "$COMMENTS_AWK_LIB$COMMENTS_AWK" "$tmp/base" "$tmp/new"
46
36
  done < <(
47
37
  if [ -n "$new" ]; then
48
38
  git diff --name-status -M --diff-filter=ACMR "$base" "$new"
@@ -53,6 +43,22 @@ comments_missing() {
53
43
  rm -rf "$tmp"
54
44
  }
55
45
 
46
+ # REPLY = langage du fichier $1 pour la détection des fonctions (shell, py,
47
+ # js), vide sinon ; $2 = son contenu, dont le shebang décide sans extension.
48
+ # Partagé par le contrôle et la carte (repowarden carte) : une seule détection.
49
+ comments_lang_r() {
50
+ local l=""
51
+ case "${1##*/}" in
52
+ *.sh | *.bash) REPLY=shell; return 0 ;;
53
+ *.py) REPLY=py; return 0 ;;
54
+ *.js | *.jsx | *.mjs | *.cjs | *.ts | *.tsx) REPLY=js; return 0 ;;
55
+ *.*) REPLY=""; return 0 ;;
56
+ esac
57
+ # Script sans extension (bin/…) : seul un shebang sh ou bash compte
58
+ IFS= read -r l <"$2" 2>/dev/null || true
59
+ if [[ "$l" =~ ^#!.*[/\ ](ba)?sh([[:space:]]|$) ]]; then REPLY=shell; else REPLY=""; fi
60
+ }
61
+
56
62
  # Avertit des fonctions ajoutées sans commentaire, sans jamais bloquer : le
57
63
  # commentaire aide la relecture, son absence n'est pas une faute. REPLY = nombre.
58
64
  comments_check() {
@@ -66,11 +72,10 @@ comments_check() {
66
72
  REPLY="$n"
67
73
  }
68
74
 
69
- # Programme awk de comments_missing : lit le fichier de base (noms des
70
- # fonctions existantes) puis le nouveau, et signale les fonctions nouvelles
71
- # dont aucune ligne de commentaire ne précède la déclaration
75
+ # Bibliothèque awk commune au contrôle et à la carte : déclarations de
76
+ # fonctions et commentaire au-dessus (variables lang et q)
72
77
  # shellcheck disable=SC2016 # programme awk, rien à développer
73
- COMMENTS_AWK='
78
+ COMMENTS_AWK_LIB='
74
79
  # Nom de la fonction déclarée par la ligne l (vide sinon), selon le langage
75
80
  function nom(l) {
76
81
  if (lang == "shell") {
@@ -111,6 +116,26 @@ function commentee(i, j, s) {
111
116
  s = "^[ \t]*[rRuUbBfF]*[\"" q "]"
112
117
  return j <= N && L[j] ~ s
113
118
  }
119
+ # Texte des lignes de commentaire contiguës juste au-dessus de la ligne i,
120
+ # jointes par une espace (décorateurs et directives sautés), vide sinon
121
+ function commentaire(i, j, s, t) {
122
+ s = ""
123
+ for (j = i - 1; j > 0 && L[j] !~ /^#!/ && (L[j] ~ /^[ \t]*@/ || L[j] ~ /^[ \t]*(#|\/\/|\/\*|\*)/); j--) {
124
+ if (L[j] ~ /^[ \t]*@/ || directive(L[j])) continue
125
+ t = L[j]
126
+ sub(/^[ \t]*(#+|\/\/+|\/\*+|\*+)[ \t]*/, "", t); sub(/[ \t]*\*\/[ \t]*$/, "", t)
127
+ gsub(/\t/, " ", t); sub(/[ \t]+$/, "", t)
128
+ if (t != "") s = (s == "" ? t : t " " s)
129
+ }
130
+ return s
131
+ }
132
+ '
133
+
134
+ # Programme awk de comments_missing : lit le fichier de base (noms des
135
+ # fonctions existantes) puis le nouveau, et signale les fonctions nouvelles
136
+ # dont aucune ligne de commentaire ne précède la déclaration
137
+ # shellcheck disable=SC2016 # programme awk, rien à développer
138
+ COMMENTS_AWK='
114
139
  FILENAME == ARGV[1] { n = nom($0); if (n != "") ancien[n] = 1; next }
115
140
  { L[FNR] = $0; N = FNR }
116
141
  END {
@@ -114,13 +114,14 @@ _msg_en() {
114
114
  help.alias.feat) _M="(branch: feature/ accepted)" ;;
115
115
  help.alias.fix) _M="(branch: bugfix/, hotfix/ accepted)" ;;
116
116
  # Help texts for commands (--help): single source shown by bin/repowarden
117
- help.main) _M="Usage: repowarden <command> [options] · detailed help: repowarden <command> --help\n\n repowarden post status and next step\n repowarden statut (same)\n repowarden install [--global] [--lang fr|en] activate hooks (install.sh)\n repowarden uninstall [--global] [--purge] remove them (before npm uninstall -g)\n repowarden bots GitHub repo bots (CI, releases, tickets…)\n repowarden cc commit assistant\n repowarden code-mort new dead code in branch\n repowarden ticket ticket of the current branch\n repowarden ticket <n> branch of ticket (created, linked if needed)\n repowarden ticket nouveau \"<title>\" new ticket\n repowarden ticket comment <n> \"<text>\" add a comment\n repowarden ticket label <n> <label> [--remove] manage ticket labels\n repowarden pr label <n> <label> [--remove] manage PR labels\n repowarden pr checks <n> show PR checks\n repowarden run rerun <id|--failed|--workflow> rerun a workflow\n repowarden run list [--workflow <name>] list recent runs\n repowarden tickets init set up ticket tracking (GitHub)\n repowarden tickets sync my assigned tickets → local branches\n repowarden proteger [options] protect GitHub branches\n repowarden migrer [--pr] [--hooks] migrate from repogarde to repowarden\n repowarden npm-publication automate npm publication\n repowarden app init GitHub App for workflows (once)\n repowarden bot [init|rapport] org bot (pilot repo)\n repowarden agents init [--claude] [--opencode] AI agent guard (Claude Code, opencode)\n repowarden agents retirer removes the AI agent guard\n repowarden garde checks an agent action (called by its hook)\n repowarden --version installed version" ;;
117
+ help.main) _M="Usage: repowarden <command> [options] · detailed help: repowarden <command> --help\n\n repowarden post status and next step\n repowarden statut (same)\n repowarden install [--global] [--lang fr|en] activate hooks (install.sh)\n repowarden uninstall [--global] [--purge] remove them (before npm uninstall -g)\n repowarden bots GitHub repo bots (CI, releases, tickets…)\n repowarden cc commit assistant\n repowarden code-mort new dead code in branch\n repowarden carte project map: functions and their why\n repowarden ticket ticket of the current branch\n repowarden ticket <n> branch of ticket (created, linked if needed)\n repowarden ticket nouveau \"<title>\" new ticket\n repowarden ticket comment <n> \"<text>\" add a comment\n repowarden ticket label <n> <label> [--remove] manage ticket labels\n repowarden pr label <n> <label> [--remove] manage PR labels\n repowarden pr checks <n> show PR checks\n repowarden run rerun <id|--failed|--workflow> rerun a workflow\n repowarden run list [--workflow <name>] list recent runs\n repowarden tickets init set up ticket tracking (GitHub)\n repowarden tickets sync my assigned tickets → local branches\n repowarden proteger [options] protect GitHub branches\n repowarden migrer [--pr] [--hooks] migrate from repogarde to repowarden\n repowarden npm-publication automate npm publication\n repowarden app init GitHub App for workflows (once)\n repowarden bot [init|rapport] org bot (pilot repo)\n repowarden agents init [--claude] [--opencode] AI agent guard (Claude Code, opencode)\n repowarden agents retirer removes the AI agent guard\n repowarden garde checks an agent action (called by its hook)\n repowarden --version installed version" ;;
118
118
  help.statut) _M="Usage: repowarden statut (or repowarden, no args)\n\nRead-only status of repowarden on this machine and in the current repo,\nplus the next command to run (like \"gh auth status\" or \"brew doctor\")." ;;
119
119
  help.install) _M="Usage: repowarden install [--global] [--lang fr|en] [--bots | --sans-bots]\n\nActivate repowarden hooks (install.sh).\n --global all repos (and commit assistant: git cc)\n --lang fr|en message language (asked once if unset)\n --bots then propose repo bots (repowarden bots)\n --sans-bots never propose bots" ;;
120
120
  help.uninstall) _M="Usage: repowarden uninstall [--global] [--purge] [--scan FOLDER]\n\nRemove repowarden hooks (only if owned). Do before npm uninstall -g.\n --global also remove global installation\n --purge remove caches and list repos still pointing to repowarden\n --scan FOLDER check repos in FOLDER still using repowarden (with --purge)" ;;
121
121
  help.bots) _M="Usage: repowarden bots\n\nSet up GitHub repo bots (CI, releases, cleanup, tickets, App, protection,\nnpm): one global question, per-bot confirmation, preview, final confirmation.\nNothing done without consent. Can be rerun. Also via install --bots.\nNeeds a terminal, gh logged in and admin rights on the repo." ;;
122
122
  help.bot) _M="Usage: repowarden bot [--repo o/r]… [--dry-run] [--rapport FILE]\n repowarden bot rapport FILE\n repowarden bot init\n\nOrg bot: nightly (and on demand) updates adopted projects (protection,\nmigration), publishes a report to the \"Rapport du bot repowarden\" issue.\n--dry-run shows without changes. init writes .github/workflows/bot.yml.\nUses installation token of the bot App (repowarden app init --bot)." ;;
123
123
  help.cc) _M="Usage: repowarden cc [git commit options] (alias: git cc)\n repowarden cc -m \"description\" [--type T] [--scope S] [--breaking NOTE] [--body TEXT] [--refs \"Ticket: #12\"]\n repowarden cc --dry-run\n\nConventional commit assistant: guides header, body and footer, runs hooks.\nBatch mode (-m etc.): same checks, type/scope inferred from branch if omitted.\n --strict any refused answer stops (nothing committed)\n --dry-run show message without committing" ;;
124
+ help.carte) _M="Usage: repowarden carte (re)generates the map\n repowarden carte <file|function|'#n'> [--json] useful excerpt\n\nProject map, no LLM: every function of the shell scripts, with\nits file, line and \"why\" (the comment right above it),\nthe tests of each file (same name, cited path or command) and the tickets\nthat changed it (git \"Ticket : #n\" footers, no network), with the design\nof each (read with gh once, cached).\nWritten to .repowarden/carte/carte.json, a folder ignored by git; when queried,\nit is regenerated first if missing or stale. --json: output for tools.\nRequires jq." ;;
124
125
  help.code-mort) _M="Usage: repowarden code-mort [base]\n\nDead code introduced by your branch (like CI): only added/changed lines\nsince base (commits, staged/uncommitted, new files). Default: merge-base\nwith origin/main. See docs/code-mort.md (proven vs candidates)." ;;
125
126
  help.ticket) _M="Usage: repowarden ticket <n> branch for ticket (created, linked if needed)\n repowarden ticket ticket of current branch\n repowarden ticket nouveau \"<title>\" [--type fix] [--description \"…\"]\n repowarden ticket comment <n> \"<text>\" add comment\n repowarden ticket label <n> <label> [--remove] manage labels\n\nTicket first: taking <n> assigns to you and switches to its branch\n(created from develop if needed). \"nouveau\" creates a ticket (\"à valider\"\nif tracking is managed). Needs gh connected (https://cli.github.com)." ;;
126
127
  help.tickets) _M="Usage: repowarden tickets init [--repo owner/name] [--sans-validation-auto]\n repowarden tickets sync\n\nSets up ticket tracking: labels, issue template, workflow; classifies\nexisting tickets; records PR branches. sync checks out local branches for\ntickets assigned to you (auto with ticketSync=auto, manual by default)." ;;
@@ -130,7 +131,7 @@ _msg_en() {
130
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/" ;;
131
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." ;;
132
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." ;;
133
- 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 plugin\n(.opencode/plugins/repowarden-garde.js). 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). 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
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." ;;
135
136
  # Hooks and language plugins
136
137
  hook.pre_commit.1) _M="Commit refused: branch name does not follow the convention." ;;
@@ -440,6 +441,21 @@ _msg_en() {
440
441
  cm.missing) _M="%s: function %s has no comment right above it" ;;
441
442
  cm.summary) _M="%s function(s) added without a comment: one or two lines right above, saying what it does and why (warning, not blocking)." ;;
442
443
  cm.none) _M="Every added function has a comment." ;;
444
+ # Project map (repowarden carte)
445
+ carte.done) _M="Map: %s file(s), %s function(s), %s without a comment → %s" ;;
446
+ carte.no_repo) _M="repowarden carte: run it inside a git repository." ;;
447
+ carte.no_jq) _M="jq is missing: the map needs it (install jq: https://jqlang.org)." ;;
448
+ carte.no_comment) _M="(no comment)" ;;
449
+ carte.file_empty) _M="%s: no function listed in the map." ;;
450
+ carte.tests) _M="Tests: %s" ;;
451
+ carte.tickets) _M="Tickets: %s" ;;
452
+ carte.conception) _M="Design:" ;;
453
+ carte.no_gh) _M="gh missing or not logged in (gh auth login): map without the tickets' design." ;;
454
+ carte.ticket_title) _M="Ticket #%s: modified files" ;;
455
+ carte.ticket_unknown) _M="Ticket #%s: no commit cites it (footer \"Ticket : #%s\" or branch type/%s-topic)." ;;
456
+ carte.tests_run) _M=" → %s" ;;
457
+ carte.no_tests) _M="No test found for %s (no test with the same name, no test citing its path or repowarden command)." ;;
458
+ carte.unknown) _M="\"%s\" is neither a file nor a function of the map." ;;
443
459
  ci.section.commits) _M="Commit messages (Conventional Commits)" ;;
444
460
  ci.section.branch) _M="Branch name" ;;
445
461
  ci.section.secrets) _M="Secrets (gitleaks)" ;;
@@ -502,6 +518,7 @@ _msg_en() {
502
518
  st.repo) _M="Repository: %s" ;;
503
519
  st.repo_local) _M="Repository-specific hooks (local core.hooksPath = %s), take precedence" ;;
504
520
  st.repo_lefthook) _M="%s present: lefthook is no longer supported since repowarden 4, its commands are not run. Move them to .repowarden/<hook>, the expected version to .repowarden.conf (version)" ;;
521
+ st.repo_precommit) _M=".pre-commit-config.yaml present: its hooks are not run (pre-commit refuses to install with core.hooksPath set). Install them in .git/hooks, which repowarden runs on every commit: %s" ;;
505
522
  st.repo_version_old) _M="The project requires repowarden %s; installed: %s" ;;
506
523
  st.repo_version_ok) _M="Version required by the project (%s): met" ;;
507
524
  st.repo_forge) _M="Platform: %s" ;;
@@ -514,6 +531,9 @@ _msg_en() {
514
531
  st.agents_sans_garde) _M="AI agent without the guard (Claude Code, opencode): its actions are only checked at commit time. → %s" ;;
515
532
  st.agents_stale) _M="Outdated repowarden guard (%s): wired by an earlier version of repowarden. → %s" ;;
516
533
  st.next) _M="Next step:" ;;
534
+ st.carte_absente) _M="No project map (optional, read by agents before changing a file): repowarden carte" ;;
535
+ st.carte_ok) _M="Project map up to date (repowarden carte <file>)" ;;
536
+ st.carte_perimee) _M="Project map is stale: files changed since, it no longer tells what the code does." ;;
517
537
  st.ready) _M="All set: git cc to commit, repowarden code-mort on a branch." ;;
518
538
  st.help) _M="Every command: repowarden --help" ;;
519
539
  # Tickets
@@ -669,8 +689,11 @@ _msg_en() {
669
689
  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." ;;
670
690
  tk.draft_opened) _M="Draft PR #%s opened for ticket #%s" ;;
671
691
  tk.draft_updated) _M="PR #%s (ticket #%s): title and description taken from the ticket" ;;
692
+ tk.assign_failed) _M="PR #%s: GitHub refused to assign %s, PR left unassigned" ;;
672
693
  tk.pr_additions) _M="Ticket additions" ;;
673
694
  tk.pr_design) _M="Design (summary)" ;;
695
+ tk.pr_does) _M="What this PR does" ;;
696
+ tk.pr_ticket) _M="Ticket #%s" ;;
674
697
  tk.created_body) _M="Ticket created automatically for PR #%s, which cited none. To be validated by a maintainer (\"validé\" label)." ;;
675
698
  tk.created) _M="Ticket #%s created for PR #%s and linked in its description: to be validated by a maintainer." ;;
676
699
  tk.init_labels) _M="Labels (statuses and validation)" ;;
@@ -723,6 +746,7 @@ _msg_en() {
723
746
  garde.detache) _M="change refused: detached HEAD in %s.\n → Switch to a ticket's branch: repowarden ticket <n>" ;;
724
747
  garde.protegee) _M="change refused: \"%s\" is a protected branch.\n → Work on a ticket's branch: repowarden ticket <n>" ;;
725
748
  garde.protegee_libre) _M="change refused: \"%s\" is a protected branch.\n → Create a work branch: git switch -c <type>/<topic>" ;;
749
+ garde.carte) _M=" → Before changing %s, once on the right branch: repowarden carte %s (functions, tests, tickets)" ;;
726
750
  garde.hors_ticket) _M="change refused: branch \"%s\" is not a ticket's branch (<type>/<n>-<topic>).\n → Take the ticket: repowarden ticket <n>" ;;
727
751
  garde.branche) _M="command refused: \"%s\" creates a branch; here, branches come from tickets.\n → repowarden ticket <n>" ;;
728
752
  garde.commit) _M="command refused: \"%s\"; commits go through the assistant (only --amend --no-edit is allowed).\n → repowarden cc -m \"<description>\" --refs \"Ticket : #<n>\"" ;;
@@ -237,13 +237,14 @@ _msg_fr() {
237
237
  help.alias.fix) _M="(branche : bugfix/, hotfix/ acceptés)" ;;
238
238
  # Aide des commandes (repowarden <commande> --help) : source unique,
239
239
  # affichée par bin/repowarden avant toute action (printf '%b\n' → \n)
240
- help.main) _M="Usage : repowarden <commande> [options] · aide détaillée : repowarden <commande> --help\n\n repowarden état du poste et étape suivante\n repowarden statut (idem)\n repowarden install [--global] [--lang fr|en] active les hooks (install.sh)\n repowarden uninstall [--global] [--purge] les retire (avant npm uninstall -g)\n repowarden bots bots du dépôt GitHub (CI, releases, tickets…)\n repowarden cc assistant de commit\n repowarden code-mort nouveau code mort de la branche\n repowarden ticket ticket de la branche courante\n repowarden ticket <n> branche du ticket (créée, liée, si besoin)\n repowarden ticket nouveau \"<titre>\" nouveau ticket\n repowarden ticket comment <n> \"<texte>\" commentaire sur un ticket\n repowarden ticket label <n> <label> [--remove] gère les labels d'un ticket\n repowarden pr label <n> <label> [--remove] gère les labels d'une PR\n repowarden pr checks <n> vérifie les checks d'une PR\n repowarden run rerun <id|--failed|--workflow> relance un workflow\n repowarden run list [--workflow <nom>] liste les runs récents\n repowarden tickets init suivi des tickets (GitHub)\n repowarden tickets sync mes tickets assignés → leurs branches locales\n repowarden proteger [options] protection des branches (GitHub)\n repowarden migrer [--pr] [--hooks] passage de repogarde à repowarden\n repowarden npm-publication publication npm automatique\n repowarden app init GitHub App des workflows (une fois)\n repowarden bot [init|rapport] bot d'organisation (dépôt de pilotage)\n repowarden agents init [--claude] [--opencode] garde-fou des agents IA (Claude Code, opencode)\n repowarden agents retirer retire le garde-fou des agents IA\n repowarden garde vérifie une action d'agent (appelé par son hook)\n repowarden --version version installée" ;;
240
+ help.main) _M="Usage : repowarden <commande> [options] · aide détaillée : repowarden <commande> --help\n\n repowarden état du poste et étape suivante\n repowarden statut (idem)\n repowarden install [--global] [--lang fr|en] active les hooks (install.sh)\n repowarden uninstall [--global] [--purge] les retire (avant npm uninstall -g)\n repowarden bots bots du dépôt GitHub (CI, releases, tickets…)\n repowarden cc assistant de commit\n repowarden code-mort nouveau code mort de la branche\n repowarden carte carte du projet : fonctions et leur pourquoi\n repowarden ticket ticket de la branche courante\n repowarden ticket <n> branche du ticket (créée, liée, si besoin)\n repowarden ticket nouveau \"<titre>\" nouveau ticket\n repowarden ticket comment <n> \"<texte>\" commentaire sur un ticket\n repowarden ticket label <n> <label> [--remove] gère les labels d'un ticket\n repowarden pr label <n> <label> [--remove] gère les labels d'une PR\n repowarden pr checks <n> vérifie les checks d'une PR\n repowarden run rerun <id|--failed|--workflow> relance un workflow\n repowarden run list [--workflow <nom>] liste les runs récents\n repowarden tickets init suivi des tickets (GitHub)\n repowarden tickets sync mes tickets assignés → leurs branches locales\n repowarden proteger [options] protection des branches (GitHub)\n repowarden migrer [--pr] [--hooks] passage de repogarde à repowarden\n repowarden npm-publication publication npm automatique\n repowarden app init GitHub App des workflows (une fois)\n repowarden bot [init|rapport] bot d'organisation (dépôt de pilotage)\n repowarden agents init [--claude] [--opencode] garde-fou des agents IA (Claude Code, opencode)\n repowarden agents retirer retire le garde-fou des agents IA\n repowarden garde vérifie une action d'agent (appelé par son hook)\n repowarden --version version installée" ;;
241
241
  help.statut) _M="Usage : repowarden statut (ou repowarden, sans argument)\n\nÉtat de repowarden sur ce poste et dans le dépôt courant, puis la commande\nsuivante à lancer (l'équivalent de « gh auth status » ou « brew doctor »).\nLecture seule : rien n'est modifié." ;;
242
242
  help.install) _M="Usage : repowarden install [--global] [--lang fr|en] [--bots | --sans-bots]\n\nActive les hooks repowarden (install.sh).\n --global tous les dépôts (et l'assistant de commit : git cc)\n --lang fr|en langue des messages (demandée une fois sinon)\n --bots propose ensuite les bots du dépôt GitHub (repowarden bots)\n --sans-bots ne plus jamais les proposer" ;;
243
243
  help.uninstall) _M="Usage : repowarden uninstall [--global] [--purge] [--scan DOSSIER]\n\nRetire les hooks repowarden (seulement s'ils sont les tiens), avant\nnpm uninstall -g.\n --global retire aussi l'installation globale\n --purge retire les caches et liste les dépôts encore branchés\n --scan DOSSIER dépôts de DOSSIER encore branchés sur repowarden (avec --purge)" ;;
244
244
  help.bots) _M="Usage : repowarden bots\n\nBots du dépôt GitHub courant : une question globale, un accord par bot (CI,\nreleases, nettoyage, tickets, App, protection, npm), l'aperçu de ce qui sera\ncréé ou modifié, puis une confirmation finale. Rien sans accord, relançable\npour compléter. Aussi : repowarden install --bots, et --sans-bots pour ne\nplus les proposer. Demande un terminal, gh connecté et les droits\nd'administration du dépôt." ;;
245
245
  help.bot) _M="Usage : repowarden bot [--repo o/r]… [--dry-run] [--rapport FICHIER]\n repowarden bot rapport FICHIER\n repowarden bot init\n\nBot d'organisation : chaque nuit (et à la demande) il applique chaque projet\nadopté (protection, migration) et publie son rapport dans l'issue « Rapport\ndu bot repowarden » ; --dry-run voit sans rien modifier. init : écrit le\nworkflow du dépôt de pilotage (.github/workflows/bot.yml). GH_TOKEN : jeton\nd'installation de l'App (repowarden app init --bot)." ;;
246
246
  help.cc) _M="Usage : repowarden cc [options de git commit] (alias : git cc, installé par install.sh)\n repowarden cc -m \"description\" [--type T] [--scope S] [--breaking NOTE] [--body TEXTE] [--refs \"Ticket : #12\"]\n repowarden cc --dry-run\n\nAssistant de commit conventionnel : guide l'en-tête, le corps et le pied,\npuis commit via les hooks habituels. Sans question (-m : scripts, agents) :\nmêmes contrôles, type et scope déduits de la branche s'ils sont omis.\n --strict une réponse refusée arrête tout (rien n'est commité)\n --dry-run affiche le message sans commiter" ;;
247
+ help.carte) _M="Usage : repowarden carte (re)génère la carte\n repowarden carte <fichier|fonction|'#n'> [--json] extrait utile\n\nCarte du projet, sans LLM : chaque fonction des scripts shell, avec\nson fichier, sa ligne et son « pourquoi » (le commentaire juste au-dessus),\nles tests de chaque fichier (même nom, chemin ou commande cités) et les\ntickets qui l'ont modifié (pieds « Ticket : #n » de git, sans réseau), avec\nla conception de chacun (lue par gh une fois, mise en cache).\nÉcrite dans .repowarden/carte/carte.json, dossier ignoré par git ; interrogée,\nelle est d'abord régénérée si elle est absente ou périmée. --json : sortie\npour les outils. Demande jq." ;;
247
248
  help.code-mort) _M="Usage : repowarden code-mort [base]\n\nCode mort introduit par ta branche, comme en CI : seules les lignes ajoutées\nou modifiées depuis la base comptent (commits, modifications en cours,\nnouveaux fichiers). Défaut : point de départ de la branche depuis origin/main.\nVoir docs/code-mort.md : ce qui est prouvé (bloquant) et ce qui est candidat." ;;
248
249
  help.ticket) _M="Usage : repowarden ticket <n> branche du ticket (créée, liée, si besoin)\n repowarden ticket ticket de la branche courante\n repowarden ticket nouveau \"<titre>\" [--type fix] [--description \"…\"]\n repowarden ticket comment <n> \"<texte>\" commentaire sur le ticket\n repowarden ticket label <n> <label> [--remove] labels du ticket\n\nLe ticket d'abord : prendre <n> l'assigne à toi et place sur sa branche\n(créée depuis develop au besoin) ; nouveau crée le ticket (« à valider » si\nle suivi est géré par repowarden). Demande gh connecté (https://cli.github.com)." ;;
249
250
  help.tickets) _M="Usage : repowarden tickets init [--repo propriétaire/nom] [--sans-validation-auto]\n repowarden tickets sync\n\nPrépare le suivi des tickets d'un dépôt GitHub (relançable sans risque) :\nétiquettes (« validé », statuts et types, préfixe des branches), modèle de\nticket, workflow ; tickets existants rangés ; branches des PR inscrites sur\nleurs tickets. sync : branches locales des tickets qui me sont assignés\n(automatique avec ticketSync = auto, défaut manual)." ;;
@@ -253,7 +254,7 @@ _msg_fr() {
253
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/" ;;
254
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." ;;
255
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." ;;
256
- 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), plugin opencode\n(.opencode/plugins/repowarden-garde.js). Sans --claude ni --opencode : les\ndeux. --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." ;;
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\ndeux. --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." ;;
257
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." ;;
258
259
  # CI et protection
259
260
  ci.check.2) _M="Pas de base : vérification ignorée." ;;
@@ -441,6 +442,21 @@ _msg_fr() {
441
442
  cm.missing) _M="%s : fonction %s sans commentaire juste au-dessus" ;;
442
443
  cm.summary) _M="%s fonction(s) ajoutée(s) sans commentaire : une à deux lignes juste au-dessus, qui disent ce qu'elle fait et pourquoi (avertissement, non bloquant)." ;;
443
444
  cm.none) _M="Chaque fonction ajoutée est commentée." ;;
445
+ # Carte du projet (repowarden carte)
446
+ carte.done) _M="Carte : %s fichier(s), %s fonction(s), dont %s sans commentaire → %s" ;;
447
+ carte.no_repo) _M="repowarden carte : à lancer dans un dépôt git." ;;
448
+ carte.no_jq) _M="jq absent : la carte en a besoin (installer jq : https://jqlang.org)." ;;
449
+ carte.no_comment) _M="(sans commentaire)" ;;
450
+ carte.file_empty) _M="%s : aucune fonction répertoriée dans la carte." ;;
451
+ carte.tests) _M="Tests : %s" ;;
452
+ carte.tickets) _M="Tickets : %s" ;;
453
+ carte.conception) _M="Conception :" ;;
454
+ carte.no_gh) _M="gh absent ou non connecté (gh auth login) : carte sans la conception des tickets." ;;
455
+ carte.ticket_title) _M="Ticket #%s : fichiers modifiés" ;;
456
+ carte.ticket_unknown) _M="Ticket #%s : aucun commit ne le cite (pied « Ticket : #%s » ou branche type/%s-sujet)." ;;
457
+ carte.tests_run) _M=" → %s" ;;
458
+ carte.no_tests) _M="Aucun test trouvé pour %s (ni test du même nom, ni chemin ou commande repowarden cités dans un test)." ;;
459
+ carte.unknown) _M="« %s » n'est ni un fichier ni une fonction de la carte." ;;
444
460
  ci.section.commits) _M="Messages de commit (Conventional Commits)" ;;
445
461
  ci.section.branch) _M="Nom de branche" ;;
446
462
  ci.section.secrets) _M="Secrets (gitleaks)" ;;
@@ -503,6 +519,7 @@ _msg_fr() {
503
519
  st.repo) _M="Dépôt : %s" ;;
504
520
  st.repo_local) _M="Hooks propres à ce dépôt (core.hooksPath local = %s), prioritaires" ;;
505
521
  st.repo_lefthook) _M="%s présent : lefthook n'est plus pris en charge depuis repowarden 4, ses commandes ne sont pas lancées. Les déplacer dans .repowarden/<hook>, la version attendue dans .repowarden.conf (version)" ;;
522
+ st.repo_precommit) _M=".pre-commit-config.yaml présent : ses hooks ne sont pas lancés (pre-commit refuse de s'installer avec core.hooksPath). Les installer dans .git/hooks, que repowarden lance à chaque commit : %s" ;;
506
523
  st.repo_version_old) _M="Le projet demande repowarden %s ; installé : %s" ;;
507
524
  st.repo_version_ok) _M="Version demandée par le projet (%s) : respectée" ;;
508
525
  st.repo_forge) _M="Plateforme : %s" ;;
@@ -515,6 +532,9 @@ _msg_fr() {
515
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" ;;
516
533
  st.agents_stale) _M="Garde-fou repowarden périmé (%s) : posé par une version antérieure de repowarden. → %s" ;;
517
534
  st.next) _M="Étape suivante :" ;;
535
+ st.carte_absente) _M="Carte du projet absente (facultative, lue par les agents avant de modifier un fichier) : repowarden carte" ;;
536
+ st.carte_ok) _M="Carte du projet à jour (repowarden carte <fichier>)" ;;
537
+ st.carte_perimee) _M="Carte du projet périmée : des fichiers ont changé depuis, elle ne dit plus ce que fait le code." ;;
518
538
  st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
519
539
  st.help) _M="Toutes les commandes : repowarden --help" ;;
520
540
  # Tickets
@@ -670,8 +690,11 @@ _msg_fr() {
670
690
  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." ;;
671
691
  tk.draft_opened) _M="PR #%s ouverte en brouillon pour le ticket #%s" ;;
672
692
  tk.draft_updated) _M="PR #%s (ticket #%s) : titre et description repris du ticket" ;;
693
+ tk.assign_failed) _M="PR #%s : assignation de %s refusée par GitHub, PR laissée sans assigné" ;;
673
694
  tk.pr_additions) _M="Ajouts du ticket" ;;
674
695
  tk.pr_design) _M="Conception (résumé)" ;;
696
+ tk.pr_does) _M="Ce que fait cette PR" ;;
697
+ tk.pr_ticket) _M="Ticket #%s" ;;
675
698
  tk.created_body) _M="Ticket créé automatiquement pour la PR #%s, qui n'en citait aucun. À valider par un mainteneur (étiquette « validé »)." ;;
676
699
  tk.created) _M="Ticket #%s créé pour la PR #%s et relié à sa description : à valider par un mainteneur." ;;
677
700
  tk.init_labels) _M="Étiquettes (statuts et validation)" ;;
@@ -724,6 +747,7 @@ _msg_fr() {
724
747
  garde.detache) _M="modification refusée : HEAD détaché dans %s.\n → Se placer sur la branche d'un ticket : repowarden ticket <n>" ;;
725
748
  garde.protegee) _M="modification refusée : « %s » est une branche protégée.\n → Travailler sur la branche d'un ticket : repowarden ticket <n>" ;;
726
749
  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>" ;;
750
+ garde.carte) _M=" → Avant de modifier %s, une fois sur la bonne branche : repowarden carte %s (fonctions, tests, tickets)" ;;
727
751
  garde.hors_ticket) _M="modification refusée : la branche « %s » n'est pas celle d'un ticket (<type>/<n>-<sujet>).\n → Prendre le ticket : repowarden ticket <n>" ;;
728
752
  garde.branche) _M="commande refusée : « %s » crée une branche ; ici, la branche naît du ticket.\n → repowarden ticket <n>" ;;
729
753
  garde.commit) _M="commande refusée : « %s » ; les commits passent par l'assistant (seul --amend --no-edit est permis).\n → repowarden cc -m \"<description>\" --refs \"Ticket : #<n>\"" ;;
package/hooks/post-merge CHANGED
@@ -11,6 +11,11 @@ exit_if_skipped
11
11
  run_local_hook "$@"
12
12
  # Branches locales des tickets qui me sont assignés (au plus toutes les 15 min)
13
13
  tickets_sync || true
14
+ # Carte du projet déjà générée (repowarden carte) : recalculée après la fusion,
15
+ # hors ligne (ni gh ni réseau), sans jamais faire échouer le hook
16
+ if [ -n "$GIT_TOPLEVEL" ] && [ -f "$GIT_TOPLEVEL/.repowarden/carte/carte.json" ] && has jq; then
17
+ REPOWARDEN_CARTE_HORS_LIGNE=1 bash "$(dirname "$0")/../bin/carte" >/dev/null 2>&1 || true
18
+ fi
14
19
  skipped prune-branches && exit 0
15
20
 
16
21
  current="$(git symbolic-ref --short HEAD 2>/dev/null || echo "")"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowarden",
3
- "version": "4.12.1-next.3",
3
+ "version": "4.13.0-next.7",
4
4
  "description": "Hooks git et CI multi-langages : messages de commit, nommage des branches, secrets, formatage, tests ciblés, code mort, releases.",
5
5
  "keywords": [
6
6
  "git-hooks",