repowarden 4.10.2 → 4.11.0-next.21

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.
@@ -50,11 +50,13 @@ _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." ;;
56
57
  tk.cli.fetch) _M="Branche du ticket #%s…" ;;
57
58
  tk.cli.switched) _M="Sur la branche %s (ticket #%s)." ;;
59
+ tk.cli.unlinked) _M="Branche %s créée avant la validation : GitHub ne la lie pas au ticket #%s après coup, son lien est inscrit sur le ticket." ;;
58
60
  tk.cli.created_branch) _M="Branche %s créée depuis %s, liée au ticket #%s." ;;
59
61
  tk.cli.not_validated) _M="Ticket #%s pas encore validé : sa branche sera créée automatiquement à la validation (étiquette « %s »)." ;;
60
62
  tk.cli.closed) _M="Ticket #%s fermé : pas de branche à créer." ;;
@@ -62,6 +64,7 @@ _msg_fr() {
62
64
  tk.cli.issue_created) _M="Ticket #%s créé, à valider. Sa branche sera créée à la validation ; ensuite : repowarden ticket %s" ;;
63
65
  tk.cli.issue_created_branch) _M="Ticket #%s créé." ;;
64
66
  tk.cli.title_required) _M="Titre du ticket obligatoire : repowarden ticket nouveau \"<titre>\"" ;;
67
+ tk.cli.bad_type) _M="Type de ticket inconnu : %s (types : %s)" ;;
65
68
  tk.cli.current) _M="Branche %s : ticket #%s." ;;
66
69
  tk.cli.no_ticket_here) _M="Aucun ticket pour cette branche." ;;
67
70
  cc.ticket_offer) _M="Aucun ticket pour cette branche. En créer un maintenant (titre : la description) ? [o/N] : " ;;
@@ -109,7 +112,6 @@ _msg_fr() {
109
112
  # Hooks et plugins de langage
110
113
  hook.pre_commit.1) _M="Commit refusé : nom de branche non conforme." ;;
111
114
  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
115
  hook.pre_commit.4) _M="Détection de secrets (gitleaks)" ;;
114
116
  hook.pre_commit.5) _M="Secret détecté : commit refusé." ;;
115
117
  hook.secrets.where) _M=" %s:%s (%s)" ;;
@@ -120,7 +122,9 @@ _msg_fr() {
120
122
  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
123
  hook.secrets.todo_4) _M=" Faux positif : ajouter son empreinte à .gitleaksignore :" ;;
122
124
  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)." ;;
125
+ 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" ;;
126
+ hook.sensitive.found) _M="Fichier sensible ni suivi ni ignoré : un git add . le publierait. Lignes à ajouter à .gitignore :" ;;
127
+ hook.sensitive.hint) _M=" (noms comparés, contenu jamais lu ; motifs : repowarden.sensitiveFiles ; désactiver : git config repowarden.skip sensitive-files)" ;;
124
128
  hook.pre_commit.7) _M="Pre-commit OK" ;;
125
129
  hook.commit_msg.1) _M="Première ligne trop longue (%s > 72 caractères)." ;;
126
130
  hook.commit_msg.type_unknown) _M="Type « %s » inconnu. Voulais-tu : %s" ;;
@@ -128,13 +132,16 @@ _msg_fr() {
128
132
  hook.commit_msg.desc_empty) _M="Description manquante après « %s » : décris le changement à l'impératif." ;;
129
133
  hook.commit_msg.kept) _M=" Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG" ;;
130
134
  hook.commit_msg.dot) _M="Point final retiré de la description (même règle que git cc)." ;;
135
+ 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
136
  hook.pre_push.1) _M="Push refusé : nom de branche non conforme." ;;
132
137
  hook.pre_push.2) _M="Push direct refusé sur '%s' : elle n'évolue que par Pull Request." ;;
133
138
  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
139
  hook.pre_push.5) _M="%s : aucun fichier modifié, rien à tester." ;;
136
140
  hook.pre_push.6) _M="Rien à tester (suppression de branche uniquement)." ;;
137
141
  hook.pre_push.7) _M="Pre-push OK" ;;
