refactor: migrate MCP to aichat and integrate llm-functions

Replace LM Studio direct integration with aichat-based MCP server, enabling Qwen3-Coder to autonomously use filesystem, shell and web tools via llm-functions. Key changes:
- Update server.py to launch aichat on a dynamic port and handle tool_calls loop
- Add llm-functions directory with prebuilt tools (fs_cat, fs_ls, execute_command, etc.)
- Revise delegation rules to remove file count limit and emphasize autonomous tool usage
- Update agents and skills docs to reflect new architecture and workflow
- Add setup instructions for aichat, argc, jq and llm-functions build step
- Bump MCP server version to 3.0.0
This commit is contained in:
2026-03-27 09:13:47 +01:00
parent 33191c54b2
commit cbb9de7c30
6 changed files with 379 additions and 153 deletions

View File

@@ -29,18 +29,20 @@ Programmeur expérimenté. Connaît parfaitement son projet, ses outils, et son
## Heuristiques de délégation ## Heuristiques de délégation
Délègue à `qwen3-worker` si la tâche est **atomique** et satisfait TOUS les critères : Délègue à `qwen3-worker` (via MCP `qwen3_task`) si la tâche satisfait TOUS les critères :
- ≤ 3 fichiers concernés - Objectif clair et bien délimité
- Pas de modification dAPI publique - Pas de modification dAPI publique (traits Rust, interfaces Go exportées)
- Pas de dépendance externe non encore importée - Pas de vision cross-module requise
- Tâches typiques : génération de tests unitaires, reformatage, documentation inline, - Tâches typiques : génération de tests, documentation inline, scaffolding de stubs,
conversion de types simples, scaffolding de stubs reformatage, recherche web, exécution de scripts, création de fichiers
- Pour le bash, éviter les commandes destructrices (voir garde-fous)
Qwen3 dispose doutils autonomes (filesystem, shell, web) — inutile de lui passer
le contenu des fichiers, il les lit lui-même.
Traite toi-même si : Traite toi-même si :
- Refactoring cross-module - Refactoring cross-module
- Conception darchitecture - Conception darchitecture
- Debugging avec contexte multi-fichiers - Debugging avec stacktrace multi-fichiers
- Modification de traits/interfaces publics - Modification de traits/interfaces publics
## Garde-fous absolus ## Garde-fous absolus

View File

@@ -1,30 +1,56 @@
--- ---
name: qwen3-worker name: qwen3-worker
description: > description: >
Délègue à Qwen3-Coder via LM Studio les tâches atomiques sur 1-3 fichiers : Délègue à Qwen3-Coder via le MCP tool `qwen3_task` les tâches de codage autonomes :
génération de tests unitaires, documentation inline, scaffolding de stubs, génération de tests, documentation, scaffolding, reformatage, recherche web,
reformatage de code. N'utilise PAS pour du refactoring cross-module ou de exécution de scripts. Qwen3 dispose d'outils filesystem, shell et web — il lit et
écrit les fichiers lui-même. N'utilise PAS pour du refactoring cross-module ou de
la conception d'architecture. la conception d'architecture.
tools: [Read, Write, Bash] tools: [mcp__qwen3__qwen3_task]
model: inherit model: inherit
--- ---
Tu es un assistant de codage spécialisé exécutant des tâches courtes et précises. Tu es un orchestrateur qui délègue des tâches de codage à Qwen3-Coder via l'outil MCP `qwen3_task`.
## Comportement ## 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. 1. Formule une description précise de la tâche.
3. Écris le résultat avec Write. 2. Appelle `qwen3_task` avec le champ `task`. Le champ `files` est optionnel — ne l'utilise
4. Exécute le linter approprié (clippy pour Rust, golangci-lint pour Go) et corrige que pour donner un contexte initial ; Qwen3 peut lire les fichiers dont il a besoin lui-même.
les erreurs éventuelles (max 2 tentatives). 3. Qwen3 opère de façon autonome : il lit, écrit, exécute des commandes et fait des requêtes
5. Retourne un résumé : fichiers modifiés, changements effectués, résultat du lint. web via ses propres outils (llm-functions). Tu n'as pas à gérer ces étapes.
4. Retourne le résultat de `qwen3_task` à l'orchestrateur principal.
## Outils disponibles pour Qwen3
Qwen3 dispose des outils suivants (définis dans `.claude/llm-functions/tools.txt`) :
| Outil | Description |
|-------|-------------|
| `fs_cat` | Lit un fichier |
| `fs_ls` | Liste un répertoire |
| `fs_mkdir` | Crée un répertoire |
| `fs_write` | Écrit/crée un fichier |
| `fs_patch` | Modifie un fichier par patch |
| `execute_command` | Exécute une commande shell |
| `fetch_url_via_curl` | Requête HTTP GET |
| `web_search_aichat` | Recherche web |
| `execute_py_code` | Exécute du code Python |
## Format de sortie ## Format de sortie
Qwen3 retourne une réponse en langage naturel. Résume-la ainsi :
``` ```
STATUS: success|partial|failure STATUS: success|partial|failure
FILES_MODIFIED: liste des fichiers FILES_MODIFIED: liste des fichiers (si applicable)
SUMMARY: description des changements 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. Si Qwen3 retourne une erreur (`ERROR:`), signale-la avec `STATUS: failure`.
## Contraintes
- Ne modifie pas les signatures publiques (traits Rust, interfaces Go exportées).
- Ne génère pas de fonctions non demandées.
- Max 2 re-délégations en cas d'échec partiel ; au-delà, remonte le problème.

