Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 166 additions & 108 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,174 +1,232 @@
# ai-git-hooks / push-review
# ai-pushgate

A language-agnostic `pre-push` hook that runs your linters and tests against changed files, then asks Claude to review the diff before every push.
`ai-pushgate` is a local pre-push gate plus CI/PR enforcement workflow.

## How it works
## Product Contract

```
```text
git push
┌─────────────────────────────────────┐
│ Changed files vs target branch
│ (ignore_paths filtering applied) │
│ Changed files vs project.base_ref
│ (include/exclude policy applied) │
└──────────────┬──────────────────────┘
┌─────────────────────────────────────┐
│ Run configured tools
│ Run deterministic checks
│ (linters, type checkers, tests) │
│ ✗ any failure → push blocked
│ ✗ blocking failure → push blocked │
└──────────────┬──────────────────────┘
all pass
checks pass
┌─────────────────────────────────────┐
│ AI review via Claude Code CLI
│ AI review via configured provider
│ (diff sent, findings returned) │
│ BLOCK → push blocked │
│ PASS → push proceeds │
└─────────────────────────────────────┘
```

## Install
`ai-pushgate` separates fast local feedback from authoritative enforcement:

```bash
# Default (base template — no tools pre-configured, fully documented)
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash
| Surface | Purpose | Blocking authority |
|---|---|---|
| Local pre-push | Fast deterministic checks before code leaves a developer machine | Convenience only; always bypassable |
| Local AI | Review changed files near the developer workflow | Blocks by default; always bypassable |
| CI/PR | Repeatable checks, review summaries, and policy enforcement | Source of truth for teams |

Local hooks help developers catch issues early, but they are not a security or
compliance boundary. Anything that must be enforced for a team belongs in CI/PR
checks combined with repository branch protection.

# Node.js
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash -s -- --template node
## Defaults

# TypeScript
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash -s -- --template typescript
- Config file: `.pushgate.yml`.
- Deterministic local checks are the default local gate.
- Local AI defaults to `blocking` when configured and available.
- Blocking AI findings stop the local push until they are fixed or skipped.
- AI payloads default to diff-only context.
- Full-file AI context is opt-in.
- Secret redaction is expected before any source content is sent to an AI
provider.

# Next.js
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash -s -- --template nextjs
## Install

Run the installer from the Git repository where pushgate should guard pushes:

# Ruby
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash -s -- --template ruby
```bash
# Default base template
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-pushgate/main/install.sh | bash

# Ruby on Rails
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash -s -- --template rails
# Stack-specific template
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-pushgate/main/install.sh \
| bash -s -- --template <name>
```

The installer:
Available templates are `base`, `node`, `typescript`, `nextjs`, `ruby`, and
`rails`.

1. Downloads and validates `hook/pre-push` → `.git/hooks/pre-push`
2. Backs up any existing `pre-push` hook before overwriting
3. Downloads the template config → `.push-review.yml` (only on first install — never overwrites)
4. Checks for Claude Code CLI and warns about missing runtimes
The installer:

## Requirements
1. Installs or verifies the `pushgate` runner.
2. Installs `.git/hooks/pre-push` as a thin delegator to `pushgate pre-push`.
3. Backs up an existing `pre-push` hook before replacing it.
4. Writes the selected template to `.pushgate.yml` when that config does not
already exist.
5. Checks configured tool and AI provider dependencies.

**Claude Code CLI** (required for AI review):
If `pushgate` is already installed, the CLI can set up a repository directly:

```bash
npm install -g @anthropic-ai/claude-code
claude /login
pushgate install --template <name>
```

**Runtime dependencies** depend on the tools you configure:

| Runtime | Required by |
|---------|-------------|
| Node.js | `node`, `typescript`, `nextjs` templates |
| Ruby | `ruby`, `rails` templates |
| Python | Python tools (manual config) |
| Go | Go tools (manual config) |

The installer checks which runtimes your config requires and warns about any that are missing. If Claude Code CLI is not installed, the hook still runs tool checks — it only skips the AI review step.

## Configuration

After install, edit `.push-review.yml` in your project root:
The config contract is:

```yaml
agent:
# Claude model used for AI review. Requires Claude Code CLI (claude /login).
model: claude-sonnet-4-20250514

review:
target_branch: main # diff base: git diff <target_branch>...HEAD
context_lines: 10 # surrounding context lines included in the diff
max_lines_for_full_file: 300 # below this threshold, full file contents are sent
# instead of just the diff for richer context

# Topics the AI reviewer focuses on
focus:
- security
- logic_errors
- test_coverage
- performance
- naming_and_readability

# Findings in these categories block the push
blocking_categories:
- security
- logic_errors

# Findings in these categories are printed as warnings but never block
warning_categories:
- test_coverage
- performance
- naming_and_readability

# Tools to run before AI review — first failure blocks the push immediately
tools:
version: 2

project:
base_ref: main
include_paths:
- "**/*"
exclude_paths:
- "*.lock"
- "dist/**"
- "coverage/**"

local:
fail_fast: true
budget_seconds: 60

checks:
- name: eslint
command: npx eslint {changed_files} # {changed_files} is replaced at runtime
command: ["npx", "eslint", "{changed_files}"]
mode: blocking
run: changed_files
extensions: [".js", ".jsx", ".ts", ".tsx"]
timeout_seconds: 30

- name: tests
command: ["npm", "test"]
mode: warning
run: always
timeout_seconds: 60

ai:
mode: blocking # off | advisory | blocking
provider:
name: claude
privacy:
send_diff: true
send_full_files: false
redact_secrets: true

ci:
mirror_blocking_checks: true
```

