@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
@@ -14,6 +14,12 @@
14
14
  * and spacing guessed at 16 where the frame said `Spacing/12`. Half the commits on
15
15
  * that branch were rework.
16
16
  *
17
+ * A second run then showed the other half of the same failure: a Jira task was
18
+ * implemented without the repo or branch picker ever appearing, straight on whatever
19
+ * the local checkout was pointing at. Step 3 and Step 8 own those decisions, and
20
+ * `agent-state.json` is the only durable evidence they happened - so the gate asserts
21
+ * their output too, not just `taskType`.
22
+ *
17
23
  * A phase that reports success without its output is worse than one that fails:
18
24
  * every later phase then reasons from a field that is not there. So this is a
19
25
  * gate, not a lint - the spec already said what to write, and prose alone did
@@ -120,6 +126,52 @@ export function evaluate(state, extraInput = "") {
120
126
  );
121
127
  }
122
128
 
129
+ // Step 3 picks the base branch and Step 8 creates the worktree. A run that skipped
130
+ // both develops on whatever the checkout happened to have, which is how a Jira task
131
+ // got implemented on a stale local branch with no picker ever shown. The state file
132
+ // is the only durable evidence those steps ran, so it has to carry their output.
133
+ //
134
+ // `local` provider runs still choose a base branch; what they skip is push and PR.
135
+ // So this applies to every provider.
136
+ const baseBranch = typeof state.baseBranch === "string" ? state.baseBranch.trim() : "";
137
+ if (!baseBranch) {
138
+ failures.push(
139
+ "agent-state.json has no baseBranch. Step 3 asks for it (or takes it from the " +
140
+ "input); an unset value means the branch picker never ran and development " +
141
+ "happens on whatever the checkout was already pointing at.",
142
+ );
143
+ }
144
+
145
+ // Which ref the base actually came from. Without it, "built on a fresh origin ref"
146
+ // and "built on a month-old local branch" are indistinguishable after the fact -
147
+ // and the fetch-failure picker exists precisely to make that an explicit choice.
148
+ const fetchStatus = typeof state.baseFetchStatus === "string" ? state.baseFetchStatus.trim() : "";
149
+ const FETCH_STATES = ["fresh", "cached-stale", "local-branch", "aborted"];
150
+ if (baseBranch && !FETCH_STATES.includes(fetchStatus)) {
151
+ failures.push(
152
+ `agent-state.json has baseFetchStatus="${fetchStatus || "<unset>"}"; Step 3 must ` +
153
+ `record one of ${FETCH_STATES.join(" | ")}. An unset value means nothing ` +
154
+ `established whether the base ref was fetched or reused from a stale cache.`,
155
+ );
156
+ }
157
+
158
+ // Worktree isolation is a standing rule: never develop in the primary checkout.
159
+ const worktree = typeof state.worktreePath === "string" ? state.worktreePath.trim() : "";
160
+ const projectRoot = typeof state.projectRoot === "string" ? state.projectRoot.trim() : "";
161
+ if (!worktree && !projectRoot) {
162
+ failures.push(
163
+ "agent-state.json records neither worktreePath nor projectRoot. Step 8 creates " +
164
+ "the worktree; with no recorded path, later phases cannot tell which tree they " +
165
+ "are meant to edit and default to the current directory.",
166
+ );
167
+ }
168
+ if (worktree && projectRoot && worktree === projectRoot) {
169
+ failures.push(
170
+ `worktreePath equals projectRoot (${worktree}). Development must happen in a ` +
171
+ `separate worktree, never in the primary checkout.`,
172
+ );
173
+ }
174
+
123
175
  // The Figma access chain records which tier answered. A component task with no
124
176
  // recorded tier means nothing verified that the design was actually reachable,
125
177
  // which is how a run ends up guessing spacing.
@@ -38,6 +38,7 @@
38
38
  import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "fs";
39
39
  import { execFileSync } from "child_process";
40
40
  import { join } from "path";
41
+ import { homedir } from "os";
41
42
  import { pathToFileURL } from "url";
42
43
  import { createInterface } from "readline";
43
44
 
