Skip to content

feat(groom): opt-in auto-builder — top-N CONFIRMED findings become review-gated PRs (BE-4003)#55

Open
mattmillerai wants to merge 3 commits into
mainfrom
matt/be-4003-groom-auto-builder
Open

feat(groom): opt-in auto-builder — top-N CONFIRMED findings become review-gated PRs (BE-4003)#55
mattmillerai wants to merge 3 commits into
mainfrom
matt/be-4003-groom-auto-builder

Conversation

@mattmillerai

Copy link
Copy Markdown
Contributor

ELI-5

The groomer finds tech-debt and files GitHub issues, but the team reviews at the PR level and never works the issues — so they're a dead end. This adds an opt-in auto-builder (split C of the groom epic): when a repo turns it on, the top few confirmed findings each run are turned into real, reviewable PRs (throttled to 5), while everything else still files as issues. The PRs run full CI + cursor-review and are never auto-merged — a human still presses the button. Off by default, so nothing changes unless you opt in.

What changed

  • groom.yml — three new jobs behind builder: true (default false):
    • build_select — the single dedup + throttle authority (no agent). Dedups verified findings against the durable ledger, then splits them: top max_prs (default 5) CONFIRM, non-security findings → build queue; the rest (DOWNGRADE, security, beyond-cap) → issue remainder. In builder-off mode it's a pass-through, so the issue path is byte-identical to before.
    • build — the credential-free builder (matrix, one cell per queued finding). permissions: contents: read, only ANTHROPIC_API_KEY. An agent writes the code change into its own fresh checkout; a deterministic step captures the working-tree diff as a patch artifact and enforces the pr_size_limit bail-out. Mirrors the finder/verifier isolation exactly.
    • build_pr — the credentialed PR job (no agent). Mints the bot token, applies the patch on a branch, opens a branded, review-gated PR (labels groom + groom-pr + cursor-review, a loud "NOT auto-merged" banner, the verifier rationale, and the signature marker). On a bail/oversized/non-applying patch it files a groom issue instead so nothing is dropped. Honors dry_run (preview, opens nothing).
  • ledger.py — PR-state added. The /issues listing already returns groom-labeled PRs; builder PRs carry the same signature marker in their body, so the ledger now classifies them: open → pr-open, merged → merged, closed-unmerged → pr-closed. All suppress re-proposing → a merged/closed finding is never re-proposed (RFC point 3). The markerless-record skip still guards against poisoning. (BE-3874 skipped all PRs because groom filed only issues then — cited in the code.)
  • builder.md — new phase-3 brief (scope-disciplined, keep-it-green, never-touch-security, patch-size bail).
  • Tests + README/AGENTS.md updated.

Acceptance criteria

  • ≤5 verifier-top findings → PRs; remainder not built — build_select caps to_build at max_prs.
  • Builder holds no creds, emits only a patch artifact; PR creation in a separate job as the bot — build (contents: read) → artifact → build_pr (bot token).
  • Ledger prevents re-proposing a merged/closed/rejected finding — PR-state in ledger.py, regression-tested (test_{open,merged,closed}_builder_pr_* in test_ledger.py).
  • Builder PRs labeled/branded, run CI + cursor-review — groom/groom-pr/cursor-review labels + bot author; opening as the bot App (required) triggers CI + the review panel.
  • [~] Excluded from merger auto-merge — done as far as this repo can: builder PRs are bot-authored and carry groom-pr (NOT agent-coded), so the agent-work merger (which lives in a different repo and keys on author == operator && agent-coded) already skips them. The actual merger-side skip rule can't be enforced from here — please confirm the merger excludes author == bot / label == groom-pr before enabling live on a repo the merger watches.
  • Groomer-only (issues) remains the default; builder opt-in via builder input.

Judgment calls (open items from the ticket — resolvable in review)

  1. 5 per run vs 1–2/day: implemented 5/run per Matt's 2026-07-21 decision (the ticket flags this supersedes the epic's earlier "1–2/day"). It's the max_prs input — trivially tunable.
  2. Beyond-cap findings: chose hybrid — remainder (DOWNGRADE, security, beyond-cap CONFIRM) is still filed as groom issues, so nothing is silently dropped (security findings especially). The alternative "defer to next run" is a one-line change if preferred.
  3. Ranking signal: used the verifier's emission order (the finder ranks ~6–12; the verifier preserves it) — zero brief change, lowest risk. A future explicit rank/score field in verifier.md can refine it.
  4. Rollback / test-coverage (Hunter/Wei Hai): not addressed here (out of scope for the plumbing) — left for the pilot; builder PRs run full CI so an under-tested refactor is caught at review, and the builder brief tells the agent to update touched tests.
  5. Patch-size bail-out: implemented — pr_size_limit (default 400 changed lines); an oversized or non-applying patch files an issue instead of a giant PR.

