diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bfb0319b..717241b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,8 @@ # Contributing to FirstClassErrors +🌍 **Languages:** +🇬🇧 English (this file) | đŸ‡«đŸ‡· [Français](./doc/CONTRIBUTING.fr.md) + FirstClassErrors treats errors as first-class, documented, diagnosable concepts. The history of the repository should be as legible as the errors the library produces. This guide defines how commits are written here. diff --git a/doc/CONTRIBUTING.fr.md b/doc/CONTRIBUTING.fr.md new file mode 100644 index 00000000..6f83a4df --- /dev/null +++ b/doc/CONTRIBUTING.fr.md @@ -0,0 +1,546 @@ +# Contribuer Ă  FirstClassErrors + +🌍 **Langues:** +🇬🇧 [English](../CONTRIBUTING.md) | đŸ‡«đŸ‡· Français (ce fichier) + +FirstClassErrors considĂšre les erreurs comme des concepts de premier ordre, +documentĂ©s et diagnostiquables. L’historique du dĂ©pĂŽt doit ĂȘtre aussi lisible que +les erreurs que la bibliothĂšque produit. Ce guide dĂ©finit la façon dont les +commits sont Ă©crits ici. + +## Compiler et tester + +* Framework cible : **.NET Standard 2.0**. +* Compiler : `dotnet build FirstClassErrors.sln` +* Tester : `dotnet test FirstClassErrors.sln` +* Tests des analyzers, lorsque vous touchez aux analyzers : `dotnet test FirstClassErrors.Analyzers.UnitTests` + +Voir [`CLAUDE.md`](../CLAUDE.md) pour l’organisation du projet et les lignes +directrices plus larges concernant les changements. + +## Activer le hook de message de commit + +Un hook `commit-msg` vĂ©rifie chaque message par rapport Ă  la convention ci-dessous +avant qu’il ne soit enregistrĂ©. Il est versionnĂ© sous `.githooks/` ; activez-le une +fois par clone : + +``` +git config core.hooksPath .githooks +``` + +La mĂȘme vĂ©rification s’exĂ©cute en CI sur chaque pull request : un hook contournĂ© +(`git commit --no-verify`) est donc rattrapĂ© avant le merge. Les commits de merge +en sont exemptĂ©s. La vĂ©rification elle-mĂȘme se trouve dans +`tools/commit-lint/lint-commit-message.sh`, partagĂ©e par le hook et la CI pour que +les deux ne divergent jamais. + +Le hook laisse passer les commits `fixup!`, `squash!` et `amend!` pour que vous +puissiez construire un rebase autosquash ; la CI les rejette, alors Ă©liminez-les +(squash) avant le merge. + +## Branches + +### Pourquoi + +Une pull request se lit par rapport Ă  sa branche. Deux branches portent la mĂȘme +fonctionnalitĂ©. La premiĂšre a Ă©tĂ© coupĂ©e depuis `origin/main` il y a une heure et +contient trois commits — le diff de sa pull request *est* la fonctionnalitĂ©, et rien +d’autre. La seconde a Ă©tĂ© coupĂ©e depuis un `main` local vieux de trois semaines, puis +ravivĂ©e pour une deuxiĂšme idĂ©e une fois la premiĂšre mergĂ©e ; son diff porte quinze +commits, dont douze dĂ©jĂ  sur `main`, et le relecteur ne peut distinguer la demande du +rĂ©sidu. + +``` +$ git log --oneline origin/main..HEAD # la seconde branche +a1b2c3d feat(core): the change the pull request is for +9f8e7d6 feat(gendoc): a renderer already merged, dragged along +...douze autres commits dĂ©jĂ  sur main... +``` + +La branche n’est pas le travail. C’est l’espace de travail jetable d’**une seule** +pull request — coupĂ©e fraĂźchement depuis le remote, utilisĂ©e une fois, jetĂ©e au merge. +Tout ce qui suit en dĂ©coule. + +### La rĂšgle + +* Une branche porte **une seule pull request**, et cette pull request porte une seule + unitĂ© de travail cohĂ©rente. Tout travail sans rapport avec cette unitĂ© DOIT prendre + sa propre branche — la lecture, au niveau de la branche, de *deux intentions, deux + commits*. +* `main` n’est Ă©crite **que** par merge. Aucun commit n’atterrit directement sur + `main` ; elle avance lorsqu’une pull request relue est mergĂ©e. +* Une branche DOIT ĂȘtre coupĂ©e depuis la **pointe de `origin/main`**, fraĂźchement + rĂ©cupĂ©rĂ©e (fetch) — jamais depuis un `main` local qui peut ĂȘtre en retard, ni depuis + une autre branche thĂ©matique : + + ``` + git fetch origin + git switch -c / origin/main + ``` +* Un nom de branche DOIT prendre la forme `/`. L’`` + est l’identifiant GitHub du propriĂ©taire de la branche — la personne ou l’outil Ă  qui + le travail appartient : `sylvain/
`, `claude/
`, `dependabot/
`. La + `` DOIT ĂȘtre en anglais, en minuscules, en kebab-case, et nommer le + changement, pas le fichier qu’il touche : `sylvain/gendoc-invalid-culture`, jamais + `sylvain/GenDoc.cs`. +* Un outil qui gĂ©nĂšre ses propres branches possĂšde son espace de noms et conserve sa + structure native en dessous — `dependabot/nuget/Newtonsoft.Json-13.0.1`, + `renovate/
`. La forme `/` lie les branches qu’une personne + ou un agent coupe Ă  la main ; le schĂ©ma d’un gĂ©nĂ©rateur lui appartient, et le combattre + n’apporte rien. +* Le nom de branche ne porte **aucun type**. Le type est une propriĂ©tĂ© de chaque commit, + vĂ©rifiĂ©e lĂ  par le hook et par la CI ; une branche rassemble des commits de plusieurs + types, et un prĂ©fixe unique en nommerait un et cacherait les autres — la mĂȘme raison + pour laquelle un titre de pull request Ă  intentions multiples ne prend pas de `type:` + (voir *Titres de pull request* plus bas). Le propriĂ©taire est ce que le nom ajoute, car + le propriĂ©taire est ce que les commits ne portent pas. +* Une branche vit exactement aussi longtemps que sa pull request reste **ouverte**, et + PEUT ĂȘtre rĂ©utilisĂ©e uniquement pour cette mĂȘme demande — corrections de relecture, + changements demandĂ©s sur la pull request. +* Une fois la pull request **mergĂ©e ou fermĂ©e**, la branche est terminĂ©e. Elle NE DOIT + PAS ĂȘtre ravivĂ©e, pas mĂȘme pour un suivi sur le mĂȘme sujet : une pull request mergĂ©e ne + peut pas dĂ©crire un nouveau travail, et une pull request fermĂ©e a Ă©tĂ© mise de cĂŽtĂ©. Le + suivi passe par une nouvelle branche, coupĂ©e fraĂźchement depuis `origin/main`. +* Pour reporter la progression de `main` dans une branche ouverte : tant que la branche + n’est qu’à vous, **rebasez-la** sur `origin/main` ; dĂšs que d’autres ont pu baser du + travail dessus, **mergez** plutĂŽt `origin/main` dedans. L’un comme l’autre garde la + branche Ă  jour sans réécrire ce qu’un collaborateur a dĂ©jĂ  rĂ©cupĂ©rĂ© (pull). +* Réécrire l’historique d’une branche — un force-push, un `git rebase -i` — est + acceptable tant que la branche n’est **qu’à vous**, et c’est ainsi qu’un message de + commit rejetĂ© par le lint ou par un relecteur se corrige, mĂȘme en cours de relecture : + un message rejetĂ© ne peut pas ĂȘtre corrigĂ© par un commit de suivi (voir *Messages de + commit*). DĂšs que quelqu’un d’autre a pu **baser du travail dessus**, son historique NE + DOIT PAS ĂȘtre réécrit — un force-push jette ce qui a Ă©tĂ© construit par-dessus. Le + travail qui n’est pas le vĂŽtre n’est pas le vĂŽtre Ă  réécrire ou Ă  supprimer. +* Avant d’ouvrir la pull request, **lisez la branche** par rapport Ă  un `origin/main` + frais : + + ``` + git fetch origin + git log --oneline origin/main..HEAD # les commits que la demande ajoute + git diff --stat origin/main...HEAD # les fichiers qu’elle touche + ``` + + Si l’un ou l’autre montre quelque chose qui ne concerne pas la demande, la branche a + dĂ©rivĂ© — scindez-la avant la relecture, pas aprĂšs. + +### La doctrine + +**La branche est l’unitĂ© de travail en cours ; la pull request est ce qu’elle devient.** +Une branche, une pull request, une unitĂ© de travail — le mĂȘme un-pour-un que la doctrine +trace entre le commit et son changement. + +**Le nom dit qui, les commits disent quoi.** Une branche possĂšde une pull request qui +peut porter Ă  la fois une fonctionnalitĂ©, le refactoring qui l’a prĂ©parĂ©e, et ses tests ; +aucun type unique ne la nomme honnĂȘtement. Le type vit sur chaque commit, lĂ  oĂč le hook +l’impose. Le nom de branche ajoute la seule chose que les commits omettent — Ă  qui +appartient le travail — de sorte que `claude/
` et `dependabot/
` ne sont pas des +exceptions mais la rĂšgle elle-mĂȘme, lue de la mĂȘme façon sur un humain ou sur une machine. + +**Une branche est jetable.** Son historique est prĂ©servĂ© par le commit de merge qui +l’intĂšgre ; la rĂ©fĂ©rence elle-mĂȘme est coupĂ©e Ă  neuf et supprimĂ©e au merge. Rien de +prĂ©cieux ne vit uniquement sur une branche. + +**Une branche mergĂ©e est Ă©puisĂ©e.** La raviver empile du nouveau travail sur un historique +dĂ©jĂ  rĂ©glĂ© et bifurque depuis un `main` qui a bougĂ©. Le relecteur en paie le prix, lisant +le rĂ©sidu comme s’il s’agissait de la demande. + +**Coupez depuis le remote, pas depuis le local.** Un `main` local est en retard +silencieusement ; une branche coupĂ©e depuis lui traĂźne ce retard dans chaque diff. +`origin/main`, fraĂźchement rĂ©cupĂ©rĂ©e, est la seule base. + +**Un travail sans rapport est une nouvelle branche, pas un passager.** Une branche qui +porte deux changements force une pull request qui ne peut dĂ©crire ni l’un ni l’autre — la +forme, au niveau de la branche, du commit qui porte deux intentions. + +### Exemples + +| Branche | Pourquoi elle convient | +|---|---| +| `sylvain/add-html-renderer` | PropriĂ©taire et changement, nommĂ©s simplement. Le type qu’elle portera vit dans ses commits. | +| `claude/gendoc-invalid-culture` | La branche d’un agent ; la description nomme la zone, pas `GenDoc.cs`. | +| `dependabot/nuget/Newtonsoft.Json-13.0.1` | Un gĂ©nĂ©rateur conserve sa structure native sous son espace de noms `dependabot/`. | +| `sylvain/security-policy` | La description seule porte le sujet ; la branche n’a besoin d’aucun type. | + +### Anti-patterns + +| Branche ou manƓuvre | Ce qui ne va pas | +|---|---| +| un commit poussĂ© directement sur `main` | `main` n’avance que par merge. MĂȘme un correctif d’une ligne prend une branche et une pull request. | +| `patch-1`, `my-work`, `tmp` | Aucun propriĂ©taire, et ça ne nomme rien. Un nom de branche se lit dans la liste des pull requests ; il DOIT dire qui possĂšde quoi. | +| `feat/add-html-renderer` | Un type Ă  la place du propriĂ©taire. Le type appartient aux commits ; le prĂ©fixe de branche est le propriĂ©taire : `sylvain/add-html-renderer`. | +| `sylvain/GenDoc.cs` | Nomme un fichier. Il devrait nommer le changement : `sylvain/gendoc-invalid-culture`. | +| `sylvain/corrige-le-rendu` | Pas en anglais. | +| raviver une branche mergĂ©e `claude/add-html-renderer` pour un suivi | Une branche mergĂ©e est Ă©puisĂ©e. Coupez le suivi Ă  neuf depuis `origin/main`. | +| une branche coupĂ©e depuis un `main` local vieux de trois semaines | Le diff de la pull request se remplit de commits dĂ©jĂ  sur `main`. Fetchez d’abord ; coupez depuis `origin/main`. | +| une seule branche portant une fonctionnalitĂ© et un ajustement CI sans rapport | Aucune pull request unique ne dĂ©crit les deux. Deux branches, deux demandes. | +| force-pusher une branche sur laquelle d’autres ont construit | Réécrit un historique partagĂ© et jette le travail poussĂ© par-dessus. Ne réécrivez que tant que la branche n’est qu’à vous. | + +## Messages de commit + +Cette section adapte la spĂ©cification [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/). +Les mots-clĂ©s DOIT, NE DOIT PAS, DEVRAIT et PEUT doivent ĂȘtre interprĂ©tĂ©s comme dĂ©crit +dans [BCP 14](https://www.rfc-editor.org/info/bcp14), et uniquement lorsqu’ils +apparaissent en majuscules. + +### Pourquoi + +Une release se prĂ©pare. Il faut savoir ce qu’une branche contient, ce qu’il faut y +reporter, et quel numĂ©ro de version en sort. + +``` +a3f1c2e fix bug +8b41d90 update renderer +1d0e4aa wip +``` + +Cet historique n’apprend rien. Chaque question force Ă  ouvrir un diff. + +``` +a3f1c2e fix(gendoc): render error examples with the invariant culture +8b41d90 feat(gendoc): emit an RFC 9457 problem type in examples +1d0e4aa refactor(gendoc): extract output routing into a writer +``` + +Celui-ci rĂ©pond Ă  trois questions sans ouvrir un seul diff : ce que la branche contient, +quel commit reporter dans une release, et si la version passe de `1.4.2` Ă  `1.4.3` ou Ă  +`1.5.0`. C’est la lecture du relecteur, et de celui qui prĂ©pare la release. Demain, ce +sera celle d’un outil. + +### La rĂšgle + +La rĂšgle porte sur **chaque commit**, pas sur un message de merge. Un commit voyage +seul : il est cherry-piquĂ© sur une branche de release, listĂ© dans un `git log --oneline`, +lu isolĂ©ment six mois plus tard. Son message DOIT se suffire Ă  lui-mĂȘme. + +#### Forme + +``` +[()][!]: + +[body] + +[footers] +``` + +* Le commit DOIT commencer par un type, Ă©ventuellement suivi d’un scope et d’un `!`, puis + d’un deux-points et d’un espace. +* Tout ce qui est Ă©crit dans le message DOIT ĂȘtre en anglais — en-tĂȘte, corps et footers. +* Un commit DOIT porter un seul type, celui de son intention. Deux intentions + indĂ©pendantes DOIVENT ĂȘtre deux commits : le message force la scission qui devrait avoir + lieu. + +#### Types + +ClassĂ©s ici avec d’abord les deux types qui pilotent la version, puis le reste par ordre +alphabĂ©tique. La liste est fermĂ©e. + +| Type | Quand l’utiliser | Effet minimal sur la version | +|---|---|---| +| `feat` | Une nouvelle capacitĂ©, visible pour le consommateur du package | `MINOR` | +| `fix` | La correction d’un comportement dĂ©fectueux | `PATCH` | +| `build` | SystĂšme de build, dĂ©pendances, packaging, artefacts de dĂ©ploiement | aucun imposĂ© | +| `chore` | Ce qui ne touche ni le code de production ni sa livraison | aucun imposĂ© | +| `ci` | Configuration du pipeline | aucun imposĂ© | +| `docs` | Documentation uniquement | aucun imposĂ© | +| `perf` | Un gain de performance, Ă  comportement observable constant | aucun imposĂ© | +| `refactor` | Restructuration, Ă  comportement observable constant | aucun imposĂ© | +| `revert` | L’annulation d’un commit antĂ©rieur | selon ce qu’il annule | +| `style` | Formatage sans effet sĂ©mantique | aucun imposĂ© | +| `test` | Tests uniquement | aucun imposĂ© | + +Le type DOIT ĂȘtre en minuscules et appartenir Ă  ce tableau. Un breaking change portĂ© par +n’importe lequel de ces types produit un `MAJOR`. + +#### Scope + +Le scope PEUT ĂȘtre fourni. Lorsqu’il est prĂ©sent, il DOIT ĂȘtre en minuscules et DOIT ĂȘtre +l’un des suivants : + +| Scope | Couvre | +|---|---| +| `core` | `FirstClassErrors` — la bibliothĂšque d’exĂ©cution (`Error`, `Outcome`, `ErrorCode`, `ErrorContextKey`, 
) | +| `analyzers` | `FirstClassErrors.Analyzers` — les analyzers Roslyn et leurs diagnostics `FCExxx` | +| `cli` | `FirstClassErrors.Cli` — l’outil en ligne de commande | +| `gendoc` | `FirstClassErrors.GenDoc` et son worker — le gĂ©nĂ©rateur de documentation | +| `testing` | `FirstClassErrors.Testing` — le package de support aux tests | + +Cette liste vit ici, dans le dĂ©pĂŽt, lĂ  oĂč un outil peut la vĂ©rifier. Un scope NE DOIT PAS +ĂȘtre un nom de fichier ni un nom de classe : ceux-ci bougent ; la zone qu’ils habitent, +non. `fix(core):`, jamais `fix(ErrorCode.cs):`. + +Le scope est optionnel ici parce que les deux packages publiĂ©s (`FirstClassErrors` et +`FirstClassErrors.Testing`) partagent une seule version pilotĂ©e par tag : le scope ne porte +aucun poids de versioning, seulement de la lisibilitĂ©. Ce qui n’appartient pas Ă  un +composant ne prend **aucun** scope — l’infrastructure du dĂ©pĂŽt (la solution, +`Directory.Build.props`, les workflows, `.gitignore`, `CLAUDE.md`), la documentation Ă  +l’échelle du dĂ©pĂŽt, les ADR, et les exemples de `FirstClassErrors.Usage` : `ci: 
`, +`docs: 
`. + +Lorsqu’un changement atomique traverse plusieurs composants, le commit DOIT porter tous +leurs scopes, sĂ©parĂ©s par des virgules sans espace et classĂ©s par ordre alphabĂ©tique. +L’ordre est alphabĂ©tique pour qu’une paire donnĂ©e s’écrive toujours de la mĂȘme façon, et se +retrouve avec un seul `git log --grep`. + +``` +fix(cli,gendoc): thread cancellation through the generate command +``` + +#### Description + +* Elle DOIT ĂȘtre Ă  l’impĂ©ratif prĂ©sent : `add`, pas `added` ni `adds`. La description + complĂšte une phrase — *If applied, this commit will 
* — et seul l’impĂ©ratif y convient : + *
will add Outcome.Map*. +* Elle DOIT commencer par une lettre minuscule et NE DOIT PAS se terminer par un point. La + ligne d’en-tĂȘte n’est pas une phrase ; c’est un titre. +* La ligne d’en-tĂȘte complĂšte — type, scope optionnel, `!` optionnel, deux-points et + description — DOIT tenir en 72 caractĂšres. Au-delĂ , une fois le hash abrĂ©gĂ© prĂ©fixĂ©, elle + dĂ©borde des 80 colonnes d’un terminal dans un `git log --oneline`. + +#### Corps + +Le corps PEUT ĂȘtre fourni, aprĂšs une ligne vide. Il explique **pourquoi** le changement a +lieu — la contrainte, le symptĂŽme, le compromis. Le *quoi* est dĂ©jĂ  dans le diff ; le +rĂ©pĂ©ter est du bruit. + +Lorsque ce pourquoi n’est pas lisible depuis le diff, le corps DEVRAIT ĂȘtre fourni. S’en +abstenir se paie six mois plus tard, sur un commit que plus personne ne peut interprĂ©ter. + +#### Footers + +Les footers PEUVENT ĂȘtre fournis, aprĂšs une ligne vide. Chaque footer DOIT prendre la forme +`Token: value`. Le token DOIT ĂȘtre des mots sĂ©parĂ©s par des traits d’union, **chaque mot +avec une majuscule** : `Co-Authored-By`, `Reviewed-By`, `Refs`, `Reverts`. +`BREAKING CHANGE` est la seule exception Ă  cette forme. + +> Cette casse « chaque mot avec une majuscule » est un Ă©cart dĂ©libĂ©rĂ© par rapport Ă  la +> convention habituelle Ă  initiale unique. Elle existe pour que les footers Ă©crits Ă  la +> main restent cohĂ©rents avec les trailers que les commits automatisĂ©s de ce dĂ©pĂŽt portent +> dĂ©jĂ  — `Co-Authored-By`, `Claude-Session`. Une seule rĂšgle pour tous les footers vaut +> mieux que deux. + +Lorsqu’une issue existe, son numĂ©ro DOIT figurer dans un footer `Refs:`, et NE DOIT PAS +apparaĂźtre dans la description — la description Ă©nonce le changement, pas l’endroit oĂč il a +Ă©tĂ© demandĂ©. Le footer porte la clĂ© (`#142`), jamais l’URL : un message de commit ne se +réécrit pas, et le numĂ©ro survit lĂ  oĂč une adresse ne survit pas. (Un footer d’outillage +comme `Claude-Session` est une URL par nature, et est exemptĂ© de ce dernier point.) + +Un commit n’est **pas** l’endroit pour fermer une issue. La fermeture relĂšve du workflow du +dĂ©pĂŽt : mettez `Closes #142` dans la description de la pull request, et GitHub ferme l’issue +au merge. Le commit lui-mĂȘme reste neutre, portant tout au plus un `Refs:`. + +#### Breaking changes + +Un breaking change DOIT ĂȘtre signalĂ© deux fois : par un `!` placĂ© juste avant le +deux-points, et par un footer `BREAKING CHANGE:` en majuscules. + +``` +feat(core)!: fail Outcome.To with an Outcome instead of throwing + +BREAKING CHANGE: Outcome.To returns a failed Outcome where it +used to throw on a null conversion. Callers must handle the Outcome instead +of catching. +``` + +Le `!` est ce que l’on voit dans un `git log --oneline`. Le footer est ce que l’on lit au +moment de migrer. Les deux ont des lecteurs diffĂ©rents ; aucun ne remplace l’autre. + +Ce qui est cassant se lit sur le **contrat publiĂ©**, pas sur le code interne. Dans ce dĂ©pĂŽt, +ce contrat inclut explicitement les codes d’erreur, les identifiants de diagnostic +(`FCExxx`) et les types publics : renommer l’un d’eux est un breaking change (voir +`CLAUDE.md`). Renommer un type `internal` ne casse rien. + +#### Reverts + +Un commit de revert DOIT porter le type `revert`, reprendre la description du commit annulĂ©, +et rĂ©fĂ©rencer son SHA dans un footer `Reverts:`. + +``` +revert(gendoc): emit an RFC 9457 problem type in examples + +Reverts: b36765a +``` + +L’effet d’un revert sur la version se qualifie comme celui de n’importe quel commit : du +point de vue du consommateur, sur le contrat publiĂ©. Annuler un changement pas encore +publiĂ© neutralise son effet. Retirer une capacitĂ© dĂ©jĂ  publiĂ©e est un breaking change, et le +commit DOIT alors porter le `!` et le footer `BREAKING CHANGE`. + +### La doctrine + +**L’issue est l’unitĂ© de la demande, le commit l’unitĂ© du changement.** Une issue produit +autant de commits qu’elle porte d’intentions : la fonctionnalitĂ©, le refactoring qui l’a +prĂ©parĂ©e, le correctif trouvĂ© en relecture. Chacun porte son propre type, tous portent le +mĂȘme `Refs:`. + +**Le type est l’intention, pas le contenu du diff.** Une fonctionnalitĂ© arrive avec ses +tests, sa documentation d’API, son exemple : le commit reste un `feat`. `test` et `docs` +dĂ©signent un changement qui touche *uniquement* aux tests, *uniquement* Ă  la documentation. +Scinder un `feat` en cinq commits parce qu’il s’étend sur cinq rĂ©pertoires fabrique des +commits qui ne compilent pas seuls. + +**`feat` ou `fix` se dĂ©cide depuis l’extĂ©rieur du composant.** Le critĂšre n’est pas la +taille du diff, c’est ce que le consommateur du package observe. Trois lignes qui restaurent +le comportement promis sont un `fix`. Une ligne qui ouvre une nouvelle capacitĂ© est un +`feat`. + +**`refactor` et `perf` font une promesse : le comportement observable ne change pas.** Un +`refactor` qui corrige un bug au passage est un `fix` mal Ă©tiquetĂ© — et la correction devient +invisible pour celui qui prĂ©pare la release. + +**`chore` n’est pas la poubelle.** Tout ce qui ne rentre nulle part y atterrit, et le type +finit par ne plus rien signifier. Avant d’écrire `chore`, relisez le tableau. + +**Ce qui est cassant se lit sur le contrat publiĂ©**, pas sur le code interne. Un type +`internal` renommĂ© ne casse rien. Un type de retour modifiĂ©, un champ sĂ©rialisĂ© renommĂ©, un +code d’erreur, un identifiant de diagnostic — eux, oui. + +**Le mauvais type se corrige avant le merge.** Un `git rebase -i` réécrit le message tant que +le commit n’a pas atteint une branche partagĂ©e. AprĂšs cela, le coĂ»t de la correction dĂ©passe +le coĂ»t de l’erreur : laissez-le et passez Ă  autre chose. + +**Le numĂ©ro de version se dĂ©cide en lisant l’historique.** Celui qui prĂ©pare la release y lit +l’incrĂ©ment : un `fix` isolĂ© donne un `PATCH`, un `feat` impose au moins un `MINOR`, un +breaking change impose un `MAJOR`. `FirstClassErrors` et `FirstClassErrors.Testing` partagent +une seule version pilotĂ©e par tag, donc l’incrĂ©ment le plus Ă©levĂ© de la release s’applique aux +deux. + +**Qui dĂ©cide de quoi.** L’auteur du commit choisit le type et le scope. Le relecteur de la +pull request refuse un message non conforme comme il refuse du code non conforme. Les +mainteneurs sont responsables de la liste des scopes et de la liste des types. + +### Exemples + +**Une fonctionnalitĂ©, avec scope et issue.** + +``` +feat(analyzers): add FCE016 for an undocumented error code + +Refs: #142 +``` + +**Un correctif dont le pourquoi n’est pas lisible depuis le diff.** + +``` +fix(gendoc): render error examples with the invariant culture + +Sample amounts were formatted with the host's culture, so the Verify +baselines matched on an invariant machine and failed on a comma-decimal one. +CI and developers disagreed on the very same commit. + +Refs: #128 +``` + +**Un refactoring, ne promettant rien d’autre qu’un comportement identique.** Ni corps ni +footer : le diff dit tout. + +``` +refactor(core): extract transience computation into TransienceCalculator +``` + +**Un breaking change, avec l’instruction de migration.** + +``` +feat(core)!: fail Outcome.To with an Outcome instead of throwing + +BREAKING CHANGE: Outcome.To returns a failed Outcome where it +used to throw on a null conversion. Callers must handle the Outcome instead +of catching. + +Refs: #150 +``` + +### Anti-patterns + +ClassĂ©s comme le sont les rĂšgles : type, scope, description, corps, breaking, issue. + +| Message | Ce qui ne va pas | +|---|---| +| `chore: handle a null error code` | Un `fix` dĂ©guisĂ©. Celui qui prĂ©pare la release ne le verra pas, et la version ne bougera pas alors qu’elle le devrait. | +| `feat: refactor the extraction reader` | Le type contredit la description. L’un des deux ment. | +| `fix(core): correct transience and add a CI cache` | Deux changements, deux commits. Aucune version ne peut dĂ©crire celui-ci. | +| `fix(ErrorCode.cs): formatting` | Le scope nomme un fichier. Il dĂ©signe une zone : `core`. | +| `feat(gendoc, cli): carry the source description` | Un espace aprĂšs la virgule, et l’ordre n’est pas alphabĂ©tique. Deux orthographes pour une mĂȘme paire — Ă©crivez `feat(cli,gendoc):`. | +| `fix(core): Fixed the null dereference.` | Majuscule, passĂ©, point final. Trois rĂšgles de forme enfreintes, un seul mot utile. | +| `feat(core): add support` | Un support pour quoi ? La description doit se suffire Ă  elle-mĂȘme dans un `git log`. | +| `fix(core): change line 42 of Error` | La description nomme une ligne. Elle devrait nommer un changement. | +| `fix(gendoc): render with the invariant culture` — corps : `Replaced DateTime.Now with CultureInfo.InvariantCulture` | Le corps rĂ©pĂšte le diff. Il devrait dire pourquoi la culture variait d’un hĂŽte Ă  l’autre. | +| `feat(core)!: fail Outcome.To with an Outcome` — sans footer | Le `!` avertit ; il ne fait migrer personne. | +| `feat(core): add Outcome.Map (#142)` | L’issue mange les 72 caractĂšres de la description. Sa place est un footer. | +| `refs: #142` | Token en minuscules. Le token du footer est `Refs`. | + +### Adoption + +Ce guide est la rĂšgle pour les commits de ce dĂ©pĂŽt. S’en Ă©carter requiert une justification — +un ADR sous `maintainers/adr/`, ou une mise Ă  jour de ce guide. + +Il s’applique Ă  partir de son adoption, Ă  chaque commit créé aprĂšs. L’historique antĂ©rieur +n’est pas réécrit. + +L’application repose aujourd’hui sur la relecture de pull request, qui refuse un message non +conforme et laisse l’auteur le corriger avant le merge. Le dĂ©pĂŽt verrouillera la rĂšgle Ă  son +tour : un hook `commit-msg` qui refuse le message Ă  l’écriture, doublĂ© d’une vĂ©rification CI, +puisqu’un hook local peut ĂȘtre contournĂ©. Les commits de merge, gĂ©nĂ©rĂ©s par GitHub, en sont +exemptĂ©s. + +### CrĂ©dits + +Cette section adapte la spĂ©cification [Conventional Commits 1.0.0](https://www.conventionalcommits.org/en/v1.0.0/), +publiĂ©e sous [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). + +## Titres de pull request + +La convention ci-dessus rĂ©git chaque commit. Une pull request a besoin d’une ligne Ă  elle, et +ce n’est pas le mĂȘme objet : le commit est l’unitĂ© du changement, la **pull request l’unitĂ© de +la demande** — la relation que la doctrine trace dĂ©jĂ  entre le commit et l’issue. Une pull +request PEUT donc rassembler plusieurs commits, de plusieurs types. + +Son titre se lit Ă  trois endroits : la liste des pull requests ouvertes, le commit +`Merge pull request #NN` que GitHub Ă©crit lorsque la branche est intĂ©grĂ©e (ce dĂ©pĂŽt fusionne +avec un commit de merge), et le brouillon des notes de version. Il mĂ©rite le mĂȘme soin qu’un +en-tĂȘte de commit. Contrairement Ă  un commit, il n’est **pas** lintĂ© ; il tient sur la +relecture, comme le code. + +### La rĂšgle + +* Le titre DOIT ĂȘtre en **anglais**, comme tout le reste consignĂ© ici. +* Il DOIT nommer la pull request **entiĂšre**, pas l’un de ses commits. Les types par commit + vivent dans les commits, lĂ  oĂč le hook et la CI les vĂ©rifient ; le titre dit ce que la + branche livre. +* Sa forme dĂ©coule du nombre d’intentions que porte la pull request : + * **Une seule intention** — la branche fait une seule sorte de chose. Le titre DOIT + reflĂ©ter l’en-tĂȘte de commit auquel il se rĂ©duit : `[()][!]: `, + sous les rĂšgles mĂȘmes de la section ci-dessus — impĂ©ratif prĂ©sent, minuscule aprĂšs le + deux-points, pas de point final. Le titre d’une pull request Ă  un seul commit est + l’en-tĂȘte de ce commit, mot pour mot. + * **Plusieurs intentions** — la branche porte une fonctionnalitĂ© et le refactoring qui l’a + prĂ©parĂ©e, ou un correctif et le test qui le fige. Le titre NE DOIT PAS emprunter un unique + prĂ©fixe `type:` : il nommerait un commit et cacherait les autres. Il Ă©nonce le sujet en + mots simples, comme un titre — une majuscule initiale, pas de point final. Un prĂ©fixe + thĂ©matique (`Release supply chain: 
`) est bienvenu ; un type Conventional Commits ne + l’est pas, sauf s’il est honnĂȘtement le seul. +* Gardez le titre dans les **72 caractĂšres** que vise un en-tĂȘte de commit, pour que la liste + des pull requests l’affiche en entier. +* La rĂ©fĂ©rence de l’issue vit dans la **description**, jamais dans le titre : `Closes #NN` + lorsque la pull request ferme l’issue, pour que GitHub la ferme au merge ; `Refs: #NN` + sinon. Le titre Ă©nonce le changement, pas l’endroit oĂč il a Ă©tĂ© demandĂ©. Un breaking change + se signale de la mĂȘme façon que sur un commit — le `!` et la note `BREAKING CHANGE:` sont + portĂ©s par le commit, et la case « Breaking change » du template le rĂ©pĂšte — pas le titre. + +### Exemples + +| Titre | Pourquoi il convient | +|---|---| +| `ci: add dependency review on pull requests` | Une seule intention. Le titre est l’en-tĂȘte de commit. | +| `feat(analyzers): add FCE016 for an undocumented error code` | Une seule intention, avec scope. L’issue qu’il ferme vit dans la description, pas ici. | +| `Adopt and enforce a Conventional Commits convention` | Le guide, le hook et le garde-fou CI — plusieurs commits de plusieurs types. Un titre simple les nomme tous. | +| `Release supply chain: build provenance + embedded SBOM` | Plusieurs intentions sous un mĂȘme thĂšme. Un prĂ©fixe thĂ©matique le porte ; aucun `type:` unique ne serait honnĂȘte. | + +### Anti-patterns + +| Titre | Ce qui ne va pas | +|---|---| +| `feat: various improvements` | Un type sur un fourre-tout. Soit c’est une seule intention — nommez-la — soit il y en a plusieurs, et `feat:` les cache. | +| `fix(core): Fixed the null dereference.` | La forme Ă  intention unique, portant les dĂ©fauts propres Ă  l’en-tĂȘte de commit : majuscule, passĂ©, point final. | +| `Add Outcome.Map (#142)` | Le numĂ©ro d’issue a sa place dans le `Closes`/`Refs` de la description, lĂ  oĂč GitHub le lit — pas Ă  manger le titre. | +| `Corrige le rendu des exemples` | Pas en anglais. | diff --git a/doc/README.fr.md b/doc/README.fr.md index 413b405a..79c3f555 100644 --- a/doc/README.fr.md +++ b/doc/README.fr.md @@ -231,7 +231,7 @@ Chaque package publiĂ© est construit et poussĂ© par [`release.yml`](../.github/w ## 🐛 Retours & contributions -Vous avez trouvĂ© un bug ou souhaitez proposer une fonctionnalitĂ© ? Ouvrez une issue sur le [gestionnaire d’issues GitHub](https://github.com/Reefact/first-class-errors/issues). Les contributions sont les bienvenues — voir [CONTRIBUTING.md](../CONTRIBUTING.md) pour commencer. +Vous avez trouvĂ© un bug ou souhaitez proposer une fonctionnalitĂ© ? Ouvrez une issue sur le [gestionnaire d’issues GitHub](https://github.com/Reefact/first-class-errors/issues). Les contributions sont les bienvenues — voir [CONTRIBUTING.fr.md](./CONTRIBUTING.fr.md) pour commencer. Pour les vulnĂ©rabilitĂ©s de **sĂ©curitĂ©**, merci de suivre le processus privĂ© dĂ©crit dans [SECURITY.fr.md](./SECURITY.fr.md) plutĂŽt que d’ouvrir une issue publique. diff --git a/maintainers/README.fr.md b/maintainers/README.fr.md index 70f92c14..e898728e 100644 --- a/maintainers/README.fr.md +++ b/maintainers/README.fr.md @@ -39,5 +39,5 @@ par un ADR plus rĂ©cent, pas Ă©ditĂ© sur place. ## En rapport -- [`CONTRIBUTING.md`](../CONTRIBUTING.md) — conventions de commit et de pull +- [`CONTRIBUTING.fr.md`](../doc/CONTRIBUTING.fr.md) — conventions de commit et de pull request (imposĂ©es par le workflow [`commit-lint`](workflows/commit-lint.fr.md)). diff --git a/maintainers/workflows/README.fr.md b/maintainers/workflows/README.fr.md index f9506821..a3f463c6 100644 --- a/maintainers/workflows/README.fr.md +++ b/maintainers/workflows/README.fr.md @@ -99,5 +99,5 @@ documentĂ©es une seule fois ici plutĂŽt que rĂ©pĂ©tĂ©es sur chaque page. - [ADR 0001 — Verrouiller le floor Roslyn de l'analyzer](../adr/0001-lock-the-analyzer-roslyn-floor.md) — pourquoi la version de Roslyn de l'analyzer est gelĂ©e, ce que le workflow `analyzers` fait respecter. *(RĂ©digĂ© en anglais.)* -- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) — les conventions de commit et de PR +- [`CONTRIBUTING.fr.md`](../../doc/CONTRIBUTING.fr.md) — les conventions de commit et de PR que le workflow `commit-lint` vĂ©rifie. diff --git a/maintainers/workflows/commit-lint.fr.md b/maintainers/workflows/commit-lint.fr.md index 80589216..82c343ad 100644 --- a/maintainers/workflows/commit-lint.fr.md +++ b/maintainers/workflows/commit-lint.fr.md @@ -10,7 +10,7 @@ ## À quoi il sert Il impose la convention de message de commit du dĂ©pĂŽt (Conventional Commits, -telle que spĂ©cifiĂ©e dans [`CONTRIBUTING.md`](../../CONTRIBUTING.md)) sur **chaque +telle que spĂ©cifiĂ©e dans [`CONTRIBUTING.fr.md`](../../doc/CONTRIBUTING.fr.md)) sur **chaque commit non-merge d'une pull request**. C'est le filet de sĂ©curitĂ© cĂŽtĂ© serveur du hook local `commit-msg` : un contributeur qui contourne le hook avec `git commit --no-verify` est quand mĂȘme rattrapĂ© ici. @@ -56,6 +56,6 @@ Un seul job, `Conventional commits` : ## En rapport -- [`CONTRIBUTING.md`](../../CONTRIBUTING.md) — les conventions de commit et de PR +- [`CONTRIBUTING.fr.md`](../../doc/CONTRIBUTING.fr.md) — les conventions de commit et de PR que ce workflow impose. Activez le hook local une fois par clone avec `git config core.hooksPath .githooks`.