142
+ hook.pre_push.msg) _M="Push refusé : message de commit non conforme (même règle que la CI)." ;;
143
+ hook.pre_push.msg_fix) _M=" Corriger : git rebase -i %s (reword), puis relancer le push." ;;
144
+ hook.pre_push.nothing) _M="Rien à pousser." ;;
138
145
  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
146
  hook.pre_push.lint.missing) _M="%s absent, %s non vérifié (installation : %s)." ;;
140
147
  hook.pre_push.lint.run) _M="%s : %s" ;;
@@ -238,7 +245,7 @@ _msg_fr() {
238
245
  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." ;;
239
246
  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)." ;;
240
247
  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)." ;;
241
- help.pr) _M="Usage : repowarden pr label <n> <label> [--remove]\n repowarden pr checks <n>\n\nlabel ajoute ou retire un label sur la PR #<n> ; checks affiche l'état de\nses checks (nom, conclusion, statut, lien).\nExemples :\n repowarden pr label 123 \"ready-for-review\"\n repowarden pr label 123 \"needs-changes\" --remove\n repowarden pr checks 123" ;;
248
+ help.pr) _M="Usage : repowarden pr label <n> <label> [--remove]\n repowarden pr checks <n>\n\nlabel ajoute ou retire un label sur la PR #<n> ; checks liste ses jobs en\néchec (avec leur lien) et en attente, puis le verdict ; code 1 si un job échoue.\nExemples :\n repowarden pr label 123 \"ready-for-review\"\n repowarden pr label 123 \"needs-changes\" --remove\n repowarden pr checks 123" ;;
242
249
  help.run) _M="Usage : repowarden run rerun <run-id|--failed|--workflow <nom>>\n repowarden run list [--workflow <nom>] [--limit N]\n\nrerun relance un workflow GitHub Actions : un run par son id, le dernier\néchoué (--failed) ou le dernier d'un workflow (--workflow) ; list affiche\nles runs récents (--limit, défaut 20).\nExemples :\n repowarden run rerun --failed\n repowarden run list --workflow \"CI\" --limit 10" ;;
243
250
  help.proteger) _M="Usage : repowarden proteger [--repo propriétaire/nom] [--checks \"repowarden,tests (ubuntu-latest)\"]\n [--relecteurs N] [--codeowners] [--environnement NOM] [--approbateurs \"login …\"]\n [--sans-tags] [--dry-run] [--verifier]\n\nProtège le dépôt GitHub courant d'après .repowarden.conf (relançable sans\nrisque) : PR obligatoire, vérifications exigées, ni suppression ni push forcé\n(ruleset « repowarden »), merge squash, tags v* réservés aux workflows,\nvalidation humaine optionnelle. --verifier compare les règles en ligne sans\nrien modifier. Réglages équivalents dans .repowarden.conf. Demande gh et les\ndroits d'administration du dépôt." ;;
244
251
  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/" ;;
@@ -288,6 +295,11 @@ _msg_fr() {
288
295
  ci.fix_pr.2) _M="PR #%s reciblée : %s → %s (flux %s)" ;;
289
296
  ci.fix_pr.3) _M="Titre conforme : %s" ;;
290
297
  ci.fix_pr.4) _M="Titre « %s » remplacé par « %s »" ;;
298
+ ci.verrous_pip.compile) _M="Verrou %s : commande de son en-tête relancée" ;;
299
+ ci.verrous_pip.refused) _M="Verrou %s ignoré : la sortie de la commande de son en-tête n'est pas ce fichier" ;;
300
+ ci.verrous_pip.compliant) _M="Verrous pip conformes à leur en-tête : aucun commit" ;;
301
+ ci.verrous_pip.committed) _M="Verrous régénérés et commités : %s" ;;
302
+ ci.verrous_pip.pushed) _M="Commit poussé sur %s" ;;
291
303
  ci.version.1) _M="version annoncée par la PR de livraison" ;;
292
304
  ci.version.2) _M="Aucun feat, fix, perf ni changement incompatible depuis %s : pas de version." ;;
293
305
  ci.version.3) _M="première version" ;;
@@ -421,6 +433,12 @@ _msg_fr() {
421
433
  dc.knip_file) _M="fichier jamais importé" ;;
