Destiné à tout agent IA (Claude ou autre) travaillant sur les plugins de GNOME Connection Manager, en particulier lors du découpage en dossiers
plugins/<nom>/(voirproposition-architecture-plugins-gtk4.md) où plusieurs agents peuvent travailler en parallèle sur des plugins différents. Ces règles sont strictes : un agent qui livre du code ne les respectant pas doit le corriger avant de le considérer terminé, pas après coup sur demande.Contexte : voir
claude.mdetfeatures.mdpour l'état du projet.gcm4_core.py(extrait le 2026-08-30) est la référence vivante de ces conventions — en cas de doute sur la forme attendue, s'y référer.
Le cœur de l'application (gnome_connection_manager.py, et sa partie
métier extraite dans gcm4_core.py) ne doit contenir aucune donnée ni
logique spécifique à un protocole (SSH, RDP, VNC, série, telnet,
SNMP...). Tout ce qui nomme un protocole, un constructeur, ou un format de
configuration propre à un plugin appartient au plugin, jamais au cœur.
- Contre-exemple corrigé le 2026-08-30 : les templates série (presets
Cisco/Huawei/Arduino...) avaient été déplacés par erreur dans
gcm4_core.pylors de sa création, puis remis dansgnome_connection_manager.pyaprès relecture — le protocole série étant déjà un plugin (plugins/plugin_serial.py), ces données n'ont leur place dans aucun des deux fichiers du cœur à terme, seulement dans le plugin. - Bon exemple :
proto_default_port()/all_default_ports()dansgcm4_core.pysont un mécanisme générique paramétré parplugin_registry— aucun nom de protocole n'y est écrit en dur. - Un agent qui découvre un vestige de protocole dans le cœur en travaillant
sur autre chose le signale dans
features.md/claude.md, mais ne le déplace pas sans confirmation — sauf si la tâche en cours porte justement sur ce protocole. - Contrats vérifiés en CI (issue #174) :
tests/test_architecture_imports.pyfait échouer toute PR qui fait importer GTK (gi) à un module cœur (*_core.py,gcm4_core.py,logging_config.py,master_password_core.py,plugins/*/core.py), qui fait importer un plugin nommé au cœur, ou un plugin par un autre plugin.tools/check_circular_imports.pytourne aussi dans le job Qualité, et les contrats import-linter desrc/gcm4(pyproject.toml) s'activeront avec ce paquet, avec l'interdiction des noms de protocole en littéral danssrc/gcm4/core/etsrc/gcm4/ui/.
Une fonction ne va jamais chercher une donnée dont elle a besoin via un
singleton global (wMain, utils.conf, etc.) si cette donnée peut lui
être passée en paramètre. Voir proto_default_port(proto, plugin_registry)
et decrypt(passw, string, version) dans gcm4_core.py : réécritures du
2026-08-30 qui reçoivent explicitement ce qu'elles utilisaient auparavant
en lisant un global. Ceci s'applique en particulier à tout code qui doit
rester importable sans GTK.
Toute fonction ou méthode non triviale importe app_logger (from loguru import logger as app_logger, ou l'instance déjà configurée du module) et
loggue :
- un
debug()en entrée, avec les paramètres significatifs (jamais un mot de passe ou une clé en clair — indiquer(masqué)ou une longueur à la place, voirget_password()dansgcm4_core.py) ; - un
debug()à chaque sortie, y compris chaquereturnanticipé et chaque levée d'exception volontaire (raise) — pas seulement undebug()générique en fin de fonction si plusieurs chemins de sortie existent.
def exemple(proto: str, plugin_registry) -> str:
"""Résume ce que fait la fonction en une ligne.
Args:
proto (str): ...
plugin_registry (PluginRegistry): ...
Returns:
str: ...
"""
app_logger.debug(f"exemple() called | proto={proto!r}")
plugin = plugin_registry.get(proto)
if plugin is None:
app_logger.debug("exemple() returning | plugin absent")
return ""
result = plugin.something()
app_logger.debug(f"exemple() returning | result={result!r}")
return resultRéférence complète : n'importe quelle fonction de gcm4_core.py.
Un seul système de journalisation (issue #170) : jamais import logging
(ni repli « si loguru est absent » : loguru est une dépendance
obligatoire), et les puits (logger.add) ne sont configurés que par
logging_config.py, sauf dans la fonction main() d'un point d'entrée
autonome (tools/*.py, plugins/ssh/core.py, scripts/pr_coverage_comment.py, scripts/i18n_report.py, scripts/audit_debug_sorties.py, scripts/generate_class_diagram.py, scripts/check_branch_protection.py).
tests/test_loguru_only.py le vérifie par analyse ast.
tests/test_loguru_regles.py (issue #171) rend bloquantes les autres
règles : except journalisés (manques hérités comptés par fichier, compte
qui ne peut que diminuer), logger de module, placeholders {} cohérents,
aucun secret interpolé, et total de scripts/audit_debug_sorties.py
(fonctions sans debug() en entrée ou à chaque sortie) qui ne doit pas
augmenter.
Déjà imposé par pyproject.toml ([tool.ruff.lint.pydocstyle] →
convention = "google", règles D activées) — ceci formalise le standard
attendu, ruff ne fait que vérifier qu'une docstring existe et respecte la
structure Google, pas qu'elle documente correctement Args/Returns.
- Chaque fonction/méthode publique et privée (préfixée
_) a une docstring. Args:liste chaque paramètre avec son type et une description courte.Returns:décrit la valeur de retour (type + sens). Omis seulement si la fonction ne retourne rien de significatif (-> None).Raises:si la fonction lève une exception volontairement (voirload_encryption_key()dansgcm4_core.py).- Toute divergence de comportement par rapport à une version antérieure
(réécriture, correction de bug latent) est documentée dans la
docstring elle-même, pas seulement dans le message de commit — voir
get_password()dansgcm4_core.pypour l'exemple.
Tout code métier nouveau ou modifié (zéro import gi/Gtk/Vte) a des
tests dans un fichier dédié qui l'importe directement, sans passer par
gnome_connection_manager.py — voir tests/test_gcm4_core.py et
tests/test_snmp_bulk_core.py (stub minimal d'une seule dépendance C
manquante dans l'environnement de développement, pas de stub GTK).
Priorités de couverture, dans l'ordre :
- Tout comportement nouveau ou changé par rapport à l'existant (signature modifiée, exception au lieu d'un ancien comportement...).
- Toute régression trouvée en écrivant le code — un bug de transcription
corrigé doit avoir un test qui aurait échoué avant la correction (voir
TestColorToHex.test_with_negative_diffetTestDepInstallHintdanstests/test_gcm4_core.py, qui verrouillent deux erreurs de transcription trouvées le 2026-08-30). - Les cas limites déjà connus (fichier absent, dossier non accessible en écriture, registre de plugin vide...).
Code touchant GTK (widgets, dialogues) : tester la logique extractible
séparément plutôt que renoncer aux tests — voir la discipline déjà en place
dans gnome_connection_manager.py (_compute_zoom_size() extrait de
Wmain.terminal_zoom() justement pour rester testable).
pyproject.toml définit déjà la configuration (select = ["E", "W", "F", "I", "D", "UP", "B", "C4", "SIM"], ligne à 99 caractères, docstrings
Google). Avant de considérer un fichier terminé :
ruff check <fichier>
ruff format <fichier>Les deux doivent passer sans erreur sur tout fichier nouveau. Sur un fichier existant modifié, ne corriger que ce qui touche au code effectivement changé — ne pas profiter d'une petite modification pour reformater tout un fichier hérité (bruit inutile dans le diff, risque de conflit avec un autre agent qui travaille sur le même fichier en parallèle).
tests/test_gcm.py
compte à lui seul 423 erreurs ruff (essentiellement des docstrings
manquantes sur des méthodes de test), et make lint
(ruff check gnome_connection_manager.py tests/) échoue donc déjà
aujourd'hui. Un agent qui touche tests/test_gcm.py pour une raison
précise peut corriger les erreurs des lignes qu'il modifie, mais n'est pas
tenu de résorber tout l'historique en une fois.
Depuis l'issue #90, la CI exécute aussi pre-commit run --all-files (job
« Qualité (lint, tests) ») : installer le hook en local évite seulement de
le découvrir en CI.
Voir .pre-commit-config.yaml (ruff, hygiène de base, et le hook local
tools/check_circular_imports.py). Installation (une fois) :
pip install pre-commit --break-system-packages
pre-commit installLe détecteur d'imports circulaires (tools/check_circular_imports.py) ne
regarde que les imports exécutés au chargement du module (imports en
tête de fichier, dans un corps de classe, sous un if/try au niveau
module) — il ignore délibérément :
- les imports à l'intérieur d'un corps de fonction/méthode (pattern déjà
utilisé intentionnellement dans ce dépôt pour casser un cycle logique,
voir
models.pyqui importegcm4_coreà l'intérieur de ses méthodes) ; - les imports sous
if TYPE_CHECKING:(jamais exécutés à l'exécution réelle).
Si le détecteur bloque un commit, la solution n'est presque jamais de
contourner le hook, mais de différer l'import côté appelant le moins
central (le passer à l'intérieur d'une fonction plutôt qu'en tête de
fichier), ou de le limiter à if TYPE_CHECKING: s'il ne sert qu'à
l'annotation de type.
pre-commit run --all-files sur ce dépôt reformate ~40
fichiers Python existants (jamais passés par ruff format jusqu'ici) et
corrige des espaces/fins de fichier sur une poignée de fichiers texte
(README.md, claude.md, pyproject.toml, ssh.expect, postinst,
style.css). C'est normal et sans risque (changements de forme
uniquement), mais à faire comme un commit dédié et isolé avant de
commencer le travail sur les plugins — pas mélangé à un premier commit de
fonctionnalité, pour ne pas noyer une vraie modification dans du bruit de
formatage. Les dossiers vendorisés (SSH-Studio/, gtk-frdp/) et les
environnements de test (rdp/, vnc/) sont exclus de tous les hooks
(exclude: en tête de .pre-commit-config.yaml) — ne pas les en retirer.
- Le style de code au sens large (nommage, longueur de fonction...) : géré par ruff, pas reformulé ici.
- Les décisions d'architecture au cas par cas (quel fichier pour quelle
logique) : voir
proposition-architecture-plugins-gtk4.mdetfeatures.md§4 pour le contexte, ce fichier ne couvre que les règles transversales de qualité de code. - Rien ici n'autorise à ignorer les autres consignes du projet
(
claude.md: ne pas trancher seul une ambiguïté, proposer un plan avant de coder un gros morceau, ne pas fixer aveuglément quelque chose signalé comme "à confirmer").