amicus 4.3.0 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +64 -0
  3. package/README.md +6 -3
  4. package/docs/DISTRIBUTION.md +234 -0
  5. package/docs/ROADMAP.md +200 -0
  6. package/docs/SHIMS.md +62 -0
  7. package/docs/architecture.md +104 -0
  8. package/docs/configuration.md +371 -0
  9. package/docs/council.md +911 -0
  10. package/docs/doc-system.md +92 -0
  11. package/docs/electron-testing.md +471 -0
  12. package/docs/jsdoc-setup.md +75 -0
  13. package/docs/opencode-integration.md +114 -0
  14. package/docs/publishing.md +60 -0
  15. package/docs/schemas.md +55 -0
  16. package/docs/testing.md +589 -0
  17. package/docs/troubleshooting.md +298 -0
  18. package/docs/usage.md +699 -0
  19. package/electron/fold.js +1 -1
  20. package/electron/ipc-workspace.js +283 -0
  21. package/electron/main.js +31 -1
  22. package/electron/preload-workspace.js +40 -0
  23. package/electron/setup-ui-aliases.js +6 -6
  24. package/electron/workspace-shell.js +85 -0
  25. package/electron/workspace-ui/index.html +111 -0
  26. package/electron/workspace-ui/live-model.js +112 -0
  27. package/electron/workspace-ui/md-lite.js +163 -0
  28. package/electron/workspace-ui/workspace-app.js +240 -0
  29. package/electron/workspace-ui/workspace-matrix.js +249 -0
  30. package/electron/workspace-ui/workspace-panels.js +237 -0
  31. package/electron/workspace-ui/workspace-render.js +277 -0
  32. package/electron/workspace-ui/workspace-verbs.js +293 -0
  33. package/electron/workspace-ui/workspace.css +172 -0
  34. package/package.json +8 -3
  35. package/schemas/council-run-live.schema.json +25 -1
  36. package/schemas/council-run.schema.json +34 -0
  37. package/schemas/progress.schema.json +26 -1
  38. package/schemas/spend.schema.json +52 -4
  39. package/skills/second-opinion/MODEL-NOTES.md +53 -5
  40. package/src/cli-handlers-council-run.js +25 -3
  41. package/src/cli-handlers-spend.js +50 -5
  42. package/src/cli-handlers-watch.js +48 -10
  43. package/src/cli.js +4 -2
  44. package/src/council/briefings-debate.js +27 -7
  45. package/src/council/briefings-stage2.js +155 -25
  46. package/src/council/briefings.js +59 -3
  47. package/src/council/findings.js +236 -9
  48. package/src/council/parse-stage2.js +10 -2
  49. package/src/council/report.js +19 -8
  50. package/src/council/run-assemble.js +42 -1
  51. package/src/council/run-budget.js +277 -0
  52. package/src/council/run-chair.js +4 -1
  53. package/src/council/run-debate.js +4 -2
  54. package/src/council/run-finalize.js +102 -0
  55. package/src/council/run-launch.js +73 -7
  56. package/src/council/run-server.js +248 -0
  57. package/src/council/run-stage2.js +118 -0
  58. package/src/council/run-stages.js +148 -113
  59. package/src/council/run-state.js +23 -1
  60. package/src/council/run.js +52 -53
  61. package/src/council/tally.js +10 -0
  62. package/src/headless.js +519 -17
  63. package/src/mcp-council-awareness.js +53 -3
  64. package/src/observe/council-legs.js +240 -0
  65. package/src/observe/live-doc.js +39 -4
  66. package/src/observe/watch-render.js +23 -1
  67. package/src/opencode-client.js +15 -3
  68. package/src/sidecar/child-sessions.js +197 -0
  69. package/src/sidecar/conversation-mirror.js +111 -37
  70. package/src/sidecar/fanout-budget.js +71 -0
  71. package/src/sidecar/fanout-leg-fallback.js +69 -21
  72. package/src/sidecar/fanout-leg.js +29 -1
  73. package/src/sidecar/fanout-signals.js +61 -0
  74. package/src/sidecar/fanout-wave-io.js +75 -0
  75. package/src/sidecar/fanout.js +65 -81
  76. package/src/sidecar/progress-fields.js +26 -4
  77. package/src/sidecar/progress.js +8 -1
  78. package/src/sidecar/session-utils.js +23 -14
  79. package/src/sidecar/tool-part.js +196 -0
  80. package/src/sidecar/workspace-window.js +62 -0
  81. package/src/spend-query.js +33 -6
  82. package/src/utils/env-num.js +42 -0
  83. package/src/utils/lifecycle.js +37 -1
  84. package/src/utils/path-fence.js +120 -0
  85. package/src/utils/pricing.js +114 -9
  86. package/src/utils/server-setup.js +79 -1
  87. package/src/utils/spend-ledger.js +24 -3
  88. package/src/workspace/artifact-guard.js +208 -0
  89. package/src/workspace/blind-mode.js +32 -0
  90. package/src/workspace/fold-format.js +124 -0
  91. package/src/workspace/live-normalize.js +169 -0
  92. package/src/workspace/matrix-model.js +94 -0
  93. package/src/workspace/run-detail.js +229 -0
  94. package/src/workspace/run-scan.js +148 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "4.3.0",
3
+ "version": "4.4.1",
4
4
  "description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
