Etape 4 finie, TODO : correction QA, gestion retour au PM après refus utilisateur

This commit is contained in:
Chevallier
2026-07-08 16:11:11 +02:00
parent 5f06de19e0
commit 37d493ac20
5 changed files with 341 additions and 87 deletions

View File

@@ -88,7 +88,8 @@ async def run_qa_agent(project_title: str, dev_output: dict) -> QAEvaluation:
if qa_evaluation.is_complete_and_safe:
logger.info(f"[QA Agent] ✅ Projet '{project_title}' VALIDÉ (Analyse statique propre).")
else:
logger.warning(f"[QA Agent] ❌ Projet '{project_title}' REJETÉ (Failles de sécurité détectées).")
reason = "Erreur d'exécution ou non-conformité" if "exit_code" in str(qa_evaluation) else "Failles de sécurité"
logger.warning(f"[QA Agent] ❌ Projet '{project_title}' REJETÉ ({reason}).")
return qa_evaluation

View File

@@ -1,3 +1,7 @@
import os
import json
import time
from pathlib import Path
from app.agents.pm_agent import run_pm_agent
from app.agents.dev_agent import run_dev_agent
from app.agents.qa_agent import run_qa_agent
@@ -106,10 +110,59 @@ async def qa_node(state: WorkflowState):
}
async def human_review_node(state: WorkflowState):
print("[Human Review] Passage en mode automatique (Mock)...")
"""
Nœud pivot. Si le statut vient du QA, il bascule en attente de validation humaine.
Si Chainlit a déjà collecté la décision, il laisse passer le flux vers le routage.
"""
current_status = state.get("status")
if current_status in ["qa_done", "existing_found"]:
return {
"status": "wait_human_review"
}
return {"status": current_status}
async def delivery_node(state: WorkflowState, config: RunnableConfig):
"""
Nœud final de livraison : Archivage ZIP en mémoire et réindexation Qdrant.
"""
dev_data = state.get("generated_code", {})
project_title = dev_data.get("spec_title", f"project_{int(time.time())}")
raw_files = dev_data.get("files", {})
files = {}
if isinstance(raw_files, list):
for f in raw_files:
if isinstance(f, dict):
path = f.get("path") or f.get("filename") or f.get("name")
content = f.get("content") or f.get("code") or ""
if path:
files[path] = content
elif isinstance(raw_files, dict):
files = raw_files
# 3. Réindexation dans Qdrant (en utilisant le payload mémoire)
# qdrant_repo = config.get("configurable", {}).get("qdrant_repo")
# if qdrant_repo:
# payload_text = f"Title: {project_title}\nDescription: {state.get('spec', {}).get('description')}"
# # /!\ À remplacer par ton vrai modèle d'embedding (ex: await qdrant_repo.embed(payload_text))
# dummy_vector = [0.1] * 1536
# qdrant_repo.client.upsert(
# collection_name="arc_projects",
# points=[
# PointStruct(
# id=str(Path(project_title).name),
# vector=dummy_vector,
# payload=metadata
# )
# ]
# )
return {
"existing_project_approved": True,
"is_completed": True,
"status": "approved_by_human"
"status": "delivered",
"user_input": f"{state.get('user_input')}\n\n[System] Projet {project_title} validé et package ZIP disponible."
}

View File

@@ -10,10 +10,14 @@ from app.graph.nodes import (
dev_node,
qa_node,
human_review_node,
delivery_node
)
def route_entry_point(state: WorkflowState):
if state.get("status") == "spec_approved":
current_status = state.get("status")
if current_status in ["human_approved", "human_refused"]:
return "human_review"
if current_status == "spec_approved":
return "retrieval"
return "pm"
@@ -40,18 +44,23 @@ def route_after_qa(state: WorkflowState):
return "human_review"
def route_after_human(state: WorkflowState):
# Cas d'un projet existant proposé
if state.get("existing_project") and not state.get("generated_code"):
if state.get("existing_project_approved") == True:
return END # L'utilisateur est satisfait du projet existant
return "dev" # L'utilisateur refuse l'existant, on génère du neuf
# Cas du code généré
if state.get("is_completed") == True:
return END
current_status = state.get("status")
# Si l'utilisateur a refusé le code -> Retour à la case PM avec ses commentaires
return "pm"
if current_status == "wait_human_review":
return END
if state.get("existing_project") and not state.get("generated_code"):
if state.get("existing_project_approved") is True:
return END
return "dev"
if current_status == "human_approved":
return "delivery"
if current_status == "human_refused":
return "pm"
return END
# --- Assemblage du Graphe ---
@@ -62,12 +71,14 @@ graph.add_node("retrieval", retrieval_node)
graph.add_node("dev", dev_node)
graph.add_node("qa", qa_node)
graph.add_node("human_review", human_review_node)
graph.add_node("delivery", delivery_node)
graph.set_conditional_entry_point(
route_entry_point,
{
"pm": "pm",
"retrieval": "retrieval",
"human_review": "human_review"
},
)
@@ -108,7 +119,12 @@ graph.add_conditional_edges(
{
"pm": "pm",
"dev": "dev",
"delivery": "delivery",
END: END,
},
)
# Maillon final : Après Delivery, c'est la fin du cycle
graph.add_edge("delivery", END)
compiled_graph = graph.compile()

