@mmerterden/multi-agent-pipeline 16.4.0 → 16.5.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/CHANGELOG.md CHANGED
@@ -16,6 +16,20 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [16.5.0] - 2026-08-25
20
+
21
+ Staying current stops being something a user has to be told to do.
22
+
23
+ ### Changed
24
+
25
+ - **`updateCheck.autoUpdate` now defaults to `true`.** A newer version is installed before the run starts, in interactive modes and autopilot alike, instead of asking once per `ttlHours`. The old default made "be current" opt-in, which in practice meant a maintainer telling people to run a command - and since v15.14 the Supported Version Gate already halts any install below the `required` floor, so the real choice was never "update or not", it was "update, or be stopped and told to update". `autoUpdate: false` restores the question; `updateCheck.enabled: false` silences the advisory check (neither disables the required floor, whose only override remains `MULTI_AGENT_ALLOW_OUTDATED=1`). Docs already loaded finish the current run on the old version; the update takes full effect on the next run.
26
+ - **The MCP server is registered as `@latest`.** The registration was the bare package name, so `npx` reused any cached copy: a machine that had cached toolkit 3.0.0 kept starting 3.0.0 after 3.1.0 was published and tagged `latest`. The new tool existed on the registry and in the docs, and in no running server. Measured, not assumed - the npx cache held exactly `3.0.0` while `latest` was `3.1.0`. The cost is a registry round-trip when the server starts, and an offline start now depends on what npx can resolve rather than on any cached version being present: a stale server is a silent wrong answer, a failed start is a loud one.
27
+ - **Registration is rewritten on every install and update, not skipped when present.** The installer treated "already registered" as done and left the stored args alone, so an existing install would have kept the bare spec forever and the `@latest` change would have reached new installs only. The current entry is now removed before the add, exactly as the pre-rename entry already was. A host that refuses the remove is reported as possibly-stale with the manual command, rather than counted as a clean registration.
28
+
29
+ ### Fixed
30
+
31
+ - Two comments in `install/_mcp-register.mjs` and one in `smoke-npm-scope-pinning.sh` still said 83 tools / `MCP_SERVER_PACKAGE`; the toolkit serves 84 and the registration builds from `MCP_SERVER_SPEC`.
32
+
19
33
  ## [16.4.0] - 2026-08-24
20
34
 
21
35
  Two default changes are worth reading before upgrading. `/multi-agent:analysis`
package/docs/features.md CHANGED
@@ -173,7 +173,7 @@ Existing tests are immutable during a task: deleting, renaming, or weakening an
173
173
 
174
174
  Phase 0 Step 0.6. Once per `ttlHours` window (cached, 3s-bounded curl to the npm registry), the installed version is compared against two dist-tags.
175
175
 
176
- **`latest` - advisory**, opt-out via `prefs.global.updateCheck.enabled`. Newer version: interactive modes ask "Update now?" (yes runs the `/multi-agent:update` flow, then the run continues); autopilot logs one line and never asks. `autoUpdate: true` updates silently before the worktree exists. Offline and failed checks are silent - never blocks.
176
+ **`latest` - automatic**, since v16.5.0. `prefs.global.updateCheck.autoUpdate` defaults to `true`: a newer version is installed before the run starts, in interactive modes and autopilot alike, and the run continues. Set `autoUpdate: false` to be asked once per `ttlHours` instead, or `updateCheck.enabled: false` to silence the check (neither disables the required-version floor).
177
177
 
178
178
  **`required` - blocking** (v15.14.0+). Most releases do not publish this tag and nothing changes for them. A release that changed a contract a run depends on is promoted with `npm dist-tag add <pkg>@<version> required`, and an install below that floor is not behind, it is wrong: `require-supported-version.sh` exits 3, the run halts, `/multi-agent:update` runs, and the user re-issues the command on the new version rather than continuing on docs already loaded from the old one. Interactive and autopilot behave identically. The gate fails open on every undeterminable answer (offline, blocked registry, no tag), `updateCheck.enabled: false` does not disable it, and the single override is the env var `MULTI_AGENT_ALLOW_OUTDATED=1`, which is logged in the run record. Exemptions: `update`, `setup`, `uninstall`, `help`, `status`, `log`, `search`, `routines`, `forget`, `language`.
179
179
 