5
5
  "author": {
6
6
  "name": "Christian Wagner"
package/CHANGELOG.md CHANGED
@@ -3,6 +3,70 @@
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.4.1] - 2026-07-27
7
+
8
+ 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.
9
+
10
+ ### Fixed
11
+
12
+ - **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.
13
+ - **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.
14
+ - **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.
15
+ - **`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.
16
+ - **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`.
17
+ - **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`.
18
+ - **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.
19
+ - **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.
20
+ - **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.
21
+ - **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.
22
+ - **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>`.
23
+
24
+ ### Changed
25
+
26
+ - **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.**
27
+ - **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.
28
+ - **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.
29
+ - **`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.
30
+ - **`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.
31
+ - **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.
32
+
33
+ ### Security
34
+
35
+ - **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.
36
+ - **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.
37
+
38
+ ## [4.4.0] - 2026-07-26
39
+
40
+ ### Added
41
+
42
+ - **Council Workspace (GUI)** — third Electron mode `council-workspace`, opened via `amicus watch <councilRunId> --ui` (bare `--ui` opens the project run list). Renders live and historical council runs: stage rail, live per-seat status/tokens/cost (v4.3 data layer, 1.5s/5s poll depending on window focus/visibility), verbatim anonymized Stage-2 packet, tier-colored adjudication matrix with basis counts/thin/override badges, dissent drill-in with prose highlight, chair verdict + street-cred + Stage-4 decisions, cost-by-seat with `--max-cost` gauge. Blind-mode toggle (labels vs models; ON while live, OFF once terminal — a reading aid against anchoring bias, not a security control: the label map is plaintext in `run.json`). Two verbs: confirm-gated Abort (delegates to the engine's own council-aware abort path) and nonced Fold (chair verdict to the launching terminal; no model call). Fully sandboxed first-party page (CSP with no network directive at all, `contextIsolation`, textContent-only rendering of model prose, enforced by a static source scan); read-only against run directories **apart from the Abort verb, which checkpoints the run through the engine's own in-process abort path** — not a direct write from the workspace code itself. `--ui` is interactive-only (`--json` is rejected, not silently ignored).
43
+ - `CdpClient.workspace(port)` e2e factory (`file://` target, port 9225) + a fixture-driven workspace CDP suite.
44
+
45
+ ### Fixed
46
+
47
+ - **Zero-cost reporting no longer lies about spend.** A leg whose captured token totals were *all zero* used to be priced as `0 × catalog price` and labelled `estimated $0.0000` — an authoritative "this seat was free" for work that had genuinely been billed. Diagnosed against four real paid council runs plus OpenCode's own session database: `council-wsgate02` spent **$0.9859 against a `--max-cost` ceiling of $0.75 (131%)** while Amicus believed $0.3720 and never emitted `COST_EXCEEDED`. Four separate defects, all fixed here:
48
+ - `resolveLegCost` now gates the estimate on **observed tokens**, not on the mere existence of a price, so a zero-token leg resolves to `{amount: null, source: 'unknown'}`. The v4.2 free-local-provider `$0` tier is unaffected — a local seat still reports real token counts, so it keeps resolving to `estimated ~$0.0000`.
49
+ - The headless poll loop's fast-path exits (trailing fold marker, SDK `idle`) could break *before* OpenCode stamps `info.tokens`/`info.cost` — measured losing by 155 ms and 29 ms on real paid legs. A bounded, best-effort **post-loop usage re-poll** (≤3 reads, ~1.2 s worst case, usage capture only — never re-mirrors text) now closes that window.
50
+ - `progress.json`'s usage snapshot was only ever written on `receiving` flushes, i.e. always before finalization — 31 of 35 real legs ended with an all-zero snapshot while `metadata.json` held thousands of tokens, and that snapshot is what the live GUI reads. A **terminal `complete` progress record** now carries the settled usage, and the reader prefers `metadata.json` for any terminal leg.
51
+ - `amicus spend` and the spend ledger no longer coerce a null cost into a measured-looking `$0.0000`: a model whose rows are all unpriced renders `?`, and `unpricedRows` is reported on `total`, `byModel`, every `group`, and `wasted`.
52
+ - **A leg is no longer declared `complete` while its OpenCode session is still working and billing.** Measured on a real paid run: `council-wsgate02`'s `wsgate02-s1-3` was declared complete on **166 characters** of reasoning preamble, 129 s before its `task` tool call finished, and its session then billed $0.14279 of further parent spend plus a $0.47105 child session — 166 characters were adjudicated as a finished peer review. Root cause was a shape drift, not a logic slip: the mirror modelled a tool call as an Anthropic-style `tool_use` part cleared by a matching `tool_result`, and **OpenCode emits neither** (36 `tool_use` records and 0 `tool_result` records across 35 recorded legs; 5,129 persisted parts resolve to six type names, none of them `tool_result`). So `pendingToolCalls` never cleared for any real leg, tool names never reached `conversation.jsonl`, and the `Task`-subagent log was permanently empty. Tool-call liveness is now keyed on the SDK's real `state.status` vocabulary (`pending`/`running`/`completed`/`error`; terminal = `completed`|`error`), and the completion gates that lack an explicit done-signal defer while a call is still executing. The wait is **bounded** by `AMICUS_TOOL_SETTLE_GRACE_MS` (default 300 s, `0` disables): on exceeding it the leg **completes anyway** — never fails — carrying `toolSettleTimedOut` on its result, its `metadata.json` and its terminal `progress.json` record, plus an error-level log line. A tool part whose status cannot be observed at all is deliberately *not* treated as live, so an unknown shape can never hang a finished leg.
53
+ - **`costExact: true` no longer claims a total is complete when it is not.** `council-wsgate01` reported `costExact: true` while **$0.0215 short** of OpenCode's ledger. Reconciled leg-by-leg: all 7 legs were `source: 'reported'` with real tokens, and **100% of the gap was one unattributed `explore` child session** ($0.021460, parent `wsgate01-s1-2`) — not rounding, not partial usage, not float drift. The predicate was wrong: `costExact` was computed as `unknownLegs === 0`, which asks "did every leg report tokens" — a statement about each leg's *own* session, not about whether the total is the whole bill. A leg that spawns a subagent now carries `subtreeUnknown` on its usage block, `sumWaveUsage` reports `subtreeUnknownLegs`, and `costExact` requires **both** every leg observed *and* no unattributed subtree. Surfaced on `run.json`, the `Notice:` line, the human summary, the workspace total and the `--max-cost` gauge (which goes indeterminate). Subtree-unknown spend still does not trip the ceiling — fail loud, not closed.
54
+ - **`--max-cost` is now threaded into the council pre-flight estimate.** `src/council/run-launch.js` never passed `maxCost` to the transport, so `src/sidecar/fanout.js` fell back to a `cfg.maxCost` key that does not exist and the `budget.js` soft ceiling was inert for every council run — the post-hoc check in `run.js` was the only ceiling, and it can only refuse *after* the money is spent. Each wave is now measured against the **remaining** allowance (ceiling − known spend − outstanding reservations).
55
+ - **The council pre-flight ceiling is now concurrency-safe.** Stage 1 launches its seat wave and its critic wave together under one `Promise.all`, and each launcher read the remaining allowance *before either wave's legs had been recorded* — so both saw the full, unreduced ceiling and both could pass a gate that only one of them fit under. A read is not a claim. The transport now takes an optional `reserveBudget(estimate)` seam (`src/sidecar/fanout-budget.js`, extracted from `runFanout` §1b) which the council answers with a **synchronous** read-and-claim against the allowance no sibling wave has taken — synchronicity is the guarantee, since the event loop cannot interleave two callers inside it. A fixed quota split was rejected as strictly more refusing than the ceiling requires. **When a wave is refused the run continues with a partial bench** — it never rolls back launched waves and never aborts (fail loud, not closed) — but the refusal is announced on stderr, recorded on `run.json` as `budgetRefusals[]`, and degrades the run's exit code to `2`. Stage 1's existing quorum gate still refuses to call a bench of fewer than two reviews a council.
56
+ - **The best-effort usage-settle re-poll can no longer discard a finished leg.** Its `try/catch` covered only the network read; the snapshot inspection that followed it (`mirrorUsageOnly`, `allAssistantUsagePresent`) ran outside the boundary, so a throw there escaped `runHeadless` and destroyed a leg whose answer had already been captured and paid for — the most expensive possible outcome for a path whose whole job is an optional usage top-up. The boundary now covers the entire loop body; a failure stops settling, keeps every dollar already mirrored, and leaves the completion verdict untouched.
57
+ - **`AMICUS_USAGE_SETTLE_POLLS=0` (and friends) now actually disable the feature.** All four v4.4 settle knobs parsed their environment override as `Number(process.env.X) || DEFAULT`, which silently rewrites an explicit `0` — the documented "off" value — back into the default. `AMICUS_USAGE_SETTLE_POLLS`, `AMICUS_USAGE_SETTLE_INTERVAL_MS`, `AMICUS_USAGE_SETTLE_CALL_TIMEOUT_MS` and `AMICUS_TOOL_SETTLE_GRACE_MS` now go through `src/utils/env-num.js`, which honors an explicit numeric value including `0` and falls back only for unset / blank / non-finite. Older knobs (`AMICUS_POLL_INTERVAL_MS`, `AMICUS_STABLE_*_POLLS`, `AMICUS_TOOL_CALL_STALL_MS`, …) deliberately keep the old form: `0` is not a documented escape hatch for any of them and honoring it would busy-loop a poller or disable a stall guard.
58
+ - **Subagent (child-session) spend is now attributed to the leg that spawned it.** A leg that calls the `task` tool spawns a *child* OpenCode session; OpenCode bills it separately, does **not** roll it into the parent session's cost, and amicus never enumerated it — so it was invisible to every total the product prints. Measured across the four recorded paid runs: **$0.492506** ($0.021460 in `wsgate01`, $0.471046 in `wsgate02`). `wsgate01` was the honest limit case — all 7 legs `source: 'reported'`, `unpricedLegs: 0`, `costExact: true`, and the run still 7.1% short, with 100% of the gap in one `explore` child session. `runHeadless` now walks each leg's child sessions at finalization (bounded, cycle-proof, directory-scoped) and the measured spend rolls into the run total, reported separately as `cost.subtreeCost` / `cost.subtreeSessions`. Replayed against the OpenCode oracle, `wsgate01` reconciles **exactly**. A child's price comes from OpenCode's own billing and is never estimated from a catalog — the SDK's session record carries no model id, so an estimate would be a guess. What the walk cannot account for still reports as `subtreeUnknown`, never as zero; conversely, a subtree that WAS fully walked now clears that flag, which the previous `task`-name proxy could never do. A failed walk with no evidence of a subagent at all flags nothing, so an OpenCode build without the `children` endpoint does not mark every leg of every run inexact.
59
+ - **A Stage-1 repair re-prompt now carries the review it is repairing.** When a review's trailing findings JSON failed validation the engine launched a repair solo — a *fresh* session with no memory of the review turn — and handed it the validation **errors without the review those errors were about**. Three of the five paid councils burned a seat on it: `wsgate02`'s `qwen` and `wsgate04`'s `glm` both refused, twice each ("I don't have a previous review to correct"; "the previous review's content was excluded by the caller… I will not fabricate findings"), so a 4-model bench silently adjudicated on 3 while still paying for the fourth's tokens; `costgate01`'s `grok` complied instead, by **inventing a self-referential finding about its own empty output**, which entered `tally.json`, the street-cred rankings and the chair synthesis as `C1` and reached a human's decision. The prompt now embeds, verbatim and uncapped, the text that actually failed — the original review on the first attempt, the previous repair's output on the second, so the errors and the artifact they describe are always the same thing. When there genuinely is no prior text the prompt **says so** and instructs the model to emit an empty `findings` array rather than leaving it to guess.
60
+
61
+ ### Changed
62
+
63
+ - **Unknown cost fails LOUD, not CLOSED.** A leg whose cost cannot be determined does **not** halt a run and does **not** by itself trip `--max-cost` — the ceiling still trips on known spend only. Instead the uncertainty is made impossible to miss: `run.json`'s `usage` block gains `unknownLegs` + `costExact`, the council run emits a `Notice:` naming the count and stating that real spend is higher, the human summary appends `+ N leg(s) unknown — real spend is at least this much`, `amicus spend` adds an explicit unpriced-rows line, and the workspace's budget gauge switches to an indeterminate (hatched) band with a `≥` readout rather than claiming a percentage it cannot know. Nothing converts uncertainty into a fabricated number in either direction.
64
+ - **`amicus watch <councilRunId>` now prints per-seat rows in the terminal**, not just the stage checklist — each seat's model, status, message count, tokens, cost and stall state, refreshed on the same poll as the stage rail. This is a **behavior change to an existing command**: a plain terminal `amicus watch` on a council run shows materially more than it did on 4.3.0, with no new flag. The Council Workspace GUI and the terminal renderer now read the same per-leg data.
65
+
66
+ ### Security
67
+
68
+ - **A council pointer file can no longer redirect reads — or writes — outside the project.** A `council-<runId>.json` pointer's `{runId, runDir}` JSON is validated only for truthiness (`src/council/run-state.js`), so a tampered or stale pointer could name any `runDir` on disk. The v4.4 Council Workspace already fenced all four of its pointer-consuming reads against the run dir's realpath; the older CLI/MCP surface behind `amicus_status` / `amicus_abort` / `amicus_list` / `amicus watch` did not, and two of its call sites are worse than a read leak — both crash detection and abort `checkpoint()` **into** `ptr.runDir`, making an unfenced pointer a write primitive at an attacker-chosen path. All of them now resolve and check containment **before touching the filesystem at all**, sharing the one fence implementation (`src/utils/path-fence.js`). A fenced-out pointer resolves to the existing "not a council run" outcome — the same `Session <id> not found in project <cwd>` error `amicus_status`/`amicus_abort` already return for an absent pointer, a skipped row in `amicus_list`, and `kind: 'unknown'` (→ `BAD_SESSION`) for `watch` — so no new error shape, and nothing is read or written from the escaping directory. Nothing legitimate is refused: a real `runDir` is always nested inside the project, enforced at creation time.
69
+
6
70
  ## [4.3.0] - 2026-07-24
7
71
 
8
72
  ### Added
package/README.md CHANGED
@@ -45,13 +45,14 @@ Hand Claude a plan, a design, a diff, an architecture decision, a manuscript —
45
45
 
46
46
  ## What is Amicus
47
47
 
48
- One install delivers five things that work together:
48
+ One install delivers six things that work together:
49
49
 
50
50
  - **The `second-opinion` LLM Council skill.** Structured multi-model review: independent reviews → anonymized peer cross-review → a non-Claude chair verdict → tiered accept/deny decisions. This is the hero.
51
51
  - **The `sidecar` chat skill.** Ad-hoc fork/work/fold — spin up one other model in a real window (or headless), work alongside it, fold the summary back.
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
56
 
56
57
  Claude is the orchestrator. The council and chat skills run *on top of* the engine; you talk to Claude, and Claude drives Amicus.
57
58
 
@@ -314,7 +315,7 @@ amicus update
314
315
  | `amicus continue` | Start a new session building on a previous one. |
315
316
  | `amicus read` | Output a session's summary / conversation / metadata. |
316
317
  | `amicus status <id>` | One-shot status for a session or fan-out wave (human or `--json`; `--wave <id>` alternative spelling). |
317
- | `amicus watch <id>` | Live-render a fan-out wave, council run, or session from any terminal until it finishes — in-place table on a TTY, `--plain` milestone lines, or `--json` NDJSON; exit code maps the terminal state. |
318
+ | `amicus watch <id>` | Live-render a fan-out wave, council run, or session from any terminal until it finishes — in-place table on a TTY, `--plain` milestone lines, or `--json` NDJSON; exit code maps the terminal state. `--ui` opens the [Council Workspace](./docs/council.md#council-workspace-gui) window instead (interactive-only, no `--json`). |
318
319
  | `amicus models` | List, search, refresh the catalog, or audit aliases. |
319
320
  | `amicus doctor` | Diagnose your setup — keys, default model, catalog, aliases, OpenCode binary, Electron, skills, MCP registration, OpenRouter credit (`--json`; `--fix` self-heals what it can). |
320
321
  | `amicus spend` | Cross-run cost rollup from the spend ledger, with per-run attribution — total + per-model spend, tokens, and source mix, most-expensive first (`--wave`/`--council`/`--project`/`--model`/`--op`/`--failed` filter it, `--group-by` buckets it, `--since 7d` windows it; `--json` for a versioned doc; shows remaining OpenRouter credit when a key is configured). |
@@ -350,7 +351,7 @@ $ amicus status demo123 --json
350
351
  "taskId": "demo123",
351
352
  "status": "complete",
352
353
  "elapsed": "5m 0s",
353
- "version": "4.3.0",
354
+ "version": "4.4.1",
354
355
  "model": "google/gemini-2.5-flash",
355
356
  "phase": "terminal"
356
357
  }
@@ -461,6 +462,8 @@ Run `amicus doctor` first — it checks keys, catalog, OpenCode binary, Electron
461
462
  | 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`. |
462
463
  | `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). |
