amicus 1.1.0 → 1.2.1

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 (46) hide show
  1. package/.claude-plugin/marketplace.json +14 -0
  2. package/.claude-plugin/plugin.json +19 -0
  3. package/CHANGELOG.md +63 -0
  4. package/LICENSE +22 -1
  5. package/README.md +12 -2
  6. package/bin/amicus.js +13 -161
  7. package/package.json +6 -4
  8. package/scripts/postinstall.js +16 -7
  9. package/skills/second-opinion/COUNCIL-DESIGN.md +34 -34
  10. package/skills/second-opinion/MODEL-NOTES.md +23 -17
  11. package/skills/second-opinion/SKILL.md +77 -45
  12. package/src/cli-handlers-council.js +59 -0
  13. package/src/cli-handlers-doctor.js +173 -0
  14. package/src/cli-handlers-run.js +196 -0
  15. package/src/cli-handlers.js +1 -1
  16. package/src/cli.js +11 -2
  17. package/src/council/findings.js +48 -0
  18. package/src/council/ledger.js +82 -0
  19. package/src/council/tally.js +108 -0
  20. package/src/council/verdict.js +48 -0
  21. package/src/headless.js +43 -149
  22. package/src/mcp-server.js +6 -0
  23. package/src/sidecar/budget.js +83 -0
  24. package/src/sidecar/conversation-mirror.js +128 -0
  25. package/src/sidecar/fanout-leg.js +4 -1
  26. package/src/sidecar/fanout.js +34 -7
  27. package/src/sidecar/interactive-mirror.js +66 -0
  28. package/src/sidecar/interactive.js +35 -21
  29. package/src/sidecar/models.js +10 -9
  30. package/src/sidecar/session-finalize.js +26 -0
  31. package/src/sidecar/session-utils.js +5 -5
  32. package/src/sidecar/setup.js +2 -2
  33. package/src/sidecar/start.js +19 -6
  34. package/src/utils/activity-poller.js +47 -0
  35. package/src/utils/alias-audit.js +17 -4
  36. package/src/utils/alias-resolver.js +1 -1
  37. package/src/utils/config.js +4 -4
  38. package/src/utils/curated-models.js +2 -2
  39. package/src/utils/error-doc.js +55 -0
  40. package/src/utils/lifecycle.js +1 -1
  41. package/src/utils/model-catalog.js +1 -1
  42. package/src/utils/pricing.js +93 -0
  43. package/src/utils/result-schema.js +21 -2
  44. package/src/utils/session-abort.js +40 -13
  45. package/src/utils/validators.js +17 -17
  46. /package/{skill → skills/sidecar}/SKILL.md +0 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "bourbondog-amicus",
