@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.
- package/README.md +330 -191
- package/build/agent-capabilities/cli.js +2 -1
- package/build/agent-launchers/claude-executor-adapter.js +17 -4
- package/build/agents.generated.js +2 -2
- package/build/claude-review-workflow.js +510 -45
- package/build/claude-user-config-doctor.js +42 -11
- package/build/cli-release.js +2 -1
- package/build/commands.generated.js +6 -5
- package/build/conduct-epic/bridge-client.js +354 -113
- package/build/conduct-epic/checkpoint-store.js +17 -0
- package/build/conduct-epic/cli.js +947 -99
- package/build/conduct-epic/cut-protocol.js +327 -0
- package/build/conduct-epic/spawn.js +14 -2
- package/build/conductor/bridge-api-client.js +148 -1
- package/build/conductor/cli.js +109 -1
- package/build/conductor/doctor.js +101 -16
- package/build/conductor/epic-reconcile.js +72 -19
- package/build/conductor/epic-runtime.js +15 -3
- package/build/conductor/errors.js +47 -0
- package/build/conductor/git-hooks.js +205 -11
- package/build/conductor/install-doctor.js +230 -1
- package/build/conductor/local-merge.js +130 -28
- package/build/conductor/recovery-cli.js +313 -0
- package/build/conductor/recovery-operations.js +219 -0
- package/build/conductor/tools.js +32 -3
- package/build/conductor/worker-ledger-cli.js +27 -1
- package/build/conductor-bin.js +20 -16
- package/build/credentials-cli.js +3 -2
- package/build/docs.generated.js +2 -1
- package/build/doctor.js +120 -44
- package/build/drive-epic.js +375 -0
- package/build/executor/cli.js +48 -1
- package/build/executor/env.js +21 -0
- package/build/executor/http-client.js +71 -3
- package/build/executor/index-scope.js +39 -0
- package/build/executor/job-errors.js +9 -0
- package/build/executor/job-log-registry.js +69 -0
- package/build/executor/job-runner.js +198 -29
- package/build/executor/live-worker-registry.js +83 -0
- package/build/executor/observation.js +259 -6
- package/build/executor/platform.js +147 -3
- package/build/executor/process.js +58 -14
- package/build/executor/runner.js +454 -48
- package/build/executor/test-clock.js +3 -2
- package/build/executor/worker-finalization.js +233 -56
- package/build/executor/worktree.js +8 -1
- package/build/index-scope-contract.js +96 -0
- package/build/index.js +2277 -270
- package/build/init.js +83 -22
- package/build/install-bridge-conductor.js +323 -14
- package/build/install-bridge.js +225 -47
- package/build/install-doctor.js +23 -9
- package/build/install-reexec.js +2 -1
- package/build/launcher-config-inspection.js +83 -22
- package/build/mcp-host-config.js +331 -67
- package/build/mcp-host-targets.js +45 -21
- package/build/mcp-identity.js +92 -0
- package/build/mcp-install-state.js +94 -1
- package/build/mcp-invoke.js +2 -1
- package/build/mcp-provisioning.js +45 -12
- package/build/mcp-registration-doctor.js +35 -13
- package/build/mcp-server-invocation.js +4 -2
- package/build/merge-pull-request.js +208 -9
- package/build/pipelines.generated.js +305 -15
- package/build/plane/cli.js +73 -7
- package/build/plane/defaults.js +18 -5
- package/build/plane/manifest.js +90 -0
- package/build/plane/preflight.js +100 -10
- package/build/plane/shutdown.js +71 -3
- package/build/plane/test-fakes.js +9 -1
- package/build/readme.generated.js +1 -1
- package/build/regression-check.js +3 -2
- package/build/review-tickets.js +8 -7
- package/build/run-unit-tests-launcher.js +149 -6
- package/build/schedule-run.js +3 -2
- package/build/setup-epic.js +531 -82
- package/build/sfcc/tool-wrapper.js +15 -0
- package/build/start-tickets-prereqs.js +11 -6
- package/build/start-tickets.js +91 -85
- package/build/update-check.js +3 -2
- package/build/upgrade-advice.js +2 -1
- package/build/upgrade-cli.js +50 -18
- package/build/version.generated.js +2 -1
- package/build/worktree-core.js +31 -17
- package/docs/CONDUCTOR.md +22 -0
- package/docs/install/mcp-tool-integrations.md +19 -3
- package/package.json +2 -2
- 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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
|
47
|
-
* I/O-free seams
|
|
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
|
-
|
|
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
|