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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +374 -0
  3. package/dist/bin.d.ts +3 -0
  4. package/dist/bin.d.ts.map +1 -0
  5. package/dist/bin.js +19 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/cli.d.ts +14 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +829 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/config.d.ts +61 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +235 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/herdr.d.ts +36 -0
  16. package/dist/herdr.d.ts.map +1 -0
  17. package/dist/herdr.js +196 -0
  18. package/dist/herdr.js.map +1 -0
  19. package/dist/index.d.ts +30 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +30 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/levels.d.ts +9 -0
  24. package/dist/levels.d.ts.map +1 -0
  25. package/dist/levels.js +19 -0
  26. package/dist/levels.js.map +1 -0
  27. package/dist/merge.d.ts +90 -0
  28. package/dist/merge.d.ts.map +1 -0
  29. package/dist/merge.js +441 -0
  30. package/dist/merge.js.map +1 -0
  31. package/dist/node-guard.d.ts +9 -0
  32. package/dist/node-guard.d.ts.map +1 -0
  33. package/dist/node-guard.js +21 -0
  34. package/dist/node-guard.js.map +1 -0
  35. package/dist/panel.d.ts +89 -0
  36. package/dist/panel.d.ts.map +1 -0
  37. package/dist/panel.js +275 -0
  38. package/dist/panel.js.map +1 -0
  39. package/dist/picker.d.ts +24 -0
  40. package/dist/picker.d.ts.map +1 -0
  41. package/dist/picker.js +182 -0
  42. package/dist/picker.js.map +1 -0
  43. package/dist/providers.d.ts +66 -0
  44. package/dist/providers.d.ts.map +1 -0
  45. package/dist/providers.js +544 -0
  46. package/dist/providers.js.map +1 -0
  47. package/dist/report.d.ts +116 -0
  48. package/dist/report.d.ts.map +1 -0
  49. package/dist/report.js +493 -0
  50. package/dist/report.js.map +1 -0
  51. package/dist/resolve.d.ts +58 -0
  52. package/dist/resolve.d.ts.map +1 -0
  53. package/dist/resolve.js +147 -0
  54. package/dist/resolve.js.map +1 -0
  55. package/dist/reviewer-spawn.d.ts +67 -0
  56. package/dist/reviewer-spawn.d.ts.map +1 -0
  57. package/dist/reviewer-spawn.js +94 -0
  58. package/dist/reviewer-spawn.js.map +1 -0
  59. package/dist/reviewer-tools.d.ts +113 -0
  60. package/dist/reviewer-tools.d.ts.map +1 -0
  61. package/dist/reviewer-tools.js +557 -0
  62. package/dist/reviewer-tools.js.map +1 -0
  63. package/dist/runner.d.ts +79 -0
  64. package/dist/runner.d.ts.map +1 -0
  65. package/dist/runner.js +811 -0
  66. package/dist/runner.js.map +1 -0
  67. package/dist/schema.d.ts +63 -0
  68. package/dist/schema.d.ts.map +1 -0
  69. package/dist/schema.js +302 -0
  70. package/dist/schema.js.map +1 -0
  71. package/dist/scope.d.ts +40 -0
  72. package/dist/scope.d.ts.map +1 -0
  73. package/dist/scope.js +372 -0
  74. package/dist/scope.js.map +1 -0
  75. package/dist/snapshot.d.ts +57 -0
  76. package/dist/snapshot.d.ts.map +1 -0
  77. package/dist/snapshot.js +471 -0
  78. package/dist/snapshot.js.map +1 -0
  79. package/dist/status.d.ts +90 -0
  80. package/dist/status.d.ts.map +1 -0
  81. package/dist/status.js +336 -0
  82. package/dist/status.js.map +1 -0
  83. package/dist/thinking.d.ts +53 -0
  84. package/dist/thinking.d.ts.map +1 -0
  85. package/dist/thinking.js +116 -0
  86. package/dist/thinking.js.map +1 -0
  87. package/package.json +81 -0
  88. package/src/bin.ts +20 -0
  89. package/src/cli.ts +996 -0
  90. package/src/config.ts +338 -0
  91. package/src/herdr.ts +231 -0
  92. package/src/index.ts +30 -0
  93. package/src/levels.ts +23 -0
  94. package/src/merge.ts +548 -0
  95. package/src/node-guard.ts +25 -0
  96. package/src/panel.ts +354 -0
  97. package/src/picker.ts +230 -0
  98. package/src/providers.ts +778 -0
  99. package/src/report.ts +594 -0
  100. package/src/resolve.ts +179 -0
  101. package/src/reviewer-spawn.ts +131 -0
  102. package/src/reviewer-tools.ts +715 -0
  103. package/src/runner.ts +1120 -0
  104. package/src/schema.ts +373 -0
  105. package/src/scope.ts +448 -0
  106. package/src/snapshot.ts +536 -0
  107. package/src/status.ts +470 -0
  108. 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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -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
@@ -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"}