Décisions de conception
Cette page consigne les décisions de conception prises pendant v0.1 qui méritent d’être revues avant tout changement.
Modèle linter contre modèle de score
Décision : v0.1 a livré la forme classique de linter, avec les sévérités info / warning. v0.2 a ajouté un modèle hybride de score (score global + sous-scores par catégorie + diagnostics) par-dessus, sans retirer la forme linter.
Raison : livrer la forme linter d’abord nous a permis de valider la qualité de détection sur de vrais corpus avant d’ajouter la couche d’agrégation. La couche de score est additive — les outils qui ne s’intéressent qu’aux diagnostics ignorent le scorecard.
Modèle hybride de score (v0.2)
Décision : un score global + 5 sous-scores par catégorie, tous sous la forme X / max. La composition empile une somme pondérée, une normalisation par densité (par 1 000 mots, plancher à 200) et un plafond par catégorie. 5 catégories figées : Structure · Rhythm · Lexicon · Syntax · Readability. Nouveau champ Diagnostic.weight, nouvelle option --min-score=N en ligne de commande.
Raison (brainstorm complet dans brainstorm/20260420-score-semantics.md) :
X / maxplutôt que 0–100 : un maximum arbitraire nous laisse réajuster sans prétendre que le 80 d’aujourd’hui est le 80 de la prochaine version. La compétence/impeccableutilise déjà cette convention.- 5 catégories figées : ne couplent rien à un renommage de règle ; utilisent l’aide
category_of(rule_id)déjà décidée en v0.1. Dériver depuis le préfixe (plan B) a été rejeté : il aurait fallu renommer 17 règles rien que pour F14. - Trois mécaniques de composition empilées : aucune seule ne couvre tous les modes de défaillance. La densité seule punit les courts documents ; les poids seuls perdent face à une règle qui s’emballe ; les plafonds seuls ne reflètent pas l’ampleur du coût.
- Notes en lettres, feux tricolores, marge réussite/échec et secondes de lecture ont été coupés du design v0.2 après une analyse à partir des principes de base (F-score-letter-grade–F-reading-time-score dans ROADMAP). Ils dupliquent la fonction-1 (vue d’un coup d’œil) que le nombre remplit déjà.
- L’actionnabilité (fonction-2) est portée par la liste des diagnostics, pas par le score. Les sous-scores peuvent donc se permettre d’être minimaux — F37 veille à ce que les messages de diagnostic tiennent le côté actionnable du contrat.
Structure Diagnostic
Décision : un Diagnostic porte rule_id, severity, location, section, message et (depuis v0.2) weight.
Ce qui n’est PAS stocké, et pourquoi :
category— dérivable depuisrule_idviaCategory::for_rule. La stocker dupliquerait l’information et créerait un risque de dérive.suggestion— toujours différée ; les messages actuels sont actionnables par eux-mêmes.
Ce qui EST stocké, et pourquoi :
section— la recalculer après coup demanderait de reparser le document pour parcourir les titres et faire correspondre les positions. Le coût de stockage est uneOption<String>par diagnostic ; le coût de recalcul est un second parsing complet.weight(v0.2) — initialisé à l’émission depuisscoring::default_weight_for, pour que les surcharges utilisatrices (par configuration) et les surcharges au niveau règle (parwith_weight) traversent l’agrégation sans seconde recherche.
Cœur déterministe, extensions pour le reste
Décision : le cœur ne livre que des règles déterministes. Les règles à base de LLM, les règles qui s’appuient sur le réseau ou les règles à base de modèle d’apprentissage vivent dans des caisses d’extension facultatives (prévues pour v0.3).
Raison : un hook pre-commit qui prend 5 secondes et varie d’une exécution à l’autre est pire que pas de hook du tout. Le déterminisme n’est pas négociable sur le chemin nominal.
Bilingue EN/FR dès le premier jour
Décision : chaque règle qui dépend de la langue gère l’anglais et le français depuis v0.1.
Raison : la plupart des développeurs francophones de l’open source écrivent leur documentation en anglais. Viser le français seul passerait à côté de la majorité. Gérer les deux dès le premier jour coûte peu et signale l’ambition.
Une seule formule de lisibilité en v0.1
Décision : v0.1 utilise le grade Flesch-Kincaid pour toutes les langues. Les formules par langue (Kandel-Moles pour le français, SMOG, Coleman-Liau) sont différées à v0.2.
Raison : Flesch-Kincaid est connue, reproductible et bien comprise. Ajouter trois formules avant de valider les bases serait une optimisation prématurée.
Markdown + texte brut + entrée standard, Pandoc pour le reste
Décision : prise en charge native de .md, .markdown, .txt et de l’entrée standard en v0.1. Les autres formats (AsciiDoc, HTML, docx, PDF) passent par Pandoc en pré-traitement.
Raison : Markdown couvre la grande majorité de l’écriture open-source et technique. Pandoc est libre, omniprésent, et lève la charge de maintenir plusieurs parseurs.
Un fichier par règle
Décision : chaque règle vit dans son propre fichier sous src/rules/, avec une structure cohérente (struct, config, impl Rule, tests).
Raison : ajouter une règle devient une opération bien définie (un nouveau fichier depuis un gabarit), et la revue est facile (une règle, une PR, un fichier à lire).
Heuristique des mots vides pour la détection de langue
Décision : v0.1 détecte la langue par le ratio de mots vides. Aucune dépendance externe.
Raison : court, déterministe, sans coût à l’exécution. Pour les cas où elle échoue (textes très courts, documents pleins de code), la valeur de repli unknown est sûre.
Préréglages de profil comme variantes d’énumération
Décision : les profils sont Profile::DevDoc | Public | Falc. Ils ne peuvent pas être définis dans la configuration de l’utilisateur en v0.1.
Raison : ajouter des profils personnalisés est une abstraction spéculative tant que personne ne le demande. Les surcharges par règle suffisent à couvrir 95 % des cas « je veux un préréglage légèrement différent ».
Pipeline de source de vérité du ROADMAP (v0.2.x+)
Décision : ROADMAP.md est rétrogradé de source éditée à artefact généré. La source de vérité devient un ensemble structuré de fichiers sous .roadmap/ (ignoré par git), un fichier markdown par fonctionnalité avec front-matter TOML, plus des fragments narratifs. Un petit membre de workspace Rust (crates/roadmap-cli) fournit les sous-commands add / generate / validate / rename. Le générateur est invoqué localement pendant la préparation de release ; le ROADMAP.md régénéré est committé sur la PR de préparation. La CI ne régénère pas. Cadré sous F-roadmap-toml-source.
Raison :
- La protection de branche sur
main(en place depuis le 2026-05-03 via F-repo-config-hardening) force chaque modification deROADMAP.mdà passer par le cycle worktree → branche → PR → CI → merge → nettoyage. Le débit prévu en régime stable était de 10 à 30 modifications ROADMAP-seules par semaine. La valeur de revue PR sur ces modifications est nulle (auteur unique), donc la cérémonie n’était que pur surcoût. - Une dérogation de ruleset par chemin sur
ROADMAP.mdaffaiblirait les signaux de protection de branche suivis par les badges OpenSSF Scorecard / Best Practices. Rétrograder le fichier hors demainpréserve ces signaux intacts. - Les fichiers par fonctionnalité donnent des diffs git par fonctionnalité, suppriment le verrouillage de schéma (le front-matter est optionnel par fichier) et laissent les sections de narration vivre en markdown brut plutôt qu’en chaînes TOML.
- Rust plutôt que Python pour le générateur : réutilise
pulldown-cmarkdéjà dans les dépendances, intègre les tests danscargo test, maintenance avec une seule chaîne d’outils, et reste extractible en caisse autonome si l’outil mûrit. - Le générateur local (pas la CI) évite d’accorder à la CI un quelconque accès à
.roadmap/(ignoré par git et local à la machine). La cadence de release — pas le temps réel — était un compromis accepté ; l’artefact publicROADMAP.mdse met à jour à chaque tagv*. - Bloqueurs jour 1 à la livraison : émission déterministe des ancres
<a id="…">(pour que les liens croisés existants de la forme[F46](#f46)dans les PR et commits contingent de résoudre), une sous-commandeaddqui sert de gabarit (pour que créer une fonctionnalité soit une seule frappe, pas une régression), et un test de déterminisme aller-retour (régénérer l’artefact, le comparer à la version committée, échouer en cas de dérive).
Solution de repli d’urgence : si le travail sur crates/roadmap-cli dépasse le budget, le fichier migre plutôt vers une branche orpheline roadmap avec push direct et la même forme .md — préserve les signaux Scorecard via un autre mécanisme, au prix d’une disposition de branches non standard. Documenté comme issue de secours mais pas comme chemin retenu.
Références à consulter avant de changer
RULES.md— la référence des règles qui fait foiROADMAP.md— les travaux à venirCODING_STANDARDS.md— les conventions du quotidien