@bridge_gpt/mcp-server 0.1.17 → 0.2.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 (36) hide show
  1. package/README.md +333 -197
  2. package/build/agent-capabilities/cli.js +152 -0
  3. package/build/agent-capabilities/default-deps.js +45 -0
  4. package/build/agent-capabilities/probe-context.js +111 -0
  5. package/build/agent-capabilities/probes.js +278 -0
  6. package/build/agent-capabilities/reporter.js +50 -0
  7. package/build/agent-capabilities/runner.js +56 -0
  8. package/build/agent-capabilities/types.js +10 -0
  9. package/build/agent-launchers/claude.js +4 -4
  10. package/build/agents.generated.js +1 -1
  11. package/build/brainstorm-files.js +89 -0
  12. package/build/bridge-config.js +404 -0
  13. package/build/chain-orchestrator.js +247 -33
  14. package/build/commands.generated.js +5 -5
  15. package/build/credential-materialization.js +128 -0
  16. package/build/credential-store.js +232 -0
  17. package/build/decision-page-schema.js +39 -6
  18. package/build/decision-page-template.js +54 -18
  19. package/build/doctor.js +18 -2
  20. package/build/git-ignore-utils.js +63 -0
  21. package/build/index.js +1510 -560
  22. package/build/mcp-invoke.js +417 -0
  23. package/build/mcp-provisioning.js +249 -0
  24. package/build/mcp-registration-doctor.js +96 -0
  25. package/build/pipeline-orchestrator.js +9 -1
  26. package/build/pipeline-utils.js +33 -0
  27. package/build/pipelines.generated.js +36 -5
  28. package/build/schedule-run.js +6 -6
  29. package/build/start-tickets-prereqs.js +90 -1
  30. package/build/start-tickets.js +106 -14
  31. package/build/third-party-mcp-targets.js +75 -0
  32. package/build/version.generated.js +1 -1
  33. package/package.json +3 -3
  34. package/pipelines/full-automation.json +3 -1
  35. package/pipelines/implement-ticket.json +28 -2
  36. package/smoke-test/SMOKE-TEST.md +4 -2
@@ -13,6 +13,9 @@
13
13
  * the runtime graph stays acyclic — `start-tickets.ts` imports values FROM here,
14
14
  * never the reverse.
15
15
  */
16
+ import { resolveBapiCredentials } from "./credential-store.js";
17
+ import { readBridgeConfig } from "./bridge-config.js";
18
+ import { probeWorktreeMcpRegistration } from "./mcp-registration-doctor.js";
16
19
  // ---------------------------------------------------------------------------
17
20
  // Constants (moved here from start-tickets.ts so both consumers share them)
18
21
  // ---------------------------------------------------------------------------
