@bridge_gpt/mcp-server 0.2.41 → 0.2.43

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 (88) hide show
  1. package/README.md +330 -191
  2. package/build/agent-capabilities/cli.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +17 -4
  4. package/build/agents.generated.js +2 -2
  5. package/build/claude-review-workflow.js +510 -45
  6. package/build/claude-user-config-doctor.js +42 -11
  7. package/build/cli-release.js +2 -1
  8. package/build/commands.generated.js +6 -5
  9. package/build/conduct-epic/bridge-client.js +354 -113
  10. package/build/conduct-epic/checkpoint-store.js +17 -0
  11. package/build/conduct-epic/cli.js +947 -99
  12. package/build/conduct-epic/cut-protocol.js +327 -0
  13. package/build/conduct-epic/spawn.js +14 -2
  14. package/build/conductor/bridge-api-client.js +148 -1
  15. package/build/conductor/cli.js +109 -1
  16. package/build/conductor/doctor.js +101 -16
  17. package/build/conductor/epic-reconcile.js +72 -19
  18. package/build/conductor/epic-runtime.js +15 -3
  19. package/build/conductor/errors.js +47 -0
  20. package/build/conductor/git-hooks.js +205 -11
  21. package/build/conductor/install-doctor.js +230 -1
  22. package/build/conductor/local-merge.js +130 -28
  23. package/build/conductor/recovery-cli.js +313 -0
  24. package/build/conductor/recovery-operations.js +219 -0
  25. package/build/conductor/tools.js +32 -3
  26. package/build/conductor/worker-ledger-cli.js +27 -1
  27. package/build/conductor-bin.js +20 -16
  28. package/build/credentials-cli.js +3 -2
  29. package/build/docs.generated.js +2 -1
  30. package/build/doctor.js +120 -44
  31. package/build/drive-epic.js +375 -0
  32. package/build/executor/cli.js +48 -1
  33. package/build/executor/env.js +21 -0
  34. package/build/executor/http-client.js +71 -3
  35. package/build/executor/index-scope.js +39 -0
  36. package/build/executor/job-errors.js +9 -0
  37. package/build/executor/job-log-registry.js +69 -0
  38. package/build/executor/job-runner.js +198 -29
  39. package/build/executor/live-worker-registry.js +83 -0
  40. package/build/executor/observation.js +259 -6
  41. package/build/executor/platform.js +147 -3
  42. package/build/executor/process.js +58 -14
  43. package/build/executor/runner.js +454 -48
  44. package/build/executor/test-clock.js +3 -2
  45. package/build/executor/worker-finalization.js +233 -56
  46. package/build/executor/worktree.js +8 -1
  47. package/build/index-scope-contract.js +96 -0
  48. package/build/index.js +2277 -270
  49. package/build/init.js +83 -22
  50. package/build/install-bridge-conductor.js +323 -14
  51. package/build/install-bridge.js +225 -47
  52. package/build/install-doctor.js +23 -9
  53. package/build/install-reexec.js +2 -1
  54. package/build/launcher-config-inspection.js +83 -22
  55. package/build/mcp-host-config.js +331 -67
  56. package/build/mcp-host-targets.js +45 -21
  57. package/build/mcp-identity.js +92 -0
  58. package/build/mcp-install-state.js +94 -1
  59. package/build/mcp-invoke.js +2 -1
  60. package/build/mcp-provisioning.js +45 -12
  61. package/build/mcp-registration-doctor.js +35 -13
  62. package/build/mcp-server-invocation.js +4 -2
  63. package/build/merge-pull-request.js +208 -9
  64. package/build/pipelines.generated.js +305 -15
  65. package/build/plane/cli.js +73 -7
  66. package/build/plane/defaults.js +18 -5
  67. package/build/plane/manifest.js +90 -0
  68. package/build/plane/preflight.js +100 -10
  69. package/build/plane/shutdown.js +71 -3
  70. package/build/plane/test-fakes.js +9 -1
  71. package/build/readme.generated.js +1 -1
  72. package/build/regression-check.js +3 -2
  73. package/build/review-tickets.js +8 -7
  74. package/build/run-unit-tests-launcher.js +149 -6
  75. package/build/schedule-run.js +3 -2
  76. package/build/setup-epic.js +531 -82
  77. package/build/sfcc/tool-wrapper.js +15 -0
  78. package/build/start-tickets-prereqs.js +11 -6
  79. package/build/start-tickets.js +91 -85
  80. package/build/update-check.js +3 -2
  81. package/build/upgrade-advice.js +2 -1
  82. package/build/upgrade-cli.js +50 -18
  83. package/build/version.generated.js +2 -1
  84. package/build/worktree-core.js +31 -17
  85. package/docs/CONDUCTOR.md +22 -0
  86. package/docs/install/mcp-tool-integrations.md +19 -3
  87. package/package.json +2 -2
  88. package/pipelines/greenfield-setup.json +286 -0