Riskiest lines & why they're safe

  • build_ledger now includes PRs (was an explicit skip). Safe because the marker check is unchanged: only PRs carrying a signature marker (= builder PRs) become records; a hand-labeled human PR is still ignored. History (BE-3874) cited inline — the skip predated the builder.
  • git diff --cachedgit apply --index round-trip: verified locally against modify + add + delete; apply-failure falls back to filing an issue.
  • Credential boundary: the build agent job is contents: read with no GitHub token; the only job with write creds (build_pr) runs no agent and never executes repo code (it only git applys a diff). A prompt-injected builder can produce a bad patch, nothing more.

Falsification note (negative-claim check)

The one deny path added — builder=true requires bot_app_id → exit 1 — is input validation that tells the operator exactly how to enable the feature, not a capability denial (no "not supported / STOP" dead-end). The bail path preserves the capability by filing an issue. No #581-class dead-end.

Testing

  • python3 -m unittest discover -s .github/groom/tests46 pass (10 new for PR-state).
  • YAML validated; all 6 embedded Python heredocs compile; build_select partition + git diff/apply round-trip smoke-tested locally.
  • Not exercised: a live end-to-end CI run (needs the bot App + a real repo) — recommend piloting with dry_run: true first (RFC point 6).

Note on size

~700 lines, but a single cohesive feature (the three jobs are one atomic unit) and fully gated behind an opt-in flag — zero behavior change when builder is off.

…view-gated PRs (BE-4003)

Split C of the groom epic (BE-3870). With builder: true, the top max_prs
(default 5) CONFIRMED, non-security findings become reviewable PRs instead of
issues, throttled and NEVER auto-merged; the remainder stay groom issues (hybrid).

Security boundary preserved (mirrors finder/verifier): a credential-free build
job (contents: read) writes only a patch artifact; a separate no-agent build_pr
job holds the bot token, applies the patch on a branch, and opens the PR. A
prompt-injected builder can only produce a bad patch, never push.

The durable ledger now recognizes builder PR state (open/merged/closed) via the
signature marker in the PR body, so a built finding is never re-proposed
(regression-tested). Groomer-only (issues) stays the default; builder requires
bot_app_id so PRs are branded and trigger CI + cursor-review.
@mattmillerai mattmillerai added cursor-review Multi-model cursor review agent-coded Authored by the agent-work loop labels Jul 22, 2026
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 22 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e523e522-88a4-4fa9-9bec-a8a0fdbfbcfe

📥 Commits

Reviewing files that changed from the base of the PR and between 8173fc2 and 08ad004.

📒 Files selected for processing (3)
  • .github/groom/ledger.py
  • .github/groom/tests/test_ledger.py
  • .github/workflows/groom.yml
📝 Walkthrough

Walkthrough

The groom workflow adds an opt-in auto-builder that converts verified non-security findings into patches and review-gated pull requests, with fallback issue filing. The durable ledger now tracks builder PR lifecycle states to prevent duplicate proposals.

Changes

Groom auto-builder

Layer / File(s) Summary
Builder contract and workflow configuration
.github/groom/README.md, .github/groom/builder.md, .github/workflows/groom.yml
Documents the optional Build phase, builder artifacts, configuration inputs, credential boundary, and patch-control output.
Pull-request ledger classification
.github/groom/ledger.py, .github/groom/tests/test_ledger.py
Classifies signed builder PRs as open, merged, or closed-unmerged and applies precedence and deduplication rules across issues and PRs.
Deduplication and finding partition
.github/workflows/groom.yml
Adds build_select as the deduplication and throttling authority, then routes findings through a shared decision artifact to either builder or issue filing.
Patch generation and PR creation
.github/workflows/groom.yml, AGENTS.md, README.md
Generates bounded patches without credentials, applies them in a serialized credentialed job, opens review-gated PRs, and falls back to issues on failure. Documentation describes the new workflow mode.

Sequence Diagram(s)

sequenceDiagram
  participant Groom
  participant build_select
  participant build
  participant build_pr
  participant GitHub
  Groom->>build_select: verified findings
  build_select->>GitHub: read issue and PR ledger state
  GitHub-->>build_select: deduplicated findings
  build_select->>build: selected findings
  build->>build_pr: patch artifact and status
  build_pr->>GitHub: open review-gated PR or fallback issue
Loading