@@ -250,6 +253,87 @@ function agentDescriptor(agent) {
250
253
  function uvDescriptor() {
251
254
  return commandDescriptor("uv", "uv", UV_INSTALL_HINTS);
252
255
  }
256
+ /** Secret-free remediation hint shared by the credential-resolution descriptor. */
257
+ const CREDENTIAL_RESOLUTION_HINT = 'Set BAPI_API_KEY in the environment, or add it under "bapi:<repo_name>" in ' +
258
+ "~/.config/bridge/credentials.json.";
259
+ const CREDENTIAL_RESOLUTION_INSTALL_HINTS = {
260
+ darwin: CREDENTIAL_RESOLUTION_HINT,
261
+ linux: CREDENTIAL_RESOLUTION_HINT,
262
+ win32: CREDENTIAL_RESOLUTION_HINT,
263
+ };
264
+ /**
265
+ * Doctor-only, strictly read-only probe: can the Bridge API credentials be
266
+ * resolved for the current repo? Resolves repo identity from `BAPI_REPO_NAME`
267
+ * first, then `.bridge/config`, then asks the credential resolver. Reports only
268
+ * the SOURCE CLASS (env vs. store) — NEVER the resolved key value.
269
+ */
270
+ export function credentialResolutionDescriptor() {
271
+ return {
272
+ id: "bapi-credentials",
273
+ label: "Bridge API credential resolution",
274
+ installHint: CREDENTIAL_RESOLUTION_INSTALL_HINTS,
275
+ probe: async (deps) => {
276
+ const { readFile, stat, homedir } = deps;
277
+ if (!readFile || !stat || !homedir) {
278
+ return { found: false, detail: "credential probe unavailable (no read-only filesystem access)" };
279
+ }
280
+ let repoName = (deps.env.BAPI_REPO_NAME ?? "").trim();
281
+ if (repoName.length === 0) {
282
+ const read = await readBridgeConfig(deps.cwd, { readFile });
283
+ if (read.ok) {
284
+ repoName = read.manifest.repoName;
285
+ }
286
+ else {
287
+ return {
288
+ found: false,
289
+ detail: "cannot determine repo identity (set BAPI_REPO_NAME or add a valid .bridge/config). " +
290
+ CREDENTIAL_RESOLUTION_HINT,
291
+ };
292
+ }
293
+ }
294
+ const result = await resolveBapiCredentials(repoName, {
295
+ env: deps.env,
296
+ homedir,
297
+ platform: deps.platform,
298
+ readFile,
299
+ stat,
300
+ });
301
+ if (result.ok) {
302
+ const via = result.credentials.source === "env" ? "env" : "store";
303
+ return { found: true, detail: `credentials resolvable via ${via}` };
304
+ }
305
+ return { found: false, detail: CREDENTIAL_RESOLUTION_HINT };
306
+ },
307
+ };
308
+ }
309
+ /** Secret-free remediation hint shared by the worktree-MCP descriptor. */
310
+ const WORKTREE_MCP_HINT = "Re-run start-tickets to provision the worktree MCP registration " +
311
+ "(.mcp.json / .cursor/mcp.json pointing at the mcp-invoke shim).";
312
+ const WORKTREE_MCP_INSTALL_HINTS = {
313
+ darwin: WORKTREE_MCP_HINT,
314
+ linux: WORKTREE_MCP_HINT,
315
+ win32: WORKTREE_MCP_HINT,
316
+ };
317
+ /**
318
+ * Doctor-only, strictly read-only probe: does the current worktree's generated
319
+ * MCP registration point at the Bridge API `mcp-invoke` shim? Inspects
320
+ * `.mcp.json` / `.cursor/mcp.json` under `deps.cwd` without spawning anything.
321
+ */
322
+ export function worktreeMcpReachabilityDescriptor() {
323
+ return {
324
+ id: "worktree-mcp-registration",
325
+ label: "Worktree MCP registration reachability",
326
+ installHint: WORKTREE_MCP_INSTALL_HINTS,
327
+ probe: async (deps) => {
328
+ const { readFile } = deps;
329
+ if (!readFile) {
330
+ return { found: false, detail: "registration probe unavailable (no read-only filesystem access)" };
331
+ }
332
+ const result = await probeWorktreeMcpRegistration(deps.cwd, { readFile });
333
+ return { found: result.found, detail: result.detail };
334
+ },
335
+ };
336
+ }
253
337
  /**
254
338
  * The exact existing live-preflight requirement set per platform, plus the git
255
339
  * work-tree custom check appended after the command checks. This is the ONLY
@@ -286,7 +370,12 @@ export function getPreflightPrereqDescriptors(platform, env) {
286
370
  * additive (BAPI-302/303 regression-safe).
287
371
  */
288
372
  export function getDoctorOnlyPrereqDescriptors(_platform, _env, agent) {
289
- return [uvDescriptor(), agentDescriptor(agent)];
373
+ return [
374
+ uvDescriptor(),
375
+ agentDescriptor(agent),
376
+ credentialResolutionDescriptor(),
377
+ worktreeMcpReachabilityDescriptor(),
378
+ ];
290
379
  }
291
380
  /**
292
381
  * The doctor's full descriptor set: the preflight descriptors (verbatim) plus
@@ -53,7 +53,10 @@
53
53
  * unit-testable on Linux CI without spawning real commands or terminals.
54
54
  */
55
55
  import { execFile } from "child_process";
56
+ import { readFile, writeFile, mkdir } from "fs/promises";
56
57
  import path from "path";
58
+ import { VERSION } from "./version.generated.js";
59
+ import { provisionMcpRegistrationsForCreatedWorktrees, } from "./mcp-provisioning.js";
57
60
  // Per-OS prerequisite knowledge + low-level command probes live in the shared
58
61
  // prereqs module so `runPreflight` (enforce) and the read-only `doctor` (render)
59
62
  // can never drift. `start-tickets.ts` imports VALUES from there; the prereqs
@@ -75,6 +78,10 @@ export const DEFAULT_MAX_PARALLEL = 3;
75
78
  export const DEFAULT_TMUX_SESSION_PREFIX = "bridge-start-tickets";
76
79
  /** Environment variable overriding the tmux session-name prefix. */
77
80
  export const TMUX_SESSION_OVERRIDE_ENV = "BAPI_TMUX_SESSION";
81
+ /** Return a copy of `row` with `warning` appended; never mutates the input. */
82
+ export function appendSummaryRowWarning(row, warning) {
83
+ return { ...row, warnings: [...(row.warnings ?? []), warning] };
84
+ }
78
85
  // ---------------------------------------------------------------------------
79
86
  // Usage / argument parsing
80
87
  // ---------------------------------------------------------------------------
@@ -227,7 +234,7 @@ export function parseStartTicketsArgs(argv) {
227
234
  dryRun = true;
228
235
  continue;
229
236
  }
230
- if (arg === "--auto-approve") {
237
+ if (arg === "--auto") {
231
238
  autoApprove = true;
232
239
  continue;
233
240
  }
@@ -789,10 +796,10 @@ export async function createWorktrees(deps, options, worktrunkBinary) {
789
796
  /**
790
797
  * The starter prompt handed to the selected agent. Identical for every agent.
791
798
  * When `autoApprove` is set, the implementation agent runs hands-off
792
- * (`/implement-ticket <KEY> --auto-approve`) — used by full-automation chains.
799
+ * (`/implement-ticket <KEY> --auto`) — used by full-automation chains.
793
800
  */
794
801
  export function buildAgentPrompt(key, opts = {}) {
795
- return `/implement-ticket ${key}${opts.autoApprove ? " --auto-approve" : ""}`;
802
+ return `/implement-ticket ${key}${opts.autoApprove ? " --auto" : ""}`;
796
803
  }
797
804
  /**
798
805
  * Build the agent invocation (`<command> <quotedPrompt>`) for the agent's prompt
@@ -1111,10 +1118,31 @@ export function getDryRunPlatformDetails(agent, platform = process.platform, env
1111
1118
  buildAgentShellCommand: (key, worktreePath) => buildAgentShellCommand(agent, key, worktreePath, platform, autoApprove),
1112
1119
  };
1113
1120
  }
1121
+ /**
1122
+ * Build the dry-run preview of the secret-free MCP provisioning that
1123
+ * `start-tickets` would perform for a worktree whose `.bridge/config` includes
1124
+ * the `bapi` target. Lists both registration files and the version-pinned shim
1125
+ * command. Pure formatting — implies no credentials and writes no files.
1126
+ */
1127
+ export function buildDryRunMcpProvisioningLines(worktreePath, platform = process.platform) {
1128
+ const api = platform === "win32" ? path.win32 : path.posix;
1129
+ const mcpJson = api.join(worktreePath, ".mcp.json");
1130
+ const cursorJson = api.join(worktreePath, ".cursor", "mcp.json");
1131
+ const shim = `npx -y @bridge_gpt/mcp-server@${VERSION} mcp-invoke --target <target> --project-root ${worktreePath}`;
1132
+ return [
1133
+ "DRY-RUN: MCP provisioning (target-driven from .bridge/config — bapi plus any",
1134
+ "DRY-RUN: supported Tier-2 target such as sfcc): would write a secret-free shim",
1135
+ "DRY-RUN: entry per target to",
1136
+ `DRY-RUN: ${mcpJson}`,
1137
+ `DRY-RUN: ${cursorJson}`,
1138
+ `DRY-RUN: ${shim}`,
1139
+ ];
1140
+ }
1114
1141
  /**
1115
1142
  * Build the user-facing dry-run detail lines for one ticket, rendering the
1116
- * platform-correct Worktrunk binary and the selected agent's shell command. Pure
1117
- * platform formatting only — no preflight, no routing failures.
1143
+ * platform-correct Worktrunk binary, the selected agent's shell command, and
1144
+ * the secret-free MCP provisioning preview. Pure platform formatting only — no
1145
+ * preflight, no routing failures.
1118
1146
  */
1119
1147
  export function buildDryRunDetailLines(agent, key, branch, platform = process.platform, env = process.env, baseBranch = "main", autoApprove = false) {
1120
1148
  const { worktrunkBinary, buildAgentShellCommand: build } = getDryRunPlatformDetails(agent, platform, env, autoApprove);
@@ -1124,6 +1152,7 @@ export function buildDryRunDetailLines(agent, key, branch, platform = process.pl
1124
1152
  `DRY-RUN: ${key} -> branch=${branch}`,
1125
1153
  `DRY-RUN: ${worktrunkBinary} ${wtArgs.join(" ")}`,
1126
1154
  `DRY-RUN: ${agentInvocation}`,
1155
+ ...buildDryRunMcpProvisioningLines("<worktree-path>", platform),
1127
1156
  ];
1128
1157
  }
1129
1158
  /**
@@ -1140,13 +1169,30 @@ export function formatSummaryReport(rows) {
1140
1169
  line += ` path=${row.path}`;
1141
1170
  lines.push(line);
1142
1171
  }
1143
- const failures = rows.filter((r) => r.status === "create-failed" || r.status === "spawn-failed");
1144
- if (failures.length > 0) {
1172
+ // Warnings section: create/spawn-failed row errors AND any non-fatal
1173
+ // per-row provisioning warnings. Identical error/warning text for the same
1174
+ // row is emitted only once.
1175
+ const warningLines = [];
1176
+ for (const row of rows) {
1177
+ const messages = [];
1178
+ if (row.status === "create-failed" || row.status === "spawn-failed") {
1179
+ messages.push(row.error ?? row.status);
1180
+ }
1181
+ for (const warning of row.warnings ?? []) {
1182
+ messages.push(warning);
1183
+ }
1184
+ const seen = new Set();
1185
+ for (const message of messages) {
1186
+ if (seen.has(message))
1187
+ continue;
1188
+ seen.add(message);
1189
+ warningLines.push(` ${row.key}: ${message}`);
1190
+ }
1191
+ }
1192
+ if (warningLines.length > 0) {
1145
1193
  lines.push("");
1146
1194
  lines.push("Warnings:");
1147
- for (const row of failures) {
1148
- lines.push(` ${row.key}: ${row.error ?? row.status}`);
1149
- }
1195
+ lines.push(...warningLines);
1150
1196
  }
1151
1197
  return lines.join("\n");
1152
1198
  }
@@ -1158,7 +1204,38 @@ export function formatSummaryReport(rows) {
1158
1204
  * using the platform-correct shell builder. Global preflight/refresh failures
1159
1205
  * are returned as `{ ok: false }`; per-ticket failures stay in the rows.
1160
1206
  */
1161
- export async function orchestrateStartTickets(deps, options) {
1207
+ /**
1208
+ * Build the `mcp-provisioning` filesystem boundary from `StartTicketsDeps`.
1209
+ * Provisioning needs real `fs/promises` ops (not part of the command-runner DI
1210
+ * boundary), but inherits the platform and cwd from the orchestration deps.
1211
+ */
1212
+ export function buildMcpProvisioningDeps(deps) {
1213
+ return {
1214
+ readFile: (filePath) => readFile(filePath, "utf-8"),
1215
+ writeFile: (filePath, data) => writeFile(filePath, data, "utf-8"),
1216
+ mkdir: (dirPath, options) => mkdir(dirPath, options),
1217
+ platform: deps.platform,
1218
+ cwd: deps.cwd,
1219
+ };
1220
+ }
1221
+ /**
1222
+ * Tier-3 file-credential materialization seam.
1223
+ *
1224
+ * The committed `.bridge/config` schema does not (yet) declare file-credential
1225
+ * entries — Tier-3 file materialization is a gated seam (see
1226
+ * `credential-materialization.ts`, whose Windows worktree-visible copy remains
1227
+ * disabled pending the open context-vs-server-only design decision). With no
1228
+ * file-credential configuration there is nothing to materialize, so the default
1229
+ * is a safe pass-through. The seam exists so `orchestrateStartTickets` calls it
1230
+ * in the correct order — AFTER MCP registration provisioning and BEFORE tab
1231
+ * spawning — once the schema and the design decision are resolved. Per-row
1232
+ * failures must mark only the affected row `spawn-failed`; normal symlinks are
1233
+ * never torn down on completion.
1234
+ */
1235
+ export async function materializeFileCredentialsForCreatedWorktrees(rows, _deps) {
1236
+ return rows;
1237
+ }
1238
+ export async function orchestrateStartTickets(deps, options, overrides = {}) {
1162
1239
  if (options.dryRun) {
1163
1240
  return { ok: true, rows: buildDryRunResults(options.keys, options.branchOverrides) };
1164
1241
  }
@@ -1183,9 +1260,24 @@ export async function orchestrateStartTickets(deps, options) {
1183
1260
  });
1184
1261
  if (!refresh.ok)
1185
1262
  return { ok: false, error: refresh.error };
1186
- const created = await createWorktrees(deps, options, platformConfig.config.worktrunkBinary);
1187
- const terminal = detectTerminal(options.terminal, deps.env);
1188
- const rows = await spawnTabsForCreatedWorktrees(deps, created, terminal, platformConfig.config.buildAgentShellCommand);
1263
+ const createWorktreesFn = overrides.createWorktrees ?? createWorktrees;
1264
+ const provisionFn = overrides.provisionMcpRegistrations ??
1265
+ ((rows, d) => provisionMcpRegistrationsForCreatedWorktrees(rows, buildMcpProvisioningDeps(d)));
1266
+ const materializeFn = overrides.materializeFileCredentials ?? materializeFileCredentialsForCreatedWorktrees;
1267
+ const detectTerminalFn = overrides.detectTerminal ?? detectTerminal;
1268
+ const spawnTabsFn = overrides.spawnTabsForCreatedWorktrees ?? spawnTabsForCreatedWorktrees;
1269
+ const created = await createWorktreesFn(deps, options, platformConfig.config.worktrunkBinary);
1270
+ // Synchronously provision secret-free worktree MCP registrations after
1271
+ // worktree creation and before launching the agent tab. Per-worktree
1272
+ // provisioning failures mark only that row `spawn-failed` (skipped by the
1273
+ // spawn step below) — they never abort the whole run.
1274
+ const provisioned = await provisionFn(created, deps);
1275
+ // Materialize any Tier-3 file credentials AFTER MCP registration provisioning
1276
+ // and BEFORE tab spawning (currently a pass-through seam; see
1277
+ // materializeFileCredentialsForCreatedWorktrees).
1278
+ const materialized = await materializeFn(provisioned, deps);
1279
+ const terminal = detectTerminalFn(options.terminal, deps.env);
1280
+ const rows = await spawnTabsFn(deps, materialized, terminal, platformConfig.config.buildAgentShellCommand);
1189
1281
  return { ok: true, rows };
1190
1282
  }
1191
1283
  /** Platform-specific guidance printed when one or more tabs fail to spawn. */
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Tier-2 third-party MCP target metadata + atomic env injection.
3
+ *
4
+ * Known third-party MCP targets (e.g. Salesforce `b2c-dx-mcp` via `sfcc`) are
5
+ * declared here as additive, data-only definitions: each names the secret env
6
+ * keys it needs. Provisioning and `mcp-invoke` consume these definitions without
7
+ * any target-specific branching, so adding a future Tier-2 target is a matter of
8
+ * adding one entry to the registry below.
9
+ *
10
+ * Credential resolution is delegated to the generic
11
+ * {@link resolveCredentialBundle}; the resulting env overlay is ATOMIC — either
12
+ * every required key resolves or the whole overlay fails. A half overlay (one of
13
+ * an `SFCC_CLIENT_ID`/`SFCC_CLIENT_SECRET` pair) is never returned. No secret
14
+ * value ever appears in an error message.
15
+ */
16
+ import { resolveCredentialBundle, } from "./credential-store.js";
17
+ // ---------------------------------------------------------------------------
18
+ // Registry — additive; add a new entry to support a new Tier-2 target.
19
+ // ---------------------------------------------------------------------------
20
+ const THIRD_PARTY_TARGETS = {
21
+ sfcc: {
22
+ target: "sfcc",
23
+ requiredEnvKeys: ["SFCC_CLIENT_ID", "SFCC_CLIENT_SECRET"],
24
+ },
25
+ };
26
+ /** Return the definition for a supported Tier-2 target, or `undefined`. */
27
+ export function getThirdPartyTargetDefinition(target) {
28
+ return THIRD_PARTY_TARGETS[target];
29
+ }
30
+ /** List all supported Tier-2 target identifiers. */
31
+ export function getSupportedThirdPartyTargets() {
32
+ return Object.keys(THIRD_PARTY_TARGETS);
33
+ }
34
+ // ---------------------------------------------------------------------------
35
+ // Manifest entry validation
36
+ // ---------------------------------------------------------------------------
37
+ /**
38
+ * Validate that a parsed manifest entry for a Tier-2 target declares everything
39
+ * needed to launch it: a non-empty `command`, an `args` array, and a non-empty
40
+ * `secret_bundle`. Errors name only the missing field, never any value.
41
+ */
42
+ export function validateThirdPartyTargetManifestEntry(entry) {
43
+ if (entry.command === undefined || entry.command.trim().length === 0) {
44
+ return { ok: false, error: `target '${entry.target}' requires a non-empty command` };
45
+ }
46
+ if (entry.args === undefined) {
47
+ return { ok: false, error: `target '${entry.target}' requires an args array` };
48
+ }
49
+ if (entry.secretBundle === undefined || entry.secretBundle.trim().length === 0) {
50
+ return { ok: false, error: `target '${entry.target}' requires a non-empty secret_bundle` };
51
+ }
52
+ return { ok: true };
53
+ }
54
+ // ---------------------------------------------------------------------------
55
+ // Atomic env resolution
56
+ // ---------------------------------------------------------------------------
57
+ /**
58
+ * Resolve the complete env overlay a Tier-2 target needs from its secret bundle.
59
+ * Delegates to {@link resolveCredentialBundle}; because that resolver fails
60
+ * unless every required key resolves, the returned overlay is atomic — there is
61
+ * no code path that returns a partial set of the target's secrets. Parent env
62
+ * values override store values key-by-key (handled by the generic resolver).
63
+ * Errors are secret-free and name the missing key(s).
64
+ */
65
+ export async function resolveThirdPartyTargetEnv(definition, secretBundle, deps) {
66
+ const result = await resolveCredentialBundle(secretBundle, definition.requiredEnvKeys, deps);
67
+ if (!result.ok) {
68
+ return { ok: false, error: result.error };
69
+ }
70
+ const env = {};
71
+ for (const key of definition.requiredEnvKeys) {
72
+ env[key] = result.values[key];
73
+ }
74
+ return { ok: true, env };
75
+ }
@@ -1,2 +1,2 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.1.17";
2
+ export const VERSION = "0.2.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.1.17",
3
+ "version": "0.2.0",
4
4
  "description": "Bridge API MCP server — exposes Jira endpoints as MCP tools for Claude Code agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,8 +20,8 @@
20
20
  "build": "node scripts/bundle-version.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && tsc",
21
21
  "postbuild": "node scripts/prepend-shebang.cjs",
22
22
  "start": "node build/index.js",
23
- "test": "node --test build/pipeline-utils.test.js build/update-check.test.js build/cli-upgrade.test.js build/decision-page-schema.test.js build/decision-page-template.test.js build/bundle-pipelines.test.js build/instructions-contract.test.js build/pipeline-orchestrator-persistence.test.js build/pipeline-orchestrator-execution.test.js build/pipeline-orchestrator-integration.test.js build/index-static.test.js build/start-tickets.test.js build/start-tickets-base-branch.test.js build/agent-registry.test.js build/start-tickets-prereqs.test.js build/doctor.test.js build/package-static.test.js build/chain-utils.test.js build/chain-orchestrator.test.js build/scheduler-backends/types.test.js build/scheduler-backends/escaping.test.js build/scheduler-backends/launchd.test.js build/scheduler-backends/task-scheduler.test.js build/scheduler-backends/systemd-user.test.js build/scheduler-backends/at-fallback.test.js build/scheduler-backends/index.test.js build/agent-launchers/claude.test.js build/agent-launchers/index.test.js build/schedule-store.test.js build/schedule-run.test.js",
24
- "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/start-tickets.integration.test.js build/integration/doctor.integration.test.js",
23
+ "test": "node --test build/pipeline-utils.test.js build/update-check.test.js build/cli-upgrade.test.js build/decision-page-schema.test.js build/decision-page-template.test.js build/bundle-pipelines.test.js build/instructions-contract.test.js build/pipeline-orchestrator-persistence.test.js build/pipeline-orchestrator-execution.test.js build/pipeline-orchestrator-integration.test.js build/index-static.test.js build/index-resolvers.test.js build/index-project-root.test.js build/index-pipelines.test.js build/index.test.js build/bridge-config.test.js build/credential-store.test.js build/mcp-invoke.test.js build/mcp-provisioning.test.js build/third-party-mcp-targets.test.js build/git-ignore-utils.test.js build/credential-materialization.test.js build/mcp-registration-doctor.test.js build/secret-safety.test.js build/start-tickets.test.js build/start-tickets-base-branch.test.js build/agent-registry.test.js build/start-tickets-prereqs.test.js build/doctor.test.js build/package-static.test.js build/chain-utils.test.js build/chain-orchestrator.test.js build/scheduler-backends/types.test.js build/scheduler-backends/escaping.test.js build/scheduler-backends/launchd.test.js build/scheduler-backends/task-scheduler.test.js build/scheduler-backends/systemd-user.test.js build/scheduler-backends/at-fallback.test.js build/scheduler-backends/index.test.js build/agent-launchers/claude.test.js build/agent-launchers/index.test.js build/schedule-store.test.js build/schedule-run.test.js build/agent-capabilities/cli.test.js build/agent-capabilities/runner.test.js build/agent-capabilities/probes.test.js build/agent-capabilities/reporter.test.js && node --experimental-test-module-mocks --test build/index-heavy-read-truncation.test.js build/index-artifacts.test.js build/index-brainstorm-filenames.test.js build/index-output-path.test.js build/index-generate-decision-page.test.js build/index-generate-decision-page.integration.test.js",
24
+ "test:integration": "node --test build/integration/refresh-main.integration.test.js build/integration/start-tickets.integration.test.js build/integration/doctor.integration.test.js build/integration/agent-capabilities.integration.test.js",
25
25
  "prepublishOnly": "npm run build && node scripts/verify-shebang.cjs"
26
26
  },
27
27
  "dependencies": {
@@ -20,13 +20,15 @@
20
20
  "auto_approve_external": "{auto_approve}"
21
21
  },
22
22
  "outputs": {
23
+ "child_ticket_keys": "child_ticket_keys",
24
+ "epic_parent_key": "epic_parent_key",
23
25
  "created_ticket_keys": "created_ticket_keys"
24
26
  }
25
27
  },
26
28
  {
27
29
  "pipeline_name": "review-ticket",
28
30
  "description": "Review each created ticket (one child pipeline run per ticket).",
29
- "fan_out_input": "created_ticket_keys",
31
+ "fan_out_input": "child_ticket_keys",
30
32
  "fan_out_variable": "ticket_key",
31
33
  "variables": {
32
34
  "ticket_key": "{ticket_key}"
@@ -22,7 +22,7 @@
22
22
  },
23
23
  {
24
24
  "type": "agent_task",
25
- "instruction_file": "execute-plan.md",
25
+ "instruction_file": "execute-plan-sectioned.md",
26
26
  "description": "Execute the implementation plan"
27
27
  },
28
28
  {
@@ -58,5 +58,31 @@
58
58
  "instruction_file": "monitor-ci-checks.md",
59
59
  "description": "Monitor CI checks and report results"
60
60
  }
61
- ]
61
+ ],
62
+ "tiered_executor_policy": {
63
+ "supported_hosts": ["claude_code"],
64
+ "sequential_only": true,
65
+ "tier_model_mapping": {
66
+ "cheap": "haiku",
67
+ "basic": "sonnet",
68
+ "premium": "opus"
69
+ },
70
+ "final_review_policy": {
71
+ "default": "premium_whole_diff_when_below_coordinator_tier_touched_code",
72
+ "skip_when": "entire_run_inline_default_at_coordinator_tier"
73
+ },
74
+ "escalation_policy": {
75
+ "max_section_escalations": 1,
76
+ "allowed_hops": {
77
+ "cheap": "basic",
78
+ "basic": "premium"
79
+ },
80
+ "final_review_fix_reverify_passes": 1
81
+ },
82
+ "budget_policy": {
83
+ "cache_hit_rate_source": "measurement_spike_go_marker",
84
+ "default_cache_hit_rate": 0.0,
85
+ "abort_mode": "inline_default"
86
+ }
87
+ }
62
88
  }
