@mmerterden/multi-agent-pipeline 15.6.0 → 15.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,17 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [15.6.1] - 2026-08-19
20
+
21
+ ### Changed
22
+ - **`/multi-agent:update` installs from npm, not from a git clone**: the registry is the single update channel - latest published release resolved with a direct registry read (never `npm view`'s cache), downloaded via `npm pack` with the registry pinned, installed with `install.js --all`, changes rendered from the packaged CHANGELOG, smokes run from the tarball. A pipeline repo clone is now purely a maintainer workspace (synced by `/multi-agent:sync`); consumers need no git access at all, so collaborator grants on the private repo can stay read-only or be dropped.
23
+
24
+ ### Fixed
25
+ - **`node --test` runs stop pinging the live dashboard**: the tracker-entities suite calls `phase-tracker.sh init` outside run-smokes' `MULTI_AGENT_SMOKE` guard, so every test run left a phantom "probe" row on the timeline. The suite now sets the flag itself, and `usage-report.mjs` refuses to emit under it as the last line of defense for any caller.
26
+ - **Usage report reads the tracker as it is actually written**: `tracker-state.json` stores `phases` as an array, but the reporter iterated it with `Object.entries`, so dashboard phase ids were array indexes - every phase after a skipped one was mislabeled (Commit id "6" reported as Faz 5). Failed-phase error tags carried the same wrong ids.
27
+ - **Run duration and terminal timestamp resolve from the tracker**: nothing stamps `state.finishedAt`, so every run reported `du=null` and a terminal emit was stamped with the reporter's wall clock (wrong for backfills). Both now fall back to the tracker's phase span (earliest start to latest completion).
28
+ - **Version and user fields stop reporting null**: the reporter reads the installer's `~/.claude/.pipeline-version` marker (installed trees have no adjacent `package.json`) and falls back to `identity.name` when `identity.username` is absent.
29
+
19
30
  ## [15.6.0] - 2026-08-18
20
31
 
21
32
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "15.6.0",
3
+ "version": "15.6.1",
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",
@@ -1,43 +1,49 @@
1
1
  ---
2
- description: "Update the pipeline to the latest version: git pull, install, migrate. Use when the installed pipeline is behind and should be brought to the latest version."
3
- description-tr: "Pipeline'ı son sürüme günceller: git pull, install, migrate."
2
+ description: "Update the pipeline to the latest published npm release: registry check, npm pack, install, migrate. Use when the installed pipeline is behind and should be brought to the latest version."
3
+ description-tr: "Pipeline'ı npm'deki son yayına günceller: registry kontrolü, npm pack, install, migrate."
4
4
  allowed-tools: Bash, Read, Write, AskUserQuestion
5
5
  ---
6
6
 
7
7
  # multi-agent update
8
8
 
9
- Update the pipeline in one command. Existing preferences are preserved; only skill / script / schema files are refreshed.
9
+ Update the pipeline in one command. The npm registry is the single update channel: the latest published release is downloaded and installed. Existing preferences are preserved; only skill / script / schema files are refreshed.
10
+
11
+ A git clone of the pipeline repo is a maintainer workspace, kept in sync by `/multi-agent:sync` - it is never consulted here. A fix that only exists on `main` reaches users when a release is published, not before.
10
12
 
11
13
  ## Steps
12
14
 
13
- 1. **Find the pipeline repo**:
15
+ 1. **Resolve the package and both versions** (run steps 1-3 in ONE shell block so the variables survive):
14
16
  ```bash
15
- PIPE_DIR=""
16
- for candidate in "$HOME/multi-agent-pipeline" "$HOME/dev/multi-agent-pipeline" "$HOME/projects/multi-agent-pipeline"; do
17
- [ -d "$candidate/.git" ] && PIPE_DIR="$candidate" && break
18
- done
19
- if [ -z "$PIPE_DIR" ]; then
20
- echo "Pipeline repo not found. Clone it:"
21
- echo " git clone git@github.com:{owner}/multi-agent-pipeline.git"
17
+ PKG="@{npm-scope}/multi-agent-pipeline"
18
+ REG="https://registry.npmjs.org"
19
+ CUR=$(tr -d '[:space:]' < "$HOME/.claude/.pipeline-version" 2>/dev/null)
20
+ [ -n "$CUR" ] || CUR="unknown"
21
+ # Read the registry directly - `npm view` can answer from a stale local cache.
22
+ LATEST=$(curl -fsS "$REG/$(printf '%s' "$PKG" | sed 's|/|%2F|')/latest" \
23
+ | node -pe 'JSON.parse(require("fs").readFileSync(0,"utf8")).version' 2>/dev/null)
24
+ if [ -z "$LATEST" ]; then
25
+ echo "Registry unreachable ($REG) - check your network and retry."
22
26
  exit 1
23
27
  fi
28
+ echo "Current: v$CUR Latest: v$LATEST"
24
29
  ```
25
30
 
26
- 2. **Capture the current version**:
31
+ 2. **Stop early when already current** (still refresh the marketplace, step 4b):
27
32
  ```bash
28
- OLD_VERSION=$(node -p "require('$PIPE_DIR/package.json').version" 2>/dev/null || echo "unknown")
29
- echo "Current: v$OLD_VERSION"
33
+ [ "$CUR" = "$LATEST" ] && echo "✓ Already up to date: v$LATEST"
30
34
  ```
31
35
 
32
- 3. **Pull latest**:
36
+ 3. **Download the release and install**:
33
37
  ```bash
34
- cd "$PIPE_DIR" && git pull origin main
38
+ UPD_DIR="${TMPDIR:-/tmp}/multi-agent-update"
39
+ rm -rf "$UPD_DIR" && mkdir -p "$UPD_DIR"
40
+ # --registry pinned: an ambient .npmrc must not reroute the scope elsewhere.
41
+ npm pack "$PKG@$LATEST" --registry "$REG" --pack-destination "$UPD_DIR" --silent >/dev/null
42
+ tar -xzf "$UPD_DIR"/*.tgz -C "$UPD_DIR"
43
+ node "$UPD_DIR/package/install.js" --all
35
44
  ```
36
45
 
37
- 4. **Install (copy the files)**:
38
- ```bash
39
- node "$PIPE_DIR/install.js" --all
40
- ```
46
+ The installer refreshes every configured CLI target (Claude Code, Copilot CLI, Codex CLI) and writes the new version to `$HOME/.claude/.pipeline-version`.
41
47
 
42
48
  4b. **Update the stack-plugin marketplace** (pulls the latest plugin versions):
43
49
  ```bash
@@ -98,44 +104,41 @@ Update the pipeline in one command. Existing preferences are preserved; only ski
98
104
  fi
99
105
  ```
100
106
 
101
- 6. **Show the new version**:
107
+ 6. **Show the new version and its changes** (from the packaged CHANGELOG - there is no git history on this channel):
102
108
  ```bash
103
- NEW_VERSION=$(node -p "require('$PIPE_DIR/package.json').version" 2>/dev/null || echo "unknown")
109
+ NEW=$(tr -d '[:space:]' < "$HOME/.claude/.pipeline-version" 2>/dev/null)
104
110
  echo ""
105
- if [ "$OLD_VERSION" = "$NEW_VERSION" ]; then
106
- echo "✓ Already up to date: v$NEW_VERSION"
107
- else
108
- echo " Updated: v$OLD_VERSION v$NEW_VERSION"
109
- echo ""
110
- echo "Changes:"
111
- cd "$PIPE_DIR" && git log --oneline "v$OLD_VERSION..v$NEW_VERSION" 2>/dev/null | head -15
112
- fi
111
+ echo "✓ Updated: v$CUR v$NEW"
112
+ echo ""
113
+ echo "Changes:"
114
+ awk -v new="## [$NEW]" -v cur="## [$CUR]" \
115
+ 'index($0,new){on=1} on&&index($0,cur){exit} on' \
116
+ "$UPD_DIR/package/CHANGELOG.md" | head -40
113
117
  ```
114
118
 
115
- 7. **Smoke test (optional)**:
119
+ 7. **Smoke test (optional)** - the release tarball ships exactly two smokes for this purpose:
116
120
  ```bash
117
121
  echo ""
118
122
  echo "Verification:"
119
- # Repo-relative on purpose: these are maintainer smokes, excluded from installs.
120
- # Both lines name $PIPE_DIR rather than relying on the cd from the line above,
121
- # so neither can be read - or copied - as an installed-tree invocation.
122
- bash "$PIPE_DIR/pipeline/scripts/smoke-schema-validation.sh" 2>&1 | tail -1
123
- bash "$PIPE_DIR/pipeline/scripts/smoke-cross-cli-behavior.sh" 2>&1 | tail -1
123
+ bash "$UPD_DIR/package/pipeline/scripts/smoke-schema-validation.sh" 2>&1 | tail -1
124
+ bash "$UPD_DIR/package/pipeline/scripts/smoke-cross-cli-behavior.sh" 2>&1 | tail -1
125
+ rm -rf "$UPD_DIR"
124
126
  ```
125
127
 
126
128
  ## Output
127
129
 
128
130
  ```
129
- Current: v14.2.2
130
- -> git pull origin main
131
+ Current: v15.6.0 Latest: v15.6.1
132
+ -> npm pack @{npm-scope}/multi-agent-pipeline@15.6.1
131
133
  -> node install.js --all (51 commands, 228 scripts, 205 skills)
132
134
  -> migrate-prefs.mjs (0 changes - already v2.6.0)
133
135
 
134
- ✓ Updated: v14.2.2 → v15.0.0
136
+ ✓ Updated: v15.6.0 → v15.6.1
135
137
 
136
138
  Changes:
137
- e3f883e fix(dev): Phase 6 must show local test prompt
138
- 59617aa fix(setup): first-run guard
139
+ ## [15.6.1] - 2026-08-19
140
+ ### Fixed
141
+ - Usage report reads the tracker as it is actually written
139
142
  ...
140
143
 
141
144
  Verification:
@@ -212,6 +212,12 @@ function resolveStatePath({ state, taskId }) {
212
212
  }
213
213
 
214
214
  function packageVersion() {
215
+ try {
216
+ const marker = readFileSync(join(homedir(), ".claude", ".pipeline-version"), "utf-8").trim();
217
+ if (marker) return marker;
218
+ } catch {
219
+ /* marker absent */
220
+ }
215
221
  const candidates = [
216
222
  join(__dirname, "..", "..", "package.json"),
217
223
  join(homedir(), ".claude", "multi-agent-pipeline", "package.json"),
@@ -260,10 +266,25 @@ function phaseDurationSec(p) {
260
266
  }
261
267
 
262
268
  function trackerSummary(state) {
269
+ const empty = {
270
+ tk: null,
271
+ cost: null,
272
+ phs: null,
273
+ failed: [],
274
+ models: [],
275
+ startedAt: null,
276
+ endedAt: null,
277
+ };
263
278
  const path = resolveTracker(state);
264
- if (!path) return { tk: null, cost: null, phs: null, failed: [], models: [] };
279
+ if (!path) return empty;
265
280
  const tracker = readJson(path);
266
- if (!tracker?.phases) return { tk: null, cost: null, phs: null, failed: [], models: [] };
281
+ const raw = tracker?.phases;
282
+ const list = Array.isArray(raw)
283
+ ? raw
284
+ : raw && typeof raw === "object"
285
+ ? Object.entries(raw).map(([id, p]) => ({ id, ...(p ?? {}) }))
286
+ : null;
287
+ if (!list || list.length === 0) return empty;
267
288
 
268
289
  let tin = 0;
269
290
  let tout = 0;
@@ -271,7 +292,10 @@ function trackerSummary(state) {
271
292
  const phs = [];
272
293
  const failed = [];
273
294
  const models = new Set();
274
- for (const [id, p] of Object.entries(tracker.phases)) {
295
+ let startedAt = typeof tracker?.started_at === "string" && tracker.started_at ? tracker.started_at : null;
296
+ let endedAt = null;
297
+ for (const p of list) {
298
+ const id = p?.id != null && String(p.id) !== "" ? String(p.id) : "?";
275
299
  if (p?.status === "failed") failed.push(id);
276
300
  if (p?.model) models.add(String(p.model));
277
301
  const pin = Number(p?.tokens_in || 0);
@@ -280,6 +304,10 @@ function trackerSummary(state) {
280
304
  tin += pin;
281
305
  tout += pout;
282
306
  tcache += pcache;
307
+ const ps = p?.started_at || p?.startedAt || null;
308
+ const pf = p?.completed_at || p?.finished_at || p?.finishedAt || null;
309
+ if (ps && (!startedAt || ps < startedAt)) startedAt = ps;
310
+ if (pf && (!endedAt || pf > endedAt)) endedAt = pf;
283
311
  phs.push({
284
312
  p: Number.isNaN(Number(id)) ? id : Number(id),
285
313
  st: p?.status || null,
@@ -300,6 +328,8 @@ function trackerSummary(state) {
300
328
  phs: phs.length ? phs : null,
301
329
  failed,
302
330
  models: Array.from(models).slice(0, 10),
331
+ startedAt,
332
+ endedAt,
303
333
  };
304
334
  }
305
335
 
@@ -312,11 +342,18 @@ function deriveErrors(state, failedPhases) {
312
342
  return Array.from(errs).slice(0, 20);
313
343
  }
314
344
 
315
- function durationSec(state) {
345
+ function durationSec(state, spend) {
316
346
  const s = state.startedAt ? Date.parse(state.startedAt) : NaN;
317
347
  const f = state.finishedAt ? Date.parse(state.finishedAt) : NaN;
318
- if (Number.isNaN(s) || Number.isNaN(f) || f < s) return null;
319
- return Math.round((f - s) / 1000);
348
+ if (!Number.isNaN(s) && !Number.isNaN(f) && f >= s) {
349
+ return Math.round((f - s) / 1000);
350
+ }
351
+ const ts = spend?.startedAt ? Date.parse(spend.startedAt) : NaN;
352
+ const tf = spend?.endedAt ? Date.parse(spend.endedAt) : NaN;
353
+ if (!Number.isNaN(ts) && !Number.isNaN(tf) && tf >= ts) {
354
+ return Math.round((tf - ts) / 1000);
355
+ }
356
+ return null;
320
357
  }
321
358
 
322
359
  function runId(state) {
@@ -324,8 +361,10 @@ function runId(state) {
324
361
  return base == null ? null : String(base);
325
362
  }
326
363
 
327
- function eventTimestamp(state) {
328
- return state.finishedAt || state.updatedAt || new Date().toISOString();
364
+ function eventTimestamp(state, spend) {
365
+ if (state.finishedAt) return state.finishedAt;
366
+ if (isTerminalStatus(state) && spend?.endedAt) return spend.endedAt;
367
+ return state.updatedAt || new Date().toISOString();
329
368
  }
330
369
 
331
370
  const TERMINAL_STATUSES = ["complete", "completed", "failed", "paused", "halted", "error"];
@@ -360,8 +399,8 @@ function buildEvent(state) {
360
399
  const ctx = deriveContext(state);
361
400
  return {
362
401
  id: runId(state),
363
- t: eventTimestamp(state),
364
- u: state.identity?.username || null,
402
+ t: eventTimestamp(state, spend),
403
+ u: state.identity?.username || state.identity?.name || null,
365
404
  c: commandOf(state),
366
405
  m: state.mode || null,
367
406
  ap: Boolean(state.autopilot),
@@ -376,7 +415,7 @@ function buildEvent(state) {
376
415
  ? state.reviewIterations.length
377
416
  : 0,
378
417
  pr: Boolean(state.pr && (state.pr.url || state.pr.number)),
379
- du: durationSec(state),
418
+ du: durationSec(state, spend),
380
419
  tk: spend.tk,
381
420
  cost: spend.cost,
382
421
  phs: spend.phs,
@@ -416,6 +455,7 @@ async function post(endpoint, token, event) {
416
455
 
417
456
  async function main() {
418
457
  const args = parseArgs(process.argv.slice(2));
458
+ if (process.env.MULTI_AGENT_SMOKE && !args.dryRun) return;
419
459
  const prefs = resolvePrefs();
420
460
  const token = resolveToken();
421
461
  const endpoint = prefs.endpoint || ENDPOINT_DEFAULT;