ai-consultants 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -107,6 +107,9 @@ ENABLE_DEEPSEEK=false
107
107
  # CLAUDE_API_URL=https://api.anthropic.com/v1/messages
108
108
  # MISTRAL_API_URL=https://api.mistral.ai/v1/chat/completions
109
109
  # QWEN3_API_URL=https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation
110
+ # Token Plan alternative (OpenAI-compatible, needs QWEN3_FORMAT=openai and a
111
+ # Token Plan key in QWEN3_API_KEY; pass the FULL path, it is used verbatim):
112
+ # https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/chat/completions
110
113
 
111
114
  # =============================================================================
112
115
  # MODEL CONFIGURATION (June 2026 premium defaults)
@@ -148,6 +151,14 @@ CLAUDE_CMD=claude
148
151
  QWEN3_MODEL=qwen3.7-max
149
152
  QWEN3_TIMEOUT=180
150
153
  QWEN3_CMD=qwen
154
+ # Reasoning effort. API mode ONLY - the qwen CLI has no effort flag, so in CLI
155
+ # mode this is ignored (with a warning); set model.reasoningEffort in
156
+ # ~/.qwen/settings.json instead. Accepted: none|minimal|low|medium|high|xhigh|max
157
+ # (the provider's own enum). The valid subset depends on the model:
158
+ # qwen3.8-max-preview takes all of them EXCEPT 'none' - thinking cannot be
159
+ # disabled on it - and defaults to xhigh. An unsupported combination is
160
+ # rejected by the provider with a 400, not silently ignored.
161
+ # QWEN3_REASONING_EFFORT=
151
162
 
152
163
  # =============================================================================
153
164
  # API-BASED CONSULTANT CONFIGURATION
@@ -174,7 +185,12 @@ MINIMAX_MODEL=MiniMax-M2.7
174
185
  MINIMAX_TIMEOUT=180
175
186
  MINIMAX_API_URL=https://api.minimax.io/v1/chat/completions
176
187
 
177
- # Response formats for API-based consultants
188
+ # Wire format for a consultant's API mode. Honored since v2.25 (previously
189
+ # declared but never read). Values: google_ai|anthropic|qwen|openai; an
190
+ # unrecognized value falls back to the consultant's default with a warning.
191
+ # Change this only to reach an endpoint that speaks a different protocol than
192
+ # the provider's own - e.g. QWEN3_FORMAT=openai for Qwen Cloud Token Plan,
193
+ # which is OpenAI-compatible while DashScope is not. See docs/RECIPES.md.
178
194
  QWEN3_FORMAT=qwen
179
195
  GLM_FORMAT=openai
180
196
  GROK_FORMAT=openai
package/CLAUDE.md CHANGED
@@ -6,7 +6,7 @@ AI Consultants is a multi-model AI deliberation system that queries up to 11 AI
6
6
 
7
7
  **Self-Exclusion**: The invoking agent is automatically excluded from the panel to prevent self-consultation. Claude Code won't query Claude, Codex CLI won't query Codex, etc.
8
8
 
9
- **Version**: 2.23.0
9
+ **Version**: 2.25.0
10
10
 
11
11
  ## Distribution
12
12
 
@@ -719,15 +719,21 @@ Manual lint of the full repo: `npm run lint`. Bypass the hook: `git commit --no-
719
719
 
720
720
  Every version bump **must** include a release note in `docs/releases/v<VERSION>.md`. Use the template below.
721
721
 
722
+ **The whole flow is automated by the maintainer's `ai-consultants-release` skill — run `/ai-consultants-release <VERSION>` from anywhere inside this checkout.** It orchestrates the steps below and refuses to proceed when a surface is missing.
723
+
724
+ That skill deliberately lives **outside this repository**, at `~/.claude/skills/ai-consultants-release/`, and is not distributed. `scripts/install.sh` git-clones this entire repo into `~/.claude/skills/ai-consultants`, so anything under a `.claude/skills/` directory here reaches every `curl | bash` user — and release tooling that pushes tags and deploys the showcase site must not. The skill's own `references/surfaces.md` is the authoritative map of every release surface; this section is the summary.
725
+
726
+ Consequence worth knowing: the skill's `finalize.sh` re-verifies the same version surfaces `scripts/release.sh` owns, and the two now live in different places with nothing making them drift together. `finalize.sh` compares its arity against that script's `SURFACES` array on every run and aborts if they disagree — so **adding or removing a surface in `scripts/release.sh` will correctly fail the next release until the skill is updated to match.** That is the tradeoff for not shipping release tooling to users.
727
+
722
728
  ### Steps
723
729
 
724
- 1. **Update version** in all files: `package.json`, `scripts/config.sh` (`AI_CONSULTANTS_VERSION`), `SKILL.md` (frontmatter + title), `README.md` (title + badge), `CLAUDE.md` (`**Version**`), `docs/cost_rates.json`, `docs/COST_RATES.md` (title)
725
- 2. **Add `## Changelog` entry** in CLAUDE.md (developer-facing, long-form, rationale + line-level commentary)
726
- 3. **Add `CHANGELOG.md` entry** at the top of `CHANGELOG.md` (Keep a Changelog format: `## [VERSION] - YYYY-MM-DD` with `### Added/Changed/Fixed/Removed/Deprecated/Security` subsections — user-facing, one-line bullets)
727
- 4. **Create `docs/releases/v<VERSION>.md`** using the template below (highlights + upgrade guide — for GitHub releases / users)
728
- 5. **Update workspace sync surfaces** if applicable: `aiconsultants.sh/index.html` (`softwareVersion` schema + badge), `../CLAUDE.md` workspace guide ("Latest at time of last sync" + Recent release line)
729
- 6. **Run tests**: `npm test` must pass before committing
730
- 7. **Commit, push**, and optionally create a GitHub release pointing to the release note
730
+ 1. **Preflight**: `~/.claude/skills/ai-consultants-release/scripts/preflight.sh <VERSION>` — read-only gate on both repos (branch, cleanliness, origin sync, forward semver, free tag, tooling)
731
+ 2. **Bump the version**: `scripts/release.sh <VERSION>` — rewrites 9 anchored surfaces across 7 files (`package.json`, `scripts/config.sh`, `SKILL.md` ×2, `README.md` ×2, `CLAUDE.md`, `docs/cost_rates.json`, `docs/COST_RATES.md`), validates them, and runs `npm test` + `npm run lint`. Never commits, tags, or publishes.
732
+ 3. **Write the three changelogs** (hand-written — see "Why three changelog surfaces?" below): `CHANGELOG.md` entry at the top, `## Changelog` entry in this file, and `docs/releases/v<VERSION>.md`
733
+ 4. **Update workspace sync surfaces**: `../CLAUDE.md` workspace guide ("Latest at time of last sync" + Recent release line)
734
+ 5. **Finalize**: `~/.claude/skills/ai-consultants-release/scripts/finalize.sh <VERSION> --message-file … --tag-message-file …` — re-verifies all 9 version surfaces *and* the 3 doc surfaces, re-runs the gate, then commits, creates the annotated tag, and pushes both
735
+ 6. **Publishing is automatic**: pushing the `v*` tag triggers `.github/workflows/publish.yml`, which re-runs the gate, verifies the tag matches `package.json`, publishes to npm via **Trusted Publishing (OIDC — no `NPM_TOKEN`)** with provenance, and creates the GitHub release from the release note. Both terminal steps are idempotent, so a failed run can be re-run after a fix — **never move or delete a pushed tag**, and never `npm publish` from a workstation.
736
+ 7. **Sync the showcase site last**: `~/.claude/skills/ai-consultants-release/scripts/sync_site.sh <VERSION> --message-file …` waits for npm to actually serve the version, bumps `index.html` (`softwareVersion` schema + badge), then commits and pushes. Editorial copy (roster, feature cards, presets table, install commands) is hand-edited before running it. The wait enforces the npx timing rule: a subcommand advertised as `npx ai-consultants <cmd>` before publication does not error — `bin/` routes an unknown argument to `consult_all.sh`, starting a real **billable** consultation.
731
737
 