422
434
  dc.knip_export) _M="export jamais importé :" ;;
423
435
  dc.knip_dep) _M="dépendance jamais importée :" ;;
436
+ # Fonctions ajoutées sans commentaire
437
+ cm.section) _M="Fonctions sans commentaire (nouveau code uniquement)" ;;
438
+ cm.no_base) _M="Pas de base : contrôle des commentaires ignoré." ;;
439
+ cm.missing) _M="%s : fonction %s sans commentaire juste au-dessus" ;;
440
+ 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)." ;;
441
+ cm.none) _M="Chaque fonction ajoutée est commentée." ;;
424
442
  ci.section.commits) _M="Messages de commit (Conventional Commits)" ;;
425
443
  ci.section.branch) _M="Nom de branche" ;;
426
444
  ci.section.secrets) _M="Secrets (gitleaks)" ;;
@@ -487,6 +505,10 @@ _msg_fr() {
487
505
  st.repo_version_ok) _M="Version demandée par le projet (%s) : respectée" ;;
488
506
  st.repo_forge) _M="Plateforme : %s" ;;
489
507
  st.repo_rules) _M="Règles GitHub à jour ? repowarden proteger --verifier (lecture seule)" ;;
508
+ st.wf_owner) _M="%s:%s : repowarden appelé depuis un autre dépôt que le dépôt officiel (%s) ; attendu : %s" ;;
509
+ st.wf_branch) _M="%s:%s : repowarden appelé sur une branche (@%s), cassé dès qu'elle disparaît ; attendu : %s" ;;
510
+ st.wf_old) _M="%s:%s : repowarden appelé dans une majeure dépassée (@%s) ; attendu : %s" ;;
511
+ st.wf_fix) _M="corriger les lignes uses: signalées ci-dessus" ;;
490
512
  st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
491
513
  st.next) _M="Étape suivante :" ;;
492
514
  st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
@@ -542,6 +564,7 @@ _msg_fr() {
542
564
  bot.ignored) _M="ignoré (bot = off)" ;;
543
565
  bot.mode_invalid) _M="bot = %s : auto, pr ou off attendu" ;;
544
566
  bot.clone_failed) _M="clone impossible : %s" ;;
567
+ bot.email_fallback) _M="Utilisateur bot %s introuvable : commits signés %s, rattachés à aucun compte (REPOWARDEN_BOT_EMAIL pour fixer l'adresse)." ;;
545
568
  bot.prot_ok) _M="à jour" ;;
546
569
  bot.prot_stale) _M="⚠ périmée : repowarden proteger" ;;
547
570
  bot.prot_applied) _M="✔ appliquée" ;;
@@ -634,6 +657,7 @@ _msg_fr() {
634
657
  tk.pr_backmerge_empty) _M="PR fermée automatiquement : \`%s\` n'apporte aucun contenu à \`%s\`. Le retour est fait automatiquement après une release, seulement quand il y a quelque chose à rapporter." ;;
635
658
  tk.pr_delivery_manual) _M="PR fermée automatiquement : la livraison \`%s\` → \`%s\` est ouverte par le workflow de release ; une PR ouverte à la main n'aurait jamais d'approbation de déploiement. Lancer *Actions → release → Run workflow* sur \`%s\` (ou \`gh workflow run release.yml --ref %s\`), puis **Approve and deploy** sur le run." ;;
636
659
  tk.branch_registered) _M="Branche du ticket : \`%s\`." ;;
660
+ tk.branch_unlinked) _M="Branche du ticket : [\`%s\`](%s). Créée avant la validation du ticket : GitHub ne lie pas une branche existante, elle n'apparaît donc pas dans « Development » ; ce commentaire en garde le lien." ;;
637
661
  tk.branch_second) _M="Seconde branche pour ce ticket : \`%s\` (déjà : %s). Un ticket, une branche : regrouper le travail, ou ouvrir un ticket séparé." ;;
638
662
  tk.adopted) _M="Tickets existants : %s rangé(s), %s branche(s) inscrite(s)." ;;
639
663
  tk.init_adopt) _M="Tickets et branches existants" ;;
