# Orchai -- Design Spec App desktop d'equipe qui surveille des trackers Tuleap et lance des agents IA pour analyser et corriger les tickets automatiquement. ## Decisions cles - **Type** : App desktop (Tauri 2 + React) - **Stack** : Rust backend (Tauri), React + TypeScript frontend, SQLite embarque - **Utilisateurs** : equipe de devs, pas de distribution large - **Zero setup** : tout embarque, pas de serveur externe ## Architecture globale ``` +--------------------------------------------------+ | React Frontend | | +----------+ +----------+ +-------------------+ | | | Projects | | Tickets | | Agent Results | | | | Manager | | Monitor | | (report + diff) | | | +----------+ +----------+ +-------------------+ | +--------------------------------------------------+ | Tauri IPC (commands) | +--------------------------------------------------+ | Rust Backend | | +-----------+ +-----------+ +----------------+ | | | Project | | Tuleap | | Agent | | | | Manager | | Poller | | Orchestrator | | | +-----------+ +-----------+ +----------------+ | | +-----------+ +-----------+ | | | Worktree | | Notifier | | | | Manager | | | | | +-----------+ +-----------+ | | +--------------------------------------------+ | | | SQLite (rusqlite) | | | +--------------------------------------------+ | +--------------------------------------------------+ ``` 5 modules backend, 1 SPA React frontend. Communication via Tauri IPC commands et events. --- ## Module 1 : Project Manager Gere les projets et leurs trackers surveilles. ### Modele de donnees ``` Project { id: UUID name: String path: String -- chemin local du repo cloned_from: Option -- URL d'origine si clone par l'app base_branch: String -- ex: "stable", "main" created_at: DateTime } WatchedTracker { id: UUID project_id: UUID -- FK -> Project tracker_id: i32 -- ID du tracker Tuleap tracker_label: String -- nom affiche polling_interval: i32 -- en minutes, defaut 10 agent_config: AgentConfig filters: Vec } AgentConfig { analyst_command: String -- ex: "claude", "codex" analyst_args: Vec developer_command: String developer_args: Vec } FilterGroup { -- groupes combines en ET conditions: Vec -- conditions dans un groupe combinees en OU } Filter { field: String -- ex: "status", "assigned_to", "priority" operator: FilterOp -- Equals, NotEquals, In, NotIn value: Vec -- ex: ["Nouveau", "A traiter"] } ``` Exemple : `(Statut Nouveau OU Statut A traiter) ET Assigne a Team Maintenance` - FilterGroup 1 : `[status IN ("Nouveau", "A traiter")]` - FilterGroup 2 : `[assigned_to IN ("Team Maintenance")]` ### Commandes Tauri - `create_project(name, path_or_url, base_branch)` -- cree le projet, clone si URL - `update_project(id, ...)` / `delete_project(id)` - `list_projects()` / `get_project(id)` - `add_tracker(project_id, tracker_id, filters, agent_config)` - `update_tracker(...)` / `remove_tracker(id)` --- ## Module 2 : Tuleap Poller Interroge l'API Tuleap a intervalle regulier, applique les filtres, detecte les nouveaux tickets. ### Fonctionnement 1. Un scheduler tourne en arriere-plan (`tokio::interval` par tracker surveille) 2. A chaque tick, appel API Tuleap `GET /api/trackers/{id}/artifacts` avec les credentials 3. Les resultats sont filtres localement selon les `FilterGroup` configures (l'API Tuleap ne supporte pas nativement les combinaisons ET/OU complexes) 4. Chaque ticket est compare a la table `processed_tickets` -- si absent, c'est un nouveau ticket 5. Les nouveaux tickets sont inseres en base avec le statut `Pending` et ajoutes a la file d'attente ### Modele de donnees ``` ProcessedTicket { id: UUID tracker_id: UUID -- FK -> WatchedTracker artifact_id: i32 -- ID du ticket Tuleap artifact_title: String artifact_data: String -- JSON brut du ticket status: TicketStatus -- Pending, Analyzing, Developing, Done, Error analyst_report: Option developer_report: Option worktree_path: Option branch_name: Option detected_at: DateTime processed_at: Option } TuleapCredentials { id: UUID tuleap_url: String username: String password: String -- chiffre au repos } ``` ### Points importants - Credentials chiffres en SQLite (cle derivee via `keyring` ou secret local) - Le poller emet un evenement Tauri `new-tickets-detected` vers le frontend - Si l'API Tuleap est injoignable, log de l'erreur et retry au prochain tick ### Commandes Tauri - `set_tuleap_credentials(url, username, password)` - `test_tuleap_connection()` - `get_tracker_fields(tracker_id)` -- recupere les champs/valeurs pour les filtres UI - `list_processed_tickets(project_id, filters)` - `manual_poll(tracker_id)` --- ## Module 3 : Agent Orchestrator Traite les tickets de la file d'attente sequentiellement via un pipeline a 2 agents. ### Pipeline 1. L'orchestrateur consomme la file (FIFO), un ticket a la fois 2. **Etape 1 -- Analyste** : lance la commande CLI configuree avec un prompt structure contenant les infos du ticket et du contexte code 3. Le rapport markdown de l'analyste est stocke en base 4. **Etape 2 -- Developpeur** : lance la commande CLI avec le rapport de l'analyste + acces au worktree 5. Le rapport + diff sont collectes 6. Le ticket passe en `Done`, le Notifier est appele ### Invocation CLI ```rust Command::new("claude") .arg("--print") .arg("--prompt") .arg(analyst_prompt) .current_dir(&project_path) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() ``` ### Prompts **Analyste :** ``` Tu es un analyste technique. Voici un ticket Tuleap a analyser. ## Ticket - ID: {artifact_id} - Titre: {title} - Description: {description} - Priorite: {priority} - Champs additionnels: {fields} ## Contexte - Projet: {project_name} - Repo: {project_path} ## Ta mission 1. Analyse le ticket et identifie les fichiers/fonctions concernes 2. Explique techniquement le probleme 3. Evalue si une correction de code est necessaire 4. Produis un rapport structure en markdown ``` **Developpeur** : recoit en plus le rapport de l'analyste et travaille dans le worktree. ### Gestion des erreurs - Timeout configurable par agent (defaut : 10 minutes) - Si un agent plante ou timeout, le ticket passe en `Error` avec le message d'erreur - L'utilisateur peut relancer manuellement un ticket en erreur ### Evenements Tauri - `ticket-processing-started { ticket_id, step: "analyst"|"developer" }` - `ticket-processing-progress { ticket_id, output_chunk }` -- streaming stdout - `ticket-processing-done { ticket_id }` - `ticket-processing-error { ticket_id, error }` ### Commandes Tauri - `get_queue_status()` -- tickets en attente + ticket en cours - `retry_ticket(ticket_id)` - `cancel_ticket(ticket_id)` - `get_ticket_result(ticket_id)` -- rapport + diff --- ## Module 4 : Worktree Manager Cree et gere les git worktrees temporaires pour les agents developpeurs. ### Cycle de vie ``` Ticket detecte -> Analyse -> Fix necessaire -> Worktree cree (branche locale orchai/{artifact_id}) basee sur {base_branch} -> Agent developpeur travaille dedans -> Resultat pret, worktree en attente de review -> User review le diff dans l'app -> User choisit "Appliquer le fix dans ma branche" -> selectionne sa branche locale -> L'app cherry-pick les commits du worktree dans la branche de l'user -> App propose suppression du worktree -> User confirme -> worktree + branche orchai/ supprimes ``` **Le code dans le worktree n'est jamais push.** La branche `orchai/{artifact_id}` est purement locale. C'est l'utilisateur qui push sa propre branche apres avoir applique le fix. ### Stockage ``` /chemin/du/projet/ -- repo principal /chemin/du/projet/.orchai/ -- dossier gere par l'app worktrees/ orchai-1234/ -- worktree pour le ticket #1234 orchai-5678/ ``` ### Operations git ```bash # Creation git worktree add .orchai/worktrees/orchai-{artifact_id} -b orchai/{artifact_id} {base_branch} # Application du fix dans la branche de l'user git checkout {user_branch} git cherry-pick {commits_from_orchai_branch} # Nettoyage git worktree remove .orchai/worktrees/orchai-{artifact_id} git branch -d orchai/{artifact_id} ``` ### Modele de donnees ``` Worktree { id: UUID ticket_id: UUID -- FK -> ProcessedTicket path: String -- chemin absolu du worktree branch_name: String -- orchai/{artifact_id} status: WorktreeStatus -- Active, Merged, Deleted created_at: DateTime merged_at: Option merged_into: Option -- branche cible } ``` ### Commandes Tauri - `create_worktree(ticket_id)` -- appele automatiquement par l'orchestrateur - `list_worktrees(project_id)` - `apply_fix_to_branch(worktree_id, target_branch)` -- cherry-pick dans la branche de l'user - `delete_worktree(worktree_id)` - `get_worktree_diff(worktree_id)` -- diff par rapport a la branche de base - `list_local_branches(project_id)` -- pour le selecteur de branche dans l'UI ### Conflits de merge - Si le cherry-pick echoue (conflit), l'app notifie l'utilisateur avec les fichiers en conflit - Le cherry-pick est annule (`git cherry-pick --abort`), le worktree reste en place - L'utilisateur peut resoudre manuellement ou demander a un agent de tenter la resolution --- ## Module 5 : Notifier Informe l'utilisateur quand un ticket a ete traite. ### Notification systeme (OS) - `notify-rust` (Linux) / API Tauri native - Declenchee quand un ticket passe en `Done` ou `Error` - Contenu : titre du ticket + resume court - Clic sur la notification -> ouvre l'app sur le detail du ticket ### Notification in-app - Centre de notifications (icone avec badge compteur) - Liste chronologique : nouveau ticket detecte, analyse terminee, fix propose, erreur - Chaque notification cliquable -> navigue vers le ticket - Marquage lu/non-lu ### Modele de donnees ``` Notification { id: UUID project_id: UUID ticket_id: Option -- FK -> ProcessedTicket type: NotificationType -- NewTicket, AnalysisDone, FixReady, Error title: String message: String read: bool created_at: DateTime } ``` ### Commandes Tauri - `list_notifications(project_id, unread_only)` - `mark_notification_read(id)` / `mark_all_read(project_id)` ### Evenement Tauri - `new-notification { notification }` -- le frontend met a jour le badge en temps reel --- ## Frontend React ### Stack - React + TypeScript - Tailwind CSS - `@tauri-apps/api` pour les IPC commands et events - `react-diff-viewer` pour l'affichage des diffs - `react-markdown` pour le rendu des rapports ### Navigation Sidebar avec la liste des projets + header avec le centre de notifications. ### Vues **1. Dashboard projet** - Resume : nom, chemin, branche de base, nombre de trackers - Liste des trackers surveilles avec statut (actif, derniere verification, tickets traites) - File d'attente en cours (ticket en traitement + en attente) - Acces rapide aux derniers resultats **2. Configuration tracker** - Selection du tracker Tuleap (liste via API) - Constructeur de filtres visuel : ajout de conditions, groupement ET/OU - Configuration des agents (analyste / developpeur) : commande CLI + arguments - Intervalle de polling **3. Liste des tickets traites** - Tableau : ID ticket, titre, statut, date - Filtrage par statut - Clic -> detail **4. Detail ticket** - Infos du ticket Tuleap (titre, description, priorite, assignation) - Rapport analyste (markdown rendu) - Rapport developpeur (markdown rendu) - Vue diff du fix (style GitHub, cote a cote) - Actions : "Appliquer le fix dans ma branche" (selecteur de branche), "Relancer", "Supprimer le worktree" **5. Parametres** - Credentials Tuleap (URL, login, password) + bouton "Tester la connexion" - Gestion des projets (ajouter, modifier, supprimer) --- ## Schema SQLite ```sql CREATE TABLE projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, path TEXT NOT NULL, cloned_from TEXT, base_branch TEXT NOT NULL DEFAULT 'main', created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE tuleap_credentials ( id TEXT PRIMARY KEY, tuleap_url TEXT NOT NULL, username TEXT NOT NULL, password_encrypted TEXT NOT NULL ); CREATE TABLE watched_trackers ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, tracker_id INTEGER NOT NULL, tracker_label TEXT NOT NULL, polling_interval INTEGER NOT NULL DEFAULT 10, agent_config_json TEXT NOT NULL, filters_json TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE processed_tickets ( id TEXT PRIMARY KEY, tracker_id TEXT NOT NULL REFERENCES watched_trackers(id) ON DELETE CASCADE, artifact_id INTEGER NOT NULL, artifact_title TEXT NOT NULL, artifact_data TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'Pending', analyst_report TEXT, developer_report TEXT, detected_at TEXT NOT NULL DEFAULT (datetime('now')), processed_at TEXT ); CREATE TABLE worktrees ( id TEXT PRIMARY KEY, ticket_id TEXT NOT NULL REFERENCES processed_tickets(id), path TEXT NOT NULL, branch_name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'Active', created_at TEXT NOT NULL DEFAULT (datetime('now')), merged_at TEXT, merged_into TEXT ); CREATE TABLE notifications ( id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, ticket_id TEXT REFERENCES processed_tickets(id), type TEXT NOT NULL, title TEXT NOT NULL, message TEXT NOT NULL, read INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); ```