repowarden 4.4.0 → 4.7.1

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.
@@ -25,8 +25,10 @@ _msg_en() {
25
25
  cc.option_value) _M="Option %s expects a value." ;;
26
26
  cc.batch_message_required) _M="Without questions, the description is required: git cc -m \"<description>\" [--type T] [--scope S] [--body …] [--refs …] [--breaking …]." ;;
27
27
  cc.type_invalid) _M="\"%s\": pick a number (1 to %s) or a name from the list." ;;
28
+ cc.type_invalid_suggest) _M="\"%s\": unknown type. Did you mean \"%s\"? Number (1 to %s) or a name from the list." ;;
28
29
  cc.step2) _M="Scope (optional)" ;;
29
30
  cc.scope_hint) _M="Part of the project affected, e.g. api, auth, chart; Enter for none." ;;
31
+ cc.scope_hint_default) _M="Part of the project, e.g. api, auth, chart; Enter keeps \"%s\", \"-\" for none." ;;
30
32
  cc.scope_prompt_default) _M="Scope (\"-\" for none) [%s]: " ;;
31
33
  cc.scope_prompt) _M="Scope: " ;;
32
34
  cc.scope_fixed) _M="Scope: lowercase letters, digits, . _ - only. Suggested: %s (Enter to accept)." ;;
@@ -40,7 +42,7 @@ _msg_en() {
40
42
  cc.breaking_note_prompt) _M="What changes: " ;;
41
43
  cc.breaking_note_invalid) _M="Describe the change (at least 10 characters); otherwise answer no to the previous question." ;;
42
44
  cc.step4) _M="Description" ;;
43
- cc.desc_hint) _M="Imperative mood, no initial capital or final period; %s characters max." ;;
45
+ cc.desc_hint) _M="Imperative mood, no final period, lowercase start except acronyms or proper nouns; %s characters max." ;;
44
46
  cc.desc_required) _M="The description is required." ;;
45
47
  cc.header_too_long) _M="Header too long: %s characters (72 max). Shorten the description." ;;
46
48
  cc.step5) _M="Details (optional)" ;;
@@ -48,7 +50,8 @@ _msg_en() {
48
50
  cc.refs_prompt_ticket) _M="References (\"-\" for none) [%s]: " ;;
49
51
  cc.refs_prompt) _M="References (e.g. %s; Enter for none): " ;;
50
52
  tk.cli.no_ticket) _M="Branch %s has no ticket: this work will not be linked to any ticket (not blocking)." ;;
51
- tk.cli.propose) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\" (or answer yes in git cc)." ;;
53
+ tk.cli.propose) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\"." ;;
54
+ tk.cli.propose_cc) _M="Start it from a ticket: repowarden ticket nouveau \"<title>\" (or answer yes in git cc)." ;;
52
55
  tk.cli.silence) _M="Project without ticket tracking? git config repowarden.skip tickets to stop seeing this." ;;
53
56
  tk.cli.fetch) _M="Branch of ticket #%s…" ;;
54
57
  tk.cli.switched) _M="On branch %s (ticket #%s)." ;;
@@ -111,6 +114,11 @@ _msg_en() {
111
114
  hook.pre_commit.6) _M="gitleaks not installed, secret detection skipped (install: %s)." ;;
112
115
  hook.pre_commit.7) _M="Pre-commit OK" ;;
113
116
  hook.commit_msg.1) _M="First line too long (%s > 72 characters)." ;;
117
+ hook.commit_msg.type_unknown) _M="Unknown type \"%s\". Did you mean: %s" ;;
118
+ hook.commit_msg.type_none) _M="Unknown type \"%s\". Possible types:" ;;
119
+ hook.commit_msg.desc_empty) _M="Missing description after \"%s\": describe the change in the imperative mood." ;;
120
+ hook.commit_msg.kept) _M=" Your message is not lost: git commit -e -F .git/COMMIT_EDITMSG" ;;
121
+ hook.commit_msg.dot) _M="Final period removed from the description (same rule as git cc)." ;;
114
122
  hook.pre_push.1) _M="Push refused: branch name does not follow the convention." ;;
115
123
  hook.pre_push.2) _M="Direct push to '%s' refused: it only changes through pull requests." ;;
116
124
  hook.pre_push.3) _M=" Create a branch: git switch -c feat/my-feature, then open a PR." ;;
@@ -118,6 +126,8 @@ _msg_en() {
118
126
  hook.pre_push.5) _M="%s: no changed file, nothing to test." ;;
119
127
  hook.pre_push.6) _M="Nothing to test (branch deletion only)." ;;
120
128
  hook.pre_push.7) _M="Pre-push OK" ;;
129
+ hook.pre_push.lint.missing) _M="%s not installed, %s not checked (install: %s)." ;;
130
+ hook.pre_push.lint.run) _M="%s: %s" ;;
121
131
  hook.branch_hint.1) _M="Branch name \"%s\" does not follow the convention: commits and pushes will be refused. Rename:" ;;
122
132
  hook.branch_hint.2) _M=" Format <type>/<topic> (e.g. feat/signup); full help on the next commit." ;;
123
133
  hook.post_merge.1) _M="🧹 Local branch deleted (merged and deleted on the server): %s" ;;
@@ -262,6 +272,7 @@ _msg_en() {
262
272
  ci.proteger.5) _M=" v* tags: %s" ;;
263
273
  ci.proteger.6) _M=" default branch: %s" ;;
264
274
  ci.proteger.7) _M=" required approvals: %s; CODEOWNERS review: %s; approval of unattributed changes: %s" ;;
275
+ ci.proteger.solo) _M=" solo repository (a single person with write access): no approval by another person required" ;;
265
276
  ci.proteger.8) _M=" (admins keep an explicit bypass, recorded in the PR history)" ;;
