refactor: rename MCP tool from qwen3_task to task and update delegation flow

- Rename MCP tool `qwen3_task` → `task` in server.py and all references
- Enforce direct use of `mcp__qwen3__task` (no sub-agents) in docs
- Add bootstrap logic to install.sh/update_crazyclaude.sh for repo self-installation
- Introduce tools-local/ directory for local tool overrides
- Add test suite (test_qwen3.sh, test_qwen3.md) to validate Qwen3 file generation
- Update .gitignore: ignore test_qwen3_output/, add missing .claude/
- Add system prompt to server.py for fs_write enforcement
- Update agent docs (qwen3-worker, code-reviewer, delegation-rules, task-planner) to reflect new tool name and workflow
- Add system instruction: always use fs_write/fs_patch, never display content in response
This commit is contained in:
2026-03-27 17:31:40 +01:00
parent ee625dfe3d
commit 5a0874c3c0
13 changed files with 361 additions and 44 deletions

2
.gitignore vendored
View File

@@ -1,7 +1,9 @@
venv/
logs/
test_qwen3_output/
llm-functions/bin/
llm-functions/cache/
__pycache__/
*.pyc
.claude
.claude/

View File

@@ -31,9 +31,13 @@ Programmeur expérimenté. Connaît parfaitement son projet, ses outils, et son
**Règle par défaut : délègue à Qwen3. Traite toi-même seulement si tu ne peux pas faire autrement.**
Qwen3 dispose doutils autonomes (filesystem, shell, web) via `qwen3_task`. Il lit et écrit
Délègue en appelant **directement** loutil MCP `mcp__qwen3__task` — ne pas passer par
un sous-agent (Agent tool), car les sous-agents nont pas accès aux MCP. Qwen3 lit et écrit
les fichiers lui-même — inutile de lui passer le contenu en entrée.
Pour toute création ou modification de fichier, inclure dans le prompt :
"Écris le fichier via fs_write, ne pas afficher le contenu dans ta réponse."
Délègue systématiquement pour :
- Créer ou modifier un fichier (code, config, doc, script)
- Générer des tests, de la documentation, des stubs

View File

