Files
college-infos/AGENTS.md
Antoine Van Elstraete 6b9ab75977 docs: document openai-compatible provider and FIXME_M9 corrections
Update GUIDE_DEV_PYTHON.md, TODO.md, and AGENTS.md to reflect the
decisions and work done in the FEAT_M9 and FIXME_M9 sessions.

GUIDE_DEV_PYTHON.md:
- Header: add entry in recent updates
- 3.1.2: AI_PROVIDER now documents openai-compatible with
  Literal type; add AI_ALLOW_INSECURE_HTTP row; move decision
  block after table to fix rendering
- 3.2: AISettings code block updated with openai-compatible and
  allow_insecure_http field; decision note extended
- 3.1.3: .env.example adds OpenRouter (HTTPS) and Ollama (HTTP)
  examples, both commented

TODO.md:
- M9 factory line now mentions openai-compatible with URL validation
- Add FEAT_M9 and FIXME_M9 notes after M9 acceptance criteria

AGENTS.md:
- Section 5: new subsection for openai-compatible provider contract
  documenting validation rules, degraded mode, and security constraints

Co-authored-by: opencode/tech-writer anthropic.claude-sonnet-4-5 <anthropic.claude-sonnet-4-5@agents.invalid>
2026-09-07 19:59:13 +02:00

329 lines
15 KiB
Markdown

# 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 `<type>/<sujet-en-kebab-case>`, où `<type>` 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: <harness>/<modèle> <adresse@agents.invalid>
```
- 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).