View File

@@ -0,0 +1 @@
2026-03-27T08:01:50Z agent=unknown

View File

@@ -1,34 +1,56 @@
#!/usr/bin/env -S .claude/venv/bin/python3 #!/usr/bin/env -S .claude/venv/bin/python3
""" """
MCP server stdio exposant un outil `qwen3_task`. MCP server stdio exposant un outil `qwen3_task`.
Lance aichat en mode serveur au démarrage et gère la boucle tool_calls.
Les outils disponibles pour Qwen3 sont chargés depuis llm-functions/functions.json.
Appelé par Claude Code via : claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py Appelé par Claude Code via : claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py
""" """
import atexit
import json import json
import os
import shutil
import socket
import subprocess
import sys import sys
import time
from pathlib import Path
import requests import requests
SERVER = "http://localhost" QWEN3_MODEL = "LMStudio:qwen/qwen3-coder-next"
PORT = 8888 # 1248 MAX_TOOL_ITERATIONS = 10
LMSTUDIO_URL = f"{SERVER}:{PORT}/v1/chat/completions"
QWEN3_MODEL = "qwen/qwen3-coder-next"
TOOLS = [ # Répertoire llm-functions relatif à ce script
_HERE = Path(__file__).parent
_LLM_FUNCTIONS_DIR = _HERE.parent.parent / "llm-functions"
_FUNCTIONS_JSON = _LLM_FUNCTIONS_DIR / "functions.json"
_BIN_DIR = _LLM_FUNCTIONS_DIR / "bin"
def _load_qwen3_tools() -> list[dict]:
"""Charge les outils depuis llm-functions/functions.json et les wrappe au format OpenAI."""
if not _FUNCTIONS_JSON.exists():
print(f"WARN: {_FUNCTIONS_JSON} introuvable — aucun outil disponible",
file=sys.stderr, flush=True)
return []
raw = json.loads(_FUNCTIONS_JSON.read_text())
return [{"type": "function", "function": tool} for tool in raw]
# Outil MCP exposé à Claude Code
MCP_TOOLS = [
{ {
"name": "qwen3_task", "name": "qwen3_task",
"description": "Délègue une tâche de codage atomique à Qwen3-Coder via LM Studio.", "description": "Délègue une tâche de codage à Qwen3-Coder via aichat. Qwen3 dispose d'outils filesystem, shell et web (llm-functions).",
"inputSchema": { "inputSchema": {
"type": "object", "type": "object",
"properties": { "properties": {
"task": { "task": {"type": "string", "description": "Description précise de la tâche"},
"type": "string",
"description": "Description précise de la tâche",
},
"files": { "files": {
"type": "array", "type": "array",
"items": {"type": "string"}, "items": {"type": "string"},
"description": "Chemins des fichiers concernés", "description": "Fichiers à mettre en contexte initial (optionnel)",
}, },
}, },
"required": ["task"], "required": ["task"],
@@ -37,40 +59,148 @@ TOOLS = [
] ]
def send(obj: dict): # --- Gestion du processus aichat ---
print(json.dumps(obj), flush=True)
_aichat_proc: subprocess.Popen | None = None
_aichat_url: str | None = None
def _find_free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def _start_aichat() -> str:
global _aichat_proc, _aichat_url
if not shutil.which("aichat"):
raise RuntimeError("aichat introuvable dans le PATH")
port = _find_free_port()
address = f"127.0.0.1:{port}"
env = os.environ.copy()
if _LLM_FUNCTIONS_DIR.exists():
env["AICHAT_FUNCTIONS_DIR"] = str(_LLM_FUNCTIONS_DIR)
_aichat_proc = subprocess.Popen(
["aichat", "--serve", address],
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
env=env,
)
atexit.register(_stop_aichat)
url = f"http://{address}"
for _ in range(30):
try:
requests.get(f"{url}/v1/models", timeout=1)
_aichat_url = url
return url
except requests.RequestException:
time.sleep(0.3)
_aichat_proc.kill()
raise RuntimeError(f"aichat n'a pas démarré sur {address}")
def _stop_aichat():
if _aichat_proc and _aichat_proc.poll() is None:
_aichat_proc.terminate()
try:
_aichat_proc.wait(timeout=5)
except subprocess.TimeoutExpired:
_aichat_proc.kill()
# --- Exécution des outils via llm-functions/bin/ ---
def _execute_tool(name: str, args: dict) -> str:
"""Exécute un outil llm-functions via son binaire dans bin/.
Les binaires attendent le JSON des arguments comme premier argument positionnel.
"""
bin_path = _BIN_DIR / name
if not bin_path.exists():
return f"ERROR: outil '{name}' introuvable dans {_BIN_DIR}"
try:
result = subprocess.run(
[str(bin_path), json.dumps(args)],
capture_output=True,
text=True,
timeout=60,
)
output = (result.stdout + result.stderr).strip()
return output or f"(exit code {result.returncode})"
except subprocess.TimeoutExpired:
return f"ERROR: timeout lors de l'exécution de '{name}'"
except Exception as e:
return f"ERROR: {e}"
# --- Boucle tool_calls → Qwen3 ---
def call_qwen3(task: str, files: list[str]) -> str: def call_qwen3(task: str, files: list[str]) -> str:
if _aichat_url is None:
raise RuntimeError("aichat non démarré")
qwen3_tools = _load_qwen3_tools()
context = "" context = ""
for path in files: for path in files:
try: try:
with open(path) as f: with open(path, encoding="utf-8") as f:
context += f"--- {path} ---\n{f.read()}\n\n" context += f"--- {path} ---\n{f.read()}\n\n"
except OSError as e: except OSError as e:
context += f"--- {path} --- ERREUR: {e}\n\n" 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. user_content = f"TÂCHE: {task}"
if context:
user_content += f"\n\nFICHIERS:\n{context}"
TÂCHE: {task} messages = [{"role": "user", "content": user_content}]
payload: dict = {
"model": QWEN3_MODEL,
"messages": messages,
"temperature": 0.2,
"max_tokens": 4096,
}
if qwen3_tools:
payload["tools"] = qwen3_tools
FICHIERS: for _ in range(MAX_TOOL_ITERATIONS):
{context} resp = requests.post(
f"{_aichat_url}/v1/chat/completions",
json=payload,
timeout=120,
)
resp.raise_for_status()
data = resp.json()
choice = data["choices"][0]
message = choice["message"]
payload["messages"].append(message)
Retourne uniquement le code modifié ou généré, sans explication. if choice["finish_reason"] != "tool_calls":
""" return message.get("content") or ""
resp = requests.post(
LMSTUDIO_URL, for tc in message.get("tool_calls", []):
json={ fn = tc["function"]
"model": QWEN3_MODEL, args = json.loads(fn["arguments"])
"messages": [{"role": "user", "content": prompt}], result = _execute_tool(fn["name"], args)
"temperature": 0.2, payload["messages"].append({
"max_tokens": 4096, "role": "tool",
}, "tool_call_id": tc["id"],
timeout=120, "content": result,
) })
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"] return "ERROR: nombre maximum d'itérations tool_calls atteint"
# --- Protocole MCP stdio ---
def send(obj: dict):
print(json.dumps(obj), flush=True)
def handle(req: dict): def handle(req: dict):
@@ -78,21 +208,19 @@ def handle(req: dict):
req_id = req.get("id") req_id = req.get("id")
if method == "initialize": if method == "initialize":
send( send({
{ "jsonrpc": "2.0",
"jsonrpc": "2.0", "id": req_id,
"id": req_id, "result": {
"result": { "protocolVersion": "2024-11-05",
"protocolVersion": "2024-11-05", "capabilities": {"tools": {}},
"capabilities": {"tools": {}}, "serverInfo": {"name": "qwen3-mcp", "version": "3.0.0"},
"serverInfo": {"name": "qwen3-mcp", "version": "1.0.0"}, },
}, })
}
)
elif method == "notifications/initialized": elif method == "notifications/initialized":
pass # notification, pas de réponse pass
elif method == "tools/list": elif method == "tools/list":
send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": TOOLS}}) send({"jsonrpc": "2.0", "id": req_id, "result": {"tools": MCP_TOOLS}})
elif method == "tools/call": elif method == "tools/call":
params = req.get("params", {}) params = req.get("params", {})
name = params.get("name", "") name = params.get("name", "")
@@ -100,42 +228,31 @@ def handle(req: dict):
if name == "qwen3_task": if name == "qwen3_task":
try: try:
text = call_qwen3(args["task"], args.get("files", [])) text = call_qwen3(args["task"], args.get("files", []))
send( send({"jsonrpc": "2.0", "id": req_id, "result": {
{ "content": [{"type": "text", "text": text}]
"jsonrpc": "2.0", }})
"id": req_id,
"result": {"content": [{"type": "text", "text": text}]},
}
)
except Exception as e: except Exception as e:
send( send({"jsonrpc": "2.0", "id": req_id, "result": {
{ "content": [{"type": "text", "text": f"ERROR: {e}"}],
"jsonrpc": "2.0", "isError": True,
"id": req_id, }})
"result": {
"content": [{"type": "text", "text": f"ERROR: {e}"}],
"isError": True,
},
}
)
else: else:
send( send({"jsonrpc": "2.0", "id": req_id,
{ "error": {"code": -32601, "message": f"Outil inconnu : {name}"}})
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32601, "message": f"Outil inconnu : {name}"},
}
)
elif req_id is not None: elif req_id is not None:
send( send({"jsonrpc": "2.0", "id": req_id,
{ "error": {"code": -32601, "message": f"Méthode inconnue : {method}"}})
"jsonrpc": "2.0",
"id": req_id,
"error": {"code": -32601, "message": f"Méthode inconnue : {method}"},
}
)
# --- Démarrage ---
try:
_start_aichat()
except Exception as e:
print(json.dumps({"error": f"Impossible de démarrer aichat : {e}"}),
file=sys.stderr, flush=True)
sys.exit(1)
for line in sys.stdin: for line in sys.stdin:
line = line.strip() line = line.strip()
if not line: if not line:

View File

@@ -1,32 +1,43 @@
--- ---
name: delegation-rules name: delegation-rules
description: Règles pour décider quand et comment déléguer à qwen3-worker ou qwen3 MCP description: Règles pour décider quand et comment déléguer à qwen3-worker via le MCP qwen3_task
--- ---
# Règles de Délégation à Qwen3 # Règles de Délégation à Qwen3
## Critères de délégation (TOUS requis) Qwen3-Coder opère via le MCP tool `qwen3_task`. Il dispose d'outils filesystem, shell
- ≤ 3 fichiers source concernés et web (llm-functions) — il peut lire/écrire des fichiers, exécuter des commandes,
- Tâche atomique, sans dépendance à résoudre faire des recherches web et exécuter du code de façon autonome.
- Pas de modification de signature publique (trait, interface exportée)
- Contexte de fichier estimé < 8 000 tokens ## Critères de délégation (TOUS requis)
- Tâche bien définie, objectif clair
- Pas de modification de signature publique (trait, interface exportée)
- Pas de refactoring cross-module nécessitant une vision globale de l'architecture
## Tâches éligibles
## Tâches typiques éligibles
- Génération de tests unitaires pour une fonction donnée - Génération de tests unitaires pour une fonction donnée
- Ajout de documentation (doc comments Rust, godoc Go) - Ajout de documentation (doc comments Rust, godoc Go)
- Scaffolding de stubs à partir d'une interface - Scaffolding de stubs à partir d'une interface
- Conversion de types internes simples - Conversion de types internes simples
- Reformatage / réorganisation d'imports - Reformatage / réorganisation d'imports
- Recherche d'information sur le web ou dans une URL
- Exécution et vérification d'un script Python
- Création ou modification de fichiers de configuration
## Procédure ## Procédure
1. Évalue les critères ci-dessus. 1. Évalue les critères ci-dessus.
2. Si éligible → utilise le subagent `qwen3-worker` en précisant la tâche et les fichiers. 2. Si éligible → utilise le subagent `qwen3-worker` avec une description précise de la tâche.
3. Attends le résumé de sortie (STATUS / FILES_MODIFIED / LINT). Ne pas pré-charger les fichiers : Qwen3 les lit lui-même si nécessaire.
4. Si STATUS=failure → prends en charge toi-même. 3. Attends le résumé (STATUS / FILES_MODIFIED / SUMMARY).
5. Si STATUS=partial → corrige les points restants directement. 4. Si `STATUS: failure` → prends en charge toi-même.
5. Si `STATUS: partial` → corrige les points restants directement.
## Ne jamais déléguer ## Ne jamais déléguer
- Refactoring impliquant > 3 fichiers
- Refactoring impliquant une vision cross-module
- Changement d'architecture ou de design pattern - Changement d'architecture ou de design pattern
- Debugging avec stacktrace à analyser - Debugging avec stacktrace à analyser sur plusieurs fichiers
- Tâches nécessitant une recherche web ou du contexte externe - Tâches nécessitant le contexte de la conversation en cours

151
README.md
View File

@@ -1,17 +1,38 @@
# CrazyClaude # 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. Configuration Claude Code pour orchestrer des tâches de développement en déléguant les tâches atomiques à **Qwen3-Coder** via [aichat](https://github.com/sigoden/aichat).
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. 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, un serveur MCP et une collection d'outils prêts à l'emploi.
Pour le détail de l'architecture, voir [`rapport-orchestration-claude-code.md`](./rapport-orchestration-claude-code.md). Pour le détail de l'architecture, voir [`rapport-orchestration-claude-code.md`](./rapport-orchestration-claude-code.md).
--- ---
## Architecture
```
Claude Code (orchestrateur)
└── MCP qwen3 (stdio)
└── aichat --serve (port dynamique)
└── LM Studio → Qwen3-Coder-Next
+ outils llm-functions
(fs, shell, web, ...)
```
Le serveur MCP lance aichat au démarrage sur un port libre. Qwen3 dispose d'outils filesystem, shell et web fournis par [llm-functions](https://github.com/sigoden/llm-functions) — sans aucune logique d'outil dans le serveur lui-même.
---
## Prérequis ## Prérequis
- [Claude Code](https://claude.ai/code) installé (`claude` disponible dans le PATH) - [Claude Code](https://claude.ai/code) (`claude` dans le PATH)
- [LM Studio](https://lmstudio.ai/) avec le modèle `qwen/qwen3-coder-next` chargé, écoutant sur `http://localhost:1248` - [aichat](https://github.com/sigoden/aichat) (`aichat` dans le PATH)
- [LM Studio](https://lmstudio.ai/) avec le modèle `qwen/qwen3-coder-next` chargé et accessible depuis aichat
- [argc](https://github.com/sigoden/argc) — runner de scripts pour llm-functions
- [jq](https://jqlang.github.io/jq/) — requis par llm-functions
- Python 3.11+ (pour le serveur MCP) - Python 3.11+ (pour le serveur MCP)
- Outils selon votre stack : `cargo` + `clippy` (Rust), `golangci-lint` (Go) - Outils selon votre stack : `cargo` + `clippy` (Rust), `golangci-lint` (Go)
@@ -28,18 +49,14 @@ Avec **Jujutsu** :
jj git clone https://gargoton.petite-maison-orange.fr/eric/CrazyClaude.git .claude-crazy jj git clone https://gargoton.petite-maison-orange.fr/eric/CrazyClaude.git .claude-crazy
``` ```
Puis déplacez ou liez le contenu : Puis copiez le contenu :
```sh ```sh
# Copier le répertoire .claude dans votre projet
cp -r .claude-crazy/.claude ./.claude cp -r .claude-crazy/.claude ./.claude
cp -r .claude-crazy/llm-functions ./.claude/llm-functions # si présent dans le clone
# 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`), > Avec jj colocalisé git, vous pouvez aussi utiliser subtree :
> vous pouvez aussi ajouter ce dépôt comme subtree :
> >
> ```sh > ```sh
> git subtree add --prefix .claude https://gargoton.petite-maison-orange.fr/eric/CrazyClaude.git main --squash > git subtree add --prefix .claude https://gargoton.petite-maison-orange.fr/eric/CrazyClaude.git main --squash
@@ -53,24 +70,61 @@ python3 -m venv venv
venv/bin/pip install -r mcp/qwen3-mcp/requirements.txt venv/bin/pip install -r mcp/qwen3-mcp/requirements.txt
``` ```
### 3. Enregistrer le serveur MCP ### 3. Installer et construire llm-functions
Si `llm-functions/` n'est pas déjà présent dans `.claude/` :
```sh
git clone https://github.com/sigoden/llm-functions .claude/llm-functions
```
Choisissez les outils à activer en éditant `.claude/llm-functions/tools.txt`, puis construisez :
```sh
cd .claude/llm-functions
argc build
```
Les outils activés par défaut :
```
fs_cat.sh
fs_ls.sh
fs_mkdir.sh
fs_write.sh
fs_patch.sh
execute_command.sh
fetch_url_via_curl.sh
web_search_aichat.sh
execute_py_code.py
```
### 4. Configurer aichat
Vérifiez que aichat peut accéder à LM Studio et que le modèle `qwen/qwen3-coder-next` est disponible :
```sh
aichat --list-models | grep qwen
```
### 5. Enregistrer le serveur MCP
```sh ```sh
claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py
``` ```
Vérifier que la connexion est établie : Vérifier :
```sh ```sh
claude mcp list claude mcp list
# qwen3: ... - ✓ Connected # qwen3: ... - ✓ Connected
``` ```
### 4. Adapter CLAUDE.md à votre projet ### 6. Adapter CLAUDE.md à votre projet
Éditez `.claude/CLAUDE.md` : mettez à jour la section `## Stack` et les heuristiques de délégation selon vos besoins. Éditez `.claude/CLAUDE.md` : mettez à jour la section `## Stack` et les heuristiques de délégation.
### 5. Rendre les hooks exécutables ### 7. Rendre les hooks exécutables
```sh ```sh
chmod +x .claude/hooks/*.sh chmod +x .claude/hooks/*.sh
@@ -85,7 +139,7 @@ chmod +x .claude/hooks/*.sh
├── CLAUDE.md # contexte projet + heuristiques de délégation ├── CLAUDE.md # contexte projet + heuristiques de délégation
├── settings.json # hooks et permissions ├── settings.json # hooks et permissions
├── agents/ ├── agents/
│ ├── qwen3-worker.md # délégation vers LM Studio (Qwen3) │ ├── qwen3-worker.md # délégation vers Qwen3 via MCP
│ ├── code-reviewer.md # révision code (Claude Haiku) │ ├── code-reviewer.md # révision code (Claude Haiku)
│ └── task-planner.md # planification complexe (Claude Opus) │ └── task-planner.md # planification complexe (Claude Opus)
├── skills/ ├── skills/
@@ -97,66 +151,81 @@ chmod +x .claude/hooks/*.sh
│ └── pre-bash-guard.sh # blocage des commandes dangereuses │ └── pre-bash-guard.sh # blocage des commandes dangereuses
├── mcp/ ├── mcp/
│ └── qwen3-mcp/ │ └── qwen3-mcp/
│ ├── server.py # serveur MCP stdio (interface LM Studio) │ ├── server.py # serveur MCP stdio : lance aichat + gère tool_calls
│ ├── agent_lm.py # agent loop CLI autonome │ ├── agent_lm.py # agent loop CLI autonome (usage direct sans MCP)
│ └── requirements.txt │ └── requirements.txt
├── llm-functions/ # collection d'outils pour Qwen3 (sous-repo)
│ ├── tools.txt # outils activés
│ ├── functions.json # déclarations générées (lues par server.py)
│ ├── bin/ # binaires générés par argc build
│ └── tools/ # scripts sources (.sh, .py, .js)
├── logs/ # produits par les hooks (ignorés par jj/git) ├── logs/ # produits par les hooks (ignorés par jj/git)
└── venv/ # virtualenv Python (ignoré par jj/git) └── venv/ # virtualenv Python (ignoré par jj/git)
``` ```
--- ---
## Configuration LM Studio ## Outils disponibles pour Qwen3
Le serveur MCP se connecte sur `http://localhost:1248` avec le modèle `qwen/qwen3-coder-next`. Les outils sont définis dans `.claude/llm-functions/tools.txt` et buildés avec `argc build`.
Pour modifier ces valeurs, éditez `.claude/mcp/qwen3-mcp/server.py` : | Outil | Description |
|-------|-------------|
| `fs_cat` | Lit un fichier |
| `fs_ls` | Liste un répertoire |
| `fs_mkdir` | Crée un répertoire |
| `fs_write` | Écrit/crée un fichier |
| `fs_patch` | Modifie un fichier par patch |
| `execute_command` | Exécute une commande shell |
| `fetch_url_via_curl` | Requête HTTP GET |
| `web_search_aichat` | Recherche web |
| `execute_py_code` | Exécute du code Python |
Pour ajouter un outil : éditez `tools.txt` et relancez `argc build`.
---
## Configuration du modèle
Le modèle Qwen3 est configuré dans `.claude/mcp/qwen3-mcp/server.py` :
```python ```python
LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions" QWEN3_MODEL = "LMStudio:qwen/qwen3-coder-next"
QWEN3_MODEL = "qwen/qwen3-coder-next"
``` ```
Le format `Provider:model-id` suit la convention aichat. Adaptez selon votre installation LM Studio.
--- ---
## Fichiers à ignorer (jj / git) ## Fichiers à ignorer (jj / git)
Ajoutez ces entrées dans votre fichier d'ignore :
``` ```
.claude/venv/ .claude/venv/
.claude/logs/ .claude/logs/
.claude/llm-functions/bin/
.claude/llm-functions/cache/
``` ```
Avec Jujutsu : Avec Jujutsu :
```sh ```sh
echo ".claude/venv/" >> .gitignore printf ".claude/venv/\n.claude/logs/\n.claude/llm-functions/bin/\n.claude/llm-functions/cache/\n" >> .gitignore
echo ".claude/logs/" >> .gitignore
``` ```
--- ---
## Mise à jour ## Mise à jour
Avec Jujutsu (workflow colocalisé git) : Avec Jujutsu colocalisé (`jj git init --colocate`), la mise à jour est gérée côté git :
```sh ```sh
git subtree pull --prefix .claude https://gargoton.petite-maison-orange.fr/eric/CrazyClaude.git main --squash git subtree pull --prefix .claude https://gargoton.petite-maison-orange.fr/eric/CrazyClaude.git main --squash
``` ```
Ou si vous avez cloné séparément, tirez les changements puis recopiez : jj voit le commit résultant comme un commit ordinaire dans son graphe.
Après mise à jour, reconstruire llm-functions si `tools.txt` a changé :
```sh ```sh
cd .claude-crazy && jj git fetch && jj new main cd .claude/llm-functions && argc build
cp -r .claude/* ../.claude/
``` ```
## Outils necessaire
### aichat
### jq
### argc