@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
package/build/review-tickets.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Optionally supports a supervisor-driven epic-dispatch path: when an
|
|
14
14
|
* `EpicDispatchIdentity` is threaded through `ReviewTicketsOptions.epic`, each
|
|
15
|
-
* spawned tab receives an epic-identity env block (
|
|
16
|
-
*
|
|
15
|
+
* spawned tab receives an epic-identity env block (BAPI_EPIC_IMPLEMENTER_EPIC_KEY /
|
|
16
|
+
* BAPI_EPIC_IMPLEMENTER_EPIC_RUN_ID / BAPI_EPIC_IMPLEMENTER_PLAN_VERSION / BAPI_EPIC_IMPLEMENTER_RUN_ID)
|
|
17
17
|
* and dispatch is made idempotent via an injected `claimEpicDispatch` callback.
|
|
18
18
|
* This path is worktree-free and base-branch-free; the human CLI path leaves
|
|
19
19
|
* `epic` unset and retains byte-for-byte identical behavior.
|
|
@@ -518,7 +518,7 @@ export function buildReviewPlanRows(deps, options, agent, status, overrides = {}
|
|
|
518
518
|
if (options.epic) {
|
|
519
519
|
const mint = overrides.mintRunId ?? mintStartTicketsRunId;
|
|
520
520
|
runId = mint([key]);
|
|
521
|
-
const env = { ...buildEpicIdentityEnv(options.epic),
|
|
521
|
+
const env = { ...buildEpicIdentityEnv(options.epic), BAPI_EPIC_IMPLEMENTER_RUN_ID: runId };
|
|
522
522
|
command = injectConductorEnvIntoShellCommand(deps.platform, baseCommand, env);
|
|
523
523
|
}
|
|
524
524
|
return {
|
|
@@ -599,7 +599,7 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
599
599
|
if (options.epic) {
|
|
600
600
|
const mint = overrides.mintRunId ?? mintStartTicketsRunId;
|
|
601
601
|
runId = mint([key]);
|
|
602
|
-
const env = { ...buildEpicIdentityEnv(options.epic),
|
|
602
|
+
const env = { ...buildEpicIdentityEnv(options.epic), BAPI_EPIC_IMPLEMENTER_RUN_ID: runId };
|
|
603
603
|
shellCommand = injectConductorEnvIntoShellCommand(deps.platform, baseShellCommand, env);
|
|
604
604
|
if (options.epic.dispatch_key) {
|
|
605
605
|
if (!overrides.claimEpicDispatch) {
|
|
@@ -65,6 +65,11 @@ export const MODULE_MOCK_MANIFEST = [
|
|
|
65
65
|
"build/conductor/store-lifecycle.test.js",
|
|
66
66
|
"build/conductor/store-queries.test.js",
|
|
67
67
|
"build/conductor/tools.test.js",
|
|
68
|
+
// BAPI-1247: drives offerBitbucketConnection to a chosen structured outcome by
|
|
69
|
+
// mocking the shared connect flow at install-bridge.ts's import site, which needs
|
|
70
|
+
// --experimental-test-module-mocks. Kept apart from install-bridge.test.js so that
|
|
71
|
+
// 346-test file stays in the NORMAL lane.
|
|
72
|
+
"build/connect-bitbucket-handoff.test.js",
|
|
68
73
|
"build/connect-github-api.test.js",
|
|
69
74
|
"build/connect-github-dispatch.static.test.js",
|
|
70
75
|
"build/connect-github-handoff.test.js",
|
package/build/setup-epic.js
CHANGED
|
@@ -78,7 +78,14 @@
|
|
|
78
78
|
import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
|
|
79
79
|
import os from "node:os";
|
|
80
80
|
import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullRequest, formatEpicIntegrationPullRequestOutcome, } from "./epic-integration-pr.js";
|
|
81
|
-
import { approveEpicPlan, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
81
|
+
import { approveEpicPlan, buildConductorJiraUrl, conductorGetHeaders, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
82
|
+
// BAPI-1133 (A2) — the single legacy-to-typed merge-execution-mode mapping and
|
|
83
|
+
// its total legacy resolver live in `merge-pull-request.ts` (no import cycle:
|
|
84
|
+
// that module never imports from `setup-epic.ts`). Reused here rather than
|
|
85
|
+
// reimplemented so the mapping stays single-defined.
|
|
86
|
+
import { mapLegacyMergeExecutionModeToPolicyMode, resolveMergeExecutionMode, } from "./merge-pull-request.js";
|
|
87
|
+
import { MERGE_EXECUTION_INELIGIBLE, MERGE_EXECUTION_MODE_GITHUB_ACTIONS, MERGE_WORKFLOW_REMEDY_COMMAND, } from "./conductor/merge-execution-contract.js";
|
|
88
|
+
import { classifyMergeExecutionEligibility } from "./conductor/merge-execution-eligibility.js";
|
|
82
89
|
import { resolveTrackedPaths } from "./git-reachability.js";
|
|
83
90
|
import { validateBranchName } from "./base-ref.js";
|
|
84
91
|
// Reused from the implement-epic client on purpose: adding a second wrapper under
|
|
@@ -89,8 +96,8 @@ import { validateBranchName } from "./base-ref.js";
|
|
|
89
96
|
// typed clients the pilot already has are the v2 path's clients too, so neither
|
|
90
97
|
// conductor can drift on a field name or an endpoint shape. No MCP tool, no
|
|
91
98
|
// input-schema field, and no description changes.
|
|
92
|
-
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./
|
|
93
|
-
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./
|
|
99
|
+
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./epic-shared/bridge-client.js";
|
|
100
|
+
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./epic-shared/cut-protocol.js";
|
|
94
101
|
import { hashPlan } from "./conductor/plan.js";
|
|
95
102
|
import { BRING_UP_REFUSAL_MESSAGES, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
|
|
96
103
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
@@ -111,7 +118,7 @@ export const SETUP_EPIC_UNATTENDED_POSTURE = "unattended";
|
|
|
111
118
|
/**
|
|
112
119
|
* The server's stable error code for an unattended-posture refusal (BAPI-734).
|
|
113
120
|
*
|
|
114
|
-
* Mirrors `api.library.
|
|
121
|
+
* Mirrors `api.library.epic_implementer.unattended_posture.UNATTENDED_POSTURE_REFUSED_CODE`.
|
|
115
122
|
* Matched by EQUALITY on the structured `error_code`, never by searching the
|
|
116
123
|
* message text: the message is prose that may be rewritten, the code is the
|
|
117
124
|
* contract.
|
|
@@ -128,6 +135,19 @@ export const SETUP_EPIC_UNATTENDED_POSTURE_REFUSED_CODE = "UNATTENDED_POSTURE_RE
|
|
|
128
135
|
* which is what the observed BAPI-1085 run did.
|
|
129
136
|
*/
|
|
130
137
|
export const SETUP_EPIC_AUTO_MERGE_POLICY_KEY = "v2_auto_merge_enabled";
|
|
138
|
+
/**
|
|
139
|
+
* The `policy_json` key declaring the typed merge-execution mode (BAPI-1133, A2).
|
|
140
|
+
*
|
|
141
|
+
* Composed into the unattended default alongside `posture` and
|
|
142
|
+
* {@link SETUP_EPIC_AUTO_MERGE_POLICY_KEY}, in the SAME fill-if-absent branch,
|
|
143
|
+
* because the server's `RunPolicy` refuses an unattended auto-merge policy that
|
|
144
|
+
* omits it (`merge_execution_mode_unconfigured`). `setup-epic` is the only
|
|
145
|
+
* IMPLICIT writer of this key — any other client may still submit it explicitly
|
|
146
|
+
* as operator policy, and an explicitly supplied value here is never overwritten.
|
|
147
|
+
* Declaring `github_actions` or `machine_token` is intent only in this slice:
|
|
148
|
+
* neither changes merge execution until a later A2 child.
|
|
149
|
+
*/
|
|
150
|
+
export const SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY = "merge_execution_mode";
|
|
131
151
|
/** Accepted `policy_json.review_policy.source` values (the `ReviewPolicy` surface). */
|
|
132
152
|
export const SETUP_EPIC_REVIEW_POLICY_SOURCES = [
|
|
133
153
|
"verdict_protocol",
|
|
@@ -169,12 +189,15 @@ export function getSetupEpicUsage() {
|
|
|
169
189
|
"repository base branch, matching plain start-tickets semantics.",
|
|
170
190
|
"",
|
|
171
191
|
"Posture (default): a run that selects an epic branch is created UNATTENDED —",
|
|
172
|
-
"posture: unattended
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"
|
|
192
|
+
"posture: unattended, v2_auto_merge_enabled: true, and merge_execution_mode",
|
|
193
|
+
"(declared automatically from the repository's configured merge execution; a",
|
|
194
|
+
"policy declaration only — it does not change merge execution in this release)",
|
|
195
|
+
"— and the server stamps its CI gate at first approval from the repository",
|
|
196
|
+
"default, or records an explicit no-CI-gate mode when the repository declares",
|
|
197
|
+
"none. Child PRs merge into the epic branch; the INTEGRATION PR into the",
|
|
198
|
+
"repository base branch stays human-gated. Pass --attended to opt out. A run",
|
|
199
|
+
"that selects no epic branch (--into-base, or a single-node plan) is unchanged",
|
|
200
|
+
"and composes no posture at all.",
|
|
178
201
|
"",
|
|
179
202
|
"Unattended runs have TWO one-time repository prerequisites, refused by name",
|
|
180
203
|
"before any run is created: repository consent (unattended_epic_implementer_allowed) and",
|
|
@@ -239,7 +262,7 @@ export function getSetupEpicUsage() {
|
|
|
239
262
|
" 10799; remediate / ci_fix 3599; rebase / smoke 2699;",
|
|
240
263
|
" merge 1799); the server rejects a higher value with a",
|
|
241
264
|
" 422 naming the offending field. Full table:",
|
|
242
|
-
" docs/claude/epic-
|
|
265
|
+
" docs/claude/epic-implementer-v2-operator-runbook.md §4.",
|
|
243
266
|
" --replace-policy Authorize replacing a LIVE run's stored policy with the",
|
|
244
267
|
" policy file, as a complete replacement. Without it, a",
|
|
245
268
|
" divergent policy on a reused run is refused with a",
|
|
@@ -1130,7 +1153,7 @@ export function resolveFeatureBranchSelection(opts, planNodeCount, policyDeclare
|
|
|
1130
1153
|
* refused every unattended run, its own named remedy included.
|
|
1131
1154
|
*
|
|
1132
1155
|
* The reason CODES are the server's bounded vocabulary, spelled exactly as
|
|
1133
|
-
* `api/library/
|
|
1156
|
+
* `api/library/epic_implementer/unattended_posture.py` spells them. The client adds
|
|
1134
1157
|
* remediation TEXT around them and invents no aliases — an alias would drift from
|
|
1135
1158
|
* the server's refusal the first time either side was edited, and an operator
|
|
1136
1159
|
* comparing a CLI message with an API response would have no way to tell they
|
|
@@ -1145,7 +1168,7 @@ export function resolveFeatureBranchSelection(opts, planNodeCount, policyDeclare
|
|
|
1145
1168
|
* Does this policy declare a notify webhook the server would probe?
|
|
1146
1169
|
*
|
|
1147
1170
|
* BAPI-1102. The shape check mirrors `_extract_webhook_url` in
|
|
1148
|
-
* `api/library/
|
|
1171
|
+
* `api/library/epic_implementer/webhook_verification.py` exactly as far as
|
|
1149
1172
|
* PRESENCE goes — a `notify` object carrying a non-blank string `webhook_url` —
|
|
1150
1173
|
* and stops there on purpose. Scheme, host, and reachability are the server's to
|
|
1151
1174
|
* judge at activation, where the probe actually happens; re-deciding them here
|
|
@@ -1244,6 +1267,66 @@ export function evaluateSetupEpicBringUpPrerequisites(readiness) {
|
|
|
1244
1267
|
return null;
|
|
1245
1268
|
return { reason, message: `${BRING_UP_REFUSAL_MESSAGES[reason]} ${NO_RUN_WAS_CREATED}` };
|
|
1246
1269
|
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Read the composed policy's typed `merge_execution_mode`, if it declares one.
|
|
1272
|
+
*
|
|
1273
|
+
* Strict equality against the mirrored constant, matching the server's own guard
|
|
1274
|
+
* (`preflight._strict_github_actions_mode`). A missing key, a non-string, and
|
|
1275
|
+
* any other mode all answer `false`, so nothing but an explicit declaration
|
|
1276
|
+
* opens the gate.
|
|
1277
|
+
*/
|
|
1278
|
+
export function policyDeclaresGitHubActionsMerge(policy) {
|
|
1279
|
+
if (policy === undefined || policy === null)
|
|
1280
|
+
return false;
|
|
1281
|
+
return policy.merge_execution_mode === MERGE_EXECUTION_MODE_GITHUB_ACTIONS;
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Evaluate merge-execution eligibility against a readiness report.
|
|
1285
|
+
*
|
|
1286
|
+
* Pure and total. Returns `null` — no refusal — for every policy that does not
|
|
1287
|
+
* declare `github_actions` mode, without reading the report at all.
|
|
1288
|
+
*
|
|
1289
|
+
* Uses {@link classifyMergeExecutionEligibility}, the same classifier readiness
|
|
1290
|
+
* and `drive-epic` read, so all three agree on the status AND on the first
|
|
1291
|
+
* blocker. A client that classified the block itself here would eventually
|
|
1292
|
+
* disagree with the row an operator had just been shown.
|
|
1293
|
+
*
|
|
1294
|
+
* BOTH `not_eligible` and `could_not_verify` return
|
|
1295
|
+
* `merge_execution_ineligible`. One code, because the operator's next action is
|
|
1296
|
+
* the same and splitting it would put the burden of telling "not proven" from
|
|
1297
|
+
* "proven absent" on the person least able to check. The MESSAGE keeps the
|
|
1298
|
+
* capability category explicit, so the distinction survives where it is
|
|
1299
|
+
* actionable.
|
|
1300
|
+
*
|
|
1301
|
+
* The message is built from the shared remedy constant, ends with the exact
|
|
1302
|
+
* standalone sentence `No run was created.`, and carries no GitHub payload, URL,
|
|
1303
|
+
* branch name, workflow source, credential, or exception text.
|
|
1304
|
+
*/
|
|
1305
|
+
export function evaluateSetupEpicMergeExecutionPrerequisites(readiness, requestedPolicy) {
|
|
1306
|
+
if (!policyDeclaresGitHubActionsMerge(requestedPolicy))
|
|
1307
|
+
return null;
|
|
1308
|
+
if (readiness === null) {
|
|
1309
|
+
return {
|
|
1310
|
+
reason: MERGE_EXECUTION_INELIGIBLE,
|
|
1311
|
+
message: "this run declares merge_execution_mode: github_actions, and the Bridge API " +
|
|
1312
|
+
"conductor readiness report could not be read, so the repository's merge " +
|
|
1313
|
+
"capability could not be confirmed. Retry once the server is reachable, or " +
|
|
1314
|
+
`resolve it with \`${MERGE_WORKFLOW_REMEDY_COMMAND}\`. ${NO_RUN_WAS_CREATED}`,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
const verdict = classifyMergeExecutionEligibility(readiness);
|
|
1318
|
+
if (verdict.status === "eligible")
|
|
1319
|
+
return null;
|
|
1320
|
+
const category = verdict.status === "could_not_verify"
|
|
1321
|
+
? "could not be confirmed"
|
|
1322
|
+
: "is not sufficient";
|
|
1323
|
+
return {
|
|
1324
|
+
reason: MERGE_EXECUTION_INELIGIBLE,
|
|
1325
|
+
message: "this run declares merge_execution_mode: github_actions, and this repository's " +
|
|
1326
|
+
`merge capability ${category}: ${verdict.detail}. Resolve it with ` +
|
|
1327
|
+
`\`${MERGE_WORKFLOW_REMEDY_COMMAND}\`, then re-run. ${NO_RUN_WAS_CREATED}`,
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1247
1330
|
/**
|
|
1248
1331
|
* Does the policy this invocation will actually send request unattended operation?
|
|
1249
1332
|
*
|
|
@@ -1283,8 +1366,10 @@ export function requestedPolicyIsUnattended(policy, attended) {
|
|
|
1283
1366
|
* * **Feature branch, `--attended`** → `base_branch` plus an explicitly
|
|
1284
1367
|
* requested `review_policy`, and nothing else. Byte-identical to the
|
|
1285
1368
|
* pre-BAPI-1102 composition, which is precisely what the opt-out promises.
|
|
1286
|
-
* * **Feature branch, default** → adds `posture: "unattended"
|
|
1287
|
-
* `v2_auto_merge_enabled: true
|
|
1369
|
+
* * **Feature branch, default** → adds `posture: "unattended"`,
|
|
1370
|
+
* `v2_auto_merge_enabled: true`, and (BAPI-1133, A2) `merge_execution_mode`
|
|
1371
|
+
* when the caller resolved one — the server refuses an unattended auto-merge
|
|
1372
|
+
* policy that omits it.
|
|
1288
1373
|
*
|
|
1289
1374
|
* Deliberately absent from every shape:
|
|
1290
1375
|
*
|
|
@@ -1306,6 +1391,9 @@ export function composeDefaultSetupEpicPolicy(args) {
|
|
|
1306
1391
|
if (!args.attended) {
|
|
1307
1392
|
policy.posture = SETUP_EPIC_UNATTENDED_POSTURE;
|
|
1308
1393
|
policy[SETUP_EPIC_AUTO_MERGE_POLICY_KEY] = true;
|
|
1394
|
+
if (args.mergeExecutionMode !== undefined) {
|
|
1395
|
+
policy[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] = args.mergeExecutionMode;
|
|
1396
|
+
}
|
|
1309
1397
|
}
|
|
1310
1398
|
}
|
|
1311
1399
|
if (args.reviewPolicy !== undefined) {
|
|
@@ -1346,6 +1434,14 @@ export function composeDefaultSetupEpicPolicy(args) {
|
|
|
1346
1434
|
* * **`v2_auto_merge_enabled` already present** → left exactly as the file set
|
|
1347
1435
|
* it, including `false`. Filling the posture does not overwrite an explicit
|
|
1348
1436
|
* merge decision.
|
|
1437
|
+
* * **`merge_execution_mode` already present** (BAPI-1133, A2) → left exactly
|
|
1438
|
+
* as the file set it, valid or not. An explicitly present but invalid mode is
|
|
1439
|
+
* NOT silently repaired here; it is forwarded unchanged so the server's
|
|
1440
|
+
* secret-safe typed validation rejects it. Absent, it is filled from the
|
|
1441
|
+
* caller's already-resolved {@link TypedMergeExecutionMode} in the same
|
|
1442
|
+
* fill-if-absent branch that stamps `posture` and
|
|
1443
|
+
* {@link SETUP_EPIC_AUTO_MERGE_POLICY_KEY} — the server refuses an
|
|
1444
|
+
* unattended auto-merge policy that omits it.
|
|
1349
1445
|
*
|
|
1350
1446
|
* Pure: it returns a new object and never mutates the input. It is applied on the
|
|
1351
1447
|
* CREATE path only. A reused live run reconciles the file against its STORED
|
|
@@ -1365,6 +1461,10 @@ export function applySetupEpicUnattendedDefault(policy, args) {
|
|
|
1365
1461
|
if (!(SETUP_EPIC_AUTO_MERGE_POLICY_KEY in composed)) {
|
|
1366
1462
|
composed[SETUP_EPIC_AUTO_MERGE_POLICY_KEY] = true;
|
|
1367
1463
|
}
|
|
1464
|
+
if (!(SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY in composed) &&
|
|
1465
|
+
args.mergeExecutionMode !== undefined) {
|
|
1466
|
+
composed[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] = args.mergeExecutionMode;
|
|
1467
|
+
}
|
|
1368
1468
|
return composed;
|
|
1369
1469
|
}
|
|
1370
1470
|
/**
|
|
@@ -1492,7 +1592,7 @@ export const SETUP_EPIC_SCOPE_RECOVERY_COMMAND = "re-run this same setup-epic co
|
|
|
1492
1592
|
* The canonical one-line recovery for a scope collision (BAPI-1009).
|
|
1493
1593
|
*
|
|
1494
1594
|
* Repeated VERBATIM as the epic-run gate snapshot's shadow-hold recovery hint
|
|
1495
|
-
* (``api/library/
|
|
1595
|
+
* (``api/library/epic_implementer/gate_snapshot_service.py``), so an operator who
|
|
1496
1596
|
* reads the diagnosis on the status surface and an operator who reads it from
|
|
1497
1597
|
* the CLI are told to do the same thing in the same words.
|
|
1498
1598
|
*/
|
|
@@ -1873,6 +1973,27 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1873
1973
|
// `base_branch` is honored exactly as `--feature-branch` would be. A malformed
|
|
1874
1974
|
// or unknown policy field is left for the server's strict validation.
|
|
1875
1975
|
const effectiveFeatureBranch = resolveEffectiveFeatureBranch(featureBranch, requestedPolicy);
|
|
1976
|
+
// --- Typed merge-execution-mode declaration (BAPI-1133, A2) ---------------
|
|
1977
|
+
// Resolved ONCE, here, before either composer runs, so the composer functions
|
|
1978
|
+
// stay pure and synchronous. Gated on the exact predicate both composers'
|
|
1979
|
+
// OWN fill-if-absent branches use — `!attended`, a feature branch selected,
|
|
1980
|
+
// and (for the file path) no `posture` already declared — so this never
|
|
1981
|
+
// performs the new configuration read for `--attended` or `--into-base` runs,
|
|
1982
|
+
// and never for a file that already answered the posture question itself.
|
|
1983
|
+
const unattendedDefaultEligible = !opts.attended &&
|
|
1984
|
+
effectiveFeatureBranch !== undefined &&
|
|
1985
|
+
(requestedPolicy === undefined || !("posture" in requestedPolicy));
|
|
1986
|
+
let declaredMergeExecutionMode;
|
|
1987
|
+
if (unattendedDefaultEligible) {
|
|
1988
|
+
const mergeExecutionModeResolverDeps = {
|
|
1989
|
+
repoName: access.repoName,
|
|
1990
|
+
buildGetUrl: (path, params) => buildConductorJiraUrl(access.baseUrl, path, params),
|
|
1991
|
+
getHeaders: async () => conductorGetHeaders(access),
|
|
1992
|
+
fetchImpl: deps.fetch,
|
|
1993
|
+
};
|
|
1994
|
+
const effectiveLegacyMode = await resolveMergeExecutionMode(mergeExecutionModeResolverDeps);
|
|
1995
|
+
declaredMergeExecutionMode = mapLegacyMergeExecutionModeToPolicyMode(effectiveLegacyMode);
|
|
1996
|
+
}
|
|
1876
1997
|
// --- The policy the CREATE path will send (BAPI-1102) ---------------------
|
|
1877
1998
|
// Composed HERE, as soon as both of its inputs exist, so the one object that
|
|
1878
1999
|
// gets reported, gated, and sent is the same object throughout. It is consumed
|
|
@@ -1883,12 +2004,30 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1883
2004
|
? applySetupEpicUnattendedDefault(requestedPolicy, {
|
|
1884
2005
|
featureBranch: effectiveFeatureBranch,
|
|
1885
2006
|
attended: opts.attended,
|
|
2007
|
+
mergeExecutionMode: declaredMergeExecutionMode,
|
|
1886
2008
|
})
|
|
1887
2009
|
: composeDefaultSetupEpicPolicy({
|
|
1888
2010
|
featureBranch,
|
|
1889
2011
|
attended: opts.attended,
|
|
1890
2012
|
reviewPolicy: opts.reviewPolicy,
|
|
2013
|
+
mergeExecutionMode: declaredMergeExecutionMode,
|
|
1891
2014
|
});
|
|
2015
|
+
// BAPI-1133 (A2). Reported only when THIS command stamped the key — i.e. it
|
|
2016
|
+
// is present in the composed policy and was absent from whatever the
|
|
2017
|
+
// operator supplied — as low-emphasis policy metadata, mirroring the
|
|
2018
|
+
// existing posture/auto-merge reporting rather than adding a new prompt,
|
|
2019
|
+
// wizard, or runtime status. An explicitly declared mode (CLI-composed
|
|
2020
|
+
// policy already carrying the key) is never reported here as if it were
|
|
2021
|
+
// this command's decision.
|
|
2022
|
+
const mergeExecutionModeStamped = declaredMergeExecutionMode !== undefined &&
|
|
2023
|
+
composedForCreate[SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY] === declaredMergeExecutionMode &&
|
|
2024
|
+
(requestedPolicy === undefined ||
|
|
2025
|
+
!(SETUP_EPIC_MERGE_EXECUTION_MODE_POLICY_KEY in requestedPolicy));
|
|
2026
|
+
if (mergeExecutionModeStamped) {
|
|
2027
|
+
say(`Policy: merge_execution_mode: ${declaredMergeExecutionMode} (declared automatically ` +
|
|
2028
|
+
`for the unattended default; policy metadata only — no dispatch, actuation, eligibility, ` +
|
|
2029
|
+
`containment, or escalation behavior until a later A2 child)`);
|
|
2030
|
+
}
|
|
1892
2031
|
if (requestedPolicy !== undefined) {
|
|
1893
2032
|
// The posture is stated for the FILE path too, and read off the composed
|
|
1894
2033
|
// object rather than re-derived. The `--policy-file` invocation is the one
|
|
@@ -2256,7 +2395,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2256
2395
|
: ` - feature branch: ${effectiveFeatureBranch} (validated server-side at approval; no request made in dry-run)`);
|
|
2257
2396
|
if (needsCut && cutCommitSha !== null && scopeBaseBranch !== null) {
|
|
2258
2397
|
say(` - would open a draft pull request ${effectiveFeatureBranch} → ${scopeBaseBranch} ` +
|
|
2259
|
-
"for
|
|
2398
|
+
"for epic-implementer-ci / gate (BAPI-951; nothing opened in dry-run)");
|
|
2260
2399
|
}
|
|
2261
2400
|
}
|
|
2262
2401
|
if (opts.reviewPolicy !== undefined) {
|
|
@@ -2430,6 +2569,21 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2430
2569
|
deps.errorLog(`setup-epic REFUSED (${bringUpRefusal.reason}): ${bringUpRefusal.message}`);
|
|
2431
2570
|
return 1;
|
|
2432
2571
|
}
|
|
2572
|
+
// BAPI-1135 — merge-execution eligibility, AFTER the universal bring-up
|
|
2573
|
+
// blockers (an unreadable readiness report and a behind database make this
|
|
2574
|
+
// question moot) and BEFORE the unattended prerequisites and every
|
|
2575
|
+
// approval check below. That position is what keeps readiness, this gate,
|
|
2576
|
+
// and the approval refusal naming the SAME first blocker.
|
|
2577
|
+
//
|
|
2578
|
+
// Outside the `requestedPolicyIsUnattended` branch on purpose: an
|
|
2579
|
+
// `--attended` run that declares `github_actions` mode is gated too. The
|
|
2580
|
+
// evaluator itself returns null for every policy that does not declare the
|
|
2581
|
+
// mode, so no other run's behavior changes.
|
|
2582
|
+
const mergeExecutionRefusal = evaluateSetupEpicMergeExecutionPrerequisites(readiness, composedForCreate);
|
|
2583
|
+
if (mergeExecutionRefusal !== null) {
|
|
2584
|
+
deps.errorLog(`setup-epic REFUSED (${mergeExecutionRefusal.reason}): ${mergeExecutionRefusal.message}`);
|
|
2585
|
+
return 1;
|
|
2586
|
+
}
|
|
2433
2587
|
if (requestedPolicyIsUnattended(composedForCreate, opts.attended)) {
|
|
2434
2588
|
// The COMPOSED policy, not the flags: a --policy-file declaring
|
|
2435
2589
|
// notify.webhook_url satisfies the escalation-channel prerequisite, and
|
|
@@ -62,20 +62,20 @@ export function mintStartTicketsWorkerId(ticketKey, agentName, fragment = random
|
|
|
62
62
|
*/
|
|
63
63
|
export function buildEpicIdentityEnv(epic) {
|
|
64
64
|
const env = {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
BAPI_EPIC_IMPLEMENTER_EPIC_KEY: epic.epic_key,
|
|
66
|
+
BAPI_EPIC_IMPLEMENTER_EPIC_RUN_ID: epic.epic_run_id,
|
|
67
|
+
BAPI_EPIC_IMPLEMENTER_PLAN_VERSION: String(epic.plan_version),
|
|
68
68
|
};
|
|
69
69
|
const declared = normalizeDeclaredTouchedFiles(epic.declared_touched_files);
|
|
70
70
|
if (declared.length > 0) {
|
|
71
|
-
env.
|
|
71
|
+
env.BAPI_EPIC_IMPLEMENTER_DECLARED_TOUCHED_FILES_JSON = JSON.stringify(declared);
|
|
72
72
|
}
|
|
73
73
|
return env;
|
|
74
74
|
}
|
|
75
75
|
// ---------------------------------------------------------------------------
|
|
76
76
|
// Conductor context
|
|
77
77
|
// ---------------------------------------------------------------------------
|
|
78
|
-
/** Default gate name when no `
|
|
78
|
+
/** Default gate name when no `BAPI_EPIC_IMPLEMENTER_GATE_NAME` override is present. */
|
|
79
79
|
export const DEFAULT_CONDUCTOR_GATE_NAME = "implement-ticket";
|
|
80
80
|
/**
|
|
81
81
|
* Resolve a packaged build artifact path relative to this compiled module.
|
|
@@ -110,11 +110,11 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
|
|
|
110
110
|
// Repo resolution must never abort conductor setup; fall back to null.
|
|
111
111
|
repoName = null;
|
|
112
112
|
}
|
|
113
|
-
const gateName = nonEmpty(deps.env.
|
|
114
|
-
? deps.env.
|
|
113
|
+
const gateName = nonEmpty(deps.env.BAPI_EPIC_IMPLEMENTER_GATE_NAME)
|
|
114
|
+
? deps.env.BAPI_EPIC_IMPLEMENTER_GATE_NAME.trim()
|
|
115
115
|
: DEFAULT_CONDUCTOR_GATE_NAME;
|
|
116
|
-
const supervisorMode = nonEmpty(deps.env.
|
|
117
|
-
? deps.env.
|
|
116
|
+
const supervisorMode = nonEmpty(deps.env.BAPI_EPIC_IMPLEMENTER_SUPERVISOR_MODE)
|
|
117
|
+
? deps.env.BAPI_EPIC_IMPLEMENTER_SUPERVISOR_MODE.trim()
|
|
118
118
|
: options.autoApprove
|
|
119
119
|
? "auto"
|
|
120
120
|
: "interactive";
|
|
@@ -142,12 +142,12 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
|
|
|
142
142
|
// ---------------------------------------------------------------------------
|
|
143
143
|
/** Conductor store-tuning env keys forwarded only when already set upstream. */
|
|
144
144
|
const CONDUCTOR_TUNING_ENV_KEYS = [
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
145
|
+
"BAPI_EPIC_IMPLEMENTER_BUSY_TIMEOUT_MS",
|
|
146
|
+
"BAPI_EPIC_IMPLEMENTER_RETENTION_DAYS",
|
|
147
|
+
"BAPI_EPIC_IMPLEMENTER_RETENTION_MAX_ROWS",
|
|
148
148
|
// BAPI-397: per-type relay cooldown — forwarded only when explicitly set so
|
|
149
149
|
// the supervisor's send config is honoured by the conductor store it writes to.
|
|
150
|
-
"
|
|
150
|
+
"BAPI_EPIC_IMPLEMENTER_MESSAGE_COOLDOWN_MS",
|
|
151
151
|
];
|
|
152
152
|
/** Truthy predicate for opt-in conductor boolean flags (`1` / `true`). */
|
|
153
153
|
export function isConductorFlagEnabled(value) {
|
|
@@ -164,30 +164,30 @@ export function isConductorFlagEnabled(value) {
|
|
|
164
164
|
* upstream.
|
|
165
165
|
*/
|
|
166
166
|
export function buildConductorWorkerEnv(context, worker, parentEnv) {
|
|
167
|
-
// Merge "
|
|
167
|
+
// Merge "epic_implementer" into the parent project's profile rather than overwriting it.
|
|
168
168
|
// This preserves groups like "sfcc" that the project already has, while adding
|
|
169
169
|
// the conductor-specific tools. Only validated group tokens from resolveProfiles
|
|
170
170
|
// are threaded — arbitrary parentEnv keys never leak through.
|
|
171
171
|
const parentActiveGroups = new Set(resolveProfiles(parentEnv.BRIDGE_MCP_PROFILE));
|
|
172
|
-
parentActiveGroups.add("
|
|
172
|
+
parentActiveGroups.add("epic_implementer");
|
|
173
173
|
const mergedProfile = Array.from(parentActiveGroups).join(",");
|
|
174
174
|
const env = {
|
|
175
|
-
|
|
175
|
+
BAPI_EPIC_IMPLEMENTER_ENABLED: "1",
|
|
176
176
|
BRIDGE_MCP_PROFILE: mergedProfile,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
177
|
+
BAPI_EPIC_IMPLEMENTER_RUN_ID: context.runId,
|
|
178
|
+
BAPI_EPIC_IMPLEMENTER_WORKER_ID: worker.workerId,
|
|
179
|
+
BAPI_EPIC_IMPLEMENTER_TICKET_KEY: worker.ticketKey,
|
|
180
|
+
BAPI_EPIC_IMPLEMENTER_WORKTREE_PATH: worker.worktreePath,
|
|
181
|
+
BAPI_EPIC_IMPLEMENTER_GATE_NAME: context.gateName,
|
|
182
|
+
BAPI_EPIC_IMPLEMENTER_SUPERVISOR_MODE: context.supervisorMode,
|
|
183
|
+
BAPI_EPIC_IMPLEMENTER_CLI_FILE: context.cliFile,
|
|
184
184
|
// BAPI-527: the CLI file + Node runtime are a PAIR. The worker runs
|
|
185
185
|
// conductor-bin.js under this captured conductor Node for every worker-side
|
|
186
186
|
// ledger op, so the worker Node never loads better-sqlite3. Non-secret.
|
|
187
|
-
|
|
187
|
+
EPIC_IMPLEMENTER_NODE_PATH: context.conductorNodePath,
|
|
188
188
|
};
|
|
189
189
|
if (context.repoName) {
|
|
190
|
-
env.
|
|
190
|
+
env.BAPI_EPIC_IMPLEMENTER_REPO_NAME = context.repoName;
|
|
191
191
|
}
|
|
192
192
|
if (context.epic) {
|
|
193
193
|
const epicEnv = buildEpicIdentityEnv(context.epic);
|
|
@@ -195,8 +195,8 @@ export function buildConductorWorkerEnv(context, worker, parentEnv) {
|
|
|
195
195
|
env[k] = v;
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
|
-
if (isConductorFlagEnabled(parentEnv.
|
|
199
|
-
env.
|
|
198
|
+
if (isConductorFlagEnabled(parentEnv.BAPI_EPIC_IMPLEMENTER_ENABLE_PRE_TOOL_USE)) {
|
|
199
|
+
env.BAPI_EPIC_IMPLEMENTER_ENABLE_PRE_TOOL_USE = "1";
|
|
200
200
|
}
|
|
201
201
|
for (const key of CONDUCTOR_TUNING_ENV_KEYS) {
|
|
202
202
|
if (nonEmpty(parentEnv[key])) {
|
|
@@ -234,13 +234,13 @@ function shellQuotePath(value) {
|
|
|
234
234
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
235
235
|
}
|
|
236
236
|
/**
|
|
237
|
-
* Resolve the hook command string. Prefers `
|
|
237
|
+
* Resolve the hook command string. Prefers `BAPI_EPIC_IMPLEMENTER_HOOK_COMMAND`
|
|
238
238
|
* verbatim; otherwise runs the packaged hook bin via the current Node
|
|
239
239
|
* executable.
|
|
240
240
|
*/
|
|
241
241
|
export function resolveConductorHookCommand(env, hookBinPath, execPath = process.execPath) {
|
|
242
|
-
if (nonEmpty(env.
|
|
243
|
-
return env.
|
|
242
|
+
if (nonEmpty(env.BAPI_EPIC_IMPLEMENTER_HOOK_COMMAND)) {
|
|
243
|
+
return env.BAPI_EPIC_IMPLEMENTER_HOOK_COMMAND;
|
|
244
244
|
}
|
|
245
245
|
return `${shellQuotePath(execPath)} ${shellQuotePath(hookBinPath)}`;
|
|
246
246
|
}
|
|
@@ -279,7 +279,7 @@ export async function provisionConductorHookForWorktree(worktreePath, command, o
|
|
|
279
279
|
*/
|
|
280
280
|
export async function provisionConductorHooksForRows(rows, context, deps) {
|
|
281
281
|
const isClaude = context.agentName === "claude";
|
|
282
|
-
const enablePreToolUse = isConductorFlagEnabled(deps.env.
|
|
282
|
+
const enablePreToolUse = isConductorFlagEnabled(deps.env.BAPI_EPIC_IMPLEMENTER_ENABLE_PRE_TOOL_USE);
|
|
283
283
|
const command = resolveConductorHookCommand(deps.env, context.hookBinPath, deps.execPath);
|
|
284
284
|
const fragment = deps.workerFragment;
|
|
285
285
|
const out = [];
|
|
@@ -451,7 +451,7 @@ export function buildSupervisorTabCommand(context, platform, nodeExecPath = proc
|
|
|
451
451
|
/**
|
|
452
452
|
* Decide whether the supervisor peer tab should launch. Enabled by default for a
|
|
453
453
|
* normal packaged conductor context; disabled only when the resolved supervisor
|
|
454
|
-
* mode is `off` (i.e. `
|
|
454
|
+
* mode is `off` (i.e. `BAPI_EPIC_IMPLEMENTER_SUPERVISOR_MODE=off`).
|
|
455
455
|
*/
|
|
456
456
|
export function isSupervisorLaunchEnabled(context) {
|
|
457
457
|
return context.supervisorMode.trim().toLowerCase() !== "off";
|
|
@@ -52,7 +52,7 @@ export const START_TICKETS_DOCTOR_COMMAND = `npx -y ${MCP_PACKAGE_NAME} doctor`;
|
|
|
52
52
|
/**
|
|
53
53
|
* BAPI-527 live-source checkout guard (conductor durable execution).
|
|
54
54
|
*
|
|
55
|
-
* `
|
|
55
|
+
* `BAPI_EPIC_IMPLEMENTER_LIVE_SOURCE_PATH` — OPTIONAL operator-set path to the checkout
|
|
56
56
|
* an operator's live dev server (e.g. `uvicorn main:app --reload`) is running
|
|
57
57
|
* from. When set, unattended conductor/epic/non-mutating start-tickets dispatch
|
|
58
58
|
* refuses to run if the conductor base repo it would operate on resolves to the
|
|
@@ -62,14 +62,14 @@ export const START_TICKETS_DOCTOR_COMMAND = `npx -y ${MCP_PACKAGE_NAME} doctor`;
|
|
|
62
62
|
* discovery, so this local env contract is introduced here. Read only from the
|
|
63
63
|
* injected `deps.env` (never global `process.env`) so it stays mockable.
|
|
64
64
|
*
|
|
65
|
-
* `
|
|
65
|
+
* `BAPI_EPIC_IMPLEMENTER_ALLOW_LIVE_SOURCE_DISPATCH` — explicit escape hatch. When set
|
|
66
66
|
* to a truthy value (`1`/`true`/`yes`/`on`, case-insensitive) a detected
|
|
67
67
|
* collision is DOWNGRADED from fatal to a loud warning rather than silently
|
|
68
68
|
* ignored, so an operator who genuinely wants same-checkout dispatch can opt in
|
|
69
69
|
* with eyes open.
|
|
70
70
|
*/
|
|
71
|
-
export const CONDUCTOR_LIVE_SOURCE_PATH_ENV = "
|
|
72
|
-
export const CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV = "
|
|
71
|
+
export const CONDUCTOR_LIVE_SOURCE_PATH_ENV = "BAPI_EPIC_IMPLEMENTER_LIVE_SOURCE_PATH";
|
|
72
|
+
export const CONDUCTOR_ALLOW_LIVE_SOURCE_DISPATCH_ENV = "BAPI_EPIC_IMPLEMENTER_ALLOW_LIVE_SOURCE_DISPATCH";
|
|
73
73
|
/** Stable descriptor id for the live-source checkout guard (used in messages + doctor). */
|
|
74
74
|
export const LIVE_SOURCE_GUARD_ID = "conductor-live-source";
|
|
75
75
|
/** True only for the three first-class supported platforms. */
|
|
@@ -566,11 +566,6 @@ const CLAUDE_MCP_SHADOWING_INSTALL_HINTS = {
|
|
|
566
566
|
* - a CONFIRMED foreign registration → `found: true` + advisory detail
|
|
567
567
|
* - unreadable / malformed config, or an unresolved main
|
|
568
568
|
* worktree path → `found: true` + detail
|
|
569
|
-
*
|
|
570
|
-
* Reporting stays deliberately independent of the retired
|
|
571
|
-
* `BAPI_CONDUCTOR_ALLOW_CLAUDE_MCP_SHADOWING` override: whether that variable is
|
|
572
|
-
* exported has never had any bearing on whether the collision exists, and now it
|
|
573
|
-
* has no bearing on anything at all.
|
|
574
569
|
*/
|
|
575
570
|
export function claudeMcpShadowingDescriptor() {
|
|
576
571
|
return {
|