@mmerterden/multi-agent-pipeline 13.3.0 → 13.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.
Files changed (31) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/install/_mcp-register.mjs +125 -0
  3. package/install/codex.mjs +4 -38
  4. package/install/copilot.mjs +8 -0
  5. package/install/index.mjs +8 -3
  6. package/package.json +1 -1
  7. package/pipeline/commands/multi-agent/setup/SKILL.md +2 -1
  8. package/pipeline/lib/account-resolver.sh +15 -4
  9. package/pipeline/lib/credential-inventory.sh +374 -0
  10. package/pipeline/lib/credential-store-resolver.sh +34 -5
  11. package/pipeline/lib/fetch-confluence.sh +27 -5
  12. package/pipeline/lib/fetch-crashlytics.sh +27 -5
  13. package/pipeline/lib/fetch-figma-annotations.sh +18 -4
  14. package/pipeline/lib/fetch-fortify.sh +27 -5
  15. package/pipeline/lib/fetch-graylog.sh +27 -5
  16. package/pipeline/lib/figma-mcp-refresh.sh +35 -11
  17. package/pipeline/lib/figma-screenshot.sh +18 -7
  18. package/pipeline/lib/issue-fetcher.sh +19 -4
  19. package/pipeline/lib/post-pr-review.sh +27 -4
  20. package/pipeline/lib/repo-cache.sh +19 -4
  21. package/pipeline/lib/vercel-deploy.sh +15 -4
  22. package/pipeline/multi-agent-refs/features/external-context-injection.md +44 -3
  23. package/pipeline/multi-agent-refs/keychain.md +63 -0
  24. package/pipeline/multi-agent-refs/phases/phase-0-init.md +51 -18
  25. package/pipeline/multi-agent-refs/rules.md +3 -3
  26. package/pipeline/scripts/audit-log.sh +10 -4
  27. package/pipeline/scripts/keychain-save.sh +27 -5
  28. package/pipeline/scripts/phase-tracker.sh +26 -2
  29. package/pipeline/scripts/phase0-exit-gate.mjs +52 -0
  30. package/pipeline/scripts/uninstall.mjs +24 -5
  31. package/pipeline/skills/shared/core/multi-agent/SKILL.md +19 -0