266
277
  ci.proteger.9) _M=" \"%s\" environment: approval by %s; protected branches and v* tags" ;;
267
278
  ci.proteger.10) _M="Ruleset \"%s\" updated." ;;
@@ -276,6 +287,13 @@ _msg_en() {
276
287
  ci.proteger.unattributed_invalid) _M="unattributedApproval: true or false." ;;
277
288
  ci.proteger.no_branch) _M="None of the branches \"%s\" exists on %s." ;;
278
289
  ci.proteger.no_cleanup) _M="Automatic branch deletion disabled (it would delete develop): add a workflow calling nettoyage-branches.yml, otherwise merged branches remain (see In an organisation)." ;;
290
+ ci.proteger.no_delivery_env) _M="main will require a successful \"%s\" deployment, but no workflow creates that deployment: a deployment nothing creates would block main forever. Call release-auto.yml with delivery-environment (see docs/releases.md, develop flow), or set deliveryApproval = review in .repowarden.conf." ;;
291
+ ci.proteger.stale_concurrency) _M="%s: a pending delivery request (manual or scheduled) would silently block the next ones. Set cancel-in-progress: \${{ github.event_name != 'push' }} (a new request replaces the pending one) and one group per trigger: release-\${{ github.ref_name }}-\${{ github.event.schedule || github.event_name }}." ;;
292
+ ci.proteger.urgent) _M=" \"urgent\" label created (its merge triggers the delivery request)" ;;
293
+ ci.proteger.verify_missing) _M="%s: ruleset missing online" ;;
294
+ ci.proteger.verify_diff) _M="%s: online rules differ\n online: %s\n expected: %s" ;;
295
+ ci.proteger.verify_fix) _M="Outdated rules: run repowarden proteger again to update them." ;;
296
+ ci.proteger.verify_ok) _M="Online rules up to date." ;;
279
297
  ci.proteger.delivery) _M=" delivery into main: %s approval(s) required (moving to a new version); auto-merge of work PRs into develop: %s" ;;
280
298
  ci.proteger.methods_flow) _M="main: merge commit; %s: squash or merge commit" ;;
281
299
  ci.proteger.tags_org) _M="reserved for workflows" ;;
@@ -363,6 +381,7 @@ _msg_en() {
363
381
  st.repo_version_old) _M="The project requires repowarden %s; installed: %s" ;;
364
382
  st.repo_version_ok) _M="Version required by the project (%s): met" ;;
365
383
  st.repo_forge) _M="Platform: %s" ;;
384
+ st.repo_rules) _M="GitHub rules up to date? repowarden proteger --verifier (read-only)" ;;
366
385
  st.tools) _M="Tools present: %s · missing: %s (depending on the projects' languages)" ;;
367
386
  st.next) _M="Next step:" ;;
368
387
  st.ready) _M="All set: git cc to commit, repowarden code-mort on a branch." ;;
@@ -403,13 +422,16 @@ _msg_en() {
403
422
  tk.validated_waiting) _M="Ticket validated, waiting to be taken: its branch will be created when it is assigned, or by taking it yourself with \`repowarden ticket %s\`." ;;
404
423
  tk.pr_inverted) _M="PR closed automatically: a PR from \`%s\` to \`%s\` is pointless. A persistent branch only goes out for a delivery (develop → main) or a back-merge (main → develop). To update a work branch: on that branch, \`git merge origin/%s\`, then \`git push\`." ;;
405
424
  tk.pr_backmerge_empty) _M="PR closed automatically: \`%s\` brings no content to \`%s\`. The back-merge is done automatically after a release, only when there is something to bring back." ;;
425
+ tk.pr_delivery_manual) _M="PR closed automatically: the \`%s\` → \`%s\` delivery is opened by the release workflow; a PR opened by hand would never get a deployment approval. Run *Actions → release → Run workflow* on \`%s\` (or \`gh workflow run release.yml --ref %s\`), then **Approve and deploy** on the run." ;;
406
426
  tk.branch_registered) _M="Ticket branch: \`%s\`." ;;
407
427
  tk.branch_second) _M="Second branch for this ticket: \`%s\` (already: %s). One ticket, one branch: group the work, or open a separate ticket." ;;
408
428
  tk.adopted) _M="Existing tickets: %s sorted, %s branch(es) registered." ;;
409
429
  tk.init_adopt) _M="Existing tickets and branches" ;;
410
430
  tk.abandoned) _M="Ticket #%s abandoned: PR closed, branch deleted." ;;
411
431
  tk.branch_deleted) _M="Branch %s deleted (ticket #%s abandoned)" ;;
432
+ tk.branch_kept) _M="Branch \`%s\` kept: %s commit(s) missing from the integration branch, open PR(s): %s. Delete it by hand if the work lives elsewhere." ;;
412
433
  tk.draft_opened) _M="Draft PR #%s opened for ticket #%s" ;;
434
+ tk.draft_updated) _M="PR #%s (ticket #%s): title and description taken from the ticket" ;;
413
435
  tk.created_body) _M="Ticket created automatically for PR #%s, which cited none. To be validated by a maintainer (\"validé\" label)." ;;
414
436
  tk.created) _M="Ticket #%s created for PR #%s and linked in its description: to be validated by a maintainer." ;;
415
437
  tk.init_labels) _M="Labels (statuses and validation)" ;;
@@ -417,9 +439,36 @@ _msg_en() {
417
439
  tk.init_exists) _M="%s already exists: left as is." ;;
418
440
  tk.init_written) _M="%s created." ;;
419
441
  tk.init_done) _M="Ticket tracking ready." ;;
442
+ tk.init_auto_create_on) _M="Auto ticket creation: ENABLED (ticketAutoCreate = true)" ;;
443
+ tk.init_auto_create_off) _M="Auto ticket creation: DISABLED (default). To enable: git config repowarden.ticketAutoCreate true" ;;
420
444
  tk.init_next) _M="Next: commit these files; require the \"ticket\" check (repowarden proteger --checks \"…,ticket\"); in release.yml, wire the tickets job (see the Tickets docs)." ;;
