5efb380da5
* feat: automate yiacad pr review lane * fix: stabilize yiacad review and spec gates * fix: scope kicad exports to repo hardware * fix: ignore kicad block library sources in exports * feat: Add infra VPS monitoring runbook and healthcheck scripts - Created INFRA_VPS_RUNBOOK_2026.md detailing operational procedures for monitoring VPS services. - Added infra_vps_healthcheck.sh script for automated health checks on DNS, TLS, TCP, and HTTP for VPS services. - Introduced infra_vps_security_audit.sh for non-intrusive security checks on external VPS services. - Established JSON schema for infra VPS inventory in infra_vps.schema.json. - Developed integration for runtime status reporting in the Next.js API route. - Implemented Playwright tests for smoke testing the application and ensuring core functionalities. - Updated Makefile for development dependencies and testing commands. - Created various test files for unit and end-to-end testing across different components. * feat(agentics): update mesh agents, gates, prompts, and workflows
340 lines
13 KiB
YAML
340 lines
13 KiB
YAML
name: KiCad Exports
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- "hardware/**"
|
|
- "tools/hw/**"
|
|
pull_request:
|
|
paths:
|
|
- "hardware/**"
|
|
- "tools/hw/**"
|
|
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 "*.kicad_block/*" \
|
|
-not -path "*backup*" | sort)
|
|
PCBS=$(find hardware -name "*.kicad_pcb" | sort)
|
|
|
|
echo "Found schematics:"
|
|
echo "$SCHEMATICS"
|
|
echo ""
|
|
echo "Found PCBs:"
|
|
echo "${PCBS:-<none>}"
|
|
|
|
# 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/
|
|
|
|
- name: Build normalized YiACAD evidence pack
|
|
if: always()
|
|
run: |
|
|
mkdir -p artifacts/ci
|
|
STATUS="success"
|
|
SUMMARY="KiCad export lanes passed with ERC/DRC green."
|
|
|
|
if [[ "${{ steps.erc.outputs.erc_exit }}" != "0" ]] || [[ "${{ steps.drc.outputs.drc_exit }}" != "0" ]]; then
|
|
STATUS="failure"
|
|
SUMMARY="KiCad export lane failed: ERC=${{ steps.erc.outputs.erc_exit }}, DRC=${{ steps.drc.outputs.drc_exit }}."
|
|
fi
|
|
|
|
python3 tools/ci/write_yiacad_evidence_pack.py \
|
|
--output artifacts/ci/kicad_exports_evidence.json \
|
|
--workflow "KiCad Exports" \
|
|
--lane "kicad-exports" \
|
|
--status "${STATUS}" \
|
|
--summary "${SUMMARY}" \
|
|
--repository "${GITHUB_REPOSITORY}" \
|
|
--server-url "${GITHUB_SERVER_URL}" \
|
|
--run-id "${GITHUB_RUN_ID}" \
|
|
--run-attempt "${GITHUB_RUN_ATTEMPT}" \
|
|
--ref "${GITHUB_REF}" \
|
|
--sha "${GITHUB_SHA}" \
|
|
--event "${GITHUB_EVENT_NAME}" \
|
|
--engine "kicad" \
|
|
--artifact-path "artifacts/hw_exports/erc/" \
|
|
--artifact-path "artifacts/hw_exports/drc/" \
|
|
--artifact-path "artifacts/hw_exports/pdf/" \
|
|
--artifact-path "artifacts/hw_exports/svg/" \
|
|
--artifact-path "artifacts/hw_exports/bom/" \
|
|
--artifact-path "artifacts/hw_exports/netlist/"
|
|
|
|
- name: Upload YiACAD evidence pack
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: yiacad-evidence-pack-kicad-exports
|
|
if-no-files-found: error
|
|
path: artifacts/ci/kicad_exports_evidence.json
|
|
|
|
# ── 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."
|