@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
@@ -33,6 +33,7 @@
33
33
  * The one exception is {@link inspectWorkflowPresence}, which awaits the
34
34
  * READ-ONLY reader seams its caller injects and never opens a path of its own.
35
35
  */
36
+ import { PRINTED_CONNECT_GITHUB_COMMAND } from "../printed-remedy-commands.js";
36
37
  import { MANAGED_HOOK_NAMES } from "./git-hooks.js";
37
38
  import { MCP_PACKAGE_NAME } from "../mcp-identity.js";
38
39
  import { CLAUDE_REVIEW_WORKFLOW_DRIFT_REMEDIATION, summarizeClaudeReviewWorkflowDrift, } from "../claude-review-workflow-drift.js";
@@ -41,7 +42,7 @@ import { classifyMergeExecutionEligibility, MERGE_EXECUTION_UNVERIFIABLE_REMEDIA
41
42
  import { MERGE_WORKFLOW_CHILD_MERGE_COMPATIBLE_VERSIONS, MERGE_WORKFLOW_PATH, mergeWorkflowVersionSupportsChildMerge, } from "./merge-execution-contract.js";
42
43
  import { classifyDatabaseMigration, classifySingleTransitionAuthority, classifyWorkerPresent, } from "./bring-up-facts.js";
43
44
  /** Operator runbook pointer surfaced beside the executor gap. */
44
- export const CONDUCTOR_OPERATOR_RUNBOOK_POINTER = "docs/claude/epic-implementer-v2-operator-runbook.md";
45
+ export const CONDUCTOR_OPERATOR_RUNBOOK_POINTER = "docs/epic-implementer/operator-runbook.md";
45
46
  /**
46
47
  * Capability-matrix guidance for the executor leg.
47
48
  *
@@ -105,6 +106,22 @@ export const REQUIRED_CHECKS_PRESENTATION_COPY = {
105
106
  remediation: "restore required-check verification (a readable default branch with static lane " +
106
107
  "workflows and a current Bridge API), or correct the required-check configuration.",
107
108
  },
109
+ // BAPI-1360 — file- and check-specific copy for the two proven, repository-admin
110
+ // causes of `unverifiable`. Fixed strings over the two hard-coded workflow paths
111
+ // and the two gate names; nothing from the payload is interpolated.
112
+ retiredGate: {
113
+ detail: "Could not verify — a configured required check is the retired gate " +
114
+ `conductor-ci / gate; ${REQUIRED_CHECKS_OPERATOR_HOLD}`,
115
+ remediation: "a repository administrator replaces conductor-ci / gate with epic-implementer-ci / gate in the " +
116
+ "plan's required_checks, the supervisor done-gate checks, and branch protection.",
117
+ },
118
+ laneAbsent: {
119
+ detail: "Could not verify — a lane workflow file is missing from the default branch, so a configured " +
120
+ `required check has no producer; ${REQUIRED_CHECKS_OPERATOR_HOLD}`,
121
+ remediation: "a repository administrator adds the missing lane workflow file(s) — " +
122
+ ".github/workflows/epic-implementer-ci.yml and/or .github/workflows/claude-review.yml — " +
123
+ "to the default branch so the configured required checks (such as epic-implementer-ci / gate) have a producer.",
124
+ },
108
125
  empty: {
109
126
  detail: "Could not verify — the required-check list is EMPTY, which is unsafe: the done gate " +
110
127
  `would pass unconditionally; ${REQUIRED_CHECKS_OPERATOR_HOLD}`,
@@ -155,6 +172,24 @@ export function presentRequiredChecks(supervisor) {
155
172
  ...REQUIRED_CHECKS_PRESENTATION_COPY.unknown_name,
156
173
  };
157
174
  }
175
+ // BAPI-1360 — optional bounded facts refine the generic unverifiable copy. An
176
+ // old server reports neither, and keeps the wording above. Retired gate ranks
177
+ // first, then a proven-absent lane; anything else stays generic.
178
+ if (supervisor.required_checks_retired_gate_configured === true) {
179
+ return {
180
+ outcome: "unverifiable",
181
+ satisfied: false,
182
+ ...REQUIRED_CHECKS_PRESENTATION_COPY.retiredGate,
183
+ };
184
+ }
185
+ if (supervisor.required_checks_ci_workflow_absent === true ||
186
+ supervisor.required_checks_review_workflow_absent === true) {
187
+ return {
188
+ outcome: "unverifiable",
189
+ satisfied: false,
190
+ ...REQUIRED_CHECKS_PRESENTATION_COPY.laneAbsent,
191
+ };
192
+ }
158
193
  return {
159
194
  outcome: "unverifiable",
160
195
  satisfied: false,
@@ -162,6 +197,355 @@ export function presentRequiredChecks(supervisor) {
162
197
  };
163
198
  }
164
199
  // ---------------------------------------------------------------------------
200
+ // Gate-profile wording (BAPI-1390) — shared with the readiness gate's rows
201
+ // ---------------------------------------------------------------------------
202
+ /**
203
+ * Render a server-validated check or command name for a remedy. The name is
204
+ * the ONLY payload value any remedy interpolates; backticks are swapped so it
205
+ * cannot close its own quoting.
206
+ */
207
+ export function quotedSubject(subject, fallback) {
208
+ if (subject === null)
209
+ return fallback;
210
+ const safe = subject.replace(/`/g, "'");
211
+ return `\`${safe}\``;
212
+ }
213
+ /** Human words for the profile kind. `null` is an unknown kind, never a held one. */
214
+ const GATE_PROFILE_KIND_LABELS = {
215
+ provider: "Provider",
216
+ local: "Local",
217
+ mixed: "Mixed",
218
+ none: "None",
219
+ };
220
+ /**
221
+ * One line describing a gate profile: kind, source, confirmation, revision,
222
+ * members, migration guard, and qualification state. Shared with the install
223
+ * doctor's section so the two surfaces cannot word the same profile apart.
224
+ * Only validated names are interpolated; everything else is a closed token.
225
+ */
226
+ export function describeGateProfile(profile) {
227
+ const kind = profile.kind === null ? "Unknown" : GATE_PROFILE_KIND_LABELS[profile.kind] ?? "Unknown";
228
+ const source = profile.source === "legacy"
229
+ ? "legacy: pre-profile done-gate checks treated as a confirmed provider profile"
230
+ : profile.source;
231
+ const parts = [
232
+ `${kind} gate profile (${source}, ${profile.confirmed ? "confirmed" : "unconfirmed"}, revision ${profile.revision})`,
233
+ ];
234
+ if (profile.provider_checks.length > 0) {
235
+ parts.push(`provider checks: ${profile.provider_checks
236
+ .map((c) => `${quotedSubject(c.name, "")} ${c.outcome === "evidence_unavailable" ? "unavailable" : c.outcome ?? "unknown"}`)
237
+ .join(", ")}`);
238
+ }
239
+ if (profile.local_commands.length > 0) {
240
+ parts.push(`local commands: ${profile.local_commands.map((c) => quotedSubject(c.name, "")).join(", ")}`);
241
+ }
242
+ parts.push(profile.migration_guard === null
243
+ ? profile.migration_guard_required === true
244
+ ? "migration guard: unset (required)"
245
+ : "migration guard: none"
246
+ : `migration guard: ${profile.migration_guard.kind}${profile.migration_guard.name === null ? "" : ` ${quotedSubject(profile.migration_guard.name, "")}`}`);
247
+ if (profile.qualification_state !== null)
248
+ parts.push(`qualification: ${profile.qualification_state}`);
249
+ if (profile.kind === "none") {
250
+ parts.push("unattended runs proceed, but every child merge waits for approval (`merge-child`)");
251
+ }
252
+ return parts.join("; ");
253
+ }
254
+ /** Fixed copy for a known warning code. Only the validated name is interpolated. */
255
+ export function describeGateProfileWarning(code, name) {
256
+ switch (code) {
257
+ case "provider_check_not_seen_on_epic_branches":
258
+ return `the check ${quotedSubject(name, "named in the gate profile")} has not been seen on an epic branch yet — this does not hold a launch`;
259
+ case "epic_branch_search_unavailable":
260
+ return "Bridge could not search epic branches for the gate-profile checks — this does not hold a launch";
261
+ default:
262
+ return "the gate profile reported a warning this CLI does not recognize — this does not hold a launch";
263
+ }
264
+ }
265
+ /** Section id for each known warn-only gate-profile code; anything else is generic. */
266
+ const GATE_PROFILE_WARNING_SECTION_IDS = {
267
+ provider_check_not_seen_on_epic_branches: "supervisor-gate-profile-epic-branch-unseen",
268
+ epic_branch_search_unavailable: "supervisor-gate-profile-epic-branch-search",
269
+ };
270
+ /**
271
+ * BAPI-1390 — the gate-profile section plus its warn-only rows. Emitted ONLY
272
+ * when the server reported a profile, so an older server's report is
273
+ * byte-identical. Never `fatal`: the server's admission evidence decides holds;
274
+ * these sections describe. Warnings are `degraded` with copy that says they do
275
+ * not hold a launch, one section per known code (names joined) plus one
276
+ * generic section for codes this client does not recognize.
277
+ */
278
+ export function gateProfileSections(profile) {
279
+ if (profile === undefined)
280
+ return [];
281
+ if (profile === null) {
282
+ return [
283
+ {
284
+ id: "supervisor-gate-profile",
285
+ label: "Supervisor gate profile",
286
+ status: "degraded",
287
+ detail: "the server reported no gate profile, so the CI gate is unverified",
288
+ remediation: "upgrade the Bridge API deploy so it reports the gate profile.",
289
+ },
290
+ ];
291
+ }
292
+ const detail = describeGateProfile(profile);
293
+ const held = profile.read_failed ||
294
+ profile.stored_malformed ||
295
+ profile.verdict === "denied" ||
296
+ profile.verdict === "unable_to_verify";
297
+ const sections = [];
298
+ if (held) {
299
+ sections.push({
300
+ id: "supervisor-gate-profile",
301
+ label: "Supervisor gate profile",
302
+ status: "degraded",
303
+ detail: profile.read_failed
304
+ ? `the stored gate profile could not be read — ${detail}`
305
+ : profile.stored_malformed
306
+ ? `the stored gate profile is malformed — ${detail}`
307
+ : `${profile.verdict === "denied" ? "denied" : "could not be verified"}${profile.reason ? ` (${profile.reason})` : ""} — ${detail}`,
308
+ remediation: "a repository administrator reviews the gate profile on the repository's Setup page; " +
309
+ "the server's launch evidence names the specific item.",
310
+ });
311
+ }
312
+ else if (profile.verdict === "confirmed" && profile.kind !== "none") {
313
+ sections.push({ id: "supervisor-gate-profile", label: "Supervisor gate profile", status: "ok", detail });
314
+ }
315
+ else {
316
+ sections.push({
317
+ id: "supervisor-gate-profile",
318
+ label: "Supervisor gate profile",
319
+ status: "degraded",
320
+ detail,
321
+ remediation: profile.kind === "none"
322
+ ? "no action is needed to launch; to let child merges proceed without approval, add checks to the gate profile."
323
+ : "a repository administrator confirms the gate profile; the server's launch evidence decides whether a run is held.",
324
+ });
325
+ }
326
+ const grouped = new Map();
327
+ for (const warning of profile.warnings) {
328
+ const id = GATE_PROFILE_WARNING_SECTION_IDS[warning.code] ?? "supervisor-gate-profile-warning";
329
+ const entry = grouped.get(id) ?? { code: warning.code, names: [] };
330
+ entry.names.push(warning.name);
331
+ grouped.set(id, entry);
332
+ }
333
+ for (const [id, { code, names }] of grouped) {
334
+ const named = names.filter((name) => name !== null);
335
+ sections.push({
336
+ id,
337
+ label: "Supervisor gate profile warning",
338
+ status: "degraded",
339
+ detail: code === "provider_check_not_seen_on_epic_branches" && named.length > 1
340
+ ? `the checks ${named.map((name) => quotedSubject(name, "")).join(", ")} have not been seen on an epic branch yet — this does not hold a launch`
341
+ : describeGateProfileWarning(code, named[0] ?? null),
342
+ remediation: "no action is needed to launch; this is a warning only.",
343
+ });
344
+ }
345
+ return sections;
346
+ }
347
+ // ---------------------------------------------------------------------------
348
+ // BAPI-1405 — epic-branch CI rows (warn-only)
349
+ // ---------------------------------------------------------------------------
350
+ /** Stable section ids for the three epic-branch CI rows. */
351
+ export const EPIC_BRANCH_CI_SECTION_IDS = {
352
+ reviewMode: "effective-review-mode",
353
+ decision: "epic-branch-ci-decision",
354
+ coverage: "epic-branch-ci-coverage-warning",
355
+ };
356
+ /** Row labels, shared by readiness and the install doctor. */
357
+ export const EPIC_BRANCH_CI_SECTION_LABELS = {
358
+ reviewMode: "Effective review mode",
359
+ decision: "Epic-branch CI decision",
360
+ coverage: "Epic-branch CI coverage warning",
361
+ };
362
+ /** What every row says when the server did not report the block. Never "no warning". */
363
+ export const EPIC_BRANCH_CI_UNREPORTED_DETAIL = "Unreported — upgrade the server";
364
+ /** The one remediation an unreported block carries. */
365
+ export const EPIC_BRANCH_CI_UNREPORTED_REMEDIATION = "upgrade the Bridge API deploy so it reports epic-branch CI facts; this does not hold a launch.";
366
+ /** The single next step for evidence that may be out of date or uncertain. */
367
+ export const EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE = "Rerun install after workflow or ruleset changes.";
368
+ /** Plain words for each effective review mode. */
369
+ const EFFECTIVE_REVIEW_MODE_WORDS = {
370
+ repository_policy: "repository_policy — no review verdict is required for epic-branch merges",
371
+ verdict_protocol: "verdict_protocol — child merges wait for the claude-review verdict",
372
+ native_review_decision: "native_review_decision — child merges wait for GitHub's own review decision",
373
+ none: "none — no review signal gates child merges",
374
+ };
375
+ /** Plain words for each known no-CI reason. An unknown reason renders generically. */
376
+ function describeNoCiReason(reason) {
377
+ switch (reason) {
378
+ case "gate_profile_none":
379
+ return "the gate profile is `none`, so no CI check gates child merges";
380
+ case "gate_profile_local_only":
381
+ return "the gate is local-only, so on a feature-branch run no provider check runs on epic-branch PRs";
382
+ case "triggers_do_not_cover_epic":
383
+ return "the evidence recorded at install says no pull_request trigger covers epic/**";
384
+ default:
385
+ return `the server reported a reason this CLI does not recognize (${reason})`;
386
+ }
387
+ }
388
+ /** One next step per known no-CI reason. */
389
+ function noCiReasonNextStep(reason) {
390
+ switch (reason) {
391
+ case "gate_profile_none":
392
+ return "add provider checks to the gate profile";
393
+ case "gate_profile_local_only":
394
+ return "add a provider check to the gate profile if epic-branch PRs should run CI";
395
+ case "triggers_do_not_cover_epic":
396
+ return "add epic/** to your CI workflow's pull_request branches filter, then rerun install";
397
+ default:
398
+ return "upgrade this CLI to read the server's reason";
399
+ }
400
+ }
401
+ function yesNoUnknown(value) {
402
+ return value === null ? "could not tell" : value ? "yes" : "no";
403
+ }
404
+ /**
405
+ * The compact "recorded at install" evidence summary. Trigger coverage is
406
+ * worded as coverage, never as proof that CI runs.
407
+ */
408
+ export function describeEpicBranchCiEvidence(evidence) {
409
+ return ("recorded at install: " +
410
+ `pull_request trigger covers epic/** ${yesNoUnknown(evidence.workflow_triggers_cover_epic)}; ` +
411
+ `approvals apply on epic/** ${yesNoUnknown(evidence.required_approvals_apply_to_epic)}; ` +
412
+ `observation quality ${evidence.observation_quality === "could_not_tell" ? "could not tell" : evidence.observation_quality}; ` +
413
+ `exemption outcome ${evidence.exemption_outcome === "not_exempt" ? "not exempt" : evidence.exemption_outcome}`);
414
+ }
415
+ /**
416
+ * One reason per "could not tell" in the evidence, each in plain words. Empty
417
+ * when nothing is uncertain. The shared next step is
418
+ * {@link EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE}.
419
+ */
420
+ export function epicBranchCiEvidenceUnknowns(evidence) {
421
+ const unknowns = [];
422
+ if (evidence.workflow_triggers_cover_epic === null) {
423
+ unknowns.push("trigger coverage could not tell: a workflow was not read confidently");
424
+ }
425
+ if (evidence.required_approvals_apply_to_epic === null) {
426
+ unknowns.push("approvals could not tell: the epic-branch rules were not readable");
427
+ }
428
+ if (evidence.observation_quality === "could_not_tell" &&
429
+ evidence.workflow_triggers_cover_epic !== null &&
430
+ evidence.required_approvals_apply_to_epic !== null) {
431
+ unknowns.push("observation quality could not tell: a finding was not confirmed");
432
+ }
433
+ if (evidence.exemption_outcome === "unknown") {
434
+ unknowns.push("exemption could not tell: the approval exemption was not confirmed");
435
+ }
436
+ return unknowns;
437
+ }
438
+ function effectiveReviewModeSection(block) {
439
+ const mode = block.effective_review_mode;
440
+ if (mode === null) {
441
+ return {
442
+ id: EPIC_BRANCH_CI_SECTION_IDS.reviewMode,
443
+ label: EPIC_BRANCH_CI_SECTION_LABELS.reviewMode,
444
+ status: "degraded",
445
+ detail: "the server reported a review mode this CLI does not recognize",
446
+ remediation: "upgrade this CLI so it can read the review mode; this does not hold a launch.",
447
+ };
448
+ }
449
+ return {
450
+ id: EPIC_BRANCH_CI_SECTION_IDS.reviewMode,
451
+ label: EPIC_BRANCH_CI_SECTION_LABELS.reviewMode,
452
+ status: "ok",
453
+ detail: EFFECTIVE_REVIEW_MODE_WORDS[mode],
454
+ };
455
+ }
456
+ function epicBranchCiDecisionSection(block) {
457
+ const choice = block.decision === "include"
458
+ ? "Include — child merges wait for the epic-branch CI checks"
459
+ : block.decision === "decline"
460
+ ? "Decline — child merges do not wait for CI checks on the epic branch; problems surface at the integration PR"
461
+ : "Not recorded — behaves like include";
462
+ const evidence = block.evidence;
463
+ const detail = evidence === null
464
+ ? `${choice}; no evidence was recorded at install`
465
+ : `${choice}; ${describeEpicBranchCiEvidence(evidence)}`;
466
+ const unknowns = evidence === null ? [] : epicBranchCiEvidenceUnknowns(evidence);
467
+ if (unknowns.length > 0) {
468
+ return {
469
+ id: EPIC_BRANCH_CI_SECTION_IDS.decision,
470
+ label: EPIC_BRANCH_CI_SECTION_LABELS.decision,
471
+ status: "degraded",
472
+ detail,
473
+ remediation: `${unknowns.join("; ")}. ${EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE} This does not hold a launch.`,
474
+ };
475
+ }
476
+ return {
477
+ id: EPIC_BRANCH_CI_SECTION_IDS.decision,
478
+ label: EPIC_BRANCH_CI_SECTION_LABELS.decision,
479
+ status: "ok",
480
+ detail: block.decision === null
481
+ ? `${detail}; run install to record a decision`
482
+ : evidence === null
483
+ ? detail
484
+ : `${detail}. ${EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE}`,
485
+ };
486
+ }
487
+ function epicBranchCiCoverageSection(block) {
488
+ const reasons = block.no_ci_reasons;
489
+ if (!block.no_ci_warning && reasons.length === 0) {
490
+ const triggers = block.evidence?.workflow_triggers_cover_epic ?? null;
491
+ return {
492
+ id: EPIC_BRANCH_CI_SECTION_IDS.coverage,
493
+ label: EPIC_BRANCH_CI_SECTION_LABELS.coverage,
494
+ status: "ok",
495
+ detail: triggers === null
496
+ ? "no warning from the gate profile; trigger coverage of epic/** was not recorded or could not be told at install"
497
+ : "no warning: the gate profile and the evidence recorded at install give no reason to expect epic-branch PRs to skip CI (trigger coverage does not guarantee CI runs)",
498
+ };
499
+ }
500
+ const described = reasons.map(describeNoCiReason);
501
+ return {
502
+ id: EPIC_BRANCH_CI_SECTION_IDS.coverage,
503
+ label: EPIC_BRANCH_CI_SECTION_LABELS.coverage,
504
+ status: "degraded",
505
+ detail: described.length === 0
506
+ ? "Warning: epic-branch PRs may run no CI (the server gave no reason)"
507
+ : `Warning: epic-branch PRs may run no CI — ${described.join("; ")}`,
508
+ remediation: (reasons.length === 0
509
+ ? "review the gate profile and your CI workflow triggers"
510
+ : reasons.map(noCiReasonNextStep).join("; ")) + ". This warning does not hold a launch.",
511
+ };
512
+ }
513
+ /**
514
+ * BAPI-1405 — the three warn-only epic-branch CI rows: **Effective review
515
+ * mode**, **Epic-branch CI decision**, and **Epic-branch CI coverage warning**.
516
+ *
517
+ * Always three rows, so an older server is visible: an absent or `null` block
518
+ * renders every row as {@link EPIC_BRANCH_CI_UNREPORTED_DETAIL}, never as "no
519
+ * warning". Never `fatal`: none of these rows may change an admission verdict,
520
+ * a readiness verdict, or an exit status. Status is carried by words in the
521
+ * detail as well as by the status field, never by color alone.
522
+ */
523
+ export function epicBranchCiSections(block) {
524
+ if (block === undefined || block === null) {
525
+ return ["reviewMode", "decision", "coverage"].map((key) => ({
526
+ id: EPIC_BRANCH_CI_SECTION_IDS[key],
527
+ label: EPIC_BRANCH_CI_SECTION_LABELS[key],
528
+ status: "degraded",
529
+ detail: EPIC_BRANCH_CI_UNREPORTED_DETAIL,
530
+ remediation: EPIC_BRANCH_CI_UNREPORTED_REMEDIATION,
531
+ }));
532
+ }
533
+ return [
534
+ effectiveReviewModeSection(block),
535
+ epicBranchCiDecisionSection(block),
536
+ epicBranchCiCoverageSection(block),
537
+ ];
538
+ }
539
+ /**
540
+ * True when the server says the effective review mode is `repository_policy`,
541
+ * which requires no review verdict, so the claude-review workflow and the
542
+ * review-policy alignment rows have nothing to nag about. Absent block → false
543
+ * (older behavior is kept exactly).
544
+ */
545
+ export function effectiveReviewModeIsRepositoryPolicy(readiness) {
546
+ return readiness?.epic_branch_ci?.effective_review_mode === "repository_policy";
547
+ }
548
+ // ---------------------------------------------------------------------------
165
549
  // Section builders
166
550
  // ---------------------------------------------------------------------------
167
551
  export function supervisorSection(readiness) {
@@ -218,7 +602,18 @@ export function supervisorSection(readiness) {
218
602
  // manufacture a finding out of a server that never answered.
219
603
  const alignment = s.review_policy_alignment;
220
604
  if (alignment !== null) {
221
- if (alignment.status === "divergent" || alignment.status === "invalid") {
605
+ if (alignment.status === "divergent" && effectiveReviewModeIsRepositoryPolicy(readiness)) {
606
+ // BAPI-1405 — `repository_policy` requires no review verdict, so a
607
+ // divergent done-gate review signal is not a nag here. `invalid` still
608
+ // warns: an unreadable review configuration is its own problem.
609
+ sections.push({
610
+ id: "supervisor-review-alignment",
611
+ label: "Supervisor review-policy alignment",
612
+ status: "ok",
613
+ detail: "not applicable: the effective review mode is 'repository_policy', which requires no review verdict",
614
+ });
615
+ }
616
+ else if (alignment.status === "divergent" || alignment.status === "invalid") {
222
617
  sections.push({
223
618
  id: "supervisor-review-alignment",
224
619
  label: "Supervisor review-policy alignment",
@@ -244,6 +639,8 @@ export function supervisorSection(readiness) {
244
639
  });
245
640
  }
246
641
  }
642
+ // BAPI-1390 — nothing at all when the server did not report a profile.
643
+ sections.push(...gateProfileSections(readiness.gate_profile));
247
644
  return sections;
248
645
  }
249
646
  export function githubCredentialsSection(readiness) {
@@ -268,7 +665,7 @@ export function githubCredentialsSection(readiness) {
268
665
  detail: g.credentials_readable
269
666
  ? `incomplete: ${missing} unresolved`
270
667
  : "no GitHub credential row for this repository",
271
- remediation: "connect GitHub from the Bridge setup UI (`install-bridge connect-github`).",
668
+ remediation: `connect GitHub from the Bridge setup UI or run \`${PRINTED_CONNECT_GITHUB_COMMAND}\`.`,
272
669
  };
273
670
  }
274
671
  export function githubActionsSection(readiness) {
@@ -396,7 +793,7 @@ export function reconcilerSection(readiness) {
396
793
  // remediation at all — so a report could name a gap with no next step.
397
794
  return {
398
795
  id: "reconciler-liveness",
399
- label: "Reconciler tick liveness",
796
+ label: "Active-run progress",
400
797
  status: "degraded",
401
798
  detail: "liveness could not be read",
402
799
  remediation: "retry once the Bridge API is reachable; reconciler liveness is unknown here, " +
@@ -406,19 +803,19 @@ export function reconcilerSection(readiness) {
406
803
  if (!r.stale) {
407
804
  return {
408
805
  id: "reconciler-liveness",
409
- label: "Reconciler tick liveness",
806
+ label: "Active-run progress",
410
807
  status: "ok",
411
808
  detail: `last tick ${r.last_tick_age_seconds}s ago across ${r.active_run_count} active run(s)`,
412
809
  };
413
810
  }
414
811
  return {
415
812
  id: "reconciler-liveness",
416
- label: "Reconciler tick liveness",
813
+ label: "Active-run progress",
417
814
  status: "degraded",
418
815
  detail: r.last_tick_at === null
419
816
  ? "no reconciler tick recorded for this repository"
420
817
  : `last tick ${r.last_tick_age_seconds}s ago (threshold ${readiness.thresholds.reconciler_stale_after_seconds}s)`,
421
- remediation: "start the reconciler (`epic-implementer epic-tick` schedule) for this repository.",
818
+ remediation: "check the worker first (`server.worker-present`), then inspect the stalled run with `epic-implementer epic-status`.",
422
819
  };
423
820
  }
424
821
  // ---------------------------------------------------------------------------
@@ -491,7 +888,9 @@ export function workflowSection(presence, reviewPolicySource, driftDetail) {
491
888
  // never `absent`, and the policy-applicability branch below does not apply
492
889
  // (a stale workflow only matters to a policy that consumes its verdict, and
493
890
  // the caller only classifies drift when it does).
494
- if (presence === "drifted") {
891
+ // BAPI-1405: under `repository_policy` no verdict is consumed, so a drifted
892
+ // file falls through to the not-applicable branch below instead of nagging.
893
+ if (presence === "drifted" && reviewPolicySource !== "repository_policy") {
495
894
  return {
496
895
  id: "claude-review-workflow",
497
896
  label: "claude-review workflow",
@@ -505,7 +904,9 @@ export function workflowSection(presence, reviewPolicySource, driftDetail) {
505
904
  // review decision does not need the Claude workflow at all, so its absence is
506
905
  // not a gap. Reporting it as degraded there would train operators to ignore
507
906
  // this section.
508
- if (reviewPolicySource === "native_review_decision" || reviewPolicySource === "none") {
907
+ if (reviewPolicySource === "native_review_decision" ||
908
+ reviewPolicySource === "none" ||
909
+ reviewPolicySource === "repository_policy") {
509
910
  return {
510
911
  id: "claude-review-workflow",
511
912
  label: "claude-review workflow",