From 010a7da0f4d2d1f5345e0bb71e888cc86ac47239 Mon Sep 17 00:00:00 2001 From: Patrick Schurig Date: Sat, 29 Aug 2026 11:33:46 +0200 Subject: [PATCH] =?UTF-8?q?ci:=20le=20contr=C3=B4le=20de=20qualit=C3=A9=20?= =?UTF-8?q?documentaire=20=E2=80=94=20=C3=A9crit,=20=C3=A9prouv=C3=A9,=20P?= =?UTF-8?q?AS=20en=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Étape 4 du chantier de documentation. tools/ci-quality.sh porte les trois vérifications qui se posent au même endroit — elles partagent la même sortie (doc-generated/) et la même commande de réparation, les séparer ferait trois jobs qui régénèrent trois fois la même chose : 1. l'index des règles est VRAI (code de retour de gen-rules.py) ; 2. la doc Doxygen est complète — 0 avertissement, DoD §5 ; 3. l'index est REPRODUCTIBLE : deux passes, un seul résultat. PAS EN SERVICE, et l'extension le garantit : ci/gitea-workflow.yml.disabled ne déclenche rien tant qu'il n'est pas déplacé dans .gitea/workflows/. La raison est datée dans le fichier — forge muette, plus de trente commits en attente ici et plus de soixante côté app ; un job activé à l'aveugle échouerait au moment précis où tout le monde pousse, et le premier réflexe serait de le désactiver. CHAQUE ÉCHEC DONNE LA COMMANDE À LANCER. Un job qui annonce « l'index est périmé » sans dire quoi faire se contourne en le désactivant : c'est le chemin de moindre effort, et il gagne toujours. VÉRIFIÉ AVANT D'ÊTRE DÉCLARÉ PRÊT, et ça ne passait pas du premier coup. — Les 26 avertissements Doxygen de ce matin sont CORRIGÉS, pas contournés par un cliquet sur un compte de référence : c'était de la dette DoD-5 (retours et paramètres non documentés dans relayrouter.h, energyarbitrator.h, loadconfig.h, les trois clearFault(), ILoadAdapter::updateSoftConfig), plus deux que j'avais introduits — un \rule{} dans un titre \par, et un \param energyLogs qui ne correspond à aucun argument. Le seuil est donc ZÉRO, le seul qui ne rote pas. — Et le script lui-même échouait sur un dépôt SAIN : `grep -c ... || echo 0` affiche 0 PUIS sort en 1 quand il ne trouve rien, si bien que le « || » empile un second zéro et que le test entier casse. Corrigé ici et dans gen-doc.sh, qui portait le même piège. Une commande de mesure dont l'échec produit une lecture fausse — le motif de la semaine, appliqué à l'outil. CE QUI N'EST PAS VÉRIFIÉ, et pourquoi : « régénérer et refuser si ça diffère de ce qui est commité » n'a rien à comparer, doc-generated/ étant dans .gitignore. Le risque qu'un tel diff attraperait — un artefact commité qui dérive de sa source — a été SUPPRIMÉ en ne le commitant pas, pas déplacé. Ce qui reste à garantir est que l'index soit vrai (contrôle 1) et déterministe (contrôle 3). État au 2026-08-29 : ./tools/ci-quality.sh → RC=0, 113 règles, 0 avertissement. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p --- ci/gitea-workflow.yml.disabled | 63 ++++++++++ .../etm/adapters/etmvariableloadadapter.h | 1 + energyplugin/etm/adapters/evadapter.h | 1 + energyplugin/etm/adapters/iloadadapter.h | 1 + energyplugin/etm/adapters/relayrouter.h | 23 +++- energyplugin/etm/adapters/sgreadyadapter.h | 1 + energyplugin/etm/energyarbitrator.h | 16 ++- energyplugin/etm/types/loadconfig.h | 7 ++ tools/ci-quality.sh | 114 ++++++++++++++++++ tools/gen-doc.sh | 4 +- 10 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 ci/gitea-workflow.yml.disabled create mode 100755 tools/ci-quality.sh diff --git a/ci/gitea-workflow.yml.disabled b/ci/gitea-workflow.yml.disabled new file mode 100644 index 0000000..9b1396e --- /dev/null +++ b/ci/gitea-workflow.yml.disabled @@ -0,0 +1,63 @@ +# ───────────────────────────────────────────────────────────────────────────── +# ÉCRIT, PAS EN SERVICE — et l'extension `.disabled` est ce qui le garantit. +# +# POUR L'ACTIVER, quand la forge répondra et que le retard de commits sera résorbé : +# +# mkdir -p .gitea/workflows +# git mv ci/gitea-workflow.yml.disabled .gitea/workflows/qualite-doc.yml +# +# Le déposer ailleurs que dans `.gitea/workflows/` ne déclenche rien : c'est +# volontaire. Un fichier renommé est un geste ; un fichier au bon endroit est un +# service. +# +# POURQUOI PAS MAINTENANT (2026-08-29). La forge ne répond pas et plus de trente +# commits attendent en local dans ce dépôt, plus de soixante côté app. Un job +# activé à l'aveugle échouerait à son premier passage — au moment précis où tout +# le monde pousse — et le premier réflexe serait de le désactiver. Un garde-fou +# désactivé une fois ne se réactive jamais. +# +# AVANT D'ACTIVER, lancer le contrôle À LA MAIN sur la branche visée : +# +# ./tools/ci-quality.sh +# +# Il passait sur l'état du dépôt au 2026-08-29 — index RC=0 (113 règles), doxygen +# 0 avertissement. S'il échoue, la cause est dans le dépôt, pas dans le job : le +# corriger AVANT l'activation, jamais après. Un job qu'on n'a jamais vu passer +# n'est pas un job, c'est une intention. +# +# CE QU'IL VÉRIFIE — trois choses, un seul endroit, parce qu'elles partagent la +# même sortie et la même commande de réparation : +# 1. l'index des règles est VRAI (orphelins, domiciles ambigus, numéros retirés +# réattribués) ; +# 2. la documentation Doxygen est complète (0 avertissement, DoD §5) ; +# 3. l'index est REPRODUCTIBLE (deux passes, un seul résultat). +# +# Ce qu'il ne vérifie PAS : « régénérer et refuser si ça diffère de ce qui est +# commité ». `doc-generated/` est dans .gitignore ; il n'y a rien à comparer, et +# le risque qu'un tel diff attraperait — un artefact commité qui dérive de sa +# source — a été SUPPRIMÉ en ne le commitant pas, pas déplacé. +# ───────────────────────────────────────────────────────────────────────────── +name: qualité documentaire + +on: + push: + branches: [ feature/beta-rulebased, main ] + pull_request: + +jobs: + qualite-doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # doxygen ET graphviz : dépendances de la CIBLE DOC uniquement, jamais du + # build du paquet. Sans elles le contrôle 2 ne s'exécute pas — et il le DIT + # au lieu de passer en silence, ce qui serait un faux vert. + - name: dépendances de la cible doc + run: sudo apt-get update && sudo apt-get install -y doxygen graphviz python3 + + # Une commande unique, celle qu'un humain lance. Si le job et la machine de + # développement divergeaient, le job cesserait d'être reproductible chez soi + # — et un contrôle qu'on ne peut pas rejouer localement se contourne. + - name: contrôle de qualité documentaire + run: ./tools/ci-quality.sh diff --git a/energyplugin/etm/adapters/etmvariableloadadapter.h b/energyplugin/etm/adapters/etmvariableloadadapter.h index c62f506..f880a75 100644 --- a/energyplugin/etm/adapters/etmvariableloadadapter.h +++ b/energyplugin/etm/adapters/etmvariableloadadapter.h @@ -123,6 +123,7 @@ public: //! La généralisation est portée par **ECS-414** (lot de mise en configuration //! du SgReadyAdapter). Déclaré explicitement plutôt qu'hérité d'un défaut vide. //! \brief Lève le verrou de défaut (ECS-414). La consigne réelle est RELUE, pas supposée. + //! \return Vrai si un défaut a réellement été levé ; faux s'il n'y en avait pas. bool clearFault() override; //! \return Vrai si la charge est en défaut (plus aucune consigne écrite). diff --git a/energyplugin/etm/adapters/evadapter.h b/energyplugin/etm/adapters/evadapter.h index 267c2ca..6275d9d 100644 --- a/energyplugin/etm/adapters/evadapter.h +++ b/energyplugin/etm/adapters/evadapter.h @@ -82,6 +82,7 @@ public: //! du SgReadyAdapter). Déclaré explicitement plutôt qu'hérité d'un défaut vide. //! Aucun mécanisme de défaut côté EV : il n'y a jamais rien à lever. Le dire //! explicitement plutôt que de laisser un corps vide (règle 7-c). + //! \return Vrai si un défaut a réellement été levé ; faux s'il n'y en avait pas. bool clearFault() override { return false; } /*! diff --git a/energyplugin/etm/adapters/iloadadapter.h b/energyplugin/etm/adapters/iloadadapter.h index 1e54098..6330660 100644 --- a/energyplugin/etm/adapters/iloadadapter.h +++ b/energyplugin/etm/adapters/iloadadapter.h @@ -141,6 +141,7 @@ public: * client qui réordonne ses priorités depuis l'app pourrait faire court-cycler son * compresseur — et provoquerait des réécritures de relais inutiles. * + * \param label Nouveau libellé d'affichage. * \param priority Nouveau rang de service (ASC, 1 = premier servi). * \param needs Nouveaux besoins déclarés. * \warning NE DOIT jamais toucher au câblage, aux paliers ni aux verrous : ces diff --git a/energyplugin/etm/adapters/relayrouter.h b/energyplugin/etm/adapters/relayrouter.h index f16c3be..28d80e3 100644 --- a/energyplugin/etm/adapters/relayrouter.h +++ b/energyplugin/etm/adapters/relayrouter.h @@ -72,6 +72,7 @@ struct RelayStageTable * combinaison retenue à somme égale, que \c levels et l'appariement position par * position capturent. */ + //! \param o Table comparée. \return Vrai si les deux tables décrivent le MÊME câblage. bool operator==(const RelayStageTable &o) const { if (levels != o.levels || mapping.size() != o.mapping.size()) @@ -84,9 +85,24 @@ struct RelayStageTable } return true; } + //! \brief Négation de \c operator==. \param o Table comparée. \return Vrai si elles diffèrent. bool operator!=(const RelayStageTable &o) const { return !(*this == o); } }; +/*! + * \brief Adaptateur d'une charge commandée par une COMBINAISON de relais (rév. 3). + * + * Traduit une enveloppe en watts en un palier, et le palier en contacts à fermer. Toute la + * combinatoire vit ici : l'optimiseur ne voit que des watts, jamais un relais ni un index de + * palier (ECS-306). Les paliers sont DÉRIVÉS des sous-ensembles de relais (\c deriveStages), + * ce qui couvre les topologies non cascadées et les puissances quelconques. + * + * \invariant Coupure avant fermeture à chaque transition — jamais deux contacts en conflit. + * \invariant Les verrous \c minOn / \c minOff sont INTERNES à l'adaptateur, évalués sur le + * \c now reçu en paramètre, jamais sur l'horloge. + * \invariant Un échec d'écriture pose un défaut COLLANT (ECS-410) : plus aucune commande + * n'est émise jusqu'à \c clearFault(). + */ class RelayRouter : public QObject, public ILoadAdapter { Q_OBJECT @@ -119,6 +135,7 @@ public: //! \return currentPowerW = somme des \c currentPower des relais ON (mesuré), sinon nominal commandé. LoadTelemetry telemetry() const override; + //! \param now Temps de cycle — source unique des verrous (contrat \c ILoadAdapter). //! \return LoadContext §5 (watts uniquement : powerLevels, currentPowerW — aucun relais). LoadContext toLoadContext(const QDateTime &now) const override; @@ -131,12 +148,13 @@ public: */ LoadAction applyAction(const LoadAction &action, const QDateTime &now) override; - //! \brief Palier courant (0 = tout coupé). + //! \brief Palier courant. \return Index du palier, 0 = tout coupé. int currentStage() const { return m_currentStage; } - //! \brief Puissance (W) du palier courant. + //! \brief Puissance du palier courant. \return Watts du palier appliqué ; 0 si hors table. double currentSetpointW() const { return m_currentStage < m_levels.size() ? m_levels.at(m_currentStage) : 0.0; } //! \brief Met à jour les champs qui ne touchent PAS le matériel (ECS-412). + //! \param label Nouveau libellé d'affichage. //! \param priority Nouveau rang de service. //! \param needs Nouveaux besoins déclarés. //! \note Permet à l'arbitre de refléter un changement de priorité SANS détruire @@ -159,6 +177,7 @@ public: /*! * \brief Lève le verrou de défaut (ECS-410, RPC \c NymeaEnergy.ClearLoadFault). + * \return Vrai si un défaut a RÉELLEMENT été levé, faux s'il n'y en avait pas. * \note Le défaut est COLLANT par conception — « cesser toute commande jusqu'à * intervention ». Sa levée est donc un acte délibéré et tracé de l'opérateur, jamais * une expiration. L'état matériel réel étant inconnu après un défaut, il est RELU diff --git a/energyplugin/etm/adapters/sgreadyadapter.h b/energyplugin/etm/adapters/sgreadyadapter.h index d412a54..e6a1694 100644 --- a/energyplugin/etm/adapters/sgreadyadapter.h +++ b/energyplugin/etm/adapters/sgreadyadapter.h @@ -107,6 +107,7 @@ public: * \note L'état réel est RELU depuis les contacts, jamais supposé : après un défaut, le * motif 2 bits peut être valide mais non voulu. */ + //! \return Vrai si un défaut a réellement été levé ; faux s'il n'y en avait pas. bool clearFault() override; //! \return Vrai si la PAC est en défaut (plus aucune commande émise). diff --git a/energyplugin/etm/energyarbitrator.h b/energyplugin/etm/energyarbitrator.h index af2f942..c39c92c 100644 --- a/energyplugin/etm/energyarbitrator.h +++ b/energyplugin/etm/energyarbitrator.h @@ -39,6 +39,15 @@ class EnergyArbitrator : public SmartChargingManager { Q_OBJECT public: + /*! + * \brief Construit l'arbitre central. Mêmes dépendances que \c SmartChargingManager amont, + * dont il hérite (justification : AGENTS.md, « 3b-iii »). + * \param energyManager Gestionnaire d'énergie nymea (compteur racine, notifications). + * \param thingManager Registre des Things — résolution des appareils pilotés/mesurés. + * \param spotMarketManager Fournisseur de tarifs dynamiques (proxy EV). + * \param configuration Réglages persistés (limites de phase, tolérance, réserve). + * \param parent Parent QObject. + */ explicit EnergyArbitrator(EnergyManager *energyManager, ThingManager *thingManager, SpotMarketManager *spotMarketManager, EnergyManagerConfiguration *configuration, @@ -137,6 +146,7 @@ public: * \note Logique injectable (temps en paramètre) — en production appelée par le * handler \c powerBalanceChanged ; en simulation/test appelée directement. Le * déclencheur réel (signal) est câblé sous \c \#ifndef ENERGY_SIMULATION. + * \param now Temps de cycle — jamais l'horloge, pour que la logique reste injectable. */ void recordMeterUpdate(const QDateTime &now); @@ -145,6 +155,7 @@ public: * * Si \c now − \c m_lastMeterUpdate > 90 s et pas déjà dégradé → \c applyDegradedMode(). * Appliqué à la TRANSITION uniquement (idempotent ensuite). \p now = temps de cycle. + * \param now Temps de cycle — jamais l'horloge, pour que la logique reste injectable. * \note Logique injectable — en production appelée par \c onMeterWatchdogTick() (QTimer * horloge murale, indépendant car le compteur muet fige aussi \c update()) ; en * simulation/test appelée directement avec le temps simulé. Symétrique de @@ -205,6 +216,9 @@ public: /*! * \brief [3g-1] Cette borne participe-t-elle au waterfall de surplus ? * + * \param ev Borne interrogée ; \c nullptr rend faux. + * \return Vrai si la borne entre dans le waterfall de surplus ce cycle. + * * **Deux** exclusions, et elles ne disent pas la même chose : le **mode manuel** * (\c ChargingModeNormal) est une INTENTION — l'utilisateur pilote, l'arbitre s'abstient ; * **aucun véhicule branché** (\c pluggedIn) est un CONSTAT — la borne ne peut rien tirer @@ -434,7 +448,7 @@ private: * Le rang, le libellé, le domaine \c ev, \c enabled. Aucune charge utile : les limites de * la borne viennent du Thing (voir \c LoadConfig::isEvCharger()). * - * \par Rang par défaut — EN QUEUE (\rule{LM-1209}) + * \par Rang par defaut — EN QUEUE (LM-1209) * `1 + max(rangs existants)`, donc libre par construction et sans bousculer personne. Ce * n'est pas une politique du plugin : le rang est un choix de l'installateur, et une * détection ne réordonne pas ce qu'un humain a décidé. Une borne détectée un mois après la diff --git a/energyplugin/etm/types/loadconfig.h b/energyplugin/etm/types/loadconfig.h index bd3cea8..1da0fed 100644 --- a/energyplugin/etm/types/loadconfig.h +++ b/energyplugin/etm/types/loadconfig.h @@ -206,10 +206,15 @@ public: * non nul : il se **dérive**, et le déclarer en double ouvrirait deux sources d'une même * vérité. `isValid()` refuse donc la combinaison. */ + //! \return Plancher de modulation en watts ; 0 = aucun plancher déclaré. uint minPowerW() const { return m_minPowerW; } + //! \brief Pose le plancher de modulation. \param v Watts, ou 0 pour aucun plancher. void setMinPowerW(uint v) { m_minPowerW = v; } + //! \brief Compteur dédié à CETTE charge (§11 / LM-1100). \return ThingId, vide si aucun. + //! \note Prioritaire sur la mesure de l'appareil piloté quand il résout (LM-1105). QString meterThingId() const { return m_meterThingId; } + //! \brief Rattache un compteur dédié. \param v ThingId, ou vide pour n'en rattacher aucun. void setMeterThingId(const QString &v) { m_meterThingId = v; } /*! @@ -252,7 +257,9 @@ public: * (température, puissance, heure) sans lequel les seuils éco/confort du §10 ne pourront * jamais être réglés autrement qu'au doigt mouillé. */ + //! \return ThingId de la sonde ; vide si aucune n'est rattachée. QString sensorThingId() const { return m_sensorThingId; } + //! \brief Rattache une sonde. \param v ThingId, ou vide pour n'en rattacher aucune. void setSensorThingId(const QString &v) { m_sensorThingId = v; } //! \return Codes de domaine acceptés, ordre stable. Source unique de l'énumération : diff --git a/tools/ci-quality.sh b/tools/ci-quality.sh new file mode 100755 index 0000000..0e99465 --- /dev/null +++ b/tools/ci-quality.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync +# +# Contrôle de qualité documentaire — TROIS vérifications, un seul endroit. +# +# ./tools/ci-quality.sh +# +# Elles se posent au même endroit parce qu'elles partagent la même sortie +# (`doc-generated/`) et la même commande de réparation. Les séparer ferait trois +# jobs qui regénèrent trois fois la même chose. +# +# 1. L'INDEX DES RÈGLES est-il VRAI ? (tools/gen-rules.py, code de retour) +# Casse sur : un identifiant cité et jamais défini ; un identifiant qui n'existe +# qu'en ligne de tableau ; un domicile ambigu ; un numéro retiré réattribué. +# 2. La DOC DOXYGEN est-elle complète ? (0 avertissement — DoD §5 d'AGENTS.md) +# 3. Le tout est-il REPRODUCTIBLE ? (deux passes donnent le même index) +# +# ── CE QU'ON NE VÉRIFIE PAS, ET POURQUOI ──────────────────────────────────── +# +# « Régénérer et refuser si ça diffère de ce qui est commité » n'a rien à comparer : +# `doc-generated/` est dans .gitignore, décision du 2026-08-29 — versionner une page +# générée produirait un diff à chaque édition de spec et des conflits sur un fichier +# que personne ne relit. Le risque qu'un tel diff attrape — un artefact commité qui +# dérive de sa source — n'existe pas quand l'artefact n'est pas commité : il a été +# SUPPRIMÉ, pas déplacé. Ce qui reste à garantir est que l'index soit VRAI, et c'est +# la vérification 1. La 3 couvre le reste : un générateur non déterministe. +# +# ── ÉTAT : ÉCRIT, PAS EN SERVICE ──────────────────────────────────────────── +# +# Aucun déclencheur automatique n'est posé. `ci/gitea-workflow.yml.disabled` porte le +# workflow prêt à l'emploi et la marche à suivre pour l'activer. La raison est datée : +# au 2026-08-29 la forge ne répond pas et plus de trente commits attendent en local. +# Un job activé à l'aveugle échouerait à son premier passage — au moment précis où +# tout le monde pousse — et le premier réflexe serait de le désactiver. Un garde-fou +# désactivé une fois ne se réactive jamais. +# +# Il PASSE sur l'état du dépôt au 2026-08-29 : index RC=0 (113 règles), doxygen 0 +# avertissement. Vérifié avant d'être déclaré prêt, parce qu'un job qu'on n'a jamais +# vu passer n'est pas un job, c'est une intention. + +set -u + +RACINE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$RACINE" || exit 1 + +ROUGE=""; VERT=""; GRAS=""; FIN="" +if [ -t 1 ]; then ROUGE=$'\e[31m'; VERT=$'\e[32m'; GRAS=$'\e[1m'; FIN=$'\e[0m'; fi + +echec=0 +titre() { printf '\n%s── %s %s\n' "$GRAS" "$1" "$FIN"; } +ko() { printf '%s✗ %s%s\n' "$ROUGE" "$1" "$FIN"; echec=1; } +ok() { printf '%s✓ %s%s\n' "$VERT" "$1" "$FIN"; } + +# Toute panne doit dire QUOI LANCER. Un job qui annonce « l'index est périmé » sans +# donner la commande se contourne en le désactivant — c'est le chemin de moindre +# effort, et il gagne toujours. +reparer() { printf ' → réparer : %s\n' "$1"; } + +# --- 1. L'index des règles est-il vrai ? ------------------------------------ +titre "1/3 · index des règles" +if python3 tools/gen-rules.py; then + ok "index complet — aucun orphelin, aucun domicile ambigu" +else + ko "l'index des règles est INCOMPLET (détail ci-dessus)" + reparer "définir la règle manquante dans specs/, ou la déclarer dans tools/rules-retired.txt" + reparer "puis : python3 tools/gen-rules.py" +fi + +# --- 2. La doc est-elle complète ? ------------------------------------------ +titre "2/3 · documentation Doxygen" +if ! command -v doxygen >/dev/null 2>&1 || ! command -v dot >/dev/null 2>&1; then + ko "doxygen ou graphviz absent — la vérification n'a PAS eu lieu" + reparer "apt install doxygen graphviz" +else + ./tools/gen-doc.sh >/dev/null 2>&1 + JOURNAL="$RACINE/doc-generated/warnings.txt" + # `wc -l` et non `grep -c` : grep -c AFFICHE 0 puis SORT EN 1 quand il ne trouve rien, + # si bien qu'un « || echo 0 » empile un second zéro et que le test entier échoue sur un + # dépôt SAIN. Une commande de mesure dont l'échec produit une lecture fausse — le motif + # de la semaine, appliqué à l'outil (AGENTS.md, « Lire le journal de la box »). + N=0 + [ -f "$JOURNAL" ] && N="$(grep -i "warning" "$JOURNAL" 2>/dev/null | wc -l)" + if [ "$N" -eq 0 ]; then + ok "0 avertissement" + else + ko "$N avertissement(s) Doxygen — DoD §5 : toute méthode publique d'etm/ se documente" + printf '\n'; sed 's|^.*/energyplugin/| |' "$JOURNAL" | head -20 + reparer "./tools/gen-doc.sh puis lire doc-generated/warnings.txt" + fi +fi + +# --- 3. Le générateur est-il déterministe ? --------------------------------- +titre "3/3 · reproductibilité de l'index" +A="$(mktemp)"; B="$(mktemp)" +python3 tools/gen-rules.py >/dev/null 2>&1; cp doc-generated/RULES.md "$A" +python3 tools/gen-rules.py >/dev/null 2>&1; cp doc-generated/RULES.md "$B" +if diff -q "$A" "$B" >/dev/null; then + ok "deux passes, un seul résultat" +else + ko "l'index n'est PAS reproductible — deux passes ont divergé" + printf '\n'; diff "$A" "$B" | head -20 + reparer "un ordre d'itération dépend d'un QHash ou du système de fichiers : trier" +fi +rm -f "$A" "$B" + +printf '\n' +if [ "$echec" -eq 0 ]; then + printf '%s✓ qualité documentaire : tout passe.%s\n' "$VERT" "$FIN" +else + printf '%s✗ qualité documentaire : au moins une vérification a échoué.%s\n' "$ROUGE" "$FIN" + printf ' Tout relancer d'"'"'un coup : ./tools/ci-quality.sh\n' +fi +exit "$echec" diff --git a/tools/gen-doc.sh b/tools/gen-doc.sh index 794f170..bb0cac7 100755 --- a/tools/gen-doc.sh +++ b/tools/gen-doc.sh @@ -85,7 +85,9 @@ if [ -n "$LOGFILE" ] && [ -f "$RACINE/$LOGFILE" ]; then else JOURNAL_EFFECTIF="$JOURNAL" fi -AVERTISSEMENTS="$(grep -c -i "warning" "$JOURNAL_EFFECTIF" 2>/dev/null || echo 0)" +# `wc -l`, jamais `grep -c ... || echo 0` : grep -c affiche 0 PUIS sort en 1 quand il ne +# trouve rien, et le « || » empile alors un second zéro dans la variable. +AVERTISSEMENTS="$(grep -i "warning" "$JOURNAL_EFFECTIF" 2>/dev/null | wc -l)" echo "" echo " doxygen $VERSION_REELLE (référence $VERSION_ATTENDUE)"