@bridge_gpt/mcp-server 0.2.38 → 0.2.39

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.
Files changed (85) hide show
  1. package/README.md +189 -14
  2. package/build/agent-capabilities/probe-context.js +2 -1
  3. package/build/agent-launchers/claude-executor-adapter.js +392 -0
  4. package/build/agent-launchers/executor-adapter-inspection.js +163 -0
  5. package/build/agent-launchers/executor-adapter-registry.js +90 -0
  6. package/build/agent-launchers/executor-adapter.js +136 -0
  7. package/build/agent-registry.js +28 -0
  8. package/build/agents.generated.js +1 -1
  9. package/build/claude-login.js +85 -0
  10. package/build/claude-user-config-doctor.js +59 -33
  11. package/build/commands.generated.js +12 -11
  12. package/build/conduct-epic/bridge-client.js +345 -0
  13. package/build/conduct-epic/checkpoint-store.js +423 -0
  14. package/build/conduct-epic/cli.js +1732 -0
  15. package/build/conduct-epic/lock.js +302 -0
  16. package/build/conduct-epic/pr-state.js +197 -0
  17. package/build/conduct-epic/spawn.js +101 -0
  18. package/build/conductor/bridge-api-client.js +37 -2
  19. package/build/conductor/doctor.js +11 -1
  20. package/build/conductor/install-doctor.js +184 -10
  21. package/build/conductor-bin.js +7 -7
  22. package/build/credential-store.js +10 -4
  23. package/build/credentials-cli.js +34 -19
  24. package/build/docs.generated.js +1 -1
  25. package/build/doctor.js +579 -88
  26. package/build/executor/agent-identity.js +32 -0
  27. package/build/executor/cli.js +50 -39
  28. package/build/executor/deps.js +15 -1
  29. package/build/executor/env.js +56 -45
  30. package/build/executor/index.js +9 -1
  31. package/build/executor/install-preflight.js +138 -0
  32. package/build/executor/job-errors.js +200 -0
  33. package/build/executor/job-runner.js +619 -268
  34. package/build/executor/observation.js +165 -0
  35. package/build/executor/permissions.js +163 -36
  36. package/build/executor/platform.js +54 -0
  37. package/build/executor/preflight.js +175 -67
  38. package/build/executor/process.js +39 -7
  39. package/build/executor/runner.js +19 -0
  40. package/build/executor/service-lifecycle.js +269 -0
  41. package/build/executor/service-unit.js +121 -12
  42. package/build/executor/stale-artifacts.js +70 -0
  43. package/build/executor/test-clock.js +188 -24
  44. package/build/executor/worker-command.js +22 -58
  45. package/build/executor/worker-log.js +82 -0
  46. package/build/executor/worktree-lock.js +264 -0
  47. package/build/index.js +527 -357
  48. package/build/install-bridge-conductor.js +376 -38
  49. package/build/install-bridge.js +414 -114
  50. package/build/install-doctor.js +13 -0
  51. package/build/install-reexec.js +5 -3
  52. package/build/mcp-install-state.js +130 -0
  53. package/build/mcp-profile.js +11 -2
  54. package/build/mcp-provisioning.js +15 -0
  55. package/build/merge-pull-request.js +562 -0
  56. package/build/phase-result-artifacts.js +450 -0
  57. package/build/pipeline-orchestrator.js +4 -0
  58. package/build/pipeline-utils.js +16 -0
  59. package/build/pipelines.generated.js +7 -7
  60. package/build/plane/preflight.js +18 -14
  61. package/build/plane/supervisor.js +8 -1
  62. package/build/project-root.js +34 -0
  63. package/build/readme.generated.js +1 -1
  64. package/build/run-unit-tests-launcher.js +36 -9
  65. package/build/setup-epic.js +57 -4
  66. package/build/sfcc/permissions.js +25 -6
  67. package/build/sfcc/reads-site-preference.js +6 -0
  68. package/build/sfcc/register.js +61 -23
  69. package/build/sfcc/registration-inventory.js +89 -0
  70. package/build/sfcc/setup-status.js +18 -34
  71. package/build/sfcc/tool-wrapper.js +294 -17
  72. package/build/sfcc/write-grants.js +33 -1
  73. package/build/sfcc/write-guard.js +41 -12
  74. package/build/sfcc/writes-custom-object-def.js +6 -2
  75. package/build/sfcc/writes-site-preference.js +6 -1
  76. package/build/sfcc/writes-system-object.js +11 -2
  77. package/build/sfcc/writes.js +13 -8
  78. package/build/start-tickets-prereqs.js +25 -15
  79. package/build/start-tickets.js +123 -21
  80. package/build/version.generated.js +1 -1
  81. package/build/worktree-core.js +9 -3
  82. package/docs/install/mcp-tool-integrations.md +54 -9
  83. package/docs/install/sfcc-integration.md +71 -24
  84. package/package.json +3 -3
  85. package/build/executor/worker-config-isolation.js +0 -287
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Bridge API client for the `conduct-epic` CLI (BAPI-802).
3
+ *
4
+ * This module lives OUTSIDE `mcp_server/src/conductor/`, which is frozen v1. It
5
+ * imports from `conductor/bridge-api-client.ts` in the reuse-only direction the
6
+ * freeze allows — shared URL construction, the access type, and the timeout/
7
+ * sanitization fetch helpers — and adds nothing under `conductor/`. That import
8
+ * is recorded in `conductor/README.md`'s survivor table.
9
+ *
10
+ * Two contract properties, fixed by `conduct-epic-bridge-client` and relied on by
11
+ * BAPI-803, which extends this same file:
12
+ *
13
+ * - **No function throws.** Every failure — transport, timeout, HTTP status,
14
+ * malformed body, even a bad `baseUrl` — resolves to
15
+ * `{ ok: false, status, error }`. A CLI that has to wrap each call in
16
+ * `try/catch` gets that wrong exactly once, in the recovery path where it
17
+ * matters most.
18
+ * - **The API key never leaves the `X-API-Key` header.** It is not in a URL, a
19
+ * query string, a request body, or any returned error text. Unrecognized
20
+ * throw values collapse to a stable generic message rather than being
21
+ * stringified, because a stringified unknown is exactly how a credential
22
+ * escapes.
23
+ *
24
+ * Credentials are NOT resolved here: the caller passes an access object it
25
+ * already obtained from `resolveConductorBridgeApiAccess()`, which reads
26
+ * `BAPI_API_KEY` then the user-scoped credential store.
27
+ *
28
+ * No MCP tool is registered from this module — the `tools/list` token budget is
29
+ * effectively frozen.
30
+ */
31
+ import { buildConductorJiraUrl, fetchConductorJsonPostWithTimeout, fetchConductorJsonPutWithTimeout, fetchConductorJsonWithTimeout, fetchConductorReadiness, fetchEffectiveSupervisorConfig, fetchEffectiveSupervisorSetup, fetchEpicRunState, fetchParseStatus, fetchPrReviewStatus, pollCiChecksForCommit, ConductorBridgeApiError, CONDUCTOR_FETCH_TIMEOUT_MS, } from "../conductor/bridge-api-client.js";
32
+ /**
33
+ * The one message returned for any failure whose cause cannot be described
34
+ * without risking the credential. Stable so callers can match on it.
35
+ */
36
+ const GENERIC_ERROR = "Bridge API request failed";
37
+ /** GET headers. The key travels in this header and nowhere else. */
38
+ function getHeaders(access) {
39
+ return { "X-API-Key": access.apiKey };
40
+ }
41
+ /** POST headers. Same rule, plus the JSON content type. */
42
+ function postHeaders(access) {
43
+ return { "X-API-Key": access.apiKey, "Content-Type": "application/json" };
44
+ }
45
+ /** PUT headers. Identical to {@link postHeaders}; named for the call site. */
46
+ function putHeaders(access) {
47
+ return { "X-API-Key": access.apiKey, "Content-Type": "application/json" };
48
+ }
49
+ /**
50
+ * Remove the exact API key from a message that is otherwise safe to surface.
51
+ *
52
+ * The shared helpers already redact header values from their diagnostics, so
53
+ * this is a second, independent pass over the final string: the belt is cheap
54
+ * and the failure it guards against is unrecoverable. Very short keys are left
55
+ * alone because scrubbing a 1-3 character string would mangle ordinary prose.
56
+ */
57
+ function scrubKey(text, apiKey) {
58
+ if (!apiKey || apiKey.length < 4)
59
+ return text;
60
+ return text.split(apiKey).join("[REDACTED]");
61
+ }
62
+ /**
63
+ * Convert any thrown value into a `{ ok: false }` result.
64
+ *
65
+ * A `ConductorBridgeApiError` carries a message the shared helper already built
66
+ * from the coarse kind, HTTP status, backend error code, and a bounded,
67
+ * secret-redacted body preview — that is what lets a 409 name the override
68
+ * branch the server preserved. Anything else is collapsed to
69
+ * {@link GENERIC_ERROR}: an unknown throw value has unknown contents, and
70
+ * stringifying it is how a credential or a raw response body escapes.
71
+ */
72
+ function toFailure(err, access) {
73
+ if (err instanceof ConductorBridgeApiError) {
74
+ return {
75
+ ok: false,
76
+ status: typeof err.status === "number" ? err.status : null,
77
+ error: scrubKey(err.message || GENERIC_ERROR, access.apiKey),
78
+ };
79
+ }
80
+ return { ok: false, status: null, error: GENERIC_ERROR };
81
+ }
82
+ /** True when `value` is a plain (non-array, non-null) object. */
83
+ function isRecord(value) {
84
+ return typeof value === "object" && value !== null && !Array.isArray(value);
85
+ }
86
+ /** Read a `string | null` field, returning `undefined` when it is neither. */
87
+ function nullableString(value) {
88
+ if (value === null)
89
+ return null;
90
+ if (typeof value === "string")
91
+ return value;
92
+ return undefined;
93
+ }
94
+ /** Parse the `override` member, or `undefined` when it is malformed. */
95
+ function parseOverride(value) {
96
+ if (value === null || value === undefined)
97
+ return null;
98
+ if (!isRecord(value))
99
+ return undefined;
100
+ const original = nullableString(value["original_base_branch"]);
101
+ if (typeof value["repo_name"] !== "string" ||
102
+ original === undefined ||
103
+ typeof value["override_branch"] !== "string" ||
104
+ typeof value["created_at"] !== "string") {
105
+ return undefined;
106
+ }
107
+ return {
108
+ repo_name: value["repo_name"],
109
+ original_base_branch: original,
110
+ override_branch: value["override_branch"],
111
+ created_at: value["created_at"],
112
+ };
113
+ }
114
+ /**
115
+ * Validate a 200 body against {@link IndexBranchStatus}, returning `null` when it
116
+ * does not match.
117
+ *
118
+ * A body that parsed as JSON but is not this shape is treated as a failure
119
+ * rather than cast through: `changed` and `current_base_branch` drive
120
+ * destructive branch decisions in the caller, and a missing `changed` read as
121
+ * `undefined` would silently mean "nothing happened".
122
+ */
123
+ function parseStatus(body) {
124
+ if (!isRecord(body))
125
+ return null;
126
+ const current = nullableString(body["current_base_branch"]);
127
+ const override = parseOverride(body["override"]);
128
+ if (typeof body["repo_name"] !== "string" ||
129
+ current === undefined ||
130
+ override === undefined ||
131
+ typeof body["changed"] !== "boolean") {
132
+ return null;
133
+ }
134
+ return {
135
+ repo_name: body["repo_name"],
136
+ current_base_branch: current,
137
+ override,
138
+ changed: body["changed"],
139
+ };
140
+ }
141
+ /** Wrap a parsed body into a success result, or a generic failure if malformed. */
142
+ function toStatusResult(body) {
143
+ const status = parseStatus(body);
144
+ if (status === null) {
145
+ return { ok: false, status: null, error: GENERIC_ERROR };
146
+ }
147
+ return { ok: true, value: status };
148
+ }
149
+ /**
150
+ * `POST /jira/index-branch/repoint` — point the indexed base branch at `branch`.
151
+ *
152
+ * A repeat call with the same branch resolves `{ ok: true }` with
153
+ * `value.changed === false`. A call for a *different* branch while an override is
154
+ * active resolves `{ ok: false, status: 409 }` with an error naming the override
155
+ * the server preserved — it is not an exception, because a conflict is an
156
+ * expected outcome the CLI must report and continue from.
157
+ */
158
+ export async function repointIndexBranch(access, input, fetchImpl = globalThis.fetch) {
159
+ try {
160
+ // `buildConductorJiraUrl` already appends the `/jira` segment.
161
+ const url = buildConductorJiraUrl(access.baseUrl, "/index-branch/repoint");
162
+ const body = await fetchConductorJsonPostWithTimeout(url, postHeaders(access), JSON.stringify({ repo_name: access.repoName, branch: input.branch }), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
163
+ return toStatusResult(body);
164
+ }
165
+ catch (err) {
166
+ return toFailure(err, access);
167
+ }
168
+ }
169
+ /**
170
+ * `POST /jira/index-branch/restore` — write the stored original branch back and
171
+ * drop the override.
172
+ *
173
+ * Idempotent: with no active override the call resolves `{ ok: true }` with
174
+ * `value.changed === false` and `value.override === null`. That matters because
175
+ * restore is the recovery path — it must be safe to run after a crash whose
176
+ * position is unknown.
177
+ */
178
+ export async function restoreIndexBranch(access, fetchImpl = globalThis.fetch) {
179
+ try {
180
+ const url = buildConductorJiraUrl(access.baseUrl, "/index-branch/restore");
181
+ const body = await fetchConductorJsonPostWithTimeout(url, postHeaders(access), JSON.stringify({ repo_name: access.repoName }), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
182
+ return toStatusResult(body);
183
+ }
184
+ catch (err) {
185
+ return toFailure(err, access);
186
+ }
187
+ }
188
+ /**
189
+ * `GET /jira/index-branch?repo_name=` — report the current indexed branch and any
190
+ * active override.
191
+ *
192
+ * This is how a stale override left behind by a crashed loop becomes visible;
193
+ * `value.changed` is always `false` because a read changes nothing.
194
+ */
195
+ export async function getIndexBranch(access, fetchImpl = globalThis.fetch) {
196
+ try {
197
+ const url = buildConductorJiraUrl(access.baseUrl, "/index-branch", {
198
+ repo_name: access.repoName,
199
+ });
200
+ const body = await fetchConductorJsonWithTimeout(url, getHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
201
+ return toStatusResult(body);
202
+ }
203
+ catch (err) {
204
+ return toFailure(err, access);
205
+ }
206
+ }
207
+ /**
208
+ * Run `operation` and convert any thrown value into a sanitized failure result.
209
+ *
210
+ * Every +803 read is this shape, so the try/catch lives in one place: a helper
211
+ * added later cannot forget it and reintroduce a throwing client.
212
+ */
213
+ async function wrap(access, operation) {
214
+ try {
215
+ return { ok: true, value: await operation() };
216
+ }
217
+ catch (err) {
218
+ return toFailure(err, access);
219
+ }
220
+ }
221
+ /**
222
+ * `PUT /jira/epic-runs/supervisor-config/defaults/?repo_name=` — replace the
223
+ * project-default supervisor configuration.
224
+ *
225
+ * The complete {@link EpicSupervisorConfigRequest} is sent. `init` builds it by
226
+ * reading the effective config and flipping exactly one field, so enabling
227
+ * auto-merge never silently reverts another operator setting.
228
+ */
229
+ export async function putSupervisorConfigDefaults(access, config, fetchImpl = globalThis.fetch) {
230
+ return wrap(access, () => {
231
+ const url = buildConductorJiraUrl(access.baseUrl, "/epic-runs/supervisor-config/defaults/", { repo_name: access.repoName });
232
+ return fetchConductorJsonPutWithTimeout(url, putHeaders(access), JSON.stringify(config), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
233
+ });
234
+ }
235
+ /** `GET /jira/epic-runs/supervisor-config/defaults/` — the effective config. */
236
+ export async function getEffectiveSupervisorConfig(access, fetchImpl = globalThis.fetch) {
237
+ return wrap(access, () => fetchEffectiveSupervisorConfig(access, undefined, fetchImpl));
238
+ }
239
+ /**
240
+ * `GET /jira/epic-runs/supervisor-setup/defaults/` — the effective setup.
241
+ *
242
+ * Returned verbatim, `done_gate_config` included and un-normalized: it is the
243
+ * raw operator-authored JSONB that `parseDoneGateConfig` is the sole authority
244
+ * on, and normalizing it here would fork that vocabulary.
245
+ */
246
+ export async function getEffectiveSupervisorSetup(access, fetchImpl = globalThis.fetch) {
247
+ return wrap(access, () => fetchEffectiveSupervisorSetup(access, undefined, fetchImpl));
248
+ }
249
+ /**
250
+ * `GET /jira/epic-runs/conductor-readiness?repo_name=` — readiness facts.
251
+ *
252
+ * `init` reads three of them: `supervisor.auto_merge_enabled`,
253
+ * `supervisor.required_checks_count`, and `supervisor.required_checks_empty`.
254
+ * The underlying fetcher validates the body fail-closed, so a 200 whose body is
255
+ * missing `required_checks_empty` becomes a failure here rather than an
256
+ * apparently-healthy report.
257
+ */
258
+ export async function getConductorReadiness(access, fetchImpl = globalThis.fetch) {
259
+ return wrap(access, () => fetchConductorReadiness(access, fetchImpl));
260
+ }
261
+ /**
262
+ * `POST /jira/resolve-ci-checks` — discover and classify the repository's CI
263
+ * checks, and warm the `poll-ci-checks` cache as a side effect.
264
+ *
265
+ * `commitRef` is optional and forwarded only when supplied. The response is the
266
+ * server's soft envelope, preserved verbatim inside `value`.
267
+ */
268
+ export async function resolveCiChecks(access, commitRef, fetchImpl = globalThis.fetch) {
269
+ return wrap(access, () => {
270
+ const url = buildConductorJiraUrl(access.baseUrl, "/resolve-ci-checks");
271
+ const payload = { repo_name: access.repoName };
272
+ if (typeof commitRef === "string" && commitRef.trim().length > 0) {
273
+ payload.commit_ref = commitRef;
274
+ }
275
+ return fetchConductorJsonPostWithTimeout(url, postHeaders(access), JSON.stringify(payload), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
276
+ });
277
+ }
278
+ /**
279
+ * `GET /jira/poll-ci-checks?repo_name=&commit_ref=` — CI status for one SHA.
280
+ *
281
+ * Until `resolve-ci-checks` has run once for the repository this answers 200
282
+ * with `{ available: false, action: "Call resolve-ci-checks first…" }`. That is
283
+ * a successful result carrying an instruction, not an error, and it stays in
284
+ * `value` so `status` can act on it.
285
+ */
286
+ export async function pollCiChecks(access, commitRef, fetchImpl = globalThis.fetch) {
287
+ return wrap(access, () => pollCiChecksForCommit(access, commitRef, fetchImpl));
288
+ }
289
+ /**
290
+ * `GET /vcs/pull-requests/{n}/reviews/status?repo_name=` — normalized review
291
+ * state. Root-mounted, NOT under `/jira`.
292
+ *
293
+ * `prNumber` is validated by the wrapped fetcher before any request is issued,
294
+ * so a zero/negative/non-integer number fails without a network round trip.
295
+ */
296
+ export async function getPrReviewStatus(access, prNumber, fetchImpl = globalThis.fetch) {
297
+ return wrap(access, () => fetchPrReviewStatus(access, prNumber, fetchImpl));
298
+ }
299
+ /** `GET /jira/parse-status?repo_name=` — the durable parse-run status. */
300
+ export async function getParseStatus(access, fetchImpl = globalThis.fetch) {
301
+ return wrap(access, () => fetchParseStatus(access, fetchImpl));
302
+ }
303
+ /**
304
+ * `GET /jira/epic-runs/runs/{epicKey}/state?repo_name=` — the durable epic-run
305
+ * state.
306
+ *
307
+ * A pure READ. `conduct-epic` never creates an `epic_run`; `init` calls this
308
+ * only to refuse when the server is already conducting the same epic through
309
+ * the v2 reconciler, which would otherwise drive the same tickets in parallel.
310
+ */
311
+ export async function getEpicRunState(access, epicKey, fetchImpl = globalThis.fetch) {
312
+ return wrap(access, () => fetchEpicRunState(access, epicKey, fetchImpl));
313
+ }
314
+ /**
315
+ * `GET /jira/config-field/base_branch?repo_name=` — the repository's configured
316
+ * base branch.
317
+ *
318
+ * Normalized defensively: an absent `value`, an explicit `null`, and a
319
+ * blank string all become `base_branch: null`, which the caller reads as "not
320
+ * configured" and falls through to `main`. A body that is not an object at all
321
+ * is a failure — silently treating an unparseable response as "not configured"
322
+ * would provision the epic branch from the wrong base.
323
+ */
324
+ export async function getConfigFieldBaseBranch(access, fetchImpl = globalThis.fetch) {
325
+ const result = await wrap(access, () => {
326
+ const url = buildConductorJiraUrl(access.baseUrl, "/config-field/base_branch", {
327
+ repo_name: access.repoName,
328
+ });
329
+ return fetchConductorJsonWithTimeout(url, getHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
330
+ });
331
+ if (!result.ok)
332
+ return result;
333
+ if (!isRecord(result.value)) {
334
+ return { ok: false, status: null, error: GENERIC_ERROR };
335
+ }
336
+ const raw = result.value["value"];
337
+ if (raw === null || raw === undefined) {
338
+ return { ok: true, value: { base_branch: null } };
339
+ }
340
+ if (typeof raw !== "string") {
341
+ return { ok: false, status: null, error: GENERIC_ERROR };
342
+ }
343
+ const trimmed = raw.trim();
344
+ return { ok: true, value: { base_branch: trimmed.length === 0 ? null : trimmed } };
345
+ }