council-review 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +19 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +829 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +235 -0
- package/dist/config.js.map +1 -0
- package/dist/herdr.d.ts +36 -0
- package/dist/herdr.d.ts.map +1 -0
- package/dist/herdr.js +196 -0
- package/dist/herdr.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/levels.d.ts +9 -0
- package/dist/levels.d.ts.map +1 -0
- package/dist/levels.js +19 -0
- package/dist/levels.js.map +1 -0
- package/dist/merge.d.ts +90 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +441 -0
- package/dist/merge.js.map +1 -0
- package/dist/node-guard.d.ts +9 -0
- package/dist/node-guard.d.ts.map +1 -0
- package/dist/node-guard.js +21 -0
- package/dist/node-guard.js.map +1 -0
- package/dist/panel.d.ts +89 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +275 -0
- package/dist/panel.js.map +1 -0
- package/dist/picker.d.ts +24 -0
- package/dist/picker.d.ts.map +1 -0
- package/dist/picker.js +182 -0
- package/dist/picker.js.map +1 -0
- package/dist/providers.d.ts +66 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +544 -0
- package/dist/providers.js.map +1 -0
- package/dist/report.d.ts +116 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +493 -0
- package/dist/report.js.map +1 -0
- package/dist/resolve.d.ts +58 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +147 -0
- package/dist/resolve.js.map +1 -0
- package/dist/reviewer-spawn.d.ts +67 -0
- package/dist/reviewer-spawn.d.ts.map +1 -0
- package/dist/reviewer-spawn.js +94 -0
- package/dist/reviewer-spawn.js.map +1 -0
- package/dist/reviewer-tools.d.ts +113 -0
- package/dist/reviewer-tools.d.ts.map +1 -0
- package/dist/reviewer-tools.js +557 -0
- package/dist/reviewer-tools.js.map +1 -0
- package/dist/runner.d.ts +79 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +811 -0
- package/dist/runner.js.map +1 -0
- package/dist/schema.d.ts +63 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +302 -0
- package/dist/schema.js.map +1 -0
- package/dist/scope.d.ts +40 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +372 -0
- package/dist/scope.js.map +1 -0
- package/dist/snapshot.d.ts +57 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +471 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/status.d.ts +90 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +336 -0
- package/dist/status.js.map +1 -0
- package/dist/thinking.d.ts +53 -0
- package/dist/thinking.d.ts.map +1 -0
- package/dist/thinking.js +116 -0
- package/dist/thinking.js.map +1 -0
- package/package.json +81 -0
- package/src/bin.ts +20 -0
- package/src/cli.ts +996 -0
- package/src/config.ts +338 -0
- package/src/herdr.ts +231 -0
- package/src/index.ts +30 -0
- package/src/levels.ts +23 -0
- package/src/merge.ts +548 -0
- package/src/node-guard.ts +25 -0
- package/src/panel.ts +354 -0
- package/src/picker.ts +230 -0
- package/src/providers.ts +778 -0
- package/src/report.ts +594 -0
- package/src/resolve.ts +179 -0
- package/src/reviewer-spawn.ts +131 -0
- package/src/reviewer-tools.ts +715 -0
- package/src/runner.ts +1120 -0
- package/src/schema.ts +373 -0
- package/src/scope.ts +448 -0
- package/src/snapshot.ts +536 -0
- package/src/status.ts +470 -0
- package/src/thinking.ts +148 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Antonije D.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# council-review
|
|
2
|
+
|
|
3
|
+
A read-only, multi-vendor code review panel over local git state.
|
|
4
|
+
|
|
5
|
+
`council-review` sends the work in your git worktree to several independent models from
|
|
6
|
+
**different vendors** — not just different gateways — and merges their findings in deterministic
|
|
7
|
+
code, never through another model. Reviewers cannot write to anything: every host built-in is
|
|
8
|
+
removed, the repository under review cannot load its own extensions or skills into the process
|
|
9
|
+
reviewing it, and the only tools a reviewer has are four read-only ones scoped to a frozen,
|
|
10
|
+
non-writable snapshot of your tree.
|
|
11
|
+
|
|
12
|
+
Every run produces a timestamped directory holding a machine-readable manifest, each reviewer's
|
|
13
|
+
raw output, the merged findings, a human report, and a handoff prompt addressed to a coding agent.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
This package is not yet published to npm, so the recommended install is directly from GitHub.
|
|
18
|
+
|
|
19
|
+
### From GitHub (recommended)
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
npm install -g github:ad-005/council-review
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This installs and builds straight from the repository and works today.
|
|
26
|
+
|
|
27
|
+
### From npm
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
npm install -g council-review
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This will be the install once the package is published to npm. It is not published yet, so this
|
|
34
|
+
command currently fails — the GitHub install above is the one that works.
|
|
35
|
+
|
|
36
|
+
### From source (development)
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
git clone https://github.com/ad-005/council-review.git
|
|
40
|
+
cd council-review
|
|
41
|
+
npm install
|
|
42
|
+
npm run build
|
|
43
|
+
npm link
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use this if you're contributing to `council-review` itself. `npm link` makes the `council-review`
|
|
47
|
+
command available globally from your checkout; alternatively, run it directly with
|
|
48
|
+
`node dist/bin.js`.
|
|
49
|
+
|
|
50
|
+
Requires Node.js 20 or later to install and run `council-review` itself, plus the
|
|
51
|
+
[`pi`](#the-pi-host-dependency) coding-agent CLI on `PATH`, authenticated for at least one
|
|
52
|
+
provider. `pi` itself requires Node.js **>= 22.19.0**, which is the effective floor for actually
|
|
53
|
+
running a review — a Node 20 or 21 install satisfies `council-review`'s own guard but leaves every
|
|
54
|
+
reviewer failing to spawn.
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
cd your-project
|
|
60
|
+
council-review init # pick a panel interactively; writes .council/config.json
|
|
61
|
+
council-review # review the current worktree's work against its base branch
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`init` runs a three-stage picker — providers, then models, then a thinking level per reasoning
|
|
65
|
+
model — and requires at least three models resolving to at least three distinct vendors before it
|
|
66
|
+
will save a panel (the vendor-independence guard; see below). The saved panel is committed
|
|
67
|
+
(`.council/config.json` is meant to be checked in) so the rest of the team reuses it without
|
|
68
|
+
re-picking.
|
|
69
|
+
|
|
70
|
+
Bare `council-review` reviews **the whole of the current worktree's divergence from its base
|
|
71
|
+
branch** — committed, staged, unstaged and untracked-but-not-gitignored changes, folded into one
|
|
72
|
+
patch — not just what's staged. Use the scope flags below to narrow that.
|
|
73
|
+
|
|
74
|
+
## Scope flags
|
|
75
|
+
|
|
76
|
+
| Flag | Meaning |
|
|
77
|
+
| ---------------------------- | ------------------------------------------------ |
|
|
78
|
+
| `--staged` | Review the staged index only |
|
|
79
|
+
| `--range <A..B>` | Review a commit range |
|
|
80
|
+
| `--revision <rev>` | Review a single revision's own change |
|
|
81
|
+
| `--paths <glob>[,<glob>...]` | Narrow to matching paths (repeatable) |
|
|
82
|
+
| `--base <branch>` | Override the configured base branch for this run |
|
|
83
|
+
|
|
84
|
+
`--staged`, `--range` and `--revision` are mutually exclusive. An empty resolved scope (nothing to
|
|
85
|
+
review) is not an error: the tool reports that and exits `0` without spawning a reviewer.
|
|
86
|
+
|
|
87
|
+
## Panel flags
|
|
88
|
+
|
|
89
|
+
| Flag | Meaning |
|
|
90
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `--models <spec>` | One-off panel for this run only: comma-separated `provider/modelId[:level]` entries or globs. Does not touch the saved panel. |
|
|
92
|
+
| `--pick` | Re-open interactive selection; the result **replaces** the saved panel |
|
|
93
|
+
| `--thinking <level>` | Panel-wide thinking level (one of `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`) |
|
|
94
|
+
| `--allow-correlated` | Waive the vendor-independence guard for this run (recorded in the manifest even when it wasn't needed) |
|
|
95
|
+
|
|
96
|
+
`council-review models` lists every discovered, ready model with its provider, derived vendor,
|
|
97
|
+
context window, cost rates and supported thinking levels — no prompt, no model call, works without
|
|
98
|
+
a terminal.
|
|
99
|
+
|
|
100
|
+
### The vendor-independence guard
|
|
101
|
+
|
|
102
|
+
A panel is admitted only with **at least three models resolving to at least three distinct
|
|
103
|
+
vendors** (`unknown` counts as its own distinct vendor, so three unrecognised models still pass).
|
|
104
|
+
This is checked at the end of selection and again immediately before every launch, so a
|
|
105
|
+
hand-edited config is checked too. On refusal, nothing is spawned and the tool exits `4`. Pass
|
|
106
|
+
`--allow-correlated` to waive both the vendor-count and the three-model minimum deliberately.
|
|
107
|
+
|
|
108
|
+
## Run flags
|
|
109
|
+
|
|
110
|
+
| Flag | Meaning |
|
|
111
|
+
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| `--timeout <seconds>` | Per-reviewer wall-clock timeout (overrides configured `timeoutSeconds`) |
|
|
113
|
+
| `--max-tokens <n>` | Per-reviewer output-token ceiling (overrides configured `maxOutputTokens`); with neither the flag nor the config key set, there is no ceiling |
|
|
114
|
+
| `--since <last\|run-id>` | Diff this run's findings against a previous one: `resolved` / `still-present` / `new` |
|
|
115
|
+
| `--fail-on <level\|none>` | Severity threshold for exit code `1` (`critical`, `high`, `medium`, `low`, or `none` to disable) |
|
|
116
|
+
| `--no-suppress` | Ignore `.council/ignore.json` for this run only; the file itself is untouched |
|
|
117
|
+
| `--json` | Emit only the merged findings document on stdout; everything else (progress, diagnostics) goes to stderr |
|
|
118
|
+
|
|
119
|
+
## Environment (herdr) flags
|
|
120
|
+
|
|
121
|
+
Only meaningful inside a [herdr](https://herdr.dev) pane (an agent multiplexer some users run
|
|
122
|
+
coding agents under, detected via an environment marker). herdr is an optional integration —
|
|
123
|
+
install it with `brew install herdr` if you want it — and every one of these flags is gated on
|
|
124
|
+
that marker: outside that environment each degrades to exactly one printed notice, and the review
|
|
125
|
+
itself proceeds and completes identically to how it would with none of these flags supplied — same
|
|
126
|
+
artifacts, same exit code. A failing or unavailable herdr command is likewise reported as a warning
|
|
127
|
+
only; it never changes the review's own outcome.
|
|
128
|
+
|
|
129
|
+
| Flag | Meaning |
|
|
130
|
+
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
131
|
+
| `--pane` | Run the review in a new herdr pane (default direction: horizontal), current working directory carried over, focus left where it was |
|
|
132
|
+
| `--direction <horizontal\|vertical>` | Direction for `--pane` |
|
|
133
|
+
| `--no-pane` | Do not delegate to a new pane even if `--pane` is set |
|
|
134
|
+
| `--handoff <agent>` | Deliver the written handoff prompt to a herdr-managed agent by name; delivery is not awaited, so the review command's own exit code reflects the review only |
|
|
135
|
+
| `--no-notify` | Suppress the herdr completion notification (still raised, conveying completion, after a degraded run) |
|
|
136
|
+
|
|
137
|
+
## Subcommands
|
|
138
|
+
|
|
139
|
+
- **`init [--pick]`** — bootstrap the current repository: run panel selection, write
|
|
140
|
+
`.council/config.json` and `.council/ignore.json`, and ensure `.gitignore` excludes
|
|
141
|
+
`.council/reviews/`. Safe to re-run: existing suppressions are preserved. Must be run inside a
|
|
142
|
+
git repository.
|
|
143
|
+
- **`models`** — list discovered, ready models. No prompt, no model call.
|
|
144
|
+
- **`show [run-id]`** — render a stored run's `REPORT.md`. Defaults to the most recent run. No
|
|
145
|
+
model call.
|
|
146
|
+
- **`ignore <finding-id> [--reason <text>] [--run <run-id>]`** — resolve a finding id from a run's
|
|
147
|
+
merged findings to its fingerprint and append it to `.council/ignore.json`. Idempotent.
|
|
148
|
+
- **`gc [--keep <n>]`** — prune stored runs under `.council/reviews/`, keeping the `<n>` most
|
|
149
|
+
recent (default: the configured `retain`, or 20). Never removes the run the `last` pointer
|
|
150
|
+
resolves to. Also sweeps snapshot directories orphaned by a run that couldn't clean up after
|
|
151
|
+
itself.
|
|
152
|
+
- **`status [--json] [--verify]`** — report whether this project is configured, plus the resolved
|
|
153
|
+
panel, independence-guard verdict, settings, suppression count, stored runs and
|
|
154
|
+
gitignore/herdr state. This is the intended entry point for a coding agent to check in
|
|
155
|
+
milliseconds whether it can run a review here at all, before doing anything else. Exits `0`
|
|
156
|
+
when configured, `2` when not — including outside a git repository, or with an invalid
|
|
157
|
+
`.council/config.json` (the offending key is named in the report) — and never anything else.
|
|
158
|
+
The default form makes no host call and no model call: it reads only files already on disk
|
|
159
|
+
(plus the single `git rev-parse` used to find the repository root). `--verify` additionally
|
|
160
|
+
discovers the model catalog and reports each panel entry's real readiness and effective
|
|
161
|
+
thinking level — a stale panel entry (a model no longer in the catalog) is reported as not
|
|
162
|
+
ready rather than causing the command to fail. `--json` emits the full report as JSON on
|
|
163
|
+
stdout instead of the human-readable form, e.g.:
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"version": "0.1.0",
|
|
167
|
+
"configured": true,
|
|
168
|
+
"repoRoot": "/path/to/project",
|
|
169
|
+
"config": {
|
|
170
|
+
"path": "/path/to/project/.council/config.json",
|
|
171
|
+
"present": true,
|
|
172
|
+
"valid": true,
|
|
173
|
+
"version": 1,
|
|
174
|
+
"error": null,
|
|
175
|
+
"errorKeyPath": null
|
|
176
|
+
},
|
|
177
|
+
"panel": [
|
|
178
|
+
{
|
|
179
|
+
"provider": "anthropic",
|
|
180
|
+
"model": "claude-opus-4",
|
|
181
|
+
"vendor": "anthropic",
|
|
182
|
+
"thinking": "high",
|
|
183
|
+
"effectiveThinking": null,
|
|
184
|
+
"clamped": null,
|
|
185
|
+
"ready": null,
|
|
186
|
+
"readyReason": null
|
|
187
|
+
}
|
|
188
|
+
],
|
|
189
|
+
"independence": {
|
|
190
|
+
"ok": true,
|
|
191
|
+
"modelCount": 3,
|
|
192
|
+
"vendorCount": 3,
|
|
193
|
+
"vendors": { "anthropic": ["anthropic/claude-opus-4"] },
|
|
194
|
+
"reason": null
|
|
195
|
+
},
|
|
196
|
+
"settings": {
|
|
197
|
+
"baseBranch": "main",
|
|
198
|
+
"failOn": "high",
|
|
199
|
+
"timeoutSeconds": 600,
|
|
200
|
+
"maxOutputTokens": null,
|
|
201
|
+
"mergeWindow": 10,
|
|
202
|
+
"claimSimilarity": 0.6,
|
|
203
|
+
"includeContextFiles": false,
|
|
204
|
+
"retain": 20
|
|
205
|
+
},
|
|
206
|
+
"suppressions": {
|
|
207
|
+
"path": "/path/to/project/.council/ignore.json",
|
|
208
|
+
"present": true,
|
|
209
|
+
"count": 4
|
|
210
|
+
},
|
|
211
|
+
"runs": {
|
|
212
|
+
"count": 12,
|
|
213
|
+
"last": {
|
|
214
|
+
"id": "20260904T153012123Z",
|
|
215
|
+
"path": "...",
|
|
216
|
+
"reportPath": "...",
|
|
217
|
+
"findingsPath": "..."
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"gitignore": { "excludesReviews": true },
|
|
221
|
+
"herdr": { "detected": false },
|
|
222
|
+
"verified": false
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Run `council-review <subcommand> --help` for a subcommand's own usage, or `council-review --help`
|
|
227
|
+
for the full flag surface.
|
|
228
|
+
|
|
229
|
+
## Exit codes
|
|
230
|
+
|
|
231
|
+
| Code | Meaning |
|
|
232
|
+
| ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
233
|
+
| `0` | Clean: nothing at or above the `failOn` threshold |
|
|
234
|
+
| `1` | Findings at or above the `failOn` threshold |
|
|
235
|
+
| `2` | Configuration or usage error |
|
|
236
|
+
| `3` | Degraded: one or more reviewers timed out, exceeded budget, or failed validation — a partial report was still written. **Outranks `1`**, so a CI job checking for "findings" never mistakes a partial panel for a clean measurement. |
|
|
237
|
+
| `4` | The vendor-independence guard refused the panel; no reviewer was spawned |
|
|
238
|
+
| `130` | Interrupted (SIGINT/SIGTERM) |
|
|
239
|
+
|
|
240
|
+
## What a run leaves behind
|
|
241
|
+
|
|
242
|
+
Every run writes a timestamped directory under `.council/reviews/` (excluded from version control
|
|
243
|
+
by an entry `init` adds to `.gitignore` — only the configuration in `.council/config.json` and
|
|
244
|
+
`.council/ignore.json` is meant to be committed):
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
.council/reviews/<run-id>/
|
|
248
|
+
manifest.json reviewed state, resolved scope, panel + vendors + thinking levels,
|
|
249
|
+
per-reviewer state/timings/usage/cost/depth, overrides in effect
|
|
250
|
+
patch.diff the diff patch — never inside the snapshot
|
|
251
|
+
findings.json the merged findings, as a JSON array
|
|
252
|
+
REPORT.md human-readable report
|
|
253
|
+
HANDOFF.md a prompt addressed to a coding agent, not a person
|
|
254
|
+
reviewers/<slug>.findings.json | .text.md | .trace.jsonl
|
|
255
|
+
.council/reviews/last -> <run-id> symlink to the most recent run
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`HANDOFF.md` instructs the consuming agent to reproduce each finding before fixing it, to check a
|
|
259
|
+
merged finding against the reviewers' verbatim raw output when its claim is ambiguous, and frames
|
|
260
|
+
a low-agreement finding as a hypothesis to test, not an established defect.
|
|
261
|
+
|
|
262
|
+
## The isolation model and its limits
|
|
263
|
+
|
|
264
|
+
Three third-party models read your repository's source under this tool. That is a real trust
|
|
265
|
+
boundary, and this section describes it plainly rather than reassuringly. See
|
|
266
|
+
[`docs/design/council-review-design.md`](./docs/design/council-review-design.md)'s
|
|
267
|
+
"Decisions" and "Risks / Trade-offs" sections for the full reasoning; this is the summary a user
|
|
268
|
+
needs before relying on it.
|
|
269
|
+
|
|
270
|
+
**What the isolation actually is**, layered so that no single mistake removes it:
|
|
271
|
+
|
|
272
|
+
- Every host built-in tool is removed (`-nbt`) — specifically no shell, no file-write, no
|
|
273
|
+
file-edit capability exists for a reviewer to call.
|
|
274
|
+
- Resource discovery from the reviewed repository is disabled wholesale: no repo-supplied
|
|
275
|
+
extensions, no skills, no prompt templates, and project trust state grants no extra capability
|
|
276
|
+
— so a repository under review cannot get its own code or instructions loaded into the process
|
|
277
|
+
reviewing it.
|
|
278
|
+
- Agent context files (e.g. project-level agent instructions) are **excluded by default**, because
|
|
279
|
+
the host loads them regardless of trust and they are therefore an instruction-injection surface.
|
|
280
|
+
Opt in only via `includeContextFiles` in `.council/config.json`; the choice is recorded in every
|
|
281
|
+
run's manifest.
|
|
282
|
+
- A reviewer's entire tool surface is exactly four read-only tools — `council_read`,
|
|
283
|
+
`council_grep`, `council_list`, `council_git` — loaded from one file this package ships, never
|
|
284
|
+
installed into the host. Every path any of them is given is realpath-resolved and rejected if it
|
|
285
|
+
escapes the snapshot root, including via a symlink or an intermediate symlinked directory.
|
|
286
|
+
`council_git` reaches the _real_ repository, but only through a fixed allowlist of read-only
|
|
287
|
+
subcommands (log, show, blame, diff) with structured arguments — never a composed command line.
|
|
288
|
+
- Reviewers run against a **frozen snapshot**, not your live worktree: a plain copy into scratch,
|
|
289
|
+
made non-writable (`chmod -R a-w`) before any reviewer launches, so every reviewer reads the same
|
|
290
|
+
photograph and your ongoing edits in the meantime have no effect on the run.
|
|
291
|
+
- Removing the shell removes a reviewer's only path to arbitrary network egress. Its only outbound
|
|
292
|
+
traffic is the host's own model API call.
|
|
293
|
+
- Host session persistence is disabled (`--no-session`), so a reviewer's transcript — which
|
|
294
|
+
contains your repository's source — is never written into the host's own session store.
|
|
295
|
+
- Each reviewer process gets an **allowlisted** environment, not a denylisted one: the executable
|
|
296
|
+
search path, the home directory (so the host can find its own config/credentials), the two roots
|
|
297
|
+
the reviewer tools need, and the host's own `PI_*` variables. Every other variable from the
|
|
298
|
+
invoking environment is dropped, so no unrelated secret — including credentials for other
|
|
299
|
+
services — ever reaches a reviewer process.
|
|
300
|
+
- No code path in this package ever opens the host's credential store
|
|
301
|
+
(`~/.pi/agent/auth.json`). No artifact this tool writes can contain a token, key or secret.
|
|
302
|
+
|
|
303
|
+
**What it is not:**
|
|
304
|
+
|
|
305
|
+
- **Not container or VM isolation.** There is no sandbox boundary beyond what's described above.
|
|
306
|
+
This is a deliberate v1 choice (see the design doc's "Decisions"), not an oversight, and a stronger
|
|
307
|
+
implementation can be added later without changing what this tool guarantees observably.
|
|
308
|
+
- **`chmod -R a-w` on the snapshot is not a security boundary against a process running as the
|
|
309
|
+
same user.** A same-user process can `chmod` its way back to writable. Freezing the tree exists
|
|
310
|
+
for _consistency_ — so line numbers mean the same thing to every reviewer and to the merge step
|
|
311
|
+
— not as the source of the read-only guarantee. That guarantee comes entirely from the absence
|
|
312
|
+
of a write tool in the reviewer's callable surface.
|
|
313
|
+
- **POSIX-only.** The freeze step and the path-containment logic assume POSIX permission and
|
|
314
|
+
symlink semantics. This has not been tested, and is not expected to work, on a non-POSIX
|
|
315
|
+
filesystem.
|
|
316
|
+
- **Verified empirically against one pinned host version.** The whole guarantee rests on flags and
|
|
317
|
+
an event-stream shape belonging to the `pi` binary, which this package does not control. See
|
|
318
|
+
[`HOST-VERSION.md`](./HOST-VERSION.md) for exactly what was verified and how, and re-verify
|
|
319
|
+
after any host upgrade before trusting the isolation guarantee against the new version.
|
|
320
|
+
- **A provider whose API key exists only as a shell environment variable will not authenticate
|
|
321
|
+
inside a reviewer.** The environment allowlist above is intentionally tight and does not pass
|
|
322
|
+
through any `*_API_KEY`-shaped variable — this was a deliberate, verified decision (a scrubbed
|
|
323
|
+
environment containing only `PATH`/`HOME` was confirmed to authenticate correctly for providers
|
|
324
|
+
whose credentials live in the host's own store), not an oversight. A provider only ever
|
|
325
|
+
authenticates inside a reviewer through the host's own credential store under `HOME`. **Remedy:**
|
|
326
|
+
authenticate that provider through the host itself (e.g. `pi auth login`) so its credential is
|
|
327
|
+
saved into the host's store, rather than relying on an environment variable your shell happens
|
|
328
|
+
to export. If a provider's credential exists only as a shell variable and was never run through
|
|
329
|
+
the host's own auth flow, reviewers using that provider will fail to authenticate — this is the
|
|
330
|
+
environment allowlist working as designed, not a bug to route around by widening it.
|
|
331
|
+
- **Third-party models read your repository's source.** That is the whole premise of this tool.
|
|
332
|
+
Nothing above prevents a model provider from seeing your code as part of serving the review
|
|
333
|
+
request; it only prevents that model, or the repository it's reviewing, from writing anything,
|
|
334
|
+
executing anything, or reaching the network on its own.
|
|
335
|
+
|
|
336
|
+
### The `pi` host dependency
|
|
337
|
+
|
|
338
|
+
`council-review` spawns the `pi` coding-agent CLI (npm package `@earendil-works/pi-coding-agent`)
|
|
339
|
+
headlessly per reviewer rather than calling each vendor's API directly, so it can reuse the
|
|
340
|
+
authentication, model catalog and agentic tool loop you already have configured for it. `pi`
|
|
341
|
+
declares `engines: { node: ">=22.19.0" }`, so that version — not `council-review`'s own `>=20`
|
|
342
|
+
floor — is what actually determines whether a review can run. See
|
|
343
|
+
[`HOST-VERSION.md`](./HOST-VERSION.md) for the exact verified version and the re-verification
|
|
344
|
+
procedure after a host upgrade.
|
|
345
|
+
|
|
346
|
+
## Configuration reference (`.council/config.json`)
|
|
347
|
+
|
|
348
|
+
Written by `init`, hand-editable afterward. An unrecognised `version` is a hard error, so a future
|
|
349
|
+
schema change has a defined migration point.
|
|
350
|
+
|
|
351
|
+
| Key | Meaning |
|
|
352
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------- |
|
|
353
|
+
| `version` | Config schema version |
|
|
354
|
+
| `baseBranch` | Default base branch for scope resolution |
|
|
355
|
+
| `panel` | Ordered list of `{ provider, model, thinking? }` |
|
|
356
|
+
| `defaultThinkingLevel` | Fallback thinking level when nothing more specific applies |
|
|
357
|
+
| `modelThinkingLevels` | Per-model thinking level, keyed `"provider/modelId"` — what the picker actually writes |
|
|
358
|
+
| `includeContextFiles` | Opt into loading agent context files into reviewers (default: excluded) |
|
|
359
|
+
| `timeoutSeconds` | Per-reviewer wall-clock timeout |
|
|
360
|
+
| `maxOutputTokens` | Per-reviewer output-token ceiling (default: none — reviewers are bounded by `timeoutSeconds` only) |
|
|
361
|
+
| `mergeWindow` | Line-proximity window for clustering findings |
|
|
362
|
+
| `claimSimilarity` | Claim-token-similarity threshold for clustering |
|
|
363
|
+
| `failOn` | Default severity threshold for exit code `1` (`critical`\|`high`\|`medium`\|`low`\|`none`) |
|
|
364
|
+
| `snapshot.include` | Path globs narrowing what's copied into the snapshot (large repos) |
|
|
365
|
+
| `vendorOverrides` | Exact `"provider/modelId"` → vendor overrides for vendor derivation |
|
|
366
|
+
| `retain` | Default number of runs `gc` keeps |
|
|
367
|
+
|
|
368
|
+
`.council/ignore.json` holds suppressed findings by fingerprint (stable across line movement —
|
|
369
|
+
see the design doc's fingerprinting rationale), each optionally carrying a human-supplied `reason`.
|
|
370
|
+
Managed through `council-review ignore`, not normally hand-edited.
|
|
371
|
+
|
|
372
|
+
## License
|
|
373
|
+
|
|
374
|
+
MIT
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* The process entry point for the `council-review` binary. This file's only purpose is to run,
|
|
4
|
+
* so it needs no "am I the actual entry point" detection: the heuristic this replaces (comparing
|
|
5
|
+
* `import.meta.url` against `pathToFileURL(process.argv[1])`) was unfixable for an npm-installed
|
|
6
|
+
* bin, because npm installs a `bin` as a symlink and Node resolves `import.meta.url` to the
|
|
7
|
+
* module's real path while leaving `process.argv[1]` as the symlink path used to invoke it — the
|
|
8
|
+
* two can never match, so the guard silently swallowed every invocation of the installed binary.
|
|
9
|
+
* Splitting the entry point out of `cli.ts` removes the need for that comparison entirely: `cli.ts`
|
|
10
|
+
* stays a pure library module that `test/unit/cli.test.ts` can import without triggering `main()`.
|
|
11
|
+
*/
|
|
12
|
+
import { assertNodeVersion } from './node-guard.js';
|
|
13
|
+
import { runCli } from './cli.js';
|
|
14
|
+
async function main() {
|
|
15
|
+
assertNodeVersion();
|
|
16
|
+
process.exitCode = await runCli(process.argv.slice(2));
|
|
17
|
+
}
|
|
18
|
+
void main();
|
|
19
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,KAAK,UAAU,IAAI;IACjB,iBAAiB,EAAE,CAAC;IACpB,OAAO,CAAC,QAAQ,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,KAAK,IAAI,EAAE,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type PickerIO } from './picker.js';
|
|
2
|
+
/** Dependencies `runCli` accepts beyond argv. `pickerIO` threads through to every call this
|
|
3
|
+
* invocation makes to `pickPanel` (`init`, `init --pick`, and a review run's own `--pick`);
|
|
4
|
+
* omitting it (the production path) leaves `pickPanel` on its own default, real-stdio behaviour
|
|
5
|
+
* unchanged. This exists purely as a test seam -- there is no CLI flag for it, and it is not
|
|
6
|
+
* part of the library's public surface. */
|
|
7
|
+
export interface CliDeps {
|
|
8
|
+
pickerIO?: PickerIO;
|
|
9
|
+
}
|
|
10
|
+
/** Exported for `test/unit/cli.test.ts`, which drives the CLI in-process rather than by spawning
|
|
11
|
+
* a subprocess for every scenario. Not part of the library's public surface (not re-exported from
|
|
12
|
+
* `src/index.ts`). */
|
|
13
|
+
export declare function runCli(argv: readonly string[], deps?: CliDeps): Promise<number>;
|
|
14
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAu6BvD;;;;2CAI2C;AAC3C,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;sBAEsB;AACtB,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,GAAE,OAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAWzF"}
|