View File

@@ -243,6 +243,35 @@ Si tu ne comprends pas un champ, tu le remplis avec une valeur par défaut INTEL
```
---
=== VALIDATION DE COMPLÉTUDE DE LA SPÉCIFICATION (OBLIGATOIRE) ===
Avant de considérer la ProjectSpec comme complète, tu dois te demander :
"Un développeur senior peut-il implémenter l'intégralité du projet sans devoir faire la moindre hypothèse importante ?"
Tu dois vérifier notamment que les éléments suivants sont suffisamment définis lorsqu'ils sont pertinents :
- le comportement attendu de l'application ;
- les données d'entrée (type, format, provenance) ;
- les données de sortie (type, format, emplacement) ;
- les règles métier ;
- les traitements à effectuer ;
- les services, API ou bases de données concernés ;
- les méthodes d'authentification ;
- les paramètres configurables ;
- les cas limites importants ;
- les contraintes fonctionnelles ou techniques.
Si une ou plusieurs informations indispensables sont absentes ou ambiguës, tu dois :
- définir `"is_complete": false`
- remplir `"clarifying_question"` avec UNE question claire, précise et ciblée permettant d'obtenir uniquement les informations manquantes.
- ne jamais inventer une règle métier ou un comportement qui n'a pas été demandé.
Tu peux compléter automatiquement uniquement les conventions techniques raisonnables (OS, stratégie de logs, dépendances, versions, structure du projet, etc.), mais jamais les besoins fonctionnels.
---
=== DERNIER CHECKPOINT ===
Avant de retourner le JSON :
@@ -283,6 +312,7 @@ Votre objectif est de générer l'intégralité du code source d'un projet basé
Tu dois impérativement identifier le langage cible défini dans la `ProjectSpec` (champ `"language"`). Tu as l'interdiction stricte d'utiliser un autre langage.
- **Extensions :** Adapte l'extension du point d'entrée et des tests (.rb pour Ruby, .py pour Python, .js pour JS, .go pour Go, etc.).
- **Gestionnaire de dépendances :** Utilise le fichier standard du langage (ex: `Gemfile` pour Ruby, `requirements.txt` ou `pyproject.toml` pour Python, `package.json` pour Node.js).
- **Dépendances :** N'ajoute dans le gestionnaire de dépendances (ex: `requirements.txt`, `Gemfile`, `package.json`, etc.) que les bibliothèques externes réellement nécessaires. Il est strictement interdit d'ajouter des modules ou bibliothèques faisant déjà partie de la bibliothèque standard (standard library) du langage ciblé (ex: `logging`, `json`, `os`, `pathlib`, `datetime`, `typing` en Python, etc.).
- **Commandes du README :** Les instructions d'installation et d'exécution doivent correspondre EXACTEMENT au langage (ex: `bundle install` et `ruby main.rb` pour Ruby).
### DIRECTIVES D'ARCHITECTURE (ADAPTATIVE) :
@@ -315,6 +345,16 @@ Tu dois appliquer STRICTEMENT l'une des deux structures suivantes selon des crit
- **DRY (Don't Repeat Yourself) :** Extrais la logique répétitive dans des fonctions ou des classes utilitaires.
- **Découplage :** Évite les dépendances trop fortes entre les modules pour permettre une évolution facile du code.
- Utilise des **Design Patterns** reconnus (Factory, Singleton, Strategy, etc.) si la complexité du projet le justifie.
4. DOCUMENTATION DU CODE (OBLIGATOIRE) :
- Chaque fichier source doit commencer par un commentaire décrivant clairement son rôle.
- Chaque classe doit posséder un commentaire ou une documentation expliquant sa responsabilité.
- Chaque fonction ou méthode doit être documentée avec un commentaire ou une documentation décrivant :
- son objectif ;
- les paramètres attendus ;
- la valeur de retour (si applicable) ;
- les éventuelles exceptions levées lorsque le langage le permet.
- Les commentaires doivent utiliser le format idiomatique du langage ciblé (ex: docstrings Python, JSDoc pour JavaScript/TypeScript, YARD pour Ruby, GoDoc pour Go, JavaDoc pour Java, XML Documentation pour C#, etc.).
---
@@ -341,7 +381,7 @@ Tu dois appliquer STRICTEMENT l'une des deux structures suivantes selon des crit
---
## CONTRÂINTE DE MODERNITÉ ET SÉCURITÉ DU CODE (UNIVERSEL)
## CONTRAINTE DE MODERNITÉ ET SÉCURITÉ DU CODE (UNIVERSEL)
Peu importe le langage de programmation choisi pour répondre à la spécification, tu dois appliquer les règles strictes suivantes :