@@ -157,21 +158,38 @@ function removeGeneratedCodexAgents(agentsDir) {
157
158
  * uninstall failure. Never edits `config.toml` directly - Codex keeps
158
159
  * marketplace and plugin state in the same file.
159
160
  */
160
- function deregisterCodexMcpServer() {
161
+ function deregisterMcpServer(host, cliPath) {
162
+ const cli = cliPath || host;
161
163
  if (dryRun) {
162
- report("would run", "codex mcp remove dev-toolkit");
164
+ report("would run", `${host} mcp remove dev-toolkit`);
163
165
  return;
164
166
  }
165
167
  try {
166
- execFileSync("codex", ["mcp", "remove", "dev-toolkit"], { stdio: "pipe", timeout: 20_000 });
167
- console.log(" removed: dev-toolkit MCP registration");
168
+ execFileSync(cli, ["mcp", "remove", "dev-toolkit"], { stdio: "pipe", timeout: 20_000 });
169
+ console.log(` removed: dev-toolkit MCP registration (${host})`);
168
170
  } catch {
169
171
  console.log(
170
- " note: could not run `codex mcp remove dev-toolkit` (codex not on PATH, or not registered)",
172
+ ` note: could not run \`${host} mcp remove dev-toolkit\` (${host} not on PATH, or not registered)`,
171
173
  );
172
174
  }
173
175
  }
174
176
 
177
+ function deregisterCodexMcpServer() {
178
+ deregisterMcpServer("codex");
179
+ }
180
+
181
+ /**
182
+ * Copilot registers the same server, so leaving it behind would keep a stale entry
183
+ * pointing at a package the user just removed.
184
+ *
185
+ * `copilot` is often a shell alias for `gh copilot`, invisible to execFileSync, so the
186
+ * real binary path is tried when the bare name is not resolvable.
187
+ */
188
+ function deregisterCopilotMcpServer() {
189
+ const ghCopilot = join(homedir(), ".local", "share", "gh", "copilot", "copilot");
190
+ deregisterMcpServer("copilot", existsSync(ghCopilot) ? ghCopilot : undefined);
191
+ }
192
+
175
193
  /**
176
194
  * Remove every directory under `parent` whose name matches a predicate.
177
195
  * @param {string} parent
@@ -475,6 +493,7 @@ export async function main() {
475
493
  if (forCopilot && HOME) {
476
494
  console.log("");
477
495
  console.log(" [Copilot CLI] Removing...");
496
+ deregisterCopilotMcpServer();
478
497
  const COP = join(HOME, ".copilot");
479
498
  rmIfExists(join(COP, "scripts"));
480
499
  // Pipeline-managed trees the installer lays down alongside scripts/.
@@ -336,6 +336,25 @@ Full contract: `refs/tracker-contract.md` section "TaskCreate ordering (strict)"
336
336
  > 6. **Branch name confirm** - `feature/PROJ-{id}-{kebab}` or `bugfix/...` - user confirms or edits. In multi-repo mode the branch name is shared across all selected repos (collision check runs per-repo; any collision applies the suffix to all). (Step 4)
337
337
  > 7. **Git identity - per repo** - route via `prefs.global.platformIdentityRouting`; ask if ambiguous. Multi-repo mode resolves identity **per repo independently** (phase-0-init.md L283). (Step 6)
338
338
  > 8. **Instruction files + workspace creation - serially per repo** - detect `.instructions/figma/` etc. In multi-repo mode, worktree creation loops per repo serially (phase-0-init.md L308); any failure rolls back previously-created worktrees. Write `agent-state.json` with `state.projects[]` array (scalar `project`/`projectRoot`/`branch` mirror `projects[0]` for back-compat). (Steps 7-8)
339
+ >
340
+ > **Exit gate (BLOCKING).** The contract above was prose only, and a run still took a
341
+ > Jira ID and implemented straight onto the local checkout with no picker shown. Before
342
+ > marking Phase 0 completed:
343
+ >
344
+ > ```bash
345
+ > node "$HOME/.copilot/scripts/phase0-exit-gate.mjs" "$TASK_ID" --input "$USER_INPUT"
346
+ > ```
347
+ >
348
+ > Non-zero means Phase 0 did not produce its own output - `taskType`, `baseBranch`,
349
+ > `baseFetchStatus`, a worktree path distinct from the project root, and a recorded Figma
350
+ > access tier when the input carries a Figma URL. Fix the missing step; do not proceed.
351
+ >
352
+ > **Credential inventory (Step 0).** Run
353
+ > `bash "$HOME/.copilot/lib/credential-inventory.sh" --json` and store the result in
354
+ > `agent-state.json.credentialInventory`. Per `refs/keychain.md` Rule 2, never ask the
355
+ > user for data a mapped credential can fetch: a run asked for a hand-pasted Crashlytics
356
+ > stack trace while a valid Firebase service account was mapped and resolving. Ask for
357
+ > the pointer (the issue URL) instead, and say what you will do with it.
339
358
 
340
359
  **Step 1 - Project detection + input parsing:**
341
360