@bridge_gpt/mcp-server 0.2.54 → 0.2.56
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 +45 -59
- package/build/agent-capabilities/types.js +4 -0
- package/build/agent-guidance.generated.js +3 -0
- package/build/agent-launchers/claude-executor-adapter.js +3 -0
- package/build/agent-notes.js +178 -0
- package/build/agent-registry.js +25 -5
- package/build/agent-utils.js +58 -0
- package/build/agents.generated.js +1 -1
- package/build/codex-launch-scope.js +49 -0
- package/build/codex-skill-adapter.js +58 -0
- package/build/command-host.js +9 -0
- package/build/command-provisioning.js +35 -4
- package/build/commands.generated.js +9 -8
- package/build/conductor/bridge-api-client.js +199 -6
- 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 +1 -1
- 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 +37 -6
- 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/store.js +2 -2
- package/build/conductor/supervisor-runtime.js +1 -1
- 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 +81 -18
- package/build/drive-epic.js +541 -115
- 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/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 +274 -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 +617 -168
- package/build/init.js +74 -9
- package/build/install-bridge-conductor.js +5 -5
- package/build/install-bridge.js +425 -79
- package/build/mcp-host-config.js +22 -60
- package/build/mcp-host-entry-adapter.js +18 -0
- package/build/mcp-host-targets.js +19 -32
- package/build/mcp-provisioning.js +1 -1
- package/build/mcp-registration-doctor.js +1 -1
- package/build/merge-pull-request.js +1 -1
- package/build/pipelines.generated.js +7 -7
- package/build/plan-epic-conductor-eligibility.js +1 -1
- package/build/plane/cli.js +36 -5
- package/build/plane/preflight.js +128 -12
- package/build/plane/shutdown.js +4 -4
- package/build/readiness-check.js +3 -3
- package/build/readme.generated.js +1 -1
- package/build/review-tickets.js +50 -35
- package/build/run-unit-tests-launcher.js +1 -1
- package/build/setup-epic.js +69 -31
- package/build/start-tickets-conductor.js +8 -7
- package/build/start-tickets.js +266 -48
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +1 -1
- package/docs/CONDUCTOR.md +8 -6
- package/package.json +5 -3
package/build/review-tickets.js
CHANGED
|
@@ -18,11 +18,21 @@
|
|
|
18
18
|
* This path is worktree-free and base-branch-free; the human CLI path leaves
|
|
19
19
|
* `epic` unset and retains byte-for-byte identical behavior.
|
|
20
20
|
*/
|
|
21
|
-
import { createDefaultStartTicketsDeps, detectTerminal, runWithConcurrency, buildAgentInvocation, shSquoteInner, powershellSquote, isCommandOnPath, resolveFirstCommandOnPath, tmuxSessionNameForTicket, TICKET_KEY_PATTERN, DEFAULT_MAX_PARALLEL, validateBranchName, fetchAndResolveBaseSha, fetchStartTicketsConfigField, resolveStartTicketsBridgeApiAccess, } from "./start-tickets.js";
|
|
21
|
+
import { createDefaultStartTicketsDeps, detectTerminal, runWithConcurrency, buildAgentInvocation, shSquoteInner, powershellSquote, isCommandOnPath, resolveFirstCommandOnPath, tmuxSessionNameForTicket, TICKET_KEY_PATTERN, DEFAULT_MAX_PARALLEL, validateBranchName, fetchAndResolveBaseSha, fetchStartTicketsConfigField, resolveStartTicketsBridgeApiAccess, buildCommandProvisioningDeps, buildMcpProvisioningDeps, } from "./start-tickets.js";
|
|
22
|
+
import { provisionCommandsForWorktree } from "./command-provisioning.js";
|
|
23
|
+
import { resolveCodexLaunchScope } from "./codex-launch-scope.js";
|
|
22
24
|
import { WINDOWS_TERMINAL_COMMAND, WINDOWS_POWERSHELL_CANDIDATES, TMUX_COMMAND, isSupportedStartTicketsPlatform, unsupportedPlatformMessage, } from "./start-tickets-prereqs.js";
|
|
23
|
-
import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName,
|
|
25
|
+
import { DEFAULT_AGENT_NAME, resolveAgentSpec, isAgentName, isValidModelAlias, } from "./agent-registry.js";
|
|
24
26
|
import { injectConductorEnvIntoShellCommand, buildEpicIdentityEnv, mintStartTicketsRunId, } from "./start-tickets-conductor.js";
|
|
25
27
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
28
|
+
/** Every registered agent with a review workflow the launcher can provision. */
|
|
29
|
+
const REVIEW_TICKETS_AGENT_NAMES = ["claude", "cursor-agent", "codex"];
|
|
30
|
+
function isReviewTicketsAgentName(value) {
|
|
31
|
+
return REVIEW_TICKETS_AGENT_NAMES.includes(value);
|
|
32
|
+
}
|
|
33
|
+
function formatReviewTicketsAgentNames() {
|
|
34
|
+
return REVIEW_TICKETS_AGENT_NAMES.join(", ");
|
|
35
|
+
}
|
|
26
36
|
/** Default base context: no flags threaded through (matches pre-BAPI-474 behavior). */
|
|
27
37
|
const NO_BASE_CONTEXT = { noRefreshBase: false };
|
|
28
38
|
// ---------------------------------------------------------------------------
|
|
@@ -37,7 +47,7 @@ export function getReviewTicketsUsage() {
|
|
|
37
47
|
" --auto Auto-approve all review gates (global, applies to all tickets)",
|
|
38
48
|
" --rounds=1|2 Review depth: 1=single-pass, 2=full two-pass (default: adaptive — backend routes by difficulty)",
|
|
39
49
|
" --review KEY=auto,rounds=1 Per-ticket review mode override (repeatable)",
|
|
40
|
-
" --agent claude|cursor-agent
|
|
50
|
+
" --agent claude|cursor-agent|codex Agent command to launch in each tab (default: claude)",
|
|
41
51
|
" --model VALUE Model alias to pass as --model to the agent (optional passthrough)",
|
|
42
52
|
" --max-parallel N Max tabs to spawn concurrently (default: 3)",
|
|
43
53
|
" --dry-run Print intended commands; creates no tabs and opens no sessions",
|
|
@@ -289,10 +299,10 @@ export function parseReviewTicketsArgs(argv) {
|
|
|
289
299
|
return { status: "error", message: "--agent requires a value (an agent name)." };
|
|
290
300
|
}
|
|
291
301
|
}
|
|
292
|
-
if (!isAgentName(value)) {
|
|
302
|
+
if (!isAgentName(value) || !isReviewTicketsAgentName(value)) {
|
|
293
303
|
return {
|
|
294
304
|
status: "error",
|
|
295
|
-
message: `Invalid --agent value: '${value}' (allowed agents: ${
|
|
305
|
+
message: `Invalid --agent value: '${value}' (allowed agents: ${formatReviewTicketsAgentNames()}).`,
|
|
296
306
|
};
|
|
297
307
|
}
|
|
298
308
|
agentName = value;
|
|
@@ -416,7 +426,7 @@ export function resolveEffectiveReviewMode(key, options) {
|
|
|
416
426
|
// hard-wired to `/implement-ticket`. Review-aware builders are used instead so
|
|
417
427
|
// the prompt uses `/review-ticket` and no directory change is prepended.
|
|
418
428
|
// ---------------------------------------------------------------------------
|
|
419
|
-
export function buildReviewTicketPrompt(key, mode, base = NO_BASE_CONTEXT) {
|
|
429
|
+
export function buildReviewTicketPrompt(key, mode, base = NO_BASE_CONTEXT, agentName = "claude") {
|
|
420
430
|
const autoFlag = mode.auto ? " --auto" : "";
|
|
421
431
|
// Omit --rounds entirely when adaptive (mode.rounds === undefined) so the
|
|
422
432
|
// backend routes by difficulty; only forward it for an explicit 1/2 override.
|
|
@@ -424,22 +434,23 @@ export function buildReviewTicketPrompt(key, mode, base = NO_BASE_CONTEXT) {
|
|
|
424
434
|
const baseBranchFlag = base.baseBranch ? ` --base-branch=${base.baseBranch}` : "";
|
|
425
435
|
const baseShaFlag = base.baseSha ? ` --base-sha=${base.baseSha}` : "";
|
|
426
436
|
const noRefreshFlag = base.noRefreshBase ? " --no-refresh-base" : "";
|
|
427
|
-
|
|
437
|
+
const command = agentName === "codex" ? "$bridge-review-ticket" : "/review-ticket";
|
|
438
|
+
return `${command} ${key}${autoFlag}${roundsFlag}${baseBranchFlag}${baseShaFlag}${noRefreshFlag}`;
|
|
428
439
|
}
|
|
429
|
-
export function buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias, base = NO_BASE_CONTEXT) {
|
|
430
|
-
const prompt = buildReviewTicketPrompt(key, mode, base);
|
|
431
|
-
const invocation = buildAgentInvocation(agent, prompt, (p) => `'${shSquoteInner(p)}'`, modelAlias);
|
|
440
|
+
export function buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias, base = NO_BASE_CONTEXT, launchArgs = []) {
|
|
441
|
+
const prompt = buildReviewTicketPrompt(key, mode, base, agent.name);
|
|
442
|
+
const invocation = buildAgentInvocation(agent, prompt, (p) => `'${shSquoteInner(p)}'`, modelAlias, launchArgs);
|
|
432
443
|
return `cd '${shSquoteInner(cwd)}' && ${invocation}`;
|
|
433
444
|
}
|
|
434
|
-
export function buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias, base = NO_BASE_CONTEXT) {
|
|
435
|
-
const prompt = buildReviewTicketPrompt(key, mode, base);
|
|
436
|
-
return buildAgentInvocation(agent, prompt, powershellSquote, modelAlias);
|
|
445
|
+
export function buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias, base = NO_BASE_CONTEXT, launchArgs = []) {
|
|
446
|
+
const prompt = buildReviewTicketPrompt(key, mode, base, agent.name);
|
|
447
|
+
return buildAgentInvocation(agent, prompt, powershellSquote, modelAlias, launchArgs);
|
|
437
448
|
}
|
|
438
|
-
export function buildReviewAgentShellCommand(agent, key, mode, platform, cwd, modelAlias, base = NO_BASE_CONTEXT) {
|
|
449
|
+
export function buildReviewAgentShellCommand(agent, key, mode, platform, cwd, modelAlias, base = NO_BASE_CONTEXT, launchArgs = []) {
|
|
439
450
|
if (platform === "win32") {
|
|
440
|
-
return buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias, base);
|
|
451
|
+
return buildPowerShellReviewAgentShellCommand(agent, key, mode, modelAlias, base, launchArgs);
|
|
441
452
|
}
|
|
442
|
-
return buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias, base);
|
|
453
|
+
return buildPosixReviewAgentShellCommand(agent, key, mode, cwd, modelAlias, base, launchArgs);
|
|
443
454
|
}
|
|
444
455
|
// ---------------------------------------------------------------------------
|
|
445
456
|
// Unsupported platform message for review-tickets
|
|
@@ -535,17 +546,18 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
535
546
|
if (!agent) {
|
|
536
547
|
return {
|
|
537
548
|
ok: false,
|
|
538
|
-
error: `Unknown agent: '${options.agentName}'. Valid agents: ${
|
|
549
|
+
error: `Unknown agent: '${options.agentName}'. Valid agents: ${formatReviewTicketsAgentNames()}.`,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// Reject an invalid multi-key claim before any Codex skill/config writes.
|
|
553
|
+
if (options.epic?.dispatch_key && options.keys.length > 1) {
|
|
554
|
+
return {
|
|
555
|
+
ok: false,
|
|
556
|
+
error: "epic.dispatch_key cannot be used with multiple keys: dispatch_key is a single-ticket claim " +
|
|
557
|
+
"and would be re-claimed for each key in the batch. Call orchestrateReviewTickets once per ticket instead.",
|
|
539
558
|
};
|
|
540
559
|
}
|
|
541
560
|
if (options.dryRun) {
|
|
542
|
-
if (options.epic?.dispatch_key && options.keys.length > 1) {
|
|
543
|
-
return {
|
|
544
|
-
ok: false,
|
|
545
|
-
error: "epic.dispatch_key cannot be used with multiple keys: dispatch_key is a single-ticket claim " +
|
|
546
|
-
"and would be re-claimed for each key in the batch. Call orchestrateReviewTickets once per ticket instead.",
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
561
|
// --dry-run never performs the base-branch fetch (side-effect-free preview);
|
|
550
562
|
// it reflects only what the user explicitly passed on the CLI.
|
|
551
563
|
const rows = buildReviewPlanRows(deps, options, agent, "dry-run", overrides, previewBaseContextFromOptions(options));
|
|
@@ -554,15 +566,18 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
554
566
|
const preflight = await runReviewTicketsPreflight(deps, options);
|
|
555
567
|
if (!preflight.ok)
|
|
556
568
|
return { ok: false, error: preflight.error };
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
569
|
+
let codexLaunchArgs = [];
|
|
570
|
+
if (agent.name === "codex") {
|
|
571
|
+
const prepare = overrides.prepareCodexSession ?? (async (d) => {
|
|
572
|
+
const provision = await provisionCommandsForWorktree(d.cwd, buildCommandProvisioningDeps(d), true);
|
|
573
|
+
if (!provision.ok)
|
|
574
|
+
return { ok: false, error: provision.error };
|
|
575
|
+
return resolveCodexLaunchScope(d.cwd, buildMcpProvisioningDeps(d));
|
|
576
|
+
});
|
|
577
|
+
const scope = await prepare(deps);
|
|
578
|
+
if (!scope.ok)
|
|
579
|
+
return { ok: false, error: scope.error };
|
|
580
|
+
codexLaunchArgs = scope.argv;
|
|
566
581
|
}
|
|
567
582
|
// BAPI-474 D-3: parent-fetch-once — resolve ONE base_branch/base_sha pin for
|
|
568
583
|
// the whole batch before any tab is spawned. D-4: on failure (and no
|
|
@@ -578,7 +593,7 @@ export async function orchestrateReviewTickets(deps, options, overrides = {}) {
|
|
|
578
593
|
const terminal = detectTerminal(undefined, deps.env);
|
|
579
594
|
const rows = await runWithConcurrency(options.keys, options.maxParallel, async (key) => {
|
|
580
595
|
const mode = resolveEffectiveReviewMode(key, options);
|
|
581
|
-
const baseShellCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, options.modelAlias, baseContext);
|
|
596
|
+
const baseShellCommand = buildReviewAgentShellCommand(agent, key, mode, deps.platform, deps.cwd, options.modelAlias, baseContext, codexLaunchArgs);
|
|
582
597
|
let shellCommand = baseShellCommand;
|
|
583
598
|
let runId;
|
|
584
599
|
if (options.epic) {
|
|
@@ -727,7 +742,7 @@ export async function runReviewTicketsCli(argv, overrides = {}) {
|
|
|
727
742
|
if (options.dryRun) {
|
|
728
743
|
const agent = resolveAgentSpec(options.agentName);
|
|
729
744
|
if (!agent) {
|
|
730
|
-
errorLog(`Error: Unknown agent: '${options.agentName}'. Valid agents: ${
|
|
745
|
+
errorLog(`Error: Unknown agent: '${options.agentName}'. Valid agents: ${formatReviewTicketsAgentNames()}.`);
|
|
731
746
|
return 1;
|
|
732
747
|
}
|
|
733
748
|
const rows = buildReviewPlanRows(deps, options, agent, "dry-run", overrides.orchestrateOverrides, previewBaseContextFromOptions(options));
|
|
@@ -55,7 +55,7 @@ export const MODULE_MOCK_MANIFEST = [
|
|
|
55
55
|
// exist — the failure mode this manifest is for.
|
|
56
56
|
"build/ci-check-wait.test.js",
|
|
57
57
|
"build/conductor/cli-git-hooks.test.js",
|
|
58
|
-
"build/conductor/cli.test.js",
|
|
58
|
+
"build/conductor/epic-implementer-cli.test.js",
|
|
59
59
|
"build/conductor/git-inspection.test.js",
|
|
60
60
|
"build/conductor/paths.test.js",
|
|
61
61
|
"build/conductor/pr-ci-producer-emit-seam.test.js",
|
package/build/setup-epic.js
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* association is immutable server-side and can never be attached later.
|
|
48
48
|
* c. Cut or validate `origin/<feature>` at EXACTLY the canonical indexed SHA
|
|
49
49
|
* with the operator's LOCAL `git`, through the shared cut protocol
|
|
50
|
-
* `
|
|
50
|
+
* `implement-epic init` also drives, with the run id so the scope is minted
|
|
51
51
|
* owned by the run. An existing branch at a different commit fails closed.
|
|
52
52
|
* The GitHub App never pushes — it is `contents: read`.
|
|
53
53
|
* d. Store and approve (unchanged requests). The server now READ-validates the
|
|
@@ -81,7 +81,7 @@ import { createProductionEpicIntegrationGhRunner, ensureEpicIntegrationPullReque
|
|
|
81
81
|
import { approveEpicPlan, createEpicRunWithDisposition, fetchConductorReadiness, fetchEpicRunState, replaceEpicRunPolicy, resolveConductorBridgeApiAccess, storeEpicPlan, validateEpicPlan, ConductorBridgeApiError, } from "./conductor/bridge-api-client.js";
|
|
82
82
|
import { resolveTrackedPaths } from "./git-reachability.js";
|
|
83
83
|
import { validateBranchName } from "./base-ref.js";
|
|
84
|
-
// Reused from the
|
|
84
|
+
// Reused from the implement-epic client on purpose: adding a second wrapper under
|
|
85
85
|
// `conductor/` would give the v2 path its own copy to drift from.
|
|
86
86
|
//
|
|
87
87
|
// BAPI-850 extends that reuse to the canonical parse read, the repository base
|
|
@@ -89,9 +89,10 @@ import { validateBranchName } from "./base-ref.js";
|
|
|
89
89
|
// typed clients the pilot already has are the v2 path's clients too, so neither
|
|
90
90
|
// conductor can drift on a field name or an endpoint shape. No MCP tool, no
|
|
91
91
|
// input-schema field, and no description changes.
|
|
92
|
-
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./
|
|
93
|
-
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./
|
|
92
|
+
import { getConfigFieldBaseBranch, getIndexScopeLifecycle, getParseStatus, } from "./implement-epic/bridge-client.js";
|
|
93
|
+
import { boundScopeLifecycleLabel, createExecFileRunCommand, ensureCommitResolvableLocally, normalizeCommitSha, performExactIndexScopeCut, pollIndexScopeLifecycle, readRemoteBranchHead, runGit, SCOPE_BOOTSTRAP_POLL_INTERVAL_MS, SCOPE_LIFECYCLE_LABELS, } from "./implement-epic/cut-protocol.js";
|
|
94
94
|
import { hashPlan } from "./conductor/plan.js";
|
|
95
|
+
import { BRING_UP_REFUSAL_MESSAGES, firstBringUpRefusal, } from "./conductor/bring-up-facts.js";
|
|
95
96
|
import { MCP_PACKAGE_NAME } from "./mcp-identity.js";
|
|
96
97
|
// BAPI-872: local plane-manifest binding, so `plane down` can later stop the
|
|
97
98
|
// run this setup resolved WITHOUT guessing from repository-wide active-run
|
|
@@ -176,7 +177,7 @@ export function getSetupEpicUsage() {
|
|
|
176
177
|
"single-node plan) is unchanged and composes no posture at all.",
|
|
177
178
|
"",
|
|
178
179
|
"Unattended runs have TWO one-time repository prerequisites, refused by name",
|
|
179
|
-
"before any run is created: repository consent (
|
|
180
|
+
"before any run is created: repository consent (unattended_epic_implementer_allowed) and",
|
|
180
181
|
"a verified notify webhook default. `drive-epic` readiness lists both with their",
|
|
181
182
|
"current state.",
|
|
182
183
|
"",
|
|
@@ -1180,7 +1181,7 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
|
|
|
1180
1181
|
return {
|
|
1181
1182
|
reason: "target_not_allowed",
|
|
1182
1183
|
message: "this repository has not consented to unattended conductor runs. Set " +
|
|
1183
|
-
"`
|
|
1184
|
+
"`unattended_epic_implementer_allowed` for it (see " +
|
|
1184
1185
|
"docs/claude/account-settings-operator-runbook.md), or pass --attended to " +
|
|
1185
1186
|
"create an attended run. No run was created.",
|
|
1186
1187
|
};
|
|
@@ -1218,6 +1219,31 @@ export function evaluateSetupEpicUnattendedPrerequisites(readiness, requestedPol
|
|
|
1218
1219
|
}
|
|
1219
1220
|
return null;
|
|
1220
1221
|
}
|
|
1222
|
+
/** The closing sentence every pre-create refusal ends with. */
|
|
1223
|
+
const NO_RUN_WAS_CREATED = "No run was created.";
|
|
1224
|
+
/**
|
|
1225
|
+
* Evaluate the bring-up facts against a readiness report.
|
|
1226
|
+
*
|
|
1227
|
+
* Pure and total: `null` is a readiness read that failed, which has proven
|
|
1228
|
+
* nothing and refuses as `readiness_unreadable`. Otherwise at most ONE refusal,
|
|
1229
|
+
* in the fixed {@link BRING_UP_REFUSAL_REASONS} order — each fact's
|
|
1230
|
+
* "unreadable" before its "bad", so an operator is never told to repair a
|
|
1231
|
+
* condition that could not be read.
|
|
1232
|
+
*/
|
|
1233
|
+
export function evaluateSetupEpicBringUpPrerequisites(readiness) {
|
|
1234
|
+
if (readiness === null) {
|
|
1235
|
+
return {
|
|
1236
|
+
reason: "readiness_unreadable",
|
|
1237
|
+
message: "the Bridge API conductor readiness report could not be read, so the database " +
|
|
1238
|
+
"migration, worker presence, and single-reconciler facts could not be confirmed. " +
|
|
1239
|
+
`Retry once the server is reachable. ${NO_RUN_WAS_CREATED}`,
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
const reason = firstBringUpRefusal(readiness.bring_up ?? null);
|
|
1243
|
+
if (reason === null)
|
|
1244
|
+
return null;
|
|
1245
|
+
return { reason, message: `${BRING_UP_REFUSAL_MESSAGES[reason]} ${NO_RUN_WAS_CREATED}` };
|
|
1246
|
+
}
|
|
1221
1247
|
/**
|
|
1222
1248
|
* Does the policy this invocation will actually send request unattended operation?
|
|
1223
1249
|
*
|
|
@@ -1619,13 +1645,13 @@ function scopeFailureGuidance(reason, featureBranch, epicRunId) {
|
|
|
1619
1645
|
if (reason === "canonical_index_advanced") {
|
|
1620
1646
|
return (`${SETUP_EPIC_SCOPE_REDRIVE_RECOVERY} The canonical index advanced before the ` +
|
|
1621
1647
|
`scope could be seeded: delete origin/${featureBranch}, abandon this run ` +
|
|
1622
|
-
`(
|
|
1648
|
+
`(epic-implementer abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
|
|
1623
1649
|
}
|
|
1624
1650
|
if (reason === "head_mismatch") {
|
|
1625
1651
|
return (`${SETUP_EPIC_SCOPE_REDRIVE_RECOVERY} origin/${featureBranch} does not sit at the ` +
|
|
1626
1652
|
`commit this scope was cut at, and setup-epic never force-updates an existing ` +
|
|
1627
1653
|
`ref: delete origin/${featureBranch}, abandon this run ` +
|
|
1628
|
-
`(
|
|
1654
|
+
`(epic-implementer abandon-run --epic-run-id ${epicRunId}), and re-run setup-epic.`);
|
|
1629
1655
|
}
|
|
1630
1656
|
return `Recovery: ${SETUP_EPIC_SCOPE_RECOVERY_COMMAND}.`;
|
|
1631
1657
|
}
|
|
@@ -1924,7 +1950,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1924
1950
|
else if (err instanceof ConductorBridgeApiError && err.status === 409) {
|
|
1925
1951
|
deps.errorLog(`Epic ${opts.epicKey} has MULTIPLE active runs — it is wedged, and every ` +
|
|
1926
1952
|
`plan call will keep failing. Abandon the duplicate before retrying:\n` +
|
|
1927
|
-
`
|
|
1953
|
+
` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
|
|
1928
1954
|
`Detail: ${errorDetail(err)}`);
|
|
1929
1955
|
return 1;
|
|
1930
1956
|
}
|
|
@@ -1982,7 +2008,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
1982
2008
|
`'${existingBaseBranch}', not the repository base '${repositoryBase}', and ` +
|
|
1983
2009
|
`--into-base asks for the repository base branch. setup-epic will not retarget ` +
|
|
1984
2010
|
`an existing run. Re-run without --into-base to reuse it unchanged, or abandon ` +
|
|
1985
|
-
`the run (
|
|
2011
|
+
`the run (epic-implementer abandon-run --epic-run-id ${existingRunId}) and recreate ` +
|
|
1986
2012
|
`it on base.`);
|
|
1987
2013
|
return 1;
|
|
1988
2014
|
}
|
|
@@ -2112,7 +2138,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2112
2138
|
`keep failing. This is TERMINAL: it will not clear on retry, and it is ` +
|
|
2113
2139
|
`NOT a problem with your plan. Adopt the run you want to keep, or ` +
|
|
2114
2140
|
`abandon the duplicate:\n` +
|
|
2115
|
-
`
|
|
2141
|
+
` epic-implementer abandon-run --epic-run-id <duplicate_epic_run_id>\n` +
|
|
2116
2142
|
`No run was created and no automation-start charge occurred.\n` +
|
|
2117
2143
|
`Detail: ${errorDetail(err)}`);
|
|
2118
2144
|
return 1;
|
|
@@ -2186,7 +2212,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2186
2212
|
// used for status instead). A no-feature run performs none of this.
|
|
2187
2213
|
//
|
|
2188
2214
|
// The scope's BASE is the repository's configured base branch (the same
|
|
2189
|
-
// resolution `
|
|
2215
|
+
// resolution `implement-epic init` uses), NOT `policy_json.base_branch` — that
|
|
2190
2216
|
// key names the FEATURE branch. A feature branch equal to the configured base
|
|
2191
2217
|
// needs no scope (dispatch already cuts from it) and matches the server's
|
|
2192
2218
|
// long-standing no-op for that case.
|
|
@@ -2371,9 +2397,9 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2371
2397
|
// run that is already going.
|
|
2372
2398
|
//
|
|
2373
2399
|
// Evaluated against the COMPOSED policy, so it covers the default profile and
|
|
2374
|
-
// a `--policy-file` alike;
|
|
2375
|
-
// policy that is not unattended
|
|
2376
|
-
//
|
|
2400
|
+
// a `--policy-file` alike; the UNATTENDED evaluation is skipped for
|
|
2401
|
+
// `--attended` and for any policy that is not unattended. (The readiness
|
|
2402
|
+
// read itself is no longer skipped — see BAPI-1153 below.)
|
|
2377
2403
|
//
|
|
2378
2404
|
// The file branch runs through `applySetupEpicUnattendedDefault` rather than
|
|
2379
2405
|
// taking the file verbatim: a file bypasses the no-file composer entirely, so
|
|
@@ -2381,18 +2407,30 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2381
2407
|
// which made the default profile unreachable by any documented invocation.
|
|
2382
2408
|
// The composed object is what gets gated AND what gets sent, so the create
|
|
2383
2409
|
// request carries exactly what was validated.
|
|
2410
|
+
//
|
|
2411
|
+
// BAPI-1153: ONE readiness read now serves both gates, and it happens for
|
|
2412
|
+
// EVERY create — attended included — because the bring-up gate below applies
|
|
2413
|
+
// to every run. Before any create request, manifest binding, or lane.
|
|
2384
2414
|
{
|
|
2415
|
+
let readiness = null;
|
|
2416
|
+
try {
|
|
2417
|
+
readiness = await fetchConductorReadiness(access, deps.fetch);
|
|
2418
|
+
}
|
|
2419
|
+
catch {
|
|
2420
|
+
// Fail CLOSED, and silently as to WHY: the thrown value can carry a URL
|
|
2421
|
+
// or a response excerpt, and this refusal is rendered to a terminal. The
|
|
2422
|
+
// generic `readiness_unreadable` message says what to do about it.
|
|
2423
|
+
readiness = null;
|
|
2424
|
+
}
|
|
2425
|
+
// Bring-up first: a behind database or a missing worker makes the
|
|
2426
|
+
// unattended questions moot, and refusing on the cheaper, universal fact
|
|
2427
|
+
// keeps the first-named blocker the same for attended and unattended runs.
|
|
2428
|
+
const bringUpRefusal = evaluateSetupEpicBringUpPrerequisites(readiness);
|
|
2429
|
+
if (bringUpRefusal !== null) {
|
|
2430
|
+
deps.errorLog(`setup-epic REFUSED (${bringUpRefusal.reason}): ${bringUpRefusal.message}`);
|
|
2431
|
+
return 1;
|
|
2432
|
+
}
|
|
2385
2433
|
if (requestedPolicyIsUnattended(composedForCreate, opts.attended)) {
|
|
2386
|
-
let readiness = null;
|
|
2387
|
-
try {
|
|
2388
|
-
readiness = await fetchConductorReadiness(access, deps.fetch);
|
|
2389
|
-
}
|
|
2390
|
-
catch {
|
|
2391
|
-
// Fail CLOSED, and silently as to WHY: the thrown value can carry a URL
|
|
2392
|
-
// or a response excerpt, and this refusal is rendered to a terminal. The
|
|
2393
|
-
// generic `readiness_unreadable` message says what to do about it.
|
|
2394
|
-
readiness = null;
|
|
2395
|
-
}
|
|
2396
2434
|
// The COMPOSED policy, not the flags: a --policy-file declaring
|
|
2397
2435
|
// notify.webhook_url satisfies the escalation-channel prerequisite, and
|
|
2398
2436
|
// it is the same object the create request carries.
|
|
@@ -2456,7 +2494,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2456
2494
|
say(`Run: adopting existing active run ${run.epic_run_id} (status: ${run.status})`);
|
|
2457
2495
|
say(` No second run was created — one epic has at most one active ` +
|
|
2458
2496
|
`run. Continue on this one, or abandon it first with ` +
|
|
2459
|
-
`\`
|
|
2497
|
+
`\`epic-implementer abandon-run --epic-run-id ${run.epic_run_id}\` to start over.`);
|
|
2460
2498
|
if (requestedPolicy !== undefined) {
|
|
2461
2499
|
const outcome = await applyReusedRunPolicy({
|
|
2462
2500
|
access,
|
|
@@ -2522,7 +2560,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2522
2560
|
else {
|
|
2523
2561
|
const msg = `Could not bind the local plane to run ${result.epic_run_id} (${bound.message}). ` +
|
|
2524
2562
|
"A later `plane down` cannot be guaranteed to stop it automatically — if you need " +
|
|
2525
|
-
`to stop this run, run \`
|
|
2563
|
+
`to stop this run, run \`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`.`;
|
|
2526
2564
|
warnings.push(msg);
|
|
2527
2565
|
say(`Plane: [warn] ${msg}`);
|
|
2528
2566
|
}
|
|
@@ -2542,7 +2580,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2542
2580
|
const msg = `A local plane manifest exists but could not be validated (${manifestRead.error}), so ` +
|
|
2543
2581
|
`run ${result.epic_run_id} was NOT bound to it. If a plane is running, \`plane down\` ` +
|
|
2544
2582
|
"will not stop this run automatically — use " +
|
|
2545
|
-
`\`
|
|
2583
|
+
`\`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`. A manifest written by a ` +
|
|
2546
2584
|
"NEWER build must be wound down with that build's `plane down`.";
|
|
2547
2585
|
warnings.push(msg);
|
|
2548
2586
|
say(`Plane: [warn] ${msg}`);
|
|
@@ -2553,7 +2591,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2553
2591
|
// an unexpected error here is reported as a warning, never thrown.
|
|
2554
2592
|
const msg = `Could not check for a local plane to bind run ${result.epic_run_id} to (${errorDetail(err)}). ` +
|
|
2555
2593
|
"A later `plane down` cannot be guaranteed to stop it automatically — if you need to stop " +
|
|
2556
|
-
`this run, run \`
|
|
2594
|
+
`this run, run \`epic-implementer stop-run --epic-run-id ${result.epic_run_id}\`.`;
|
|
2557
2595
|
warnings.push(msg);
|
|
2558
2596
|
say(`Plane: [warn] ${msg}`);
|
|
2559
2597
|
}
|
|
@@ -2562,7 +2600,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2562
2600
|
// AFTER the run exists and BEFORE the plan is stored/approved: the scope's run
|
|
2563
2601
|
// association is immutable server-side, so the cut is driven with this run's
|
|
2564
2602
|
// id and the scope is minted owned by it. Everything from `cut/begin` to the
|
|
2565
|
-
// hold's release is the SHARED protocol `
|
|
2603
|
+
// hold's release is the SHARED protocol `implement-epic init` drives — local git
|
|
2566
2604
|
// only, an exact-SHA create-only push, never the GitHub App, never a force
|
|
2567
2605
|
// update. A failure leaves the run non-active (never discoverable by the
|
|
2568
2606
|
// reconciler) and re-running setup-epic reuses it and re-drives the cut.
|
|
@@ -2683,7 +2721,7 @@ async function executeSetupEpic(argv, overrides, runIdSink) {
|
|
|
2683
2721
|
deps.errorLog(`The plan was NOT approved: this repository is no longer authorized for ` +
|
|
2684
2722
|
`unattended runs. The run WAS created and is left in 'planning' with no ` +
|
|
2685
2723
|
`activation and no executor dispatch — nothing was half-applied.\n` +
|
|
2686
|
-
`Restore \`
|
|
2724
|
+
`Restore \`unattended_epic_implementer_allowed\` for this repository (see ` +
|
|
2687
2725
|
`docs/claude/account-settings-operator-runbook.md) and re-run setup-epic, ` +
|
|
2688
2726
|
`or deliberately convert this run to an attended posture through the ` +
|
|
2689
2727
|
`supported operator flow.\nDetail: ${errorDetail(err)}`);
|
|
@@ -83,9 +83,10 @@ export const DEFAULT_CONDUCTOR_GATE_NAME = "implement-ticket";
|
|
|
83
83
|
* Exported since BAPI-1020 so the executor resolves the worker-guard bin through
|
|
84
84
|
* the SAME resolver rather than growing a second one. Correct in both build
|
|
85
85
|
* shapes because this module compiles to the build ROOT: the esbuild bundle puts
|
|
86
|
-
* `import.meta.url` at `build/index.js` / `build/
|
|
87
|
-
* it at `build/start-tickets-conductor.js`. A resolver living in a
|
|
88
|
-
* module would resolve to `build/<subdir>/<filename>` under `tsc`
|
|
86
|
+
* `import.meta.url` at `build/index.js` / `build/epic-implementer-bin.js`, and
|
|
87
|
+
* `tsc` puts it at `build/start-tickets-conductor.js`. A resolver living in a
|
|
88
|
+
* subdirectory module would resolve to `build/<subdir>/<filename>` under `tsc`
|
|
89
|
+
* and be wrong.
|
|
89
90
|
*/
|
|
90
91
|
export function defaultResolveBinPath(filename) {
|
|
91
92
|
return fileURLToPath(new URL(`./${filename}`, import.meta.url));
|
|
@@ -124,12 +125,12 @@ export async function createStartTicketsConductorContext(options, agent, deps) {
|
|
|
124
125
|
gateName,
|
|
125
126
|
supervisorMode,
|
|
126
127
|
agentName: agent.name,
|
|
127
|
-
cliFile: resolveBinPath("
|
|
128
|
+
cliFile: resolveBinPath("epic-implementer-bin.js"),
|
|
128
129
|
// Capture the conductor process's Node executable so dispatched workers run
|
|
129
|
-
//
|
|
130
|
-
// worker's own Node (BAPI-527).
|
|
130
|
+
// epic-implementer-bin.js under THIS runtime (which owns the ledger), never
|
|
131
|
+
// the worker's own Node (BAPI-527).
|
|
131
132
|
conductorNodePath: deps.execPath ?? process.execPath,
|
|
132
|
-
hookBinPath: resolveBinPath("
|
|
133
|
+
hookBinPath: resolveBinPath("epic-implementer-claude-hook-bin.js"),
|
|
133
134
|
};
|
|
134
135
|
if (options.epic) {
|
|
135
136
|
context.epic = options.epic;
|