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
36 changes: 36 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# CodeRabbit configuration — https://docs.coderabbit.ai/guides/configure-coderabbit
# CodeRabbit is the AI reviewer for vouch (free for this public repo). it reviews
# every non-draft PR automatically. it is advisory: it comments, it does not gate
# merges. the merge gate stays deterministic — ci + trust-gate + CODEOWNERS, with
# the owner's auto-merge label as the go signal.
language: "en-US"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: false
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: false
base_branches:
- "test"
- "main"
path_instructions:
- path: "src/vouch/**"
instructions: >-
Follow CLAUDE.md and AGENTS.md. Use lowercase prose in comments and
review notes. Vouch's load-bearing invariant is the review gate: every
write goes through proposals.approve(). Flag any change that adds a
parallel data path bypassing the review gate. storage.py is pure I/O —
flag business logic added there.
- path: ".github/workflows/**"
instructions: >-
These are core, security-sensitive workflows. Flag any pull_request_target
or workflow_run that checks out or runs untrusted head code, any unpinned
third-party action, and any untrusted event field interpolated into a run
block instead of passed through env.
chat:
auto_reply: true
29 changes: 16 additions & 13 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: auto-merge
on:
pull_request_target: # zizmor: ignore[dangerous-triggers] no untrusted code runs here; verification is delegated to pr-verify.yml behind an environment approval gate
pull_request_target: # zizmor: ignore[dangerous-triggers] no untrusted code runs here; only metadata is read and native auto-merge is armed. review is done by CodeRabbit + ci.
types: [labeled, synchronize]
permissions: {}
# a newer event for the same PR supersedes an in-flight run.
Expand All @@ -10,7 +10,7 @@ concurrency:
jobs:
# any push VOIDS prior authorization: disable auto-merge and drop the label.
# never checks out or runs head code. re-authorize (label or /auto-merge) to
# re-verify the new head. closes the label-then-swap TOCTOU.
# re-arm on the new head. closes the label-then-swap TOCTOU.
deauthorize-on-push:
if: github.event.action == 'synchronize'
runs-on: ubuntu-latest
Expand All @@ -29,11 +29,11 @@ jobs:
if [ "$had_label" = "true" ]; then
gh pr edit "$PR" --repo "$REPO" --remove-label auto-merge || true
gh pr comment "$PR" --repo "$REPO" --body \
"new commits were pushed after authorization, so the prior verification no longer matches the head. auto-merge is disarmed and the label removed — re-authorize (add the auto-merge label or comment /auto-merge) to re-verify the new commits."
"new commits were pushed after authorization. auto-merge is disarmed and the label removed — re-add the auto-merge label (or comment /auto-merge) to re-arm on the new head."
fi

guard:
# only a FRESH label starts verification, and only from the trusted owner.
# only a FRESH label arms auto-merge, and only from the trusted owner.
if: github.event.action == 'labeled' && github.event.label.name == 'auto-merge'
runs-on: ubuntu-latest
permissions:
Expand Down Expand Up @@ -72,16 +72,19 @@ jobs:
klass=$(PYTHONPATH=src python -m vouch.pr_bot classify --files-file changed.txt --print-klass)
echo "klass=$klass" >> "$GITHUB_OUTPUT"

