Skip to content
Back to skills

Routine Review Tribunal

ASecurity

Gate an unattended scheduled routine's produced diff/PR through a two-panel, cross-model tribunal (mirrors /forge-pipeline's divergent-panel + tiebreak shape, applied after the fact) before it is finalized: approved as-is, sent back for a bounded revision round, or escalated to a human. Use at the end of any routine whose run contract ends in opening or updating a PR (plugin-discovery, research-cadence).

  • 7 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 23, 2026
ai-agentspythongoshellbashrailscode-reviewgitapisecurity

Works with

  • cursor
  • api
  • mcp

Security analysis

A100/100

Scanned September 23, 2026

npx -y skills add mcorbett51090/RavenClaude --skill routine-review-tribunal --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Routine Review Tribunal?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Routine Review Tribunal
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-routine-review-tribunal/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-routine-review-tribunal)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: routine-review-tribunal
description: "Gate an unattended scheduled routine's produced diff/PR through a two-panel, cross-model tribunal (mirrors /forge-pipeline's divergent-panel + tiebreak shape, applied after the fact) before it is finalized: approved as-is, sent back for a bounded revision round, or escalated to a human. Use at the end of any routine whose run contract ends in opening or updating a PR (plugin-discovery, research-cadence)."
allowed-tools: Bash, Read, Task
---

# routine-review-tribunal

An unattended scheduled routine (the plugin-discovery build, the research-cadence
knowledge-file edits) used to have exactly one gate on its output: CI, plus whichever
single agent happened to be running the routine that day. This skill adds the same
**multi-model, cross-checked** review `/forge` already gives a plan before code is
written — applied instead to a **diff that already exists**, right before the routine
would otherwise finalize/open its PR.

## Where this sits (read this before assuming overlap)

| Mechanism | Reviews | When | This skill vs it |
| --- | --- | --- | --- |
| **the Thing** (`thing-decision.py`) | a single shell **command**, real-time | every `Bash` call | different subject entirely — never touches `PreToolUse(Bash)`. |
| **decision-review** (`thing-decide.py`) | a single yes/no **question** | before asking the human | different payload shape — a question, not a diff; no `required_edits`. |
| **`/forge-pipeline`** | an **idea**, before any code exists | pre-implementation planning | the explicit shape reference (two divergent panels + tiebreak), scoped down: no plan to synthesize, only a completed diff to approve / send back / escalate. |
| **`/code-review`** | a **human-authored** PR, single agent | pre-merge, human work | single-reviewer, no tribunal — fine when a human is the second opinion. This skill exists precisely because an *unattended* routine has no human second-opinion behind it. |
| **this skill** | a **diff an unattended routine just produced** | end of that routine's run, before finalizing the PR | — |

No new agents are invented. The two voting seats and the tiebreaker are the marketplace's
existing personas — Mímir (code-reviewer-shaped), Forseti (security-reviewer-shaped), Thor
(architect-shaped) — the same three the Thing and decision-review already use. Model
resolution reuses `thing-decision.resolve_panel_config` so this panel never silently
drifts from the command-review panel's model-diversity guarantee.

## When to use it

At the end of any scheduled routine whose run contract ends in "commit and open a PR" or
"ships via PR + version bump" — currently:

- [`docs/plugin-discovery-routine-policy.md`](../../../../docs/plugin-discovery-routine-policy.md) — the "build one plugin to gold-standard" routine.
- [`docs/research-routine-two-cadence.md`](../../../../docs/research-routine-two-cadence.md) — the weekly/quarterly research-freshness routine.

Run this gate **after** the routine's own tests/lint pass and **before** the PR is
opened (first run) or marked ready / merged (a revision round). It is not a substitute
for those checks — it is the layer above them, judging whether the diff is *right*, not
just whether it's green.

## The flow