@@ -642,6 +666,8 @@ _msg_fr() {
642
666
  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." ;;
643
667
  tk.draft_opened) _M="PR #%s ouverte en brouillon pour le ticket #%s" ;;
644
668
  tk.draft_updated) _M="PR #%s (ticket #%s) : titre et description repris du ticket" ;;
669
+ tk.pr_additions) _M="Ajouts du ticket" ;;
670
+ tk.pr_design) _M="Conception (résumé)" ;;
645
671
  tk.created_body) _M="Ticket créé automatiquement pour la PR #%s, qui n'en citait aucun. À valider par un mainteneur (étiquette « validé »)." ;;
646
672
  tk.created) _M="Ticket #%s créé pour la PR #%s et relié à sa description : à valider par un mainteneur." ;;
647
673
  tk.init_labels) _M="Étiquettes (statuts et validation)" ;;
@@ -655,6 +681,8 @@ _msg_fr() {
655
681
  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
682
  tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
657
683
  tk.released) _M="Publié dans **%s**." ;;
684
+ tk.footer) _M="Message de l'App GitHub de ce dépôt ([doc repowarden](https://simdevwarden.github.io/repowarden/industrialisation/#github-app-des-workflows)) : [%s](https://github.com/apps/%s)" ;;
685
+ tk.delivered) _M="Livré sur **%s**, sans nouvelle version (commit %s)." ;;
658
686
  # Ticket comment
659
687
  tk.comment.adding) _M="Ajout d'un commentaire sur le ticket #%s..." ;;
660
688
  tk.comment.added) _M="Commentaire ajouté au ticket #%s." ;;
@@ -680,6 +708,11 @@ _msg_fr() {
680
708
  run.listing.workflow) _M="Liste des runs du workflow « %s »..." ;;
681
709
  # PR checks
682
710
  pr.checks.checking) _M="Vérification des checks de la PR #%s..." ;;
711
+ pr.checks.failed) _M="En échec : %s %s" ;;
712
+ pr.checks.pending) _M="En attente : %s" ;;
713
+ pr.checks.verdict) _M="PR #%s : %s réussi(s), %s en échec, %s en attente." ;;
714
+ pr.checks.green) _M="Tous les checks de la PR #%s sont verts." ;;
715
+ pr.checks.none) _M="Aucun check sur la PR #%s." ;;
683
716
  # Garde-fou des agents IA (repowarden garde, repowarden agents)
684
717
  garde.format) _M="Format inconnu « %s » : claude ou opencode." ;;
685
718
  garde.no_jq) _M="repowarden garde : jq absent, action non vérifiée (installer jq : https://jqlang.org)." ;;
package/hooks/lib/lang.sh CHANGED
@@ -54,6 +54,9 @@ done
54
54
  # Détection de code mort (fonctions <plugin>_deadcode des plugins ci-dessus)
55
55
  # shellcheck source=deadcode.sh
56
56
  source "$(dirname "${BASH_SOURCE[0]}")/deadcode.sh"
57
+ # Fonctions ajoutées sans commentaire (pre-commit et CI)
58
+ # shellcheck source=commentaires.sh
59
+ source "$(dirname "${BASH_SOURCE[0]}")/commentaires.sh"
57
60
  unset _plugin
58
61
 
59
62
  # --- Détection ---------------------------------------------------------------
@@ -300,8 +303,11 @@ projects_for_files() {
300
303
  }
301
304
 
302
305
  # Lance les tests de chaque projet touché. Lecture des fichiers modifiés sur stdin.
