Skip to content

feat: reusable label-gated PR review loop (review / security / tests)#2

Merged
WeekendSuperhero merged 5 commits into
mainfrom
feat/pr-review-loop
Jul 14, 2026
Merged

feat: reusable label-gated PR review loop (review / security / tests)#2
WeekendSuperhero merged 5 commits into
mainfrom
feat/pr-review-loop

Conversation

@WeekendSuperhero

@WeekendSuperhero WeekendSuperhero commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

A reusable three-agent PR review loop built on the same jules-ai plumbing as pr-description/changelog, designed around no wasted agent turns and no PR noise. Control labels use the muse:* namespace.

  • Label as the button: muse:review starts a round and is removed on pickup (re-arms); muse:force bypasses the same-SHA no-op. Outcome labels per agent + muse:approved/muse:error rollups; taxonomy bootstraps itself idempotently and is versioned in config/sources/repo.labels.json with scripts/sync-repo-labels.sh to apply it org-wide.
  • Delta rounds: each agent keeps ONE sticky comment (PATCHed in place) with a hidden marker carrying the reviewed SHA, round number, and base64 open-findings state. The next round verifies each previous finding (resolved/unresolved) and reviews only the diff since the last reviewed SHA.
  • Three lenses: code review (logic/error-handling/DRY vs caller-supplied guidelines), security (injection, secrets, trust boundaries, entitlements, deps), tests (maps diff behaviors → tests, names the concrete missing cases).
  • Deterministic verdicts: labels derive from open findings (critical/high ⇒ fail, medium ⇒ warn); the model verdict can only make it stricter — an agent can't green-light its own findings.
  • Loop safety: caller uses job-level concurrency + exact-match label guard (documented in the header) so our own label writes never cancel an in-flight round; synchronize strips stale outcome labels and cancels the round; comment/label writes use github.token (never triggers other workflows).

Test plan

  • actionlint (only info-level SC2016 jq single-quote notes)
  • bash -n on all run blocks + the sync script
  • Fixture round-trip: marker+base64 state parse → carry-forward (resolved dropped, unresolved carried, new appended) → derived verdict (model 'pass' with an open medium ⇒ 'warn') → sticky render
  • Live: press muse:review on a real PR in the agent repo (caller: agent#298)

No changes to existing workflows — new files only.

Three Jules agents per round, each with a distinct lens and a strict JSON
contract; designed around two constraints: no wasted agent turns and no
PR noise.

Loop mechanics:
- The trigger label is a button: removed on pickup (re-arms), jules:reviewing
  while running, outcome labels per agent (review:approved|needs-changes,
  security:clear|flagged, tests:sufficient|needs-work) plus jules:approved /
  jules:error rollups. Label taxonomy bootstraps idempotently.
- Same-SHA presses no-op unless the force label is used; the caller holds a
  per-PR job-level concurrency group with an exact-match label guard so our
  own label writes can never cancel an in-flight round.
- New commits (synchronize) strip stale outcome labels and cancel the round.
- Delta rounds: each agent's sticky comment carries a hidden marker
  (reviewed SHA, round, base64 open-findings state); the next round makes
  agents verify each previous finding (resolved/unresolved) and review only
  the diff since the last reviewed SHA.
- Noise ceiling: exactly one sticky comment per agent, PATCHed in place.
  Resolved findings collapse into <details>.
- Verdict -> labels is DERIVED from open findings (critical/high = fail,
  medium = warn); the model's own verdict can only make it stricter, so an
  agent cannot green-light its own open findings.
- Human PR discussion (minus our stickies) is included in the prompt so
  agents see pushback and decisions.

Context gathering reuses the pr-description include/exclude + per-file
collapse strategy; per-agent guidelines files from the calling repo are
appended to the prompts (e.g. RULES.md for review, CREDENTIALS.md for
security).
- Rename the review-loop control labels jules:* -> muse:* (trigger
  muse:review, muse:force, state muse:reviewing, rollups muse:approved /
  muse:error) and brand user-visible strings (sticky headers, label
  descriptions) as Muse; Jules stays named where it is the engine
  (secrets, jules-ai action, session references). Sticky markers move to
  <!-- muse:<agent> ... --> — safe now, nothing is in production yet.
- config/sources/repo.labels.json: canonical versioned label set (control
  + per-agent outcome labels).
- scripts/sync-repo-labels.sh: idempotent sync of the manifest to given
  repos or every non-archived repo in an org (gh label create --force).
  GitHub's org 'default labels for new repos' has no API — mirror the
  manifest there once by hand, or re-run the script / rely on the
  workflow's self-bootstrap.
The three reusable workflows carried their operator documentation (usage
snippets, loop mechanics, Linear/split-mode explanations) as YAML header
comments. That prose now lives in docs/muse-workflows.md — one reference
covering pr-description, changelog, and the review loop (label taxonomy,
delta rounds, verdict derivation, failure modes, inputs) — and each YAML
header is a one-breath summary + pointer. Deliberately NOT named
AGENTS.md: that name is the convention for instructions addressed TO AI
coding agents working on a repo, not documentation about CI automation.
The three lens prompts and the shared JSON contract were heredocs inside
reusable-pr-review.yml. They now live as plain markdown in
.github/actions/muse-prompts/prompts/ (contract.md, review.md,
security.md, tests.md) with {{AGENT}}/{{PREFIX}}/{{ROUND}}/{{MAX_FINDINGS}}
tokens substituted at runtime — editable and reviewable as prose, not
YAML string art.

Delivery mechanism: reusable workflows never check out their own repo,
so files next to the YAML don't exist on the runner. The tiny
muse-prompts composite action solves that — referencing a remote action
downloads the platform-tools tarball, and github.action_path exposes the
templates directory to the calling job.
@WeekendSuperhero
WeekendSuperhero enabled auto-merge (squash) July 14, 2026 20:19
@WeekendSuperhero
WeekendSuperhero merged commit 36d3348 into main Jul 14, 2026
3 checks passed
@WeekendSuperhero
WeekendSuperhero deleted the feat/pr-review-loop branch July 14, 2026 20:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant