diff --git a/.opencode/plugins/review-guardrails.ts b/.opencode/plugins/review-guardrails.ts index abdd2c6..6b88df1 100644 --- a/.opencode/plugins/review-guardrails.ts +++ b/.opencode/plugins/review-guardrails.ts @@ -1,5 +1,5 @@ import type { Plugin } from "@opencode-ai/plugin" -import { readFile, writeFile } from "node:fs/promises" +import { mkdir, readFile, writeFile } from "node:fs/promises" import { createHash } from "node:crypto" import os from "node:os" import path from "node:path" @@ -130,6 +130,7 @@ export const ReviewGuardrails: Plugin = async ({ $, worktree }) => { throw new Error(`swarm budget exhausted: ${state.swarmInvocations} reviewer-* calls > cap ${swarmCap}`) } + await mkdir(path.dirname(stateFile), { recursive: true }) await writeFile(stateFile, `${JSON.stringify(state, null, 2)}\n`, "utf8") }) diff --git a/__tests__/review-guardrails.test.ts b/__tests__/review-guardrails.test.ts new file mode 100644 index 0000000..c88ae69 --- /dev/null +++ b/__tests__/review-guardrails.test.ts @@ -0,0 +1,52 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test" +import { mkdtemp, readFile, readdir, rm } from "node:fs/promises" +import os from "node:os" +import path from "node:path" +import { ReviewGuardrails } from "../.opencode/plugins/review-guardrails.ts" + +function fakeShell(branch: string) { + return () => ({ + cwd: () => ({ + text: async () => `${branch}\n`, + }), + }) +} + +describe("review-guardrails plugin", () => { + let tmpRoot: string + let worktree: string + let originalXdg: string | undefined + + beforeEach(async () => { + tmpRoot = await mkdtemp(path.join(os.tmpdir(), "review-guardrails-test-")) + worktree = path.join(tmpRoot, "worktree") + originalXdg = process.env.XDG_STATE_HOME + process.env.XDG_STATE_HOME = tmpRoot + }) + + afterEach(async () => { + if (originalXdg === undefined) { + delete process.env.XDG_STATE_HOME + } else { + process.env.XDG_STATE_HOME = originalXdg + } + + await rm(tmpRoot, { recursive: true, force: true }) + }) + + test("creates state directory before recording reviewer task budget", async () => { + const plugin = await ReviewGuardrails({ $: fakeShell("feature/review") as never, worktree } as never) + + await plugin["tool.execute.before"]?.( + { tool: "task" } as never, + { args: { subagent_type: "reviewer-quick" } } as never, + ) + + const repoDirs = await readdir(path.join(tmpRoot, "opencode", "review-state")) + expect(repoDirs).toHaveLength(1) + + const statePath = path.join(tmpRoot, "opencode", "review-state", repoDirs[0]!, "feature__review.json") + const state = JSON.parse(await readFile(statePath, "utf8")) as { swarmInvocations?: number } + expect(state.swarmInvocations).toBe(1) + }) +}) diff --git a/agents/reviewer-arch.md b/agents/reviewer-arch.md index ebc4049..5e2ea73 100644 --- a/agents/reviewer-arch.md +++ b/agents/reviewer-arch.md @@ -17,13 +17,23 @@ permission: webfetch: allow bash: "*": deny - "git diff*": allow - "git log*": allow - "git show*": allow - "git status*": allow - "git blame*": allow + "git *": allow "ls *": allow "wc *": allow + "cat *": allow + "head *": allow + "tail *": allow + "sed *": allow + "awk *": allow + "find *": allow + "grep *": allow + "rg *": allow + "jq *": allow + "tree *": allow + "pwd": allow + "pwd *": allow + "realpath *": allow + "dirname *": allow --- You are an architecture and design reviewer. You do NOT write or modify code - you only analyze and report. @@ -77,12 +87,12 @@ If you find nothing worth raising, say so explicitly. Don't invent issues to jus ## Tool boundaries -You have Read, Grep, Glob, git read commands (`git diff/log/show/status/blame`), `ls`, `wc`, and `webfetch` (only when you genuinely need external docs - never to "gather repo context" you can read locally). +You have Read, Grep, Glob, read-only shell commands for inspecting files, git commands for repository inspection, and `webfetch` (only when you genuinely need external docs - never to "gather repo context" you can read locally). Do NOT attempt: - `write`, `edit`, `patch` - you have no write tools, and fixing is the fixer's job, not yours. -- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, `find *`, `cat *`, `rg *`, etc. - all denied; use the dedicated Read/Grep/Glob tools instead). +- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, etc. - all denied; use the dedicated Read/Grep/Glob tools or read-only shell commands instead). - `task` - you cannot spawn other agents. - Any MCP tool (`sequential-thinking`) - out of scope for architectural review. diff --git a/agents/reviewer-e2e.md b/agents/reviewer-e2e.md index f4e6c83..0388417 100644 --- a/agents/reviewer-e2e.md +++ b/agents/reviewer-e2e.md @@ -16,14 +16,23 @@ permission: webfetch: allow bash: "*": deny - "git diff*": allow - "git log*": allow - "git show*": allow - "git status*": allow - "git blame*": allow + "git *": allow "ls *": allow "wc *": allow + "cat *": allow + "head *": allow + "tail *": allow + "sed *": allow + "awk *": allow "find *": allow + "grep *": allow + "rg *": allow + "jq *": allow + "tree *": allow + "pwd": allow + "pwd *": allow + "realpath *": allow + "dirname *": allow --- You are an end-to-end / integration reviewer. You do NOT write or modify code - you only analyze and report. @@ -78,12 +87,12 @@ If you find nothing worth raising, say so explicitly. ## Tool boundaries -You have Read, Grep, Glob, git read commands (`git diff/log/show/status/blame`), `ls`, `wc`, `find`, and `webfetch` (only when you genuinely need external docs). +You have Read, Grep, Glob, read-only shell commands for inspecting files, git commands for repository inspection, and `webfetch` (only when you genuinely need external docs). Do NOT attempt: - `write`, `edit`, `patch` - you have no write tools, and fixing is the fixer's job. -- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, `cat *`, `rg *`, `grep *` - all denied; use the dedicated Grep/Glob/Read tools). +- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *` - all denied; use the dedicated Grep/Glob/Read tools or read-only shell commands). - `task` - you cannot spawn other agents. - Any MCP tool (`sequential-thinking`) - out of scope for integration review. diff --git a/agents/reviewer-quick.md b/agents/reviewer-quick.md index 97ba60f..bb937c6 100644 --- a/agents/reviewer-quick.md +++ b/agents/reviewer-quick.md @@ -18,9 +18,23 @@ permission: webfetch: deny bash: "*": deny - "git diff*": allow - "git log*": allow - "git status*": allow + "git *": allow + "ls *": allow + "wc *": allow + "cat *": allow + "head *": allow + "tail *": allow + "sed *": allow + "awk *": allow + "find *": allow + "grep *": allow + "rg *": allow + "jq *": allow + "tree *": allow + "pwd": allow + "pwd *": allow + "realpath *": allow + "dirname *": allow --- You are a fast first-pass reviewer. You do NOT write or modify code - you only analyze and report. @@ -62,12 +76,13 @@ If the diff is clean, say "Nothing jumps out." in one line and stop. Don't pad. ## Tool boundaries -You have Read, Grep, Glob, and a small allowlist of git read commands (`git diff/log/status`). That is the entire surface you should touch. +You have Read, Grep, Glob, read-only shell commands for inspecting files, and git commands for repository inspection. +That is the entire surface you should touch. Do NOT attempt: - `write`, `edit`, `patch` - you have no write tools, and the orchestrator does not want you fixing anything. -- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, `find *`, `cat *`, etc. - all denied). +- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, etc. - all denied). - `task` - you cannot spawn other agents. - `webfetch` - denied by config. - Any MCP tool (`sequential-thinking`) - out of scope for review. diff --git a/agents/reviewer-reasoning.md b/agents/reviewer-reasoning.md index 329c20c..ae1cc50 100644 --- a/agents/reviewer-reasoning.md +++ b/agents/reviewer-reasoning.md @@ -17,13 +17,23 @@ permission: webfetch: allow bash: "*": deny - "git diff*": allow - "git log*": allow - "git show*": allow - "git status*": allow - "git blame*": allow + "git *": allow "ls *": allow "wc *": allow + "cat *": allow + "head *": allow + "tail *": allow + "sed *": allow + "awk *": allow + "find *": allow + "grep *": allow + "rg *": allow + "jq *": allow + "tree *": allow + "pwd": allow + "pwd *": allow + "realpath *": allow + "dirname *": allow --- You are a correctness reviewer. You do NOT write or modify code - you only analyze and report. @@ -77,12 +87,12 @@ If you find nothing worth raising, say so explicitly. False positives are worse ## Tool boundaries -You have Read, Grep, Glob, git read commands (`git diff/log/show/status/blame`), `ls`, `wc`, and `webfetch` (only when you genuinely need external docs). +You have Read, Grep, Glob, read-only shell commands for inspecting files, git commands for repository inspection, and `webfetch` (only when you genuinely need external docs). Do NOT attempt: - `write`, `edit`, `patch` - you have no write tools, and fixing is the fixer's job. -- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, `find *`, `cat *`, `rg *`, etc. - all denied; use Read/Grep/Glob). +- `bash` beyond the allowlist (`git push`, `gh *`, `npm *`, `bun *`, etc. - all denied; use Read/Grep/Glob or read-only shell commands). - `task` - you cannot spawn other agents. - Any MCP tool (`sequential-thinking`) - out of scope for correctness review. diff --git a/agents/reviewer.md b/agents/reviewer.md index c813637..4eda092 100644 --- a/agents/reviewer.md +++ b/agents/reviewer.md @@ -18,32 +18,27 @@ permission: bash: "*": deny # Git read + push (push gated by review-state plugin) - "git diff*": allow - "git log*": allow - "git show*": allow - "git status*": allow - "git blame*": allow - "git branch*": allow + "git *": allow "git push*": allow # GitHub PR + label management - "gh pr create*": allow - "gh pr edit*": allow - "gh pr view*": allow - "gh pr list*": allow - "gh pr ready*": allow - "gh label list*": allow - "gh label create*": allow + "gh *": allow # Filesystem read "ls *": allow "wc *": allow "cat *": allow "head *": allow "tail *": allow + "sed *": allow + "awk *": allow "find *": allow "grep *": allow "rg *": allow "jq *": allow "tree *": allow + "pwd": allow + "pwd *": allow + "realpath *": allow + "dirname *": allow # Verify gate - test / build / lint / typecheck runners. These may write # build artifacts and test outputs to disk but must NOT edit source. The # reviewer's tools section denies write/edit/patch, which is the real guard;