306
+ # $1 = resume (pre-push) : sortie de chaque lanceur gardée de côté ; succès →
307
+ # sa dernière ligne seule (« 2 passed in 0.5s »), échec → sortie complète. Sans
308
+ # argument (CI) : sortie complète, le journal sert au diagnostic.
303
309
  test_projects() {
304
- local cmd dir p plugins key ran=0
310
+ local mode="${1:-}" cmd dir p plugins key ran=0 out="" line last rc
305
311
  cmd="$(cfg test)"
306
312
  if [ -n "$cmd" ]; then
307
313
  # Fichiers modifiés transmis sur l'entrée standard : la commande peut
@@ -326,13 +332,31 @@ test_projects() {
326
332
  ran=1
327
333
  if [ "$dir" = . ]; then key="$p"; else key="$p ($dir)"; fi
328
334
  info_t hook.lang.5 "$key"
329
- if ! (cd "$dir" && "${p}_test") </dev/null; then
335
+ if [ "$mode" != resume ]; then
336
+ if ! (cd "$dir" && "${p}_test") </dev/null; then
337
+ err_t hook.lang.6 "$key" >&2
338
+ return 1
339
+ fi
340
+ continue
341
+ fi
342
+ [ -n "$out" ] || out="$(mktemp)"
343
+ rc=0
344
+ (cd "$dir" && "${p}_test") </dev/null >"$out" 2>&1 || rc=$?
345
+ if [ "$rc" -ne 0 ]; then
346
+ cat "$out"
347
+ rm -f "$out"
330
348
  err_t hook.lang.6 "$key" >&2
331
349
  return 1
332
350
  fi
351
+ last=""
352
+ while IFS= read -r line || [ -n "$line" ]; do
353
+ [ -n "${line//[[:space:]]/}" ] && last="$line"
354
+ done <"$out"
355
+ [ -z "$last" ] || printf ' %s\n' "$last"
333
356
  done
334
357
  done < <(projects_for_files)
335
358
 
359
+ [ -z "$out" ] || rm -f "$out"
336
360
  [ "$ran" = 1 ] || info_t hook.lang.7
337
361
  return 0
338
362
  }
@@ -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.
@@ -108,6 +112,49 @@ ticket_type_r() {
108
112
  done <<<"$1"
109
113
  }
110
114
 
115
+ # Registre des branches d'un ticket : repère invisible dans les commentaires
116
+ # du bot. GitHub ne lie une branche au ticket qu'à sa création, et une branche
117
+ # liée supprimée y perd son nom : le registre garde le nom de chaque branche.
118
+ REPERE="repowarden:branche"
119
+
120
+ repere() { printf '<!-- %s %s -->' "$REPERE" "$1"; }
121
+
122
+ # REPLY = branches inscrites sur le ticket $1 (séparées par des espaces)
123
+ branches_inscrites_r() {
124
+ local corps
125
+ corps="$(gh issue view "$1" --json comments -q '.comments[].body' 2>/dev/null || true)"
126
+ REPLY="$(grep -oE "<!-- $REPERE [^ ]+ -->" <<<"$corps" | awk '{ printf "%s ", $3 }' || true)"
127
+ REPLY="${REPLY% }"
128
+ }
129
+
130
+ # REPLY = adresse de la branche $2 si elle n'est pas liée au ticket $1, vide si
131
+ # elle l'est ou si GitHub ne répond pas (dans le doute, on ne signale rien)
132
+ ticket_unlinked_branch_url_r() {
133
+ local lignes url
134
+ REPLY=""
135
+ # shellcheck disable=SC2016 # variables GraphQL, pas du shell
136
+ lignes="$(gh api graphql -F owner='{owner}' -F repo='{repo}' -F n="$1" -f query='query($owner: String!, $repo: String!, $n: Int!) {
137
+ repository(owner: $owner, name: $repo) { url issue(number: $n) { linkedBranches(first: 50) { nodes { ref { name } } } } }
138
+ }' -q '.data.repository.url, .data.repository.issue.linkedBranches.nodes[].ref.name' 2>/dev/null)" || return 0
139
+ url="${lignes%%$'\n'*}"
140
+ [ -n "$url" ] || return 0
141
+ grep -qxF "$2" <<<"$lignes" || REPLY="$url/tree/$2"
142
+ }
143
+
144
+ # Branche $2 du ticket $1 créée avant sa validation : GitHub ne lie pas une
145
+ # branche existante (createLinkedBranch répond null), son lien est donc inscrit
146
+ # sur le ticket, une seule fois. Vrai si le commentaire a été posté.
147
+ ticket_note_unlinked_branch() {
148
+ local n="$1" b="$2" url
149
+ ticket_unlinked_branch_url_r "$n" "$b"
150
+ url="$REPLY"
151
+ [ -n "$url" ] || return 1
152
+ branches_inscrites_r "$n"
153
+ [[ " $REPLY " != *" $b "* ]] || return 1
154
+ _tr tk.branch_unlinked "$b" "$url"
155
+ gh issue comment "$n" --body "$_T"$'\n\n'"$(repere "$b")" >/dev/null
156
+ }
157
+
111
158
  # Vrai si la branche $1 est une branche de travail : ni protégée, ni une