Possibly related issues

  • Comfy-Org/unreviewed-merges#77 — Directly covers extending the groom workflow with the opt-in builder phase and PR-aware ledger.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/be-4003-groom-auto-builder
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/be-4003-groom-auto-builder

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/groom.yml:
- Around line 1000-1006: Scope each create-github-app-token invocation to least
privilege: at .github/workflows/groom.yml lines 570-576 (build_select), add
read-only issues and pull-requests permissions; at lines 689-695 (file), add
issues write permission; and at lines 1000-1006 (build_pr), add contents write,
pull-requests write, and issues write permissions. Use the action’s permission-*
inputs for each job and leave unrelated token configuration unchanged.
- Around line 69-77: Update the bot App permission requirements documented near
the builder configuration in the workflow to include issues:write alongside
contents:write and pull-requests:write, covering both File findings issue
creation and the build_pr fallback.
- Around line 941-968: Normalize the PR_SIZE_LIMIT value from the workflow input
to an integer before the size comparison in the patch-capture step. Update the
check around CHANGED and PR_SIZE_LIMIT so fractional values such as 400.0 are
safely converted or clamped, while preserving the existing bail behavior when
CHANGED exceeds the normalized limit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f4cadb3d-c944-474d-aa33-e2ab46af4c3e

📥 Commits

Reviewing files that changed from the base of the PR and between 9a7e853 and 8173fc2.

📒 Files selected for processing (7)
  • .github/groom/README.md
  • .github/groom/builder.md
  • .github/groom/ledger.py
  • .github/groom/tests/test_ledger.py
  • .github/workflows/groom.yml
  • AGENTS.md
  • README.md

Comment thread .github/workflows/groom.yml
Comment thread .github/workflows/groom.yml
Comment thread .github/workflows/groom.yml
…issues:write (BE-4003)

Address CodeRabbit review on PR #55:
- Scope each create-github-app-token mint to least privilege via permission-*
  inputs: build_select reads issues+PRs (read), file opens issues (write),
  build_pr pushes+opens PR+bails to issue (contents/pull-requests/issues write).
- Floor PR_SIZE_LIMIT to an int before the integer-only `-gt` test so a
  fractional `pr_size_limit` (e.g. 400.0) can't abort the patch-capture step
  under `set -euo pipefail`.
- Document that the bot App also needs issues:write (file + build_pr-bail paths
  open groom issues).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Cursor Review — Consolidated panel

Triggered by @mattmillerai.

Found 10 finding(s).

Severity Count
🔴 Critical 1
🟠 High 4
🟡 Medium 2
🟢 Low 3

Panel: 8/8 reviewers contributed findings.

Comment thread .github/workflows/groom.yml
Comment thread .github/workflows/groom.yml Outdated
Comment thread .github/workflows/groom.yml Outdated
Comment thread .github/workflows/groom.yml
Comment thread .github/workflows/groom.yml Outdated
Comment thread .github/groom/ledger.py
Comment thread .github/workflows/groom.yml Outdated
Comment thread .github/workflows/groom.yml
Comment thread .github/workflows/groom.yml
Comment thread .github/workflows/groom.yml
Address the cursor-review panel on PR #55:

- build_pr: gate on `!cancelled()` so one failed/timed-out build matrix cell
  (fail-fast:false) no longer skips the PR sink for EVERY index — each
  successfully-built finding still opens its PR. (High)
- builder allowedTools: drop `Bash(find:*)` — `find … -exec` is arbitrary
  command execution / an egress channel for a prompt-injected builder holding
  the model key. Glob/Grep/Read cover discovery without exec. (High)
- capture: refuse a patch touching `.github/workflows/` (files an issue
  instead) — such a patch would run in credentialed CI on the same-repo push
  before any human review, self-authoring CI. (High)
- security gate: fail CLOSED in both build_select and build_pr — a finding is
  auto-buildable only if the verifier PROVABLY marked it `security == false`;
  missing/malformed values now stay issues, never auto-implemented. (High)
- ledger: admit a PR as a builder record only if it carries the bot-applied
  `groom-pr` label, so a hand-opened `groom` PR pasting a public signature
  marker cannot suppress a live finding. (Medium)
- build_pr base: use `github.event.repository.default_branch` (rev-parse
  fallback) instead of `git rev-parse HEAD`, which mis-targets on a
  non-default-branch dispatch or yields "HEAD" on a detached checkout. (Medium)
- builder labels step: add the `if: dry_run != true` guard the file job has,
  so a dry run no longer mutates the target repo's labels. (Low)

Also fixes a bug in the initial hardening: rationale comments must not live
inside the `claude_args:` block scalar (they would reach the CLI as literal
args) — moved to a YAML comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mattmillerai

Copy link
Copy Markdown
Contributor Author

🤖 The reviews loop filed Linear follow-up ticket(s) for review thread(s) deferred as out of scope for this PR:

  • BE-4012 — Groom auto-builder: block patches that add package install/build hooks (not just .github/workflows/)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-coded Authored by the agent-work loop cursor-review Multi-model cursor review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant