amicus 4.6.2 → 4.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +349 -0
  3. package/README.md +24 -13
  4. package/bin/amicus.js +31 -0
  5. package/docs/ROADMAP.md +172 -36
  6. package/docs/configuration.md +56 -6
  7. package/docs/council.md +63 -10
  8. package/docs/doc-system.md +8 -7
  9. package/docs/schemas.md +10 -1
  10. package/docs/troubleshooting.md +27 -1
  11. package/docs/usage.md +68 -15
  12. package/electron/workspace-ui/index.html +3 -0
  13. package/electron/workspace-ui/live-model.js +132 -21
  14. package/electron/workspace-ui/workspace-app.js +20 -4
  15. package/electron/workspace-ui/workspace-lazy.js +233 -0
  16. package/electron/workspace-ui/workspace-matrix.js +12 -1
  17. package/electron/workspace-ui/workspace-panels.js +24 -171
  18. package/electron/workspace-ui/workspace-render.js +15 -5
  19. package/electron/workspace-ui/workspace-seats.js +88 -5
  20. package/electron/workspace-ui/workspace-verbs.js +1 -1
  21. package/electron/workspace-ui/workspace.css +6 -0
  22. package/package.json +5 -2
  23. package/schemas/council-run.schema.json +1 -0
  24. package/schemas/council-stats.schema.json +9 -1
  25. package/schemas/run.schema.json +2 -1
  26. package/schemas/spend.schema.json +1 -1
  27. package/schemas/wave.schema.json +2 -1
  28. package/skills/second-opinion/MANUAL-ORCHESTRATION.md +12 -0
  29. package/skills/second-opinion/MODEL-NOTES.md +5 -4
  30. package/skills/sidecar/SKILL.md +7 -2
  31. package/src/cli-council-run-bench.js +86 -0
  32. package/src/cli-handlers-council-run.js +65 -81
  33. package/src/cli-handlers-council.js +24 -3
  34. package/src/cli-handlers-doctor.js +9 -3
  35. package/src/cli-handlers-fanout.js +179 -0
  36. package/src/cli-handlers-pack.js +24 -10
  37. package/src/cli-handlers-run.js +19 -161
  38. package/src/cli-template-args.js +48 -0
  39. package/src/cli.js +39 -46
  40. package/src/council/debate.js +89 -10
  41. package/src/council/ledger.js +72 -11
  42. package/src/council/presets-cli.js +6 -2
  43. package/src/council/report.js +17 -6
  44. package/src/council/run-assemble.js +15 -3
  45. package/src/council/run-budget.js +2 -2
  46. package/src/council/run-chair.js +70 -11
  47. package/src/council/run-debate.js +51 -67
  48. package/src/council/run-launch.js +9 -2
  49. package/src/council/run-retry.js +4 -1
  50. package/src/council/run-stage1-launch.js +94 -0
  51. package/src/council/run-stage2.js +25 -4
  52. package/src/council/run-stages.js +79 -86
  53. package/src/council/run-state.js +10 -2
  54. package/src/council/run.js +26 -2
  55. package/src/council/tally.js +6 -2
  56. package/src/mcp-council-awareness.js +1 -0
  57. package/src/mcp-council-bench.js +4 -0
  58. package/src/mcp-council-run.js +10 -0
  59. package/src/mcp-server.js +114 -54
  60. package/src/mcp-tools.js +12 -5
  61. package/src/pack/pack-cli.js +1 -1
  62. package/src/pack/pack-forward.js +12 -4
  63. package/src/pack/pack-resolve.js +3 -0
  64. package/src/pack/pack-store.js +20 -3
  65. package/src/pack/pack-validate.js +5 -1
  66. package/src/session-manager.js +6 -2
  67. package/src/sidecar/budget.js +38 -4
  68. package/src/sidecar/fanout-budget.js +1 -2
  69. package/src/sidecar/fanout-leg-fallback.js +7 -3
  70. package/src/sidecar/fanout-wave-io.js +13 -1
  71. package/src/sidecar/fanout.js +11 -9
  72. package/src/sidecar/list-limit.js +50 -0
  73. package/src/sidecar/list-search.js +69 -0
  74. package/src/sidecar/read.js +90 -5
  75. package/src/sidecar/start-metadata.js +58 -0
  76. package/src/sidecar/start.js +8 -43
  77. package/src/sidecar/workspace-auto-open.js +2 -2
  78. package/src/spend-query.js +2 -1
  79. package/src/template/apply.js +7 -4
  80. package/src/template/render.js +6 -2
  81. package/src/template/store.js +1 -1
  82. package/src/utils/alias-audit.js +19 -0
  83. package/src/utils/cli-preflight.js +27 -1
  84. package/src/utils/config.js +15 -0
  85. package/src/utils/curated-models.js +43 -7
  86. package/src/utils/gateway-route-audit.js +16 -3
  87. package/src/utils/model-fetcher.js +8 -6
  88. package/src/utils/remediation-hints.js +14 -0
  89. package/src/utils/result-schema-rebuild.js +1 -0
  90. package/src/utils/result-schema.js +6 -1
  91. package/src/utils/session-index-tmp-sweep.js +18 -3
  92. package/src/utils/session-index.js +1 -0
  93. package/src/utils/session-metadata-tmp-sweep.js +156 -0
  94. package/src/utils/spend-ledger.js +11 -4
  95. package/src/utils/validators.js +16 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "4.6.2",