@@ -31,6 +31,22 @@ import { isDryRun } from "./_common.mjs";
31
31
  export const MCP_SERVER_NAME = "multi-agent-toolkit";
32
32
  export const MCP_SERVER_PACKAGE = "@mmerterden/multi-agent-toolkit-mcp";
33
33
 
34
+ /**
35
+ * The spec the host is registered with. `@latest` is deliberate and it is the
36
+ * difference between "the toolkit was published" and "the toolkit is running".
37
+ *
38
+ * Measured before this was added: the registration was the bare package name,
39
+ * npx found ANY cached version and reused it, and a machine that had cached
40
+ * 3.0.0 kept starting 3.0.0 after 3.1.0 was published and tagged `latest`. The
41
+ * new tool existed on the registry and in the docs, and in no running server.
42
+ *
43
+ * The cost is a registry round-trip when the MCP server starts, and an offline
44
+ * start now depends on what npx can resolve from its cache rather than on a
45
+ * cached version being present at all. That trade was made explicitly: a stale
46
+ * server is a silent wrong answer, a failed start is a loud one.
47
+ */
48
+ export const MCP_SERVER_SPEC = `${MCP_SERVER_PACKAGE}@latest`;
49
+
34
50
  /**
35
51
  * The registry the package is published to, pinned BY SCOPE.
36
52
  *
@@ -44,7 +60,7 @@ export const MCP_SERVER_PACKAGE = "@mmerterden/multi-agent-toolkit-mcp";
44
60
  *
45
61
  * So `npx` fetched from the wrong registry, the package was not there, the
46
62
  * process never started, and the host reported `CONNECTION_CLOSED` - a server
47
- * that works perfectly when run directly (83 tools) looked broken.
63
+ * that works perfectly when run directly (84 tools) looked broken.
48
64
  *
49
65
  * `--registry` does NOT fix this: a scope mapping outranks it. The scope itself
50
66
  * has to be pinned, which is why the flag below is built from the package name
@@ -64,7 +80,7 @@ export function scopeRegistryFlag(pkg = MCP_SERVER_PACKAGE, registry = MCP_REGIS
64
80
  * A host keys its registration by name, so renaming does NOT upgrade an entry in
65
81
  * place: without this, an existing install ends up with both `dev-toolkit`
66
82
  * (pointing at the old package, still resolvable, now frozen at 2.26.0) and
67
- * `multi-agent-toolkit`. Two servers advertising the same 83 tools is worse than
83
+ * `multi-agent-toolkit`. Two servers advertising the same 84 tools is worse than
68
84
  * either alone - the host has to pick, and which one it picks is not something
69
85
  * the pipeline controls. Registration removes the legacy entry first.
70
86
  */
