@bridge_gpt/mcp-server 0.2.53 → 0.2.55
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 +116 -56
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-launchers/claude.js +3 -3
- package/build/agent-launchers/prompt.js +8 -11
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +5 -2
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/base-ref.js +33 -9
- package/build/bounded-wait.js +174 -0
- package/build/codex-skill-adapter.js +55 -0
- package/build/commands.generated.js +6 -5
- package/build/conductor/bridge-api-client.js +235 -14
- package/build/conductor/bring-up-facts.js +187 -0
- package/build/conductor/claude-hook.js +7 -5
- package/build/conductor/cli.js +28 -0
- package/build/conductor/doctor.js +80 -9
- package/build/conductor/epic-implementer-cli.js +1298 -0
- package/build/conductor/epic-runtime.js +134 -98
- package/build/conductor/errors.js +2 -2
- package/build/conductor/git-ci-types.js +1 -1
- package/build/conductor/git-hooks.js +28 -14
- package/build/conductor/install-doctor.js +11 -5
- package/build/conductor/readiness-cli.js +10 -10
- package/build/conductor/readiness-sections.js +58 -9
- package/build/conductor/readiness.js +120 -4
- package/build/conductor/recovery-cli.js +289 -10
- package/build/conductor/recovery-operations.js +125 -2
- package/build/conductor/repair-contract.js +58 -0
- package/build/conductor/run-branch.js +137 -0
- package/build/conductor/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- package/build/conductor/test-run-branch-vectors.js +165 -0
- package/build/conductor-bin.js +2 -139
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +31 -0
- package/build/conductor-removed-stub-bin.js +30 -0
- package/build/docs.generated.js +1 -1
- package/build/doctor.js +145 -18
- package/build/drive-epic.js +752 -90
- package/build/epic-implementer-bin.js +145 -0
- package/build/epic-implementer-bundle-cli.js +264 -0
- package/build/epic-implementer-claude-hook-bin.js +3 -0
- package/build/epic-integration-pr.js +5 -3
- package/build/executor/claim-scope.js +104 -0
- package/build/executor/cli.js +14 -25
- package/build/executor/env-file-guard.js +82 -3
- package/build/executor/env.js +6 -0
- package/build/executor/interrupted-worktree.js +60 -0
- package/build/executor/job-errors.js +45 -0
- package/build/executor/job-runner.js +334 -9
- package/build/executor/job-types.js +25 -9
- package/build/executor/merge-tree-classifier.js +171 -0
- package/build/executor/reconcile-overlap-governance.js +129 -0
- package/build/executor/reconcile-overlap-job.js +989 -0
- package/build/executor/reconcile-overlap-types.js +14 -0
- package/build/executor/spawn-job-driver.js +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/worker-finalization.js +25 -2
- package/build/executor/worker-guard-hook.js +15 -7
- package/build/implement-epic/bridge-client.js +773 -0
- package/build/implement-epic/checkpoint-store.js +542 -0
- package/build/implement-epic/cli.js +3158 -0
- package/build/implement-epic/cut-protocol.js +392 -0
- package/build/implement-epic/lock.js +302 -0
- package/build/implement-epic/pr-state.js +286 -0
- package/build/implement-epic/spawn.js +113 -0
- package/build/index.js +657 -481
- package/build/init.js +72 -8
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +403 -70
- package/build/local-artifact-storage.js +130 -0
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +1 -21
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +23 -16
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +321 -41
- package/build/plane/manifest.js +209 -1
- package/build/plane/member-roster.js +70 -0
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +18 -5
- package/build/plane/status.js +35 -1
- package/build/plane/supervisor.js +546 -164
- package/build/plane/types.js +25 -2
- package/build/polling-policy.js +72 -0
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/review-generation.js +219 -0
- package/build/run-unit-tests-launcher.js +6 -1
- package/build/setup-epic.js +567 -38
- package/build/start-tickets-conductor.js +8 -7
- package/build/ticket-key-utils.js +4 -3
- package/build/ticket-review-artifact-gate.js +461 -0
- package/build/upgrade-cli.js +5 -26
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/docs/install/mcp-tool-integrations.md +23 -1
- package/package.json +5 -3
- package/pipelines/review-ticket.json +17 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Epic Supervisor stateless reconciliation tick (BAPI-408).
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
4
|
+
* `epic-implementer epic-tick --epic-key <key>` runs one stateless pass:
|
|
5
5
|
* 1. Claim the epic supervision lease (single-writer guard)
|
|
6
6
|
* 2. Drift check (log if fired materially late)
|
|
7
7
|
* 3. Rebuild observed state from Postgres + local ledger
|
|
@@ -39,6 +39,7 @@ import { createDefaultStartTicketsDeps, orchestrateStartTickets } from "../start
|
|
|
39
39
|
import { orchestrateReviewTickets } from "../review-tickets.js";
|
|
40
40
|
import { createStartTicketsConductorContext, provisionConductorHooksForRows, emitStartTicketsRunStarted, } from "../start-tickets-conductor.js";
|
|
41
41
|
import { validateBranchName } from "../base-ref.js";
|
|
42
|
+
import { resolveDeclaredRunBaseBranch } from "./run-branch.js";
|
|
42
43
|
// ---------------------------------------------------------------------------
|
|
43
44
|
// Constants
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
@@ -1108,15 +1109,26 @@ export async function runEpicTick(options, deps = {}) {
|
|
|
1108
1109
|
// tickets, gated by quiescence. Invert the impl-dispatch maps so ledger
|
|
1109
1110
|
// events can be attributed back to a ticket for local-first PR binding.
|
|
1110
1111
|
const dispatchedBackstopPolicy = resolveDispatchedBackstopPolicy(epicRunState.epic_run.policy_json);
|
|
1111
|
-
// BAPI-586: resolve the run's
|
|
1112
|
-
// can catch a wrong-base PR (one not targeting the run
|
|
1113
|
-
// reconciliation time — the backstop to the executor's own
|
|
1114
|
-
// guard.
|
|
1115
|
-
//
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1112
|
+
// BAPI-586/BAPI-1127: resolve the run's EFFECTIVE base branch so the
|
|
1113
|
+
// done-gate pass can catch a wrong-base PR (one not targeting the run
|
|
1114
|
+
// base) at reconciliation time — the backstop to the executor's own
|
|
1115
|
+
// finalization guard.
|
|
1116
|
+
//
|
|
1117
|
+
// BAPI-1127 moved branch-name validation out of the shared declaration
|
|
1118
|
+
// resolver and into this operational wrapper, so this call site now owns
|
|
1119
|
+
// the fail-closed disposition it used to inherit. It fails CLOSED: when
|
|
1120
|
+
// the declared base is not a usable ref the pass is SKIPPED entirely
|
|
1121
|
+
// rather than run with the base comparison silently disabled. A pass that
|
|
1122
|
+
// cannot verify a PR's base cannot honestly admit one as done, and the
|
|
1123
|
+
// wrong-base PR this check exists to catch is exactly what would slip
|
|
1124
|
+
// through. The skip happens BEFORE any PR binding resolution, CI
|
|
1125
|
+
// observation, or provider call, and reports only the failed rule.
|
|
1126
|
+
const runBaseResolution = resolveEffectiveRunBaseBranch(epicRunState.epic_run.policy_json);
|
|
1127
|
+
if (!runBaseResolution.ok) {
|
|
1128
|
+
errorLog(`[epic-tick] done-gate pass skipped for ${epic_key}: ${runBaseResolution.error}`);
|
|
1129
|
+
}
|
|
1118
1130
|
const doneGateExpectedBaseBranch = runBaseResolution.ok
|
|
1119
|
-
? runBaseResolution.
|
|
1131
|
+
? runBaseResolution.effectiveBaseBranch
|
|
1120
1132
|
: undefined;
|
|
1121
1133
|
const ticketForRunId = new Map();
|
|
1122
1134
|
for (const [tk, rid] of ticketRunIdMap)
|
|
@@ -1134,63 +1146,65 @@ export async function runEpicTick(options, deps = {}) {
|
|
|
1134
1146
|
policy: dispatchedBackstopPolicy,
|
|
1135
1147
|
resolveImplRunId: resolveTicketRunId,
|
|
1136
1148
|
});
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1149
|
+
if (runBaseResolution.ok) {
|
|
1150
|
+
await runConductorDoneGatePass(observed.ticket_statuses, {
|
|
1151
|
+
observePrCi: observePrCiSeamFn,
|
|
1152
|
+
// BAPI-586: the run base so the done-gate pass fails a wrong-base PR
|
|
1153
|
+
// before CI/review evaluation (reconciliation backstop to executor
|
|
1154
|
+
// finalization).
|
|
1155
|
+
expectedBaseBranch: doneGateExpectedBaseBranch,
|
|
1156
|
+
resolvePrBinding,
|
|
1157
|
+
// BAPI-525 Change B: local-first binding + quiescence gate for the new
|
|
1158
|
+
// dispatched/running admission (policy default OFF ⇒ no behavior change).
|
|
1159
|
+
resolveActivePrBinding,
|
|
1160
|
+
dispatchedBackstopPolicy,
|
|
1161
|
+
resolveActiveTicketQuiescence,
|
|
1162
|
+
// BAPI-487: re-evaluate a blocked ticket only when its PR head advanced
|
|
1163
|
+
// past the head recorded on its latest blocking signal.
|
|
1164
|
+
resolveBlockedHeadSha: (tk) => observed.ticket_blocked_heads?.get(tk) ?? null,
|
|
1165
|
+
resolveRunId: resolveTicketRunId,
|
|
1166
|
+
resolveWorkerId: resolveDoneGateWorkerId,
|
|
1167
|
+
// BAPI-494: convert a detected conflict into a durable, head-scoped
|
|
1168
|
+
// `merge.conflict` ledger event stamped with the ticket's dispatch run/worker
|
|
1169
|
+
// so the fold correlates it and the remediation pass redispatches. Emitted via
|
|
1170
|
+
// the shared injectable emitter, idempotent per conflict head. Folded next tick
|
|
1171
|
+
// (this pass runs after rebuildObservedState), matching the gate.met latency.
|
|
1172
|
+
emitConflictSignal: (input) => {
|
|
1173
|
+
emitConductorEventFn({
|
|
1174
|
+
source: MERGE_CONFLICT_EVENT_SOURCE,
|
|
1175
|
+
type: "merge.conflict",
|
|
1176
|
+
subject: input.ticketKey,
|
|
1177
|
+
run_id: input.runId,
|
|
1178
|
+
worker_id: input.workerId,
|
|
1179
|
+
producer: MERGE_CONFLICT_EVENT_PRODUCER,
|
|
1180
|
+
observed_via: "supervisor",
|
|
1181
|
+
time: new Date(nowFn()).toISOString(),
|
|
1182
|
+
data: {
|
|
1183
|
+
summary: `PR #${input.prNumber} for ${input.ticketKey} is not mergeable`,
|
|
1184
|
+
status: "blocked",
|
|
1185
|
+
reason: "merge.conflict",
|
|
1186
|
+
details: {
|
|
1187
|
+
epic_key,
|
|
1188
|
+
ticket_key: input.ticketKey,
|
|
1189
|
+
repo: input.repoName,
|
|
1190
|
+
pr_number: input.prNumber,
|
|
1191
|
+
head_sha: input.headSha,
|
|
1192
|
+
mergeable: input.mergeable,
|
|
1193
|
+
mergeStateStatus: input.mergeStateStatus,
|
|
1194
|
+
},
|
|
1181
1195
|
},
|
|
1182
|
-
},
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1196
|
+
}, {
|
|
1197
|
+
event_type: "merge.conflict",
|
|
1198
|
+
run_id: input.runId ?? undefined,
|
|
1199
|
+
commit_sha: input.headSha,
|
|
1200
|
+
});
|
|
1201
|
+
},
|
|
1202
|
+
access,
|
|
1203
|
+
env,
|
|
1204
|
+
log,
|
|
1205
|
+
errorLog,
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1194
1208
|
const maxSeqForRun = (runId) => {
|
|
1195
1209
|
let maxSeq = 0;
|
|
1196
1210
|
for (const ev of localEvents) {
|
|
@@ -1554,39 +1568,46 @@ export async function runEpicTick(options, deps = {}) {
|
|
|
1554
1568
|
}
|
|
1555
1569
|
}
|
|
1556
1570
|
/**
|
|
1557
|
-
* BAPI-586: resolve the epic run's
|
|
1558
|
-
*
|
|
1559
|
-
*
|
|
1560
|
-
*
|
|
1571
|
+
* BAPI-586/BAPI-1127: resolve the epic run's EFFECTIVE base branch — the branch
|
|
1572
|
+
* every fresh dispatch cuts from and every child PR targets.
|
|
1573
|
+
*
|
|
1574
|
+
* Two layers, deliberately kept apart:
|
|
1561
1575
|
*
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
1565
|
-
*
|
|
1566
|
-
*
|
|
1576
|
+
* 1. DECLARATION, delegated in full to the dependency-free
|
|
1577
|
+
* {@link resolveDeclaredRunBaseBranch} leaf, which is the shared cross-runtime
|
|
1578
|
+
* contract this function does not get a second opinion about. The ordered
|
|
1579
|
+
* `base_branch` → `baseBranch` scan, the skip-and-continue treatment of a
|
|
1580
|
+
* non-string or blank candidate, and the trim all live there.
|
|
1581
|
+
* 2. OPERATIONAL POLICY, which is this function's own and is NOT shared with
|
|
1582
|
+
* Python: an undeclared policy defaults to `main`, and the branch that will
|
|
1583
|
+
* actually be handed to git is validated before it is returned.
|
|
1584
|
+
*
|
|
1585
|
+
* The frozen dispositions (BAPI-1127) show up here as follows:
|
|
1586
|
+
*
|
|
1587
|
+
* - A NON-STRING candidate is absent, not fatal. `{base_branch: 42,
|
|
1588
|
+
* baseBranch: "epic/X"}` now resolves to `epic/X`; before BAPI-1127 it failed
|
|
1589
|
+
* closed, and `{base_branch: "", baseBranch: "epic/X"}` reported `main` — a
|
|
1590
|
+
* different real branch from the one the server provisioned.
|
|
1591
|
+
* - A MALFORMED declared name does NOT fall back to `main`. Silently
|
|
1592
|
+
* substituting the default for a bad declaration is how a wrong base becomes
|
|
1593
|
+
* invisible (the BAPI-586 defect), so it fails closed and dispatch refuses.
|
|
1594
|
+
* Only an ACTUAL absence of a declaration reaches the default.
|
|
1595
|
+
*
|
|
1596
|
+
* Validation happens here, once, ahead of both operational consumers (dispatch
|
|
1597
|
+
* and the done gate) rather than inside the shared resolver, so that Python and
|
|
1598
|
+
* TypeScript agree about what a policy declares while each keeps its own
|
|
1599
|
+
* judgment about what is safe to use. The failure carries only the rule that
|
|
1600
|
+
* failed — never the policy document or an unbounded branch value.
|
|
1567
1601
|
*/
|
|
1568
|
-
export function
|
|
1602
|
+
export function resolveEffectiveRunBaseBranch(policyJson) {
|
|
1569
1603
|
const DEFAULT_BASE = "main";
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
const raw = policyJson.base_branch ??
|
|
1574
|
-
policyJson.baseBranch;
|
|
1575
|
-
if (raw === undefined || raw === null) {
|
|
1576
|
-
return { ok: true, baseBranch: DEFAULT_BASE };
|
|
1577
|
-
}
|
|
1578
|
-
if (typeof raw !== "string") {
|
|
1579
|
-
return { ok: false, error: "epic run policy base_branch is present but is not a string." };
|
|
1580
|
-
}
|
|
1581
|
-
const trimmed = raw.trim();
|
|
1582
|
-
if (trimmed.length === 0) {
|
|
1583
|
-
return { ok: true, baseBranch: DEFAULT_BASE };
|
|
1584
|
-
}
|
|
1585
|
-
const validationError = validateBranchName(trimmed);
|
|
1604
|
+
const declaredBaseBranch = resolveDeclaredRunBaseBranch(policyJson);
|
|
1605
|
+
const effectiveBaseBranch = declaredBaseBranch ?? DEFAULT_BASE;
|
|
1606
|
+
const validationError = validateBranchName(effectiveBaseBranch);
|
|
1586
1607
|
if (validationError) {
|
|
1587
1608
|
return { ok: false, error: `epic run policy base_branch is invalid: ${validationError}` };
|
|
1588
1609
|
}
|
|
1589
|
-
return { ok: true,
|
|
1610
|
+
return { ok: true, declaredBaseBranch, effectiveBaseBranch };
|
|
1590
1611
|
}
|
|
1591
1612
|
/**
|
|
1592
1613
|
* Build the production EpicRuntimeDeps for use inside `runEpicTickCommand`.
|
|
@@ -1615,13 +1636,23 @@ export async function buildProductionEpicRuntimeDeps(epicKey) {
|
|
|
1615
1636
|
// error (observe-only would already default to `main`); a malformed configured
|
|
1616
1637
|
// base is captured as an error and re-raised at dispatch time so a bad base
|
|
1617
1638
|
// never silently dispatches.
|
|
1618
|
-
|
|
1639
|
+
//
|
|
1640
|
+
// BAPI-1127: the fetch-failure fallback declares NOTHING and dispatches from
|
|
1641
|
+
// `main`, which is what "we could not read the policy" honestly means — the
|
|
1642
|
+
// two values are kept separate so a later reader cannot mistake the default
|
|
1643
|
+
// for a declaration the run never made.
|
|
1644
|
+
const UNDECLARED_MAIN = {
|
|
1645
|
+
ok: true,
|
|
1646
|
+
declaredBaseBranch: undefined,
|
|
1647
|
+
effectiveBaseBranch: "main",
|
|
1648
|
+
};
|
|
1649
|
+
let cachedRunBase = UNDECLARED_MAIN;
|
|
1619
1650
|
try {
|
|
1620
1651
|
const runState = await fetchEpicRunState(access, epicKey);
|
|
1621
|
-
cachedRunBase =
|
|
1652
|
+
cachedRunBase = resolveEffectiveRunBaseBranch(runState.epic_run.policy_json);
|
|
1622
1653
|
}
|
|
1623
1654
|
catch {
|
|
1624
|
-
cachedRunBase =
|
|
1655
|
+
cachedRunBase = UNDECLARED_MAIN;
|
|
1625
1656
|
}
|
|
1626
1657
|
// Shared closure state populated by fetchPlan and consumed by dispatchSeam.
|
|
1627
1658
|
let cachedPlanVersion = 0;
|
|
@@ -1687,12 +1718,17 @@ export async function buildProductionEpicRuntimeDeps(epicKey) {
|
|
|
1687
1718
|
if (cachedPlanVersion === 0) {
|
|
1688
1719
|
throw new Error(`dispatchSeam called before fetchPlan for epic ${ek} ticket ${tk}; cachedPlanVersion is 0`);
|
|
1689
1720
|
}
|
|
1690
|
-
// BAPI-586: fail CLOSED on a malformed configured run base rather
|
|
1691
|
-
// dispatching a worker (and opening a PR) against a bad base.
|
|
1721
|
+
// BAPI-586/BAPI-1127: fail CLOSED on a malformed configured run base rather
|
|
1722
|
+
// than dispatching a worker (and opening a PR) against a bad base. The check
|
|
1723
|
+
// is here — ahead of the dispatch claim, the spawn command, and any git or
|
|
1724
|
+
// provider work — because `resolveEffectiveRunBaseBranch` validates the ref
|
|
1725
|
+
// but this is the boundary that actually uses it. Only `effectiveBaseBranch`
|
|
1726
|
+
// (declaration, else `main`) is handed on; the declaration itself stays
|
|
1727
|
+
// separately readable on `cachedRunBase` and is never overwritten.
|
|
1692
1728
|
if (!cachedRunBase.ok) {
|
|
1693
1729
|
throw new Error(`invalid configured base branch for epic ${ek}: ${cachedRunBase.error}`);
|
|
1694
1730
|
}
|
|
1695
|
-
const runBaseBranch = cachedRunBase.
|
|
1731
|
+
const runBaseBranch = cachedRunBase.effectiveBaseBranch;
|
|
1696
1732
|
// BAPI-441: a remediation re-dispatch (attempt > 0) reuses the existing
|
|
1697
1733
|
// branch/worktree (resume mode) and claims an attempt-scoped dispatch key so
|
|
1698
1734
|
// it is not deduped against the original epic dispatch.
|
|
@@ -62,7 +62,7 @@ export class ConductorStoreError extends Error {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* Raised when the frozen v1 `
|
|
65
|
+
* Raised when the frozen v1 `epic-implementer epic-tick` entrypoint is invoked
|
|
66
66
|
* (BAPI-534, Epic Conductor v2 slice 0). The v1 local reconciler/ledger tick is
|
|
67
67
|
* frozen: Epic Conductor v2 replaces it with a server-side reconciler plus the
|
|
68
68
|
* local executor (`mcp-server executor`). The message points operators at the v2
|
|
@@ -70,7 +70,7 @@ export class ConductorStoreError extends Error {
|
|
|
70
70
|
*/
|
|
71
71
|
export class ConductorEpicTickV1FrozenError extends Error {
|
|
72
72
|
constructor() {
|
|
73
|
-
super("The v1 `
|
|
73
|
+
super("The v1 `epic-implementer epic-tick` path is frozen (EPIC_TICK_V1_FROZEN). Epic " +
|
|
74
74
|
"Conductor v2 replaces the local epic tick with a server-side reconciler " +
|
|
75
75
|
"plus the local executor — run `mcp-server executor` instead.");
|
|
76
76
|
this.name = "ConductorEpicTickV1FrozenError";
|
|
@@ -155,7 +155,7 @@ export function stableJsonHash(value) {
|
|
|
155
155
|
* The two literals are exported as named constants so no consumer spells them
|
|
156
156
|
* inline — a typo in one consumer would otherwise silently disagree with the
|
|
157
157
|
* parser about which disposition is configured. The Python mirror lives in
|
|
158
|
-
* `api/library/epic_conductor/
|
|
158
|
+
* `api/library/epic_conductor/epic_implementer_readiness.py` and must carry the same
|
|
159
159
|
* closed set.
|
|
160
160
|
*/
|
|
161
161
|
export const VERDICTLESS_DISPOSITION_PARK = "park";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Managed, non-blocking local git hook installation and verification (BAPI-395).
|
|
3
3
|
*
|
|
4
|
-
* `
|
|
4
|
+
* `epic-implementer install-git-hooks` installs opportunistic `post-commit` and
|
|
5
5
|
* `reference-transaction` hooks that launch the conductor producer in the
|
|
6
6
|
* background and never block the user's git workflow. Hooks are LOCAL and
|
|
7
7
|
* unversioned: only a clearly-delimited managed block is inserted/replaced, all
|
|
@@ -36,8 +36,13 @@ export function resolveGitHooksDirectory(deps = {}) {
|
|
|
36
36
|
const absoluteCommonDir = isAbsolute(commonDir) ? commonDir : resolve(cwd, commonDir);
|
|
37
37
|
return { is_worktree: true, hooks_dir: resolve(absoluteCommonDir, "hooks") };
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
40
|
-
|
|
39
|
+
/**
|
|
40
|
+
* The file name every managed hook body must invoke. S4 (BAPI-1080): this is
|
|
41
|
+
* the renamed REAL event-ledger bin output; the retained `conductor-bin.js`
|
|
42
|
+
* output name is now a fixed, failing migration stub and must never be
|
|
43
|
+
* resolved here.
|
|
44
|
+
*/
|
|
45
|
+
export const CONDUCTOR_BIN_FILENAME = "epic-implementer-bin.js";
|
|
41
46
|
/**
|
|
42
47
|
* The sanitized failure reason. Names the layout categories that were searched
|
|
43
48
|
* so the operator can act, and nothing else — no probed path, no caught error
|
|
@@ -48,18 +53,19 @@ export const CONDUCTOR_HOOK_BIN_UNRESOLVED_REASON = `no usable ${CONDUCTOR_BIN_F
|
|
|
48
53
|
`the invocation target when it is named ${CONDUCTOR_BIN_FILENAME}). ` +
|
|
49
54
|
"Reinstall or rebuild the package so a conductor binary ships beside the executing module.";
|
|
50
55
|
/**
|
|
51
|
-
* Absolute, canonical path of the `
|
|
52
|
-
* that is currently executing (BAPI-772, the BAPI-768 pattern applied
|
|
56
|
+
* Absolute, canonical path of the `epic-implementer-bin.js` belonging to the
|
|
57
|
+
* artifact that is currently executing (BAPI-772, the BAPI-768 pattern applied
|
|
58
|
+
* to hooks; renamed from `conductor-bin.js` by S4/BAPI-1080).
|
|
53
59
|
*
|
|
54
60
|
* ## Why this cannot be path arithmetic
|
|
55
61
|
*
|
|
56
62
|
* The old implementation hard-coded ONE layout — `dirname(import.meta.url)/../
|
|
57
63
|
* conductor-bin.js`, the tsc emit — and returned it unverified. In the shipped
|
|
58
|
-
* esbuild bundle this module IS `build/
|
|
59
|
-
* landed on `<pkg>/
|
|
64
|
+
* esbuild bundle this module IS `build/epic-implementer-bin.js`, so the directory hop
|
|
65
|
+
* landed on `<pkg>/epic-implementer-bin.js`, a path that has never existed. Every hook
|
|
60
66
|
* installed from the published package embedded that dead path, and because hook
|
|
61
67
|
* bodies discard output and end in `|| true`, each commit ENOENTed in silence
|
|
62
|
-
* while `
|
|
68
|
+
* while `epic-implementer doctor` still called the hooks healthy.
|
|
63
69
|
*
|
|
64
70
|
* So this resolver PROBES. Candidates are drawn only from the executing module's
|
|
65
71
|
* own URL and Node's actual invocation target — never `process.cwd()` or an
|
|
@@ -82,13 +88,13 @@ export function resolveConductorHookBin(overrides = {}) {
|
|
|
82
88
|
// Bundled layout: the bin sits beside the executing module (and in the
|
|
83
89
|
// single-file bundle it IS the executing module).
|
|
84
90
|
candidates.push(join(dir, CONDUCTOR_BIN_FILENAME));
|
|
85
|
-
// tsc layout: `build/conductor/git-hooks.js` → `build/
|
|
91
|
+
// tsc layout: `build/conductor/git-hooks.js` → `build/epic-implementer-bin.js`.
|
|
86
92
|
candidates.push(join(dir, "..", CONDUCTOR_BIN_FILENAME));
|
|
87
93
|
}
|
|
88
94
|
// Last resort, and only ever a validated one: a packaged install launches
|
|
89
95
|
// through a `node_modules/.bin` symlink. Accepted only when the invocation
|
|
90
|
-
// target is exactly the
|
|
91
|
-
// must never become a hook target.
|
|
96
|
+
// target is exactly the epic-implementer bin — `index.js` or
|
|
97
|
+
// `epic-implementer-bin.js.bak` must never become a hook target.
|
|
92
98
|
if (typeof deps.argv1 === "string" && deps.argv1.length > 0) {
|
|
93
99
|
if (basename(deps.argv1) === CONDUCTOR_BIN_FILENAME)
|
|
94
100
|
candidates.push(deps.argv1);
|
|
@@ -379,14 +385,22 @@ export function inspectConductorGitHooks(deps = {}) {
|
|
|
379
385
|
// embedded target READ-ONLY so a dead hook is visible instead of
|
|
380
386
|
// reported healthy. Nothing here rewrites or canonicalizes the hook.
|
|
381
387
|
embeddedBinPath = extractManagedHookBinPath(content);
|
|
382
|
-
|
|
388
|
+
// S4 (BAPI-1080): a hook installed before the rename still embeds
|
|
389
|
+
// the old `conductor-bin.js` path, which still exists post-upgrade
|
|
390
|
+
// as the migration stub — stat-true but dead. Require the embedded
|
|
391
|
+
// path's basename to be the current real bin so a stale hook is
|
|
392
|
+
// correctly flagged degraded instead of reported healthy.
|
|
393
|
+
embeddedBinIsFile =
|
|
394
|
+
embeddedBinPath !== null &&
|
|
395
|
+
basename(embeddedBinPath) === CONDUCTOR_BIN_FILENAME &&
|
|
396
|
+
defaultIsFile(embeddedBinPath);
|
|
383
397
|
if (embeddedBinPath === null) {
|
|
384
398
|
warnings.push(`${name} hook has a managed block with no recognizable conductor command; ` +
|
|
385
|
-
"reinstall with `
|
|
399
|
+
"reinstall with `epic-implementer install-git-hooks`");
|
|
386
400
|
}
|
|
387
401
|
else if (!embeddedBinIsFile) {
|
|
388
402
|
warnings.push(`${name} hook points at a missing or invalid conductor binary (${embeddedBinPath}); ` +
|
|
389
|
-
"the hook is dead — reinstall with `
|
|
403
|
+
"the hook is dead — reinstall with `epic-implementer install-git-hooks`");
|
|
390
404
|
}
|
|
391
405
|
}
|
|
392
406
|
}
|
|
@@ -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 { conductorHookTargetsSection, conductorLedgerLoadabilitySection, conductorProfileTokenSection, CONDUCTOR_PROFILE_TOKEN, detectConductorContext, executorObservationSection, executorProvisioningSection, executorServiceStateSection, githubActionsSection, githubCredentialsSection, inspectWorkflowPresence, reconcilerSection, supervisorSection, workflowSection, } from "./readiness-sections.js";
|
|
39
|
+
import { bringUpSafetySections, conductorHookTargetsSection, conductorLedgerLoadabilitySection, conductorProfileTokenSection, CONDUCTOR_PROFILE_TOKEN, detectConductorContext, executorObservationSection, executorProvisioningSection, executorServiceStateSection, githubActionsSection, 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.
|
|
@@ -89,8 +89,8 @@ export async function runConductorInstallDoctor(deps) {
|
|
|
89
89
|
id: "conductor-ledger",
|
|
90
90
|
label: "Conductor ledger doctor",
|
|
91
91
|
status: "degraded",
|
|
92
|
-
detail: "the local
|
|
93
|
-
remediation: "run `
|
|
92
|
+
detail: "the local epic-implementer doctor could not be built",
|
|
93
|
+
remediation: "run `epic-implementer doctor` directly to see why the local ledger report failed.",
|
|
94
94
|
});
|
|
95
95
|
}
|
|
96
96
|
const legacyExecutorPreflight = executorPreflightSettled.value ?? null;
|
|
@@ -161,6 +161,10 @@ export async function runConductorInstallDoctor(deps) {
|
|
|
161
161
|
const workflow = await inspectWorkflowPresence(deps.readWorkflowFile, deps.classifyWorkflowDrift);
|
|
162
162
|
sections.push(workflowSection(workflow.presence, deps.reviewPolicySource, workflow.driftDetail));
|
|
163
163
|
if (readiness) {
|
|
164
|
+
// BAPI-1153 — the "Bring-up safety" group, ahead of the repository-scoped
|
|
165
|
+
// reconciler/executor story: these three decide whether a plane can drive a
|
|
166
|
+
// run at all, and `setup-epic` refuses on them before a run exists.
|
|
167
|
+
sections.push(...bringUpSafetySections(readiness));
|
|
164
168
|
sections.push(reconcilerSection(readiness));
|
|
165
169
|
}
|
|
166
170
|
// --- Executor provisioning (BAPI-688): real, read-only unit detection -----
|
|
@@ -326,9 +330,11 @@ export function conductorInstallDoctorHasFatal(report) {
|
|
|
326
330
|
//
|
|
327
331
|
// The section contract and every pure section builder now live in
|
|
328
332
|
// `readiness-sections.ts`, so the install-time doctor and the advisory
|
|
329
|
-
// `
|
|
333
|
+
// `epic-implementer readiness` gate share ONE adapter set. They are re-exported from
|
|
330
334
|
// here unchanged: every existing import of `ConductorInstallSection`, the
|
|
331
335
|
// remediation constants, or the installer reporting APIs from
|
|
332
336
|
// `conductor/install-doctor.js` keeps resolving exactly as before.
|
|
333
337
|
// ---------------------------------------------------------------------------
|
|
334
|
-
export { CONDUCTOR_OPERATOR_RUNBOOK_POINTER, EXECUTOR_PROVISIONING_GUIDANCE, CONDUCTOR_PROFILE_TOKEN, CONDUCTOR_PROFILE_TOKEN_NO_ENTRY_REMEDIATION, CONDUCTOR_PROFILE_TOKEN_REMEDIATION, CONDUCTOR_HOOK_TARGET_REMEDIATION, CONDUCTOR_LEDGER_LOADABILITY_REMEDIATION, EXECUTOR_INSTALL_SERVICE_REMEDIATION, EXECUTOR_SERVICE_START_REMEDIATION, } from "./readiness-sections.js";
|
|
338
|
+
export { BRING_UP_SECTION_LABELS, bringUpSafetySections, databaseMigrationCurrentSection, singleTransitionAuthoritySection, workerPresentSection, CONDUCTOR_OPERATOR_RUNBOOK_POINTER, EXECUTOR_PROVISIONING_GUIDANCE, CONDUCTOR_PROFILE_TOKEN, CONDUCTOR_PROFILE_TOKEN_NO_ENTRY_REMEDIATION, CONDUCTOR_PROFILE_TOKEN_REMEDIATION, CONDUCTOR_HOOK_TARGET_REMEDIATION, CONDUCTOR_LEDGER_LOADABILITY_REMEDIATION, EXECUTOR_INSTALL_SERVICE_REMEDIATION, EXECUTOR_SERVICE_START_REMEDIATION, } from "./readiness-sections.js";
|
|
339
|
+
// BAPI-1153 — the bring-up remediations the three sections above render.
|
|
340
|
+
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";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `
|
|
2
|
+
* The `epic-implementer readiness` command surface (BAPI-1055, AC-6).
|
|
3
3
|
*
|
|
4
4
|
* A CLI subcommand, deliberately, and NOT an MCP tool. Every peer diagnostic in
|
|
5
5
|
* this package — `doctor`, `plane`, `drive-epic`, `executor`, `install` — is an
|
|
@@ -40,13 +40,13 @@ import { claudeReviewWorkflowPath } from "../claude-review-workflow.js";
|
|
|
40
40
|
import { runDefaultPlanePreflight } from "../plane/cli.js";
|
|
41
41
|
/** Fixed notice emitted to stderr when a source could not be collected. */
|
|
42
42
|
export const READINESS_COLLECTION_NOTICE = "one or more readiness sources could not be collected; the affected prerequisites are reported as failures.";
|
|
43
|
-
/** Usage text for `
|
|
43
|
+
/** Usage text for `epic-implementer readiness`. */
|
|
44
44
|
export function getReadinessUsage() {
|
|
45
45
|
return [
|
|
46
|
-
"Usage:
|
|
46
|
+
"Usage: epic-implementer readiness [--json] [--no-deny-probe]",
|
|
47
47
|
"",
|
|
48
48
|
"Read-only ADVISORY report of every conductor prerequisite, consolidated from the",
|
|
49
|
-
"Bridge install checklist, the
|
|
49
|
+
"Bridge install checklist, the epic-implementer doctor, the plane preflight, and the",
|
|
50
50
|
"server conductor-readiness collector. Each prerequisite reports pass/warn/fail/skip",
|
|
51
51
|
"and, on a failure, exactly ONE named remediation.",
|
|
52
52
|
"",
|
|
@@ -56,12 +56,12 @@ export function getReadinessUsage() {
|
|
|
56
56
|
"",
|
|
57
57
|
"Options:",
|
|
58
58
|
" --json Print the versioned structured report as JSON",
|
|
59
|
-
" --no-deny-probe Accepted for parity with `
|
|
59
|
+
" --no-deny-probe Accepted for parity with `epic-implementer doctor`; inert here, because",
|
|
60
60
|
" this command never spawns the deny-enforcement probe",
|
|
61
61
|
" --help Print this usage message",
|
|
62
62
|
].join("\n");
|
|
63
63
|
}
|
|
64
|
-
/** Parse `
|
|
64
|
+
/** Parse `epic-implementer readiness` argv. Unknown flags and positionals are errors. */
|
|
65
65
|
export function parseReadinessArgs(argv) {
|
|
66
66
|
const parsed = { json: false, help: false, noDenyProbe: false };
|
|
67
67
|
for (const token of argv) {
|
|
@@ -72,7 +72,7 @@ export function parseReadinessArgs(argv) {
|
|
|
72
72
|
else if (token === "--help" || token === "-h")
|
|
73
73
|
parsed.help = true;
|
|
74
74
|
else
|
|
75
|
-
return { error: `Unknown argument "${token}". Run "
|
|
75
|
+
return { error: `Unknown argument "${token}". Run "epic-implementer readiness --help" for usage.` };
|
|
76
76
|
}
|
|
77
77
|
return parsed;
|
|
78
78
|
}
|
|
@@ -80,7 +80,7 @@ export function parseReadinessArgs(argv) {
|
|
|
80
80
|
* Build the production report for `repoRoot`.
|
|
81
81
|
*
|
|
82
82
|
* Every seam is a real, READ-ONLY one: the install doctor's own GET, the
|
|
83
|
-
*
|
|
83
|
+
* epic-implementer doctor's local inspections, and the same plane preflight
|
|
84
84
|
* `plane up` and `drive-epic` run. Nothing is re-implemented here.
|
|
85
85
|
*/
|
|
86
86
|
async function collectProductionReport(repoRoot, env) {
|
|
@@ -111,13 +111,13 @@ async function collectProductionReport(repoRoot, env) {
|
|
|
111
111
|
},
|
|
112
112
|
},
|
|
113
113
|
// The deny-enforcement probe is never spawned from this command; the check
|
|
114
|
-
// is reported as an explicit skip naming `
|
|
114
|
+
// is reported as an explicit skip naming `epic-implementer doctor` instead.
|
|
115
115
|
conductorDoctorDeps: { env, skipDenyProbe: true },
|
|
116
116
|
runPlanePreflight: () => runDefaultPlanePreflight(repoRoot, env),
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
/**
|
|
120
|
-
* Run `
|
|
120
|
+
* Run `epic-implementer readiness`.
|
|
121
121
|
*
|
|
122
122
|
* Returns 0 for every readiness outcome; see the module docstring. The only
|
|
123
123
|
* non-zero return is a usage error.
|