ai-consultants 2.17.2 → 2.20.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.
@@ -13,6 +13,7 @@ All skill details (consultants, personas, presets, strategies, features, configu
13
13
  |---------|-------------|
14
14
  | `/ai-consultants:consult` | Main consultation — ask AI consultants a coding question |
15
15
  | `/ai-consultants:debate` | Run consultation with multi-round debate |
16
+ | `/ai-consultants:roster-audit` | Audit which consultants add distinct value vs echo the panel |
16
17
  | `/ai-consultants:help` | Show this help |
17
18
 
18
19
  ## Quick Examples
@@ -0,0 +1,38 @@
1
+ ---
2
+ description: Audit the consultant roster for uncorrelated value — which consultants contribute a distinct approach vs echo the panel (candidates to drop or down-weight). Use when deciding roster composition or after noticing redundant consultations.
3
+ argument-hint: [--recent N] [--threshold PCT] [dir ...]
4
+ allowed-tools: Bash
5
+ ---
6
+
7
+ # AI Consultants - Roster Audit
8
+
9
+ Measure each consultant's *uncorrelated value* across past consultations: a
10
+ consultant whose approach rarely differs from the rest (low keyword overlap) is
11
+ redundant; one that is often distinct earns its seat on diversity. This is the
12
+ "does it add signal the others miss?" bar, applied to the panel.
13
+
14
+ **Arguments:** $ARGUMENTS
15
+
16
+ ## Instructions
17
+
18
+ 1. Run the audit (pass `$ARGUMENTS` through; with no args it audits the most
19
+ recent consultations under the output base):
20
+ ```bash
21
+ cd "${AI_CONSULTANTS_DIR:-$HOME/.claude/skills/ai-consultants}" && ./scripts/roster_audit.sh $ARGUMENTS
22
+ ```
23
+ - No args → the 20 most recent consultation dirs.
24
+ - `--recent N` → the N most recent; `--threshold PCT` → correlation cutoff (default 20%).
25
+ - Or pass explicit consultation output dirs; add `--json` for machine-readable output.
26
+
27
+ 2. **Present the table** as-is (consultant · participated · distinct · distinct% ·
28
+ verdict), then interpret each verdict:
29
+ - **unique-value** — pulls its weight on diversity; keep.
30
+ - **some-value** — contributes occasionally.
31
+ - **redundant?** — correlated with the panel; candidate to drop or down-weight
32
+ in `references/affinity.json` (lower its category affinities, or its
33
+ capability axes when capability routing is enabled).
34
+ - **insufficient-data** — too few samples; audit more consultations.
35
+
36
+ 3. If the audit prints "nothing to audit" (exit 1), tell the user no consultations
37
+ with ≥2 responders were found and suggest running some consultations first —
38
+ each `consult_all.sh` run writes an output directory the audit can read.
package/CLAUDE.md CHANGED
@@ -6,7 +6,7 @@ AI Consultants is a multi-model AI deliberation system that queries up to 15 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.17.2
9
+ **Version**: 2.20.0
10
10
 
11
11
  ## Distribution
12
12
 
@@ -39,6 +39,10 @@ ai-consultants/
39
39
  │ ├── config.sh # Centralized configuration
40
40
  │ ├── doctor.sh # Diagnostic and auto-fix tool (v2.2)
41
41
  │ ├── peer_review.sh # Anonymous peer review (v2.2)
42
+ │ ├── roster_audit.sh # Roster uncorrelated-value audit (v2.20)
43
+ │ ├── roster_calibrate.sh # Measured capability calibration, Tier A (v2.20)
44
+ │ ├── run_calibration.sh # Calibration data-collection harness (v2.20)
45
+ │ ├── taste_elo.sh # Pairwise-judge taste Elo, Tier B (v2.20)
42
46
  │ ├── install.sh # One-liner installer (v2.2)
43
47
  │ ├── query_*.sh # Wrapper for each consultant
44
48
  │ ├── query_claude.sh # Claude consultant (v2.2)
@@ -668,6 +672,10 @@ for f in scripts/*.sh scripts/lib/*.sh; do bash -n "$f" && echo "OK: $f"; done
668
672
  | `ENABLE_COST_AWARE_ROUTING` | false | Cost-based model routing (v2.3) |
669
673
  | `ENABLE_DEBATE_OPTIMIZATION` | false | Skip debate if all agree (v2.3, opt-in) |
670
674
  | `ENABLE_COMPACT_REPORT` | true | Compact report format (v2.3) |
675
+ | `ENABLE_CAPABILITY_WEIGHTING` | false | Capability-weighted voting (v2.20, opt-in) |
676
+ | `ENABLE_CAPABILITY_ROUTING` | false | Capability-aware panel composition (v2.20, opt-in) |
677
+ | `CAPABILITY_WEIGHT_STRENGTH` | 10 | Vote-weight modulation: conf*(S+cap)/S (v2.20) |
678
+ | `CAPABILITY_DEFAULT` | 5 | Fallback capability for a missing consultant/axis (v2.20) |
671
679
  | `ENABLE_BUDGET_LIMIT` | false | Budget enforcement (v2.4, opt-in) |
672
680
  | `BUDGET_ACTION` | warn | Action on budget exceeded: warn/stop (v2.4) |
673
681
  | `QWEN3_USE_API` | false | Use DashScope API instead of qwen CLI (v2.7) |
@@ -826,6 +834,48 @@ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/s
826
834
 
827
835
  ## Changelog
828
836
 
837
+ ### v2.20.0
838
+ - **Capability axes (borrowed from the delegation-kit cost/intelligence/taste table).** `references/affinity.json` → v1.1: new `capabilities` (per-consultant {intelligence, taste, cost}, 1-10), `category_axis` (category → the quality axis it stresses: taste for API_DESIGN/ARCHITECTURE/CODE_REVIEW/GENERAL, intelligence otherwise), `capability_default`. New `lib/routing.sh::get_capability` / `get_category_axis` (cached like `get_affinity`).
839
+ - **Capability-weighted voting** (`ENABLE_CAPABILITY_WEIGHTING`, opt-in): `lib/voting.sh::_effective_vote_weight` modulates a vote by the consultant's capability on the run's axis — `confidence × (S + cap) / S` (`S = CAPABILITY_WEIGHT_STRENGTH`, default 10). Applied in `calculate_weighted_recommendation` and `calculate_final_score` (normalizer uses the same weight → 1-10 scale preserved). Axis from `QUESTION_CATEGORY`. `cost` never weights a vote (tie-break intelligence > taste > cost).
840
+ - **Capability-aware composition** (`ENABLE_CAPABILITY_ROUTING`, opt-in): `select_consultants` keeps the raw-affinity eligibility filter but ranks eligible consultants by `affinity + (cap − capability_default)`, so under a size limit the quality axis reorders who makes the cut.
841
+ - **Roster audit** — `scripts/roster_audit.sh` scores each consultant's "distinct approach" rate (max pairwise Jaccard of approaches < threshold) across consultations; verdicts unique-value / some-value / redundant? / insufficient-data. Wired as `doctor.sh --roster-audit` (short-circuit mode) and the `/ai-consultants:roster-audit` slash command (3 hosts). Reuses `voting.sh` keyword/Jaccard helpers.
842
+ - **Measured calibration** — `scripts/roster_calibrate.sh` (Tier A): intelligence/taste = mean blind peer-review score sliced by `category_axis`, cost = mean observed `tokens_used`×rate (60/40 split, rank-normalized cheapest=10); emits a `capabilities` block (`--json`/`--write`). `scripts/taste_elo.sh` (Tier B): taste via pairwise-judge Elo (pluggable judge — `JUDGE_CLI` / `TASTE_JUDGE_CMD`). `scripts/run_calibration.sh` drives `references/calibration_benchmark.json` (50 questions, 20 taste / 30 intelligence) through the panel with peer review, then calibrates.
843
+ - **Config** (`config.sh`): `ENABLE_CAPABILITY_WEIGHTING`, `ENABLE_CAPABILITY_ROUTING` (both false), `CAPABILITY_WEIGHT_STRENGTH` (10), `CAPABILITY_DEFAULT` (5). **Observability**: `consult_all.sh` records `capability{weighting_enabled, routing_enabled, axis}` in `optimization_metrics.json`.
844
+ - **Tests**: `test_capability_weighting` (17), `test_roster_audit` (6), `test_roster_calibrate` (10), `test_taste_elo` (6) — 12 suites total. All opt-in; with flags off, existing voting/routing/parity tests pass unchanged. shellcheck clean.
845
+ - **Docs**: `SMART_ROUTING.md` (capability + roster-audit + measured-calibration sections), `.env.example`, `references/configuration.md`, env-var table + structure list here. Website feature card added (sync on push).
846
+ - **Seeds are subjective** (Claude/Codex grounded on the model-routing table; the rest heuristic) — the calibration workflow measures them empirically. Fully back-compat.
847
+
848
+ ### v2.19.2
849
+ - **Cost tracking silently lost on a fresh install — fixed.** `lib/costs.sh::track_session_cost` wrote to `$COST_TRACKING_FILE` (`$XDG_DATA_HOME/ai-consultants/costs.json`, i.e. `~/.local/share/...`) without ever creating the parent directory. That dir doesn't exist until something makes it, and `costs.json` is the only artifact stored there — so on a fresh install every session's cost write failed with "No such file or directory" (swallowed under `set -e` in the caller) and cumulative cost tracking never accumulated. Fix: `mkdir -p "$(dirname "$COST_TRACKING_FILE")"` up front; on failure, `log_warn` + `return 0`.
850
+ - **Function split for lock discipline**: `track_session_cost` (outer) now owns dir creation + locking and delegates the read-modify-write to a new inner `_track_session_cost_update`, which runs with the lock held and never propagates failure. Rationale documented inline: the caller in `consult_all.sh` runs under `set -e` *after* every consultant has already been queried and billed, so a bookkeeping failure must degrade to a warning, never abort the run.
851
+ - **Concurrency**: the RMW of `costs.json` was unguarded, so parallel consultations could interleave and lose records or fail the `mv`. Added a portable `mkdir`-based lock (flock is unavailable on macOS) with a bounded wait (50 × 0.1s = 5s); if the lock stays busy it proceeds unlocked with a warning rather than blocking or aborting. Writes now go through `mktemp "${COST_TRACKING_FILE}.XXXXXX"` instead of a fixed `.tmp` sibling, so an unlocked concurrent writer can't clobber another run's temp file (lost record / failed `mv`).
852
+ - **Corrupt-file self-heal**: a corrupt `costs.json` (truncated write, interleaved update) previously failed every future `jq` update forever and never recovered. Now `jq empty` gates the file; on failure it's moved aside to `${COST_TRACKING_FILE}.corrupt` and reset, so tracking recovers on the next session.
853
+ - **Tests**: `scripts/test_suite.sh::test_cost_tracking_resilience` (5 assertions, wired into `main()`) — costs.json created under a missing nested parent dir (fresh-install path), second session accumulates on the existing file (0.25 → 0.75, both exact in binary float), a corrupt costs.json is reset with the new session recorded, and the corrupt original is preserved as `.corrupt`. Calls guarded with `|| true` so a regression surfaces as a FAIL assertion, not a `set -e` suite abort. 8 suites pass; the new assertions are green; shellcheck clean.
854
+ - **No behavioral change for existing installs** whose data dir already exists; the fix only affects the first-run/absent-dir, concurrent, and corrupt-file paths. Cost bookkeeping remains best-effort by design.
855
+
856
+ ### v2.19.1
857
+ - Cleanup from `/code-review max` on v2.19.0 (the workflow ran degraded under rate limits but surfaced these once it completed; all confirmed inline):
858
+ - **Report-table mangling fixed**: a failure reason containing a `|` (e.g. a CLI error mentioning a piped command) broke the "Diagnosed Failures" markdown row. The new `lib/common.sh::render_diagnosed_failure <entry> [console|table]` escapes `|`→`\|` in table mode. (Not a security injection — the text is the user's own CLI stderr — but a real rendering bug.)
859
+ - **DRY**: that helper is now the single source of the `name|reason` decode; the console log (quorum FAILED branch) and the report table both call it, so a future delimiter/encoding change touches one place instead of two.
860
+ - **Health gate is cache-aware**: it no longer pings a consultant whose response is already cached (Round 1 would serve it free via `check_cache`) — it keeps the cached consultant without a billed probe. Avoids the opt-in gate defeating the semantic cache.
861
+ - **`ping_consultant` takes the already-lowercased id** (callers compute it for the out/err paths anyway), dropping a redundant `to_lower` fork per consultant on both call sites (`doctor --live`, health gate).
862
+ - **Documented** the health gate's inherent pre-Round-1 startup latency (serial with the run by definition; up to `HEALTH_GATE_TIMEOUT`) in `config.sh` — it's the cost of pruning up front; opt-in + tunable.
863
+ - Tests: `test_render_diagnosed_failure` (4 assertions incl. pipe-escaping); `test_ping_consultant` updated for the lowercased-id signature. 8 suites pass; shellcheck clean. Smoke-verified: report table renders correctly, health gate still prunes + the min-2 guard fires on the pruned count.
864
+
865
+ ### v2.19.0
866
+ - **Quorum grading + "Diagnosed Failures" report** (the #1 pick from the `/workflows` CLI-reliability investigation — chosen for zero transport/billing risk over the CLI→API fallback, which the workflow's adversarial critique showed would emit persona-less confidence-5 stubs that poison voting). After the round-1 collect loop, `grade_quorum <success> <attempted> <min>` (new pure helper in `lib/common.sh`) classifies the run **MET / DEGRADED / FAILED** vs `QUORUM_MIN` (default 2). The report gains an `**Outcome**:` banner and a **## Diagnosed Failures** table that surfaces each dropped consultant's reason (the v2.18.0 `.err` capture) — so a panel that silently shrank to 2/7 is visibly DEGRADED instead of presenting as authoritative. `QUORUM_ACTION=stop` aborts below quorum; default `warn` continues with the banner. Failures are collected in `_surface_consultant_error` (DRY — same call that already logs the reason).
867
+ - **Health gate (`ENABLE_HEALTH_GATE`, opt-in)** — before the run, ping every selected consultant **in parallel** (new `lib/common.sh::ping_consultant`, `HEALTH_GATE_TIMEOUT` default 30s) and drop the non-responsive ones, so installed-but-unauthenticated CLIs (Cursor/Kimi/stale installs) are pruned up front and the quorum/budget checks see the genuinely-working panel. Script-less custom API agents (no `query_*.sh`) return code 2 and are kept (not probeable). Opt-in because it costs one extra tiny query per consultant; it prunes, it does not switch transport.
868
+ - **DRY**: `doctor --live` refactored to use the shared `ping_consultant`; both surfaces now share one probe implementation.
869
+ - **Explicitly NOT done** (per the workflow synthesis): CLI→API automatic fallback (persona-loss vote poisoning; Gemini already auto-resolves), cold-start retry-timeout escalation (stretches the whole round's worst case), warm-up calls, and the transport-abstraction circuit breaker (racy on-disk state, highest blast radius).
870
+ - **Tests**: `test_functions.sh` — `test_grade_quorum` (5 assertions: MET/DEGRADED/FAILED + boundaries) and `test_ping_consultant` (3: valid→0, no-output→fail, missing-script→2, via stub query scripts). 8 suites pass; shellcheck clean. End-to-end smoke verified: a forced CLI failure yields FAILED outcome + Diagnosed Failures table with the real reason, and the health gate prunes the dead consultant pre-run.
871
+
872
+ ### v2.18.0
873
+ - **Failures are now diagnosable, not silent (Fix A).** `consult_all.sh` launched every consultant with `> /dev/null 2>&1` — discarding stderr — so a failed consultant produced only a bare `Failed`/`Empty response`, with no way to tell *not installed* vs *not authenticated* vs *transient* (this is exactly what led a real session to mis-attribute failures to a timeout). Now each consultant's stderr is captured to `$OUTPUT_DIR/<consultant>.err`, and on FAILED/EMPTY the run surfaces a one-line reason via the new `lib/common.sh::get_consultant_error_reason` helper (ANSI-stripped, prefers an explicit error line, drops orchestration status noise, falls back to "no error captured — CLI likely missing or not authenticated").
874
+ - **`run_query` now embeds the CLI's real error in its failure log.** The per-attempt CLI stderr (`${output_file}.err`, an unpredictable mktemp path the orchestrator can't locate) was only `log_debug`'d. Its first line is now appended to the `Error (code: N)` and `All N attempts failed` warnings, so the actual reason (e.g. `401 Unauthorized`, `command not found`) reaches the captured stderr and the surfaced message.
875
+ - **`doctor.sh --live` (Fix B): real ping per consultant.** The static checks only verify a CLI is installed (`--version`), so `doctor` reports a consultant healthy even when it errors at query time (unauthenticated) — a real source of "All systems healthy" while 3 consultants silently fail. `--live` sends a minimal real query to each *enabled* (and not self-excluded) consultant with a short timeout (`DOCTOR_LIVE_TIMEOUT`, default 45s), and reports ✓/✗ with the captured reason; failures become `doctor` issues (exit 1). Opt-in (costs one tiny query each). Standalone short-circuit mode like `--suggest-preset`.
876
+ - **Tests**: `test_functions.sh::test_get_consultant_error_reason` (5 assertions: explicit-error line, embedded-auth reason, orchestration-noise-not-mistaken, empty/missing file). 8 suites pass; shellcheck clean (CI invocation).
877
+ - **Not fixable in code (documented for users)**: the underlying CLI auth/install state, and cold-start/warm-up retry effects, are environment issues — `--live` *surfaces* them but can't authenticate a CLI for you.
878
+
829
879
  ### v2.17.2
830
880
  - **SECURITY: revert v2.17.1's `allow_absolute=true` for context files — it opened a secret-exfiltration surface (caught in `/code-review max`).** v2.17.1 fixed the macOS `/private/tmp` drop by letting `build_context.sh` accept *any* absolute context path behind `validate_file_path`'s prefix-only blocklist (`/etc /root /var/log /proc /sys /dev`). That blocklist covers no home secrets, so a context arg of `~/.ssh/id_rsa`, `~/.aws/credentials`, `~/.netrc`, or `~/.config/gh/hosts.yml` was read verbatim and **sent to the external AI providers**. It also matched literal non-canonical prefixes, so `/private/etc/master.passwd` (the real `/etc` on macOS, via the same symlink aliasing) bypassed the `/etc` rule.
831
881
  - **Fix (correct altitude — allowlist, not blocklist)**: context files are now accepted only when (a) relative and in-tree (`validate_file_path ... false` — rejects absolute and `..`), or (b) under a recognized temp root via the new `_is_temp_path` helper (`/tmp`, `/private/tmp`, `$TMPDIR`, and the `/private`-prefixed macOS alias of `$TMPDIR`; `..` rejected so a temp prefix can't traverse out). Still fixes the original macOS scratch-file regression (Claude Code writes `/private/tmp/...`) **without** widening to the whole filesystem. Verified: `/tmp`, `/private/tmp`, `$TMPDIR/...`, relative in-tree → accepted; `~/.ssh/id_rsa`, `/etc/passwd`, `/private/etc/master.passwd`, `/tmp/../etc/passwd` → rejected.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
- # AI Consultants v2.17.2
1
+ # AI Consultants v2.20.0
2
2
 
3
3
  > **A harness for every question.** A panel of up to 15 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.17.2-blue.svg)](https://github.com/matteoscurati/ai-consultants)
5
+ [![Version](https://img.shields.io/badge/version-2.20.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)
@@ -574,13 +574,16 @@ Full reference (~150 vars): [`references/configuration.md`](references/configura
574
574
  Diagnose, suggest, and fix:
575
575
 
576
576
  ```bash
577
- ai-consultants doctor # 22 health checks
577
+ ai-consultants doctor # 22 health checks (CLI installed?)
578
578
  ai-consultants doctor --fix # Auto-fix common issues
579
579
  ai-consultants doctor --json # JSON for automation
580
+ ai-consultants doctor --live # Real ping per consultant — catches installed-but-unauthenticated CLIs
580
581
  ai-consultants doctor --suggest-config # Print recommended ENABLE_* based on detected CLIs
581
582
  ai-consultants doctor --suggest-preset --question "..." # Recommend preset + strategy for a question
582
583
  ```
583
584
 
585
+ > When a consultant fails mid-consultation, the run surfaces the captured reason (e.g. `CLI not found`, `401 Unauthorized`) instead of a bare "Failed", so you can tell *not installed* from *not authenticated* from *transient*.
586
+
584
587
  ---
585
588
 
586
589
  ## How It Works
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.17.2
8
+ version: 2.20.0
9
9
  ---
10
10
 
11
- # AI Consultants v2.17.2 - AI Expert Panel
11
+ # AI Consultants v2.20.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
 
@@ -130,11 +130,14 @@ See [Full Configuration Reference](references/configuration.md) for all variable
130
130
  Diagnose and fix issues:
131
131
 
132
132
  ```bash
133
- ./scripts/doctor.sh # Full check
133
+ ./scripts/doctor.sh # Full check (CLI installed?)
134
134
  ./scripts/doctor.sh --fix # Auto-fix
135
135
  ./scripts/doctor.sh --json # JSON output
136
+ ./scripts/doctor.sh --live # Real ping per consultant — catches installed-but-unauthenticated CLIs
136
137
  ```
137
138
 
139
+ When a consultant fails during a consultation, the run prints the captured reason (e.g. "CLI not found", "401 Unauthorized"). The run is also **graded** MET/DEGRADED/FAILED by how many responded (`QUORUM_MIN`), with a "Diagnosed Failures" section in the report. Set `ENABLE_HEALTH_GATE=true` to ping consultants and drop the dead ones *before* the run.
140
+
138
141
  ## Interpreting Results
139
142
 
140
143
  | Scenario | Recommendation |
@@ -1,4 +1,4 @@
1
- # Cost Rates - AI Consultants v2.17.2
1
+ # Cost Rates - AI Consultants v2.20.0
2
2
 
3
3
  This page documents the per-token rates used by the cost tracking system.
4
4
 
@@ -46,13 +46,21 @@ customization possible without editing bash.
46
46
 
47
47
  ```json
48
48
  {
49
- "version": "1.0",
49
+ "version": "1.1",
50
50
  "default_score": 5,
51
51
  "general_score": 8,
52
+ "capability_default": 5,
52
53
  "known_consultants": ["Gemini", "Codex", "..."],
53
54
  "categories": {
54
55
  "CODE_REVIEW": { "Gemini": 7, "Codex": 10, "..." },
55
56
  "BUG_DEBUG": { "..." }
57
+ },
58
+ "capabilities": {
59
+ "Gemini": { "intelligence": 8, "taste": 8, "cost": 6 },
60
+ "Codex": { "intelligence": 8, "taste": 5, "cost": 4 }
61
+ },
62
+ "category_axis": {
63
+ "API_DESIGN": "taste", "CODE_REVIEW": "taste", "ALGORITHM": "intelligence"
56
64
  }
57
65
  }
58
66
  ```
@@ -119,6 +127,112 @@ COMPLEXITY_THRESHOLD_MEDIUM=6 # Score 4-6 = use standard models
119
127
 
120
128
  See [COST_RATES.md](COST_RATES.md) for model pricing by tier.
121
129
 
130
+ ## Capability-Aware Routing & Voting (v2.20+)
131
+
132
+ Beyond category *fit* (the affinity matrix), v1.1 of `affinity.json` adds a
133
+ per-consultant **capability** score on three axes (1-10): `intelligence`,
134
+ `taste`, and `cost`. A `category_axis` map names the quality axis each category
135
+ stresses — **taste** for design-shaped work (API_DESIGN, ARCHITECTURE,
136
+ CODE_REVIEW, GENERAL), **intelligence** otherwise.
137
+
138
+ Two opt-in features consume this:
139
+
140
+ - **`ENABLE_CAPABILITY_WEIGHTING`** — a consultant's vote weight becomes
141
+ `confidence × (S + capability) / S` (`S = CAPABILITY_WEIGHT_STRENGTH`, default
142
+ 10), so on a taste-shaped question a high-taste model's opinion counts for more.
143
+ - **`ENABLE_CAPABILITY_ROUTING`** — panel *eligibility* still uses raw affinity,
144
+ but the *sort rank* becomes `affinity + (capability − CAPABILITY_DEFAULT)`, so
145
+ under a size limit the quality axis reorders which eligible consultants make
146
+ the cut.
147
+
148
+ `cost` is an efficiency axis for budget-aware composition only — **never** a
149
+ vote weight (tie-break order: intelligence > taste > cost). Scores are
150
+ subjective, point-in-time seeds — re-derive them for your own panel. Helpers:
151
+ `get_capability <consultant> <axis>` and `get_category_axis <category>` in
152
+ `lib/routing.sh`.
153
+
154
+ ## Roster Audit — Uncorrelated Value (v2.20+)
155
+
156
+ Adding a consultant only helps if it says something the others don't. The
157
+ `scripts/roster_audit.sh` tool measures this across past consultations: a
158
+ consultant's approach is *distinct* in a round when its keyword set has low
159
+ Jaccard overlap (`< --threshold`, default 20%) with **every** other consultant
160
+ that round. A consultant that is rarely distinct is correlated with the panel —
161
+ a candidate to drop or down-weight; one that is often distinct earns its seat on
162
+ diversity.
163
+
164
+ ```bash
165
+ # audit specific consultation output dirs
166
+ ./scripts/roster_audit.sh /path/to/consultation_dir ...
167
+
168
+ # or the N most recent under the consultations base
169
+ ./scripts/roster_audit.sh --recent 30
170
+
171
+ # machine-readable
172
+ ./scripts/roster_audit.sh --json --recent 30
173
+ ```
174
+
175
+ Read-only; reuses the `voting.sh` keyword/Jaccard machinery. Consultations with
176
+ fewer than two responders are skipped (correlation is undefined). This is the
177
+ model-routing "before a new model earns a row" bar (contribute >=1 thing the
178
+ incumbents miss) applied to the consultation panel instead of a review lane.
179
+
180
+ ## Measured Calibration — Replacing the Heuristic Seeds (v2.20+)
181
+
182
+ The `capabilities` scores in `affinity.json` ship as subjective seeds. To
183
+ **measure** them instead of guessing, three tools derive each axis from data the
184
+ tool already produces — sliced by the same `category_axis` that consumes them:
185
+
186
+ | Tool | Derives | From |
187
+ |---|---|---|
188
+ | `scripts/roster_calibrate.sh` | intelligence, taste, cost (**Tier A**) | blind peer-review scores (sliced by axis) + observed `tokens_used` × catalog rate |
189
+ | `scripts/taste_elo.sh` | taste (**Tier B**) | pairwise LLM-as-judge Elo over taste-axis answers (removes peer self-bias) |
190
+ | `references/calibration_benchmark.json` | the question set | 50 questions, 5 per category, balanced across the two axes |
191
+ | `scripts/run_calibration.sh` | the data | runs the benchmark through the panel with `ENABLE_PEER_REVIEW=true`, then calibrates |
192
+
193
+ ### Workflow
194
+
195
+ ```bash
196
+ # 1. Collect data: run the benchmark through the full panel (real calls — costs money).
197
+ ./scripts/run_calibration.sh --limit 10 --dry-run # preview
198
+ ./scripts/run_calibration.sh # full 50-question run
199
+
200
+ # 2. Tier A: measured intelligence/taste/cost -> capabilities block
201
+ ./scripts/roster_calibrate.sh --recent 50 # print measured block
202
+ ./scripts/roster_calibrate.sh --recent 50 --write # merge into affinity.json (keeps unmeasured cells)
203
+
204
+ # 3. Tier B (optional): refine taste via pairwise-judge Elo
205
+ ./scripts/taste_elo.sh --recent 50 --write
206
+ ```
207
+
208
+ Both `roster_calibrate.sh` and `taste_elo.sh` also accept explicit consultation
209
+ dirs and `--json`, and back up `affinity.json` to `.bak` before `--write`.
210
+
211
+ ### How each axis is measured
212
+
213
+ - **cost** — mean *observed* `$/response` (`tokens_used` × catalog rate, 60/40
214
+ input/output split), rank-normalized so the cheapest = 10. This is cost per
215
+ *task* (verbosity + completion), not per-token sticker price — a terse pricey
216
+ model can still rank cheap.
217
+ - **intelligence / taste** — mean **blind peer-review** `quality_score` (already
218
+ 1-10), sliced by `category_axis`: intelligence = performance on
219
+ intelligence-axis categories, taste = on taste-axis categories.
220
+ - **taste (Tier B)** — taste has no ground truth, so `taste_elo.sh` measures it
221
+ *relative to a chosen judge*. Each pair of answers on a taste-axis question is
222
+ shown to the judge, which picks the better one on design taste only; wins feed
223
+ an Elo ranking. The judge is pluggable:
224
+ - `JUDGE_CLI` — CLI for the built-in judge (default `claude`).
225
+ - `TASTE_JUDGE_CMD` — external judge `cmd <ctx> <A.json> <B.json>` printing
226
+ `A`/`B` (used by tests and custom judges).
227
+
228
+ ### Companion: which consultants earn a seat
229
+
230
+ `roster_audit.sh` (above) answers *who to keep*; calibration answers *how much
231
+ each is worth per axis*. Together they make the roster data-driven: audit to
232
+ prune redundant consultants, calibrate to score the survivors, then enable
233
+ `ENABLE_CAPABILITY_WEIGHTING` / `ENABLE_CAPABILITY_ROUTING` to act on the
234
+ measured scores.
235
+
122
236
  ## Timeout per Category
123
237
 
124
238
  Timeouts optimized based on category complexity:
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.17.2",
2
+ "version": "2.20.0",
3
3
  "updated": "2026-06-24",
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.",
@@ -0,0 +1,63 @@
1
+ # Release v2.18.0
2
+
3
+ **Date:** 2026-06-26
4
+ **Type:** Minor — observability (diagnosable failures + live doctor)
5
+ **Previous:** v2.17.2
6
+
7
+ ## Highlights
8
+
9
+ - **When a consultant fails, you now see *why*** — "CLI not found", "401 Unauthorized", "Timeout", etc. — instead of a bare "Failed".
10
+ - **`doctor.sh --live`** sends a real ping to each enabled consultant, catching CLIs that are installed but not authenticated (which the static check reports as healthy).
11
+
12
+ ## What's New
13
+
14
+ ### Failures tell you the reason
15
+
16
+ Previously the orchestrator launched every consultant with its output **and stderr** sent to `/dev/null`, so a failing consultant produced only `Failed` / `Empty response` with no detail — making it impossible to distinguish *not installed* from *not authenticated* from a *transient* error without digging through temp files. (In a real session this led to a wrong diagnosis — blaming a timeout for what were actually auth failures.)
17
+
18
+ Now each consultant's stderr is captured, and on failure the run prints a one-line reason:
19
+
20
+ ```
21
+ ✗ Cursor: Failed
22
+ ↳ Cursor: [ERROR] Cursor CLI not found (command: agent)
23
+ ✗ GLM: Failed
24
+ ↳ GLM: All 2 attempts failed: 401 Unauthorized
25
+ ```
26
+
27
+ If nothing diagnostic was captured, it says so plainly ("no output and no error captured — CLI likely missing or not authenticated").
28
+
29
+ ### `doctor.sh --live`
30
+
31
+ The normal `doctor.sh` only checks that a consultant's CLI is *installed* (`--version`). `--live` goes further: it sends a minimal real query to each **enabled** consultant and reports which actually respond.
32
+
33
+ ```bash
34
+ ./scripts/doctor.sh --live
35
+ # ✓ Gemini: responded
36
+ # ✗ Cursor: no valid response — Cursor CLI not found (command: agent)
37
+ # ✗ Kimi: no valid response
38
+ # Live result: 1 responding, 2 failing
39
+ ```
40
+
41
+ It's opt-in (one small query per consultant) and honors `DOCTOR_LIVE_TIMEOUT` (default 45s). Failures are reported as `doctor` issues (exit 1).
42
+
43
+ ## Breaking Changes
44
+
45
+ None.
46
+
47
+ ## Not fixable in code
48
+
49
+ `--live` and the surfaced reasons *diagnose* a consultant that's missing or unauthenticated — but installing/authenticating that CLI is up to you. Cold-start/warm-up retry effects are also environmental.
50
+
51
+ ## Upgrade Guide
52
+
53
+ ```bash
54
+ cd ~/.claude/skills/ai-consultants && git pull # or: npx ai-consultants@latest install
55
+ ```
56
+
57
+ ## Commits
58
+
59
+ - `_______` feat: diagnosable consultant failures + doctor --live (observability)
60
+
61
+ ## Contributors
62
+
63
+ - @matteoscurati
@@ -0,0 +1,66 @@
1
+ # Release v2.19.0
2
+
3
+ **Date:** 2026-06-26
4
+ **Type:** Minor — reliability (quorum grading + health gate)
5
+ **Previous:** v2.18.0
6
+
7
+ ## Highlights
8
+
9
+ - **A consultation that quietly ran with fewer panelists no longer looks authoritative.** It's now graded **MET / DEGRADED / FAILED**, and the report lists exactly which consultants dropped and why.
10
+ - **Opt-in health gate** prunes non-responsive consultants *before* the run, so you don't spend the full consultation on CLIs that are installed but not authenticated.
11
+
12
+ These were chosen from a multi-agent investigation of the "flaky CLI" problem; the adversarial review explicitly steered us *away* from an automatic CLI→API fallback (it would emit persona-less, confidence-5 stubs that poison the vote).
13
+
14
+ ## What's New
15
+
16
+ ### Quorum grading + Diagnosed Failures
17
+
18
+ After the panel responds, the run is graded by how many consultants actually answered, relative to `QUORUM_MIN` (default 2):
19
+
20
+ - **MET** — everyone responded
21
+ - **DEGRADED** — some failed, but ≥ quorum
22
+ - **FAILED** — below quorum
23
+
24
+ The report now opens with an `Outcome:` line and, when anything dropped, a **Diagnosed Failures** table:
25
+
26
+ ```
27
+ **Outcome**: DEGRADED (5/7 responded, quorum=2)
28
+
29
+ ## Diagnosed Failures
30
+ | Consultant | Reason |
31
+ |------------|--------|
32
+ | Cursor | Cursor CLI not found (command: agent) |
33
+ | Kimi | no output and no error captured — CLI likely missing or not authenticated |
34
+ ```
35
+
36
+ `QUORUM_ACTION=stop` aborts a below-quorum run; the default `warn` continues with the banner so nothing silently passes as a full-panel result.
37
+
38
+ ### Health gate (opt-in)
39
+
40
+ ```bash
41
+ ENABLE_HEALTH_GATE=true ai-consultants "your question"
42
+ ```
43
+
44
+ Pings every selected consultant **in parallel** with a tiny real query (`HEALTH_GATE_TIMEOUT`, default 30s) and drops the ones that don't respond before the real run starts — catching installed-but-unauthenticated CLIs and stale installs. It prunes; it does not switch transport. Custom API agents without a query script are kept (not probeable).
45
+
46
+ ## Breaking Changes
47
+
48
+ None. Both features are additive; `QUORUM_ACTION` defaults to `warn` (banner only) and the health gate is off by default.
49
+
50
+ ## Not included (deliberately)
51
+
52
+ The investigation rejected, for now: automatic CLI→API fallback (vote-poisoning persona loss), cold-start retry-timeout escalation (stretches the whole round's worst case), warm-up calls (extra billing/OAuth contention), and a transport-abstraction circuit breaker (racy on-disk state).
53
+
54
+ ## Upgrade Guide
55
+
56
+ ```bash
57
+ cd ~/.claude/skills/ai-consultants && git pull # or: npx ai-consultants@latest install
58
+ ```
59
+
60
+ ## Commits
61
+
62
+ - `_______` feat: quorum grading + diagnosed-failures report + opt-in health gate
63
+
64
+ ## Contributors
65
+
66
+ - @matteoscurati
@@ -0,0 +1,44 @@
1
+ # Release v2.19.1
2
+
3
+ **Date:** 2026-06-27
4
+ **Type:** Patch — code-review cleanup for v2.19.0
5
+ **Previous:** v2.19.0
6
+
7
+ ## Highlights
8
+
9
+ - **Fixes the "Diagnosed Failures" report table breaking** when a consultant's error reason contains a `|`.
10
+ - **The opt-in health gate no longer wastes a ping** on consultants whose answer is already cached.
11
+
12
+ ## What's New
13
+
14
+ ### Report table escapes pipes
15
+
16
+ A failure reason containing a `|` (e.g. a CLI error that mentions a piped command) used to mangle the markdown row in the **Diagnosed Failures** table. The reason is now escaped (`|` → `\|`) in table mode. (This was a rendering bug, not a security issue — the text is your own CLI's stderr.)
17
+
18
+ ### Cache-aware health gate
19
+
20
+ When `ENABLE_HEALTH_GATE=true` and semantic caching is on, a consultant whose response is already cached is kept **without** a ping — Round 1 would serve it for free, so probing it would defeat the cache. The gate only pings consultants it would actually run live.
21
+
22
+ ### Internal cleanup (from `/code-review max`)
23
+
24
+ - A single `render_diagnosed_failure` helper now renders both the console log and the report table — the `name|reason` decode lives in one place.
25
+ - `ping_consultant` takes the already-lowercased consultant id (callers compute it anyway), removing a redundant `to_lower` fork per consultant.
26
+ - The health gate's inherent pre-run startup latency (it's serial with the consultation by design, up to `HEALTH_GATE_TIMEOUT`) is now documented in `config.sh`.
27
+
28
+ ## Breaking Changes
29
+
30
+ None.
31
+
32
+ ## Upgrade Guide
33
+
34
+ ```bash
35
+ cd ~/.claude/skills/ai-consultants && git pull # or: npx ai-consultants@latest install
36
+ ```
37
+
38
+ ## Commits
39
+
40
+ - `_______` fix: escape pipes in diagnosed-failures table + cache-aware health gate + review cleanup
41
+
42
+ ## Contributors
43
+
44
+ - @matteoscurati
@@ -0,0 +1,54 @@
1
+ # Release v2.19.2
2
+
3
+ **Date:** 2026-07-11
4
+ **Type:** Patch — cost-tracking resilience (fresh-install fix + concurrency + corruption self-heal)
5
+ **Previous:** v2.19.1
6
+
7
+ ## Highlights
8
+
9
+ - **Fresh-install cost tracking is fixed** — sessions no longer fail to record their cost the first time you run the tool.
10
+ - **A corrupt `costs.json` now self-heals** instead of permanently wedging every future cost update.
11
+ - **Concurrent consultations can't lose cost records** — the update is serialized and uses a unique temp file.
12
+ - **Cost bookkeeping can never abort a run** — every failure path degrades to a warning.
13
+ - No configuration changes; no impact on existing installs whose data dir already exists.
14
+
15
+ ## What's New
16
+
17
+ ### Cost tracking resilience (`lib/costs.sh`)
18
+
19
+ `track_session_cost` records each consultation's cost to `costs.json` under the XDG data dir (`~/.local/share/ai-consultants/`). Four problems are fixed:
20
+
21
+ | Problem | Before | After |
22
+ |---|---|---|
23
+ | **Fresh install** | Wrote to `costs.json` without creating its parent dir → every session failed with "No such file or directory"; cost tracking silently never accumulated | `mkdir -p` the parent dir on demand |
24
+ | **Corrupt file** | A malformed `costs.json` failed every future `jq` update, forever | Detected via `jq empty`, moved aside to `costs.json.corrupt`, and reset |
25
+ | **Concurrency** | Unguarded read-modify-write; parallel runs could interleave and lose records | Serialized with a portable `mkdir` lock (5s bounded wait, then proceeds unlocked) |
26
+ | **Temp file** | Fixed `.tmp` sibling shared across runs → clobbered writes / failed `mv` | Unique `mktemp` temp file per write |
27
+
28
+ All failure paths now log a warning and return success: cost bookkeeping is best-effort and can never abort a consultation. This matters because the caller in `consult_all.sh` runs under `set -e` *after* every consultant has already been queried and billed — a bookkeeping hiccup must not throw away the run's results.
29
+
30
+ The function is split into an outer `track_session_cost` (directory creation + lock management) and an inner `_track_session_cost_update` (the read-modify-write, run with the lock held).
31
+
32
+ ### Tests
33
+
34
+ New `test_cost_tracking_resilience` in `scripts/test_suite.sh` (5 assertions, wired into `main()`) covers the fresh-install path (costs.json created under a missing nested parent dir), cross-session accumulation (0.25 → 0.75), and the corrupt-file self-heal including the `.corrupt` backup. All 8 suites pass; shellcheck clean.
35
+
36
+ ## Breaking Changes
37
+
38
+ None. Backwards-compatible. Installs whose data dir already exists see no behavioral change.
39
+
40
+ ## Upgrade Guide
41
+
42
+ ```bash
43
+ cd ~/.claude/skills/ai-consultants && git pull # or: npx ai-consultants@latest install
44
+ ```
45
+
46
+ No configuration changes required.
47
+
48
+ ## Commits
49
+
50
+ - `_______` fix(costs): harden track_session_cost against fresh-install, concurrency, and corruption
51
+
52
+ ## Contributors
53
+
54
+ - @matteoscurati