@bridge_gpt/mcp-server 0.2.68 → 0.2.70
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 +226 -320
- package/build/agent-launchers/claude-executor-adapter.js +17 -4
- package/build/agents.generated.js +4 -4
- package/build/artifact-diff.js +110 -0
- package/build/bin-entrypoint.js +36 -0
- package/build/bridge-permissions.js +650 -98
- package/build/check-update-cli.js +74 -0
- package/build/claude-review-workflow-drift.js +3 -3
- package/build/claude-settings.js +84 -2
- package/build/cli-help.js +7 -1
- package/build/codex-bridge-profile.js +1 -1
- package/build/codex-home.js +9 -1
- package/build/codex-launcher-pin.js +598 -0
- package/build/commands.generated.js +8 -8
- package/build/conductor/bridge-api-client.js +598 -19
- package/build/conductor/bring-up-facts.js +9 -9
- package/build/conductor/doctor.js +466 -18
- package/build/conductor/epic-child-continuation.js +54 -3
- package/build/conductor/epic-implementer-cli.js +132 -34
- package/build/conductor/epic-runtime.js +2 -2
- package/build/conductor/epic-state.js +1 -1
- package/build/conductor/hook-runtime-qualification.js +1 -1
- package/build/conductor/install-doctor.js +4 -2
- package/build/conductor/launch-need-probes.js +337 -0
- package/build/conductor/local-merge.js +241 -56
- package/build/conductor/merge-credential.js +172 -0
- package/build/conductor/merge-execution-eligibility.js +1 -1
- package/build/conductor/merged-child-check.js +95 -0
- package/build/conductor/plan-trim.js +30 -0
- package/build/conductor/pr-ci-producer.js +1 -1
- package/build/conductor/readiness-cli.js +11 -6
- package/build/conductor/readiness-prerequisites.js +41 -0
- package/build/conductor/readiness-sections.js +345 -55
- package/build/conductor/readiness.js +99 -87
- package/build/conductor/recovery-cli.js +606 -21
- package/build/conductor/recovery-operations.js +306 -7
- package/build/conductor/repair-contract.js +47 -2
- package/build/conductor/repository-workflow-remedy.js +8 -6
- package/build/conductor/stored-merge-credential.js +76 -0
- package/build/conductor/stored-merge-token-probe.js +28 -0
- package/build/conductor/temporary-plan.js +50 -0
- package/build/conductor/wait-run-cli.js +759 -0
- package/build/conductor/worker-database-doctor-inspector.js +49 -0
- package/build/conductor-bin.js +2 -2
- package/build/conductor-claude-hook-bin.js +2 -2
- package/build/conductor-claude-hook-removed-stub-bin.js +2 -5
- package/build/conductor-removed-stub-bin.js +2 -5
- package/build/connect-github-api.js +33 -14
- package/build/credential-store.js +356 -13
- package/build/docs.generated.js +27 -14
- package/build/doctor.js +203 -38
- package/build/drive-epic-help.js +212 -0
- package/build/drive-epic.js +1840 -373
- package/build/epic-ci-workflow-drift.js +77 -0
- package/build/epic-ci-workflow-install.js +183 -0
- package/build/epic-ci-workflow.js +157 -0
- package/build/epic-contract-context.js +326 -0
- package/build/epic-implementer-bin.js +131 -120
- package/build/epic-implementer-claude-hook-bin.js +2 -2
- package/build/escalation-reachability.js +134 -0
- package/build/executor/cleanup-report.js +99 -0
- package/build/executor/cli.js +12 -1
- package/build/executor/completion-contract.js +68 -11
- package/build/executor/completion-supervision.js +99 -0
- package/build/executor/delivery-outcome.js +179 -0
- package/build/executor/deps.js +3 -0
- package/build/executor/env.js +71 -0
- package/build/executor/failure-reason-codes.js +126 -0
- package/build/executor/heartbeat.js +38 -0
- package/build/executor/http-client.js +115 -4
- package/build/executor/integration-job.js +298 -5
- package/build/executor/job-errors.js +36 -6
- package/build/executor/job-runner.js +720 -36
- package/build/executor/likely-tests.js +36 -2
- package/build/executor/merge-job.js +145 -2
- package/build/executor/observation.js +73 -4
- package/build/executor/permissions.js +193 -97
- package/build/executor/preflight.js +14 -3
- package/build/executor/process-containment.js +28 -2
- package/build/executor/prompt-spec.js +50 -5
- package/build/executor/rebase-finalization.js +38 -3
- package/build/executor/reconcile-overlap-job.js +35 -7
- package/build/executor/required-work-ledger.js +236 -81
- package/build/executor/resume-pre-spawn.js +38 -1
- package/build/executor/runner.js +91 -3
- package/build/executor/test-clock.js +8 -0
- package/build/executor/types.js +37 -2
- package/build/executor/worker-cli-launcher.js +259 -0
- package/build/executor/worker-command.js +41 -4
- package/build/executor/worker-database-target.js +91 -0
- package/build/executor/worker-database.js +908 -0
- package/build/executor/worker-finalization.js +106 -0
- package/build/executor/worker-guard-hook.js +203 -0
- package/build/executor/worker-resources.js +47 -0
- package/build/executor/worker-verification-guidance.js +15 -2
- package/build/executor/worktree.js +303 -15
- package/build/executor-launch.js +11 -1
- package/build/generated-assets-doctor.js +9 -1
- package/build/generated-assets-health.js +13 -4
- package/build/generated-assets-legacy.js +39 -0
- package/build/generated-assets-paths.js +12 -1
- package/build/generated-assets-repair.js +4 -1
- package/build/generated-assets-test-support.js +12 -0
- package/build/github-remote.js +33 -0
- package/build/github-sign-in.js +62 -0
- package/build/index.js +1214 -465
- package/build/init.js +39 -1
- package/build/install-bridge-conductor-gate.js +78 -0
- package/build/install-bridge-conductor-setup.js +413 -0
- package/build/install-bridge-conductor.js +1226 -267
- package/build/install-bridge.js +629 -350
- package/build/launcher-config-inspection.js +184 -19
- package/build/mcp-host-targets.js +4 -0
- package/build/mcp-identity.js +7 -0
- package/build/mcp-install-state.js +8 -2
- package/build/merge-pull-request.js +6 -0
- package/build/merge-workflow-install.js +763 -345
- package/build/phase-result-artifacts.js +75 -20
- package/build/pipeline-orchestrator.js +247 -4
- package/build/pipelines.generated.js +35 -35
- package/build/plan-discovery.js +15 -2
- package/build/plan-execution-ledger.js +306 -17
- package/build/plan-phase-routing.js +48 -13
- package/build/plane/cli.js +105 -51
- package/build/plane/defaults.js +3 -0
- package/build/plane/plane-freshness.js +294 -0
- package/build/plane/schema-compatibility.js +137 -0
- package/build/plane/shutdown-deps.js +38 -0
- package/build/plane/shutdown.js +83 -8
- package/build/plane/terminal-run-cleanup.js +303 -0
- package/build/printed-remedy-commands.js +26 -4
- package/build/readiness-check.js +6 -2
- package/build/readiness-prerequisites.generated.js +532 -0
- package/build/readme.generated.js +1 -1
- package/build/session-upgrade-notice.js +86 -0
- package/build/setup-epic.js +400 -30
- package/build/shared-resources.generated.js +5 -0
- package/build/start-tickets.js +43 -15
- package/build/test-command-detection.js +247 -0
- package/build/test-command-validation.js +73 -0
- package/build/testing-config-client.js +150 -0
- package/build/ticket-delivery-artifacts.js +258 -0
- package/build/ticket-delivery-enrollment.js +306 -0
- package/build/ticket-delivery.js +205 -0
- package/build/ticket-review-summary.js +14 -0
- package/build/tool-call-advisory.js +162 -0
- package/build/tool-surface-gating.js +28 -89
- package/build/update-check.js +97 -16
- package/build/update-status.js +61 -71
- package/build/upgrade-advice.js +15 -11
- package/build/upgrade-cli.js +360 -20
- package/build/v2-readiness-requirements.js +552 -0
- package/build/version.generated.js +3 -3
- package/build/worker-guard-hook-bin.js +2 -2
- package/build/worktree-core.js +22 -7
- package/docs/BRIDGE-DIRECTORY-README.md +48 -0
- package/docs/EPIC-IMPLEMENTER-INSTALL.md +12 -867
- package/docs/EPIC-IMPLEMENTER.md +104 -63
- package/docs/epic-implementer/getting-started.md +131 -0
- package/docs/epic-implementer/self-hosting.md +133 -0
- package/package.json +3 -3
- package/pipelines/idea-to-ticket.json +1 -1
- package/pipelines/implement-ticket.json +8 -8
- package/pipelines/review-ticket.json +2 -2
- package/design-assets/favicon/favicon-32x32.png +0 -0
- package/design-assets/just-logo-rough-draft.png +0 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import { mergeReadinessPrerequisites, formatReadinessPrerequisites } from "./conductor/readiness-prerequisites.js";
|
|
2
|
+
import { READINESS_PREREQUISITE_BY_ID, READINESS_LEGACY_IDS } from "./readiness-prerequisites.generated.js";
|
|
3
|
+
/**
|
|
4
|
+
* The v2 readiness requirement catalog and its row projection (BAPI-1452).
|
|
5
|
+
*
|
|
6
|
+
* Moved out of `drive-epic.ts` so the requirement-to-row projection has ONE
|
|
7
|
+
* shared, explicitly typed home that both the launch classifier
|
|
8
|
+
* (`classifyV2Readiness` in `drive-epic.ts`) and the advisory readiness report
|
|
9
|
+
* (`conductor/readiness.ts`) can read without either importing the other. The
|
|
10
|
+
* predicates and remedies are unchanged from their `drive-epic.ts` versions;
|
|
11
|
+
* what is new is the explicit `governedRowIds` list on every requirement.
|
|
12
|
+
*
|
|
13
|
+
* ## Blocking is applicability, never status
|
|
14
|
+
*
|
|
15
|
+
* A readiness row is `blocking` exactly when an APPLICABLE requirement lists its
|
|
16
|
+
* ID. A failing row no requirement governs stays non-blocking, and a passing
|
|
17
|
+
* governed row stays blocking — the flag says "this row can refuse this launch",
|
|
18
|
+
* not "this row is red". The unattended-only requirements are inapplicable for
|
|
19
|
+
* `--attended` and `--into-base`, so their rows are informational there.
|
|
20
|
+
*
|
|
21
|
+
* Dependency-light by design: no CLI orchestration, no filesystem, no process
|
|
22
|
+
* execution. It imports the readiness response type, the shared remedy
|
|
23
|
+
* formatter, the readiness-check constructor, and the merge credential key
|
|
24
|
+
* names (from the dependency-free `conductor/merge-credential.ts`), nothing else.
|
|
25
|
+
*/
|
|
26
|
+
import { gateProfileWaivesCiWorkflow, isProviderRepositoryIdentity, } from "./conductor/bridge-api-client.js";
|
|
27
|
+
import { repositoryWorkflowRemedy } from "./conductor/repository-workflow-remedy.js";
|
|
28
|
+
import { LOCAL_MERGE_CREDENTIAL_ENV_KEYS } from "./conductor/merge-credential.js";
|
|
29
|
+
import { createReadinessCheckSafely } from "./readiness-check.js";
|
|
30
|
+
/**
|
|
31
|
+
* The v2 readiness predicate, as a list so a failure can name what is missing.
|
|
32
|
+
*
|
|
33
|
+
* These are the facts v2 actually needs to drive an epic: a stored supervisor
|
|
34
|
+
* posture, a reconciler that is ticking, a provisioned and live executor, and
|
|
35
|
+
* complete GitHub App credentials. Each is read from the SERVER's report and
|
|
36
|
+
* never re-derived locally — two implementations of one classification drift,
|
|
37
|
+
* and a CLI that disagreed with the server about whether v2 can run would be
|
|
38
|
+
* worse than no check at all.
|
|
39
|
+
*/
|
|
40
|
+
const LEGACY_V2_READINESS_REQUIREMENTS = [
|
|
41
|
+
{
|
|
42
|
+
id: "supervisor_setup",
|
|
43
|
+
governedRowIds: ["server.supervisor-setup"],
|
|
44
|
+
describe: "supervisor setup stored for this repository",
|
|
45
|
+
satisfied: (r) => r.supervisor.setup_present,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "supervisor_config",
|
|
49
|
+
governedRowIds: ["server.supervisor-config"],
|
|
50
|
+
describe: "supervisor configuration stored for this repository",
|
|
51
|
+
satisfied: (r) => r.supervisor.config_present,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "github_credentials",
|
|
55
|
+
governedRowIds: ["server.github-credentials"],
|
|
56
|
+
describe: "GitHub App credentials that resolve completely",
|
|
57
|
+
satisfied: (r) => r.github.credentials_complete,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "reconciler_live",
|
|
61
|
+
governedRowIds: ["server.worker-present", "server.single-transition-authority", "server.reconciler-liveness"],
|
|
62
|
+
describe: "a reconciler that is ticking and not stale",
|
|
63
|
+
// BAPI-1279 — launch liveness is a DEPLOYMENT fact. The `bring_up` block
|
|
64
|
+
// reads the reconciler's process heartbeat deployment-wide, so a repository
|
|
65
|
+
// that has never run an epic still reads live when the worker is healthy.
|
|
66
|
+
// The repository-scoped `reconciler` section ("is this repository's work
|
|
67
|
+
// moving?", from `epic_runs.last_tick_at`) is only the fallback for a server
|
|
68
|
+
// too old to report `bring_up`.
|
|
69
|
+
satisfied: (r) => r.bring_up
|
|
70
|
+
? r.bring_up.heartbeat_readable && r.bring_up.worker_present &&
|
|
71
|
+
r.bring_up.single_transition_authority
|
|
72
|
+
: r.reconciler.liveness_readable && !r.reconciler.stale,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "executor_live",
|
|
76
|
+
governedRowIds: ["server.executor-readiness"],
|
|
77
|
+
describe: "an executor provisioned and reporting ready",
|
|
78
|
+
// BAPI-1279 — diagnostic only. drive-epic starts (or, for a durable profile,
|
|
79
|
+
// verifies) the run's executor itself once the run exists.
|
|
80
|
+
advisoryOnly: true,
|
|
81
|
+
satisfied: (r) => r.executor.liveness_readable && r.executor.ready === true,
|
|
82
|
+
},
|
|
83
|
+
// BAPI-1102 — the three unattended prerequisites, in the order `setup-epic`
|
|
84
|
+
// refuses them, so readiness and the refusal name the same first blocker.
|
|
85
|
+
//
|
|
86
|
+
// Each reads the SERVER's `unattended` block and re-derives nothing. A server
|
|
87
|
+
// that does not report the block (older than BAPI-1102) leaves every one of
|
|
88
|
+
// them UNSATISFIED: "could not be read" is not "holds", and these authorize an
|
|
89
|
+
// unattended merge.
|
|
90
|
+
//
|
|
91
|
+
// The FIRST of them therefore carries the older-server remedy too, because it
|
|
92
|
+
// is the one an unreported block always blocks on, and telling that operator
|
|
93
|
+
// to set a consent flag would send them to fix something that may already be
|
|
94
|
+
// set — the server simply never said (BAPI-1102 review).
|
|
95
|
+
{
|
|
96
|
+
id: "unattended_consent",
|
|
97
|
+
governedRowIds: ["server.unattended-posture"],
|
|
98
|
+
describe: "repository consent to unattended Epic Implementer runs",
|
|
99
|
+
unattendedOnly: true,
|
|
100
|
+
// No fixed `remedy`: this requirement's action always depends on WHY.
|
|
101
|
+
remedyFor: (r) => r.unattended === null || r.unattended === undefined
|
|
102
|
+
? "This Bridge API deploy does not report a repository's unattended " +
|
|
103
|
+
"prerequisites, so they could not be confirmed. Retry readiness, or use --attended."
|
|
104
|
+
: "Unattended runs are off for this repository. Turn on 'Allow unattended " +
|
|
105
|
+
"Epic Implementer runs' on the repository's Setup page in the Bridge " +
|
|
106
|
+
"admin panel, or pass --attended. Retry readiness after changing the setting.",
|
|
107
|
+
satisfied: (r) => r.unattended?.conductor_allowed === true,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "unattended_notify_default",
|
|
111
|
+
governedRowIds: ["server.unattended-notify-default"],
|
|
112
|
+
describe: "an escalation channel (a verified notify webhook or the account email)",
|
|
113
|
+
unattendedOnly: true,
|
|
114
|
+
remedy: "Supply a --policy-file declaring notify.webhook_url, or add an email address " +
|
|
115
|
+
"to the account so escalation alerts can be emailed, or pass --attended. " +
|
|
116
|
+
"notify.local_sink does not satisfy this.",
|
|
117
|
+
// BAPI-1274 — the account's email address is an escalation channel when no
|
|
118
|
+
// webhook is declared; the server reports its presence, never the address.
|
|
119
|
+
satisfied: (r) => r.unattended?.notify_webhook_default_declared === true ||
|
|
120
|
+
r.unattended?.escalation_email_available === true,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: "repository_readiness",
|
|
124
|
+
governedRowIds: ["server.repository-workflow-readiness"],
|
|
125
|
+
describe: "confirmed review and epic-implementer-ci workflows on the default branch",
|
|
126
|
+
unattendedOnly: true,
|
|
127
|
+
// BAPI-1359 (Miss 38): the remedy names the SPECIFIC missing item, from the
|
|
128
|
+
// same shared formatter the readiness row and the setup-epic refusal use. The
|
|
129
|
+
// fixed install-command sentence that stood here could never clear a missing
|
|
130
|
+
// migration-guard job — no install step creates one — so it is gone from this
|
|
131
|
+
// refusal.
|
|
132
|
+
//
|
|
133
|
+
// BAPI-1390: the server decides which files are required (the review file
|
|
134
|
+
// only for a `verdict_protocol` run; the CI lane file only for a repository
|
|
135
|
+
// with no STORED gate profile and no `no_ci_gate`), and `satisfied` reads that
|
|
136
|
+
// decision as given. The remedy waives the CI file the same way, so a stored
|
|
137
|
+
// profile is never told to add a file it does not need. Gate-profile WARNINGS
|
|
138
|
+
// (epic-branch rows) are not requirements and are never read here.
|
|
139
|
+
remedyFor: (r) => repositoryWorkflowRemedy(gateProfileWaivesCiWorkflow(r) ? { ...r, conductor_ci_workflow: null } : r),
|
|
140
|
+
satisfied: (r) => r.unattended?.repository_readiness_confirmed === true,
|
|
141
|
+
},
|
|
142
|
+
// BAPI-1135 — merge-execution eligibility is deliberately NOT a requirement here.
|
|
143
|
+
//
|
|
144
|
+
// It was one, and it made `drive-epic` refuse EVERY repository the moment it
|
|
145
|
+
// shipped. The requirement could only be keyed on repository facts, because
|
|
146
|
+
// this layer keeps the composed run policy opaque to itself — and no
|
|
147
|
+
// repository carries the merge workflow until epic child 4 lands it. So both
|
|
148
|
+
// states resolved the same way: an upgraded server reported
|
|
149
|
+
// `merge_workflow_present: false` (`not_eligible`), an older one reported no
|
|
150
|
+
// block at all (`could_not_verify`), and either made `selectConductor` return
|
|
151
|
+
// `not_ready` and `classifyV2Readiness` return `operator_blocked` — for
|
|
152
|
+
// `--attended`, `--into-base` and `local`-mode runs alike, with a remedy
|
|
153
|
+
// naming a flag that does not exist yet.
|
|
154
|
+
//
|
|
155
|
+
// That is exactly the failure `selectConductor`'s own docstring warns about,
|
|
156
|
+
// and it contradicts the compatibility guarantee the rest of this slice keeps:
|
|
157
|
+
// nothing changes for a run that does not declare `github_actions`.
|
|
158
|
+
//
|
|
159
|
+
// Eligibility is still enforced, at the three layers that can actually see the
|
|
160
|
+
// declared mode: `setup-epic`'s pre-create refusal (which reads the composed
|
|
161
|
+
// policy), the approval route's server-side gate, and — authoritatively — the
|
|
162
|
+
// before-merge preflight. Readiness still REPORTS the facts as diagnostic
|
|
163
|
+
// rows, which inform without blocking. Reinstating a requirement here belongs
|
|
164
|
+
// with child 4, once a repository can actually satisfy it.
|
|
165
|
+
];
|
|
166
|
+
/** The three additive outcomes are authoritative; absent or unknown never denies. */
|
|
167
|
+
const NEW_IDS = ['server.indexed-workflows', 'server.unattended-notify-default', 'server.test-command'];
|
|
168
|
+
export const V2_READINESS_REQUIREMENTS = [
|
|
169
|
+
...LEGACY_V2_READINESS_REQUIREMENTS.map(req => {
|
|
170
|
+
const id = READINESS_LEGACY_IDS[req.id] ?? req.id;
|
|
171
|
+
const descriptor = READINESS_PREREQUISITE_BY_ID.get(id);
|
|
172
|
+
return { ...req, id, describe: descriptor?.label ?? req.describe,
|
|
173
|
+
governedRowIds: req.governedRowIds.map(row => READINESS_LEGACY_IDS[row] ?? row),
|
|
174
|
+
...(req.id === 'repository_readiness' ? { satisfied: (r) => r.prerequisites !== undefined ? true : req.satisfied(r) } : {}),
|
|
175
|
+
...(req.id === 'unattended_notify_default' ? {
|
|
176
|
+
satisfied: (r) => !r.prerequisites?.some(p => p.id === id && p.check_status === 'not_met' && p.impact === 'blocking'),
|
|
177
|
+
remedyFor: (r) => r.prerequisites?.find(p => p.id === id)?.fix ?? 'Retry readiness.',
|
|
178
|
+
} : {}),
|
|
179
|
+
};
|
|
180
|
+
}),
|
|
181
|
+
...NEW_IDS.filter(id => id !== 'server.unattended-notify-default').map(id => ({
|
|
182
|
+
id, describe: READINESS_PREREQUISITE_BY_ID.get(id).label, governedRowIds: [id], unattendedOnly: true,
|
|
183
|
+
satisfied: (r) => !r.prerequisites?.some(p => p.id === id && p.check_status === 'not_met' && p.impact === 'blocking'),
|
|
184
|
+
remedyFor: (r) => r.prerequisites?.find(p => p.id === id)?.fix ?? 'Retry readiness.',
|
|
185
|
+
})),
|
|
186
|
+
];
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// BAPI-1452: launch capability evidence
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
/** Stable row IDs for the launch summary, in render order. */
|
|
191
|
+
export const UNATTENDED_POSTURE_ROW_ID = "server.unattended-posture";
|
|
192
|
+
export const MERGE_CREDENTIAL_CAPABILITY_ROW_ID = "conductor.merge-credential-capability";
|
|
193
|
+
export const WORKTREE_PREPARATION_ROW_ID = "conductor.worktree-preparation";
|
|
194
|
+
export const RESOLVED_MERGE_MODE_ROW_ID = "server.resolved-merge-mode";
|
|
195
|
+
export const PROVIDER_REPOSITORY_ROW_ID = "server.provider-repository";
|
|
196
|
+
/** The exact Setup-page control name, kept visible in every posture refusal. */
|
|
197
|
+
export const UNATTENDED_POSTURE_SETUP_CONTROL = "Allow unattended Epic Implementer runs";
|
|
198
|
+
/**
|
|
199
|
+
* BAPI-1452 — the two launch capability requirements.
|
|
200
|
+
*
|
|
201
|
+
* The credential requirement does not apply to a `github_actions` run: no local
|
|
202
|
+
* credential merges there, so its row is informational. Both are unattended-only,
|
|
203
|
+
* because they protect an unattended merge and an unattended worker's commits.
|
|
204
|
+
*/
|
|
205
|
+
const LEGACY_V2_LAUNCH_EVIDENCE_REQUIREMENTS = [
|
|
206
|
+
{
|
|
207
|
+
id: "merge_credential_capability",
|
|
208
|
+
describe: "a merge credential that has push and can read pull requests",
|
|
209
|
+
unattendedOnly: true,
|
|
210
|
+
appliesTo: (evidence) => evidence.credential.kind !== "github_actions",
|
|
211
|
+
governedRowIds: [MERGE_CREDENTIAL_CAPABILITY_ROW_ID],
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "worktree_preparation",
|
|
215
|
+
describe: "an executor clone whose shared git exclude can be prepared",
|
|
216
|
+
unattendedOnly: true,
|
|
217
|
+
appliesTo: () => true,
|
|
218
|
+
governedRowIds: [WORKTREE_PREPARATION_ROW_ID],
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
export const V2_LAUNCH_EVIDENCE_REQUIREMENTS = LEGACY_V2_LAUNCH_EVIDENCE_REQUIREMENTS.map(req => {
|
|
222
|
+
const id = READINESS_LEGACY_IDS[req.id] ?? req.id;
|
|
223
|
+
return { ...req, id, describe: READINESS_PREREQUISITE_BY_ID.get(id).label };
|
|
224
|
+
});
|
|
225
|
+
/** True when the context opts out of unattended operation. */
|
|
226
|
+
function optedOut(context) {
|
|
227
|
+
return context.attended === true || context.intoBase === true;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The row IDs that are blocking in this context: the union of every APPLICABLE
|
|
231
|
+
* requirement's governed rows. Advisory requirements never contribute.
|
|
232
|
+
*/
|
|
233
|
+
export function applicableGovernedRowIds(context) {
|
|
234
|
+
const ids = new Set();
|
|
235
|
+
const out = optedOut(context);
|
|
236
|
+
for (const req of V2_READINESS_REQUIREMENTS) {
|
|
237
|
+
if (req.advisoryOnly === true)
|
|
238
|
+
continue;
|
|
239
|
+
if (out && req.unattendedOnly === true)
|
|
240
|
+
continue;
|
|
241
|
+
for (const id of req.governedRowIds)
|
|
242
|
+
ids.add(id);
|
|
243
|
+
}
|
|
244
|
+
if (context.launchEvidence !== undefined && !out) {
|
|
245
|
+
for (const req of V2_LAUNCH_EVIDENCE_REQUIREMENTS) {
|
|
246
|
+
if (!req.appliesTo(context.launchEvidence))
|
|
247
|
+
continue;
|
|
248
|
+
for (const id of req.governedRowIds)
|
|
249
|
+
ids.add(id);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return ids;
|
|
253
|
+
}
|
|
254
|
+
/** Every row ID any requirement governs, applicable or not. */
|
|
255
|
+
const ALL_GOVERNED_ROW_IDS = new Set([
|
|
256
|
+
...V2_READINESS_REQUIREMENTS.flatMap((req) => req.governedRowIds),
|
|
257
|
+
...V2_LAUNCH_EVIDENCE_REQUIREMENTS.flatMap((req) => req.governedRowIds),
|
|
258
|
+
]);
|
|
259
|
+
/** True when some requirement governs this row ID (BAPI-1452). */
|
|
260
|
+
export function isGovernedReadinessRowId(id) {
|
|
261
|
+
return ALL_GOVERNED_ROW_IDS.has(id);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Project requirement applicability onto rows: `blocking` is membership in an
|
|
265
|
+
* applicable requirement's governed row IDs, and nothing else. Status is never
|
|
266
|
+
* read.
|
|
267
|
+
*/
|
|
268
|
+
export function projectReadinessRowBlocking(checks, context = {}) {
|
|
269
|
+
const governed = applicableGovernedRowIds(context);
|
|
270
|
+
return checks.map((check) => ({ ...check, blocking: governed.has(check.id) }));
|
|
271
|
+
}
|
|
272
|
+
/** The credential configuration key a mode reads. Never the deprecated alias. */
|
|
273
|
+
export function launchCredentialKey(mode) {
|
|
274
|
+
return LOCAL_MERGE_CREDENTIAL_ENV_KEYS[mode];
|
|
275
|
+
}
|
|
276
|
+
const UNVERIFIED_CREDENTIAL_COPY = {
|
|
277
|
+
probe_unavailable: {
|
|
278
|
+
detail: "GitHub gave no usable answer to the capability check (rate limit, outage, timeout, or an inconclusive status)",
|
|
279
|
+
remedy: "Retry drive-epic shortly. The merge itself still checks the credential before it runs.",
|
|
280
|
+
},
|
|
281
|
+
gh_unavailable: {
|
|
282
|
+
detail: "the GitHub CLI (gh) could not be run on this host, so the credential could not be checked",
|
|
283
|
+
remedy: "Install the GitHub CLI (gh) on the host that runs the executor; local merges run through it.",
|
|
284
|
+
},
|
|
285
|
+
repository_unresolved: {
|
|
286
|
+
detail: "the server could not resolve this repository's GitHub owner/name, so there was nothing to check against",
|
|
287
|
+
remedy: "Fix the repository's GitHub App credentials (owner and repository) in Bridge, then retry drive-epic.",
|
|
288
|
+
},
|
|
289
|
+
mode_unresolved: {
|
|
290
|
+
detail: "the server could not resolve which merge execution mode a new run would use",
|
|
291
|
+
remedy: "Check the supervisor configuration's merge execution setting, then retry drive-epic.",
|
|
292
|
+
},
|
|
293
|
+
server_fields_missing: {
|
|
294
|
+
detail: "this Bridge API deploy does not report the resolved merge mode or the provider repository",
|
|
295
|
+
remedy: "Try again later. If drive-epic keeps refusing, email info@bridgegpt.ai so merge capability can be verified before launch.",
|
|
296
|
+
},
|
|
297
|
+
external_executor_plane: {
|
|
298
|
+
detail: "a plane lane runs this run's executor, so this launcher cannot check that executor's credential",
|
|
299
|
+
remedy: "Check the merge credential where the plane runs (`epic-implementer doctor` on that host).",
|
|
300
|
+
},
|
|
301
|
+
external_executor_durable: {
|
|
302
|
+
detail: "the durable launch profile's host runs this run's executor, so this launcher cannot check its credential",
|
|
303
|
+
remedy: "Check the merge credential on the durable executor host (`epic-implementer doctor` there).",
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
/** The merge credential row. Value-free: names keys and `owner/name`, never a token. */
|
|
307
|
+
function credentialCheck(evidence) {
|
|
308
|
+
const base = {
|
|
309
|
+
id: MERGE_CREDENTIAL_CAPABILITY_ROW_ID,
|
|
310
|
+
source: "conductor",
|
|
311
|
+
label: "Merge credential capability",
|
|
312
|
+
};
|
|
313
|
+
if (evidence === null) {
|
|
314
|
+
return createReadinessCheckSafely({
|
|
315
|
+
...base,
|
|
316
|
+
status: "skip",
|
|
317
|
+
detail: "not checked — this launch is attended, so no unattended merge runs",
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
switch (evidence.kind) {
|
|
321
|
+
case "verified": {
|
|
322
|
+
const key = launchCredentialKey(evidence.mode);
|
|
323
|
+
return createReadinessCheckSafely({
|
|
324
|
+
...base,
|
|
325
|
+
status: "pass",
|
|
326
|
+
detail: `${key} has push and can read pull requests on ${evidence.repository}. ` +
|
|
327
|
+
"Branch protection and rulesets are still checked at merge time.",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
case "github_actions":
|
|
331
|
+
return createReadinessCheckSafely({
|
|
332
|
+
...base,
|
|
333
|
+
status: "skip",
|
|
334
|
+
detail: "merges run through the GitHub Actions merge workflow, so no local merge credential is checked",
|
|
335
|
+
});
|
|
336
|
+
case "credential_missing": {
|
|
337
|
+
const key = launchCredentialKey(evidence.mode);
|
|
338
|
+
return createReadinessCheckSafely({
|
|
339
|
+
...base,
|
|
340
|
+
status: "fail",
|
|
341
|
+
detail: `${key} is not set in the environment drive-epic starts the executor with`,
|
|
342
|
+
remediation: `Set ${key} in the environment you run drive-epic from, then run drive-epic again. ` +
|
|
343
|
+
"Bridge never stores it.",
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
case "credential_invalid": {
|
|
347
|
+
const key = launchCredentialKey(evidence.mode);
|
|
348
|
+
return createReadinessCheckSafely({
|
|
349
|
+
...base,
|
|
350
|
+
status: "fail",
|
|
351
|
+
detail: `${key} is set but is not a usable credential (malformed, or rejected by GitHub)`,
|
|
352
|
+
remediation: `Replace ${key} with a valid token, then run drive-epic again.`,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
case "no_access": {
|
|
356
|
+
const key = launchCredentialKey(evidence.mode);
|
|
357
|
+
return createReadinessCheckSafely({
|
|
358
|
+
...base,
|
|
359
|
+
status: "fail",
|
|
360
|
+
detail: `${key} cannot push to ${evidence.repository} or cannot read its pull requests`,
|
|
361
|
+
remediation: `Give the account behind ${key} push (write) access to ${evidence.repository}, ` +
|
|
362
|
+
"or use a token that has it, then run drive-epic again.",
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
case "unverified": {
|
|
366
|
+
const copy = UNVERIFIED_CREDENTIAL_COPY[evidence.reason];
|
|
367
|
+
return createReadinessCheckSafely({
|
|
368
|
+
...base,
|
|
369
|
+
status: "warn",
|
|
370
|
+
detail: `Capability not verified: ${copy.detail}`,
|
|
371
|
+
remediation: copy.remedy,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** The worktree-preparation row. Never carries the absolute exclude path. */
|
|
377
|
+
function worktreeCheck(evidence) {
|
|
378
|
+
const base = {
|
|
379
|
+
id: WORKTREE_PREPARATION_ROW_ID,
|
|
380
|
+
source: "conductor",
|
|
381
|
+
label: "Worktree preparation",
|
|
382
|
+
};
|
|
383
|
+
if (evidence === null) {
|
|
384
|
+
return createReadinessCheckSafely({
|
|
385
|
+
...base,
|
|
386
|
+
status: "skip",
|
|
387
|
+
detail: "not checked — this launch is attended",
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
switch (evidence.kind) {
|
|
391
|
+
case "ready":
|
|
392
|
+
return createReadinessCheckSafely({
|
|
393
|
+
...base,
|
|
394
|
+
status: "pass",
|
|
395
|
+
detail: evidence.present
|
|
396
|
+
? "the executor clone's shared git exclude already lists the executor's entries"
|
|
397
|
+
: "the executor clone's shared git exclude can be written when the executor prepares it",
|
|
398
|
+
});
|
|
399
|
+
case "blocked":
|
|
400
|
+
return createReadinessCheckSafely({
|
|
401
|
+
...base,
|
|
402
|
+
status: "fail",
|
|
403
|
+
detail: "the executor clone's shared git exclude is missing the executor's entries and cannot be written",
|
|
404
|
+
remediation: "Make the clone's .git/info/exclude (or its info directory) writable by the user that runs the " +
|
|
405
|
+
"executor, then run drive-epic again.",
|
|
406
|
+
});
|
|
407
|
+
case "unverified":
|
|
408
|
+
return createReadinessCheckSafely({
|
|
409
|
+
...base,
|
|
410
|
+
status: "warn",
|
|
411
|
+
detail: evidence.reason === "inspection_unavailable"
|
|
412
|
+
? "Capability not verified: the executor clone's shared git exclude could not be inspected"
|
|
413
|
+
: evidence.reason === "external_executor_plane"
|
|
414
|
+
? "Capability not verified: a plane lane owns this run's executor clone, so this launcher cannot inspect it"
|
|
415
|
+
: "Capability not verified: the durable launch profile's host owns this run's executor clone, so this launcher cannot inspect it",
|
|
416
|
+
remediation: evidence.reason === "inspection_unavailable"
|
|
417
|
+
? "The executor still prepares the exclude itself at startup; run `epic-implementer doctor` if workers later commit executor files."
|
|
418
|
+
: "That executor still prepares its own clone at startup; inspect it on the host that runs it.",
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/** The Setup-page posture row, from the server's unattended fact only. */
|
|
423
|
+
export function unattendedPostureCheck(readiness) {
|
|
424
|
+
const requirement = V2_READINESS_REQUIREMENTS.find((req) => req.id === "server.unattended-posture");
|
|
425
|
+
const remedy = requirement?.remedyFor?.(readiness) ??
|
|
426
|
+
`Turn on '${UNATTENDED_POSTURE_SETUP_CONTROL}' on the repository's Setup page in the Bridge admin panel.`;
|
|
427
|
+
const base = {
|
|
428
|
+
id: UNATTENDED_POSTURE_ROW_ID,
|
|
429
|
+
source: "server",
|
|
430
|
+
label: "Unattended posture (Setup page)",
|
|
431
|
+
};
|
|
432
|
+
if (readiness.unattended === null || readiness.unattended === undefined) {
|
|
433
|
+
return createReadinessCheckSafely({
|
|
434
|
+
...base,
|
|
435
|
+
status: "fail",
|
|
436
|
+
detail: "this Bridge API deploy does not report the Setup-page unattended switch",
|
|
437
|
+
remediation: remedy,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
return readiness.unattended.conductor_allowed === true
|
|
441
|
+
? createReadinessCheckSafely({
|
|
442
|
+
...base,
|
|
443
|
+
status: "pass",
|
|
444
|
+
detail: `'${UNATTENDED_POSTURE_SETUP_CONTROL}' is on for this repository`,
|
|
445
|
+
})
|
|
446
|
+
: createReadinessCheckSafely({
|
|
447
|
+
...base,
|
|
448
|
+
status: "fail",
|
|
449
|
+
detail: `'${UNATTENDED_POSTURE_SETUP_CONTROL}' is off for this repository, so an unattended run is refused`,
|
|
450
|
+
remediation: remedy,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
/** The resolved merge mode, as a supporting fact. Absent or null is a warning, never a default. */
|
|
454
|
+
export function resolvedMergeModeCheck(readiness) {
|
|
455
|
+
const base = { id: RESOLVED_MERGE_MODE_ROW_ID, source: "server", label: "Resolved merge mode" };
|
|
456
|
+
const mode = readiness.resolved_merge_execution_mode;
|
|
457
|
+
if (mode === undefined) {
|
|
458
|
+
return createReadinessCheckSafely({
|
|
459
|
+
...base,
|
|
460
|
+
status: "warn",
|
|
461
|
+
detail: "Capability not verified: this Bridge API deploy does not report the resolved merge mode",
|
|
462
|
+
remediation: UNVERIFIED_CREDENTIAL_COPY.server_fields_missing.remedy,
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
if (mode === null) {
|
|
466
|
+
return createReadinessCheckSafely({
|
|
467
|
+
...base,
|
|
468
|
+
status: "warn",
|
|
469
|
+
detail: "Capability not verified: the server could not resolve a supported merge mode",
|
|
470
|
+
remediation: UNVERIFIED_CREDENTIAL_COPY.mode_unresolved.remedy,
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
return createReadinessCheckSafely({ ...base, status: "pass", detail: `a new run merges in ${mode} mode` });
|
|
474
|
+
}
|
|
475
|
+
/** The provider repository, as a supporting fact. */
|
|
476
|
+
export function providerRepositoryCheck(readiness) {
|
|
477
|
+
const base = { id: PROVIDER_REPOSITORY_ROW_ID, source: "server", label: "Provider repository" };
|
|
478
|
+
const repository = readiness.provider_repository;
|
|
479
|
+
if (repository === undefined) {
|
|
480
|
+
return createReadinessCheckSafely({
|
|
481
|
+
...base,
|
|
482
|
+
status: "warn",
|
|
483
|
+
detail: "Capability not verified: this Bridge API deploy does not report the provider repository",
|
|
484
|
+
remediation: UNVERIFIED_CREDENTIAL_COPY.server_fields_missing.remedy,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
if (!isProviderRepositoryIdentity(repository)) {
|
|
488
|
+
return createReadinessCheckSafely({
|
|
489
|
+
...base,
|
|
490
|
+
status: "warn",
|
|
491
|
+
detail: "Capability not verified: the server could not resolve this repository's GitHub owner/name",
|
|
492
|
+
remediation: UNVERIFIED_CREDENTIAL_COPY.repository_unresolved.remedy,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return createReadinessCheckSafely({ ...base, status: "pass", detail: `GitHub repository ${repository}` });
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* The launch readiness rows, in render order: posture, merge credential
|
|
499
|
+
* capability, worktree preparation, then the supporting facts. `blocking` is
|
|
500
|
+
* projected from requirement applicability only.
|
|
501
|
+
*/
|
|
502
|
+
export function buildLaunchReadinessChecks(readiness, context) {
|
|
503
|
+
const evidence = optedOut(context) ? undefined : context.launchEvidence;
|
|
504
|
+
return projectReadinessRowBlocking([
|
|
505
|
+
unattendedPostureCheck(readiness),
|
|
506
|
+
credentialCheck(evidence?.credential ?? null),
|
|
507
|
+
worktreeCheck(evidence?.worktree ?? null),
|
|
508
|
+
resolvedMergeModeCheck(readiness),
|
|
509
|
+
providerRepositoryCheck(readiness),
|
|
510
|
+
], context);
|
|
511
|
+
}
|
|
512
|
+
/** The two launch-evidence rows a launch can be refused on. */
|
|
513
|
+
export const LAUNCH_EVIDENCE_ROW_IDS = [
|
|
514
|
+
MERGE_CREDENTIAL_CAPABILITY_ROW_ID,
|
|
515
|
+
WORKTREE_PREPARATION_ROW_ID,
|
|
516
|
+
];
|
|
517
|
+
/**
|
|
518
|
+
* The first PROVEN launch-evidence blocker: a failing row an applicable
|
|
519
|
+
* requirement governs. Unverified evidence is a warning and never returned.
|
|
520
|
+
*/
|
|
521
|
+
export function firstLaunchEvidenceBlocker(checks) {
|
|
522
|
+
for (const id of LAUNCH_EVIDENCE_ROW_IDS) {
|
|
523
|
+
const check = checks.find((c) => c.id === id);
|
|
524
|
+
if (check && check.status === "fail" && check.blocking)
|
|
525
|
+
return check;
|
|
526
|
+
}
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* The applicability label a readiness row renders with (BAPI-1452).
|
|
531
|
+
*
|
|
532
|
+
* `Blocking` only for a FAILED row an applicable requirement governs; `Warning`
|
|
533
|
+
* for any warning (unknown evidence is never green); `Informational` for a row
|
|
534
|
+
* no applicable requirement governs; `OK` for a passing governed row.
|
|
535
|
+
*/
|
|
536
|
+
export function readinessApplicabilityLabel(check) {
|
|
537
|
+
if (check.detail === 'unknown')
|
|
538
|
+
return "Couldn't check";
|
|
539
|
+
if (check.detail === 'not_met' && !check.blocking)
|
|
540
|
+
return 'Not met';
|
|
541
|
+
if (check.status === "fail" && check.blocking)
|
|
542
|
+
return "Blocking";
|
|
543
|
+
if (check.status === "warn")
|
|
544
|
+
return "Warning";
|
|
545
|
+
if (!check.blocking || check.status === "fail" || check.status === "skip")
|
|
546
|
+
return "Informational";
|
|
547
|
+
return "OK";
|
|
548
|
+
}
|
|
549
|
+
/** Render the compact launch summary drive-epic prints before creating a run. */
|
|
550
|
+
export function formatLaunchReadinessSummary(checks, options = {}) {
|
|
551
|
+
return formatReadinessPrerequisites(mergeReadinessPrerequisites(checks, options.prerequisites), options);
|
|
552
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED — do not edit manually. Regenerate with: npm run build
|
|
2
|
-
export const VERSION = "0.2.
|
|
3
|
-
export const BUILD_COMMIT = "
|
|
4
|
-
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.
|
|
2
|
+
export const VERSION = "0.2.70";
|
|
3
|
+
export const BUILD_COMMIT = "7fb22e3a1d7b";
|
|
4
|
+
export const LAUNCHER_ARGS = ["-y", "--prefer-offline", "@bridge_gpt/mcp-server@0.2.70", "serve"];
|