@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.
- package/CHANGELOG.md +131 -0
- package/install/_mcp-register.mjs +125 -0
- package/install/codex.mjs +4 -38
- package/install/copilot.mjs +8 -0
- package/install/index.mjs +8 -3
- package/package.json +1 -1
- package/pipeline/commands/multi-agent/setup/SKILL.md +2 -1
- package/pipeline/lib/account-resolver.sh +15 -4
- package/pipeline/lib/credential-inventory.sh +374 -0
- package/pipeline/lib/credential-store-resolver.sh +34 -5
- package/pipeline/lib/fetch-confluence.sh +27 -5
- package/pipeline/lib/fetch-crashlytics.sh +27 -5
- package/pipeline/lib/fetch-figma-annotations.sh +18 -4
- package/pipeline/lib/fetch-fortify.sh +27 -5
- package/pipeline/lib/fetch-graylog.sh +27 -5
- package/pipeline/lib/figma-mcp-refresh.sh +35 -11
- package/pipeline/lib/figma-screenshot.sh +18 -7
- package/pipeline/lib/issue-fetcher.sh +19 -4
- package/pipeline/lib/post-pr-review.sh +27 -4
- package/pipeline/lib/repo-cache.sh +19 -4
- package/pipeline/lib/vercel-deploy.sh +15 -4
- package/pipeline/multi-agent-refs/features/external-context-injection.md +44 -3
- package/pipeline/multi-agent-refs/keychain.md +63 -0
- package/pipeline/multi-agent-refs/phases/phase-0-init.md +51 -18
- package/pipeline/multi-agent-refs/rules.md +3 -3
- package/pipeline/scripts/audit-log.sh +10 -4
- package/pipeline/scripts/keychain-save.sh +27 -5
- package/pipeline/scripts/phase-tracker.sh +26 -2
- package/pipeline/scripts/phase0-exit-gate.mjs +52 -0
- package/pipeline/scripts/uninstall.mjs +24 -5
- 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
|
-
/**
|
|
47
|
-
export
|
|
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 };
|
package/install/copilot.mjs
CHANGED
|
@@ -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,
|
|
185
|
+
console.log(" For UI testing, the mobile MCP server:");
|
|
186
186
|
if (forCodex) {
|
|
187
|
-
console.log(" Codex:
|
|
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
|
+
"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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|