ai-consultants 2.23.0 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +25 -8
- package/README.md +2 -2
- package/SKILL.md +2 -2
- package/bin/ai-consultants +19 -0
- package/docs/COST_RATES.md +1 -1
- package/docs/cost_rates.json +1 -1
- package/docs/releases/v2.24.0.md +88 -0
- package/package.json +2 -1
- package/scripts/config.sh +1 -1
- package/scripts/install.sh +50 -4
- package/scripts/release.sh +0 -220
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.
|
|
9
|
+
**Version**: 2.24.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. **
|
|
725
|
-
2. **
|
|
726
|
-
3. **
|
|
727
|
-
4. **
|
|
728
|
-
5. **
|
|
729
|
-
6. **
|
|
730
|
-
7. **
|
|
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,17 @@ curl -fsSL https://raw.githubusercontent.com/matteoscurati/ai-consultants/main/s
|
|
|
795
801
|
|
|
796
802
|
## Changelog
|
|
797
803
|
|
|
804
|
+
### v2.24.0
|
|
805
|
+
- **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.
|
|
806
|
+
- **`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.
|
|
807
|
+
- **`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.
|
|
808
|
+
- **`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).
|
|
809
|
+
- **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).
|
|
810
|
+
- **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.
|
|
811
|
+
- **`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.
|
|
812
|
+
- **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.
|
|
813
|
+
- **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".
|
|
814
|
+
|
|
798
815
|
### v2.23.0
|
|
799
816
|
- **`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
817
|
- **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.
|
|
1
|
+
# AI Consultants v2.24.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
|
-
[](https://github.com/matteoscurati/ai-consultants)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://docs.anthropic.com/en/docs/claude-code/skills)
|
|
8
8
|
[](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.
|
|
8
|
+
version: 2.24.0
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
# AI Consultants v2.
|
|
11
|
+
# AI Consultants v2.24.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
|
|
package/bin/ai-consultants
CHANGED
|
@@ -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
|
package/docs/COST_RATES.md
CHANGED
package/docs/cost_rates.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
2
|
+
"version": "2.24.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.",
|
|
@@ -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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-consultants",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.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
package/scripts/install.sh
CHANGED
|
@@ -138,6 +138,51 @@ check_dependencies() {
|
|
|
138
138
|
return 0
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
# Remove installed slash commands that this project no longer ships.
|
|
142
|
+
#
|
|
143
|
+
# The install step only copies files in, so a command deleted from the repo
|
|
144
|
+
# would live in the user's commands directory forever. v2.10.0's command
|
|
145
|
+
# consolidation dropped ten of them, and every install predating it still
|
|
146
|
+
# carries all ten — including config-features, which instructs users to set
|
|
147
|
+
# ENABLE_REFLECTION, a setting v2.23.0 removed and `configure --set` now
|
|
148
|
+
# rejects with a non-zero exit.
|
|
149
|
+
#
|
|
150
|
+
# Scoped to this project's own "ai-consultants:*.md" namespace, so another
|
|
151
|
+
# tool's commands are never considered. Within that namespace it removes
|
|
152
|
+
# anything this repo does not ship — including a command the user authored
|
|
153
|
+
# under the same prefix. That is the intended bound: the prefix is this
|
|
154
|
+
# project's namespace, and there is no way to tell a user's file there from
|
|
155
|
+
# one of ours that was deleted upstream.
|
|
156
|
+
# Sets PRUNED_COUNT rather than echoing, so log output can't be mistaken for it.
|
|
157
|
+
prune_removed_commands() {
|
|
158
|
+
local commands_dir="$1" repo_commands_dir="$2" installed
|
|
159
|
+
PRUNED_COUNT=0
|
|
160
|
+
|
|
161
|
+
[[ -d "$commands_dir" ]] || return 0
|
|
162
|
+
|
|
163
|
+
for installed in "$commands_dir"/ai-consultants:*.md; do
|
|
164
|
+
[[ -e "$installed" ]] || continue # glob didn't match: nothing installed yet
|
|
165
|
+
if [[ ! -f "$repo_commands_dir/$(basename "$installed")" ]]; then
|
|
166
|
+
rm -f "$installed"
|
|
167
|
+
PRUNED_COUNT=$((PRUNED_COUNT + 1))
|
|
168
|
+
fi
|
|
169
|
+
done
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Test hook: `AI_CONSULTANTS_INSTALL_DEFINE_ONLY=1 source scripts/install.sh`
|
|
173
|
+
# yields the functions above and nothing else.
|
|
174
|
+
#
|
|
175
|
+
# It sits HERE — above the argument parser and the uninstall block — because a
|
|
176
|
+
# sourced script inherits the CALLER's positional parameters. With the guard
|
|
177
|
+
# below them, `bash scripts/test_install.sh --uninstall` had install.sh parse
|
|
178
|
+
# the suite's own flag and run the real uninstall: rm -rf on the user's
|
|
179
|
+
# installed skill plus every ~/.claude/commands/ai-consultants*.md. Anything
|
|
180
|
+
# added below this point must be safe to skip, and nothing above it may act.
|
|
181
|
+
if [[ -n "${AI_CONSULTANTS_INSTALL_DEFINE_ONLY:-}" ]]; then
|
|
182
|
+
# shellcheck disable=SC2317 # exit fallback for script-mode load
|
|
183
|
+
return 0 2>/dev/null || exit 0
|
|
184
|
+
fi
|
|
185
|
+
|
|
141
186
|
# =============================================================================
|
|
142
187
|
# ARGUMENT PARSING
|
|
143
188
|
# =============================================================================
|
|
@@ -203,10 +248,6 @@ if [[ "$UNINSTALL_MODE" == "true" ]]; then
|
|
|
203
248
|
exit 0
|
|
204
249
|
fi
|
|
205
250
|
|
|
206
|
-
# =============================================================================
|
|
207
|
-
# INSTALL / UPDATE
|
|
208
|
-
# =============================================================================
|
|
209
|
-
|
|
210
251
|
print_header
|
|
211
252
|
|
|
212
253
|
# Check dependencies
|
|
@@ -260,6 +301,11 @@ if [[ "$INSTALL_COMMANDS" == "true" ]]; then
|
|
|
260
301
|
log_info "Installing slash commands..."
|
|
261
302
|
|
|
262
303
|
if [[ -d "$INSTALL_DIR/.claude/commands" ]]; then
|
|
304
|
+
prune_removed_commands "$COMMANDS_DIR" "$INSTALL_DIR/.claude/commands"
|
|
305
|
+
if [[ "$PRUNED_COUNT" -gt 0 ]]; then
|
|
306
|
+
log_info "Pruned $PRUNED_COUNT slash command(s) removed in a previous release"
|
|
307
|
+
fi
|
|
308
|
+
|
|
263
309
|
cp "$INSTALL_DIR"/.claude/commands/*.md "$COMMANDS_DIR/" 2>/dev/null || true
|
|
264
310
|
cmd_count=$(find "$INSTALL_DIR/.claude/commands" -maxdepth 1 -name '*.md' -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
265
311
|
log_success "Installed $cmd_count slash commands"
|
package/scripts/release.sh
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# release.sh - Automates the version-bump surfaces for an ai-consultants release.
|
|
3
|
-
#
|
|
4
|
-
# Usage:
|
|
5
|
-
# scripts/release.sh <new-version> [--dry-run]
|
|
6
|
-
#
|
|
7
|
-
# Bumps the CURRENT version (read from package.json) to <new-version> across
|
|
8
|
-
# exactly these surfaces:
|
|
9
|
-
# package.json, scripts/config.sh, SKILL.md (frontmatter + title),
|
|
10
|
-
# README.md (title + badge), CLAUDE.md (**Version**), docs/cost_rates.json,
|
|
11
|
-
# docs/COST_RATES.md (title)
|
|
12
|
-
#
|
|
13
|
-
# Then validates consistency (no stray CURRENT version left on those exact
|
|
14
|
-
# lines), runs `npm test` + `npm run lint`, and prints a reminder of the
|
|
15
|
-
# manual release steps this script deliberately does NOT do.
|
|
16
|
-
#
|
|
17
|
-
# Env overrides:
|
|
18
|
-
# ROOT - repo root to operate under (default: computed from script location)
|
|
19
|
-
# SKIP_GATE=1 - skip npm test / npm run lint (used by the hermetic test suite)
|
|
20
|
-
#
|
|
21
|
-
# This script never commits, tags, or publishes.
|
|
22
|
-
|
|
23
|
-
set -euo pipefail
|
|
24
|
-
|
|
25
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
|
-
source "$SCRIPT_DIR/lib/common.sh"
|
|
27
|
-
|
|
28
|
-
ROOT="${ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
|
29
|
-
|
|
30
|
-
DRY_RUN=false
|
|
31
|
-
NEW_VERSION=""
|
|
32
|
-
|
|
33
|
-
for arg in "$@"; do
|
|
34
|
-
case "$arg" in
|
|
35
|
-
--dry-run)
|
|
36
|
-
DRY_RUN=true
|
|
37
|
-
;;
|
|
38
|
-
-h|--help)
|
|
39
|
-
echo "Usage: $0 <new-version> [--dry-run]"
|
|
40
|
-
exit 0
|
|
41
|
-
;;
|
|
42
|
-
*)
|
|
43
|
-
if [[ -z "$NEW_VERSION" ]]; then
|
|
44
|
-
NEW_VERSION="$arg"
|
|
45
|
-
else
|
|
46
|
-
log_error "Unexpected argument: $arg"
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
;;
|
|
50
|
-
esac
|
|
51
|
-
done
|
|
52
|
-
|
|
53
|
-
if [[ -z "$NEW_VERSION" ]]; then
|
|
54
|
-
log_error "Missing required argument: <new-version>"
|
|
55
|
-
echo "Usage: $0 <new-version> [--dry-run]"
|
|
56
|
-
exit 1
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
if [[ ! "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
60
|
-
log_error "Invalid version '$NEW_VERSION' — must be semver X.Y.Z (e.g. 2.21.0)"
|
|
61
|
-
exit 1
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
if [[ ! -f "$ROOT/package.json" ]]; then
|
|
65
|
-
log_error "package.json not found under ROOT: $ROOT"
|
|
66
|
-
exit 1
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
CURRENT_VERSION="$(jq -r '.version' "$ROOT/package.json")"
|
|
70
|
-
|
|
71
|
-
if [[ -z "$CURRENT_VERSION" || "$CURRENT_VERSION" == "null" ]]; then
|
|
72
|
-
log_error "Could not read current version from $ROOT/package.json"
|
|
73
|
-
exit 1
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
if [[ "$CURRENT_VERSION" == "$NEW_VERSION" ]]; then
|
|
77
|
-
log_error "New version ($NEW_VERSION) is the same as the current version"
|
|
78
|
-
exit 1
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
# Escape a literal string for safe use in a sed BRE pattern (dots, slashes, etc).
|
|
82
|
-
esc_regex() {
|
|
83
|
-
printf '%s' "$1" | sed -e 's/[.[\*^$/]/\\&/g'
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
CUR_ESC="$(esc_regex "$CURRENT_VERSION")"
|
|
87
|
-
|
|
88
|
-
# Each surface: file | sed program (anchored on the surrounding text, not a
|
|
89
|
-
# blanket version replace) | old-line regex (for --dry-run preview + the
|
|
90
|
-
# "old version gone" check) | new-line literal text (for the "new version
|
|
91
|
-
# present" check, matched with fixed-string grep — avoids treating '*'/'.' in
|
|
92
|
-
# markdown/JSON content as regex metacharacters) | human-readable label.
|
|
93
|
-
SURFACES=(
|
|
94
|
-
"package.json|s/\"version\": \"${CUR_ESC}\"/\"version\": \"${NEW_VERSION}\"/|\"version\": \"${CUR_ESC}\"| \"version\": \"${NEW_VERSION}\",|package.json: \"version\" field"
|
|
95
|
-
"scripts/config.sh|s/AI_CONSULTANTS_VERSION=\"${CUR_ESC}\"/AI_CONSULTANTS_VERSION=\"${NEW_VERSION}\"/|AI_CONSULTANTS_VERSION=\"${CUR_ESC}\"|AI_CONSULTANTS_VERSION=\"${NEW_VERSION}\"|scripts/config.sh: AI_CONSULTANTS_VERSION"
|
|
96
|
-
"SKILL.md|s/^ version: ${CUR_ESC}\$/ version: ${NEW_VERSION}/|^ version: ${CUR_ESC}\$| version: ${NEW_VERSION}|SKILL.md: frontmatter version"
|
|
97
|
-
"SKILL.md|s/^# AI Consultants v${CUR_ESC} - AI Expert Panel\$/# AI Consultants v${NEW_VERSION} - AI Expert Panel/|^# AI Consultants v${CUR_ESC} - AI Expert Panel\$|# AI Consultants v${NEW_VERSION} - AI Expert Panel|SKILL.md: title"
|
|
98
|
-
"README.md|s/^# AI Consultants v${CUR_ESC}\$/# AI Consultants v${NEW_VERSION}/|^# AI Consultants v${CUR_ESC}\$|# AI Consultants v${NEW_VERSION}|README.md: title"
|
|
99
|
-
"README.md|s/version-${CUR_ESC}-blue/version-${NEW_VERSION}-blue/|version-${CUR_ESC}-blue|version-${NEW_VERSION}-blue|README.md: badge"
|
|
100
|
-
"CLAUDE.md|s/^\*\*Version\*\*: ${CUR_ESC}\$/**Version**: ${NEW_VERSION}/|^\*\*Version\*\*: ${CUR_ESC}\$|**Version**: ${NEW_VERSION}|CLAUDE.md: **Version**"
|
|
101
|
-
"docs/cost_rates.json|s/\"version\": \"${CUR_ESC}\"/\"version\": \"${NEW_VERSION}\"/|\"version\": \"${CUR_ESC}\"| \"version\": \"${NEW_VERSION}\",|docs/cost_rates.json: \"version\" field"
|
|
102
|
-
"docs/COST_RATES.md|s/^# Cost Rates - AI Consultants v${CUR_ESC}\$/# Cost Rates - AI Consultants v${NEW_VERSION}/|^# Cost Rates - AI Consultants v${CUR_ESC}\$|# Cost Rates - AI Consultants v${NEW_VERSION}|docs/COST_RATES.md: title"
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
log_info "Release: $CURRENT_VERSION -> $NEW_VERSION (ROOT: $ROOT)"
|
|
106
|
-
|
|
107
|
-
if [[ "$DRY_RUN" == "true" ]]; then
|
|
108
|
-
echo ""
|
|
109
|
-
echo "DRY RUN — no files will be changed."
|
|
110
|
-
echo ""
|
|
111
|
-
fi
|
|
112
|
-
|
|
113
|
-
for surface in "${SURFACES[@]}"; do
|
|
114
|
-
IFS='|' read -r rel_file sed_prog old_regex new_literal label <<< "$surface"
|
|
115
|
-
file="$ROOT/$rel_file"
|
|
116
|
-
|
|
117
|
-
if [[ ! -f "$file" ]]; then
|
|
118
|
-
log_error "Surface file not found: $file"
|
|
119
|
-
exit 1
|
|
120
|
-
fi
|
|
121
|
-
|
|
122
|
-
matched_line="$(grep -nE -m1 -- "$old_regex" "$file" 2>/dev/null || true)"
|
|
123
|
-
|
|
124
|
-
if [[ "$DRY_RUN" == "true" ]]; then
|
|
125
|
-
echo " [$label]"
|
|
126
|
-
echo " file: $rel_file"
|
|
127
|
-
if [[ -n "$matched_line" ]]; then
|
|
128
|
-
echo " old: ${matched_line#*:}"
|
|
129
|
-
else
|
|
130
|
-
echo " old: (no match found — see WARNING below)"
|
|
131
|
-
fi
|
|
132
|
-
echo " new version: $NEW_VERSION"
|
|
133
|
-
if [[ -z "$matched_line" ]]; then
|
|
134
|
-
log_warn "No match for [$label] in $rel_file — this surface would not be updated"
|
|
135
|
-
fi
|
|
136
|
-
continue
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
if [[ -z "$matched_line" ]]; then
|
|
140
|
-
log_error "No match for [$label] in $rel_file — refusing to proceed"
|
|
141
|
-
exit 1
|
|
142
|
-
fi
|
|
143
|
-
|
|
144
|
-
sed -i.bak -e "$sed_prog" "$file"
|
|
145
|
-
rm -f "${file}.bak"
|
|
146
|
-
log_success "Updated $label"
|
|
147
|
-
done
|
|
148
|
-
|
|
149
|
-
if [[ "$DRY_RUN" == "true" ]]; then
|
|
150
|
-
echo ""
|
|
151
|
-
log_info "Dry run complete. No files were changed."
|
|
152
|
-
exit 0
|
|
153
|
-
fi
|
|
154
|
-
|
|
155
|
-
# -----------------------------------------------------------------------------
|
|
156
|
-
# Consistency validation: each surface's anchor line must now show NEW and
|
|
157
|
-
# must NOT still show CURRENT. Checked line-by-line against the same anchors
|
|
158
|
-
# used for the edit (not a whole-file grep), so legitimate historical
|
|
159
|
-
# mentions of CURRENT elsewhere (CHANGELOG.md, docs/releases/*.md, CLAUDE.md's
|
|
160
|
-
# `## Changelog` section) are correctly excluded from this check.
|
|
161
|
-
# -----------------------------------------------------------------------------
|
|
162
|
-
log_info "Validating consistency..."
|
|
163
|
-
|
|
164
|
-
validation_failed=false
|
|
165
|
-
for surface in "${SURFACES[@]}"; do
|
|
166
|
-
IFS='|' read -r rel_file sed_prog old_regex new_literal label <<< "$surface"
|
|
167
|
-
file="$ROOT/$rel_file"
|
|
168
|
-
|
|
169
|
-
if grep -qE -- "$old_regex" "$file" 2>/dev/null; then
|
|
170
|
-
log_error "[$label] still shows the old version ($CURRENT_VERSION) in $rel_file"
|
|
171
|
-
validation_failed=true
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
if ! grep -qF -- "$new_literal" "$file" 2>/dev/null; then
|
|
175
|
-
log_error "[$label] missing expected new version ($NEW_VERSION) in $rel_file"
|
|
176
|
-
validation_failed=true
|
|
177
|
-
fi
|
|
178
|
-
done
|
|
179
|
-
|
|
180
|
-
if [[ "$validation_failed" == "true" ]]; then
|
|
181
|
-
log_error "Consistency validation FAILED. Fix the surfaces above before releasing."
|
|
182
|
-
exit 1
|
|
183
|
-
fi
|
|
184
|
-
|
|
185
|
-
log_success "Consistency validation passed: all surfaces show $NEW_VERSION, no stray $CURRENT_VERSION."
|
|
186
|
-
|
|
187
|
-
# -----------------------------------------------------------------------------
|
|
188
|
-
# Gate: full test suite + lint (skippable for the hermetic test harness)
|
|
189
|
-
# -----------------------------------------------------------------------------
|
|
190
|
-
if [[ "${SKIP_GATE:-}" == "1" ]]; then
|
|
191
|
-
log_warn "SKIP_GATE=1 set — skipping npm test / npm run lint"
|
|
192
|
-
else
|
|
193
|
-
log_info "Running npm test..."
|
|
194
|
-
if ! (cd "$ROOT" && npm test); then
|
|
195
|
-
log_error "npm test FAILED — release aborted. Fix tests before releasing."
|
|
196
|
-
exit 1
|
|
197
|
-
fi
|
|
198
|
-
log_success "npm test passed."
|
|
199
|
-
|
|
200
|
-
log_info "Running npm run lint..."
|
|
201
|
-
if ! (cd "$ROOT" && npm run lint); then
|
|
202
|
-
log_error "npm run lint FAILED — release aborted. Fix lint issues before releasing."
|
|
203
|
-
exit 1
|
|
204
|
-
fi
|
|
205
|
-
log_success "npm run lint passed."
|
|
206
|
-
fi
|
|
207
|
-
|
|
208
|
-
echo ""
|
|
209
|
-
echo "======================================================================"
|
|
210
|
-
echo " Version bumped: $CURRENT_VERSION -> $NEW_VERSION"
|
|
211
|
-
echo "======================================================================"
|
|
212
|
-
echo ""
|
|
213
|
-
echo "REMINDER — the following steps are NOT automated by this script:"
|
|
214
|
-
echo " 1. Add a '## [$NEW_VERSION] - $(date +%Y-%m-%d)' entry to CHANGELOG.md"
|
|
215
|
-
echo " 2. Add a '### v$NEW_VERSION' entry to CLAUDE.md's '## Changelog' section"
|
|
216
|
-
echo " 3. Create docs/releases/v${NEW_VERSION}.md (see CLAUDE.md's release note template)"
|
|
217
|
-
echo " 4. Sync the website: ../aiconsultants.sh/index.html (softwareVersion schema + badge)"
|
|
218
|
-
echo ""
|
|
219
|
-
echo "This script did NOT commit, tag, or publish anything."
|
|
220
|
-
echo ""
|