call-verify:
arm:
needs: guard
# core PRs are never armed — CODEOWNERS requires the owner's approval.
if: needs.guard.outputs.klass != 'core'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: write
uses: ./.github/workflows/pr-verify.yml
with:
pr_number: ${{ github.event.pull_request.number }}
head_sha: ${{ github.event.pull_request.head.sha }}
klass: ${{ needs.guard.outputs.klass }}
secrets:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: arm native auto-merge (non-core)
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
gh pr merge "$PR" --repo "$REPO" --auto --squash
40 changes: 20 additions & 20 deletions .github/workflows/comment-command.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: comment-command
# slash-command trigger: the owner comments `/auto-merge` or `/verify` on a PR
# to authorize verification, as an alternative to applying the auto-merge label.
# slash-command trigger: the owner comments `/auto-merge` on a PR to arm
# auto-merge, as an alternative to applying the auto-merge label. review is done
# by CodeRabbit + ci; this only arms native auto-merge for non-core PRs.
on:
issue_comment:
types: [created]
Expand All @@ -10,7 +11,7 @@ concurrency:
cancel-in-progress: false
jobs:
parse:
# only PR comments, only the trusted owner, only a recognized command.
# only PR comments, only the trusted owner, only the /auto-merge command.
if: >
github.event.issue.pull_request &&
github.event.comment.author_association == 'OWNER' &&
Expand All @@ -21,15 +22,14 @@ jobs:
pull-requests: write
outputs:
is_command: ${{ steps.cmd.outputs.is_command }}
head_sha: ${{ steps.meta.outputs.head_sha }}
klass: ${{ steps.meta.outputs.klass }}
steps:
- name: detect /auto-merge or /verify
- name: detect /auto-merge
id: cmd
env:
BODY: ${{ github.event.comment.body }}
run: |
if printf '%s' "$BODY" | grep -Eiq '(^|[[:space:]])/(auto-merge|verify)([[:space:]]|$)'; then
if printf '%s' "$BODY" | grep -Eiq '(^|[[:space:]])/auto-merge([[:space:]]|$)'; then
echo "is_command=true" >> "$GITHUB_OUTPUT"
else
echo "is_command=false" >> "$GITHUB_OUTPUT"
Expand All @@ -42,35 +42,35 @@ jobs:
if: steps.cmd.outputs.is_command == 'true'
with:
python-version: "3.12"
- name: resolve head, classify, and mark authorized
- name: classify and mark authorized
id: meta
if: steps.cmd.outputs.is_command == 'true'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.issue.number }}
run: |
head_sha="$(gh pr view "$PR" --repo "$REPO" --json headRefOid --jq .headRefOid)"
gh pr view "$PR" --repo "$REPO" --json files --jq '.files[].path' > changed.txt
klass="$(PYTHONPATH=src python -m vouch.pr_bot classify --files-file changed.txt --print-klass)"
# mark authorized (visible, and enables deauthorize-on-push). a label
# added via GITHUB_TOKEN does not re-trigger auto-merge.yml (GitHub's
# token-recursion guard), so this does not double-run verification.
# token-recursion guard), so this does not double-arm.
gh pr edit "$PR" --repo "$REPO" --add-label auto-merge || true
echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT"
echo "klass=$klass" >> "$GITHUB_OUTPUT"

call-verify:
arm:
needs: parse
if: needs.parse.outputs.is_command == 'true'
# core PRs are never armed — CODEOWNERS requires the owner's approval.
if: needs.parse.outputs.is_command == 'true' && needs.parse.outputs.klass != 'core'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: write
uses: ./.github/workflows/pr-verify.yml
with:
pr_number: ${{ github.event.issue.number }}
head_sha: ${{ needs.parse.outputs.head_sha }}
klass: ${{ needs.parse.outputs.klass }}
secrets:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
steps:
- name: arm native auto-merge (non-core)
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.issue.number }}
run: |
gh pr merge "$PR" --repo "$REPO" --auto --squash
87 changes: 0 additions & 87 deletions .github/workflows/pr-verify.yml

This file was deleted.

