@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
package/build/init.js CHANGED
@@ -818,41 +818,33 @@ export async function runInit(cwd, options = {}) {
818
818
  // ---- Phase 4a: Scaffold GitHub Copilot command artifacts ----
819
819
  // VS Code Local agent discovers prompt files; Agent Host discovers project
820
820
  // skills. Both artifacts are rendered from COMMANDS above, never a second
821
- // Copilot-specific command catalog.
821
+ // Copilot-specific command catalog. Each representation reports its own
822
+ // counts, so a written prompt never implies its skill exists (or vice versa).
822
823
  if (ideDetection.vscode) {
823
824
  const promptsDir = path.join(cwd, ".github", "prompts");
824
825
  const skillsDir = path.join(cwd, ".github", "skills");
825
- const copilotWritten = new Set();
826
- const copilotSkipped = new Set();
827
- const copilotOverwritten = new Set();
828
826
  const artifacts = commandEntries.map(([filename, content]) => ({
829
- filename,
830
827
  commandName: filename.replace(/\.md$/, ""),
831
828
  ...translateCommandToCopilot(filename, content),
832
829
  }));
833
- const outcomes = await Promise.all(artifacts.flatMap(({ filename, commandName, prompt, skill }) => [
834
- scaffoldFile(path.join(promptsDir, filename.replace(/\.md$/, ".prompt.md")), prompt, promptsDir),
835
- scaffoldFile(path.join(skillsDir, commandName, "SKILL.md"), skill, path.join(skillsDir, commandName)),
836
- ]));
837
- artifacts.forEach(({ commandName }, i) => {
838
- const commandOutcomes = outcomes.slice(i * 2, i * 2 + 2);
839
- if (commandOutcomes.every((outcome) => outcome === "skipped")) {
840
- copilotSkipped.add(commandName);
841
- return;
842
- }
843
- if (commandOutcomes.some((outcome) => outcome === "overwritten")) {
844
- copilotOverwritten.add(commandName);
845
- }
846
- copilotWritten.add(commandName);
847
- });
848
- console.log(`\nCopilot workflows: scaffolded ${total} prompt files and ${total} Agent Host skills`);
849
- if (copilotWritten.size > 0)
850
- console.log(` Written: ${copilotWritten.size}`);
851
- if (copilotOverwritten.size > 0)
852
- console.log(` Overwritten (content changed): ${copilotOverwritten.size}`);
853
- if (copilotSkipped.size > 0)
854
- console.log(` Skipped (unchanged): ${copilotSkipped.size}`);
855
- console.log(" .github/prompts/ (VS Code Local agent) and .github/skills/ (Agent Host)");
830
+ const promptOutcomes = await Promise.all(artifacts.map(({ commandName, prompt }) => scaffoldFile(path.join(promptsDir, `${commandName}.prompt.md`), prompt, promptsDir)));
831
+ const skillOutcomes = await Promise.all(artifacts.map(({ commandName, skill }) => scaffoldFile(path.join(skillsDir, commandName, "SKILL.md"), skill, path.join(skillsDir, commandName))));
832
+ const copilotReports = [
833
+ ["VS Code Local-agent prompt files", ".github/prompts/", promptOutcomes],
834
+ ["Agent Host skills", ".github/skills/", skillOutcomes],
835
+ ];
836
+ for (const [label, dir, outcomes] of copilotReports) {
837
+ const written = outcomes.filter((outcome) => outcome !== "skipped").length;
838
+ const overwritten = outcomes.filter((outcome) => outcome === "overwritten").length;
839
+ const skipped = outcomes.filter((outcome) => outcome === "skipped").length;
840
+ console.log(`\n${label}: scaffolded ${outcomes.length} into ${dir}`);
841
+ if (written > 0)
842
+ console.log(` Written: ${written}`);
843
+ if (overwritten > 0)
844
+ console.log(` Overwritten (content changed): ${overwritten}`);
845
+ if (skipped > 0)
846
+ console.log(` Skipped (unchanged): ${skipped}`);
847
+ }
856
848
  }
857
849
  // ---- Phase 4b: Scaffold shipped documentation assets ----
858
850
  // Every doc asset the /install-bridge capability report points a human at,
@@ -54,6 +54,32 @@
54
54
  * Neither can produce `complete`: they provision LOCAL capability, and the
55
55
  * terminal outcome still turns only on the live server-observed executor claim.
56
56
  *
57
+ * DURABLE-HOST MODE (BAPI-1137). The executor phase below is the supported
58
+ * bring-up for a *durable host*: an execution home that is not an interactive
59
+ * shell session, so a sleeping or closed operator laptop no longer stalls a run.
60
+ * The client command spelling is unchanged — it is still `install conductor` —
61
+ * and so is every primitive it drives; what this slice does is name the
62
+ * composition and make its boundaries explicit. Durable-host mode IS that
63
+ * composition, and nothing more:
64
+ *
65
+ * planning (pure)
66
+ * → separately consented unit writing
67
+ * → separately consented start
68
+ * → read-only local inspection (service state, session/linger durability)
69
+ * → bounded server-observed readiness
70
+ *
71
+ * `executor install-service` remains the standalone, lifecycle-free generator
72
+ * for operators who deliberately manage lifecycle themselves.
73
+ *
74
+ * HOST OWNERSHIP is an operational selection, not server policy. A durable host
75
+ * may be operator-operated or Bridge-operated, chosen per repository, and both
76
+ * choices run IDENTICAL generation and lifecycle logic: `BAPI_BASE_URL` is the
77
+ * only ownership/location input, while platform paths, unit format, and `PATH`
78
+ * are machine-specific details rather than ownership signals. Nothing about that
79
+ * choice reaches `RunPolicy` or any other server policy payload. Bridge-operated
80
+ * mode is also NON-ALLOCATING: this command can configure a host that has
81
+ * already been assigned, and it never provisions a fleet or allocates a machine.
82
+ *
57
83
  * The executor phase (BAPI-779) is opt-in via `--executor-id` and runs behind
58
84
  * TWO independent consents, per property 3: writing the unit is not consent to
59
85
  * start a persistent daemon. Its shape is:
@@ -70,6 +96,13 @@
70
96
  * non-fatal: the run prints the exact platform-specific start command and
71
97
  * finishes at `complete-with-manual-steps`.
72
98
  *
99
+ * MERGE AUTHORITY IS UNTOUCHED (BAPI-1137). Provisioning a durable host gives
100
+ * the executor no merge-decision route and no direct GitHub write operation: the
101
+ * reconciler remains the sole transition authority and the actuator writes only
102
+ * evidence. Readiness output above must never be worded so as to imply
103
+ * otherwise — `executor_ready` means an executor is claiming work, not that this
104
+ * host can merge anything.
105
+ *
73
106
  * The command does NOT persist a repository-level review policy: that is
74
107
  * BAPI-694's. The policy selected here is per-run and is reported as such.
75
108
  */
@@ -81,6 +114,7 @@ import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
81
114
  import { CLAUDE_REVIEW_WORKFLOW_RELPATH, renderClaudeReviewWorkflow, } from "./claude-review-workflow.js";
82
115
  import { formatExecutorInstallPreflight, } from "./executor/install-preflight.js";
83
116
  import { buildExecutorServiceStartPlan, formatExecutorServiceStartFailure, } from "./executor/service-lifecycle.js";
117
+ import { executorEpicRunIdRefusal, isValidExecutorEpicRunId, } from "./executor/claim-scope.js";
84
118
  import { planExecutorServiceInstall, } from "./executor/service-unit.js";
85
119
  // ---------------------------------------------------------------------------
86
120
  // Options + parsing
@@ -117,6 +151,12 @@ export function getInstallBridgeConductorUsage() {
117
151
  " --executor-id <id> Stable executor id. Supplying it OPTS IN to the",
118
152
  " consented executor-provisioning phase below; the",
119
153
  " phase is skipped entirely without it.",
154
+ " --epic-run-id <id> Claim scope for the generated unit (repeatable).",
155
+ " --repo-wide Claim-scope opt-out: claim from every authorized",
156
+ " repo, unscoped by run. EXACTLY ONE of --epic-run-id",
157
+ " or --repo-wide is required with --executor-id — the",
158
+ " executor refuses to start without one, so a unit",
159
+ " generated without it would fail on every boot.",
120
160
  " --allowed-bot <slug> claude-review workflow: allowed bot login",
121
161
  " --secret-name <NAME> claude-review workflow: OAuth token secret NAME",
122
162
  " --branches a,b claude-review workflow: PR branch triggers",
@@ -136,7 +176,7 @@ export function getInstallBridgeConductorUsage() {
136
176
  "production when unset). The bootstrap POST then asks its own, separate write",
137
177
  "consent. Without a TTY there is no consent, so the command aborts.",
138
178
  "",
139
- "Tool visibility (its own consent): adds the `conductor` token to the",
179
+ "Tool visibility (its own consent): adds the `epic_implementer` token to the",
140
180
  "BRIDGE_MCP_PROFILE of the Bridge entry in host MCP configurations that",
141
181
  "ALREADY have one. Automatic merging covers .mcp.json, .vscode/mcp.json, and",
142
182
  ".cursor/mcp.json only — never Codex or Copilot, which are neither inspected",
@@ -152,8 +192,17 @@ export function getInstallBridgeConductorUsage() {
152
192
  "hook consent; a degraded ledger reports the module, NODE_MODULE_VERSION, and",
153
193
  "failure kind, and never triggers an install, rebuild, or migration.",
154
194
  "",
155
- "Executor provisioning: `executor install-service` still generates a unit and",
156
- "nothing else, and remains available for operators who want the file only.",
195
+ "Durable-host mode: this command is the supported bring-up for a persistent,",
196
+ "laptop-independent executor. It composes planning, separately consented unit",
197
+ "writing, separately consented start, read-only local inspection, and bounded",
198
+ "server-observed readiness. `executor install-service` still generates a unit and",
199
+ "nothing else, and remains available for operators who manage lifecycle themselves.",
200
+ "",
201
+ "Host ownership (operator-operated or Bridge-operated) is an operational selection,",
202
+ "not server policy: both run identical generation and lifecycle logic, and",
203
+ "BAPI_BASE_URL is the only ownership/location input. Bridge-operated mode allocates",
204
+ "no host — it configures one already assigned. A repository with no assigned durable",
205
+ "host is NOT ready; escalate that to Bridge operations.",
157
206
  "",
158
207
  "With --executor-id, this command does the whole leg. A preflight checks the platform,",
159
208
  "`wt` / `git` / `claude` on PATH, and an explicit base URL, then reports whether",
@@ -200,6 +249,8 @@ export function parseInstallBridgeConductorArgs(argv) {
200
249
  let epicKey;
201
250
  let planFile;
202
251
  let executorId;
252
+ const epicRunIds = [];
253
+ let repoWide = false;
203
254
  let allowedBot;
204
255
  let secretName;
205
256
  let branches;
@@ -231,6 +282,11 @@ export function parseInstallBridgeConductorArgs(argv) {
231
282
  return { status: "error", message: "--skip-workflow takes no value" };
232
283
  skipWorkflow = true;
233
284
  break;
285
+ case "--repo-wide":
286
+ if (inline !== undefined)
287
+ return { status: "error", message: "--repo-wide takes no value" };
288
+ repoWide = true;
289
+ break;
234
290
  case "--repo":
235
291
  case "--required-check":
236
292
  case "--required-checks":
@@ -238,6 +294,7 @@ export function parseInstallBridgeConductorArgs(argv) {
238
294
  case "--epic-key":
239
295
  case "--plan-file":
240
296
  case "--executor-id":
297
+ case "--epic-run-id":
241
298
  case "--allowed-bot":
242
299
  case "--secret-name":
243
300
  case "--branches": {
@@ -259,6 +316,8 @@ export function parseInstallBridgeConductorArgs(argv) {
259
316
  planFile = value;
260
317
  else if (flag === "--executor-id")
261
318
  executorId = value;
319
+ else if (flag === "--epic-run-id")
320
+ epicRunIds.push(value);
262
321
  else if (flag === "--allowed-bot")
263
322
  allowedBot = value;
264
323
  else if (flag === "--secret-name")
@@ -304,6 +363,47 @@ export function parseInstallBridgeConductorArgs(argv) {
304
363
  "so the bootstrap refuses to write one.",
305
364
  };
306
365
  }
366
+ // BAPI-1137 — the claim scope is validated HERE, at parse time, for the same
367
+ // reason the required-check list is: a unit that cannot start is worse than a
368
+ // refused invocation, and discovering it costs a reboot to notice.
369
+ if (executorId !== undefined) {
370
+ if (epicRunIds.length > 0 && repoWide) {
371
+ return {
372
+ status: "error",
373
+ message: "--epic-run-id and --repo-wide are mutually exclusive: a generated unit carries exactly " +
374
+ "one claim scope.",
375
+ };
376
+ }
377
+ if (epicRunIds.length === 0 && !repoWide) {
378
+ return {
379
+ status: "error",
380
+ message: "--executor-id requires an explicit claim scope: pass --epic-run-id <id> (repeatable) or " +
381
+ "--repo-wide. The executor refuses to start without one, so a unit generated without it " +
382
+ "would install cleanly and then fail on every boot.",
383
+ };
384
+ }
385
+ const seenRunIds = new Set();
386
+ for (const runId of epicRunIds) {
387
+ // The SAME shape check `executor install-service` applies, reused rather
388
+ // than restated: a value that reaches the rendered argument vector is
389
+ // validated at the point of entry, so a malformed id is one refusal here
390
+ // instead of a unit that installs and then fails on every boot.
391
+ if (!isValidExecutorEpicRunId(runId)) {
392
+ return { status: "error", message: executorEpicRunIdRefusal(runId) };
393
+ }
394
+ if (seenRunIds.has(runId)) {
395
+ return { status: "error", message: `Duplicate --epic-run-id: ${runId}` };
396
+ }
397
+ seenRunIds.add(runId);
398
+ }
399
+ }
400
+ else if (epicRunIds.length > 0 || repoWide) {
401
+ return {
402
+ status: "error",
403
+ message: "--epic-run-id / --repo-wide configure the generated executor unit, so they require " +
404
+ "--executor-id.",
405
+ };
406
+ }
307
407
  return {
308
408
  status: "ok",
309
409
  options: {
@@ -319,6 +419,8 @@ export function parseInstallBridgeConductorArgs(argv) {
319
419
  branches,
320
420
  skipWorkflow,
321
421
  executorId,
422
+ ...(epicRunIds.length > 0 ? { epicRunIds } : {}),
423
+ ...(repoWide ? { repoWide: true } : {}),
322
424
  },
323
425
  };
324
426
  }
@@ -435,7 +537,12 @@ function buildExecutorProvisioningPlans(executor, executorId, baseUrl,
435
537
  * access rather than from a flag, so the unit can only ever serve the
436
538
  * repository this install actually authenticated for.
437
539
  */
438
- repos) {
540
+ repos,
541
+ /**
542
+ * The claim scope the unit starts with (BAPI-1137). Exactly one side is set,
543
+ * validated at parse time — a unit without a scope fails on every boot.
544
+ */
545
+ claimScope) {
439
546
  if (executor.platform !== "darwin" && executor.platform !== "linux") {
440
547
  return {
441
548
  ok: false,
@@ -455,6 +562,13 @@ repos) {
455
562
  baseUrl,
456
563
  homeDir: executor.homeDir,
457
564
  envPath: executor.envPath,
565
+ // Forwarded so the supervised process starts with the scope the
566
+ // operator chose. Without it the executor refuses to start (R54) and the
567
+ // service manager restarts it forever.
568
+ ...(claimScope.epicRunIds !== undefined && claimScope.epicRunIds.length > 0
569
+ ? { epicRunIds: claimScope.epicRunIds }
570
+ : {}),
571
+ ...(claimScope.repoWide === true ? { repoWide: true } : {}),
458
572
  }, platform);
459
573
  }
460
574
  catch (err) {
@@ -523,23 +637,102 @@ async function runExecutorProvisioningPhase(deps, executor, plans, access) {
523
637
  return null;
524
638
  }
525
639
  deps.log(" the service manager accepted the unit.");
640
+ // ---- Read-only local inspection (BAPI-1137) ---------------------------
641
+ // Reported as its own fact, deliberately BEFORE and separately from the server
642
+ // observation below. "The manager accepted the unit" and "the manager is
643
+ // running it" and "Bridge has seen it claim" are three different statements,
644
+ // and an operator who reads one as another is exactly how a dead durable host
645
+ // gets called healthy.
646
+ let localState = "unknown";
647
+ if (executor.inspectServiceState) {
648
+ try {
649
+ const inspection = await executor.inspectServiceState(plans.install);
650
+ localState = inspection.state;
651
+ }
652
+ catch {
653
+ // A probe that throws leaves the state unknown. Nothing here is allowed to
654
+ // upgrade "I could not ask" into a state.
655
+ localState = "unknown";
656
+ }
657
+ }
658
+ deps.log(` local service state (read-only): ${localState}`);
659
+ deps.log(" that is a LOCAL fact only — it says nothing about whether Bridge has observed a claim.");
660
+ if (executor.inspectDurability) {
661
+ let durability = null;
662
+ try {
663
+ durability = await executor.inspectDurability(plans.install);
664
+ }
665
+ catch {
666
+ durability = null;
667
+ }
668
+ if (durability === null || durability.prerequisite === "unverifiable") {
669
+ deps.log(" durable session: unverifiable — this run could not establish whether the executor " +
670
+ "survives logout.");
671
+ }
672
+ else if (durability.prerequisite === "configured") {
673
+ deps.log(" durable session: configured — the executor survives logout on this host.");
674
+ }
675
+ else if (durability.prerequisite === "session-bound") {
676
+ // The probe answered, and what it answered is "a login session exists" —
677
+ // not "this survives logout". Reported as the qualified fact it is, with
678
+ // the session prerequisite the operator has to keep true.
679
+ deps.log(" durable session: session-bound — a login session exists, but the executor is NOT " +
680
+ "logout-durable on this host.");
681
+ if (durability.remediation) {
682
+ deps.log(` manual action: ${durability.remediation}`);
683
+ }
684
+ }
685
+ else if (durability.prerequisite === "not-configured") {
686
+ // NOT durably configured, said plainly, with the command the OPERATOR runs.
687
+ // Bridge never runs it: enabling lingering changes how the host behaves
688
+ // after logout, and that is the operator's decision on their own machine.
689
+ deps.log(" durable session: NOT durably configured — the executor stops when you log out.");
690
+ if (durability.remediation) {
691
+ deps.log(` manual action: ${durability.remediation}`);
692
+ }
693
+ }
694
+ else {
695
+ // `not-applicable`. Unreachable in practice — this phase only runs on
696
+ // darwin and linux, both of which have a prerequisite — but stated rather
697
+ // than left as silence, because a fact that renders nothing is
698
+ // indistinguishable from a fact that was never collected.
699
+ deps.log(" durable session: not applicable on this platform.");
700
+ }
701
+ }
526
702
  // ---- Bounded observation ----------------------------------------------
527
703
  // Only reached after a SUCCESSFUL start: polling for an executor that was
528
704
  // never started would burn the whole budget to learn what we already know.
529
705
  deps.log(` watching the server for an executor claim (up to ${Math.round(EXECUTOR_OBSERVATION_BUDGET_MS / 1000)}s)…`);
530
706
  const observation = await observeExecutorReadiness(access, deps.fetchJson, executor);
531
707
  if (observation.status === "observed") {
532
- deps.log(" the server observed this executor.");
708
+ deps.log(` the server observed an executor claim for this repository (requested executor id: ${plans.install.executorId}).`);
709
+ // BAPI-1137, honestly scoped: the readiness contract this client can read is
710
+ // REPOSITORY-scoped — `GET .../conductor-readiness?repo_name=` returns
711
+ // `executor.{liveness_readable,last_seen_at,last_seen_age_seconds,ready}`
712
+ // and carries no executor id, because the server derives liveness from the
713
+ // repository's `executor_jobs` rows. So this observation proves an executor
714
+ // is claiming for this repository; it cannot prove it is THIS executor id.
715
+ // The honest move is to say so rather than to invent a request or response
716
+ // field the endpoint does not implement — see the durable-host runbook's
717
+ // prerequisite note for the server-side contract this needs.
718
+ deps.log(" note: the readiness contract is repository-scoped and carries no executor id, so this");
719
+ deps.log(" confirms an executor is claiming for this repository — not specifically the one just started.");
720
+ deps.log(" if another executor already serves this repository, verify the requested id yourself before");
721
+ deps.log(" treating this host as the one doing the work.");
533
722
  return observation.readiness;
534
723
  }
535
724
  // The honest degradation: name what WAS achieved and what was not, separately.
536
- deps.log(" the unit was written and the service manager started it,");
537
- deps.log(" but the server did not observe an executor claim within the budget " +
538
- `(${observation.attempts} read(s), ${observation.readFailures} unreadable).`);
725
+ deps.log(` started locally, not observed (executor id: ${plans.install.executorId}).`);
726
+ deps.log(` local service state (read-only): ${localState}`);
727
+ deps.log(" the unit was written and the service manager started it, but the server did not observe " +
728
+ `an executor claim within the budget (${observation.attempts} read(s), ` +
729
+ `${observation.readFailures} unreadable).`);
539
730
  deps.log(observation.lastReady === false
540
731
  ? " the server last reported this repository's executor as stale/not ready."
541
732
  : " the server has not yet seen any executor claim for this repository.");
542
733
  deps.log(" executor_ready is NOT claimed — only a live server observation can produce it.");
734
+ deps.log(" escalate to the host's operating party (operator-operated) or to Bridge operations " +
735
+ "(Bridge-operated) with the repository name and the executor id above.");
543
736
  return null;
544
737
  }
545
738
  // ---------------------------------------------------------------------------
@@ -556,10 +749,10 @@ async function runExecutorProvisioningPhase(deps, executor, plans, access) {
556
749
  * said nothing would reasonably conclude the tools were already visible.
557
750
  */
558
751
  export function formatConductorRestartAdvisory(mergedProfile) {
559
- return (`⚠️ Conductor profile updated: BRIDGE_MCP_PROFILE is now set to \`${mergedProfile}\` ` +
752
+ return (`⚠️ Epic Implementer profile updated: BRIDGE_MCP_PROFILE is now set to \`${mergedProfile}\` ` +
560
753
  "in your local MCP config file(s). The running MCP server captured its active groups " +
561
754
  "at startup, so this activates on the next MCP server launch — restart your MCP " +
562
- "client to gain access to the conductor tools.");
755
+ "client to gain access to the Epic Implementer tools.");
563
756
  }
564
757
  /** Remediation printed when no supported host config carries a Bridge entry. */
565
758
  export const CONDUCTOR_PROFILE_EMPTY_MERGE_REMEDIATION = `run \`npx ${MCP_PACKAGE_NAME} install\` to create and register the host MCP entry, ` +
@@ -570,14 +763,14 @@ export const CONDUCTOR_HOOKS_MANUAL_REMEDIATION = "re-run `install conductor` an
570
763
  /**
571
764
  * Phase: tool visibility.
572
765
  *
573
- * Adds the `conductor` token to EXISTING supported host MCP entries behind its
766
+ * Adds the `epic_implementer` token to EXISTING supported host MCP entries behind its
574
767
  * own consent, then prints the restart advisory. It never creates a host config
575
768
  * — the merge helper's never-create rule is what keeps a declined or
576
769
  * unconfigured repository from acquiring files it did not ask for.
577
770
  */
578
771
  async function runConductorToolVisibilityPhase(deps, local) {
579
772
  const consented = await confirm(deps, "Update existing supported host MCP configurations (.mcp.json, .vscode/mcp.json, " +
580
- ".cursor/mcp.json) to add the `conductor` profile? [y/N]: ");
773
+ ".cursor/mcp.json) to add the `epic_implementer` profile? [y/N]: ");
581
774
  if (!consented) {
582
775
  deps.log(" declined — no host MCP configuration and no install state were written.");
583
776
  return {
@@ -620,7 +813,7 @@ async function runConductorToolVisibilityPhase(deps, local) {
620
813
  }
621
814
  deps.log(carrying.length > 0
622
815
  ? ` updated: ${carrying.join(", ")}`
623
- : " the conductor profile was merged into your supported host MCP config(s).");
816
+ : " the Epic Implementer profile was merged into your supported host MCP config(s).");
624
817
  // Coverage honesty: the merge path writes these three files and no others.
625
818
  deps.log(" Codex and Copilot host configuration was NOT inspected or changed.");
626
819
  deps.log(` ${formatConductorRestartAdvisory(mergedProfile)}`);
@@ -847,7 +1040,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
847
1040
  // could not be resolved the doctor above has already produced a fatal
848
1041
  // section, so this fallback only matters for the narrow window where a
849
1042
  // caller injected a non-fatal doctor.
850
- const planned = buildExecutorProvisioningPlans(executor, options.executorId, baseUrl, access ? [access.repoName] : []);
1043
+ const planned = buildExecutorProvisioningPlans(executor, options.executorId, baseUrl, access ? [access.repoName] : [], { epicRunIds: options.epicRunIds, repoWide: options.repoWide });
851
1044
  if (!planned.ok) {
852
1045
  deps.errorLog("");
853
1046
  deps.errorLog(`Executor provisioning refused — ${planned.error}`);
@@ -1027,7 +1220,7 @@ export async function runInstallBridgeConductorCli(argv, deps) {
1027
1220
  else {
1028
1221
  deps.log(" skipped — this caller supplied no local-provisioning seams.");
1029
1222
  manualActions.push("tool visibility: run the packaged `install conductor` command to add the " +
1030
- "`conductor` profile to your host MCP configuration.");
1223
+ "`epic_implementer` profile to your host MCP configuration.");
1031
1224
  }
1032
1225
  deps.log("");
1033
1226
  deps.log("Phase 5/8 — conductor local observability…");