@expo/code-review-cli 0.1.0 → 0.2.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/README.md +188 -152
- package/build/commands/init.js +10 -7
- package/package.json +2 -1
- package/templates/config.jsonc +7 -5
- package/templates/workflow.yml +40 -14
package/README.md
CHANGED
|
@@ -1,26 +1,95 @@
|
|
|
1
|
-
# expo
|
|
1
|
+
# @expo/code-review-cli
|
|
2
2
|
|
|
3
3
|
A config-driven, multi-agent AI code reviewer. Specialist agents review a diff in
|
|
4
4
|
parallel; a coordinator consolidates their findings into one structured review.
|
|
5
|
-
|
|
5
|
+
The same engine runs locally (advisory) and in CI (posts one PR comment). The CLI
|
|
6
|
+
is the **engine** — each repo supplies its own agents and settings under
|
|
7
|
+
`.expo-code-review/`, so behavior is configured per-repo, not baked in.
|
|
8
|
+
|
|
9
|
+
> **Status: experimental.** Comment-only and non-blocking — it never blocks a merge
|
|
10
|
+
> and never auto-approves. See [`ROADMAP.md`](./ROADMAP.md).
|
|
11
|
+
|
|
12
|
+
Inspired in part by Cloudflare's [_How we built our AI code review bot_](https://blog.cloudflare.com/ai-code-review/).
|
|
13
|
+
|
|
14
|
+
```mermaid
|
|
15
|
+
flowchart TD
|
|
16
|
+
SRC["Source<br/>local git · GitHub PR (gh)"] --> FILTER["Noise filter<br/>drop lockfiles · generated · binary"]
|
|
17
|
+
FILTER --> CHUNK["Chunk<br/>by changed lines (large diffs only)"]
|
|
18
|
+
CHUNK --> AGENTS["Agents (parallel)<br/>each .md in agents/ · read·grep·glob·list"]
|
|
19
|
+
CHUNK --> XCUT["Cross-cutting pass<br/>multi-file issues (large diffs)"]
|
|
20
|
+
AGENTS --> COORD["Coordinator<br/>dedupe · re-judge · decide"]
|
|
21
|
+
XCUT --> COORD
|
|
22
|
+
COORD --> VERIFY["Verify<br/>quote-ground · adversarially verify criticals"]
|
|
23
|
+
VERIFY --> REPORT["Reporter<br/>one PR comment (CI) · terminal (local)"]
|
|
24
|
+
```
|
|
6
25
|
|
|
7
|
-
|
|
8
|
-
> never blocks a merge and never auto-approves. The package is incubated inside
|
|
9
|
-
> `eas-cli` for fast iteration and is intended to graduate into its own repo; see
|
|
10
|
-
> [`ROADMAP.md`](./ROADMAP.md).
|
|
26
|
+
## Usage
|
|
11
27
|
|
|
12
|
-
|
|
13
|
-
|
|
28
|
+
Run via `npx @expo/code-review-cli <command>` (or the `ecr` / `expo-code-review`
|
|
29
|
+
binary once installed).
|
|
30
|
+
|
|
31
|
+
Reviewing a PR (`--pr`/`ci`) needs the GitHub CLI — `brew install gh && gh auth login`.
|
|
32
|
+
Everything else the reviewer needs (including the `opencode` runtime) ships with the
|
|
33
|
+
package.
|
|
34
|
+
|
|
35
|
+
### First-time setup
|
|
36
|
+
|
|
37
|
+
Scaffold, add credentials, verify.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Scaffold .expo-code-review/ + a CI workflow (--no-workflow to skip)
|
|
41
|
+
npx @expo/code-review-cli init
|
|
42
|
+
```
|
|
14
43
|
|
|
15
|
-
|
|
44
|
+
Then give it model credentials. **Recommended: a Claude Pro/Max subscription** — the
|
|
45
|
+
scaffolded config uses OAuth by default, so just mint a token and export it under the
|
|
46
|
+
env var your `config.jsonc`'s `auth.tokenEnv` names:
|
|
16
47
|
|
|
48
|
+
```bash
|
|
49
|
+
# Mint a Claude Pro/Max token (prints an sk-ant-oat… token)
|
|
50
|
+
claude setup-token
|
|
51
|
+
# Export it under the env var your config.jsonc's auth.tokenEnv names
|
|
52
|
+
export ANTHROPIC_OAUTH_API_KEY=sk-ant-oat...
|
|
53
|
+
# Check env, config, and credentials
|
|
54
|
+
npx @expo/code-review-cli doctor
|
|
17
55
|
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
56
|
+
|
|
57
|
+
Prefer an Anthropic **API key**, or **OpenAI/GPT** or another provider? See
|
|
58
|
+
[Other providers & auth modes](#other-providers) at the bottom.
|
|
59
|
+
|
|
60
|
+
### Reviewing (already configured)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Review working-tree changes; prints here, posts nothing
|
|
64
|
+
ecr review
|
|
65
|
+
# Review a GitHub PR by number (preview only)
|
|
66
|
+
ecr review --pr 4057
|
|
67
|
+
# …and post it as the PR comment
|
|
68
|
+
ecr review --pr 4057 --post
|
|
22
69
|
```
|
|
23
70
|
|
|
71
|
+
In CI it runs automatically from the scaffolded workflows — by label or a `/review`
|
|
72
|
+
comment (see **CI usage**). From Claude Code (or another agent), add a slash command
|
|
73
|
+
that runs it; eas-cli's
|
|
74
|
+
[`/expo-review`](https://github.com/expo/eas-cli/blob/main/.claude/commands/expo-review.md)
|
|
75
|
+
is a ready example to adapt.
|
|
76
|
+
|
|
77
|
+
### Command reference
|
|
78
|
+
|
|
79
|
+
| Command | What it does |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| `ecr init [--no-workflow] [--force]` | Scaffold `.expo-code-review/` (config, agents, prompts) + a CI workflow. |
|
|
82
|
+
| `ecr review [options]` | Review local changes and print an advisory review (default command). |
|
|
83
|
+
| `ecr ci` | Review the current GitHub PR and post/update a comment. For GitHub Actions. |
|
|
84
|
+
| `ecr doctor` | Check environment, config, and model credentials. |
|
|
85
|
+
|
|
86
|
+
(When developing this repo itself, use `bun run src/cli.ts <command>`.)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
<details>
|
|
91
|
+
<summary><b>How it works</b></summary>
|
|
92
|
+
|
|
24
93
|
- **Source** — local git (working tree, staged, or a ref range) or a GitHub PR
|
|
25
94
|
(diff + metadata fetched over the `gh` API).
|
|
26
95
|
- **Noise filter** — drops lockfiles, generated bundles/maps, snapshots, files
|
|
@@ -33,6 +102,8 @@ diff source ─▶ noise filter ─▶ chunk ─▶ agents (parallel) ─▶ coo
|
|
|
33
102
|
run in parallel with read-only repo tools (`read`/`grep`/`glob`/`list`).
|
|
34
103
|
- **Coordinator** — a single pass that dedupes, re-judges severity, and produces
|
|
35
104
|
the final `{ decision, findings, summary }`.
|
|
105
|
+
- **Verify** — quote-grounds every finding against the real file and adversarially
|
|
106
|
+
verifies criticals, so a confident-but-wrong finding doesn't ship.
|
|
36
107
|
- **Reporter** — posts/updates a single fingerprinted PR comment (CI), or prints
|
|
37
108
|
a grouped summary (local). Findings below the configured severity floor are
|
|
38
109
|
suppressed.
|
|
@@ -40,19 +111,10 @@ diff source ─▶ noise filter ─▶ chunk ─▶ agents (parallel) ─▶ coo
|
|
|
40
111
|
Built on the [OpenCode](https://opencode.ai) SDK, which spawns the model provider
|
|
41
112
|
and applies Anthropic prompt caching automatically.
|
|
42
113
|
|
|
43
|
-
|
|
114
|
+
</details>
|
|
44
115
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
| Command | What it does |
|
|
49
|
-
| --- | --- |
|
|
50
|
-
| `ecr review [options]` | Review local changes and print an advisory review (default command). |
|
|
51
|
-
| `ecr ci` | Review the current GitHub PR and post/update a comment. For GitHub Actions. |
|
|
52
|
-
| `ecr init [--with-workflow] [--force]` | Scaffold `.expo-code-review/` (config, agents, prompts) in this repo. |
|
|
53
|
-
| `ecr doctor` | Check environment, config, and model credentials. |
|
|
54
|
-
|
|
55
|
-
### `ecr review` options
|
|
116
|
+
<details>
|
|
117
|
+
<summary><b><code>ecr review</code> options</b></summary>
|
|
56
118
|
|
|
57
119
|
```
|
|
58
120
|
--base <ref> Base ref to diff against (default: merge-base with default branch)
|
|
@@ -70,18 +132,14 @@ or as the `ecr` / `expo-code-review` binary once built/installed.
|
|
|
70
132
|
-h, --help Show help
|
|
71
133
|
```
|
|
72
134
|
|
|
73
|
-
Reviewing a PR without checking it out — preview, then optionally post:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
ecr review --pr 4057 # print the review here; posts nothing
|
|
77
|
-
ecr review --pr 4057 --post # re-run and post it as the PR comment
|
|
78
|
-
```
|
|
79
|
-
|
|
80
135
|
`--pr` uses the PR's diff (authoritative) but reads your checked-out files for
|
|
81
136
|
surrounding context; for full fidelity, `gh pr checkout <n>` first and run a plain
|
|
82
137
|
`ecr review`.
|
|
83
138
|
|
|
84
|
-
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary><b>Configuration — <code>.expo-code-review/</code></b></summary>
|
|
85
143
|
|
|
86
144
|
```
|
|
87
145
|
.expo-code-review/
|
|
@@ -101,14 +159,19 @@ surrounding context; for full fidelity, `gh pr checkout <n>` first and run a pla
|
|
|
101
159
|
---
|
|
102
160
|
description: One line the router uses to decide relevance.
|
|
103
161
|
alwaysRun: true # run even when the router would skip this agent
|
|
104
|
-
model: anthropic/claude-sonnet-5 # override the default model
|
|
162
|
+
model: anthropic/claude-sonnet-5 # override the default model
|
|
105
163
|
temperature: 0.1
|
|
106
164
|
---
|
|
107
165
|
|
|
108
166
|
# Agent instructions in Markdown…
|
|
109
167
|
```
|
|
110
168
|
|
|
111
|
-
|
|
169
|
+
For a real-world example, see eas-cli's
|
|
170
|
+
[`.expo-code-review/`](https://github.com/expo/eas-cli/tree/main/.expo-code-review)
|
|
171
|
+
— correctness/security/consistency agents, Opus for security + the coordinator, and
|
|
172
|
+
per-repo `noise.additionalIgnores`.
|
|
173
|
+
|
|
174
|
+
`config.jsonc` (JSONC — comments + trailing commas supported):
|
|
112
175
|
|
|
113
176
|
```jsonc
|
|
114
177
|
{
|
|
@@ -119,142 +182,115 @@ temperature: 0.1
|
|
|
119
182
|
"breakGlass": { "marker": "/skip-review" }, // PR body marker that skips the review
|
|
120
183
|
"commentTag": "expo-ai-code-reviewer", // hidden tag used to find/update the comment
|
|
121
184
|
"auth": { "mode": "oauth", "provider": "anthropic",
|
|
122
|
-
"tokenEnv": "
|
|
185
|
+
"tokenEnv": "ANTHROPIC_OAUTH_API_KEY" }
|
|
123
186
|
}
|
|
124
187
|
```
|
|
125
188
|
|
|
126
|
-
|
|
189
|
+
</details>
|
|
127
190
|
|
|
128
|
-
|
|
191
|
+
<details>
|
|
192
|
+
<summary><b>Model selection</b></summary>
|
|
129
193
|
|
|
130
|
-
|
|
194
|
+
Precedence: **`REVIEWER_MODEL` env** (global override) → per-file **frontmatter
|
|
195
|
+
`model:`** → **`config.jsonc` `model`** (the default). So a repo can run a mixed
|
|
196
|
+
setup, and a developer can override everything locally.
|
|
131
197
|
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
`
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
OpenCode login instead of the repo's configured credentials — handy locally
|
|
140
|
-
(e.g. `REVIEWER_MODEL=openai/gpt-5.4-mini-fast`). There is no shared fallback key;
|
|
141
|
-
if a run fails for lack of credentials, authenticate a provider in OpenCode.
|
|
142
|
-
|
|
143
|
-
Run `ecr doctor` to diagnose setup.
|
|
144
|
-
|
|
145
|
-
## Model selection
|
|
146
|
-
|
|
147
|
-
Models are resolved with this precedence: **`REVIEWER_MODEL` env** (global override)
|
|
148
|
-
→ per-file **frontmatter `model:`** → **`config.jsonc` `model`** (the default). So a
|
|
149
|
-
repo can run a mixed setup, and a developer can override everything locally.
|
|
150
|
-
|
|
151
|
-
Rules of thumb for the reviewer's workload:
|
|
152
|
-
|
|
153
|
-
- **Specialist agents** (correctness/security/consistency) do the real bug-finding
|
|
154
|
-
and benefit from a reasoning-tier model — **Sonnet** is the quality/speed sweet
|
|
155
|
-
spot (the default for correctness/consistency). **Opus** finds more but is slower
|
|
156
|
-
and more rate-limited, which makes large-PR timeouts worse — so scope it to the
|
|
157
|
-
one agent where the extra threat-model reasoning pays off most: **security runs on
|
|
158
|
-
Opus** (set in `security.md` frontmatter), the rest on Sonnet. This keeps the
|
|
159
|
-
latency/rate-limit cost to a single agent, and the timeout handling (subdivide +
|
|
160
|
-
per-fetch deadline) keeps a slow Opus pass from hanging the run.
|
|
161
|
-
- **The coordinator** only consolidates text (no repo tools), so a fast, cheap
|
|
162
|
-
model — **Haiku** — fits well and keeps the serial tail short. Set it in
|
|
163
|
-
`coordinator.md` frontmatter.
|
|
198
|
+
- **Specialist agents** (correctness/security/consistency) benefit from a
|
|
199
|
+
reasoning-tier model — **Sonnet** is the quality/speed sweet spot (default for
|
|
200
|
+
correctness/consistency). **Opus** finds more but is slower and more
|
|
201
|
+
rate-limited, so scope it to the highest-stakes agent: **security runs on Opus**
|
|
202
|
+
(set in `security.md` frontmatter), the rest on Sonnet.
|
|
203
|
+
- **The coordinator** makes the final call (dedupe / re-judge / decide) — worth a
|
|
204
|
+
strong model; set it in `coordinator.md` frontmatter.
|
|
164
205
|
- If latency/timeouts dominate on big PRs, moving the specialists to a faster model
|
|
165
206
|
is the most direct lever (a real recall tradeoff — measure it).
|
|
166
207
|
|
|
167
|
-
Example mixed setup:
|
|
168
|
-
|
|
169
|
-
```jsonc
|
|
170
|
-
// config.jsonc
|
|
171
|
-
"model": "anthropic/claude-sonnet-5" // default: specialists + cross-file pass
|
|
172
|
-
```
|
|
173
|
-
```markdown
|
|
174
|
-
<!-- security.md frontmatter --> → Opus for the highest-stakes agent
|
|
175
|
-
---
|
|
176
|
-
model: anthropic/claude-opus-4-8
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
<!-- coordinator.md frontmatter --> → Haiku for the text-only consolidation
|
|
180
|
-
---
|
|
181
|
-
model: anthropic/claude-haiku-4-5-20251001
|
|
182
|
-
---
|
|
183
|
-
```
|
|
184
|
-
|
|
185
208
|
There is no automatic cross-provider "equivalent" fallback — that would silently
|
|
186
209
|
change which model reviewed your code. Use an explicit override instead.
|
|
187
210
|
|
|
188
|
-
|
|
211
|
+
</details>
|
|
189
212
|
|
|
190
|
-
|
|
191
|
-
|
|
213
|
+
<details>
|
|
214
|
+
<summary><b>Reliability</b> — never hangs, never silently drops work</summary>
|
|
192
215
|
|
|
193
|
-
- **Per-task time caps** —
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
job's `timeout-minutes` (60), since the coordinator + verification run afterward.
|
|
216
|
+
- **Per-task time caps** — chunk passes 15 min; cross-cutting 25 min; coordinator
|
|
217
|
+
10 min. A global passes budget (32 min) bounds all passes incl. the subdivision
|
|
218
|
+
waves, fitting inside the CI job's `timeout-minutes` (60).
|
|
197
219
|
- **Tool-call cap** — a pass that makes too many `read`/`grep` calls without
|
|
198
|
-
finishing is *wandering*, not converging
|
|
199
|
-
timeout). Hitting the cap trips the same soft landing as the time cap.
|
|
220
|
+
finishing is *wandering*, not converging; hitting the cap trips the soft landing.
|
|
200
221
|
- **Soft landing on timeout** — at either cap, the run is interrupted and the agent
|
|
201
222
|
is asked to return the findings it already has, rather than discarding its work.
|
|
202
|
-
- **Subdivide-on-timeout —
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
fast **no-tools fallback** reviews just its inlined diff (a lighter review, but
|
|
207
|
-
never nothing). Only if *that* can't finish inside the budget is a coverage gap
|
|
208
|
-
reported — and it is always reported, never silent.
|
|
223
|
+
- **Subdivide-on-timeout** — a pass that times out with nothing to show has its
|
|
224
|
+
chunk split in half and the halves re-reviewed (recursively, down to a single
|
|
225
|
+
file), then a fast **no-tools fallback** over the inlined diff. Only a genuinely
|
|
226
|
+
un-reducible pass reports a coverage gap — and it is always reported, never silent.
|
|
209
227
|
- **Parse failures are retried** (same session, then once in a bounded fresh
|
|
210
|
-
session)
|
|
211
|
-
- **A failed run never reads as "Approve"** —
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
actions, labels = persistent configuration.**
|
|
227
|
-
|
|
228
|
-
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
or `secrets` finding can never be hidden this way. (Also: an inline
|
|
248
|
-
`expo-code-review-ignore` comment on/above a line suppresses that line's findings,
|
|
249
|
-
same critical/secrets carve-out.)
|
|
250
|
-
|
|
251
|
-
These workflows are comment-only (they never fail the PR's checks). For security,
|
|
252
|
-
they build/run only the trusted base ref (never the PR head) — see the comment at
|
|
253
|
-
the top of each file.
|
|
254
|
-
|
|
255
|
-
## Run logs
|
|
228
|
+
session) — separate from the timeout path.
|
|
229
|
+
- **A failed run never reads as "Approve"** — all passes fail → "could not
|
|
230
|
+
complete"; some fail → never a clean approve, and coverage-reduced.
|
|
231
|
+
- **The coordinator can't sink the run** — if consolidation fails, findings are
|
|
232
|
+
merged deterministically and still posted.
|
|
233
|
+
- **Coverage notes** — passes that timed out/failed are listed (routine noise
|
|
234
|
+
filtering is *not* flagged — it's expected and stays in the run log).
|
|
235
|
+
- **CI always gets a terminal state** — on any failure the PR gets a "didn't run"
|
|
236
|
+
comment, not a stuck reaction and silence.
|
|
237
|
+
|
|
238
|
+
</details>
|
|
239
|
+
|
|
240
|
+
<details>
|
|
241
|
+
<summary><b>CI usage</b></summary>
|
|
242
|
+
|
|
243
|
+
`ecr init --with-workflow` scaffolds a `pull_request` workflow. Split along a clean
|
|
244
|
+
line: **comments = one-shot actions, labels = persistent configuration.**
|
|
245
|
+
|
|
246
|
+
- **command workflow** — one-shot `/review` comments (maintainers): `/review`
|
|
247
|
+
(router picks agents), `/review all`, `/review correctness security`. Never
|
|
248
|
+
changes configuration.
|
|
249
|
+
- **auto workflow** — continuous review, configured by **labels**: `ai-review`
|
|
250
|
+
(router), `ai-review:all`, `ai-review:<agent>` (e.g. `ai-review:security`;
|
|
251
|
+
combine to widen), `ai-review:skip` (opt-out).
|
|
252
|
+
- **dismiss workflow** — `/dismiss <id> [… -- reason]` / `/undismiss <id>`
|
|
253
|
+
(maintainers). Each finding shows a short `` `id:…` ``. Dismissal is a **display
|
|
254
|
+
filter only** — the reviewer still analyzes everything, and a `critical`/`secrets`
|
|
255
|
+
finding can never be hidden. (An inline `expo-code-review-ignore` comment on/above
|
|
256
|
+
a line does the same, with the same critical/secrets carve-out.)
|
|
257
|
+
|
|
258
|
+
These workflows are comment-only (they never fail the PR's checks). The engine runs
|
|
259
|
+
as the published package via `npx`, so no PR-controlled code is built.
|
|
260
|
+
|
|
261
|
+
</details>
|
|
262
|
+
|
|
263
|
+
<details>
|
|
264
|
+
<summary><b>Run logs</b></summary>
|
|
256
265
|
|
|
257
266
|
Each run appends a JSON line to `.expo-code-review/.runs/reviews.jsonl` with the
|
|
258
267
|
inputs, decision, finding count, duration, per-agent cost, and aggregate token
|
|
259
|
-
usage (
|
|
268
|
+
usage (incl. prompt-cache read/write counts) — for auditing and measuring
|
|
260
269
|
cost/latency/cache reuse over time.
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
<a id="other-providers"></a>
|
|
274
|
+
<details>
|
|
275
|
+
<summary><b>Other providers & auth modes</b></summary>
|
|
276
|
+
|
|
277
|
+
The recommended setup is a Claude Pro/Max subscription (OAuth) — see Usage above.
|
|
278
|
+
Alternatives, all set in `config.auth` (credentials come from OpenCode):
|
|
279
|
+
|
|
280
|
+
- **Anthropic API key** — set `auth.mode` to `"api-key"` and point `tokenEnv` at the
|
|
281
|
+
env var holding the key (e.g. `ANTHROPIC_API_KEY`); it's sent as `x-api-key`. Omit
|
|
282
|
+
the `auth` block entirely to fall back to OpenCode's own login / `ANTHROPIC_API_KEY`.
|
|
283
|
+
- **OAuth (Pro/Max)** — `tokenEnv` holds an `sk-ant-oat…` token from
|
|
284
|
+
`claude setup-token` (*not* an x-api-key); it's written to an isolated OpenCode
|
|
285
|
+
`auth.json` as a bearer credential, using the native subscription path.
|
|
286
|
+
- **OpenAI / GPT, or another provider** — the current path is the `REVIEWER_MODEL`
|
|
287
|
+
env override: `opencode auth login` once (pick the provider), then run with
|
|
288
|
+
e.g. `REVIEWER_MODEL=openai/gpt-5.4-mini-fast`. It overrides every agent's model
|
|
289
|
+
and uses your OpenCode login, so no `auth` block is needed. *(First-class
|
|
290
|
+
per-provider config — Anthropic/OpenAI/others in `config.jsonc`, and mixing them
|
|
291
|
+
per agent — is on the [roadmap](./ROADMAP.md).)*
|
|
292
|
+
|
|
293
|
+
There is no shared fallback key; if a run fails for lack of credentials, authenticate
|
|
294
|
+
a provider in OpenCode. `ecr doctor` diagnoses setup.
|
|
295
|
+
|
|
296
|
+
</details>
|
package/build/commands/init.js
CHANGED
|
@@ -9,12 +9,12 @@ const TEMPLATES_DIR = fileURLToPath(new URL('../../templates/', import.meta.url)
|
|
|
9
9
|
const USAGE = `ecr init — scaffold .expo-code-review/ in the current repo
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
ecr init [--
|
|
12
|
+
ecr init [--no-workflow] [--force]
|
|
13
13
|
|
|
14
14
|
Options:
|
|
15
|
-
--
|
|
16
|
-
--force
|
|
17
|
-
-h, --help
|
|
15
|
+
--no-workflow Skip writing the CI workflow (.github/workflows/expo-code-review.yml)
|
|
16
|
+
--force Overwrite existing files
|
|
17
|
+
-h, --help Show this help
|
|
18
18
|
`;
|
|
19
19
|
export async function initCommand(argv) {
|
|
20
20
|
if (argv.includes('-h') || argv.includes('--help')) {
|
|
@@ -32,7 +32,10 @@ export async function initCommand(argv) {
|
|
|
32
32
|
/** Scaffold .expo-code-review/ (and optionally the CI workflow) into the repo. */
|
|
33
33
|
async function scaffold(argv) {
|
|
34
34
|
const force = argv.includes('--force');
|
|
35
|
-
|
|
35
|
+
// The CI workflow is scaffolded by default (most repos adopting this want it);
|
|
36
|
+
// `--no-workflow` opts out. `--with-workflow` is still accepted as a no-op for
|
|
37
|
+
// back-compat.
|
|
38
|
+
const withWorkflow = !argv.includes('--no-workflow');
|
|
36
39
|
const root = (await repoRoot()) ?? process.cwd();
|
|
37
40
|
const configDir = path.join(root, CONFIG_DIRNAME);
|
|
38
41
|
// Create only the config dir; let copyInto create prompts/ so it reports
|
|
@@ -70,8 +73,8 @@ async function scaffold(argv) {
|
|
|
70
73
|
' 2. Configure a model provider in OpenCode (or set REVIEWER_MODEL).',
|
|
71
74
|
' 3. Run `ecr doctor`, then `ecr review`.',
|
|
72
75
|
withWorkflow
|
|
73
|
-
? ' 4. Add the model-key secret referenced by the workflow.'
|
|
74
|
-
: ' 4.
|
|
76
|
+
? ' 4. Add the model-key secret referenced by the workflow, then add an `ai-review` label to a PR.'
|
|
77
|
+
: ' 4. (No CI workflow written — re-run without `--no-workflow` to add it.)',
|
|
75
78
|
'',
|
|
76
79
|
].join('\n'));
|
|
77
80
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/code-review-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Generic, config-driven AI code reviewer engine. Repos supply their agents via .expo-code-review/.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"dev": "bun run src/cli.ts",
|
|
30
30
|
"test:unit": "bun test",
|
|
31
|
+
"release": "bash scripts/release.sh",
|
|
31
32
|
"prepublishOnly": "rimraf build && tsc -p tsconfig.build.json"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
package/templates/config.jsonc
CHANGED
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
// HTML marker used to find + update the single PR comment. Keep it stable.
|
|
32
32
|
"commentTag": "expo-ai-code-reviewer",
|
|
33
33
|
|
|
34
|
-
// How model credentials are provided.
|
|
35
|
-
// "api-key": tokenEnv holds a provider API key (sent as x-api-key). If you
|
|
36
|
-
// omit `auth`, OpenCode's own login / ANTHROPIC_API_KEY is used.
|
|
34
|
+
// How model credentials are provided. Default: a Claude Pro/Max subscription.
|
|
37
35
|
// "oauth": tokenEnv holds a Claude Pro/Max OAuth token (from
|
|
38
36
|
// `claude setup-token`); it's injected as a bearer credential.
|
|
37
|
+
// "api-key": tokenEnv holds a provider API key (sent as x-api-key). If you
|
|
38
|
+
// omit `auth`, OpenCode's own login / ANTHROPIC_API_KEY is used.
|
|
39
|
+
// For OpenAI/GPT or another provider, omit `auth` and set REVIEWER_MODEL after an
|
|
40
|
+
// `opencode auth login` for that provider.
|
|
39
41
|
"auth": {
|
|
40
|
-
"mode": "
|
|
42
|
+
"mode": "oauth",
|
|
41
43
|
"provider": "anthropic",
|
|
42
|
-
"tokenEnv": "
|
|
44
|
+
"tokenEnv": "ANTHROPIC_OAUTH_API_KEY"
|
|
43
45
|
}
|
|
44
46
|
}
|
package/templates/workflow.yml
CHANGED
|
@@ -2,12 +2,13 @@ name: AI code review
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
5
|
-
types: [opened, synchronize, reopened]
|
|
5
|
+
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
6
6
|
|
|
7
|
-
# Comment-only:
|
|
7
|
+
# Comment-only: read the repo, write PR comments (issue comments API).
|
|
8
8
|
permissions:
|
|
9
9
|
contents: read
|
|
10
10
|
pull-requests: write
|
|
11
|
+
issues: write
|
|
11
12
|
|
|
12
13
|
concurrency:
|
|
13
14
|
group: ai-code-review-${{ github.event.pull_request.number }}
|
|
@@ -16,28 +17,53 @@ concurrency:
|
|
|
16
17
|
jobs:
|
|
17
18
|
review:
|
|
18
19
|
runs-on: ubuntu-latest
|
|
20
|
+
# Opt-in per PR: only run when the `ai-review` label is present. Remove this
|
|
21
|
+
# line to review every PR automatically.
|
|
22
|
+
if: contains(join(github.event.pull_request.labels.*.name, ','), 'ai-review')
|
|
23
|
+
# Backstop so a stalled review fails fast instead of hanging.
|
|
24
|
+
timeout-minutes: 60
|
|
19
25
|
# A reviewer failure must never fail the PR's checks.
|
|
20
26
|
continue-on-error: true
|
|
21
27
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
28
|
+
- uses: actions/checkout@v5
|
|
23
29
|
with:
|
|
24
|
-
|
|
30
|
+
# Shallow is enough — the reviewer gets the diff from the API (`gh`).
|
|
31
|
+
fetch-depth: 1
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
# SECURITY: this workflow checks out the PR's code, including
|
|
34
|
+
# .expo-code-review/config.jsonc, whose auth.tokenEnv names the env var the CLI
|
|
35
|
+
# forwards as the model credential. Refuse to run unless it's the expected value
|
|
36
|
+
# (below / repo var ECR_EXPECTED_TOKEN_ENV) so a PR can't repoint it at another
|
|
37
|
+
# secret in the runner. Keep this in sync with auth.tokenEnv in config.jsonc.
|
|
38
|
+
- name: Guard config.jsonc tokenEnv
|
|
39
|
+
env:
|
|
40
|
+
EXPECTED: ${{ vars.ECR_EXPECTED_TOKEN_ENV || 'ANTHROPIC_OAUTH_API_KEY' }}
|
|
41
|
+
run: |
|
|
42
|
+
values=$(grep -oE '"tokenEnv"[[:space:]]*:[[:space:]]*"[A-Za-z0-9_]+"' .expo-code-review/config.jsonc | sed -E 's/.*"([A-Za-z0-9_]+)"$/\1/')
|
|
43
|
+
count=$(printf '%s\n' "$values" | grep -c .)
|
|
44
|
+
if [ "$count" != "1" ] || [ "$values" != "$EXPECTED" ]; then
|
|
45
|
+
echo "::error::.expo-code-review/config.jsonc auth.tokenEnv must be \"$EXPECTED\" (found: \"${values:-none}\"). Refusing to run so a PR can't redirect which secret is forwarded to the model provider."
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
- uses: actions/setup-node@v5
|
|
27
50
|
with:
|
|
28
51
|
node-version: 24
|
|
52
|
+
# The reviewer runs via npx and never installs with a package manager, so
|
|
53
|
+
# disable setup-node's auto package-manager cache (its post step would try
|
|
54
|
+
# to save an empty cache and error).
|
|
55
|
+
package-manager-cache: false
|
|
29
56
|
|
|
30
57
|
- name: Run AI review
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
# PATH for this process.
|
|
35
|
-
run: npx --yes expo-code-review@latest ci
|
|
58
|
+
# npx installs @expo/code-review-cli and its bundled `opencode` binary and
|
|
59
|
+
# puts them on PATH for this process. Pin @latest to a version to freeze it.
|
|
60
|
+
run: npx --yes -p "@expo/code-review-cli@latest" ecr ci
|
|
36
61
|
continue-on-error: true
|
|
37
62
|
env:
|
|
38
63
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
# Claude Pro/Max OAuth token (from `claude setup-token`) — the env var
|
|
65
|
+
# named by auth.tokenEnv in config.jsonc. Store it as a repo secret.
|
|
66
|
+
# (For an API key instead, set auth.mode "api-key" and pass that key here.)
|
|
67
|
+
ANTHROPIC_OAUTH_API_KEY: ${{ secrets.ANTHROPIC_OAUTH_API_KEY }}
|
|
68
|
+
# Optional: override the model for every agent (uses your OpenCode login).
|
|
43
69
|
REVIEWER_MODEL: ${{ vars.REVIEWER_MODEL }}
|