@bridge_gpt/mcp-server 0.2.66 → 0.2.68

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 (171) hide show
  1. package/README.md +421 -83
  2. package/build/agent-capabilities/probes.js +15 -5
  3. package/build/agent-launchers/claude-executor-adapter.js +70 -16
  4. package/build/agent-launchers/executor-adapter.js +7 -4
  5. package/build/agents.generated.js +22 -2
  6. package/build/bitbucket-workspace-remote.js +136 -0
  7. package/build/bootstrap-project-identity.js +24 -0
  8. package/build/bounded-wait.js +67 -10
  9. package/build/bridge-permissions.js +829 -0
  10. package/build/claude-settings.js +18 -0
  11. package/build/cli-help.js +21 -0
  12. package/build/command-assets-doctor.js +24 -70
  13. package/build/command-provisioning.js +29 -5
  14. package/build/commands.generated.js +12 -12
  15. package/build/conductor/bridge-api-client.js +857 -1
  16. package/build/conductor/bring-up-facts.js +1 -1
  17. package/build/conductor/command-drift-production.js +92 -0
  18. package/build/conductor/command-drift.js +89 -0
  19. package/build/conductor/doctor.js +72 -29
  20. package/build/conductor/epic-branch-ci-inspection.js +1375 -0
  21. package/build/conductor/epic-implementer-cli.js +98 -28
  22. package/build/conductor/errors.js +4 -3
  23. package/build/conductor/gate-discovery.js +890 -0
  24. package/build/conductor/guided-repair.js +6 -2
  25. package/build/conductor/hook-runtime-qualification.js +1 -1
  26. package/build/conductor/install-doctor.js +15 -3
  27. package/build/conductor/json-output.js +63 -0
  28. package/build/conductor/merge-execution-contract.js +27 -8
  29. package/build/conductor/merge-execution-eligibility.js +3 -2
  30. package/build/conductor/pr-ci-producer.js +1 -1
  31. package/build/conductor/readiness-cli.js +113 -12
  32. package/build/conductor/readiness-sections.js +410 -9
  33. package/build/conductor/readiness.js +388 -33
  34. package/build/conductor/recovery-cli.js +310 -12
  35. package/build/conductor/recovery-operations.js +167 -3
  36. package/build/conductor/repair-contract.js +54 -3
  37. package/build/conductor/repository-workflow-remedy.js +125 -0
  38. package/build/conductor/tools.js +2 -2
  39. package/build/conductor-bundle-artifacts.js +8 -0
  40. package/build/connect-bitbucket-api.js +41 -6
  41. package/build/connect-bitbucket.js +106 -33
  42. package/build/credential-store.js +207 -47
  43. package/build/docs.generated.js +62 -3
  44. package/build/doctor.js +92 -1
  45. package/build/drive-epic-key.js +6 -5
  46. package/build/drive-epic.js +129 -61
  47. package/build/epic-coherence-input.js +2 -1
  48. package/build/epic-coherence-input.test-fixtures.js +4 -3
  49. package/build/epic-implementer-bin.js +1379 -28
  50. package/build/epic-implementer-bundle-cli.js +2 -2
  51. package/build/epic-workarounds-path.js +39 -0
  52. package/build/executor/cli.js +33 -2
  53. package/build/executor/completion-contract.js +66 -10
  54. package/build/executor/completion-supervision.js +4 -0
  55. package/build/executor/deps.js +4 -1
  56. package/build/executor/env.js +19 -0
  57. package/build/executor/gate-qualification-job.js +499 -0
  58. package/build/executor/guarded-fast-forward-push.js +129 -0
  59. package/build/executor/heartbeat.js +1 -1
  60. package/build/executor/http-client.js +20 -0
  61. package/build/executor/integration-job.js +122 -8
  62. package/build/executor/job-errors.js +33 -1
  63. package/build/executor/job-runner.js +813 -87
  64. package/build/executor/likely-tests.js +290 -0
  65. package/build/executor/live-worker-registry.js +1 -1
  66. package/build/executor/observation.js +37 -3
  67. package/build/executor/packaged-recipe.js +106 -0
  68. package/build/executor/permissions.js +115 -28
  69. package/build/executor/platform.js +1 -1
  70. package/build/executor/preflight.js +112 -26
  71. package/build/executor/process-containment.js +36 -12
  72. package/build/executor/process.js +15 -7
  73. package/build/executor/prompt-spec.js +33 -1
  74. package/build/executor/recently-failed-tests-file.js +71 -0
  75. package/build/executor/reconcile-overlap-job.js +41 -23
  76. package/build/executor/required-verification.js +1 -1
  77. package/build/executor/required-work-ledger.js +111 -18
  78. package/build/executor/resume-pre-spawn.js +2 -0
  79. package/build/executor/retirement.js +69 -0
  80. package/build/executor/runner.js +122 -13
  81. package/build/executor/service-unit.js +7 -1
  82. package/build/executor/test-clock.js +15 -0
  83. package/build/executor/types.js +32 -0
  84. package/build/executor/viewer-tabs.js +58 -8
  85. package/build/executor/worker-command.js +23 -7
  86. package/build/executor/worker-diagnostics.js +37 -0
  87. package/build/executor/worker-finalization.js +113 -34
  88. package/build/executor/worker-guard-hook.js +316 -58
  89. package/build/executor/worker-log.js +4 -3
  90. package/build/executor/worker-verification-guidance.js +37 -0
  91. package/build/executor/worktree.js +166 -8
  92. package/build/first-build-advisory.js +97 -0
  93. package/build/generated-assets-advisory.js +14 -0
  94. package/build/generated-assets-catalog.js +66 -0
  95. package/build/generated-assets-context.js +46 -0
  96. package/build/generated-assets-dependencies.js +26 -0
  97. package/build/generated-assets-doctor.js +50 -0
  98. package/build/generated-assets-health.js +65 -0
  99. package/build/generated-assets-host.js +17 -0
  100. package/build/generated-assets-inventory.js +17 -0
  101. package/build/generated-assets-paths.js +60 -0
  102. package/build/generated-assets-publisher.js +133 -0
  103. package/build/generated-assets-remedy.js +25 -0
  104. package/build/generated-assets-repair.js +72 -0
  105. package/build/generated-assets-session.js +71 -0
  106. package/build/generated-assets-test-support.js +35 -0
  107. package/build/index.js +1224 -1054
  108. package/build/init.js +54 -177
  109. package/build/install-bridge-conductor-gate.js +1372 -0
  110. package/build/install-bridge-conductor.js +296 -41
  111. package/build/install-bridge.js +547 -40
  112. package/build/mcp-install-state.js +50 -4
  113. package/build/merge-workflow-install.js +3 -4
  114. package/build/phase-context-git-probe.js +68 -0
  115. package/build/phase-result-artifacts.js +173 -5
  116. package/build/pipeline-utils.js +6 -2
  117. package/build/pipelines.generated.js +34 -160
  118. package/build/plan-execution-ledger.js +2 -32
  119. package/build/plane/alembic-head.js +12 -3
  120. package/build/plane/base-worktree-guard.js +147 -0
  121. package/build/plane/cli.js +217 -17
  122. package/build/plane/code-snapshot.js +468 -0
  123. package/build/plane/defaults.js +48 -3
  124. package/build/plane/endpoint.js +41 -0
  125. package/build/plane/manifest.js +136 -2
  126. package/build/plane/member-roster.js +94 -19
  127. package/build/plane/preflight.js +83 -2
  128. package/build/plane/process-cwd.js +67 -0
  129. package/build/plane/restart.js +542 -0
  130. package/build/plane/shutdown.js +31 -0
  131. package/build/plane/status.js +143 -8
  132. package/build/plane/supervisor.js +586 -179
  133. package/build/plane/types.js +54 -2
  134. package/build/printed-remedy-commands.js +52 -0
  135. package/build/readiness-check.js +1 -1
  136. package/build/readme.generated.js +1 -1
  137. package/build/repair-assets.js +93 -0
  138. package/build/review-generation.js +22 -0
  139. package/build/run-unit-tests-launcher.js +130 -49
  140. package/build/secret-redaction.js +33 -0
  141. package/build/setup-epic.js +152 -22
  142. package/build/sfcc/config.js +9 -0
  143. package/build/sfcc/credentials.js +100 -38
  144. package/build/sfcc/hostname.js +67 -0
  145. package/build/sfcc/permissions.js +17 -13
  146. package/build/sfcc/register.js +1 -1
  147. package/build/sfcc/tool-wrapper.js +39 -43
  148. package/build/sfcc/write-grants.js +17 -11
  149. package/build/sfcc/write-result.js +4 -3
  150. package/build/sfcc/writes-site-preference.js +20 -8
  151. package/build/start-tickets-prereqs.js +70 -16
  152. package/build/start-tickets.js +208 -36
  153. package/build/tool-error-envelope.js +61 -0
  154. package/build/tool-surface-gating.js +36 -0
  155. package/build/upgrade-cli.js +55 -1
  156. package/build/version.generated.js +3 -3
  157. package/build/visual-diff.js +22 -1
  158. package/build/worker-guard-hook-bin.js +4 -6
  159. package/build/worker-guard-hook-main.js +47 -0
  160. package/build/worktree-core.js +145 -6
  161. package/docs/EPIC-IMPLEMENTER-INSTALL.md +867 -0
  162. package/docs/EPIC-IMPLEMENTER.md +728 -0
  163. package/docs/install/mcp-tool-integrations.md +24 -15
  164. package/docs/install/sfcc-integration.md +12 -4
  165. package/package.json +3 -3
  166. package/pipelines/greenfield-setup.json +4 -128
  167. package/pipelines/implement-ticket.json +3 -2
  168. package/pipelines/plan-epic.json +0 -5
  169. package/pipelines/review-ticket.json +1 -1
  170. package/docs/CONDUCTOR.md +0 -605
  171. package/pipelines/idea-to-pr.json +0 -49
