@bridge_gpt/mcp-server 0.2.9 → 0.2.12
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 +59 -7
- package/build/commands.generated.js +6 -6
- package/build/conductor/bridge-api-client.js +263 -35
- package/build/conductor/cli.js +38 -17
- package/build/conductor/doctor.js +35 -2
- package/build/conductor/done-gate.js +301 -58
- package/build/conductor/epic-reconcile.js +318 -4
- package/build/conductor/epic-runtime.js +382 -18
- package/build/conductor/epic-state.js +188 -15
- package/build/conductor/errors.js +12 -0
- package/build/conductor/git-ci-types.js +16 -0
- package/build/conductor/git-producer.js +4 -4
- package/build/conductor/merge-ledger.js +7 -7
- package/build/conductor/pr-ci-producer.js +118 -19
- package/build/conductor/pr-review-producer.js +116 -0
- package/build/conductor/producer-ledger.js +5 -5
- package/build/conductor/spec-review-producer.js +88 -0
- package/build/conductor/store.js +105 -26
- package/build/conductor/supervisor-ledger.js +2 -2
- package/build/conductor/supervisor-merge.js +5 -5
- package/build/conductor/supervisor-message-relay.js +32 -1
- package/build/conductor/supervisor-runtime.js +10 -10
- package/build/conductor/taxonomy.js +8 -0
- package/build/conductor/tools.js +7 -7
- package/build/conductor-bin.js +12350 -19
- package/build/conductor-claude-hook-bin.js +167 -17
- package/build/decision-page-schema.js +26 -0
- package/build/doctor.js +200 -0
- package/build/index.js +23696 -4351
- package/build/init.js +481 -0
- package/build/install-bridge.js +772 -0
- package/build/mcp-profile.js +43 -0
- package/build/pipelines.generated.js +70 -48
- package/build/readme.generated.js +1 -1
- package/build/start-tickets-conductor.js +1 -0
- package/build/start-tickets.js +186 -10
- package/build/upgrade-cli.js +154 -0
- package/build/version.generated.js +1 -1
- package/package.json +7 -4
- package/pipelines/check-ci-ticket.json +2 -2
- package/pipelines/implement-ticket.json +2 -2
- package/pipelines/learn-repository.json +84 -42
- package/smoke-test/SMOKE-TEST.md +11 -17
|
@@ -13,7 +13,7 @@ import os from "node:os";
|
|
|
13
13
|
import { readFile, stat } from "node:fs/promises";
|
|
14
14
|
import { resolveBapiCredentials } from "../credential-store.js";
|
|
15
15
|
import { resolveStartTicketsRepoName } from "../start-tickets-repo.js";
|
|
16
|
-
import {
|
|
16
|
+
import { normalizePrNumber, normalizeSha } from "./git-ci-types.js";
|
|
17
17
|
import { ConductorValidationError } from "./errors.js";
|
|
18
18
|
/** Default Bridge API base URL when `BAPI_BASE_URL` is unset. */
|
|
19
19
|
export const CONDUCTOR_DEFAULT_BASE_URL = "https://bridgegpt-api.com";
|
|
@@ -95,7 +95,7 @@ function conductorGetHeaders(access) {
|
|
|
95
95
|
* non-2xx response — never including the response body, headers, or API key. The
|
|
96
96
|
* timer is always cleared.
|
|
97
97
|
*/
|
|
98
|
-
export async function fetchConductorJsonWithTimeout(url, headers, timeoutMs, fetchImpl = fetch) {
|
|
98
|
+
export async function fetchConductorJsonWithTimeout(url, headers, timeoutMs, fetchImpl = globalThis.fetch) {
|
|
99
99
|
const controller = new AbortController();
|
|
100
100
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
101
101
|
try {
|
|
@@ -132,7 +132,7 @@ export async function fetchConductorJsonWithTimeout(url, headers, timeoutMs, fet
|
|
|
132
132
|
* `value` payload (or `undefined` when the envelope lacks a `value`). The field
|
|
133
133
|
* name is URL-encoded; the repo scope is always included.
|
|
134
134
|
*/
|
|
135
|
-
export async function fetchConductorConfigField(access, fieldName, fetchImpl = fetch) {
|
|
135
|
+
export async function fetchConductorConfigField(access, fieldName, fetchImpl = globalThis.fetch) {
|
|
136
136
|
const url = buildConductorJiraUrl(access.baseUrl, `/config-field/${encodeURIComponent(fieldName)}`, {
|
|
137
137
|
repo_name: access.repoName,
|
|
138
138
|
});
|
|
@@ -142,9 +142,41 @@ export async function fetchConductorConfigField(access, fieldName, fetchImpl = f
|
|
|
142
142
|
}
|
|
143
143
|
return undefined;
|
|
144
144
|
}
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Fetch the effective supervisor setup for a repo.
|
|
147
|
+
*
|
|
148
|
+
* When ``epicKey`` is provided, hits ``GET /jira/epic-runs/runs/{epicKey}/supervisor-setup/``
|
|
149
|
+
* (per-epic row with project-default fallback). When omitted, hits
|
|
150
|
+
* ``GET /jira/epic-runs/supervisor-setup/defaults/`` (project-default only).
|
|
151
|
+
* Fails closed (throws ``ConductorBridgeApiError``) on any network or server error.
|
|
152
|
+
*/
|
|
153
|
+
export async function fetchEffectiveSupervisorSetup(access, epicKey, fetchImpl = globalThis.fetch) {
|
|
154
|
+
const apiPath = epicKey
|
|
155
|
+
? `${EPIC_RUNS_API_PREFIX}/runs/${encodeURIComponent(epicKey)}/supervisor-setup/`
|
|
156
|
+
: `${EPIC_RUNS_API_PREFIX}/supervisor-setup/defaults/`;
|
|
157
|
+
const url = buildConductorJiraUrl(access.baseUrl, apiPath, {
|
|
158
|
+
repo_name: access.repoName,
|
|
159
|
+
});
|
|
160
|
+
const parsed = await fetchConductorJsonWithTimeout(url, conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
161
|
+
return parsed;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fetch the effective supervisor config for a repo.
|
|
165
|
+
*
|
|
166
|
+
* When ``epicKey`` is provided, hits ``GET /jira/epic-runs/runs/{epicKey}/supervisor-config/``
|
|
167
|
+
* (per-epic row with project-default fallback). When omitted, hits
|
|
168
|
+
* ``GET /jira/epic-runs/supervisor-config/defaults/`` (project-default only).
|
|
169
|
+
* Fails closed (throws ``ConductorBridgeApiError``) on any network or server error.
|
|
170
|
+
*/
|
|
171
|
+
export async function fetchEffectiveSupervisorConfig(access, epicKey, fetchImpl = globalThis.fetch) {
|
|
172
|
+
const apiPath = epicKey
|
|
173
|
+
? `${EPIC_RUNS_API_PREFIX}/runs/${encodeURIComponent(epicKey)}/supervisor-config/`
|
|
174
|
+
: `${EPIC_RUNS_API_PREFIX}/supervisor-config/defaults/`;
|
|
175
|
+
const url = buildConductorJiraUrl(access.baseUrl, apiPath, {
|
|
176
|
+
repo_name: access.repoName,
|
|
177
|
+
});
|
|
178
|
+
const parsed = await fetchConductorJsonWithTimeout(url, conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
179
|
+
return parsed;
|
|
148
180
|
}
|
|
149
181
|
/**
|
|
150
182
|
* Poll CI checks for a commit through the existing `/poll-ci-checks` endpoint.
|
|
@@ -152,7 +184,7 @@ export function fetchDoneGateConfigField(access, fetchImpl = fetch) {
|
|
|
152
184
|
* request when it is not a valid SHA. Returns the endpoint response verbatim —
|
|
153
185
|
* the client never invents `pr_number`/`head_sha` fields.
|
|
154
186
|
*/
|
|
155
|
-
export async function pollCiChecksForCommit(access, commitRef, fetchImpl = fetch) {
|
|
187
|
+
export async function pollCiChecksForCommit(access, commitRef, fetchImpl = globalThis.fetch) {
|
|
156
188
|
const sha = normalizeSha(commitRef);
|
|
157
189
|
if (sha === null) {
|
|
158
190
|
throw new ConductorBridgeApiError("invalid-input");
|
|
@@ -164,6 +196,26 @@ export async function pollCiChecksForCommit(access, commitRef, fetchImpl = fetch
|
|
|
164
196
|
return fetchConductorJsonWithTimeout(url, conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
165
197
|
}
|
|
166
198
|
// ---------------------------------------------------------------------------
|
|
199
|
+
// Conductor BAPI-440: PR review status GET client
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
/**
|
|
202
|
+
* Fetch the normalized PR review status from the backend-owned review-status
|
|
203
|
+
* endpoint. Validates `prNumber` with {@link normalizePrNumber} before issuing
|
|
204
|
+
* the request. Returns the endpoint response verbatim — the client never invents
|
|
205
|
+
* review fields. Throws a sanitized {@link ConductorBridgeApiError} on any failure
|
|
206
|
+
* so the producer can fail-closed without leaking body/headers/key.
|
|
207
|
+
*/
|
|
208
|
+
export async function fetchPrReviewStatus(access, prNumber, fetchImpl = globalThis.fetch) {
|
|
209
|
+
const pr = normalizePrNumber(prNumber);
|
|
210
|
+
if (pr === null) {
|
|
211
|
+
throw new ConductorBridgeApiError("invalid-input");
|
|
212
|
+
}
|
|
213
|
+
const url = buildConductorVcsUrl(access.baseUrl, `/vcs/pull-requests/${pr}/reviews/status`);
|
|
214
|
+
const fullUrl = new URL(url);
|
|
215
|
+
fullUrl.searchParams.set("repo_name", access.repoName);
|
|
216
|
+
return fetchConductorJsonWithTimeout(fullUrl.toString(), conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
217
|
+
}
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
167
219
|
// Conductor C6 (BAPI-398): protected VCS merge POST client
|
|
168
220
|
// ---------------------------------------------------------------------------
|
|
169
221
|
/**
|
|
@@ -181,19 +233,20 @@ function conductorPostHeaders(access) {
|
|
|
181
233
|
return { "X-API-Key": access.apiKey, "Content-Type": "application/json" };
|
|
182
234
|
}
|
|
183
235
|
/**
|
|
184
|
-
*
|
|
185
|
-
* {@link fetchConductorJsonWithTimeout}. Throws a sanitized
|
|
236
|
+
* Send a JSON request body with the given HTTP method and an `AbortController`
|
|
237
|
+
* timeout, mirroring {@link fetchConductorJsonWithTimeout}. Throws a sanitized
|
|
186
238
|
* {@link ConductorBridgeApiError} on abort/timeout, network failure, or any
|
|
187
239
|
* non-2xx response — never including the response body, headers, API key, or
|
|
188
|
-
* request body in the error. The timer is always cleared.
|
|
240
|
+
* request body in the error. The timer is always cleared. Shared core for the
|
|
241
|
+
* POST and PATCH wrappers below.
|
|
189
242
|
*/
|
|
190
|
-
|
|
243
|
+
async function fetchConductorJsonWithMethodAndTimeout(method, url, headers, body, timeoutMs, fetchImpl) {
|
|
191
244
|
const controller = new AbortController();
|
|
192
245
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
193
246
|
try {
|
|
194
247
|
let resp;
|
|
195
248
|
try {
|
|
196
|
-
resp = await fetchImpl(url, { method
|
|
249
|
+
resp = await fetchImpl(url, { method, headers, body, signal: controller.signal });
|
|
197
250
|
}
|
|
198
251
|
catch {
|
|
199
252
|
throw new ConductorBridgeApiError(controller.signal.aborted ? "timeout" : "network");
|
|
@@ -218,6 +271,29 @@ export async function fetchConductorJsonPostWithTimeout(url, headers, body, time
|
|
|
218
271
|
clearTimeout(timer);
|
|
219
272
|
}
|
|
220
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* POST JSON with an `AbortController` timeout. Thin wrapper over
|
|
276
|
+
* {@link fetchConductorJsonWithMethodAndTimeout}.
|
|
277
|
+
*/
|
|
278
|
+
export async function fetchConductorJsonPostWithTimeout(url, headers, body, timeoutMs, fetchImpl) {
|
|
279
|
+
return fetchConductorJsonWithMethodAndTimeout("POST", url, headers, body, timeoutMs, fetchImpl);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* PATCH JSON with an `AbortController` timeout. Thin wrapper over
|
|
283
|
+
* {@link fetchConductorJsonWithMethodAndTimeout}. Used by the per-ticket CAS
|
|
284
|
+
* status endpoint, which the backend declares as PATCH.
|
|
285
|
+
*/
|
|
286
|
+
export async function fetchConductorJsonPatchWithTimeout(url, headers, body, timeoutMs, fetchImpl) {
|
|
287
|
+
return fetchConductorJsonWithMethodAndTimeout("PATCH", url, headers, body, timeoutMs, fetchImpl);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* PUT JSON with an `AbortController` timeout. Thin wrapper over
|
|
291
|
+
* {@link fetchConductorJsonWithMethodAndTimeout}. Used by endpoints that the
|
|
292
|
+
* backend declares as PUT (e.g. the Jira status transition endpoint).
|
|
293
|
+
*/
|
|
294
|
+
export async function fetchConductorJsonPutWithTimeout(url, headers, body, timeoutMs, fetchImpl) {
|
|
295
|
+
return fetchConductorJsonWithMethodAndTimeout("PUT", url, headers, body, timeoutMs, fetchImpl);
|
|
296
|
+
}
|
|
221
297
|
/**
|
|
222
298
|
* Call the protected `POST /vcs/pull-requests/{pr_number}/merge` endpoint. The PR
|
|
223
299
|
* number lives ONLY in the path; the API key travels ONLY in headers; the body
|
|
@@ -225,7 +301,7 @@ export async function fetchConductorJsonPostWithTimeout(url, headers, body, time
|
|
|
225
301
|
* before the request is issued. Throws a sanitized {@link ConductorBridgeApiError}
|
|
226
302
|
* on any failure.
|
|
227
303
|
*/
|
|
228
|
-
export async function mergePullRequestForGate(access, request, fetchImpl = fetch) {
|
|
304
|
+
export async function mergePullRequestForGate(access, request, fetchImpl = globalThis.fetch) {
|
|
229
305
|
const pr = normalizePrNumber(request.pr_number);
|
|
230
306
|
const sha = normalizeSha(request.expected_head_sha);
|
|
231
307
|
if (pr === null || sha === null || !request.action_key || !request.repo_name) {
|
|
@@ -244,6 +320,51 @@ export async function mergePullRequestForGate(access, request, fetchImpl = fetch
|
|
|
244
320
|
const parsed = await fetchConductorJsonPostWithTimeout(url, conductorPostHeaders(access), body, CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
245
321
|
return parsed;
|
|
246
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Call the protected `POST /vcs/pull-requests/{pr_number}/remediate` endpoint.
|
|
325
|
+
* The PR number lives ONLY in the path; the API key travels ONLY in headers; the
|
|
326
|
+
* body carries no branch name or provider token. A 409 (idempotency-key replay)
|
|
327
|
+
* is caught and returned as `{ ok: true, conflict: true }`; all other failures
|
|
328
|
+
* throw a sanitized {@link ConductorBridgeApiError}.
|
|
329
|
+
*/
|
|
330
|
+
export async function remediateEpicTicket(access, request, fetchImpl = globalThis.fetch) {
|
|
331
|
+
const pr = normalizePrNumber(request.pr_number);
|
|
332
|
+
if (pr === null ||
|
|
333
|
+
!request.epic_run_id ||
|
|
334
|
+
!request.ticket_key ||
|
|
335
|
+
!request.head_sha ||
|
|
336
|
+
!request.idempotency_key) {
|
|
337
|
+
throw new ConductorBridgeApiError("invalid-input");
|
|
338
|
+
}
|
|
339
|
+
requireNonNegativeSafeInteger(request.expected_row_version);
|
|
340
|
+
if (request.attempt_kind !== "nudge" && request.attempt_kind !== "redispatch") {
|
|
341
|
+
throw new ConductorBridgeApiError("invalid-input");
|
|
342
|
+
}
|
|
343
|
+
const url = buildConductorVcsUrl(access.baseUrl, `/vcs/pull-requests/${pr}/remediate`);
|
|
344
|
+
// The body omits pr_number (it is in the path) and never includes a branch name
|
|
345
|
+
// or provider token; extra fields would be rejected server-side (extra=forbid).
|
|
346
|
+
const body = JSON.stringify({
|
|
347
|
+
repo_name: access.repoName,
|
|
348
|
+
epic_run_id: request.epic_run_id,
|
|
349
|
+
ticket_key: request.ticket_key,
|
|
350
|
+
expected_row_version: request.expected_row_version,
|
|
351
|
+
head_sha: request.head_sha,
|
|
352
|
+
idempotency_key: request.idempotency_key,
|
|
353
|
+
attempt_kind: request.attempt_kind,
|
|
354
|
+
...(request.reason ? { reason: request.reason } : {}),
|
|
355
|
+
});
|
|
356
|
+
try {
|
|
357
|
+
const parsed = await fetchConductorJsonPostWithTimeout(url, conductorPostHeaders(access), body, CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
358
|
+
return { ok: true, conflict: false, response: parsed };
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
if (err instanceof ConductorBridgeApiError && err.kind === "http" && err.status === 409) {
|
|
362
|
+
// Idempotency-key replay: the attempt was already recorded on a prior tick.
|
|
363
|
+
return { ok: true, conflict: true };
|
|
364
|
+
}
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
247
368
|
// ---------------------------------------------------------------------------
|
|
248
369
|
// Local boundary validators (reject before any fetch call)
|
|
249
370
|
// ---------------------------------------------------------------------------
|
|
@@ -268,7 +389,8 @@ function requireNoSlashPathSegment(value) {
|
|
|
268
389
|
}
|
|
269
390
|
}
|
|
270
391
|
const EPIC_TICKET_STATUS_VALUES = [
|
|
271
|
-
"planned", "ready", "dispatched", "running", "blocked", "abandoned", "done",
|
|
392
|
+
"planned", "ready", "dispatched", "running", "blocked", "abandoned", "done", "ready_for_review",
|
|
393
|
+
"reviewing",
|
|
272
394
|
];
|
|
273
395
|
function requireEpicTicketStatusValue(value) {
|
|
274
396
|
if (typeof value !== "string" || !EPIC_TICKET_STATUS_VALUES.includes(value)) {
|
|
@@ -299,15 +421,21 @@ function epicDispatchTransitionApiPath(dispatchKey, nextStatus) {
|
|
|
299
421
|
* Build the canonical dispatch idempotency key in lock-step with the server-side
|
|
300
422
|
* `build_dispatch_key` Python helper.
|
|
301
423
|
*
|
|
302
|
-
* Format: `dispatch:{epicKey}:{ticketKey}:{planVersion}`.
|
|
424
|
+
* Format: `dispatch:{epicKey}:{ticketKey}:{planVersion}`. For a remediation
|
|
425
|
+
* re-dispatch (BAPI-441), pass `attempt > 0` to append an `:r{attempt}` suffix
|
|
426
|
+
* (e.g. `dispatch:BAPI-405:BAPI-441:3:r2`) so the re-dispatch claims a *distinct*
|
|
427
|
+
* key from the original epic dispatch and is not deduped against it. `attempt`
|
|
428
|
+
* defaults to 0, which yields the original (un-suffixed) key for normal dispatch.
|
|
303
429
|
*
|
|
304
430
|
* // TODO(epic-run-store): the epic component maps to the server-side epic_run_id component used by build_dispatch_key; confirm naming if the sibling renames it at integration.
|
|
305
431
|
*/
|
|
306
|
-
export function buildEpicDispatchKey(epicKey, ticketKey, planVersion) {
|
|
432
|
+
export function buildEpicDispatchKey(epicKey, ticketKey, planVersion, attempt = 0) {
|
|
307
433
|
requireNonEmptyString(epicKey);
|
|
308
434
|
requireNonEmptyString(ticketKey);
|
|
309
435
|
requireNonNegativeSafeInteger(planVersion);
|
|
310
|
-
|
|
436
|
+
requireNonNegativeSafeInteger(attempt);
|
|
437
|
+
const base = `dispatch:${epicKey}:${ticketKey}:${planVersion}`;
|
|
438
|
+
return attempt > 0 ? `${base}:r${attempt}` : base;
|
|
311
439
|
}
|
|
312
440
|
// ---------------------------------------------------------------------------
|
|
313
441
|
// Epic supervision lease claim/renew
|
|
@@ -334,7 +462,7 @@ function parseEpicSupervisionLeaseResult(parsed) {
|
|
|
334
462
|
* acquired/renewed from held-by-other and terminal so the control loop can decide
|
|
335
463
|
* whether to act or exit as an observer. Validates inputs before the request.
|
|
336
464
|
*/
|
|
337
|
-
export async function claimEpicSupervisionLease(access, request, fetchImpl = fetch) {
|
|
465
|
+
export async function claimEpicSupervisionLease(access, request, fetchImpl = globalThis.fetch) {
|
|
338
466
|
requireNonEmptyString(request.epicKey);
|
|
339
467
|
requireNonEmptyString(request.leaseOwner);
|
|
340
468
|
requirePositiveSafeInteger(request.ttlSeconds);
|
|
@@ -355,12 +483,31 @@ export async function claimEpicSupervisionLease(access, request, fetchImpl = fet
|
|
|
355
483
|
* Returns the server payload verbatim — no ready-set computation, plan-hash
|
|
356
484
|
* assertion, or derived control-loop fields are added.
|
|
357
485
|
*/
|
|
358
|
-
export async function fetchEpicRunState(access, epicKey, fetchImpl = fetch) {
|
|
486
|
+
export async function fetchEpicRunState(access, epicKey, fetchImpl = globalThis.fetch) {
|
|
359
487
|
requireNonEmptyString(epicKey);
|
|
360
488
|
const url = buildConductorJiraUrl(access.baseUrl, `${epicRunApiPath(epicKey)}/state`, { repo_name: access.repoName });
|
|
361
489
|
const parsed = await fetchConductorJsonWithTimeout(url, conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
362
490
|
return parsed;
|
|
363
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* GET `/jira/epic-runs/runs?repo_name=<repo>&status=active` and return the
|
|
494
|
+
* list of active epic runs. Caps at 20 results — enough for typical deployments
|
|
495
|
+
* and prevents runaway iteration in the producer resolution seam.
|
|
496
|
+
*/
|
|
497
|
+
export async function fetchActiveEpicRuns(access, fetchImpl = globalThis.fetch) {
|
|
498
|
+
const url = buildConductorJiraUrl(access.baseUrl, `${EPIC_RUNS_API_PREFIX}/runs`, {
|
|
499
|
+
repo_name: access.repoName,
|
|
500
|
+
status: "active",
|
|
501
|
+
limit: "20",
|
|
502
|
+
});
|
|
503
|
+
const parsed = await fetchConductorJsonWithTimeout(url, conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
504
|
+
if (parsed && typeof parsed === "object") {
|
|
505
|
+
const runs = parsed.runs;
|
|
506
|
+
if (Array.isArray(runs))
|
|
507
|
+
return runs;
|
|
508
|
+
}
|
|
509
|
+
return [];
|
|
510
|
+
}
|
|
364
511
|
// ---------------------------------------------------------------------------
|
|
365
512
|
// Per-ticket CAS status advancement
|
|
366
513
|
// ---------------------------------------------------------------------------
|
|
@@ -393,13 +540,14 @@ function parseAdvanceEpicTicketStatusResult(parsed) {
|
|
|
393
540
|
throw new ConductorBridgeApiError("server");
|
|
394
541
|
}
|
|
395
542
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
543
|
+
* PATCH the per-ticket CAS status endpoint
|
|
544
|
+
* (`PATCH /runs/{epic_run_id}/tickets/{ticket_key}`). Surfaces structured CAS
|
|
545
|
+
* conflicts as a distinct non-throwing outcome where the backend returns one;
|
|
546
|
+
* the current backend instead raises a 400 VALIDATION on a stale row_version,
|
|
547
|
+
* which surfaces as a thrown `ConductorBridgeApiError("http", 400)`.
|
|
398
548
|
* Transport/auth/server failures still throw a sanitized {@link ConductorBridgeApiError}.
|
|
399
|
-
*
|
|
400
|
-
* // TODO(epic-run-store): ticket requires POST but sibling currently declares PATCH /runs/{epic_run_id}/tickets/{ticket_key}; reconcile HTTP method at integration.
|
|
401
549
|
*/
|
|
402
|
-
export async function advanceEpicTicketStatus(access, request, fetchImpl = fetch) {
|
|
550
|
+
export async function advanceEpicTicketStatus(access, request, fetchImpl = globalThis.fetch) {
|
|
403
551
|
requireNonEmptyString(request.epicKey);
|
|
404
552
|
requireNonEmptyString(request.ticketKey);
|
|
405
553
|
requireNonNegativeSafeInteger(request.expectedRowVersion);
|
|
@@ -408,7 +556,10 @@ export async function advanceEpicTicketStatus(access, request, fetchImpl = fetch
|
|
|
408
556
|
if (request.dispatchRunId !== undefined) {
|
|
409
557
|
requireNonEmptyString(request.dispatchRunId);
|
|
410
558
|
}
|
|
411
|
-
//
|
|
559
|
+
// The backend declares this endpoint as PATCH /runs/{epic_run_id}/tickets/{ticket_key}
|
|
560
|
+
// (api/routes/epic_runs.py:489). The body matches PatchEpicTicketStatusRequest:
|
|
561
|
+
// repo_name + expected_row_version are required; status/plan_version/dispatch_run_id
|
|
562
|
+
// are optional and accepted (the model does not forbid extras).
|
|
412
563
|
const CAS_ENDPOINT_PATH = `${epicRunApiPath(request.epicKey)}/tickets/${encodeURIComponent(request.ticketKey)}`;
|
|
413
564
|
const url = buildConductorJiraUrl(access.baseUrl, CAS_ENDPOINT_PATH);
|
|
414
565
|
const body = JSON.stringify({
|
|
@@ -418,7 +569,7 @@ export async function advanceEpicTicketStatus(access, request, fetchImpl = fetch
|
|
|
418
569
|
expected_row_version: request.expectedRowVersion,
|
|
419
570
|
...(request.dispatchRunId ? { dispatch_run_id: request.dispatchRunId } : {}),
|
|
420
571
|
});
|
|
421
|
-
const parsed = await
|
|
572
|
+
const parsed = await fetchConductorJsonPatchWithTimeout(url, conductorPostHeaders(access), body, CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
422
573
|
return parseAdvanceEpicTicketStatusResult(parsed);
|
|
423
574
|
}
|
|
424
575
|
/**
|
|
@@ -427,7 +578,7 @@ export async function advanceEpicTicketStatus(access, request, fetchImpl = fetch
|
|
|
427
578
|
* ticks and re-plans is safe. Throws a sanitized {@link ConductorBridgeApiError}
|
|
428
579
|
* on any transport/auth/server error.
|
|
429
580
|
*/
|
|
430
|
-
export async function createEpicTicketStatus(access, request, fetchImpl = fetch) {
|
|
581
|
+
export async function createEpicTicketStatus(access, request, fetchImpl = globalThis.fetch) {
|
|
431
582
|
requireNonEmptyString(request.epicKey);
|
|
432
583
|
requireNonEmptyString(request.ticketKey);
|
|
433
584
|
requireEpicTicketStatusValue(request.status);
|
|
@@ -486,13 +637,13 @@ function parseEpicDispatchResult(parsed) {
|
|
|
486
637
|
* outcome so a crashed-then-retried tick never double-dispatches. The dispatch key
|
|
487
638
|
* is composed exclusively via {@link buildEpicDispatchKey}.
|
|
488
639
|
*/
|
|
489
|
-
export async function recordEpicDispatch(access, request, fetchImpl = fetch) {
|
|
640
|
+
export async function recordEpicDispatch(access, request, fetchImpl = globalThis.fetch) {
|
|
490
641
|
requireNonEmptyString(request.epicKey);
|
|
491
642
|
requireNonEmptyString(request.ticketKey);
|
|
492
643
|
requireNonEmptyString(request.leaseOwner);
|
|
493
644
|
requireNonNegativeSafeInteger(request.planVersion);
|
|
494
645
|
requirePositiveSafeInteger(request.ttlSeconds);
|
|
495
|
-
const dispatchKey = buildEpicDispatchKey(request.epicKey, request.ticketKey, request.planVersion);
|
|
646
|
+
const dispatchKey = buildEpicDispatchKey(request.epicKey, request.ticketKey, request.planVersion, request.attempt ?? 0) + (request.reviewRole ? ":review" : "");
|
|
496
647
|
const url = buildConductorJiraUrl(access.baseUrl, `${EPIC_RUNS_API_PREFIX}/dispatch/claim`);
|
|
497
648
|
const body = JSON.stringify({
|
|
498
649
|
repo_name: access.repoName,
|
|
@@ -515,7 +666,7 @@ export async function recordEpicDispatch(access, request, fetchImpl = fetch) {
|
|
|
515
666
|
* Dispatch keys containing `/` are rejected before the request because transition
|
|
516
667
|
* endpoints embed the key as a URL path segment.
|
|
517
668
|
*/
|
|
518
|
-
export async function transitionEpicDispatch(access, request, fetchImpl = fetch) {
|
|
669
|
+
export async function transitionEpicDispatch(access, request, fetchImpl = globalThis.fetch) {
|
|
519
670
|
requireNonEmptyString(request.dispatchKey);
|
|
520
671
|
requireNoSlashPathSegment(request.dispatchKey);
|
|
521
672
|
requireEpicDispatchTransitionStatus(request.nextStatus);
|
|
@@ -537,7 +688,7 @@ export async function transitionEpicDispatch(access, request, fetchImpl = fetch)
|
|
|
537
688
|
* in the `X-API-Key` header, never in the URL. Throws a sanitized
|
|
538
689
|
* {@link ConductorBridgeApiError} on any transport/auth/server error.
|
|
539
690
|
*/
|
|
540
|
-
export async function storeEpicPlan(access, request, fetchImpl = fetch) {
|
|
691
|
+
export async function storeEpicPlan(access, request, fetchImpl = globalThis.fetch) {
|
|
541
692
|
requireNonEmptyString(request.epicKey);
|
|
542
693
|
requirePositiveSafeInteger(request.planVersion);
|
|
543
694
|
requireNonEmptyString(request.planHash);
|
|
@@ -562,7 +713,7 @@ export async function storeEpicPlan(access, request, fetchImpl = fetch) {
|
|
|
562
713
|
* monotonic constraint violation). Other errors throw a sanitized
|
|
563
714
|
* {@link ConductorBridgeApiError}.
|
|
564
715
|
*/
|
|
565
|
-
export async function approveEpicPlan(access, request, fetchImpl = fetch) {
|
|
716
|
+
export async function approveEpicPlan(access, request, fetchImpl = globalThis.fetch) {
|
|
566
717
|
requireNonEmptyString(request.epicKey);
|
|
567
718
|
requirePositiveSafeInteger(request.planVersion);
|
|
568
719
|
const url = buildConductorJiraUrl(access.baseUrl, `${epicRunApiPath(request.epicKey)}/approve-plan`);
|
|
@@ -589,7 +740,7 @@ export async function approveEpicPlan(access, request, fetchImpl = fetch) {
|
|
|
589
740
|
* Returns the response as a {@link GetEpicPlanResponse}. The API key travels
|
|
590
741
|
* ONLY in the `X-API-Key` header, never in the URL.
|
|
591
742
|
*/
|
|
592
|
-
export async function getEpicPlan(access, epicKey, planVersion, fetchImpl = fetch) {
|
|
743
|
+
export async function getEpicPlan(access, epicKey, planVersion, fetchImpl = globalThis.fetch) {
|
|
593
744
|
requireNonEmptyString(epicKey);
|
|
594
745
|
const url = buildConductorJiraUrl(access.baseUrl, `${epicRunApiPath(epicKey)}/plan`, { repo_name: access.repoName, plan_version: String(planVersion) });
|
|
595
746
|
const parsed = await fetchConductorJsonWithTimeout(url, conductorGetHeaders(access), CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
@@ -604,7 +755,7 @@ export async function getEpicPlan(access, epicKey, planVersion, fetchImpl = fetc
|
|
|
604
755
|
* holds the lock) and `"idle"` (no lock is held). Throws a sanitized
|
|
605
756
|
* {@link ConductorBridgeApiError} on any transport/auth/server failure.
|
|
606
757
|
*/
|
|
607
|
-
export async function fetchParseStatus(access, fetchImpl = fetch) {
|
|
758
|
+
export async function fetchParseStatus(access, fetchImpl = globalThis.fetch) {
|
|
608
759
|
const url = buildConductorJiraUrl(access.baseUrl, "/parse-status", {
|
|
609
760
|
repo_name: access.repoName,
|
|
610
761
|
});
|
|
@@ -618,8 +769,85 @@ export async function fetchParseStatus(access, fetchImpl = fetch) {
|
|
|
618
769
|
* safe. Returns the response envelope verbatim. Throws a sanitized
|
|
619
770
|
* {@link ConductorBridgeApiError} on any transport/auth/server failure.
|
|
620
771
|
*/
|
|
621
|
-
export async function triggerRepositoryParse(access, fetchImpl = fetch) {
|
|
772
|
+
export async function triggerRepositoryParse(access, fetchImpl = globalThis.fetch) {
|
|
622
773
|
const url = buildConductorJiraUrl(access.baseUrl, "/parse-repository");
|
|
623
774
|
const body = JSON.stringify({ repo_name: access.repoName });
|
|
624
775
|
return fetchConductorJsonPostWithTimeout(url, conductorPostHeaders(access), body, CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
625
776
|
}
|
|
777
|
+
/**
|
|
778
|
+
* Call the protected `DELETE /vcs/pull-requests/{pr_number}/branch` endpoint.
|
|
779
|
+
* Treats 404-equivalent responses as idempotent success (already deleted).
|
|
780
|
+
* Throws a sanitized {@link ConductorBridgeApiError} on auth/server/network
|
|
781
|
+
* failures.
|
|
782
|
+
*/
|
|
783
|
+
export async function deletePullRequestBranch(access, prNumber, expectedHeadSha, fetchImpl = globalThis.fetch) {
|
|
784
|
+
const pr = normalizePrNumber(prNumber);
|
|
785
|
+
if (pr === null) {
|
|
786
|
+
throw new ConductorBridgeApiError("invalid-input");
|
|
787
|
+
}
|
|
788
|
+
const url = buildConductorVcsUrl(access.baseUrl, `/vcs/pull-requests/${pr}/branch?repo_name=${encodeURIComponent(access.repoName)}&expected_head_sha=${encodeURIComponent(expectedHeadSha)}`);
|
|
789
|
+
const controller = new AbortController();
|
|
790
|
+
const timer = setTimeout(() => controller.abort(), CONDUCTOR_FETCH_TIMEOUT_MS);
|
|
791
|
+
try {
|
|
792
|
+
let resp;
|
|
793
|
+
try {
|
|
794
|
+
resp = await fetchImpl(url, {
|
|
795
|
+
method: "DELETE",
|
|
796
|
+
headers: { "X-API-Key": access.apiKey },
|
|
797
|
+
body: "",
|
|
798
|
+
signal: controller.signal,
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
catch {
|
|
802
|
+
throw new ConductorBridgeApiError(controller.signal.aborted ? "timeout" : "network");
|
|
803
|
+
}
|
|
804
|
+
// 404 from the provider → branch already gone → idempotent success
|
|
805
|
+
if (resp.status === 404) {
|
|
806
|
+
return { deleted: false, branch: null, reason: "not_found" };
|
|
807
|
+
}
|
|
808
|
+
if (!resp.ok) {
|
|
809
|
+
if (resp.status === 401 || resp.status === 403) {
|
|
810
|
+
throw new ConductorBridgeApiError("unauthorized", resp.status);
|
|
811
|
+
}
|
|
812
|
+
if (resp.status >= 500) {
|
|
813
|
+
throw new ConductorBridgeApiError("server", resp.status);
|
|
814
|
+
}
|
|
815
|
+
throw new ConductorBridgeApiError("http", resp.status);
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
return (await resp.json());
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
throw new ConductorBridgeApiError("network");
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
finally {
|
|
825
|
+
clearTimeout(timer);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Call `PUT /jira/tickets/{ticketNumber}/jira-status` to transition the ticket's
|
|
830
|
+
* Jira status on truly-done. Uses `target_status: "auto"` for server-side resolution.
|
|
831
|
+
* Treats HTTP 400 (no matching transition / already in target) as a benign skip.
|
|
832
|
+
* Throws a sanitized {@link ConductorBridgeApiError} on auth/server/network failures.
|
|
833
|
+
*/
|
|
834
|
+
export async function transitionJiraStatus(access, ticketNumber, targetStatus = "auto", fetchImpl = globalThis.fetch) {
|
|
835
|
+
if (!ticketNumber) {
|
|
836
|
+
throw new ConductorBridgeApiError("invalid-input");
|
|
837
|
+
}
|
|
838
|
+
// repo_name travels in the JSON body only (TransitionJiraStatusRequest) — the
|
|
839
|
+
// backend ignores any query param, so don't duplicate it in the URL.
|
|
840
|
+
const url = buildConductorJiraUrl(access.baseUrl, `/tickets/${encodeURIComponent(ticketNumber)}/jira-status`);
|
|
841
|
+
const body = JSON.stringify({ repo_name: access.repoName, target_status: targetStatus });
|
|
842
|
+
try {
|
|
843
|
+
await fetchConductorJsonPutWithTimeout(url, conductorPostHeaders(access), body, CONDUCTOR_FETCH_TIMEOUT_MS, fetchImpl);
|
|
844
|
+
return { status: "transitioned" };
|
|
845
|
+
}
|
|
846
|
+
catch (err) {
|
|
847
|
+
// HTTP 400 = no matching transition / already in target → benign skip.
|
|
848
|
+
if (err instanceof ConductorBridgeApiError && err.kind === "http" && err.status === 400) {
|
|
849
|
+
return { status: "skipped" };
|
|
850
|
+
}
|
|
851
|
+
throw err;
|
|
852
|
+
}
|
|
853
|
+
}
|
package/build/conductor/cli.js
CHANGED
|
@@ -336,13 +336,13 @@ export function parseEmitEventArgs(argv, deps = {}) {
|
|
|
336
336
|
return { input, json: bools.has("--json"), help: false };
|
|
337
337
|
}
|
|
338
338
|
/** Run the `emit-event` command. Prints the inserted event summary. */
|
|
339
|
-
export function runEmitEventCommand(argv, deps = {}) {
|
|
339
|
+
export async function runEmitEventCommand(argv, deps = {}) {
|
|
340
340
|
const parsed = parseEmitEventArgs(argv, deps);
|
|
341
341
|
if (parsed.help) {
|
|
342
342
|
console.log(getConductorUsage());
|
|
343
343
|
return 0;
|
|
344
344
|
}
|
|
345
|
-
const result = emitConductorEvent(parsed.input);
|
|
345
|
+
const result = await emitConductorEvent(parsed.input);
|
|
346
346
|
if (parsed.json) {
|
|
347
347
|
console.log(JSON.stringify(result));
|
|
348
348
|
}
|
|
@@ -442,13 +442,13 @@ export function parseSendMessageArgs(argv, deps = {}) {
|
|
|
442
442
|
* Run `send-message`. Prints compact JSON when `--json` is set; otherwise a
|
|
443
443
|
* sanitized human summary (message id / status / type only — NEVER the payload).
|
|
444
444
|
*/
|
|
445
|
-
export function runSendMessageCommand(argv, deps = {}) {
|
|
445
|
+
export async function runSendMessageCommand(argv, deps = {}) {
|
|
446
446
|
const parsed = parseSendMessageArgs(argv, deps);
|
|
447
447
|
if (parsed.help) {
|
|
448
448
|
console.log(getConductorUsage());
|
|
449
449
|
return 0;
|
|
450
450
|
}
|
|
451
|
-
const result = sendWorkerMessage(parsed.input);
|
|
451
|
+
const result = await sendWorkerMessage(parsed.input);
|
|
452
452
|
if (parsed.json) {
|
|
453
453
|
console.log(JSON.stringify(result));
|
|
454
454
|
}
|
|
@@ -495,13 +495,13 @@ export function parseCheckMessagesArgs(argv) {
|
|
|
495
495
|
* Run `check-messages`. Prints compact JSON when `--json` is set; otherwise a
|
|
496
496
|
* sanitized human summary (counts + per-message id/type only — NEVER payloads).
|
|
497
497
|
*/
|
|
498
|
-
export function runCheckMessagesCommand(argv) {
|
|
498
|
+
export async function runCheckMessagesCommand(argv) {
|
|
499
499
|
const parsed = parseCheckMessagesArgs(argv);
|
|
500
500
|
if (parsed.help) {
|
|
501
501
|
console.log(getConductorUsage());
|
|
502
502
|
return 0;
|
|
503
503
|
}
|
|
504
|
-
const result = checkWorkerMessages(parsed.input);
|
|
504
|
+
const result = await checkWorkerMessages(parsed.input);
|
|
505
505
|
if (parsed.json) {
|
|
506
506
|
console.log(JSON.stringify(result));
|
|
507
507
|
return 0;
|
|
@@ -528,7 +528,7 @@ export async function runDoctorCommand(argv) {
|
|
|
528
528
|
// scheduleDeps omitted: buildConductorDoctorReport lazily loads schedule-run.
|
|
529
529
|
const report = await buildConductorDoctorReport({});
|
|
530
530
|
if (bools.has("--json")) {
|
|
531
|
-
console.log(JSON.stringify({ ...report.ledger, git_hooks: report.git_hooks, epic_tick: report.epic_tick }));
|
|
531
|
+
console.log(JSON.stringify({ ...report.ledger, git_hooks: report.git_hooks, epic_tick: report.epic_tick, mcp_profile: report.mcp_profile }));
|
|
532
532
|
return 0;
|
|
533
533
|
}
|
|
534
534
|
console.log(formatConductorDoctorReport(report));
|
|
@@ -590,7 +590,7 @@ export function parseGitHookArgs(argv) {
|
|
|
590
590
|
* warning) so a conductor producer failure never blocks the git commit/ref update
|
|
591
591
|
* the hook is attached to.
|
|
592
592
|
*/
|
|
593
|
-
export function runGitHookCommand(argv) {
|
|
593
|
+
export async function runGitHookCommand(argv) {
|
|
594
594
|
let parsed;
|
|
595
595
|
try {
|
|
596
596
|
parsed = parseGitHookArgs(argv);
|
|
@@ -602,7 +602,7 @@ export function runGitHookCommand(argv) {
|
|
|
602
602
|
}
|
|
603
603
|
try {
|
|
604
604
|
if (parsed.subcommand === "post-commit") {
|
|
605
|
-
runPostCommitHookProducer();
|
|
605
|
+
await runPostCommitHookProducer();
|
|
606
606
|
return 0;
|
|
607
607
|
}
|
|
608
608
|
// reference-transaction: read the captured updates from the stdin file.
|
|
@@ -625,7 +625,7 @@ export function runGitHookCommand(argv) {
|
|
|
625
625
|
}
|
|
626
626
|
}
|
|
627
627
|
}
|
|
628
|
-
runReferenceTransactionHookProducer({ phase: parsed.phase ?? "", stdin });
|
|
628
|
+
await runReferenceTransactionHookProducer({ phase: parsed.phase ?? "", stdin });
|
|
629
629
|
return 0;
|
|
630
630
|
}
|
|
631
631
|
catch {
|
|
@@ -662,6 +662,24 @@ export function parseEpicTickArgs(argv) {
|
|
|
662
662
|
const leaseTtlSeconds = parsePositiveIntFlag(values, "--lease-ttl-seconds");
|
|
663
663
|
return { epicKey: epicKeyRaw.trim(), scheduledAt, leaseTtlSeconds, help: false };
|
|
664
664
|
}
|
|
665
|
+
/**
|
|
666
|
+
* Default the PreToolUse hook ON for epic-dispatched workers so the supervisor
|
|
667
|
+
* gets worker-liveness (`tool.intent`) signals without the operator having to
|
|
668
|
+
* export `BAPI_CONDUCTOR_ENABLE_PRE_TOOL_USE=1` (BAPI-441/A1 finding).
|
|
669
|
+
*
|
|
670
|
+
* Both the child-env copy and the hook registration key off this SAME
|
|
671
|
+
* parent-process env flag, read at dispatch time, so setting it once at the
|
|
672
|
+
* epic-tick boundary covers the whole tick. Only defaults when the flag is
|
|
673
|
+
* UNSET — an explicit "0"/"" still disables it, because `isConductorFlagEnabled`
|
|
674
|
+
* treats those as false. Mutates the supplied env in place (defaults to
|
|
675
|
+
* `process.env`) and returns it.
|
|
676
|
+
*/
|
|
677
|
+
export function applyEpicTickPreToolUseDefault(env = process.env) {
|
|
678
|
+
if (env.BAPI_CONDUCTOR_ENABLE_PRE_TOOL_USE === undefined) {
|
|
679
|
+
env.BAPI_CONDUCTOR_ENABLE_PRE_TOOL_USE = "1";
|
|
680
|
+
}
|
|
681
|
+
return env;
|
|
682
|
+
}
|
|
665
683
|
/**
|
|
666
684
|
* Run the `epic-tick` command. Lazily imports the epic runtime so a plain
|
|
667
685
|
* `conductor doctor` / `emit-event` invocation never eagerly resolves the
|
|
@@ -673,6 +691,9 @@ export async function runEpicTickCommand(argv) {
|
|
|
673
691
|
console.log(getConductorUsage());
|
|
674
692
|
return 0;
|
|
675
693
|
}
|
|
694
|
+
// Enable supervisor worker-liveness by default for the whole tick (see the
|
|
695
|
+
// helper's doc comment); set before runEpicTick dispatches any worker.
|
|
696
|
+
applyEpicTickPreToolUseDefault();
|
|
676
697
|
// `runEpicTick` is imported LAZILY so the store/supervisor graph is not
|
|
677
698
|
// evaluated for other conductor CLI commands that never need it.
|
|
678
699
|
const { runEpicTick, buildProductionEpicRuntimeDeps } = await import("./epic-runtime.js");
|
|
@@ -975,13 +996,13 @@ export async function runSuperviseCommand(argv) {
|
|
|
975
996
|
return result.exit_code;
|
|
976
997
|
}
|
|
977
998
|
/** Run the explicit `purge` command. Prints deleted row counts. */
|
|
978
|
-
export function runPurgeCommand(argv) {
|
|
999
|
+
export async function runPurgeCommand(argv) {
|
|
979
1000
|
const { bools } = tokenizeFlags(argv, new Set(), DIAGNOSTIC_BOOL_FLAGS);
|
|
980
1001
|
if (bools.has("--help")) {
|
|
981
1002
|
console.log(getConductorUsage());
|
|
982
1003
|
return 0;
|
|
983
1004
|
}
|
|
984
|
-
const result = purgeConductorLedger();
|
|
1005
|
+
const result = await purgeConductorLedger();
|
|
985
1006
|
if (bools.has("--json")) {
|
|
986
1007
|
console.log(JSON.stringify(result));
|
|
987
1008
|
return 0;
|
|
@@ -1014,7 +1035,7 @@ export async function runConductorCli(argv) {
|
|
|
1014
1035
|
try {
|
|
1015
1036
|
switch (parsed.command) {
|
|
1016
1037
|
case "emit-event":
|
|
1017
|
-
return runEmitEventCommand(parsed.argv);
|
|
1038
|
+
return await runEmitEventCommand(parsed.argv);
|
|
1018
1039
|
case "supervise":
|
|
1019
1040
|
return await runSuperviseCommand(parsed.argv);
|
|
1020
1041
|
case "epic-tick":
|
|
@@ -1024,17 +1045,17 @@ export async function runConductorCli(argv) {
|
|
|
1024
1045
|
case "epic-status":
|
|
1025
1046
|
return await runEpicStatusCommand(parsed.argv);
|
|
1026
1047
|
case "send-message":
|
|
1027
|
-
return runSendMessageCommand(parsed.argv);
|
|
1048
|
+
return await runSendMessageCommand(parsed.argv);
|
|
1028
1049
|
case "check-messages":
|
|
1029
|
-
return runCheckMessagesCommand(parsed.argv);
|
|
1050
|
+
return await runCheckMessagesCommand(parsed.argv);
|
|
1030
1051
|
case "doctor":
|
|
1031
1052
|
return await runDoctorCommand(parsed.argv);
|
|
1032
1053
|
case "purge":
|
|
1033
|
-
return runPurgeCommand(parsed.argv);
|
|
1054
|
+
return await runPurgeCommand(parsed.argv);
|
|
1034
1055
|
case "install-git-hooks":
|
|
1035
1056
|
return runInstallGitHooksCommand(parsed.argv);
|
|
1036
1057
|
case "git-hook":
|
|
1037
|
-
return runGitHookCommand(parsed.argv);
|
|
1058
|
+
return await runGitHookCommand(parsed.argv);
|
|
1038
1059
|
default:
|
|
1039
1060
|
console.error('Error: Unknown command. Run "conductor --help" for usage.');
|
|
1040
1061
|
return 1;
|