@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
@@ -32,6 +32,9 @@ export const GUIDED_REPAIR_VERBS = [
32
32
  "scope-reparse",
33
33
  "scope-catch-up",
34
34
  "dispatch-remediation",
35
+ // BAPI-1388 — ticket-addressed, pinned to the same version-and-head subject as
36
+ // `dispatch-remediation`, and consuming the binding ExplainRun renders.
37
+ "rerun-ci",
35
38
  ];
36
39
  /** A malformed or mismatched binding. The message never echoes the token. */
37
40
  export class GuidedBindingError extends Error {
@@ -57,6 +60,7 @@ const ALLOWED_KEYS = {
57
60
  "scope-reparse": ["scope_id", "lifecycle_state", "required_commit_sha"],
58
61
  "scope-catch-up": ["scope_id", "lifecycle_state", "required_commit_sha"],
59
62
  "dispatch-remediation": ["ticket_key", "expected_row_version", "expected_head_sha"],
63
+ "rerun-ci": ["ticket_key", "expected_row_version", "expected_head_sha"],
60
64
  };
61
65
  function invalid() {
62
66
  return new GuidedBindingError("--binding is not a valid guided-repair binding; nothing was sent. Copy the " +
@@ -148,7 +152,7 @@ export function decodeGuidedBinding(token) {
148
152
  throw invalid();
149
153
  }
150
154
  }
151
- else if (guidedVerb === "dispatch-remediation") {
155
+ else if (guidedVerb === "dispatch-remediation" || guidedVerb === "rerun-ci") {
152
156
  binding.ticketKey = boundedString(raw.ticket_key, 64);
153
157
  binding.expectedRowVersion = nonNegativeInt(raw.expected_row_version);
154
158
  binding.expectedHeadSha = optionalSha(raw.expected_head_sha);
@@ -202,7 +206,7 @@ export function guardForBinding(binding, requestId) {
202
206
  guard.block_reason = binding.blockReason;
203
207
  return guard;
204
208
  }
205
- if (binding.verb === "dispatch-remediation") {
209
+ if (binding.verb === "dispatch-remediation" || binding.verb === "rerun-ci") {
206
210
  return {
207
211
  request_id: requestId,
208
212
  epic_run_id: binding.epicRunId,
@@ -89,7 +89,7 @@ export const HOOK_RUNTIME_ACTIONS = {
89
89
  "from a persistent install.",
90
90
  "unqualified-runtime": "the hook runtime is not a qualified persistent installation (supported: a persistent npm " +
91
91
  `install of ${MCP_PACKAGE_NAME} ${HOOK_RUNTIME_COMPATIBLE_RANGE} on macOS or Linux with an even ` +
92
- `Node LTS line >= ${HOOK_RUNTIME_MIN_NODE_MAJOR}). See mcp_server/docs/CONDUCTOR.md.`,
92
+ `Node LTS line >= ${HOOK_RUNTIME_MIN_NODE_MAJOR}). See mcp_server/docs/EPIC-IMPLEMENTER.md.`,
93
93
  "node-unavailable": "`node` could not be launched from PATH, so the hook cannot run. Put a qualified Node on PATH " +
94
94
  "and re-run `epic-implementer doctor`.",
95
95
  "native-unavailable": "better-sqlite3 does not load for the hook's Node, so no event can be recorded. Reinstall the " +
@@ -36,7 +36,7 @@ import { buildConductorDoctorReport, formatConductorDoctorReport, } from "./doct
36
36
  import { inspectBridgeApiProfileToken, } from "../mcp-host-config.js";
37
37
  import { collectExecutorServiceDiagnostics, formatExecutorServiceDiagnosticsReport, } from "../doctor.js";
38
38
  import { collectInstallStatusChecks, formatInstallStatusReport, } from "../install-doctor.js";
39
- import { bringUpSafetySections, conductorHookTargetsSection, conductorLedgerLoadabilitySection, gitHubActionsMergeObservabilitySections, conductorProfileTokenSection, EPIC_IMPLEMENTER_PROFILE_TOKEN, detectConductorContext, executorObservationSection, executorProvisioningSection, executorServiceStateSection, githubActionsSection, mergeExecutionSections, githubCredentialsSection, inspectWorkflowPresence, reconcilerSection, supervisorSection, workflowSection, } from "./readiness-sections.js";
39
+ import { bringUpSafetySections, conductorHookTargetsSection, conductorLedgerLoadabilitySection, gitHubActionsMergeObservabilitySections, conductorProfileTokenSection, effectiveReviewModeIsRepositoryPolicy, epicBranchCiSections, EPIC_IMPLEMENTER_PROFILE_TOKEN, detectConductorContext, executorObservationSection, executorProvisioningSection, executorServiceStateSection, githubActionsSection, mergeExecutionSections, githubCredentialsSection, inspectWorkflowPresence, reconcilerSection, supervisorSection, workflowSection, } from "./readiness-sections.js";
40
40
  import { ConductorBridgeApiError, fetchConductorReadiness, } from "./bridge-api-client.js";
41
41
  /**
42
42
  * Run one optional collector, containing any throw.
@@ -155,6 +155,11 @@ export async function runConductorInstallDoctor(deps) {
155
155
  }
156
156
  if (readiness) {
157
157
  sections.push(...supervisorSection(readiness));
158
+ // BAPI-1405 — the three warn-only epic-branch CI rows, beside the gate
159
+ // profile they qualify. Never fatal, so they cannot change the exit code or
160
+ // the installer's pre-write abort. An older server renders them as
161
+ // "Unreported — upgrade the server", never as "no warning".
162
+ sections.push(...epicBranchCiSections(readiness.epic_branch_ci));
158
163
  sections.push(githubCredentialsSection(readiness));
159
164
  sections.push(githubActionsSection(readiness));
160
165
  // BAPI-1135 — the shared "Merge execution eligibility" section, immediately
@@ -166,7 +171,12 @@ export async function runConductorInstallDoctor(deps) {
166
171
  sections.push(...mergeExecutionSections(readiness));
167
172
  }
168
173
  const workflow = await inspectWorkflowPresence(deps.readWorkflowFile, deps.classifyWorkflowDrift);
169
- sections.push(workflowSection(workflow.presence, deps.reviewPolicySource, workflow.driftDetail));
174
+ // BAPI-1405 — when the server says the effective review mode is
175
+ // `repository_policy`, no review verdict is consumed, so the workflow row
176
+ // reports not-applicable instead of nagging. An older server keeps the
177
+ // caller's source exactly as before.
178
+ const effectiveReviewPolicySource = effectiveReviewModeIsRepositoryPolicy(readiness) ? "repository_policy" : deps.reviewPolicySource;
179
+ sections.push(workflowSection(workflow.presence, effectiveReviewPolicySource, workflow.driftDetail));
170
180
  if (readiness) {
171
181
  // BAPI-1153 — the "Bring-up safety" group, ahead of the repository-scoped
172
182
  // reconciler/executor story: these three decide whether a plane can drive a
@@ -357,6 +367,8 @@ mergeExecutionSections, databaseMigrationCurrentSection, singleTransitionAuthori
357
367
  // BAPI-1135 — both new section builders join the compatibility surface like
358
368
  // every other one, so an existing importer of this module does not learn a
359
369
  // second import path for them.
360
- gitHubActionsMergeObservabilitySections, } from "./readiness-sections.js";
370
+ gitHubActionsMergeObservabilitySections,
371
+ // BAPI-1405 — the epic-branch CI rows and their shared copy.
372
+ epicBranchCiSections, EPIC_BRANCH_CI_RERUN_INSTALL_ADVICE, EPIC_BRANCH_CI_SECTION_IDS, EPIC_BRANCH_CI_SECTION_LABELS, EPIC_BRANCH_CI_UNREPORTED_DETAIL, } from "./readiness-sections.js";
361
373
  // BAPI-1153 — the bring-up remediations the three sections above render.
362
374
  export { BRING_UP_RUNBOOK, BRING_UP_UNREPORTED_REMEDIATION, DATABASE_MIGRATION_BEHIND_REMEDIATION, DATABASE_MIGRATION_UNREADABLE_REMEDIATION, SECOND_RECONCILER_REMEDIATION, WORKER_HEARTBEAT_UNREADABLE_REMEDIATION, WORKER_NOT_PRESENT_REMEDIATION, } from "./bring-up-facts.js";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Complete `--json` output for CLI commands (BAPI-1397, F3).
3
+ *
4
+ * The CLI ends through `process.exit(cliExitCode)` in `index.ts` as soon as a
5
+ * command returns its exit code. On macOS a piped stdout is asynchronous, so a
6
+ * large document written with `console.log` could still be queued when the
7
+ * process exited — a 72 KB `epic-status --json` reached its reader truncated.
8
+ *
9
+ * {@link writeJsonDocument} prints the already-serialized document through the
10
+ * same sink as before (so its bytes and the tests that capture `console.log` are
11
+ * unchanged) and then waits until stdout has handed every queued byte to the
12
+ * operating system before the command returns. It never changes a payload.
13
+ */
14
+ /**
15
+ * Wait until `stream` has flushed everything written to it so far.
16
+ *
17
+ * A writable stream completes writes in order, so the callback of a zero-length
18
+ * write runs only after every earlier write has completed. Resolves `true` once
19
+ * flushed and `false` when the stream reports an error (for example a reader
20
+ * that closed the pipe). Never throws and never leaves an `error` listener
21
+ * behind, so a closed pipe cannot become an uncaught exception.
22
+ */
23
+ export function flushOutputStream(stream) {
24
+ return new Promise((resolve) => {
25
+ let settled = false;
26
+ const finish = (ok) => {
27
+ if (settled)
28
+ return;
29
+ settled = true;
30
+ stream.removeListener("error", onError);
31
+ resolve(ok);
32
+ };
33
+ const onError = () => finish(false);
34
+ stream.once("error", onError);
35
+ try {
36
+ stream.write("", (error) => finish(!error));
37
+ }
38
+ catch {
39
+ finish(false);
40
+ }
41
+ });
42
+ }
43
+ /** Flush the real process stdout. */
44
+ export function flushProcessStdout() {
45
+ return flushOutputStream(process.stdout);
46
+ }
47
+ /**
48
+ * Print one already-serialized JSON document and wait for stdout to drain.
49
+ *
50
+ * Resolves `true` when the whole document was handed to the operating system,
51
+ * `false` when the output could not complete. The text is printed exactly as
52
+ * given, so no command's JSON shape changes.
53
+ */
54
+ export async function writeJsonDocument(serialized, deps = {}) {
55
+ const log = deps.log ?? ((line) => console.log(line));
56
+ try {
57
+ log(serialized);
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ return (deps.flush ?? flushProcessStdout)();
63
+ }
@@ -23,14 +23,13 @@
23
23
  * inspected a local checkout to decide merge eligibility would be answering a
24
24
  * different question from the one the server's before-merge preflight answers.
25
25
  *
26
- * ## Deferred remedy
26
+ * ## Remedy command
27
27
  *
28
- * {@link MERGE_WORKFLOW_REMEDY_COMMAND} names the generate-and-PR command epic
29
- * child 4 ships. Until child 4 lands, a refusal may name a command that is not
30
- * yet an executable entry point on the operator's machine. That residual risk,
31
- * its three containments, and who owns escalation are recorded once, in the
28
+ * {@link MERGE_WORKFLOW_REMEDY_COMMAND} is the complete generate-and-PR command that
29
+ * BAPI-1136 shipped (BAPI-1376 made it a full launcher command). Its history and containments are recorded once, in the
32
30
  * Python module's docstring; this mirror does not restate them.
33
31
  */
32
+ import { MCP_PACKAGE_NAME } from "../mcp-identity.js";
34
33
  /** The one `RunPolicy.merge_execution_mode` value this contract governs. */
35
34
  export const MERGE_EXECUTION_MODE_GITHUB_ACTIONS = "github_actions";
36
35
  /**
@@ -80,7 +79,27 @@ export function mergeWorkflowVersionSupportsIntegrationActions(version) {
80
79
  INTEGRATION_ACTION_WORKFLOW_VERSIONS.includes(version));
81
80
  }
82
81
  /**
83
- * The single command that generates the merge workflow and opens its pull
84
- * request. Every refusal, remediation, and readiness line imports this constant.
82
+ * The dispatched subcommand that generates the merge workflow and opens its pull
83
+ * request. This is the part the CLI's own dispatcher (`index.ts`) matches on
84
+ * (`install`), not the launcher that starts it. The Python mirror derives its
85
+ * command head from this value.
85
86
  */
86
- export const MERGE_WORKFLOW_REMEDY_COMMAND = "install-bridge conductor --merge-workflow";
87
+ export const MERGE_WORKFLOW_SUBCOMMAND = "install conductor --merge-workflow";
88
+ /**
89
+ * The complete, copy-ready command that generates the merge workflow and opens
90
+ * its pull request. Every refusal, remediation, and readiness line imports this
91
+ * constant.
92
+ *
93
+ * The package name comes from `mcp-identity.ts`, the sole source of that
94
+ * literal (BAPI-806). That module is itself dependency-free, so this import can
95
+ * never close a cycle. The `<@org/team|@user>` placeholder is deliberately NOT a valid owner — the
96
+ * installer refuses it — so an operator must replace it before running.
97
+ */
98
+ export const MERGE_WORKFLOW_REMEDY_COMMAND = `npx -y ${MCP_PACKAGE_NAME} ${MERGE_WORKFLOW_SUBCOMMAND} --codeowner <@org/team|@user>`;
99
+ /**
100
+ * One reusable instruction to print beside {@link MERGE_WORKFLOW_REMEDY_COMMAND}.
101
+ * Bridge cannot infer who should review the workflow file, so the placeholder
102
+ * must be replaced by hand.
103
+ */
104
+ export const MERGE_WORKFLOW_CODEOWNER_INSTRUCTION = "Bridge cannot infer the code owner. Before running, replace <@org/team|@user> " +
105
+ "with the one @user or @org/team who should review the workflow file.";
@@ -31,7 +31,7 @@
31
31
  * not eligible: it is a workflow this build has never been taught to reason
32
32
  * about, and "newer is probably fine" is a guess about a merge actuator.
33
33
  */
34
- import { MERGE_WORKFLOW_CHILD_MERGE_COMPATIBLE_VERSIONS, MERGE_WORKFLOW_REMEDY_COMMAND, mergeWorkflowVersionSupportsChildMerge, } from "./merge-execution-contract.js";
34
+ import { MERGE_WORKFLOW_CHILD_MERGE_COMPATIBLE_VERSIONS, MERGE_WORKFLOW_CODEOWNER_INSTRUCTION, MERGE_WORKFLOW_REMEDY_COMMAND, mergeWorkflowVersionSupportsChildMerge, } from "./merge-execution-contract.js";
35
35
  /**
36
36
  * Remediation for a verdict that could not be established.
37
37
  *
@@ -51,7 +51,8 @@ export const MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION = "re-run once the Bridge
51
51
  * second way.
52
52
  */
53
53
  export const MERGE_EXECUTION_INELIGIBLE_REMEDIATION = `run \`${MERGE_WORKFLOW_REMEDY_COMMAND}\` to generate the merge workflow and open its ` +
54
- "pull request, then re-run once it is merged to the default branch.";
54
+ `pull request. ${MERGE_WORKFLOW_CODEOWNER_INSTRUCTION} ` +
55
+ "Then re-run once it is merged to the default branch.";
55
56
  function unverifiable(reason, detail) {
56
57
  return {
57
58
  status: "could_not_verify",
@@ -531,7 +531,7 @@ export async function observePrCiFromPollResponse(commitRef, pollResponse, deps
531
531
  function extractTicketKeyFromRef(headRef) {
532
532
  if (!headRef)
533
533
  return null;
534
- const match = /([A-Z][A-Z0-9]+-\d+)/i.exec(headRef);
534
+ const match = /([A-Z][A-Z0-9_]{0,19}-\d+)/i.exec(headRef);
535
535
  return match ? match[1].toUpperCase() : null;
536
536
  }
537
537
  /**
@@ -35,22 +35,31 @@
35
35
  import os from "node:os";
36
36
  import { readFile as fsReadFile, stat as fsStat } from "node:fs/promises";
37
37
  import { collectConductorReadinessGate, formatConductorReadinessGateReport, } from "./readiness.js";
38
- import { readinessEndpointEnvironments, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess } from "./bridge-api-client.js";
38
+ import { fetchEffectiveSupervisorSetup, readinessEndpointEnvironments, resolveConductorBridgeApiAccess, resolveEpicLaunchBridgeApiAccess, } from "./bridge-api-client.js";
39
+ import { createCommandDriftInspector } from "./command-drift-production.js";
40
+ import { flushProcessStdout, writeJsonDocument } from "./json-output.js";
39
41
  import { validateHttpBaseUrl } from "../base-url.js";
40
42
  import { claudeReviewWorkflowPath } from "../claude-review-workflow.js";
41
43
  import { runDefaultPlanePreflight } from "../plane/cli.js";
44
+ import { planeNotApplicableForTarget } from "../plane/endpoint.js";
45
+ import { resolvePlaneServerEndpoint } from "../plane/member-roster.js";
42
46
  /** Fixed notice emitted to stderr when a source could not be collected. */
43
47
  export const READINESS_COLLECTION_NOTICE = "one or more readiness sources could not be collected; the affected prerequisites are reported as failures.";
44
48
  /** Usage text for `epic-implementer readiness`. */
45
49
  export function getReadinessUsage() {
46
50
  return [
47
- "Usage: epic-implementer readiness [--json] [--no-deny-probe] [--base-url <url>]",
51
+ "Usage: epic-implementer readiness [--json] [--no-deny-probe] [--base-url <url>] [--epic-run-id <id>]",
48
52
  "",
49
53
  "Read-only ADVISORY report of every conductor prerequisite, consolidated from the",
50
54
  "Bridge install checklist, the epic-implementer doctor, the plane preflight, and the",
51
55
  "server conductor-readiness collector. Each prerequisite reports pass/warn/fail/skip",
52
56
  "and, on a failure, exactly ONE named remediation.",
53
57
  "",
58
+ "The claude-review workflow row is assessed for a NEW feature-branch run's first",
59
+ "approval: it is not applicable only when the server confirms no repository review",
60
+ "default is set. If that cannot be confirmed the row stays required. This report is",
61
+ "not a prediction or an authorization of any particular run.",
62
+ "",
54
63
  "Remediations are NOT run automatically. This command changes nothing: it starts no",
55
64
  "plane, approves no run, writes no file, and always exits 0 — `drive-epic` remains",
56
65
  "the only route-selection gate and the server-side admission the only hard refusal.",
@@ -61,6 +70,9 @@ export function getReadinessUsage() {
61
70
  " this command never spawns the deny-enforcement probe",
62
71
  " --base-url <url> Select a read-only Bridge API endpoint for this report;",
63
72
  " this does not authorize or supply a credential",
73
+ " --epic-run-id <id>",
74
+ " The epic run being attached. When the live plane is bound to",
75
+ " this run, its port and manifest rows pass with reason own_plane",
64
76
  " --help Print this usage message",
65
77
  ].join("\n");
66
78
  }
@@ -84,11 +96,64 @@ export function parseReadinessArgs(argv) {
84
96
  return { error: "--base-url requires a valid HTTP(S) URL." };
85
97
  parsed.baseUrl = valid.baseUrl;
86
98
  }
99
+ else if (token === "--epic-run-id") {
100
+ const raw = argv[++index];
101
+ if (!raw || raw.startsWith("--") || raw.trim() === "") {
102
+ return { error: "--epic-run-id requires an epic run id." };
103
+ }
104
+ parsed.epicRunId = raw.trim();
105
+ }
87
106
  else
88
107
  return { error: `Unknown argument "${token}". Run "epic-implementer readiness --help" for usage.` };
89
108
  }
90
109
  return parsed;
91
110
  }
111
+ const KNOWN_REVIEW_POLICY_SOURCES = [
112
+ "verdict_protocol",
113
+ "native_review_decision",
114
+ "none",
115
+ "repository_policy",
116
+ ];
117
+ /**
118
+ * Choose the review-policy source the workflow row is judged under (BAPI-1406).
119
+ *
120
+ * `repository_policy` is selected ONLY when a successful, parseable response
121
+ * explicitly confirms the repository's effective review default is unset: the
122
+ * `review_policy` key is present and `null`. A stored default is respected, and
123
+ * an unknown or missing source falls back to `verdict_protocol`. Every other
124
+ * outcome — no access, a read failure, a malformed body, a missing key (an older
125
+ * server), an unrecognised value — returns `verdict_protocol`, so the workflow
126
+ * row stays required rather than falsely healthy.
127
+ *
128
+ * Read-only: one GET through the existing supervisor-setup client. This answers
129
+ * "what would a new feature-branch run's first approval resolve to", which is a
130
+ * prospective assessment and not a prediction or authorization of any run.
131
+ */
132
+ export async function resolveReadinessReviewPolicySource(access, fetchImpl) {
133
+ if (access === null)
134
+ return "verdict_protocol";
135
+ try {
136
+ const setup = await fetchEffectiveSupervisorSetup(access, undefined, fetchImpl);
137
+ if (typeof setup !== "object" || setup === null || Array.isArray(setup)) {
138
+ return "verdict_protocol";
139
+ }
140
+ const record = setup;
141
+ if (!Object.prototype.hasOwnProperty.call(record, "review_policy")) {
142
+ return "verdict_protocol";
143
+ }
144
+ const reviewPolicy = record.review_policy;
145
+ if (reviewPolicy === null)
146
+ return "repository_policy";
147
+ if (typeof reviewPolicy !== "object" || Array.isArray(reviewPolicy)) {
148
+ return "verdict_protocol";
149
+ }
150
+ const source = reviewPolicy.source;
151
+ return KNOWN_REVIEW_POLICY_SOURCES.find((known) => known === source) ?? "verdict_protocol";
152
+ }
153
+ catch {
154
+ return "verdict_protocol";
155
+ }
156
+ }
92
157
  /**
93
158
  * Build the production report for `repoRoot`.
94
159
  *
@@ -96,17 +161,26 @@ export function parseReadinessArgs(argv) {
96
161
  * epic-implementer doctor's local inspections, and the same plane preflight
97
162
  * `plane up` and `drive-epic` run. Nothing is re-implemented here.
98
163
  */
99
- async function collectProductionReport(repoRoot, env, baseUrl) {
164
+ async function collectProductionReport(repoRoot, env, baseUrl, epicRunId) {
100
165
  const { reportEnv, resolverEnv } = readinessEndpointEnvironments(env, baseUrl);
101
166
  const accessResult = baseUrl === undefined
102
167
  ? await resolveConductorBridgeApiAccess({ env: reportEnv, cwd: repoRoot })
103
168
  : await resolveEpicLaunchBridgeApiAccess({ baseUrl }, { env: resolverEnv, cwd: repoRoot });
169
+ // BAPI-1360: the plane checks apply only when the selected access target names
170
+ // the local plane. Resolved from the SAME env the plane preflight reads, through
171
+ // the shared leaf predicate `drive-epic` uses, and never guessed: a target that
172
+ // is remote, or that cannot be resolved at all, is an explicit skip.
173
+ const planeNotApplicable = planeNotApplicableForTarget(resolvePlaneServerEndpoint(reportEnv), accessResult.ok ? accessResult.access.baseUrl : null);
104
174
  if (baseUrl !== undefined && !accessResult.ok) {
105
175
  // A refused override is never permission for a downstream doctor collector
106
176
  // to resolve credentials afresh from the general CLI environment.
107
- return collectConductorReadinessGate({ repoName: null });
177
+ return collectConductorReadinessGate({
178
+ repoName: null,
179
+ ...(planeNotApplicable !== null ? { planeNotApplicable } : {}),
180
+ });
108
181
  }
109
182
  const access = accessResult.ok ? accessResult.access : null;
183
+ const reviewPolicySource = await resolveReadinessReviewPolicySource(access);
110
184
  return collectConductorReadinessGate({
111
185
  // Never guessed: an unresolved identity is reported as such rather than
112
186
  // invented, and the repository ROOT is never placed in the report at all.
@@ -115,10 +189,11 @@ async function collectProductionReport(repoRoot, env, baseUrl) {
115
189
  access,
116
190
  accessError: accessResult.ok ? undefined : accessResult.error,
117
191
  fetch: (...args) => fetch(...args),
118
- // The consolidated gate reports workflow presence as a fact; it selects no
119
- // per-run review policy, so applicability is evaluated under the policy
120
- // that always consumes the workflow's verdict.
121
- reviewPolicySource: "verdict_protocol",
192
+ // BAPI-1406: the workflow row is judged under the review default the
193
+ // server CONFIRMS for a new feature-branch run's first approval, and under
194
+ // `verdict_protocol` whenever that cannot be established. This is a
195
+ // prospective assessment, not a prediction or authorization of any run.
196
+ reviewPolicySource,
122
197
  readWorkflowFile: () => fsReadFile(claudeReviewWorkflowPath(repoRoot), "utf-8"),
123
198
  projectRoot: repoRoot,
124
199
  installDoctorDeps: {
@@ -133,8 +208,21 @@ async function collectProductionReport(repoRoot, env, baseUrl) {
133
208
  },
134
209
  // The deny-enforcement probe is never spawned from this command; the check
135
210
  // is reported as an explicit skip naming `epic-implementer doctor` instead.
136
- conductorDoctorDeps: { env: reportEnv, skipDenyProbe: true },
137
- runPlanePreflight: () => runDefaultPlanePreflight(repoRoot, reportEnv),
211
+ conductorDoctorDeps: {
212
+ env: reportEnv,
213
+ skipDenyProbe: true,
214
+ // BAPI-1392: read-only, warn-only committed-command drift row.
215
+ inspectCommandDrift: createCommandDriftInspector({ env: reportEnv, cwd: repoRoot }),
216
+ },
217
+ // Provided ONLY for a target that names the local plane; otherwise the rows
218
+ // are skipped without any probe.
219
+ ...(planeNotApplicable === null
220
+ ? {
221
+ runPlanePreflight: () => runDefaultPlanePreflight(repoRoot, reportEnv),
222
+ // BAPI-1397 (F4): only for an applicable local plane.
223
+ ...(epicRunId !== undefined ? { epicRunId } : {}),
224
+ }
225
+ : { planeNotApplicable }),
138
226
  });
139
227
  }
140
228
  /**
@@ -159,7 +247,8 @@ export async function runConductorReadinessCommand(argv, deps = {}) {
159
247
  }
160
248
  const cwd = deps.cwd ?? process.cwd();
161
249
  const env = deps.env ?? process.env;
162
- const collect = deps.collect ?? ((repoRoot) => collectProductionReport(repoRoot, env, parsed.baseUrl));
250
+ const collect = deps.collect ??
251
+ ((repoRoot) => collectProductionReport(repoRoot, env, parsed.baseUrl, parsed.epicRunId));
163
252
  const report = await collect(cwd);
164
253
  // Fixed, sanitized, and stderr-only: never a credential, exception, header,
165
254
  // body, subprocess output, or absolute path. It fires on a COLLECTION gap,
@@ -167,7 +256,19 @@ export async function runConductorReadinessCommand(argv, deps = {}) {
167
256
  // every time an ordinary check fails teaches operators to ignore it.
168
257
  if (report.unavailableSources.length > 0)
169
258
  sinks.stderr(READINESS_COLLECTION_NOTICE);
170
- sinks.stdout(parsed.json ? JSON.stringify(report) : formatConductorReadinessGateReport(report));
259
+ if (parsed.json) {
260
+ // BAPI-1397 (F3): the document goes through the shared CLI writer, which waits
261
+ // for a piped stdout to drain. An injected sink is the caller's own boundary,
262
+ // so only the production stdout is flushed. Output completion never becomes a
263
+ // readiness verdict and never changes the advisory exit code.
264
+ await writeJsonDocument(JSON.stringify(report), {
265
+ log: sinks.stdout,
266
+ flush: deps.sinks ? async () => true : flushProcessStdout,
267
+ });
268
+ }
269
+ else {
270
+ sinks.stdout(formatConductorReadinessGateReport(report));
271
+ }
171
272
  // Advisory: the exit code is a constant, never derived from a check status.
172
273
  return 0;
173
274
  }