amicus 4.8.1 → 4.9.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 (117) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +316 -0
  3. package/README.md +8 -3
  4. package/docs/ROADMAP.md +101 -10
  5. package/docs/configuration.md +54 -4
  6. package/docs/council.md +102 -14
  7. package/docs/troubleshooting.md +9 -2
  8. package/docs/usage.md +128 -12
  9. package/electron/ipc-setup.js +41 -18
  10. package/electron/main.js +19 -5
  11. package/electron/offer-session.js +51 -0
  12. package/electron/setup-ui.js +46 -39
  13. package/electron/workspace-ui/live-dead-seats.js +163 -91
  14. package/electron/workspace-ui/workspace-banners.js +30 -7
  15. package/electron/workspace-ui/workspace-matrix.js +23 -3
  16. package/electron/workspace-ui/workspace-seats.js +95 -79
  17. package/package.json +1 -1
  18. package/schemas/council-run.schema.json +2 -2
  19. package/schemas/council-tally.schema.json +17 -1
  20. package/schemas/council-verdict.schema.json +12 -4
  21. package/schemas/run.schema.json +6 -1
  22. package/skills/second-opinion/COUNCIL-DESIGN.md +1 -1
  23. package/skills/second-opinion/MANUAL-ORCHESTRATION.md +1 -1
  24. package/skills/second-opinion/MODEL-NOTES.md +88 -9
  25. package/skills/second-opinion/SEAT-BRIEFS.md +36 -4
  26. package/skills/second-opinion/SKILL.md +151 -36
  27. package/src/cli-council-run-bench.js +98 -6
  28. package/src/cli-handlers-council-run.js +18 -6
  29. package/src/cli-handlers-council.js +57 -7
  30. package/src/cli-handlers-doctor.js +1 -1
  31. package/src/cli.js +3 -1
  32. package/src/council/anonymize.js +2 -1
  33. package/src/council/briefings-chair-task.js +161 -0
  34. package/src/council/briefings-chair.js +33 -8
  35. package/src/council/briefings-debate.js +79 -13
  36. package/src/council/briefings-stage2-task.js +236 -0
  37. package/src/council/briefings-stage2.js +103 -26
  38. package/src/council/briefings-task.js +167 -0
  39. package/src/council/briefings.js +41 -4
  40. package/src/council/chair-fallback.js +95 -0
  41. package/src/council/debate.js +38 -21
  42. package/src/council/findings.js +3 -2
  43. package/src/council/ledger.js +2 -2
  44. package/src/council/parse-stage2.js +63 -15
  45. package/src/council/report-cost.js +61 -0
  46. package/src/council/report-html.js +26 -4
  47. package/src/council/report-md.js +30 -2
  48. package/src/council/report.js +40 -37
  49. package/src/council/run-assemble.js +21 -6
  50. package/src/council/run-chair.js +44 -95
  51. package/src/council/run-debate-revote.js +81 -49
  52. package/src/council/run-debate.js +51 -34
  53. package/src/council/run-finish.js +5 -3
  54. package/src/council/run-retry-keys.js +4 -4
  55. package/src/council/run-retry-launch.js +4 -4
  56. package/src/council/run-retry-notes.js +72 -15
  57. package/src/council/run-stage1-launch.js +4 -4
  58. package/src/council/run-stage1-rows.js +9 -6
  59. package/src/council/run-stage2.js +81 -47
  60. package/src/council/run-stages.js +9 -21
  61. package/src/council/run-stats-entry.js +46 -1
  62. package/src/council/run.js +28 -13
  63. package/src/council/seats.js +2 -2
  64. package/src/council/stage1-bind.js +3 -2
  65. package/src/council/verdict-seat-loss.js +124 -0
  66. package/src/council/verdict.js +108 -99
  67. package/src/headless.js +256 -49
  68. package/src/mcp-council-bench.js +64 -3
  69. package/src/mcp-council-run.js +10 -3
  70. package/src/mcp-server.js +52 -12
  71. package/src/mcp-tools.js +41 -5
  72. package/src/observe/council-legs.js +2 -2
  73. package/src/opencode-client.js +19 -1
  74. package/src/pack/pack-forward.js +15 -12
  75. package/src/pack/pack-resolve.js +1 -1
  76. package/src/prompt-builder.js +17 -1
  77. package/src/sidecar/fanout-leg-fallback.js +2 -1
  78. package/src/sidecar/fanout-leg.js +26 -0
  79. package/src/sidecar/fanout.js +1 -1
  80. package/src/sidecar/list-council.js +178 -0
  81. package/src/sidecar/list-limit.js +3 -1
  82. package/src/sidecar/list-search.js +2 -1
  83. package/src/sidecar/models-render.js +71 -0
  84. package/src/sidecar/models.js +19 -45
  85. package/src/sidecar/read.js +34 -10
  86. package/src/sidecar/reopen-spend.js +2 -1
  87. package/src/sidecar/setup.js +13 -4
  88. package/src/sidecar/start.js +2 -1
  89. package/src/template/render.js +16 -7
  90. package/src/utils/alias-audit.js +10 -3
  91. package/src/utils/alias-shadow-writer.js +220 -0
  92. package/src/utils/alias-shadow.js +294 -0
  93. package/src/utils/config.js +1 -1
  94. package/src/utils/curated-models.js +19 -9
  95. package/src/utils/degrade.js +12 -5
  96. package/src/utils/doctor-alias-check.js +2 -5
  97. package/src/utils/engine-log-parse.js +289 -0
  98. package/src/utils/engine-log-tail.js +114 -0
  99. package/src/utils/engine-log.js +250 -0
  100. package/src/utils/engine-skew-records.js +146 -0
  101. package/src/utils/engine-skew.js +300 -0
  102. package/src/utils/gateway-router.js +21 -3
  103. package/src/utils/model-canonicalization.js +55 -6
  104. package/src/utils/model-catalog.js +27 -9
  105. package/src/utils/model-fetcher.js +69 -16
  106. package/src/utils/model-shortlist.js +5 -2
  107. package/src/utils/provider-default-picker.js +6 -3
  108. package/src/utils/quick-picks.js +45 -7
  109. package/src/utils/result-schema.js +17 -1
  110. package/src/utils/text-sanitize.js +81 -0
  111. package/src/utils/ttft.js +57 -0
  112. package/src/utils/untrusted-fence.js +111 -1
  113. package/src/workspace/fold-format.js +28 -7
  114. package/src/workspace/live-normalize.js +2 -1
  115. package/src/workspace/matrix-model.js +6 -2
  116. package/src/workspace/run-detail.js +33 -7
  117. package/src/workspace/seat-space.js +10 -6
@@ -18,6 +18,15 @@ non-Claude chair, per the council's core rule)._
18
18
  (`src/council/briefings.js` + `briefings-stage2.js` + `briefings-debate.js`). This file stays
