@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.
- package/README.md +421 -83
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +70 -16
- package/build/agent-launchers/executor-adapter.js +7 -4
- package/build/agents.generated.js +22 -2
- package/build/bitbucket-workspace-remote.js +136 -0
- package/build/bootstrap-project-identity.js +24 -0
- package/build/bounded-wait.js +67 -10
- package/build/bridge-permissions.js +829 -0
- package/build/claude-settings.js +18 -0
- package/build/cli-help.js +21 -0
- package/build/command-assets-doctor.js +24 -70
- package/build/command-provisioning.js +29 -5
- package/build/commands.generated.js +12 -12
- package/build/conductor/bridge-api-client.js +857 -1
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/command-drift-production.js +92 -0
- package/build/conductor/command-drift.js +89 -0
- package/build/conductor/doctor.js +72 -29
- package/build/conductor/epic-branch-ci-inspection.js +1375 -0
- package/build/conductor/epic-implementer-cli.js +98 -28
- package/build/conductor/errors.js +4 -3
- package/build/conductor/gate-discovery.js +890 -0
- package/build/conductor/guided-repair.js +6 -2
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/install-doctor.js +15 -3
- package/build/conductor/json-output.js +63 -0
- package/build/conductor/merge-execution-contract.js +27 -8
- package/build/conductor/merge-execution-eligibility.js +3 -2
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +113 -12
- package/build/conductor/readiness-sections.js +410 -9
- package/build/conductor/readiness.js +388 -33
- package/build/conductor/recovery-cli.js +310 -12
- package/build/conductor/recovery-operations.js +167 -3
- package/build/conductor/repair-contract.js +54 -3
- package/build/conductor/repository-workflow-remedy.js +125 -0
- package/build/conductor/tools.js +2 -2
- package/build/conductor-bundle-artifacts.js +8 -0
- package/build/connect-bitbucket-api.js +41 -6
- package/build/connect-bitbucket.js +106 -33
- package/build/credential-store.js +207 -47
- package/build/docs.generated.js +62 -3
- package/build/doctor.js +92 -1
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +129 -61
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-coherence-input.test-fixtures.js +4 -3
- package/build/epic-implementer-bin.js +1379 -28
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/epic-workarounds-path.js +39 -0
- package/build/executor/cli.js +33 -2
- package/build/executor/completion-contract.js +66 -10
- package/build/executor/completion-supervision.js +4 -0
- package/build/executor/deps.js +4 -1
- package/build/executor/env.js +19 -0
- package/build/executor/gate-qualification-job.js +499 -0
- package/build/executor/guarded-fast-forward-push.js +129 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +20 -0
- package/build/executor/integration-job.js +122 -8
- package/build/executor/job-errors.js +33 -1
- package/build/executor/job-runner.js +813 -87
- package/build/executor/likely-tests.js +290 -0
- package/build/executor/live-worker-registry.js +1 -1
- package/build/executor/observation.js +37 -3
- package/build/executor/packaged-recipe.js +106 -0
- package/build/executor/permissions.js +115 -28
- package/build/executor/platform.js +1 -1
- package/build/executor/preflight.js +112 -26
- package/build/executor/process-containment.js +36 -12
- package/build/executor/process.js +15 -7
- package/build/executor/prompt-spec.js +33 -1
- package/build/executor/recently-failed-tests-file.js +71 -0
- package/build/executor/reconcile-overlap-job.js +41 -23
- package/build/executor/required-verification.js +1 -1
- package/build/executor/required-work-ledger.js +111 -18
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/retirement.js +69 -0
- package/build/executor/runner.js +122 -13
- package/build/executor/service-unit.js +7 -1
- package/build/executor/test-clock.js +15 -0
- package/build/executor/types.js +32 -0
- package/build/executor/viewer-tabs.js +58 -8
- package/build/executor/worker-command.js +23 -7
- package/build/executor/worker-diagnostics.js +37 -0
- package/build/executor/worker-finalization.js +113 -34
- package/build/executor/worker-guard-hook.js +316 -58
- package/build/executor/worker-log.js +4 -3
- package/build/executor/worker-verification-guidance.js +37 -0
- package/build/executor/worktree.js +166 -8
- package/build/first-build-advisory.js +97 -0
- package/build/generated-assets-advisory.js +14 -0
- package/build/generated-assets-catalog.js +66 -0
- package/build/generated-assets-context.js +46 -0
- package/build/generated-assets-dependencies.js +26 -0
- package/build/generated-assets-doctor.js +50 -0
- package/build/generated-assets-health.js +65 -0
- package/build/generated-assets-host.js +17 -0
- package/build/generated-assets-inventory.js +17 -0
- package/build/generated-assets-paths.js +60 -0
- package/build/generated-assets-publisher.js +133 -0
- package/build/generated-assets-remedy.js +25 -0
- package/build/generated-assets-repair.js +72 -0
- package/build/generated-assets-session.js +71 -0
- package/build/generated-assets-test-support.js +35 -0
- package/build/index.js +1224 -1054
- package/build/init.js +54 -177
- package/build/install-bridge-conductor-gate.js +1372 -0
- package/build/install-bridge-conductor.js +296 -41
- package/build/install-bridge.js +547 -40
- package/build/mcp-install-state.js +50 -4
- package/build/merge-workflow-install.js +3 -4
- package/build/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipeline-utils.js +6 -2
- package/build/pipelines.generated.js +34 -160
- package/build/plan-execution-ledger.js +2 -32
- package/build/plane/alembic-head.js +12 -3
- package/build/plane/base-worktree-guard.js +147 -0
- package/build/plane/cli.js +217 -17
- package/build/plane/code-snapshot.js +468 -0
- package/build/plane/defaults.js +48 -3
- package/build/plane/endpoint.js +41 -0
- package/build/plane/manifest.js +136 -2
- package/build/plane/member-roster.js +94 -19
- package/build/plane/preflight.js +83 -2
- package/build/plane/process-cwd.js +67 -0
- package/build/plane/restart.js +542 -0
- package/build/plane/shutdown.js +31 -0
- package/build/plane/status.js +143 -8
- package/build/plane/supervisor.js +586 -179
- package/build/plane/types.js +54 -2
- package/build/printed-remedy-commands.js +52 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/repair-assets.js +93 -0
- package/build/review-generation.js +22 -0
- package/build/run-unit-tests-launcher.js +130 -49
- package/build/secret-redaction.js +33 -0
- package/build/setup-epic.js +152 -22
- package/build/sfcc/config.js +9 -0
- package/build/sfcc/credentials.js +100 -38
- package/build/sfcc/hostname.js +67 -0
- package/build/sfcc/permissions.js +17 -13
- package/build/sfcc/register.js +1 -1
- package/build/sfcc/tool-wrapper.js +39 -43
- package/build/sfcc/write-grants.js +17 -11
- package/build/sfcc/write-result.js +4 -3
- package/build/sfcc/writes-site-preference.js +20 -8
- package/build/start-tickets-prereqs.js +70 -16
- package/build/start-tickets.js +208 -36
- package/build/tool-error-envelope.js +61 -0
- package/build/tool-surface-gating.js +36 -0
- package/build/upgrade-cli.js +55 -1
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/worker-guard-hook-bin.js +4 -6
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +145 -6
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +867 -0
- package/docs/EPIC-IMPLEMENTER.md +728 -0
- package/docs/install/mcp-tool-integrations.md +24 -15
- package/docs/install/sfcc-integration.md +12 -4
- package/package.json +3 -3
- package/pipelines/greenfield-setup.json +4 -128
- package/pipelines/implement-ticket.json +3 -2
- package/pipelines/plan-epic.json +0 -5
- package/pipelines/review-ticket.json +1 -1
- package/docs/CONDUCTOR.md +0 -605
- 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/
|
|
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
|
-
|
|
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,
|
|
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
|
-
* ##
|
|
26
|
+
* ## Remedy command
|
|
27
27
|
*
|
|
28
|
-
* {@link MERGE_WORKFLOW_REMEDY_COMMAND}
|
|
29
|
-
*
|
|
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
|
|
84
|
-
* request.
|
|
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
|
|
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
|
-
|
|
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-
|
|
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({
|
|
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
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// that
|
|
121
|
-
|
|
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: {
|
|
137
|
-
|
|
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 ??
|
|
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
|
-
|
|
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
|
}
|