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
55 changes: 55 additions & 0 deletions .fullsend/harness/review.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# .fullsend/harness/review.yaml
#
# Composes the upstream fullsend review harness (pinned to agents v0.38.0) and
# adds the local coderabbit-review skill as a complementary finding source.
# v0.38.0 review harness includes all built-in dimensions (correctness, protected
# paths, intent/coherence, challenger pass). This harness extends it to also
# ingest CodeRabbit findings via GitHub comments (S3 pattern) for synthesis.
#
# CodeRabbit findings are mapped with coderabbit-* category prefixes so the
# synthesizer can dedupe and attribute them properly. The built-in review
# dimensions remain authoritative for the verdict.
#
# Trigger: Inherits from base harness (no override needed - /fs-review works
# automatically for generated PRs per https://fullsend.sh/docs/guides/user/bugfix-workflow#slash-commands)
base: https://raw.githubusercontent.com/fullsend-ai/agents/48511880eaea5ef01f80b69ba4f228147611db33/harness/review.yaml#sha256=260bf4939f52ef173911d3d788f85bd62b26802f09711d8dcdcce8dbb24c5f04

allowed_remote_resources: [https://raw.githubusercontent.com/fullsend-ai/agents/]

# Extend base skills with repo-specific coderabbit-review skill
skills:
- skills/coderabbit-review

# Note: Required env vars should be set before running fullsend:
# export PRIOR_REVIEW_SHA=""
# export PRIOR_REVIEW_PROVENANCE="none"
# export REPO_FULL_NAME="openshift/ocm-agent-operator"
# export PR_NUMBER="123"
# export GH_TOKEN="$(gh auth token)"
#
# S2 (CLI mode) - CodeRabbit API key configuration:
# The CODERABBIT_API_KEY must be set as a CI secret in your pipeline
# (OpenShift Prow, Tekton, GitHub Actions, etc.) and exposed to the
# runner-side pre-script that generates coderabbit-findings.json.
# NEVER inject this secret into the sandbox - only the runner-side
# pre-script should have access to it.
#
# Example Tekton secret:
# apiVersion: v1
# kind: Secret
# metadata:
# name: coderabbit-api-key
# data:
# api-key: <base64-encoded-key>
#
# Example pre-script invocation in CI:
# export CODERABBIT_API_KEY="$(cat /path/to/secret)"
# export CODERABBIT_MODE="cli"
# .fullsend/skills/coderabbit-review/scripts/run-coderabbit.sh "$PR_NUMBER" \
# > /tmp/workspace/coderabbit-findings.json
#
# S2 (CLI mode) host_files mapping for CodeRabbit findings:
host_files:
- src: /tmp/workspace/coderabbit-findings.json
dest: /sandbox/workspace/coderabbit-findings.json
optional: true
107 changes: 107 additions & 0 deletions .fullsend/skills/coderabbit-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
name: coderabbit-review
description: >-
Ingest CodeRabbit findings for the current PR and map them into FullSend
review findings for synthesis. Use during /fs-review after the built-in
dimension sub-agents return, as a complementary (not replacement) source.
---

# CodeRabbit Review Skill

This skill adds CodeRabbit's AI review as an **extra finding source** for the
FullSend review agent. It does **not** replace `code-review` / `pr-review` —
those own protected-path checks, intent/coherence, the challenger pass, and the
JSON the post-script consumes. This skill only *gathers* CodeRabbit findings and
*maps* them into the same finding shape so the orchestrator can synthesise them.

> Naming: this skill is intentionally named `coderabbit-review` (a novel name).
> A repo skill named `code-review` or `pr-review` would be shadowed by the
> built-in and never invoked (Personal > Project precedence).

## When to use

- During `/fs-review` on a pull request, after the built-in review dimensions
have produced their findings, to fold in CodeRabbit's findings.
- Not for local pre-push (`code-review`) unless CodeRabbit results are already
available for the branch.

## Sources (in priority order)

The CodeRabbit CLI **cannot** run inside the review sandbox (no `coderabbit`
binary, no `curl`, `api.coderabbit.ai` is not in the network allowlist, and no
`CODERABBIT_API_KEY` is injected). So this skill never invokes the CLI directly.
Instead it reads findings that already exist:

1. **Injected file (S2, preferred for production):** if
`/sandbox/workspace/coderabbit-findings.json` exists, read it. A runner-side
pre-script produced it outside the sandbox; the API key never enters the
sandbox.

**CI Setup for S2 mode:**
- Store `CODERABBIT_API_KEY` as a secret in your CI system (OpenShift Prow,
Tekton, GitHub Actions, etc.)
- In your CI pipeline, before the review harness runs, execute the pre-script:
```bash
export CODERABBIT_API_KEY="$(cat /path/to/secret)"
export CODERABBIT_MODE="cli"
.fullsend/skills/coderabbit-review/scripts/run-coderabbit.sh "$PR_NUMBER" \
> /tmp/workspace/coderabbit-findings.json
```
- The `host_files` mapping in `review.yaml` copies this into the sandbox
- **Never** pass the API key into the sandbox environment

2. **GitHub ingest (S3, spike default):** otherwise run
`scripts/run-coderabbit.sh <PR_NUMBER>`, which uses the read-only `gh` client
already available to the review agent to pull CodeRabbit's existing PR review
comments. No extra network, binary, or secret required.

If neither source yields findings, emit a short informational note and continue.
**Do not fail the whole review because CodeRabbit was unavailable.**

## Step 1: Gather CodeRabbit findings

```bash
# Prefer the injected file; fall back to GitHub ingest.
if [ -f /sandbox/workspace/coderabbit-findings.json ]; then
cat /sandbox/workspace/coderabbit-findings.json
else
scripts/run-coderabbit.sh "$PR_NUMBER"
fi
```

The script emits a JSON array of `{source, path, line, body, url}` objects.

## Step 2: Map into FullSend review findings

For each CodeRabbit item, produce a finding object with these fields:

- `severity` — map CodeRabbit's severity to the review scale; default to a
low/`info` severity when CodeRabbit does not state one.
- `category` — prefix with `coderabbit-` (e.g. `coderabbit-correctness`,
`coderabbit-style`) so the challenger / synthesiser can dedupe against the
built-in dimensions.
- `file`, `line` — from the CodeRabbit comment (`null` for PR-level comments).
- `description` — CodeRabbit's finding text.
- `remediation` — CodeRabbit's suggested change, if present.
- `url` — link back to the CodeRabbit comment for human traceability.

## Step 3: Respect repo path exclusions

`.coderabbit.yaml` already excludes `boilerplate/**`, `hack/**`, `vendor/**`,
`**/testdata/**`, and generated `**/zz_generated.*.go`. Drop any CodeRabbit
finding whose `path` matches those globs so the review does not re-flag
generated or vendored code.

## Constraints

- **Do not** write `agent-result.json` / `review-result.json` — `pr-review` is
the sole producer of the schema-valid verdict. This skill only contributes
findings for synthesis.
- **Never** log, echo, or commit `CODERABBIT_API_KEY` or any token.
- Treat CodeRabbit findings as advisory input, not gate decisions — the
built-in dimensions and challenger remain authoritative.

## Related

- `scripts/run-coderabbit.sh` — GitHub ingest (S3) and the runner-only CLI (S2)
mode, with sandbox caveats documented inline.
133 changes: 133 additions & 0 deletions .fullsend/skills/coderabbit-review/scripts/run-coderabbit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#!/usr/bin/env bash
# Collect CodeRabbit findings for a PR and emit them as JSON on stdout.
#
# Modes:
# comment (default, S3) - read CodeRabbit's existing PR review comments via
# `gh`. Sandbox-safe: uses only the read-only GitHub
# client already available to the review agent. No
# CodeRabbit credentials and no egress to
# coderabbit.ai.
# cli (S2) - run the CodeRabbit CLI for a fresh review. RUNNER
# ONLY. This will FAIL inside the FullSend review
# sandbox: no `coderabbit` binary, no `curl`,
# `api.coderabbit.ai` is not in the network allowlist,
# and CODERABBIT_API_KEY is not injected. Intended to
# run in a trusted pre-script whose output is copied
# into the sandbox via `host_files`.
#
# Usage:
# scripts/run-coderabbit.sh <PR_NUMBER>
# CODERABBIT_MODE=cli scripts/run-coderabbit.sh <PR_NUMBER>
set -euo pipefail

# Emit empty JSON array and exit (for non-fatal failures in comment mode)
emit_empty() {
echo "[]"
exit 0
}

REPO="${CODERABBIT_REPO:-openshift/ocm-agent-operator}"
MODE="${CODERABBIT_MODE:-comment}"
BOT="${CODERABBIT_BOT:-coderabbitai}"

die() { echo "error: $*" >&2; exit 1; }

PR="${1:-}"
[ -n "$PR" ] || die "usage: run-coderabbit.sh <PR_NUMBER>"

case "$MODE" in
comment)
# Sandbox-compatible: uses only gh and node (both allowlisted).
# Failures are non-fatal - emit empty array if CodeRabbit data unavailable.
command -v gh >/dev/null || { echo "warning: gh CLI not found" >&2; emit_empty; }
command -v node >/dev/null || { echo "warning: node not found" >&2; emit_empty; }

