repowarden 4.9.3 → 4.10.0-next.4

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/repowarden CHANGED
@@ -21,6 +21,8 @@
21
21
  # repowarden npm-publication publication npm automatique
22
22
  # repowarden app init GitHub App des workflows (une fois)
23
23
  # repowarden bot [init|rapport] bot d'organisation (dépôt de pilotage)
24
+ # repowarden agents init [--claude] [--opencode] garde-fou des agents IA
25
+ # repowarden garde [--format claude|opencode] vérifie une action d'agent
24
26
  # repowarden --version
25
27
  set -euo pipefail
26
28
 
@@ -38,11 +40,55 @@ source "$ROOT/hooks/lib/ui.sh"
38
40
  # shellcheck source=../hooks/lib/i18n.sh
39
41
  source "$ROOT/hooks/lib/i18n.sh"
40
42
 
41
- aide() { sed -n '2,24p' "$ROOT/bin/repowarden" | sed 's/^# \{0,1\}//'; }
43
+ # Clé d'aide d'une commande, alias compris ; vide si la commande est inconnue.
44
+ help_key_r() {
45
+ case "$1" in
46
+ install) REPLY=help.install ;;
47
+ uninstall) REPLY=help.uninstall ;;
48
+ bots) REPLY=help.bots ;;
49
+ bot) REPLY=help.bot ;;
50
+ agents) REPLY=help.agents ;;
51
+ garde | guard) REPLY=help.garde ;;
52
+ cc | commit) REPLY=help.cc ;;
53
+ code-mort | deadcode) REPLY=help.code-mort ;;
54
+ ticket) REPLY=help.ticket ;;
55
+ tickets) REPLY=help.tickets ;;
56
+ pr) REPLY=help.pr ;;
57
+ run) REPLY=help.run ;;
58
+ app) REPLY=help.app ;;
59
+ migrer | migrate) REPLY=help.migrer ;;
60
+ proteger | protect) REPLY=help.proteger ;;
61
+ npm-publication | npm-publish) REPLY=help.npm-publication ;;
62
+ "" | statut | status) REPLY=help.statut ;;
63
+ *) REPLY="" ;;
64
+ esac
65
+ }
66
+
67
+ aide() { _tr help.main; printf '%b\n' "$_T" >&2; }
42
68
 
43
69
  commande="${1:-}"
70
+ cmd="$commande"
44
71
  [ $# -eq 0 ] || shift
45
- case "$commande" in
72
+
73
+ # Une seule interception : le premier argument de la sous-commande, testé
74
+ # avant l'exécution (code 0, rien n'est fait). Seul ce premier argument
75
+ # compte : cc -m "--help" reste un message. pr et run gardent leurs
76
+ # sous-commandes (label, checks, rerun, list), dispatchées plus bas.
77
+ case "${1:-}" in
78
+ -h | --help | help)
79
+ help_key_r "$cmd"
80
+ if [ -n "$REPLY" ]; then
81
+ _tr "$REPLY"
82
+ printf '%b\n' "$_T"
83
+ exit 0
84
+ fi
85
+ err_t install.unknown_arg "$cmd"
86
+ aide
87
+ exit 1
88
+ ;;
89
+ esac
90
+
91
+ case "$cmd" in
46
92
  install)
47
93
  # npx : paquet dans un cache temporaire, effaçable à tout moment ;
48
94
  # des hooks globaux y pointeraient vers un dossier disparu
@@ -59,6 +105,28 @@ case "$commande" in
59
105
  ticket) exec bash "$ROOT/bin/ticket" "$@" ;;
60
106
  ticket-comment) exec bash "$ROOT/bin/ticket-comment" "$@" ;;
61
107
  ticket-label) exec bash "$ROOT/bin/ticket-label" "$@" ;;
108
+ pr)
109
+ if [ $# -ge 1 ]; then
110
+ case "$1" in
111
+ label) exec bash "$ROOT/bin/pr-label" "${@:2}" ;;
112
+ checks) exec bash "$ROOT/bin/pr-checks" "${@:2}" ;;
113
+ *) err_t install.unknown_arg "$1"; _tr help.pr; printf '%b\n' "$_T" >&2; exit 1 ;;
114
+ esac
115
+ else
116
+ _tr help.pr; printf '%b\n' "$_T"; exit 0
117
+ fi
118
+ ;;
119
+ run)
120
+ if [ $# -ge 1 ]; then
121
+ case "$1" in
122
+ rerun) exec bash "$ROOT/bin/run-rerun" "${@:2}" ;;
123
+ list) exec bash "$ROOT/bin/run-list" "${@:2}" ;;
124
+ *) err_t install.unknown_arg "$1"; _tr help.run; printf '%b\n' "$_T" >&2; exit 1 ;;
125
+ esac
126
+ else
127
+ _tr help.run; printf '%b\n' "$_T"; exit 0
128
+ fi
129
+ ;;
62
130
  pr-label) exec bash "$ROOT/bin/pr-label" "$@" ;;
