feat: add Claude Code orchestration setup with MCP and subagents
Add comprehensive Claude Code configuration for Rust/Go projects, including: - CLAUDE.md with delegation heuristics and guardrails - Subagents: qwen3-worker (LM Studio), code-reviewer, task-planner - Skills for delegation rules and Rust/Go conventions - Hooks: post-write-lint, pre-bash-guard, subagent-stop-log - MCP server (stdio) to interface with LM Studio - README and technical report documenting the architecture
This commit is contained in:
26
.claude/CLAUDE.md
Normal file
26
.claude/CLAUDE.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Projet CrazyClaude
|
||||
|
||||
## Stack
|
||||
- Rust (edition 2024), Go 1.24
|
||||
- Tests : cargo test, go test
|
||||
- Lint : clippy (Rust), golangci-lint (Go)
|
||||
|
||||
## Heuristiques de délégation
|
||||
|
||||
Délègue à `qwen3-worker` si la tâche est **atomique** et satisfait TOUS les critères :
|
||||
- ≤ 3 fichiers concernés
|
||||
- Pas de modification d'API publique
|
||||
- Pas de dépendance externe non encore importée
|
||||
- Tâches typiques : génération de tests unitaires, reformatage, documentation inline,
|
||||
conversion de types simples, scaffolding de stubs
|
||||
|
||||
Traite toi-même si :
|
||||
- Refactoring cross-module
|
||||
- Conception d'architecture
|
||||
- Debugging avec contexte multi-fichiers
|
||||
- Modification de traits/interfaces publics
|
||||
|
||||
## Garde-fous absolus
|
||||
- Ne jamais passer `rm -rf` sans confirmation explicite
|
||||
- Ne jamais committer sans que les tests passent
|
||||
- Toujours vérifier `git diff` avant un commit
|
||||
21
.claude/agents/code-reviewer.md
Normal file
21
.claude/agents/code-reviewer.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: >
|
||||
Révise du code Rust ou Go pour détecter bugs, régressions, problèmes de style.
|
||||
Utilise après génération par qwen3-worker ou avant un commit.
|
||||
tools: [Read, Grep, Glob, Bash]
|
||||
model: claude-haiku-4-5-20251001
|
||||
---
|
||||
|
||||
Tu es un reviewer expérimenté en Rust et Go.
|
||||
|
||||
Pour chaque fichier fourni :
|
||||
1. Vérifie la cohérence des types et la gestion d'erreurs.
|
||||
2. Identifie les patterns non idiomatiques.
|
||||
3. Signale les tests manquants pour les chemins critiques.
|
||||
|
||||
Format de sortie :
|
||||
```
|
||||
VERDICT: approved|changes_requested
|
||||
ISSUES: liste numérotée (vide si approved)
|
||||
```
|
||||
30
.claude/agents/qwen3-worker.md
Normal file
30
.claude/agents/qwen3-worker.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: qwen3-worker
|
||||
description: >
|
||||
Délègue à Qwen3-Coder via LM Studio les tâches atomiques sur 1-3 fichiers :
|
||||
génération de tests unitaires, documentation inline, scaffolding de stubs,
|
||||
reformatage de code. N'utilise PAS pour du refactoring cross-module ou de
|
||||
la conception d'architecture.
|
||||
tools: [Read, Write, Bash]
|
||||
model: inherit
|
||||
---
|
||||
|
||||
Tu es un assistant de codage spécialisé exécutant des tâches courtes et précises.
|
||||
|
||||
## Comportement
|
||||
1. Lis les fichiers nécessaires avec l'outil Read (ne reçois pas le contenu en entrée).
|
||||
2. Effectue la transformation demandée.
|
||||
3. Écris le résultat avec Write.
|
||||
4. Exécute le linter approprié (clippy pour Rust, golangci-lint pour Go) et corrige
|
||||
les erreurs éventuelles (max 2 tentatives).
|
||||
5. Retourne un résumé : fichiers modifiés, changements effectués, résultat du lint.
|
||||
|
||||
## Format de sortie
|
||||
```
|
||||
STATUS: success|partial|failure
|
||||
FILES_MODIFIED: liste des fichiers
|
||||
SUMMARY: description des changements
|
||||
LINT: passed|failed (+ détail si failed)
|
||||
```
|
||||
|
||||
Ne génère pas de fonctions non demandées. Ne modifie pas les signatures publiques.
|
||||
25
.claude/agents/task-planner.md
Normal file
25
.claude/agents/task-planner.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: task-planner
|
||||
description: >
|
||||
Planifie des tâches complexes impliquant plusieurs modules ou une refonte architecturale.
|
||||
Utilise pour décomposer une demande en étapes atomiques avant exécution.
|
||||
tools: [Read, Grep, Glob]
|
||||
model: claude-opus-4-6
|
||||
---
|
||||
|
||||
Tu es un architecte logiciel expérimenté en Rust et Go.
|
||||
|
||||
Pour la demande reçue :
|
||||
1. Analyse le périmètre et les dépendances (lis les fichiers pertinents).
|
||||
2. Décompose en étapes atomiques ordonnées.
|
||||
3. Identifie lesquelles sont délégables à `qwen3-worker` (≤ 3 fichiers, sans changement d'API publique).
|
||||
4. Estime les risques et propose des points de contrôle.
|
||||
|
||||
Format de sortie :
|
||||
```
|
||||
PLAN:
|
||||
1. [étape] — délégable: oui|non — raison
|
||||
2. ...
|
||||
RISQUES: liste
|
||||
POINTS_DE_CONTRÔLE: liste
|
||||
```
|
||||
9
.claude/hooks/post-write-lint.sh
Executable file
9
.claude/hooks/post-write-lint.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# Lint automatique après écriture de fichier
|
||||
FILE=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('file_path',''))")
|
||||
|
||||
if [[ "$FILE" == *.rs ]]; then
|
||||
cargo clippy --quiet 2>&1 | tail -20
|
||||
elif [[ "$FILE" == *.go ]]; then
|
||||
golangci-lint run "$FILE" 2>&1 | tail -20
|
||||
fi
|
||||
8
.claude/hooks/pre-bash-guard.sh
Executable file
8
.claude/hooks/pre-bash-guard.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bloque les commandes destructives sans confirmation
|
||||
CMD=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('command',''))")
|
||||
|
||||
if echo "$CMD" | grep -qE 'rm -rf|DROP TABLE|git push --force'; then
|
||||
echo "BLOQUÉ : commande dangereuse détectée. Confirmez explicitement."
|
||||
exit 2 # exit 2 = deny dans Claude Code
|
||||
fi
|
||||
6
.claude/hooks/subagent-stop-log.sh
Executable file
6
.claude/hooks/subagent-stop-log.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Observabilité : log chaque fin de subagent
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
PAYLOAD=$(cat)
|
||||
AGENT=$(echo "$PAYLOAD" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('agent_name','unknown'))" 2>/dev/null)
|
||||
echo "$TIMESTAMP agent=$AGENT" >> .claude/logs/subagent-decisions.log
|
||||
182
.claude/mcp/qwen3-mcp/agent_lm.py
Normal file
182
.claude/mcp/qwen3-mcp/agent_lm.py
Normal file
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env -S .claude/venv/bin/python3
|
||||
"""
|
||||
Agent loop local (~120 lignes) pour interagir avec LM Studio via l'API OpenAI-compatible.
|
||||
Usage : python3 .claude/mcp/qwen3-mcp/agent_lm.py --task "..." --files file1.py file2.go
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions"
|
||||
LMSTUDIO_MODELS_URL = "http://localhost:1248/v1/models"
|
||||
DEFAULT_MODEL = "qwen/qwen3-coder-next"
|
||||
|
||||
SYSTEM_PROMPT = """Tu es un assistant de codage spécialisé. Tu effectues des tâches atomiques sur des fichiers source.
|
||||
Règles :
|
||||
- Ne modifie que ce qui est demandé
|
||||
- Ne change pas les signatures publiques (traits Rust, interfaces Go exportées)
|
||||
- Retourne uniquement le code, sans explication ni markdown
|
||||
- Si tu ne peux pas accomplir la tâche, réponds avec: ERROR: <raison>
|
||||
"""
|
||||
|
||||
|
||||
def list_models() -> list[str]:
|
||||
"""Retourne la liste des modèles disponibles dans LM Studio."""
|
||||
try:
|
||||
resp = requests.get(LMSTUDIO_MODELS_URL, timeout=10)
|
||||
resp.raise_for_status()
|
||||
return [m["id"] for m in resp.json().get("data", [])]
|
||||
except requests.RequestException as e:
|
||||
print(f"Impossible de contacter LM Studio : {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
|
||||
def read_files(paths: list[str]) -> str:
|
||||
"""Lit les fichiers et les formate pour le prompt."""
|
||||
context = ""
|
||||
for path in paths:
|
||||
try:
|
||||
content = Path(path).read_text(encoding="utf-8")
|
||||
context += f"=== {path} ===\n{content}\n\n"
|
||||
except OSError as e:
|
||||
context += f"=== {path} === ERREUR: {e}\n\n"
|
||||
return context
|
||||
|
||||
|
||||
def call_lmstudio(task: str, file_context: str, model: str) -> str:
|
||||
"""Envoie la tâche à LM Studio et retourne la réponse."""
|
||||
user_message = f"TÂCHE: {task}\n\nFICHIERS:\n{file_context}" if file_context else f"TÂCHE: {task}"
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": [
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{"role": "user", "content": user_message},
|
||||
],
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 4096,
|
||||
}
|
||||
|
||||
try:
|
||||
resp = requests.post(LMSTUDIO_URL, json=payload, timeout=120)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["choices"][0]["message"]["content"]
|
||||
except requests.Timeout:
|
||||
return "ERROR: LM Studio timeout (>120s)"
|
||||
except requests.RequestException as e:
|
||||
return f"ERROR: {e}"
|
||||
except (KeyError, IndexError) as e:
|
||||
return f"ERROR: réponse inattendue de LM Studio : {e}"
|
||||
|
||||
|
||||
def write_file(path: str, content: str) -> bool:
|
||||
"""Écrit le contenu dans un fichier, crée les répertoires si nécessaire."""
|
||||
try:
|
||||
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(path).write_text(content, encoding="utf-8")
|
||||
return True
|
||||
except OSError as e:
|
||||
print(f"Erreur écriture {path}: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def run_lint(file_path: str) -> tuple[bool, str]:
|
||||
"""Lance le linter approprié selon l'extension."""
|
||||
import subprocess
|
||||
|
||||
if file_path.endswith(".rs"):
|
||||
result = subprocess.run(
|
||||
["cargo", "clippy", "--quiet"],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
elif file_path.endswith(".go"):
|
||||
result = subprocess.run(
|
||||
["golangci-lint", "run", file_path],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
else:
|
||||
return True, "no linter for this file type"
|
||||
|
||||
passed = result.returncode == 0
|
||||
output = (result.stdout + result.stderr).strip()
|
||||
return passed, output
|
||||
|
||||
|
||||
def agent_loop(task: str, input_files: list[str], output_file: str | None, model: str, max_retries: int = 2) -> dict:
|
||||
"""Boucle principale : génère, écrit, lint, corrige (max_retries fois)."""
|
||||
file_context = read_files(input_files) if input_files else ""
|
||||
result = {"status": "failure", "files_modified": [], "summary": "", "lint": "skipped"}
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
response = call_lmstudio(task, file_context, model)
|
||||
|
||||
if response.startswith("ERROR:"):
|
||||
result["summary"] = response
|
||||
break
|
||||
|
||||
target = output_file or (input_files[0] if input_files else None)
|
||||
if not target:
|
||||
result["status"] = "success"
|
||||
result["summary"] = response
|
||||
result["lint"] = "skipped (no output file)"
|
||||
break
|
||||
|
||||
if write_file(target, response):
|
||||
result["files_modified"] = [target]
|
||||
|
||||
lint_ok, lint_output = run_lint(target)
|
||||
result["lint"] = "passed" if lint_ok else f"failed: {lint_output[:500]}"
|
||||
|
||||
if lint_ok:
|
||||
result["status"] = "success"
|
||||
result["summary"] = f"Attempt {attempt + 1}: task completed successfully"
|
||||
break
|
||||
elif attempt < max_retries:
|
||||
task = f"{task}\n\nCORRECTION REQUISE (tentative {attempt + 1}):\n{lint_output}"
|
||||
file_context = read_files([target])
|
||||
else:
|
||||
result["status"] = "partial"
|
||||
result["summary"] = f"Lint failed after {max_retries + 1} attempts"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Agent loop local pour LM Studio")
|
||||
parser.add_argument("--task", required=True, help="Description de la tâche")
|
||||
parser.add_argument("--files", nargs="*", default=[], help="Fichiers source à lire")
|
||||
parser.add_argument("--output", help="Fichier de sortie (défaut: premier fichier input)")
|
||||
parser.add_argument("--model", default=DEFAULT_MODEL, help="Modèle LM Studio")
|
||||
parser.add_argument("--list-models", action="store_true", help="Liste les modèles disponibles")
|
||||
parser.add_argument("--json", action="store_true", dest="json_output", help="Sortie JSON")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.list_models:
|
||||
models = list_models()
|
||||
if models:
|
||||
print("Modèles disponibles :")
|
||||
for m in models:
|
||||
print(f" - {m}")
|
||||
else:
|
||||
print("Aucun modèle trouvé ou LM Studio inaccessible.")
|
||||
return
|
||||
|
||||
result = agent_loop(args.task, args.files, args.output, args.model)
|
||||
|
||||
if args.json_output:
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(f"STATUS: {result['status']}")
|
||||
print(f"FILES_MODIFIED: {', '.join(result['files_modified']) or 'none'}")
|
||||
print(f"SUMMARY: {result['summary']}")
|
||||
print(f"LINT: {result['lint']}")
|
||||
|
||||
sys.exit(0 if result["status"] == "success" else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
.claude/mcp/qwen3-mcp/requirements.txt
Normal file
1
.claude/mcp/qwen3-mcp/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
requests>=2.31.0
|
||||
104
.claude/mcp/qwen3-mcp/server.py
Normal file
104
.claude/mcp/qwen3-mcp/server.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env -S .claude/venv/bin/python3
|
||||
"""
|
||||
MCP server stdio exposant un outil `qwen3_task`.
|
||||
Appelé par Claude Code via : claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
|
||||
LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions"
|
||||
QWEN3_MODEL = "qwen/qwen3-coder-next"
|
||||
|
||||
TOOLS = [{
|
||||
"name": "qwen3_task",
|
||||
"description": "Délègue une tâche de codage atomique à Qwen3-Coder via LM Studio.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {"type": "string", "description": "Description précise de la tâche"},
|
||||
"files": {"type": "array", "items": {"type": "string"}, "description": "Chemins des fichiers concernés"}
|
||||
},
|
||||
"required": ["task"]
|
||||
}
|
||||
}]
|
||||
|
||||
|
||||
def send(obj: dict):
|
||||
print(json.dumps(obj), flush=True)
|
||||
|
||||
|
||||
def call_qwen3(task: str, files: list[str]) -> str:
|
||||
context = ""
|
||||
for path in files:
|
||||
try:
|
||||
with open(path) as f:
|
||||
context += f"--- {path} ---\n{f.read()}\n\n"
|
||||
except OSError as e:
|
||||
context += f"--- {path} --- ERREUR: {e}\n\n"
|
||||
|
||||
prompt = f"""Tu es un assistant de codage. Effectue la tâche suivante de manière précise.
|
||||
|
||||
TÂCHE: {task}
|
||||
|
||||
FICHIERS:
|
||||
{context}
|
||||
|
||||
Retourne uniquement le code modifié ou généré, sans explication.
|
||||
"""
|
||||
resp = requests.post(
|
||||
LMSTUDIO_URL,
|
||||
json={
|
||||
"model": QWEN3_MODEL,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 4096,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["choices"][0]["message"]["content"]
|
||||
|
||||
|
||||
def handle(req: dict):
|
||||
method = req.get("method", "")
|
||||
req_id = req.get("id")
|
||||
|
||||
if method == "initialize":
|
||||
send({
|
||||
"jsonrpc": "2.0",
|
||||
"id": req_id,
|
||||
"result": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"capabilities": {"tools": {}},
|
||||
"serverInfo": {"name": "qwen3-mcp", "version": "1.0.0"},
|
||||
},
|
||||
})
|
||||
elif method == "notifications/initialized":
|
||||
pass # notification, pas de réponse
|
||||
elif method == "tools/list":
|
||||
send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}})
|
||||
elif method == "tools/call":
|
||||
params = req.get("params", {})
|
||||
name = params.get("name", "")
|
||||
args = params.get("arguments", {})
|
||||
if name == "qwen3_task":
|
||||
try:
|
||||
text = call_qwen3(args["task"], args.get("files", []))
|
||||
send({"jsonrpc": "2.0", "id": req_id, "result": {"content": [{"type": "text", "text": text}]}})
|
||||
except Exception as e:
|
||||
send({"jsonrpc": "2.0", "id": req_id, "result": {"content": [{"type": "text", "text": f"ERROR: {e}"}], "isError": True}})
|
||||
else:
|
||||
send({"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Outil inconnu : {name}"}})
|
||||
elif req_id is not None:
|
||||
send({"jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Méthode inconnue : {method}"}})
|
||||
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
handle(json.loads(line))
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}), file=sys.stderr, flush=True)
|
||||
58
.claude/settings.json
Normal file
58
.claude/settings.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/post-write-lint.sh",
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubagentStop": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/subagent-stop-log.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/pre-bash-guard.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cargo:*)",
|
||||
"Bash(go:*)",
|
||||
"Bash(git diff*)",
|
||||
"Bash(git add*)",
|
||||
"Bash(git commit*)",
|
||||
"Bash(curl*)",
|
||||
"Bash(chmod*)",
|
||||
"Read(**/*.rs)",
|
||||
"Read(**/*.go)",
|
||||
"Write(**/*.rs)",
|
||||
"Write(**/*.go)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(rm -rf*)",
|
||||
"Bash(curl * | bash*)",
|
||||
"Bash(git push --force*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
32
.claude/skills/delegation-rules.md
Normal file
32
.claude/skills/delegation-rules.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
name: delegation-rules
|
||||
description: Règles pour décider quand et comment déléguer à qwen3-worker ou qwen3 MCP
|
||||
---
|
||||
|
||||
# Règles de Délégation à Qwen3
|
||||
|
||||
## Critères de délégation (TOUS requis)
|
||||
- ≤ 3 fichiers source concernés
|
||||
- Tâche atomique, sans dépendance à résoudre
|
||||
- Pas de modification de signature publique (trait, interface exportée)
|
||||
- Contexte de fichier estimé < 8 000 tokens
|
||||
|
||||
## Tâches typiques éligibles
|
||||
- Génération de tests unitaires pour une fonction donnée
|
||||
- Ajout de documentation (doc comments Rust, godoc Go)
|
||||
- Scaffolding de stubs à partir d'une interface
|
||||
- Conversion de types internes simples
|
||||
- Reformatage / réorganisation d'imports
|
||||
|
||||
## Procédure
|
||||
1. Évalue les critères ci-dessus.
|
||||
2. Si éligible → utilise le subagent `qwen3-worker` en précisant la tâche et les fichiers.
|
||||
3. Attends le résumé de sortie (STATUS / FILES_MODIFIED / LINT).
|
||||
4. Si STATUS=failure → prends en charge toi-même.
|
||||
5. Si STATUS=partial → corrige les points restants directement.
|
||||
|
||||
## Ne jamais déléguer
|
||||
- Refactoring impliquant > 3 fichiers
|
||||
- Changement d'architecture ou de design pattern
|
||||
- Debugging avec stacktrace à analyser
|
||||
- Tâches nécessitant une recherche web ou du contexte externe
|
||||
25
.claude/skills/rust-go-conventions.md
Normal file
25
.claude/skills/rust-go-conventions.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
name: rust-go-conventions
|
||||
description: Conventions de style et patterns idiomatiques Rust (edition 2024) et Go 1.24 pour ce projet
|
||||
---
|
||||
|
||||
# Conventions Rust / Go
|
||||
|
||||
## Rust (edition 2024)
|
||||
- Utilise `thiserror` pour les types d'erreur publics, `anyhow` dans les binaires.
|
||||
- Préfère `?` à `unwrap()` sauf dans les tests.
|
||||
- Les `pub` structs doivent avoir des doc comments (`///`).
|
||||
- Nomme les types de retour complexes avec des type aliases.
|
||||
- `clippy::pedantic` activé — corrige tous les lints avant commit.
|
||||
|
||||
## Go 1.24
|
||||
- Gestion d'erreurs : toujours `if err != nil { return ..., err }`, jamais `_`.
|
||||
- Interfaces en minuscule si non exportées.
|
||||
- Docstring obligatoire pour toute fonction exportée (commence par le nom de la fonction).
|
||||
- Utilise `errors.Is` / `errors.As` pour comparer les erreurs.
|
||||
- `golangci-lint` avec preset `default` + `gocritic`.
|
||||
|
||||
## Tests
|
||||
- Rust : tests unitaires dans le même fichier (`#[cfg(test)]`), intégration dans `tests/`.
|
||||
- Go : fichiers `_test.go`, table-driven tests avec `t.Run`.
|
||||
- Couverture cible : 80 % sur les chemins critiques.
|
||||
153
README.md
Normal file
153
README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# CrazyClaude
|
||||
|
||||
Configuration Claude Code pour orchestrer des tâches de développement Rust/Go en déléguant les tâches atomiques à **Qwen3-Coder** via LM Studio.
|
||||
|
||||
Ce dépôt est conçu pour être intégré comme sous-répertoire `.claude/` dans n'importe quel projet. Il fournit agents, hooks, skills et un serveur MCP prêts à l'emploi.
|
||||
|
||||
Pour le détail de l'architecture, voir [`rapport-orchestration-claude-code.md`](./rapport-orchestration-claude-code.md).
|
||||
|
||||
---
|
||||
|
||||
## Prérequis
|
||||
|
||||
- [Claude Code](https://claude.ai/code) installé (`claude` disponible dans le PATH)
|
||||
- [LM Studio](https://lmstudio.ai/) avec le modèle `qwen/qwen3-coder-next` chargé, écoutant sur `http://localhost:1248`
|
||||
- Python 3.11+ (pour le serveur MCP)
|
||||
- Outils selon votre stack : `cargo` + `clippy` (Rust), `golangci-lint` (Go)
|
||||
|
||||
---
|
||||
|
||||
## Installation dans un projet existant
|
||||
|
||||
### 1. Cloner ce dépôt
|
||||
|
||||
Avec **Jujutsu** :
|
||||
|
||||
```sh
|
||||
# Depuis la racine de votre projet
|
||||
jj git clone <url-du-depot> .claude-crazy
|
||||
```
|
||||
|
||||
Puis déplacez ou liez le contenu :
|
||||
|
||||
```sh
|
||||
# Copier le répertoire .claude dans votre projet
|
||||
cp -r .claude-crazy/.claude ./.claude
|
||||
|
||||
# Ou, si vous préférez garder une référence au dépôt source :
|
||||
# utilisez jj workspace add pour ajouter un workspace dédié
|
||||
```
|
||||
|
||||
> Si vous utilisez git en sous-jacent avec jj (`jj git init --colocate`),
|
||||
> vous pouvez aussi ajouter ce dépôt comme subtree :
|
||||
>
|
||||
> ```sh
|
||||
> git subtree add --prefix .claude <url-du-depot> main --squash
|
||||
> ```
|
||||
|
||||
### 2. Créer le virtualenv Python
|
||||
|
||||
```sh
|
||||
cd .claude
|
||||
python3 -m venv venv
|
||||
venv/bin/pip install -r mcp/qwen3-mcp/requirements.txt
|
||||
```
|
||||
|
||||
### 3. Enregistrer le serveur MCP
|
||||
|
||||
```sh
|
||||
claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py
|
||||
```
|
||||
|
||||
Vérifier que la connexion est établie :
|
||||
|
||||
```sh
|
||||
claude mcp list
|
||||
# qwen3: ... - ✓ Connected
|
||||
```
|
||||
|
||||
### 4. Adapter CLAUDE.md à votre projet
|
||||
|
||||
Éditez `.claude/CLAUDE.md` : mettez à jour la section `## Stack` et les heuristiques de délégation selon vos besoins.
|
||||
|
||||
### 5. Rendre les hooks exécutables
|
||||
|
||||
```sh
|
||||
chmod +x .claude/hooks/*.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
.claude/
|
||||
├── CLAUDE.md # contexte projet + heuristiques de délégation
|
||||
├── settings.json # hooks et permissions
|
||||
├── agents/
|
||||
│ ├── qwen3-worker.md # délégation vers LM Studio (Qwen3)
|
||||
│ ├── code-reviewer.md # révision code (Claude Haiku)
|
||||
│ └── task-planner.md # planification complexe (Claude Opus)
|
||||
├── skills/
|
||||
│ ├── delegation-rules.md # quand déléguer à Qwen3
|
||||
│ └── rust-go-conventions.md # conventions Rust/Go
|
||||
├── hooks/
|
||||
│ ├── post-write-lint.sh # lint automatique après écriture
|
||||
│ ├── subagent-stop-log.sh # log des décisions de délégation
|
||||
│ └── pre-bash-guard.sh # blocage des commandes dangereuses
|
||||
├── mcp/
|
||||
│ └── qwen3-mcp/
|
||||
│ ├── server.py # serveur MCP stdio (interface LM Studio)
|
||||
│ ├── agent_lm.py # agent loop CLI autonome
|
||||
│ └── requirements.txt
|
||||
├── logs/ # produits par les hooks (ignorés par jj/git)
|
||||
└── venv/ # virtualenv Python (ignoré par jj/git)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration LM Studio
|
||||
|
||||
Le serveur MCP se connecte sur `http://localhost:1248` avec le modèle `qwen/qwen3-coder-next`.
|
||||
|
||||
Pour modifier ces valeurs, éditez `.claude/mcp/qwen3-mcp/server.py` :
|
||||
|
||||
```python
|
||||
LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions"
|
||||
QWEN3_MODEL = "qwen/qwen3-coder-next"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fichiers à ignorer (jj / git)
|
||||
|
||||
Ajoutez ces entrées dans votre fichier d'ignore :
|
||||
|
||||
```
|
||||
.claude/venv/
|
||||
.claude/logs/
|
||||
```
|
||||
|
||||
Avec Jujutsu :
|
||||
|
||||
```sh
|
||||
echo ".claude/venv/" >> .gitignore
|
||||
echo ".claude/logs/" >> .gitignore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mise à jour
|
||||
|
||||
Avec Jujutsu (workflow colocalisé git) :
|
||||
|
||||
```sh
|
||||
git subtree pull --prefix .claude <url-du-depot> main --squash
|
||||
```
|
||||
|
||||
Ou si vous avez cloné séparément, tirez les changements puis recopiez :
|
||||
|
||||
```sh
|
||||
cd .claude-crazy && jj git fetch && jj new main
|
||||
cp -r .claude/* ../.claude/
|
||||
```
|
||||
507
rapport-orchestration-claude-code.md
Normal file
507
rapport-orchestration-claude-code.md
Normal file
@@ -0,0 +1,507 @@
|
||||
# Rapport Technique : Orchestration Robuste avec Claude Code et Délégation à un LLM Local
|
||||
|
||||
**Auteur** : Eric Coissac
|
||||
**Date** : 26 mars 2026
|
||||
**Contexte** : Ce document décrit une architecture robuste exploitant les mécanismes natifs de **Claude Code** (subagents, hooks, skills, MCP) pour déléguer des tâches à faible complexité à **Qwen3-Coder** (via LM Studio), tout en maintenant Claude Code comme orchestrateur central.
|
||||
|
||||
---
|
||||
|
||||
## 1. Pourquoi cette architecture — et ce qu'elle n'est *pas*
|
||||
|
||||
Le document original décrivait un système où Claude Code appelait un script Python externe (`delegate_to_qwen.py`) pour piloter Qwen3. C'est un anti-pattern : cela court-circuite les mécanismes natifs de Claude Code et réintroduit de la complexité là où l'écosystème propose déjà des solutions mieux intégrées.
|
||||
|
||||
En mars 2026, Claude Code dispose de cinq systèmes fondamentaux :
|
||||
|
||||
| Système | Rôle |
|
||||
|---------|------|
|
||||
| **CLAUDE.md** | Contexte permanent du projet (règles, conventions, heuristiques) |
|
||||
| **Skills** | Instructions chargées à la demande selon la pertinence |
|
||||
| **Subagents** | Agents spécialisés avec fenêtre de contexte isolée, outils restreints, modèle configurable |
|
||||
| **Hooks** | Scripts shell ou prompts LLM déclenchés sur des événements du cycle de vie |
|
||||
| **MCP servers** | Extensions vers des outils et services externes via protocole standardisé |
|
||||
|
||||
La délégation à Qwen3 s'intègre naturellement via un **subagent pointant vers LM Studio** ou via un **MCP server stdio minimal**. Le script Python devient optionnel — il reste utile comme wrapper si LM Studio n'expose pas d'interface MCP, mais il ne doit pas être l'orchestrateur.
|
||||
|
||||
---
|
||||
|
||||
## 2. Architecture cible
|
||||
|
||||
```
|
||||
Utilisateur
|
||||
│
|
||||
▼
|
||||
Claude Code (orchestrateur)
|
||||
│ lit CLAUDE.md au démarrage
|
||||
│ charge les skills pertinents à la demande
|
||||
│
|
||||
├─── Subagent : qwen3-worker ──────► LM Studio (localhost:1234)
|
||||
│ contexte isolé modèle Qwen3-Coder
|
||||
│ outils : Read, Write, Bash
|
||||
│
|
||||
├─── Subagent : code-reviewer ──────► Claude Haiku (coût réduit)
|
||||
│ validation syntaxe/style
|
||||
│
|
||||
├─── Hooks ──────────────────────────► scripts shell
|
||||
│ PostToolUse : lint auto
|
||||
│ SubagentStop : log décision
|
||||
│ PreToolUse : garde-fous
|
||||
│
|
||||
└─── MCP servers (optionnel)
|
||||
qwen3-mcp (stdio) : wrapper LM Studio
|
||||
```
|
||||
|
||||
**Principe clé** : Claude Code ne lit pas les fichiers sources pour les passer à Qwen3. C'est Qwen3 (via son subagent ou son agent loop) qui lit les fichiers dont il a besoin — le contenu ne transite pas par le contexte de Claude Code.
|
||||
|
||||
---
|
||||
|
||||
## 3. Structure du projet
|
||||
|
||||
```
|
||||
.claude/
|
||||
├── CLAUDE.md # contexte projet + heuristiques de délégation
|
||||
├── settings.json # hooks et permissions
|
||||
├── agents/
|
||||
│ ├── qwen3-worker.md # subagent → LM Studio
|
||||
│ ├── code-reviewer.md # subagent → Haiku (validation)
|
||||
│ └── task-planner.md # subagent → Opus (planification complexe)
|
||||
├── skills/
|
||||
│ ├── delegation-rules.md # quand déléguer à Qwen3
|
||||
│ └── rust-go-conventions.md # conventions spécifiques au projet
|
||||
└── hooks/
|
||||
├── post-write-lint.sh # lint automatique après écriture
|
||||
├── subagent-stop-log.sh # observabilité
|
||||
└── pre-bash-guard.sh # sécurité
|
||||
mcp/
|
||||
└── qwen3-mcp/
|
||||
├── server.py # MCP server stdio (optionnel)
|
||||
└── agent_lm.py # agent loop local (~120 lignes)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. CLAUDE.md — contexte et heuristiques
|
||||
|
||||
```markdown
|
||||
# Projet [nom]
|
||||
|
||||
## Stack
|
||||
- Rust (edition 2024), Go 1.24
|
||||
- Tests : cargo test, go test
|
||||
- Lint : clippy (Rust), golangci-lint (Go)
|
||||
|
||||
## Heuristiques de délégation
|
||||
|
||||
Délègue à `qwen3-worker` si la tâche est **atomique** et satisfait TOUS les critères :
|
||||
- ≤ 3 fichiers concernés
|
||||
- Pas de modification d'API publique
|
||||
- Pas de dépendance externe non encore importée
|
||||
- Tâches typiques : génération de tests unitaires, reformatage, documentation inline,
|
||||
conversion de types simples, scaffolding de stubs
|
||||
|
||||
Traite toi-même si :
|
||||
- Refactoring cross-module
|
||||
- Conception d'architecture
|
||||
- Debugging avec contexte multi-fichiers
|
||||
- Modification de traits/interfaces publics
|
||||
|
||||
## Garde-fous absolus
|
||||
- Ne jamais passer `rm -rf` sans confirmation explicite
|
||||
- Ne jamais committer sans que les tests passent
|
||||
- Toujours vérifier `git diff` avant un commit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Subagent `qwen3-worker`
|
||||
|
||||
Fichier : `.claude/agents/qwen3-worker.md`
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: qwen3-worker
|
||||
description: >
|
||||
Délègue à Qwen3-Coder via LM Studio les tâches atomiques sur 1-3 fichiers :
|
||||
génération de tests unitaires, documentation inline, scaffolding de stubs,
|
||||
reformatage de code. N'utilise PAS pour du refactoring cross-module ou de
|
||||
la conception d'architecture.
|
||||
tools: [Read, Write, Bash]
|
||||
model: inherit
|
||||
---
|
||||
|
||||
Tu es un assistant de codage spécialisé exécutant des tâches courtes et précises.
|
||||
|
||||
## Comportement
|
||||
1. Lis les fichiers nécessaires avec l'outil Read (ne reçois pas le contenu en entrée).
|
||||
2. Effectue la transformation demandée.
|
||||
3. Écris le résultat avec Write.
|
||||
4. Exécute le linter approprié (clippy pour Rust, golangci-lint pour Go) et corrige
|
||||
les erreurs éventuelles (max 2 tentatives).
|
||||
5. Retourne un résumé : fichiers modifiés, changements effectués, résultat du lint.
|
||||
|
||||
## Format de sortie
|
||||
```
|
||||
STATUS: success|partial|failure
|
||||
FILES_MODIFIED: liste des fichiers
|
||||
SUMMARY: description des changements
|
||||
LINT: passed|failed (+ détail si failed)
|
||||
```
|
||||
|
||||
Ne génère pas de fonctions non demandées. Ne modifie pas les signatures publiques.
|
||||
```
|
||||
|
||||
> **Note importante** : le champ `model: inherit` signifie que ce subagent utilise le modèle courant de la session. Pour pointer vers LM Studio, il faut soit configurer `ANTHROPIC_BASE_URL` pour ce subagent, soit utiliser le MCP server décrit en section 7.
|
||||
|
||||
---
|
||||
|
||||
## 6. Hooks
|
||||
|
||||
### `settings.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/post-write-lint.sh",
|
||||
"async": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SubagentStop": [
|
||||
{
|
||||
"matcher": ".*",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/subagent-stop-log.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash .claude/hooks/pre-bash-guard.sh"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `post-write-lint.sh`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Lint automatique après écriture de fichier
|
||||
FILE=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('file_path',''))")
|
||||
|
||||
if [[ "$FILE" == *.rs ]]; then
|
||||
cargo clippy --quiet 2>&1 | tail -20
|
||||
elif [[ "$FILE" == *.go ]]; then
|
||||
golangci-lint run "$FILE" 2>&1 | tail -20
|
||||
fi
|
||||
```
|
||||
|
||||
### `subagent-stop-log.sh`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Observabilité : log chaque fin de subagent
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
PAYLOAD=$(cat)
|
||||
AGENT=$(echo "$PAYLOAD" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('agent_name','unknown'))" 2>/dev/null)
|
||||
echo "$TIMESTAMP agent=$AGENT" >> .claude/logs/subagent-decisions.log
|
||||
```
|
||||
|
||||
### `pre-bash-guard.sh`
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Bloque les commandes destructives sans confirmation
|
||||
CMD=$(echo "$CLAUDE_TOOL_INPUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('command',''))")
|
||||
|
||||
if echo "$CMD" | grep -qE 'rm -rf|DROP TABLE|git push --force'; then
|
||||
echo "BLOQUÉ : commande dangereuse détectée. Confirmez explicitement."
|
||||
exit 2 # exit 2 = deny dans Claude Code
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. MCP Server (optionnel — si LM Studio n'est pas accessible comme modèle)
|
||||
|
||||
Si l'objectif est de router un subagent vers LM Studio plutôt que vers l'API Anthropic, le moyen le plus simple est un MCP server stdio minimal.
|
||||
|
||||
### `mcp/qwen3-mcp/server.py`
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
MCP server stdio minimaliste exposant un outil `qwen3_task`.
|
||||
Appelé par Claude Code via : claude mcp add --transport stdio qwen3 -- python3 mcp/qwen3-mcp/server.py
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
|
||||
LMSTUDIO_URL = "http://localhost:1234/v1/chat/completions"
|
||||
QWEN3_MODEL = "qwen3-coder" # nom du modèle dans LM Studio
|
||||
|
||||
|
||||
def call_qwen3(task: str, files: list[str]) -> str:
|
||||
context = ""
|
||||
for path in files:
|
||||
try:
|
||||
with open(path) as f:
|
||||
context += f"--- {path} ---\n{f.read()}\n\n"
|
||||
except OSError as e:
|
||||
context += f"--- {path} --- ERREUR: {e}\n\n"
|
||||
|
||||
prompt = f"""Tu es un assistant de codage. Effectue la tâche suivante de manière précise.
|
||||
|
||||
TÂCHE: {task}
|
||||
|
||||
FICHIERS:
|
||||
{context}
|
||||
|
||||
Retourne uniquement le code modifié ou généré, sans explication.
|
||||
"""
|
||||
resp = requests.post(
|
||||
LMSTUDIO_URL,
|
||||
json={
|
||||
"model": QWEN3_MODEL,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"temperature": 0.2,
|
||||
"max_tokens": 4096,
|
||||
},
|
||||
timeout=120,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.json()["choices"][0]["message"]["content"]
|
||||
|
||||
|
||||
def handle_tool_call(tool_name: str, args: dict) -> str:
|
||||
if tool_name == "qwen3_task":
|
||||
return call_qwen3(args["task"], args.get("files", []))
|
||||
return f"Outil inconnu : {tool_name}"
|
||||
|
||||
|
||||
# Boucle MCP stdio (protocole JSON-RPC simplifié)
|
||||
for line in sys.stdin:
|
||||
try:
|
||||
req = json.loads(line)
|
||||
if req.get("method") == "tools/call":
|
||||
result = handle_tool_call(
|
||||
req["params"]["name"], req["params"].get("arguments", {})
|
||||
)
|
||||
print(json.dumps({"id": req["id"], "result": {"content": [{"type": "text", "text": result}]}}))
|
||||
elif req.get("method") == "tools/list":
|
||||
print(json.dumps({
|
||||
"id": req["id"],
|
||||
"result": {"tools": [{
|
||||
"name": "qwen3_task",
|
||||
"description": "Délègue une tâche de codage atomique à Qwen3-Coder via LM Studio.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"task": {"type": "string", "description": "Description précise de la tâche"},
|
||||
"files": {"type": "array", "items": {"type": "string"}, "description": "Chemins des fichiers concernés"}
|
||||
},
|
||||
"required": ["task"]
|
||||
}
|
||||
}]}
|
||||
}))
|
||||
elif req.get("method") == "initialize":
|
||||
print(json.dumps({"id": req["id"], "result": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}}}))
|
||||
except Exception as e:
|
||||
print(json.dumps({"error": str(e)}), file=sys.stderr)
|
||||
sys.stdout.flush()
|
||||
```
|
||||
|
||||
**Installation** :
|
||||
```bash
|
||||
claude mcp add --transport stdio qwen3 -- python3 mcp/qwen3-mcp/server.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Skill de délégation
|
||||
|
||||
Fichier : `.claude/skills/delegation-rules.md`
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: delegation-rules
|
||||
description: Règles pour décider quand et comment déléguer à qwen3-worker ou qwen3 MCP
|
||||
---
|
||||
|
||||
# Règles de Délégation à Qwen3
|
||||
|
||||
## Critères de délégation (TOUS requis)
|
||||
- ≤ 3 fichiers source concernés
|
||||
- Tâche atomique, sans dépendance à résoudre
|
||||
- Pas de modification de signature publique (trait, interface exportée)
|
||||
- Contexte de fichier estimé < 8 000 tokens
|
||||
|
||||
## Tâches typiques éligibles
|
||||
- Génération de tests unitaires pour une fonction donnée
|
||||
- Ajout de documentation (doc comments Rust, godoc Go)
|
||||
- Scaffolding de stubs à partir d'une interface
|
||||
- Conversion de types internes simples
|
||||
- Reformatage / réorganisation d'imports
|
||||
|
||||
## Procédure
|
||||
1. Évalue les critères ci-dessus.
|
||||
2. Si éligible → utilise le subagent `qwen3-worker` en précisant la tâche et les fichiers.
|
||||
3. Attends le résumé de sortie (STATUS / FILES_MODIFIED / LINT).
|
||||
4. Si STATUS=failure → prends en charge toi-même.
|
||||
5. Si STATUS=partial → corrige les points restants directement.
|
||||
|
||||
## Ne jamais déléguer
|
||||
- Refactoring impliquant > 3 fichiers
|
||||
- Changement d'architecture ou de design pattern
|
||||
- Debugging avec stacktrace à analyser
|
||||
- Tâches nécessitant une recherche web ou du contexte externe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Pipeline de validation native
|
||||
|
||||
Claude Code valide via les hooks et les subagents dédiés — pas via du code de validation embarqué dans le script de délégation.
|
||||
|
||||
| Étape | Mécanisme | Déclencheur |
|
||||
|-------|-----------|-------------|
|
||||
| Lint syntaxique | `post-write-lint.sh` (hook) | PostToolUse/Write automatique |
|
||||
| Validation de style | subagent `code-reviewer` | explicitement demandé |
|
||||
| Tests | `Bash: cargo test / go test` | dans le subagent ou directement |
|
||||
| Contrôle des diffs | `Bash: git diff --stat` | avant tout commit |
|
||||
|
||||
### Subagent `code-reviewer`
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: code-reviewer
|
||||
description: >
|
||||
Révise du code Rust ou Go pour détecter bugs, régressions, problèmes de style.
|
||||
Utilise après génération par qwen3-worker ou avant un commit.
|
||||
tools: [Read, Grep, Glob, Bash]
|
||||
model: claude-haiku-4-5-20251001
|
||||
---
|
||||
|
||||
Tu es un reviewer expérimenté en Rust et Go.
|
||||
|
||||
Pour chaque fichier fourni :
|
||||
1. Vérifie la cohérence des types et la gestion d'erreurs.
|
||||
2. Identifie les patterns non idiomatiques.
|
||||
3. Signale les tests manquants pour les chemins critiques.
|
||||
|
||||
Format de sortie :
|
||||
```
|
||||
VERDICT: approved|changes_requested
|
||||
ISSUES: liste numérotée (vide si approved)
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Boucle de correction contrôlée
|
||||
|
||||
La boucle de correction n'est pas un script externe — elle est gérée nativement par Claude Code via le prompt du subagent et les hooks.
|
||||
|
||||
Séquence type :
|
||||
1. Claude Code délègue à `qwen3-worker`.
|
||||
2. Le subagent retourne `STATUS: failure` avec détail.
|
||||
3. Claude Code lit le résumé, corrige lui-même ou re-délègue avec le feedback intégré dans le prompt.
|
||||
4. Maximum 2 re-délégations ; au-delà, Claude Code traite directement.
|
||||
|
||||
---
|
||||
|
||||
## 11. Sécurité
|
||||
|
||||
Les garde-fous sont dans les **hooks** (non contournables par le contexte) plutôt que dans des fonctions Python.
|
||||
|
||||
```json
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cargo:*)",
|
||||
"Bash(go:*)",
|
||||
"Bash(git diff*)",
|
||||
"Bash(git add*)",
|
||||
"Bash(git commit*)",
|
||||
"Read(**/*.rs)",
|
||||
"Read(**/*.go)",
|
||||
"Write(**/*.rs)",
|
||||
"Write(**/*.go)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(rm -rf*)",
|
||||
"Bash(curl * | bash*)",
|
||||
"Bash(git push --force*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Observabilité
|
||||
|
||||
Les logs sont produits par les hooks, pas par du code applicatif.
|
||||
|
||||
```
|
||||
.claude/logs/
|
||||
├── subagent-decisions.log # timestamp + agent name à chaque SubagentStop
|
||||
└── delegation-outcomes.log # STATUS de chaque délégation (si ajouté au hook)
|
||||
```
|
||||
|
||||
Pour des métriques plus élaborées (taux de délégation, taux d'échec), un hook `SubagentStop` peut écrire dans un fichier JSON et un script externe peut agréger.
|
||||
|
||||
---
|
||||
|
||||
## 13. Workflow complet — exemple
|
||||
|
||||
**Requête** : "Génère les tests unitaires pour les fonctions `parse_header` et `validate_checksum` dans `src/parser.rs`."
|
||||
|
||||
1. Claude Code lit `CLAUDE.md` → identifie les heuristiques de délégation.
|
||||
2. Charge le skill `delegation-rules.md` → tâche éligible (1 fichier, atomique, pas d'API publique).
|
||||
3. Délègue au subagent `qwen3-worker` : *"Génère des tests unitaires pour `parse_header` et `validate_checksum` dans `src/parser.rs`"*.
|
||||
4. `qwen3-worker` lit `src/parser.rs` via l'outil Read (le contenu ne passe pas par le contexte principal).
|
||||
5. Écrit les tests dans `src/parser.rs` (ou `tests/parser_tests.rs`).
|
||||
6. Hook `post-write-lint.sh` déclenché → `cargo clippy` → résultat injecté dans le contexte du subagent.
|
||||
7. Subagent retourne : `STATUS: success | FILES_MODIFIED: src/parser.rs | LINT: passed`.
|
||||
8. Claude Code confirme à l'utilisateur.
|
||||
|
||||
---
|
||||
|
||||
## 14. Ce qui a changé par rapport au document initial
|
||||
|
||||
| Document initial | Cette version |
|
||||
|-----------------|---------------|
|
||||
| Script Python `delegate_to_qwen.py` comme orchestrateur | Claude Code natif comme orchestrateur |
|
||||
| Validation syntaxique dans le script | Hook `post-write-lint.sh` (déterministe, non contournable) |
|
||||
| Boucle de correction en Python | Gérée par le prompt du subagent + logique d'orchestration Claude |
|
||||
| Sécurité dans `is_safe_file()` | Permissions natives (`settings.json`) + hook `pre-bash-guard.sh` |
|
||||
| Logs en Python | Hooks shell sur les événements du cycle de vie |
|
||||
| Tools MCP = scripts ad hoc | MCP server stdio standard + subagents natifs |
|
||||
|
||||
---
|
||||
|
||||
## 15. Prochaines étapes recommandées
|
||||
|
||||
1. **Bootstrapper la structure** : créer `.claude/agents/`, `settings.json`, `CLAUDE.md` avec les templates ci-dessus.
|
||||
2. **Tester LM Studio** : vérifier que `curl http://localhost:1234/v1/models` répond avant d'activer le MCP server.
|
||||
3. **Calibrer les heuristiques** : après 20-30 délégations, affiner les critères dans `delegation-rules.md` selon les résultats observés dans les logs.
|
||||
4. **Envisager `model:` explicite** : si Claude Haiku est suffisant pour `qwen3-worker` (coût réduit, latence moindre), configurer `model: claude-haiku-4-5-20251001` à la place du LLM local.
|
||||
5. **MCP Tool Search** : activer `ENABLE_TOOL_SEARCH=auto` pour réduire la consommation de contexte quand les MCP servers sont nombreux.
|
||||
Reference in New Issue
Block a user