@@ -1,12 +1,30 @@
1
1
  /**
2
2
  * merge_pull_request — the fail-closed, server-decided protected merge tool (BAPI-799).
3
3
  *
4
- * The agent asks; the SERVER decides. This module holds no VCS credential, spawns
5
- * no `gh` subprocess, and implements no merge of its own. It resolves the
6
- * authoritative required-check set, constructs the deterministic action key, POSTs
7
- * the protected root-mounted route, and renders the server's decision as one
8
- * compact JSON text envelope. Every branch — including every error branch —
9
- * resolves to that envelope; nothing throws through the MCP transport.
4
+ * The agent asks; the SERVER decides. This module holds no VCS credential and
5
+ * implements no merge policy of its own. It resolves the authoritative
6
+ * required-check set, constructs the deterministic action key, POSTs the
7
+ * protected root-mounted route, and renders the server's decision as one compact
8
+ * JSON text envelope. Every branch — including every error branch — resolves to
9
+ * that envelope; nothing throws through the MCP transport.
10
+ *
11
+ * WHO PERFORMS THE WRITE (BAPI-832)
12
+ * ---------------------------------
13
+ * The server decides; the WRITE may happen here. GitHub's merge endpoint needs
14
+ * `contents: write` and this org's App installation is `contents: read`, so a
15
+ * server-side merge returns `provider_unauthorized` no matter how green the pull
16
+ * request is. In `local` mode — the default — the server runs its entire
17
+ * deterministic tail (action key, lease, head-SHA drift guard, review gate,
18
+ * workflow-file guard, CI revalidation) and returns
19
+ * `approved_for_local_execution`; this module then performs the merge with the
20
+ * OPERATOR's `gh` login, exactly as v2's `merge` job has since BAPI-572, and
21
+ * reports the result to the completion route so the ledger closes.
22
+ *
23
+ * That local path is reachable ONLY from a live approval for this exact pull
24
+ * request and head SHA. This module still holds no credential of its own — it
25
+ * borrows the operator's `gh` session — still makes no merge decision, and still
26
+ * never calls the provider API directly. `merged: true` means the same thing in
27
+ * both modes, so the caller's mapping is unchanged.
10
28
  *
11
29
  * WHY A FIXED ENVELOPE
12
30
  * --------------------
@@ -43,14 +61,23 @@
43
61
  *
44
62
  * FROZEN-TREE BOUNDARY
45
63
  * --------------------
46
- * This module lives OUTSIDE `conductor/` and consumes it only through pure,
47
- * I/O-free seams: `git-ci-types.js`, `done-gate.js`, and `merge-identity.js`. The
64
+ * This module lives OUTSIDE `conductor/` and consumes it through the pure,
65
+ * I/O-free seams `git-ci-types.js`, `done-gate.js`, and `merge-identity.js`. The
48
66
  * action key and gate identity are imported rather than reimplemented, because a
49
67
  * second copy that drifts by one character produces a 409 the caller cannot act on.
68
+ *
69
+ * BAPI-832 adds ONE non-pure seam, `local-merge.js`, and only for the local
70
+ * execution path. It is imported rather than reimplemented for the same reason
71
+ * the action key is: that module already owns the head-drift guard, the
72
+ * `--match-head-commit` binding, and the whole ambiguity-resolution ladder that
73
+ * v2's `merge` job has run in production since BAPI-572. A second `gh pr merge`
74
+ * call site written from scratch here would be a second chance to get a
75
+ * privileged write subtly wrong.
50
76
  */
