substrate-ai 0.1.28 → 0.1.31
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 +327 -19
- package/dist/{upgrade-RK_VoMg3.js → upgrade-4j5rZskl.js} +2 -2
- package/dist/{upgrade-IVr1D46-.js → upgrade-j7tWzbZ0.js} +2 -2
- package/dist/{version-manager-impl-vjZ6Bx9v.js → version-manager-impl-CJLdocS1.js} +1 -1
- package/dist/{version-manager-impl-D7klVqyj.js → version-manager-impl-mBbvaQL2.js} +21 -8
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { AdapterRegistry, ConfigError, ConfigIncompatibleFormatError, DatabaseWrapper, MonitorDatabaseImpl, ParseError, RecommendationEngine, TaskGraphFileSchema, ValidationError, computeChangedKeys, createConfigWatcher, createDatabaseService, createEventBus, createGitWorktreeManager, createLogger, createMonitorAgent, createMonitorDatabase, createRoutingEngine, createTaskGraphEngine, createTuiApp, createWorkerPoolManager, deepMask, detectCycle, getAllTasks, getLatestSessionId, getLogByEvent, getSession, getSessionLog, getTaskLog, isTuiCapable, parseGraphFile, printNonTtyWarning, queryLogFiltered, runMigrations, validateDependencies, validateGraph } from "../app-CY3MaJtP.js";
|
|
3
3
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema } from "../config-schema-C9tTMcm1.js";
|
|
4
|
-
import { defaultConfigMigrator } from "../version-manager-impl-
|
|
5
|
-
import { registerUpgradeCommand } from "../upgrade-
|
|
4
|
+
import { defaultConfigMigrator } from "../version-manager-impl-mBbvaQL2.js";
|
|
5
|
+
import { registerUpgradeCommand } from "../upgrade-4j5rZskl.js";
|
|
6
6
|
import { createRequire } from "module";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
@@ -8362,16 +8362,40 @@ function getTokenUsageSummary(db, runId) {
|
|
|
8362
8362
|
//#region src/persistence/queries/metrics.ts
|
|
8363
8363
|
/**
|
|
8364
8364
|
* Write or update run-level metrics.
|
|
8365
|
+
*
|
|
8366
|
+
* Uses INSERT ... ON CONFLICT DO UPDATE to avoid a TOCTOU race on the
|
|
8367
|
+
* `restarts` counter: when a row already exists, `restarts` is preserved from
|
|
8368
|
+
* the DB (so any `incrementRunRestarts()` calls made by the supervisor between
|
|
8369
|
+
* the caller's read and this write are not silently overwritten).
|
|
8365
8370
|
*/
|
|
8366
8371
|
function writeRunMetrics(db, input) {
|
|
8367
8372
|
const stmt = db.prepare(`
|
|
8368
|
-
INSERT
|
|
8373
|
+
INSERT INTO run_metrics (
|
|
8369
8374
|
run_id, methodology, status, started_at, completed_at,
|
|
8370
8375
|
wall_clock_seconds, total_input_tokens, total_output_tokens, total_cost_usd,
|
|
8371
8376
|
stories_attempted, stories_succeeded, stories_failed, stories_escalated,
|
|
8372
8377
|
total_review_cycles, total_dispatches, concurrency_setting, max_concurrent_actual, restarts,
|
|
8373
8378
|
is_baseline
|
|
8374
8379
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8380
|
+
ON CONFLICT(run_id) DO UPDATE SET
|
|
8381
|
+
methodology = excluded.methodology,
|
|
8382
|
+
status = excluded.status,
|
|
8383
|
+
started_at = excluded.started_at,
|
|
8384
|
+
completed_at = excluded.completed_at,
|
|
8385
|
+
wall_clock_seconds = excluded.wall_clock_seconds,
|
|
8386
|
+
total_input_tokens = excluded.total_input_tokens,
|
|
8387
|
+
total_output_tokens = excluded.total_output_tokens,
|
|
8388
|
+
total_cost_usd = excluded.total_cost_usd,
|
|
8389
|
+
stories_attempted = excluded.stories_attempted,
|
|
8390
|
+
stories_succeeded = excluded.stories_succeeded,
|
|
8391
|
+
stories_failed = excluded.stories_failed,
|
|
8392
|
+
stories_escalated = excluded.stories_escalated,
|
|
8393
|
+
total_review_cycles = excluded.total_review_cycles,
|
|
8394
|
+
total_dispatches = excluded.total_dispatches,
|
|
8395
|
+
concurrency_setting = excluded.concurrency_setting,
|
|
8396
|
+
max_concurrent_actual = excluded.max_concurrent_actual,
|
|
8397
|
+
restarts = run_metrics.restarts,
|
|
8398
|
+
is_baseline = run_metrics.is_baseline
|
|
8375
8399
|
`);
|
|
8376
8400
|
stmt.run(input.run_id, input.methodology, input.status, input.started_at, input.completed_at ?? null, input.wall_clock_seconds ?? 0, input.total_input_tokens ?? 0, input.total_output_tokens ?? 0, input.total_cost_usd ?? 0, input.stories_attempted ?? 0, input.stories_succeeded ?? 0, input.stories_failed ?? 0, input.stories_escalated ?? 0, input.total_review_cycles ?? 0, input.total_dispatches ?? 0, input.concurrency_setting ?? 1, input.max_concurrent_actual ?? 1, input.restarts ?? 0, input.is_baseline ?? 0);
|
|
8377
8401
|
}
|
|
@@ -8397,6 +8421,26 @@ function tagRunAsBaseline(db, runId) {
|
|
|
8397
8421
|
})();
|
|
8398
8422
|
}
|
|
8399
8423
|
/**
|
|
8424
|
+
* Get the current baseline run metrics (if any).
|
|
8425
|
+
*/
|
|
8426
|
+
function getBaselineRunMetrics(db) {
|
|
8427
|
+
return db.prepare("SELECT * FROM run_metrics WHERE is_baseline = 1 LIMIT 1").get();
|
|
8428
|
+
}
|
|
8429
|
+
/**
|
|
8430
|
+
* Increment the restart count for a run by 1.
|
|
8431
|
+
* Called by the supervisor each time it successfully restarts the pipeline.
|
|
8432
|
+
* If the run_id does not yet exist in run_metrics, a placeholder row is
|
|
8433
|
+
* inserted so the restart count is not lost — writeRunMetrics will overwrite
|
|
8434
|
+
* all other fields when the run reaches a terminal state.
|
|
8435
|
+
*/
|
|
8436
|
+
function incrementRunRestarts(db, runId) {
|
|
8437
|
+
db.prepare(`
|
|
8438
|
+
INSERT INTO run_metrics (run_id, methodology, status, started_at, restarts)
|
|
8439
|
+
VALUES (?, 'unknown', 'running', datetime('now'), 1)
|
|
8440
|
+
ON CONFLICT(run_id) DO UPDATE SET restarts = run_metrics.restarts + 1
|
|
8441
|
+
`).run(runId);
|
|
8442
|
+
}
|
|
8443
|
+
/**
|
|
8400
8444
|
* Write or update story-level metrics.
|
|
8401
8445
|
*/
|
|
8402
8446
|
function writeStoryMetrics(db, input) {
|
|
@@ -8435,7 +8479,7 @@ function compareRunMetrics(db, runIdA, runIdB) {
|
|
|
8435
8479
|
const a = getRunMetrics(db, runIdA);
|
|
8436
8480
|
const b = getRunMetrics(db, runIdB);
|
|
8437
8481
|
if (!a || !b) return null;
|
|
8438
|
-
const pct = (base, diff) => base === 0 ?
|
|
8482
|
+
const pct = (base, diff) => base === 0 ? null : Math.round(diff / base * 100 * 10) / 10;
|
|
8439
8483
|
const inputDelta = b.total_input_tokens - a.total_input_tokens;
|
|
8440
8484
|
const outputDelta = b.total_output_tokens - a.total_output_tokens;
|
|
8441
8485
|
const clockDelta = (b.wall_clock_seconds ?? 0) - (a.wall_clock_seconds ?? 0);
|
|
@@ -10478,6 +10522,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
10478
10522
|
const _phaseStartMs = new Map();
|
|
10479
10523
|
const _phaseEndMs = new Map();
|
|
10480
10524
|
const _storyDispatches = new Map();
|
|
10525
|
+
let _maxConcurrentActual = 0;
|
|
10481
10526
|
function startPhase(storyKey, phase) {
|
|
10482
10527
|
if (!_phaseStartMs.has(storyKey)) _phaseStartMs.set(storyKey, new Map());
|
|
10483
10528
|
_phaseStartMs.get(storyKey).set(phase, Date.now());
|
|
@@ -10494,9 +10539,14 @@ function createImplementationOrchestrator(deps) {
|
|
|
10494
10539
|
const ends = _phaseEndMs.get(storyKey);
|
|
10495
10540
|
if (!starts || starts.size === 0) return "{}";
|
|
10496
10541
|
const durations = {};
|
|
10542
|
+
const nowMs = Date.now();
|
|
10497
10543
|
for (const [phase, startMs] of starts) {
|
|
10498
|
-
const endMs = ends?.get(phase)
|
|
10499
|
-
|
|
10544
|
+
const endMs = ends?.get(phase);
|
|
10545
|
+
if (endMs === void 0) logger$36.warn({
|
|
10546
|
+
storyKey,
|
|
10547
|
+
phase
|
|
10548
|
+
}, "Phase has no end time — story may have errored mid-phase. Duration capped to now() and may be inflated.");
|
|
10549
|
+
durations[phase] = Math.round(((endMs ?? nowMs) - startMs) / 1e3);
|
|
10500
10550
|
}
|
|
10501
10551
|
return JSON.stringify(durations);
|
|
10502
10552
|
}
|
|
@@ -10542,6 +10592,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
10542
10592
|
if (_startedAt !== void 0) status.totalDurationMs = new Date(_completedAt).getTime() - new Date(_startedAt).getTime();
|
|
10543
10593
|
}
|
|
10544
10594
|
if (_decomposition !== void 0) status.decomposition = { ..._decomposition };
|
|
10595
|
+
if (_maxConcurrentActual > 0) status.maxConcurrentActual = _maxConcurrentActual;
|
|
10545
10596
|
return status;
|
|
10546
10597
|
}
|
|
10547
10598
|
function updateStory(storyKey, updates) {
|
|
@@ -11322,6 +11373,7 @@ function createImplementationOrchestrator(deps) {
|
|
|
11322
11373
|
if (idx !== -1) running.splice(idx, 1);
|
|
11323
11374
|
});
|
|
11324
11375
|
running.push(p);
|
|
11376
|
+
if (running.length > _maxConcurrentActual) _maxConcurrentActual = running.length;
|
|
11325
11377
|
}
|
|
11326
11378
|
const initial = Math.min(maxConcurrency, queue.length);
|
|
11327
11379
|
for (let i = 0; i < initial; i++) enqueue();
|
|
@@ -16156,6 +16208,167 @@ async function scaffoldClaudeSettings(projectRoot) {
|
|
|
16156
16208
|
await writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
16157
16209
|
logger$3.info({ settingsPath }, "Wrote substrate settings to .claude/settings.json");
|
|
16158
16210
|
}
|
|
16211
|
+
/**
|
|
16212
|
+
* Resolve the absolute path to bmad-method's installer lib directory.
|
|
16213
|
+
* Returns null if bmad-method is not installed.
|
|
16214
|
+
*/
|
|
16215
|
+
function resolveBmadMethodInstallerLibPath(fromDir = __dirname) {
|
|
16216
|
+
try {
|
|
16217
|
+
const _require = createRequire$1(join(fromDir, "synthetic.js"));
|
|
16218
|
+
const pkgJsonPath = _require.resolve("bmad-method/package.json");
|
|
16219
|
+
return join(dirname(pkgJsonPath), "tools", "cli", "installers", "lib");
|
|
16220
|
+
} catch {
|
|
16221
|
+
return null;
|
|
16222
|
+
}
|
|
16223
|
+
}
|
|
16224
|
+
/**
|
|
16225
|
+
* Scan the _bmad/ directory for installed module names (excluding 'core' and '_config').
|
|
16226
|
+
* Returns module names that contain agents/, workflows/, or tasks/ subdirs.
|
|
16227
|
+
*/
|
|
16228
|
+
function scanBmadModules(bmadDir) {
|
|
16229
|
+
const modules = [];
|
|
16230
|
+
try {
|
|
16231
|
+
const entries = readdirSync(bmadDir, { withFileTypes: true });
|
|
16232
|
+
for (const entry of entries) {
|
|
16233
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name.startsWith("_") || entry.name === "core") continue;
|
|
16234
|
+
const modPath = join(bmadDir, entry.name);
|
|
16235
|
+
const hasAgents = existsSync(join(modPath, "agents"));
|
|
16236
|
+
const hasWorkflows = existsSync(join(modPath, "workflows"));
|
|
16237
|
+
const hasTasks = existsSync(join(modPath, "tasks"));
|
|
16238
|
+
if (hasAgents || hasWorkflows || hasTasks) modules.push(entry.name);
|
|
16239
|
+
}
|
|
16240
|
+
} catch {}
|
|
16241
|
+
return modules;
|
|
16242
|
+
}
|
|
16243
|
+
/**
|
|
16244
|
+
* Remove existing bmad-*.md files from .claude/commands/ for idempotent regeneration.
|
|
16245
|
+
* Preserves user's custom (non-bmad) command files.
|
|
16246
|
+
*/
|
|
16247
|
+
function clearBmadCommandFiles(commandsDir) {
|
|
16248
|
+
try {
|
|
16249
|
+
const entries = readdirSync(commandsDir);
|
|
16250
|
+
for (const entry of entries) if (entry.startsWith("bmad-") && entry.endsWith(".md")) try {
|
|
16251
|
+
unlinkSync(join(commandsDir, entry));
|
|
16252
|
+
} catch {}
|
|
16253
|
+
} catch {}
|
|
16254
|
+
}
|
|
16255
|
+
/**
|
|
16256
|
+
* Compile .agent.yaml files to .md format using bmad-method's agent compiler.
|
|
16257
|
+
* The command generators only recognize compiled .md files with <agent> XML tags.
|
|
16258
|
+
* Scans _bmad/core/agents/ and _bmad/{module}/agents/ for uncompiled YAML files.
|
|
16259
|
+
*
|
|
16260
|
+
* @returns number of agents compiled
|
|
16261
|
+
*/
|
|
16262
|
+
async function compileBmadAgents(bmadDir) {
|
|
16263
|
+
const _require = createRequire$1(join(__dirname, "synthetic.js"));
|
|
16264
|
+
let compilerPath;
|
|
16265
|
+
try {
|
|
16266
|
+
const pkgJsonPath = _require.resolve("bmad-method/package.json");
|
|
16267
|
+
compilerPath = join(dirname(pkgJsonPath), "tools", "cli", "lib", "agent", "compiler.js");
|
|
16268
|
+
} catch {
|
|
16269
|
+
return 0;
|
|
16270
|
+
}
|
|
16271
|
+
const { compileAgent } = _require(compilerPath);
|
|
16272
|
+
const agentDirs = [];
|
|
16273
|
+
const coreAgentsDir = join(bmadDir, "core", "agents");
|
|
16274
|
+
if (existsSync(coreAgentsDir)) agentDirs.push(coreAgentsDir);
|
|
16275
|
+
try {
|
|
16276
|
+
const entries = readdirSync(bmadDir, { withFileTypes: true });
|
|
16277
|
+
for (const entry of entries) {
|
|
16278
|
+
if (!entry.isDirectory() || entry.name === "core" || entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
|
|
16279
|
+
const modAgentsDir = join(bmadDir, entry.name, "agents");
|
|
16280
|
+
if (existsSync(modAgentsDir)) agentDirs.push(modAgentsDir);
|
|
16281
|
+
}
|
|
16282
|
+
} catch {}
|
|
16283
|
+
let compiled = 0;
|
|
16284
|
+
for (const agentDir of agentDirs) try {
|
|
16285
|
+
const files = readdirSync(agentDir);
|
|
16286
|
+
for (const file of files) {
|
|
16287
|
+
if (!file.endsWith(".agent.yaml")) continue;
|
|
16288
|
+
const yamlPath = join(agentDir, file);
|
|
16289
|
+
const mdPath = join(agentDir, file.replace(".agent.yaml", ".md"));
|
|
16290
|
+
if (existsSync(mdPath)) continue;
|
|
16291
|
+
try {
|
|
16292
|
+
const yamlContent = readFileSync(yamlPath, "utf-8");
|
|
16293
|
+
const agentName = file.replace(".agent.yaml", "");
|
|
16294
|
+
const result = await compileAgent(yamlContent, {}, agentName, mdPath);
|
|
16295
|
+
writeFileSync(mdPath, result.xml, "utf-8");
|
|
16296
|
+
compiled++;
|
|
16297
|
+
} catch (compileErr) {
|
|
16298
|
+
logger$3.debug({
|
|
16299
|
+
err: compileErr,
|
|
16300
|
+
file
|
|
16301
|
+
}, "Failed to compile agent YAML");
|
|
16302
|
+
}
|
|
16303
|
+
}
|
|
16304
|
+
} catch {}
|
|
16305
|
+
return compiled;
|
|
16306
|
+
}
|
|
16307
|
+
/**
|
|
16308
|
+
* Generate .claude/commands/ files by calling bmad-method's command generators.
|
|
16309
|
+
*
|
|
16310
|
+
* Uses the installed bmad-method package's AgentCommandGenerator,
|
|
16311
|
+
* WorkflowCommandGenerator, and TaskToolCommandGenerator classes via createRequire.
|
|
16312
|
+
* Compiles agent YAML to MD first, then generates CSV manifests so workflow/task
|
|
16313
|
+
* generators can discover content.
|
|
16314
|
+
*
|
|
16315
|
+
* Graceful degradation: warns but never fails init.
|
|
16316
|
+
*/
|
|
16317
|
+
async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
16318
|
+
const bmadDir = join(projectRoot, "_bmad");
|
|
16319
|
+
if (!existsSync(bmadDir)) return;
|
|
16320
|
+
const installerLibPath = resolveBmadMethodInstallerLibPath();
|
|
16321
|
+
if (!installerLibPath) {
|
|
16322
|
+
if (outputFormat !== "json") process.stderr.write("Warning: bmad-method not found. Skipping .claude/commands/ generation.\n");
|
|
16323
|
+
return;
|
|
16324
|
+
}
|
|
16325
|
+
try {
|
|
16326
|
+
const _require = createRequire$1(join(__dirname, "synthetic.js"));
|
|
16327
|
+
try {
|
|
16328
|
+
const compiledCount = await compileBmadAgents(bmadDir);
|
|
16329
|
+
if (compiledCount > 0) logger$3.info({ compiledCount }, "Compiled agent YAML files to MD");
|
|
16330
|
+
} catch (compileErr) {
|
|
16331
|
+
logger$3.warn({ err: compileErr }, "Agent compilation failed; agent commands may be incomplete");
|
|
16332
|
+
}
|
|
16333
|
+
const { AgentCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "agent-command-generator.js"));
|
|
16334
|
+
const { WorkflowCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "workflow-command-generator.js"));
|
|
16335
|
+
const { TaskToolCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "task-tool-command-generator.js"));
|
|
16336
|
+
const { ManifestGenerator } = _require(join(installerLibPath, "core", "manifest-generator.js"));
|
|
16337
|
+
const nonCoreModules = scanBmadModules(bmadDir);
|
|
16338
|
+
const allModules = ["core", ...nonCoreModules];
|
|
16339
|
+
try {
|
|
16340
|
+
const manifestGen = new ManifestGenerator();
|
|
16341
|
+
await manifestGen.generateManifests(bmadDir, allModules, [], { ides: ["claude-code"] });
|
|
16342
|
+
} catch (manifestErr) {
|
|
16343
|
+
logger$3.warn({ err: manifestErr }, "ManifestGenerator failed; workflow/task commands may be incomplete");
|
|
16344
|
+
}
|
|
16345
|
+
const commandsDir = join(projectRoot, ".claude", "commands");
|
|
16346
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
16347
|
+
clearBmadCommandFiles(commandsDir);
|
|
16348
|
+
const agentGen = new AgentCommandGenerator("_bmad");
|
|
16349
|
+
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, nonCoreModules);
|
|
16350
|
+
const agentCount = await agentGen.writeDashArtifacts(commandsDir, agentArtifacts);
|
|
16351
|
+
const workflowGen = new WorkflowCommandGenerator("_bmad");
|
|
16352
|
+
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
|
|
16353
|
+
const workflowCount = await workflowGen.writeDashArtifacts(commandsDir, workflowArtifacts);
|
|
16354
|
+
const taskToolGen = new TaskToolCommandGenerator("_bmad");
|
|
16355
|
+
const { artifacts: taskToolArtifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
|
|
16356
|
+
const taskToolCount = await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts);
|
|
16357
|
+
const total = agentCount + workflowCount + taskToolCount;
|
|
16358
|
+
if (outputFormat !== "json") process.stdout.write(`Generated ${String(total)} Claude Code commands (${String(agentCount)} agents, ${String(workflowCount)} workflows, ${String(taskToolCount)} tasks/tools)\n`);
|
|
16359
|
+
logger$3.info({
|
|
16360
|
+
agentCount,
|
|
16361
|
+
workflowCount,
|
|
16362
|
+
taskToolCount,
|
|
16363
|
+
total,
|
|
16364
|
+
commandsDir
|
|
16365
|
+
}, "Generated .claude/commands/");
|
|
16366
|
+
} catch (err) {
|
|
16367
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16368
|
+
if (outputFormat !== "json") process.stderr.write(`Warning: .claude/commands/ generation failed: ${msg}\n`);
|
|
16369
|
+
logger$3.warn({ err }, "scaffoldClaudeCommands failed; init continues");
|
|
16370
|
+
}
|
|
16371
|
+
}
|
|
16159
16372
|
async function runAutoInit(options) {
|
|
16160
16373
|
const { pack: packName, projectRoot, outputFormat, force = false } = options;
|
|
16161
16374
|
const packPath = join(projectRoot, "packs", packName);
|
|
@@ -16206,6 +16419,7 @@ async function runAutoInit(options) {
|
|
|
16206
16419
|
await scaffoldClaudeMd(projectRoot);
|
|
16207
16420
|
await scaffoldStatuslineScript(projectRoot);
|
|
16208
16421
|
await scaffoldClaudeSettings(projectRoot);
|
|
16422
|
+
await scaffoldClaudeCommands(projectRoot, outputFormat);
|
|
16209
16423
|
const successMsg = `Pack '${packName}' and database initialized successfully at ${dbPath}`;
|
|
16210
16424
|
if (outputFormat === "json") process.stdout.write(formatOutput({
|
|
16211
16425
|
pack: packName,
|
|
@@ -16676,7 +16890,8 @@ async function runAutoRun(options) {
|
|
|
16676
16890
|
stories_escalated: escalatedKeys.length,
|
|
16677
16891
|
total_review_cycles: totalReviewCycles,
|
|
16678
16892
|
total_dispatches: totalDispatches,
|
|
16679
|
-
concurrency_setting: concurrency
|
|
16893
|
+
concurrency_setting: concurrency,
|
|
16894
|
+
max_concurrent_actual: status.maxConcurrentActual ?? Math.min(concurrency, storyKeys.length)
|
|
16680
16895
|
});
|
|
16681
16896
|
} catch (metricsErr) {
|
|
16682
16897
|
logger$3.warn({ err: metricsErr }, "Failed to write run metrics (best-effort)");
|
|
@@ -17665,7 +17880,51 @@ function defaultSupervisorDeps() {
|
|
|
17665
17880
|
process.kill(pid, signal);
|
|
17666
17881
|
},
|
|
17667
17882
|
resumePipeline: runAutoResume,
|
|
17668
|
-
sleep: (ms) => new Promise((resolve$2) => setTimeout(resolve$2, ms))
|
|
17883
|
+
sleep: (ms) => new Promise((resolve$2) => setTimeout(resolve$2, ms)),
|
|
17884
|
+
incrementRestarts: (() => {
|
|
17885
|
+
let cachedDbWrapper = null;
|
|
17886
|
+
return (runId, projectRoot) => {
|
|
17887
|
+
try {
|
|
17888
|
+
if (cachedDbWrapper === null) {
|
|
17889
|
+
const dbDir = join(projectRoot, ".substrate");
|
|
17890
|
+
const dbPath = join(dbDir, "substrate.db");
|
|
17891
|
+
cachedDbWrapper = new DatabaseWrapper(dbPath);
|
|
17892
|
+
}
|
|
17893
|
+
incrementRunRestarts(cachedDbWrapper.getDb(), runId);
|
|
17894
|
+
} catch {
|
|
17895
|
+
try {
|
|
17896
|
+
cachedDbWrapper?.close();
|
|
17897
|
+
} catch {}
|
|
17898
|
+
cachedDbWrapper = null;
|
|
17899
|
+
}
|
|
17900
|
+
};
|
|
17901
|
+
})(),
|
|
17902
|
+
runAnalysis: async (runId, projectRoot) => {
|
|
17903
|
+
const dbPath = join(projectRoot, ".substrate", "substrate.db");
|
|
17904
|
+
if (!existsSync(dbPath)) return;
|
|
17905
|
+
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
17906
|
+
try {
|
|
17907
|
+
dbWrapper.open();
|
|
17908
|
+
runMigrations(dbWrapper.db);
|
|
17909
|
+
const db = dbWrapper.db;
|
|
17910
|
+
const run = getRunMetrics(db, runId);
|
|
17911
|
+
if (!run) return;
|
|
17912
|
+
const stories = getStoryMetricsForRun(db, runId);
|
|
17913
|
+
const baseline = getBaselineRunMetrics(db);
|
|
17914
|
+
const baselineStories = baseline && baseline.run_id !== runId ? getStoryMetricsForRun(db, baseline.run_id) : [];
|
|
17915
|
+
const analysisPath = "../../modules/supervisor/analysis.js";
|
|
17916
|
+
const { generateAnalysisReport, writeAnalysisReport } = await import(
|
|
17917
|
+
/* @vite-ignore */
|
|
17918
|
+
analysisPath
|
|
17919
|
+
);
|
|
17920
|
+
const report = generateAnalysisReport(run, stories, baseline, baselineStories);
|
|
17921
|
+
writeAnalysisReport(report, projectRoot);
|
|
17922
|
+
} catch {} finally {
|
|
17923
|
+
try {
|
|
17924
|
+
dbWrapper.close();
|
|
17925
|
+
} catch {}
|
|
17926
|
+
}
|
|
17927
|
+
}
|
|
17669
17928
|
};
|
|
17670
17929
|
}
|
|
17671
17930
|
/**
|
|
@@ -17680,8 +17939,8 @@ function defaultSupervisorDeps() {
|
|
|
17680
17939
|
* 2 — max restarts exceeded (safety valve triggered)
|
|
17681
17940
|
*/
|
|
17682
17941
|
async function runAutoSupervisor(options, deps = {}) {
|
|
17683
|
-
const { pollInterval, stallThreshold, maxRestarts, outputFormat, projectRoot, runId, pack } = options;
|
|
17684
|
-
const { getHealth, killPid, resumePipeline, sleep } = {
|
|
17942
|
+
const { pollInterval, stallThreshold, maxRestarts, outputFormat, projectRoot, runId, pack, experiment } = options;
|
|
17943
|
+
const { getHealth, killPid, resumePipeline, sleep, incrementRestarts, runAnalysis } = {
|
|
17685
17944
|
...defaultSupervisorDeps(),
|
|
17686
17945
|
...deps
|
|
17687
17946
|
};
|
|
@@ -17721,6 +17980,52 @@ async function runAutoSupervisor(options, deps = {}) {
|
|
|
17721
17980
|
restarts: restartCount
|
|
17722
17981
|
});
|
|
17723
17982
|
log(`\nPipeline reached terminal state. Elapsed: ${elapsedSeconds}s | succeeded: ${succeeded.length} | failed: ${failed.length} | restarts: ${restartCount}`);
|
|
17983
|
+
if (health.run_id !== null && runAnalysis !== void 0) {
|
|
17984
|
+
log(`[supervisor] Running post-run analysis for ${health.run_id}...`);
|
|
17985
|
+
await runAnalysis(health.run_id, projectRoot);
|
|
17986
|
+
log(`[supervisor] Analysis report written to _bmad-output/supervisor-reports/${health.run_id}-analysis.md`);
|
|
17987
|
+
emitEvent$1({
|
|
17988
|
+
type: "supervisor:analysis:complete",
|
|
17989
|
+
run_id: health.run_id
|
|
17990
|
+
});
|
|
17991
|
+
}
|
|
17992
|
+
if (experiment && health.run_id !== null) {
|
|
17993
|
+
log(`\n[supervisor] Experiment mode enabled. Checking for optimization recommendations...`);
|
|
17994
|
+
emitEvent$1({
|
|
17995
|
+
type: "supervisor:experiment:start",
|
|
17996
|
+
run_id: health.run_id
|
|
17997
|
+
});
|
|
17998
|
+
const analysisReportPath = join(projectRoot, "_bmad-output", "supervisor-reports", `${health.run_id}-analysis.json`);
|
|
17999
|
+
try {
|
|
18000
|
+
const { readFile: fsReadFile } = await import("fs/promises");
|
|
18001
|
+
const raw = await fsReadFile(analysisReportPath, "utf-8");
|
|
18002
|
+
const analysisData = JSON.parse(raw);
|
|
18003
|
+
const recommendations = analysisData.recommendations ?? [];
|
|
18004
|
+
if (recommendations.length === 0) {
|
|
18005
|
+
log(`[supervisor] No recommendations found in analysis report — skipping experiments.`);
|
|
18006
|
+
emitEvent$1({
|
|
18007
|
+
type: "supervisor:experiment:skip",
|
|
18008
|
+
run_id: health.run_id,
|
|
18009
|
+
reason: "no_recommendations"
|
|
18010
|
+
});
|
|
18011
|
+
} else {
|
|
18012
|
+
log(`[supervisor] Found ${recommendations.length} recommendation(s) to experiment with.`);
|
|
18013
|
+
emitEvent$1({
|
|
18014
|
+
type: "supervisor:experiment:recommendations",
|
|
18015
|
+
run_id: health.run_id,
|
|
18016
|
+
count: recommendations.length
|
|
18017
|
+
});
|
|
18018
|
+
}
|
|
18019
|
+
} catch {
|
|
18020
|
+
log(`[supervisor] Analysis report not found at ${analysisReportPath} — skipping experiments.`);
|
|
18021
|
+
log(`[supervisor] Run 'substrate auto metrics --analysis <run-id>' first to generate recommendations.`);
|
|
18022
|
+
emitEvent$1({
|
|
18023
|
+
type: "supervisor:experiment:skip",
|
|
18024
|
+
run_id: health.run_id,
|
|
18025
|
+
reason: "no_analysis_report"
|
|
18026
|
+
});
|
|
18027
|
+
}
|
|
18028
|
+
}
|
|
17724
18029
|
return failed.length > 0 || escalated.length > 0 ? 1 : 0;
|
|
17725
18030
|
}
|
|
17726
18031
|
if (health.staleness_seconds >= stallThreshold) {
|
|
@@ -17767,6 +18072,7 @@ async function runAutoSupervisor(options, deps = {}) {
|
|
|
17767
18072
|
return 2;
|
|
17768
18073
|
}
|
|
17769
18074
|
restartCount++;
|
|
18075
|
+
if (health.run_id !== null) incrementRestarts(health.run_id, projectRoot);
|
|
17770
18076
|
emitEvent$1({
|
|
17771
18077
|
type: "supervisor:restart",
|
|
17772
18078
|
run_id: health.run_id,
|
|
@@ -18129,12 +18435,13 @@ async function runAutoMetrics(options) {
|
|
|
18129
18435
|
if (outputFormat === "json") process.stdout.write(formatOutput(delta, "json", true) + "\n");
|
|
18130
18436
|
else {
|
|
18131
18437
|
const sign = (n) => n > 0 ? "+" : "";
|
|
18438
|
+
const fmtPct = (pct) => pct === null ? "N/A" : `${sign(pct)}${pct}%`;
|
|
18132
18439
|
process.stdout.write(`\nMetrics Comparison: ${idA.slice(0, 8)} vs ${idB.slice(0, 8)}\n`);
|
|
18133
|
-
process.stdout.write(` Input tokens: ${sign(delta.token_input_delta)}${delta.token_input_delta.toLocaleString()} (${
|
|
18134
|
-
process.stdout.write(` Output tokens: ${sign(delta.token_output_delta)}${delta.token_output_delta.toLocaleString()} (${
|
|
18135
|
-
process.stdout.write(` Wall clock: ${sign(delta.wall_clock_delta_seconds)}${delta.wall_clock_delta_seconds}s (${
|
|
18136
|
-
process.stdout.write(` Review cycles: ${sign(delta.review_cycles_delta)}${delta.review_cycles_delta} (${
|
|
18137
|
-
process.stdout.write(` Cost USD: ${sign(delta.cost_delta)}$${Math.abs(delta.cost_delta).toFixed(4)} (${
|
|
18440
|
+
process.stdout.write(` Input tokens: ${sign(delta.token_input_delta)}${delta.token_input_delta.toLocaleString()} (${fmtPct(delta.token_input_pct)})\n`);
|
|
18441
|
+
process.stdout.write(` Output tokens: ${sign(delta.token_output_delta)}${delta.token_output_delta.toLocaleString()} (${fmtPct(delta.token_output_pct)})\n`);
|
|
18442
|
+
process.stdout.write(` Wall clock: ${sign(delta.wall_clock_delta_seconds)}${delta.wall_clock_delta_seconds}s (${fmtPct(delta.wall_clock_pct)})\n`);
|
|
18443
|
+
process.stdout.write(` Review cycles: ${sign(delta.review_cycles_delta)}${delta.review_cycles_delta} (${fmtPct(delta.review_cycles_pct)})\n`);
|
|
18444
|
+
process.stdout.write(` Cost USD: ${delta.cost_delta < 0 ? "-" : sign(delta.cost_delta)}$${Math.abs(delta.cost_delta).toFixed(4)} (${fmtPct(delta.cost_pct)})\n`);
|
|
18138
18445
|
}
|
|
18139
18446
|
return 0;
|
|
18140
18447
|
}
|
|
@@ -18256,7 +18563,7 @@ function registerAutoCommand(program, _version = "0.0.0", projectRoot = process.
|
|
|
18256
18563
|
});
|
|
18257
18564
|
process.exitCode = exitCode;
|
|
18258
18565
|
});
|
|
18259
|
-
auto.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("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
|
|
18566
|
+
auto.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("--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).action(async (opts) => {
|
|
18260
18567
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
18261
18568
|
const exitCode = await runAutoSupervisor({
|
|
18262
18569
|
pollInterval: opts.pollInterval,
|
|
@@ -18265,7 +18572,8 @@ function registerAutoCommand(program, _version = "0.0.0", projectRoot = process.
|
|
|
18265
18572
|
runId: opts.runId,
|
|
18266
18573
|
pack: opts.pack,
|
|
18267
18574
|
outputFormat,
|
|
18268
|
-
projectRoot: opts.projectRoot
|
|
18575
|
+
projectRoot: opts.projectRoot,
|
|
18576
|
+
experiment: opts.experiment
|
|
18269
18577
|
});
|
|
18270
18578
|
process.exitCode = exitCode;
|
|
18271
18579
|
});
|
|
@@ -19368,8 +19676,8 @@ async function createProgram() {
|
|
|
19368
19676
|
/** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
|
|
19369
19677
|
function checkForUpdatesInBackground(currentVersion) {
|
|
19370
19678
|
if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
|
|
19371
|
-
import("../upgrade-
|
|
19372
|
-
const { createVersionManager } = await import("../version-manager-impl-
|
|
19679
|
+
import("../upgrade-j7tWzbZ0.js").then(async () => {
|
|
19680
|
+
const { createVersionManager } = await import("../version-manager-impl-CJLdocS1.js");
|
|
19373
19681
|
const vm = createVersionManager();
|
|
19374
19682
|
const result = await vm.checkForUpdates();
|
|
19375
19683
|
if (result.updateAvailable) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createVersionManager } from "./version-manager-impl-
|
|
1
|
+
import { createVersionManager } from "./version-manager-impl-mBbvaQL2.js";
|
|
2
2
|
import { execSync, spawn } from "child_process";
|
|
3
3
|
import * as readline from "readline";
|
|
4
4
|
|
|
@@ -123,4 +123,4 @@ function registerUpgradeCommand(program) {
|
|
|
123
123
|
|
|
124
124
|
//#endregion
|
|
125
125
|
export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
|
|
126
|
-
//# sourceMappingURL=upgrade-
|
|
126
|
+
//# sourceMappingURL=upgrade-4j5rZskl.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./config-schema-C9tTMcm1.js";
|
|
2
|
-
import "./version-manager-impl-
|
|
3
|
-
import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-
|
|
2
|
+
import "./version-manager-impl-mBbvaQL2.js";
|
|
3
|
+
import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-4j5rZskl.js";
|
|
4
4
|
|
|
5
5
|
export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SUPPORTED_CONFIG_FORMAT_VERSIONS, SUPPORTED_TASK_GRAPH_VERSIONS } from "./config-schema-C9tTMcm1.js";
|
|
2
2
|
import { createRequire } from "module";
|
|
3
|
-
import
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import path, { dirname, resolve } from "path";
|
|
4
5
|
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
6
|
import os from "os";
|
|
6
7
|
import https from "https";
|
|
@@ -334,13 +335,25 @@ var VersionManagerImpl = class {
|
|
|
334
335
|
}
|
|
335
336
|
/**
|
|
336
337
|
* Read the current package version from the bundled package.json.
|
|
338
|
+
* Tries multiple relative paths because the bundler may place this chunk
|
|
339
|
+
* at different depths (e.g. dist/version-manager-impl-xxx.js vs
|
|
340
|
+
* src/modules/version-manager/version-manager-impl.ts).
|
|
337
341
|
* Falls back to '0.0.0' if the file is unreadable.
|
|
338
342
|
*/
|
|
339
343
|
getCurrentVersion() {
|
|
340
344
|
try {
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
345
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
346
|
+
const candidates = [
|
|
347
|
+
resolve(__dirname, "../package.json"),
|
|
348
|
+
resolve(__dirname, "../../package.json"),
|
|
349
|
+
resolve(__dirname, "../../../package.json")
|
|
350
|
+
];
|
|
351
|
+
for (const candidate of candidates) try {
|
|
352
|
+
const raw = readFileSync(candidate, "utf-8");
|
|
353
|
+
const pkg = JSON.parse(raw);
|
|
354
|
+
if (pkg.name === "substrate-ai" && typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
355
|
+
} catch {}
|
|
356
|
+
return "0.0.0";
|
|
344
357
|
} catch {
|
|
345
358
|
return "0.0.0";
|
|
346
359
|
}
|
|
@@ -377,12 +390,12 @@ var VersionManagerImpl = class {
|
|
|
377
390
|
if (!forceRefresh) {
|
|
378
391
|
const cached = this.cache.read();
|
|
379
392
|
if (cached !== null) {
|
|
380
|
-
const updateAvailable = cached.latestVersion !==
|
|
393
|
+
const updateAvailable = cached.latestVersion !== currentVersion;
|
|
381
394
|
return {
|
|
382
|
-
currentVersion
|
|
395
|
+
currentVersion,
|
|
383
396
|
latestVersion: cached.latestVersion,
|
|
384
397
|
updateAvailable,
|
|
385
|
-
isBreaking: this.updateChecker.isBreaking(
|
|
398
|
+
isBreaking: this.updateChecker.isBreaking(currentVersion, cached.latestVersion),
|
|
386
399
|
changelog: this.updateChecker.getChangelog(cached.latestVersion)
|
|
387
400
|
};
|
|
388
401
|
}
|
|
@@ -482,4 +495,4 @@ function createVersionManager(deps = {}) {
|
|
|
482
495
|
|
|
483
496
|
//#endregion
|
|
484
497
|
export { VersionManagerImpl, createVersionManager, defaultConfigMigrator };
|
|
485
|
-
//# sourceMappingURL=version-manager-impl-
|
|
498
|
+
//# sourceMappingURL=version-manager-impl-mBbvaQL2.js.map
|