@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.
Files changed (127) hide show
  1. package/README.md +231 -113
  2. package/build/agent-guidance.generated.js +1 -1
  3. package/build/agent-notes.js +7 -1
  4. package/build/agent-registry.js +15 -0
  5. package/build/agent-utils.js +16 -8
  6. package/build/claude-user-config-doctor.js +1 -35
  7. package/build/codex-bridge-profile.js +1 -1
  8. package/build/commands.generated.js +7 -8
  9. package/build/conductor/bridge-api-client.js +202 -2
  10. package/build/conductor/bring-up-facts.js +1 -1
  11. package/build/conductor/claude-hook.js +16 -16
  12. package/build/conductor/doctor.js +279 -72
  13. package/build/conductor/epic-implementer-cli.js +38 -5
  14. package/build/conductor/epic-reconcile.js +1 -1
  15. package/build/conductor/epic-runtime.js +21 -12
  16. package/build/conductor/errors.js +3 -3
  17. package/build/conductor/file-scope-guard.js +4 -4
  18. package/build/conductor/git-ci-types.js +1 -1
  19. package/build/conductor/git-inspection.js +2 -2
  20. package/build/conductor/install-doctor.js +28 -6
  21. package/build/conductor/local-merge.js +319 -26
  22. package/build/conductor/merge-execution-contract.js +61 -0
  23. package/build/conductor/merge-execution-eligibility.js +129 -0
  24. package/build/conductor/merge-ledger.js +2 -2
  25. package/build/conductor/pr-ci-producer.js +6 -6
  26. package/build/conductor/pr-review-producer.js +2 -2
  27. package/build/conductor/producer-ledger.js +2 -0
  28. package/build/conductor/readiness-sections.js +166 -9
  29. package/build/conductor/readiness.js +97 -16
  30. package/build/conductor/recovery-cli.js +243 -12
  31. package/build/conductor/recovery-operations.js +64 -6
  32. package/build/conductor/repair-contract.js +38 -4
  33. package/build/conductor/spec-review-producer.js +6 -6
  34. package/build/conductor/store.js +10 -10
  35. package/build/conductor/supervisor-config.js +14 -14
  36. package/build/conductor/supervisor-ledger.js +2 -2
  37. package/build/conductor/supervisor-message-relay.js +2 -2
  38. package/build/conductor/worker-ledger-cli.js +19 -19
  39. package/build/conductor-bundle-artifacts.js +2 -2
  40. package/build/connect-bitbucket-api.js +6 -1
  41. package/build/connect-bitbucket.js +398 -88
  42. package/build/credential-store.js +16 -0
  43. package/build/docs.generated.js +2 -2
  44. package/build/doctor.js +199 -303
  45. package/build/drive-epic.js +88 -71
  46. package/build/epic-implementer-bin.js +22 -17
  47. package/build/epic-implementer-claude-hook-bin.js +4 -2
  48. package/build/epic-implementer-env.js +184 -0
  49. package/build/epic-integration-pr.js +2 -2
  50. package/build/executor/ci-rerun-job.js +269 -0
  51. package/build/executor/cli.js +31 -0
  52. package/build/executor/credentials.js +11 -0
  53. package/build/executor/env.js +79 -8
  54. package/build/executor/heartbeat.js +1 -1
  55. package/build/executor/index.js +21 -4
  56. package/build/executor/job-errors.js +3 -3
  57. package/build/executor/job-runner.js +127 -1
  58. package/build/executor/job-types.js +12 -2
  59. package/build/executor/merge-job.js +46 -5
  60. package/build/executor/observation.js +1 -1
  61. package/build/executor/preflight.js +1 -1
  62. package/build/executor/prompt-spec.js +1 -1
  63. package/build/executor/reconcile-overlap-job.js +10 -130
  64. package/build/executor/reconcile-overlap-types.js +1 -1
  65. package/build/executor/runner.js +1 -1
  66. package/build/executor/service-lifecycle.js +136 -0
  67. package/build/executor/service-unit.js +427 -9
  68. package/build/executor/types.js +5 -0
  69. package/build/index.js +320 -342
  70. package/build/init.js +20 -28
  71. package/build/install-bridge-conductor.js +208 -15
  72. package/build/install-bridge.js +555 -37
  73. package/build/mcp-host-entry-adapter.js +6 -0
  74. package/build/mcp-host-targets.js +2 -0
  75. package/build/mcp-install-state.js +42 -4
  76. package/build/mcp-invoke.js +5 -5
  77. package/build/mcp-profile.js +2 -2
  78. package/build/mcp-provisioning.js +2 -2
  79. package/build/mcp-server-invocation.js +3 -3
  80. package/build/merge-pull-request.js +118 -10
  81. package/build/merge-workflow-codeowners.js +224 -0
  82. package/build/merge-workflow-config.js +133 -0
  83. package/build/merge-workflow-drift.js +66 -0
  84. package/build/merge-workflow-install.js +638 -0
  85. package/build/merge-workflow.js +572 -0
  86. package/build/model-routing-host.js +25 -0
  87. package/build/model-routing-preferences.js +99 -0
  88. package/build/pipelines.generated.js +10 -10
  89. package/build/{plan-epic-conductor-eligibility.js → plan-epic-implementer-eligibility.js} +18 -16
  90. package/build/plan-execution-ledger.js +8 -0
  91. package/build/plane/cli.js +24 -2
  92. package/build/plane/member-roster.js +5 -5
  93. package/build/plane/preflight.js +68 -0
  94. package/build/plane/supervisor.js +1 -1
  95. package/build/plane/types.js +5 -5
  96. package/build/readiness-check.js +1 -1
  97. package/build/readme.generated.js +1 -1
  98. package/build/review-tickets.js +4 -4
  99. package/build/run-unit-tests-launcher.js +5 -0
  100. package/build/setup-epic.js +171 -17
  101. package/build/start-tickets-conductor.js +32 -32
  102. package/build/start-tickets-prereqs.js +4 -9
  103. package/build/start-tickets.js +475 -91
  104. package/build/version.generated.js +3 -3
  105. package/docs/CONDUCTOR.md +69 -18
  106. package/docs/install/mcp-tool-integrations.md +2 -1
  107. package/package.json +4 -4
  108. package/pipelines/implement-ticket.json +1 -1
  109. package/pipelines/plan-epic.json +2 -2
  110. package/build/conduct-epic/bridge-client.js +0 -773
  111. package/build/conduct-epic/checkpoint-store.js +0 -542
  112. package/build/conduct-epic/cli.js +0 -3145
  113. package/build/conduct-epic/cut-protocol.js +0 -392
  114. package/build/conduct-epic/lock.js +0 -302
  115. package/build/conduct-epic/pr-state.js +0 -286
  116. package/build/conduct-epic/spawn.js +0 -113
  117. package/build/conductor/cli.js +0 -1278
  118. package/build/conductor-bundle-cli.js +0 -256
  119. package/build/direct-ticket-tools.js +0 -70
  120. package/build/executor/reconcile-overlap-governance.js +0 -129
  121. package/build/implement-epic/checkpoint-store.js +0 -542
  122. package/build/implement-epic/cli.js +0 -3158
  123. package/build/implement-epic/lock.js +0 -302
  124. package/build/implement-epic/pr-state.js +0 -286
  125. package/build/implement-epic/spawn.js +0 -113
  126. /package/build/{implement-epic → epic-shared}/bridge-client.js +0 -0
  127. /package/build/{implement-epic → epic-shared}/cut-protocol.js +0 -0