421
445
  tk.prerelease) _M="Available in pre-release **%s**: to be tested before production." ;;
422
446
  tk.released) _M="Released in **%s**." ;;
447
+ # Ticket comment
448
+ tk.comment.adding) _M="Adding comment to ticket #%s..." ;;
449
+ tk.comment.added) _M="Comment added to ticket #%s." ;;
450
+ # Ticket label
451
+ tk.label.adding) _M="Adding label \"%s\" to ticket #%s..." ;;
452
+ tk.label.added) _M="Label \"%s\" added to ticket #%s." ;;
453
+ tk.label.removing) _M="Removing label \"%s\" from ticket #%s..." ;;
454
+ tk.label.removed) _M="Label \"%s\" removed from ticket #%s." ;;
455
+ # PR label
456
+ pr.label.adding) _M="Adding label \"%s\" to PR #%s..." ;;
457
+ pr.label.added) _M="Label \"%s\" added to PR #%s." ;;
458
+ pr.label.removing) _M="Removing label \"%s\" from PR #%s..." ;;
459
+ pr.label.removed) _M="Label \"%s\" removed from PR #%s." ;;
460
+ # Run rerun
461
+ run.rerun.failed) _M="Rerunning last failed run..." ;;
462
+ run.rerun.workflow) _M="Rerunning last run of workflow \"%s\"..." ;;
463
+ run.rerun.running) _M="Rerunning run #%s..." ;;
464
+ run.rerun.done) _M="Run #%s rerun." ;;
465
+ run.rerun.none_failed) _M="No failed run found." ;;
466
+ run.rerun.none_workflow) _M="No run found for workflow \"%s\"." ;;
467
+ # Run list
468
+ run.listing.all) _M="Listing recent runs..." ;;
469
+ run.listing.workflow) _M="Listing runs for workflow \"%s\"..." ;;
470
+ # PR checks
471
+ pr.checks.checking) _M="Checking checks for PR #%s..." ;;
423
472
  *) _M="" ;;
424
473
  esac
425
474
  }
@@ -25,8 +25,10 @@ _msg_fr() {
25
25
  cc.option_value) _M="L'option %s attend une valeur." ;;
26
26
  cc.batch_message_required) _M="Sans question, la description est obligatoire : git cc -m \"<description>\" [--type T] [--scope S] [--body …] [--refs …] [--breaking …]." ;;
27
27
  cc.type_invalid) _M="« %s » : choisis un numéro (1 à %s) ou un nom de la liste." ;;
28
+ cc.type_invalid_suggest) _M="« %s » : type inconnu. Voulais-tu « %s » ? Numéro (1 à %s) ou nom de la liste." ;;
28
29
  cc.step2) _M="Scope (optionnel)" ;;
29
30
  cc.scope_hint) _M="Partie du projet concernée, ex. api, auth, chart ; Entrée pour aucun." ;;
31
+ cc.scope_hint_default) _M="Partie du projet concernée, ex. api, auth, chart ; Entrée garde « %s », « - » pour aucun." ;;
30
32
  cc.scope_prompt_default) _M="Scope (« - » pour aucun) [%s] : " ;;
31
33
  cc.scope_prompt) _M="Scope : " ;;
32
34
  cc.scope_fixed) _M="Scope : minuscules, chiffres, . _ - uniquement. Proposé : %s (Entrée pour l'accepter)." ;;
@@ -40,7 +42,7 @@ _msg_fr() {
40
42
  cc.breaking_note_prompt) _M="Ce qui change : " ;;
41
43
  cc.breaking_note_invalid) _M="Décris le changement (10 caractères au moins) ; sinon, réponds non à la question précédente." ;;
42
44
  cc.step4) _M="Description" ;;
43
- cc.desc_hint) _M="À l'impératif, sans majuscule initiale ni point final ; %s caractères au plus." ;;
45
+ cc.desc_hint) _M="À l'impératif, sans point final, minuscule initiale sauf sigle ou nom propre ; %s caractères au plus." ;;
44
46
  cc.desc_required) _M="La description est obligatoire." ;;
45
47
  cc.header_too_long) _M="En-tête trop long : %s caractères (72 max). Raccourcis la description." ;;
46
48
  cc.step5) _M="Détails (optionnels)" ;;
@@ -48,7 +50,8 @@ _msg_fr() {
48
50
  cc.refs_prompt_ticket) _M="Références (« - » pour aucune) [%s] : " ;;
49
51
  cc.refs_prompt) _M="Références (ex. %s ; Entrée pour aucune) : " ;;
50
52
  tk.cli.no_ticket) _M="Branche %s sans ticket : ce travail ne sera relié à aucun ticket (non bloquant)." ;;
51
- tk.cli.propose) _M="Le partir d'un ticket : repowarden ticket nouveau \"<titre>\" (ou réponds oui à git cc)." ;;
53
+ tk.cli.propose) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\"." ;;
54
+ tk.cli.propose_cc) _M="Partir d'un ticket : repowarden ticket nouveau \"<titre>\" (ou réponds oui à git cc)." ;;
52
55
  tk.cli.silence) _M="Projet sans suivi de tickets ? git config repowarden.skip tickets pour ne plus le voir." ;;
53
56
  tk.cli.fetch) _M="Branche du ticket #%s…" ;;
54
57
  tk.cli.switched) _M="Sur la branche %s (ticket #%s)." ;;
@@ -109,6 +112,11 @@ _msg_fr() {
109
112
  hook.pre_commit.6) _M="gitleaks absent, détection de secrets ignorée (installation : %s)." ;;
110
113
  hook.pre_commit.7) _M="Pre-commit OK" ;;
