+
@@ -217,6 +297,60 @@ const styles: Record = {
panelTitle: {
marginTop: 0
},
+ badgeRow: {
+ display: "flex",
+ alignItems: "center",
+ gap: "12px",
+ marginBottom: "10px"
+ },
+ badgeMeta: {
+ color: "#8da6c8",
+ fontSize: "0.82rem"
+ },
+ badgeReady: {
+ display: "inline-block",
+ padding: "4px 10px",
+ borderRadius: "999px",
+ background: "rgba(57, 211, 132, 0.2)",
+ color: "#7df0ba",
+ border: "1px solid rgba(57, 211, 132, 0.45)",
+ fontWeight: 700,
+ letterSpacing: "0.04em",
+ fontSize: "0.74rem"
+ },
+ badgeDegraded: {
+ display: "inline-block",
+ padding: "4px 10px",
+ borderRadius: "999px",
+ background: "rgba(242, 199, 119, 0.2)",
+ color: "#ffd88f",
+ border: "1px solid rgba(242, 199, 119, 0.45)",
+ fontWeight: 700,
+ letterSpacing: "0.04em",
+ fontSize: "0.74rem"
+ },
+ badgeBlocked: {
+ display: "inline-block",
+ padding: "4px 10px",
+ borderRadius: "999px",
+ background: "rgba(242, 119, 119, 0.22)",
+ color: "#ff9f9f",
+ border: "1px solid rgba(242, 119, 119, 0.45)",
+ fontWeight: 700,
+ letterSpacing: "0.04em",
+ fontSize: "0.74rem"
+ },
+ badgeUnknown: {
+ display: "inline-block",
+ padding: "4px 10px",
+ borderRadius: "999px",
+ background: "rgba(141, 166, 200, 0.2)",
+ color: "#c7d5ea",
+ border: "1px solid rgba(141, 166, 200, 0.45)",
+ fontWeight: 700,
+ letterSpacing: "0.04em",
+ fontSize: "0.74rem"
+ },
list: {
margin: 0,
paddingLeft: "18px",
diff --git a/web/e2e/smoke.spec.ts b/web/e2e/smoke.spec.ts
new file mode 100644
index 0000000..7eea707
--- /dev/null
+++ b/web/e2e/smoke.spec.ts
@@ -0,0 +1,11 @@
+import { expect, test } from "@playwright/test";
+
+test("homepage smoke", async ({ page }) => {
+ await page.goto("/");
+
+ await expect(page).toHaveTitle(/YiACAD Web/i);
+ await expect(page.getByRole("heading", { name: "Project dashboard" })).toBeVisible();
+ await expect(page.getByText("Git-based EDA platform")).toBeVisible();
+ await expect(page.getByRole("heading", { name: "Core platform" })).toBeVisible();
+ await expect(page.getByRole("heading", { name: "Infra VPS" })).toBeVisible();
+});
\ No newline at end of file
diff --git a/web/package-lock.json b/web/package-lock.json
index 1d752a1..1a86a84 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -12,6 +12,7 @@
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/ws": "^8.18.1",
+ "@y/websocket-server": "^0.1.5",
"bullmq": "5.71.0",
"cors": "^2.8.6",
"express": "^5.2.1",
@@ -25,6 +26,7 @@
"yjs": "13.6.30"
},
"devDependencies": {
+ "@playwright/test": "1.41.2",
"@types/node": "^20.17.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
@@ -632,6 +634,23 @@
"node": ">= 10"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.41.2.tgz",
+ "integrity": "sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==",
+ "deprecated": "Please update to the latest version of Playwright to test up-to-date browsers.",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.41.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/@radix-ui/primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
@@ -1688,6 +1707,131 @@
"node": ">=18.0.0"
}
},
+ "node_modules/@y/protocols": {
+ "version": "1.0.6-rc.1",
+ "resolved": "https://registry.npmjs.org/@y/protocols/-/protocols-1.0.6-rc.1.tgz",
+ "integrity": "sha512-e/qs7hXcLk/SeNitxMXv2ymozyWFTULwbJEi7cAf/K/iXw9nGwGXHrR5TNluQ/bMwOX1cwuUT0hjEojkfH0gsA==",
+ "license": "MIT",
+ "dependencies": {
+ "lib0": "^1.0.0-rc.1"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ },
+ "peerDependencies": {
+ "@y/y": "*"
+ }
+ },
+ "node_modules/@y/protocols/node_modules/lib0": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-1.0.0-rc.7.tgz",
+ "integrity": "sha512-V5T141UN99W0uR2ULcmOBdRXl7tA21G6PLnMYpm1Ub5pn3ULbN4BHPKovgoWLZF0hrY0C8gd9uQJnuAGmVhF6w==",
+ "license": "MIT",
+ "bin": {
+ "0ecdsa-generate-keypair": "src/bin/0ecdsa-generate-keypair.js",
+ "0gentesthtml": "src/bin/gentesthtml.js",
+ "0serve": "src/bin/0serve.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/@y/websocket-server": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/@y/websocket-server/-/websocket-server-0.1.5.tgz",
+ "integrity": "sha512-ue1zENKxwj66AV8QoTwk0MPEH6veUhgvw+Gn84iX6Tco4lN9DHuF9k8oEKObJSPWoKBsOjeUk/ZYJcSK7iGc1w==",
+ "license": "MIT",
+ "dependencies": {
+ "@y/protocols": "^1.0.6-1",
+ "lib0": "^0.2.102",
+ "ws": "^6.2.1",
+ "yjs": "^14.0.0-7"
+ },
+ "bin": {
+ "y-websocket": "src/server.js",
+ "y-websocket-server": "src/server.js"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/@y/websocket-server/node_modules/ws": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
+ "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
+ "license": "MIT",
+ "dependencies": {
+ "async-limiter": "~1.0.0"
+ }
+ },
+ "node_modules/@y/websocket-server/node_modules/yjs": {
+ "version": "14.0.0-16",
+ "resolved": "https://registry.npmjs.org/yjs/-/yjs-14.0.0-16.tgz",
+ "integrity": "sha512-n7jMrQz4pgU/NFnf4qY53K2adR/fu6ViQ79qVIw6Og+BtuDs1hx3DjOi3iREVnA6tsxQXXVG3gvG0I2kpmAwoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "lib0": "^0.2.115-6"
+ },
+ "engines": {
+ "node": ">=16.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/@y/y": {
+ "version": "14.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/@y/y/-/y-14.0.0-rc.7.tgz",
+ "integrity": "sha512-J2nZO3+fMnGpRgqVFnOUX+AgqbuRSmUROl+fuJSPJFfk0gR5/Tb6WRqJAzpYSMf37HM5NGpUMqgSXA8wN3VpyA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "lib0": "^1.0.0-rc.7"
+ },
+ "engines": {
+ "node": ">=22.0.0",
+ "npm": ">=8.0.0"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
+ "node_modules/@y/y/node_modules/lib0": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-1.0.0-rc.7.tgz",
+ "integrity": "sha512-V5T141UN99W0uR2ULcmOBdRXl7tA21G6PLnMYpm1Ub5pn3ULbN4BHPKovgoWLZF0hrY0C8gd9uQJnuAGmVhF6w==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "0ecdsa-generate-keypair": "src/bin/0ecdsa-generate-keypair.js",
+ "0gentesthtml": "src/bin/gentesthtml.js",
+ "0serve": "src/bin/0serve.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "GitHub Sponsors ❤",
+ "url": "https://github.com/sponsors/dmonad"
+ }
+ },
"node_modules/accepts": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
@@ -1726,6 +1870,12 @@
"node": ">=10"
}
},
+ "node_modules/async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==",
+ "license": "MIT"
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -4218,6 +4368,52 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/playwright": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.41.2.tgz",
+ "integrity": "sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.41.2"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.41.2",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.41.2.tgz",
+ "integrity": "sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/png-chunk-text": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/png-chunk-text/-/png-chunk-text-1.0.0.tgz",
diff --git a/web/package.json b/web/package.json
index fa8e900..bc9dbde 100644
--- a/web/package.json
+++ b/web/package.json
@@ -9,13 +9,16 @@
"start": "next start",
"dev:realtime": "node ./realtime/server.mjs",
"worker:eda": "node ./workers/eda-worker.mjs",
- "vendor:kicanvas": "node ./scripts/vendor-kicanvas.mjs"
+ "vendor:kicanvas": "node ./scripts/vendor-kicanvas.mjs",
+ "test:e2e": "playwright test",
+ "test:e2e:smoke": "playwright test e2e/smoke.spec.ts"
},
"dependencies": {
"@excalidraw/excalidraw": "0.18.0",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/ws": "^8.18.1",
+ "@y/websocket-server": "^0.1.5",
"bullmq": "5.71.0",
"cors": "^2.8.6",
"express": "^5.2.1",
@@ -29,6 +32,7 @@
"yjs": "13.6.30"
},
"devDependencies": {
+ "@playwright/test": "1.41.2",
"@types/node": "^20.17.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
diff --git a/web/playwright.config.ts b/web/playwright.config.ts
new file mode 100644
index 0000000..be2f4aa
--- /dev/null
+++ b/web/playwright.config.ts
@@ -0,0 +1,33 @@
+import { defineConfig, devices } from "@playwright/test";
+
+const port = Number(process.env.PLAYWRIGHT_PORT ?? 3000);
+const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? `http://127.0.0.1:${port}`;
+
+export default defineConfig({
+ testDir: "./e2e",
+ fullyParallel: true,
+ forbidOnly: !!process.env.CI,
+ retries: process.env.CI ? 2 : 0,
+ reporter: process.env.CI ? [["html", { open: "never" }], ["list"]] : "list",
+ use: {
+ baseURL,
+ trace: "on-first-retry"
+ },
+ webServer: {
+ command: `npm run dev -- --hostname 127.0.0.1 --port ${port}`,
+ url: baseURL,
+ reuseExistingServer: !process.env.CI,
+ stdout: "pipe",
+ stderr: "pipe",
+ timeout: 120000
+ },
+ projects: [
+ {
+ name: "chromium",
+ use: {
+ ...devices["Desktop Chrome"],
+ channel: process.env.CI ? undefined : "chrome"
+ }
+ }
+ ]
+});
\ No newline at end of file
diff --git a/web/realtime/server.mjs b/web/realtime/server.mjs
index e3cf41b..7c18ebe 100644
--- a/web/realtime/server.mjs
+++ b/web/realtime/server.mjs
@@ -1,4 +1,4 @@
-import { mkdirSync } from "node:fs";
+import { existsSync, mkdirSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { spawn } from "node:child_process";
@@ -6,7 +6,15 @@ import { spawn } from "node:child_process";
const here = dirname(fileURLToPath(import.meta.url));
const root = resolve(here, "..");
const persistenceDir = resolve(root, "realtime", "data");
-const serverEntry = resolve(root, "node_modules", "y-websocket", "bin", "server.js");
+const serverEntryCandidates = [
+ resolve(root, "node_modules", "@y", "websocket-server", "src", "server.js"),
+ resolve(root, "node_modules", "y-websocket", "bin", "server.js")
+];
+
+const serverEntry = serverEntryCandidates.find((candidate) => {
+ // Prefer the modern @y/websocket-server entrypoint when available.
+ return existsSync(candidate);
+}) ?? serverEntryCandidates[0];
mkdirSync(persistenceDir, { recursive: true });
--
2.52.0
From 7c9c91697c0009fec4c61695e08dd2bd7bb57d1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=27=C3=A9lectron=20rare?=
<108685187+electron-rare@users.noreply.github.com>
Date: Sun, 29 Mar 2026 17:14:05 +0200
Subject: [PATCH 6/6] feat(agentics): update mesh agents, gates, prompts, and
workflows
---
.github/agents/arch_mesh.md | 14 +
.github/agents/architect_agent.md | 13 -
.github/agents/doc_agent.md | 12 -
.github/agents/docs_research.md | 14 +
.github/agents/embedded_cad.md | 14 +
.github/agents/firmware.md | 14 +
.github/agents/firmware_agent.md | 13 -
.github/agents/hw_schematic_agent.md | 64 --
.github/agents/kill_life_bridge.md | 14 +
.github/agents/pm_agent.md | 13 -
.github/agents/pm_mesh.md | 14 +
.github/agents/qa_agent.md | 12 -
.github/agents/qa_compliance.md | 14 +
.github/agents/runtime_companion.md | 14 +
.github/agents/schema_guard.md | 14 +
.github/agents/sync_ops.md | 14 +
.github/agents/ux_lead.md | 14 +
.github/agents/web_cad_platform.md | 14 +
.github/copilot-instructions.md | 88 +-
.../contracts.instructions.md} | 0
.../plan_wizard_agents_coordination.prompt.md | 30 +-
.../plan_wizard_agents_management.prompt.md | 14 +-
.../prompts/plan_wizard_arch_mesh.prompt.md | 11 +
.../plan_wizard_architect_agent.prompt.md | 14 -
...rd_coordination_handoff_evidence.prompt.md | 0
.../prompts/plan_wizard_doc_agent.prompt.md | 14 -
.../plan_wizard_docs_research.prompt.md | 11 +
.../plan_wizard_embedded_cad.prompt.md | 11 +
.../prompts/plan_wizard_firmware.prompt.md | 11 +
.../plan_wizard_firmware_agent.prompt.md | 14 -
.../plan_wizard_hw_schematic_agent.prompt.md | 14 -
.../plan_wizard_kill_life_bridge.prompt.md | 11 +
.../prompts/plan_wizard_pm_agent.prompt.md | 14 -
.github/prompts/plan_wizard_pm_mesh.prompt.md | 11 +
.../prompts/plan_wizard_qa_agent.prompt.md | 14 -
.../plan_wizard_qa_compliance.prompt.md | 11 +
.../plan_wizard_runtime_companion.prompt.md | 11 +
.../plan_wizard_schema_guard.prompt.md | 11 +
.../prompts/plan_wizard_sync_ops.prompt.md | 11 +
.github/prompts/plan_wizard_ux_lead.prompt.md | 11 +
.../plan_wizard_web_cad_platform.prompt.md | 11 +
..._transversal_lot_py_cockpit_docs.prompt.md | 0
.github/prompts/start_arch_mesh.prompt.md | 11 +
.../prompts/start_architect_agent.prompt.md | 14 -
.github/prompts/start_doc_agent.prompt.md | 14 -
.github/prompts/start_docs_research.prompt.md | 11 +
.github/prompts/start_embedded_cad.prompt.md | 11 +
.github/prompts/start_firmware.prompt.md | 11 +
.../prompts/start_firmware_agent.prompt.md | 14 -
.../start_hw_schematic_agent.prompt.md | 14 -
.../prompts/start_kill_life_bridge.prompt.md | 11 +
.github/prompts/start_pm_agent.prompt.md | 14 -
.github/prompts/start_pm_mesh.prompt.md | 11 +
.github/prompts/start_qa_agent.prompt.md | 14 -
.github/prompts/start_qa_compliance.prompt.md | 11 +
.../prompts/start_runtime_companion.prompt.md | 11 +
.github/prompts/start_schema_guard.prompt.md | 11 +
.github/prompts/start_sync_ops.prompt.md | 11 +
.github/prompts/start_ux_lead.prompt.md | 11 +
.../prompts/start_web_cad_platform.prompt.md | 11 +
.github/workflows/mesh_contracts.yml | 28 +
AGENTS.md | 102 +++
README.md | 48 +-
README_FR.md | 48 +-
agents/arch_mesh.md | 20 +
agents/architect_agent.md | 26 -
agents/doc_agent.md | 29 -
agents/docs_research.md | 20 +
agents/embedded_cad.md | 20 +
agents/firmware.md | 20 +
agents/firmware_agent.md | 29 -
agents/hw_schematic_agent.md | 78 --
agents/kill_life_bridge.md | 20 +
agents/pm_agent.md | 29 -
agents/pm_mesh.md | 20 +
agents/qa_agent.md | 24 -
agents/qa_compliance.md | 20 +
agents/runtime_companion.md | 20 +
agents/schema_guard.md | 20 +
agents/sync_ops.md | 20 +
agents/ux_lead.md | 20 +
agents/web_cad_platform.md | 20 +
.../specs/contracts/agent_catalog.schema.json | 173 ++++
.../contracts/artifact_wms_index_rules.json | 28 +-
.../specs/contracts/infra_vps.schema.json | 127 +++
.../contracts/kill_life_agent_catalog.json | 537 ++++++++++++
.../contracts/ops_kill_life_erp_registry.json | 53 +-
.../specs/contracts/pcb_ai_fab_registry.json | 10 +-
.../runtime_mcp_ia_gateway.schema.json | 62 ++
bmad/gates/gate_s2.md | 0
bmad/rituals/kickoff.md | 4 +-
bmad/templates/handoff.md | 9 +
docs/AGENTIC_LANDSCAPE.md | 30 +
docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md | 216 ++---
docs/AI_WORKFLOWS.md | 17 +
docs/GLOBAL_MULTI_AGENT_CHECKLIST.md | 0
docs/plans/12_plan_gestion_des_agents.md | 761 ++----------------
...rdination_transversale_agents_kill_life.md | 0
.../30_plan_management_agents_kill_life.md | 0
.../30_todo_management_agents_kill_life.md | 0
kill_life/agent_catalog.py | 62 ++
kill_life/server.py | 76 +-
kill_life/yiacad_action_registry.py | 78 ++
specs/contracts/agent_catalog.schema.json | 173 ++++
specs/contracts/artifact_wms_index_rules.json | 28 +-
specs/contracts/kill_life_agent_catalog.json | 537 ++++++++++++
.../contracts/ops_kill_life_erp_registry.json | 53 +-
specs/contracts/pcb_ai_fab_registry.json | 10 +-
specs/contracts/yiacad_action_registry.json | 232 ++++++
.../yiacad_action_registry.schema.json | 116 +++
test/test_agent_catalog_validator.py | 170 ++++
test/test_autonomous_next_lots_catalog.py | 38 +
test/test_intelligence_tui_contract.py | 12 +
test/test_kill_life_agent_api.py | 85 ++
test/test_runtime_ai_gateway_contract.py | 80 ++
test/test_validate_specs.py | 3 +
test/test_yiacad_action_registry_contract.py | 40 +
test/test_yiacad_ai_bridge_contract.py | 41 +
test/test_yiacad_backend_contract.py | 48 ++
.../test_yiacad_freecad_workbench_contract.py | 76 ++
test/test_yiacad_kicad_plugin_contract.py | 13 +
tools/autonomous_next_lots.py | 5 +-
tools/cad/install_yiacad_native_gui.sh | 1 +
.../freecad/YiACADWorkbench/_adapter.py | 30 +
.../freecad/YiACADWorkbench/_common.py | 185 +++++
.../YiACADWorkbench/yiacad_freecad_gui.py | 302 ++++---
.../kicad/yiacad_kicad_plugin/README.md | 8 +-
.../kicad/yiacad_kicad_plugin/_common.py | 58 +-
.../yiacad_kicad_plugin/yiacad_action.py | 55 +-
tools/cad/yiacad_ai_bridge.py | 101 ++-
tools/cad/yiacad_backend.py | 51 +-
tools/cad/yiacad_backend_client.py | 51 +-
tools/cad/yiacad_backend_service.py | 56 +-
tools/cad/yiacad_fusion_lot.sh | 6 +-
tools/cad/yiacad_native_ops.py | 109 ++-
tools/cockpit/intelligence_tui.sh | 106 ++-
tools/cockpit/runtime_ai_gateway.sh | 61 +-
tools/cockpit/validate_mascarade_execution.sh | 434 ++++++++++
tools/specs/validate_agent_catalog.py | 218 +++++
tools/test_python.sh | 56 +-
tools/validate_specs.py | 25 +
web/README.md | 7 +
web/README_FR.md | 7 +
web/app/api/ops/platform/route.ts | 21 +-
web/app/api/ops/queue/route.ts | 9 +
web/app/api/ops/worker/route.ts | 9 +
web/lib/intelligence/health-aggregator.ts | 22 +-
web/lib/intelligence/ops-health.ts | 78 ++
web/playwright-report/index.html | 69 ++
web/workers/eda-worker.mjs | 45 ++
150 files changed, 5775 insertions(+), 1948 deletions(-)
create mode 100644 .github/agents/arch_mesh.md
delete mode 100644 .github/agents/architect_agent.md
delete mode 100644 .github/agents/doc_agent.md
create mode 100644 .github/agents/docs_research.md
create mode 100644 .github/agents/embedded_cad.md
create mode 100644 .github/agents/firmware.md
delete mode 100644 .github/agents/firmware_agent.md
delete mode 100644 .github/agents/hw_schematic_agent.md
create mode 100644 .github/agents/kill_life_bridge.md
delete mode 100644 .github/agents/pm_agent.md
create mode 100644 .github/agents/pm_mesh.md
delete mode 100644 .github/agents/qa_agent.md
create mode 100644 .github/agents/qa_compliance.md
create mode 100644 .github/agents/runtime_companion.md
create mode 100644 .github/agents/schema_guard.md
create mode 100644 .github/agents/sync_ops.md
create mode 100644 .github/agents/ux_lead.md
create mode 100644 .github/agents/web_cad_platform.md
rename .github/{agents/plan-audit-quotidien.agent.md => instructions/contracts.instructions.md} (100%)
create mode 100644 .github/prompts/plan_wizard_arch_mesh.prompt.md
delete mode 100644 .github/prompts/plan_wizard_architect_agent.prompt.md
create mode 100644 .github/prompts/plan_wizard_coordination_handoff_evidence.prompt.md
delete mode 100644 .github/prompts/plan_wizard_doc_agent.prompt.md
create mode 100644 .github/prompts/plan_wizard_docs_research.prompt.md
create mode 100644 .github/prompts/plan_wizard_embedded_cad.prompt.md
create mode 100644 .github/prompts/plan_wizard_firmware.prompt.md
delete mode 100644 .github/prompts/plan_wizard_firmware_agent.prompt.md
delete mode 100644 .github/prompts/plan_wizard_hw_schematic_agent.prompt.md
create mode 100644 .github/prompts/plan_wizard_kill_life_bridge.prompt.md
delete mode 100644 .github/prompts/plan_wizard_pm_agent.prompt.md
create mode 100644 .github/prompts/plan_wizard_pm_mesh.prompt.md
delete mode 100644 .github/prompts/plan_wizard_qa_agent.prompt.md
create mode 100644 .github/prompts/plan_wizard_qa_compliance.prompt.md
create mode 100644 .github/prompts/plan_wizard_runtime_companion.prompt.md
create mode 100644 .github/prompts/plan_wizard_schema_guard.prompt.md
create mode 100644 .github/prompts/plan_wizard_sync_ops.prompt.md
create mode 100644 .github/prompts/plan_wizard_ux_lead.prompt.md
create mode 100644 .github/prompts/plan_wizard_web_cad_platform.prompt.md
create mode 100644 .github/prompts/review_transversal_lot_py_cockpit_docs.prompt.md
create mode 100644 .github/prompts/start_arch_mesh.prompt.md
delete mode 100644 .github/prompts/start_architect_agent.prompt.md
delete mode 100644 .github/prompts/start_doc_agent.prompt.md
create mode 100644 .github/prompts/start_docs_research.prompt.md
create mode 100644 .github/prompts/start_embedded_cad.prompt.md
create mode 100644 .github/prompts/start_firmware.prompt.md
delete mode 100644 .github/prompts/start_firmware_agent.prompt.md
delete mode 100644 .github/prompts/start_hw_schematic_agent.prompt.md
create mode 100644 .github/prompts/start_kill_life_bridge.prompt.md
delete mode 100644 .github/prompts/start_pm_agent.prompt.md
create mode 100644 .github/prompts/start_pm_mesh.prompt.md
delete mode 100644 .github/prompts/start_qa_agent.prompt.md
create mode 100644 .github/prompts/start_qa_compliance.prompt.md
create mode 100644 .github/prompts/start_runtime_companion.prompt.md
create mode 100644 .github/prompts/start_schema_guard.prompt.md
create mode 100644 .github/prompts/start_sync_ops.prompt.md
create mode 100644 .github/prompts/start_ux_lead.prompt.md
create mode 100644 .github/prompts/start_web_cad_platform.prompt.md
create mode 100644 AGENTS.md
create mode 100644 agents/arch_mesh.md
delete mode 100644 agents/architect_agent.md
delete mode 100644 agents/doc_agent.md
create mode 100644 agents/docs_research.md
create mode 100644 agents/embedded_cad.md
create mode 100644 agents/firmware.md
delete mode 100644 agents/firmware_agent.md
delete mode 100644 agents/hw_schematic_agent.md
create mode 100644 agents/kill_life_bridge.md
delete mode 100644 agents/pm_agent.md
create mode 100644 agents/pm_mesh.md
delete mode 100644 agents/qa_agent.md
create mode 100644 agents/qa_compliance.md
create mode 100644 agents/runtime_companion.md
create mode 100644 agents/schema_guard.md
create mode 100644 agents/sync_ops.md
create mode 100644 agents/ux_lead.md
create mode 100644 agents/web_cad_platform.md
create mode 100644 ai-agentic-embedded-base/specs/contracts/agent_catalog.schema.json
create mode 100644 ai-agentic-embedded-base/specs/contracts/infra_vps.schema.json
create mode 100644 ai-agentic-embedded-base/specs/contracts/kill_life_agent_catalog.json
create mode 100644 bmad/gates/gate_s2.md
create mode 100644 docs/GLOBAL_MULTI_AGENT_CHECKLIST.md
create mode 100644 docs/plans/29_plan_coordination_transversale_agents_kill_life.md
create mode 100644 docs/plans/30_plan_management_agents_kill_life.md
create mode 100644 docs/plans/30_todo_management_agents_kill_life.md
create mode 100644 kill_life/agent_catalog.py
create mode 100644 kill_life/yiacad_action_registry.py
create mode 100644 specs/contracts/agent_catalog.schema.json
create mode 100644 specs/contracts/kill_life_agent_catalog.json
create mode 100644 specs/contracts/yiacad_action_registry.json
create mode 100644 specs/contracts/yiacad_action_registry.schema.json
create mode 100644 test/test_agent_catalog_validator.py
create mode 100644 test/test_autonomous_next_lots_catalog.py
create mode 100644 test/test_kill_life_agent_api.py
create mode 100644 test/test_yiacad_action_registry_contract.py
create mode 100644 test/test_yiacad_ai_bridge_contract.py
create mode 100644 test/test_yiacad_freecad_workbench_contract.py
create mode 100644 tools/cad/integrations/freecad/YiACADWorkbench/_adapter.py
create mode 100644 tools/cad/integrations/freecad/YiACADWorkbench/_common.py
mode change 100755 => 100644 tools/cad/yiacad_ai_bridge.py
create mode 100644 tools/cockpit/validate_mascarade_execution.sh
create mode 100644 tools/specs/validate_agent_catalog.py
create mode 100644 web/app/api/ops/queue/route.ts
create mode 100644 web/app/api/ops/worker/route.ts
create mode 100644 web/lib/intelligence/ops-health.ts
create mode 100644 web/playwright-report/index.html
diff --git a/.github/agents/arch_mesh.md b/.github/agents/arch_mesh.md
new file mode 100644
index 0000000..8968277
--- /dev/null
+++ b/.github/agents/arch_mesh.md
@@ -0,0 +1,14 @@
+# Arch-Mesh
+
+Canonical runtime/doc agent for architecture, ADRs, and contract boundaries.
+
+## Runtime contract
+- owner_agent: `Arch-Mesh`
+- subagents: `Contract-Guard`, `Mesh-Contracts`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Reconcile specs and constraints.
+2. Keep interfaces versioned and explicit.
+3. Publish architecture rationale with evidence.
diff --git a/.github/agents/architect_agent.md b/.github/agents/architect_agent.md
deleted file mode 100644
index 1460320..0000000
--- a/.github/agents/architect_agent.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Architect Agent
-
-Objectif : produire/mettre à jour `02_arch.md` + ADR.
-Doit respecter standards + contraintes, et garder les interfaces versionnées.
-
-## Plan
-1. Analyse des specs et contraintes (specs/01_spec.md, specs/constraints.yaml)
-2. Brainstorming architecture (diagrammes, interfaces)
-3. Rédaction ou mise à jour `02_arch.md` (architecture détaillée)
-4. Validation des gates BMAD (rituels, handoffs)
-5. Documentation ADR (Architecture Decision Record)
-6. Evidence pack (artifacts/arch/)
-7. Mise à jour des prompts agents si impact
diff --git a/.github/agents/doc_agent.md b/.github/agents/doc_agent.md
deleted file mode 100644
index efb30d1..0000000
--- a/.github/agents/doc_agent.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Doc Agent
-
-Objectif : maintenir `docs/` + README, sans blabla.
-
-## Plan
-1. Analyse des specs, plan, evidence pack
-2. Mise à jour docs/, README, guides, FAQ
-3. Ajout d’exemples de projet minimal
-4. Documentation des pipelines et conventions
-5. Changelog si impact
-6. Onboarding & feedback
-7. Release documentation
diff --git a/.github/agents/docs_research.md b/.github/agents/docs_research.md
new file mode 100644
index 0000000..9f29880
--- /dev/null
+++ b/.github/agents/docs_research.md
@@ -0,0 +1,14 @@
+# Docs-Research
+
+Canonical runtime/doc agent for documentation, research, and agent catalog references.
+
+## Runtime contract
+- owner_agent: `Docs-Research`
+- subagents: `Doc-Entry`, `Plan-Recorder`, `Agent-Catalog`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Refresh canonical entry points and docs.
+2. Keep plan memory and research coherent.
+3. Attach evidence links to operator-facing docs.
diff --git a/.github/agents/embedded_cad.md b/.github/agents/embedded_cad.md
new file mode 100644
index 0000000..e6fd97a
--- /dev/null
+++ b/.github/agents/embedded_cad.md
@@ -0,0 +1,14 @@
+# Embedded-CAD
+
+Canonical runtime/doc agent for CAD tooling, hardware assets, and fabrication-facing lanes.
+
+## Runtime contract
+- owner_agent: `Embedded-CAD`
+- subagents: `CAD-Bridge`, `HW-BOM`, `CAD-Fusion`
+- gates: `bmad/gates/gate_s0.md`, `bmad/gates/gate_s1.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Keep CAD and hardware lanes coherent.
+2. Emit fabrication evidence and degraded reasons.
+3. Use subagents only as metadata for the active lane.
diff --git a/.github/agents/firmware.md b/.github/agents/firmware.md
new file mode 100644
index 0000000..9816f54
--- /dev/null
+++ b/.github/agents/firmware.md
@@ -0,0 +1,14 @@
+# Firmware
+
+Canonical runtime/doc agent for embedded firmware and PlatformIO evidence.
+
+## Runtime contract
+- owner_agent: `Firmware`
+- subagents: `FW-Build`
+- gate: `bmad/gates/gate_s1.md`
+- handoff: `specs/contracts/operator_lane_evidence.schema.json`
+
+## Plan
+1. Keep embedded code and tests reproducible.
+2. Publish build/test evidence.
+3. Escalate blockers through explicit degraded reasons.
diff --git a/.github/agents/firmware_agent.md b/.github/agents/firmware_agent.md
deleted file mode 100644
index 8e26372..0000000
--- a/.github/agents/firmware_agent.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Firmware Agent
-
-Objectif : implémenter le plan dans `firmware/` avec tests Unity.
-Règles :
-
-## Plan
-1. Analyse du plan et des specs (specs/03_plan.md, 01_spec.md)
-2. Implémentation firmware (firmware/src/)
-3. Ajout ou mise à jour des tests Unity (firmware/test/)
-4. Validation via commandes PlatformIO
-5. Evidence pack (artifacts/firmware/)
-6. Documentation des commandes et conventions
-7. Release & versioning
diff --git a/.github/agents/hw_schematic_agent.md b/.github/agents/hw_schematic_agent.md
deleted file mode 100644
index 359c4ac..0000000
--- a/.github/agents/hw_schematic_agent.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# HW Schematic Agent (bulk edits + briques)
-
-Objectif :
-
-Ce rôle est conçu pour être appelé par un orchestrateur (PM/Architect/Codex) sur des tâches de schéma.
-Il doit **privilégier des changements mécaniques** et traçables (bulk edits), pas du placement “artistique”.
-
-Gates obligatoires :
-
-## Runbook (ordre strict)
-
-1) Snapshot avant (pour preuve)
-```bash
-python tools/hw/schops/schops.py snapshot --schematic <...> --name before.json
-```
-
-2) Bulk edits (une opération par PR si possible)
-```bash
-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
-```
-
-3) Exports & checks
-```bash
-python tools/hw/schops/schops.py erc --schematic <...>
-python tools/hw/schops/schops.py netlist --schematic <...>
-python tools/hw/schops/schops.py bom --schematic <...> --exclude-dnp
-```
-
-4) Snapshot après
-```bash
-python tools/hw/schops/schops.py snapshot --schematic <...> --name after.json
-```
-
-## Plan
-1. Analyse des specs et roadmap hardware
-2. Préparation des règles bulk edit (fields, footprints, nets)
-3. Orchestration des modifications (une PR par bulk edit)
-4. Exports ERC/DRC/BOM/netlist
-5. Snapshots avant/après, evidence pack
-6. Documentation des artefacts et conventions
-7. Release & versioning
-
-5) Diff (simple)
-Utiliser `tools/hw/hw_diff.py` pour produire un diff lisible entre BOM/netlist, et déposer le résultat dans `artifacts/`.
-
-## Design Blocks
-
-But : capturer des “briques” réutilisables (connecteurs, power rails, UART header, cap array, etc.).
-
-Commande :
-```bash
-python tools/hw/schops/schops.py block-make \
- --name \
- --from-sheet \
- --lib hardware/blocks/.kicad_blocks \
- --description "..." \
- --keywords "k1,k2"
-```
-
-Livrables attendus :
-- `hardware/blocks/.kicad_blocks/.kicad_block/.kicad_sch`
-- `hardware/blocks/.kicad_blocks/.kicad_block/.json`
diff --git a/.github/agents/kill_life_bridge.md b/.github/agents/kill_life_bridge.md
new file mode 100644
index 0000000..8c90b18
--- /dev/null
+++ b/.github/agents/kill_life_bridge.md
@@ -0,0 +1,14 @@
+# KillLife-Bridge
+
+Canonical runtime/doc agent for cross-repo bridge contracts, continuity memory, and bridge artifacts.
+
+## Runtime contract
+- owner_agent: `KillLife-Bridge`
+- subagents: `Schema-Consumer`, `Artifact-Curator`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Translate shared contracts into bridge-safe outputs.
+2. Maintain continuity memory and handoff artifacts.
+3. Keep bridge responses explicit and restart-friendly.
diff --git a/.github/agents/pm_agent.md b/.github/agents/pm_agent.md
deleted file mode 100644
index 8a680e2..0000000
--- a/.github/agents/pm_agent.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# PM Agent
-
-Entrées : `specs/00_intake.md` + contraintes + standards.
-Sorties :
-
-## Plan
-1. Première entrée : lecture intake, standards, contraintes
-2. Brainstorming multi-agent (collecte besoins, risques)
-3. Rédaction ou amélioration de la spec (01_spec.md)
-4. Construction du backlog (04_tasks.md)
-5. Synchronisation avec roadmap hardware/firmware
-6. Suivi des gates BMAD et evidence pack
-7. Mise à jour des plans et prompts
diff --git a/.github/agents/pm_mesh.md b/.github/agents/pm_mesh.md
new file mode 100644
index 0000000..8a4e067
--- /dev/null
+++ b/.github/agents/pm_mesh.md
@@ -0,0 +1,14 @@
+# PM-Mesh
+
+Canonical runtime/doc agent for planning, prioritization, and mesh governance.
+
+## Runtime contract
+- owner_agent: `PM-Mesh`
+- subagents: `Plan-Orchestrator`, `Intake-Guard`, `Todo-Tracker`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Clarify the lot, write set, dependencies, and evidence.
+2. Update plans/tasks inside the assigned scope.
+3. Emit a handoff with explicit owner metadata.
diff --git a/.github/agents/qa_agent.md b/.github/agents/qa_agent.md
deleted file mode 100644
index 6f3dc5f..0000000
--- a/.github/agents/qa_agent.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# QA Agent
-
-Objectif : assurer tests + evidence.
-
-## Plan
-1. Analyse des specs, plan, gates
-2. Ajout ou validation des tests (Unity, host)
-3. Vérification des gates BMAD (S0/S1)
-4. Génération du summary report (artifacts/report.md)
-5. Evidence pack QA
-6. Documentation troubleshooting
-7. Feedback & contribution
diff --git a/.github/agents/qa_compliance.md b/.github/agents/qa_compliance.md
new file mode 100644
index 0000000..0bc7d72
--- /dev/null
+++ b/.github/agents/qa_compliance.md
@@ -0,0 +1,14 @@
+# QA-Compliance
+
+Canonical runtime/doc agent for tests, compliance gates, and release evidence.
+
+## Runtime contract
+- owner_agent: `QA-Compliance`
+- subagents: `Constraint-Gate`, `Contract-Tests`, `Release-Gates`
+- gates: `bmad/gates/gate_s0.md`, `bmad/gates/gate_s1.md`
+- handoff: `specs/contracts/operator_lane_evidence.schema.json`
+
+## Plan
+1. Validate contracts, tests, and evidence.
+2. Guard schema and release drift.
+3. Keep failures explicit and reproducible.
diff --git a/.github/agents/runtime_companion.md b/.github/agents/runtime_companion.md
new file mode 100644
index 0000000..206afa5
--- /dev/null
+++ b/.github/agents/runtime_companion.md
@@ -0,0 +1,14 @@
+# Runtime-Companion
+
+Canonical runtime/doc agent for runtime AI, MCP alignment, and provider bridges.
+
+## Runtime contract
+- owner_agent: `Runtime-Companion`
+- subagents: `MCP-Health`, `Provider-Bridge`, `Runtime-Guard`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Stabilize runtime and provider routing.
+2. Keep degraded-safe operator summaries.
+3. Publish recovery evidence with each change.
diff --git a/.github/agents/schema_guard.md b/.github/agents/schema_guard.md
new file mode 100644
index 0000000..6fab2e1
--- /dev/null
+++ b/.github/agents/schema_guard.md
@@ -0,0 +1,14 @@
+# Schema-Guard
+
+Canonical runtime/doc agent for schemas, machine-readable contracts, and strict validation.
+
+## Runtime contract
+- owner_agent: `Schema-Guard`
+- subagents: `Handoff-Schema`, `Evidence-Schema`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/workflow_handshake.schema.json`
+
+## Plan
+1. Keep schemas and examples in sync.
+2. Fail on drift before consumers break.
+3. Publish validation evidence with each contract change.
diff --git a/.github/agents/sync_ops.md b/.github/agents/sync_ops.md
new file mode 100644
index 0000000..72fe3c8
--- /dev/null
+++ b/.github/agents/sync_ops.md
@@ -0,0 +1,14 @@
+# SyncOps
+
+Canonical runtime/doc agent for cockpit scripts, logs, SSH convergence, and operator lanes.
+
+## Runtime contract
+- owner_agent: `SyncOps`
+- subagents: `TUI-Ops`, `Log-Ops`, `Ops-Governor`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/operator_lane_evidence.schema.json`
+
+## Plan
+1. Keep short operator paths stable.
+2. Publish handoffs, logs, and summaries.
+3. Prefer degraded-safe behavior over silent failure.
diff --git a/.github/agents/ux_lead.md b/.github/agents/ux_lead.md
new file mode 100644
index 0000000..dc1e2b3
--- /dev/null
+++ b/.github/agents/ux_lead.md
@@ -0,0 +1,14 @@
+# UX-Lead
+
+Canonical runtime/doc agent for YiACAD UI/UX direction.
+
+## Runtime contract
+- owner_agent: `UX-Lead`
+- subagents: `Apple-HIG`, `CAD-UX`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+
+## Plan
+1. Convert product constraints into UI/UX decisions.
+2. Keep UX guidance aligned with canonical specs.
+3. Publish rationale and evidence without stealing execution ownership.
diff --git a/.github/agents/web_cad_platform.md b/.github/agents/web_cad_platform.md
new file mode 100644
index 0000000..2027dd7
--- /dev/null
+++ b/.github/agents/web_cad_platform.md
@@ -0,0 +1,14 @@
+# Web-CAD-Platform
+
+Canonical runtime/doc agent for the YiACAD web product and its queue/realtime/read-model surfaces.
+
+## Runtime contract
+- owner_agent: `Web-CAD-Platform`
+- subagents: `Project-Service`, `EDA-CI-Orchestrator`, `Realtime-Collab`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/summary_short.schema.json`
+
+## Plan
+1. Keep project state, queueing, and realtime aligned.
+2. Preserve review-first product boundaries.
+3. Publish web evidence and contract-safe metadata.
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index fae8830..b1c357d 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,51 +1,71 @@
-# Kill_LIFE - Code Review Instructions
+# Kill_LIFE Workspace Instructions
## Scope
-This workspace instruction is review-only by default.
-Prioritize defect discovery over implementation unless explicitly requested.
+Kill_LIFE is the control plane repo for spec-first governance, cockpit operations, runtime/MCP health, YiACAD web/CAD integration, firmware, and hardware evidence.
+Use these instructions for all work in this repository.
-## Review Priorities
+## Canonical Entry Points
-1. Behavioral regressions and broken user-visible flows.
-2. Contract/schema drift across `specs/contracts/`, backend outputs, and web consumers.
-3. Runtime and CI gate risks (missing env assumptions, flaky paths, weak failure handling).
-4. Security/safety risks (unsafe shell usage, secret exposure, destructive defaults).
-5. Missing or weak tests for changed behavior.
+- Product and consolidation context: `README.md`
+- Operator navigation: `docs/index.md`
+- Build, test, and architecture reference: `CLAUDE.md`
+- Runbooks: `RUNBOOK.md` and `docs/RUNBOOK.md`
+- Spec-first source of truth: `specs/README.md`
+- Cockpit and TUI entry points: `tools/cockpit/README.md`
-## Review Method
+## Build And Test
-- Start with changed files, then trace impacted call sites and consumers.
-- Validate `status/degraded/blocked`, `degraded_reasons`, `engine_status`, and artifact paths consistency.
-- Favor concrete findings with file and line references over broad summaries.
-- If no issue is found, state that explicitly and list residual risk/test gaps.
-
-## Required Checks
-
-- Specs chain alignment when behavior changes:
- - `specs/00_intake.md -> specs/01_spec.md -> specs/02_arch.md -> specs/03_plan.md -> specs/04_tasks.md`
-- Contract compatibility:
- - `specs/contracts/*.schema.json`
- - `specs/contracts/examples/*.json`
-- Evidence/log discipline:
- - outputs under `artifacts/` and `docs/evidence/`
-
-## Canonical Commands (When Verifying)
+Prefer the repo commands already documented in `CLAUDE.md`. Default validation commands are:
- `bash tools/bootstrap_python_env.sh`
- `bash tools/test_python.sh --suite stable`
- `python3 tools/validate_specs.py --strict`
- `ruff check .`
+Use domain-specific commands when relevant:
+
+- Web: `cd web && npm install`, `cd web && npm run build`, `cd web && npx playwright test`
+- Firmware: `cd firmware && pio run -e esp32s3_waveshare`, `cd firmware && pio test -e native`
+- Hardware: `make hw SCHEM=hardware/esp32_minimal/esp32_minimal.kicad_sch`, `bash tools/hw/hw_export.sh`
+- Runtime and cockpit: `bash tools/cockpit/runtime_ai_gateway.sh --action status --refresh --json`
+
+## Architecture
+
+Key boundaries:
+
+- `specs/` is the source of truth: `00_intake -> 01_spec -> 02_arch -> 03_plan -> 04_tasks`
+- `kill_life/` contains the Python control-plane and server logic
+- `tools/cockpit/` is the canonical operator and runtime surface
+- `web/` is the YiACAD Next.js frontend and worker surface
+- `firmware/` is the PlatformIO firmware project
+- `hardware/` contains KiCad schematics and manufacturing sources
+- `specs/contracts/` defines runtime and artifact contracts that consumers rely on
+
## Conventions
-- Specs/docs mostly French; code/comments remain English.
-- Python target 3.12+, line length 120, `ruff` style.
-- Keep review comments scoped and actionable; avoid unrelated refactors.
+- Specs and docs are mostly French; code and comments stay in English.
+- Preserve the existing `ready`, `degraded`, `blocked` status discipline when touching runtime, cockpit, or evidence outputs.
+- Keep artifact paths stable and consumable by operators; prefer `artifacts/` and `docs/evidence/`.
+- When behavior changes, check whether related specs, contracts, examples, and runbooks also need updates.
+- Prefer link-out to existing docs instead of duplicating long operational guidance in code comments or instructions.
-## References
+## Common Pitfalls
-- `README.md`
-- `CLAUDE.md`
-- `docs/index.md`
-- `RUNBOOK.md`
+- This repo may already be dirty; do not revert unrelated user changes.
+- Many workflows depend on generated JSON or evidence artifacts; avoid changing field names or paths casually.
+- The web, CAD, and cockpit surfaces share contracts indirectly through `specs/contracts/`; validate those links before closing a task.
+- For execution-heavy tasks, prefer explicit `--json` outputs and operator-facing evidence.
+
+## Area-Specific Guidance
+
+Use the specialized instruction files when your change touches those areas:
+
+- `.github/instructions/web.instructions.md` for `web/`
+- `.github/instructions/firmware.instructions.md` for `firmware/`
+- `.github/instructions/cad.instructions.md` for CAD and hardware tooling
+- `.github/instructions/ops.instructions.md` for cockpit, evidence, and workflows
+
+## Review Default
+
+Unless explicitly asked to implement, default to a review mindset in this workspace: focus on behavioral regressions, contract drift, runtime risks, security issues, and missing tests.
\ No newline at end of file
diff --git a/.github/agents/plan-audit-quotidien.agent.md b/.github/instructions/contracts.instructions.md
similarity index 100%
rename from .github/agents/plan-audit-quotidien.agent.md
rename to .github/instructions/contracts.instructions.md
diff --git a/.github/prompts/plan_wizard_agents_coordination.prompt.md b/.github/prompts/plan_wizard_agents_coordination.prompt.md
index 5b4fa99..3a925e4 100644
--- a/.github/prompts/plan_wizard_agents_coordination.prompt.md
+++ b/.github/prompts/plan_wizard_agents_coordination.prompt.md
@@ -1,26 +1,20 @@
---
name: planWizardAgentsCoordination
description: Génère un plan de coordination multi-agent pour éviter la redondance et optimiser les workflows.
-argument-hint: Spécifie les rôles, étapes communes, evidence pack, synchronisation.
+argument-hint: Spécifie les agents canoniques, étapes communes, handoffs, evidence et synchronisation.
---
-Génère un plan structuré pour la coordination des agents :
+Génère un plan structuré pour la coordination multi-agent en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de vérité.
## Checklist commune
-- Analyse des specs et contraintes
-- Validation des gates BMAD
-- Evidence pack (archivage systématique)
-- Documentation et mise à jour guides/README
-- Synchronisation multi-agent (rituels, handoffs)
-- Feedback et contribution
+- Charger `docs/GLOBAL_MULTI_AGENT_CHECKLIST.md`
+- Vérifier `owner_repo`, `owner_agent`, `write_set`, `status`, `evidence`
+- Valider les gates BMAD et les contrats de handoff
+- Documenter la preuve et la synchronisation dans les surfaces canoniques
+- Garder les sous-agents comme metadata de lane, jamais comme agents publics
## Étapes de coordination
-1. Centraliser les étapes communes dans une checklist globale
-2. Référencer la checklist dans chaque plan agent ou plan transversal
-3. Adapter chaque plan pour ne garder que les étapes spécifiques au rôle ou au contexte
-4. Organiser des points de synchronisation réguliers (rituels, gates)
-5. Archiver les actions et décisions dans evidence pack
-6. Documenter les évolutions et feedbacks
-
-## Objectif
-- Optimiser la traçabilité, éviter la redondance, fluidifier la collaboration multi-agent
-- Garantir la cohérence des workflows et la reproductibilité
+1. Identifier quels agents canoniques parmi `PM-Mesh`, `Arch-Mesh`, `Docs-Research`, `Runtime-Companion`, `QA-Compliance`, `Embedded-CAD`, `Web-CAD-Platform`, `UX-Lead`, `Firmware`, `SyncOps`, `Schema-Guard`, `KillLife-Bridge` participent au lot.
+2. Définir un unique owner top-level par write set et reléguer les sous-agents au rôle de metadata.
+3. Référencer les handoffs, gates et evidence obligatoires dans le plan.
+4. Organiser la synchronisation sans redondance entre docs, prompts, runtime et contrats.
+5. Clore avec le validateur de catalogue, les risques restants et la prochaine action.
diff --git a/.github/prompts/plan_wizard_agents_management.prompt.md b/.github/prompts/plan_wizard_agents_management.prompt.md
index 2e978e4..8a6bcd5 100644
--- a/.github/prompts/plan_wizard_agents_management.prompt.md
+++ b/.github/prompts/plan_wizard_agents_management.prompt.md
@@ -1,11 +1,11 @@
---
name: planWizardAgentsManagement
description: Génère un plan pour la gestion des agents.
-argument-hint: Spécifie rôles, prompts, rituels, gates, handoffs, evidence pack.
+argument-hint: Spécifie catalogue, prompts, write sets, rituels, gates, handoffs et evidence.
---
-Génère un plan structuré pour la gestion des agents :
-1. Définir rôles, prompts, rituels, gates, handoffs
-2. Documenter dans agents/ et bmad/
-3. Archiver evidence pack pour chaque action agent
-4. Suivi des évolutions
-5. Validation multi-agent
+Génère un plan structuré pour la gestion des agents en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de vérité.
+1. Partir de la liste canonique top-level: `PM-Mesh`, `Arch-Mesh`, `Docs-Research`, `Runtime-Companion`, `QA-Compliance`, `Embedded-CAD`, `Web-CAD-Platform`, `UX-Lead`, `Firmware`, `SyncOps`, `Schema-Guard`, `KillLife-Bridge`.
+2. Vérifier la parité entre contrat, `agents/`, `.github/agents/`, prompts `start_*` et `plan_wizard_*`, README et matrice.
+3. Référencer `docs/GLOBAL_MULTI_AGENT_CHECKLIST.md` pour les étapes communes, handoffs et preuves.
+4. Garder les sous-agents comme metadata de gouvernance, jamais comme agents API publics.
+5. Conclure par le validateur à exécuter, les preuves attendues et la prochaine action.
diff --git a/.github/prompts/plan_wizard_arch_mesh.prompt.md b/.github/prompts/plan_wizard_arch_mesh.prompt.md
new file mode 100644
index 0000000..06bad2d
--- /dev/null
+++ b/.github/prompts/plan_wizard_arch_mesh.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardArchMesh
+description: Génère un plan canonique pour Arch-Mesh.
+argument-hint: Précise les contrats, dépendances, risques et preuves attendues.
+---
+Génère un plan structuré pour `Arch-Mesh`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier préconditions, interfaces publiques, dépendances et risques de compatibilité.
+3. Lister les étapes dans l'ordre avec gates, handoffs et preuves de stabilité contractuelle.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, le rollback et la prochaine action.
diff --git a/.github/prompts/plan_wizard_architect_agent.prompt.md b/.github/prompts/plan_wizard_architect_agent.prompt.md
deleted file mode 100644
index bc610f8..0000000
--- a/.github/prompts/plan_wizard_architect_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: planWizardArchitectAgent
-description: Génère un plan détaillé pour l’agent architect dans un projet multi-agent.
-argument-hint: Spécifie les specs, contraintes, gates, evidence pack.
----
-Génère un plan structuré pour l’agent architect :
-1. Analyse des specs et contraintes
-2. Brainstorming architecture
-3. Rédaction ou mise à jour `02_arch.md`
-4. Validation gates BMAD
-5. Documentation ADR
-6. Evidence pack
-7. Mise à jour prompts agents
-Ajoute checklist, liens vers standards, section risques, historique des décisions.
\ No newline at end of file
diff --git a/.github/prompts/plan_wizard_coordination_handoff_evidence.prompt.md b/.github/prompts/plan_wizard_coordination_handoff_evidence.prompt.md
new file mode 100644
index 0000000..e69de29
diff --git a/.github/prompts/plan_wizard_doc_agent.prompt.md b/.github/prompts/plan_wizard_doc_agent.prompt.md
deleted file mode 100644
index 9f6a407..0000000
--- a/.github/prompts/plan_wizard_doc_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: planWizardDocAgent
-description: Génère un plan détaillé pour l’agent documentation dans un projet multi-agent.
-argument-hint: Spécifie specs, plan, evidence pack, guides.
----
-Génère un plan structuré pour l’agent documentation :
-1. Analyse specs, plan, evidence pack
-2. Mise à jour docs, guides, FAQ
-3. Ajout exemples projet minimal
-4. Documentation pipelines/conventions
-5. Changelog
-6. Onboarding/feedback
-7. Release documentation
-Ajoute table des docs à jour, section FAQ, liens vers specs, historique releases documentation.
\ No newline at end of file
diff --git a/.github/prompts/plan_wizard_docs_research.prompt.md b/.github/prompts/plan_wizard_docs_research.prompt.md
new file mode 100644
index 0000000..2ce0118
--- /dev/null
+++ b/.github/prompts/plan_wizard_docs_research.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardDocsResearch
+description: Génère un plan canonique pour Docs-Research.
+argument-hint: Précise la surface documentaire, les entrées canoniques et les preuves attendues.
+---
+Génère un plan structuré pour `Docs-Research`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier navigation canonique, dépendances documentaires, risques de drift et surfaces miroir.
+3. Lister les étapes avec checkpoints de cohérence, evidence et handoffs.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue et la prochaine publication.
diff --git a/.github/prompts/plan_wizard_embedded_cad.prompt.md b/.github/prompts/plan_wizard_embedded_cad.prompt.md
new file mode 100644
index 0000000..a063de3
--- /dev/null
+++ b/.github/prompts/plan_wizard_embedded_cad.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardEmbeddedCad
+description: Génère un plan canonique pour Embedded-CAD.
+argument-hint: Précise la surface CAD, les outils indisponibles, les preuves et le rollback.
+---
+Génère un plan structuré pour `Embedded-CAD`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier dépendances toolchain, surfaces fabrication, risques degraded-safe et evidence attendue.
+3. Lister les étapes avec checks KiCad/FreeCAD, handoffs et preuves.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par le rollback, la validation attendue et la prochaine action.
diff --git a/.github/prompts/plan_wizard_firmware.prompt.md b/.github/prompts/plan_wizard_firmware.prompt.md
new file mode 100644
index 0000000..4852be7
--- /dev/null
+++ b/.github/prompts/plan_wizard_firmware.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardFirmware
+description: Génère un plan canonique pour Firmware.
+argument-hint: Précise la cible, les tests, les dépendances matérielles et les preuves attendues.
+---
+Génère un plan structuré pour `Firmware`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier cible, dépendances matérielles, risques mémoire ou boot et evidence attendue.
+3. Lister les étapes avec build, tests, handoffs et preuves.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, le rollback et la prochaine action.
diff --git a/.github/prompts/plan_wizard_firmware_agent.prompt.md b/.github/prompts/plan_wizard_firmware_agent.prompt.md
deleted file mode 100644
index ad2e1af..0000000
--- a/.github/prompts/plan_wizard_firmware_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: planWizardFirmwareAgent
-description: Génère un plan détaillé pour l’agent firmware dans un projet multi-agent.
-argument-hint: Spécifie plan, specs, tests, evidence pack.
----
-Génère un plan structuré pour l’agent firmware :
-1. Analyse plan/specs
-2. Implémentation firmware
-3. Ajout/mise à jour tests Unity
-4. Validation PlatformIO
-5. Evidence pack
-6. Documentation commandes
-7. Release/versioning
-Ajoute table de couverture tests, section erreurs, liens vers specs, historique releases.
\ No newline at end of file
diff --git a/.github/prompts/plan_wizard_hw_schematic_agent.prompt.md b/.github/prompts/plan_wizard_hw_schematic_agent.prompt.md
deleted file mode 100644
index 36c8a97..0000000
--- a/.github/prompts/plan_wizard_hw_schematic_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: planWizardHwSchematicAgent
-description: Génère un plan détaillé pour l’agent hardware schematic dans un projet multi-agent.
-argument-hint: Spécifie specs, roadmap, bulk edit, evidence pack.
----
-Génère un plan structuré pour l’agent hardware schematic :
-1. Analyse specs/roadmap
-2. Préparation règles bulk edit
-3. Orchestration modifications
-4. Exports ERC/DRC/BOM/netlist
-5. Snapshots avant/après
-6. Documentation artefacts
-7. Release/versioning
-Ajoute table des modifications, section risques, liens vers specs, historique releases.
\ No newline at end of file
diff --git a/.github/prompts/plan_wizard_kill_life_bridge.prompt.md b/.github/prompts/plan_wizard_kill_life_bridge.prompt.md
new file mode 100644
index 0000000..e249b18
--- /dev/null
+++ b/.github/prompts/plan_wizard_kill_life_bridge.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardKillLifeBridge
+description: Génère un plan canonique pour KillLife-Bridge.
+argument-hint: Précise le workflow, les producteurs, les consommateurs et les preuves attendues.
+---
+Génère un plan structuré pour `KillLife-Bridge`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier producteurs d'evidence, consommateurs tri-repo, risques de propagation et dépendances.
+3. Lister les étapes avec handoffs, validations et artefacts obligatoires.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, le rollback et la prochaine action.
diff --git a/.github/prompts/plan_wizard_pm_agent.prompt.md b/.github/prompts/plan_wizard_pm_agent.prompt.md
deleted file mode 100644
index 7cfb866..0000000
--- a/.github/prompts/plan_wizard_pm_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: planWizardPmAgent
-description: Génère un plan détaillé pour l’agent PM dans un projet multi-agent.
-argument-hint: Spécifie intake, standards, backlog, roadmap.
----
-Génère un plan structuré pour l’agent PM :
-1. Lecture intake, standards, contraintes
-2. Brainstorming multi-agent
-3. Rédaction/amélioration spec
-4. Construction backlog
-5. Synchronisation roadmap
-6. Suivi gates BMAD
-7. Mise à jour plans/prompts
-Ajoute table de priorisation, section risques, liens vers specs, historique releases.
\ No newline at end of file
diff --git a/.github/prompts/plan_wizard_pm_mesh.prompt.md b/.github/prompts/plan_wizard_pm_mesh.prompt.md
new file mode 100644
index 0000000..f6e1110
--- /dev/null
+++ b/.github/prompts/plan_wizard_pm_mesh.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardPmMesh
+description: Génère un plan canonique pour PM-Mesh.
+argument-hint: Précise le lot, les dépendances, les gates et les preuves à produire.
+---
+Génère un plan structuré pour `PM-Mesh`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier préconditions, dépendances, risques et surfaces de handoff.
+3. Lister les étapes dans l'ordre avec gates, evidence et validations attendues.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la prochaine action, le rollback et le statut attendu.
diff --git a/.github/prompts/plan_wizard_qa_agent.prompt.md b/.github/prompts/plan_wizard_qa_agent.prompt.md
deleted file mode 100644
index 12e67e8..0000000
--- a/.github/prompts/plan_wizard_qa_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: planWizardQaAgent
-description: Génère un plan détaillé pour l’agent QA dans un projet multi-agent.
-argument-hint: Spécifie specs, plan, gates, evidence pack.
----
-Génère un plan structuré pour l’agent QA :
-1. Analyse specs, plan, gates
-2. Ajout/validation tests
-3. Vérification gates BMAD
-4. Génération summary report
-5. Evidence pack QA
-6. Documentation troubleshooting
-7. Feedback/contribution
-Ajoute table de validation, section erreurs QA, liens vers specs, historique releases.
\ No newline at end of file
diff --git a/.github/prompts/plan_wizard_qa_compliance.prompt.md b/.github/prompts/plan_wizard_qa_compliance.prompt.md
new file mode 100644
index 0000000..c78e652
--- /dev/null
+++ b/.github/prompts/plan_wizard_qa_compliance.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardQaCompliance
+description: Génère un plan canonique pour QA-Compliance.
+argument-hint: Précise la gate, les suites ciblées, le contrat et les preuves attendues.
+---
+Génère un plan structuré pour `QA-Compliance`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier suites stables, contrats JSON, risques de régression et preuves attendues.
+3. Lister les étapes avec validations, gates et handoffs.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par l'état attendu, les écarts restants et la prochaine gate.
diff --git a/.github/prompts/plan_wizard_runtime_companion.prompt.md b/.github/prompts/plan_wizard_runtime_companion.prompt.md
new file mode 100644
index 0000000..85136ce
--- /dev/null
+++ b/.github/prompts/plan_wizard_runtime_companion.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardRuntimeCompanion
+description: Génère un plan canonique pour Runtime-Companion.
+argument-hint: Précise la surface runtime, les dépendances externes, la dégradation et les preuves.
+---
+Génère un plan structuré pour `Runtime-Companion`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier dépendances externes, conditions degraded-safe, artefacts et risques de latence ou disponibilité.
+3. Lister les étapes avec checks runtime, handoffs et evidence obligatoires.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, le fallback et la prochaine action.
diff --git a/.github/prompts/plan_wizard_schema_guard.prompt.md b/.github/prompts/plan_wizard_schema_guard.prompt.md
new file mode 100644
index 0000000..622634e
--- /dev/null
+++ b/.github/prompts/plan_wizard_schema_guard.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardSchemaGuard
+description: Génère un plan canonique pour Schema-Guard.
+argument-hint: Précise le schéma, les producteurs, les invariants et les preuves attendues.
+---
+Génère un plan structuré pour `Schema-Guard`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier producteurs, consommateurs, invariants cassables et evidence attendue.
+3. Lister les étapes avec mises à jour de schéma, validateurs, tests et handoffs.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, la compatibilité et la prochaine action.
diff --git a/.github/prompts/plan_wizard_sync_ops.prompt.md b/.github/prompts/plan_wizard_sync_ops.prompt.md
new file mode 100644
index 0000000..5e1446a
--- /dev/null
+++ b/.github/prompts/plan_wizard_sync_ops.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardSyncOps
+description: Génère un plan canonique pour SyncOps.
+argument-hint: Précise la lane opératoire, les incidents, les probes et les preuves attendues.
+---
+Génère un plan structuré pour `SyncOps`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier dépendances machines, risques réseau, surfaces logs et evidence attendue.
+3. Lister les étapes avec checks TUI, SSH, incident registry et handoffs.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par l'état attendu, le rollback et la prochaine action.
diff --git a/.github/prompts/plan_wizard_ux_lead.prompt.md b/.github/prompts/plan_wizard_ux_lead.prompt.md
new file mode 100644
index 0000000..e8bf0f1
--- /dev/null
+++ b/.github/prompts/plan_wizard_ux_lead.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardUxLead
+description: Génère un plan canonique pour UX-Lead.
+argument-hint: Précise la surface UX, les artefacts, les critères Apple-native et les preuves attendues.
+---
+Génère un plan structuré pour `UX-Lead`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier hypothèses UX, dépendances de shell, risques de cohérence produit et evidence attendue.
+3. Lister les étapes avec sorties UI, validation design et handoffs.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, les écarts et la prochaine action.
diff --git a/.github/prompts/plan_wizard_web_cad_platform.prompt.md b/.github/prompts/plan_wizard_web_cad_platform.prompt.md
new file mode 100644
index 0000000..fae46bd
--- /dev/null
+++ b/.github/prompts/plan_wizard_web_cad_platform.prompt.md
@@ -0,0 +1,11 @@
+---
+name: planWizardWebCadPlatform
+description: Génère un plan canonique pour Web-CAD-Platform.
+argument-hint: Précise la surface Next.js, GraphQL, Yjs, BullMQ ou review concernée.
+---
+Génère un plan structuré pour `Web-CAD-Platform`, aligné sur `specs/contracts/kill_life_agent_catalog.json`.
+1. Rappeler la mission, le write set et les sous-agents metadata associés.
+2. Identifier dépendances web, risques de cohérence, latence, queue et evidence attendue.
+3. Lister les étapes avec checkpoints API, realtime, worker et handoffs.
+4. Vérifier que chaque sortie reste dans le write set canonique.
+5. Conclure par la validation attendue, le fallback et la prochaine action.
diff --git a/.github/prompts/review_transversal_lot_py_cockpit_docs.prompt.md b/.github/prompts/review_transversal_lot_py_cockpit_docs.prompt.md
new file mode 100644
index 0000000..e69de29
diff --git a/.github/prompts/start_arch_mesh.prompt.md b/.github/prompts/start_arch_mesh.prompt.md
new file mode 100644
index 0000000..0f3e9f8
--- /dev/null
+++ b/.github/prompts/start_arch_mesh.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startArchMesh
+description: Lance le workflow canonique Arch-Mesh.
+argument-hint: Donne l'interface, le contrat ou la frontière à stabiliser.
+---
+Lance le workflow canonique `Arch-Mesh` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Arch-Mesh`, `agents/arch_mesh.md` et les specs ou ADR lies.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_architect_agent.prompt.md b/.github/prompts/start_architect_agent.prompt.md
deleted file mode 100644
index d902626..0000000
--- a/.github/prompts/start_architect_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: startArchitectAgent
-description: Démarre le workflow de l’agent architect.
-argument-hint: Spécifie specs, contraintes, gates, evidence pack.
----
-Démarre le workflow architect :
-1. Analyse specs et contraintes
-2. Brainstorming architecture
-3. Rédaction ou mise à jour `02_arch.md`
-4. Validation gates BMAD
-5. Documentation ADR
-6. Evidence pack
-7. Mise à jour prompts agents
-> Voir checklist commune : plan_wizard_agents_coordination.prompt.md
\ No newline at end of file
diff --git a/.github/prompts/start_doc_agent.prompt.md b/.github/prompts/start_doc_agent.prompt.md
deleted file mode 100644
index 82a0b53..0000000
--- a/.github/prompts/start_doc_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: startDocAgent
-description: Démarre le workflow de l’agent documentation.
-argument-hint: Spécifie specs, plan, evidence pack, guides.
----
-Démarre le workflow documentation :
-1. Analyse specs, plan, evidence pack
-2. Mise à jour docs, guides, FAQ
-3. Ajout exemples projet minimal
-4. Documentation pipelines/conventions
-5. Changelog
-6. Onboarding/feedback
-7. Release documentation
-> Voir checklist commune : plan_wizard_agents_coordination.prompt.md
\ No newline at end of file
diff --git a/.github/prompts/start_docs_research.prompt.md b/.github/prompts/start_docs_research.prompt.md
new file mode 100644
index 0000000..a8a7afb
--- /dev/null
+++ b/.github/prompts/start_docs_research.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startDocsResearch
+description: Lance le workflow canonique Docs-Research.
+argument-hint: Donne la surface documentaire, la navigation ou la synthèse à mettre à jour.
+---
+Lance le workflow canonique `Docs-Research` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Docs-Research`, `agents/docs_research.md` et les docs d'entree associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_embedded_cad.prompt.md b/.github/prompts/start_embedded_cad.prompt.md
new file mode 100644
index 0000000..b11f150
--- /dev/null
+++ b/.github/prompts/start_embedded_cad.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startEmbeddedCad
+description: Lance le workflow canonique Embedded-CAD.
+argument-hint: Donne la lane KiCad, FreeCAD, fabrication ou CAD native à faire avancer.
+---
+Lance le workflow canonique `Embedded-CAD` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Embedded-CAD`, `agents/embedded_cad.md` et les surfaces CAD associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_firmware.prompt.md b/.github/prompts/start_firmware.prompt.md
new file mode 100644
index 0000000..f73bdea
--- /dev/null
+++ b/.github/prompts/start_firmware.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startFirmware
+description: Lance le workflow canonique Firmware.
+argument-hint: Donne la cible PlatformIO, le lot firmware, les tests et les preuves à produire.
+---
+Lance le workflow canonique `Firmware` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Firmware`, `agents/firmware.md` et les surfaces firmware associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_firmware_agent.prompt.md b/.github/prompts/start_firmware_agent.prompt.md
deleted file mode 100644
index 07dd553..0000000
--- a/.github/prompts/start_firmware_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: startFirmwareAgent
-description: Démarre le workflow de l’agent firmware.
-argument-hint: Spécifie plan, specs, tests, evidence pack.
----
-Démarre le workflow firmware :
-1. Analyse plan/specs
-2. Implémentation firmware
-3. Ajout/mise à jour tests Unity
-4. Validation PlatformIO
-5. Evidence pack
-6. Documentation commandes
-7. Release/versioning
-> Voir checklist commune : plan_wizard_agents_coordination.prompt.md
\ No newline at end of file
diff --git a/.github/prompts/start_hw_schematic_agent.prompt.md b/.github/prompts/start_hw_schematic_agent.prompt.md
deleted file mode 100644
index 94f24d7..0000000
--- a/.github/prompts/start_hw_schematic_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: startHwSchematicAgent
-description: Démarre le workflow de l’agent hardware schematic.
-argument-hint: Spécifie specs, roadmap, bulk edit, evidence pack.
----
-Démarre le workflow hardware schematic :
-1. Analyse specs/roadmap
-2. Préparation règles bulk edit
-3. Orchestration modifications
-4. Exports ERC/DRC/BOM/netlist
-5. Snapshots avant/après
-6. Documentation artefacts
-7. Release/versioning
-> Voir checklist commune : plan_wizard_agents_coordination.prompt.md
\ No newline at end of file
diff --git a/.github/prompts/start_kill_life_bridge.prompt.md b/.github/prompts/start_kill_life_bridge.prompt.md
new file mode 100644
index 0000000..9c40237
--- /dev/null
+++ b/.github/prompts/start_kill_life_bridge.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startKillLifeBridge
+description: Lance le workflow canonique KillLife-Bridge.
+argument-hint: Donne le workflow, le bridge tri-repo, l'evidence ou le handoff à stabiliser.
+---
+Lance le workflow canonique `KillLife-Bridge` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `KillLife-Bridge`, `agents/kill_life_bridge.md` et les surfaces bridge associées.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_pm_agent.prompt.md b/.github/prompts/start_pm_agent.prompt.md
deleted file mode 100644
index 3d7c9c0..0000000
--- a/.github/prompts/start_pm_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: startPmAgent
-description: Démarre le workflow de l’agent PM.
-argument-hint: Spécifie intake, standards, backlog, roadmap.
----
-Démarre le workflow PM :
-1. Lecture intake, standards, contraintes
-2. Brainstorming multi-agent
-3. Rédaction/amélioration spec
-4. Construction backlog
-5. Synchronisation roadmap
-6. Suivi gates BMAD
-7. Mise à jour plans/prompts
-> Voir checklist commune : plan_wizard_agents_coordination.prompt.md
\ No newline at end of file
diff --git a/.github/prompts/start_pm_mesh.prompt.md b/.github/prompts/start_pm_mesh.prompt.md
new file mode 100644
index 0000000..6167ad2
--- /dev/null
+++ b/.github/prompts/start_pm_mesh.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startPmMesh
+description: Lance le workflow canonique PM-Mesh.
+argument-hint: Donne le lot, le write set, les risques et les preuves attendues.
+---
+Lance le workflow canonique `PM-Mesh` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `PM-Mesh`, `agents/pm_mesh.md` et les surfaces de plan associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_qa_agent.prompt.md b/.github/prompts/start_qa_agent.prompt.md
deleted file mode 100644
index f89f21b..0000000
--- a/.github/prompts/start_qa_agent.prompt.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: startQaAgent
-description: Démarre le workflow de l’agent QA.
-argument-hint: Spécifie specs, plan, gates, evidence pack.
----
-Démarre le workflow QA :
-1. Analyse specs, plan, gates
-2. Ajout/validation tests
-3. Vérification gates BMAD
-4. Génération summary report
-5. Evidence pack QA
-6. Documentation troubleshooting
-7. Feedback/contribution
-> Voir checklist commune : plan_wizard_agents_coordination.prompt.md
\ No newline at end of file
diff --git a/.github/prompts/start_qa_compliance.prompt.md b/.github/prompts/start_qa_compliance.prompt.md
new file mode 100644
index 0000000..12a3539
--- /dev/null
+++ b/.github/prompts/start_qa_compliance.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startQaCompliance
+description: Lance le workflow canonique QA-Compliance.
+argument-hint: Donne le contrat, la suite de tests, la gate ou la preuve de conformité à valider.
+---
+Lance le workflow canonique `QA-Compliance` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `QA-Compliance`, `agents/qa_compliance.md` et les surfaces de validation associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_runtime_companion.prompt.md b/.github/prompts/start_runtime_companion.prompt.md
new file mode 100644
index 0000000..12f9267
--- /dev/null
+++ b/.github/prompts/start_runtime_companion.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startRuntimeCompanion
+description: Lance le workflow canonique Runtime-Companion.
+argument-hint: Donne la surface runtime, MCP, provider bridge ou dégradation à traiter.
+---
+Lance le workflow canonique `Runtime-Companion` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Runtime-Companion`, `agents/runtime_companion.md` et les contrats runtime lies.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_schema_guard.prompt.md b/.github/prompts/start_schema_guard.prompt.md
new file mode 100644
index 0000000..45610d4
--- /dev/null
+++ b/.github/prompts/start_schema_guard.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startSchemaGuard
+description: Lance le workflow canonique Schema-Guard.
+argument-hint: Donne le schéma, le contrat ou la validation structurelle à durcir.
+---
+Lance le workflow canonique `Schema-Guard` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Schema-Guard`, `agents/schema_guard.md` et les contrats associes.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_sync_ops.prompt.md b/.github/prompts/start_sync_ops.prompt.md
new file mode 100644
index 0000000..799bfdc
--- /dev/null
+++ b/.github/prompts/start_sync_ops.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startSyncOps
+description: Lance le workflow canonique SyncOps.
+argument-hint: Donne la surface TUI, mesh, logs, SSH ou incident à stabiliser.
+---
+Lance le workflow canonique `SyncOps` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `SyncOps`, `agents/sync_ops.md` et les surfaces opératoires associées.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_ux_lead.prompt.md b/.github/prompts/start_ux_lead.prompt.md
new file mode 100644
index 0000000..9a7dfd6
--- /dev/null
+++ b/.github/prompts/start_ux_lead.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startUxLead
+description: Lance le workflow canonique UX-Lead.
+argument-hint: Donne la surface UX, shell natif, review center ou recherche design à cadrer.
+---
+Lance le workflow canonique `UX-Lead` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `UX-Lead`, `agents/ux_lead.md` et les surfaces UX associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/prompts/start_web_cad_platform.prompt.md b/.github/prompts/start_web_cad_platform.prompt.md
new file mode 100644
index 0000000..0c4c408
--- /dev/null
+++ b/.github/prompts/start_web_cad_platform.prompt.md
@@ -0,0 +1,11 @@
+---
+name: startWebCadPlatform
+description: Lance le workflow canonique Web-CAD-Platform.
+argument-hint: Donne la surface web, GraphQL, realtime, queue, review ou artifacts à traiter.
+---
+Lance le workflow canonique `Web-CAD-Platform` en prenant `specs/contracts/kill_life_agent_catalog.json` comme source de verite.
+1. Charger l'entree `Web-CAD-Platform`, `agents/web_cad_platform.md` et les surfaces web associees.
+2. Confirmer `owner_repo`, `owner_agent`, `write_set_roots`, gates et evidence avant toute modification.
+3. Utiliser les sous-agents uniquement comme metadata de lane, jamais comme agents API autonomes.
+4. Produire une execution ou un handoff strictement contenu dans le write set declare.
+5. Finir avec risques restants, gate suivante et evidence mise a jour.
diff --git a/.github/workflows/mesh_contracts.yml b/.github/workflows/mesh_contracts.yml
index 3a6532b..717e684 100644
--- a/.github/workflows/mesh_contracts.yml
+++ b/.github/workflows/mesh_contracts.yml
@@ -6,15 +6,41 @@ on:
paths:
- "specs/contracts/**"
- "tools/specs/mesh_contract_check.py"
+ - "tools/specs/validate_agent_catalog.py"
- ".github/workflows/mesh_contracts.yml"
- "tools/autonomous_next_lots.py"
+ - "tools/cad/yiacad_fusion_lot.sh"
+ - "tools/cockpit/intelligence_tui.sh"
+ - "tools/cockpit/runtime_ai_gateway.sh"
+ - "kill_life/server.py"
+ - "kill_life/agent_catalog.py"
+ - "agents/**"
+ - ".github/agents/**"
+ - ".github/prompts/**"
+ - "README.md"
+ - "README_FR.md"
+ - "docs/plans/12_plan_gestion_des_agents.md"
+ - "docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md"
pull_request:
branches: [main]
paths:
- "specs/contracts/**"
- "tools/specs/mesh_contract_check.py"
+ - "tools/specs/validate_agent_catalog.py"
- ".github/workflows/mesh_contracts.yml"
- "tools/autonomous_next_lots.py"
+ - "tools/cad/yiacad_fusion_lot.sh"
+ - "tools/cockpit/intelligence_tui.sh"
+ - "tools/cockpit/runtime_ai_gateway.sh"
+ - "kill_life/server.py"
+ - "kill_life/agent_catalog.py"
+ - "agents/**"
+ - ".github/agents/**"
+ - ".github/prompts/**"
+ - "README.md"
+ - "README_FR.md"
+ - "docs/plans/12_plan_gestion_des_agents.md"
+ - "docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md"
permissions:
contents: read
@@ -44,6 +70,8 @@ jobs:
--schema specs/contracts/workflow_handshake.schema.json \
--instance specs/contracts/examples/workflow_handshake.mesh.json \
| tee artifacts/ci/workflow_handshake_contract.json
+ python3 tools/specs/validate_agent_catalog.py --json \
+ | tee artifacts/ci/agent_catalog_contract.json
- name: Upload mesh contract reports
uses: actions/upload-artifact@v4
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..8021c6a
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,102 @@
+# AGENTS.md
+
+This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
+
+## Project Overview
+
+Kill_LIFE is an AI-native embedded systems control plane combining spec-first governance, multi-agent orchestration (BMAD method), hardware/firmware CI/CD, and YiACAD — an AI-native CAD/EDA web platform. The repo is the public control plane; sister repos `kill-life-mesh` (orchestration) and `kill-life-operator` (execution) complete the tri-repo mesh.
+
+**Stack**: Python 3.12+ (FastAPI, Pydantic), PlatformIO (ESP32/STM32), KiCad 10, Next.js 14, Excalidraw, Yjs CRDT.
+
+## Build & Test Commands
+
+```bash
+# Python environment
+bash tools/bootstrap_python_env.sh
+
+# Tests (stable = no external deps, mcp = MCP integration, all = everything)
+bash tools/test_python.sh --suite stable
+bash tools/test_python.sh --suite all
+bash tools/test_python.sh --list # list available tests
+
+# Single test file
+python3 -m pytest test/test_specific_file.py -v
+
+# Lint
+ruff check .
+
+# Coverage
+make coverage
+
+# Firmware
+cd firmware && pio run -e esp32s3_waveshare # build
+cd firmware && pio test -e native # unit tests
+
+# Hardware ERC
+make hw SCHEM=hardware/esp32_minimal/esp32_minimal.kicad_sch
+
+# CAD stack (Docker)
+make cad-up # start container
+make cad-kicad CAD_ARGS='version' # KiCad CLI
+make cad-freecad CAD_ARGS='-c "..."' # FreeCAD
+
+# Specs & compliance
+python3 tools/validate_specs.py --strict
+make compliance
+make docs # MkDocs build
+```
+
+## Architecture
+
+### Spec-First Pipeline (source of truth)
+All work flows through `specs/`:
+```
+00_intake.md → 01_spec.md → 02_arch.md → 03_plan.md → 04_tasks.md
+```
+Runtime contracts live in `specs/contracts/*.schema.json`. The mirror at `ai-agentic-embedded-base/specs/` is synced via `bash tools/specs/sync_spec_mirror.sh all --yes`.
+
+### BMAD Agents (`agents/`)
+Six role-based agents (pm, architect, firmware, hw_schematic, qa, doc) defined as markdown. The FastAPI server (`kill_life/server.py`) bridges agents to the mascarade-core LLM router. Agents are triggered by `ai:*` labels on GitHub issues.
+
+### Cockpit TUI (`tools/cockpit/`)
+~66 shell scripts providing operator dashboards. Three canonical entry points:
+- `yiacad_operator_index.sh` — public operator dashboard
+- `intelligence_tui.sh` — agentic governance & memory
+- `runtime_ai_gateway.sh` — consolidated runtime/MCP health
+
+All output `cockpit-v1` JSON to `artifacts/cockpit/`.
+
+### YiACAD (`tools/cad/` + `web/`)
+AI-native CAD platform with four layers:
+1. Native KiCad plugin + FreeCAD workbench (GUI)
+2. Service-first backend (`yiacad_backend.py`, `yiacad_backend_service.py`)
+3. Web EDA (`web/`) — Next.js + Excalidraw + KiCanvas + Yjs realtime + BullMQ workers
+4. Intelligence overlay (read-only review hints via MCP)
+
+### Firmware (`firmware/`)
+PlatformIO project targeting ESP32-S3 Waveshare. Unity for native tests. Wokwi for CI simulation (requires `WOKWI_CLI_TOKEN`).
+
+### Hardware (`hardware/`)
+KiCad 10 schematics. KiBot for exports (BOM, SVG, PDF, netlist). ERC validation in CI.
+
+## Key Conventions
+
+- **Language**: Specs and docs are primarily in French; code and comments in English.
+- **Python**: Target 3.12+, ruff for linting, line length 120.
+- **Lot contract fields**: Every lot must expose `owner_repo`, `owner_agent`, `write_set`, `status`, `evidence`.
+- **Label discipline**: Issues require `prio:*`, `risk:*`, `scope:*`, `type:*`. Automation via `ai:*` labels; `ai:hold` blocks automation.
+- **Evidence**: Proof artifacts go to `artifacts/` and `docs/evidence/`.
+
+## CI/CD (`.github/workflows/`)
+
+- `ci.yml` — main CI: Python tests, firmware build, hardware ERC
+- `release.yml` — tag-triggered release (validates `VERSION` file matches tag)
+- `evidence_pack.yml` — evidence artifact generation
+- `mesh_contracts.yml` — tri-repo contract validation
+- `kicad-exports.yml` — hardware SVG/PDF/BOM/netlist exports
+
+## External Services
+
+- **Mascarade** (`MASCARADE_CORE_URL`, default `http://192.168.0.119:8100`) — LLM router with agentic RAG
+- **MCP servers** (10 configured in `mcp.json`) — kicad, freecad, openscad, platformio, github-dispatch, knowledge-base, validate-specs, apify, huggingface, mascarade-bridge
+- **n8n** — workflow automation (ZeroClaw integration)
diff --git a/README.md b/README.md
index 1b4042f..0ab69bc 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ The positioning chosen for this consolidation pass is:
- **Spec-first**: Every evolution starts with a clear definition in `specs/` ([Spec Generator FX](https://www.youtube.com/watch?v=9bZkp7q19f0)).
> _Schaeffer: Pipeline agents listen to the noise of specs like a symphony of found sounds._
- **Standards injection**: Versioned standards and injected profiles (Agent OS).
-- **BMAD / BMAD-METHOD**: Role-based agents (PM, Architect, Firmware, QA, Doc, HW), rituals, gates, handoffs ([agents/](agents/), [bmad/](bmad/)).
+- **2026 agent catalog**: 12 catalog-backed top-level agents with contract-defined write sets, rituals, gates, prompts, and handoffs ([agents/](agents/), [specs/contracts/kill_life_agent_catalog.json](specs/contracts/kill_life_agent_catalog.json)).
- **Tool-first**: Reproducible scripts ([tools/](tools/)), canonical evidence pack in `docs/evidence/` and exposed as a CI artifact.
- **Hardware/firmware pipeline**: Bulk edits, exports, tests, compliance, snapshots.
- **Headless CAD**: KiCad 10 first + FreeCAD + OpenSCAD via MCP, containerized.
@@ -87,7 +87,7 @@ The positioning chosen for this consolidation pass is:
-> "The answer to the ultimate question of life, the universe, and AI embedded development: 42 specs, 7 agents, and a pipeline that never panics."
+> "The answer to the ultimate question of life, the universe, and AI embedded development: 42 specs, 12 agents, and a pipeline that never panics."
> — The README that never panics
>
@@ -130,7 +130,7 @@ flowchart TD
Gate --> Evidence[Evidence Pack]
Evidence --> CI[20 CI/CD workflows]
CI --> Deploy[Multi-target deployment]
- PR --> Agents[6 Agents PM Arch FW QA Doc HW]
+ PR --> Agents[12 Canonical Agents]
Agents --> Specs[specs/ — 16 specs]
Agents --> Firmware[firmware/ PlatformIO]
Agents --> Hardware[hardware/ KiCad]
@@ -175,7 +175,7 @@ Kill_LIFE/
├── hardware/ # Hardware assets and KiCad blocks
├── specs/ # 21 canonical specs and tasks (00_intake -> 04_tasks + MCP/ZeroClaw/CAD)
├── workflows/ # Canonical JSON workflows + templates + schema
-├── agents/ # 6 specialized agents (PM, Arch, FW, QA, Doc, HW)
+├── agents/ # 12 catalog-backed top-level agents
├── bmad/ # Gates (S0, S1), rituals (kickoff), templates (handoff, status)
├── compliance/ # Regulatory profiles, standards catalog, evidence
├── standards/ # Versioned global standards
@@ -191,8 +191,8 @@ Kill_LIFE/
├── docs/ # Operator docs, bridge, plans, workflows
├── test/ # Python tests (stable + MCP)
├── .github/
-│ ├── agents/ # 6 GitHub agent definitions
-│ ├── prompts/ # 37 prompts (plan_wizard_*, start_*, Eureka_*)
+│ ├── agents/ # 12 GitHub agent definitions
+│ ├── prompts/ # Catalog-backed start/plan prompts + shared orchestration prompts
│ └── workflows/ # 20 CI/CD workflows
├── KIKIFOU/ # Diagnostics, diagram, mapping, recommendations
├── mcp.json # 7 configured MCP servers
@@ -268,18 +268,24 @@ Constraints: [`specs/constraints.yaml`](specs/constraints.yaml) — source of tr
### Agents & prompts
-6 specialized agents in [`agents/`](agents/) and [`.github/agents/`](.github/agents/) :
+The canonical 2026 agent catalog is defined in [`specs/contracts/kill_life_agent_catalog.json`](specs/contracts/kill_life_agent_catalog.json) and mirrored in [`agents/`](agents/) and [`.github/agents/`](.github/agents/).
| Agent | Role |
|---|---|
-| `pm_agent` | Project management, planning, backlog |
-| `architect_agent` | System architecture, ADR |
-| `firmware_agent` | PlatformIO embedded code |
-| `hw_schematic_agent` | KiCad schematics, bulk edits |
-| `qa_agent` | Tests, quality, evidence packs |
-| `doc_agent` | Documentation, onboarding |
+| `PM-Mesh` | Intake, planning, prioritization, mesh governance |
+| `Arch-Mesh` | Architecture, ADRs, contract boundaries |
+| `Docs-Research` | Canonical docs, navigation, research, catalog narrative |
+| `Runtime-Companion` | Runtime AI, MCP alignment, provider bridges |
+| `QA-Compliance` | Tests, schema checks, release gates, evidence |
+| `Embedded-CAD` | KiCad, FreeCAD, fabrication-facing CAD lanes |
+| `Web-CAD-Platform` | Next.js, GraphQL, realtime, workers, review surfaces |
+| `UX-Lead` | YiACAD UX, native shell, design research |
+| `Firmware` | PlatformIO firmware implementation and validation |
+| `SyncOps` | Cockpit, logs, SSH, incidents, mesh operations |
+| `Schema-Guard` | Contract schemas and structural validation |
+| `KillLife-Bridge` | Tri-repo workflow/evidence bridge surfaces |
-37 prompts in [`.github/prompts/`](.github/prompts/) cover: brainstorming, specification, agent coordination, CI/CD, compliance, troubleshooting, release, HW bulk edit, and startup prompts (`start_*`) and ideation prompts (`Eureka_*`).
+Each canonical agent now owns one `start_.prompt.md` and one `plan_wizard_.prompt.md`, while [`.github/prompts/plan_wizard_agents_management.prompt.md`](.github/prompts/plan_wizard_agents_management.prompt.md) and [`.github/prompts/plan_wizard_agents_coordination.prompt.md`](.github/prompts/plan_wizard_agents_coordination.prompt.md) stay shared for orchestration and checklist alignment.
### BMAD (gates & rituals)
@@ -480,9 +486,9 @@ Workflows editable by `crazy_life` live in [`workflows/`](workflows/) and are va
1. Write the spec in `specs/`.
2. Open an issue with label `ai:spec`.
-3. The PM/Architect agent generates the plan and architecture.
-4. The Firmware agent implements code in `firmware/`.
-5. The QA agent adds Unity tests.
+3. `PM-Mesh` and `Arch-Mesh` generate the plan and architecture.
+4. `Firmware` implements code in `firmware/`.
+5. `QA-Compliance` adds Unity tests and contract validation.
6. Evidence pack generated automatically.
@@ -492,7 +498,7 @@ Workflows editable by `crazy_life` live in [`workflows/`](workflows/) and are va
### 2. KiCad Hardware Bulk Edit
1. Open an issue `type:systems` + `scope:hardware`, then add `ai:plan` (or `ai:impl` if the batch is already framed).
-2. The HW agent performs a bulk edit via `tools/hw/schops`.
+2. `Embedded-CAD` performs the bulk edit via `tools/hw/schops`.
3. Export ERC/DRC, BOM, netlist.
4. Before/after snapshot in `artifacts/hw//`.
@@ -502,9 +508,9 @@ Workflows editable by `crazy_life` live in [`workflows/`](workflows/) and are va
### 3. Documentation & Compliance
-1. Open an issue with label `ai:docs` or `ai:qa`.
-2. The Doc agent updates `docs/` and the README.
-3. The QA agent validates the compliance profile and generates the report, with doc handoff if needed.
+1. Open an issue with `ai:plan` and route the write set to the canonical owner from the agent catalog.
+2. `Docs-Research` updates `docs/`, `README.md`, and `README_FR.md`.
+3. `QA-Compliance` validates the compliance profile and generates the report, with doc handoff if needed.
diff --git a/README_FR.md b/README_FR.md
index 19f1c87..db7f724 100644
--- a/README_FR.md
+++ b/README_FR.md
@@ -72,7 +72,7 @@ Le positionnement retenu pour cette passe de consolidation est:
- **Spec-first** : Chaque évolution commence par une définition claire dans `specs/` ([Spec Generator FX](https://www.youtube.com/watch?v=9bZkp7q19f0)).
> _Schaeffer : Les agents du pipeline écoutent le bruit des specs comme une symphonie de sons trouvés._
- **Injection de standards** : Standards versionnés et profils injectés (Agent OS).
-- **BMAD / BMAD-METHOD** : Agents par rôles (PM, Architecte, Firmware, QA, Doc, HW), rituels, gates, handoffs ([agents/](agents/), [bmad/](bmad/)).
+- **Catalogue agents 2026** : 12 agents top-level pilotés par contrat avec write sets, rituels, gates, prompts et handoffs définis ([agents/](agents/), [specs/contracts/kill_life_agent_catalog.json](specs/contracts/kill_life_agent_catalog.json)).
- **Tool-first** : Scripts reproductibles ([tools/](tools/)), evidence pack canonique dans `docs/evidence/` et expose en artifact CI.
- **Pipeline hardware/firmware** : Bulk edits, exports, tests, conformité, snapshots.
- **CAD headless** : KiCad 10 first + FreeCAD + OpenSCAD via MCP, conteneurisés.
@@ -85,7 +85,7 @@ Le positionnement retenu pour cette passe de consolidation est:
-> « La réponse à la question ultime de la vie, de l'univers et du développement embarqué IA : 42 specs, 7 agents, et un pipeline qui ne panique jamais. »
+> « La réponse à la question ultime de la vie, de l'univers et du développement embarqué IA : 42 specs, 12 agents, et un pipeline qui ne panique jamais. »
> — Le README qui ne panique jamais
>
@@ -120,7 +120,7 @@ flowchart TD
Gate --> Evidence[Evidence Pack]
Evidence --> CI[22 workflows CI/CD]
CI --> Deploy[Déploiement multi-cible]
- PR --> Agents[6 Agents PM Archi FW QA Doc HW]
+ PR --> Agents[12 agents canoniques]
Agents --> Specs[specs/ — 21 specs]
Agents --> Firmware[firmware/ PlatformIO]
Agents --> Hardware[hardware/ KiCad]
@@ -162,7 +162,7 @@ Kill_LIFE/
├── hardware/ # Assets hardware et blocs KiCad
├── specs/ # 21 specs et tâches canoniques (00_intake → 04_tasks + MCP/ZeroClaw/CAD)
├── workflows/ # Workflows JSON canoniques + templates + schéma
-├── agents/ # 6 agents spécialisés (PM, Archi, FW, QA, Doc, HW)
+├── agents/ # 12 agents top-level backés par le catalogue
├── bmad/ # Gates (S0, S1), rituels (kickoff), templates (handoff, status)
├── compliance/ # Profils réglementaires, standards catalog, evidence
├── standards/ # Standards globaux versionnés
@@ -178,8 +178,8 @@ Kill_LIFE/
├── docs/ # Docs opérateur, bridge, plans, workflows
├── test/ # Tests Python (stable + MCP)
├── .github/
-│ ├── agents/ # 6 définitions agents GitHub
-│ ├── prompts/ # 37 prompts (plan_wizard_*, start_*, Eureka_*)
+│ ├── agents/ # 12 définitions agents GitHub
+│ ├── prompts/ # Prompts start/plan backés par le catalogue + prompts partagés
│ └── workflows/ # 22 workflows CI/CD
├── KIKIFOU/ # Diagnostic, diagramme, mapping, recommandations
├── mcp.json # 7 serveurs MCP configurés
@@ -255,18 +255,24 @@ Contraintes : [`specs/constraints.yaml`](specs/constraints.yaml) — source de v
### Agents & prompts
-6 agents spécialisés dans [`agents/`](agents/) et [`.github/agents/`](.github/agents/) :
+Le catalogue canonique 2026 est défini dans [`specs/contracts/kill_life_agent_catalog.json`](specs/contracts/kill_life_agent_catalog.json) puis reflété dans [`agents/`](agents/) et [`.github/agents/`](.github/agents/).
| Agent | Rôle |
|---|---|
-| `pm_agent` | Gestion de projet, planning, backlog |
-| `architect_agent` | Architecture système, ADR |
-| `firmware_agent` | Code embarqué PlatformIO |
-| `hw_schematic_agent` | Schémas KiCad, bulk edits |
-| `qa_agent` | Tests, qualité, evidence packs |
-| `doc_agent` | Documentation, onboarding |
+| `PM-Mesh` | intake, planification, priorisation, gouvernance mesh |
+| `Arch-Mesh` | architecture, ADR, frontières de contrat |
+| `Docs-Research` | docs canoniques, navigation, recherche, narration catalogue |
+| `Runtime-Companion` | runtime IA, alignement MCP, provider bridges |
+| `QA-Compliance` | tests, schémas, gates release, evidence |
+| `Embedded-CAD` | KiCad, FreeCAD, lanes CAD/fabrication |
+| `Web-CAD-Platform` | Next.js, GraphQL, realtime, workers, review |
+| `UX-Lead` | UX YiACAD, shell natif, recherche design |
+| `Firmware` | implémentation et validation PlatformIO |
+| `SyncOps` | cockpit, logs, SSH, incidents, opérations mesh |
+| `Schema-Guard` | schémas de contrat et validation structurelle |
+| `KillLife-Bridge` | workflows et evidence bridge tri-repo |
-37 prompts dans [`.github/prompts/`](.github/prompts/) couvrent : brainstorming, spécification, coordination agents, CI/CD, compliance, troubleshooting, release, bulk edit HW, et les prompts de démarrage (`start_*`) et d'idéation (`Eureka_*`).
+Chaque agent canonique possède maintenant un `start_.prompt.md` et un `plan_wizard_.prompt.md`, tandis que [`.github/prompts/plan_wizard_agents_management.prompt.md`](.github/prompts/plan_wizard_agents_management.prompt.md) et [`.github/prompts/plan_wizard_agents_coordination.prompt.md`](.github/prompts/plan_wizard_agents_coordination.prompt.md) restent partagés pour l'orchestration et la checklist globale.
### BMAD (gates & rituels)
@@ -467,9 +473,9 @@ Les workflows éditables par `crazy_life` vivent dans [`workflows/`](workflows/)
1. Rédige la spec dans `specs/`.
2. Ouvre une issue avec le label `ai:spec`.
-3. L'agent PM/Architecte génère le plan et l'architecture.
-4. L'agent Firmware implémente le code dans `firmware/`.
-5. L'agent QA ajoute des tests Unity.
+3. `PM-Mesh` et `Arch-Mesh` génèrent le plan et l'architecture.
+4. `Firmware` implémente le code dans `firmware/`.
+5. `QA-Compliance` ajoute les tests Unity et la validation contractuelle.
6. Evidence pack généré automatiquement.
@@ -479,7 +485,7 @@ Les workflows éditables par `crazy_life` vivent dans [`workflows/`](workflows/)
### 2. Bulk Edit Hardware KiCad
1. Ouvre une issue `type:systems` + `scope:hardware`, puis ajoute `ai:plan` (ou `ai:impl` si le batch est déjà cadré).
-2. L'agent HW effectue un bulk edit via `tools/hw/schops`.
+2. `Embedded-CAD` effectue le bulk edit via `tools/hw/schops`.
3. Exporte ERC/DRC, BOM, netlist.
4. Snapshot avant/après dans `artifacts/hw//`.
@@ -489,9 +495,9 @@ Les workflows éditables par `crazy_life` vivent dans [`workflows/`](workflows/)
### 3. Documentation & Conformité
-1. Ouvre une issue avec le label `ai:docs` ou `ai:qa`.
-2. L'agent Doc met à jour `docs/` et le README.
-3. L'agent QA valide le profil de conformité et génère le rapport, avec relais doc si nécessaire.
+1. Ouvre une issue avec `ai:plan` puis route le write set vers l'owner canonique du catalogue.
+2. `Docs-Research` met à jour `docs/`, `README.md` et `README_FR.md`.
+3. `QA-Compliance` valide le profil de conformité et génère le rapport, avec relais doc si nécessaire.
diff --git a/agents/arch_mesh.md b/agents/arch_mesh.md
new file mode 100644
index 0000000..f3dbf44
--- /dev/null
+++ b/agents/arch_mesh.md
@@ -0,0 +1,20 @@
+# Arch-Mesh
+
+Top-level canonical agent for architecture, ADRs, and contract boundaries.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Arch-Mesh`
+- subagents: `Contract-Guard`, `Mesh-Contracts`
+- write_set_roots: `specs/`, `docs/`, `kill_life/`
+
+## Workflow
+1. Ground architecture changes in the current specs and constraints.
+2. Keep interfaces versioned and mesh-facing contracts explicit.
+3. Publish ADR-ready rationale and handoff evidence.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `artifacts/arch/`, `docs/`
diff --git a/agents/architect_agent.md b/agents/architect_agent.md
deleted file mode 100644
index 5f425b9..0000000
--- a/agents/architect_agent.md
+++ /dev/null
@@ -1,26 +0,0 @@
-# Architect Agent
-
-> "L’architecture est une terre brisée, chaque interface une faille, et chaque spec une chance de reconstruire le monde. (N.K. Jemisin)"
-
-> "Chaque architecture commence par une spec dystopique, générée façon science-fiction automatisée. Ici, les interfaces versionnées sont la réponse à la question ultime, et chaque ADR est validé par le Spec Generator FX."
-
-Objectif : produire/mettre à jour `02_arch.md` + ADR.
-Doit respecter standards + contraintes, et garder les interfaces versionnées.
-
-## Plan
-1. Analyse des specs et contraintes (specs/01_spec.md, specs/constraints.yaml)
-2. Brainstorming architecture (diagrammes, interfaces)
-3. Rédaction ou mise à jour `02_arch.md` (architecture détaillée)
-4. Validation des gates BMAD (rituels, handoffs)
-5. Documentation ADR (Architecture Decision Record)
-6. Evidence pack (artifacts/arch/)
-7. Mise à jour des prompts agents si impact
-
-## Evidence pack
-- artifacts/arch/
-
-## Artefacts produits
-- ADR
-- architecture détaillée
-- interfaces versionnées
-- documentation
diff --git a/agents/doc_agent.md b/agents/doc_agent.md
deleted file mode 100644
index b12afe2..0000000
--- a/agents/doc_agent.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Easter Egg musique expérimentale
-
-_« La documentation s’écrit en silence, à la manière d’Éliane Radigue : chaque mot vibre lentement dans le pipeline. »_
-# Doc Agent
-
-> "La documentation est le carburant du vaisseau, et chaque guide est une escale sur la route de l’espace. (Becky Chambers, Wayfarers)"
-
-> "Bienvenue dans le meilleur des mondes : ici, chaque guide est une serviette, chaque README ne panique jamais, et la documentation dystopique motive les contributeurs à survivre à l’apocalypse technique."
-
-## Evidence pack
-- docs/
-
-## Artefacts produits
-- guides
-- README
-- pipelines
-- conventions
-- changelog
-
-Objectif : maintenir `docs/` + README, sans blabla.
-
-## Plan
-1. Analyse des specs, plan, evidence pack
-2. Mise à jour docs/, README, guides, FAQ
-3. Ajout d’exemples de projet minimal
-4. Documentation des pipelines et conventions
-5. Changelog si impact
-6. Onboarding & feedback
-7. Release documentation
diff --git a/agents/docs_research.md b/agents/docs_research.md
new file mode 100644
index 0000000..fb0add4
--- /dev/null
+++ b/agents/docs_research.md
@@ -0,0 +1,20 @@
+# Docs-Research
+
+Top-level canonical agent for documentation, navigation, research, and the agent catalog narrative.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Docs-Research`
+- subagents: `Doc-Entry`, `Plan-Recorder`, `Agent-Catalog`
+- write_set_roots: `docs/`, `README.md`, `README_FR.md`, `agents/`, `.github/agents/`
+
+## Workflow
+1. Keep the canonical entry points and plan memory coherent.
+2. Refresh operator-facing docs, research, and catalog references together.
+3. Publish documentation changes with explicit evidence links.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `docs/`, `artifacts/cockpit/`
diff --git a/agents/embedded_cad.md b/agents/embedded_cad.md
new file mode 100644
index 0000000..c3dba1e
--- /dev/null
+++ b/agents/embedded_cad.md
@@ -0,0 +1,20 @@
+# Embedded-CAD
+
+Top-level canonical agent for KiCad, FreeCAD, hardware assets, and fabrication-facing CAD lanes.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Embedded-CAD`
+- subagents: `CAD-Bridge`, `HW-BOM`, `CAD-Fusion`
+- write_set_roots: `tools/cad/`, `tools/hw/`, `hardware/`, `specs/kicad_mcp_scope_spec.md`
+
+## Workflow
+1. Keep CAD authoring, MCP lanes, and fabrication outputs coherent.
+2. Publish CAD/fab evidence with explicit degraded reasons when a toolchain is unavailable.
+3. Use subagents only as metadata for the specific lane being changed.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gates: `bmad/gates/gate_s0.md`, `bmad/gates/gate_s1.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `artifacts/cad-fusion/`, `artifacts/cad-ai-native/`, `docs/evidence/`
diff --git a/agents/firmware.md b/agents/firmware.md
new file mode 100644
index 0000000..2864141
--- /dev/null
+++ b/agents/firmware.md
@@ -0,0 +1,20 @@
+# Firmware
+
+Top-level canonical agent for embedded firmware, PlatformIO flows, and firmware evidence.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Firmware`
+- subagents: `FW-Build`
+- write_set_roots: `firmware/`, `specs/zeroclaw_dual_hw_todo.md`
+
+## Workflow
+1. Keep embedded code, native tests, and target builds reproducible.
+2. Attach build/test evidence to firmware-facing work.
+3. Escalate hardware/runtime blockers through explicit degraded reasons.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s1.md`
+- handoff: `specs/contracts/operator_lane_evidence.schema.json`
+- evidence: `docs/evidence/esp/`, `docs/evidence/linux/`
diff --git a/agents/firmware_agent.md b/agents/firmware_agent.md
deleted file mode 100644
index b770655..0000000
--- a/agents/firmware_agent.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Easter Egg musique concrète
-
-_« Le firmware rêve parfois d’un paysage sonore, comme Luc Ferrari improvisant sur des circuits imprimés. »_
-# Firmware Agent
-
-> "Chaque commit firmware est une histoire possible, et chaque test Unity une expérience sur la nature du temps. (Ted Chiang)"
-
-> "Chaque commit firmware est une fête technique, validée par les tests Unity et orchestrée comme dans une dystopie CI/CD. Bulk Edit Party FX, mode firmware activé."
-
-## Evidence pack
-- artifacts/firmware/
-
-## Artefacts produits
-- firmware
-- tests Unity
-- documentation
-- commandes
-
-Objectif : implémenter le plan dans `firmware/` avec tests Unity.
-Règles :
-
-## Plan
-1. Analyse du plan et des specs (specs/03_plan.md, 01_spec.md)
-2. Implémentation firmware (firmware/src/)
-3. Ajout ou mise à jour des tests Unity (firmware/test/)
-4. Validation via commandes PlatformIO
-5. Evidence pack (artifacts/firmware/)
-6. Documentation des commandes et conventions
-7. Release & versioning
diff --git a/agents/hw_schematic_agent.md b/agents/hw_schematic_agent.md
deleted file mode 100644
index 0cc10f4..0000000
--- a/agents/hw_schematic_agent.md
+++ /dev/null
@@ -1,78 +0,0 @@
-# Easter Egg musique concrète
-
-_« Les schémas sont des sons trouvés, comme Pierre Schaeffer captant le bruit des machines. »_
-# HW Schematic Agent (bulk edits + briques)
-
-> "Ici, chaque bulk edit est une fête technique, et chaque snapshot est rangé comme dans une dystopie bien organisée. Ne panique jamais, garde ta serviette, et laisse les robots IA danser sur le schéma !"
-
-## Evidence pack
-- snapshots avant/après
-- exports
-
-## Artefacts produits
-- schémas
-- bulk edits
-- exports
-
-Objectif :
-
-Ce rôle est conçu pour être appelé par un orchestrateur (PM/Architect/Codex) sur des tâches de schéma.
-Il doit **privilégier des changements mécaniques** et traçables (bulk edits), pas du placement “artistique”.
-
-Gates obligatoires :
-
-## Runbook (ordre strict)
-
-1) Snapshot avant (pour preuve)
-```bash
-python tools/hw/schops/schops.py snapshot --schematic <...> --name before.json
-```
-
-2) Bulk edits (une opération par PR si possible)
-```bash
-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
-```
-
-3) Exports & checks
-```bash
-python tools/hw/schops/schops.py erc --schematic <...>
-python tools/hw/schops/schops.py netlist --schematic <...>
-python tools/hw/schops/schops.py bom --schematic <...> --exclude-dnp
-```
-
-4) Snapshot après
-```bash
-python tools/hw/schops/schops.py snapshot --schematic <...> --name after.json
-```
-
-## Plan
-1. Analyse des specs et roadmap hardware
-2. Préparation des règles bulk edit (fields, footprints, nets)
-3. Orchestration des modifications (une PR par bulk edit)
-4. Exports ERC/DRC/BOM/netlist
-5. Snapshots avant/après, evidence pack
-6. Documentation des artefacts et conventions
-7. Release & versioning
-
-5) Diff (simple)
-Utiliser `tools/hw/hw_diff.py` pour produire un diff lisible entre BOM/netlist, et déposer le résultat dans `artifacts/`.
-
-## Design Blocks
-
-But : capturer des “briques” réutilisables (connecteurs, power rails, UART header, cap array, etc.).
-
-Commande :
-```bash
-python tools/hw/schops/schops.py block-make \
- --name \
- --from-sheet \
- --lib hardware/blocks/.kicad_blocks \
- --description "..." \
- --keywords "k1,k2"
-```
-
-Livrables attendus :
-- `hardware/blocks/.kicad_blocks/.kicad_block/.kicad_sch`
-- `hardware/blocks/.kicad_blocks/.kicad_block/.json`
diff --git a/agents/kill_life_bridge.md b/agents/kill_life_bridge.md
new file mode 100644
index 0000000..376ca19
--- /dev/null
+++ b/agents/kill_life_bridge.md
@@ -0,0 +1,20 @@
+# KillLife-Bridge
+
+Top-level canonical agent for cross-repo bridge contracts, continuity memory, and consumer-facing bridge lanes.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `KillLife-Bridge`
+- subagents: `Schema-Consumer`, `Artifact-Curator`
+- write_set_roots: `docs/TRI_REPO_MESH_CONTRACT_`, `specs/mesh_contracts.md`, `artifacts/cockpit/kill_life_memory/`
+
+## Workflow
+1. Translate shared contracts into bridge-safe operator and consumer surfaces.
+2. Maintain continuity memory and handoff-ready artifacts across repos.
+3. Keep bridge outputs small, explicit, and restart-friendly.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `artifacts/cockpit/kill_life_memory/`, `artifacts/cockpit/product_contract_handoff/`
diff --git a/agents/pm_agent.md b/agents/pm_agent.md
deleted file mode 100644
index 79a7780..0000000
--- a/agents/pm_agent.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Easter Egg musique expérimentale
-
-_« Le plan du projet se transforme, comme un evidence pack modulé par Daphne Oram. »_
-# PM Agent
-
-> "Le backlog est une fabrique d’eau, chaque tâche une goutte, et chaque gate une digue contre l’apocalypse. (Paolo Bacigalupi)"
-
-> "Ici, chaque gate est un mini-jeu dystopique : passe les gates, évite les bugs, et synchronise les agents comme dans une apocalypse technique. Gate Runner mode activé."
-
-## Evidence pack
-- gates BMAD
-
-## Artefacts produits
-- spec
-- backlog
-- plans
-- prompts
-
-Entrées : `specs/00_intake.md` + contraintes + standards.
-Sorties :
-
-## Plan
-1. Première entrée : lecture intake, standards, contraintes
-2. Brainstorming multi-agent (collecte besoins, risques)
-3. Rédaction ou amélioration de la spec (01_spec.md)
-4. Construction du backlog (04_tasks.md)
-5. Synchronisation avec roadmap hardware/firmware
-6. Suivi des gates BMAD et evidence pack
-7. Mise à jour des plans et prompts
diff --git a/agents/pm_mesh.md b/agents/pm_mesh.md
new file mode 100644
index 0000000..ed640d4
--- /dev/null
+++ b/agents/pm_mesh.md
@@ -0,0 +1,20 @@
+# PM-Mesh
+
+Top-level canonical agent for intake, planning, prioritization, and mesh governance.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `PM-Mesh`
+- subagents: `Plan-Orchestrator`, `Intake-Guard`, `Todo-Tracker`
+- write_set_roots: `specs/`, `docs/plans/`, `.github/prompts/`
+
+## Workflow
+1. Clarify the active lot, dependencies, risks, and expected evidence.
+2. Update the canonical plan and backlog surfaces without leaving the assigned write set.
+3. Produce a handoff aligned with BMAD and mesh summary contracts.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `artifacts/cockpit/intelligence_program/`, `docs/plans/`
diff --git a/agents/qa_agent.md b/agents/qa_agent.md
deleted file mode 100644
index eccee50..0000000
--- a/agents/qa_agent.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# QA Agent
-
-> "La conformité est une justice ancillaire, chaque evidence pack un fragment de mémoire, et chaque gate une décision collective. (Ann Leckie)"
-
-> "Un evidence pack peut-il rêver de conformité ? Ici, chaque test est une question existentielle, et chaque rapport brille dans l’obscurité près des gates S1. QA Replicant, mode dystopie activé."
-
-Objectif : assurer tests + evidence.
-
-## Plan
-1. Analyse des specs, plan, gates
-2. Ajout ou validation des tests (Unity, host)
-3. Vérification des gates BMAD (S0/S1)
-4. Génération du summary report (artifacts/report.md)
-5. Evidence pack QA
-6. Documentation troubleshooting
-7. Feedback & contribution
-
-## Evidence pack
-- QA
-
-## Artefacts produits
-- tests
-- summary report
-- documentation troubleshooting
diff --git a/agents/qa_compliance.md b/agents/qa_compliance.md
new file mode 100644
index 0000000..0c9e52c
--- /dev/null
+++ b/agents/qa_compliance.md
@@ -0,0 +1,20 @@
+# QA-Compliance
+
+Top-level canonical agent for tests, compliance gates, schema checks, and release evidence.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `QA-Compliance`
+- subagents: `Constraint-Gate`, `Contract-Tests`, `Release-Gates`
+- write_set_roots: `test/`, `compliance/`, `.github/workflows/`, `tools/specs/`
+
+## Workflow
+1. Validate contracts, tests, and release gates against the current plan.
+2. Keep evidence outputs machine-readable and operator-safe.
+3. Fail loudly on drift in schemas, contracts, or evidence.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gates: `bmad/gates/gate_s0.md`, `bmad/gates/gate_s1.md`, `bmad/gates/gate_s2.md`
+- handoff: `specs/contracts/operator_lane_evidence.schema.json`
+- evidence: `docs/evidence/`, `compliance/evidence/`, `artifacts/ci/`
diff --git a/agents/runtime_companion.md b/agents/runtime_companion.md
new file mode 100644
index 0000000..241e1cc
--- /dev/null
+++ b/agents/runtime_companion.md
@@ -0,0 +1,20 @@
+# Runtime-Companion
+
+Top-level canonical agent for runtime AI, MCP alignment, provider bridges, and degraded-safe execution.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Runtime-Companion`
+- subagents: `MCP-Health`, `Provider-Bridge`, `Runtime-Guard`
+- write_set_roots: `tools/ai/`, `tools/ops/`, `mcp.json`, `specs/mcp_agentics_target_backlog.md`
+
+## Workflow
+1. Stabilize runtime surfaces and provider routing.
+2. Keep MCP and runtime summaries operator-readable when degraded.
+3. Ship runtime changes with explicit recovery paths and evidence.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `artifacts/cockpit/runtime_ai_gateway/`, `artifacts/ops/`
diff --git a/agents/schema_guard.md b/agents/schema_guard.md
new file mode 100644
index 0000000..4d3d8f4
--- /dev/null
+++ b/agents/schema_guard.md
@@ -0,0 +1,20 @@
+# Schema-Guard
+
+Top-level canonical agent for machine-readable contracts, schema versioning, and strict validation.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Schema-Guard`
+- subagents: `Handoff-Schema`, `Evidence-Schema`
+- write_set_roots: `specs/contracts/`, `tools/specs/`, `tools/validate_specs.py`
+
+## Workflow
+1. Keep schemas and examples in lockstep.
+2. Fail on contract drift before operators or consumers see it.
+3. Enforce compatibility across handoff, workflow, and evidence surfaces.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/workflow_handshake.schema.json`
+- evidence: `artifacts/ci/`, `specs/contracts/examples/`, `specs/contracts/yiacad_uiux_output.schema.json` (PR review lane)
diff --git a/agents/sync_ops.md b/agents/sync_ops.md
new file mode 100644
index 0000000..2ef95be
--- /dev/null
+++ b/agents/sync_ops.md
@@ -0,0 +1,20 @@
+# SyncOps
+
+Top-level canonical agent for cockpit scripts, operator lanes, SSH convergence, logs, and runbook execution.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `SyncOps`
+- subagents: `TUI-Ops`, `Log-Ops`, `Ops-Governor`
+- write_set_roots: `tools/cockpit/`, `artifacts/cockpit/`, `docs/FULL_OPERATOR_LANE_`
+
+## Workflow
+1. Keep short operator paths stable and transparent.
+2. Publish daily/weekly summaries, handoffs, and incident evidence.
+3. Prefer degraded-safe behavior over silent failure in cockpit lanes.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/operator_lane_evidence.schema.json`
+- evidence: `artifacts/cockpit/`, `docs/evidence/`
diff --git a/agents/ux_lead.md b/agents/ux_lead.md
new file mode 100644
index 0000000..cf1bebc
--- /dev/null
+++ b/agents/ux_lead.md
@@ -0,0 +1,20 @@
+# UX-Lead
+
+Top-level canonical agent for YiACAD UI/UX direction and user-facing interaction design.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `UX-Lead`
+- subagents: `Apple-HIG`, `CAD-UX`
+- write_set_roots: `docs/YIACAD_`, `docs/CAD_AI_NATIVE_`, `specs/yiacad_uiux_apple_native_spec.md`
+
+## Workflow
+1. Turn product constraints into explicit interaction and layout decisions.
+2. Keep UX guidance aligned with the canonical specs and runtime boundaries.
+3. Publish design rationale and evidence without redefining execution ownership.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gate: `bmad/gates/gate_s0.md`
+- handoff: `specs/contracts/agent_handoff.schema.json`
+- evidence: `artifacts/uiux_tui/`, `docs/`
diff --git a/agents/web_cad_platform.md b/agents/web_cad_platform.md
new file mode 100644
index 0000000..f497020
--- /dev/null
+++ b/agents/web_cad_platform.md
@@ -0,0 +1,20 @@
+# Web-CAD-Platform
+
+Top-level canonical agent for the YiACAD web product, project read models, queue/workers, realtime, and review surfaces.
+
+## Scope
+- owner_repo: `Kill_LIFE`
+- owner_agent: `Web-CAD-Platform`
+- subagents: `Project-Service`, `EDA-CI-Orchestrator`, `Realtime-Collab`, `PR-Review-Orchestrator`
+- write_set_roots: `web/app/`, `web/components/`, `web/lib/`, `web/realtime/`, `web/workers/`, `web/project/.ci/`
+
+## Workflow
+1. Keep Git-first project state, queue orchestration, and realtime transport aligned.
+2. Preserve the review-first product posture and explicit artifacts.
+3. Publish web changes with contract-safe metadata and evidence.
+
+## Contracts
+- ritual: `bmad/rituals/kickoff.md`
+- gates: `bmad/gates/gate_s0.md`, `bmad/gates/gate_s2.md`
+- handoff: `specs/contracts/summary_short.schema.json`
+- evidence: `web/project/.ci/`, `artifacts/cockpit/intelligence_program/`
diff --git a/ai-agentic-embedded-base/specs/contracts/agent_catalog.schema.json b/ai-agentic-embedded-base/specs/contracts/agent_catalog.schema.json
new file mode 100644
index 0000000..7d5daf0
--- /dev/null
+++ b/ai-agentic-embedded-base/specs/contracts/agent_catalog.schema.json
@@ -0,0 +1,173 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://electron-rare.local/specs/contracts/agent_catalog.schema.json",
+ "title": "Kill_LIFE Agent Catalog",
+ "type": "object",
+ "required": [
+ "contract_version",
+ "updated_at",
+ "repo",
+ "agents",
+ "legacy_runtime_aliases"
+ ],
+ "properties": {
+ "contract_version": {
+ "type": "string",
+ "const": "kill-life-agent-catalog/v1"
+ },
+ "updated_at": {
+ "type": "string",
+ "minLength": 1
+ },
+ "repo": {
+ "type": "string",
+ "const": "Kill_LIFE"
+ },
+ "agents": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/$defs/agent"
+ }
+ },
+ "legacy_runtime_aliases": {
+ "type": "object",
+ "minProperties": 1,
+ "additionalProperties": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ },
+ "$defs": {
+ "agent": {
+ "type": "object",
+ "required": [
+ "id",
+ "slug",
+ "display_name",
+ "purpose",
+ "owner_repo",
+ "agent_doc",
+ "github_agent_doc",
+ "start_prompt",
+ "plan_wizard_prompt",
+ "write_set_roots",
+ "rituals",
+ "gates",
+ "handoff_contracts",
+ "evidence_paths",
+ "subagents",
+ "public_api_enabled"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "slug": {
+ "type": "string",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "display_name": {
+ "type": "string",
+ "minLength": 1
+ },
+ "purpose": {
+ "type": "string",
+ "minLength": 1
+ },
+ "owner_repo": {
+ "type": "string",
+ "const": "Kill_LIFE"
+ },
+ "agent_doc": {
+ "type": "string",
+ "minLength": 1
+ },
+ "github_agent_doc": {
+ "type": "string",
+ "minLength": 1
+ },
+ "start_prompt": {
+ "type": "string",
+ "minLength": 1
+ },
+ "plan_wizard_prompt": {
+ "type": "string",
+ "minLength": 1
+ },
+ "write_set_roots": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "rituals": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "gates": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "handoff_contracts": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "evidence_paths": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "subagents": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/$defs/subagent"
+ }
+ },
+ "public_api_enabled": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": true
+ },
+ "subagent": {
+ "type": "object",
+ "required": [
+ "id",
+ "purpose"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "purpose": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "additionalProperties": true
+}
diff --git a/ai-agentic-embedded-base/specs/contracts/artifact_wms_index_rules.json b/ai-agentic-embedded-base/specs/contracts/artifact_wms_index_rules.json
index d336df3..5a6c071 100644
--- a/ai-agentic-embedded-base/specs/contracts/artifact_wms_index_rules.json
+++ b/ai-agentic-embedded-base/specs/contracts/artifact_wms_index_rules.json
@@ -18,6 +18,7 @@
"match": "kill_life_memory",
"consumer_layer": "MES",
"owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": ["T-RE-261", "T-RE-262", "T-RE-267"],
"purpose": "Canonical execution memory and resume continuity"
},
@@ -38,56 +39,64 @@
{
"match": "mascarade_incident",
"consumer_layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": ["T-RE-233", "T-RE-240", "T-RE-241", "T-RE-245"],
"purpose": "Incident brief, registry, queue, and watch outputs"
},
{
"match": "mascarade_watch",
"consumer_layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": ["T-RE-250", "T-RE-252", "T-RE-257"],
"purpose": "Short watchboard and history artifacts"
},
{
"match": "mascarade_runtime_health",
"consumer_layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"lot_refs": ["T-RE-222", "T-RE-263"],
"purpose": "Mascarade and Ollama runtime health"
},
{
"match": "mascarade_agent_smoke",
"consumer_layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"lot_refs": ["T-RE-*"],
"purpose": "Agent smoke checks and runtime reachability"
},
{
"match": "mesh",
"consumer_layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"lot_refs": ["T-RE-*"],
"purpose": "Mesh, dispatch, and runtime preflight"
},
{
"match": "dataset",
"consumer_layer": "WMS",
- "owner_agent": "Dataset-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Dataset-Curator",
"lot_refs": ["T-MA-015", "T-MS-002", "T-MS-003"],
"purpose": "Dataset audit, preflight, and fine-tune staging"
},
{
"match": "repo_state",
"consumer_layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"lot_refs": ["T-RE-*"],
"purpose": "Repository coherence and state evidence"
},
{
"match": "specs",
"consumer_layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"lot_refs": ["T-RE-*"],
"purpose": "Specification-derived artifacts"
},
@@ -101,7 +110,8 @@
],
"defaults": {
"consumer_layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": [],
"purpose": "Unclassified artifact group"
}
diff --git a/ai-agentic-embedded-base/specs/contracts/infra_vps.schema.json b/ai-agentic-embedded-base/specs/contracts/infra_vps.schema.json
new file mode 100644
index 0000000..261fb58
--- /dev/null
+++ b/ai-agentic-embedded-base/specs/contracts/infra_vps.schema.json
@@ -0,0 +1,127 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://electron-rare.local/specs/contracts/infra_vps.schema.json",
+ "title": "Infra VPS Inventory",
+ "type": "object",
+ "required": [
+ "contract_version",
+ "generated_at",
+ "component",
+ "owner_repo",
+ "services"
+ ],
+ "properties": {
+ "contract_version": {
+ "type": "string",
+ "const": "infra-vps-inventory/v1"
+ },
+ "generated_at": {
+ "type": "string"
+ },
+ "component": {
+ "type": "string",
+ "const": "infra-vps-inventory"
+ },
+ "owner_repo": {
+ "type": "string"
+ },
+ "owner_agent": {
+ "type": "string"
+ },
+ "note": {
+ "type": "string"
+ },
+ "services": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/$defs/service"
+ }
+ }
+ },
+ "additionalProperties": true,
+ "$defs": {
+ "service": {
+ "type": "object",
+ "required": [
+ "id",
+ "service",
+ "protocol",
+ "status"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "domain": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "user": {
+ "type": [
+ "string",
+ "null"
+ ]
+ },
+ "service": {
+ "type": "string",
+ "minLength": 1
+ },
+ "protocol": {
+ "type": "string",
+ "enum": [
+ "https",
+ "http",
+ "internal",
+ "tcp"
+ ]
+ },
+ "port": {
+ "type": "integer"
+ },
+ "status": {
+ "type": "string",
+ "enum": [
+ "ok",
+ "degraded",
+ "blocked",
+ "parked",
+ "unknown"
+ ]
+ },
+ "tags": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "note": {
+ "type": "string"
+ },
+ "sec_audit": {
+ "type": "string"
+ },
+ "degraded_reasons": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "checked_at": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/ai-agentic-embedded-base/specs/contracts/kill_life_agent_catalog.json b/ai-agentic-embedded-base/specs/contracts/kill_life_agent_catalog.json
new file mode 100644
index 0000000..bd80e04
--- /dev/null
+++ b/ai-agentic-embedded-base/specs/contracts/kill_life_agent_catalog.json
@@ -0,0 +1,537 @@
+{
+ "$schema": "specs/contracts/agent_catalog.schema.json",
+ "contract_version": "kill-life-agent-catalog/v1",
+ "updated_at": "2026-03-29",
+ "repo": "Kill_LIFE",
+ "agents": [
+ {
+ "id": "PM-Mesh",
+ "slug": "pm_mesh",
+ "display_name": "PM Mesh",
+ "purpose": "Pilot intake, planning, prioritization, and mesh governance handoffs.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/pm_mesh.md",
+ "github_agent_doc": ".github/agents/pm_mesh.md",
+ "start_prompt": ".github/prompts/start_pm_mesh.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_pm_mesh.prompt.md",
+ "write_set_roots": [
+ "specs/",
+ "docs/plans/",
+ ".github/prompts/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/summary_short.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/plans/",
+ "artifacts/cockpit/intelligence_program/"
+ ],
+ "subagents": [
+ {
+ "id": "Plan-Orchestrator",
+ "purpose": "Sequence lots and canonical plans."
+ },
+ {
+ "id": "Intake-Guard",
+ "purpose": "Triage intake and capture goals."
+ },
+ {
+ "id": "Todo-Tracker",
+ "purpose": "Maintain execution-ready task lists."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Arch-Mesh",
+ "slug": "arch_mesh",
+ "display_name": "Arch Mesh",
+ "purpose": "Own architecture, ADRs, and cross-surface contract design.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/arch_mesh.md",
+ "github_agent_doc": ".github/agents/arch_mesh.md",
+ "start_prompt": ".github/prompts/start_arch_mesh.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_arch_mesh.prompt.md",
+ "write_set_roots": [
+ "specs/",
+ "docs/",
+ "kill_life/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/arch/",
+ "docs/"
+ ],
+ "subagents": [
+ {
+ "id": "Contract-Guard",
+ "purpose": "Keep public interfaces versioned and coherent."
+ },
+ {
+ "id": "Mesh-Contracts",
+ "purpose": "Align tri-repo contract boundaries."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Docs-Research",
+ "slug": "docs_research",
+ "display_name": "Docs Research",
+ "purpose": "Maintain canonical documentation, navigation, research summaries, and the agent catalog narrative.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/docs_research.md",
+ "github_agent_doc": ".github/agents/docs_research.md",
+ "start_prompt": ".github/prompts/start_docs_research.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_docs_research.prompt.md",
+ "write_set_roots": [
+ "docs/",
+ "README.md",
+ "README_FR.md",
+ "agents/",
+ ".github/agents/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/",
+ "artifacts/cockpit/"
+ ],
+ "subagents": [
+ {
+ "id": "Doc-Entry",
+ "purpose": "Maintain canonical entry points and onboarding."
+ },
+ {
+ "id": "Plan-Recorder",
+ "purpose": "Capture plan memory and governance deltas."
+ },
+ {
+ "id": "Agent-Catalog",
+ "purpose": "Own agent-facing docs and references."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Runtime-Companion",
+ "slug": "runtime_companion",
+ "display_name": "Runtime Companion",
+ "purpose": "Own runtime AI, MCP alignment, provider bridges, and degraded-safe execution surfaces.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/runtime_companion.md",
+ "github_agent_doc": ".github/agents/runtime_companion.md",
+ "start_prompt": ".github/prompts/start_runtime_companion.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_runtime_companion.prompt.md",
+ "write_set_roots": [
+ "tools/ai/",
+ "tools/ops/",
+ "mcp.json",
+ "specs/mcp_agentics_target_backlog.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/runtime_mcp_ia_gateway.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cockpit/runtime_ai_gateway/",
+ "artifacts/ops/"
+ ],
+ "subagents": [
+ {
+ "id": "MCP-Health",
+ "purpose": "Maintain runtime and MCP summary surfaces."
+ },
+ {
+ "id": "Provider-Bridge",
+ "purpose": "Stabilize provider routing and live smokes."
+ },
+ {
+ "id": "Runtime-Guard",
+ "purpose": "Track runtime health and recovery paths."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "QA-Compliance",
+ "slug": "qa_compliance",
+ "display_name": "QA Compliance",
+ "purpose": "Own tests, compliance gates, schema checks, and release-ready evidence.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/qa_compliance.md",
+ "github_agent_doc": ".github/agents/qa_compliance.md",
+ "start_prompt": ".github/prompts/start_qa_compliance.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_qa_compliance.prompt.md",
+ "write_set_roots": [
+ "test/",
+ "compliance/",
+ ".github/workflows/",
+ "tools/specs/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md",
+ "bmad/gates/gate_s1.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/operator_lane_evidence.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/evidence/",
+ "compliance/evidence/",
+ "artifacts/ci/"
+ ],
+ "subagents": [
+ {
+ "id": "Constraint-Gate",
+ "purpose": "Enforce invariants and compliance rules."
+ },
+ {
+ "id": "Contract-Tests",
+ "purpose": "Keep JSON and CLI contracts stable."
+ },
+ {
+ "id": "Release-Gates",
+ "purpose": "Guard CI, build, and release checks."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Embedded-CAD",
+ "slug": "embedded_cad",
+ "display_name": "Embedded CAD",
+ "purpose": "Own KiCad, FreeCAD, OpenSCAD, hardware assets, and fabrication-facing CAD lanes.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/embedded_cad.md",
+ "github_agent_doc": ".github/agents/embedded_cad.md",
+ "start_prompt": ".github/prompts/start_embedded_cad.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_embedded_cad.prompt.md",
+ "write_set_roots": [
+ "tools/cad/",
+ "tools/hw/",
+ "hardware/",
+ "specs/kicad_mcp_scope_spec.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md",
+ "bmad/gates/gate_s1.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/fab_package.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cad-fusion/",
+ "artifacts/cad-ai-native/",
+ "docs/evidence/"
+ ],
+ "subagents": [
+ {
+ "id": "CAD-Bridge",
+ "purpose": "Bridge KiCad, FreeCAD, and MCP lanes."
+ },
+ {
+ "id": "HW-BOM",
+ "purpose": "Own fabrication and BOM-oriented outputs."
+ },
+ {
+ "id": "CAD-Fusion",
+ "purpose": "Run the YiACAD AI-native fusion lot."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Web-CAD-Platform",
+ "slug": "web_cad_platform",
+ "display_name": "Web CAD Platform",
+ "purpose": "Own the YiACAD web product, project read models, queue/workers, realtime, and review surfaces.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/web_cad_platform.md",
+ "github_agent_doc": ".github/agents/web_cad_platform.md",
+ "start_prompt": ".github/prompts/start_web_cad_platform.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_web_cad_platform.prompt.md",
+ "write_set_roots": [
+ "web/app/",
+ "web/components/",
+ "web/lib/",
+ "web/realtime/",
+ "web/workers/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/summary_short.schema.json",
+ "specs/contracts/yiacad_uiux_output.schema.json"
+ ],
+ "evidence_paths": [
+ "web/project/.ci/",
+ "artifacts/cockpit/intelligence_program/"
+ ],
+ "subagents": [
+ {
+ "id": "Project-Service",
+ "purpose": "Own GraphQL and project metadata."
+ },
+ {
+ "id": "EDA-CI-Orchestrator",
+ "purpose": "Own queue, workers, and EDA job orchestration."
+ },
+ {
+ "id": "Realtime-Collab",
+ "purpose": "Own Yjs transport and persistence."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "UX-Lead",
+ "slug": "ux_lead",
+ "display_name": "UX Lead",
+ "purpose": "Own UI/UX direction for YiACAD and user-facing design systems.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/ux_lead.md",
+ "github_agent_doc": ".github/agents/ux_lead.md",
+ "start_prompt": ".github/prompts/start_ux_lead.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_ux_lead.prompt.md",
+ "write_set_roots": [
+ "docs/YIACAD_",
+ "docs/CAD_AI_NATIVE_",
+ "specs/yiacad_uiux_apple_native_spec.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/uiux_tui/",
+ "docs/"
+ ],
+ "subagents": [
+ {
+ "id": "Apple-HIG",
+ "purpose": "Carry Apple-native design constraints."
+ },
+ {
+ "id": "CAD-UX",
+ "purpose": "Design CAD-specific interaction flows."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Firmware",
+ "slug": "firmware",
+ "display_name": "Firmware",
+ "purpose": "Own embedded firmware, PlatformIO flows, and firmware evidence.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/firmware.md",
+ "github_agent_doc": ".github/agents/firmware.md",
+ "start_prompt": ".github/prompts/start_firmware.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_firmware.prompt.md",
+ "write_set_roots": [
+ "firmware/",
+ "specs/zeroclaw_dual_hw_todo.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s1.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/operator_lane_evidence.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/evidence/esp/",
+ "docs/evidence/linux/"
+ ],
+ "subagents": [
+ {
+ "id": "FW-Build",
+ "purpose": "Drive builds, tests, and firmware-ready outputs."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "SyncOps",
+ "slug": "sync_ops",
+ "display_name": "Sync Ops",
+ "purpose": "Own cockpit scripts, operator lanes, SSH convergence, logs, and runbook execution.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/sync_ops.md",
+ "github_agent_doc": ".github/agents/sync_ops.md",
+ "start_prompt": ".github/prompts/start_sync_ops.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_sync_ops.prompt.md",
+ "write_set_roots": [
+ "tools/cockpit/",
+ "artifacts/cockpit/",
+ "docs/FULL_OPERATOR_LANE_"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/operator_lane_evidence.schema.json",
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cockpit/",
+ "docs/evidence/"
+ ],
+ "subagents": [
+ {
+ "id": "TUI-Ops",
+ "purpose": "Maintain shell/TUI operator entry points."
+ },
+ {
+ "id": "Log-Ops",
+ "purpose": "Own log summaries, purges, and incident outputs."
+ },
+ {
+ "id": "Ops-Governor",
+ "purpose": "Track operator governance and capacity views."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Schema-Guard",
+ "slug": "schema_guard",
+ "display_name": "Schema Guard",
+ "purpose": "Own machine-readable contracts, schema versioning, and strict validation.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/schema_guard.md",
+ "github_agent_doc": ".github/agents/schema_guard.md",
+ "start_prompt": ".github/prompts/start_schema_guard.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_schema_guard.prompt.md",
+ "write_set_roots": [
+ "specs/contracts/",
+ "tools/specs/",
+ "tools/validate_specs.py"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/workflow_handshake.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/ci/",
+ "specs/contracts/examples/"
+ ],
+ "subagents": [
+ {
+ "id": "Handoff-Schema",
+ "purpose": "Maintain handoff schema compatibility."
+ },
+ {
+ "id": "Evidence-Schema",
+ "purpose": "Guard evidence contracts and summaries."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "KillLife-Bridge",
+ "slug": "kill_life_bridge",
+ "display_name": "Kill Life Bridge",
+ "purpose": "Own cross-repo bridge contracts, continuity memory, and consumer-facing bridge lanes.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/kill_life_bridge.md",
+ "github_agent_doc": ".github/agents/kill_life_bridge.md",
+ "start_prompt": ".github/prompts/start_kill_life_bridge.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_kill_life_bridge.prompt.md",
+ "write_set_roots": [
+ "docs/TRI_REPO_MESH_CONTRACT_",
+ "specs/mesh_contracts.md",
+ "artifacts/cockpit/kill_life_memory/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/workflow_handshake.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cockpit/kill_life_memory/",
+ "artifacts/cockpit/product_contract_handoff/"
+ ],
+ "subagents": [
+ {
+ "id": "Schema-Consumer",
+ "purpose": "Translate shared contracts into consumer-facing surfaces."
+ },
+ {
+ "id": "Artifact-Curator",
+ "purpose": "Maintain continuity memory and artifact-facing bridge rules."
+ }
+ ],
+ "public_api_enabled": true
+ }
+ ],
+ "legacy_runtime_aliases": {
+ "pm": "PM-Mesh",
+ "architect": "Arch-Mesh",
+ "firmware": "Firmware",
+ "hw_schematic": "Embedded-CAD",
+ "qa": "QA-Compliance",
+ "doc": "Docs-Research"
+ }
+}
diff --git a/ai-agentic-embedded-base/specs/contracts/ops_kill_life_erp_registry.json b/ai-agentic-embedded-base/specs/contracts/ops_kill_life_erp_registry.json
index f8d3fc6..ddf2445 100644
--- a/ai-agentic-embedded-base/specs/contracts/ops_kill_life_erp_registry.json
+++ b/ai-agentic-embedded-base/specs/contracts/ops_kill_life_erp_registry.json
@@ -15,13 +15,15 @@
{
"id": "PLM",
"name": "Product Lifecycle Management",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"purpose": "Specifications, plans, contracts, diagrams, design memory"
},
{
"id": "ERP",
"name": "L'electronrare Ops",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"purpose": "Resource governance, machine allocation, ownership, secret scopes, priorities"
},
{
@@ -33,13 +35,15 @@
{
"id": "WMS",
"name": "Artifacts and Operational Memory",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"purpose": "Artifacts, logs, datasets, handoff outputs, retention"
},
{
"id": "DCS",
"name": "Runtime and Mesh Control",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"purpose": "Runtimes, SSH mesh, dispatch P2P, health checks, load balancing"
}
],
@@ -51,7 +55,8 @@
"runtime_roots": ["/home/clems/mascarade", "/home/clems/mascarade-main"],
"role": "primary-heavy",
"criticality": "high",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"priority_order": 1,
"load_policy": "prefer-heavy",
"secret_scope": ["router", "governance"],
@@ -64,7 +69,8 @@
"runtime_roots": ["/root/mascarade-main"],
"role": "vm-governance-reserve",
"criticality": "medium",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"priority_order": 5,
"load_policy": "avoid-heavy",
"secret_scope": ["router", "governance"],
@@ -77,7 +83,8 @@
"runtime_roots": ["/home/kxkm/mascarade", "/home/kxkm/mascarade-main"],
"role": "interactive-ai",
"criticality": "high",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"priority_order": 2,
"load_policy": "secondary-heavy",
"secret_scope": ["router", "governance"],
@@ -90,7 +97,8 @@
"runtime_roots": ["/Users/cils/mascarade-main", "/Users/cils/mascarade"],
"role": "non-essential-burst",
"criticality": "medium",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"priority_order": 3,
"load_policy": "burst-only",
"secret_scope": ["router", "governance"],
@@ -114,7 +122,8 @@
{
"name": "router",
"env_var": "MISTRAL_API_KEY",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"consumer_scope": "Mascarade router and Studio integrations",
"storage": [
"/Users/electron/Documents/Projets/mascarade/.env",
@@ -125,6 +134,7 @@
"name": "governance",
"env_var": "MISTRAL_GOVERNANCE_API_KEY",
"owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Schema-Consumer",
"consumer_scope": "Kill_LIFE governance scripts and Mistral operator tooling",
"storage": [
"~/.kill-life/mistral.env",
@@ -136,13 +146,15 @@
{
"path": "docs/",
"layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"purpose": "Plans, contracts, diagrams, research"
},
{
"path": "specs/",
"layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"purpose": "Task backlog, contracts, structured specifications"
},
{
@@ -166,43 +178,50 @@
{
"path": "tools/cockpit/machine_registry.sh",
"layer": "ERP",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"purpose": "Machine and capacity registry"
},
{
"path": "tools/cockpit/mascarade_mesh_env_sync.sh",
"layer": "ERP",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"purpose": "Router secret propagation"
},
{
"path": "tools/cockpit/kill_life_mistral_governance_sync.sh",
"layer": "ERP",
"owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Schema-Consumer",
"purpose": "Governance secret propagation"
},
{
"path": "artifacts/",
"layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"purpose": "Artifacts, logs, reports, watchboards"
},
{
"path": "tools/cockpit/dataset_audit_tui.sh",
"layer": "WMS",
- "owner_agent": "Dataset-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Dataset-Curator",
"purpose": "Dataset audit and preflight"
},
{
"path": "tools/cockpit/mesh_sync_preflight.sh",
"layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"purpose": "Mesh preflight and dispatch readiness"
},
{
"path": "tools/cockpit/mascarade_runtime_health.sh",
"layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"purpose": "Mascarade and Ollama runtime health"
}
]
diff --git a/ai-agentic-embedded-base/specs/contracts/pcb_ai_fab_registry.json b/ai-agentic-embedded-base/specs/contracts/pcb_ai_fab_registry.json
index f748838..b5c4a49 100644
--- a/ai-agentic-embedded-base/specs/contracts/pcb_ai_fab_registry.json
+++ b/ai-agentic-embedded-base/specs/contracts/pcb_ai_fab_registry.json
@@ -13,7 +13,7 @@
"kind": "saas-eda-fast-fab",
"status": "evaluate-fast-fab",
"owner_agent": "Embedded-CAD",
- "sub_agent": "HW-BOM",
+ "owner_subagent": "HW-BOM",
"inputs": ["schematic", "kicad-import", "altium-import"],
"outputs": ["gerber", "odb++", "kicad", "altium", "bom"],
"strengths": [
@@ -41,8 +41,8 @@
"name": "Quilter",
"kind": "physics-driven-routing-engine",
"status": "canary-route",
- "owner_agent": "CAD-Bridge",
- "sub_agent": "CAD-Smoke",
+ "owner_agent": "Embedded-CAD",
+ "owner_subagent": "CAD-Bridge",
"inputs": ["schematic", "board-file", "project-file"],
"outputs": ["same-cad-format", "layout-candidates", "constraint-review"],
"strengths": [
@@ -71,8 +71,8 @@
"name": "kicad-happy",
"kind": "open-source-claude-skills",
"status": "adopt-patterns",
- "owner_agent": "HW-BOM",
- "sub_agent": "Forge",
+ "owner_agent": "Embedded-CAD",
+ "owner_subagent": "HW-BOM",
"inputs": ["kicad-sch", "kicad-pcb", "gerbers", "bom"],
"outputs": ["reviews", "pricing", "supplier-orders", "jlcpcb-order-files"],
"strengths": [
diff --git a/ai-agentic-embedded-base/specs/contracts/runtime_mcp_ia_gateway.schema.json b/ai-agentic-embedded-base/specs/contracts/runtime_mcp_ia_gateway.schema.json
index 88b950d..c78a2ad 100644
--- a/ai-agentic-embedded-base/specs/contracts/runtime_mcp_ia_gateway.schema.json
+++ b/ai-agentic-embedded-base/specs/contracts/runtime_mcp_ia_gateway.schema.json
@@ -105,6 +105,45 @@
},
"ia": {
"$ref": "#/$defs/surfaceStatus"
+ },
+ "firmware_cad": {
+ "$ref": "#/$defs/surfaceStatus"
+ },
+ "web_platform": {
+ "$ref": "#/$defs/surfaceStatus"
+ },
+ "langfuse": {
+ "$ref": "#/$defs/surfaceStatus"
+ },
+ "infra_vps": {
+ "$ref": "#/$defs/surfaceStatus"
+ }
+ },
+ "additionalProperties": true
+ },
+ "sources": {
+ "type": "object",
+ "properties": {
+ "intelligence": {
+ "$ref": "#/$defs/sourceStatus"
+ },
+ "mesh": {
+ "$ref": "#/$defs/sourceStatus"
+ },
+ "mascarade": {
+ "$ref": "#/$defs/sourceStatus"
+ },
+ "firmware_cad": {
+ "$ref": "#/$defs/sourceStatus"
+ },
+ "web_platform": {
+ "$ref": "#/$defs/sourceStatus"
+ },
+ "langfuse": {
+ "$ref": "#/$defs/sourceStatus"
+ },
+ "infra_vps": {
+ "$ref": "#/$defs/sourceStatus"
}
},
"additionalProperties": true
@@ -156,6 +195,29 @@
}
},
"additionalProperties": true
+ },
+ "sourceStatus": {
+ "type": "object",
+ "required": [
+ "status"
+ ],
+ "properties": {
+ "status": {
+ "type": "string",
+ "enum": [
+ "ready",
+ "degraded",
+ "blocked"
+ ]
+ },
+ "path": {
+ "type": [
+ "string",
+ "null"
+ ]
+ }
+ },
+ "additionalProperties": true
}
},
"additionalProperties": true
diff --git a/bmad/gates/gate_s2.md b/bmad/gates/gate_s2.md
new file mode 100644
index 0000000..e69de29
diff --git a/bmad/rituals/kickoff.md b/bmad/rituals/kickoff.md
index 1f76359..ebbd5e0 100644
--- a/bmad/rituals/kickoff.md
+++ b/bmad/rituals/kickoff.md
@@ -1,7 +1,7 @@
# Kickoff (15–30 min)
-- Clarifier l’objectif et la definition of done
+- Clarifier l'objectif et la definition of done
- Valider `constraints.yaml`
- Créer/mettre à jour `01_spec.md`
- Découper `03_plan.md` + `04_tasks.md`
-- Décider des gates (S0/S1/…)
+- Identifier et valider les gates applicables (S0/S1/S2/…) selon le domaine du lot
diff --git a/bmad/templates/handoff.md b/bmad/templates/handoff.md
index 8e536fc..19c1838 100644
--- a/bmad/templates/handoff.md
+++ b/bmad/templates/handoff.md
@@ -7,9 +7,18 @@
- Files:
- Behavior:
+## Gate passed
+- [ ] S0 — Spec ready
+- [ ] S1 — Build & tests (firmware/hardware, si applicable)
+- [ ] S2 — Web & PR review automation (si applicable)
+
## Evidence
- Logs:
- Artifacts:
+## Consumer contracts
+- Schemas relied on by downstream agents:
+- Artifact paths exposed to consumers:
+
## Risks / follow-ups
- ...
diff --git a/docs/AGENTIC_LANDSCAPE.md b/docs/AGENTIC_LANDSCAPE.md
index f603af0..3f18cfb 100644
--- a/docs/AGENTIC_LANDSCAPE.md
+++ b/docs/AGENTIC_LANDSCAPE.md
@@ -99,3 +99,33 @@ Delta d'execution machine-readable:
- `Schema-Guard` publie des schemas JSON versionnes pour `agent_handoff`, `repo_snapshot`, `workflow_handshake`.
- `Runtime-Smoke` et `Schema-Consumer` consomment ces contrats depuis la lane soeur `Kill_LIFE-main` au moment des preflights compagnons.
- La validation CI visible du handshake passe desormais par `.github/workflows/mesh_contracts.yml`.
+
+## Delta agentics 2026-03-29
+
+Mise a jour de la lane PR review automation (branche feat/yiacad-pr-review-automation, PR #18).
+
+### Nouveautes
+
+- Gate S2 (bmad/gates/gate_s2.md) -- couvre la lane Web & PR review : Playwright, BullMQ worker, evidence intelligence_program/, validation summary_short.schema.json.
+- Subagent PR-Review-Orchestrator -- ajoute a Web-CAD-Platform, write set : web/project/.ci/.
+- Schema-Guard -- reference specs/contracts/yiacad_uiux_output.schema.json comme contrat evidence de la lane review.
+- QA-Compliance -- gate S2 ajoutee aux gates de validation release.
+- bmad/templates/handoff.md -- enrichi : sections Gate passed + Consumer contracts.
+- bmad/rituals/kickoff.md -- identification dynamique des gates (S0/S1/S2/...) selon le domaine du lot.
+
+### Table agents canoniques (2026-03-29)
+
+| Agent | owner_agent | Handoff contract | Gates |
+| --- | --- | --- | --- |
+| PM-Mesh | Kill_LIFE | agent_handoff.schema.json | S0 |
+| Arch-Mesh | Kill_LIFE | agent_handoff.schema.json | S0 |
+| Schema-Guard | Kill_LIFE | workflow_handshake.schema.json | S0 |
+| QA-Compliance | transversal | operator_lane_evidence.schema.json | S0, S1, S2 |
+| KillLife-Bridge | Kill_LIFE | agent_handoff.schema.json | S0 |
+| Runtime-Companion | Kill_LIFE | agent_handoff.schema.json | S0 |
+| SyncOps | Kill_LIFE | operator_lane_evidence.schema.json | S0 |
+| Web-CAD-Platform | Kill_LIFE | summary_short.schema.json | S0, S2 |
+| Docs-Research | transversal | agent_handoff.schema.json | S0 |
+| Embedded-CAD | Kill_LIFE | agent_handoff.schema.json | S0, S1 |
+| Firmware | Kill_LIFE | operator_lane_evidence.schema.json | S1 |
+| UX-Lead | Kill_LIFE | agent_handoff.schema.json | S0 |
diff --git a/docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md b/docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md
index b2be028..d5dba07 100644
--- a/docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md
+++ b/docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md
@@ -1,162 +1,86 @@
# Matrice agentique spec/module tri-repo
-Last updated: 2026-03-22 14:30 CET
+Last updated: 2026-03-29
-## Regles d'affectation
+Cette matrice aligne les surfaces du repo avec le catalogue canonique 2026. Les seuls `owner_agent` top-level autorisés sont `PM-Mesh`, `Arch-Mesh`, `Docs-Research`, `Runtime-Companion`, `QA-Compliance`, `Embedded-CAD`, `Web-CAD-Platform`, `UX-Lead`, `Firmware`, `SyncOps`, `Schema-Guard` et `KillLife-Bridge`.
-- Une specification ou un module = un `owner_agent`, un `sub_agent`, un `write_set` et une preuve attendue.
-- Les surfaces cross-repo et hors-repo sont `single-writer`.
-- Les write-sets restent disjoints par lot.
-- Les lanes mesh `*-main` sont les seules lanes de propagation.
-- `cils` reste sous garde `cils-lockdown` pour les surfaces non critiques.
+## Règles
+
+- Une surface = un `owner_agent` top-level canonique.
+- Les sous-agents sont de la metadata de lane et ne remplacent jamais l'owner top-level.
+- Les write sets restent disjoints par lot.
+- Les surfaces bridge et evidence doivent publier `owner_repo`, `owner_agent`, `write_set`, `status`, `evidence`.
```mermaid
flowchart LR
- PM[PM-Mesh] --> KL[Kill_LIFE specs + workflows]
- PM --> DOC[Docs-Research]
- ARCH[Arch-Mesh] --> MCP[Runtime + contracts]
- ARCH --> CAD[Embedded-CAD]
- WEB[Web-Cockpit] --> UI[crazy_life UI + gateway]
- RUN[Runtime-Companion] --> MAS[mascarade core + bridge]
- OPS[SyncOps] --> TUI[TUI + logs + SSH + dirty-set]
- QA[QA-Compliance] --> CI[CI + schemas + release gates]
- DOC --> KL
- MAS --> UI
- UI --> KL
- OPS --> MAS
- OPS --> UI
- QA --> MAS
- QA --> UI
+ PM["PM-Mesh"] --> KL["specs + backlog"]
+ ARCH["Arch-Mesh"] --> CONTRACTS["ADRs + contrats"]
+ DOCS["Docs-Research"] --> ENTRY["README + index + plans"]
+ RUNTIME["Runtime-Companion"] --> MCP["runtime + MCP + providers"]
+ QA["QA-Compliance"] --> GATES["tests + CI + evidence"]
+ CAD["Embedded-CAD"] --> FAB["KiCad + FreeCAD + fabrication"]
+ WEB["Web-CAD-Platform"] --> EDA["Next.js + GraphQL + workers + Yjs"]
+ UX["UX-Lead"] --> UI["UX native + design research"]
+ FW["Firmware"] --> TARGET["PlatformIO + tests"]
+ OPS["SyncOps"] --> COCKPIT["cockpit + incidents + logs"]
+ SCHEMA["Schema-Guard"] --> JSON["schemas + validateurs"]
+ BRIDGE["KillLife-Bridge"] --> FLOW["workflows + evidence bridge"]
```
-## Kill_LIFE - specifications
+## Specifications Kill_LIFE
-| Specification | Owner agent | Sub-agent | Competences / skills | Write-set principal |
-| --- | --- | --- | --- | --- |
-| `specs/00_intake.md` | `PM-Mesh` | `Intake-Guard` | cadrage, triage, backlog | `specs/00_intake.md` |
-| `specs/01_spec.md` | `Arch-Mesh` | `Requirements-Lead` | exigences, AC, frontieres | `specs/01_spec.md` |
-| `specs/02_arch.md` | `Arch-Mesh` | `Contract-Guard` | architecture, ADR, compat | `specs/02_arch.md` |
-| `specs/03_plan.md` | `PM-Mesh` | `Plan-Orchestrator` | sequencing, dependances, lots | `specs/03_plan.md` |
-| `specs/04_tasks.md` | `PM-Mesh` | `Todo-Tracker` | execution, preuve, priorisation | `specs/04_tasks.md` |
-| `specs/constraints.yaml` | `QA-Compliance` | `Constraint-Gate` | gates, invariants, drift control | `specs/constraints.yaml` |
-| `specs/README.md` | `Docs-Research` | `Spec-Index` | navigation, source of truth | `specs/README.md` |
-| `specs/knowledge_base_mcp_spec.md` | `Runtime-Companion` | `KB-MCP` | MCP, degraded-safe runtime | `specs/knowledge_base_mcp_spec.md` |
-| `specs/github_mcp_conversion_spec.md` | `Runtime-Companion` | `Dispatch-MCP` | GitHub dispatch, runtime bridge | `specs/github_mcp_conversion_spec.md` |
-| `specs/ci_cd_spec.md` | `QA-Compliance` | `CI-Gate` | CI, release gates, evidence packs | `specs/ci_cd_spec.md` |
-| `specs/agentic_intelligence_integration_spec.md` | `PM-Mesh` | `Plan-Orchestrator` | gouvernance intelligence, contrats courts, lanes TUI | `specs/agentic_intelligence_integration_spec.md` |
-| `specs/mcp_agentics_target_backlog.md` | `Runtime-Companion` | `MCP-Orchestrator` | backlog MCP, orchestration agentique | `specs/mcp_agentics_target_backlog.md` |
-| `specs/mcp_tasks.md` | `Runtime-Companion` | `MCP-Orchestrator` | plan d'execution MCP, checklist | `specs/mcp_tasks.md` |
-| `specs/kicad_mcp_scope_spec.md` | `Embedded-CAD` | `CAD-Bridge` | KiCad MCP, host-first CAD | `specs/kicad_mcp_scope_spec.md` |
-| `specs/zeroclaw_dual_hw_orchestration_spec.md` | `Embedded-CAD` | `ZeroClaw-Orchestrator` | hardware orchestration, smokes | `specs/zeroclaw_dual_hw_orchestration_spec.md` |
-| `specs/zeroclaw_dual_hw_todo.md` | `Embedded-CAD` | `ZeroClaw-Orchestrator` | firmware backlog, execution lot | `specs/zeroclaw_dual_hw_todo.md` |
-| `specs/yiacad_uiux_apple_native_spec.md` | `UX-Lead` | `Apple-HIG` | Apple-native UX, CAD shell | `specs/yiacad_uiux_apple_native_spec.md` |
-| `specs/yiacad_git_eda_platform_spec.md` | `Web-CAD-Platform` | `Project-Service` | Git-first EDA product, GraphQL, workers, review, multi-tenant path | `specs/yiacad_git_eda_platform_spec.md` |
-| `specs/cad_modeling_tasks.md` | `Embedded-CAD` | `CAD-Smoke` | CAD tasking, modeling workflow | `specs/cad_modeling_tasks.md` |
-| `specs/mesh_contracts.md` | `Arch-Mesh` | `Mesh-Contracts` | tri-repo contract, handoffs | `specs/mesh_contracts.md` |
-| `specs/contracts/agent_handoff.schema.json` | `Schema-Guard` | `Handoff-Schema` | schema versioning, strict contract | `specs/contracts/agent_handoff.schema.json` |
-| `specs/contracts/repo_snapshot.schema.json` | `Schema-Guard` | `Snapshot-Schema` | repo-state, snapshot contract | `specs/contracts/repo_snapshot.schema.json` |
-| `specs/contracts/workflow_handshake.schema.json` | `Schema-Guard` | `Workflow-Schema` | workflow handshake, compat UI/API | `specs/contracts/workflow_handshake.schema.json` |
-| `specs/contracts/operator_lane_evidence.schema.json` | `Schema-Guard` | `Evidence-Schema` | E2E evidence, operator lane | `specs/contracts/operator_lane_evidence.schema.json` |
+| Surface | Owner agent | Metadata de lane | Write set principal |
+| --- | --- | --- | --- |
+| `specs/00_intake.md` | `PM-Mesh` | `Intake-Guard` | `specs/00_intake.md` |
+| `specs/01_spec.md` | `Arch-Mesh` | `Requirements-Lead` | `specs/01_spec.md` |
+| `specs/02_arch.md` | `Arch-Mesh` | `Contract-Guard` | `specs/02_arch.md` |
+| `specs/03_plan.md` | `PM-Mesh` | `Plan-Orchestrator` | `specs/03_plan.md` |
+| `specs/04_tasks.md` | `PM-Mesh` | `Todo-Tracker` | `specs/04_tasks.md` |
+| `specs/README.md` | `Docs-Research` | `Doc-Entry` | `specs/README.md` |
+| `specs/constraints.yaml` | `QA-Compliance` | `Constraint-Gate` | `specs/constraints.yaml` |
+| `specs/agentic_intelligence_integration_spec.md` | `PM-Mesh` | `Plan-Orchestrator` | `specs/agentic_intelligence_integration_spec.md` |
+| `specs/mcp_agentics_target_backlog.md` | `Runtime-Companion` | `MCP-Health` | `specs/mcp_agentics_target_backlog.md` |
+| `specs/kicad_mcp_scope_spec.md` | `Embedded-CAD` | `CAD-Bridge` | `specs/kicad_mcp_scope_spec.md` |
+| `specs/yiacad_git_eda_platform_spec.md` | `Web-CAD-Platform` | `Project-Service` | `specs/yiacad_git_eda_platform_spec.md` |
+| `specs/yiacad_uiux_apple_native_spec.md` | `UX-Lead` | `Apple-HIG` | `specs/yiacad_uiux_apple_native_spec.md` |
+| `specs/zeroclaw_dual_hw_orchestration_spec.md` | `Embedded-CAD` | `CAD-Fusion` | `specs/zeroclaw_dual_hw_orchestration_spec.md` |
+| `specs/zeroclaw_dual_hw_todo.md` | `Firmware` | `FW-Build` | `specs/zeroclaw_dual_hw_todo.md` |
+| `specs/contracts/agent_catalog.schema.json` | `Schema-Guard` | `Catalog-Schema` | `specs/contracts/agent_catalog.schema.json` |
+| `specs/contracts/kill_life_agent_catalog.json` | `Schema-Guard` | `Catalog-Registry` | `specs/contracts/kill_life_agent_catalog.json` |
+| `specs/contracts/agent_handoff.schema.json` | `Schema-Guard` | `Handoff-Schema` | `specs/contracts/agent_handoff.schema.json` |
+| `specs/contracts/runtime_mcp_ia_gateway.schema.json` | `Schema-Guard` | `Gateway-Schema` | `specs/contracts/runtime_mcp_ia_gateway.schema.json` |
+| `specs/contracts/operator_lane_evidence.schema.json` | `Schema-Guard` | `Evidence-Schema` | `specs/contracts/operator_lane_evidence.schema.json` |
-## Kill_LIFE - modules
+## Modules Kill_LIFE
-| Module | Owner agent | Sub-agent | Competences / skills | Write-set principal |
-| --- | --- | --- | --- | --- |
-| `tools/cockpit/*` | `SyncOps` | `TUI-Ops` | `bash-cli-tui`, SSH, logs, runbooks | `tools/cockpit/*` |
-| `tools/cad/*` | `Embedded-CAD` | `CAD-UX` | host-first CAD, native hooks, review/sync ops | `tools/cad/*` |
-| `tools/ai/*` | `Runtime-Companion` | `Agent-Orchestrator` | runtime AI, orchestration, provider bridges | `tools/ai/*` |
-| `tools/hw/*` | `Embedded-CAD` | `CAD-Bridge` | KiCad, FreeCAD, MCP, exports, smoke | `tools/hw/*` |
-| `tools/ops/*` | `Runtime-Companion` | `Provider-Bridge` | live provider bridge, smoke E2E | `tools/ops/*` |
-| `tools/repo_state/*` | `SyncOps` | `Repo-State` | repo refresh, machine memory | `tools/repo_state/*` |
-| `tools/specs/*` | `Schema-Guard` | `Spec-Mirror` | schema checks, mirror sync | `tools/specs/*` |
-| `web/app/*` | `Web-CAD-Platform` | `Product-Web` | Next.js routes, dashboard, review, product shell | `web/app/*` |
-| `web/components/*` | `Web-CAD-Platform` | `Review-Assist` | Excalidraw, KiCanvas, PR review, diff UX | `web/components/*` |
-| `web/lib/graphql/*`, `web/lib/project-store.ts`, `web/lib/types.ts` | `Web-CAD-Platform` | `Project-Service` | GraphQL, Git read model, project metadata, contracts | `web/lib/graphql/*`, `web/lib/project-store.ts`, `web/lib/types.ts` |
-| `web/lib/eda-queue.ts`, `web/lib/ci-enqueue.ts`, `web/workers/*` | `Web-CAD-Platform` | `EDA-CI-Orchestrator` | BullMQ, Redis, KiCad/KiBot/KiAuto worker orchestration | `web/lib/eda-queue.ts`, `web/lib/ci-enqueue.ts`, `web/workers/*` |
-| `web/realtime/*` | `Web-CAD-Platform` | `Realtime-Collab` | Yjs, CRDT, websocket, presence, persistence | `web/realtime/*` |
-| `web/project/*`, `web/public/vendor/*` | `Web-CAD-Platform` | `Artifacts-Bridge` | demo fixtures, artifacts, vendored viewers, sample state | `web/project/*`, `web/public/vendor/*` |
-| `hardware/*` | `Embedded-CAD` | `HW-BOM` | hardware assets, BOM, DRC, previews | `hardware/*` |
-| `firmware/*` | `Firmware` | `FW-Build` | PlatformIO, firmware loop, evidence | `firmware/*` |
-| `compliance/*` | `QA-Compliance` | `Constraint-Gate` | standards, evidence, quality gates | `compliance/*` |
-| `openclaw/*` | `Runtime-Companion` | `Sandbox-Guard` | safe automation, labels, scope guard | `openclaw/*` |
-| `agents/*` + `.github/agents/*` | `Docs-Research` | `Agent-Catalog` | role docs, ownership, onboarding | `agents/*`, `.github/agents/*` |
-| `.github/prompts/*` | `PM-Mesh` | `Prompt-Registry` | orchestration prompts, handoff prompts | `.github/prompts/*` |
-| `workflows/*` | `KillLife-Bridge` | `Workflow-Editor` | workflow contracts, operator lane | `workflows/*` |
-| `.github/workflows/*` | `QA-Compliance` | `Release-Gates` | CI, static checks, contract gates | `.github/workflows/*` |
-| `docs/plans/*` | `Docs-Research` | `Plan-Recorder` | plan memory, ownership, priorities | `docs/plans/*` |
-| `docs/*manifest*`, `docs/*LANDSCAPE*`, `docs/*FEATURE*` | `Docs-Research` | `Mermaid-Map` | narrative docs, feature maps | `docs/REFACTOR_MANIFEST_2026-03-20.md`, `docs/AGENTIC_LANDSCAPE.md`, `docs/KILL_LIFE_FEATURE_MAP_2026-03-11.md` |
-| `docs/index.md`, `docs/QUICKSTART.md`, `docs/RUNBOOK.md` | `Docs-Research` | `Doc-Entry` | entry docs, operator navigation, discoverability | `docs/index.md`, `docs/QUICKSTART.md`, `docs/RUNBOOK.md` |
-| `docs/CAD_AI_NATIVE_*`, `docs/YIACAD_*` | `UX-Lead` | `UI-Research` | Apple-native UX, runbooks, insertion points | `docs/CAD_AI_NATIVE_GUI_RUNBOOK_2026-03-20.md`, `docs/CAD_AI_NATIVE_HOOKS_2026-03-20.md`, `docs/YIACAD_*` |
-| `.runtime-home/cad-ai-native-forks/kicad-ki/*` | `Embedded-CAD` | `KiCad-Native` | wx, AUI, tool actions, shell natif | `.runtime-home/cad-ai-native-forks/kicad-ki/*` |
-| `.runtime-home/cad-ai-native-forks/freecad-ki/*` | `Embedded-CAD` | `FreeCAD-Native` | Qt, workbench, dock windows, shell natif | `.runtime-home/cad-ai-native-forks/freecad-ki/*` |
+| Surface | Owner agent | Metadata de lane | Write set principal |
+| --- | --- | --- | --- |
+| `kill_life/server.py`, `kill_life/agent_catalog.py` | `Arch-Mesh` | `Contract-Guard` | `kill_life/` |
+| `agents/*`, `.github/agents/*` | `Docs-Research` | `Agent-Catalog` | `agents/*`, `.github/agents/*` |
+| `.github/prompts/*` | `PM-Mesh` | `Prompt-Registry` | `.github/prompts/*` |
+| `.github/workflows/*` | `QA-Compliance` | `Release-Gates` | `.github/workflows/*` |
+| `tools/specs/*` | `Schema-Guard` | `Catalog-Validator` | `tools/specs/*` |
+| `tools/cockpit/*` | `SyncOps` | `TUI-Ops` | `tools/cockpit/*` |
+| `tools/ai/*`, `tools/ops/*` | `Runtime-Companion` | `Provider-Bridge` | `tools/ai/*`, `tools/ops/*` |
+| `tools/cad/*`, `tools/hw/*`, `hardware/*` | `Embedded-CAD` | `CAD-Bridge` | `tools/cad/*`, `tools/hw/*`, `hardware/*` |
+| `web/app/*`, `web/components/*`, `web/lib/*`, `web/realtime/*`, `web/workers/*` | `Web-CAD-Platform` | `Project-Service`, `Realtime-Collab`, `EDA-CI-Orchestrator`, `Review-Assist`, `Artifacts-Bridge` | `web/*` |
+| `docs/YIACAD_*`, `docs/CAD_AI_NATIVE_*` | `UX-Lead` | `UI-Research` | `docs/YIACAD_*`, `docs/CAD_AI_NATIVE_*` |
+| `firmware/*` | `Firmware` | `FW-Build` | `firmware/*` |
+| `README.md`, `README_FR.md`, `docs/index.md`, `docs/plans/*` | `Docs-Research` | `Doc-Entry`, `Plan-Recorder` | `README.md`, `README_FR.md`, `docs/index.md`, `docs/plans/*` |
+| `workflows/*`, `specs/contracts/ops_*`, `specs/contracts/artifact_*` | `KillLife-Bridge` | `Workflow-Editor`, `Evidence-Runner` | `workflows/*`, `specs/contracts/ops_*`, `specs/contracts/artifact_*` |
-## `mascarade-main` - workstreams
+## Surfaces tri-repo
-| Workstream | Owner agent | Sub-agent | Competences / skills | Write-set principal |
-| --- | --- | --- | --- | --- |
-| `WS0 Gouvernance / sync` | `SyncOps` | `Docs-Research` | publication, merge preflight, hub | `README.md`, `plan.md`, `docs/EXECUTION_HUB.md`, `scripts/merge_preflight.sh` |
-| `WS1 Core routing/providers` | `Runtime-Companion` | `Provider-Bridge` | providers, routing, metrics | `core/mascarade/router/**`, `load_balancer/**`, `provider_admin.py` |
-| `WS2 Core agents/orchestrator` | `Runtime-Companion` | `Agent-Orchestrator` | orchestration, conversation, dispatch | `core/mascarade/agents/**`, `orchestrator/**`, `dispatch/**` |
-| `WS3 Cluster / P2P` | `SyncOps` | `Mesh-Cluster` | P2P, topology, host review | `core/mascarade/p2p/**`, `core/scripts/p2p/**`, `cluster.py` |
-| `WS4 Node engine` | `Arch-Mesh` | `Graph-Engine` | graph workflow, type mirror | `core/mascarade/node_engine/**`, `api/src/types/node-engine.ts`, `api/src/routes/nodes.ts` |
-| `WS5 API gateway` | `Runtime-Companion` | `Gateway-Contracts` | API auth, routes, proxy core | `api/src/**` hors `killlife.ts` et `node-engine.ts` |
-| `WS6 Kill_LIFE bridge` | `KillLife-Bridge` | `Evidence-Runner` | cross-repo workflow/evidence bridge | `api/src/lib/killlife.ts`, `api/src/routes/killlife.ts`, `docker-compose.yml` |
-| `WS7 Web cockpit bridge` | `Web-Cockpit` | `Ops-Views` | bridge web, snapshots, operator views | `web/src/**` hors `KillLife*`, `api/public/**` |
-| `WS8 Deploy / ops stack` | `SyncOps` | `Compose-Generator` | compose, services, deploy, observability | `docker-compose.yml`, `deploy/**`, `scripts/lib.sh`, `scripts/services.sh`, `scripts/compose.sh`, `scripts/modules/**` |
-| `WS9 Finetune / training` | `Runtime-Companion` | `Finetune-Ops` | datasets, finetune, batch runner | `finetune/**`, `training/**`, `core/scripts/finetune/**` |
+| Surface | Owner agent | Metadata de lane | Intention |
+| --- | --- | --- | --- |
+| `kill-life-studio` product/spec surfaces | `PM-Mesh` | `Plan-Orchestrator` | arbitrage produit, lot sequencing, dépendances |
+| `kill-life-mesh` contract and ownership surfaces | `Arch-Mesh` | `Mesh-Contracts` | contrats tri-repo, ownership, propagation |
+| `kill-life-operator` runbooks and execution evidence | `SyncOps` | `Doc-Runbook`, `Log-Ops` | opérations, incidents, evidence packs |
+| `mascarade-main` runtime bridge | `Runtime-Companion` | `Provider-Bridge`, `MCP-Health` | runtime live, provider routing, degraded-safe bridge |
+| `crazy_life-main` workflow/evidence bridge | `KillLife-Bridge` | `Workflow-Editor`, `Schema-Consumer` | bridge API/workflow/evidence |
-## `crazy_life-main` - workstreams
+## Delta 2026-03-29
-| Workstream | Owner agent | Sub-agent | Competences / skills | Write-set principal |
-| --- | --- | --- | --- | --- |
-| `WS-01 Shell-nav` | `Web-Cockpit` | `UI-Flow` | shell UI, routing, layout | `src/main.tsx`, `src/App.tsx`, `src/components/layout/**` |
-| `WS-02 Auth-theme-config` | `Web-Cockpit` | `UI-Theme` | auth, providers settings, design system | `src/index.css`, `src/components/ui/**`, `src/auth/**`, `src/pages/Settings.tsx` |
-| `WS-03 Core AI/operator` | `Web-Cockpit` | `UI-Flow` | dashboard, agents, orchestrate | `src/pages/Dashboard.tsx`, `src/pages/Agents.tsx`, `src/pages/Orchestrate.tsx`, `src/api/agents.ts` |
-| `WS-04 Ops/observability` | `Web-Cockpit` | `Log-Ops` | logs, metrics, infrastructure, ops hub | `src/pages/OpsHub.tsx`, `src/pages/Logs.tsx`, `src/pages/Metrics.tsx`, `src/pages/Infrastructure.tsx`, `src/api/ops.ts` |
-| `WS-05 Integrations externes` | `Web-Cockpit` | `Schema-Consumer` | knowledge base, ComfyUI, external bridges | `src/pages/KnowledgeBrowser.tsx`, `src/pages/ComfyUI.tsx`, `src/api/knowledgeBase.ts`, `src/api/comfyui.ts` |
-| `WS-06 Crazy Lane UI` | `Web-Cockpit` | `Schema-Consumer` | Crazy Lane, workflow editor UI | `src/pages/CrazyLane.tsx`, `src/pages/CrazyLaneEditor.tsx`, `src/api/killlife.ts` |
-| `WS-07 Gateway core` | `Web-Cockpit` | `Gateway-Core` | Hono gateway, auth, proxy allowlist | `api/src/index.ts`, `api/src/index.test.ts` |
-| `WS-08 Kill_LIFE backend lane` | `KillLife-Bridge` | `Schema-Consumer` | backend lane, operator bridge, evidence | `api/src/routes/killlife.ts`, `api/src/lib/killlife.ts`, `api/src/routes/killlife.integration.test.ts`, `api/src/lib/killlife.test.ts` |
-| `WS-09 Build/release/deploy` | `SyncOps` | `Release-Gates` | package/build/deploy/release | `package.json`, `api/package.json`, `Dockerfile`, `docker-compose.local.yml`, `deploy/**`, `scripts/**`, `.github/workflows/**` |
-| `WS-10 Docs/contracts` | `Docs-Research` | `Mermaid-Map` | operator docs, publish flow, lot split | `README.md`, `plan.md`, `docs/**` |
-| `WS-11 Legacy quarantine` | `Web-Cockpit` | `Quarantine-Guard` | legacy freeze, drift prevention | `src/pages/KillLifeWorkflows.tsx`, `src/pages/KillLifeWorkflowEditor.tsx` |
-
-## Hotspots `single-writer`
-
-- `Kill_LIFE`: `tools/cockpit/mesh_sync_preflight.sh`, `tools/cockpit/full_operator_lane.sh`, `workflows/embedded-operator-live.json`.
-- `mascarade-main`: `api/src/lib/killlife.ts`, `api/src/routes/killlife.ts`, `docker-compose.yml`, `scripts/mesh_runtime_preflight.sh`.
-- `crazy_life-main`: `api/src/index.ts`, `api/src/lib/killlife.ts`, `api/src/routes/killlife.ts`, `src/pages/CrazyLaneEditor.tsx`.
-
-## Prochains lots recommandes
-
-1. `T-AG-004` - brancher cette matrice dans les runbooks et TODOs companions sans casser les single-writer hotspots.
-2. `T-UX-003` - exposer la matrice et les points d’insertion natifs dans les TUI operatoires.
-3. `T-RE-204` - reprendre `zeroclaw-integrations` une fois la matrice agentique publiee.
-4. `T-RE-209` - finaliser `yiacad-fusion` avec ownership strict `Arch-Mesh` / `CAD-Bridge`.
-5. `T-OL-005` - requalifier le `model` explicite apres stabilisation complete du chemin par defaut.
-
-## Delta 2026-03-20 - mapping spec/module T-UX-004
-- `specs/yiacad_tux004_orchestration_spec.md` -> owner `DesignOps-UI` -> sous-agents `Sagan/Godel`, `Peirce/Locke`, `AgentMatrix lane`.
-- module `kicad-ki/*` -> owner `Sagan/Godel lane` -> focus `command palette`, `review center`, `output contract hookup`.
-- module `freecad-ki/src/Mod/YiACADWorkbench/*` -> owner `Peirce/Locke lane` -> focus `persistent inspector`, `review center`, `command palette`.
-- module `tools/cad/yiacad_native_ops.py` -> owner `CAD IA runtime lane` -> focus `normalized outputs`, `context broker`, `artifact lineage`.
-
-## Delta 2026-03-20 18:35 - canonical mapping T-UX-003 / T-UX-004
-
-- `T-UX-003A` -> owner `CAD-UX / KiCad-Shell` -> write-set `kicad-ki/pcbnew/toolbars_pcb_editor.cpp`, `kicad-ki/eeschema/toolbars_sch_editor.cpp`
-- `T-UX-003B` -> owner `CAD-UX / FreeCAD-Shell` -> write-set `freecad-ki/src/Mod/YiACADWorkbench/yiacad_freecad_gui.py`
-- `T-UX-003C` -> owner `CAD-UX / KiCad-Native` -> write-set `kicad-ki/pcbnew/tools/board_editor_control.*`, `kicad-ki/eeschema/tools/sch_editor_control.*`
-- `T-UX-003D` -> owner `CAD-UX / FreeCAD-Native` -> write-set `freecad-ki/src/Gui/MainWindow.cpp`
-- `T-UX-004A` -> owner `CAD-UX / KiCad-Surface + FreeCAD-Surface` -> write-set `kicad-ki/scripting/plugins/yiacad_kicad_plugin/yiacad_action.py`, `freecad-ki/src/Mod/YiACADWorkbench/yiacad_freecad_gui.py`
-- `T-UX-004B` -> owner `Doc-Research / Mermaid-Map + OSS-Watch` -> write-set `docs/YIACAD_*`, `docs/plans/20_*`, `specs/04_tasks.md`
-- `Support UI/UX Ops` -> owner `SyncOps / TUI-Ops + Log-Guard` -> write-set `tools/cockpit/yiacad_uiux_tui.sh`, `artifacts/uiux_tui/*`
-
-## Delta 2026-03-22 - canonical mapping intelligence web / plan 23
-
-- `T-AI-320` -> owner `PM-Mesh / Plan-Orchestrator` -> write-set `specs/agentic_intelligence_integration_spec.md`, `docs/plans/22_*`, `tools/cockpit/intelligence_tui.sh`
-- `T-AI-321` -> owner `Docs-Research / Runbook-Editor + Mermaid-Map + OSS-Watch` -> write-set `docs/KILL_LIFE_CONSOLIDATION_AUDIT_2026-03-22.md`, `docs/WEB_RESEARCH_OPEN_SOURCE_2026-03-22.md`, `docs/AGENTIC_INTELLIGENCE_FEATURE_MAP_2026-03-22.md`
-- `T-AI-322` -> owner `Docs-Research / Plan-Recorder` -> write-set `docs/plans/12_plan_gestion_des_agents.md`, `docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md`
-- `T-AI-323` -> owner `Web-CAD-Platform / EDA-CI-Orchestrator + Realtime-Collab` -> write-set `web/lib/*`, `web/realtime/*`, `web/workers/*`, `tools/cockpit/intelligence_tui.sh`
-- `T-AI-324` -> owner `Web-CAD-Platform / Project-Service` -> write-set `web/lib/project-store.ts`, `web/lib/graphql/*`, `web/app/api/*`
-- `T-AI-325` -> owner `Web-CAD-Platform / Realtime-Collab` -> write-set `web/components/excalidraw-canvas.tsx`, `web/lib/realtime/*`, `web/components/project-shell.tsx`
-- `T-AI-326` -> owner `Runtime-Companion / Review-Assist` -> write-set `web/lib/intelligence/*`, `docs/AI_WORKFLOWS.md`, `specs/mcp_agentics_target_backlog.md`
+- Les anciens owners prose comme `Web-Cockpit`, `DesignOps-UI`, `Studio-Product`, `Mesh-Contracts` et `Operator-Lane` ne servent plus de couche top-level dans cette matrice.
+- Le runtime public et les producteurs de gouvernance doivent exposer uniquement les 12 IDs canoniques.
+- Les détails de lane restent possibles via `owner_subagent` ou métadonnées équivalentes, sans casser la couche top-level canonique.
diff --git a/docs/AI_WORKFLOWS.md b/docs/AI_WORKFLOWS.md
index 2904c9a..ff0bd9d 100644
--- a/docs/AI_WORKFLOWS.md
+++ b/docs/AI_WORKFLOWS.md
@@ -278,3 +278,20 @@ flowchart TD
- `bash tools/cockpit/refonte_tui.sh --action status`
- `bash tools/cockpit/refonte_tui.sh --action mesh-preflight`
- `bash tools/cockpit/refonte_tui.sh --action mcp-check`
+
+## Flux dedie PR review automation (2026-03-29)
+
+```mermaid
+flowchart TD
+ A[PR ouverte sur yiacad] --> B[Web-CAD-Platform: PR-Review-Orchestrator]
+ B --> C[Gate S2: Playwright + BullMQ check]
+ C --> D{Gate S2 pass?}
+ D -->|KO| E[ai:hold + correction lot]
+ D -->|OK| F[Evidence: web/project/.ci/]
+ F --> G[summary_short.schema.json valide]
+ G --> H[artifacts/cockpit/intelligence_program/]
+ H --> I[QA-Compliance: validate_specs.py --strict]
+ I --> J[Schema-Guard: yiacad_uiux_output.schema.json]
+ J --> K[Handoff: operator_lane_evidence]
+ K --> L[Close lot + memoire]
+```
diff --git a/docs/GLOBAL_MULTI_AGENT_CHECKLIST.md b/docs/GLOBAL_MULTI_AGENT_CHECKLIST.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/plans/12_plan_gestion_des_agents.md b/docs/plans/12_plan_gestion_des_agents.md
index eae5fff..0c797d1 100644
--- a/docs/plans/12_plan_gestion_des_agents.md
+++ b/docs/plans/12_plan_gestion_des_agents.md
@@ -1,684 +1,93 @@
# 12) Plan de gestion des agents
-Last updated: 2026-03-22
+Last updated: 2026-03-29
-Ce document pilote la gouvernance agentique et les 3 plans opérationnels: **PM**, **Architect-Firmware** et **SyncOps**.
+Ce plan acte le hard switch vers le catalogue 2026. La source de vérité des agents `Kill_LIFE` est désormais `specs/contracts/kill_life_agent_catalog.json`. Les sous-agents restent documentés comme metadata de lane, mais seuls les agents top-level ci-dessous sont canoniques et publiquement exposables.
-Matrice explicite specs/modules -> agents dédiés: `docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md`
+## Source de vérité
-## Consolidation 2026-03-21 - extensions d'abord
+- Contrat canonique: `specs/contracts/kill_life_agent_catalog.json`
+- Schéma du contrat: `specs/contracts/agent_catalog.schema.json`
+- Docs humaines: `agents/*`, `.github/agents/*`
+- Prompts catalogués: `.github/prompts/start_.prompt.md`, `.github/prompts/plan_wizard_.prompt.md`
+- Validation: `python3 tools/specs/validate_agent_catalog.py --json`
-La passe active retient `kill-life-studio` comme produit pilote. `kill-life-mesh` et `kill-life-operator` n'ouvrent pas de spécialisation profonde tant que le socle `projet actif + grounding + tests` n'est pas validé dans Studio.
+## Catalogue top-level 2026
-| Lead | Sous-agents | Compétences | Backlog initial |
+| Agent canonique | Mission | Sous-agents metadata | Write set principal |
| --- | --- | --- | --- |
-| `PM-Mesh` | `Plan-Orchestrator`, `Risk-Triager`, `Release-Gate` | arbitrage, backlog, release gate | baseline sale, P0/P1, critères de "prêt avant public" |
-| `Docs-Research` | `Doc-Entry`, `Mermaid-Map`, `Feature-Map`, `Runbook-Editor` | consolidation doc, benchmark, cartes | `README`, `docs/index`, `tools/cockpit/README`, `specs/README`, audit unique |
-| `Studio-Product` | `UX-Lead`, `Context-Builder`, `Artifact-Model`, `Extension-Test` | UX auteur, contexte chat, artefacts, tests | projet actif, workflow `brief -> spec -> decisions -> AC -> plan`, tests unitaires et extension-host |
-| `Mesh-Contracts` | `Handoff-Guard`, `Contract-View`, `Dependency-Mapper` | contrats, handoffs, ownership | reprendre le socle Studio validé puis spécialiser les vues multi-repo |
-| `Operator-Lane` | `Runbook-Guard`, `Evidence-Runner`, `Log-Ops` | runbooks, preuves, exécution | reprendre le socle Studio validé puis spécialiser checks et evidence |
-| `Runtime-Companion` | `MCP-Health`, `Prompt-Eval`, `Bridge-Queue` | santé runtime/MCP, dégradation maîtrisée, prompt eval | gateway canonique `ready/degraded/blocked` via `runtime_ai_gateway.sh` |
-| `QA-Compliance` | `Contract-Tests`, `Shell-Harness`, `Smoke-E2E` | tests shell/TUI, contrats JSON, smoke VSIX | stable suite Python, tests cockpit ciblés, `runtime_ai_gateway.sh`, smoke multi-root |
-| `OSS-Watch` | `VSCode-Agent-Benchmark`, `Agent-Framework-Benchmark`, `MCP-Benchmark` | veille web et décisions d'adoption | benchmark unique Roo Code / OpenHands / LangGraph / MCP |
-
-## Delta 2026-03-21 - lane intelligence program
-
-- Le lot dedie `22` ouvre une surface cockpit propre:
- - `tools/cockpit/intelligence_tui.sh`
- - `docs/plans/22_plan_integration_intelligence_agentique.md`
- - `docs/plans/22_todo_integration_intelligence_agentique.md`
-- La passerelle courte au-dessus de cette lane est:
- - `tools/cockpit/runtime_ai_gateway.sh`
-- Cette surface publie aussi une memoire exploitable:
- - `artifacts/cockpit/intelligence_program/latest.json`
- - `artifacts/cockpit/intelligence_program/latest.md`
-- Owner principal de la surface:
- - `QA-Compliance / Shell-Harness / Contract-Tests` pour la TUI, les logs et le contrat JSON
-- Owner principal de la continuite documentaire:
- - `Docs-Research / Doc-Entry / Mermaid-Map` pour la spec, la feature map et la veille 2026
-- Routine recommandee pour `PM-Mesh`, `Docs-Research` et `QA-Compliance`:
- - `bash tools/cockpit/lot_chain.sh status`
- - `bash tools/cockpit/intelligence_tui.sh --action status --json`
- - `bash tools/cockpit/intelligence_tui.sh --action next-actions`
- - `bash tools/cockpit/intelligence_tui.sh --action memory --json`
- - `bash tools/cockpit/runtime_ai_gateway.sh --action status --refresh --json`
-
-## Delta 2026-03-22 - lane web Git EDA + intelligence
-
-- Le lot intelligence couvre maintenant explicitement le backlog `web/` et le `plan 23`.
-- Le binome d'audit lecture seule utilise dans cette passe:
- - `Hilbert` -> audit contrats/spec/plan/TUI intelligence
- - `Huygens` -> audit `web/` Git EDA, GraphQL, realtime, queue et workers
-- Owners canoniques de la slice `web/`:
- - `Web-CAD-Platform` -> `Project-Service`, `Product-Web`, `Review-Assist`
- - `Realtime-Collab` -> `Yjs-Bridge`, `Presence-Transport`
- - `EDA-CI-Orchestrator` -> `Worker-Runner`, `Artifacts-Bridge`
-- Skills a privilegier sur cette lane:
- - `bash-cli-tui` pour les TUIs et contrats shell
- - `playwright` pour les checks UI reels quand requis
- - `openai-docs` pour les integrations officielles OpenAI quand la lane review-assist s'ouvre
-- Routine recommandee pour cette lane:
- - `bash tools/cockpit/intelligence_tui.sh --action status --json`
- - `bash tools/cockpit/intelligence_tui.sh --action next-actions`
- - `bash tools/cockpit/yiacad_operator_index.sh --action status`
- - `bash tools/cockpit/runtime_ai_gateway.sh --action status --refresh --json`
-
-## Carte opérationnelle des 3 plans
-
-| Plan | Lead | Sous-agents actifs | Compétences | Mission de lot | Prochaine action |
-| --- | --- | --- | --- | --- | --- |
-| PM | `PM-Mesh` | `PM-Plan`, `PM-Choix`, `PM-Doc` | Orchestration lot, preuve JSON, arbitrage P0-P2 | piloter `run_autonomous_next_lots` | Lancer `yiacad-fusion` dès stabilisation du prérequis runtime |
-| Architect-Firmware | `Arch-Mesh` | `Runtime-Companion`, `Schema-Guard`, `Embedded-CAD`, `Firmware` | robustesse ready/degraded/blocked, host-first CAD, intégrations IA-native | sécuriser lanes MCP/CAD + préflight mesh | finaliser `yiacad-fusion` (prepare→smoke→status) |
-| SyncOps | `SyncOps` | `SSH-Health`, `Log-Ops`, `Doc-Runbook` | SSH, TUI, logs, incidents | piloter load-balancing P2P et registre d’incidents | clôturer le backlog `mesh` + purge TTL quotidienne contrôlée |
-| DesignOps-UI | `UX-Lead` | `Apple-HIG`, `CAD-UX`, `UI-Research`, `TUI-Ops` | Apple HIG, UI architecture macOS, surfaces KiCad/FreeCAD, TUI opératoire | piloter la refonte UI/UX YiACAD Apple-native | passer du plugin/workbench Python au shell natif compilé et aux commandes IA contextualisées |
-
-```mermaid
-flowchart LR
- PM[Plan PM] -->|ordonne| lots[run_autonomous_next_lots.sh]
- lots -->|preuves| arch[Architect-Firmware]
- lots -->|observabilité| ops[SyncOps]
- arch -->|contracts| docs[(docs/*)]
- ops -->|incidents + logs| docs
- docs -->|feedback| lots
-```
-
-## Labels recommandés
-
-- `type:agentics`
-- `ai:plan`
-- `ai:hold`
-- `priority:p0/p1/p2`
-
-## Référentiel multi-plans
-
-| Plan | Responsable | Sous-agents | Compétences | Missions clés |
-| --- | --- | --- | --- | --- |
-| PM | `PM-Mesh`, `PM-Plan`, `PM-Choix` | coordination lot, priorité, arbitrage, preuves | `specs/04_tasks.md`, `docs/plans/18_plan_enchainement_*`, `docs/plans/19_todo_mesh_tri_repo.md` | cycle lot, dépendances, revue produit |
-| Architect-Firmware | `Arch-Mesh`, `Runtime-Companion`, `Embedded-CAD`, `Firmware` | architecture, contracts, runtime, robustesse | `docs/AGENTIC_LANDSCAPE.md`, `docs/REFACTOR_MANIFEST_2026-03-20.md`, `tools/autonomous_next_lots.py`, `tools/hw/*`, `tools/cockpit/mesh_sync_preflight.sh` | robustesse `ready/degraded/blocked`, host-first CAD |
-| SyncOps | `SyncOps`, `SSH-Health`, `Log-Ops` | SSH, observabilité, logs, purge | `tools/cockpit/ssh_healthcheck.sh`, `tools/cockpit/run_alignment_daily.sh`, `tools/cockpit/log_ops.sh`, `tools/cockpit/mesh_sync_preflight.sh`, `tools/cockpit/mesh_health_check.sh`, `docs/MACHINE_ALIGNMENT_CONTRACT_2026-03-20.md` | alignment 4 cibles + incidents réseau/runtime |
-
-## Plan 1 — PM (pilotage + lot)
-
-### Objectifs
-
-- Détecter les lots utiles (`run_autonomous_next_lots`), exécuter en cycle standard et tenir la preuve.
-- Valider la matrice **lot → owner → dépendances → rollback**.
-- Gérer les priorités P0/P1/P2 et la revue quotidienne.
-
-### I/O attendues
-
-- `bash tools/run_autonomous_next_lots.sh status`
-- `bash tools/run_autonomous_next_lots.sh run`
-- `bash tools/run_autonomous_next_lots.sh json`
-- `tools/autonomous_next_lots.py` doit produire `18_plan_enchainement_autonome_des_lots_utiles.md` et `18_todo_enchainement_autonome_des_lots_utiles.md`.
-
-### Priorités
-
-- P0: stabiliser `post-e2e-hardening`, restaurer `clems` dans le preflight, puis reprendre `zeroclaw-integrations`.
-- P1: corriger les lots `mcp-runtime` et `cad-mcp-host`.
-- P2: finaliser `python-local` après stabilité runtime.
-
-## Plan 2 — Architect / Firmware
-
-### Objectifs
-
-- Finaliser intégration runtime IA-native et lanes de robustesse.
-- Stabiliser scripts MCP/CAD en mode `host-first` avec fallback maîtrisé.
-- Assurer cohérence des contrats dans `docs/*` et `specs/*`.
-
-### I/O attendues
-
-- `bash tools/ai/zeroclaw_integrations_lot.sh verify --json`
-- `bash tools/ai/zeroclaw_integrations_up.sh --json`
-- `bash tools/ai/zeroclaw_integrations_status.sh --json`
-- `bash tools/ai/zeroclaw_integrations_import_n8n.sh --json`
-- `bash tools/hw/cad_stack.sh doctor`
-- `bash tools/hw/run_kicad_mcp.sh --doctor`
-
-### Priorités
-
-- P0: consolider le bridge `Full operator lane` en mode container-safe et degraded-safe.
-- P1: finaliser `mcp-runtime` et `cad-mcp-host`.
-- P2: verrouiller la stratégie Python repo-locale.
-
-## Plan 3 — SyncOps
-
-### Objectifs
-
-- Assurer la convergence des 4 cibles SSH.
-- Appliquer la politique de charge P2P (`tower-first` / `photon-safe`) sur les préchecks mesh pour éviter de forcer les services sur `cils`.
-- Centraliser health-check, logs (analyse + purge), préflight mesh, et registre d'incidents.
-- Alimenter la mémoire d'exécution avec preuves JSON.
-
-### I/O attendues
-
-- `bash tools/cockpit/ssh_healthcheck.sh --json`
-- `bash tools/cockpit/mascarade_runtime_health.sh --json`
-- `bash tools/cockpit/run_alignment_daily.sh --json [--skip-healthcheck]`
-- `bash tools/cockpit/mesh_sync_preflight.sh --json`
-- `bash tools/cockpit/log_ops.sh --action summary|list|purge --json`
-- `bash tools/cockpit/mesh_health_check.sh --json --load-profile [tower-first|photon-safe]`
-- `bash tools/cockpit/refonte_tui.sh --action mesh-preflight`
-
-### Priorités
-
-- P0: confirmer l'opérateur daily sur 4 cibles et restaurer la visibilité preflight de `clems`.
-- P0: maintenir `cils` hors charges essentielles (`Kill_LIFE` prioritaire, pas de service durable sur photon).
-- P1: tenir la matrice `logs` (summary/list/purge) + TTL.
-- P2: documenter les incidents réseau/runtime dans le registre.
-- P0: vérifier la convergence du load-balancing P2P `Tower -> KXKM -> CILS -> local -> root` à chaque run.
-
-## Registre d'incidents réseau/runtime
-
-- Voir: `docs/MESH_SYNC_INCIDENT_REGISTER_2026-03-20.md`
-- Format: horodatage / cible / couche / statut / preuve / ETA / propriétaire.
-
-## Rôles actifs
-
-1. `PM-Mesh` / `PM-Plan`
-2. `Arch-Mesh` / `Schema-Guard`
-3. `Embedded-CAD` / `CAD-Intégration`
-4. `Runtime-Companion` / `Runtime-Smoke`
-5. `SyncOps` / `Log-Ops`
-6. `Doc-Runbook`
-
-## Couverture explicite specs/modules
-
-- Chaque specification et chaque module operatoire majeur ont maintenant un agent pilote, un sous-agent, un `write_set` et une sortie attendue.
-- La matrice de reference est publiee dans `docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md`.
-- Les skills locales a privilegier sur les surfaces outillees restent: `bash-cli-tui` pour les scripts shell/TUI, `playwright` pour les verifications UI reelles, `openai-docs` pour les integrations officielles OpenAI quand elles deviennent necessaires.
-- Passe structurante en cours: `mesh_sync_preflight` consomme maintenant le registre machine/capacite comme source de verite runtime; prochaine extension cible `ssh_healthcheck`, `run_alignment_daily` et les derniers chemins repo candidats.
-
-## Checkpoint (résumé)
-
-- `T-MESH-001` : contrat mesh tri-repo versionné — aligné
-- `T-MESH-002` : preflight `ready/degraded/blocked` — aligné
-- `T-MESH-003` : ownership `owner_repo / owner_agent` — aligné
-- `T-MESH-005` : propagation `mascarade` — en cours
-- `T-MESH-006` : handshake `crazy_life` — en cours
-- `T-RE-001` / `T-RE-105` : plans + compétences — en exécution
-- `T-OL-001` : full operator lane valide sur `clems` — aligné
-- `T-OL-002` : propagation + restauration preflight `clems` — en cours
-- `T-OL-002` : propagation + restauration preflight `clems` — aligne
-- `T-OL-003` : note de compatibilité provider/runtime — aligné
-- `T-OL-004` : TUI de sync du patchset opérateur — aligné
-
-### Lot CAD (inclus dans Architect-Firmware)
-
-### Objectifs
-
-- Intégrer YiACAD dans le lot Architect-Firmware sans altérer la charge sur `cils`/`photon`.
-- Conserver les forks locales et distants de KiCad/FreeCAD en mode isolé `kill-life-ai-native`.
-- Produire des artefacts de santé CAD auditables et une procédure de rollback claire, avec priorité à la stabilité de la couche runtime.
-
-### I/O attendues
-
-- `bash tools/cad/yiacad_fusion_lot.sh --action prepare`
-- `bash tools/cad/yiacad_fusion_lot.sh --action smoke`
-- `bash tools/cad/yiacad_fusion_lot.sh --action status`
-- `bash tools/cad/yiacad_fusion_lot.sh --action logs`
-- `bash tools/cockpit/refonte_tui.sh --action yiacad-fusion:prepare`
-- `bash tools/cockpit/refonte_tui.sh --action yiacad-fusion:smoke`
-
-### Priorités
-
-- P0: garder `prepare` + `status` + `logs` + `clean-logs` stables, avec un blocage KiCad host explicite et traçable.
-- P1: maintenir les preuves/logs à jour dans `specs/04_tasks.md` et `docs/CAD_AI_NATIVE_FORK_STRATEGY.md`.
-- P2: sécuriser la réconciliation des forks distants et le fallback container.
-- `T-CAD-001` : lot YiACAD intégré — implémenté/documenté, lane runtime encore `blocked` côté KiCad host
-
-## Affectation active des lots ouverts
-
-| Tâche / lot | Agent pilote | Sous-agent | Compétences / skills | Write set principal |
-| --- | --- | --- | --- | --- |
-| `T-RE-204` / `zeroclaw-integrations` | `PM-Mesh` | `Runtime-Smoke` | triage lot, runtime local, `bash-cli-tui` | `specs/zeroclaw_dual_hw_todo.md`, `tools/cockpit/lot_chain.sh`, `tools/cockpit/run_next_lots_autonomously.sh` |
-| `mesh-governance` | `PM-Mesh` | `Schema-Guard` | contrats mesh, handoff, preuve CI | `docs/TRI_REPO_MESH_CONTRACT_2026-03-20.md`, `docs/plans/19_todo_mesh_tri_repo.md`, `tools/cockpit/mesh_sync_preflight.sh` |
-| `T-RE-215` / `mascarade-model-profiles` | `Runtime-Companion` | `Provider-Bridge` | routing provider/model, profils runtime, `bash-cli-tui` | `specs/contracts/mascarade_model_profiles.kxkm_ai.json`, `tools/cockpit/mascarade_models_tui.sh`, `tools/ops/operator_live_provider_smoke.py`, `tools/ops/sync_mascarade_agents_kxkm.sh` |
-| `T-RE-218` / `mascarade-ollama-live` | `Runtime-Companion` | `Provider-Bridge` | bridge Ollama local, compat runtime, profils `ollama-first`, `bash-cli-tui` | `specs/contracts/mascarade_model_profiles.kxkm_ai.json`, `docs/MASCARADE_MODEL_PROFILES_KXKM_AI_2026-03-20.md`, `tools/ops/sync_mascarade_agents_kxkm.sh` |
-| `T-RE-219` / `mascarade-agent-smoke` | `Runtime-Companion` | `Runtime-Smoke` | smoke API live, artefacts JSON, validation ciblee | `tools/ops/smoke_mascarade_agents_kxkm.sh`, `artifacts/ops/mascarade_agent_smoke/*`, `docs/MASCARADE_MODEL_PROFILES_KXKM_AI_2026-03-20.md` |
-| `T-RE-221` / `mascarade-ui-presets` | `Runtime-Companion` | `UI-Dispatch` | presets UI, registre agents, publication front | `/home/kxkm/mascarade-main/web/src/pages/Agents.tsx`, `docs/MASCARADE_MODEL_PROFILES_KXKM_AI_2026-03-20.md` |
-| `T-RE-222` / `mascarade-runtime-health` | `SyncOps` | `Runtime-Smoke` | health live, Ollama, JSON cockpit, runbook daily | `tools/cockpit/mascarade_runtime_health.sh`, `tools/cockpit/run_alignment_daily.sh`, `tools/cockpit/README.md` |
-| `T-RE-223` / `oss-chat-presets-research` | `PM` | `Research-Curator` | veille web, chats multi-agents, personas, orchestration | `docs/WEB_RESEARCH_OPEN_SOURCE_2026-03-20.md`, `specs/04_tasks.md` |
-| `T-RE-224` / `operator-runtime-health-surface` | `SyncOps` | `TUI-Ops` | TUI cockpit, lane operateur, contrat JSON, `bash-cli-tui` | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/refonte_tui.sh`, `tools/cockpit/README.md` |
-| `T-RE-225` / `mascarade-logs-tui` | `SyncOps` | `Log-Curator` | logs runtime, purge contrôlée, TUI cockpit, `bash-cli-tui` | `tools/cockpit/mascarade_logs_tui.sh`, `tools/cockpit/refonte_tui.sh`, `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/README.md` |
-| `T-RE-226` / `operator-mascarade-logs-bridge` | `SyncOps` | `Lane-Guard` | lane operateur, logs runtime, hints API, `bash-cli-tui` | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/mascarade_logs_tui.sh`, `specs/04_tasks.md` |
-| `T-RE-227` / `operator-native-logs-mode` | `SyncOps` | `Lane-Guard` | lane operateur, logs natifs, hints API, `bash-cli-tui` | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/README.md`, `specs/04_tasks.md` |
-| `T-RE-228` / `operator-logs-tui-reroute` | `SyncOps` | `TUI-Ops` | TUI cockpit, lane operateur, contrat JSON, `bash-cli-tui` | `tools/cockpit/refonte_tui.sh`, `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/README.md` |
-| `T-RE-229` / `operator-logs-shortcuts` | `SyncOps` | `TUI-Ops` | TUI cockpit, raccourcis logs, ergonomie operateur, `bash-cli-tui` | `tools/cockpit/refonte_tui.sh`, `tools/cockpit/README.md`, `specs/04_tasks.md` |
-| `T-RE-230` / `operator-post-run-log-snapshot` | `SyncOps` | `Lane-Guard` | lane operateur, snapshot latest, artefacts runtime, `bash-cli-tui` | `tools/cockpit/full_operator_lane.sh`, `specs/04_tasks.md`, `tools/cockpit/README.md` |
-| `T-RE-231` / `daily-mascarade-log-snapshot` | `SyncOps` | `Doc-Runbook` | routine daily, logs runtime, synthèse opératoire, `bash-cli-tui` | `tools/cockpit/run_alignment_daily.sh`, `tools/cockpit/README.md`, `specs/04_tasks.md` |
-| `T-RE-232` / `mascarade-incident-brief` | `PM` | `Doc-Runbook` | markdown opératoire, incidents runtime, handoff quotidien | `tools/cockpit/render_mascarade_incident_brief.sh`, `tools/cockpit/run_alignment_daily.sh`, `specs/04_tasks.md` |
-| `T-RE-233` / `mascarade-incident-registry` | `PM` | `Doc-Runbook` | registre incidents, historique horodaté, handoff agentique | `tools/cockpit/mascarade_incident_registry.sh`, `artifacts/cockpit/*`, `specs/04_tasks.md` |
-| `T-RE-234` / `weekly-mascarade-ops-embed` | `SyncOps` | `Doc-Runbook` | synthèse hebdo, brief incident, registre, `bash-cli-tui` | `tools/cockpit/render_weekly_refonte_summary.sh`, `tools/cockpit/render_mascarade_incident_brief.sh`, `tools/cockpit/mascarade_incident_registry.sh` |
-| `T-RE-235` / `mascarade-observability-research` | `PM` | `Research-Curator` | veille OSS, observabilité légère, incidents, status pages | `docs/WEB_RESEARCH_MASCARADE_OBSERVABILITY_2026-03-21.md`, `specs/04_tasks.md` |
-| `T-RE-236` / `daily-operator-summary-mascarade` | `SyncOps` | `Doc-Runbook` | synthèse quotidienne, handoff opérateur, markdown/json | `tools/cockpit/render_daily_operator_summary.sh`, `tools/cockpit/run_alignment_daily.sh`, `specs/04_tasks.md` |
-| `T-RE-237` / `mascarade-ops-feature-map` | `PM` | `Doc-Runbook` | feature map Mermaid, cartographie agents, roadmap ops | `docs/MASCARADE_OPS_OBSERVABILITY_FEATURE_MAP_2026-03-21.md`, `specs/04_tasks.md` |
-| `T-RE-238` / `operator-daily-summary-bridge` | `SyncOps` | `Lane-Guard` | lane operateur, synthèse quotidienne, contrat JSON | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/render_daily_operator_summary.sh`, `specs/04_tasks.md` |
-| `T-RE-239` / `incident-registry-severity-map` | `PM` | `Doc-Runbook` | priorisation incidents, gravité, handoff opérateur | `tools/cockpit/mascarade_incident_registry.sh`, `specs/04_tasks.md` |
-| `T-RE-240` / `weekly-severity-rollup` | `SyncOps` | `Doc-Runbook` | synthèse hebdo, priorité incidents, handoff opérateur | `tools/cockpit/render_weekly_refonte_summary.sh`, `tools/cockpit/mascarade_incident_registry.sh`, `specs/04_tasks.md` |
-| `T-RE-241` / `incidents-tui-surface` | `SyncOps` | `TUI-Ops` | TUI cockpit, incident review, runbook daily | `tools/cockpit/mascarade_incidents_tui.sh`, `tools/cockpit/refonte_tui.sh`, `tools/cockpit/README.md` |
-| `T-RE-242` / `incident-queue-export` | `SyncOps` | `Doc-Runbook` | file d’incidents, tri priorité/sévérité, handoff quotidien | `tools/cockpit/render_mascarade_incident_queue.sh`, `tools/cockpit/run_alignment_daily.sh`, `specs/04_tasks.md` |
-| `T-RE-243` / `daily-final-status-reconcile` | `SyncOps` | `Lane-Guard` | statut final daily, contrat JSON, cohérence runbook | `tools/cockpit/run_alignment_daily.sh`, `tools/cockpit/render_daily_operator_summary.sh`, `tools/cockpit/README.md` |
-| `T-RE-244` / `incident-queue-tui-surface` | `SyncOps` | `TUI-Ops` | TUI cockpit, queue priorisée, ergonomie opérateur | `tools/cockpit/mascarade_incidents_tui.sh`, `tools/cockpit/refonte_tui.sh`, `tools/cockpit/README.md` |
-| `T-RE-245` / `incident-queue-summary-embed` | `SyncOps` | `Doc-Runbook` | synthèses Markdown, queue priorisée, handoff opérateur | `tools/cockpit/render_daily_operator_summary.sh`, `tools/cockpit/render_weekly_refonte_summary.sh`, `specs/04_tasks.md` |
-| `T-RE-246` / `incident-queue-native-lane-bridge` | `SyncOps` | `Lane-Guard` | lane opérateur, queue priorisée, contrat JSON cockpit | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/render_mascarade_incident_queue.sh`, `specs/04_tasks.md` |
-| `T-RE-247` / `daily-priority-rollup` | `SyncOps` | `Doc-Runbook` | handoff quotidien, résumé priorité/sévérité, Markdown opérateur | `tools/cockpit/render_daily_operator_summary.sh`, `tools/cockpit/README.md`, `specs/04_tasks.md` |
-| `T-RE-248` / `incident-watch-tui` | `SyncOps` | `TUI-Ops` | watchboard terminal, garde opérateur, lecture ultra-courte | `tools/cockpit/mascarade_incidents_tui.sh`, `tools/cockpit/refonte_tui.sh`, `tools/cockpit/README.md` |
-| `T-RE-249` / `native-lane-incident-rollup` | `SyncOps` | `Lane-Guard` | JSON opérateur, priorité/sévérité, automation-friendly | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/render_daily_operator_summary.sh`, `specs/04_tasks.md` |
-| `T-RE-250` / `daily-incident-watch-artifact` | `SyncOps` | `Doc-Runbook` | artefact court de garde, JSON/Markdown, runbook quotidien | `tools/cockpit/render_mascarade_incident_watch.sh`, `tools/cockpit/run_alignment_daily.sh`, `specs/04_tasks.md` |
-| `T-RE-251` / `watchboard-research` | `PM` | `Research-Curator` | veille officielle, watchboard/status pages, arbitrage léger vs lourd | `docs/WEB_RESEARCH_MASCARADE_OBSERVABILITY_2026-03-21.md`, `specs/04_tasks.md` |
-| `T-RE-252` / `native-lane-watch-bridge` | `SyncOps` | `Lane-Guard` | watchboard court, lane opérateur, contrat JSON | `tools/cockpit/full_operator_lane.sh`, `tools/cockpit/render_mascarade_incident_watch.sh`, `specs/04_tasks.md` |
-| `T-RE-253` / `weekly-watch-embed` | `SyncOps` | `Doc-Runbook` | synthèse hebdo, watchboard court, handoff opérateur | `tools/cockpit/render_weekly_refonte_summary.sh`, `tools/cockpit/render_mascarade_incident_watch.sh`, `specs/04_tasks.md` |
-| `T-RE-254` / `operator-index-watch-shortcuts` | `SyncOps` | `TUI-Ops` | index opérateur, raccourcis garde, ergonomie courte | `tools/cockpit/yiacad_operator_index.sh`, `tools/cockpit/README.md`, `specs/04_tasks.md` |
-| `T-RE-255` / `watch-history-registry` | `SyncOps` | `Doc-Runbook` | historique watchboard, suivi P1/P2/P3, mémoire opératoire | `tools/cockpit/render_mascarade_watch_history.sh`, `artifacts/cockpit/*`, `specs/04_tasks.md` |
-| `T-RE-256` / `daily-watch-history-bridge` | `SyncOps` | `Lane-Guard` | routine daily, historique watchboard, contrat JSON | `tools/cockpit/run_alignment_daily.sh`, `tools/cockpit/render_mascarade_watch_history.sh`, `specs/04_tasks.md` |
-| `T-RE-257` / `weekly-watch-history-embed` | `SyncOps` | `Doc-Runbook` | synthèse hebdo, historique de tendance, handoff opérateur | `tools/cockpit/render_weekly_refonte_summary.sh`, `tools/cockpit/render_mascarade_watch_history.sh`, `specs/04_tasks.md` |
-| `T-RE-209` / `yiacad-fusion` | `Arch-Mesh` | `CAD-Bridge` | host-first CAD, fork strategy, `bash-cli-tui`, OSS benchmark | `docs/CAD_AI_NATIVE_FORK_STRATEGY.md`, `tools/cad/yiacad_fusion_lot.sh`, `tools/cad/ai_native_forks.sh` |
-| `T-RE-210` / `yiacad-fusion` | `Arch-Mesh` | `CAD-Smoke` | smoke KiCad/FreeCAD/OpenSCAD, status snapshot, rollback | `tools/autonomous_next_lots.py`, `tools/cockpit/refonte_tui.sh`, `tools/cockpit/render_weekly_refonte_summary.sh` |
-| `T-UX-001` / audit-uiux-apple` | `UX-Lead` | `UI-Research` | Apple HIG, information architecture, audit produit | `docs/YIACAD_APPLE_UI_UX_AUDIT_2026-03-20.md`, `docs/YIACAD_APPLE_UI_UX_OSS_RESEARCH_2026-03-20.md` |
-| `T-UX-002` / shell-ui-native` | `UX-Lead` | `CAD-UX` | KiCad/FreeCAD surface design, command palette, sidebar/inspector | `specs/yiacad_uiux_apple_native_spec.md`, `.runtime-home/cad-ai-native-forks/kicad-ki/*`, `.runtime-home/cad-ai-native-forks/freecad-ki/*` |
-| `T-UX-003` / uiux-tui` | `SyncOps` | `TUI-Ops` | `bash-cli-tui`, logs, purge contrôlée | `tools/cockpit/yiacad_uiux_tui.sh`, `artifacts/uiux_tui/*` |
-| `T-RE-214` / machine-registry-runtime` | `SyncOps` | `Schema-Guard` | registre machine, preflight mesh, `bash-cli-tui` | `specs/contracts/machine_registry.mesh.json`, `tools/cockpit/machine_registry.sh`, `tools/cockpit/mesh_sync_preflight.sh` |
-| `T-RE-301` | `SyncOps` | `Doc-Runbook` | synthèse opératoire, logs, `bash-cli-tui` | `tools/cockpit/render_weekly_refonte_summary.sh`, `artifacts/cockpit/weekly_refonte_summary.md` |
-
-Delta lot 2026-03-20:
-- le runbook cockpit integre maintenant un health-check `Mascarade/Ollama` avant le preflight mesh, avec sortie JSON et artefacts dedies
-- la veille OSS couvre explicitement les patterns `chat multi-agents + presets/personas` reutilisables pour Mascarade et Kill_LIFE
-- la surface `full_operator_lane` reste maintenant exploitable meme si l'API locale `localhost:3100` est indisponible, avec JSON contractuel et sortie non silencieuse
-- le daily cockpit integre maintenant une file d'incidents Mascarade priorisee et recalcule son statut final apres la synthese operateur quotidienne
-- la queue d'incidents est maintenant visible dans la TUI cockpit et dans les syntheses Markdown daily/weekly
-- la lane operateur native remonte maintenant aussi la queue d'incidents, et le daily affiche un rollup court P1/P2/P3 pour la lecture de garde
-- la TUI cockpit expose maintenant un `incident-watch` court, et la lane native publie aussi ce rollup au niveau top-level JSON
-- le daily cockpit publie maintenant aussi un artefact `incident-watch` dedie, tandis que la veille officielle `OpenStatus` / `OneUptime` reste documentee comme benchmark futur
-- la lane native embarque maintenant aussi ce watchboard court, et la synthese hebdomadaire l'affiche a son tour pour une revue plus rapide
-- l'index operateur YiACAD expose maintenant `incident-watch` et `incident-history`, et un historique watch dedie suit l'evolution des priorites sur plusieurs runs
-- la routine daily publie maintenant aussi l'historique `watch`, et la synthese hebdomadaire l'affiche pour suivre la tendance des priorites
-| `T-RE-302` | `Docs-Research` | `Mermaid-Map` | documentation, owner mapping, Mermaid | `docs/plans/12_plan_gestion_des_agents.md`, `docs/plans/19_todo_mesh_tri_repo.md` |
-| `T-RE-303` | `Docs-Research` | `Evidence-Pack` | synthèse, sources de vérité, mémoire d’exécution | `SYNTHESE_AGENTIQUE.md` |
-| `T-RE-304` | `SyncOps` | `Log-Ops` | checklist lot, preflight, preuve logs | `docs/plans/18_plan_enchainement_autonome_des_lots_utiles.md`, `artifacts/cockpit/weekly_refonte_summary.md` |
-| `T-OL-002` | `SyncOps` | `Lane-Repair` | propagation conservative, SSH, `bash-cli-tui` | `tools/cockpit/full_operator_lane_sync.sh`, `docs/MACHINE_SYNC_STATUS_2026-03-20.md` |
-| `T-OL-003` | `Docs-Research` | `Compat-Guard` | veille officielle, compat provider/runtime | `docs/PROVIDER_RUNTIME_COMPAT_2026-03-20.md`, `docs/FULL_OPERATOR_LANE_2026-03-20.md` |
-
-## Delta 2026-03-20 13:00
-
-- `T-CI-001` : workflow CI visible pour `agent_handoff`, `repo_snapshot`, `workflow_handshake` — aligné
-- `T-LOT-001` : consommation du checker mesh dans `autonomous_next_lots` — lot `mesh-governance` ciblé
-- `Runtime-Smoke` et `Schema-Consumer` consomment désormais les contrats depuis la lane soeur `Kill_LIFE-main`
-- `docs/plans/19_todo_mesh_tri_repo.md` devient la checklist lot-level de référence pour la passe mesh courante
-
-## Delta 2026-03-20 14:20 - full operator lane
-
-- `Docs-Research`: published the operator-lane contract, Mermaid and README deltas.
-- `Runtime-Companion`: kept the mascarade live bridge explicit and degraded-safe.
-- `Web-Cockpit`: connected the new live-provider local action to the existing Crazy Lane routes.
-- `SyncOps`: added the dedicated TUI runbook and staged the lot on the mesh lanes.
-
-## Delta 2026-03-20 14:45 - post-E2E hardening
-
-- `Runtime-Companion`: moved the live bridge to a container-safe Node runner while keeping Python parity for host-side debugging.
-- `Compat-Guard`: documented the effective runtime contract from official Anthropic/OpenAI sources and local evidence.
-- `SyncOps`: published `full_operator_lane_sync.sh` to stop relying on ad hoc file copies.
-- `PM-Mesh`: re-prioritized the next lot around staged-lane recovery on `clems` before resuming broader mesh propagation.
-
-## Delta 2026-03-20 15:01 - plan de passe enchaînée (3 plans)
-
-- `PM`: lot runbook exécuté `status→run→json`; les lots `zeroclaw-integrations`, `mesh-governance`, `mcp-runtime`, `cad-mcp-host` sont passés, `yiacad-fusion` bloque toujours sur contraintes d’intégration CAD prévues.
-- `SyncOps`: préflight `mesh_health_check --load-profile tower-first --json` et `run_alignment_daily --mesh-load-profile tower-first` alignés avec ordre `clems, kxkm, cils, local, root`; `cils` reste en garde `cils-lockdown`.
-- `Architect-Firmware`: carte de plans/compétences consolidée dans la section “Carte opérationnelle des 3 plans” pour mémoire d’exécution continue.
-
-## Delta 2026-03-20 18:35 - canonical owners YiACAD
-
-- `DesignOps-UI` garde le lead produit sur `T-UX-003` et `T-UX-004`.
-- `CAD-UX / KiCad-Shell` devient owner de `T-UX-003A` pour les toolbars `pcbnew` / `eeschema`.
-- `CAD-UX / FreeCAD-Shell` reste owner de `T-UX-003B` sur `yiacad_freecad_gui.py`.
-- `CAD-UX / KiCad-Native` prend `T-UX-003C` pour la symetrie des control layers PCB/SCH.
-- `CAD-UX / FreeCAD-Native` garde `T-UX-003D` comme prochaine tranche shell minimale sur `MainWindow.cpp`.
-- `Doc-Research / OSS-Watch` prend `T-UX-004B` et la veille officielle associee.
-- `SyncOps / TUI-Ops` prend `Support UI/UX Ops` pour la lisibilite de la lane, les preuves et les logs via `tools/cockpit/yiacad_uiux_tui.sh`.
-
-## Delta 2026-03-20 16:05 - DesignOps UI/UX Apple-native
-
-## Delta 2026-03-20 18:30 - Mascarade `kxkm-ai` live Ollama
-
-- `Runtime-Companion`: le runtime live charge bien les `18` agents `kxkm-*` et s'appuie maintenant sur un `mascarade-ollama-runtime` local en `0.18.2`, raccorde au store de modeles deja presents sur `kxkm-ai`.
-- `Provider-Bridge`: le catalogue `mascarade_model_profiles.kxkm_ai.json` passe en `ollama-first` pour eliminer la derive vers des providers externes non exposes par la stack live.
-- `PM-Mesh`: nouveau lot `T-RE-218` ferme l'ecart entre catalogue, seed runtime et realite machine; smoke live confirme `kxkm-analysis` -> `ollama/mascarade-power:latest` et `kxkm-firmware` -> `ollama/mascarade-platformio:latest`.
-- `Runtime-Smoke`: `T-RE-219` ajoute un smoke scriptable et logge pour la vague critique restante `code`, `cad`, `ops`, `fallback-safe`.
-- `Runtime-Smoke`: le smoke consolide est valide via `artifacts/ops/mascarade_agent_smoke/latest.json`; `kxkm-analysis`, `kxkm-firmware`, `kxkm-code`, `kxkm-cad`, `kxkm-ops` et `kxkm-fallback-safe` repondent tous en `200` sur `ollama`.
-- `Provider-Bridge`: le bridge utilisateur transitoire `kxkm-ollama-bridge.service` est maintenant desactive; la pile Mascarade live reste stable uniquement avec `mascarade-ollama-runtime`.
-- `UI-Dispatch`: la page `Agents` de Mascarade expose maintenant des presets `kxkm-*` directement visibles et la publication front a ete regeneree dans `api/public`.
-
-- `UX-Lead`: ouvre la refonte UI/UX YiACAD alignée sur les recommandations Apple officielles disponibles au 2026-03-20.
-- `Apple-HIG`: tient la matrice `sidebar / toolbar / inspector / search / generative-ai affordances`.
-
-## Delta 2026-03-20 18:25 - Mascarade kxkm-ai
-
-- `Runtime-Companion`: a reutilise le chat Mascarade existant sur `kxkm-ai` au lieu de creer une nouvelle surcouche.
-- `Provider-Bridge`: a seed 8 agents dynamiques `kxkm-*` dans `/home/kxkm/mascarade-main/data/agents.json` a partir du catalogue de profils runtime.
-
-## Consolidation canonique 2026-03-20 - YiACAD
-
-- Lanes de reference:
- - `Audit-Core`: audit global, cartes, synthese
- - `AI-Integration`: trajectoire IA, backend YiACAD, contexte unifie
- - `CAD-Native`: hooks shells KiCad/FreeCAD, propagation de contexte
- - `DesignOps-UI`: `T-UX-004`, palette, review center, inspector
- - `Ops-TUI`: TUIs, logs, index operateur, preuves
- - `Docs-Continuity`: README, index, plans, TODOs, coherence
-- Canon current:
- - `DesignOps-UI` garde le lead produit sur `T-UX-004`
- - `CAD-Native` prend `T-ARCH-101` comme priorite architecture immediate
- - `Ops-TUI` tient la convergence entre `yiacad_uiux_tui.sh` et `yiacad_refonte_tui.sh`
-- Write-set architecture actif:
- - `CAD-Native`: `tools/cad/yiacad_backend.py`, `tools/cad/yiacad_native_ops.py`
- - `AI-Integration`: `specs/contracts/yiacad_context_broker.schema.json`, `specs/yiacad_backend_architecture_spec.md`
- - `Docs-Continuity`: `docs/YIACAD_BACKEND_ARCHITECTURE_2026-03-20.md`, `README.md`, `docs/index.md`
-- Regle d'arbitrage:
- - les affectations historiques restent valides comme trace
- - les prochains write-sets YiACAD doivent se distribuer en priorite entre `backend`, `palette`, `review center`, `inspector`
-
-## Delta 2026-03-20 18:35 - Mascarade kxkm-ai vague 2
-
-- `Runtime-Companion`: etend le catalogue avec `firmware`, `cad`, `ops`, `docs`, `security`, `fine-tune`.
-- `Provider-Bridge`: reutilise le meme seed `data/agents.json` pour densifier le chat existant sans nouvelle couche applicative.
-
-## Delta 2026-03-20 18:45 - Mascarade kxkm-ai vague 3 + smoke
-
-- `Runtime-Companion`: ajoute une vague metier `kill-life-firmware`, `yiacad-cad`, `mesh-syncops`, `docs-specs`.
-- `Provider-Bridge`: confirme que le seed disque est bon mais que l'API live `127.0.0.1:3100/api/agents` ne remonte encore que les agents builtin; un reload runtime reste a programmer pour exposer les `kxkm-*` dans le chat actif.
-- `CAD-UX`: porte la translation vers KiCad/FreeCAD (`plugin/workbench -> hooks directs -> shell natif`).
-- `TUI-Ops`: publie la boucle `yiacad_uiux_tui.sh` avec lecture de logs, synthèse et purge contrôlée.
-
-## Delta 2026-03-20 16:35 - T-UX-003 en cours
-
-- `UX-Lead`: active le lot `T-UX-003` comme prochaine marche prioritaire de YiACAD.
-- `KiCad-Native`: ownership de la montée shell côté `kicad-ki` via menus/toolbars/actions natives.
-- `FreeCAD-Native`: ownership de la montée shell côté `freecad-ki` via workbench/manipulator/dock natifs.
-- `Doc-Entry`: raccorde les docs d’entrée (`index`, `quickstart`, `runbook`, `MCP_SETUP`, `deploy/cad`) pour rendre les surfaces YiACAD découvrables.
-
-## Delta 2026-03-20 16:55 - T-UX-003 palier 1 livré
-
-- `KiCad-Native`: livre un premier palier dans `kicad_manager` avec `YiACAD Status` visible en menu et toolbar natifs.
-- `FreeCAD-Native`: livre un premier palier avec `YiACAD Inspector` dockable dans `YiACADWorkbench`.
-- `Doc-Entry`: a relié les docs opératoires et templates design à la lane YiACAD UI/UX.
-- reste ouvert: montée native `pcbnew`, `eeschema`, palette de commandes et inspector plus profonds.
-
-## Delta 2026-03-20 15:45 - registre canonique spec/module
-
-- Canonique tri-repo publie dans `docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md`.
-- `Kill_LIFE`:
- - specs = ownership explicite `PM-Mesh`, `Arch-Mesh`, `Schema-Guard`, `QA-Compliance`, `Embedded-CAD`, `UX-Lead`
- - modules = ownership explicite `SyncOps`, `Runtime-Companion`, `KillLife-Bridge`, `Release-Gates`
-- `mascarade-main`:
- - workstreams `WS0..WS9` ventiles entre `Runtime-Companion`, `SyncOps`, `Arch-Mesh`, `Web-Cockpit`
-- `crazy_life-main`:
- - workstreams `WS-01..WS-11` ventiles entre `Web-Cockpit`, `KillLife-Bridge`, `SyncOps`, `Docs-Research`
-- Hotspots `single-writer` confirms:
- - `tools/cockpit/mesh_sync_preflight.sh`
- - `mascarade-main/api/src/lib/killlife.ts`
- - `crazy_life-main/api/src/index.ts`
- - `crazy_life-main/src/pages/CrazyLaneEditor.tsx`
-- Nouvelle TUI:
- - `bash tools/cockpit/agent_matrix_tui.sh --action summary`
- - `bash tools/cockpit/agent_matrix_tui.sh --action owners`
- - `bash tools/cockpit/agent_matrix_tui.sh --action open-tasks`
-- Dirty-sets inter-machines realignes via `tools/cockpit/mesh_dirtyset_sync.sh`; le residuel `mesh_status=degraded` vient du `cils-lockdown` et non d'un ecart reel de worktree.
-
-## Delta 2026-03-20 - coordination agents T-UX-003
-- `DesignOps-UI` conserve le lead sur `T-UX-003` et prépare `T-UX-004`.
-- `Sagan` est désormais owner du write-set `kicad-ki/kicad`, `kicad-ki/pcbnew`, `kicad-ki/eeschema` pour les insertions UI natives YiACAD.
-- `Peirce` reste owner du write-set `freecad-ki/src/Mod/YiACADWorkbench` pour l'inspector, le docking et la montée vers des hooks plus profonds.
-- Compétences actives sur le lot: intégration UI native, orchestration CAD IA, TUI cockpit, documentation de handoff.
-
-## Delta 2026-03-20 - coordination agents direct hooks
-- `Godel` a livré le recâblage direct `kicad-ki -> yiacad_native_ops.py` sur `kicad manager`, `pcbnew` et `eeschema`.
-- `Locke` a livré le recâblage direct `freecad-ki -> yiacad_native_ops.py` dans le workbench `YiACADWorkbench`.
-- `DesignOps-UI` peut maintenant déplacer la priorité de `wiring` vers `interaction design`, `feedback loops` et `palette/inspector orchestration`.
-
-## Delta 2026-03-20 - distribution agents T-UX-004
-- `DesignOps-UI` garde le lead fonctionnel sur `T-UX-004` et devient owner du contrat UX commun (`done|degraded|blocked`, severite, artefacts, next_steps).
-- `Sagan/Godel lane` prend le write-set KiCad pour `palette` et `review center` sur `kicad manager`, `pcbnew`, `eeschema`.
-- `Peirce/Locke lane` prend le write-set FreeCAD pour `palette`, `review center` et `inspector persistant` dans `YiACADWorkbench`.
-- `AgentMatrix lane` garde la maintenance des plans, todos, competence maps et handoffs.
-
-## Delta 2026-03-20 - agents refonte globale YiACAD
-- `Audit-Core` prend ownership du bundle `docs/YIACAD_GLOBAL_*` et de `specs/yiacad_global_refonte_spec.md`.
-- `AI-Integration` prend ownership de la matrice d'opportunites IA et de la veille OSS associee.
-- `Ops-TUI` prend ownership de `tools/cockpit/yiacad_refonte_tui.sh` et de `artifacts/yiacad_refonte_tui/*`.
-- `DesignOps-UI` conserve le lead sur `T-UX-004` et depend des sorties du bundle global pour la priorisation UX.
-
-## Delta 2026-03-21 - operator index lane
-- `Ops-TUI` prend l'ownership de l'entree operateur stable:
- - `docs/YIACAD_OPERATOR_INDEX_2026-03-21.md`
- - `tools/cockpit/yiacad_operator_index.sh`
-- `DesignOps-UI` garde le lead produit sur la suite de `T-UX-004`.
-
-## Delta 2026-03-21 - T-UX-005 execution
-- `Newton` a livre l'enrichissement du review center cote plugin KiCad.
-- `Hypatia` a livre l'enrichissement du review center cote workbench FreeCAD.
-- `DesignOps-UI` garde maintenant comme front principal `T-UX-006` pour l'inspector persistant.
-
-## Delta 2026-03-21 - T-UX-006 execution
-- `Franklin` a livre la persistance de session cote plugin KiCad.
-- `Lorentz` a livre la persistance de session cote workbench FreeCAD.
-- `DesignOps-UI` peut maintenant deplacer le focus produit vers une session de revue plus riche, tandis que `CAD-Native` garde `T-ARCH-101C` comme front architecture.
-
-## Delta 2026-03-21 - T-ARCH-101C execution
-- `Hume` a recable le plugin KiCad sur `yiacad_backend_client.py` en mode `service-first` avec fallback direct.
-- `Mendel` a recable le workbench FreeCAD sur `yiacad_backend_client.py` en mode `service-first` avec fallback direct.
-- `CAD-Native` garde maintenant comme suite logique l'extension du chemin service-first aux surfaces plus profondes si necessaire.
-
-## 2026-03-21 - Lot update
-- `T-ARCH-101C` etendu: les surfaces KiCad compilees passent en `service-first` via `tools/cad/yiacad_backend_client.py`, avec auto-start du service local et fallback direct vers `tools/cad/yiacad_native_ops.py`.
-- `T-OPS-119` consolide: `tools/cockpit/yiacad_operator_index.sh` devient l'entree operateur stable avec `status`, `uiux`, `global`, `backend`, `proofs` et des alias de compatibilite conserves.
-- Risque residuel: aucune validation d'execution n'a ete lancee; l'extension aux call sites compiles restants doit etre traitee dans un lot separe.
-
-## 2026-03-21 - Proofs lane
-- Nouveau point d'entree: `bash tools/cockpit/yiacad_proofs_tui.sh --action status`.
-- Objectif: centraliser `backend`, `review-session`, `review-history`, `review-taxonomy` et l'hygiene des logs dans une surface canonique sans casser les alias historiques.
-- Documentation: `docs/YIACAD_PROOFS_TUI_2026-03-21.md`.
-
-## 2026-03-21 - Canonical operator entry
-- Entree publique recommandee: `bash tools/cockpit/yiacad_operator_index.sh --action status`.
-- Surface de preuves: `bash tools/cockpit/yiacad_proofs_tui.sh --action status`.
-- Surface de logs: `bash tools/cockpit/yiacad_logs_tui.sh --action status`.
-- Les routes directes historiques restent compatibles, mais ne sont plus l'entree publique recommandee.
-
-## Delta 2026-03-21 - Tower Mascarade runtime / dispatch
-- `Runtime-Companion` prend l'ownership de:
- - `specs/contracts/mascarade_model_profiles.tower.json`
- - `tools/ops/sync_mascarade_agents_tower.sh`
- - `tools/ops/deploy_mascarade_tower_runtime.sh`
-- `SyncOps` prend l'ownership de:
- - `specs/contracts/mascarade_dispatch.mesh.json`
- - `tools/cockpit/mascarade_dispatch_mesh.sh`
-- `Docs-Research` publie:
- - `docs/MASCARADE_TOWER_RUNTIME_2026-03-21.md`
-- Politique agentique retenue:
- - `tower-code`, `tower-analysis`, `tower-research` = lanes lourdes prioritaires sur `tower`
- - `tower-text` = lane documentaire secondaire `tower`, fallback `kxkm`
- - `kxkm-*` reste la couche interactive/orchestration et le premier fallback operable
-
-## Delta 2026-03-21 - consolidation produit ops / Mascarade / kill_life
-- `PM-Mesh` prend l'ownership du contrat produit commun:
- - `specs/contracts/ops_mascarade_kill_life.contract.json`
- - `docs/OPS_MASCARADE_KILL_LIFE_PRODUCT_CONTRACT_2026-03-21.md`
-- `SyncOps` devra projeter ce contrat dans les sorties cockpit prioritaires.
-- `Runtime-Companion` devra exposer `routing` et `trust_level` cote Mascarade.
-- `KillLife-Bridge` devra materialiser `resume_ref` et `memory_entry` comme traces de continuite d'execution.
-
-## Delta 2026-03-21 - projection contrat sur les surfaces cockpit
-- `SyncOps` prend l'ownership de:
- - `tools/cockpit/full_operator_lane.sh`
- - `tools/cockpit/run_alignment_daily.sh`
-- `KillLife-Bridge` prend l'ownership de:
- - `tools/cockpit/write_kill_life_memory_entry.sh`
-- objectif atteint sur ce lot:
- - `owner`, `decision`, `resume_ref`, `trust_level`, `routing` et `memory_entry` remontent dans les JSON cockpit prioritaires
- - une memoire `kill_life` timestampée et `latest` est ecrite dans `artifacts/cockpit/kill_life_memory`
-
-## Delta 2026-03-21 - projection contrat sur les surfaces Mascarade et handoffs
-- `Runtime-Companion` prend l'ownership de:
- - `tools/cockpit/mascarade_runtime_health.sh`
- - `tools/cockpit/mascarade_incidents_tui.sh`
-- `Docs-Research` prend l'ownership de:
- - `tools/cockpit/render_daily_operator_summary.sh`
- - `tools/cockpit/render_weekly_refonte_summary.sh`
-- objectif atteint sur ce lot:
- - les surfaces Mascarade courtes relisent la memoire `kill_life`
- - les handoffs Markdown quotidiens et hebdomadaires rendent explicites `trust_level`, `resume_ref` et la cible de routing
-
-## Delta 2026-03-21 - micro-surfaces Mascarade
-- `Runtime-Companion` prend aussi l'ownership de:
- - `tools/cockpit/render_mascarade_incident_brief.sh`
- - `tools/cockpit/render_mascarade_incident_queue.sh`
- - `tools/cockpit/render_mascarade_incident_watch.sh`
- - `tools/cockpit/render_mascarade_watch_history.sh`
-- objectif atteint:
- - les resumes Mascarade les plus courts gardent eux aussi la continuite `kill_life`
-
-## Delta 2026-03-21 - veille mémoire agentique
-- `Docs-Research` consolide la veille dans:
- - `docs/WEB_RESEARCH_MASCARADE_OBSERVABILITY_2026-03-21.md`
-- décision de plan:
- - garder la mémoire `latest` fichier tant que le contrat produit n'est pas gelé
- - n'ouvrir un vrai store mémoire agentique qu'après stabilisation de `resume_ref`, `trust_level`, `routing` et `memory_entry`
-
-## Delta 2026-03-21 - writer kill_life et dernières surfaces
-- `KillLife-Bridge` corrige `tools/cockpit/write_kill_life_memory_entry.sh` pour transmettre réellement les artefacts de reprise.
-- `Runtime-Companion` étend la projection contrat à:
- - `tools/cockpit/mascarade_incident_registry.sh`
- - `tools/cockpit/mascarade_logs_tui.sh`
-- objectif atteint:
- - la mémoire `kill_life` latest devient cohérente avec les surfaces de logs et d'incidents
-
-## Delta 2026-03-21 - gouvernance / points d'entrée
-- `Ops-TUI` étend les points d'entrée vers:
- - `tools/cockpit/yiacad_operator_index.sh`
-- `Docs-Research` et `PM-Mesh` étendent la convergence vers:
- - `tools/cockpit/intelligence_tui.sh`
-- objectif atteint:
- - les entrées opérateur et la mémoire de gouvernance pointent elles aussi vers la continuité `kill_life`
-
-## Delta 2026-03-21 - chaînes de pilotage cockpit
-- `Ops-TUI` étend la continuité `kill_life` vers:
- - `tools/cockpit/refonte_tui.sh`
- - `tools/cockpit/lot_chain.sh`
-- `PM-Mesh` garde l'ownership du fil conducteur entre:
- - `artifacts/cockpit/kill_life_memory/latest.json`
- - `artifacts/cockpit/useful_lots_status.md`
- - `specs/03_plan.md`
- - `specs/04_tasks.md`
-- objectif atteint:
- - la TUI courte et la chaîne de lots cockpit convergent elles aussi vers le même point de reprise
-
-## Delta 2026-03-21 - audit et cartographie du contrat produit
-- `PM-Mesh` prend l'ownership de:
- - `tools/cockpit/product_contract_audit.sh`
- - `docs/OPS_MASCARADE_KILL_LIFE_FEATURE_MAP_2026-03-21.md`
-- `Docs-Research` consolide la veille officielle dans:
- - `docs/WEB_RESEARCH_MASCARADE_OBSERVABILITY_2026-03-21.md`
-- objectif atteint:
- - un garde-fou statique contrôle la présence des ancrages de continuité dans les surfaces source
- - la carte Mermaid rend explicite la séparation `ops / Mascarade / kill_life` et leur point de convergence
-
-## Delta 2026-03-21 - handoff produit minimal
-- `Ops-TUI` prend l'ownership de:
- - `tools/cockpit/render_product_contract_handoff.sh`
-- `Docs-Research` consolide le rationnel HITL / reprise dans:
- - `docs/WEB_RESEARCH_MASCARADE_OBSERVABILITY_2026-03-21.md`
-- objectif atteint:
- - un brief opérateur unique agrège audit, mémoire `kill_life` et synthèse quotidienne
- - le prochain pas produit reste lisible sans changer de surface
-
-## Delta 2026-03-21 - handoff produit degraded-safe
-- `Ops-TUI` durcit `tools/cockpit/render_product_contract_handoff.sh`.
-- `PM-Mesh` conserve l'ownership des critères de sincérité:
- - pas de statut `ok` si la mémoire `kill_life` latest ou la synthèse quotidienne manquent
-- objectif atteint:
- - le handoff produit devient un vrai garde-fou opérateur, même en absence d'artefacts amont
-
-## Delta 2026-03-21 - handoff produit self-healing et exposition opérateur
-- `KillLife-Bridge` prend l'ownership de:
- - l'auto-régénération légère dans `tools/cockpit/render_product_contract_handoff.sh`
-- `SyncOps` prend l'ownership de:
- - l'intégration du handoff dans `tools/cockpit/run_alignment_daily.sh`
- - l'intégration du handoff dans `tools/cockpit/full_operator_lane.sh`
-- `Ops-TUI` prend l'ownership de:
- - l'exposition du handoff dans `tools/cockpit/yiacad_operator_index.sh`
- - l'exposition du handoff dans `tools/cockpit/refonte_tui.sh`
-- `Docs-Research` consolide:
- - `tools/cockpit/README.md`
-- objectif atteint:
- - le handoff produit devient le point de reprise canonique visible depuis les chemins opérateur
- - la séparation entre audit statique et handoff runtime léger reste explicite
-
-## Delta 2026-03-21 - correctifs producteurs du handoff
-- `KillLife-Bridge` corrige `tools/cockpit/write_kill_life_memory_entry.sh`.
-- `Docs-Research` corrige la sortie JSON unique de `tools/cockpit/render_daily_operator_summary.sh`.
-- objectif atteint:
- - le self-healing du handoff peut réellement recréer sa mémoire `kill_life`
- - le résumé quotidien reste parseable en JSON par les autres surfaces
-
-## Delta 2026-03-21 - propagation Markdown du handoff
-- `Ops-TUI` corrige le contrat de `tools/cockpit/render_product_contract_handoff.sh`.
-- objectif atteint:
- - `markdown_file` et `latest_markdown_file` remontent explicitement dans le JSON du handoff
- - les surfaces opérateur qui relisent ce handoff retrouvent un pointeur Markdown exploitable
-
-## Delta 2026-03-21 - robustesse JSON lane opérateur
-- `SyncOps` durcit `tools/cockpit/full_operator_lane.sh`.
-- objectif atteint:
- - la lecture d'artefacts JSON concaténés ne casse plus la lane opérateur
- - les régressions liées à des payloads historiques multi-JSON sont contenues dans le helper de lecture
-
-## Delta 2026-03-21 - T-UX-003 / T-UX-004 closure
-- `DesignOps-UI` clot les lots parents `T-UX-003` et `T-UX-004` apres verification des surfaces reelles KiCad/FreeCAD.
-- `CAD-UX / KiCad-Shell`, `CAD-UX / KiCad-Native`, `CAD-UX / FreeCAD-Shell` et `CAD-UX / FreeCAD-Native` basculent leur front restant vers `T-UX-007`.
-- `QA-Compliance` ajoute `test/test_yiacad_native_surface_contract.py` pour verrouiller la presence des ancrages shell, de la palette et de l'inspector persistant.
-
-## Session 2026-03-22 — ERP minimal / bridge Ops -> Kill_LIFE
-
-Nouveaux agents dedies ou reaffectes:
-- `Ops-Governor` -> couche `ERP / L'electronrare Ops`, gouvernance machines, roots, secret scopes, ownership
-- `KillLife-Bridge` -> pont secrets de gouvernance et continuite outillage `Kill_LIFE`
-- `Artifact-Curator` -> couche `WMS`, index artefacts / logs / datasets / handoffs
-- `Runtime-Guard` -> couche `DCS`, health-checks, mesh, dispatch, charge
-- `PLM-Archivist` -> couche `PLM`, contrats, plans, cartes systeme
-- `Dataset-Curator` -> preflight dataset et blocages VM fine-tune
-
-Livrables rattaches:
-- `docs/OPS_KILL_LIFE_ERP_BRIDGE_CONTRACT_2026-03-22.md`
-- `specs/contracts/ops_kill_life_erp_registry.json`
-- `tools/cockpit/ops_erp_registry_tui.sh`
-- `docs/WEB_RESEARCH_DIGITAL_FACTORY_STACK_2026-03-22.md`
-
-## Session 2026-03-22 — WMS artifact index minimal
-
-Affectations confirmees:
-- `Artifact-Curator` -> proprietaire de l'index `artifact -> lot -> couche -> consumer`
-- `SyncOps` -> consumer principal cote `MES`
-- `Runtime-Guard` -> consumer principal cote `DCS`
-- `PLM-Archivist` -> consumer principal cote `PLM`
-
-Livrables rattaches:
-- `specs/contracts/artifact_wms_index_rules.json`
-- `tools/cockpit/artifact_wms_index_tui.sh`
-- `docs/ARTIFACT_WMS_INDEX_2026-03-22.md`
-- `Artifact-Curator` and `Ops-Governor` now also own the comparative reference set for WMS indexing and future cockpit catalog patterns (`MLflow`, `Dagster`, `DVC`, `Backstage`, `Rundeck`).
-
-## Delta 2026-03-22 - PCB AI / fabrication ownership
-
-- `Embedded-CAD` prend l'ownership du cadrage externe `PCB Designer AI / Quilter` pour les lanes de layout et de fabrication.
-- `CAD-Bridge` prend la future lane `T-RE-296` de round-trip `KiCad -> Quilter -> preuves locales`.
-- `HW-BOM` prend l'ownership des patterns `BOM / sourcing / JLCPCB / LCSC` issus de `kicad-happy`.
-- `Forge` devient copilote de traduction des patterns `review.bom`, `alternatives composants`, `fabrication prep` vers les playbooks locaux.
-- `CAD-Smoke` garde la responsabilite des preuves de canary et des criteres d'acceptation avant toute adoption runtime.
-
-## Delta 2026-03-22 - realignment lot 26 + fab package ownership
-
-- `PM-Mesh / Todo-Tracker` prend l'ownership du realignement de `Plan 26` et des ecarts doc vs repo actif.
-- `Embedded-CAD` prend l'ownership du contrat `fab package` et de l'orchestration locale `BOM + DRC + Gerber/drill + provenance`.
-- `SyncOps / TUI-Ops` prend l'ownership de `tools/cockpit/fab_package_tui.sh` et des artefacts `latest.json` / `latest.md`.
-- `HW-BOM` porte la sortie `assembly-ready|blocked`, les alternatives composants et le mapping `LCSC/JLCPCB`.
-- `Runtime-Companion` ne reprend la main sur les providers/agents EDA que lorsque le realignement lot 26 est clos et que le contrat `fab package` est valide.
+| `PM-Mesh` | intake, priorisation, plans et lots | `Plan-Orchestrator`, `Intake-Guard`, `Todo-Tracker` | `specs/`, `docs/plans/`, `.github/prompts/` |
+| `Arch-Mesh` | architecture, ADR, frontières et contrats | `Contract-Guard`, `Mesh-Contracts` | `specs/`, `docs/`, `kill_life/` |
+| `Docs-Research` | docs canoniques, navigation, recherche, récit catalogue | `Doc-Entry`, `Plan-Recorder`, `Agent-Catalog` | `docs/`, `README.md`, `README_FR.md`, `agents/`, `.github/agents/` |
+| `Runtime-Companion` | runtime IA, MCP, provider bridges, dégradation maîtrisée | `MCP-Health`, `Provider-Bridge`, `Runtime-Guard` | `tools/ai/`, `tools/ops/`, `mcp.json`, `specs/mcp_agentics_target_backlog.md` |
+| `QA-Compliance` | tests, schémas, gates, evidence release-ready | `Constraint-Gate`, `Contract-Tests`, `Release-Gates` | `test/`, `compliance/`, `.github/workflows/`, `tools/specs/` |
+| `Embedded-CAD` | KiCad, FreeCAD, OpenSCAD, fab et CAD host-first | `CAD-Bridge`, `HW-BOM`, `CAD-Fusion` | `tools/cad/`, `tools/hw/`, `hardware/`, `specs/kicad_mcp_scope_spec.md` |
+| `Web-CAD-Platform` | web YiACAD, GraphQL, realtime, workers, review | `Project-Service`, `EDA-CI-Orchestrator`, `Realtime-Collab`, `Review-Assist`, `Artifacts-Bridge` | `web/`, `specs/yiacad_git_eda_platform_spec.md` |
+| `UX-Lead` | UX YiACAD, shell natif, recherche design, UI contracts | `Apple-HIG`, `CAD-UX`, `UI-Research` | `docs/YIACAD_*`, `docs/CAD_AI_NATIVE_*`, `specs/yiacad_uiux_apple_native_spec.md` |
+| `Firmware` | implémentation PlatformIO, tests, validation embarquée | `FW-Build`, `FW-Test`, `FW-Evidence` | `firmware/`, `docs/evidence/esp/` |
+| `SyncOps` | cockpit, logs, incidents, SSH, mesh ops | `TUI-Ops`, `Log-Ops`, `Mesh-Cluster`, `Doc-Runbook` | `tools/cockpit/`, `artifacts/cockpit/`, `docs/MESH_*` |
+| `Schema-Guard` | schémas, validateurs, invariants structurels | `Handoff-Schema`, `Evidence-Schema`, `Workflow-Schema` | `specs/contracts/`, `tools/specs/` |
+| `KillLife-Bridge` | surfaces workflow/evidence inter-repo et bridge applicatif | `Workflow-Editor`, `Evidence-Runner`, `Schema-Consumer` | `workflows/`, `specs/contracts/ops_*`, `specs/contracts/artifact_*` |
+
+## Taxonomie de lanes
+
+La taxonomie commune utilisée dans ce repo est maintenant:
+
+| Lane | Owner top-level | Sorties attendues |
+| --- | --- | --- |
+| Program Governance | `PM-Mesh` | plans, backlog, arbitrages, next lots |
+| Architecture Contracts | `Arch-Mesh` | specs, ADR, interfaces, migrations |
+| Documentation Catalog | `Docs-Research` | README, index, plans, catalog narrative |
+| Runtime Gateway | `Runtime-Companion` | santé runtime/MCP, provider bridges, fallback |
+| Quality Gates | `QA-Compliance` | tests, validateurs, evidence packs |
+| CAD Native | `Embedded-CAD` | CAD host-first, fab outputs, CAD smokes |
+| Web EDA | `Web-CAD-Platform` | Next.js, GraphQL, Yjs, BullMQ, review |
+| UX Native | `UX-Lead` | UI audits, design contracts, shell native |
+| Firmware Delivery | `Firmware` | builds, unit tests, firmware evidence |
+| Operations Mesh | `SyncOps` | cockpit JSON, logs, incidents, SSH |
+| Schema Governance | `Schema-Guard` | schemas, contract validators, drift gates |
+| Cross-Repo Bridge | `KillLife-Bridge` | workflow bridge, handoffs, shared evidence |
+
+## Règles de gouvernance
+
+1. Un `owner_agent` doit toujours être l'un des 12 IDs canoniques.
+2. Les sous-agents comme `Plan-Orchestrator`, `CAD-Bridge`, `Review-Assist` et `Constraint-Gate` sont de la metadata, pas des agents API.
+3. Chaque lot doit publier `owner_repo`, `owner_agent`, `write_set`, `status` et `evidence`.
+4. Les prompts, docs agents, README et matrice doivent rester alignés avec le contrat machine-readable.
+5. Les alias historiques de l'ancienne surface BMAD à 6 agents sont retirés du runtime public et ne doivent plus être documentés comme surfaces actives.
+
+## Routage et handoffs
+
+- `/agents` expose uniquement le catalogue 2026.
+- `/agents/{name}/run` accepte uniquement les IDs top-level canoniques.
+- Les alias historiques de la surface BMAD retirée renvoient un `410 Gone` avec hint de migration vers le catalogue canonique.
+- Les handoffs et preuves conservent leurs schémas existants, mais `owner_agent` doit résoudre vers le catalogue.
+
+## Routine opératoire
+
+1. Mettre à jour le contrat catalogué avant toute extension du catalogue.
+2. Régénérer ou ajuster `agents/*`, `.github/agents/*` et les prompts par agent.
+3. Valider la parité avec `tools/specs/validate_agent_catalog.py`.
+4. Mettre à jour les producteurs `tools/cockpit/*`, `tools/autonomous_next_lots.py` et les contrats `specs/contracts/*`.
+5. Archiver les preuves de validation dans `artifacts/ci/` et `docs/evidence/`.
+
+## Delta 2026-03-29
+
+- `PM-Mesh`, `Arch-Mesh`, `Docs-Research`, `Runtime-Companion`, `QA-Compliance`, `Embedded-CAD`, `Web-CAD-Platform`, `UX-Lead`, `Firmware`, `SyncOps`, `Schema-Guard` et `KillLife-Bridge` deviennent la seule couche top-level canonique.
+- Les docs agents et prompts historiques de la surface BMAD à 6 agents sont retirés du runtime et remplacés par la surface catalogue 2026.
+- Les producteurs de gouvernance doivent désormais publier les owners top-level et déplacer les détails de lane dans `owner_subagent`.
+
+### Revalidation baseline / miroir / stable suite
+
+- Baseline catalogue/runtime rejouée et archivée:
+ - `artifacts/ci/agent_catalog_contract.json`
+ - `artifacts/ci/validate_specs.json`
+ - `artifacts/ci/catalog_runtime_pytest.log`
+- Miroir `ai-agentic-embedded-base/specs` resynchronisé; validation stricte verte via:
+ - `artifacts/ci/validate_specs_require_mirror_sync.json`
+ - `artifacts/specs/mirror_sync_report.md`
+- Gouvernance revalidée avec owners canoniques:
+ - `artifacts/ci/governance_owner_agents.json`
+- Suite stable rejouée avec succès:
+ - `artifacts/ci/stable_suite.log`
+- Blocage YiACAD traité dans `tools/cad/yiacad_backend.py`: la détection des engines est maintenant bornée par timeout et n'exécute plus le binaire GUI FreeCAD pour lire la version.
diff --git a/docs/plans/29_plan_coordination_transversale_agents_kill_life.md b/docs/plans/29_plan_coordination_transversale_agents_kill_life.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/plans/30_plan_management_agents_kill_life.md b/docs/plans/30_plan_management_agents_kill_life.md
new file mode 100644
index 0000000..e69de29
diff --git a/docs/plans/30_todo_management_agents_kill_life.md b/docs/plans/30_todo_management_agents_kill_life.md
new file mode 100644
index 0000000..e69de29
diff --git a/kill_life/agent_catalog.py b/kill_life/agent_catalog.py
new file mode 100644
index 0000000..19d8097
--- /dev/null
+++ b/kill_life/agent_catalog.py
@@ -0,0 +1,62 @@
+"""Shared helpers for the canonical Kill_LIFE 2026 agent catalog."""
+
+from __future__ import annotations
+
+import json
+from functools import lru_cache
+from pathlib import Path
+from typing import Any
+
+REPO_ROOT = Path(__file__).resolve().parent.parent
+AGENT_CATALOG_PATH = REPO_ROOT / "specs" / "contracts" / "kill_life_agent_catalog.json"
+
+
+@lru_cache(maxsize=1)
+def load_agent_catalog() -> dict[str, Any]:
+ return json.loads(AGENT_CATALOG_PATH.read_text(encoding="utf-8"))
+
+
+def canonical_agents() -> list[dict[str, Any]]:
+ return list(load_agent_catalog().get("agents", []))
+
+
+def canonical_agent_ids() -> list[str]:
+ return [agent["id"] for agent in canonical_agents()]
+
+
+def canonical_agents_by_id() -> dict[str, dict[str, Any]]:
+ return {agent["id"]: agent for agent in canonical_agents()}
+
+
+def canonical_agents_for_api() -> dict[str, dict[str, Any]]:
+ agents: dict[str, dict[str, Any]] = {}
+ for agent in canonical_agents():
+ if not agent.get("public_api_enabled", False):
+ continue
+ agents[agent["id"]] = {
+ "display_name": agent["display_name"],
+ "purpose": agent["purpose"],
+ "owner_repo": agent["owner_repo"],
+ "slug": agent["slug"],
+ "agent_doc": agent["agent_doc"],
+ "github_agent_doc": agent["github_agent_doc"],
+ "start_prompt": agent["start_prompt"],
+ "plan_wizard_prompt": agent["plan_wizard_prompt"],
+ "write_set_roots": agent["write_set_roots"],
+ "rituals": agent["rituals"],
+ "gates": agent["gates"],
+ "handoff_contracts": agent["handoff_contracts"],
+ "evidence_paths": agent["evidence_paths"],
+ "subagents": agent["subagents"],
+ }
+ return agents
+
+
+def legacy_runtime_aliases() -> dict[str, str]:
+ aliases = load_agent_catalog().get("legacy_runtime_aliases", {})
+ return {str(key): str(value) for key, value in aliases.items()}
+
+
+def legacy_runtime_mapping(agent_name: str) -> str | None:
+ return legacy_runtime_aliases().get(agent_name)
+
diff --git a/kill_life/server.py b/kill_life/server.py
index 076f81d..7ab0f36 100644
--- a/kill_life/server.py
+++ b/kill_life/server.py
@@ -12,6 +12,7 @@ from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from kill_life import __version__
+from kill_life.agent_catalog import canonical_agents_by_id, canonical_agents_for_api, legacy_runtime_mapping
MASCARADE_CORE_URL = os.environ.get("MASCARADE_CORE_URL", "http://localhost:8100")
@@ -23,44 +24,6 @@ app = FastAPI(
description="Control plane API for Kill_LIFE embedded systems framework",
)
-# ---------------------------------------------------------------------------
-# Agent definitions (from agents/*.md)
-# ---------------------------------------------------------------------------
-
-BMAD_AGENTS: dict[str, dict[str, str]] = {
- "pm": {
- "name": "PM Agent",
- "file": "agents/pm_agent.md",
- "role": "Project management, intake, prioritization",
- },
- "architect": {
- "name": "Architect Agent",
- "file": "agents/architect_agent.md",
- "role": "System architecture, component design, tech decisions",
- },
- "firmware": {
- "name": "Firmware Agent",
- "file": "agents/firmware_agent.md",
- "role": "Embedded firmware development, HAL, drivers",
- },
- "hw_schematic": {
- "name": "HW Schematic Agent",
- "file": "agents/hw_schematic_agent.md",
- "role": "Hardware schematics, KiCad, PCB layout, BOM",
- },
- "qa": {
- "name": "QA Agent",
- "file": "agents/qa_agent.md",
- "role": "Testing, compliance, evidence collection",
- },
- "doc": {
- "name": "Doc Agent",
- "file": "agents/doc_agent.md",
- "role": "Documentation, runbooks, operator guides",
- },
-}
-
-
def _list_specs() -> list[dict[str, str]]:
"""Scan specs/ directory for markdown files."""
specs_dir = REPO_ROOT / "specs"
@@ -92,7 +55,11 @@ async def health():
@app.get("/agents")
async def list_agents():
- return {"agents": BMAD_AGENTS}
+ agents = canonical_agents_for_api()
+ return {
+ "agents": agents,
+ "count": len(agents),
+ }
@app.get("/specs")
@@ -107,18 +74,33 @@ class AgentRunRequest(BaseModel):
@app.post("/agents/{name}/run")
async def run_agent(name: str, req: AgentRunRequest):
- if name not in BMAD_AGENTS:
- raise HTTPException(status_code=404, detail=f"Agent '{name}' not found. Available: {list(BMAD_AGENTS.keys())}")
+ legacy_target = legacy_runtime_mapping(name)
+ if legacy_target is not None:
+ raise HTTPException(
+ status_code=410,
+ detail={
+ "message": f"Legacy agent '{name}' has been removed from the runtime API.",
+ "legacy_agent": name,
+ "canonical_agent": legacy_target,
+ },
+ )
- agent = BMAD_AGENTS[name]
+ agents = canonical_agents_by_id()
+ if name not in agents:
+ raise HTTPException(
+ status_code=404,
+ detail=f"Agent '{name}' not found. Available: {list(agents.keys())}",
+ )
+
+ agent = agents[name]
# Load agent definition as system prompt
- agent_file = REPO_ROOT / agent["file"]
+ agent_file = REPO_ROOT / agent["agent_doc"]
system_prompt = ""
if agent_file.exists():
- system_prompt = agent_file.read_text()
+ system_prompt = agent_file.read_text(encoding="utf-8")
else:
- system_prompt = f"You are the {agent['name']}. Your role: {agent['role']}."
+ system_prompt = f"You are {agent['display_name']}. Your role: {agent['purpose']}."
# Build messages
messages = [{"role": "user", "content": req.input}]
@@ -155,7 +137,7 @@ async def run_agent(name: str, req: AgentRunRequest):
return {
"agent": name,
- "role": agent["role"],
+ "role": agent["purpose"],
"status": "ok",
"response": result,
}
@@ -186,6 +168,6 @@ async def mascarade_bridge_info():
"integration": {
"specs_as_datasets": "mascarade finetune/ reads Kill_LIFE specs/",
"mcp_tools": "mascarade mcp/client.py loads Kill_LIFE MCP servers",
- "agent_execution": "POST /agents/{name}/run routes through mascarade-core LLM",
+ "agent_execution": "POST /agents/{name}/run routes canonical 2026 agent IDs through mascarade-core LLM",
},
}
diff --git a/kill_life/yiacad_action_registry.py b/kill_life/yiacad_action_registry.py
new file mode 100644
index 0000000..351ad0d
--- /dev/null
+++ b/kill_life/yiacad_action_registry.py
@@ -0,0 +1,78 @@
+"""Shared helpers for the canonical YiACAD action registry."""
+
+from __future__ import annotations
+
+import json
+from functools import lru_cache
+from pathlib import Path
+from typing import Any
+
+REPO_ROOT = Path(__file__).resolve().parent.parent
+ACTION_REGISTRY_PATH = REPO_ROOT / "specs" / "contracts" / "yiacad_action_registry.json"
+INPUT_ARGUMENTS: dict[str, dict[str, str]] = {
+ "source_path": {
+ "flag": "--source-path",
+ "help": "Any project path to infer paired CAD files from",
+ },
+ "board": {
+ "flag": "--board",
+ "help": "Path to .kicad_pcb",
+ },
+ "schematic": {
+ "flag": "--schematic",
+ "help": "Path to .kicad_sch",
+ },
+ "freecad_document": {
+ "flag": "--freecad-document",
+ "help": "Path to .FCStd",
+ },
+ "kibot_config": {
+ "flag": "--kibot-config",
+ "help": "Optional path to a KiBot config for direct exports",
+ },
+}
+
+
+@lru_cache(maxsize=1)
+def load_yiacad_action_registry() -> dict[str, Any]:
+ return json.loads(ACTION_REGISTRY_PATH.read_text(encoding="utf-8"))
+
+
+def yiacad_actions() -> list[dict[str, Any]]:
+ return list(load_yiacad_action_registry().get("actions", []))
+
+
+def yiacad_actions_by_command() -> dict[str, dict[str, Any]]:
+ return {entry["transport_command"]: entry for entry in yiacad_actions()}
+
+
+def get_yiacad_action(command: str) -> dict[str, Any]:
+ try:
+ return yiacad_actions_by_command()[command]
+ except KeyError as exc: # pragma: no cover - defensive callsite guard
+ raise KeyError(f"Unknown YiACAD action registry command: {command}") from exc
+
+
+def yiacad_action_id(command: str) -> str:
+ return str(get_yiacad_action(command)["action_id"])
+
+
+def yiacad_action_inputs(command: str) -> list[str]:
+ return [str(item) for item in get_yiacad_action(command).get("accepted_inputs", [])]
+
+
+def yiacad_actions_for_surface(surface: str) -> list[dict[str, Any]]:
+ return [
+ entry
+ for entry in yiacad_actions()
+ if surface in entry.get("supported_surfaces", [])
+ ]
+
+
+def yiacad_command_for_alias(surface: str, alias: str) -> str | None:
+ lowered = alias.strip().lower()
+ for entry in yiacad_actions_for_surface(surface):
+ aliases = [str(item).strip().lower() for item in entry.get("intent_aliases", [])]
+ if lowered == str(entry["transport_command"]).strip().lower() or lowered in aliases:
+ return str(entry["transport_command"])
+ return None
diff --git a/specs/contracts/agent_catalog.schema.json b/specs/contracts/agent_catalog.schema.json
new file mode 100644
index 0000000..7d5daf0
--- /dev/null
+++ b/specs/contracts/agent_catalog.schema.json
@@ -0,0 +1,173 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://electron-rare.local/specs/contracts/agent_catalog.schema.json",
+ "title": "Kill_LIFE Agent Catalog",
+ "type": "object",
+ "required": [
+ "contract_version",
+ "updated_at",
+ "repo",
+ "agents",
+ "legacy_runtime_aliases"
+ ],
+ "properties": {
+ "contract_version": {
+ "type": "string",
+ "const": "kill-life-agent-catalog/v1"
+ },
+ "updated_at": {
+ "type": "string",
+ "minLength": 1
+ },
+ "repo": {
+ "type": "string",
+ "const": "Kill_LIFE"
+ },
+ "agents": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/$defs/agent"
+ }
+ },
+ "legacy_runtime_aliases": {
+ "type": "object",
+ "minProperties": 1,
+ "additionalProperties": {
+ "type": "string",
+ "minLength": 1
+ }
+ }
+ },
+ "$defs": {
+ "agent": {
+ "type": "object",
+ "required": [
+ "id",
+ "slug",
+ "display_name",
+ "purpose",
+ "owner_repo",
+ "agent_doc",
+ "github_agent_doc",
+ "start_prompt",
+ "plan_wizard_prompt",
+ "write_set_roots",
+ "rituals",
+ "gates",
+ "handoff_contracts",
+ "evidence_paths",
+ "subagents",
+ "public_api_enabled"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "slug": {
+ "type": "string",
+ "pattern": "^[a-z0-9_]+$"
+ },
+ "display_name": {
+ "type": "string",
+ "minLength": 1
+ },
+ "purpose": {
+ "type": "string",
+ "minLength": 1
+ },
+ "owner_repo": {
+ "type": "string",
+ "const": "Kill_LIFE"
+ },
+ "agent_doc": {
+ "type": "string",
+ "minLength": 1
+ },
+ "github_agent_doc": {
+ "type": "string",
+ "minLength": 1
+ },
+ "start_prompt": {
+ "type": "string",
+ "minLength": 1
+ },
+ "plan_wizard_prompt": {
+ "type": "string",
+ "minLength": 1
+ },
+ "write_set_roots": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "rituals": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "gates": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "handoff_contracts": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "evidence_paths": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "subagents": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "$ref": "#/$defs/subagent"
+ }
+ },
+ "public_api_enabled": {
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": true
+ },
+ "subagent": {
+ "type": "object",
+ "required": [
+ "id",
+ "purpose"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "purpose": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "additionalProperties": true
+ }
+ },
+ "additionalProperties": true
+}
diff --git a/specs/contracts/artifact_wms_index_rules.json b/specs/contracts/artifact_wms_index_rules.json
index d336df3..5a6c071 100644
--- a/specs/contracts/artifact_wms_index_rules.json
+++ b/specs/contracts/artifact_wms_index_rules.json
@@ -18,6 +18,7 @@
"match": "kill_life_memory",
"consumer_layer": "MES",
"owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": ["T-RE-261", "T-RE-262", "T-RE-267"],
"purpose": "Canonical execution memory and resume continuity"
},
@@ -38,56 +39,64 @@
{
"match": "mascarade_incident",
"consumer_layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": ["T-RE-233", "T-RE-240", "T-RE-241", "T-RE-245"],
"purpose": "Incident brief, registry, queue, and watch outputs"
},
{
"match": "mascarade_watch",
"consumer_layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": ["T-RE-250", "T-RE-252", "T-RE-257"],
"purpose": "Short watchboard and history artifacts"
},
{
"match": "mascarade_runtime_health",
"consumer_layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"lot_refs": ["T-RE-222", "T-RE-263"],
"purpose": "Mascarade and Ollama runtime health"
},
{
"match": "mascarade_agent_smoke",
"consumer_layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"lot_refs": ["T-RE-*"],
"purpose": "Agent smoke checks and runtime reachability"
},
{
"match": "mesh",
"consumer_layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"lot_refs": ["T-RE-*"],
"purpose": "Mesh, dispatch, and runtime preflight"
},
{
"match": "dataset",
"consumer_layer": "WMS",
- "owner_agent": "Dataset-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Dataset-Curator",
"lot_refs": ["T-MA-015", "T-MS-002", "T-MS-003"],
"purpose": "Dataset audit, preflight, and fine-tune staging"
},
{
"match": "repo_state",
"consumer_layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"lot_refs": ["T-RE-*"],
"purpose": "Repository coherence and state evidence"
},
{
"match": "specs",
"consumer_layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"lot_refs": ["T-RE-*"],
"purpose": "Specification-derived artifacts"
},
@@ -101,7 +110,8 @@
],
"defaults": {
"consumer_layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"lot_refs": [],
"purpose": "Unclassified artifact group"
}
diff --git a/specs/contracts/kill_life_agent_catalog.json b/specs/contracts/kill_life_agent_catalog.json
new file mode 100644
index 0000000..bd80e04
--- /dev/null
+++ b/specs/contracts/kill_life_agent_catalog.json
@@ -0,0 +1,537 @@
+{
+ "$schema": "specs/contracts/agent_catalog.schema.json",
+ "contract_version": "kill-life-agent-catalog/v1",
+ "updated_at": "2026-03-29",
+ "repo": "Kill_LIFE",
+ "agents": [
+ {
+ "id": "PM-Mesh",
+ "slug": "pm_mesh",
+ "display_name": "PM Mesh",
+ "purpose": "Pilot intake, planning, prioritization, and mesh governance handoffs.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/pm_mesh.md",
+ "github_agent_doc": ".github/agents/pm_mesh.md",
+ "start_prompt": ".github/prompts/start_pm_mesh.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_pm_mesh.prompt.md",
+ "write_set_roots": [
+ "specs/",
+ "docs/plans/",
+ ".github/prompts/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/summary_short.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/plans/",
+ "artifacts/cockpit/intelligence_program/"
+ ],
+ "subagents": [
+ {
+ "id": "Plan-Orchestrator",
+ "purpose": "Sequence lots and canonical plans."
+ },
+ {
+ "id": "Intake-Guard",
+ "purpose": "Triage intake and capture goals."
+ },
+ {
+ "id": "Todo-Tracker",
+ "purpose": "Maintain execution-ready task lists."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Arch-Mesh",
+ "slug": "arch_mesh",
+ "display_name": "Arch Mesh",
+ "purpose": "Own architecture, ADRs, and cross-surface contract design.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/arch_mesh.md",
+ "github_agent_doc": ".github/agents/arch_mesh.md",
+ "start_prompt": ".github/prompts/start_arch_mesh.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_arch_mesh.prompt.md",
+ "write_set_roots": [
+ "specs/",
+ "docs/",
+ "kill_life/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/arch/",
+ "docs/"
+ ],
+ "subagents": [
+ {
+ "id": "Contract-Guard",
+ "purpose": "Keep public interfaces versioned and coherent."
+ },
+ {
+ "id": "Mesh-Contracts",
+ "purpose": "Align tri-repo contract boundaries."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Docs-Research",
+ "slug": "docs_research",
+ "display_name": "Docs Research",
+ "purpose": "Maintain canonical documentation, navigation, research summaries, and the agent catalog narrative.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/docs_research.md",
+ "github_agent_doc": ".github/agents/docs_research.md",
+ "start_prompt": ".github/prompts/start_docs_research.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_docs_research.prompt.md",
+ "write_set_roots": [
+ "docs/",
+ "README.md",
+ "README_FR.md",
+ "agents/",
+ ".github/agents/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/",
+ "artifacts/cockpit/"
+ ],
+ "subagents": [
+ {
+ "id": "Doc-Entry",
+ "purpose": "Maintain canonical entry points and onboarding."
+ },
+ {
+ "id": "Plan-Recorder",
+ "purpose": "Capture plan memory and governance deltas."
+ },
+ {
+ "id": "Agent-Catalog",
+ "purpose": "Own agent-facing docs and references."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Runtime-Companion",
+ "slug": "runtime_companion",
+ "display_name": "Runtime Companion",
+ "purpose": "Own runtime AI, MCP alignment, provider bridges, and degraded-safe execution surfaces.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/runtime_companion.md",
+ "github_agent_doc": ".github/agents/runtime_companion.md",
+ "start_prompt": ".github/prompts/start_runtime_companion.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_runtime_companion.prompt.md",
+ "write_set_roots": [
+ "tools/ai/",
+ "tools/ops/",
+ "mcp.json",
+ "specs/mcp_agentics_target_backlog.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/runtime_mcp_ia_gateway.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cockpit/runtime_ai_gateway/",
+ "artifacts/ops/"
+ ],
+ "subagents": [
+ {
+ "id": "MCP-Health",
+ "purpose": "Maintain runtime and MCP summary surfaces."
+ },
+ {
+ "id": "Provider-Bridge",
+ "purpose": "Stabilize provider routing and live smokes."
+ },
+ {
+ "id": "Runtime-Guard",
+ "purpose": "Track runtime health and recovery paths."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "QA-Compliance",
+ "slug": "qa_compliance",
+ "display_name": "QA Compliance",
+ "purpose": "Own tests, compliance gates, schema checks, and release-ready evidence.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/qa_compliance.md",
+ "github_agent_doc": ".github/agents/qa_compliance.md",
+ "start_prompt": ".github/prompts/start_qa_compliance.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_qa_compliance.prompt.md",
+ "write_set_roots": [
+ "test/",
+ "compliance/",
+ ".github/workflows/",
+ "tools/specs/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md",
+ "bmad/gates/gate_s1.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/operator_lane_evidence.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/evidence/",
+ "compliance/evidence/",
+ "artifacts/ci/"
+ ],
+ "subagents": [
+ {
+ "id": "Constraint-Gate",
+ "purpose": "Enforce invariants and compliance rules."
+ },
+ {
+ "id": "Contract-Tests",
+ "purpose": "Keep JSON and CLI contracts stable."
+ },
+ {
+ "id": "Release-Gates",
+ "purpose": "Guard CI, build, and release checks."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Embedded-CAD",
+ "slug": "embedded_cad",
+ "display_name": "Embedded CAD",
+ "purpose": "Own KiCad, FreeCAD, OpenSCAD, hardware assets, and fabrication-facing CAD lanes.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/embedded_cad.md",
+ "github_agent_doc": ".github/agents/embedded_cad.md",
+ "start_prompt": ".github/prompts/start_embedded_cad.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_embedded_cad.prompt.md",
+ "write_set_roots": [
+ "tools/cad/",
+ "tools/hw/",
+ "hardware/",
+ "specs/kicad_mcp_scope_spec.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md",
+ "bmad/gates/gate_s1.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/fab_package.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cad-fusion/",
+ "artifacts/cad-ai-native/",
+ "docs/evidence/"
+ ],
+ "subagents": [
+ {
+ "id": "CAD-Bridge",
+ "purpose": "Bridge KiCad, FreeCAD, and MCP lanes."
+ },
+ {
+ "id": "HW-BOM",
+ "purpose": "Own fabrication and BOM-oriented outputs."
+ },
+ {
+ "id": "CAD-Fusion",
+ "purpose": "Run the YiACAD AI-native fusion lot."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Web-CAD-Platform",
+ "slug": "web_cad_platform",
+ "display_name": "Web CAD Platform",
+ "purpose": "Own the YiACAD web product, project read models, queue/workers, realtime, and review surfaces.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/web_cad_platform.md",
+ "github_agent_doc": ".github/agents/web_cad_platform.md",
+ "start_prompt": ".github/prompts/start_web_cad_platform.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_web_cad_platform.prompt.md",
+ "write_set_roots": [
+ "web/app/",
+ "web/components/",
+ "web/lib/",
+ "web/realtime/",
+ "web/workers/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/summary_short.schema.json",
+ "specs/contracts/yiacad_uiux_output.schema.json"
+ ],
+ "evidence_paths": [
+ "web/project/.ci/",
+ "artifacts/cockpit/intelligence_program/"
+ ],
+ "subagents": [
+ {
+ "id": "Project-Service",
+ "purpose": "Own GraphQL and project metadata."
+ },
+ {
+ "id": "EDA-CI-Orchestrator",
+ "purpose": "Own queue, workers, and EDA job orchestration."
+ },
+ {
+ "id": "Realtime-Collab",
+ "purpose": "Own Yjs transport and persistence."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "UX-Lead",
+ "slug": "ux_lead",
+ "display_name": "UX Lead",
+ "purpose": "Own UI/UX direction for YiACAD and user-facing design systems.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/ux_lead.md",
+ "github_agent_doc": ".github/agents/ux_lead.md",
+ "start_prompt": ".github/prompts/start_ux_lead.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_ux_lead.prompt.md",
+ "write_set_roots": [
+ "docs/YIACAD_",
+ "docs/CAD_AI_NATIVE_",
+ "specs/yiacad_uiux_apple_native_spec.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/uiux_tui/",
+ "docs/"
+ ],
+ "subagents": [
+ {
+ "id": "Apple-HIG",
+ "purpose": "Carry Apple-native design constraints."
+ },
+ {
+ "id": "CAD-UX",
+ "purpose": "Design CAD-specific interaction flows."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Firmware",
+ "slug": "firmware",
+ "display_name": "Firmware",
+ "purpose": "Own embedded firmware, PlatformIO flows, and firmware evidence.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/firmware.md",
+ "github_agent_doc": ".github/agents/firmware.md",
+ "start_prompt": ".github/prompts/start_firmware.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_firmware.prompt.md",
+ "write_set_roots": [
+ "firmware/",
+ "specs/zeroclaw_dual_hw_todo.md"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s1.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/operator_lane_evidence.schema.json"
+ ],
+ "evidence_paths": [
+ "docs/evidence/esp/",
+ "docs/evidence/linux/"
+ ],
+ "subagents": [
+ {
+ "id": "FW-Build",
+ "purpose": "Drive builds, tests, and firmware-ready outputs."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "SyncOps",
+ "slug": "sync_ops",
+ "display_name": "Sync Ops",
+ "purpose": "Own cockpit scripts, operator lanes, SSH convergence, logs, and runbook execution.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/sync_ops.md",
+ "github_agent_doc": ".github/agents/sync_ops.md",
+ "start_prompt": ".github/prompts/start_sync_ops.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_sync_ops.prompt.md",
+ "write_set_roots": [
+ "tools/cockpit/",
+ "artifacts/cockpit/",
+ "docs/FULL_OPERATOR_LANE_"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/operator_lane_evidence.schema.json",
+ "specs/contracts/agent_handoff.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cockpit/",
+ "docs/evidence/"
+ ],
+ "subagents": [
+ {
+ "id": "TUI-Ops",
+ "purpose": "Maintain shell/TUI operator entry points."
+ },
+ {
+ "id": "Log-Ops",
+ "purpose": "Own log summaries, purges, and incident outputs."
+ },
+ {
+ "id": "Ops-Governor",
+ "purpose": "Track operator governance and capacity views."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "Schema-Guard",
+ "slug": "schema_guard",
+ "display_name": "Schema Guard",
+ "purpose": "Own machine-readable contracts, schema versioning, and strict validation.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/schema_guard.md",
+ "github_agent_doc": ".github/agents/schema_guard.md",
+ "start_prompt": ".github/prompts/start_schema_guard.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_schema_guard.prompt.md",
+ "write_set_roots": [
+ "specs/contracts/",
+ "tools/specs/",
+ "tools/validate_specs.py"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/workflow_handshake.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/ci/",
+ "specs/contracts/examples/"
+ ],
+ "subagents": [
+ {
+ "id": "Handoff-Schema",
+ "purpose": "Maintain handoff schema compatibility."
+ },
+ {
+ "id": "Evidence-Schema",
+ "purpose": "Guard evidence contracts and summaries."
+ }
+ ],
+ "public_api_enabled": true
+ },
+ {
+ "id": "KillLife-Bridge",
+ "slug": "kill_life_bridge",
+ "display_name": "Kill Life Bridge",
+ "purpose": "Own cross-repo bridge contracts, continuity memory, and consumer-facing bridge lanes.",
+ "owner_repo": "Kill_LIFE",
+ "agent_doc": "agents/kill_life_bridge.md",
+ "github_agent_doc": ".github/agents/kill_life_bridge.md",
+ "start_prompt": ".github/prompts/start_kill_life_bridge.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_kill_life_bridge.prompt.md",
+ "write_set_roots": [
+ "docs/TRI_REPO_MESH_CONTRACT_",
+ "specs/mesh_contracts.md",
+ "artifacts/cockpit/kill_life_memory/"
+ ],
+ "rituals": [
+ "bmad/rituals/kickoff.md"
+ ],
+ "gates": [
+ "bmad/gates/gate_s0.md"
+ ],
+ "handoff_contracts": [
+ "specs/contracts/agent_handoff.schema.json",
+ "specs/contracts/workflow_handshake.schema.json"
+ ],
+ "evidence_paths": [
+ "artifacts/cockpit/kill_life_memory/",
+ "artifacts/cockpit/product_contract_handoff/"
+ ],
+ "subagents": [
+ {
+ "id": "Schema-Consumer",
+ "purpose": "Translate shared contracts into consumer-facing surfaces."
+ },
+ {
+ "id": "Artifact-Curator",
+ "purpose": "Maintain continuity memory and artifact-facing bridge rules."
+ }
+ ],
+ "public_api_enabled": true
+ }
+ ],
+ "legacy_runtime_aliases": {
+ "pm": "PM-Mesh",
+ "architect": "Arch-Mesh",
+ "firmware": "Firmware",
+ "hw_schematic": "Embedded-CAD",
+ "qa": "QA-Compliance",
+ "doc": "Docs-Research"
+ }
+}
diff --git a/specs/contracts/ops_kill_life_erp_registry.json b/specs/contracts/ops_kill_life_erp_registry.json
index f8d3fc6..ddf2445 100644
--- a/specs/contracts/ops_kill_life_erp_registry.json
+++ b/specs/contracts/ops_kill_life_erp_registry.json
@@ -15,13 +15,15 @@
{
"id": "PLM",
"name": "Product Lifecycle Management",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"purpose": "Specifications, plans, contracts, diagrams, design memory"
},
{
"id": "ERP",
"name": "L'electronrare Ops",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"purpose": "Resource governance, machine allocation, ownership, secret scopes, priorities"
},
{
@@ -33,13 +35,15 @@
{
"id": "WMS",
"name": "Artifacts and Operational Memory",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"purpose": "Artifacts, logs, datasets, handoff outputs, retention"
},
{
"id": "DCS",
"name": "Runtime and Mesh Control",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"purpose": "Runtimes, SSH mesh, dispatch P2P, health checks, load balancing"
}
],
@@ -51,7 +55,8 @@
"runtime_roots": ["/home/clems/mascarade", "/home/clems/mascarade-main"],
"role": "primary-heavy",
"criticality": "high",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"priority_order": 1,
"load_policy": "prefer-heavy",
"secret_scope": ["router", "governance"],
@@ -64,7 +69,8 @@
"runtime_roots": ["/root/mascarade-main"],
"role": "vm-governance-reserve",
"criticality": "medium",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"priority_order": 5,
"load_policy": "avoid-heavy",
"secret_scope": ["router", "governance"],
@@ -77,7 +83,8 @@
"runtime_roots": ["/home/kxkm/mascarade", "/home/kxkm/mascarade-main"],
"role": "interactive-ai",
"criticality": "high",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"priority_order": 2,
"load_policy": "secondary-heavy",
"secret_scope": ["router", "governance"],
@@ -90,7 +97,8 @@
"runtime_roots": ["/Users/cils/mascarade-main", "/Users/cils/mascarade"],
"role": "non-essential-burst",
"criticality": "medium",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"priority_order": 3,
"load_policy": "burst-only",
"secret_scope": ["router", "governance"],
@@ -114,7 +122,8 @@
{
"name": "router",
"env_var": "MISTRAL_API_KEY",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"consumer_scope": "Mascarade router and Studio integrations",
"storage": [
"/Users/electron/Documents/Projets/mascarade/.env",
@@ -125,6 +134,7 @@
"name": "governance",
"env_var": "MISTRAL_GOVERNANCE_API_KEY",
"owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Schema-Consumer",
"consumer_scope": "Kill_LIFE governance scripts and Mistral operator tooling",
"storage": [
"~/.kill-life/mistral.env",
@@ -136,13 +146,15 @@
{
"path": "docs/",
"layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"purpose": "Plans, contracts, diagrams, research"
},
{
"path": "specs/",
"layer": "PLM",
- "owner_agent": "PLM-Archivist",
+ "owner_agent": "Docs-Research",
+ "owner_subagent": "PLM-Archivist",
"purpose": "Task backlog, contracts, structured specifications"
},
{
@@ -166,43 +178,50 @@
{
"path": "tools/cockpit/machine_registry.sh",
"layer": "ERP",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"purpose": "Machine and capacity registry"
},
{
"path": "tools/cockpit/mascarade_mesh_env_sync.sh",
"layer": "ERP",
- "owner_agent": "Ops-Governor",
+ "owner_agent": "PM-Mesh",
+ "owner_subagent": "Ops-Governor",
"purpose": "Router secret propagation"
},
{
"path": "tools/cockpit/kill_life_mistral_governance_sync.sh",
"layer": "ERP",
"owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Schema-Consumer",
"purpose": "Governance secret propagation"
},
{
"path": "artifacts/",
"layer": "WMS",
- "owner_agent": "Artifact-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Artifact-Curator",
"purpose": "Artifacts, logs, reports, watchboards"
},
{
"path": "tools/cockpit/dataset_audit_tui.sh",
"layer": "WMS",
- "owner_agent": "Dataset-Curator",
+ "owner_agent": "KillLife-Bridge",
+ "owner_subagent": "Dataset-Curator",
"purpose": "Dataset audit and preflight"
},
{
"path": "tools/cockpit/mesh_sync_preflight.sh",
"layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"purpose": "Mesh preflight and dispatch readiness"
},
{
"path": "tools/cockpit/mascarade_runtime_health.sh",
"layer": "DCS",
- "owner_agent": "Runtime-Guard",
+ "owner_agent": "Runtime-Companion",
+ "owner_subagent": "Runtime-Guard",
"purpose": "Mascarade and Ollama runtime health"
}
]
diff --git a/specs/contracts/pcb_ai_fab_registry.json b/specs/contracts/pcb_ai_fab_registry.json
index f748838..b5c4a49 100644
--- a/specs/contracts/pcb_ai_fab_registry.json
+++ b/specs/contracts/pcb_ai_fab_registry.json
@@ -13,7 +13,7 @@
"kind": "saas-eda-fast-fab",
"status": "evaluate-fast-fab",
"owner_agent": "Embedded-CAD",
- "sub_agent": "HW-BOM",
+ "owner_subagent": "HW-BOM",
"inputs": ["schematic", "kicad-import", "altium-import"],
"outputs": ["gerber", "odb++", "kicad", "altium", "bom"],
"strengths": [
@@ -41,8 +41,8 @@
"name": "Quilter",
"kind": "physics-driven-routing-engine",
"status": "canary-route",
- "owner_agent": "CAD-Bridge",
- "sub_agent": "CAD-Smoke",
+ "owner_agent": "Embedded-CAD",
+ "owner_subagent": "CAD-Bridge",
"inputs": ["schematic", "board-file", "project-file"],
"outputs": ["same-cad-format", "layout-candidates", "constraint-review"],
"strengths": [
@@ -71,8 +71,8 @@
"name": "kicad-happy",
"kind": "open-source-claude-skills",
"status": "adopt-patterns",
- "owner_agent": "HW-BOM",
- "sub_agent": "Forge",
+ "owner_agent": "Embedded-CAD",
+ "owner_subagent": "HW-BOM",
"inputs": ["kicad-sch", "kicad-pcb", "gerbers", "bom"],
"outputs": ["reviews", "pricing", "supplier-orders", "jlcpcb-order-files"],
"strengths": [
diff --git a/specs/contracts/yiacad_action_registry.json b/specs/contracts/yiacad_action_registry.json
new file mode 100644
index 0000000..3ebc76e
--- /dev/null
+++ b/specs/contracts/yiacad_action_registry.json
@@ -0,0 +1,232 @@
+{
+ "contract_version": "yiacad-action-registry/v1",
+ "component": "yiacad-action-registry",
+ "description": "Canonical internal registry for YiACAD transport commands, normalized action identifiers, supported surfaces, and engine boundaries.",
+ "actions": [
+ {
+ "transport_command": "status",
+ "action_id": "status.surface",
+ "display_name": "YiACAD Status",
+ "description": "Expose YiACAD runtime, engine, and artifact status for the current surface.",
+ "supported_surfaces": [
+ "yiacad-api",
+ "yiacad-web",
+ "yiacad-desktop",
+ "yiacad-kicad",
+ "yiacad-freecad",
+ "tui"
+ ],
+ "required_inputs": [],
+ "accepted_inputs": [
+ "source_path"
+ ],
+ "engine_families": [
+ "kicad",
+ "freecad",
+ "kibot",
+ "kiauto"
+ ],
+ "produced_artifacts": [
+ "context.json",
+ "uiux_output.json"
+ ],
+ "default_next_steps": [
+ "open artifacts",
+ "inspect engine status"
+ ],
+ "intent_aliases": [
+ "status",
+ "surface-status"
+ ],
+ "native_handler": "command_status"
+ },
+ {
+ "transport_command": "kicad-erc-drc",
+ "action_id": "review.erc_drc",
+ "display_name": "YiACAD ERC/DRC",
+ "description": "Run KiCad ERC and DRC through the YiACAD backend boundary.",
+ "supported_surfaces": [
+ "yiacad-api",
+ "yiacad-web",
+ "yiacad-desktop",
+ "yiacad-kicad",
+ "tui"
+ ],
+ "required_inputs": [],
+ "accepted_inputs": [
+ "source_path",
+ "board",
+ "schematic"
+ ],
+ "engine_families": [
+ "kicad"
+ ],
+ "produced_artifacts": [
+ "context.json",
+ "uiux_output.json",
+ "erc.json",
+ "drc.json",
+ "summary.md"
+ ],
+ "default_next_steps": [
+ "open review artifacts",
+ "rerun review with a loaded KiCad project"
+ ],
+ "intent_aliases": [
+ "board-review",
+ "erc-drc-assist"
+ ],
+ "native_handler": "command_kicad_erc_drc"
+ },
+ {
+ "transport_command": "bom-review",
+ "action_id": "review.bom",
+ "display_name": "YiACAD BOM Review",
+ "description": "Export and summarize a BOM through the YiACAD backend boundary.",
+ "supported_surfaces": [
+ "yiacad-api",
+ "yiacad-web",
+ "yiacad-desktop",
+ "yiacad-kicad",
+ "tui"
+ ],
+ "required_inputs": [],
+ "accepted_inputs": [
+ "source_path",
+ "schematic"
+ ],
+ "engine_families": [
+ "kicad"
+ ],
+ "produced_artifacts": [
+ "context.json",
+ "uiux_output.json",
+ "bom.csv",
+ "summary.md"
+ ],
+ "default_next_steps": [
+ "open BOM artifacts",
+ "rerun review with a schematic"
+ ],
+ "intent_aliases": [
+ "bom-footprint-audit"
+ ],
+ "native_handler": "command_bom_review"
+ },
+ {
+ "transport_command": "ecad-mcad-sync",
+ "action_id": "sync.ecad_mcad",
+ "display_name": "YiACAD ECAD/MCAD Sync",
+ "description": "Export KiCad and FreeCAD sync artifacts through the YiACAD backend boundary.",
+ "supported_surfaces": [
+ "yiacad-api",
+ "yiacad-web",
+ "yiacad-desktop",
+ "yiacad-kicad",
+ "yiacad-freecad",
+ "tui"
+ ],
+ "required_inputs": [],
+ "accepted_inputs": [
+ "source_path",
+ "board",
+ "schematic",
+ "freecad_document"
+ ],
+ "engine_families": [
+ "kicad",
+ "freecad"
+ ],
+ "produced_artifacts": [
+ "context.json",
+ "uiux_output.json",
+ "summary.md",
+ "step"
+ ],
+ "default_next_steps": [
+ "open sync artifacts",
+ "rerun with both ECAD and MCAD files present"
+ ],
+ "intent_aliases": [
+ "ecad-mcad-sync",
+ "step-export-review"
+ ],
+ "native_handler": "command_ecad_mcad_sync"
+ },
+ {
+ "transport_command": "manufacturing-package",
+ "action_id": "manufacturing.export",
+ "display_name": "YiACAD Manufacturing Package",
+ "description": "Build a manufacturing package behind the YiACAD backend boundary.",
+ "supported_surfaces": [
+ "yiacad-api",
+ "yiacad-web",
+ "yiacad-desktop",
+ "yiacad-freecad",
+ "tui"
+ ],
+ "required_inputs": [],
+ "accepted_inputs": [
+ "source_path",
+ "board",
+ "schematic",
+ "kibot_config"
+ ],
+ "engine_families": [
+ "kicad",
+ "kibot"
+ ],
+ "produced_artifacts": [
+ "context.json",
+ "uiux_output.json",
+ "summary.md",
+ "fabrication_outputs"
+ ],
+ "default_next_steps": [
+ "inspect fabrication artifacts",
+ "rerun with a valid KiBot recipe"
+ ],
+ "intent_aliases": [
+ "manufacturing-package"
+ ],
+ "native_handler": "command_manufacturing_package"
+ },
+ {
+ "transport_command": "kiauto-checks",
+ "action_id": "manufacturing.validate",
+ "display_name": "YiACAD KiAuto Checks",
+ "description": "Run KiAuto validation behind the YiACAD backend boundary.",
+ "supported_surfaces": [
+ "yiacad-api",
+ "yiacad-web",
+ "yiacad-desktop",
+ "yiacad-freecad",
+ "tui"
+ ],
+ "required_inputs": [],
+ "accepted_inputs": [
+ "source_path",
+ "board",
+ "schematic"
+ ],
+ "engine_families": [
+ "kicad",
+ "kiauto"
+ ],
+ "produced_artifacts": [
+ "context.json",
+ "uiux_output.json",
+ "summary.md",
+ "validation_logs"
+ ],
+ "default_next_steps": [
+ "inspect validation logs",
+ "rerun with a resolved board and schematic"
+ ],
+ "intent_aliases": [
+ "kiauto-checks"
+ ],
+ "native_handler": "command_kiauto_checks"
+ }
+ ]
+}
diff --git a/specs/contracts/yiacad_action_registry.schema.json b/specs/contracts/yiacad_action_registry.schema.json
new file mode 100644
index 0000000..43a51b8
--- /dev/null
+++ b/specs/contracts/yiacad_action_registry.schema.json
@@ -0,0 +1,116 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://kill-life.dev/specs/contracts/yiacad_action_registry.schema.json",
+ "title": "YiACAD Action Registry",
+ "type": "object",
+ "required": [
+ "contract_version",
+ "component",
+ "actions"
+ ],
+ "properties": {
+ "contract_version": {
+ "const": "yiacad-action-registry/v1"
+ },
+ "component": {
+ "const": "yiacad-action-registry"
+ },
+ "description": {
+ "type": "string"
+ },
+ "actions": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "object",
+ "required": [
+ "transport_command",
+ "action_id",
+ "display_name",
+ "description",
+ "supported_surfaces",
+ "required_inputs",
+ "accepted_inputs",
+ "engine_families",
+ "produced_artifacts",
+ "default_next_steps",
+ "native_handler"
+ ],
+ "properties": {
+ "transport_command": {
+ "type": "string",
+ "minLength": 1
+ },
+ "action_id": {
+ "type": "string",
+ "minLength": 1
+ },
+ "display_name": {
+ "type": "string",
+ "minLength": 1
+ },
+ "description": {
+ "type": "string",
+ "minLength": 1
+ },
+ "supported_surfaces": {
+ "type": "array",
+ "minItems": 1,
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "required_inputs": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "accepted_inputs": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "engine_families": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "produced_artifacts": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "default_next_steps": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "intent_aliases": {
+ "type": "array",
+ "items": {
+ "type": "string",
+ "minLength": 1
+ }
+ },
+ "native_handler": {
+ "type": "string",
+ "pattern": "^command_[a-z0-9_]+$"
+ }
+ },
+ "additionalProperties": true
+ }
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/test/test_agent_catalog_validator.py b/test/test_agent_catalog_validator.py
new file mode 100644
index 0000000..7d64f65
--- /dev/null
+++ b/test/test_agent_catalog_validator.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import importlib.util
+import json
+import shutil
+import sys
+import tempfile
+import unittest
+from pathlib import Path
+
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+VALIDATOR_PATH = REPO_ROOT / "tools" / "specs" / "validate_agent_catalog.py"
+CATALOG_PATH = REPO_ROOT / "specs" / "contracts" / "kill_life_agent_catalog.json"
+
+
+def load_validator():
+ spec = importlib.util.spec_from_file_location("validate_agent_catalog", VALIDATOR_PATH)
+ module = importlib.util.module_from_spec(spec)
+ assert spec is not None and spec.loader is not None
+ sys.modules[spec.name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+VALIDATOR = load_validator()
+CATALOG = json.loads(CATALOG_PATH.read_text(encoding="utf-8"))
+
+
+class AgentCatalogValidatorTests(unittest.TestCase):
+ def build_temp_repo(self) -> Path:
+ temp_root = Path(tempfile.mkdtemp(prefix="kill-life-agent-catalog-"))
+ shutil.copytree(REPO_ROOT / "specs" / "contracts", temp_root / "specs" / "contracts")
+
+ for rel_path in (
+ "README.md",
+ "README_FR.md",
+ "docs/plans/12_plan_gestion_des_agents.md",
+ "docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md",
+ "tools/autonomous_next_lots.py",
+ "tools/cad/yiacad_fusion_lot.sh",
+ "tools/cockpit/intelligence_tui.sh",
+ "tools/cockpit/runtime_ai_gateway.sh",
+ ):
+ source = REPO_ROOT / rel_path
+ destination = temp_root / rel_path
+ destination.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(source, destination)
+
+ for agent in CATALOG["agents"]:
+ for rel_path in (
+ agent["agent_doc"],
+ agent["github_agent_doc"],
+ agent["start_prompt"],
+ agent["plan_wizard_prompt"],
+ ):
+ source = REPO_ROOT / rel_path
+ destination = temp_root / rel_path
+ destination.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(source, destination)
+
+ return temp_root
+
+ def validate_temp_repo(self, repo_root: Path) -> dict:
+ return VALIDATOR.validate_agent_catalog(
+ repo_root=repo_root,
+ catalog_path=repo_root / "specs" / "contracts" / "kill_life_agent_catalog.json",
+ )
+
+ def test_missing_prompt_is_reported(self) -> None:
+ repo_root = self.build_temp_repo()
+ try:
+ (repo_root / ".github" / "prompts" / "start_pm_mesh.prompt.md").unlink()
+ payload = self.validate_temp_repo(repo_root)
+ finally:
+ shutil.rmtree(repo_root)
+
+ self.assertFalse(payload["ok"])
+ self.assertIn("catalog-missing-files", payload["errors"])
+ self.assertIn(".github/prompts/start_pm_mesh.prompt.md", payload["missing_files"])
+
+ def test_missing_agent_doc_is_reported(self) -> None:
+ repo_root = self.build_temp_repo()
+ try:
+ (repo_root / "agents" / "pm_mesh.md").unlink()
+ payload = self.validate_temp_repo(repo_root)
+ finally:
+ shutil.rmtree(repo_root)
+
+ self.assertFalse(payload["ok"])
+ self.assertIn("catalog-missing-files", payload["errors"])
+ self.assertIn("agents/pm_mesh.md", payload["missing_files"])
+
+ def test_readme_drift_is_reported(self) -> None:
+ repo_root = self.build_temp_repo()
+ try:
+ readme = repo_root / "README.md"
+ readme.write_text(readme.read_text(encoding="utf-8").replace("KillLife-Bridge", "KillLifeBridge", 1), encoding="utf-8")
+ payload = self.validate_temp_repo(repo_root)
+ finally:
+ shutil.rmtree(repo_root)
+
+ self.assertFalse(payload["ok"])
+ self.assertIn("catalog-doc-drift", payload["errors"])
+ self.assertTrue(any(item["path"] == "README.md" for item in payload["docs_missing_agents"]))
+
+ def test_matrix_drift_is_reported(self) -> None:
+ repo_root = self.build_temp_repo()
+ try:
+ matrix = repo_root / "docs" / "AGENT_SPEC_MODULE_MATRIX_2026-03-20.md"
+ matrix.write_text(matrix.read_text(encoding="utf-8").replace("Schema-Guard", "SchemaGuard"), encoding="utf-8")
+ payload = self.validate_temp_repo(repo_root)
+ finally:
+ shutil.rmtree(repo_root)
+
+ self.assertFalse(payload["ok"])
+ self.assertIn("catalog-doc-drift", payload["errors"])
+ self.assertTrue(
+ any(item["path"] == "docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md" for item in payload["docs_missing_agents"])
+ )
+
+ def test_invalid_owner_agent_is_reported(self) -> None:
+ repo_root = self.build_temp_repo()
+ try:
+ registry_path = repo_root / "specs" / "contracts" / "ops_kill_life_erp_registry.json"
+ registry = json.loads(registry_path.read_text(encoding="utf-8"))
+ registry["layers"][0]["owner_agent"] = "Not-A-Real-Agent"
+ registry_path.write_text(json.dumps(registry, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
+ payload = self.validate_temp_repo(repo_root)
+ finally:
+ shutil.rmtree(repo_root)
+
+ self.assertFalse(payload["ok"])
+ self.assertIn("catalog-invalid-owner-agent", payload["errors"])
+ self.assertTrue(any(item["owner_agent"] == "Not-A-Real-Agent" for item in payload["invalid_owner_refs"]))
+
+ def test_new_catalog_agent_requires_matching_files(self) -> None:
+ repo_root = self.build_temp_repo()
+ try:
+ catalog_path = repo_root / "specs" / "contracts" / "kill_life_agent_catalog.json"
+ catalog = json.loads(catalog_path.read_text(encoding="utf-8"))
+ extra_agent = dict(catalog["agents"][0])
+ extra_agent.update(
+ {
+ "id": "Ops-Preview",
+ "slug": "ops_preview",
+ "display_name": "Ops Preview",
+ "agent_doc": "agents/ops_preview.md",
+ "github_agent_doc": ".github/agents/ops_preview.md",
+ "start_prompt": ".github/prompts/start_ops_preview.prompt.md",
+ "plan_wizard_prompt": ".github/prompts/plan_wizard_ops_preview.prompt.md",
+ }
+ )
+ catalog["agents"].append(extra_agent)
+ catalog_path.write_text(json.dumps(catalog, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
+ payload = self.validate_temp_repo(repo_root)
+ finally:
+ shutil.rmtree(repo_root)
+
+ self.assertFalse(payload["ok"])
+ self.assertIn("catalog-missing-files", payload["errors"])
+ self.assertIn("agents/ops_preview.md", payload["missing_files"])
+ self.assertIn(".github/agents/ops_preview.md", payload["missing_files"])
+ self.assertIn(".github/prompts/start_ops_preview.prompt.md", payload["missing_files"])
+ self.assertIn(".github/prompts/plan_wizard_ops_preview.prompt.md", payload["missing_files"])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_autonomous_next_lots_catalog.py b/test/test_autonomous_next_lots_catalog.py
new file mode 100644
index 0000000..bd08a6c
--- /dev/null
+++ b/test/test_autonomous_next_lots_catalog.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import importlib.util
+import sys
+import unittest
+from pathlib import Path
+
+from kill_life.agent_catalog import canonical_agent_ids
+
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+SCRIPT_PATH = REPO_ROOT / "tools" / "autonomous_next_lots.py"
+CANONICAL_AGENT_IDS = set(canonical_agent_ids())
+
+
+def load_module():
+ spec = importlib.util.spec_from_file_location("autonomous_next_lots", SCRIPT_PATH)
+ module = importlib.util.module_from_spec(spec)
+ assert spec is not None and spec.loader is not None
+ sys.modules[spec.name] = module
+ spec.loader.exec_module(module)
+ return module
+
+
+AUTONOMOUS_NEXT_LOTS = load_module()
+
+
+class AutonomousNextLotsCatalogTests(unittest.TestCase):
+ def test_all_declared_lot_owners_are_canonical(self) -> None:
+ self.assertGreaterEqual(len(AUTONOMOUS_NEXT_LOTS.LOTS), 1)
+ for lot in AUTONOMOUS_NEXT_LOTS.LOTS:
+ with self.subTest(lot=lot.key):
+ self.assertIn(lot.owner_agent, CANONICAL_AGENT_IDS)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_intelligence_tui_contract.py b/test/test_intelligence_tui_contract.py
index aa4dd64..b79e4ba 100644
--- a/test/test_intelligence_tui_contract.py
+++ b/test/test_intelligence_tui_contract.py
@@ -7,9 +7,12 @@ import tempfile
import unittest
from pathlib import Path
+from kill_life.agent_catalog import canonical_agent_ids
+
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPT = REPO_ROOT / "tools" / "cockpit" / "intelligence_tui.sh"
LEGACY_SCRIPT = REPO_ROOT / "tools" / "cockpit" / "intelligence_program_tui.sh"
+CANONICAL_AGENT_IDS = set(canonical_agent_ids())
class IntelligenceTuiContractTests(unittest.TestCase):
@@ -79,6 +82,10 @@ class IntelligenceTuiContractTests(unittest.TestCase):
set(persisted["intelligence_views"].keys()),
{"scorecard", "comparison", "recommendations"},
)
+ probes = persisted["web_platform_health"]["probes"]
+ self.assertIn("worker", probes)
+ self.assertIn("queue", probes)
+ self.assertIn(probes["worker"]["status"], {"up", "degraded", "down", "unknown"})
def test_status_is_repo_root_stable_outside_repo_cwd(self) -> None:
with tempfile.TemporaryDirectory() as tmp_dir:
@@ -109,6 +116,8 @@ class IntelligenceTuiContractTests(unittest.TestCase):
self.assertIn("overall_maturity_score", payload)
self.assertIn("lane_maturity", payload)
self.assertGreaterEqual(len(payload["lane_maturity"]), 4)
+ for lane in payload["lane_maturity"]:
+ self.assertIn(lane["owner_agent"], CANONICAL_AGENT_IDS)
lane_names = {lane["lane"] for lane in payload["lane_maturity"]}
self.assertIn("Program-Governance", lane_names)
self.assertIn("Contracts", lane_names)
@@ -142,6 +151,8 @@ class IntelligenceTuiContractTests(unittest.TestCase):
self.assertEqual(payload["action"], "recommendations")
queue = payload["queue"]
self.assertGreaterEqual(len(queue), 5)
+ for item in queue:
+ self.assertIn(item["owner_agent"], CANONICAL_AGENT_IDS)
self.assertEqual(queue[0]["id"], "AI-RQ-101")
priorities = [item["priority"] for item in queue]
self.assertEqual(priorities, sorted(priorities, key=lambda value: {"P0": 0, "P1": 1, "P2": 2}[value]))
@@ -174,6 +185,7 @@ class IntelligenceTuiContractTests(unittest.TestCase):
self.assertEqual(payload["action"], "summary-short")
self.assertEqual(payload["owner_repo"], "Kill_LIFE")
self.assertEqual(payload["owner_agent"], "PM-Mesh")
+ self.assertIn(payload["owner_agent"], CANONICAL_AGENT_IDS)
self.assertEqual(payload["owner_subagent"], "Plan-Orchestrator")
self.assertIn(payload["status"], {"ready", "degraded", "blocked"})
self.assertIsInstance(payload["write_set"], list)
diff --git a/test/test_kill_life_agent_api.py b/test/test_kill_life_agent_api.py
new file mode 100644
index 0000000..ea914c5
--- /dev/null
+++ b/test/test_kill_life_agent_api.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import unittest
+from unittest.mock import patch
+
+from fastapi.testclient import TestClient
+
+from kill_life.agent_catalog import canonical_agent_ids, legacy_runtime_aliases
+from kill_life.server import app
+
+
+class DummyResponse:
+ def __init__(self, payload: dict):
+ self._payload = payload
+
+ def raise_for_status(self) -> None:
+ return None
+
+ def json(self) -> dict:
+ return self._payload
+
+
+class DummyAsyncClient:
+ last_url: str | None = None
+ last_payload: dict | None = None
+
+ def __init__(self, *args, **kwargs) -> None:
+ self.timeout = kwargs.get("timeout")
+
+ async def __aenter__(self):
+ return self
+
+ async def __aexit__(self, exc_type, exc, tb):
+ return False
+
+ async def post(self, url: str, json: dict) -> DummyResponse:
+ DummyAsyncClient.last_url = url
+ DummyAsyncClient.last_payload = json
+ return DummyResponse({"reply": "ok"})
+
+
+class KillLifeAgentApiTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.client = TestClient(app)
+
+ def test_list_agents_uses_canonical_catalog(self) -> None:
+ response = self.client.get("/agents")
+ self.assertEqual(response.status_code, 200)
+ payload = response.json()
+ self.assertEqual(payload["count"], 12)
+ self.assertEqual(set(payload["agents"].keys()), set(canonical_agent_ids()))
+
+ def test_legacy_agents_return_gone_with_migration_mapping(self) -> None:
+ for legacy_id, canonical_id in legacy_runtime_aliases().items():
+ with self.subTest(legacy_id=legacy_id):
+ response = self.client.post(f"/agents/{legacy_id}/run", json={"input": "hello"})
+ self.assertEqual(response.status_code, 410)
+ detail = response.json()["detail"]
+ self.assertEqual(detail["legacy_agent"], legacy_id)
+ self.assertEqual(detail["canonical_agent"], canonical_id)
+ self.assertIn("removed from the runtime API", detail["message"])
+
+ def test_canonical_agent_run_routes_through_mascarade(self) -> None:
+ with patch("kill_life.server.httpx.AsyncClient", DummyAsyncClient):
+ response = self.client.post(
+ "/agents/PM-Mesh/run",
+ json={"input": "Plan the next lot", "context": {"lot_id": "T-123"}},
+ )
+
+ self.assertEqual(response.status_code, 200)
+ payload = response.json()
+ self.assertEqual(payload["agent"], "PM-Mesh")
+ self.assertEqual(payload["status"], "ok")
+ self.assertEqual(payload["response"], {"reply": "ok"})
+ self.assertTrue(DummyAsyncClient.last_url and DummyAsyncClient.last_url.endswith("/v1/send"))
+ self.assertIsNotNone(DummyAsyncClient.last_payload)
+ assert DummyAsyncClient.last_payload is not None
+ self.assertEqual(DummyAsyncClient.last_payload["routing_policy"], "auto")
+ self.assertIn("Plan the next lot", DummyAsyncClient.last_payload["messages"][0]["content"])
+ self.assertIn('"lot_id": "T-123"', DummyAsyncClient.last_payload["messages"][0]["content"])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_runtime_ai_gateway_contract.py b/test/test_runtime_ai_gateway_contract.py
index 8b05ecb..18483c8 100644
--- a/test/test_runtime_ai_gateway_contract.py
+++ b/test/test_runtime_ai_gateway_contract.py
@@ -8,8 +8,11 @@ import tempfile
import unittest
from pathlib import Path
+from kill_life.agent_catalog import canonical_agent_ids
+
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPT = REPO_ROOT / "tools" / "cockpit" / "runtime_ai_gateway.sh"
+CANONICAL_AGENT_IDS = set(canonical_agent_ids())
class RuntimeAiGatewayContractTests(unittest.TestCase):
@@ -78,6 +81,7 @@ class RuntimeAiGatewayContractTests(unittest.TestCase):
self.assertEqual(payload["component"], "runtime-mcp-ia-gateway")
self.assertEqual(payload["owner_repo"], "Kill_LIFE")
self.assertEqual(payload["owner_agent"], "Runtime-Companion")
+ self.assertIn(payload["owner_agent"], CANONICAL_AGENT_IDS)
self.assertEqual(payload["owner_subagent"], "MCP-Health")
self.assertEqual(payload["status"], "degraded")
self.assertTrue(payload["summary_short"])
@@ -95,6 +99,8 @@ class RuntimeAiGatewayContractTests(unittest.TestCase):
self.assertIn("firmware_cad", payload["summary_short_artifacts"])
firmware_cad_summary_path = REPO_ROOT / payload["summary_short_artifacts"]["firmware_cad"]["json"]
self.assertTrue(firmware_cad_summary_path.exists())
+ firmware_cad_summary = json.loads(firmware_cad_summary_path.read_text(encoding="utf-8"))
+ self.assertIn(firmware_cad_summary["owner_agent"], CANONICAL_AGENT_IDS)
self.assertEqual(payload["sources"]["mascarade"]["provider"], "ollama")
self.assertIn("infra_vps", payload["sources"])
self.assertGreaterEqual(len(payload["evidence"]), 1)
@@ -155,6 +161,80 @@ class RuntimeAiGatewayContractTests(unittest.TestCase):
any("timed out" in step.lower() for step in payload["next_steps"])
)
+ def test_status_keeps_precise_web_reasons_and_refreshes_infra_vps_when_missing(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ root = Path(tmp_dir)
+ intelligence = root / "intelligence.json"
+ mesh = root / "mesh.json"
+ mascarade = root / "mascarade.json"
+ infra_script = root / "infra.sh"
+ missing_infra = root / "missing-infra.json"
+
+ intelligence.write_text(
+ json.dumps(
+ {
+ "contract_version": "summary-short/v1",
+ "status": "ready",
+ "summary_short": "IA lane ready.",
+ "evidence": ["artifacts/cockpit/intelligence_program/latest.json"],
+ "open_task_count": 0,
+ "next_steps": [],
+ "web_platform_health": {
+ "status": "degraded",
+ "up_count": 1,
+ "total": 4,
+ "probes": {
+ "nextjs": {"status": "up", "reason": "nextjs-ready"},
+ "realtime": {"status": "down", "reason": "yjs-absent"},
+ "queue": {"status": "down", "reason": "redis-env-missing"},
+ "worker": {"status": "down", "reason": "worker-absent"},
+ },
+ },
+ }
+ ),
+ encoding="utf-8",
+ )
+ mesh.write_text(json.dumps({"mesh_status": "ready", "load_profile": "tower-first", "host_order": []}), encoding="utf-8")
+ mascarade.write_text(json.dumps({"status": "ok", "provider": "ollama", "model": "qwen"}), encoding="utf-8")
+ infra_script.write_text(
+ "#!/usr/bin/env bash\n"
+ "printf '%s\\n' '{\"component\":\"infra-vps-healthcheck\",\"status\":\"ok\",\"services\":[]}'\n",
+ encoding="utf-8",
+ )
+
+ env = os.environ.copy()
+ env["RUNTIME_GATEWAY_INFRA_VPS_SCRIPT"] = str(infra_script)
+
+ proc = subprocess.run(
+ [
+ "bash",
+ str(SCRIPT),
+ "--action",
+ "status",
+ "--json",
+ "--intelligence-report",
+ str(intelligence),
+ "--mesh-report",
+ str(mesh),
+ "--mascarade-report",
+ str(mascarade),
+ "--infra-vps-report",
+ str(missing_infra),
+ ],
+ cwd=str(REPO_ROOT),
+ capture_output=True,
+ text=True,
+ check=True,
+ env=env,
+ )
+
+ payload = json.loads(proc.stdout)
+ self.assertEqual(payload["surfaces"]["infra_vps"]["status"], "ready")
+ self.assertIn("web-queue-redis-env-missing", payload["degraded_reasons"])
+ self.assertIn("web-worker-worker-absent", payload["degraded_reasons"])
+ self.assertTrue(any("REDIS_URL" in step for step in payload["next_steps"]))
+ self.assertTrue(any("worker:eda" in step for step in payload["next_steps"]))
+
if __name__ == "__main__":
unittest.main()
diff --git a/test/test_validate_specs.py b/test/test_validate_specs.py
index b9a27fb..44e01f7 100644
--- a/test/test_validate_specs.py
+++ b/test/test_validate_specs.py
@@ -49,9 +49,12 @@ class ValidateSpecsTests(unittest.TestCase):
self.assertIn("compliance", payload)
self.assertIn("rfc2119", payload)
self.assertIn("mirror_sync", payload)
+ self.assertIn("agent_catalog", payload)
self.assertIsInstance(payload["ok"], bool)
self.assertFalse(payload["require_mirror_sync"])
self.assertTrue(payload["mirror_sync"]["mirror_exists"])
+ self.assertTrue(payload["agent_catalog"]["ok"])
+ self.assertEqual(payload["agent_catalog"]["agent_count"], 12)
def test_mcp_server_supports_initialize_and_tools_list(self):
proc = subprocess.Popen(
diff --git a/test/test_yiacad_action_registry_contract.py b/test/test_yiacad_action_registry_contract.py
new file mode 100644
index 0000000..21d2505
--- /dev/null
+++ b/test/test_yiacad_action_registry_contract.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import unittest
+
+from kill_life.yiacad_action_registry import (
+ INPUT_ARGUMENTS,
+ load_yiacad_action_registry,
+ yiacad_actions,
+ yiacad_actions_for_surface,
+)
+
+
+class YiacadActionRegistryContractTests(unittest.TestCase):
+ def test_registry_is_machine_readable_and_unique(self) -> None:
+ payload = load_yiacad_action_registry()
+ self.assertEqual(payload["contract_version"], "yiacad-action-registry/v1")
+ self.assertEqual(payload["component"], "yiacad-action-registry")
+ actions = yiacad_actions()
+ commands = [entry["transport_command"] for entry in actions]
+ action_ids = [entry["action_id"] for entry in actions]
+ self.assertEqual(len(commands), len(set(commands)))
+ self.assertEqual(len(action_ids), len(set(action_ids)))
+ for entry in actions:
+ for input_name in entry["accepted_inputs"]:
+ self.assertIn(input_name, INPUT_ARGUMENTS)
+
+ def test_surface_views_cover_kicad_and_freecad(self) -> None:
+ kicad_commands = {
+ entry["transport_command"] for entry in yiacad_actions_for_surface("yiacad-kicad")
+ }
+ freecad_commands = {
+ entry["transport_command"] for entry in yiacad_actions_for_surface("yiacad-freecad")
+ }
+ self.assertTrue({"status", "kicad-erc-drc", "bom-review", "ecad-mcad-sync"}.issubset(kicad_commands))
+ self.assertTrue({"status", "ecad-mcad-sync", "manufacturing-package", "kiauto-checks"}.issubset(freecad_commands))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_yiacad_ai_bridge_contract.py b/test/test_yiacad_ai_bridge_contract.py
new file mode 100644
index 0000000..071243b
--- /dev/null
+++ b/test/test_yiacad_ai_bridge_contract.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import argparse
+import contextlib
+import io
+import json
+import unittest
+from types import SimpleNamespace
+from unittest.mock import patch
+
+import tools.cad.yiacad_ai_bridge as bridge
+
+
+class YiacadAiBridgeContractTests(unittest.TestCase):
+ def test_request_delegates_to_backend_without_persisting_local_queue(self) -> None:
+ fake_proc = SimpleNamespace(
+ returncode=0,
+ stdout=json.dumps({"status": "degraded", "summary": "ok", "next_steps": ["step"]}),
+ stderr="",
+ )
+ args = argparse.Namespace(
+ surface="freecad",
+ intent="ecad-mcad-sync",
+ prompt="",
+ source_path="/tmp/demo.FCStd",
+ selection_json="[]",
+ )
+ with patch.object(bridge.subprocess, "run", return_value=fake_proc):
+ buffer = io.StringIO()
+ with contextlib.redirect_stdout(buffer):
+ rc = bridge.command_request(args)
+ payload = json.loads(buffer.getvalue())
+ self.assertEqual(rc, 0)
+ self.assertEqual(payload["request_path"], "")
+ self.assertEqual(payload["transport_command"], "ecad-mcad-sync")
+ self.assertEqual(payload["surface"], "freecad")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_yiacad_backend_contract.py b/test/test_yiacad_backend_contract.py
index c0e2664..7fc49a6 100644
--- a/test/test_yiacad_backend_contract.py
+++ b/test/test_yiacad_backend_contract.py
@@ -2,7 +2,9 @@
from __future__ import annotations
import unittest
+from unittest.mock import patch
+import tools.cad.yiacad_backend as backend
from tools.cad.yiacad_backend import build_context_record, build_uiux_output, detect_integrated_engines
@@ -42,6 +44,52 @@ class YiacadBackendContractTests(unittest.TestCase):
self.assertIn("freecad", runtime["integrated_engines"])
self.assertIn(runtime["integrated_engines"]["kicad"]["status"], {"done", "degraded", "blocked"})
+ def test_command_output_returns_timeout_instead_of_hanging(self) -> None:
+ with patch("tools.cad.yiacad_backend.subprocess.run", side_effect=backend.subprocess.TimeoutExpired(cmd=["demo"], timeout=0.1)):
+ rc, text = backend.command_output(["demo"], timeout_sec=0.1)
+ self.assertEqual(rc, 124)
+ self.assertIn("timed out", text)
+
+ def test_detect_integrated_engines_uses_freecad_bundle_without_launching_gui(self) -> None:
+ commands: list[list[str]] = []
+
+ def fake_resolve_binary(*candidates):
+ first = candidates[0]
+ if first == backend.KICAD_APP_CLI:
+ return str(backend.KICAD_APP_CLI)
+ if first == backend.FREECAD_APP_CMD:
+ return None
+ if first == backend.FREECAD_APP_GUI:
+ return str(backend.FREECAD_APP_GUI)
+ if first == "kibot":
+ return "/usr/local/bin/kibot"
+ if first == "pcbnew_do":
+ return "/usr/local/bin/pcbnew_do"
+ return None
+
+ def fake_command_output(command, timeout_sec=backend.COMMAND_TIMEOUT_SEC):
+ commands.append(command)
+ if command[0] == str(backend.KICAD_APP_CLI):
+ return (0, "10.0.0")
+ if command[0] == "/usr/local/bin/kibot":
+ return (0, "KiBot 1.8.5")
+ if command[0] == "/usr/local/bin/pcbnew_do":
+ return (0, "pcbnew_do 2.3.5")
+ raise AssertionError(f"unexpected command: {command}")
+
+ with (
+ patch.object(backend, "resolve_binary", side_effect=fake_resolve_binary),
+ patch.object(backend, "command_output", side_effect=fake_command_output),
+ patch.object(backend, "bundle_version", return_value="1.1.0"),
+ ):
+ engines = backend.detect_integrated_engines()
+
+ self.assertEqual(engines["freecad"]["detected_version"], "1.1.0")
+ self.assertEqual(engines["freecad"]["status"], "done")
+ self.assertEqual(engines["freecad"]["reason"], "bundle-ready")
+ self.assertEqual(engines["freecad"]["binary"], str(backend.FREECAD_APP_GUI))
+ self.assertTrue(all(command[0] != str(backend.FREECAD_APP_GUI) for command in commands))
+
if __name__ == "__main__":
unittest.main()
diff --git a/test/test_yiacad_freecad_workbench_contract.py b/test/test_yiacad_freecad_workbench_contract.py
new file mode 100644
index 0000000..b7e2091
--- /dev/null
+++ b/test/test_yiacad_freecad_workbench_contract.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import importlib
+import sys
+import tempfile
+import unittest
+from pathlib import Path
+
+
+REPO_ROOT = Path(__file__).resolve().parents[1]
+PLUGIN_ROOT = REPO_ROOT / "tools" / "cad" / "integrations" / "freecad"
+
+
+class YiacadFreecadWorkbenchContractTests(unittest.TestCase):
+ def setUp(self) -> None:
+ sys.path.insert(0, str(PLUGIN_ROOT))
+
+ def tearDown(self) -> None:
+ if str(PLUGIN_ROOT) in sys.path:
+ sys.path.remove(str(PLUGIN_ROOT))
+ for name in list(sys.modules):
+ if name.startswith("YiACADWorkbench"):
+ sys.modules.pop(name, None)
+
+ def test_adapter_exposes_document_selection_and_actions_without_runtime(self) -> None:
+ adapter = importlib.import_module("YiACADWorkbench._adapter")
+ freecad = type("FreeCAD", (), {"ActiveDocument": type("Doc", (), {"FileName": "/tmp/demo.FCStd"})()})
+ selection_item = type("SelectionItem", (), {"ObjectName": "Body"})()
+ selection = type(
+ "Selection",
+ (),
+ {"getSelectionEx": staticmethod(lambda: [selection_item])},
+ )
+ freecad_gui = type("FreeCADGui", (), {"Selection": selection})
+
+ self.assertEqual(adapter.current_document_path(freecad), "/tmp/demo.FCStd")
+ self.assertEqual(adapter.selection_summary(freecad_gui), ["Body"])
+ commands = {entry["transport_command"] for entry in adapter.available_registry_actions()}
+ self.assertIn("ecad-mcad-sync", commands)
+
+ def test_gui_module_imports_without_freecad_runtime(self) -> None:
+ module = importlib.import_module("YiACADWorkbench.yiacad_freecad_gui")
+ self.assertTrue(hasattr(module, "show_dialog"))
+ commands = {entry["transport_command"] for entry in module.ACTION_ENTRIES}
+ self.assertIn("ecad-mcad-sync", commands)
+ self.assertIn("manufacturing-package", commands)
+
+ def test_session_roundtrip(self) -> None:
+ common = importlib.import_module("YiACADWorkbench._common")
+ with tempfile.TemporaryDirectory() as tmpdir:
+ session_file = Path(tmpdir) / "session.json"
+ common.remember_session_state("ecad-mcad-sync", "check fit", "/tmp/demo.FCStd", session_file)
+ common.append_session_message(
+ "user",
+ "check fit",
+ intent="ecad-mcad-sync",
+ source_path="/tmp/demo.FCStd",
+ path=session_file,
+ )
+ common.append_session_message(
+ "assistant",
+ "Status: done\nSummary: sync completed",
+ intent="ecad-mcad-sync",
+ source_path="/tmp/demo.FCStd",
+ status="done",
+ path=session_file,
+ )
+ payload = common.load_session(session_file)
+ self.assertEqual(payload["last_intent"], "ecad-mcad-sync")
+ self.assertEqual(len(payload["messages"]), 2)
+ self.assertEqual(payload["messages"][1]["status"], "done")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/test/test_yiacad_kicad_plugin_contract.py b/test/test_yiacad_kicad_plugin_contract.py
index 1879412..5fe7cda 100644
--- a/test/test_yiacad_kicad_plugin_contract.py
+++ b/test/test_yiacad_kicad_plugin_contract.py
@@ -33,6 +33,19 @@ class YiACADKiCadPluginContractTest(unittest.TestCase):
self.assertEqual(payload["board"], str(board))
self.assertEqual(payload["schematic"], str(schematic))
+ def test_common_exposes_registry_backed_actions_and_runtime_discovery(self) -> None:
+ common = importlib.import_module("yiacad_kicad_plugin._common")
+ actions = common.available_actions()
+ commands = {entry["transport_command"] for entry in actions}
+ self.assertIn("kicad-erc-drc", commands)
+ self.assertIn("bom-review", commands)
+
+ board = type("Board", (), {"GetFileName": lambda self: "/tmp/demo.kicad_pcb"})()
+ pcbnew = type("PcbNew", (), {"GetBoard": staticmethod(lambda: board)})
+ source_path, backend = common.resolve_kicad_source_path(pcbnew, {"KICAD_IPC_PROJECT_PATH": ""})
+ self.assertEqual(source_path, "/tmp/demo.kicad_pcb")
+ self.assertIn(backend, {"pcbnew-runtime", "kicad-python-runtime"})
+
def test_action_module_imports_without_kicad_runtime(self) -> None:
module = importlib.import_module("yiacad_kicad_plugin.yiacad_action")
message = module._result_message(
diff --git a/tools/autonomous_next_lots.py b/tools/autonomous_next_lots.py
index 9f3bae0..7340000 100755
--- a/tools/autonomous_next_lots.py
+++ b/tools/autonomous_next_lots.py
@@ -362,8 +362,9 @@ LOTS: tuple[Lot, ...] = (
"docs/plans/18_todo_enchainement_autonome_des_lots_utiles.md",
),
owner_repo="Kill_LIFE",
- owner_agent="CAD-Fusion",
- owner_team="CAD-Fusion",
+ owner_agent="Embedded-CAD",
+ owner_subagent="CAD-Fusion",
+ owner_team="Embedded-CAD",
dependencies=("cad-mcp-host",),
rollback_plan=(
"Geler la branche `kill-life-ai-native` et revenir au mode container-only (`tools/hw/cad_stack.sh`).",
diff --git a/tools/cad/install_yiacad_native_gui.sh b/tools/cad/install_yiacad_native_gui.sh
index 7daeabc..d042e06 100755
--- a/tools/cad/install_yiacad_native_gui.sh
+++ b/tools/cad/install_yiacad_native_gui.sh
@@ -37,6 +37,7 @@ status() {
printf 'ROOT_DIR=%s\n' "$ROOT_DIR"
printf 'KICAD_PLUGIN_DIR=%s\n' "$KICAD_PLUGIN_DIR"
printf 'FREECAD_MOD_DIR=%s\n' "$FREECAD_MOD_DIR"
+ printf 'TRANSPORT=service-first via %s\n' "${ROOT_DIR}/tools/cad/yiacad_backend_client.py"
printf 'KICAD_LINK=%s\n' "$(link_target "${KICAD_PLUGIN_DIR}/yiacad_kicad_plugin")"
printf 'FREECAD_LINK=%s\n' "$(link_target "${FREECAD_MOD_DIR}/YiACADWorkbench")"
}
diff --git a/tools/cad/integrations/freecad/YiACADWorkbench/_adapter.py b/tools/cad/integrations/freecad/YiACADWorkbench/_adapter.py
new file mode 100644
index 0000000..a7be226
--- /dev/null
+++ b/tools/cad/integrations/freecad/YiACADWorkbench/_adapter.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from ._common import available_actions
+
+
+def current_document_path(freecad_module=None) -> str:
+ if freecad_module is None:
+ return ""
+ document = getattr(freecad_module, "ActiveDocument", None)
+ if document is None:
+ return ""
+ return str(getattr(document, "FileName", "") or "")
+
+
+def selection_summary(freecad_gui_module=None) -> list[str]:
+ if freecad_gui_module is None:
+ return []
+ try:
+ selected = []
+ for item in freecad_gui_module.Selection.getSelectionEx():
+ object_name = getattr(item, "ObjectName", None)
+ if object_name:
+ selected.append(str(object_name))
+ return selected
+ except Exception:
+ return []
+
+
+def available_registry_actions() -> list[dict]:
+ return list(available_actions())
diff --git a/tools/cad/integrations/freecad/YiACADWorkbench/_common.py b/tools/cad/integrations/freecad/YiACADWorkbench/_common.py
new file mode 100644
index 0000000..72637a3
--- /dev/null
+++ b/tools/cad/integrations/freecad/YiACADWorkbench/_common.py
@@ -0,0 +1,185 @@
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+from datetime import datetime, timezone
+from pathlib import Path
+
+from kill_life.yiacad_action_registry import (
+ yiacad_action_inputs,
+ yiacad_actions_for_surface,
+ yiacad_command_for_alias,
+)
+
+SESSION_VERSION = 1
+MAX_SESSION_MESSAGES = 50
+
+
+def _candidate_roots() -> list[Path]:
+ candidates: list[Path] = []
+ if os.environ.get("KILL_LIFE_ROOT"):
+ candidates.append(Path(os.environ["KILL_LIFE_ROOT"]).expanduser())
+ here = Path(__file__).resolve()
+ candidates.extend(here.parents)
+ return candidates
+
+
+def repo_root() -> Path:
+ for candidate in _candidate_roots():
+ backend_client = candidate / "tools" / "cad" / "yiacad_backend_client.py"
+ if backend_client.exists():
+ return candidate
+ raise RuntimeError("Unable to locate Kill_LIFE root for YiACAD backend client")
+
+
+def backend_client_script() -> Path:
+ return repo_root() / "tools" / "cad" / "yiacad_backend_client.py"
+
+
+def _run_json_command(args: list[str]) -> dict:
+ proc = subprocess.run(
+ ["python3", str(backend_client_script()), *args],
+ cwd=repo_root(),
+ text=True,
+ capture_output=True,
+ check=False,
+ timeout=30,
+ )
+ if proc.returncode != 0 and not proc.stdout.strip():
+ raise RuntimeError(proc.stderr.strip() or "YiACAD backend client failed")
+ return json.loads(proc.stdout.strip() or "{}")
+
+
+def _project_inputs(source_path: str) -> dict[str, str]:
+ path = Path(source_path).expanduser() if source_path else Path()
+ if not source_path:
+ return {"source_path": "", "board": "", "schematic": "", "freecad_document": ""}
+ suffix = path.suffix
+ stem = path.with_suffix("")
+ board = stem.with_suffix(".kicad_pcb")
+ schematic = stem.with_suffix(".kicad_sch")
+ freecad_document = stem.with_suffix(".FCStd")
+ return {
+ "source_path": str(path),
+ "board": str(board) if board.exists() else "",
+ "schematic": str(schematic) if schematic.exists() else "",
+ "freecad_document": str(path if suffix == ".FCStd" else freecad_document) if (path if suffix == ".FCStd" else freecad_document).exists() else "",
+ }
+
+
+def available_actions(surface: str = "yiacad-freecad") -> list[dict]:
+ return list(yiacad_actions_for_surface(surface))
+
+
+def _now_iso() -> str:
+ return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
+
+
+def _default_session() -> dict:
+ return {
+ "version": SESSION_VERSION,
+ "updated_at": "",
+ "last_intent": "ecad-mcad-sync",
+ "last_prompt": "",
+ "last_source_path": "",
+ "messages": [],
+ }
+
+
+def session_dir(root: Path | None = None) -> Path:
+ path = (root or repo_root()) / "artifacts" / "cad-ai-native" / "freecad_workbench"
+ path.mkdir(parents=True, exist_ok=True)
+ return path
+
+
+def session_path(root: Path | None = None) -> Path:
+ return session_dir(root) / "session.json"
+
+
+def load_session(path: Path | None = None) -> dict:
+ target = path or session_path()
+ if not target.exists():
+ return _default_session()
+ try:
+ payload = json.loads(target.read_text(encoding="utf-8"))
+ except (OSError, json.JSONDecodeError):
+ return _default_session()
+ session = _default_session()
+ session.update({key: value for key, value in payload.items() if key in session})
+ session["messages"] = payload.get("messages") if isinstance(payload.get("messages"), list) else []
+ return session
+
+
+def save_session(session: dict, path: Path | None = None) -> dict:
+ target = path or session_path()
+ normalized = _default_session()
+ normalized.update({key: value for key, value in session.items() if key in normalized})
+ normalized["messages"] = list(session.get("messages") or [])[-MAX_SESSION_MESSAGES:]
+ normalized["updated_at"] = _now_iso()
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.write_text(json.dumps(normalized, ensure_ascii=True, indent=2) + "\n", encoding="utf-8")
+ return normalized
+
+
+def remember_session_state(intent: str, prompt: str, source_path: str, path: Path | None = None) -> dict:
+ session = load_session(path)
+ session["last_intent"] = intent
+ session["last_prompt"] = prompt
+ session["last_source_path"] = source_path
+ return save_session(session, path)
+
+
+def append_session_message(
+ role: str,
+ content: str,
+ *,
+ intent: str = "",
+ source_path: str = "",
+ status: str = "",
+ path: Path | None = None,
+) -> dict:
+ session = load_session(path)
+ session["messages"].append(
+ {
+ "role": role,
+ "content": content,
+ "intent": intent,
+ "source_path": source_path,
+ "status": status,
+ "created_at": _now_iso(),
+ }
+ )
+ if intent:
+ session["last_intent"] = intent
+ if source_path:
+ session["last_source_path"] = source_path
+ return save_session(session, path)
+
+
+def clear_session(path: Path | None = None) -> dict:
+ return save_session(_default_session(), path)
+
+
+def run_intent(surface: str, intent: str, prompt: str, source_path: str, selection: list[str]) -> dict:
+ del prompt
+ del selection
+ inputs = _project_inputs(source_path)
+ command = yiacad_command_for_alias("yiacad-freecad", intent) or yiacad_command_for_alias("yiacad-kicad", intent) or "status"
+ args = ["--surface", surface, "--json-output", command]
+ for key in yiacad_action_inputs(command):
+ value = inputs.get(key, "")
+ if value:
+ args.extend([f"--{key.replace('_', '-')}", value])
+ return _run_json_command(args)
+
+
+def fetch_status_payload(source_path: str = "") -> dict:
+ args = ["--surface", "yiacad-desktop", "--json-output", "status"]
+ if source_path:
+ args.extend(["--source-path", source_path])
+ return _run_json_command(args)
+
+
+def open_path(path: Path) -> None:
+ subprocess.Popen(["open", str(path)])
diff --git a/tools/cad/integrations/freecad/YiACADWorkbench/yiacad_freecad_gui.py b/tools/cad/integrations/freecad/YiACADWorkbench/yiacad_freecad_gui.py
index e1a1dc4..35b62a1 100644
--- a/tools/cad/integrations/freecad/YiACADWorkbench/yiacad_freecad_gui.py
+++ b/tools/cad/integrations/freecad/YiACADWorkbench/yiacad_freecad_gui.py
@@ -1,152 +1,242 @@
from __future__ import annotations
import json
-import os
import subprocess
-from pathlib import Path
-import FreeCAD # type: ignore
-import FreeCADGui # type: ignore
+try:
+ import FreeCAD # type: ignore
+except Exception: # pragma: no cover
+ FreeCAD = None
+
+try:
+ import FreeCADGui # type: ignore
+except Exception: # pragma: no cover
+ FreeCADGui = None
try:
from PySide2 import QtWidgets # type: ignore
except Exception: # pragma: no cover
- from PySide import QtGui as QtWidgets # type: ignore
+ try:
+ from PySide import QtGui as QtWidgets # type: ignore
+ except Exception: # pragma: no cover
+ QtWidgets = None
+
+from ._adapter import available_registry_actions, current_document_path, selection_summary
+from ._common import (
+ append_session_message,
+ backend_client_script,
+ clear_session,
+ fetch_status_payload,
+ load_session,
+ open_path,
+ remember_session_state,
+ repo_root,
+ run_intent,
+)
-def _candidate_roots() -> list[Path]:
- candidates: list[Path] = []
- if os.environ.get("KILL_LIFE_ROOT"):
- candidates.append(Path(os.environ["KILL_LIFE_ROOT"]).expanduser())
- here = Path(__file__).resolve()
- candidates.extend(here.parents)
- return candidates
+ACTION_ENTRIES = [entry for entry in available_registry_actions() if entry["transport_command"] != "status"]
-def repo_root() -> Path:
- for candidate in _candidate_roots():
- bridge = candidate / "tools" / "cad" / "yiacad_ai_bridge.py"
- if bridge.exists():
- return candidate
- raise RuntimeError("Unable to locate Kill_LIFE root for YiACAD bridge")
+def _result_message(payload: dict) -> str:
+ lines = [
+ f"Status: {payload.get('status', 'unknown')}",
+ f"Summary: {payload.get('summary', '(no summary)')}",
+ ]
+ degraded_reasons = payload.get("degraded_reasons") or []
+ next_steps = payload.get("next_steps") or []
+ if degraded_reasons:
+ lines.append("")
+ lines.append("Degraded reasons:")
+ lines.extend(f"- {item}" for item in degraded_reasons[:4])
+ if next_steps:
+ lines.append("")
+ lines.append("Next steps:")
+ lines.extend(f"- {item}" for item in next_steps[:4])
+ return "\n".join(lines)
-def bridge_script() -> Path:
- return repo_root() / "tools" / "cad" / "yiacad_ai_bridge.py"
+def _transcript_text(session: dict) -> str:
+ messages = session.get("messages") or []
+ if not messages:
+ return "No YiACAD session history yet."
+ chunks: list[str] = []
+ for item in messages:
+ stamp = item.get("created_at") or "unknown-time"
+ role = str(item.get("role") or "assistant").upper()
+ intent = item.get("intent") or ""
+ source_path = item.get("source_path") or ""
+ status = item.get("status") or ""
+ header = f"[{stamp}] {role}"
+ if intent:
+ header += f" | {intent}"
+ if status:
+ header += f" | {status}"
+ chunks.append(header)
+ if source_path:
+ chunks.append(f"source: {source_path}")
+ chunks.append(str(item.get("content") or ""))
+ chunks.append("")
+ return "\n".join(chunks).strip()
-def run_bridge(args: list[str]) -> dict:
+def run_native_json_action(command: str, *args: str) -> dict:
proc = subprocess.run(
- ["python3", str(bridge_script()), *args],
+ ["python3", str(backend_client_script()), "--surface", "yiacad-desktop", "--json-output", command, *args],
cwd=repo_root(),
text=True,
capture_output=True,
check=False,
timeout=30,
)
- if proc.returncode != 0:
- raise RuntimeError(proc.stderr.strip() or proc.stdout.strip() or "YiACAD bridge failed")
+ if proc.returncode != 0 and not proc.stdout.strip():
+ raise RuntimeError(proc.stderr.strip() or "YiACAD backend client failed")
return json.loads(proc.stdout.strip() or "{}")
-def current_document_path() -> str:
- doc = FreeCAD.ActiveDocument
- if doc is None:
- return ""
- return str(getattr(doc, "FileName", "") or "")
-
-
-def selection_summary() -> list[str]:
- selected = []
- for item in FreeCADGui.Selection.getSelectionEx():
- if getattr(item, "ObjectName", None):
- selected.append(str(item.ObjectName))
- return selected
-
-
-def _status_summary() -> str:
- payload = run_bridge(["status"])
- lines = payload.get("yiacad_status_excerpt") or []
- latest_request = payload.get("latest_request") or "(none)"
- excerpt = "\n".join(lines[:8]) if lines else "No YiACAD status snapshot yet."
- return f"Latest request:\n{latest_request}\n\nStatus:\n{excerpt}"
-
-
def show_status_message() -> None:
- QtWidgets.QMessageBox.information(None, "YiACAD Status", _status_summary())
+ if QtWidgets is None:
+ raise RuntimeError("QtWidgets is unavailable in the current FreeCAD runtime")
+ payload = fetch_status_payload(current_document_path(FreeCAD))
+ QtWidgets.QMessageBox.information(None, "YiACAD Status", _result_message(payload))
def open_artifacts() -> None:
- subprocess.Popen(["open", str(repo_root() / "artifacts")])
+ open_path(repo_root() / "artifacts")
-class YiACADDialog(QtWidgets.QDialog):
- def __init__(self) -> None:
- super().__init__(None)
- self.setWindowTitle("YiACAD AI for FreeCAD")
- self.resize(560, 420)
+if QtWidgets is not None:
- layout = QtWidgets.QVBoxLayout(self)
+ class YiACADDialog(QtWidgets.QDialog):
+ def __init__(self) -> None:
+ super().__init__(None)
+ self.setWindowTitle("YiACAD AI for FreeCAD")
+ self.resize(720, 560)
- self.intent = QtWidgets.QComboBox(self)
- self.intent.addItems(
- [
- "model-assist",
- "parametric-refactor",
- "step-export-review",
- "ecad-mcad-sync",
- ]
- )
- self.source = QtWidgets.QLineEdit(self)
- self.source.setReadOnly(True)
- self.source.setText(current_document_path())
- self.prompt = QtWidgets.QPlainTextEdit(self)
- self.prompt.setPlainText("Describe the FreeCAD task to queue for YiACAD.")
+ layout = QtWidgets.QVBoxLayout(self)
+ session = load_session()
+ default_source = current_document_path(FreeCAD) or session.get("last_source_path") or ""
+ self.actions = list(ACTION_ENTRIES)
- layout.addWidget(QtWidgets.QLabel("Intent", self))
- layout.addWidget(self.intent)
- layout.addWidget(QtWidgets.QLabel("Document / source path", self))
- layout.addWidget(self.source)
- layout.addWidget(QtWidgets.QLabel("Prompt", self))
- layout.addWidget(self.prompt)
+ self.intent = QtWidgets.QComboBox(self)
+ self.intent.addItems([entry["display_name"] for entry in self.actions])
+ commands = [entry["transport_command"] for entry in self.actions]
+ if session.get("last_intent") in commands:
+ self.intent.setCurrentIndex(commands.index(session["last_intent"]))
+ elif self.actions:
+ self.intent.setCurrentIndex(0)
- button_row = QtWidgets.QHBoxLayout()
- queue_button = QtWidgets.QPushButton("Queue AI Request", self)
- status_button = QtWidgets.QPushButton("YiACAD Status", self)
- artifacts_button = QtWidgets.QPushButton("Open Artifacts", self)
- close_button = QtWidgets.QPushButton("Close", self)
+ self.source = QtWidgets.QLineEdit(self)
+ self.source.setReadOnly(True)
+ self.source.setText(default_source)
+ self.prompt = QtWidgets.QPlainTextEdit(self)
+ self.prompt.setPlainText(
+ session.get("last_prompt") or "Describe the FreeCAD task context for YiACAD. The action run remains deterministic."
+ )
+ self.transcript = QtWidgets.QPlainTextEdit(self)
+ self.transcript.setReadOnly(True)
+ self.refresh_transcript()
- queue_button.clicked.connect(self.on_queue)
- status_button.clicked.connect(lambda: show_status_message())
- artifacts_button.clicked.connect(lambda: open_artifacts())
- close_button.clicked.connect(self.close)
+ layout.addWidget(QtWidgets.QLabel("Action", self))
+ layout.addWidget(self.intent)
+ layout.addWidget(QtWidgets.QLabel("Document / source path", self))
+ layout.addWidget(self.source)
+ layout.addWidget(QtWidgets.QLabel("Transcript", self))
+ layout.addWidget(self.transcript)
+ layout.addWidget(QtWidgets.QLabel("Task context", self))
+ layout.addWidget(self.prompt)
- for button in (queue_button, status_button, artifacts_button, close_button):
- button_row.addWidget(button)
+ button_row = QtWidgets.QHBoxLayout()
+ run_button = QtWidgets.QPushButton("Run YiACAD Action", self)
+ status_button = QtWidgets.QPushButton("YiACAD Status", self)
+ artifacts_button = QtWidgets.QPushButton("Open Artifacts", self)
+ clear_button = QtWidgets.QPushButton("Clear Session", self)
+ close_button = QtWidgets.QPushButton("Close", self)
- layout.addLayout(button_row)
+ run_button.clicked.connect(self.on_run)
+ status_button.clicked.connect(self.on_status)
+ artifacts_button.clicked.connect(self.on_artifacts)
+ clear_button.clicked.connect(self.on_clear)
+ close_button.clicked.connect(self.close)
- def on_queue(self) -> None:
- payload = run_bridge(
- [
- "request",
- "--surface",
- "freecad",
- "--intent",
- self.intent.currentText(),
- "--prompt",
+ for button in (run_button, status_button, artifacts_button, clear_button, close_button):
+ button_row.addWidget(button)
+
+ layout.addLayout(button_row)
+
+ def persist_state(self) -> None:
+ command = self.actions[self.intent.currentIndex()]["transport_command"]
+ remember_session_state(
+ command,
self.prompt.toPlainText().strip(),
- "--source-path",
self.source.text().strip(),
- "--selection-json",
- json.dumps(selection_summary(), ensure_ascii=True),
- ]
- )
- QtWidgets.QMessageBox.information(
- self,
- "YiACAD",
- f"Queued request:\n{payload.get('request_path', '')}",
- )
+ )
+
+ def refresh_transcript(self) -> None:
+ self.transcript.setPlainText(_transcript_text(load_session()))
+ cursor = self.transcript.textCursor()
+ cursor.movePosition(cursor.End)
+ self.transcript.setTextCursor(cursor)
+
+ def on_run(self) -> None:
+ if not self.actions:
+ return
+ prompt = self.prompt.toPlainText().strip()
+ source_path = self.source.text().strip()
+ command = self.actions[self.intent.currentIndex()]["transport_command"]
+ self.persist_state()
+ append_session_message(
+ "user",
+ prompt or "(no additional task context provided)",
+ intent=command,
+ source_path=source_path,
+ )
+ payload = run_intent(
+ "yiacad-desktop",
+ command,
+ prompt,
+ source_path,
+ selection_summary(FreeCADGui),
+ )
+ result_text = _result_message(payload)
+ append_session_message(
+ "assistant",
+ result_text,
+ intent=command,
+ source_path=source_path,
+ status=str(payload.get("status") or ""),
+ )
+ self.refresh_transcript()
+ QtWidgets.QMessageBox.information(self, "YiACAD", result_text)
+
+ def on_status(self) -> None:
+ source_path = self.source.text().strip()
+ self.persist_state()
+ payload = fetch_status_payload(source_path)
+ result_text = _result_message(payload)
+ append_session_message(
+ "assistant",
+ result_text,
+ intent="status",
+ source_path=source_path,
+ status=str(payload.get("status") or ""),
+ )
+ self.refresh_transcript()
+ QtWidgets.QMessageBox.information(self, "YiACAD Status", result_text)
+
+ def on_artifacts(self) -> None:
+ self.persist_state()
+ open_artifacts()
+
+ def on_clear(self) -> None:
+ clear_session()
+ self.refresh_transcript()
+
+else:
+
+ class YiACADDialog: # pragma: no cover
+ def exec_(self) -> None:
+ raise RuntimeError("QtWidgets is unavailable in the current FreeCAD runtime")
def show_dialog() -> None:
diff --git a/tools/cad/integrations/kicad/yiacad_kicad_plugin/README.md b/tools/cad/integrations/kicad/yiacad_kicad_plugin/README.md
index 4bef499..93a8609 100644
--- a/tools/cad/integrations/kicad/yiacad_kicad_plugin/README.md
+++ b/tools/cad/integrations/kicad/yiacad_kicad_plugin/README.md
@@ -11,12 +11,13 @@ KiCad action plugin for the YiACAD desktop lane.
## Current model
- transport: `service-first` through `tools/cad/yiacad_backend_client.py`
+- actions: registry-backed from `specs/contracts/yiacad_action_registry.json`
- product surface: `yiacad-desktop`
- integrated actions:
- - `board-review`
- - `erc-drc-assist`
- - `bom-footprint-audit`
+ - `kicad-erc-drc`
+ - `bom-review`
- `ecad-mcad-sync`
+ - `status`
## Install in local KiCad
@@ -34,3 +35,4 @@ This links the plugin into:
- This plugin is the YiACAD working surface used to move quickly on KiCad integration.
- This plugin is independent and remains under YiACAD control as the canonical KiCad integration surface.
+- Context discovery prefers explicit KiCad runtime hints when available and falls back to project path inference otherwise.
diff --git a/tools/cad/integrations/kicad/yiacad_kicad_plugin/_common.py b/tools/cad/integrations/kicad/yiacad_kicad_plugin/_common.py
index 29acb16..199d9f2 100644
--- a/tools/cad/integrations/kicad/yiacad_kicad_plugin/_common.py
+++ b/tools/cad/integrations/kicad/yiacad_kicad_plugin/_common.py
@@ -1,11 +1,18 @@
from __future__ import annotations
+import importlib.util
import json
import os
import subprocess
from datetime import datetime, timezone
from pathlib import Path
+from kill_life.yiacad_action_registry import (
+ yiacad_action_inputs,
+ yiacad_actions_for_surface,
+ yiacad_command_for_alias,
+)
+
SESSION_VERSION = 1
MAX_SESSION_MESSAGES = 50
@@ -66,6 +73,38 @@ def _project_inputs(source_path: str) -> dict[str, str]:
return {"source_path": str(path), "board": "", "schematic": ""}
+def available_actions(surface: str = "yiacad-kicad") -> list[dict]:
+ return list(yiacad_actions_for_surface(surface))
+
+
+def resolve_kicad_source_path(pcbnew_module=None, env: dict[str, str] | None = None) -> tuple[str, str]:
+ env = env or os.environ
+ ipc_path = env.get("KICAD_IPC_PROJECT_PATH", "").strip()
+ if ipc_path:
+ return ipc_path, "ipc-api-env"
+
+ board_path = env.get("KICAD_CURRENT_BOARD", "").strip()
+ if board_path:
+ return board_path, "board-env"
+
+ if pcbnew_module is not None:
+ try:
+ board = pcbnew_module.GetBoard()
+ if board is not None:
+ file_name = board.GetFileName() or ""
+ if file_name:
+ backend = "kicad-python-runtime" if importlib.util.find_spec("kicad") else "pcbnew-runtime"
+ return str(file_name), backend
+ except Exception:
+ pass
+
+ return "", "unresolved"
+
+
+def selection_summary(source_path: str) -> list[str]:
+ return [source_path] if source_path else []
+
+
def _now_iso() -> str:
return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
@@ -74,7 +113,7 @@ def _default_session() -> dict:
return {
"version": SESSION_VERSION,
"updated_at": "",
- "last_intent": "board-review",
+ "last_intent": "kicad-erc-drc",
"last_prompt": "",
"last_source_path": "",
"messages": [],
@@ -159,20 +198,13 @@ def run_intent(surface: str, intent: str, prompt: str, source_path: str, selecti
del prompt
del selection
inputs = _project_inputs(source_path)
- command = {
- "board-review": "kicad-erc-drc",
- "erc-drc-assist": "kicad-erc-drc",
- "bom-footprint-audit": "bom-review",
- "ecad-mcad-sync": "ecad-mcad-sync",
- }.get(intent, "status")
+ command = yiacad_command_for_alias("yiacad-kicad", intent) or "status"
args = ["--surface", surface, "--json-output", command]
- if inputs["source_path"]:
- args.extend(["--source-path", inputs["source_path"]])
- if inputs["board"]:
- args.extend(["--board", inputs["board"]])
- if inputs["schematic"] and command in {"kicad-erc-drc", "bom-review", "ecad-mcad-sync"}:
- args.extend(["--schematic", inputs["schematic"]])
+ for key in yiacad_action_inputs(command):
+ value = inputs.get(key, "")
+ if value:
+ args.extend([f"--{key.replace('_', '-')}", value])
return _run_json_command(args)
diff --git a/tools/cad/integrations/kicad/yiacad_kicad_plugin/yiacad_action.py b/tools/cad/integrations/kicad/yiacad_kicad_plugin/yiacad_action.py
index 437ecfd..fbfddac 100644
--- a/tools/cad/integrations/kicad/yiacad_kicad_plugin/yiacad_action.py
+++ b/tools/cad/integrations/kicad/yiacad_kicad_plugin/yiacad_action.py
@@ -2,13 +2,16 @@ from __future__ import annotations
from ._common import (
append_session_message,
+ available_actions,
clear_session,
fetch_status_payload,
load_session,
open_path,
remember_session_state,
repo_root,
+ resolve_kicad_source_path,
run_intent,
+ selection_summary,
)
try:
@@ -19,29 +22,7 @@ except Exception: # pragma: no cover
wx = None
-INTENTS = [
- "board-review",
- "erc-drc-assist",
- "bom-footprint-audit",
- "ecad-mcad-sync",
-]
-
-
-def _board_path() -> str:
- if pcbnew is None:
- return ""
- try:
- board = pcbnew.GetBoard()
- if board is None:
- return ""
- return board.GetFileName() or ""
- except Exception:
- return ""
-
-
-def _selection_summary() -> list[str]:
- path = _board_path()
- return [path] if path else []
+REGISTRY_ACTIONS = [entry for entry in available_actions() if entry["transport_command"] != "status"]
def _result_message(payload: dict) -> str:
@@ -94,11 +75,15 @@ if wx is not None:
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
session = load_session()
- default_source = _board_path() or session.get("last_source_path") or ""
+ default_source, _source_backend = resolve_kicad_source_path(pcbnew)
+ default_source = default_source or session.get("last_source_path") or ""
+ self.actions = list(REGISTRY_ACTIONS)
- self.intent = wx.Choice(panel, choices=INTENTS)
- if session.get("last_intent") in INTENTS:
- self.intent.SetStringSelection(session["last_intent"])
+ self.intent = wx.Choice(panel, choices=[entry["display_name"] for entry in self.actions])
+ last_intent = session.get("last_intent")
+ commands = [entry["transport_command"] for entry in self.actions]
+ if last_intent in commands:
+ self.intent.SetSelection(commands.index(last_intent))
else:
self.intent.SetSelection(0)
self.source = wx.TextCtrl(panel, value=default_source, style=wx.TE_READONLY)
@@ -110,7 +95,7 @@ if wx is not None:
self.transcript = wx.TextCtrl(panel, style=wx.TE_MULTILINE | wx.TE_READONLY)
self.refresh_transcript()
- sizer.Add(wx.StaticText(panel, label="Intent"), 0, wx.ALL, 8)
+ sizer.Add(wx.StaticText(panel, label="Action"), 0, wx.ALL, 8)
sizer.Add(self.intent, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8)
sizer.Add(wx.StaticText(panel, label="Board / source path"), 0, wx.ALL, 8)
sizer.Add(self.source, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 8)
@@ -139,8 +124,9 @@ if wx is not None:
panel.SetSizer(sizer)
def persist_state(self) -> None:
+ command = self.actions[self.intent.GetSelection()]["transport_command"]
remember_session_state(
- self.intent.GetStringSelection(),
+ command,
self.prompt.GetValue().strip(),
self.source.GetValue().strip(),
)
@@ -152,26 +138,27 @@ if wx is not None:
def on_run(self, _event) -> None:
prompt = self.prompt.GetValue().strip()
source_path = self.source.GetValue().strip()
- intent = self.intent.GetStringSelection()
+ action = self.actions[self.intent.GetSelection()]
+ command = action["transport_command"]
self.persist_state()
append_session_message(
"user",
prompt or "(no additional task context provided)",
- intent=intent,
+ intent=command,
source_path=source_path,
)
payload = run_intent(
"yiacad-desktop",
- intent,
+ command,
prompt,
source_path,
- _selection_summary(),
+ selection_summary(source_path),
)
result_text = _result_message(payload)
append_session_message(
"assistant",
result_text,
- intent=intent,
+ intent=command,
source_path=source_path,
status=str(payload.get("status") or ""),
)
diff --git a/tools/cad/yiacad_ai_bridge.py b/tools/cad/yiacad_ai_bridge.py
old mode 100755
new mode 100644
index 86bdc21..a81b36d
--- a/tools/cad/yiacad_ai_bridge.py
+++ b/tools/cad/yiacad_ai_bridge.py
@@ -1,16 +1,18 @@
#!/usr/bin/env python3
-"""Shared request bridge for KiCad and FreeCAD AI-native helpers."""
+"""Compatibility shim that delegates legacy desktop requests to the YiACAD backend client."""
from __future__ import annotations
import argparse
import json
-from datetime import datetime
+import subprocess
from pathlib import Path
from typing import Any
+from kill_life.yiacad_action_registry import yiacad_action_inputs, yiacad_command_for_alias
+
ROOT = Path(__file__).resolve().parents[2]
-REQUEST_DIR = ROOT / "artifacts" / "cad-ai-requests"
+BACKEND_CLIENT = ROOT / "tools" / "cad" / "yiacad_backend_client.py"
YIACAD_STATUS = ROOT / "artifacts" / "cad-fusion" / "yiacad-fusion-last-status.md"
@@ -29,10 +31,6 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args()
-def ensure_request_dir() -> None:
- REQUEST_DIR.mkdir(parents=True, exist_ok=True)
-
-
def selection_from_json(raw: str) -> list[Any]:
try:
payload = json.loads(raw)
@@ -43,12 +41,6 @@ def selection_from_json(raw: str) -> list[Any]:
return [payload]
-def latest_request() -> Path | None:
- ensure_request_dir()
- files = sorted(REQUEST_DIR.glob("*.json"), key=lambda item: item.stat().st_mtime, reverse=True)
- return files[0] if files else None
-
-
def latest_status_excerpt() -> list[str]:
if not YIACAD_STATUS.exists():
return []
@@ -56,45 +48,74 @@ def latest_status_excerpt() -> list[str]:
return lines[:12]
+def infer_inputs(source_path: str) -> dict[str, str]:
+ if not source_path:
+ return {"source_path": "", "board": "", "schematic": "", "freecad_document": ""}
+ path = Path(source_path).expanduser()
+ stem = path.with_suffix("")
+ board = stem.with_suffix(".kicad_pcb")
+ schematic = stem.with_suffix(".kicad_sch")
+ freecad_document = stem.with_suffix(".FCStd")
+ return {
+ "source_path": str(path),
+ "board": str(board) if board.exists() else "",
+ "schematic": str(schematic) if schematic.exists() else "",
+ "freecad_document": str(path if path.suffix == ".FCStd" else freecad_document) if (path if path.suffix == ".FCStd" else freecad_document).exists() else "",
+ }
+
+
+def run_backend(command: str, *, source_path: str = "") -> dict:
+ args = ["python3", str(BACKEND_CLIENT), "--surface", "yiacad-desktop", "--json-output", command]
+ inputs = infer_inputs(source_path)
+ for key in yiacad_action_inputs(command):
+ value = inputs.get(key, "")
+ if value:
+ args.extend([f"--{key.replace('_', '-')}", value])
+ proc = subprocess.run(
+ args,
+ cwd=ROOT,
+ text=True,
+ capture_output=True,
+ check=False,
+ timeout=30,
+ )
+ if proc.returncode != 0 and not proc.stdout.strip():
+ raise RuntimeError(proc.stderr.strip() or "YiACAD backend client failed")
+ return json.loads(proc.stdout.strip() or "{}")
+
+
def command_request(args: argparse.Namespace) -> int:
- ensure_request_dir()
- stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- request_path = REQUEST_DIR / f"{stamp}_{args.surface}_{args.intent}.json"
- payload = {
- "id": request_path.stem,
- "created_at": datetime.now().isoformat(timespec="seconds"),
+ surface_key = "yiacad-kicad" if args.surface == "kicad" else "yiacad-freecad"
+ command = yiacad_command_for_alias(surface_key, args.intent) or "status"
+ payload = run_backend(command, source_path=args.source_path)
+ compatibility = {
+ "status": payload.get("status", "blocked"),
"surface": args.surface,
"intent": args.intent,
- "prompt": args.prompt,
- "source_path": args.source_path,
+ "transport_command": command,
"selection": selection_from_json(args.selection_json),
- "status_hint": str(YIACAD_STATUS),
+ "request_path": "",
+ "uiux_output": payload.get("uiux_output"),
+ "summary": payload.get("summary"),
+ "degraded_reasons": payload.get("degraded_reasons", []),
+ "next_steps": payload.get("next_steps", []),
+ "payload": payload,
}
- request_path.write_text(json.dumps(payload, ensure_ascii=True, indent=2) + "\n", encoding="utf-8")
- print(
- json.dumps(
- {
- "status": "queued",
- "request_path": str(request_path),
- "surface": args.surface,
- "intent": args.intent,
- },
- ensure_ascii=True,
- )
- )
- return 0
+ print(json.dumps(compatibility, ensure_ascii=True))
+ return 0 if payload.get("status") != "blocked" else 1
def command_status(_: argparse.Namespace) -> int:
- last_request = latest_request()
- payload = {
- "request_dir": str(REQUEST_DIR),
- "latest_request": str(last_request) if last_request else "",
+ payload = run_backend("status")
+ compatibility = {
+ "request_dir": "",
+ "latest_request": "",
"yiacad_status": str(YIACAD_STATUS),
"yiacad_status_exists": YIACAD_STATUS.exists(),
"yiacad_status_excerpt": latest_status_excerpt(),
+ "payload": payload,
}
- print(json.dumps(payload, ensure_ascii=True))
+ print(json.dumps(compatibility, ensure_ascii=True))
return 0
diff --git a/tools/cad/yiacad_backend.py b/tools/cad/yiacad_backend.py
index 3406d87..f6470fe 100644
--- a/tools/cad/yiacad_backend.py
+++ b/tools/cad/yiacad_backend.py
@@ -18,6 +18,7 @@ FUSION_STATUS = ROOT / "artifacts" / "cad-fusion" / "yiacad-fusion-last-status.m
KICAD_APP_CLI = Path("/Applications/KiCad/KiCad.app/Contents/MacOS/kicad-cli")
FREECAD_APP_CMD = Path("/Applications/FreeCAD.app/Contents/MacOS/FreeCADCmd")
FREECAD_APP_GUI = Path("/Applications/FreeCAD.app/Contents/MacOS/FreeCAD")
+COMMAND_TIMEOUT_SEC = 2.0
ENGINE_BASELINE = {
"kicad": ">=10.0",
"freecad": ">=1.1",
@@ -80,8 +81,12 @@ def version_gte(detected: str | None, minimum: str | None) -> bool | None:
return padded_detected >= minimum_tuple
-def command_output(command: list[str]) -> tuple[int, str]:
- proc = subprocess.run(command, text=True, capture_output=True)
+def command_output(command: list[str], timeout_sec: float = COMMAND_TIMEOUT_SEC) -> tuple[int, str]:
+ try:
+ proc = subprocess.run(command, text=True, capture_output=True, timeout=timeout_sec)
+ except subprocess.TimeoutExpired:
+ rendered = " ".join(command)
+ return (124, f"command timed out after {timeout_sec:.1f}s: {rendered}")
combined = "\n".join(part.strip() for part in (proc.stdout, proc.stderr) if part.strip()).strip()
return proc.returncode, combined
@@ -110,16 +115,20 @@ def bundle_version(binary: str | None) -> str | None:
return None
-def probe_engine_version(binary: str | None, probes: list[list[str]]) -> str | None:
- if not binary:
- return None
- for probe in probes:
- rc, text = command_output([binary, *probe])
- if rc == 0 and text:
- match = re.search(r"(\d+(?:\.\d+){0,2})", text)
- if match:
- return match.group(1)
- return bundle_version(binary)
+def probe_engine_version(
+ probe_binary: str | None,
+ probes: list[list[str]],
+ *,
+ bundle_binary: str | None = None,
+) -> str | None:
+ if probe_binary:
+ for probe in probes:
+ rc, text = command_output([probe_binary, *probe])
+ if rc == 0 and text:
+ match = re.search(r"(\d+(?:\.\d+){0,2})", text)
+ if match:
+ return match.group(1)
+ return bundle_version(bundle_binary or probe_binary)
def engine_entry(
@@ -128,9 +137,11 @@ def engine_entry(
binary: str | None,
required_version: str | None,
probes: list[list[str]],
+ bundle_binary: str | None = None,
) -> dict:
- detected_version = probe_engine_version(binary, probes)
- if not binary:
+ reported_binary = binary or bundle_binary
+ detected_version = probe_engine_version(binary, probes, bundle_binary=bundle_binary)
+ if not reported_binary:
status = "blocked"
reason = "missing-binary"
elif required_version and required_version != "installed":
@@ -143,20 +154,20 @@ def engine_entry(
reason = "version-unresolved"
else:
status = "done"
- reason = "ready"
+ reason = "ready" if binary else "bundle-ready"
elif detected_version is None:
status = "degraded"
reason = "version-unresolved"
else:
status = "done"
- reason = "ready"
+ reason = "ready" if binary else "bundle-ready"
return {
"name": name,
"integrated": True,
- "binary": binary,
+ "binary": reported_binary,
"required_version": required_version,
"detected_version": detected_version,
- "available": binary is not None,
+ "available": reported_binary is not None,
"status": status,
"reason": reason,
}
@@ -164,7 +175,8 @@ def engine_entry(
def detect_integrated_engines() -> dict:
kicad_binary = resolve_binary(KICAD_APP_CLI, "kicad-cli")
- freecad_binary = resolve_binary(FREECAD_APP_CMD, FREECAD_APP_GUI, "FreeCADCmd", "freecadcmd", "FreeCAD")
+ freecad_binary = resolve_binary(FREECAD_APP_CMD, "FreeCADCmd", "freecadcmd")
+ freecad_bundle_binary = resolve_binary(FREECAD_APP_GUI, "FreeCAD")
kibot_binary = resolve_binary("kibot")
kiauto_binary = resolve_binary("pcbnew_do", "eeschema_do", "kicad2step_do", "kiauto")
return {
@@ -179,6 +191,7 @@ def detect_integrated_engines() -> dict:
binary=freecad_binary,
required_version=ENGINE_BASELINE["freecad"],
probes=[["--version"], ["-v"]],
+ bundle_binary=freecad_bundle_binary,
),
"kibot": engine_entry(
name="KiBot",
diff --git a/tools/cad/yiacad_backend_client.py b/tools/cad/yiacad_backend_client.py
index c01d3ae..f97cc72 100644
--- a/tools/cad/yiacad_backend_client.py
+++ b/tools/cad/yiacad_backend_client.py
@@ -12,6 +12,12 @@ import urllib.error
import urllib.request
from pathlib import Path
+from kill_life.yiacad_action_registry import (
+ INPUT_ARGUMENTS,
+ yiacad_action_inputs,
+ yiacad_actions,
+)
+
ROOT = Path(__file__).resolve().parents[2]
SERVICE_SCRIPT = ROOT / "tools" / "cad" / "yiacad_backend_service.py"
@@ -83,6 +89,12 @@ def direct_fallback(argv: list[str]) -> int:
return proc.returncode
+def add_registry_arguments(parser: argparse.ArgumentParser, command: str) -> None:
+ for name in yiacad_action_inputs(command):
+ spec = INPUT_ARGUMENTS[name]
+ parser.add_argument(spec["flag"], default="", help=spec["help"])
+
+
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="YiACAD backend client")
parser.add_argument("--host", default=DEFAULT_HOST, help="Service host")
@@ -98,41 +110,18 @@ def parse_args() -> argparse.Namespace:
subparsers.add_parser("health", help="Check YiACAD backend service health")
subparsers.add_parser("projects-current", help="Read the latest YiACAD context snapshot")
subparsers.add_parser("artifacts", help="Read the latest YiACAD artifact index")
-
- status = subparsers.add_parser("status", help="YiACAD status")
- status.add_argument("--source-path", default="")
-
- erc = subparsers.add_parser("kicad-erc-drc", help="Run ERC/DRC through service")
- erc.add_argument("--source-path", default="")
- erc.add_argument("--board", default="")
- erc.add_argument("--schematic", default="")
-
- bom = subparsers.add_parser("bom-review", help="Run BOM review through service")
- bom.add_argument("--source-path", default="")
- bom.add_argument("--schematic", default="")
-
- sync = subparsers.add_parser("ecad-mcad-sync", help="Run ECAD/MCAD sync through service")
- sync.add_argument("--source-path", default="")
- sync.add_argument("--board", default="")
- sync.add_argument("--schematic", default="")
- sync.add_argument("--freecad-document", default="")
-
- package = subparsers.add_parser("manufacturing-package", help="Build YiACAD manufacturing package through service")
- package.add_argument("--source-path", default="")
- package.add_argument("--board", default="")
- package.add_argument("--schematic", default="")
- package.add_argument("--kibot-config", default="")
-
- kiauto = subparsers.add_parser("kiauto-checks", help="Run KiAuto checks through service")
- kiauto.add_argument("--source-path", default="")
- kiauto.add_argument("--board", default="")
- kiauto.add_argument("--schematic", default="")
+ for entry in yiacad_actions():
+ subparser = subparsers.add_parser(
+ entry["transport_command"],
+ help=entry["description"],
+ )
+ add_registry_arguments(subparser, entry["transport_command"])
return parser.parse_args()
def payload_from_args(args: argparse.Namespace) -> dict:
payload = {"command": args.command, "surface": args.surface}
- for key in ("source_path", "board", "schematic", "freecad_document", "kibot_config"):
+ for key in yiacad_action_inputs(args.command):
if hasattr(args, key):
payload[key] = getattr(args, key)
return payload
@@ -165,7 +154,7 @@ def main() -> int:
direct_argv = [args.command]
if args.surface:
direct_argv.extend(["--surface", args.surface])
- for key in ("source_path", "board", "schematic", "freecad_document", "kibot_config"):
+ for key in yiacad_action_inputs(args.command):
if key in payload and payload[key]:
direct_argv.extend([f"--{key.replace('_', '-')}", payload[key]])
if args.json_output:
diff --git a/tools/cad/yiacad_backend_service.py b/tools/cad/yiacad_backend_service.py
index b39caf7..a13e6b8 100644
--- a/tools/cad/yiacad_backend_service.py
+++ b/tools/cad/yiacad_backend_service.py
@@ -13,6 +13,8 @@ import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
+from kill_life.yiacad_action_registry import get_yiacad_action, yiacad_action_id, yiacad_action_inputs
+
try:
import yiacad_native_ops as native_ops
from yiacad_backend import (
@@ -76,45 +78,8 @@ def uiux_contract_from_failure(
def build_args(command: str, payload: dict) -> argparse.Namespace:
common = {"command": command, "json_output": True, "surface": payload.get("surface", "yiacad-api")}
- if command == "status":
- return argparse.Namespace(**common)
- if command == "kicad-erc-drc":
- return argparse.Namespace(
- **common,
- source_path=payload.get("source_path", ""),
- board=payload.get("board", ""),
- schematic=payload.get("schematic", ""),
- )
- if command == "bom-review":
- return argparse.Namespace(
- **common,
- source_path=payload.get("source_path", ""),
- schematic=payload.get("schematic", ""),
- )
- if command == "ecad-mcad-sync":
- return argparse.Namespace(
- **common,
- source_path=payload.get("source_path", ""),
- board=payload.get("board", ""),
- schematic=payload.get("schematic", ""),
- freecad_document=payload.get("freecad_document", ""),
- )
- if command == "manufacturing-package":
- return argparse.Namespace(
- **common,
- source_path=payload.get("source_path", ""),
- board=payload.get("board", ""),
- schematic=payload.get("schematic", ""),
- kibot_config=payload.get("kibot_config", ""),
- )
- if command == "kiauto-checks":
- return argparse.Namespace(
- **common,
- source_path=payload.get("source_path", ""),
- board=payload.get("board", ""),
- schematic=payload.get("schematic", ""),
- )
- raise KeyError(f"Unsupported YiACAD command: {command}")
+ common.update({name: payload.get(name, "") for name in yiacad_action_inputs(command)})
+ return argparse.Namespace(**common)
def latest_context_payload() -> dict | None:
@@ -151,15 +116,8 @@ def latest_artifacts_payload() -> dict:
def dispatch_command(command: str, payload: dict) -> tuple[int, dict]:
- mapping = {
- "status": native_ops.command_status,
- "kicad-erc-drc": native_ops.command_kicad_erc_drc,
- "bom-review": native_ops.command_bom_review,
- "ecad-mcad-sync": native_ops.command_ecad_mcad_sync,
- "manufacturing-package": native_ops.command_manufacturing_package,
- "kiauto-checks": native_ops.command_kiauto_checks,
- }
- handler = mapping[command]
+ entry = get_yiacad_action(command)
+ handler = getattr(native_ops, entry["native_handler"])
args = build_args(command, payload)
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
@@ -175,7 +133,7 @@ def dispatch_command(command: str, payload: dict) -> tuple[int, dict]:
pass
contract = build_uiux_output(
surface=payload.get("surface", "yiacad-api"),
- action=command,
+ action=yiacad_action_id(command),
execution_mode="background",
status="done" if rc == 0 else "blocked",
severity="info" if rc == 0 else "error",
diff --git a/tools/cad/yiacad_fusion_lot.sh b/tools/cad/yiacad_fusion_lot.sh
index 883bf6d..2295ff5 100755
--- a/tools/cad/yiacad_fusion_lot.sh
+++ b/tools/cad/yiacad_fusion_lot.sh
@@ -10,8 +10,9 @@ DEFAULT_BRANCH="${KILL_LIFE_FORK_BRANCH:-kill-life-ai-native}"
LOG_DIR="${ROOT_DIR}/artifacts/cad-fusion"
TIMESTAMP="$(date '+%Y%m%d_%H%M%S')"
LOT_ID="yiacad-fusion"
-OWNER_TEAM="CAD-Fusion"
-OWNER_AGENT="CAD-Fusion"
+OWNER_TEAM="Embedded-CAD"
+OWNER_AGENT="Embedded-CAD"
+OWNER_SUBAGENT="CAD-Fusion"
LAST_LOG_FILE="${LOG_DIR}/yiacad-fusion-last.log"
LAST_STATUS_FILE="${LOG_DIR}/yiacad-fusion-last-status.md"
ACTION="prepare"
@@ -117,6 +118,7 @@ write_status_snapshot() {
printf -- '- lot_id: %s\n' "$LOT_ID"
printf -- '- owner_team: %s\n' "$OWNER_TEAM"
printf -- '- owner_agent: %s\n' "$OWNER_AGENT"
+ printf -- '- owner_subagent: %s\n' "$OWNER_SUBAGENT"
printf -- '- root: %s\n' "$ROOT_DIR"
printf -- '- base_dir: %s\n' "$BASE_DIR"
printf -- '- owner: %s\n' "$OWNER"
diff --git a/tools/cad/yiacad_native_ops.py b/tools/cad/yiacad_native_ops.py
index fcd90d1..01eb629 100755
--- a/tools/cad/yiacad_native_ops.py
+++ b/tools/cad/yiacad_native_ops.py
@@ -15,6 +15,13 @@ import time
from pathlib import Path
from typing import Iterable
+from kill_life.yiacad_action_registry import (
+ INPUT_ARGUMENTS,
+ yiacad_action_id,
+ yiacad_actions,
+ yiacad_action_inputs,
+)
+
try:
from yiacad_backend import (
artifact_entry,
@@ -230,7 +237,14 @@ def print_contract_or_fallback(args: argparse.Namespace, payload: dict, fallback
print(str(fallback))
+def add_registry_arguments(parser: argparse.ArgumentParser, command: str) -> None:
+ for name in yiacad_action_inputs(command):
+ spec = INPUT_ARGUMENTS[name]
+ parser.add_argument(spec["flag"], default="", help=spec["help"])
+
+
def command_status(args: argparse.Namespace) -> int:
+ action_id = yiacad_action_id("status")
engine_status = detect_integrated_engines()
degraded_reasons = collect_engine_reasons(engine_status)
lines = ["# YiACAD Native Status", ""]
@@ -250,7 +264,7 @@ def command_status(args: argparse.Namespace) -> int:
severity = "info" if status == "done" else "warning"
output_payload = build_uiux_output(
surface=requested_surface(args, "tui"),
- action="status.surface",
+ action=action_id,
execution_mode="batch",
status=status,
severity=severity,
@@ -279,6 +293,7 @@ def command_status(args: argparse.Namespace) -> int:
def command_kicad_erc_drc(args: argparse.Namespace) -> int:
+ action_id = yiacad_action_id("kicad-erc-drc")
started_at = time.time()
board = Path(args.board).expanduser().resolve() if args.board else guess_board_from_source(args.source_path)
schematic = (
@@ -312,7 +327,7 @@ def command_kicad_erc_drc(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="review.erc_drc",
+ action=action_id,
summary="YiACAD could not start KiCad review because the KiCad runtime baseline is not met.",
details="Install KiCad 10+ with the YiACAD AI layer available to the local runtime, then rerun the review.",
context_ref=context["context_ref"],
@@ -333,7 +348,7 @@ def command_kicad_erc_drc(args: argparse.Namespace) -> int:
payload["error"] = "No KiCad board or schematic could be resolved."
output_payload = build_uiux_output(
surface=surface,
- action="review.erc_drc",
+ action=action_id,
execution_mode="batch",
status="blocked",
severity="error",
@@ -445,7 +460,7 @@ def command_kicad_erc_drc(args: argparse.Namespace) -> int:
artifacts.append(artifact_entry(path, "log", label))
output_payload = build_uiux_output(
surface=surface,
- action="review.erc_drc",
+ action=action_id,
execution_mode="batch",
status=status,
severity=severity,
@@ -466,6 +481,7 @@ def command_kicad_erc_drc(args: argparse.Namespace) -> int:
def command_bom_review(args: argparse.Namespace) -> int:
+ action_id = yiacad_action_id("bom-review")
started_at = time.time()
schematic = (
Path(args.schematic).expanduser().resolve() if args.schematic else guess_schematic_from_source(args.source_path)
@@ -489,7 +505,7 @@ def command_bom_review(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="review.bom",
+ action=action_id,
summary="YiACAD could not start BOM review because the KiCad runtime baseline is not met.",
details="Install or upgrade KiCad 10+ before running YiACAD BOM review.",
context_ref=context["context_ref"],
@@ -514,7 +530,7 @@ def command_bom_review(args: argparse.Namespace) -> int:
}
output_payload = build_uiux_output(
surface=surface,
- action="review.bom",
+ action=action_id,
execution_mode="batch",
status="blocked",
severity="error",
@@ -613,7 +629,7 @@ def command_bom_review(args: argparse.Namespace) -> int:
artifacts.append(artifact_entry(path, "log", label))
output_payload = build_uiux_output(
surface=surface,
- action="review.bom",
+ action=action_id,
execution_mode="batch",
status=status,
severity=severity,
@@ -667,6 +683,7 @@ def export_freecad_document(document_path: Path, output_path: Path, run_dir: Pat
def command_ecad_mcad_sync(args: argparse.Namespace) -> int:
+ action_id = yiacad_action_id("ecad-mcad-sync")
started_at = time.time()
board = Path(args.board).expanduser().resolve() if args.board else guess_board_from_source(args.source_path)
schematic = (
@@ -698,7 +715,7 @@ def command_ecad_mcad_sync(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="sync.ecad_mcad",
+ action=action_id,
summary="YiACAD could not export the ECAD side because the KiCad runtime baseline is not met.",
details="Install or upgrade KiCad 10+ before running ECAD/MCAD sync with board exports.",
context_ref=context["context_ref"],
@@ -719,7 +736,7 @@ def command_ecad_mcad_sync(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="sync.ecad_mcad",
+ action=action_id,
summary="YiACAD could not export the MCAD side because the FreeCAD runtime baseline is not met.",
details="Install or upgrade FreeCAD 1.1+ before running ECAD/MCAD sync with MCAD exports.",
context_ref=context["context_ref"],
@@ -821,7 +838,7 @@ def command_ecad_mcad_sync(args: argparse.Namespace) -> int:
artifacts.append(artifact_entry(step_path, "export", f"STEP export: {step_path.name}"))
output_payload = build_uiux_output(
surface=surface,
- action="sync.ecad_mcad",
+ action=action_id,
execution_mode="batch",
status=status,
severity=severity,
@@ -842,6 +859,7 @@ def command_ecad_mcad_sync(args: argparse.Namespace) -> int:
def command_manufacturing_package(args: argparse.Namespace) -> int:
+ action_id = yiacad_action_id("manufacturing-package")
started_at = time.time()
board = Path(args.board).expanduser().resolve() if args.board else guess_board_from_source(args.source_path)
schematic = (
@@ -878,7 +896,7 @@ def command_manufacturing_package(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="manufacturing.export",
+ action=action_id,
summary="YiACAD could not build a manufacturing package because no KiCad inputs were resolved.",
details="Provide --board, --schematic or a valid --source-path with KiCad project files present.",
context_ref=context["context_ref"],
@@ -900,7 +918,7 @@ def command_manufacturing_package(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="manufacturing.export",
+ action=action_id,
summary="YiACAD could not build a manufacturing package because the KiCad runtime baseline is not met.",
details="Install or upgrade KiCad 10+ before running YiACAD manufacturing export.",
context_ref=context["context_ref"],
@@ -1034,7 +1052,7 @@ def command_manufacturing_package(args: argparse.Namespace) -> int:
output_payload = build_uiux_output(
surface=surface,
- action="manufacturing.export",
+ action=action_id,
execution_mode="batch",
status=status,
severity=severity,
@@ -1055,6 +1073,7 @@ def command_manufacturing_package(args: argparse.Namespace) -> int:
def command_kiauto_checks(args: argparse.Namespace) -> int:
+ action_id = yiacad_action_id("kiauto-checks")
started_at = time.time()
board = Path(args.board).expanduser().resolve() if args.board else guess_board_from_source(args.source_path)
schematic = (
@@ -1089,7 +1108,7 @@ def command_kiauto_checks(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="manufacturing.validate",
+ action=action_id,
summary="YiACAD could not run KiAuto checks because no board was resolved.",
details="Provide --board or a valid --source-path with a .kicad_pcb present.",
context_ref=context["context_ref"],
@@ -1111,7 +1130,7 @@ def command_kiauto_checks(args: argparse.Namespace) -> int:
run_dir=run_dir,
context_path=context_path,
surface=surface,
- action="manufacturing.validate",
+ action=action_id,
summary="YiACAD could not run KiAuto checks because the KiCad or KiAuto runtime baseline is not met.",
details="Install KiCad 10+ and a working KiAuto runtime before rerunning manufacturing validation.",
context_ref=context["context_ref"],
@@ -1164,7 +1183,7 @@ def command_kiauto_checks(args: argparse.Namespace) -> int:
]
output_payload = build_uiux_output(
surface=surface,
- action="manufacturing.validate",
+ action=action_id,
execution_mode="batch",
status=status,
severity=severity,
@@ -1198,48 +1217,22 @@ def build_parser() -> argparse.ArgumentParser:
default="",
help="Canonical YiACAD client surface override (e.g. yiacad-web, yiacad-desktop, tui)",
)
-
- status = subparsers.add_parser("status", parents=[common], help="Show YiACAD fusion and native surface status")
- status.set_defaults(func=command_status)
-
- erc_drc = subparsers.add_parser("kicad-erc-drc", parents=[common], help="Run KiCad ERC and DRC reports")
- erc_drc.add_argument("--source-path", default="", help="Any project path to infer KiCad files from")
- erc_drc.add_argument("--board", default="", help="Path to .kicad_pcb")
- erc_drc.add_argument("--schematic", default="", help="Path to .kicad_sch")
- erc_drc.set_defaults(func=command_kicad_erc_drc)
-
- bom = subparsers.add_parser("bom-review", parents=[common], help="Export and summarize a KiCad BOM")
- bom.add_argument("--source-path", default="", help="Any project path to infer schematic from")
- bom.add_argument("--schematic", default="", help="Path to .kicad_sch")
- bom.set_defaults(func=command_bom_review)
-
- sync = subparsers.add_parser("ecad-mcad-sync", parents=[common], help="Export KiCad and FreeCAD STEP artifacts for sync")
- sync.add_argument("--source-path", default="", help="Any project path to infer paired files from")
- sync.add_argument("--board", default="", help="Path to .kicad_pcb")
- sync.add_argument("--schematic", default="", help="Path to .kicad_sch")
- sync.add_argument("--freecad-document", default="", help="Path to .FCStd")
- sync.set_defaults(func=command_ecad_mcad_sync)
-
- package = subparsers.add_parser(
- "manufacturing-package",
- parents=[common],
- help="Build a YiACAD manufacturing package with KiBot/fab outputs",
- )
- package.add_argument("--source-path", default="", help="Any project path to infer KiCad files from")
- package.add_argument("--board", default="", help="Path to .kicad_pcb")
- package.add_argument("--schematic", default="", help="Path to .kicad_sch")
- package.add_argument("--kibot-config", default="", help="Optional path to a KiBot config for direct exports")
- package.set_defaults(func=command_manufacturing_package)
-
- kiauto = subparsers.add_parser(
- "kiauto-checks",
- parents=[common],
- help="Run YiACAD KiAuto runtime checks",
- )
- kiauto.add_argument("--source-path", default="", help="Any project path to infer KiCad files from")
- kiauto.add_argument("--board", default="", help="Path to .kicad_pcb")
- kiauto.add_argument("--schematic", default="", help="Path to .kicad_sch")
- kiauto.set_defaults(func=command_kiauto_checks)
+ handlers = {
+ "status": command_status,
+ "kicad-erc-drc": command_kicad_erc_drc,
+ "bom-review": command_bom_review,
+ "ecad-mcad-sync": command_ecad_mcad_sync,
+ "manufacturing-package": command_manufacturing_package,
+ "kiauto-checks": command_kiauto_checks,
+ }
+ for entry in yiacad_actions():
+ subparser = subparsers.add_parser(
+ entry["transport_command"],
+ parents=[common],
+ help=entry["description"],
+ )
+ add_registry_arguments(subparser, entry["transport_command"])
+ subparser.set_defaults(func=handlers[entry["transport_command"]])
return parser
diff --git a/tools/cockpit/intelligence_tui.sh b/tools/cockpit/intelligence_tui.sh
index 3332393..1450dc9 100755
--- a/tools/cockpit/intelligence_tui.sh
+++ b/tools/cockpit/intelligence_tui.sh
@@ -48,6 +48,7 @@ build_snapshot_json() {
from __future__ import annotations
import json
+import os
import subprocess
import sys
from datetime import datetime
@@ -167,12 +168,14 @@ def parse_all_open_tasks(text: str) -> list[str]:
def web_platform_health() -> dict[str, object]:
- """Probe Next.js, Yjs realtime server, and Redis/BullMQ queue.
+ """Probe Next.js, Yjs realtime server, Redis/BullMQ queue, and the EDA worker.
Primary: calls /api/ops/platform on the running Next.js app (T-AI-323).
Fallback: direct socket probes when the app is not running.
"""
import json
+ import socket
+ import urllib.parse
import urllib.request
platform_url = "http://localhost:3000/api/ops/platform"
@@ -187,9 +190,10 @@ def web_platform_health() -> dict[str, object]:
"nextjs": probes.get("next-js", {"status": "unknown"}),
"realtime": probes.get("yjs-realtime", {"status": "unknown"}),
"queue": probes.get("eda-queue", {"status": "unknown"}),
+ "worker": probes.get("eda-worker", {"status": "unknown"}),
},
"up_count": data.get("up_count", 0),
- "total": data.get("total", 3),
+ "total": data.get("total", 4),
"source": "platform-api",
}
except Exception:
@@ -197,29 +201,91 @@ def web_platform_health() -> dict[str, object]:
# --- Fallback: direct probes (app not running) ---
probes: dict[str, object] = {}
+ web_root = root / "web"
try:
req = urllib.request.urlopen("http://localhost:3000/", timeout=3)
- probes["nextjs"] = {"status": "up", "http_status": req.status}
+ probes["nextjs"] = {"status": "up", "http_status": req.status, "reason": "nextjs-ready"}
except Exception as exc:
- probes["nextjs"] = {"status": "down", "error": str(exc)[:120]}
+ probes["nextjs"] = {"status": "down", "error": str(exc)[:120], "reason": "nextjs-absent"}
try:
req = urllib.request.urlopen("http://localhost:1234/", timeout=3)
- probes["realtime"] = {"status": "up", "http_status": req.status}
+ probes["realtime"] = {"status": "up", "http_status": req.status, "reason": "yjs-ready"}
except Exception as exc:
- probes["realtime"] = {"status": "down", "error": str(exc)[:120]}
+ probes["realtime"] = {"status": "down", "error": str(exc)[:120], "reason": "yjs-absent"}
- try:
- import socket
- sock = socket.create_connection(("127.0.0.1", 6379), timeout=2)
- sock.sendall(b"LLEN bull:yiacad-eda:wait\r\n")
- data = sock.recv(256).decode("utf-8", errors="replace").strip()
- sock.close()
- depth = int(data[1:]) if data.startswith(":") else None
- probes["queue"] = {"status": "up", "queue_name": "yiacad-eda", "depth": depth}
- except Exception as exc:
- probes["queue"] = {"status": "down", "error": str(exc)[:120]}
+ redis_url = ""
+ for candidate in (
+ web_root / ".env.local",
+ web_root / ".env.development.local",
+ web_root / ".env",
+ ):
+ if not candidate.exists():
+ continue
+ for raw_line in candidate.read_text(encoding="utf-8").splitlines():
+ line = raw_line.strip()
+ if line.startswith("REDIS_URL="):
+ redis_url = line.split("=", 1)[1].strip()
+ break
+ if redis_url:
+ break
+
+ if not redis_url:
+ probes["queue"] = {
+ "status": "down",
+ "reason": "redis-env-missing",
+ "detail": "REDIS_URL is not configured in web/.env*.",
+ }
+ else:
+ parsed = urllib.parse.urlparse(redis_url)
+ host = parsed.hostname or "127.0.0.1"
+ port = parsed.port or 6379
+ queue_name = os.environ.get("EDA_QUEUE_NAME") or "yiacad-eda"
+ try:
+ sock = socket.create_connection((host, port), timeout=2)
+ sock.sendall(f"LLEN bull:{queue_name}:wait\r\n".encode("utf-8"))
+ data = sock.recv(256).decode("utf-8", errors="replace").strip()
+ sock.close()
+ depth = int(data[1:]) if data.startswith(":") else None
+ probes["queue"] = {
+ "status": "up",
+ "queue_name": queue_name,
+ "depth": depth,
+ "reason": "redis-ready",
+ }
+ except Exception as exc:
+ probes["queue"] = {
+ "status": "down",
+ "error": str(exc)[:120],
+ "reason": "redis-unreachable",
+ "detail": f"{host}:{port}",
+ }
+
+ worker_health = web_root / "project" / ".ci" / "worker-health.json"
+ if not worker_health.exists():
+ probes["worker"] = {
+ "status": "down",
+ "reason": "worker-absent",
+ }
+ else:
+ try:
+ payload = json.loads(worker_health.read_text(encoding="utf-8"))
+ updated_at = payload.get("updatedAt")
+ reason = payload.get("lastStatus") or "worker-running"
+ probes["worker"] = {
+ "status": "up" if payload.get("running") else "degraded",
+ "reason": reason,
+ "updated_at": updated_at,
+ "last_job_id": payload.get("lastJobId"),
+ "last_job_ms": payload.get("lastJobMs"),
+ }
+ except Exception as exc:
+ probes["worker"] = {
+ "status": "down",
+ "reason": "worker-health-unreadable",
+ "error": str(exc)[:120],
+ }
up_count = sum(1 for p in probes.values() if isinstance(p, dict) and p.get("status") == "up")
total = len(probes)
@@ -702,8 +768,8 @@ add_lane(
)
add_lane(
"Contracts",
- "Mesh-Contracts",
- "Contract-View",
+ "Schema-Guard",
+ "Catalog-Validator",
[
("summary-short-schema", (root / "specs" / "contracts" / "summary_short.schema.json").exists()),
("runtime-gateway-schema", (root / "specs" / "contracts" / "runtime_mcp_ia_gateway.schema.json").exists()),
@@ -748,8 +814,8 @@ add_lane(
)
add_lane(
"Extensions-Consumption",
- "Studio-Product",
- "Context-Builder",
+ "KillLife-Bridge",
+ "Schema-Consumer",
[
("studio-governance", (studio_root / "src" / "governance.ts").exists()),
("mesh-governance", (mesh_root / "src" / "governance.ts").exists()),
diff --git a/tools/cockpit/runtime_ai_gateway.sh b/tools/cockpit/runtime_ai_gateway.sh
index d82f57e..e5c2e57 100755
--- a/tools/cockpit/runtime_ai_gateway.sh
+++ b/tools/cockpit/runtime_ai_gateway.sh
@@ -22,12 +22,15 @@ INTELLIGENCE_SCRIPT="${RUNTIME_GATEWAY_INTELLIGENCE_SCRIPT:-${ROOT_DIR}/tools/co
MESH_SCRIPT="${RUNTIME_GATEWAY_MESH_SCRIPT:-${ROOT_DIR}/tools/cockpit/mesh_health_check.sh}"
MASCARADE_SCRIPT="${RUNTIME_GATEWAY_MASCARADE_SCRIPT:-${ROOT_DIR}/tools/cockpit/mascarade_runtime_health.sh}"
LANGFUSE_SCRIPT="${RUNTIME_GATEWAY_LANGFUSE_SCRIPT:-${ROOT_DIR}/tools/cockpit/langfuse_health.sh}"
+INFRA_VPS_SCRIPT="${RUNTIME_GATEWAY_INFRA_VPS_SCRIPT:-${ROOT_DIR}/tools/cockpit/infra_vps_healthcheck.sh}"
LANGFUSE_REPORT="${ROOT_DIR}/artifacts/ops/langfuse_health/latest.json"
INFRA_VPS_REPORT="${ROOT_DIR}/artifacts/cockpit/infra_vps_healthcheck_latest.json"
INTELLIGENCE_TIMEOUT_SEC="${RUNTIME_GATEWAY_INTELLIGENCE_TIMEOUT_SEC:-15}"
MESH_TIMEOUT_SEC="${RUNTIME_GATEWAY_MESH_TIMEOUT_SEC:-15}"
MASCARADE_TIMEOUT_SEC="${RUNTIME_GATEWAY_MASCARADE_TIMEOUT_SEC:-15}"
LANGFUSE_TIMEOUT_SEC="${RUNTIME_GATEWAY_LANGFUSE_TIMEOUT_SEC:-10}"
+INFRA_VPS_TIMEOUT_SEC="${RUNTIME_GATEWAY_INFRA_VPS_TIMEOUT_SEC:-20}"
+INFRA_VPS_MAX_AGE_SEC="${RUNTIME_GATEWAY_INFRA_VPS_MAX_AGE_SEC:-1800}"
usage() {
cat <<'EOF'
@@ -41,6 +44,8 @@ Options:
--intelligence-report
--mesh-report
--mascarade-report
+ --langfuse-report
+ --infra-vps-report
-h, --help
EOF
}
@@ -130,6 +135,13 @@ def fallback(reason: str, detail: str) -> dict[str, object]:
"langfuse_url": "unknown",
}
)
+ elif label == "infra_vps":
+ payload.update(
+ {
+ "component": "infra-vps-healthcheck",
+ "services": [],
+ }
+ )
return payload
@@ -203,6 +215,37 @@ refresh_sources() {
LANGFUSE_REPORT="${langfuse_out}"
}
+ensure_infra_vps_live_report() {
+ local refresh_needed=0
+ if [[ ! -f "${INFRA_VPS_REPORT}" ]]; then
+ refresh_needed=1
+ else
+ local age
+ age="$(python3 - "${INFRA_VPS_REPORT}" <<'PY'
+from __future__ import annotations
+
+import os
+import sys
+import time
+
+path = sys.argv[1]
+print(int(time.time() - os.path.getmtime(path)))
+PY
+)"
+ if [[ "${age}" -gt "${INFRA_VPS_MAX_AGE_SEC}" ]]; then
+ refresh_needed=1
+ fi
+ fi
+
+ if [[ "${refresh_needed}" -eq 1 ]]; then
+ local infra_out="${ARTIFACT_DIR}/infra_vps-${STAMP}.json"
+ log_line "INFO" "refreshing infra VPS health"
+ run_refresh_probe infra_vps "${INFRA_VPS_TIMEOUT_SEC}" "${infra_out}" "${LOAD_PROFILE}" \
+ bash "${INFRA_VPS_SCRIPT}" --json
+ INFRA_VPS_REPORT="${infra_out}"
+ fi
+}
+
emit_status_json() {
python3 - "${ROOT_DIR}" "${INTELLIGENCE_REPORT}" "${MESH_REPORT}" "${MASCARADE_REPORT}" "${LANGFUSE_REPORT}" "${RUN_LOG}" "${INFRA_VPS_REPORT:-}" <<'PY'
from __future__ import annotations
@@ -413,16 +456,24 @@ def build_web_platform_surface(ia_payload: dict[str, object] | None) -> tuple[di
next_steps: list[str] = []
for probe_name, probe_data in probes.items():
if probe_data.get("status") != "up":
- degraded.append(f"web-{probe_name}-down")
+ reason = str(probe_data.get("reason") or "").strip().lower().replace("_", "-")
+ degraded.append(f"web-{probe_name}-{reason or 'down'}")
if not probes:
degraded.append("web-platform-no-probes")
next_steps.append("Run intelligence_tui.sh --action memory --json to populate web platform health probes.")
if any(p.get("status") != "up" for p in probes.values()):
- next_steps.append("Check that Next.js (port 3000), Yjs realtime (port 1234), and Redis (port 6379) are running.")
+ next_steps.append("Check that Next.js (port 3000), Yjs realtime (port 1234), Redis (port 6379), and the EDA worker are running.")
queue_depth = None
queue_probe = probes.get("queue") or {}
if "depth" in queue_probe:
queue_depth = queue_probe["depth"]
+ if queue_probe.get("reason") == "redis-env-missing":
+ next_steps.append("Set REDIS_URL in web/.env or web/.env.local before starting Next.js and the EDA worker.")
+ elif queue_probe.get("reason") == "redis-unreachable":
+ next_steps.append("Start Redis with `docker compose -f web/compose.dev.yml up -d redis` or point REDIS_URL to a live instance.")
+ worker_probe = probes.get("worker") or {}
+ if worker_probe.get("reason") == "worker-absent":
+ next_steps.append("Start the EDA worker with `cd web && npm run worker:eda` once Redis is available.")
surface = {
"status": status,
"summary_short": compact(
@@ -989,6 +1040,10 @@ while [ "$#" -gt 0 ]; do
LANGFUSE_REPORT="${2:-}"
shift 2
;;
+ --infra-vps-report)
+ INFRA_VPS_REPORT="${2:-}"
+ shift 2
+ ;;
-h|--help)
usage
exit 0
@@ -1009,6 +1064,8 @@ if [ "${REFRESH}" -eq 1 ]; then
refresh_sources
fi
+ensure_infra_vps_live_report
+
case "${ACTION}" in
status)
log_line "INFO" "action=status refresh=${REFRESH}"
diff --git a/tools/cockpit/validate_mascarade_execution.sh b/tools/cockpit/validate_mascarade_execution.sh
new file mode 100644
index 0000000..db2add9
--- /dev/null
+++ b/tools/cockpit/validate_mascarade_execution.sh
@@ -0,0 +1,434 @@
+#!/bin/bash
+# tools/cockpit/validate_mascarade_execution.sh
+# Comprehensive Mascarade connectivity + agent execution validation across machines
+# Usage: bash tools/cockpit/validate_mascarade_execution.sh [--machine HOST] [--test-agents PM|ARCH|FW|QA] [--json]
+
+set -euo pipefail
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Configuration
+# ─────────────────────────────────────────────────────────────────────────────
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+ROOT_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+MASCARADE_URL="${MASCARADE_CORE_URL:-http://192.168.0.119:8100}"
+MASCARADE_HOST="${MASCARADE_URL#*://}"
+MASCARADE_HOST="${MASCARADE_HOST%%:*}"
+MASCARADE_PORT="${MASCARADE_URL##*:}"
+
+MACHINES=(
+ "192.168.0.119" # Mascarade primary
+ "192.168.0.120" # clems (hardware execution)
+ "kxkm-ai" # KXKM machine
+ "192.168.0.210" # CILS (optional)
+)
+
+TEST_AGENTS=("pm" "architect" "firmware" "qa")
+OUTPUT_FORMAT="both" # text or json or both
+STRICT_MODE="${STRICT_MODE:-false}"
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Parse arguments
+# ─────────────────────────────────────────────────────────────────────────────
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --machine)
+ MACHINES=("$2")
+ shift 2
+ ;;
+ --machines)
+ shift
+ MACHINES=()
+ while [[ $# -gt 0 ]] && [[ "$1" != --* ]]; do
+ MACHINES+=("$1")
+ shift
+ done
+ ;;
+ --test-agents)
+ shift
+ TEST_AGENTS=()
+ while [[ $# -gt 0 ]] && [[ "$1" != --* ]]; do
+ TEST_AGENTS+=("${1,,}")
+ shift
+ done
+ ;;
+ --json)
+ OUTPUT_FORMAT="json"
+ shift
+ ;;
+ --text)
+ OUTPUT_FORMAT="text"
+ shift
+ ;;
+ --strict)
+ STRICT_MODE="true"
+ shift
+ ;;
+ *)
+ echo "Unknown option: $1" >&2
+ exit 1
+ ;;
+ esac
+done
+
+# ─────────────────────────────────────────────────────────────────────────────
+# State tracking
+# ─────────────────────────────────────────────────────────────────────────────
+
+declare -A RESULTS
+declare -A DURATIONS
+PASSED=0
+FAILED=0
+WARNINGS=0
+START_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Helper functions
+# ─────────────────────────────────────────────────────────────────────────────
+
+log_info() {
+ [[ "$OUTPUT_FORMAT" == "text" ]] || [[ "$OUTPUT_FORMAT" == "both" ]] && echo "ℹ️ $*" >&2
+}
+
+log_pass() {
+ [[ "$OUTPUT_FORMAT" == "text" ]] || [[ "$OUTPUT_FORMAT" == "both" ]] && echo "✅ $*" >&2
+ ((PASSED++)) || true
+}
+
+log_fail() {
+ [[ "$OUTPUT_FORMAT" == "text" ]] || [[ "$OUTPUT_FORMAT" == "both" ]] && echo "❌ $*" >&2
+ ((FAILED++)) || true
+}
+
+log_warn() {
+ [[ "$OUTPUT_FORMAT" == "text" ]] || [[ "$OUTPUT_FORMAT" == "both" ]] && echo "⚠️ $*" >&2
+ ((WARNINGS++)) || true
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Test: Network connectivity to Mascarade
+# ─────────────────────────────────────────────────────────────────────────────
+
+test_mascarade_dns() {
+ local test_name="mascarade_dns_${MASCARADE_HOST}"
+ local start=$(date +%s%N)
+
+ if ping -c 1 -W 2 "$MASCARADE_HOST" &>/dev/null; then
+ local duration=$(( ($(date +%s%N) - start) / 1000000 ))
+ RESULTS["$test_name"]="pass"
+ DURATIONS["$test_name"]=$duration
+ log_pass "Mascarade host reachable: $MASCARADE_HOST (${duration}ms)"
+ return 0
+ else
+ RESULTS["$test_name"]="fail"
+ log_fail "Mascarade host unreachable: $MASCARADE_HOST"
+ return 1
+ fi
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Test: Mascarade health endpoint
+# ─────────────────────────────────────────────────────────────────────────────
+
+test_mascarade_health() {
+ local test_name="mascarade_health_endpoint"
+ local start=$(date +%s%N)
+
+ local response
+ response=$(curl -s -m 5 -o /dev/null -w "%{http_code}" "${MASCARADE_URL}/health" 2>/dev/null || echo "000")
+
+ local duration=$(( ($(date +%s%N) - start) / 1000000 ))
+ DURATIONS["$test_name"]=$duration
+
+ if [[ "$response" == "200" ]]; then
+ RESULTS["$test_name"]="pass"
+ log_pass "Mascarade /health endpoint OK (HTTP $response, ${duration}ms)"
+ return 0
+ elif [[ "$response" =~ ^[45] ]]; then
+ RESULTS["$test_name"]="fail"
+ log_fail "Mascarade /health returned HTTP $response"
+ return 1
+ else
+ RESULTS["$test_name"]="warn"
+ log_warn "Mascarade /health unreachable (HTTP $response, ${duration}ms)"
+ return 2
+ fi
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Test: Kill_LIFE FastAPI server can reach Mascarade
+# ─────────────────────────────────────────────────────────────────────────────
+
+test_killlife_mascarade_bridge() {
+ local test_name="killlife_server_mascarade_bridge"
+ local start=$(date +%s%N)
+
+ # Start Kill_LIFE server in background (if not running)
+ if ! pgrep -f "python.*kill_life.*server" &>/dev/null; then
+ log_info "Starting Kill_LIFE FastAPI server..."
+ cd "$ROOT_DIR"
+ python3 -m kill_life.server &>/tmp/killlife_server.log &
+ KILLLIFE_PID=$!
+ sleep 2
+ fi
+
+ # Test that FastAPI server is up
+ if ! curl -s -m 3 "http://localhost:8200/health" &>/dev/null; then
+ RESULTS["$test_name"]="fail"
+ log_fail "Kill_LIFE FastAPI server not running or unreachable"
+ return 1
+ fi
+
+ # Test that FastAPI can retrieve agents (which requires Mascarade bridge or local fallback)
+ local agents_response
+ agents_response=$(curl -s -m 5 "http://localhost:8200/agents" 2>/dev/null || echo "")
+
+ local duration=$(( ($(date +%s%N) - start) / 1000000 ))
+ DURATIONS["$test_name"]=$duration
+
+ if [[ -n "$agents_response" ]] && echo "$agents_response" | grep -q "pm"; then
+ RESULTS["$test_name"]="pass"
+ log_pass "Kill_LIFE ↔ Mascarade bridge working (${duration}ms)"
+ return 0
+ else
+ RESULTS["$test_name"]="warn"
+ log_warn "Kill_LIFE agents endpoint responded but format unexpected"
+ return 2
+ fi
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Test: Agent execution (mock call to Mascarade agent-run)
+# ─────────────────────────────────────────────────────────────────────────────
+
+test_agent_execution() {
+ local agent="$1"
+ local test_name="agent_execution_${agent}"
+ local start=$(date +%s%N)
+
+ # Simulate agent execution via Mascarade MCP bridge
+ # In production, this would call /mcp endpoint with agent-run RPC
+ local payload=$(cat </dev/null || echo "")
+
+ local duration=$(( ($(date +%s%N) - start) / 1000000 ))
+ DURATIONS["$test_name"]=$duration
+
+ if [[ -n "$response" ]] && echo "$response" | grep -q "status"; then
+ RESULTS["$test_name"]="pass"
+ log_pass "Agent '$agent' execution test OK (${duration}ms)"
+ return 0
+ elif [[ "$STRICT_MODE" == "true" ]]; then
+ RESULTS["$test_name"]="fail"
+ log_fail "Agent '$agent' execution failed (strict mode)"
+ return 1
+ else
+ RESULTS["$test_name"]="warn"
+ log_warn "Agent '$agent' execution endpoint not responding (expected in offline mode)"
+ return 2
+ fi
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Test: Remote machine connectivity (SSH)
+# ─────────────────────────────────────────────────────────────────────────────
+
+test_remote_machine() {
+ local machine="$1"
+ local test_name="machine_connectivity_${machine//./_}"
+ local start=$(date +%s%N)
+
+ # Determine user (default to root for IPs, ubuntu for hostnames)
+ local user="root"
+ if [[ "$machine" =~ ^[a-zA-Z] ]]; then
+ user="$(whoami)" # Use current user for hostnames
+ fi
+
+ if timeout 5 ssh -o ConnectTimeout=3 -o BatchMode=yes \
+ "$user@$machine" 'echo OK' &>/dev/null; then
+ local duration=$(( ($(date +%s%N) - start) / 1000000 ))
+ RESULTS["$test_name"]="pass"
+ DURATIONS["$test_name"]=$duration
+ log_pass "Machine reachable: $machine@$user (${duration}ms)"
+ return 0
+ else
+ RESULTS["$test_name"]="warn"
+ log_warn "Machine unreachable: $machine (check SSH keys, network)"
+ return 2
+ fi
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# Test: Cockpit script → Mascarade integration
+# ─────────────────────────────────────────────────────────────────────────────
+
+test_cockpit_mascarade_integration() {
+ local test_name="cockpit_mascarade_integration"
+ local start=$(date +%s%N)
+
+ log_info "Testing cockpit runtime gateway..."
+
+ if bash "$ROOT_DIR/tools/cockpit/runtime_ai_gateway.sh" --action status --json &>/tmp/cockpit_test.json; then
+ if grep -q "status.*ready\|status.*degraded\|status.*blocked" /tmp/cockpit_test.json; then
+ local duration=$(( ($(date +%s%N) - start) / 1000000 ))
+ RESULTS["$test_name"]="pass"
+ DURATIONS["$test_name"]=$duration
+ log_pass "Cockpit ↔ Mascarade integration OK (${duration}ms)"
+ return 0
+ fi
+ fi
+
+ RESULTS["$test_name"]="warn"
+ log_warn "Cockpit runtime gateway not fully operational"
+ return 2
+}
+
+# ─────────────────────────────────────────────────────────────────────────────
+# JSON output builder
+# ─────────────────────────────────────────────────────────────────────────────
+
+generate_json_report() {
+ local end_time=$(date -u +%Y-%m-%dT%H:%M:%SZ)
+
+ cat < "$artifact_file"
+ log_info "Artifact written: $artifact_file"
+
+ # Exit code
+ [[ $FAILED -eq 0 ]]
+}
+
+main "$@"
diff --git a/tools/specs/validate_agent_catalog.py b/tools/specs/validate_agent_catalog.py
new file mode 100644
index 0000000..b6d416c
--- /dev/null
+++ b/tools/specs/validate_agent_catalog.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+"""Validate the canonical Kill_LIFE 2026 agent catalog and its references."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+from pathlib import Path
+from typing import Any
+
+
+ROOT = Path(__file__).resolve().parents[2]
+CATALOG_PATH = ROOT / "specs" / "contracts" / "kill_life_agent_catalog.json"
+REQUIRED_DOCS = (
+ Path("README.md"),
+ Path("README_FR.md"),
+ Path("docs/plans/12_plan_gestion_des_agents.md"),
+ Path("docs/AGENT_SPEC_MODULE_MATRIX_2026-03-20.md"),
+)
+OWNER_AGENT_JSON_ROOT = Path("specs/contracts")
+OWNER_AGENT_TEXT_FILES = (
+ Path("tools/autonomous_next_lots.py"),
+ Path("tools/cad/yiacad_fusion_lot.sh"),
+ Path("tools/cockpit/intelligence_tui.sh"),
+ Path("tools/cockpit/runtime_ai_gateway.sh"),
+)
+
+
+def _load_json(path: Path) -> dict[str, Any]:
+ return json.loads(path.read_text(encoding="utf-8"))
+
+
+def _expected_slug(agent_id: str) -> str:
+ normalized = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", agent_id)
+ return re.sub(r"[^a-z0-9]+", "_", normalized.lower()).strip("_")
+
+
+def _legacy_doc_patterns(legacy_id: str) -> tuple[re.Pattern[str], ...]:
+ escaped = re.escape(legacy_id)
+ escaped_agent = re.escape(f"{legacy_id}_agent")
+ escaped_start = re.escape(f"start_{legacy_id}_agent")
+ escaped_plan = re.escape(f"plan_wizard_{legacy_id}_agent")
+ return (
+ re.compile(rf"`{escaped}`"),
+ re.compile(rf'"{escaped}"'),
+ re.compile(rf"'{escaped}'"),
+ re.compile(rf"\b{escaped_agent}\b"),
+ re.compile(rf"\b{escaped_start}\b"),
+ re.compile(rf"\b{escaped_plan}\b"),
+ )
+
+
+def _collect_owner_agent_json(path: Path) -> list[dict[str, str]]:
+ payload = _load_json(path)
+ found: list[dict[str, str]] = []
+ stack: list[Any] = [payload]
+ while stack:
+ current = stack.pop()
+ if isinstance(current, dict):
+ owner_agent = current.get("owner_agent")
+ if isinstance(owner_agent, str):
+ found.append({"path": path.as_posix(), "owner_agent": owner_agent})
+ stack.extend(current.values())
+ elif isinstance(current, list):
+ stack.extend(current)
+ return found
+
+
+def _collect_owner_agent_text(path: Path) -> list[dict[str, str]]:
+ found: list[dict[str, str]] = []
+ text = path.read_text(encoding="utf-8")
+ patterns = (
+ re.compile(r'owner_agent"\s*:\s*"([^"]+)"'),
+ re.compile(r'owner_agent\s*=\s*"([^"]+)"'),
+ )
+ for pattern in patterns:
+ for match in pattern.finditer(text):
+ found.append({"path": path.as_posix(), "owner_agent": match.group(1)})
+ return found
+
+
+def validate_agent_catalog(repo_root: Path = ROOT, catalog_path: Path = CATALOG_PATH) -> dict[str, Any]:
+ catalog = _load_json(catalog_path)
+ agents = catalog.get("agents", [])
+ canonical_ids = [agent.get("id", "") for agent in agents]
+ legacy_aliases = {str(key): str(value) for key, value in catalog.get("legacy_runtime_aliases", {}).items()}
+ errors: list[str] = []
+
+ if catalog.get("contract_version") != "kill-life-agent-catalog/v1":
+ errors.append("catalog-contract-version-invalid")
+ if catalog.get("repo") != "Kill_LIFE":
+ errors.append("catalog-owner-repo-invalid")
+ if not agents:
+ errors.append("catalog-empty")
+
+ ids_are_unique = len(canonical_ids) == len(set(canonical_ids))
+ if not ids_are_unique:
+ errors.append("catalog-duplicate-agent-id")
+
+ slugs_are_unique = len([agent.get("slug") for agent in agents]) == len(
+ {agent.get("slug") for agent in agents}
+ )
+ if not slugs_are_unique:
+ errors.append("catalog-duplicate-agent-slug")
+
+ invalid_slug_rows: list[dict[str, str]] = []
+ missing_files: list[str] = []
+ parity_rows: list[dict[str, str]] = []
+
+ for agent in agents:
+ agent_id = str(agent.get("id", ""))
+ slug = str(agent.get("slug", ""))
+ expected_slug = _expected_slug(agent_id)
+ if slug != expected_slug:
+ invalid_slug_rows.append({"agent_id": agent_id, "slug": slug, "expected_slug": expected_slug})
+
+ required_paths = (
+ ("agent_doc", agent.get("agent_doc")),
+ ("github_agent_doc", agent.get("github_agent_doc")),
+ ("start_prompt", agent.get("start_prompt")),
+ ("plan_wizard_prompt", agent.get("plan_wizard_prompt")),
+ )
+ row: dict[str, str] = {"agent_id": agent_id}
+ for field, raw_path in required_paths:
+ if not isinstance(raw_path, str) or not raw_path:
+ errors.append(f"{agent_id}:{field}-missing")
+ continue
+ rel_path = Path(raw_path)
+ row[field] = rel_path.as_posix()
+ if not (repo_root / rel_path).exists():
+ missing_files.append(rel_path.as_posix())
+ parity_rows.append(row)
+
+ docs_missing_agents: list[dict[str, Any]] = []
+ docs_legacy_refs: list[dict[str, Any]] = []
+ for rel_path in REQUIRED_DOCS:
+ path = repo_root / rel_path
+ if not path.exists():
+ missing_files.append(rel_path.as_posix())
+ continue
+ text = path.read_text(encoding="utf-8")
+ missing_ids = [agent_id for agent_id in canonical_ids if agent_id not in text]
+ if missing_ids:
+ docs_missing_agents.append({"path": rel_path.as_posix(), "missing_agents": missing_ids})
+ legacy_hits = [
+ legacy
+ for legacy in legacy_aliases
+ if any(pattern.search(text) for pattern in _legacy_doc_patterns(legacy))
+ ]
+ if legacy_hits:
+ docs_legacy_refs.append({"path": rel_path.as_posix(), "legacy_ids": legacy_hits})
+
+ owner_refs: list[dict[str, str]] = []
+ for json_path in sorted((repo_root / OWNER_AGENT_JSON_ROOT).rglob("*.json")):
+ owner_refs.extend(_collect_owner_agent_json(json_path))
+ for rel_path in OWNER_AGENT_TEXT_FILES:
+ path = repo_root / rel_path
+ if path.exists():
+ owner_refs.extend(_collect_owner_agent_text(path))
+
+ invalid_owner_refs = [
+ item for item in owner_refs if item["owner_agent"] not in canonical_ids
+ ]
+
+ if invalid_slug_rows:
+ errors.append("catalog-slug-drift")
+ if missing_files:
+ errors.append("catalog-missing-files")
+ if docs_missing_agents:
+ errors.append("catalog-doc-drift")
+ if docs_legacy_refs:
+ errors.append("catalog-legacy-doc-refs")
+ if invalid_owner_refs:
+ errors.append("catalog-invalid-owner-agent")
+
+ return {
+ "ok": not errors,
+ "catalog_path": catalog_path.relative_to(repo_root).as_posix(),
+ "contract_version": catalog.get("contract_version"),
+ "repo": catalog.get("repo"),
+ "agent_count": len(agents),
+ "canonical_agent_ids": canonical_ids,
+ "legacy_runtime_aliases": legacy_aliases,
+ "errors": errors,
+ "invalid_slug_rows": invalid_slug_rows,
+ "missing_files": sorted(set(missing_files)),
+ "docs_missing_agents": docs_missing_agents,
+ "docs_legacy_refs": docs_legacy_refs,
+ "invalid_owner_refs": invalid_owner_refs,
+ "parity_rows": parity_rows,
+ }
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Validate the Kill_LIFE canonical agent catalog.")
+ parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.")
+ args = parser.parse_args()
+
+ result = validate_agent_catalog()
+ if args.json:
+ print(json.dumps(result, indent=2, ensure_ascii=False))
+ else:
+ status = "OK" if result["ok"] else "FAIL"
+ print(f"{status}: agent catalog")
+ print(f"- agent_count: {result['agent_count']}")
+ print(f"- invalid_slug_rows: {len(result['invalid_slug_rows'])}")
+ print(f"- missing_files: {len(result['missing_files'])}")
+ print(f"- docs_missing_agents: {len(result['docs_missing_agents'])}")
+ print(f"- docs_legacy_refs: {len(result['docs_legacy_refs'])}")
+ print(f"- invalid_owner_refs: {len(result['invalid_owner_refs'])}")
+ if result["errors"]:
+ print("- errors: " + ", ".join(result["errors"]))
+ return 0 if result["ok"] else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tools/test_python.sh b/tools/test_python.sh
index 6864798..a70a069 100755
--- a/tools/test_python.sh
+++ b/tools/test_python.sh
@@ -15,7 +15,7 @@ Usage: bash tools/test_python.sh [options]
Run the supported Kill_LIFE repo-local Python suites through the repo-local venv.
Options:
- --suite NAME stable | mcp | all (default: stable)
+ --suite NAME stable | yiacad-desktop | yiacad-manufacturing | mcp | all (default: stable)
--bootstrap Create/install the venv first if missing
--python BIN Python interpreter forwarded to bootstrap if needed
--venv-dir PATH Virtualenv directory to use (default: ./.venv)
@@ -23,9 +23,11 @@ Options:
-h, --help Show this help
Suites:
- stable Repo-local unittest suite without companion runtimes.
- mcp MCP integration tests that exercise local launchers.
- all stable + mcp.
+ stable Repo-local unittest suite without companion runtimes.
+ yiacad-desktop YiACAD desktop/service-first contract and adapter coverage.
+ yiacad-manufacturing YiACAD manufacturing/backend proof contract coverage.
+ mcp MCP integration tests that exercise local launchers.
+ all stable + mcp.
EOF
}
@@ -51,10 +53,14 @@ stable:
test/test_log_ops_contract.py
test/test_intelligence_tui_contract.py
test/test_runtime_ai_gateway_contract.py
+ test/test_yiacad_action_registry_contract.py
+ test/test_yiacad_ai_bridge_contract.py
test/test_yiacad_backend_contract.py
test/test_yiacad_backend_service_contract.py
+ test/test_yiacad_freecad_workbench_contract.py
test/test_yiacad_native_ops_contract.py
test/test_yiacad_uiux_tui_contract.py
+ test/test_yiacad_kicad_plugin_contract.py
test/test_yiacad_native_surface_contract.py
test/test_yiacad_project_shell_contract.py
test/test_yiacad_pr_summary_contract.py
@@ -67,6 +73,25 @@ stable:
test/test_firmware_evidence.py
test/test_validate_specs.py
tools/hw/schops/tests/test_*.py
+EOF
+ ;;
+ yiacad-desktop)
+ cat <<'EOF'
+yiacad-desktop:
+ test/test_yiacad_action_registry_contract.py
+ test/test_yiacad_ai_bridge_contract.py
+ test/test_yiacad_backend_contract.py
+ test/test_yiacad_backend_service_contract.py
+ test/test_yiacad_freecad_workbench_contract.py
+ test/test_yiacad_kicad_plugin_contract.py
+EOF
+ ;;
+ yiacad-manufacturing)
+ cat <<'EOF'
+yiacad-manufacturing:
+ test/test_yiacad_native_ops_contract.py
+ test/test_yiacad_web_worker_contract.py
+ test/test_runtime_ai_gateway_contract.py
EOF
;;
mcp)
@@ -79,6 +104,8 @@ EOF
;;
all)
print_suite stable
+ print_suite yiacad-desktop
+ print_suite yiacad-manufacturing
print_suite mcp
;;
*)
@@ -125,7 +152,7 @@ while [[ $# -gt 0 ]]; do
done
case "${SUITE}" in
- stable|mcp|all)
+ stable|yiacad-desktop|yiacad-manufacturing|mcp|all)
;;
*)
echo "Unknown suite: ${SUITE}" >&2
@@ -157,8 +184,12 @@ if [[ "${SUITE}" == "stable" || "${SUITE}" == "all" ]]; then
run_discover test 'test_log_ops_contract.py'
run_discover test 'test_intelligence_tui_contract.py'
run_discover test 'test_runtime_ai_gateway_contract.py'
+ run_discover test 'test_yiacad_action_registry_contract.py'
+ run_discover test 'test_yiacad_ai_bridge_contract.py'
run_discover test 'test_yiacad_backend_contract.py'
run_discover test 'test_yiacad_backend_service_contract.py'
+ run_discover test 'test_yiacad_freecad_workbench_contract.py'
+ run_discover test 'test_yiacad_kicad_plugin_contract.py'
run_discover test 'test_yiacad_native_ops_contract.py'
run_discover test 'test_yiacad_uiux_tui_contract.py'
run_discover test 'test_yiacad_native_surface_contract.py'
@@ -175,6 +206,21 @@ if [[ "${SUITE}" == "stable" || "${SUITE}" == "all" ]]; then
run_discover tools/hw/schops/tests 'test_*.py'
fi
+if [[ "${SUITE}" == "yiacad-desktop" ]]; then
+ run_discover test 'test_yiacad_action_registry_contract.py'
+ run_discover test 'test_yiacad_ai_bridge_contract.py'
+ run_discover test 'test_yiacad_backend_contract.py'
+ run_discover test 'test_yiacad_backend_service_contract.py'
+ run_discover test 'test_yiacad_freecad_workbench_contract.py'
+ run_discover test 'test_yiacad_kicad_plugin_contract.py'
+fi
+
+if [[ "${SUITE}" == "yiacad-manufacturing" ]]; then
+ run_discover test 'test_yiacad_native_ops_contract.py'
+ run_discover test 'test_yiacad_web_worker_contract.py'
+ run_discover test 'test_runtime_ai_gateway_contract.py'
+fi
+
if [[ "${SUITE}" == "mcp" || "${SUITE}" == "all" ]]; then
run_discover test 'test_knowledge_base_mcp.py'
run_discover test 'test_github_dispatch_mcp.py'
diff --git a/tools/validate_specs.py b/tools/validate_specs.py
index 7912d0c..4055dce 100644
--- a/tools/validate_specs.py
+++ b/tools/validate_specs.py
@@ -120,6 +120,19 @@ def compare_spec_mirror() -> Dict[str, Any]:
}
+def validate_agent_catalog_contract() -> Dict[str, Any]:
+ module_path = ROOT / "tools" / "specs" / "validate_agent_catalog.py"
+ spec = importlib.util.spec_from_file_location("validate_agent_catalog", module_path)
+ if spec is None or spec.loader is None:
+ return {
+ "ok": False,
+ "errors": ["agent-catalog-validator-load-failed"],
+ }
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module.validate_agent_catalog(repo_root=ROOT)
+
+
def validate_specs(
strict: bool = False, require_mirror_sync: bool = False
) -> Dict[str, Any]:
@@ -149,12 +162,14 @@ def validate_specs(
rfc2119 = scan_rfc2119()
mirror_sync = compare_spec_mirror()
+ agent_catalog = validate_agent_catalog_contract()
ok = (
not missing_files
and compliance["ok"]
and rfc2119["ok"]
and (mirror_sync["ok"] or not require_mirror_sync)
+ and agent_catalog["ok"]
)
return {
"ok": ok,
@@ -165,6 +180,7 @@ def validate_specs(
"dependencies": dependencies,
"rfc2119": rfc2119,
"mirror_sync": mirror_sync,
+ "agent_catalog": agent_catalog,
}
@@ -191,6 +207,13 @@ def format_cli_summary(result: Dict[str, Any]) -> str:
f"extra={len(mirror_sync['extra_in_mirror'])} "
f"mismatch={len(mirror_sync['content_mismatch'])}"
),
+ (
+ "- agent catalog: "
+ f"ok={result['agent_catalog']['ok']} "
+ f"agents={result['agent_catalog']['agent_count']} "
+ f"missing_files={len(result['agent_catalog']['missing_files'])} "
+ f"invalid_owner_refs={len(result['agent_catalog']['invalid_owner_refs'])}"
+ ),
]
if result["missing_files"]:
@@ -205,6 +228,8 @@ def format_cli_summary(result: Dict[str, Any]) -> str:
lines.append("- compliance stdout: " + compliance["stdout"])
if compliance["stderr"]:
lines.append("- compliance stderr: " + compliance["stderr"])
+ if result["agent_catalog"]["errors"]:
+ lines.append("- agent catalog errors: " + ", ".join(result["agent_catalog"]["errors"]))
return "\n".join(lines)
diff --git a/web/README.md b/web/README.md
index 17940b6..8b682d4 100644
--- a/web/README.md
+++ b/web/README.md
@@ -36,10 +36,17 @@ Queue and workers:
- GraphQL enqueues EDA jobs through Redis-backed `BullMQ`
- `npm run worker:eda` consumes the queue and calls existing repo tools
+- worker heartbeat is published to `web/project/.ci/worker-health.json`
- `kicad-headless` uses `tools/cad/yiacad_backend_client.py` in `service-first` mode
- `kibot` now routes through `tools/cad/yiacad_backend_client.py` and emits a normalized YiACAD manufacturing package
- `kiauto-checks` now routes through `tools/cad/yiacad_backend_client.py` and emits normalized YiACAD validation output
+Ops probes:
+
+- `GET /api/ops/platform` exposes `next-js`, `yjs-realtime`, `eda-queue`, and `eda-worker`
+- `GET /api/ops/queue` distinguishes `REDIS_URL` misconfiguration from Redis reachability failures
+- `GET /api/ops/worker` reports worker heartbeat, last job, and stale or absent worker state
+
Environment:
- copy values from `.env.example`
diff --git a/web/README_FR.md b/web/README_FR.md
index 036047b..5593b1e 100644
--- a/web/README_FR.md
+++ b/web/README_FR.md
@@ -36,10 +36,17 @@ Queue and workers:
- GraphQL enqueues EDA jobs through Redis-backed `BullMQ`
- `npm run worker:eda` consumes the queue and calls existing repo tools
+- le heartbeat worker est publie dans `web/project/.ci/worker-health.json`
- `kicad-headless` utilise `tools/cad/yiacad_backend_client.py` en mode `service-first`
- `kibot` passe maintenant par `tools/cad/yiacad_backend_client.py` et publie un package manufacturing YiACAD normalise
- `kiauto-checks` passe maintenant par `tools/cad/yiacad_backend_client.py` et publie une sortie de validation YiACAD normalisee
+Ops probes:
+
+- `GET /api/ops/platform` expose `next-js`, `yjs-realtime`, `eda-queue` et `eda-worker`
+- `GET /api/ops/queue` distingue une mauvaise configuration `REDIS_URL` d'un Redis injoignable
+- `GET /api/ops/worker` publie le heartbeat worker, le dernier job et les etats stale ou absent
+
Environment:
- copy values from `.env.example`
diff --git a/web/app/api/ops/platform/route.ts b/web/app/api/ops/platform/route.ts
index 8ce6ec7..62f062c 100644
--- a/web/app/api/ops/platform/route.ts
+++ b/web/app/api/ops/platform/route.ts
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server";
-import { getEdaQueue } from "@/lib/eda-queue";
+
+import { probeQueue, probeWorker } from "@/lib/intelligence/ops-health";
export const runtime = "nodejs";
@@ -19,36 +20,24 @@ async function probeUrl(url: string, timeoutMs = 1500): Promise {
}
}
-async function probeQueue(): Promise {
- try {
- const queue = getEdaQueue();
- const counts = await queue.getJobCounts("wait", "active", "failed");
- const depth = counts.wait ?? 0;
- const active = counts.active ?? 0;
- const failed = counts.failed ?? 0;
- const status = failed > 10 ? "degraded" : "up";
- return { status, detail: depth, ...(active ? { active } : {}), ...(failed ? { failed } : {}) } as ProbeResult & Record;
- } catch (err) {
- return { status: "down", detail: String(err) };
- }
-}
-
export async function GET() {
const yjsUrl =
process.env.YJS_WS_HTTP_URL ??
`http://localhost:${process.env.YJS_WS_PORT ?? "1234"}/`;
const nextUrl = `http://localhost:${process.env.PORT ?? "3000"}/`;
- const [nextProbe, yjsProbe, queueProbe] = await Promise.all([
+ const [nextProbe, yjsProbe, queueProbe, workerProbe] = await Promise.all([
probeUrl(nextUrl),
probeUrl(yjsUrl),
probeQueue(),
+ probeWorker(),
]);
const probes = {
"next-js": nextProbe,
"yjs-realtime": yjsProbe,
"eda-queue": queueProbe,
+ "eda-worker": workerProbe,
};
const upCount = Object.values(probes).filter((p) => p.status === "up").length;
diff --git a/web/app/api/ops/queue/route.ts b/web/app/api/ops/queue/route.ts
new file mode 100644
index 0000000..49b35d9
--- /dev/null
+++ b/web/app/api/ops/queue/route.ts
@@ -0,0 +1,9 @@
+import { NextResponse } from "next/server";
+
+import { probeQueue } from "@/lib/intelligence/ops-health";
+
+export const runtime = "nodejs";
+
+export async function GET() {
+ return NextResponse.json(await probeQueue());
+}
diff --git a/web/app/api/ops/worker/route.ts b/web/app/api/ops/worker/route.ts
new file mode 100644
index 0000000..e6fd7b9
--- /dev/null
+++ b/web/app/api/ops/worker/route.ts
@@ -0,0 +1,9 @@
+import { NextResponse } from "next/server";
+
+import { probeWorker } from "@/lib/intelligence/ops-health";
+
+export const runtime = "nodejs";
+
+export async function GET() {
+ return NextResponse.json(await probeWorker());
+}
diff --git a/web/lib/intelligence/health-aggregator.ts b/web/lib/intelligence/health-aggregator.ts
index 2358ef3..60c722c 100644
--- a/web/lib/intelligence/health-aggregator.ts
+++ b/web/lib/intelligence/health-aggregator.ts
@@ -13,6 +13,7 @@ export interface QueueHealth {
active: number;
failed: number;
latencyMs?: number;
+ reason?: string;
}
export interface WorkerHealth {
@@ -20,6 +21,7 @@ export interface WorkerHealth {
running: boolean;
lastJobId?: string;
lastJobMs?: number;
+ reason?: string;
}
export interface RealtimeHealth {
@@ -54,12 +56,20 @@ async function fetchJson(url: string, timeoutMs = 1500): Promise {
async function getQueueHealth(): Promise {
const data = await fetchJson>(QUEUE_API);
if (!data) return { status: "unavailable", waiting: 0, active: 0, failed: 0 };
- const waiting = typeof data.waiting === "number" ? data.waiting : 0;
+ const waiting =
+ typeof data.waiting === "number"
+ ? data.waiting
+ : typeof data.depth === "number"
+ ? data.depth
+ : 0;
const active = typeof data.active === "number" ? data.active : 0;
const failed = typeof data.failed === "number" ? data.failed : 0;
const latencyMs = typeof data.latencyMs === "number" ? data.latencyMs : undefined;
- const status: ServiceStatus = failed > 10 ? "degraded" : "ok";
- return { status, waiting, active, failed, latencyMs };
+ const reason = typeof data.reason === "string" ? data.reason : undefined;
+ const statusValue = typeof data.status === "string" ? data.status : "";
+ const status: ServiceStatus =
+ statusValue === "down" ? "unavailable" : statusValue === "degraded" || failed > 10 ? "degraded" : "ok";
+ return { status, waiting, active, failed, latencyMs, reason };
}
async function getWorkerHealth(): Promise {
@@ -68,7 +78,11 @@ async function getWorkerHealth(): Promise {
const running = data.running === true;
const lastJobId = typeof data.lastJobId === "string" ? data.lastJobId : undefined;
const lastJobMs = typeof data.lastJobMs === "number" ? data.lastJobMs : undefined;
- return { status: running ? "ok" : "degraded", running, lastJobId, lastJobMs };
+ const reason = typeof data.reason === "string" ? data.reason : undefined;
+ const statusValue = typeof data.status === "string" ? data.status : "";
+ const status: ServiceStatus =
+ statusValue === "down" ? "unavailable" : statusValue === "degraded" || !running ? "degraded" : "ok";
+ return { status, running, lastJobId, lastJobMs, reason };
}
async function getRealtimeHealth(): Promise {
diff --git a/web/lib/intelligence/ops-health.ts b/web/lib/intelligence/ops-health.ts
new file mode 100644
index 0000000..8d0da79
--- /dev/null
+++ b/web/lib/intelligence/ops-health.ts
@@ -0,0 +1,78 @@
+import { readFile } from "node:fs/promises";
+import { join } from "node:path";
+
+import { getEdaQueue } from "@/lib/eda-queue";
+
+type ProbeStatus = "up" | "degraded" | "down";
+
+const CI_ROOT = join(process.cwd(), "project", ".ci");
+const WORKER_HEALTH_FILE = join(CI_ROOT, "worker-health.json");
+
+function queueFailureReason(error: unknown): string {
+ const message = String(error ?? "");
+ if (message.includes("REDIS_URL is not configured")) {
+ return "redis-env-missing";
+ }
+ return "redis-unreachable";
+}
+
+export async function probeQueue(): Promise> {
+ try {
+ const queue = getEdaQueue();
+ const counts = await queue.getJobCounts("wait", "active", "failed");
+ const depth = counts.wait ?? 0;
+ const active = counts.active ?? 0;
+ const failed = counts.failed ?? 0;
+ const status: ProbeStatus = failed > 10 ? "degraded" : "up";
+ return {
+ status,
+ reason: status === "degraded" ? "queue-failures-high" : "queue-ready",
+ depth,
+ waiting: depth,
+ active,
+ failed
+ };
+ } catch (error) {
+ return {
+ status: "down",
+ reason: queueFailureReason(error),
+ detail: String(error ?? "")
+ };
+ }
+}
+
+export async function probeWorker(staleAfterMs = 180_000): Promise> {
+ try {
+ const raw = await readFile(WORKER_HEALTH_FILE, "utf8");
+ const data = JSON.parse(raw) as Record;
+ const updatedAt = typeof data.updatedAt === "string" ? data.updatedAt : "";
+ const updatedMs = updatedAt ? Date.parse(updatedAt) : NaN;
+ const ageMs = Number.isNaN(updatedMs) ? null : Math.max(0, Date.now() - updatedMs);
+ if (ageMs !== null && ageMs > staleAfterMs) {
+ return {
+ status: "degraded",
+ reason: "worker-stale",
+ running: data.running === true,
+ lastJobId: typeof data.lastJobId === "string" ? data.lastJobId : undefined,
+ lastJobMs: typeof data.lastJobMs === "number" ? data.lastJobMs : undefined,
+ updatedAt,
+ ageMs
+ };
+ }
+ return {
+ status: data.running === true ? "up" : "degraded",
+ reason: data.running === true ? "worker-running" : "worker-idle",
+ running: data.running === true,
+ lastJobId: typeof data.lastJobId === "string" ? data.lastJobId : undefined,
+ lastJobMs: typeof data.lastJobMs === "number" ? data.lastJobMs : undefined,
+ updatedAt,
+ ageMs
+ };
+ } catch {
+ return {
+ status: "down",
+ reason: "worker-absent",
+ running: false
+ };
+ }
+}
diff --git a/web/playwright-report/index.html b/web/playwright-report/index.html
new file mode 100644
index 0000000..606fc8b
--- /dev/null
+++ b/web/playwright-report/index.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/workers/eda-worker.mjs b/web/workers/eda-worker.mjs
index 51c0d0e..f73d863 100644
--- a/web/workers/eda-worker.mjs
+++ b/web/workers/eda-worker.mjs
@@ -11,8 +11,19 @@ const repoRoot = resolve(webRoot, "..");
const ciRoot = resolve(webRoot, "project", ".ci");
const runsFile = join(ciRoot, "runs.json");
const artifactsFile = join(ciRoot, "artifacts.json");
+const workerHealthFile = join(ciRoot, "worker-health.json");
const logsRoot = join(ciRoot, "logs");
const queueName = process.env.EDA_QUEUE_NAME ?? "yiacad-eda";
+const workerState = {
+ queueName,
+ running: false,
+ updatedAt: null,
+ currentJobId: null,
+ currentJobName: null,
+ lastJobId: null,
+ lastJobMs: null,
+ lastStatus: null
+};
function pipelineEngine(pipeline) {
switch (pipeline) {
@@ -62,6 +73,11 @@ async function ensureCiFiles() {
}
}
+async function persistWorkerHealth(patch = {}) {
+ Object.assign(workerState, patch, { updatedAt: new Date().toISOString() });
+ await writeFile(workerHealthFile, JSON.stringify(workerState, null, 2), "utf8");
+}
+
async function readJson(path, fallback) {
try {
return JSON.parse(await readFile(path, "utf8"));
@@ -309,11 +325,19 @@ async function processStepExport(job, logPrefix) {
}
await ensureCiFiles();
+await persistWorkerHealth({ running: false, lastStatus: "starting" });
const worker = new Worker(
queueName,
async (job) => {
const logPrefix = resolve(logsRoot, `${job.id}-${job.name}`);
+ const startedAt = Date.now();
+ await persistWorkerHealth({
+ running: true,
+ currentJobId: job.id,
+ currentJobName: job.name,
+ lastStatus: "running"
+ });
await updateRun(job.id, {
status: "running",
engine: pipelineEngine(job.name),
@@ -344,6 +368,14 @@ const worker = new Worker(
artifactCount: result.artifactCount,
completedAt: new Date().toISOString()
});
+ await persistWorkerHealth({
+ running: true,
+ currentJobId: null,
+ currentJobName: null,
+ lastJobId: String(job.id),
+ lastJobMs: Date.now() - startedAt,
+ lastStatus: result.status
+ });
return result;
},
{
@@ -366,8 +398,21 @@ worker.on("failed", async (job, error) => {
error?.stack || error?.message || "worker failed",
"utf8"
);
+ await persistWorkerHealth({
+ running: true,
+ currentJobId: null,
+ currentJobName: null,
+ lastJobId: String(job.id),
+ lastJobMs: null,
+ lastStatus: "failed"
+ });
});
worker.on("ready", () => {
+ void persistWorkerHealth({ running: true, lastStatus: "ready" });
console.log(`EDA worker ready on queue ${queueName}`);
});
+
+setInterval(() => {
+ void persistWorkerHealth({ running: true });
+}, 30_000);
--
2.52.0