A local-first utility that maps a Git change set to the narrowest repository-native validation commands explicitly declared safe for those changes. It shows the complete plan and rationale before execution, escalates shared/configuration changes, and reports unsupported classifications instead of inventing confidence.
| Tool | Why |
|---|---|
| Python 3.12+ | Implementation language |
| uv | Environment + dependency management |
| argparse | CLI |
| pytest | Temp-repo, descriptor, planner, and subprocess tests |
| Ruff | Lint + format |
| mypy (strict) | Static typing |
- Python 3.12+
- uv
uv sync --extra dev
uv run pytest -q
uv run ruff check .
uv run mypy --strict srcchanged-check plan # show the narrowest declared checks for the current change set
changed-check explain # rationale: exact command, cwd, triggering files, escalation, cost class
changed-check run # execute the resolved plan with bounded subprocessesTarget root defaults to the enclosing git root of cwd (--root <path> override); the descriptor is
auto-discovered as .changed-check.toml at that root (--descriptor <path> override). A missing
descriptor is an explicit error (exit 3), never a heuristic fallback.
| Code | Meaning |
|---|---|
| 0 | all selected checks pass and no unsupported findings |
| 1 | at least one check failed (including timeouts) |
| 2 | checks passed but unsupported findings present |
| 3 | descriptor invalid or usage error |
Precedence when conditions co-occur: 3 > 1 > 2 > 0.
version = 1
[commands.pytest]
argv = ["uv", "run", "pytest", "-q"] # token list; executed with shell=False
cwd = "." # repo-relative; default "."
cost = "standard" # fast | standard | slow (display-only)
timeout_seconds = 600 # optional; default 600 when absent
[path_classes]
python = ["src/**/*.py", "tests/**/*.py"]
docs = ["docs/**", "*.md"]
[mappings]
python = ["pytest"]
docs = [] # [] = explicitly no check for this class
[[escalation]]
when = "pyproject.toml"
run = ["pytest"]-
Explicit mappings beat heuristics. A check runs only because the descriptor maps a path class to a declared command.
-
Unknown is visible. Unmatched files and invalid descriptors produce unsupported findings and a non-success exit — never an empty green plan.
-
Plan before run. Both text and JSON expose exact command, cwd, triggering files, escalation rule, and estimated cost class before execution.
-
Trust boundary. Commands are argv token lists run with
shell=False(no shell interpolation of filenames). The descriptor is trusted repository code — running against an untrusted repo executes that repo's commands.Environment inheritance — read before running against an unfamiliar repo.
changed-check runexecutes the repository's declared commands with your full environment inherited — the samePATH, virtualenv, and every environment variable in your shell, including any secrets (API tokens, cloud credentials). This is deliberate: real checks needPATH/venv/etc., exactly likemakeor annpmscript. It also means only run descriptors you trust, the same rule you already apply tomake/npm. A future opt-in--clean-env/ env-allowlist mode (v1.1 idea) may let you scrub the child environment, but it is off by default because it would break legitimate checks that rely on inherited tooling. -
Machine seam is the JSON format. JSON reports carry
schema_version = 1; downstream consumers bind to the format contract, never to internals.
src/changed_check/
models.py change classes, check declarations, plan, result shapes
descriptor.py strict TOML descriptor loader + validator
changes.py Git diff collection + normalization
planner.py mapping + escalation engine
runner.py bounded subprocess execution
cli.py plan / run / explain
See plans/plan.md for the full build plan and the normative descriptor shape.