63
131
  run-rerun) exec bash "$ROOT/bin/run-rerun" "$@" ;;
64
132
  run-list) exec bash "$ROOT/bin/run-list" "$@" ;;
@@ -69,15 +137,20 @@ case "$commande" in
69
137
  app) exec bash "$ROOT/bin/app" "$@" ;;
70
138
  bots) exec bash "$ROOT/bin/bots" "$@" ;;
71
139
  bot) exec bash "$ROOT/bin/bot" "$@" ;;
140
+ agents) exec bash "$ROOT/bin/agents" "$@" ;;
141
+ garde | guard) exec bash "$ROOT/bin/garde" "$@" ;;
72
142
  -v | --version | version)
73
143
  repowarden_version_r "$ROOT"
74
144
  echo "$REPLY"
75
145
  ;;
76
146
  "" | statut | status) exec bash "$ROOT/bin/statut" "$@" ;;
77
- -h | --help | help) aide ;;
147
+ -h | --help | help)
148
+ _tr help.main
149
+ printf '%b\n' "$_T"
150
+ ;;
78
151
  *)
79
- err_t install.unknown_arg "$commande"
80
- aide >&2
152
+ err_t install.unknown_arg "$cmd"
153
+ aide
81
154
  exit 1
82
155
  ;;
83
156
  esac
package/bin/run-list CHANGED
@@ -10,19 +10,8 @@ source "$ROOT/hooks/lib/common.sh"
10
10
  source "$ROOT/hooks/lib/i18n.sh"
11
11
 
12
12
  aide() {
13
- cat <<EOF
14
- Usage: repowarden run list [--workflow <nom>] [--limit N]
15
-
16
- Liste les runs de workflows récents.
17
-
18
- Options:
19
- --workflow <nom> Filtre par nom de workflow
20
- --limit N Nombre max de runs (défaut: 20)
21
-
22
- Exemples:
23
- repowarden run list
24
- repowarden run list --workflow "CI" --limit 10
25
- EOF
13
+ _tr help.run
14
+ printf '%b\n' "$_T"
26
15
  }
27
16
 
28
17
  if [[ "$1" == "--help" || "$1" == "-h" ]]; then
package/bin/run-rerun CHANGED
@@ -10,21 +10,8 @@ source "$ROOT/hooks/lib/common.sh"
10
10
  source "$ROOT/hooks/lib/i18n.sh"
11
11
 
12
12
  aide() {
13
- cat <<EOF
14
- Usage: repowarden run rerun <run-id|--failed|--workflow <nom>>
15
-
16
- Relance un workflow GitHub Actions.
17
-
18
- Options:
19
- <run-id> ID du run à relancer
20
- --failed Relance le dernier run échoué du repo
21
- --workflow <nom> Relance le dernier run du workflow spécifié
22
-
23
- Exemples:
24
- repowarden run rerun 123456789
25
- repowarden run rerun --failed
26
- repowarden run rerun --workflow "CI"
27
- EOF
13
+ _tr help.run
14
+ printf '%b\n' "$_T"
28
15
  }
29
16
 
