substrate-ai 0.2.4 → 0.2.5
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 +9 -0
- package/dist/cli/index.js +120 -130
- package/dist/index.d.ts +40 -1
- package/dist/{run-D3ZscMlL.js → run-CDYE1PT3.js} +77 -23
- package/dist/{run-Bwyy5-RY.js → run-XkrV99HV.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,6 +229,15 @@ These commands are invoked by AI agents (Claude Code, Codex, Gemini CLI) during
|
|
|
229
229
|
| `substrate monitor recommendations` | Display routing recommendations from performance data |
|
|
230
230
|
| `substrate cost` | View cost and token usage summary |
|
|
231
231
|
|
|
232
|
+
### Export & Sharing
|
|
233
|
+
|
|
234
|
+
| Command | Description |
|
|
235
|
+
|---------|-------------|
|
|
236
|
+
| `substrate export` | Export planning artifacts (product brief, PRD, architecture, epics) as markdown |
|
|
237
|
+
| `substrate export --run-id <id>` | Export artifacts from a specific pipeline run |
|
|
238
|
+
| `substrate export --output-dir <dir>` | Write to a custom directory (default: `_bmad-output/planning-artifacts/`) |
|
|
239
|
+
| `substrate export --output-format json` | Emit JSON result to stdout for agent consumption |
|
|
240
|
+
|
|
232
241
|
### Worktree Management
|
|
233
242
|
|
|
234
243
|
| Command | Description |
|
package/dist/cli/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { createLogger, deepMask } from "../logger-C6n1g8uP.js";
|
|
3
3
|
import { AdapterRegistry, createEventBus } from "../event-bus-J-bw-pkp.js";
|
|
4
4
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "../version-manager-impl-BpVx2DkY.js";
|
|
5
|
-
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
5
|
+
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CDYE1PT3.js";
|
|
6
6
|
import { ConfigError, ConfigIncompatibleFormatError } from "../errors-BPqtzQ4U.js";
|
|
7
7
|
import { addTokenUsage, createDecision, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-DNYByk0U.js";
|
|
8
|
-
import { compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../metrics-BSg8VIHd.js";
|
|
8
|
+
import { aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../metrics-BSg8VIHd.js";
|
|
9
9
|
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-BtI5eNoN.js";
|
|
10
10
|
import { registerUpgradeCommand } from "../upgrade-rV26kdh3.js";
|
|
11
11
|
import { createRequire } from "module";
|
|
@@ -2404,20 +2404,46 @@ function registerAmendCommand(program, _version = "0.0.0", projectRoot = process
|
|
|
2404
2404
|
//#endregion
|
|
2405
2405
|
//#region src/cli/commands/health.ts
|
|
2406
2406
|
const logger$11 = createLogger("health-cmd");
|
|
2407
|
-
|
|
2407
|
+
/** Default stall threshold in seconds — also used by supervisor default */
|
|
2408
|
+
const DEFAULT_STALL_THRESHOLD_SECONDS = 600;
|
|
2409
|
+
/**
|
|
2410
|
+
* Determine whether a ps output line represents the substrate pipeline orchestrator.
|
|
2411
|
+
* Handles invocation via:
|
|
2412
|
+
* - `substrate run` (globally installed)
|
|
2413
|
+
* - `substrate-ai run`
|
|
2414
|
+
* - `node dist/cli/index.js run` (npm run substrate:dev)
|
|
2415
|
+
* - `npx substrate run`
|
|
2416
|
+
* - any node process whose command contains `run` with `--events` or `--stories`
|
|
2417
|
+
*/
|
|
2418
|
+
function isOrchestratorProcessLine(line) {
|
|
2419
|
+
if (line.includes("grep")) return false;
|
|
2420
|
+
if (line.includes("substrate run")) return true;
|
|
2421
|
+
if (line.includes("substrate-ai run")) return true;
|
|
2422
|
+
if (line.includes("index.js run")) return true;
|
|
2423
|
+
if (line.includes("node") && /\srun(\s|$)/.test(line) && (line.includes("--events") || line.includes("--stories"))) return true;
|
|
2424
|
+
return false;
|
|
2425
|
+
}
|
|
2426
|
+
function inspectProcessTree(execFileSyncOverride) {
|
|
2408
2427
|
const result = {
|
|
2409
2428
|
orchestrator_pid: null,
|
|
2410
2429
|
child_pids: [],
|
|
2411
2430
|
zombies: []
|
|
2412
2431
|
};
|
|
2413
2432
|
try {
|
|
2414
|
-
|
|
2415
|
-
|
|
2433
|
+
let psOutput;
|
|
2434
|
+
if (execFileSyncOverride !== void 0) psOutput = execFileSyncOverride("ps", ["-eo", "pid,ppid,stat,command"], {
|
|
2416
2435
|
encoding: "utf-8",
|
|
2417
2436
|
timeout: 5e3
|
|
2418
2437
|
});
|
|
2438
|
+
else {
|
|
2439
|
+
const { execFileSync } = __require("node:child_process");
|
|
2440
|
+
psOutput = execFileSync("ps", ["-eo", "pid,ppid,stat,command"], {
|
|
2441
|
+
encoding: "utf-8",
|
|
2442
|
+
timeout: 5e3
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2419
2445
|
const lines = psOutput.split("\n");
|
|
2420
|
-
for (const line of lines) if (
|
|
2446
|
+
for (const line of lines) if (isOrchestratorProcessLine(line)) {
|
|
2421
2447
|
const match = line.trim().match(/^(\d+)/);
|
|
2422
2448
|
if (match) {
|
|
2423
2449
|
result.orchestrator_pid = parseInt(match[1], 10);
|
|
@@ -2478,7 +2504,7 @@ async function getAutoHealthData(options) {
|
|
|
2478
2504
|
if (runId !== void 0) run = getPipelineRunById(db, runId);
|
|
2479
2505
|
else run = getLatestRun(db);
|
|
2480
2506
|
if (run === void 0) return NO_PIPELINE;
|
|
2481
|
-
const updatedAt =
|
|
2507
|
+
const updatedAt = parseDbTimestampAsUtc(run.updated_at);
|
|
2482
2508
|
const stalenessSeconds = Math.round((Date.now() - updatedAt.getTime()) / 1e3);
|
|
2483
2509
|
let storyDetails = {};
|
|
2484
2510
|
let active = 0;
|
|
@@ -2500,8 +2526,9 @@ async function getAutoHealthData(options) {
|
|
|
2500
2526
|
} catch {}
|
|
2501
2527
|
const processInfo = inspectProcessTree();
|
|
2502
2528
|
let verdict = "NO_PIPELINE_RUNNING";
|
|
2503
|
-
if (run.status === "running") if (processInfo.
|
|
2504
|
-
else if (
|
|
2529
|
+
if (run.status === "running") if (processInfo.orchestrator_pid === null && active === 0 && completed > 0) verdict = "NO_PIPELINE_RUNNING";
|
|
2530
|
+
else if (processInfo.zombies.length > 0) verdict = "STALLED";
|
|
2531
|
+
else if (stalenessSeconds > DEFAULT_STALL_THRESHOLD_SECONDS) verdict = "STALLED";
|
|
2505
2532
|
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length === 0 && active > 0) verdict = "STALLED";
|
|
2506
2533
|
else verdict = "HEALTHY";
|
|
2507
2534
|
else if (run.status === "completed" || run.status === "failed" || run.status === "stopped") verdict = "NO_PIPELINE_RUNNING";
|
|
@@ -2527,124 +2554,31 @@ async function getAutoHealthData(options) {
|
|
|
2527
2554
|
}
|
|
2528
2555
|
}
|
|
2529
2556
|
async function runHealthAction(options) {
|
|
2530
|
-
const { outputFormat
|
|
2531
|
-
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
2532
|
-
const dbPath = join(dbRoot, ".substrate", "substrate.db");
|
|
2533
|
-
if (!existsSync(dbPath)) {
|
|
2534
|
-
const output = {
|
|
2535
|
-
verdict: "NO_PIPELINE_RUNNING",
|
|
2536
|
-
run_id: null,
|
|
2537
|
-
status: null,
|
|
2538
|
-
current_phase: null,
|
|
2539
|
-
staleness_seconds: 0,
|
|
2540
|
-
last_activity: "",
|
|
2541
|
-
process: {
|
|
2542
|
-
orchestrator_pid: null,
|
|
2543
|
-
child_pids: [],
|
|
2544
|
-
zombies: []
|
|
2545
|
-
},
|
|
2546
|
-
stories: {
|
|
2547
|
-
active: 0,
|
|
2548
|
-
completed: 0,
|
|
2549
|
-
escalated: 0,
|
|
2550
|
-
details: {}
|
|
2551
|
-
}
|
|
2552
|
-
};
|
|
2553
|
-
if (outputFormat === "json") process.stdout.write(formatOutput(output, "json", true) + "\n");
|
|
2554
|
-
else process.stdout.write("NO_PIPELINE_RUNNING — no substrate database found\n");
|
|
2555
|
-
return 0;
|
|
2556
|
-
}
|
|
2557
|
-
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
2557
|
+
const { outputFormat } = options;
|
|
2558
2558
|
try {
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
let run;
|
|
2562
|
-
if (runId !== void 0) run = getPipelineRunById(db, runId);
|
|
2563
|
-
else run = getLatestRun(db);
|
|
2564
|
-
if (run === void 0) {
|
|
2565
|
-
const output$1 = {
|
|
2566
|
-
verdict: "NO_PIPELINE_RUNNING",
|
|
2567
|
-
run_id: null,
|
|
2568
|
-
status: null,
|
|
2569
|
-
current_phase: null,
|
|
2570
|
-
staleness_seconds: 0,
|
|
2571
|
-
last_activity: "",
|
|
2572
|
-
process: {
|
|
2573
|
-
orchestrator_pid: null,
|
|
2574
|
-
child_pids: [],
|
|
2575
|
-
zombies: []
|
|
2576
|
-
},
|
|
2577
|
-
stories: {
|
|
2578
|
-
active: 0,
|
|
2579
|
-
completed: 0,
|
|
2580
|
-
escalated: 0,
|
|
2581
|
-
details: {}
|
|
2582
|
-
}
|
|
2583
|
-
};
|
|
2584
|
-
if (outputFormat === "json") process.stdout.write(formatOutput(output$1, "json", true) + "\n");
|
|
2585
|
-
else process.stdout.write("NO_PIPELINE_RUNNING — no pipeline runs found\n");
|
|
2586
|
-
return 0;
|
|
2587
|
-
}
|
|
2588
|
-
const updatedAt = new Date(run.updated_at);
|
|
2589
|
-
const stalenessSeconds = Math.round((Date.now() - updatedAt.getTime()) / 1e3);
|
|
2590
|
-
let storyDetails = {};
|
|
2591
|
-
let active = 0;
|
|
2592
|
-
let completed = 0;
|
|
2593
|
-
let escalated = 0;
|
|
2594
|
-
try {
|
|
2595
|
-
if (run.token_usage_json) {
|
|
2596
|
-
const state = JSON.parse(run.token_usage_json);
|
|
2597
|
-
if (state.stories) for (const [key, s] of Object.entries(state.stories)) {
|
|
2598
|
-
storyDetails[key] = {
|
|
2599
|
-
phase: s.phase,
|
|
2600
|
-
review_cycles: s.reviewCycles
|
|
2601
|
-
};
|
|
2602
|
-
if (s.phase === "COMPLETE") completed++;
|
|
2603
|
-
else if (s.phase === "ESCALATED") escalated++;
|
|
2604
|
-
else if (s.phase !== "PENDING") active++;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
} catch {}
|
|
2608
|
-
const processInfo = inspectProcessTree();
|
|
2609
|
-
let verdict = "NO_PIPELINE_RUNNING";
|
|
2610
|
-
if (run.status === "running") if (processInfo.zombies.length > 0) verdict = "STALLED";
|
|
2611
|
-
else if (stalenessSeconds > 600) verdict = "STALLED";
|
|
2612
|
-
else if (processInfo.orchestrator_pid !== null && processInfo.child_pids.length === 0 && active > 0) verdict = "STALLED";
|
|
2613
|
-
else verdict = "HEALTHY";
|
|
2614
|
-
else if (run.status === "completed" || run.status === "failed" || run.status === "stopped") verdict = "NO_PIPELINE_RUNNING";
|
|
2615
|
-
const output = {
|
|
2616
|
-
verdict,
|
|
2617
|
-
run_id: run.id,
|
|
2618
|
-
status: run.status,
|
|
2619
|
-
current_phase: run.current_phase,
|
|
2620
|
-
staleness_seconds: stalenessSeconds,
|
|
2621
|
-
last_activity: run.updated_at,
|
|
2622
|
-
process: processInfo,
|
|
2623
|
-
stories: {
|
|
2624
|
-
active,
|
|
2625
|
-
completed,
|
|
2626
|
-
escalated,
|
|
2627
|
-
details: storyDetails
|
|
2628
|
-
}
|
|
2629
|
-
};
|
|
2630
|
-
if (outputFormat === "json") process.stdout.write(formatOutput(output, "json", true) + "\n");
|
|
2559
|
+
const health = await getAutoHealthData(options);
|
|
2560
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(health, "json", true) + "\n");
|
|
2631
2561
|
else {
|
|
2632
|
-
const verdictLabel = verdict === "HEALTHY" ? "HEALTHY" : verdict === "STALLED" ? "STALLED" : "NO PIPELINE RUNNING";
|
|
2562
|
+
const verdictLabel = health.verdict === "HEALTHY" ? "HEALTHY" : health.verdict === "STALLED" ? "STALLED" : "NO PIPELINE RUNNING";
|
|
2633
2563
|
process.stdout.write(`\nPipeline Health: ${verdictLabel}\n`);
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
process.stdout.write("\n
|
|
2646
|
-
|
|
2647
|
-
|
|
2564
|
+
if (health.run_id !== null) {
|
|
2565
|
+
process.stdout.write(` Run: ${health.run_id}\n`);
|
|
2566
|
+
process.stdout.write(` Status: ${health.status}\n`);
|
|
2567
|
+
process.stdout.write(` Phase: ${health.current_phase ?? "N/A"}\n`);
|
|
2568
|
+
process.stdout.write(` Last Active: ${health.last_activity} (${health.staleness_seconds}s ago)\n`);
|
|
2569
|
+
const processInfo = health.process;
|
|
2570
|
+
if (processInfo.orchestrator_pid !== null) {
|
|
2571
|
+
process.stdout.write(` Orchestrator: PID ${processInfo.orchestrator_pid}\n`);
|
|
2572
|
+
process.stdout.write(` Children: ${processInfo.child_pids.length} active`);
|
|
2573
|
+
if (processInfo.zombies.length > 0) process.stdout.write(` (${processInfo.zombies.length} ZOMBIE)`);
|
|
2574
|
+
process.stdout.write("\n");
|
|
2575
|
+
} else process.stdout.write(" Orchestrator: not running\n");
|
|
2576
|
+
const storyDetails = health.stories.details;
|
|
2577
|
+
if (Object.keys(storyDetails).length > 0) {
|
|
2578
|
+
process.stdout.write("\n Stories:\n");
|
|
2579
|
+
for (const [key, s] of Object.entries(storyDetails)) process.stdout.write(` ${key}: ${s.phase} (${s.review_cycles} review cycles)\n`);
|
|
2580
|
+
process.stdout.write(`\n Summary: ${health.stories.active} active, ${health.stories.completed} completed, ${health.stories.escalated} escalated\n`);
|
|
2581
|
+
}
|
|
2648
2582
|
}
|
|
2649
2583
|
}
|
|
2650
2584
|
return 0;
|
|
@@ -2654,10 +2588,6 @@ async function runHealthAction(options) {
|
|
|
2654
2588
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
2655
2589
|
logger$11.error({ err }, "health action failed");
|
|
2656
2590
|
return 1;
|
|
2657
|
-
} finally {
|
|
2658
|
-
try {
|
|
2659
|
-
dbWrapper.close();
|
|
2660
|
-
} catch {}
|
|
2661
2591
|
}
|
|
2662
2592
|
}
|
|
2663
2593
|
function registerHealthCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
@@ -2700,6 +2630,36 @@ function defaultSupervisorDeps() {
|
|
|
2700
2630
|
}
|
|
2701
2631
|
};
|
|
2702
2632
|
})(),
|
|
2633
|
+
getTokenSnapshot: (runId, projectRoot) => {
|
|
2634
|
+
try {
|
|
2635
|
+
const dbPath = join(projectRoot, ".substrate", "substrate.db");
|
|
2636
|
+
if (!existsSync(dbPath)) return {
|
|
2637
|
+
input: 0,
|
|
2638
|
+
output: 0,
|
|
2639
|
+
cost_usd: 0
|
|
2640
|
+
};
|
|
2641
|
+
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
2642
|
+
try {
|
|
2643
|
+
dbWrapper.open();
|
|
2644
|
+
const agg = aggregateTokenUsageForRun(dbWrapper.db, runId);
|
|
2645
|
+
return {
|
|
2646
|
+
input: agg.input,
|
|
2647
|
+
output: agg.output,
|
|
2648
|
+
cost_usd: agg.cost
|
|
2649
|
+
};
|
|
2650
|
+
} finally {
|
|
2651
|
+
try {
|
|
2652
|
+
dbWrapper.close();
|
|
2653
|
+
} catch {}
|
|
2654
|
+
}
|
|
2655
|
+
} catch {
|
|
2656
|
+
return {
|
|
2657
|
+
input: 0,
|
|
2658
|
+
output: 0,
|
|
2659
|
+
cost_usd: 0
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
},
|
|
2703
2663
|
runAnalysis: async (runId, projectRoot) => {
|
|
2704
2664
|
const dbPath = join(projectRoot, ".substrate", "substrate.db");
|
|
2705
2665
|
if (!existsSync(dbPath)) return;
|
|
@@ -2741,7 +2701,7 @@ function defaultSupervisorDeps() {
|
|
|
2741
2701
|
*/
|
|
2742
2702
|
async function runSupervisorAction(options, deps = {}) {
|
|
2743
2703
|
const { pollInterval, stallThreshold, maxRestarts, outputFormat, projectRoot, runId, pack, experiment, maxExperiments } = options;
|
|
2744
|
-
const { getHealth, killPid, resumePipeline, sleep, incrementRestarts, runAnalysis } = {
|
|
2704
|
+
const { getHealth, killPid, resumePipeline, sleep, incrementRestarts, runAnalysis, getTokenSnapshot } = {
|
|
2745
2705
|
...defaultSupervisorDeps(),
|
|
2746
2706
|
...deps
|
|
2747
2707
|
};
|
|
@@ -2765,6 +2725,36 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2765
2725
|
projectRoot
|
|
2766
2726
|
});
|
|
2767
2727
|
const ts = new Date().toISOString();
|
|
2728
|
+
if (outputFormat === "json") {
|
|
2729
|
+
const tokenSnapshot = health.run_id !== null ? getTokenSnapshot(health.run_id, projectRoot) : {
|
|
2730
|
+
input: 0,
|
|
2731
|
+
output: 0,
|
|
2732
|
+
cost_usd: 0
|
|
2733
|
+
};
|
|
2734
|
+
const proc = health.process ?? {
|
|
2735
|
+
orchestrator_pid: null,
|
|
2736
|
+
child_pids: [],
|
|
2737
|
+
zombies: []
|
|
2738
|
+
};
|
|
2739
|
+
emitEvent({
|
|
2740
|
+
type: "supervisor:poll",
|
|
2741
|
+
run_id: health.run_id,
|
|
2742
|
+
verdict: health.verdict,
|
|
2743
|
+
staleness_seconds: health.staleness_seconds,
|
|
2744
|
+
stories: {
|
|
2745
|
+
active: health.stories.active,
|
|
2746
|
+
completed: health.stories.completed,
|
|
2747
|
+
escalated: health.stories.escalated
|
|
2748
|
+
},
|
|
2749
|
+
story_details: health.stories.details,
|
|
2750
|
+
tokens: tokenSnapshot,
|
|
2751
|
+
process: {
|
|
2752
|
+
orchestrator_pid: proc.orchestrator_pid,
|
|
2753
|
+
child_count: proc.child_pids.length,
|
|
2754
|
+
zombie_count: proc.zombies.length
|
|
2755
|
+
}
|
|
2756
|
+
});
|
|
2757
|
+
}
|
|
2768
2758
|
log(`[${ts}] Health: ${health.verdict} | staleness=${health.staleness_seconds}s | stories: active=${health.stories.active} completed=${health.stories.completed} escalated=${health.stories.escalated}`);
|
|
2769
2759
|
if (health.verdict === "NO_PIPELINE_RUNNING") {
|
|
2770
2760
|
const elapsedSeconds = Math.round((Date.now() - startTime) / 1e3);
|
|
@@ -2843,7 +2833,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2843
2833
|
const expDb = expDbWrapper.db;
|
|
2844
2834
|
const { runRunAction: runPipeline } = await import(
|
|
2845
2835
|
/* @vite-ignore */
|
|
2846
|
-
"../run-
|
|
2836
|
+
"../run-XkrV99HV.js"
|
|
2847
2837
|
);
|
|
2848
2838
|
const runStoryFn = async (opts) => {
|
|
2849
2839
|
const exitCode = await runPipeline({
|
package/dist/index.d.ts
CHANGED
|
@@ -231,6 +231,45 @@ interface StoryStallEvent {
|
|
|
231
231
|
/** Milliseconds since the last progress event */
|
|
232
232
|
elapsed_ms: number;
|
|
233
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Emitted after each `getHealth()` call in the supervisor poll loop.
|
|
236
|
+
* Allows agents to observe health state, story progress, and token costs
|
|
237
|
+
* on every cycle without needing a separate health query.
|
|
238
|
+
*/
|
|
239
|
+
interface SupervisorPollEvent {
|
|
240
|
+
type: 'supervisor:poll';
|
|
241
|
+
/** ISO-8601 timestamp generated at emit time */
|
|
242
|
+
ts: string;
|
|
243
|
+
/** Current pipeline run ID, or null if no run is active */
|
|
244
|
+
run_id: string | null;
|
|
245
|
+
/** Health verdict from the most recent getHealth() call */
|
|
246
|
+
verdict: 'HEALTHY' | 'STALLED' | 'NO_PIPELINE_RUNNING';
|
|
247
|
+
/** Seconds since the last pipeline activity */
|
|
248
|
+
staleness_seconds: number;
|
|
249
|
+
/** Story counts from the health snapshot */
|
|
250
|
+
stories: {
|
|
251
|
+
active: number;
|
|
252
|
+
completed: number;
|
|
253
|
+
escalated: number;
|
|
254
|
+
};
|
|
255
|
+
/** Per-story phase and review cycle details */
|
|
256
|
+
story_details: Record<string, {
|
|
257
|
+
phase: string;
|
|
258
|
+
review_cycles: number;
|
|
259
|
+
}>;
|
|
260
|
+
/** Cumulative token/cost snapshot for the current run */
|
|
261
|
+
tokens: {
|
|
262
|
+
input: number;
|
|
263
|
+
output: number;
|
|
264
|
+
cost_usd: number;
|
|
265
|
+
};
|
|
266
|
+
/** Process health from the health snapshot */
|
|
267
|
+
process: {
|
|
268
|
+
orchestrator_pid: number | null;
|
|
269
|
+
child_count: number;
|
|
270
|
+
zombie_count: number;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
234
273
|
/**
|
|
235
274
|
* Emitted when the supervisor kills a stalled pipeline process tree.
|
|
236
275
|
*/
|
|
@@ -389,7 +428,7 @@ interface SupervisorExperimentErrorEvent {
|
|
|
389
428
|
* }
|
|
390
429
|
* ```
|
|
391
430
|
*/
|
|
392
|
-
type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent; //#endregion
|
|
431
|
+
type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent; //#endregion
|
|
393
432
|
//#region src/core/errors.d.ts
|
|
394
433
|
|
|
395
434
|
/**
|
|
@@ -1024,6 +1024,21 @@ function validateStopAfterFromConflict(stopAfter, from) {
|
|
|
1024
1024
|
|
|
1025
1025
|
//#endregion
|
|
1026
1026
|
//#region src/cli/commands/pipeline-shared.ts
|
|
1027
|
+
/**
|
|
1028
|
+
* Parse a DB timestamp string to a Date, correctly treating it as UTC.
|
|
1029
|
+
*
|
|
1030
|
+
* SQLite stores timestamps as "YYYY-MM-DD HH:MM:SS" without a timezone suffix.
|
|
1031
|
+
* JavaScript's Date constructor parses strings without a timezone suffix as
|
|
1032
|
+
* *local time*, which causes staleness/duration to be calculated incorrectly
|
|
1033
|
+
* on machines not in UTC.
|
|
1034
|
+
*
|
|
1035
|
+
* Fix: append 'Z' if the string has no timezone marker so it is always
|
|
1036
|
+
* parsed as UTC.
|
|
1037
|
+
*/
|
|
1038
|
+
function parseDbTimestampAsUtc(ts) {
|
|
1039
|
+
if (ts.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(ts)) return new Date(ts);
|
|
1040
|
+
return new Date(ts.replace(" ", "T") + "Z");
|
|
1041
|
+
}
|
|
1027
1042
|
const __filename = fileURLToPath(import.meta.url);
|
|
1028
1043
|
const __dirname = dirname(__filename);
|
|
1029
1044
|
/**
|
|
@@ -1197,7 +1212,7 @@ function buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCou
|
|
|
1197
1212
|
decisions_count: decisionsCount,
|
|
1198
1213
|
stories_count: storiesCount,
|
|
1199
1214
|
last_activity: run.updated_at,
|
|
1200
|
-
staleness_seconds: Math.round((Date.now() -
|
|
1215
|
+
staleness_seconds: Math.round((Date.now() - parseDbTimestampAsUtc(run.updated_at).getTime()) / 1e3)
|
|
1201
1216
|
};
|
|
1202
1217
|
}
|
|
1203
1218
|
/**
|
|
@@ -1817,6 +1832,53 @@ const PIPELINE_EVENT_METADATA = [
|
|
|
1817
1832
|
}
|
|
1818
1833
|
]
|
|
1819
1834
|
},
|
|
1835
|
+
{
|
|
1836
|
+
type: "supervisor:poll",
|
|
1837
|
+
description: "Heartbeat each poll (JSON only).",
|
|
1838
|
+
when: "Per cycle.",
|
|
1839
|
+
fields: [
|
|
1840
|
+
{
|
|
1841
|
+
name: "ts",
|
|
1842
|
+
type: "string",
|
|
1843
|
+
description: "Timestamp."
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
name: "run_id",
|
|
1847
|
+
type: "string|null",
|
|
1848
|
+
description: "Run ID."
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
name: "verdict",
|
|
1852
|
+
type: "HEALTHY|STALLED|NO_PIPELINE_RUNNING",
|
|
1853
|
+
description: "Verdict."
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
name: "staleness_seconds",
|
|
1857
|
+
type: "number",
|
|
1858
|
+
description: "Seconds stale."
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
name: "stories",
|
|
1862
|
+
type: "object",
|
|
1863
|
+
description: "active/completed/escalated."
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
name: "story_details",
|
|
1867
|
+
type: "object",
|
|
1868
|
+
description: "phase+cycles per story."
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
name: "tokens",
|
|
1872
|
+
type: "object",
|
|
1873
|
+
description: "input/output/cost_usd."
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
name: "process",
|
|
1877
|
+
type: "object",
|
|
1878
|
+
description: "pid/child/zombie counts."
|
|
1879
|
+
}
|
|
1880
|
+
]
|
|
1881
|
+
},
|
|
1820
1882
|
{
|
|
1821
1883
|
type: "supervisor:kill",
|
|
1822
1884
|
description: "Supervisor killed stalled pipeline process tree.",
|
|
@@ -2238,42 +2300,34 @@ function generateInteractionPatternsSection() {
|
|
|
2238
2300
|
Use this decision flowchart when handling events from \`substrate run --events\`:
|
|
2239
2301
|
|
|
2240
2302
|
### On \`story:done\` with \`result: success\`
|
|
2241
|
-
- Report
|
|
2242
|
-
- Note the story key and number of review_cycles for telemetry.
|
|
2303
|
+
- Report success to the user.
|
|
2243
2304
|
|
|
2244
2305
|
### On \`story:done\` with \`result: failed\`
|
|
2245
|
-
- Report failure
|
|
2246
|
-
- Suggest checking logs or running \`substrate status\` for details.
|
|
2306
|
+
- Report failure with the story key.
|
|
2247
2307
|
|
|
2248
2308
|
### On \`story:escalation\`
|
|
2249
|
-
- Read
|
|
2250
|
-
- Present
|
|
2251
|
-
- Offer to fix the issues or explain them.
|
|
2252
|
-
- Ask the user whether to retry or abandon the story.
|
|
2309
|
+
- Read \`issues\`: each has \`severity\`, \`file\`, \`desc\`.
|
|
2310
|
+
- Present grouped by severity; ask user to retry or abandon.
|
|
2253
2311
|
|
|
2254
2312
|
### On \`story:phase\` with \`verdict: NEEDS_MINOR_FIXES\`
|
|
2255
|
-
-
|
|
2256
|
-
- Offer to apply the fixes or skip.
|
|
2257
|
-
- This is non-blocking — pipeline continues unless you intervene.
|
|
2313
|
+
- Non-blocking minor suggestions. Offer to apply or skip.
|
|
2258
2314
|
|
|
2259
2315
|
### On \`story:warn\`
|
|
2260
|
-
-
|
|
2261
|
-
- Common warnings: token ceiling truncation, partial batch failures.
|
|
2262
|
-
- Pipeline continues normally after a warn event.
|
|
2316
|
+
- Non-blocking warning; pipeline continues normally.
|
|
2263
2317
|
|
|
2264
2318
|
### On \`story:log\`
|
|
2265
|
-
-
|
|
2266
|
-
- Display if verbose mode is active; otherwise buffer or discard.
|
|
2319
|
+
- Informational only. Display in verbose mode.
|
|
2267
2320
|
|
|
2268
2321
|
### On \`pipeline:complete\`
|
|
2269
|
-
- Summarize
|
|
2270
|
-
- List any \`failed\` or \`escalated\` stories with reasons if available.
|
|
2271
|
-
- This is always the last event emitted.
|
|
2322
|
+
- Summarize \`succeeded\`, \`failed\`, \`escalated\` counts.
|
|
2272
2323
|
|
|
2273
2324
|
## Supervisor Interaction Patterns
|
|
2274
2325
|
|
|
2275
2326
|
Patterns for \`substrate supervisor --output-format json\` events:
|
|
2276
2327
|
|
|
2328
|
+
### On \`supervisor:poll\`
|
|
2329
|
+
- Track \`verdict\` and \`tokens.cost_usd\` each cycle. JSON only.
|
|
2330
|
+
|
|
2277
2331
|
### On \`supervisor:summary\`
|
|
2278
2332
|
- Summarize \`succeeded\`, \`failed\`, \`escalated\` counts and \`restarts\`.
|
|
2279
2333
|
- Offer analysis: \`substrate metrics --analysis <run_id> --output-format json\`.
|
|
@@ -10318,7 +10372,7 @@ async function runRunAction(options) {
|
|
|
10318
10372
|
else failedKeys.push(key);
|
|
10319
10373
|
try {
|
|
10320
10374
|
const runEndMs = Date.now();
|
|
10321
|
-
const runStartMs =
|
|
10375
|
+
const runStartMs = parseDbTimestampAsUtc(pipelineRun.created_at).getTime();
|
|
10322
10376
|
const tokenAgg = aggregateTokenUsageForRun(db, pipelineRun.id);
|
|
10323
10377
|
const storyMetrics = getStoryMetricsForRun(db, pipelineRun.id);
|
|
10324
10378
|
const totalReviewCycles = storyMetrics.reduce((sum, m) => sum + (m.review_cycles ?? 0), 0);
|
|
@@ -10721,5 +10775,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
|
|
|
10721
10775
|
}
|
|
10722
10776
|
|
|
10723
10777
|
//#endregion
|
|
10724
|
-
export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
10725
|
-
//# sourceMappingURL=run-
|
|
10778
|
+
export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
|
|
10779
|
+
//# sourceMappingURL=run-CDYE1PT3.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./logger-C6n1g8uP.js";
|
|
2
2
|
import "./event-bus-J-bw-pkp.js";
|
|
3
|
-
import { registerRunCommand, runRunAction } from "./run-
|
|
3
|
+
import { registerRunCommand, runRunAction } from "./run-CDYE1PT3.js";
|
|
4
4
|
import "./decisions-DNYByk0U.js";
|
|
5
5
|
import "./metrics-BSg8VIHd.js";
|
|
6
6
|
|