@@ -92,15 +92,17 @@
92
92
  * unrelated additive change look like a breaking one here, or worse, hide a real
93
93
  * one.
94
94
  */
95
+ import { PRINTED_CHECK_NAME_INSTRUCTION, PRINTED_CONDUCTOR_EXECUTOR_INSTALL_COMMAND, PRINTED_CONDUCTOR_INSTALL_COMMAND, PRINTED_CONNECT_GITHUB_COMMAND, PRINTED_EXECUTOR_ID_INSTRUCTION, PRINTED_QUALIFY_GATE_COMMAND, } from "../printed-remedy-commands.js";
95
96
  import { mapConductorDoctorReportToReadinessChecks, } from "./doctor.js";
96
97
  import { mapInstallStatusChecksToReadinessChecks, } from "../install-doctor.js";
97
98
  import { mapPlanePreflightToReadinessChecks, } from "../plane/preflight.js";
98
99
  import { runConductorInstallDoctor, } from "./install-doctor.js";
99
- import { BRING_UP_SECTION_LABELS, presentRequiredChecks, } from "./readiness-sections.js";
100
+ import { BRING_UP_SECTION_LABELS, describeGateProfile, describeGateProfileWarning, effectiveReviewModeIsRepositoryPolicy, EPIC_BRANCH_CI_SECTION_IDS, epicBranchCiSections, presentRequiredChecks, quotedSubject, } from "./readiness-sections.js";
100
101
  import { classifyBringUpFacts } from "./bring-up-facts.js";
102
+ import { CI_WORKFLOW_PATH, REVIEW_WORKFLOW_PATH, ciWorkflowRemedy, repositoryWorkflowRemedy, } from "./repository-workflow-remedy.js";
101
103
  import { classifyMergeExecutionEligibility, MERGE_EXECUTION_INELIGIBLE_REMEDIATION, MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION, } from "./merge-execution-eligibility.js";
102
104
  import { createReadinessCheck, createReadinessCheckSafely, summarizeReadinessChecks, validateReadinessChecks, } from "../readiness-check.js";
103
- import { ADMISSION_EVIDENCE_SCHEMA_VERSION, } from "./bridge-api-client.js";
105
+ import { ADMISSION_EVIDENCE_SCHEMA_VERSION, gateProfileWaivesCiWorkflow, } from "./bridge-api-client.js";
104
106
  /** This report's own schema version. See the module docstring. */
105
107
  export const CONDUCTOR_READINESS_GATE_SCHEMA_VERSION = 1;
106
108
  /** Fixed statement carried in every report, so no consumer can mistake it for a gate. */
