@bridge_gpt/mcp-server 0.2.62 → 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.
- package/README.md +47 -8
- package/build/agent-launchers/claude-executor-adapter.js +134 -3
- package/build/agent-launchers/executor-adapter.js +54 -2
- package/build/agents.generated.js +10 -1
- package/build/bounded-wait.js +32 -1
- package/build/commands.generated.js +6 -5
- package/build/conductor/bridge-api-client.js +335 -22
- package/build/conductor/bring-up-facts.js +3 -1
- package/build/conductor/doctor.js +23 -14
- package/build/conductor/epic-child-continuation.js +324 -0
- package/build/conductor/epic-implementer-cli.js +142 -17
- package/build/conductor/git-hooks.js +529 -118
- package/build/conductor/hook-runtime-qualification.js +340 -0
- package/build/conductor/readiness-sections.js +5 -4
- package/build/conductor/readiness.js +22 -11
- package/build/conductor/recovery-cli.js +354 -13
- package/build/conductor/recovery-operations.js +251 -5
- package/build/conductor/repair-contract.js +8 -0
- package/build/conductor-bundle-artifacts.js +1 -0
- package/build/credential-store.js +50 -12
- package/build/docs.generated.js +2 -1
- package/build/doctor.js +77 -11
- package/build/drive-epic-key.js +12 -0
- package/build/drive-epic.js +1211 -116
- package/build/epic-coherence-input-cli.js +137 -0
- package/build/epic-coherence-input.js +920 -0
- package/build/epic-coherence-input.test-fixtures.js +175 -0
- package/build/epic-implementer-bin.js +35 -29
- package/build/epic-implementer-bundle-cli.js +14 -1
- package/build/epic-launch-profile.js +14 -5
- package/build/epic-run-status.js +5 -0
- package/build/epic-shared/bridge-client.js +46 -4
- package/build/executor/cli.js +39 -0
- package/build/executor/completion-contract.js +423 -0
- package/build/executor/completion-supervision.js +286 -0
- package/build/executor/credentials.js +3 -1
- package/build/executor/deps.js +65 -1
- package/build/executor/env.js +6 -0
- package/build/executor/heartbeat.js +13 -3
- package/build/executor/http-client.js +76 -4
- package/build/executor/job-errors.js +48 -0
- package/build/executor/job-runner.js +1294 -26
- package/build/executor/job-types.js +10 -2
- package/build/executor/preflight.js +99 -0
- package/build/executor/process-containment.js +756 -0
- package/build/executor/process.js +235 -5
- package/build/executor/prompt-spec.js +262 -1
- package/build/executor/provider-checks.js +69 -0
- package/build/executor/rebase-finalization.js +351 -0
- package/build/executor/reconcile-overlap-job.js +77 -84
- package/build/executor/required-verification.js +313 -0
- package/build/executor/required-work-ledger.js +1121 -0
- package/build/executor/results.js +142 -0
- package/build/executor/resume-pre-spawn.js +87 -4
- package/build/executor/runner.js +145 -0
- package/build/executor/test-declaration.js +20 -0
- package/build/executor/types.js +12 -0
- package/build/executor/worker-command.js +49 -13
- package/build/executor/worker-finalization.js +88 -11
- package/build/executor/worker-guard-hook.js +29 -4
- package/build/executor/worktree.js +13 -0
- package/build/executor-launch.js +658 -0
- package/build/index.js +537 -943
- package/build/install-bridge-conductor.js +231 -41
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +8 -7
- package/build/plan-discovery.js +137 -0
- package/build/plan-epic-implementer-eligibility.js +2 -2
- package/build/plane/cli.js +37 -3
- package/build/plane/member-logs.js +3 -0
- package/build/plane/member-roster.js +7 -1
- package/build/plane/shutdown.js +59 -1
- package/build/plane/supervisor.js +142 -0
- package/build/readme.generated.js +1 -1
- package/build/scripts/executor-protocol-contract-driver.js +13 -3
- package/build/setup-epic.js +28 -17
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +2 -2
- package/docs/CONDUCTOR.md +116 -2
- package/package.json +2 -1
|
@@ -58,6 +58,16 @@
|
|
|
58
58
|
*/
|
|
59
59
|
import { ReconcileOverlapPushUnfinalized, secretFreeErrorMessage, WorkerFinalizationMissingRemoteBranchAndPr, WorkerFinalizationPrBaseMismatch, WorkerFinalizationPrNotAttached, WorkerFinalizationSavedButUnfinalized, } from "./job-errors.js";
|
|
60
60
|
import { isImplementationStyleJobType } from "./job-types.js";
|
|
61
|
+
/** The `runCommand` options for one bounded read; `{ cwd }` alone when unbounded. */
|
|
62
|
+
function callOptions(worktreePath, bound) {
|
|
63
|
+
return bound && typeof bound.timeoutMs === "number"
|
|
64
|
+
? { cwd: worktreePath, timeoutMs: Math.max(1, Math.floor(bound.timeoutMs)) }
|
|
65
|
+
: { cwd: worktreePath };
|
|
66
|
+
}
|
|
67
|
+
/** True when a bounded call has no time left and must not be issued at all. */
|
|
68
|
+
function callExpired(bound) {
|
|
69
|
+
return bound !== undefined && typeof bound.timeoutMs === "number" && bound.timeoutMs <= 0;
|
|
70
|
+
}
|
|
61
71
|
/** Bounded settling re-check defaults for the authoritative origin-tip lookup. */
|
|
62
72
|
const DEFAULT_ORIGIN_FINALIZATION_ATTEMPTS = 3;
|
|
63
73
|
const DEFAULT_ORIGIN_FINALIZATION_RETRY_DELAY_MS = 500;
|
|
@@ -113,9 +123,11 @@ function parseLsRemoteHeadSha(stdout, expectedRef) {
|
|
|
113
123
|
* on a recovery worktree. A non-zero exit is treated as "branch not found"
|
|
114
124
|
* without copying raw stderr into diagnostics.
|
|
115
125
|
*/
|
|
116
|
-
export async function resolveOriginBranchSha(runCommand, worktreePath, branch) {
|
|
126
|
+
export async function resolveOriginBranchSha(runCommand, worktreePath, branch, bound) {
|
|
117
127
|
const normalizedRef = normalizeBranchRef(branch);
|
|
118
|
-
|
|
128
|
+
if (callExpired(bound))
|
|
129
|
+
return null;
|
|
130
|
+
const result = await runCommand("git", ["ls-remote", "--exit-code", "--heads", "origin", normalizedRef], callOptions(worktreePath, bound));
|
|
119
131
|
if (result.exitCode !== 0)
|
|
120
132
|
return null;
|
|
121
133
|
return parseLsRemoteHeadSha(result.stdout, normalizedRef);
|
|
@@ -129,11 +141,11 @@ export async function resolveOriginBranchSha(runCommand, worktreePath, branch) {
|
|
|
129
141
|
* yet match. Returns the LAST authoritative observation — the caller decides
|
|
130
142
|
* loud-failure from that final value, so true negatives are preserved.
|
|
131
143
|
*/
|
|
132
|
-
async function resolveOriginBranchShaForFinalization(runCommand, worktreePath, branch, trimmedHeadSha, attempts, retryDelayMs, sleep) {
|
|
144
|
+
async function resolveOriginBranchShaForFinalization(runCommand, worktreePath, branch, trimmedHeadSha, attempts, retryDelayMs, sleep, bound) {
|
|
133
145
|
const total = Math.max(1, attempts);
|
|
134
146
|
let remoteSha = null;
|
|
135
147
|
for (let attempt = 0; attempt < total; attempt++) {
|
|
136
|
-
remoteSha = await resolveOriginBranchSha(runCommand, worktreePath, branch);
|
|
148
|
+
remoteSha = await resolveOriginBranchSha(runCommand, worktreePath, branch, bound?.());
|
|
137
149
|
if (remoteSha !== null) {
|
|
138
150
|
// Existence-only success when HEAD is unknown; exact-match success otherwise.
|
|
139
151
|
if (!trimmedHeadSha || remoteSha === trimmedHeadSha) {
|
|
@@ -170,14 +182,16 @@ const PR_LOOKUP_ABSENT = {
|
|
|
170
182
|
* collapses to {@link PR_LOOKUP_ABSENT} without copying raw stderr, an
|
|
171
183
|
* authenticated remote, or the command line into anything the caller can render.
|
|
172
184
|
*/
|
|
173
|
-
async function resolvePrForFinalization(runCommand, worktreePath, branch) {
|
|
185
|
+
async function resolvePrForFinalization(runCommand, worktreePath, branch, bound) {
|
|
174
186
|
const args = ["pr", "view"];
|
|
175
187
|
if (branch)
|
|
176
188
|
args.push(branch);
|
|
177
189
|
args.push("--json", PR_VIEW_JSON_FIELDS);
|
|
190
|
+
if (callExpired(bound))
|
|
191
|
+
return PR_LOOKUP_ABSENT;
|
|
178
192
|
let result;
|
|
179
193
|
try {
|
|
180
|
-
result = await runCommand("gh", args,
|
|
194
|
+
result = await runCommand("gh", args, callOptions(worktreePath, bound));
|
|
181
195
|
}
|
|
182
196
|
catch {
|
|
183
197
|
return PR_LOOKUP_ABSENT;
|
|
@@ -398,6 +412,8 @@ export async function validateWorkerFinalization(input) {
|
|
|
398
412
|
if (!isImplementationStyleJobType(job.job_type)) {
|
|
399
413
|
return { ok: true };
|
|
400
414
|
}
|
|
415
|
+
// BAPI-1311: resolved fresh for every call so each read gets only the time left.
|
|
416
|
+
const bound = () => input.callTimeoutMs ? { timeoutMs: input.callTimeoutMs() } : undefined;
|
|
401
417
|
const hasPrUrl = Boolean(extractPrUrl(result));
|
|
402
418
|
// BAPI-762: `branch`/`headSha` are normalized once here and reused through the
|
|
403
419
|
// rest of the check — including by the diagnostics below — instead of being
|
|
@@ -418,7 +434,7 @@ export async function validateWorkerFinalization(input) {
|
|
|
418
434
|
// unconditional. Their dispositions still differ on purpose (see `PrLookup`),
|
|
419
435
|
// and the ORDER is unchanged: a wrong-base PR is still reported before any
|
|
420
436
|
// origin lookup, exactly as BAPI-586 specified.
|
|
421
|
-
let pr = await resolvePrForFinalization(runCommand, worktreePath, trimmedBranch);
|
|
437
|
+
let pr = await resolvePrForFinalization(runCommand, worktreePath, trimmedBranch, bound());
|
|
422
438
|
// BAPI-586: a wrong-base PR (opened against a dependency's feature branch or the
|
|
423
439
|
// repo default instead of the run base) never fires `claude-review.yml` and
|
|
424
440
|
// strands the ticket at `code_review`. When the run base is known, fail loud on
|
|
@@ -448,7 +464,7 @@ export async function validateWorkerFinalization(input) {
|
|
|
448
464
|
const sleep = input.sleep ?? sleepMs;
|
|
449
465
|
let origin;
|
|
450
466
|
try {
|
|
451
|
-
origin = await resolveOriginBranchShaForFinalization(runCommand, worktreePath, trimmedBranch, trimmedHeadSha, attempts, retryDelayMs, sleep);
|
|
467
|
+
origin = await resolveOriginBranchShaForFinalization(runCommand, worktreePath, trimmedBranch, trimmedHeadSha, attempts, retryDelayMs, sleep, bound);
|
|
452
468
|
}
|
|
453
469
|
catch (err) {
|
|
454
470
|
return {
|
|
@@ -497,7 +513,7 @@ export async function validateWorkerFinalization(input) {
|
|
|
497
513
|
// base check fail-opened on it, and the PR that has since appeared must not
|
|
498
514
|
// reach success without that check.
|
|
499
515
|
if (origin.attemptsUsed > 1) {
|
|
500
|
-
pr = await resolvePrForFinalization(runCommand, worktreePath, trimmedBranch);
|
|
516
|
+
pr = await resolvePrForFinalization(runCommand, worktreePath, trimmedBranch, bound());
|
|
501
517
|
const refreshedBaseVerdict = evaluatePrBase(job, pr, expectedBase);
|
|
502
518
|
if (refreshedBaseVerdict)
|
|
503
519
|
return { ok: false, failure: refreshedBaseVerdict };
|
|
@@ -505,7 +521,7 @@ export async function validateWorkerFinalization(input) {
|
|
|
505
521
|
let contract = evaluatePrContract(pr, trimmedHeadSha);
|
|
506
522
|
for (let attempt = 1; attempt < attempts && !contract.ok && contract.retryable; attempt++) {
|
|
507
523
|
await sleep(retryDelayMs);
|
|
508
|
-
pr = await resolvePrForFinalization(runCommand, worktreePath, trimmedBranch);
|
|
524
|
+
pr = await resolvePrForFinalization(runCommand, worktreePath, trimmedBranch, bound());
|
|
509
525
|
contract = evaluatePrContract(pr, trimmedHeadSha);
|
|
510
526
|
}
|
|
511
527
|
if (!contract.ok) {
|
|
@@ -514,5 +530,66 @@ export async function validateWorkerFinalization(input) {
|
|
|
514
530
|
failure: prNotAttachedFailure(job, trimmedBranch, trimmedHeadSha, expectedBase, contract.detail),
|
|
515
531
|
};
|
|
516
532
|
}
|
|
517
|
-
return {
|
|
533
|
+
return {
|
|
534
|
+
ok: true,
|
|
535
|
+
prUrl: pr.url ?? undefined,
|
|
536
|
+
prNumber: pr.prNumber,
|
|
537
|
+
// BAPI-1311: the verified origin tip — proven equal to the worker's HEAD above
|
|
538
|
+
// whenever that HEAD was known. Only for a caller that asked for it.
|
|
539
|
+
...(input.exposePushedHead === true ? { pushedHeadSha: remoteSha } : {}),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
// ---------------------------------------------------------------------------
|
|
543
|
+
// BAPI-1311 — the focused rebase wait's pull-request read
|
|
544
|
+
// ---------------------------------------------------------------------------
|
|
545
|
+
/**
|
|
546
|
+
* The bounded public projection the focused rebase wait asks for. Separate from
|
|
547
|
+
* {@link PR_VIEW_JSON_FIELDS} on purpose: legacy finalization keeps its exact
|
|
548
|
+
* field selection, and only the rebase wait pays for state and mergeability.
|
|
549
|
+
*/
|
|
550
|
+
const FOCUSED_PR_VIEW_JSON_FIELDS = "number,state,headRefOid,headRefName,baseRefName,mergeable";
|
|
551
|
+
/**
|
|
552
|
+
* Read the bound pull request's current state with ONE bounded `gh pr view`.
|
|
553
|
+
*
|
|
554
|
+
* Addresses the pull request by NUMBER, never by branch, so a second pull request
|
|
555
|
+
* opened for the same branch can never be mistaken for the bound one. Parses only
|
|
556
|
+
* {@link FOCUSED_PR_VIEW_JSON_FIELDS}; raw stdout/stderr never leave this
|
|
557
|
+
* function. An already-expired bound issues no command at all.
|
|
558
|
+
*/
|
|
559
|
+
export async function readFocusedPullRequest(runCommand, worktreePath, prNumber, bound) {
|
|
560
|
+
if (!Number.isInteger(prNumber) || prNumber <= 0 || callExpired(bound)) {
|
|
561
|
+
return { available: false };
|
|
562
|
+
}
|
|
563
|
+
let result;
|
|
564
|
+
try {
|
|
565
|
+
result = await runCommand("gh", ["pr", "view", String(prNumber), "--json", FOCUSED_PR_VIEW_JSON_FIELDS], callOptions(worktreePath, bound));
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return { available: false };
|
|
569
|
+
}
|
|
570
|
+
if (result.exitCode !== 0)
|
|
571
|
+
return { available: false };
|
|
572
|
+
let parsed;
|
|
573
|
+
try {
|
|
574
|
+
parsed = JSON.parse(result.stdout);
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
return { available: false };
|
|
578
|
+
}
|
|
579
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
580
|
+
return { available: false };
|
|
581
|
+
const obj = parsed;
|
|
582
|
+
const rawState = nonBlankString(obj.state);
|
|
583
|
+
const rawHead = nonBlankString(obj.headRefOid);
|
|
584
|
+
const rawMergeable = nonBlankString(obj.mergeable);
|
|
585
|
+
const mergeable = rawMergeable === "MERGEABLE" ? "mergeable" : rawMergeable === "CONFLICTING" ? "conflicting" : "unknown";
|
|
586
|
+
return {
|
|
587
|
+
available: true,
|
|
588
|
+
number: typeof obj.number === "number" && Number.isInteger(obj.number) ? obj.number : null,
|
|
589
|
+
state: rawState === null ? null : rawState.toUpperCase(),
|
|
590
|
+
headRefOid: rawHead === null ? null : rawHead.toLowerCase(),
|
|
591
|
+
headRefName: nonBlankString(obj.headRefName),
|
|
592
|
+
baseRefName: nonBlankString(obj.baseRefName),
|
|
593
|
+
mergeable,
|
|
594
|
+
};
|
|
518
595
|
}
|
|
@@ -63,6 +63,12 @@ export const WORKER_GUARD_MALFORMED_LINE = `${WORKER_GUARD_REASON_PREFIX}: paylo
|
|
|
63
63
|
* than a rule a caller is asked to remember.
|
|
64
64
|
*/
|
|
65
65
|
export function renderWorkerGuardReason(category, family) {
|
|
66
|
+
if (category === "git-ambiguous") {
|
|
67
|
+
return (`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). ` +
|
|
68
|
+
"This shell form cannot be safely resolved: substitution, redirection, or " +
|
|
69
|
+
"stdin-fed arguments hide what git will run. Use literal arguments instead; " +
|
|
70
|
+
"for a commit message, write it to a file and run git commit -F <file>.");
|
|
71
|
+
}
|
|
66
72
|
return (`${WORKER_GUARD_REASON_PREFIX}: denied ${family} (${category}). ` +
|
|
67
73
|
"This command family is restricted to the worker's own branch, worktree, and " +
|
|
68
74
|
"read-only network access. Use a different approach and continue.");
|
|
@@ -618,11 +624,30 @@ function classify(executable) {
|
|
|
618
624
|
return "network";
|
|
619
625
|
return null;
|
|
620
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* The category an unmodelable `git` segment denies with.
|
|
629
|
+
*
|
|
630
|
+
* CLASSIFICATION ONLY. The caller has already decided to deny; this picks the
|
|
631
|
+
* truthful reason. A literal `push`, `reset`, or `clean` subcommand keeps its own
|
|
632
|
+
* category, because the worker does need to know it hit that restriction. Anything
|
|
633
|
+
* else — a literal `commit`, or a subcommand token built from `$…` or a backtick —
|
|
634
|
+
* is `git-ambiguous`: no dynamic token is ever guessed to be `push`.
|
|
635
|
+
*/
|
|
636
|
+
function unsafeGitCategory(argv) {
|
|
637
|
+
const { subcommand } = parseGitInvocation(argv, undefined);
|
|
638
|
+
if (subcommand === "push")
|
|
639
|
+
return "git-push";
|
|
640
|
+
if (subcommand === "reset")
|
|
641
|
+
return "git-reset-hard";
|
|
642
|
+
if (subcommand === "clean")
|
|
643
|
+
return "filesystem-clean";
|
|
644
|
+
return "git-ambiguous";
|
|
645
|
+
}
|
|
621
646
|
/** The category a governed family denies with when its segment is unmodelable. */
|
|
622
|
-
function unsafeSegmentDecision(family) {
|
|
647
|
+
function unsafeSegmentDecision(family, argv) {
|
|
623
648
|
switch (family) {
|
|
624
649
|
case "git":
|
|
625
|
-
return { kind: "deny", category:
|
|
650
|
+
return { kind: "deny", category: unsafeGitCategory(argv), family: "git" };
|
|
626
651
|
case "rm":
|
|
627
652
|
return { kind: "deny", category: "filesystem-remove", family: "rm" };
|
|
628
653
|
case "database":
|
|
@@ -810,11 +835,11 @@ function evaluateSimpleCommand(segment, ctx, payloadCwd, depth = 0, stdinFed = f
|
|
|
810
835
|
// under `stdinFed` can never be proven safe regardless of how its visible argv
|
|
811
836
|
// looks — the fail-CLOSED rule, applied after every wrapper has been resolved.
|
|
812
837
|
if (stdinFed)
|
|
813
|
-
return unsafeSegmentDecision(family);
|
|
838
|
+
return unsafeSegmentDecision(family, segment.argv);
|
|
814
839
|
// A governed family inside a segment carrying substitution or redirection can
|
|
815
840
|
// never be proven safe — deny, per the fail-CLOSED half of the contract.
|
|
816
841
|
if (segment.unsafe)
|
|
817
|
-
return unsafeSegmentDecision(family);
|
|
842
|
+
return unsafeSegmentDecision(family, segment.argv);
|
|
818
843
|
const cwd = resolveEffectiveCwd(ctx, payloadCwd);
|
|
819
844
|
if (family === "rm")
|
|
820
845
|
return evaluateRemove(segment.argv, ctx, cwd);
|
|
@@ -95,6 +95,19 @@ async function finalizeExecutorWorktree(worktreePath, branch, expectedHeadSha, d
|
|
|
95
95
|
}
|
|
96
96
|
return { ok: true, worktreePath, branch, expectedHeadSha };
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* BAPI-1300 — finish a RETAINED continuation worktree the same way a created one
|
|
100
|
+
* is finished: pin its current HEAD as the pre-spawn expectation and provision
|
|
101
|
+
* its commands. The worktree itself is not touched: no fetch, no reset, no
|
|
102
|
+
* recreation — its (already verified and checkpointed) local state is the work.
|
|
103
|
+
*/
|
|
104
|
+
export async function finalizeRetainedExecutorWorktree(worktreePath, branch, deps) {
|
|
105
|
+
const headSha = await resolveCommitSha(deps, worktreePath, "HEAD");
|
|
106
|
+
if (headSha === null) {
|
|
107
|
+
return { ok: false, error: `failed to resolve HEAD of the retained worktree for '${branch}'` };
|
|
108
|
+
}
|
|
109
|
+
return finalizeExecutorWorktree(worktreePath, branch, headSha, deps);
|
|
110
|
+
}
|
|
98
111
|
/** A full 40-character hex object name. */
|
|
99
112
|
const FULL_SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
100
113
|
/**
|