111
114
  hook.commit_msg.1) _M="Première ligne trop longue (%s > 72 caractères)." ;;
115
+ hook.commit_msg.type_unknown) _M="Type « %s » inconnu. Voulais-tu : %s" ;;
116
+ hook.commit_msg.type_none) _M="Type « %s » inconnu. Types possibles :" ;;
117
+ hook.commit_msg.desc_empty) _M="Description manquante après « %s » : décris le changement à l'impératif." ;;
118
+ hook.commit_msg.kept) _M=" Ton message n'est pas perdu : git commit -e -F .git/COMMIT_EDITMSG" ;;
119
+ hook.commit_msg.dot) _M="Point final retiré de la description (même règle que git cc)." ;;
112
120
  hook.pre_push.1) _M="Push refusé : nom de branche non conforme." ;;
113
121
  hook.pre_push.2) _M="Push direct refusé sur '%s' : elle n'évolue que par Pull Request." ;;
114
122
  hook.pre_push.3) _M=" Crée une branche : git switch -c feat/ma-feature, puis ouvre une PR." ;;
@@ -116,6 +124,8 @@ _msg_fr() {
116
124
  hook.pre_push.5) _M="%s : aucun fichier modifié, rien à tester." ;;
117
125
  hook.pre_push.6) _M="Rien à tester (suppression de branche uniquement)." ;;
118
126
  hook.pre_push.7) _M="Pre-push OK" ;;
127
+ hook.pre_push.lint.missing) _M="%s absent, %s non vérifié (installation : %s)." ;;
128
+ hook.pre_push.lint.run) _M="%s : %s" ;;
119
129
  hook.branch_hint.1) _M="Nom de branche « %s » non conforme : commits et push seront refusés. Renommer :" ;;
120
130
  hook.branch_hint.2) _M=" Format <type>/<sujet> (ex. feat/inscription) ; aide complète au prochain commit." ;;
121
131
  hook.post_merge.1) _M="🧹 Branche locale supprimée (mergée et supprimée sur le serveur) : %s" ;;
@@ -262,6 +272,7 @@ _msg_fr() {
262
272
  ci.proteger.5) _M=" tags v* : %s" ;;
263
273
  ci.proteger.6) _M=" branche par défaut : %s" ;;
264
274
  ci.proteger.7) _M=" approbations exigées : %s ; revue des CODEOWNERS : %s ; approbation des changements non attribués : %s" ;;
275
+ ci.proteger.solo) _M=" dépôt solo (une seule personne avec l'écriture) : aucune approbation par une autre personne exigée" ;;
265
276
  ci.proteger.8) _M=" (les admins gardent un contournement explicite, tracé dans l'historique de la PR)" ;;
266
277
  ci.proteger.9) _M=" environnement « %s » : approbation de %s ; branches protégées et tags v*" ;;
267
278
  ci.proteger.10) _M="Ruleset « %s » mis à jour." ;;
@@ -276,6 +287,13 @@ _msg_fr() {
276
287
  ci.proteger.unattributed_invalid) _M="unattributedApproval : true ou false." ;;
277
288
  ci.proteger.no_branch) _M="Aucune des branches « %s » n'existe sur %s." ;;
278
289
  ci.proteger.no_cleanup) _M="Suppression automatique des branches désactivée (elle supprimerait develop) : ajouter un workflow qui appelle nettoyage-branches.yml, sinon les branches mergées restent (voir En organisation)." ;;
290
+ ci.proteger.no_delivery_env) _M="main exigera un déploiement « %s » réussi, mais aucun workflow ne crée ce déploiement : un déploiement que rien ne crée bloquerait main pour toujours. Appeler release-auto.yml avec delivery-environment (voir docs/releases.md, flux develop), ou deliveryApproval = review dans .repowarden.conf." ;;
291
+ ci.proteger.stale_concurrency) _M="%s : une demande de livraison (manuelle ou planifiée) en attente bloquerait en silence les suivantes. Mettre cancel-in-progress: \${{ github.event_name != 'push' }} (une nouvelle demande remplace celle en attente) et un groupe par déclencheur : release-\${{ github.ref_name }}-\${{ github.event.schedule || github.event_name }}." ;;
292
+ ci.proteger.urgent) _M=" étiquette « urgent » créée (son merge lance la demande de livraison)" ;;
293
+ ci.proteger.verify_missing) _M="%s : ruleset absent en ligne" ;;
294
+ ci.proteger.verify_diff) _M="%s : règles en ligne différentes\n en ligne : %s\n attendu : %s" ;;
295
+ ci.proteger.verify_fix) _M="Règles périmées : relancer repowarden proteger pour les mettre à jour." ;;
296
+ ci.proteger.verify_ok) _M="Règles en ligne à jour." ;;
279
297
  ci.proteger.delivery) _M=" livraison vers main : %s approbation(s) exigée(s) (passage à une nouvelle version) ; merge automatique des PR de travail vers develop : %s" ;;
280
298
  ci.proteger.methods_flow) _M="main : merge commit ; %s : squash ou merge commit" ;;
281
299
  ci.proteger.tags_org) _M="réservés aux workflows" ;;
@@ -363,6 +381,7 @@ _msg_fr() {
363
381
  st.repo_version_old) _M="Le projet demande repowarden %s ; installé : %s" ;;
364
382
  st.repo_version_ok) _M="Version demandée par le projet (%s) : respectée" ;;
365
383
  st.repo_forge) _M="Plateforme : %s" ;;
384
+ st.repo_rules) _M="Règles GitHub à jour ? repowarden proteger --verifier (lecture seule)" ;;
366
385
  st.tools) _M="Outils présents : %s · absents : %s (selon les langages des projets)" ;;
367
386
  st.next) _M="Étape suivante :" ;;
