@bridge_gpt/mcp-server 0.2.59 → 0.2.62

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 (78) hide show
  1. package/README.md +181 -51
  2. package/build/agent-guidance.generated.js +1 -1
  3. package/build/agent-notes.js +7 -1
  4. package/build/commands.generated.js +6 -6
  5. package/build/conductor/bridge-api-client.js +406 -4
  6. package/build/conductor/doctor.js +255 -53
  7. package/build/conductor/epic-runtime.js +11 -2
  8. package/build/conductor/install-doctor.js +24 -2
  9. package/build/conductor/local-merge.js +319 -26
  10. package/build/conductor/merge-execution-contract.js +86 -0
  11. package/build/conductor/merge-execution-eligibility.js +130 -0
  12. package/build/conductor/readiness-sections.js +256 -16
  13. package/build/conductor/readiness.js +203 -29
  14. package/build/conductor/recovery-operations.js +8 -2
  15. package/build/connect-bitbucket-api.js +6 -1
  16. package/build/connect-bitbucket.js +398 -88
  17. package/build/credential-store.js +172 -0
  18. package/build/docs.generated.js +2 -2
  19. package/build/doctor.js +277 -15
  20. package/build/drive-epic.js +147 -12
  21. package/build/epic-implementer-bin.js +15 -15
  22. package/build/epic-implementer-claude-hook-bin.js +1 -1
  23. package/build/epic-implementer-env.js +43 -14
  24. package/build/epic-integration-pr.js +61 -15
  25. package/build/epic-launch-profile.js +455 -0
  26. package/build/executor/ci-rerun-job.js +269 -0
  27. package/build/executor/cli.js +40 -0
  28. package/build/executor/credentials.js +28 -1
  29. package/build/executor/env.js +60 -0
  30. package/build/executor/http-client.js +45 -1
  31. package/build/executor/index.js +21 -4
  32. package/build/executor/integration-job.js +759 -0
  33. package/build/executor/job-runner.js +218 -9
  34. package/build/executor/job-types.js +12 -2
  35. package/build/executor/merge-job.js +11 -2
  36. package/build/executor/observation.js +114 -7
  37. package/build/executor/runner.js +101 -1
  38. package/build/executor/service-lifecycle.js +136 -0
  39. package/build/executor/service-unit.js +427 -9
  40. package/build/executor/types.js +48 -0
  41. package/build/index.js +479 -240
  42. package/build/install-bridge-conductor.js +599 -14
  43. package/build/install-bridge.js +448 -46
  44. package/build/mcp-install-state.js +42 -4
  45. package/build/merge-pull-request.js +103 -10
  46. package/build/merge-workflow-codeowners.js +224 -0
  47. package/build/merge-workflow-config.js +187 -0
  48. package/build/merge-workflow-drift.js +97 -0
  49. package/build/merge-workflow-install.js +638 -0
  50. package/build/merge-workflow.js +956 -0
  51. package/build/pipelines.generated.js +3 -3
  52. package/build/plane/cli.js +24 -2
  53. package/build/plane/preflight.js +68 -0
  54. package/build/plane/supervisor.js +6 -0
  55. package/build/readme.generated.js +1 -1
  56. package/build/run-unit-tests-launcher.js +5 -0
  57. package/build/setup-epic.js +263 -38
  58. package/build/version.generated.js +3 -3
  59. package/package.json +3 -3
  60. package/build/conduct-epic/bridge-client.js +0 -773
  61. package/build/conduct-epic/checkpoint-store.js +0 -542
  62. package/build/conduct-epic/cli.js +0 -3145
  63. package/build/conduct-epic/cut-protocol.js +0 -392
  64. package/build/conduct-epic/lock.js +0 -302
  65. package/build/conduct-epic/pr-state.js +0 -286
  66. package/build/conduct-epic/spawn.js +0 -113
  67. package/build/conductor/cli.js +0 -1278
  68. package/build/conductor-bundle-cli.js +0 -256
  69. package/build/direct-ticket-tools.js +0 -70
  70. package/build/executor/reconcile-overlap-governance.js +0 -129
  71. package/build/implement-epic/bridge-client.js +0 -773
  72. package/build/implement-epic/checkpoint-store.js +0 -542
  73. package/build/implement-epic/cli.js +0 -3158
  74. package/build/implement-epic/cut-protocol.js +0 -392
  75. package/build/implement-epic/lock.js +0 -302
  76. package/build/implement-epic/pr-state.js +0 -286
  77. package/build/implement-epic/spawn.js +0 -113
  78. package/build/plan-epic-conductor-eligibility.js +0 -213
