@bridge_gpt/mcp-server 0.2.60 → 0.2.65

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 (89) hide show
  1. package/README.md +58 -11
  2. package/build/agent-launchers/claude-executor-adapter.js +134 -3
  3. package/build/agent-launchers/executor-adapter.js +54 -2
  4. package/build/agents.generated.js +10 -1
  5. package/build/bounded-wait.js +32 -1
  6. package/build/commands.generated.js +8 -7
  7. package/build/conductor/bridge-api-client.js +678 -14
  8. package/build/conductor/bring-up-facts.js +3 -1
  9. package/build/conductor/doctor.js +23 -14
  10. package/build/conductor/epic-child-continuation.js +324 -0
  11. package/build/conductor/epic-implementer-cli.js +142 -17
  12. package/build/conductor/git-hooks.js +529 -118
  13. package/build/conductor/hook-runtime-qualification.js +340 -0
  14. package/build/conductor/merge-execution-contract.js +30 -5
  15. package/build/conductor/merge-execution-eligibility.js +10 -9
  16. package/build/conductor/readiness-sections.js +104 -20
  17. package/build/conductor/readiness.js +129 -25
  18. package/build/conductor/recovery-cli.js +354 -13
  19. package/build/conductor/recovery-operations.js +251 -5
  20. package/build/conductor/repair-contract.js +8 -0
  21. package/build/conductor-bundle-artifacts.js +1 -0
  22. package/build/credential-store.js +194 -0
  23. package/build/docs.generated.js +2 -1
  24. package/build/doctor.js +161 -7
  25. package/build/drive-epic-key.js +12 -0
  26. package/build/drive-epic.js +1318 -111
  27. package/build/epic-coherence-input-cli.js +137 -0
  28. package/build/epic-coherence-input.js +920 -0
  29. package/build/epic-coherence-input.test-fixtures.js +175 -0
  30. package/build/epic-implementer-bin.js +35 -29
  31. package/build/epic-implementer-bundle-cli.js +14 -1
  32. package/build/epic-integration-pr.js +61 -15
  33. package/build/epic-launch-profile.js +464 -0
  34. package/build/epic-run-status.js +5 -0
  35. package/build/epic-shared/bridge-client.js +46 -4
  36. package/build/executor/cli.js +48 -0
  37. package/build/executor/completion-contract.js +423 -0
  38. package/build/executor/completion-supervision.js +286 -0
  39. package/build/executor/credentials.js +19 -1
  40. package/build/executor/deps.js +65 -1
  41. package/build/executor/env.js +6 -0
  42. package/build/executor/heartbeat.js +13 -3
  43. package/build/executor/http-client.js +117 -1
  44. package/build/executor/integration-job.js +759 -0
  45. package/build/executor/job-errors.js +48 -0
  46. package/build/executor/job-runner.js +1386 -35
  47. package/build/executor/job-types.js +10 -2
  48. package/build/executor/observation.js +114 -7
  49. package/build/executor/preflight.js +99 -0
  50. package/build/executor/process-containment.js +756 -0
  51. package/build/executor/process.js +235 -5
  52. package/build/executor/prompt-spec.js +262 -1
  53. package/build/executor/provider-checks.js +69 -0
  54. package/build/executor/rebase-finalization.js +351 -0
  55. package/build/executor/reconcile-overlap-job.js +77 -84
  56. package/build/executor/required-verification.js +313 -0
  57. package/build/executor/required-work-ledger.js +1121 -0
  58. package/build/executor/results.js +142 -0
  59. package/build/executor/resume-pre-spawn.js +87 -4
  60. package/build/executor/runner.js +246 -1
  61. package/build/executor/test-declaration.js +20 -0
  62. package/build/executor/types.js +55 -0
  63. package/build/executor/worker-command.js +49 -13
  64. package/build/executor/worker-finalization.js +88 -11
  65. package/build/executor/worker-guard-hook.js +29 -4
  66. package/build/executor/worktree.js +13 -0
  67. package/build/executor-launch.js +658 -0
  68. package/build/index.js +772 -954
  69. package/build/install-bridge-conductor.js +622 -40
  70. package/build/install-bridge.js +92 -16
  71. package/build/merge-pull-request.js +1 -1
  72. package/build/merge-workflow-config.js +54 -0
  73. package/build/merge-workflow-drift.js +35 -4
  74. package/build/merge-workflow.js +386 -2
  75. package/build/pipelines.generated.js +9 -8
  76. package/build/plan-discovery.js +137 -0
  77. package/build/plan-epic-implementer-eligibility.js +2 -2
  78. package/build/plane/cli.js +37 -3
  79. package/build/plane/member-logs.js +3 -0
  80. package/build/plane/member-roster.js +7 -1
  81. package/build/plane/shutdown.js +59 -1
  82. package/build/plane/supervisor.js +148 -0
  83. package/build/readme.generated.js +1 -1
  84. package/build/scripts/executor-protocol-contract-driver.js +13 -3
  85. package/build/setup-epic.js +130 -48
  86. package/build/version.generated.js +3 -3
  87. package/build/worker-guard-hook-bin.js +2 -2
  88. package/docs/CONDUCTOR.md +116 -2
  89. package/package.json +2 -1