112
159
  # exception (main, develop, branches des bots…)
113
160
  work_branch() {
@@ -164,13 +211,21 @@ ticket_auto_create() {
164
211
  # Branche de travail sans ticket : avertissement, jamais bloquant. Création
165
212
  # proposée seulement si repowarden gère la vie des tickets du projet (et que
166
213
  # gh est là pour la faire) ; sinon, comment faire taire l'avertissement.
214
+ # Projet sans suivi géré : une fois par branche seulement (le répéter à chaque
215
+ # commit n'apprend rien), mémorisé dans la config locale de la branche, que git
216
+ # efface avec elle.
167
217
  ticket_hint() {
168
218
  local branch="$1"
169
219
  skipped tickets && return 0
170
220
  work_branch "$branch" || return 0
171
221
  branch_ticket_r "$branch"
172
222
  [ -z "$REPLY" ] || return 0
223
+ if ! tickets_managed; then
224
+ [ "$(git config --get "branch.$branch.repowardenTicketWarned" 2>/dev/null)" != true ] || return 0
225
+ git config "branch.$branch.repowardenTicketWarned" true 2>/dev/null || true
226
+ fi
173
227
  attention_t tk.cli.no_ticket "$branch"
228
+ dim_t tk.cli.explain
174
229
  if tickets_managed && has gh; then
175
230
  # git cc ne propose de créer le ticket qu'avec ticketAutoCreate
176
231
  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
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Hook pre-commit : bloque les commits directs sur main, détecte les secrets,
3
- # puis formate les fichiers stagés selon le(s) langage(s) détecté(s).
3
+ # formate les fichiers stagés selon le(s) langage(s) détecté(s), puis signale
4
+ # les fonctions ajoutées sans commentaire.
4
5
  set -euo pipefail
5
6
 
6
7
  source "$(dirname "$0")/lib/common.sh"
@@ -26,7 +27,6 @@ fi
26
27
  PROTECTED="$(cfg protectedBranches "main master")"
27
28
  if ! skipped protect-branch && [[ " $PROTECTED " == *" $BRANCH "* ]] && git rev-parse --verify -q HEAD >/dev/null; then
28
29
  err_t hook.pre_commit.2 "$BRANCH" >&2
29
- echo_t hook.pre_commit.3 >&2
30
30
  exit 1
31
31
  fi
32
32
 
@@ -55,15 +55,18 @@ if ! skipped secrets; then
55
55
  fi
56
56
  rm -f "$rapport" "$journal"
57
57
  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
58
+ gitleaks_install_r
59
+ warn_t hook.pre_commit.6 "$REPLY"
64
60
  fi
65
61
  fi
66
62
 
63
+ # 2 bis. Fichier sensible ni suivi ni ignoré : un « git add . » le publierait
64
+ # (avertissement seulement, contenu jamais lu)
65
+ if ! skipped secrets && ! skipped sensitive-files; then
66
+ sensitive_files_r
67
+ [ -z "$REPLY" ] || sensitive_files_warn "$REPLY" >&2
68
+ fi
69
+
67
70
  # 3. Formatage : chaque fichier stagé avec l'outil de son langage / projet
68
71
  if ! skipped format; then
69
72
  # Ne formater (et re-stager) que le contenu stagé, jamais le travail en cours
@@ -72,4 +75,10 @@ if ! skipped format; then
72
75
  restore_unstaged
73
76
  fi
74
77
 
78
+ # 4. Fonctions ajoutées sans commentaire juste au-dessus (avertissement seulement,
79
+ # après le formatage : les lignes signalées sont celles du commit)
80
+ if ! skipped comments && git rev-parse -q --verify HEAD >/dev/null; then
81
+ comments_check HEAD ""
82
+ fi
83
+
75
84
  ok_t hook.pre_commit.7
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.21",
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",