adversarial-review-gate 2.0.2 → 2.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +78 -0
- package/README.md +152 -45
- package/package.json +7 -2
- package/src/cli/doctor.js +108 -11
- package/src/cli/install.js +300 -35
- package/src/cli/main.js +6 -1
- package/src/cli/uninstall.js +204 -0
- package/src/core/gate.js +91 -11
- package/src/core/load-config.js +18 -9
- package/src/core/process.js +0 -19
- package/src/core/transcript.js +12 -45
- package/src/core/verdict.js +31 -4
- package/src/hosts/claude-code.js +221 -19
- package/src/hosts/index.js +2 -1
- package/src/integrations/opencode/adversarial-reviewer.agent.md +142 -0
- package/src/reviewers/_shared.js +146 -0
- package/src/reviewers/codex.js +59 -99
- package/src/reviewers/custom.js +58 -96
- package/src/reviewers/index.js +5 -3
- package/src/reviewers/opencode.js +126 -162
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adversarial-review",
|
|
3
3
|
"owner": {
|
|
4
|
-
"name": "
|
|
4
|
+
"name": "louisphamdev"
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Internal marketplace hosting the adversarial-review code-quality gate."
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"name": "adversarial-review",
|
|
12
12
|
"source": "./",
|
|
13
|
-
"description": "
|
|
13
|
+
"description": "Stop-hook gate (default: enforced) that forces an adversarial review of significant code changes before an agent finishes."
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adversarial-review",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "NodeJS multi-tool adversarial review gate. Installs a SessionStart baseline hook and a Stop gate hook for Claude Code. Supports multiple host tools (Claude Code, Codex, opencode) with configurable reviewer mappings, policy modes (soft/enforced/strict-ci), and native or wrapper enforcement. Significant code changes must pass an adversarial review before the agent finishes.",
|
|
5
5
|
"author": {
|
|
6
|
-
"name": "
|
|
6
|
+
"name": "louisphamdev"
|
|
7
7
|
},
|
|
8
8
|
"keywords": ["code-review", "quality", "hook", "stop-hook", "adversarial"],
|
|
9
9
|
"hooks": {
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.1.0] - 2026-06-13
|
|
9
|
+
|
|
10
|
+
Public-release perfection pass: hardening, machine-wide install, and docs.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `install --global` (alias `--user`): machine-wide install that writes the
|
|
14
|
+
host/reviewer defaults to `~/.adversarial-review/config.json` and merges the
|
|
15
|
+
Claude Code `SessionStart` + `Stop` hooks into the user-level
|
|
16
|
+
`~/.claude/settings.json`.
|
|
17
|
+
- `uninstall` command (`uninstall [--user]`): removes the hooks this tool wrote
|
|
18
|
+
and the install-registry entry, for both project and machine-wide installs.
|
|
19
|
+
- `install` now merges into an existing Claude Code `settings.json` (preserving
|
|
20
|
+
other keys) instead of replacing it.
|
|
21
|
+
- Additional `doctor` checks for the merged settings, the opencode read-only
|
|
22
|
+
agent, and reviewer isolation.
|
|
23
|
+
- CI workflow (`.github/workflows/ci.yml`): tests on
|
|
24
|
+
`ubuntu-latest`/`windows-latest` x Node 20/22, plus `npm run pack:dry-run`.
|
|
25
|
+
- `SECURITY.md`, `CONTRIBUTING.md`, and this `CHANGELOG.md`.
|
|
26
|
+
- README badges (npm version, CI, license, node) and documentation for the new
|
|
27
|
+
install flags and `uninstall`.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- The installed Claude Code `Stop` hook now carries a **300-second timeout** so a
|
|
31
|
+
debate-tier review is not aborted mid-run.
|
|
32
|
+
- Hardened reviewer isolation: enforced/strict-ci modes reject any reviewer that
|
|
33
|
+
is not `readOnly && noEdit`.
|
|
34
|
+
- More robust coverage parsing in verdict handling.
|
|
35
|
+
- Deduplication of changed-file scope so a file is not reviewed twice.
|
|
36
|
+
- `package.json`: added `prepublishOnly` (test + pack dry-run) and
|
|
37
|
+
`publishConfig.access = public`; added `CHANGELOG.md` to the files allowlist.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
- `skills/adversarial-review-setup/SKILL.md` referenced the wrong package name
|
|
41
|
+
(`adversarial-review` instead of `adversarial-review-gate`).
|
|
42
|
+
|
|
43
|
+
## [2.0.3] - 2026-06-13
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- A fresh install now produces a working `enforced` + opencode gate out of the
|
|
47
|
+
box: the installer creates the read-only opencode `adversarial-reviewer` agent
|
|
48
|
+
(idempotent), writes `reviewers.opencode.readOnlyConfig: true`, and skips the
|
|
49
|
+
install-time agent-existence check so a clean machine can bootstrap.
|
|
50
|
+
|
|
51
|
+
## [2.0.2] - 2026-06-13
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- A package-name-matching `adversarial-review-gate` bin so `npx
|
|
55
|
+
adversarial-review-gate` resolves correctly.
|
|
56
|
+
|
|
57
|
+
## [2.0.1] - 2026-06-13
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
- Installer bin name corrected to `adversarial-review-gate`.
|
|
61
|
+
|
|
62
|
+
## [2.0.0] - 2026-06-13
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
- Initial NodeJS multi-tool adversarial-review gate, replacing the previous
|
|
66
|
+
Python/Claude-plugin implementation.
|
|
67
|
+
- Multi-host support (Claude Code native Stop hook; codex, opencode,
|
|
68
|
+
github-copilot-cli, antigravity via wrapper) with configurable reviewer
|
|
69
|
+
mappings and self-review (`none`) orchestration.
|
|
70
|
+
- Policy modes `soft` / `enforced` / `strict-ci`, layered config
|
|
71
|
+
(default < user < project) with a tighten-only user policy floor.
|
|
72
|
+
- `install`, `check`, `run`, `doctor`, and `hook` commands.
|
|
73
|
+
|
|
74
|
+
[2.1.0]: https://github.com/louisphamdev/adversarial-review/releases/tag/v2.1.0
|
|
75
|
+
[2.0.3]: https://github.com/louisphamdev/adversarial-review/releases/tag/v2.0.3
|
|
76
|
+
[2.0.2]: https://github.com/louisphamdev/adversarial-review/releases/tag/v2.0.2
|
|
77
|
+
[2.0.1]: https://github.com/louisphamdev/adversarial-review/releases/tag/v2.0.1
|
|
78
|
+
[2.0.0]: https://github.com/louisphamdev/adversarial-review/releases/tag/v2.0.0
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# adversarial-review
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/adversarial-review-gate)
|
|
4
|
+
[](https://github.com/louisphamdev/adversarial-review/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
|
|
3
8
|
A NodeJS multi-tool adversarial review gate for coding agents.
|
|
4
9
|
|
|
5
10
|
The gate stops a coding agent from finishing a turn when a significant code
|
|
@@ -29,20 +34,62 @@ npx adversarial-review-gate install \
|
|
|
29
34
|
|
|
30
35
|
The installer detects available host and reviewer tools, verifies each reviewer
|
|
31
36
|
binary (it must resolve on `PATH` and pass a version/auth check), and writes the
|
|
32
|
-
project config plus any native host integration files.
|
|
33
|
-
|
|
37
|
+
project config plus any native host integration files.
|
|
38
|
+
|
|
39
|
+
`--hosts` and `--reviewer` are **required** — there is no interactive wizard
|
|
40
|
+
(one may be added in a future release). Pass them explicitly for a scripted
|
|
41
|
+
setup.
|
|
42
|
+
|
|
43
|
+
What the installer writes for you (idempotent — it never clobbers a file you
|
|
44
|
+
have customized):
|
|
45
|
+
|
|
46
|
+
- The **project config** at `.adversarial-review/config.json` with the
|
|
47
|
+
host → reviewer mapping you chose.
|
|
48
|
+
- For an **opencode** reviewer: the read-only `adversarial-reviewer` opencode
|
|
49
|
+
agent at `~/.config/opencode/agent/adversarial-reviewer.md` (skipped if it
|
|
50
|
+
already exists) **and** `reviewers.opencode.readOnlyConfig: true` in the
|
|
51
|
+
project config, so enforced-mode isolation passes out of the box.
|
|
52
|
+
- For **Claude Code**: the native `SessionStart` + `Stop` hooks in
|
|
53
|
+
`.claude/settings.json`.
|
|
54
|
+
- The user-level install registry at `~/.adversarial-review/install.json`.
|
|
34
55
|
|
|
35
56
|
Supported flags (see `src/cli/install.js`):
|
|
36
57
|
|
|
37
58
|
| Flag | Meaning |
|
|
38
59
|
|---|---|
|
|
39
|
-
| `--hosts a,b` | Comma-separated list of hosts to install (repeatable). |
|
|
40
|
-
| `--reviewer host=reviewer` | Reviewer mapping for a host (repeatable). Use `host=none` for self-review. |
|
|
60
|
+
| `--hosts a,b` | Comma-separated list of hosts to install (repeatable). **Required.** |
|
|
61
|
+
| `--reviewer host=reviewer` | Reviewer mapping for a host (repeatable). Use `host=none` for self-review. **Required per host.** |
|
|
62
|
+
| `--global` / `--user` | Machine-wide install: write the defaults to `~/.adversarial-review/config.json` and merge the Claude Code hooks into your user-level `~/.claude/settings.json` instead of the per-project files. |
|
|
41
63
|
| `--dry-run` | Print every planned write and exit 0 without writing anything. |
|
|
42
64
|
| `--project-config <path>` | Write the project config to an explicit path. |
|
|
43
65
|
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
### Machine-wide install
|
|
67
|
+
|
|
68
|
+
To install once for **every** project on the machine, add `--global` (alias
|
|
69
|
+
`--user`):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx adversarial-review-gate install --global \
|
|
73
|
+
--hosts claude-code,codex \
|
|
74
|
+
--reviewer claude-code=opencode \
|
|
75
|
+
--reviewer codex=opencode
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
This writes the host/reviewer defaults to `~/.adversarial-review/config.json`
|
|
79
|
+
and merges the Claude Code `SessionStart` + `Stop` hooks into your user-level
|
|
80
|
+
`~/.claude/settings.json` (existing keys are preserved). New projects then
|
|
81
|
+
inherit the gate without re-running install per project.
|
|
82
|
+
|
|
83
|
+
### Uninstall
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx adversarial-review-gate uninstall # remove the project install
|
|
87
|
+
npx adversarial-review-gate uninstall --user # remove the machine-wide install
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`uninstall` removes the hooks this tool wrote (from the project or user
|
|
91
|
+
`settings.json`) and the install-registry entry. Re-run `doctor` afterward to
|
|
92
|
+
confirm the gate is no longer active.
|
|
46
93
|
|
|
47
94
|
### After install
|
|
48
95
|
|
|
@@ -55,13 +102,25 @@ project config validity, and the Claude Code session baseline.
|
|
|
55
102
|
|
|
56
103
|
### Machine-wide defaults
|
|
57
104
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
105
|
+
Two distinct user-level files shape every project on the machine:
|
|
106
|
+
|
|
107
|
+
- **`~/.adversarial-review/config.json`** — the user **override layer**. It
|
|
108
|
+
provides host/reviewer defaults and policy that apply across **all** projects.
|
|
109
|
+
As a normal config layer it can either loosen **or** tighten relative to the
|
|
110
|
+
built-in defaults; a project config can in turn override it.
|
|
111
|
+
- **`~/.adversarial-review/policy.json`** — the **policy floor**. It is
|
|
112
|
+
**tighten-only**: it can raise the minimum policy (e.g. force `enforced` or
|
|
113
|
+
`strict-ci`) but no later layer — user config or project config — can ever
|
|
114
|
+
loosen below it.
|
|
115
|
+
|
|
116
|
+
Config is layered in this order, where each later layer overrides the earlier
|
|
117
|
+
ones, and the policy floor is applied last and can only ever tighten:
|
|
62
118
|
|
|
63
119
|
```text
|
|
64
|
-
DEFAULT_CONFIG
|
|
120
|
+
DEFAULT_CONFIG
|
|
121
|
+
< userConfig (~/.adversarial-review/config.json) # override layer (loosen or tighten)
|
|
122
|
+
< projectConfig (.adversarial-review/config.json) # override layer (loosen or tighten)
|
|
123
|
+
< policyFloor (~/.adversarial-review/policy.json) # tighten-only, applied last
|
|
65
124
|
```
|
|
66
125
|
|
|
67
126
|
Example `~/.adversarial-review/config.json`:
|
|
@@ -87,7 +146,8 @@ Example `~/.adversarial-review/config.json`:
|
|
|
87
146
|
|
|
88
147
|
With this in place, a new project inherits the host/reviewer mapping and the
|
|
89
148
|
`enforced` mode without re-running install per project. A project may still ship
|
|
90
|
-
its own `.adversarial-review/config.json` to
|
|
149
|
+
its own `.adversarial-review/config.json` to override these defaults, but it can
|
|
150
|
+
never go below the policy floor in `~/.adversarial-review/policy.json` (see
|
|
91
151
|
[Policy Modes](#policy-modes)).
|
|
92
152
|
|
|
93
153
|
---
|
|
@@ -135,9 +195,23 @@ explicitly `none`.
|
|
|
135
195
|
Claude Code -> codex (external reviewer)
|
|
136
196
|
Codex -> opencode (external reviewer)
|
|
137
197
|
opencode -> none (self-review orchestration)
|
|
138
|
-
GitHub Copilot CLI -> claude-code (external reviewer, if available)
|
|
139
198
|
```
|
|
140
199
|
|
|
200
|
+
The five hosts in the registry, with their enforcement and whether they can
|
|
201
|
+
delegate to an **external** reviewer:
|
|
202
|
+
|
|
203
|
+
| Host | Enforcement | External reviewer? |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `claude-code` | native-enforced | yes |
|
|
206
|
+
| `codex` | wrapper-enforced | yes |
|
|
207
|
+
| `opencode` | wrapper-enforced | yes |
|
|
208
|
+
| `github-copilot-cli` | wrapper-enforced | no — self-review (`none`) only |
|
|
209
|
+
| `antigravity` | wrapper-enforced | no — self-review (`none`) only |
|
|
210
|
+
|
|
211
|
+
`github-copilot-cli` and `antigravity` are marked `supportsExternalReview: false`
|
|
212
|
+
in the registry, so they cannot be mapped to an external reviewer — use
|
|
213
|
+
`--reviewer github-copilot-cli=none` (self-review orchestration) for those.
|
|
214
|
+
|
|
141
215
|
Reviewer tools are verified during install: binary must exist, basic version
|
|
142
216
|
check must succeed, and auth check must pass where available.
|
|
143
217
|
|
|
@@ -162,38 +236,50 @@ unresolved Critical or Important findings.
|
|
|
162
236
|
|
|
163
237
|
## Using opencode as the reviewer (read-only)
|
|
164
238
|
|
|
165
|
-
**
|
|
166
|
-
|
|
239
|
+
**The installer sets this up for you.** When you map any host to
|
|
240
|
+
`--reviewer <host>=opencode`, `install` writes a working read-only opencode
|
|
241
|
+
reviewer with no manual steps:
|
|
242
|
+
|
|
243
|
+
- It creates the bundled `adversarial-reviewer` agent at
|
|
244
|
+
`~/.config/opencode/agent/adversarial-reviewer.md` (idempotent — it is
|
|
245
|
+
**skipped if the file already exists**, so a customized agent is never
|
|
246
|
+
overwritten).
|
|
247
|
+
- It writes `reviewers.opencode.readOnlyConfig: true` into the project config so
|
|
248
|
+
the gate's enforced-mode isolation check passes.
|
|
167
249
|
|
|
168
250
|
opencode is invoked as `opencode run --pure --agent adversarial-reviewer -f <diff>`
|
|
169
|
-
with the review brief delivered on stdin.
|
|
170
|
-
|
|
171
|
-
|
|
251
|
+
with the review brief delivered on stdin.
|
|
252
|
+
|
|
253
|
+
### What the bundled setup guarantees
|
|
172
254
|
|
|
173
|
-
|
|
255
|
+
The agent the installer ships satisfies three invariants that the gate enforces.
|
|
256
|
+
You do not configure these by hand — verify them with
|
|
257
|
+
`npx adversarial-review-gate doctor` and `opencode agent list`:
|
|
174
258
|
|
|
175
|
-
1. **The agent
|
|
259
|
+
1. **The agent is `mode: primary` — NOT `subagent`.** `opencode run --agent`
|
|
176
260
|
rejects a subagent and **silently** falls back to the full-permission default
|
|
177
261
|
agent, printing `Falling back to default agent` to stderr. The gate detects
|
|
178
262
|
that marker and rejects the review as an operational failure
|
|
179
263
|
(`reviewer_agent_fallback`), so a subagent-mode agent can never pass — even if
|
|
180
264
|
it printed a perfect verdict block.
|
|
181
265
|
|
|
182
|
-
2. **
|
|
183
|
-
off,
|
|
184
|
-
|
|
185
|
-
`
|
|
186
|
-
|
|
187
|
-
`reviewers.opencode.readOnlyConfig: true` is set in config — so you must both
|
|
188
|
-
make the agent read-only **and** set that flag.
|
|
266
|
+
2. **The agent is read-only.** `permission` denies everything and tools are
|
|
267
|
+
turned off, and `reviewers.opencode.readOnlyConfig: true` is set, so the
|
|
268
|
+
adapter reports `readOnly === true && noEdit === true`. In
|
|
269
|
+
`enforced`/`strict-ci` the gate refuses any reviewer whose `verify()`
|
|
270
|
+
capabilities are not isolated (`reviewer_not_isolated`).
|
|
189
271
|
|
|
190
|
-
3. **The agent body
|
|
272
|
+
3. **The agent body contains the verdict-block format the gate parses.** The
|
|
191
273
|
brief on stdin carries the per-job `job_id` / `diff_hash` / `payload_hash` /
|
|
192
|
-
`reviewer` / `level`; the agent
|
|
193
|
-
|
|
274
|
+
`reviewer` / `level`; the agent echoes those exact values back inside a single
|
|
275
|
+
`<<<ADVERSARIAL-REVIEW-VERDICT>>> ... <<<END>>>` block (see
|
|
194
276
|
[Verdict Format](#verdict-format)) with nothing after `<<<END>>>`.
|
|
195
277
|
|
|
196
|
-
|
|
278
|
+
### If you customize the agent, keep these invariants
|
|
279
|
+
|
|
280
|
+
Because the installer never overwrites an existing agent file, an edited
|
|
281
|
+
`~/.config/opencode/agent/adversarial-reviewer.md` must still satisfy all three
|
|
282
|
+
invariants above. A minimal shape:
|
|
197
283
|
|
|
198
284
|
```markdown
|
|
199
285
|
---
|
|
@@ -274,11 +360,20 @@ node C:\abs\path\to\adversarial-review\bin\adversarial-review.js run --host code
|
|
|
274
360
|
|
|
275
361
|
## Claude Code (native)
|
|
276
362
|
|
|
277
|
-
Claude Code is the only **native-enforced** host. The installer
|
|
278
|
-
|
|
363
|
+
Claude Code is the only **native-enforced** host. **The installer writes the
|
|
364
|
+
hooks for you** — when `claude-code` is in `--hosts`, `install` merges two hooks
|
|
365
|
+
into `.claude/settings.json` (or, with `--global`, into your user-level
|
|
366
|
+
`~/.claude/settings.json`):
|
|
279
367
|
|
|
280
368
|
- A **SessionStart** hook that records the workspace baseline.
|
|
281
|
-
- A **Stop** hook that applies the gate before
|
|
369
|
+
- A **Stop** hook (with a **300-second timeout**) that applies the gate before
|
|
370
|
+
the turn finishes.
|
|
371
|
+
|
|
372
|
+
### What the bundled setup guarantees
|
|
373
|
+
|
|
374
|
+
The hook commands invoke `adversarial-review-gate` directly when it resolves on
|
|
375
|
+
`PATH` (a global npm install), otherwise via `npx adversarial-review-gate`. The
|
|
376
|
+
written block looks like this:
|
|
282
377
|
|
|
283
378
|
```json
|
|
284
379
|
{
|
|
@@ -288,7 +383,7 @@ Claude Code is the only **native-enforced** host. The installer adds two hooks t
|
|
|
288
383
|
"hooks": [
|
|
289
384
|
{
|
|
290
385
|
"type": "command",
|
|
291
|
-
"command": "
|
|
386
|
+
"command": "adversarial-review-gate hook --host claude-code --event session-start"
|
|
292
387
|
}
|
|
293
388
|
]
|
|
294
389
|
}
|
|
@@ -298,7 +393,8 @@ Claude Code is the only **native-enforced** host. The installer adds two hooks t
|
|
|
298
393
|
"hooks": [
|
|
299
394
|
{
|
|
300
395
|
"type": "command",
|
|
301
|
-
"command": "
|
|
396
|
+
"command": "adversarial-review-gate hook --host claude-code --event stop",
|
|
397
|
+
"timeout": 300
|
|
302
398
|
}
|
|
303
399
|
]
|
|
304
400
|
}
|
|
@@ -307,15 +403,19 @@ Claude Code is the only **native-enforced** host. The installer adds two hooks t
|
|
|
307
403
|
}
|
|
308
404
|
```
|
|
309
405
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
the absolute node path as shown above.
|
|
406
|
+
> The Stop hook carries a **300s timeout** because an external review can take up
|
|
407
|
+
> to a few minutes — Claude Code will not abort the hook before the review
|
|
408
|
+
> finishes.
|
|
314
409
|
|
|
315
410
|
> Both hooks are required. A Stop hook that sees edit evidence but finds **no
|
|
316
411
|
> recorded SessionStart baseline** fails closed (blocks) in `enforced`/`strict-ci`,
|
|
317
|
-
> because the full change scope is unknown. Restart Claude Code after
|
|
318
|
-
> `settings.json`.
|
|
412
|
+
> because the full change scope is unknown. **Restart Claude Code after install**
|
|
413
|
+
> so it re-reads `settings.json`. Verify the hooks with
|
|
414
|
+
> `npx adversarial-review-gate doctor`.
|
|
415
|
+
|
|
416
|
+
> For a **local (non-marketplace) checkout** where the package is not on `PATH`,
|
|
417
|
+
> the hook command needs an **absolute path** to `bin/adversarial-review.js`;
|
|
418
|
+
> `${CLAUDE_PLUGIN_ROOT}` only resolves inside a marketplace plugin.
|
|
319
419
|
|
|
320
420
|
---
|
|
321
421
|
|
|
@@ -337,7 +437,9 @@ never committed.
|
|
|
337
437
|
|
|
338
438
|
## Cost
|
|
339
439
|
|
|
340
|
-
An external opencode review takes roughly **30 seconds** and
|
|
440
|
+
An external opencode review takes roughly **30 seconds** — and a debate-tier
|
|
441
|
+
review on a large or sensitive diff can take **up to a few minutes** (the Claude
|
|
442
|
+
Code Stop hook is given a 300-second timeout for this reason). It **BLOCKS in
|
|
341
443
|
`enforced`** until it passes. With a machine-wide `enforced` config, that gate
|
|
342
444
|
runs on every significant-edit Stop across **all** projects — which adds up
|
|
343
445
|
quickly.
|
|
@@ -563,14 +665,19 @@ Common issues:
|
|
|
563
665
|
## Commands
|
|
564
666
|
|
|
565
667
|
```bash
|
|
566
|
-
npx adversarial-review-gate install # Interactive install wizard
|
|
567
668
|
npx adversarial-review-gate install --hosts claude-code,codex --reviewer claude-code=opencode --reviewer codex=opencode
|
|
568
|
-
npx adversarial-review-gate install --
|
|
669
|
+
npx adversarial-review-gate install --global --hosts claude-code --reviewer claude-code=opencode # Machine-wide
|
|
670
|
+
npx adversarial-review-gate install --dry-run ... # Preview without writing
|
|
671
|
+
npx adversarial-review-gate uninstall # Remove the project install
|
|
672
|
+
npx adversarial-review-gate uninstall --user # Remove the machine-wide install
|
|
569
673
|
npx adversarial-review-gate check # Run the gate manually against current working tree
|
|
570
674
|
npx adversarial-review-gate run --host codex -- codex exec "..." # Wrapper mode
|
|
571
675
|
npx adversarial-review-gate doctor # Verify installation
|
|
572
676
|
```
|
|
573
677
|
|
|
678
|
+
`--hosts` and `--reviewer` are required for `install`; there is no interactive
|
|
679
|
+
wizard (one may be added later).
|
|
680
|
+
|
|
574
681
|
For a local (unpublished) checkout, `npx adversarial-review-gate` becomes
|
|
575
682
|
`node /abs/path/to/adversarial-review/bin/adversarial-review.js`.
|
|
576
683
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adversarial-review-gate",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "NodeJS multi-tool adversarial review gate for coding agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "node --test",
|
|
12
12
|
"doctor": "node ./bin/adversarial-review.js doctor --dry-run",
|
|
13
|
-
"pack:dry-run": "npm pack --dry-run"
|
|
13
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
14
|
+
"prepublishOnly": "npm test && npm run pack:dry-run"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
14
18
|
},
|
|
15
19
|
"files": [
|
|
16
20
|
"bin/",
|
|
17
21
|
"src/",
|
|
18
22
|
".claude-plugin/",
|
|
19
23
|
"README.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
20
25
|
"LICENSE"
|
|
21
26
|
],
|
|
22
27
|
"engines": {
|
package/src/cli/doctor.js
CHANGED
|
@@ -11,31 +11,44 @@
|
|
|
11
11
|
import { readFile } from "node:fs/promises";
|
|
12
12
|
import { existsSync } from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
|
-
import os from "node:os";
|
|
15
14
|
import { fileURLToPath } from "node:url";
|
|
16
15
|
|
|
17
16
|
import { HOSTS } from "../hosts/index.js";
|
|
18
17
|
import { createReviewer } from "../reviewers/index.js";
|
|
19
|
-
import { loadEffectiveConfig } from "../core/load-config.js";
|
|
18
|
+
import { loadEffectiveConfig, resolveHomeDir } from "../core/load-config.js";
|
|
19
|
+
import { detectClaudeCodeHooks, claudeCodeSettingsPath } from "../hosts/claude-code.js";
|
|
20
20
|
|
|
21
21
|
// Paths relative to home / cwd.
|
|
22
22
|
const PROJECT_CONFIG_REL = path.join(".adversarial-review", "config.json");
|
|
23
23
|
const USER_CONFIG_REL = path.join(".adversarial-review", "config.json");
|
|
24
24
|
const USER_POLICY_REL = path.join(".adversarial-review", "policy.json");
|
|
25
|
+
const OPENCODE_AGENT_REL = path.join(
|
|
26
|
+
".config",
|
|
27
|
+
"opencode",
|
|
28
|
+
"agent",
|
|
29
|
+
"adversarial-reviewer.md"
|
|
30
|
+
);
|
|
25
31
|
|
|
26
32
|
// ---------------------------------------------------------------------------
|
|
27
33
|
// Helpers
|
|
28
34
|
// ---------------------------------------------------------------------------
|
|
29
35
|
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Read + tolerantly parse a Claude Code settings.json. Returns {} on any error
|
|
38
|
+
* (missing or corrupt) so the hook-presence detection never throws.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} filePath
|
|
41
|
+
* @returns {Promise<object>}
|
|
42
|
+
*/
|
|
43
|
+
async function readSettingsTolerant(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = await readFile(filePath, "utf8");
|
|
46
|
+
const parsed = JSON.parse(raw);
|
|
47
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
48
|
+
return {};
|
|
49
|
+
} catch {
|
|
50
|
+
return {};
|
|
37
51
|
}
|
|
38
|
-
return os.homedir();
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
/** Read package.json to get the package version. */
|
|
@@ -77,7 +90,7 @@ export async function doctorCommand(argv, io) {
|
|
|
77
90
|
const json = argv.includes("--json");
|
|
78
91
|
const cwd = io.cwd || process.cwd();
|
|
79
92
|
const env = io.env || process.env;
|
|
80
|
-
const home =
|
|
93
|
+
const home = resolveHomeDir(env);
|
|
81
94
|
|
|
82
95
|
// Read package version.
|
|
83
96
|
const version = await readPackageVersion();
|
|
@@ -110,6 +123,23 @@ export async function doctorCommand(argv, io) {
|
|
|
110
123
|
// hosts.)
|
|
111
124
|
const effectiveConfig = await loadEffectiveConfig(cwd, io);
|
|
112
125
|
|
|
126
|
+
// For native hosts (claude-code) detect whether OUR SessionStart + Stop hooks
|
|
127
|
+
// are actually registered in the relevant .claude/settings.json — at BOTH
|
|
128
|
+
// project (<cwd>/.claude) and user (<home>/.claude) scope. A configured host
|
|
129
|
+
// whose hooks are missing means the gate would never fire.
|
|
130
|
+
const projectSettingsPath = claudeCodeSettingsPath(cwd);
|
|
131
|
+
const userSettingsPath = claudeCodeSettingsPath(home);
|
|
132
|
+
const projectHooks = detectClaudeCodeHooks(
|
|
133
|
+
await readSettingsTolerant(projectSettingsPath)
|
|
134
|
+
);
|
|
135
|
+
const userHooks = detectClaudeCodeHooks(
|
|
136
|
+
await readSettingsTolerant(userSettingsPath)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// opencode read-only agent presence (machine-wide, under <home>).
|
|
140
|
+
const opencodeAgentPath = path.join(home, OPENCODE_AGENT_REL);
|
|
141
|
+
const opencodeAgentExists = existsSync(opencodeAgentPath);
|
|
142
|
+
|
|
113
143
|
// Enumerate configured hosts.
|
|
114
144
|
const configuredHostIds = Object.keys(effectiveConfig.hosts || {});
|
|
115
145
|
const hostReports = [];
|
|
@@ -135,6 +165,54 @@ export async function doctorCommand(argv, io) {
|
|
|
135
165
|
reviewerNote: reviewerResult.note || null,
|
|
136
166
|
reviewerReason: reviewerResult.reason || null,
|
|
137
167
|
};
|
|
168
|
+
|
|
169
|
+
// For claude-code, report whether our native hooks are registered. We treat
|
|
170
|
+
// the host as "registered" if BOTH SessionStart + Stop are present at EITHER
|
|
171
|
+
// scope (project or user), since a user-scope install also covers this cwd.
|
|
172
|
+
if (hostId === "claude-code") {
|
|
173
|
+
const projectRegistered = projectHooks.sessionStart && projectHooks.stop;
|
|
174
|
+
const userRegistered = userHooks.sessionStart && userHooks.stop;
|
|
175
|
+
hostReport.hooks = {
|
|
176
|
+
projectSettingsPath,
|
|
177
|
+
userSettingsPath,
|
|
178
|
+
project: projectHooks,
|
|
179
|
+
user: userHooks,
|
|
180
|
+
registered: projectRegistered || userRegistered,
|
|
181
|
+
};
|
|
182
|
+
if (!hostReport.hooks.registered) {
|
|
183
|
+
warnings.push(
|
|
184
|
+
`WARNING: Host "claude-code" is configured but our SessionStart + Stop ` +
|
|
185
|
+
`hooks are NOT registered in ${projectSettingsPath} (project) or ` +
|
|
186
|
+
`${userSettingsPath} (user). Run \`adversarial-review install\` to register them.`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// For an opencode reviewer, report whether the read-only agent exists and
|
|
192
|
+
// whether reviewers.opencode.readOnlyConfig is set — the two settings the
|
|
193
|
+
// README promises and that enforced-mode isolation depends on.
|
|
194
|
+
if (reviewerId === "opencode") {
|
|
195
|
+
const readOnlyConfig =
|
|
196
|
+
effectiveConfig.reviewers?.opencode?.readOnlyConfig === true;
|
|
197
|
+
hostReport.opencode = {
|
|
198
|
+
agentPath: opencodeAgentPath,
|
|
199
|
+
agentExists: opencodeAgentExists,
|
|
200
|
+
readOnlyConfig,
|
|
201
|
+
};
|
|
202
|
+
if (!opencodeAgentExists) {
|
|
203
|
+
warnings.push(
|
|
204
|
+
`WARNING: opencode reviewer for host "${hostId}" but the read-only agent ` +
|
|
205
|
+
`is MISSING at ${opencodeAgentPath}. Run \`adversarial-review install\` to create it.`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (!readOnlyConfig) {
|
|
209
|
+
warnings.push(
|
|
210
|
+
`WARNING: reviewers.opencode.readOnlyConfig is not set for host "${hostId}". ` +
|
|
211
|
+
`Enforced-mode isolation (readOnly && noEdit) will fail without it.`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
138
216
|
hostReports.push(hostReport);
|
|
139
217
|
|
|
140
218
|
// Warn about wrapper/advisory hosts.
|
|
@@ -247,6 +325,25 @@ function printHumanReport(report, io) {
|
|
|
247
325
|
} else {
|
|
248
326
|
w(` reviewer status: UNAVAILABLE (${h.reviewerReason || "unknown"})\n`);
|
|
249
327
|
}
|
|
328
|
+
// Native hook registration (claude-code only).
|
|
329
|
+
if (h.hooks) {
|
|
330
|
+
w(
|
|
331
|
+
` native hooks registered: ${h.hooks.registered ? "yes" : "NO"}\n`
|
|
332
|
+
);
|
|
333
|
+
w(
|
|
334
|
+
` project (${h.hooks.projectSettingsPath}): ` +
|
|
335
|
+
`SessionStart=${h.hooks.project.sessionStart} Stop=${h.hooks.project.stop}\n`
|
|
336
|
+
);
|
|
337
|
+
w(
|
|
338
|
+
` user (${h.hooks.userSettingsPath}): ` +
|
|
339
|
+
`SessionStart=${h.hooks.user.sessionStart} Stop=${h.hooks.user.stop}\n`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
// opencode reviewer details.
|
|
343
|
+
if (h.opencode) {
|
|
344
|
+
w(` opencode agent exists: ${h.opencode.agentExists ? "yes" : "NO"} (${h.opencode.agentPath})\n`);
|
|
345
|
+
w(` opencode readOnlyConfig: ${h.opencode.readOnlyConfig}\n`);
|
|
346
|
+
}
|
|
250
347
|
}
|
|
251
348
|
}
|
|
252
349
|
|