463
464
  | `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. |
465
+ | 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). |
466
+ | `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). |
464
467
  | Session not found | No session matches the given ID | Run `amicus list`, or omit `--session-id` to use the most recent. |
465
468
  | No conversation history found | Project-path encoding | Check `~/.claude/projects/`; `/` and `_` in the project path are encoded as `-` in the directory name. |
466
469
  | Headless run never finishes | Task is bigger than the default timeout | Raise it: `--timeout 30`. |
@@ -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.
@@ -0,0 +1,200 @@
1
+ # Amicus — reprioritized roadmap
2
+
3
+ **Reprioritization guidance (Christian, 2026-07-18):** engine-first is locked; the near-term work
4
+ ships as an incremental **4.x point-release line**, each rev delivering a **behavioral / feature
5
+ benefit users feel**; **enterprise-readiness is a venture unto itself** — the deliberate **5.0**
6
+ major jump, gated on funding/cofounder. The observability arc is split so the **data layer ships
7
+ first (v4.3)** and the **Electron "Council Workspace" (v4.4)** rides on top of it. `--dry-run` cost
8
+ preview dropped to the backlog.
9
+
10
+ Amicus is at **v4.4.1** (2026-07-27), the fast-follow patch on v4.4.0 (tagged 2026-07-26). Each
11
+ 4.x rev below leads with the benefit, not the plumbing.
12
+
13
+ **Status:** v4.0 through **v4.4.1** have **shipped** — everything down to the v4.5 heading is a
14
+ record of what landed, not a plan. **v4.5 (policy packs + composition) is the next rev.** v5.0
15
+ remains forward-looking.
16
+
17
+ > 📁 **Reading this from an npm install?** Some references below point at working documents that
18
+ > live in the git repository and are deliberately **not** in the published package — anything under
19
+ > `.superpowers/` (the SDD working area, gitignored) and the root `BACKLOG.md`. The npm tarball
20
+ > ships `docs/*.md` only. Read those files at
21
+ > [github.com/BourbonDog/amicus](https://github.com/BourbonDog/amicus); the `.superpowers/` ones are
22
+ > local-only working notes and are not published anywhere. Every claim this roadmap makes is
23
+ > summarized here — the pointers are provenance, not prerequisites.
24
+
25
+ ---
26
+
27
+ ## v4.0 — "Councils become a command you can trust" *(foundation — engine-first)* — ✅ SHIPPED v4.0.0, 2026-07-20
28
+ **Benefit:** the flagship council stops being a manual 6-stage ritual — run a real adjudicated
29
+ council headlessly and in CI, and trust the output enough to gate on it.
30
+ - **Headless council orchestration engine** + `council run --headless` + `amicus_council_run` MCP — B1/A2/D2/F1 *(L)*
31
+ - **Council Review GitHub Action v2** (real adjudicated verdict on PRs) — B2 *(M)*
32
+ - **Versioned JSON envelope + published schema**, all failures routed through it — D3/C3 *(M)* — the engine's trustable contract
33
+ - **Prompt-injection fencing on JSON MCP tools** (H9) — A5/C6/D5 *(S)* — required before councils chew on untrusted CI content
34
+ - **Per-run fold nonce** (BL-7) — C5/D4 *(S)* — correctness/safety
35
+ > Why here: the engine is the moat and everything downstream (CI, automation, dashboards) needs it. The 3 cheap trust fixes ride along because a council you can't trust in automation isn't automatable.
36
+
37
+ ## v4.1 — "The skill sheds the ritual" *(skill-on-engine fast path)* — ✅ SHIPPED v4.1.0, 2026-07-21
38
+ **Benefit:** the daily interactive council stops being a manual 6-stage ritual too — the
39
+ second-opinion skill delegates Stages 1–3+5 to `council run` and keeps only the human stages
40
+ (0 intake, 4 decisions, 6 lessons).
41
+ - **Skill fast path** — SKILL.md orchestration rewired onto `amicus council run` *(M)*
42
+ - **Debate mode headless** (Stage 2.5 rebuttal round in the engine; here or v4.2 at the latest) *(M)*
43
+ - **README + docs update** — skill fast path and headless debate mode reflected in `README.md` and `docs/council.md` *(S)*
44
+ > Why here: locked during the v4.0 design (2026-07-19) — the engine proves itself in CI first
45
+ > (v4.0), then the flagship interactive UX adopts it before any new feature front opens.
46
+
47
+ ## v4.2 — "Bring your own models — $0, private, offline" — ✅ SHIPPED v4.2.0, 2026-07-23
48
+ **Benefit:** run sidecars and councils on local / OpenAI-compatible models (Ollama, LM Studio, vLLM)
49
+ — free marginal cost, private, air-gapped. The single biggest adoption + cost unlock (5 of 6 lenses' #1).
50
+ - **Local / OpenAI-compatible provider support** — `baseURL`/`type` discriminator, `$0`/offline pricing tier, setup-wizard support — A1/B3/C1/D1/E1 *(L)*
51
+ - **Adoption polish** (rides the "easy to start" story): `amicus init --claude` (C2), `doctor` at end of setup wizard (C8), docs for `spend`/`doctor`/`key` (C10) *(S)*
52
+ - **README + docs update** — local / OpenAI-compatible provider setup + `$0` pricing tier in `README.md` and `docs/configuration.md` *(S)*
53
+ > Why here (not enterprise): local models are a broad user benefit — cost, privacy, offline — not an enterprise-only feature. Comes right after the engine so councils can run on free/local seats.
54
+
55
+ ## v4.3 — "See runs live in the terminal — and never waste one" *(observability data layer, first)* — ✅ SHIPPED v4.3.0, 2026-07-24
56
+ **Benefit:** watch runs in real time in the terminal, recover from dead legs, and see where every dollar went.
57
+ - **Live wave observability data layer** + CLI/TUI `amicus watch <waveId>` + `--follow` streaming + `--on-complete <exec|mcp-notify>` hook — F3/D6 *(M)* — the shared data layer v4.4 builds on
58
+ - **Failed-leg retry** `fanout --retry-failed <waveId>` + **cheaper-model fallback chains** + failed-leg partial-spend tracking — F2/E10/E8 *(M)*
59
+ - **Spend visibility & attribution (basic):** fix continue/resume zero-spend rows, attribute waveId/council/project on every row, queryable `spend query` — A4(basic)/E3/E4/E9/D7/C9 *(M)*
60
+ - **README + docs update** — `watch`/`--follow`, failed-leg retry, and `spend query` documented in `README.md` and `docs/usage.md` *(S)*
61
+ > Why here / why first: this is the observability data layer + terminal surface. It ships **before** the GUI (v4.4) because the desktop workspace is a front-end on exactly this data. All M-effort, so it lands fast.
62
+
63
+ ## v4.4 — "The Council Workspace" *(desktop GUI on the v4.3 data layer)* — ✅ SHIPPED v4.4.0, 2026-07-26
64
+ **Benefit:** the same live data as a rich desktop app — watch a council *think*, not just tail a log.
65
+ - **★ Electron "Council Workspace" GUI** — live reviewer progress, anonymized peer packets, adjudication tiers, dissent, cost-by-seat, one-click fold into Claude Code — **B9** *(L)*
66
+ - **README + docs update** — Council Workspace walkthrough + screenshots in `README.md` and `docs/` *(S)*
67
+ > Why here: a GUI layer on top of v4.3's data layer. Split into its own point release because it's the one **L-effort** build in the observability arc — keeping v4.3 small and shippable.
68
+ >
69
+ > The five paid gate councils run against it (`wsgate01`–`wsgate04`, `costgate01`) are also what
70
+ > produced the 4.4.1 backlog below: the GUI shipped, and running real money through it is what
71
+ > surfaced the cost-attribution and repair-path defects that patch closes.
72
+
73
+ ## v4.4.1 — "What the gate councils found" *(fast-follow patch on 4.4.0)* — ✅ SHIPPED v4.4.1, 2026-07-27
74
+ **Benefit:** the product stops mis-stating its own spend, a repair leg stops fabricating findings,
75
+ and a review that honestly finds nothing stops being an error.
76
+ - **Cost truthfulness** — subtree-unknown spend carried into the ledger, the sticky unknown-spend notice unstuck, a cache-only leg reported `unknown` rather than falsely free, and `--max-cost` degraded to exit `2` when the total is inexact rather than claiming a percentage it cannot know — CA-2/CA-3/CA-6/CA-7 *(M)*
77
+ - **The repair path, whole** — all four remaining repair-prompt builders now carry the artifact they are repairing, and a repaired review no longer splices two generations together — LC-12/LC-11 *(M)*
78
+ - **A clean review is a valid review** — `EMPTY_FINDINGS` accepts a well-formed empty set, and the tally, street-cred and chair degrade gracefully on an all-clean bench — LC-10 *(M)*
79
+ - **One OpenCode server per council run** — concurrent waves no longer race each other's SQLite open, which was making `--critic` a coin flip *(M)*
80
+ - Renderer, progress and leg-row robustness; `electron/` under the lint gate; the read-only-workspace invariant test; live rails green as documented *(S each)*
81
+ > Why a patch and not a rev: every item is a correction to something already shipped, all of it
82
+ > measured against real paid runs. Two behaviour changes ride along (LC-2's session abort at the
83
+ > tool-settle ceiling, LC-10's acceptance of an empty finding set) — both owner-ruled, both
84
+ > corrections rather than new capability. Scope, rulings and the full 61-item inventory live in the
85
+ > repo's working notes (`.superpowers/sdd/v441/backlog-and-proposal.md`, local-only) and in the
86
+ > repo's root `BACKLOG.md` — neither ships in the npm package; see the note at the top.
87
+
88
+ ## v4.5 — "Save, share, and compose your councils"
89
+ **Benefit:** complex councils become one-command, repeatable, and chainable.
90
+ - **★ Auto-open the Council Workspace on a council run (Christian, 2026-07-26)** — when a council is
91
+ invoked from Claude Desktop and Electron is already present, the Workspace window opens by
92
+ default instead of requiring a separate `amicus watch <runId> --ui`. Today the GUI is opt-in and
93
+ discoverable only from `watch --help`, so the flagship v4.4 surface goes unseen on the very
94
+ client best able to show it. *(S–M; the pieces exist — see the design notes below.)*
95
+ - **Council policy packs + full run-profiles** (bench + lenses + options + briefing template, invoke by name) — B7/F5 *(M)*
96
+ - **Composable/chained waves** (`--input-from <waveId>` / pipe) for generate→critique→refine — F6 *(M)*
97
+ - **Briefing templates + library** (F9), **session/wave tagging + `--search` + grouped history** (F8), **GUI power ergonomics** (F10) *(S–M)*
98
+ - **README + docs update** — policy packs, chained waves, and the briefing-template library in `README.md` and `docs/` *(S)*
99
+ > Why here: velocity multipliers that only pay off once councils are a command (v4.0) and observable (v4.3/v4.4).
100
+
101
+ ### Deferred out of v4.4.1 into v4.5 (2026-07-27)
102
+
103
+ Each is `M`+, or needs data or a design decision — the bar a patch on a published release cannot
104
+ carry. The table below is self-contained; the full write-ups (what, where, what breaks if it stays)
105
+ live in the repo's local-only working notes — `.superpowers/sdd/v44/v4.4.1-backlog.md`, with the
106
+ disposition that put them here in `.superpowers/sdd/v441/backlog-and-proposal.md`. **If you have
107
+ those notes, read that backlog's Appendix A (settled decisions) and Appendix B (known false
108
+ positives) before re-filing anything from this list.**
109
+
110
+ | ID | What | Why not 4.4.1 |
111
+ |---|---|---|
112
+ | **CA-4** | `tally.json`'s `runStats` omits Stage-2 judges, repair solos and failed chair attempts (5 rows for 11 real legs in `wsgate04`) | `M` — a schema question, not a fix |
113
+ | **CA-5** | `isSubagentToolCall` is still a `name === 'task'` string proxy | `M`, and **reduced** by v4.4.0: it is now only the fallback when the real subtree walk finds nothing |
114
+ | **LC-1** | B53's stall kill is skipped while a tool-settle deferral is active | `S–M` — shipped deliberately; the author wants a second opinion, which needs data from real runs |
115
+ | **LC-5** | A chair fallback leaves no trace in `run.json` (`wsgate02`'s haiku failed twice; only `"chair":"minimax"` was recorded) | `M` — a run-record schema addition |
116
+ | **RN-1** | `sanitizeName` collisions surface as a banner rather than a refusal | `S` + a product decision that was already argued once |
117
+ | **RN-2** | `renderRunList` blind masking is best-effort — only the open run resolves labels | `M` |
118
+ | **RN-5** | A blind-mode flip closes every open prose panel and repaints twice | `S–M` |
119
+ | **RN-11** | `renderSeats` never reorders existing rows | `S`, cosmetic, no consequence yet |
120
+ | **REL-2** | `mcp-repomix-e2e` skips, so plugin-chain MCP discovery is exercised nowhere | `M` — needs `AMICUS_REPOMIX_E2E_PROJECT` pointed at a real project *and* `repomix` on PATH |
121
+ | **TST-1 / TST-2** | No real `--debate` fixture; the `lens:<slug>` role branch has zero coverage | `M` each, and they want doing together |
122
+ | **TST-3** | Abort confirm→status-flip is proven only against the fake DOM | `M` — needs a real CDP pass |
123
+ | **TST-7** | Six render functions have no unit coverage | `M` |
124
+ | *(new)* | **Residual integration-suite handle leaks** — a NAMED leak with evidence, filed 2026-07-27 after 4.4.1 fixed ENV-6 and the live rail still warned from *different* suites | `S–M`. Full evidence, including why `--detectOpenHandles` cannot diagnose this class, is in the repo's root `BACKLOG.md` (not in the npm package — read it on GitHub) — start there rather than re-deriving it |
125
+
126
+ **ENV-6 is NOT on this list** — it was pulled into 4.4.1 by owner ruling and fixed at the source
127
+ (the CDP e2e suite's SIGKILL escalation timer). **ENV-1** is not on it either: it is a decision
128
+ record ("eleven `Number(env) || default` sites"), not a task — a blanket migration would introduce
129
+ six new defects to fix one, and `src/utils/env-num.js`'s docblock records which knobs deliberately
130
+ keep the old form.
131
+
132
+ ## v5.0 — Enterprise-readiness *(the deliberate major jump — a venture unto itself, gated on funding / cofounder)*
133
+ **Benefit:** team/org deployment — but a distinct product + go-to-market motion (SOC2, SLAs, sales, support), not a feature drop. Parked as the 5.0 major per the chair's hard-question #5: a solo dev can't credibly ship or support this alone.
134
+ - Secret-store backends + env-var-only mode (A3); org allowlists/blocklists, per-team cost ceilings, read-only enforcement (A6); RBAC
135
+ - Audit & compliance: reproducibility manifests + replay (B11), seed/temp/version pinning (A7), spend export to SIEM/warehouse (A10), `/health` + metrics + structured logging (A8)
136
+ - Team config `.amicusrc` (A9); spend **governance** (per-team caps/enforcement) — the governance half of A4
137
+ - Learning loops that need scale anyway: reliability-aware seat selection (B4), calibration benchmarks (B5), decision-outcome feedback (B10), adaptive strategy planner (B8), evidence provenance (B6)
138
+ - README + docs update: deployment/admin documentation for the above, in `README.md` and `docs/`
139
+ > These cluster because they share one prerequisite you don't have yet: an org buyer + the org to support. Revisit as a funded track.
140
+
141
+ ---
142
+
143
+ ### Design notes — auto-open the Council Workspace
144
+
145
+ Recorded 2026-07-26 from a read of the shipped code, so the v4.5 implementer starts from facts
146
+ rather than re-deriving them.
147
+
148
+ **The pieces already exist.**
149
+
150
+ | Need | Where it lives today |
151
+ |---|---|
152
+ | Launch the window | `src/sidecar/workspace-window.js` `launchWorkspaceWindow({project, runId})` |
153
+ | Detect the client | `src/utils/client-detect.js` `detectClient(mcpServer)` → `code-local` \| `code-web` \| `cowork` |
154
+ | Is Electron usable | `src/sidecar/electron-install.js` `isElectronUsable` / `resolveElectronBinary` |
155
+ | Current entry point | `amicus watch <runId> --ui` (`src/cli-handlers-watch.js:87`) |
156
+
157
+ **"Claude Desktop" maps to `code-local`.** ⚠️ But `detectClient` reads the MCP client's
158
+ `getClientVersion().name`, so it **only works on the MCP path** — `amicus_council_run`, which is
159
+ exactly the Claude Desktop case. A `council run` typed into a terminal has no MCP server, so
160
+ detection there falls through to the env override or the `cowork` status-quo default. Do not build
161
+ this on the CLI path expecting detection to work; either gate it on the MCP entry point or thread
162
+ an explicit client tag through. (Related: the Phase 12 backlog item about persisting the client tag
163
+ into shared-server `metadata.json` is the same seam.)
164
+
165
+ **Four guards, all load-bearing:**
166
+
167
+ 1. **Never under `--json`.** `--ui` already rejects `--json` (interactive-only); an implicit default
168
+ must not create the combination the explicit flag refuses.
169
+ 2. **Never in CI or headless.** `council run` is the engine behind the Council Review GitHub Action
170
+ and every headless fanout. A popped window on a runner is a hang, not a feature. Gate on the same
171
+ display check the e2e suite uses (`HAS_DISPLAY`).
172
+ 3. **Never trigger an install.** Requirement is *"where Electron is installed"* — check
173
+ `isElectronUsable`, and if it is absent, do nothing silently. An implicit ~100 MB Electron
174
+ download on someone's first council run is a hostile surprise.
175
+ 4. **Must be opt-out.** A `--no-ui` (or config key) that suppresses it, because this changes default
176
+ behaviour for an existing command.
177
+
178
+ **Why not v4.4.1.** It is a new default behaviour — a feature — and 4.4.1 is a patch on a shipped
179
+ release whose scope was explicitly locked. The patch already carries two behaviour changes (LC-2,
180
+ LC-10) that stretch the definition; a third that pops a GUI window would not be defensible as a
181
+ patch. Sits naturally beside v4.5's existing **GUI power ergonomics (F10)** line.
182
+
183
+ ## Backlog (tracked, not scheduled)
184
+ - **`--dry-run` / cost & route preview** across start/fanout/council — E2/C7/F4 *(M)* — "know the cost/route before you commit"; useful, not essential to the near-term line.
185
+ - **F7** — Parallel council panels + super-chair (opt-in `--panels N` high-assurance; niche).
186
+ - **E7** — Prompt dedup cache (exact-dup, opt-in, excludes council/fanout) — minor cost optimization.
187
+ - **E6** — Cost-per-quality metric (withdrawn in debate; revisit if street-cred stabilizes).
188
+ - **`amicus key --local` picker** *(S)* — a default local-provider picker so `amicus key` sets/clears a bearer without naming the provider id: auto-select (and announce) when exactly one local provider is configured, a numbered prompt when several, and a hard error under `--json`/non-interactive rather than guessing a secret's destination. Register `--local` as a boolean flag. Deferred out of v4.2.1 as feature material (a new CLI surface, not a patch fix).
189
+ - **Headless no-output fast-fail backstop** *(M)* — fail a headless run fast (env-tunable `AMICUS_NO_OUTPUT_BACKSTOP_MS`, ~120s default) when a misconfigured local model produces zero output, reasoning, and tool-calls, instead of polling to the request/overall timeout. Disarms permanently on the first token/reasoning/tool_use, so a legit slow cold-prefill local model (30–90s is normal) is never affected. Deferred out of v4.2.1 as new runtime behavior.
190
+
191
+ ## What changed vs. the council's flat top-10
192
+ - Split the flat list into a **benefit-themed 4.x point-release line** (v4.0 → v4.2 → v4.3 → v4.4 → v4.5).
193
+ - **v4.1 inserted post-design (2026-07-19):** skill-on-engine fast path + headless debate mode,
194
+ between the engine (v4.0) and local providers (v4.2). Spec: `docs/superpowers/specs/2026-07-19-v4.0-headless-council-engine-design.md`.
195
+ - **Local providers stays near-term** (v4.2) — a broad cost/privacy benefit, not enterprise.
196
+ - **Observability arc split:** v4.3 = the data layer + terminal surface + resilience + spend (ships first); **v4.4 = the Electron Council Workspace (B9)** as a GUI on that data.
197
+ - **`--dry-run` cost preview → backlog** (was in the observability rev).
198
+ - **Enterprise/governance/audit/compliance/learning-loops → v5.0**, reframed as the deliberate *major-version venture* gated on funding.
199
+ - The **cheap trust fixes** (envelope, injection fencing, fold nonce) pulled into **v4.0** because the engine needs them to be trustworthy in automation.
200
+ - **Docs are part of the rev (2026-07-20):** every rev from v4.1 onward closes with a **README + docs update** line item, so each release ships its own documentation rather than deferring it.