20 changes: 9 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,15 @@ All notable changes to vouch are documented here. Format follows
available, and a `degraded` flag — a base install serving lexical hits
under a semantic-capable backend name now says so instead of labelling
them "hybrid" (#476).
- ai auto-merge bot: owner-labeled prs are verified by claude code on their
branch (functional smoke-test for `code`, before/after screenshot review
for `ui`) and auto-merged when green. core paths always require owner
review via `.github/CODEOWNERS`; ui prs opened without before/after
screenshots are auto-closed. deterministic decision logic lives in
`src/vouch/pr_bot.py` (pure stdlib, no model dependency). the owner can
authorize a pr with the auto-merge label or by commenting `/auto-merge`
or `/verify`; verification runs behind a `pr-verify` environment approval
gate. repo guards (branch ruleset + environment + labels) are configured
by `scripts/setup_repo_guards.sh`. workflow security is linted in ci with
zizmor + actionlint.
- ai auto-merge bot: the owner arms auto-merge on a pr with the `auto-merge`
label or by commenting `/auto-merge`; a non-core pr then merges once ci is
green. core paths always require owner review via `.github/CODEOWNERS`; ui
prs opened without before/after screenshots are auto-closed. review is done
by CodeRabbit (`.coderabbit.yaml`, free for this public repo) plus the free
`ci` + `trust-gate` gates — no paid model in the loop. deterministic
decision logic lives in `src/vouch/pr_bot.py` (pure stdlib). repo guards
(branch ruleset + labels) are configured by `scripts/setup_repo_guards.sh`.
workflow security is linted in ci with zizmor + actionlint.

### Changed
- `kb.search` is one implementation (`context.search_kb`) across mcp and
Expand Down
24 changes: 4 additions & 20 deletions scripts/setup_repo_guards.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,13 @@
# sets up:
# - labels: auto-merge, ci: passing, ci: failing
# - repo setting: allow auto-merge
# - environment `pr-verify` with the owner as a required reviewer (#2). the
# verify job pauses for your approval before it runs untrusted head code or
# touches ANTHROPIC_API_KEY.
# - a branch RULESET (#1) requiring a PR, code-owner review, and the ci +
# - a branch RULESET requiring a PR, code-owner review, and the ci +
# trust-gate status checks — the versionable replacement for classic branch
# protection, with org admins allowed to bypass (so you can still merge your
# own core PRs, which you can't self-approve).
#
# after running, set the api key as a repo secret:
# gh secret set ANTHROPIC_API_KEY --repo "$REPO"
# the caller passes it explicitly to the pr-verify handler; the environment's
# required-reviewer gate is what protects it — the verify job doesn't run, and
# the key isn't used, until you approve the run.
# review is done by CodeRabbit (install its GitHub App) + ci; this script does
# not configure any AI reviewer or secret.
#
# VERIFY BEFORE RELYING ON IT (schema/ids are account-specific):
# - the required check names below match .github/workflows/ci.yml job names.
Expand All @@ -27,26 +21,16 @@ set -euo pipefail

REPO="${REPO:-vouchdev/vouch}"
BRANCH="${1:-test}"
OWNER_LOGIN="${OWNER_LOGIN:-plind-junior}"
RULESET_NAME="auto-merge guard ($BRANCH)"

echo "==> labels"
gh label create "auto-merge" --repo "$REPO" --color 1d76db --description "owner-authorized: claude code verifies, then auto-merge" --force
gh label create "auto-merge" --repo "$REPO" --color 1d76db --description "owner-authorized: arm native auto-merge for non-core PRs" --force
gh label create "ci: passing" --repo "$REPO" --color 2ecc71 --description "ci is green" --force
gh label create "ci: failing" --repo "$REPO" --color e74c3c --description "ci is red" --force

echo "==> allow auto-merge"
gh api --method PATCH "repos/$REPO" -F allow_auto_merge=true >/dev/null

echo "==> environment pr-verify (required reviewer: $OWNER_LOGIN)"
owner_id="$(gh api "users/$OWNER_LOGIN" --jq .id)"
env_body="$(mktemp)"
cat > "$env_body" <<JSON
{"reviewers": [{"type": "User", "id": $owner_id}], "deployment_branch_policy": null}
JSON
gh api --method PUT "repos/$REPO/environments/pr-verify" --input "$env_body" >/dev/null
rm -f "$env_body"

echo "==> branch ruleset: $RULESET_NAME"
rules_body="$(mktemp)"
cat > "$rules_body" <<JSON
Expand Down
Loading