@bridge_gpt/mcp-server 0.2.66 → 0.2.67
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 +252 -77
- package/build/agent-capabilities/probes.js +15 -5
- package/build/agent-launchers/claude-executor-adapter.js +43 -16
- package/build/agent-launchers/executor-adapter.js +7 -4
- package/build/agents.generated.js +1 -1
- package/build/bounded-wait.js +67 -10
- package/build/commands.generated.js +11 -11
- package/build/conductor/bridge-api-client.js +143 -1
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/doctor.js +3 -3
- package/build/conductor/epic-implementer-cli.js +22 -6
- package/build/conductor/errors.js +4 -3
- package/build/conductor/guided-repair.js +6 -2
- package/build/conductor/hook-runtime-qualification.js +1 -1
- 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 +18 -2
- package/build/conductor/readiness-sections.js +41 -6
- package/build/conductor/readiness.js +181 -29
- package/build/conductor/recovery-cli.js +200 -8
- package/build/conductor/recovery-operations.js +69 -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/docs.generated.js +8 -3
- package/build/drive-epic-key.js +6 -5
- package/build/drive-epic.js +85 -54
- package/build/epic-coherence-input.js +2 -1
- package/build/epic-implementer-bin.js +22 -20
- package/build/epic-implementer-bundle-cli.js +2 -2
- package/build/executor/cli.js +3 -2
- package/build/executor/completion-contract.js +48 -5
- package/build/executor/completion-supervision.js +4 -0
- package/build/executor/env.js +19 -0
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/http-client.js +20 -0
- package/build/executor/job-errors.js +25 -0
- package/build/executor/job-runner.js +225 -41
- package/build/executor/observation.js +6 -1
- package/build/executor/preflight.js +51 -14
- package/build/executor/required-work-ledger.js +6 -1
- package/build/executor/resume-pre-spawn.js +2 -0
- package/build/executor/runner.js +8 -2
- package/build/executor/service-unit.js +7 -1
- package/build/executor/worker-finalization.js +82 -29
- package/build/executor/worker-guard-hook.js +137 -33
- package/build/executor/worktree.js +82 -1
- package/build/index.js +403 -429
- package/build/install-bridge-conductor.js +8 -6
- package/build/install-bridge.js +3 -3
- package/build/phase-context-git-probe.js +68 -0
- package/build/phase-result-artifacts.js +173 -5
- package/build/pipelines.generated.js +18 -24
- 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 +120 -1
- package/build/plane/member-roster.js +94 -19
- package/build/plane/preflight.js +30 -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 +37 -0
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +22 -0
- package/build/setup-epic.js +44 -19
- 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.js +179 -31
- package/build/tool-error-envelope.js +61 -0
- package/build/version.generated.js +3 -3
- package/build/visual-diff.js +22 -1
- package/build/worker-guard-hook-bin.js +3 -6
- package/build/worker-guard-hook-main.js +47 -0
- package/build/worktree-core.js +130 -4
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +849 -0
- package/docs/EPIC-IMPLEMENTER.md +662 -0
- package/docs/install/mcp-tool-integrations.md +24 -15
- package/docs/install/sfcc-integration.md +12 -4
- package/package.json +1 -1
- 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
|
@@ -92,12 +92,14 @@
|
|
|
92
92
|
* unrelated additive change look like a breaking one here, or worse, hide a real
|
|
93
93
|
* one.
|
|
94
94
|
*/
|
|
95
|
+
import { PRINTED_CHECK_NAME_INSTRUCTION, PRINTED_CONDUCTOR_EXECUTOR_INSTALL_COMMAND, PRINTED_CONDUCTOR_INSTALL_COMMAND, PRINTED_CONNECT_GITHUB_COMMAND, PRINTED_EXECUTOR_ID_INSTRUCTION, } from "../printed-remedy-commands.js";
|
|
95
96
|
import { mapConductorDoctorReportToReadinessChecks, } from "./doctor.js";
|
|
96
97
|
import { mapInstallStatusChecksToReadinessChecks, } from "../install-doctor.js";
|
|
97
98
|
import { mapPlanePreflightToReadinessChecks, } from "../plane/preflight.js";
|
|
98
99
|
import { runConductorInstallDoctor, } from "./install-doctor.js";
|
|
99
100
|
import { BRING_UP_SECTION_LABELS, presentRequiredChecks, } from "./readiness-sections.js";
|
|
100
101
|
import { classifyBringUpFacts } from "./bring-up-facts.js";
|
|
102
|
+
import { CI_WORKFLOW_PATH, REVIEW_WORKFLOW_PATH, ciWorkflowRemedy, repositoryWorkflowRemedy, } from "./repository-workflow-remedy.js";
|
|
101
103
|
import { classifyMergeExecutionEligibility, MERGE_EXECUTION_INELIGIBLE_REMEDIATION, MERGE_EXECUTION_UNVERIFIABLE_REMEDIATION, } from "./merge-execution-eligibility.js";
|
|
102
104
|
import { createReadinessCheck, createReadinessCheckSafely, summarizeReadinessChecks, validateReadinessChecks, } from "../readiness-check.js";
|
|
103
105
|
import { ADMISSION_EVIDENCE_SCHEMA_VERSION, } from "./bridge-api-client.js";
|
|
@@ -137,7 +139,10 @@ export const SERVER_READINESS_DESCRIPTORS = [
|
|
|
137
139
|
id: "single-transition-authority",
|
|
138
140
|
label: BRING_UP_SECTION_LABELS["single-transition-authority"],
|
|
139
141
|
},
|
|
140
|
-
|
|
142
|
+
// The stable ID stays `reconciler-liveness`; only the label changed (BAPI-1360),
|
|
143
|
+
// because the row reports whether ACTIVE runs are making progress, not whether a
|
|
144
|
+
// reconciler process exists.
|
|
145
|
+
{ id: "reconciler-liveness", label: "Active-run progress" },
|
|
141
146
|
{ id: "executor-readiness", label: "Executor server observation" },
|
|
142
147
|
{ id: "review-workflow", label: "Review workflow protocol" },
|
|
143
148
|
{ id: "conductor-ci-workflow", label: "Conductor CI migration guard" },
|
|
@@ -145,7 +150,7 @@ export const SERVER_READINESS_DESCRIPTORS = [
|
|
|
145
150
|
// third unattended refusal reads. Noun phrases, like every row above: these
|
|
146
151
|
// render as list items under a source heading, not as sentences.
|
|
147
152
|
{ id: "unattended-consent", label: "Repository unattended consent" },
|
|
148
|
-
{ id: "unattended-notify-default", label: "
|
|
153
|
+
{ id: "unattended-notify-default", label: "Escalation channel (webhook or email)" },
|
|
149
154
|
{ id: "repository-workflow-readiness", label: "Repository workflow readiness" },
|
|
150
155
|
];
|
|
151
156
|
/** Fixed, secret-free prose per failure kind. Never a status body or header. */
|
|
@@ -169,23 +174,29 @@ const SERVER_FAILURE_COPY = {
|
|
|
169
174
|
};
|
|
170
175
|
/** Remediations for the individual server facts, as fixed prose. */
|
|
171
176
|
const SERVER_REMEDIATIONS = {
|
|
172
|
-
supervisorSetup:
|
|
173
|
-
supervisorConfig:
|
|
177
|
+
supervisorSetup: `run \`${PRINTED_CONDUCTOR_INSTALL_COMMAND}\` (or \`setup-epic\`) for this repository to write the project-default supervisor posture. ${PRINTED_CHECK_NAME_INSTRUCTION}`,
|
|
178
|
+
supervisorConfig: `run \`${PRINTED_CONDUCTOR_INSTALL_COMMAND}\` for this repository to write the project-default supervisor configuration. ${PRINTED_CHECK_NAME_INSTRUCTION}`,
|
|
174
179
|
mergeApproval: "set the merge-approval requirement explicitly in the supervisor configuration rather than leaving it unset.",
|
|
175
180
|
reviewPolicyMissing: "set the repository review-policy default so the merge path and the code_review gate read the same signal.",
|
|
176
181
|
reviewPolicyDivergent: "align the repository review-policy default with the done gate's review_state condition, or confirm the difference is intended.",
|
|
177
|
-
githubCredentials:
|
|
182
|
+
githubCredentials: `connect GitHub from the Bridge setup UI or run \`${PRINTED_CONNECT_GITHUB_COMMAND}\` so owner, repository id, and installation id all resolve.`,
|
|
178
183
|
actionsProbeFailed: "re-run once credentials resolve; the permission could not be checked, which is not the same as confirmed-missing.",
|
|
179
184
|
actionsMissing: "grant the GitHub App `actions: write`; without it the conductor's workflow rerun lane fails open on 403.",
|
|
180
185
|
reconcilerUnreadable: "retry once the Bridge API is reachable; reconciler liveness is unknown here, not confirmed stale.",
|
|
181
|
-
|
|
186
|
+
// BAPI-1360: the frozen `epic-tick` command is never named. The reconciler runs
|
|
187
|
+
// inside the worker, so the first check is that the worker is up, then the run's
|
|
188
|
+
// own status.
|
|
189
|
+
reconcilerStale: "check the worker first (`server.worker-present`), then inspect the stalled run with `epic-implementer epic-status`.",
|
|
182
190
|
executorUnreadable: "retry once the Bridge API is reachable; no executor readiness claim is ever made from local state.",
|
|
183
|
-
executorNeverSeen:
|
|
191
|
+
executorNeverSeen: `provision and start an executor (\`${PRINTED_CONDUCTOR_EXECUTOR_INSTALL_COMMAND}\` or \`executor install-service\`); readiness comes only from a live server observation. ${PRINTED_CHECK_NAME_INSTRUCTION} ${PRINTED_EXECUTOR_ID_INSTRUCTION}`,
|
|
192
|
+
executorNoneObservedNoRuns: `no action is needed to launch: \`drive-epic\` starts a run-scoped executor. To keep an executor running between runs, install one with \`${PRINTED_CONDUCTOR_EXECUTOR_INSTALL_COMMAND}\` or \`executor install-service\`. ${PRINTED_CHECK_NAME_INSTRUCTION} ${PRINTED_EXECUTOR_ID_INSTRUCTION}`,
|
|
184
193
|
executorStale: "restart the executor service; it was observed once and has gone quiet past the server's staleness threshold.",
|
|
185
194
|
reviewWorkflowUnknown: "re-run; the default branch's review workflow could not be evaluated, which is not the same as it being absent.",
|
|
186
195
|
reviewWorkflowIncomplete: "install the claude-review workflow template on the default branch so it emits both the run id and the reviewed SHA the verdict protocol reads.",
|
|
187
196
|
ciWorkflowUnknown: "re-run; the default branch's conductor CI workflow could not be evaluated, which is not the same as the guard being absent.",
|
|
188
|
-
|
|
197
|
+
// BAPI-1359: the missing-guard remedy is built by `ciWorkflowRemedy` (the shared
|
|
198
|
+
// formatter), because its wording depends on WHY the guard is missing — a
|
|
199
|
+
// confirmed Alembic chain, an older server, or an unreadable answer.
|
|
189
200
|
olderServer: "upgrade the Bridge API deploy so it reports this fact; its state is unknown here, not healthy.",
|
|
190
201
|
// BAPI-1102. Each names the ONE action that clears it, and none of them
|
|
191
202
|
// mentions a webhook URL, a destination, or any configuration value.
|
|
@@ -193,7 +204,10 @@ const SERVER_REMEDIATIONS = {
|
|
|
193
204
|
// admin panel's Setup page. No MCP or CLI surface ever writes it.
|
|
194
205
|
unattendedConsent: "turn on 'Allow unattended Epic Implementer runs' on the repository's Setup page in the Bridge admin panel, or run setup with --attended. If it is already on, contact Bridge support.",
|
|
195
206
|
unattendedNotifyDefault: "supply a --policy-file declaring notify.webhook_url, or add an email address to the account so escalation alerts can be emailed, or run setup with --attended; an unattended run must have a working escalation channel, and notify.local_sink is not one.",
|
|
196
|
-
|
|
207
|
+
// BAPI-1359: the `repository-workflow-readiness` remedy is built by
|
|
208
|
+
// `repositoryWorkflowRemedy` (the shared formatter) from the SAME report, so it
|
|
209
|
+
// names the specific missing item. The generic "run `install-bridge
|
|
210
|
+
// conductor`" text was retired: no install step creates a migration-guard job.
|
|
197
211
|
// BAPI-1135. Both are IMPORTED from the shared classifier rather than written
|
|
198
212
|
// out here, so the remedy command has exactly one spelling across the refusal,
|
|
199
213
|
// the readiness row, and the doctor — see `merge-execution-contract.ts`.
|
|
@@ -331,9 +345,16 @@ export function mapServerReadinessToChecks(readiness, failure = "unreachable") {
|
|
|
331
345
|
label,
|
|
332
346
|
status: outcome.status,
|
|
333
347
|
detail: outcome.detail,
|
|
348
|
+
// A pass carries no remediation, and a `skip` carries one only when its
|
|
349
|
+
// outcome supplied one: the older-server default would misdescribe a fact
|
|
350
|
+
// that was deliberately not evaluated.
|
|
334
351
|
...(outcome.status === "pass"
|
|
335
352
|
? {}
|
|
336
|
-
:
|
|
353
|
+
: outcome.status === "skip"
|
|
354
|
+
? outcome.remediation === undefined
|
|
355
|
+
? {}
|
|
356
|
+
: { remediation: outcome.remediation }
|
|
357
|
+
: { remediation: outcome.remediation ?? SERVER_REMEDIATIONS.olderServer }),
|
|
337
358
|
});
|
|
338
359
|
});
|
|
339
360
|
// BAPI-1260 — appended ONLY when the server reported the block, so every
|
|
@@ -377,6 +398,44 @@ const ADMISSION_OWNER_REMEDIATION = {
|
|
|
377
398
|
repository_admin: "a repository administrator resolves it (unattended consent is the 'Allow unattended Epic Implementer runs' toggle on the repository's Setup page in the Bridge admin panel); setup never grants it.",
|
|
378
399
|
bridge_operations: "escalate to Bridge operations; this is not something an operator command can change.",
|
|
379
400
|
};
|
|
401
|
+
/** The retired and current Bridge CI gate names, spelled once for the fixed remedies. */
|
|
402
|
+
const RETIRED_GATE_NAME = "conductor-ci / gate";
|
|
403
|
+
const CURRENT_GATE_NAME = "epic-implementer-ci / gate";
|
|
404
|
+
/**
|
|
405
|
+
* Fixed, per-reason remedies (BAPI-1360). Assembled from literals and the two
|
|
406
|
+
* hard-coded workflow paths only: a record's reason picks a template, and nothing
|
|
407
|
+
* from the payload is interpolated. `null` means "no specific remedy — use the
|
|
408
|
+
* owner's generic one".
|
|
409
|
+
*/
|
|
410
|
+
function admissionReasonRemedy(record, readiness) {
|
|
411
|
+
const sup = readiness.supervisor;
|
|
412
|
+
const ciAbsent = sup.required_checks_ci_workflow_absent === true ||
|
|
413
|
+
(readiness.conductor_ci_workflow?.probe_succeeded === true &&
|
|
414
|
+
readiness.conductor_ci_workflow?.workflow_present === false);
|
|
415
|
+
const reviewAbsent = sup.required_checks_review_workflow_absent === true ||
|
|
416
|
+
(readiness.review_workflow?.probe_succeeded === true &&
|
|
417
|
+
readiness.review_workflow?.workflow_present === false);
|
|
418
|
+
switch (record.reason) {
|
|
419
|
+
case "review_workflow_unconfirmed":
|
|
420
|
+
return (`a repository administrator adds ${REVIEW_WORKFLOW_PATH} to the default branch, or repairs it so it ` +
|
|
421
|
+
"emits the run-id: and Reviewed-SHA: markers in its verdict comment; setup never grants it.");
|
|
422
|
+
case "conductor_ci_workflow_unconfirmed":
|
|
423
|
+
return (`a repository administrator adds ${CI_WORKFLOW_PATH} to the default branch, or repairs it (a ` +
|
|
424
|
+
"migration-guard job is required when the repository has alembic.ini); setup never grants it.");
|
|
425
|
+
case "required_checks_lane_workflow_absent": {
|
|
426
|
+
const files = [ciAbsent ? CI_WORKFLOW_PATH : null, reviewAbsent ? REVIEW_WORKFLOW_PATH : null].filter((file) => file !== null);
|
|
427
|
+
const named = files.length > 0 ? files.join(" and ") : `${CI_WORKFLOW_PATH} and ${REVIEW_WORKFLOW_PATH}`;
|
|
428
|
+
return (`a repository administrator adds ${named} to the default branch so the configured required ` +
|
|
429
|
+
`checks (such as ${CURRENT_GATE_NAME}) have a producer; setup never grants it.`);
|
|
430
|
+
}
|
|
431
|
+
case "required_check_retired_gate":
|
|
432
|
+
return (`a repository administrator replaces ${RETIRED_GATE_NAME} with ${CURRENT_GATE_NAME} in the plan's ` +
|
|
433
|
+
"required_checks, in the supervisor done-gate checks, and in branch protection." +
|
|
434
|
+
(ciAbsent ? ` Also add ${CI_WORKFLOW_PATH} to the default branch so ${CURRENT_GATE_NAME} has a producer.` : ""));
|
|
435
|
+
default:
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
380
439
|
/**
|
|
381
440
|
* Project the server's typed launch evidence (BAPI-1260) into `server.*`
|
|
382
441
|
* checks, read-only.
|
|
@@ -405,13 +464,30 @@ export function admissionEvidenceChecks(readiness) {
|
|
|
405
464
|
}),
|
|
406
465
|
];
|
|
407
466
|
}
|
|
408
|
-
return evidence.records.map((record) => admissionEvidenceCheck(record));
|
|
467
|
+
return evidence.records.map((record) => admissionEvidenceCheck(record, readiness));
|
|
409
468
|
}
|
|
410
|
-
function admissionEvidenceCheck(record) {
|
|
469
|
+
function admissionEvidenceCheck(record, readiness) {
|
|
411
470
|
const slug = record.predicate.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "unknown";
|
|
412
471
|
const label = ADMISSION_EVIDENCE_LABELS[record.predicate] ?? "Launch evidence";
|
|
413
472
|
const reason = record.reason ? ` (${record.reason})` : "";
|
|
414
|
-
const remediation =
|
|
473
|
+
const remediation = admissionReasonRemedy(record, readiness) ??
|
|
474
|
+
ADMISSION_OWNER_REMEDIATION[record.next_action_owner ?? "operator"] ??
|
|
475
|
+
ADMISSION_OWNER_REMEDIATION.operator;
|
|
476
|
+
// BAPI-1360: the durable-actor handshake being unsupported is a known, current
|
|
477
|
+
// limitation, not a hold — it renders as a warning that says so. Every other
|
|
478
|
+
// predicate, and every other reason for this one, keeps its existing branch.
|
|
479
|
+
if (record.predicate === "durable_actor" && record.reason === "actor_handshake_unsupported") {
|
|
480
|
+
return createReadinessCheckSafely({
|
|
481
|
+
id: `server.admission-${slug}`,
|
|
482
|
+
source: "server",
|
|
483
|
+
label,
|
|
484
|
+
status: "warn",
|
|
485
|
+
detail: `not yet supported by this deploy${reason} — this does not hold a launch`,
|
|
486
|
+
// Fixed, non-escalating: the owner remedy would tell the operator to escalate
|
|
487
|
+
// for a limitation that blocks nothing.
|
|
488
|
+
remediation: "no action is needed to launch; this warning clears once the deploy supports the durable-actor handshake.",
|
|
489
|
+
});
|
|
490
|
+
}
|
|
415
491
|
switch (record.verdict) {
|
|
416
492
|
case "confirmed":
|
|
417
493
|
return createReadinessCheckSafely({
|
|
@@ -503,7 +579,7 @@ function unattendedOutcomes(readiness) {
|
|
|
503
579
|
? {
|
|
504
580
|
id: "unattended-consent",
|
|
505
581
|
status: "pass",
|
|
506
|
-
detail: "this repository has consented to unattended
|
|
582
|
+
detail: "this repository has consented to unattended Epic Implementer runs",
|
|
507
583
|
}
|
|
508
584
|
: {
|
|
509
585
|
id: "unattended-consent",
|
|
@@ -540,13 +616,21 @@ function unattendedOutcomes(readiness) {
|
|
|
540
616
|
? {
|
|
541
617
|
id: "repository-workflow-readiness",
|
|
542
618
|
status: "pass",
|
|
543
|
-
detail: "the review and
|
|
619
|
+
detail: "the review and epic-implementer-ci workflows are both confirmed",
|
|
544
620
|
}
|
|
545
621
|
: {
|
|
546
622
|
id: "repository-workflow-readiness",
|
|
547
623
|
status: "warn",
|
|
548
|
-
|
|
549
|
-
|
|
624
|
+
// BAPI-1360: name a confirmed-missing file separately from a file that
|
|
625
|
+
// could not be read; the remedy already differs by owner.
|
|
626
|
+
detail: readiness.review_workflow?.probe_succeeded === true &&
|
|
627
|
+
readiness.review_workflow?.workflow_present === false
|
|
628
|
+
? `${REVIEW_WORKFLOW_PATH} is missing from the default branch — an unattended auto-merging run will be refused`
|
|
629
|
+
: readiness.conductor_ci_workflow?.probe_succeeded === true &&
|
|
630
|
+
readiness.conductor_ci_workflow?.workflow_present === false
|
|
631
|
+
? `${CI_WORKFLOW_PATH} is missing from the default branch — an unattended auto-merging run will be refused`
|
|
632
|
+
: "the review and epic-implementer-ci workflows are not both confirmed — an unattended auto-merging run will be refused",
|
|
633
|
+
remediation: repositoryWorkflowRemedy(readiness),
|
|
550
634
|
},
|
|
551
635
|
];
|
|
552
636
|
}
|
|
@@ -677,6 +761,17 @@ function reconcilerOutcome(readiness) {
|
|
|
677
761
|
remediation: SERVER_REMEDIATIONS.reconcilerUnreadable,
|
|
678
762
|
};
|
|
679
763
|
}
|
|
764
|
+
// BAPI-1360 — with no active run there is no progress to judge: the row is an
|
|
765
|
+
// explicit, remedy-free `skip`, not a stale-tick failure about a reconciler that
|
|
766
|
+
// has nothing to reconcile. ONLY a readable response with a strict zero count
|
|
767
|
+
// qualifies; a missing or malformed count keeps the stale/pass reading below.
|
|
768
|
+
if (r.liveness_readable === true && r.active_run_count === 0) {
|
|
769
|
+
return {
|
|
770
|
+
id: "reconciler-liveness",
|
|
771
|
+
status: "skip",
|
|
772
|
+
detail: "no active runs, so there is no run progress to check",
|
|
773
|
+
};
|
|
774
|
+
}
|
|
680
775
|
if (!r.stale) {
|
|
681
776
|
return {
|
|
682
777
|
id: "reconciler-liveness",
|
|
@@ -726,10 +821,21 @@ function executorOutcome(readiness) {
|
|
|
726
821
|
remediation: SERVER_REMEDIATIONS.executorStale,
|
|
727
822
|
};
|
|
728
823
|
}
|
|
824
|
+
// `ready === null`. BAPI-1360: with readable executor AND reconciler liveness and
|
|
825
|
+
// a strict zero active-run count, no executor being present is expected —
|
|
826
|
+
// `drive-epic` starts a run-scoped one — so it is a warning, not a failure.
|
|
827
|
+
if (readiness.reconciler.liveness_readable === true && readiness.reconciler.active_run_count === 0) {
|
|
828
|
+
return {
|
|
829
|
+
id: "executor-readiness",
|
|
830
|
+
status: "warn",
|
|
831
|
+
detail: "no executor was observed; `drive-epic` starts a run-scoped executor at launch, so a durable service is optional",
|
|
832
|
+
remediation: SERVER_REMEDIATIONS.executorNoneObservedNoRuns,
|
|
833
|
+
};
|
|
834
|
+
}
|
|
729
835
|
return {
|
|
730
836
|
id: "executor-readiness",
|
|
731
837
|
status: "fail",
|
|
732
|
-
detail: "
|
|
838
|
+
detail: "no executor was observed while runs are active or their count could not be read",
|
|
733
839
|
remediation: SERVER_REMEDIATIONS.executorNeverSeen,
|
|
734
840
|
};
|
|
735
841
|
}
|
|
@@ -754,6 +860,16 @@ function reviewWorkflowOutcome(readiness) {
|
|
|
754
860
|
remediation: SERVER_REMEDIATIONS.reviewWorkflowUnknown,
|
|
755
861
|
};
|
|
756
862
|
}
|
|
863
|
+
// BAPI-1360 — a SUCCESSFUL probe that found no file is a confirmed absence, kept
|
|
864
|
+
// apart from an unreadable file (Bridge operations) and from missing markers.
|
|
865
|
+
if (w.workflow_present === false) {
|
|
866
|
+
return {
|
|
867
|
+
id: "review-workflow",
|
|
868
|
+
status: "fail",
|
|
869
|
+
detail: `${REVIEW_WORKFLOW_PATH} is missing from the default branch (confirmed)`,
|
|
870
|
+
remediation: `a repository administrator adds ${REVIEW_WORKFLOW_PATH} to the default branch so it emits both the run id and the reviewed SHA the verdict protocol reads.`,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
757
873
|
if (w.emits_run_id && w.emits_reviewed_sha) {
|
|
758
874
|
return {
|
|
759
875
|
id: "review-workflow",
|
|
@@ -780,7 +896,7 @@ function ciWorkflowOutcome(readiness) {
|
|
|
780
896
|
return {
|
|
781
897
|
id: "conductor-ci-workflow",
|
|
782
898
|
status: "fail",
|
|
783
|
-
detail: "this Bridge API deploy did not report the
|
|
899
|
+
detail: "this Bridge API deploy did not report the epic-implementer-ci workflow facts",
|
|
784
900
|
remediation: SERVER_REMEDIATIONS.olderServer,
|
|
785
901
|
};
|
|
786
902
|
}
|
|
@@ -794,18 +910,51 @@ function ciWorkflowOutcome(readiness) {
|
|
|
794
910
|
remediation: SERVER_REMEDIATIONS.ciWorkflowUnknown,
|
|
795
911
|
};
|
|
796
912
|
}
|
|
797
|
-
|
|
798
|
-
|
|
913
|
+
// BAPI-1360 — confirmed absence of the file, not a missing migration guard.
|
|
914
|
+
if (c.workflow_present === false) {
|
|
915
|
+
return {
|
|
916
|
+
id: "conductor-ci-workflow",
|
|
917
|
+
status: "fail",
|
|
918
|
+
detail: `${CI_WORKFLOW_PATH} is missing from the default branch (confirmed)`,
|
|
919
|
+
remediation: `a repository administrator adds ${CI_WORKFLOW_PATH} to the default branch. ${ciWorkflowRemedy(c)}`,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
if (c.migration_guard_present) {
|
|
923
|
+
return {
|
|
799
924
|
id: "conductor-ci-workflow",
|
|
800
925
|
status: "pass",
|
|
801
926
|
detail: "the default branch's conductor CI workflow runs the migration guard",
|
|
802
|
-
}
|
|
803
|
-
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
// BAPI-1359 — the guard is conditional on an Alembic chain. Three explicit
|
|
930
|
+
// server answers, plus the older-server case where neither field is reported.
|
|
931
|
+
if (c.migration_chain_probe_succeeded === true && c.migration_guard_required === false) {
|
|
932
|
+
return {
|
|
804
933
|
id: "conductor-ci-workflow",
|
|
805
|
-
status: "
|
|
806
|
-
|
|
807
|
-
|
|
934
|
+
status: "pass",
|
|
935
|
+
// States only what was observed. It does not say migrations are safe, or
|
|
936
|
+
// that the repository has none.
|
|
937
|
+
detail: "migration guard not required (no Alembic chain: no alembic.ini on the default branch)",
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
if (c.migration_chain_probe_succeeded === false) {
|
|
941
|
+
return {
|
|
942
|
+
id: "conductor-ci-workflow",
|
|
943
|
+
status: "fail",
|
|
944
|
+
detail: "the Alembic check could not be read, so it is not known whether the conductor CI workflow needs the migration guard",
|
|
945
|
+
remediation: ciWorkflowRemedy(c),
|
|
808
946
|
};
|
|
947
|
+
}
|
|
948
|
+
return {
|
|
949
|
+
id: "conductor-ci-workflow",
|
|
950
|
+
status: "warn",
|
|
951
|
+
detail: c.migration_guard_required === true && c.migration_chain_probe_succeeded === true
|
|
952
|
+
? "the conductor CI workflow does not run the migration guard, which this repository requires because it has alembic.ini on its default branch"
|
|
953
|
+
: // An older server (no new fields) or a partial report: keep today's
|
|
954
|
+
// wording and today's behavior, and claim nothing about alembic.ini.
|
|
955
|
+
"the conductor CI workflow does not run the migration guard",
|
|
956
|
+
remediation: ciWorkflowRemedy(c),
|
|
957
|
+
};
|
|
809
958
|
}
|
|
810
959
|
// ---------------------------------------------------------------------------
|
|
811
960
|
// Install-time section adapter
|
|
@@ -873,7 +1022,7 @@ export function mapInstallSectionsToReadinessChecks(sections) {
|
|
|
873
1022
|
? {}
|
|
874
1023
|
: {
|
|
875
1024
|
remediation: section.remediation ??
|
|
876
|
-
|
|
1025
|
+
`run \`${PRINTED_CONDUCTOR_INSTALL_COMMAND}\` for this repository to re-provision this capability. ${PRINTED_CHECK_NAME_INSTRUCTION}`,
|
|
877
1026
|
}),
|
|
878
1027
|
}));
|
|
879
1028
|
}
|
|
@@ -903,13 +1052,14 @@ export async function collectConductorReadinessGate(deps) {
|
|
|
903
1052
|
}
|
|
904
1053
|
// A plane preflight was requested and could not be run. Not requesting one is
|
|
905
1054
|
// not an unavailability — its prerequisites are still reported as unknowns.
|
|
906
|
-
if (deps.runPlanePreflight && planeResult === null)
|
|
1055
|
+
if (deps.runPlanePreflight && deps.planeNotApplicable === undefined && planeResult === null) {
|
|
907
1056
|
unavailableSources.push("plane");
|
|
1057
|
+
}
|
|
908
1058
|
const checks = [
|
|
909
1059
|
...mapInstallStatusChecksToReadinessChecks((installReport?.legacyInstallChecks ?? null), installReport === null),
|
|
910
1060
|
...mapConductorDoctorReportToReadinessChecks(installReport?.legacyConductor ?? null),
|
|
911
1061
|
...mapInstallSectionsToReadinessChecks(installReport?.sections ?? null),
|
|
912
|
-
...mapPlanePreflightToReadinessChecks(planeResult),
|
|
1062
|
+
...mapPlanePreflightToReadinessChecks(planeResult, deps.planeNotApplicable),
|
|
913
1063
|
...mapServerReadinessToChecks(installReport?.readiness ?? null, classifyServerFailure(installReport)),
|
|
914
1064
|
];
|
|
915
1065
|
// Validate the ASSEMBLED report, not just the pieces: an injected collector or
|
|
@@ -946,6 +1096,8 @@ async function runInstallDoctorSafely(deps) {
|
|
|
946
1096
|
}
|
|
947
1097
|
/** Run the plane preflight, containing any throw. */
|
|
948
1098
|
async function runPlaneSafely(deps) {
|
|
1099
|
+
if (deps.planeNotApplicable !== undefined)
|
|
1100
|
+
return null;
|
|
949
1101
|
if (!deps.runPlanePreflight)
|
|
950
1102
|
return null;
|
|
951
1103
|
try {
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `conductor` operator recovery and repair verbs —
|
|
2
|
+
* `conductor` operator recovery and repair verbs — fourteen CLI-only commands.
|
|
3
3
|
*
|
|
4
4
|
* The four BAPI-872 recovery verbs: `stop-run`, `abandon-run`, `unpark`,
|
|
5
5
|
* `adopt-current-head-and-unpark`. The six run-addressed repair verbs: BAPI-1154's
|
|
6
6
|
* `scope-reparse`, `dispatch-remediation`, `merge-child`, and BAPI-1176's
|
|
7
7
|
* `scope-catch-up`, `patch-policy`, `resume-run`. BAPI-1300's parked-child
|
|
8
8
|
* continuation pair: `continue-child` and the read-only `continuation-receipt`.
|
|
9
|
-
* BAPI-1326's allowance-preserving CI resume: `resume-ci`.
|
|
9
|
+
* BAPI-1326's allowance-preserving CI resume: `resume-ci`. BAPI-1388's guided
|
|
10
|
+
* provider re-run of failed CI workflow runs: `rerun-ci`.
|
|
10
11
|
*
|
|
11
12
|
* CLI-only by design — this is an explicit ticket non-goal, not an oversight:
|
|
12
|
-
* none of these
|
|
13
|
+
* none of these fourteen command names is ever registered as an MCP tool, tool
|
|
13
14
|
* description, or generated tool-schema input (`index-static.test.ts` guards
|
|
14
15
|
* this). They exist so an operator (or `plane down`, via
|
|
15
16
|
* `recovery-operations.ts`) can recover a stuck run or ticket WITHOUT
|
|
@@ -709,11 +710,13 @@ export async function runScopeReparseCommand(argv) {
|
|
|
709
710
|
const DISPATCH_REMEDIATION_USAGE = [
|
|
710
711
|
"Usage: epic-implementer dispatch-remediation --epic-run-id <id> --ticket-key <key> --context-file <path> [--json]",
|
|
711
712
|
"",
|
|
712
|
-
"Dispatch a remediation job for a ticket at code_review (or parked there),",
|
|
713
|
+
"Dispatch a remediation job for a ticket at code_review or ci (or parked there),",
|
|
713
714
|
"carrying your driver context to the worker. The SERVER chooses the job type",
|
|
714
|
-
"(remediate, or reconcile_overlap for a conflict/overlap ticket
|
|
715
|
+
"(remediate, or reconcile_overlap for a conflict/overlap ticket; ci_fix for a CI",
|
|
716
|
+
"failure the server's FRESH CI read routes to investigation) and admits it",
|
|
715
717
|
"through the reconciler's own strict-serial lane, remediation budget, and",
|
|
716
|
-
"dedup key. A parked ticket is resumed in the same step.",
|
|
718
|
+
"dedup key. A parked ticket is resumed in the same step (unguided only). An",
|
|
719
|
+
"infrastructure CI failure is refused by name and points to rerun-ci.",
|
|
717
720
|
"",
|
|
718
721
|
`--context-file must be non-empty UTF-8 text of at most DRIVER_CONTEXT_MAX_BYTES`,
|
|
719
722
|
`(${DRIVER_CONTEXT_MAX_BYTES} bytes). It is rejected, never truncated, and is never`,
|
|
@@ -764,7 +767,7 @@ export async function runDispatchRemediationCommand(argv, deps = {}) {
|
|
|
764
767
|
guard,
|
|
765
768
|
}),
|
|
766
769
|
label: `ticket ${parsed.ticketKey}`,
|
|
767
|
-
stateLabel: "
|
|
770
|
+
stateLabel: "selected gate",
|
|
768
771
|
}, parsed.json);
|
|
769
772
|
}
|
|
770
773
|
const { dispatchRemediationRepair } = await import("./recovery-operations.js");
|
|
@@ -777,7 +780,7 @@ export async function runDispatchRemediationCommand(argv, deps = {}) {
|
|
|
777
780
|
verb: "dispatch-remediation",
|
|
778
781
|
epicRunId: parsed.epicRunId,
|
|
779
782
|
targetLabel: `ticket ${parsed.ticketKey}`,
|
|
780
|
-
stateLabel: "
|
|
783
|
+
stateLabel: "selected gate",
|
|
781
784
|
}, parsed.json, (result) => [
|
|
782
785
|
` job: ${result.job_type || "unknown"}${result.job_id !== null ? ` (job ${result.job_id})` : ""}`,
|
|
783
786
|
]);
|
|
@@ -787,6 +790,195 @@ export async function runDispatchRemediationCommand(argv, deps = {}) {
|
|
|
787
790
|
return 1;
|
|
788
791
|
}
|
|
789
792
|
}
|
|
793
|
+
// ---------------------------------------------------------------------------
|
|
794
|
+
// BAPI-1388 — rerun-ci
|
|
795
|
+
// ---------------------------------------------------------------------------
|
|
796
|
+
const RERUN_CI_VALUE_FLAGS = new Set(["--epic-run-id", "--ticket-key", "--binding"]);
|
|
797
|
+
/** Exit codes: 0 applied (including an all-not-re-runnable no-op), 2 refused/stale, 3 unconfirmed, 1 other. */
|
|
798
|
+
export const RERUN_CI_EXIT_OK = 0;
|
|
799
|
+
export const RERUN_CI_EXIT_FAILED = 1;
|
|
800
|
+
export const RERUN_CI_EXIT_REFUSED = 2;
|
|
801
|
+
export const RERUN_CI_EXIT_UNCONFIRMED = 3;
|
|
802
|
+
const RERUN_CI_USAGE = [
|
|
803
|
+
"Usage: epic-implementer rerun-ci --epic-run-id <id> --ticket-key <key> --binding <token> [--json]",
|
|
804
|
+
"",
|
|
805
|
+
"Ask the CI provider to re-run a ticket's FAILED required checks at its bound head,",
|
|
806
|
+
"for a ticket at ci or parked on it. Copy the command exactly as the run",
|
|
807
|
+
"explanation rendered it: --binding pins the ticket version and head it showed.",
|
|
808
|
+
"The SERVER re-reads fresh CI evidence, re-runs each GitHub Actions workflow run at",
|
|
809
|
+
"most once per head, and reports every failed check as enqueued, deduplicated,",
|
|
810
|
+
"deferred, or not_re_runnable.",
|
|
811
|
+
"",
|
|
812
|
+
"rerun-ci NEVER re-runs the claude-review workflow. It charges no allowance and",
|
|
813
|
+
"changes no park state, and a re-run is not a verdict: when CI is green, use",
|
|
814
|
+
"`epic-implementer resume-ci` (a parked ticket) or let the reconciler observe it.",
|
|
815
|
+
"If the failure is a code defect, use `dispatch-remediation --context-file`.",
|
|
816
|
+
"",
|
|
817
|
+
"The request is stored locally before it is sent, so re-running the same command",
|
|
818
|
+
"after a lost response re-sends the SAME request. A subject that has since changed",
|
|
819
|
+
"is refused as stale with nothing written.",
|
|
820
|
+
"",
|
|
821
|
+
"Exit codes: 0 applied (including a no-op when nothing could be re-run); 2 refused",
|
|
822
|
+
"or stale; 3 unconfirmed; 1 other failure (including a server without this verb).",
|
|
823
|
+
].join("\n");
|
|
824
|
+
export function parseRerunCiArgs(argv) {
|
|
825
|
+
const { values, bools } = tokenizeRecoveryFlags(argv, RERUN_CI_VALUE_FLAGS, RECOVERY_BOOL_FLAGS);
|
|
826
|
+
if (bools.has("--help")) {
|
|
827
|
+
return { epicRunId: "", ticketKey: "", binding: "", json: bools.has("--json"), help: true };
|
|
828
|
+
}
|
|
829
|
+
return {
|
|
830
|
+
epicRunId: requireFlag(values, "--epic-run-id"),
|
|
831
|
+
ticketKey: requireFlag(values, "--ticket-key"),
|
|
832
|
+
binding: requireFlag(values, "--binding"),
|
|
833
|
+
json: bools.has("--json"),
|
|
834
|
+
help: false,
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
const RERUN_CI_STATUS_TEXT = {
|
|
838
|
+
enqueued: "re-run requested",
|
|
839
|
+
deduplicated: "already requested",
|
|
840
|
+
deferred: "deferred (not requested; run the verb again after a fresh explain-run)",
|
|
841
|
+
not_re_runnable: "not re-runnable",
|
|
842
|
+
};
|
|
843
|
+
const RERUN_CI_REASON_TEXT = {
|
|
844
|
+
not_github_actions: "not a GitHub Actions check",
|
|
845
|
+
no_workflow_run_id: "no workflow run id",
|
|
846
|
+
claude_review_excluded: "claude-review is never re-run",
|
|
847
|
+
};
|
|
848
|
+
const RERUN_CI_OUTCOME_TEXT = {
|
|
849
|
+
enqueued: "re-runs requested",
|
|
850
|
+
partial: "partially requested (some runs deferred)",
|
|
851
|
+
nothing_to_enqueue: "nothing to enqueue (no failed check could be re-run)",
|
|
852
|
+
};
|
|
853
|
+
const RERUN_CI_NEXT_ACTION = {
|
|
854
|
+
enqueued: "Wait for the re-runs to finish, then read `epic-implementer explain-run`. When CI is " +
|
|
855
|
+
"green use `epic-implementer resume-ci` (a parked ticket); this verb did not mark CI passed.",
|
|
856
|
+
partial: "Some runs were not requested. Read `epic-implementer explain-run` and, if still " +
|
|
857
|
+
"offered, re-run the command it renders with its fresh binding.",
|
|
858
|
+
nothing_to_enqueue: "No re-run was possible. Read `epic-implementer explain-run`; for a code defect use " +
|
|
859
|
+
"`dispatch-remediation --context-file`.",
|
|
860
|
+
};
|
|
861
|
+
function rerunCiCheckLine(check) {
|
|
862
|
+
const identity = [
|
|
863
|
+
check.name ?? "(unnamed check)",
|
|
864
|
+
check.workflow_identity ? `workflow ${check.workflow_identity}` : null,
|
|
865
|
+
check.run_id !== null ? `run ${check.run_id}` : null,
|
|
866
|
+
]
|
|
867
|
+
.filter((part) => part !== null)
|
|
868
|
+
.join(", ");
|
|
869
|
+
const reason = check.reason ? ` — ${RERUN_CI_REASON_TEXT[check.reason] ?? check.reason}` : "";
|
|
870
|
+
const job = check.job_id !== null ? ` (job ${check.job_id})` : "";
|
|
871
|
+
return ` - ${identity}: ${RERUN_CI_STATUS_TEXT[check.status] ?? check.status}${reason}${job}`;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Exported for tests: render one `rerun-ci` outcome. Success goes to stdout and
|
|
875
|
+
* every failure to stderr (`--json` included). An unsupported server is NEVER a
|
|
876
|
+
* success and says it is "not available on this server".
|
|
877
|
+
*/
|
|
878
|
+
export function renderRerunCiOutcome(outcome, parsed, json) {
|
|
879
|
+
const label = `ticket ${parsed.ticketKey} (run ${parsed.epicRunId})`;
|
|
880
|
+
if (outcome.kind === "applied") {
|
|
881
|
+
const result = outcome.result;
|
|
882
|
+
if (result.committed && !result.event_recorded) {
|
|
883
|
+
console.error(`Warning: rerun-ci for ${label} COMMITTED, but its operator_verb_applied audit ` +
|
|
884
|
+
"event was not recorded. The action stands.");
|
|
885
|
+
}
|
|
886
|
+
if (json) {
|
|
887
|
+
console.log(JSON.stringify({
|
|
888
|
+
ok: true,
|
|
889
|
+
outcome: "applied",
|
|
890
|
+
request_id: outcome.requestId,
|
|
891
|
+
reference_reused: outcome.referenceReused,
|
|
892
|
+
result,
|
|
893
|
+
}));
|
|
894
|
+
return RERUN_CI_EXIT_OK;
|
|
895
|
+
}
|
|
896
|
+
const lines = [
|
|
897
|
+
`rerun-ci (guided) — ${label}`,
|
|
898
|
+
` ticket: ${result.ticket_key}`,
|
|
899
|
+
` head: ${result.head_sha ? "`" + result.head_sha + "`" : "unknown"}`,
|
|
900
|
+
` outcome: ${RERUN_CI_OUTCOME_TEXT[result.outcome] ?? result.outcome}`,
|
|
901
|
+
" failed checks:",
|
|
902
|
+
...result.checks.map(rerunCiCheckLine),
|
|
903
|
+
` next action: ${RERUN_CI_NEXT_ACTION[result.outcome] ?? "Read `epic-implementer explain-run`."}`,
|
|
904
|
+
` request: ${outcome.requestId}${outcome.referenceReused ? " (re-sent from the local reference)" : ""}`,
|
|
905
|
+
` event recorded: ${result.event_recorded}`,
|
|
906
|
+
];
|
|
907
|
+
console.log(lines.join("\n"));
|
|
908
|
+
return RERUN_CI_EXIT_OK;
|
|
909
|
+
}
|
|
910
|
+
if (outcome.kind === "refused" || outcome.kind === "stale") {
|
|
911
|
+
const payload = {
|
|
912
|
+
ok: false,
|
|
913
|
+
outcome: outcome.kind,
|
|
914
|
+
code: outcome.code,
|
|
915
|
+
message: outcome.message,
|
|
916
|
+
request_id: outcome.requestId,
|
|
917
|
+
nothing_written: true,
|
|
918
|
+
};
|
|
919
|
+
if (json) {
|
|
920
|
+
console.error(JSON.stringify(payload));
|
|
921
|
+
}
|
|
922
|
+
else {
|
|
923
|
+
console.error(`Refused: rerun-ci for ${label} — ${outcome.code}: ${outcome.message}. ` +
|
|
924
|
+
(outcome.kind === "stale"
|
|
925
|
+
? "Nothing was written. The run changed since this command was rendered; re-read the run explanation."
|
|
926
|
+
: "Nothing was written."));
|
|
927
|
+
}
|
|
928
|
+
return RERUN_CI_EXIT_REFUSED;
|
|
929
|
+
}
|
|
930
|
+
if (outcome.kind === "unconfirmed") {
|
|
931
|
+
if (json) {
|
|
932
|
+
console.error(JSON.stringify({
|
|
933
|
+
ok: false,
|
|
934
|
+
outcome: "unconfirmed",
|
|
935
|
+
reason: outcome.reason,
|
|
936
|
+
message: outcome.message,
|
|
937
|
+
request_id: outcome.requestId,
|
|
938
|
+
}));
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
console.error(`Unconfirmed: rerun-ci for ${label} — ${outcome.message}.`);
|
|
942
|
+
}
|
|
943
|
+
return RERUN_CI_EXIT_UNCONFIRMED;
|
|
944
|
+
}
|
|
945
|
+
if (json) {
|
|
946
|
+
console.error(JSON.stringify({ ok: false, outcome: outcome.kind, message: outcome.message }));
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
console.error(`Failed: rerun-ci for ${label} — ${outcome.message}.`);
|
|
950
|
+
}
|
|
951
|
+
return RERUN_CI_EXIT_FAILED;
|
|
952
|
+
}
|
|
953
|
+
export async function runRerunCiCommand(argv) {
|
|
954
|
+
try {
|
|
955
|
+
const parsed = parseRerunCiArgs(argv);
|
|
956
|
+
if (parsed.help) {
|
|
957
|
+
console.log(RERUN_CI_USAGE);
|
|
958
|
+
return 0;
|
|
959
|
+
}
|
|
960
|
+
// Prove the token names THIS command, run, and ticket before credentials are
|
|
961
|
+
// resolved, so a mismatched binding reaches no one.
|
|
962
|
+
const binding = checkGuidedBinding(parsed.binding, "rerun-ci", {
|
|
963
|
+
epicRunId: parsed.epicRunId,
|
|
964
|
+
ticketKey: parsed.ticketKey,
|
|
965
|
+
});
|
|
966
|
+
const access = await resolveRecoveryAccess(parsed.json);
|
|
967
|
+
if (!access)
|
|
968
|
+
return RERUN_CI_EXIT_FAILED;
|
|
969
|
+
const { rerunCiRepair } = await import("./recovery-operations.js");
|
|
970
|
+
const outcome = await rerunCiRepair(access, {
|
|
971
|
+
epicRunId: parsed.epicRunId,
|
|
972
|
+
ticketKey: parsed.ticketKey,
|
|
973
|
+
binding,
|
|
974
|
+
});
|
|
975
|
+
return renderRerunCiOutcome(outcome, parsed, parsed.json);
|
|
976
|
+
}
|
|
977
|
+
catch (error) {
|
|
978
|
+
console.error(`Error: ${toConductorErrorEnvelope(error).message}`);
|
|
979
|
+
return RERUN_CI_EXIT_FAILED;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
790
982
|
const MERGE_CHILD_USAGE = [
|
|
791
983
|
"Usage: epic-implementer merge-child --epic-run-id <id> --pr <number> [--json]",
|
|
792
984
|
"",
|