19
19
  authoritative for the manual path and for element *semantics*.
20
20
 
21
+ **v4.9 — everything below is REVIEW wording.** Task mode (`--intent task`, SKILL.md §5.5) has an
22
+ engine-composed twin of every block here (`briefings-task.js`, `briefings-stage2-task.js`,
23
+ `briefings-chair-task.js`, and the task frames in `briefings-debate.js`), so a fast-path task run
24
+ needs nothing from this file. Only the **manual path** has to swap the wording by hand: the seat
25
+ role becomes *do the work, do not review the briefing*; "findings" become the **load-bearing claims**
26
+ the deliverable rests on (same JSON shape, same severity enum, `location` = source, computation, or
27
+ the word `assumption`); the judge's ranking axis becomes *how well the work was done* rather than
28
+ how accurate a critique was; and the chair closes on the ANSWER scale below.
29
+
21
30
  ---
22
31
 
23
32
  ## Standard anti-sycophancy clause (ALL Stage-1 briefings — not an optional element)
@@ -173,9 +182,11 @@ verbatim:
173
182
 
174
183
  ---
175
184
 
176
- ## Chair verdict-scale addendum
185
+ ## Chair closing-scale addendum
177
186
 
178
- Append to the chair packet (`_tmp-chair-packet.md`) when the element is toggled ON:
187
+ Append to the chair packet (`_tmp-chair-packet.md`). ⚠️ **No longer an opt-in element** the engine
188
+ makes every chair close on a scale, so the manual path must too; SKILL.md's Stage-0 menu says the
189
+ same. Review runs use the VERDICT scale below; task runs use the ANSWER twin after it.
179
190
 
180
191
  > After your synthesis, add two closing sections:
181
192
  >
@@ -192,5 +203,26 @@ Append to the chair packet (`_tmp-chair-packet.md`) when the element is toggled
192
203
  > Name the gaps or the structural problems in the synthesis ABOVE, not on the VERDICT
193
204
  > line itself — that line carries the phrase and nothing else.
194
205
 