@@ -137,7 +139,10 @@ export const SERVER_READINESS_DESCRIPTORS = [
137
139
  id: "single-transition-authority",
138
140
  label: BRING_UP_SECTION_LABELS["single-transition-authority"],
139
141
  },
140
- { id: "reconciler-liveness", label: "Reconciler tick liveness" },
142
+ // The stable ID stays `reconciler-liveness`; only the label changed (BAPI-1360),
143
+ // because the row reports whether ACTIVE runs are making progress, not whether a
144
+ // reconciler process exists.
145
+ { id: "reconciler-liveness", label: "Active-run progress" },
141
146
  { id: "executor-readiness", label: "Executor server observation" },
142
147
  { id: "review-workflow", label: "Review workflow protocol" },
143
148
  { id: "conductor-ci-workflow", label: "Conductor CI migration guard" },
@@ -145,7 +150,7 @@ export const SERVER_READINESS_DESCRIPTORS = [
145
150
  // third unattended refusal reads. Noun phrases, like every row above: these
146
151
  // render as list items under a source heading, not as sentences.
147
152
  { id: "unattended-consent", label: "Repository unattended consent" },
148
- { id: "unattended-notify-default", label: "Verified notify webhook default" },
153
+ { id: "unattended-notify-default", label: "Escalation channel (webhook or email)" },
149
154
  { id: "repository-workflow-readiness", label: "Repository workflow readiness" },
150
155
  ];
151
156
  /** Fixed, secret-free prose per failure kind. Never a status body or header. */
@@ -169,23 +174,29 @@ const SERVER_FAILURE_COPY = {
169
174
  };
170
175
  /** Remediations for the individual server facts, as fixed prose. */
171
176
  const SERVER_REMEDIATIONS = {
172
- supervisorSetup: "run `install conductor` (or `setup-epic`) for this repository to write the project-default supervisor posture.",
173
- supervisorConfig: "run `install conductor` for this repository to write the project-default supervisor configuration.",
177
+ supervisorSetup: `run \`${PRINTED_CONDUCTOR_INSTALL_COMMAND}\` (or \`setup-epic\`) for this repository to write the project-default supervisor posture. ${PRINTED_CHECK_NAME_INSTRUCTION}`,
178
+ supervisorConfig: `run \`${PRINTED_CONDUCTOR_INSTALL_COMMAND}\` for this repository to write the project-default supervisor configuration. ${PRINTED_CHECK_NAME_INSTRUCTION}`,
174
179
  mergeApproval: "set the merge-approval requirement explicitly in the supervisor configuration rather than leaving it unset.",
175
180
  reviewPolicyMissing: "set the repository review-policy default so the merge path and the code_review gate read the same signal.",
176
181
  reviewPolicyDivergent: "align the repository review-policy default with the done gate's review_state condition, or confirm the difference is intended.",
177
- githubCredentials: "connect GitHub from the Bridge setup UI (`install-bridge connect-github`) so owner, repository id, and installation id all resolve.",
182
+ githubCredentials: `connect GitHub from the Bridge setup UI or run \`${PRINTED_CONNECT_GITHUB_COMMAND}\` so owner, repository id, and installation id all resolve.`,
178
183
  actionsProbeFailed: "re-run once credentials resolve; the permission could not be checked, which is not the same as confirmed-missing.",
179
184
  actionsMissing: "grant the GitHub App `actions: write`; without it the conductor's workflow rerun lane fails open on 403.",
180
185
  reconcilerUnreadable: "retry once the Bridge API is reachable; reconciler liveness is unknown here, not confirmed stale.",
181
- reconcilerStale: "start the reconciler for this repository (the `epic-implementer epic-tick` schedule) so ticks resume.",
186
+ // BAPI-1360: the frozen `epic-tick` command is never named. The reconciler runs
187
+ // inside the worker, so the first check is that the worker is up, then the run's
188
+ // own status.
189
+ reconcilerStale: "check the worker first (`server.worker-present`), then inspect the stalled run with `epic-implementer epic-status`.",
182
190
  executorUnreadable: "retry once the Bridge API is reachable; no executor readiness claim is ever made from local state.",
183
- executorNeverSeen: "provision and start an executor (`install conductor --executor-id <id> --repo-wide` or `executor install-service`); readiness comes only from a live server observation.",
191
+ executorNeverSeen: `provision and start an executor (\`${PRINTED_CONDUCTOR_EXECUTOR_INSTALL_COMMAND}\` or \`executor install-service\`); readiness comes only from a live server observation. ${PRINTED_CHECK_NAME_INSTRUCTION} ${PRINTED_EXECUTOR_ID_INSTRUCTION}`,
192
+ executorNoneObservedNoRuns: `no action is needed to launch: \`drive-epic\` starts a run-scoped executor. To keep an executor running between runs, install one with \`${PRINTED_CONDUCTOR_EXECUTOR_INSTALL_COMMAND}\` or \`executor install-service\`. ${PRINTED_CHECK_NAME_INSTRUCTION} ${PRINTED_EXECUTOR_ID_INSTRUCTION}`,
184
193
  executorStale: "restart the executor service; it was observed once and has gone quiet past the server's staleness threshold.",
185
194
  reviewWorkflowUnknown: "re-run; the default branch's review workflow could not be evaluated, which is not the same as it being absent.",
186
195
  reviewWorkflowIncomplete: "install the claude-review workflow template on the default branch so it emits both the run id and the reviewed SHA the verdict protocol reads.",
187
196
  ciWorkflowUnknown: "re-run; the default branch's conductor CI workflow could not be evaluated, which is not the same as the guard being absent.",
188
- ciWorkflowMissingGuard: "add the migration-guard job to the conductor CI workflow on the default branch so migrations are checked before a merge.",
197
+ // BAPI-1359: the missing-guard remedy is built by `ciWorkflowRemedy` (the shared
198
+ // formatter), because its wording depends on WHY the guard is missing — a
199
+ // confirmed Alembic chain, an older server, or an unreadable answer.
189
200
  olderServer: "upgrade the Bridge API deploy so it reports this fact; its state is unknown here, not healthy.",
190
201
  // BAPI-1102. Each names the ONE action that clears it, and none of them
191
202
  // mentions a webhook URL, a destination, or any configuration value.
@@ -193,7 +204,10 @@ const SERVER_REMEDIATIONS = {
193
204
  // admin panel's Setup page. No MCP or CLI surface ever writes it.
194
205
  unattendedConsent: "turn on 'Allow unattended Epic Implementer runs' on the repository's Setup page in the Bridge admin panel, or run setup with --attended. If it is already on, contact Bridge support.",
195
206
  unattendedNotifyDefault: "supply a --policy-file declaring notify.webhook_url, or add an email address to the account so escalation alerts can be emailed, or run setup with --attended; an unattended run must have a working escalation channel, and notify.local_sink is not one.",
196
- repositoryWorkflowReadiness: "run `install-bridge conductor` so the review and conductor-CI workflows are confirmed on the default branch, or run setup with --attended.",
207
+ // BAPI-1359: the `repository-workflow-readiness` remedy is built by
208
+ // `repositoryWorkflowRemedy` (the shared formatter) from the SAME report, so it
209
+ // names the specific missing item. The generic "run `install-bridge
210
+ // conductor`" text was retired: no install step creates a migration-guard job.
197
211
  // BAPI-1135. Both are IMPORTED from the shared classifier rather than written
198
212
  // out here, so the remedy command has exactly one spelling across the refusal,
199
213
  // the readiness row, and the doctor — see `merge-execution-contract.ts`.
@@ -331,14 +345,31 @@ export function mapServerReadinessToChecks(readiness, failure = "unreachable") {
331
345
  label,
332
346
  status: outcome.status,
333
347
  detail: outcome.detail,
348
+ // A pass carries no remediation, and a `skip` carries one only when its
349
+ // outcome supplied one: the older-server default would misdescribe a fact
350
+ // that was deliberately not evaluated.
334
351
  ...(outcome.status === "pass"
335
352
  ? {}
336
- : { remediation: outcome.remediation ?? SERVER_REMEDIATIONS.olderServer }),
353
+ : outcome.status === "skip"
354
+ ? outcome.remediation === undefined
355
+ ? {}
356
+ : { remediation: outcome.remediation }
357
+ : { remediation: outcome.remediation ?? SERVER_REMEDIATIONS.olderServer }),
337
358
  });