@@ -65,6 +65,11 @@ export const MODULE_MOCK_MANIFEST = [
65
65
  "build/conductor/store-lifecycle.test.js",
66
66
  "build/conductor/store-queries.test.js",
67
67
  "build/conductor/tools.test.js",
68
+ // BAPI-1247: drives offerBitbucketConnection to a chosen structured outcome by
69
+ // mocking the shared connect flow at install-bridge.ts's import site, which needs
70
+ // --experimental-test-module-mocks. Kept apart from install-bridge.test.js so that
71
+ // 346-test file stays in the NORMAL lane.
72
+ "build/connect-bitbucket-handoff.test.js",
68
73
  "build/connect-github-api.test.js",
69
74
  "build/connect-github-dispatch.static.test.js",
70
75
  "build/connect-github-handoff.test.js",
@@ -78,7 +78,14 @@
78
78
  import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
79
79
  import os from "node:os";
80
80
  import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullRequest, formatEpicIntegrationPullRequestOutcome, } from "./epic-integration-pr.js";
81
- import { approveEpicPlan, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
81
+ import { approveEpicPlan, buildConductorJiraUrl, conductorGetHeaders, createEpicRunWithDisposition, decodeCanonicalProvenance, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
82
+ // BAPI-1133 (A2) — the single legacy-to-typed merge-execution-mode mapping and
83
+ // its total legacy resolver live in `merge-pull-request.ts` (no import cycle:
84
+ // that module never imports from `setup-epic.ts`). Reused here rather than
85
+ // reimplemented so the mapping stays single-defined.
86
+ import { mapLegacyMergeExecutionModeToPolicyMode, resolveMergeExecutionMode, } from "./merge-pull-request.js";
87
+ import { MERGE_EXECUTION_INELIGIBLE, MERGE_EXECUTION_MODE_GITHUB_ACTIONS, MERGE_WORKFLOW_REMEDY_COMMAND, } from "./conductor/merge-execution-contract.js";
88
+ import { classifyMergeExecutionEligibility } from "./conductor/merge-execution-eligibility.js";
82
89
  import { resolveTrackedPaths } from "./git-reachability.js";
83
90
  import { validateBranchName } from "./base-ref.js";
84
91
  // Reused from the implement-epic client on purpose: adding a second wrapper under
@@ -90,7 +97,7 @@ import { validateBranchName } from "./base-ref.js";
90
97
  // conductor can drift on a field name or an endpoint shape. No MCP tool, no
91
98
  // input-schema field, and no description changes.
92
99
  import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./epic-shared/bridge-client.js";
93
- import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./epic-shared/cut-protocol.js";
100
+ import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./epic-shared/cut-protocol.js";
94
101
  import { hashPlan } from "./conductor/plan.js";
95
102
  import { BRING_UP_REFUSAL_MESSAGES, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
96
103
  import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
@@ -128,6 +135,29 @@ export const SETUP_EPIC_UNATTENDED_POSTURE_REFUSED_CODE = "UNATTENDED_POSTURE_RE
128
135
  * which is what the observed BAPI-1085 run did.
129
136
  */
130
137
  export const SETUP_EPIC_AUTO_MERGE_POLICY_KEY = "v2_auto_merge_enabled";
138
+ /**
139
+ * The `policy_json` key declaring the typed merge-execution mode (BAPI-1133, A2).
140
+ *
141
+ * Composed into the unattended default alongside `posture` and
142
+ * {@link SETUP_EPIC_AUTO_MERGE_POLICY_KEY}, in the SAME fill-if-absent branch,
143
+ * because the server's `RunPolicy` refuses an unattended auto-merge policy that
144
+ * omits it (`merge_execution_mode_unconfigured`). `setup-epic` is the only
145
+ * IMPLICIT writer of this key — any other client may still submit it explicitly
146
+ * as operator policy, and an explicitly supplied value here is never overwritten.
147
+ * Declaring `github_actions` or `machine_token` is intent only in this slice:
148
+ * neither changes merge execution until a later A2 child.
149
+ */
150
+ export const SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY = "merge_execution_mode";
151
+ /**
152
+ * BAPI-1259 — the opt-in for automatic integration-PR preparation. Written into
153
+ * the UNATTENDED epic-branch default only (fill-if-absent), so a run the
154
+ * operator starts unattended is prepared and handed off without a manual base
155
+ * merge or a manual un-draft. Attended runs, `--into-base` runs, and runs whose
156
+ * `--policy-file` already declares the key keep exactly what they had; a run
157
+ * approved before this key existed is never changed retroactively.
158
+ */
159
+ export const SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY = "integration_preparation";
160
+ const SETUP_EPIC_INTEGRATION_PREPARATION_DEFAULT = { enabled: true };
131
161
  /** Accepted `policy_json.review_policy.source` values (the `ReviewPolicy` surface). */
132
162
  export const SETUP_EPIC_REVIEW_POLICY_SOURCES = [
133
163
  "verdict_protocol",
@@ -169,12 +199,15 @@ export function getSetupEpicUsage() {
169
199
  "repository base branch, matching plain start-tickets semantics.",
170
200
  "",
171
201
  "Posture (default): a run that selects an epic branch is created UNATTENDED —",
172
- "posture: unattended and v2_auto_merge_enabled: true — and the server stamps its",
173
- "CI gate at first approval from the repository default, or records an explicit",
174
- "no-CI-gate mode when the repository declares none. Child PRs merge into the epic",
175
- "branch; the INTEGRATION PR into the repository base branch stays human-gated.",
176
- "Pass --attended to opt out. A run that selects no epic branch (--into-base, or a",
177
- "single-node plan) is unchanged and composes no posture at all.",
202
+ "posture: unattended, v2_auto_merge_enabled: true, and merge_execution_mode",
203
+ "(declared automatically from the repository's configured merge execution; a",
204
+ "policy declaration only — it does not change merge execution in this release)",
205
+ "— and the server stamps its CI gate at first approval from the repository",
206
+ "default, or records an explicit no-CI-gate mode when the repository declares",
207
+ "none. Child PRs merge into the epic branch; the INTEGRATION PR into the",
208
+ "repository base branch stays human-gated. Pass --attended to opt out. A run",
209
+ "that selects no epic branch (--into-base, or a single-node plan) is unchanged",
210
+ "and composes no posture at all.",
178
211
  "",
179
212
  "Unattended runs have TWO one-time repository prerequisites, refused by name",
180
213
  "before any run is created: repository consent (unattended_epic_implementer_allowed) and",
@@ -226,6 +259,10 @@ export function getSetupEpicUsage() {
226
259
  " sets no review policy of its own, so a value supplied",
227
260
  " here always wins.",
228
261
  " --json Emit a single JSON result object on stdout",
262
+ " --launch-profile <name> (BAPI-1260) Resolve the target, repository, and bound",
263
+ " credential from this host-local launch profile. Passed",
264
+ " by drive-epic; there is no production default with it.",
265
+ " --base-url <url> (BAPI-1260) Explicit target; must agree with the profile.",
229
266
  " -h, --help Show this help",
230
267
  "",
231
268
  "Policy controls:",
@@ -326,6 +363,8 @@ export function parseSetupEpicArgs(argv) {
326
363
  let localOnly = false;
327
364
  let dryRun = false;
328
365
  let json = false;
366
+ let launchProfile;
367
+ let baseUrl;
329
368
  for (let i = 0; i < argv.length; i++) {
330
369
  const arg = argv[i];
331
370
  // `--policy-file=<path>` inline form (BAPI-754).
@@ -427,6 +466,19 @@ export function parseSetupEpicArgs(argv) {
427
466
  i++;
428
467
  break;
429
468
  }
469
+ // BAPI-1260 — the launch selection forwarded by drive-epic.
470
+ case "--launch-profile":
471
+ case "--base-url": {
472
+ const v = takeValue(argv, i, arg);
473
+ if (v === null || v.trim() === "")
474
+ return { status: "error", message: `${arg} requires a value.` };
475
+ if (arg === "--launch-profile")
476
+ launchProfile = v.trim();
477
+ else
478
+ baseUrl = v.trim();
479
+ i++;
480
+ break;
481
+ }
430
482
  case "--into-base":
431
483
  intoBase = true;
432
484
  break;
@@ -508,6 +560,9 @@ export function parseSetupEpicArgs(argv) {
508
560
  localOnly,
509
561
  dryRun,
510
562
  json,
563
+ // BAPI-1260 — only when supplied, so a legacy options object is unchanged.
564
+ ...(launchProfile ? { launchProfile } : {}),
565
+ ...(baseUrl ? { baseUrl } : {}),
511
566
  },
512
567
  };
513
568
  }
@@ -1244,6 +1299,66 @@ export function evaluateSetupEpicBringUpPrerequisites(readiness) {
1244
1299
  return null;
1245
1300
  return { reason, message: `${BRING_UP_REFUSAL_MESSAGES[reason]} ${NO_RUN_WAS_CREATED}` };
1246
1301
  }
1302
+ /**
1303
+ * Read the composed policy's typed `merge_execution_mode`, if it declares one.
1304
+ *
1305
+ * Strict equality against the mirrored constant, matching the server's own guard
1306
+ * (`preflight._strict_github_actions_mode`). A missing key, a non-string, and
1307
+ * any other mode all answer `false`, so nothing but an explicit declaration
1308
+ * opens the gate.
1309
+ */
1310
+ export function policyDeclaresGitHubActionsMerge(policy) {
1311
+ if (policy === undefined || policy === null)
1312
+ return false;
1313
+ return policy.merge_execution_mode === MERGE_EXECUTION_MODE_GITHUB_ACTIONS;
1314
+ }
1315
+ /**
1316
+ * Evaluate merge-execution eligibility against a readiness report.
1317
+ *
1318
+ * Pure and total. Returns `null` — no refusal — for every policy that does not
1319
+ * declare `github_actions` mode, without reading the report at all.
1320
+ *
1321
+ * Uses {@link classifyMergeExecutionEligibility}, the same classifier readiness
1322
+ * and `drive-epic` read, so all three agree on the status AND on the first
1323
+ * blocker. A client that classified the block itself here would eventually
1324
+ * disagree with the row an operator had just been shown.
1325
+ *
1326
+ * BOTH `not_eligible` and `could_not_verify` return
1327
+ * `merge_execution_ineligible`. One code, because the operator's next action is
1328
+ * the same and splitting it would put the burden of telling "not proven" from
1329
+ * "proven absent" on the person least able to check. The MESSAGE keeps the
1330
+ * capability category explicit, so the distinction survives where it is
1331
+ * actionable.
1332
+ *
1333
+ * The message is built from the shared remedy constant, ends with the exact
1334
+ * standalone sentence `No run was created.`, and carries no GitHub payload, URL,
1335
+ * branch name, workflow source, credential, or exception text.
1336
+ */
1337
+ export function evaluateSetupEpicMergeExecutionPrerequisites(readiness, requestedPolicy) {
1338
+ if (!policyDeclaresGitHubActionsMerge(requestedPolicy))
1339
+ return null;
1340
+ if (readiness === null) {
1341
+ return {
1342
+ reason: MERGE_EXECUTION_INELIGIBLE,
1343
+ message: "this run declares merge_execution_mode: github_actions, and the Bridge API " +
1344
+ "conductor readiness report could not be read, so the repository's merge " +
1345
+ "capability could not be confirmed. Retry once the server is reachable, or " +
1346
+ `resolve it with \`${MERGE_WORKFLOW_REMEDY_COMMAND}\`. ${NO_RUN_WAS_CREATED}`,
1347
+ };
1348
+ }
1349
+ const verdict = classifyMergeExecutionEligibility(readiness);
1350
+ if (verdict.status === "eligible")
1351
+ return null;
1352
+ const category = verdict.status === "could_not_verify"
1353
+ ? "could not be confirmed"
1354
+ : "is not sufficient";
1355
+ return {
1356
+ reason: MERGE_EXECUTION_INELIGIBLE,
1357
+ message: "this run declares merge_execution_mode: github_actions, and this repository's " +
1358
+ `merge capability ${category}: ${verdict.detail}. Resolve it with ` +
1359
+ `\`${MERGE_WORKFLOW_REMEDY_COMMAND}\`, then re-run. ${NO_RUN_WAS_CREATED}`,
1360
+ };
1361
+ }
1247
1362
  /**
1248
1363
  * Does the policy this invocation will actually send request unattended operation?
1249
1364
  *
@@ -1283,8 +1398,11 @@ export function requestedPolicyIsUnattended(policy, attended) {
1283
1398
  * * **Feature branch, `--attended`** → `base_branch` plus an explicitly
1284
1399
  * requested `review_policy`, and nothing else. Byte-identical to the
1285
1400
  * pre-BAPI-1102 composition, which is precisely what the opt-out promises.
1286
- * * **Feature branch, default** → adds `posture: "unattended"` and
1287
- * `v2_auto_merge_enabled: true`.
1401
+ * * **Feature branch, default** → adds `posture: "unattended"`,
1402
+ * `v2_auto_merge_enabled: true`, (BAPI-1133, A2) `merge_execution_mode`
1403
+ * when the caller resolved one — the server refuses an unattended auto-merge
1404
+ * policy that omits it — and (BAPI-1259) `integration_preparation:
1405
+ * { enabled: true }`, so the run prepares its own integration PR.
1288
1406
  *
1289
1407
  * Deliberately absent from every shape:
1290
1408
  *
@@ -1306,6 +1424,12 @@ export function composeDefaultSetupEpicPolicy(args) {
1306
1424
  if (!args.attended) {
1307
1425
  policy.posture = SETUP_EPIC_UNATTENDED_POSTURE;
1308
1426
  policy[SETUP_EPIC_AUTO_MERGE_POLICY_KEY] = true;
1427
+ if (args.mergeExecutionMode !== undefined) {
1428
+ policy[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] = args.mergeExecutionMode;
1429
+ }
1430
+ policy[SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY] = {
1431
+ ...SETUP_EPIC_INTEGRATION_PREPARATION_DEFAULT,
1432
+ };
1309
1433
  }
1310
1434
  }
1311
1435
  if (args.reviewPolicy !== undefined) {
@@ -1346,6 +1470,14 @@ export function composeDefaultSetupEpicPolicy(args) {
1346
1470
  * * **`v2_auto_merge_enabled` already present** → left exactly as the file set
1347
1471
  * it, including `false`. Filling the posture does not overwrite an explicit
1348
1472
  * merge decision.
1473
+ * * **`merge_execution_mode` already present** (BAPI-1133, A2) → left exactly
1474
+ * as the file set it, valid or not. An explicitly present but invalid mode is
1475
+ * NOT silently repaired here; it is forwarded unchanged so the server's
1476
+ * secret-safe typed validation rejects it. Absent, it is filled from the
1477
+ * caller's already-resolved {@link TypedMergeExecutionMode} in the same
1478
+ * fill-if-absent branch that stamps `posture` and
1479
+ * {@link SETUP_EPIC_AUTO_MERGE_POLICY_KEY} — the server refuses an
1480
+ * unattended auto-merge policy that omits it.
1349
1481
  *
1350
1482
  * Pure: it returns a new object and never mutates the input. It is applied on the
1351
1483
  * CREATE path only. A reused live run reconciles the file against its STORED
@@ -1365,6 +1497,15 @@ export function applySetupEpicUnattendedDefault(policy, args) {
1365
1497
  if (!(SETUP_EPIC_AUTO_MERGE_POLICY_KEY in composed)) {
1366
1498
  composed[SETUP_EPIC_AUTO_MERGE_POLICY_KEY] = true;
1367
1499
  }
1500
+ if (!(SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY in composed) &&
1501
+ args.mergeExecutionMode !== undefined) {
1502
+ composed[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] = args.mergeExecutionMode;
1503
+ }
1504
+ if (!(SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY in composed)) {
1505
+ composed[SETUP_EPIC_INTEGRATION_PREPARATION_POLICY_KEY] = {
1506
+ ...SETUP_EPIC_INTEGRATION_PREPARATION_DEFAULT,
1507
+ };
1508
+ }
1368
1509
  return composed;
1369
1510
  }
1370
1511
  /**
@@ -1554,14 +1695,51 @@ export function resolveEffectiveFeatureBranch(featureBranch, requestedPolicy) {
1554
1695
  return trimmed;
1555
1696
  }
1556
1697
  /**
1557
- * Read canonical parse truth and require a `succeeded` parse with a full
1558
- * indexed commit — the commit `origin/<feature>` will be cut at.
1698
+ * BAPI-1252: the operator sentence for each canonical refusal. Every one names
1699
+ * the situation and what to do; none names a SHA, generation, or server error.
1700
+ */
1701
+ export function canonicalRefusalMessage(repoName, reason) {
1702
+ switch (reason) {
1703
+ case "canonical_capability_unsupported":
1704
+ return (`The Bridge server for ${repoName} did not report verified canonical index provenance ` +
1705
+ `(it predates this contract, or sent a value this CLI cannot verify). Upgrade the ` +
1706
+ `server; a cut is never authorized from the legacy indexed commit.`);
1707
+ case "canonical_index_unverified":
1708
+ return (`${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the canonical index for ${repoName} has no verified ` +
1709
+ `generation (legacy, interrupted, or still-writing content is never trusted), so there ` +
1710
+ `is no commit to cut the feature branch at.`);
1711
+ case "canonical_source_missing":
1712
+ return (`${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the last canonical parse of ${repoName} observed no ` +
1713
+ `source commit (for example it parsed a supplied directory), so its content cannot be ` +
1714
+ `verified. Re-parse it from its remote.`);
1715
+ case "canonical_manifest_mismatch":
1716
+ return (`The canonical index content for ${repoName} no longer matches its verified manifest ` +
1717
+ `and has been marked unverified. Re-parse the repository before cutting.`);
1718
+ case "canonical_generation_mismatch":
1719
+ case "canonical_index_advanced":
1720
+ return (`The canonical index for ${repoName} advanced while it was being checked. Retry once ` +
1721
+ `the current parse finishes.`);
1722
+ case "canonical_validity_unavailable":
1723
+ default:
1724
+ return (`The canonical validity for ${repoName} could not be read. Retry — this is not ` +
1725
+ `evidence the repository is unindexed.`);
1726
+ }
1727
+ }
1728
+ /**
1729
+ * Read canonical parse truth and require AFFIRMATIVE verified provenance — the
1730
+ * commit `origin/<feature>` will be cut at (BAPI-1252).
1731
+ *
1732
+ * The cut commit is the server's VERIFIED canonical source SHA, decoded through
1733
+ * `decodeCanonicalProvenance`. Lifecycle fields (`status`, `indexed_commit_sha`)
1734
+ * are display history and are never consulted: a `succeeded` row can describe a
1735
+ * newer-created parse whose content the index does not hold. An absent field
1736
+ * (an older server), an unknown state, a read failure, or an unverified
1737
+ * generation all refuse before any branch or run mutation.
1559
1738
  *
1560
1739
  * Fails CLOSED (ratified R-2). The first line of every refusal is the exact
1561
- * operator instruction; the closing lines state what did NOT happen. An
1562
- * UNREADABLE status is kept distinct from an unsuccessful one — "could not read"
1563
- * is not evidence the repository is unindexed, and telling that operator to
1564
- * re-index would send them to re-parse a repository that may be fine.
1740
+ * operator instruction; the closing line states what did NOT happen. An
1741
+ * UNREADABLE status is kept distinct from an unverified one — "could not read"
1742
+ * is not evidence the repository is unindexed.
1565
1743
  */
1566
1744
  export async function readCanonicalParseTruth(access, fetchImpl) {
1567
1745
  const notCreated = [
@@ -1578,30 +1756,14 @@ export async function readCanonicalParseTruth(access, fetchImpl) {
1578
1756
  ],
1579
1757
  };
1580
1758
  }
1581
- if (parse.value.status !== "succeeded") {
1582
- return {
1583
- ok: false,
1584
- lines: [
1585
- `${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the canonical index for ${access.repoName} has no ` +
1586
- `successful parse (status: ${String(parse.value.status)}), so there is no commit to ` +
1587
- `cut the feature branch at.`,
1588
- ...notCreated,
1589
- ],
1590
- };
1591
- }
1592
- const indexed = normalizeCommitSha(parse.value.indexed_commit_sha);
1593
- if (indexed === null) {
1759
+ const evidence = decodeCanonicalProvenance(parse.value);
1760
+ if (!evidence.verified) {
1594
1761
  return {
1595
1762
  ok: false,
1596
- lines: [
1597
- `${SETUP_EPIC_PARSE_FIRST_MESSAGE}: the canonical index for ${access.repoName} published ` +
1598
- `no commit for its last successful parse, so there is no commit to cut the feature ` +
1599
- `branch at.`,
1600
- ...notCreated,
1601
- ],
1763
+ lines: [canonicalRefusalMessage(access.repoName, evidence.reason), ...notCreated],
1602
1764
  };
1603
1765
  }
1604
- return { ok: true, cutCommitSha: indexed };
1766
+ return { ok: true, cutCommitSha: evidence.sourceCommitSha };
1605
1767
  }
1606
1768
  /** The strict subset of `SetupEpicDeps` the shared cut protocol consumes. */
1607
1769
  function cutProtocolDeps(deps, runCommand) {
@@ -1836,7 +1998,10 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
1836
1998
  return 0;
1837
1999
  }
1838
2000
  // --- Resolve access ------------------------------------------------------
1839
- const accessResult = await resolveConductorBridgeApiAccess({
2001
+ // BAPI-1260: a caller that selected the epic-launch contract (drive-epic
2002
+ // forwards --launch-profile / --base-url) gets the target-first resolver and
2003
+ // the bound credential; the legacy resolver is kept byte-for-byte otherwise.
2004
+ const accessDeps = {
1840
2005
  env: deps.env,
1841
2006
  cwd: deps.cwd,
1842
2007
  homedir: deps.homedir,
@@ -1844,7 +2009,13 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
1844
2009
  readFile: deps.readFile,
1845
2010
  stat: deps.stat,
1846
2011
  repoName: opts.repo,
1847
- });
2012
+ };
2013
+ const accessResult = opts.launchProfile !== undefined || opts.baseUrl !== undefined
2014
+ ? await resolveEpicLaunchBridgeApiAccess({
2015
+ ...(opts.launchProfile ? { profileName: opts.launchProfile } : {}),
2016
+ ...(opts.baseUrl ? { baseUrl: opts.baseUrl } : {}),
2017
+ }, accessDeps)
2018
+ : await resolveConductorBridgeApiAccess(accessDeps);
1848
2019
  if (!accessResult.ok) {
1849
2020
  deps.errorLog(`Cannot reach the Bridge API: ${accessResult.error}`);
1850
2021
  return 1;
@@ -1873,6 +2044,27 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
1873
2044
  // `base_branch` is honored exactly as `--feature-branch` would be. A malformed
1874
2045
  // or unknown policy field is left for the server's strict validation.
1875
2046
  const effectiveFeatureBranch = resolveEffectiveFeatureBranch(featureBranch, requestedPolicy);
2047
+ // --- Typed merge-execution-mode declaration (BAPI-1133, A2) ---------------
2048
+ // Resolved ONCE, here, before either composer runs, so the composer functions
2049
+ // stay pure and synchronous. Gated on the exact predicate both composers'
2050
+ // OWN fill-if-absent branches use — `!attended`, a feature branch selected,
2051
+ // and (for the file path) no `posture` already declared — so this never
2052
+ // performs the new configuration read for `--attended` or `--into-base` runs,
2053
+ // and never for a file that already answered the posture question itself.
2054
+ const unattendedDefaultEligible = !opts.attended &&
2055
+ effectiveFeatureBranch !== undefined &&
2056
+ (requestedPolicy === undefined || !("posture" in requestedPolicy));
2057
+ let declaredMergeExecutionMode;
2058
+ if (unattendedDefaultEligible) {
2059
+ const mergeExecutionModeResolverDeps = {
2060
+ repoName: access.repoName,
2061
+ buildGetUrl: (path, params) => buildConductorJiraUrl(access.baseUrl, path, params),
2062
+ getHeaders: async () => conductorGetHeaders(access),
2063
+ fetchImpl: deps.fetch,
2064
+ };
2065
+ const effectiveLegacyMode = await resolveMergeExecutionMode(mergeExecutionModeResolverDeps);
2066
+ declaredMergeExecutionMode = mapLegacyMergeExecutionModeToPolicyMode(effectiveLegacyMode);
2067
+ }
1876
2068
  // --- The policy the CREATE path will send (BAPI-1102) ---------------------
1877
2069
  // Composed HERE, as soon as both of its inputs exist, so the one object that
1878
2070
  // gets reported, gated, and sent is the same object throughout. It is consumed
@@ -1883,12 +2075,30 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
1883
2075
  ? applySetupEpicUnattendedDefault(requestedPolicy, {
1884
2076
  featureBranch: effectiveFeatureBranch,
1885
2077
  attended: opts.attended,
2078
+ mergeExecutionMode: declaredMergeExecutionMode,
1886
2079
  })
1887
2080
  : composeDefaultSetupEpicPolicy({
1888
2081
  featureBranch,
1889
2082
  attended: opts.attended,
1890
2083
  reviewPolicy: opts.reviewPolicy,
2084
+ mergeExecutionMode: declaredMergeExecutionMode,
1891
2085
  });
2086
+ // BAPI-1133 (A2). Reported only when THIS command stamped the key — i.e. it
2087
+ // is present in the composed policy and was absent from whatever the
2088
+ // operator supplied — as low-emphasis policy metadata, mirroring the
2089
+ // existing posture/auto-merge reporting rather than adding a new prompt,
2090
+ // wizard, or runtime status. An explicitly declared mode (CLI-composed
2091
+ // policy already carrying the key) is never reported here as if it were
2092
+ // this command's decision.
2093
+ const mergeExecutionModeStamped = declaredMergeExecutionMode !== undefined &&
2094
+ composedForCreate[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] === declaredMergeExecutionMode &&
2095
+ (requestedPolicy === undefined ||
2096
+ !(SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY in requestedPolicy));
2097
+ if (mergeExecutionModeStamped) {
2098
+ say(`Policy: merge_execution_mode: ${declaredMergeExecutionMode} (declared automatically ` +
2099
+ `for the unattended default; policy metadata only — no dispatch, actuation, eligibility, ` +
2100
+ `containment, or escalation behavior until a later A2 child)`);
2101
+ }
1892
2102
  if (requestedPolicy !== undefined) {
1893
2103
  // The posture is stated for the FILE path too, and read off the composed
1894
2104
  // object rather than re-derived. The `--policy-file` invocation is the one
@@ -2430,6 +2640,21 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
2430
2640
  deps.errorLog(`setup-epic REFUSED (${bringUpRefusal.reason}): ${bringUpRefusal.message}`);
2431
2641
  return 1;
2432
2642
  }
2643
+ // BAPI-1135 — merge-execution eligibility, AFTER the universal bring-up
2644
+ // blockers (an unreadable readiness report and a behind database make this
2645
+ // question moot) and BEFORE the unattended prerequisites and every
2646
+ // approval check below. That position is what keeps readiness, this gate,
2647
+ // and the approval refusal naming the SAME first blocker.
2648
+ //
2649
+ // Outside the `requestedPolicyIsUnattended` branch on purpose: an
2650
+ // `--attended` run that declares `github_actions` mode is gated too. The
2651
+ // evaluator itself returns null for every policy that does not declare the
2652
+ // mode, so no other run's behavior changes.
2653
+ const mergeExecutionRefusal = evaluateSetupEpicMergeExecutionPrerequisites(readiness, composedForCreate);
2654
+ if (mergeExecutionRefusal !== null) {
2655
+ deps.errorLog(`setup-epic REFUSED (${mergeExecutionRefusal.reason}): ${mergeExecutionRefusal.message}`);
2656
+ return 1;
2657
+ }
2433
2658
  if (requestedPolicyIsUnattended(composedForCreate, opts.attended)) {
2434
2659
  // The COMPOSED policy, not the flags: a --policy-file declaring
2435
2660
  // notify.webhook_url satisfies the escalation-channel prerequisite, and
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
2
- export const VERSION = "0.2.59";
3
- export const BUILD_COMMIT = "6caab78d0b75";
4
- export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.59", "serve"];
2
+ export const VERSION = "0.2.62";
3
+ export const BUILD_COMMIT = "9351fac413bf";
4
+ export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.62", "serve"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge_gpt/mcp-server",
3
- "version": "0.2.59",
3
+ "version": "0.2.62",
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",
@@ -26,7 +26,7 @@
26
26
  "//test": "The `test` script delegates to the bounded launcher (scripts/run-unit-tests.js → src/run-unit-tests-launcher.ts, BAPI-683), which discovers build/*.test.js at runtime and spawns them in size-bounded batches so the argv never exceeds Windows' ~8191-char command-line limit. Two determinism rules from the pre-launcher script are preserved INSIDE the launcher: (1) multi-file batches run WITHOUT --test-force-exit, because that flag makes node:test exit before aggregating per-subprocess summaries and the tally becomes nondeterministic (failures still surface and still set exit 1, but the count cannot be trusted as a completeness signal); (2) build/secret-safety.test.js leaks a handle that holds the event loop ~60s after its tests finish (they run in ~147ms), so the launcher quarantines it into its own single-file --test-force-exit invocation (FORCE_EXIT_QUARANTINE in src/run-unit-tests-launcher.ts) where the tally stays exact. Do NOT add --test-force-exit to batched invocations, and do NOT remove the quarantine, until the underlying handle leak is fixed.",
27
27
  "//version": "BAPI-930: fired by `npm version <level>` AFTER npm rewrites package.json's version field, so the bumped version and the repinned README land in the SAME commit. A bump performed by hand-editing package.json instead of running `npm version` bypasses this hook entirely and must repin manually via `node scripts/sync-readme-host-examples.js` — the `--check` wired into `build` below is the backstop that catches that case.",
28
28
  "scripts": {
29
- "build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
29
+ "build": "node scripts/bundle-version.js && node scripts/sync-readme-host-examples.js --check && node scripts/validate-packaged-command-references.js && node scripts/bundle-readme.js && node scripts/bundle-pipelines.js && node scripts/bundle-commands.js && node scripts/bundle-agents.js && node scripts/bundle-docs.js && node scripts/bundle-agent-guidance.js && tsc && node scripts/sync-readme-merge-workflow.js --check && node scripts/sync-agent-mirrors.js && node scripts/bundle-esbuild.js",
30
30
  "version": "node scripts/sync-readme-host-examples.js --write && git add -- README.md package.json package-lock.json && git commit -m \"chore(mcp-server): release $npm_package_version\" && git tag \"mcp-server/v$npm_package_version\"",
31
31
  "check:version-generated": "node scripts/bundle-version.js && node scripts/check-version-generated.js",
32
32
  "assert:release-identity": "node scripts/assert-release-identity.js",
@@ -51,7 +51,7 @@
51
51
  "playwright": "^1.51.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@types/better-sqlite3": "^7.6.12",
54
+ "@types/better-sqlite3": "^9.6.0",
55
55
  "@types/node": "^26.0.1",
56
56
  "@types/pngjs": "^6.0.5",
57
57
  "esbuild": "^0.28.1",