Adopt KiCad 10-first CAD runtime stack

This commit is contained in:
Clément SAILLANT
2026-03-07 18:52:22 +01:00
parent c8d22fc0ff
commit 7d413a3d6d
34 changed files with 1193 additions and 153 deletions
@@ -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é
+2 -2
View File
@@ -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
@@ -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
@@ -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/<timestamp>/`.
## 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 dinté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é nest 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`.
+3 -32
View File
@@ -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, lapproche “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`.
+1 -1
View File
@@ -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 loutillage (schops + gates + CI).
@@ -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)
@@ -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)"
@@ -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
@@ -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)")
+11
View File
@@ -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"]
+38
View File
@@ -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"]
+15
View File
@@ -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"]
+39
View File
@@ -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 quune image stable 10.x nest pas explicitement épinglée.
- `CAD_WORKSPACE_DIR` : workspace monté dans `/workspace`
## Note MCP
Le choix ici est volontairement sobre :
- `stdio` pour lusage 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.
+39
View File
@@ -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
+1 -1
View File
@@ -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é
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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
+30 -6
View File
@@ -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/<timestamp>/`.
## 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 dinté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 dexé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 dentré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
```
+164
View File
@@ -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 dexé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` | cest la source de vérité méthodologique |
| Exécution LLM locale et orchestration provider/model | `Mascarade` | cest le cockpit dexé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 dacceptation 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 lusage de ces students dans les workflows agents de `Kill_LIFE`
## Alignement avec l’état de lart 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 dexé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 dinté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
```
+58 -19
View File
@@ -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` nest plus le chemin recommandé dans ce repo. Il reste un ancien axe documentaire, mais il nest 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, lapproche “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 lusage 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 nest exposé par défaut
- les serveurs mock/demo restent hors chemin de production tant quils 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
+29 -5
View File
@@ -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 lESP (`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` nest pas installé sur lhô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 quaucune cible STM nexiste 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
+15 -6
View File
@@ -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 dautomatisation 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 quaucune cible STM nexiste 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" \
+1 -1
View File
@@ -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 loutillage (schops + gates + CI).
+1 -1
View File
@@ -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)
+7 -1
View File
@@ -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": ["*"]
}
}
+279
View File
@@ -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 <command> [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 <args...> Run kicad-cli with Kill_LIFE mounted as workspace
freecad-cmd <args...> Run FreeCADCmd with Kill_LIFE mounted as workspace
pio <args...> 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
+3 -3
View File
@@ -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}"
+4 -4
View File
@@ -24,12 +24,12 @@ fi
echo "Using schematic: ${SCHEM:-<none>}"
echo "Using pcb: ${PCB:-<none>}"
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"
+2 -2
View File
@@ -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)"
+163
View File
@@ -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())
+264
View File
@@ -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] [-- <server args>]
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 <<EOF
ROOT_DIR=$ROOT_DIR
MASCARADE_DIR=$MASCARADE_DIR
SERVER_DIR=$SERVER_DIR
ENTRYPOINT=$ENTRYPOINT
NODE_BIN=$NODE_BIN
KICAD_MCP_HOME=$MCP_HOME
XDG_CONFIG_HOME=$CONFIG_HOME
XDG_CACHE_HOME=$CACHE_HOME
KICAD_MCP_DATA_DIR=$DATA_DIR
PROBE_PYTHON=$PROBE_PYTHON
KICAD_PYTHONPATH=$PYTHONPATH_VALUE
HOST_PCBNEW_IMPORT=$HOST_PCBNEW_STATUS
CONTAINER_STATUS=$CONTAINER_STATUS
KICAD_MCP_IMAGE=$KICAD_MCP_IMAGE
SELECTED_RUNTIME=$RUNTIME
EOF
exit 0
fi
command -v "$NODE_BIN" >/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
+2 -2
View File
@@ -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
+3 -3
View File
@@ -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)")