368
387
  st.ready) _M="Tout est prêt : git cc pour commiter, repowarden code-mort sur une branche." ;;
@@ -403,13 +422,16 @@ _msg_fr() {
403
422
  tk.validated_waiting) _M="Ticket validé, en attente d'être pris : sa branche sera créée à l'assignation, ou en le prenant soi-même avec \`repowarden ticket %s\`." ;;
404
423
  tk.pr_inverted) _M="PR fermée automatiquement : une PR de \`%s\` vers \`%s\` est sans objet. Une branche persistante ne part que pour une livraison (develop → main) ou un retour (main → develop). Pour mettre une branche de travail à jour : sur cette branche, \`git merge origin/%s\`, puis \`git push\`." ;;
405
424
  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." ;;
425
+ 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." ;;
406
426
  tk.branch_registered) _M="Branche du ticket : \`%s\`." ;;
407
427
  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é." ;;
408
428
  tk.adopted) _M="Tickets existants : %s rangé(s), %s branche(s) inscrite(s)." ;;
409
429
  tk.init_adopt) _M="Tickets et branches existants" ;;
410
430
  tk.abandoned) _M="Ticket #%s abandonné : PR fermée, branche supprimée." ;;
411
431
  tk.branch_deleted) _M="Branche %s supprimée (ticket #%s abandonné)" ;;
432
+ 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." ;;
412
433
  tk.draft_opened) _M="PR #%s ouverte en brouillon pour le ticket #%s" ;;
434
+ tk.draft_updated) _M="PR #%s (ticket #%s) : titre et description repris du ticket" ;;
413
435
  tk.created_body) _M="Ticket créé automatiquement pour la PR #%s, qui n'en citait aucun. À valider par un mainteneur (étiquette « validé »)." ;;
414
436
  tk.created) _M="Ticket #%s créé pour la PR #%s et relié à sa description : à valider par un mainteneur." ;;
415
437
  tk.init_labels) _M="Étiquettes (statuts et validation)" ;;
@@ -417,9 +439,36 @@ _msg_fr() {
417
439
  tk.init_exists) _M="%s existe déjà : laissé tel quel." ;;
418
440
  tk.init_written) _M="%s créé." ;;
419
441
  tk.init_done) _M="Suivi des tickets prêt." ;;
442
+ tk.init_auto_create_on) _M="Création auto de ticket : ACTIVÉE (ticketAutoCreate = true)" ;;
443
+ tk.init_auto_create_off) _M="Création auto de ticket : DÉSACTIVÉE (défaut). Pour l'activer : git config repowarden.ticketAutoCreate true" ;;
420
444
  tk.init_next) _M="Ensuite : committer ces fichiers ; exiger la vérification « ticket » (repowarden proteger --checks \"…,ticket\") ; dans release.yml, brancher le job tickets (voir la doc Tickets)." ;;
421
445
  tk.prerelease) _M="Disponible en préversion **%s** : à tester avant la production." ;;
422
446
  tk.released) _M="Publié dans **%s**." ;;
447
+ # Ticket comment
448
+ tk.comment.adding) _M="Ajout d'un commentaire sur le ticket #%s..." ;;
449
+ tk.comment.added) _M="Commentaire ajouté au ticket #%s." ;;
450
+ # Ticket label
451
+ tk.label.adding) _M="Ajout du label « %s » sur le ticket #%s..." ;;
452
+ tk.label.added) _M="Label « %s » ajouté au ticket #%s." ;;
453
+ tk.label.removing) _M="Retrait du label « %s » du ticket #%s..." ;;
454
+ tk.label.removed) _M="Label « %s » retiré du ticket #%s." ;;
455
+ # PR label
456
+ pr.label.adding) _M="Ajout du label « %s » sur la PR #%s..." ;;
457
+ pr.label.added) _M="Label « %s » ajouté à la PR #%s." ;;
458
+ pr.label.removing) _M="Retrait du label « %s » de la PR #%s..." ;;
459
+ pr.label.removed) _M="Label « %s » retiré de la PR #%s." ;;
460
+ # Run rerun
461
+ run.rerun.failed) _M="Relance du dernier run échoué..." ;;
462
+ run.rerun.workflow) _M="Relance du dernier run du workflow « %s »..." ;;
463
+ run.rerun.running) _M="Relance du run #%s..." ;;
464
+ run.rerun.done) _M="Run #%s relancé." ;;
465
+ run.rerun.none_failed) _M="Aucun run échoué trouvé." ;;
466
+ run.rerun.none_workflow) _M="Aucun run trouvé pour le workflow « %s »." ;;
467
+ # Run list
468
+ run.listing.all) _M="Liste des runs récents..." ;;
469
+ run.listing.workflow) _M="Liste des runs du workflow « %s »..." ;;
470
+ # PR checks
471
+ pr.checks.checking) _M="Vérification des checks de la PR #%s..." ;;
423
472
  *) _M="" ;;
424
473
  esac
425
474
  }
@@ -24,21 +24,73 @@ branch_ticket_r() {
24
24
  if [[ "$1" =~ ^[a-z]+/([0-9]+)(-|$) ]]; then REPLY="${BASH_REMATCH[1]}"; fi
25
25
  }
26
26
 