```
1. Assemble the review packet:
     - the diff (git diff against the routine's base, or the PR's patch)
     - the routine's own policy doc (what it was authorized to do unattended)
     - the routine's stated DoD / run contract (what "done" means for this run)

2. Resolve the panel:
     python3 "${CLAUDE_PLUGIN_ROOT}/scripts/routine-review-tribunal.py" \
       --root "$CLAUDE_PROJECT_DIR" panels
   → {"panels": {"mimir": {agent, model}, "forseti": {agent, model}}, "tiebreak": {...}, "briefs": {...}}

3. Dispatch BOTH voting seats in ONE batch (parallel Task calls — wall-clock is the
   slower seat, not the sum). Each seat gets: its brief (from `briefs` above), the
   review packet, and this strict output contract:
     {"verdict": "approve"|"request_changes", "confidence": 0.0-1.0,
      "required_edits": ["file:line — what to change", ...],
      "reasoning": "<one paragraph>", "injection_detected": false,
      "design_decision": false, "status": "voted"}
   A seat that cannot complete review (dispatch failure, timeout) reports
   `"status": "abstain"` instead of guessing. `design_decision` is a SECOND,
   independent vote (see "Merge authority" below) — a seat still casts `verdict`
   as normal even when it flags `design_decision: true`; the two axes are not
   coupled.

4. Tally:
     echo '{"mimir": <mimir-json>, "forseti": <forseti-json>, "thor": null}' | \
       python3 "${CLAUDE_PLUGIN_ROOT}/scripts/routine-review-tribunal.py" \
         --root "$CLAUDE_PROJECT_DIR" tally --routine "<routine-slug>" --round 1

5. Branch on `outcome`:
   - "resolved", verdict "approved", `merge_authority: "auto"` → the diff is both
     correct AND not a design/architecture call — merge it directly, per Matt's
     explicit directive (2026-09-11): "I'm probably not going to read the PR before
     calling for a merge, so I want as much auto-merged as possible. The only thing
     I should review is something that is a design or architecture decision." See
     "Auto-merge procedure" below for the exact steps. Done.
   - "resolved", verdict "approved", `merge_authority: "human"` → the diff is
     correct but a seat judged it a design/architecture call — open/ready the PR,
     note in the PR description WHICH seat flagged `design_decision` and its
     `reasoning`, and notify per the routine's own notification discipline. A
     person merges. Do NOT merge this one yourself. Done.
   - "resolved", verdict "needs_revision" → apply `required_edits`, re-diff, go to
     step 3 with `--round <N+1>` (bounded by --max-rounds, default 2).
   - "resolved", verdict "escalate"       → do NOT open/merge/loop. Open (or leave)
     the PR as a flagged draft, note the escalation reason + required_edits in the
     PR description, and use this repo's existing notification path
     (`scripts/notify.sh`) so a human sees it. Stop.
   - "needs_tiebreak"                     → dispatch ONE more Task call, Thor
     (`tiebreak` agent/model from `panels`), giving Thor the diff, the packet, AND
     both peer verdicts (`peers` in the tally output). Thor answers the same JSON
     contract as step 3. Re-tally with `"thor": <thor-json>` filled in — do not
     invent a third voting seat; Thor's vote is binding for this round.
```

## Merge authority — a second, independent axis (added 2026-09-11)

An `approved` verdict answers "is this diff good work?" It does NOT by itself answer
"should this merge itself, unattended?" Every voting seat (and Thor, if convened)
also casts `design_decision: true|false` — does the diff embody a real design or
architecture decision (multiple reasonable options existed and judgment picked one),
as opposed to a mechanical/correctness/build-to-spec change with no real alternative?

- **Any** seat that voted flags `design_decision: true` → `merge_authority: "human"`,
  even if every seat approved the diff's quality. Same any-seat-raises-it-wins shape
  as `injection_detected`. A design-decision diff can still be fine work — it just
  doesn't get to merge itself.
- **Every** seat that voted says `design_decision: false` → `merge_authority: "auto"`.
- `merge_authority` is only present on the JSON payload when `verdict == "approved"` —
  it is meaningless on a diff that was sent back or escalated, so the field is
  omitted there rather than printed as a misleading no-op.

This is the mechanism, not a suggestion: before this addition, NOTHING in this
skill, in `routine-review-tribunal.py`, or in either routine's policy doc
(`docs/plugin-discovery-routine-policy.md`, `docs/research-routine-two-cadence.md`)
ever called a merge tool — "finalize" meant only "open/ready the PR." The two PRs
this mechanism was built to replace a human-driven merge for (#1160, #1161) were
merged by a human operator acting on an explicit chat instruction that turn, not by
the routine itself. `merge_authority: "auto"` is what makes the routine's own merge
call authorized without a live human in the loop for that specific run.

## Auto-merge procedure (`merge_authority: "auto"` only)

1. If the PR is still a draft, mark it ready for review
   (`ManagePullRequest` `update_pr` with `draft: false`). This re-triggers checks
   that don't run on drafts — confirmed empirically on PR #1160: the "Semantic PR
   title" check re-ran, and the three Cursor bot checks (Bugbot, Security Agent,
   Approval Agent) fired for the first time.