30
17
  [ $# -ge 1 ] || { aide >&2; exit 1; }
package/bin/statut CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env bash
2
+ # Usage : repowarden statut (ou repowarden, sans argument)
3
+ #
2
4
  # État de repowarden sur ce poste et dans le dépôt courant, puis la commande
3
- # suivante à lancer (l'équivalent de « gh auth status » ou « brew doctor ») :
4
- # repowarden statut (ou repowarden, sans argument)
5
+ # suivante à lancer (l'équivalent de « gh auth status » ou « brew doctor »).
5
6
  # Lecture seule : rien n'est modifié.
6
7
  set -euo pipefail
7
8
 
package/bin/ticket CHANGED
@@ -18,16 +18,36 @@ source "$ROOT/hooks/lib/common.sh"
18
18
  # shellcheck source=../hooks/lib/tickets.sh
19
19
  source "$ROOT/hooks/lib/tickets.sh"
20
20
 
21
- aide() { sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//'; }
21
+ aide() { _tr help.ticket; printf '%b\n' "$_T" >&2; }
22
22
 
23
23
  [ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
24
24
  cd "$GIT_TOPLEVEL"
25
25
 
26
+ # Vrai si remote.origin.fetch couvre déjà la branche $1 : ligne exacte, ou
27
+ # refspec générale d'un clone normal. Évite d'empiler la même ligne à chaque
28
+ # appel (la config de l'utilisateur est laissée intacte quand elle suffit).
29
+ refspec_covers() {
30
+ local ligne
31
+ while IFS= read -r ligne; do
32
+ case "$ligne" in
33
+ "+refs/heads/$1:refs/remotes/origin/$1" | "+refs/heads/*:refs/remotes/origin/*") return 0 ;;
34
+ esac
35
+ done < <(git config --get-all remote.origin.fetch)
36
+ return 1
37
+ }
38
+
26
39
  # Se place sur la branche $1 (locale, ou suivie depuis origin)
27
40
  switch_to() {
28
41
  if git show-ref -q --verify "refs/heads/$1"; then
29
42
  git switch -q "$1"
30
43
  else
44
+ # Clone mono-branche : la ref du ticket existe sans être suivie, et
45
+ # « switch --track » la refuse (« … is not a branch ») tant que la
46
+ # refspec ne la couvre pas. On la couvre une seule fois, puis on suit.
47
+ if ! refspec_covers "$1"; then
48
+ git config --add remote.origin.fetch "+refs/heads/$1:refs/remotes/origin/$1"
49
+ fi
50
+ git fetch -q origin
31
51
  git switch -q --track -c "$1" "origin/$1"
32
52
  fi
33
53
  }
@@ -37,7 +57,7 @@ prendre() { gh issue edit "$1" --add-assignee @me >/dev/null 2>&1 || true; }
37
57
 
38
58
  cmd_ticket() {
39
59
  local n="$1" json etat titre etiquettes base
40
- command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
60
+ gh_ready || exit 1
41
61
  step_t tk.cli.fetch "$n"
42
62
  git fetch -q origin --prune
43
63
  ticket_remote_branch_r "$n"
@@ -83,7 +103,7 @@ cmd_nouveau() {
83
103
  shift
84
104
  done
85
105
  [ -n "$titre" ] || { err_t tk.cli.title_required; exit 1; }
86
- command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
106
+ gh_ready || exit 1
87
107
  if tickets_managed; then
88
108
  etiquettes+=(--label "$S_AVALIDER")
89
109
  [ -z "$type" ] || etiquettes+=(--label "type: $type")
@@ -119,7 +139,9 @@ case "${1:-}" in
119
139
  if [ -n "$REPLY" ]; then
120
140
  info_t tk.cli.current "$branche" "$REPLY"
121
141
  else
122
- ticket_hint "$branche"
142
+ err_t tk.cli.no_ticket_here
143
+ aide >&2
144
+ exit 1
123
145
  fi
124
146
  ;;
125
147
  *)
@@ -10,19 +10,16 @@ source "$ROOT/hooks/lib/common.sh"
10
10
  source "$ROOT/hooks/lib/i18n.sh"
11
11
 
12
12
  aide() {
13
- cat <<EOF
14
- Usage: repowarden ticket comment <n> "<texte>"
15
-
16
- Ajoute un commentaire sur le ticket #<n>.
17
-
18
- Exemples:
19
- repowarden ticket comment 123 "Conception retenue : ..."
20
- repowarden ticket comment 456 "PR prête pour review"
21
- EOF
13
+ _tr help.ticket
14
+ printf '%b\n' "$_T"
22
15
  }
23
16
 
17
+ case "${1:-}" in
18
+ -h | --help) aide; exit 0 ;;
19
+ esac
20
+
24
21
  [ $# -ge 2 ] || { aide >&2; exit 1; }
25
- command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
22
+ gh_ready || exit 1
26
23
 
27
24
  n="${1}"
28
25
  texte="${2}"
package/bin/ticket-label CHANGED
@@ -10,23 +10,16 @@ source "$ROOT/hooks/lib/common.sh"
10
10
  source "$ROOT/hooks/lib/i18n.sh"
11
11
 
12
12
  aide() {
13
- cat <<EOF
14
- Usage: repowarden ticket label <n> <label> [--remove]
15
-
16
- Ajoute ou retire un label sur le ticket #<n>.
17
-
18
- Options:
19
- --remove Retire le label au lieu de l'ajouter
20
-
21
- Exemples:
22
- repowarden ticket label 123 "validé"
23
- repowarden ticket label 123 "statut: backlog"
24
- repowarden ticket label 123 "wip" --remove
25
- EOF
13
+ _tr help.ticket
14
+ printf '%b\n' "$_T"
26
15
  }
27
16
 
17
+ case "${1:-}" in
18
+ -h | --help) aide; exit 0 ;;
19
+ esac
20
+
28
21
  [ $# -ge 2 ] || { aide >&2; exit 1; }
29
- command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
22
+ gh_ready || exit 1
30
23
 
31
24
  n="${1}"
32
25
  label="${2}"
package/bin/tickets CHANGED
@@ -34,7 +34,7 @@ done
34
34
  if [ "$commande" = sync ]; then
35
35
  # shellcheck source=../hooks/lib/tickets.sh
36
36
  source "$ROOT/hooks/lib/tickets.sh"
37
- command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
37
+ gh_ready || exit 1
38
38
  [ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
39
39
  cd "$GIT_TOPLEVEL"
40
40
  tickets_sync force
@@ -42,12 +42,11 @@ if [ "$commande" = sync ]; then
42
42
  exit 0
43
43
  fi
44
44
  if [ "$commande" != init ]; then
45
- sed -n '2,16p' "$0" | sed 's/^# \{0,1\}//'
46
- [ "$commande" = "" ] || [ "$commande" = -h ] || [ "$commande" = --help ]
45
+ _tr help.tickets; printf '%b\n' "$_T"
47
46
  exit
48
47
  fi
49
48
 
50
- command -v gh >/dev/null || { err_t bot.no_gh; exit 1; }
49
+ gh_ready || exit 1
51
50
  [ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
52
51
  cd "$GIT_TOPLEVEL"
53
52
  [ -n "$REPO" ] || REPO="$(gh repo view --json nameWithOwner -q .nameWithOwner)"
@@ -0,0 +1,16 @@
1
+ // Garde-fou repowarden pour opencode (é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é.
5
+ // Plugins opencode : https://opencode.ai/docs/plugins/ (tool.execute.before)
6
+ import { spawnSync } from "node:child_process"
7
+
8
+ export const RepowardenGarde = async ({ directory }) => ({
9
+ "tool.execute.before": async (input, output) => {
10
+ const r = spawnSync("repowarden", ["garde"], {
11
+ input: JSON.stringify({ tool: input.tool, args: output.args, cwd: directory }),
12
+ encoding: "utf8",
13
+ })
14
+ if (r.status === 2) throw new Error((r.stderr || "").trim())
15
+ },
16
+ })
@@ -44,15 +44,17 @@ node_test() {
44
44
  # inaccessible : prouvé) ; knip s'il est installé dans le projet (fichiers,
45
45
  # exports, dépendances non référencés : candidats)
46
46
  node_deadcode() {
47
- local ran=""
47
+ local ran="" a_tsc=0 a_knip=0
48
48
  if [ -f tsconfig.json ] && [ -x node_modules/.bin/tsc ]; then
49
49
  ran=1
50
+ a_tsc=1
50
51
  node_modules/.bin/tsc --noEmit -p . --noUnusedLocals --noUnusedParameters \
51
52
  --allowUnreachableCode false --pretty false 2>/dev/null |
52
53
  sed -nE 's/^(.+)\(([0-9]+),[0-9]+\): error (TS6133|TS6138|TS6192|TS6196|TS6198|TS7027): (.*)$/prouve\t\1\t\2\t\4/p'
53
54
  fi
54
55
  if [ -x node_modules/.bin/knip ] && has node; then
55
56
  ran=1
57
+ a_knip=1
56
58
  _tr dc.knip_file
57
59
  local m_file="$_T"
58
60
  _tr dc.knip_export
@@ -71,5 +73,15 @@ node_deadcode() {
71
73
  for (const d of i[k] || []) out(i.file, 0, process.env.M_DEP + " " + d.name);
72
74
  }'
73
75
  fi
74
- [ -n "$ran" ] || deadcode_tool_missing "JavaScript / TypeScript" "typescript / knip"
76
+ if [ -z "$ran" ]; then
77
+ deadcode_tool_missing "JavaScript / TypeScript" "typescript / knip"
78
+ elif [ "$a_tsc" = 0 ]; then
79
+ # knip seul : les variables, paramètres et imports inutilisés restent à analyser
80
+ _tr dc.scope.locals
81
+ deadcode_partial "JavaScript / TypeScript" typescript "$_T" "npm i -D typescript"
82
+ elif [ "$a_knip" = 0 ]; then
83
+ # tsc seul : les fichiers, exports et dépendances non référencés restent à analyser
84
+ _tr dc.scope.files
85
+ deadcode_partial "JavaScript / TypeScript" knip "$_T" "npm i -D knip"
86
+ fi
75
87
  }
@@ -63,17 +63,29 @@ python_test() {
63
63
  # vulture (confiance 100 % : prouvé, en dessous : candidat — les frameworks
64
64
  # appellent des fonctions par leur nom)
65
65
  python_deadcode() {
66
- local ran=""
66
+ local ran="" a_ruff=0 a_vulture=0
67
67
  if python_has ruff; then
68
68
  ran=1
69
+ a_ruff=1
69
70
  python_run ruff check --select F401,F811,F841 --output-format concise --quiet --exit-zero . 2>/dev/null |
70
71
  sed -nE 's/^(.+):([0-9]+):[0-9]+: F[0-9]+ (\[\*\] )?(.*)$/prouve\t\1\t\2\t\4/p'
71
72
  fi
72
73
  if python_has vulture; then
73
74
  ran=1
75
+ a_vulture=1
74
76
  python_run vulture . --min-confidence 60 --exclude ".venv,venv,node_modules,build,dist,site-packages" 2>/dev/null |
75
77
  sed -nE -e 's/^(.+):([0-9]+): (.*) \(100% confidence\)$/prouve\t\1\t\2\t\3/p' \
76
78
  -e 's/^(.+):([0-9]+): (.*) \(([0-9]+)% confidence\)$/candidat\t\1\t\2\t\3 (\4 %)/p'
77
79
  fi
78
- [ -n "$ran" ] || deadcode_tool_missing Python "ruff / vulture"
80
+ if [ -z "$ran" ]; then
81
+ deadcode_tool_missing Python "ruff / vulture"
82
+ elif [ "$a_ruff" = 0 ]; then
83
+ # vulture seul : les imports et variables inutilisés restent à analyser
84
+ _tr dc.scope.imports
85
+ deadcode_partial Python ruff "$_T" "pip install ruff"
86
+ elif [ "$a_vulture" = 0 ]; then
87
+ # ruff seul : les fonctions et classes inutilisées restent à analyser
88
+ _tr dc.scope.functions
89
+ deadcode_partial Python vulture "$_T" "pip install vulture"
90
+ fi
79
91
  }
@@ -370,7 +370,9 @@ check_version() {
370
370
  root="${HOOKS_DIR%/hooks}"
371
371
  repowarden_version_r "$root"
372
372
  installed="$REPLY"
373
- [ -n "$installed" ] || return 0
373
+ # 0.0.0-dev : version inconnue (clone sans tags, git clone --depth) ;
374
+ # aucune comparaison possible, pas de fausse alerte à chaque commit
375
+ [ -n "$installed" ] && [ "$installed" != 0.0.0-dev ] || return 0
374
376
  local IFS=.
375
377
  # shellcheck disable=SC2206 # découpage voulu sur les points
376
378
  w=($wanted) i=($installed)
@@ -17,11 +17,23 @@
17
17
  # Code mort introduit depuis $1 (base) dans les fichiers lus sur stdin.
18
18
  # Retour : 1 si un élément bloque selon le réglage deadcode.
19
19
  deadcode_projects() {
20
- local base="$1" changed added found dir p plugins niveau f l msg path proven=0 cand=0 mode ignore pat code var
21
- local ran="" file
22
- changed="$(mktemp)"
23
- added="$(mktemp)"
24
- found="$(mktemp)"
20
+ local base="$1" changed added found partial dir p plugins niveau f l msg path proven=0 cand=0 mode ignore pat code var
21
+ local ran="" file tmp partiel=0
22
+ # Un dossier temporaire unique : les fichiers de l'analyse, dont le
23
+ # fichier témoin de l'analyse partielle (voir deadcode_partial). Créés
24
+ # vides tout de suite : sans plugin ni fichier non suivi, rien ne les
25
+ # ouvrirait en écriture, et leur lecture (« done < found ») échouerait.
26
+ tmp="$(mktemp -d)"
27
+ changed="$tmp/changed"
28
+ added="$tmp/added"
29
+ found="$tmp/found"
30
+ partial="$tmp/partial"
31
+ : >"$changed"
32
+ : >"$added"
33
+ : >"$found"
34
+ : >"$partial"
35
+ DEADCODE_PARTIAL_FILE="$partial"
36
+ export DEADCODE_PARTIAL_FILE
25
37
  cat >"$changed"
26
38
 
27
39
  # Lignes ajoutées ou modifiées (fichier:ligne), du commit de base à
@@ -109,14 +121,18 @@ deadcode_projects() {
109
121
  attention_t dc.candidate "$path" "$msg"
110
122
  fi
111
123
  done <"$found"
112
- rm -f "$changed" "$added" "$found"
124
+ # Une analyse partielle (outil absent) interdit tout feu vert sans réserve
125
+ [ ! -s "$partial" ] || partiel=1
126
+ rm -rf "$tmp"
113
127
 
114
128
  if [ -z "$ran" ]; then
115
129
  info_t dc.nothing
116
130
  return 0
117
131
  fi
118
132
  if [ "$proven" = 0 ] && [ "$cand" = 0 ]; then
119
- ok_t dc.none
133
+ if [ "$partiel" = 1 ]; then attention_t dc.none_partial
134
+ else ok_t dc.none
135
+ fi
120
136
  return 0
121
137
  fi
122
138
  [ "$cand" = 0 ] || info_t dc.summary_candidates "$cand"
@@ -137,8 +153,29 @@ deadcode_projects() {
137
153
  }
138
154
 
139
155
  # Outil d'analyse absent : signalé, sans faire échouer le mode strict
140
- # (la détection de code mort reste une aide, pas un prérequis du projet)
141
- deadcode_tool_missing() { attention_t dc.tool_missing "$1" "$2"; }
156
+ # (la détection de code mort reste une aide, pas un prérequis du projet).
157
+ # Un outil totalement absent compte comme une analyse partielle.
158
+ deadcode_tool_missing() {
159
+ attention_t dc.tool_missing "$1" "$2"
160
+ deadcode_partial_note "$1"
161
+ }
162
+
163
+ # Analyse partielle : $1 langage, $2 outil absent, $3 ce qui n'est pas analysé
164
+ # (déjà traduit, clé dc.scope.*), $4 installation à donner.
165
+ # Un des outils du langage manque : ce qu'il couvre n'est pas analysé, et la
166
+ # fin de l'analyse ne dira pas que tout va bien (voir deadcode_projects).
167
+ deadcode_partial() {
168
+ attention_t dc.partial "$1" "$2" "$3" "$4"
169
+ deadcode_partial_note "$1"
170
+ }
171
+
172
+ # Note l'analyse partielle : les plugins tournent dans un sous-shell de tube
173
+ # ({ … } |), une variable ne reviendrait pas — le signal passe par un fichier
174
+ # témoin sous le dossier temporaire de l'analyse
175
+ deadcode_partial_note() {
176
+ [ -n "${DEADCODE_PARTIAL_FILE:-}" ] || return 0
177
+ printf '%s\n' "$1" >>"$DEADCODE_PARTIAL_FILE"
178
+ }
142
179
 
143
180
  # Java (Maven, Gradle) : règles PMD dont le résultat est prouvé — import,
144
181
  # champ, méthode ou variable privés jamais utilisés dans leur portée
@@ -45,3 +45,24 @@ github_sans_rulesets() {
45
45
  local reponse
46
46
  ! reponse="$(gh api "repos/$1/rulesets" 2>&1)" && [[ "$reponse" == *"403"* ]]
47
47
  }
48
+
49
+ # gh présent (rien n'est affiché)
50
+ gh_present() { command -v gh >/dev/null 2>&1; }
51
+
52
+ # gh présent et connecté : gh auth status réussi
53
+ gh_connected() { gh_present && gh auth status >/dev/null 2>&1; }
54
+
55
+ # Contrôle partagé des commandes qui parlent à GitHub (bin/ticket, bin/tickets,
56
+ # bin/app) : gh installé ET connecté, sinon le message du problème et un échec.
57
+ # Sans gh : bot.no_gh ; sans connexion : gh.not_logged (la commande de
58
+ # connexion, le scope workflow étant requis pour pousser .github/workflows/).
59
+ gh_ready() {
60
+ if ! gh_present; then
61
+ err_t bot.no_gh
62
+ return 1
63
+ fi
64
+ if ! gh_connected; then
65
+ err_t gh.not_logged
66
+ return 1
67
+ fi
68
+ }
@@ -63,6 +63,7 @@ _msg_en() {
63
63
  tk.cli.issue_created_branch) _M="Ticket #%s created." ;;
64
64
  tk.cli.title_required) _M="Ticket title required: repowarden ticket nouveau \"<title>\"" ;;
65
65
  tk.cli.current) _M="Branch %s: ticket #%s." ;;
66
+ tk.cli.no_ticket_here) _M="No ticket for this branch." ;;
66
67
  cc.ticket_offer) _M="No ticket for this branch. Create one now (title: the description)? [y/N]: " ;;
67
68
  cc.ticket_created) _M="Ticket #%s created and referenced in the message." ;;
68
69
  cc.preview) _M="Message" ;;
@@ -106,6 +107,25 @@ _msg_en() {
106
107
  install.keys_migrated) _M="repogarde.* settings (%s) renamed to repowarden.*" ;;
107
108
  help.alias.feat) _M="(branch: feature/ accepted)" ;;
108
109
  help.alias.fix) _M="(branch: bugfix/, hotfix/ accepted)" ;;
110
+ # Help texts for commands (--help): single source shown by bin/repowarden
111
+ 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 garde checks an agent action (called by its hook)\n repowarden --version installed version" ;;
112
+ 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\")." ;;
113
+ 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" ;;
114
+ 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)" ;;
115
+ 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." ;;
116
+ 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)." ;;
117
+ 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" ;;
118
+ 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)." ;;
119
+ 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)." ;;
120
+ 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)." ;;
121
+ help.pr) _M="Usage: repowarden pr label <n> <label> [--remove]\n repowarden pr checks <n>\n\nlabel adds or removes a label from PR #<n>; checks shows its checks\n(name, conclusion, status, link).\nExamples:\n repowarden pr label 123 \"ready-for-review\"\n repowarden pr label 123 \"needs-changes\" --remove\n repowarden pr checks 123" ;;
122
+ help.run) _M="Usage: repowarden run rerun <run-id|--failed|--workflow <name>>\n repowarden run list [--workflow <name>] [--limit N]\n\nrerun restarts a GitHub Actions workflow. list shows recent runs\n(default limit 20).\nExamples:\n repowarden run rerun --failed\n repowarden run list --workflow \"CI\" --limit 10" ;;
123
+ help.proteger) _M="Usage: repowarden proteger [--repo owner/name] [--checks \"repowarden,tests (ubuntu-latest)\"]\n [--relecteurs N] [--codeowners] [--environnement NAME] [--approbateurs \"login …\"]\n [--sans-tags] [--dry-run] [--verifier]\n\nProtects GitHub repo per .repowarden.conf: required PR, required checks,\nno force push/deletion, ruleset, squash merges, v* tags reserved to\nworkflows, optional human approvals. --verifier checks online rules\nwithout changes. Needs gh and admin rights." ;;
124
+ 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/" ;;
125
+ 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." ;;
126
+ 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; no --no-verify, no git add . / -A / --all. Requires jq." ;;
127
+ help.agents) _M="Usage: repowarden agents init [--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. Re-runnable: existing files are completed, never\noverwritten." ;;
128
+ 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." ;;
109
129
  # Hooks and language plugins
