This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user