feat: 发布固件底层开发 Skill 套件
This commit is contained in:
commit
c7ae3b4ce1
72 files changed
+10212
No files matched your search
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate a machine-readable BSP GPIO implementation plan."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DIRECTIONS = {"input", "output", "peripheral", "none"}
|
||||
ACTIVE_LEVELS = {"high", "low", "paired", "unknown", None}
|
||||
DECISIONS = {"confirmed", "inherited", "no-gpio", "provisional", "blocked"}
|
||||
IMPLEMENTABLE = {"confirmed", "inherited", "no-gpio"}
|
||||
REQUIRED_SIGNAL_FIELDS = {
|
||||
"function",
|
||||
"pad",
|
||||
"mux",
|
||||
"gpio",
|
||||
"direction",
|
||||
"active_level",
|
||||
"boot_value",
|
||||
"runtime_owner",
|
||||
"dts_consumers",
|
||||
"source_evidence",
|
||||
"decision",
|
||||
"note",
|
||||
}
|
||||
|
||||
|
||||
def validate_plan(payload: object, allow_unconfirmed: bool = False) -> dict[str, object]:
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
if not isinstance(payload, dict):
|
||||
return {"passed": False, "errors": ["顶层必须是 JSON 对象"], "warnings": []}
|
||||
|
||||
if payload.get("schema_version") != 1:
|
||||
errors.append("schema_version 必须为 1")
|
||||
if not isinstance(payload.get("project"), str) or not payload.get("project", "").strip():
|
||||
errors.append("project 必须是非空字符串")
|
||||
if payload.get("validation_mode") not in {"emxres-config", "full-dts-audit"}:
|
||||
errors.append("validation_mode 必须为 emxres-config 或 full-dts-audit")
|
||||
sources = payload.get("sources")
|
||||
if not isinstance(sources, list) or not sources or not all(
|
||||
isinstance(item, str) and item.strip() for item in sources
|
||||
):
|
||||
errors.append("sources 必须是非空字符串数组")
|
||||
|
||||
confirmation = payload.get("confirmation")
|
||||
confirmed = isinstance(confirmation, dict) and confirmation.get("confirmed") is True
|
||||
if not confirmed and not allow_unconfirmed:
|
||||
errors.append("confirmation.confirmed 必须为 true")
|
||||
if confirmed:
|
||||
if not isinstance(confirmation.get("confirmed_at"), str) or not confirmation.get(
|
||||
"confirmed_at", ""
|
||||
).strip():
|
||||
errors.append("确认后必须填写 confirmation.confirmed_at")
|
||||
if not isinstance(confirmation.get("summary"), str) or not confirmation.get(
|
||||
"summary", ""
|
||||
).strip():
|
||||
errors.append("确认后必须填写 confirmation.summary")
|
||||
|
||||
signals = payload.get("signals")
|
||||
if not isinstance(signals, list) or not signals:
|
||||
errors.append("signals 必须是非空数组")
|
||||
signals = []
|
||||
|
||||
functions: set[str] = set()
|
||||
pads: set[str] = set()
|
||||
gpios: set[str] = set()
|
||||
for index, signal in enumerate(signals):
|
||||
prefix = f"signals[{index}]"
|
||||
if not isinstance(signal, dict):
|
||||
errors.append(f"{prefix} 必须是对象")
|
||||
continue
|
||||
missing = sorted(REQUIRED_SIGNAL_FIELDS - set(signal))
|
||||
if missing:
|
||||
errors.append(f"{prefix} 缺少字段: {', '.join(missing)}")
|
||||
continue
|
||||
|
||||
function = signal.get("function")
|
||||
if not isinstance(function, str) or not function.strip():
|
||||
errors.append(f"{prefix}.function 必须是非空字符串")
|
||||
elif function in functions:
|
||||
errors.append(f"重复功能名: {function}")
|
||||
else:
|
||||
functions.add(function)
|
||||
|
||||
direction = signal.get("direction")
|
||||
decision = signal.get("decision")
|
||||
active_level = signal.get("active_level")
|
||||
boot_value = signal.get("boot_value")
|
||||
pad = signal.get("pad")
|
||||
gpio = signal.get("gpio")
|
||||
|
||||
if direction not in DIRECTIONS:
|
||||
errors.append(f"{prefix}.direction 非法: {direction!r}")
|
||||
if active_level not in ACTIVE_LEVELS:
|
||||
errors.append(f"{prefix}.active_level 非法: {active_level!r}")
|
||||
if decision not in DECISIONS:
|
||||
errors.append(f"{prefix}.decision 非法: {decision!r}")
|
||||
elif decision not in IMPLEMENTABLE and not allow_unconfirmed:
|
||||
errors.append(f"{prefix} 仍为 {decision},不能进入实现")
|
||||
|
||||
if not isinstance(signal.get("runtime_owner"), str):
|
||||
errors.append(f"{prefix}.runtime_owner 必须是字符串")
|
||||
if not isinstance(signal.get("note"), str):
|
||||
errors.append(f"{prefix}.note 必须是字符串")
|
||||
if not isinstance(signal.get("dts_consumers"), list) or not all(
|
||||
isinstance(item, str) for item in signal.get("dts_consumers", [])
|
||||
):
|
||||
errors.append(f"{prefix}.dts_consumers 必须是字符串数组")
|
||||
evidence = signal.get("source_evidence")
|
||||
if not isinstance(evidence, list) or not evidence or not all(
|
||||
isinstance(item, str) and item.strip() for item in evidence
|
||||
):
|
||||
errors.append(f"{prefix}.source_evidence 必须是非空字符串数组")
|
||||
|
||||
if decision == "no-gpio":
|
||||
if direction != "none" or any(
|
||||
value is not None
|
||||
for value in (pad, signal.get("mux"), gpio, active_level, boot_value)
|
||||
):
|
||||
errors.append(
|
||||
f"{prefix} 的 no-gpio 行必须使用 direction=none 且 pad/mux/gpio/active_level/boot_value=null"
|
||||
)
|
||||
elif direction in {"input", "output"}:
|
||||
if not isinstance(pad, str) or not pad.strip():
|
||||
errors.append(f"{prefix} 的 GPIO 行必须填写 pad")
|
||||
if not isinstance(gpio, str) or not gpio.strip():
|
||||
errors.append(f"{prefix} 的 GPIO 行必须填写 gpio")
|
||||
if active_level is None:
|
||||
errors.append(f"{prefix} 的 GPIO 行不能使用 active_level=null")
|
||||
if active_level == "unknown" and not allow_unconfirmed:
|
||||
errors.append(f"{prefix} 的 active_level 仍为 unknown")
|
||||
if direction == "output" and boot_value not in {0, 1}:
|
||||
errors.append(f"{prefix} 的输出必须填写 boot_value=0 或 1")
|
||||
if direction == "input" and boot_value is not None:
|
||||
errors.append(f"{prefix} 的输入必须使用 boot_value=null")
|
||||
elif direction == "peripheral":
|
||||
if not isinstance(pad, str) or not pad.strip():
|
||||
errors.append(f"{prefix} 的外设复用必须填写 pad")
|
||||
if gpio is not None or active_level is not None or boot_value is not None:
|
||||
errors.append(f"{prefix} 的外设复用必须使用 gpio/active_level/boot_value=null")
|
||||
elif direction == "none" and decision != "no-gpio":
|
||||
errors.append(f"{prefix} 只有 no-gpio 决策可以使用 direction=none")
|
||||
|
||||
if isinstance(pad, str) and pad:
|
||||
if pad in pads:
|
||||
errors.append(f"重复 Pad: {pad}")
|
||||
pads.add(pad)
|
||||
if isinstance(gpio, str) and gpio:
|
||||
if gpio in gpios:
|
||||
errors.append(f"重复 GPIO: {gpio}")
|
||||
gpios.add(gpio)
|
||||
|
||||
if allow_unconfirmed and not confirmed:
|
||||
warnings.append("计划尚未获得用户确认,仅通过候选结构检查")
|
||||
return {
|
||||
"passed": not errors,
|
||||
"signal_count": len(signals),
|
||||
"errors": errors,
|
||||
"warnings": warnings,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("plan", type=Path)
|
||||
parser.add_argument("--allow-unconfirmed", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
payload = json.loads(args.plan.read_text(encoding="utf-8"))
|
||||
result = validate_plan(payload, allow_unconfirmed=args.allow_unconfirmed)
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
print(json.dumps({"passed": False, "errors": [str(exc)]}, ensure_ascii=False, indent=2))
|
||||
return 2
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
return 0 if result["passed"] else 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user