110
130
  hook.pre_commit.1) _M="Commit refused: branch name does not follow the convention." ;;
111
131
  hook.pre_commit.2) _M="Direct commit on '%s' forbidden. Create a branch: git switch -c feat/my-feature" ;;
@@ -127,6 +147,7 @@ _msg_en() {
127
147
  hook.pre_push.5) _M="%s: no changed file, nothing to test." ;;
128
148
  hook.pre_push.6) _M="Nothing to test (branch deletion only)." ;;
129
149
  hook.pre_push.7) _M="Pre-push OK" ;;
150
+ hook.pre_push.workflow_scope) _M="gh token without the 'workflow' scope: GitHub will refuse a push touching .github/workflows/. Allow it (then sign in again): gh auth refresh -h github.com -s workflow" ;;
130
151
  hook.pre_push.lint.missing) _M="%s not installed, %s not checked (install: %s)." ;;
131
152
  hook.pre_push.lint.run) _M="%s: %s" ;;
132
153
  hook.branch_hint.1) _M="Branch name \"%s\" does not follow the convention: commits and pushes will be refused. Rename:" ;;
@@ -310,6 +331,7 @@ _msg_en() {
310
331
  ci.proteger.tags_none) _M="not protected (--sans-tags)" ;;
311
332
  ci.proteger.unknown_team) _M="Unknown or inaccessible team: %s (format @organisation/team)" ;;