package/CHANGELOG.md CHANGED
@@ -16,6 +16,137 @@ Internal file-layout changes that don't affect the slash-command surface are sti
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [13.5.0] - 2026-07-28
20
+
21
+ ### Fixed
22
+
23
+ - **Every fetcher died on a Copilot-only or Codex-only install.** Eleven runtime scripts
24
+ loaded `credential-store-resolver.sh` as `. "$HOME/.claude/lib/..." || . <next> || {
25
+ error }`, all of them under `set -e`. Sourcing a file that does not exist aborts the
26
+ shell outright - `||` included - so on a host without `~/.claude/lib` the chain reached
27
+ neither its later candidates nor its error branch: bare exit 1, no message. Reordering
28
+ does not help, because whichever candidate is absent aborts at that point. All eleven
29
+ now check `[ -f ]` before sourcing and decide on `$CRED_STORE`, and the resolver ends
30
+ with `resolve_credential_store || :` so sourcing it can never trip a caller's `set -e`.
31
+ - The resolver only knew about `~/.claude` and `~/.copilot`. A Codex-only install could
32
+ not locate the credential helper at all, while the file sat in `~/.codex/lib`.
33
+ - **Copilot CLI registered no MCP server.** Codex had registration from the day its
34
+ installer was written; Copilot never did, so it carried all 257 pipeline skills and
35
+ could not call one of the 80 dev-toolkit tools those skills depend on. Both hosts now
36
+ go through the shared `install/_mcp-register.mjs`, which also treats Copilot's
37
+ `already exists` (exit 1, unlike Codex's replace-and-exit-0) as the desired end state
38
+ rather than reporting a working registration as a failed install. Uninstall deregisters
39
+ on both.
40
+ - **`figma-mcp-refresh.sh` called macOS `security` directly**, so silent Figma MCP token
41
+ renewal - the path that keeps every Figma consumer on Tier 1 - never worked on Linux or
42
+ Windows, and surfaced as an unexplained expired-token prompt. It now goes through
43
+ `credential-store.sh` with the `set <key> -` stdin form, which keeps the secret off
44
+ argv on every platform, the property the old `security -i` call was preserving.
45
+ - **`phase-tracker.sh` hashed with a bare `shasum`** - a perl script absent from slim
46
+ Linux images. Where it was missing, OTel trace and span ids came back empty: valid
47
+ JSON, useless telemetry, no error. Same for `post-pr-review.sh`, where an empty
48
+ fingerprint made every finding look new and re-posted the whole review each iteration.
49
+ - `audit-log.sh` fell back to printing `unhashed:<url>` when no hasher was available,
50
+ writing the plaintext remote - credentials included, if the URL carried any - into the
51
+ audit log that hashing exists to protect. It emits a marker instead.
52
+ - `smoke-write-state.sh` counted an honest exit-2 lock timeout as data loss, because it
53
+ read the final state without looking at exit codes. That made it fail once inside a
54
+ fully loaded suite run while passing 12/12 in isolation - a flake that read as a
55
+ data-loss bug and would eventually have been "fixed" by lowering the writer count,
56
+ removing the regression detection the test exists for. It now asserts the real
57
+ invariant: no writer reports success while losing its update.
58
+ - `smoke-token-preflight.sh` asserted the literal `security add-generic-password`, which
59
+ pinned the implementation to macOS. It now asserts the portable route and fails if a
60
+ direct platform keychain call comes back.
61
+ - `multi-agent-refs/rules.md` pointed at `~/.claude/scripts/vercel-deploy.sh`; the
62
+ wrapper installs to `lib/` with the other shell libraries.
63
+
64
+ ### Added
65
+
66
+ - **`smoke-shell-portability.sh`** (8 assertions) over every shell file that ships to a
67
+ user tree: single-platform hashing without a fallback, unguarded BSD/GNU-divergent
68
+ constructs, hardcoded absolute home paths, direct platform keychain calls, and the
69
+ `.`-chain-under-`set -e` trap above. It found the `post-pr-review.sh` hasher and the
70
+ `figma-mcp-refresh.sh` keychain call on its first run.
71
+ - `smoke-install-layout.sh --update-fixture` regenerates the layout fixture from the tree
72
+ the gate itself installs, probe plugin included. The documented hand-rolled recipe
73
+ omitted it, so following it baked a three-file shortfall into the fixture.
74
+
75
+ ## [13.4.0] - 2026-07-28
76
+
77
+ ### Added
78
+
79
+ - **`credential-inventory.sh`** - one command that answers "what can I reach right now?"
80
+ A run asked the user to paste a Crashlytics stack trace by hand, offering the manual
81
+ path as the fastest route, while a valid Firebase service-account JSON sat in the
82
+ Keychain mapped as `firebase` and resolving fine. Nothing had failed: the pipeline
83
+ simply never asked itself whether it already held a credential that answered the
84
+ question. The tool reports `present` / `mapped-but-missing` / `unmapped` per logical
85
+ key plus what each one unlocks, and `keychain.md` Rule 2 now binds every question that
86
+ requests external data to its output - ask for the pointer (the issue URL), never for
87
+ the payload. Values are never printed.
88
+ - `--probe` verifies that configured credentials actually answer, because "the key is in
89
+ the Keychain" and "the service replies" are different claims that the user fixes in
90
+ different ways. The verdicts stay separate on purpose: `auth-rejected` (refresh the
91
+ token), `unreachable` (on a corporate host, almost always the VPN),
92
+ `no-host-configured` (a token with nowhere to point), `well-formed` (valid shape,
93
+ liveness needs more input), `not-probeable`. Collapsing them into one "failed" bucket
94
+ is what led the user to three different wrong actions. Only configured credentials are
95
+ probed - an unmapped key is a capability the user chose not to enable.
96
+ - `smoke-credential-awareness.sh` (32 assertions) and `test/phase0-exit-gate.test.mjs`
97
+ (20 cases) lock all of the above, including two secret-leak checks and the rule that
98
+ the credential reaches curl through a stdin config rather than argv, where any `ps`
99
+ could read it.
100
+ - `smoke-install-layout.sh --update-fixture` regenerates the layout fixture from the
101
+ tree the gate itself installs. The documented hand-rolled recipe omitted the seeded
102
+ probe plugin, so following it baked a three-file shortfall into the fixture and the
103
+ next run failed on a drift that was really a bad regen.
104
+
105
+ ### Fixed
106
+
107
+ - **The Phase 0 exit gate now asserts that the pickers ran.** It checked `taskType` and
108
+ the Figma access tier only, so a run that skipped the project and branch pickers and
109
+ developed straight on the local checkout passed cleanly - which is exactly what a
110
+ reported run did with a Jira ID. The gate now also requires `baseBranch`,
111
+ a `baseFetchStatus` from the known set, and a worktree path distinct from the project
112
+ root. Branch selection is marked non-skippable in `phase-0-init.md`, in every mode:
113
+ `--dev` skips the LLM phases, not Phase 0's pickers.
114
+ - **An unreachable external source is announced instead of absorbed.** Exit code `3`
115
+ used to mean "mark it failed and continue", so an expired token and a VPN-off remote
116
+ both reached the analysis phase as *no data*, indistinguishable from a ticket that
117
+ referenced nothing. The run then planned from a partial picture and reported success.
118
+ `external-context-injection.md` now classifies the stderr and surfaces a decision -
119
+ refresh the credential, connect the VPN and retry, supply a current URL, or continue
120
+ without - records `userDecision` in state, and requires autopilot to report what it
121
+ skipped rather than hide it. Retry is a real branch, not a label.
122
+ - The base-branch and fetch-failure pickers were still written as ASCII numbered menus
123
+ in `phase-0-init.md`, against the picker contract's native-widget rule. Both are now
124
+ expressed as picker options, and the fetch-failure question names the corporate host
125
+ when the remote points at one - a host that will not resolve is almost always the VPN,
126
+ and saying so is the difference between a five-second fix and a run built on a stale ref.
127
+ - `credential-inventory.sh --json` returned empty arrays while the table mode looked
128
+ correct: `python3 - <<EOF` takes its script from stdin, so the piped payload never
129
+ reached `sys.stdin`. Caught by the new gate on its first run.
130
+ - The probe read hosts from `global.<service>Host`; the real path is
131
+ `global.hosts.<service>`, so every self-hosted service reported
132
+ `no-host-configured`. Found by running the probe against the real preferences instead
133
+ of trusting the field name.
134
+ - `figma_mcp` is an OAuth token for the MCP server, not a REST PAT: probing it against
135
+ `api.figma.com` returned 403 for a healthy token and would have sent the user to
136
+ regenerate something that worked. It is now reported as not-probeable, with liveness
137
+ left to `figma-mcp-refresh.sh`, which owns the grant.
138
+ - A failed `curl` wrote `000` through `write-out` *and* triggered the `|| echo "000"`
139
+ fallback, producing `000000` and a bogus verdict for what was simply a closed VPN.
140
+ Any non-three-digit status now reads as `probe-error` rather than being dressed up as
141
+ a service verdict.
142
+ - `multi-agent-refs/rules.md` pointed at `~/.claude/scripts/vercel-deploy.sh`; the
143
+ wrapper installs to `lib/` with the other shell libraries. It had been reported as a
144
+ dangling reference for several releases.
145
+ - `/multi-agent:setup` no longer describes an unreachable discovery source as skipped
146
+ "silently". Setup is where the user is configuring things, so a source that could not
147
+ be reached is precisely what they need told, with the classification that decides
148
+ their next move.
149
+
19
150
  ## [13.3.0] - 2026-07-28
20
151
 
21
152
  ### Added
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Register the companion MCP server with a host CLI.
3
+ *
4
+ * WHY THIS IS SHARED
5
+ *
6
+ * Codex got MCP registration when its installer was written; Copilot never did. The
7
+ * result was a host that carried all 257 pipeline skills and could not call a single
8
+ * one of the 80 dev-toolkit tools those skills depend on - `design-check`, every
9
+ * `ios_*` / `android_*` simulator call, the archive audits. Nothing failed at install
10
+ * time, and the skills only fail when a run actually reaches for a tool.
11
+ *
12
+ * Both CLIs take the same shape (`<cli> mcp add <name> -- <command> [args...]`) and
13
+ * both write their own user-level config, so the difference was purely that one
14
+ * installer called it and the other did not. One implementation, two callers.
15
+ *
16
+ * @module install/_mcp-register
17
+ */
18
+
19
+ import { execFileSync } from "child_process";
20
+ import { existsSync } from "fs";
21
+ import { join } from "path";
22
+ import { homedir } from "os";
23
+
24
+ import { isDryRun } from "./_common.mjs";
25
+
26
+ export const MCP_SERVER_NAME = "dev-toolkit";
27
+ export const MCP_SERVER_PACKAGE = "@mmerterden/dev-toolkit-mcp";
28
+
29
+ /**
30
+ * Where each host's CLI might live.
31
+ *
32
+ * A bare name is resolved through PATH by execFileSync. The explicit paths matter for
33
+ * Copilot: `copilot` is frequently a *shell alias* for `gh copilot`, and an alias is
34
+ * invisible to execFileSync - so a PATH-only lookup reports "not on PATH" on a machine
35
+ * where the user runs `copilot` every day. `gh` installs the real binary under
36
+ * ~/.local/share/gh/copilot/.
37
+ */
38
+ const CLI_CANDIDATES = Object.freeze({
39
+ codex: ["codex"],
40
+ copilot: ["copilot", join(homedir(), ".local", "share", "gh", "copilot", "copilot")],
41
+ });
42
+
43
+ /**
44
+ * First candidate that exists as a file, or the first bare name to let PATH resolution
45
+ * try. Returning a bare name rather than null keeps the "not on PATH" diagnosis in one
46
+ * place: the execFileSync catch.
47
+ *
48
+ * @param {"codex"|"copilot"} host
49
+ * @returns {string}
50
+ */
51
+ function resolveCli(host) {
52
+ const candidates = CLI_CANDIDATES[host] || [host];
53
+ for (const c of candidates) {
54
+ if (c.includes("/") && existsSync(c)) return c;
55
+ }
56
+ return candidates[0];
57
+ }
58
+
59
+ /**
60
+ * Register the MCP server. Idempotent from the caller's point of view, though the two
61
+ * CLIs get there differently:
62
+ *
63
+ * - `codex mcp add` replaces an existing entry and exits 0.
64
+ * - `copilot mcp add` refuses, printing `Server "<name>" already exists` and exiting 1.
65
+ *
66
+ * So a non-zero exit is not automatically a failure. An already-registered server is
67
+ * the desired end state, and reporting it as "skipped MCP registration (Command
68
+ * failed)" would send the user to fix something that is already correct - which is
69
+ * exactly what the first version of this did.
70
+ *
71
+ * The existing entry is left alone rather than removed and re-added: a user may have
72
+ * added env vars or switched the command to a local checkout, and an installer has no
73
+ * business discarding that.
74
+ *
75
+ * Never fatal: a missing CLI or a failed write must not fail the install, because the
76
+ * rest of the tree is still correct and useful. It prints the manual command instead.
77
+ *
78
+ * @param {"codex"|"copilot"} host
79
+ * @param {string} label - host label for log lines
80
+ * @returns {{registered: boolean, alreadyPresent?: boolean, reason?: string}}
81
+ */
82
+ export function registerMcpServer(host, label) {
83
+ const cli = resolveCli(host);
84
+ const manual = `${host} mcp add ${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`;
85
+
86
+ if (isDryRun()) {
87
+ console.log(` [dry-run] would run: ${manual}`);
88
+ return { registered: false, reason: "dry-run" };
89
+ }
90
+
91
+ // Opt-out for hermetic installs. Registration shells out to the host CLI, so its
92
+ // result depends on whether that binary is present - which makes the install layout
93
+ // non-deterministic across machines and CI. The layout smoke sets this so its
94
+ // fingerprint stays stable.
95
+ if (process.env.MULTI_AGENT_SKIP_MCP_REGISTER === "1") {
96
+ console.log(` -> ${label}: skipped MCP registration (MULTI_AGENT_SKIP_MCP_REGISTER=1)`);
97
+ return { registered: false, reason: "opted out" };
98
+ }
99
+
100
+ try {
101
+ // Bounded: an installer must never hang on a child process. This is a local config
102
+ // write and returns in milliseconds.
103
+ execFileSync(cli, ["mcp", "add", MCP_SERVER_NAME, "--", "npx", "-y", MCP_SERVER_PACKAGE], {
104
+ stdio: "pipe",
105
+ timeout: 20_000,
106
+ });
107
+ console.log(` -> ${label}: registered the ${MCP_SERVER_NAME} MCP server`);
108
+ return { registered: true };
109
+ } catch (e) {
110
+ // The CLI writes its diagnosis to stderr, which execFileSync parks on the error
111
+ // rather than in `message`. Read both, or "already exists" is invisible.
112
+ const out = `${e?.stderr?.toString() ?? ""}\n${e?.stdout?.toString() ?? ""}\n${e?.message ?? ""}`;
113
+ if (/already exists/i.test(out)) {
114
+ console.log(` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered, left as-is`);
115
+ return { registered: true, alreadyPresent: true };
116
+ }
117
+ const why =
118
+ e?.code === "ENOENT"
119
+ ? `${host} not on PATH`
120
+ : (out.split("\n").find((l) => l.trim()) || "unknown error").trim();
121
+ console.log(` -> ${label}: skipped MCP registration (${why})`);
122
+ console.log(` run manually: ${manual}`);
123
+ return { registered: false, reason: why };
124
+ }
125
+ }
package/install/codex.mjs CHANGED
@@ -19,7 +19,6 @@
19
19
 
20
20
  import { existsSync, readFileSync, readdirSync, rmSync } from "fs";
21
21
  import { join } from "path";
22
- import { execFileSync } from "child_process";
23
22
 
24
23
  import {
25
24
  copyDir,
@@ -32,6 +31,7 @@ import {
32
31
  writeFile,
33
32
  } from "./_common.mjs";
34
33
  import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
34
+ import { registerMcpServer } from "./_mcp-register.mjs";
35
35
  import { installCodexAgents } from "./_codex-agents.mjs";
36
36
  import { existingSkillNames, installAuthoredPluginSkills } from "./_plugin-skills.mjs";
37
37
  import { generateCodexInstructions } from "./_codex-instructions.mjs";
@@ -43,9 +43,8 @@ export const AGENTS_MD_START_MARKER = "# Multi-Agent Development Pipeline";
43
43
  /** Explicit end marker written after the pipeline section. */
44
44
  export const AGENTS_MD_END_MARKER = "<!-- multi-agent-pipeline:codex-instructions:end -->";
45
45
 
46
- /** MCP server the pipeline's `/multi-agent:test` + design-check flows depend on. */
47
- export const MCP_SERVER_NAME = "dev-toolkit";
48
- const MCP_SERVER_PACKAGE = "@mmerterden/dev-toolkit-mcp";
46
+ /** Re-exported for the uninstaller and the Codex install smoke. */
47
+ export { MCP_SERVER_NAME } from "./_mcp-register.mjs";
49
48
 
50
49
  /**
51
50
  * `$HOME/.claude/...` path rewrites applied to every file installed into the
@@ -200,7 +199,7 @@ export function installCodex(ctx) {
200
199
  installTree("rules", pipelineSrc, CODEX_RULES, useSymlinks);
201
200
  writeAgentsMd(join(CODEX_DIR, "AGENTS.md"));
202
201
  ensureSharedPreferences(home, pipelineSrc);
203
- registerMcpServer();
202
+ registerMcpServer("codex", "Codex CLI");
204
203
 
205
204
  console.log("");
206
205
  }
@@ -482,37 +481,4 @@ function ensureSharedPreferences(home, pipelineSrc) {
482
481
  * it. Hand-merging TOML here would be the riskiest write in this installer for
483
482
  * no benefit.
484
483
  */
485
- function registerMcpServer() {
486
- if (isDryRun()) {
487
- console.log(
488
- ` [dry-run] would run: codex mcp add ${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`,
489
- );
490
- return;
491
- }
492
- // Opt-out for hermetic installs. Registration shells out to `codex`, so its
493
- // result depends on whether the binary is present - which makes the install
494
- // layout non-deterministic across machines and CI. The layout smoke sets this
495
- // so its fingerprint stays stable.
496
- if (process.env.MULTI_AGENT_SKIP_MCP_REGISTER === "1") {
497
- console.log(" -> skipped MCP registration (MULTI_AGENT_SKIP_MCP_REGISTER=1)");
498
- return;
499
- }
500
- try {
501
- // Bounded: an installer must never hang on a child process. `codex mcp add`
502
- // is a local config write and returns in milliseconds.
503
- execFileSync("codex", ["mcp", "add", MCP_SERVER_NAME, "--", "npx", "-y", MCP_SERVER_PACKAGE], {
504
- stdio: "pipe",
505
- timeout: 20_000,
506
- });
507
- console.log(` -> registered the ${MCP_SERVER_NAME} MCP server`);
508
- } catch (e) {
509
- const why =
510
- e?.code === "ENOENT" ? "codex not on PATH" : (e?.message || "unknown error").split("\n")[0];
511
- console.log(` -> skipped MCP registration (${why})`);
512
- console.log(
513
- ` run manually: codex mcp add ${MCP_SERVER_NAME} -- npx -y ${MCP_SERVER_PACKAGE}`,
514
- );
515
- }
516
- }
517
-
518
484
  export { generateCodexInstructions };
@@ -26,6 +26,7 @@ import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
26
26
  import { generateCopilotInstructions } from "./_copilot-instructions.mjs";
27
27
  import { legacyTrailingContent, mergeManagedBlock } from "./_managed-block.mjs";
28
28
  import { existingSkillNames, installAuthoredPluginSkills } from "./_plugin-skills.mjs";
29
+ import { registerMcpServer } from "./_mcp-register.mjs";
29
30
 
30
31
  /**
31
32
  * @param {{
@@ -56,6 +57,13 @@ export function installCopilot(ctx) {
56
57
  installSchemas(pipelineSrc, COPILOT_SCHEMAS, useSymlinks);
57
58
  installLib(pipelineSrc, COPILOT_LIB, useSymlinks);
58
59
  installSkills({ home, pipelineSrc, dest: COPILOT_SKILLS, indexOnly, useSymlinks, platformFlag });
60
+
61
+ // Copilot never registered the companion MCP server, while Codex did from the day its
62
+ // installer was written. So Copilot carried every pipeline skill and could not call a
63
+ // single one of the tools those skills depend on: design-check, the ios_* / android_*
64
+ // simulator calls, the archive audits. Nothing failed at install time - the skills
65
+ // only fail once a run reaches for a tool that is not there.
66
+ registerMcpServer("copilot", "Copilot CLI");
59
67
  }
60
68
 
61
69
  /** Start of the pipeline-managed span in copilot-instructions.md. */
package/install/index.mjs CHANGED
@@ -182,11 +182,16 @@ function printSummary(opts) {
182
182
  console.log("");
183
183
  }
184
184
 
185
- console.log(" For UI testing, also install the mobile MCP server:");
185
+ console.log(" For UI testing, the mobile MCP server:");
186
186
  if (forCodex) {
187
- console.log(" Codex: registered automatically (codex mcp add dev-toolkit)");
187
+ console.log(" Codex CLI: registered automatically (codex mcp add dev-toolkit)");
188
+ }
189
+ if (forCopilot) {
190
+ console.log(" Copilot CLI: registered automatically (copilot mcp add dev-toolkit)");
191
+ }
192
+ if (forClaude) {
193
+ console.log(" Claude Code: add it yourself - claude mcp add dev-toolkit -- npx -y @mmerterden/dev-toolkit-mcp");
188
194
  }
189
- console.log(" Otherwise add to your MCP config: npx @mmerterden/dev-toolkit-mcp");
190
195
  console.log("");
191
196
  console.log(" Uninstall everything: npx @mmerterden/multi-agent-pipeline uninstall");
192
197
  console.log(" (Personal access tokens in keychain are preserved.)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "13.3.0",
3
+ "version": "13.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",
@@ -633,7 +633,8 @@ Entries are objects (not bare strings). The schema `recentProjects.items` is a `
633
633
 
634
634
  **7g. Skip / degrade rules**
635
635
 
636
- - Source failure (network, VPN, 401, rate-limit) skips that source silently; the others still run. Failure is recorded in `serviceStatus.{service} = { ok: false, checkedAt: <now>, reason: <short> }` so Phase 0 can also read that cache.
636
+ - Source failure (network, VPN, 401, rate-limit) skips that source; the others still run. Failure is recorded in `serviceStatus.{service} = { ok: false, checkedAt: <now>, reason: <short> }` so Phase 0 can also read that cache.
637
+ - **Report the skip, do not swallow it.** Setup is where the user is actively configuring things, so a source that could not be reached is exactly what they need to hear about, and the reason decides their next move: a 401 means refresh that token, no response at all on a corporate host means connect the VPN, and a missing `global.hosts.<service>` means the token has nowhere to point. Print one line per skipped source with its classification (see `refs/keychain.md` Rule 2) before moving on. A discovery pass that quietly returned three repos out of forty reads as "you only have three repos".
637
638
  - If all three sources yield zero hits, Step 5 is a no-op - the picker in Phase 0 falls back to manual path entry.
638
639
  - Discovery is **never destructive**: it never modifies git state, never writes outside `PREFS_FILE`.
639
640
  - Step 5 is idempotent. Re-running it refreshes `recentProjects` and `serviceStatus` but preserves user-curated entries unless explicitly deselected.
@@ -69,10 +69,21 @@ except Exception:
69
69
  # A pre-set CRED_STORE (tests, custom installs) is honored as-is.
70
70
  if [ -z "${CRED_STORE:-}" ]; then
71
71
  # shellcheck disable=SC1090,SC1091
72
- . "$(cd "$(dirname "$0")" && pwd)/credential-store-resolver.sh" 2>/dev/null \
73
- || . "$HOME/.claude/lib/credential-store-resolver.sh" 2>/dev/null \
74
- || . "$HOME/.copilot/lib/credential-store-resolver.sh" 2>/dev/null \
75
- || true
72
+ # Existence check before sourcing: `. <missing>` aborts the shell under `set -e`,
73
+ # `||` included, so a `.`-chain reaches neither its later candidates nor its error
74
+ # branch. The loop also covers all three hosts - the chain it replaced knew only
75
+ # .claude and .copilot, so a Codex-only install could not resolve at all.
76
+ for _cred_resolver in \
77
+ "$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)/credential-store-resolver.sh" \
78
+ "$HOME/.claude/lib/credential-store-resolver.sh" \
79
+ "$HOME/.copilot/lib/credential-store-resolver.sh" \
80
+ "$HOME/.codex/lib/credential-store-resolver.sh"; do
81
+ [ -f "$_cred_resolver" ] || continue
82
+ # shellcheck source=/dev/null
83
+ . "$_cred_resolver" 2>/dev/null || true
84
+ if [ -n "${CRED_STORE:-}" ]; then break; fi
85
+ done
86
+ unset _cred_resolver
76
87
  fi
77
88
  services=""
78
89
  if [ -n "${CRED_STORE:-}" ]; then