# Guide pour les agents et contributeurs — `pronote-sync` > **Langue de travail** : Français (ce fichier et toute la documentation projet). --- ## 1. Description du projet `pronote-sync` est un pipeline Python qui synchronise les données de **Pronote** (système de gestion scolaire) vers **CalDAV** (agendas) et **XMPP** (notifications). > **Spécification complète** : Voir [`GUIDE_DEV_PYTHON.md`](./GUIDE_DEV_PYTHON.md). --- ## 2. Stack technique ### Langage et dépendances - **Python** : ≥ 3.13.5 (actuellement 3.14 dans `.venv/`) - **Dépendances principales** : `pydantic>=2.0`, `pydantic-settings`, `icalendar`, `caldav`, `slixmpp`, `pronotepy`, `feedparser`, `beautifulsoup4`, `requests`, `httpx`, `openai` ### Outils de développement - **Linter** : `ruff` (longueur de ligne : 100) - **Typage** : `mypy` (mode `strict=true`) - **Tests** : `pytest` (couverture ≥ 90%), `pytest-mock`, `responses`, `aioresponses` - **Sécurité** : `bandit` - **Pre-commit** : hooks configurés pour `ruff`, `mypy`, `bandit` ### Environnement - **Virtualenv** : `.venv/` (à activer avant toute opération) - **Fichier de configuration** : `.env` (à partir de `.env.example`) --- ## 3. Structure du projet ``` pronote_sync/ ├── config/ # Configuration et paramètres ├── errors.py # Hiérarchie canonique des erreurs du pipeline ├── models/ # Modèles de données (Pydantic v2) ├── sources/ # Connecteurs (Pronote, iCal, etc.) ├── sync/ # Logique de synchronisation ├── synthesis/ # Génération de synthèses (IA) ├── channels/ # Canaux de sortie (CalDAV, XMPP) ├── pipeline/ # Orchestration du pipeline ├── utils/ # Utilitaires (redaction, logging, etc.) ├── cli/ # Interface en ligne de commande └── __init__.py tests/ ├── fixtures/ # Données de test anonymisées ├── unit/ # Tests unitaires └── integration/ # Tests d'intégration ``` > **Plan de développement** : Voir [`TODO.md`](./TODO.md) pour les 15 jalons (M1-M15). --- ## 4. Commandes essentielles ### Environnement ```bash # Activer le virtualenv source .venv/bin/activate # Installer les dépendances de développement pip install -e ".[dev]" ``` ### Qualité de code ```bash # Linter (ruff) ruff check . # Formattage (ruff) ruff format . # Vérification des types (mypy) mypy . # Analyse de sécurité (bandit) bandit -r pronote_sync/ # Pre-commit (tous les hooks) pre-commit run --all-files ``` ### Tests ```bash # Exécuter les tests pytest # Exécuter les tests avec couverture pytest --cov ``` ### CLI ```bash # Exécuter en mode dry-run (simulation) pronote-sync --dry-run ``` --- ## 5. Conventions de code ### Typage - **Strict** : `mypy` en mode `strict=true`. **Tous** les paramètres, retours et variables locales doivent être typés. - **Pydantic v2** : Tous les modèles de données héritent de `BaseModel`. Les modèles de contrat utilisent `frozen=True`. ### Architecture - **Injection de dépendances** : Utiliser `typing.Protocol` et une **composition root** dans `pipeline/run.py`. **Interdiction** des singletons globaux. - **Erreurs** : Conserver une seule hiérarchie dans `pronote_sync/errors.py` ; ne pas créer de doublon dans `pipeline/steps/errors.py`. ### Style - **Longueur de ligne** : 100 caractères maximum (configuré dans `ruff`). - **Imports** : Triés automatiquement par `ruff` (intègre `isort`). ### Tests - **Sans réseau** : Utiliser **uniquement** des mocks (`pytest-mock`, `responses`, `aioresponses`). - **Fixtures** : Les données de test doivent être anonymisées et stockées dans `tests/fixtures/`. ### Comportement - **Idempotence** : Deux exécutions identiques sans changement externe doivent produire le **même résultat**. - **Mode dégradé** : - Si l'IA échoue → retourner un message **sans synthèse**. - En mode source `auto`, essayer iCal puis utiliser `pronotepy` uniquement si iCal lève une exception. - En mode source explicite (`ical` ou `pronotepy`), ne pas changer silencieusement de source. - En mode `auto`, si iCal et `pronotepy` échouent → lever une erreur critique explicite. - Une liste vide est un succès valide ; elle ne doit pas être assimilée à une panne. ### Contrat des sources Pronote - `PRONOTE_URL` (connexion API) et `PRONOTE_ICAL_URL` (flux iCal sensible) sont deux paramètres distincts ; ne pas déduire l'un de l'autre. - Le compte actuellement visé est un compte parent : utiliser `pronotepy.ParentClient(pronote_url, username, password, ent=ent_function)`. - Résoudre le slug `PRONOTE_ENT` vers une fonction de `pronotepy.ent` au moyen d'une liste fermée. - Exposer séparément les cours et les devoirs dans le client ; filtrer les devoirs `pronotepy` sur la date cible. - Les récupérations critiques agenda/devoirs propagent une erreur expurgée ; seuls les messages et informations non critiques peuvent se dégrader en liste vide avec warning. - Réutiliser un téléchargement/parsing iCal pour l'agenda et les devoirs pendant un même run, sans cache global ni persistant. ### Contrat du provider `openai-compatible` - Le provider `openai-compatible` réutilise `OpenAISynthesisProvider` avec un `base_url` personnalisé ; aucun nouveau provider n'est créé. - `AI_BASE_URL` et `AI_MODEL` sont requis ; `AI_API_KEY` est requis (MVP). - L'URL doit utiliser `https` sauf si `AI_ALLOW_INSECURE_HTTP=true`. - Les credentials dans l'URL (`user:pass@host`) sont refusés. - Les paramètres sensibles dans la *query string* sont refusés, y compris ceux sans valeur (`?token`). - Les URL malformées ou sans hostname sont rejetées (`ValueError` catché). - Aucune manipulation automatique de `/v1` n'est effectuée. - Configuration incomplète ou invalide → `None` avec avertissement (mode dégradé) ; la factory ne lève jamais d'exception. - La factory ne fait aucun appel réseau ; les avertissements utilisent `redact_url()`. ### Documentation (docstrings) - **Obligatoire** : **Toute** fonction, méthode et classe publique doit avoir une docstring. - **Format** : Utiliser le format **Sphinx/reST** (pas Google ou NumPy) pour une compatibilité native avec Sphinx. - **Priorité** : Les blocs historiques de `GUIDE_DEV_PYTHON.md` utilisant `Args:`/`Returns:` sont illustratifs ; le format Sphinx/reST défini ici prévaut pour le code de production. - **Contenu** : - Une ligne de résumé courte (une phrase). - Une description étendue optionnelle. - Les paramètres avec `:param nom:`. - Le retour avec `:return:` et `:rtype:`. - Les exceptions avec `:raises TypeException:`. - **Modules** : Chaque module doit avoir une docstring au niveau module. - **Objectif** : Générer une **documentation PDF via LaTeX** avec Sphinx. Exemple : ```python def fetch_ical(url: str) -> str: """Récupère le contenu d'un flux iCal Pronote. :param url: URL du flux iCal (avec token ``icalsecurise``). :return: Contenu brut du flux iCal. :rtype: str :raises requests.RequestException: Si la requête HTTP échoue. """ ``` --- ## 6. Sécurité et secrets ### Règles absolues - **Aucun secret en clair** : Ni dans le code, ni dans les logs, ni dans les erreurs, ni dans les fixtures. - **Masquage** : Utiliser systématiquement `redact_url()`, `redact_secrets()`, et `redact_exception()` depuis `utils/redaction.py`. - **Chaînage d'exceptions** : Ne jamais conserver comme `__cause__` ou `__context__` une exception externe brute susceptible de contenir un secret. Journaliser la version expurgée puis utiliser `raise ... from None`, ou chaîner une cause elle-même expurgée. - **Tests de non-fuite** : Vérifier les messages, les logs, `__cause__`, `__context__` et le traceback complet avec des sentinelles distinctes pour chaque secret. ### Bonnes pratiques - **Types sécurisés** : Les mots de passe et clés API doivent utiliser `pydantic.SecretStr`. - **Fichier `.env`** : **Jamais** committé (couvert par `.gitignore`). Utiliser `.env.example` comme template. --- ## 7. Plan de développement Le projet suit un plan séquentiel en **15 jalons** (M1-M15) décrits dans [`TODO.md`](./TODO.md). - Chaque jalon a des **critères d'acceptation** à valider avant de passer au suivant. - Ordre logique : **scaffolding → config → modèles → sources → sync → synthèse → canaux → pipeline → CLI → tests → déploiement → docs** --- ## 8. Référence | Document | Rôle | |----------|------| | [`GUIDE_DEV_PYTHON.md`](./GUIDE_DEV_PYTHON.md) | Spécification complète (architecture, modèles, parsing, déploiement) | | [`TODO.md`](./TODO.md) | Plan de développement détaillé (15 jalons) | --- ## 9. Agents OpenCode Cette section s'applique uniquement lorsque le travail est exécuté avec le système d'agents d'OpenCode. Les rôles d'agents disponibles pour ce projet sont les suivants : - `@architect` : Arbitrages d'architecture et choix techniques structurants. **Ne produit pas de code.** - `@coder` : Écrit et modifie du code, de la configuration et des scripts. **Ne valide pas** (ruff, mypy, pytest) — c'est le rôle de `@verifier`. **Ne diagnostique pas** — c'est le rôle de `@debugger`. - `@debugger` : Reproduit un symptôme et établit sa cause profonde. **Ne modifie pas le code.** - `@explorer` : Explore le dépôt en lecture seule. **Ne modifie rien, n'exécute pas de commandes.** - `@orchestrator` : Compréhension globale, définition des jalons, coordination et garantie du résultat. **N'écrit pas de code.** - `@planner` : Transforme une demande complexe en unités exécutables. **Ne dirige aucun technicien.** - `@reviewer` : Revues indépendantes de correction, régression, contrats et maintenabilité. **Ne modifie pas le code.** - `@security-auditor` : Audit indépendant d'une surface de sécurité. **Ne modifie pas le code.** - `@tech-writer` : Rédige et maintient la documentation. **N'écrit pas de code applicatif.** - `@test-engineer` : Conçoit, écrit et exécute des tests ciblés. **N'écrit pas de code de production.** - `@ui-designer` : Conçoit et implémente les interfaces Web et terminal. - `@verifier` : Vérifie indépendamment le comportement livré, les régressions et le respect des conventions (ruff, mypy, pytest, bandit, idempotence, mode dégradé). **Ne modifie pas le code.** - `@web-explorer` : Recherche et extrait des sources Web vérifiables. **Ne modifie pas le dépôt.** > **Note** : Ne pas utiliser `@coder` pour les tâches de documentation (`@tech-writer`) ni pour les tests (`@test-engineer`). ### Séparation des rôles | Type de tâche | Agent responsable | Ne pas confier à | |---|---|---| | Écrire/modifier du code | `@coder` | `@verifier`, `@explorer` | | Valider (ruff, mypy, pytest, bandit) | `@verifier` | `@coder` | | Diagnostiquer un bug | `@debugger` | `@coder` | | Écrire un test | `@test-engineer` | `@coder` | | Rédiger de la documentation | `@tech-writer` | `@coder` | | Explorer le dépôt (lecture) | `@explorer` | `@coder`, `@verifier` | | Arbitrage technique structurant | `@architect` | `@coder`, `@planner` | | Revue de code | `@reviewer` | `@coder`, `@verifier` | | Audit de sécurité | `@security-auditor` | `@coder`, `@verifier` | | Recherche web | `@web-explorer` | `@explorer` | | Découpage de travail complexe | `@planner` | `@coder` | --- ## 10. Workflow de modification 1. Lire la demande, [`TODO.md`](./TODO.md), les sections pertinentes de [`GUIDE_DEV_PYTHON.md`](./GUIDE_DEV_PYTHON.md), `git status` et les fichiers concernés. 2. Préserver les changements existants de l'utilisateur. 3. Pour une correction, reproduire d'abord le défaut avec un test automatisé lorsque c'est raisonnable. 4. Faire une modification étroite et cohérente, en respectant les conventions du projet (idempotence, modes explicites stricts et repli iCal → `pronotepy` uniquement en mode `auto`). 5. Faire vérifier le comportement par `@verifier` (ruff, mypy, pytest, bandit) et les cas d'erreur, notamment : - Succès de la synchronisation Pronote → CalDAV/XMPP. - Repli d'iCal vers `pronotepy` en mode `auto`, sans repli dans les modes explicites. - Distinction entre résultat vide et échec des sources. - Gestion des erreurs explicites sans fuite dans les causes ou tracebacks. 6. Mettre à jour la documentation et les exemples dans le même changement si leur comportement public évolue. 7. Cocher dans [`TODO.md`](./TODO.md) uniquement les éléments entièrement réalisés et validés. 8. Terminer avec un *handoff* concis : fichiers modifiés, validations exécutées, limites et prochaine étape. > **Pour les changements larges ou risqués** : Produire d'abord un audit ou un aperçu. > **Règle de commit** : Ne pas committer sans autorisation explicite. Une autorisation de commit ne vaut pas autorisation de push. --- ## 11. Branches et commits - Une évolution cohérente se fait sur une **branche dédiée**. - Nommer les branches selon le format `/`, où `` est l'un des suivants : `feature`, `fix`, `docs`, `chore` ou `refactor`. - Partir de l'état validé de la branche principale (`main` ou `dev`), sauf demande explicite. - Garder **un commit atomique par objectif vérifiable**. Utiliser les préfixes conventionnels pour les messages de commit : - `feat:` pour une nouvelle fonctionnalité. - `fix:` pour une correction de bug. - `docs:` pour une mise à jour de documentation. - `chore:` pour une tâche de maintenance. - `refactor:` pour une refactorisation de code. - Quand un agent a contribué au changement, ajouter un *trailer* Git standard au commit : ``` Co-authored-by: / ``` - Avant un commit autorisé, vérifier : - `git status` (fichiers modifiés attendus). - Le diff complet (`git diff`). - L'absence de secret ou de configuration locale dans le diff. - Les validations pertinentes (`ruff`, `mypy`, `pytest`, `bandit`). - `git diff --check` (pas de problèmes d'espaces blancs). - Après le commit, rapporter : - Le hash du commit. - Le contenu du commit. - Les validations exécutées. > **Règle absolue** : Ne jamais pousser (`git push`) sans demande distincte et explicite. --- ## 12. Définition de terminé Un changement est considéré comme **terminé** lorsque : - Le cas nominal et les échecs pertinents sont testés (ex. : synchronisation réussie, repli iCal → `pronotepy` en mode `auto`, modes explicites stricts, erreurs explicites). - Les outils de validation (`ruff`, `mypy`, `pytest`, `bandit`) passent sans erreur. - Aucun secret ni configuration locale n'apparaît dans le diff ou les fichiers suivis. - La documentation reste cohérente avec le code (ex. : mise à jour des exemples, des contrats ou des décisions d'architecture). - Le *handoff* distingue clairement : - Ce qui a été vérifié localement (ex. : tests unitaires, linter). - Ce qui nécessite encore une vérification manuelle (ex. : tests d'intégration avec un serveur CalDAV réel). --- ## 13. Versionnage et releases ### Politique de versionnage Le projet suit **Semantic Versioning** (semver.org v2.0.0). Phase actuelle : `0.x` (pré-`1.0.0`). | Changement | Incrément | |------------|----------| | Défaut constaté au déploiement | Patch (`0.1.Z`) — correction rétrocompatible | | Ajout ou cassure en phase `0.x` | Minor (`0.Y.0`) | | Déploiement réel validé | `1.0.0` | ### Règle absolue de validation **Aucune montée de version (tag + release) ne peut être effectuée sans validation préalable en environnement réel.** Les tests automatisés et la revue de code ne suffisent pas ; le correctif ou la fonctionnalité doit avoir été testé avec succès sur le serveur de production (ou un environnement équivalent) avant de tagger. ### Procédure de release 1. **Valider en environnement réel** : le correctif ou la fonctionnalité est testé sur le serveur de production. 2. **Mettre à jour `pyproject.toml`** : incrémenter le champ `version` à la nouvelle version. 3. **Mettre à jour `CHANGELOG.md`** : ajouter une entrée sous le format [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) avec la nouvelle version et la date. 4. **Committer** : un commit `chore: monter en version x.y.z` regroupe les mises à jour de `pyproject.toml` et `CHANGELOG.md`. 5. **Tagger** : créer un tag annoté `vx.y.z` sur le commit de version. 6. **Pousser le tag** : `git push origin vx.y.z`. 7. **Créer la release** sur Gitea avec le changelog correspondant. ### Cohérence des versions Les trois sources de version doivent toujours être synchronisées au moment d'un tag : - Le tag Git (`vx.y.z`) - `pyproject.toml` (`version = "x.y.z"`) - `CHANGELOG.md` (`## [x.y.z] - YYYY-MM-DD`) > **Rappel** : Ne jamais créer un tag sans avoir d'abord mis à jour > `pyproject.toml` et `CHANGELOG.md`.