Files
John SmithandClaude Code a138b13668 feat: agentic-gates — mechanical quality-gate marketplace for Claude Code
Multi-plugin marketplace of forced mechanical gates (the Turnstile): agents
cannot end a turn with mechanically failing code. Verdicts are deterministic
CLI results — never LLM-judged.

- core/gate: stdlib-only dispatcher (Python 3.11+) with SARIF-lite finding
  protocol (content-based fingerprints, line-drift proof), policy loader with
  includes/tiers, tool-chain resolver with fallback order + explicit DEGRADED
  verdicts, verdict cache, ratchet baseline, expiring waivers, guard-the-guard,
  loop-guard, turn ledger, doctor, report
- 14 tool adapters: py_ast, ruff_format/check, secret_scan, node_check, tsc,
  oxlint, biome_*, prettier, ty, pyright, mypy, gitleaks (pure parsers, unit-
  testable without the tools installed)
- packs: gates-essential (engine + all enforcement hooks + commands),
  gates-polyglot/security/strict (declarative content packs, registration-only
  hooks, dependencies on the engine — multi-fire structurally prevented)
- Quality Ladder wiring: L0 PostToolUse (format/syntax/secrets, unchanged-hash
  fast path), L1 PostToolBatch (lint+types), L2 Stop turnstile with loop-guard,
  L3 gate check for CI parity
- 56 tests: unit + golden-stdin hook contracts + packaging invariants +
  registration idempotence; manifest validator; seed fixtures
- Spike results: H4 ratchet PASSED (0 false blocks / 10-10 caught); H2 partial
  falsification recorded (unchanged-hash <15ms unreachable at ~51ms p95 with
  per-edit CPython spawn — escalation path documented)
- Design history: .research/brainstorm_20260926T1835.md (dialectic + tool matrix)

Co-Authored-By: Claude Code <[email protected]>
2026-09-26 19:35:51 -04:00

111 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""Launcher shim: `python3 <plugin>/core/gate.py <level>` (hooks.json exec form).
Two responsibilities:
1. Bootstrap `gate` package importability from its own directory (repo or vendored).
2. L0 unchanged-hash fast-path: when the edited file's (mtime,size) signature is
unchanged since the last clean L0 pass, exit 0 WITHOUT importing the engine.
Interpreter startup is the latency floor, so the engine is only loaded on a miss.
"""
from __future__ import annotations
import json
import os
import sys
from pathlib import Path
_HERE = Path(__file__).resolve().parent
if str(_HERE) not in sys.path:
sys.path.insert(0, str(_HERE))
def _read_payload() -> tuple[str, dict]:
raw = sys.stdin.read()
if not raw.strip():
return raw, {}
try:
data = json.loads(raw)
except json.JSONDecodeError:
return raw, {}
return raw, data if isinstance(data, dict) else {}
def _file_target(payload: dict) -> Path | None:
tool_input = payload.get("tool_input") or {}
raw_path = tool_input.get("file_path") or tool_input.get("notebook_path")
if not isinstance(raw_path, str) or not raw_path:
return None
target = Path(raw_path)
return target if target.is_absolute() else (Path.cwd() / target)
def _signature(path: Path) -> str | None:
try:
st = path.stat()
except OSError:
return None
return f"{st.st_mtime_ns}:{st.st_size}"
def _store_path() -> Path:
return Path.cwd() / ".gate" / "cache" / "l0-seen.json"
def _load_store() -> dict:
try:
return json.loads(_store_path().read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return {}
def _save_store(store: dict) -> None:
try:
path = _store_path()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(store), encoding="utf-8")
except OSError:
pass
def main() -> int:
raw, payload = _read_payload()
argv = sys.argv[1:]
is_l0 = bool(argv) and argv[0] == "l0"
target: Path | None = None
sig: str | None = None
if is_l0 and payload:
target = _file_target(payload)
if target is not None:
sig = _signature(target)
key = str(target)
store = _load_store()
if sig is not None and store.get(key) == sig:
return 0 # unchanged since last clean L0 pass
elif is_l0:
return 0 # nothing to do
# Engine path: hand over the payload we already consumed.
if raw.strip():
os.environ["GATE_HOOK_INPUT"] = raw
from gate.cli import main as engine_main
code = engine_main()
if code == 0 and is_l0 and target is not None and sig is not None:
# Re-stat: format may have rewritten the file during the run.
fresh = _signature(target)
if fresh is not None:
store = _load_store()
store[str(target)] = fresh
_save_store(store)
return code
if __name__ == "__main__":
try:
raise SystemExit(main())
except SystemExit as exc:
raise SystemExit(int(exc.code or 0))