@@ -364,8 +364,15 @@ export function safeDiagnosticMessage(err, fallback) {
364
364
  }
365
365
  return fallback;
366
366
  }
367
- /** GET auth headers. The API key travels ONLY in a header, never in the URL. */
368
- function conductorGetHeaders(access) {
367
+ /**
368
+ * GET auth headers. The API key travels ONLY in a header, never in the URL.
369
+ *
370
+ * Exported (BAPI-1133) so a caller outside this module — `setup-epic.ts`'s
371
+ * {@link ConductorBridgeApiAccess}-based adapter for
372
+ * `resolveMergeExecutionMode`'s narrow dependency shape — can build the same
373
+ * headers without a second copy of this one-line mapping.
374
+ */
375
+ export function conductorGetHeaders(access) {
369
376
  return { "X-API-Key": access.apiKey };
370
377
  }
371
378
  /**
@@ -1331,6 +1338,155 @@ export async function completeMergeChild(access, request, fetchImpl = globalThis
1331
1338
  }, fetchImpl);
1332
1339
  return parseMergeChildRouteResponse(parsed);
1333
1340
  }
1341
+ // ---------------------------------------------------------------------------
1342
+ // BAPI-1176 — scope-catch-up, patch-policy, resume-run (CLI-only; no MCP tool)
1343
+ // ---------------------------------------------------------------------------
1344
+ // The same request path as the BAPI-1154 clients above, with STRICT parsers: a
1345
+ // response carrying any field outside its documented set is refused, so a server
1346
+ // that ever returned a generation, a control record, or a policy value would fail
1347
+ // closed here rather than reach operator output. No parser error echoes the body.
1348
+ /** The fields every repair response carries. */
1349
+ const REPAIR_BASE_KEYS = [
1350
+ "epic_run_id",
1351
+ "before",
1352
+ "after",
1353
+ "committed",
1354
+ "event_recorded",
1355
+ ];
1356
+ /** A bounded, closed-vocabulary token (a catch-up outcome such as `parse_scheduled`). */
1357
+ const REPAIR_TOKEN_RE = /^[a-z0-9_.:-]{1,120}$/;
1358
+ /** A `patch-policy` digest display token: `policy:` and twelve lowercase hex digits. */
1359
+ const POLICY_DIGEST_TOKEN_RE = /^policy:[0-9a-f]{12}$/;
1360
+ /** A dotted policy-path NAME. Never a value. */
1361
+ const POLICY_PATH_RE = /^[A-Za-z0-9_-]{1,100}(?:\.[A-Za-z0-9_-]{1,100}){0,15}$/;
1362
+ /** `resume-run`'s closed run-control state vocabulary. */
1363
+ const RESUME_RUN_STATES = ["paused", "quarantined", "none"];
1364
+ function requireOnlyRepairKeys(p, extraKeys) {
1365
+ for (const key of Object.keys(p)) {
1366
+ if (!REPAIR_BASE_KEYS.includes(key) && !extraKeys.includes(key)) {
1367
+ throw new ConductorBridgeApiError("server");
1368
+ }
1369
+ }
1370
+ }
1371
+ function parseStrictNullableString(value) {
1372
+ if (value === null || value === undefined)
1373
+ return null;
1374
+ if (typeof value !== "string") {
1375
+ throw new ConductorBridgeApiError("server");
1376
+ }
1377
+ return value;
1378
+ }
1379
+ function parseScopeCatchUpResult(parsed) {
1380
+ const p = parseRepairBase(parsed);
1381
+ requireOnlyRepairKeys(p, ["scope_id", "outcome", "parse_scheduled"]);
1382
+ const scopeId = p["scope_id"];
1383
+ const outcome = p["outcome"];
1384
+ const parseScheduled = p["parse_scheduled"];
1385
+ if (typeof scopeId !== "string" ||
1386
+ typeof outcome !== "string" ||
1387
+ !REPAIR_TOKEN_RE.test(outcome) ||
1388
+ typeof parseScheduled !== "boolean") {
1389
+ throw new ConductorBridgeApiError("server");
1390
+ }
1391
+ return {
1392
+ epic_run_id: p["epic_run_id"],
1393
+ scope_id: scopeId,
1394
+ before: parseStrictNullableString(p["before"]),
1395
+ after: parseStrictNullableString(p["after"]),
1396
+ committed: p["committed"],
1397
+ event_recorded: p["event_recorded"],
1398
+ outcome,
1399
+ parse_scheduled: parseScheduled,
1400
+ };
1401
+ }
1402
+ function parsePatchPolicyResult(parsed) {
1403
+ const p = parseRepairBase(parsed);
1404
+ requireOnlyRepairKeys(p, ["policy_paths"]);
1405
+ const before = p["before"];
1406
+ const after = p["after"];
1407
+ const paths = p["policy_paths"];
1408
+ if (typeof before !== "string" ||
1409
+ !POLICY_DIGEST_TOKEN_RE.test(before) ||
1410
+ typeof after !== "string" ||
1411
+ !POLICY_DIGEST_TOKEN_RE.test(after) ||
1412
+ !Array.isArray(paths) ||
1413
+ !paths.every((path) => typeof path === "string" && POLICY_PATH_RE.test(path))) {
1414
+ throw new ConductorBridgeApiError("server");
1415
+ }
1416
+ return {
1417
+ epic_run_id: p["epic_run_id"],
1418
+ before,
1419
+ after,
1420
+ committed: p["committed"],
1421
+ event_recorded: p["event_recorded"],
1422
+ policy_paths: [...paths],
1423
+ };
1424
+ }
1425
+ function parseResumeRunResult(parsed) {
1426
+ const p = parseRepairBase(parsed);
1427
+ requireOnlyRepairKeys(p, []);
1428
+ const before = p["before"];
1429
+ const after = p["after"];
1430
+ if (typeof before !== "string" ||
1431
+ !RESUME_RUN_STATES.includes(before) ||
1432
+ typeof after !== "string" ||
1433
+ !RESUME_RUN_STATES.includes(after)) {
1434
+ throw new ConductorBridgeApiError("server");
1435
+ }
1436
+ return {
1437
+ epic_run_id: p["epic_run_id"],
1438
+ before,
1439
+ after,
1440
+ committed: p["committed"],
1441
+ event_recorded: p["event_recorded"],
1442
+ };
1443
+ }
1444
+ /**
1445
+ * POST `/jira/epic-runs/runs/{epic_run_id}/scope-catch-up` — replay merges the
1446
+ * run's OWN index scope did not observe, through the unchanged guarded advance
1447
+ * gate. Body EXACTLY `{repo_name, scope_id}`: no force flag and no caller-supplied
1448
+ * commit. The server refuses a scope not bound to the run (`404
1449
+ * TICKET_NOT_IN_RUN`) and a blocked advance (`409 SCOPE_CATCH_UP_REFUSED`).
1450
+ */
1451
+ export async function scopeCatchUp(access, request, fetchImpl = globalThis.fetch) {
1452
+ requireNonEmptyString(request.epicRunId);
1453
+ requireNonEmptyString(request.scopeId);
1454
+ const parsed = await postRepairRequest(access, `${epicRunApiPath(request.epicRunId)}/scope-catch-up`, { scope_id: request.scopeId }, fetchImpl);
1455
+ return parseScopeCatchUpResult(parsed);
1456
+ }
1457
+ /**
1458
+ * POST `/jira/epic-runs/runs/{epic_run_id}/patch-policy` — body EXACTLY
1459
+ * `{repo_name, merge_patch}`. The SERVER reads the stored policy, applies the RFC
1460
+ * 7396 merge patch, validates the result, and writes it under a snapshot guard.
1461
+ * This is deliberately never `replaceEpicRunPolicy`: the client never reads,
1462
+ * holds, or sends a whole policy, so a server-retained value it cannot see (the
1463
+ * webhook URL) can never be dropped by a read-modify-write.
1464
+ */
1465
+ export async function patchPolicy(access, request, fetchImpl = globalThis.fetch) {
1466
+ requireNonEmptyString(request.epicRunId);
1467
+ const patch = request.mergePatch;
1468
+ if (!patch ||
1469
+ typeof patch !== "object" ||
1470
+ Array.isArray(patch) ||
1471
+ Object.keys(patch).length === 0) {
1472
+ throw new ConductorBridgeApiError("invalid-input");
1473
+ }
1474
+ const parsed = await postRepairRequest(access, `${epicRunApiPath(request.epicRunId)}/patch-policy`, { merge_patch: patch }, fetchImpl);
1475
+ return parsePatchPolicyResult(parsed);
1476
+ }
1477
+ /**
1478
+ * POST `/jira/epic-runs/runs/{epic_run_id}/resume-run` — body EXACTLY
1479
+ * `{repo_name, clear_quarantine}`. No generation and no reason: the server
1480
+ * re-reads the control record, re-evaluates activation, and owns the CAS retry.
1481
+ */
1482
+ export async function resumeRun(access, request, fetchImpl = globalThis.fetch) {
1483
+ requireNonEmptyString(request.epicRunId);
1484
+ if (typeof request.clearQuarantine !== "boolean") {
1485
+ throw new ConductorBridgeApiError("invalid-input");
1486
+ }
1487
+ const parsed = await postRepairRequest(access, `${epicRunApiPath(request.epicRunId)}/resume-run`, { clear_quarantine: request.clearQuarantine }, fetchImpl);
1488
+ return parseResumeRunResult(parsed);
1489
+ }
1334
1490
  /**
1335
1491
  * Idempotently seed an epic ticket status row via POST to the per-epic tickets
1336
1492
  * endpoint. The backend uses ON CONFLICT DO NOTHING so repeated seeding across
@@ -1940,6 +2096,8 @@ export const CONDUCTOR_REVIEW_ALIGNMENT_STATUSES = new Set([
1940
2096
  export const READINESS_FRESH_RECONCILER_COUNT_CAP = 2;
1941
2097
  const READINESS_SOURCES = new Set(["epic", "project_default", "none"]);
1942
2098
  const ACTIONS_LEVELS = new Set(["write", "read", "none", "unknown"]);
2099
+ /** The one scope the server reports for the merge-execution block (BAPI-1135). */
2100
+ const MERGE_EXECUTION_BRANCH_SCOPES = new Set(["default_branch"]);
1943
2101
  function readinessShapeError() {
1944
2102
  // Deliberately carries NO field name, value, or payload excerpt: a malformed
1945
2103
  // body is untrusted input and its contents must not reach output.
@@ -2091,6 +2249,48 @@ export function parseConductorReadinessResponse(body) {
2091
2249
  conductor_ci_workflow: parseConductorCiWorkflow(root),
2092
2250
  unattended: parseUnattended(root),
2093
2251
  bring_up: parseBringUp(root),
2252
+ merge_execution: parseMergeExecution(root),
2253
+ };
2254
+ }
2255
+ /**
2256
+ * Parse the optional `merge_execution` block (BAPI-1135).
2257
+ *
2258
+ * Same older-server rule as every optional block above: absent (or null) is
2259
+ * `null`, and a PRESENT block is validated strictly — every boolean through
2260
+ * `requireBool`, the scope through the shared enum parser, and the version as
2261
+ * either `null` or a non-negative integer through `requireInt`.
2262
+ *
2263
+ * A malformed PRESENT block or a malformed present FIELD throws, and is never
2264
+ * degraded to `null`. The distinction is load-bearing: `null` means "this server
2265
+ * does not report it", which is a server-version fact, while a block that claims
2266
+ * to carry merge eligibility and cannot be read is untrusted input. Silently
2267
+ * turning the second into the first would let a corrupt body masquerade as an
2268
+ * older deploy, and the two have different remediations.
2269
+ */
2270
+ function parseMergeExecution(body) {
2271
+ const raw = body.merge_execution;
2272
+ if (raw === undefined || raw === null)
2273
+ return null;
2274
+ const o = requireObject(raw);
2275
+ const version = o.merge_workflow_version;
2276
+ let parsedVersion;
2277
+ if (version === null || version === undefined) {
2278
+ parsedVersion = null;
2279
+ }
2280
+ else {
2281
+ parsedVersion = requireInt(o, "merge_workflow_version");
2282
+ if (parsedVersion < 0)
2283
+ throw readinessShapeError();
2284
+ }
2285
+ return {
2286
+ branch_scope: requireEnum(o, "branch_scope", MERGE_EXECUTION_BRANCH_SCOPES),
2287
+ branch_protection_probe_succeeded: requireBool(o, "branch_protection_probe_succeeded"),
2288
+ branch_protection_actions_bot_admitted: requireBool(o, "branch_protection_actions_bot_admitted"),
2289
+ merge_workflow_probe_succeeded: requireBool(o, "merge_workflow_probe_succeeded"),
2290
+ merge_workflow_present: requireBool(o, "merge_workflow_present"),
2291
+ merge_workflow_enabled: requireBool(o, "merge_workflow_enabled"),
2292
+ merge_workflow_version: parsedVersion,
2293
+ merge_workflow_token_write_authority: requireBool(o, "merge_workflow_token_write_authority"),
2094
2294
  };
2095
2295
  }
2096
2296
  /**
@@ -1,5 +1,5 @@
1
1
  /** The pinned operator runbook every remedy below points into. */
2
- export const BRING_UP_RUNBOOK = "docs/claude/epic-conductor-v2-operator-runbook.md";
2
+ export const BRING_UP_RUNBOOK = "docs/claude/epic-implementer-v2-operator-runbook.md";
3
3
  /**
4
4
  * The client-derived refusal vocabulary, IN REFUSAL ORDER.
5
5
  *
@@ -87,10 +87,10 @@ export function mapClaudeHookEventToSemanticType(eventName) {
87
87
  }
88
88
  /** Conductor identity env required before any worker event may be emitted. */
89
89
  const REQUIRED_IDENTITY_ENV = [
90
- "BAPI_CONDUCTOR_RUN_ID",
91
- "BAPI_CONDUCTOR_WORKER_ID",
92
- "BAPI_CONDUCTOR_TICKET_KEY",
93
- "BAPI_CONDUCTOR_WORKTREE_PATH",
90
+ "BAPI_EPIC_IMPLEMENTER_RUN_ID",
91
+ "BAPI_EPIC_IMPLEMENTER_WORKER_ID",
92
+ "BAPI_EPIC_IMPLEMENTER_TICKET_KEY",
93
+ "BAPI_EPIC_IMPLEMENTER_WORKTREE_PATH",
94
94
  ];
95
95
  function nonEmpty(value) {
96
96
  return typeof value === "string" && value.trim().length > 0;
@@ -100,7 +100,7 @@ function nonEmpty(value) {
100
100
  * the process environment, or `null` when the event is unmapped or the
101
101
  * conductor identity is incomplete.
102
102
  *
103
- * Identity gating: requires `BAPI_CONDUCTOR_ENABLED=1` and all of
103
+ * Identity gating: requires `BAPI_EPIC_IMPLEMENTER_ENABLED=1` and all of
104
104
  * {@link REQUIRED_IDENTITY_ENV}. Any missing value returns `null` WITHOUT
105
105
  * emitting — this prevents accidental emissions from unrelated worktrees that
106
106
  * happen to have the hook on PATH.
@@ -116,7 +116,7 @@ export function buildClaudeHookConductorEvent(payload, env) {
116
116
  if (type === null) {
117
117
  return null;
118
118
  }
119
- if (env.BAPI_CONDUCTOR_ENABLED !== "1") {
119
+ if (env.BAPI_EPIC_IMPLEMENTER_ENABLED !== "1") {
120
120
  return null;
121
121
  }
122
122
  for (const key of REQUIRED_IDENTITY_ENV) {
@@ -124,14 +124,14 @@ export function buildClaudeHookConductorEvent(payload, env) {
124
124
  return null;
125
125
  }
126
126
  }
127
- const ticketKey = env.BAPI_CONDUCTOR_TICKET_KEY.trim();
128
- const worktreePath = env.BAPI_CONDUCTOR_WORKTREE_PATH.trim();
127
+ const ticketKey = env.BAPI_EPIC_IMPLEMENTER_TICKET_KEY.trim();
128
+ const worktreePath = env.BAPI_EPIC_IMPLEMENTER_WORKTREE_PATH.trim();
129
129
  const details = {
130
130
  ticket_key: ticketKey,
131
131
  worktree_path: worktreePath,
132
132
  };
133
- if (nonEmpty(env.BAPI_CONDUCTOR_REPO_NAME)) {
134
- details.repo = env.BAPI_CONDUCTOR_REPO_NAME.trim();
133
+ if (nonEmpty(env.BAPI_EPIC_IMPLEMENTER_REPO_NAME)) {
134
+ details.repo = env.BAPI_EPIC_IMPLEMENTER_REPO_NAME.trim();
135
135
  }
136
136
  // The native payload lives ONLY under data.raw. Wrap non-object payloads so the
137
137
  // raw contract is always an object, never echoing native fields elsewhere.
@@ -142,8 +142,8 @@ export function buildClaudeHookConductorEvent(payload, env) {
142
142
  source: "claude-code",
143
143
  type,
144
144
  subject: ticketKey,
145
- run_id: env.BAPI_CONDUCTOR_RUN_ID.trim(),
146
- worker_id: env.BAPI_CONDUCTOR_WORKER_ID.trim(),
145
+ run_id: env.BAPI_EPIC_IMPLEMENTER_RUN_ID.trim(),
146
+ worker_id: env.BAPI_EPIC_IMPLEMENTER_WORKER_ID.trim(),
147
147
  producer: "claude-code-hook",
148
148
  observed_via: "claude-code-hook",
149
149
  data: {
@@ -176,21 +176,21 @@ export function buildConductorEmitEventArgs(event) {
176
176
  }
177
177
  /**
178
178
  * Resolve how to invoke `epic-implementer emit-event`. Prefers
179
- * `BAPI_CONDUCTOR_CLI_FILE` (run via the current Node executable so no global
180
- * install is required), otherwise falls back to `BAPI_CONDUCTOR_BIN` or the
179
+ * `BAPI_EPIC_IMPLEMENTER_CLI_FILE` (run via the current Node executable so no global
180
+ * install is required), otherwise falls back to `BAPI_EPIC_IMPLEMENTER_BIN` or the
181
181
  * bare `epic-implementer` binary on PATH — never the retained `conductor`
182
182
  * name, which is now a fixed migration stub (S4/BAPI-1080). `emitArgs` are
183
183
  * appended after the `emit-event` subcommand.
184
184
  */
185
185
  export function resolveConductorEmitCommand(env, emitArgs) {
186
- const cliFile = env.BAPI_CONDUCTOR_CLI_FILE;
186
+ const cliFile = env.BAPI_EPIC_IMPLEMENTER_CLI_FILE;
187
187
  if (nonEmpty(cliFile)) {
188
188
  return {
189
189
  command: process.execPath,
190
190
  args: [cliFile.trim(), "emit-event", ...emitArgs],
191
191
  };
192
192
  }
193
- const bin = nonEmpty(env.BAPI_CONDUCTOR_BIN) ? env.BAPI_CONDUCTOR_BIN.trim() : "epic-implementer";
193
+ const bin = nonEmpty(env.BAPI_EPIC_IMPLEMENTER_BIN) ? env.BAPI_EPIC_IMPLEMENTER_BIN.trim() : "epic-implementer";
194
194
  return { command: bin, args: ["emit-event", ...emitArgs] };
195
195
  }
196
196
  const defaultConductorSpawn = (command, args, input) => {