Files
zemion 2ffdb23f69
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 11m30s
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.
2026-09-09 02:03:17 +02:00

567 lines
21 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Test planning over existing checks; planning never executes a test or server."""
from __future__ import annotations
from pathlib import Path
from copy import deepcopy
from itertools import islice
import re
from .common import read_json
from .package_tests import declared_tests, discovered_sources, source_stage_id
PROFILES = ("quick", "ui", "backend", "full")
def stage(
identity: str,
title: str,
argv: list[str],
cwd: Path,
*,
reason: str,
deps: list[str] | None = None,
after: list[str] | None = None,
resources: list[str] | None = None,
timeout_seconds: int = 300,
) -> dict:
return {
"id": identity,
"title": title,
"argv": argv,
"cwd": str(cwd),
"deps": deps or [],
"after": after or [],
"resources": resources or [],
"timeout_seconds": timeout_seconds,
"reason": reason,
}
def _custom_stages(
project, workspace_root: Path, profile: str, selected, filtered: bool
) -> list[dict]:
# load_project validates every nested declaration before selection.
records = project.config.get("checks", [])
checks = {item["id"]: item for item in records}
profiles = project.config.get("profiles", {})
if profile not in profiles:
raise ValueError(
f"Project {project.name!r} does not declare profile {profile!r}"
)
selected_names = {repo.name for repo in selected}
wanted = set()
def include(identity: str, visiting: frozenset[str] = frozenset()) -> None:
if identity not in checks:
raise ValueError(f"Unknown check dependency: {identity}")
if identity in visiting:
raise ValueError(f"Cyclic check dependency: {identity}")
if identity in wanted:
return
for dependency in [
*checks[identity].get("deps", []),
*checks[identity].get("after", []),
]:
include(dependency, visiting | {identity})
wanted.add(identity)
for identity in profiles[profile]:
if identity not in checks:
raise ValueError(f"Unknown profile check: {identity}")
owned = set(checks[identity].get("repos", []))
if not filtered or not owned or selected_names & owned:
include(identity)
result = []
# A dependency may precede/follow its consumer in the configuration: the
# execution engine owns scheduling, not JSON declaration order.
for identity, item in checks.items():
if identity in wanted:
result.append(
stage(
identity,
item.get("title", identity),
list(item["argv"]),
workspace_root / item.get("cwd", "."),
deps=list(item.get("deps", [])),
after=list(item.get("after", [])),
resources=list(item.get("resources", [])),
timeout_seconds=item.get("timeout_seconds", 300),
reason=f"Project profile {profile}",
)
)
for field in ("inputs", "reuse"):
if field in item:
result[-1][field] = deepcopy(item[field])
return result
def focused_phases(meta: Path) -> list[dict]:
"""Read the same bounded, ordered phase metadata as the standalone gate."""
value = read_json(meta / "tools/checks/focused-phases.json", max_bytes=1024 * 1024)
if (
not isinstance(value, dict)
or set(value) != {"schema_version", "phases"}
or type(value["schema_version"]) is not int
or value["schema_version"] != 1
):
raise ValueError("Unsupported focused phase metadata schema")
phases = value["phases"]
if not isinstance(phases, list) or not 1 <= len(phases) <= 32:
raise ValueError("Focused phase catalog requires 132 definitions")
seen = set()
fields = {
"id",
"title",
"cwd",
"order_after",
"depends_on",
"resources",
"outputs",
"notes",
}
for phase in phases:
if not isinstance(phase, dict) or set(phase) != fields:
raise ValueError("Invalid focused phase metadata fields")
identity = phase["id"]
if (
not isinstance(identity, str)
or not re.fullmatch(r"[a-z][a-z0-9-]{0,63}", identity)
or identity in seen
):
raise ValueError("Invalid or duplicate focused phase ID")
if (
not isinstance(phase["title"], str)
or not phase["title"].strip()
or len(phase["title"]) > 256
or any(ord(char) < 32 for char in phase["title"])
):
raise ValueError("Invalid focused phase title")
if not isinstance(phase["cwd"], str) or phase["cwd"] not in {
"core",
"meta",
"core-webui",
"access-webui",
}:
raise ValueError("Unsupported focused phase working directory")
for field in ("order_after", "depends_on", "resources", "outputs", "notes"):
values = phase[field]
if (
not isinstance(values, list)
or len(values) > 64
or any(
not isinstance(item, str)
or not item
or len(item) > 4096
or "\0" in item
for item in values
)
or len(set(values)) != len(values)
):
raise ValueError("Invalid focused phase list: " + field)
if (set(phase["order_after"]) | set(phase["depends_on"])) - seen:
raise ValueError("Focused prerequisites must precede their consumer")
seen.add(identity)
return phases
def focused_phase_bodies(text: str, phases: list[dict]) -> dict[str, str]:
"""Only exact top-level registered wrappers are authoritative phase bodies.
Do not extract lookalike markers from heredocs, conditionals or unrelated
shell functions. This recognizes the maintained wrapper convention, not
arbitrary executable shell semantics.
"""
registered = {
"focused_phase_" + phase["id"].replace("-", "_"): phase["id"]
for phase in phases
}
lines, bodies, index, depth, heredoc = text.splitlines(), {}, 0, 0, None
while index < len(lines):
line = lines[index]
stripped = line.strip()
if heredoc is not None:
if stripped == heredoc:
heredoc = None
index += 1
continue
match = re.search(r"<<-?\s*['\"]?([A-Za-z_][A-Za-z0-9_]*)['\"]?", line)
if match:
heredoc = match[1]
index += 1
continue
function = re.fullmatch(r"(focused_phase_[a-z0-9_]+)\(\) \{", line)
if depth == 0 and function and function[1] in registered:
identity = registered[function[1]]
if (
identity in bodies
or index + 1 >= len(lines)
or lines[index + 1] != f"# devkit-phase: {identity} begin"
):
raise ValueError("Invalid or duplicate focused phase wrapper")
end = index + 2
while end < len(lines) and lines[end] != f"# devkit-phase: {identity} end":
end += 1
if end + 1 >= len(lines) or lines[end + 1] != "}":
raise ValueError("Unclosed focused phase wrapper")
bodies[identity] = "\n".join(lines[index + 2 : end]) + "\n"
index = end + 2
continue
if re.match(
r"(?:if|for|while|until|case|select)\b|(?:function\s+\w+|\w+\s*\(\s*\))",
stripped,
):
depth += 1
elif re.match(r"(?:fi|done|esac)\b|^}\s*;?$", stripped):
depth = max(0, depth - 1)
index += 1
if set(bodies) != {phase["id"] for phase in phases}:
raise ValueError("Focused phase metadata and marked implementations differ")
return {phase["id"]: bodies[phase["id"]] for phase in phases}
def _undeclared_source_note(project, workspace_root: Path) -> str | None:
"""Directory discovery is wider than registered Git input ownership."""
registered_paths = {repo.path.resolve() for repo in project.repositories}
children = list(islice(workspace_root.iterdir(), 4097))
if len(children) > 4096:
return "Workspace discovery exceeds its bounded ownership audit; native reuse is disabled."
for child in children:
if not child.name.startswith("govoplan") or child.resolve() in registered_paths:
continue
if any(
(child / name).exists() or (child / name).is_symlink()
for name in ("src", "webui")
):
return "Unregistered sibling src/WebUI inputs may be consumed by workspace discovery or PYTHONPATH; native reuse is disabled until their repository ownership is declared."
return None
def _apply_undeclared_source_note(checks: list[dict], note: str | None) -> None:
if note:
for check in checks:
check["reuse"] = "never"
check.setdefault("coverage_notes", []).append(note)
def _focused_ui_inputs(project) -> tuple[list[str] | None, str]:
names = {repo.name for repo in project.repositories}
if not {"govoplan", "govoplan-core"} <= names:
return None, "Missing Core/Meta ownership; input scope stays workspace-wide."
selected = {"govoplan", "govoplan-core"}
for repo in project.repositories:
root, webui = repo.path, repo.path / "webui"
if (
not root.is_dir()
or root.is_symlink()
or webui.is_symlink()
or webui.exists()
and not webui.is_dir()
):
return (
None,
"Missing or ambiguous repository/WebUI layout; input scope stays workspace-wide.",
)
if webui.is_dir():
selected.add(repo.name)
return (
sorted(selected),
"UI input scope includes whole Core/Meta and every registered WebUI repository, including helpers/configuration; it is not per-file dependency inference.",
)
def _apply_ui_inputs(check: dict, scope: tuple[list[str] | None, str]) -> None:
names, note = scope
if names is not None:
check["inputs"] = {"repos": list(names)}
check.setdefault("coverage_notes", []).append(note)
def _full_stages(project, workspace_root: Path, meta: Path, core: Path) -> list[dict]:
phases = focused_phases(meta)
ui_scope = _focused_ui_inputs(project)
directories = {
"core": core,
"meta": meta,
"core-webui": core / "webui",
"access-webui": workspace_root / "govoplan-access/webui",
}
checks = []
for phase in phases:
check = stage(
"focused." + phase["id"],
phase["title"],
[
"bash",
str(meta / "tools/checks/check-focused.sh"),
"--phase",
phase["id"],
],
directories[phase["cwd"]],
reason="Full retains every canonical phase in order; repository filters never narrow the required gate.",
deps=["focused." + value for value in phase["depends_on"]],
after=["focused." + value for value in phase["order_after"]],
resources=list(dict.fromkeys(["workspace:focused", *phase["resources"]])),
timeout_seconds=14400,
)
check["coverage_notes"] = list(phase["notes"])
check["phase_outputs"] = list(phase["outputs"])
if phase["id"] in {"core-ui", "module-builds", "browser", "module-ui"}:
check["resources"] = list(
dict.fromkeys(
[
*check["resources"],
*[f"webui:{repo.name}" for repo in project.repositories],
]
)
)
_apply_ui_inputs(check, ui_scope)
else:
check["coverage_notes"].append(
"Cross-module backend/tooling checks retain conservative whole-workspace inputs."
)
checks.append(check)
_apply_undeclared_source_note(
checks, _undeclared_source_note(project, workspace_root)
)
return checks
def _expanded_repositories(project, selected, *, changed: bool):
"""Conservative shared changes; declared interface consumers otherwise.
This is a selection aid, not a claim of exhaustive runtime dependency
analysis. The full profile always retains the canonical workspace gate.
"""
if not changed or not selected:
return selected, "explicit selection" if selected else "no changed repositories"
names = {repo.name for repo in selected}
if names & {"govoplan", "govoplan-core"}:
return list(
project.repositories
), "Core/Meta changed; all registered consumers conservatively selected"
# Reuse the release contract parser without importing module application
# code. If available declarations cannot be parsed, broaden selection.
import sys
meta = next(
(repo.path for repo in project.repositories if repo.name == "govoplan"), None
)
if meta is None:
return selected, "changed repositories; no GovOPlaN contract catalog"
release = meta / "tools" / "release"
if not (release / "govoplan_release" / "contracts.py").is_file():
return selected, "changed repositories; contract parser unavailable"
sys.path.insert(0, str(release))
try:
from govoplan_release.contracts import parse_manifest_contract
contracts = []
for repo in project.repositories:
for manifest in sorted((repo.path / "src").glob("*/backend/manifest.py")):
parsed = parse_manifest_contract(manifest, repo_name=repo.name)
if parsed is None:
return list(
project.repositories
), "unresolved manifest contract; conservative workspace selection"
contracts.append(parsed)
while True:
providers = {
item.name
for contract in contracts
if contract.repo in names
for item in contract.provides_interfaces
}
consumers = {
contract.repo
for contract in contracts
if any(item.name in providers for item in contract.requires_interfaces)
}
added = consumers - names
if not added:
break
names.update(added)
except (ImportError, AttributeError, OSError, SyntaxError, ValueError):
return list(
project.repositories
), "contract analysis unavailable; conservative workspace selection"
finally:
sys.path.remove(str(release))
return [
repo for repo in project.repositories if repo.name in names
], "changed repositories plus declared interface consumers"
def _module_ui_plan(repo, *, reason: str) -> tuple[list[dict], list[str]]:
"""Use package-owned direct Node test metadata, excluding shell/build chains.
Source structural scripts are also discoverable by the existing established
names. Unknown shell commands are deliberately not guessed or rewritten.
"""
webui = repo.path / "webui"
package_path = webui / "package.json"
if not package_path.is_file():
return [], []
scripts: dict[tuple[str, ...], list[str]] = {}
omitted = []
for item in [*declared_tests(repo, package_path), *discovered_sources(repo)]:
if item["component_suite"] is not None:
omitted.append(
f"{repo.name} {item['name']}: only covered by the explicit UI component batch; quick does not compile components"
)
elif item["name"] in {"test:module-permutations", "test:vite-cache-isolation"}:
omitted.append(
f"{repo.name} {item['name']}: separate environment/permutation verification, not run by this scoped profile"
)
elif item["_argv"]:
scripts[tuple(item["_argv"])] = item["_argv"]
else:
omitted.append(f"{repo.name} {item['name']}: {item['reason']}")
return [
stage(
source_stage_id(repo.name, argv),
f"{repo.name}: {Path(argv[-1]).stem}",
argv,
webui,
reason=reason,
resources=[f"webui:{repo.name}"],
)
for _, argv in sorted(scripts.items())
], omitted
def module_ui_stages(repo, *, reason: str) -> list[dict]:
return _module_ui_plan(repo, reason=reason)[0]
def build_stages(
workspace_root: Path,
profile: str,
repos: list[str],
changed: bool,
project: Path | None = None,
) -> list[dict]:
from .workspace import load_project, selected_repositories
if profile not in PROFILES:
raise ValueError(f"Unknown check profile: {profile}")
workspace_root = workspace_root.resolve()
loaded = load_project(workspace_root, project)
selected = selected_repositories(loaded, repos, changed=changed)
if project is not None:
return _custom_stages(
loaded, workspace_root, profile, selected, bool(repos or changed)
)
selected, reason = _expanded_repositories(loaded, selected, changed=changed)
mapping = {repo.name: repo.path for repo in loaded.repositories}
meta = mapping.get("govoplan", workspace_root / "govoplan")
core = mapping.get("govoplan-core", workspace_root / "govoplan-core")
if profile == "full":
return _full_stages(loaded, workspace_root, meta, core)
if changed and not selected:
return []
checks = []
for identity, command in (
(
"contracts",
[
"{python}",
str(meta / "tools/checks/check-contracts.py"),
"--workspace-root",
str(workspace_root),
"--no-impact",
],
),
(
"manifests",
[
"{python}",
str(meta / "tools/checks/check-manifest-shapes.py"),
"--workspace-root",
str(workspace_root),
"--require-architecture",
],
),
):
checks.append(
stage(
identity,
f"Workspace {identity}",
command,
meta,
reason="Shared manifest/interface invariants",
timeout_seconds=600,
)
)
if profile in {"quick", "ui"}:
ui_scope = _focused_ui_inputs(loaded)
coverage_notes = [
"Scoped source/component checks are not a complete module review or the full focused gate."
]
for identity in ("jsx-value-imports", "heading-help"):
checks.append(
stage(
identity,
f"Shared {identity} contract",
["{node}", str(meta / f"tools/checks/check-{identity}.mjs")],
meta,
reason="Existing source-only cross-module guard",
)
)
_apply_ui_inputs(checks[-1], ui_scope)
for repo in selected:
stages, omissions = _module_ui_plan(repo, reason=reason)
checks.extend(stages)
coverage_notes.extend(omissions)
checks[0]["coverage_notes"] = coverage_notes
if profile == "ui":
checks.append(
stage(
"core.component-batch",
"Core component suites (compile once)",
["{node}", str(core / "webui/scripts/run-component-tests.mjs")],
core / "webui",
reason="Shared components affect every UI consumer",
timeout_seconds=900,
)
)
_apply_ui_inputs(checks[-1], ui_scope)
if profile == "backend":
for repo in selected:
if (repo.path / "tests").is_dir():
checks.append(
stage(
f"{repo.name}.backend",
f"{repo.name} backend tests",
["{python}", "-m", "pytest", "-q", str(repo.path / "tests")],
repo.path,
reason=reason,
resources=["backend:test-state"],
timeout_seconds=1800,
)
)
_apply_undeclared_source_note(
checks, _undeclared_source_note(loaded, workspace_root)
)
return checks
def build_coverage(
workspace_root: Path,
profile: str,
repos: list[str],
changed: bool,
project: Path | None = None,
*,
stages: list[dict] | None = None,
) -> dict:
"""Read-only suite inventory; prebuilt stages avoid repeating selection queries."""
from .coverage import coverage_inventory
if profile not in PROFILES:
raise ValueError(f"Unknown check profile: {profile}")
if stages is None:
stages = build_stages(workspace_root, profile, repos, changed, project)
return coverage_inventory(workspace_root, profile, project, stages)