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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,7 +1,9 @@
|
||||
venv/
|
||||
logs/
|
||||
test_qwen3_output/
|
||||
llm-functions/bin/
|
||||
llm-functions/cache/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.claude
|
||||
.claude/
|
||||
|
||||
@@ -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 d’outils autonomes (filesystem, shell, web) via `qwen3_task`. Il lit et écrit
|
||||
Délègue en appelant **directement** l’outil MCP `mcp__qwen3__task` — ne pas passer par
|
||||
un sous-agent (Agent tool), car les sous-agents n’ont 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
|
||||
@@ -55,4 +59,4 @@ En cas de doute : délègue.
|
||||
- Ne jamais committer sans que les tests passent
|
||||
- Toujours vérifier `git diff` avant un commit
|
||||
- Ne jamais ajouter de `Co-Authored-By: Claude` ou toute mention d'IA dans les messages de commit
|
||||
- Pour le bash : ne jamais exécuter de commande avec `eval` non contrôlé ; privilégier l’utilisation de `shellcheck` pour détecter les erreurs courantes
|
||||
- Pour le bash : ne jamais exécuter de commande avec `eval` non contrôlé ; privilégier l’utilisation de `shellcheck` pour détecter les erreurs courantes
|
||||
@@ -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```"
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 :
|
||||
@@ -22,4 +22,4 @@ PLAN:
|
||||
2. ...
|
||||
RISQUES: liste
|
||||
POINTS_DE_CONTRÔLE: liste
|
||||
```
|
||||
```
|
||||
28
install.sh
28
install.sh
@@ -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 ""
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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.
|
||||
@@ -40,4 +43,4 @@ faire des recherches web et exécuter du code de façon autonome.
|
||||
- Refactoring impliquant une vision cross-module
|
||||
- Changement d'architecture ou de design pattern
|
||||
- Debugging avec stacktrace à analyser sur plusieurs fichiers
|
||||
- Tâches nécessitant le contexte de la conversation en cours
|
||||
- Tâches nécessitant le contexte de la conversation en cours
|
||||
46
skills/test_qwen3.md
Normal file
46
skills/test_qwen3.md
Normal 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
73
test_qwen3.sh
Executable 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
9
tools-local/README.md
Normal 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
119
tools-local/run-tool.sh
Executable 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 "$@"
|
||||
@@ -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
|
||||
|
||||
@@ -45,4 +59,22 @@ else
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "OK — outils reconstruits dans $LLM_DIR/bin/"
|
||||
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/"
|
||||
Reference in New Issue
Block a user