51
77
  import { DEFAULT_GATE_NAME, REQUIRED_CI_CHECKS_GREEN, REVIEW_STATE, normalizeCheckName, } from "./conductor/git-ci-types.js";
52
78
  import { evaluateReviewCondition, normalizeReviewSnapshot, parseDoneGateConfig, } from "./conductor/done-gate.js";
53
79
  import { buildGateIdentity, makeMergeActionKey } from "./conductor/merge-identity.js";
80
+ import { resolveLocalMergeMethod, runApprovedLocalMerge, } from "./conductor/local-merge.js";
54
81
  /**
55
82
  * The exact remediation for a `dry_run`. Fixed text, because a caller that has to
56
83
  * infer WHICH setting to flip from a reason code guesses wrong.
@@ -106,6 +133,8 @@ function envelope(merged, outcome, reason, retryHint, evaluatedHeadSha, prNumber
106
133
  result.hint = diagnostics.hint;
107
134
  if (diagnostics.http_status !== undefined)
108
135
  result.http_status = diagnostics.http_status;
136
+ if (diagnostics.completion !== undefined)
137
+ result.completion = diagnostics.completion;
109
138
  return result;
110
139
  }
111
140
  // ---------------------------------------------------------------------------
@@ -464,6 +493,162 @@ function interpretMergeResponse(body, expectedHeadSha, prNumber) {
464
493
  return malformed();
465
494
  }
466
495
  // ---------------------------------------------------------------------------
496
+ // BAPI-832: local execution of a server-approved merge
497
+ // ---------------------------------------------------------------------------
498
+ /** The non-terminal status the server returns when it approves a local merge. */
499
+ const LOCAL_APPROVAL_STATUS = "approved_for_local_execution";
500
+ const DEFAULT_MERGE_EXECUTION = "local";
501
+ /** Operator-fixable hints for the two preflight refusals. */
502
+ const LOCAL_GH_HINTS = {
503
+ local_gh_unavailable: "Install the GitHub CLI (`gh`) on the machine running this MCP server — local merges execute there.",
504
+ local_gh_unauthenticated: "Run `gh auth login` in the shell that hosts this MCP server — local merges use its GitHub session.",
505
+ };
506
+ /**
507
+ * Read the repository's merge execution mode.
508
+ *
509
+ * Mode is server-side configuration, never a tool argument: an agent must not be
510
+ * able to choose who performs a privileged write. An unreadable or unrecognized
511
+ * value falls back to `local`, which is the mode that works on an installation
512
+ * without `contents: write` — defaulting to the broken mode on a transient
513
+ * config read failure would park a perfectly mergeable pull request.
514
+ */
515
+ async function resolveMergeExecutionMode(deps) {
516
+ try {
517
+ const resp = await (deps.fetchImpl ?? fetch)(deps.buildGetUrl("/epic-runs/supervisor-config/defaults/", { repo_name: deps.repoName }), { headers: await deps.getHeaders() });
518
+ if (!resp.ok)
519
+ return DEFAULT_MERGE_EXECUTION;
520
+ const body = await readJson(resp);
521
+ if (!isPlainObject(body))
522
+ return DEFAULT_MERGE_EXECUTION;
523
+ return body.merge_execution === "server" ? "server" : DEFAULT_MERGE_EXECUTION;
524
+ }
525
+ catch {
526
+ return DEFAULT_MERGE_EXECUTION;
527
+ }
528
+ }
529
+ /**
530
+ * Confirm an approval body really authorizes THIS merge.
531
+ *
532
+ * The local path is the one place this tool performs a privileged write, so the
533
+ * approval it acts on must name the same pull request, the same head commit, and
534
+ * the same action key the request was built from. Anything else — a stale body, a
535
+ * mismatched response, a confused proxy — is refused rather than merged.
536
+ */
537
+ function localApprovalMismatch(body, prNumber, expectedHeadSha, actionKey) {
538
+ return (body.pr_number !== prNumber ||
539
+ typeof body.expected_head_sha !== "string" ||
540
+ body.expected_head_sha.toLowerCase() !== expectedHeadSha.toLowerCase() ||
541
+ body.action_key !== actionKey);
542
+ }
543
+ /** Pull the merge commit SHA out of the local executor's success ledger events. */
544
+ function mergeShaFromLedger(response) {
545
+ const events = Array.isArray(response.ledger_events) ? response.ledger_events : [];
546
+ for (const event of events) {
547
+ if (!isPlainObject(event) || event.type !== "merge.succeeded")
548
+ continue;
549
+ const details = isPlainObject(event.details) ? event.details : {};
550
+ const sha = details.merge_commit_sha;
551
+ if (typeof sha === "string" && SHA_RE.test(sha))
552
+ return sha;
553
+ }
554
+ return undefined;
555
+ }
556
+ /**
557
+ * Report a local execution result to the completion route.
558
+ *
559
+ * Returns the parsed response body, or `null` when the report could not be
560
+ * delivered. The distinction matters in only one direction: an undelivered
561
+ * report after a SUCCESSFUL merge leaves a real merge unrecorded server-side,
562
+ * which the caller is told about via `completion: "unreported"`.
563
+ */
564
+ async function reportLocalCompletion(deps, prNumber, body) {
565
+ try {
566
+ const resp = await (deps.fetchImpl ?? fetch)(deps.buildApiUrl(`/vcs/pull-requests/${prNumber}/merge/complete`), {
567
+ method: "POST",
568
+ headers: await deps.getPostHeaders(),
569
+ body: JSON.stringify(body),
570
+ });
571
+ if (!resp.ok) {
572
+ await deps.handleResponse(resp).catch(() => "");
573
+ return null;
574
+ }
575
+ return await readJson(resp);
576
+ }
577
+ catch {
578
+ return null;
579
+ }
580
+ }
581
+ /**
582
+ * Perform a server-approved merge with the operator's `gh`, then close the ledger.
583
+ *
584
+ * Fail-closed in both directions. A preflight or executor failure is reported to
585
+ * the completion route BEFORE returning, so the lease is released rather than
586
+ * left to time out; and a refusal here is always `needs_human`, because every
587
+ * local failure this can produce is an operator fix, not a wait.
588
+ */
589
+ async function executeApprovedLocalMerge(deps, approval, prNumber, expectedHeadSha, actionKey) {
590
+ if (localApprovalMismatch(approval, prNumber, expectedHeadSha, actionKey)) {
591
+ return envelope(false, "refused", "local_approval_mismatch", "needs_human", expectedHeadSha, prNumber);
592
+ }
593
+ const method = resolveLocalMergeMethod(approval.merge_method);
594
+ const request = {
595
+ repo_name: deps.repoName,
596
+ pr_number: prNumber,
597
+ expected_head_sha: expectedHeadSha,
598
+ gate: { name: DEFAULT_GATE_NAME },
599
+ action_key: actionKey,
600
+ };
601
+ let local;
602
+ try {
603
+ const runLocal = deps.runLocalMerge ?? runApprovedLocalMerge;
604
+ local = await runLocal(request, { method }, { env: process.env });
605
+ }
606
+ catch {
607
+ // The executor itself threw. Treat it exactly like a reported failure: close
608
+ // the lease, then refuse. Never assume a merge that could not be observed.
609
+ await reportLocalCompletion(deps, prNumber, {
610
+ repo_name: deps.repoName,
611
+ action_key: actionKey,
612
+ expected_head_sha: expectedHeadSha,
613
+ result: "failed",
614
+ reason: "gh_merge_failed",
615
+ });
616
+ return envelope(false, "refused", "gh_merge_failed", "needs_human", expectedHeadSha, prNumber);
617
+ }
618
+ const localReason = typeof local.reason === "string" ? local.reason : null;
619
+ if (local.status === "succeeded") {
620
+ const result = localReason === "already_merged" ? "already_merged" : "merged";
621
+ const mergeSha = mergeShaFromLedger(local);
622
+ const completion = await reportLocalCompletion(deps, prNumber, {
623
+ repo_name: deps.repoName,
624
+ action_key: actionKey,
625
+ expected_head_sha: expectedHeadSha,
626
+ result,
627
+ ...(mergeSha ? { merge_sha: mergeSha } : {}),
628
+ });
629
+ // The merge is real whether or not the report landed. Reporting `merged:
630
+ // false` here because a bookkeeping call failed would strand a merged pull
631
+ // request and invite a second merge attempt.
632
+ if (completion === null) {
633
+ return envelope(true, result === "already_merged" ? "already_merged" : "merged", result, null, expectedHeadSha, prNumber, { completion: "unreported" });
634
+ }
635
+ return envelope(true, result === "already_merged" ? "already_merged" : "merged", result, null, expectedHeadSha, prNumber);
636
+ }
637
+ // --- local failure ------------------------------------------------------
638
+ const reason = localReason ?? "gh_merge_failed";
639
+ await reportLocalCompletion(deps, prNumber, {
640
+ repo_name: deps.repoName,
641
+ action_key: actionKey,
642
+ expected_head_sha: expectedHeadSha,
643
+ result: "failed",
644
+ reason,
645
+ });
646
+ const hint = LOCAL_GH_HINTS[reason];
647
+ return envelope(false, "refused", reason, "needs_human", expectedHeadSha, prNumber, {
648
+ ...(hint ? { hint } : {}),
649
+ });
650
+ }
651
+ // ---------------------------------------------------------------------------
467
652
  // Handler
