@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
|
@@ -364,8 +364,15 @@ export function safeDiagnosticMessage(err, fallback) {
|
|
|
364
364
|
}
|
|
365
365
|
return fallback;
|
|
366
366
|
}
|
|
367
|
-
/**
|
|
368
|
-
|
|
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-
|
|
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
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
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 `
|
|
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.
|
|
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.
|
|
128
|
-
const worktreePath = env.
|
|
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.
|
|
134
|
-
details.repo = env.
|
|
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.
|
|
146
|
-
worker_id: env.
|
|
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
|
-
* `
|
|
180
|
-
* install is required), otherwise falls back to `
|
|
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.
|
|
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.
|
|
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) => {
|