substrate-ai 0.19.27 → 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.
@@ -0,0 +1,4 @@
1
+ import { AdapterRegistry } from "./dist-R0W4ofKv.js";
2
+ import "./adapter-registry-DXLMTmfD.js";
3
+
4
+ export { AdapterRegistry };
package/dist/cli/index.js CHANGED
@@ -1,15 +1,15 @@
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-CAg5B3m_.js";
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
- 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-D0SnP1ns.js";
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-CsNW777k.js";
8
- import "../errors-Crc4y34k.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-BBYhrXw9.js";
8
+ import "../errors-BJRMJyGb.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
11
11
  import "../version-manager-impl-BmOWu8ml.js";
12
- import { registerUpgradeCommand } from "../upgrade-DldTl30b.js";
12
+ import { registerUpgradeCommand } from "../upgrade-DwEbjHWg.js";
13
13
  import { Command } from "commander";
14
14
  import { fileURLToPath } from "url";
15
15
  import { dirname, join, resolve } from "path";
@@ -21,15 +21,16 @@ 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";
27
- import { appendFileSync, chmodSync, cpSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync, rmSync as rmSync$1, statSync, unlinkSync, writeFileSync as writeFileSync$1 } from "fs";
28
+ import { appendFileSync, chmodSync, cpSync, existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync, rmSync as rmSync$1, statSync, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
28
29
  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
@@ -1801,7 +1802,7 @@ function clearBmadCommandFiles(commandsDir) {
1801
1802
  try {
1802
1803
  const entries = readdirSync$1(commandsDir);
1803
1804
  for (const entry of entries) if (entry.startsWith("bmad-") && entry.endsWith(".md")) try {
1804
- unlinkSync(join(commandsDir, entry));
1805
+ unlinkSync$1(join(commandsDir, entry));
1805
1806
  } catch {}
1806
1807
  } catch {}
1807
1808
  }
@@ -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
- try {
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-gww8zfnD.js");
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,
@@ -4115,7 +4191,7 @@ function defaultSupervisorDeps() {
4115
4191
  if (cached === null) {
4116
4192
  const { AdapterRegistry: AR } = await import(
4117
4193
  /* @vite-ignore */
4118
- "../adapter-registry-Cw2DpQOy.js"
4194
+ "../adapter-registry-S_u-oxuM.js"
4119
4195
  );
4120
4196
  cached = new AR();
4121
4197
  await cached.discoverAndRegister();
@@ -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 (deps.getRunConfig !== void 0 && health.run_id !== null) try {
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) : {
@@ -4557,11 +4696,11 @@ async function runSupervisorAction(options, deps = {}) {
4557
4696
  try {
4558
4697
  const { createExperimenter } = await import(
4559
4698
  /* @vite-ignore */
4560
- "../experimenter-Ajtt8D60.js"
4699
+ "../experimenter-Sq8sNz9y.js"
4561
4700
  );
4562
4701
  const { getLatestRun: getLatest } = await import(
4563
4702
  /* @vite-ignore */
4564
- "../decisions-BYjuW3st.js"
4703
+ "../decisions-CnvFtZ7P.js"
4565
4704
  );
4566
4705
  const expAdapter = createDatabaseAdapter({
4567
4706
  backend: "auto",
@@ -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-CH6kOKMs.js"
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
  });
@@ -5101,7 +5241,7 @@ async function runMetricsAction(options) {
5101
5241
  const routingConfigPath = join(dbDir, "routing.yml");
5102
5242
  let routingConfig = null;
5103
5243
  if (existsSync$1(routingConfigPath)) try {
5104
- const { loadModelRoutingConfig } = await import("../routing-DZuOjyTy.js");
5244
+ const { loadModelRoutingConfig } = await import("../routing-CRjtRmIl.js");
5105
5245
  routingConfig = loadModelRoutingConfig(routingConfigPath);
5106
5246
  } catch {}
5107
5247
  if (routingConfig === null) routingConfig = {
@@ -7461,7 +7601,7 @@ async function runCancelAction(options) {
7461
7601
  }, "json", true) + "\n");
7462
7602
  else process.stdout.write("No running pipeline found.\n");
7463
7603
  if (existsSync$1(pidFilePath)) try {
7464
- unlinkSync(pidFilePath);
7604
+ unlinkSync$1(pidFilePath);
7465
7605
  if (outputFormat === "human") process.stdout.write("Cleaned up stale PID file.\n");
7466
7606
  } catch {}
7467
7607
  return 0;
@@ -7492,7 +7632,7 @@ async function runCancelAction(options) {
7492
7632
  killed.push(zombiePid);
7493
7633
  } catch {}
7494
7634
  if (existsSync$1(pidFilePath)) try {
7495
- unlinkSync(pidFilePath);
7635
+ unlinkSync$1(pidFilePath);
7496
7636
  } catch {}
7497
7637
  try {
7498
7638
  const adapter = createDatabaseAdapter({
@@ -8355,8 +8495,8 @@ async function createProgram() {
8355
8495
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
8356
8496
  function checkForUpdatesInBackground(currentVersion) {
8357
8497
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
8358
- import("../upgrade-d21gzvUw.js").then(async () => {
8359
- const { createVersionManager } = await import("../version-manager-impl-Vlz013ih.js");
8498
+ import("../upgrade-Q9YROhpA.js").then(async () => {
8499
+ const { createVersionManager } = await import("../version-manager-impl-DReRXdW7.js");
8360
8500
  const vm = createVersionManager();
8361
8501
  const result = await vm.checkForUpdates();
8362
8502
  if (result.updateAvailable) {
@@ -1,4 +1,4 @@
1
- import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-D0SnP1ns.js";
1
+ import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-R0W4ofKv.js";
2
2
  import "./decisions-C0pz9Clx.js";
3
3
 
4
4
  export { getLatestRun };
@@ -38,7 +38,7 @@ const DEFAULT_TIMEOUTS = {
38
38
  "create-story": 6e5,
39
39
  "dev-story": 18e5,
40
40
  "code-review": 9e5,
41
- "minor-fixes": 12e5,
41
+ "minor-fixes": 3e5,
42
42
  "major-rework": 9e5,
43
43
  "readiness-check": 6e5,
44
44
  "elicitation": 9e5,
@@ -213,6 +213,56 @@ function containsAnchorKey(content) {
213
213
  return YAML_ANCHOR_KEYS.some((key) => content.includes(key));
214
214
  }
215
215
  /**
216
+ * Merge duplicate top-level YAML keys by concatenating their array values.
217
+ *
218
+ * LLMs sometimes emit the same mapping key twice, splitting a list across two
219
+ * blocks (e.g., `non_functional_requirements:` appears twice, each with
220
+ * different items). This violates YAML 1.2 but is recoverable: we detect
221
+ * duplicate top-level keys and merge their children into a single key.
222
+ *
223
+ * Only operates on top-level keys (no indentation). Nested duplicates are not
224
+ * handled — they're rare in practice and harder to fix safely.
225
+ */
226
+ function mergeDuplicateYamlKeys(yamlText) {
227
+ const lines = yamlText.split("\n");
228
+ const sections = [];
229
+ let current = null;
230
+ for (const line of lines) {
231
+ const keyMatch = line.match(/^([a-zA-Z_][a-zA-Z0-9_]*):\s*(.*)$/);
232
+ if (keyMatch) {
233
+ const key = keyMatch[1];
234
+ const rest = keyMatch[2].trim();
235
+ current = {
236
+ key,
237
+ lines: [line]
238
+ };
239
+ sections.push(current);
240
+ if (rest.length > 0 && !rest.startsWith("#")) current = null;
241
+ } else if (current) current.lines.push(line);
242
+ else sections.push({
243
+ key: "",
244
+ lines: [line]
245
+ });
246
+ }
247
+ const seen = new Map();
248
+ const merged = [];
249
+ for (const section of sections) {
250
+ if (section.key === "") {
251
+ merged.push(section);
252
+ continue;
253
+ }
254
+ const existing = seen.get(section.key);
255
+ if (existing !== void 0) {
256
+ const target = merged[existing];
257
+ target.lines.push(...section.lines.slice(1));
258
+ } else {
259
+ seen.set(section.key, merged.length);
260
+ merged.push(section);
261
+ }
262
+ }
263
+ return merged.flatMap((s) => s.lines).join("\n");
264
+ }
265
+ /**
216
266
  * Parse a YAML string and optionally validate it against a Zod schema.
217
267
  *
218
268
  * @param yamlText - Raw YAML string to parse
@@ -225,7 +275,15 @@ function parseYamlResult(yamlText, schema) {
225
275
  raw = yaml.load(sanitizeYamlEscapes(yamlText));
226
276
  } catch (err) {
227
277
  const message = err instanceof Error ? err.message : String(err);
228
- return {
278
+ if (message.includes("duplicated mapping key")) try {
279
+ raw = yaml.load(sanitizeYamlEscapes(mergeDuplicateYamlKeys(yamlText)));
280
+ } catch {
281
+ return {
282
+ parsed: null,
283
+ error: `YAML parse error: ${message}`
284
+ };
285
+ }
286
+ else return {
229
287
  parsed: null,
230
288
  error: `YAML parse error: ${message}`
231
289
  };
@@ -4098,7 +4156,10 @@ var DoltQueryError = class extends Error {
4098
4156
  */
4099
4157
  function runExecFile(cmd, args, opts) {
4100
4158
  return new Promise((resolve$2, reject) => {
4101
- execFile(cmd, args, opts, (err, stdout, stderr) => {
4159
+ execFile(cmd, args, {
4160
+ ...opts,
4161
+ maxBuffer: 10 * 1024 * 1024
4162
+ }, (err, stdout, stderr) => {
4102
4163
  if (err) reject(err);
4103
4164
  else resolve$2({
4104
4165
  stdout,
@@ -5383,7 +5444,9 @@ const BudgetConfigSchema = z.object({
5383
5444
  }).strict();
5384
5445
  const TelemetryConfigSchema = z.object({
5385
5446
  enabled: z.boolean().default(false),
5386
- port: z.number().int().min(1).max(65535).default(4318)
5447
+ port: z.number().int().min(1).max(65535).default(4318),
5448
+ meshUrl: z.string().url().optional(),
5449
+ projectId: z.string().optional()
5387
5450
  }).strict();
5388
5451
  /** Current supported config format version */
5389
5452
  const CURRENT_CONFIG_FORMAT_VERSION = "1";
@@ -5400,7 +5463,8 @@ const SubstrateConfigSchema = z.object({
5400
5463
  providers: ProvidersSchema,
5401
5464
  cost_tracker: CostTrackerConfigSchema.optional(),
5402
5465
  budget: BudgetConfigSchema.optional(),
5403
- telemetry: TelemetryConfigSchema.optional()
5466
+ telemetry: TelemetryConfigSchema.optional(),
5467
+ trivialOutputThreshold: z.number().int().nonnegative().optional()
5404
5468
  }).passthrough();
5405
5469
  const PartialProviderConfigSchema = ProviderConfigSchema.partial();
5406
5470
  const PartialGlobalSettingsSchema = GlobalSettingsSchema.partial();
@@ -5415,7 +5479,8 @@ const PartialSubstrateConfigSchema = z.object({
5415
5479
  }).partial().optional(),
5416
5480
  cost_tracker: CostTrackerConfigSchema.partial().optional(),
5417
5481
  budget: BudgetConfigSchema.partial().optional(),
5418
- telemetry: TelemetryConfigSchema.partial().optional()
5482
+ telemetry: TelemetryConfigSchema.partial().optional(),
5483
+ trivialOutputThreshold: z.number().int().nonnegative().optional()
5419
5484
  }).passthrough();
5420
5485
 
5421
5486
  //#endregion
@@ -10453,4 +10518,4 @@ async function callLLM(params) {
10453
10518
 
10454
10519
  //#endregion
10455
10520
  export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, IngestionServer, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createDoltClient, createExperimenter, createPipelineRun, createRequirement, createVersionManager, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
10456
- //# sourceMappingURL=dist-D0SnP1ns.js.map
10521
+ //# sourceMappingURL=dist-R0W4ofKv.js.map
@@ -1,4 +1,4 @@
1
- import { AdtError } from "./dist-D0SnP1ns.js";
1
+ import { AdtError } from "./dist-R0W4ofKv.js";
2
2
 
3
3
  //#region src/core/errors.ts
4
4
  /** Error thrown when task configuration is invalid */
@@ -71,4 +71,4 @@ var TaskGraphIncompatibleFormatError = class extends AdtError {
71
71
 
72
72
  //#endregion
73
73
  export { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError };
74
- //# sourceMappingURL=errors-Crc4y34k.js.map
74
+ //# sourceMappingURL=errors-BJRMJyGb.js.map
@@ -1,3 +1,3 @@
1
- import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-D0SnP1ns.js";
1
+ import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-R0W4ofKv.js";
2
2
 
3
3
  export { createExperimenter };