Skip to content
Draft
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
15 changes: 15 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "codejury",
"description": "Code Jury: independent AI code reviewers and one judge, from inside Claude Code.",
"owner": {
"name": "agentsdance",
"url": "https://github.com/agentsdance"
},
"plugins": [
{
"name": "codejury",
"source": "./plugin",
"description": "Run a Code Jury review of a pull request, and the multi-agent review loop skill."
}
]
}
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- Code Jury is now a Claude Code plugin: `claude plugin marketplace add agentsdance/codejury`, then
`claude plugin install codejury@codejury`. `/codejury:review <pr-url>` runs a review from a Claude
Code session without pushing unless asked, and the review-loop skill moved from `skill/SKILL.md`
to `plugin/skills/multi-agent-code-review/SKILL.md` (#115).
- The `agy` install hint now points at the official `https://antigravity.google/cli/install.sh`;
the npm package it named does not exist.
- Configure the model each agent runs with: `jury agents model <agent> <model>` saves a default in
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,22 @@ jury help review # complete review options
`jury <pr-url>` is shorthand for `jury review <pr-url>`. You can also run without installing:
`npx @agentsdance/codejury review <pr-url> --reviewer claude --push=false`.

### Claude Code plugin

Code Jury is also a [Claude Code plugin](https://code.claude.com/docs/en/plugins). Add this
repository as a plugin marketplace and install it:

```sh
claude plugin marketplace add agentsdance/codejury
claude plugin install codejury@codejury
```

In a session, `/codejury:review <pr-url>` runs `jury review` (or `npx @agentsdance/codejury`
when `jury` is not installed) with `--web=false` and, unless you pass `--push`, `--push=false`, then
summarizes the findings and verdicts. With no URL it reviews the current branch. The plugin also
ships the `multi-agent-code-review` skill for running the review loop by hand. Agent CLIs are
still installed and authenticated separately.

## Screenshots

A review of Code Jury PR #67, with Droid judging and Claude, Codex, and Grok reviewing.
Expand Down
13 changes: 13 additions & 0 deletions plugin/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "codejury",
"displayName": "Code Jury",
"description": "Review a pull request with independent AI reviewers and one judge, using the Code Jury CLI.",
"author": {
"name": "agentsdance",
"url": "https://github.com/agentsdance"
},
"homepage": "https://github.com/agentsdance/codejury#readme",
"repository": "https://github.com/agentsdance/codejury",
"license": "MIT",
"keywords": ["code-review", "pull-request", "agents", "codex", "claude"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: Review a pull request with several independent AI reviewers (codex,
You are the **main agent**. You write code, answer findings, and own the commit. Reviewers only read
and report — never let one edit files.

The CLI owns the mechanics; you own the judgement. See `DESIGN.md` for the split.
The CLI owns the mechanics; you own the judgement. See [DESIGN.md](https://github.com/agentsdance/codejury/blob/master/DESIGN.md) for the split.

## The loop

Expand All @@ -32,7 +32,7 @@ git worktree add "$WT" origin/<branch> --detach
```

Launch concurrently and in the background — the slowest sets the round's wall clock. Agent
invocations are configuration (`config.example.yaml`); only three things vary:
invocations are configuration ([config.example.yaml](https://github.com/agentsdance/codejury/blob/master/config.example.yaml)); only three things vary:

- **prompt delivery** — argv (`codex`) vs a file flag (`droid -f`)
- **cwd** — process cwd (`codex`) vs an explicit `--cwd` flag (`droid`)
Expand All @@ -41,7 +41,7 @@ invocations are configuration (`config.example.yaml`); only three things vary:

## The prompt

Four sections, in order. Template in `prompts/review-round.md`.
Four sections, in order. Template in [prompts/review-round.md](https://github.com/agentsdance/codejury/blob/master/prompts/review-round.md).

1. **What the change does** — a few bullets, so the reviewer does not infer intent.
2. **ALREADY SETTLED — do NOT re-report** — grows every round. Without it, each fresh reviewer
Expand Down Expand Up @@ -90,7 +90,7 @@ without reproducing anything, which is the whole point of the loop.

Reply to every finding — silence is not a resolution. Per finding:
**ACCEPTED** / **AGREE-BUT-DEFERRED** / **REJECTED**, each with reasoning, each ending in a direct
question. Template in `prompts/feedback.md`.
question. Template in [prompts/feedback.md](https://github.com/agentsdance/codejury/blob/master/prompts/feedback.md).

```bash
jury reply --dir "$WT" # one conversation per reviewer, concurrently
Expand Down
48 changes: 48 additions & 0 deletions plugin/skills/review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: review
description: Run a Code Jury review of a pull request — independent AI reviewers report findings, one judge triages and fixes them, and the loop repeats until the reviewers agree there is nothing new.
argument-hint: "[pr-url ...] [jury flags]"
disable-model-invocation: true
---

# Code Jury review

Run the Code Jury CLI on: `$ARGUMENTS`

## 1. Find the CLI

Use `jury` when `command -v jury` finds it. Otherwise run it without installing, as
`npx -y @agentsdance/codejury`. It needs Node.js 20 or newer; if neither works, tell the user to run
`npm install -g @agentsdance/codejury` and stop.

## 2. Build the command

Start from `jury review`, then add the arguments above unchanged:

- One or more PR URLs review those PRs. With none, Jury reviews the current Git checkout's branch
against its trunk; if the working directory is not a Git repository, ask the user for a PR URL.
- Add `--web=false` unless the arguments already set `--web`. The browser console otherwise keeps
the command running after the review finishes.
- Add `--push=false` unless the arguments already set `--push`. Jury pushes fixes by default; pushing
from inside this session should be the user's explicit choice. Say that you added it.

Do not choose reviewers, a judge or models for the user; Jury already applies the flags, the
repository's `jury.config.json` and the saved defaults (`jury agents`).

## 3. Run it

Print the command, then run it with the Bash tool in the background: a review runs several agent
CLIs for several rounds and can take many minutes. Before agents start, Jury prints the selected
judge and reviewers; report them. If it stops early (a missing or unauthenticated agent CLI, a
missing `gh` login for a GitHub URL), show the error and the fix it names; `jury agents` lists
which agent CLIs are installed.

## 4. Report

When it finishes, summarize from the output: the rounds run, each reviewer's findings with the
judge's verdict (accepted, deferred or rejected), the commits the judge made, and whether they were
pushed. Every run is saved locally: `jury runs` lists them, and `jury --web-only` reopens the console
to read the full prompts, reports and verdicts.

Do not fix the findings yourself, and do not edit the files Jury is working on while it runs:
the judge owns the fixes for this review.
46 changes: 46 additions & 0 deletions test/plugin.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { execFileSync } from "node:child_process";

const root = fileURLToPath(new URL("..", import.meta.url));
const json = file => JSON.parse(readFileSync(path.join(root, file), "utf8"));

test("the marketplace lists the plugin under the same name as its manifest", () => {
const market = json(".claude-plugin/marketplace.json");
assert.equal(market.name, "codejury");
assert.ok(market.owner?.name);
const [entry] = market.plugins;
assert.equal(market.plugins.length, 1);
assert.match(entry.source, /^\.\/[^.]/);
const manifest = json(path.join(entry.source, ".claude-plugin/plugin.json"));
assert.equal(entry.name, manifest.name);
assert.equal(manifest.license, json("package.json").license);
// Unpinned on purpose: installs follow the repository instead of a version to keep in sync.
assert.equal(manifest.version, undefined);
// A plugin bin/ goes on PATH and blocks claude.ai installs; the repository's bin/ must stay outside.
assert.ok(!existsSync(path.join(root, entry.source, "bin")));
});

test("every plugin skill has a name and description, and the review skill uses real flags", () => {
const skills = path.join(root, "plugin/skills");
const names = readdirSync(skills).sort();
assert.deepEqual(names, ["multi-agent-code-review", "review"]);
for (const name of names) {
const text = readFileSync(path.join(skills, name, "SKILL.md"), "utf8");
const front = text.match(/^---\n([\s\S]*?)\n---\n/)?.[1];
assert.ok(front, `${name}: frontmatter`);
assert.match(front, new RegExp(`^name: ${name}$`, "m"));
assert.match(front, /^description: \S/m);
// Installed plugins are copied without the rest of the repository.
assert.doesNotMatch(text, /`(DESIGN\.md|config\.example\.yaml|prompts\/[\w-]+\.md)`/, `${name}: repository-relative path`);
}
const review = readFileSync(path.join(skills, "review/SKILL.md"), "utf8");
assert.match(review, /^disable-model-invocation: true$/m);
const help = execFileSync(process.execPath, [path.join(root, "bin/jury.js"), "help", "review"], { encoding: "utf8" });
for (const flag of review.match(/--[a-z][\w-]*/g)) assert.ok(help.includes(`${flag} `), `jury review has no ${flag}`);
assert.equal(json("package.json").name, "@agentsdance/codejury");
assert.match(review, /npx -y @agentsdance\/codejury/);
});
Loading