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: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Orca-Code-Review
OrcaCode Review
Copyright 2026 OrcaRouter

This product orchestrates Open Code Review ("ocr"), an open-source code review
Expand Down
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Orca-Code-Review
# OrcaCode Review

AI pull-request review powered by the [OrcaRouter](https://orcarouter.ai) model
gateway. A cheap model screens every push; once a push comes back clear of
Expand Down Expand Up @@ -34,7 +34,10 @@ merge.
4. **Merge gate** — the job fails if any **P0/P1** is found; mark the check
"required" in branch protection to block the merge.
5. **Per-commit loop** — `synchronize` re-reviews on every new push; comment
`/orca-code-review` on a PR to re-run on demand. The comment re-run posts
`/orcacode-review` on a PR to re-run on demand. Either prefix (`/` or `@`)
with either separator works — `/orcacode-review`, `/orcacode review`,
`@orcacode-review`, `@orcacode review` — the same set the OrcaCode Review
GitHub App accepts, so no spelling silently does nothing. The comment re-run posts
fresh review comments but does **not** update the required merge-gate check:
an `issue_comment` run is tied to the default branch, not the PR head, so its
pass/fail can't attach to the PR's commit. Push a new commit to refresh the
Expand Down Expand Up @@ -80,7 +83,7 @@ merge.
(copy from [`workflows/orca-code-review.yml`](./workflows/orca-code-review.yml)):

```yaml
name: Orca-Code-Review
name: OrcaCode Review
on:
pull_request_target:
# ready_for_review makes the dashboard's trigger=ready_for_review mode
Expand All @@ -95,13 +98,17 @@ merge.
jobs:
review:
runs-on: ubuntu-latest
# PR events, or a `/orca-code-review` command from a maintainer —
# otherwise any commenter could spend your quota.
# PR events, or a review command from a maintainer — otherwise any
# commenter could spend your quota. All four spellings are accepted,
# matching the OrcaCode Review GitHub App.
if: |
github.event_name == 'pull_request_target' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/orca-code-review') &&
(startsWith(github.event.comment.body, '/orcacode-review') ||
startsWith(github.event.comment.body, '/orcacode review') ||
startsWith(github.event.comment.body, '@orcacode-review') ||
startsWith(github.event.comment.body, '@orcacode review')) &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
steps:
- uses: Continuum-AI-Corp/orca-code-review@v1
Expand All @@ -123,15 +130,15 @@ All optional — pass as `with:` inputs on the action:
|---|---|---|
| `orcarouter-api-key` | _(required)_ | OrcaRouter API key |
| `orcarouter-url` | `https://api.orcarouter.ai/v1/chat/completions` | Gateway endpoint |
| `brand` | `Orca-Code-Review` | Name shown on PR comments |
| `brand` | `OrcaCode Review` | Name shown on PR comments |
| `router` | `orcarouter/code-review` | OrcaRouter router alias whose DSL recipe picks the cheap/strong model per tier (the action names no models) |
| `fix-first` | `P0,P1` | Keep the PR on the cheap tier until these are cleared (then it's promoted) |
| `block-on` | `P0,P1` | Fail the check (block merge) on one of these |
| `max-diff-kb` | `512` | Skip the review when the merge-base diff is bigger than this many KB — a skip posts a notice without running the engine; the check's outcome is `on-oversized-diff` |
| `max-diff-files` | `300` | Skip the review (same notice + `on-oversized-diff` outcome) when the diff touches more than this many files |
| `on-oversized-diff` | `fail` | What an oversized-diff skip does to the check: `fail` (default) fails it, so a diff padded past the limits can never bypass a required merge gate; `pass` makes skips advisory (notice + green check) |
| `settings` | `true` | Fetch per-repo settings from the OrcaRouter dashboard on every run; set `"false"` to skip the fetch and make the workflow file authoritative (inputs/defaults apply as-is, no dashboard override) |
| `auto-review-authors` | `""` (everyone) | Comma-separated author-association allowlist for **automatic** reviews. Empty reviews every PR. On a **public** repo, set e.g. `OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR` so anonymous fork PRs can't drain your wallet with paid cascades (they can still be reviewed on demand via `/orca-code-review`). See [Public repos & spend](SECURITY.md#public-repos--spend). |
| `auto-review-authors` | `""` (everyone) | Comma-separated author-association allowlist for **automatic** reviews. Empty reviews every PR. On a **public** repo, set e.g. `OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR` so anonymous fork PRs can't drain your wallet with paid cascades (they can still be reviewed on demand via `/orcacode-review`). See [Public repos & spend](SECURITY.md#public-repos--spend). |
| `report` | `true` | Send a per-run summary (severity counts only — never code) to the OrcaRouter control plane; set `"false"` to disable — see [Run reporting](#run-reporting) |
| `github-token` | `${{ github.token }}` | Token used to fetch the PR head, post review comments, and manage the tier label; override only if the default `GITHUB_TOKEN` lacks the needed scopes |
| `engine-version` | `1.3.13` | Pinned `@alibaba-group/open-code-review` version (the review engine); bump deliberately after testing — later steps parse its JSON output shape |
Expand All @@ -153,8 +160,8 @@ OrcaRouter dashboard without touching the workflow:

| Setting | Values (default first) | Effect in the Action |
|---|---|---|
| `auto_review` | `true` / `false` | `false`: automatic (`pull_request_target`) runs skip the engine, leave one small "automatic review is off" comment, and **pass** the check. `/orca-code-review` comment commands still run. |
| `trigger` | `every_push` / `ready_for_review` / `on_demand` | `every_push`: review every push. `ready_for_review`: skip automatic runs **while the PR is a draft** (add `ready_for_review` to your workflow's `pull_request_target.types` so the review fires when the PR leaves draft). `on_demand`: skip all automatic runs — only `/orca-code-review` comments review. All skips pass the check. |
| `auto_review` | `true` / `false` | `false`: automatic (`pull_request_target`) runs skip the engine, leave one small "automatic review is off" comment, and **pass** the check. `/orcacode-review` comment commands still run. |
| `trigger` | `every_push` / `ready_for_review` / `on_demand` | `every_push`: review every push. `ready_for_review`: skip automatic runs **while the PR is a draft** (add `ready_for_review` to your workflow's `pull_request_target.types` so the review fires when the PR leaves draft). `on_demand`: skip all automatic runs — only `/orcacode-review` comments review. All skips pass the check. |
| `exhaustive` | `false` / `true` | Re-run the engine up to **2 extra times on the strong (enforced) tier**, deduplicating findings across passes (one review pass is not exhaustive; a re-run surfaces missed findings). The cheap screening pass never gets extras — its result is either superseded by the same-run strong review or held on fix-first findings anyway. The loop stops early once a pass adds nothing new **or a fix-first (P0/P1) finding is already in hand** (the gate blocks on it regardless of extra depth). **Cost cap: at most 3 engine passes total on the enforced tier.** The summary comment notes `exhaustive: N passes`. |
| `quiet` | `false` / `true` | Advisory **P2 comments are not posted inline** — they are muted at the posting step only. The summary keeps the **true** P0/P1/P2 counts with a `quiet mode: P2 shown in summary only` note, and the gate/run report always see the unfiltered counts. |
| `fix_first` | `"P0,P1"` | Same meaning as the `fix-first` input — see precedence below. |
Expand Down Expand Up @@ -225,7 +232,7 @@ repo setting:
4. Save.

Now a failing review disables the merge button until it goes green. Re-run the
gate by pushing a new commit (the `/orca-code-review` comment posts a fresh
gate by pushing a new commit (the `/orcacode-review` comment posts a fresh
read but can't flip the required check — see the per-commit loop note above).

**Merge-gate note — oversized diffs.** An oversized-diff skip (`max-diff-kb` /
Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ for public repos:
- **Restrict auto-review authors** with the `auto-review-authors` input (e.g.
`OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR`) so anonymous/first-time PRs are not
auto-reviewed. They can still be reviewed on demand by a maintainer's
`/orca-code-review` comment, which is separately gated to
`/orcacode-review` comment, which is separately gated to
OWNER/MEMBER/COLLABORATOR.

### `pull_request_target` and secrets
Expand All @@ -54,7 +54,7 @@ to read. Consumers MUST NOT add build, test, or install steps that execute
PR-controlled code to this workflow. If you need to run PR code, do it in a
separate `pull_request`-triggered workflow with no secrets.

The comment trigger (`/orca-code-review`) is gated to
The comment trigger (`/orcacode-review`) is gated to
OWNER/MEMBER/COLLABORATOR in the shipped workflow so arbitrary commenters
cannot spend your quota.

Expand Down
83 changes: 72 additions & 11 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Orca-Code-Review — reusable composite action.
# OrcaCode Review — reusable composite action.
#
# Consumers reference this with `uses:` instead of copying the folder; see
# workflows/orca-code-review.yml for the ~15-line example workflow.
Expand Down Expand Up @@ -35,7 +35,7 @@
# (scripts/report.mjs) posts severity counts — never code — to the OrcaRouter
# control plane after the gate.

name: "Orca-Code-Review"
name: "OrcaCode Review"
description: "AI PR review via the OrcaRouter gateway — cost-tiered cascade with a severity merge gate."
author: "OrcaRouter"

Expand All @@ -58,7 +58,7 @@ inputs:
brand:
description: "Name shown on PR comments."
required: false
default: "Orca-Code-Review"
default: "OrcaCode Review"
router:
description: >-
OrcaRouter router alias (orcarouter/<name>) that owns model selection.
Expand All @@ -84,7 +84,7 @@ inputs:
paid cascades by opening PRs — set e.g.
"OWNER,MEMBER,COLLABORATOR,CONTRIBUTOR" to auto-review only known
contributors (others can still be reviewed on demand via
/orca-code-review). Also set a wallet budget + alert on the key.
/orcacode-review). Also set a wallet budget + alert on the key.
Case-insensitive; values: OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR,
FIRST_TIME_CONTRIBUTOR, FIRST_TIMER, MANNEQUIN, NONE.
required: false
Expand Down Expand Up @@ -165,6 +165,29 @@ inputs:
raise to be stricter.
required: false
default: "0.5"
concurrency:
description: >-
Max concurrent file reviews. Raise to shorten wall clock on large diffs;
lower it if your model has a tight per-minute request quota. Empty uses
the engine default.
required: false
default: "24"
max-tools:
description: >-
Max tool-call rounds per file. Lowering it reduces cost and time but can
cost review depth. Empty uses the engine default.
required: false
default: ""
meter:
description: >-
Record per-call token accounting for the run (prompt / completion /
cached tokens and the model the router resolved) and print a totals table
in the job log, so a run's real token cost is visible. Local only —
nothing is uploaded, and the file is removed with the other run files.
Soft-fail: metering never alters or blocks a review. Set "false" to skip
it.
required: false
default: "true"

runs:
using: "composite"
Expand All @@ -187,7 +210,7 @@ runs:
"$RUNNER_TEMP/cr-facts.json" "$RUNNER_TEMP/proxy.out" "$RUNNER_TEMP/proxy.err" \
"$RUNNER_TEMP/policy-block.json" \
"$RUNNER_TEMP/pr.diff" "$RUNNER_TEMP/diff-guard.json" "$RUNNER_TEMP/prev-summary.md" \
"$RUNNER_TEMP/wallclock-timeout" \
"$RUNNER_TEMP/wallclock-timeout" "$RUNNER_TEMP/cr-usage.jsonl" \
"$RUNNER_TEMP/result.l1.json" "$RUNNER_TEMP/result.l2.json" \
"$RUNNER_TEMP/result-extra.l1.json" "$RUNNER_TEMP/result-extra.l2.json"

Expand Down Expand Up @@ -270,7 +293,7 @@ runs:
if [ -n "${PR_NUMBER:-}" ]; then
react "repos/$REPO/issues/$PR_NUMBER/reactions"
fi
# For a maintainer's /orca-code-review command, also react on that
# For a maintainer's /orcacode-review command, also react on that
# trigger comment so the operator sees direct feedback on THEIR comment.
if [ "$EVENT_NAME" = "issue_comment" ] && [ -n "${COMMENT_ID:-}" ]; then
react "repos/$REPO/issues/comments/$COMMENT_ID/reactions"
Expand Down Expand Up @@ -331,7 +354,7 @@ runs:
# Auto-event gate -> sets DECISION/REASON. Only AUTO runs are gated —
# any pull_request* event, so a hand-written plain `pull_request`
# workflow honors gating exactly like the documented
# `pull_request_target` one. An explicit `/orca-code-review` comment
# `pull_request_target` one. An explicit `/orcacode-review` comment
# command (issue_comment) always proceeds — on-demand mode exists
# precisely for those, and a human asking for a review of a
# draft/disabled repo is deliberate. Shared by both settings paths so the
Expand Down Expand Up @@ -461,7 +484,8 @@ runs:
`${MARKER}\n## 🐳 ${brand}\n\n` +
`⏸️ **Automatic review is off for this PR** (${process.env.REASON}).\n\n` +
`The check **passes** — this never blocks your merge. A maintainer can still ` +
`run a review on demand by commenting \`/orca-code-review\`, or change the ` +
`run a review on demand by commenting \`/orcacode-review\` ` +
`(\`@orcacode review\` works too), or change the ` +
`repo's review settings in the OrcaRouter dashboard.` +
footer;

Expand Down Expand Up @@ -681,6 +705,12 @@ runs:
RESULT_EXTRA: ${{ runner.temp }}/result-extra.json
RESULT_MERGED: ${{ runner.temp }}/result-merged.json
FACTS_FILE: ${{ runner.temp }}/cr-facts.json
# Per-call token accounting (see the `meter` input). Empty disables the
# tap entirely inside the proxy.
CONCURRENCY: ${{ inputs.concurrency }}
MAX_TOOLS: ${{ inputs.max-tools }}
METER: ${{ inputs.meter }}
USAGE_FILE: ${{ runner.temp }}/cr-usage.jsonl
# The proxy writes the guardrail/firewall reason here on a block; a later
# always()-step posts it to the PR (the cascade itself fails closed).
POLICY_BLOCK: ${{ runner.temp }}/policy-block.json
Expand Down Expand Up @@ -709,6 +739,10 @@ runs:
echo '{}' > "$FACTS_FILE"
: > "$RUNNER_TEMP/proxy.out"
: > "$RUNNER_TEMP/proxy.err"
# Metering is opt-out; an empty CR_USAGE_FILE turns the tap off in the proxy.
CR_USAGE_FILE=""
if [ "$METER" = "true" ]; then CR_USAGE_FILE="$USAGE_FILE"; fi
export CR_USAGE_FILE
CR_FACTS_FILE="$FACTS_FILE" CR_POLICY_BLOCK_FILE="$POLICY_BLOCK" node "$PROXY" > "$RUNNER_TEMP/proxy.out" 2> "$RUNNER_TEMP/proxy.err" &
PROXY_PID=$!
trap 'kill "$PROXY_PID" 2>/dev/null || true' EXIT
Expand Down Expand Up @@ -787,9 +821,15 @@ runs:
# engine ignored the term. Redirections apply to whatever `timeout`
# runs — i.e. ocr's own stdout/stderr — so the JSON result and log
# end up in the same files as before.
# Engine tuning flags are only passed when set, so an empty input
# leaves the engine's own default in place rather than forcing a value.
ENGINE_FLAGS=""
[ -n "$CONCURRENCY" ] && ENGINE_FLAGS="$ENGINE_FLAGS --concurrency $CONCURRENCY"
[ -n "$MAX_TOOLS" ] && ENGINE_FLAGS="$ENGINE_FLAGS --max-tools $MAX_TOOLS"
# shellcheck disable=SC2086
timeout --kill-after=10s "${TIMEOUT_MIN}m" \
ocr review --from "$BASE" --to "$HEAD" \
--background "$(cat "$BACKGROUND")" --format json \
--background "$(cat "$BACKGROUND")" --format json $ENGINE_FLAGS \
> "$1" 2> "$REVIEW_LOG" || rc=$?
cat "$REVIEW_LOG"
# 124 = GNU timeout tripped (SIGTERM); 137 = 128+9, i.e. --kill-after
Expand Down Expand Up @@ -954,6 +994,27 @@ runs:
# >1 only in exhaustive mode; the summary comment notes it.
echo "passes=$PASSES" >> "$GITHUB_OUTPUT"

# Token accounting for this run — a separate always() step rather than the
# tail of the review shell above. That shell `exit 1`s on wall-clock timeout,
# unusable engine output, and policy blocks, and those are precisely the runs
# whose token spend you most want to see: the tokens were spent either way.
# The final cleanup step deletes cr-usage.jsonl, so accounting not printed
# here is gone for good. Soft-fail throughout — metering reports on a run's
# verdict, it never changes it.
- name: Token accounting
if: always()
shell: bash
env:
METER: ${{ inputs.meter }}
USAGE_FILE: ${{ runner.temp }}/cr-usage.jsonl
USAGE_SUMMARY: ${{ github.action_path }}/scripts/usage-summary.mjs
run: |
if [ "$METER" = "true" ] && [ -s "$USAGE_FILE" ]; then
echo "::group::Token accounting"
node "$USAGE_SUMMARY" "$USAGE_FILE" || true
echo "::endgroup::"
fi

# If a dashboard guardrail/firewall blocked the request, the cascade above
# failed closed and the normal review comment never runs. Surface the reason
# on the PR so the block is actionable, not just a red X with a log line.
Expand Down Expand Up @@ -1046,7 +1107,7 @@ runs:
try {
await github.rest.issues.createLabel({
...repo, name, color: '0e8a16',
description: 'Orca-Code-Review: promoted to the strong model tier',
description: 'OrcaCode Review: promoted to the strong model tier',
});
core.info(`Created repository label ${name}.`);
} catch (e2) {
Expand Down Expand Up @@ -1397,6 +1458,6 @@ runs:
"$RUNNER_TEMP/cr-facts.json" "$RUNNER_TEMP/proxy.out" "$RUNNER_TEMP/proxy.err" \
"$RUNNER_TEMP/policy-block.json" \
"$RUNNER_TEMP/pr.diff" "$RUNNER_TEMP/diff-guard.json" "$RUNNER_TEMP/prev-summary.md" \
"$RUNNER_TEMP/wallclock-timeout" \
"$RUNNER_TEMP/wallclock-timeout" "$RUNNER_TEMP/cr-usage.jsonl" \
"$RUNNER_TEMP/result.l1.json" "$RUNNER_TEMP/result.l2.json" \
"$RUNNER_TEMP/result-extra.l1.json" "$RUNNER_TEMP/result-extra.l2.json"
2 changes: 1 addition & 1 deletion recipes/code-review.dsl.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Orca-Code-Review — model-selection policy (the real recipe).
# OrcaCode Review — model-selection policy (the real recipe).
#
# Paste this into the per-workspace router `orcarouter/code-review`
# (Dashboard → Routers → code-review → DSL). The Action never names a model; it
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-result.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// Availability check for the Orca-Code-Review cascade.
// Availability check for the OrcaCode Review cascade.
//
// node check-result.mjs <result.json> <exit-code>
// exit 0 -> the engine produced a complete, usable review
Expand Down
2 changes: 1 addition & 1 deletion scripts/diff-guard.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// Oversized-diff guard for the Orca-Code-Review cascade.
// Oversized-diff guard for the OrcaCode Review cascade.
//
// Reviewing a huge diff is noise: the model truncates context, files get
// skipped, and the severity signal collapses — better to skip loudly and let
Expand Down
2 changes: 1 addition & 1 deletion scripts/exhaustive-merge.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// Exhaustive-mode merge for the Orca-Code-Review cascade.
// Exhaustive-mode merge for the OrcaCode Review cascade.
//
// node exhaustive-merge.mjs --base <a.json> --new <b.json> --out <merged.json>
//
Expand Down
Loading