@bridge_gpt/mcp-server 0.2.58 → 0.2.59
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 +105 -109
- package/build/agent-registry.js +15 -0
- package/build/agent-utils.js +16 -8
- package/build/claude-user-config-doctor.js +1 -35
- package/build/codex-bridge-profile.js +1 -1
- package/build/commands.generated.js +5 -6
- package/build/conductor/bridge-api-client.js +149 -0
- package/build/conductor/bring-up-facts.js +1 -1
- package/build/conductor/claude-hook.js +16 -16
- package/build/conductor/doctor.js +24 -19
- package/build/conductor/epic-implementer-cli.js +38 -5
- package/build/conductor/epic-reconcile.js +1 -1
- package/build/conductor/epic-runtime.js +10 -10
- package/build/conductor/errors.js +3 -3
- package/build/conductor/file-scope-guard.js +4 -4
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-inspection.js +2 -2
- package/build/conductor/install-doctor.js +6 -6
- package/build/conductor/merge-ledger.js +2 -2
- package/build/conductor/pr-ci-producer.js +6 -6
- package/build/conductor/pr-review-producer.js +2 -2
- package/build/conductor/producer-ledger.js +2 -0
- package/build/conductor/readiness-sections.js +7 -7
- package/build/conductor/readiness.js +2 -2
- package/build/conductor/recovery-cli.js +243 -12
- package/build/conductor/recovery-operations.js +56 -4
- package/build/conductor/repair-contract.js +38 -4
- package/build/conductor/spec-review-producer.js +6 -6
- package/build/conductor/store.js +10 -10
- package/build/conductor/supervisor-config.js +14 -14
- package/build/conductor/supervisor-ledger.js +2 -2
- package/build/conductor/supervisor-message-relay.js +2 -2
- package/build/conductor/worker-ledger-cli.js +19 -19
- package/build/conductor-bundle-artifacts.js +2 -2
- package/build/doctor.js +16 -294
- package/build/drive-epic.js +65 -71
- package/build/epic-implementer-bin.js +22 -17
- package/build/epic-implementer-claude-hook-bin.js +4 -2
- package/build/epic-implementer-env.js +155 -0
- package/build/epic-integration-pr.js +2 -2
- package/build/epic-shared/bridge-client.js +773 -0
- package/build/epic-shared/cut-protocol.js +392 -0
- package/build/executor/env.js +19 -8
- package/build/executor/heartbeat.js +1 -1
- package/build/executor/job-errors.js +3 -3
- package/build/executor/job-runner.js +1 -1
- package/build/executor/merge-job.js +35 -3
- package/build/executor/observation.js +1 -1
- package/build/executor/preflight.js +1 -1
- package/build/executor/prompt-spec.js +1 -1
- package/build/executor/reconcile-overlap-job.js +10 -130
- package/build/executor/reconcile-overlap-types.js +1 -1
- package/build/executor/runner.js +1 -1
- package/build/implement-epic/cli.js +1 -1
- package/build/index.js +181 -218
- package/build/init.js +20 -28
- package/build/install-bridge-conductor.js +7 -7
- package/build/install-bridge.js +199 -7
- package/build/mcp-host-entry-adapter.js +6 -0
- package/build/mcp-host-targets.js +2 -0
- package/build/mcp-invoke.js +5 -5
- package/build/mcp-profile.js +2 -2
- package/build/mcp-provisioning.js +2 -2
- package/build/mcp-server-invocation.js +3 -3
- package/build/merge-pull-request.js +15 -0
- package/build/model-routing-host.js +25 -0
- package/build/model-routing-preferences.js +99 -0
- package/build/pipelines.generated.js +9 -9
- package/build/plan-epic-implementer-eligibility.js +215 -0
- package/build/plan-execution-ledger.js +8 -0
- package/build/plane/member-roster.js +5 -5
- package/build/plane/supervisor.js +1 -1
- package/build/plane/types.js +5 -5
- package/build/readiness-check.js +1 -1
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +4 -4
- package/build/setup-epic.js +8 -8
- package/build/start-tickets-conductor.js +32 -32
- package/build/start-tickets-prereqs.js +4 -9
- package/build/start-tickets.js +475 -91
- package/build/version.generated.js +3 -3
- package/docs/CONDUCTOR.md +69 -18
- package/docs/install/mcp-tool-integrations.md +2 -1
- package/package.json +2 -2
- package/pipelines/implement-ticket.json +1 -1
- package/pipelines/plan-epic.json +2 -2
|
@@ -17,8 +17,6 @@
|
|
|
17
17
|
* worker-completed ONLY when the merge conflicted or the checks were red:
|
|
18
18
|
* exactly one worker, holding both tickets' intents, edits
|
|
19
19
|
* the working tree — it never commits and never pushes;
|
|
20
|
-
* governance-scanned the candidate is compared against the governance regions
|
|
21
|
-
* the server read at the INPUT epic head;
|
|
22
20
|
* pushed one fast-forward push, leased on the recorded child head;
|
|
23
21
|
* cleaned every exit that does not push resets the worktree to the
|
|
24
22
|
* recorded child head.
|
|
@@ -32,7 +30,7 @@
|
|
|
32
30
|
* The job returns FACTS, never a verdict (R14 rule 3): the input heads, the
|
|
33
31
|
* classification, whether a worker ran, every edited path and why, the local
|
|
34
32
|
* checks observed, and either the pushed head or the reason nothing was pushed —
|
|
35
|
-
*
|
|
33
|
+
* an observed moved head. The server verifies all of it
|
|
36
34
|
* against the provider (`reconcile_overlap_completion_contract.py`).
|
|
37
35
|
*
|
|
38
36
|
* Runtime prerequisite: the push runs in the executor's OWN process under the
|
|
@@ -47,7 +45,6 @@ import { allRequiredChecksGreen } from "../conductor/local-merge.js";
|
|
|
47
45
|
import { buildExecutorBaseWorkerEnv } from "./env.js";
|
|
48
46
|
import { ReconcileOverlapBranchMismatch, ReconcileOverlapConflictsUnresolved, ReconcileOverlapMergeFailed, ReconcileOverlapNothingToMerge, ReconcileOverlapPayload as ReconcileOverlapPayloadInvalid, ReconcileOverlapPushRejected, ReconcileOverlapPushUnauthorized, ReconcileOverlapTopologyRefused, ReconcileOverlapVerificationRed, ReconcileOverlapWorkerRewroteHistory, } from "./job-errors.js";
|
|
49
47
|
import { classifyMergeTree } from "./merge-tree-classifier.js";
|
|
50
|
-
import { scanGovernanceRegions } from "./reconcile-overlap-governance.js";
|
|
51
48
|
import { RECONCILE_OVERLAP_PAYLOAD_SCHEMA_VERSION, } from "./reconcile-overlap-types.js";
|
|
52
49
|
import { validateWorkerFinalization } from "./worker-finalization.js";
|
|
53
50
|
export const RECONCILE_OVERLAP_JOB_TYPE = "reconcile_overlap";
|
|
@@ -65,7 +62,6 @@ const MAX_ARGV = 32;
|
|
|
65
62
|
const MAX_ARG_CHARS = 1024;
|
|
66
63
|
const MAX_COMMAND_TIMEOUT_SECONDS = 30 * 60;
|
|
67
64
|
const MAX_CHANGED_FILES = 1000;
|
|
68
|
-
const MAX_GOVERNANCE_REGIONS = 64;
|
|
69
65
|
const REF_NAME_MAX_CHARS = 255;
|
|
70
66
|
const CONFLICT_REPORT_DIR = ".conductor/";
|
|
71
67
|
/** The executor's per-worktree artifact directory, never part of a candidate. */
|
|
@@ -92,8 +88,6 @@ const WRAPPER_STAGE_EXCLUDES = [
|
|
|
92
88
|
":(exclude).claude/settings.local.json",
|
|
93
89
|
];
|
|
94
90
|
const SHA_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
95
|
-
const TAG_RE = /^[A-Z][A-Z0-9_]{0,31}$/;
|
|
96
|
-
const SHA256_HEX_RE = /^[0-9a-f]{64}$/;
|
|
97
91
|
const FAILED_CHECK_CONCLUSIONS = new Set([
|
|
98
92
|
"failure",
|
|
99
93
|
"cancelled",
|
|
@@ -194,54 +188,6 @@ function ticketContext(value, field) {
|
|
|
194
188
|
verificationCommands: verificationCommands(entry.verification_commands, `${field}.verification_commands`),
|
|
195
189
|
};
|
|
196
190
|
}
|
|
197
|
-
function governanceRegions(value) {
|
|
198
|
-
if (!Array.isArray(value))
|
|
199
|
-
throw new PayloadError("governance.regions must be a list");
|
|
200
|
-
if (value.length > MAX_GOVERNANCE_REGIONS)
|
|
201
|
-
throw new PayloadError("governance.regions exceeds its bound");
|
|
202
|
-
return value.map((raw, index) => {
|
|
203
|
-
const entry = asRecord(raw, `governance.regions[${index}]`);
|
|
204
|
-
const tag = typeof entry.tag === "string" ? entry.tag : "";
|
|
205
|
-
const regionPath = normalizeRepoRelativePath(entry.path);
|
|
206
|
-
const begin = entry.begin_marker;
|
|
207
|
-
const end = entry.end_marker;
|
|
208
|
-
const sha = entry.content_sha256;
|
|
209
|
-
if (!TAG_RE.test(tag) ||
|
|
210
|
-
regionPath === null ||
|
|
211
|
-
typeof begin !== "string" ||
|
|
212
|
-
typeof end !== "string" ||
|
|
213
|
-
!begin.includes(tag) ||
|
|
214
|
-
!end.includes(tag) ||
|
|
215
|
-
typeof sha !== "string" ||
|
|
216
|
-
!SHA256_HEX_RE.test(sha) ||
|
|
217
|
-
!Number.isInteger(entry.begin_line) ||
|
|
218
|
-
!Number.isInteger(entry.end_line)) {
|
|
219
|
-
throw new PayloadError(`governance.regions[${index}] is malformed`);
|
|
220
|
-
}
|
|
221
|
-
return {
|
|
222
|
-
tag,
|
|
223
|
-
path: regionPath,
|
|
224
|
-
beginLine: entry.begin_line,
|
|
225
|
-
endLine: entry.end_line,
|
|
226
|
-
beginMarker: begin,
|
|
227
|
-
endMarker: end,
|
|
228
|
-
contentSha256: sha,
|
|
229
|
-
};
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
function governanceFindings(value) {
|
|
233
|
-
if (value === undefined || value === null)
|
|
234
|
-
return [];
|
|
235
|
-
if (!Array.isArray(value))
|
|
236
|
-
throw new PayloadError("governance.findings must be a list");
|
|
237
|
-
return value.map((raw, index) => {
|
|
238
|
-
const entry = asRecord(raw, `governance.findings[${index}]`);
|
|
239
|
-
const findingPath = normalizeRepoRelativePath(entry.path);
|
|
240
|
-
if (findingPath === null)
|
|
241
|
-
throw new PayloadError(`governance.findings[${index}].path is unsafe`);
|
|
242
|
-
return { path: findingPath, reason: typeof entry.reason === "string" ? entry.reason.slice(0, 300) : "" };
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
191
|
/**
|
|
246
192
|
* Validate the claimed job's payload. Pure — no subprocess, no filesystem. A
|
|
247
193
|
* payload missing either input SHA fails here, so the job never infers a head.
|
|
@@ -270,7 +216,9 @@ export function validateReconcileOverlapPayload(job) {
|
|
|
270
216
|
.map((c) => c.trim());
|
|
271
217
|
const child = ticketContext(payload.child, "payload.child");
|
|
272
218
|
const peer = payload.peer === null || payload.peer === undefined ? null : ticketContext(payload.peer, "payload.peer");
|
|
273
|
-
|
|
219
|
+
// BAPI-1215 — `payload.governance` is retired. A server that predates the
|
|
220
|
+
// retirement still sends it: it is accepted when present and never read, so
|
|
221
|
+
// this executor consumes both payload shapes at schema version 1.
|
|
274
222
|
const conflictReportPath = normalizeRepoRelativePath(payload.conflict_report_path);
|
|
275
223
|
if (conflictReportPath === null || !conflictReportPath.startsWith(CONFLICT_REPORT_DIR)) {
|
|
276
224
|
throw new PayloadError("payload.conflict_report_path must name a file under .conductor/");
|
|
@@ -295,11 +243,6 @@ export function validateReconcileOverlapPayload(job) {
|
|
|
295
243
|
requiredChecks,
|
|
296
244
|
child,
|
|
297
245
|
peer,
|
|
298
|
-
governance: {
|
|
299
|
-
regions: governanceRegions(governanceRaw.regions ?? []),
|
|
300
|
-
findings: governanceFindings(governanceRaw.findings),
|
|
301
|
-
scannedPaths: pathList(governanceRaw.scanned_paths ?? [], "governance.scanned_paths", MAX_CHANGED_FILES * 2),
|
|
302
|
-
},
|
|
303
246
|
declaredTouchedFilesUnion: pathList(payload.declared_touched_files_union ?? [], "payload.declared_touched_files_union", MAX_CHANGED_FILES * 4),
|
|
304
247
|
conflictReportPath,
|
|
305
248
|
prompt,
|
|
@@ -566,55 +509,6 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
566
509
|
}
|
|
567
510
|
return runs;
|
|
568
511
|
};
|
|
569
|
-
/** Paths the candidate changes relative to the input epic head. */
|
|
570
|
-
const changedPathsAgainstEpic = async (ctx, candidate) => {
|
|
571
|
-
const r = await git(ctx, ["diff", "--name-only", "-z", "--no-renames", payload.requiredEpicSha, candidate]);
|
|
572
|
-
if (r.exitCode !== 0)
|
|
573
|
-
return null;
|
|
574
|
-
return r.stdout
|
|
575
|
-
.split("\0")
|
|
576
|
-
.map((p) => normalizeRepoRelativePath(p))
|
|
577
|
-
.filter((p) => p !== null);
|
|
578
|
-
};
|
|
579
|
-
const readTreeFile = async (ctx, treeish, filePath) => {
|
|
580
|
-
const r = await git(ctx, ["cat-file", "blob", `${treeish}:${filePath}`]);
|
|
581
|
-
if (r.exitCode !== 0) {
|
|
582
|
-
const exists = await git(ctx, ["cat-file", "-e", `${treeish}:${filePath}`]);
|
|
583
|
-
return exists.exitCode === 0 ? { kind: "unreadable" } : { kind: "absent" };
|
|
584
|
-
}
|
|
585
|
-
return r.stdout.includes("\0") ? { kind: "binary" } : { kind: "text", text: r.stdout };
|
|
586
|
-
};
|
|
587
|
-
/** Governance scan of a candidate tree or commit against the input epic head. */
|
|
588
|
-
const scanCandidate = async (ctx, candidate) => {
|
|
589
|
-
const changed = await changedPathsAgainstEpic(ctx, candidate);
|
|
590
|
-
if (changed === null)
|
|
591
|
-
return "unprovable";
|
|
592
|
-
return scanGovernanceRegions({
|
|
593
|
-
snapshot: payload.governance,
|
|
594
|
-
changedPaths: changed,
|
|
595
|
-
readCandidate: (p) => readTreeFile(ctx, candidate, p),
|
|
596
|
-
readInput: (p) => readTreeFile(ctx, payload.requiredEpicSha, p),
|
|
597
|
-
});
|
|
598
|
-
};
|
|
599
|
-
const unprovableFact = () => ({
|
|
600
|
-
tag: "GOVERNANCE_MARKERS_UNPROVABLE",
|
|
601
|
-
paths: [],
|
|
602
|
-
findings: [{ tag: "GOVERNANCE_MARKERS_UNPROVABLE", paths: [] }],
|
|
603
|
-
truncated: false,
|
|
604
|
-
});
|
|
605
|
-
/** `git write-tree` of the index: the candidate BEFORE a commit exists. */
|
|
606
|
-
const indexTree = async (ctx) => {
|
|
607
|
-
const r = await git(ctx, ["write-tree"]);
|
|
608
|
-
const oid = r.stdout.trim().toLowerCase();
|
|
609
|
-
return r.exitCode === 0 && SHA_RE.test(oid) ? oid : null;
|
|
610
|
-
};
|
|
611
|
-
const governanceGate = async (ctx, candidate) => {
|
|
612
|
-
const scanned = candidate === null ? "unprovable" : await scanCandidate(ctx, candidate);
|
|
613
|
-
if (scanned === null)
|
|
614
|
-
return null;
|
|
615
|
-
await resetToChildHead(ctx);
|
|
616
|
-
return complete({ governance_guard_change_required: scanned === "unprovable" ? unprovableFact() : scanned });
|
|
617
|
-
};
|
|
618
512
|
const commitStaged = async (ctx, message) => {
|
|
619
513
|
const r = await git(ctx, ["commit", "--no-verify", "--no-edit", "-m", message]);
|
|
620
514
|
if (r.exitCode !== 0)
|
|
@@ -678,12 +572,12 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
678
572
|
await seams.sleep(Math.min(CI_WAIT_POLL_INTERVAL_MS, Math.max(0, ctx.remainingMs() - CI_WAIT_MARGIN_MS)));
|
|
679
573
|
}
|
|
680
574
|
};
|
|
681
|
-
/** Drift check,
|
|
575
|
+
/** Drift check, guarded push, finalization, CI wait. */
|
|
682
576
|
const pushFlow = async (ctx) => {
|
|
683
577
|
const candidate = await revParse(ctx, "HEAD");
|
|
684
578
|
if (candidate === null) {
|
|
685
579
|
await resetToChildHead(ctx);
|
|
686
|
-
return fail(ReconcileOverlapTopologyRefused, "
|
|
580
|
+
return fail(ReconcileOverlapTopologyRefused, "pushed", "the candidate head could not be read");
|
|
687
581
|
}
|
|
688
582
|
// (1) Freshness: both remote heads must still be the dispatched ones. A moved
|
|
689
583
|
// head is an OBSERVATION the server re-dispatches on; it is not a failure.
|
|
@@ -708,11 +602,7 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
708
602
|
}
|
|
709
603
|
return complete({ observed_epic_head: heads.epic });
|
|
710
604
|
}
|
|
711
|
-
// (2)
|
|
712
|
-
const gated = await governanceGate(ctx, candidate);
|
|
713
|
-
if (gated !== null)
|
|
714
|
-
return gated;
|
|
715
|
-
// (3) The ONE push: a fast-forward of the child branch, leased on the
|
|
605
|
+
// (2) The ONE push: a fast-forward of the child branch, leased on the
|
|
716
606
|
// recorded child head. Never plain --force, never a lease-less lease.
|
|
717
607
|
const destination = `refs/heads/${payload.expectedBranch}`;
|
|
718
608
|
const push = await git(ctx, [
|
|
@@ -741,7 +631,7 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
741
631
|
}
|
|
742
632
|
return fail(ReconcileOverlapPushRejected, "pushed", "the remote refused the guarded push");
|
|
743
633
|
}
|
|
744
|
-
// (
|
|
634
|
+
// (3) Finalization runs ONCE, after the wrapper's push, against the pushed
|
|
745
635
|
// head: origin must carry it and the PR must be attached at it.
|
|
746
636
|
const finalize = seams.finalize ?? validateWorkerFinalization;
|
|
747
637
|
const finalization = await finalize({
|
|
@@ -758,7 +648,7 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
758
648
|
});
|
|
759
649
|
if (!finalization.ok)
|
|
760
650
|
return { kind: "fail", failure: finalization.failure };
|
|
761
|
-
// (
|
|
651
|
+
// (4) Wait for provider checks at the pushed head, bounded by the job budget.
|
|
762
652
|
await waitForProviderChecks(ctx, candidate);
|
|
763
653
|
return complete({ resulting_head: candidate }, candidate);
|
|
764
654
|
};
|
|
@@ -835,10 +725,6 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
835
725
|
declaredTouchedFiles: payload.declaredTouchedFilesUnion,
|
|
836
726
|
};
|
|
837
727
|
}
|
|
838
|
-
// Clean: scan the merged index BEFORE the wrapper creates the merge commit.
|
|
839
|
-
const gated = await governanceGate(ctx, await indexTree(ctx));
|
|
840
|
-
if (gated !== null)
|
|
841
|
-
return finish(gated);
|
|
842
728
|
const mergeCommit = await commitStaged(ctx, `Reconcile overlap: merge ${payload.requiredEpicSha.slice(0, 12)} into ${payload.expectedBranch}`);
|
|
843
729
|
if (mergeCommit === null) {
|
|
844
730
|
await resetToChildHead(ctx);
|
|
@@ -900,9 +786,6 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
900
786
|
await resetToChildHead(ctx);
|
|
901
787
|
return fail(ReconcileOverlapConflictsUnresolved, "worker-completed", "unmerged index entries remain");
|
|
902
788
|
}
|
|
903
|
-
const gated = await governanceGate(ctx, await indexTree(ctx));
|
|
904
|
-
if (gated !== null)
|
|
905
|
-
return gated;
|
|
906
789
|
const mergeCommit = await commitStaged(ctx, `Reconcile overlap: merge ${payload.requiredEpicSha.slice(0, 12)} into ${payload.expectedBranch}`);
|
|
907
790
|
if (mergeCommit === null) {
|
|
908
791
|
await resetToChildHead(ctx);
|
|
@@ -913,9 +796,6 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
913
796
|
// Any remaining worker repair: one ordinary commit ON TOP of the merge.
|
|
914
797
|
await git(ctx, ["add", "-A", "--", ".", ...WRAPPER_STAGE_EXCLUDES]);
|
|
915
798
|
if (await stagedChanges(ctx)) {
|
|
916
|
-
const gated = await governanceGate(ctx, await indexTree(ctx));
|
|
917
|
-
if (gated !== null)
|
|
918
|
-
return gated;
|
|
919
799
|
const repairPaths = await git(ctx, ["diff", "--cached", "--name-only", "-z"]);
|
|
920
800
|
const repairReason = facts.failingCommandNames.length > 0
|
|
921
801
|
? `repair for failing check(s): ${facts.failingCommandNames.join(", ")}`
|
|
@@ -953,7 +833,7 @@ export function createReconcileOverlapDriver(job, payload, seams) {
|
|
|
953
833
|
!(await isAncestor(ctx, payload.childHead, candidate)) ||
|
|
954
834
|
!(await isAncestor(ctx, payload.requiredEpicSha, candidate))) {
|
|
955
835
|
await resetToChildHead(ctx);
|
|
956
|
-
return fail(ReconcileOverlapTopologyRefused, "
|
|
836
|
+
return fail(ReconcileOverlapTopologyRefused, "checked", "the candidate is not a merge of the dispatched heads");
|
|
957
837
|
}
|
|
958
838
|
return pushFlow(ctx);
|
|
959
839
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Types for the `reconcile_overlap` executor job (BAPI-1152).
|
|
3
3
|
*
|
|
4
|
-
* The server authors the payload (`api/library/
|
|
4
|
+
* The server authors the payload (`api/library/epic_implementer/reconcile_overlap_payload.py`)
|
|
5
5
|
* and verifies the result (`reconcile_overlap_completion_contract.py`); these
|
|
6
6
|
* interfaces are the executor's view of both. They are deliberately plain data —
|
|
7
7
|
* validation lives in `reconcile-overlap-job.ts`, which refuses anything that does
|
package/build/executor/runner.js
CHANGED
|
@@ -64,7 +64,7 @@ export function formatDispatcherUnavailableDiagnostic(liveness) {
|
|
|
64
64
|
return (`executor: no fresh reconciler/dispatcher heartbeat is available ` +
|
|
65
65
|
`(reconciler_liveness=${liveness}); the dispatcher may not be running. ` +
|
|
66
66
|
"Check DISABLE_SCHEDULER and GET /automation/health; see " +
|
|
67
|
-
"docs/claude/epic-
|
|
67
|
+
"docs/claude/epic-implementer-v2-operator-runbook.md. Continuing to poll.");
|
|
68
68
|
}
|
|
69
69
|
/** The recovery diagnostic, emitted once when an absent dispatcher comes back. */
|
|
70
70
|
export function formatDispatcherRecoveredDiagnostic() {
|
|
@@ -1323,7 +1323,7 @@ export async function runConductEpicInit(deps, options) {
|
|
|
1323
1323
|
"Planned (dry run — nothing was pushed, cut, seeded, repointed, or written):",
|
|
1324
1324
|
...describePlan(),
|
|
1325
1325
|
`would open a draft pull request ${epicBranch} → ${preflight.baseBranch} for ` +
|
|
1326
|
-
"
|
|
1326
|
+
"epic-implementer-ci / gate (BAPI-951; nothing opened in dry-run)",
|
|
1327
1327
|
]);
|
|
1328
1328
|
}
|
|
1329
1329
|
// The FIRST durable mutation of the whole verb, deliberately placed here: every
|