repowarden 4.10.2 → 4.11.0-next.13

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/bot CHANGED
@@ -115,7 +115,24 @@ TRAVAIL="$(mktemp -d)"
115
115
  trap 'rm -rf "$TRAVAIL"' EXIT
116
116
  # Identité des commits du bot (workflow : <slug>[bot]), crédits de gh
117
117
  NOM_BOT="${REPOWARDEN_BOT_NAME:-repowarden-bot}"
118
- COURRIEL_BOT="${REPOWARDEN_BOT_EMAIL:-repowarden-bot@users.noreply.github.com}"
118
+
119
+ # Adresse <id>+<slug>[bot]@users.noreply.github.com, comme bot.yml : seule
120
+ # forme que GitHub rattache à l'App. Slug tiré du nom, car un jeton
121
+ # d'installation ne peut pas lire /app ; repli signalé si l'id est introuvable.
122
+ courriel_bot_r() {
123
+ REPLY="${REPOWARDEN_BOT_EMAIL:-}"
124
+ [ -z "$REPLY" ] || return 0
125
+ local slug="${NOM_BOT%\[bot\]}" id
126
+ id="$(gh api "users/${slug}[bot]" --jq .id 2>/dev/null || true)"
127
+ case "$id" in
128
+ "" | *[!0-9]*)
129
+ REPLY="repowarden-bot@users.noreply.github.com"
130
+ attention_t bot.email_fallback "${slug}[bot]" "$REPLY" ;;
131
+ *) REPLY="${id}+${slug}[bot]@users.noreply.github.com" ;;
132
+ esac
133
+ }
134
+ courriel_bot_r
135
+ COURRIEL_BOT="$REPLY"
119
136
 
120
137
  # Dernière ligne d'erreur d'une sortie (✖ …), pour le rapport
121
138
  derniere_erreur_r() {
package/bin/bots CHANGED
@@ -431,18 +431,19 @@ YAML
431
431
  YAML
432
432
  fi
433
433
  fi
434
- # Tickets livrés : statut done et fermeture à la release, préprod à la
435
- # préversion ; seulement avec le suivi des tickets (sinon le job échoue)
434
+ # Tickets livrés : statut done et fermeture à la release (ou à une
435
+ # livraison sans nouvelle version), préprod à la préversion ; seulement avec le suivi des tickets (sinon le job échoue)
436
436
  if choisi tickets || [ -e .github/workflows/tickets.yml ]; then
437
437
  cat <<'YAML'
438
438
 
439
439
  tickets:
440
440
  needs: release
441
- if: needs.release.outputs.release_created == 'true' || needs.release.outputs.prerelease_created == 'true'
441
+ if: needs.release.outputs.release_created == 'true' || needs.release.outputs.prerelease_created == 'true' || needs.release.outputs.delivered_without_version != ''
442
442
  uses: simdevWarden/repowarden/.github/workflows/tickets.yml@v4
443
443
  permissions: { contents: write, issues: write, pull-requests: write, statuses: write }
444
444
  with:
445
445
  tag: ${{ needs.release.outputs.tag_name || needs.release.outputs.prerelease_tag }}
446
+ delivered-sha: ${{ needs.release.outputs.delivered_without_version }}
446
447
  YAML
447
448
  if [ -n "$AVEC_APP" ]; then
448
449
  cat <<'YAML'
package/bin/garde CHANGED
@@ -100,6 +100,16 @@ verifier_commit() {
100
100
  fi
101
101
  }
102
102
 
103
+ # Vrai si les mots $@ demandent une lecture seule (aide, aperçu) : rien n'est
104
+ # commité, rien à refuser
105
+ lecture_seule() {
106
+ local mot
107
+ for mot in "$@"; do
108
+ case "$mot" in -h | --help | --dry-run) return 0 ;; esac
109
+ done
110
+ return 1
111
+ }
112
+
103
113
  # repowarden cc / commit (ou git cc), lancé depuis le dossier $dir
104
114
  verifier_cc() {
105
115
  dossier_r "$dir"
@@ -169,6 +179,7 @@ verifier_git() {
169
179
  done
170
180
  case "$sous" in
171
181
  commit)
182
+ lecture_seule ${args_git[@]+"${args_git[@]}"} && exit 0
172
183
  amend="" noedit=""
173
184
  for mot in ${args_git[@]+"${args_git[@]}"}; do
174
185
  case "$mot" in
@@ -183,7 +194,7 @@ verifier_git() {
183
194
  refuser garde.commit "$ligne"
184
195
  ;;
185
196
  cc)
186
- verifier_commit "$ligne"
197
+ lecture_seule ${args_git[@]+"${args_git[@]}"} || verifier_commit "$ligne"
187
198
  ;;
188
199
  add)
189
200
  for mot in ${args_git[@]+"${args_git[@]}"}; do
@@ -227,7 +238,7 @@ verifier_git() {
227
238
  # Ligne de commande shell : chaque commande (&&, ||, ;, |, retour à la ligne),
228
239
  # « cd » suivi, variables d'environnement en tête ignorées
229
240
  verifier_shell() {
230
- local commande="$1" segment mots m nl=$'\n' lignes
241
+ local commande="$1" segment mots m cmd_cc nl=$'\n' lignes
231
242
  dir="$cwd"
232
243
  # Une commande par ligne. Découpage hors de <( … ) : bash 3.2 (macOS) y
233
244
  # lit mal ces motifs (« bad substitution ») et ne vérifierait plus rien
@@ -264,7 +275,13 @@ verifier_shell() {
264
275
  verifier_git "${@//[\"\']/}"
265
276
  ;;
266
277
  repowarden | */repowarden)
267
- case "${2//[\"\']/}" in cc | commit) verifier_cc "repowarden ${2//[\"\']/}" ;; esac
278
+ case "${2//[\"\']/}" in
279
+ cc | commit)
280
+ cmd_cc="repowarden ${2//[\"\']/}"
281
+ shift 2
282
+ lecture_seule "${@//[\"\']/}" || verifier_cc "$cmd_cc"
283
+ ;;
284
+ esac
268
285
  ;;
269
286
  esac
270
287
  done <<<"$lignes"
package/bin/migrer CHANGED
@@ -29,24 +29,10 @@ done
29
29
  [ -n "$GIT_TOPLEVEL" ] || { err_t install.not_repo; exit 1; }
30
30
  cd "$GIT_TOPLEVEL"
31
31
 
32
- # Version majeure dont le tag flottant (vN) est publié : celle de repowarden ;
33
- # préversion d'une nouvelle majeure (X.0.0-next) : X-1, vX n'existant pas encore
34
- majeure_r() {
35
- local v
36
- repowarden_version_r "$ROOT"
37
- v="$REPLY"
38
- # 0.0.0-dev : version inconnue (clone sans tags), rien à déduire
39
- if [ "$v" = 0.0.0-dev ]; then REPLY=""; return; fi
40
- REPLY="${v%%.*}"
41
- REPLY="${REPLY%%-*}"
42
- [[ "$v" != *.0.0-* ]] || REPLY=$((REPLY - 1))
43
- }
44
- if [ -n "${REPOWARDEN_MAJEURE:-}" ]; then
45
- MAJEURE="$REPOWARDEN_MAJEURE"
46
- else
47
- majeure_r
48
- MAJEURE="$REPLY"
49
- fi
32
+ # Version majeure dont le tag flottant (vN) est publié
33
+ repowarden_version_r "$ROOT"
34
+ repowarden_majeure_r "$REPLY"
35
+ MAJEURE="$REPLY"
50
36
  [ -n "$MAJEURE" ] || { err_t mig.unknown_version "$ROOT"; exit 1; }
51
37
  [[ "$MAJEURE" =~ ^[1-9][0-9]*$ ]] || { err_t mig.no_major "$MAJEURE"; exit 1; }
52
38
  BRANCHE="chore/migration-repowarden-v$MAJEURE"
package/bin/statut CHANGED
@@ -9,6 +9,8 @@ set -euo pipefail
9
9
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
10
10
  # shellcheck source=../hooks/lib/common.sh
11
11
  source "$ROOT/hooks/lib/common.sh"
12
+ # shellcheck source=../hooks/lib/secrets.sh
13
+ source "$ROOT/hooks/lib/secrets.sh"
12
14
  exec 2>&1 # une seule sortie, dans l'ordre (avertissements compris)
13
15
  # shellcheck disable=SC2034 # lu par check_version (common.sh)
14
16
  HOOKS_DIR="$ROOT/hooks"
@@ -78,6 +80,42 @@ if [ -n "$GIT_TOPLEVEL" ]; then
78
80
  attention_t st.repo_migrate
79
81
  suivant="${suivant:-repowarden migrer}"
80
82
  fi
83
+ # Workflows qui appellent repowarden : dépôt officiel et tag vN de la
84
+ # majeure installée. Autre dépôt (ancien propriétaire, fork), branche
85
+ # (cassé quand elle disparaît) ou majeure dépassée : la ligne à corriger
86
+ repowarden_majeure_r "$version"
87
+ majeure="$REPLY"
88
+ re_uses='^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*["'"'"']?([A-Za-z0-9_.-]+)/repowarden(/[^@[:space:]"'"'"']*)?@([^[:space:]"'"'"'#]+)'
89
+ re_tag='^v([0-9]+)(\.[0-9]+)*(-[0-9A-Za-z.]+)?$'
90
+ re_sha='^[0-9a-f]{40}$'
91
+ for wf in "$GIT_TOPLEVEL"/.github/workflows/*.yml "$GIT_TOPLEVEL"/.github/workflows/*.yaml; do
92
+ [ -f "$wf" ] || continue
93
+ n=0
94
+ while IFS= read -r ligne || [ -n "$ligne" ]; do
95
+ n=$((n + 1))
96
+ [[ "$ligne" =~ $re_uses ]] || continue
97
+ proprio="${BASH_REMATCH[2]}" chemin="${BASH_REMATCH[3]}" ref="${BASH_REMATCH[4]}"
98
+ attendu="simdevWarden/repowarden$chemin@v${majeure:-N}"
99
+ shopt -s nocasematch
100
+ officiel=""
101
+ [[ "$proprio" != simdevwarden ]] || officiel=1
102
+ shopt -u nocasematch
103
+ cle=""
104
+ if [ -z "$officiel" ]; then
105
+ cle=st.wf_owner arg="$proprio/repowarden"
106
+ elif [[ "$ref" =~ $re_tag ]]; then
107
+ if [ -n "$majeure" ] && [ "${BASH_REMATCH[1]}" -lt "$majeure" ]; then
108
+ cle=st.wf_old arg="$ref"
109
+ fi
110
+ elif ! [[ "$ref" =~ $re_sha ]]; then
111
+ cle=st.wf_branch arg="$ref"
112
+ fi
113
+ [ -n "$cle" ] || continue
114
+ attention_t "$cle" "${wf#"$GIT_TOPLEVEL"/}" "$n" "$arg" "$attendu"
115
+ t st.wf_fix
116
+ suivant="${suivant:-$REPLY}"
117
+ done <"$wf"
118
+ done
81
119
  # Branche develop présente, flux non déclaré : traité comme « main seule »
82
120
  cfg_r integrationBranch ""
83
121
  if [ -z "$REPLY" ] && git rev-parse -q --verify refs/remotes/origin/develop >/dev/null; then
@@ -93,6 +131,11 @@ if [ -n "$GIT_TOPLEVEL" ]; then
93
131
  ok_t st.repo_version_ok "$attendue"
94
132
  fi
95
133
  fi
134
+ # Fichier sensible ni suivi ni ignoré (même avertissement qu'au pre-commit)
135
+ if ! skipped secrets && ! skipped sensitive-files; then
136
+ sensitive_files_r
137
+ [ -z "$REPLY" ] || sensitive_files_warn "$REPLY"
138
+ fi
96
139
  info_t st.repo_forge "$FORGE"
97
140
  [ "$FORGE" != github ] || dim_t st.repo_rules
98
141
  fi
@@ -103,6 +146,11 @@ for outil in gitleaks ruff vulture staticcheck pmd; do
103
146
  if has "$outil"; then presents="$presents $outil"; else absents="$absents $outil"; fi
104
147
  done
105
148
  info_t st.tools "${presents# }" "${absents# }"
149
+ # Sans gitleaks, les secrets ne sont vérifiés que par la CI
150
+ if ! has gitleaks && ! skipped secrets; then
151
+ gitleaks_install_r
152
+ suivant="${suivant:-$REPLY}"
153
+ fi
106
154
 
107
155
  echo
108
156
  if [ -n "$suivant" ]; then
package/bin/ticket CHANGED
@@ -103,6 +103,11 @@ cmd_nouveau() {
103
103
  shift
104
104
  done
105
105
  [ -n "$titre" ] || { err_t tk.cli.title_required; exit 1; }
106
+ # Étiquette « type: … » inexistante : gh refuserait de créer le ticket
107
+ if [ -n "$type" ] && tickets_managed && [[ " $TYPES_TICKET " != *" $type "* ]]; then
108
+ err_t tk.cli.bad_type "$type" "${TYPES_TICKET// /, }"
109
+ exit 1
110
+ fi
106
111
  gh_ready || exit 1
107
112
  if tickets_managed; then
108
113
  etiquettes+=(--label "$S_AVALIDER")
package/bin/tickets CHANGED
@@ -3,7 +3,7 @@
3
3
  # repowarden tickets init [--repo propriétaire/nom] [--sans-validation-auto]
4
4
  # repowarden tickets sync branches locales des tickets qui me sont assignés
5
5
  # (automatique si réglage ticketSync = auto ; défaut manual)
6
- # - étiquettes : « validé », les statuts (à valider, backlog, en cours,
6
+ # - étiquettes : « validé », « à décider » (suspend la validation d'office), les statuts (à valider, backlog, en cours,
7
7
  # en relecture, préprod, done) et les types (type: feat, type: fix…), qui
8
8
  # donnent le préfixe de la branche du ticket ;
9
9
  # - modèle de ticket .github/ISSUE_TEMPLATE/ticket.yml (créé « à valider ») ;
@@ -60,6 +60,7 @@ while IFS='|' read -r nom couleur description; do
60
60
  done <<'ETIQUETTES'
61
61
  validé|0e8a16|Ticket accepté par un mainteneur (débloque ses PR)
62
62
  statut: à valider|fbca04|Ticket créé, en attente de validation
63
+ à décider|d4c5f9|Proposé : pas de validation d'office, un mainteneur décide
63
64
  statut: backlog|c5def5|Validé, pas encore commencé
64
65
  statut: en cours|1d76db|Travail commencé (PR en brouillon)
65
66
  statut: en relecture|5319e7|PR prête, en relecture
@@ -6,7 +6,7 @@
6
6
 
7
7
  # Mise en route sur un dépôt existant (tickets init, relançable) : tickets
8
8
  # ouverts sans statut rangés (validé ou ouvert par un mainteneur → backlog,
9
- # sinon à valider) ; branches des PR ouvertes et mergées inscrites sur les
9
+ # sinon, ou « à décider », à valider) ; branches des PR ouvertes et mergées inscrites sur les
10
10
  # tickets qu'elles citent. Aucune branche créée en masse : elle naît quand le
11
11
  # ticket est pris (repowarden ticket N).
12
12
  cmd_adopter() {
@@ -17,7 +17,8 @@ cmd_adopter() {
17
17
  ! grep -q '^statut: ' <<<"$labels" || continue
18
18
  if grep -qxF "$VALIDE" <<<"$labels"; then
19
19
  statut "$n" "$S_BACKLOG"
20
- elif [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] && est_mainteneur "$assoc"; then
20
+ elif [ "${AUTO_VALIDATE:-maintainers}" = maintainers ] && est_mainteneur "$assoc" \
21
+ && ! grep -qxF "$A_DECIDER" <<<"$labels"; then
21
22
  gh issue edit "$n" --add-label "$VALIDE" >/dev/null
22
23
  statut "$n" "$S_BACKLOG"
23
24
  else
@@ -7,10 +7,15 @@
7
7
  # Ticket $1 validé : backlog s'il attendait la validation, sa branche, et
8
8
  # vérification relancée sur les PR ouvertes qui l'attendaient
9
9
  accepter() {
10
- local n="$1" pr head sha body base brouillon
11
- if grep -qxF "$S_AVALIDER" <<<"$(etiquettes "$n")"; then
10
+ local n="$1" pr head sha body base brouillon labels
11
+ labels="$(etiquettes "$n")"
12
+ if grep -qxF "$S_AVALIDER" <<<"$labels"; then
12
13
  statut "$n" "$S_BACKLOG"
13
14
  fi
15
+ # Décision prise : « à décider » n'a plus lieu d'être
16
+ if grep -qxF "$A_DECIDER" <<<"$labels"; then
17
+ gh issue edit "$n" --remove-label "$A_DECIDER" >/dev/null
18
+ fi
14
19
  # La branche naît quand le ticket est pris (assigné) : un ticket validé
15
20
  # pour plus tard n'a pas de branche qui vieillit en attendant
16
21
  if [ -n "$(assignes "$n")" ]; then
@@ -36,7 +41,8 @@ accepter() {
36
41
  # GitHub ne garde qu'un run en attente par groupe de concurrence, le run
37
42
  # « opened » peut être annulé par ceux des étiquettes posées à la création.
38
43
  # Non si déjà validé, ou (hors ouverture) rangé au-delà de « à valider »
39
- # (« validé » retiré à la main : décision respectée). Test seul : appelé dans un « if », où set -e
44
+ # (« validé » retiré à la main : décision respectée), ou proposé à décider
45
+ # (étiquette « à décider » : le mainteneur pose « validé »). Test seul : appelé dans un « if », où set -e
40
46
  # ne s'applique pas.
41
47
  a_valider_d_office() {
42
48
  local labels l
@@ -45,6 +51,7 @@ a_valider_d_office() {
45
51
  [ "${ISSUE_STATE:-open}" = open ] || return 1
46
52
  labels="$(etiquettes "$ISSUE")"
47
53
  ! grep -qxF "$VALIDE" <<<"$labels" || return 1
54
+ ! grep -qxF "$A_DECIDER" <<<"$labels" || return 1
48
55
  [ "$ACTION" != opened ] || return 0
49
56
  while IFS= read -r l; do
50
57
  [[ "$l" == "statut: "* && "$l" != "$S_AVALIDER" ]] && return 1
@@ -1,20 +1,30 @@
1
1
  # shellcheck shell=bash
2
- # Suivi des tickets, préversion et release (ci/tickets.sh release) : tickets
3
- # prévenus, ou passés en done et fermés.
2
+ # Suivi des tickets, préversion, release ou livraison sans nouvelle version
3
+ # (ci/tickets.sh release) : tickets prévenus, ou passés en done et fermés.
4
4
  # Chargé par ci/tickets.sh (après hooks/lib/common.sh et hooks/lib/tickets.sh).
5
5
  # shellcheck disable=SC2154 # statuts, réglages et compteurs définis dans les autres modules
6
6
 
7
+ # TAG : release (vX.Y.Z) ou préversion (vX.Y.Z-next.N) ; sans TAG,
8
+ # DELIVERED_SHA : commit livré sur la branche principale sans nouvelle version
9
+ # (livraison sans fix ni feat), les tickets livrés sont fermés tout de même
7
10
  cmd_release() {
8
- local n
11
+ local n tag="${TAG:-}" sha="${DELIVERED_SHA:-}"
12
+ if [ -n "$tag" ]; then
13
+ if [[ "$tag" == *-* ]]; then _tr tk.prerelease "$tag"; else _tr tk.released "$tag"; fi
14
+ elif [ -n "$sha" ]; then
15
+ _tr tk.delivered "${MAIN:-main}" "${sha:0:7}"
16
+ else
17
+ echo "ci/tickets.sh release : TAG ou DELIVERED_SHA requis" >&2
18
+ return 2
19
+ fi
20
+ local msg="$_T"
9
21
  while IFS= read -r n; do
10
22
  [ -n "$n" ] || continue
11
- if [[ "$TAG" == *-* ]]; then
12
- _tr tk.prerelease "$TAG"
13
- gh issue comment "$n" --body "$_T" >/dev/null
23
+ if [[ "$tag" == *-* ]]; then
24
+ gh issue comment "$n" --body "$msg" >/dev/null
14
25
  else
15
- _tr tk.released "$TAG"
16
26
  statut "$n" "$S_DONE"
17
- gh issue close "$n" --comment "$_T" >/dev/null
27
+ gh issue close "$n" --comment "$msg" >/dev/null
18
28
  nettoyer_branche "$n"
19
29
  fi
20
30
  done < <(gh issue list --state open --label "$S_PREPROD" --limit 500 --json number -q '.[].number')
package/ci/tickets.sh CHANGED
@@ -12,8 +12,9 @@
12
12
  # ci/tickets.sh push premier push sur la branche d'un ticket : PR
13
13
  # brouillon ouverte, ticket en cours
14
14
  # ci/tickets.sh revue corrections demandées en revue → en cours
15
- # ci/tickets.sh release release publiée : tickets en préprod → done (fermés),
16
- # ou prévenus de la préversion
15
+ # ci/tickets.sh release release publiée, ou livraison sur la branche
16
+ # principale sans nouvelle version : tickets en
17
+ # préprod → done (fermés) ; ou prévenus de la préversion
17
18
  # ci/tickets.sh adopter mise en route (tickets init) : tickets existants
18
19
  # rangés, branches des PR inscrites sur leurs tickets
19
20
  #
@@ -27,7 +28,7 @@
27
28
  # Variables : GH_TOKEN, GH_REPO, INTEGRATION ; pr : PR, ACTION, MERGED, DRAFT,
28
29
  # HEAD, BASE, HEAD_SHA, AUTHOR ; issue : ISSUE, ACTION, LABEL, STATE_REASON ;
29
30
  # push : PUSH_BRANCH, DELETED, PUSHER, SHA ; revue : PR, HEAD, REVIEW_STATE ;
30
- # release : TAG
31
+ # release : TAG, ou DELIVERED_SHA et MAIN (livraison sans nouvelle version)
31
32
  set -euo pipefail
32
33
 
33
34
  REPOWARDEN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
package/hooks/commit-msg CHANGED
@@ -74,13 +74,6 @@ Expected format: <type>(<optional scope>): <description>
74
74
  refactor!: change the public API (! = breaking change)
75
75
 
76
76
  Types:
77
- EOF
78
- types_help
79
- cat >&2 <<EOF
80
-
81
- 👉 Tip: name your branch <type>/<topic> (e.g. feat/user) and the prefix
82
- is added automatically: git commit -m "add sign-up" (or use git cc)
83
- Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG
84
77
  EOF
85
78
  else
86
79
  cat >&2 <<EOF
@@ -93,9 +86,22 @@ Format attendu : <type>(<scope optionnel>): <description>
93
86
 
94
87
  Types :
95
88
  EOF
96
- types_help
89
+ fi
90
+ types_help
91
+ echo >&2
92
+ # HEAD détachée (bisect, essai, rebase) : pas de branche dont tirer le
93
+ # préfixe, le conseil de nommage ne s'applique pas
94
+ if ! git symbolic-ref -q HEAD >/dev/null; then
95
+ echo_t hook.commit_msg.detached >&2
96
+ echo_t hook.commit_msg.kept >&2
97
+ elif [ "${REPOWARDEN_LANG:-}" = en ]; then
98
+ cat >&2 <<EOF
99
+ 👉 Tip: name your branch <type>/<topic> (e.g. feat/user) and the prefix
100
+ is added automatically: git commit -m "add sign-up" (or use git cc)
101
+ Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG
102
+ EOF
103
+ else
97
104
  cat >&2 <<EOF
98
-
99
105
  👉 Astuce : nomme ta branche <type>/<sujet> (ex. feat/user) et le préfixe
100
106
  sera ajouté automatiquement : git commit -m "ajoute l'inscription" (ou git cc)
101
107
  Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG
@@ -218,6 +218,30 @@ header_valid() {
218
218
  [ "$REPLY" -le 72 ]
219
219
  }
220
220
 
221
+ # Bots de mise à jour des dépendances : leurs titres (« bump <paquet> from X
222
+ # to Y ») dépassent souvent 72 caractères ; seul leur format est exigé.
223
+ BOTS='^[0-9]*\+?(dependabot|renovate)\[bot\]@'
224
+
225
+ # Règle des en-têtes de commit, commune au pre-push et à la CI : même verdict,
226
+ # même message, que le commit soit vu avant le push ou après.
227
+ # REPLY = clé du message de refus de l'en-tête $1 (e-mail de l'auteur $2),
228
+ # vide si l'en-tête est admis
229
+ commit_subject_problem_r() {
230
+ if [[ "$1" =~ ^(fixup|squash|amend)! ]]; then
231
+ REPLY=ci.check.3
232
+ elif [[ "$1" =~ ^(Merge|Revert) ]]; then
233
+ REPLY=""
234
+ elif ! [[ "$1" =~ $CC_PATTERN ]]; then
235
+ REPLY=ci.check.4
236
+ elif [[ "$2" =~ $BOTS ]]; then
237
+ REPLY=""
238
+ elif authored_length "$1" && [ "$REPLY" -gt 72 ]; then
239
+ REPLY=ci.check.5
240
+ else
241
+ REPLY=""
242
+ fi
243
+ }
244
+
221
245
  # REPLY = titre de PR conforme déduit de la branche $1 :
222
246
  # feat/ajout-panier → « feat: ajout panier », hotfix/crash → « fix: crash »,
223
247
  # release/1.2.0 → « chore(release): 1.2.0 », develop → livraison sur main
@@ -310,7 +334,6 @@ EOF
310
334
 
311
335
  Allowed exceptions: $allowed
312
336
  (change: git config repowarden.allowedBranches "main develop release/*")
313
- Disable for this repository: git config repowarden.skip branch-name
314
337
  EOF
315
338
  else
316
339
  cat >&2 <<EOF
@@ -333,7 +356,6 @@ EOF
333
356
 
334
357
  Exceptions autorisées : $allowed
335
358
  (modifier : git config repowarden.allowedBranches "main develop release/*")
336
- Désactiver pour ce dépôt : git config repowarden.skip branch-name
337
359
  EOF
338
360
  fi
339
361
  }
@@ -341,7 +363,7 @@ EOF
341
363
  # Vrai si l'élément est désactivé (repowarden.skip, git config ou .repowarden.conf).
342
364
  # Valeurs : true|all, ou liste séparée par espaces/virgules parmi
343
365
  # pre-commit prepare-commit-msg commit-msg pre-push post-checkout
344
- # branch-name protect-branch secrets format tests, ou un langage (node, python…)
366
+ # branch-name protect-branch secrets sensitive-files format tests, ou un langage (node, python…)
345
367
  skipped() {
346
368
  local v
347
369
  cfg_r skip
@@ -50,6 +50,7 @@ _msg_en() {
50
50
  cc.refs_prompt_ticket) _M="References (\"-\" for none) [%s]: " ;;
51
51
  cc.refs_prompt) _M="References (e.g. %s; Enter for none): " ;;
52
52
  tk.cli.no_ticket) _M="Branch %s has no ticket: this work will not be linked to any ticket (not blocking)." ;;
53
+ tk.cli.explain) _M="Ticket: the GitHub issue each change starts from (branch type/<n>-topic, PR citing it); its lifecycle: https://simdevwarden.github.io/repowarden/en/tickets/" ;;
53
54
  tk.cli.propose) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\"." ;;
54
55
  tk.cli.propose_cc) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\" (or answer yes in git cc)." ;;
55
56
  tk.cli.silence) _M="Project without ticket tracking? git config repowarden.skip tickets to stop seeing this." ;;
@@ -62,6 +63,7 @@ _msg_en() {
62
63
  tk.cli.issue_created) _M="Ticket #%s created, awaiting validation. Its branch will be created on validation; then: repowarden ticket %s" ;;
63
64
  tk.cli.issue_created_branch) _M="Ticket #%s created." ;;
64
65
  tk.cli.title_required) _M="Ticket title required: repowarden ticket nouveau \"<title>\"" ;;
66
+ tk.cli.bad_type) _M="Unknown ticket type: %s (types: %s)" ;;
65
67
  tk.cli.current) _M="Branch %s: ticket #%s." ;;
66
68
  tk.cli.no_ticket_here) _M="No ticket for this branch." ;;
67
69
  cc.ticket_offer) _M="No ticket for this branch. Create one now (title: the description)? [y/N]: " ;;
@@ -130,7 +132,6 @@ _msg_en() {
130
132
  # Hooks and language plugins
131
133
  hook.pre_commit.1) _M="Commit refused: branch name does not follow the convention." ;;
132
134
  hook.pre_commit.2) _M="Direct commit on '%s' forbidden. Create a branch: git switch -c feat/my-feature" ;;
133
- hook.pre_commit.3) _M=" (disable for this repository: git config repowarden.skip protect-branch)" ;;
134
135
  hook.pre_commit.4) _M="Secret detection (gitleaks)" ;;
135
136
  hook.pre_commit.5) _M="Secret detected: commit refused." ;;
136
137
  hook.secrets.where) _M=" %s:%s (%s)" ;;
@@ -141,7 +142,9 @@ _msg_en() {
141
142
  hook.secrets.todo_3) _M=" 3. Key that may have leaked elsewhere (already pushed, shared): revoke it at its provider." ;;
142
143
  hook.secrets.todo_4) _M=" False positive: add its fingerprint to .gitleaksignore:" ;;
143
144
  hook.secrets.failed) _M="gitleaks failed (code %s): commit refused, secret detection not done." ;;
144
- hook.pre_commit.6) _M="gitleaks not installed, secret detection skipped (install: %s)." ;;
145
+ hook.pre_commit.6) _M="gitleaks not installed: secrets not checked before the commit, the CI (secrets check) will do it. Install: %s" ;;
146
+ hook.sensitive.found) _M="Sensitive file neither tracked nor ignored: git add . would publish it. Lines to add to .gitignore:" ;;
147
+ hook.sensitive.hint) _M=" (names compared, content never read; patterns: repowarden.sensitiveFiles; disable: git config repowarden.skip sensitive-files)" ;;
145
148
  hook.pre_commit.7) _M="Pre-commit OK" ;;
146
149
  hook.commit_msg.1) _M="First line too long (%s > 72 characters)." ;;
147
150
  hook.commit_msg.type_unknown) _M="Unknown type \"%s\". Did you mean: %s" ;;
@@ -149,13 +152,16 @@ _msg_en() {
149
152
  hook.commit_msg.desc_empty) _M="Missing description after \"%s\": describe the change in the imperative mood." ;;
150
153
  hook.commit_msg.kept) _M=" Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG" ;;
151
154
  hook.commit_msg.dot) _M="Final period removed from the description (same rule as git cc)." ;;
155
+ hook.commit_msg.detached) _M="👉 Detached HEAD (bisect, trial): no prefix comes from a branch, write the full header (e.g. fix: correct the total)." ;;
152
156
  hook.pre_push.1) _M="Push refused: branch name does not follow the convention." ;;
153
157
  hook.pre_push.2) _M="Direct push to '%s' refused: it only changes through pull requests." ;;
154
158
  hook.pre_push.3) _M=" Create a branch: git switch -c feat/my-feature, then open a PR." ;;
155
- hook.pre_push.4) _M=" (disable for this repository: git config repowarden.skip protect-branch)" ;;
156
159
  hook.pre_push.5) _M="%s: no changed file, nothing to test." ;;
157
160
  hook.pre_push.6) _M="Nothing to test (branch deletion only)." ;;
158
161
  hook.pre_push.7) _M="Pre-push OK" ;;
162
+ hook.pre_push.msg) _M="Push refused: commit message does not follow the convention (same rule as CI)." ;;
163
+ hook.pre_push.msg_fix) _M=" Fix: git rebase -i %s (reword), then push again." ;;
164
+ hook.pre_push.nothing) _M="Nothing to push." ;;
159
165
  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" ;;
160
166
  hook.pre_push.lint.missing) _M="%s not installed, %s not checked (install: %s)." ;;
161
167
  hook.pre_push.lint.run) _M="%s: %s" ;;
@@ -486,6 +492,10 @@ _msg_en() {
486
492
  st.repo_version_ok) _M="Version required by the project (%s): met" ;;
487
493
  st.repo_forge) _M="Platform: %s" ;;
488
494
  st.repo_rules) _M="GitHub rules up to date? repowarden proteger --verifier (read-only)" ;;
495
+ st.wf_owner) _M="%s:%s: repowarden called from another repository than the official one (%s); expected: %s" ;;
496
+ st.wf_branch) _M="%s:%s: repowarden called on a branch (@%s), broken as soon as it is deleted; expected: %s" ;;
497
+ st.wf_old) _M="%s:%s: repowarden called in an outdated major version (@%s); expected: %s" ;;
498
+ st.wf_fix) _M="fix the uses: lines reported above" ;;
489
499
  st.tools) _M="Tools present: %s · missing: %s (depending on the projects' languages)" ;;
490
500
  st.next) _M="Next step:" ;;
491
501
  st.ready) _M="All set: git cc to commit, repowarden code-mort on a branch." ;;
@@ -541,6 +551,7 @@ _msg_en() {
541
551
  bot.ignored) _M="ignored (bot = off)" ;;
542
552
  bot.mode_invalid) _M="bot = %s: auto, pr or off expected" ;;
543
553
  bot.clone_failed) _M="clone failed: %s" ;;
554
+ bot.email_fallback) _M="Bot user %s not found: commits signed %s, linked to no account (set REPOWARDEN_BOT_EMAIL to fix the address)." ;;
544
555
  bot.prot_ok) _M="up to date" ;;
545
556
  bot.prot_stale) _M="⚠ outdated: repowarden proteger" ;;
546
557
  bot.prot_applied) _M="✔ applied" ;;
@@ -654,6 +665,7 @@ _msg_en() {
654
665
  tk.init_next_wired) _M="Next: commit these files; require the \"ticket\" check (repowarden proteger --checks \"…,ticket\"). The tickets job of release.yml is already wired." ;;
655
666
  tk.prerelease) _M="Available in pre-release **%s**: to be tested before production." ;;
656
667
  tk.released) _M="Released in **%s**." ;;
668
+ tk.delivered) _M="Delivered to **%s**, without a new version (commit %s)." ;;
657
669
  # Ticket comment
658
670
  tk.comment.adding) _M="Adding comment to ticket #%s..." ;;
659
671
  tk.comment.added) _M="Comment added to ticket #%s." ;;
@@ -50,6 +50,7 @@ _msg_fr() {
50
50
  cc.refs_prompt_ticket) _M="Références (« - » pour aucune) [%s] : " ;;
51
51
  cc.refs_prompt) _M="Références (ex. %s ; Entrée pour aucune) : " ;;
52
52
  tk.cli.no_ticket) _M="Branche %s sans ticket : ce travail ne sera relié à aucun ticket (non bloquant)." ;;
53
+ tk.cli.explain) _M="Ticket : issue GitHub d'où part chaque modification (branche type/<n°>-sujet, PR qui la cite) ; son cycle : https://simdevwarden.github.io/repowarden/tickets/" ;;
53
54
  tk.cli.propose) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\"." ;;
54
55
  tk.cli.propose_cc) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\" (ou réponds oui à git cc)." ;;
55
56
  tk.cli.silence) _M="Projet sans suivi de tickets ? git config repowarden.skip tickets pour ne plus le voir." ;;
@@ -62,6 +63,7 @@ _msg_fr() {
62
63
  tk.cli.issue_created) _M="Ticket #%s créé, à valider. Sa branche sera créée à la validation ; ensuite : repowarden ticket %s" ;;
63
64
  tk.cli.issue_created_branch) _M="Ticket #%s créé." ;;
64
65
  tk.cli.title_required) _M="Titre du ticket obligatoire : repowarden ticket nouveau \"<titre>\"" ;;
66
+ tk.cli.bad_type) _M="Type de ticket inconnu : %s (types : %s)" ;;
65
67
  tk.cli.current) _M="Branche %s : ticket #%s." ;;
66
68
  tk.cli.no_ticket_here) _M="Aucun ticket pour cette branche." ;;
67
69
  cc.ticket_offer) _M="Aucun ticket pour cette branche. En créer un maintenant (titre : la description) ? [o/N] : " ;;
@@ -109,7 +111,6 @@ _msg_fr() {
109
111
  # Hooks et plugins de langage
110
112
  hook.pre_commit.1) _M="Commit refusé : nom de branche non conforme." ;;
111
113
  hook.pre_commit.2) _M="Commit direct sur '%s' interdit. Crée une branche : git switch -c feat/ma-feature" ;;
112
- hook.pre_commit.3) _M=" (désactiver pour ce dépôt : git config repowarden.skip protect-branch)" ;;
113
114
  hook.pre_commit.4) _M="Détection de secrets (gitleaks)" ;;
114
115
  hook.pre_commit.5) _M="Secret détecté : commit refusé." ;;
115
116
  hook.secrets.where) _M=" %s:%s (%s)" ;;
@@ -120,7 +121,9 @@ _msg_fr() {
120
121
  hook.secrets.todo_3) _M=" 3. Clé qui a pu fuir ailleurs (déjà poussée, partagée) : la révoquer chez son fournisseur." ;;
121
122
  hook.secrets.todo_4) _M=" Faux positif : ajouter son empreinte à .gitleaksignore :" ;;
122
123
  hook.secrets.failed) _M="gitleaks en échec (code %s) : commit refusé, détection de secrets non faite." ;;
123
- hook.pre_commit.6) _M="gitleaks absent, détection de secrets ignorée (installation : %s)." ;;
124
+ hook.pre_commit.6) _M="gitleaks absent : secrets non vérifiés avant le commit, la CI (vérification secrets) fera le contrôle. Installation : %s" ;;
125
+ hook.sensitive.found) _M="Fichier sensible ni suivi ni ignoré : un git add . le publierait. Lignes à ajouter à .gitignore :" ;;
126
+ hook.sensitive.hint) _M=" (noms comparés, contenu jamais lu ; motifs : repowarden.sensitiveFiles ; désactiver : git config repowarden.skip sensitive-files)" ;;
124
127
  hook.pre_commit.7) _M="Pre-commit OK" ;;
125
128
  hook.commit_msg.1) _M="Première ligne trop longue (%s > 72 caractères)." ;;
126
129
  hook.commit_msg.type_unknown) _M="Type « %s » inconnu. Voulais-tu : %s" ;;
@@ -128,13 +131,16 @@ _msg_fr() {
128
131
  hook.commit_msg.desc_empty) _M="Description manquante après « %s » : décris le changement à l'impératif." ;;
129
132
  hook.commit_msg.kept) _M=" Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG" ;;
130
133
  hook.commit_msg.dot) _M="Point final retiré de la description (même règle que git cc)." ;;
134
+ hook.commit_msg.detached) _M="👉 HEAD détachée (bisect, essai) : aucun préfixe ne vient d'une branche, écris l'en-tête complet (ex. fix: corrige le calcul)." ;;
131
135
  hook.pre_push.1) _M="Push refusé : nom de branche non conforme." ;;
132
136
  hook.pre_push.2) _M="Push direct refusé sur '%s' : elle n'évolue que par Pull Request." ;;
133
137
  hook.pre_push.3) _M=" Crée une branche : git switch -c feat/ma-feature, puis ouvre une PR." ;;
134
- hook.pre_push.4) _M=" (désactiver pour ce dépôt : git config repowarden.skip protect-branch)" ;;
135
138
  hook.pre_push.5) _M="%s : aucun fichier modifié, rien à tester." ;;
136
139
  hook.pre_push.6) _M="Rien à tester (suppression de branche uniquement)." ;;
137
140
  hook.pre_push.7) _M="Pre-push OK" ;;
141
+ hook.pre_push.msg) _M="Push refusé : message de commit non conforme (même règle que la CI)." ;;
142
+ hook.pre_push.msg_fix) _M=" Corriger : git rebase -i %s (reword), puis relancer le push." ;;
143
+ hook.pre_push.nothing) _M="Rien à pousser." ;;
138
144
  hook.pre_push.workflow_scope) _M="Jeton gh sans le scope « workflow » : GitHub refusera un push touchant .github/workflows/. L'autoriser (puis se reconnecter) : gh auth refresh -h github.com -s workflow" ;;
139
145
  hook.pre_push.lint.missing) _M="%s absent, %s non vérifié (installation : %s)." ;;
140
146
  hook.pre_push.lint.run) _M="%s : %s" ;;
@@ -487,6 +493,10 @@ _msg_fr() {
487
493
  st.repo_version_ok) _M="Version demandée par le projet (%s) : respectée" ;;
488
494
  st.repo_forge) _M="Plateforme : %s" ;;
489
495
  st.repo_rules) _M="Règles GitHub à jour ? repowarden proteger --verifier (lecture seule)" ;;
496
+ st.wf_owner) _M="%s:%s : repowarden appelé depuis un autre dépôt que le dépôt officiel (%s) ; attendu : %s" ;;
497
+ st.wf_branch) _M="%s:%s : repowarden appelé sur une branche (@%s), cassé dès qu'elle disparaît ; attendu : %s" ;;
498
+ st.wf_old) _M="%s:%s : repowarden appelé dans une majeure dépassée (@%s) ; attendu : %s" ;;
499
+ st.wf_fix) _M="corriger les lignes uses: signalées ci-dessus" ;;
490
500
  st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
491
501
  st.next) _M="Étape suivante :" ;;
492
502
  st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
@@ -542,6 +552,7 @@ _msg_fr() {
542
552
  bot.ignored) _M="ignoré (bot = off)" ;;
543
553
  bot.mode_invalid) _M="bot = %s : auto, pr ou off attendu" ;;
544
554
  bot.clone_failed) _M="clone impossible : %s" ;;
555
+ bot.email_fallback) _M="Utilisateur bot %s introuvable : commits signés %s, rattachés à aucun compte (REPOWARDEN_BOT_EMAIL pour fixer l'adresse)." ;;
545
556
  bot.prot_ok) _M="à jour" ;;
546
557
  bot.prot_stale) _M="⚠ périmée : repowarden proteger" ;;
547
558
  bot.prot_applied) _M="✔ appliquée" ;;
@@ -655,6 +666,7 @@ _msg_fr() {
655
666
  tk.init_next_wired) _M="Ensuite : committer ces fichiers ; exiger la vérification « ticket » (repowarden proteger --checks \"…,ticket\"). Le job tickets de release.yml est déjà branché." ;;
656
667
  tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
657
668
  tk.released) _M="Publié dans **%s**." ;;
669
+ tk.delivered) _M="Livré sur **%s**, sans nouvelle version (commit %s)." ;;
658
670
  # Ticket comment
659
671
  tk.comment.adding) _M="Ajout d'un commentaire sur le ticket #%s..." ;;
660
672
  tk.comment.added) _M="Commentaire ajouté au ticket #%s." ;;
package/hooks/lib/lang.sh CHANGED
@@ -300,8 +300,11 @@ projects_for_files() {
300
300
  }
301
301
 
302
302
  # Lance les tests de chaque projet touché. Lecture des fichiers modifiés sur stdin.
303
+ # $1 = resume (pre-push) : sortie de chaque lanceur gardée de côté ; succès →
304
+ # sa dernière ligne seule (« 2 passed in 0.5s »), échec → sortie complète. Sans
305
+ # argument (CI) : sortie complète, le journal sert au diagnostic.
303
306
  test_projects() {
304
- local cmd dir p plugins key ran=0
307
+ local mode="${1:-}" cmd dir p plugins key ran=0 out="" line last rc
305
308
  cmd="$(cfg test)"
306
309
  if [ -n "$cmd" ]; then
307
310
  # Fichiers modifiés transmis sur l'entrée standard : la commande peut
@@ -326,13 +329,31 @@ test_projects() {
326
329
  ran=1
327
330
  if [ "$dir" = . ]; then key="$p"; else key="$p ($dir)"; fi
328
331
  info_t hook.lang.5 "$key"
329
- if ! (cd "$dir" && "${p}_test") </dev/null; then
332
+ if [ "$mode" != resume ]; then
333
+ if ! (cd "$dir" && "${p}_test") </dev/null; then
334
+ err_t hook.lang.6 "$key" >&2
335
+ return 1
336
+ fi
337
+ continue
338
+ fi
339
+ [ -n "$out" ] || out="$(mktemp)"
340
+ rc=0
341
+ (cd "$dir" && "${p}_test") </dev/null >"$out" 2>&1 || rc=$?
342
+ if [ "$rc" -ne 0 ]; then
343
+ cat "$out"
344
+ rm -f "$out"
330
345
  err_t hook.lang.6 "$key" >&2
331
346
  return 1
332
347
  fi
348
+ last=""
349
+ while IFS= read -r line || [ -n "$line" ]; do
350
+ [ -n "${line//[[:space:]]/}" ] && last="$line"
351
+ done <"$out"
352
+ [ -z "$last" ] || printf ' %s\n' "$last"
333
353
  done
334
354
  done < <(projects_for_files)
335
355
 
356
+ [ -z "$out" ] || rm -f "$out"
336
357
  [ "$ran" = 1 ] || info_t hook.lang.7
337
358
  return 0
338
359
  }
@@ -47,3 +47,55 @@ secrets_report() {
47
47
  printf '%s' "$empreintes"
48
48
  fi
49
49
  }
50
+
51
+ # REPLY = commande d'installation de gitleaks pour ce système (bash le connaît
52
+ # sans sous-processus)
53
+ gitleaks_install_r() {
54
+ case "${OSTYPE:-}" in
55
+ darwin*) REPLY="brew install gitleaks" ;;
56
+ msys* | cygwin* | win*) REPLY="winget install gitleaks" ;;
57
+ *) REPLY="https://github.com/gitleaks/gitleaks#installing" ;;
58
+ esac
59
+ }
60
+
61
+ # Noms de fichiers sensibles par défaut (réglage repowarden.sensitiveFiles)
62
+ SENSITIVE_FILES_DEFAULT='.env .env.* *token* *secret* *.pem *.key id_rsa*'
63
+
64
+ # REPLY = fichiers non suivis et non ignorés (.gitignore, .git/info/exclude,
65
+ # exclusion globale) au nom sensible, un par ligne depuis la racine : un
66
+ # « git add . » les publierait, et gitleaks ne les lit qu'une fois indexés.
67
+ # Seul le nom est comparé (sans casse) : le contenu n'est jamais lu.
68
+ # .env.example, modèle fait pour être versionné, n'est jamais signalé.
69
+ sensitive_files_r() {
70
+ local f nom m trouves="" nocase=""
71
+ local -a motifs=()
72
+ cfg_r sensitiveFiles "$SENSITIVE_FILES_DEFAULT"
73
+ # read -a : motifs découpés sans être développés sur les fichiers présents
74
+ read -r -a motifs <<<"${REPLY//,/ }"
75
+ shopt -q nocasematch && nocase=1
76
+ shopt -s nocasematch
77
+ while IFS= read -r -d '' f; do
78
+ nom="${f##*/}"
79
+ [[ "$nom" != .env.example ]] || continue
80
+ for m in ${motifs[@]+"${motifs[@]}"}; do
81
+ # shellcheck disable=SC2053 # $m est un motif
82
+ if [[ "$nom" == $m ]]; then
83
+ trouves="$trouves${trouves:+$'\n'}$f"
84
+ break
85
+ fi
86
+ done
87
+ done < <(git -C "$GIT_TOPLEVEL" ls-files -z --others --exclude-standard 2>/dev/null)
88
+ [ -n "$nocase" ] || shopt -u nocasematch
89
+ REPLY="$trouves"
90
+ }
91
+
92
+ # Avertit, sans bloquer, des fichiers sensibles $1 (un par ligne, sortie de
93
+ # sensitive_files_r) avec les lignes à ajouter à .gitignore
94
+ sensitive_files_warn() {
95
+ local f
96
+ warn_t hook.sensitive.found
97
+ while IFS= read -r f; do
98
+ echo " $f"
99
+ done <<<"$1"
100
+ echo_t hook.sensitive.hint
101
+ }
@@ -12,6 +12,10 @@ S_ENCOURS="statut: en cours"
12
12
  S_RELECTURE="statut: en relecture"
