diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json new file mode 100644 index 0000000..aa829b7 --- /dev/null +++ b/.claude-plugin/marketplace.json @@ -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." + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c7356d..af23c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ` 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 ` saves a default in diff --git a/README.md b/README.md index 04542ea..aee417e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,22 @@ jury help review # complete review options `jury ` is shorthand for `jury review `. You can also run without installing: `npx @agentsdance/codejury review --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 ` 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. diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json new file mode 100644 index 0000000..1438e67 --- /dev/null +++ b/plugin/.claude-plugin/plugin.json @@ -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"] +} diff --git a/skill/SKILL.md b/plugin/skills/multi-agent-code-review/SKILL.md similarity index 91% rename from skill/SKILL.md rename to plugin/skills/multi-agent-code-review/SKILL.md index a91db99..7dd18f9 100644 --- a/skill/SKILL.md +++ b/plugin/skills/multi-agent-code-review/SKILL.md @@ -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 @@ -32,7 +32,7 @@ git worktree add "$WT" origin/ --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`) @@ -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 @@ -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 diff --git a/plugin/skills/review/SKILL.md b/plugin/skills/review/SKILL.md new file mode 100644 index 0000000..4d9bc26 --- /dev/null +++ b/plugin/skills/review/SKILL.md @@ -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. diff --git a/test/plugin.test.js b/test/plugin.test.js new file mode 100644 index 0000000..580cbac --- /dev/null +++ b/test/plugin.test.js @@ -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/); +});