27
+ # $1 → sans accents (un à un : les classes [àâ] sont fausses en locale C) ;
28
+ # les autres caractères sont conservés, le nettoyage reste à l'appelant
29
+ sans_accents() {
30
+ printf '%s' "$1" | sed -e 's/à/a/g' -e 's/â/a/g' -e 's/ä/a/g' -e 's/é/e/g' -e 's/è/e/g' -e 's/ê/e/g' -e 's/ë/e/g' -e 's/î/i/g' -e 's/ï/i/g' -e 's/ô/o/g' -e 's/ö/o/g' -e 's/ù/u/g' -e 's/û/u/g' -e 's/ü/u/g' -e 's/ç/c/g' -e 's/œ/oe/g' -e 's/æ/ae/g' -e 's/À/a/g' -e 's/Â/a/g' -e 's/Ä/a/g' -e 's/É/e/g' -e 's/È/e/g' -e 's/Ê/e/g' -e 's/Ë/e/g' -e 's/Î/i/g' -e 's/Ï/i/g' -e 's/Ô/o/g' -e 's/Ö/o/g' -e 's/Ù/u/g' -e 's/Û/u/g' -e 's/Ü/u/g' -e 's/Ç/c/g' -e 's/Œ/oe/g' -e 's/Æ/ae/g'
31
+ }
32
+
33
+ # Mots vides qu'un nom de branche (ou une portée) ne garde pas en fin de coupe :
34
+ # « …-garde-la-trace-de » → « …-garde-la-trace »
35
+ MOTS_VIDES="a au aux de des du en et l la le les of on ou par pour sur the to un une"
36
+
37
+ # $1 (minuscules, tirets) → sans ses mots vides finaux
38
+ sans_mots_vides() {
39
+ local slug="$1" mot
40
+ while [[ "$slug" == *-* ]]; do
41
+ mot="${slug##*-}"
42
+ [[ " $MOTS_VIDES " == *" $mot "* ]] || break
43
+ slug="${slug%-*}"
44
+ done
45
+ printf '%s' "$slug"
46
+ }
47
+
27
48
  # REPLY = nom de branche d'un ticket : $1 numéro, $2 titre, $3 type (feat
28
49
  # par défaut) → feat/12-ajoute-le-panier (minuscules, sans accents, 40
29
- # caractères de titre au plus, coupé sur un tiret)
50
+ # caractères au plus, coupé sur un tiret puis sur un mot entier)
30
51
  ticket_branch_r() {
31
52
  local n="$1" titre="$2" type="${3:-feat}" slug
32
- # Accents retirés un à un (pas de classe [àâ] : fausse en locale C)
33
- slug="$(printf '%s' "$titre" | sed -e 's/à/a/g' -e 's/â/a/g' -e 's/ä/a/g' -e 's/é/e/g' -e 's/è/e/g' -e 's/ê/e/g' -e 's/ë/e/g' -e 's/î/i/g' -e 's/ï/i/g' -e 's/ô/o/g' -e 's/ö/o/g' -e 's/ù/u/g' -e 's/û/u/g' -e 's/ü/u/g' -e 's/ç/c/g' -e 's/œ/oe/g' -e 's/æ/ae/g' -e 's/À/a/g' -e 's/Â/a/g' -e 's/Ä/a/g' -e 's/É/e/g' -e 's/È/e/g' -e 's/Ê/e/g' -e 's/Ë/e/g' -e 's/Î/i/g' -e 's/Ï/i/g' -e 's/Ô/o/g' -e 's/Ö/o/g' -e 's/Ù/u/g' -e 's/Û/u/g' -e 's/Ü/u/g' -e 's/Ç/c/g' -e 's/Œ/oe/g' -e 's/Æ/ae/g' |
34
- LC_ALL=C tr '[:upper:]' '[:lower:]' | LC_ALL=C sed -e 's/[^a-z0-9]\{1,\}/-/g' -e 's/^-//' -e 's/-$//')"
53
+ slug="$(sans_accents "$titre" | LC_ALL=C tr '[:upper:]' '[:lower:]' |
54
+ LC_ALL=C sed -e 's/[^a-z0-9]\{1,\}/-/g' -e 's/^-//' -e 's/-$//')"
35
55
  if [ "${#slug}" -gt 40 ]; then
36
56
  slug="${slug:0:40}"
37
57
  [[ "$slug" == *-* ]] && slug="${slug%-*}"
38
58
  fi
59
+ slug="$(sans_mots_vides "$slug")"
39
60
  REPLY="$type/$n${slug:+-$slug}"
40
61
  }
41
62
 
63
+ # REPLY = titre de PR d'un ticket : $1 type (« feat » par défaut), $2 titre du
64
+ # ticket → « feat(portée): sujet ». Portée = préfixe du titre avant le premier
65
+ # « : » (« Tickets : … » → « tickets »), 20 caractères au plus, omise si vide ;
66
+ # sujet = le reste, première lettre abaissée. 72 caractères au plus, coupé sur
67
+ # un mot entier. Vide si le titre ne donne rien (l'appelant décide du repli).
68
+ ticket_pr_title_r() {
69
+ local type="${1:-feat}" titre="$2" portee sujet tete res
70
+ sujet="$titre"
71
+ portee=""
72
+ if [[ "$titre" =~ ^([^:]{1,30})[[:space:]]*:[[:space:]]+(.+)$ ]]; then
73
+ portee="$(sans_accents "${BASH_REMATCH[1]}" | LC_ALL=C tr '[:upper:]' '[:lower:]' |
74
+ LC_ALL=C sed -e 's/[^a-z0-9._-]\{1,\}/-/g' -e 's/^-//' -e 's/-$//')"
75
+ [ "${#portee}" -le 20 ] || portee=""
76
+ sujet="${BASH_REMATCH[2]}"
77
+ fi
78
+ sujet="$(printf '%s' "${sujet:0:1}" | LC_ALL=C tr '[:upper:]' '[:lower:]')${sujet:1}"
79
+ tete="$type${portee:+($portee)}: "
80
+ REPLY=""
81
+ [ -n "$sujet" ] || return 0
82
+ res="$tete$sujet"
83
+ # 72 caractères au plus, coupé sur un mot entier (authored_length → REPLY)
84
+ authored_length "$res"
85
+ while [ "$REPLY" -gt 72 ] && [ -n "$sujet" ]; do
86
+ if [[ "$sujet" == *" "* ]]; then sujet="${sujet% *}"; else sujet="${sujet%?}"; fi
87
+ res="$tete$sujet"
88
+ authored_length "$res"
89
+ done
90
+ [ -n "$sujet" ] || res=""
91
+ REPLY="$res"
92
+ }
93
+
42
94
  # REPLY = type de branche d'après les étiquettes d'un ticket (une par ligne) :