312
333
  ci.proteger.unknown_user) _M="Unknown user: %s" ;;
334
+ mig.unknown_version) _M="repowarden version unknown (clone without tags): git -C %s fetch --tags, or REPOWARDEN_MAJEURE=4 repowarden migrer." ;;
313
335
  mig.no_major) _M="repowarden major version not found (%s): REPOWARDEN_MAJEURE=4 repowarden migrer." ;;
314
336
  mig.dirty) _M="Uncommitted changes: commit or stash before repowarden migrer --pr." ;;
315
337
  mig.pr_open) _M="Migration PR already open (%s): nothing to do." ;;
@@ -376,6 +398,12 @@ _msg_en() {
376
398
  dc.candidate) _M="%s: %s (candidate: check for dynamic usage)" ;;
377
399
  dc.nothing) _M="No file in an analysable language among the changes: nothing to check." ;;
378
400
  dc.none) _M="No new dead code." ;;
401
+ dc.none_partial) _M="No new dead code in what was analysed: partial analysis, see above." ;;
402
+ dc.partial) _M="%s: %s not installed: %s (%s)." ;;
403
+ dc.scope.functions) _M="unused functions and classes not analysed" ;;
404
+ dc.scope.imports) _M="unused imports and variables not analysed" ;;
405
+ dc.scope.locals) _M="unused variables, parameters and imports not analysed" ;;
406
+ dc.scope.files) _M="unreferenced files, exports and dependencies not analysed" ;;
379
407
  dc.summary_candidates) _M="%s candidate(s) to check: not blocking (reflection, framework or public API calls are possible)." ;;
