Aller au contenu

ADR 0040 — Le sujet d'un commit est une syntaxe, pas une phrase française

  • Statut : Accepté — 2026-07-20
  • Chantier : EPIC #2104 / lot 0 (#2105)

Contexte

Le dépôt n'a plus publié aucune version entre le 18 et le 20 juillet 2026, alors que 58 commits de type releasable s'y sont accumulés. Le job release a fini vert à chaque push :

[semantic-release] Analyzing commit: fix(captures) : refuser une capture…
[semantic-release] Analysis of 153 commits complete: no release

La cause tient dans un espace. Le dépôt écrit en français, et le français met une espace insécable avant les deux-points. La pratique a donc glissé, sans que personne ne le décide, de type(scope): vers type(scope) :. Conventional Commits n'admet pas cette forme. Le parser de semantic-release@24 en fait la démonstration :

Sujet type reconnu
fix(captures) : refuser une capture… null
fix(captures): refuser une capture… fix

Sur les 400 derniers commits : 136 non conformes, 10 conformes. Le dernier commit conforme date du 18 juillet, exactement la date du dernier tag. CONTRIBUTING.md documentait pourtant la bonne forme depuis toujours : la dérive s'est faite contre une règle écrite, ce qui est précisément le cas qu'une règle écrite ne suffit pas à empêcher.

Le vrai défaut n'est pas la dérive, c'est son silence. Un dépôt qui ne publie plus rien présentait exactement la même CI verte qu'un dépôt qui publie : « aucun changement pertinent » est un verdict de succès. Aucun contrôle ne portait sur la convention, et le seul signal - l'absence de release - est une non-occurrence, ce que personne ne surveille.

Décision

Dans un sujet de commit, : est un token de syntaxe, pas une ponctuation de phrase. La règle typographique française de l'espace avant les deux-points ne s'y applique pas, pas plus qu'elle ne s'applique au : d'un switch Java ou au : d'un key: value YAML. La forme est type(scope): sujet, et le sujet lui-même reste du français, avec sa typographie.

Trois conséquences.

1. Le contrôle porte sur le titre de la PR, pas sur les commits. Le dépôt fusionne en squash avec squash_merge_commit_title = PR_TITLE : le titre de la PR devient le sujet du commit sur main, et les messages des commits de branche sont écartés à la fusion. Valider les commits individuels policerait des messages jetés, tout en laissant passer le seul texte que semantic-release lira. .github/workflows/titre-pr.yml valide donc le titre, et lui seul.

2. Le parser reste élargi, à titre transitoire. .releaserc.json accepte l'espace (headerPattern avec \s*:), sur le commit-analyzer et sur le release-notes-generator : sans le second, la version sortirait avec des notes vides. Cet élargissement existe pour rattraper les 58 commits bloqués, vérifié sur la vraie plage v2.16.1..HEAD (159 commits : aucune release avec le parser par défaut, minor avec le parser élargi). Il devient inutile dès que le garde-fou du titre a fait son effet, mais il est conservé comme filet : il ne coûte rien, et il évite qu'un contournement du contrôle re-fige la publication.

3. Le message d'erreur nomme la cause. Un titre refusé pour cette raison précise affiche la forme attendue et l'explication du :-token. Une règle contre-intuitive doit se réexpliquer au moment où elle mord, sinon elle se fait contourner.

Conséquences

Ce qu'on gagne. La publication redémarre, et une dérive de convention devient visible à la PR plutôt qu'invisible pendant deux jours. Le garde-fou a été vérifié en le voyant rouge sur la forme exacte qui a arrêté la release, et sur onze autres cas dont un titre porteur d'une tentative d'injection de commande.

Ce qu'on perd. Les 58 commits rattrapés portent des sujets non conformes : ils apparaîtront dans les notes de la version de rattrapage tels qu'ils ont été écrits. Le journal de cette version sera donc plus long et moins homogène que d'habitude. C'est le prix du rattrapage, préféré à la perte pure et simple de deux jours d'historique.

Ce qui reste ouvert. Le contrôle ne s'applique qu'aux PR. Un push direct sur main y échapperait, mais c'est déjà interdit par ailleurs (CLAUDE.md, garde-fous non négociables).

Une leçon plus générale. Ce chantier a été ouvert sur trois autres sujets - artefacts autonomes, découpage de la CI, montée de version - et a trouvé celui-ci en cartographiant. Un pipeline dont la sortie normale est ne rien faire a besoin d'un contrôle qui distingue « rien à publier » de « incapable de publier ». La même question se pose pour les autres étapes silencieuses de la chaîne.