feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
This commit is contained in:
Executable
+453
@@ -0,0 +1,453 @@
|
||||
"""Explicit suite-plan coverage; never execute or guess nested shell/npm flows."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shlex
|
||||
import stat
|
||||
|
||||
from .common import digest, redact_argv, reject_symlinks
|
||||
from .package_tests import CORE_COMPONENT_SUITES, declared_tests, discovered_sources
|
||||
from .workspace import load_project
|
||||
|
||||
DISPOSITIONS = ("planned", "covered_elsewhere", "excluded", "unsupported")
|
||||
MAX_SUITES = 4096
|
||||
|
||||
|
||||
def _read_canonical(path: Path) -> str:
|
||||
reject_symlinks(path)
|
||||
descriptor = os.open(
|
||||
path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
|
||||
)
|
||||
with os.fdopen(descriptor, "rb") as handle:
|
||||
metadata = os.fstat(handle.fileno())
|
||||
if not stat.S_ISREG(metadata.st_mode) or metadata.st_size > 1024 * 1024:
|
||||
raise ValueError("Canonical focused gate must be a bounded regular file")
|
||||
encoded = handle.read(1024 * 1024 + 1)
|
||||
if len(encoded) > 1024 * 1024:
|
||||
raise ValueError("Canonical focused gate exceeds its size bound")
|
||||
return encoded.decode("utf-8")
|
||||
|
||||
|
||||
def _node_argv(argv: list[str], cwd: Path) -> tuple[str, ...] | None:
|
||||
if (
|
||||
not argv
|
||||
or argv[0] not in {"node", "{node}", "$NODE"}
|
||||
and Path(argv[0]).name not in {"node", "nodejs"}
|
||||
):
|
||||
return None
|
||||
offset = 2 if len(argv) > 1 and argv[1] == "--test" else 1
|
||||
if len(argv) != offset + 1:
|
||||
return None
|
||||
target = Path(argv[offset])
|
||||
if "$" in str(target):
|
||||
return None
|
||||
return ("node", *argv[1:offset], str((cwd / target).resolve()))
|
||||
|
||||
|
||||
def canonical_invocations(workspace_root: Path, meta: Path, core: Path) -> dict:
|
||||
"""Recognize direct commands in exact registered phase wrappers or legacy top level.
|
||||
|
||||
Shell is not evaluated. Bodies, conditionals, loops, functions, npm hooks and
|
||||
recursive commands do not grant coverage. Unknown cwd invalidates matches.
|
||||
The registered marked wrappers are the only function bodies admitted.
|
||||
"""
|
||||
path = meta / "tools/checks/check-focused.sh"
|
||||
try:
|
||||
text = _read_canonical(path)
|
||||
except (OSError, ValueError):
|
||||
return {
|
||||
"path": str(path),
|
||||
"sha256": None,
|
||||
"npm": [],
|
||||
"node": [],
|
||||
"node_phases": [],
|
||||
"notes": [
|
||||
"Canonical focused script is unavailable/unreadable; no package coverage inferred."
|
||||
],
|
||||
}
|
||||
values = {
|
||||
"WORKSPACE_ROOT": str(workspace_root),
|
||||
"META_ROOT": str(meta),
|
||||
"ROOT": str(core),
|
||||
}
|
||||
|
||||
def expand(value: str) -> str | None:
|
||||
value = re.sub(
|
||||
r"\$\{(WORKSPACE_ROOT|META_ROOT|ROOT)\}|\$(WORKSPACE_ROOT|META_ROOT|ROOT)(?![A-Za-z0-9_])",
|
||||
lambda match: values[match[1] or match[2]],
|
||||
value,
|
||||
)
|
||||
return None if "$" in value or "`" in value else value
|
||||
|
||||
result = {
|
||||
"path": str(path),
|
||||
"sha256": hashlib.sha256(text.encode()).hexdigest(),
|
||||
"npm": [],
|
||||
"node": [],
|
||||
"node_phases": [],
|
||||
"notes": [],
|
||||
}
|
||||
metadata = meta / "tools/checks/focused-phases.json"
|
||||
blocks = {None: text}
|
||||
if metadata.exists() or metadata.is_symlink() or "# devkit-phase:" in text:
|
||||
from .catalog import focused_phases, focused_phase_bodies
|
||||
|
||||
try:
|
||||
phases = focused_phases(meta)
|
||||
blocks = focused_phase_bodies(text, phases)
|
||||
result["phase_ids"] = [phase["id"] for phase in phases]
|
||||
result["metadata_sha256"] = digest(phases)
|
||||
except (OSError, ValueError):
|
||||
result["notes"].append(
|
||||
"Focused phase metadata/marked bodies are invalid or unavailable; no phase coverage inferred."
|
||||
)
|
||||
return result
|
||||
cwd: Path | None = meta
|
||||
body_end, nesting = None, 0
|
||||
current_phase = object()
|
||||
for phase_id, line in (
|
||||
(phase_id, line)
|
||||
for phase_id, block in blocks.items()
|
||||
for line in block.replace("\\\n", " ").splitlines()
|
||||
):
|
||||
if current_phase != phase_id:
|
||||
cwd, body_end, nesting = meta, None, 0
|
||||
current_phase = phase_id
|
||||
stripped = line.strip()
|
||||
if body_end is not None:
|
||||
if stripped == body_end:
|
||||
body_end = None
|
||||
continue
|
||||
heredoc = re.search(r"<<-?\s*['\"]?([A-Za-z_][A-Za-z0-9_]*)['\"]?", line)
|
||||
if heredoc:
|
||||
body_end = heredoc[1]
|
||||
continue
|
||||
if not stripped or stripped.startswith("#"):
|
||||
continue
|
||||
if re.match(
|
||||
r"(?:if|for|while|until|case|select)\b|(?:function\s+\w+|\w+\s*\(\s*\))",
|
||||
stripped,
|
||||
):
|
||||
nesting += 1
|
||||
continue
|
||||
if re.match(r"(?:fi|done|esac)\b|^}\s*;?$", stripped):
|
||||
nesting = max(0, nesting - 1)
|
||||
continue
|
||||
if nesting:
|
||||
if re.search(r"\bcd\s", stripped):
|
||||
cwd = None
|
||||
continue
|
||||
try:
|
||||
parts = shlex.split(line, comments=True)
|
||||
except ValueError:
|
||||
result["notes"].append(
|
||||
"An unparseable shell line grants no suite coverage."
|
||||
)
|
||||
continue
|
||||
if not parts:
|
||||
continue
|
||||
if parts[0] == "cd":
|
||||
destination = expand(parts[1]) if len(parts) == 2 else None
|
||||
candidate = (
|
||||
(cwd / destination).resolve()
|
||||
if destination and cwd
|
||||
else Path(destination).resolve()
|
||||
if destination and Path(destination).is_absolute()
|
||||
else None
|
||||
)
|
||||
cwd = (
|
||||
candidate
|
||||
if candidate and candidate.is_relative_to(workspace_root)
|
||||
else None
|
||||
)
|
||||
continue
|
||||
if cwd is None:
|
||||
continue
|
||||
if (
|
||||
parts[0] in {"$NPM", "${NPM}", "npm"}
|
||||
and len(parts) >= 3
|
||||
and parts[1] == "run"
|
||||
and re.fullmatch(r"test(?::[A-Za-z0-9_.:-]+)?", parts[2])
|
||||
):
|
||||
arguments = parts[3:]
|
||||
if arguments and (
|
||||
arguments[0] != "--"
|
||||
or any(
|
||||
not re.fullmatch(r"[A-Za-z0-9_.-]+", item) for item in arguments[1:]
|
||||
)
|
||||
):
|
||||
continue
|
||||
result["npm"].append(
|
||||
{
|
||||
"cwd": str(cwd),
|
||||
"name": parts[2],
|
||||
"args": arguments[1:] if arguments else [],
|
||||
"phase": phase_id,
|
||||
}
|
||||
)
|
||||
elif parts[0] in {"$NODE", "${NODE}", "node"}:
|
||||
expanded = [expand(part) for part in parts[1:]]
|
||||
if all(part is not None for part in expanded):
|
||||
command = _node_argv(["node", *expanded], cwd)
|
||||
if command:
|
||||
result["node"].append(command)
|
||||
result["node_phases"].append({"argv": command, "phase": phase_id})
|
||||
result["notes"] = list(dict.fromkeys(result["notes"]))[:8]
|
||||
return result
|
||||
|
||||
|
||||
def coverage_inventory(
|
||||
workspace_root: Path, profile: str, project_file: Path | None, stages: list[dict]
|
||||
) -> dict:
|
||||
workspace_root = workspace_root.resolve()
|
||||
project = load_project(workspace_root, project_file)
|
||||
stage_map = {item["id"]: item for item in stages}
|
||||
direct = {}
|
||||
explicit_npm = {}
|
||||
for stage in stages:
|
||||
argv, cwd = stage["argv"], Path(stage["cwd"])
|
||||
normalized = _node_argv(argv, cwd)
|
||||
if normalized:
|
||||
direct[normalized] = stage["id"]
|
||||
if (
|
||||
len(argv) == 3
|
||||
and (argv[0] in {"npm", "{npm}"} or Path(argv[0]).name == "npm")
|
||||
and argv[1] == "run"
|
||||
):
|
||||
explicit_npm[(str(cwd.resolve()), argv[2])] = stage["id"]
|
||||
paths = {repo.name: repo.path for repo in project.repositories}
|
||||
canonical = (
|
||||
canonical_invocations(
|
||||
workspace_root,
|
||||
paths.get("govoplan", workspace_root / "govoplan"),
|
||||
paths.get("govoplan-core", workspace_root / "govoplan-core"),
|
||||
)
|
||||
if project_file is None
|
||||
else None
|
||||
)
|
||||
canonical_stages = {}
|
||||
if canonical is not None:
|
||||
legacy = stage_map.get("focused-workspace")
|
||||
if legacy and legacy["argv"] == ["bash", canonical["path"]]:
|
||||
canonical_stages[None] = "focused-workspace"
|
||||
for identity in canonical.get("phase_ids", []):
|
||||
check = stage_map.get("focused." + identity)
|
||||
if check and check["argv"] == [
|
||||
"bash",
|
||||
canonical["path"],
|
||||
"--phase",
|
||||
identity,
|
||||
]:
|
||||
canonical_stages[identity] = check["id"]
|
||||
full = bool(canonical_stages)
|
||||
invocations = (
|
||||
[
|
||||
{**item, "covering_stage": canonical_stages[item.get("phase")]}
|
||||
for item in canonical["npm"]
|
||||
if item.get("phase") in canonical_stages
|
||||
]
|
||||
if canonical
|
||||
else []
|
||||
)
|
||||
canonical_nodes = (
|
||||
{
|
||||
tuple(item["argv"]): canonical_stages[item.get("phase")]
|
||||
for item in canonical.get("node_phases", [])
|
||||
if item.get("phase") in canonical_stages
|
||||
}
|
||||
if canonical
|
||||
else {}
|
||||
)
|
||||
suites, notes = [], []
|
||||
if canonical:
|
||||
notes.extend(canonical["notes"])
|
||||
core_package = (
|
||||
paths.get("govoplan-core", workspace_root / "govoplan-core")
|
||||
/ "webui/package.json"
|
||||
)
|
||||
core_requests = [
|
||||
item
|
||||
for item in invocations
|
||||
if item["cwd"] == str(core_package.parent) and item["name"] == "test:components"
|
||||
]
|
||||
requested_components = set()
|
||||
for item in core_requests:
|
||||
requested_components.update(
|
||||
CORE_COMPONENT_SUITES
|
||||
if not item["args"] or item["args"] == ["all"]
|
||||
else item["args"]
|
||||
)
|
||||
requested_components.intersection_update(CORE_COMPONENT_SUITES)
|
||||
|
||||
for repo in project.repositories:
|
||||
for location in ("package.json", "webui/package.json"):
|
||||
package = repo.path / location
|
||||
if not package.exists() and not package.is_symlink():
|
||||
continue
|
||||
entries = declared_tests(repo, package)
|
||||
if location == "webui/package.json":
|
||||
declared_argv = {
|
||||
tuple(item["_argv"]) for item in entries if item["_argv"]
|
||||
}
|
||||
entries.extend(
|
||||
item
|
||||
for item in discovered_sources(repo)
|
||||
if not item["_argv"] or tuple(item["_argv"]) not in declared_argv
|
||||
)
|
||||
for item in entries:
|
||||
private_argv = item.pop("_argv")
|
||||
component = item["component_suite"]
|
||||
identity = (str(package.parent), item["name"])
|
||||
normalized = (
|
||||
_node_argv(private_argv, package.parent) if private_argv else None
|
||||
)
|
||||
matching = [
|
||||
value
|
||||
for value in invocations
|
||||
if (value["cwd"], value["name"]) == identity
|
||||
]
|
||||
item.update(disposition="excluded", covering_stage=None)
|
||||
if component is not None:
|
||||
all_or_selected = (
|
||||
component == "all" or component in requested_components
|
||||
)
|
||||
if "core.component-batch" in stage_map:
|
||||
item.update(
|
||||
disposition="planned"
|
||||
if component == "all"
|
||||
else "covered_elsewhere",
|
||||
covering_stage="core.component-batch",
|
||||
reason="UI explicitly runs the shared component batch; quick never compiles it.",
|
||||
)
|
||||
elif full and core_requests and all_or_selected:
|
||||
item.update(
|
||||
disposition="planned"
|
||||
if component == "all"
|
||||
else "covered_elsewhere",
|
||||
covering_stage=core_requests[0]["covering_stage"],
|
||||
reason=f"Canonical focused gate explicitly selects {len(requested_components)}/{len(CORE_COMPONENT_SUITES)} component suites; this is not the complete component batch.",
|
||||
)
|
||||
else:
|
||||
item["reason"] = (
|
||||
"No component compilation in quick/backend; UI runs all components. Full runs only its explicitly named subset."
|
||||
)
|
||||
if component == "all":
|
||||
selected = (
|
||||
list(CORE_COMPONENT_SUITES)
|
||||
if "core.component-batch" in stage_map
|
||||
else sorted(requested_components)
|
||||
)
|
||||
item.update(
|
||||
covered_components=selected,
|
||||
excluded_components=[
|
||||
name
|
||||
for name in CORE_COMPONENT_SUITES
|
||||
if name not in selected
|
||||
],
|
||||
)
|
||||
elif identity in explicit_npm:
|
||||
item.update(
|
||||
disposition="planned",
|
||||
covering_stage=explicit_npm[identity],
|
||||
reason="Explicit project check invokes this exact package suite.",
|
||||
)
|
||||
elif matching:
|
||||
if any(not value["args"] for value in matching):
|
||||
item.update(
|
||||
disposition="planned",
|
||||
covering_stage=next(
|
||||
value["covering_stage"]
|
||||
for value in matching
|
||||
if not value["args"]
|
||||
),
|
||||
reason="The canonical focused script directly invokes this exact package suite.",
|
||||
)
|
||||
else:
|
||||
item["reason"] = (
|
||||
"Canonical invocation supplies arguments; complete suite coverage cannot be inferred."
|
||||
)
|
||||
elif normalized in direct:
|
||||
item.update(
|
||||
disposition="planned",
|
||||
covering_stage=direct[normalized],
|
||||
reason="A selected stage runs this exact direct source-test command.",
|
||||
)
|
||||
elif normalized in canonical_nodes:
|
||||
item.update(
|
||||
disposition="covered_elsewhere",
|
||||
covering_stage=canonical_nodes[normalized],
|
||||
reason="Canonical focused script directly runs this suite's exact Node target.",
|
||||
)
|
||||
elif private_argv is None:
|
||||
item["disposition"] = "unsupported"
|
||||
elif item["name"] in {
|
||||
"test:module-permutations",
|
||||
"test:vite-cache-isolation",
|
||||
}:
|
||||
item["reason"] = (
|
||||
"Separate environment/permutation suite; absent from the selected stage plan."
|
||||
)
|
||||
else:
|
||||
item["reason"] = (
|
||||
"Not directly present in this profile's stage plan; nested npm scripts/hooks are not inferred."
|
||||
)
|
||||
suites.append(item)
|
||||
if len(suites) > MAX_SUITES:
|
||||
raise ValueError("Coverage inventory exceeds its bounded suite count")
|
||||
if project_file is not None:
|
||||
for check in project.config.get("checks", []):
|
||||
identity = check["id"]
|
||||
suites.append(
|
||||
{
|
||||
"repo": ",".join(check.get("repos", [])) or "project",
|
||||
"name": identity,
|
||||
"kind": "project-check",
|
||||
"package_path": None,
|
||||
"argv": redact_argv(check["argv"]),
|
||||
"command_sha256": digest(check["argv"]),
|
||||
"disposition": "planned" if identity in stage_map else "excluded",
|
||||
"covering_stage": identity if identity in stage_map else None,
|
||||
"reason": "Selected declared check or dependency."
|
||||
if identity in stage_map
|
||||
else "Declared check is outside this profile/selection.",
|
||||
}
|
||||
)
|
||||
if len(suites) > MAX_SUITES:
|
||||
raise ValueError("Coverage inventory exceeds its bounded suite count")
|
||||
counts = {
|
||||
kind: sum(item["disposition"] == kind for item in suites)
|
||||
for kind in DISPOSITIONS
|
||||
}
|
||||
notes.append(
|
||||
"Coverage describes planned suite invocations, not passing tests, per-test coverage, or completed UI review."
|
||||
)
|
||||
if full:
|
||||
notes.append(
|
||||
"Full is the canonical focused gate, not every declared package test; recursive commands and npm hooks are deliberately not guessed."
|
||||
)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"profile": profile,
|
||||
"scope": "Declared root/webui package test scripts, discovered UI structural checks, and custom project checks across registered repositories.",
|
||||
"suite_count": len(suites),
|
||||
"stages": list(stage_map),
|
||||
"counts": counts,
|
||||
"suites": suites,
|
||||
"canonical_gate": {
|
||||
key: canonical[key]
|
||||
for key in ("path", "sha256", "metadata_sha256")
|
||||
if key in canonical
|
||||
}
|
||||
if canonical
|
||||
else None,
|
||||
"notes": notes,
|
||||
"summary": [
|
||||
"Suite coverage: "
|
||||
+ ", ".join(f"{value} {key}" for key, value in counts.items()),
|
||||
*notes,
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user