380
408
  dc.summary_proven) _M="%s proven dead code item(s): remove them, or ignore them (deadcodeIgnore)." ;;
381
409
  dc.summary_strict) _M="%s dead code item(s) (deadcode = strict mode)." ;;
@@ -397,6 +425,7 @@ _msg_en() {
397
425
  ci.notify.failure) _M="❌ %s: release workflow failed: %s" ;;
398
426
  # GitHub CLI
399
427
  bot.no_gh) _M="GitHub CLI (gh) required: https://cli.github.com" ;;
428
+ gh.not_logged) _M="gh not logged in: gh auth login -s workflow" ;;
400
429
  # npm package
401
430
  npm.npx_global) _M="npx uses a temporary folder: global hooks would point to a deleted folder. Install the package first: npm install -g repowarden, then repowarden install --global." ;;
402
431
  npm.no_npm) _M="npm required: https://nodejs.org" ;;
@@ -641,6 +670,25 @@ _msg_en() {
641
670
  run.listing.workflow) _M="Listing runs for workflow \"%s\"..." ;;
642
671
  # PR checks
643
672
  pr.checks.checking) _M="Checking checks for PR #%s..." ;;
673
+ # AI agent guard (repowarden garde, repowarden agents)
674
+ garde.format) _M="Unknown format \"%s\": claude or opencode." ;;
675
+ garde.no_jq) _M="repowarden garde: jq missing, action not checked (install jq: https://jqlang.org)." ;;
676
+ garde.json) _M="repowarden garde: unreadable JSON input, action not checked." ;;
677
+ garde.detache) _M="change refused: detached HEAD in %s.\n → Switch to a ticket's branch: repowarden ticket <n>" ;;
678
+ garde.protegee) _M="change refused: \"%s\" is a protected branch.\n → Work on a ticket's branch: repowarden ticket <n>" ;;
679
+ garde.protegee_libre) _M="change refused: \"%s\" is a protected branch.\n → Create a work branch: git switch -c <type>/<topic>" ;;
680
+ garde.hors_ticket) _M="change refused: branch \"%s\" is not a ticket's branch (<type>/<n>-<topic>).\n → Take the ticket: repowarden ticket <n>" ;;
681
+ garde.branche) _M="command refused: \"%s\" creates a branch; here, branches come from tickets.\n → repowarden ticket <n>" ;;
682
+ 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>\"" ;;
683
+ garde.no_verify) _M="command refused: \"%s\" bypasses the repowarden hooks.\n → Run it again without --no-verify and fix what the hooks report." ;;
684
+ garde.add_tout) _M="command refused: \"%s\" adds the whole repository.\n → Add files one by one: git add <files>" ;;
685
+ agents.added) _M="%s: guard added (%s)." ;;
686
+ agents.present) _M="%s: guard already in place (%s)." ;;
687
+ agents.kept) _M="%s: %s exists and differs from the template; kept as is (template: %s)." ;;
688
+ agents.bad_json) _M="%s: %s is not valid JSON; nothing was changed." ;;
689
+ agents.no_jq) _M="jq is required to update %s (https://jqlang.org)." ;;
690
+ agents.versionner) _M="To commit: git add %s" ;;
691
+ agents.no_repowarden) _M="The guard calls \"repowarden garde\": repowarden must be on the PATH (npm install -g repowarden)." ;;
644
692
  *) _M="" ;;
645
693
  esac
646
694
  }