Pre-commit System

The pre-commit system enforces three core principles through three defense layers, each operating at a different level of the stack:

  1. Performance is king – no regressions in GPU-first execution

  2. Zero-copy by default – data stays on device until explicit materialization

  3. Agent-first discoverability – all code is routable through the intake system

Quick Start

uv run python scripts/install_githooks.py

That’s it. The git hook runs deterministic checks on every commit and prints a reminder about the AI review commands. The skill and hook layers are configured automatically via .claude/settings.json and .claude/skills/.

Layer 1: Deterministic Checks (git pre-commit hook)

Runs on every commit for all contributors. No network, no AI, no GPU required. Total runtime: ~3-5 seconds. Enforced by .githooks/pre-commit.

Order

Check

Script

Rules

1

Ruff lint

ruff check

E, F, W, I, UP, B, PERF, RUF

2

Doc refresh

check_docs.py --refresh

Auto-refresh generated headers

3

Doc validate

check_docs.py --check

Header budgets, routing sections

4

Architecture

check_architecture_lints.py --all

ARCH001-006

5

Zero-copy

check_zero_copy.py --all

ZCOPY001-003

6

Performance

check_perf_patterns.py --all

VPAT001-004

7

Maintainability

check_maintainability.py --all

MAINT001-003

After checks pass, a non-blocking reminder prints the AI review commands. This works in terminals, VS Code git output, and CI alike.

Zero-Copy Rules (ZCOPY)

Code

Rule

Example

ZCOPY001

No ping-pong D/H transfers in same function

.get() followed by cp.asarray()

ZCOPY002

No per-element D/H transfers in loops

.get() inside a for body

ZCOPY003

Functions using device APIs must not return host data

Public function calls .get() in return

Performance Rules (VPAT)

Code

Rule

Example

VPAT001

No Python for-loops over geometry objects

for g in series.geoms

VPAT002

No Shapely imports in kernel modules

import shapely in kernels/

VPAT003

No np.fromiter in runtime code

np.fromiter(gen, dtype=float)

VPAT004

No .astype(object) on arrays

coords.astype(object)

Maintainability Rules (MAINT)

Code

Rule

Example

MAINT001

New modules in src/vibespatial/ must be in intake

Missing from intake-index.json

MAINT002

New ADRs must be indexed

Missing from docs/decisions/index.md

MAINT003

New scripts must be in AGENTS.md

Missing from project shape section

Layer 2: Pre-Land Review Skill (Claude Code, proactive)

The pre-land-review skill (.claude/skills/pre-land-review/SKILL.md) fires proactively when Claude detects intent to commit or land work. Unlike AGENTS.md instructions which can be compressed away in long conversations, skills are matched against current context on every turn by their description field. The skill fires on keywords like “commit”, “land”, “done”, “ship it”, “wrap up”, and “let’s finish”.

When triggered, the skill loads the full pre-land checklist fresh into context, regardless of how long the conversation has been running.

Workflow

  1. Make your changes in a Claude Code session

  2. When you’re ready to land, say “commit” or “let’s land this”

  3. The skill fires automatically and runs the full checklist

  4. If verdict is LAND, the skill writes .claude/.review-completed marker

  5. git commit – deterministic checks run, commit-msg hook verifies marker

  6. Commit completes

Available commands

You can also invoke the review commands explicitly:

Command

Purpose

/pre-land-review

Skill: full checklist, orchestrates all enforcers

/gpu-code-review

Skill: 6-pass GPU kernel review (auto-invoked by pre-land-review when GPU code touched)

/performance-analysis

Command: GPU utilization, tier compliance, regression risk

/zero-copy-enforcer

Command: device residency, transfer path tracing

/maintainability-enforcer

Command: intake routing, doc coherence

Suppressing the pre-commit reminder

The reminder is non-blocking. To suppress it:

VIBESPATIAL_SKIP_AI_REMINDER=1 git commit -m "message"

Layer 3: PreToolUse Hooks (Claude Code, mechanical)

Two PreToolUse hooks run outside the LLM context window – they cannot be compressed away, forgotten, or skipped by the model.

Bash guard (.claude/hooks/pre-land-gate.sh)

Fires on every Bash tool call. Returns a hard block ({"decision":"block"}) for commands that would bypass or tamper with enforcement infrastructure:

Pattern

What it blocks

git commit --no-verify / -n

Skipping all git hooks

git config core.hooksPath

Redirecting hooks away from .githooks/

git -c core.hooksPath=...

Inline hook path override

rm/mv/chmod/sed/… on .githooks/

Tampering with git hooks

rm/mv/chmod/sed/… on .claude/hooks/

Tampering with Claude hooks

rm/mv/> on .claude/settings*.json

Removing hook registrations

For git commit commands that pass the above checks, the hook injects a system message reminding Claude to complete /pre-land-review first.

File guard (.claude/hooks/file-guard.sh)

Fires on every Edit and Write tool call. Returns a hard block for writes to enforcement-critical paths:

Protected path

Reason

