132 lines
4.1 KiB
Python
132 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import configparser
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if hasattr(sys.stdout, "reconfigure"):
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
if hasattr(sys.stderr, "reconfigure"):
|
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
REQUIRED_PATHS = [
|
|
"AGENTS.md",
|
|
".gitea/workflows/ci.yml",
|
|
"README.md",
|
|
"Requirements.md",
|
|
"docs/TESTS.md",
|
|
"docs/POWER_OPTIMIZATION.md",
|
|
"docs/engineering/current-state.md",
|
|
"docs/engineering/risk-register.md",
|
|
"docs/engineering/test-matrix.md",
|
|
"docs/engineering/hil-strategy.md",
|
|
"hil_tests/README.md",
|
|
]
|
|
TEST_SUITES = [
|
|
"test_html_escape",
|
|
"test_json_codec",
|
|
"test_lora_transport",
|
|
"test_meter_fault_count",
|
|
"test_payload_codec",
|
|
"test_refactor_smoke",
|
|
"test_security_fuzz",
|
|
]
|
|
TRACEABILITY_CONTRACTS = [
|
|
"LoRa frame format",
|
|
"CRC validation",
|
|
"Chunk reassembly",
|
|
"Payload schema",
|
|
"Sender-ID validation",
|
|
"ACK matching",
|
|
"Time bootstrap",
|
|
"Retry bounds",
|
|
"Queue bounds",
|
|
"Duplicate handling",
|
|
"MQTT JSON keys",
|
|
"Home Assistant discovery fields",
|
|
"CSV compatibility",
|
|
"HTML escaping",
|
|
"Web input validation",
|
|
]
|
|
|
|
|
|
def read_text(path: str) -> str:
|
|
return (ROOT / path).read_text(encoding="utf-8")
|
|
|
|
|
|
def platformio_envs() -> set[str]:
|
|
parser = configparser.RawConfigParser()
|
|
parser.optionxform = str
|
|
with (ROOT / "platformio.ini").open(encoding="utf-8") as handle:
|
|
parser.read_file(handle)
|
|
return {section.split(":", 1)[1] for section in parser.sections() if section.startswith("env:")}
|
|
|
|
|
|
def main() -> int:
|
|
errors: list[str] = []
|
|
|
|
for rel in REQUIRED_PATHS:
|
|
if not (ROOT / rel).exists():
|
|
errors.append(f"Missing required documentation or harness file: {rel}")
|
|
|
|
if errors:
|
|
for error in errors:
|
|
print(error)
|
|
return 1
|
|
|
|
envs = platformio_envs()
|
|
tests_doc = read_text("docs/TESTS.md")
|
|
readme = read_text("README.md")
|
|
requirements = read_text("Requirements.md")
|
|
current_state = read_text("docs/engineering/current-state.md")
|
|
risk_register = read_text("docs/engineering/risk-register.md")
|
|
test_matrix = read_text("docs/engineering/test-matrix.md")
|
|
agents = read_text("AGENTS.md")
|
|
|
|
for env in sorted(envs):
|
|
if f"`{env}`" not in tests_doc:
|
|
errors.append(f"docs/TESTS.md does not document environment {env}.")
|
|
for stale in ["lilygo-t3-v1-6-1", "schema v3", "schema `3`"]:
|
|
if stale in tests_doc:
|
|
errors.append(f"docs/TESTS.md still contains stale text: {stale}")
|
|
for suite in TEST_SUITES:
|
|
if suite not in tests_doc:
|
|
errors.append(f"docs/TESTS.md does not document suite {suite}.")
|
|
for contract in TRACEABILITY_CONTRACTS:
|
|
if contract not in tests_doc or contract not in test_matrix:
|
|
errors.append(f"Traceability contract missing from docs: {contract}")
|
|
|
|
schema_v4_markers = ["schema v4", "schema `4`", "schema `v4`"]
|
|
for path, text in [("README.md", readme), ("Requirements.md", requirements), ("current-state.md", current_state)]:
|
|
if not any(marker in text for marker in schema_v4_markers):
|
|
errors.append(f"{path} does not document payload schema v4.")
|
|
if "schema v3" in text or "schema `3`" in text:
|
|
errors.append(f"{path} still documents schema v3.")
|
|
|
|
for category in [
|
|
"Verified Defects",
|
|
"Probable Risks",
|
|
"Missing Tests",
|
|
"Missing Documentation",
|
|
"Security Decisions",
|
|
"Hardware-Dependent Questions",
|
|
]:
|
|
if category not in risk_register:
|
|
errors.append(f"risk-register.md missing category: {category}")
|
|
|
|
if "python tools/verify.py" not in agents:
|
|
errors.append("AGENTS.md does not name the canonical verification command.")
|
|
|
|
if errors:
|
|
for error in errors:
|
|
print(f"ERROR: {error}")
|
|
return 1
|
|
|
|
print("Documentation consistency checks passed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|