fonzarely/regine-photos-archiverpublic Fork
⑂ main
Commits
⬇ Clone ▾
git clone https://git.rickub.com/fonzarely/regine-photos-archiver.git
git clone ssh://git@rickub.com/fonzarely/regine-photos-archiver.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP
refactor 19cf29c · on main · Fabien Champigny · Sep 24, 2026
tasks.md · 196 lines · 32.2 KBmarkdown
Blame HistoryOpen raw

description: "Task list for feature implementation"

Tasks: Configuration du contexte de travail de Régine

Input: Design documents from /specs/003-config-contexte-travail/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md

Tests: Incluses — plan.md § Project Structure nomme explicitement les fichiers de test attendus, ce qui vaut demande explicite au sens des règles de génération.

Organisation: Tâches groupées par user story (spec.md) pour une implémentation et une validation indépendantes de chacune.

Dépendances inter-specs (à lire avant de commencer)

Dépendance Fournit État
specs/004-categorisation-dossiers regine_core.config.db.open_context_db (base de contexte centralisée, déjà créée) Implémentée
specs/002-profil-boitiers-optionnel regine_core.camera_profile.db (table boitiers, list_boitiers, rename_boitier, get_or_create_boitier) — possède la table boitiers, cette spec ne fait que l'exposer (cf. plan.md « Correction du 2026-09-18 ») Implémentée
specs/005-checkout-reconciliation regine_core.archive.manifest (manifeste persistant par dossier, PRAGMA user_version/application_id) et regine_core.archive.verrou (verrouillage) — réutilisés tels quels pour FR-006/FR-010 (détection de travail non réconcilié) Implémentée
specs/001-import-photos regine_core.import_carte.copie.EXTENSIONS_MAITRES (réutilisée par le parcours NAS, US3/FR-012, pour reconnaître un fichier maître sans dupliquer la liste) Implémentée

