amicus 2.1.0 → 3.0.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/plugin.json +1 -1
- package/CHANGELOG.md +83 -0
- package/README.md +13 -5
- package/bin/amicus.js +5 -0
- package/commands/council.md +7 -1
- package/electron/close-guard.js +4 -4
- package/electron/fold.js +8 -8
- package/electron/ipc-guard.js +3 -3
- package/electron/main.js +31 -31
- package/electron/opencode-theme.js +3 -3
- package/electron/preload-content.js +1 -1
- package/package.json +4 -3
- package/skills/second-opinion/COUNCIL-DESIGN.md +68 -4
- package/skills/second-opinion/MODEL-NOTES.md +35 -2
- package/skills/second-opinion/SEAT-BRIEFS.md +190 -0
- package/skills/second-opinion/SKILL.md +72 -11
- package/src/sidecar/electron-install.js +9 -8
- package/src/utils/node-version-guard.js +16 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Christian Wagner"
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,89 @@ All notable changes to Amicus are documented here. Format follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [3.0.0] - 2026-07-15
|
|
9
|
+
|
|
10
|
+
### ⚠️ Breaking
|
|
11
|
+
|
|
12
|
+
- **Node >=22.12 is now required** (`engines.node`). Amicus 3.0 fails fast on older Node with a
|
|
13
|
+
clear message instead of a confusing error deep in provisioning. This is driven by
|
|
14
|
+
`@electron/get` 5.x (ESM-only, requires Node >=22.12), which the Electron self-heal depends on.
|
|
15
|
+
Node 18/20 users — **including headless / council-only users who never touch the GUI** — must
|
|
16
|
+
upgrade Node.
|
|
17
|
+
- **Electron upgraded 28 -> 43.1.1**, which drops OS support for **Windows 8/8.1, Windows Server
|
|
18
|
+
2012/2012 R2, and macOS 11**. The interactive GUI will not run there; headless runs and the
|
|
19
|
+
council are unaffected.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Electron 28.3.3 -> 43.1.1**, clearing the outstanding high-severity `npm audit` finding
|
|
24
|
+
(ASAR Integrity Bypass, GHSA-vmqv-hx8q-j7mg). Amicus runs Electron **unpackaged**, so the
|
|
25
|
+
ASAR-integrity attack class never applied to its deployment — the concrete effect is a clean
|
|
26
|
+
audit and staying on a supported Electron line.
|
|
27
|
+
- **Content view migrated from the deprecated `BrowserView` to `WebContentsView`**
|
|
28
|
+
(`mainWindow.contentView.addChildView`). All four windows now set `sandbox` explicitly.
|
|
29
|
+
- **`@electron/get` 2.x -> 5.x** (now a direct `dependency`, ESM-only). The self-heal defers a
|
|
30
|
+
lazy dynamic `import()` to the network path and bounds the download with an `AbortSignal`
|
|
31
|
+
timeout (5.x dropped the old `got`-style timeout).
|
|
32
|
+
- CI matrices raised to Node 22/24.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Runtime Node-version guard (`src/utils/node-version-guard.js`) fires early in `bin/amicus.js`,
|
|
37
|
+
before heavy imports, so an unsupported Node fails with an actionable message.
|
|
38
|
+
|
|
39
|
+
### Known limitations
|
|
40
|
+
|
|
41
|
+
- `@electron/get` 5.x uses native `fetch`, which does **not** honor `HTTPS_PROXY` / `NO_PROXY`.
|
|
42
|
+
Provisioning Electron behind a corporate proxy needs a manual cache copy or `ELECTRON_MIRROR`
|
|
43
|
+
(see `docs/troubleshooting.md`). Headless runs and the council never download Electron.
|
|
44
|
+
|
|
45
|
+
## [2.2.0] - 2026-07-14
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
- **Optional council elements** (second-opinion skill): four new opt-in behaviors, presented
|
|
50
|
+
together with Claude-in-the-council as a single numbered Stage-0 menu — all default OFF, enabled
|
|
51
|
+
only when the user names them, and the launch confirmation must enumerate what's on:
|
|
52
|
+
- **Critic seat** — one bench member swaps to a four-pass adversarial brief (adversarial pass,
|
|
53
|
+
edge-case hunt, consistency check, executability test), launched as a concurrent solo beside
|
|
54
|
+
the fanout wave (`role: "critic"`). Its findings enter the same anonymized bundle and are
|
|
55
|
+
peer-adjudicated like any other seat's — the council disciplines the critic.
|
|
56
|
+
- **Expert lenses** — each seat reviews through a distinct expert perspective (panel domain
|
|
57
|
+
scoped with the user: business/technical/customer/financial/custom). Lens runs always tally
|
|
58
|
+
`--no-ledger` and the report discloses the weakened cross-review anonymity.
|
|
59
|
+
- **Debate mode** — a new Stage 2.5 rebuttal round: Contested/Disputed findings go back to their
|
|
60
|
+
raisers to DEFEND / AMEND / WITHDRAW, disputing judges re-vote, then the final ledger-recorded
|
|
61
|
+
tally. Exactly one round; withdrawn findings are auto-denied and listed in the report.
|
|
62
|
+
- **Chair verdict scale** — the chair closes with 3–5 hard questions and a final parseable
|
|
63
|
+
`VERDICT: Ship it | Fix these first | Fundamental rethink` line, surfaced at the top of the
|
|
64
|
+
report.
|
|
65
|
+
- New `skills/second-opinion/SEAT-BRIEFS.md` — briefing boilerplate for the elements plus a
|
|
66
|
+
standard anti-sycophancy clause now required in **every** Stage-1 briefing. Critic and lens
|
|
67
|
+
methodologies adapted from the `/critic` and `/debate` agents in John Renaldi's product-kit
|
|
68
|
+
(MIT), with deliberate deviations documented (no findings quota; verdict moved to the chair).
|
|
69
|
+
- Zero engine changes: free-form `runStats[].role` labels, tally-input re-assembly, and the
|
|
70
|
+
existing `--no-ledger` flag cover all four elements.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- `COUNCIL-DESIGN.md` gains §12 documenting the elements, their caveats (critic
|
|
75
|
+
self-identification in cross-review; lens anonymity/ledger trade-offs), and parallel panels as
|
|
76
|
+
future work. The `/council` command accepts pre-requested elements in its arguments.
|
|
77
|
+
- MODEL-NOTES seed: fold-back of the v2.2.0 verification council — claim-class dedup
|
|
78
|
+
adjudication limit, minimax debut (strong critic seat), qwen-coder debut.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- Resurrected both Windows e2e integration suites, silently dead since before the rebrand:
|
|
83
|
+
Node's `spawn()` cannot execute `node_modules/.bin` `.cmd` shims on Windows, so the Electron
|
|
84
|
+
toolbar suite (electron shim) and the OpenCode server test helper ENOENT'd without a visible
|
|
85
|
+
error. The helper now uses the shared `ensureNodeModulesBinInPath()` (which adds the platform
|
|
86
|
+
`opencode.exe` dirs to PATH) and the toolbar suite spawns the real binary via
|
|
87
|
+
`require('electron')`. Also refreshed two stale pins the dead suites never caught: the wave
|
|
88
|
+
document's `schemaVersion` (now pinned to the shared `SCHEMA_VERSION` constant instead of a
|
|
89
|
+
literal `1`) and the pre-rebrand `Sidecar` toolbar brand assertion (now `Amicus`).
|
|
90
|
+
|
|
8
91
|
## [2.1.0] - 2026-07-04
|
|
9
92
|
|
|
10
93
|
### Added
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**A multi-model LLM Council for Claude — with a parallel AI window underneath.**
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|
|
|
9
9
|
Hand Claude a plan, a design, a diff, an architecture decision, a manuscript — anything — and say *council review this*: Amicus routes it through several models from different families, has them anonymously cross-review each other, and a non-Claude chair synthesizes a verdict you turn into accept/deny edits. Or skip the ceremony and **fork** a single conversation to Gemini, GPT, DeepSeek, or any other model — it works in parallel with full context, and you **fold** the result back when you're ready. Claude orchestrates throughout; you stay in your editor.
|
|
10
10
|
|
|
@@ -209,11 +209,19 @@ Everything you need before your first run, and what's optional.
|
|
|
209
209
|
findings-by-tier, cost — no chair prose). This is the default artifact handed to the user.
|
|
210
210
|
- For an **editable source**, the accepted edits land in `<stem>-reviewed.<ext>` next to the original.
|
|
211
211
|
|
|
212
|
-
**
|
|
212
|
+
**Optional council elements** (v2.2.0, all default off): five opt-in behaviors, offered once as a menu at launch — nothing turns on unless you name it, and the confirmation lists exactly what's on.
|
|
213
213
|
|
|
214
|
-
**
|
|
214
|
+
- **Critic seat** — one reviewer swaps to a four-pass adversarial brief (adversarial pass, edge-case hunt, consistency check, executability test). Its findings enter the same anonymized bundle as everyone else's, so the bench disciplines the critic: manufactured negativity lands Disputed and dies in the tally.
|
|
215
|
+
- **Expert lenses** — each reviewer takes a distinct expert perspective; you pick the panel domain (business, technical, customer, financial, or custom). Lens runs never feed the reliability ledger, and the report discloses the weakened cross-review anonymity.
|
|
216
|
+
- **Debate mode** — after cross-review, every Contested or Disputed finding goes back to its raiser to **defend, amend, or withdraw**, and the disputing judges re-vote. Exactly one rebuttal round, then the final tally.
|
|
217
|
+
- **Chair verdict scale** — the chair closes with 3–5 hard questions and one parseable line: `VERDICT: Ship it | Fix these first | Fundamental rethink`.
|
|
218
|
+
- **Claude in the council** — Claude adds its own fresh review to the bundle so the bench ranks and adjudicates it. Claude is *judged* but never votes or chairs, so the verdict stays independent.
|
|
215
219
|
|
|
216
|
-
|
|
220
|
+
The critic and lens methodologies are adapted from the `/critic` and `/debate` agents in [John Renaldi's product-kit](https://github.com/jrenaldi79/plugin-marketplace) (MIT); the briefing boilerplate lives in [`skills/second-opinion/SEAT-BRIEFS.md`](./skills/second-opinion/SEAT-BRIEFS.md).
|
|
221
|
+
|
|
222
|
+
**Cost is disclosed up front.** Before any model launches, you see the run shape — including any enabled optional elements — for example:
|
|
223
|
+
|
|
224
|
+
> This run uses 3 council models across 2 fanout waves + 1 chair call, with critic seat + debate mode ON (~7 base runs + up to 6 rebuttal calls).
|
|
217
225
|
|
|
218
226
|
Then the council waits for your confirmation.
|
|
219
227
|
|
|
@@ -311,7 +319,7 @@ $ amicus status demo123 --json
|
|
|
311
319
|
"taskId": "demo123",
|
|
312
320
|
"status": "complete",
|
|
313
321
|
"elapsed": "5m 0s",
|
|
314
|
-
"version": "
|
|
322
|
+
"version": "3.0.0",
|
|
315
323
|
"model": "google/gemini-2.5-flash",
|
|
316
324
|
"phase": "terminal"
|
|
317
325
|
}
|
package/bin/amicus.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* Routes commands to appropriate handlers.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
// Node version guard: fail fast on unsupported Node versions
|
|
11
|
+
const { checkNodeVersion } = require('../src/utils/node-version-guard');
|
|
12
|
+
const _nv = checkNodeVersion(process.version);
|
|
13
|
+
if (!_nv.ok) { process.stderr.write(_nv.message + '\n'); process.exit(1); }
|
|
14
|
+
|
|
10
15
|
// Load API keys from all sources: process.env > amicus .env > auth.json
|
|
11
16
|
const { loadCredentials } = require('../src/utils/env-loader');
|
|
12
17
|
loadCredentials();
|
package/commands/council.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Run a structured multi-model LLM council review of the given material — wraps the second-opinion skill (independent reviews → anonymous cross-review → non-Claude chair verdict → accept/deny decisions).
|
|
3
|
-
argument-hint: [material, path, or URL] [analysis request + criteria]
|
|
3
|
+
argument-hint: [material, path, or URL] [analysis request + criteria] [optional elements — e.g. "with a critic seat", "debate mode", "expert lenses", "chair verdict scale"]
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -24,3 +24,9 @@ and explicit user confirmation; Stage 1 independent reviews, running
|
|
|
24
24
|
cross-review, followed by `amicus council tally` once cross-review settles;
|
|
25
25
|
Stage 3 council-chair synthesis; Stage 4 the accept/deny decision pass; and
|
|
26
26
|
Stage 5, which runs `amicus council verdict` to write the final `verdict.json`.
|
|
27
|
+
|
|
28
|
+
The user may also name **optional council elements** in the arguments (critic seat,
|
|
29
|
+
expert lenses, debate mode, verdict scale, Claude in the council). All elements
|
|
30
|
+
default OFF; the skill's Stage-0 menu is the single opt-in point. If the user named
|
|
31
|
+
elements here, carry them into Stage 0 as pre-requested — confirm them back by name
|
|
32
|
+
instead of re-asking — and never enable an element the user did not explicitly name.
|
package/electron/close-guard.js
CHANGED
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
* @param {() => boolean} [deps.hasCompleted] - Whether the fold's
|
|
55
55
|
* `[SIDECAR_FOLD]` stdout write has actually succeeded. Falls back to
|
|
56
56
|
* `hasFolded()` when omitted.
|
|
57
|
-
* @param {(mainWindow: object,
|
|
57
|
+
* @param {(mainWindow: object, opencodeView: object) => Promise<void>} deps.triggerFold
|
|
58
58
|
* - The SAME fold.js closure used by the shortcut/toolbar/IPC paths.
|
|
59
|
-
* @returns {{ handleClose: (event: object, mainWindow: object,
|
|
59
|
+
* @returns {{ handleClose: (event: object, mainWindow: object, opencodeView: object) => void }}
|
|
60
60
|
*/
|
|
61
61
|
function createCloseGuard({ hasFolded, isFolding, hasCompleted, triggerFold }) {
|
|
62
62
|
const checkIsFolding = isFolding || hasFolded;
|
|
@@ -78,7 +78,7 @@ function createCloseGuard({ hasFolded, isFolding, hasCompleted, triggerFold }) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function handleClose(event, mainWindow,
|
|
81
|
+
function handleClose(event, mainWindow, opencodeView) {
|
|
82
82
|
if (checkHasCompleted()) {
|
|
83
83
|
// Fold already completed — proceed exactly like the pre-existing
|
|
84
84
|
// behavior (no interception, no destroy call from the guard itself;
|
|
@@ -110,7 +110,7 @@ function createCloseGuard({ hasFolded, isFolding, hasCompleted, triggerFold }) {
|
|
|
110
110
|
}
|
|
111
111
|
closeFoldAttempted = true;
|
|
112
112
|
|
|
113
|
-
Promise.resolve(triggerFold(mainWindow,
|
|
113
|
+
Promise.resolve(triggerFold(mainWindow, opencodeView)).then(() => {
|
|
114
114
|
// triggerFold can RESOLVE without ever calling mainWindow.close() —
|
|
115
115
|
// its outer catch swallows failures (including a synchronous throw
|
|
116
116
|
// from the post-write nudge-overlay executeJavaScript call, which can
|
package/electron/fold.js
CHANGED
|
@@ -41,13 +41,13 @@ function createFoldHandler(state) {
|
|
|
41
41
|
let folded = false;
|
|
42
42
|
let completed = false;
|
|
43
43
|
|
|
44
|
-
async function triggerFold(mainWindow,
|
|
44
|
+
async function triggerFold(mainWindow, opencodeView) {
|
|
45
45
|
if (folded) { return; }
|
|
46
46
|
folded = true;
|
|
47
47
|
completed = false;
|
|
48
48
|
|
|
49
49
|
// Show fold progress in toolbar and content overlay
|
|
50
|
-
showFoldOverlay(mainWindow,
|
|
50
|
+
showFoldOverlay(mainWindow, opencodeView);
|
|
51
51
|
|
|
52
52
|
try {
|
|
53
53
|
// Ask the model to generate a structured summary
|
|
@@ -76,8 +76,8 @@ function createFoldHandler(state) {
|
|
|
76
76
|
logger.info('Fold completed', { taskId: state.taskId });
|
|
77
77
|
|
|
78
78
|
// Show nudge overlay before closing
|
|
79
|
-
if (
|
|
80
|
-
await
|
|
79
|
+
if (opencodeView) {
|
|
80
|
+
await opencodeView.webContents.executeJavaScript(`
|
|
81
81
|
(function() {
|
|
82
82
|
var overlay = document.getElementById('amicus-fold-overlay');
|
|
83
83
|
if (overlay) {
|
|
@@ -147,7 +147,7 @@ function createFoldHandler(state) {
|
|
|
147
147
|
* Note: The JS strings below contain only hardcoded markup (no user input),
|
|
148
148
|
* so there is no XSS risk from DOM manipulation.
|
|
149
149
|
*/
|
|
150
|
-
function showFoldOverlay(mainWindow,
|
|
150
|
+
function showFoldOverlay(mainWindow, opencodeView) {
|
|
151
151
|
if (mainWindow) {
|
|
152
152
|
mainWindow.webContents.executeJavaScript(`
|
|
153
153
|
(function() {
|
|
@@ -174,7 +174,7 @@ function showFoldOverlay(mainWindow, contentView) {
|
|
|
174
174
|
})();
|
|
175
175
|
`).catch(() => {});
|
|
176
176
|
}
|
|
177
|
-
if (
|
|
177
|
+
if (opencodeView) {
|
|
178
178
|
// Scope token vars to the overlay container so var(--x) resolves without
|
|
179
179
|
// touching OpenCode's own :root (which would clobber its CSS variables).
|
|
180
180
|
const rawCss = tokenCss({ absoluteFontUrls: true });
|
|
@@ -182,9 +182,9 @@ function showFoldOverlay(mainWindow, contentView) {
|
|
|
182
182
|
// custom properties are defined on #amicus-fold-overlay and inherited by
|
|
183
183
|
// its descendants. @font-face blocks are left at global scope (no selector).
|
|
184
184
|
const scopedCss = rawCss.replace(/:root\s*\{/, '#amicus-fold-overlay {');
|
|
185
|
-
|
|
185
|
+
opencodeView.webContents.insertCSS(scopedCss).catch(() => {});
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
opencodeView.webContents.executeJavaScript(`
|
|
188
188
|
(function() {
|
|
189
189
|
if (!document.getElementById('fold-spin-style')) {
|
|
190
190
|
var style = document.createElement('style');
|
package/electron/ipc-guard.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* unit-testable (main.js itself runs heavy Electron side effects at import).
|
|
6
6
|
*
|
|
7
7
|
* - isPrivilegedSender: pin privileged IPC handlers to the toolbar window so a
|
|
8
|
-
* compromised/remote page in the OpenCode
|
|
9
|
-
* - isAllowedContentNavigation: pin the OpenCode
|
|
8
|
+
* compromised/remote page in the OpenCode WebContentsView cannot invoke them (M9).
|
|
9
|
+
* - isAllowedContentNavigation: pin the OpenCode WebContentsView to its localhost
|
|
10
10
|
* origin so it cannot be navigated off to an attacker-controlled page (M9).
|
|
11
11
|
* - handleFatalException: EPIPE stays a no-op; any other uncaught exception is
|
|
12
12
|
* logged and then quits the app rather than leaving a wedged invisible shell
|
|
@@ -26,7 +26,7 @@ function isPrivilegedSender(event, getToolbarWindow) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Whether a navigation target is allowed for the OpenCode content
|
|
29
|
+
* Whether a navigation target is allowed for the OpenCode content WebContentsView.
|
|
30
30
|
* Only the OpenCode localhost origin (any path) is permitted; everything else
|
|
31
31
|
* (external http(s), file:, etc.) is blocked. data: URLs are allowed so the
|
|
32
32
|
* in-app load-error page can render.
|
package/electron/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Amicus Electron Shell - v3
|
|
3
3
|
*
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses WebContentsView to split the window into two physical areas:
|
|
5
5
|
* - Top: OpenCode Web UI (gets its own viewport, no CSS conflicts)
|
|
6
6
|
* - Bottom 40px: Amicus toolbar (branding, task ID, timer, fold button)
|
|
7
7
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Spec Reference: §4.4 Electron Wrapper
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const { app, BrowserWindow,
|
|
15
|
+
const { app, BrowserWindow, WebContentsView, globalShortcut, ipcMain, screen } = require('electron');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const { logger } = require('../src/utils/logger');
|
|
18
18
|
const { buildToolbarHTML, TOOLBAR_H, getBrandName } = require('./toolbar');
|
|
@@ -84,7 +84,7 @@ const OPENCODE_URL = `http://localhost:${OPENCODE_PORT}`;
|
|
|
84
84
|
// ============================================================================
|
|
85
85
|
|
|
86
86
|
let mainWindow = null;
|
|
87
|
-
let
|
|
87
|
+
let opencodeView = null;
|
|
88
88
|
let currentToolbarH = TOOLBAR_H;
|
|
89
89
|
|
|
90
90
|
const foldHandler = createFoldHandler({
|
|
@@ -127,7 +127,7 @@ function createAmicusWindow() {
|
|
|
127
127
|
icon: ICON_PATH,
|
|
128
128
|
webPreferences: {
|
|
129
129
|
preload: path.join(__dirname, 'preload.js'),
|
|
130
|
-
contextIsolation: true, nodeIntegration: false,
|
|
130
|
+
contextIsolation: true, nodeIntegration: false, sandbox: true,
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
|
|
@@ -157,27 +157,27 @@ function createAmicusWindow() {
|
|
|
157
157
|
mainWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(toolbarHtml)}`);
|
|
158
158
|
mainWindow.webContents.on('page-title-updated', (e) => e.preventDefault());
|
|
159
159
|
|
|
160
|
-
//
|
|
160
|
+
// WebContentsView for OpenCode content. It uses a MINIMAL preload that exposes no
|
|
161
161
|
// privileged bridge — the OpenCode page must not be able to reach the fold /
|
|
162
162
|
// settings / update IPC (M9). The toolbar window keeps preload.js.
|
|
163
|
-
|
|
163
|
+
opencodeView = new WebContentsView({
|
|
164
164
|
webPreferences: {
|
|
165
165
|
preload: path.join(__dirname, 'preload-content.js'),
|
|
166
|
-
contextIsolation: true, nodeIntegration: false,
|
|
166
|
+
contextIsolation: true, nodeIntegration: false, sandbox: true,
|
|
167
167
|
}
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
// Pin the content view to the OpenCode localhost origin: block any attempt to
|
|
171
171
|
// navigate it off-origin or open new windows (defense-in-depth, M9). data:
|
|
172
172
|
// URLs (the in-app load-error page) are still allowed by the guard.
|
|
173
|
-
|
|
173
|
+
opencodeView.webContents.on('will-navigate', (event, targetUrl) => {
|
|
174
174
|
if (!isAllowedContentNavigation(targetUrl, OPENCODE_URL)) {
|
|
175
175
|
logger.warn('Blocked content-view navigation', { targetUrl });
|
|
176
176
|
event.preventDefault();
|
|
177
177
|
}
|
|
178
178
|
});
|
|
179
|
-
|
|
180
|
-
// Load OpenCode off-screen first; only attach
|
|
179
|
+
opencodeView.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
|
180
|
+
// Load OpenCode off-screen first; only attach WebContentsView after rebranding
|
|
181
181
|
// to prevent the OpenCode logo/splash from flashing during load.
|
|
182
182
|
mainWindow.on('resize', updateContentBounds);
|
|
183
183
|
|
|
@@ -190,9 +190,9 @@ function createAmicusWindow() {
|
|
|
190
190
|
// is token-driven — it inlines tokenCss() and remaps OpenCode's own :root
|
|
191
191
|
// custom properties — so it tracks the toolbar without brittle class
|
|
192
192
|
// selectors. insertCSS is more reliable than preload DOM injection in a
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
// WebContentsView. NOTE: the live visual match is a user-side CDP/manual check.
|
|
194
|
+
opencodeView.webContents.on('dom-ready', () => {
|
|
195
|
+
opencodeView.webContents.insertCSS(buildOpencodeThemeCSS()).catch(() => {});
|
|
196
196
|
});
|
|
197
197
|
|
|
198
198
|
// Navigate directly to the session URL to bypass the project selection screen.
|
|
@@ -205,7 +205,7 @@ function createAmicusWindow() {
|
|
|
205
205
|
// failsafe, a failed/stalled UI load leaves an invisible window and a
|
|
206
206
|
// silently hung process (the historical "Starting up... | 0 messages" bug).
|
|
207
207
|
const failsafe = attachLoadFailsafe({
|
|
208
|
-
webContents:
|
|
208
|
+
webContents: opencodeView.webContents,
|
|
209
209
|
timeoutMs: parseInt(process.env.AMICUS_GUI_LOAD_TIMEOUT_MS || '', 10) || undefined,
|
|
210
210
|
onFail: ({ reason, errorCode, errorDescription, validatedURL }) => {
|
|
211
211
|
logger.error('OpenCode UI failed to load', {
|
|
@@ -215,12 +215,12 @@ function createAmicusWindow() {
|
|
|
215
215
|
const html = buildLoadErrorHTML({
|
|
216
216
|
url: validatedURL || contentUrl, errorCode, errorDescription
|
|
217
217
|
});
|
|
218
|
-
|
|
218
|
+
opencodeView.webContents
|
|
219
219
|
.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`)
|
|
220
220
|
.catch(() => {});
|
|
221
221
|
}
|
|
222
222
|
// On timeout, show whatever is in flight rather than aborting the load.
|
|
223
|
-
mainWindow.
|
|
223
|
+
mainWindow.contentView.addChildView(opencodeView);
|
|
224
224
|
updateContentBounds();
|
|
225
225
|
if (!process.env.AMICUS_HEADLESS_TEST) {
|
|
226
226
|
mainWindow.show();
|
|
@@ -228,16 +228,16 @@ function createAmicusWindow() {
|
|
|
228
228
|
}
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
opencodeView.webContents.loadURL(contentUrl);
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
opencodeView.webContents.on('did-finish-load', () => {
|
|
234
234
|
// Wait for React to render, then rebrand and show window
|
|
235
235
|
setTimeout(() => {
|
|
236
236
|
rebrandUI().then(() => {
|
|
237
237
|
// Disarm only once the window is actually about to show, so a wedged
|
|
238
238
|
// rebrand/executeJavaScript is still covered by the timeout.
|
|
239
239
|
failsafe.cancel();
|
|
240
|
-
mainWindow.
|
|
240
|
+
mainWindow.contentView.addChildView(opencodeView);
|
|
241
241
|
updateContentBounds();
|
|
242
242
|
if (!process.env.AMICUS_HEADLESS_TEST) {
|
|
243
243
|
mainWindow.show();
|
|
@@ -247,7 +247,7 @@ function createAmicusWindow() {
|
|
|
247
247
|
});
|
|
248
248
|
|
|
249
249
|
globalShortcut.register(FOLD_SHORTCUT, () => {
|
|
250
|
-
foldHandler.triggerFold(mainWindow,
|
|
250
|
+
foldHandler.triggerFold(mainWindow, opencodeView);
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
// Poll toolbar for button clicks (IPC doesn't work with data: URLs).
|
|
@@ -258,7 +258,7 @@ function createAmicusWindow() {
|
|
|
258
258
|
if (!action) { return; }
|
|
259
259
|
mainWindow.webContents.executeJavaScript('window.__amicusToolbarAction = null');
|
|
260
260
|
if (action === 'fold') {
|
|
261
|
-
foldHandler.triggerFold(mainWindow,
|
|
261
|
+
foldHandler.triggerFold(mainWindow, opencodeView);
|
|
262
262
|
} else if (action === 'open-settings') {
|
|
263
263
|
createSettingsChildWindow();
|
|
264
264
|
}
|
|
@@ -300,11 +300,11 @@ function createAmicusWindow() {
|
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
mainWindow.on('close', (event) => {
|
|
303
|
-
closeGuard.handleClose(event, mainWindow,
|
|
303
|
+
closeGuard.handleClose(event, mainWindow, opencodeView);
|
|
304
304
|
});
|
|
305
305
|
mainWindow.on('closed', () => {
|
|
306
306
|
mainWindow = null;
|
|
307
|
-
|
|
307
|
+
opencodeView = null;
|
|
308
308
|
globalShortcut.unregisterAll();
|
|
309
309
|
app.quit();
|
|
310
310
|
});
|
|
@@ -334,7 +334,7 @@ async function createSetupWindow() {
|
|
|
334
334
|
resizable: false,
|
|
335
335
|
webPreferences: {
|
|
336
336
|
preload: path.join(__dirname, 'preload-setup.js'),
|
|
337
|
-
contextIsolation: true, nodeIntegration: false,
|
|
337
|
+
contextIsolation: true, nodeIntegration: false, sandbox: false, // preload-setup.js require()s shell (not sandbox-safe)
|
|
338
338
|
}
|
|
339
339
|
});
|
|
340
340
|
|
|
@@ -356,9 +356,9 @@ async function createSetupWindow() {
|
|
|
356
356
|
// ============================================================================
|
|
357
357
|
|
|
358
358
|
function updateContentBounds() {
|
|
359
|
-
if (!mainWindow || !
|
|
359
|
+
if (!mainWindow || !opencodeView) { return; }
|
|
360
360
|
const [w, h] = mainWindow.getContentSize();
|
|
361
|
-
|
|
361
|
+
opencodeView.setBounds({ x: 0, y: 0, width: w, height: h - currentToolbarH });
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
// Amicus wordmark SVG in the same pixel/block art style as the OpenCode logo.
|
|
@@ -387,12 +387,12 @@ const AMICUS_WORDMARK = [
|
|
|
387
387
|
].join('');
|
|
388
388
|
|
|
389
389
|
function rebrandUI() {
|
|
390
|
-
if (!
|
|
390
|
+
if (!opencodeView) { return Promise.resolve(); }
|
|
391
391
|
const brandName = getBrandName(CLIENT);
|
|
392
392
|
// The OpenCode logo may be hidden (display:none/visibility:hidden) by preload.js
|
|
393
393
|
// or insertCSS before this runs. Use a MutationObserver with a fallback timeout
|
|
394
394
|
// to catch it whenever React renders it into the DOM.
|
|
395
|
-
return
|
|
395
|
+
return opencodeView.webContents.executeJavaScript(`
|
|
396
396
|
(function() {
|
|
397
397
|
document.title = '${brandName}';
|
|
398
398
|
var header = document.querySelector('#root > div > header');
|
|
@@ -436,7 +436,7 @@ function rebrandUI() {
|
|
|
436
436
|
// ============================================================================
|
|
437
437
|
|
|
438
438
|
// These handlers are privileged (fold/settings/update/resize). Only the toolbar
|
|
439
|
-
// window may invoke them — the OpenCode content
|
|
439
|
+
// window may invoke them — the OpenCode content WebContentsView must not (M9). The
|
|
440
440
|
// content view no longer gets a bridge preload, but we still validate the
|
|
441
441
|
// sender as belt-and-suspenders in case a future preload change reintroduces one.
|
|
442
442
|
const fromToolbar = (event) => isPrivilegedSender(event, () => mainWindow);
|
|
@@ -444,7 +444,7 @@ const fromToolbar = (event) => isPrivilegedSender(event, () => mainWindow);
|
|
|
444
444
|
// Amicus mode: fold
|
|
445
445
|
ipcMain.handle('sidecar:fold', (event) => {
|
|
446
446
|
if (!fromToolbar(event)) { return; }
|
|
447
|
-
return foldHandler.triggerFold(mainWindow,
|
|
447
|
+
return foldHandler.triggerFold(mainWindow, opencodeView);
|
|
448
448
|
});
|
|
449
449
|
|
|
450
450
|
// Amicus mode: open settings in a child window
|
|
@@ -498,7 +498,7 @@ function createSettingsChildWindow() {
|
|
|
498
498
|
resizable: false,
|
|
499
499
|
webPreferences: {
|
|
500
500
|
preload: path.join(__dirname, 'preload-setup.js'),
|
|
501
|
-
contextIsolation: true, nodeIntegration: false,
|
|
501
|
+
contextIsolation: true, nodeIntegration: false, sandbox: false, // shares preload-setup.js (shell)
|
|
502
502
|
}
|
|
503
503
|
});
|
|
504
504
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Issue #49 — token-driven theme for the embedded OpenCode web UI.
|
|
5
5
|
*
|
|
6
|
-
* main.js injects this via `
|
|
6
|
+
* main.js injects this via `opencodeView.webContents.insertCSS(...)` on
|
|
7
7
|
* `dom-ready`. Before #49 that hook only HID OpenCode's header/wordmark; this
|
|
8
8
|
* module extends it so the embedded chat surface inherits the clay/gold tokens
|
|
9
9
|
* and matches the token-driven Amicus toolbar (toolbar.js).
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* PREFER overriding OpenCode's OWN :root CSS custom properties — a much more
|
|
14
14
|
* stable surface than `.css-abc123` class selectors. We:
|
|
15
15
|
* 1. inline the canonical token CSS (tokenCss) so OUR vars + @font-face are
|
|
16
|
-
* available inside the
|
|
16
|
+
* available inside the WebContentsView (absolute font URLs, same as toolbar.js
|
|
17
17
|
* / setup-ui-styles.js / load-failsafe.js do),
|
|
18
18
|
* 2. remap a generous superset of OpenCode's plausible theme custom-property
|
|
19
19
|
* names to our tokens (var(--...)), covering the prefixes OpenCode has
|
|
@@ -115,7 +115,7 @@ const ELEMENT_FALLBACKS = `
|
|
|
115
115
|
`;
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
* Build the full theme CSS string injected into the OpenCode
|
|
118
|
+
* Build the full theme CSS string injected into the OpenCode WebContentsView.
|
|
119
119
|
* @returns {string} hide-chrome + inlined tokens + :root overrides + fallbacks.
|
|
120
120
|
*/
|
|
121
121
|
function buildOpencodeThemeCSS() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Content Preload - OpenCode
|
|
2
|
+
* Content Preload - OpenCode WebContentsView (minimal, no privileged bridge)
|
|
3
3
|
*
|
|
4
4
|
* The OpenCode Web UI is remote-ish content: it should NOT be able to reach the
|
|
5
5
|
* privileged sidecar IPC (fold/open-settings/perform-update/...). This preload
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"mcpName": "io.github.BourbonDog/amicus",
|
|
5
5
|
"description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
|
|
6
6
|
"keywords": [
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"check:tarball": "node scripts/check-tarball-lifecycle.js"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
+
"@electron/get": "^5.0.0",
|
|
72
73
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
73
74
|
"@opencode-ai/sdk": "^1.1.36",
|
|
74
75
|
"dotenv": "^17.2.3",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"zod": "^3.0.0"
|
|
79
80
|
},
|
|
80
81
|
"optionalDependencies": {
|
|
81
|
-
"electron": "^
|
|
82
|
+
"electron": "^43.1.1"
|
|
82
83
|
},
|
|
83
84
|
"devDependencies": {
|
|
84
85
|
"chrome-remote-interface": "^0.33.3",
|
|
@@ -90,7 +91,7 @@
|
|
|
90
91
|
"ws": "^8.19.0"
|
|
91
92
|
},
|
|
92
93
|
"engines": {
|
|
93
|
-
"node": ">=
|
|
94
|
+
"node": ">=22.12.0"
|
|
94
95
|
},
|
|
95
96
|
"lint-staged": {
|
|
96
97
|
"src/**/*.js": [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Second Opinion v3 — "LLM Council" Design
|
|
2
2
|
|
|
3
3
|
_Status: implemented (v3). v2 (2026-06-03) added the council mechanics; v3 (2026-06-10) swapped the
|
|
4
|
-
transport onto the Amicus fanout/JSON engine primitives.
|
|
5
|
-
(`V2-COUNCIL-DESIGN.md`, deleted at v3)._
|
|
4
|
+
transport onto the Amicus fanout/JSON engine primitives; v3.1 (2026-07-14) added the optional
|
|
5
|
+
council elements (§12). v2 history lives in git (`V2-COUNCIL-DESIGN.md`, deleted at v3)._
|
|
6
6
|
_Design for `SKILL.md` and `MODEL-NOTES.md` of the `second-opinion` skill._
|
|
7
7
|
|
|
8
8
|
## 1. Intent
|
|
@@ -224,6 +224,70 @@ The append-only `council-ledger.jsonl` (consumed via `amicus council stats`) is
|
|
|
224
224
|
four qualitative tiers, if tiers prove too coarse in practice.
|
|
225
225
|
|
|
226
226
|
## 11. Implementation surface
|
|
227
|
-
- `SKILL.md` — the Stage 0–6 council flow on the v3 transport (WS-3: findings contract, tally assembly recipe, `amicus council tally/stats`, `verdict.json`, ledger auto-append).
|
|
227
|
+
- `SKILL.md` — the Stage 0–6 council flow on the v3 transport (WS-3: findings contract, tally assembly recipe, `amicus council tally/stats`, `verdict.json`, ledger auto-append). v3.1 adds the Stage-0 optional-elements menu, the Stage-1 critic/lens launch blocks, Stage 2.5, and the chair verdict-scale addendum.
|
|
228
|
+
- `SEAT-BRIEFS.md` — v3.1 briefing boilerplate for the optional elements + the standard anti-sycophancy clause.
|
|
228
229
|
- `MODEL-NOTES.md` — qualitative per-model quirks, structural-conformance notes, cost guardrail, Stage-2 briefing tips. Quantitative reliability data now generated by `amicus council stats` (ledger). Engine workarounds that F1/F2/F4 made obsolete were pruned at v3.
|
|
229
|
-
- `src/council/` — the deterministic helpers (`findings.js`, `tally.js`, `verdict.js`, `ledger.js`). No other files.
|
|
230
|
+
- `src/council/` — the deterministic helpers (`findings.js`, `tally.js`, `verdict.js`, `ledger.js`). No other files. **v3.1 required zero engine changes**: `runStats[].role` labels are free-form, the rebuttal round is a tally-input re-assembly, and `--no-ledger` already existed.
|
|
231
|
+
|
|
232
|
+
## 12. Optional council elements (v3.1)
|
|
233
|
+
|
|
234
|
+
The `/critic` and `/debate` behaviors from John Renaldi's product-kit plugin (MIT — the
|
|
235
|
+
elite-advisor and expert-debate-facilitator agents), grafted onto the council. Both source
|
|
236
|
+
agents are single-model theater: one model playing an adversarial reviewer or a panel of
|
|
237
|
+
disagreeing experts, with no external check on manufactured negativity or fake disagreement.
|
|
238
|
+
The council supplies exactly that check — independent models and deterministic peer-validated
|
|
239
|
+
tiers — so the methodologies port over as *optional elements* whose output is disciplined by
|
|
240
|
+
Stage-2 adjudication.
|
|
241
|
+
|
|
242
|
+
**Opt-in contract.** All elements default OFF. They are presented once, as a single numbered
|
|
243
|
+
menu at Stage 0 (after bench/chair selection, before launch confirmation); the user must name
|
|
244
|
+
an element to enable it — silence means off, and the launch confirmation enumerates enabled
|
|
245
|
+
elements by name. "Claude in the council" (§5.4, semantics unchanged) is presented as one item
|
|
246
|
+
of the same menu so there is exactly one opt-in point per run. Elements compose freely except
|
|
247
|
+
critic + lenses on one seat (the critic takes one seat; lenses take the rest).
|
|
248
|
+
|
|
249
|
+
### 12.1 Critic seat
|
|
250
|
+
One bench member swaps its standard brief for an adversarial four-pass brief (adversarial
|
|
251
|
+
pass, edge-case hunt, consistency check, executability test — `SEAT-BRIEFS.md`). Launches as
|
|
252
|
+
a concurrent solo run (the red-team variant pattern); same findings contract; `role: "critic"`.
|
|
253
|
+
Judges are never told the seat exists. Two deliberate deviations from the source agent: the
|
|
254
|
+
minimum-10-findings quota is dropped (quotas force invented findings, which waste the bench's
|
|
255
|
+
adjudication capital — the anti-padding rule replaces it), and the hard questions +
|
|
256
|
+
Ship/Fix/Rethink verdict moved to the chair (§12.4). Known cost: the critic model can
|
|
257
|
+
self-identify its review in the Stage-2 bundle by shape, weakening self-bias wash-out for that
|
|
258
|
+
one seat — disclosed in the report. A single-seat brief variant is judged acceptable to record
|
|
259
|
+
to the ledger (precedent: the red-team variant records normally).
|
|
260
|
+
|
|
261
|
+
### 12.2 Expert lenses
|
|
262
|
+
Each seat gets a distinct expert-perspective brief (panel domain scoped with the user:
|
|
263
|
+
business/technical/specialty/customer/financial/custom). All legs launch as concurrent solos
|
|
264
|
+
(no shared-prompt wave); lens↔model assignment is random and private; `role: "lens:<slug>"`.
|
|
265
|
+
Two standing caveats, both disclosed: (a) **anonymity leak** — every judge can spot its own
|
|
266
|
+
lens-flavored review, so self-bias wash-out is weakened bench-wide; (b) **ledger pollution** —
|
|
267
|
+
lens reviews are not comparable to standard reviews (street-cred and confirm-rate assume a
|
|
268
|
+
shared task), so lens-run tallies always pass `--no-ledger`. The bench-wide task change is what
|
|
269
|
+
separates lenses (no ledger) from the single-seat critic (ledger ok).
|
|
270
|
+
|
|
271
|
+
### 12.3 Debate mode (Stage 2.5 rebuttal round)
|
|
272
|
+
The debate agent's challenge/stress-test rounds, grounded in findings instead of persona
|
|
273
|
+
prose: the Stage-2 tally runs `--no-ledger` (provisional); every Contested/Disputed finding
|
|
274
|
+
goes back to its raiser (defense mini-wave — solo per raiser: DEFEND / AMEND / WITHDRAW), the
|
|
275
|
+
disputing judges re-vote on defended/amended findings (one shared fanout), and the final tally
|
|
276
|
+
runs with the ledger. Exactly one round — the source agent's open-ended rounds become a fixed
|
|
277
|
+
convergence step, or cost is unbounded. Withdrawn findings stay in `findings[]` and are
|
|
278
|
+
auto-recorded `denied` (never presented for user decision). Missing/unparseable rebuttal or
|
|
279
|
+
re-vote lines mean the original claim/verdict stands. No new decision vocabulary and no engine
|
|
280
|
+
changes: `buildVerdict` and the renderer see a normal tally record.
|
|
281
|
+
|
|
282
|
+
### 12.4 Chair verdict scale
|
|
283
|
+
A chair-packet addendum: the chair closes with 3–5 hard questions plus a final parseable
|
|
284
|
+
`VERDICT: Ship it | Fix these first | Fundamental rethink` line, surfaced verbatim at the top
|
|
285
|
+
of `report.md` and in chat. Gives the council the single overall call its tier counts never
|
|
286
|
+
provided. Possible later engine touch: an `overallVerdict` field in `verdict.json` + a
|
|
287
|
+
renderer line (not needed for the prose version).
|
|
288
|
+
|
|
289
|
+
### 12.5 Future work — parallel panels
|
|
290
|
+
The debate agent's parallel-panels + cross-panel synthesis maps to two full council runs over
|
|
291
|
+
different benches or lens-sets plus a super-chair synthesis of where the panels agree,
|
|
292
|
+
disagree, and what each missed. Deferred: cost doubles and the run-folder conventions need
|
|
293
|
+
namespacing. Revisit if lens runs prove valuable.
|
|
@@ -4,8 +4,8 @@ This file is the `second-opinion` skill's evolving memory of **how to actually d
|
|
|
4
4
|
well**. Read it before Stage 0 (council selection and launch); update it, with the user's
|
|
5
5
|
approval, at the end of each run (Stage 6). Keep it tight — merge and prune rather than append.
|
|
6
6
|
|
|
7
|
-
_Last updated: 2026-07-
|
|
8
|
-
|
|
7
|
+
_Last updated: 2026-07-14 (v2.2.0 fold-back: v3.1 optional council elements verified live;
|
|
8
|
+
claim-class dedup adjudication limit; minimax and qwen-coder debut notes; see changelog)._
|
|
9
9
|
|
|
10
10
|
## Global operating rules (all models)
|
|
11
11
|
- **Council runs are headless by design** (autonomous batch work): `fanout` is headless by
|
|
@@ -54,6 +54,19 @@ injection, long-read failures, judge tool-wander; see changelog)._
|
|
|
54
54
|
no-tools preamble is now mandatory in SKILL.md Stage 2/3 — keep it verbatim.
|
|
55
55
|
- The tally input needs **all five keys** (`meta` incl. `meta.models`, `findings`, `adjudications`,
|
|
56
56
|
`rankings`, `runStats`) — see the SKILL.md Stage-2 recipe step 0.
|
|
57
|
+
- **Claim-class dedup glosses rationale errors.** When several reviews raise the same claim, judges
|
|
58
|
+
adjudicate the class ("same as A3 — agree") and skip verifying each finding's own rationale text:
|
|
59
|
+
a direction-inverted arithmetic detail inside an otherwise-correct finding drew unanimous agrees
|
|
60
|
+
despite an explicit "dispute material factual errors in the rationale" instruction. If
|
|
61
|
+
rationale-level precision matters, instruct judges to independently verify the numbers in each
|
|
62
|
+
finding; otherwise expect class-level adjudication and let the chair reconcile details (a chair
|
|
63
|
+
has caught and corrected such an error unprompted).
|
|
64
|
+
- **Optional council elements (v2.2.0) verified live:** critic seat (solo-alongside-fanout;
|
|
65
|
+
`role: "critic"` passes through `council tally` untouched), debate mode's nothing-to-debate path
|
|
66
|
+
(provisional `--no-ledger` tally → skip rebuttals → final ledger-recorded tally), and the chair
|
|
67
|
+
verdict scale (parseable `VERDICT:` line + hard questions) all behaved per SEAT-BRIEFS. **Debate
|
|
68
|
+
mode's defense/re-vote waves remain unexercised** — an all-Confirmed consensus run has no
|
|
69
|
+
rebuttal surface; exercise them on a contentious artifact before trusting that path blind.
|
|
57
70
|
|
|
58
71
|
## Per-model notes
|
|
59
72
|
|
|
@@ -102,6 +115,21 @@ injection, long-read failures, judge tool-wander; see changelog)._
|
|
|
102
115
|
### Claude (in-council, when toggle on)
|
|
103
116
|
- Consistently the most *calibrated* reviewer (no severity inflation; findings overwhelmingly Confirmed; bench-best street-cred in recent runs) but sometimes the least *original* — it can miss the boldest single catch. Treat as a reliability floor, not a discovery engine.
|
|
104
117
|
|
|
118
|
+
### minimax (`--model minimax` → via OpenRouter)
|
|
119
|
+
- Fast (~2 min review legs), cheap, `clean` findings-JSON conformance on debut.
|
|
120
|
+
- Took the **critic seat** brief exceptionally well: unanimously ranked #1 by its bench, full
|
|
121
|
+
coverage on a ground-truth test, zero padded findings under the anti-padding rule. A strong
|
|
122
|
+
default critic-seat candidate.
|
|
123
|
+
- **Quirks:** occasional CJK character intrusions mid-English prose (cosmetic; the findings JSON
|
|
124
|
+
is unaffected); can invert the direction of an arithmetic detail inside an otherwise-correct
|
|
125
|
+
rationale — see the claim-class-dedup tip in Stage-2 briefing tips.
|
|
126
|
+
|
|
127
|
+
### qwen-coder (`--model qwen-coder` → via OpenRouter; distinct from `qwen`)
|
|
128
|
+
- Very fast (16–22 s legs, review and judging alike), `clean` conformance, competent
|
|
129
|
+
core-blocker coverage on prose/PRD artifacts.
|
|
130
|
+
- Ranked last on its debut bench — misses the offline, interruption-handling, and
|
|
131
|
+
test-methodology gap classes stronger seats catch. Fine budget-bench filler; do not chair it.
|
|
132
|
+
|
|
105
133
|
### (others — add as used)
|
|
106
134
|
- Opus / o-series etc. are reachable via amicus **if their API keys are configured**. Add notes
|
|
107
135
|
here the first time each is used.
|
|
@@ -160,3 +188,8 @@ This section keeps only per-model **qualitative quirks** and **structural-confor
|
|
|
160
188
|
GUI-hangs-on-this-machine rule (resolved 2026-06-10; headless stays the council default by
|
|
161
189
|
design). Config path updated to `~/.config/amicus/.env`.
|
|
162
190
|
- **2026-07-02** — Folded back field lessons from runs 4-7 (AV-receiver, pork-shoulder, resume, novel ×2 councils): PowerShell `--models` quoting; current-date injection; long-read model selection; judge no-tools preamble; severity-inflation-justifies-dispute; five-keys tally schema; new Grok/Kimi/Mistral/Claude-in-council sections. Quantitative history stays in the ledger (`amicus council stats`).
|
|
191
|
+
- **2026-07-14 (v2.2.0)** — Optional council elements shipped and verified on a planted-flaw
|
|
192
|
+
ground-truth council (critic seat, debate mode nothing-to-debate path, chair verdict scale;
|
|
193
|
+
expert lenses defined but not yet field-run). New lessons: claim-class dedup glosses
|
|
194
|
+
rationale-level errors in Stage-2; minimax debut (strong critic seat, CJK-intrusion quirk);
|
|
195
|
+
qwen-coder debut (fast budget filler). Debate mode's defense/re-vote waves still unexercised.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Seat Briefs — Optional Council Elements
|
|
2
|
+
|
|
3
|
+
Briefing boilerplate for the **optional council elements** offered at Stage 0 of the
|
|
4
|
+
`second-opinion` skill (SKILL.md → "Optional council elements"). When an element is toggled
|
|
5
|
+
ON, Claude copies the relevant block below into the run's `_tmp-*` briefing files and fills
|
|
6
|
+
the `<placeholders>`. Everything here is orchestration prose — the findings contract,
|
|
7
|
+
`council validate`, tally, and verdict mechanics are unchanged unless a section says
|
|
8
|
+
otherwise.
|
|
9
|
+
|
|
10
|
+
_The critic and lens methodologies are adapted from the `/critic` (elite-advisor) and
|
|
11
|
+
`/debate` (expert-debate-facilitator) agents in John Renaldi's product-kit plugin (MIT)._
|
|
12
|
+
_Deliberate deviations from those sources: no minimum-findings quota (quotas force invented
|
|
13
|
+
findings, which waste the bench's adjudication capital), and the hard questions +
|
|
14
|
+
Ship/Fix/Rethink verdict belong to the chair (synthesis-level judgments stay with the
|
|
15
|
+
non-Claude chair, per the council's core rule)._
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Standard anti-sycophancy clause (ALL Stage-1 briefings — not an optional element)
|
|
20
|
+
|
|
21
|
+
Include this block **verbatim in every Stage-1 review briefing**, standard seats included.
|
|
22
|
+
This is briefing hygiene, not a toggle:
|
|
23
|
+
|
|
24
|
+
> Do not soften findings to be agreeable. Lead with your most severe finding. No praise
|
|
25
|
+
> cushions before criticism, and never perform enthusiasm you don't hold — if the artifact
|
|
26
|
+
> is mediocre, say so and show why. Do not pad: report every real finding and no invented
|
|
27
|
+
> ones. An empty severity category is a valid result.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Critic seat brief
|
|
32
|
+
|
|
33
|
+
Body of `_tmp-briefing-critic.md`. Follow it with the standard structured-output contract
|
|
34
|
+
(prose review + trailing findings JSON, identical to every other seat) and the material.
|
|
35
|
+
One bench member gets this brief **instead of** the standard review brief; the rest of the
|
|
36
|
+
bench runs the normal fanout wave.
|
|
37
|
+
|
|
38
|
+
> You are this review bench's designated critic. Assume problems exist; your job is to find
|
|
39
|
+
> them, not to confirm the artifact is fine. Work through four passes and fold everything
|
|
40
|
+
> into one findings list:
|
|
41
|
+
>
|
|
42
|
+
> 1. **Adversarial pass** — for every claim: what evidence supports it, or is it an
|
|
43
|
+
> assumption presented as fact? For every goal: is it measurable — would the author know
|
|
44
|
+
> if they hit it or missed it? For every decision: what alternatives were considered, or
|
|
45
|
+
> was this just the first idea? For every scope boundary: real constraint, or avoidance
|
|
46
|
+
> of hard work?
|
|
47
|
+
> 2. **Edge-case hunt** — walk every journey, requirement, and scenario. What happens on the
|
|
48
|
+
> unexpected input, the failed integration, the malformed data? At zero, at one, at
|
|
49
|
+
> scale? With the confused, frustrated, or adversarial user instead of the happy-path
|
|
50
|
+
> one? Report only unhandled cases — if the artifact addresses an edge case, move on
|
|
51
|
+
> silently.
|
|
52
|
+
> 3. **Consistency check** — cross-reference sections against each other: do goals have
|
|
53
|
+
> metrics, and metrics targets? do requirements trace back to stated needs? do the
|
|
54
|
+
> milestones fit the declared scope? does anything contradict a stated non-goal or
|
|
55
|
+
> constraint?
|
|
56
|
+
> 4. **Executability test** — could someone act on this artifact without coming back with
|
|
57
|
+
> clarifying questions? Wherever the answer is no, name the specific section and exactly
|
|
58
|
+
> what is missing.
|
|
59
|
+
>
|
|
60
|
+
> Be specific: name the section, the line, the exact gap — not "the requirements need work"
|
|
61
|
+
> but which requirement, and what about it is untestable. Report every real finding and no
|
|
62
|
+
> invented ones; do not pad to look thorough. An empty pass is a valid result.
|
|
63
|
+
|
|
64
|
+
**Orchestration notes (not part of the brief):**
|
|
65
|
+
- Launch as a separate concurrent solo run alongside the fanout wave — the exact red-team
|
|
66
|
+
variant pattern in SKILL.md Stage 1.
|
|
67
|
+
- Same findings contract, same `council validate` + repair loop, same anonymization into the
|
|
68
|
+
Stage-2 bundle. Judges are never told a critic seat exists.
|
|
69
|
+
- Record `role: "critic"` on the seat's `runStats` entry.
|
|
70
|
+
- Disclose in `report.md`: the critic model can recognize its own review in the Stage-2
|
|
71
|
+
bundle by its adversarial shape, so self-bias wash-out is weakened for that one seat.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Expert lens briefs
|
|
76
|
+
|
|
77
|
+
**Panel scoping first (Stage 0, when the element is toggled ON).** Ask the user which
|
|
78
|
+
domain the panel should come from, then propose one distinct lens per seat:
|
|
79
|
+
|
|
80
|
+
- **Business / Venture** — VCs, operators, market strategists (viability, positioning, GTM)
|
|
81
|
+
- **Technical** — architects, security specialists, infrastructure engineers (feasibility,
|
|
82
|
+
scalability, build-vs-buy)
|
|
83
|
+
- **Specialty technical** — user names the field (ML, biotech, regulatory, hardware, …)
|
|
84
|
+
- **Customer / Market** — user researchers, skeptical buyers, channel partners (demand,
|
|
85
|
+
willingness to pay, adoption barriers)
|
|
86
|
+
- **Financial** — CFOs, pricing strategists, unit-economics experts
|
|
87
|
+
- **Mixed / Custom** — the user defines the lens set
|
|
88
|
+
|
|
89
|
+
Pick lenses that will produce productive tension (a growth-stage VC + a bootstrapped
|
|
90
|
+
operator + a skeptical enterprise buyer beats three near-identical strategists). Confirm the
|
|
91
|
+
lens set with the user before launch.
|
|
92
|
+
|
|
93
|
+
**Per-seat brief opener** (`_tmp-briefing-lens-<slug>.md`, one per seat; follow with the
|
|
94
|
+
standard structured-output contract and the material):
|
|
95
|
+
|
|
96
|
+
> Review this artifact strictly through the lens of a <lens — e.g. "growth-stage VC",
|
|
97
|
+
> "security architect", "skeptical enterprise buyer", "CFO focused on unit economics">.
|
|
98
|
+
> Raise only findings that perspective is qualified to raise, at the depth a top
|
|
99
|
+
> practitioner of it would reach. Stay in-domain: if something matters but is outside your
|
|
100
|
+
> lens, leave it to the other reviewers.
|
|
101
|
+
|
|
102
|
+
**Orchestration notes (not part of the brief):**
|
|
103
|
+
- Every seat gets a distinct brief, so there is no shared-prompt wave — launch ALL legs as
|
|
104
|
+
concurrent solo runs.
|
|
105
|
+
- The lens↔model assignment is random and lives only in the private label map. Never tell
|
|
106
|
+
any reviewer which lenses the other seats hold, and never mention lenses in the Stage-2
|
|
107
|
+
bundle or judging instructions — judges rank on accuracy and insight only.
|
|
108
|
+
- Record `role: "lens:<slug>"` on each `runStats` entry.
|
|
109
|
+
- The Stage-2 tally runs `--no-ledger` — lens reviews are not comparable to standard
|
|
110
|
+
reviews, so they must not feed cross-run reliability stats.
|
|
111
|
+
- Disclose in `report.md`: anonymity is weakened (each judge can spot its own lens-flavored
|
|
112
|
+
review), street-cred is not comparable across lenses, and the run was not recorded to the
|
|
113
|
+
reliability ledger.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Rebuttal-round templates (debate mode, Stage 2.5)
|
|
118
|
+
|
|
119
|
+
### Defense brief
|
|
120
|
+
|
|
121
|
+
`_tmp-rebuttal-<label>.md` — one concurrent solo run per raiser that has at least one
|
|
122
|
+
Contested or Disputed finding. First line is the no-tools preamble, verbatim:
|
|
123
|
+
|
|
124
|
+
> Do NOT use any tools or read any files; everything is in this message; begin immediately
|
|
125
|
+
> with the first finding id.
|
|
126
|
+
>
|
|
127
|
+
> You reviewed an artifact and raised the findings below. Peer reviewers (anonymous)
|
|
128
|
+
> disputed them for the stated reasons. For EACH finding, respond with exactly one line
|
|
129
|
+
> starting with the finding id and one verb:
|
|
130
|
+
>
|
|
131
|
+
> `<id>: DEFEND — <your strongest evidence-based defense, one paragraph maximum>`
|
|
132
|
+
> `<id>: AMEND — <the corrected claim, full replacement text>`
|
|
133
|
+
> `<id>: WITHDRAW`
|
|
134
|
+
>
|
|
135
|
+
> Withdraw anything you cannot defend with evidence — an unsupported repeat of the original
|
|
136
|
+
> claim is weaker than a withdrawal.
|
|
137
|
+
>
|
|
138
|
+
> [one block per finding: id · claim · severity · the peers' dispute reasons, anonymized]
|
|
139
|
+
|
|
140
|
+
### Re-vote bundle
|
|
141
|
+
|
|
142
|
+
`_tmp-revote-bundle.md` — ONE shared bundle, sent as a single fanout wave to every judge
|
|
143
|
+
that disputed at least one defended or amended finding. First line is the no-tools preamble,
|
|
144
|
+
verbatim:
|
|
145
|
+
|
|
146
|
+
> Do NOT use any tools or read any files; everything is in this message; begin immediately
|
|
147
|
+
> with the first finding id.
|
|
148
|
+
>
|
|
149
|
+
> You previously adjudicated the findings below and disputed them. The (anonymous) raiser
|
|
150
|
+
> has now responded. Re-adjudicate ONLY these findings in light of the response. For each,
|
|
151
|
+
> respond with exactly one line:
|
|
152
|
+
>
|
|
153
|
+
> `<id>: agree | dispute | neutral — <one-line reason>`
|
|
154
|
+
>
|
|
155
|
+
> Changing your verdict when the defense is convincing is good judging, not weakness; so is
|
|
156
|
+
> holding your dispute when it isn't.
|
|
157
|
+
>
|
|
158
|
+
> [one block per finding: id · claim (amended claims marked AMENDED) · the raiser's
|
|
159
|
+
> defense]
|
|
160
|
+
|
|
161
|
+
**Orchestration notes (not part of the briefs):**
|
|
162
|
+
- Exactly ONE rebuttal round, ever. Whatever remains unsettled after the re-vote keeps its
|
|
163
|
+
final tier.
|
|
164
|
+
- A missing or unparseable defense line = the original claim stands undefended. A missing
|
|
165
|
+
re-vote line = that judge's original verdict stands.
|
|
166
|
+
- WITHDRAWN findings stay in the tally input (they were raised) and are auto-recorded as
|
|
167
|
+
`denied` in Stage 4 — never presented for a user decision. List them in `report.md` under
|
|
168
|
+
"Withdrawn by raiser (debate mode)".
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Chair verdict-scale addendum
|
|
173
|
+
|
|
174
|
+
Append to the chair packet (`_tmp-chair-packet.md`) when the element is toggled ON:
|
|
175
|
+
|
|
176
|
+
> After your synthesis, add two closing sections:
|
|
177
|
+
>
|
|
178
|
+
> 1. **HARD QUESTIONS** — three to five questions the artifact's author has probably not
|
|
179
|
+
> asked themselves, chosen so that an unanswerable question reveals a structural gap in
|
|
180
|
+
> the artifact (not gotchas — questions whose answers should exist).
|
|
181
|
+
> 2. A final line, alone on the last line, in exactly this format:
|
|
182
|
+
>
|
|
183
|
+
> `VERDICT: Ship it` | `VERDICT: Fix these first` | `VERDICT: Fundamental rethink`
|
|
184
|
+
>
|
|
185
|
+
> Pick one. "Ship it" = solid, nothing blocking. "Fix these first" = specific gaps must
|
|
186
|
+
> be resolved before the artifact is useful — name them. "Fundamental rethink" =
|
|
187
|
+
> structural problems that cannot be patched — say what is wrong at the foundation.
|
|
188
|
+
|
|
189
|
+
**Orchestration note:** surface the chair's `VERDICT:` line verbatim at the top of
|
|
190
|
+
`report.md` and in the inline chat presentation of the results.
|
|
@@ -50,7 +50,7 @@ Operating lessons from each run fold back into `MODEL-NOTES.md` (with approval),
|
|
|
50
50
|
|
|
51
51
|
## The council flow
|
|
52
52
|
|
|
53
|
-
The flow runs as a **Stage 0 intake/prep step** followed by **three sequential review waves** (Stage 1 → Stage 2 → Stage 3), each wave dependent on the prior. Within each wave, models run in parallel. Track the stages as todos and do not advance until the prior wave's outputs are in hand.
|
|
53
|
+
The flow runs as a **Stage 0 intake/prep step** followed by **three sequential review waves** (Stage 1 → Stage 2 → Stage 3), each wave dependent on the prior — plus an optional **Stage 2.5 rebuttal round** when debate mode is on. Within each wave, models run in parallel. Track the stages as todos and do not advance until the prior wave's outputs are in hand.
|
|
54
54
|
|
|
55
55
|
### Stage 0 — Intake & prep
|
|
56
56
|
|
|
@@ -73,7 +73,7 @@ in this run is written here. Use its absolute path in all `--prompt-file` argume
|
|
|
73
73
|
know "today" and have raised false "future-dated" blockers; state the date explicitly, e.g.
|
|
74
74
|
"Today's date is YYYY-MM-DD."
|
|
75
75
|
|
|
76
|
-
**Pick the council.** Default: **3 models from different families (non-Claude)**. Recommend them ranked by fit, consulting both the reviewer-reliability data from `amicus council stats` (the authoritative quantitative source — runs, avg peers-only street-cred, confirm-rate, fact-error rate) and the qualitative quirks in `MODEL-NOTES.md`. State the estimated cost. The estimate is the budget gate's pre-flight figure (per-$/Mtok pricing from the cached catalog; direct-provider legs without catalog pricing are disclosed as "cost unknown"). State it as an estimate, not a guarantee. **Disclose the run shape up front** before asking for confirmation — e.g.:
|
|
76
|
+
**Pick the council.** Default: **3 models from different families (non-Claude)**. Recommend them ranked by fit, consulting both the reviewer-reliability data from `amicus council stats` (the authoritative quantitative source — runs, avg peers-only street-cred, confirm-rate, fact-error rate) and the qualitative quirks in `MODEL-NOTES.md`. State the estimated cost. The estimate is the budget gate's pre-flight figure (per-$/Mtok pricing from the cached catalog; direct-provider legs without catalog pricing are disclosed as "cost unknown"). State it as an estimate, not a guarantee. **Disclose the run shape up front** before asking for confirmation — naming any enabled optional council elements and their cost impact (see the optional-elements menu below) — e.g.:
|
|
77
77
|
|
|
78
78
|
**Free council (zero-cost).** If the user asks for a "free council" / "zero-cost council",
|
|
79
79
|
read `councils.free` from `~/.config/amicus/config.json` and run
|
|
@@ -93,6 +93,10 @@ read `councils.free` from `~/.config/amicus/config.json` and run
|
|
|
93
93
|
|
|
94
94
|
> This run uses 3 council models across 2 fanout waves + 1 chair call (~7 model runs), ~10 min.
|
|
95
95
|
|
|
96
|
+
or, with elements enabled:
|
|
97
|
+
|
|
98
|
+
> This run uses 3 council models across 2 fanout waves + 1 chair call, with **critic seat + debate mode ON** (~7 base runs + up to 6 rebuttal calls), ~15 min.
|
|
99
|
+
|
|
96
100
|
Then **wait for confirmation**. Never launch without it. The budget gate enforces the cost guardrail in code: by default it refuses any leg whose price exceeds the per-$/Mtok threshold (the o3/o3-pro guard). To run an intentionally expensive model the user explicitly asked for by name, pass `--no-cost-gate`; to raise only the total ceiling, pass `--max-cost <$>`.
|
|
97
101
|
|
|
98
102
|
**Scale-down is explicit — state which mode applies:**
|
|
@@ -102,11 +106,27 @@ Then **wait for confirmation**. Never launch without it. The budget gate enforce
|
|
|
102
106
|
|
|
103
107
|
The scale-down levels count **non-Claude judges**; enabling "Claude in the council" adds a judged review but not a judge, so it does not change these levels. If the bench drops below 2 surviving reviews during the run, apply the Stage-1 wave-degrade rule (offer re-run or a disclosed single-pass downgrade).
|
|
104
108
|
|
|
105
|
-
**Present the
|
|
109
|
+
**Present the optional council elements (all default OFF — explicit opt-in only).** After the
|
|
110
|
+
bench and chair are picked and before asking for launch confirmation, present this menu once
|
|
111
|
+
(adjust the run-shape numbers to the actual bench):
|
|
112
|
+
|
|
113
|
+
> Optional council elements — all OFF unless you name them. Reply with any you want (e.g. "1 and 3", "critic + debate mode", or "none"):
|
|
114
|
+
>
|
|
115
|
+
> 1. **Critic seat** — one reviewer swaps to an adversarial brief (adversarial pass, edge-case hunt, consistency check, executability test). Same review count; that seat runs as a solo call. Trade-off: that reviewer can recognize its own review during cross-review (disclosed in the report).
|
|
116
|
+
> 2. **Expert lenses** — each reviewer gets a distinct expert perspective; you pick the panel domain (business, technical, customer, financial, custom). Same review count, launched as solo calls. Trade-offs: weakens cross-review anonymity (disclosed) and the run is not recorded to the reliability ledger.
|
|
117
|
+
> 3. **Debate mode** — after cross-review, Contested and Disputed findings go back to their raisers to defend, amend, or withdraw, and the disputing judges re-vote before the final tally. Adds 1–2 short waves (up to ~2N extra calls, ~+5 min).
|
|
118
|
+
> 4. **Chair verdict scale** — the chair ends its synthesis with `VERDICT: Ship it | Fix these first | Fundamental rethink` plus 3–5 hard questions. No extra calls.
|
|
119
|
+
> 5. **Claude in the council** — I add my own fresh review to the bundle so the bench can rank and adjudicate it; I'm judged but do not vote (Stage 2) or chair (Stage 3), so the verdict stays independent. +1 review in the bundle, no extra council calls.
|
|
106
120
|
|
|
107
|
-
|
|
121
|
+
Rules for this menu:
|
|
108
122
|
|
|
109
|
-
|
|
123
|
+
- **Never enable an element the user did not explicitly name.** Silence, "no", or "none" = all off. Do not infer opt-in from the nature of the material ("this doc could use a critic…") — offer, don't decide.
|
|
124
|
+
- If elements were **pre-requested in the invoking command** (e.g. `/council … with a critic seat and debate mode`), confirm them back by name ("Critic seat and debate mode are ON per your request; the others are off") instead of re-asking.
|
|
125
|
+
- **The launch confirmation must enumerate the enabled elements by name** — an element not named in the confirmation is off. Restate its cost/shape impact there (see the run-shape disclosure above).
|
|
126
|
+
- Elements compose freely, with one exception: **critic seat + expert lenses together** means one bench member takes the critic brief and the *remaining* seats take lenses — never stack both briefs on one seat.
|
|
127
|
+
- Briefing boilerplate for elements 1–4 lives in **`SEAT-BRIEFS.md`** next to this file — read it when any of them is toggled on.
|
|
128
|
+
|
|
129
|
+
When Claude-in-the-council is off, Claude does not contribute a review and does not appear in the bundle. When on, see Stage 1 and §5.4.
|
|
110
130
|
|
|
111
131
|
---
|
|
112
132
|
|
|
@@ -140,6 +160,10 @@ amicus start --model <redteam-model> --no-ui --json \
|
|
|
140
160
|
|
|
141
161
|
Its stdout is a single run document; the `summary` field is the review.
|
|
142
162
|
|
|
163
|
+
**Critic seat (optional element, when ON):** one bench member — recommended by Claude at Stage 0, typically a strong reasoner that is not the chair — receives the critic brief from `SEAT-BRIEFS.md § Critic seat brief` **instead of** the standard review brief. Launch it exactly like the red-team variant: a separate concurrent solo run (`_tmp-briefing-critic.md`) alongside the fanout wave of the remaining members — same total review count. Everything downstream is unchanged: same structured-output contract, same `council validate` + repair loop, same anonymization into the Stage-2 bundle (judges are never told a critic seat exists). Record `role: "critic"` on that seat's `runStats` entry. One standing disclosure for `report.md`: the critic model can recognize its own review in the Stage-2 bundle by its adversarial shape, so self-bias wash-out is weakened for that one seat.
|
|
164
|
+
|
|
165
|
+
**Expert lenses (optional element, when ON):** every seat gets a distinct per-seat brief, so there is no shared-prompt wave — launch **all** legs as concurrent solo runs (`_tmp-briefing-lens-<slug>.md`, one per seat), using the lens templates and panel-scoping rules in `SEAT-BRIEFS.md § Expert lens briefs`. The lens↔model assignment is random and lives only in the private label map — no reviewer learns the other seats' lenses. Same structured-output contract and validation. Record `role: "lens:<slug>"` on each `runStats` entry. Two standing consequences, both disclosed in `report.md`: Stage-2 anonymity is weakened (each judge can spot its own lens-flavored review), and the Stage-2 tally runs `--no-ledger` (lens reviews are not comparable to standard reviews and must not feed cross-run reliability stats). Wave-degrade rules apply to these solos exactly as to fanout legs.
|
|
166
|
+
|
|
143
167
|
**Cowork / no-Bash environments:** use the MCP tools instead — `amicus_fanout` (briefing via
|
|
144
168
|
file) returns `{waveId, taskIds[]}` immediately. Preferred: call `amicus_wait` with the waveId —
|
|
145
169
|
one blocking call per wave; re-call it while it returns `timedOut: true`. Fallback: poll
|
|
@@ -166,6 +190,8 @@ self-contained (`--no-context`), so MCP transport is equivalent.
|
|
|
166
190
|
|
|
167
191
|
Instruct models to emit the structured JSON verbatim after the prose, without preamble, so it parses cleanly.
|
|
168
192
|
|
|
193
|
+
**Every Stage-1 briefing — standard seats included — must contain the standard anti-sycophancy clause from `SEAT-BRIEFS.md` verbatim** (do not soften, lead with the most severe finding, no praise cushions, no padding — an empty severity category is a valid result). This is briefing hygiene, not an optional element.
|
|
194
|
+
|
|
169
195
|
Save each leg's full output (prose + findings block) to the run folder as `review-<model>.md`
|
|
170
196
|
(one file per reviewer) before moving on.
|
|
171
197
|
|
|
@@ -232,6 +258,8 @@ FINAL RANKING:
|
|
|
232
258
|
|
|
233
259
|
**Task B — Adjudicate findings.** For every finding in the bundle, state: `agree | dispute | neutral` plus one-line reason. Reference each finding as **review-label + finding-id** — for example, `A2` means Review A's 2nd finding, `B1` means Review B's 1st finding. An "I missed this — it's valid" counts as `agree`.
|
|
234
260
|
|
|
261
|
+
**When critic seat or expert lenses are ON:** the bundle and judging instructions must not mention seats, lenses, or briefs — judges rank and adjudicate on accuracy and insight only. The element briefs are Stage-1 information; leaking them into Stage 2 tells every judge which review is which.
|
|
262
|
+
|
|
235
263
|
As each judge's ranking + adjudication response returns, collect it (the raw per-judge responses are working intermediates, not separate run-folder artifacts). Once all are in, **assemble the de-anonymized tally input** and then call `amicus council tally`:
|
|
236
264
|
|
|
237
265
|
**Stage-2 → tally assembly recipe (Claude's work before calling `tally`):**
|
|
@@ -251,12 +279,34 @@ Then call, saving the printed `record` to `<run-folder>/tally.json` (Stage 5's `
|
|
|
251
279
|
amicus council tally <run-folder>/tally-input.json --json > <run-folder>/tally.json
|
|
252
280
|
```
|
|
253
281
|
|
|
282
|
+
**Ledger flags for optional elements:** when **expert lenses** are ON, always pass `--no-ledger` — lens runs never feed cross-run reliability stats. When **debate mode** is ON, this Stage-2 tally is *provisional* — pass `--no-ledger` here; the final, ledger-recorded tally happens at the end of Stage 2.5 (the critic seat and Claude-in-the-council change nothing about ledger handling).
|
|
283
|
+
|
|
254
284
|
**Windows PowerShell 5.1 caveat:** that `>` redirect writes UTF-16 under legacy Windows PowerShell 5.1 (fine on pwsh 7+ or bash), which corrupts `tally.json` for Stage 5's `amicus council verdict` and surfaces as a confusing `BAD_ARGS` there instead of here — on 5.1 pipe through `| Out-File -Encoding utf8` (or run under pwsh 7+) instead of a bare `>`.
|
|
255
285
|
|
|
256
286
|
The output `record` carries the deterministic tiers (Disputed / Confirmed / Contested / Singleton), `confidence` (`solid` | `thin`), both street-cred numbers (`withSelf` and `peersOnly`), the validated `runStats`, and `tierCounts`. **Claude may override a `thin`-confidence tier at the margins** before Stage 4 — record the override in `tierOverride: {from, to, reason}`; the matrix and `verdict.json` surface it. De-anonymize and write the tally results to `crossreview-matrix.md` — the adjudication grid plus the street-cred table. This data feeds Stage 3 (chair briefing) and is never re-anonymized or forwarded to any council model.
|
|
257
287
|
|
|
258
288
|
---
|
|
259
289
|
|
|
290
|
+
### Stage 2.5 — Rebuttal round ("debate mode", optional element — skip this entire stage when OFF)
|
|
291
|
+
|
|
292
|
+
One structured challenge round on the findings the bench did not settle: every **Contested** and **Disputed** finding goes back to its raiser to defend, amend, or withdraw; the judges that disputed it re-vote; then the final tally. **Exactly ONE round, ever** — never iterate further; whatever remains unsettled after the re-vote keeps its final tier. Briefing templates are in `SEAT-BRIEFS.md § Rebuttal-round templates`; every rebuttal briefing opens with the no-tools preamble, same as Stage 2.
|
|
293
|
+
|
|
294
|
+
The Stage-2 tally above ran `--no-ledger` (provisional). If it produced **zero Contested + Disputed findings**, skip the rebuttal waves entirely: re-run the tally on the unchanged input *without* `--no-ledger` to record it, note "debate mode: nothing to debate" for `report.md`, and proceed to Stage 3.
|
|
295
|
+
|
|
296
|
+
**1. Defense mini-wave.** For each raiser with ≥ 1 Contested/Disputed finding, write `_tmp-rebuttal-<label>.md`: its findings (run-global ids and claims), each with the peers' dispute reasons — anonymized, no judge identities. Launch one concurrent solo run per raiser (same flags and budget-gate handling as the Stage-1 solos). Parse each response line: `<id>: DEFEND — …` | `<id>: AMEND — <replacement claim>` | `<id>: WITHDRAW`. A missing or unparseable line = the original claim stands undefended (original verdicts carry).
|
|
297
|
+
|
|
298
|
+
**2. Re-vote mini-wave.** Build ONE shared `_tmp-revote-bundle.md` holding every defended or amended finding plus its (anonymous) defense. Send it as a single fanout wave to the judges that disputed at least one of those findings — judges that never disputed sit this round out. Parse verdict lines `<id>: agree | dispute | neutral — <reason>`; a judge's missing line = its original verdict stands.
|
|
299
|
+
|
|
300
|
+
**3. Final tally.** Re-assemble the tally input: re-vote verdicts replace those judges' original adjudications on those findings; AMENDED claims replace the originals (`id`, `raiser`, and `severity` unchanged); WITHDRAWN findings **stay in `findings[]`** (they were raised) and take whatever tier the final cascade assigns. Run `amicus council tally` **without** `--no-ledger` (unless expert lenses are also ON — lens runs never ledger) and save this record as the run's `tally.json`. This final record — not the provisional one — is what Stages 3–5 consume; the chair packet and `crossreview-matrix.md` are built from it, with verdict changes from the re-vote called out before/after.
|
|
301
|
+
|
|
302
|
+
**Withdrawals downstream:** WITHDRAWN findings are auto-recorded in `decisions.json` as `{"id": …, "decision": "denied"}` — never presented for a user decision in Stage 4 — and listed in `report.md` under "Withdrawn by raiser (debate mode)".
|
|
303
|
+
|
|
304
|
+
**Degrade rules:** a dead defense leg → all of that raiser's contested findings stand undefended. A dead re-vote leg → that judge's original verdicts carry. Never re-run the round.
|
|
305
|
+
|
|
306
|
+
**Cost/shape:** adds up to 2 short waves (≤ N defense solos + 1 re-vote fanout), disclosed at Stage 0. If the actual rebuttal surface turns out much larger than estimated (many contested findings), say so before launching the mini-waves.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
260
310
|
### Stage 3 — Council-chair synthesis
|
|
261
311
|
|
|
262
312
|
A designated **non-Claude** chair synthesizes the verdict across all reviews, rankings, and adjudications. The chair produces an independent verdict that Claude then presents — Claude does not paraphrase, edit, or re-synthesize it.
|
|
@@ -291,6 +341,8 @@ Instruct the chair to write a **synthesized verdict** that:
|
|
|
291
341
|
- Distinguishes findings the bench broadly endorsed from contested or singleton claims
|
|
292
342
|
- Arrives at an overall assessment of the artifact
|
|
293
343
|
|
|
344
|
+
**Chair verdict scale (optional element, when ON):** append the addendum from `SEAT-BRIEFS.md § Chair verdict-scale addendum` to the chair packet — the chair must close with 3–5 **hard questions** the artifact's author probably hasn't asked themselves, then a final parseable line: `VERDICT: Ship it | Fix these first | Fundamental rethink`. Surface that line verbatim at the top of `report.md` and in the inline chat presentation. When debate mode is also ON, the chair packet is built from the *final* (post-rebuttal) tally and includes the defense/re-vote outcomes.
|
|
345
|
+
|
|
294
346
|
Save the chair's output to the run folder as `verdict.md`.
|
|
295
347
|
|
|
296
348
|
---
|
|
@@ -301,6 +353,8 @@ All findings from the bundle are sorted into tiers based on the **peer-confidenc
|
|
|
301
353
|
|
|
302
354
|
**Scale-down:** In a 1-model run, Stage 2 was skipped — there is no peer-confidence data, so present every finding individually for decision (no tiers). In a 2-model run, the Confirmed tier rests on thin cross-review (one ranker per review, per Stage 0) — say so when presenting it.
|
|
303
355
|
|
|
356
|
+
**Debate mode:** the tiers below come from the *final* (post-rebuttal) tally. Findings WITHDRAWN by their raiser in Stage 2.5 are already recorded as `denied` — do not present them for decision; note them as withdrawn when walking the tiers.
|
|
357
|
+
|
|
304
358
|
**Consensus tier — Confirmed findings** (≥ 2 peer agreements, agrees dominate)
|
|
305
359
|
|
|
306
360
|
- Present the full list in one block: id, claim, severity, and which models raised / endorsed it.
|
|
@@ -341,7 +395,12 @@ Do not advance to Stage 5 until every finding in both tiers has a recorded decis
|
|
|
341
395
|
- `verdict.md` (already saved in Stage 3)
|
|
342
396
|
- `verdict.json` — write by running `amicus council verdict <run-folder>/tally.json --decisions <run-folder>/decisions.json -o <run-folder>/verdict.json` (a thin CLI wrapper over `buildVerdict(record, decisions)` + `writeVerdictAtomic`, `src/council/verdict.js`). `<run-folder>/tally.json` is the `record` saved from the Stage-2 `amicus council tally` call. `<run-folder>/decisions.json` is a **JSON array**, one object per finding: `{id, decision, applied?, duplicateOf?, tierOverride?}` — `id` is the run-global label id (e.g. `A1`); `decision` is the Stage-4 outcome (accepted / denied / modified / deferred); `applied` (optional bool) marks whether the accepted change was actually applied to the artifact in Stage 5; `duplicateOf` (optional) links to another finding's id when Claude identified a duplicate; `tierOverride` (optional) carries any `{from, to, reason}` override recorded in Stage 2. Save this array to `<run-folder>/decisions.json` first, then run the command — it parses the tally record and the decisions file, calls `buildVerdict`, and writes the schema-stamped machine-readable record to the run folder via the same atomic tmp+rename convention the function always used.
|
|
343
397
|
- `report.md` — the chair's synthesis + the full Stage-4 decision log + a summary of what was
|
|
344
|
-
applied (+ the "How Claude's review fared" readout when "Claude in the council" is on) +
|
|
398
|
+
applied (+ the "How Claude's review fared" readout when "Claude in the council" is on) + an
|
|
399
|
+
**Optional elements** section whenever any element was ON: which elements ran; the chair's
|
|
400
|
+
`VERDICT:` line verbatim at the top of the report (chair verdict scale); the "Withdrawn by
|
|
401
|
+
raiser (debate mode)" list and re-vote verdict changes (debate mode); and the standing
|
|
402
|
+
disclosures — critic self-identification in cross-review (critic seat), weakened anonymity +
|
|
403
|
+
non-comparable street-cred + `--no-ledger` (expert lenses) + a
|
|
345
404
|
**run-stats table**: one row per model call — **stage** (which stage you launched the call for)
|
|
346
405
|
plus **model, status, durationMs, and cost** read from the wave/run JSON `usage`
|
|
347
406
|
block. Cost is `usage.cost.amount` (USD); mark it with its `usage.cost.source`
|
|
@@ -377,7 +436,7 @@ The `MODEL-NOTES.md` **next to this file** is your machine-local run ledger: npm
|
|
|
377
436
|
|
|
378
437
|
Draft new or updated entries for the per-model sections of `MODEL-NOTES.md` that capture what was learned.
|
|
379
438
|
|
|
380
|
-
**Ledger auto-append (automatic — no approval required).** Running `amicus council tally` (the Stage-2
|
|
439
|
+
**Ledger auto-append (automatic — no approval required).** Running `amicus council tally` (the finalize call — Stage 2, or the Stage-2.5 final tally when debate mode is on; skipped entirely on expert-lens runs, which always pass `--no-ledger`) appends one row per (run × model) to the append-only `council-ledger.jsonl` under `getConfigDir()` — no separate step is needed. Pass `--no-ledger` to compute a tally record *without* recording it (e.g. a re-tally that shouldn't double-count). The run summary shows the appended row. This is a deterministic, content-free model-level record (no finding text, no claim strings, no artifact body content). The quantitative reviewer-reliability data in `MODEL-NOTES.md` is now sourced entirely from `amicus council stats` (which aggregates the ledger) — **do not hand-edit reliability numbers in MODEL-NOTES**.
|
|
381
440
|
|
|
382
441
|
**Compose the proposed MODEL-NOTES diff.** Combine the run-lessons updates and the reviewer-reliability table updates into a single proposed diff (old → new for every changed section). **Write the full diff to a file in the run folder** — `_tmp-proposed-model-notes-update.md` — so the user can open and review it before deciding. Presenting the diff as chat text alone is **not sufficient**: an approval dialog can hide the chat transcript, so the user may be asked to decide on a diff they never saw.
|
|
383
442
|
|
|
@@ -476,13 +535,15 @@ Always **rank recommendations by fit**, state the trade-off for each option, and
|
|
|
476
535
|
- `report.md` — Claude-authored; full contract defined once in *Stage 5 → Run-folder artifacts* above (chair's synthesis + Stage-4 decision log + run-stats table).
|
|
477
536
|
- `report.html` — a **separate, deterministic** artifact generated by the `amicus council report <verdict.json> --html` renderer directly from `verdict.json` (no chair prose, no decision-log narrative — see Stage 5's *Renderer* note); the default artifact to share.
|
|
478
537
|
- Reviewed copy: `<stem>-reviewed.<ext>`, next to the source.
|
|
479
|
-
- Temp working files (`_tmp-*.md`: extracts, stage briefings, red-team brief,
|
|
480
|
-
|
|
481
|
-
|
|
538
|
+
- Temp working files (`_tmp-*.md`: extracts, stage briefings, red-team brief, critic/lens seat briefs
|
|
539
|
+
(`_tmp-briefing-critic.md`, `_tmp-briefing-lens-<slug>.md`), rebuttal briefs (`_tmp-rebuttal-<label>.md`,
|
|
540
|
+
`_tmp-revote-bundle.md`), bundle, chair packet, proposed MODEL-NOTES diff) live in the run folder and are
|
|
541
|
+
cleaned up at the end of the run — the proposed-diff file only after the Stage-6 approval decision is resolved.
|
|
482
542
|
|
|
483
543
|
---
|
|
484
544
|
|
|
485
545
|
## Files
|
|
486
546
|
|
|
487
547
|
- `MODEL-NOTES.md` — operating rules, per-model qualitative quirks, cost guardrail, and structural-conformance notes. **Read it before Stage 0 (council selection and launch); update qualitative notes (with approval) in Stage 6.** Quantitative reliability data (runs, avg street-cred, confirm-rate, fact-error rate) comes from `amicus council stats`, not this file. This copy is machine-local (never overwritten on update); the shipped seed lives in the amicus repo and absorbs durable lessons at release time.
|
|
488
|
-
- `
|
|
548
|
+
- `SEAT-BRIEFS.md` — briefing boilerplate for the optional council elements (critic seat, expert lenses, rebuttal round, chair verdict scale) plus the standard anti-sycophancy clause every Stage-1 briefing carries. **Read it whenever any element is toggled on at Stage 0** (the anti-sycophancy clause applies to every run).
|
|
549
|
+
- `COUNCIL-DESIGN.md` — the design spec this skill implements (v3 + WS-3; §12 covers the optional council elements). Consult it if a mechanics question arises that the skill prose does not resolve.
|
|
@@ -146,7 +146,7 @@ function cacheRootFor(env = process.env) {
|
|
|
146
146
|
* @returns {Promise<void>}
|
|
147
147
|
*/
|
|
148
148
|
async function controlledProvision({
|
|
149
|
-
electronDir, platform, arch, version, downloadArtifact, extract, fs, env = process.env,
|
|
149
|
+
electronDir, platform, arch, version, downloadArtifact, extract, fs, env = process.env, downloadMs = 480000,
|
|
150
150
|
}) {
|
|
151
151
|
const zip = await downloadArtifact({
|
|
152
152
|
version,
|
|
@@ -155,11 +155,7 @@ async function controlledProvision({
|
|
|
155
155
|
cacheRoot: cacheRootFor(env),
|
|
156
156
|
platform,
|
|
157
157
|
arch,
|
|
158
|
-
|
|
159
|
-
// Bound the fetch so a stalled/blocked network aborts (got v11 timeouts:
|
|
160
|
-
// socket = inactivity, request = total) instead of hanging the repair —
|
|
161
|
-
// a hung-then-killed download is what orphaned the single-flight lock.
|
|
162
|
-
downloadOptions: { timeout: { socket: 60000, request: 480000 } },
|
|
158
|
+
downloadOptions: { signal: AbortSignal.timeout(downloadMs) }, // 5.x native fetch: bound stalled downloads, free the lock
|
|
163
159
|
});
|
|
164
160
|
await extractFromCache({ zip, electronDir, platform, extract, fs });
|
|
165
161
|
}
|
|
@@ -214,7 +210,11 @@ async function repairElectron({
|
|
|
214
210
|
const spawn = deps.spawn || ((cmd, args, o) => spawnSync(cmd, args, { ...o, timeout: timeoutMs || 480000 }));
|
|
215
211
|
const findZip = deps.cachedZip || ((o) => cachedZip(o));
|
|
216
212
|
const acquireLock = deps.acquireLock || ((o) => acquireRepairLock({ ...o, fs }));
|
|
217
|
-
|
|
213
|
+
// Lazy: import the ESM-only @electron/get only on the network path, so cacheOnly
|
|
214
|
+
// repairs and injected mocks stay parseable under Jest (which can't import() ESM).
|
|
215
|
+
const resolveDownloadArtifact = deps.downloadArtifact
|
|
216
|
+
? async () => deps.downloadArtifact
|
|
217
|
+
: async () => (await import('@electron/get')).downloadArtifact;
|
|
218
218
|
|
|
219
219
|
if (!version) {
|
|
220
220
|
try {
|
|
@@ -268,8 +268,9 @@ async function repairElectron({
|
|
|
268
268
|
// download that produced no usable exe is a FAILURE (no false success; #53).
|
|
269
269
|
let controlledExtracted = false;
|
|
270
270
|
try {
|
|
271
|
+
const downloadArtifact = await resolveDownloadArtifact();
|
|
271
272
|
await controlledProvision({
|
|
272
|
-
electronDir, platform, arch, version, downloadArtifact, extract, fs, env: process.env,
|
|
273
|
+
electronDir, platform, arch, version, downloadArtifact, extract, fs, env: process.env, downloadMs: timeoutMs,
|
|
273
274
|
});
|
|
274
275
|
controlledExtracted = true; // download + extract returned without throwing
|
|
275
276
|
} catch {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const MIN_NODE = '22.12.0';
|
|
3
|
+
|
|
4
|
+
/** @param {string} current @param {string} min @returns {{ok:boolean,message:string|null}} */
|
|
5
|
+
function checkNodeVersion(current, min = MIN_NODE) {
|
|
6
|
+
const c = current.replace(/^v/, '').split('.').map(Number);
|
|
7
|
+
const m = min.split('.').map(Number);
|
|
8
|
+
for (let i = 0; i < 3; i++) {
|
|
9
|
+
if ((c[i] || 0) > (m[i] || 0)) { return { ok: true, message: null }; }
|
|
10
|
+
if ((c[i] || 0) < (m[i] || 0)) {
|
|
11
|
+
return { ok: false, message: `Amicus 3.0 requires Node >=${min}; you are on ${current.replace(/^v/, '')}. Upgrade Node and retry.` };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return { ok: true, message: null };
|
|
15
|
+
}
|
|
16
|
+
module.exports = { checkNodeVersion, MIN_NODE };
|