3
+ "owner": { "name": "Christian Wagner", "url": "https://github.com/BourbonDog" },
4
+ "metadata": { "description": "Amicus — multi-model LLM Council + parallel AI window for Claude Code." },
5
+ "plugins": [
6
+ {
7
+ "name": "amicus",
8
+ "source": "./",
9
+ "description": "Multi-model LLM Council + parallel AI window. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
10
+ "author": { "name": "Christian Wagner" },
11
+ "keywords": ["claude-code", "multi-model", "llm", "council", "second-opinion"]
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "amicus",
3
+ "version": "1.2.1",
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
+ "author": { "name": "Christian Wagner" },
6
+ "homepage": "https://bourbondog.github.io/amicus/",
7
+ "repository": "https://github.com/BourbonDog/amicus",
8
+ "bugs": "https://github.com/BourbonDog/amicus/issues",
9
+ "license": "MIT",
10
+ "keywords": ["claude-code", "multi-model", "llm", "council", "second-opinion", "sidecar", "gemini", "gpt", "deepseek"],
11
+ "skills": ["./skills/sidecar", "./skills/second-opinion"],
12
+ "mcpServers": {
13
+ "amicus": {
14
+ "command": "npx",
15
+ "args": ["-y", "amicus@latest", "mcp"],
16
+ "env": { "AMICUS_SKIP_POSTINSTALL": "1" }
17
+ }
18
+ }
19
+ }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,69 @@ All notable changes to Amicus are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.2.1] - 2026-06-24
9
+
10
+ ### Fixed
11
+ - **`amicus models --check` / `amicus doctor` stale deepseek warning is now clearable**: the
12
+ built-in deepseek direct fallback (`deepseek/deepseek-chat`) has been updated to
13
+ `deepseek/deepseek-v4-pro`. Additionally, stale curated-route warnings are now suppressed when
14
+ the same alias already resolves live via any other source (default openrouter route or a
15
+ user-set alias), so the suggested `--add-alias` fix actually clears the warning instead of
16
+ leaving it permanently unresolvable.
17
+
18
+ ## [1.2.0] - 2026-06-24
19
+
20
+ A post-launch enhancement program: reliability and cost made real, the council's
21
+ trust machinery turned from hand-math into deterministic code, plus first-run
22
+ diagnostics, a Claude Code plugin, and an observable interactive surface.
23
+
24
+ ### Added
25
+ - **`amicus doctor`**: a one-screen first-run health check — configured providers, default-model
26
+ resolution vs. the live catalog, catalog freshness, the OpenCode binary, Electron, installed
27
+ skills, and MCP registration. Each red line carries the exact fix command; `--json` lets skills
28
+ self-diagnose.
29
+ - **Claude Code plugin**: Amicus is now installable from the marketplace —
30
+ `/plugin marketplace add BourbonDog/amicus` then `/plugin install amicus`. The plugin ships both
31
+ skills and the MCP server; npm stays the engine/CLI. (The plugin channel skips the global
32
+ postinstall via `AMICUS_SKIP_POSTINSTALL` so it can't double-register.)
33
+ - **Per-leg cost & token telemetry**: the run/wave schema (now `schemaVersion: 2`) carries a
34
+ `usage` block — input/output/reasoning tokens and a `$` cost tagged by source (reported >
35
+ estimated > unknown). Surfaced in `fanout --json` and council run-stats.
36
+ - **Enforced budget gate**: a per-`$/Mtok` threshold (on by default — blocks o3-pro-class models
37
+ before a wave launches) plus an optional `--max-cost` total ceiling. `--no-cost-gate` is the
38
+ explicit escape hatch.
39
+ - **`amicus council tally|stats`**: deterministic council scoring — a structured findings
40
+ contract, a peers-only tier cascade with self-vote-corrected street-cred, a compounding
41
+ reviewer-reliability ledger, and a machine-readable `verdict.json`. The council stays a skill;
42
+ the engine owns only the arithmetic and schemas.
43
+ - **Structured `--json` error envelope**: pre-flight failures now emit a typed
44
+ `{ ok: false, error: { code, message, hint } }` document on stdout (stable codes like
45
+ `MISSING_KEY`, `BAD_MODEL`, `BUDGET_EXCEEDED`) instead of bare text on stderr.
46
+
47
+ ### Changed
48
+ - **Interactive GUI sessions now persist live**: `conversation.jsonl` and `progress.json` are
49
+ written as the session runs, so the CLI heartbeat, `amicus status`, and
50
+ `amicus read --conversation` work for GUI sessions — and **closing the window without folding no
51
+ longer loses the transcript**. Interactive runs also record token/cost usage. (Headless and
52
+ interactive now share one persistence transform.)
53
+ - **Reliability**: a single source of truth for terminal state (exit code and `metadata.status`
54
+ always agree; the idle backstop no longer exits 0 with `running` metadata), and an
55
+ activity-driven interactive watchdog that won't kill an actively-working-but-quiet GUI session.
56
+ - **CI**: a real matrix (Ubuntu / Windows / macOS × Node 18 / 20 / 22) plus lint, secret-scan, and
57
+ size-gate now gate every push and the publish.
58
+ - Repo layout: the chat skill moved to `skills/sidecar/` (both skills live under `skills/`); npm
59
+ `homepage` now points at the live site; README and the landing page gained a "Prerequisites &
60
+ cost" section.
61
+
62
+ ### Fixed
63
+ - **MCP stderr fd leak**: `spawnSidecarProcess` opened a `debug.log` descriptor for the child's
64
+ stderr but never closed the parent's copy — a descriptor leak that, on Windows, also held the
65
+ file open and blocked session-dir cleanup.
66
+ - Platform-correct missing-key guidance (PowerShell `$PROFILE`/`setx` on Windows; leads with
67
+ `amicus key`); the committed-secret scan now knows all five providers; `amicus models` marks
68
+ your **actual** aliases (not curated defaults); OpenRouter's `-1` "variable pricing" sentinel
69
+ renders as `—` instead of a nonsense negative price.
70
+
8
71
  ## [1.1.0] - 2026-06-11
9
72
 
10
73
  ### Added
package/LICENSE CHANGED
@@ -1,6 +1,27 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 John Renaldi
3
+ Copyright (c) 2025 John Renaldi Claude Sidecar
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.
22
+
23
+
24
+ Copyright (c) 2026 Christian Wagner Amicus
4
25
 
5
26
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
27
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ![Amicus: an LLM Council and a parallel AI window for Claude](./docs/hero.png)
8
8
 
9
- Hand Claude a document 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.
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
 
11
11
  [![npm version](https://img.shields.io/npm/v/amicus?color=D97757&labelColor=1A1C29)](https://www.npmjs.com/package/amicus)
12
12
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue?labelColor=1A1C29)](./LICENSE)
@@ -67,6 +67,16 @@ The postinstall step auto-configures everything — no manual registration:
67
67
  - Registers the **MCP server** in Claude Code and in Claude Desktop / Cowork, so the Amicus tools appear natively.
68
68
  - Installs **both skills** into `~/.claude/skills/` — `second-opinion` (the council) and `sidecar` (the chat skill).
69
69
 
70
+ ## Prerequisites & what it costs you
71
+
72
+ Before your first run:
73
+
74
+ - **Node.js ≥ 18** — `node --version` to check.
75
+ - **An active Claude Code or Cowork session** — Amicus is orchestrated by Claude; it is not a standalone chatbot.
76
+ - **At least one paid model API key** — OpenRouter (covers the most models) or a direct Google / OpenAI / Anthropic / DeepSeek key. Add one with `amicus setup` or `amicus key <provider> <key>`.
77
+
78
+ **What a run costs.** A sidecar is a single model call. A full council is typically **~5–8 paid model calls** (e.g. 3 reviewers across 2 fan-out waves + 1 chair). Amicus shows an estimate before each council and enforces a built-in budget gate that refuses ultra-expensive models (o3-pro class) unless you opt in with `--no-cost-gate`. You pay your providers directly for the tokens; Amicus itself is free and open-source.
79
+
70
80
  **Configure:**
71
81
 
72
82
  ```bash
@@ -452,7 +462,7 @@ LOG_LEVEL=debug amicus start --model gemini --prompt "test" --no-ui
452
462
  | [docs/publishing.md](./docs/publishing.md) | Release and publish process. |
453
463
  | [docs/SHIMS.md](./docs/SHIMS.md) | Legacy `SIDECAR_*` → `AMICUS_*` compatibility shims. |
454
464
  | [skills/second-opinion/SKILL.md](./skills/second-opinion/SKILL.md) | The LLM Council skill. |
455
- | [skill/SKILL.md](./skill/SKILL.md) | The `sidecar` chat skill. |
465
+ | [skills/sidecar/SKILL.md](./skills/sidecar/SKILL.md) | The `sidecar` chat skill. |
456
466
  | [evals/README.md](./evals/README.md) | End-to-end eval harness for LLM interactions. |
457
467
 
458
468
  ---
package/bin/amicus.js CHANGED
@@ -11,10 +11,11 @@
11
11
  const { loadCredentials } = require('../src/utils/env-loader');
12
12
  loadCredentials();
13
13
 
14
- const { parseArgs, validateStartArgs, getUsage } = require('../src/cli');
14
+ const { parseArgs, getUsage } = require('../src/cli');
15
15
  const { validateTaskId } = require('../src/utils/validators');
16
16
  const { resolveModelFromArgs, validateFallbackModel } = require('../src/utils/start-helpers');
17
17
  const { handleSetup, handleAbort, handleUpdate, handleMcp, handleKey } = require('../src/cli-handlers');
18
+ const { handleStart, handleFanout, handleRead } = require('../src/cli-handlers-run');
18
19
  const { isOneShotCommand, armExitWatchdog } = require('../src/utils/lifecycle');
19
20
  const { logger } = require('../src/utils/logger');
20
21
 
@@ -73,7 +74,7 @@ async function main() {
73
74
  try {
74
75
  switch (command) {
75
76
  case 'start':
76
- await handleStart(args);
77
+ exitCode = await handleStart(args);
77
78
  break;
78
79
  case 'fanout':
79
80
  exitCode = await handleFanout(args);
@@ -95,6 +96,16 @@ async function main() {
95
96
  exitCode = await handleModels(args);
96
97
  break;
97
98
  }
99
+ case 'council': {
100
+ const { handleCouncil } = require('../src/cli-handlers-council');
101
+ exitCode = await handleCouncil(args);
102
+ break;
103
+ }
104
+ case 'doctor': {
105
+ const { handleDoctor } = require('../src/cli-handlers-doctor');
106
+ exitCode = await handleDoctor(args);
107
+ break;
108
+ }
98
109
  case 'setup':
99
110
  await handleSetup(args);
100
111
  break;
@@ -129,135 +140,6 @@ async function main() {
129
140
  }
130
141
  }
131
142
 
132
- /**
133
- * Handle 'sidecar start' command
134
- * Spec Reference: §4.1
135
- */
136
- async function handleStart(args) {
137
- // F4: --prompt-file support (XOR --prompt) and --json gating
138
- if (args.prompt !== undefined || args['prompt-file'] !== undefined) {
139
- const { resolvePromptSource } = require('../src/utils/prompt-source');
140
- const promptRes = resolvePromptSource(args);
141
- if (promptRes.error) {
142
- console.error(promptRes.error);
143
- process.exit(1);
144
- }
145
- args.prompt = promptRes.prompt;
146
- }
147
- if (args.json && !args['no-ui']) {
148
- console.error('Error: --json requires --no-ui');
149
- process.exit(1);
150
- }
151
-
152
- const { model, alias } = resolveModelFromArgs(args);
153
- args.model = model;
154
- args.model = await validateFallbackModel(args, alias);
155
-
156
- // Normalize agent: --agent takes precedence, otherwise use --mode
157
- args.agent = args.agent || args.mode;
158
-
159
- const validation = validateStartArgs(args);
160
- if (!validation.valid) {
161
- console.error(validation.error);
162
- process.exit(1);
163
- }
164
-
165
- const { startSidecar } = require('../src/index');
166
-
167
- await startSidecar({
168
- taskId: args['task-id'],
169
- model: args.model,
170
- prompt: args.prompt,
171
- sessionId: args['session-id'],
172
- cwd: args.cwd,
173
- contextTurns: args['context-turns'],
174
- contextSince: args['context-since'],
175
- contextMaxTokens: args['context-max-tokens'],
176
- noUi: args['no-ui'],
177
- timeout: args.timeout,
178
- agent: args.agent,
179
- mcp: args.mcp,
180
- mcpConfig: args['mcp-config'],
181
- thinking: args.thinking,
182
- summaryLength: args['summary-length'],
183
- client: args.client,
184
- sessionDir: args['session-dir'],
185
- foldShortcut: args['fold-shortcut'],
186
- opencodePort: args['opencode-port'],
187
- noMcp: args['no-mcp'],
188
- excludeMcp: args['exclude-mcp'],
189
- coworkProcess: args['cowork-process'],
190
- position: args.position,
191
- json: !!args.json,
192
- modelInput: alias || null,
193
- });
194
- }
195
-
196
- /**
197
- * Handle 'amicus fanout' command (F4).
198
- * Returns the wave exit code: 0 all complete, 2 partial, 1 none/hard failure,
199
- * 130/143 when the wave was signal-aborted.
200
- */
201
- async function handleFanout(args) {
202
- const { resolvePromptSource } = require('../src/utils/prompt-source');
203
- const promptRes = resolvePromptSource(args);
204
- if (promptRes.error) {
205
- console.error(promptRes.error);
206
- process.exit(1);
207
- }
208
- if (typeof args.models !== 'string' || !args.models.trim()) {
209
- console.error('Error: --models is required (comma-separated aliases or provider/model IDs)');
210
- process.exit(1);
211
- }
212
- if (args['wave-id']) {
213
- const check = validateTaskId(String(args['wave-id']));
214
- if (!check.valid) {
215
- console.error(check.error);
216
- process.exit(1);
217
- }
218
- }
219
- if (args.agent && String(args.agent).toLowerCase() === 'chat') {
220
- console.error('Error: --agent chat is interactive-only; fanout is headless');
221
- process.exit(1);
222
- }
223
- if (args.timeout !== undefined && args.timeout <= 0) {
224
- console.error('Error: --timeout must be a positive number');
225
- process.exit(1);
226
- }
227
- const { parseModelsList } = require('../src/sidecar/fanout');
228
- if (parseModelsList(args.models).length === 0) {
229
- console.error('Error: --models must contain at least one non-empty entry');
230
- process.exit(1);
231
- }
232
-
233
- // Direct require — the src/index.js public re-export is added later (Task 13)
234
- const { runFanout } = require('../src/sidecar/fanout');
235
- const { exitCode } = await runFanout({
236
- models: args.models,
237
- prompt: promptRes.prompt,
238
- promptMeta: promptRes.promptMeta,
239
- waveId: args['wave-id'],
240
- project: args.cwd || process.cwd(),
241
- agent: args.agent || args.mode,
242
- thinking: args.thinking,
243
- timeout: args.timeout,
244
- summaryLength: args['summary-length'],
245
- includeContext: !args['no-context'],
246
- sessionId: args['session-id'],
247
- contextTurns: args['context-turns'],
248
- contextSince: args['context-since'],
249
- contextMaxTokens: args['context-max-tokens'],
250
- mcp: args.mcp,
251
- mcpConfig: args['mcp-config'],
252
- noMcp: args['no-mcp'],
253
- excludeMcp: args['exclude-mcp'],
254
- noValidateModel: args['no-validate-model'],
255
- json: !!args.json,
256
- client: args.client,
257
- });
258
- return exitCode;
259
- }
260
-
261
143
  /**
262
144
  * Handle 'sidecar list' command
263
145
  * Spec Reference: §4.2
@@ -348,36 +230,6 @@ async function handleContinue(args) {
348
230
  });
349
231
  }
350
232
 
351
- /**
352
- * Handle 'sidecar read' command
353
- * Spec Reference: §4.5
354
- */
355
- async function handleRead(args) {
356
- const taskId = args._[1];
357
-
358
- if (!taskId) {
359
- console.error('Error: task_id is required for read');
360
- console.error('Usage: sidecar read <task_id> [--summary|--conversation]');
361
- process.exit(1);
362
- }
363
-
364
- const taskIdCheck = validateTaskId(taskId);
365
- if (!taskIdCheck.valid) {
366
- console.error(taskIdCheck.error);
367
- process.exit(1);
368
- }
369
-
370
- const { readSidecar } = require('../src/index');
371
-
372
- await readSidecar({
373
- taskId,
374
- conversation: args.conversation,
375
- metadata: args.metadata,
376
- json: args.json,
377
- project: args.cwd
378
- });
379
- }
380
-
381
233
  // Run main
382
234
  main().catch(err => {
383
235
  console.error(`Fatal error: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
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
  "keywords": [
6
6
  "claude",
@@ -24,7 +24,7 @@
24
24
  "url": "git+https://github.com/BourbonDog/amicus.git"
25
25
  },
26
26
  "bugs": "https://github.com/BourbonDog/amicus/issues",
27
- "homepage": "https://github.com/BourbonDog/amicus#readme",
27
+ "homepage": "https://bourbondog.github.io/amicus/",
28
28
  "bin": {
29
29
  "amicus": "./bin/amicus.js",
30
30
  "am": "./bin/amicus.js",
@@ -39,8 +39,8 @@
39
39
  "bin/",
40
40
  "src/",
41
41
  "electron/",
42
- "skill/",
43
42
  "skills/",
43
+ ".claude-plugin/",
44
44
  "CHANGELOG.md",
45
45
  "scripts/postinstall.js",
46
46
  "scripts/setup-hooks.js"
@@ -63,7 +63,9 @@
63
63
  "generate-docs": "node scripts/generate-docs.js",
64
64
  "generate-docs:check": "node scripts/generate-docs.js --check",
65
65
  "validate-docs": "node scripts/validate-docs.js --full",
66
- "prepare": "node scripts/setup-hooks.js"
66
+ "prepare": "node scripts/setup-hooks.js",
67
+ "check:secrets": "node scripts/check-secrets.js --all",
68
+ "check:sizes": "node scripts/check-file-sizes.js --all"
67
69
  },
68
70
  "dependencies": {
69
71
  "@modelcontextprotocol/sdk": "^1.27.0",
@@ -13,7 +13,7 @@ const path = require('path');
13
13
  const os = require('os');
14
14
  const { execFileSync } = require('child_process');
15
15
 
16
- const SKILL_SOURCE = path.join(__dirname, '..', 'skill', 'SKILL.md');
16
+ const SKILL_SOURCE = path.join(__dirname, '..', 'skills', 'sidecar', 'SKILL.md');
17
17
  const COUNCIL_SOURCE_DIR = path.join(__dirname, '..', 'skills', 'second-opinion');
18
18
 
19
19
  /** Council files + per-file install semantics: SKILL/COUNCIL-DESIGN are product code
@@ -172,12 +172,21 @@ function registerClaudeDesktop() {
172
172
  addMcpToConfigFile(configPath, 'sidecar', MCP_CONFIG);
173
173
  }
174
174
 
175
- function main() {
175
+ function main(deps = {}) {
176
+ if (process.env.AMICUS_SKIP_POSTINSTALL === '1') {
177
+ console.log('[amicus] AMICUS_SKIP_POSTINSTALL set — skipping global setup (plugin channel handles registration).');
178
+ return;
179
+ }
180
+ const _installSkill = deps.installSkill || installSkill;
181
+ const _installCouncilSkill = deps.installCouncilSkill || installCouncilSkill;
182
+ const _registerClaudeCode = deps.registerClaudeCode || registerClaudeCode;
183
+ const _registerClaudeDesktop = deps.registerClaudeDesktop || registerClaudeDesktop;
184
+
176
185
  console.log('[amicus] Installing...');
177
- installSkill();
178
- installCouncilSkill();
179
- registerClaudeCode();
180
- registerClaudeDesktop();
186
+ _installSkill();
187
+ _installCouncilSkill();
188
+ _registerClaudeCode();
189
+ _registerClaudeDesktop();
181
190
 
182
191
  console.log('');
183
192
  console.log('[amicus] Setup:');
@@ -190,4 +199,4 @@ if (require.main === module) {
190
199
  main();
191
200
  }
192
201
 
193
- module.exports = { addMcpToConfigFile, installSkill, installCouncilSkill, COUNCIL_FILES };
202
+ module.exports = { main, addMcpToConfigFile, installSkill, installCouncilSkill, COUNCIL_FILES };
@@ -35,7 +35,7 @@ non-Claude chairman + per-model inspectable artifacts.
35
35
  chair) is prose workflow Claude performs while driving the `amicus` CLI. v3 note: the *transport*
36
36
  is now engine-native — each review wave is ONE `amicus fanout --json` call returning structured
37
37
  run documents — but scoring, tallying, anonymization, and synthesis remain Claude's manual work.
38
- No backend, no parsing code beyond reading JSON fields.
38
+ No backend, no parsing code beyond reading JSON fields. Deterministic arithmetic/formatting/schema helpers under `amicus council` (findings validation, tier tally, street-cred, ledger) are sanctioned; judgment, synthesis, anonymization, and de-anonymization remain Claude's inline work.
39
39
 
40
40
  ## 3. What changes vs. v1
41
41
 
@@ -44,7 +44,7 @@ non-Claude chairman + per-model inspectable artifacts.
44
44
  | Independent reviews | ✅ Phase 2 parallel sidecars | ✅ Stage 1 — now emits a **structured findings list** |
45
45
  | Cross-review | ❌ none | ⭐ **Stage 2** — anonymized peer ranking **+** per-finding adjudication |
46
46
  | Synthesis | Claude synthesizes | ⭐ **Council-model chair** synthesizes; Claude only presents |
47
- | Decision tiers | Claude's consensus/divergence read | ⭐ **Peer-validated** tiers (Confirmed / Contested / Singleton) |
47
+ | Decision tiers | Claude's consensus/divergence read | ⭐ **Peer-validated** tiers (Disputed / Confirmed / Contested / Singleton) |
48
48
  | Scoring | none | ⭐ Reviewer **street-cred** + per-finding **peer-confidence** |
49
49
  | Artifacts | reviewed copy + report | + per-model raw reviews, cross-review matrix, chair verdict (run folder) |
50
50
  | MODEL-NOTES | per-model quirks | + **reviewer-reliability** rolling table feeding recommendations |
@@ -106,11 +106,9 @@ Run as ordered phases; track as todos. **Three sequential waves of model calls**
106
106
  - Chair selection & fallback: §5.3.
107
107
 
108
108
  ### Stage 4 — Tiered decisions (peer-validated)
109
- - **Consensus tier** = **Confirmed** findings (peers agree) → offer one **bulk accept/deny**
110
- (user may name exceptions).
111
- - **Judgment tier** = **Contested** (peers dispute/split) or **Singleton** (only the raiser)
112
- findings → present **each individually**, showing the dissent and which model raised/disputed it.
113
- - Record every decision (accepted / denied / modified).
109
+ - **Consensus tier** = **Confirmed** findings ( 2 peer agreements, agrees dominate) → offer one **bulk accept/deny** (user may name exceptions).
110
+ - **Judgment tier** = **Disputed** (strong peer pushback), **Contested** (live dispute), or **Singleton** (only the raiser) → present **each individually**, showing the adjudication data and which model raised/disputed it.
111
+ - Record every decision (accepted / denied / modified / deferred).
114
112
 
115
113
  ### Stage 5 — Outputs
116
114
  - **Editable source** → write `<stem>-reviewed.<ext>` next to the original (accepted changes
@@ -119,8 +117,8 @@ Run as ordered phases; track as todos. **Three sequential waves of model calls**
119
117
 
120
118
  ### Stage 6 — Capture lessons (compounding)
121
119
  - Reflect on failures/mitigations and briefing wording, as today.
122
- - **Additionally** update the per-model **reviewer-reliability** table (§7).
123
- - **Write the proposed MODEL-NOTES diff to a run-folder file and get approval before writing.**
120
+ - **Ledger auto-appends** `ledger.appendRun(record)` writes one row per (run × model) to `council-ledger.jsonl` automatically at finalize (shown in the run summary). No manual reliability-table update needed.
121
+ - **Qualitative MODEL-NOTES update (approval-gated):** draft per-model quirk/conformance notes; write the proposed diff to `_tmp-proposed-model-notes-update.md`; present its path in the approval prompt; do not write until approved.
124
122
  The approval prompt carries the diff file's path; chat text alone is not sufficient (an approval
125
123
  dialog can hide the chat transcript). Keep it tight.
126
124
 
@@ -136,14 +134,23 @@ Run as ordered phases; track as todos. **Three sequential waves of model calls**
136
134
  and judged blind by the council models. Claude never ranks/adjudicates (it holds the map) —
137
135
  the asymmetry detailed in §5.4.
138
136
 
139
- ### 5.2 Scoring (Claude tallies by hand no code required)
140
- - **Street cred** = each model's **average rank position** across all judges' `FINAL RANKING:`
141
- blocks (lower = better), exactly as LLM Council's aggregate. Surface as a small table.
142
- - **Per-finding peer-confidence** = qualitative tier from the adjudications:
143
- - **Confirmed** agrees clearly outweigh disputes (and ≥2 judges engaged).
144
- - **Contested** meaningful split or explicit disputes.
145
- - **Singleton** — only the original raiser; others neutral/silent.
146
- These tiers drive Stage 4. Claude exercises judgment at the margins; no rigid formula.
137
+ ### 5.2 Scoring (`amicus council tally` computes; Claude may override at the margins)
138
+
139
+ **Street cred** computed two ways by `amicus council tally`:
140
+ - **withSelf** = each model's mean rank position across **all** judges' `FINAL RANKING:` blocks (lower = better).
141
+ - **peersOnly** = mean rank across judges **other than** that model (self-vote excluded).
142
+ The cross-review matrix shows both; the ledger and Stage-0 bench recommendations consume **peersOnly** only.
143
+
144
+ **Per-finding peer-confidence tier** determined by a **peers-only** cascade: for a finding raised by model R, peers are all judges except R (the raiser's own adjudication is excluded — consistent with the peers-only street-cred rule). Let `a` = peer agrees, `d` = peer disputes. The cascade is exhaustive and mutually exclusive:
145
+
146
+ | Priority | Tier | Rule | Meaning |
147
+ |---|---|---|---|
148
+ | 1 | **Disputed** | `d ≥ 2` and `d > a` | Strong peer pushback — the finding itself is likely wrong |
149
+ | 2 | **Confirmed** | `a ≥ 2` and `a > d` | ≥ 2 independent corroborations, agrees dominate |
150
+ | 3 | **Contested** | `d ≥ 1` (whatever remains) | At least one live dispute — in question |
151
+ | 4 | **Singleton** | else (`d = 0` and `a < 2`) | At most one endorsement, no pushback — thin |
152
+
153
+ `confidence` is `thin` when total engaged peers `a + d ≤ 1` — cells `(0,0)`, `(1,0)`, and `(0,1)`. **Claude may override the tier at `thin` margins** (recorded as `tierOverride: {from, to, reason}` and surfaced in the matrix and `verdict.json`). These four tiers drive Stage 4. `amicus council tally` assigns them deterministically; judgment at the margins remains Claude's.
147
154
 
148
155
  ### 5.3 Chair selection & fallback
149
156
  - Default: Claude **recommends a non-Claude chair** from the council each run (often the
@@ -161,7 +168,7 @@ Lets you see how the bench judges Claude's *own* take.
161
168
  - **Which review: always fresh** — Claude does a new structured Stage-1 review on the artifact
162
169
  every time it's enabled (not a formalization of upstream feedback).
163
170
  - **Readout — "How Claude's review fared":** Claude's street-cred rank among peers and the
164
- Confirmed/Contested/Singleton split of its findings, reported in the matrix and report.
171
+ Disputed/Confirmed/Contested/Singleton split of its findings, reported in the matrix and report.
165
172
  - **Integrity:** when Claude presents results, it reports the bench's verdict on its own review
166
173
  at face value — no defending or re-litigating.
167
174
 
@@ -171,22 +178,16 @@ One tidy run folder: `output/<stem>-council/` (or `./second-opinion/<stem>-counc
171
178
  - `review-<model>.md` ×N — raw Stage 1 reviews (plus `review-claude.md` when "Claude in the
172
179
  council" is on)
173
180
  - `crossreview-matrix.md` — adjudication grid + street-cred table (de-anonymized)
174
- - `verdict.md` — the chair's synthesis
181
+ - `verdict.md` — the chair's synthesis (prose)
182
+ - `verdict.json` — schema-stamped machine-readable record: tally output + Stage-4 decisions, written via `buildVerdict(record, decisions)` at Stage 5
175
183
  - `report.md` — synthesis + decision log + what was applied (+ the "How Claude's review fared"
176
184
  readout when "Claude in the council" is on)
177
185
  - `<stem>-reviewed.<ext>` — written **next to the original**, as today (editable sources only)
178
186
  - Temp extracts get a clearly-temporary name and are cleaned up at the end.
179
187
 
180
188
  ## 7. MODEL-NOTES reviewer-reliability
181
- Add a compact rolling table consulted in Stage 0 and updated (with approval) in Stage 6:
182
-
183
- | model | runs | avg street-cred | confirm-rate | notes |
184
- |---|---|---|---|---|
185
189
 
186
- - **avg street-cred** running average rank when peer-ranked.
187
- - **confirm-rate** — share of this model's findings that ended up **Confirmed** by peers.
188
- - Used to justify recommendations ("DeepSeek findings peer-confirm ~80% → strong default
189
- reviewer"). Kept tight per the existing no-bloat rule; merge/prune rather than append.
190
+ The append-only `council-ledger.jsonl` (consumed via `amicus council stats`) is the **authoritative source of quantitative reviewer-reliability data** — runs, avg peers-only street-cred, confirm-rate, fact-error rate, conformance distribution. `MODEL-NOTES.md` keeps only *qualitative* per-model quirks and structural-conformance notes (`clean` / `repaired` / `unstructured`); it may embed a snapshot generated from `amicus council stats --json` but is no longer hand-edited for numbers. Stage-6 reliability updates are written by the ledger auto-append; the MODEL-NOTES prose update remains approval-gated.
190
191
 
191
192
  ## 8. Gating, cost, degradation & failure handling
192
193
 
@@ -207,23 +208,22 @@ Add a compact rolling table consulted in Stage 0 and updated (with approval) in
207
208
  - **Stage 3:** chair failure uses the same fallback chain (re-run → promote next-best non-Claude
208
209
  → Claude chairs with explicit disclosure).
209
210
  - **Run stats (v3):** `report.md` includes a per-leg table (model, status, durationMs) read from
210
- the wave/run documents. The schema carries no cost data — never invent cost figures.
211
+ the wave/run documents. `durationMs` and `usage` are copied verbatim from the per-leg run docs; any leg with no run doc gets `durationMs: null` (and `usage: null`) — never invent a value. The schema carries no cost data — never invent cost figures.
211
212
  - **Transient failures:** provider 502s etc. → re-run the affected leg (solo `start --json`) or
212
213
  the wave; never present a half-finished run as an answer.
213
214
 
214
215
  ## 9. Non-goals (YAGNI)
215
216
  - No web UI, API server, or persistent conversation store (LLM Council's app shell).
216
- - No code/backend for scoring or parsing — Claude does it inline.
217
+ - No code/backend for scoring or parsing — Claude does it inline. Deterministic arithmetic/formatting/schema helpers under `amicus council` (findings validation, tier tally, street-cred, ledger) are sanctioned; judgment, synthesis, anonymization, and de-anonymization remain Claude's inline work.
217
218
  - No automatic MODEL-NOTES writes — always approval-gated.
218
219
  - Claude is **not** a council member by default; it joins only via the opt-in toggle (§5.4),
219
220
  and even then it is judged-but-non-voting/non-chairing.
220
221
 
221
222
  ## 10. Open questions
222
223
  - None blocking. Possible later refinement: a numeric peer-confidence score instead of the
223
- three qualitative tiers, if tiers prove too coarse in practice.
224
+ four qualitative tiers, if tiers prove too coarse in practice.
224
225
 
225
226
  ## 11. Implementation surface
226
- - `SKILL.md` — the Stage 0–6 council flow on the v3 transport.
227
- - `MODEL-NOTES.md` — reviewer-reliability table, per-model quirks, cost guardrail, Stage-2
228
- briefing tips. Engine workarounds that F1/F2/F4 made obsolete were pruned at v3.
229
- - No other files.
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).
228
+ - `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.