Écarts entre les documents de conception et l'état réel du code (à respecter pendant l'implémentation)

  • data-model.md § Base de contexte centralisée a été rédigé avant l'implémentation de specs/002-profil-boitiers-optionnel et décrit une colonne source à deux valeurs (import/parcours_nas). Le schéma réellement livré par specs/002 (camera_profile/db.py) utilise déjà source pour une distinction différente et orthogonale (modele/numero_serie/manuel — comment un boîtier a été désambiguïsé). Ne PAS renommer ni migrer cette colonne déjà testée : réutiliser la même colonne texte libre en y ajoutant la seule valeur manquante parcours_nas (FR-012) ; à l'affichage (cameras list), traiter toute valeur différente de parcours_nas comme « détecté via import » (FR-008).
  • research.md ne fixe pas l'emplacement de fichier de la base de contexte centralisée (seulement son schéma). Décision prise dans cette liste de tâches (T005) : un emplacement fixe par utilisateur, indépendant de local_work_dir (qui, lui, est modifiable, cf. FR-010/SC-005 — la base de contexte ne doit jamais se perdre à un changement de chemin).
  • FR-006/Acceptance Scenario 3 (US1) : lu littéralement (« une base de données de travail est créée... à la racine de ce répertoire »). Réutilise directement regine_core.archive.manifest.ouvrir_ou_creer sur local_work_dir lui-même (déjà générique, accepte n'importe quel dossier) plutôt que d'inventer un second mécanisme — cohérent avec l'Assumption de spec.md (« cette spécification couvre son initialisation... pas son schéma détaillé »). Le manifeste par dossier archivé (FR-007) reste géré indépendamment par specs/001/specs/005, déjà correct. Retiré par la Phase 7 (User Story 5, 2026-09-23/24) : ce choix s'est révélé être une impasse — ce manifeste vide à la racine de local_work_dir n'a jamais été exploité par aucun autre module (le verrou/manifeste réel vit uniquement côté archive, cf. T009 ci-dessous), et FR-006 a depuis été révisée par l'utilisateur pour désigner explicitement la base de contexte centralisée elle-même (boîtiers/catégories/historique), déplacée à cet emplacement plutôt qu'à ~/.regine/.
  • Ajouté en Phase 7 (User Story 5, 2026-09-23/24) : open_context_db perd son défaut implicite (path: Path | None = None → path: Path) mais garde exactement la même sémantique de paramètre (un chemin de fichier complet, jamais un dossier) — tous les appels existants de specs/001/002/004 passent déjà un chemin de fichier explicite (tmp_path / "contexte.sqlite3" ou équivalent) et restent donc valides sans modification (cf. T046). Seuls les appels qui reposaient sur l'ancien défaut (open_context_db(None), dans config_cmd.py/archive_cmd.py/les écrans GUI, et le bug déjà présent dans import_cmd.py) sont concernés par cette révision.

Phase 1: Setup

  • T001 [P] Créer packages/regine-core/src/regine_core/config/context.py (squelette, docstring de module uniquement)
  • T002 [P] Créer packages/regine-core/src/regine_core/config/smb.py (squelette, docstring de module uniquement)
  • T003 [P] Créer packages/regine-core/src/regine_core/config/cameras_screen.py (squelette, docstring de module uniquement)
  • T004 [P] Créer packages/regine-cli/src/regine_cli/config_cmd.py (squelette, docstring de module uniquement) — et packages/regine-cli/tests/contract/, [tool.pytest.ini_options] testpaths = ["tests"] ajouté à packages/regine-cli/pyproject.toml (aucun test CLI automatisé n'existait encore dans le dépôt)

Phase 2: Foundational (Blocking Prerequisites)

Objectif : la persistance du contexte de travail (chemins + état SMB) et la détection de travail non réconcilié, dont dépendent les 3 user stories.

⚠️ CRITIQUE : aucune user story ne peut être implémentée avant la fin de cette phase.

  • T005 Étendre packages/regine-core/src/regine_core/config/db.py : ajouter CHEMIN_CONTEXTE_PAR_DEFAUT = Path.home() / ".regine" / "contexte.sqlite3" et rendre open_context_db(path: Path | None = None) optionnel (défaut : CHEMIN_CONTEXTE_PAR_DEFAUT), sans changer le comportement pour les appelants existants qui passent déjà un chemin explicite (specs/001/002/004)
  • T006 [P] Test unitaire de open_context_db(None) (utilise le chemin par défaut, le crée si absent) dans packages/regine-core/tests/unit/test_config_db_defaut.py — le test patch la constante de module CHEMIN_CONTEXTE_PAR_DEFAUT plutôt que Path.home (celle-ci est déjà évaluée à l'import, un monkeypatch après coup n'aurait aucun effet)
  • T007 Implémenter ContexteTravail (dataclass), le schéma de la table contexte (ligne unique, id INTEGER PRIMARY KEY CHECK (id = 1)) et charger_contexte(conn) -> ContexteTravail dans packages/regine-core/src/regine_core/config/context.py (data-model.md § Contexte de travail)
  • T008 Implémenter les exceptions dédiées dans context.py : RepertoireAbsentError, CheminsAmbigusError, CheminArchiveInvalideError, TravailNonReconcilieError (porte la liste des dossiers concernés) — ArchiveInaccessibleError ajoutée en plus (cf. note T009)
  • T009 Implémenter lister_dossiers_non_reconcilies(local_work_dir: Path, archive_root_local: Path) -> list[Path] dans context.py — écart avec la conception initiale découvert à l'implémentation : le verrou/manifeste vit uniquement côté archive (specs/005, jamais recopié dans la copie de travail locale lors d'un checkout), donc parcourir local_work_dir seul ne peut jamais rien trouver. Corrigé : parcourt les manifestes de archive_root_local (point de montage local du partage SMB, smb.chemin_local_du_partage), ne retient que ceux ayant une copie locale présente sous local_work_dir. Lève ArchiveInaccessibleError si l'archive n'est pas accessible plutôt que de conclure à tort qu'il n'y a rien à signaler
  • T010 Implémenter enregistrer_chemins(conn, *, temp_dir=None, local_work_dir=None, archive_smb_path=None, creer_repertoires_manquants=False, forcer_collision=False, ignorer_verification_archive=False) -> ContexteTravail dans context.py : valide archive_smb_path (DOIT commencer par smb://, sinon CheminArchiveInvalideError), détecte la collision temp_dir == local_work_dir (CheminsAmbigusError sauf forcer_collision), bloque un changement de local_work_dir via lister_dossiers_non_reconcilies (TravailNonReconcilieError) — sauf si aucune archive n'est encore configurée (rien à vérifier) ou ignorer_verification_archive=True (geste explicite, même famille que forcer_collision) ; une archive configurée mais inaccessible refuse le changement par défaut (ArchiveInaccessibleError remontée telle quelle). Crée les répertoires manquants seulement si creer_repertoires_manquants (sinon RepertoireAbsentError), initialise la base de travail à la racine d'un nouveau local_work_dir (regine_core.archive.manifest.ouvrir_ou_creer, FR-006/SC-003), puis persiste
  • T011 Implémenter mettre_a_jour_etat_smb(conn, etat: Literal["mounted","unavailable","unknown"]) -> None dans context.py
  • T012 [P] Tests unitaires de context.py dans packages/regine-core/tests/unit/test_context_paths.py : configuration initiale, répertoire manquant sans/avec creer_repertoires_manquants, collision temp_dir/local_work_dir sans/avec forcer_collision, archive_smb_path invalide (ne commence pas par smb://), FR-006 (base de travail présente à la racine après configuration), champs non fournis conservés (SC-005)
  • T013 [P] Tests unitaires de lister_dossiers_non_reconcilies/enregistrer_chemins dans test_context_paths.py : dossier verrouillé détecté, dossier réconcilié ignoré, archive inaccessible lève, aucune archive configurée n'exige aucune vérification, ignorer_verification_archive passe outre explicitement — 15/15 tests passent (packages/regine-core/src/regine_core/config/smb.py reçoit dès cette phase chemin_local_du_partage, requis par T009/T010 ; le reste de smb.py — disponible/declencher_montage/assurer_disponibilite — reste prévu en Phase 4/T020)

Checkpoint : la persistance du contexte est prête ; les 3 user stories peuvent commencer.


Phase 3: User Story 1 - Configuration initiale des emplacements de travail (Priority: P1) 🎯 MVP

Objectif : configurer et consulter les 3 emplacements de travail, avec initialisation de la base de travail et refus explicite d'un changement dangereux.

Test indépendant : configurer les 3 chemins sur un contexte vide (regine config set-paths), vérifier leur persistance (regine config show), vérifier qu'un changement de local_work_dir avec un dossier verrouillé dessous est refusé.

Tests for User Story 1

  • T014 [P] [US1] Test d'intégration du cycle configuration → initialisation de la base de travail → changement de chemin bloqué dans packages/regine-core/tests/integration/test_config_context.py (Acceptance Scenarios 1 à 5 de US1)
  • T015 [P] [US1] Test de contrat de la surface CLI (set-paths, show, codes de sortie, --json) dans packages/regine-cli/tests/contract/test_cli_config.py

Implementation for User Story 1

  • T016 [US1] Implémenter la sous-commande regine config set-paths dans packages/regine-cli/src/regine_cli/config_cmd.py : --temp-dir, --local-work-dir, --archive-smb, --yes (confirme la création de répertoire, FR-003), --force (confirme la collision de chemins), --ignorer-verification-archive ; traduit chaque exception de context.py en message stderr + code de sortie non-zéro (cf. contracts/cli-config.md)
  • T017 [US1] Implémenter la sous-commande regine config show [--json] dans config_cmd.py : affiche les 3 chemins et l'état SMB connu, ou la liste des chemins manquants si le contexte n'est pas encore configuré (Edge Case) — implémentée en même temps que T021 (US2) : show déclenche smb.assurer_disponibilite dès qu'une archive est configurée, testé par test_cli_config.py
  • T018 [US1] Construire l'analyseur d'arguments (construire_analyseur/main) dans config_cmd.py, cohérent avec import_cmd.py/archive_cmd.py

Checkpoint : User Story 1 fonctionnelle et testable indépendamment (MVP) — 125/125 tests passent.


Phase 4: User Story 2 - Assistance quand l'archive NAS n'est pas accessible (Priority: P2)

Objectif : détecter l'indisponibilité du partage SMB avant qu'une opération échoue, déclencher le montage natif, permettre une reprise sans reconfiguration.

Test indépendant : configurer un archive_smb_path pointant vers un partage démonté, lancer regine config show, vérifier qu'un montage est déclenché et que l'état SMB est mis à jour ; remonter manuellement puis relancer, vérifier la reprise sans reconfiguration.

Note d'ordonnancement : implémentée en avance sur l'ordre de priorité initial, dès la Phase 3 — regine config show (T017) en a besoin directement (cf. quickstart.md scénario 4) et context.enregistrer_chemins/lister_dossiers_non_reconcilies (T009/T010, Phase 2) dépendaient déjà de smb.chemin_local_du_partage.

Tests for User Story 2

  • T019 [P] [US2] Test unitaire de smb.py dans packages/regine-core/tests/unit/test_smb_disponibilite.py : disponible() sur un point de montage présent/absent (via un répertoire temporaire simulant /Volumes/<partage>, injecté par dépendance plutôt que codé en dur), assurer_disponibilite() met à jour smb_last_known_state, déclenche/ne déclenche pas le montage selon declencher

Implementation for User Story 2

  • T020 [US2] Implémenter packages/regine-core/src/regine_core/config/smb.py : disponible(archive_smb_path: str) -> bool (vérifie l'accessibilité du point de montage macOS /Volumes/<partage>, dérivé de l'URL smb://...), declencher_montage(archive_smb_path: str) -> None (open smb://..., cf. research.md § 1), assurer_disponibilite(conn, archive_smb_path, *, declencher=True) -> bool (met à jour l'état via context.mettre_a_jour_etat_smb, déclenche le montage si indisponible)
  • T021 [US2] Intégrer smb.assurer_disponibilite dans regine config show (config_cmd.py) : si un archive_smb_path est configuré, vérifier sa disponibilité avant d'afficher l'état, déclencher le montage natif si nécessaire (Acceptance Scenario 1/2 de US2) — pas de nouvelle tentative automatique en boucle (Acceptance Scenario 3, une seule vérification par invocation, cohérent avec l'exécution sans démon)

Checkpoint : User Stories 1 et 2 fonctionnelles indépendamment.


Phase 5: User Story 3 - Nommer les boîtiers à partir des photos déjà archivées (Priority: P3)

Objectif : lister et nommer les boîtiers déjà enregistrés dans la base de contexte centralisée, sans réimport ni parcours du NAS ; parcours volontaire du NAS en complément optionnel (FR-012).

Test indépendant : sur une base de contexte contenant déjà des boîtiers enregistrés par un import (cf. specs/001), lister les boîtiers puis en nommer un, sans déclencher d'accès au NAS.

Tests for User Story 3

  • T022 [P] [US3] Test unitaire de cameras_screen.py dans packages/regine-core/tests/unit/test_cameras_screen.py : liste des boîtiers déjà enregistrés (réutilise camera_profile.db), nommage d'un boîtier connu, BoitierInconnuError sur un identifiant absent
  • T023 [P] [US3] Test d'intégration du parcours NAS dans packages/regine-core/tests/integration/test_cameras_scan_nas.py : une arborescence simulant une archive avec des fichiers maîtres de boîtiers non encore connus (tags EXIF réels via exiftool) ; vérifie que parcourir_nas les ajoute avec source="parcours_nas" et n'affecte pas les boîtiers déjà connus (source modele), plus idempotence (second parcours ne redouble rien)

Implementation for User Story 3

  • T024 [US3] Implémenter packages/regine-core/src/regine_core/config/cameras_screen.py : lister_boitiers(conn) -> list[Boitier] (délègue à camera_profile.db.list_boitiers, FR-008), nommer_boitier(conn, boitier_id: int, nom: str) -> None (délègue à camera_profile.db.rename_boitier après avoir vérifié l'existence de boitier_id, sinon BoitierInconnuError — cf. contracts/cli-config.md), parcourir_nas(conn, archive_root: Path) -> int (parcourt archive_root à la recherche de fichiers d'extension regine_core.import_carte.copie.EXTENSIONS_MAITRES, lit Model via regine_core.metadata.exif.read_camera_tags, appelle camera_profile.db.get_or_create_boitier(..., source="parcours_nas"), retourne le nombre de boîtiers réellement nouveaux — FR-012, PEUT donc optionnel mais implémenté ici)
  • T025 [US3] Implémenter les sous-commandes regine config cameras list [--json], regine config cameras rename --id ID --nom NOM, regine config cameras scan-nas [--confirm] dans config_cmd.py — scan-nas sans --confirm avertit et sort en code non-zéro sans rien exécuter (FR-012, cf. contracts/cli-config.md) ; vérifie la disponibilité de l'archive (smb.assurer_disponibilite) puis dérive son point de montage local via smb.chemin_local_du_partage (même convention que US2, pas de second mécanisme de résolution). 9/9 tests de contrat CLI passent (test_cli_config.py, étendu pour cameras list/rename/scan-nas)

Checkpoint : les 3 user stories sont fonctionnelles indépendamment — 134/134 tests passent (packages/regine-core + packages/regine-cli).


Phase 6: User Story 4 - Configurer le contexte de travail depuis l'interface graphique (Priority: P4)

Ajoutée le 2026-09-21 (clarification + extension de plan post-implémentation) — cf. spec.md § Clarifications Session 2026-09-21.

Objectif : consulter et modifier le contexte de travail (chemins, état SMB, boîtiers nommés) depuis un 3e onglet de regine-gui (déjà implémentée par specs/006-gui-tri-consultation), en appelant exclusivement les fonctions regine_core.config déjà existantes — aucune nouvelle fonction regine-core requise (cf. plan.md extension du 2026-09-21).

Test indépendant : ouvrir l'onglet Configuration, y modifier un chemin, vérifier via regine config show (CLI) que le changement est bien reflété — sans dépendre des onglets Tri/Consultation.

Implementation for User Story 4

  • T029 [US4] Créer packages/regine-gui/src/regine_gui/config_view.py (squelette, docstring de module uniquement)
  • T030 [US4] Implémenter EcranConfiguration dans config_view.py : affichage des 3 chemins et de l'état SMB au chargement (regine_core.config.context.charger_contexte), formulaire de modification appelant context.enregistrer_chemins — chaque exception (RepertoireAbsentError, CheminsAmbigusError, CheminArchiveInvalideError, TravailNonReconcilieError, ArchiveInaccessibleError) présentée clairement à l'utilisateur plutôt qu'une exception technique brute (FR-013, US4 Acceptance Scenario 2/3)
  • T031 [US4] Étendre config_view.py avec la liste des boîtiers et leur nommage (cameras_screen.lister_boitiers/nommer_boitier, US4 Acceptance Scenario 4)
  • T032 [US4] Câbler l'onglet « Configuration » dans packages/regine-gui/src/regine_gui/app.py (FenetrePrincipale), aux côtés des onglets Tri/Consultation déjà existants
  • T033 [US4] Valider manuellement en mode headless (QT_QPA_PLATFORM=offscreen, même méthode que specs/006) : affichage/modification des chemins, gestion d'erreur, liste/nommage de boîtiers, parité avec regine config show/cameras list (Scénario 7 de quickstart.md, SC-008) — découverte de méthode de test : QMessageBox.warning bloque en attente d'un clic (comportement normal en usage réel), non testable tel quel en script headless ; validé en monkeypatchant QMessageBox.warning pour capturer l'appel sans dialogue modal réel — RepertoireAbsentError et TravailNonReconcilieError confirmées gérées sans crash, message clair, rien enregistré suite au refus

Checkpoint : les 4 user stories sont fonctionnelles indépendamment.


Phase 7: User Story 5 - Reprendre le travail sur une nouvelle machine sans perdre le contexte (Priority: P2)

Ajoutée le 2026-09-23/24 (écart trouvé par l'utilisateur + spec/plan révisés) — cf. spec.md § Clarifications Session 2026-09-23.

Objectif : séparer la configuration globale (chemins, propre à la machine, ~/.regine/config.sqlite3) de la base de contexte centralisée (boîtiers/catégories/historique d'imports, désormais à <local_work_dir>/contexte.sqlite3, remplaçant l'ancien manifeste vide jamais exploité qu'enregistrer_chemins y créait) ; répliquer cette dernière vers l'archive à chaque regine reconcile (FR-016) et la restaurer automatiquement à la configuration initiale ou au premier regine checkout sur une machine qui ne l'a pas encore (FR-017).

Test indépendant : enrichir le contexte sur une installation (nommer un boîtier), archiver un dossier (regine reconcile), configurer une seconde installation « neuve » pointant vers la même archive, vérifier que regine config cameras list y retrouve immédiatement ce boîtier sans réimport.

Tests for User Story 5

  • T035 [P] [US5] Adapter packages/regine-core/tests/unit/test_config_db_defaut.py : open_global_config_db() sans argument utilise ~/.regine/config.sqlite3 par défaut (comportement testé jusqu'ici sur open_context_db) ; open_context_db(path) exige désormais path (plus de défaut implicite)
  • T036 [P] [US5] Test unitaire de environnement.ouvrir_environnement dans packages/regine-core/tests/unit/test_environnement.py : contexte_conn is None tant que local_work_dir n'est pas configuré dans la config globale ; une fois configuré, contexte_conn pointe vers <local_work_dir>/contexte.sqlite3 (créée si absente)
  • T037 [P] [US5] Test unitaire de context.sauvegarder_contexte/restaurer_contexte_si_absent dans packages/regine-core/tests/unit/test_contexte_replication.py : réplique vers l'archive (contenu identique après coup, vérifié par somme de contrôle), no-op si la base locale n'existe pas encore, restaure si absente localement mais présente sur l'archive, no-op si absente des deux côtés (première installation)

Implementation for User Story 5

  • T038 [US5] Refactorer packages/regine-core/src/regine_core/config/db.py : extraire le bootstrap PRAGMA application_id/user_version commun dans une fonction privée partagée ; open_context_db(path: Path) -> sqlite3.Connection (chemin désormais obligatoire, CHEMIN_CONTEXTE_PAR_DEFAUT retirée) ; nouvelle open_global_config_db(path: Path | None = None) -> sqlite3.Connection (défaut CHEMIN_CONFIG_GLOBALE_PAR_DEFAUT = Path.home() / ".regine" / "config.sqlite3")
  • T039 [US5] Créer packages/regine-core/src/regine_core/config/environnement.py : dataclass Environnement (config_conn, contexte_conn: sqlite3.Connection | None, local_work_dir: Path | None), ouvrir_environnement(config_db_path: Path | None = None) -> Environnement (FR-014/015)
  • T040 [US5] Étendre packages/regine-core/src/regine_core/config/context.py : enregistrer_chemins initialise désormais <local_work_dir>/contexte.sqlite3 via db.open_context_db à la place de l'ancien regine_core.archive.manifest.ouvrir_ou_creer (retiré — supprime la dépendance de ce module vers regine_core.archive, jamais réellement exploitée, cf. § Écarts en tête de fichier) ; nouvelles sauvegarder_contexte(local_work_dir, archive_root_local) -> None (FR-016, copie vérifiée vers <archive_root_local>/.regine-contexte.sqlite3, no-op si source absente) et restaurer_contexte_si_absent(local_work_dir, archive_root_local) -> bool (FR-017, copie vérifiée dans l'autre sens si la base locale est absente et qu'une sauvegarde existe)
  • T041 [US5] Mettre à jour packages/regine-cli/src/regine_cli/config_cmd.py : ouvrir_environnement() remplace open_context_db(Path(args.contexte_db) if args.contexte_db else None) ; flag --contexte-db renommé --config-db ; set-paths appelle restaurer_contexte_si_absent juste après un enregistrer_chemins réussi si local-work-dir et archive-smb sont tous deux connus à l'issue de la commande, annonce le résultat sur stdout (cf. contracts/cli-config.md)
  • T042 [US5] Mettre à jour packages/regine-cli/src/regine_cli/archive_cmd.py : _charger_archive_root_local utilise open_global_config_db/ouvrir_environnement (l'emplacement de l'archive vit désormais dans la config globale, pas la base de contexte centralisée) ; flag --contexte-db renommé --config-db (browse/restore) ; _cmd_reconcile appelle sauvegarder_contexte juste après un archivage réussi (FR-016) ; _cmd_checkout appelle restaurer_contexte_si_absent juste après un checkout réussi (FR-017)
  • T043 [US5] Corriger packages/regine-cli/src/regine_cli/import_cmd.py (bug réel trouvé par l'utilisateur, cf. contracts/regine-core-api.md) : supprime _NOM_CONTEXTE_DB_PAR_DEFAUT/le flag --contexte-db (divergeait silencieusement du reste de la CLI) ; dérive directement Path(args.local_root) / "contexte.sqlite3" pour db.open_context_db
  • T044 [US5] Mettre à jour packages/regine-gui/src/regine_gui/app.py : FenetrePrincipale résout ouvrir_environnement() une fois à la construction, propage le résultat aux onglets plutôt que chacun n'ouvre indépendamment open_context_db(self._contexte_db)
  • T045 [US5] Mettre à jour packages/regine-gui/src/regine_gui/config_view.py/import_view.py/consultation_view.py : consomment l'environnement résolu (chemins/état SMB via environnement.config_conn, boîtiers/catégories/historique via environnement.contexte_conn) plutôt que d'ouvrir elles-mêmes une connexion unique ambiguë
  • T046 [P] [US5] Vérifier chaque appelant existant de open_context_db dans specs/001-import-photos/specs/002-profil-boitiers-optionnel/specs/004-categorisation-dossiers (tests unitaires/intégration) : tous passent déjà un chemin de fichier explicite (tmp_path / "contexte.sqlite3" ou équivalent), donc déjà compatibles avec la signature révisée sans modification — seul test_config_db_defaut.py dépendait de l'ancien défaut implicite, déjà traité par T035
  • T047 [US5] Valider manuellement en mode headless (QT_QPA_PLATFORM=offscreen, même méthode que les phases précédentes) les Scénarios 8, 9 et 10 de quickstart.md : réplication à un regine reconcile, restauration à la configuration initiale d'une « nouvelle machine », restauration au premier regine checkout si la configuration initiale n'a pas suffi (archive inaccessible à ce moment-là) — Scénarios 8 et 9 exécutés de bout en bout via un script headless simulant deux machines (import GUI sur machine A, regine reconcile déclenchant bien la sauvegarde vers <archive>/.regine-contexte.sqlite3, puis regine config set-paths sur une machine B neuve restaurant automatiquement contexte.sqlite3 avant tout import) : Contexte local machine A existe ? True, Sauvegarde présente sur l'archive ? True, Contexte restauré sur machine B ? True. Scénario 10 (restauration au premier checkout plutôt qu'à set-paths) non exécuté en conditions réelles (aurait nécessité un troisième montage SMB simulé) mais son câblage (_restaurer_contexte_post_checkout dans archive_cmd.py, appelant exactement restaurer_contexte_si_absent) est identique à celui du Scénario 9 déjà validé et est couvert unitairement par T037 — aucun boîtier n'a été détecté lors de l'import (fichiers factices sans EXIF), donc c'est la présence de contexte.sqlite3 restauré (plutôt qu'un boîtier nommé visible dans cameras list) qui atteste de la réplication

Checkpoint : les 5 user stories sont fonctionnelles indépendamment ; un contexte enrichi sur une machine est retrouvable intégralement sur une autre pointant vers la même archive.


Phase 8: Polish & Cross-Cutting Concerns

  • T026 [P] Exécuter manuellement les 6 scénarios de specs/003-config-contexte-travail/quickstart.md (adaptés à la surface CLI réellement livrée) et consigner le résultat — scénarios 1, 2, 3, 5, 6 exécutés via la vraie CLI (uv run python -m regine_cli.config_cmd config ...), tous conformes. Scénario 4 (montage/démontage SMB réel) non exécutable dans cet environnement de développement (pas de partage SMB physique disponible) — le mécanisme sous-jacent (smb.disponible/assurer_disponibilite/declencher_montage) est validé unitairement avec un point de montage injecté (T019), et config show a correctement détecté smb://nas.local/regine-archive (inexistant dans cet environnement) comme unavailable lors du scénario 1, confirmant la détection réelle
  • T027 [P] ruff check --fix + ruff format sur packages/regine-core et packages/regine-cli
  • T028 Mettre à jour contracts/cli-config.md et créer contracts/regine-core-api.md — signatures réelles documentées, écarts de conception consignés (cf. § Écarts en tête de fichier) ; note explicite que regine_cli.import_cmd/archive_cmd (specs/001/005) ne consomment pas encore ce contexte configuré (ils prennent --archive-root/--local-root en flags explicites) — intégration future hors périmètre de cette spec, non construite ici sans nouvelle demande explicite
  • T034 [P] Exécuter le Scénario 7 de quickstart.md (déjà couvert par T033, sans duplication) et ruff check --fix/ruff format sur packages/regine-gui (nouveau fichier config_view.py) — 172/172 tests regine-core/regine-cli toujours au vert (aucune régression, aucun nouveau test requis côté core puisqu'aucune nouvelle fonction n'a été ajoutée)
  • T048 [P] Exécuter les Scénarios 8, 9 et 10 de quickstart.md (cf. T047, sans duplication) et ruff check --fix/ruff format sur les trois paquets (regine-core, regine-cli, regine-gui) ; exécuter la suite de tests complète du dépôt et confirmer l'absence de régression sur les specs consommatrices (specs/001/002/004/005/006) ; mettre à jour contracts/regine-core-api.md/cli-config.md si l'implémentation révèle un écart avec les signatures documentées — ruff check --fix : aucun problème détecté sur les trois paquets ; ruff format : 1 fichier reformaté (changement cosmétique, sans lien avec cette story) ; suite complète : 190/190 tests passent (aucune régression sur specs/001/002/004/005/006) ; aucun écart supplémentaire trouvé nécessitant une mise à jour des contrats au-delà de ceux déjà consignés lors de T041-T043

Dependencies & Execution Order

  • Setup (Phase 1) : aucune dépendance, tâches [P] en parallèle.
  • Foundational (Phase 2) : dépend de Setup ; bloque toutes les user stories.
  • US1 (Phase 3) : dépend de Foundational uniquement. MVP.
  • US2 (Phase 4) : dépend de Foundational ; s'intègre à regine config show (US1) mais reste testable indépendamment (smb.py seul).
  • US3 (Phase 5) : dépend de Foundational et de specs/002 (déjà livrée) ; indépendante de US1/US2.
  • US4 (Phase 6) : dépend de US1/US2/US3 (appelle leurs fonctions telles quelles) et de specs/006-gui-tri-consultation (déjà livrée, fournit regine-gui/app.py/PySide6) ; aucune dépendance nouvelle côté regine-core.
  • US5 (Phase 7) : dépend de Foundational (révise directement db.py/context.py de la Phase 2) et de specs/005-checkout-reconciliation (déjà livrée, fournit reconcile/checkout sur lesquels s'accrochent FR-016/FR-017) ; touche transversalement US1 (config_cmd.py), US3 (consommateur de la base de contexte déplacée) et US4 (écrans GUI) sans changer leur comportement observable — à mener après elles, jamais en parallèle, du fait de ce chevauchement de fichiers.
  • Polish (Phase 8) : dépend des user stories livrées (au minimum US1 ; T034 dépend spécifiquement de US4, T048 dépend spécifiquement de US5).

Implementation Strategy

MVP = Phase 1 + 2 + 3 (US1). US2 et US3 sont des incréments indépendants livrables ensuite dans l'ordre de priorité. US4 (ajoutée le 2026-09-21) est un incrément de confort pur, livrable à tout moment après specs/006-gui-tri-consultation — sans lui, la CLI (US1-3) reste pleinement fonctionnelle et suffisante. US5 (ajoutée le 2026-09-23/24) révise une hypothèse de conception jamais correctement exploitée depuis le début (US1/FR-006) plutôt que d'ajouter une capacité isolée : contrairement à US2-4, elle touche des fichiers déjà livrés par les phases précédentes (db.py, context.py, config_cmd.py, archive_cmd.py, import_cmd.py, les écrans GUI) et doit donc être menée en dernier, séquentiellement, plutôt qu'en parallèle d'une autre user story.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
---

description: "Task list for feature implementation"
---

# Tasks: Configuration du contexte de travail de Régine

**Input**: Design documents from `/specs/003-config-contexte-travail/`
**Prerequisites**: plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md

**Tests**: Incluses — `plan.md` § Project Structure nomme explicitement les fichiers de test attendus, ce qui vaut demande explicite au sens des règles de génération.

**Organisation**: Tâches groupées par user story (spec.md) pour une implémentation et une validation indépendantes de chacune.

## Dépendances inter-specs (à lire avant de commencer)

| Dépendance | Fournit | État |
|---|---|---|
| `specs/004-categorisation-dossiers` | `regine_core.config.db.open_context_db` (base de contexte centralisée, déjà créée) | Implémentée |
| `specs/002-profil-boitiers-optionnel` | `regine_core.camera_profile.db` (table `boitiers`, `list_boitiers`, `rename_boitier`, `get_or_create_boitier`) — **possède** la table `boitiers`, cette spec ne fait que l'exposer (cf. plan.md « Correction du 2026-09-18 ») | Implémentée |
| `specs/005-checkout-reconciliation` | `regine_core.archive.manifest` (manifeste persistant par dossier, `PRAGMA user_version`/`application_id`) et `regine_core.archive.verrou` (verrouillage) — réutilisés tels quels pour FR-006/FR-010 (détection de travail non réconcilié) | Implémentée |
| `specs/001-import-photos` | `regine_core.import_carte.copie.EXTENSIONS_MAITRES` (réutilisée par le parcours NAS, US3/FR-012, pour reconnaître un fichier maître sans dupliquer la liste) | Implémentée |

## Écarts entre les documents de conception et l'état réel du code (à respecter pendant l'implémentation)

- **`data-model.md` § Base de contexte centralisée** a été rédigé avant l'implémentation de `specs/002-profil-boitiers-optionnel` et décrit une colonne `source` à deux valeurs (`import`/`parcours_nas`). Le schéma réellement livré par specs/002 (`camera_profile/db.py`) utilise déjà `source` pour une distinction différente et orthogonale (`modele`/`numero_serie`/`manuel` — **comment** un boîtier a été désambiguïsé). Ne PAS renommer ni migrer cette colonne déjà testée : réutiliser la même colonne texte libre en y ajoutant la seule valeur manquante `parcours_nas` (FR-012) ; à l'affichage (`cameras list`), traiter toute valeur différente de `parcours_nas` comme « détecté via import » (FR-008).
- **research.md ne fixe pas l'emplacement de fichier de la base de contexte centralisée** (seulement son schéma). Décision prise dans cette liste de tâches (T005) : un emplacement fixe par utilisateur, indépendant de `local_work_dir` (qui, lui, est modifiable, cf. FR-010/SC-005 — la base de contexte ne doit jamais se perdre à un changement de chemin).
- **FR-006/Acceptance Scenario 3 (US1)** : lu littéralement (« une base de données de travail est créée... à la racine de ce répertoire »). Réutilise directement `regine_core.archive.manifest.ouvrir_ou_creer` sur `local_work_dir` lui-même (déjà générique, accepte n'importe quel dossier) plutôt que d'inventer un second mécanisme — cohérent avec l'Assumption de spec.md (« cette spécification couvre son initialisation... pas son schéma détaillé »). Le manifeste par dossier archivé (FR-007) reste géré indépendamment par `specs/001`/`specs/005`, déjà correct. **Retiré par la Phase 7 (User Story 5, 2026-09-23/24)** : ce choix s'est révélé être une impasse — ce manifeste vide à la racine de `local_work_dir` n'a jamais été exploité par aucun autre module (le verrou/manifeste réel vit uniquement côté archive, cf. T009 ci-dessous), et FR-006 a depuis été révisée par l'utilisateur pour désigner explicitement la base de contexte centralisée elle-même (boîtiers/catégories/historique), déplacée à cet emplacement plutôt qu'à `~/.regine/`.
- **Ajouté en Phase 7 (User Story 5, 2026-09-23/24)** : `open_context_db` perd son défaut implicite (`path: Path | None = None` → `path: Path`) mais garde exactement la même sémantique de paramètre (un chemin de fichier complet, jamais un dossier) — tous les appels existants de `specs/001/002/004` passent déjà un chemin de fichier explicite (`tmp_path / "contexte.sqlite3"` ou équivalent) et restent donc valides sans modification (cf. T046). Seuls les appels qui reposaient sur l'ancien défaut (`open_context_db(None)`, dans `config_cmd.py`/`archive_cmd.py`/les écrans GUI, et le bug déjà présent dans `import_cmd.py`) sont concernés par cette révision.

## Phase 1: Setup

- [X] T001 [P] Créer `packages/regine-core/src/regine_core/config/context.py` (squelette, docstring de module uniquement)
- [X] T002 [P] Créer `packages/regine-core/src/regine_core/config/smb.py` (squelette, docstring de module uniquement)
- [X] T003 [P] Créer `packages/regine-core/src/regine_core/config/cameras_screen.py` (squelette, docstring de module uniquement)
- [X] T004 [P] Créer `packages/regine-cli/src/regine_cli/config_cmd.py` (squelette, docstring de module uniquement) — et `packages/regine-cli/tests/contract/`, `[tool.pytest.ini_options] testpaths = ["tests"]` ajouté à `packages/regine-cli/pyproject.toml` (aucun test CLI automatisé n'existait encore dans le dépôt)

---

## Phase 2: Foundational (Blocking Prerequisites)

**Objectif** : la persistance du contexte de travail (chemins + état SMB) et la détection de travail non réconcilié, dont dépendent les 3 user stories.

**⚠️ CRITIQUE** : aucune user story ne peut être implémentée avant la fin de cette phase.

- [X] T005 Étendre `packages/regine-core/src/regine_core/config/db.py` : ajouter `CHEMIN_CONTEXTE_PAR_DEFAUT = Path.home() / ".regine" / "contexte.sqlite3"` et rendre `open_context_db(path: Path | None = None)` optionnel (défaut : `CHEMIN_CONTEXTE_PAR_DEFAUT`), sans changer le comportement pour les appelants existants qui passent déjà un chemin explicite (specs/001/002/004)
- [X] T006 [P] Test unitaire de `open_context_db(None)` (utilise le chemin par défaut, le crée si absent) dans `packages/regine-core/tests/unit/test_config_db_defaut.py` — le test patch la constante de module `CHEMIN_CONTEXTE_PAR_DEFAUT` plutôt que `Path.home` (celle-ci est déjà évaluée à l'import, un monkeypatch après coup n'aurait aucun effet)
- [X] T007 Implémenter `ContexteTravail` (dataclass), le schéma de la table `contexte` (ligne unique, `id INTEGER PRIMARY KEY CHECK (id = 1)`) et `charger_contexte(conn) -> ContexteTravail` dans `packages/regine-core/src/regine_core/config/context.py` (data-model.md § Contexte de travail)
- [X] T008 Implémenter les exceptions dédiées dans `context.py` : `RepertoireAbsentError`, `CheminsAmbigusError`, `CheminArchiveInvalideError`, `TravailNonReconcilieError` (porte la liste des dossiers concernés) — `ArchiveInaccessibleError` ajoutée en plus (cf. note T009)
- [X] T009 Implémenter `lister_dossiers_non_reconcilies(local_work_dir: Path, archive_root_local: Path) -> list[Path]` dans `context.py` — **écart avec la conception initiale découvert à l'implémentation** : le verrou/manifeste vit uniquement côté archive (`specs/005`, jamais recopié dans la copie de travail locale lors d'un checkout), donc parcourir `local_work_dir` seul ne peut jamais rien trouver. Corrigé : parcourt les manifestes de `archive_root_local` (point de montage local du partage SMB, `smb.chemin_local_du_partage`), ne retient que ceux ayant une copie locale présente sous `local_work_dir`. Lève `ArchiveInaccessibleError` si l'archive n'est pas accessible plutôt que de conclure à tort qu'il n'y a rien à signaler
- [X] T010 Implémenter `enregistrer_chemins(conn, *, temp_dir=None, local_work_dir=None, archive_smb_path=None, creer_repertoires_manquants=False, forcer_collision=False, ignorer_verification_archive=False) -> ContexteTravail` dans `context.py` : valide `archive_smb_path` (DOIT commencer par `smb://`, sinon `CheminArchiveInvalideError`), détecte la collision `temp_dir == local_work_dir` (`CheminsAmbigusError` sauf `forcer_collision`), bloque un changement de `local_work_dir` via `lister_dossiers_non_reconcilies` (`TravailNonReconcilieError`) — sauf si aucune archive n'est encore configurée (rien à vérifier) ou `ignorer_verification_archive=True` (geste explicite, même famille que `forcer_collision`) ; une archive configurée mais inaccessible refuse le changement par défaut (`ArchiveInaccessibleError` remontée telle quelle). Crée les répertoires manquants seulement si `creer_repertoires_manquants` (sinon `RepertoireAbsentError`), initialise la base de travail à la racine d'un nouveau `local_work_dir` (`regine_core.archive.manifest.ouvrir_ou_creer`, FR-006/SC-003), puis persiste
- [X] T011 Implémenter `mettre_a_jour_etat_smb(conn, etat: Literal["mounted","unavailable","unknown"]) -> None` dans `context.py`
- [X] T012 [P] Tests unitaires de `context.py` dans `packages/regine-core/tests/unit/test_context_paths.py` : configuration initiale, répertoire manquant sans/avec `creer_repertoires_manquants`, collision `temp_dir`/`local_work_dir` sans/avec `forcer_collision`, `archive_smb_path` invalide (ne commence pas par `smb://`), FR-006 (base de travail présente à la racine après configuration), champs non fournis conservés (SC-005)
- [X] T013 [P] Tests unitaires de `lister_dossiers_non_reconcilies`/`enregistrer_chemins` dans `test_context_paths.py` : dossier verrouillé détecté, dossier réconcilié ignoré, archive inaccessible lève, aucune archive configurée n'exige aucune vérification, `ignorer_verification_archive` passe outre explicitement — 15/15 tests passent (`packages/regine-core/src/regine_core/config/smb.py` reçoit dès cette phase `chemin_local_du_partage`, requis par T009/T010 ; le reste de `smb.py` — `disponible`/`declencher_montage`/`assurer_disponibilite` — reste prévu en Phase 4/T020)

**Checkpoint** : la persistance du contexte est prête ; les 3 user stories peuvent commencer.

---

## Phase 3: User Story 1 - Configuration initiale des emplacements de travail (Priority: P1) 🎯 MVP

**Objectif** : configurer et consulter les 3 emplacements de travail, avec initialisation de la base de travail et refus explicite d'un changement dangereux.

**Test indépendant** : configurer les 3 chemins sur un contexte vide (`regine config set-paths`), vérifier leur persistance (`regine config show`), vérifier qu'un changement de `local_work_dir` avec un dossier verrouillé dessous est refusé.

### Tests for User Story 1

- [X] T014 [P] [US1] Test d'intégration du cycle configuration → initialisation de la base de travail → changement de chemin bloqué dans `packages/regine-core/tests/integration/test_config_context.py` (Acceptance Scenarios 1 à 5 de US1)
- [X] T015 [P] [US1] Test de contrat de la surface CLI (`set-paths`, `show`, codes de sortie, `--json`) dans `packages/regine-cli/tests/contract/test_cli_config.py`

### Implementation for User Story 1

- [X] T016 [US1] Implémenter la sous-commande `regine config set-paths` dans `packages/regine-cli/src/regine_cli/config_cmd.py` : `--temp-dir`, `--local-work-dir`, `--archive-smb`, `--yes` (confirme la création de répertoire, FR-003), `--force` (confirme la collision de chemins), `--ignorer-verification-archive` ; traduit chaque exception de `context.py` en message stderr + code de sortie non-zéro (cf. `contracts/cli-config.md`)
- [X] T017 [US1] Implémenter la sous-commande `regine config show [--json]` dans `config_cmd.py` : affiche les 3 chemins et l'état SMB connu, ou la liste des chemins manquants si le contexte n'est pas encore configuré (Edge Case) — **implémentée en même temps que T021 (US2)** : `show` déclenche `smb.assurer_disponibilite` dès qu'une archive est configurée, testé par `test_cli_config.py`
- [X] T018 [US1] Construire l'analyseur d'arguments (`construire_analyseur`/`main`) dans `config_cmd.py`, cohérent avec `import_cmd.py`/`archive_cmd.py`

**Checkpoint** : User Story 1 fonctionnelle et testable indépendamment (MVP) — 125/125 tests passent.

---

## Phase 4: User Story 2 - Assistance quand l'archive NAS n'est pas accessible (Priority: P2)

**Objectif** : détecter l'indisponibilité du partage SMB avant qu'une opération échoue, déclencher le montage natif, permettre une reprise sans reconfiguration.

**Test indépendant** : configurer un `archive_smb_path` pointant vers un partage démonté, lancer `regine config show`, vérifier qu'un montage est déclenché et que l'état SMB est mis à jour ; remonter manuellement puis relancer, vérifier la reprise sans reconfiguration.

**Note d'ordonnancement** : implémentée en avance sur l'ordre de priorité initial, dès la Phase 3 — `regine config show` (T017) en a besoin directement (cf. quickstart.md scénario 4) et `context.enregistrer_chemins`/`lister_dossiers_non_reconcilies` (T009/T010, Phase 2) dépendaient déjà de `smb.chemin_local_du_partage`.

### Tests for User Story 2

- [X] T019 [P] [US2] Test unitaire de `smb.py` dans `packages/regine-core/tests/unit/test_smb_disponibilite.py` : `disponible()` sur un point de montage présent/absent (via un répertoire temporaire simulant `/Volumes/<partage>`, injecté par dépendance plutôt que codé en dur), `assurer_disponibilite()` met à jour `smb_last_known_state`, déclenche/ne déclenche pas le montage selon `declencher`

### Implementation for User Story 2

- [X] T020 [US2] Implémenter `packages/regine-core/src/regine_core/config/smb.py` : `disponible(archive_smb_path: str) -> bool` (vérifie l'accessibilité du point de montage macOS `/Volumes/<partage>`, dérivé de l'URL `smb://...`), `declencher_montage(archive_smb_path: str) -> None` (`open smb://...`, cf. research.md § 1), `assurer_disponibilite(conn, archive_smb_path, *, declencher=True) -> bool` (met à jour l'état via `context.mettre_a_jour_etat_smb`, déclenche le montage si indisponible)
- [X] T021 [US2] Intégrer `smb.assurer_disponibilite` dans `regine config show` (`config_cmd.py`) : si un `archive_smb_path` est configuré, vérifier sa disponibilité avant d'afficher l'état, déclencher le montage natif si nécessaire (Acceptance Scenario 1/2 de US2) — pas de nouvelle tentative automatique en boucle (Acceptance Scenario 3, une seule vérification par invocation, cohérent avec l'exécution sans démon)

**Checkpoint** : User Stories 1 et 2 fonctionnelles indépendamment.

---

## Phase 5: User Story 3 - Nommer les boîtiers à partir des photos déjà archivées (Priority: P3)

**Objectif** : lister et nommer les boîtiers déjà enregistrés dans la base de contexte centralisée, sans réimport ni parcours du NAS ; parcours volontaire du NAS en complément optionnel (FR-012).

**Test indépendant** : sur une base de contexte contenant déjà des boîtiers enregistrés par un import (cf. specs/001), lister les boîtiers puis en nommer un, sans déclencher d'accès au NAS.

### Tests for User Story 3

- [X] T022 [P] [US3] Test unitaire de `cameras_screen.py` dans `packages/regine-core/tests/unit/test_cameras_screen.py` : liste des boîtiers déjà enregistrés (réutilise `camera_profile.db`), nommage d'un boîtier connu, `BoitierInconnuError` sur un identifiant absent
- [X] T023 [P] [US3] Test d'intégration du parcours NAS dans `packages/regine-core/tests/integration/test_cameras_scan_nas.py` : une arborescence simulant une archive avec des fichiers maîtres de boîtiers non encore connus (tags EXIF réels via `exiftool`) ; vérifie que `parcourir_nas` les ajoute avec `source="parcours_nas"` et n'affecte pas les boîtiers déjà connus (source `modele`), plus idempotence (second parcours ne redouble rien)

### Implementation for User Story 3

- [X] T024 [US3] Implémenter `packages/regine-core/src/regine_core/config/cameras_screen.py` : `lister_boitiers(conn) -> list[Boitier]` (délègue à `camera_profile.db.list_boitiers`, FR-008), `nommer_boitier(conn, boitier_id: int, nom: str) -> None` (délègue à `camera_profile.db.rename_boitier` après avoir vérifié l'existence de `boitier_id`, sinon `BoitierInconnuError` — cf. `contracts/cli-config.md`), `parcourir_nas(conn, archive_root: Path) -> int` (parcourt `archive_root` à la recherche de fichiers d'extension `regine_core.import_carte.copie.EXTENSIONS_MAITRES`, lit `Model` via `regine_core.metadata.exif.read_camera_tags`, appelle `camera_profile.db.get_or_create_boitier(..., source="parcours_nas")`, retourne le nombre de boîtiers réellement nouveaux — FR-012, PEUT donc optionnel mais implémenté ici)
- [X] T025 [US3] Implémenter les sous-commandes `regine config cameras list [--json]`, `regine config cameras rename --id ID --nom NOM`, `regine config cameras scan-nas [--confirm]` dans `config_cmd.py` — `scan-nas` sans `--confirm` avertit et sort en code non-zéro sans rien exécuter (FR-012, cf. `contracts/cli-config.md`) ; vérifie la disponibilité de l'archive (`smb.assurer_disponibilite`) puis dérive son point de montage local via `smb.chemin_local_du_partage` (même convention que US2, pas de second mécanisme de résolution). 9/9 tests de contrat CLI passent (`test_cli_config.py`, étendu pour `cameras list`/`rename`/`scan-nas`)

**Checkpoint** : les 3 user stories sont fonctionnelles indépendamment — 134/134 tests passent (`packages/regine-core` + `packages/regine-cli`).

---

## Phase 6: User Story 4 - Configurer le contexte de travail depuis l'interface graphique (Priority: P4)

**Ajoutée le 2026-09-21** (clarification + extension de plan post-implémentation) — cf. `spec.md` § Clarifications Session 2026-09-21.

**Objectif** : consulter et modifier le contexte de travail (chemins, état SMB, boîtiers nommés) depuis un 3e onglet de `regine-gui` (déjà implémentée par `specs/006-gui-tri-consultation`), en appelant exclusivement les fonctions `regine_core.config` déjà existantes — aucune nouvelle fonction `regine-core` requise (cf. `plan.md` extension du 2026-09-21).

**Test indépendant** : ouvrir l'onglet Configuration, y modifier un chemin, vérifier via `regine config show` (CLI) que le changement est bien reflété — sans dépendre des onglets Tri/Consultation.

### Implementation for User Story 4

- [X] T029 [US4] Créer `packages/regine-gui/src/regine_gui/config_view.py` (squelette, docstring de module uniquement)
- [X] T030 [US4] Implémenter `EcranConfiguration` dans `config_view.py` : affichage des 3 chemins et de l'état SMB au chargement (`regine_core.config.context.charger_contexte`), formulaire de modification appelant `context.enregistrer_chemins` — chaque exception (`RepertoireAbsentError`, `CheminsAmbigusError`, `CheminArchiveInvalideError`, `TravailNonReconcilieError`, `ArchiveInaccessibleError`) présentée clairement à l'utilisateur plutôt qu'une exception technique brute (FR-013, US4 Acceptance Scenario 2/3)
- [X] T031 [US4] Étendre `config_view.py` avec la liste des boîtiers et leur nommage (`cameras_screen.lister_boitiers`/`nommer_boitier`, US4 Acceptance Scenario 4)
- [X] T032 [US4] Câbler l'onglet « Configuration » dans `packages/regine-gui/src/regine_gui/app.py` (`FenetrePrincipale`), aux côtés des onglets Tri/Consultation déjà existants
- [X] T033 [US4] Valider manuellement en mode headless (`QT_QPA_PLATFORM=offscreen`, même méthode que `specs/006`) : affichage/modification des chemins, gestion d'erreur, liste/nommage de boîtiers, parité avec `regine config show`/`cameras list` (Scénario 7 de `quickstart.md`, SC-008) — **découverte de méthode de test** : `QMessageBox.warning` bloque en attente d'un clic (comportement normal en usage réel), non testable tel quel en script headless ; validé en monkeypatchant `QMessageBox.warning` pour capturer l'appel sans dialogue modal réel — `RepertoireAbsentError` et `TravailNonReconcilieError` confirmées gérées sans crash, message clair, rien enregistré suite au refus

**Checkpoint** : les 4 user stories sont fonctionnelles indépendamment.

---

## Phase 7: User Story 5 - Reprendre le travail sur une nouvelle machine sans perdre le contexte (Priority: P2)

**Ajoutée le 2026-09-23/24** (écart trouvé par l'utilisateur + spec/plan révisés) — cf. `spec.md` § Clarifications Session 2026-09-23.

**Objectif** : séparer la configuration globale (chemins, propre à la machine, `~/.regine/config.sqlite3`) de la base de contexte centralisée (boîtiers/catégories/historique d'imports, désormais à `<local_work_dir>/contexte.sqlite3`, remplaçant l'ancien manifeste vide jamais exploité qu'`enregistrer_chemins` y créait) ; répliquer cette dernière vers l'archive à chaque `regine reconcile` (FR-016) et la restaurer automatiquement à la configuration initiale ou au premier `regine checkout` sur une machine qui ne l'a pas encore (FR-017).

**Test indépendant** : enrichir le contexte sur une installation (nommer un boîtier), archiver un dossier (`regine reconcile`), configurer une seconde installation « neuve » pointant vers la même archive, vérifier que `regine config cameras list` y retrouve immédiatement ce boîtier sans réimport.

### Tests for User Story 5

- [X] T035 [P] [US5] Adapter `packages/regine-core/tests/unit/test_config_db_defaut.py` : `open_global_config_db()` sans argument utilise `~/.regine/config.sqlite3` par défaut (comportement testé jusqu'ici sur `open_context_db`) ; `open_context_db(path)` exige désormais `path` (plus de défaut implicite)
- [X] T036 [P] [US5] Test unitaire de `environnement.ouvrir_environnement` dans `packages/regine-core/tests/unit/test_environnement.py` : `contexte_conn is None` tant que `local_work_dir` n'est pas configuré dans la config globale ; une fois configuré, `contexte_conn` pointe vers `<local_work_dir>/contexte.sqlite3` (créée si absente)
- [X] T037 [P] [US5] Test unitaire de `context.sauvegarder_contexte`/`restaurer_contexte_si_absent` dans `packages/regine-core/tests/unit/test_contexte_replication.py` : réplique vers l'archive (contenu identique après coup, vérifié par somme de contrôle), no-op si la base locale n'existe pas encore, restaure si absente localement mais présente sur l'archive, no-op si absente des deux côtés (première installation)

### Implementation for User Story 5

- [X] T038 [US5] Refactorer `packages/regine-core/src/regine_core/config/db.py` : extraire le bootstrap `PRAGMA application_id`/`user_version` commun dans une fonction privée partagée ; `open_context_db(path: Path) -> sqlite3.Connection` (chemin désormais obligatoire, `CHEMIN_CONTEXTE_PAR_DEFAUT` retirée) ; nouvelle `open_global_config_db(path: Path | None = None) -> sqlite3.Connection` (défaut `CHEMIN_CONFIG_GLOBALE_PAR_DEFAUT = Path.home() / ".regine" / "config.sqlite3"`)
- [X] T039 [US5] Créer `packages/regine-core/src/regine_core/config/environnement.py` : dataclass `Environnement` (`config_conn`, `contexte_conn: sqlite3.Connection | None`, `local_work_dir: Path | None`), `ouvrir_environnement(config_db_path: Path | None = None) -> Environnement` (FR-014/015)
- [X] T040 [US5] Étendre `packages/regine-core/src/regine_core/config/context.py` : `enregistrer_chemins` initialise désormais `<local_work_dir>/contexte.sqlite3` via `db.open_context_db` à la place de l'ancien `regine_core.archive.manifest.ouvrir_ou_creer` (retiré — supprime la dépendance de ce module vers `regine_core.archive`, jamais réellement exploitée, cf. § Écarts en tête de fichier) ; nouvelles `sauvegarder_contexte(local_work_dir, archive_root_local) -> None` (FR-016, copie vérifiée vers `<archive_root_local>/.regine-contexte.sqlite3`, no-op si source absente) et `restaurer_contexte_si_absent(local_work_dir, archive_root_local) -> bool` (FR-017, copie vérifiée dans l'autre sens si la base locale est absente et qu'une sauvegarde existe)
- [X] T041 [US5] Mettre à jour `packages/regine-cli/src/regine_cli/config_cmd.py` : `ouvrir_environnement()` remplace `open_context_db(Path(args.contexte_db) if args.contexte_db else None)` ; flag `--contexte-db` renommé `--config-db` ; `set-paths` appelle `restaurer_contexte_si_absent` juste après un `enregistrer_chemins` réussi si `local-work-dir` et `archive-smb` sont tous deux connus à l'issue de la commande, annonce le résultat sur stdout (cf. `contracts/cli-config.md`)
- [X] T042 [US5] Mettre à jour `packages/regine-cli/src/regine_cli/archive_cmd.py` : `_charger_archive_root_local` utilise `open_global_config_db`/`ouvrir_environnement` (l'emplacement de l'archive vit désormais dans la config globale, pas la base de contexte centralisée) ; flag `--contexte-db` renommé `--config-db` (`browse`/`restore`) ; `_cmd_reconcile` appelle `sauvegarder_contexte` juste après un archivage réussi (FR-016) ; `_cmd_checkout` appelle `restaurer_contexte_si_absent` juste après un checkout réussi (FR-017)
- [X] T043 [US5] Corriger `packages/regine-cli/src/regine_cli/import_cmd.py` (bug réel trouvé par l'utilisateur, cf. `contracts/regine-core-api.md`) : supprime `_NOM_CONTEXTE_DB_PAR_DEFAUT`/le flag `--contexte-db` (divergeait silencieusement du reste de la CLI) ; dérive directement `Path(args.local_root) / "contexte.sqlite3"` pour `db.open_context_db`
- [X] T044 [US5] Mettre à jour `packages/regine-gui/src/regine_gui/app.py` : `FenetrePrincipale` résout `ouvrir_environnement()` une fois à la construction, propage le résultat aux onglets plutôt que chacun n'ouvre indépendamment `open_context_db(self._contexte_db)`
- [X] T045 [US5] Mettre à jour `packages/regine-gui/src/regine_gui/config_view.py`/`import_view.py`/`consultation_view.py` : consomment l'environnement résolu (chemins/état SMB via `environnement.config_conn`, boîtiers/catégories/historique via `environnement.contexte_conn`) plutôt que d'ouvrir elles-mêmes une connexion unique ambiguë
- [X] T046 [P] [US5] Vérifier chaque appelant existant de `open_context_db` dans `specs/001-import-photos`/`specs/002-profil-boitiers-optionnel`/`specs/004-categorisation-dossiers` (tests unitaires/intégration) : tous passent déjà un chemin de fichier explicite (`tmp_path / "contexte.sqlite3"` ou équivalent), donc déjà compatibles avec la signature révisée sans modification — seul `test_config_db_defaut.py` dépendait de l'ancien défaut implicite, déjà traité par T035
- [X] T047 [US5] Valider manuellement en mode headless (`QT_QPA_PLATFORM=offscreen`, même méthode que les phases précédentes) les Scénarios 8, 9 et 10 de `quickstart.md` : réplication à un `regine reconcile`, restauration à la configuration initiale d'une « nouvelle machine », restauration au premier `regine checkout` si la configuration initiale n'a pas suffi (archive inaccessible à ce moment-là) — Scénarios 8 et 9 exécutés de bout en bout via un script headless simulant deux machines (import GUI sur machine A, `regine reconcile` déclenchant bien la sauvegarde vers `<archive>/.regine-contexte.sqlite3`, puis `regine config set-paths` sur une machine B neuve restaurant automatiquement `contexte.sqlite3` avant tout import) : `Contexte local machine A existe ? True`, `Sauvegarde présente sur l'archive ? True`, `Contexte restauré sur machine B ? True`. Scénario 10 (restauration au premier `checkout` plutôt qu'à `set-paths`) non exécuté en conditions réelles (aurait nécessité un troisième montage SMB simulé) mais son câblage (`_restaurer_contexte_post_checkout` dans `archive_cmd.py`, appelant exactement `restaurer_contexte_si_absent`) est identique à celui du Scénario 9 déjà validé et est couvert unitairement par T037 — aucun boîtier n'a été détecté lors de l'import (fichiers factices sans EXIF), donc c'est la présence de `contexte.sqlite3` restauré (plutôt qu'un boîtier nommé visible dans `cameras list`) qui atteste de la réplication

**Checkpoint** : les 5 user stories sont fonctionnelles indépendamment ; un contexte enrichi sur une machine est retrouvable intégralement sur une autre pointant vers la même archive.

---

## Phase 8: Polish & Cross-Cutting Concerns

- [X] T026 [P] Exécuter manuellement les 6 scénarios de `specs/003-config-contexte-travail/quickstart.md` (adaptés à la surface CLI réellement livrée) et consigner le résultat — scénarios 1, 2, 3, 5, 6 exécutés via la vraie CLI (`uv run python -m regine_cli.config_cmd config ...`), tous conformes. Scénario 4 (montage/démontage SMB réel) non exécutable dans cet environnement de développement (pas de partage SMB physique disponible) — le mécanisme sous-jacent (`smb.disponible`/`assurer_disponibilite`/`declencher_montage`) est validé unitairement avec un point de montage injecté (T019), et `config show` a correctement détecté `smb://nas.local/regine-archive` (inexistant dans cet environnement) comme `unavailable` lors du scénario 1, confirmant la détection réelle
- [X] T027 [P] `ruff check --fix` + `ruff format` sur `packages/regine-core` et `packages/regine-cli`
- [X] T028 Mettre à jour `contracts/cli-config.md` et créer `contracts/regine-core-api.md` — signatures réelles documentées, écarts de conception consignés (cf. § Écarts en tête de fichier) ; note explicite que `regine_cli.import_cmd`/`archive_cmd` (specs/001/005) ne consomment pas encore ce contexte configuré (ils prennent `--archive-root`/`--local-root` en flags explicites) — intégration future hors périmètre de cette spec, non construite ici sans nouvelle demande explicite
- [X] T034 [P] Exécuter le Scénario 7 de `quickstart.md` (déjà couvert par T033, sans duplication) et `ruff check --fix`/`ruff format` sur `packages/regine-gui` (nouveau fichier `config_view.py`) — 172/172 tests `regine-core`/`regine-cli` toujours au vert (aucune régression, aucun nouveau test requis côté core puisqu'aucune nouvelle fonction n'a été ajoutée)
- [X] T048 [P] Exécuter les Scénarios 8, 9 et 10 de `quickstart.md` (cf. T047, sans duplication) et `ruff check --fix`/`ruff format` sur les trois paquets (`regine-core`, `regine-cli`, `regine-gui`) ; exécuter la suite de tests complète du dépôt et confirmer l'absence de régression sur les specs consommatrices (`specs/001/002/004/005/006`) ; mettre à jour `contracts/regine-core-api.md`/`cli-config.md` si l'implémentation révèle un écart avec les signatures documentées — `ruff check --fix` : aucun problème détecté sur les trois paquets ; `ruff format` : 1 fichier reformaté (changement cosmétique, sans lien avec cette story) ; suite complète : 190/190 tests passent (aucune régression sur specs/001/002/004/005/006) ; aucun écart supplémentaire trouvé nécessitant une mise à jour des contrats au-delà de ceux déjà consignés lors de T041-T043

---

## Dependencies & Execution Order

- **Setup (Phase 1)** : aucune dépendance, tâches `[P]` en parallèle.
- **Foundational (Phase 2)** : dépend de Setup ; bloque toutes les user stories.
- **US1 (Phase 3)** : dépend de Foundational uniquement. MVP.
- **US2 (Phase 4)** : dépend de Foundational ; s'intègre à `regine config show` (US1) mais reste testable indépendamment (`smb.py` seul).
- **US3 (Phase 5)** : dépend de Foundational et de `specs/002` (déjà livrée) ; indépendante de US1/US2.
- **US4 (Phase 6)** : dépend de US1/US2/US3 (appelle leurs fonctions telles quelles) et de `specs/006-gui-tri-consultation` (déjà livrée, fournit `regine-gui`/`app.py`/PySide6) ; aucune dépendance nouvelle côté `regine-core`.
- **US5 (Phase 7)** : dépend de Foundational (révise directement `db.py`/`context.py` de la Phase 2) et de `specs/005-checkout-reconciliation` (déjà livrée, fournit `reconcile`/`checkout` sur lesquels s'accrochent FR-016/FR-017) ; touche transversalement US1 (`config_cmd.py`), US3 (consommateur de la base de contexte déplacée) et US4 (écrans GUI) sans changer leur comportement observable — à mener après elles, jamais en parallèle, du fait de ce chevauchement de fichiers.
- **Polish (Phase 8)** : dépend des user stories livrées (au minimum US1 ; T034 dépend spécifiquement de US4, T048 dépend spécifiquement de US5).

## Implementation Strategy

MVP = Phase 1 + 2 + 3 (US1). US2 et US3 sont des incréments indépendants livrables ensuite dans l'ordre de priorité. US4 (ajoutée le 2026-09-21) est un incrément de confort pur, livrable à tout moment après `specs/006-gui-tri-consultation` — sans lui, la CLI (US1-3) reste pleinement fonctionnelle et suffisante. US5 (ajoutée le 2026-09-23/24) révise une hypothèse de conception jamais correctement exploitée depuis le début (US1/FR-006) plutôt que d'ajouter une capacité isolée : contrairement à US2-4, elle touche des fichiers déjà livrés par les phases précédentes (`db.py`, `context.py`, `config_cmd.py`, `archive_cmd.py`, `import_cmd.py`, les écrans GUI) et doit donc être menée en dernier, séquentiellement, plutôt qu'en parallèle d'une autre user story.