468
653
  // ---------------------------------------------------------------------------
469
654
  /**
@@ -516,6 +701,10 @@ export async function mergePullRequestHandler(deps, args) {
516
701
  const actionKey = makeMergeActionKey(deps.repoName, prNumber, expectedHeadSha, gateIdentity);
517
702
  // The body is built from server-side values only. No caller-supplied field is
518
703
  // spread in, so an extra runtime `repo_name` argument cannot retarget the merge.
704
+ // Mode is resolved server-side and sent explicitly. The WIRE default is
705
+ // `server` so pre-BAPI-832 callers are unaffected; this tool's default is
706
+ // `local`, because `server` cannot merge on a `contents: read` installation.
707
+ const executionMode = await resolveMergeExecutionMode(deps);
519
708
  const mergeBody = {
520
709
  repo_name: deps.repoName,
521
710
  expected_head_sha: expectedHeadSha,
@@ -525,6 +714,7 @@ export async function mergePullRequestHandler(deps, args) {
525
714
  required_checks: resolution.checks,
526
715
  },
527
716
  action_key: actionKey,
717
+ execution: executionMode,
528
718
  };
529
719
  // (3) POST the protected root-mounted route. Wrapped on its own so a rejection
530
720
  // AFTER dispatch is reported as genuinely unknown rather than as a failure.
@@ -552,7 +742,16 @@ export async function mergePullRequestHandler(deps, args) {
552
742
  http_status: mergeResp.status,
553
743
  }));
554
744
  }
555
- return text(interpretMergeResponse(await readJson(mergeResp), expectedHeadSha, prNumber));
745
+ const mergeJson = await readJson(mergeResp);
746
+ // (4) A local approval is not a decision to RENDER — it is a decision to ACT
747
+ // ON. Gated on having actually asked for `local`, so a stray or replayed
748
+ // approval can never provoke a privileged write this tool did not request.
749
+ if (executionMode === "local" &&
750
+ isPlainObject(mergeJson) &&
751
+ mergeJson.status === LOCAL_APPROVAL_STATUS) {
752
+ return text(await executeApprovedLocalMerge(deps, mergeJson, prNumber, expectedHeadSha, actionKey));
753
+ }
754
+ return text(interpretMergeResponse(mergeJson, expectedHeadSha, prNumber));
556
755
  }
557
756
  catch {
558
757
  // Containment: never throw through the MCP transport, and never serialize the