13
13
  S_PREPROD="statut: préprod"
14
14
  S_DONE="statut: done"
15
+ # Posée à l'ouverture : pas de validation d'office, un mainteneur décide
16
+ A_DECIDER="à décider"
17
+ # Types de ticket (étiquettes « type: … », préfixe de la branche)
18
+ TYPES_TICKET="feat fix docs refactor chore"
15
19
 
16
20
  # Vrai si les étiquettes $1 (une par ligne) disent le travail terminé : mergé
17
21
  # (préprod) ou en production (done). Un tel ticket n'a plus de branche à créer.
@@ -171,6 +175,7 @@ ticket_hint() {
171
175
  branch_ticket_r "$branch"
172
176
  [ -z "$REPLY" ] || return 0
173
177
  attention_t tk.cli.no_ticket "$branch"
178
+ dim_t tk.cli.explain
174
179
  if tickets_managed && has gh; then
175
180
  # git cc ne propose de créer le ticket qu'avec ticketAutoCreate
176
181
  if ticket_auto_create; then dim_t tk.cli.propose_cc; else dim_t tk.cli.propose; fi
package/hooks/lib/ui.sh CHANGED
@@ -56,5 +56,17 @@ repowarden_version_r() {
56
56
  fi
57
57
  }
58
58
 
59
+ # REPLY = version majeure dont le tag flottant (vN) est publié, pour la
60
+ # version $1 de repowarden : sa majeure ; préversion d'une nouvelle majeure
61
+ # (X.0.0-next) : X-1, vX n'existant pas encore ; 0.0.0-dev (clone sans
62
+ # tags) : vide. REPOWARDEN_MAJEURE l'impose (tests, clone sans tags).
63
+ repowarden_majeure_r() {
64
+ if [ -n "${REPOWARDEN_MAJEURE:-}" ]; then REPLY="$REPOWARDEN_MAJEURE"; return; fi
65
+ if [ -z "$1" ] || [ "$1" = 0.0.0-dev ]; then REPLY=""; return; fi
66
+ REPLY="${1%%.*}"
67
+ REPLY="${REPLY%%-*}"
68
+ [[ "$1" != *.0.0-* ]] || REPLY=$((REPLY - 1))
69
+ }
70
+
59
71
  # En-tête de marque
60
72
  brand() { echo "${UI_C}${UI_B}◆ repowarden${UI_N}${UI_D}${1:+ · $1}${UI_N}"; }
package/hooks/pre-commit CHANGED
@@ -26,7 +26,6 @@ fi
26
26
  PROTECTED="$(cfg protectedBranches "main master")"
27
27
  if ! skipped protect-branch && [[ " $PROTECTED " == *" $BRANCH "* ]] && git rev-parse --verify -q HEAD >/dev/null; then
28
28
  err_t hook.pre_commit.2 "$BRANCH" >&2
29
- echo_t hook.pre_commit.3 >&2
30
29
  exit 1
31
30
  fi
32
31
 
@@ -55,15 +54,18 @@ if ! skipped secrets; then
55
54
  fi
56
55
  rm -f "$rapport" "$journal"
57
56
  else
58
- # Installation adaptée au système (bash le connaît sans sous-processus)
59
- case "${OSTYPE:-}" in
60
- darwin*) warn_t hook.pre_commit.6 "brew install gitleaks" ;;
61
- msys* | cygwin* | win*) warn_t hook.pre_commit.6 "winget install gitleaks" ;;
62
- *) warn_t hook.pre_commit.6 "https://github.com/gitleaks/gitleaks#installing" ;;
63
- esac
57
+ gitleaks_install_r
58
+ warn_t hook.pre_commit.6 "$REPLY"
64
59
  fi
