docs+ci: update README features summary, fix intelligence TUI health probe, add KiCad exports workflow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kxkm
2026-03-27 03:44:22 +01:00
parent 1c6c1952e8
commit 1e362c1a14
3 changed files with 345 additions and 16 deletions
+308
View File
@@ -0,0 +1,308 @@
name: KiCad Exports
on:
push:
branches:
- main
paths:
- "hardware/**"
- "tools/hw/**"
- ".github/workflows/kicad-exports.yml"
pull_request:
paths:
- "hardware/**"
- "tools/hw/**"
- ".github/workflows/kicad-exports.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
ARTIFACTS_DIR: artifacts/hw_exports
jobs:
kicad-checks:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install KiCad 10
run: |
sudo add-apt-repository -y ppa:kicad/kicad-10.0-releases
sudo apt-get update
sudo apt-get install -y --no-install-recommends kicad
kicad-cli version
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Prepare output directories
run: |
mkdir -p "$ARTIFACTS_DIR/erc"
mkdir -p "$ARTIFACTS_DIR/drc"
mkdir -p "$ARTIFACTS_DIR/pdf"
mkdir -p "$ARTIFACTS_DIR/svg"
mkdir -p "$ARTIFACTS_DIR/bom"
mkdir -p "$ARTIFACTS_DIR/netlist"
# ── Discover all KiCad projects ──────────────────────────────
- name: Discover schematics and PCBs
id: discover
run: |
# Find all .kicad_pro files (each is a project root)
SCHEMATICS=$(find hardware -name "*.kicad_sch" \
-not -path "*/.kicad_blocks/*" \
-not -path "*/kicad_blocks/*" \
-not -path "*backup*" | sort)
PCBS=$(find hardware -name "*.kicad_pcb" | sort)
echo "Found schematics:"
echo "$SCHEMATICS"
echo ""
echo "Found PCBs:"
echo "${PCBS:-<none>}"
# Also check tools/cad proof fixtures
PROOF_PCBS=$(find tools/cad -name "*.kicad_pcb" 2>/dev/null | sort || true)
if [[ -n "$PROOF_PCBS" ]]; then
echo ""
echo "Found proof-fixture PCBs:"
echo "$PROOF_PCBS"
PCBS=$(printf "%s\n%s" "$PCBS" "$PROOF_PCBS" | sed '/^$/d')
fi
# Export for later steps
{
echo "schematics<<EOFSCH"
echo "$SCHEMATICS"
echo "EOFSCH"
echo "pcbs<<EOFPCB"
echo "$PCBS"
echo "EOFPCB"
} >> "$GITHUB_OUTPUT"
# ── ERC (Electrical Rules Check) ─────────────────────────────
- name: Run ERC on all schematics
id: erc
run: |
EXIT_CODE=0
while IFS= read -r SCH; do
[[ -z "$SCH" ]] && continue
BASENAME="$(basename "$SCH" .kicad_sch)"
echo "::group::ERC $SCH"
if kicad-cli sch erc "$SCH" \
--format json \
--output "$ARTIFACTS_DIR/erc/${BASENAME}_erc.json" 2>&1; then
# Parse error count
ERRORS=$(python3 -c "
import json, sys
try:
d = json.load(open('$ARTIFACTS_DIR/erc/${BASENAME}_erc.json'))
sheets = d.get('sheets', [])
errs = sum(1 for s in sheets for v in s.get('violations', []) if v.get('severity') == 'error')
print(errs)
except Exception as e:
print(f'parse-error: {e}', file=sys.stderr)
print(0)
")
if [[ "$ERRORS" -gt 0 ]]; then
echo "::error file=$SCH::ERC found $ERRORS error(s)"
EXIT_CODE=1
else
echo "ERC passed: $SCH"
fi
else
echo "::warning file=$SCH::ERC command failed (kicad-cli error)"
EXIT_CODE=1
fi
echo "::endgroup::"
done <<< "${{ steps.discover.outputs.schematics }}"
echo "erc_exit=$EXIT_CODE" >> "$GITHUB_OUTPUT"
# ── DRC (Design Rules Check) ─────────────────────────────────
- name: Run DRC on all PCBs
id: drc
run: |
EXIT_CODE=0
while IFS= read -r PCB; do
[[ -z "$PCB" ]] && continue
BASENAME="$(basename "$PCB" .kicad_pcb)"
echo "::group::DRC $PCB"
if kicad-cli pcb drc "$PCB" \
--format json \
--output "$ARTIFACTS_DIR/drc/${BASENAME}_drc.json" 2>&1; then
ERRORS=$(python3 -c "
import json, sys
try:
d = json.load(open('$ARTIFACTS_DIR/drc/${BASENAME}_drc.json'))
errs = sum(1 for v in d.get('violations', []) if v.get('severity') == 'error')
print(errs)
except Exception as e:
print(f'parse-error: {e}', file=sys.stderr)
print(0)
")
if [[ "$ERRORS" -gt 0 ]]; then
echo "::error file=$PCB::DRC found $ERRORS error(s)"
EXIT_CODE=1
else
echo "DRC passed: $PCB"
fi
else
echo "::warning file=$PCB::DRC command failed (kicad-cli error)"
EXIT_CODE=1
fi
echo "::endgroup::"
done <<< "${{ steps.discover.outputs.pcbs }}"
echo "drc_exit=$EXIT_CODE" >> "$GITHUB_OUTPUT"
# ── PDF Schematic Export ─────────────────────────────────────
- name: Export schematic PDFs
run: |
while IFS= read -r SCH; do
[[ -z "$SCH" ]] && continue
BASENAME="$(basename "$SCH" .kicad_sch)"
echo "Exporting PDF: $SCH"
kicad-cli sch export pdf "$SCH" \
--output "$ARTIFACTS_DIR/pdf/${BASENAME}.pdf" 2>&1 \
|| echo "::warning file=$SCH::PDF export failed"
done <<< "${{ steps.discover.outputs.schematics }}"
# ── SVG Schematic Export ─────────────────────────────────────
- name: Export schematic SVGs
run: |
while IFS= read -r SCH; do
[[ -z "$SCH" ]] && continue
BASENAME="$(basename "$SCH" .kicad_sch)"
SVGDIR="$ARTIFACTS_DIR/svg/${BASENAME}"
mkdir -p "$SVGDIR"
echo "Exporting SVG: $SCH"
kicad-cli sch export svg "$SCH" \
--output "$SVGDIR/" 2>&1 \
|| echo "::warning file=$SCH::SVG export failed"
done <<< "${{ steps.discover.outputs.schematics }}"
# ── BOM Export ───────────────────────────────────────────────
- name: Export BOMs
run: |
while IFS= read -r SCH; do
[[ -z "$SCH" ]] && continue
BASENAME="$(basename "$SCH" .kicad_sch)"
echo "Exporting BOM: $SCH"
kicad-cli sch export bom "$SCH" \
--output "$ARTIFACTS_DIR/bom/${BASENAME}_bom.csv" \
--fields 'Reference,Value,Footprint,Datasheet,Manufacturer,MPN,${QUANTITY},${DNP}' \
--group-by "Value" \
--sort-field "Reference" 2>&1 \
|| echo "::warning file=$SCH::BOM export failed"
done <<< "${{ steps.discover.outputs.schematics }}"
# ── Netlist Export ───────────────────────────────────────────
- name: Export netlists
run: |
while IFS= read -r SCH; do
[[ -z "$SCH" ]] && continue
BASENAME="$(basename "$SCH" .kicad_sch)"
echo "Exporting netlist: $SCH"
kicad-cli sch export netlist "$SCH" \
--output "$ARTIFACTS_DIR/netlist/${BASENAME}_netlist.xml" 2>&1 \
|| echo "::warning file=$SCH::Netlist export failed"
done <<< "${{ steps.discover.outputs.schematics }}"
# ── Summary ──────────────────────────────────────────────────
- name: Generate summary
if: always()
run: |
echo "## KiCad Export Summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
# ERC results
echo "### ERC Reports" >> "$GITHUB_STEP_SUMMARY"
for f in "$ARTIFACTS_DIR"/erc/*.json; do
[[ -f "$f" ]] || continue
NAME="$(basename "$f" _erc.json)"
ERRORS=$(python3 -c "
import json
d = json.load(open('$f'))
sheets = d.get('sheets', [])
errs = sum(1 for s in sheets for v in s.get('violations', []) if v.get('severity') == 'error')
warns = sum(1 for s in sheets for v in s.get('violations', []) if v.get('severity') == 'warning')
print(f'{errs} errors, {warns} warnings')
" 2>/dev/null || echo "parse error")
echo "- **$NAME**: $ERRORS" >> "$GITHUB_STEP_SUMMARY"
done
# DRC results
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### DRC Reports" >> "$GITHUB_STEP_SUMMARY"
for f in "$ARTIFACTS_DIR"/drc/*.json; do
[[ -f "$f" ]] || continue
NAME="$(basename "$f" _drc.json)"
ERRORS=$(python3 -c "
import json
d = json.load(open('$f'))
errs = sum(1 for v in d.get('violations', []) if v.get('severity') == 'error')
warns = sum(1 for v in d.get('violations', []) if v.get('severity') == 'warning')
print(f'{errs} errors, {warns} warnings')
" 2>/dev/null || echo "parse error")
echo "- **$NAME**: $ERRORS" >> "$GITHUB_STEP_SUMMARY"
done
# Exports
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "### Exported Artifacts" >> "$GITHUB_STEP_SUMMARY"
echo "- PDFs: $(find "$ARTIFACTS_DIR/pdf" -name "*.pdf" 2>/dev/null | wc -l | tr -d ' ') files" >> "$GITHUB_STEP_SUMMARY"
echo "- SVGs: $(find "$ARTIFACTS_DIR/svg" -name "*.svg" 2>/dev/null | wc -l | tr -d ' ') files" >> "$GITHUB_STEP_SUMMARY"
echo "- BOMs: $(find "$ARTIFACTS_DIR/bom" -name "*.csv" 2>/dev/null | wc -l | tr -d ' ') files" >> "$GITHUB_STEP_SUMMARY"
echo "- Netlists: $(find "$ARTIFACTS_DIR/netlist" -name "*.xml" 2>/dev/null | wc -l | tr -d ' ') files" >> "$GITHUB_STEP_SUMMARY"
# ── Upload Artifacts ─────────────────────────────────────────
- name: Upload ERC/DRC reports
if: always()
uses: actions/upload-artifact@v4
with:
name: kicad-reports
if-no-files-found: warn
path: |
artifacts/hw_exports/erc/
artifacts/hw_exports/drc/
- name: Upload schematic exports
if: always()
uses: actions/upload-artifact@v4
with:
name: kicad-schematics
if-no-files-found: warn
path: |
artifacts/hw_exports/pdf/
artifacts/hw_exports/svg/
- name: Upload BOM and netlists
if: always()
uses: actions/upload-artifact@v4
with:
name: kicad-bom-netlist
if-no-files-found: warn
path: |
artifacts/hw_exports/bom/
artifacts/hw_exports/netlist/
# ── Fail if ERC or DRC had errors ────────────────────────────
- name: Check ERC/DRC results
if: always()
run: |
ERC_EXIT="${{ steps.erc.outputs.erc_exit }}"
DRC_EXIT="${{ steps.drc.outputs.drc_exit }}"
if [[ "${ERC_EXIT:-0}" -ne 0 ]] || [[ "${DRC_EXIT:-0}" -ne 0 ]]; then
echo "::error::ERC or DRC checks failed. Review the uploaded reports."
exit 1
fi
echo "All ERC/DRC checks passed."
+5
View File
@@ -13,6 +13,8 @@
Welcome to **Kill_LIFE**, the public control plane of the `Kill_LIFE` agentic program. The repo now concentrates the operator cockpit, the spec-first pipeline, runtime/MCP contracts, execution evidence, and the pilot batch that powers sister VS Code extensions `kill-life-studio`, `kill-life-mesh`, and `kill-life-operator`.
*Last updated: 2026-03-27*
The 2026-03-22 reading rule is simple:
- this `README.md` describes the product/program and consolidation decisions
@@ -104,6 +106,9 @@ The positioning chosen for this consolidation pass is:
- **Workflow catalog**: JSON workflows editable by `crazy_life`, validated against a JSON schema.
- **Mascarade LLM Router**: Fake Ollama API, Agentic RAG, Cody Gateway, 5-machine fleet (Tower/KXKM/Photon/CILS/Local).
- **10 MCP servers**: kicad, freecad, openscad, github-dispatch, knowledge-base, apify, huggingface, mascarade-llm, opcua, mqtt.
- **RAG pipeline**: Agentic RAG with Qdrant hybrid search, LLM reranking, CRAG fallback, SearXNG web search.
- **KiCad CI exports**: SVG, ERC, DRC, BOM, netlist -- headless via KiCad 10 MCP.
- **Firmware Gate S1**: PlatformIO build gate for ESP32/STM32 targets with evidence pack generation.
- **Factory 4.0**: Industrial agents (copilot, maintenance predictor, log analyst), OPC-UA/MQTT MCP servers.
- **EDA AI tools**: PCBDesigner, Quilter, KiCadHappy providers for automated PCB design + fabrication.
- **Project template**: `templates/kill-life-project/` scaffold for client repos (see `docs/PROJECT_TEMPLATE.md`).
+32 -16
View File
@@ -167,54 +167,70 @@ 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, and Redis/BullMQ queue.
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 urllib.request
platform_url = "http://localhost:3000/api/ops/platform"
try:
req = urllib.request.urlopen(platform_url, timeout=4)
data = json.loads(req.read())
# Normalise probe keys for downstream consumers
probes = data.get("probes", {})
return {
"status": data.get("status", "unknown"),
"probes": {
"nextjs": probes.get("next-js", {"status": "unknown"}),
"realtime": probes.get("yjs-realtime", {"status": "unknown"}),
"queue": probes.get("eda-queue", {"status": "unknown"}),
},
"up_count": data.get("up_count", 0),
"total": data.get("total", 3),
"source": "platform-api",
}
except Exception:
pass # fall through to direct probes
# --- Fallback: direct probes (app not running) ---
probes: dict[str, object] = {}
# Next.js web app
try:
req = urllib.request.urlopen("http://localhost:3000/", timeout=3)
probes["nextjs"] = {"status": "up", "http_status": req.status}
except Exception as exc:
probes["nextjs"] = {"status": "down", "error": str(exc)[:120]}
# Yjs realtime server (default port 1234, see web/realtime/server.mjs)
try:
req = urllib.request.urlopen("http://localhost:1234/", timeout=3)
probes["realtime"] = {"status": "up", "http_status": req.status}
except Exception as exc:
probes["realtime"] = {"status": "down", "error": str(exc)[:120]}
# Redis / BullMQ queue depth
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()
if data.startswith(":"):
depth = int(data[1:])
probes["queue"] = {"status": "up", "queue_name": "yiacad-eda", "depth": depth}
else:
probes["queue"] = {"status": "up", "queue_name": "yiacad-eda", "depth": None, "raw": data[:80]}
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]}
up_count = sum(1 for p in probes.values() if p.get("status") == "up")
up_count = sum(1 for p in probes.values() if isinstance(p, dict) and p.get("status") == "up")
total = len(probes)
if up_count == total:
overall = "up"
elif up_count == 0:
overall = "down"
else:
overall = "degraded"
overall = "up" if up_count == total else ("down" if up_count == 0 else "degraded")
return {
"status": overall,
"probes": probes,
"up_count": up_count,
"total": total,
"source": "direct-probes",
}