@bridge_gpt/mcp-server 0.2.49 → 0.2.50
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 +24 -7
- package/build/base-ref.js +28 -3
- package/build/claude-review-workflow-drift-probe.js +130 -0
- package/build/claude-review-workflow-drift.js +173 -0
- package/build/claude-review-workflow.js +81 -16
- package/build/commands.generated.js +5 -5
- package/build/conductor/done-gate.js +25 -3
- package/build/conductor/install-doctor.js +65 -5
- package/build/conductor/latest-check-selector.js +170 -0
- package/build/conductor/local-merge.js +8 -6
- package/build/conductor-bin.js +1 -1
- package/build/{brainstorm-files.js → council-files.js} +15 -15
- package/build/decision-page-schema.js +1 -1
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +162 -4
- package/build/executor/worktree.js +46 -1
- package/build/index.js +92 -51
- package/build/init.js +9 -2
- package/build/install-bridge.js +60 -2
- package/build/install-reexec.js +47 -9
- package/build/pipelines.generated.js +1 -1
- package/build/plane/cli.js +12 -2
- package/build/plane/manifest.js +25 -1
- package/build/plane/member-roster.js +61 -7
- package/build/plane/preflight.js +24 -9
- package/build/plane/supervisor.js +77 -5
- package/build/plane/types.js +23 -3
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +2 -1
- package/build/stale-worktree-doctor.js +120 -0
- package/build/start-tickets-prereqs.js +70 -0
- package/build/start-tickets.js +91 -3
- package/build/version.generated.js +3 -2
- package/package.json +4 -2
- package/build/chain-orchestrator.js +0 -1457
- package/build/chain-utils.js +0 -68
- package/build/command-catalog.js +0 -376
- package/build/schedule-run.js +0 -1300
- package/build/schedule-store.js +0 -172
- package/build/scheduled-prompt.js +0 -115
- package/build/scheduler-backends/at-fallback.js +0 -139
- package/build/scheduler-backends/escaping.js +0 -143
- package/build/scheduler-backends/index.js +0 -72
- package/build/scheduler-backends/launchd.js +0 -225
- package/build/scheduler-backends/systemd-user.js +0 -250
- package/build/scheduler-backends/task-scheduler.js +0 -214
- package/build/scheduler-backends/types.js +0 -23
package/build/plane/preflight.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* it — including any manifest belonging to a plane that is already running.
|
|
13
13
|
*/
|
|
14
14
|
import path from "path";
|
|
15
|
-
import {
|
|
15
|
+
import { PLANE_SERVER_PORT_ENV_VAR, } from "./types.js";
|
|
16
16
|
import { checkPlaneBuildFreshness, checkPlaneRuntimeEntrypoint } from "./build-freshness.js";
|
|
17
17
|
import { checkAlembicHead } from "./alembic-head.js";
|
|
18
18
|
import { manifestHasLiveProcess, readPlaneManifest } from "./manifest.js";
|
|
@@ -56,7 +56,15 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
56
56
|
// unresolvable re-exec target aggregates with them instead of short-circuiting.
|
|
57
57
|
const runtimeEntrypoint = deps.resolveRuntimeEntrypoint();
|
|
58
58
|
add(checkPlaneRuntimeEntrypoint(runtimeEntrypoint));
|
|
59
|
-
|
|
59
|
+
// An unresolvable override is a configuration failure, not a port failure:
|
|
60
|
+
// there is no port to probe, so the probe is skipped entirely rather than
|
|
61
|
+
// falling back to 8000 and reporting on a port the operator did not ask for.
|
|
62
|
+
if (deps.endpoint.ok) {
|
|
63
|
+
add(await checkServerPort(deps, deps.endpoint.endpoint));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
add({ check: "server-port", severity: "blocking", message: deps.endpoint.message });
|
|
67
|
+
}
|
|
60
68
|
add(await checkAlembicHead(repoRoot, {
|
|
61
69
|
execFile: deps.execFile,
|
|
62
70
|
fileExists: (filePath) => fileExists(filePath, deps.fs),
|
|
@@ -67,7 +75,10 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
67
75
|
// `!runtimeEntrypoint.ok` is already covered by the blocking count; it is
|
|
68
76
|
// repeated here so the compiler narrows the union rather than requiring a
|
|
69
77
|
// non-null assertion on the context field below.
|
|
70
|
-
|
|
78
|
+
// `!deps.endpoint.ok` is already covered by the blocking count; it is repeated
|
|
79
|
+
// here, like `!runtimeEntrypoint.ok`, so the compiler narrows the union rather
|
|
80
|
+
// than requiring a non-null assertion on the context field below.
|
|
81
|
+
if (blocking.length > 0 || !credentials.ok || !runtimeEntrypoint.ok || !deps.endpoint.ok) {
|
|
71
82
|
return { ok: false, diagnostics };
|
|
72
83
|
}
|
|
73
84
|
return {
|
|
@@ -76,7 +87,9 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
76
87
|
context: {
|
|
77
88
|
repoRoot,
|
|
78
89
|
repoName: credentials.repoName,
|
|
79
|
-
|
|
90
|
+
// The SAME object `checkServerPort` probed above. Never re-resolved, so
|
|
91
|
+
// the probed port and the launched port cannot diverge.
|
|
92
|
+
endpoint: deps.endpoint.endpoint,
|
|
80
93
|
// Location-chosen: always THIS repository's build tree, so an executor can
|
|
81
94
|
// never come from a published package.
|
|
82
95
|
executorEntrypoint: path.join(repoRoot, "mcp_server", "build", "index.js"),
|
|
@@ -296,23 +309,25 @@ function createResolverWarningBuffer() {
|
|
|
296
309
|
* let two servers race for the port, and silently assuming "occupied" would
|
|
297
310
|
* refuse a perfectly good bring-up.
|
|
298
311
|
*/
|
|
299
|
-
export async function checkServerPort(deps) {
|
|
300
|
-
const
|
|
312
|
+
export async function checkServerPort(deps, endpoint) {
|
|
313
|
+
const target = `${endpoint.host}:${endpoint.port}`;
|
|
314
|
+
const result = await deps.probePort(endpoint.host, endpoint.port, PLANE_PORT_PROBE_TIMEOUT_MS);
|
|
301
315
|
if (result.kind === "refused")
|
|
302
316
|
return null;
|
|
303
317
|
if (result.kind === "connected") {
|
|
304
318
|
return {
|
|
305
319
|
check: "server-port",
|
|
306
320
|
severity: "blocking",
|
|
307
|
-
message: `${
|
|
321
|
+
message: `${target} is already accepting connections. ` +
|
|
308
322
|
"That port may belong to a SIBLING WORKTREE's server — check before you kill it. " +
|
|
309
|
-
|
|
323
|
+
`Stop the existing server (or wind down its plane) and retry, or set ${PLANE_SERVER_PORT_ENV_VAR} ` +
|
|
324
|
+
"to a free port.",
|
|
310
325
|
};
|
|
311
326
|
}
|
|
312
327
|
return {
|
|
313
328
|
check: "server-port",
|
|
314
329
|
severity: "warning",
|
|
315
|
-
message: `could not determine whether ${
|
|
330
|
+
message: `could not determine whether ${target} is free ` +
|
|
316
331
|
`(${result.error}); startup continues and uvicorn will fail loudly if the port is taken.`,
|
|
317
332
|
};
|
|
318
333
|
}
|
|
@@ -176,7 +176,7 @@ export async function launchPlaneSupervisor(params, deps) {
|
|
|
176
176
|
}
|
|
177
177
|
runtimeEnv[PLANE_ID_ENV_VAR] = manifest.planeId;
|
|
178
178
|
runtimeEnv.BAPI_REPO_NAME = context.repoName;
|
|
179
|
-
runtimeEnv.BAPI_BASE_URL = context.baseUrl;
|
|
179
|
+
runtimeEnv.BAPI_BASE_URL = context.endpoint.baseUrl;
|
|
180
180
|
runtimeEnv.BAPI_API_KEY = context.bridgeApiKey;
|
|
181
181
|
let child;
|
|
182
182
|
try {
|
|
@@ -339,12 +339,76 @@ export async function runPlaneRuntime(repoRoot, roster, deps) {
|
|
|
339
339
|
})),
|
|
340
340
|
updatedAt: deps.clock.now().toISOString(),
|
|
341
341
|
};
|
|
342
|
-
await writePlaneManifest(manifest, deps.fs);
|
|
343
342
|
const started = [];
|
|
344
|
-
|
|
343
|
+
// ---- Serialized manifest persistence (BAPI-950) --------------------------
|
|
344
|
+
//
|
|
345
|
+
// `writePlaneManifest` publishes through a temporary file and an atomic
|
|
346
|
+
// rename. That is safe for ONE writer and unsafe for two: overlapping writers
|
|
347
|
+
// interleave on the temp file and the rename then publishes a half-written
|
|
348
|
+
// `plane.json`, which `readPlaneManifest` rejects as "manifest is not valid
|
|
349
|
+
// JSON" and `plane down` refuses to act on — the corrupt manifest is then
|
|
350
|
+
// un-removable and poisons every later bring-up. The runtime used to make
|
|
351
|
+
// that overlap routine: the spawn loop awaited `persist()` while every
|
|
352
|
+
// member's `close` handler fired `void persist()` with nothing serializing
|
|
353
|
+
// them, so reaping several members in one window (exactly what `plane down`
|
|
354
|
+
// and a SIGKILL do) raced them against each other.
|
|
355
|
+
//
|
|
356
|
+
// One promise tail removes the overlap for this process's own writes.
|
|
357
|
+
let persistTail = Promise.resolve();
|
|
358
|
+
// Writes nobody awaits inline — the `close`-handler ones. Held so the runtime
|
|
359
|
+
// can wait for them instead of leaving detached filesystem work behind.
|
|
360
|
+
const pendingPersists = new Set();
|
|
361
|
+
// Set the moment the manifest's ownership passes to `shutdownPlane`, which
|
|
362
|
+
// removes it. A write that landed after that removal would resurrect a
|
|
363
|
+
// `plane.json` describing a plane that no longer exists.
|
|
364
|
+
let manifestReleased = false;
|
|
365
|
+
const persist = () => {
|
|
366
|
+
if (manifestReleased)
|
|
367
|
+
return Promise.resolve();
|
|
368
|
+
// Stamped and snapshotted SYNCHRONOUSLY, at enqueue time. A queued write
|
|
369
|
+
// must publish the state as it stood when it was enqueued; reading the
|
|
370
|
+
// mutable `manifest` binding when the write finally runs would let a later
|
|
371
|
+
// member patch overwrite an earlier transition's record.
|
|
345
372
|
manifest = { ...manifest, updatedAt: deps.clock.now().toISOString() };
|
|
346
|
-
|
|
373
|
+
const snapshot = manifest;
|
|
374
|
+
const operation = persistTail.then(() => writePlaneManifest(snapshot, deps.fs));
|
|
375
|
+
// The tail is recovered but the caller's promise is not: one failed write
|
|
376
|
+
// must not poison every write queued behind it, and it must not be
|
|
377
|
+
// swallowed either. `operation` still rejects with the original error.
|
|
378
|
+
persistTail = operation.catch(() => undefined);
|
|
379
|
+
return operation;
|
|
347
380
|
};
|
|
381
|
+
/**
|
|
382
|
+
* Enroll a write whose completion no caller awaits, and observe its failure.
|
|
383
|
+
*
|
|
384
|
+
* The message is fixed prose. The thrown value can carry a path or an errno
|
|
385
|
+
* struct, and the runtime's stderr is the operator's terminal.
|
|
386
|
+
*/
|
|
387
|
+
const trackPersist = (operation) => {
|
|
388
|
+
const tracked = operation.catch(() => {
|
|
389
|
+
deps.sinks.stderr("Plane manifest could not be updated after a member exit.");
|
|
390
|
+
});
|
|
391
|
+
pendingPersists.add(tracked);
|
|
392
|
+
void tracked.then(() => {
|
|
393
|
+
pendingPersists.delete(tracked);
|
|
394
|
+
});
|
|
395
|
+
};
|
|
396
|
+
/** Wait for every manifest write already enqueued, including untracked ones. */
|
|
397
|
+
const drainPersists = async () => {
|
|
398
|
+
// Looped because a member can exit — and enqueue — while an earlier write
|
|
399
|
+
// is still settling. Each close enqueues exactly one write, and `persist`
|
|
400
|
+
// stops enqueuing once the manifest is released, so this terminates.
|
|
401
|
+
while (pendingPersists.size > 0) {
|
|
402
|
+
await Promise.all([...pendingPersists]);
|
|
403
|
+
}
|
|
404
|
+
await persistTail;
|
|
405
|
+
};
|
|
406
|
+
/** Hand the manifest to `shutdownPlane` with no runtime write still in flight. */
|
|
407
|
+
const releaseManifest = async () => {
|
|
408
|
+
manifestReleased = true;
|
|
409
|
+
await drainPersists();
|
|
410
|
+
};
|
|
411
|
+
await persist();
|
|
348
412
|
const patchMember = (name, patch) => {
|
|
349
413
|
manifest = {
|
|
350
414
|
...manifest,
|
|
@@ -357,6 +421,10 @@ export async function runPlaneRuntime(repoRoot, roster, deps) {
|
|
|
357
421
|
return null;
|
|
358
422
|
shuttingDown = true;
|
|
359
423
|
deps.sinks.stdout(`Received ${signal} — winding the plane down.`);
|
|
424
|
+
// Before `shutdownPlane` reads and removes the manifest, not after: it must
|
|
425
|
+
// see this runtime's completed state, and no queued write may outlive its
|
|
426
|
+
// removal.
|
|
427
|
+
await releaseManifest();
|
|
360
428
|
return shutdownPlane(repoRoot, {
|
|
361
429
|
fs: deps.fs,
|
|
362
430
|
proc: deps.proc,
|
|
@@ -413,7 +481,10 @@ export async function runPlaneRuntime(repoRoot, roster, deps) {
|
|
|
413
481
|
child.on("close", ((code, signal) => {
|
|
414
482
|
const timestamp = deps.clock.now().toISOString();
|
|
415
483
|
patchMember(spec.name, { state: "exited", exitCode: code, exitSignal: signal });
|
|
416
|
-
|
|
484
|
+
// Enrolled synchronously so simultaneous exits queue in the order their
|
|
485
|
+
// patches were applied, and tracked so the write is awaited — never
|
|
486
|
+
// abandoned — before the runtime returns.
|
|
487
|
+
trackPersist(persist());
|
|
417
488
|
emitMemberEvent(deps.sinks, {
|
|
418
489
|
member: spec.name,
|
|
419
490
|
kind: "exit",
|
|
@@ -499,6 +570,7 @@ export async function runPlaneRuntime(repoRoot, roster, deps) {
|
|
|
499
570
|
if (started.length > 0) {
|
|
500
571
|
deps.sinks.stderr(`Rolling back ${started.length} member(s) that had already started.`);
|
|
501
572
|
shuttingDown = true;
|
|
573
|
+
await releaseManifest();
|
|
502
574
|
const result = await shutdownPlane(repoRoot, {
|
|
503
575
|
fs: deps.fs,
|
|
504
576
|
proc: deps.proc,
|
package/build/plane/types.js
CHANGED
|
@@ -35,12 +35,32 @@ export const PLANE_MANIFEST_FILENAME = "plane.json";
|
|
|
35
35
|
export const PLANE_RUNTIME_LOG_FILENAME = "runtime.log";
|
|
36
36
|
/** Repository-relative path of the runtime startup trace. */
|
|
37
37
|
export const PLANE_RUNTIME_LOG_PATH = `${PLANE_RUNTIME_DIR}/${PLANE_RUNTIME_LOG_FILENAME}`;
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Host the local Bridge API server binds for an attended run.
|
|
40
|
+
*
|
|
41
|
+
* Loopback, and NOT overridable. The plane binds a development server with no
|
|
42
|
+
* authentication in front of it; letting an operator move it off `127.0.0.1`
|
|
43
|
+
* would expose that server to the local network, which is a different decision
|
|
44
|
+
* from "run it on a different port because 8000 is taken".
|
|
45
|
+
*/
|
|
39
46
|
export const PLANE_SERVER_HOST = "127.0.0.1";
|
|
40
|
-
/**
|
|
47
|
+
/** Default port the local Bridge API server binds for an attended run. */
|
|
41
48
|
export const PLANE_SERVER_PORT = 8000;
|
|
42
|
-
/**
|
|
49
|
+
/** Default base URL the executor members are pointed at. */
|
|
43
50
|
export const PLANE_SERVER_BASE_URL = `http://${PLANE_SERVER_HOST}:${PLANE_SERVER_PORT}`;
|
|
51
|
+
/**
|
|
52
|
+
* Environment variable that overrides {@link PLANE_SERVER_PORT}.
|
|
53
|
+
*
|
|
54
|
+
* Named to match the existing `BAPI_PLANE_PYTHON` / `BAPI_PLANE_UVICORN`
|
|
55
|
+
* convention: `BAPI_PLANE_*` is the plane's own configuration namespace, kept
|
|
56
|
+
* distinct from `BAPI_*` values (`BAPI_BASE_URL`, `BAPI_API_KEY`) that the
|
|
57
|
+
* spawned members consume.
|
|
58
|
+
*
|
|
59
|
+
* Only the PORT is configurable. An absent or blank value resolves to exactly
|
|
60
|
+
* the default endpoint — `http://127.0.0.1:8000` — so a machine that never sets
|
|
61
|
+
* it is byte-for-byte unchanged.
|
|
62
|
+
*/
|
|
63
|
+
export const PLANE_SERVER_PORT_ENV_VAR = "BAPI_PLANE_PORT";
|
|
44
64
|
/**
|
|
45
65
|
* Private action name for the detached supervisor runtime.
|
|
46
66
|
*
|