diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 87a2218..a476e0e 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -29,18 +29,20 @@ Programmeur expérimenté. Connaît parfaitement son projet, ses outils, et son ## 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 -- Pour le bash, éviter les commandes destructrices (voir garde-fous) +Délègue à `qwen3-worker` (via MCP `qwen3_task`) si la tâche satisfait TOUS les critères : +- Objectif clair et bien délimité +- Pas de modification d’API publique (traits Rust, interfaces Go exportées) +- Pas de vision cross-module requise +- Tâches typiques : génération de tests, documentation inline, scaffolding de stubs, + reformatage, recherche web, exécution de scripts, création de fichiers + +Qwen3 dispose d’outils autonomes (filesystem, shell, web) — inutile de lui passer +le contenu des fichiers, il les lit lui-même. Traite toi-même si : - Refactoring cross-module - Conception d’architecture -- Debugging avec contexte multi-fichiers +- Debugging avec stacktrace multi-fichiers - Modification de traits/interfaces publics ## Garde-fous absolus diff --git a/.claude/agents/qwen3-worker.md b/.claude/agents/qwen3-worker.md index 846f3ab..e67da64 100644 --- a/.claude/agents/qwen3-worker.md +++ b/.claude/agents/qwen3-worker.md @@ -1,30 +1,56 @@ --- 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 + Délègue à Qwen3-Coder via le MCP tool `qwen3_task` les tâches de codage autonomes : + génération de tests, documentation, scaffolding, reformatage, recherche web, + 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. -tools: [Read, Write, Bash] +tools: [mcp__qwen3__qwen3_task] 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 -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. + +1. Formule une description précise de la tâche. +2. Appelle `qwen3_task` avec le champ `task`. Le champ `files` est optionnel — ne l'utilise + que pour donner un contexte initial ; Qwen3 peut lire les fichiers dont il a besoin lui-même. +3. Qwen3 opère de façon autonome : il lit, écrit, exécute des commandes et fait des requêtes + 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 + +Qwen3 retourne une réponse en langage naturel. Résume-la ainsi : + ``` STATUS: success|partial|failure -FILES_MODIFIED: liste des fichiers +FILES_MODIFIED: liste des fichiers (si applicable) 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. diff --git a/.claude/logs/subagent-decisions.log b/.claude/logs/subagent-decisions.log new file mode 100644 index 0000000..1ecdcc4 --- /dev/null +++ b/.claude/logs/subagent-decisions.log @@ -0,0 +1 @@ +2026-03-27T08:01:50Z agent=unknown diff --git a/.claude/mcp/qwen3-mcp/server.py b/.claude/mcp/qwen3-mcp/server.py index 8e8c1ae..655e8d0 100644 --- a/.claude/mcp/qwen3-mcp/server.py +++ b/.claude/mcp/qwen3-mcp/server.py @@ -1,34 +1,56 @@ #!/usr/bin/env -S .claude/venv/bin/python3 """ 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 """ +import atexit import json +import os +import shutil +import socket +import subprocess import sys +import time +from pathlib import Path import requests -SERVER = "http://localhost" -PORT = 8888 # 1248 -LMSTUDIO_URL = f"{SERVER}:{PORT}/v1/chat/completions" -QWEN3_MODEL = "qwen/qwen3-coder-next" +QWEN3_MODEL = "LMStudio:qwen/qwen3-coder-next" +MAX_TOOL_ITERATIONS = 10 -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", - "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": { "type": "object", "properties": { - "task": { - "type": "string", - "description": "Description précise de la tâche", - }, + "task": {"type": "string", "description": "Description précise de la tâche"}, "files": { "type": "array", "items": {"type": "string"}, - "description": "Chemins des fichiers concernés", + "description": "Fichiers à mettre en contexte initial (optionnel)", }, }, "required": ["task"], @@ -37,40 +59,148 @@ TOOLS = [ ] -def send(obj: dict): - print(json.dumps(obj), flush=True) +# --- Gestion du processus aichat --- +_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: + if _aichat_url is None: + raise RuntimeError("aichat non démarré") + + qwen3_tools = _load_qwen3_tools() + context = "" for path in files: try: - with open(path) as f: + with open(path, encoding="utf-8") 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. + 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: -{context} + for _ in range(MAX_TOOL_ITERATIONS): + 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. -""" - 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"] + if choice["finish_reason"] != "tool_calls": + return message.get("content") or "" + + for tc in message.get("tool_calls", []): + fn = tc["function"] + args = json.loads(fn["arguments"]) + result = _execute_tool(fn["name"], args) + payload["messages"].append({ + "role": "tool", + "tool_call_id": tc["id"], + "content": result, + }) + + 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): @@ -78,21 +208,19 @@ def handle(req: dict): 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"}, - }, - } - ) + send({ + "jsonrpc": "2.0", + "id": req_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": {"tools": {}}, + "serverInfo": {"name": "qwen3-mcp", "version": "3.0.0"}, + }, + }) elif method == "notifications/initialized": - pass # notification, pas de réponse + pass 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": params = req.get("params", {}) name = params.get("name", "") @@ -100,42 +228,31 @@ def handle(req: dict): 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}]}, - } - ) + 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, - }, - } - ) + 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}"}, - } - ) + 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}"}, - } - ) + send({"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: line = line.strip() if not line: diff --git a/.claude/skills/delegation-rules.md b/.claude/skills/delegation-rules.md index abfbf93..94cb392 100644 --- a/.claude/skills/delegation-rules.md +++ b/.claude/skills/delegation-rules.md @@ -1,32 +1,43 @@ --- 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 -## 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 +Qwen3-Coder opère via le MCP tool `qwen3_task`. Il dispose d'outils filesystem, shell +et web (llm-functions) — il peut lire/écrire des fichiers, exécuter des commandes, +faire des recherches web et exécuter du code de façon autonome. + +## 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 - 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 +- 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 + 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. +2. Si éligible → utilise le subagent `qwen3-worker` avec une description précise de la tâche. + Ne pas pré-charger les fichiers : Qwen3 les lit lui-même si nécessaire. +3. Attends le résumé (STATUS / FILES_MODIFIED / SUMMARY). +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 + +- Refactoring impliquant une vision cross-module - Changement d'architecture ou de design pattern -- Debugging avec stacktrace à analyser -- Tâches nécessitant une recherche web ou du contexte externe +- Debugging avec stacktrace à analyser sur plusieurs fichiers +- Tâches nécessitant le contexte de la conversation en cours diff --git a/README.md b/README.md index 6bd61b9..f434c7c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,38 @@ # 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). --- +## 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 -- [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` +- [Claude Code](https://claude.ai/code) (`claude` dans le PATH) +- [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) - 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 ``` -Puis déplacez ou liez le contenu : +Puis copiez 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é +cp -r .claude-crazy/llm-functions ./.claude/llm-functions # si présent dans le clone ``` -> Si vous utilisez git en sous-jacent avec jj (`jj git init --colocate`), -> vous pouvez aussi ajouter ce dépôt comme subtree : +> Avec jj colocalisé git, vous pouvez aussi utiliser subtree : > > ```sh > 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 ``` -### 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 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 claude mcp list # 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 chmod +x .claude/hooks/*.sh @@ -85,7 +139,7 @@ chmod +x .claude/hooks/*.sh ├── 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) +│ ├── qwen3-worker.md # délégation vers Qwen3 via MCP │ ├── code-reviewer.md # révision code (Claude Haiku) │ └── task-planner.md # planification complexe (Claude Opus) ├── skills/ @@ -97,66 +151,81 @@ chmod +x .claude/hooks/*.sh │ └── 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 +│ ├── server.py # serveur MCP stdio : lance aichat + gère tool_calls +│ ├── agent_lm.py # agent loop CLI autonome (usage direct sans MCP) │ └── 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) └── 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 -LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions" -QWEN3_MODEL = "qwen/qwen3-coder-next" +QWEN3_MODEL = "LMStudio:qwen/qwen3-coder-next" ``` +Le format `Provider:model-id` suit la convention aichat. Adaptez selon votre installation LM Studio. + --- ## Fichiers à ignorer (jj / git) -Ajoutez ces entrées dans votre fichier d'ignore : - ``` .claude/venv/ .claude/logs/ +.claude/llm-functions/bin/ +.claude/llm-functions/cache/ ``` Avec Jujutsu : ```sh -echo ".claude/venv/" >> .gitignore -echo ".claude/logs/" >> .gitignore +printf ".claude/venv/\n.claude/logs/\n.claude/llm-functions/bin/\n.claude/llm-functions/cache/\n" >> .gitignore ``` --- ## 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 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 -cd .claude-crazy && jj git fetch && jj new main -cp -r .claude/* ../.claude/ +cd .claude/llm-functions && argc build ``` - - -## Outils necessaire - -### aichat - -### jq - -### argc