# Get current PR head commit to filter stale comments.
head_sha=$(gh api "repos/${REPO}/pulls/${PR}" --jq '.head.sha' 2>/dev/null || true)
if [ -z "$head_sha" ]; then
echo "warning: failed to get PR head commit" >&2
emit_empty
fi

# Fetch review comments and issue comments (non-fatal on failure).
review_raw=$(gh api --paginate "repos/${REPO}/pulls/${PR}/comments" 2>/dev/null || echo "[]")
issue_raw=$(gh api --paginate "repos/${REPO}/issues/${PR}/comments" 2>/dev/null || echo "[]")

# Use node to filter and transform comments (sandbox-compatible).
node -e "
const bot = process.argv[1].toLowerCase();
const headSha = process.argv[2];
const reviewRaw = JSON.parse(process.argv[3]);
const issueRaw = JSON.parse(process.argv[4]);

// Filter review comments: exact bot login, current commit only.
const review = reviewRaw
.filter(c => c.user?.login?.toLowerCase() === bot)
.filter(c => c.commit_id === headSha)
.map(c => ({
source: 'coderabbit',
path: c.path,
line: c.line ?? c.original_line,
body: c.body,
url: c.html_url,
commit_id: c.commit_id
}));

// Filter issue comments: exact bot login.
const issue = issueRaw
.filter(c => c.user?.login?.toLowerCase() === bot)
.map(c => ({
source: 'coderabbit',
path: null,
line: null,
body: c.body,
url: c.html_url
}));

