Desktop app (Tauri + React) that monitors Tuleap trackers and dispatches AI agents to analyze and fix tickets automatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
14 KiB
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<String> -- 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<FilterGroup>
}
AgentConfig {
analyst_command: String -- ex: "claude", "codex"
analyst_args: Vec<String>
developer_command: String
developer_args: Vec<String>
}
FilterGroup { -- groupes combines en ET
conditions: Vec<Filter> -- conditions dans un groupe combinees en OU
}
Filter {
field: String -- ex: "status", "assigned_to", "priority"
operator: FilterOp -- Equals, NotEquals, In, NotIn
value: Vec<String> -- 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 URLupdate_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
- Un scheduler tourne en arriere-plan (
tokio::intervalpar tracker surveille) - A chaque tick, appel API Tuleap
GET /api/trackers/{id}/artifactsavec les credentials - Les resultats sont filtres localement selon les
FilterGroupconfigures (l'API Tuleap ne supporte pas nativement les combinaisons ET/OU complexes) - Chaque ticket est compare a la table
processed_tickets-- si absent, c'est un nouveau ticket - Les nouveaux tickets sont inseres en base avec le statut
Pendinget 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<String>
developer_report: Option<String>
worktree_path: Option<String>
branch_name: Option<String>
detected_at: DateTime
processed_at: Option<DateTime>
}
TuleapCredentials {
id: UUID
tuleap_url: String
username: String
password: String -- chiffre au repos
}
Points importants
- Credentials chiffres en SQLite (cle derivee via
keyringou secret local) - Le poller emet un evenement Tauri
new-tickets-detectedvers 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 UIlist_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
- L'orchestrateur consomme la file (FIFO), un ticket a la fois
- Etape 1 -- Analyste : lance la commande CLI configuree avec un prompt structure contenant les infos du ticket et du contexte code
- Le rapport markdown de l'analyste est stocke en base
- Etape 2 -- Developpeur : lance la commande CLI avec le rapport de l'analyste + acces au worktree
- Le rapport + diff sont collectes
- Le ticket passe en
Done, le Notifier est appele
Invocation CLI
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
Erroravec 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 stdoutticket-processing-done { ticket_id }ticket-processing-error { ticket_id, error }
Commandes Tauri
get_queue_status()-- tickets en attente + ticket en coursretry_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
# 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<DateTime>
merged_into: Option<String> -- branche cible
}
Commandes Tauri
create_worktree(ticket_id)-- appele automatiquement par l'orchestrateurlist_worktrees(project_id)apply_fix_to_branch(worktree_id, target_branch)-- cherry-pick dans la branche de l'userdelete_worktree(worktree_id)get_worktree_diff(worktree_id)-- diff par rapport a la branche de baselist_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
DoneouError - 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<UUID> -- 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/apipour les IPC commands et eventsreact-diff-viewerpour l'affichage des diffsreact-markdownpour 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
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'))
);