Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions .antigravity/skills/code-refinement/SKILL.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .claude/commands/code-refinement.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ Verify that the project's UI framework components and utility classes are used w
Run the project's linting command and fix all reported errors and warnings. Discover the command from package scripts, a Makefile, CI config, or pre-commit config; if the project has no linter, note that and move on. Avoid using lint-suppression comments (e.g., eslint-disable, noqa, @ts-ignore) to make the lint pass unless absolutely necessary, and only with a clear justification in the code.

Review tests and code coverage: check whether existing tests adequately cover the new or modified code, add tests for any gaps you find, and update any existing tests that must change to handle the new behavior correctly. When finished, ensure everything is ready for a high-quality code review.

Do not stage, commit, or push. Leave every change in the working tree: the review loop stages what it needs on its own, and the commit is the developer's call.
2 changes: 2 additions & 0 deletions .codex/skills/code-refinement/SKILL.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .copilot/skills/code-refinement/SKILL.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
# Enforce Unix line endings for all text files in this repo
* text=auto eol=lf

# Derived from .claude/commands/ by tools/generate; never hand-edited.
# linguist-generated is what bin/review-gate reads to tell a generated
# artifact from a source change when it classifies a staged diff.
.codex/skills/** linguist-generated=true
.copilot/skills/** linguist-generated=true
.antigravity/skills/** linguist-generated=true
.kimi-code/skills/** linguist-generated=true
prompts/code-refinement.md linguist-generated=true
prompts/code-review.md linguist-generated=true
21 changes: 21 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,24 @@ jobs:
with:
python-version: "3.x"
- uses: pre-commit/action@v3.0.1

# The scripts here are Bash, but they run under Git Bash on Windows too, and
# bin/review-gate in particular has to cope with the `C:\...` paths and the
# separate PowerShell tool a Windows harness hands it. Only the test suite
# runs here; shellcheck and markdownlint are platform-independent and already
# covered above.
test-windows:
name: BATS on Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Give git an identity for the throwaway repos the tests build
shell: bash
run: |
git config --global user.email ci@example.com
git config --global user.name CI
- name: Run BATS under Git Bash
shell: bash
run: test/run
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ agent-review-summary.md
feedback-plan.md
plan-review-summary.md

# Plan documents written for plan-review-loop. Working artifacts, not
# deliverables. Anchored to the root so the pattern cannot reach the prompts
# of similar name in prompts/, which Windows checkouts match case-insensitively.
/PLAN-*.md

# Impeccable hook installer artifacts (machine-specific absolute paths)
.claude/settings.local.json
.codex/hooks.json
Expand Down
2 changes: 2 additions & 0 deletions .kimi-code/skills/code-refinement/SKILL.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 84 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ Trade-off to know about: a custom `Explore` loads your `CLAUDE.md`/user memory l

## Review Loops

Two multi-agent feedback loops live in [bin/](bin/): `code-review-loop` (for staged code) and `plan-review-loop` (for plan documents). Each loop pairs an **editor** agent with a different **reviewer** agent and iterates until the reviewer is satisfied or `--max-iterations` is hit. Using two different models for editing and reviewing surfaces issues a single agent tends to miss in its own output.
Two multi-agent feedback loops live in [bin/](bin/): `code-review-loop` (for staged code) and `plan-review-loop` (for plan documents), alongside `review-gate`, the hook that keeps an agent from committing before the first of those has run. Each loop pairs an **editor** agent with a different **reviewer** agent and iterates until the reviewer is satisfied or `--max-iterations` is hit. Using two different models for editing and reviewing surfaces issues a single agent tends to miss in its own output.

Both scripts are installed onto your `PATH` by `./setup` and rely on the prompts in [prompts/](prompts/) (installed to `~/.local/share/ai-coding-setup/prompts/`).

Expand Down Expand Up @@ -228,13 +228,90 @@ plan-review-loop --reviewer claude --editor codex PLAN-feature.md

**Outputs (project root):** the plan file is edited in place; `feedback-plan.md` (latest feedback, removed when reviewer is satisfied); `plan-review-summary.md` (narrative).

### review-gate

A hook that stops a coding agent from committing code nobody reviewed.

Every harness has a pre-tool event that can deny a tool call. `review-gate` sits on that event, watches for a `git commit`, and answers one question in well under a second: does this staged change already have a clean review? If it does, or if the commit is not really a code change at all, the agent never sees the gate. If it does not, the gate denies the commit and hands the agent the staged diff, per-category line counts, and a rubric, and the agent either judges the change trivial and says so out loud, or asks you what to do.

The gate never runs `code-review-loop` itself. That takes minutes and is designed to hand back to a human at the end; the gate is pure git plumbing, and the loop runs afterward as an ordinary foreground command if you pick that option.

**What passes without a word:**

- nothing staged, or an `--amend` that only rewords
- a clean review receipt for exactly this index on exactly this base, which is what `code-review-loop` records when it finishes
- a history rewrite: rebase, interactive rebase, cherry-pick, revert, or merge, including every `--continue` step. A twelve-commit rebase must not stop to ask twelve times
- an index tree identical to `ORIG_HEAD` or `HEAD@{1}`, which catches a rewrite whose in-progress markers are already cleaned up
- an unresolved merge conflict in the index, which means a merge is in progress anyway
- `AI_REVIEW_GATE=off` on the command, or `REVIEW_GATE=off` in the config

**What always gets stopped:** `git commit -a`, `git commit <path>`, and the `-o` / `--only` / `-i` / `--include` forms. They commit content that was not in the index when the gate ran, so a matching receipt describes something else. That check runs before every index-derived rule, or `git commit -a` with a clean index would sail through the empty-index check and land unreviewed work.

**Modes**, set with `REVIEW_GATE` in `~/.ai-coding-setup.conf` or in the environment (the environment wins):

| Mode | Behavior |
| --- | --- |
| `warn` | Default. Prints the reason and lets the commit through. |
| `block` | Denies the commit and hands the agent the reason. |
| `off` | Disabled. |

It ships in `warn` because the rubric is untested against your commits and a wrong `block` is far more annoying than a wrong `warn`. Once a few weeks of warn output shows it is not crying wolf, switch to `block`.

**Escape hatches**, in the order you are likely to want them:

- `AI_REVIEW_GATE=off git commit -m "..."` bypasses one commit. In PowerShell, where that prefix form is a parse error, write it as `$env:AI_REVIEW_GATE = "off"; git commit -m "..."`. The gate reads the bypass off the command string rather than its own environment, so it has to sit on the same command as the commit either way.
- The gate issues a single-use nonce with every block, scoped to the index as it stands. The agent uses it to act on its own trivial-change judgment: `AI_REVIEW_GATE=<nonce> git commit -m "..."`. Staging more work invalidates it, and it works once.
- `REVIEW_GATE=off` in `~/.ai-coding-setup.conf` turns the gate off everywhere.

**Headless runs degrade to warn.** In CI, or under `AI_REVIEW_HEADLESS=1`, there is nobody to ask, and a gate that hard-blocks there deadlocks the build. Detection is explicit and never a TTY check: every harness spawns hooks with pipes on all three descriptors, so keying off `[ -t 0 ]` would degrade every interactive run too, and quietly turn the gate off everywhere while still looking installed.

**Installation.** `./setup` offers to wire it into Claude Code, appending a `PreToolUse` hook to `~/.claude/settings.json` without disturbing hooks that are already there. The script speaks every harness's output shape via `--format`, but only Claude is wired automatically, because the other four take different config shapes and paths that are worth confirming against their current docs before writing into your config:

```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell",
"hooks": [
{ "type": "command", "command": "\"$HOME/.local/bin/review-gate\" --format=claude" }
]
}
]
}
}
```

Available formats: `claude` and `codex` emit the decision nested under `hookSpecificOutput`; `copilot` and `antigravity` emit a flat `{"permissionDecision": ..., "permissionDecisionReason": ...}`; `kimi` is exit-code driven, blocking with exit 2 and the reason on stderr. The allow path is always silence and exit 0, never an affirmative `"allow"`: an affirmative allow from a `PreToolUse` hook would skip your own permission rules and auto-approve every shell command the agent runs.

**Windows.** The gate is a Bash script, so it needs Git Bash, like the rest of this repo. Everything it calls is bundled with Git for Windows except `jq`, which you install separately; without `jq` the gate allows every commit rather than failing, so check that it is on `PATH` before trusting the gate there. Claude Code runs hook commands through Git Bash on Windows by default, falling back to PowerShell only when Git Bash is absent, so the Git Bash style path `./setup` writes into `~/.claude/settings.json` (`/c/Users/you/.local/bin/review-gate`) resolves as written.

Three Windows specifics are worth knowing:

- **The matcher has to name both shell tools.** Windows exposes a `PowerShell` tool alongside `Bash`, and a matcher of `Bash` alone lets every commit made through the other one straight past the gate. `./setup` writes `Bash|PowerShell`, and widens an existing `Bash`-only entry in place when you re-run it.
- **The bypass takes PowerShell syntax there.** A bash `AI_REVIEW_GATE=off git commit ...` prefix is a parse error in PowerShell, so the gate reads the statement form too, and writes whichever one matches the tool the commit is coming from into its own deny message: `$env:AI_REVIEW_GATE = "off"; git commit ...`. Either way it has to ride on the same command as the commit. The gate is a separate process spawned before your command runs, so it never inherits a variable you set in an earlier call; it can only read what is on the command string in front of it.
- **It costs about 150ms per shell call.** The hook fires on every command the agent runs, not just commits, and process startup under Git Bash is far slower than on macOS or Linux. The non-commit fast path exits before any git call or subshell, but bash itself still has to start. Measured here: roughly 160ms warm against 55ms for a bare `bash -c true`, and over a second on a cold file cache.

`./setup` falls back to copying when `ln -s` cannot make a real symlink, which is the default on Windows unless Developer Mode is on. That works, but `~/.local/bin/review-gate` is then a snapshot rather than a link, so re-run `./setup` to pick up changes to the script.

**Known blind spot:** a commit made inside a script the agent invokes is invisible, because the gate only ever sees the command the agent typed. Nothing short of a git-level hook closes that, and a git-level hook cannot ask a question, so it would only ever warn after the fact.

**State** lives in `$(git rev-parse --git-dir)/ai-review/`, so it is never committed, is per-worktree, and survives branch switches:

| File | Contents |
| --- | --- |
| `receipts.json` | The last 10 review results, newest first: index tree, HEAD, verdict, cycles, agents, timestamp. |
| `nonce` | The outstanding single-use bypass and the index tree it was issued for. |
| `running` | The active `code-review-loop`'s PID and start time. The gate exempts commits while the loop runs, and prunes the file when the PID is dead or the timestamp is too old to trust. |

### Configuration

Defaults are `--editor claude --reviewer codex`. Override per-run with `-e` / `-r`, or persist defaults in `~/.ai-coding-setup.conf`:

```ini
EDITOR_AGENT=claude
REVIEWER_AGENT=codex
REVIEW_GATE=warn
```

Supported agents: `claude`, `codex`, `copilot`, `antigravity`, `kimi`. Only the agents you actually have installed need to be referenced.
Expand All @@ -250,6 +327,10 @@ Environment variables:
| `CODE_REVIEW_LOOP_LOG_DIR` | `~/.cache/code-review-loop/<timestamp>` | Where `code-review-loop` writes its run logs. Setting it also turns off log pruning, on the grounds that a directory you named is yours to manage. |
| `REVIEW_LOOP_LOG_DAYS` | `1` | Delete run logs older than this many days. Only applies to the default location. |
| `AI_CODING_SETUP_PROMPTS_DIR` | `~/.local/share/ai-coding-setup/prompts` | Where the loops read their prompts from. |
| `REVIEW_GATE` | `warn` | `review-gate` mode: `off`, `warn`, or `block`. Overrides the config file. |
| `AI_REVIEW_GATE` | unset | `off` bypasses the gate for one invocation. Also carries the single-use nonce the gate issues. Read off the command string, as a `VAR=value` prefix or a PowerShell `$env:AI_REVIEW_GATE = "..."` statement, so it must sit on the same command as the commit. |
| `AI_REVIEW_HEADLESS` | unset | `1` degrades the gate to warn-only. `code-review-loop` sets it for its own run. |
| `REVIEW_GATE_LOCK_MAX_AGE` | `21600` | Seconds before the gate stops trusting a `code-review-loop` lock file and prunes it. |

### Shared prompts

Expand Down Expand Up @@ -397,6 +478,8 @@ Delete the command/skill from the corresponding directory (or uninstall the plug
- Antigravity: Run `agy plugin uninstall ai-coding-setup`
- Kimi Code: `~/.kimi-code/skills/` (or `$KIMI_CODE_HOME/skills/`)

If you installed the review gate hook, remove its `PreToolUse` entry from `~/.claude/settings.json` and delete `~/.local/bin/review-gate`. Per-repository state under `.git/ai-review/` can go too; nothing else reads it.

The setup script only manages commands it originally installed. The upstream extras are removed separately: `gh extension remove gh-stack` for the `gh stack` extension, and for the `gh`/`gh-stack` agent skills, delete their directories as described in [`gh` Agent Skills](#gh-agent-skills).

## Contributing
Expand Down
24 changes: 23 additions & 1 deletion bin/code-review-loop
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,22 @@ fi

# ---- temp directory with cleanup -----------------------------------------
setup_temp "code-review-loop"
trap cleanup_temp EXIT

cleanup_run() {
cleanup_temp
review_gate_lock_release
}
trap cleanup_run EXIT

# Tell bin/review-gate this loop is running, so an agent it spawns that decides
# to commit its own fixes cannot trip the gate from inside the loop. This
# survives an agent that sanitizes its environment, which the AI_REVIEW_GATE
# export in run_agent does not.
review_gate_lock_acquire

# Agents spawned from here have no user to ask, so the gate degrades to
# warn-only for the whole run rather than deadlocking on a question.
export AI_REVIEW_HEADLESS=1

# ---- run logs -------------------------------------------------------------
# Outside TMPDIR_REVIEW, which the EXIT trap wipes, and outside the project,
Expand Down Expand Up @@ -414,6 +429,13 @@ else
final_status="MAX ITERATIONS REACHED - some issues remain"
fi

# ---- review receipt ------------------------------------------------------
# Recorded before the summary step, which only writes agent-review-summary.md
# and so leaves the index exactly as the developer will commit it. This is what
# lets bin/review-gate answer "has this been reviewed" for the retried commit.
if $is_clean; then receipt_verdict="clean"; else receipt_verdict="needs-review"; fi
write_review_receipt "$receipt_verdict" "$iteration" "$EDITOR_AGENT" "$REVIEWER_AGENT"

write_step "Final" "$EDITOR_AGENT: Writing improvement summary"
# Clear any summary left by a previous run so the post-run existence check
# below reflects this run, not a stale artifact
Expand Down
Loading
Loading