195
- **Orchestration note:** surface the chair's `VERDICT:` line verbatim at the top of
196
- `report.md` and in the inline chat presentation of the results.
206
+ ### Task-mode twin the ANSWER scale
207
+
208
+ Same two closing sections, same "final line, alone" rule, same HARD QUESTIONS item (all three are
209
+ scale-independent). Only the phrase list and its gloss change:
210
+
211
+ > `ANSWER: Converged` | `ANSWER: Split` | `ANSWER: Insufficient`
212
+ >
213
+ > Pick one. "Converged" = the bench substantially agrees and the synthesis above is
214
+ > well-supported. "Split" = material disagreement remains; the synthesis states both positions
215
+ > and what would settle them. "Insufficient" = the bench's work cannot support an answer —
216
+ > missing information, an unsound premise, or too little usable output. Name which, in the
217
+ > synthesis ABOVE, not on the ANSWER line.
218
+
219
+ The task chair's synthesis instruction also differs: adopt the strongest response, merge
220
+ complementary ones, or refuse the premise if the bench showed it unsound — then close the synthesis
221
+ with a **RESIDUAL RISK** section naming the disputed claims the answer still depends on, or the
222
+ single line `RESIDUAL RISK: none — no load-bearing claim was disputed.` when that is the truth.
223
+ (Drop the RESIDUAL RISK close when the bench declared no claims at all: it is defined as "the claims
224
+ peers disputed", and asking for it over an empty set is an unfollowable instruction.)
225
+
226
+ **Orchestration note:** surface the chair's closing line — `VERDICT:` or `ANSWER:`, whichever the
227
+ chair actually wrote — verbatim at the top of `report.md` and in the inline chat presentation of
228
+ the results. Never translate one scale into the other.
@@ -1,31 +1,48 @@
1
1
  ---
2
2
  name: second-opinion
3
3
  description: >
4
- Run a structured, multi-model "council" review of material the user provides plus
5
- an analysis request and criteria, turning it into decisions. Models review the
6
- material independently, then anonymously rank and adjudicate each other's reviews
7
- in a peer cross-review stage, and a designated non-Claude "chair" model synthesizes
8
- the verdict Claude orchestrates but does not synthesize. Trigger on "second
9
- opinion", "multi-model review", "council review", "have other models
4
+ A structured, multi-model "council" that either REVIEWS material against criteria or
5
+ DOES the open-ended work asked for, turning either into decisions. Models work
6
+ independently, then anonymously rank and adjudicate each other's output; a non-Claude
7
+ "chair" synthesizes the verdict or answer Claude orchestrates, never synthesizes.
8
+ Trigger on "second opinion", "multi-model review", "council review", "have other models
10
9
  review/critique/evaluate this", "cross-check this against the research",
11
- "red-team/stress-test this doc", "what would other models conclude about this", or
12
- any request to review provided material with external model(s) and turn it into
13
- accept/deny decisions — even if the user never says "sidecar". This is NOT for
14
- quick or exploratory single-model chats "ask Gemini…", "what does DeepSeek
15
- think", brainstorming with a model, or spawning/forking a conversation with
16
- another model — use the sidecar skill instead.
10
+ "red-team/stress-test this doc", "what would other models conclude about this", or any
11
+ request to have external models review material and turn it into accept/deny decisions —
12
+ even without the word "sidecar". Also TASK mode: "have the council work this out", or
13
+ any request for several models to independently produce an analysis, answer, or artifact
14
+ and reconcile them. NOT for quick or exploratory single-model chats — "ask
15
+ Gemini…", brainstorming, or forking a chat with another model — use the sidecar
16
+ skill instead.
17
17
  ---
18
18
 
19
19
  # Second Opinion (LLM Council)
20
20
 
21
- Independent, multi-model review of material the user provides, turned into decisions. Any single model — including the one running this conversation — has consistent blind spots. Routing the same material through models from *different* families surfaces disagreements, missed issues, and overstated claims that one model alone won't catch.
21
+ Independent, multi-model work — either a review of material the user provides or the work itself — turned into decisions. Any single model — including the one running this conversation — has consistent blind spots. Routing the same brief through models from *different* families surfaces disagreements, missed issues, and overstated claims that one model alone won't catch.
22
22
 
23
23
  Four principles govern this skill:
24
24
 
25
25
  1. **Secondary tool.** By the time this skill runs, Claude has already given its opinion in the main conversation. The skill exists to bring in *independent outside* views — it does not replace or re-run Claude's upstream analysis.
26
26
  2. **The council is the non-Claude bench by default.** Council members are models from families other than the orchestrator (Gemini, DeepSeek, GPT, etc.). Claude is not a first-opinion council member unless the optional "Claude in the council" toggle is on — and even then it is judged but does not vote or chair.
27
27
  3. **Claude orchestrates; Claude does not synthesize the verdict.** Claude preps material, recommends the council, drives the run, presents accept/deny decisions, and applies them. A designated non-Claude chair model synthesizes the final verdict. Claude's role ends at presenting it.
28
- 4. **The subject of cross-review is the other reviews, not the artifact again.** In the peer cross-review stage, models critique and rank *each other's reviews* — not re-review the original artifact. This is the mechanism that surfaces reviewer blind spots and inflated confidence.
28
+ 4. **The subject of cross-review is the other reviews, not the artifact again.** In the peer cross-review stage, models critique and rank *each other's reviews* — not re-review the original artifact (in task mode: each other's *responses*; the subject is never the briefing again). This is the mechanism that surfaces reviewer blind spots and inflated confidence.
29
+
30
+ **Two intents, one machine (v4.9).** The council runs in one of two intents, and choosing between
31
+ them is a Stage-0 decision, not a wording preference:
32
+
33
+ - **Review (default).** The user supplies material and the bench reviews it against criteria. Seats
34
+ raise findings; judges rank how accurate each review was; the chair closes
35
+ `VERDICT: Ship it | Fix these first | Fundamental rethink`.
36
+ - **Task** (`--intent task`). The user asks for work to be DONE, and the bench does it — each seat
37
+ independently produces the analysis, answer, or artifact the briefing requests, plus the
38
+ load-bearing claims it rests on. Judges rank the responses by *how well each did the work* and
39
+ adjudicate those claims; the chair synthesizes an ANSWER across them and closes
40
+ `ANSWER: Converged | Split | Insufficient`.
41
+
42
+ Everything else is shared: same seats, same anonymized bundle, same tier machinery (though a tier
43
+ means peer *concurrence* on a task run, not peer verification of a defect), same commands, same
44
+ artifacts under the same filenames (a seat's deliverable still lands in `review-<seat>.md`). Task
45
+ mode is not a lesser mode or a fallback — see *Key mechanics → §5.5*.
29
46
 
30
47
  **The engine runs the mechanics (v4.1).** Stages 1–3 and the deterministic Stage-5 artifacts are
31
48
  ONE `amicus council run` call: the engine composes every model-facing briefing, runs the review
@@ -47,7 +64,8 @@ Operating lessons from each run fold back into `MODEL-NOTES.md` (with approval),
47
64
  - The user provides documents, artifacts, or links **and** an analysis request **and** criteria, and wants other models to weigh in independently.
48
65
  - They want a fact-check, critique, research-backed evaluation, red-team, or "what would another model conclude about this?"
49
66
  - They need actionable decisions (accept / defer / deny) on the material, not just a summary of findings.
50
- - A thorough **single-model** pass is wantedscales down gracefully; the boundary vs the `sidecar` skill is **intent** (reviewing provided material against criteria), not model count.
67
+ - **Task mode:** the ask is open-ended work produce the analysis, the answer, the artifact and they want several independent models to do it and the disagreements reconciled rather than one model's draft. Run it with `--intent task`; §5.5 has the mechanics.
68
+ - A thorough **single-model** pass is wanted — scales down gracefully; the boundary vs the `sidecar` skill is **intent** (structured, criteria-bound work reconciled into decisions), not model count.
51
69
 
52
70
  ## When NOT to use
53
71
 
@@ -68,7 +86,15 @@ human-in-the-loop; the engine call itself is one background command you do not p
68
86
 
69
87
  ### Stage 0 — Intake & prep
70
88
 
71
- Confirm the three inputs before doing anything else: **source material**, **the analysis** (the thing to be reviewed), and **the criteria** (what quality/correctness means for this material). Ask only for what is missing; don't re-ask for what is already provided.
89
+ **Settle the intent first it changes what the inputs are.** Ask which the user wants only when
90
+ the ask is genuinely ambiguous; usually it is plain. Review when there is a *thing* and the ask is a
91
+ judgment on it ("is this right", "what did I miss", "red-team this"). Task when the deliverable does
92
+ not exist yet and the ask is to produce it ("work out X", "draft the plan", "analyze this market").
93
+ An ask that supplies material but wants new work built *from* it ("take these notes and write the
94
+ strategy") is a TASK — the material is input, not the subject. State the intent in the run-shape
95
+ disclosure below so the user can correct you before any spend.
96
+
97
+ Then confirm the three inputs before doing anything else — **review:** **source material**, **the analysis** (the thing to be reviewed), and **the criteria** (what quality/correctness means for this material); **task:** **the brief** (the work to be done, stated precisely enough that two models can do it without asking), any **material** it should draw on, and **the criteria** (what a good answer must do — scope, depth, form, what counts as evidence). Ask only for what is missing; don't re-ask for what is already provided.
72
98
 
73
99
  **Establish the run folder first:** `output/<stem>-council/` (or `./second-opinion/<stem>-council/`
74
100
  if no `output/` directory exists). Create it now — it is both your working directory and the
@@ -82,14 +108,19 @@ its absolute path in all path arguments.
82
108
  - Small, clean text → feed inline in the briefing.
83
109
 
84
110
  **Author ONE briefing file: `<run-folder>/briefing.md`.** This is the only briefing Claude
85
- writes, and it carries only the *review request*:
111
+ writes, and it carries only the *request* — the review request, or in task mode the work request:
86
112
 
87
113
  - the material — inline when small and clean, otherwise the **absolute path** of the extracted
88
114
  clean-text file (Stage-1 seats run agentically in the invoking cwd and can read referenced files);
89
- - the analysis request;
115
+ - the analysis request (task mode: the brief — what to produce, and in what form);
90
116
  - the criteria;
91
117
  - any material-specific cautions ("the appendix is out of scope", "treat the numbers as given").
92
118
 
119
+ **Task mode: write the brief, not the answer.** The seats execute this file. Do not sketch the
120
+ conclusion, pre-select an approach, or attach your own draft — every one of those collapses the
121
+ independence the bench exists for, and unlike a leaked review contract the engine cannot detect it.
122
+ State the goal, the constraints, the audience and the form; leave the work to the bench.
123
+
93
124
  **Do not restate output contracts in `briefing.md`.** The anti-sycophancy clause, the findings-JSON
94
125
  schema, the judge tasks, the no-tools preambles, the chair packet and the verdict-scale addendum
95
126
  are all the engine's (`briefings.js` templates) — duplicated contracts drift and the engine's copy
@@ -147,15 +178,19 @@ seats**; a 1-model run is the scale-down path below, not an engine run.
147
178
  bench and chair are picked and before asking for launch confirmation, present this menu once
148
179
  (adjust the run-shape numbers to the actual bench):
149
180
 
150
- > Optional council elements — all OFF unless you name them. Reply with any you want (e.g. "1 and 3", "critic + debate mode", or "none"). Note the chair's **verdict scale is now standard** — the engine always makes the chair close with `VERDICT: Ship it | Fix these first | Fundamental rethink` plus its hard questions, so it is no longer something to opt into:
181
+ > Optional council elements — all OFF unless you name them. Reply with any you want (e.g. "1 and 3", "critic + debate mode", or "none"). Note the chair's **verdict scale is now standard** — the engine always makes the chair close with `VERDICT: Ship it | Fix these first | Fundamental rethink` plus its hard questions, so it is no longer something to opt into (a task run closes on the ANSWER twin instead — `Converged | Split | Insufficient`):
151
182
  >
152
183
  > 1. **Critic seat** (`--critic <model>`) — one reviewer, which must be one of the bench seats, swaps to an adversarial brief (adversarial pass, edge-case hunt, consistency check, executability test). Same review count. Trade-off: that reviewer can recognize its own review during cross-review (disclosed in the report).
153
184
  > 2. **Expert lenses** (`--lenses s1,s2,s3`) — each reviewer gets a distinct expert perspective; you pick the panel domain (business, technical, customer, financial, custom), one lens per seat. Trade-offs: weakens cross-review anonymity (disclosed) and the run is **not** recorded to the reliability ledger.
154
185
  > 3. **Debate mode** (`--debate`) — after cross-review, Contested and Disputed findings go back to their raisers to defend, amend, or withdraw, and the disputing judges re-vote before the final tally. Adds 1–2 short waves (up to ~2N extra calls, ~+5 min).
155
- > 4. **Claude in the council** (`--claude-review`) — I add my own fresh review to the bundle so the bench can rank and adjudicate it; I'm judged but do not vote or chair, so the verdict stays independent. +1 review in the bundle, no extra council calls.
186
+ > 4. **Claude in the council** (`--claude-review`) — **review runs only.** I add my own fresh review to the bundle so the bench can rank and adjudicate it; I'm judged but do not vote or chair, so the verdict stays independent. +1 review in the bundle, no extra council calls.
156
187
 
157
188
  Rules for this menu:
158
189
 
190
+ - **On a task run, offer 1–3 only.** `--claude-review` enters a *review* as review N+1 and has no
191
+ task-mode meaning; the engine rejects the pair pre-flight, exit 1 before any spend. Don't offer
192
+ it and then have the launch fail.
193
+
159
194
  - **Never enable an element the user did not explicitly name.** Silence, "no", or "none" = all off. Do not infer opt-in from the nature of the material ("this doc could use a critic…") — offer, don't decide.
160
195
  - If elements were **pre-requested in the invoking command** (e.g. `/council … with a critic seat and debate mode`), confirm them back by name ("Critic seat and debate mode are ON per your request; the others are off") instead of re-asking.
161
196
  - **The launch confirmation must enumerate the enabled elements by name** — an element not named in the confirmation is off. Restate its cost/shape impact there.
@@ -169,10 +204,16 @@ Rules for this menu:
169
204
  this file and fails the run *before any spend* if it is malformed, so a bad file costs nothing but a
170
205
  relaunch. See §5.4.
171
206
 
172
- **Disclose the run shape up front**, naming any enabled elements and their cost impact — e.g.:
207
+ **Disclose the run shape up front**, naming the intent and any enabled elements and their cost
208
+ impact — e.g.:
173
209
 
174
210
  > This run uses 3 council models across 2 engine waves + 1 chair call (~7 model runs), ~10 min.
175
211
 
212
+ or, in task mode:
213
+
214
+ > **Task run** — the 3 models each produce the analysis independently, then rank and adjudicate each
215
+ > other's; the chair synthesizes one answer. 2 engine waves + 1 chair call (~7 model runs), ~10 min.
216
+
176
217
  or, with elements enabled:
177
218
 
178
219
  > This run uses 3 council models across 2 engine waves + 1 chair call, with **critic seat + debate mode ON** (~7 base runs + up to 6 rebuttal calls), ~15 min.
@@ -190,17 +231,19 @@ The scale-down levels count **non-Claude judges**; `--claude-review` adds a judg
190
231
 
191
232
  ### The engine run — Stages 1–3 plus the Stage-5 artifacts
192
233
 
193
- ONE call. The engine executes the Stage-1 review wave, the per-leg findings validation and bounded
234
+ ONE call. The engine executes the Stage-1 wave (reviews, or in task mode the seats' deliverables),
235
+ the per-leg findings validation and bounded
194
236
  repair loop, anonymization and run-global finding-id rewriting, the identical judge bundle and
195
237
  cross-review wave, the optional rebuttal round, the tally (which appends the reliability ledger
196
- **once**), the chair synthesis with the verdict scale, and the deterministic
197
- `verdict.json` + `report.html` — checkpointing `run.json` as it goes.
238
+ **once** — never on a task run, see §5.5), the chair synthesis with the standard closing scale, and
239
+ the deterministic `verdict.json` + `report.html` — checkpointing `run.json` as it goes.
198
240
 
199
241
  **Canonical launch (shell contexts):**
200
242
 
201
243
  ```
202
244
  amicus council run --prompt-file <run-folder>/briefing.md \
203
245
  --models "<m1,m2,m3>" --chair <chair> --out-dir <run-folder> --json \
246
+ [--intent task] \
204
247
  [--critic <m>] [--lenses s1,s2,s3] [--debate] \
205
248
  [--claude-review <run-folder>/review-claude.md] \
206
249
  [--max-cost <$> | --no-cost-gate] [--timeout <min>] [--gateway auto|direct|openrouter]
@@ -224,8 +267,10 @@ Stage-1 wave, the repair re-prompts, the Stage-2 judge wave, the debate legs, an
224
267
  so a single invocation replaces the old per-call pass-through footgun entirely.
225
268
 
226
269
  **Cowork / no-Bash environments:** use the MCP tools instead. `amicus_council_run`
227
- `{briefingFile, models|council, chair, critic?, lenses?, debate?, claudeReviewFile?, outDir,
270
+ `{briefingFile, models|council, chair, intent?, critic?, lenses?, debate?, claudeReviewFile?, outDir,
228
271
  maxCost?|noCostGate?, timeoutMinutes?, gateway?}` returns `{runId, runDir}` immediately.
272
+ `intent: 'task'` is the MCP twin of `--intent task`; omit it for a review run — absence IS review,
273
+ and passing `'review'` explicitly is accepted but never stored.
229
274
  Preferred: call `amicus_wait` with the runId — one blocking call; re-call it while it returns
230
275
  `timedOut: true`. Fallback: poll `amicus_status`, which shows stage progression. Then read the
231
276
  run-folder artifacts with the host's file tools. Council JSON returned by the MCP tools arrives
@@ -282,7 +327,16 @@ withdrew it in the rebuttal round (`findings[].debate.action === 'withdrawn'` in
282
327
  **auto-recorded `denied` in `decisions.json` and never presented for a user decision** — just note
283
328
  it as withdrawn when walking the tiers.
284
329
 
285
- **Consensus tier Confirmed findings** (≥ 2 peer agreements, agrees dominate)
330
+ **Task mode:** the findings are the **load-bearing claims** each response rests on, not defects — so
331
+ a tier says how many peers CONCURRED with a claim, never that anyone verified it, and models
332
+ correlate on priors. Say so when presenting, exactly as the rendered report does. Decide them as
333
+ claims: `accepted` = the answer may rest on this, `denied` = it may not, and anything downstream of
334
+ it goes with it, `modified` = it holds only in a narrower form, and the narrowing is recorded. A
335
+ clean bench is a valid result here too — an answer whose reasoning is fully inline can declare no
336
+ adjudicable claims at all, in which case there is nothing to tier and Stage 4 is one decision:
337
+ does the chair's answer stand as written.
338
+
339
+ **Consensus tier — Confirmed findings** (≥ 2 peer agreements with agrees dominating — or a lone corroborating peer with zero disputes)
286
340
 
287
341
  - Present the full list in one block: id, claim, severity, and which models raised / endorsed it.
288
342
  - Offer one **bulk accept/deny decision** over the whole tier:
@@ -322,6 +376,16 @@ Do not advance to Stage 5 until every finding has a recorded decision.
322
376
  - Do not attempt to produce a modified copy.
323
377
  - Write a **standalone reviewed report** instead: the full decision log, the chair's verdict, and clear callouts of what should be changed and where — formatted so the user can apply the changes manually.
324
378
 
379
+ **Task mode** (there is no source to revise — the deliverable is the output):
380
+ - The deliverable is the **chair's synthesized answer**, constrained by the Stage-4 decisions:
381
+ strike anything that rested on a `denied` claim, narrow anything a `modified` claim narrowed.
382
+ - Write it as `<run-folder>/answer.md` — or, when the brief asked for a specific artifact
383
+ (a spec, a plan, a letter), as that artifact under the name the brief asked for, next to the
384
+ material it drew on.
385
+ - **Never re-do the work yourself.** If the answer needs material the bench did not produce, say so
386
+ and offer a re-run with a sharper brief; silently writing your own version is the failure this
387
+ skill exists to prevent, and it is invisible to the user once the chair's prose is edited.
388
+
325
389
  **Run-folder artifacts.** The engine already wrote the deterministic set (see *Output & naming*).
326
390
  Two artifacts are yours:
327
391
 
@@ -354,7 +418,11 @@ Two artifacts are yours:
354
418
  `render: true` and `outDir: <run-folder>` — this refreshes `<outDir>/report.html` on disk and
355
419
  returns the Markdown rendering for `report.md` below; it still does **not** write `verdict.json`.
356
420
  - `report.md` — Claude-authored: the chair's synthesis (read verbatim from
357
- `<run-folder>/chair-output.md`, including its closing `VERDICT:` line at the top of the report) +
421
+ `<run-folder>/chair-output.md`, including its closing scale line at the top of the report
422
+ `VERDICT: Ship it | Fix these first | Fundamental rethink` on a review run,
423
+ `ANSWER: Converged | Split | Insufficient` on a task run. Reproduce the line the chair actually
424
+ wrote; never translate one scale into the other, and a run whose chair produced none says
425
+ `VERDICT: none` / `ANSWER: none` rather than inventing a phrase) +
358
426
  the full Stage-4 decision log (one row per finding: `id` + claim + decision — the claim text
359
427
  comes from `tally-input.json`, joined on `id` exactly as in Stage 4, since `decisions.json`
360
428
  and `tally.json` both carry only the id) + a summary of what was applied (+ the "How Claude's
@@ -385,7 +453,7 @@ The fast path retires the manual path's two hand-written artifacts: there is no
385
453
  `report.html`/`report.md`) and no `verdict.md` (the chair's prose is `chair-output.md`, written by
386
454
  the engine). Do not recreate them — `MANUAL-ORCHESTRATION.md` is where they still live.
387
455
 
388
- Tell the user exactly which files were written and where, leading with `report.html`, **and present the verdict inline in chat** — the chair's overall assessment (verbatim or lightly trimmed) plus the tier counts (Confirmed/Disputed/Contested/Singleton) and what was applied. Never hand over only file paths.
456
+ Tell the user exactly which files were written and where, leading with `report.html`, **and present the verdict inline in chat** — the chair's overall assessment (verbatim or lightly trimmed), its closing `VERDICT:`/`ANSWER:` line, plus the tier counts (Confirmed/Disputed/Contested/Singleton) and what was applied. On a task run lead with the answer itself, not the process. Never hand over only file paths.
389
457
 
390
458
  ---
391
459
 
@@ -405,8 +473,8 @@ Draft new or updated entries for the per-model sections of `MODEL-NOTES.md` that
405
473
  **Ledger — already appended; do not touch it.** The engine's finalize tally appended one row per
406
474
  (run × model × resolved executable) to the append-only `council-ledger.jsonl` under `getConfigDir()`
407
475
  as part of the run — one row per model on an ordinary bench, and since v4.8 one row per executable
408
- when an alias's seats resolved differently (expert-lens runs are deliberately excluded). **In the
409
- fast path, never run `council tally` yourself.** The ledger is append-only, so a second tally over
476
+ when an alias's seats resolved differently (expert-lens runs **and task runs** are deliberately
477
+ excluded — see §5.5). **In the fast path, never run `council tally` yourself.** The ledger is append-only, so a second tally over
410
478
  the same run double-appends: that permanently doubles every affected model's `conformance` histogram
411
479
  in `amicus council stats` and cannot be undone. (It does *not* skew the lifetime averages — a
412
480
  duplicated set of rows has the same mean — and since v4.8 it no longer inflates `runs`/`low-N`
@@ -516,6 +584,51 @@ in `--critic`, which must be a bench seat) with `council_claude_review_invalid`.
516
584
 
517
585
  ---
518
586
 
587
+ ### §5.5 Task mode (`--intent task`, default off)
588
+
589
+ The bench does the work instead of judging someone else's. Every stage still runs; what each stage
590
+ is *about* changes.
591
+
592
+ **What the seats are told.** Execute the briefing — "produce the analysis, answer, or artifact it
593
+ requests — you are not reviewing the briefing" — and close with the same trailing JSON block review
594
+ mode uses, carrying the **load-bearing claims** the deliverable rests on instead of defects found in
595
+ someone's work. The fenced JSON shape, the `blocker|major|minor|nit` enum and the required non-empty
596
+ `location` are unchanged; here `location` is the grounding discipline — the source, the computation,
597
+ or the literal word `assumption`. An empty claims list under a real answer is a valid result. Critic and lens seats
598
+ have task twins (argue against the easy answer *while doing the work*; do the work through one
599
+ expert perspective), so menu items 1–3 are all available.
600
+
601
+ **What the judges rank.** Review mode ranks how ACCURATE a critique was; there is no critique here,
602
+ so the axis is how well the asked-for work was DONE. Adjudication is unchanged — agree / dispute /
603
+ neutral, per claim.
604
+
605
+ **What the chair produces.** An ANSWER, not a verdict: adopt the strongest response, merge
606
+ complementary ones, or refuse the premise if the bench showed it unsound; state the consensus and
607
+ which way each disagreement went; close with a RESIDUAL RISK section naming the disputed claims the
608
+ answer still depends on. Then the final line, alone — `ANSWER: Converged` (the bench substantially
609
+ agrees and the synthesis is well-supported), `ANSWER: Split` (material disagreement remains; the
610
+ synthesis states both positions and what would settle them), or `ANSWER: Insufficient` (the bench's
611
+ work cannot support an answer — missing information, an unsound premise, or too little usable
612
+ output; *which* is named in the synthesis, never on the ANSWER line).
613
+
614
+ **What the mode refuses.** `--claude-review` is rejected pre-flight, exit 1 before any spend — it
615
+ enters a REVIEW as review N+1 and has no task-mode meaning. There is no task twin, and that is the
616
+ point: Claude contributing its own deliverable would be doing the work the bench was convened for.
617
+
618
+ **What is not recorded.** A task run is kept out of the reliability ledger, for the reason
619
+ expert-lens runs are: cred earned doing work is not comparable with cred earned reviewing. So
620
+ `amicus council stats` never moves on a task run, its numbers stay a review-mode signal — still the
621
+ right basis for bench selection — and a task run's rankings must not be read as if they fed it.
622
+
623
+ **How it surfaces.** `intent: 'task'` is emit-when-task and rides `run.json`, the tally `meta` and
624
+ `verdict.json`; absence means review, and `'review'` is never materialized. That one key is what
625
+ makes `report.html`/`report.md` head their summary **Answer summary** rather than *Verdict summary*,
626
+ print the peer-concurrence caveat, and makes the Workspace fold read `ANSWER:`. If a task run's fold
627
+ or panels say *verdict*, the intent did not reach that artifact — report it, do not relabel it by
628
+ hand.
629
+
630
+ ---
631
+
519
632
  ## Model-recommendation heuristics
520
633
 
521
634
  Use these together with `amicus council stats` (the ledger — authoritative quantitative reliability data: runs, avg peers-only street-cred, confirm-rate, fact-error rate) and the qualitative quirks in `MODEL-NOTES.md`:
@@ -536,13 +649,14 @@ Always **rank recommendations by fit**, state the trade-off for each option, and
536
649
  ## Output & naming
537
650
 
538
651
  - Run folder: `output/<stem>-council/` (or `./second-opinion/<stem>-council/` if no `output/` exists), passed to the engine as `--out-dir`. After a fast-path run it holds:
539
- - `briefing.md` — the Stage-0 review request Claude authored (run provenance, not a temp file)
652
+ - `briefing.md` — the Stage-0 request Claude authored — the review request, or in task mode the work brief (run provenance, not a temp file)
540
653
  - `review-claude.md` — Claude's own fresh review, only when "Claude in the council" is on
541
654
  - `run.json` — the engine's run manifest: stage log, wave ids, degradation, `runStats`, cost
542
655
  - `review-<seat>.md` ×N and `judge-<seat>.md` ×N — the raw engine legs. `<seat>` is the bench
543
656
  seat's id, which **is** the model alias for any bench that names each alias once; seat one and
544
657
  seat two of a repeated alias write `…-<alias>-1.md` and `…-<alias>-2.md` instead of sharing
545
- one file
658
+ one file. The names are intent-independent: on a task run `review-<seat>.md` holds that seat's
659
+ **deliverable**, not a review
546
660
  - `briefing-stage1.md`, `bundle-stage2.md`, `chair-packet.md` — the model-facing briefings the engine composed
547
661
  - `tally-input.json` and `tally.json` — the assembled input and the tiered record (plus `tally-provisional.json` and `debate.json` when `--debate` was on)
548
662
  - `rebuttal-<seat>.md` ×(raising seats) and `revote-bundle.md` + `revote-<seat>.md` ×(disputing seats) — the debate round's raw defense and re-vote leg outputs plus the shared re-vote prompt, only when `--debate` was on. Both waves are sized in seats, so a bench that repeats an alias buys up to two extra billed legs per duplicated pair per round (one defense solo, one re-vote leg), each of which may draw its own bounded repair solo
@@ -550,8 +664,9 @@ Always **rank recommendations by fit**, state the trade-off for each option, and
550
664
  - `decisions.json` — the Stage-4 decision array Claude writes
551
665
  - `verdict.json` — schema-stamped machine-readable record: tally output + Stage-4 decisions, written via `amicus council verdict` at Stage 5 (replacing the engine's undecided version)
552
666
  - `report.md` — Claude-authored; full contract defined once in *Stage 5 → Run-folder artifacts* above (chair's synthesis + Stage-4 decision log + run-stats table).
553
- - `report.html` — a **separate, deterministic** artifact rendered from `verdict.json` (no chair prose, no decision-log narrative — see Stage 5's *Renderer* note); the default artifact to share.
554
- - Reviewed copy: `<stem>-reviewed.<ext>`, next to the source.
667
+ - `report.html` — a **separate, deterministic** artifact rendered from `verdict.json` (no chair prose, no decision-log narrative — see Stage 5's *Renderer* note); the default artifact to share. On a task run it heads **Answer summary** and carries the peer-concurrence caveat, off `verdict.json`'s `intent: 'task'`.
668
+ - `answer.md` — task runs only: the chair's answer as constrained by the Stage-4 decisions (or the named artifact the brief asked for, wherever the brief asked for it). See *Stage 5 → Task mode*.
669
+ - Reviewed copy: `<stem>-reviewed.<ext>`, next to the source (review runs only — a task run revises nothing).
555
670
  - The only working file Claude writes in the fast path is the Stage-6 proposed MODEL-NOTES diff (`_tmp-proposed-model-notes-update.md`), cleaned up once the approval decision is resolved. The manual fallback's `_tmp-*.md` files are documented in `MANUAL-ORCHESTRATION.md`.
556
671
 
557
672
  ---
@@ -560,5 +675,5 @@ Always **rank recommendations by fit**, state the trade-off for each option, and
560
675
 
561
676
  - `MANUAL-ORCHESTRATION.md` — the **fallback path**: the hand-driven Stage 1/2/2.5/3 mechanics and the Stage-5 artifacts the engine replaced. **Read it when the engine is unavailable, too old, or misbehaving; when a seat needs a fully custom brief beyond `--critic`/`--lenses`; or when you need to inspect or intervene mid-stage.**
562
677
  - `MODEL-NOTES.md` — operating rules, per-model qualitative quirks, cost guardrail, and structural-conformance notes. **Read it before Stage 0 (council selection and launch); update qualitative notes (with approval) in Stage 6.** Quantitative reliability data (runs, avg street-cred, confirm-rate, fact-error rate) comes from `amicus council stats`, not this file. This copy is machine-local (never overwritten on update); the shipped seed lives in the amicus repo and absorbs durable lessons at release time.
563
- - `SEAT-BRIEFS.md` — the semantics of the optional council elements (critic seat, expert lenses, rebuttal round, chair verdict scale) plus the standard anti-sycophancy clause. The engine composes its own stricter-JSON variants of these headlessly; this file stays authoritative for the manual path and for what each element *means*. **Read it whenever any element is toggled on at Stage 0.**
564
- - `COUNCIL-DESIGN.md` — the design spec this skill implements (§12 covers the optional council elements). Consult it if a mechanics question arises that the skill prose does not resolve.
678
+ - `SEAT-BRIEFS.md` — the semantics of the optional council elements (critic seat, expert lenses, rebuttal round, chair closing scale) plus the standard anti-sycophancy clause. Its blocks are REVIEW wording; the task twins are engine-composed, and the file names the swaps the manual path has to make (including the ANSWER-scale addendum). The engine composes its own stricter-JSON variants of all of them headlessly; this file stays authoritative for the manual path and for what each element *means*. **Read it whenever any element is toggled on at Stage 0.**
679
+ - `COUNCIL-DESIGN.md` — the design spec this skill implements (§12 covers the optional council elements). Consult it if a mechanics question arises that the skill prose does not resolve. It predates task mode and does not describe it — §5.5 above is the authority there.
@@ -1,20 +1,51 @@
1
1
  /**
2
2
  * Bench and input resolution for the council run command.
3
3
  *
4
- * Exports parseList, sanitizeCouncilName, resolveBench, extracted verbatim
5
- * from cli-handlers-council-run.js (v4.7 PR0). ⚠️ The top-level cli*.js
6
- * name is LOAD-BEARING: the known-flags source scan covers only src/cli*.js,
7
- * and resolveBench reads args['dropped-members'].
4
+ * Exports parseList, sanitizeCouncilName, resolveBench extracted verbatim
5
+ * from cli-handlers-council-run.js (v4.7 PR0). ⚠️ v4.9 W13 split `resolveBench`
6
+ * into a private `resolveBenchCore` (the extracted body, still verbatim) plus a
7
+ * thin exported wrapper that owns the CLI's alias-shadow notice site (the MCP
8
+ * surface has its own, in mcp-council-bench.js — PR #207 round 2, A1); the
9
+ * export name, its arguments and its return shapes are unchanged.
10
+ * ⚠️ The top-level cli*.js name is LOAD-BEARING: the known-flags source scan
11
+ * covers only src/cli*.js, and resolveBenchCore reads args['dropped-members'].
8
12
  */
9
13
 
10
14
  'use strict';
11
15
 
12
16
  const { failJson, ERROR_CODES } = require('./utils/error-doc');
13
17
 
18
+ /**
19
+ * The chair a council gets when nobody names one.
20
+ *
21
+ * ⚠️ Lives HERE, in the bench/seat-resolution leaf, and is imported by
22
+ * `cli-handlers-council-run.js` (which re-exports it, so existing importers are
23
+ * unchanged) and by `mcp-council-run.js`. PR #203 round 1 (A6) needed the
24
+ * default at this seam to audit the chair; two spellings already existed and a
25
+ * third would have been the "wrong lever" mistake — one owner, three readers.
26
+ */
27
+ const CHAIR_DEFAULT = 'deepseek';
28
+
14
29
  function parseList(value) {
15
30
  return String(value).split(',').map(s => s.trim()).filter(Boolean);
16
31
  }
17
32
 
33
+ /**
34
+ * The chair this run will use: a trimmed `--chair`, else CHAIR_DEFAULT.
35
+ * Extracted so the alias audit below and the handler that enforces the
36
+ * chair-not-in-bench rule read ONE definition. @param {object} args
37
+ * @returns {string}
38
+ */
39
+ function resolveChair(args) {
40
+ return (typeof args.chair === 'string' && args.chair.trim()) ? args.chair.trim() : CHAIR_DEFAULT;
41
+ }
42
+
43
+ /** The critic seat, or null when this run has none. Same one-definition
44
+ * rationale as resolveChair. @param {object} args @returns {string|null} */
45
+ function resolveCritic(args) {
46
+ return (typeof args.critic === 'string' && args.critic.trim()) ? args.critic.trim() : null;
47
+ }
48
+
18
49
  /**
19
50
  * Sanitize the internal `--council-name` passthrough before it can reach the
20
51
  * spend ledger's `councilName` column (v4.3 Task 4 review fix, spec §7.3:
@@ -43,8 +74,12 @@ function sanitizeCouncilName(name) {
43
74
  * They have already diverged (this side has a third guard for a valueless
44
75
  * --council, and the min-seat rule lives in both callers, not here) — change
45
76
  * a validation rule on one side, change the other.
77
+ *
78
+ * ⚠️ Not exported directly — `resolveBench` below wraps it so this transport's
79
+ * alias-shadow notice has exactly ONE site. Keep new return shapes going through
80
+ * that wrapper.
46
81
  */
47
- function resolveBench(args, useJson) {
82
+ function resolveBenchCore(args, useJson) {
48
83
  const hasModels = typeof args.models === 'string' && args.models.trim();
49
84
  const hasCouncil = args.council !== undefined && args.council !== false;
50
85
  if (hasModels && hasCouncil) {
@@ -83,4 +118,61 @@ function resolveBench(args, useJson) {
83
118
  return { bench: parseList(args.models), presetName: null, droppedMembers: dm };
84
119
  }
85
120
 
86
- module.exports = { parseList, sanitizeCouncilName, resolveBench };
121
+ /**
122
+ * `resolveBenchCore` plus the CLI's alias-shadow notice site (v4.9 W13 Task B,
123
+ * BACKLOG C5). This is the shared bench-resolution helper BOTH council
124
+ * transports execute: `mcp-council-run.js` always spawns the CLI child with an
125
+ * already-expanded `--models` list (never `--council`), so the MCP path
126
+ * re-enters here exactly like a hand-typed `amicus council run`. Measured, not
127
+ * assumed — see tests/alias-shadow.test.js's header.
128
+ *
129
+ * ⚠️ EXECUTES on both transports, SURFACES on one. The child's stderr is a
130
+ * `debug.log` fd, so the line this site writes never reaches an MCP client;
131
+ * `mcp-council-bench.js :: auditBenchAliases` is the parallel site that puts it
132
+ * on the tool result (PR #207 round 2, A1). See alias-shadow.js's own docblock.
133
+ *
134
+ * Wrapped rather than called from each `return`, so the rule cannot grow a twin
135
+ * as branches are added. A rejected bench carries no `bench` key and is
136
+ * therefore silent by construction: nothing was resolved, so nothing is
137
+ * diagnosed. Diagnosis only — the returned value is byte-identical to what
138
+ * `resolveBenchCore` produced.
139
+ *
140
+ * v4.9 W13, PR #203 council round 1:
141
+ * A5 — `auditAliasShadows` (not `noteAliasShadows`) opens a fresh notice scope
142
+ * first, so a host process that resolves two councils in a row audits
143
+ * BOTH instead of silently auditing only the first. This call is reached
144
+ * exactly once per council run, which is what makes "one scope" mean
145
+ * "one run".
146
+ * A6 — the audited names are the bench PLUS the chair (explicit or default)
147
+ * and the critic. All three resolve through the same alias table, so a
148
+ * shadow on any of them is equally invisible downstream. Order matters
149
+ * only cosmetically (rows come back in the order given), and the audit
150
+ * de-dups, so a critic — which a valid run always draws from the bench —
151
+ * adds a row only when it is outside it.
152
+ */
153
+ function resolveBench(args, useJson) {
154
+ const res = resolveBenchCore(args, useJson);
155
+ if (Array.isArray(res.bench)) {
156
+ const critic = resolveCritic(args);
157
+ require('./utils/alias-shadow').auditAliasShadows(
158
+ [...res.bench, resolveChair(args), ...(critic ? [critic] : [])]);
159
+ }
160
+ return res;
161
+ }
162
+
163
+ // ⚠️ THIS ORDER IS LOAD-BEARING, and only for the docs (PR #207 round 4, B3).
164
+ // `scripts/generate-docs-helpers.js:107` keeps the FIRST FIVE names in source
165
+ // order for CLAUDE.md's Key Exports column, and this module exports six — so
166
+ // whichever name sits last is the one the generated table never shows.
167
+ // `CHAIR_DEFAULT` is DEFINED here and merely re-exported by
168
+ // cli-handlers-council-run.js, whose row (3 exports, all inside the cap) always
169
+ // listed it; the round-4 council read the definer's silence beside the
170
+ // re-exporter's mention as the docs disagreeing about where the constant lives.
171
+ // Moving it inside the cap makes the definer the visible source. The resolve
172
+ // trio stays intact and `sanitizeCouncilName` — the one name here that is a
173
+ // generic string helper rather than part of this module's headline — is the one
174
+ // that falls off the end. Nothing else depends on this order: consumers
175
+ // destructure by name.
176
+ module.exports = {
177
+ resolveBench, resolveChair, resolveCritic, CHAIR_DEFAULT, parseList, sanitizeCouncilName,
178
+ };
@@ -17,11 +17,15 @@ const { GATEWAY_MODES } = require('./utils/model-descriptor');
17
17
  // file re-exports it below so every existing require() of this path still
18
18
  // resolves it unchanged.
19
19
  const { renderRunHuman } = require('./cli-council-run-render');
20
- const { parseList, sanitizeCouncilName, resolveBench } = require('./cli-council-run-bench');
20
+ // v4.9 W13 (PR #203 round 1, A6): CHAIR_DEFAULT and the two seat resolvers moved
21
+ // to cli-council-run-bench.js so the alias audit at the resolveBench seam and the
22
+ // validation below read ONE definition of each. Re-exported at the bottom of this
23
+ // file, so `require('./cli-handlers-council-run').CHAIR_DEFAULT` still resolves.
24
+ const {
25
+ parseList, sanitizeCouncilName, resolveBench, resolveChair, resolveCritic, CHAIR_DEFAULT,
26
+ } = require('./cli-council-run-bench');
21
27
  const { applyTemplateForArgs } = require('./cli-template-args');
22
28
 
23
- const CHAIR_DEFAULT = 'deepseek';
24
-
25
29
  /**
26
30
  * Default real helpers; tests override via depsOverride (mirrors
27
31
  * cli-handlers-spend.js's realDeps()/depsOverride convention).
@@ -132,14 +136,13 @@ async function handleCouncilRun(args, depsOverride = {}) {
132
136
  message: 'Error: a council needs at least 2 seats (fanout semantics)' });
133
137
  }
134
138
 
135
- const chair = (typeof args.chair === 'string' && args.chair.trim())
136
- ? args.chair.trim() : CHAIR_DEFAULT;
139
+ const chair = resolveChair(args);
137
140
  if (bench.includes(chair)) {
138
141
  return failJson(useJson, { code: ERROR_CODES.BAD_ARGS,
139
142
  message: `Error: chair '${chair}' is a bench seat — the chair must not review${packSuffix('chair')}`,
140
143
  hint: `pick a chair outside --models (default: ${CHAIR_DEFAULT}), or remove '${chair}' from the bench` });
141
144
  }
142
- const critic = (typeof args.critic === 'string' && args.critic.trim()) ? args.critic.trim() : null;
145
+ const critic = resolveCritic(args);
143
146
  if (critic && !bench.includes(critic)) {
144
147
  return failJson(useJson, { code: ERROR_CODES.BAD_ARGS,
145
148
  message: `Error: critic '${critic}' must be one of the bench seats${packSuffix('critic')}`,
@@ -189,6 +192,13 @@ async function handleCouncilRun(args, depsOverride = {}) {
189
192
  return failJson(useJson, { code: ERROR_CODES.BAD_ARGS, message: tagCheck.error });
190
193
  }
191
194
  }
195
+ // v4.9 W5.2 (spec §5.3): emit-when-'task' everywhere — 'review' is the
196
+ // default spelled out and is never materialized on the options object.
197
+ if (args.intent !== undefined && args.intent !== 'review' && args.intent !== 'task') {
198
+ return failJson(useJson, { code: ERROR_CODES.BAD_ARGS,
199
+ message: 'Error: --intent must be review or task',
200
+ hint: "review (the default) may be omitted; only '--intent task' changes the run" });
201
+ }
192
202
  let runId;
193
203
  if (args['run-id']) {
194
204
  const check = validateTaskId(String(args['run-id']));
@@ -238,6 +248,8 @@ async function handleCouncilRun(args, depsOverride = {}) {
238
248
  template: templateMeta, // F9 (v4.5): null when no --template; additive on the run.json seed (run-state.js).
239
249
  pack: packRecord, // v4.5 Task 12 (B7/F5): null when no --pack; additive on the run.json seed (run-state.js).
240
250
  tag: args.tag, // v4.7 F8: undefined when no --tag; Task 3 stores it on the run.json seed.
251
+ // v4.9 W5.2: o.intent is 'task' or ABSENT, never 'review' (validated above).
252
+ ...(args.intent === 'task' ? { intent: 'task' } : {}),
241
253
  droppedMembers: benchRes.droppedMembers, // v4.5 Wave 2: [] when nothing dropped; additive on the run.json seed (run-state.js).
242
254
  // v4.1 §4.5b/§4.5d. `--claude-review` is resolved here but VALIDATED by the
243
255
  // engine's preflightClaudeReview (run-assemble.js): the reserved-seat and