65
60
  fi
66
61
 
62
+ # 2 bis. Fichier sensible ni suivi ni ignoré : un « git add . » le publierait
63
+ # (avertissement seulement, contenu jamais lu)
64
+ if ! skipped secrets && ! skipped sensitive-files; then
65
+ sensitive_files_r
66
+ [ -z "$REPLY" ] || sensitive_files_warn "$REPLY" >&2
67
+ fi
68
+
67
69
  # 3. Formatage : chaque fichier stagé avec l'outil de son langage / projet
68
70
  if ! skipped format; then
69
71
  # Ne formater (et re-stager) que le contenu stagé, jamais le travail en cours
package/hooks/pre-push CHANGED
@@ -41,13 +41,49 @@ if ! skipped protect-branch; then
41
41
  [[ "$remote_sha" =~ ^0+$ ]] && continue
42
42
  err_t hook.pre_push.2 "$branch" >&2
43
43
  echo_t hook.pre_push.3 >&2
44
- echo_t hook.pre_push.4 >&2
45
44
  exit 1
46
45
  done <<<"$REFS"
47
46
  fi
48
47
 
49
48
  REMOTE="${1:-origin}"
50
49
 
50
+ # Messages des commits poussés : même règle et mêmes messages que la CI, pour
51
+ # un commit créé hors hooks (autre outil, rebase sans hooks, --no-verify).
52
+ # Seuls les commits absents du remote sont vus : l'historique déjà poussé
53
+ # n'est pas rejugé.
54
+ if ! skipped commit-msg; then
55
+ MSG_BAD=""
56
+ while read -r _ local_sha remote_ref remote_sha; do
57
+ [[ "$remote_ref" == refs/heads/* ]] || continue
58
+ [[ "$local_sha" =~ ^0+$ ]] && continue
59
+ if [[ ! "$remote_sha" =~ ^0+$ ]] && git cat-file -e "$remote_sha^{commit}" 2>/dev/null; then
60
+ plage=("$remote_sha..$local_sha")
61
+ else
62
+ plage=("$local_sha" --not --remotes="$REMOTE")
63
+ fi
64
+ # Séparateur 0x1f (non blanc) : un champ vide ne décale pas les suivants
65
+ while IFS=$'\x1f' read -r sha email subject; do
66
+ [ -n "$sha" ] || continue
67
+ commit_subject_problem_r "$subject" "$email"
68
+ [ -n "$REPLY" ] || continue
69
+ [ -n "$MSG_BAD" ] || err_t hook.pre_push.msg
70
+ # git log va du plus récent au plus ancien : MSG_BAD finit sur le
71
+ # plus ancien commit fautif, point de départ de la correction
72
+ MSG_BAD="$sha"
73
+ _tr "$REPLY" "${sha:0:7}" "$subject"
74
+ echo " $_T" >&2
75
+ done < <(git log --no-merges --format='%H%x1f%ae%x1f%s' "${plage[@]}")
76
+ done <<<"$REFS"
77
+ if [ -n "$MSG_BAD" ]; then
78
+ if git rev-parse -q --verify "$MSG_BAD^" >/dev/null; then
79
+ echo_t hook.pre_push.msg_fix "${MSG_BAD:0:7}^" >&2
80
+ else
81
+ echo_t hook.pre_push.msg_fix "--root" >&2
82
+ fi
83
+ exit 1
84
+ fi
85
+ fi
86
+
51
87
  # Fichiers modifiés par le push de <sha> : depuis l'état distant connu, ou pour
52
88
  # une nouvelle branche depuis le dernier commit déjà présent sur le remote.
53
89
  changed_files() {
@@ -177,7 +213,8 @@ fi
177
213
 
178
214
  skipped tests && { info "Tests désactivés (repowarden.skip)."; exit 0; }
179
215
 
180
- run_tests_for() { test_projects <"$1"; }
216
+ # Succès : une ligne de résumé par projet ; échec : sortie complète
217
+ run_tests_for() { test_projects resume <"$1"; }
181
218
 
182
219
  TESTED=""
183
220
  CHANGED="$(git rev-parse --absolute-git-dir)/repowarden-changed-files"
@@ -196,5 +233,10 @@ while read -r _ local_sha _ remote_sha; do
196
233
  done <<<"$REFS"
197
234
 
198
235
  rm -f "$CHANGED"
199
- [ -n "$TESTED" ] || info_t hook.pre_push.6
236
+ # Entrée vide : git n'a rien à envoyer (« Everything up-to-date »)
237
+ if [ -z "$REFS" ]; then
238
+ info_t hook.pre_push.nothing
239
+ elif [ -z "$TESTED" ]; then
240
+ info_t hook.pre_push.6
241
+ fi
200
242
  ok_t hook.pre_push.7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowarden",
3
- "version": "4.10.2",
3
+ "version": "4.11.0-next.13",
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",