@@ -1,21 +1 @@
---
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)
```
"---\nname: code-reviewer\ndescription: >\n Révise du code Rust ou Go pour détecter bugs, régressions, problèmes de style.\n Utilise après délégation via mcp__qwen3__task ou avant un commit.\ntools: [Read, Grep, Glob, Bash]\nmodel: claude-haiku-4-5-20251001\n---\n\nTu es un reviewer expérimenté en Rust et Go.\n\nPour chaque fichier fourni :\n1. Vérifie la cohérence des types et la gestion d'erreurs.\n2. Identifie les patterns non idiomatiques.\n3. Signale les tests manquants pour les chemins critiques.\n\nFormat de sortie :\n```\nVERDICT: approved|changes_requested\nISSUES: liste numérotée (vide si approved)\n```"

View File

@@ -1,25 +1,27 @@
---
name: qwen3-worker
description: >
Délègue à Qwen3-Coder via le MCP tool `qwen3_task` les tâches de codage autonomes :
Délègue à Qwen3-Coder via le MCP tool `mcp__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: [mcp__qwen3__qwen3_task]
tools: [mcp__qwen3__task]
model: inherit
---
Tu es un orchestrateur qui délègue des tâches de codage à Qwen3-Coder via l'outil MCP `qwen3_task`.
⚠️ Ce fichier est une documentation de référence. Ne pas invoquer comme sous-agent via l'Agent tool — les sous-agents n'ont pas accès aux MCP. Appeler `mcp__qwen3__task` directement depuis la conversation principale.
Tu es un orchestrateur qui délègue des tâches de codage à Qwen3-Coder via l'outil MCP `mcp__qwen3__task`.
## Comportement
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
2. Appelle `mcp__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.
4. Retourne le résultat de `mcp__qwen3__task` à l'orchestrateur principal.
## Outils disponibles pour Qwen3

View File

@@ -12,7 +12,7 @@ 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).
3. Identifie lesquelles sont délégables via `mcp__qwen3__task` (≤ 3 fichiers, sans changement d'API publique).
4. Estime les risques et propose des points de contrôle.
Format de sortie :

View File

@@ -7,6 +7,16 @@
set -euo pipefail
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Bootstrap : si on lance install.sh depuis le repo CrazyClaude lui-même
# (et non depuis .claude/ d'un autre projet), on s'installe en tant que .claude/
if [[ "$(basename "$CLAUDE_DIR")" != ".claude" ]]; then
TARGET="${1:-$(pwd)/.claude}"
echo "==> Bootstrap : installation de CrazyClaude dans $TARGET"
git clone "$CLAUDE_DIR" "$TARGET"
exec bash "$TARGET/install.sh"
fi
PROJECT_DIR="$(dirname "$CLAUDE_DIR")"
require() {
@@ -31,7 +41,25 @@ if [[ ! -f "$CLAUDE_DIR/llm-functions/Argcfile.sh" ]]; then
else
echo "déjà présent"
fi
if [[ ! -f "$CLAUDE_DIR/llm-functions/tools.txt" ]]; then
echo "Génération de tools.txt..."
find "$CLAUDE_DIR/llm-functions/tools" -maxdepth 1 -type f \
! -name 'demo_*' \
| sed 's|.*/||' \
| sort > "$CLAUDE_DIR/llm-functions/tools.txt"
echo " $(wc -l < "$CLAUDE_DIR/llm-functions/tools.txt") outils activés"
fi
(cd "$CLAUDE_DIR/llm-functions" && argc build)
LOCAL_TOOLS_DIR="$CLAUDE_DIR/tools-local"
if [[ -d "$LOCAL_TOOLS_DIR" ]]; then
for src in "$LOCAL_TOOLS_DIR"/*; do
[[ -f "$src" ]] || continue
name="$(basename "$src")"
cp "$src" "$CLAUDE_DIR/llm-functions/bin/$name"
chmod +x "$CLAUDE_DIR/llm-functions/bin/$name"
done
echo " overrides locaux appliqués"
fi
echo "OK"
echo ""

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env -S venv/bin/python3
"""
MCP server stdio exposant un outil `qwen3_task`.
MCP server stdio exposant un outil `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
@@ -28,20 +28,28 @@ _FUNCTIONS_JSON = _LLM_FUNCTIONS_DIR / "functions.json"
_BIN_DIR = _LLM_FUNCTIONS_DIR / "bin"
_qwen3_tools: list[dict] | None = None
def _load_qwen3_tools() -> list[dict]:
"""Charge les outils depuis llm-functions/functions.json et les wrappe au format OpenAI."""
"""Charge les outils depuis llm-functions/functions.json (mise en cache au premier appel)."""
global _qwen3_tools
if _qwen3_tools is not None:
return _qwen3_tools
if not _FUNCTIONS_JSON.exists():
print(f"WARN: {_FUNCTIONS_JSON} introuvable — aucun outil disponible",
file=sys.stderr, flush=True)
return []
_qwen3_tools = []
return _qwen3_tools
raw = json.loads(_FUNCTIONS_JSON.read_text())
return [{"type": "function", "function": tool} for tool in raw]
_qwen3_tools = [{"type": "function", "function": tool} for tool in raw]
return _qwen3_tools
# Outil MCP exposé à Claude Code
MCP_TOOLS = [
{
"name": "qwen3_task",
"name": "task",
"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",
@@ -159,7 +167,18 @@ def call_qwen3(task: str, files: list[str]) -> str:
if context:
user_content += f"\n\nFICHIERS:\n{context}"
messages = [{"role": "user", "content": user_content}]
system = (
"Tu es un assistant de développement logiciel. "
"Pour toute tâche impliquant la création ou modification de fichiers, "
"utilise TOUJOURS les outils fs_write ou fs_patch — ne jamais afficher "
"le contenu dans ta réponse. "
"Utilise execute_command pour vérifier le résultat si nécessaire. "
"Réponds en français, de façon concise."
)
messages = [
{"role": "system", "content": system},
{"role": "user", "content": user_content},
]
payload: dict = {
"model": QWEN3_MODEL,
"messages": messages,
@@ -225,7 +244,7 @@ def handle(req: dict):
params = req.get("params", {})
name = params.get("name", "")
args = params.get("arguments", {})
if name == "qwen3_task":
if name == "task":
try:
text = call_qwen3(args["task"], args.get("files", []))
send({"jsonrpc": "2.0", "id": req_id, "result": {

View File

@@ -1,13 +1,14 @@
---
name: delegation-rules
description: Règles pour décider quand et comment déléguer à qwen3-worker via le MCP qwen3_task
description: Règles pour décider quand et comment déléguer à Qwen3 via mcp__qwen3__task
---
# Règles de Délégation à Qwen3
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.
Qwen3-Coder opère via l'outil MCP `mcp__qwen3__task`, appelé **directement** — jamais
via un sous-agent (Agent tool). 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)
@@ -29,8 +30,10 @@ faire des recherches web et exécuter du code de façon autonome.
## Procédure
1. Évalue les critères ci-dessus.
2. Si éligible → utilise le subagent `qwen3-worker` avec une description précise de la tâche.
2. Si éligible → appelle directement `mcp__qwen3__task` avec une description précise de la tâche.
Ne pas pré-charger les fichiers : Qwen3 les lit lui-même si nécessaire.
Pour toute création/modification de fichier, préciser dans le prompt :
"Écris le fichier via fs_write, ne pas afficher le contenu dans ta réponse."
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.

46
skills/test_qwen3.md Normal file
View File

@@ -0,0 +1,46 @@
---
name: test-qwen3
description: Évalue Qwen3 en lui déléguant des tâches via mcp__qwen3__task et en vérifiant les fichiers produits.
---
Exécute les tests suivants dans l'ordre. Pour chaque test :
1. Appelle `mcp__qwen3__task` avec la tâche
2. Vérifie que le fichier attendu existe dans `test_qwen3_output/`
3. Vérifie que le contenu est propre (pas de balises markdown résiduelles, pas de sérialisation JSON)
4. Valide syntaxiquement si applicable
5. Affiche ✓ PASS ou ✗ FAIL avec la raison
Répertoire de sortie : `test_qwen3_output/` à la racine du projet (le créer si absent).
## Tests
**T1 — Python basique**
Tâche : "Crée le fichier test_qwen3_output/hello.py qui affiche Hello, World!"
Vérif : fichier existe, contient `print`, `python3` produit "Hello, World!", pas de balises ``` dans le fichier
**T2 — Bash boucle**
Tâche : "Crée le fichier test_qwen3_output/count.sh, script bash qui affiche les nombres de 1 à 5 un par ligne"
Vérif : fichier existe, `bash -n` passe, exécution produit "5" en dernière ligne, pas de balises ```
**T3 — Python récursion**
Tâche : "Crée le fichier test_qwen3_output/fibonacci.py avec une fonction fib(n) et à la fin print(fib(10))"
Vérif : fichier existe, `python3` retourne 55, pas de balises ```
**T4 — Markdown titre**
Tâche : "Crée le fichier test_qwen3_output/intro.md avec un titre H1 CrazyClaude et un paragraphe de présentation"
Vérif : fichier existe, commence par `#`, plus de 50 caractères, pas de balises ``` englobantes
**T5 — Markdown documentation**
Tâche : "Crée le fichier test_qwen3_output/api.md documentant la fonction process(data, options) avec les sections Description, Paramètres, Retour, Exemple"
Vérif : 4 sections présentes, contenu structuré, pas de balises ```
**T6 — Markdown changelog**
Tâche : "Crée le fichier test_qwen3_output/changelog.md avec 3 entrées au format ## [version] - date avec sous-sections Added/Fixed/Changed"
Vérif : au moins 3 entrées `## [`, sous-sections présentes
## Résumé final
Affiche :
- Le score N/6
- Pour les FAIL : contenu du fichier produit (ou absence) pour diagnostic
- Évaluation qualitative : ce que Qwen3 fait bien/mal (utilisation des outils, qualité du contenu, propreté des fichiers)

73
test_qwen3.sh Executable file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Test end-to-end de Qwen3 — tâches de difficulté croissante
set -uo pipefail
TESTDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test_qwen3_output"
mkdir -p "$TESTDIR"
pass=0
fail=0
check() {
local n="$1" desc="$2" cond="$3" reason="$4"
if eval "$cond" 2>/dev/null; then
echo " ✓ PASS"
pass=$((pass + 1))
else
echo " ✗ FAIL: $reason"
fail=$((fail + 1))
fi
}
run() {
local n="$1" desc="$2" file="$3" prompt="$4"
echo "[TEST $n] $desc"
content="$(aichat --no-stream "$prompt" 2>/dev/null | sed 's/^```[a-z]*$//' | sed '/^```$/d')" || true
printf '%s\n' "$content" > "$file"
}
# --- Programmation ---
run 1 "hello.py — Python basique" "$TESTDIR/hello.py" \
"Génère uniquement le contenu d'un fichier Python qui affiche 'Hello, World!' avec print. Sans balises markdown, sans explication."
check 1 "hello.py" \
"[[ -f '$TESTDIR/hello.py' ]] && grep -q 'print' '$TESTDIR/hello.py'" \
"fichier absent ou pas de print"
run 2 "count.sh — boucle bash 1 à 5" "$TESTDIR/count.sh" \
"Génère uniquement le contenu d'un script bash qui affiche les nombres de 1 à 5 un par ligne avec une boucle for. Sans balises markdown, sans explication."
chmod +x "$TESTDIR/count.sh" 2>/dev/null || true
check 2 "count.sh" \
"[[ -f '$TESTDIR/count.sh' ]] && bash '$TESTDIR/count.sh' | grep -q '5'" \
"fichier absent ou ne produit pas '5'"
run 3 "fibonacci.py — récursion" "$TESTDIR/fibonacci.py" \
"Génère uniquement le contenu d'un fichier Python avec une fonction fib(n) (fib(1)=1, fib(2)=1) et à la fin print(fib(10)). Sans balises markdown, sans explication."
check 3 "fibonacci.py" \
"[[ -f '$TESTDIR/fibonacci.py' ]] && [[ \"\$(python3 '$TESTDIR/fibonacci.py' 2>/dev/null)\" == '55' ]]" \
"fichier absent ou fib(10) != 55"
# --- Markdown ---
run 4 "intro.md — titre + paragraphe" "$TESTDIR/intro.md" \
"Génère uniquement le contenu markdown d'un fichier avec un titre H1 'CrazyClaude' et un paragraphe de présentation d'au moins 50 caractères. Sans balises de code."
check 4 "intro.md" \
"[[ -f '$TESTDIR/intro.md' ]] && head -1 '$TESTDIR/intro.md' | grep -q '^#' && [[ \$(wc -c < '$TESTDIR/intro.md') -gt 50 ]]" \
"fichier absent, pas de H1 ou trop court"
run 5 "api.md — documentation de fonction" "$TESTDIR/api.md" \
"Génère uniquement le contenu markdown documentant la fonction process(data, options) avec ces 4 sections : Description, Paramètres, Retour, Exemple. Sans balises de code."
check 5 "api.md" \
"[[ -f '$TESTDIR/api.md' ]] && grep -qi 'description' '$TESTDIR/api.md' && grep -qi 'param' '$TESTDIR/api.md' && grep -qi 'retour\|return' '$TESTDIR/api.md' && grep -qi 'exemple\|example' '$TESTDIR/api.md'" \
"fichier absent ou sections manquantes"
run 6 "changelog.md — format semver avec 3 entrées" "$TESTDIR/changelog.md" \
"Génère uniquement le contenu markdown d'un changelog avec 3 entrées au format '## [1.x.0] - YYYY-MM-DD', chacune avec au moins une sous-section ### Added, ### Fixed ou ### Changed. Sans balises de code."
check 6 "changelog.md" \
"[[ -f '$TESTDIR/changelog.md' ]] && [[ \$(grep -c '## \[' '$TESTDIR/changelog.md') -ge 3 ]]" \
"fichier absent ou moins de 3 entrées versionnées"
# --- Résumé ---
echo ""
echo "Sorties conservées dans $TESTDIR"
echo "=== Résultats : $pass/6 tests passés ==="
[[ $fail -eq 0 ]]

9
tools-local/README.md Normal file
View File

@@ -0,0 +1,9 @@
# tools-local
Overrides locaux pour les outils llm-functions.
Les fichiers ici sont copiés dans `llm-functions/bin/` après chaque `argc build`,
écrasant les versions upstream si elles ont le même nom (sans extension).
Utilisation : placer un script exécutable portant le même nom que le binaire à remplacer.
Exemple : `run-tool.sh` remplacera `llm-functions/bin/run-tool.sh` après le build.

119
tools-local/run-tool.sh Executable file
View File

@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# Usage: ./run-tool.sh <tool-name> <tool-data>
set -e
main() {
root_dir="$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd)"
self_name=run-tool.sh
parse_argv "$@"
setup_env
tool_path="$root_dir/tools/$tool_name.sh"
run
}
parse_argv() {
if [[ "$0" == *"$self_name" ]]; then
tool_name="$1"
tool_data="$2"
else
tool_name="$(basename "$0")"
tool_data="$1"
fi
if [[ "$tool_name" == *.sh ]]; then
tool_name="${tool_name:0:$((${#tool_name}-3))}"
fi
if [[ -z "$tool_data" ]] || [[ -z "$tool_name" ]]; then
die "usage: ./run-tool.sh <tool-name> <tool-data>"
fi
}
setup_env() {
load_env "$root_dir/.env"
export LLM_ROOT_DIR="$root_dir"
export LLM_TOOL_NAME="$tool_name"
export LLM_TOOL_CACHE_DIR="$LLM_ROOT_DIR/cache/$tool_name"
}
load_env() {
local env_file="$1" env_vars
if [[ -f "$env_file" ]]; then
while IFS='=' read -r key value; do
if [[ "$key" == $'#'* ]] || [[ -z "$key" ]]; then
continue
fi
if [[ -z "${!key+x}" ]]; then
env_vars="$env_vars $key=$value"
fi
done < <(cat "$env_file"; echo "")
if [[ -n "$env_vars" ]]; then
eval "export $env_vars"
fi
fi
}
run() {
if [[ -z "$tool_data" ]]; then
die "error: no JSON data"
fi
if [[ "$OS" == "Windows_NT" ]]; then
set -o igncr
tool_path="$(cygpath -w "$tool_path")"
tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')"
fi
jq_script="$(cat <<-'EOF'
def escape_shell_word:
if type == "string" then . else tostring end
| gsub("'"; "'\"'\"'")
| gsub("\n"; "'$'\\n''")
| "'\(.)'";
def to_args:
to_entries | .[] |
(.key | split("_") | join("-")) as $key |
if .value | type == "array" then
.value | .[] | "--\($key) \(. | escape_shell_word)"
elif .value | type == "boolean" then
if .value then "--\($key)" else "" end
else
"--\($key) \(.value | escape_shell_word)"
end;
[ to_args ] | join(" ")
EOF
)"
args="$(echo "$tool_data" | jq -r "$jq_script" 2>/dev/null)" || {
die "error: invalid JSON data"
}
if [[ -z "$LLM_OUTPUT" ]]; then
is_temp_llm_output=1
export LLM_OUTPUT="$(mktemp)"
fi
eval "'$tool_path' $args"
if [[ "$is_temp_llm_output" -eq 1 ]]; then
cat "$LLM_OUTPUT"
else
dump_result "$tool_name"
fi
}
dump_result() {
if [[ "$LLM_OUTPUT" == "/dev/stdout" ]] || [[ -z "$LLM_DUMP_RESULTS" ]] || [[ ! -t 1 ]]; then
return;
fi
if grep -q -w -E "$LLM_DUMP_RESULTS" <<<"$1"; then
cat <<EOF
$(echo -e "\e[2m")----------------------
$(cat "$LLM_OUTPUT")
----------------------$(echo -e "\e[0m")
EOF
fi
}
die() {
echo "$*" >&2
exit 1
}
main "$@"

View File

@@ -9,6 +9,20 @@ set -euo pipefail
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LLM_DIR="$CLAUDE_DIR/llm-functions"
# Bootstrap : si on lance depuis le repo CrazyClaude lui-même (pas depuis .claude/)
# on recopie les fichiers sources vers .claude/ puis on rebuild les outils
if [[ "$(basename "$CLAUDE_DIR")" != ".claude" ]]; then
TARGET="${1:-$(pwd)/.claude}"
echo "==> Bootstrap : synchronisation de $CLAUDE_DIR$TARGET"
rsync -a --exclude='.git' --exclude='llm-functions' --exclude='venv' \
"$CLAUDE_DIR/" "$TARGET/"
echo "==> Rebuild des outils"
exec bash "$TARGET/update_crazyclaude.sh" "${@:2}"
fi
# On est maintenant dans .claude/, on peut continuer normalement
dry_run=0
[[ "${1:-}" == "--dry-run" ]] && dry_run=1
@@ -44,5 +58,23 @@ else
(cd "$LLM_DIR" && argc build)
fi
echo ""
echo "=== Application des overrides locaux ==="
LOCAL_TOOLS_DIR="$CLAUDE_DIR/tools-local"
if [[ -d "$LOCAL_TOOLS_DIR" ]]; then
count=0
for src in "$LOCAL_TOOLS_DIR"/*; do
[[ -f "$src" ]] || continue
name="$(basename "$src")"
dest="$CLAUDE_DIR/llm-functions/bin/$name"
cp "$src" "$dest"
chmod +x "$dest"
count=$((count + 1))
done
echo " $count override(s) appliqué(s)"
else
echo " (aucun répertoire tools-local/)"
fi
echo ""
echo "OK — outils reconstruits dans $LLM_DIR/bin/"