@bridge_gpt/mcp-server 0.2.54 → 0.2.55
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 +38 -54
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +5 -2
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/codex-skill-adapter.js +55 -0
- package/build/commands.generated.js +5 -4
- package/build/conductor/bridge-api-client.js +199 -6
- package/build/conductor/bring-up-facts.js +187 -0
- package/build/conductor/claude-hook.js +7 -5
- package/build/conductor/cli.js +28 -0
- package/build/conductor/doctor.js +80 -9
- package/build/conductor/epic-implementer-cli.js +1298 -0
- package/build/conductor/epic-runtime.js +1 -1
- package/build/conductor/errors.js +2 -2
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-hooks.js +28 -14
- package/build/conductor/install-doctor.js +11 -5
- package/build/conductor/readiness-cli.js +10 -10
- package/build/conductor/readiness-sections.js +58 -9
- package/build/conductor/readiness.js +37 -6
- package/build/conductor/recovery-cli.js +289 -10
- package/build/conductor/recovery-operations.js +125 -2
- package/build/conductor/repair-contract.js +58 -0
- package/build/conductor/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- package/build/conductor-bin.js +2 -139
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
- package/build/conductor-removed-stub-bin.js +30 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +77 -17
- package/build/drive-epic.js +541 -115
- package/build/epic-implementer-bin.js +145 -0
- package/build/epic-implementer-bundle-cli.js +264 -0
- package/build/epic-implementer-claude-hook-bin.js +3 -0
- package/build/epic-integration-pr.js +5 -3
- package/build/executor/env.js +6 -0
- package/build/executor/interrupted-worktree.js +60 -0
- package/build/executor/job-errors.js +45 -0
- package/build/executor/job-runner.js +274 -9
- package/build/executor/job-types.js +25 -9
- package/build/executor/merge-tree-classifier.js +171 -0
- package/build/executor/reconcile-overlap-governance.js +129 -0
- package/build/executor/reconcile-overlap-job.js +989 -0
- package/build/executor/reconcile-overlap-types.js +14 -0
- package/build/executor/spawn-job-driver.js +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/worker-finalization.js +25 -2
- package/build/executor/worker-guard-hook.js +15 -7
- package/build/implement-epic/bridge-client.js +773 -0
- package/build/implement-epic/checkpoint-store.js +542 -0
- package/build/implement-epic/cli.js +3158 -0
- package/build/implement-epic/cut-protocol.js +392 -0
- package/build/implement-epic/lock.js +302 -0
- package/build/implement-epic/pr-state.js +286 -0
- package/build/implement-epic/spawn.js +113 -0
- package/build/index.js +586 -138
- package/build/init.js +72 -8
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +403 -70
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +1 -21
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +7 -7
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +36 -5
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +4 -4
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/run-unit-tests-launcher.js +1 -1
- package/build/setup-epic.js +69 -31
- package/build/start-tickets-conductor.js +8 -7
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/package.json +5 -3
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Runs once the epic decomposition's child manifest is frozen and every
|
|
5
5
|
* sub-task's exploration text is written, so this module can predict — before
|
|
6
6
|
* any conductor worker is spawned — which children are likely to trip the
|
|
7
|
-
* runtime workflow-file merge guard (`api/library/vcs/
|
|
7
|
+
* runtime workflow-file merge guard (`api/library/vcs/epic_implementer_merge_service.py`)
|
|
8
8
|
* and which of those additionally touch `claude-review.yml` itself, where
|
|
9
9
|
* automated review cannot run at all (BAPI-941's supply-chain preflight refuses
|
|
10
10
|
* to review a workflow that differs from the base branch's copy).
|
package/build/plane/cli.js
CHANGED
|
@@ -402,7 +402,7 @@ async function runDownAction(sinks, overrides) {
|
|
|
402
402
|
* Resolves Bridge access scoped to the MANIFEST's own repository root (never
|
|
403
403
|
* the process's `cwd`, which is not necessarily the same directory) and calls
|
|
404
404
|
* the shared `stopEpicRunRecovery` — the identical stop semantics
|
|
405
|
-
* `
|
|
405
|
+
* `epic-implementer stop-run` uses, so the two surfaces can never drift apart.
|
|
406
406
|
*
|
|
407
407
|
* Never throws. `shutdownPlane` awaits this directly with no try/catch of its
|
|
408
408
|
* own, so a credential, transport, or unexpected failure here is converted
|
|
@@ -723,6 +723,40 @@ export async function requestPlaneScopedLanes(args) {
|
|
|
723
723
|
const result = await requestPlaneManifestLanes(args.repoRoot, args.planeId, args.epicRunId, args.claimScope, createPlaneFsDeps());
|
|
724
724
|
return result.ok ? { ok: true } : { ok: false, reason: result.reason, message: result.message };
|
|
725
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* The detached runtime's own output sinks (BAPI-1153).
|
|
728
|
+
*
|
|
729
|
+
* The runtime's stdout and stderr are pipes to the LAUNCHER, and the launcher is
|
|
730
|
+
* allowed to exit long before the plane does — `drive-epic` returns once its run
|
|
731
|
+
* is created, and a composed bring-up that was refused leaves its control plane
|
|
732
|
+
* running for a later attach. Once the reading end is gone, Node turns the
|
|
733
|
+
* runtime's next write into an asynchronous EPIPE that, unhandled, terminates
|
|
734
|
+
* the process: the plane lost its supervisor at its first event after the
|
|
735
|
+
* launcher left — a lane request served by attach, a member exit, a shutdown
|
|
736
|
+
* signal. The launcher side already refuses to let a broken feed take down a
|
|
737
|
+
* healthy plane; this is the same rule on the runtime side. The feed is
|
|
738
|
+
* advisory — the manifest and the member logs are the durable record — so a
|
|
739
|
+
* write that cannot be delivered is dropped, never fatal.
|
|
740
|
+
*/
|
|
741
|
+
function createDetachedRuntimeSinks() {
|
|
742
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
743
|
+
stream.on("error", () => {
|
|
744
|
+
/* the launcher that was reading this feed has gone; the plane stays up */
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
const write = (stream, line) => {
|
|
748
|
+
try {
|
|
749
|
+
stream.write(`${line}\n`);
|
|
750
|
+
}
|
|
751
|
+
catch {
|
|
752
|
+
/* a destroyed feed is not a reason to stop supervising */
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
return {
|
|
756
|
+
stdout: (line) => write(process.stdout, line),
|
|
757
|
+
stderr: (line) => write(process.stderr, line),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
726
760
|
/** The private detached-runtime action. Never part of the documented surface. */
|
|
727
761
|
async function runRuntimeAction(options, overrides) {
|
|
728
762
|
const executors = options.executors;
|
|
@@ -736,10 +770,7 @@ async function runRuntimeAction(options, overrides) {
|
|
|
736
770
|
// cannot assert the refusal at all — which is why the refusal below had never
|
|
737
771
|
// executed under test and its message string appeared nowhere else in the
|
|
738
772
|
// repository.
|
|
739
|
-
const sinks = overrides.sinks ??
|
|
740
|
-
stdout: (line) => process.stdout.write(`${line}\n`),
|
|
741
|
-
stderr: (line) => process.stderr.write(`${line}\n`),
|
|
742
|
-
};
|
|
773
|
+
const sinks = overrides.sinks ?? createDetachedRuntimeSinks();
|
|
743
774
|
if (typeof planeId !== "string" || planeId.length === 0) {
|
|
744
775
|
sinks.stderr(`plane runtime refused: ${PLANE_ID_ENV_VAR} was not provided by the launcher.`);
|
|
745
776
|
return 1;
|
package/build/plane/preflight.js
CHANGED
|
@@ -15,11 +15,97 @@ import path from "path";
|
|
|
15
15
|
import { PLANE_SERVER_PORT_ENV_VAR, planeCheckFinding, planeCheckPassed, } from "./types.js";
|
|
16
16
|
import { checkPlaneBuildFreshness, checkPlaneRuntimeEntrypoint } from "./build-freshness.js";
|
|
17
17
|
import { checkAlembicHead } from "./alembic-head.js";
|
|
18
|
-
import { manifestHasLiveProcess, readPlaneManifest } from "./manifest.js";
|
|
18
|
+
import { manifestHasLiveProcess, planeManifestLifecycle, probeManifestMembers, readPlaneManifest, } from "./manifest.js";
|
|
19
19
|
import { detectClaudeLogin, formatClaudeLoginAdvisory } from "../claude-login.js";
|
|
20
20
|
import { createReadinessCheck, createReadinessCheckSafely, } from "../readiness-check.js";
|
|
21
21
|
/** Files that must exist for a path to be this repository's root. */
|
|
22
22
|
const REQUIRED_REPO_FILES = ["main.py", "worker.py", "alembic.ini"];
|
|
23
|
+
/** Phases in which a plane already serving a BOUND run can be re-attached to it. */
|
|
24
|
+
const BOUND_RUN_ATTACHABLE_PHASES = [
|
|
25
|
+
"lanes-starting",
|
|
26
|
+
"lanes-ready",
|
|
27
|
+
"ready",
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Classify a VALID, LIVE manifest found at `repoRoot` for attachment. Pure.
|
|
31
|
+
*
|
|
32
|
+
* The exact-root comparison is the same one `bindPlaneManifestEpicRun` and the
|
|
33
|
+
* guarded lifecycle writers apply (`manifest.repoRoot !== repoRoot`), so a
|
|
34
|
+
* manifest this repository could not bind or request lanes on is never reported
|
|
35
|
+
* attachable.
|
|
36
|
+
*/
|
|
37
|
+
export function classifyLivePlaneForAttach(repoRoot, manifest, proc) {
|
|
38
|
+
if (manifest.repoRoot !== repoRoot)
|
|
39
|
+
return { kind: "foreign-root" };
|
|
40
|
+
const lifecycle = planeManifestLifecycle(manifest);
|
|
41
|
+
const probes = probeManifestMembers(manifest, proc);
|
|
42
|
+
const memberAlive = (name) => probes.some((probe) => probe.member.name === name && probe.liveness === "alive");
|
|
43
|
+
if (!memberAlive("server") || !memberAlive("worker")) {
|
|
44
|
+
return { kind: "not-attachable", lifecycle };
|
|
45
|
+
}
|
|
46
|
+
if (lifecycle === "control-plane-ready")
|
|
47
|
+
return { kind: "attachable", manifest };
|
|
48
|
+
if (BOUND_RUN_ATTACHABLE_PHASES.includes(lifecycle) &&
|
|
49
|
+
manifest.epicRunId !== undefined &&
|
|
50
|
+
manifest.laneScope !== undefined) {
|
|
51
|
+
return { kind: "attachable", manifest };
|
|
52
|
+
}
|
|
53
|
+
return { kind: "not-attachable", lifecycle };
|
|
54
|
+
}
|
|
55
|
+
/** Fixed remediation for an occupied port that is not this repository's plane. */
|
|
56
|
+
export const PLANE_PORT_UNRELATED_LISTENER_REMEDIATION = "the listener is not a live plane of this repository — it is often a SIBLING WORKTREE's " +
|
|
57
|
+
"server. Stop it (or wind down its plane from its own worktree) and run drive-epic again.";
|
|
58
|
+
/** Fixed remediation for a live manifest naming a different repository root. */
|
|
59
|
+
export const PLANE_FOREIGN_ROOT_REMEDIATION = "the plane manifest here names a different repository root (copied or symlinked). " +
|
|
60
|
+
"Inspect .bridge/plane/plane.json, wind that plane down from its own repository, and run " +
|
|
61
|
+
"drive-epic again.";
|
|
62
|
+
/** Fixed remediation for this repository's live plane that cannot take a run. */
|
|
63
|
+
export const PLANE_NOT_ATTACHABLE_REMEDIATION = "this repository's plane is running but cannot take a new run — it is still starting, its " +
|
|
64
|
+
"server or reconciler is gone, its lanes failed, or its lanes were started by hand with " +
|
|
65
|
+
"their own claim scope. Inspect it with `plane status`; wait for it to finish starting, or " +
|
|
66
|
+
"wind it down with `plane down` and run drive-epic again to cold-start.";
|
|
67
|
+
/** Fixed remediation when this repository's attachable plane was not attached to. */
|
|
68
|
+
export const PLANE_ATTACHABLE_BASE_URL_REMEDIATION = `do not stop it — point BAPI_BASE_URL at the plane endpoint (http://127.0.0.1:<port>, the port ` +
|
|
69
|
+
`${PLANE_SERVER_PORT_ENV_VAR} or the default, with no trailing slash) and run drive-epic again to attach.`;
|
|
70
|
+
/**
|
|
71
|
+
* Describe what occupies the plane port, for a single operator refusal. Pure.
|
|
72
|
+
*
|
|
73
|
+
* Every non-attachable occupant keeps its own classification and fix, but they
|
|
74
|
+
* all render as ONE refusal: the operator has one thing to do, and it depends on
|
|
75
|
+
* which of these it is.
|
|
76
|
+
*/
|
|
77
|
+
export function describePlanePortOccupant(observation) {
|
|
78
|
+
switch (observation?.kind) {
|
|
79
|
+
case "unvalidated":
|
|
80
|
+
return {
|
|
81
|
+
classification: "an existing plane manifest could not be validated",
|
|
82
|
+
remediation: PLANE_MANIFEST_UNREADABLE_REMEDIATION,
|
|
83
|
+
};
|
|
84
|
+
case "foreign-root":
|
|
85
|
+
return {
|
|
86
|
+
classification: "a live plane manifest names a different repository root",
|
|
87
|
+
remediation: PLANE_FOREIGN_ROOT_REMEDIATION,
|
|
88
|
+
};
|
|
89
|
+
case "not-attachable":
|
|
90
|
+
return {
|
|
91
|
+
classification: `this repository's plane is live but not attachable (lifecycle: ${observation.lifecycle})`,
|
|
92
|
+
remediation: PLANE_NOT_ATTACHABLE_REMEDIATION,
|
|
93
|
+
};
|
|
94
|
+
case "attachable":
|
|
95
|
+
// Reached only when the caller declined to attach — the Bridge API it
|
|
96
|
+
// talks to is not this plane. Telling the operator to stop the listener
|
|
97
|
+
// would stop a healthy plane; the fix is the base URL.
|
|
98
|
+
return {
|
|
99
|
+
classification: "this repository's own healthy plane holds the port, but BAPI_BASE_URL does not name it",
|
|
100
|
+
remediation: PLANE_ATTACHABLE_BASE_URL_REMEDIATION,
|
|
101
|
+
};
|
|
102
|
+
default:
|
|
103
|
+
return {
|
|
104
|
+
classification: "the port is held by a listener that is not a live plane of this repository",
|
|
105
|
+
remediation: PLANE_PORT_UNRELATED_LISTENER_REMEDIATION,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
23
109
|
/** Fixed remediation for a `plane up` run started outside the repository root. */
|
|
24
110
|
export const PLANE_REPOSITORY_ROOT_REMEDIATION = "run `plane up` from the Bridge API repository root (the directory holding main.py, worker.py, and alembic.ini).";
|
|
25
111
|
/** Fixed remediation for an unresolvable Bridge repository identity. */
|
|
@@ -148,8 +234,11 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
148
234
|
// An unresolvable override is a configuration failure, not a port failure:
|
|
149
235
|
// there is no port to probe, so the probe is skipped entirely rather than
|
|
150
236
|
// falling back to 8000 and reporting on a port the operator did not ask for.
|
|
237
|
+
const observations = {};
|
|
151
238
|
if (deps.endpoint.ok) {
|
|
152
|
-
|
|
239
|
+
const port = await checkServerPort(deps, deps.endpoint.endpoint);
|
|
240
|
+
record(port);
|
|
241
|
+
observations.serverPort = port.probe;
|
|
153
242
|
}
|
|
154
243
|
else {
|
|
155
244
|
// The endpoint resolver owns this diagnostic's prose; the structured fix is
|
|
@@ -166,7 +255,9 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
166
255
|
fileExists: (filePath) => fileExists(filePath, deps.fs),
|
|
167
256
|
platform: deps.platform,
|
|
168
257
|
}));
|
|
169
|
-
|
|
258
|
+
const existing = await checkExistingPlane(repoRoot, deps);
|
|
259
|
+
record(existing);
|
|
260
|
+
observations.existingPlane = existing.observation;
|
|
170
261
|
const blocking = diagnostics.filter((d) => d.severity === "blocking");
|
|
171
262
|
// `!runtimeEntrypoint.ok` is already covered by the blocking count; it is
|
|
172
263
|
// repeated here so the compiler narrows the union rather than requiring a
|
|
@@ -175,12 +266,13 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
175
266
|
// here, like `!runtimeEntrypoint.ok`, so the compiler narrows the union rather
|
|
176
267
|
// than requiring a non-null assertion on the context field below.
|
|
177
268
|
if (blocking.length > 0 || !credentials.ok || !runtimeEntrypoint.ok || !deps.endpoint.ok) {
|
|
178
|
-
return { ok: false, diagnostics, outcomes };
|
|
269
|
+
return { ok: false, diagnostics, outcomes, observations };
|
|
179
270
|
}
|
|
180
271
|
return {
|
|
181
272
|
ok: true,
|
|
182
273
|
diagnostics,
|
|
183
274
|
outcomes,
|
|
275
|
+
observations,
|
|
184
276
|
context: {
|
|
185
277
|
repoRoot,
|
|
186
278
|
repoName: credentials.repoName,
|
|
@@ -559,16 +651,22 @@ function createResolverWarningBuffer() {
|
|
|
559
651
|
* refuse a perfectly good bring-up.
|
|
560
652
|
*/
|
|
561
653
|
export async function checkServerPort(deps, endpoint) {
|
|
654
|
+
// BAPI-1153: the probe's own result kind rides beside the unchanged report, so
|
|
655
|
+
// a composing caller reads what was observed instead of re-probing the port.
|
|
656
|
+
const report = await checkServerPortReport(deps, endpoint);
|
|
657
|
+
return { ...report.report, probe: report.probe };
|
|
658
|
+
}
|
|
659
|
+
async function checkServerPortReport(deps, endpoint) {
|
|
562
660
|
const label = "Local server port";
|
|
563
661
|
const target = `${endpoint.host}:${endpoint.port}`;
|
|
564
662
|
const result = await deps.probePort(endpoint.host, endpoint.port, PLANE_PORT_PROBE_TIMEOUT_MS);
|
|
565
663
|
if (result.kind === "refused") {
|
|
566
|
-
return planeCheckPassed("server-port", label, `${target} is free`);
|
|
664
|
+
return { report: planeCheckPassed("server-port", label, `${target} is free`), probe: "refused" };
|
|
567
665
|
}
|
|
568
666
|
// Severity is unchanged in both branches: `connected` still blocks, an
|
|
569
667
|
// indeterminate probe still only warns.
|
|
570
668
|
if (result.kind === "connected") {
|
|
571
|
-
|
|
669
|
+
const occupied = planeCheckFinding({
|
|
572
670
|
check: "server-port",
|
|
573
671
|
severity: "blocking",
|
|
574
672
|
message: `${target} is already accepting connections. ` +
|
|
@@ -577,8 +675,9 @@ export async function checkServerPort(deps, endpoint) {
|
|
|
577
675
|
"to a free port.",
|
|
578
676
|
remediation: PLANE_SERVER_PORT_OCCUPIED_REMEDIATION,
|
|
579
677
|
}, label, `${target} is already accepting connections`);
|
|
678
|
+
return { report: occupied, probe: "connected" };
|
|
580
679
|
}
|
|
581
|
-
|
|
680
|
+
const indeterminate = planeCheckFinding({
|
|
582
681
|
check: "server-port",
|
|
583
682
|
severity: "warning",
|
|
584
683
|
message: `could not determine whether ${target} is free ` +
|
|
@@ -588,6 +687,7 @@ export async function checkServerPort(deps, endpoint) {
|
|
|
588
687
|
// The probe's own `error` text stays in the legacy message for the operator
|
|
589
688
|
// at the terminal and is not copied into the consolidated detail.
|
|
590
689
|
`could not determine whether ${target} is free`);
|
|
690
|
+
return { report: indeterminate, probe: "error" };
|
|
591
691
|
}
|
|
592
692
|
/**
|
|
593
693
|
* Refuse to start over a plane that is still alive.
|
|
@@ -602,11 +702,18 @@ export async function checkExistingPlane(repoRoot, deps) {
|
|
|
602
702
|
// Three distinct states, three distinct fixes (BAPI-1055): unreadable/malformed,
|
|
603
703
|
// stale, and live. Manifest validation, the liveness probe, and the
|
|
604
704
|
// never-touch-disk rule are all unchanged.
|
|
705
|
+
//
|
|
706
|
+
// BAPI-1153: each arm also returns its ATTACH classification. The diagnostics
|
|
707
|
+
// are untouched — a live plane still blocks `plane up` — and the classification
|
|
708
|
+
// is read from this one manifest read, never a second one.
|
|
605
709
|
if (read.kind === "missing") {
|
|
606
|
-
return
|
|
710
|
+
return {
|
|
711
|
+
...planeCheckPassed("existing-plane", label, "no plane manifest is present"),
|
|
712
|
+
observation: { kind: "none" },
|
|
713
|
+
};
|
|
607
714
|
}
|
|
608
715
|
if (read.kind !== "valid") {
|
|
609
|
-
|
|
716
|
+
const unvalidated = planeCheckFinding({
|
|
610
717
|
check: "existing-plane",
|
|
611
718
|
severity: "blocking",
|
|
612
719
|
message: `an existing .bridge/plane/plane.json could not be validated (${read.error}). ` +
|
|
@@ -614,6 +721,7 @@ export async function checkExistingPlane(repoRoot, deps) {
|
|
|
614
721
|
"it by hand if no plane is running.",
|
|
615
722
|
remediation: PLANE_MANIFEST_UNREADABLE_REMEDIATION,
|
|
616
723
|
}, label, "an existing plane manifest could not be validated");
|
|
724
|
+
return { ...unvalidated, observation: { kind: "unvalidated" } };
|
|
617
725
|
}
|
|
618
726
|
// BAPI-882: a manifest carrying THIS process's own plane id is its own claim,
|
|
619
727
|
// not a competitor. Checked after validation and BEFORE the liveness probe,
|
|
@@ -632,13 +740,17 @@ export async function checkExistingPlane(repoRoot, deps) {
|
|
|
632
740
|
// A live manifest with a DIFFERENT plane id still blocks, unchanged — that is
|
|
633
741
|
// a genuine second plane and the whole point of the check.
|
|
634
742
|
if (deps.ownPlaneId !== undefined && read.manifest.planeId === deps.ownPlaneId) {
|
|
635
|
-
return
|
|
743
|
+
return {
|
|
744
|
+
...planeCheckPassed("existing-plane", label, "the existing manifest carries this launch's own plane id"),
|
|
745
|
+
observation: { kind: "own-plane" },
|
|
746
|
+
};
|
|
636
747
|
}
|
|
637
748
|
if (!manifestHasLiveProcess(read.manifest, deps.proc)) {
|
|
638
749
|
// Stale. Reported as information so the operator understands why an old
|
|
639
750
|
// manifest is about to be replaced; the claim revalidates before it does.
|
|
640
751
|
// No remediation: the operator has nothing to do — the claim replaces it.
|
|
641
752
|
return {
|
|
753
|
+
observation: { kind: "stale" },
|
|
642
754
|
diagnostic: {
|
|
643
755
|
check: "existing-plane",
|
|
644
756
|
severity: "warning",
|
|
@@ -656,7 +768,7 @@ export async function checkExistingPlane(repoRoot, deps) {
|
|
|
656
768
|
],
|
|
657
769
|
};
|
|
658
770
|
}
|
|
659
|
-
|
|
771
|
+
const running = planeCheckFinding({
|
|
660
772
|
check: "existing-plane",
|
|
661
773
|
severity: "blocking",
|
|
662
774
|
message: `a plane is already running (supervisor pid ${read.manifest.supervisorPid}). ` +
|
|
@@ -666,6 +778,10 @@ export async function checkExistingPlane(repoRoot, deps) {
|
|
|
666
778
|
// The supervisor pid stays in the legacy message; the consolidated detail
|
|
667
779
|
// reports the fact, not the process identifier.
|
|
668
780
|
"a plane is already running for this repository");
|
|
781
|
+
return {
|
|
782
|
+
...running,
|
|
783
|
+
observation: classifyLivePlaneForAttach(repoRoot, read.manifest, deps.proc),
|
|
784
|
+
};
|
|
669
785
|
}
|
|
670
786
|
async function fileExists(filePath, fs) {
|
|
671
787
|
try {
|
|
@@ -702,7 +818,7 @@ export const PLANE_READINESS_DESCRIPTORS = [
|
|
|
702
818
|
/** Fixed detail for a plane prerequisite the preflight never reported on. */
|
|
703
819
|
const PLANE_UNREPORTED_DETAIL = "not reported — the plane preflight produced no outcome for this prerequisite";
|
|
704
820
|
/** Fixed remediation for a plane prerequisite the preflight never reported on. */
|
|
705
|
-
const PLANE_UNREPORTED_REMEDIATION = "run `plane up` (or `
|
|
821
|
+
const PLANE_UNREPORTED_REMEDIATION = "run `plane up` (or `epic-implementer readiness` again) to re-collect the plane preflight; this " +
|
|
706
822
|
"prerequisite's state is unknown, not healthy.";
|
|
707
823
|
/**
|
|
708
824
|
* Project a plane preflight result into canonical readiness checks.
|
package/build/plane/shutdown.js
CHANGED
|
@@ -230,7 +230,7 @@ async function waitForDeath(manifest, deps, budgetMs, pollMs) {
|
|
|
230
230
|
/**
|
|
231
231
|
* Render the server-side run-shutdown outcome (BAPI-872) as its own message
|
|
232
232
|
* lines — never claims signalling local processes changed server-side state,
|
|
233
|
-
* and always names the supported `
|
|
233
|
+
* and always names the supported `epic-implementer stop-run` check/remediation
|
|
234
234
|
* surface for every outcome that is not a fresh committed stop.
|
|
235
235
|
*/
|
|
236
236
|
function formatRunShutdown(outcome) {
|
|
@@ -239,7 +239,7 @@ function formatRunShutdown(outcome) {
|
|
|
239
239
|
return [
|
|
240
240
|
"Run shutdown: no epic run is bound to this plane. If a server-side run is " +
|
|
241
241
|
"active, this command did NOT stop it and its queued work may remain live. " +
|
|
242
|
-
"Run `
|
|
242
|
+
"Run `epic-implementer stop-run --epic-run-id <epic_run_id>` to stop it explicitly.",
|
|
243
243
|
];
|
|
244
244
|
case "committed":
|
|
245
245
|
return [`Run shutdown: stopped run ${outcome.epicRunId} — its queued work was cancelled.`];
|
|
@@ -249,13 +249,13 @@ function formatRunShutdown(outcome) {
|
|
|
249
249
|
return [
|
|
250
250
|
`Run shutdown: run ${outcome.epicRunId} is terminal (${outcome.status}) and cannot be ` +
|
|
251
251
|
"stopped. Signalling local processes does not change this — run " +
|
|
252
|
-
`\`
|
|
252
|
+
`\`epic-implementer stop-run --epic-run-id ${outcome.epicRunId}\` to check its state.`,
|
|
253
253
|
];
|
|
254
254
|
case "unavailable":
|
|
255
255
|
return [
|
|
256
256
|
`Run shutdown: could not reach the Bridge API to stop run ${outcome.epicRunId} ` +
|
|
257
257
|
`(${outcome.message}). The run may remain active with queued work — run ` +
|
|
258
|
-
`\`
|
|
258
|
+
`\`epic-implementer stop-run --epic-run-id ${outcome.epicRunId}\` to retry.`,
|
|
259
259
|
];
|
|
260
260
|
}
|
|
261
261
|
}
|
package/build/readiness-check.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The canonical shared readiness-check contract (BAPI-1055, AC-6).
|
|
3
3
|
*
|
|
4
4
|
* Conductor prerequisite probing is spread across five loci — `bridge doctor`
|
|
5
|
-
* ({@link file:./install-doctor.ts}), `
|
|
5
|
+
* ({@link file:./install-doctor.ts}), `epic-implementer doctor`
|
|
6
6
|
* ({@link file:./conductor/doctor.ts}), `plane` preflight
|
|
7
7
|
* ({@link file:./plane/preflight.ts}), the server `conductor-readiness`
|
|
8
8
|
* collector, and the unified install doctor
|
|
@@ -277,7 +277,7 @@ export function createReadinessCheck(input) {
|
|
|
277
277
|
/** Fixed prose for a check that had to be replaced because it was malformed. */
|
|
278
278
|
export const READINESS_MALFORMED_CHECK_DETAIL = "this prerequisite reported a result that failed the readiness contract, so its real state is unknown";
|
|
279
279
|
/** Fixed remediation for a replaced malformed check. */
|
|
280
|
-
export const READINESS_MALFORMED_CHECK_REMEDIATION = "re-run `
|
|
280
|
+
export const READINESS_MALFORMED_CHECK_REMEDIATION = "re-run `epic-implementer readiness`; if it persists, run `epic-implementer doctor` and `install conductor` to re-collect this locus.";
|
|
281
281
|
/** Fixed label for a replaced malformed check. */
|
|
282
282
|
export const READINESS_MALFORMED_CHECK_LABEL = "Unreadable prerequisite";
|
|
283
283
|
/**
|
|
@@ -330,7 +330,7 @@ export function summarizeReadinessChecks(checks) {
|
|
|
330
330
|
/** Fixed stand-in for a source detail that failed the secret-safety contract. */
|
|
331
331
|
export const READINESS_UNSAFE_DETAIL_REPLACEMENT = "detail withheld — the source reported it in a form this report may not carry";
|
|
332
332
|
/** Fixed stand-in for a source remediation that failed the secret-safety contract. */
|
|
333
|
-
export const READINESS_UNSAFE_REMEDIATION_REPLACEMENT = "re-run `
|
|
333
|
+
export const READINESS_UNSAFE_REMEDIATION_REPLACEMENT = "re-run `epic-implementer readiness`; this prerequisite's own fix text could not be carried into this report safely.";
|
|
334
334
|
/**
|
|
335
335
|
* Build a check, degrading REJECTED prose rather than losing the finding — and
|
|
336
336
|
* never throwing.
|