@@ -325,7 +325,11 @@ export const INSTALL_BRIDGE_AGENT_PROMPT = "Run /install-bridge now, with no arg
325
325
  "do' line. The server decides eligibility (repository state AND whether this caller can run the " +
326
326
  "command), so do NOT apply a role rule or a learned-state rule of your own. Do NOT run the " +
327
327
  "returned command yourself — only recommend it; running it is the human's next explicit " +
328
- "invocation. For supporting context around a recommended command, use the manifest's own " +
328
+ "invocation. When you recommend a command and `next_step.command_context` is a non-empty " +
329
+ "string, print it verbatim on its own line directly under the command — it is the server's " +
330
+ "explanation of what the command gives the human and how long it takes; do not reword, shorten, " +
331
+ "or extend it, and render nothing in its place when it is empty or missing. " +
332
+ "For other supporting context around a recommended command, use the manifest's own " +
329
333
  "next_step.post_install_indexing and next_step.post_install_scheduler text rather than inventing " +
330
334
  "an explanation for it. " +
331
335
  "Never request, echo, or transport any credential — only ever direct the human to that " +
@@ -1943,21 +1947,34 @@ export async function resolveSignupEmail(options, deps) {
1943
1947
  if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
1944
1948
  return { ok: true, value: fromEnv.trim() };
1945
1949
  }
1950
+ const entered = await promptForBoundedEmail(deps, "Email for Bridge workspace setup (required): ");
1951
+ if (entered === undefined)
1952
+ return { ok: false, error: "No email entered." };
1953
+ return { ok: true, value: entered };
1954
+ }
1955
+ /**
1956
+ * The bounded interactive email prompt shared by self-serve signup
1957
+ * ({@link resolveSignupEmail}) and invite redemption (BAPI-1263). Two reads at
1958
+ * most: the first answer, then ONE re-prompt after a single hint line. EOF
1959
+ * resolves as an empty value (promptLineViaReadline resolves "" on close), which
1960
+ * fails the shape check like any other blank answer, so it costs one re-prompt and
1961
+ * then fails cleanly rather than hanging. Returns the trimmed (case-preserved)
1962
+ * address, or `undefined` after two invalid answers. The answer is never echoed.
1963
+ * The caller guarantees `deps.promptLine` exists.
1964
+ */
1965
+ async function promptForBoundedEmail(deps, promptText) {
1946
1966
  const promptLine = deps.promptLine;
1947
- // BAPI-1171: bounded to two reads total — the first answer, then ONE re-prompt
1948
- // after a single hint line. EOF resolves as an empty value (promptLineViaReadline
1949
- // resolves "" on close), which fails the shape check like any other blank
1950
- // answer, so it costs one re-prompt and then fails cleanly rather than hanging.
1967
+ if (!promptLine)
1968
+ return undefined;
1951
1969
  for (let attempt = 0; attempt < 2; attempt += 1) {
1952
- const raw = await promptLine("Email for Bridge workspace setup (required): ");
1970
+ const raw = await promptLine(promptText);
1953
1971
  const entered = String(raw ?? "").trim();
1954
- if (INSTALL_BRIDGE_SIGNUP_EMAIL_SHAPE.test(entered)) {
1955
- return { ok: true, value: entered };
1956
- }
1972
+ if (INSTALL_BRIDGE_SIGNUP_EMAIL_SHAPE.test(entered))
1973
+ return entered;
1957
1974
  if (attempt === 0)
1958
1975
  deps.log(INSTALL_BRIDGE_SIGNUP_EMAIL_HINT);
1959
1976
  }
1960
- return { ok: false, error: "No email entered." };
1977
+ return undefined;
1961
1978
  }
