@bridge_gpt/mcp-server 0.2.58 → 0.2.60
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 +231 -113
- package/build/agent-guidance.generated.js +1 -1
- package/build/agent-notes.js +7 -1
- package/build/agent-registry.js +15 -0
- package/build/agent-utils.js +16 -8
- package/build/claude-user-config-doctor.js +1 -35
- package/build/codex-bridge-profile.js +1 -1
- package/build/commands.generated.js +7 -8
- package/build/conductor/bridge-api-client.js +202 -2
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/claude-hook.js +16 -16
- package/build/conductor/doctor.js +279 -72
- package/build/conductor/epic-implementer-cli.js +38 -5
- package/build/conductor/epic-reconcile.js +1 -1
- package/build/conductor/epic-runtime.js +21 -12
- package/build/conductor/errors.js +3 -3
- package/build/conductor/file-scope-guard.js +4 -4
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-inspection.js +2 -2
- package/build/conductor/install-doctor.js +28 -6
- package/build/conductor/local-merge.js +319 -26
- package/build/conductor/merge-execution-contract.js +61 -0
- package/build/conductor/merge-execution-eligibility.js +129 -0
- package/build/conductor/merge-ledger.js +2 -2
- package/build/conductor/pr-ci-producer.js +6 -6
- package/build/conductor/pr-review-producer.js +2 -2
- package/build/conductor/producer-ledger.js +2 -0
- package/build/conductor/readiness-sections.js +166 -9
- package/build/conductor/readiness.js +97 -16
- package/build/conductor/recovery-cli.js +243 -12
- package/build/conductor/recovery-operations.js +64 -6
- package/build/conductor/repair-contract.js +38 -4
- package/build/conductor/spec-review-producer.js +6 -6
- package/build/conductor/store.js +10 -10
- package/build/conductor/supervisor-config.js +14 -14
- package/build/conductor/supervisor-ledger.js +2 -2
- package/build/conductor/supervisor-message-relay.js +2 -2
- package/build/conductor/worker-ledger-cli.js +19 -19
- package/build/conductor-bundle-artifacts.js +2 -2
- package/build/connect-bitbucket-api.js +6 -1
- package/build/connect-bitbucket.js +398 -88
- package/build/credential-store.js +16 -0
- package/build/docs.generated.js +2 -2
- package/build/doctor.js +199 -303
- package/build/drive-epic.js +88 -71
- package/build/epic-implementer-bin.js +22 -17
- package/build/epic-implementer-claude-hook-bin.js +4 -2
- package/build/epic-implementer-env.js +184 -0
- package/build/epic-integration-pr.js +2 -2
- package/build/executor/ci-rerun-job.js +269 -0
- package/build/executor/cli.js +31 -0
- package/build/executor/credentials.js +11 -0
- package/build/executor/env.js +79 -8
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/index.js +21 -4
- package/build/executor/job-errors.js +3 -3
- package/build/executor/job-runner.js +127 -1
- package/build/executor/job-types.js +12 -2
- package/build/executor/merge-job.js +46 -5
- package/build/executor/observation.js +1 -1
- package/build/executor/preflight.js +1 -1
- package/build/executor/prompt-spec.js +1 -1
- package/build/executor/reconcile-overlap-job.js +10 -130
- package/build/executor/reconcile-overlap-types.js +1 -1
- package/build/executor/runner.js +1 -1
- package/build/executor/service-lifecycle.js +136 -0
- package/build/executor/service-unit.js +427 -9
- package/build/executor/types.js +5 -0
- package/build/index.js +320 -342
- package/build/init.js +20 -28
- package/build/install-bridge-conductor.js +208 -15
- package/build/install-bridge.js +555 -37
- package/build/mcp-host-entry-adapter.js +6 -0
- package/build/mcp-host-targets.js +2 -0
- package/build/mcp-install-state.js +42 -4
- package/build/mcp-invoke.js +5 -5
- package/build/mcp-profile.js +2 -2
- package/build/mcp-provisioning.js +2 -2
- package/build/mcp-server-invocation.js +3 -3
- package/build/merge-pull-request.js +118 -10
- package/build/merge-workflow-codeowners.js +224 -0
- package/build/merge-workflow-config.js +133 -0
- package/build/merge-workflow-drift.js +66 -0
- package/build/merge-workflow-install.js +638 -0
- package/build/merge-workflow.js +572 -0
- package/build/model-routing-host.js +25 -0
- package/build/model-routing-preferences.js +99 -0
- package/build/pipelines.generated.js +10 -10
- package/build/{plan-epic-conductor-eligibility.js → plan-epic-implementer-eligibility.js} +18 -16
- package/build/plan-execution-ledger.js +8 -0
- package/build/plane/cli.js +24 -2
- package/build/plane/member-roster.js +5 -5
- package/build/plane/preflight.js +68 -0
- package/build/plane/supervisor.js +1 -1
- package/build/plane/types.js +5 -5
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +4 -4
- package/build/run-unit-tests-launcher.js +5 -0
- package/build/setup-epic.js +171 -17
- package/build/start-tickets-conductor.js +32 -32
- package/build/start-tickets-prereqs.js +4 -9
- package/build/start-tickets.js +475 -91
- package/build/version.generated.js +3 -3
- package/docs/CONDUCTOR.md +69 -18
- package/docs/install/mcp-tool-integrations.md +2 -1
- package/package.json +4 -4
- package/pipelines/implement-ticket.json +1 -1
- package/pipelines/plan-epic.json +2 -2
- package/build/conduct-epic/bridge-client.js +0 -773
- package/build/conduct-epic/checkpoint-store.js +0 -542
- package/build/conduct-epic/cli.js +0 -3145
- package/build/conduct-epic/cut-protocol.js +0 -392
- package/build/conduct-epic/lock.js +0 -302
- package/build/conduct-epic/pr-state.js +0 -286
- package/build/conduct-epic/spawn.js +0 -113
- package/build/conductor/cli.js +0 -1278
- package/build/conductor-bundle-cli.js +0 -256
- package/build/direct-ticket-tools.js +0 -70
- package/build/executor/reconcile-overlap-governance.js +0 -129
- package/build/implement-epic/checkpoint-store.js +0 -542
- package/build/implement-epic/cli.js +0 -3158
- package/build/implement-epic/lock.js +0 -302
- package/build/implement-epic/pr-state.js +0 -286
- package/build/implement-epic/spawn.js +0 -113
- /package/build/{implement-epic → epic-shared}/bridge-client.js +0 -0
- /package/build/{implement-epic → epic-shared}/cut-protocol.js +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Combined merge/review
|
|
2
|
+
* Combined merge/review Epic Implementer eligibility assessment for `plan-epic` (BAPI-964).
|
|
3
3
|
*
|
|
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
|
-
* any
|
|
6
|
+
* any Epic Implementer worker is spawned — which children are likely to trip the
|
|
7
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
|
|
@@ -26,17 +26,19 @@ import { CLAUDE_REVIEW_WORKFLOW_RELPATH } from "./claude-review-workflow-drift.j
|
|
|
26
26
|
* Build the real network-backed classification client that POSTs the complete
|
|
27
27
|
* child set to `/jira/planning/epic-workflow-eligibility` exactly once.
|
|
28
28
|
*/
|
|
29
|
-
export function
|
|
29
|
+
export function createBridgeEpicImplementerEligibilityClient(deps) {
|
|
30
30
|
return {
|
|
31
31
|
async classifyChildren(children) {
|
|
32
32
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
33
|
+
// S3 backend route seam: keep the existing endpoint until its API migration.
|
|
33
34
|
const resp = await fetchImpl(deps.buildUrl("/planning/epic-workflow-eligibility"), {
|
|
34
35
|
method: "POST",
|
|
35
36
|
headers: await deps.getPostHeaders(),
|
|
36
37
|
body: JSON.stringify({ repo_name: deps.repoName, children }),
|
|
37
38
|
});
|
|
38
39
|
if (!resp.ok) {
|
|
39
|
-
|
|
40
|
+
const errorText = await deps.handleResponse(resp);
|
|
41
|
+
throw new Error(`epic-workflow-eligibility classification request failed: ${resp.status}: ${errorText}`);
|
|
40
42
|
}
|
|
41
43
|
return (await resp.json());
|
|
42
44
|
},
|
|
@@ -92,7 +94,7 @@ function validateChildren(children) {
|
|
|
92
94
|
return validated;
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
95
|
-
* Assess
|
|
97
|
+
* Assess Epic Implementer merge/review eligibility for one frozen epic child set.
|
|
96
98
|
*
|
|
97
99
|
* Sends the complete ordered child set to the injected Bridge client exactly
|
|
98
100
|
* once. Never throws — every failure path (malformed input, client rejection,
|
|
@@ -100,10 +102,10 @@ function validateChildren(children) {
|
|
|
100
102
|
* to the explicit `unavailable` result, diagnosed with `console.error` (never
|
|
101
103
|
* `console.log`, which is reserved for the MCP stdio transport).
|
|
102
104
|
*/
|
|
103
|
-
export async function
|
|
105
|
+
export async function assessEpicImplementerEligibility(children, deps) {
|
|
104
106
|
const validated = validateChildren(children);
|
|
105
107
|
if (validated === null) {
|
|
106
|
-
console.error("
|
|
108
|
+
console.error("assessEpicImplementerEligibility: malformed child collection; unavailable.");
|
|
107
109
|
return UNAVAILABLE;
|
|
108
110
|
}
|
|
109
111
|
let response;
|
|
@@ -111,7 +113,7 @@ export async function assessEpicConductorEligibility(children, deps) {
|
|
|
111
113
|
response = await deps.client.classifyChildren(validated.map((child) => ({ child_id: child.id, combined_text: child.combinedText })));
|
|
112
114
|
}
|
|
113
115
|
catch (err) {
|
|
114
|
-
console.error("
|
|
116
|
+
console.error("assessEpicImplementerEligibility: Bridge classification call failed:", err);
|
|
115
117
|
return UNAVAILABLE;
|
|
116
118
|
}
|
|
117
119
|
if (response === null ||
|
|
@@ -124,11 +126,11 @@ export async function assessEpicConductorEligibility(children, deps) {
|
|
|
124
126
|
!Number.isInteger(response.containment_children) ||
|
|
125
127
|
response.containment_children < 0 ||
|
|
126
128
|
typeof response.reason !== "string") {
|
|
127
|
-
console.error("
|
|
129
|
+
console.error("assessEpicImplementerEligibility: malformed backend response shape; unavailable.");
|
|
128
130
|
return UNAVAILABLE;
|
|
129
131
|
}
|
|
130
132
|
if (response.predictions.length !== validated.length) {
|
|
131
|
-
console.error("
|
|
133
|
+
console.error("assessEpicImplementerEligibility: backend prediction count does not match child count; unavailable.");
|
|
132
134
|
return UNAVAILABLE;
|
|
133
135
|
}
|
|
134
136
|
// The backend is contractually order-preserving; still verify identity and
|
|
@@ -143,16 +145,16 @@ export async function assessEpicConductorEligibility(children, deps) {
|
|
|
143
145
|
!isStringArray(prediction.matched_paths) ||
|
|
144
146
|
typeof prediction.predicted_containment_hazard !== "boolean" ||
|
|
145
147
|
!isStringArray(prediction.matched_hazards)) {
|
|
146
|
-
console.error("
|
|
148
|
+
console.error("assessEpicImplementerEligibility: malformed per-child prediction; unavailable.");
|
|
147
149
|
return UNAVAILABLE;
|
|
148
150
|
}
|
|
149
151
|
if (prediction.predicted_containment_hazard !== (prediction.matched_hazards.length > 0)) {
|
|
150
|
-
console.error(`
|
|
152
|
+
console.error(`assessEpicImplementerEligibility: child ${prediction.child_id} has a containment flag that ` +
|
|
151
153
|
"disagrees with whether matched hazard evidence is present; unavailable.");
|
|
152
154
|
return UNAVAILABLE;
|
|
153
155
|
}
|
|
154
156
|
if (predictionById.has(prediction.child_id)) {
|
|
155
|
-
console.error("
|
|
157
|
+
console.error("assessEpicImplementerEligibility: duplicate child_id in backend response; unavailable.");
|
|
156
158
|
return UNAVAILABLE;
|
|
157
159
|
}
|
|
158
160
|
predictionById.set(prediction.child_id, prediction);
|
|
@@ -164,7 +166,7 @@ export async function assessEpicConductorEligibility(children, deps) {
|
|
|
164
166
|
for (const child of validated) {
|
|
165
167
|
const prediction = predictionById.get(child.id);
|
|
166
168
|
if (!prediction) {
|
|
167
|
-
console.error(`
|
|
169
|
+
console.error(`assessEpicImplementerEligibility: backend response is missing child ${child.id}; unavailable.`);
|
|
168
170
|
return UNAVAILABLE;
|
|
169
171
|
}
|
|
170
172
|
const reviewPathReferenced = child.combinedText.includes(CLAUDE_REVIEW_WORKFLOW_RELPATH);
|
|
@@ -172,7 +174,7 @@ export async function assessEpicConductorEligibility(children, deps) {
|
|
|
172
174
|
// Subset invariant violated: a child naming claude-review.yml itself must
|
|
173
175
|
// also be in the broader merge-blocked set. Publishing this as a
|
|
174
176
|
// "review-only" child would contradict the merge count, so refuse instead.
|
|
175
|
-
console.error(`
|
|
177
|
+
console.error(`assessEpicImplementerEligibility: child ${child.id} references the review workflow ` +
|
|
176
178
|
"but the backend did not mark it merge-blocked; refusing contradictory result.");
|
|
177
179
|
return UNAVAILABLE;
|
|
178
180
|
}
|
|
@@ -197,7 +199,7 @@ export async function assessEpicConductorEligibility(children, deps) {
|
|
|
197
199
|
});
|
|
198
200
|
}
|
|
199
201
|
if (containmentChildren !== response.containment_children) {
|
|
200
|
-
console.error("
|
|
202
|
+
console.error("assessEpicImplementerEligibility: containment_children count disagrees with the validated " +
|
|
201
203
|
"per-child containment flags; unavailable.");
|
|
202
204
|
return UNAVAILABLE;
|
|
203
205
|
}
|
|
@@ -43,6 +43,10 @@ export const PLAN_PROVENANCE_CLASSES = [
|
|
|
43
43
|
"rendered_ui_review",
|
|
44
44
|
"test_gap_review",
|
|
45
45
|
"final_plan_review",
|
|
46
|
+
// BAPI-1223: the optimized posture's combined review/test stage. ONE class, not
|
|
47
|
+
// a test/review pair, because a single agent authors one contiguous body with
|
|
48
|
+
// interleaved test and review steps plus its deterministic floor repairs.
|
|
49
|
+
"combined_review_test",
|
|
46
50
|
];
|
|
47
51
|
export const PLAN_PHASES = [
|
|
48
52
|
"produce",
|
|
@@ -107,6 +111,10 @@ export const PLAN_CLASS_OWNERSHIP = Object.freeze({
|
|
|
107
111
|
rendered_ui_review: "pre_pr_verification",
|
|
108
112
|
test_gap_review: "pre_pr_verification",
|
|
109
113
|
final_plan_review: "pre_pr_verification",
|
|
114
|
+
// BAPI-1223: mixed test and review work in one body. Both halves are settled
|
|
115
|
+
// by the phase that RUNS them, which is the same phase that owns the separate
|
|
116
|
+
// classes this one replaces — so the route change moves no ownership.
|
|
117
|
+
combined_review_test: "pre_pr_verification",
|
|
110
118
|
});
|
|
111
119
|
export const PRE_PR_VERIFICATION_LIMITS = Object.freeze({
|
|
112
120
|
maxCorrectionTurns: 3,
|
package/build/plane/cli.js
CHANGED
|
@@ -469,6 +469,27 @@ async function runUpAction(options, sinks, overrides) {
|
|
|
469
469
|
}
|
|
470
470
|
return launch.exitCode;
|
|
471
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Format one diagnostic as one or more sink lines (BAPI-1237).
|
|
474
|
+
*
|
|
475
|
+
* `formatDiagnostic` returns a single string, but a diagnostic's `message` can
|
|
476
|
+
* itself be multiline — the shared legacy-environment guard's message is its
|
|
477
|
+
* own fixed header plus one `key -> successor` line per offending key. Writing
|
|
478
|
+
* that whole string through a single sink call would bury every line after the
|
|
479
|
+
* first inside one write, so this splits on newline boundaries and lets each
|
|
480
|
+
* substantive line become its own element — preserving the deterministic order
|
|
481
|
+
* the guard already produced.
|
|
482
|
+
*
|
|
483
|
+
* The final EMPTY element is dropped, never any other: it exists only because
|
|
484
|
+
* `runLegacyEpicImplementerEnvPreflight`'s formatter ends its block with a
|
|
485
|
+
* trailing newline, which is block punctuation, not a line to render.
|
|
486
|
+
*/
|
|
487
|
+
function formatDiagnosticLines(diagnostic) {
|
|
488
|
+
const lines = formatDiagnostic(diagnostic).split("\n");
|
|
489
|
+
if (lines.length > 0 && lines[lines.length - 1].length === 0)
|
|
490
|
+
lines.pop();
|
|
491
|
+
return lines;
|
|
492
|
+
}
|
|
472
493
|
/**
|
|
473
494
|
* The aggregate preflight refusal, as lines.
|
|
474
495
|
*
|
|
@@ -482,7 +503,7 @@ export function formatPlanePreflightRefusal(preflight) {
|
|
|
482
503
|
"plane up REFUSED — preflight found blocking problems:",
|
|
483
504
|
...preflight.diagnostics
|
|
484
505
|
.filter((d) => d.severity === "blocking")
|
|
485
|
-
.
|
|
506
|
+
.flatMap((d) => formatDiagnosticLines(d)),
|
|
486
507
|
"",
|
|
487
508
|
`Fix every item above and retry. ${PLANE_NOTHING_STARTED}.`,
|
|
488
509
|
];
|
|
@@ -792,7 +813,8 @@ async function runRuntimeAction(options, overrides) {
|
|
|
792
813
|
sinks.stderr("");
|
|
793
814
|
sinks.stderr("plane runtime REFUSED — preflight found blocking problems:");
|
|
794
815
|
for (const diagnostic of preflight.diagnostics.filter((d) => d.severity === "blocking")) {
|
|
795
|
-
|
|
816
|
+
for (const line of formatDiagnosticLines(diagnostic))
|
|
817
|
+
sinks.stderr(line);
|
|
796
818
|
}
|
|
797
819
|
sinks.stderr("");
|
|
798
820
|
sinks.stderr(`${PLANE_NOTHING_STARTED}.`);
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - the dead-man observer is a SEPARATE member in its own OS process (R-1,
|
|
14
14
|
* BAPI-1029): the plane contains exactly one ordinary worker and exactly one
|
|
15
15
|
* dead-man-only observer, and only the observer's environment carries
|
|
16
|
-
* `
|
|
16
|
+
* `EPIC_IMPLEMENTER_DEAD_MAN_ONLY`. The two are built from distinct environment
|
|
17
17
|
* objects so the observer flag can never reach the worker by reference —
|
|
18
18
|
* a plane whose "worker" is a second observer has nothing reconciling it.
|
|
19
19
|
* Independence here is process-level on ONE host; separate-host isolation is
|
|
@@ -132,7 +132,7 @@ export function resolvePlaneServerEndpoint(env) {
|
|
|
132
132
|
* Build a child environment: a copy of the parent plus the resolved values the
|
|
133
133
|
* existing processes already expect.
|
|
134
134
|
*
|
|
135
|
-
* `
|
|
135
|
+
* `EPIC_IMPLEMENTER_DEAD_MAN_ONLY` is explicitly deleted rather than merely left
|
|
136
136
|
* unset. An operator who exported it in this shell to run an observer would
|
|
137
137
|
* otherwise silently turn the plane's reconciler worker into a second observer,
|
|
138
138
|
* and the run would sit there with nothing reconciling it.
|
|
@@ -146,7 +146,7 @@ export function buildPlaneChildEnv(parentEnv, context) {
|
|
|
146
146
|
env.BAPI_REPO_NAME = context.repoName;
|
|
147
147
|
env.BAPI_BASE_URL = context.endpoint.baseUrl;
|
|
148
148
|
env.BAPI_API_KEY = context.bridgeApiKey;
|
|
149
|
-
delete env
|
|
149
|
+
delete env[PLANE_OBSERVER_MODE_ENV];
|
|
150
150
|
return env;
|
|
151
151
|
}
|
|
152
152
|
/**
|
|
@@ -159,7 +159,7 @@ export function buildPlaneChildEnv(parentEnv, context) {
|
|
|
159
159
|
* the precise failure `buildPlaneChildEnv`'s `delete` exists to prevent, arrived
|
|
160
160
|
* at from the other direction.
|
|
161
161
|
*
|
|
162
|
-
* Any `
|
|
162
|
+
* Any `EPIC_IMPLEMENTER_DEADMAN_CHANNEL_TYPE` / `EPIC_IMPLEMENTER_DEADMAN_DESTINATION_REF` the
|
|
163
163
|
* operator exported is inherited through the copy and is never read, echoed,
|
|
164
164
|
* placed in argv, or written to the manifest here: the observer resolves its own
|
|
165
165
|
* alert channel from its environment, exactly as a hand-started observer does.
|
|
@@ -251,7 +251,7 @@ export function buildPlaneMemberRoster(params) {
|
|
|
251
251
|
* The independent dead-man observer (R-1, BAPI-1029).
|
|
252
252
|
*
|
|
253
253
|
* The SAME `worker.py` the ordinary worker runs, in its own OS process, with
|
|
254
|
-
* `
|
|
254
|
+
* `EPIC_IMPLEMENTER_DEAD_MAN_ONLY=true` in its environment and nowhere else. No shell
|
|
255
255
|
* string: `PLANE_OBSERVER_COMMAND` remains operator-facing prose while the
|
|
256
256
|
* spawn is an executable plus an argument vector, so the mode flag never
|
|
257
257
|
* passes through a shell that could log it or reorder it.
|
package/build/plane/preflight.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import path from "path";
|
|
15
15
|
import { PLANE_SERVER_PORT_ENV_VAR, planeCheckFinding, planeCheckPassed, } from "./types.js";
|
|
16
|
+
import { runLegacyEpicImplementerEnvPreflight } from "../epic-implementer-env.js";
|
|
16
17
|
import { checkPlaneBuildFreshness, checkPlaneRuntimeEntrypoint } from "./build-freshness.js";
|
|
17
18
|
import { checkAlembicHead } from "./alembic-head.js";
|
|
18
19
|
import { manifestHasLiveProcess, planeManifestLifecycle, probeManifestMembers, readPlaneManifest, } from "./manifest.js";
|
|
@@ -163,6 +164,50 @@ const ROOT_DEPENDENT_CHECKS = [
|
|
|
163
164
|
["alembic-head", "alembic-head", "Database migration head"],
|
|
164
165
|
["existing-plane", "existing-plane", "Existing plane"],
|
|
165
166
|
];
|
|
167
|
+
/** Fixed detail for a prerequisite skipped because the legacy-environment guard refused first. */
|
|
168
|
+
const PLANE_LEGACY_ENVIRONMENT_SKIP_DETAIL = "not run — the legacy-environment guard refused first";
|
|
169
|
+
/**
|
|
170
|
+
* Every prerequisite `runPlanePreflight` short-circuits past when the shared
|
|
171
|
+
* legacy-environment guard finds an old name (BAPI-1237).
|
|
172
|
+
*
|
|
173
|
+
* `repository-root` is included here, unlike {@link ROOT_DEPENDENT_CHECKS}: the
|
|
174
|
+
* legacy-environment guard is the FIRST check, running even before repository
|
|
175
|
+
* root is confirmed, so a hit skips everything else the same way a bad root
|
|
176
|
+
* skips everything after it.
|
|
177
|
+
*/
|
|
178
|
+
const LEGACY_ENVIRONMENT_DEPENDENT_CHECKS = [
|
|
179
|
+
["repository-root", "repository-root", "Repository root"],
|
|
180
|
+
...ROOT_DEPENDENT_CHECKS,
|
|
181
|
+
];
|
|
182
|
+
/** Fixed remediation for a legacy Conductor / dead-man environment variable still set. */
|
|
183
|
+
export const PLANE_LEGACY_ENVIRONMENT_REMEDIATION = "rename or remove each legacy variable named above to its EPIC_IMPLEMENTER_* successor, then retry.";
|
|
184
|
+
/**
|
|
185
|
+
* Run the shared legacy Epic Implementer / dead-man environment guard as a
|
|
186
|
+
* plane preflight check (BAPI-1237).
|
|
187
|
+
*
|
|
188
|
+
* Key-only and non-mutating: this adapter never reads a legacy value,
|
|
189
|
+
* translates a key, deletes a key, or constructs a successor value from a
|
|
190
|
+
* supplied value. It only asks the shared guard — the same one `epic-implementer-env.ts`
|
|
191
|
+
* exports for every other guarded entry point — whether any old name is
|
|
192
|
+
* present in the INJECTED preflight environment, and renders its one fixed
|
|
193
|
+
* multiline diagnostic as a blocking finding when it is.
|
|
194
|
+
*/
|
|
195
|
+
function checkLegacyEnvironment(env) {
|
|
196
|
+
const label = "Legacy environment";
|
|
197
|
+
let captured = "";
|
|
198
|
+
const hit = runLegacyEpicImplementerEnvPreflight(env, (chunk) => {
|
|
199
|
+
captured += chunk;
|
|
200
|
+
});
|
|
201
|
+
if (hit === 0) {
|
|
202
|
+
return planeCheckPassed("legacy-environment", label, "no legacy Conductor or dead-man environment variable is set");
|
|
203
|
+
}
|
|
204
|
+
return planeCheckFinding({
|
|
205
|
+
check: "legacy-environment",
|
|
206
|
+
severity: "blocking",
|
|
207
|
+
message: captured,
|
|
208
|
+
remediation: PLANE_LEGACY_ENVIRONMENT_REMEDIATION,
|
|
209
|
+
}, label, "a legacy Conductor or dead-man environment variable is still set");
|
|
210
|
+
}
|
|
166
211
|
/** Bound on the port probe so a black-holed port cannot stall bring-up. */
|
|
167
212
|
export const PLANE_PORT_PROBE_TIMEOUT_MS = 1_500;
|
|
168
213
|
/**
|
|
@@ -188,6 +233,23 @@ export async function runPlanePreflight(repoRoot, deps) {
|
|
|
188
233
|
outcomes.push(...report.outcomes);
|
|
189
234
|
return report;
|
|
190
235
|
};
|
|
236
|
+
// BAPI-1237: the shared legacy-environment guard runs FIRST — before
|
|
237
|
+
// repository inspection, filesystem setup, subprocess execution, port
|
|
238
|
+
// probing, or credential resolution — so a legacy Conductor/dead-man name
|
|
239
|
+
// blocks before any of those collaborators run, not merely before launch.
|
|
240
|
+
const legacyEnvironment = record(checkLegacyEnvironment(deps.env));
|
|
241
|
+
if (legacyEnvironment.diagnostic) {
|
|
242
|
+
for (const [check, id, label] of LEGACY_ENVIRONMENT_DEPENDENT_CHECKS) {
|
|
243
|
+
outcomes.push({
|
|
244
|
+
check,
|
|
245
|
+
id,
|
|
246
|
+
label,
|
|
247
|
+
status: "skip",
|
|
248
|
+
detail: PLANE_LEGACY_ENVIRONMENT_SKIP_DETAIL,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return { ok: false, diagnostics, outcomes };
|
|
252
|
+
}
|
|
191
253
|
const rootCheck = record(await checkRepositoryRoot(repoRoot, deps));
|
|
192
254
|
// Every remaining check is relative to the repository root. Without a valid
|
|
193
255
|
// one there is nothing coherent left to check, so report and stop here rather
|
|
@@ -803,6 +865,12 @@ async function fileExists(filePath, fs) {
|
|
|
803
865
|
* yield every prerequisite as an explicit unknown.
|
|
804
866
|
*/
|
|
805
867
|
export const PLANE_READINESS_DESCRIPTORS = [
|
|
868
|
+
// BAPI-1237 — the shared legacy-environment guard is the first check
|
|
869
|
+
// `runPlanePreflight` runs, so it leads this descriptor table too. Omitting
|
|
870
|
+
// it here would make the check invisible to `epic-implementer readiness` /
|
|
871
|
+
// `drive-epic` even though it can block `plane up` — exactly the "silence
|
|
872
|
+
// reads as pass" failure this table exists to rule out.
|
|
873
|
+
{ id: "legacy-environment", label: "Legacy environment" },
|
|
806
874
|
{ id: "repository-root", label: "Repository root" },
|
|
807
875
|
{ id: "worktree-presence", label: "Git work tree" },
|
|
808
876
|
{ id: "claude-login", label: "Claude login" },
|
|
@@ -106,7 +106,7 @@ export function createLineReader(onLine) {
|
|
|
106
106
|
* Rebuild the instance identity a spawned member publishes under.
|
|
107
107
|
*
|
|
108
108
|
* Mirrors `_resolve_process_instance_id` in
|
|
109
|
-
* `api/library/
|
|
109
|
+
* `api/library/epic_implementer/reconciler.py` exactly, because the string has to
|
|
110
110
|
* MATCH: `DYNO` when that variable is set and non-blank after trimming,
|
|
111
111
|
* otherwise `<hostname>:<pid>`, truncated to 128 characters. Python resolves it
|
|
112
112
|
* once at import from the process's own environment and pid; this rebuilds it
|
package/build/plane/types.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* - **R-1, the dead-man observer is a same-host, separate-process member**
|
|
14
14
|
* (BAPI-1029, superseding R-3). The observer runs `worker.py` with
|
|
15
|
-
* `
|
|
15
|
+
* `EPIC_IMPLEMENTER_DEAD_MAN_ONLY=true` in its OWN OS process, owned by the plane
|
|
16
16
|
* lifecycle: it has a member name, a log path, and a manifest record, and
|
|
17
17
|
* `plane status` / `plane down` treat it like every other member. Its failure
|
|
18
18
|
* domain is independent at the PROCESS level — killing the reconciler leaves
|
|
@@ -143,15 +143,15 @@ export const PLANE_ID_ENV_VAR = "BAPI_PLANE_ID";
|
|
|
143
143
|
* session-background FALLBACK still starts the observer by hand, and because an
|
|
144
144
|
* operator debugging the plane needs to know exactly what the observer member is.
|
|
145
145
|
*/
|
|
146
|
-
export const PLANE_OBSERVER_COMMAND = "
|
|
146
|
+
export const PLANE_OBSERVER_COMMAND = "EPIC_IMPLEMENTER_DEAD_MAN_ONLY=true python worker.py";
|
|
147
147
|
/** Env var that puts `worker.py` into dead-man-only mode. Observer member ONLY. */
|
|
148
|
-
export const PLANE_OBSERVER_MODE_ENV = "
|
|
148
|
+
export const PLANE_OBSERVER_MODE_ENV = "EPIC_IMPLEMENTER_DEAD_MAN_ONLY";
|
|
149
149
|
/** The exact value {@link PLANE_OBSERVER_MODE_ENV} is set to for the observer. */
|
|
150
150
|
export const PLANE_OBSERVER_MODE_VALUE = "true";
|
|
151
151
|
/** Env var naming the observer's alert channel type. */
|
|
152
|
-
export const PLANE_OBSERVER_CHANNEL_TYPE_ENV = "
|
|
152
|
+
export const PLANE_OBSERVER_CHANNEL_TYPE_ENV = "EPIC_IMPLEMENTER_DEADMAN_CHANNEL_TYPE";
|
|
153
153
|
/** Env var naming the env var that holds the observer's destination URL. */
|
|
154
|
-
export const PLANE_OBSERVER_DESTINATION_ENV = "
|
|
154
|
+
export const PLANE_OBSERVER_DESTINATION_ENV = "EPIC_IMPLEMENTER_DEADMAN_DESTINATION_REF";
|
|
155
155
|
/** Build a {@link PlaneCheckReport} for a check that ran and passed. */
|
|
156
156
|
export function planeCheckPassed(check, label, detail) {
|
|
157
157
|
return {
|
package/build/readiness-check.js
CHANGED
|
@@ -147,7 +147,7 @@ const POSIX_PATH_ROOTS = [
|
|
|
147
147
|
* Deliberately narrow: it catches the obvious leak (`/Users/...`, `/private/tmp/...`,
|
|
148
148
|
* `C:\\...`) without rejecting the relative, repository-conventional paths
|
|
149
149
|
* remediations legitimately name (`.github/workflows/claude-review.yml`,
|
|
150
|
-
* `docs/claude/epic-
|
|
150
|
+
* `docs/claude/epic-implementer-v2-operator-runbook.md`) or the approved setup
|
|
151
151
|
* URLs (`https://host/setup`) — note the Windows drive alternative requires a
|
|
152
152
|
* word boundary before the letter, so the `s:/` inside `https://` is not a hit.
|
|
153
153
|
*/
|