732
738
  **Why three changelog surfaces?** They serve different audiences:
733
739
  - `CLAUDE.md ## Changelog` — what the *next maintainer* needs (file/line references, rationale, latent bugs uncovered)
@@ -795,6 +801,38 @@ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/s
795
801
 
796
802
  ## Changelog
797
803
 
804
+ ### v2.25.0
805
+ - **`tokens_used` was hardcoded to 0, so the cost report priced pipeline metadata and nothing else.** `build_response_metadata` (`lib/common.sh`) emitted a literal `tokens_used: 0` and was the only writer of that field in the repo; `calculate_session_cost` (`lib/costs.sh:248`) multiplies it by the model rate. Structurally zero for all 11 consultants, both transports, since the field existed. **Do not describe this as "every session reported $0.00"** — I did, in three changelogs, and it is wrong: the pipeline files caught by the same unfiltered glob have no `tokens_used` key at all, so they took the `// 1000` fallback at the default rate (~$0.009 each). Measured against HEAD on a realistic three-consultant directory, v2.24 reports **$0.0270**, entirely phantom. The reported number tracked how many pipeline stages ran. Worse in API mode: `run_api_mode_query` (`lib/api_query.sh:154`) called `extract_token_usage`, which computes the right number (verified: extracts 85 from a live Token Plan body), `log_debug`'d it, and dropped it on the floor.
806
+ - **Four paths write response metadata**, which is why the fix is spread out: `process_consultant_response` (codex/cursor/gemini/kimi/minimax/qwen3), direct builder calls (claude/mistral), `run_api_consultant` (glm/deepseek/grok), and the error path. All converge on `build_response_metadata`, so that is where the new `tokens`/`tokens_source` parameters land; the builders thread them through and default to `0`/`unknown` so pre-existing 5-arg calls still work.
807
+ - **The measured split travels in a module variable, `_API_TOKEN_SPLIT`.** `run_api_mode_query` publishes it via `set_api_token_split`; `resolve_response_tokens` reads it; the CALLER clears it via `clear_api_token_split`. The asymmetry is the whole subtlety: `resolve_response_tokens` runs inside `$(...)`, so it can *read* the variable (subshells inherit) but an assignment there dies with the subshell — it cannot clear it. Its callers are plain statements and can. **An earlier cut used a temp-file sidecar justified by the claim that some callers of `run_api_mode_query` run in a command substitution. That claim was false** — all six call sites (`query_{claude,codex,gemini,mistral,qwen3}.sh` and `run_api_consultant`) are plain statements — and it bought a cross-function file lifecycle with five cleanup sites for nothing. Caught by `/code-review max`, which also noted the false comment had been promoted to binding policy in this very file. The real subshell constraint applies to `calculate_session_cost` / `format_cost_caveats`, which ARE always `$(...)` and therefore take their input as arguments.
808
+ - **The provider's prompt/completion SPLIT is kept, not just the total.** `extract_token_split` (api.sh) preserves it and `metadata.tokens_input`/`tokens_output` record it; `calculate_session_cost` prefers them over the 60/40 fallback. Collapsing to a total and re-splitting it 60/40 overstated API-mode cost ~2.7x on a realistic 20000-prompt/500-completion gpt-5.5 call ($0.3075 vs $0.1150) — while still labeling it `measured`, the one value that is supposed to mean "the provider's own figure". The 60/40 guess survives only for CLI mode, which has no split to record.
809
+ - **CLI mode estimates, and says so.** No CLI exposes token counts, and CLI-first is mandatory since v2.21, so leaving CLI at 0 would have fixed almost nothing. `resolve_response_tokens` falls back to `estimate_tokens` (the pre-existing 4-chars-per-token helper at `common.sh:864`, previously unused for this) over prompt + reply. The six `process_consultant_response` call sites pass `"${#FULL_QUERY}"`; claude/mistral/`run_api_consultant` resolve inline. `metadata.tokens_source` records `measured|estimated|unknown` so a consumer can tell the difference — added to both `lib/schema.json` and `docs/JSON_SCHEMA.md`.
810
+ - **Budget impact is narrower than it first looks.** `enforce_budget` is called four times in `consult_all.sh`. The first two (`:345`, `:451`) pass literal `0` as current cost and project from `ESTIMATED_COST`, derived from `CONTEXT_SIZE` — those always worked. Only `:755` and `:862` pass `CURRENT_COST` from `calculate_session_cost`, and that was the zero. Do not describe this as "budget enforcement never triggered"; it did, off estimates.
811
+ - **Five `*_FORMAT` flags were dead since v2.6.** `QWEN3_`/`GLM_`/`GROK_`/`DEEPSEEK_`/`MINIMAX_FORMAT` are declared in `config.sh:174,185,195,205,216` and documented in `.env.example:178-182`, but `get_api_format()` (`common.sh:396`) hardcoded the mapping and `api_query.sh:50` is its only consumer — ten occurrences, all definitions, zero reads. Same shape as the `ENABLE_REFLECTION` removal in v2.23.0, but the opposite call: that capability was covered elsewhere, this one is not — there is no other route to a non-DashScope Qwen endpoint, so deleting the flag would have forced a Qwen-only hardcode. Now honored, with validation against `google_ai|anthropic|qwen|openai` and fallback-with-warning on anything else.
812
+ - **Note the deliberate asymmetry**: a bad `*_FORMAT` falls back to the default; a bad `*_REASONING_EFFORT` fails. Format has a correct per-consultant default to fall back to. Effort does not — silently substituting the model's default when the user asked for `xhigh` *is* the silent-no-op bug the knob exists to avoid.
813
+ - **The effort enum came from the API, not the docs, and the docs were wrong.** Every public write-up on `qwen3.8-max-preview` reported `low|high|xhigh`. A live 400 reports `'reasoning_effort' must be one of: 'none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'`. The first implementation allowed five values and would have rejected `minimal`, which the model accepts. The per-model subset is intentionally NOT enforced in shell — `none` is rejected by this model at a second layer (`The value of the enable_thinking parameter is restricted to True`), which is a model fact that belongs to the provider, not to us. Hardcoding per-model facts is what produced the stale `MODEL_USED` bugs also fixed here.
814
+ - **Qwen 3.8 is opt-in and stays that way.** It needs a Token Plan subscription (`https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1`, its own key, credit billing) and is not on DashScope, so swapping `config.sh:668` would break the Qwen consultant for every user without one. `test_suite.sh` now asserts the negative explicitly (`premium != qwen3.8-max-preview`) so the contract is self-documenting. `references/affinity.json` is untouched **by construction**: affinity and capability axes are keyed on the consultant name, never the model id, so changing `QWEN3_MODEL` implies no affinity change and `test_routing_parity.sh` needed no edit.
815
+ - **No invented cost rate.** `qwen3.8-max-preview` is credit-billed with no published per-token price, so it is catalogued at 0/0 plus a new `unpriced_models` list. Doing nothing was NOT neutral: `calculate_session_cost` never calls `resolve_model_for_cost`, so an absent model falls to the `*)` arm of `get_input_cost_per_1k` (`costs.sh:163`) at 0.005/0.015 — ~4x the real qwen3.7-max input rate, reported confidently. The same fallback sits in `roster_calibrate.sh:34-35`, so a measured cost axis would have been corrupted by a model that is not billed per token at all. Run `run_calibration.sh` only after this entry exists.
816
+ - **Verified live against Token Plan**, not just fixtures: endpoint 200, structured response through the real adapter (confidence 9/10, persona applied — not the confidence-5 stub the Gemini fence bug produced in v2.15.0), `622 measured` tokens in API mode, `311 estimated` in CLI mode via Codex, and a full `consult_all.sh` run reporting `$0.0550 [token counts estimated for 3 of 3]` where it would previously have said `0¢`.
817
+ - **Making `tokens_used` real turned five latent mis-billings into money, all found by `/code-review max` (55 agents, 52 verified findings → 15 defects).** Every one was harmless while the field was hardcoded 0. `calculate_session_cost` globbed `*.json` unfiltered, so `voting.json`/`orchestration.json`/`synthesis.json` were each priced at the `// 1000` fallback times the default rate (measured 2.8x inflation); a cache hit replayed the original response's metadata and billed at full price ($0.52 for zero API calls); debate rounds live in `round_N/` subdirectories the non-recursive glob never saw; and `<consultant>_escalated.json` was left beside the file it had just been copied over, counting one query twice. New `_billable_response_files` is the single gate for all four — **any future loop over the responses dir should use it**, not a bare glob. `format_cost_caveats` additionally counted `unknown` (i.e. failed) responses in the "estimated for N of M" denominator, which made failures read as provider-measured.
818
+ - **The reasoning-effort knob was a silent no-op on the exact path the docs steer users to.** `${AGENT}_REASONING_EFFORT` was resolved inside the `openai` arm of the format switch, but `config.sh:174` defaults `QWEN3_FORMAT=qwen` — so `QWEN3_USE_API=true` + `QWEN3_REASONING_EFFORT=high` against DashScope built a body with no effort field, emitted no warning, and never validated the value. Resolution is now hoisted **above** the switch so every wire format sees it, and the three formats that cannot carry it (`qwen`, `google_ai`, `anthropic`) say so. Related: `validate_reasoning_effort`'s `return 1` propagated to a bare `run_api_mode_query` call under `set -euo pipefail`, killing the query script before `exit_code=$?` — no error envelope, a 0-byte output file, and the consultant silently vanishing from the panel instead of being reported as failed. All five API-capable query scripts now use the explicit `if ! …; then` guard `run_query` already used; this also fixes the same pre-existing hole for network failures.
819
+ - **Cataloguing the credit-billed model at 0/0 fixed the estimator and broke the calibrator.** `roster_calibrate.sh` falls back to `FALLBACK_IN/OUT` only when the catalog lookup *misses*; with a 0/0 entry the lookup now succeeds, so every qwen3.8-max-preview response cost 0.00000000 and the "cheapest = 10" rank-normalizer scored Qwen3 the cheapest consultant on the panel — which `--write` would have persisted into `affinity.json`, preferentially routing work to a model whose real consumption is invisible there. It now sources `costs.sh` and skips unpriced models from the cost axis: no data beats wrong data.
820
+ - **Applying the review fixes introduced four more bugs, every one caught by a test rather than by reading.** Worth recording because they are all the same shape — a change that *looks* applied but is not: (1) the `roster_calibrate.sh` guard called `is_unpriced_model` in a script that did not source `costs.sh`, so it would have been a silent no-op; (2) the shared CLI effort warning fired in API mode too, where the setting is honored, until it was made self-guarding; (3) `warn_effort_ignored_in_cli` used `is_api_mode "$agent" && return 0` — an &&-list that short-circuits returns non-zero as a statement and kills the caller under `set -e`, **the exact v2.22.0 `prompt_value` bug re-created**; and (4) `build_fallback_response` used `$tokens_in` without declaring the local, so under `set -u` **every unstructured reply would have aborted for all 11 consultants**. Only (4) was caught by an existing test — `test_query_kimi.sh`, whose stub happens to return an unstructured envelope — which means the fallback path had no direct coverage at all. `test_response_tokens` now exercises all three builders at both the legacy and the full arity under `set -u`, plus the `set -e` safety of the warning helper.
821
+ - **`${#FULL_QUERY}` is characters, `estimate_tokens` counts bytes.** The estimate summed the two, under-counting multibyte input roughly threefold (`日本語のテスト` is 7 chars, 21 bytes). `resolve_response_tokens` now takes the prompt TEXT and runs both halves through `estimate_tokens`, so the units match.
822
+ - **Three suites were not hermetic against `LOG_LEVEL`, and it read as a flake.** One `npm test` run reported `test_api_transport.sh` failed and could not be reproduced. It is not flaky: `_log` filters by `LOG_LEVEL`, so `log_warn` is silent at `ERROR`, and any assertion matching on warning output fails for that environment and passes everywhere else — deterministic per-environment, exactly the v2.23.0 release-gate shape. Found by sweeping every suite at DEBUG/INFO/WARN/ERROR rather than by re-running and hoping. Two were pre-existing: `test_context_optimization.sh` (3 assertions, **two of them the v2.17.2 secret-exfiltration guards** — a security regression test that depended on the caller's verbosity) and `test_update_clis.sh` (2 assertions). All three now `export LOG_LEVEL=INFO` before sourcing `config.sh`, which honors an ambient value. **If you add an assertion that matches on log output, pin the level in the suite.** Note the earlier "all assertions passed" reading was wrong — it came from a `grep -A40` truncated before the summary line, not from the actual output.
823
+ - **`npm test` = 22 suites.** New `test_api_transport.sh` (41 assertions) exists mainly as a back-compat gate: `get_api_format` for all 11 consultants and a byte-identical assertion on `build_openai_request`, which six consultants share. That assertion was written and confirmed green against the *unmodified* function before either was touched — the md5 of the default body is unchanged end to end.
824
+
825
+ ### v2.24.0
826
+ - **Stale slash commands are finally removed on install/update, on both entry points.** `scripts/install.sh` and `bin/ai-consultants install` only ever `cp`'d `.claude/commands/*.md` into `~/.claude/commands`, never pruning — so a command deleted from the repo lived on the user's machine indefinitely. Measured on a real installation: **14 `ai-consultants:*.md` installed, 4 of which this repo still ships.** The other ten were dropped by `9ae73e3` (v2.10.0, "command consolidation") and had been dead for 13 releases. Not inert — they stay invocable, and `config-features.md` tells the user to set `ENABLE_REFLECTION`, removed in v2.23.0 and now rejected by `configure --set` with a non-zero exit. New `prune_removed_commands()` in `install.sh` plus an inline equivalent in `bin/ai-consultants`; both scoped to the `ai-consultants:*.md` namespace. **Known and accepted bound**: within that namespace it also removes a user-authored command, because nothing distinguishes it from one of ours deleted upstream. Documented in `CHANGELOG.md` rather than worked around.
827
+ - **`bin/ai-consultants install` was initially missed** — the first cut of the fix landed only in `install.sh`, leaving npx users (the path the shipped `bin` help and `docs/releases/v2.19.2.md` advertise) with exactly the stale commands the fix targeted. Caught by the `/code-review max` pass, not by tests.
828
+ - **`install.sh` gained a define-only source guard, and its first placement was destructive.** The guard (`AI_CONSULTANTS_INSTALL_DEFINE_ONLY`) exists so `test_install.sh` can source the file for its helpers without the installer running. It was placed at line 240 — *below* the argument parser (line 145) and *below* the uninstall block (line 179). A sourced script inherits the **caller's** positional parameters, so `bash scripts/test_install.sh --uninstall` had `install.sh` parse the suite's own flag and execute the real uninstall: `rm -rf` on the installed skill plus every `~/.claude/commands/ai-consultants*.md`. Reproduced in a sandboxed `HOME` with a canary file before fixing, and re-verified after. The guard and `prune_removed_commands()` now sit above the parser, with the invariant stated inline: nothing above the guard may act, and anything below it must be safe to skip. Milder instance of the same root cause: `./scripts/test_install.sh --verbose` exited 1 printing `install.sh`'s help.
829
+ - **`scripts/release.sh` dropped from the npm tarball** (`package.json` `files` gains `!scripts/release.sh`). It is the last maintainer-only artifact in the package now that `.claude/skills/` is gone; `bin/ai-consultants` does not route it, so `npx ai-consultants release` reaches the consult fallback, not the script. **Deliberately kept** after checking their consumers: `roster_audit.sh` (backs the shipped `roster-audit` slash command and `doctor --roster-audit`), `roster_calibrate.sh` / `taste_elo.sh` / `run_calibration.sh` (documented for users in the shipped `docs/SMART_ROUTING.md`), `install-hooks.sh` (wired to a `package.json` script, and `CHANGELOG.md` records it as safe for tarball consumption).
830
+ - **Release tooling moved out of the repository entirely** (`6a4e70d`, `95d2e29`, `384a1cd`). `scripts/install.sh` git-clones the whole repo into `~/.claude/skills/ai-consultants`, making `.claude/skills/` a distribution channel — confirmed on a real install, which carried both `release` and `verify`. Shipping tooling that pushes tags and deploys the showcase site to end users is both useless and dangerous, so the release and verify skills now live in the maintainer's `~/.claude/skills/`, and this repo has **no** `.claude/skills/` directory. Keep it that way; if a future skill must be in-tree, prune it in `install.sh` after the clone. New `.github/workflows/publish.yml` publishes to npm via **Trusted Publishing (OIDC, no `NPM_TOKEN`)** on a `v*` tag push and creates the GitHub release from `docs/releases/v<VERSION>.md`; it re-runs the full gate because `ci.yml` only covers branches and PRs, and verifies all nine version surfaces server-side (checking only `package.json` would let a hand-bumped tag publish a package whose `config.sh`/`SKILL.md`/`README.md` still report the previous version — unfixable, since npm forbids republishing).
831
+ - **Two test-harness weaknesses fixed, both found by breaking them on purpose.** `lib/test_helpers.sh::test_summary` now fails a suite that ran zero assertions: `run_test` takes `"<name>" <function>`, and given the function alone the name absorbs it, `shift` empties the list, `"$@"` expands to nothing — no test runs and the suite exits 0. That is the v2.23.0 `test_functions.sh` fix one level up (that one scored a test by its last assertion; this one scored a suite that had none). The guard only covers suites calling `test_summary`, so `test_routing_parity.sh` got the same check inline, and `test_set_e_safety.sh` — a static lint with no assertion counter — now asserts its scan corpus is non-empty, since a lint that scans nothing reports "zero occurrences" and passes forever.
832
+ - **`test_configure.sh`'s parameter-contract scan correctly flagged the new test hook.** It walks every `${VAR:-}` in non-test scripts and demands each be a declared config parameter; `AI_CONSULTANTS_INSTALL_DEFINE_ONLY` joins the existing internal hooks (`SKIP_GATE`, `ROOT`, `FORCE`) in its exclusion list. The test did its job on a change it had no knowledge of.
833
+ - **New `scripts/test_install.sh`** (12 assertions, `npm test` now 21 suites): prunes what upstream dropped, keeps what it ships, ignores other tools' commands and lookalike filenames, no-ops on a fresh install (glob matches nothing) and on an absent commands directory, and asserts the define-only guard is still present so a refactor that drops it fails loudly rather than making `npm test` clone.
834
+ - **Cosmetic, found while sweeping for dead code**: three SKIP lines in `test_context_optimization.sh` used plain `echo` with `${C_YELLOW}`, which under bash prints a literal `\033[33m` instead of colour (zsh's builtin `echo` interprets escapes, which masked it during the first check). Now `echo -e`. `C_YELLOW` itself is **not** dead despite shellcheck SC2034 — that suite sources `test_helpers.sh` and uses it; shellcheck analyses one file at a time, which is why its message says "or export if used externally".
835
+
798
836
  ### v2.23.0
799
837
  - **`lib/reflection.sh` deleted (452 lines) and `ENABLE_REFLECTION` / `REFLECTION_CYCLES` removed.** The module was never sourced — `grep -rn 'reflection\.sh' scripts/` matched only the file's own header — so the generate-critique-refine loop had never executed. The two flags were declared at `config.sh` and exported by four presets (`high-stakes`, `max_quality`, `fast`, `_disable_all_consultants`) with no consumer anywhere. It had also aged past the codebase: `_exec_consultant` hardcoded a Gemini/Codex/Mistral case (3 of 11 consultants; the rest hit `*) return 1`) and its Gemini branch still used the pre-`agy` CLI syntax retired in v2.15.0 — so wiring it up as written would have produced partial reflection with a broken Gemini path. Removed rather than implemented because v2.16's `converge`/`adversarial` shapes already run critique-refine off measured consensus rather than a fixed cycle count. Worth noting for archaeology: the module was *maintained* in at least three review passes (v2.15.1 de-fencing, the `--model` parity change, the v2.10.9 `((var++))` sweep) — effort spent on code that never ran.
800
838
  - **The release gate had been red since 2.22.0 and nobody could see it.** `scripts/release.sh:26` sources `lib/common.sh` → `config.sh` → `load_user_config` *before* `npm test` at line 194, so `_apply_env_file`'s `export "$key=$value"` put the maintainer's entire `~/.config/ai-consultants/.env` into the test environment. Two suites were not hermetic against that, and the first aborted the run before the second could surface:
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # AI Consultants v2.23.0
1
+ # AI Consultants v2.25.0
2
2
 
3
3
  > **A harness for every question.** A panel of up to 11 frontier models that writes its own playbook per question — fan out, debate to convergence, cross-examine under adversarial review, or run a tournament — and checks its work before it reaches you.
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.23.0-blue.svg)](https://github.com/matteoscurati/ai-consultants)
5
+ [![Version](https://img.shields.io/badge/version-2.25.0-blue.svg)](https://github.com/matteoscurati/ai-consultants)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
  [![Claude Code Skill](https://img.shields.io/badge/Claude%20Code-Skill-orange.svg)](https://docs.anthropic.com/en/docs/claude-code/skills)
8
8
  [![GitHub stars](https://img.shields.io/github/stars/matteoscurati/ai-consultants?style=social)](https://github.com/matteoscurati/ai-consultants)
package/SKILL.md CHANGED
@@ -5,10 +5,10 @@ license: MIT
5
5
  compatibility: Requires bash, jq, and at least 2 AI CLI tools (agy, codex, vibe, etc.). macOS and Linux.
6
6
  metadata:
7
7
  author: matteoscurati
8
- version: 2.23.0
8
+ version: 2.25.0
9
9
  ---
10
10
 
11
- # AI Consultants v2.23.0 - AI Expert Panel
11
+ # AI Consultants v2.25.0 - AI Expert Panel
12
12
 
13
13
  **A harness for every question.** Convene a panel of AI "consultants" for coding questions — and let the engine pick *how* they deliberate: a quick read, a convergence loop, an adversarial refutation gate, a tournament, or an exhaustive sweep. Each consultant has a **configurable persona** that shapes its analysis.
14
14
 
@@ -185,6 +185,25 @@ EOF
185
185
  exit 1
186
186
  fi
187
187
  mkdir -p "$COMMANDS_DST"
188
+
189
+ # Prune commands this project no longer ships, before copying the current
190
+ # set in. Copying alone leaves a removed command installed forever: the
191
+ # v2.10.0 consolidation dropped ten, and config-features among them still
192
+ # tells users to set ENABLE_REFLECTION, which v2.23.0 removed and
193
+ # `configure --set` now rejects. scripts/install.sh does the same for the
194
+ # curl | bash path — both entry points must prune or npx users keep them.
195
+ pruned=0
196
+ for f in "$COMMANDS_DST"/ai-consultants:*.md; do
197
+ [ -e "$f" ] || continue
198
+ if [ ! -f "$COMMANDS_SRC/$(basename "$f")" ]; then
199
+ rm -f "$f"
200
+ pruned=$((pruned + 1))
201
+ fi
202
+ done
203
+ if [ "$pruned" -gt 0 ]; then
204
+ echo "Pruned $pruned slash command(s) removed in a previous release"
205
+ fi
206
+
188
207
  copied=0
189
208
  for f in "$COMMANDS_SRC"/ai-consultants:*.md; do
190
209
  [ -f "$f" ] || continue
@@ -1,4 +1,4 @@
1
- # Cost Rates - AI Consultants v2.23.0
1
+ # Cost Rates - AI Consultants v2.25.0
2
2
 
3
3
  This page documents the per-token rates used by the cost tracking system.
4
4
 
@@ -32,6 +32,9 @@ The schema is defined in `scripts/lib/schema.json` following JSON Schema Draft-0
32
32
  "debate": {...},
33
33
  "metadata": {
34
34
  "tokens_used": 1500,
35
+ "tokens_source": "measured",
36
+ "tokens_input": 1200,
37
+ "tokens_output": 300,
35
38
  "latency_ms": 2345,
36
39
  "model_version": "gemini-3.1-pro-preview",
37
40
  "timestamp": "2024-01-14T12:34:56Z"
@@ -188,6 +191,9 @@ Present only if Multi-Agent Debate is enabled (round >= 2):
188
191
  {
189
192
  "metadata": {
190
193
  "tokens_used": 1500,
194
+ "tokens_source": "measured",
195
+ "tokens_input": 1200,
196
+ "tokens_output": 300,
191
197
  "latency_ms": 2345,
192
198
  "model_version": "gemini-3.1-pro-preview",
193
199
  "timestamp": "2024-01-14T12:34:56Z"
@@ -197,7 +203,10 @@ Present only if Multi-Agent Debate is enabled (round >= 2):
197
203
 
198
204
  | Field | Type | Description |
199
205
  |-------|------|-------------|
200
- | `tokens_used` | integer | Tokens consumed (estimate) |
206
+ | `tokens_used` | integer | Tokens consumed. Provider-reported in API mode; locally approximated in CLI mode |
207
+ | `tokens_source` | string | `measured` \| `estimated` \| `unknown` — how `tokens_used` was obtained |
208
+ | `tokens_input` | integer | Prompt tokens, when the provider reported them (API mode only) |
209
+ | `tokens_output` | integer | Completion tokens, when the provider reported them (API mode only) |
201
210
  | `latency_ms` | integer | Response time in ms |
202
211
  | `model_version` | string | Exact model version |
203
212
  | `timestamp` | string | ISO 8601 timestamp |
@@ -225,6 +234,28 @@ Present only if Multi-Agent Debate is enabled (round >= 2):
225
234
  "minor" | "moderate" | "major"
226
235
  ```
227
236
 
237
+ ### metadata.tokens_source
238
+
239
+ ```
240
+ "measured" | "estimated" | "unknown"
241
+ ```
242
+
243
+ - `measured` — the provider's own usage figures, available in API mode.
244
+ - `estimated` — a local 4-chars-per-token approximation over prompt + reply.
245
+ CLI-backed consultants report no token counts, so this is what they get; it
246
+ is the majority case, since CLI is the default transport.
247
+ - `unknown` — not recorded (error responses, and callers on the pre-v2.25.0
248
+ metadata signature).
249
+
250
+ When `tokens_input`/`tokens_output` are present, price on those rather than
251
+ splitting `tokens_used`: output rates run several times input rates and
252
+ consultations are large-context/short-reply, so a fixed split overstates cost
253
+ substantially.
254
+
255
+ Consumers that add up cost should read this field: a total built partly on
256
+ estimates is not a measurement. `consult_all.sh` discloses the split on its
257
+ session-cost line for that reason.
258
+
228
259
  ## Confidence Score Guidelines
229
260
 
230
261
  | Score | Meaning |
@@ -297,6 +328,7 @@ jq 'has("consultant") and has("response") and has("confidence")' output.json
297
328
  },
298
329
  "metadata": {
299
330
  "tokens_used": 1250,
331
+ "tokens_source": "estimated",
300
332
  "latency_ms": 1850,
301
333
  "model_version": "gemini-3.1-pro-preview",
302
334
  "timestamp": "2024-01-14T12:34:56Z"
package/docs/RECIPES.md CHANGED
@@ -268,3 +268,69 @@ ai-consultants update-clis --dry-run
268
268
  For every available variable and its default, see
269
269
  [`references/configuration.md`](../references/configuration.md). The executable
270
270
  source of truth is [`scripts/config.sh`](../scripts/config.sh).
271
+
272
+ ## Qwen via Qwen Cloud Token Plan (opt-in, preview)
273
+
274
+ `qwen3.8-max-preview` is **not** served by DashScope/ModelStudio. It is reachable
275
+ only through a Qwen Cloud **Token Plan** subscription, which has its own base URL
276
+ and its own API key. This is opt-in: nothing below is a default, and the panel's
277
+ premium Qwen model remains `qwen3.7-max`.
278
+
279
+ Understand the tradeoffs before enabling it:
280
+
281
+ - **Preview, not GA.** Alibaba describes it as continuously evolving. No open
282
+ weights, no published benchmarks, no stable per-token price.
283
+ - **Billed in prepaid credits**, not per token — and credit consumption varies
284
+ with reasoning depth. Cost reports therefore exclude it and say so explicitly
285
+ rather than estimating a figure.
286
+
287
+ ### API mode (the effort knob works here)
288
+
289
+ ```bash
290
+ QWEN3_USE_API=true
291
+ QWEN3_FORMAT=openai # Token Plan is OpenAI-compatible; DashScope is not
292
+ QWEN3_API_URL=https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1/chat/completions
293
+ QWEN3_MODEL=qwen3.8-max-preview
294
+ QWEN3_API_KEY=<your Token Plan key>
295
+ QWEN3_REASONING_EFFORT=high # none|minimal|low|medium|high|xhigh|max; default xhigh
296
+ ```
297
+
298
+ Pass the **full** URL including `/chat/completions` — it is used verbatim. Use
299
+ `QWEN3_API_KEY` for the Token Plan key; there is deliberately no separate
300
+ key variable, so the key always matches whatever `QWEN3_API_URL` points at.
301
+
302
+ Accepted effort values are the provider's own enum:
303
+ `none|minimal|low|medium|high|xhigh|max`. Verified against the live endpoint on
304
+ 2026-07-21 — note this is wider than the `low|high|xhigh` reported in the public
305
+ write-ups. The one value this model rejects is `none`: thinking is always
306
+ enabled and cannot be disabled (`The value of the enable_thinking parameter is
307
+ restricted to True`). Anything unsupported comes back as a 400, never as a
308
+ silently ignored setting.
309
+
310
+ ### CLI mode (the effort knob does NOT work here)
311
+
312
+ The `qwen` CLI has no reasoning-effort flag. Effort is a user-owned setting, so
313
+ ai-consultants cannot deliver it per call and warns if you set
314
+ `QWEN3_REASONING_EFFORT` in CLI mode. Configure it yourself in
315
+ `~/.qwen/settings.json`:
316
+
317
+ ```json
318
+ {
319
+ "modelProviders": {
320
+ "openai": [
321
+ {
322
+ "id": "qwen3.8-max-preview",
323
+ "name": "[Token Plan] qwen3.8-max-preview",
324
+ "baseUrl": "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
325
+ "envKey": "BAILIAN_TOKEN_PLAN_API_KEY",
326
+ "generationConfig": { "reasoning": { "effort": "high" } }
327
+ }
328
+ ]
329
+ }
330
+ }
331
+ ```
332
+
333
+ **Caveat worth knowing**: if you also set `generationConfig.samplingParams` on an
334
+ OpenAI-compatible provider, Qwen Code ships those keys to the wire verbatim and
335
+ **skips the `reasoning` injection entirely** — so setting both silently gives you
336
+ no effort control at all.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.23.0",
2
+ "version": "2.25.0",
3
3
  "updated": "2026-07-16",
4
4
  "models": {
5
5
  "_comment_units": "All rates are USD per 1,000 tokens (per-1K). lib/costs.sh computes cost as (token_count / 1000) * rate, so every value here MUST be a per-1K rate = (provider $/1M price) / 1000. Do NOT paste $/MTok figures directly.",
@@ -320,8 +320,16 @@
320
320
  "grok-4.3": {
321
321
  "input": 0.00125,
322
322
  "output": 0.0025
323
+ },
324
+ "_comment_unpriced": "Credit-billed models: drawn from a prepaid subscription pool, with no published per-token price. Rated 0 so the estimator does not fabricate a figure - an absent model would otherwise fall through to default_rate (0.005/0.015) and be reported as a confident, wrong number. Every entry here must also be listed in unpriced_models, which is what makes the session report disclose the exclusion instead of implying the model was free.",
325
+ "qwen3.8-max-preview": {
326
+ "input": 0,
327
+ "output": 0
323
328
  }
324
329
  },
330
+ "unpriced_models": [
331
+ "qwen3.8-max-preview"
332
+ ],
325
333
  "consultant_fallbacks": {
326
334
  "_comment": "Premium defaults - Updated Jun 2026 (v2.17.0)",
327
335
  "claude": "claude-opus-4-8",
@@ -0,0 +1,88 @@
1
+ # Release v2.24.0
2
+
3
+ **Date:** 2026-07-20
4
+ **Type:** Minor — installing now removes slash commands that were deleted in earlier releases
5
+ **Previous:** v2.23.0
6
+
7
+ ## Highlights
8
+
9
+ - Installing or updating now **removes** `ai-consultants:*.md` slash commands this project no longer ships. Previously they were only ever copied in, so a command deleted upstream stayed on your machine forever.
10
+ - Ten such commands have been dead since v2.10.0 and are still installed on every setup predating it. One of them, `ai-consultants:config-features`, tells you to set `ENABLE_REFLECTION` — removed in v2.23.0, and now rejected by `configure --set`.
11
+ - **Read the breaking-change note below if you wrote your own commands under the `ai-consultants:` prefix.**
12
+ - Both entry points are fixed: `curl | bash` (`scripts/install.sh`) and npx (`ai-consultants install`).
13
+ - `scripts/release.sh` is no longer shipped in the npm package.
14
+
15
+ ## What's New
16
+
17
+ ### Stale slash commands are pruned on install and update
18
+
19
+ Both installers used to `cp` the current commands into `~/.claude/commands` and nothing else. Nothing ever removed one that had been deleted from the project, so obsolete commands accumulated and stayed invocable.
20
+
21
+ Measured on a real installation: **14 `ai-consultants:*.md` files present, 4 of which this project still ships.** The other ten were removed by the v2.10.0 command consolidation, thirteen releases ago:
22
+
23
+ ```
24
+ ask-experts config-check config-preset config-strategy
25
+ config-api config-features config-status config-wizard
26
+ config-budget config-personas
27
+ ```
28
+
29
+ They are not merely clutter. `config-features` instructs you to set `ENABLE_REFLECTION`, a setting v2.23.0 removed; `ai-consultants configure --set ENABLE_REFLECTION=...` now exits non-zero. Following that command's instructions fails.
30
+
31
+ Both paths now prune before copying and report what they removed:
32
+
33
+ ```
34
+ Pruned 10 slash command(s) removed in a previous release
35
+ Installed 4 slash commands to /Users/you/.claude/commands
36
+ ```
37
+
38
+ ### `scripts/release.sh` removed from the npm package
39
+
40
+ Maintainer-only version-bump automation. `bin/ai-consultants` does not route it, so it was never reachable from the CLI — `npx ai-consultants release` starts a consultation, it does not run the script.
41
+
42
+ Other scripts that look maintainer-only are **deliberately kept**, having checked what depends on them: `roster_audit.sh` backs the shipped `roster-audit` slash command and `doctor --roster-audit`; `roster_calibrate.sh`, `taste_elo.sh` and `run_calibration.sh` are documented for your own use in `docs/SMART_ROUTING.md`; `install-hooks.sh` is wired to a `package.json` script.
43
+
44
+ ## Breaking Changes
45
+
46
+ **The prune removes any `ai-consultants:*.md` this project does not ship — including a command you wrote yourself under that prefix.**
47
+
48
+ There is no way to distinguish a command you authored in this namespace from one of ours that was deleted upstream: both are simply files we do not ship. The `ai-consultants:` prefix is this project's namespace, and the prune treats it as such.
49
+
50
+ If you have custom commands there, **rename them out of the prefix before upgrading**:
51
+
52
+ ```bash
53
+ mv ~/.claude/commands/ai-consultants:mine.md ~/.claude/commands/my-consultants:mine.md
54
+ ```
55
+
56
+ Commands belonging to other tools are never considered — only the `ai-consultants:` prefix is examined.
57
+
58
+ ## Upgrade Guide
59
+
60
+ ```bash
61
+ # npx (no install)
62
+ npx ai-consultants "question"
63
+
64
+ # refresh the slash commands (this is what prunes)
65
+ npx ai-consultants install
66
+
67
+ # git clone install
68
+ cd ~/.claude/skills/ai-consultants && git pull
69
+
70
+ # curl | bash install
71
+ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/scripts/install.sh | bash
72
+ ```
73
+
74
+ No configuration changes required.
75
+
76
+ Note for `git pull` users: pulling updates the checkout but does **not** re-run the installer, so it does not prune. Run `scripts/install.sh` or `ai-consultants install` afterwards to refresh the installed commands.
77
+
78
+ ## Commits
79
+
80
+ - `315afb6` fix: harden the release and install tooling after a max code review
81
+ - `48892e4` fix(install): prune slash commands removed in earlier releases
82
+ - `384a1cd` chore: move the verify skill out of the distributed repo
83
+ - `95d2e29` chore(release): move the release skill out of the distributed repo
84
+ - `6a4e70d` chore(release): automate the release and publish pipeline
85
+
86
+ ## Contributors
87
+
88
+ - Matteo Scurati
@@ -0,0 +1,101 @@
1
+ # Release v2.25.0
2
+
3
+ **Date:** 2026-07-21
4
+ **Type:** Minor — cost reporting starts working, and five settings that never did
5
+ **Previous:** v2.24.0
6
+
7
+ ## Highlights
8
+
9
+ - **The session cost you were shown was phantom.** Every consultant response carried a hardcoded token count of zero, so none of them contributed anything; the figure came entirely from the pipeline's own metadata files, priced at a fallback rate. A realistic three-consultant run reported $0.0270 without pricing a single consultation.
10
+ - **Five `*_FORMAT` settings had never done anything** since v2.6, despite being documented in `.env.example`. Now honored.
11
+ - **Opt-in Qwen Cloud Token Plan support**, including `qwen3.8-max-preview`. No default changed.
12
+ - **New `QWEN3_REASONING_EFFORT`** for API mode — and it tells you when it cannot apply, instead of pretending.
13
+ - **New `metadata.tokens_source`** field distinguishing a measured token count from an estimated one.
14
+
15
+ ## What's New
16
+
17
+ ### Cost reporting reflects reality
18
+
19
+ `build_response_metadata` emitted `tokens_used: 0` on every response, and it was the only writer of that field. `calculate_session_cost` multiplies it by the model's rate, so every real consultation contributed exactly nothing.
20
+
21
+ What you saw instead came from the same loop reaching files it should never have priced: `voting.json` and the other pipeline artifacts carry no token count, so each fell to a 1000-token fallback at the default rate. The reported cost tracked how many pipeline stages had run.
22
+
23
+ In API mode the correct number was already being computed — `run_api_mode_query` extracted the provider's usage figures, logged them at debug level, and discarded them.
24
+
25
+ Responses now carry a real count from two sources:
26
+
27
+ | Transport | Source | Field value |
28
+ |---|---|---|
29
+ | API | the provider's own usage figures | `tokens_source: "measured"` |
30
+ | CLI | local 4-chars-per-token approximation over prompt + reply | `tokens_source: "estimated"` |
31
+
32
+ No CLI exposes token counts, and CLI is the default transport, so the approximation is unavoidable if the fix is to mean anything. It is labeled rather than hidden, and the session-cost line says what it rests on:
33
+
34
+ ```
35
+ Session cost: $0.0550 [token counts estimated for 3 of 3]
36
+ ```
37
+
38
+ ### Settings that were never read
39
+
40
+ `QWEN3_FORMAT`, `GLM_FORMAT`, `GROK_FORMAT`, `DEEPSEEK_FORMAT` and `MINIMAX_FORMAT` were declared and documented, but the function that picks a consultant's wire format hardcoded the mapping and never consulted them. All five now work. An unrecognized value falls back to the consultant's default with a warning, so a typo cannot produce a malformed request.
41
+
42
+ This is what makes the Token Plan endpoint reachable: it is OpenAI-compatible, whereas Qwen's own DashScope endpoint is not.
43
+
44
+ ### Qwen Cloud Token Plan (opt-in)
45
+
46
+ `qwen3.8-max-preview` is reachable only through a Token Plan subscription — separate endpoint, separate key, billed in prepaid credits. It is a preview, with no published benchmarks, no open weights, and no per-token price.
47
+
48
+ Because of that it is **configuration, not a default**: the premium Qwen model remains `qwen3.7-max`, and a test asserts that explicitly. Setting it up would break nothing for users without a subscription, because nothing points at it unless you do. See the recipe in `docs/RECIPES.md`.
49
+
50
+ Credit-billed models are catalogued at zero and listed under `unpriced_models`, so the session-cost line names them as excluded. Leaving such a model out of the catalog is not neutral — an unknown model falls through to a default rate roughly 4x the real Qwen premium input rate, and reports it as a confident figure.
51
+
52
+ ### Reasoning effort
53
+
54
+ `QWEN3_REASONING_EFFORT` accepts the provider's own enum: `none|minimal|low|medium|high|xhigh|max`. Note this is wider than the `low|high|xhigh` that public write-ups report for this model — the list here was read off the API, not the documentation.
55
+
56
+ **It applies in API mode only.** The `qwen` CLI has no effort flag; the level is a user-owned setting in `~/.qwen/settings.json`. Setting the variable in CLI mode logs a warning pointing at that file, rather than silently doing nothing.
57
+
58
+ Which values a given model accepts is enforced by the provider, not guessed locally. `qwen3.8-max-preview`, for instance, rejects `none` because it cannot disable thinking — a 400 with a clear message beats a stale hardcoded list.
59
+
60
+ ### Review pass
61
+
62
+ The change was put through a multi-agent review at maximum depth before release (55 agents, 52 verified findings, 15 distinct defects, all fixed). Nearly all of them were in the same class: making the token count real converted long-standing latent mis-billings into real money.
63
+
64
+ Pipeline metadata files were priced as phantom consultants. Cache hits — which make no API call — were billed at full price. Debate rounds were invisible. Escalated consultants were counted twice. And the provider's measured prompt/completion split was being collapsed and re-derived with a fixed 60/40 guess, overstating API-mode cost roughly 2.7x while still reporting it as measured. All are fixed, and a single helper now gates every loop over the responses directory so the next one cannot reintroduce them piecemeal.
65
+
66
+ The review also caught the reasoning-effort knob doing nothing on Qwen's *default* API configuration — the one path the documentation steers users to — which is precisely the failure the feature was designed to avoid.
67
+
68
+ ## Breaking Changes
69
+
70
+ None in the API sense — no documented behavior is removed and defaults are byte-identical.
71
+
72
+ Two behavioral changes worth knowing:
73
+
74
+ - **Reported costs go from zero to real numbers.** If you tuned `MAX_SESSION_COST` against always-zero reports, revisit it. Budget projections before debate and synthesis now include actual accumulated spend; the pre-run checks were driven by a context-size estimate and already worked.
75
+ - **Responses gained `metadata.tokens_source`, and `tokens_input`/`tokens_output` when the provider reports them.** Additive, but consumers parsing response JSON strictly should know.
76
+
77
+ ## Upgrade Guide
78
+
79
+ ```bash
80
+ # If installed via git clone
81
+ cd ~/.claude/skills/ai-consultants && git pull
82
+
83
+ # If installed via curl | bash
84
+ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/scripts/install.sh | bash
85
+ ```
86
+
87
+ No configuration changes are required. Everything new is opt-in.
88
+
89
+ To use Qwen 3.8, you need a Qwen Cloud Token Plan subscription and its dedicated API key, then the five settings in the `docs/RECIPES.md` recipe.
90
+
91
+ ## Verification
92
+
93
+ Verified against a live Token Plan subscription rather than fixtures alone: a structured response through the real adapter (confidence 9, persona applied), `622` measured tokens in API mode, `311` estimated in CLI mode, and a full consultation reporting `$0.0550` where it would previously have said `0¢`.
94
+
95
+ A sweep of every suite at all four `LOG_LEVEL` values found three that were not hermetic against it — assertions matching on warning output are silent at `ERROR`, which fails deterministically for that environment while passing everywhere else. Two were pre-existing, and two of the affected assertions were the v2.17.2 secret-exfiltration guards. All three now pin the level.
96
+
97
+ `npm test` covers 22 suites. The new `test_api_transport.sh` exists mainly as a back-compat gate — its byte-identical assertion on the request builder shared by six consultants was written and confirmed green against the unmodified function before anything changed.
98
+
99
+ ## Contributors
100
+
101
+ - @matteoscurati
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-consultants",
3
- "version": "2.23.0",
3
+ "version": "2.25.0",
4
4
  "description": "A harness for every question - a panel of up to 11 AI models that picks its own deliberation strategy per question (convergence, adversarial verification, tournament) and checks its work before answering",
5
5
  "bin": {
6
6
  "ai-consultants": "bin/ai-consultants"
@@ -11,6 +11,7 @@
11
11
  "!scripts/test_*.sh",
12
12
  "!scripts/test_fixtures/",
13
13
  "!scripts/lib/test_helpers.sh",
14
+ "!scripts/release.sh",
14
15
  "templates/",
15
16
  ".env.example",
16
17
  ".claude/commands/",
package/scripts/config.sh CHANGED
@@ -861,4 +861,4 @@ EOF
861
861
  # VERSION
862
862
  # =============================================================================
863
863
 
864
- AI_CONSULTANTS_VERSION="2.23.0"
864
+ AI_CONSULTANTS_VERSION="2.25.0"
@@ -692,9 +692,14 @@ if is_escalation_enabled && [[ $SUCCESS_COUNT -gt 0 ]]; then
692
692
  [[ "$new_confidence" =~ ^[0-9]+$ ]] || new_confidence=0
693
693
  if [[ "$new_confidence" -gt "$original_confidence" ]]; then
694
694
  cp "$escalation_file" "$response_file"
695
+ # Remove the copy: leaving it makes one query look
696
+ # like two to every glob over this directory -
697
+ # cost (now that tokens are real) and voting alike.
698
+ rm -f "$escalation_file"
695
699
  log_success " $consultant escalated: confidence $original_confidence → $new_confidence"
696
700
  ESCALATED_COUNT=$((ESCALATED_COUNT + 1))
697
701
  else
702
+ rm -f "$escalation_file"
698
703
  log_debug " $consultant escalation did not improve confidence"
699
704
  fi
700
705
  fi
@@ -1092,7 +1097,7 @@ log_info "Session saved: $SESSION_ID"
1092
1097
  if [[ "$ENABLE_COST_TRACKING" == "true" ]]; then
1093
1098
  ACTUAL_COST=$(calculate_session_cost "$OUTPUT_DIR")
1094
1099
  track_session_cost "$SESSION_ID" "$ACTUAL_COST"
1095
- log_info "Session cost: $(format_cost "$ACTUAL_COST")"
1100
+ log_info "Session cost: $(format_cost "$ACTUAL_COST")$(format_cost_caveats "$OUTPUT_DIR")"
1096
1101
  fi
1097
1102
 
1098
1103
  # --- Final Summary ---