substrate-ai 0.2.20 → 0.2.23
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 +349 -133
- package/dist/{event-bus-BMxhfxfT.js → errors-CswS7Mzg.js} +93 -715
- package/dist/event-bus-CAvDMst7.js +734 -0
- package/dist/{experimenter-prkFLFPw.js → experimenter-bc40oi8p.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/{operational-Dq4IfJzE.js → operational-CnMlvWqc.js} +47 -2
- package/dist/{run-CcWb6Kb-.js → run-BaAws8IQ.js} +950 -200
- package/dist/run-BenC8JuM.js +7 -0
- package/package.json +1 -1
- package/packs/bmad/prompts/analysis-step-1-vision.md +4 -1
- package/packs/bmad/prompts/test-expansion.md +65 -0
- package/packs/bmad/prompts/test-plan.md +41 -0
- package/dist/errors-BPqtzQ4U.js +0 -111
- package/dist/run-C5zfaWYN.js +0 -7
package/dist/cli/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
2
|
+
import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-BaAws8IQ.js";
|
|
3
3
|
import { createLogger, deepMask } from "../logger-D2fS2ccL.js";
|
|
4
|
-
import { AdapterRegistry,
|
|
4
|
+
import { AdapterRegistry, ConfigError, ConfigIncompatibleFormatError } from "../errors-CswS7Mzg.js";
|
|
5
5
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "../version-manager-impl-CZ6KF1Ds.js";
|
|
6
|
-
import {
|
|
7
|
-
import { addTokenUsage, createDecision, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-Dq4cAA2L.js";
|
|
8
|
-
import { EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-
|
|
6
|
+
import { createEventBus } from "../event-bus-CAvDMst7.js";
|
|
7
|
+
import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-Dq4cAA2L.js";
|
|
8
|
+
import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-CnMlvWqc.js";
|
|
9
9
|
import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-CtmrZrHS.js";
|
|
10
10
|
import { registerUpgradeCommand } from "../upgrade-CjjAx5kD.js";
|
|
11
11
|
import { Command } from "commander";
|
|
@@ -207,8 +207,7 @@ function registerAdaptersCommand(program, version, registry) {
|
|
|
207
207
|
const adaptersCmd = program.command("adapters").description("Manage and inspect CLI agent adapters");
|
|
208
208
|
adaptersCmd.command("list").description("List all known adapters with availability status").option("--output-format <format>", "Output format: table (default) or json", "table").option("--verbose", "Show additional detail in output", false).action(async (opts) => {
|
|
209
209
|
const outputFormat = opts.outputFormat;
|
|
210
|
-
const
|
|
211
|
-
const report = await reg.discoverAndRegister();
|
|
210
|
+
const report = await registry.discoverAndRegister();
|
|
212
211
|
if (outputFormat === "json") {
|
|
213
212
|
const jsonData = report.results.map((r) => healthResultToJson(r.adapterId, r.displayName, r.healthResult));
|
|
214
213
|
const output = buildJsonOutput("substrate adapters list", jsonData, version);
|
|
@@ -231,8 +230,7 @@ function registerAdaptersCommand(program, version, registry) {
|
|
|
231
230
|
});
|
|
232
231
|
adaptersCmd.command("check").description("Run health checks on all adapters and verify headless mode").option("--output-format <format>", "Output format: table (default) or json", "table").option("--verbose", "Show additional detail including error messages", false).action(async (opts) => {
|
|
233
232
|
const outputFormat = opts.outputFormat;
|
|
234
|
-
const
|
|
235
|
-
const report = await reg.discoverAndRegister();
|
|
233
|
+
const report = await registry.discoverAndRegister();
|
|
236
234
|
const noneInstalled = report.results.length > 0 && report.results.every((r) => isNotInstalled(r.healthResult));
|
|
237
235
|
if (outputFormat === "json") {
|
|
238
236
|
const jsonData = report.results.map((r) => healthResultToJson(r.adapterId, r.displayName, r.healthResult));
|
|
@@ -324,7 +322,7 @@ const DEFAULT_CONFIG = {
|
|
|
324
322
|
|
|
325
323
|
//#endregion
|
|
326
324
|
//#region src/cli/commands/init.ts
|
|
327
|
-
const logger$
|
|
325
|
+
const logger$17 = createLogger("init");
|
|
328
326
|
const __dirname = dirname(new URL(import.meta.url).pathname);
|
|
329
327
|
const INIT_EXIT_SUCCESS = 0;
|
|
330
328
|
const INIT_EXIT_ERROR = 1;
|
|
@@ -345,7 +343,7 @@ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
|
|
|
345
343
|
const version = resolveBmadMethodVersion();
|
|
346
344
|
if (force && bmadExists) process.stderr.write(`Warning: Replacing existing _bmad/ framework with bmad-method@${version}\n`);
|
|
347
345
|
process.stdout.write(`Scaffolding BMAD framework from bmad-method@${version}\n`);
|
|
348
|
-
logger$
|
|
346
|
+
logger$17.info({
|
|
349
347
|
version,
|
|
350
348
|
dest: bmadDest
|
|
351
349
|
}, "Scaffolding BMAD framework");
|
|
@@ -355,7 +353,7 @@ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
|
|
|
355
353
|
const destDir = join(bmadDest, dir);
|
|
356
354
|
mkdirSync(destDir, { recursive: true });
|
|
357
355
|
cpSync(srcDir, destDir, { recursive: true });
|
|
358
|
-
logger$
|
|
356
|
+
logger$17.info({
|
|
359
357
|
dir,
|
|
360
358
|
dest: destDir
|
|
361
359
|
}, "Scaffolded BMAD framework directory");
|
|
@@ -374,7 +372,7 @@ async function scaffoldBmadFramework(projectRoot, force, outputFormat) {
|
|
|
374
372
|
"document_output_language: English"
|
|
375
373
|
].join("\n") + "\n";
|
|
376
374
|
await writeFile(configFile, configStub, "utf8");
|
|
377
|
-
logger$
|
|
375
|
+
logger$17.info({ configFile }, "Generated _bmad/_config/config.yaml stub");
|
|
378
376
|
}
|
|
379
377
|
}
|
|
380
378
|
const CLAUDE_MD_START_MARKER = "<!-- substrate:start -->";
|
|
@@ -389,7 +387,7 @@ async function scaffoldClaudeMd(projectRoot) {
|
|
|
389
387
|
try {
|
|
390
388
|
sectionContent = await readFile(templatePath, "utf8");
|
|
391
389
|
} catch {
|
|
392
|
-
logger$
|
|
390
|
+
logger$17.warn({ templatePath }, "CLAUDE.md substrate section template not found; skipping");
|
|
393
391
|
return;
|
|
394
392
|
}
|
|
395
393
|
if (!sectionContent.endsWith("\n")) sectionContent += "\n";
|
|
@@ -407,7 +405,7 @@ async function scaffoldClaudeMd(projectRoot) {
|
|
|
407
405
|
newContent = existingContent + separator + sectionContent;
|
|
408
406
|
}
|
|
409
407
|
await writeFile(claudeMdPath, newContent, "utf8");
|
|
410
|
-
logger$
|
|
408
|
+
logger$17.info({ claudeMdPath }, "Wrote substrate section to CLAUDE.md");
|
|
411
409
|
}
|
|
412
410
|
async function scaffoldStatuslineScript(projectRoot) {
|
|
413
411
|
const pkgRoot = findPackageRoot(__dirname);
|
|
@@ -418,7 +416,7 @@ async function scaffoldStatuslineScript(projectRoot) {
|
|
|
418
416
|
try {
|
|
419
417
|
content = await readFile(templatePath, "utf8");
|
|
420
418
|
} catch {
|
|
421
|
-
logger$
|
|
419
|
+
logger$17.warn({ templatePath }, "statusline.sh template not found; skipping");
|
|
422
420
|
return;
|
|
423
421
|
}
|
|
424
422
|
const claudeDir = join(projectRoot, ".claude");
|
|
@@ -426,7 +424,7 @@ async function scaffoldStatuslineScript(projectRoot) {
|
|
|
426
424
|
mkdirSync(claudeDir, { recursive: true });
|
|
427
425
|
await writeFile(statuslinePath, content, "utf8");
|
|
428
426
|
chmodSync(statuslinePath, 493);
|
|
429
|
-
logger$
|
|
427
|
+
logger$17.info({ statuslinePath }, "Wrote .claude/statusline.sh");
|
|
430
428
|
}
|
|
431
429
|
async function scaffoldClaudeSettings(projectRoot) {
|
|
432
430
|
const claudeDir = join(projectRoot, ".claude");
|
|
@@ -442,7 +440,7 @@ async function scaffoldClaudeSettings(projectRoot) {
|
|
|
442
440
|
if (!merged["$schema"]) merged["$schema"] = "https://json.schemastore.org/claude-code-settings.json";
|
|
443
441
|
mkdirSync(claudeDir, { recursive: true });
|
|
444
442
|
await writeFile(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
445
|
-
logger$
|
|
443
|
+
logger$17.info({ settingsPath }, "Wrote substrate settings to .claude/settings.json");
|
|
446
444
|
}
|
|
447
445
|
function resolveBmadMethodInstallerLibPath(fromDir = __dirname) {
|
|
448
446
|
try {
|
|
@@ -512,7 +510,7 @@ async function compileBmadAgents(bmadDir) {
|
|
|
512
510
|
writeFileSync(mdPath, result.xml, "utf-8");
|
|
513
511
|
compiled++;
|
|
514
512
|
} catch (compileErr) {
|
|
515
|
-
logger$
|
|
513
|
+
logger$17.debug({
|
|
516
514
|
err: compileErr,
|
|
517
515
|
file
|
|
518
516
|
}, "Failed to compile agent YAML");
|
|
@@ -533,9 +531,9 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
533
531
|
const _require = createRequire(join(__dirname, "synthetic.js"));
|
|
534
532
|
try {
|
|
535
533
|
const compiledCount = await compileBmadAgents(bmadDir);
|
|
536
|
-
if (compiledCount > 0) logger$
|
|
534
|
+
if (compiledCount > 0) logger$17.info({ compiledCount }, "Compiled agent YAML files to MD");
|
|
537
535
|
} catch (compileErr) {
|
|
538
|
-
logger$
|
|
536
|
+
logger$17.warn({ err: compileErr }, "Agent compilation failed; agent commands may be incomplete");
|
|
539
537
|
}
|
|
540
538
|
const { AgentCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "agent-command-generator.js"));
|
|
541
539
|
const { WorkflowCommandGenerator } = _require(join(installerLibPath, "ide", "shared", "workflow-command-generator.js"));
|
|
@@ -547,7 +545,7 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
547
545
|
const manifestGen = new ManifestGenerator();
|
|
548
546
|
await manifestGen.generateManifests(bmadDir, allModules, [], { ides: ["claude-code"] });
|
|
549
547
|
} catch (manifestErr) {
|
|
550
|
-
logger$
|
|
548
|
+
logger$17.warn({ err: manifestErr }, "ManifestGenerator failed; workflow/task commands may be incomplete");
|
|
551
549
|
}
|
|
552
550
|
const commandsDir = join(projectRoot, ".claude", "commands");
|
|
553
551
|
mkdirSync(commandsDir, { recursive: true });
|
|
@@ -563,7 +561,7 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
563
561
|
const taskToolCount = await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts);
|
|
564
562
|
const total = agentCount + workflowCount + taskToolCount;
|
|
565
563
|
if (outputFormat !== "json") process.stdout.write(`Generated ${String(total)} Claude Code commands (${String(agentCount)} agents, ${String(workflowCount)} workflows, ${String(taskToolCount)} tasks/tools)\n`);
|
|
566
|
-
logger$
|
|
564
|
+
logger$17.info({
|
|
567
565
|
agentCount,
|
|
568
566
|
workflowCount,
|
|
569
567
|
taskToolCount,
|
|
@@ -573,7 +571,7 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
|
|
|
573
571
|
} catch (err) {
|
|
574
572
|
const msg = err instanceof Error ? err.message : String(err);
|
|
575
573
|
if (outputFormat !== "json") process.stderr.write(`Warning: .claude/commands/ generation failed: ${msg}\n`);
|
|
576
|
-
logger$
|
|
574
|
+
logger$17.warn({ err }, "scaffoldClaudeCommands failed; init continues");
|
|
577
575
|
}
|
|
578
576
|
}
|
|
579
577
|
const PROVIDER_DEFAULTS = DEFAULT_CONFIG.providers;
|
|
@@ -641,13 +639,14 @@ async function runInitAction(options) {
|
|
|
641
639
|
if (outputFormat !== "json") process.stdout.write(` .substrate/ directory already exists at ${substrateDir}\n`);
|
|
642
640
|
}
|
|
643
641
|
if (outputFormat !== "json") process.stdout.write("\n Discovering installed AI agents...\n");
|
|
644
|
-
const registry = options.registry
|
|
642
|
+
const registry = options.registry;
|
|
643
|
+
if (!registry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
|
|
645
644
|
let discoveryReport;
|
|
646
645
|
try {
|
|
647
646
|
discoveryReport = await registry.discoverAndRegister();
|
|
648
647
|
} catch (err) {
|
|
649
648
|
const message = err instanceof Error ? err.message : String(err);
|
|
650
|
-
logger$
|
|
649
|
+
logger$17.error({ err }, "Adapter discovery failed");
|
|
651
650
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, `Adapter discovery failed: ${message}`) + "\n");
|
|
652
651
|
else process.stderr.write(` Error: adapter discovery failed — ${message}\n`);
|
|
653
652
|
return INIT_EXIT_ERROR;
|
|
@@ -696,12 +695,12 @@ async function runInitAction(options) {
|
|
|
696
695
|
return INIT_EXIT_ERROR;
|
|
697
696
|
}
|
|
698
697
|
if (force && existsSync(localManifest)) {
|
|
699
|
-
logger$
|
|
698
|
+
logger$17.info({ pack: packName }, "Replacing existing pack with bundled version");
|
|
700
699
|
process.stderr.write(`Warning: Replacing existing pack '${packName}' with bundled version\n`);
|
|
701
700
|
}
|
|
702
701
|
mkdirSync(dirname(packPath), { recursive: true });
|
|
703
702
|
cpSync(bundledPackPath, packPath, { recursive: true });
|
|
704
|
-
logger$
|
|
703
|
+
logger$17.info({
|
|
705
704
|
pack: packName,
|
|
706
705
|
dest: packPath
|
|
707
706
|
}, "Scaffolded methodology pack");
|
|
@@ -754,7 +753,7 @@ async function runInitAction(options) {
|
|
|
754
753
|
const msg = err instanceof Error ? err.message : String(err);
|
|
755
754
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
756
755
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
757
|
-
logger$
|
|
756
|
+
logger$17.error({ err }, "init failed");
|
|
758
757
|
return INIT_EXIT_ERROR;
|
|
759
758
|
}
|
|
760
759
|
}
|
|
@@ -800,7 +799,7 @@ function formatUnsupportedVersionError(formatType, version, supported) {
|
|
|
800
799
|
|
|
801
800
|
//#endregion
|
|
802
801
|
//#region src/modules/config/config-system-impl.ts
|
|
803
|
-
const logger$
|
|
802
|
+
const logger$16 = createLogger("config");
|
|
804
803
|
function deepMerge(base, override) {
|
|
805
804
|
const result = { ...base };
|
|
806
805
|
for (const [key, val] of Object.entries(override)) if (val !== null && val !== void 0 && typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) result[key] = deepMerge(result[key], val);
|
|
@@ -845,7 +844,7 @@ function readEnvOverrides() {
|
|
|
845
844
|
}
|
|
846
845
|
const parsed = PartialSubstrateConfigSchema.safeParse(overrides);
|
|
847
846
|
if (!parsed.success) {
|
|
848
|
-
logger$
|
|
847
|
+
logger$16.warn({ errors: parsed.error.issues }, "Invalid environment variable overrides ignored");
|
|
849
848
|
return {};
|
|
850
849
|
}
|
|
851
850
|
return parsed.data;
|
|
@@ -909,7 +908,7 @@ var ConfigSystemImpl = class {
|
|
|
909
908
|
throw new ConfigError(`Configuration validation failed:\n${issues}`, { issues: result.error.issues });
|
|
910
909
|
}
|
|
911
910
|
this._config = result.data;
|
|
912
|
-
logger$
|
|
911
|
+
logger$16.debug("Configuration loaded successfully");
|
|
913
912
|
}
|
|
914
913
|
getConfig() {
|
|
915
914
|
if (this._config === null) throw new ConfigError("Configuration has not been loaded. Call load() before getConfig().", {});
|
|
@@ -972,7 +971,7 @@ var ConfigSystemImpl = class {
|
|
|
972
971
|
if (version !== void 0 && typeof version === "string" && !isVersionSupported(version, SUPPORTED_CONFIG_FORMAT_VERSIONS)) if (defaultConfigMigrator.canMigrate(version, CURRENT_CONFIG_FORMAT_VERSION)) {
|
|
973
972
|
const migrationOutput = defaultConfigMigrator.migrate(rawObj, version, CURRENT_CONFIG_FORMAT_VERSION, filePath);
|
|
974
973
|
if (migrationOutput.result.success) {
|
|
975
|
-
logger$
|
|
974
|
+
logger$16.info({
|
|
976
975
|
from: version,
|
|
977
976
|
to: CURRENT_CONFIG_FORMAT_VERSION,
|
|
978
977
|
backup: migrationOutput.result.backupPath
|
|
@@ -1015,7 +1014,7 @@ function createConfigSystem(options = {}) {
|
|
|
1015
1014
|
|
|
1016
1015
|
//#endregion
|
|
1017
1016
|
//#region src/cli/commands/config.ts
|
|
1018
|
-
const logger$
|
|
1017
|
+
const logger$15 = createLogger("config-cmd");
|
|
1019
1018
|
const CONFIG_EXIT_SUCCESS = 0;
|
|
1020
1019
|
const CONFIG_EXIT_ERROR = 1;
|
|
1021
1020
|
const CONFIG_EXIT_INVALID = 2;
|
|
@@ -1041,7 +1040,7 @@ async function runConfigShow(opts = {}) {
|
|
|
1041
1040
|
return CONFIG_EXIT_INVALID;
|
|
1042
1041
|
}
|
|
1043
1042
|
const message = err instanceof Error ? err.message : String(err);
|
|
1044
|
-
logger$
|
|
1043
|
+
logger$15.error({ err }, "Failed to load configuration");
|
|
1045
1044
|
process.stderr.write(` Error loading configuration: ${message}\n`);
|
|
1046
1045
|
return CONFIG_EXIT_ERROR;
|
|
1047
1046
|
}
|
|
@@ -1115,7 +1114,7 @@ async function runConfigExport(opts = {}) {
|
|
|
1115
1114
|
return CONFIG_EXIT_INVALID;
|
|
1116
1115
|
}
|
|
1117
1116
|
const message = err instanceof Error ? err.message : String(err);
|
|
1118
|
-
logger$
|
|
1117
|
+
logger$15.error({ err }, "Failed to load configuration");
|
|
1119
1118
|
process.stderr.write(`Error loading configuration: ${message}\n`);
|
|
1120
1119
|
return CONFIG_EXIT_ERROR;
|
|
1121
1120
|
}
|
|
@@ -1269,9 +1268,9 @@ function registerConfigCommand(program, _version) {
|
|
|
1269
1268
|
|
|
1270
1269
|
//#endregion
|
|
1271
1270
|
//#region src/cli/commands/resume.ts
|
|
1272
|
-
const logger$
|
|
1271
|
+
const logger$14 = createLogger("resume-cmd");
|
|
1273
1272
|
async function runResumeAction(options) {
|
|
1274
|
-
const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName } = options;
|
|
1273
|
+
const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, registry } = options;
|
|
1275
1274
|
if (stopAfter !== void 0 && !VALID_PHASES.includes(stopAfter)) {
|
|
1276
1275
|
const errorMsg = `Invalid phase: "${stopAfter}". Valid phases: ${VALID_PHASES.join(", ")}`;
|
|
1277
1276
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
@@ -1345,13 +1344,14 @@ async function runResumeAction(options) {
|
|
|
1345
1344
|
concurrency,
|
|
1346
1345
|
outputFormat,
|
|
1347
1346
|
existingRunId: runId,
|
|
1348
|
-
projectRoot
|
|
1347
|
+
projectRoot,
|
|
1348
|
+
registry
|
|
1349
1349
|
});
|
|
1350
1350
|
} catch (err) {
|
|
1351
1351
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1352
1352
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1353
1353
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
1354
|
-
logger$
|
|
1354
|
+
logger$14.error({ err }, "auto resume failed");
|
|
1355
1355
|
return 1;
|
|
1356
1356
|
} finally {
|
|
1357
1357
|
try {
|
|
@@ -1360,7 +1360,7 @@ async function runResumeAction(options) {
|
|
|
1360
1360
|
}
|
|
1361
1361
|
}
|
|
1362
1362
|
async function runFullPipelineFromPhase(options) {
|
|
1363
|
-
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, existingRunId, projectRoot } = options;
|
|
1363
|
+
const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, existingRunId, projectRoot, registry: injectedRegistry } = options;
|
|
1364
1364
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
1365
1365
|
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
1366
1366
|
try {
|
|
@@ -1380,11 +1380,10 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1380
1380
|
}
|
|
1381
1381
|
const eventBus = createEventBus();
|
|
1382
1382
|
const contextCompiler = createContextCompiler({ db });
|
|
1383
|
-
|
|
1384
|
-
await adapterRegistry.discoverAndRegister();
|
|
1383
|
+
if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
|
|
1385
1384
|
const dispatcher = createDispatcher({
|
|
1386
1385
|
eventBus,
|
|
1387
|
-
adapterRegistry
|
|
1386
|
+
adapterRegistry: injectedRegistry
|
|
1388
1387
|
});
|
|
1389
1388
|
const phaseDeps = {
|
|
1390
1389
|
db,
|
|
@@ -1502,7 +1501,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1502
1501
|
});
|
|
1503
1502
|
}
|
|
1504
1503
|
} catch (err) {
|
|
1505
|
-
logger$
|
|
1504
|
+
logger$14.warn({ err }, "Failed to record token usage");
|
|
1506
1505
|
}
|
|
1507
1506
|
});
|
|
1508
1507
|
const storyDecisions = db.prepare(`SELECT description FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`).all(runId);
|
|
@@ -1561,7 +1560,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1561
1560
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1562
1561
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1563
1562
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
1564
|
-
logger$
|
|
1563
|
+
logger$14.error({ err }, "pipeline from phase failed");
|
|
1565
1564
|
return 1;
|
|
1566
1565
|
} finally {
|
|
1567
1566
|
try {
|
|
@@ -1569,7 +1568,7 @@ async function runFullPipelineFromPhase(options) {
|
|
|
1569
1568
|
} catch {}
|
|
1570
1569
|
}
|
|
1571
1570
|
}
|
|
1572
|
-
function registerResumeCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
1571
|
+
function registerResumeCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
1573
1572
|
program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
|
|
1574
1573
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
1575
1574
|
const exitCode = await runResumeAction({
|
|
@@ -1578,7 +1577,8 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
1578
1577
|
outputFormat,
|
|
1579
1578
|
projectRoot: opts.projectRoot,
|
|
1580
1579
|
concurrency: opts.concurrency,
|
|
1581
|
-
pack: opts.pack
|
|
1580
|
+
pack: opts.pack,
|
|
1581
|
+
registry
|
|
1582
1582
|
});
|
|
1583
1583
|
process.exitCode = exitCode;
|
|
1584
1584
|
});
|
|
@@ -1586,7 +1586,7 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
1586
1586
|
|
|
1587
1587
|
//#endregion
|
|
1588
1588
|
//#region src/cli/commands/status.ts
|
|
1589
|
-
const logger$
|
|
1589
|
+
const logger$13 = createLogger("status-cmd");
|
|
1590
1590
|
async function runStatusAction(options) {
|
|
1591
1591
|
const { outputFormat, runId, projectRoot } = options;
|
|
1592
1592
|
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
@@ -1663,7 +1663,7 @@ async function runStatusAction(options) {
|
|
|
1663
1663
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1664
1664
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1665
1665
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
1666
|
-
logger$
|
|
1666
|
+
logger$13.error({ err }, "status action failed");
|
|
1667
1667
|
return 1;
|
|
1668
1668
|
} finally {
|
|
1669
1669
|
try {
|
|
@@ -2087,7 +2087,7 @@ Analyze thoroughly and return ONLY the JSON array with no additional text.`;
|
|
|
2087
2087
|
|
|
2088
2088
|
//#endregion
|
|
2089
2089
|
//#region src/cli/commands/amend.ts
|
|
2090
|
-
const logger$
|
|
2090
|
+
const logger$12 = createLogger("amend-cmd");
|
|
2091
2091
|
/**
|
|
2092
2092
|
* Detect and apply supersessions after a phase completes in an amendment run.
|
|
2093
2093
|
*
|
|
@@ -2118,7 +2118,7 @@ function runPostPhaseSupersessionDetection(db, amendmentRunId, currentPhase, han
|
|
|
2118
2118
|
});
|
|
2119
2119
|
} catch (err) {
|
|
2120
2120
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2121
|
-
logger$
|
|
2121
|
+
logger$12.warn({
|
|
2122
2122
|
err,
|
|
2123
2123
|
originalId: parentMatch.id,
|
|
2124
2124
|
supersedingId: newDec.id
|
|
@@ -2127,7 +2127,7 @@ function runPostPhaseSupersessionDetection(db, amendmentRunId, currentPhase, han
|
|
|
2127
2127
|
}
|
|
2128
2128
|
}
|
|
2129
2129
|
async function runAmendAction(options) {
|
|
2130
|
-
const { concept: conceptArg, conceptFile, runId: specifiedRunId, stopAfter, from: startPhase, projectRoot, pack: packName } = options;
|
|
2130
|
+
const { concept: conceptArg, conceptFile, runId: specifiedRunId, stopAfter, from: startPhase, projectRoot, pack: packName, registry: injectedRegistry } = options;
|
|
2131
2131
|
let concept;
|
|
2132
2132
|
if (conceptFile !== void 0 && conceptFile !== "") try {
|
|
2133
2133
|
concept = await readFile(conceptFile, "utf-8");
|
|
@@ -2214,11 +2214,10 @@ async function runAmendAction(options) {
|
|
|
2214
2214
|
}
|
|
2215
2215
|
const eventBus = createEventBus();
|
|
2216
2216
|
const contextCompiler = createContextCompiler({ db });
|
|
2217
|
-
|
|
2218
|
-
await adapterRegistry.discoverAndRegister();
|
|
2217
|
+
if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
|
|
2219
2218
|
const dispatcher = createDispatcher({
|
|
2220
2219
|
eventBus,
|
|
2221
|
-
adapterRegistry
|
|
2220
|
+
adapterRegistry: injectedRegistry
|
|
2222
2221
|
});
|
|
2223
2222
|
const phaseDeps = {
|
|
2224
2223
|
db,
|
|
@@ -2253,7 +2252,7 @@ async function runAmendAction(options) {
|
|
|
2253
2252
|
for (let i = startIdx; i < phaseOrder.length; i++) {
|
|
2254
2253
|
const currentPhase = phaseOrder[i];
|
|
2255
2254
|
const amendmentContext = handler.loadContextForPhase(currentPhase);
|
|
2256
|
-
logger$
|
|
2255
|
+
logger$12.info({
|
|
2257
2256
|
phase: currentPhase,
|
|
2258
2257
|
amendmentContextLen: amendmentContext.length
|
|
2259
2258
|
}, "Amendment context loaded for phase");
|
|
@@ -2373,7 +2372,7 @@ async function runAmendAction(options) {
|
|
|
2373
2372
|
} catch (err) {
|
|
2374
2373
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2375
2374
|
process.stderr.write(`Error: ${msg}\n`);
|
|
2376
|
-
logger$
|
|
2375
|
+
logger$12.error({ err }, "amend failed");
|
|
2377
2376
|
return 1;
|
|
2378
2377
|
} finally {
|
|
2379
2378
|
try {
|
|
@@ -2381,7 +2380,7 @@ async function runAmendAction(options) {
|
|
|
2381
2380
|
} catch {}
|
|
2382
2381
|
}
|
|
2383
2382
|
}
|
|
2384
|
-
function registerAmendCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
2383
|
+
function registerAmendCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
2385
2384
|
program.command("amend").description("Run an amendment pipeline against a completed run and an existing run").option("--concept <text>", "Amendment concept description (inline)").option("--concept-file <path>", "Path to concept file").option("--run-id <id>", "Parent run ID (defaults to latest completed run)").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--from <phase>", "Start pipeline from this phase").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) => {
|
|
2386
2385
|
const exitCode = await runAmendAction({
|
|
2387
2386
|
concept: opts.concept,
|
|
@@ -2390,7 +2389,8 @@ function registerAmendCommand(program, _version = "0.0.0", projectRoot = process
|
|
|
2390
2389
|
stopAfter: opts.stopAfter,
|
|
2391
2390
|
from: opts.from,
|
|
2392
2391
|
projectRoot: opts.projectRoot,
|
|
2393
|
-
pack: opts.pack
|
|
2392
|
+
pack: opts.pack,
|
|
2393
|
+
registry
|
|
2394
2394
|
});
|
|
2395
2395
|
process.exitCode = exitCode;
|
|
2396
2396
|
});
|
|
@@ -2828,7 +2828,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2828
2828
|
try {
|
|
2829
2829
|
const { createExperimenter } = await import(
|
|
2830
2830
|
/* @vite-ignore */
|
|
2831
|
-
"../experimenter-
|
|
2831
|
+
"../experimenter-bc40oi8p.js"
|
|
2832
2832
|
);
|
|
2833
2833
|
const { getLatestRun: getLatest } = await import(
|
|
2834
2834
|
/* @vite-ignore */
|
|
@@ -2842,7 +2842,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2842
2842
|
const expDb = expDbWrapper.db;
|
|
2843
2843
|
const { runRunAction: runPipeline } = await import(
|
|
2844
2844
|
/* @vite-ignore */
|
|
2845
|
-
"../run-
|
|
2845
|
+
"../run-BenC8JuM.js"
|
|
2846
2846
|
);
|
|
2847
2847
|
const runStoryFn = async (opts) => {
|
|
2848
2848
|
const exitCode = await runPipeline({
|
|
@@ -3085,7 +3085,7 @@ function registerSupervisorCommand(program, _version = "0.0.0", projectRoot = pr
|
|
|
3085
3085
|
|
|
3086
3086
|
//#endregion
|
|
3087
3087
|
//#region src/cli/commands/metrics.ts
|
|
3088
|
-
const logger$
|
|
3088
|
+
const logger$11 = createLogger("metrics-cmd");
|
|
3089
3089
|
async function runMetricsAction(options) {
|
|
3090
3090
|
const { outputFormat, projectRoot, limit = 10, compare, tagBaseline, analysis } = options;
|
|
3091
3091
|
if (analysis !== void 0) {
|
|
@@ -3237,7 +3237,7 @@ async function runMetricsAction(options) {
|
|
|
3237
3237
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3238
3238
|
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
3239
3239
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
3240
|
-
logger$
|
|
3240
|
+
logger$11.error({ err }, "metrics action failed");
|
|
3241
3241
|
return 1;
|
|
3242
3242
|
} finally {
|
|
3243
3243
|
try {
|
|
@@ -3491,7 +3491,7 @@ function getPlanningCostTotal(db, sessionId) {
|
|
|
3491
3491
|
function getLatestSessionId(_db) {
|
|
3492
3492
|
return null;
|
|
3493
3493
|
}
|
|
3494
|
-
const logger$
|
|
3494
|
+
const logger$10 = createLogger("cost-cmd");
|
|
3495
3495
|
const COST_EXIT_SUCCESS = 0;
|
|
3496
3496
|
const COST_EXIT_ERROR = 1;
|
|
3497
3497
|
/**
|
|
@@ -3737,7 +3737,7 @@ async function runCostAction(options) {
|
|
|
3737
3737
|
} catch (err) {
|
|
3738
3738
|
const message = err instanceof Error ? err.message : String(err);
|
|
3739
3739
|
process.stderr.write(`Error: ${message}\n`);
|
|
3740
|
-
logger$
|
|
3740
|
+
logger$10.error({ err }, "runCostAction failed");
|
|
3741
3741
|
return COST_EXIT_ERROR;
|
|
3742
3742
|
} finally {
|
|
3743
3743
|
if (wrapper !== null) try {
|
|
@@ -3839,7 +3839,7 @@ function applyMonitorSchema(db) {
|
|
|
3839
3839
|
|
|
3840
3840
|
//#endregion
|
|
3841
3841
|
//#region src/persistence/monitor-database.ts
|
|
3842
|
-
const logger$
|
|
3842
|
+
const logger$9 = createLogger("persistence:monitor-db");
|
|
3843
3843
|
var MonitorDatabaseImpl = class {
|
|
3844
3844
|
_db = null;
|
|
3845
3845
|
_path;
|
|
@@ -3850,10 +3850,10 @@ var MonitorDatabaseImpl = class {
|
|
|
3850
3850
|
this._open();
|
|
3851
3851
|
}
|
|
3852
3852
|
_open() {
|
|
3853
|
-
logger$
|
|
3853
|
+
logger$9.info({ path: this._path }, "Opening monitor database");
|
|
3854
3854
|
this._db = new BetterSqlite3(this._path);
|
|
3855
3855
|
const walResult = this._db.pragma("journal_mode = WAL");
|
|
3856
|
-
if (walResult?.[0]?.journal_mode !== "wal") logger$
|
|
3856
|
+
if (walResult?.[0]?.journal_mode !== "wal") logger$9.warn({ result: walResult?.[0]?.journal_mode }, "Monitor DB: WAL pragma did not confirm wal mode");
|
|
3857
3857
|
this._db.pragma("synchronous = NORMAL");
|
|
3858
3858
|
this._db.pragma("busy_timeout = 5000");
|
|
3859
3859
|
this._db.pragma("foreign_keys = ON");
|
|
@@ -3888,7 +3888,7 @@ var MonitorDatabaseImpl = class {
|
|
|
3888
3888
|
total_retries = total_retries + @retries,
|
|
3889
3889
|
last_updated = @lastUpdated
|
|
3890
3890
|
`);
|
|
3891
|
-
logger$
|
|
3891
|
+
logger$9.info({ path: this._path }, "Monitor database ready");
|
|
3892
3892
|
}
|
|
3893
3893
|
_assertOpen() {
|
|
3894
3894
|
if (this._db === null) throw new Error("MonitorDatabase: connection is closed");
|
|
@@ -4037,7 +4037,7 @@ var MonitorDatabaseImpl = class {
|
|
|
4037
4037
|
const db = this._assertOpen();
|
|
4038
4038
|
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
4039
4039
|
const result = db.prepare("DELETE FROM task_metrics WHERE recorded_at < @cutoff").run({ cutoff });
|
|
4040
|
-
logger$
|
|
4040
|
+
logger$9.info({
|
|
4041
4041
|
cutoff,
|
|
4042
4042
|
deleted: result.changes
|
|
4043
4043
|
}, "Pruned old task_metrics rows");
|
|
@@ -4076,13 +4076,13 @@ var MonitorDatabaseImpl = class {
|
|
|
4076
4076
|
db.exec("ROLLBACK");
|
|
4077
4077
|
throw err;
|
|
4078
4078
|
}
|
|
4079
|
-
logger$
|
|
4079
|
+
logger$9.info("Rebuilt performance_aggregates from task_metrics");
|
|
4080
4080
|
}
|
|
4081
4081
|
resetAllData() {
|
|
4082
4082
|
const db = this._assertOpen();
|
|
4083
4083
|
db.exec("DELETE FROM task_metrics");
|
|
4084
4084
|
db.exec("DELETE FROM performance_aggregates");
|
|
4085
|
-
logger$
|
|
4085
|
+
logger$9.info({ path: this._path }, "Monitor data reset — all rows deleted");
|
|
4086
4086
|
}
|
|
4087
4087
|
getTaskMetricsDateRange() {
|
|
4088
4088
|
const db = this._assertOpen();
|
|
@@ -4099,7 +4099,7 @@ var MonitorDatabaseImpl = class {
|
|
|
4099
4099
|
if (this._db === null) return;
|
|
4100
4100
|
this._db.close();
|
|
4101
4101
|
this._db = null;
|
|
4102
|
-
logger$
|
|
4102
|
+
logger$9.info({ path: this._path }, "Monitor database closed");
|
|
4103
4103
|
}
|
|
4104
4104
|
/**
|
|
4105
4105
|
* Access the raw underlying database for testing purposes only.
|
|
@@ -4112,7 +4112,7 @@ var MonitorDatabaseImpl = class {
|
|
|
4112
4112
|
|
|
4113
4113
|
//#endregion
|
|
4114
4114
|
//#region src/modules/monitor/recommendation-engine.ts
|
|
4115
|
-
const logger$
|
|
4115
|
+
const logger$8 = createLogger("monitor:recommendations");
|
|
4116
4116
|
var RecommendationEngine = class {
|
|
4117
4117
|
_monitorDb;
|
|
4118
4118
|
_filters;
|
|
@@ -4145,7 +4145,7 @@ var RecommendationEngine = class {
|
|
|
4145
4145
|
const sinceDate = new Date(Date.now() - this._historyDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
4146
4146
|
const aggregates = this._monitorDb.getAggregates({ sinceDate });
|
|
4147
4147
|
if (aggregates.length === 0) {
|
|
4148
|
-
logger$
|
|
4148
|
+
logger$8.debug("No performance aggregates found — no recommendations to generate");
|
|
4149
4149
|
return [];
|
|
4150
4150
|
}
|
|
4151
4151
|
const byTaskType = new Map();
|
|
@@ -4210,7 +4210,7 @@ var RecommendationEngine = class {
|
|
|
4210
4210
|
if (confDiff !== 0) return confDiff;
|
|
4211
4211
|
return b.improvement_percentage - a.improvement_percentage;
|
|
4212
4212
|
});
|
|
4213
|
-
logger$
|
|
4213
|
+
logger$8.debug({ count: recommendations.length }, "Generated routing recommendations");
|
|
4214
4214
|
return recommendations;
|
|
4215
4215
|
}
|
|
4216
4216
|
/**
|
|
@@ -4376,7 +4376,7 @@ function generateMonitorReport(monitorDb, options = {}) {
|
|
|
4376
4376
|
|
|
4377
4377
|
//#endregion
|
|
4378
4378
|
//#region src/cli/commands/monitor.ts
|
|
4379
|
-
const logger$
|
|
4379
|
+
const logger$7 = createLogger("monitor-cmd");
|
|
4380
4380
|
const MONITOR_EXIT_SUCCESS = 0;
|
|
4381
4381
|
const MONITOR_EXIT_ERROR = 1;
|
|
4382
4382
|
/**
|
|
@@ -4579,7 +4579,7 @@ async function runMonitorReportAction(options) {
|
|
|
4579
4579
|
} catch (err) {
|
|
4580
4580
|
const message = err instanceof Error ? err.message : String(err);
|
|
4581
4581
|
process.stderr.write(`Error: ${message}\n`);
|
|
4582
|
-
logger$
|
|
4582
|
+
logger$7.error({ err }, "runMonitorReportAction failed");
|
|
4583
4583
|
return MONITOR_EXIT_ERROR;
|
|
4584
4584
|
} finally {
|
|
4585
4585
|
if (monitorDb !== null) try {
|
|
@@ -4641,7 +4641,7 @@ async function runMonitorStatusAction(options) {
|
|
|
4641
4641
|
} catch (err) {
|
|
4642
4642
|
const message = err instanceof Error ? err.message : String(err);
|
|
4643
4643
|
process.stderr.write(`Error: ${message}\n`);
|
|
4644
|
-
logger$
|
|
4644
|
+
logger$7.error({ err }, "runMonitorStatusAction failed");
|
|
4645
4645
|
return MONITOR_EXIT_ERROR;
|
|
4646
4646
|
} finally {
|
|
4647
4647
|
if (monitorDb !== null) try {
|
|
@@ -4676,7 +4676,7 @@ async function runMonitorResetAction(options) {
|
|
|
4676
4676
|
} catch (err) {
|
|
4677
4677
|
const message = err instanceof Error ? err.message : String(err);
|
|
4678
4678
|
process.stderr.write(`Error: ${message}\n`);
|
|
4679
|
-
logger$
|
|
4679
|
+
logger$7.error({ err }, "runMonitorResetAction failed");
|
|
4680
4680
|
return MONITOR_EXIT_ERROR;
|
|
4681
4681
|
} finally {
|
|
4682
4682
|
if (monitorDb !== null) try {
|
|
@@ -4724,7 +4724,7 @@ async function runMonitorRecommendationsAction(options) {
|
|
|
4724
4724
|
} catch (err) {
|
|
4725
4725
|
const message = err instanceof Error ? err.message : String(err);
|
|
4726
4726
|
process.stderr.write(`Error: ${message}\n`);
|
|
4727
|
-
logger$
|
|
4727
|
+
logger$7.error({ err }, "runMonitorRecommendationsAction failed");
|
|
4728
4728
|
return MONITOR_EXIT_ERROR;
|
|
4729
4729
|
} finally {
|
|
4730
4730
|
if (monitorDb !== null) try {
|
|
@@ -4802,7 +4802,7 @@ function registerMonitorCommand(program, version = "0.0.0", projectRoot = proces
|
|
|
4802
4802
|
|
|
4803
4803
|
//#endregion
|
|
4804
4804
|
//#region src/modules/git-worktree/git-worktree-manager-impl.ts
|
|
4805
|
-
const logger$
|
|
4805
|
+
const logger$6 = createLogger("git-worktree");
|
|
4806
4806
|
const BRANCH_PREFIX = "substrate/task-";
|
|
4807
4807
|
const DEFAULT_WORKTREE_BASE = ".substrate-worktrees";
|
|
4808
4808
|
var GitWorktreeManagerImpl = class {
|
|
@@ -4821,7 +4821,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4821
4821
|
this._db = db;
|
|
4822
4822
|
this._onTaskReady = ({ taskId }) => {
|
|
4823
4823
|
this._handleTaskReady(taskId).catch((err) => {
|
|
4824
|
-
logger$
|
|
4824
|
+
logger$6.error({
|
|
4825
4825
|
taskId,
|
|
4826
4826
|
err
|
|
4827
4827
|
}, "Unhandled error in _handleTaskReady");
|
|
@@ -4835,40 +4835,40 @@ var GitWorktreeManagerImpl = class {
|
|
|
4835
4835
|
};
|
|
4836
4836
|
}
|
|
4837
4837
|
async initialize() {
|
|
4838
|
-
logger$
|
|
4838
|
+
logger$6.info({ projectRoot: this._projectRoot }, "GitWorktreeManager.initialize()");
|
|
4839
4839
|
await this.verifyGitVersion();
|
|
4840
4840
|
const cleaned = await this.cleanupAllWorktrees();
|
|
4841
|
-
if (cleaned > 0) logger$
|
|
4841
|
+
if (cleaned > 0) logger$6.info({ cleaned }, "Recovered orphaned worktrees on startup");
|
|
4842
4842
|
this._eventBus.on("task:ready", this._onTaskReady);
|
|
4843
4843
|
this._eventBus.on("task:complete", this._onTaskComplete);
|
|
4844
4844
|
this._eventBus.on("task:failed", this._onTaskFailed);
|
|
4845
|
-
logger$
|
|
4845
|
+
logger$6.info("GitWorktreeManager initialized");
|
|
4846
4846
|
}
|
|
4847
4847
|
async shutdown() {
|
|
4848
|
-
logger$
|
|
4848
|
+
logger$6.info("GitWorktreeManager.shutdown()");
|
|
4849
4849
|
this._eventBus.off("task:ready", this._onTaskReady);
|
|
4850
4850
|
this._eventBus.off("task:complete", this._onTaskComplete);
|
|
4851
4851
|
this._eventBus.off("task:failed", this._onTaskFailed);
|
|
4852
4852
|
await this.cleanupAllWorktrees();
|
|
4853
|
-
logger$
|
|
4853
|
+
logger$6.info("GitWorktreeManager shutdown complete");
|
|
4854
4854
|
}
|
|
4855
4855
|
async _handleTaskReady(taskId) {
|
|
4856
|
-
logger$
|
|
4856
|
+
logger$6.debug({ taskId }, "task:ready — creating worktree");
|
|
4857
4857
|
try {
|
|
4858
4858
|
await this.createWorktree(taskId);
|
|
4859
4859
|
} catch (err) {
|
|
4860
|
-
logger$
|
|
4860
|
+
logger$6.error({
|
|
4861
4861
|
taskId,
|
|
4862
4862
|
err
|
|
4863
4863
|
}, "Failed to create worktree for task");
|
|
4864
4864
|
}
|
|
4865
4865
|
}
|
|
4866
4866
|
async _handleTaskDone(taskId) {
|
|
4867
|
-
logger$
|
|
4867
|
+
logger$6.debug({ taskId }, "task done — cleaning up worktree");
|
|
4868
4868
|
try {
|
|
4869
4869
|
await this.cleanupWorktree(taskId);
|
|
4870
4870
|
} catch (err) {
|
|
4871
|
-
logger$
|
|
4871
|
+
logger$6.warn({
|
|
4872
4872
|
taskId,
|
|
4873
4873
|
err
|
|
4874
4874
|
}, "Failed to cleanup worktree for task");
|
|
@@ -4878,7 +4878,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4878
4878
|
if (!taskId || taskId.trim().length === 0) throw new Error("createWorktree: taskId must be a non-empty string");
|
|
4879
4879
|
const branchName = BRANCH_PREFIX + taskId;
|
|
4880
4880
|
const worktreePath = this.getWorktreePath(taskId);
|
|
4881
|
-
logger$
|
|
4881
|
+
logger$6.debug({
|
|
4882
4882
|
taskId,
|
|
4883
4883
|
branchName,
|
|
4884
4884
|
worktreePath,
|
|
@@ -4898,7 +4898,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4898
4898
|
worktreePath,
|
|
4899
4899
|
createdAt
|
|
4900
4900
|
};
|
|
4901
|
-
logger$
|
|
4901
|
+
logger$6.info({
|
|
4902
4902
|
taskId,
|
|
4903
4903
|
branchName,
|
|
4904
4904
|
worktreePath
|
|
@@ -4908,7 +4908,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4908
4908
|
async cleanupWorktree(taskId) {
|
|
4909
4909
|
const branchName = BRANCH_PREFIX + taskId;
|
|
4910
4910
|
const worktreePath = this.getWorktreePath(taskId);
|
|
4911
|
-
logger$
|
|
4911
|
+
logger$6.debug({
|
|
4912
4912
|
taskId,
|
|
4913
4913
|
branchName,
|
|
4914
4914
|
worktreePath
|
|
@@ -4918,7 +4918,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4918
4918
|
await access$1(worktreePath);
|
|
4919
4919
|
worktreeExists = true;
|
|
4920
4920
|
} catch {
|
|
4921
|
-
logger$
|
|
4921
|
+
logger$6.debug({
|
|
4922
4922
|
taskId,
|
|
4923
4923
|
worktreePath
|
|
4924
4924
|
}, "cleanupWorktree: worktree does not exist, skipping removal");
|
|
@@ -4926,7 +4926,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4926
4926
|
if (worktreeExists) try {
|
|
4927
4927
|
await removeWorktree(worktreePath, this._projectRoot);
|
|
4928
4928
|
} catch (err) {
|
|
4929
|
-
logger$
|
|
4929
|
+
logger$6.warn({
|
|
4930
4930
|
taskId,
|
|
4931
4931
|
worktreePath,
|
|
4932
4932
|
err
|
|
@@ -4935,7 +4935,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
4935
4935
|
try {
|
|
4936
4936
|
await removeBranch(branchName, this._projectRoot);
|
|
4937
4937
|
} catch (err) {
|
|
4938
|
-
logger$
|
|
4938
|
+
logger$6.warn({
|
|
4939
4939
|
taskId,
|
|
4940
4940
|
branchName,
|
|
4941
4941
|
err
|
|
@@ -4945,13 +4945,13 @@ var GitWorktreeManagerImpl = class {
|
|
|
4945
4945
|
taskId,
|
|
4946
4946
|
branchName
|
|
4947
4947
|
});
|
|
4948
|
-
logger$
|
|
4948
|
+
logger$6.info({
|
|
4949
4949
|
taskId,
|
|
4950
4950
|
branchName
|
|
4951
4951
|
}, "Worktree cleaned up");
|
|
4952
4952
|
}
|
|
4953
4953
|
async cleanupAllWorktrees() {
|
|
4954
|
-
logger$
|
|
4954
|
+
logger$6.debug({ projectRoot: this._projectRoot }, "cleanupAllWorktrees");
|
|
4955
4955
|
const orphanedPaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
|
|
4956
4956
|
let cleaned = 0;
|
|
4957
4957
|
for (const worktreePath of orphanedPaths) {
|
|
@@ -4960,12 +4960,12 @@ var GitWorktreeManagerImpl = class {
|
|
|
4960
4960
|
try {
|
|
4961
4961
|
await removeWorktree(worktreePath, this._projectRoot);
|
|
4962
4962
|
worktreeRemoved = true;
|
|
4963
|
-
logger$
|
|
4963
|
+
logger$6.debug({
|
|
4964
4964
|
taskId,
|
|
4965
4965
|
worktreePath
|
|
4966
4966
|
}, "cleanupAllWorktrees: removed orphaned worktree");
|
|
4967
4967
|
} catch (err) {
|
|
4968
|
-
logger$
|
|
4968
|
+
logger$6.warn({
|
|
4969
4969
|
taskId,
|
|
4970
4970
|
worktreePath,
|
|
4971
4971
|
err
|
|
@@ -4975,12 +4975,12 @@ var GitWorktreeManagerImpl = class {
|
|
|
4975
4975
|
let branchRemoved = false;
|
|
4976
4976
|
try {
|
|
4977
4977
|
branchRemoved = await removeBranch(branchName, this._projectRoot);
|
|
4978
|
-
if (branchRemoved) logger$
|
|
4978
|
+
if (branchRemoved) logger$6.debug({
|
|
4979
4979
|
taskId,
|
|
4980
4980
|
branchName
|
|
4981
4981
|
}, "cleanupAllWorktrees: removed orphaned branch");
|
|
4982
4982
|
} catch (err) {
|
|
4983
|
-
logger$
|
|
4983
|
+
logger$6.warn({
|
|
4984
4984
|
taskId,
|
|
4985
4985
|
branchName,
|
|
4986
4986
|
err
|
|
@@ -4988,14 +4988,14 @@ var GitWorktreeManagerImpl = class {
|
|
|
4988
4988
|
}
|
|
4989
4989
|
if (worktreeRemoved) cleaned++;
|
|
4990
4990
|
}
|
|
4991
|
-
if (cleaned > 0) logger$
|
|
4991
|
+
if (cleaned > 0) logger$6.info({ cleaned }, "cleanupAllWorktrees: recovered orphaned worktrees");
|
|
4992
4992
|
return cleaned;
|
|
4993
4993
|
}
|
|
4994
4994
|
async detectConflicts(taskId, targetBranch = "main") {
|
|
4995
4995
|
if (!taskId || taskId.trim().length === 0) throw new Error("detectConflicts: taskId must be a non-empty string");
|
|
4996
4996
|
const branchName = BRANCH_PREFIX + taskId;
|
|
4997
4997
|
const worktreePath = this.getWorktreePath(taskId);
|
|
4998
|
-
logger$
|
|
4998
|
+
logger$6.debug({
|
|
4999
4999
|
taskId,
|
|
5000
5000
|
branchName,
|
|
5001
5001
|
targetBranch
|
|
@@ -5023,7 +5023,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
5023
5023
|
branch: branchName,
|
|
5024
5024
|
conflictingFiles: report.conflictingFiles
|
|
5025
5025
|
});
|
|
5026
|
-
logger$
|
|
5026
|
+
logger$6.info({
|
|
5027
5027
|
taskId,
|
|
5028
5028
|
hasConflicts: report.hasConflicts,
|
|
5029
5029
|
conflictCount: conflictingFiles.length
|
|
@@ -5033,14 +5033,14 @@ var GitWorktreeManagerImpl = class {
|
|
|
5033
5033
|
async mergeWorktree(taskId, targetBranch = "main") {
|
|
5034
5034
|
if (!taskId || taskId.trim().length === 0) throw new Error("mergeWorktree: taskId must be a non-empty string");
|
|
5035
5035
|
const branchName = BRANCH_PREFIX + taskId;
|
|
5036
|
-
logger$
|
|
5036
|
+
logger$6.debug({
|
|
5037
5037
|
taskId,
|
|
5038
5038
|
branchName,
|
|
5039
5039
|
targetBranch
|
|
5040
5040
|
}, "mergeWorktree");
|
|
5041
5041
|
const conflictReport = await this.detectConflicts(taskId, targetBranch);
|
|
5042
5042
|
if (conflictReport.hasConflicts) {
|
|
5043
|
-
logger$
|
|
5043
|
+
logger$6.info({
|
|
5044
5044
|
taskId,
|
|
5045
5045
|
conflictCount: conflictReport.conflictingFiles.length
|
|
5046
5046
|
}, "Merge skipped due to conflicts");
|
|
@@ -5062,7 +5062,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
5062
5062
|
success: true,
|
|
5063
5063
|
mergedFiles
|
|
5064
5064
|
};
|
|
5065
|
-
logger$
|
|
5065
|
+
logger$6.info({
|
|
5066
5066
|
taskId,
|
|
5067
5067
|
branchName,
|
|
5068
5068
|
mergedFileCount: mergedFiles.length
|
|
@@ -5070,7 +5070,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
5070
5070
|
return result;
|
|
5071
5071
|
}
|
|
5072
5072
|
async listWorktrees() {
|
|
5073
|
-
logger$
|
|
5073
|
+
logger$6.debug({
|
|
5074
5074
|
projectRoot: this._projectRoot,
|
|
5075
5075
|
baseDirectory: this._baseDirectory
|
|
5076
5076
|
}, "listWorktrees");
|
|
@@ -5094,7 +5094,7 @@ var GitWorktreeManagerImpl = class {
|
|
|
5094
5094
|
createdAt
|
|
5095
5095
|
});
|
|
5096
5096
|
}
|
|
5097
|
-
logger$
|
|
5097
|
+
logger$6.debug({ count: results.length }, "listWorktrees: found worktrees");
|
|
5098
5098
|
return results;
|
|
5099
5099
|
}
|
|
5100
5100
|
getWorktreePath(taskId) {
|
|
@@ -5114,7 +5114,7 @@ function createGitWorktreeManager(options) {
|
|
|
5114
5114
|
|
|
5115
5115
|
//#endregion
|
|
5116
5116
|
//#region src/cli/commands/merge.ts
|
|
5117
|
-
const logger$
|
|
5117
|
+
const logger$5 = createLogger("merge-cmd");
|
|
5118
5118
|
const MERGE_EXIT_SUCCESS = 0;
|
|
5119
5119
|
const MERGE_EXIT_CONFLICT = 1;
|
|
5120
5120
|
const MERGE_EXIT_ERROR = 2;
|
|
@@ -5152,7 +5152,7 @@ async function mergeTask(taskId, targetBranch, projectRoot) {
|
|
|
5152
5152
|
projectRoot
|
|
5153
5153
|
});
|
|
5154
5154
|
try {
|
|
5155
|
-
logger$
|
|
5155
|
+
logger$5.info({
|
|
5156
5156
|
taskId,
|
|
5157
5157
|
targetBranch
|
|
5158
5158
|
}, "Running conflict detection...");
|
|
@@ -5174,7 +5174,7 @@ async function mergeTask(taskId, targetBranch, projectRoot) {
|
|
|
5174
5174
|
} catch (err) {
|
|
5175
5175
|
const message = err instanceof Error ? err.message : String(err);
|
|
5176
5176
|
console.error(`Error merging task "${taskId}": ${message}`);
|
|
5177
|
-
logger$
|
|
5177
|
+
logger$5.error({
|
|
5178
5178
|
taskId,
|
|
5179
5179
|
err
|
|
5180
5180
|
}, "merge --task failed");
|
|
@@ -5228,7 +5228,7 @@ async function mergeAll(targetBranch, projectRoot, taskIds) {
|
|
|
5228
5228
|
error: message
|
|
5229
5229
|
});
|
|
5230
5230
|
console.log(` Error for task "${taskId}": ${message}`);
|
|
5231
|
-
logger$
|
|
5231
|
+
logger$5.error({
|
|
5232
5232
|
taskId,
|
|
5233
5233
|
err
|
|
5234
5234
|
}, "merge --all: task failed");
|
|
@@ -5281,7 +5281,7 @@ function registerMergeCommand(program, projectRoot = process.cwd()) {
|
|
|
5281
5281
|
|
|
5282
5282
|
//#endregion
|
|
5283
5283
|
//#region src/cli/commands/worktrees.ts
|
|
5284
|
-
const logger$
|
|
5284
|
+
const logger$4 = createLogger("worktrees-cmd");
|
|
5285
5285
|
const WORKTREES_EXIT_SUCCESS = 0;
|
|
5286
5286
|
const WORKTREES_EXIT_ERROR = 1;
|
|
5287
5287
|
/** Valid task statuses for filtering */
|
|
@@ -5408,7 +5408,7 @@ async function listWorktreesAction(options) {
|
|
|
5408
5408
|
try {
|
|
5409
5409
|
worktreeInfos = await manager.listWorktrees();
|
|
5410
5410
|
} catch (err) {
|
|
5411
|
-
logger$
|
|
5411
|
+
logger$4.error({ err }, "Failed to list worktrees");
|
|
5412
5412
|
const message = err instanceof Error ? err.message : String(err);
|
|
5413
5413
|
process.stderr.write(`Error listing worktrees: ${message}\n`);
|
|
5414
5414
|
return WORKTREES_EXIT_ERROR;
|
|
@@ -5435,7 +5435,7 @@ async function listWorktreesAction(options) {
|
|
|
5435
5435
|
} catch (err) {
|
|
5436
5436
|
const message = err instanceof Error ? err.message : String(err);
|
|
5437
5437
|
process.stderr.write(`Error: ${message}\n`);
|
|
5438
|
-
logger$
|
|
5438
|
+
logger$4.error({ err }, "listWorktreesAction failed");
|
|
5439
5439
|
return WORKTREES_EXIT_ERROR;
|
|
5440
5440
|
}
|
|
5441
5441
|
}
|
|
@@ -5476,7 +5476,7 @@ function registerWorktreesCommand(program, version = "0.0.0", projectRoot = proc
|
|
|
5476
5476
|
|
|
5477
5477
|
//#endregion
|
|
5478
5478
|
//#region src/cli/commands/brainstorm.ts
|
|
5479
|
-
const logger$
|
|
5479
|
+
const logger$3 = createLogger("brainstorm-cmd");
|
|
5480
5480
|
/**
|
|
5481
5481
|
* Detect whether the project has existing planning artifacts that indicate
|
|
5482
5482
|
* this is an amendment session (vs. a brand-new project brainstorm).
|
|
@@ -5522,13 +5522,13 @@ async function loadAmendmentContextDocuments(projectRoot) {
|
|
|
5522
5522
|
try {
|
|
5523
5523
|
brief = await readFile(briefPath, "utf-8");
|
|
5524
5524
|
} catch {
|
|
5525
|
-
logger$
|
|
5525
|
+
logger$3.warn({ briefPath }, "product-brief.md not found — continuing without brief context");
|
|
5526
5526
|
process.stderr.write(`Warning: product-brief.md not found at ${briefPath}\n`);
|
|
5527
5527
|
}
|
|
5528
5528
|
try {
|
|
5529
5529
|
prd = await readFile(prdPath, "utf-8");
|
|
5530
5530
|
} catch {
|
|
5531
|
-
logger$
|
|
5531
|
+
logger$3.warn({ prdPath }, "requirements.md not found — continuing without PRD context");
|
|
5532
5532
|
process.stderr.write(`Warning: requirements.md not found at ${prdPath}\n`);
|
|
5533
5533
|
}
|
|
5534
5534
|
return {
|
|
@@ -5737,7 +5737,7 @@ async function dispatchToPersonas(userPrompt, context, llmDispatch) {
|
|
|
5737
5737
|
}
|
|
5738
5738
|
];
|
|
5739
5739
|
const defaultDispatch = async (prompt, personaName) => {
|
|
5740
|
-
logger$
|
|
5740
|
+
logger$3.debug({
|
|
5741
5741
|
personaName,
|
|
5742
5742
|
promptLength: prompt.length
|
|
5743
5743
|
}, "Dispatching to persona (stub mode)");
|
|
@@ -5754,7 +5754,7 @@ async function dispatchToPersonas(userPrompt, context, llmDispatch) {
|
|
|
5754
5754
|
};
|
|
5755
5755
|
} catch (err) {
|
|
5756
5756
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5757
|
-
logger$
|
|
5757
|
+
logger$3.error({
|
|
5758
5758
|
err,
|
|
5759
5759
|
personaName: persona.name
|
|
5760
5760
|
}, "Persona dispatch failed");
|
|
@@ -5906,7 +5906,7 @@ async function runBrainstormSession(options, llmDispatch, rlInterface) {
|
|
|
5906
5906
|
}
|
|
5907
5907
|
});
|
|
5908
5908
|
rl.on("error", (err) => {
|
|
5909
|
-
logger$
|
|
5909
|
+
logger$3.error({ err }, "readline error");
|
|
5910
5910
|
if (!sessionEnded) endSession(false);
|
|
5911
5911
|
});
|
|
5912
5912
|
});
|
|
@@ -6496,7 +6496,7 @@ function renderReadinessReport(decisions) {
|
|
|
6496
6496
|
|
|
6497
6497
|
//#endregion
|
|
6498
6498
|
//#region src/cli/commands/export.ts
|
|
6499
|
-
const logger$
|
|
6499
|
+
const logger$2 = createLogger("export-cmd");
|
|
6500
6500
|
/**
|
|
6501
6501
|
* Execute the export action.
|
|
6502
6502
|
* Returns an exit code (0 = success, 1 = error).
|
|
@@ -6623,7 +6623,7 @@ async function runExportAction(options) {
|
|
|
6623
6623
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6624
6624
|
if (outputFormat === "json") process.stdout.write(JSON.stringify({ error: msg }) + "\n");
|
|
6625
6625
|
else process.stderr.write(`Error: ${msg}\n`);
|
|
6626
|
-
logger$
|
|
6626
|
+
logger$2.error({ err }, "export action failed");
|
|
6627
6627
|
return 1;
|
|
6628
6628
|
} finally {
|
|
6629
6629
|
if (dbWrapper !== void 0) try {
|
|
@@ -6645,6 +6645,219 @@ function registerExportCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
6645
6645
|
});
|
|
6646
6646
|
}
|
|
6647
6647
|
|
|
6648
|
+
//#endregion
|
|
6649
|
+
//#region src/persistence/queries/retry-escalated.ts
|
|
6650
|
+
/**
|
|
6651
|
+
* Query the decision store for escalation-diagnosis decisions and classify
|
|
6652
|
+
* each story key as retryable or skipped.
|
|
6653
|
+
*
|
|
6654
|
+
* Key format in the DB: `{storyKey}:{runId}`
|
|
6655
|
+
*
|
|
6656
|
+
* - When `runId` is provided, only decisions whose key contains that runId
|
|
6657
|
+
* are considered (AC5 scoping).
|
|
6658
|
+
* - When `runId` is omitted, the runId of the last (most recently created)
|
|
6659
|
+
* escalation-diagnosis decision is used as the default (AC1 defaulting).
|
|
6660
|
+
*
|
|
6661
|
+
* @param db The SQLite database connection
|
|
6662
|
+
* @param runId Optional run ID to scope the query
|
|
6663
|
+
*/
|
|
6664
|
+
function getRetryableEscalations(db, runId) {
|
|
6665
|
+
const decisions = getDecisionsByCategory(db, ESCALATION_DIAGNOSIS);
|
|
6666
|
+
const result = {
|
|
6667
|
+
retryable: [],
|
|
6668
|
+
skipped: []
|
|
6669
|
+
};
|
|
6670
|
+
if (decisions.length === 0) return result;
|
|
6671
|
+
const parsed = [];
|
|
6672
|
+
for (const decision of decisions) {
|
|
6673
|
+
const colonIdx = decision.key.indexOf(":");
|
|
6674
|
+
if (colonIdx === -1) continue;
|
|
6675
|
+
const storyKey = decision.key.slice(0, colonIdx);
|
|
6676
|
+
const decisionRunId = decision.key.slice(colonIdx + 1);
|
|
6677
|
+
let diagnosis;
|
|
6678
|
+
try {
|
|
6679
|
+
diagnosis = JSON.parse(decision.value);
|
|
6680
|
+
} catch {
|
|
6681
|
+
continue;
|
|
6682
|
+
}
|
|
6683
|
+
parsed.push({
|
|
6684
|
+
storyKey,
|
|
6685
|
+
decisionRunId,
|
|
6686
|
+
diagnosis
|
|
6687
|
+
});
|
|
6688
|
+
}
|
|
6689
|
+
if (parsed.length === 0) return result;
|
|
6690
|
+
const effectiveRunId = runId ?? parsed[parsed.length - 1].decisionRunId;
|
|
6691
|
+
const lastEntryByKey = new Map();
|
|
6692
|
+
for (const entry of parsed) {
|
|
6693
|
+
if (entry.decisionRunId !== effectiveRunId) continue;
|
|
6694
|
+
lastEntryByKey.set(entry.storyKey, entry);
|
|
6695
|
+
}
|
|
6696
|
+
for (const [storyKey, entry] of lastEntryByKey.entries()) {
|
|
6697
|
+
const { recommendedAction } = entry.diagnosis;
|
|
6698
|
+
if (recommendedAction === "retry-targeted") result.retryable.push(storyKey);
|
|
6699
|
+
else if (recommendedAction === "human-intervention") result.skipped.push({
|
|
6700
|
+
key: storyKey,
|
|
6701
|
+
reason: "needs human review"
|
|
6702
|
+
});
|
|
6703
|
+
else if (recommendedAction === "split-story") result.skipped.push({
|
|
6704
|
+
key: storyKey,
|
|
6705
|
+
reason: "story should be split"
|
|
6706
|
+
});
|
|
6707
|
+
}
|
|
6708
|
+
return result;
|
|
6709
|
+
}
|
|
6710
|
+
|
|
6711
|
+
//#endregion
|
|
6712
|
+
//#region src/cli/commands/retry-escalated.ts
|
|
6713
|
+
const logger$1 = createLogger("retry-escalated-cmd");
|
|
6714
|
+
async function runRetryEscalatedAction(options) {
|
|
6715
|
+
const { runId, dryRun, outputFormat, projectRoot, concurrency, pack: packName, registry: injectedRegistry } = options;
|
|
6716
|
+
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
6717
|
+
const dbPath = join(dbRoot, ".substrate", "substrate.db");
|
|
6718
|
+
if (!existsSync(dbPath)) {
|
|
6719
|
+
const errorMsg = `Decision store not initialized. Run 'substrate init' first.`;
|
|
6720
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
6721
|
+
else process.stderr.write(`Error: ${errorMsg}\n`);
|
|
6722
|
+
return 1;
|
|
6723
|
+
}
|
|
6724
|
+
const dbWrapper = new DatabaseWrapper(dbPath);
|
|
6725
|
+
try {
|
|
6726
|
+
dbWrapper.open();
|
|
6727
|
+
runMigrations(dbWrapper.db);
|
|
6728
|
+
const db = dbWrapper.db;
|
|
6729
|
+
const { retryable, skipped } = getRetryableEscalations(db, runId);
|
|
6730
|
+
if (retryable.length === 0) {
|
|
6731
|
+
if (outputFormat === "json") process.stdout.write(formatOutput({
|
|
6732
|
+
retryKeys: [],
|
|
6733
|
+
skippedKeys: skipped
|
|
6734
|
+
}, "json", true) + "\n");
|
|
6735
|
+
else process.stdout.write("No retry-targeted escalations found.\n");
|
|
6736
|
+
return 0;
|
|
6737
|
+
}
|
|
6738
|
+
if (dryRun) {
|
|
6739
|
+
if (outputFormat === "json") process.stdout.write(formatOutput({
|
|
6740
|
+
retryKeys: retryable,
|
|
6741
|
+
skippedKeys: skipped
|
|
6742
|
+
}, "json", true) + "\n");
|
|
6743
|
+
else {
|
|
6744
|
+
const count = retryable.length;
|
|
6745
|
+
process.stdout.write(`Retrying: ${count} ${count === 1 ? "story" : "stories"} — ${retryable.join(", ")}\n`);
|
|
6746
|
+
for (const s of skipped) process.stdout.write(`Skipping: ${s.key} (${s.reason})\n`);
|
|
6747
|
+
}
|
|
6748
|
+
return 0;
|
|
6749
|
+
}
|
|
6750
|
+
const packPath = join(projectRoot, "packs", packName);
|
|
6751
|
+
const packLoader = createPackLoader();
|
|
6752
|
+
let pack;
|
|
6753
|
+
try {
|
|
6754
|
+
pack = await packLoader.load(packPath);
|
|
6755
|
+
} catch (err) {
|
|
6756
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6757
|
+
const errorMsg = `Methodology pack '${packName}' not found. Run 'substrate init' first.\n${msg}`;
|
|
6758
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
|
|
6759
|
+
else process.stderr.write(`Error: ${errorMsg}\n`);
|
|
6760
|
+
return 1;
|
|
6761
|
+
}
|
|
6762
|
+
if (outputFormat === "human") {
|
|
6763
|
+
const count = retryable.length;
|
|
6764
|
+
process.stdout.write(`Retrying: ${count} ${count === 1 ? "story" : "stories"} — ${retryable.join(", ")}\n`);
|
|
6765
|
+
for (const s of skipped) process.stdout.write(`Skipping: ${s.key} (${s.reason})\n`);
|
|
6766
|
+
}
|
|
6767
|
+
const pipelineRun = createPipelineRun(db, {
|
|
6768
|
+
methodology: pack.manifest.name,
|
|
6769
|
+
start_phase: "implementation",
|
|
6770
|
+
config_json: JSON.stringify({
|
|
6771
|
+
storyKeys: retryable,
|
|
6772
|
+
concurrency,
|
|
6773
|
+
retryRun: true
|
|
6774
|
+
})
|
|
6775
|
+
});
|
|
6776
|
+
const eventBus = createEventBus();
|
|
6777
|
+
const contextCompiler = createContextCompiler({ db });
|
|
6778
|
+
if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
|
|
6779
|
+
const dispatcher = createDispatcher({
|
|
6780
|
+
eventBus,
|
|
6781
|
+
adapterRegistry: injectedRegistry
|
|
6782
|
+
});
|
|
6783
|
+
const orchestrator = createImplementationOrchestrator({
|
|
6784
|
+
db,
|
|
6785
|
+
pack,
|
|
6786
|
+
contextCompiler,
|
|
6787
|
+
dispatcher,
|
|
6788
|
+
eventBus,
|
|
6789
|
+
config: {
|
|
6790
|
+
maxConcurrency: concurrency,
|
|
6791
|
+
maxReviewCycles: 2,
|
|
6792
|
+
pipelineRunId: pipelineRun.id
|
|
6793
|
+
},
|
|
6794
|
+
projectRoot
|
|
6795
|
+
});
|
|
6796
|
+
eventBus.on("orchestrator:story-phase-complete", (payload) => {
|
|
6797
|
+
try {
|
|
6798
|
+
const result = payload.result;
|
|
6799
|
+
if (result?.tokenUsage !== void 0) {
|
|
6800
|
+
const { input, output } = result.tokenUsage;
|
|
6801
|
+
const costUsd = (input * 3 + output * 15) / 1e6;
|
|
6802
|
+
addTokenUsage(db, pipelineRun.id, {
|
|
6803
|
+
phase: payload.phase,
|
|
6804
|
+
agent: "claude-code",
|
|
6805
|
+
input_tokens: input,
|
|
6806
|
+
output_tokens: output,
|
|
6807
|
+
cost_usd: costUsd
|
|
6808
|
+
});
|
|
6809
|
+
}
|
|
6810
|
+
} catch (err) {
|
|
6811
|
+
logger$1.warn({ err }, "Failed to record token usage");
|
|
6812
|
+
}
|
|
6813
|
+
});
|
|
6814
|
+
if (outputFormat === "human") {
|
|
6815
|
+
eventBus.on("orchestrator:story-complete", (payload) => {
|
|
6816
|
+
process.stdout.write(` [COMPLETE] ${payload.storyKey} (${payload.reviewCycles} review cycle(s))\n`);
|
|
6817
|
+
});
|
|
6818
|
+
eventBus.on("orchestrator:story-escalated", (payload) => {
|
|
6819
|
+
process.stdout.write(` [ESCALATED] ${payload.storyKey}: ${payload.lastVerdict}\n`);
|
|
6820
|
+
});
|
|
6821
|
+
}
|
|
6822
|
+
await orchestrator.run(retryable);
|
|
6823
|
+
if (outputFormat === "json") process.stdout.write(formatOutput({
|
|
6824
|
+
retryKeys: retryable,
|
|
6825
|
+
skippedKeys: skipped
|
|
6826
|
+
}, "json", true) + "\n");
|
|
6827
|
+
else process.stdout.write("[RETRY] Complete\n");
|
|
6828
|
+
return 0;
|
|
6829
|
+
} catch (err) {
|
|
6830
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6831
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
6832
|
+
else process.stderr.write(`Error: ${msg}\n`);
|
|
6833
|
+
logger$1.error({ err }, "retry-escalated failed");
|
|
6834
|
+
return 1;
|
|
6835
|
+
} finally {
|
|
6836
|
+
try {
|
|
6837
|
+
dbWrapper.close();
|
|
6838
|
+
} catch {}
|
|
6839
|
+
}
|
|
6840
|
+
}
|
|
6841
|
+
function registerRetryEscalatedCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
|
|
6842
|
+
program.command("retry-escalated").description("Retry escalated stories flagged as retry-targeted by escalation diagnosis").option("--run-id <id>", "Scope to a specific pipeline run ID (defaults to latest run with escalations)").option("--dry-run", "Print retryable and skipped stories without invoking the orchestrator").option("--concurrency <n>", "Maximum parallel story executions", (v) => {
|
|
6843
|
+
const n = parseInt(v, 10);
|
|
6844
|
+
if (isNaN(n) || n < 1) throw new Error(`--concurrency must be a positive integer, got: ${v}`);
|
|
6845
|
+
return n;
|
|
6846
|
+
}, 3).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) => {
|
|
6847
|
+
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
6848
|
+
const exitCode = await runRetryEscalatedAction({
|
|
6849
|
+
runId: opts.runId,
|
|
6850
|
+
dryRun: opts.dryRun === true,
|
|
6851
|
+
outputFormat,
|
|
6852
|
+
projectRoot: opts.projectRoot,
|
|
6853
|
+
concurrency: opts.concurrency,
|
|
6854
|
+
pack: opts.pack,
|
|
6855
|
+
registry
|
|
6856
|
+
});
|
|
6857
|
+
process.exitCode = exitCode;
|
|
6858
|
+
});
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6648
6861
|
//#endregion
|
|
6649
6862
|
//#region src/cli/index.ts
|
|
6650
6863
|
process.setMaxListeners(20);
|
|
@@ -6680,16 +6893,19 @@ async function createProgram() {
|
|
|
6680
6893
|
const version = await getPackageVersion();
|
|
6681
6894
|
const program = new Command();
|
|
6682
6895
|
program.name("substrate").description("Substrate - Autonomous implementation pipeline for AI coding agents").version(version, "-v, --version", "Output the current version");
|
|
6683
|
-
|
|
6684
|
-
|
|
6896
|
+
const registry = new AdapterRegistry();
|
|
6897
|
+
await registry.discoverAndRegister();
|
|
6898
|
+
registerAdaptersCommand(program, version, registry);
|
|
6899
|
+
registerInitCommand(program, version, registry);
|
|
6685
6900
|
registerConfigCommand(program, version);
|
|
6686
|
-
registerRunCommand(program, version);
|
|
6687
|
-
registerResumeCommand(program, version);
|
|
6901
|
+
registerRunCommand(program, version, process.cwd(), registry);
|
|
6902
|
+
registerResumeCommand(program, version, process.cwd(), registry);
|
|
6688
6903
|
registerStatusCommand(program, version);
|
|
6689
|
-
registerAmendCommand(program, version);
|
|
6904
|
+
registerAmendCommand(program, version, process.cwd(), registry);
|
|
6690
6905
|
registerHealthCommand(program, version);
|
|
6691
6906
|
registerSupervisorCommand(program, version);
|
|
6692
6907
|
registerMetricsCommand(program, version);
|
|
6908
|
+
registerRetryEscalatedCommand(program, version, process.cwd(), registry);
|
|
6693
6909
|
registerCostCommand(program, version);
|
|
6694
6910
|
registerMonitorCommand(program, version);
|
|
6695
6911
|
registerMergeCommand(program);
|