console.log(JSON.stringify([...review, ...issue]));
" "$BOT" "$head_sha" "$review_raw" "$issue_raw"
;;
cli)
# RUNNER ONLY - see header. Blocked inside the review sandbox.
# CODERABBIT_API_KEY must be set as a CI secret and exposed to this pre-script.
# See SKILL.md and review.yaml for CI setup instructions.
command -v coderabbit >/dev/null || die "coderabbit CLI not found (runner-only; use MODE=comment in-sandbox)"
command -v node >/dev/null || die "node not found (needed to normalize CLI output)"
[ -n "${CODERABBIT_API_KEY:-}" ] || die "CODERABBIT_API_KEY not set (must be configured as CI secret; see SKILL.md)"

# Get CLI output (try JSON format first, fall back to plain text parsing if needed).
# Adjust flags based on your installed CLI version's capabilities.
cli_output=$(coderabbit review --format json --pr "$PR" --api-key "$CODERABBIT_API_KEY" 2>/dev/null \
|| coderabbit review --plain --pr "$PR" --api-key "$CODERABBIT_API_KEY" 2>&1 \
|| die "coderabbit CLI failed")

# Normalize CLI output to {source, path, line, body, url}[] format.
node -e "
const raw = process.argv[1];
let findings = [];

try {
// Try parsing as JSON first
const parsed = JSON.parse(raw);
findings = (Array.isArray(parsed) ? parsed : [parsed])
.filter(f => f.file && f.line && f.message)
.map(f => ({
source: 'coderabbit',
path: f.file || f.path || null,
line: f.line || null,
body: f.message || f.body || f.comment || '',
url: f.url || null
}));
} catch (e) {
// Plain text fallback: emit empty array (CLI output not parseable)
console.error('warning: CLI output not in expected JSON format', e.message);
findings = [];
}

console.log(JSON.stringify(findings));
" "$cli_output"
;;
*)
die "unknown CODERABBIT_MODE: $MODE (expected 'comment' or 'cli')"
;;
esac
Loading