1962
1979
  /**
1963
1980
  * Select the onboarding branch as pure logic (deterministic, no I/O, no prompt):
@@ -3428,15 +3445,21 @@ export function buildBootstrapExchangeUrl(baseUrl) {
3428
3445
  * POST the bootstrap-invite exchange. The token and `key_secret` travel ONLY in
3429
3446
  * the JSON request body (never a query string, never a header, never a log line).
3430
3447
  * Bounded by the same 10s timeout as the connectivity ping.
3448
+ *
3449
+ * BAPI-1263: an optional nonempty `email` is appended as a fourth body field, used
3450
+ * only on the retry after an `email-required` refusal. Omitted or empty, the body is
3451
+ * the legacy three-field one, byte for byte.
3431
3452
  */
3432
- export async function exchangeBootstrapInvite(deps, baseUrl, token, repoName, keySecret) {
3453
+ export async function exchangeBootstrapInvite(deps, baseUrl, token, repoName, keySecret, email) {
3433
3454
  const url = buildBootstrapExchangeUrl(baseUrl);
3434
3455
  let resp;
3435
3456
  try {
3436
3457
  resp = await deps.fetch(url, {
3437
3458
  method: "POST",
3438
3459
  headers: { "Content-Type": "application/json" },
3439
- body: JSON.stringify({ token, repo_name: repoName, key_secret: keySecret }),
3460
+ body: JSON.stringify(email
3461
+ ? { token, repo_name: repoName, key_secret: keySecret, email }
3462
+ : { token, repo_name: repoName, key_secret: keySecret }),
3440
3463
  signal: AbortSignal.timeout(10_000),
3441
3464
  });
3442
3465
  }
@@ -3490,12 +3513,42 @@ export async function exchangeBootstrapInvite(deps, baseUrl, token, repoName, ke
3490
3513
  if (resp.status === 401) {
3491
3514
  return { ok: false, kind: "invalid-invite", message: `The Bridge API rejected the bootstrap invite (HTTP ${resp.status}).` };
3492
3515
  }
3516
+ if (resp.status === 426) {
3517
+ // Only the exact `email_required` refusal is recognized; any other, absent, or
3518
+ // unreadable 426 body stays on the generic failure path below. The message is
3519
+ // installer-owned — nothing from the response is propagated.
3520
+ let recognized = false;
3521
+ try {
3522
+ const body = (await resp.json());
3523
+ recognized = body?.detail?.error === "email_required";
3524
+ }
3525
+ catch {
3526
+ recognized = false;
3527
+ }
3528
+ if (recognized) {
3529
+ return {
3530
+ ok: false,
3531
+ kind: "email-required",
3532
+ message: "The Bridge API requires an email address to redeem this invite.",
3533
+ };
3534
+ }
3535
+ }
3493
3536
  return {
3494
3537
  ok: false,
3495
3538
  kind: "failed",
3496
3539
  message: `The bootstrap exchange failed (HTTP ${resp.status}). Verify BAPI_BASE_URL and try again.`,
3497
3540
  };
3498
3541
  }
3542
+ /** The invite-email prompt (BAPI-1263), shown once after an `email_required` refusal. */
3543
+ const INVITE_EMAIL_PROMPT = "Email for this Bridge invite (required): ";
3544
+ /** BAPI-1263: fixed, secret-free failures for the invite email recovery. */
3545
+ export const INVITE_EMAIL_REQUIRED_NON_INTERACTIVE_MESSAGE = "Error: this invite needs an email address, and there is no interactive terminal to ask for it. " +
3546
+ `The bootstrap invite has NOT been used. Re-run \`npx -y ${MCP_PACKAGE_NAME} install\` in an ` +
3547
+ "interactive terminal.";
3548
+ export const INVITE_EMAIL_INVALID_ANSWERS_MESSAGE = "Error: no valid email address was entered. The bootstrap invite has NOT been used. Re-run " +
3549
+ "install and enter an address like you@example.com.";
3550
+ export const INVITE_EMAIL_REPEATED_REFUSAL_MESSAGE = "Error: the Bridge API still requires an email address after one was entered. The bootstrap " +
3551
+ "invite has NOT been used. Re-run install, or contact support if this repeats.";
3499
3552
  // ---------------------------------------------------------------------------
3500
3553
  // Self-serve mint (BAPI-618): email -> invite token
3501
3554
  // ---------------------------------------------------------------------------
@@ -6014,12 +6067,35 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
6014
6067
  // self-serve record — once more with a freshly minted token (R4-3).
6015
6068
  let exchange;
6016
6069
  let selfServeRefreshed = false;
6070
+ // BAPI-1263: the email typed after an `email_required` refusal. Run-local only —
6071
+ // never persisted, logged, or written to the pending record — and carried on
6072
+ // every later ordinary-invite exchange, including after a 409 rename.
6073
+ let inviteEmail;
6017
6074
  for (;;) {
6018
- exchange = await exchangeBootstrapInvite(deps, baseUrl, inviteToken, repoName, keySecret);
6075
+ exchange = await exchangeBootstrapInvite(deps, baseUrl, inviteToken, repoName, keySecret, inviteEmail);
6019
6076
  // 409 repo_name_taken: the server ROLLED BACK, so the invite is NOT consumed and
6020
6077
  // the SAME token can be retried under a new name — with the SAME pending secret,
6021
- // re-pointed to that name.
6022
- while (!exchange.ok && exchange.kind === "repo-name-taken") {
6078
+ // re-pointed to that name. The same loop handles an ordinary invite's 426
6079
+ // `email_required` (BAPI-1263), so a 409 on the email-bearing retry re-enters
6080
+ // the rename branch without an extra exchange request.
6081
+ while (!exchange.ok &&
6082
+ (exchange.kind === "repo-name-taken" ||
6083
+ (exchange.kind === "email-required" && !selfServeSignupMode))) {
6084
+ if (exchange.kind === "email-required") {
6085
+ if (inviteEmail !== undefined) {
6086
+ return fatal(INVITE_EMAIL_REPEATED_REFUSAL_MESSAGE);
6087
+ }
6088
+ if (!deps.isTTY || !deps.promptLine) {
6089
+ return fatal(INVITE_EMAIL_REQUIRED_NON_INTERACTIVE_MESSAGE);
6090
+ }
6091
+ const typedEmail = await promptForBoundedEmail(deps, INVITE_EMAIL_PROMPT);
6092
+ if (typedEmail === undefined) {
6093
+ return fatal(INVITE_EMAIL_INVALID_ANSWERS_MESSAGE);
6094
+ }
6095
+ inviteEmail = typedEmail;
6096
+ exchange = await exchangeBootstrapInvite(deps, baseUrl, inviteToken, repoName, keySecret, inviteEmail);
6097
+ continue;
6098
+ }
6023
6099
  if (!deps.isTTY || !deps.promptLine) {
6024
6100
  errorLog(`Error: ${exchange.message} Re-run with a different --repo (the bootstrap invite has NOT ` +
6025
6101
  "been used).");
@@ -6046,7 +6122,7 @@ export async function runInstallBridgeCli(argv, overrides = {}) {
6046
6122
  }
6047
6123
  repoName = nextRepo;
6048
6124
  // Same token, same key_secret — never regenerated inside the rename loop.
6049
- exchange = await exchangeBootstrapInvite(deps, baseUrl, inviteToken, repoName, keySecret);
6125
+ exchange = await exchangeBootstrapInvite(deps, baseUrl, inviteToken, repoName, keySecret, inviteEmail);
6050
6126
  }
6051
6127
  if (exchange.ok)
6052
6128
  break;
@@ -83,7 +83,7 @@ import { resolveLocalMergeMethod, runApprovedLocalMerge, resolveLocalMergeCreden
83
83
  * The exact remediation for a `dry_run`. Fixed text, because a caller that has to
84
84
  * infer WHICH setting to flip from a reason code guesses wrong.
85
85
  */
86
- const DRY_RUN_HINT = "set auto_merge_enabled=true on the project default via PUT /jira/epic-runs/supervisor-config/defaults/";
86
+ const DRY_RUN_HINT = "auto_merge_enabled is off on the project default; turning it on grants merge authority, so the supervisor-config PUT refuses it (BAPI-1288) — an operator must enable it through the account-settings runbook";
87
87
  /**
88
88
  * Guidance for a post-dispatch transport failure. The action key makes the request
89
89
  * idempotent, so repeating it verbatim is safe and is the ONLY safe move: the merge
@@ -131,3 +131,57 @@ export const MERGE_COMMAND_FETCH_RESPONSE_FIELDS = [
131
131
  "execution",
132
132
  "lease_expires_at",
133
133
  ];
134
+ /**
135
+ * The integration-action actor contract the rendered workflow's
136
+ * `integration` job consumes (BAPI-1259).
137
+ *
138
+ * A DEDICATED contract, deliberately not an extension of the merge-command
139
+ * fetch above: an integration action is never a child merge, and a workflow
140
+ * that could interpret one as the other would be one refactor away from
141
+ * merging an integration pull request. The routes are mounted under
142
+ * {@link INTEGRATION_ACTION_PATH_PREFIX} in `api/routes/__init__.py`.
143
+ *
144
+ * The dispatch inputs only wake and correlate: `action_key` carries the
145
+ * server-minted action id (`integ:{repo}:{hex}`) and `lease_owner` carries the
146
+ * server-minted per-claim token. Everything the job acts on — the operation,
147
+ * the exact epic branch, the expected heads — comes from the fetch response.
148
+ */
149
+ export const INTEGRATION_ACTION_PATH_PREFIX = "/epic-integration-actions";
150
+ /** Re-read the approved work under the dispatched lease. */
151
+ export const INTEGRATION_ACTION_FETCH_PATH = INTEGRATION_ACTION_PATH_PREFIX + "/fetch";
152
+ /** Report bounded facts. Returns an acknowledgement only. */
153
+ export const INTEGRATION_ACTION_REPORT_PATH = INTEGRATION_ACTION_PATH_PREFIX + "/report";
154
+ /** The one protocol version the rendered job speaks (exact equality server-side). */
155
+ export const INTEGRATION_ACTION_PROTOCOL_VERSION = 1;
156
+ /** The dispatch `operation` value that selects the integration job. */
157
+ export const INTEGRATION_ACTION_WORKFLOW_OPERATION = "integration_action";
158
+ /** The dispatch `operation` default that selects the child-merge job. */
159
+ export const CHILD_MERGE_WORKFLOW_OPERATION = "child_merge";
160
+ /**
161
+ * The operations the WORKFLOW may perform. `correct` is absent on purpose: a
162
+ * correction needs a worker, which only a local or durable executor runs, so the
163
+ * server never issues one to the workflow and the job refuses one if it arrives.
164
+ */
165
+ export const INTEGRATION_WORKFLOW_OPERATIONS = ["sync_base", "mark_ready"];
166
+ /** The request fields of the fetch, in the contract's own spelling. */
167
+ export const INTEGRATION_ACTION_FETCH_REQUEST_FIELDS = [
168
+ "protocol_version",
169
+ "repo_name",
170
+ "action_id",
171
+ "claim_token",
172
+ ];
173
+ /** The response fields the rendered job reads from the fetch. */
174
+ export const INTEGRATION_ACTION_FETCH_RESPONSE_FIELDS = [
175
+ "protocol_version",
176
+ "action_id",
177
+ "operation",
178
+ "actor",
179
+ "repo_name",
180
+ "provider_repository",
181
+ "epic_key",
182
+ "epic_branch",
183
+ "base_branch",
184
+ "expected_epic_head_sha",
185
+ "expected_base_sha",
186
+ "pr_number",
187
+ ];
@@ -33,11 +33,42 @@ export function canonicalMergeWorkflowSha() {
33
33
  * Every Bridge-published historical rendering of the merge workflow, by Git blob
34
34
  * SHA, with the contract version it declared.
35
35
  *
36
- * Empty at contract version 1: there is no earlier published version to upgrade
37
- * FROM. A future contract version adds its predecessor's SHA here, and only
38
- * then can {@link classifyMergeWorkflowContent} return `managed_version_older`.
36
+ * A future contract version adds its predecessor's SHA here, and only then can
37
+ * {@link classifyMergeWorkflowContent} return `managed_version_older`.
38
+ *
39
+ * ## Why a version-1 entry exists without a version bump (BAPI-1257)
40
+ *
41
+ * The first entry is a rendering that was published AS version 1 and then
42
+ * corrected WITHIN version 1. Its fork-origin guard read
43
+ * `jq -r '.isCrossRepository // true'`, and jq's `//` yields its right-hand side
44
+ * when the left is null OR FALSE — so a same-repository pull request, which
45
+ * every epic child is, read as `true` and the merge was refused as fork-origin.
46
+ * That rendering could never merge anything.
47
+ *
48
+ * The marker version is deliberately NOT bumped: the wire contract, the dispatch
49
+ * inputs and the command-fetch protocol are all unchanged, and
50
+ * `MERGE_WORKFLOW_EXPECTED_VERSION` is the server's deploy-ordering check, not a
51
+ * byte checksum. But without this entry a repository holding that rendering
52
+ * classifies as `modified_or_unrecognized`, and the installer's preflight hard
53
+ * refuses to touch a workflow Bridge does not recognise — leaving no automated
54
+ * path off a copy that cannot merge. Registering the superseded SHA restores it:
55
+ * the copy classifies `managed_version_older` and the installer opens its
56
+ * ordinary upgrade pull request.
57
+ *
58
+ * The SHA is the Git blob SHA of the complete superseded rendering, which is
59
+ * what {@link canonicalMergeWorkflowSha} computes for the current one.
60
+ *
61
+ * ## The version-1 entry added by BAPI-1259
62
+ *
63
+ * `0d44c814…` is the last version-1 rendering (the one BAPI-1257 corrected).
64
+ * Contract version 2 adds the `operation` input and the integration job, so a
65
+ * repository holding that rendering is an ordinary managed older version and
66
+ * the installer opens its upgrade pull request.
39
67
  */
40
- export const MANAGED_MERGE_WORKFLOW_HISTORY = new Map();
68
+ export const MANAGED_MERGE_WORKFLOW_HISTORY = new Map([
69
+ ["3d4956c9447223c37075069352cdd9aae2d561dc", 1],
70
+ ["0d44c81436005beb3c314de1b98eaf972ef3b4db", 1],
71
+ ]);
41
72
  /**
42
73
  * Classify an installed copy against the canonical bytes.
43
74
  *