From 7d413a3d6d43675e4750b9e8f7cd2bab5e7d09c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Sat, 7 Mar 2026 18:52:22 +0100 Subject: [PATCH] Adopt KiCad 10-first CAD runtime stack --- .../docs/AGENTIC_LANDSCAPE.md | 2 +- ai-agentic-embedded-base/docs/BLOCKS.md | 4 +- .../docs/HARDWARE_QUICKSTART.md | 4 +- .../docs/KICAD_AI_LOCAL.md | 53 +--- ai-agentic-embedded-base/docs/MCP_SETUP.md | 35 +-- ai-agentic-embedded-base/hardware/README.md | 2 +- .../hardware/blocks/README.md | 2 +- .../tools/hw/kicad_cli.sh | 4 +- .../tools/hw/schops/README.md | 4 +- .../tools/hw/schops/schops.py | 6 +- deploy/cad/Dockerfile.freecad-headless | 11 + deploy/cad/Dockerfile.kicad-mcp | 38 +++ deploy/cad/Dockerfile.platformio | 15 + deploy/cad/README.md | 39 +++ deploy/cad/docker-compose.yml | 39 +++ docs/AGENTIC_LANDSCAPE.md | 2 +- docs/BLOCKS.md | 4 +- docs/HARDWARE_QUICKSTART.md | 4 +- docs/KICAD_AI_LOCAL.md | 36 ++- docs/MASCARADE_BRIDGE.md | 164 ++++++++++ docs/MCP_SETUP.md | 77 +++-- docs/QUICKSTART.md | 34 ++- docs/RUNBOOK.md | 21 +- hardware/README.md | 2 +- hardware/blocks/README.md | 2 +- mcp.json | 8 +- tools/hw/cad_stack.sh | 279 ++++++++++++++++++ tools/hw/hw_check.sh | 6 +- tools/hw/hw_gate.sh | 8 +- tools/hw/kicad_cli.sh | 4 +- tools/hw/mcp_smoke.py | 163 ++++++++++ tools/hw/run_kicad_mcp.sh | 264 +++++++++++++++++ tools/hw/schops/README.md | 4 +- tools/hw/schops/schops.py | 6 +- 34 files changed, 1193 insertions(+), 153 deletions(-) create mode 100644 deploy/cad/Dockerfile.freecad-headless create mode 100644 deploy/cad/Dockerfile.kicad-mcp create mode 100644 deploy/cad/Dockerfile.platformio create mode 100644 deploy/cad/README.md create mode 100644 deploy/cad/docker-compose.yml create mode 100644 docs/MASCARADE_BRIDGE.md create mode 100755 tools/hw/cad_stack.sh create mode 100755 tools/hw/mcp_smoke.py create mode 100755 tools/hw/run_kicad_mcp.sh diff --git a/ai-agentic-embedded-base/docs/AGENTIC_LANDSCAPE.md b/ai-agentic-embedded-base/docs/AGENTIC_LANDSCAPE.md index f6f63ef..7f76d25 100644 --- a/ai-agentic-embedded-base/docs/AGENTIC_LANDSCAPE.md +++ b/ai-agentic-embedded-base/docs/AGENTIC_LANDSCAPE.md @@ -4,7 +4,7 @@ - Standards injection: Agent OS (standards versionnés + profils) citeturn0search3 - Role workflows + gates: BMAD-METHOD - Tool-first runtime (local): Agent Zero -- Interop tools: MCP (ex: kicad-sch-mcp) citeturn0search9 +- Interop tools: MCP local en `stdio` via le runtime `kicad-mcp` Le repo fournit : - `specs/` pour la source de vérité diff --git a/ai-agentic-embedded-base/docs/BLOCKS.md b/ai-agentic-embedded-base/docs/BLOCKS.md index ac851aa..a2aa661 100644 --- a/ai-agentic-embedded-base/docs/BLOCKS.md +++ b/ai-agentic-embedded-base/docs/BLOCKS.md @@ -1,6 +1,6 @@ -# Design Blocks (KiCad 9) + registry +# Design Blocks (KiCad 10) + registry Les briques se stockent sous `hardware/blocks/**.kicad_block/` (avec un `.kicad_sch` + un `.json` metadata). Un registry est généré automatiquement : `hardware/blocks/REGISTRY.md`. -Référence : KiCad 9 Design Blocks. citeturn0search8 +Référence : KiCad 10 Design Blocks. citeturn0search8 diff --git a/ai-agentic-embedded-base/docs/HARDWARE_QUICKSTART.md b/ai-agentic-embedded-base/docs/HARDWARE_QUICKSTART.md index d7bc18f..3effd29 100644 --- a/ai-agentic-embedded-base/docs/HARDWARE_QUICKSTART.md +++ b/ai-agentic-embedded-base/docs/HARDWARE_QUICKSTART.md @@ -1,7 +1,7 @@ -# Hardware quickstart (KiCad) +# Hardware quickstart (KiCad 10) ## Prérequis -- KiCad 9 installé (inclut `kicad-cli`) +- KiCad 10 installé ou une image Docker KiCad 10 compatible (inclut `kicad-cli`) - Python 3.11+ - (optionnel) venv diff --git a/ai-agentic-embedded-base/docs/KICAD_AI_LOCAL.md b/ai-agentic-embedded-base/docs/KICAD_AI_LOCAL.md index bf0bdd3..fcd174c 100644 --- a/ai-agentic-embedded-base/docs/KICAD_AI_LOCAL.md +++ b/ai-agentic-embedded-base/docs/KICAD_AI_LOCAL.md @@ -1,51 +1,6 @@ -# IA & KiCad en local (bulk edits + analyse) +# IA & KiCad en local -Ce template privilégie **deux couches** complémentaires : +Source canonique: `../../docs/KICAD_AI_LOCAL.md` -1) **schops** (ce repo) : un CLI simple, traçable, qui fait - - exports déterministes via `kicad-cli` (ERC / BOM / netlist) - - bulk edits via `kicad-sch-api` (fields / footprints / labels) - - packaging de Design Blocks KiCad 9 - -2) **MCP (optionnel)** : si tu utilises un client IA compatible MCP, tu peux exposer - des opérations KiCad comme un “tool server” local. - -## 1) schops - -Install : -```bash -python -m venv .venv && source .venv/bin/activate -pip install -r tools/hw/schops/requirements.txt -``` - -Workflow typique : -```bash -python tools/hw/schops/schops.py snapshot --schematic <...> --name before.json -python tools/hw/schops/schops.py apply-fields --schematic <...> --rules hardware/rules/fields.yaml -python tools/hw/schops/schops.py apply-footprints --schematic <...> --map hardware/rules/footprints.csv -python tools/hw/schops/schops.py rename-nets --schematic <...> --rules hardware/rules/nets_rename.yaml -python tools/hw/schops/schops.py erc --schematic <...> -python tools/hw/schops/schops.py bom --schematic <...> --exclude-dnp -python tools/hw/schops/schops.py netlist --schematic <...> -python tools/hw/schops/schops.py snapshot --schematic <...> --name after.json -``` - -Tous les rapports vont dans `artifacts/hw//`. - -## 2) MCP KiCad (optionnel) - -Si ton client IA supporte MCP, installe un serveur MCP KiCad basé sur `kicad-sch-api` : - -```bash -pip install kicad-sch-api kicad-sch-mcp - -# démarre le serveur (stdio) -kicad-sch-mcp -``` - -### Convention d’intégration recommandée - -- **Édits mécaniques** → `schops` (backup + report) -- **Création de schéma / placement** (si besoin) → MCP + validation ensuite via `schops` + `kicad-cli` - -> Même avec MCP, garde `kicad-cli` en “source de vérité” pour ERC/BOM/netlist. +Le runtime MCP supporté n’est plus décrit localement dans ce miroir pour éviter les divergences. +Utiliser le launcher `Kill_LIFE/tools/hw/run_kicad_mcp.sh` et la procédure de `../../docs/MCP_SETUP.md`. diff --git a/ai-agentic-embedded-base/docs/MCP_SETUP.md b/ai-agentic-embedded-base/docs/MCP_SETUP.md index 98253ef..9f17ebe 100644 --- a/ai-agentic-embedded-base/docs/MCP_SETUP.md +++ b/ai-agentic-embedded-base/docs/MCP_SETUP.md @@ -1,35 +1,6 @@ # MCP setup (KiCad) -## Option A — Schematic MCP (recommended) -`kicad-sch-api` inclut un serveur MCP : `kicad-sch-mcp`. citeturn0search9 +Source canonique: `../../docs/MCP_SETUP.md` -Installation : -```bash -pip install kicad-sch-api -# ou via uv -# uv tool install kicad-sch-mcp -``` - -Lancer le serveur (dans le repo) : -```bash -kicad-sch-mcp -``` - -Exemple (Claude Desktop) — à adapter selon ton OS : -```json -{ - "mcpServers": { - "kicad_schematic": { - "command": "kicad-sch-mcp", - "args": [] - } - } -} -``` - -## Option B — KiCad “live/PCB” MCP (expérimental) -Il existe des serveurs MCP orientés PCB / IPC API (dépend de ta version KiCad et du serveur choisi). citeturn0search1turn0search16 - -Dans ce repo, l’approche “robuste” reste : -- bulk edits schéma via `kicad-sch-api` -- exports/DRC via `kicad-cli` +Ce dépôt miroir ne maintient plus une copie indépendante de la procédure MCP. +Le chemin supporté reste le launcher `Kill_LIFE/tools/hw/run_kicad_mcp.sh`, qui délègue au serveur KiCad MCP versionné dans le repo compagnon `mascarade`. diff --git a/ai-agentic-embedded-base/hardware/README.md b/ai-agentic-embedded-base/hardware/README.md index 38c9fcf..cb725cc 100644 --- a/ai-agentic-embedded-base/hardware/README.md +++ b/ai-agentic-embedded-base/hardware/README.md @@ -2,7 +2,7 @@ - `kicad/` : projets KiCad - `rules/` : règles champs/footprints/nets -- `blocks/` : Design Blocks KiCad 9 (bibliothèque de briques) +- `blocks/` : Design Blocks KiCad 10 (bibliothèque de briques) ⚠️ Les fichiers KiCad réels sont à créer/committer depuis ton poste. Ce template fournit l’outillage (schops + gates + CI). diff --git a/ai-agentic-embedded-base/hardware/blocks/README.md b/ai-agentic-embedded-base/hardware/blocks/README.md index 66c8d20..240d97e 100644 --- a/ai-agentic-embedded-base/hardware/blocks/README.md +++ b/ai-agentic-embedded-base/hardware/blocks/README.md @@ -1,4 +1,4 @@ -# Design Blocks (KiCad 9) +# Design Blocks (KiCad 10) Créer une brique : - isoler un sous-schéma stable (ex: régulateur 3V3) diff --git a/ai-agentic-embedded-base/tools/hw/kicad_cli.sh b/ai-agentic-embedded-base/tools/hw/kicad_cli.sh index 8cabe02..c514c5c 100644 --- a/ai-agentic-embedded-base/tools/hw/kicad_cli.sh +++ b/ai-agentic-embedded-base/tools/hw/kicad_cli.sh @@ -7,7 +7,7 @@ set -euo pipefail # # Env: # KICAD_CLI_BIN: override local path -# KICAD_DOCKER_IMAGE: override docker image (default: kicad/kicad:9.0.7-full) +# KICAD_DOCKER_IMAGE: override docker image (default: kicad/kicad:nightly) BIN="${KICAD_CLI_BIN:-}" if [[ -z "$BIN" ]]; then @@ -28,7 +28,7 @@ if ! command -v docker >/dev/null 2>&1; then exit 127 fi -IMG="${KICAD_DOCKER_IMAGE:-kicad/kicad:9.0.7-full}" +IMG="${KICAD_DOCKER_IMAGE:-kicad/kicad:nightly}" # run as current user to avoid root-owned artifacts UIDGID="$(id -u):$(id -g)" diff --git a/ai-agentic-embedded-base/tools/hw/schops/README.md b/ai-agentic-embedded-base/tools/hw/schops/README.md index 338619d..dba7579 100644 --- a/ai-agentic-embedded-base/tools/hw/schops/README.md +++ b/ai-agentic-embedded-base/tools/hw/schops/README.md @@ -3,7 +3,7 @@ CLI local pour : - ERC/BOM/netlist via `kicad-cli` - bulk edits via `kicad-sch-api` (si installé) -- Design Blocks KiCad 9 (structure + metadata) +- Design Blocks KiCad 10 (structure + metadata) > Philosophie : **bulk edits safe** (backup + report) + exports déterministes (kicad-cli). @@ -66,7 +66,7 @@ python tools/hw/schops/schops.py snapshot --schematic ... --name before.json python tools/hw/schops/schops.py snapshot --schematic ... --name after.json ``` -## Design Blocks (KiCad 9) +## Design Blocks (KiCad 10) Les design blocks sont des dossiers `*.kicad_block` stockés dans une librairie `*.kicad_blocks`. ```bash diff --git a/ai-agentic-embedded-base/tools/hw/schops/schops.py b/ai-agentic-embedded-base/tools/hw/schops/schops.py index 7196f7b..8ffc36f 100644 --- a/ai-agentic-embedded-base/tools/hw/schops/schops.py +++ b/ai-agentic-embedded-base/tools/hw/schops/schops.py @@ -4,7 +4,7 @@ Goals: - deterministic exports via kicad-cli (ERC / netlist / BOM) - safe bulk edits via kicad-sch-api (fields / footprints / net labels) - - Design Blocks (KiCad 9) helpers + - Design Blocks (KiCad 10) helpers This tool is intentionally conservative: - it always writes an artifacts report @@ -512,7 +512,7 @@ def cmd_snapshot(args) -> int: # --------------------------- -# Design Blocks (KiCad 9) +# Design Blocks (KiCad 10) # --------------------------- @@ -643,7 +643,7 @@ def build_parser() -> argparse.ArgumentParser: p.add_argument("--name", help="output filename (default snapshot.json)") p.set_defaults(fn=cmd_snapshot) - p = sub.add_parser("block-make", help="Create a KiCad 9 design block folder") + p = sub.add_parser("block-make", help="Create a KiCad 10 design block folder") p.add_argument("--name", required=True) p.add_argument("--from-sheet", required=True, help=".kicad_sch file to package as a block") p.add_argument("--lib", required=True, help="Design blocks library folder (usually *.kicad_blocks)") diff --git a/deploy/cad/Dockerfile.freecad-headless b/deploy/cad/Dockerfile.freecad-headless new file mode 100644 index 0000000..5e603c5 --- /dev/null +++ b/deploy/cad/Dockerfile.freecad-headless @@ -0,0 +1,11 @@ +FROM debian:bookworm-slim + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install -y --no-install-recommends freecad-python3 ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace + +CMD ["sleep", "infinity"] diff --git a/deploy/cad/Dockerfile.kicad-mcp b/deploy/cad/Dockerfile.kicad-mcp new file mode 100644 index 0000000..381c39a --- /dev/null +++ b/deploy/cad/Dockerfile.kicad-mcp @@ -0,0 +1,38 @@ +ARG KICAD_MCP_BASE_IMAGE=kicad/kicad:nightly-full +FROM ${KICAD_MCP_BASE_IMAGE} + +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +USER root + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + nodejs \ + npm \ + python3-pip \ + python3-venv \ + && rm -rf /var/lib/apt/lists/* \ + && python3 -m venv /opt/kicad-mcp/venv \ + && /opt/kicad-mcp/venv/bin/pip install --no-cache-dir --upgrade pip + +WORKDIR /opt/kicad-mcp/server + +COPY requirements.txt /tmp/kicad-mcp-requirements.txt +RUN /opt/kicad-mcp/venv/bin/pip install --no-cache-dir -r /tmp/kicad-mcp-requirements.txt + +COPY package.json package-lock.json tsconfig.json ./ +RUN npm ci --ignore-scripts + +COPY src ./src +COPY python ./python +RUN npx tsc --declaration false --sourceMap false --skipLibCheck + +ENV PATH=/opt/kicad-mcp/venv/bin:${PATH} \ + KICAD_PYTHON=/opt/kicad-mcp/venv/bin/python + +WORKDIR /workspace + +CMD ["sleep", "infinity"] diff --git a/deploy/cad/Dockerfile.platformio b/deploy/cad/Dockerfile.platformio new file mode 100644 index 0000000..4891d13 --- /dev/null +++ b/deploy/cad/Dockerfile.platformio @@ -0,0 +1,15 @@ +FROM python:3.12-slim + +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends build-essential ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + && pip install --no-cache-dir --upgrade pip \ + && pip install --no-cache-dir platformio + +WORKDIR /workspace + +CMD ["sleep", "infinity"] diff --git a/deploy/cad/README.md b/deploy/cad/README.md new file mode 100644 index 0000000..2f67a9a --- /dev/null +++ b/deploy/cad/README.md @@ -0,0 +1,39 @@ +# CAD Stack + +Stack Docker CAD/EDA intégrée directement dans `Kill_LIFE`. + +## Direction retenue + +- `KiCad headless`: `kicad-cli` via une image KiCad 10 (`kicad/kicad:nightly` par défaut) +- `KiCad MCP`: `kicad-sch-mcp` en `stdio` +- `FreeCAD headless`: `FreeCADCmd` +- `PlatformIO`: `pio` dans un conteneur Python léger + +## Usage rapide + +```bash +tools/hw/cad_stack.sh up +tools/hw/cad_stack.sh doctor +tools/hw/cad_stack.sh kicad-cli version +tools/hw/cad_stack.sh freecad-cmd -c "import FreeCAD; print(FreeCAD.Version())" +tools/hw/cad_stack.sh pio system info +tools/hw/cad_stack.sh mcp +``` + +Le workspace monté dans les conteneurs est la racine de `Kill_LIFE` par défaut. +`tools/hw/cad_stack.sh build ...` redéploie aussi automatiquement les services persistants reconstruits, pour éviter de garder un ancien conteneur sur une image obsolète. + +Variables utiles : + +- `KICAD_DOCKER_IMAGE` : image KiCad 10 à utiliser. Par défaut, la stack suit la branche `nightly` tant qu’une image stable 10.x n’est pas explicitement épinglée. +- `CAD_WORKSPACE_DIR` : workspace monté dans `/workspace` + +## Note MCP + +Le choix ici est volontairement sobre : + +- `stdio` pour l’usage local +- `kicad-sch-api` pour le schématique via `kicad-sch-mcp` +- `kicad-cli` comme source de vérité pour les exports et checks + +Si un serveur MCP distant devient nécessaire plus tard, il faudra ajouter un vrai transport réseau adapté, pas détourner le mode local. diff --git a/deploy/cad/docker-compose.yml b/deploy/cad/docker-compose.yml new file mode 100644 index 0000000..e9aa670 --- /dev/null +++ b/deploy/cad/docker-compose.yml @@ -0,0 +1,39 @@ +name: kill_life_cad + +services: + kicad-headless: + image: ${KICAD_DOCKER_IMAGE:-kicad/kicad:nightly-full} + working_dir: /workspace + command: ["sleep", "infinity"] + volumes: + - ${CAD_WORKSPACE_DIR:-../..}:/workspace + + kicad-mcp: + image: ${KICAD_MCP_IMAGE:-kill_life_cad-kicad-mcp:latest} + build: + context: ${KICAD_MCP_CONTEXT_DIR:-../../../mascarade/finetune/kicad_mcp_server} + dockerfile: ../../../Kill_LIFE/deploy/cad/Dockerfile.kicad-mcp + args: + KICAD_MCP_BASE_IMAGE: ${KICAD_DOCKER_IMAGE:-kicad/kicad:nightly-full} + working_dir: ${CAD_WORKSPACE_DIR:-/workspace} + command: ["sleep", "infinity"] + volumes: + - ${CAD_HOST_ROOT:-/tmp/cad-host}:${CAD_HOST_ROOT:-/tmp/cad-host} + + freecad-headless: + build: + context: . + dockerfile: Dockerfile.freecad-headless + working_dir: /workspace + command: ["sleep", "infinity"] + volumes: + - ${CAD_WORKSPACE_DIR:-../..}:/workspace + + platformio: + build: + context: . + dockerfile: Dockerfile.platformio + working_dir: /workspace + command: ["sleep", "infinity"] + volumes: + - ${CAD_WORKSPACE_DIR:-../..}:/workspace diff --git a/docs/AGENTIC_LANDSCAPE.md b/docs/AGENTIC_LANDSCAPE.md index f6f63ef..7f76d25 100644 --- a/docs/AGENTIC_LANDSCAPE.md +++ b/docs/AGENTIC_LANDSCAPE.md @@ -4,7 +4,7 @@ - Standards injection: Agent OS (standards versionnés + profils) citeturn0search3 - Role workflows + gates: BMAD-METHOD - Tool-first runtime (local): Agent Zero -- Interop tools: MCP (ex: kicad-sch-mcp) citeturn0search9 +- Interop tools: MCP local en `stdio` via le runtime `kicad-mcp` Le repo fournit : - `specs/` pour la source de vérité diff --git a/docs/BLOCKS.md b/docs/BLOCKS.md index ac851aa..a2aa661 100644 --- a/docs/BLOCKS.md +++ b/docs/BLOCKS.md @@ -1,6 +1,6 @@ -# Design Blocks (KiCad 9) + registry +# Design Blocks (KiCad 10) + registry Les briques se stockent sous `hardware/blocks/**.kicad_block/` (avec un `.kicad_sch` + un `.json` metadata). Un registry est généré automatiquement : `hardware/blocks/REGISTRY.md`. -Référence : KiCad 9 Design Blocks. citeturn0search8 +Référence : KiCad 10 Design Blocks. citeturn0search8 diff --git a/docs/HARDWARE_QUICKSTART.md b/docs/HARDWARE_QUICKSTART.md index 701e959..bb4abfe 100644 --- a/docs/HARDWARE_QUICKSTART.md +++ b/docs/HARDWARE_QUICKSTART.md @@ -1,12 +1,12 @@ # Easter Egg musique concrète _« Le hardware s’écoute, se manipule, se transforme : comme Bernard Parmegiani, chaque export est une métamorphose électronique. »_ -# Hardware quickstart (KiCad) +# Hardware quickstart (KiCad 10) > "Chaque bloc hardware est une évolution, chaque bulk edit une adaptation, et chaque export une trace laissée pour les générations futures. (Adrian Tchaikovsky, Children of Time)" ## Prérequis -- KiCad 9 installé (inclut `kicad-cli`) +- KiCad 10 installé ou une image Docker KiCad 10 compatible (inclut `kicad-cli`) - Python 3.11+ - (optionnel) venv diff --git a/docs/KICAD_AI_LOCAL.md b/docs/KICAD_AI_LOCAL.md index bf0bdd3..442b5c2 100644 --- a/docs/KICAD_AI_LOCAL.md +++ b/docs/KICAD_AI_LOCAL.md @@ -5,7 +5,7 @@ Ce template privilégie **deux couches** complémentaires : 1) **schops** (ce repo) : un CLI simple, traçable, qui fait - exports déterministes via `kicad-cli` (ERC / BOM / netlist) - bulk edits via `kicad-sch-api` (fields / footprints / labels) - - packaging de Design Blocks KiCad 9 + - packaging de Design Blocks KiCad 10 2) **MCP (optionnel)** : si tu utilises un client IA compatible MCP, tu peux exposer des opérations KiCad comme un “tool server” local. @@ -34,13 +34,11 @@ Tous les rapports vont dans `artifacts/hw//`. ## 2) MCP KiCad (optionnel) -Si ton client IA supporte MCP, installe un serveur MCP KiCad basé sur `kicad-sch-api` : +Si ton client IA supporte MCP, le chemin supporté dans ce workspace est le launcher local de `Kill_LIFE`, branché sur le serveur `mascarade/finetune/kicad_mcp_server` : ```bash -pip install kicad-sch-api kicad-sch-mcp - -# démarre le serveur (stdio) -kicad-sch-mcp +tools/hw/run_kicad_mcp.sh --doctor +tools/hw/run_kicad_mcp.sh ``` ### Convention d’intégration recommandée @@ -49,3 +47,29 @@ kicad-sch-mcp - **Création de schéma / placement** (si besoin) → MCP + validation ensuite via `schops` + `kicad-cli` > Même avec MCP, garde `kicad-cli` en “source de vérité” pour ERC/BOM/netlist. + +## 3) Stack locale intégrée Kill_LIFE + +`Kill_LIFE` embarque maintenant directement une couche d’exécution locale : + +- `kicad-headless` via une image KiCad 10 compatible (`kicad/kicad:nightly` par défaut) +- `kicad-mcp` en `stdio` via le launcher `tools/hw/run_kicad_mcp.sh` +- `freecad-headless` via `FreeCADCmd` +- `platformio` via `pio` + +Le point important est le suivant : + +- `stdio` reste le bon transport MCP en local +- `Streamable HTTP` ne doit être ajouté que pour un vrai serveur distant + +Le détail du pont entre les deux repos est documenté dans [MASCARADE_BRIDGE.md](MASCARADE_BRIDGE.md). + +La configuration MCP versionnée est documentée dans [MCP_SETUP.md](MCP_SETUP.md). + +Point d’entrée pratique côté `Kill_LIFE` : + +```bash +tools/hw/cad_stack.sh doctor +tools/hw/cad_stack.sh kicad-cli version +tools/hw/cad_stack.sh mcp +``` diff --git a/docs/MASCARADE_BRIDGE.md b/docs/MASCARADE_BRIDGE.md new file mode 100644 index 0000000..b43d95f --- /dev/null +++ b/docs/MASCARADE_BRIDGE.md @@ -0,0 +1,164 @@ +# Pont Kill_LIFE <-> Mascarade + +Ce document rattache les décisions et implémentations produites pendant la session de travail locale au repo `Kill_LIFE`. + +## But + +Garder une séparation propre entre : + +- `Kill_LIFE` : repo de méthode, specs, gates, evidence packs, workflows hardware/firmware. +- `Mascarade` : repo compagnon d’exécution locale pour orchestration LLM, distillation/fine-tuning, et outillage CAD conteneurisé. + +En local, les deux dépôts sont côte à côte : + +- `Kill_LIFE` : `../Kill_LIFE` +- `Mascarade` : `../mascarade` + +## Ce qui a été mis en place côté Mascarade + +### 1. Stack CAD/EDA locale + +La stack CAD/EDA a maintenant été intégrée directement dans `Kill_LIFE` : + +- `deploy/cad/docker-compose.yml` +- `tools/hw/cad_stack.sh` + +Elle fournit : + +- `kicad-headless` via `kicad-cli` sur une image KiCad 10 compatible +- `kicad-mcp` en transport `stdio`, lancé via `tools/hw/run_kicad_mcp.sh` +- `freecad-headless` via `FreeCADCmd` +- `platformio` via CLI `pio` + +Le repo `Mascarade` reste utile comme repo compagnon pour : + +- orchestration LLM +- distillation / fine-tuning +- modèles students spécialisés + +### 2. Pipeline local teacher -> student + +Le repo compagnon contient aussi un pipeline local de distillation et fine-tuning : + +- `../mascarade/finetune/distill_dataset.py` +- `../mascarade/finetune/distill_and_train.py` +- `../mascarade/finetune/run_local.py` +- `../mascarade/finetune/batch_local.py` + +Domaines déjà traités ou préparés : + +- `kicad` +- `freecad` +- `platformio` +- `esp32/iot` +- `spice` + +Ce pipeline sert à produire de petits modèles spécialisés exploitables localement pour les agents `HW`, `Firmware`, `Doc` et `QA`. + +## Répartition recommandée des rôles + +| Besoin | Repo recommandé | Raison | +|---|---|---| +| Specs, ADR, gates, evidence packs | `Kill_LIFE` | c’est la source de vérité méthodologique | +| Exécution LLM locale et orchestration provider/model | `Mascarade` | c’est le cockpit d’exécution | +| KiCad/FreeCAD/PlatformIO en conteneur | `Kill_LIFE` | stack CAD désormais intégrée localement | +| Datasets, distillation, adapters LoRA/QLoRA | `Mascarade` | pipeline de training déjà opérationnel | +| Rapports finaux, runbooks, conformité | `Kill_LIFE` | cohérence des artefacts et traçabilité | + +## Comment relier les deux dépôts + +### Pour le hardware / CAD + +`Kill_LIFE` garde : + +- les règles +- les schémas +- les evidence packs +- les handoffs + +`Kill_LIFE` exécute maintenant directement : + +- `kicad-cli` +- le serveur `kicad-mcp` +- `FreeCADCmd` +- `pio` + +Le flux recommandé est : + +1. préparer la spec et les critères d’acceptation dans `Kill_LIFE` +2. exécuter les outils CAD/EDA dans `Kill_LIFE` +3. rapatrier les rapports, exports et décisions dans `Kill_LIFE/docs/` et `Kill_LIFE/artifacts/` + +### Pour les agents IA spécialisés + +`Kill_LIFE` fournit la matière de domaine : + +- `specs/` +- `docs/` +- `hardware/` +- `tools/` +- conventions et profils compliance + +`Mascarade` transforme cette matière en : + +- datasets `ShareGPT JSONL` +- distillation teacher -> student +- adapters LoRA/QLoRA locaux + +Le flux recommandé est : + +1. extraire ou générer des datasets depuis `Kill_LIFE` +2. entraîner les students côté `Mascarade` +3. réinjecter l’usage de ces students dans les workflows agents de `Kill_LIFE` + +## Alignement avec l’état de l’art au 6 mars 2026 + +Points retenus pendant la recherche : + +- `kicad-cli` est la voie officielle pour le headless KiCad +- pour MCP, `stdio` reste le bon choix en local +- `Streamable HTTP` ne doit être ajouté que si on expose un vrai serveur distant +- `FreeCADCmd` est le mode headless propre pour FreeCAD +- `PlatformIO` reste le plus simple à conteneuriser via CLI Python + +Conséquence pour `Kill_LIFE` : + +- ne pas mélanger la méthode et la couche d’exécution +- documenter `Mascarade` comme repo compagnon, pas comme dépendance cachée +- garder les evidence packs et la gouvernance dans `Kill_LIFE` + +## Statut actuel + +L’étape d’intégration côté `Kill_LIFE` est maintenant réalisée. + +La couche CAD/EDA locale vit directement dans ce dépôt : + +- `deploy/cad/docker-compose.yml` +- `deploy/cad/Dockerfile.kicad-mcp` +- `deploy/cad/Dockerfile.freecad-headless` +- `deploy/cad/Dockerfile.platformio` +- `tools/hw/cad_stack.sh` + +## Intégration actuelle dans Kill_LIFE + +Le dépôt `Kill_LIFE` expose maintenant un launcher local natif : + +- `tools/hw/cad_stack.sh` +- `tools/hw/run_kicad_mcp.sh` + +Ce launcher : + +- monte `Kill_LIFE` comme workspace par défaut +- pilote directement `deploy/cad/docker-compose.yml` +- évite de cacher une dépendance implicite vers `Mascarade` +- laisse `Mascarade` jouer son rôle de repo compagnon pour LLM local et fine-tuning + +Exemples : + +```bash +tools/hw/cad_stack.sh doctor +tools/hw/cad_stack.sh kicad-cli version +tools/hw/cad_stack.sh pio system info +tools/hw/run_kicad_mcp.sh --doctor +tools/hw/cad_stack.sh mcp +``` diff --git a/docs/MCP_SETUP.md b/docs/MCP_SETUP.md index 98253ef..61b7b23 100644 --- a/docs/MCP_SETUP.md +++ b/docs/MCP_SETUP.md @@ -1,35 +1,74 @@ # MCP setup (KiCad) -## Option A — Schematic MCP (recommended) -`kicad-sch-api` inclut un serveur MCP : `kicad-sch-mcp`. citeturn0search9 +Source canonique pour la configuration MCP locale de `Kill_LIFE`. + +Spec de perimetre: + +- `specs/kicad_mcp_scope_spec.md` + +## Chemin supporté + +- Serveur MCP supporté: `mascarade/finetune/kicad_mcp_server` +- Launcher supporté côté `Kill_LIFE`: `tools/hw/run_kicad_mcp.sh` +- Transport supporté: `stdio` local uniquement + +`kicad-sch-mcp` n’est plus le chemin recommandé dans ce repo. Il reste un ancien axe documentaire, mais il n’est ni installé ni supporté ici comme runtime principal. + +## Prérequis + +- le repo compagnon `mascarade` existe en voisin (`../mascarade`) ou via `MASCARADE_DIR` +- `node` est disponible sur la machine +- le serveur est buildé dans `mascarade/finetune/kicad_mcp_server/dist/index.js` +- KiCad et son Python sont visibles par le runtime du serveur + +Diagnostic rapide : -Installation : ```bash -pip install kicad-sch-api -# ou via uv -# uv tool install kicad-sch-mcp +tools/hw/run_kicad_mcp.sh --doctor +python3 tools/hw/mcp_smoke.py ``` -Lancer le serveur (dans le repo) : -```bash -kicad-sch-mcp -``` +## Configuration locale + +Le fichier versionné [mcp.json](../mcp.json) pointe déjà vers le launcher supporté : -Exemple (Claude Desktop) — à adapter selon ton OS : ```json { "mcpServers": { - "kicad_schematic": { - "command": "kicad-sch-mcp", - "args": [] + "kicad": { + "type": "local", + "command": "bash", + "args": ["tools/hw/run_kicad_mcp.sh"], + "tools": ["*"] } } } ``` -## Option B — KiCad “live/PCB” MCP (expérimental) -Il existe des serveurs MCP orientés PCB / IPC API (dépend de ta version KiCad et du serveur choisi). citeturn0search1turn0search16 +Le launcher prépare un runtime local writable sous `.cad-home/kicad-mcp/` et exporte `KICAD_MCP_DATA_DIR` pour éviter les écritures dans un préfixe immuable. -Dans ce repo, l’approche “robuste” reste : -- bulk edits schéma via `kicad-sch-api` -- exports/DRC via `kicad-cli` +## Serveur auxiliaire + +`Kill_LIFE` expose aussi un serveur MCP auxiliaire `validate-specs` pour la validation repo/specs : + +```bash +python3 tools/validate_specs.py --json +python3 tools/validate_specs.py --mcp +``` + +Ce serveur ne remplace pas le runtime KiCad. Il sert à vérifier les specs, la conformité et l’usage RFC2119 côté dépôt. + +## Usage + +Depuis `Kill_LIFE` : + +```bash +tools/hw/run_kicad_mcp.sh +``` + +## Politique de support + +- `stdio` reste le seul transport supporté par défaut +- aucun serveur MCP réseau n’est exposé par défaut +- les serveurs mock/demo restent hors chemin de production tant qu’ils ne parlent pas à un backend réel +- `tools/hw/cad_stack.sh mcp` reste un chemin legacy à réaligner avant d’être re-documenté ici diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index dfa1220..c59545e 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -24,15 +24,22 @@ pip install -r requirements-mistral.txt ## 3. Build et test minimal (exemple PlatformIO) ## 3bis. Build et test multi-cible (CI/CD agentique) -Le workflow CI/CD compile et teste le firmware sur ESP, STM et Linux automatiquement. +Le workflow CI/CD actuellement câblé build l’ESP (`esp32s3_arduino`) et teste la cible native (`linux` -> `native`). + +Par défaut, les wrappers tentent `pio` en natif, puis basculent automatiquement sur `tools/hw/cad_stack.sh pio` si `pio` n’est pas installé sur l’hôte. +Tu peux forcer le runner avec `KILL_LIFE_PIO_MODE=native` ou `KILL_LIFE_PIO_MODE=container`. Pour lancer manuellement : ```bash -python tools/build_firmware.py esp -python tools/test_firmware.py esp -python tools/collect_evidence.py esp +python3 tools/build_firmware.py esp +python3 tools/collect_evidence.py esp +python3 tools/verify_evidence.py esp + +python3 tools/test_firmware.py linux +python3 tools/collect_evidence.py linux +python3 tools/verify_evidence.py linux ``` -Remplace `esp` par `stm` ou `linux` selon la cible. +`stm` reste non supporté tant qu’aucune cible STM n’existe dans `firmware/platformio.ini`. Les evidence packs sont générés dans `docs/evidence/`. @@ -52,6 +59,23 @@ mkdocs serve - Suivre les guides détaillés dans `docs/` - Consulter la [FAQ](docs/FAQ.md) pour les questions fréquentes +## 6. Outils CAD/EDA intégrés + +`Kill_LIFE` embarque maintenant une stack CAD/EDA locale : + +```bash +make cad-doctor +make cad-kicad CAD_ARGS="version" +make cad-pio CAD_ARGS="system info" +KILL_LIFE_PIO_MODE=container python3 tools/test_firmware.py linux + +tools/hw/cad_stack.sh doctor +tools/hw/cad_stack.sh kicad-cli version +tools/hw/cad_stack.sh pio system info +``` + +Le workspace monté est `Kill_LIFE` par défaut. + --- ## 🤝 Contribuer diff --git a/docs/RUNBOOK.md b/docs/RUNBOOK.md index f09d592..05226f7 100644 --- a/docs/RUNBOOK.md +++ b/docs/RUNBOOK.md @@ -33,22 +33,31 @@ Ajoute un label `ai:*` : ## 3) CI/CD multi-cible hardware-in-the-loop -Le workflow CI/CD compile, teste et valide le firmware sur ESP, STM et Linux. +Le workflow CI/CD compile, teste et valide le firmware sur ESP et Linux. Les scripts d’automatisation sont dans `tools/` : - `build_firmware.py` : build par cible - `test_firmware.py` : tests par cible - `collect_evidence.py` : génération evidence pack +Les wrappers PlatformIO utilisent `pio` en natif si disponible, sinon basculent automatiquement sur la stack conteneurisée `tools/hw/cad_stack.sh pio`. +Override possible : +- `KILL_LIFE_PIO_MODE=native` +- `KILL_LIFE_PIO_MODE=container` + Les evidence packs sont stockés dans `docs/evidence/`. La couverture est générée via `coverage_badge.py`. Pour vérifier manuellement : ```bash -python tools/build_firmware.py esp -python tools/test_firmware.py esp -python tools/collect_evidence.py esp +python3 tools/build_firmware.py esp +python3 tools/collect_evidence.py esp +python3 tools/verify_evidence.py esp + +python3 tools/test_firmware.py linux +python3 tools/collect_evidence.py linux +python3 tools/verify_evidence.py linux ``` -Remplace `esp` par `stm` ou `linux`. +`stm` reste non supporté tant qu’aucune cible STM n’existe pas dans `firmware/platformio.ini`. Vérifie la présence des artefacts et evidence packs après chaque run. @@ -64,7 +73,7 @@ ajoute un check statique de parité routes API. Exemple (adapté au repo cible) : ```bash -python tools/gates/route_parity_check.py \ +python3 tools/gates/route_parity_check.py \ --backend "src/**/*.cpp" \ --backend "src/**/*.h" \ --frontend "data/webui/**/*.js" \ diff --git a/hardware/README.md b/hardware/README.md index 38c9fcf..cb725cc 100644 --- a/hardware/README.md +++ b/hardware/README.md @@ -2,7 +2,7 @@ - `kicad/` : projets KiCad - `rules/` : règles champs/footprints/nets -- `blocks/` : Design Blocks KiCad 9 (bibliothèque de briques) +- `blocks/` : Design Blocks KiCad 10 (bibliothèque de briques) ⚠️ Les fichiers KiCad réels sont à créer/committer depuis ton poste. Ce template fournit l’outillage (schops + gates + CI). diff --git a/hardware/blocks/README.md b/hardware/blocks/README.md index 66c8d20..240d97e 100644 --- a/hardware/blocks/README.md +++ b/hardware/blocks/README.md @@ -1,4 +1,4 @@ -# Design Blocks (KiCad 9) +# Design Blocks (KiCad 10) Créer une brique : - isoler un sous-schéma stable (ex: régulateur 3V3) diff --git a/mcp.json b/mcp.json index f9a217c..529f7f5 100644 --- a/mcp.json +++ b/mcp.json @@ -1,9 +1,15 @@ { "mcpServers": { + "kicad": { + "type": "local", + "command": "bash", + "args": ["tools/hw/run_kicad_mcp.sh"], + "tools": ["*"] + }, "validate-specs": { "type": "local", "command": "python3", - "args": ["tools/validate_specs.py"], + "args": ["tools/validate_specs.py", "--mcp"], "tools": ["*"] } } diff --git a/tools/hw/cad_stack.sh b/tools/hw/cad_stack.sh new file mode 100755 index 0000000..5d5e842 --- /dev/null +++ b/tools/hw/cad_stack.sh @@ -0,0 +1,279 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)" +COMPOSE_FILE="$ROOT_DIR/deploy/cad/docker-compose.yml" +VERBOSE=0 + +usage() { + cat <<'EOF' +Usage: tools/hw/cad_stack.sh [args...] + +Stack CAD/EDA locale intégrée à Kill_LIFE. + +Commands: + up [services...] Start CAD helper containers + down Stop CAD helper containers + ps Show CAD container status + build [services...] Build local CAD images + doctor Print tool versions from the containers + kicad-cli Run kicad-cli with Kill_LIFE mounted as workspace + freecad-cmd Run FreeCADCmd with Kill_LIFE mounted as workspace + pio Run PlatformIO with Kill_LIFE mounted as workspace + mcp [args...] Run the KiCad MCP server in stdio mode + help Show this help + +Env: + CAD_WORKSPACE_DIR Workspace mounted at /workspace (default: Kill_LIFE root) + CAD_HOST_ROOT Parent path mounted 1:1 for the MCP container (default: parent of Kill_LIFE) + KICAD_DOCKER_IMAGE KiCad v10 image/tag override (default: kicad/kicad:nightly-full) +EOF +} + +log() { + printf '[kill_life:cad] %s\n' "$*" +} + +debug() { + if [ "$VERBOSE" -eq 1 ]; then + printf '[kill_life:cad][dbg] %s\n' "$*" >&2 + fi +} + +die() { + printf '[kill_life:cad][err] %s\n' "$*" >&2 + exit 1 +} + +compose() { + debug "docker compose -f $COMPOSE_FILE $*" + docker compose -f "$COMPOSE_FILE" "$@" +} + +ensure_service_up() { + local service="$1" + if ! compose ps --status running --services | grep -qx "$service"; then + log "Starting $service" + compose up -d "$service" + fi +} + +run_shell_as_host_user() { + local service="$1" + local home_dir="$2" + shift 2 + local shell_command="$*" + compose exec -T \ + --user "$(id -u):$(id -g)" \ + -e HOME="$home_dir" \ + "$service" \ + sh -lc "$shell_command" +} + +run_tool_as_host_user() { + local service="$1" + local home_dir="$2" + local prelude="$3" + local binary="$4" + shift 4 + compose exec -T \ + --user "$(id -u):$(id -g)" \ + -e HOME="$home_dir" \ + "$service" \ + sh -lc 'set -e; mkdir -p "$HOME"; '"$prelude"'; exec "$@"' sh "$binary" "$@" +} + +run_shell_in_service_user() { + local service="$1" + local home_dir="$2" + shift 2 + local shell_command="$*" + compose exec -T \ + -e HOME="$home_dir" \ + "$service" \ + sh -lc "$shell_command" +} + +run_tool_in_service_user() { + local service="$1" + local home_dir="$2" + local prelude="$3" + local binary="$4" + shift 4 + compose exec -T \ + -e HOME="$home_dir" \ + "$service" \ + sh -lc 'set -e; mkdir -p "$HOME"; '"$prelude"'; exec "$@"' sh "$binary" "$@" +} + +build_cmd() { + local restart_services=() + if [ "$#" -gt 0 ]; then + compose build "$@" + for service in "$@"; do + case "$service" in + kicad-headless|freecad-headless|platformio) + restart_services+=("$service") + ;; + esac + done + else + compose build + restart_services=(kicad-headless freecad-headless platformio) + fi + + if [ "${#restart_services[@]}" -gt 0 ]; then + log "Recreating updated services: ${restart_services[*]}" + compose up -d --force-recreate "${restart_services[@]}" + fi +} + +up_cmd() { + if [ "$#" -gt 0 ]; then + compose up -d "$@" + return + fi + + compose up -d kicad-headless freecad-headless platformio +} + +doctor_cmd() { + ensure_service_up kicad-headless + ensure_service_up freecad-headless + ensure_service_up platformio + + run_shell_as_host_user \ + kicad-headless \ + /workspace/.cad-home/kicad-headless \ + 'mkdir -p "$HOME" && kicad-cli version' + + run_shell_in_service_user \ + freecad-headless \ + /workspace/.cad-home/freecad-headless \ + 'mkdir -p "$HOME" && freecadcmd -c "import FreeCAD; print(\".\".join(FreeCAD.Version()[:3]))"' + + run_shell_as_host_user \ + platformio \ + /workspace/.cad-home/platformio \ + 'mkdir -p "$HOME" "$HOME/.platformio" && export PLATFORMIO_CORE_DIR="$HOME/.platformio" && pio --version' +} + +kicad_cli_cmd() { + ensure_service_up kicad-headless + run_tool_as_host_user \ + kicad-headless \ + /workspace/.cad-home/kicad-headless \ + ":" \ + kicad-cli \ + "$@" +} + +freecad_cmd() { + ensure_service_up freecad-headless + run_tool_in_service_user \ + freecad-headless \ + /workspace/.cad-home/freecad-headless \ + ":" \ + freecadcmd \ + "$@" +} + +pio_cmd() { + ensure_service_up platformio + run_tool_as_host_user \ + platformio \ + /workspace/.cad-home/platformio \ + 'mkdir -p "$HOME/.platformio"; export PLATFORMIO_CORE_DIR="$HOME/.platformio"' \ + pio \ + "$@" +} + +mcp_cmd() { + "$ROOT_DIR/tools/hw/run_kicad_mcp.sh" "$@" +} + +while [ "$#" -gt 0 ]; do + case "$1" in + -v|--verbose) + VERBOSE=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + break + ;; + esac +done + +CMD="${1:-help}" +if [ "$#" -gt 0 ]; then + shift +fi + +[ -f "$COMPOSE_FILE" ] || die "CAD compose file not found: $COMPOSE_FILE" +export CAD_WORKSPACE_DIR="${CAD_WORKSPACE_DIR:-$ROOT_DIR}" +export CAD_HOST_ROOT="${CAD_HOST_ROOT:-$(cd "$ROOT_DIR/.." && pwd)}" + +if [ "$CMD" = "help" ]; then + usage + exit 0 +fi + +case "$CMD" in + up|down|ps|build|doctor|kicad-cli|freecad-cmd|pio|mcp) + ;; + *) + usage >&2 + die "unknown command: $CMD" + ;; +esac + +debug "root=$ROOT_DIR" +debug "compose_file=$COMPOSE_FILE" +debug "workspace_dir=$CAD_WORKSPACE_DIR" +debug "command=$CMD" + +case "$CMD" in + up) + up_cmd "$@" + ;; + down) + compose down + ;; + ps) + compose ps + ;; + build) + build_cmd "$@" + ;; + doctor) + doctor_cmd + ;; + kicad-cli) + if [ "$#" -eq 0 ]; then + echo "cad_stack.sh kicad-cli: missing arguments" >&2 + exit 2 + fi + kicad_cli_cmd "$@" + ;; + freecad-cmd) + if [ "$#" -eq 0 ]; then + echo "cad_stack.sh freecad-cmd: missing arguments" >&2 + exit 2 + fi + freecad_cmd "$@" + ;; + pio) + if [ "$#" -eq 0 ]; then + echo "cad_stack.sh pio: missing arguments" >&2 + exit 2 + fi + pio_cmd "$@" + ;; + mcp) + mcp_cmd "$@" + ;; +esac diff --git a/tools/hw/hw_check.sh b/tools/hw/hw_check.sh index 9b5e149..7b87834 100644 --- a/tools/hw/hw_check.sh +++ b/tools/hw/hw_check.sh @@ -7,6 +7,6 @@ if [[ -z "${SCHEMATIC}" ]]; then exit 2 fi -python tools/hw/schops/schops.py erc --schematic "${SCHEMATIC}" -python tools/hw/schops/schops.py netlist --schematic "${SCHEMATIC}" -python tools/hw/schops/schops.py bom --schematic "${SCHEMATIC}" +python3 tools/hw/schops/schops.py erc --schematic "${SCHEMATIC}" +python3 tools/hw/schops/schops.py netlist --schematic "${SCHEMATIC}" +python3 tools/hw/schops/schops.py bom --schematic "${SCHEMATIC}" diff --git a/tools/hw/hw_gate.sh b/tools/hw/hw_gate.sh index f4c208d..21f8b52 100644 --- a/tools/hw/hw_gate.sh +++ b/tools/hw/hw_gate.sh @@ -24,12 +24,12 @@ fi echo "Using schematic: ${SCHEM:-}" echo "Using pcb: ${PCB:-}" -OUTDIR="$(python tools/hw/exports.py ${SCHEM:+--schematic "$SCHEM"} ${PCB:+--pcb "$PCB"})" +OUTDIR="$(python3 tools/hw/exports.py ${SCHEM:+--schematic "$SCHEM"} ${PCB:+--pcb "$PCB"})" echo "Previews: ${OUTDIR}" -python tools/hw/blocks/lint_blocks.py --blocks-dir hardware/blocks -python tools/hw/blocks/generate_registry.py --blocks-dir hardware/blocks --out hardware/blocks/REGISTRY.md +python3 tools/hw/blocks/lint_blocks.py --blocks-dir hardware/blocks +python3 tools/hw/blocks/generate_registry.py --blocks-dir hardware/blocks --out hardware/blocks/REGISTRY.md -python tools/compliance/validate.py +python3 tools/compliance/validate.py echo "OK" diff --git a/tools/hw/kicad_cli.sh b/tools/hw/kicad_cli.sh index 8cabe02..c514c5c 100644 --- a/tools/hw/kicad_cli.sh +++ b/tools/hw/kicad_cli.sh @@ -7,7 +7,7 @@ set -euo pipefail # # Env: # KICAD_CLI_BIN: override local path -# KICAD_DOCKER_IMAGE: override docker image (default: kicad/kicad:9.0.7-full) +# KICAD_DOCKER_IMAGE: override docker image (default: kicad/kicad:nightly) BIN="${KICAD_CLI_BIN:-}" if [[ -z "$BIN" ]]; then @@ -28,7 +28,7 @@ if ! command -v docker >/dev/null 2>&1; then exit 127 fi -IMG="${KICAD_DOCKER_IMAGE:-kicad/kicad:9.0.7-full}" +IMG="${KICAD_DOCKER_IMAGE:-kicad/kicad:nightly}" # run as current user to avoid root-owned artifacts UIDGID="$(id -u):$(id -g)" diff --git a/tools/hw/mcp_smoke.py b/tools/hw/mcp_smoke.py new file mode 100755 index 0000000..40bed1f --- /dev/null +++ b/tools/hw/mcp_smoke.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +"""Minimal STDIO smoke test for the supported KiCad MCP launcher.""" + +from __future__ import annotations + +import argparse +import json +import os +import selectors +import signal +import subprocess +import sys +import time +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[2] + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--timeout", + type=float, + default=20.0, + help="Seconds to wait for each MCP response.", + ) + return parser.parse_args() + + +def read_response(proc: subprocess.Popen[str], timeout: float) -> dict[str, Any]: + if proc.stdout is None: + raise RuntimeError("stdout pipe is not available") + + if proc.stdout.closed: + raise RuntimeError("stdout pipe is closed") + + deadline = time.monotonic() + timeout + selector = selectors.DefaultSelector() + selector.register(proc.stdout, selectors.EVENT_READ) + buffer = "" + + try: + while True: + remaining = deadline - time.monotonic() + if remaining <= 0: + raise RuntimeError(f"Timed out after {timeout:.1f}s waiting for MCP response") + + events = selector.select(remaining) + if not events: + raise RuntimeError(f"Timed out after {timeout:.1f}s waiting for MCP response") + + line = proc.stdout.readline() + if not line: + raise RuntimeError("MCP process closed stdout before replying") + + buffer += line + if "\n" not in buffer: + continue + + message, _, remainder = buffer.partition("\n") + buffer = remainder + message = message.strip() + if not message: + continue + + return json.loads(message) + finally: + selector.close() + + +def send_message(proc: subprocess.Popen[str], payload: dict[str, Any]) -> None: + if proc.stdin is None: + raise RuntimeError("stdin pipe is not available") + + body = json.dumps(payload) + proc.stdin.write(body + "\n") + proc.stdin.flush() + + +def main() -> int: + args = parse_args() + launcher = ROOT / "tools" / "hw" / "run_kicad_mcp.sh" + + proc = subprocess.Popen( + ["bash", str(launcher)], + cwd=ROOT, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + start_new_session=True, + ) + + try: + send_message( + proc, + { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2025-03-26", + "capabilities": {}, + "clientInfo": {"name": "kill-life-mcp-smoke", "version": "1.0.0"}, + }, + }, + ) + init = read_response(proc, args.timeout) + + result = init.get("result") or {} + protocol_version = result.get("protocolVersion") + server_info = result.get("serverInfo") or {} + server_name = server_info.get("name", "unknown") + + send_message( + proc, + { + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": {}, + }, + ) + send_message( + proc, + {"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}, + ) + tools_resp = read_response(proc, args.timeout) + tools = (tools_resp.get("result") or {}).get("tools") or [] + + if not protocol_version: + raise RuntimeError("initialize response missing protocolVersion") + if not tools: + raise RuntimeError("tools/list returned no tools") + + print(f"OK: server={server_name} protocol={protocol_version} tools={len(tools)}") + return 0 + except Exception as exc: + print(f"ERROR: {exc}", file=sys.stderr) + return 1 + finally: + if proc.poll() is None: + os.killpg(proc.pid, signal.SIGTERM) + try: + proc.wait(timeout=5) + except subprocess.TimeoutExpired: + os.killpg(proc.pid, signal.SIGKILL) + proc.wait(timeout=5) + + stderr_output = "" + if proc.stderr is not None: + try: + stderr_output = proc.stderr.read().strip() + except Exception: + stderr_output = "" + if stderr_output: + sys.stderr.write(stderr_output + "\n") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/hw/run_kicad_mcp.sh b/tools/hw/run_kicad_mcp.sh new file mode 100755 index 0000000..2d31901 --- /dev/null +++ b/tools/hw/run_kicad_mcp.sh @@ -0,0 +1,264 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +COMPOSE_FILE="$ROOT_DIR/deploy/cad/docker-compose.yml" +VERBOSE=0 +DOCTOR=0 +RUNTIME="${KICAD_MCP_RUNTIME:-auto}" + +usage() { + cat <<'EOF' +Usage: tools/hw/run_kicad_mcp.sh [--doctor] [--verbose] [-- ] + +Launch the supported KiCad MCP server from the companion `mascarade` repo. + +Options: + --doctor Print resolved paths and exit + --verbose Print resolved command before exec + -h, --help Show this help + +Environment: + MASCARADE_DIR Override the companion repo path (default: ../mascarade) + KICAD_MCP_ENTRYPOINT Override the Node entrypoint + KICAD_MCP_HOME Override the MCP runtime home (default: .cad-home/kicad-mcp) + NODE_BIN Override the Node executable (default: node) + KICAD_MCP_DATA_DIR Override the writable data directory used by the server + KICAD_MCP_RUNTIME host | container | auto (default: auto) +EOF +} + +log() { + printf '[kill_life:mcp] %s\n' "$*" >&2 +} + +debug() { + if [ "$VERBOSE" -eq 1 ]; then + log "$*" + fi +} + +cleanup_container() { + if [ -n "${ACTIVE_CONTAINER_NAME:-}" ]; then + docker rm -f "$ACTIVE_CONTAINER_NAME" >/dev/null 2>&1 || true + fi +} + +die() { + printf '[kill_life:mcp][err] %s\n' "$*" >&2 + exit 1 +} + +compose() { + docker compose -f "$COMPOSE_FILE" "$@" +} + +build_pythonpath() { + local -a candidates=() + local value="" + + if [ -n "${KICAD_PYTHONPATH:-}" ]; then + value="${KICAD_PYTHONPATH}" + elif [ -n "${PYTHONPATH:-}" ]; then + value="${PYTHONPATH}" + else + candidates=( + /usr/lib/kicad/lib/python3/dist-packages + /usr/local/lib/kicad/lib/python3/dist-packages + /usr/lib/python3/dist-packages + /usr/local/lib/python3/dist-packages + /usr/lib/python3.12/dist-packages + /usr/local/lib/python3.12/dist-packages + ) + for candidate in "${candidates[@]}"; do + if [ -d "$candidate" ]; then + if [ -n "$value" ]; then + value="${value}:$candidate" + else + value="$candidate" + fi + fi + done + fi + + printf '%s' "$value" +} + +resolve_probe_python() { + if [ -n "${KICAD_PYTHON:-}" ]; then + printf '%s' "$KICAD_PYTHON" + return 0 + fi + + if [ -x "$SERVER_DIR/venv/bin/python" ]; then + printf '%s' "$SERVER_DIR/venv/bin/python" + return 0 + fi + + command -v python3 || true +} + +probe_pcbnew() { + local probe_python="$1" + local pythonpath="$2" + + [ -n "$probe_python" ] || return 1 + + if [ -n "$pythonpath" ]; then + env PYTHONPATH="$pythonpath" "$probe_python" -c 'import pcbnew' >/dev/null 2>&1 + return $? + fi + + "$probe_python" -c 'import pcbnew' >/dev/null 2>&1 +} + +while [ "$#" -gt 0 ]; do + case "$1" in + --doctor) + DOCTOR=1 + shift + ;; + --runtime) + [ "$#" -ge 2 ] || die "--runtime requires a value" + RUNTIME="$2" + shift 2 + ;; + --runtime=*) + RUNTIME="${1#*=}" + shift + ;; + --container) + RUNTIME="container" + shift + ;; + --host) + RUNTIME="host" + shift + ;; + --verbose) + VERBOSE=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +REPO_PARENT="$(cd "$ROOT_DIR/.." && pwd)" +export CAD_HOST_ROOT="${CAD_HOST_ROOT:-$REPO_PARENT}" +export CAD_WORKSPACE_DIR="${CAD_WORKSPACE_DIR:-$ROOT_DIR}" +export KICAD_MCP_IMAGE="${KICAD_MCP_IMAGE:-kill_life_cad-kicad-mcp:latest}" +MASCARADE_DIR="${MASCARADE_DIR:-$REPO_PARENT/mascarade}" +SERVER_DIR="$MASCARADE_DIR/finetune/kicad_mcp_server" +ENTRYPOINT="${KICAD_MCP_ENTRYPOINT:-$SERVER_DIR/dist/index.js}" +NODE_BIN="${NODE_BIN:-node}" +MCP_HOME="${KICAD_MCP_HOME:-$ROOT_DIR/.cad-home/kicad-mcp}" +CONFIG_HOME="${XDG_CONFIG_HOME:-$MCP_HOME/.config}" +CACHE_HOME="${XDG_CACHE_HOME:-$MCP_HOME/.cache}" +DATA_DIR="${KICAD_MCP_DATA_DIR:-$MCP_HOME/data}" +PYTHONPATH_VALUE="$(build_pythonpath)" +PROBE_PYTHON="$(resolve_probe_python)" +HOST_PCBNEW_STATUS="missing" +CONTAINER_STATUS="unknown" + +if probe_pcbnew "$PROBE_PYTHON" "$PYTHONPATH_VALUE"; then + HOST_PCBNEW_STATUS="ok" +fi + +if command -v docker >/dev/null 2>&1 && [ -f "$COMPOSE_FILE" ]; then + CONTAINER_STATUS="available" +else + CONTAINER_STATUS="missing" +fi + +if [ "$DOCTOR" -eq 1 ]; then + cat </dev/null 2>&1 || die "node executable not found: $NODE_BIN" +[ -d "$MASCARADE_DIR" ] || die "companion repo not found: $MASCARADE_DIR" +[ -f "$ENTRYPOINT" ] || die "MCP entrypoint missing: $ENTRYPOINT (run npm build in mascarade/finetune/kicad_mcp_server)" +[ -n "$PROBE_PYTHON" ] || die "no Python executable found for KiCad MCP runtime" + +mkdir -p "$MCP_HOME" "$CONFIG_HOME" "$CACHE_HOME" "$DATA_DIR" + +export HOME="$MCP_HOME" +export XDG_CONFIG_HOME="$CONFIG_HOME" +export XDG_CACHE_HOME="$CACHE_HOME" +export KICAD_MCP_DATA_DIR="$DATA_DIR" +if [ -n "$PYTHONPATH_VALUE" ]; then + export KICAD_PYTHONPATH="$PYTHONPATH_VALUE" +fi + +case "$RUNTIME" in + auto) + if [ "$HOST_PCBNEW_STATUS" = "ok" ]; then + RUNTIME="host" + else + RUNTIME="container" + fi + ;; + host|container) + ;; + *) + die "invalid runtime: $RUNTIME" + ;; +esac + +if [ "$RUNTIME" = "host" ]; then + probe_pcbnew "$PROBE_PYTHON" "${KICAD_PYTHONPATH:-}" || die "pcbnew is not importable from $PROBE_PYTHON. Install KiCad with Python bindings, set KICAD_PYTHON/KICAD_PYTHONPATH, or use --container." + debug "exec host $NODE_BIN $ENTRYPOINT $*" + exec "$NODE_BIN" "$ENTRYPOINT" "$@" +fi + +command -v docker >/dev/null 2>&1 || die "docker is required for container runtime" +[ -f "$COMPOSE_FILE" ] || die "compose file not found: $COMPOSE_FILE" + +if ! docker image inspect "$KICAD_MCP_IMAGE" >/dev/null 2>&1; then + log "Building kicad-mcp container (KiCad v10 via kicad/kicad:nightly-full)" + compose build kicad-mcp >&2 +fi + +ACTIVE_CONTAINER_NAME="${KICAD_MCP_CONTAINER_NAME:-kill-life-kicad-mcp-$(id -u)-$$}" +trap cleanup_container EXIT INT TERM + +debug "exec container kicad-mcp $*" +compose run --rm --no-deps -T \ + --name "$ACTIVE_CONTAINER_NAME" \ + --user "$(id -u):$(id -g)" \ + -e HOME="$MCP_HOME" \ + -e XDG_CONFIG_HOME="$CONFIG_HOME" \ + -e XDG_CACHE_HOME="$CACHE_HOME" \ + -e KICAD_MCP_DATA_DIR="$DATA_DIR" \ + kicad-mcp \ + sh -lc 'set -e; mkdir -p "$HOME" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" "$KICAD_MCP_DATA_DIR"; exec node /opt/kicad-mcp/server/dist/index.js "$@"' sh "$@" +status=$? +trap - EXIT INT TERM +cleanup_container +exit $status diff --git a/tools/hw/schops/README.md b/tools/hw/schops/README.md index 338619d..dba7579 100644 --- a/tools/hw/schops/README.md +++ b/tools/hw/schops/README.md @@ -3,7 +3,7 @@ CLI local pour : - ERC/BOM/netlist via `kicad-cli` - bulk edits via `kicad-sch-api` (si installé) -- Design Blocks KiCad 9 (structure + metadata) +- Design Blocks KiCad 10 (structure + metadata) > Philosophie : **bulk edits safe** (backup + report) + exports déterministes (kicad-cli). @@ -66,7 +66,7 @@ python tools/hw/schops/schops.py snapshot --schematic ... --name before.json python tools/hw/schops/schops.py snapshot --schematic ... --name after.json ``` -## Design Blocks (KiCad 9) +## Design Blocks (KiCad 10) Les design blocks sont des dossiers `*.kicad_block` stockés dans une librairie `*.kicad_blocks`. ```bash diff --git a/tools/hw/schops/schops.py b/tools/hw/schops/schops.py index 8713ae1..fb16768 100644 --- a/tools/hw/schops/schops.py +++ b/tools/hw/schops/schops.py @@ -4,7 +4,7 @@ Goals: - deterministic exports via kicad-cli (ERC / netlist / BOM) - safe bulk edits via kicad-sch-api (fields / footprints / net labels) - - Design Blocks (KiCad 9) helpers + - Design Blocks (KiCad 10) helpers This tool is intentionally conservative: - it always writes an artifacts report @@ -515,7 +515,7 @@ def cmd_snapshot(args) -> int: # --------------------------- -# Design Blocks (KiCad 9) +# Design Blocks (KiCad 10) # --------------------------- @@ -646,7 +646,7 @@ def build_parser() -> argparse.ArgumentParser: p.add_argument("--name", help="output filename (default snapshot.json)") p.set_defaults(fn=cmd_snapshot) - p = sub.add_parser("block-make", help="Create a KiCad 9 design block folder") + p = sub.add_parser("block-make", help="Create a KiCad 10 design block folder") p.add_argument("--name", required=True) p.add_argument("--from-sheet", required=True, help=".kicad_sch file to package as a block") p.add_argument("--lib", required=True, help="Design blocks library folder (usually *.kicad_blocks)")