.githooks/*

Git hook scripts

.claude/hooks/*

Claude hook scripts

.claude/settings.json

Hook registrations

.claude/settings.local.json

Local hook overrides

.claude/skills/pre-land-review/SKILL.md

Review skill definition

To modify any of these files, edit them manually outside Claude Code.

Configuration

Both hooks are registered in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {"matcher": "Bash",  "hooks": [{"type": "command", "command": ".claude/hooks/pre-land-gate.sh"}]},
      {"matcher": "Edit",  "hooks": [{"type": "command", "command": ".claude/hooks/file-guard.sh"}]},
      {"matcher": "Write", "hooks": [{"type": "command", "command": ".claude/hooks/file-guard.sh"}]}
    ]
  }
}

## Layer 4: commit-msg Gate (Content-Addressable Marker)

The `commit-msg` hook (`.githooks/commit-msg`) is the **hard gate** for
Claude-assisted commits. It checks the commit message for a
`Co-Authored-By: ... Claude` line:

- **Present**: the review marker `.claude/.review-completed` must exist,
  be less than 1 hour old, contain a `staged_hash` that matches the current
  `git diff --cached | sha256sum`, and have a `verdict` of `LAND`.
  If any check fails, the commit is blocked.
- **Absent**: human-only commit, passes unconditionally.

The marker is a JSON file written by the `/pre-land-review` skill after a
LAND verdict. It binds the review to the exact staged diff via SHA-256 hash,
so a stub file (`touch .claude/.review-completed`) or a marker from a
different staging state will not pass. If you stage additional changes after
the review, the hash breaks and you must re-run `/pre-land-review`.

## How The Layers Interact

The four layers form a defense-in-depth chain:

Layer 2: Skill fires on “commit” / “land” / “done” intent –> Loads full checklist, Claude runs AI analysis –> Writes .claude/.review-completed marker on LAND verdict –> Layer 3: Bash guard hard-blocks –no-verify, hook tampering –> Layer 3: File guard hard-blocks Edit/Write to enforcement files –> Layer 3: Bash guard injects commit reminder if checks above pass –> Layer 1: Pre-commit hook runs deterministic checks –> Layer 4: commit-msg hook checks Co-Author + marker –> Blocks if Claude co-authored without review


Each layer catches what the previous one might miss:

| Failure mode | Caught by |
|--------------|-----------|
| Agent forgets to review before committing | Layer 2 (skill) |
| Long context compresses skill trigger away | Layer 3 (bash guard reminder) |
| Agent uses `--no-verify` to skip git hooks | Layer 3 (bash guard hard block) |
| Agent redirects `core.hooksPath` | Layer 3 (bash guard hard block) |
| Agent `rm`/`sed`/`chmod` on hook files via Bash | Layer 3 (bash guard hard block) |
| Agent edits hook files via Edit/Write tools | Layer 3 (file guard hard block) |
| Agent ignores skill and hook, commits anyway | Layer 4 (commit-msg gate) |
| Agent creates stub marker file (`touch ...`) | Layer 4 (missing JSON / hash) |
| Agent stages new changes after review | Layer 4 (hash mismatch) |
| Claude Code not available (human contributor) | Layer 1 (pre-commit, no gate) |
| Non-interactive environment (CI, VS Code) | Layer 1 (pre-commit, no gate) |

## Ratchet Baseline System

The ZCOPY, VPAT, and MAINT checks use a **ratchet** to handle pre-existing
violations without blocking new work:

- Each script has a `_VIOLATION_BASELINE` constant (the known debt count).
- The check **passes** if `current_count <= baseline`.
- The check **fails** if `current_count > baseline` (new violations introduced).
- When debt is paid down, the script prints a reminder to tighten the baseline.

This means:

- **New code** must comply from day one.
- **Existing debt** doesn't block commits but can only shrink, never grow.
- **Paying down debt** is encouraged with a visible nudge.

### Updating baselines

When a script says "Debt reduced! Update `_VIOLATION_BASELINE` to N":

```bash
# Find and update the constant in the script
grep -n _VIOLATION_BASELINE scripts/check_zero_copy.py
# Edit the number, commit the change

Do not increase baselines without documenting why (new code should comply, not get an exemption).

Adding New Checks

To add a new lint rule to an existing enforcer:

  1. Add a check_* function in the appropriate scripts/check_*.py

  2. Add it to the run_checks() function

  3. Run the script – if new violations appear, decide:

    • Fix them (preferred)

    • Increase the baseline with a comment explaining why

  4. Update the rule table in this doc

  5. Run uv run python scripts/check_docs.py --refresh to update headers

To add an entirely new enforcer:

  1. Create scripts/check_<name>.py following the existing pattern

  2. Add it to .githooks/pre-commit in the Layer 1 block

  3. Add it to AGENTS.md project shape and verification sections

  4. Add a corresponding command in .claude/commands/ for AI-powered analysis

  5. Document it in this file

Troubleshooting

Pre-commit hook not running

# Check that hooks are installed
git config --local core.hooksPath
# Should print: .githooks

# Reinstall if needed
uv run python scripts/install_githooks.py

Deterministic check fails on pre-existing code

The ratchet baseline should prevent this. If it happens:

  1. Run the failing script standalone to see all violations

  2. Check if _VIOLATION_BASELINE was accidentally lowered

  3. If a dependency update introduced new violations, raise the baseline with a comment explaining the cause

Skill not firing in Claude Code

The skill triggers on keywords in the conversation. If it doesn’t fire:

  1. Say /pre-land-review explicitly to invoke it

  2. Check that .claude/skills/pre-land-review/SKILL.md exists

  3. Verify the skill appears in Claude Code’s skill list

Hook not injecting reminder

  1. Check that .claude/settings.json has the PreToolUse hook configured

  2. Verify .claude/hooks/pre-land-gate.sh is executable (chmod +x)

  3. Test manually: echo '{"command":"git commit -m test"}' | .claude/hooks/pre-land-gate.sh

Skipping hooks entirely

The --no-verify flag is hard-blocked for AI agents by the Bash guard hook. It cannot be used from within Claude Code sessions.

For human contributors working outside Claude Code, --no-verify still works in a plain terminal. Use sparingly – it bypasses ALL checks including deterministic ones.