Files
PerishFire 84381efab9 feat(ci): add global hash skip planning (#7185)
* feat(ci): add global hash skip planning

* refactor(ci): remove superseded hash skip cache

* refactor(ci): keep scope trust in planner

* fix(ci): run scope goldens for planner changes

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

* fix(ci): publish hash state after successful validation

Generated-By: looper 0.11.2 (runner=fixer, agent=codex)

---------

Co-authored-by: Looper <looper@noreply.github.com>
2026-08-21 00:12:33 +08:00

56 lines
1.5 KiB
Python

import json
from pathlib import Path
class ConfigError(ValueError):
pass
def repository_root(script_file: str) -> Path:
return Path(script_file).resolve().parents[2]
def load_json(path: Path):
try:
with path.open(encoding="utf-8") as source:
return json.load(source)
except (OSError, json.JSONDecodeError) as error:
raise ConfigError(f"cannot load {path}: {error}") from error
def object_value(value, label: str):
if not isinstance(value, dict):
raise ConfigError(f"{label} must be an object")
return value
def array_value(value, label: str):
if not isinstance(value, list):
raise ConfigError(f"{label} must be an array")
return value
def string_value(value, label: str):
if not isinstance(value, str) or not value:
raise ConfigError(f"{label} must be a non-empty string")
return value
def exact_keys(value: dict, expected: set[str], label: str):
actual = set(value)
if actual != expected:
missing = sorted(expected - actual)
extra = sorted(actual - expected)
raise ConfigError(f"{label} keys differ (missing={missing}, extra={extra})")
def schema_v1(value: dict, label: str):
schema = object_value(value.get("schema"), f"{label}.schema")
exact_keys(schema, {"version"}, f"{label}.schema")
if schema["version"] != 1:
raise ConfigError(f"{label}.schema.version must be 1")
def compact_json(value) -> str:
return json.dumps(value, separators=(",", ":"), sort_keys=True)