@@ -445,12 +445,12 @@ and raw protocol noise.** Sensitive values are redacted.
445
445
 
446
446
  ---
447
447
 
448
- ## Full 53-Tool Smoke Tier Matrix
448
+ ## Full 55-Tool Smoke Tier Matrix
449
449
 
450
450
  Every registered MCP tool appears **exactly once** below with its smoke tier.
451
451
  Tier 4 (mutating/hazardous) tools are **deferred in v1**.
452
452
 
453
- Coverage reconciliation: **8 + 18 + 1 + 9 + 17 = 53**.
453
+ Coverage reconciliation: **8 + 18 + 1 + 10 + 18 = 55**.
454
454
 
455
455
  | Tier | Tool | Default action / expected-empty / special handling |
456
456
  |---|---|---|
@@ -482,6 +482,7 @@ Coverage reconciliation: **8 + 18 + 1 + 9 + 17 = 53**.
482
482
  | Tier 1 | `get_pipeline_recipe` | Read-only; needs a pipeline name. |
483
483
  | Tier 2 | `generate_decision_page` | Local-only file-write canary; always run with the verbatim payload above. |
484
484
  | Tier 3 | `second_opinion` | Synchronous, token-costing; opt-in. |
485
+ | Tier 3 | `generate_image` | Synchronous, token/credit-costing; spends provider credits; opt-in. |
485
486
  | Tier 3 | `request_plan_generation` | Async (ticket-key based); retrieve with `get_plan`; opt-in. |
486
487
  | Tier 3 | `request_architecture` | Async (ticket-key based); retrieve with `get_architecture`; opt-in. |
487
488
  | Tier 3 | `request_clarifying_questions` | Async (ticket-key based); retrieve with `get_clarifying_questions`; opt-in. |
@@ -507,3 +508,4 @@ Coverage reconciliation: **8 + 18 + 1 + 9 + 17 = 53**.
507
508
  | Tier 4 | `regenerate_directory_map` | Hazardous; deferred in v1. |
508
509
  | Tier 4 | `run_full_automation` | Orchestration (dispatches mutating child pipelines / spawns worktrees); deferred in v1. |
509
510
  | Tier 4 | `resume_full_automation` | Orchestration; deferred in v1. |
511
+ | Tier 4 | `record_tiered_section_metric` | Writes server-side telemetry state (tiered_section_metrics row); deferred in v1. |