338
359
  });
339
360
  // BAPI-1260 — appended ONLY when the server reported the block, so every
340
361
  // pre-BAPI-1260 report keeps exactly the descriptor set above.
341
- return [...described, ...admissionEvidenceChecks(readiness), ...genericDiagnosticChecks(readiness)];
362
+ return [
363
+ ...described,
364
+ ...admissionEvidenceChecks(readiness),
365
+ // BAPI-1390 — appended ONLY when the server reported a gate profile.
366
+ ...gateProfileChecks(readiness),
367
+ // BAPI-1405 — always three warn-only epic-branch CI rows. An older server
368
+ // that omits the block renders them as "Unreported — upgrade the server",
369
+ // never as "no warning".
370
+ ...epicBranchCiChecks(readiness),
371
+ ...genericDiagnosticChecks(readiness),
372
+ ];
342
373
  }
343
374
  function genericDiagnosticChecks(readiness) {
344
375
  return (readiness.diagnostics ?? []).map((diagnostic, index) => {
@@ -377,6 +408,190 @@ const ADMISSION_OWNER_REMEDIATION = {
377
408
  repository_admin: "a repository administrator resolves it (unattended consent is the 'Allow unattended Epic Implementer runs' toggle on the repository's Setup page in the Bridge admin panel); setup never grants it.",
378
409
  bridge_operations: "escalate to Bridge operations; this is not something an operator command can change.",
379
410
  };
411
+ /** The retired and current Bridge CI gate names, spelled once for the fixed remedies. */
412
+ const RETIRED_GATE_NAME = "conductor-ci / gate";
413
+ const CURRENT_GATE_NAME = "epic-implementer-ci / gate";
414
+ /**
415
+ * Fixed, per-reason remedies (BAPI-1360). Assembled from literals and the two
416
+ * hard-coded workflow paths only: a record's reason picks a template, and nothing
417
+ * from the payload is interpolated. `null` means "no specific remedy — use the
418
+ * owner's generic one".
419
+ */
420
+ function admissionReasonRemedy(record, readiness) {
421
+ const sup = readiness.supervisor;
422
+ const ciAbsent = sup.required_checks_ci_workflow_absent === true ||
423
+ (readiness.conductor_ci_workflow?.probe_succeeded === true &&
424
+ readiness.conductor_ci_workflow?.workflow_present === false);
425
+ const reviewAbsent = sup.required_checks_review_workflow_absent === true ||
426
+ (readiness.review_workflow?.probe_succeeded === true &&
427
+ readiness.review_workflow?.workflow_present === false);
428
+ switch (record.reason) {
429
+ case "review_workflow_unconfirmed":
430
+ return (`a repository administrator adds ${REVIEW_WORKFLOW_PATH} to the default branch, or repairs it so it ` +
431
+ "emits the run-id: and Reviewed-SHA: markers in its verdict comment; setup never grants it.");
432
+ case "conductor_ci_workflow_unconfirmed":
433
+ return (`a repository administrator adds ${CI_WORKFLOW_PATH} to the default branch, or repairs it (a ` +
434
+ "migration-guard job is required when the repository has alembic.ini); setup never grants it.");
435
+ case "required_checks_lane_workflow_absent": {
436
+ const files = [ciAbsent ? CI_WORKFLOW_PATH : null, reviewAbsent ? REVIEW_WORKFLOW_PATH : null].filter((file) => file !== null);
437
+ const named = files.length > 0 ? files.join(" and ") : `${CI_WORKFLOW_PATH} and ${REVIEW_WORKFLOW_PATH}`;
438
+ return (`a repository administrator adds ${named} to the default branch so the configured required ` +
439
+ `checks (such as ${CURRENT_GATE_NAME}) have a producer; setup never grants it.`);
440
+ }
441
+ case "required_check_retired_gate":
442
+ return (`a repository administrator replaces ${RETIRED_GATE_NAME} with ${CURRENT_GATE_NAME} in the plan's ` +
443
+ "required_checks, in the supervisor done-gate checks, and in branch protection." +
444
+ (ciAbsent ? ` Also add ${CI_WORKFLOW_PATH} to the default branch so ${CURRENT_GATE_NAME} has a producer.` : ""));
445
+ default:
446
+ return gateProfileReasonRemedy(record.reason, record.verdict, record.subject ?? null);
447
+ }
448
+ }
449
+ /** The provider-evidence window the server searches, spelled once for the remedies. */
450
+ const PROVIDER_EVIDENCE_WINDOW = "the last 12 default-branch commits or 8 merged pull-request heads from the last 90 days";
451
+ /**
452
+ * BAPI-1390 — fixed remedies for the four gate-profile reasons. Everything is a
453
+ * literal except the validated `subject`. `null` for any other reason, so an
454
+ * unknown reason falls back to the owner's generic remedy.
455
+ */
456
+ function gateProfileReasonRemedy(reason, verdict, subject) {
457
+ switch (reason) {
458
+ case "provider_check_unseen":
459
+ return (`a repository administrator confirms that the check ${quotedSubject(subject, "named in the gate profile")} ` +
460
+ `reports on the default branch — it was not seen on ${PROVIDER_EVIDENCE_WINDOW} — or edits the gate ` +
461
+ "profile; setup never grants it.");
462
+ case "provider_evidence_unavailable":
463
+ return (`Bridge could not read the provider's check history for ${quotedSubject(subject, "a gate-profile check")} ` +
464
+ `(${PROVIDER_EVIDENCE_WINDOW}), so it is unknown, not absent. Re-run readiness; if it persists, ` +
465
+ "escalate to Bridge operations.");
466
+ case "local_command_unqualified":
467
+ return verdict === "unable_to_verify"
468
+ ? `the qualification of the local gate command ${quotedSubject(subject, "in the gate profile")} could not ` +
469
+ "be read, so it is unknown, not qualified. Re-run readiness; if it persists, escalate to Bridge operations."
470
+ : `the local gate command ${quotedSubject(subject, "in the gate profile")} is not qualified for the current ` +
471
+ "gate-profile revision (unqualified or stale). Qualify it with " +
472
+ `\`${PRINTED_QUALIFY_GATE_COMMAND}\`, or have a repository administrator ` +
473
+ "edit the gate profile.";
474
+ case "migration_guard_unset":
475
+ return verdict === "unable_to_verify"
476
+ ? "Bridge could not confirm whether this repository has alembic.ini on its default branch, so it could " +
477
+ "not decide whether the gate profile needs a migration guard. Re-run readiness, or have a repository " +
478
+ "administrator set a migration guard in the gate profile."
479
+ : "this repository has alembic.ini on its default branch, so its gate profile must name a migration " +
480
+ "guard (a provider check or a local command). A repository administrator sets it in the gate " +
481
+ "profile; setup never grants it.";
482
+ default:
483
+ return null;
484
+ }
485
+ }
486
+ const GATE_PROFILE_WARNING_REMEDIATION = "no action is needed to launch; confirm the check reports once an epic branch runs CI.";
487
+ /**
488
+ * BAPI-1390 — the gate-profile row plus one warn row per warning. Appended
489
+ * ONLY when the server reported the block, so an older server's report is
490
+ * unchanged. The row is descriptive: the server's admission evidence is the
491
+ * authority, and the warning rows never hold a launch.
492
+ */
493
+ export function gateProfileChecks(readiness) {
494
+ const profile = readiness.gate_profile;
495
+ if (profile === undefined)
496
+ return [];
497
+ if (profile === null) {
498
+ return [
499
+ createReadinessCheckSafely({
500
+ id: "server.gate-profile",
501
+ source: "server",
502
+ label: "Gate profile",
503
+ status: "fail",
504
+ detail: "the server reported no gate profile, so the CI gate is unverified",
505
+ remediation: SERVER_REMEDIATIONS.olderServer,
506
+ }),
507
+ ];
508
+ }
509
+ const detail = describeGateProfile(profile);
510
+ let row;
511
+ if (profile.read_failed || profile.stored_malformed) {
512
+ row = createReadinessCheckSafely({
513
+ id: "server.gate-profile",
514
+ source: "server",
515
+ label: "Gate profile",
516
+ status: "fail",
517
+ detail: `${profile.read_failed ? "the stored gate profile could not be read" : "the stored gate profile is malformed"} — ${detail}`,
518
+ remediation: profile.read_failed
519
+ ? "re-run readiness; if it persists, escalate to Bridge operations."
520
+ : "a repository administrator re-saves the gate profile; setup never grants it.",
521
+ });
522
+ }
523
+ else if (profile.verdict === "denied" || profile.verdict === "unable_to_verify") {
524
+ row = createReadinessCheckSafely({
525
+ id: "server.gate-profile",
526
+ source: "server",
527
+ label: "Gate profile",
528
+ status: "fail",
529
+ detail: `${profile.verdict === "denied" ? "DENIED" : "could not be verified"}${profile.reason ? ` (${profile.reason})` : ""} — ${detail}`,
530
+ remediation: gateProfileReasonRemedy(profile.reason, profile.verdict, profile.subject) ??
531
+ (profile.verdict === "denied"
532
+ ? ADMISSION_OWNER_REMEDIATION.repository_admin
533
+ : ADMISSION_OWNER_REMEDIATION.bridge_operations),
534
+ });
535
+ }
536
+ else if (profile.verdict === "confirmed" && profile.kind !== "none") {
537
+ row = createReadinessCheckSafely({
538
+ id: "server.gate-profile",
539
+ source: "server",
540
+ label: "Gate profile",
541
+ status: "pass",
542
+ detail,
543
+ });
544
+ }
545
+ else {
546
+ row = createReadinessCheckSafely({
547
+ id: "server.gate-profile",
548
+ source: "server",
549
+ label: "Gate profile",
550
+ status: "warn",
551
+ detail,
552
+ remediation: profile.kind === "none"
553
+ ? "no action is needed to launch; to let child merges proceed without approval, a repository administrator adds checks to the gate profile."
554
+ : "a repository administrator confirms the gate profile; the server's launch evidence decides whether a run is held.",
555
+ });
556
+ }
557
+ const warnings = profile.warnings.map((warning, index) => createReadinessCheckSafely({
558
+ id: `server.gate-profile-warning-${index + 1}`,
559
+ source: "server",
560
+ label: "Gate profile warning",
561
+ status: "warn",
562
+ detail: describeGateProfileWarning(warning.code, warning.name),
563
+ remediation: GATE_PROFILE_WARNING_REMEDIATION,
564
+ }));
565
+ return [row, ...warnings];
566
+ }
567
+ /**
568
+ * BAPI-1405 — the three warn-only epic-branch CI rows as `server.*` checks:
569
+ * `server.effective-review-mode`, `server.epic-branch-ci-decision`, and
570
+ * `server.epic-branch-ci-coverage-warning`.
571
+ *
572
+ * Built from the SAME shared sections the install doctor renders, so the two
573
+ * surfaces cannot word one fact apart. Status is only ever `pass` or `warn`:
574
+ * these rows never fail, never hold a launch, and feed no verdict.
575
+ *
576
+ * Unlike `gateProfileChecks`, an absent or `null` block (older server) still
577
+ * yields the three rows, each `warn` with "Unreported — upgrade the server" and
578
+ * the upgrade remediation. Emitting nothing would read as "no warning", which
579
+ * is the wrong answer: the server simply did not say. Omitted and `null` render
580
+ * identically. The install doctor's own copies of these sections are filtered
581
+ * by `INSTALL_SECTION_IDS_OWNED_ELSEWHERE`, so each row still appears once.
582
+ */
583
+ export function epicBranchCiChecks(readiness) {
584
+ return epicBranchCiSections(readiness.epic_branch_ci).map((section) => createReadinessCheckSafely({
585
+ id: `server.${section.id}`,
586
+ source: "server",
587
+ label: section.label,
588
+ status: section.status === "ok" ? "pass" : "warn",
589
+ detail: section.detail,
590
+ ...(section.status === "ok" || section.remediation === undefined
591
+ ? {}
592
+ : { remediation: section.remediation }),
593
+ }));
594
+ }
380
595
  /**
381
596
  * Project the server's typed launch evidence (BAPI-1260) into `server.*`
382
597
  * checks, read-only.
@@ -405,13 +620,30 @@ export function admissionEvidenceChecks(readiness) {
405
620
  }),
406
621
  ];
407
622
  }
408
- return evidence.records.map((record) => admissionEvidenceCheck(record));
623
+ return evidence.records.map((record) => admissionEvidenceCheck(record, readiness));
409
624
  }
410
- function admissionEvidenceCheck(record) {
625
+ function admissionEvidenceCheck(record, readiness) {
411
626
  const slug = record.predicate.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
412
627
  const label = ADMISSION_EVIDENCE_LABELS[record.predicate] ?? "Launch evidence";
413
628
  const reason = record.reason ? ` (${record.reason})` : "";
414
- const remediation = ADMISSION_OWNER_REMEDIATION[record.next_action_owner ?? "operator"] ?? ADMISSION_OWNER_REMEDIATION.operator;
629
+ const remediation = admissionReasonRemedy(record, readiness) ??
630
+ ADMISSION_OWNER_REMEDIATION[record.next_action_owner ?? "operator"] ??
631
+ ADMISSION_OWNER_REMEDIATION.operator;
632
+ // BAPI-1360: the durable-actor handshake being unsupported is a known, current
633
+ // limitation, not a hold — it renders as a warning that says so. Every other
634
+ // predicate, and every other reason for this one, keeps its existing branch.
635
+ if (record.predicate === "durable_actor" && record.reason === "actor_handshake_unsupported") {
636
+ return createReadinessCheckSafely({
637
+ id: `server.admission-${slug}`,
638
+ source: "server",
639
+ label,
640
+ status: "warn",
641
+ detail: `not yet supported by this deploy${reason} — this does not hold a launch`,
642
+ // Fixed, non-escalating: the owner remedy would tell the operator to escalate
643
+ // for a limitation that blocks nothing.
644
+ remediation: "no action is needed to launch; this warning clears once the deploy supports the durable-actor handshake.",
645
+ });
646
+ }
415
647
  switch (record.verdict) {
416
648
  case "confirmed":
417
649
  return createReadinessCheckSafely({
@@ -503,7 +735,7 @@ function unattendedOutcomes(readiness) {
503
735
  ? {
504
736
  id: "unattended-consent",
505
737
  status: "pass",
506
- detail: "this repository has consented to unattended conductor runs",
738
+ detail: "this repository has consented to unattended Epic Implementer runs",
507
739
  }
508
740
  : {
509
741
  id: "unattended-consent",
@@ -540,13 +772,22 @@ function unattendedOutcomes(readiness) {
540
772
  ? {
541
773
  id: "repository-workflow-readiness",
542
774
  status: "pass",
543
- detail: "the review and conductor-CI workflows are both confirmed",
775
+ detail: "the review and epic-implementer-ci workflows are both confirmed",
544
776
  }
545
777
  : {
546
778
  id: "repository-workflow-readiness",
547
779
  status: "warn",
548
- detail: "the review and conductor-CI workflows are not both confirmed — an unattended auto-merging run will be refused",
549
- remediation: SERVER_REMEDIATIONS.repositoryWorkflowReadiness,
780
+ // BAPI-1360: name a confirmed-missing file separately from a file that
781
+ // could not be read; the remedy already differs by owner.
782
+ detail: readiness.review_workflow?.probe_succeeded === true &&
783
+ readiness.review_workflow?.workflow_present === false
784
+ ? `${REVIEW_WORKFLOW_PATH} is missing from the default branch — an unattended auto-merging run will be refused`
785
+ : !gateProfileWaivesCiWorkflow(readiness) &&
786
+ readiness.conductor_ci_workflow?.probe_succeeded === true &&
787
+ readiness.conductor_ci_workflow?.workflow_present === false
788
+ ? `${CI_WORKFLOW_PATH} is missing from the default branch — an unattended auto-merging run will be refused`
789
+ : "the review and epic-implementer-ci workflows are not both confirmed — an unattended auto-merging run will be refused",
790
+ remediation: gateProfileAwareRepositoryWorkflowRemedy(readiness),
550
791
  },
551
792
  ];
552
793
  }
@@ -555,6 +796,15 @@ function reviewPolicyOutcome(readiness) {
555
796
  const s = readiness.supervisor;
556
797
  const alignment = s.review_policy_alignment;
557
798
  if (alignment !== null) {
799
+ // BAPI-1405 — `repository_policy` requires no review verdict, so a divergent
800
+ // done-gate review signal is not a nag. `invalid` still warns.
801
+ if (alignment.status === "divergent" && effectiveReviewModeIsRepositoryPolicy(readiness)) {
802
+ return {
803
+ id: "review-policy",
804
+ status: "pass",
805
+ detail: "alignment not applicable: the effective review mode is 'repository_policy', which requires no review verdict",
806
+ };
807
+ }
558
808
  if (alignment.status === "divergent" || alignment.status === "invalid") {
559
809
  return {
560
810
  id: "review-policy",
@@ -677,6 +927,17 @@ function reconcilerOutcome(readiness) {
677
927
  remediation: SERVER_REMEDIATIONS.reconcilerUnreadable,
678
928
  };
679
929
  }
930
+ // BAPI-1360 — with no active run there is no progress to judge: the row is an
931
+ // explicit, remedy-free `skip`, not a stale-tick failure about a reconciler that
932
+ // has nothing to reconcile. ONLY a readable response with a strict zero count
933
+ // qualifies; a missing or malformed count keeps the stale/pass reading below.
934
+ if (r.liveness_readable === true && r.active_run_count === 0) {
935
+ return {
936
+ id: "reconciler-liveness",
937
+ status: "skip",
938
+ detail: "no active runs, so there is no run progress to check",
939
+ };
940
+ }
680
941
  if (!r.stale) {
681
942
  return {
682
943
  id: "reconciler-liveness",
@@ -726,14 +987,41 @@ function executorOutcome(readiness) {
726
987
  remediation: SERVER_REMEDIATIONS.executorStale,
727
988
  };
728
989
  }
990
+ // `ready === null`. BAPI-1360: with readable executor AND reconciler liveness and
991
+ // a strict zero active-run count, no executor being present is expected —
992
+ // `drive-epic` starts a run-scoped one — so it is a warning, not a failure.
993
+ if (readiness.reconciler.liveness_readable === true && readiness.reconciler.active_run_count === 0) {
994
+ return {
995
+ id: "executor-readiness",
996
+ status: "warn",
997
+ detail: "no executor was observed; `drive-epic` starts a run-scoped executor at launch, so a durable service is optional",
998
+ remediation: SERVER_REMEDIATIONS.executorNoneObservedNoRuns,
999
+ };
1000
+ }
729
1001
  return {
730
1002
  id: "executor-readiness",
731
1003
  status: "fail",
732
- detail: "never observed by the server",
1004
+ detail: "no executor was observed while runs are active or their count could not be read",
733
1005
  remediation: SERVER_REMEDIATIONS.executorNeverSeen,
734
1006
  };
735
1007
  }
736
- /** Review-workflow protocol facts. `null` means an older server, not health. */
1008
+ /**
1009
+ * BAPI-1390 — the repository-workflow remedy under the server's gate-profile
1010
+ * rule: with a STORED gate profile the CI lane file is not required, so the
1011
+ * remedy never names it. Without one, this is exactly
1012
+ * {@link repositoryWorkflowRemedy}.
1013
+ */
1014
+ export function gateProfileAwareRepositoryWorkflowRemedy(readiness) {
1015
+ return repositoryWorkflowRemedy(gateProfileWaivesCiWorkflow(readiness) ? { ...readiness, conductor_ci_workflow: null } : readiness);
1016
+ }
1017
+ /**
1018
+ * Review-workflow protocol facts. `null` means an older server, not health.
1019
+ *
1020
+ * BAPI-1390: the server now requires the review file only for a run whose review
1021
+ * policy source is `verdict_protocol`. This repository-scoped report carries no
1022
+ * run policy, so the row keeps its existing behavior; the per-run decision is the
1023
+ * server's (`unattended.repository_readiness_confirmed` and admission evidence).
1024
+ */
737
1025
  function reviewWorkflowOutcome(readiness) {
738
1026
  const w = readiness.review_workflow;
739
1027
  if (w === null) {
@@ -754,6 +1042,16 @@ function reviewWorkflowOutcome(readiness) {
754
1042
  remediation: SERVER_REMEDIATIONS.reviewWorkflowUnknown,
755
1043
  };
756
1044
  }
1045
+ // BAPI-1360 — a SUCCESSFUL probe that found no file is a confirmed absence, kept
1046
+ // apart from an unreadable file (Bridge operations) and from missing markers.
1047
+ if (w.workflow_present === false) {
1048
+ return {
1049
+ id: "review-workflow",
1050
+ status: "fail",
1051
+ detail: `${REVIEW_WORKFLOW_PATH} is missing from the default branch (confirmed)`,
1052
+ remediation: `a repository administrator adds ${REVIEW_WORKFLOW_PATH} to the default branch so it emits both the run id and the reviewed SHA the verdict protocol reads.`,
1053
+ };
1054
+ }
757
1055
  if (w.emits_run_id && w.emits_reviewed_sha) {
758
1056
  return {
759
1057
  id: "review-workflow",
@@ -776,11 +1074,21 @@ function reviewWorkflowOutcome(readiness) {
776
1074
  /** Conductor-CI migration-guard facts. `null` means an older server. */
777
1075
  function ciWorkflowOutcome(readiness) {
778
1076
  const c = readiness.conductor_ci_workflow;
1077
+ // BAPI-1390 — a STORED gate profile names its own checks and migration guard,
1078
+ // so the lane file is not required. A healthy file still reports a pass; any
1079
+ // other state is reported as not applicable rather than as a gap.
1080
+ if (gateProfileWaivesCiWorkflow(readiness) && !(c?.probe_succeeded === true && c.migration_guard_present === true)) {
1081
+ return {
1082
+ id: "conductor-ci-workflow",
1083
+ status: "skip",
1084
+ detail: `${CI_WORKFLOW_PATH} is not required: this repository has a stored gate profile that names its own checks`,
1085
+ };
1086
+ }
779
1087
  if (c === null) {
780
1088
  return {
781
1089
  id: "conductor-ci-workflow",
782
1090
  status: "fail",
783
- detail: "this Bridge API deploy did not report the conductor-CI workflow facts",
1091
+ detail: "this Bridge API deploy did not report the epic-implementer-ci workflow facts",
784
1092
  remediation: SERVER_REMEDIATIONS.olderServer,
785
1093
  };
786
1094
  }
@@ -794,18 +1102,51 @@ function ciWorkflowOutcome(readiness) {
794
1102
  remediation: SERVER_REMEDIATIONS.ciWorkflowUnknown,
795
1103
  };
796
1104
  }
797
- return c.migration_guard_present
798
- ? {
1105
+ // BAPI-1360 — confirmed absence of the file, not a missing migration guard.
1106
+ if (c.workflow_present === false) {
1107
+ return {
1108
+ id: "conductor-ci-workflow",
1109
+ status: "fail",
1110
+ detail: `${CI_WORKFLOW_PATH} is missing from the default branch (confirmed)`,
1111
+ remediation: `a repository administrator adds ${CI_WORKFLOW_PATH} to the default branch. ${ciWorkflowRemedy(c)}`,
1112
+ };
1113
+ }
1114
+ if (c.migration_guard_present) {
1115
+ return {
799
1116
  id: "conductor-ci-workflow",
800
1117
  status: "pass",
801
1118
  detail: "the default branch's conductor CI workflow runs the migration guard",
802
- }
803
- : {
1119
+ };
1120
+ }
1121
+ // BAPI-1359 — the guard is conditional on an Alembic chain. Three explicit
1122
+ // server answers, plus the older-server case where neither field is reported.
1123
+ if (c.migration_chain_probe_succeeded === true && c.migration_guard_required === false) {
1124
+ return {
804
1125
  id: "conductor-ci-workflow",
805
- status: "warn",
806
- detail: "the conductor CI workflow does not run the migration guard",
807
- remediation: SERVER_REMEDIATIONS.ciWorkflowMissingGuard,
1126
+ status: "pass",
1127
+ // States only what was observed. It does not say migrations are safe, or
1128
+ // that the repository has none.
1129
+ detail: "migration guard not required (no Alembic chain: no alembic.ini on the default branch)",
1130
+ };
1131
+ }
1132
+ if (c.migration_chain_probe_succeeded === false) {
1133
+ return {
1134
+ id: "conductor-ci-workflow",
1135
+ status: "fail",
1136
+ detail: "the Alembic check could not be read, so it is not known whether the conductor CI workflow needs the migration guard",
1137
+ remediation: ciWorkflowRemedy(c),
808
1138
  };
1139
+ }
1140
+ return {
1141
+ id: "conductor-ci-workflow",
1142
+ status: "warn",
1143
+ detail: c.migration_guard_required === true && c.migration_chain_probe_succeeded === true
1144
+ ? "the conductor CI workflow does not run the migration guard, which this repository requires because it has alembic.ini on its default branch"
1145
+ : // An older server (no new fields) or a partial report: keep today's
1146
+ // wording and today's behavior, and claim nothing about alembic.ini.
1147
+ "the conductor CI workflow does not run the migration guard",
1148
+ remediation: ciWorkflowRemedy(c),
1149
+ };
809
1150
  }
810
1151
  // ---------------------------------------------------------------------------
811
1152
  // Install-time section adapter
@@ -825,6 +1166,15 @@ const INSTALL_SECTION_IDS_OWNED_ELSEWHERE = new Set([
825
1166
  "supervisor-configuration",
826
1167
  "supervisor-required-checks",
827
1168
  "supervisor-review-alignment",
1169
+ // BAPI-1390 — owned by `gateProfileChecks`.
1170
+ "supervisor-gate-profile",
1171
+ "supervisor-gate-profile-epic-branch-unseen",
1172
+ "supervisor-gate-profile-epic-branch-search",
1173
+ "supervisor-gate-profile-warning",
1174
+ // BAPI-1405 — owned by `epicBranchCiChecks`.
1175
+ EPIC_BRANCH_CI_SECTION_IDS.reviewMode,
1176
+ EPIC_BRANCH_CI_SECTION_IDS.decision,
1177
+ EPIC_BRANCH_CI_SECTION_IDS.coverage,
828
1178
  "github-credentials",
829
1179
  "github-actions-permission",
830
1180
  "reconciler-liveness",
@@ -873,7 +1223,7 @@ export function mapInstallSectionsToReadinessChecks(sections) {
873
1223
  ? {}
874
1224
  : {
875
1225
  remediation: section.remediation ??
876
- "run `install conductor` for this repository to re-provision this capability.",
1226
+ `run \`${PRINTED_CONDUCTOR_INSTALL_COMMAND}\` for this repository to re-provision this capability. ${PRINTED_CHECK_NAME_INSTRUCTION}`,
877
1227
  }),
878
1228
  }));
879
1229
  }
@@ -903,13 +1253,16 @@ export async function collectConductorReadinessGate(deps) {
903
1253
  }
904
1254
  // A plane preflight was requested and could not be run. Not requesting one is
905
1255
  // not an unavailability — its prerequisites are still reported as unknowns.
906
- if (deps.runPlanePreflight && planeResult === null)
1256
+ if (deps.runPlanePreflight && deps.planeNotApplicable === undefined && planeResult === null) {
907
1257
  unavailableSources.push("plane");
1258
+ }
908
1259
  const checks = [
909
1260
  ...mapInstallStatusChecksToReadinessChecks((installReport?.legacyInstallChecks ?? null), installReport === null),
910
1261
  ...mapConductorDoctorReportToReadinessChecks(installReport?.legacyConductor ?? null),
911
1262
  ...mapInstallSectionsToReadinessChecks(installReport?.sections ?? null),
912
- ...mapPlanePreflightToReadinessChecks(planeResult),
1263
+ ...mapPlanePreflightToReadinessChecks(planeResult, deps.planeNotApplicable, {
1264
+ ...(deps.epicRunId !== undefined ? { epicRunId: deps.epicRunId } : {}),
1265
+ }),
913
1266
  ...mapServerReadinessToChecks(installReport?.readiness ?? null, classifyServerFailure(installReport)),
914
1267
  ];
915
1268
  // Validate the ASSEMBLED report, not just the pieces: an injected collector or
@@ -946,6 +1299,8 @@ async function runInstallDoctorSafely(deps) {
946
1299
  }
947
1300
  /** Run the plane preflight, containing any throw. */
948
1301
  async function runPlaneSafely(deps) {
1302
+ if (deps.planeNotApplicable !== undefined)
1303
+ return null;
949
1304
  if (!deps.runPlanePreflight)
950
1305
  return null;
951
1306
  try {