### Config Fields

`project.base_ref` defines the comparison base used to collect changed files.
`project.include_paths` and `project.exclude_paths` define the shared path
policy for deterministic checks and optional AI.

`checks[]` defines deterministic commands. Commands are argv arrays, not shell
strings, so changed files can be passed safely as discrete arguments. A check can
run on changed files or the whole project, and can be `blocking` or `warning`.

`ai.mode` controls local AI behavior:

| Mode | Behavior |
|---|---|
| `off` | Do not run local AI |
| `advisory` | Run local AI when available, print findings, never block |
| `blocking` | Run local AI and block `BLOCK` findings until fixed or skipped |

- name: brakeman
command: bundle exec brakeman --no-pager --quiet
# no {changed_files} → runs on the whole project
`ci` is reserved for generated or documented CI mirror behavior. CI/PR policy is
where teams should enforce required checks.

# Files and patterns excluded from tool checks and AI review
ignore_paths:
- "*.lock"
- "dist/**"
- "coverage/**"
## Git Workflow Integration

The default developer workflow should remain regular Git:

```bash
git push
```

## Available templates
The installed `.git/hooks/pre-push` hook is a thin delegator that invokes the
versioned runner:

| `--template` | Stack | Tools pre-configured |
|---|---|---|
| `base` | Any | None (fully-documented reference config) |
| `node` | Node.js | ESLint, Prettier, Jest |
| `typescript` | TypeScript | tsc, ESLint, Prettier, Jest |
| `nextjs` | Next.js | tsc, next lint, Prettier, Jest |
| `ruby` | Ruby | RuboCop, Reek, RSpec |
| `rails` | Ruby on Rails | RuboCop, Reek, Brakeman, RSpec |
```bash
pushgate pre-push
```

The hook is responsible for passing along Git hook input and exiting with the
runner's exit code. The runner owns config loading, changed-file detection,
deterministic checks, optional local AI, and user-facing output.

The `pushgate push` wrapper is an ergonomic layer for flags Git cannot pass to
hooks, not a replacement for the normal Git workflow. It should translate
pushgate-specific flags into temporary Git config and then call `git push`.

## Skip checks
## Skip Controls

To bypass the hook for a single push:
Raw `git push` cannot pass arbitrary `--skip-*` flags to a pre-push hook; Git
rejects unknown `git push` flags before the hook runs. For a one-off skip in the
regular Git workflow, pass temporary pushgate config with Git:

```bash
git push --no-verify
git -c pushgate.skip-ai-check=true push
git -c pushgate.skip-all-checks=true push
```

## Updating
`pushgate.skip-ai-check` skips only local AI while preserving deterministic
checks. `pushgate.skip-all-checks` skips all local pushgate behavior for that
push.

Re-run the installer to update the hook script. Your `.push-review.yml` is **never overwritten** — it stays exactly as you've configured it.
The `pushgate push` wrapper provides shorter equivalents:

```bash
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash
pushgate push --skip-ai-check
pushgate push --skip-all-checks
```

To also reset your config to a template, delete it first:
The wrapper should apply the matching temporary Git config before it calls
`git push`.

Git also keeps its native escape hatch:

```bash
rm .push-review.yml
curl -fsSL https://raw.githubusercontent.com/rootstrap/ai-git-hooks/main/install.sh | bash -s -- --template <name>
git push --no-verify
```

`--no-verify` skips Git hooks entirely, including pushgate.

## Privacy Contract

The default AI payload is changed-file metadata and diff context only. Full-file
context must be enabled explicitly because it can increase latency, cost, and
privacy exposure.

Before any AI call, pushgate should apply the configured path policy and secret
redaction. If redaction cannot run, local AI should fail closed for privacy by
skipping AI feedback instead of sending unredacted content.

## Contributing

To add a new template:
All changes should go through a pull request. Release files are managed by
release-please and should not be edited manually.

1. Add `templates/<name>.yml` following the structure of an existing template (e.g. `ruby.yml`)
2. Add a row to the **Available templates** table in this README
3. Open a pull request
Before opening a pull request, verify that shell scripts still parse and that
template YAML remains valid:

Templates should include sensible `ignore_paths` defaults and pre-configured `tools` for the common tools in that stack. The `base.yml` template is the reference for all available config options.
```bash
bash -n hook/pre-push
bash -n install.sh
for f in templates/*.yml; do python3 -c "import yaml; yaml.safe_load(open('$f'))"; done
```
Loading