diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 1bc7420..87a2218 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1,22 +1,45 @@ -# Projet CrazyClaude +## Instructions pour Claude + +### Profil utilisateur +Programmeur expérimenté. Connaît parfaitement son projet, ses outils, et son environnement. + +### Comportement attendu +- Faire ce qui est demandé, directement. +- Ne pas expliquer des choses triviales ou évidentes. +- Ne pas répéter ce qui vient d'être dit. +- Faire confiance au jugement de l'utilisateur. +- Si quelque chose ne fonctionne pas, chercher le bug — ne pas réexpliquer comment utiliser l'outil. +- Avant de demander la validation d'un plan, toujours afficher la totalité du plan dans le chat. L'utilisateur est responsable du code et décide en toute connaissance de cause. ## Stack - Rust (edition 2024), Go 1.24 -- Tests : cargo test, go test -- Lint : clippy (Rust), golangci-lint (Go) +- Python 3.12, R 4.4, bash (scripts shell) +- Tests : + - Rust : `cargo test` + - Go : `go test` + - Python : `pytest` (avec coverage) + - R : `testthat` (via `Rscript -e "testthat::test_dir('tests')"`) + - bash : `bats` (Bash Automated Testing System) +- Lint : + - Rust : `clippy` + - Go : `golangci-lint` + - Python : `ruff` (ou `flake8` + `black`) + - R : `lintr` + - bash : `shellcheck` ## 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 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) Traite toi-même si : - Refactoring cross-module -- Conception d'architecture +- Conception d’architecture - Debugging avec contexte multi-fichiers - Modification de traits/interfaces publics @@ -24,3 +47,4 @@ Traite toi-même si : - Ne jamais passer `rm -rf` sans confirmation explicite - Ne jamais committer sans que les tests passent - Toujours vérifier `git diff` avant un 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 diff --git a/.claude/mcp/qwen3-mcp/agent_lm.py b/.claude/mcp/qwen3-mcp/agent_lm.py index 344e553..07ac40a 100644 --- a/.claude/mcp/qwen3-mcp/agent_lm.py +++ b/.claude/mcp/qwen3-mcp/agent_lm.py @@ -3,6 +3,7 @@ Agent loop local (~120 lignes) pour interagir avec LM Studio via l'API OpenAI-compatible. Usage : python3 .claude/mcp/qwen3-mcp/agent_lm.py --task "..." --files file1.py file2.go """ + import argparse import json import os @@ -11,8 +12,10 @@ from pathlib import Path import requests -LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions" -LMSTUDIO_MODELS_URL = "http://localhost:1248/v1/models" +SERVER = "http://localhost" +PORT = 8888 # 1248 +LMSTUDIO_URL = f"{SERVER}:{PORT}/v1/chat/completions" +LMSTUDIO_MODELS_URL = f"{SERVER}:{PORT}/v1/models" DEFAULT_MODEL = "qwen/qwen3-coder-next" SYSTEM_PROMPT = """Tu es un assistant de codage spécialisé. Tu effectues des tâches atomiques sur des fichiers source. @@ -49,7 +52,11 @@ def read_files(paths: list[str]) -> str: def call_lmstudio(task: str, file_context: str, model: str) -> str: """Envoie la tâche à LM Studio et retourne la réponse.""" - user_message = f"TÂCHE: {task}\n\nFICHIERS:\n{file_context}" if file_context else f"TÂCHE: {task}" + user_message = ( + f"TÂCHE: {task}\n\nFICHIERS:\n{file_context}" + if file_context + else f"TÂCHE: {task}" + ) payload = { "model": model, @@ -90,13 +97,14 @@ def run_lint(file_path: str) -> tuple[bool, str]: if file_path.endswith(".rs"): result = subprocess.run( - ["cargo", "clippy", "--quiet"], - capture_output=True, text=True, timeout=60 + ["cargo", "clippy", "--quiet"], capture_output=True, text=True, timeout=60 ) elif file_path.endswith(".go"): result = subprocess.run( ["golangci-lint", "run", file_path], - capture_output=True, text=True, timeout=60 + capture_output=True, + text=True, + timeout=60, ) else: return True, "no linter for this file type" @@ -106,10 +114,21 @@ def run_lint(file_path: str) -> tuple[bool, str]: return passed, output -def agent_loop(task: str, input_files: list[str], output_file: str | None, model: str, max_retries: int = 2) -> dict: +def agent_loop( + task: str, + input_files: list[str], + output_file: str | None, + model: str, + max_retries: int = 2, +) -> dict: """Boucle principale : génère, écrit, lint, corrige (max_retries fois).""" file_context = read_files(input_files) if input_files else "" - result = {"status": "failure", "files_modified": [], "summary": "", "lint": "skipped"} + result = { + "status": "failure", + "files_modified": [], + "summary": "", + "lint": "skipped", + } for attempt in range(max_retries + 1): response = call_lmstudio(task, file_context, model) @@ -133,7 +152,9 @@ def agent_loop(task: str, input_files: list[str], output_file: str | None, model if lint_ok: result["status"] = "success" - result["summary"] = f"Attempt {attempt + 1}: task completed successfully" + result["summary"] = ( + f"Attempt {attempt + 1}: task completed successfully" + ) break elif attempt < max_retries: task = f"{task}\n\nCORRECTION REQUISE (tentative {attempt + 1}):\n{lint_output}" @@ -149,10 +170,16 @@ def main(): parser = argparse.ArgumentParser(description="Agent loop local pour LM Studio") parser.add_argument("--task", required=True, help="Description de la tâche") parser.add_argument("--files", nargs="*", default=[], help="Fichiers source à lire") - parser.add_argument("--output", help="Fichier de sortie (défaut: premier fichier input)") + parser.add_argument( + "--output", help="Fichier de sortie (défaut: premier fichier input)" + ) parser.add_argument("--model", default=DEFAULT_MODEL, help="Modèle LM Studio") - parser.add_argument("--list-models", action="store_true", help="Liste les modèles disponibles") - parser.add_argument("--json", action="store_true", dest="json_output", help="Sortie JSON") + parser.add_argument( + "--list-models", action="store_true", help="Liste les modèles disponibles" + ) + parser.add_argument( + "--json", action="store_true", dest="json_output", help="Sortie JSON" + ) args = parser.parse_args() if args.list_models: diff --git a/.claude/mcp/qwen3-mcp/server.py b/.claude/mcp/qwen3-mcp/server.py index 88f3f72..8e8c1ae 100644 --- a/.claude/mcp/qwen3-mcp/server.py +++ b/.claude/mcp/qwen3-mcp/server.py @@ -3,25 +3,38 @@ MCP server stdio exposant un outil `qwen3_task`. Appelé par Claude Code via : claude mcp add --transport stdio qwen3 -- .claude/venv/bin/python3 .claude/mcp/qwen3-mcp/server.py """ -import sys + import json +import sys + import requests -LMSTUDIO_URL = "http://localhost:1248/v1/chat/completions" +SERVER = "http://localhost" +PORT = 8888 # 1248 +LMSTUDIO_URL = f"{SERVER}:{PORT}/v1/chat/completions" QWEN3_MODEL = "qwen/qwen3-coder-next" -TOOLS = [{ - "name": "qwen3_task", - "description": "Délègue une tâche de codage atomique à Qwen3-Coder via LM Studio.", - "inputSchema": { - "type": "object", - "properties": { - "task": {"type": "string", "description": "Description précise de la tâche"}, - "files": {"type": "array", "items": {"type": "string"}, "description": "Chemins des fichiers concernés"} +TOOLS = [ + { + "name": "qwen3_task", + "description": "Délègue une tâche de codage atomique à Qwen3-Coder via LM Studio.", + "inputSchema": { + "type": "object", + "properties": { + "task": { + "type": "string", + "description": "Description précise de la tâche", + }, + "files": { + "type": "array", + "items": {"type": "string"}, + "description": "Chemins des fichiers concernés", + }, + }, + "required": ["task"], }, - "required": ["task"] } -}] +] def send(obj: dict): @@ -65,15 +78,17 @@ 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": "1.0.0"}, + }, + } + ) elif method == "notifications/initialized": pass # notification, pas de réponse elif method == "tools/list": @@ -85,13 +100,40 @@ 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}"}, + } + ) for line in sys.stdin: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7206ea0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,90 @@ +# AGENTS.md + +This file documents the project's coding standards, build/test/lint commands, and operational guidelines for agentic AI assistants. + +## Build, Lint, and Test Commands + +- **Rust**: `cargo build --all-targets && cargo test --all-targets` +- **Go**: `go build ./... && go vet ./... && go test ./...` +- **Python**: `python -m pytest tests/` (single test: `python -m pytest tests/test_module.py::test_function`) +- **R**: `Rscript -e "devtools::test()"` (single test: `Rscript -e "testthat::test_file('tests/testthat/test_module.R')"`) +- **Bash**: Run tests directly or use `bash -n script.sh` for syntax check + +For all languages: run linters before committing (see hooks in `.claude/hooks/`) + +## Code Style Guidelines + +### General Principles +- Follow language-specific idioms and conventions +- Prioritize readability over cleverness +- Use descriptive names for variables, functions, and types +- Keep functions small and focused on a single responsibility + +### Imports and Dependencies +- **Rust**: Use `use` statements at top of file; group std, external crates, and local modules +- **Go**: Import blocks organized: standard library, then external packages, then local packages +- **Python**: Standard library imports first, then third-party, then local imports +- **R**: Attach packages with `library()` at top of script; use `pkg::function()` for occasional calls +- **Bash**: Source local modules with absolute paths or relative from script location + +### Naming Conventions +- **Rust**: Types/CamelCase, functions/snake_case, constants/SCREAMING_SNAKE_CASE +- **Go**: Exported names start with uppercase, unexported with lowercase; use CamelCase for acronyms +- **Python**: Classes/CamelCase, functions/variables/snake_case; use _private for internal +- **R**: Functions/lowercase_with_underscores; avoid naming conflicts with base R functions +- **Bash**: Functions/snake_case; variables lowercase; constants UPPERCASE + +### Error Handling +- **Rust**: Use `Result` and `?` operator; define custom error types for libraries +- **Go**: Return `(result, error)` tuple; handle errors immediately after function call +- **Python**: Raise specific exception types; catch only what you can handle meaningfully +- **R**: Use `stop()` for errors, `warning()` for warnings; consider tryCatch for recovery +- **Bash**: Check return codes with `$?`; use `set -e` for strict error handling + +### Types and Documentation +- **Rust**: Specify all types explicitly; use `cargo doc` for documentation +- **Go**: Document exported functions with comments; use type aliases for clarity +- **Python**: Use type hints (PEP 484); docstrings with Google or NumPy style +- **R**: Use roxygen2 for function documentation; document all exported functions +- **Bash**: Comment complex logic; use `set -x` for debugging trace + +## Cursor Rules + +- Run linters after any file write (see `.claude/hooks/post-write-lint.sh`) +- For Rust/Go projects, run `cargo clippy` or `go vet` before committing +- Verify tests pass after changes: `cargo test`, `go test`, `pytest` +- When modifying multiple files, run full test suite before finalizing + +## Copilot Rules + +- Delegation: Use Qwen3-Coder for atomic tasks via MCP server at port 1248 +- Code review: Invoke code-reviewer agent for PRs and significant changes +- Planning: Use task-planner to break complex tasks into atomic steps +- Always verify agent outputs before committing or pushing + +## Hooks and Automation + +- **post-write-lint.sh**: Runs linters after file writes +- **subagent-stop-log.sh**: Logs subagent completion and exit codes +- **pre-bash-guard.sh**: Validates bash scripts before execution + +## MCP Server Configuration + +- **Endpoint**: `http://localhost:1248` +- **Model**: `qwen/qwen3-coder-next` +- **Local agent loop**: `.claude/mcp/qwen3-mcp/agent_lm.py` +- **Server script**: `.claude/mcp/qwen3-mcp/server.py` + +## Project Structure + +- `.claude/agents/` - Agent definitions (qwen3-worker, code-reviewer, task-planner) +- `.claude/skills/` - Domain-specific skills and conventions +- `.claude/hooks/` - Git hooks for automated checks +- `.claude/mcp/qwen3-mcp/` - MCP server and agent integration + +## Testing Strategy + +- Run tests after any code change +- For single test execution, use language-specific commands above +- CI/CD should run full test suite on every push +- Code coverage targets: 80% minimum for production code diff --git a/README.md b/README.md index eab0a8d..6bd61b9 100644 --- a/README.md +++ b/README.md @@ -151,3 +151,12 @@ Ou si vous avez cloné séparément, tirez les changements puis recopiez : cd .claude-crazy && jj git fetch && jj new main cp -r .claude/* ../.claude/ ``` + + +## Outils necessaire + +### aichat + +### jq + +### argc