diff --git a/.gitignore b/.gitignore index f412458..2ab5929 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ node_modules/ # Docker *.pid docker-compose.override.yml +.cad-home/ # Autres outils CI/CD coverage/ diff --git a/TUTO4NOOB.md b/TUTO4NOOB.md index 07294cf..8b9752b 100644 --- a/TUTO4NOOB.md +++ b/TUTO4NOOB.md @@ -55,7 +55,7 @@ Kill_LIFE, c’est un gros dossier pour faire des projets électroniques avec de Dans le terminal, tape : ```bash -python tools/validate_specs.py +python3 tools/validate_specs.py ``` Ça vérifie que les règles du projet sont OK. diff --git a/ai-agentic-embedded-base/README.md b/ai-agentic-embedded-base/README.md index 0120adf..822c457 100644 --- a/ai-agentic-embedded-base/README.md +++ b/ai-agentic-embedded-base/README.md @@ -10,9 +10,9 @@ Repo “de base” qui combine le meilleur de : ## TL;DR - Écris/itère la spec dans `specs/` -- (optionnel) Génère un squelette de spec depuis `.specify/` : `python tools/ai/specify_init.py --name ` +- (optionnel) Génère un squelette de spec depuis `.specify/` : `python3 tools/ai/specify_init.py --name ` - Applique les standards dans `standards/` -- Exécute le cockpit : `python tools/cockpit/cockpit.py menu` +- Exécute le cockpit : `python3 tools/cockpit/cockpit.py menu` - Pour l’automatisation GitHub : label `ai:codex` → Issue → PR ## Structure @@ -37,7 +37,7 @@ pio run -e esp32s3_arduino pio test -e native # cockpit -python tools/cockpit/cockpit.py menu +python3 tools/cockpit/cockpit.py menu ``` ## Conventions de sortie @@ -46,8 +46,8 @@ Tous les scripts écrivent sous `artifacts///` (logs + export ## V4 — KiCad agentique (bulk + previews + MCP) - `bash tools/hw/hw_gate.sh hardware/kicad` : exports SVG + ERC/DRC JSON + BOM/netlist -- `python tools/watch/watch_hw.py` : watch mode (re-run gate on save) -- `docs/MCP_SETUP.md` : configuration MCP (kicad-sch-mcp) citeturn0search9 +- `python3 tools/watch/watch_hw.py` : watch mode (re-run gate on save) +- `docs/MCP_SETUP.md` : configuration MCP (`kicad-sch-api` / `kicad-sch-mcp`) citeturn0search9 ## Compliance (2 options) - **Prototype interne** : profil `prototype` (pas de CE/RED) @@ -55,10 +55,9 @@ Tous les scripts écrivent sous `artifacts///` (logs + export Changer de profil : ```bash -python tools/compliance/use_profile.py prototype -python tools/compliance/use_profile.py iot_wifi_eu -python tools/compliance/validate.py +python3 tools/compliance/use_profile.py prototype +python3 tools/compliance/use_profile.py iot_wifi_eu +python3 tools/compliance/validate.py ``` Docs : `docs/COMPLIANCE.md` - diff --git a/ai-agentic-embedded-base/specs/README.md b/ai-agentic-embedded-base/specs/README.md index cf75a23..a54e8aa 100644 --- a/ai-agentic-embedded-base/specs/README.md +++ b/ai-agentic-embedded-base/specs/README.md @@ -12,6 +12,7 @@ Le fichier `constraints.yaml` est la **source de vérité** des contraintes non- Specs complémentaires: +- `kicad_mcp_scope_spec.md`: perimetre fonctionnel, hors scope et criteres d'acceptation du MCP KiCad supporte. - `zeroclaw_dual_hw_orchestration_spec.md`: architecture d'orchestration ZeroClaw multi-repo + double matériel. - `zeroclaw_dual_hw_todo.md`: backlog opérationnel court terme pour autonomie contrôlée. diff --git a/ai-agentic-embedded-base/specs/kicad_mcp_scope_spec.md b/ai-agentic-embedded-base/specs/kicad_mcp_scope_spec.md new file mode 100644 index 0000000..8d4b294 --- /dev/null +++ b/ai-agentic-embedded-base/specs/kicad_mcp_scope_spec.md @@ -0,0 +1,136 @@ +# Spec perimetre MCP KiCad + +Last updated: 2026-03-07 + +## Objectifs + +- O1. Definir le perimetre fonctionnel du serveur MCP KiCad supporte par `Kill_LIFE`. +- O2. Eviter la derive entre implementation serveur, launcher local, docs et attentes des agents. +- O3. Fixer une frontiere claire entre ce que le MCP KiCad MUST couvrir en v1, ce qui SHOULD venir en v2, et ce qui reste hors scope. + +## Non-objectifs + +- N1. Faire du serveur MCP KiCad un assistant generaliste ou un agent conversationnel. +- N2. Exposer un transport reseau ou multi-tenant en v1. +- N3. Couvrir Git, CI, docs repo, orchestration infra, ou des actions hors CAD. +- N4. Ajouter des effets de bord caches hors des chemins explicitement fournis par l'utilisateur ou le runtime. + +## Ownership + +- Implementation serveur de reference: `mascarade/finetune/kicad_mcp_server` +- Contrat local, launcher, smoke test et doc d'usage: `Kill_LIFE` +- Consommateurs externes eventuels: autres repos, sans ownership serveur + +## User stories + +- US1. En tant qu'ingenieur hardware, je veux creer, ouvrir, modifier et exporter un projet KiCad depuis un client MCP local. +- US2. En tant qu'agent outille, je veux inspecter schema, board, librairies et regles sans corrompre le projet. +- US3. En tant qu'operateur de fabrication, je veux lancer les validations et exports minimaux avant production. +- US4. En tant qu'agent de sourcing, je veux relier composants, footprints, datasheets et references JLCPCB/LCSC. + +## Exigences fonctionnelles v1 + +- F1. Transport + - Le serveur MUST exposer `stdio` local uniquement. + - Le serveur MUST parler JSON-RPC compatible MCP avec framing ligne par ligne, conforme au SDK utilise. + - Le serveur MUST fonctionner via `tools/hw/run_kicad_mcp.sh`. + +- F2. Cycle projet + - Le serveur MUST permettre de creer, ouvrir, sauvegarder et inspecter un projet KiCad. + - Le serveur MUST distinguer les operations de lecture des operations de mutation. + +- F3. Schema + - Le serveur MUST permettre le placement de symboles, la connexion de pins, la pose de labels/nets et la generation de netlist. + - Le serveur SHOULD permettre l'enrichissement des champs de composant utiles a la suite du flux CAD. + +- F4. PCB / layout + - Le serveur MUST permettre l'inspection du board, des couches, des extents et de la geometrie. + - Le serveur MUST couvrir outline, trous de fixation, textes, zones et operations de placement/deplacement/rotation de composants. + +- F5. Routage + - Le serveur MUST permettre la creation ou l'inspection de pistes, vias, pads, nets et couches utiles au routage. + - Le serveur SHOULD exposer des aides de recherche/outillage de routage. + +- F6. Librairies + - Le serveur MUST lister et rechercher des symboles et footprints. + - Le serveur MUST permettre l'enregistrement de librairies projet/globales quand l'operation est explicitement demandee. + - Le serveur SHOULD permettre la creation de symboles et footprints custom en local. + +- F7. Validation + - Le serveur MUST permettre l'execution de checks de validation de type DRC ou equivalent. + - Le serveur MUST renvoyer des erreurs structurees et exploitables quand une validation echoue. + +- F8. Export + - Le serveur MUST couvrir les exports minimaux de fabrication et de revue: Gerber, PDF, 3D ou sorties equivalentes deja supportees par l'implementation. + - Le serveur SHOULD rendre le chemin de sortie explicite. + +- F9. Sourcing + - Le serveur SHOULD permettre la recherche de composants JLCPCB/LCSC, les alternatives proches et l'acces aux datasheets. + - Les integrations de sourcing MAY utiliser un backend local ou une API externe si les credentials sont presents. + +- F10. Runtime + - Le serveur MUST preferer un backend KiCad reel. + - Le backend SHOULD preferer IPC quand disponible. + - Le backend MUST pouvoir retomber sur SWIG si IPC n'est pas disponible. + - Le runtime MUST fonctionner soit sur host avec `pcbnew`, soit via le conteneur KiCad supporte. + +## Exigences non-fonctionnelles + +- Securite + - Le serveur MUST rester local par defaut. + - Le serveur MUST eviter les ecritures hors `projectPath`, `libraryPath`, runtime home et `KICAD_MCP_DATA_DIR`. + - Le serveur MUST NOT exiger de secrets pour les usages locaux de base. + +- Fiabilite + - `initialize` puis `tools/list` MUST passer sur une machine supportee. + - Le launcher MUST gerer le fallback host -> container quand l'hote n'expose pas `pcbnew`. + - Les erreurs MUST etre actionnables et non silencieuses. + +- Determinisme + - Les outils de lecture MUST NOT muter l'etat du projet. + - Les outils de mutation SHOULD exiger des parametres explicites et des chemins non ambigus. + +- Observabilite + - Le serveur MUST garder les erreurs et warnings exploitables. + - Le niveau de log par defaut SHOULD etre discret. + - En succes nominal, le runtime SHOULD eviter de polluer `stderr` avec du bruit de demarrage non essentiel. + +- Compatibilite + - Le contrat de transport MUST rester stable pour les consommateurs locaux de `Kill_LIFE`. + - Le runtime conteneur supporte SHOULD rester aligne avec la version KiCad cible du projet, y compris la trajectoire v10. + +## V2 / extensions legitimes + +- V2.1. Session IPC plus riche, synchronisee avec une UI KiCad vivante. +- V2.2. Coverage schematic plus avancee: edition structurelle plus large, contraintes electriques plus fines, automation de patterns repetitifs. +- V2.3. Workflows de revue plus riches: snapshots, diff de board/schema, previews avant mutation. +- V2.4. Sourcing plus fort: BOM, substitutions, scoring cout/disponibilite. +- V2.5. Dry-run systematique pour les mutations a fort impact. + +## Hors scope + +- H1. Chat libre, planification generale, ou raisonnement non CAD. +- H2. Serveur MCP HTTP/SSE expose sur le reseau par defaut. +- H3. Git operations, PR management, CI/CD, ou orchestration repo. +- H4. Gestion de secrets globale de la machine. +- H5. Autonomie d'achat ou de commande fournisseur. +- H6. Couverture d'autres outils CAD hors KiCad dans ce serveur de reference. + +## Contrats / interfaces + +- I1. Point d'entree supporte: `tools/hw/run_kicad_mcp.sh` +- I2. Config consommatrice supportee: `mcp.json` cote `Kill_LIFE` +- I3. Implementation serveur de reference: `mascarade/finetune/kicad_mcp_server` +- I4. Smoke minimal supporte: + - `initialize` + - `notifications/initialized` + - `tools/list` + +## Critieres d'acceptation + +- AC1. Le repo documente un seul serveur KiCad MCP supporte en v1. +- AC2. Le serveur expose au moins une famille d'outils fonctionnelle pour: projet, schema, PCB, librairies, validation, export. +- AC3. `python3 tools/hw/mcp_smoke.py` passe sur un environnement supporte. +- AC4. En absence de `pcbnew` host, le launcher supporte bascule vers le runtime conteneur. +- AC5. La doc d'usage locale renvoie explicitement vers cette spec de perimetre. +- AC6. Les sujets hors scope sont documentes au lieu d'etre supposes implicitement. diff --git a/docs/plans/03_plan_specification.md b/docs/plans/03_plan_specification.md index e274667..a2ec11c 100644 --- a/docs/plans/03_plan_specification.md +++ b/docs/plans/03_plan_specification.md @@ -44,7 +44,7 @@ Checklist : Exemple : ```bash -python tools/validate_specs.py || true +python3 tools/validate_specs.py || true ``` ## Gates @@ -57,4 +57,4 @@ python tools/validate_specs.py || true ## Références - `docs/AI_WORKFLOWS.md` -- `docs/templates/ValidationPlan.md` \ No newline at end of file +- `docs/templates/ValidationPlan.md` diff --git a/docs/plans/15_plan_mcp_runtime_alignment.md b/docs/plans/15_plan_mcp_runtime_alignment.md new file mode 100644 index 0000000..51c1c03 --- /dev/null +++ b/docs/plans/15_plan_mcp_runtime_alignment.md @@ -0,0 +1,42 @@ +# 15) Plan d’alignement MCP local + +Last updated: 2026-03-07 + +## Objectif + +Faire de `Kill_LIFE` le repo de consommation et de gouvernance MCP, sans maintenir un second serveur MCP concurrent. + +## Cible supportée + +- serveur: `../mascarade/finetune/kicad_mcp_server` +- launcher local: `tools/hw/run_kicad_mcp.sh` +- config MCP versionnée: `mcp.json` +- transport: `stdio` +- serveur auxiliaire repo/specs: `tools/validate_specs.py --mcp` + +## Actions + +### 1. Source de vérité + +1. Retirer toute promesse de serveur fantôme. +2. Pointer `mcp.json` vers le launcher supporté et les MCP auxiliaires réels. +3. Basculer la doc MCP canonique sur `docs/MCP_SETUP.md`. + +### 2. Runtime opérable + +1. Faire de `tools/hw/cad_stack.sh mcp` un simple wrapper vers le launcher supporté. +2. Préparer un runtime writable sous `.cad-home/kicad-mcp`. +3. Garder `MASCARADE_DIR` comme override explicite, pas comme dépendance cachée. + +### 3. Documentation + +1. Transformer les duplications `ai-agentic-embedded-base/docs/*` en pointeurs vers les docs canoniques. +2. Corriger les références à `validate_specs.py`. +3. Garder `stdio only` comme politique MCP locale. + +## Critères de sortie + +- `mcp.json` ne référence plus de fichier absent +- `tools/hw/run_kicad_mcp.sh --doctor` résout un serveur réel +- `tools/hw/cad_stack.sh mcp` utilise le même chemin que `mcp.json` +- la doc root n’annonce plus `kicad-sch-mcp` comme chemin principal diff --git a/docs/plans/15_plan_mcp_stack.md b/docs/plans/15_plan_mcp_stack.md new file mode 100644 index 0000000..fe084d1 --- /dev/null +++ b/docs/plans/15_plan_mcp_stack.md @@ -0,0 +1,33 @@ +# 15) Plan MCP stack + +Last updated: 2026-03-07 + +## Objectif + +Rendre la couche MCP de `Kill_LIFE` cohérente côté config, validation locale et usage opérateur, sans embarquer une promesse cassée. + +## Décisions figées + +- `mcp.json` publie le launcher KiCad supporté `tools/hw/run_kicad_mcp.sh`. +- `Kill_LIFE` expose aussi `validate-specs` comme serveur/CLI auxiliaire de validation repo. +- Le runtime KiCad côté `Kill_LIFE` est piloté par `mcp.json` et `tools/hw/run_kicad_mcp.sh`. +- `Kill_LIFE` ne publie pas de second serveur KiCad host-side concurrent tant que la convergence n’est pas terminée. + +## Implémenté + +- [x] Ajouter `tools/validate_specs.py` comme script CLI réel. +- [x] Exposer le même script en serveur MCP `stdio` minimal. +- [x] Garder `mcp.json` sur le launcher KiCad versionné. +- [x] Ajouter des tests sur le mode CLI et le handshake MCP minimal. + +## Reste à faire + +- [ ] Documenter précisément ce que couvre `validate-specs` et ce qu’il ne couvre pas. +- [ ] Réaligner `tools/hw/cad_stack.sh mcp` sur le launcher supporté ou le déclasser explicitement. +- [ ] Réconcilier `docs/MCP_SETUP.md`, `docs/KICAD_AI_LOCAL.md` et `deploy/cad/README.md`. + +## Critères de sortie + +- `python3 tools/validate_specs.py` fonctionne sans ambigüité. +- `mcp.json` démarre un serveur MCP KiCad réel. +- La doc `Kill_LIFE` ne présente plus plusieurs vérités contradictoires pour le lancement MCP. diff --git a/docs/plans/15_todo_mcp_stack.md b/docs/plans/15_todo_mcp_stack.md new file mode 100644 index 0000000..88ccbf0 --- /dev/null +++ b/docs/plans/15_todo_mcp_stack.md @@ -0,0 +1,21 @@ +# TODO MCP stack — `Kill_LIFE` + +Last updated: 2026-03-07 + +## Sprint actuel + +- [x] Implémenter `tools/validate_specs.py`. +- [x] Rendre `mcp.json` lançable. +- [x] Ajouter un test de handshake MCP minimal. +- [ ] Documenter le périmètre exact de `validate-specs`. + +## J7 + +- [ ] Aligner la doc KiCad MCP sur un seul chemin opérateur supporté. +- [ ] Décrire la dépendance éventuelle à `mascarade` pour le runtime KiCad avancé. +- [ ] Ajouter une preuve d’exécution `tools/hw/cad_stack.sh mcp`. + +## J30 + +- [ ] Transformer `docs/MCP_SETUP.md` en doc canonique unique ou en simple pointeur. +- [ ] Ajouter une matrice `supporté / expérimental / démo`. diff --git a/specs/README.md b/specs/README.md index cf75a23..a54e8aa 100644 --- a/specs/README.md +++ b/specs/README.md @@ -12,6 +12,7 @@ Le fichier `constraints.yaml` est la **source de vérité** des contraintes non- Specs complémentaires: +- `kicad_mcp_scope_spec.md`: perimetre fonctionnel, hors scope et criteres d'acceptation du MCP KiCad supporte. - `zeroclaw_dual_hw_orchestration_spec.md`: architecture d'orchestration ZeroClaw multi-repo + double matériel. - `zeroclaw_dual_hw_todo.md`: backlog opérationnel court terme pour autonomie contrôlée. diff --git a/specs/kicad_mcp_scope_spec.md b/specs/kicad_mcp_scope_spec.md new file mode 100644 index 0000000..8d4b294 --- /dev/null +++ b/specs/kicad_mcp_scope_spec.md @@ -0,0 +1,136 @@ +# Spec perimetre MCP KiCad + +Last updated: 2026-03-07 + +## Objectifs + +- O1. Definir le perimetre fonctionnel du serveur MCP KiCad supporte par `Kill_LIFE`. +- O2. Eviter la derive entre implementation serveur, launcher local, docs et attentes des agents. +- O3. Fixer une frontiere claire entre ce que le MCP KiCad MUST couvrir en v1, ce qui SHOULD venir en v2, et ce qui reste hors scope. + +## Non-objectifs + +- N1. Faire du serveur MCP KiCad un assistant generaliste ou un agent conversationnel. +- N2. Exposer un transport reseau ou multi-tenant en v1. +- N3. Couvrir Git, CI, docs repo, orchestration infra, ou des actions hors CAD. +- N4. Ajouter des effets de bord caches hors des chemins explicitement fournis par l'utilisateur ou le runtime. + +## Ownership + +- Implementation serveur de reference: `mascarade/finetune/kicad_mcp_server` +- Contrat local, launcher, smoke test et doc d'usage: `Kill_LIFE` +- Consommateurs externes eventuels: autres repos, sans ownership serveur + +## User stories + +- US1. En tant qu'ingenieur hardware, je veux creer, ouvrir, modifier et exporter un projet KiCad depuis un client MCP local. +- US2. En tant qu'agent outille, je veux inspecter schema, board, librairies et regles sans corrompre le projet. +- US3. En tant qu'operateur de fabrication, je veux lancer les validations et exports minimaux avant production. +- US4. En tant qu'agent de sourcing, je veux relier composants, footprints, datasheets et references JLCPCB/LCSC. + +## Exigences fonctionnelles v1 + +- F1. Transport + - Le serveur MUST exposer `stdio` local uniquement. + - Le serveur MUST parler JSON-RPC compatible MCP avec framing ligne par ligne, conforme au SDK utilise. + - Le serveur MUST fonctionner via `tools/hw/run_kicad_mcp.sh`. + +- F2. Cycle projet + - Le serveur MUST permettre de creer, ouvrir, sauvegarder et inspecter un projet KiCad. + - Le serveur MUST distinguer les operations de lecture des operations de mutation. + +- F3. Schema + - Le serveur MUST permettre le placement de symboles, la connexion de pins, la pose de labels/nets et la generation de netlist. + - Le serveur SHOULD permettre l'enrichissement des champs de composant utiles a la suite du flux CAD. + +- F4. PCB / layout + - Le serveur MUST permettre l'inspection du board, des couches, des extents et de la geometrie. + - Le serveur MUST couvrir outline, trous de fixation, textes, zones et operations de placement/deplacement/rotation de composants. + +- F5. Routage + - Le serveur MUST permettre la creation ou l'inspection de pistes, vias, pads, nets et couches utiles au routage. + - Le serveur SHOULD exposer des aides de recherche/outillage de routage. + +- F6. Librairies + - Le serveur MUST lister et rechercher des symboles et footprints. + - Le serveur MUST permettre l'enregistrement de librairies projet/globales quand l'operation est explicitement demandee. + - Le serveur SHOULD permettre la creation de symboles et footprints custom en local. + +- F7. Validation + - Le serveur MUST permettre l'execution de checks de validation de type DRC ou equivalent. + - Le serveur MUST renvoyer des erreurs structurees et exploitables quand une validation echoue. + +- F8. Export + - Le serveur MUST couvrir les exports minimaux de fabrication et de revue: Gerber, PDF, 3D ou sorties equivalentes deja supportees par l'implementation. + - Le serveur SHOULD rendre le chemin de sortie explicite. + +- F9. Sourcing + - Le serveur SHOULD permettre la recherche de composants JLCPCB/LCSC, les alternatives proches et l'acces aux datasheets. + - Les integrations de sourcing MAY utiliser un backend local ou une API externe si les credentials sont presents. + +- F10. Runtime + - Le serveur MUST preferer un backend KiCad reel. + - Le backend SHOULD preferer IPC quand disponible. + - Le backend MUST pouvoir retomber sur SWIG si IPC n'est pas disponible. + - Le runtime MUST fonctionner soit sur host avec `pcbnew`, soit via le conteneur KiCad supporte. + +## Exigences non-fonctionnelles + +- Securite + - Le serveur MUST rester local par defaut. + - Le serveur MUST eviter les ecritures hors `projectPath`, `libraryPath`, runtime home et `KICAD_MCP_DATA_DIR`. + - Le serveur MUST NOT exiger de secrets pour les usages locaux de base. + +- Fiabilite + - `initialize` puis `tools/list` MUST passer sur une machine supportee. + - Le launcher MUST gerer le fallback host -> container quand l'hote n'expose pas `pcbnew`. + - Les erreurs MUST etre actionnables et non silencieuses. + +- Determinisme + - Les outils de lecture MUST NOT muter l'etat du projet. + - Les outils de mutation SHOULD exiger des parametres explicites et des chemins non ambigus. + +- Observabilite + - Le serveur MUST garder les erreurs et warnings exploitables. + - Le niveau de log par defaut SHOULD etre discret. + - En succes nominal, le runtime SHOULD eviter de polluer `stderr` avec du bruit de demarrage non essentiel. + +- Compatibilite + - Le contrat de transport MUST rester stable pour les consommateurs locaux de `Kill_LIFE`. + - Le runtime conteneur supporte SHOULD rester aligne avec la version KiCad cible du projet, y compris la trajectoire v10. + +## V2 / extensions legitimes + +- V2.1. Session IPC plus riche, synchronisee avec une UI KiCad vivante. +- V2.2. Coverage schematic plus avancee: edition structurelle plus large, contraintes electriques plus fines, automation de patterns repetitifs. +- V2.3. Workflows de revue plus riches: snapshots, diff de board/schema, previews avant mutation. +- V2.4. Sourcing plus fort: BOM, substitutions, scoring cout/disponibilite. +- V2.5. Dry-run systematique pour les mutations a fort impact. + +## Hors scope + +- H1. Chat libre, planification generale, ou raisonnement non CAD. +- H2. Serveur MCP HTTP/SSE expose sur le reseau par defaut. +- H3. Git operations, PR management, CI/CD, ou orchestration repo. +- H4. Gestion de secrets globale de la machine. +- H5. Autonomie d'achat ou de commande fournisseur. +- H6. Couverture d'autres outils CAD hors KiCad dans ce serveur de reference. + +## Contrats / interfaces + +- I1. Point d'entree supporte: `tools/hw/run_kicad_mcp.sh` +- I2. Config consommatrice supportee: `mcp.json` cote `Kill_LIFE` +- I3. Implementation serveur de reference: `mascarade/finetune/kicad_mcp_server` +- I4. Smoke minimal supporte: + - `initialize` + - `notifications/initialized` + - `tools/list` + +## Critieres d'acceptation + +- AC1. Le repo documente un seul serveur KiCad MCP supporte en v1. +- AC2. Le serveur expose au moins une famille d'outils fonctionnelle pour: projet, schema, PCB, librairies, validation, export. +- AC3. `python3 tools/hw/mcp_smoke.py` passe sur un environnement supporte. +- AC4. En absence de `pcbnew` host, le launcher supporte bascule vers le runtime conteneur. +- AC5. La doc d'usage locale renvoie explicitement vers cette spec de perimetre. +- AC6. Les sujets hors scope sont documentes au lieu d'etre supposes implicitement. diff --git a/specs/mcp_tasks.md b/specs/mcp_tasks.md new file mode 100644 index 0000000..33f1d7e --- /dev/null +++ b/specs/mcp_tasks.md @@ -0,0 +1,40 @@ +# Tasks MCP local + +Last updated: 2026-03-07 + +Format: + +- `[ ]` non fait +- `[x]` fait + +## Sprint actif + +- [x] K-001 — Rendre `validate-specs` réel ou retirer la promesse + - AC: aucun chemin absent n’est versionné comme serveur MCP. + +- [x] K-002 — Ajouter un launcher supporté `tools/hw/run_kicad_mcp.sh` + - AC: le launcher résout `mascarade`, prépare un runtime writable et exécute le serveur. + +- [x] K-003 — Aligner `tools/hw/cad_stack.sh mcp` + - AC: la commande `mcp` appelle le même launcher que `mcp.json`. + +- [x] K-004 — Désigner `docs/MCP_SETUP.md` comme doc canonique + - AC: la version miroir renvoie vers la doc racine. + +- [x] K-005 — Retirer les références à `tools/validate_specs.py` + - AC: les docs visibles pointent vers un validateur réellement présent. + +## À faire ensuite + +- [x] K-006 — Ajouter un smoke test MCP consommateur côté `Kill_LIFE` + - AC: un script versionné existe pour tester `initialize` et `tools/list`. + +- [ ] K-007 — Documenter les prérequis machine minimum pour le runtime MCP + - AC: Node, KiCad et repo compagnon sont explicitement listés. + +- [ ] K-008 — Décider du sort final du service Docker `kicad-mcp` historique + - AC: soit supprimé, soit rebranché sur le runtime supporté sans drift. + +- [ ] K-009 — Faire passer le smoke réel `initialize -> tools/list` + - AC: `python3 tools/hw/mcp_smoke.py` passe sur une machine avec `pcbnew` disponible. + - Blocage actuel: le host audité n’expose pas `pcbnew`, donc le runtime KiCad ne peut pas démarrer. diff --git a/test/test_validate_specs.py b/test/test_validate_specs.py new file mode 100644 index 0000000..e2e0a38 --- /dev/null +++ b/test/test_validate_specs.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import subprocess +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPO_ROOT / "tools" / "validate_specs.py" + + +def write_message(proc: subprocess.Popen[str], payload: dict) -> None: + body = json.dumps(payload) + proc.stdin.write(f"Content-Length: {len(body.encode('utf-8'))}\r\n\r\n{body}") + proc.stdin.flush() + + +def read_message(proc: subprocess.Popen[str]) -> dict: + headers = {} + while True: + line = proc.stdout.readline() + if not line: + raise AssertionError("unexpected EOF while reading MCP headers") + if line in ("\r\n", "\n"): + break + key, _, value = line.partition(":") + headers[key.strip().lower()] = value.strip() + + length = int(headers["content-length"]) + body = proc.stdout.read(length) + return json.loads(body) + + +class ValidateSpecsTests(unittest.TestCase): + def test_cli_json_mode_emits_machine_readable_summary(self): + proc = subprocess.run( + ["python3", str(SCRIPT), "--json"], + cwd=REPO_ROOT, + capture_output=True, + text=True, + check=False, + ) + + payload = json.loads(proc.stdout) + self.assertIn("ok", payload) + self.assertIn("compliance", payload) + self.assertIn("rfc2119", payload) + + def test_mcp_server_supports_initialize_and_tools_list(self): + proc = subprocess.Popen( + ["python3", str(SCRIPT), "--mcp"], + cwd=REPO_ROOT, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + try: + write_message( + proc, + { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": {}, + }, + ) + initialize = read_message(proc) + self.assertEqual(initialize["id"], 1) + self.assertEqual( + initialize["result"]["serverInfo"]["name"], "validate-specs" + ) + + write_message( + proc, + { + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {}, + }, + ) + tools_list = read_message(proc) + tool_names = {tool["name"] for tool in tools_list["result"]["tools"]} + self.assertIn("validate_specs", tool_names) + self.assertIn("scan_rfc2119", tool_names) + finally: + proc.terminate() + proc.wait(timeout=5) + if proc.stdin: + proc.stdin.close() + if proc.stdout: + proc.stdout.close() + if proc.stderr: + proc.stderr.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/validate_specs.py b/tools/validate_specs.py new file mode 100644 index 0000000..9afd726 --- /dev/null +++ b/tools/validate_specs.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +"""Validate repo specs as a CLI and as a minimal MCP stdio server.""" + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from pathlib import Path +from typing import Any, Dict + + +ROOT = Path(__file__).resolve().parents[1] +RFC2119_TERMS = ("MUST", "SHOULD", "MAY") +RFC2119_FORBIDDEN = ("must", "should", "may", "Must", "Should", "May") + + +def run_repo_command(args: list[str]) -> Dict[str, Any]: + proc = subprocess.run( + args, + cwd=ROOT, + capture_output=True, + text=True, + check=False, + ) + return { + "ok": proc.returncode == 0, + "returncode": proc.returncode, + "stdout": proc.stdout.strip(), + "stderr": proc.stderr.strip(), + } + + +def scan_rfc2119() -> Dict[str, Any]: + files = sorted((ROOT / "specs").rglob("*.md")) + counts = {term: 0 for term in RFC2119_TERMS} + forbidden_matches: list[str] = [] + file_summary: Dict[str, Dict[str, Any]] = {} + + for file_path in files: + text = file_path.read_text(encoding="utf-8") + relative = file_path.relative_to(ROOT).as_posix() + file_summary[relative] = { + term: len(re.findall(rf"\b{term}\b", text)) for term in RFC2119_TERMS + } + file_summary[relative]["forbidden"] = [] + + for term in RFC2119_TERMS: + counts[term] += file_summary[relative][term] + + for forbidden in RFC2119_FORBIDDEN: + matches = re.findall(rf"\b{forbidden}\b", text) + if matches: + forbidden_matches.extend(matches) + file_summary[relative]["forbidden"].extend(matches) + + return { + "spec_file_count": len(files), + "counts": counts, + "forbidden": forbidden_matches, + "files": file_summary, + "ok": len(files) > 0 and not forbidden_matches, + } + + +def validate_specs(strict: bool = False) -> Dict[str, Any]: + required_files = [ + "specs/03_plan.md", + "specs/04_tasks.md", + "compliance/plan.yaml", + ] + missing_files = [path for path in required_files if not (ROOT / path).exists()] + + compliance_cmd = [sys.executable, str(ROOT / "tools/compliance/validate.py")] + if strict: + compliance_cmd.append("--strict") + compliance = run_repo_command(compliance_cmd) + + rfc2119 = scan_rfc2119() + + ok = not missing_files and compliance["ok"] and rfc2119["ok"] + return { + "ok": ok, + "missing_files": missing_files, + "strict": strict, + "compliance": compliance, + "rfc2119": rfc2119, + } + + +def format_cli_summary(result: Dict[str, Any]) -> str: + status = "OK" if result["ok"] else "FAIL" + compliance = result["compliance"] + rfc2119 = result["rfc2119"] + lines = [ + f"{status}: spec validation", + f"- missing files: {len(result['missing_files'])}", + f"- compliance ok: {compliance['ok']}", + ( + "- RFC2119 counts: " + f"MUST={rfc2119['counts']['MUST']} " + f"SHOULD={rfc2119['counts']['SHOULD']} " + f"MAY={rfc2119['counts']['MAY']}" + ), + f"- RFC2119 forbidden terms: {len(rfc2119['forbidden'])}", + ] + + if result["missing_files"]: + lines.append("- missing: " + ", ".join(result["missing_files"])) + if compliance["stdout"]: + lines.append("- compliance stdout: " + compliance["stdout"]) + if compliance["stderr"]: + lines.append("- compliance stderr: " + compliance["stderr"]) + + return "\n".join(lines) + + +def tool_validate_specs(arguments: Dict[str, Any]) -> Dict[str, Any]: + strict = bool(arguments.get("strict", False)) + result = validate_specs(strict=strict) + return { + "content": [{"type": "text", "text": format_cli_summary(result)}], + "structuredContent": result, + "isError": not result["ok"], + } + + +def tool_scan_rfc2119(_: Dict[str, Any]) -> Dict[str, Any]: + result = scan_rfc2119() + summary = ( + "RFC2119 summary: " + f"MUST={result['counts']['MUST']} " + f"SHOULD={result['counts']['SHOULD']} " + f"MAY={result['counts']['MAY']} " + f"forbidden={len(result['forbidden'])}" + ) + return { + "content": [{"type": "text", "text": summary}], + "structuredContent": result, + "isError": not result["ok"], + } + + +TOOLS = [ + { + "name": "validate_specs", + "description": "Validate Kill_LIFE spec structure, compliance profile and RFC2119 usage.", + "inputSchema": { + "type": "object", + "properties": { + "strict": { + "type": "boolean", + "description": "Enable strict evidence checks from tools/compliance/validate.py.", + "default": False, + } + }, + }, + }, + { + "name": "scan_rfc2119", + "description": "Summarize RFC2119 keywords across specs/*.md without writing files.", + "inputSchema": {"type": "object", "properties": {}}, + }, +] + + +def make_response(request_id: Any, result: Dict[str, Any]) -> Dict[str, Any]: + return {"jsonrpc": "2.0", "id": request_id, "result": result} + + +def make_error(request_id: Any, code: int, message: str) -> Dict[str, Any]: + return {"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}} + + +def read_message() -> Dict[str, Any] | None: + headers: Dict[str, str] = {} + while True: + line = sys.stdin.buffer.readline() + if not line: + return None + if line in (b"\r\n", b"\n"): + break + key, _, value = line.decode("utf-8").partition(":") + headers[key.strip().lower()] = value.strip() + + content_length = int(headers.get("content-length", "0")) + if content_length <= 0: + return None + + body = sys.stdin.buffer.read(content_length) + if not body: + return None + + return json.loads(body.decode("utf-8")) + + +def write_message(message: Dict[str, Any]) -> None: + payload = json.dumps(message).encode("utf-8") + sys.stdout.buffer.write(f"Content-Length: {len(payload)}\r\n\r\n".encode("utf-8")) + sys.stdout.buffer.write(payload) + sys.stdout.buffer.flush() + + +def serve_mcp() -> int: + while True: + request = read_message() + if request is None: + return 0 + + method = request.get("method") + request_id = request.get("id") + params = request.get("params") or {} + + if method == "initialize": + write_message( + make_response( + request_id, + { + "protocolVersion": "2025-06-18", + "capabilities": {"tools": {"listChanged": False}}, + "serverInfo": { + "name": "validate-specs", + "version": "1.0.0", + }, + }, + ) + ) + continue + + if method == "notifications/initialized": + continue + + if method == "ping": + write_message(make_response(request_id, {})) + continue + + if method == "tools/list": + write_message(make_response(request_id, {"tools": TOOLS})) + continue + + if method == "tools/call": + tool_name = params.get("name") + arguments = params.get("arguments") or {} + + if tool_name == "validate_specs": + write_message(make_response(request_id, tool_validate_specs(arguments))) + continue + + if tool_name == "scan_rfc2119": + write_message(make_response(request_id, tool_scan_rfc2119(arguments))) + continue + + write_message(make_error(request_id, -32602, f"Unknown tool: {tool_name}")) + continue + + if request_id is not None: + write_message(make_error(request_id, -32601, f"Method not found: {method}")) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Validate Kill_LIFE specs") + parser.add_argument("--strict", action="store_true", help="Enable strict compliance evidence validation.") + parser.add_argument("--json", action="store_true", help="Print JSON output in CLI mode.") + parser.add_argument("--mcp", action="store_true", help="Run as an MCP stdio server.") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + if args.mcp: + return serve_mcp() + + result = validate_specs(strict=args.strict) + if args.json: + print(json.dumps(result, indent=2, ensure_ascii=False)) + else: + print(format_cli_summary(result)) + return 0 if result["ok"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main())