@bridge_gpt/mcp-server 0.2.58 → 0.2.59
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 +105 -109
- 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 +5 -6
- package/build/conductor/bridge-api-client.js +149 -0
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/claude-hook.js +16 -16
- package/build/conductor/doctor.js +24 -19
- package/build/conductor/epic-implementer-cli.js +38 -5
- package/build/conductor/epic-reconcile.js +1 -1
- package/build/conductor/epic-runtime.js +10 -10
- 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 +6 -6
- 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 +7 -7
- package/build/conductor/readiness.js +2 -2
- package/build/conductor/recovery-cli.js +243 -12
- package/build/conductor/recovery-operations.js +56 -4
- 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/doctor.js +16 -294
- package/build/drive-epic.js +65 -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 +155 -0
- package/build/epic-integration-pr.js +2 -2
- package/build/epic-shared/bridge-client.js +773 -0
- package/build/epic-shared/cut-protocol.js +392 -0
- package/build/executor/env.js +19 -8
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/job-errors.js +3 -3
- package/build/executor/job-runner.js +1 -1
- package/build/executor/merge-job.js +35 -3
- 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/implement-epic/cli.js +1 -1
- package/build/index.js +181 -218
- package/build/init.js +20 -28
- package/build/install-bridge-conductor.js +7 -7
- package/build/install-bridge.js +199 -7
- package/build/mcp-host-entry-adapter.js +6 -0
- package/build/mcp-host-targets.js +2 -0
- 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 +15 -0
- package/build/model-routing-host.js +25 -0
- package/build/model-routing-preferences.js +99 -0
- package/build/pipelines.generated.js +9 -9
- package/build/plan-epic-implementer-eligibility.js +215 -0
- package/build/plan-execution-ledger.js +8 -0
- package/build/plane/member-roster.js +5 -5
- 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/setup-epic.js +8 -8
- 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 +2 -2
- package/pipelines/implement-ticket.json +1 -1
- package/pipelines/plan-epic.json +2 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Combined merge/review Epic Implementer eligibility assessment for `plan-epic` (BAPI-964).
|
|
3
|
+
*
|
|
4
|
+
* Runs once the epic decomposition's child manifest is frozen and every
|
|
5
|
+
* sub-task's exploration text is written, so this module can predict — before
|
|
6
|
+
* any Epic Implementer worker is spawned — which children are likely to trip the
|
|
7
|
+
* runtime workflow-file merge guard (`api/library/vcs/epic_implementer_merge_service.py`)
|
|
8
|
+
* and which of those additionally touch `claude-review.yml` itself, where
|
|
9
|
+
* automated review cannot run at all (BAPI-941's supply-chain preflight refuses
|
|
10
|
+
* to review a workflow that differs from the base branch's copy).
|
|
11
|
+
*
|
|
12
|
+
* The BROADER merge classification is delegated entirely to the Python
|
|
13
|
+
* classifier via one Bridge API call for the complete child set — this module
|
|
14
|
+
* never reimplements "what counts as a workflow file". The NARROWER review
|
|
15
|
+
* subclass is classified locally by checking each child's combined text for the
|
|
16
|
+
* full `CLAUDE_REVIEW_WORKFLOW_RELPATH`, imported (not re-declared) from
|
|
17
|
+
* `claude-review-workflow-drift.js`.
|
|
18
|
+
*
|
|
19
|
+
* Fails open, deliberately: any validation failure, classification failure, or
|
|
20
|
+
* internal contradiction (a review-subset child the backend did not also mark
|
|
21
|
+
* merge-blocked) becomes the explicit `unavailable` result — never a fabricated
|
|
22
|
+
* zero count. A caller that cannot get a real answer must say so, not guess.
|
|
23
|
+
*/
|
|
24
|
+
import { CLAUDE_REVIEW_WORKFLOW_RELPATH } from "./claude-review-workflow-drift.js";
|
|
25
|
+
/**
|
|
26
|
+
* Build the real network-backed classification client that POSTs the complete
|
|
27
|
+
* child set to `/jira/planning/epic-workflow-eligibility` exactly once.
|
|
28
|
+
*/
|
|
29
|
+
export function createBridgeEpicImplementerEligibilityClient(deps) {
|
|
30
|
+
return {
|
|
31
|
+
async classifyChildren(children) {
|
|
32
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
33
|
+
// S3 backend route seam: keep the existing endpoint until its API migration.
|
|
34
|
+
const resp = await fetchImpl(deps.buildUrl("/planning/epic-workflow-eligibility"), {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: await deps.getPostHeaders(),
|
|
37
|
+
body: JSON.stringify({ repo_name: deps.repoName, children }),
|
|
38
|
+
});
|
|
39
|
+
if (!resp.ok) {
|
|
40
|
+
const errorText = await deps.handleResponse(resp);
|
|
41
|
+
throw new Error(`epic-workflow-eligibility classification request failed: ${resp.status}: ${errorText}`);
|
|
42
|
+
}
|
|
43
|
+
return (await resp.json());
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const UNAVAILABLE = { status: "unavailable" };
|
|
48
|
+
function isNonBlankString(value) {
|
|
49
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
50
|
+
}
|
|
51
|
+
/** Shape-validate an evidence array: every element must be a string. */
|
|
52
|
+
function isStringArray(value) {
|
|
53
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
54
|
+
}
|
|
55
|
+
/** Treat a missing/undefined optional field as empty text; reject a non-string value. */
|
|
56
|
+
function optionalTextOrNull(value) {
|
|
57
|
+
if (value === undefined || value === null)
|
|
58
|
+
return "";
|
|
59
|
+
return typeof value === "string" ? value : null;
|
|
60
|
+
}
|
|
61
|
+
/** Deterministically assemble one child's scan text from its authoritative fields. */
|
|
62
|
+
function buildCombinedText(child) {
|
|
63
|
+
return [child.title, child.scope, child.description, child.requirements]
|
|
64
|
+
.filter((part) => typeof part === "string" && part.length > 0)
|
|
65
|
+
.join("\n\n");
|
|
66
|
+
}
|
|
67
|
+
/** Validate the child collection at the module boundary. `null` means malformed. */
|
|
68
|
+
function validateChildren(children) {
|
|
69
|
+
if (!Array.isArray(children) || children.length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const validated = [];
|
|
72
|
+
for (const child of children) {
|
|
73
|
+
if (child === null || typeof child !== "object")
|
|
74
|
+
return null;
|
|
75
|
+
if (!isNonBlankString(child.id) || !isNonBlankString(child.title))
|
|
76
|
+
return null;
|
|
77
|
+
const scope = optionalTextOrNull(child.scope);
|
|
78
|
+
const description = optionalTextOrNull(child.description);
|
|
79
|
+
const requirements = optionalTextOrNull(child.requirements);
|
|
80
|
+
if (scope === null || description === null || requirements === null)
|
|
81
|
+
return null;
|
|
82
|
+
validated.push({
|
|
83
|
+
id: child.id,
|
|
84
|
+
title: child.title,
|
|
85
|
+
combinedText: buildCombinedText({
|
|
86
|
+
id: child.id,
|
|
87
|
+
title: child.title,
|
|
88
|
+
scope,
|
|
89
|
+
description,
|
|
90
|
+
requirements,
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return validated;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Assess Epic Implementer merge/review eligibility for one frozen epic child set.
|
|
98
|
+
*
|
|
99
|
+
* Sends the complete ordered child set to the injected Bridge client exactly
|
|
100
|
+
* once. Never throws — every failure path (malformed input, client rejection,
|
|
101
|
+
* malformed backend response, an internal merge/review contradiction) resolves
|
|
102
|
+
* to the explicit `unavailable` result, diagnosed with `console.error` (never
|
|
103
|
+
* `console.log`, which is reserved for the MCP stdio transport).
|
|
104
|
+
*/
|
|
105
|
+
export async function assessEpicImplementerEligibility(children, deps) {
|
|
106
|
+
const validated = validateChildren(children);
|
|
107
|
+
if (validated === null) {
|
|
108
|
+
console.error("assessEpicImplementerEligibility: malformed child collection; unavailable.");
|
|
109
|
+
return UNAVAILABLE;
|
|
110
|
+
}
|
|
111
|
+
let response;
|
|
112
|
+
try {
|
|
113
|
+
response = await deps.client.classifyChildren(validated.map((child) => ({ child_id: child.id, combined_text: child.combinedText })));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error("assessEpicImplementerEligibility: Bridge classification call failed:", err);
|
|
117
|
+
return UNAVAILABLE;
|
|
118
|
+
}
|
|
119
|
+
if (response === null ||
|
|
120
|
+
typeof response !== "object" ||
|
|
121
|
+
!Array.isArray(response.predictions) ||
|
|
122
|
+
!Number.isInteger(response.total_children) ||
|
|
123
|
+
response.total_children < 0 ||
|
|
124
|
+
!Number.isInteger(response.predicted_workflow_children) ||
|
|
125
|
+
response.predicted_workflow_children < 0 ||
|
|
126
|
+
!Number.isInteger(response.containment_children) ||
|
|
127
|
+
response.containment_children < 0 ||
|
|
128
|
+
typeof response.reason !== "string") {
|
|
129
|
+
console.error("assessEpicImplementerEligibility: malformed backend response shape; unavailable.");
|
|
130
|
+
return UNAVAILABLE;
|
|
131
|
+
}
|
|
132
|
+
if (response.predictions.length !== validated.length) {
|
|
133
|
+
console.error("assessEpicImplementerEligibility: backend prediction count does not match child count; unavailable.");
|
|
134
|
+
return UNAVAILABLE;
|
|
135
|
+
}
|
|
136
|
+
// The backend is contractually order-preserving; still verify identity and
|
|
137
|
+
// order rather than trusting it, so a reordered/duplicate/unknown response
|
|
138
|
+
// never silently mislabels a child's prediction.
|
|
139
|
+
const predictionById = new Map();
|
|
140
|
+
for (const prediction of response.predictions) {
|
|
141
|
+
if (prediction === null ||
|
|
142
|
+
typeof prediction !== "object" ||
|
|
143
|
+
!isNonBlankString(prediction.child_id) ||
|
|
144
|
+
typeof prediction.predicted_workflow_modified !== "boolean" ||
|
|
145
|
+
!isStringArray(prediction.matched_paths) ||
|
|
146
|
+
typeof prediction.predicted_containment_hazard !== "boolean" ||
|
|
147
|
+
!isStringArray(prediction.matched_hazards)) {
|
|
148
|
+
console.error("assessEpicImplementerEligibility: malformed per-child prediction; unavailable.");
|
|
149
|
+
return UNAVAILABLE;
|
|
150
|
+
}
|
|
151
|
+
if (prediction.predicted_containment_hazard !== (prediction.matched_hazards.length > 0)) {
|
|
152
|
+
console.error(`assessEpicImplementerEligibility: child ${prediction.child_id} has a containment flag that ` +
|
|
153
|
+
"disagrees with whether matched hazard evidence is present; unavailable.");
|
|
154
|
+
return UNAVAILABLE;
|
|
155
|
+
}
|
|
156
|
+
if (predictionById.has(prediction.child_id)) {
|
|
157
|
+
console.error("assessEpicImplementerEligibility: duplicate child_id in backend response; unavailable.");
|
|
158
|
+
return UNAVAILABLE;
|
|
159
|
+
}
|
|
160
|
+
predictionById.set(prediction.child_id, prediction);
|
|
161
|
+
}
|
|
162
|
+
const affectedChildren = [];
|
|
163
|
+
let reviewSubsetChildren = 0;
|
|
164
|
+
let predictedWorkflowChildren = 0;
|
|
165
|
+
let containmentChildren = 0;
|
|
166
|
+
for (const child of validated) {
|
|
167
|
+
const prediction = predictionById.get(child.id);
|
|
168
|
+
if (!prediction) {
|
|
169
|
+
console.error(`assessEpicImplementerEligibility: backend response is missing child ${child.id}; unavailable.`);
|
|
170
|
+
return UNAVAILABLE;
|
|
171
|
+
}
|
|
172
|
+
const reviewPathReferenced = child.combinedText.includes(CLAUDE_REVIEW_WORKFLOW_RELPATH);
|
|
173
|
+
if (reviewPathReferenced && !prediction.predicted_workflow_modified) {
|
|
174
|
+
// Subset invariant violated: a child naming claude-review.yml itself must
|
|
175
|
+
// also be in the broader merge-blocked set. Publishing this as a
|
|
176
|
+
// "review-only" child would contradict the merge count, so refuse instead.
|
|
177
|
+
console.error(`assessEpicImplementerEligibility: child ${child.id} references the review workflow ` +
|
|
178
|
+
"but the backend did not mark it merge-blocked; refusing contradictory result.");
|
|
179
|
+
return UNAVAILABLE;
|
|
180
|
+
}
|
|
181
|
+
if (prediction.predicted_workflow_modified) {
|
|
182
|
+
predictedWorkflowChildren += 1;
|
|
183
|
+
if (reviewPathReferenced)
|
|
184
|
+
reviewSubsetChildren += 1;
|
|
185
|
+
}
|
|
186
|
+
if (prediction.predicted_containment_hazard)
|
|
187
|
+
containmentChildren += 1;
|
|
188
|
+
// A child is affected — and therefore included — when it is workflow-modifying,
|
|
189
|
+
// containment-flagged, or both. Skip only a child that is neither.
|
|
190
|
+
if (!prediction.predicted_workflow_modified && !prediction.predicted_containment_hazard)
|
|
191
|
+
continue;
|
|
192
|
+
affectedChildren.push({
|
|
193
|
+
id: child.id,
|
|
194
|
+
title: child.title,
|
|
195
|
+
matchedPaths: [...prediction.matched_paths],
|
|
196
|
+
requiresHandReview: reviewPathReferenced,
|
|
197
|
+
requiresContainmentReview: prediction.predicted_containment_hazard,
|
|
198
|
+
matchedHazards: [...prediction.matched_hazards],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (containmentChildren !== response.containment_children) {
|
|
202
|
+
console.error("assessEpicImplementerEligibility: containment_children count disagrees with the validated " +
|
|
203
|
+
"per-child containment flags; unavailable.");
|
|
204
|
+
return UNAVAILABLE;
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
status: "assessed",
|
|
208
|
+
totalChildren: validated.length,
|
|
209
|
+
predictedWorkflowChildren,
|
|
210
|
+
reason: response.reason,
|
|
211
|
+
reviewSubsetChildren,
|
|
212
|
+
containmentChildren,
|
|
213
|
+
affectedChildren,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -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,
|
|
@@ -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.
|
|
@@ -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
|
*/
|