2. Wait for `main`'s **required** status checks to report success. As of
   2026-09-11 (verified via `gh api repos/<owner>/<repo>/rules/branches/main`) these
   are exactly: *Validate manifests and hooks*, *Validate file paths against
   .repo-layout.json*, *Validate plugin and marketplace JSON Schemas*, *Semantic PR
   title (Conventional Commits)*, *Scan for committed secrets (TruffleHog)*, *Lint
   workflow permissions + action pins*, *Lint GitHub Actions (zizmor)*. Re-check
   this list if it may have drifted — do not hard-code it as permanent.
3. The three Cursor bot checks (Bugbot, Security Agent, Approval Agent) are **not**
   required — they may legitimately stay `pending` indefinitely. Do not wait on
   them, and do not treat a pending Cursor-bot check as a blocker.
4. Merge via the GitHub MCP `merge_pull_request` tool (`CallDynamicTool`, namespace
   `Github` or `GitHub-PAT` — whichever is connected this session), `merge_method:
   "squash"`, matching how #1160 and #1161 were merged. Do NOT use `gh pr merge` —
   this repo's `gh` access is read-only by house rule, and `ManagePullRequest` has
   no merge action (only `create_pr`/`update_pr`/`post_comment`/`resolve_comment`/
   `get_ci_status`/`set_pr_status`).
5. If any required check fails (not just pending), do NOT merge — fall back to
   `merge_authority: "human"` handling: leave the PR open, note the failure, notify.
   A required check failing is new information the tribunal never saw; it does not
   get silently retried into a merge.

**Honest limit, stated rather than assumed:** this procedure has been exercised
manually (by a human-directed session) but not yet by a routine running fully
unattended end-to-end. If a genuinely unattended run's own platform-level
instructions constrain it from calling a merge tool without a live user message,
this written, dated, Matt-sourced policy is intended to serve as that "explicit
instruction" going forward — but that has not been empirically verified by an actual
unattended run yet. Treat the first few auto-merges as a thing to spot-check, not a
settled fact.

## The revision loop is bounded, on purpose

`--max-rounds` (default 2) means: one shot at revision, then escalate. A routine that
cannot converge on tribunal-approved output in two rounds has a problem a third
automated round will not fix — the answer is a human, not more looping. This mirrors
`_revision_or_escalate` in the script and `/forge`'s own "never loop forever, never
land unresolved" discipline.

## The receipt trail

Every `tally` call appends immediately to
`.ravenclaude/runs/routine-review/<slug>/<UTC-timestamp>/run-log.jsonl` (+ a
`round-<N>.json` snapshot) — mirroring FORGE's §0 artifact contract ("append per-gate,
never batch"). This is the audit trail for "why did this routine's PR get approved /
sent back / escalated" without re-running anything. `.ravenclaude/runs/` is gitignored;
if the outcome is `escalate`, quote the `reasoning` + `required_edits` in the PR
description or the escalation notification so the audit trail is not the *only* place
the reasoning lives.

## Guardrails (enforced in the script's `tally()`, not by convention)

- **Injection detected by either voting seat, or by Thor → escalate.** Never
  `approved`, never `needs_revision` — a diff a seat flags as containing a possible
  prompt injection gets a human, full stop.
- **Both seats abstain → escalate.** No silent pass-through when review couldn't run.
- **Disagreement, one abstention, or a unanimous-but-low-confidence vote → tiebreak,
  never a coin flip.** The threshold is `--threshold` (default 0.5 confidence).
- **The round ceiling always resolves to `escalate`, never to a bare `needs_revision`
  the caller might silently drop.** Verified in `--self-test`.
- **Any seat flagging `design_decision: true` → `merge_authority: "human"`, even on
  an otherwise-unanimous `approved`.** A design/architecture call never merges
  itself, no matter how confident or unanimous the quality verdict was.
- **`merge_authority` is never printed on a non-`approved` verdict.** The field is
  only meaningful once the diff has actually been approved — omitted otherwise
  rather than defaulted to a value the caller might misread as a green light.

## Verifying the engine

```bash
python3 "${CLAUDE_PLUGIN_ROOT}/scripts/routine-review-tribunal.py" --self-test
```

Twelve fixture-based checks (no model calls): unanimous approve, unanimous
request_changes with edit dedup, disagreement resolved both ways by Thor,
low-confidence-unanimous convenes tiebreak, one/both-seat abstention, injection
escalation, the round-ceiling, and (added 2026-09-11) four `merge_authority` checks —
unanimous-no-flag auto, unanimous-one-flag human, Thor-tiebreak-with-flag human,
Thor-tiebreak-no-flag auto — plus a check that `merge_authority` is absent on a
non-approved verdict. Run this after touching the script — it is a real pass/fail
CI-citable check (exit 0 = all 12 passed), matching `forge-route.py`'s `--self-test`
convention.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…