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:
@@ -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."
|
||||
@@ -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`).
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user