@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
@@ -0,0 +1,313 @@
1
+ import { createProcessTerminationController, runProcessWithTimeout } from "./process.js";
2
+ /** Grace between SIGTERM and SIGKILL for an interrupted check. */
3
+ export const CHECK_TERM_GRACE_MS = 5_000;
4
+ /** Bound on waiting for a check's pipes to reach EOF after it exits. */
5
+ export const CHECK_DRAIN_BOUND_MS = 2_000;
6
+ /** Bound on each git read of the final artifact. */
7
+ export const ARTIFACT_GIT_TIMEOUT_MS = 30_000;
8
+ /** Local-only output tail kept per check for the executor's own log. */
9
+ export const CHECK_OUTPUT_TAIL_CHARS = 4_000;
10
+ /** How often a running check re-asks whether the executor still owns the claim. */
11
+ export const OWNERSHIP_POLL_MS = 1_000;
12
+ // ---------------------------------------------------------------------------
13
+ // Canonical JSON (mirror of the server's `canonical_json`)
14
+ // ---------------------------------------------------------------------------
15
+ /**
16
+ * Sorted keys, no whitespace, non-ASCII kept as-is. Byte-identical to Python's
17
+ * `json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)`
18
+ * for the values a declaration holds (strings, integers, null, lists, objects
19
+ * with ASCII keys), which is what lets the executor recompute every digest.
20
+ */
21
+ export function canonicalJson(value) {
22
+ if (value === null || value === undefined)
23
+ return "null";
24
+ if (Array.isArray(value))
25
+ return `[${value.map(canonicalJson).join(",")}]`;
26
+ if (typeof value === "object") {
27
+ const entries = Object.entries(value)
28
+ .filter(([, v]) => v !== undefined)
29
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
30
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
31
+ }
32
+ return JSON.stringify(value);
33
+ }
34
+ function tail(text) {
35
+ return text.length <= CHECK_OUTPUT_TAIL_CHARS ? text : "…" + text.slice(-CHECK_OUTPUT_TAIL_CHARS);
36
+ }
37
+ /**
38
+ * Run declared commands in declaration order.
39
+ *
40
+ * Every declared command gets exactly one fact, in order: a launched command
41
+ * reports how it ended, and one that was never launched reports `not_run` with
42
+ * the reason. Each launch is capped at `min(declared timeout, deadline - now)`;
43
+ * a command with no time left is `not_run` / `reservation_exhausted`, never
44
+ * given a fresh budget.
45
+ */
46
+ export async function runRequiredVerificationCommands(input) {
47
+ const policy = input.onInterruption ?? "stop";
48
+ const facts = [];
49
+ let stopReason = null;
50
+ const notRun = (command, reason) => ({
51
+ identity: command.identity,
52
+ position: command.position,
53
+ name: command.name,
54
+ outcome: "not_run",
55
+ reason,
56
+ durationMs: 0,
57
+ output: "",
58
+ });
59
+ for (const command of input.commands) {
60
+ if (stopReason !== null) {
61
+ facts.push(notRun(command, stopReason));
62
+ continue;
63
+ }
64
+ if (input.signal.aborted) {
65
+ stopReason = "executor_cancelled";
66
+ facts.push(notRun(command, stopReason));
67
+ continue;
68
+ }
69
+ if (input.ownershipHeld && !input.ownershipHeld()) {
70
+ stopReason = "ownership_lost";
71
+ facts.push(notRun(command, stopReason));
72
+ continue;
73
+ }
74
+ const declaredMs = command.timeoutSeconds * 1000;
75
+ const remainingMs = input.deadlineAtMs - input.now();
76
+ const budgetMs = Math.min(declaredMs, remainingMs);
77
+ if (budgetMs <= 0) {
78
+ facts.push(notRun(command, "reservation_exhausted"));
79
+ if (policy === "stop")
80
+ stopReason = "reservation_exhausted";
81
+ continue;
82
+ }
83
+ const started = input.now();
84
+ let launched;
85
+ try {
86
+ launched = await input.launch({
87
+ argv: command.argv,
88
+ cwd: input.worktreePath,
89
+ env: input.env,
90
+ timeoutMs: budgetMs,
91
+ signal: input.signal,
92
+ });
93
+ }
94
+ catch {
95
+ launched = { outcome: { kind: "launch_error" } };
96
+ }
97
+ const durationMs = Math.max(0, input.now() - started);
98
+ const output = tail(launched.output ?? "");
99
+ let outcome;
100
+ let reason = null;
101
+ switch (launched.outcome.kind) {
102
+ case "exited":
103
+ outcome = launched.outcome.exitCode === 0 ? "exited_zero" : "exited_nonzero";
104
+ break;
105
+ case "timed_out":
106
+ outcome = "timed_out";
107
+ // The declared maximum was the binding limit, or the reservation was.
108
+ reason = budgetMs < declaredMs ? "reservation_exhausted" : "declared_timeout";
109
+ break;
110
+ case "cancelled":
111
+ outcome = "cancelled";
112
+ reason = input.ownershipHeld && !input.ownershipHeld() ? "ownership_lost" : "executor_cancelled";
113
+ break;
114
+ default:
115
+ outcome = "launch_error";
116
+ reason = "spawn_failed";
117
+ }
118
+ facts.push({
119
+ identity: command.identity,
120
+ position: command.position,
121
+ name: command.name,
122
+ outcome,
123
+ reason,
124
+ durationMs,
125
+ output,
126
+ });
127
+ const interrupted = outcome === "timed_out" || outcome === "cancelled";
128
+ if (interrupted && input.onInterrupted) {
129
+ try {
130
+ await input.onInterrupted();
131
+ }
132
+ catch {
133
+ /* cleanup is re-verified by the caller's own sweep */
134
+ }
135
+ }
136
+ if (policy === "stop" && (interrupted || outcome === "launch_error")) {
137
+ // A cancellation keeps its own reason for the commands it prevented.
138
+ stopReason =
139
+ reason === "ownership_lost" || reason === "executor_cancelled" ? reason : "stopped_after_interruption";
140
+ }
141
+ }
142
+ return { facts, stopReason };
143
+ }
144
+ /** The server-bound view of one fact: identity, closed tokens and duration only. */
145
+ export function toCompletionCheckFact(fact) {
146
+ return {
147
+ identity: fact.identity,
148
+ position: fact.position,
149
+ outcome: fact.outcome,
150
+ reason: fact.reason,
151
+ duration_ms: Math.min(Math.max(0, Math.floor(fact.durationMs)), 2 * 60 * 60 * 1000),
152
+ };
153
+ }
154
+ const FULL_SHA_RE = /^[0-9a-f]{40}$/;
155
+ /**
156
+ * Read full HEAD and the tracked tree/index state. Untracked files are not
157
+ * read: the guarantee covers what a commit would carry, not every file.
158
+ */
159
+ export async function observeFinalArtifact(runCommand, worktreePath) {
160
+ let head = null;
161
+ let trackedStatus = null;
162
+ try {
163
+ const r = await runCommand("git", ["rev-parse", "--verify", "HEAD"], {
164
+ cwd: worktreePath,
165
+ timeoutMs: ARTIFACT_GIT_TIMEOUT_MS,
166
+ });
167
+ const value = r.stdout.trim();
168
+ if (r.exitCode === 0 && FULL_SHA_RE.test(value))
169
+ head = value;
170
+ }
171
+ catch {
172
+ head = null;
173
+ }
174
+ try {
175
+ const r = await runCommand("git", ["status", "--porcelain=v1", "--untracked-files=no"], {
176
+ cwd: worktreePath,
177
+ timeoutMs: ARTIFACT_GIT_TIMEOUT_MS,
178
+ });
179
+ if (r.exitCode === 0)
180
+ trackedStatus = r.stdout.trim();
181
+ }
182
+ catch {
183
+ trackedStatus = null;
184
+ }
185
+ return { head, trackedStatus };
186
+ }
187
+ /** Whether the pre-check observation is a certifiable starting point, and why not. */
188
+ export function preCheckArtifactState(before, expectedHead) {
189
+ if (before.head === null || before.trackedStatus === null)
190
+ return "head_unreadable";
191
+ if (expectedHead !== null && before.head !== expectedHead)
192
+ return "head_moved";
193
+ if (before.trackedStatus !== "")
194
+ return "dirty_before";
195
+ return null;
196
+ }
197
+ /** The artifact verdict after the checks and their descendant cleanup. */
198
+ export function compareFinalArtifact(before, after) {
199
+ if (after.head === null || after.trackedStatus === null)
200
+ return "unobservable_after";
201
+ if (after.head !== before.head)
202
+ return "head_moved";
203
+ if (after.trackedStatus !== before.trackedStatus)
204
+ return "tracked_changed";
205
+ return "stable";
206
+ }
207
+ // ---------------------------------------------------------------------------
208
+ // The covered lane's launcher and runner
209
+ // ---------------------------------------------------------------------------
210
+ /**
211
+ * A launch seam built on the executor's one spawn boundary.
212
+ *
213
+ * argv through `spawnProcess` (never a shell), the caller's explicit
214
+ * environment, a termination controller registered with the executor's
215
+ * live-process registry for the command's lifetime, a timeout the process
216
+ * supervisor enforces and reports, and cancellation through the same
217
+ * controller. Output is kept as a bounded local tail only.
218
+ */
219
+ export function createSupervisedCommandLauncher(input) {
220
+ const { deps } = input;
221
+ return async (request) => {
222
+ const [file, ...args] = request.argv;
223
+ if (file === undefined)
224
+ return { outcome: { kind: "launch_error" } };
225
+ if (request.signal.aborted)
226
+ return { outcome: { kind: "cancelled" } };
227
+ let proc;
228
+ try {
229
+ proc = deps.spawnProcess(file, args, { cwd: request.cwd, env: request.env });
230
+ }
231
+ catch {
232
+ return { outcome: { kind: "launch_error" } };
233
+ }
234
+ const controller = createProcessTerminationController(proc, deps, CHECK_TERM_GRACE_MS);
235
+ const deregister = input.registerLive?.(controller);
236
+ let cancelled = false;
237
+ const onAbort = () => {
238
+ cancelled = true;
239
+ controller.requestTermination();
240
+ };
241
+ request.signal.addEventListener("abort", onAbort, { once: true });
242
+ try {
243
+ // Whole seconds, rounded DOWN so the cap never exceeds the reservation.
244
+ const timeoutSeconds = Math.max(1, Math.floor(request.timeoutMs / 1000));
245
+ const result = await runProcessWithTimeout(proc, timeoutSeconds, deps, {
246
+ termGraceMs: CHECK_TERM_GRACE_MS,
247
+ terminationController: controller,
248
+ drainDeadlineMs: CHECK_DRAIN_BOUND_MS,
249
+ });
250
+ const output = `${result.stdoutExcerpt}\n${result.stderrExcerpt}`;
251
+ if (cancelled)
252
+ return { outcome: { kind: "cancelled" }, output };
253
+ if (result.classification === "timeout")
254
+ return { outcome: { kind: "timed_out" }, output };
255
+ if (result.exitUnconfirmed || result.exitCode === null) {
256
+ // Killed or vanished without an exit code we can trust: not an exit.
257
+ return { outcome: { kind: "cancelled" }, output };
258
+ }
259
+ return { outcome: { kind: "exited", exitCode: result.exitCode }, output };
260
+ }
261
+ finally {
262
+ request.signal.removeEventListener("abort", onAbort);
263
+ controller.dispose();
264
+ deregister?.();
265
+ }
266
+ };
267
+ }
268
+ /**
269
+ * The covered lane's {@link DeclaredCheckRunner}: every declared command, in
270
+ * order, from the prepared worktree root, stopping on an interruption.
271
+ *
272
+ * The runner reports facts; the job runner owns the artifact observations
273
+ * around it, the post-check cleanup sweep and the verdict.
274
+ */
275
+ export function createRequiredVerificationRunner(input) {
276
+ return {
277
+ async run(request) {
278
+ const result = await runRequiredVerificationCommands({
279
+ commands: request.checks.map(toRequiredCommand),
280
+ worktreePath: request.worktreePath,
281
+ env: request.env,
282
+ launch: input.launch,
283
+ now: input.now,
284
+ deadlineAtMs: request.deadlineAtMs,
285
+ signal: request.signal,
286
+ ...(request.ownershipHeld ? { ownershipHeld: request.ownershipHeld } : {}),
287
+ onInterruption: "stop",
288
+ });
289
+ if (input.log) {
290
+ // Names and closed tokens only: no argv, no output, no path.
291
+ for (const fact of result.facts) {
292
+ input.log(`[executor] declared check ${fact.position} (${fact.name.slice(0, 60)}): ${fact.outcome}${fact.reason ? ` (${fact.reason})` : ""}`);
293
+ }
294
+ }
295
+ const outcomes = result.facts.map((f) => f.outcome);
296
+ const verification = outcomes.length > 0 && outcomes.every((o) => o === "exited_zero")
297
+ ? "passed"
298
+ : outcomes.every((o) => o === "exited_zero" || o === "exited_nonzero")
299
+ ? "failed"
300
+ : "incomplete";
301
+ return { verification, facts: result.facts };
302
+ },
303
+ };
304
+ }
305
+ function toRequiredCommand(check) {
306
+ return {
307
+ identity: check.identity,
308
+ position: check.position,
309
+ name: check.name,
310
+ argv: check.argv,
311
+ timeoutSeconds: check.timeoutSeconds,
312
+ };
313
+ }