Implement configuration, runtime, frontend, and task governance closure

This commit is contained in:
2026-08-21 16:51:42 +08:00
parent b1b5a3cc8a
commit 63c0408ce7
92 changed files with 343619 additions and 172 deletions

View File

@@ -7,6 +7,7 @@ import argparse
import hashlib
import json
import re
import subprocess
from pathlib import Path
from typing import Iterable
@@ -32,6 +33,7 @@ HAL_COMMANDS = {"source": "hal_source", "loadusr": "hal_user_component"}
TOKEN_RE = re.compile(r"(?<![A-Za-z0-9_./~-])([^\s\"'=;,]+)")
EXT_RE = re.compile(r"\.(?:ini|hal|tbl|var|ngc|nc|tap|py|xml|ui|glade|tcl|clp|pref|prefs|dat|png|jpg|jpeg|gif|stl|obj)$", re.I)
_SHA_CACHE: dict[Path, str] = {}
_LOCKED_SUBMODULE_CACHE: dict[Path, set[str] | None] = {}
def sha256(path: Path) -> str:
@@ -99,6 +101,44 @@ def config_files(config_root: Path) -> list[Path]:
return sorted(path for path in config_root.rglob("*") if path.is_file())
def locked_submodule_files(repo_root: Path) -> set[str] | None:
"""Return untracked LinuxCNC paths excluded from the locked inventory.
Ignored build outputs are intentionally retained because the existing
context lock was generated from that checkout. Only explicit untracked
source/config fixtures are excluded from the locked inventory.
"""
submodule = repo_root / "linuxcnc"
key = submodule.resolve()
if key in _LOCKED_SUBMODULE_CACHE:
return _LOCKED_SUBMODULE_CACHE[key]
try:
completed = subprocess.run(
["git", "-C", str(submodule), "status", "--porcelain=v1", "--untracked-files=all"],
check=False,
capture_output=True,
text=True,
)
if completed.returncode != 0:
result = None
else:
result = {line[3:] for line in completed.stdout.splitlines() if line.startswith("?? ")}
except (OSError, UnicodeError):
result = None
_LOCKED_SUBMODULE_CACHE[key] = result
return result
def is_locked_path(repo_root: Path, path: Path, locked: set[str] | None) -> bool:
if locked is None:
return True
try:
relative = path.resolve().relative_to((repo_root / "linuxcnc").resolve()).as_posix()
except ValueError:
return False
return relative not in locked
def add_row(rows: list[dict[str, object]], repo_root: Path, config_root: Path, entrypoint: Path,
target: Path | None, raw: str, kind: str, edge_kind: str, line: int, status: str | None = None,
origin: Path | None = None) -> None:
@@ -128,6 +168,7 @@ def add_row(rows: list[dict[str, object]], repo_root: Path, config_root: Path, e
def scan_entrypoint(repo_root: Path, config_root: Path, entrypoint: Path) -> list[dict[str, object]]:
rows: list[dict[str, object]] = []
locked = locked_submodule_files(repo_root)
ini_rows = parse_ini(entrypoint)
row_numbers = {(section, key, value): index for index, (section, key, value) in enumerate(ini_rows, 1)}
for section, key, value in ini_rows:
@@ -152,7 +193,7 @@ def scan_entrypoint(repo_root: Path, config_root: Path, entrypoint: Path) -> lis
add_row(rows, repo_root, config_root, entrypoint, target, token, RESOURCE_EXTENSIONS.get(Path(token).suffix.lower(), "external"), f"ini:{section}:{key}", row_numbers[(section, key, value)], status if target is None else None)
seen_paths = {str(row["path"]) for row in rows}
for path in sorted(path for path in entrypoint.parent.rglob("*") if path.is_file() and path != entrypoint):
for path in sorted(path for path in entrypoint.parent.rglob("*") if path.is_file() and path != entrypoint and is_locked_path(repo_root, path, locked)):
path_key = rel(repo_root, path)
if path.suffix.lower() in RESOURCE_EXTENSIONS and path_key not in seen_paths:
add_row(rows, repo_root, config_root, entrypoint, path, rel(repo_root, path), RESOURCE_EXTENSIONS[path.suffix.lower()], "directory_member", 0)
@@ -187,7 +228,8 @@ def generate(repo_root: Path, output: Path, report_path: Path) -> tuple[int, str
config_root = repo_root / CONFIG_ROOT
if not config_root.is_dir():
raise FileNotFoundError(f"required config root is missing: {CONFIG_ROOT}")
entrypoints = sorted(path for path in config_root.rglob("*.ini") if path.is_file())
locked = locked_submodule_files(repo_root)
entrypoints = sorted(path for path in config_root.rglob("*.ini") if path.is_file() and is_locked_path(repo_root, path, locked))
rows: list[dict[str, object]] = []
for entrypoint in entrypoints:
rows.extend(scan_entrypoint(repo_root, config_root, entrypoint))