@in-the-loop-labs/pair-review 4.0.0 → 4.1.1
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/README.md +82 -7
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin-code-critic/.claude-plugin/plugin.json +3 -4
- package/plugin-pair-loop/.claude-plugin/plugin.json +19 -0
- package/plugin-pair-loop/skills/loop/SKILL.md +233 -0
- package/public/js/index.js +87 -10
- package/public/js/local.js +19 -9
- package/public/js/pr.js +64 -36
- package/public/js/repo-links.js +11 -3
- package/public/js/utils/analyze-params.js +69 -0
- package/public/js/utils/provider-model.js +66 -1
- package/public/js/vendor/pierre-diffs-worker.js +16158 -0
- package/public/js/vendor/pierre-diffs.js +1880 -0
- package/public/local.html +1 -0
- package/public/pr.html +1 -0
- package/public/setup.html +35 -16
- package/src/ai/codex-provider.js +59 -11
- package/src/config.js +150 -28
- package/src/database.js +127 -3
- package/src/external/github-adapter.js +18 -3
- package/src/github/client.js +37 -0
- package/src/github/parser.js +41 -7
- package/src/interactive-analysis-config.js +2 -2
- package/src/links/repo-links.js +66 -28
- package/src/local-review.js +134 -5
- package/src/local-scope.js +38 -0
- package/src/main.js +199 -33
- package/src/routes/config.js +47 -12
- package/src/routes/external-comments.js +13 -1
- package/src/routes/github-collections.js +175 -13
- package/src/routes/local.js +11 -20
- package/src/routes/pr.js +63 -36
- package/src/routes/setup.js +63 -8
- package/src/routes/shared.js +85 -0
- package/src/routes/stack-analysis.js +39 -3
- package/src/server.js +74 -3
- package/src/setup/local-setup.js +23 -7
- package/src/setup/pr-setup.js +237 -39
- package/src/setup/stack-setup.js +7 -2
- package/src/single-port.js +73 -18
- package/src/utils/host-resolution.js +157 -0
- package/plugin-code-critic/skills/loop/SKILL.md +0 -373
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- [AI-Guided Review](#3-ai-guided-review-when-youre-accountable)
|
|
21
21
|
- [Quick Start](#quick-start)
|
|
22
22
|
- [Command Line Interface](#command-line-interface)
|
|
23
|
+
- [Local review scope](#local-review-scope)
|
|
23
24
|
- [Headless analysis mode](#headless-analysis-mode)
|
|
24
25
|
- [Configuration](#configuration)
|
|
25
26
|
- [Environment Variables](#environment-variables)
|
|
@@ -89,7 +90,8 @@ Compared to giving feedback in chat, this feels like moving from a machete to a
|
|
|
89
90
|
|
|
90
91
|
**Tips:**
|
|
91
92
|
- Stage previous changes in git, then only review new modifications in the next round
|
|
92
|
-
-
|
|
93
|
+
- By default local mode reviews unstaged changes and untracked files; adjust the range with `--scope` (or the web UI scope selector) — e.g. `branch..untracked` to cover the whole branch (see [Local review scope](#local-review-scope))
|
|
94
|
+
|
|
93
95
|
### 2. Meta-Review: Judging AI Suggestions
|
|
94
96
|
|
|
95
97
|
**When to use:** You're not going to read every line of code. Let AI be your reader.
|
|
@@ -203,7 +205,10 @@ pair-review --local [path]
|
|
|
203
205
|
| `-d`, `--debug` | Enable verbose debug logging for troubleshooting |
|
|
204
206
|
| `-h`, `--help` | Show help message with full CLI documentation |
|
|
205
207
|
| `-l`, `--local [path]` | Review local uncommitted changes. Optional path defaults to current directory |
|
|
208
|
+
| `--scope <start>..<end>` | **Local mode only.** Set the diff range a local review covers. Stops (in order): `branch`, `staged`, `unstaged`, `untracked`. Six valid ranges (contiguous, must include `unstaged`); default `unstaged..untracked`. `branch..*` diffs from the merge-base with the base branch. See [Local review scope](#local-review-scope). |
|
|
209
|
+
| `--base <branch>` | **Local mode only.** With a `branch..*` scope, override base-branch auto-detection. Errors if used without a branch-start scope. |
|
|
206
210
|
| `--model <name>` | Override the AI model for any provider. Model availability depends on provider configuration. |
|
|
211
|
+
| `--provider <name>` | Override the AI provider. Applies to headless modes (`--ai-draft` / `--ai-review`) **and** to browser-driven auto-analysis (`--ai`), where it overrides the repo/app default the browser would otherwise use — including across single-port delegation to an already-running server. Defaults to the repo/app default provider (`claude`). Pair with `--model` when the model belongs to a non-default provider (e.g. `--provider codex --model gpt-5.5`). |
|
|
207
212
|
| `--register` | Register `pair-review://` URL scheme handler (macOS only) |
|
|
208
213
|
| `--unregister` | Unregister `pair-review://` URL scheme handler (macOS only) |
|
|
209
214
|
| `--command <cmd>` | Custom CLI command for `--register` (default: `npx @in-the-loop-labs/pair-review`) |
|
|
@@ -216,8 +221,10 @@ pair-review 123 # Review PR #123 in current repo
|
|
|
216
221
|
pair-review https://github.com/owner/repo/pull/456
|
|
217
222
|
pair-review --local # Review uncommitted local changes
|
|
218
223
|
pair-review 123 --ai # Auto-run AI analysis
|
|
224
|
+
pair-review 123 --ai --provider codex # Auto-run analysis in the browser with a specific provider
|
|
219
225
|
pair-review --list-councils # List saved councils and their handles
|
|
220
226
|
pair-review 123 --ai-draft --council security-review # Headless draft with a council
|
|
227
|
+
pair-review 123 --ai-draft --provider codex --model gpt-5.5 # Headless draft with a specific provider + model
|
|
221
228
|
pair-review --local --ai --council security-review # Local review with a council
|
|
222
229
|
pair-review --local --headless # Analyze local changes, print a summary, exit
|
|
223
230
|
pair-review --local --headless --json # Analyze local changes, emit JSON, exit
|
|
@@ -240,6 +247,44 @@ pair-review --register --command "node bin/pair-review.js" # Custom command
|
|
|
240
247
|
> web UI's default **Analyze** action, so `--council`/`--model` are optional when
|
|
241
248
|
> a repo default is configured.
|
|
242
249
|
|
|
250
|
+
### Local review scope
|
|
251
|
+
|
|
252
|
+
`--scope <start>..<end>` sets which changes a **local** review covers (it has
|
|
253
|
+
no effect on PR reviews). The scope walks four ordered stops — `branch` →
|
|
254
|
+
`staged` → `unstaged` → `untracked` — and a range must be contiguous and always
|
|
255
|
+
include `unstaged`, since the AI models read files from the working tree and the
|
|
256
|
+
diff must cover that state. That leaves six valid ranges:
|
|
257
|
+
|
|
258
|
+
| Scope | Covers |
|
|
259
|
+
|-------|--------|
|
|
260
|
+
| `branch..unstaged` | All tracked changes since the base branch (committed + staged + unstaged); no new files |
|
|
261
|
+
| `branch..untracked` | Everything since the base branch, including new files |
|
|
262
|
+
| `staged..unstaged` | Staged changes plus working-tree edits vs `HEAD`; no new files |
|
|
263
|
+
| `staged..untracked` | Staged changes, working-tree edits, and new files |
|
|
264
|
+
| `unstaged..unstaged` | Only unstaged working-tree edits (staged changes treated as already reviewed) |
|
|
265
|
+
| `unstaged..untracked` | Unstaged working-tree edits plus new files (**default**) |
|
|
266
|
+
|
|
267
|
+
A scope ending at `untracked` includes new (untracked) files automatically — no
|
|
268
|
+
`git add -N` needed. `branch..*` diffs from the merge-base with the base branch.
|
|
269
|
+
|
|
270
|
+
`--base <branch>` overrides base-branch auto-detection for a `branch..*` scope.
|
|
271
|
+
Detection order when `--base` is omitted: Graphite stack state (only when
|
|
272
|
+
`enable_graphite: true` is set in `~/.pair-review/config.json`) → GitHub PR base →
|
|
273
|
+
`origin`'s default branch → `main`/`master`. `--base` errors if used without a
|
|
274
|
+
branch-start scope.
|
|
275
|
+
|
|
276
|
+
Both flags are **local-mode only**: they error when given a PR argument and do
|
|
277
|
+
**not** imply `--local`. An explicit `--scope` is persisted on the review
|
|
278
|
+
session, so opening the web UI for that review later shows the same scope.
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Review everything on this branch since it diverged from the trunk, plus new files
|
|
282
|
+
pair-review --local --scope branch..untracked
|
|
283
|
+
|
|
284
|
+
# Same, but against an explicit base branch (stacked branches / non-default trunk)
|
|
285
|
+
pair-review --local --scope branch..untracked --base develop
|
|
286
|
+
```
|
|
287
|
+
|
|
243
288
|
### Headless analysis mode
|
|
244
289
|
|
|
245
290
|
`--headless` runs an AI analysis (single provider or council), stores the results
|
|
@@ -427,7 +472,7 @@ Nested objects (like `chat`, `providers`, `chat_providers`, `monorepos`) are dee
|
|
|
427
472
|
|
|
428
473
|
### Alternate Git Hosts
|
|
429
474
|
|
|
430
|
-
pair-review can review pull requests on self-hosted Git platforms that expose a GitHub-compatible REST API, configured per-repository via `repos["owner/repo"].api_host` and related keys. See [docs/alt-host.md](docs/alt-host.md) for the full configuration guide.
|
|
475
|
+
pair-review can review pull requests on self-hosted Git platforms that expose a GitHub-compatible REST API, configured per-repository via `repos["owner/repo"].api_host` and related keys. By default an `api_host` repo is treated as living exclusively on the alt host; set `exclusive: false` on the repo entry to mark it as **dual-host**, where some PRs live on `github.com` and others on the alt host and pair-review resolves the host per PR. See [docs/alt-host.md](docs/alt-host.md) for the full configuration guide.
|
|
431
476
|
|
|
432
477
|
### Environment Variables
|
|
433
478
|
|
|
@@ -444,10 +489,13 @@ pair-review supports several environment variables for customizing behavior:
|
|
|
444
489
|
| `PAIR_REVIEW_CURSOR_AGENT_CMD` | Custom command to invoke Cursor Agent CLI | `agent` |
|
|
445
490
|
| `PAIR_REVIEW_PI_CMD` | Custom command to invoke Pi CLI | `pi` |
|
|
446
491
|
| `PAIR_REVIEW_MODEL` | Override the AI model to use (same as `--model` flag) | Provider default |
|
|
492
|
+
| `PAIR_REVIEW_PROVIDER` | Override the AI provider to use (same as `--provider` flag) | `claude` |
|
|
447
493
|
|
|
448
494
|
**Note:** `GITHUB_TOKEN` is the standard environment variable used by many GitHub tools (gh CLI, GitHub Actions, etc.). When set, it takes precedence over the `github_token` field in the config file.
|
|
449
495
|
|
|
450
|
-
**Note:** The `--model` CLI flag is shorthand for setting `PAIR_REVIEW_MODEL`. If both are specified, the CLI flag takes precedence.
|
|
496
|
+
**Note:** The `--model` CLI flag is shorthand for setting `PAIR_REVIEW_MODEL`, and `--provider` is shorthand for `PAIR_REVIEW_PROVIDER`. If both the flag and env var are specified, the CLI flag takes precedence. The `--provider` flag selects which provider runs the analysis; `--model` selects the model within that provider, so set both when the model belongs to a non-default provider (e.g. `--provider codex --model gpt-5.5`). The override outranks saved repository settings (`CLI/env > repo settings`); if the repo's default is a Review Council, an active `--provider`/`--model` override forces the single-provider path instead.
|
|
497
|
+
|
|
498
|
+
**Delegation caveat:** With single-port mode (the default `single_port: true`), a new invocation delegates to an already-running server. On that path the `--provider`/`--model` override is carried to the running server only alongside browser auto-analysis (`--ai`). Delegating without `--ai` (just opening the review) does not seed the override into the other process's manual analysis dialog — use `--ai`, or a headless mode, to pin the provider on the delegated path.
|
|
451
499
|
|
|
452
500
|
These variables are useful when:
|
|
453
501
|
- Your CLI tools are installed in a non-standard location
|
|
@@ -472,6 +520,9 @@ PAIR_REVIEW_ANTIGRAVITY_CMD="/usr/local/bin/agy" pair-review --local
|
|
|
472
520
|
# Force a specific model for this review
|
|
473
521
|
PAIR_REVIEW_MODEL="opus" pair-review 123
|
|
474
522
|
|
|
523
|
+
# Force a specific provider + model for a headless review
|
|
524
|
+
PAIR_REVIEW_PROVIDER="codex" PAIR_REVIEW_MODEL="gpt-5.5" pair-review 123 --ai-draft
|
|
525
|
+
|
|
475
526
|
# Combine multiple settings
|
|
476
527
|
PAIR_REVIEW_CLAUDE_CMD="/opt/claude/bin/claude" PAIR_REVIEW_MODEL="haiku" pair-review 123
|
|
477
528
|
```
|
|
@@ -791,7 +842,10 @@ Templates typically include `{description}` to render the suggestion body.
|
|
|
791
842
|
|
|
792
843
|
### Local Mode
|
|
793
844
|
|
|
794
|
-
Review
|
|
845
|
+
Review your uncommitted changes before creating a PR — by default the unstaged
|
|
846
|
+
working-tree edits plus untracked files, with the range adjustable via `--scope`
|
|
847
|
+
(or the web UI scope selector), e.g. `branch..untracked` to cover the whole
|
|
848
|
+
branch (see [Local review scope](#local-review-scope)):
|
|
795
849
|
|
|
796
850
|
```bash
|
|
797
851
|
pair-review --local
|
|
@@ -806,11 +860,28 @@ Perfect for:
|
|
|
806
860
|
|
|
807
861
|
## Claude Code Plugins
|
|
808
862
|
|
|
809
|
-
pair-review provides
|
|
863
|
+
pair-review provides three [Claude Code plugins](https://code.claude.com/docs/en/plugins) that bring AI-powered code review directly into Claude Code.
|
|
864
|
+
|
|
865
|
+
### pair-loop — Agent-Orchestrated Review Loop
|
|
866
|
+
|
|
867
|
+
An implement→review→fix loop with pair-review as the review oracle. The agent runs multi-model council reviews through the headless CLI, triages the findings (fixing, dismissing with reasons, or asking you), applies fixes, and repeats with narrowing instructions until a final review returns no blockers. Every round persists to pair-review, and when the server is running the agent writes its triage back — so you can open the web UI, inspect every round, and see exactly what was fixed or dismissed and why. No MCP required.
|
|
868
|
+
|
|
869
|
+
**Install via Marketplace:**
|
|
870
|
+
|
|
871
|
+
```
|
|
872
|
+
/plugin marketplace add in-the-loop-labs/pair-review
|
|
873
|
+
/plugin install pair-review@pair-loop
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
**Available Skills:**
|
|
877
|
+
|
|
878
|
+
| Skill | Description |
|
|
879
|
+
|-------|-------------|
|
|
880
|
+
| `/pair-loop:loop` | Implement, review with pair-review councils, triage, fix, and repeat until clean |
|
|
810
881
|
|
|
811
882
|
### code-critic — Standalone Analysis
|
|
812
883
|
|
|
813
|
-
AI-powered code review analysis that works without any server or MCP dependency. Install this plugin for three-level AI analysis
|
|
884
|
+
AI-powered code review analysis that works without any server or MCP dependency. Install this plugin for three-level AI analysis directly in your coding agent.
|
|
814
885
|
|
|
815
886
|
**Install via Marketplace:**
|
|
816
887
|
|
|
@@ -824,7 +895,8 @@ AI-powered code review analysis that works without any server or MCP dependency.
|
|
|
824
895
|
| Skill | Description |
|
|
825
896
|
|-------|-------------|
|
|
826
897
|
| `/code-critic:analyze` | Run three-level AI analysis using Task agents directly (standalone, no server needed) |
|
|
827
|
-
|
|
898
|
+
|
|
899
|
+
Looking for an implement-review-fix loop? That's the [pair-loop plugin](#pair-loop--agent-orchestrated-review-loop) above, which replaced the former `/code-critic:loop` skill.
|
|
828
900
|
|
|
829
901
|
These skills work standalone. If the pair-review MCP server happens to be available (from the pair-review plugin), `code-critic:analyze` will use it for prompts and push results to the web UI — but it's entirely optional.
|
|
830
902
|
|
|
@@ -857,10 +929,12 @@ If you prefer not to use the marketplace, load plugins directly from an npm-inst
|
|
|
857
929
|
|
|
858
930
|
```bash
|
|
859
931
|
# From a local clone
|
|
932
|
+
claude --plugin-dir ./path/to/pair-review/plugin-pair-loop
|
|
860
933
|
claude --plugin-dir ./path/to/pair-review/plugin-code-critic
|
|
861
934
|
claude --plugin-dir ./path/to/pair-review/plugin
|
|
862
935
|
|
|
863
936
|
# From a globally installed npm package
|
|
937
|
+
claude --plugin-dir "$(npm root -g)/@in-the-loop-labs/pair-review/plugin-pair-loop"
|
|
864
938
|
claude --plugin-dir "$(npm root -g)/@in-the-loop-labs/pair-review/plugin-code-critic"
|
|
865
939
|
claude --plugin-dir "$(npm root -g)/@in-the-loop-labs/pair-review/plugin"
|
|
866
940
|
```
|
|
@@ -880,6 +954,7 @@ To pre-configure plugins for all contributors on a repository, add this to your
|
|
|
880
954
|
}
|
|
881
955
|
},
|
|
882
956
|
"enabledPlugins": {
|
|
957
|
+
"pair-review@pair-loop": true,
|
|
883
958
|
"pair-review@code-critic": true,
|
|
884
959
|
"pair-review@pair-review": true
|
|
885
960
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@in-the-loop-labs/pair-review",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Your AI-powered code review partner - Close the feedback loop with AI coding agents",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"public/",
|
|
15
15
|
"plugin/",
|
|
16
16
|
"plugin-code-critic/",
|
|
17
|
+
"plugin-pair-loop/",
|
|
17
18
|
"config.managed.json",
|
|
18
19
|
"README.md",
|
|
19
20
|
"LICENSE"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pair-review",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "pair-review app integration — Open PRs and local changes in the pair-review web UI, run server-side AI analysis, and address review feedback. Requires the pair-review MCP server.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-critic",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "AI-powered code review analysis — Run three-level AI analysis
|
|
3
|
+
"version": "4.1.1",
|
|
4
|
+
"description": "AI-powered code review analysis — Run three-level AI analysis directly in your coding agent. Works standalone, no server required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "in-the-loop-labs",
|
|
7
7
|
"email": "tjwp@inthelooplabs.com"
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"code-review",
|
|
14
14
|
"ai",
|
|
15
15
|
"analysis",
|
|
16
|
-
"code-critic"
|
|
17
|
-
"critic-loop"
|
|
16
|
+
"code-critic"
|
|
18
17
|
]
|
|
19
18
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pair-loop",
|
|
3
|
+
"version": "4.1.1",
|
|
4
|
+
"description": "Agent-orchestrated review loop — implement, review with pair-review multi-model councils via the headless CLI, triage, fix, and repeat until a final review returns no blockers. MCP-independent.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "in-the-loop-labs",
|
|
7
|
+
"email": "tjwp@inthelooplabs.com"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/in-the-loop-labs/pair-review",
|
|
10
|
+
"repository": "https://github.com/in-the-loop-labs/pair-review",
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"code-review",
|
|
14
|
+
"ai",
|
|
15
|
+
"review-loop",
|
|
16
|
+
"council",
|
|
17
|
+
"feedback-loop"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop
|
|
3
|
+
description: >
|
|
4
|
+
Drive an implement→review→fix loop with pair-review as the review oracle:
|
|
5
|
+
run multi-model council reviews, triage the findings, apply fixes, and
|
|
6
|
+
repeat with narrowing instructions until a final review returns no
|
|
7
|
+
blockers. Use when the user says "pair loop", "review loop",
|
|
8
|
+
"loop until clean", "review this with pair-review and fix what it finds",
|
|
9
|
+
or wants iterative development reviewed by pair-review councils.
|
|
10
|
+
arguments:
|
|
11
|
+
council:
|
|
12
|
+
description: "Council handle (name, name-slug, or id prefix) to review with. Defaults to the repo's default council when omitted."
|
|
13
|
+
required: false
|
|
14
|
+
maxRounds:
|
|
15
|
+
description: "Budget for review rounds. On reaching it without convergence, stop and report honestly."
|
|
16
|
+
required: false
|
|
17
|
+
default: 5
|
|
18
|
+
instructions:
|
|
19
|
+
description: "Extra review instructions applied to every round (e.g. 'focus on security')"
|
|
20
|
+
required: false
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# pair-loop
|
|
24
|
+
|
|
25
|
+
You are a thin orchestrator. pair-review does the heavy lifting — multi-level,
|
|
26
|
+
multi-model council reviews with persistence and a web UI. Your job is
|
|
27
|
+
judgment: what to send for review, how to triage what comes back, what to
|
|
28
|
+
fix, when to ask the user, and when to stop.
|
|
29
|
+
|
|
30
|
+
Reviews run through **one execution mode: the headless CLI**. It is
|
|
31
|
+
synchronous (no polling), emits JSON, and needs no server. The HTTP API is
|
|
32
|
+
used for exactly one optional thing: writing triage back so a running web UI
|
|
33
|
+
shows reality (see Write triage back).
|
|
34
|
+
|
|
35
|
+
Any text in the user's request beyond the named arguments is the
|
|
36
|
+
**objective** — what to build or change. If there is no objective, the loop
|
|
37
|
+
runs over the changes already in the working tree.
|
|
38
|
+
|
|
39
|
+
## Hard rules
|
|
40
|
+
|
|
41
|
+
These are not judgment calls:
|
|
42
|
+
|
|
43
|
+
1. **Never report "clean" or "ready" unless a full-quality council round,
|
|
44
|
+
instructed to report only merge-blockers, came back with an empty finding
|
|
45
|
+
list.** A cheaper or broader round cannot grant the final verdict.
|
|
46
|
+
2. **Never silently stop.** If the loop is not converging or hits
|
|
47
|
+
`maxRounds`, say so, list the outstanding findings, and (when a server is
|
|
48
|
+
up) link the review URL.
|
|
49
|
+
3. **Sequence strictly: fix → write triage back → then run the next round.**
|
|
50
|
+
4. **One review at a time.** Never run two reviews concurrently for the same
|
|
51
|
+
repo.
|
|
52
|
+
5. **Do not commit or push.** Leave changes in the working tree. New
|
|
53
|
+
(untracked) files are included automatically whenever the review scope ends
|
|
54
|
+
at `untracked` — which the default scope (`unstaged..untracked`) does — so
|
|
55
|
+
no `git add -N` (intent-to-add) is needed.
|
|
56
|
+
6. **Report failures faithfully.** A failed run (`"ok": false`, non-zero
|
|
57
|
+
exit) is reported as what it is — never papered over as "no findings".
|
|
58
|
+
|
|
59
|
+
## Phase: Setup
|
|
60
|
+
|
|
61
|
+
1. `git rev-parse --show-toplevel` → `REPO_ROOT`. All commands run from here.
|
|
62
|
+
2. Create the round log at `REPO_ROOT/.pair-loop/round-log.md` (mkdir -p; add
|
|
63
|
+
`.pair-loop/` to `.gitignore` if missing). Record: objective, arguments,
|
|
64
|
+
start time. Append to this file after every phase — it is your resume
|
|
65
|
+
state if the session compacts.
|
|
66
|
+
3. If a `council` argument was given, confirm the handle exists:
|
|
67
|
+
`pair-review --list-councils` prints the saved councils. If `pair-review`
|
|
68
|
+
is not on PATH, use `npx -y @in-the-loop-labs/pair-review` for every CLI
|
|
69
|
+
call. An unknown handle → show the available names and ask the user.
|
|
70
|
+
4. If an objective was given and the working tree has no relevant changes
|
|
71
|
+
yet, implement the objective first (directly or via a Task agent — your
|
|
72
|
+
call based on size). Verify with `git status --short` that changes exist
|
|
73
|
+
before the first review round; if none, tell the user and stop.
|
|
74
|
+
|
|
75
|
+
## Running a review round
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pair-review --local --headless --json --council <handle> [--scope <range>] --instructions "<round instructions>"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
- Run from `REPO_ROOT`. `--local` with no path uses the current directory.
|
|
82
|
+
- Omit `--council` to use the repo's default council.
|
|
83
|
+
- **Scope (`--scope`).** By default a local review covers only uncommitted
|
|
84
|
+
working-tree changes plus new files (`unstaged..untracked`). That is right
|
|
85
|
+
only while all the work under review stays uncommitted. If the work under
|
|
86
|
+
review already spans commits on a branch, a clean working tree would review as
|
|
87
|
+
**empty** — pass
|
|
88
|
+
`--scope branch..untracked` so every round covers the whole branch
|
|
89
|
+
(everything since the base branch) plus the working tree and new files. Add
|
|
90
|
+
`--base <branch>` when the base branch is not auto-detected (a non-default
|
|
91
|
+
trunk, or a stacked branch). `--scope`/`--base` are local-mode only.
|
|
92
|
+
- **Be patient — council reviews routinely take 15–40 minutes.** Individual
|
|
93
|
+
council voices have their own internal timeouts; trust the CLI to finish.
|
|
94
|
+
Run the command in the background (or with a timeout of at least 45
|
|
95
|
+
minutes) and wait for it to exit. Do not abandon or kill it early; do not
|
|
96
|
+
start a second review while one is running (hard rule 4).
|
|
97
|
+
- stdout is exactly one JSON document (logs go to stderr):
|
|
98
|
+
- success: `{"ok": true, "mode": "local", "run": {...}, "suggestions":
|
|
99
|
+
[...], "count": N}` — exit code 0. **Zero suggestions is still
|
|
100
|
+
success.** Record `run.id` (the runId) and `run.review_id` (the
|
|
101
|
+
reviewId, used for write-back and the review URL).
|
|
102
|
+
- failure: `{"ok": false, "error": {"message": ...}}`, exit code 1.
|
|
103
|
+
|
|
104
|
+
### Round instructions
|
|
105
|
+
|
|
106
|
+
Build `--instructions` per round from, in order: this standing line —
|
|
107
|
+
"Do not report praise findings; report only issues." — then the user's
|
|
108
|
+
`instructions` argument, the objective, and this round's directive (see
|
|
109
|
+
Convergence judgment). On rounds after the first, add: "Earlier review
|
|
110
|
+
rounds already reported issues that have since been fixed or dismissed;
|
|
111
|
+
report only issues present in the current code."
|
|
112
|
+
|
|
113
|
+
### Suggestion fields
|
|
114
|
+
|
|
115
|
+
Each entry in `suggestions`: `id`, `file`, `line_start`, `line_end`, `type`
|
|
116
|
+
(`bug|improvement|suggestion|design|performance|security|code-style` —
|
|
117
|
+
praise is instructed away; ignore it if it appears anyway), `severity`
|
|
118
|
+
(`critical|medium|minor`), `title`, `body`, `reasoning`, `ai_confidence`
|
|
119
|
+
(0–1).
|
|
120
|
+
|
|
121
|
+
## Triage
|
|
122
|
+
|
|
123
|
+
For each finding, decide one of:
|
|
124
|
+
|
|
125
|
+
- **Fix** — it is real and in scope. Goes on this round's fix list.
|
|
126
|
+
- **Dismiss** — false positive, out of scope, or explicitly contrary to the
|
|
127
|
+
user's stated intent. Requires a stated reason in the round log.
|
|
128
|
+
- **Ask the user** — mandatory stop, not a preference, when any of:
|
|
129
|
+
(a) you would dismiss a **critical**-severity finding;
|
|
130
|
+
(b) the fix would change behavior beyond the stated objective;
|
|
131
|
+
(c) the same finding has survived two fix rounds;
|
|
132
|
+
(d) findings conflict with the user's explicit instructions.
|
|
133
|
+
Batch the questions, ask once per round at most, record the answers in the
|
|
134
|
+
round log so later rounds do not re-ask.
|
|
135
|
+
|
|
136
|
+
Dedup against your round log: a finding you already dismissed in an earlier
|
|
137
|
+
round, reappearing unchanged, keeps its dismissal (re-dismiss it in
|
|
138
|
+
write-back) — unless a fix touched that code since, in which case judge it
|
|
139
|
+
fresh.
|
|
140
|
+
|
|
141
|
+
## Fix
|
|
142
|
+
|
|
143
|
+
Apply the fixes (directly or via a Task agent for large batches — your
|
|
144
|
+
call). Run the project's relevant tests for the changed code. A fix that
|
|
145
|
+
breaks tests is not a fix.
|
|
146
|
+
|
|
147
|
+
## Write triage back (optional, needs the server)
|
|
148
|
+
|
|
149
|
+
If the pair-review server is running, keep its UI truthful. Probe once per
|
|
150
|
+
loop: `curl -s http://localhost:7247/health` → it is pair-review iff the
|
|
151
|
+
response has `"service": "pair-review"`. (7247 is the default and is right
|
|
152
|
+
most of the time; if it isn't pair-review, check `port` in
|
|
153
|
+
`REPO_ROOT/.pair-review/config.json` then `~/.pair-review/config.json`, then
|
|
154
|
+
give up on write-back for this loop.)
|
|
155
|
+
|
|
156
|
+
When the server is up, after fixing/dismissing, for each handled finding:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
curl -s -X POST http://localhost:<port>/api/reviews/<run.review_id>/suggestions/<id>/status \
|
|
160
|
+
-H 'Content-Type: application/json' -d '{"status": "dismissed"}'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
- Only `"dismissed"` and `"active"` are accepted; `"adopted"` is reserved
|
|
164
|
+
for the human's adopt flow — never send it.
|
|
165
|
+
- Newer servers accept a `reason` field on this endpoint (check
|
|
166
|
+
`http://localhost:<port>/api.md?reviewId=<reviewId>` if unsure); when
|
|
167
|
+
supported, include it: `"Fixed in loop round <n>: ..."` or
|
|
168
|
+
`"Dismissed: <why>"`. Otherwise the fixed-vs-rejected distinction lives in
|
|
169
|
+
your round log and final report.
|
|
170
|
+
- The human may be triaging in the same UI: before fixing, you may re-check
|
|
171
|
+
`GET /api/reviews/<reviewId>/suggestions` and skip findings whose status
|
|
172
|
+
is no longer `active` — the human got there first. Do not resurrect
|
|
173
|
+
anything the human dismissed.
|
|
174
|
+
- The review URL for your report: `http://localhost:<port>/local/<reviewId>`.
|
|
175
|
+
Note: the UI does not live-update during CLI runs; results appear on
|
|
176
|
+
refresh.
|
|
177
|
+
|
|
178
|
+
No server → skip this entirely; triage lives in the round log and final
|
|
179
|
+
report, and the persisted run history is visible whenever the user next
|
|
180
|
+
starts the server.
|
|
181
|
+
|
|
182
|
+
## Convergence judgment
|
|
183
|
+
|
|
184
|
+
You decide each round's directive and whether to continue. Principles:
|
|
185
|
+
|
|
186
|
+
- **Round 1**: broad. Directive like: "Review these changes thoroughly.
|
|
187
|
+
Objective: <objective>."
|
|
188
|
+
- **Escalate** back to a broad directive whenever the previous round
|
|
189
|
+
produced any `critical` finding.
|
|
190
|
+
- **Narrow** as findings shrink. The gate directive:
|
|
191
|
+
"Final review. Only report blockers — issues that must be fixed before
|
|
192
|
+
merging. Is this ready to merge?"
|
|
193
|
+
- **The gate uses the same council.** The round that says "merge it" must be
|
|
194
|
+
just as rigorous as the rounds that found problems. Do not substitute a
|
|
195
|
+
cheaper configuration for gate rounds.
|
|
196
|
+
- **Continue vs stop**: another round is worth it when the last one caused
|
|
197
|
+
substantial fixes. When rounds churn the same ground without progress,
|
|
198
|
+
stop and ask the user instead of grinding the budget.
|
|
199
|
+
- The loop **ends successfully** when a gate round returns **no blockers** —
|
|
200
|
+
an empty finding list (per hard rule 1). Read the run's `summary` for the
|
|
201
|
+
reviewer's own answer to the ready-to-merge question and quote it in your
|
|
202
|
+
report.
|
|
203
|
+
|
|
204
|
+
## PR mode
|
|
205
|
+
|
|
206
|
+
The loop also works against a GitHub PR:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
pair-review <pr-number-or-url> --headless --json [--council <handle>] [--instructions "..."]
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This self-bootstraps the PR (fetches metadata, prepares a review worktree)
|
|
213
|
+
and requires a GitHub token in `~/.pair-review/config.json`. The review's
|
|
214
|
+
checkout lives in a pair-review worktree — your fixes belong in whatever
|
|
215
|
+
checkout the user asked you to work in; do not edit the pool worktree.
|
|
216
|
+
Write-back and the review URL work the same way via `run.review_id`.
|
|
217
|
+
`--scope`/`--base` do not apply here — they are local-mode only; a PR review
|
|
218
|
+
always covers the PR's full diff.
|
|
219
|
+
|
|
220
|
+
## Reporting
|
|
221
|
+
|
|
222
|
+
Every loop ends with a report containing:
|
|
223
|
+
|
|
224
|
+
1. **Verdict** — converged (quote the final run's summary) or not converged
|
|
225
|
+
(why, and what remains).
|
|
226
|
+
2. **Rounds table** — per round: directive, findings found, fixed, dismissed,
|
|
227
|
+
asked.
|
|
228
|
+
3. **Triage detail** — every dismissed finding with its reason; every fix in
|
|
229
|
+
one line each.
|
|
230
|
+
4. **The review URL** when a server is up, so the user can inspect
|
|
231
|
+
everything in the web UI.
|
|
232
|
+
5. Working-tree status reminder: changes are uncommitted, tests run and
|
|
233
|
+
their result.
|
package/public/js/index.js
CHANGED
|
@@ -445,8 +445,14 @@
|
|
|
445
445
|
? ''
|
|
446
446
|
: '<td class="col-author">' + authorDisplay + '</td>';
|
|
447
447
|
|
|
448
|
+
// `data-host` carries the alt-host api_host string for PRs that live on an
|
|
449
|
+
// alternate host; github.com rows have no host and render an empty
|
|
450
|
+
// attribute. The click handler threads it into the setup call so a dual
|
|
451
|
+
// repo's alt-host PR opens against the right system without re-probing.
|
|
452
|
+
var hostAttr = pr.host ? ' data-host="' + escapeHtml(pr.host) + '"' : '';
|
|
453
|
+
|
|
448
454
|
return '' +
|
|
449
|
-
'<tr class="collection-pr-row" data-pr-url="' + escapeHtml(prUrl) + '" data-owner="' + escapeHtml(pr.owner) + '" data-repo="' + escapeHtml(pr.repo) + '" data-number="' + pr.number + '">' +
|
|
455
|
+
'<tr class="collection-pr-row" data-pr-url="' + escapeHtml(prUrl) + '" data-owner="' + escapeHtml(pr.owner) + '" data-repo="' + escapeHtml(pr.repo) + '" data-number="' + pr.number + '"' + hostAttr + '>' +
|
|
450
456
|
'<td class="col-repo">' + escapeHtml(repoFull) + '</td>' +
|
|
451
457
|
'<td class="col-pr"><span class="collection-pr-number">#' + pr.number + '</span></td>' +
|
|
452
458
|
'<td class="col-title" title="' + escapeHtml(pr.title || '') + '">' + escapeHtml(pr.title || '') + '</td>' +
|
|
@@ -1293,7 +1299,14 @@
|
|
|
1293
1299
|
return {
|
|
1294
1300
|
owner: data.owner,
|
|
1295
1301
|
repo: data.repo,
|
|
1296
|
-
prNumber: data.prNumber
|
|
1302
|
+
prNumber: data.prNumber,
|
|
1303
|
+
// `host` tells setup which system the PR lives on: an api_host URL
|
|
1304
|
+
// string = alt host, null = github.com, undefined = unknown.
|
|
1305
|
+
host: data.host,
|
|
1306
|
+
bindingRepository: data.bindingRepository,
|
|
1307
|
+
// Whether the repo is dual (github + alt-host). Only dual repos probe,
|
|
1308
|
+
// so only they need an explicit github pick when the URL is a github URL.
|
|
1309
|
+
isDualHost: data.isDualHost
|
|
1297
1310
|
};
|
|
1298
1311
|
}
|
|
1299
1312
|
|
|
@@ -1343,7 +1356,22 @@
|
|
|
1343
1356
|
// Navigate to the PR route which serves setup.html (with step-by-step progress)
|
|
1344
1357
|
// for new PRs, or pr.html directly for PRs already in the database
|
|
1345
1358
|
let href = '/pr/' + encodeURIComponent(parsed.owner) + '/' + encodeURIComponent(parsed.repo) + '/' + encodeURIComponent(parsed.prNumber);
|
|
1346
|
-
|
|
1359
|
+
var params = new URLSearchParams();
|
|
1360
|
+
if (analyze) params.set('analyze', 'true');
|
|
1361
|
+
// Forward the parsed host so setup binds directly instead of probing:
|
|
1362
|
+
// - alt-host URL string → the api_host (setup binds that alt host)
|
|
1363
|
+
// - github URL (host === null) on a DUAL repo → the "github" sentinel, so
|
|
1364
|
+
// setup binds github.com instead of probing alt-first (which would fail
|
|
1365
|
+
// loudly if the alt host is down for a PR we KNOW is on github)
|
|
1366
|
+
// - anything else (plain/exclusive repo, unknown host) → omit; no probe
|
|
1367
|
+
// happens for those and omitting avoids the exclusive-null throw.
|
|
1368
|
+
if (typeof parsed.host === 'string' && parsed.host) {
|
|
1369
|
+
params.set('host', parsed.host);
|
|
1370
|
+
} else if (parsed.host === null && parsed.isDualHost) {
|
|
1371
|
+
params.set('host', 'github');
|
|
1372
|
+
}
|
|
1373
|
+
var qs = params.toString();
|
|
1374
|
+
if (qs) href += '?' + qs;
|
|
1347
1375
|
window.location.href = href;
|
|
1348
1376
|
}
|
|
1349
1377
|
|
|
@@ -1385,6 +1413,11 @@
|
|
|
1385
1413
|
window.__pairReview.chatProviders = chatProviders;
|
|
1386
1414
|
window.__pairReview.defaultProvider = config.default_provider || 'claude';
|
|
1387
1415
|
window.__pairReview.defaultModel = config.default_model || 'opus';
|
|
1416
|
+
// CLI/env override (PAIR_REVIEW_PROVIDER / PAIR_REVIEW_MODEL) surfaced
|
|
1417
|
+
// by /api/config. The bulk-analysis seed prepends these ahead of repo
|
|
1418
|
+
// settings so a `--provider` override outranks a repo's saved default.
|
|
1419
|
+
window.__pairReview.providerOverride = config.provider_override || null;
|
|
1420
|
+
window.__pairReview.modelOverride = config.model_override || null;
|
|
1388
1421
|
window.__pairReview.hasGithubToken = Boolean(config.has_github_token);
|
|
1389
1422
|
window.__pairReview.enableGraphite = config.enable_graphite === true;
|
|
1390
1423
|
window.__pairReview.chatSpinner = config.chat_spinner || 'dots';
|
|
@@ -1891,7 +1924,7 @@
|
|
|
1891
1924
|
* Read selected collection rows as PR descriptors.
|
|
1892
1925
|
* @param {Set} selectedIds - PR URLs (data-pr-url values)
|
|
1893
1926
|
* @param {string} tbodyId - tbody element ID
|
|
1894
|
-
* @returns {Array<{owner: string, repo: string, number: string, prUrl: string}>}
|
|
1927
|
+
* @returns {Array<{owner: string, repo: string, number: string, prUrl: string, host: string|undefined}>}
|
|
1895
1928
|
*/
|
|
1896
1929
|
function getSelectedCollectionRows(selectedIds, tbodyId) {
|
|
1897
1930
|
var tbody = document.getElementById(tbodyId);
|
|
@@ -1908,7 +1941,11 @@
|
|
|
1908
1941
|
owner: tr.dataset.owner,
|
|
1909
1942
|
repo: tr.dataset.repo,
|
|
1910
1943
|
number: tr.dataset.number,
|
|
1911
|
-
prUrl: prUrl
|
|
1944
|
+
prUrl: prUrl,
|
|
1945
|
+
// Alt-host rows carry a host; github.com rows leave it undefined. The
|
|
1946
|
+
// single-row click path threads this the same way (see the collection
|
|
1947
|
+
// row click handler) so bulk-open binds to the right system too.
|
|
1948
|
+
host: tr.dataset.host
|
|
1912
1949
|
});
|
|
1913
1950
|
}
|
|
1914
1951
|
}
|
|
@@ -1917,7 +1954,14 @@
|
|
|
1917
1954
|
|
|
1918
1955
|
function buildReviewUrlsFromRows(rows, query) {
|
|
1919
1956
|
return rows.map(function (row) {
|
|
1920
|
-
|
|
1957
|
+
// Preserve any existing params (analyze / analysisConfigId) and append the
|
|
1958
|
+
// alt host so setup opens the PR against the system it lives on instead of
|
|
1959
|
+
// re-probing. github.com rows (no host) keep the URL unchanged.
|
|
1960
|
+
var qs = query || '';
|
|
1961
|
+
if (row.host) {
|
|
1962
|
+
qs += (qs ? '&' : '?') + 'host=' + encodeURIComponent(row.host);
|
|
1963
|
+
}
|
|
1964
|
+
return '/pr/' + encodeURIComponent(row.owner) + '/' + encodeURIComponent(row.repo) + '/' + row.number + qs;
|
|
1921
1965
|
});
|
|
1922
1966
|
}
|
|
1923
1967
|
|
|
@@ -2000,10 +2044,18 @@
|
|
|
2000
2044
|
};
|
|
2001
2045
|
|
|
2002
2046
|
var providersInfo = await getBulkProvidersInfo();
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2047
|
+
// Shape __pairReview into the appConfig fields buildProviderModelScopes reads
|
|
2048
|
+
// so a CLI/env `--provider` override is prepended ahead of repo settings.
|
|
2049
|
+
var bulkAppConfig = {
|
|
2050
|
+
default_provider: window.__pairReview?.defaultProvider,
|
|
2051
|
+
default_model: window.__pairReview?.defaultModel,
|
|
2052
|
+
provider_override: window.__pairReview?.providerOverride || null,
|
|
2053
|
+
model_override: window.__pairReview?.modelOverride || null
|
|
2054
|
+
};
|
|
2055
|
+
var resolvedPair = window.resolveProviderModelPair(
|
|
2056
|
+
window.buildProviderModelScopes(repoSettings, bulkAppConfig),
|
|
2057
|
+
providersInfo
|
|
2058
|
+
);
|
|
2007
2059
|
|
|
2008
2060
|
var config = await bulkAnalysisConfigModal.show({
|
|
2009
2061
|
currentModel: resolvedPair.model,
|
|
@@ -2163,6 +2215,21 @@
|
|
|
2163
2215
|
return;
|
|
2164
2216
|
}
|
|
2165
2217
|
|
|
2218
|
+
// Alt-host PR: we already know owner/repo/number and the host it lives
|
|
2219
|
+
// on, so navigate straight to the PR route with the host as a query
|
|
2220
|
+
// param (setup.html forwards it into the setup POST body). This bypasses
|
|
2221
|
+
// the URL-parse round trip, which can't reliably recover the host from a
|
|
2222
|
+
// pasted alt-host html_url. github.com rows (no host) keep the existing
|
|
2223
|
+
// parse-and-submit flow below, byte-identical.
|
|
2224
|
+
var rowHost = collectionRow.dataset.host;
|
|
2225
|
+
if (rowHost) {
|
|
2226
|
+
var o = encodeURIComponent(collectionRow.dataset.owner);
|
|
2227
|
+
var r = encodeURIComponent(collectionRow.dataset.repo);
|
|
2228
|
+
var n = encodeURIComponent(collectionRow.dataset.number);
|
|
2229
|
+
window.location.href = '/pr/' + o + '/' + r + '/' + n + '?host=' + encodeURIComponent(rowHost);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2166
2233
|
var prUrl = collectionRow.dataset.prUrl;
|
|
2167
2234
|
if (prUrl) {
|
|
2168
2235
|
// Switch to PR tab to show loading state (do NOT persist to
|
|
@@ -2542,4 +2609,14 @@
|
|
|
2542
2609
|
window.__pairReview = window.__pairReview || {};
|
|
2543
2610
|
window.__pairReview.refreshAnalysisSpinners = fetchAndApplyActiveAnalyses;
|
|
2544
2611
|
|
|
2612
|
+
// Test-only exports. In the browser `module` is undefined, so this is a
|
|
2613
|
+
// no-op; under Vitest (CommonJS) it exposes the internal bulk-open helpers
|
|
2614
|
+
// for direct unit testing rather than duplicating their logic in a test.
|
|
2615
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
2616
|
+
module.exports = {
|
|
2617
|
+
buildReviewUrlsFromRows: buildReviewUrlsFromRows,
|
|
2618
|
+
getSelectedCollectionRows: getSelectedCollectionRows
|
|
2619
|
+
};
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2545
2622
|
})();
|