3
+ "version": "4.7.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,344 @@ All notable changes to Amicus are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.7.0] - 2026-08-08
9
+
10
+ **"The count is the count."** Every number Amicus shows you is the number — what a council cost,
11
+ which legs ran, and which model earned the credit. Shipped across ten `v4.7-*` PRs — PR0
12
+ (extractions) through PR7, plus the PR3 riders and a closing documentation pass: the `runStats`
13
+ completeness half of CA-4, the GOA-7 ledger prerequisite, F8 session/wave tagging with `--search`,
14
+ and four correction sweeps.
15
+
16
+ ### CI
17
+
18
+ - **The macOS/node-24 unit leg's `--workerIdleMemoryLimit` drops from 1GB to 512MB.** That leg
19
+ segfaulted a jest worker again on 2026-08-07 (PR #126) — killing
20
+ `tests/utils/gateway-route-audit.test.js` while 6791 tests passed and 0 failed, with a green
21
+ rerun — which was the first such hit *with* the 1GB ceiling in force, so 1GB does not bound the
22
+ growth on this runner. Halving recycles a worker sooner, at a small wall-clock cost on a leg
23
+ already mid-pack (~3m59s vs ubuntu's ~3m58s). Three pins in
24
+ `tests/scripts/ci-workflow.test.js` now guard the mitigation, which previously had none: it
25
+ produces no assertion failure when it works and none when it is deleted, so nothing else in the
26
+ repo would notice its loss. If a fifth hit lands at 512MB, the next lever is `--maxWorkers` on
27
+ that leg — fewer concurrent heaps — not a smaller idle ceiling.
28
+
29
+ ### Added
30
+
31
+ - **`runStats` gains a row for every paid launch, not just one per requested seat** (v4.7 CA-4,
32
+ the row-per-launch design — closes the chair-cost accounting gap). Three new roles cover legs
33
+ that were billed but never rowed: `chair-attempt` (a failed ch1–ch3 chair launch), `repair`
34
+ (a Stage-1 `-p`, Stage-2 `-q`, chair-ch4, or debate-born `-d<N>r`/`-rv-…r` solo — a failed
35
+ defense or re-vote repair), and `superseded` (a first leg a later attempt replaced — an SL-2
36
+ retry or a debate repair); `wasChair` is always `false` on these. Every dead seat/critic/lens
37
+ with no recovery, and a chair walk that gives up entirely, now get an honest primary error row
38
+ too, extending the #83 judge treatment to every seat.
39
+ - **`runStats[].waveId`** (emit-only-when-set): every row backed by a real billed leg now names
40
+ the exact wave/leg it came from; e.g. the synthetic `claude` row, a give-up chair's error row,
41
+ and a leg-less dead-seat/critic/lens primary error row (the two SL-2 retry note-classes that
42
+ never produced a real leg for the seat at all) carry none.
43
+ - **The run-cost bijection invariant suite** (`tests/council/run-cost-bijection.test.js`): every
44
+ terminal run now proves Σ(`runStats` legged-row usage) equals `run.json`'s `usage` block —
45
+ cost, and the reported/estimated/unpriced/subtreeUnknown leg counts — across clean, repair,
46
+ chair-walk-failure, debate-repair, retry-healed, and retry-failed scenarios.
47
+ - **`runStats[].resolvedModel`** (v4.7 GOA-7, emit-only-when-set): every row built from a served
48
+ leg now records the executable id that actually served (post-fallback-substitution), never the
49
+ alias; carried verbatim through `tally.json`/`verdict.json` and onto `council-ledger.jsonl` rows.
50
+ - **`council stats` rows gain `aliases[]`** (every alias observed for the group, most recent
51
+ first — `aliases[0]` is the launch-preferred name) **and a `legacy` mark** on groups whose rows
52
+ all lack `resolvedModel`; the human table sizes the model column to the longest key (16-char
53
+ floor) and marks `legacy` in the notes column beside `low-N`.
54
+ - **`--tag <t>` on `start`/`fanout`/`council run`** (CLI + MCP, v4.7 F8 D13): labels a session
55
+ for `list`/`--search`/`spend --group-by tag`. Reject-style validation
56
+ (`^[A-Za-z0-9_-]{1,64}$`) — an invalid tag fails fast rather than being silently truncated or
57
+ charset-stripped the way `sanitizeCouncilName` cleans, since a stored tag is a search key.
58
+ Stored absent-not-null on `metadata.json`/`wave.json`/`run.json` and every result doc; the MCP
59
+ shared-server's in-process start path stamps it too. `--tag` is rejected alongside
60
+ `--retry-failed` (`BAD_ARGS`).
61
+ Council sub-waves (Stage-1, critic/lens solos, Stage-2, chair, debate) all carry the run's tag
62
+ on their own wave metadata. Riders: `continue`/`resume` don't yet inherit the parent session's
63
+ tag (their rows group under `(unattributed)` for `--group-by tag`), and `--retry-failed` doesn't
64
+ yet inherit the original wave's tag either — both are future work, not oversights.
65
+ - **`amicus list --search <q>` / MCP `amicus_list {search}`** (F8 D15, errata E-PR3-5):
66
+ case-insensitive substring filter over `id`, `tag`, and briefing material. Fan-out wave rows
67
+ match against the full `briefing.md` text (falling back to the row's 200-char excerpt when
68
+ unreadable); council-run rows match `briefing.md` written at MCP launch, or the
69
+ post-`--- MATERIAL / BRIEFING ---` portion of `briefing-stage1.md` for CLI-launched runs; leg
70
+ rows (spawned by a wave) match `id`/`tag` only, so a wave's briefing never surfaces once per
71
+ leg it spawned. A bare `--search` with no value is a usage error.
72
+ - **`amicus spend --group-by tag`** (F8 D16): spend-ledger rows now carry `tag` (leg and solo
73
+ rows alike); untagged history groups under `(unattributed)`, matching every other dimension's
74
+ convention. `SPEND_LEDGER_SCHEMA_VERSION` stays at `1` — this is an additive field, not the kind
75
+ of change that forced the council-ledger's `LEDGER_SCHEMA_VERSION` 1 → 2 bump above, which
76
+ existed to segment history by resolved-model id, a different need.
77
+
78
+ ### Changed
79
+
80
+ - **Tally/report/GUI cost totals now include legs that used to be silently dropped** — repairs,
81
+ failed chair attempts, and superseded/replaced legs all get a row now. Totals read HIGHER than
82
+ v4.6.x for an identical run, intentionally: this is the fix for the two-numbers-disagree
83
+ symptom (`council tally`'s sum vs. `run.json`'s total quietly diverging by the omitted spend).
84
+ - **The council-ledger join is now an explicit allowlist, not a skip-set**: `seat`, `critic`,
85
+ `lens:*`, `chair`, `claude`, `council`, `redteam`. Fail-closed by design — consumers keying
86
+ `runStats` by model must now exclude every role outside that set, the v4.7 analogue of v4.6's
87
+ "must exclude `role: 'judge'`" rule: a custom/free-form `role` label (e.g. a skill-authored tag)
88
+ still renders in the tally/report artifact but no longer contributes role/wasChair/conformance
89
+ to `amicus council stats` reliability numbers.
90
+ - **The Workspace seats panel no longer renders the three new non-seat launch rows**
91
+ (`chair-attempt`, `repair`, `superseded`) — they still appear in the report/tally cost tables,
92
+ just not as a seat.
93
+ - **`LEDGER_SCHEMA_VERSION` 1 → 2** (v4.7 GOA-7). Legacy-read, no migration: rows without
94
+ `resolvedModel` (all pre-v4.7 history, plus leg-less rows whose resolution is unknowable)
95
+ aggregate under their alias; a group is marked `legacy` only when every row in it lacks
96
+ `resolvedModel`.
97
+ - **`council stats` groups reliability by resolved model id** (`resolvedModel || model`) instead
98
+ of by alias alone — history splits honestly at the bump; a retargeted alias's new rows start
99
+ a fresh `low-N` group.
100
+ - **Chair fallback promotion (`pickFallbackChair`) excludes candidates by their full name set**
101
+ (group key + `aliases[]`) and launches the group's most-recent alias (`aliases[0]`) — a bench
102
+ seat's resolved-keyed group can no longer be promoted as its own chair.
103
+ - **`amicus list` and the MCP `amicus_list` tool now share one enumeration**
104
+ (`src/sidecar/read.js`, F8 D14): MCP rows gain `type`/`parentWave`/`legCount`, CLI rows gain
105
+ `mode`, and both gain `tag`. The MCP `status` input relaxed from a 3-value enum
106
+ (`'all' | 'running' | 'complete'`) to a free string, so `error`/`aborted`/`crashed`/
107
+ `timed-out`/`idle-timeout` — always real statuses, previously rejected outright by MCP's schema
108
+ even though the CLI already accepted them — now filter correctly there too. The CLI table
109
+ gained a `TAG` column.
110
+ - Test/comment/docs sweep (v4.7 PR4, theme a): ~30 census nits dispositioned — no behavior
111
+ changes.
112
+ - **`--out`/`-o` no longer accepts a flag-shaped value as a filename.** `--out -x` and
113
+ `--out=-x` previously wrote a file literally named `-x` (the dash-leading value was accepted
114
+ at face value); both forms now fail fast with `BAD_ARGS` ("-o/--out cannot start with '-':
115
+ got '-x'") instead. `-o -x` was already rejected before this change, via a separate internal
116
+ check — it still fails with the same code and exit status, now carrying the more accurate
117
+ message above in place of "-o/--out requires a value".
118
+ - **`{{project}}` template variable is now path-resolved** (absolute, normalized via
119
+ `path.resolve`), matching `{{artifact_path}}`'s existing behavior — it previously rendered the
120
+ raw `--cwd`/`process.cwd()` string verbatim. All three `TEMPLATE_RENDER` error paths also gain a
121
+ real, followable `hint` instead of `hint: null`.
122
+ - **MCP-launched council runs now record `run.template` provenance**, closing the one remaining
123
+ gap after Wave 1's D1 forwarded it on fanout/start — `mcp-council-run.js`'s template path no
124
+ longer discards `promptMeta`, so `run.json` carries `{name,hash}` the same way the CLI path
125
+ already did.
126
+
127
+ ### Fixed
128
+
129
+ - **`amicus list --all` now actually lists every project.** The flag has been documented since
130
+ before v4.7 (`--all` / "Show all projects" in `amicus list`'s help text) but `listSidecars`
131
+ never read it, so it silently behaved exactly like a bare `amicus list`. It now enumerates
132
+ every project the global sessions-index knows about (an advisory navigation aid, not
133
+ authoritative — a stale entry pointing at a missing or unreadable project is skipped, not
134
+ surfaced as an error), deduped by canonical project identity.
135
+ - **`amicus list --search` was accepted and silently ignored.** The generic CLI arg parser took
136
+ any `--search <value>`, but nothing downstream read it, so the flag — whose sibling convention,
137
+ `amicus models --search`, was already a repo-wide pattern — quietly did nothing. It's now
138
+ implemented; see Added, above.
139
+ - **`amicus fanout --quiet` was accepted and silently ignored.** `quiet` is a repo-wide known
140
+ flag, so the command parsed and exited 0 — but `handleFanout` never forwarded it into
141
+ `runFanout`, which printed the launch banner and per-leg lines anyway. Same
142
+ accepted-but-ignored shape as `list --search` above.
143
+ - **`amicus pack save --version <semver>` was accepted and silently ignored — it wrote no pack at
144
+ all.** `version` is a global boolean flag, so `parseArgs` set `args.version = true`, stranded the
145
+ semver in positionals, and `bin/amicus.js` printed the amicus version banner *before* command
146
+ dispatch — `handlePack` never ran, and the command exited 0 having saved nothing.
147
+ `--version=2.0.0` failed identically. Meanwhile the handler read `args.version` for a value it
148
+ could never receive, and both the help text and `docs/usage.md` documented `--version <semver>`
149
+ as a real option. The pack's own version is now spelled **`--pack-version <semver>`** (honored on
150
+ both the flags and `--from-run` paths), and `pack save --version` fails fast with `BAD_ARGS`
151
+ naming the right spelling. Every other `--version` still prints the banner. Third instance of the
152
+ accepted-but-ignored shape, after `list --search` and `fanout --quiet` above.
153
+ - **A fanout whose server fails to start no longer drops its pack.** `errorWave` — the third
154
+ `buildWaveResult` call site — inherited a pre-seeded `tag` from `metadata.json` but not a
155
+ pre-seeded `pack`, so an MCP-spawned wave that died at server start persisted a `wave.json`
156
+ missing the pack it was launched with (and `amicus read --json` prefers `wave.json`). The two
157
+ other call sites already inherited both; this was the lone holdout.
158
+ - **The test suite no longer writes session directories outside its sandbox.** Several suites
159
+ passed the literal `'/tmp'` as a project cwd — on Windows that resolves to `C:\tmp`, a real
160
+ directory — so every run leaked real session dirs onto the developer's filesystem and into the
161
+ global sessions-index. They now sandbox under `os.tmpdir()`, pinned by
162
+ `tests/hermetic-tmp-guard.test.js`. This residue was what made `--all` an 8-second, 21k-row
163
+ dump on a developer machine.
164
+ - **`pack list` warnings now print to stderr, not stdout.** They previously shared stdout with
165
+ `pack list`'s data output — unlike `pack save`, which already used stderr for the same
166
+ "Warning:" text — so `pack list | grep` mixed diagnostics into data. `--json` output is
167
+ unaffected.
168
+ - **A council pack combining `critic` and `lenses` with a by-name (string) bench is now rejected
169
+ at validate-time (`PACK_INVALID`), matching the array-bench form.** It previously survived
170
+ run-mode validation and could later surface a mutual-exclusion error naming a flag the user
171
+ never typed. One consequence: a string-bench pack naming both `critic` and `lenses` — even
172
+ invoked with an explicit flag — now hard-fails `PACK_INVALID` instead of running; array-bench
173
+ packs already behaved this way, so this closes a bench-shape-dependent inconsistency rather
174
+ than introducing a new restriction.
175
+ - **The `{{var.}}` (empty variable key) template error now has a followable message.** It
176
+ previously told the user to run `--var =<value>` — a form the CLI's own `--var` parser rejects,
177
+ making the remedy unfollowable by construction; it now correctly explains that
178
+ `{{var.<key>}}` requires a key and lists the known variables.
179
+ - **`doctor`'s tmp-sweep checks no longer report a name-shaped directory as an unremovable
180
+ orphan.** A directory whose name happened to match the tmp-file pattern (e.g.
181
+ `.metadata.json.<pid>.<rand>.tmp`) was picked up by the orphan scan, then permanently failed to
182
+ unlink — parking the check at `warn` forever ("swept 0, N remaining (too fresh or
183
+ unremovable)") even after every real orphan was cleared. Directories are now excluded from
184
+ both sweeps (the metadata sweep, which never follows symlinks, also excludes
185
+ symlinks/sockets/FIFOs) before the scan ever returns them, so the check reports `ok` once
186
+ nothing real is left.
187
+
188
+ ### Added (follow-up)
189
+
190
+ - **`amicus list --limit <n>`**: show only the *n* newest rows (`0` = unlimited, and absent
191
+ behaves exactly as before). When the cap elides rows it says so, naming the real total; in
192
+ `--json` mode that notice goes to stderr so stdout stays a single parseable document. Caps
193
+ output only — `--all` still enumerates every project, since capping the walk would rank rows it
194
+ never saw.
195
+
196
+ ### CI
197
+
198
+ - **The MCP-Registry skip-check's version match is no longer a BRE with unescaped dots.**
199
+ `$VERSION` (a semver like `4.7.0`) was interpolated directly into the grep pattern, where an
200
+ unescaped `.` matches any character; the fail direction is fail-toward-skip (a false match
201
+ would silently drop a publish), so the dots are now backslash-escaped before matching. Not
202
+ exploitable in practice, but tightens the same idempotency check the v4.6.3 CI fix below
203
+ hardened.
204
+
205
+ ### Fixed
206
+
207
+ - A pack file whose JSON body is not an object (`null`, an array, a bare number or string) used to
208
+ read as a *successful* load and then crash with an uncaught `TypeError` — `council run --pack` and
209
+ `pack show` both died, and `pack show --json` exited 0 with `"pack": null`. It is now a clean
210
+ `PACK_NOT_FOUND`.
211
+ - **The MCP `amicus_start` shared-server path skipped the budget gate entirely unless a pack set
212
+ `maxCost`.** It now gates unconditionally with the same `maxCost`/`maxCostPerMtok` config fallback
213
+ the CLI has always used. Runs that previously proceeded may now be refused — that is the fix.
214
+ - Budget refusal text is now surface-aware on the MCP `amicus_start` (shared-server) path: it no
215
+ longer names `--max-cost`/`--no-cost-gate`, flags that do not exist there. Other MCP-visible
216
+ refusals — the `amicus_start` spawn fallback and the fanout/council reservation hint recorded in
217
+ run docs — still carry CLI-flavoured text; that remainder is filed, not fixed here. On BOTH
218
+ surfaces the remedy now names only levers that can actually clear the branch that fired: raising
219
+ the ceiling was previously suggested for a per-$/Mtok refusal, which it could never clear.
220
+ - A council pack could set `options.timeout` to `true` or to a non-numeric string and have it reach
221
+ the engine — `validatePack` checks option key names, never value types, and the old post-merge
222
+ check was `timeout <= 0`, which `true` passes by coercing to `1`. Such a pack now exits 1.
223
+ - `--cwd` typed without a value parsed as boolean `true` and reached 16 consumer sites — crashing
224
+ `council run` with a raw `TypeError` and silently resolving templates against `<cwd>/true`. It now
225
+ exits 1 at the entry point. Same treatment for `council run`'s `--out-dir`, `--claude-review`,
226
+ `--run-id`, and `--timeout` (which also accepted `NaN`).
227
+ - **`council run --out-dir` could write outside the project.** The MCP path has been fenced since
228
+ v4.5; the CLI now applies the same containment check.
229
+ - A council member whose model name collides with an `Object.prototype` key (`toString`,
230
+ `constructor`, …) crashed the Workspace seats repaint and every live tick after it.
231
+
232
+ ### Added (v4.7 PR7)
233
+
234
+ - **The seats table's trailing flag column marks a seat that was retried and stayed failed**
235
+ (PR1F-4): `↻ retried once`, on the errored seat's own primary cost row. Previously that
236
+ information only existed on a dead-seat row the v4.7 CA-4 convergence now suppresses, so it had
237
+ no home at all; a status-cell suffix was rejected because a dead seat's row can legitimately
238
+ carry `status: 'complete'`, and `complete — retried once` reads as "it finished, twice." Terminal
239
+ path only (`seatsFromRunStats`) — a seat retried while its run is still live shows no marker
240
+ until the run finishes.
241
+
242
+ ### Fixed (v4.7 PR7)
243
+
244
+ - **Four stale-paint paths in the workspace prose panels, plus three unhandled-rejection sites,
245
+ closed together (T19-m1/T19-m2).** A panel could repaint stale content after a
246
+ close/flip-blind/reopen sequence, after two loads landed out of order, after the same run's
247
+ artifact manifest grew mid-flight, or after being collapsed mid-flight and reopened; separately,
248
+ three fire-and-forget panel-load promises could reject uncaught. Fixed behind a new
249
+ `workspace-lazy.js` extraction: an unconditional panel-cache drop on every issue, a monotonic
250
+ per-panel issue token so only the newest in-flight load can paint, two-argument
251
+ `.then(ok, fail)` termination on every load promise (with an announced eviction and a self-check
252
+ against a stale cache write), and a sync-safe wrap around the matrix drill-in call so a
253
+ synchronous throw still surfaces instead of escaping uncaught. Worth recording honestly: the
254
+ collapse-mid-flight path was not closed by the cache-drop half — that half *converted* it from a
255
+ deterministic stale paint into a race, by orphaning a still-in-flight load that no longer had a
256
+ cache entry to fence it. The issue token is what actually closes it. Both land in the same commit
257
+ series, so no released state ever carried the race.
258
+ - **`amicus_fanout`'s wave briefing is now the rendered prompt, not the raw one** (W1-M4),
259
+ matching the parity the `amicus_start` in-process path already had. Previously a spawned child
260
+ that aborted before rendering its own copy left `briefing.md` — the file
261
+ `amicus list --search`/`amicus read` treat as the wave's search corpus — permanently holding
262
+ unrendered template markup. **Behaviour change:** a wave launched from a pack that forwards a
263
+ `template` now writes a second file into the wave dir, `briefing-input.md` (the raw prompt handed
264
+ to the spawned child so its own render still drives `promptMeta.template` provenance), and
265
+ `--search` now matches the rendered text even for a wave whose MCP-spawned child aborted early —
266
+ previously it matched nothing meaningful in that case; the same rendered excerpt (first 200
267
+ characters) is also seeded into `metadata.json` at creation for every MCP fan-out wave, pack or
268
+ not, so `amicus list`'s BRIEFING column is populated for an aborted wave too, not just a
269
+ completed one. `amicus_start`'s identical divergence
270
+ (`mcp-server.js:669`) is unchanged; nobody has driven that path end to end yet, so it is filed in
271
+ `BACKLOG.md`, not fixed here.
272
+ - **`amicus_fanout` now rejects an empty prompt or a non-positive timeout before creating anything
273
+ on disk**, closing the one remaining pid-less `status: 'running'` orphan-wave class the schema
274
+ didn't already cover. **Behaviour change:** a request shaped like `{prompt: '', ...}` or
275
+ `{timeout: -1, ...}` previously created a wave directory and spawned a child that then failed on
276
+ its own terms; it now returns a plain-text error and creates nothing. The Zod schema closes this
277
+ for one entry point (`prompt: z.string().min(1, …)`, `timeout: z.number().positive(…)`); a second,
278
+ identical check lives in the handler itself, after pack merge, because a pack can push the same
279
+ invalid values through a door the schema never sees (`validatePack` checks option key names, not
280
+ value types).
281
+
282
+ ### Changed (v4.7 PR7)
283
+
284
+ - **The budget-refusal text recorded in a run/wave doc's degrade record is now surface-neutral**
285
+ (PR6F-1), since that doc is read by both the CLI and MCP: "the $N cost ceiling for this run
286
+ refused it" / "Raise this run's cost ceiling, or turn the cost gate off, to seat them," in place
287
+ of CLI-flavoured `--max-cost`/`--no-cost-gate` wording that named flags an MCP caller cannot type.
288
+ A separate, structurally-unreachable copy of the same CLI-flavoured trailer (nothing ever renders
289
+ its `hint` on that path) was removed rather than reworded.
290
+
291
+ ## [4.6.3] - 2026-08-05
292
+
293
+ ### Added
294
+
295
+ - **`fable` now carries an authored direct-Anthropic route** (`anthropic/claude-fable-5`,
296
+ verified live: Anthropic's `/v1/models` lists it and a direct leg serves). With an
297
+ Anthropic key present, `fable` routes direct-first like the other Anthropic aliases;
298
+ the `ANTHROPIC_MODELS` floor gains a matching row so keyless installs validate it.
299
+ - **`doctor` gains a `session-metadata-tmp` check; `--fix` sweeps the orphans.** A kill
300
+ between an atomic write's tmp-file and rename leaves `.metadata.json.*.tmp` orphans in
301
+ per-session directories (the B09 class — ~30 write sites). Plain `doctor` reports them;
302
+ `--fix` removes orphans older than 60 s from the current project's sessions root and
303
+ announces the heal in the one voice (`Recovered: …`).
304
+ - **`council save` announces when it shadows a built-in bench.** Saving a council named
305
+ `free`/`budget`/`frontier` previously printed no notice at all (the overwrite marker only
306
+ tracked user-config names); the save now reports `shadowsBuiltin` (`--json`) and prints
307
+ the shadow notice.
308
+
309
+ ### Fixed
310
+
311
+ - **`models --check` no longer false-flags deliberate gateway-only routes.** A curated
312
+ alias whose direct form is derived (not authored) from its OpenRouter route is no
313
+ longer reported STALE — flat row, `GATEWAY STALE` row, candidates, and the
314
+ `fix: --add-alias` retarget suggestion all suppressed together — when the OpenRouter
315
+ route still serves. Deliberately gateway-only entries (`gpt-pro`) are annotated in
316
+ the curated data and never audited for a direct sibling. Kills the v4.6.2
317
+ release-gate false positive whose suggested "fix" was a silent tier downgrade.
318
+ - **The Workspace seats panel's dead-seat rows are now role-aware and old-run
319
+ resilient.** A model that died as critic but succeeded as chair no longer has
320
+ its dead row hidden by the chair's cost row (only a live reviewing leg —
321
+ seat/critic/lens — suppresses, and a dead critic only by a live critic leg);
322
+ the dead critic's row names its role. Pre-v4.6 runs render their losses too:
323
+ `verdict.seatLoss.deadBenchSeats` feeds rows, and `verdict.json`'s
324
+ `degrades[]` backstops a `run.json` that lost its checkpoint. A stale
325
+ `get-run` reply from a run you navigated away from can no longer repaint the
326
+ run now open.
327
+ - **A valueless `-o`/`--out` on `council verdict` now errors** (`BAD_ARGS`, flag named,
328
+ exit 1) instead of crashing mid-write (renameSync `TypeError`) and orphaning a
329
+ `true.tmp-<pid>` temp file. Behavior change, per the v4.6.3 R1 ruling.
330
+ - **README corrections from the v4.6.3 accuracy review**: the Node.js floor is
331
+ 22.12 (required since v3.0 — the README, install scripts, landing page, and
332
+ `amicus doctor`'s node check still said 18), and the optional-council-elements
333
+ list now matches the shipped skill (four opt-ins; the chair's verdict scale
334
+ has been standard, not opt-in). The doctor's `node` row now errors below
335
+ 22.12 (it previously passed anything ≥ 18).
336
+
337
+ ### CI
338
+
339
+ - **The MCP-Registry skip-check now verifies the version it trusts.** The
340
+ release workflow's idempotency pre-check previously skipped registry publish
341
+ on a bare HTTP 200; it now also requires the response body to both name the
342
+ exact version and report it as `active`, so preview-API schema churn — or a
343
+ stale/deprecated registry entry — can no longer produce a false skip. Every
344
+ new failure mode still routes toward publishing.
345
+
8
346
  ## [4.6.2] - 2026-08-05
9
347
 
10
348
  ### Added
@@ -91,6 +429,17 @@ All notable changes to Amicus are documented here. Format follows
91
429
  with replacement suggestions by `models --check` / `doctor`. This was the pin that made
92
430
  `models --check` exit 1 — the v4.6.2 release-gate risk.
93
431
 
432
+ ### CI
433
+
434
+ - **The macOS/node-24 unit leg now runs jest with `--workerIdleMemoryLimit=1GB`.** That leg —
435
+ and only that leg — intermittently lost a worker to a native SIGSEGV ("A jest worker process
436
+ was terminated by another process"), which fails whichever suite occupied the worker with
437
+ zero assertion failures and a green rerun. Three confirmed hits (2026-07-31
438
+ run-cost-unknown, 2026-08-04 PR #100 update-notice, 2026-08-05 PR #105 — 6466 passed, the
439
+ dead worker alone failed 1 suite) tripped the standing third-occurrence rule. The limit
440
+ makes jest recycle an idle worker before the leak reaches segfault territory; the flag is
441
+ injected via a matrix `include`, so the other five legs still run a bare `npm test`.
442
+
94
443
  ## [4.6.1] - 2026-08-03
95
444
 
96
445
  ### 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
- ![The Amicus council mid-ritual: five models Gemini, Llama, Grok, Claude Opusreading the same material independently, chaired by GPT](./docs/council.png)
7
+ <p align="center"><img src="./docs/cards/council-hero.svg" width="560" alt="The Amicus council ritual across three convenings: independent review spokes, anonymous cross-review, and a non-Claude chair synthesizing the verdict — the third convening seats a local Qwen2.5-Coder on-device alongside cloud peers"></p>
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
 
@@ -12,7 +12,7 @@ Hand Claude a plan, a design, a diff, an architecture decision, a manuscript —
12
12
 
13
13
  [![npm version](https://img.shields.io/npm/v/amicus?color=D97757&labelColor=1A1C29)](https://www.npmjs.com/package/amicus)
14
14
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue?labelColor=1A1C29)](./LICENSE)
15
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen?labelColor=1A1C29)](https://nodejs.org)
15
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D22.12-brightgreen?labelColor=1A1C29)](https://nodejs.org)
16
16
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?labelColor=1A1C29)](./CONTRIBUTING.md)
17
17
 
18
18
  **[Quick start ↓](#quick-start)** · [Commands](#commands) · [Documentation](#documentation) · [Troubleshooting](#troubleshooting)
@@ -62,7 +62,7 @@ One install delivers six things that work together:
62
62
 
63
63
  Claude is the orchestrator. The council and chat skills run *on top of* the engine; you talk to Claude, and Claude drives Amicus.
64
64
 
65
- ![What one install delivers: council skill, chat skill, CLI + MCP, live catalog](./docs/what-is-amicus.png)
65
+ <p align="center"><img src="./docs/cards/what-is-amicus.svg" alt="One install: the second-opinion council and sidecar skills riding the amicus CLI + MCP engine, with the live model catalog, watch/spend, and Council Workspace fed off it"></p>
66
66
 
67
67
  ---
68
68
 
@@ -89,6 +89,8 @@ flowchart LR
89
89
  E --> F["Reviewed copy<br/>+ run folder"]
90
90
  ```
91
91
 
92
+ <p align="center"><img src="./docs/cards/blind-ballots.svg" width="520" alt="Anonymized cross-review: reviews are relabeled A, B, C before the bench ranks them — one model disputes its own review"></p>
93
+
92
94
  **What a run produces** (in `output/<stem>-council/`):
93
95
 
94
96
  - `review-<model>.md` × N — each model's independent review.
@@ -99,14 +101,15 @@ flowchart LR
99
101
  findings-by-tier, cost — no chair prose). This is the default artifact handed to the user.
100
102
  - For an **editable source**, the accepted edits land in `<stem>-reviewed.<ext>` next to the original.
101
103
 
102
- **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.
104
+ **Optional council elements** (v2.2.0, all default off): four 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. **Chair verdict scale** *(standard since v2.2.0's follow-ups — no longer opt-in)*: the chair always closes with 3–5 hard questions and one parseable `VERDICT: Ship it | Fix these first | Fundamental rethink` line.
103
105
 
104
106
  - **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.
105
107
  - **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.
106
108
  - **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.
107
- - **Chair verdict scale** — the chair closes with 3–5 hard questions and one parseable line: `VERDICT: Ship it | Fix these first | Fundamental rethink`.
108
109
  - **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.
109
110
 
111
+ <p align="center"><img src="./docs/cards/dial-it-up.svg" alt="The four opt-in council elements as toggles — critic seat and debate on, expert lenses and Claude-in-the-council off — with the up-front run-shape disclosure"></p>
112
+
110
113
  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).
111
114
 
112
115
  **Cost is disclosed up front.** Before any model launches, you see the run shape — including any enabled optional elements — for example:
@@ -115,6 +118,8 @@ The critic and lens methodologies are adapted from the `/critic` and `/debate` a
115
118
 
116
119
  Then the council waits for your confirmation.
117
120
 
121
+ <p align="center"><img src="./docs/cards/priced-up-front.svg" width="520" alt="The budget gate: a $0.42 run passes, a $31.80 bench is blocked, and the receipt matches the estimate"></p>
122
+
118
123
  The skill lives at **[`skills/second-opinion/SKILL.md`](./skills/second-opinion/SKILL.md)**; the design spec behind it is **[`skills/second-opinion/COUNCIL-DESIGN.md`](./skills/second-opinion/COUNCIL-DESIGN.md)**. For what `amicus council tally|verdict|report|stats` actually take as input and produce — field-by-field schemas, verdict.json's provenance, and a full worked example run against the real CLI — see **[docs/council.md](./docs/council.md)**.
119
124
 
120
125
  ---
@@ -128,10 +133,14 @@ The council is the hero — start with the everyday way, and reach for the more
128
133
  - **With a debate round.** Add `--debate` and every Contested or Disputed finding goes back to its raiser to **defend, amend, or withdraw** while the disputing judges re-vote — exactly one rebuttal round, then the final tally. → [The Council](#the-council)
129
134
  - **On free, local, private models — at $0.** Point the council (and sidecars) at an OpenAI-compatible server already running on your machine — Ollama, LM Studio, or vLLM — with `amicus provider add`. No API key, no per-token bill, nothing leaves your machine, and it works offline. → [`amicus provider`](./docs/usage.md#amicus-provider)
130
135
 
136
+ <p align="center"><img src="./docs/cards/same-table.svg" width="520" alt="A local Ollama model seated as an equal council member at $0.00 — a member, not a mode"></p>
137
+
131
138
  ### Headless council (CI)
132
139
 
133
140
  The same pipeline runs with no Claude runtime at all: `amicus council run --prompt-file briefing.md --models gemini,glm --chair deepseek --json` executes the review waves, the anonymized cross-review, the tally, and the chair verdict in one command, and writes the full run directory (`verdict.json` with the chair's parsed `overallVerdict`, `report.html`, every review and judge output). That is what powers the repo's own **Council Review GitHub Action v2** — on PRs labeled `council-review` it posts an adjudicated verdict as a check run plus a sticky comment, uploads the run directory as an evidence artifact, and can optionally gate merges via its `fail_on` input (default: report-only). Reference: [docs/council.md](./docs/council.md#amicus-council-run).
134
141
 
142
+ <p align="center"><img src="./docs/cards/ship-gate.svg" alt="A council gating a release pipeline: exit 0 ships it, exit 1 sends it back"></p>
143
+
135
144
  ### Free council (zero-cost)
136
145
 
137
146
  Want the cross-examination without the model spend? `amicus setup` offers a **Free OpenRouter council** mode — readline wizard option 2, and the Electron **Models** step. It detects the free `:free` models live from the catalog, lets you multi-pick (Enter takes a vendor-diverse default), and saves them as `councils.free` — a first-class `councils` config primitive seeded under collision-safe `free-*` aliases. Your `config.default` is left untouched, and all you need is an `OPENROUTER_API_KEY`.
@@ -192,7 +201,7 @@ Every path delivers the MCP server and both skills. They differ in what else you
192
201
 
193
202
  #### With npm — recommended
194
203
 
195
- The canonical path, and the one that gets you the full interactive experience (needs [Node.js](https://nodejs.org) ≥ 18):
204
+ The canonical path, and the one that gets you the full interactive experience (needs [Node.js](https://nodejs.org) ≥ 22.12):
196
205
 
197
206
  ```bash
198
207
  npm install -g amicus
@@ -202,7 +211,7 @@ This is the path to pick unless you specifically want the plugin's slash command
202
211
 
203
212
  #### With the install script
204
213
 
205
- Same result as npm, one command — macOS, Linux, or Windows (needs [Node.js](https://nodejs.org) ≥ 18):
214
+ Same result as npm, one command — macOS, Linux, or Windows (needs [Node.js](https://nodejs.org) ≥ 22.12):
206
215
 
207
216
  ```bash
208
217
  # macOS / Linux
@@ -333,7 +342,7 @@ Everything you need before your first run, and what's optional.
333
342
 
334
343
  **Runtime**
335
344
 
336
- - **Node.js ≥ 18** — `node --version` to check. This is the only hard runtime prerequisite.
345
+ - **Node.js ≥ 22.12** — `node --version` to check. This is the only hard runtime prerequisite.
337
346
  - **An active Claude Code or Cowork session** — Amicus is orchestrated by Claude; it is not a standalone chatbot.
338
347
 
339
348
  **Install path & the git toolchain**
@@ -398,7 +407,7 @@ When you don't need a full council — just one other model's take — fork a co
398
407
  |---------|--------------|
399
408
  | `amicus start` | Launch a new session (interactive or `--no-ui`). |
400
409
  | `amicus fanout` | Run N models on the same prompt in parallel (headless). |
401
- | `amicus list` | Show previous sessions. |
410
+ | `amicus list` | Show previous sessions — shows the `--tag <t>` set at launch, `--search <q>` filters, `--limit <n>` caps the rows, `--all` spans every known project. |
402
411
  | `amicus resume` | Reopen a previous session with full history. |
403
412
  | `amicus continue` | Start a new session building on a previous one. |
404
413
  | `amicus read` | Output a session's summary / conversation / metadata. |
@@ -441,13 +450,13 @@ $ amicus status demo123 --json
441
450
  "taskId": "demo123",
442
451
  "status": "complete",
443
452
  "elapsed": "5m 0s",
444
- "version": "4.6.2",
453
+ "version": "4.7.0",
445
454
  "model": "google/gemini-2.5-flash",
446
455
  "phase": "terminal"
447
456
  }
448
457
  ```
449
458
 
450
- `amicus list --status` accepts `running`, `complete`, `error`, `timed-out`, `aborted`, `crashed`, `idle-timeout`. Full field-by-field docs (a running session's `messages`/`STALLED` reporting, wave-ID status shape, etc.) are in [docs/usage.md](./docs/usage.md).
459
+ `amicus list --status` accepts `running`, `complete`, `error`, `timed-out`, `aborted`, `crashed`, `idle-timeout`. Rows carry a `TAG` column (set at launch with `--tag <t>`); `--search <q>` filters by id/tag/briefing substring, `--limit <n>` caps the row count (0 = unlimited), and `--all` lists across every known project. Full field-by-field docs (a running session's `messages`/`STALLED` reporting, wave-ID status shape, etc.) are in [docs/usage.md](./docs/usage.md).
451
460
 
452
461
  ---
453
462
 
@@ -462,6 +471,8 @@ amicus models --search gemini # filter by substring
462
471
 
463
472
  `start`/`fanout` validate your model against the catalog before launching (skip with `--no-validate-model`). You can also always bypass aliases and pass a full model ID directly — bare `provider/model` (canonical) or `openrouter/provider/model` (explicit override); see Routing below. Catalog internals, alias management, and the full-id passthrough table are in **[docs/usage.md § Models](./docs/usage.md#amicus-models--the-model-catalog)**.
464
473
 
474
+ <p align="center"><img src="./docs/cards/build-the-bench.svg" alt="Direct keys, one OpenRouter key, or local runtimes seat a mixed bench — different families, different blind spots"></p>
475
+
465
476
  ### Routing
466
477
 
467
478
  - **Bare `provider/model`** (e.g. `openai/gpt-5.5`, `anthropic/claude-opus-4-8`, `google/gemini-3.6-flash`) is the canonical, policy-routed form — Amicus routes it **direct-first**: your direct provider key when one is configured, falling back to OpenRouter automatically when only an OpenRouter key exists.
@@ -475,7 +486,7 @@ Full details, the API-key/prefix table, and the migration notice are in **[docs/
475
486
 
476
487
  ## MCP integration
477
488
 
478
- The MCP server is auto-registered on install (Claude Code and Claude Desktop / Cowork). It exposes sixteen tools:
489
+ The MCP server is auto-registered on install (Claude Code and Claude Desktop / Cowork). It exposes these tools:
479
490
 
480
491
  | Tool | What it does |
481
492
  |------|--------------|
@@ -483,7 +494,7 @@ The MCP server is auto-registered on install (Claude Code and Claude Desktop / C
483
494
  | `amicus_status` | Poll a task (or a fanout wave) for completion. |
484
495
  | `amicus_wait` | Block inside one tool call until a session/wave finishes or the wait window closes. |
485
496
  | `amicus_read` | Read results: summary, conversation, metadata, or JSON. |
486
- | `amicus_list` | List past sessions. |
497
+ | `amicus_list` | List past sessions (`tag` field; `search` filters by id/tag/briefing substring). |
487
498
  | `amicus_resume` | Reopen a session. |
488
499
  | `amicus_continue` | New session building on a previous one. |
489
500
  | `amicus_abort` | Stop a running session. |
package/bin/amicus.js CHANGED
@@ -23,6 +23,8 @@ const { handleResume, handleContinue } = require('../src/cli-handlers-resume-con
23
23
  const { isOneShotCommand, armExitWatchdog } = require('../src/utils/lifecycle');
24
24
  const { suggestCommand } = require('../src/utils/input-validators');
25
25
  const { unknownFlags, getKnownFlags } = require('../src/utils/known-flags');
26
+ const { packSaveVersionConflict } = require('../src/utils/cli-preflight');
27
+ const { failJson } = require('../src/utils/error-doc');
26
28
  const { logger } = require('../src/utils/logger');
27
29
 
28
30
  const VERSION = require('../package.json').version;
@@ -53,6 +55,23 @@ async function main() {
53
55
  process.exit(1);
54
56
  }
55
57
 
58
+ // `--cwd` typed with no value parses as boolean `true` (src/cli.js:101) and
59
+ // `--cwd=` as '' (src/cli.js:72). DEFAULTS (src/cli.js:28) always seeds a
60
+ // real absolute string, so a non-string or empty cwd can ONLY mean "typed
61
+ // without a value" — which makes this guard provably free of false
62
+ // positives. Left unguarded it reached 16 `args.cwd || process.cwd()`
63
+ // sites across 9 handlers: council run threw a raw TypeError, template
64
+ // silently resolved <cwd>/true.
65
+ // No dash check here: absolute paths never start with '-', and
66
+ // `--cwd ./x` is legitimate.
67
+ if (typeof args.cwd !== 'string' || args.cwd === '') {
68
+ console.error('Error: --cwd requires a value');
69
+ console.error(command
70
+ ? `Run \`amicus ${command} --help\` to see valid options.`
71
+ : 'Run `amicus --help` to see valid options.');
72
+ process.exit(1);
73
+ }
74
+
56
75
  // Install crash handler for MCP-spawned processes (have --task-id)
57
76
  if (args['task-id'] && (command === 'start' || command === 'continue')) {
58
77
  const { installCrashHandler } = require('../src/sidecar/crash-handler');
@@ -86,6 +105,16 @@ async function main() {
86
105
  }
87
106
  }
88
107
 
108
+ // `pack save` documents a per-pack `--pack-version <semver>`. `--version` is a
109
+ // global BOOLEAN_FLAG, so `pack save … --version 2.0.0` used to fall straight
110
+ // into the banner below: exit 0, no pack written, the semver stranded in
111
+ // positionals. Reject that one combination by name instead of silently doing
112
+ // something else; every other --version still prints the banner.
113
+ const versionConflict = packSaveVersionConflict(args);
114
+ if (versionConflict) {
115
+ process.exit(failJson(!!args.json, versionConflict));
116
+ }
117
+
89
118
  // Handle --version
90
119
  if (args.version) {
91
120
  console.log(`amicus v${VERSION}`);
@@ -221,6 +250,8 @@ async function handleList(args) {
221
250
  status: args.status,
222
251
  all: args.all,
223
252
  json: args.json,
253
+ search: args.search,
254
+ limit: args.limit,
224
255
  project: args.cwd
225
256
  });
226
257
  }