@permissionbrick/auto-review-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 permissionBRICK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,265 @@
1
+ # auto-review-mcp
2
+
3
+ A local MCP server that orchestrates a **continuous review loop between two coding agents**: one
4
+ **developer** and one **reviewer**. They both connect to this one long-running server at the same
5
+ time and hand work back and forth through it — no human in the middle of the loop.
6
+
7
+ ```
8
+ developer agent auto-review server reviewer agent
9
+ ─────────────── ────────────────── ──────────────
10
+ (works on a batch)
11
+ request_review(summary, ──────▶ stages all changes, diffs vs HEAD
12
+ commit_message) registers the batch, BLOCKS dev
13
+ wakes reviewer ──────────────────▶ get_next_review()
14
+ (returns summary + full diff)
15
+ reviews it…
16
+ ◀────────────────────────────────── submit_review(approved
17
+ approved → git commit (dev's msg) | changes_requested,
18
+ ◀────── {approved, commit_sha} changes → forward the issue issue, category)
19
+ or {changes_requested, issue}
20
+ (continue / fix & resubmit) get_next_review() …
21
+ ```
22
+
23
+ The protocol is taught entirely through the MCP **tool descriptions**, so the two agents
24
+ self-orchestrate from the tools alone.
25
+
26
+ ## How it works
27
+
28
+ - **One shared coordinator, two role-scoped endpoints**:
29
+ - Developer agent → `…/developer/mcp` (tools: `initialize_review_session`, `request_review`,
30
+ `signal_complete`, `workflow_status`)
31
+ - Reviewer agent → `…/reviewer/mcp` (tools: `get_next_review`, `submit_review`,
32
+ `workflow_status`)
33
+ - Role is fixed by the endpoint, so there is no role/agent-id parameter on the calls.
34
+ - **The developer names the repo at runtime.** As its first step the developer agent calls
35
+ `initialize_review_session` with the absolute path of the repo it's editing. The coordinator
36
+ remembers it for its lifetime (or until called again). So there's nothing repo-specific to bake
37
+ into config. (You can still pre-set it with `--repo`/`AUTO_REVIEW_REPO` if you prefer.)
38
+ - **Two ways to attach** (see [Connect the two agents](#connect-the-two-agents)):
39
+ - **stdio (recommended)** — each agent's `.mcp.json` runs `npx -y @permissionbrick/auto-review-mcp
40
+ --role …`, a thin proxy. The first proxy auto-starts a single shared background coordinator; both
41
+ proxies forward to it. No server to start by hand. The shared state lives in the coordinator, not
42
+ the agents.
43
+ - **HTTP** — you start the coordinator yourself (`auto-review-server`) and point each agent at its
44
+ URL (good for remote agents or sharing one coordinator across machines).
45
+ - **Blocking handoffs.** `request_review` blocks until the reviewer rules; `get_next_review`
46
+ blocks until a batch arrives. Waiting is **event-driven**, so the actual handoff is instant. A
47
+ call only holds the connection open up to a bounded **poll window** (`AUTO_REVIEW_POLL_SECONDS`),
48
+ then returns `keep_waiting`; the agent immediately calls again. This makes the wait effectively
49
+ unbounded while surviving connection drops and client timeouts. See
50
+ [Tuning how long a call waits](#tuning-how-long-a-call-waits).
51
+ - **The server owns the commits.** On approval the server runs `git add -A` + `git commit` with
52
+ the developer's commit message (plus a `Reviewed-by: auto-review` trailer). HEAD advances, so
53
+ each review's diff is naturally just the new batch. The developer never commits.
54
+ - **One batch at a time**, identified by a `batch_id`. The diff shown to the reviewer is the full
55
+ unified diff of the working tree vs HEAD (new/deleted files included).
56
+
57
+ ## Install
58
+
59
+ You don't need to install anything by hand — the recommended setup runs the server on demand with
60
+ `npx` (see [Connect the two agents](#connect-the-two-agents)). Prefer global CLIs? Install them with:
61
+
62
+ ```bash
63
+ npm install -g @permissionbrick/auto-review-mcp
64
+ # bins: auto-review-mcp (stdio proxy) · auto-review-server (HTTP coordinator) · auto-review-cli (poller)
65
+ ```
66
+
67
+ ### Build from source
68
+
69
+ ```bash
70
+ git clone https://github.com/permissionBRICK/auto-review.git
71
+ cd auto-review
72
+ npm install # installs deps and builds (via the prepare script)
73
+ ```
74
+
75
+ ## Connect the two agents
76
+
77
+ Launch **two Claude Code instances** — one developer, one reviewer. Both must point at the **same
78
+ git repository** the developer edits (it must be a git repo, not this server's directory).
79
+
80
+ ### Option A — `node`/stdio (recommended)
81
+
82
+ Each agent's `.mcp.json` runs the stdio proxy via `npx`; the first one auto-starts the shared
83
+ coordinator. Nothing to install or run by hand, and **no repo path in config** — the developer agent
84
+ declares it at runtime via `initialize_review_session`. Sample configs are in
85
+ [`configs/`](configs/):
86
+
87
+ ```jsonc
88
+ // configs/developer.mcp.json (reviewer.mcp.json is identical with --role reviewer)
89
+ {
90
+ "mcpServers": {
91
+ "auto-review": {
92
+ "command": "npx",
93
+ "args": ["-y", "@permissionbrick/auto-review-mcp", "--role", "developer"],
94
+ "env": { "AUTO_REVIEW_POLL_SECONDS": "240" },
95
+ "timeout": 1800000
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ The shipped configs use a **240 s** poll window (`AUTO_REVIEW_POLL_SECONDS`) — the practical max
102
+ per single hold (see [Tuning how long a call waits](#tuning-how-long-a-call-waits)). Idle waiters
103
+ re-poll every ~240 s; handoffs are still instant. For long *uninterrupted* waits, use the poller
104
+ CLI, which loops over these windows.
105
+
106
+ ```bash
107
+ # Developer instance (point at the config above, e.g. configs/developer.mcp.json)
108
+ claude --mcp-config configs/developer.mcp.json --strict-mcp-config
109
+ # Reviewer instance (separate terminal)
110
+ claude --mcp-config configs/reviewer.mcp.json --strict-mcp-config
111
+ ```
112
+
113
+ `--strict-mcp-config` makes each instance load *only* that file. No launch-time env is needed — the
114
+ config's `timeout` field raises Claude Code's per-call cap by itself.
115
+
116
+ Proxy env / args (also accepted as `--flags` in `args`): `AUTO_REVIEW_ROLE`, `AUTO_REVIEW_PORT`
117
+ (default `8765`), `AUTO_REVIEW_HOST` (default `127.0.0.1`), `AUTO_REVIEW_POLL_SECONDS` (default
118
+ `40` if unset; the shipped config sets `240`), `AUTO_REVIEW_MAX_DIFF_BYTES` (default `200000`), and
119
+ optionally `AUTO_REVIEW_REPO` to pre-set the repo instead of using `initialize_review_session`. The
120
+ coordinator logs to `${TMPDIR}/auto-review-coordinator-<port>.log` and keeps running after the
121
+ agents exit (kill it via that port if you want a clean reset — also needed to apply a changed poll
122
+ window, see below).
123
+
124
+ ### Option B — HTTP (manual start / remote agents)
125
+
126
+ Start the coordinator yourself and point each agent at its URL (configs:
127
+ `configs/*.http.mcp.json`):
128
+
129
+ ```bash
130
+ # start the shared HTTP coordinator (no install needed via npx; or use the global bin)
131
+ npx -y -p @permissionbrick/auto-review-mcp auto-review-server --port 8765 --poll-seconds 240 # keep ≤ ~270; optional: --repo /path
132
+ claude --mcp-config configs/developer.http.mcp.json --strict-mcp-config
133
+ claude --mcp-config configs/reviewer.http.mcp.json --strict-mcp-config
134
+ ```
135
+
136
+ The HTTP configs carry `"timeout": 3600000`, so no launch env is needed here either. Server flags
137
+ (env var in parens): `--repo` (`AUTO_REVIEW_REPO`, optional — else the developer sets it via
138
+ `initialize_review_session`), `--port` (`AUTO_REVIEW_PORT`, `8765`), `--host` (`AUTO_REVIEW_HOST`,
139
+ `0.0.0.0` — reachable as `agent-vm.mshome.net`), `--poll-seconds` (`AUTO_REVIEW_POLL_SECONDS`,
140
+ `1500`), `--max-diff-bytes` (`AUTO_REVIEW_MAX_DIFF_BYTES`, `200000`). `GET /healthz` returns a JSON
141
+ snapshot of the workflow.
142
+
143
+ ### Tuning how long a call waits
144
+
145
+ `get_next_review` and `request_review` each block in **one** call for up to the **poll window**,
146
+ then return `keep_waiting` and the agent re-calls. Two limits bound that single call:
147
+
148
+ | Knob | Where | Effect |
149
+ |------|-------|--------|
150
+ | `AUTO_REVIEW_POLL_SECONDS` | config `env` (stdio) / `--poll-seconds` (HTTP) | how long the server holds before `keep_waiting`. **Must stay under ~270 s** (see below). |
151
+ | `timeout` (ms) | per-server field in `.mcp.json` | Claude Code's per-call cap (default **60000**). Must be **≥ the poll window**. Overrides `MCP_TOOL_TIMEOUT`; not extended by progress. |
152
+
153
+ Handoffs themselves are event-driven (instant) regardless — the poll window only sets how long an
154
+ *idle* waiter holds before re-polling.
155
+
156
+ > ⚠️ **Hard ~5-minute ceiling on a single HTTP hold.** Every MCP client here is Node-based (the
157
+ > stdio proxy and the poller CLI both use Node's `fetch`/undici, whose default `headersTimeout` is
158
+ > **300 s**), so a long-poll held longer than ~5 min dies as `fetch failed`. Worse, the abandoned
159
+ > request leaves a server-side waiter that could swallow a batch. **So keep
160
+ > `AUTO_REVIEW_POLL_SECONDS` ≤ ~270 s** (the shipped configs use **240**). You cannot get a longer
161
+ > *single* hold by raising the window — for longer **effective** waits, the poller CLI loops over
162
+ > many ≤270 s polls (see [the Codex section](#avoiding-re-polls-on-codex-the-shell-poll-command)),
163
+ > and the CLI never fails fatally on a hiccup.
164
+
165
+ Two more caveats:
166
+
167
+ - **The coordinator is a singleton.** Changing the poll window only takes effect on a fresh
168
+ coordinator — kill the running one (`fuser -k <port>/tcp`, or `kill` the pid on that port) so the
169
+ next agent restarts it with the new value. Both agents should use the same window.
170
+ - Across machines (HTTP transport), a dropped connection isn't retried until the next poll; prefer
171
+ a shorter window there too.
172
+
173
+ ### Using Codex instead of Claude Code
174
+
175
+ Codex (OpenAI) works as the client, but it's the opposite of Claude Code on timeouts:
176
+
177
+ - It reads `~/.codex/config.toml`, **not** `.mcp.json`, and **ignores the `timeout` field**.
178
+ - All Codex harnesses (CLI, VS Code extension, Windows app) enforce a **hard ~120 s
179
+ `awaiting tools/call` deadline** that `tool_timeout_sec` does **not** reliably raise
180
+ ([openai/codex#13831](https://github.com/openai/codex/issues/13831)). So unlike Claude Code, a
181
+ single blocking call **cannot exceed ~120 s** on Codex.
182
+
183
+ Therefore, do the **reverse** of the Claude tuning: keep the poll window safely **under** 120 s so
184
+ the coordinator returns `keep_waiting` before Codex gives up, and the agent re-polls. `90` is a good
185
+ value. See [`configs/codex.config.toml`](configs/codex.config.toml):
186
+
187
+ ```toml
188
+ [mcp_servers.auto-review]
189
+ command = "npx"
190
+ args = ["-y", "@permissionbrick/auto-review-mcp", "--role", "developer"] # reviewer in its own config
191
+ env = { AUTO_REVIEW_POLL_SECONDS = "90" } # MUST be < Codex's ~120 s ceiling
192
+ startup_timeout_sec = 30
193
+ tool_timeout_sec = 110
194
+ ```
195
+
196
+ Handoffs are still instant — the window only sets how often an idle waiter re-polls. The
197
+ singleton-coordinator rule still applies: both agents need the same `AUTO_REVIEW_POLL_SECONDS`, and
198
+ you must kill any running coordinator for a changed window to take effect.
199
+
200
+ #### Avoiding re-polls on Codex: the shell poll command
201
+
202
+ Re-polling under 120 s works but is chatty. To get long, quiet waits anyway, there's a built-in
203
+ escape hatch: an MCP tool call is capped at ~120 s, but a **shell command isn't**. When
204
+ `get_next_review` / `request_review` is killed by Codex's `timed out awaiting tools/call` error, the
205
+ agent never receives a result — so the tool **descriptions** (not the responses) tell it to fall
206
+ back to a blocking shell command that waits without the limit and prints the same JSON:
207
+
208
+ ```
209
+ # the tool description already prints the exact command (absolute paths, ready to paste); by name:
210
+ npx -y -p @permissionbrick/auto-review-mcp auto-review-cli next-review --port 8765 --timeout 1500 # reviewer
211
+ npx -y -p @permissionbrick/auto-review-mcp auto-review-cli await-verdict --port 8765 --timeout 1500 # developer
212
+ ```
213
+
214
+ These hit a plain-HTTP long-poll endpoint on the coordinator, hide `keep_waiting` internally, and
215
+ block until there's a real result (give the command a long *command* timeout, ~25 min). The agent
216
+ should use them **only after an actual timeout error** — a normal `keep_waiting` return just means
217
+ "call the tool again", and running the shell command then would be a redundant double-wait.
218
+
219
+ With this fallback you can even set a **long** poll window (so the MCP call carries the fast case
220
+ within 120 s and the shell command carries longer waits). Verify it with `npm run demo:cli`.
221
+
222
+ ## Suggested prompts
223
+
224
+ The tools are self-describing, so the prompts can be short.
225
+
226
+ **Developer:**
227
+ > You are the *developer*. First call `initialize_review_session` (the `auto-review` MCP) with the
228
+ > absolute path of this repo. Then implement <the task> in small, self-contained batches. After each
229
+ > batch, call `request_review` with a clear summary and commit message, and follow whatever it
230
+ > returns: on `changes_requested`, fix the issue and resubmit; on `approved`, continue with the next
231
+ > batch. Do not run `git commit` yourself. When the whole task is done and the last batch is
232
+ > approved, call `signal_complete`.
233
+
234
+ **Reviewer:**
235
+ > You are the *reviewer*. Repeatedly call `get_next_review` (the `auto-review` MCP) to receive each
236
+ > batch (summary + full diff vs HEAD), review it against <the task/spec> and for code quality, then
237
+ > call `submit_review` — `approved`, or `changes_requested` with a clear `issue` and `category`
238
+ > (`spec`/`code`). Keep looping until you get `workflow_complete`.
239
+
240
+ On **Codex**, also allow shell commands and add: *"If a `get_next_review`/`request_review` call ever
241
+ fails with `timed out awaiting tools/call`, follow that tool's CLIENT-TIMEOUT FALLBACK instructions
242
+ — run the shell command it names to wait, then continue."*
243
+
244
+ ## Verify
245
+
246
+ End-to-end tests that spin up a throwaway git repo and drive the full loop (keep_waiting → submit →
247
+ review → changes_requested → fix → approve → commit → complete):
248
+
249
+ ```bash
250
+ npm run build
251
+ npm run demo # HTTP path: two SDK clients against a running server
252
+ npm run demo:stdio # node/stdio path: two spawned proxies + auto-started coordinator
253
+ npm run demo:cli # shell poll-command path: MCP for instant ops + cli.js for the blocking wait
254
+ ```
255
+
256
+ Each prints a checklist and exits non-zero if anything fails.
257
+
258
+ ## Notes & limitations (v1)
259
+
260
+ - **State is in-memory.** Restarting the server resets the loop (no batch is mid-flight unless an
261
+ agent is actively blocked). There is no persistence yet.
262
+ - **One developer + one reviewer.** A single batch flows at a time; extra connections share the
263
+ same state.
264
+ - **Commit hooks are respected.** If a pre-commit hook rejects an approved batch, the reviewer gets
265
+ an error and the batch stays open to retry or send back as `changes_requested`.
package/dist/cli.js ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Thin blocking poller for the auto-review coordinator — the shell command an
4
+ * agent runs when its MCP client kills a blocking tool call with a hard timeout
5
+ * (e.g. Codex's ~120s "timed out awaiting tools/call"). A shell process is NOT
6
+ * bound by that MCP per-call limit, so this can wait far longer.
7
+ *
8
+ * It hits a plain-HTTP long-poll endpoint on the coordinator and hides
9
+ * `keep_waiting` internally: it blocks until there is a REAL result, then prints
10
+ * that JSON to stdout and exits. The agent reads stdout and acts on it.
11
+ *
12
+ * next-review wait for the next batch to review (reviewer)
13
+ * await-verdict wait for the verdict of the batch you already submitted (developer)
14
+ * status print the current workflow status once and exit
15
+ *
16
+ * Flags: --port (8765) --host (127.0.0.1) --timeout <seconds> (1500) and the
17
+ * usual --poll-seconds / --max-diff-bytes (only used if it has to start the
18
+ * coordinator). On reaching --timeout it prints {"status":"keep_waiting"} and
19
+ * exits 0, so the agent can simply run it again.
20
+ */
21
+ import { setTimeout as sleep } from "node:timers/promises";
22
+ import { ensureCoordinator } from "./launch.js";
23
+ const ENDPOINTS = {
24
+ "next-review": { path: "/reviewer/next-review" },
25
+ "await-verdict": { path: "/developer/await-verdict" },
26
+ };
27
+ function out(obj) {
28
+ process.stdout.write(`${JSON.stringify(obj, null, 2)}\n`);
29
+ }
30
+ function errlog(msg) {
31
+ process.stderr.write(`[auto-review cli] ${msg}\n`);
32
+ }
33
+ function parse(argv) {
34
+ let sub = "";
35
+ const opts = new Map();
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const a = argv[i];
38
+ if (!a.startsWith("--")) {
39
+ if (!sub)
40
+ sub = a;
41
+ continue;
42
+ }
43
+ const key = a.slice(2);
44
+ const eq = key.indexOf("=");
45
+ if (eq >= 0)
46
+ opts.set(key.slice(0, eq), key.slice(eq + 1));
47
+ else if (i + 1 < argv.length && !argv[i + 1].startsWith("--"))
48
+ opts.set(key, argv[++i]);
49
+ else
50
+ opts.set(key, "true");
51
+ }
52
+ const get = (k, env, dflt) => opts.get(k) ?? process.env[env] ?? dflt;
53
+ return {
54
+ sub,
55
+ host: get("host", "AUTO_REVIEW_HOST", "127.0.0.1"),
56
+ port: Number(get("port", "AUTO_REVIEW_PORT", "8765")),
57
+ timeoutSeconds: Number(get("timeout", "AUTO_REVIEW_CLI_TIMEOUT", "3600")),
58
+ pollSeconds: Number(get("poll-seconds", "AUTO_REVIEW_POLL_SECONDS", "40")),
59
+ maxDiffBytes: Number(get("max-diff-bytes", "AUTO_REVIEW_MAX_DIFF_BYTES", "200000")),
60
+ };
61
+ }
62
+ async function main() {
63
+ const cfg = parse(process.argv.slice(2));
64
+ const base = `http://${cfg.host}:${cfg.port}`;
65
+ if (cfg.sub === "status") {
66
+ await ensureCoordinator({ ...cfg, log: errlog });
67
+ const r = await fetch(`${base}/healthz`);
68
+ const body = (await r.json());
69
+ out(body.status ?? body);
70
+ return;
71
+ }
72
+ const endpoint = ENDPOINTS[cfg.sub];
73
+ if (!endpoint) {
74
+ errlog(`unknown command '${cfg.sub}'. Use: next-review | await-verdict | status`);
75
+ process.exit(2);
76
+ }
77
+ const target = { ...cfg, log: errlog };
78
+ await ensureCoordinator(target);
79
+ const url = `${base}${endpoint.path}`;
80
+ const deadline = Date.now() + cfg.timeoutSeconds * 1000;
81
+ // Cap each HTTP request well under Node/undici's default 300s headersTimeout,
82
+ // which otherwise kills a longer-held long-poll with "fetch failed". The
83
+ // coordinator returns keep_waiting within its (smaller) poll window, so this
84
+ // cap normally never bites; it just bounds a single request for safety.
85
+ const PER_REQUEST_MS = 270_000;
86
+ // Loop until a real result or our own --timeout budget. Transient errors
87
+ // (undici timeout, a coordinator restart, a network blip) are NEVER fatal
88
+ // mid-wait — we self-heal and keep polling, so one shell command can wait
89
+ // for the whole budget (default 1h) without ever returning "fetch failed".
90
+ for (;;) {
91
+ const remaining = deadline - Date.now();
92
+ if (remaining <= 0) {
93
+ out({ status: "keep_waiting", message: "Time budget elapsed; run this command again to keep waiting." });
94
+ return;
95
+ }
96
+ let body;
97
+ try {
98
+ const res = await fetch(url, { signal: AbortSignal.timeout(Math.min(remaining, PER_REQUEST_MS)) });
99
+ if (!res.ok) {
100
+ errlog(`coordinator returned HTTP ${res.status}; retrying`);
101
+ await sleep(2000);
102
+ continue;
103
+ }
104
+ body = (await res.json());
105
+ }
106
+ catch (e) {
107
+ // Per-request cap reached, undici headers/body timeout, or a blip. Keep
108
+ // looping; re-ensure the coordinator in case it died, then retry.
109
+ if (deadline - Date.now() > 1500) {
110
+ errlog(`poll hiccup (${e?.name ?? e}); retrying`);
111
+ await ensureCoordinator({ ...target, log: () => { } }).catch(() => { });
112
+ await sleep(1000);
113
+ }
114
+ continue;
115
+ }
116
+ if (body.status === "keep_waiting")
117
+ continue; // coordinator window elapsed → re-poll
118
+ out(body);
119
+ return;
120
+ }
121
+ }
122
+ main().catch((e) => {
123
+ errlog(`FATAL: ${e?.stack ?? String(e)}`);
124
+ process.exit(1);
125
+ });
126
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Tool descriptions. These are the *contract*: they must teach the whole
3
+ * auto-review protocol so the two agents can self-orchestrate from the tool
4
+ * descriptions alone, with no extra prompting.
5
+ */
6
+ export const INITIALIZE_SESSION_DESC = `Start the auto-review session by telling the server which git repository this workflow operates on. CALL THIS ONCE, FIRST — before request_review.
7
+
8
+ Pass 'repo_path': the ABSOLUTE path to the root of the git repository you are working in (normally your current working directory). The server performs ALL git operations there — it diffs your working tree against HEAD to show the reviewer, and commits each approved batch — so it MUST be the exact checkout you are editing.
9
+
10
+ The repository is remembered for the lifetime of the server, or until you call this again (which starts a fresh session). Returns {"status":"ok","repo":...,"head":...} on success, or {"status":"error","message":...} if the path is not a git repository.`;
11
+ export const REQUEST_REVIEW_DESC = `Submit a completed batch of work to the reviewer and BLOCK until the reviewer responds. (Call initialize_review_session once first; if you haven't, this returns {"status":"not_initialized"}.)
12
+
13
+ WHEN TO CALL: every time you finish a self-contained, coherent chunk of the task — a logical, commit-sized unit (a part of a feature, an enclosed subset of the task). Work in small, reviewable increments rather than one giant change.
14
+
15
+ WHAT TO PASS:
16
+ - summary: what you changed and WHY, in enough detail for a reviewer to judge it against the task/spec.
17
+ - commit_message: a good commit message for this batch (this exact message is used for the commit on approval).
18
+
19
+ IMPORTANT: Do NOT stage or commit anything yourself. The server automatically snapshots ALL current changes in the working tree (it runs 'git add -A') and computes the diff against HEAD. The server commits the batch for you, and only after the reviewer approves it. Never run 'git commit'.
20
+
21
+ This call BLOCKS until the review is finished. It returns exactly one of:
22
+ - {"status":"approved","commit_sha":...} → the reviewer approved and the server has committed this batch. Move on to the NEXT batch of work.
23
+ - {"status":"changes_requested","issue":...,"category":"spec"|"code"} → the reviewer found a problem. 'category' is "spec" (it does not meet the requirement) or "code" (a code-level defect/bug/quality issue). Read 'issue', fix it, then call request_review again with an updated summary/commit_message.
24
+ - {"status":"keep_waiting"} → the review is still in progress; this returned only to keep the connection alive. IMMEDIATELY call request_review again with the SAME arguments to keep waiting. (Re-submitting unchanged work does not create a duplicate review.)
25
+ - {"status":"nothing_to_review"} → there are no changes versus HEAD. Do some work first, then call again.
26
+
27
+ Only one batch is reviewed at a time.`;
28
+ export const SIGNAL_COMPLETE_DESC = `Signal that the ENTIRE task is finished and there is nothing left to implement (typically right after your final batch was approved).
29
+
30
+ This tells the reviewer the workflow is over: the reviewer's next get_next_review will return {"status":"workflow_complete"} so it can stop waiting. Optionally pass a closing 'note'. Do not call this while you still have batches to submit.`;
31
+ export const GET_NEXT_REVIEW_DESC = `Ask for the next batch of work to review and BLOCK until one is available.
32
+
33
+ WHEN TO CALL: FIRST, as soon as you start (you are the reviewer and wait for the developer). Then again every time after you submit a verdict, to wait for the next batch.
34
+
35
+ This call BLOCKS. It returns exactly one of:
36
+ - {"status":"review_ready","batch_id":...,"summary":...,"commit_message":...,"diff":...,"diff_stat":...} → a developer submitted a batch. 'summary' is the developer's description, 'commit_message' their proposed message, 'diff' is the FULL unified diff of all current changes versus HEAD (i.e. exactly this batch), 'diff_stat' is the file/line summary. Review it against BOTH the task/spec AND code quality, then call submit_review with this same batch_id.
37
+ - {"status":"keep_waiting"} → nothing is ready yet; this returned only to keep the connection alive. IMMEDIATELY call get_next_review again to keep waiting.
38
+ - {"status":"workflow_complete"} → the developer signalled the whole task is done. Stop; there is nothing left to review.
39
+
40
+ You review one batch at a time. After you receive a "review_ready" batch you must eventually call submit_review for that batch_id before any further work can flow.`;
41
+ export const SUBMIT_REVIEW_DESC = `Record your verdict on the batch you are currently reviewing and unblock the developer.
42
+
43
+ WHAT TO PASS:
44
+ - batch_id: the id you received from get_next_review.
45
+ - verdict: "approved" or "changes_requested".
46
+ - "approved" → the batch meets the requirement and the code is acceptable. The server IMMEDIATELY commits the batch (using the developer's commit_message) and reports the new commit to the developer. Do not commit anything yourself.
47
+ - "changes_requested" → something is wrong. You MUST also pass:
48
+ - issue: a clear, actionable description of what is wrong and what to change.
49
+ - category: "spec" (it fails to meet the task/requirement) or "code" (a code-level defect such as a bug, regression, or quality problem).
50
+ The developer receives this, fixes it, and resubmits.
51
+
52
+ After submitting, call get_next_review again to wait for the next batch.`;
53
+ export const WORKFLOW_STATUS_DESC = `Read-only. Return the current state of the auto-review workflow: the phase (idle / awaiting_review / reviewing), a summary of the batch currently under review (if any), the last verdict, the number of completed batches, and the repo path. Does not block and never changes anything.`;
54
+ /**
55
+ * Appended to a blocking tool's description so the agent can recover when its
56
+ * MCP client kills the call with a hard timeout (e.g. Codex's ~120s
57
+ * "timed out awaiting tools/call"). On that timeout there is NO tool response,
58
+ * so this guidance must live in the static description — not in any result.
59
+ */
60
+ export function timeoutFallbackNote(pollCommand, waits) {
61
+ return `CLIENT-TIMEOUT FALLBACK — only when THIS call FAILS with a client-side timeout error (e.g. "timed out awaiting tools/call after 120s"): your MCP client gave up before the wait finished, so you never received a result. Do NOT just retry the tool (it will time out again the same way). Instead run this shell command to wait for ${waits} without the per-call MCP limit — it blocks until there is a real result and then prints the same JSON:
62
+
63
+ ${pollCommand}
64
+
65
+ RUN IT SO IT ACTUALLY BLOCKS — it must stay in the foreground until it returns; do not let it drop into a background task and do not poll it with repeated short waits.
66
+ - On Codex: if exec_command returns a session_id because the process is still running (exec_command may yield a session after ~30s even when you pass a longer timeout), do NOT poll repeatedly with short waits. Immediately call write_stdin on that session with chars:"" and a long yield_time_ms (~1500000 ms, i.e. ~25 min) so you block until the command produces its result; if it yields again before finishing, repeat the blocking write_stdin.
67
+ - On other harnesses: just give the command a long command/exec timeout (~25 min) and let it block.
68
+
69
+ Only use this on an actual timeout error. If the tool RETURNS normally — including a "keep_waiting" result — do NOT run the shell command; just call the tool again as usual. Running it after a normal return would be a redundant double-wait.`;
70
+ }
71
+ //# sourceMappingURL=descriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"descriptions.js","sourceRoot":"","sources":["../src/descriptions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;6PAIsN,CAAC;AAE9P,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;sCAgBG,CAAC;AAEvC,MAAM,CAAC,MAAM,oBAAoB,GAAG;;gPAE4M,CAAC;AAEjP,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;oKASgI,CAAC;AAErK,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;yEAWuC,CAAC;AAE1E,MAAM,CAAC,MAAM,oBAAoB,GAAG,2RAA2R,CAAC;AAEhU;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAmB,EAAE,KAAa;IACpE,OAAO,0UAA0U,KAAK;;MAElV,WAAW;;;;;;iPAMgO,CAAC;AAClP,CAAC"}
package/dist/git.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Thin async wrapper around the `git` CLI for the project repo under review.
3
+ *
4
+ * All commands use `git -C <dir> ...` and `execFile` (no shell) so paths and
5
+ * messages with spaces or special characters are safe.
6
+ */
7
+ import { execFile } from "node:child_process";
8
+ import { createHash } from "node:crypto";
9
+ import { promisify } from "node:util";
10
+ const execFileAsync = promisify(execFile);
11
+ export class GitError extends Error {
12
+ stderr;
13
+ constructor(message, stderr) {
14
+ super(message);
15
+ this.stderr = stderr;
16
+ this.name = "GitError";
17
+ }
18
+ }
19
+ export class GitRepo {
20
+ dir;
21
+ constructor(dir) {
22
+ this.dir = dir;
23
+ }
24
+ async git(args) {
25
+ try {
26
+ const { stdout, stderr } = await execFileAsync("git", ["-C", this.dir, ...args], {
27
+ maxBuffer: 64 * 1024 * 1024, // 64MB — big diffs are fine
28
+ encoding: "utf8",
29
+ });
30
+ return { stdout, stderr };
31
+ }
32
+ catch (err) {
33
+ const e = err;
34
+ throw new GitError(`git ${args.join(" ")} failed: ${e.message ?? "unknown error"}`, e.stderr ?? e.stdout);
35
+ }
36
+ }
37
+ /** Throws a helpful error if `dir` is not inside a git work tree. */
38
+ async assertRepo() {
39
+ try {
40
+ const { stdout } = await this.git(["rev-parse", "--is-inside-work-tree"]);
41
+ if (stdout.trim() !== "true") {
42
+ throw new GitError(`${this.dir} is not a git work tree`);
43
+ }
44
+ }
45
+ catch (err) {
46
+ if (err instanceof GitError) {
47
+ throw new GitError(`'${this.dir}' is not a git repository. Point --repo / AUTO_REVIEW_REPO at the ` +
48
+ `project the developer agent is editing (it must be a git repo).`, err.stderr);
49
+ }
50
+ throw err;
51
+ }
52
+ }
53
+ /** Current HEAD sha, or null if the repo has no commits yet. */
54
+ async head() {
55
+ try {
56
+ const { stdout } = await this.git(["rev-parse", "HEAD"]);
57
+ return stdout.trim();
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ /**
64
+ * Stage everything (`git add -A`) and capture the full diff vs HEAD, including
65
+ * new and deleted files. Staging means a later commit captures exactly what
66
+ * was reviewed.
67
+ */
68
+ async captureDiff(maxBytes) {
69
+ await this.git(["add", "-A"]);
70
+ const { stdout: stat } = await this.git(["diff", "--cached", "--stat"]);
71
+ const { stdout: fullDiff } = await this.git(["diff", "--cached"]);
72
+ const fullBytes = Buffer.byteLength(fullDiff, "utf8");
73
+ const isEmpty = fullDiff.trim() === "";
74
+ const diffHash = createHash("sha1").update(fullDiff).digest("hex");
75
+ let diff = fullDiff;
76
+ let truncated = false;
77
+ if (fullBytes > maxBytes) {
78
+ truncated = true;
79
+ diff =
80
+ fullDiff.slice(0, maxBytes) +
81
+ `\n\n... [diff truncated: ${fullBytes} bytes total, showing first ${maxBytes}. ` +
82
+ `See the --stat summary above for the full file list and inspect the working tree directly if needed.]`;
83
+ }
84
+ return { diff, diffStat: stat.trim(), diffHash, isEmpty, truncated, fullBytes };
85
+ }
86
+ /**
87
+ * Commit the currently-staged changes with `message` plus a Reviewed-by
88
+ * trailer. Returns the new commit sha. Throws GitError on failure (e.g. a
89
+ * pre-commit hook rejected the change) so the caller can keep the batch open.
90
+ */
91
+ async commit(message) {
92
+ await this.git(["commit", "-m", message, "--trailer", "Reviewed-by: auto-review"]);
93
+ const sha = await this.head();
94
+ if (!sha)
95
+ throw new GitError("commit appeared to succeed but HEAD is still unset");
96
+ return sha;
97
+ }
98
+ }
99
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}