@@ -152,7 +168,7 @@ function saysAlreadyExists(text) {
152
168
  export function registerMcpServer(host, label) {
153
169
  const cli = resolveCli(host);
154
170
  const scopeArgs = HOSTS[host]?.scopeArgs || [];
155
- const npxArgs = ["-y", ...scopeRegistryFlag(), MCP_SERVER_PACKAGE];
171
+ const npxArgs = ["-y", ...scopeRegistryFlag(), MCP_SERVER_SPEC];
156
172
  const manual = `${host} mcp add ${scopeArgs.join(" ")}${scopeArgs.length ? " " : ""}${MCP_SERVER_NAME} -- npx ${npxArgs.join(" ")}`;
157
173
 
158
174
  if (isDryRun()) {
@@ -182,6 +198,24 @@ export function registerMcpServer(host, label) {
182
198
  /* not present, or the host declines - either way the add below is what matters */
183
199
  }
184
200
 
201
+ // Drop the CURRENT entry too, so the add below rewrites its spec. Without this
202
+ // the host answers "already exists", the installer reported success, and the
203
+ // stored args stayed whatever an older version wrote - which is how a machine
204
+ // kept starting the toolkit from a bare package name (and therefore from
205
+ // whatever npx had cached) long after the spec gained `@latest`. The entry is
206
+ // installer-owned, so replacing it is not clobbering a user's work; a host
207
+ // that refuses the remove is reported below rather than counted as current.
208
+ let replacedExisting = false;
209
+ try {
210
+ execFileSync(cli, ["mcp", "remove", ...scopeArgs, MCP_SERVER_NAME], {
211
+ stdio: "pipe",
212
+ timeout: 20_000,
213
+ });
214
+ replacedExisting = true;
215
+ } catch {
216
+ /* not registered yet - the common case on a fresh install */
217
+ }
218
+
185
219
  try {
186
220
  // Bounded: an installer must never hang on a child process. This is a local config
187
221
  // write and returns in milliseconds.
@@ -193,10 +227,18 @@ export function registerMcpServer(host, label) {
193
227
  // Claude Code refuses a duplicate on the SUCCESS path (exit 0), so the check
194
228
  // cannot live in the catch alone.
195
229
  if (saysAlreadyExists(stdout?.toString() ?? "")) {
196
- console.log(` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered, left as-is`);
197
- return { registered: true, alreadyPresent: true };
230
+ // Reached only when the remove above did not take. The entry survives, but
231
+ // its spec is whatever was there before, so this is not a current
232
+ // registration - say that instead of reporting a clean pass.
233
+ console.log(
234
+ ` -> ${label}: ${MCP_SERVER_NAME} already registered and the host declined to replace it; ` +
235
+ `its spec may predate ${MCP_SERVER_SPEC}. Re-register manually with: ${manual}`,
236
+ );
237
+ return { registered: true, alreadyPresent: true, staleSpec: true };
198
238
  }
199
- console.log(` -> ${label}: registered the ${MCP_SERVER_NAME} MCP server`);
239
+ console.log(
240
+ ` -> ${label}: ${replacedExisting ? "re-registered" : "registered"} the ${MCP_SERVER_NAME} MCP server (${MCP_SERVER_SPEC})`,
241
+ );
200
242
  return { registered: true };
201
243
  } catch (e) {
202
244
  // The CLI writes its diagnosis to stderr, which execFileSync parks on the error
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "16.4.0",
3
+ "version": "16.5.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI and Codex CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -118,8 +118,8 @@ Run `bash $HOME/.claude/scripts/update-check.sh` (cached per `updateCheck.ttlHou
118
118
 
119
119
  **Advisory branch (never blocks).**
120
120
 
121
- - **Interactive**: log `→ update available: v<local> -> v<latest>`, ask ONE AskUserQuestion - **Update now** (recommended) / **Continue without updating**. On *Update now* (or `updateCheck.autoUpdate: true`, which skips the question): run the `/multi-agent:update` flow, log `→ updated to v<latest>`, continue the run (note in the log: already-loaded phase docs finish this run on the old version; full effect next session). On *Continue*: no re-ask until the TTL expires.
122
- - **Autopilot**: never ask (zero-interaction contract). Log `→ update available: ... (log-only; run /multi-agent:update)` and continue - unless `autoUpdate: true`, then update silently first.
121
+ - **Interactive**: `updateCheck.autoUpdate` defaults to `true`, so do not ask - run the `/multi-agent:update` flow, log `→ updated to v<latest>`, continue (docs already loaded finish this run on the old version; full effect next run). Only `autoUpdate: false` makes it a question: log `→ update available: v<local> -> v<latest>`, ask ONE AskUserQuestion - **Update now** (recommended) / **Continue without updating**; on *Continue*, no re-ask until the TTL expires.
122
+ - **Autopilot**: never ask (zero-interaction contract). On the default it updates first, logging `→ updated to v<latest>`; on `autoUpdate: false` it is log-only and continues.
123
123
 
124
124
  Both branches must run BEFORE Step 6 (worktree creation) so an accepted or forced update cannot mutate `~/.claude` under a mid-phase run.
125
125
 
@@ -840,8 +840,8 @@
840
840
  },
841
841
  "autoUpdate": {
842
842
  "type": "boolean",
843
- "default": false,
844
- "description": "When true, skip the question and run the update flow automatically before the run starts (interactive AND autopilot). Off by default - self-modifying ~/.claude without asking is a surprise."
843
+ "default": true,
844
+ "description": "Run the update flow automatically before the run starts, in interactive modes and autopilot alike, instead of asking. ON by default since v16.5.0: the alternative was telling every user to run a command, and the Supported Version Gate already halts an install below the required floor - so the real choice was never 'update or not', it was 'update, or stop and be told to update'. Set false to be asked once per ttlHours instead; updateCheck.enabled: false silences the check entirely (it does not disable the required floor). Already-loaded phase docs finish the current run on the old version either way; the update takes full effect on the next run."
845
845
  }
846
846
  }
847
847
  },