43
95
  # « type: fix », « bug » → fix ; feat par défaut
44
96
  ticket_type_r() {
@@ -90,6 +142,21 @@ tickets_managed() {
90
142
  [ "$TICKETS_MANAGED" = oui ]
91
143
  }
92
144
 
145
+ # Vrai si la création automatique de ticket est activée : réglage
146
+ # ticketAutoCreate = true (défaut false)
147
+ TICKET_AUTO_CREATE=""
148
+ ticket_auto_create() {
149
+ if [ -z "$TICKET_AUTO_CREATE" ]; then
150
+ TICKET_AUTO_CREATE=non
151
+ cfg_r ticketAutoCreate ""
152
+ case "$REPLY" in
153
+ true | oui | yes) TICKET_AUTO_CREATE=oui ;;
154
+ false | non | no | "") ;;
155
+ esac
156
+ fi
157
+ [ "$TICKET_AUTO_CREATE" = oui ]
158
+ }
159
+
93
160
  # Branche de travail sans ticket : avertissement, jamais bloquant. Création
94
161
  # proposée seulement si repowarden gère la vie des tickets du projet (et que
95
162
  # gh est là pour la faire) ; sinon, comment faire taire l'avertissement.
@@ -101,7 +168,8 @@ ticket_hint() {
101
168
  [ -z "$REPLY" ] || return 0
102
169
  attention_t tk.cli.no_ticket "$branch"
103
170
  if tickets_managed && has gh; then
104
- dim_t tk.cli.propose
171
+ # git cc ne propose de créer le ticket qu'avec ticketAutoCreate
172
+ if ticket_auto_create; then dim_t tk.cli.propose_cc; else dim_t tk.cli.propose; fi
105
173
  else
106
174
  dim_t tk.cli.silence
107
175
  fi
package/hooks/lib/ui.sh CHANGED
@@ -31,7 +31,27 @@ repowarden_version_r() {
31
31
  local tag=""
32
32
  REPLY="$(sed -n 's/^ "version": *"\([^"]*\)".*/\1/p' "$1/package.json" 2>/dev/null | sed -n 1p)"
33
33
  if [ -e "$1/.git" ]; then
34
- tag="$(git -C "$1" describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true)"
34
+ # Sous-shell sans les variables que git exporte aux hooks (worktree) :
35
+ # elles l'emportent sur -C, ce seraient les tags du projet. Tag le plus
36
+ # proche de HEAD (git describe), remplacé par un tag de release dont le
37
+ # commit mergé est au moins aussi proche : la release est taguée sur le
38
+ # merge de la livraison dans main, invisible depuis develop pour
39
+ # describe ; à égalité, la version stable l'emporte sur la préversion
40
+ tag="$(
41
+ unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR
42
+ best="$(git -C "$1" describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true)"
43
+ dist=""
44
+ [ -z "$best" ] || dist="$(git -C "$1" rev-list --count "$best..HEAD")"
45
+ while IFS= read -r t; do
46
+ git -C "$1" merge-base --is-ancestor "$t^2" HEAD 2>/dev/null || continue
47
+ d="$(git -C "$1" rev-list --count "$t^2..HEAD")"
48
+ if [ -z "$best" ] || [ "$d" -lt "$dist" ] ||
49
+ { [ "$d" -eq "$dist" ] && [[ "$t" != *-* && "$best" == *-* ]]; }; then
50
+ best="$t" dist="$d"
51
+ fi
52
+ done < <(git -C "$1" tag --no-merged HEAD --list 'v[0-9]*' 2>/dev/null)
53
+ echo "$best"
54
+ )"
35
55
  [ -z "$tag" ] || REPLY="${tag#v}"
36
56
  fi
37
57
  }
package/hooks/pre-push CHANGED
@@ -46,8 +46,6 @@ if ! skipped protect-branch; then
46
46
  done <<<"$REFS"
47
47
  fi
48
48
 
49
- skipped tests && { info "Tests désactivés (repowarden.skip)."; exit 0; }
50
-
51
49
  REMOTE="${1:-origin}"
52
50
 
53
51
  # Fichiers modifiés par le push de <sha> : depuis l'état distant connu, ou pour
@@ -68,6 +66,95 @@ changed_files() {
68
66
  fi
69
67
  }
70
68
 
69
+ # Linting des fichiers modifiés (shellcheck, actionlint, etc.) : ce que la CI
70
+ # refusera doit être vu avant le push. Désactivable avec repowarden.skip lint.
71
+ if ! skipped lint; then
72
+ # Sévérité shellcheck : warning (défaut), error, style. Config : repowarden.lintSeverity.
73
+ cfg_r lintSeverity warning
74
+ LINT_SEVERITY="$REPLY"
75
+
76
+ # Commande d'installation suggérée selon l'OS
77
+ lint_install_cmd() {
78
+ case "$1" in
79
+ shellcheck)
80
+ case "${OSTYPE:-}" in
81
+ darwin*) printf 'brew install shellcheck' ;;
82
+ linux*) printf 'apt-get install shellcheck' ;;
83
+ msys*|cygwin*|win*) printf 'choco install shellcheck' ;;
84
+ *) printf 'https://www.shellcheck.net/' ;;
85
+ esac
86
+ ;;
87
+ actionlint)
88
+ case "${OSTYPE:-}" in
89
+ darwin*) printf 'brew install actionlint' ;;
90
+ linux*) printf 'go install github.com/rhysd/actionlint/cmd/actionlint@latest' ;;
91
+ msys*|cygwin*|win*) printf 'scoop install actionlint' ;;
92
+ *) printf 'https://github.com/rhysd/actionlint' ;;
93
+ esac
94
+ ;;
95
+ *) printf '%s' "$1" ;;
96
+ esac
97
+ }
98
+
99
+ # Vrai si le fichier $1 est un script shell (extension .sh/.bash ou shebang)
100
+ is_shell_script() {
101
+ local file="$1" first_line
102
+ case "$file" in
103
+ *.sh|*.bash) return 0 ;;
104
+ esac
105
+ # Pas d'extension reconnue : on lit la 1re ligne (shebang)
106
+ IFS= read -r first_line <"$file" 2>/dev/null || return 1
107
+ case "$first_line" in
108
+ '#!/bin/sh'|'#!/bin/bash'|'#!/usr/bin/env sh'|'#!/usr/bin/env bash'|'#!/usr/bin/env -S bash'*)
109
+ return 0 ;;
110
+ esac
111
+ return 1
112
+ }
113
+
114
+ lint_changed_files() {
115
+ local changed_file rc=0
116
+ # Les checks optionnels (style, info) ne sont activés que si la sévérité le demande
117
+ local shellcheck_opts="--severity=$LINT_SEVERITY"
118
+ case "$LINT_SEVERITY" in
119
+ style|info) shellcheck_opts="$shellcheck_opts -o all" ;;
120
+ esac
121
+ while IFS= read -r changed_file; do
122
+ [ -f "$changed_file" ] || continue
123
+ if is_shell_script "$changed_file"; then
124
+ if has shellcheck; then
125
+ step_t hook.pre_push.lint.run "shellcheck" "$changed_file"
126
+ # shellcheck disable=SC2086
127
+ shellcheck $shellcheck_opts "$changed_file" || rc=1
128
+ else
129
+ local install_cmd; install_cmd="$(lint_install_cmd shellcheck)"
130
+ warn_t hook.pre_push.lint.missing "shellcheck" "$changed_file" "$install_cmd"
131
+ [ -n "${REPOWARDEN_WARN_FILE:-}" ] && return 1
132
+ fi
133
+ elif case "$changed_file" in .github/workflows/*.yml|.github/workflows/*.yaml) true;; *) false;; esac; then
134
+ if has actionlint; then
135
+ step_t hook.pre_push.lint.run "actionlint" "$changed_file"
136
+ actionlint "$changed_file" || rc=1
137
+ else
138
+ local install_cmd; install_cmd="$(lint_install_cmd actionlint)"
139
+ warn_t hook.pre_push.lint.missing "actionlint" "$changed_file" "$install_cmd"
140
+ [ -n "${REPOWARDEN_WARN_FILE:-}" ] && return 1
141
+ fi
142
+ fi
143
+ done
144
+ return "$rc"
145
+ }
146
+
147
+ LINT_FAILED=0
148
+ while read -r _ local_sha _ remote_sha; do
149
+ # suppression de branche : rien à lint
150
+ if [ -z "$local_sha" ] || [[ "$local_sha" =~ ^0+$ ]]; then continue; fi
151
+ changed_files "$local_sha" "$remote_sha" | lint_changed_files || LINT_FAILED=1
152
+ done <<<"$REFS"
153
+ [ "$LINT_FAILED" -eq 0 ] || exit 1
154
+ fi
155
+
156
+ skipped tests && { info "Tests désactivés (repowarden.skip)."; exit 0; }
157
+
71
158
  run_tests_for() { test_projects <"$1"; }
72
159
 
73
160
  TESTED=""
@@ -2,7 +2,7 @@
2
2
  # Hook prepare-commit-msg : déduit le préfixe Conventional Commits du nom de branche.
3
3
  # branche feat/bean : git commit → éditeur prérempli avec "feat(bean): "
4
4
  # git commit -m "ajoute X" → "feat(bean): ajoute X"
5
- # Un message déjà conforme n'est pas modifié.
5
+ # Un message déjà conforme, ou qui a la forme d'un en-tête, n'est pas modifié.
6
6
  set -euo pipefail
7
7
 
8
8
  source "$(dirname "$0")/lib/common.sh"
@@ -36,6 +36,9 @@ fi
36
36
 
37
37
  SUBJECT="$(grep -v '^#' "$MSG_FILE" | sed -n 1p || true)"
38
38
  [[ "$SUBJECT" =~ $CC_PATTERN ]] && exit 0
39
+ # En-tête déjà tenté (mot: ou mot(portée):), même raté : jamais enveloppé,
40
+ # commit-msg le juge (« doc: x » deviendrait « feat(y): doc: x », accepté)
41
+ [[ "$SUBJECT" =~ ^[A-Za-z]+(\([^\)]*\))?!?: ]] && exit 0
39
42
  [[ "$SUBJECT" =~ ^(Merge|Revert|fixup!|squash!) ]] && exit 0
40
43
 
41
44
  # Ajoute le préfixe en tête de la première ligne (vide ou non)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repowarden",
3
- "version": "4.4.0",
3
+ "version": "4.7.1",
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",
@@ -10,11 +10,11 @@
10
10
  "ci",
11
11
  "release"
12
12
  ],
13
- "homepage": "https://simbienvenuehoulboumi.github.io/repowarden/",
14
- "bugs": "https://github.com/SimBienvenueHoulBoumi/repowarden/issues",
13
+ "homepage": "https://simdevwarden.github.io/repowarden/",
14
+ "bugs": "https://github.com/simdevWarden/repowarden/issues",
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/SimBienvenueHoulBoumi/repowarden.git"
17
+ "url": "git+https://github.com/simdevWarden/repowarden.git"
18
18
  },
19
19
  "license": "MIT",
20
20
  "author": "SimBienvenueHoulBoumi (https://github.com/SimBienvenueHoulBoumi)",