substrate-ai 0.19.28 → 0.19.29
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/dist/cli/index.js +164 -24
- package/dist/{health-M0iCuP26.js → health-BS20i6mY.js} +1216 -22
- package/dist/{health-CVfyC7j0.js → health-Cy_9GgQ_.js} +1 -1
- package/dist/{run-DabSV2xH.js → run-BBYhrXw9.js} +2494 -2653
- package/dist/{run-CjwCYY8Q.js → run-D0-aXchh.js} +2 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot } from "../health-
|
|
2
|
+
import { FileStateStore, RunManifest, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveRunManifest } from "../health-BS20i6mY.js";
|
|
3
3
|
import { createLogger } from "../logger-KeHncl-f.js";
|
|
4
4
|
import { createEventBus } from "../helpers-CElYrONe.js";
|
|
5
5
|
import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-R0W4ofKv.js";
|
|
6
6
|
import "../adapter-registry-DXLMTmfD.js";
|
|
7
|
-
import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
7
|
+
import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BBYhrXw9.js";
|
|
8
8
|
import "../errors-BJRMJyGb.js";
|
|
9
9
|
import "../routing-CcBOCuC9.js";
|
|
10
10
|
import "../decisions-C0pz9Clx.js";
|
|
@@ -21,6 +21,7 @@ import * as path$3 from "node:path";
|
|
|
21
21
|
import * as path$2 from "node:path";
|
|
22
22
|
import * as path$1 from "node:path";
|
|
23
23
|
import { join as join$1 } from "node:path";
|
|
24
|
+
import { randomUUID } from "node:crypto";
|
|
24
25
|
import { z } from "zod";
|
|
25
26
|
import * as fs from "node:fs/promises";
|
|
26
27
|
import { access as access$1, readFile as readFile$1, readdir as readdir$1 } from "node:fs/promises";
|
|
@@ -29,7 +30,7 @@ import { homedir } from "os";
|
|
|
29
30
|
import { createRequire } from "node:module";
|
|
30
31
|
import { fileURLToPath as fileURLToPath$1 } from "node:url";
|
|
31
32
|
import { createInterface } from "node:readline";
|
|
32
|
-
import { randomUUID } from "crypto";
|
|
33
|
+
import { randomUUID as randomUUID$1 } from "crypto";
|
|
33
34
|
import { createInterface as createInterface$1 } from "readline";
|
|
34
35
|
|
|
35
36
|
//#region packages/core/dist/git/git-utils.js
|
|
@@ -2761,6 +2762,23 @@ async function runResumeAction(options) {
|
|
|
2761
2762
|
if (Array.isArray(config.explicitStories) && config.explicitStories.length > 0) scopedStories = config.explicitStories;
|
|
2762
2763
|
} catch {}
|
|
2763
2764
|
const dbDir = dbPath.replace("/substrate.db", "");
|
|
2765
|
+
if (options.stories === void 0 || options.stories.length === 0) {
|
|
2766
|
+
const { manifest: resolvedManifest } = await resolveRunManifest(dbRoot, runId);
|
|
2767
|
+
if (resolvedManifest !== null) try {
|
|
2768
|
+
const manifestData = await resolvedManifest.read();
|
|
2769
|
+
const manifestStories = manifestData.cli_flags["stories"] ?? manifestData.story_scope;
|
|
2770
|
+
if (Array.isArray(manifestStories) && manifestStories.length > 0) {
|
|
2771
|
+
scopedStories = manifestStories;
|
|
2772
|
+
logger$13.debug({
|
|
2773
|
+
runId,
|
|
2774
|
+
stories: scopedStories
|
|
2775
|
+
}, "resume scope loaded from manifest");
|
|
2776
|
+
}
|
|
2777
|
+
} catch {
|
|
2778
|
+
logger$13.debug({ runId }, "manifest read failed in resume — using legacy config_json scope");
|
|
2779
|
+
}
|
|
2780
|
+
else logger$13.debug({ runId }, "Run manifest not found for scope preservation — using legacy config_json scope");
|
|
2781
|
+
}
|
|
2764
2782
|
return runFullPipelineFromPhase({
|
|
2765
2783
|
packName,
|
|
2766
2784
|
packPath,
|
|
@@ -3134,6 +3152,55 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
3134
3152
|
//#endregion
|
|
3135
3153
|
//#region src/cli/commands/status.ts
|
|
3136
3154
|
const logger$12 = createLogger("status-cmd");
|
|
3155
|
+
/**
|
|
3156
|
+
* Map a manifest per-story status string to the appropriate WorkGraphCounts bucket.
|
|
3157
|
+
* Unknown strings are treated as `inProgress` (safe default).
|
|
3158
|
+
*/
|
|
3159
|
+
function manifestStatusToWorkGraphBucket(status) {
|
|
3160
|
+
switch (status) {
|
|
3161
|
+
case "complete": return "complete";
|
|
3162
|
+
case "escalated": return "escalated";
|
|
3163
|
+
case "failed":
|
|
3164
|
+
case "verification-failed": return "failed";
|
|
3165
|
+
case "dispatched":
|
|
3166
|
+
case "in-review":
|
|
3167
|
+
case "recovered": return "inProgress";
|
|
3168
|
+
case "gated":
|
|
3169
|
+
case "pending": return "ready";
|
|
3170
|
+
default: return "inProgress";
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
/**
|
|
3174
|
+
* Build a WorkGraphSummary from manifest `per_story_state`.
|
|
3175
|
+
* readyStories and blockedStories are left empty — manifest does not carry
|
|
3176
|
+
* dependency-graph detail (only status counts).
|
|
3177
|
+
*/
|
|
3178
|
+
function buildWorkGraphFromManifest(perStoryState) {
|
|
3179
|
+
const counts = {
|
|
3180
|
+
ready: 0,
|
|
3181
|
+
blocked: 0,
|
|
3182
|
+
inProgress: 0,
|
|
3183
|
+
complete: 0,
|
|
3184
|
+
escalated: 0,
|
|
3185
|
+
failed: 0
|
|
3186
|
+
};
|
|
3187
|
+
for (const entry of Object.values(perStoryState)) {
|
|
3188
|
+
const bucket = manifestStatusToWorkGraphBucket(entry.status);
|
|
3189
|
+
counts[bucket]++;
|
|
3190
|
+
}
|
|
3191
|
+
return {
|
|
3192
|
+
summary: {
|
|
3193
|
+
ready: counts.ready,
|
|
3194
|
+
blocked: counts.blocked,
|
|
3195
|
+
inProgress: counts.inProgress,
|
|
3196
|
+
complete: counts.complete,
|
|
3197
|
+
escalated: counts.escalated,
|
|
3198
|
+
failed: counts.failed
|
|
3199
|
+
},
|
|
3200
|
+
readyStories: [],
|
|
3201
|
+
blockedStories: []
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3137
3204
|
async function runStatusAction(options) {
|
|
3138
3205
|
const { outputFormat, runId, projectRoot, stateStore, history } = options;
|
|
3139
3206
|
if (history === true) {
|
|
@@ -3176,8 +3243,29 @@ async function runStatusAction(options) {
|
|
|
3176
3243
|
});
|
|
3177
3244
|
try {
|
|
3178
3245
|
await initSchema(adapter);
|
|
3246
|
+
let run;
|
|
3247
|
+
if (runId !== void 0 && runId !== "") run = await getPipelineRunById(adapter, runId);
|
|
3248
|
+
else {
|
|
3249
|
+
let currentRunId;
|
|
3250
|
+
try {
|
|
3251
|
+
const currentRunIdPath = join(dbRoot, ".substrate", "current-run-id");
|
|
3252
|
+
const content = readFileSync$1(currentRunIdPath, "utf-8").trim();
|
|
3253
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3254
|
+
if (UUID_RE.test(content)) currentRunId = content;
|
|
3255
|
+
} catch {}
|
|
3256
|
+
if (currentRunId !== void 0) run = await getPipelineRunById(adapter, currentRunId);
|
|
3257
|
+
if (run === void 0) run = await getLatestRun(adapter);
|
|
3258
|
+
}
|
|
3179
3259
|
let workGraph;
|
|
3180
|
-
|
|
3260
|
+
const { manifest: resolvedManifest } = await resolveRunManifest(dbRoot, run?.id);
|
|
3261
|
+
if (resolvedManifest !== null) try {
|
|
3262
|
+
const manifestData = await resolvedManifest.read();
|
|
3263
|
+
workGraph = buildWorkGraphFromManifest(manifestData.per_story_state);
|
|
3264
|
+
logger$12.debug({ runId: run?.id }, "status: workGraph built from manifest per_story_state");
|
|
3265
|
+
} catch {
|
|
3266
|
+
logger$12.debug({ runId: run?.id }, "status: manifest read failed — falling back to wg_stories");
|
|
3267
|
+
}
|
|
3268
|
+
if (workGraph === void 0) try {
|
|
3181
3269
|
const wgRepo = new WorkGraphRepository(adapter);
|
|
3182
3270
|
const allStories = await adapter.query(`SELECT story_key, title, status FROM wg_stories`);
|
|
3183
3271
|
if (allStories.length > 0) {
|
|
@@ -3214,21 +3302,8 @@ async function runStatusAction(options) {
|
|
|
3214
3302
|
} catch (err) {
|
|
3215
3303
|
logger$12.debug({ err }, "Work graph query failed, continuing without work graph data");
|
|
3216
3304
|
}
|
|
3217
|
-
let run;
|
|
3218
|
-
if (runId !== void 0 && runId !== "") run = await getPipelineRunById(adapter, runId);
|
|
3219
|
-
else {
|
|
3220
|
-
let currentRunId;
|
|
3221
|
-
try {
|
|
3222
|
-
const currentRunIdPath = join(dbRoot, ".substrate", "current-run-id");
|
|
3223
|
-
const content = readFileSync$1(currentRunIdPath, "utf-8").trim();
|
|
3224
|
-
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3225
|
-
if (UUID_RE.test(content)) currentRunId = content;
|
|
3226
|
-
} catch {}
|
|
3227
|
-
if (currentRunId !== void 0) run = await getPipelineRunById(adapter, currentRunId);
|
|
3228
|
-
if (run === void 0) run = await getLatestRun(adapter);
|
|
3229
|
-
}
|
|
3230
3305
|
if (run === void 0) {
|
|
3231
|
-
const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-
|
|
3306
|
+
const { inspectProcessTree: inspectProcessTree$1 } = await import("../health-Cy_9GgQ_.js");
|
|
3232
3307
|
const substrateDirPath = join(projectRoot, ".substrate");
|
|
3233
3308
|
const processInfo = inspectProcessTree$1({
|
|
3234
3309
|
projectRoot,
|
|
@@ -3822,7 +3897,7 @@ async function runAmendAction(options) {
|
|
|
3822
3897
|
}
|
|
3823
3898
|
parentRunId = latestCompleted.id;
|
|
3824
3899
|
}
|
|
3825
|
-
const amendmentRunId = randomUUID();
|
|
3900
|
+
const amendmentRunId = randomUUID$1();
|
|
3826
3901
|
let methodology = packName;
|
|
3827
3902
|
try {
|
|
3828
3903
|
const packLoader$1 = createPackLoader();
|
|
@@ -4044,6 +4119,7 @@ function registerAmendCommand(program, _version = "0.0.0", projectRoot = process
|
|
|
4044
4119
|
|
|
4045
4120
|
//#endregion
|
|
4046
4121
|
//#region src/cli/commands/supervisor.ts
|
|
4122
|
+
const supervisorLogger = createLogger("supervisor-cmd");
|
|
4047
4123
|
function defaultSupervisorDeps() {
|
|
4048
4124
|
return {
|
|
4049
4125
|
getHealth: getAutoHealthData,
|
|
@@ -4379,7 +4455,13 @@ async function handleStallRecovery(health, state, config, deps, io) {
|
|
|
4379
4455
|
log(`Supervisor: Restarting pipeline (attempt ${newRestartCount}/${maxRestarts})`);
|
|
4380
4456
|
try {
|
|
4381
4457
|
let scopedStories;
|
|
4382
|
-
if (
|
|
4458
|
+
if (health.run_id !== null) try {
|
|
4459
|
+
const manifest = RunManifest.open(health.run_id, projectRoot);
|
|
4460
|
+
const data = await manifest.read();
|
|
4461
|
+
const manifestStories = data?.cli_flags?.stories;
|
|
4462
|
+
if (Array.isArray(manifestStories) && manifestStories.length > 0) scopedStories = manifestStories;
|
|
4463
|
+
} catch {}
|
|
4464
|
+
if (scopedStories === void 0 && deps.getRunConfig !== void 0 && health.run_id !== null) try {
|
|
4383
4465
|
const runConfig = await deps.getRunConfig(health.run_id, projectRoot);
|
|
4384
4466
|
if (runConfig?.explicitStories !== void 0 && runConfig.explicitStories.length > 0) scopedStories = runConfig.explicitStories;
|
|
4385
4467
|
} catch {}
|
|
@@ -4442,7 +4524,7 @@ async function handleStallRecovery(health, state, config, deps, io) {
|
|
|
4442
4524
|
* 2 — max restarts exceeded (safety valve triggered)
|
|
4443
4525
|
*/
|
|
4444
4526
|
async function runSupervisorAction(options, deps = {}) {
|
|
4445
|
-
const { pollInterval, stallThreshold, maxRestarts, outputFormat, projectRoot, runId, pack, experiment, maxExperiments } = options;
|
|
4527
|
+
const { pollInterval, stallThreshold, maxRestarts, outputFormat, projectRoot, runId, pack, experiment, maxExperiments, force } = options;
|
|
4446
4528
|
const resolvedDeps = {
|
|
4447
4529
|
...defaultSupervisorDeps(),
|
|
4448
4530
|
...deps
|
|
@@ -4455,6 +4537,62 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
4455
4537
|
};
|
|
4456
4538
|
let maxRestartsExhausted = false;
|
|
4457
4539
|
const startTime = Date.now();
|
|
4540
|
+
const sessionId = randomUUID();
|
|
4541
|
+
let supervisorLock = null;
|
|
4542
|
+
/** Track whether process exit handlers have been registered for this supervisor. */
|
|
4543
|
+
let exitHandlersRegistered = false;
|
|
4544
|
+
/**
|
|
4545
|
+
* Register process.once exit handlers to release the lock on exit.
|
|
4546
|
+
* Called exactly once, after the first successful lock acquisition.
|
|
4547
|
+
* Using process.once (not process.on) per Story 52-2 spec.
|
|
4548
|
+
*/
|
|
4549
|
+
function registerExitHandlers(lock) {
|
|
4550
|
+
if (exitHandlersRegistered) return;
|
|
4551
|
+
exitHandlersRegistered = true;
|
|
4552
|
+
process.once("exit", () => {
|
|
4553
|
+
lock.release().catch((e) => {
|
|
4554
|
+
supervisorLogger.debug({ error: e }, "lock release on exit failed");
|
|
4555
|
+
});
|
|
4556
|
+
});
|
|
4557
|
+
process.once("SIGTERM", () => {
|
|
4558
|
+
lock.release().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
4559
|
+
});
|
|
4560
|
+
process.once("SIGINT", () => {
|
|
4561
|
+
lock.release().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
/**
|
|
4565
|
+
* Acquire the supervisor lock for a given run ID.
|
|
4566
|
+
* Non-fatal: logs and continues on failure so the supervisor can still
|
|
4567
|
+
* function in degraded mode without blocking the pipeline.
|
|
4568
|
+
*/
|
|
4569
|
+
async function acquireLockForRun(targetRunId) {
|
|
4570
|
+
if (supervisorLock !== null) return;
|
|
4571
|
+
try {
|
|
4572
|
+
const runsDir = join(projectRoot, ".substrate", "runs");
|
|
4573
|
+
const manifest = RunManifest.open(targetRunId, runsDir);
|
|
4574
|
+
const lock = new SupervisorLock(targetRunId, manifest, supervisorLogger);
|
|
4575
|
+
await lock.acquire(process.pid, sessionId, { force: force ?? false });
|
|
4576
|
+
supervisorLock = lock;
|
|
4577
|
+
supervisorLogger.debug({ runId: targetRunId }, "Supervisor lock acquired");
|
|
4578
|
+
registerExitHandlers(lock);
|
|
4579
|
+
} catch (lockErr) {
|
|
4580
|
+
const msg = lockErr instanceof Error ? lockErr.message : String(lockErr);
|
|
4581
|
+
supervisorLogger.warn({
|
|
4582
|
+
runId: targetRunId,
|
|
4583
|
+
error: msg
|
|
4584
|
+
}, "Supervisor lock acquisition failed");
|
|
4585
|
+
if (outputFormat === "json") process.stdout.write(JSON.stringify({
|
|
4586
|
+
type: "supervisor:lock-failed",
|
|
4587
|
+
run_id: targetRunId,
|
|
4588
|
+
reason: msg,
|
|
4589
|
+
ts: new Date().toISOString()
|
|
4590
|
+
}) + "\n");
|
|
4591
|
+
else process.stderr.write(`Warning: Supervisor lock acquisition failed: ${msg}\n`);
|
|
4592
|
+
if (msg.includes("is already supervised by PID") && !force) throw lockErr;
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4595
|
+
if (runId !== void 0) await acquireLockForRun(runId);
|
|
4458
4596
|
function emitEvent(event) {
|
|
4459
4597
|
if (outputFormat === "json") {
|
|
4460
4598
|
const stamped = {
|
|
@@ -4479,6 +4617,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
4479
4617
|
runId: health.run_id
|
|
4480
4618
|
};
|
|
4481
4619
|
log(`Supervisor: auto-bound to active run ${health.run_id}`);
|
|
4620
|
+
await acquireLockForRun(health.run_id);
|
|
4482
4621
|
}
|
|
4483
4622
|
if (outputFormat === "json") {
|
|
4484
4623
|
const tokenSnapshot = health.run_id !== null ? await getTokenSnapshot(health.run_id, projectRoot) : {
|
|
@@ -4571,7 +4710,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
4571
4710
|
await initSchema(expAdapter);
|
|
4572
4711
|
const { runRunAction: runPipeline } = await import(
|
|
4573
4712
|
/* @vite-ignore */
|
|
4574
|
-
"../run-
|
|
4713
|
+
"../run-D0-aXchh.js"
|
|
4575
4714
|
);
|
|
4576
4715
|
const runStoryFn = async (opts) => {
|
|
4577
4716
|
const exitCode = await runPipeline({
|
|
@@ -4780,7 +4919,7 @@ async function runMultiProjectSupervisor(options, deps = {}) {
|
|
|
4780
4919
|
}
|
|
4781
4920
|
}
|
|
4782
4921
|
function registerSupervisorCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
4783
|
-
program.command("supervisor").description("Monitor a pipeline run and automatically recover from stalls").option("--poll-interval <seconds>", "Health poll interval in seconds", (v) => parseInt(v, 10), 60).option("--stall-threshold <seconds>", "Staleness in seconds before killing a stalled pipeline", (v) => parseInt(v, 10), 600).option("--max-restarts <n>", "Maximum automatic restarts before aborting", (v) => parseInt(v, 10), 3).option("--run-id <id>", "Pipeline run ID to monitor (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", projectRoot).option("--projects <paths>", "Comma-separated project root directories to monitor (multi-project mode)").option("--output-format <format>", "Output format: human (default) or json", "human").option("--experiment", "After post-run analysis, enter experiment mode: create branches, apply modifications, run single-story experiments, and report verdicts (Story 17-4)", false).option("--max-experiments <n>", "Maximum number of experiments to run per analysis cycle (default: 2, Story 17-4 AC6)", (v) => parseInt(v, 10), 2).action(async (opts) => {
|
|
4922
|
+
program.command("supervisor").description("Monitor a pipeline run and automatically recover from stalls").option("--poll-interval <seconds>", "Health poll interval in seconds", (v) => parseInt(v, 10), 60).option("--stall-threshold <seconds>", "Staleness in seconds before killing a stalled pipeline", (v) => parseInt(v, 10), 600).option("--max-restarts <n>", "Maximum automatic restarts before aborting", (v) => parseInt(v, 10), 3).option("--run-id <id>", "Pipeline run ID to monitor (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", projectRoot).option("--projects <paths>", "Comma-separated project root directories to monitor (multi-project mode)").option("--output-format <format>", "Output format: human (default) or json", "human").option("--experiment", "After post-run analysis, enter experiment mode: create branches, apply modifications, run single-story experiments, and report verdicts (Story 17-4)", false).option("--max-experiments <n>", "Maximum number of experiments to run per analysis cycle (default: 2, Story 17-4 AC6)", (v) => parseInt(v, 10), 2).option("--force", "Forcefully evict an existing supervisor process (SIGTERM + 500ms) before attaching (Story 52-2)", false).action(async (opts) => {
|
|
4784
4923
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
4785
4924
|
if (opts.stallThreshold < 120) console.warn(`Warning: --stall-threshold ${opts.stallThreshold}s is below 120s. Agent steps typically take 45-90s. This may cause false stall detections and wasted restarts.`);
|
|
4786
4925
|
if (opts.projects) {
|
|
@@ -4811,7 +4950,8 @@ function registerSupervisorCommand(program, _version = "0.0.0", projectRoot = pr
|
|
|
4811
4950
|
outputFormat,
|
|
4812
4951
|
projectRoot: opts.projectRoot,
|
|
4813
4952
|
experiment: opts.experiment,
|
|
4814
|
-
maxExperiments: opts.maxExperiments
|
|
4953
|
+
maxExperiments: opts.maxExperiments,
|
|
4954
|
+
force: opts.force
|
|
4815
4955
|
});
|
|
4816
4956
|
process.exitCode = exitCode;
|
|
4817
4957
|
});
|