amicus 4.4.0 → 4.5.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 +162 -0
- package/README.md +17 -2
- package/bin/amicus.js +10 -0
- package/docs/DISTRIBUTION.md +234 -0
- package/docs/ROADMAP.md +226 -0
- package/docs/SHIMS.md +62 -0
- package/docs/architecture.md +104 -0
- package/docs/configuration.md +395 -0
- package/docs/council.md +970 -0
- package/docs/doc-system.md +92 -0
- package/docs/electron-testing.md +471 -0
- package/docs/jsdoc-setup.md +75 -0
- package/docs/opencode-integration.md +114 -0
- package/docs/publishing.md +60 -0
- package/docs/schemas.md +56 -0
- package/docs/testing.md +589 -0
- package/docs/troubleshooting.md +298 -0
- package/docs/usage.md +849 -0
- package/electron/fold.js +1 -1
- package/electron/main.js +4 -1
- package/electron/setup-ui-aliases.js +6 -6
- package/electron/workspace-ui/live-model.js +12 -1
- package/electron/workspace-ui/md-lite.js +52 -8
- package/electron/workspace-ui/workspace-app.js +39 -17
- package/electron/workspace-ui/workspace-matrix.js +46 -9
- package/electron/workspace-ui/workspace-panels.js +88 -19
- package/electron/workspace-ui/workspace-render.js +17 -1
- package/electron/workspace-ui/workspace-verbs.js +48 -2
- package/package.json +8 -3
- package/schemas/council-run-live.schema.json +1 -1
- package/schemas/council-run.schema.json +34 -0
- package/schemas/error.schema.json +1 -1
- package/schemas/event.schema.json +1 -1
- package/schemas/pack.schema.json +30 -0
- package/schemas/progress.schema.json +13 -1
- package/schemas/run-live.schema.json +1 -1
- package/schemas/run.schema.json +2 -1
- package/schemas/spend.schema.json +52 -4
- package/schemas/wave-live.schema.json +1 -1
- package/schemas/wave.schema.json +2 -1
- package/skills/second-opinion/SKILL.md +5 -0
- package/src/cli-handlers-council-run.js +51 -8
- package/src/cli-handlers-pack.js +238 -0
- package/src/cli-handlers-run.js +36 -8
- package/src/cli-handlers-spend.js +20 -2
- package/src/cli-handlers-template.js +53 -0
- package/src/cli-handlers-watch.js +11 -0
- package/src/cli.js +68 -5
- package/src/council/briefings-debate.js +27 -7
- package/src/council/briefings-stage2.js +155 -25
- package/src/council/briefings.js +24 -1
- package/src/council/findings.js +199 -9
- package/src/council/parse-stage2.js +10 -2
- package/src/council/presets-cli.js +23 -11
- package/src/council/report.js +19 -8
- package/src/council/run-assemble.js +42 -1
- package/src/council/run-budget.js +64 -11
- package/src/council/run-chair.js +4 -1
- package/src/council/run-debate.js +4 -2
- package/src/council/run-finalize.js +102 -0
- package/src/council/run-launch.js +29 -1
- package/src/council/run-server.js +248 -0
- package/src/council/run-stage2.js +118 -0
- package/src/council/run-stages.js +134 -110
- package/src/council/run-state.js +40 -1
- package/src/council/run.js +45 -47
- package/src/council/tally.js +10 -0
- package/src/headless.js +180 -7
- package/src/mcp-council-run.js +108 -4
- package/src/mcp-server.js +203 -7
- package/src/mcp-tools.js +15 -5
- package/src/observe/council-legs.js +60 -3
- package/src/observe/live-doc.js +18 -1
- package/src/observe/watch-render.js +4 -1
- package/src/pack/pack-cli.js +38 -0
- package/src/pack/pack-forward.js +96 -0
- package/src/pack/pack-resolve.js +297 -0
- package/src/pack/pack-store.js +130 -0
- package/src/pack/pack-validate.js +113 -0
- package/src/sidecar/child-sessions.js +1 -2
- package/src/sidecar/fanout-leg-fallback.js +69 -21
- package/src/sidecar/fanout-leg.js +6 -0
- package/src/sidecar/fanout-signals.js +61 -0
- package/src/sidecar/fanout-wave-io.js +75 -0
- package/src/sidecar/fanout.js +82 -74
- package/src/sidecar/progress-fields.js +26 -4
- package/src/sidecar/progress.js +42 -1
- package/src/sidecar/session-utils.js +23 -14
- package/src/sidecar/start.js +5 -4
- package/src/sidecar/workspace-auto-open.js +69 -0
- package/src/sidecar/workspace-window.js +46 -1
- package/src/spend-query.js +17 -5
- package/src/template/apply.js +88 -0
- package/src/template/render.js +86 -0
- package/src/template/store.js +106 -0
- package/src/utils/config.js +65 -25
- package/src/utils/error-doc.js +5 -0
- package/src/utils/lifecycle.js +37 -1
- package/src/utils/path-fence.js +39 -1
- package/src/utils/pricing.js +26 -10
- package/src/utils/result-schema-rebuild.js +1 -0
- package/src/utils/result-schema.js +8 -2
- package/src/utils/server-setup.js +79 -1
- package/src/utils/spend-ledger.js +24 -3
- package/src/workspace/artifact-guard.js +66 -7
- package/src/workspace/fold-format.js +33 -4
- package/src/workspace/live-normalize.js +28 -15
- package/src/workspace/run-detail.js +13 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.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
|
@@ -3,6 +3,168 @@
|
|
|
3
3
|
All notable changes to Amicus are documented here. Format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
|
|
5
5
|
|
|
6
|
+
## [4.5.0] - 2026-07-28
|
|
7
|
+
|
|
8
|
+
"Save and share your councils" — complex run configurations become one command, repeatable and
|
|
9
|
+
shareable, and the flagship Council Workspace stops being opt-in on its best client.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Policy packs — save a full run configuration and invoke it by name.** `amicus pack save <name>
|
|
14
|
+
--kind council|fanout|solo [flags]` (or `--from-run <id>`, which captures an existing council
|
|
15
|
+
run / fanout wave / solo session instead of typing flags) writes one JSON file per pack to
|
|
16
|
+
`~/.config/amicus/packs/<name>.json`; `pack list` / `pack show` / `pack rm` manage them. `--pack
|
|
17
|
+
<name|path>` on `amicus start` / `fanout` / `council run` — and the new `pack` param on the
|
|
18
|
+
`amicus_start` / `amicus_fanout` / `amicus_council_run` MCP tools — loads a pack's bench,
|
|
19
|
+
chair/critic/lenses, options, and briefing template as this run's defaults. **Explicit flags
|
|
20
|
+
always override the pack's values, and the pack is recorded either way** — `pack: {name,
|
|
21
|
+
version, hash, source}` lands on the resulting session `metadata.json`, wave `metadata.json` /
|
|
22
|
+
`wave.json`, or council `run.json`. Precedence throughout: **flag > pack > config default >
|
|
23
|
+
built-in default**. A pack is validated on save (hard-fail — `PACK_INVALID` — with any
|
|
24
|
+
non-fatal warnings printed to stderr) and again whenever it's used to launch a run; `pack show`
|
|
25
|
+
never fails on an invalid pack, only reports what's wrong with it; `pack show` and `pack rm` both
|
|
26
|
+
return `PACK_NOT_FOUND` for a missing pack. New `schemas/pack.schema.json`.
|
|
27
|
+
- **MCP pack semantics.** Over MCP, `pack` resolves entirely **in-process**, on the same call that
|
|
28
|
+
reads it — a resolved pack is never forwarded as `--pack` to a spawned child. Two knobs get
|
|
29
|
+
special-cased handling for CLI parity: a pack's `options.maxCost` and `briefing.template` have no
|
|
30
|
+
MCP schema param of their own on `amicus_start`/`amicus_fanout`, but they still apply — forwarded
|
|
31
|
+
to the spawned CLI child's argv as `--max-cost`/`--template` (`amicus_fanout` always spawns;
|
|
32
|
+
`amicus_start`'s spawn-fallback path does the same), or, on `amicus_start`'s in-process
|
|
33
|
+
shared-server path, applied via the same budget-gate/template-render code the CLI itself uses,
|
|
34
|
+
before any session is created — so a shared pack's spend cap and briefing template never silently
|
|
35
|
+
vanish over MCP. (`amicus_council_run` already has real MCP params for both.) Any *other* pack
|
|
36
|
+
knob with no destination in a tool's own MCP input schema is never silently dropped either: it
|
|
37
|
+
surfaces as an explicit `Notice: pack '<name>' sets <key>, which <tool> does not support over
|
|
38
|
+
MCP — ignored.` content block, naming the pack's own camelCase option key (e.g. `contextTurns`,
|
|
39
|
+
never the CLI's `context-turns`). Concretely, `amicus_fanout` has no MCP destination for
|
|
40
|
+
`options.contextTurns` / `options.contextMaxTokens` (both notice); `amicus_start` has real params
|
|
41
|
+
for both, so neither does.
|
|
42
|
+
- **Council packs do not accept `agent`/`thinking`/`summaryLength`.** They were inert on every
|
|
43
|
+
surface — no council code path, CLI or MCP, ever reads a pack-filled one; the engine hardcodes
|
|
44
|
+
agent `Plan`/summaryLength `verbose` regardless of what a pack says — so `KIND_OPTIONS.council`
|
|
45
|
+
never accepted them; a council pack that sets one fails `pack save` (`PACK_INVALID`), naming the
|
|
46
|
+
offending key. They remain valid, and functional, on `fanout`/`solo` packs.
|
|
47
|
+
- **Briefing templates.** `amicus template list|show`, plus `--template <name|path>` / `--artifact
|
|
48
|
+
<file>` / `--var <k=v>` (repeatable) on `start` / `fanout` / `council run`, render a
|
|
49
|
+
`{{variable}}` briefing before it's sent. Templates are Markdown files in
|
|
50
|
+
`~/.config/amicus/templates/` — a same-named user file shadows a built-in, the same precedent
|
|
51
|
+
saved councils already use — and v4.5 ships one built-in, `review`. Known variables: `{{prompt}}`,
|
|
52
|
+
`{{artifact}}`, `{{artifact_path}}`, `{{date}}`, `{{project}}`, `{{var.<key>}}`. Rendering is
|
|
53
|
+
strict by design: an unknown variable, a slot with no data behind it, or data passed with no slot
|
|
54
|
+
to receive it are all hard errors (`TEMPLATE_RENDER`) rather than a silently dropped value. MCP
|
|
55
|
+
has no `template` param of its own on any of the three run tools — a pack's `briefing.template` is
|
|
56
|
+
the only way a template reaches an MCP-invoked run.
|
|
57
|
+
- **The Council Workspace auto-opens on `amicus_council_run` from Claude Code (local).** When the
|
|
58
|
+
MCP tool `amicus_council_run` is invoked from Claude Code (local), the same Electron window that
|
|
59
|
+
`amicus watch <runId> --ui` has always opened by hand now launches automatically, detached, right
|
|
60
|
+
after the run starts — no more separate `--ui` call to see the flagship v4.4 surface. The CLI
|
|
61
|
+
`amicus council run` is unaffected (there is no MCP client to detect on that path). Decision
|
|
62
|
+
order: an explicit `ui: false` param beats everything; the hard guards (Electron not installed —
|
|
63
|
+
this path never installs it; Linux with no `DISPLAY`) beat even an explicit `ui: true`; `ui: true`
|
|
64
|
+
then overrides both the new `workspace.autoOpen` config key and the client check; short of an
|
|
65
|
+
explicit param, `workspace.autoOpen === false` disables it, and any client other than Claude Code
|
|
66
|
+
(local) simply doesn't auto-open. New `workspace.autoOpen` config key (`config.json`, default on
|
|
67
|
+
— only an explicit `false` turns it off). The tool response carries `workspaceOpened: boolean`
|
|
68
|
+
and, only when it did not open, `workspaceOpenReason` (`param-suppressed`, `electron-absent`,
|
|
69
|
+
`no-display`, `config-disabled`, `client-not-code-local`, or a `spawn-failed:` /
|
|
70
|
+
`auto-open-failed:` detail).
|
|
71
|
+
|
|
72
|
+
### Fixed
|
|
73
|
+
|
|
74
|
+
- **A failed council seat no longer renders as perpetually live.** `createSession`'s early return
|
|
75
|
+
under a shared server bypassed the terminal `progress.json` write, leaving a stage marked
|
|
76
|
+
in-progress in `progress.json` after `metadata.json` had already recorded the error. The
|
|
77
|
+
terminal-write logic is now one shared helper (`writeTerminalProgressSafe`), called at all three
|
|
78
|
+
early-return sites plus the original one, so the paths can no longer drift apart.
|
|
79
|
+
- **Collided artifact names no longer misattribute one model's prose to another's.** Two bench
|
|
80
|
+
models whose sanitized filenames collide (e.g. `vendor/a` and `vendor?a` both → `vendor-a`) share
|
|
81
|
+
one physical file on disk; the Council Workspace compounded that with a rendering bug that showed
|
|
82
|
+
the first model's review/judge prose under the *second* model's panel — including, in a `--debate`
|
|
83
|
+
run, the rebuttal/re-vote drill-in. Collided names now get a deterministic suffix (`~2`, `~3`,
|
|
84
|
+
…), and every Workspace file lookup consults the resulting name map instead of recomputing a bare
|
|
85
|
+
sanitized name — the second colliding model's row is now correctly dropped by the existing
|
|
86
|
+
presence filter (its suffixed name was never physically written) instead of showing the wrong
|
|
87
|
+
model's text, and a run-integrity banner names the collision so the gap reads as a known
|
|
88
|
+
limitation rather than missing data.
|
|
89
|
+
- **A blind-mode toggle no longer collapses every open prose panel or repaints twice.** Flipping
|
|
90
|
+
Blind mid-run used to unconditionally recompute the blind default, forcing one paint with the
|
|
91
|
+
wrong value, a restore, and a second compensating repaint — and reset every lazy-loaded panel's
|
|
92
|
+
open/loaded state along the way, closing whatever the user had expanded. Blind state and
|
|
93
|
+
lazy-panel state now key off whether the run — and, separately, its status — actually changed
|
|
94
|
+
since the last render, so a same-run toggle updates in place and paints once without closing any
|
|
95
|
+
open panel; a run reaching its terminal status still recomputes the blind default and auto-reveals
|
|
96
|
+
exactly as before.
|
|
97
|
+
- **`renderSeats` now reorders rows to match the composed run document.** The keyed seat-table
|
|
98
|
+
update already added and removed rows on change but never moved one, so the table's row order
|
|
99
|
+
froze at first render — visibly wrong once a repair solo or a new wave changed the underlying leg
|
|
100
|
+
order mid-run. Existing rows are now moved into place at the end of every render pass.
|
|
101
|
+
- **`amicus council show` no longer reports a catalog-delisted bench member as healthy, and a
|
|
102
|
+
dropped member is no longer invisible to scripted/MCP callers.** `show`'s resolved/dropped split
|
|
103
|
+
checked only whether a member's alias mapped to *some* id, never whether that id was still in
|
|
104
|
+
the cached catalog — so a preset member whose alias now resolves to a catalog-absent id (e.g. a
|
|
105
|
+
direct-vendor route with no matching cached row) read as fully healthy in `show` while the real
|
|
106
|
+
run path (`resolveCouncilMembers`) silently dropped it on every actual run. `show` now reuses
|
|
107
|
+
that exact check — alias resolution, then catalog membership, with the same local-provider/
|
|
108
|
+
offline-catalog rule that a catalog it cannot consult never blocks a member, only a non-empty
|
|
109
|
+
catalog that omits it does. Separately, `council run --json` already suppressed the human-mode
|
|
110
|
+
`Notice: dropped unavailable council member(s): ...` line, and `run.json` carried no field for
|
|
111
|
+
it at all — a JSON-mode or MCP caller had zero signal a bench member vanished short of diffing
|
|
112
|
+
`bench` against the preset's nominal member list. `run.json` now carries an additive
|
|
113
|
+
`droppedMembers: [{member, reason}]` array (present only when at least one member was actually
|
|
114
|
+
dropped), reaching the `--json` envelope and the `amicus_council_run` MCP response body for
|
|
115
|
+
free. Resolution behavior itself — which members run, exit codes, spend — is unchanged; this is
|
|
116
|
+
observability only.
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- **`amicus_start` / `amicus_fanout`'s MCP schemas no longer declare a JSON-Schema `default` for
|
|
121
|
+
`agent`, `noUi`, or `includeContext`.** Client-visible metadata only — nothing behavioral: the
|
|
122
|
+
defaults are still applied at the same read sites they always were, and are still stated in each
|
|
123
|
+
param's own description. (`amicus_resume` / `amicus_continue`'s `noUi` keep their schema-level
|
|
124
|
+
default; they were not part of this pass.)
|
|
125
|
+
|
|
126
|
+
### Removed
|
|
127
|
+
|
|
128
|
+
- **The inert `repairCanHonorContract` guard.** 4.4.1's empty-findings acceptance flipped this
|
|
129
|
+
predicate permanently true by its own design, so `run-stages.js`'s `repairable &&` check could no
|
|
130
|
+
longer short-circuit on it and no test failed if the function were deleted outright — a
|
|
131
|
+
silent-deletion hazard that would otherwise re-arm the deadlock it used to guard against the day
|
|
132
|
+
empty-set validation tightens again. Removed deliberately instead: the underlying reasoning moved
|
|
133
|
+
to its call site, and the zero-findings regression test's comment now explains why the case it
|
|
134
|
+
covers still holds without the guard.
|
|
135
|
+
|
|
136
|
+
## [4.4.1] - 2026-07-27
|
|
137
|
+
|
|
138
|
+
A fast-follow patch on 4.4.0. Every item is a correction to something already shipped, and almost all of it was measured against real paid council runs rather than reasoned about — the five gate councils that certified the Council Workspace are also what found these. Five behaviour changes ride along and are called out under **Changed**, because a user upgrading a patch should not discover them by surprise.
|
|
139
|
+
|
|
140
|
+
### Fixed
|
|
141
|
+
|
|
142
|
+
- **The repair path is whole for the first time.** 4.4.0 gave the Stage-1 findings repair the review it was repairing; the judge, chair, defense and re-vote repair prompts carried the identical omission, and `buildChairRepairPrompt` took no arguments at all. A repair leg is a *fresh* session with no memory of the turn it is repairing, so shipping only the validation errors asked a model to correct something it had never seen. **Three of the five paid gate councils burned a seat on it:** `wsgate02`'s `qwen` and `wsgate04`'s `glm` each refused twice ("I don't have a previous review to correct"), so a 4-model bench silently adjudicated on 3 while still paying for the fourth; `costgate01`'s `grok` complied instead — by **inventing a self-referential finding about its own empty output**, which entered `tally.json`, the street-cred rankings, the chair synthesis, and a human's decision. Every repair call site now embeds the text that actually failed, verbatim and uncapped, tracked across attempts so the errors and the artifact always describe the same generation. Separately, a repair that **silently changes the finding count is now refused** rather than adjudicated: the repair contract is "the same findings, fixed", and a count change is exactly the fabrication shape above. Where the original block was absent or unparseable there is no count to compare, so the repair is accepted but marked `findingsUnverified` rather than implying a check happened. A repair's output never replaces the review's prose — that would hand the judges a narrative-free review and put a JSON dump in the Stage-2 bundle.
|
|
143
|
+
- **Three of four seats on a paid council were silently truncated by the fence extractor.** The closing-fence pattern was unanchored, so the **first triple-backtick anywhere inside a JSON body ended the match** — and a review *of markdown* inevitably quotes a fence. On the $1.95 renderer-review council, `glm`, `opus` and `minimax` all came back `NOT_PARSEABLE` and collapsed to `conformance: unstructured`; replayed against the same artifacts after the fix they yield 6, 5 and 4 findings respectively. **15 of 17 findings were lost or left to a paid repair wave to rescue, and the chair synthesised from the two that survived without knowing the rest existed.** The extractor now enumerates every fenced opener independently, reads each one both ways (close-at-line-start primary, same-line close as fallback), and lets **`JSON.parse` arbitrate** — the last opener whose body actually parses wins. This is the repo's only fence extractor and all five consumers funnel through it, so judge, debate-defense and re-vote parsing carried the identical defect and are fixed by the same change; each now has its own test so a future re-implementation cannot regress one silently. The malformed-versus-absent distinction is preserved deliberately: a cut-off emit that never closed at all is still *absent*, because the repair path answers "no findings block" and "a broken findings block" differently.
|
|
144
|
+
- **A council run no longer races itself for OpenCode's database.** A run started a fresh OpenCode server for the Stage-1 seat wave, the critic solo, each findings repair, the Stage-2 judge wave, each judge repair, each debate wave and the chair chain — 10+ spawns, each one a fresh chance to lose OpenCode's SQLite startup race. Stage 1 launches its seat wave and its critic solo under one `Promise.all`, so two of those starts are ~140 ms apart *by construction*: one run **lost four of five seats in 736 ms** to `database is locked` and failed quorum, which is what made `--critic` a coin flip. A run now acquires **one** server and forwards it into every launch, closed once on the single path every terminal outcome already funnels through. It never fails closed — a shared server that will not start is a notice, and the run falls back to one server per wave exactly as before. The Stage-2 anonymization boundary was verified rather than assumed: judges run in the run's `_scratch` directory and scoping is per-call, so nothing about it ever lived in the server process. Separately, a **lock-class** start failure (`database is locked`, `database table is locked`, `SQLITE_BUSY` — and nothing else) is now retried 3 times over ≤750 ms, which covers the races a single process cannot remove: two amicus processes, or a CLI run beside a live MCP server. A missing binary, an auth failure, a port conflict and a failed health check are deterministic and fall straight through rather than tripling the latency before the same error.
|
|
145
|
+
- **`amicus spend` stopped reporting a total that `council run` calls inexact.** A leg whose spawned subagent could not be accounted for wrote a **priced** ledger row, so `unpricedRows` never caught it and the product's two truthfulness surfaces disagreed about the same dollars. Such a row now carries the flag and is counted as `unattributedSubtreeRows` **beside — never instead of —** `unpricedRows`, since a row can be both; the human table gains a second, distinctly worded line, because "we could not see this leg at all" and "we saw this leg but not what it spawned" are different facts. The MCP `amicus_spend` tool inherits it unchanged. And the unknown-spend notice is no longer **sticky**: it guarded on a boolean, so the first unknown leg was announced and every one created afterwards — Stage 2, repairs, debate, chair — was silently swallowed. It now re-announces on a growing count, and says "so far this run" rather than repeating a cumulative number as if it were new.
|
|
146
|
+
- **A leg observed only through cache tokens reports `unknown`, not a falsely free `$0`.** The observation gate accepted `cacheRead`/`cacheWrite`/`reasoning`, but the estimate prices `input`/`output` only — so a leg observed in neither passed the gate and resolved to `estimated $0.0000`, the same authoritative false zero 4.4.0 exists to eliminate, in the one corner its predicate did not cover. The v4.2 free-local `$0` tier is untouched: a real local seat reports genuine token counts and still resolves to `estimated ~$0.0000`.
|
|
147
|
+
- **A leg that both fell back to another model and left an unattributable subtree no longer reports `costExact: true`.** Folding a leg's attempts together returned a bare `{tokens, cost}` and dropped the subtree flag, so `run.json` claimed a complete total for a number that was a floor. The fold now preserves every key: the flag ORs across attempts (a gap admitted once cannot be erased by a later clean attempt) and a measured subtree sums rather than last-wins. Separately, the spend reader promised to skip corrupt ledger lines but let a valid-JSON *scalar* through as a row, inflating both `runs` and `unpricedRows`.
|
|
148
|
+
- **A failed leg no longer renders as complete in the Council Workspace.** A seat that errored or timed out could show a green check, because `timed-out` was missing from the terminal-state lists the mirror consults. Alongside it: the leg-role guard is now symmetric, a leg that throws writes a terminal progress record instead of leaving its last live one as the final word, and swallowed read failures are logged rather than discarded.
|
|
149
|
+
- **A permission failure is no longer reported as "not written yet".** An unreadable run artifact was indistinguishable from one the run had not produced yet, which had been producing a **silent chairless fold reporting `{ok: true}`** — the fold said it succeeded and carried no verdict. Three dead fallbacks and an unwired `legsTotal`/`legsComplete` pair were deleted in the same pass: a documented field that is never populated is worse than no field.
|
|
150
|
+
- **A Stage-1 wave that dies before its legs start no longer leaves the run looking healthy.** Such a wave wrote no `wave.json` at all, so nothing downstream could tell "the wave failed" from "the wave has not reported yet"; it now degrades the run loudly. A shared-server acquisition that fails likewise degrades loudly instead of silently, and its **success** is recorded too — previously only the failure was.
|
|
151
|
+
- **The `haiku` alias was never broken.** It hard-404'd 3 of 3 times across two paid councils — as chair (twice, including the fallback retry) and as a bench seat, ~2 s and zero tokens each — and the standing diagnosis was a rotten model id. It is not: the cause is an `ANTHROPIC_BASE_URL` without its `/v1` suffix, an **environment** misconfiguration. Documented as such, because the prescribed "fix or remove the alias" would have deleted a working route. The more useful half of the lesson stands unchanged from 4.4.0: a dead alias does not stop a council, it shrinks one.
|
|
152
|
+
- **Renderer and markdown robustness.** The workspace's markdown renderer no longer re-slices its input on every inline token (identical output on 219,543 verified inputs — exhaustive to length 6 plus 200,000 fuzz cases — removing a dependence on a V8 string-representation detail nothing stated or tested); heading text is trimmed rather than baking trailing blanks into a text node; and an unreachable heading-level clamp is gone, pinned so a future widening fails loudly instead of silently emitting an `<h7>`.
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- **A review that honestly finds nothing is now a valid review.** An empty finding set was a hard error, which structurally pressured a model to invent a finding — directly contradicting the anti-sycophancy clause shipped in every Stage-1 briefing, and `costgate01`'s `grok` did exactly that, reaching a human's decision. A **present-but-empty** `findings` array with a non-empty `overall` now validates. The lines that already existed are preserved: a broken emit keeps its own codes, an empty set with a blank or missing `overall` is still an error, and a *missing* `findings` key is still an error — only an array that is present and empty means "I read it and found nothing". The briefing now says what the validator enforces, which also makes the repair prompt's "emit an empty findings array and say so" branch describe an answer that can actually pass rather than a trap costing two paid legs. Downstream, an all-clean bench degrades gracefully: Stage 2 still runs (peer ranking, and therefore street-cred, is unaffected by an empty findings pool), and the judge bundle and chair packet **state** the empty index instead of rendering a heading over nothing under an order to adjudicate ids that do not exist — an instruction a judge obeying it answers by inventing an id, which buys up to two paid repair solos per judge. The debate stage is genuinely skipped and already records its reason. **This changes what a council means when every seat comes back clean.**
|
|
157
|
+
- **A leg that exceeds the tool-settle grace ceiling now has its OpenCode session aborted.** 4.4.0 bounded the wait and completed the leg anyway; it left the underlying session running and **billing** for output nobody would read. The leg's completion and partial output are unchanged — only the session is stopped, after the child-session walk so subtree attribution survives, and before the server closes, since on a shared server (i.e. every council run) the server is not closed here at all — which is precisely the pathological case. Whether the abort landed rides the result, the terminal `progress.json` and the leg's `metadata.json` as `toolSettleAborted`, and it is recorded as `false` when the abort was attempted and failed rather than omitted: "we tried to stop it and could not; it may still be billing" is the useful half of that signal. A failed or hung abort can never alter a leg that already succeeded.
|
|
158
|
+
- **A run under a `--max-cost` ceiling now exits `2` when its own total is inexact.** A fully-unpriced council could never trip the ceiling, so a ceiling silently bounded nothing while the run exited `0` — an unqualified success for a number the run itself was reporting as a floor. When a ceiling is set *and* the total is inexact, the run now exits `2`, through the same degraded path a budget-refused wave already uses; a signal and a real error are never re-labelled. **The ceiling still never blocks a run** and still trips on **known** spend only — a fully-unpriced bench under a $0.01 ceiling runs every stage to completion. The docs now say "`--max-cost` bounds **known** spend", which is what it has always done. Anything gating on `council run`'s exit status should expect `2` where it previously saw `0` on an inexact run.
|
|
159
|
+
- **`amicus watch <id> --ui` now validates the run id.** A malformed id skipped validation entirely and surfaced whatever the run lookup produced — a vaguer error than the identical typo gets on the terminal path. It now **exits `1` with the validator's message instead of launching the workspace**. Bare `amicus watch --ui` still opens the project run-list landing; the check applies only when an id is actually supplied.
|
|
160
|
+
- **`npm i -g amicus` now installs the documentation.** The package's `files[]` excluded `docs/` entirely, so every word of documentation was unreachable from an install — and the moment a user most needs troubleshooting text already on disk is an opaque `Not Found` with zero tokens, which is exactly when they have no reason to trust a browser tab instead. The 15 top-level `docs/*.md` pages now ship (**+15 files, 285 → 300; +93 KB packed, +8.8%**). Images and the plan/spec archive deliberately do not — ~425 KB with no offline value. A handful of roadmap references point at repo working files that still do not ship; they are now labelled as such rather than reading as broken paths.
|
|
161
|
+
- **The fold's `Cost:` line stops saying the same thing twice.** It appended the source name on top of a glyph that already encoded the same fact, printing `~$0.0100 (estimated)` and `? (unknown)`. Those two words are gone — `~` already means inexact and a bare `?` already means unknown. Two sources keep their word because the glyph vocabulary cannot express them: **`reported`**, since a plain `$0.4321` is also what an unrecognised source renders as, so the absence of a glyph cannot mean "exact"; and **`mixed`**, since `~` says *inexact* without saying *which kind* — collapsing `mixed` to a bare `~$…` makes it indistinguishable from `estimated`, and the two are not the same claim. `mixed` asserts that part of the number is genuinely measured.
|
|
162
|
+
|
|
163
|
+
### Security
|
|
164
|
+
|
|
165
|
+
- **The Council Workspace's markdown renderer has finally been reviewed by a bench.** The component that turns **another model's prose into DOM** was in none of the five gate councils' review sets and shipped in 4.4.0 uncertified; one chair called that out explicitly and rated it blocking, while another cleared the same file by reading its *consumers* rather than the file. A paid council was run against the file itself. **It found nothing exploitable** — no DOM injection (`textContent`/`createTextNode` only, fixed tag names, no HTML parsing of model-controlled content) and no prototype pollution — and the findings it did raise are architectural, fixed above under **Fixed**. The certification is recorded here rather than in a working note because the disagreement it settles was a public one. Its adversarial coverage is now pinned: prototype-pollution inertness, `javascript:`/`data:` URL inertness, malformed and unmatched inline backticks, the resource budget at exactly the artifact-size cap under both pathological shapes, and a widened attribute-sink guard — the previous banned-token scan covered four HTML-string APIs and would **not** have caught `setAttribute('href', userText)`, with a negative control proving the guard is not vacuous. Two rendering-fidelity findings were surfaced and deliberately not fixed; neither is a security property.
|
|
166
|
+
- **The Council Workspace's read-only posture is now enforced rather than asserted.** That the workspace never writes into a run directory was checked nowhere. It now is, by a guard that **parses** rather than greps: these files are dense with prose *about* writes, and a text scan that goes red for a comment gets weakened rather than fixed, so the guard builds an AST (comments are structurally absent from it) and matches string literals by exact equality. It pins that no write API appears in the workspace source, that the registered IPC channel set is **exactly** the seven known channels — so a new channel cannot be added without being classified — and that the one verb which legitimately writes, Abort, **delegates** to the engine's own abort path rather than writing itself. A positive control scans the engine's own writer and requires it to come back dirty, so a broken scan cannot pass by finding nothing. The Workspace's CSP likewise gains a real regression guard: "no violations" is also what a *loosened* policy produces, so the check now appends an inline `<script>` and requires the refusal to appear.
|
|
167
|
+
|
|
6
168
|
## [4.4.0] - 2026-07-26
|
|
7
169
|
|
|
8
170
|
### Added
|
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ One install delivers six things that work together:
|
|
|
52
52
|
- **The `amicus` CLI (with an `am` alias) and an MCP server.** The engine underneath both skills: launches sessions, shares context, runs parallel waves, and exposes the same surface to Claude as MCP tools.
|
|
53
53
|
- **A self-updating model catalog.** Aliases and validation resolve against a live catalog fetched from provider APIs (cached locally), so model names stay current without a hard-coded table.
|
|
54
54
|
- **Observability.** `amicus watch <id>` renders any live or finished run (fan-out or council) from any terminal; `--follow` streams milestones as they happen; `--on-complete` fires a hook when a run lands; `--retry-failed` plus opt-in cheaper-model fallbacks recover dead legs without relaunching the whole wave; `amicus spend` answers "what did this cost, and where" with per-run attribution.
|
|
55
|
-
- **Council Workspace.** `amicus watch <runId> --ui`: a window that shows a council *thinking* — live seats, the anonymized judge packet, the adjudication matrix, dissent drill-in, chair verdict, and cost-by-seat — for both live and historical runs.
|
|
55
|
+
- **Council Workspace.** `amicus watch <runId> --ui`: a window that shows a council *thinking* — live seats, the anonymized judge packet, the adjudication matrix, dissent drill-in, chair verdict, and cost-by-seat — for both live and historical runs. It also **auto-opens** on an MCP-invoked council run from Claude Code (local), so you no longer have to remember the flag (see [The Council](#the-council)).
|
|
56
56
|
|
|
57
57
|
Claude is the orchestrator. The council and chat skills run *on top of* the engine; you talk to Claude, and Claude drives Amicus.
|
|
58
58
|
|
|
@@ -267,6 +267,17 @@ The `amicus_fanout` MCP tool takes the same `council` parameter, and the `second
|
|
|
267
267
|
|
|
268
268
|
**Council presets.** Save your own named member lists with `amicus council save <name> --models a,b,c` (≥2 resolvable aliases or `provider/model` IDs), then run them with `--council <name>` anywhere a council runs. `amicus council list` shows saved presets plus three built-in benches that work with no setup at all — `free` (the same zero-cost dynamic pick described above, used when you haven't seeded `councils.free`), `budget` (cheap workhorses, one per vendor family), and `frontier` (premium flagships, one per vendor family). `amicus council show <name>` resolves any of them (saved or built-in) and reports which members are currently usable. A saved council always shadows a built-in of the same name — exactly how the wizard's `councils.free` seeding already worked.
|
|
269
269
|
|
|
270
|
+
**Policy packs (v4.5).** A council preset only saves the bench. A **pack** saves the whole run — bench, chair, critic/lenses, cost/timeout options, and a briefing template — as one named, shareable JSON file:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
amicus pack save review-bench --kind council --bench gemini,deepseek,gpt --chair opus --timeout 20 --max-cost 2
|
|
274
|
+
amicus council run --pack review-bench --prompt-file plan.md --json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Any flag you also type on that second line overrides just that value — a pack only fills in what you didn't say explicitly, and it's recorded on the run either way. Packs work the same way on `fanout`/`start` and on the `amicus_fanout`/`amicus_start`/`amicus_council_run` MCP tools. `amicus pack list`/`show`/`rm` manage them, and `--from-run <id>` builds one from a run you already liked instead of typing flags at all. Full reference: [docs/usage.md § Policy packs](./docs/usage.md#policy-packs).
|
|
278
|
+
|
|
279
|
+
**Briefing templates (v4.5).** `--template <name> --artifact <file>` (plus repeatable `--var k=v`) renders a `{{prompt}}`/`{{artifact}}`-style Markdown template before it's sent, on `start`/`fanout`/`council run` alike — templates live in `~/.config/amicus/templates/`, and a pack's `briefing.template` is how one reaches an MCP-invoked run (MCP has no template param of its own). `amicus template list|show` manage them; v4.5 ships one built-in, `review`. Full reference: [docs/usage.md § Briefing templates](./docs/usage.md#briefing-templates).
|
|
280
|
+
|
|
270
281
|
---
|
|
271
282
|
|
|
272
283
|
## The parallel window
|
|
@@ -323,6 +334,8 @@ amicus update
|
|
|
323
334
|
| `amicus provider` | Add/list/test/remove local, OpenAI-compatible providers (LM Studio, Ollama, vLLM) — configured with `--preset` or `--url`, at **$0** marginal cost (`--json` on every subcommand). |
|
|
324
335
|
| `amicus council` | Council math: `tally <input.json>` (deterministic tiers + ledger append), `stats` (reviewer reliability), `report <verdict.json> [--md\|--html]`, `validate <file>` (findings-block check, exit 0/2/1), `verdict <tally.json> [--decisions <d.json>] [-o <out.json>]` (build + write verdict.json). Presets: `save <name> --models a,b,c`, `list [--json]`, `show <name> [--json]` — see [The Council](#the-council) for the built-in `free`/`budget`/`frontier` benches. |
|
|
325
336
|
| `amicus council run` | The headless council engine: Stage-1 reviews → anonymized cross-review → deterministic tally → non-Claude chair verdict, in one command with no Claude runtime. Add `--debate` for a Stage-2.5 rebuttal round (raisers defend/amend/withdraw, disputing judges re-vote) and `--claude-review <file>` to enter Claude's own review as judged review N+1. Writes a run directory with `verdict.json` (including `overallVerdict`) and `report.html` — see [docs/council.md](./docs/council.md#amicus-council-run). |
|
|
337
|
+
| `amicus pack` | Save a full run configuration — bench, chair/critic/lenses, options, briefing template — and invoke it by name: `save <name> --kind council\|fanout\|solo [flags]` (or `--from-run <id>`), `list`, `show <name>`, `rm <name>`. `--pack <name>` on `start`/`fanout`/`council run` loads one; explicit flags always override it. See [docs/usage.md § Policy packs](./docs/usage.md#policy-packs). |
|
|
338
|
+
| `amicus template` | `list`/`show <name>` a briefing template. `--template <name> [--artifact <file>] [--var k=v]` on `start`/`fanout`/`council run` renders one before the briefing is sent. See [docs/usage.md § Briefing templates](./docs/usage.md#briefing-templates). |
|
|
326
339
|
| `amicus abort` | Abort a running session (or `--all`). |
|
|
327
340
|
| `amicus setup` | Configure default model, API keys, and aliases. |
|
|
328
341
|
| `amicus update` | Update to the latest version. |
|
|
@@ -351,7 +364,7 @@ $ amicus status demo123 --json
|
|
|
351
364
|
"taskId": "demo123",
|
|
352
365
|
"status": "complete",
|
|
353
366
|
"elapsed": "5m 0s",
|
|
354
|
-
"version": "4.
|
|
367
|
+
"version": "4.5.0",
|
|
355
368
|
"model": "google/gemini-2.5-flash",
|
|
356
369
|
"phase": "terminal"
|
|
357
370
|
}
|
|
@@ -462,6 +475,8 @@ Run `amicus doctor` first — it checks keys, catalog, OpenCode binary, Electron
|
|
|
462
475
|
| Install fails partway, or `amicus doctor` reports the OpenCode binary "not found" | A **transient** error during the OpenCode engine's own postinstall (a spawn `ENOENT`, or an antivirus file-lock while it lays down its 11 per-platform binaries) can roll back the whole atomic install — retrying usually succeeds | Just re-run `npm install -g amicus`. If it still fails, clear the cache first: `npm cache clean --force && npm install -g amicus`. |
|
|
463
476
|
| `401` / auth error | No usable key for the model's vendor — bare `provider/model` ids fall back to `OPENROUTER_API_KEY` automatically, so this means neither the direct key nor an OpenRouter key is configured (or `--gateway direct`/`openrouter` forced a gateway whose key is missing) | Run `amicus setup`, or `amicus key <provider> <key>` to add the missing key; see [Routing](#routing). |
|
|
464
477
|
| `402` / "Payment Required" on first council review / `start` / `fanout` call | Your OpenRouter key is real but has no credit. Key save (`amicus key openrouter <key>` or the setup wizard's key step) only checks that the key **authenticates** — it doesn't check balance, so a zero-credit key saves cleanly and only fails later, on the first real model call. (The `amicus council` subcommand itself is deterministic math and never calls a model.) | Add credit at [openrouter.ai/credits](https://openrouter.ai/credits), **or** switch to a zero-cost council: `amicus setup` → option 2 (Free OpenRouter council) builds one from live `:free`-suffixed models and saves it as `councils.free` — then run `amicus fanout --council free …`. See "Free council (zero-cost)" under [The Council](#the-council) above. |
|
|
478
|
+
| Every direct `anthropic/…` model (`haiku`, `sonnet`, `opus`, `claude`) errors `Not Found` in ~2 s at zero tokens, but the same model works via `openrouter/anthropic/…` | An inherited `ANTHROPIC_BASE_URL` missing its `/v1` path segment. The engine appends only `/messages`, so requests hit `https://api.anthropic.com/messages` → HTTP 404 with an empty body → the bare status text. A shell spawned by Claude Code sets the `/v1`-less form for you. The model id, alias, and key are all fine. | `export ANTHROPIC_BASE_URL=https://api.anthropic.com/v1`, or unset it entirely, or pass `--gateway openrouter`. In a council a dead seat **degrades the run instead of failing it** — smoke-test each seat with one throwaway `amicus start` before paying for a council. See [docs/troubleshooting.md](./docs/troubleshooting.md#every-direct-anthropic-model-fails-with-not-found). |
|
|
479
|
+
| `Model 'X' is unverified against the direct catalog; attempting anyway` for a model that plainly exists | Not a claim the model is wrong — amicus **couldn't check**. That vendor's direct catalog fetch failed (usually a stale or truncated stored key), leaving its namespace empty, and an empty namespace never blocks a launch. The engine may still run the model from its own credential store, so a working model warns forever. | `amicus models --refresh` and watch for a provider that stays empty; re-save the good key with `amicus key <provider> <apikey>`. See [docs/troubleshooting.md](./docs/troubleshooting.md#model-x-is-unverified-against-the-direct-catalog-attempting-anyway). |
|
|
465
480
|
| Session not found | No session matches the given ID | Run `amicus list`, or omit `--session-id` to use the most recent. |
|
|
466
481
|
| No conversation history found | Project-path encoding | Check `~/.claude/projects/`; `/` and `_` in the project path are encoded as `-` in the directory name. |
|
|
467
482
|
| Headless run never finishes | Task is bigger than the default timeout | Raise it: `--timeout 30`. |
|
package/bin/amicus.js
CHANGED
|
@@ -152,6 +152,16 @@ async function main() {
|
|
|
152
152
|
case 'update':
|
|
153
153
|
await handleUpdate();
|
|
154
154
|
break;
|
|
155
|
+
case 'template': {
|
|
156
|
+
const { handleTemplate } = require('../src/cli-handlers-template');
|
|
157
|
+
exitCode = await handleTemplate(args);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'pack': {
|
|
161
|
+
const { handlePack } = require('../src/cli-handlers-pack');
|
|
162
|
+
exitCode = await handlePack(args);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
155
165
|
default: {
|
|
156
166
|
console.error(`Unknown command: ${command}`);
|
|
157
167
|
// suggestCommand honors a cap-3 contract (up to 3 candidates, closest
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Distribution channels
|
|
2
|
+
|
|
3
|
+
Amicus ships through three channels. This doc is the runbook for each — what's
|
|
4
|
+
live today, what the submission/publish steps are, and what to check before
|
|
5
|
+
pulling the trigger on a release-facing action.
|
|
6
|
+
|
|
7
|
+
## 1. npm (existing)
|
|
8
|
+
|
|
9
|
+
**Status: live.** Tag `v*` on `main` → `.github/workflows/publish.yml` → npm
|
|
10
|
+
**Trusted Publishing** (GitHub OIDC, no long-lived npm token in CI) →
|
|
11
|
+
`npm publish --access public --provenance`. The workflow also cuts a GitHub
|
|
12
|
+
Release and asks Claude to draft categorized release notes from the commit
|
|
13
|
+
log + diff since the previous tag.
|
|
14
|
+
|
|
15
|
+
Postinstall (`scripts/postinstall.js`) registers the MCP server in Claude
|
|
16
|
+
Code / Claude Desktop / Cowork and copies both skills (`sidecar`,
|
|
17
|
+
`second-opinion`) into `~/.claude/skills/`. It does **not** copy
|
|
18
|
+
`commands/council.md` — that only ships via the plugin channel below. This
|
|
19
|
+
is a known, accepted gap for npm/install-script users (slash commands are
|
|
20
|
+
plugin-channel-only by design; see the npm-vs-plugin note at the top of the
|
|
21
|
+
CHANGELOG's Unreleased section).
|
|
22
|
+
|
|
23
|
+
## 2. Claude Code community marketplace (claude-community)
|
|
24
|
+
|
|
25
|
+
**Status: submitted 2026-07-01 — awaiting Anthropic review.** (Update this
|
|
26
|
+
line as the process advances: `submitted <date> / approved <date> / listed <date>`.
|
|
27
|
+
Note: the submission predates the Phase-9 polish on `main` — reviewers pulling
|
|
28
|
+
the repo see the current surface, including `commands/` and a clean
|
|
29
|
+
`claude plugin validate . --strict`.)
|
|
30
|
+
|
|
31
|
+
Two Anthropic-run marketplaces exist:
|
|
32
|
+
- **`claude-plugins-official`** — curated by Anthropic, no application process.
|
|
33
|
+
- **`claude-community`** — third-party plugins, submitted for review. This is
|
|
34
|
+
the one amicus targets.
|
|
35
|
+
|
|
36
|
+
Approved plugins are pinned to a commit SHA in
|
|
37
|
+
`anthropics/claude-plugins-community/.claude-plugin/marketplace.json`. CI in
|
|
38
|
+
that repo auto-bumps the pin as we push to `main`, and the public catalog
|
|
39
|
+
syncs from it nightly (a delay after any push is normal, not a bug). **PRs
|
|
40
|
+
opened directly against `anthropics/claude-plugins-community` are closed
|
|
41
|
+
automatically** — the only way in is the submission form below.
|
|
42
|
+
|
|
43
|
+
Because the pin auto-bumps on every push to `main`, every push after listing
|
|
44
|
+
reaches marketplace users on their next sync. `plugin.json` already pins an
|
|
45
|
+
explicit `version` synced to `package.json` (enforced by
|
|
46
|
+
`tests/plugin-manifest.test.js`), so this is the existing safe behavior:
|
|
47
|
+
users only see a version bump when we bump it, not on every commit. Treat
|
|
48
|
+
`main` as release-quality once listed.
|
|
49
|
+
|
|
50
|
+
### Preflight (run before every submission or major post-listing update)
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
claude plugin validate . --strict
|
|
54
|
+
claude --plugin-dir . # smoke: /amicus:council, /amicus:sidecar, /amicus:second-opinion, MCP tools
|
|
55
|
+
npm test
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `claude plugin validate . --strict` is the same structural check the
|
|
59
|
+
review pipeline runs; `--strict` promotes unrecognized-field warnings to
|
|
60
|
+
errors so nothing slips through that CI would later flag. It needs the
|
|
61
|
+
Claude Code CLI installed locally — it is **not** wired into this repo's
|
|
62
|
+
CI (runners have no `claude` auth), so `tests/plugin-manifest.test.js` is
|
|
63
|
+
the CI-side proxy for manifest completeness.
|
|
64
|
+
- `claude --plugin-dir .` loads the plugin from the working tree so you can
|
|
65
|
+
manually confirm `/amicus:council`, `/amicus:sidecar`, and
|
|
66
|
+
`/amicus:second-opinion` all appear in the command picker exactly once,
|
|
67
|
+
and that the `amicus` MCP server connects (tools list populates).
|
|
68
|
+
- `npm test` must be green, specifically `tests/plugin-manifest.test.js`
|
|
69
|
+
and `tests/plugin-commands.test.js`.
|
|
70
|
+
|
|
71
|
+
**Known current-tree preflight result (checked 2026-07-02, `p9/distribution`
|
|
72
|
+
at a1bea3c):** `claude plugin validate . --strict` passes clean, exit 0.
|
|
73
|
+
(History: `--strict` previously flagged an unknown `plugin.json → bugs`
|
|
74
|
+
field; that field was removed in commit `4207485`, so the warning is gone.)
|
|
75
|
+
|
|
76
|
+
### Submit
|
|
77
|
+
|
|
78
|
+
- **Individual-author route:** https://platform.claude.com/plugins/submit
|
|
79
|
+
(Console form). This is the route for us — Christian has no Team/Enterprise
|
|
80
|
+
org, so the directory-management admin route below doesn't apply.
|
|
81
|
+
- **Team/Enterprise route:** https://claude.ai/admin-settings/directory/submissions/plugins/new
|
|
82
|
+
(requires Team/Enterprise org + directory management permissions — not
|
|
83
|
+
applicable here, listed for completeness).
|
|
84
|
+
- **Metadata to enter in the form:**
|
|
85
|
+
- Repository: `https://github.com/BourbonDog/amicus` (public, MIT)
|
|
86
|
+
- Plugin name: `amicus`
|
|
87
|
+
- Description: pulled from `.claude-plugin/plugin.json` → `description`
|
|
88
|
+
- Contact: `sendtowags@outlook.com`
|
|
89
|
+
- **Timing:** submit only after Task 9a (`commands/council.md` + the sidecar
|
|
90
|
+
argument surface) has merged to `main`, so the SHA the reviewer evaluates
|
|
91
|
+
and the SHA that eventually gets pinned both include the slash commands.
|
|
92
|
+
Submitting before 9a merges means the reviewed surface is incomplete.
|
|
93
|
+
- Never open a PR against `anthropics/claude-plugins-community` — it's a
|
|
94
|
+
read-only mirror for the public catalog; PRs there are auto-closed.
|
|
95
|
+
|
|
96
|
+
### What review checks (and what it might ask about)
|
|
97
|
+
|
|
98
|
+
The pipeline runs `claude plugin validate` plus automated safety screening.
|
|
99
|
+
There's no published SLA for turnaround — **budget this as unscheduled** and
|
|
100
|
+
don't put any downstream work on the critical path of approval.
|
|
101
|
+
|
|
102
|
+
Anticipated reviewer question: the npm package runs a postinstall
|
|
103
|
+
(`scripts/postinstall.js`) and the repo ships `install.sh` / `install.ps1`.
|
|
104
|
+
The **plugin channel itself never triggers postinstall** —
|
|
105
|
+
`.claude-plugin/plugin.json`'s `mcpServers.amicus.env` sets
|
|
106
|
+
`AMICUS_SKIP_POSTINSTALL=1`, so a plugin install only ever runs
|
|
107
|
+
`npx -y amicus@latest mcp` with that guard set, not the interactive
|
|
108
|
+
setup/registration flow. This paragraph is the answer if a reviewer asks.
|
|
109
|
+
|
|
110
|
+
### After approval
|
|
111
|
+
|
|
112
|
+
1. Search `"amicus"` in
|
|
113
|
+
https://github.com/anthropics/claude-plugins-community/blob/main/.claude-plugin/marketplace.json
|
|
114
|
+
to confirm the listing synced (nightly delay after approval is normal).
|
|
115
|
+
2. End-to-end verify:
|
|
116
|
+
```bash
|
|
117
|
+
claude plugin marketplace add anthropics/claude-plugins-community
|
|
118
|
+
claude plugin install amicus@claude-community
|
|
119
|
+
```
|
|
120
|
+
3. Update the Status line at the top of this section with the
|
|
121
|
+
submitted/approved/listed dates.
|
|
122
|
+
4. Optionally add an "Install as a Claude Code plugin" section to
|
|
123
|
+
`README.md` referencing `@claude-community` (not done as part of this
|
|
124
|
+
task — premature before approval, since the listing doesn't exist yet).
|
|
125
|
+
|
|
126
|
+
*(Everything in this subsection past "search 'amicus' in
|
|
127
|
+
`marketplace.json`" is unverified as of 2026-07-02 in the sense that we
|
|
128
|
+
haven't been through it yet — it's the documented expected flow per
|
|
129
|
+
Anthropic's published docs, not something we've personally exercised
|
|
130
|
+
end-to-end against the real submission.)*
|
|
131
|
+
|
|
132
|
+
## 3. MCP Registry
|
|
133
|
+
|
|
134
|
+
**Status: wired, not yet published (Phase 9c).** `server.json` (repo root)
|
|
135
|
+
and the `mcpName` field in `package.json` now exist, and
|
|
136
|
+
`.github/workflows/publish.yml` publishes to the MCP Registry
|
|
137
|
+
(`registry.modelcontextprotocol.io`) as the last three steps before the GitHub
|
|
138
|
+
Release, on every `v*` tag push. This has not fired yet — the first tag
|
|
139
|
+
push after this merge is the first real publish attempt.
|
|
140
|
+
|
|
141
|
+
**Namespace:** `io.github.BourbonDog/amicus` (case-sensitive — the registry
|
|
142
|
+
grants `io.github.<Login>/*` using the exact-case GitHub login/repository
|
|
143
|
+
owner). Confirmed unclaimed via
|
|
144
|
+
`https://registry.modelcontextprotocol.io/v0/servers?search=amicus`
|
|
145
|
+
(0 results, checked 2026-07-02).
|
|
146
|
+
|
|
147
|
+
**Flow:** tag↔`package.json` version lockstep is verified first (fails fast
|
|
148
|
+
with `::error::` on a mis-tag) → npm publish (existing, OIDC), itself guarded
|
|
149
|
+
by a version-exists check so a re-run does not re-attempt a version already
|
|
150
|
+
live on npm → `mcp-publisher` binary installed → `server.json` version
|
|
151
|
+
synced from the tag via `jq` (belt-and-braces; the in-repo
|
|
152
|
+
`server.json`/`package.json`/`packages[0]` versions are also kept in
|
|
153
|
+
lockstep by hand at release time and enforced by
|
|
154
|
+
`tests/scripts/package-manifest.test.js`) → MCP Registry publish, itself
|
|
155
|
+
pre-checked against the registry API so a re-run does not double-publish →
|
|
156
|
+
`mcp-publisher login github-oidc` (no secret needed, uses the same
|
|
157
|
+
`id-token: write` OIDC permission as the npm Trusted Publishing step),
|
|
158
|
+
retried up to 5 times on transient OIDC token-exchange failures → `mcp-publisher
|
|
159
|
+
publish`, retried up to 5 times (npm propagation lag) before hard-failing the
|
|
160
|
+
job → GitHub Release creation, guarded by an existence check so a re-run
|
|
161
|
+
does not fail on a release that already exists. The registry steps run
|
|
162
|
+
strictly after `npm publish` because npm-side ownership validation reads
|
|
163
|
+
`mcpName` from the *published* `package.json`.
|
|
164
|
+
|
|
165
|
+
**Release-order dependency (historical — satisfied before the first registry
|
|
166
|
+
publish):** the first post-merge `v*` tag had to wait for the Phase 4
|
|
167
|
+
tool-surface de-bloat, because the server still registered 13 deprecated
|
|
168
|
+
`sidecar_*` aliases alongside the real tools and the first registry publish
|
|
169
|
+
snapshots whatever surface exists at that time. Both have long since landed.
|
|
170
|
+
The rule that outlives the episode: **a registry publish snapshots the tool
|
|
171
|
+
surface, so land any tool-surface change before you cut the tag.** For the
|
|
172
|
+
current tool count, see the MCP table in [README.md](../README.md) — it is the
|
|
173
|
+
single source of truth and moves with each release (v4.0 added
|
|
174
|
+
`amicus_council_run`).
|
|
175
|
+
|
|
176
|
+
**Registry preview caveat:** the MCP Registry is still in preview per its
|
|
177
|
+
own docs (breaking changes/data resets possible before general
|
|
178
|
+
availability). The publish steps are additive to the existing npm/GitHub
|
|
179
|
+
Release flow and do not touch it; a registry publish failure after 5 retries
|
|
180
|
+
does fail the workflow job (hard `exit 1`), which means the 'Create GitHub
|
|
181
|
+
Release' step does not run on that path. If that trade-off proves unwanted
|
|
182
|
+
in practice, add `continue-on-error: true` to the 'Publish to MCP Registry'
|
|
183
|
+
step.
|
|
184
|
+
|
|
185
|
+
**First-publish de-risk:** before relying on CI for the first real publish,
|
|
186
|
+
run once locally: download `mcp-publisher` (Windows: the tarball flow from
|
|
187
|
+
the quickstart docs), `mcp-publisher login github` (device-flow auth as
|
|
188
|
+
BourbonDog), then `mcp-publisher publish` — to fail fast on any
|
|
189
|
+
namespace/validation error outside of CI. If publish returns "You do not
|
|
190
|
+
have permission…", the error message states the granted pattern; align
|
|
191
|
+
`server.json`'s `name` casing to it exactly.
|
|
192
|
+
|
|
193
|
+
**If the registry publish fails in CI (Phase 11 hardening):** re-running the
|
|
194
|
+
workflow is now the primary recovery path. Every publish-ish step in
|
|
195
|
+
`publish.yml` is idempotency-guarded, so a re-run skips whatever already
|
|
196
|
+
succeeded and only retries the step that actually failed:
|
|
197
|
+
- **npm publish** checks `npm view amicus@<version>` first and skips with a
|
|
198
|
+
`::notice::` if that version is already on the registry (instead of
|
|
199
|
+
hitting `EPUBLISHCONFLICT`).
|
|
200
|
+
- **MCP Registry publish** pre-checks
|
|
201
|
+
`registry.modelcontextprotocol.io/v0/servers/io.github.BourbonDog%2Famicus/versions/<version>`
|
|
202
|
+
(HTTP 200 = already published, 404 = not yet) and skips with a
|
|
203
|
+
`::notice::` if present, before attempting login or publish.
|
|
204
|
+
- **`mcp-publisher login github-oidc`** now retries up to 5 times (20s
|
|
205
|
+
apart) on transient OIDC token-exchange failures, same pattern as the
|
|
206
|
+
publish retry.
|
|
207
|
+
- **GitHub Release creation** checks `gh release view <tag>` first and skips
|
|
208
|
+
with a `::notice::` if the release already exists.
|
|
209
|
+
|
|
210
|
+
So: fix whatever caused the failure (registry outage, OIDC hiccup, etc.),
|
|
211
|
+
then re-run the failed job from the Actions tab (or `gh run rerun
|
|
212
|
+
--failed`). Do not delete and re-push the tag — the existing job re-run is
|
|
213
|
+
sufficient, and steps that already succeeded (npm publish, an earlier
|
|
214
|
+
registry publish, an existing release) are detected and skipped rather than
|
|
215
|
+
re-attempted or double-published.
|
|
216
|
+
|
|
217
|
+
**Caveat — content-level 422s are NOT re-run-recoverable.** A workflow
|
|
218
|
+
re-run checks out the tag, so a `server.json` validation error (the registry
|
|
219
|
+
returns HTTP 422 naming the failing field) reproduces identically on re-run.
|
|
220
|
+
Fix `server.json` on main and recover via the manual path below, or let the
|
|
221
|
+
fix ride the next tag. Known registry constraint (learned live): the
|
|
222
|
+
top-level `description` is capped at **100 characters** — v1.9.0's first
|
|
223
|
+
publish attempt 422'd on a 199-char description (2026-07-03); now pinned by
|
|
224
|
+
`tests/scripts/package-manifest.test.js`.
|
|
225
|
+
|
|
226
|
+
**Manual recovery (fallback, if re-run is not viable):**
|
|
227
|
+
1. **Registry publish:** run the same local de-risk flow above for real —
|
|
228
|
+
`mcp-publisher login github` (device-flow login as BourbonDog), sync
|
|
229
|
+
`server.json`'s `.version` and `.packages[0].version` to the tag that
|
|
230
|
+
already published to npm, then `mcp-publisher publish`.
|
|
231
|
+
2. **GitHub Release:** cut it by hand:
|
|
232
|
+
`gh release create <tag> --generate-notes --latest`. The "Generate release
|
|
233
|
+
notes with Claude" step is optional polish — skip it or run it manually
|
|
234
|
+
against the API.
|