substrate-ai 0.2.40 → 0.3.0
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
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createConfigSystem, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-
|
|
2
|
+
import { DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltNotInstalled, FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initializeDolt, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-DO9n3cwy.js";
|
|
3
3
|
import { createLogger } from "../logger-D2fS2ccL.js";
|
|
4
4
|
import { AdapterRegistry } from "../adapter-registry-PsWhP_1Q.js";
|
|
5
5
|
import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DSi8KhQC.js";
|
|
@@ -18,11 +18,11 @@ import yaml from "js-yaml";
|
|
|
18
18
|
import { createRequire } from "node:module";
|
|
19
19
|
import * as path$1 from "node:path";
|
|
20
20
|
import { isAbsolute, join as join$1 } from "node:path";
|
|
21
|
-
import
|
|
21
|
+
import Database from "better-sqlite3";
|
|
22
|
+
import { access as access$1 } from "node:fs/promises";
|
|
22
23
|
import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
|
|
23
24
|
import { createInterface } from "node:readline";
|
|
24
25
|
import { homedir } from "os";
|
|
25
|
-
import { access as access$1 } from "node:fs/promises";
|
|
26
26
|
import { randomUUID } from "crypto";
|
|
27
27
|
import { createInterface as createInterface$1 } from "readline";
|
|
28
28
|
|
|
@@ -662,13 +662,37 @@ async function runInitAction(options) {
|
|
|
662
662
|
await scaffoldStatuslineScript(projectRoot);
|
|
663
663
|
await scaffoldClaudeSettings(projectRoot);
|
|
664
664
|
await scaffoldClaudeCommands(projectRoot, outputFormat);
|
|
665
|
+
const doltMode = options.doltMode ?? "auto";
|
|
666
|
+
let doltInitialized = false;
|
|
667
|
+
if (doltMode !== "skip") try {
|
|
668
|
+
if (doltMode === "auto") await checkDoltInstalled();
|
|
669
|
+
await initializeDolt({ projectRoot });
|
|
670
|
+
doltInitialized = true;
|
|
671
|
+
} catch (err) {
|
|
672
|
+
if (err instanceof DoltNotInstalled) {
|
|
673
|
+
if (doltMode === "force") {
|
|
674
|
+
process.stderr.write(`${err.message}\n`);
|
|
675
|
+
return INIT_EXIT_ERROR;
|
|
676
|
+
}
|
|
677
|
+
logger$16.debug("Dolt not installed, skipping auto-init");
|
|
678
|
+
} else {
|
|
679
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
680
|
+
if (doltMode === "force") {
|
|
681
|
+
process.stderr.write(`✗ Dolt initialization failed: ${msg}\n`);
|
|
682
|
+
return INIT_EXIT_ERROR;
|
|
683
|
+
}
|
|
684
|
+
logger$16.warn("Dolt auto-init failed (non-blocking)", { error: msg });
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else logger$16.debug("Dolt step was skipped (--no-dolt)");
|
|
665
688
|
const successMsg = `Pack '${packName}' and database initialized successfully at ${dbPath}`;
|
|
666
689
|
if (outputFormat === "json") process.stdout.write(formatOutput({
|
|
667
690
|
pack: packName,
|
|
668
691
|
dbPath,
|
|
669
692
|
scaffolded,
|
|
670
693
|
configPath,
|
|
671
|
-
routingPolicyPath
|
|
694
|
+
routingPolicyPath,
|
|
695
|
+
doltInitialized
|
|
672
696
|
}, "json", true) + "\n");
|
|
673
697
|
else {
|
|
674
698
|
process.stdout.write(`\n Substrate initialized successfully!\n\n`);
|
|
@@ -683,6 +707,7 @@ async function runInitAction(options) {
|
|
|
683
707
|
process.stdout.write(` CLAUDE.md pipeline instructions for Claude Code\n`);
|
|
684
708
|
process.stdout.write(` .claude/commands/ /substrate-run, /substrate-supervisor, /substrate-metrics\n`);
|
|
685
709
|
process.stdout.write(` .substrate/ config, database, routing policy\n`);
|
|
710
|
+
if (doltInitialized) process.stdout.write(`✓ Dolt state store initialized at .substrate/state/\n`);
|
|
686
711
|
process.stdout.write("\n Next steps:\n 1. Start a Claude Code session in this project\n 2. Tell Claude: \"Run the substrate pipeline\"\n 3. Or use the /substrate-run slash command for a guided run\n");
|
|
687
712
|
}
|
|
688
713
|
return INIT_EXIT_SUCCESS;
|
|
@@ -695,14 +720,16 @@ async function runInitAction(options) {
|
|
|
695
720
|
}
|
|
696
721
|
}
|
|
697
722
|
function registerInitCommand(program, _version, registry) {
|
|
698
|
-
program.command("init").description("Initialize Substrate — creates config, scaffolds methodology pack, and sets up database").option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", process.cwd()).option("-y, --yes", "Skip all interactive prompts and use defaults", false).option("--force", "Overwrite existing files and packs", false).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
|
|
723
|
+
program.command("init").description("Initialize Substrate — creates config, scaffolds methodology pack, and sets up database").option("--pack <name>", "Methodology pack name", "bmad").option("--project-root <path>", "Project root directory", process.cwd()).option("-y, --yes", "Skip all interactive prompts and use defaults", false).option("--force", "Overwrite existing files and packs", false).option("--output-format <format>", "Output format: human (default) or json", "human").option("--dolt", "Initialize Dolt state database as part of init (forces Dolt bootstrapping)", false).option("--no-dolt", "Skip Dolt state store initialization even if Dolt is installed").action(async (opts) => {
|
|
699
724
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
725
|
+
const doltMode = opts.noDolt ? "skip" : opts.dolt ? "force" : "auto";
|
|
700
726
|
const exitCode = await runInitAction({
|
|
701
727
|
pack: opts.pack,
|
|
702
728
|
projectRoot: opts.projectRoot,
|
|
703
729
|
outputFormat,
|
|
704
730
|
force: opts.force,
|
|
705
731
|
yes: opts.yes,
|
|
732
|
+
doltMode,
|
|
706
733
|
...registry !== void 0 && { registry }
|
|
707
734
|
});
|
|
708
735
|
process.exitCode = exitCode;
|
|
@@ -1281,7 +1308,32 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
|
|
|
1281
1308
|
//#region src/cli/commands/status.ts
|
|
1282
1309
|
const logger$13 = createLogger("status-cmd");
|
|
1283
1310
|
async function runStatusAction(options) {
|
|
1284
|
-
const { outputFormat, runId, projectRoot } = options;
|
|
1311
|
+
const { outputFormat, runId, projectRoot, stateStore, history } = options;
|
|
1312
|
+
if (history === true) {
|
|
1313
|
+
if (!stateStore) {
|
|
1314
|
+
process.stdout.write("History not available with file backend. Use Dolt backend for state history.\n");
|
|
1315
|
+
return 0;
|
|
1316
|
+
}
|
|
1317
|
+
try {
|
|
1318
|
+
const entries = await stateStore.getHistory(20);
|
|
1319
|
+
if (outputFormat === "json") {
|
|
1320
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + "\n");
|
|
1321
|
+
return 0;
|
|
1322
|
+
}
|
|
1323
|
+
process.stdout.write("TIMESTAMP HASH MESSAGE\n");
|
|
1324
|
+
for (const entry of entries) {
|
|
1325
|
+
const ts = (entry.timestamp ?? "").padEnd(20);
|
|
1326
|
+
const hash = (entry.hash ?? "").padEnd(8);
|
|
1327
|
+
process.stdout.write(`${ts} ${hash} ${entry.message}\n`);
|
|
1328
|
+
}
|
|
1329
|
+
return 0;
|
|
1330
|
+
} catch (err) {
|
|
1331
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1332
|
+
if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
|
|
1333
|
+
else process.stderr.write(`Error: ${msg}\n`);
|
|
1334
|
+
return 1;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1285
1337
|
const dbRoot = await resolveMainRepoRoot(projectRoot);
|
|
1286
1338
|
const dbPath = join(dbRoot, ".substrate", "substrate.db");
|
|
1287
1339
|
if (!existsSync(dbPath)) {
|
|
@@ -1306,6 +1358,12 @@ async function runStatusAction(options) {
|
|
|
1306
1358
|
const tokenSummary = getTokenUsageSummary(db, run.id);
|
|
1307
1359
|
const decisionsCount = db.prepare(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`).get(run.id)?.cnt ?? 0;
|
|
1308
1360
|
const storiesCount = db.prepare(`SELECT COUNT(*) as cnt FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`).get(run.id)?.cnt ?? 0;
|
|
1361
|
+
let storeStories = [];
|
|
1362
|
+
if (stateStore) try {
|
|
1363
|
+
storeStories = await stateStore.queryStories({});
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
logger$13.debug("StateStore query failed, continuing without store data", { err });
|
|
1366
|
+
}
|
|
1309
1367
|
if (outputFormat === "json") {
|
|
1310
1368
|
const statusOutput = buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCount);
|
|
1311
1369
|
const storyMetricsRows = getStoryMetricsForRun(db, run.id);
|
|
@@ -1352,7 +1410,8 @@ async function runStatusAction(options) {
|
|
|
1352
1410
|
total_output_tokens: totalOutputTokens,
|
|
1353
1411
|
stories_per_hour: storiesPerHour,
|
|
1354
1412
|
cost_usd: totalCostUsd
|
|
1355
|
-
}
|
|
1413
|
+
},
|
|
1414
|
+
story_states: storeStories
|
|
1356
1415
|
};
|
|
1357
1416
|
process.stdout.write(formatOutput(enhancedOutput, "json", true) + "\n");
|
|
1358
1417
|
} else {
|
|
@@ -1394,6 +1453,10 @@ async function runStatusAction(options) {
|
|
|
1394
1453
|
}
|
|
1395
1454
|
}
|
|
1396
1455
|
}
|
|
1456
|
+
if (storeStories.length > 0) {
|
|
1457
|
+
process.stdout.write("\nStateStore Story States:\n");
|
|
1458
|
+
for (const s of storeStories) process.stdout.write(` ${s.storyKey}: ${s.phase} (${s.reviewCycles} review cycles)\n`);
|
|
1459
|
+
}
|
|
1397
1460
|
process.stdout.write("\n");
|
|
1398
1461
|
process.stdout.write(formatTokenTelemetry(tokenSummary) + "\n");
|
|
1399
1462
|
}
|
|
@@ -1411,14 +1474,34 @@ async function runStatusAction(options) {
|
|
|
1411
1474
|
}
|
|
1412
1475
|
}
|
|
1413
1476
|
function registerStatusCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
1414
|
-
program.command("status").description("Show status of the most recent (or specified) pipeline run").option("--run-id <id>", "Pipeline run ID to query (defaults to latest)").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
|
|
1477
|
+
program.command("status").description("Show status of the most recent (or specified) pipeline run").option("--run-id <id>", "Pipeline run ID to query (defaults to latest)").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--history", "Show Dolt commit history for the state store").action(async (opts) => {
|
|
1415
1478
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
1416
|
-
const
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1479
|
+
const root = opts.projectRoot;
|
|
1480
|
+
let stateStore;
|
|
1481
|
+
const doltStatePath = join(root, ".substrate", "state", ".dolt");
|
|
1482
|
+
if (existsSync(doltStatePath)) try {
|
|
1483
|
+
stateStore = createStateStore({
|
|
1484
|
+
backend: "dolt",
|
|
1485
|
+
basePath: join(root, ".substrate", "state")
|
|
1486
|
+
});
|
|
1487
|
+
await stateStore.initialize();
|
|
1488
|
+
} catch {
|
|
1489
|
+
stateStore = void 0;
|
|
1490
|
+
}
|
|
1491
|
+
try {
|
|
1492
|
+
const exitCode = await runStatusAction({
|
|
1493
|
+
outputFormat,
|
|
1494
|
+
runId: opts.runId,
|
|
1495
|
+
projectRoot: root,
|
|
1496
|
+
stateStore,
|
|
1497
|
+
history: opts.history
|
|
1498
|
+
});
|
|
1499
|
+
process.exitCode = exitCode;
|
|
1500
|
+
} finally {
|
|
1501
|
+
try {
|
|
1502
|
+
await stateStore?.close();
|
|
1503
|
+
} catch {}
|
|
1504
|
+
}
|
|
1422
1505
|
});
|
|
1423
1506
|
}
|
|
1424
1507
|
|
|
@@ -2581,7 +2664,7 @@ async function runSupervisorAction(options, deps = {}) {
|
|
|
2581
2664
|
const expDb = expDbWrapper.db;
|
|
2582
2665
|
const { runRunAction: runPipeline } = await import(
|
|
2583
2666
|
/* @vite-ignore */
|
|
2584
|
-
"../run-
|
|
2667
|
+
"../run-DP932Mmn.js"
|
|
2585
2668
|
);
|
|
2586
2669
|
const runStoryFn = async (opts) => {
|
|
2587
2670
|
const exitCode = await runPipeline({
|
|
@@ -2826,7 +2909,7 @@ function registerSupervisorCommand(program, _version = "0.0.0", projectRoot = pr
|
|
|
2826
2909
|
//#region src/cli/commands/metrics.ts
|
|
2827
2910
|
const logger$11 = createLogger("metrics-cmd");
|
|
2828
2911
|
async function runMetricsAction(options) {
|
|
2829
|
-
const { outputFormat, projectRoot, limit = 10, compare, tagBaseline, analysis } = options;
|
|
2912
|
+
const { outputFormat, projectRoot, limit = 10, compare, tagBaseline, analysis, sprint, story, taskType, since, aggregate } = options;
|
|
2830
2913
|
if (analysis !== void 0) {
|
|
2831
2914
|
const dbRoot$1 = await resolveMainRepoRoot(projectRoot);
|
|
2832
2915
|
const reportBase = join(dbRoot$1, "_bmad-output", "supervisor-reports", `${analysis}-analysis`);
|
|
@@ -2906,6 +2989,26 @@ async function runMetricsAction(options) {
|
|
|
2906
2989
|
return 0;
|
|
2907
2990
|
}
|
|
2908
2991
|
const runs = listRunMetrics(db, limit);
|
|
2992
|
+
let doltMetrics;
|
|
2993
|
+
const doltStatePath = join(dbRoot, ".substrate", "state", ".dolt");
|
|
2994
|
+
const hasDoltFilters = sprint !== void 0 || story !== void 0 || taskType !== void 0 || since !== void 0 || aggregate === true;
|
|
2995
|
+
if (existsSync(doltStatePath) && hasDoltFilters) try {
|
|
2996
|
+
const stateStore = createStateStore({
|
|
2997
|
+
backend: "dolt",
|
|
2998
|
+
basePath: join(dbRoot, ".substrate", "state")
|
|
2999
|
+
});
|
|
3000
|
+
await stateStore.initialize();
|
|
3001
|
+
const doltFilter = {};
|
|
3002
|
+
if (sprint !== void 0) doltFilter.sprint = sprint;
|
|
3003
|
+
if (story !== void 0) doltFilter.storyKey = story;
|
|
3004
|
+
if (taskType !== void 0) doltFilter.taskType = taskType;
|
|
3005
|
+
if (since !== void 0) doltFilter.since = since;
|
|
3006
|
+
if (aggregate !== void 0) doltFilter.aggregate = aggregate;
|
|
3007
|
+
doltMetrics = await stateStore.queryMetrics(doltFilter);
|
|
3008
|
+
await stateStore.close();
|
|
3009
|
+
} catch (doltErr) {
|
|
3010
|
+
logger$11.warn({ err: doltErr }, "StateStore query failed — falling back to SQLite metrics only");
|
|
3011
|
+
}
|
|
2909
3012
|
const storyMetricDecisions = getDecisionsByCategory(db, STORY_METRICS);
|
|
2910
3013
|
const storyMetrics = storyMetricDecisions.map((d) => {
|
|
2911
3014
|
const colonIdx = d.key.indexOf(":");
|
|
@@ -2935,12 +3038,31 @@ async function runMetricsAction(options) {
|
|
|
2935
3038
|
};
|
|
2936
3039
|
}
|
|
2937
3040
|
});
|
|
2938
|
-
if (outputFormat === "json")
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
if (
|
|
3041
|
+
if (outputFormat === "json") {
|
|
3042
|
+
const jsonPayload = {
|
|
3043
|
+
runs,
|
|
3044
|
+
story_metrics: storyMetrics
|
|
3045
|
+
};
|
|
3046
|
+
if (doltMetrics !== void 0) if (aggregate) {
|
|
3047
|
+
const aggregateResults = doltMetrics.map((m) => ({
|
|
3048
|
+
task_type: m.taskType,
|
|
3049
|
+
count: m.count ?? 0,
|
|
3050
|
+
avg_cost_usd: m.costUsd ?? 0,
|
|
3051
|
+
sum_tokens_in: m.tokensIn ?? 0,
|
|
3052
|
+
sum_tokens_out: m.tokensOut ?? 0
|
|
3053
|
+
}));
|
|
3054
|
+
const aggregateTotals = {
|
|
3055
|
+
total_count: aggregateResults.reduce((sum, r) => sum + r.count, 0),
|
|
3056
|
+
total_avg_cost_usd: aggregateResults.reduce((sum, r) => sum + r.avg_cost_usd, 0),
|
|
3057
|
+
total_tokens_in: aggregateResults.reduce((sum, r) => sum + r.sum_tokens_in, 0),
|
|
3058
|
+
total_tokens_out: aggregateResults.reduce((sum, r) => sum + r.sum_tokens_out, 0)
|
|
3059
|
+
};
|
|
3060
|
+
jsonPayload.aggregate_metrics = aggregateResults;
|
|
3061
|
+
jsonPayload.aggregate_totals = aggregateTotals;
|
|
3062
|
+
} else jsonPayload.dolt_metrics = doltMetrics;
|
|
3063
|
+
process.stdout.write(formatOutput(jsonPayload, "json", true) + "\n");
|
|
3064
|
+
} else {
|
|
3065
|
+
if (runs.length === 0 && storyMetrics.length === 0 && (doltMetrics === void 0 || doltMetrics.length === 0)) {
|
|
2944
3066
|
process.stdout.write("No run metrics recorded yet. Run `substrate run` to generate metrics.\n");
|
|
2945
3067
|
return 0;
|
|
2946
3068
|
}
|
|
@@ -2970,6 +3092,41 @@ async function runMetricsAction(options) {
|
|
|
2970
3092
|
process.stdout.write(` ${sm.story_key.padEnd(16)} ${runShort.padEnd(12)} ${String(sm.wall_clock_seconds).padStart(8)} ${sm.input_tokens.toLocaleString().padStart(10)} ${sm.output_tokens.toLocaleString().padStart(11)} ${String(sm.review_cycles).padStart(7)} ${stalledStr.padStart(8)}${costStr}\n`);
|
|
2971
3093
|
}
|
|
2972
3094
|
}
|
|
3095
|
+
if (doltMetrics !== void 0 && doltMetrics.length > 0) if (aggregate) {
|
|
3096
|
+
process.stdout.write(`\nStateStore Aggregate Metrics (by task type)\n`);
|
|
3097
|
+
process.stdout.write("─".repeat(80) + "\n");
|
|
3098
|
+
process.stdout.write(` ${"Task Type".padEnd(20)} ${"Count".padStart(8)} ${"Avg Cost".padStart(12)} ${"Sum Tokens In".padStart(14)} ${"Sum Tokens Out".padStart(15)}\n`);
|
|
3099
|
+
process.stdout.write(" " + "─".repeat(72) + "\n");
|
|
3100
|
+
let totalCount = 0;
|
|
3101
|
+
let totalCost = 0;
|
|
3102
|
+
let totalTokensIn = 0;
|
|
3103
|
+
let totalTokensOut = 0;
|
|
3104
|
+
for (const m of doltMetrics) {
|
|
3105
|
+
const count = m.count ?? 0;
|
|
3106
|
+
const avgCost = m.costUsd !== void 0 ? `$${m.costUsd.toFixed(4)}` : "-";
|
|
3107
|
+
const sumIn = m.tokensIn !== void 0 ? m.tokensIn.toLocaleString() : "-";
|
|
3108
|
+
const sumOut = m.tokensOut !== void 0 ? m.tokensOut.toLocaleString() : "-";
|
|
3109
|
+
totalCount += count;
|
|
3110
|
+
totalCost += m.costUsd ?? 0;
|
|
3111
|
+
totalTokensIn += m.tokensIn ?? 0;
|
|
3112
|
+
totalTokensOut += m.tokensOut ?? 0;
|
|
3113
|
+
process.stdout.write(` ${m.taskType.padEnd(20)} ${String(count).padStart(8)} ${avgCost.padStart(12)} ${sumIn.padStart(14)} ${sumOut.padStart(15)}\n`);
|
|
3114
|
+
}
|
|
3115
|
+
process.stdout.write(" " + "─".repeat(72) + "\n");
|
|
3116
|
+
process.stdout.write(` ${"TOTAL".padEnd(20)} ${String(totalCount).padStart(8)} ${`$${totalCost.toFixed(4)}`.padStart(12)} ${totalTokensIn.toLocaleString().padStart(14)} ${totalTokensOut.toLocaleString().padStart(15)}\n`);
|
|
3117
|
+
} else {
|
|
3118
|
+
process.stdout.write(`\nStateStore Metrics (${doltMetrics.length} records)\n`);
|
|
3119
|
+
process.stdout.write("─".repeat(80) + "\n");
|
|
3120
|
+
process.stdout.write(` ${"Story".padEnd(16)} ${"Task Type".padEnd(16)} ${"Tokens In".padStart(10)} ${"Tokens Out".padStart(11)} ${"Wall(ms)".padStart(10)} ${"Result".padEnd(12)}\n`);
|
|
3121
|
+
process.stdout.write(" " + "─".repeat(76) + "\n");
|
|
3122
|
+
for (const m of doltMetrics) {
|
|
3123
|
+
const tokIn = m.tokensIn !== void 0 ? m.tokensIn.toLocaleString() : "-";
|
|
3124
|
+
const tokOut = m.tokensOut !== void 0 ? m.tokensOut.toLocaleString() : "-";
|
|
3125
|
+
const wall = m.wallClockMs !== void 0 ? String(m.wallClockMs) : "-";
|
|
3126
|
+
const res = m.result ?? "-";
|
|
3127
|
+
process.stdout.write(` ${m.storyKey.padEnd(16)} ${m.taskType.padEnd(16)} ${tokIn.padStart(10)} ${tokOut.padStart(11)} ${wall.padStart(10)} ${res.padEnd(12)}\n`);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
2973
3130
|
}
|
|
2974
3131
|
return 0;
|
|
2975
3132
|
} catch (err) {
|
|
@@ -2985,7 +3142,7 @@ async function runMetricsAction(options) {
|
|
|
2985
3142
|
}
|
|
2986
3143
|
}
|
|
2987
3144
|
function registerMetricsCommand(program, _version = "0.0.0", projectRoot = process.cwd()) {
|
|
2988
|
-
program.command("metrics").description("Show historical pipeline run metrics and cross-run comparison").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--limit <n>", "Number of runs to show (default: 10)", (v) => parseInt(v, 10), 10).option("--compare <run-id-a,run-id-b>", "Compare two runs side-by-side (comma-separated IDs, e.g. abc123,def456)").option("--tag-baseline <run-id>", "Mark a run as the performance baseline").option("--analysis <run-id>", "Read and output the analysis report for the specified run (AC5 of Story 17-3)").action(async (opts) => {
|
|
3145
|
+
program.command("metrics").description("Show historical pipeline run metrics and cross-run comparison").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--limit <n>", "Number of runs to show (default: 10)", (v) => parseInt(v, 10), 10).option("--compare <run-id-a,run-id-b>", "Compare two runs side-by-side (comma-separated IDs, e.g. abc123,def456)").option("--tag-baseline <run-id>", "Mark a run as the performance baseline").option("--analysis <run-id>", "Read and output the analysis report for the specified run (AC5 of Story 17-3)").option("--sprint <sprint>", "Filter StateStore metrics by sprint (e.g. sprint-1)").option("--story <story-key>", "Filter StateStore metrics by story key (e.g. 26-1)").option("--task-type <type>", "Filter StateStore metrics by task type (e.g. dev-story)").option("--since <iso-date>", "Filter StateStore metrics at or after this ISO timestamp").option("--aggregate", "Aggregate StateStore metrics grouped by task_type").action(async (opts) => {
|
|
2989
3146
|
const outputFormat = opts.outputFormat === "json" ? "json" : "human";
|
|
2990
3147
|
let compareIds;
|
|
2991
3148
|
if (opts.compare !== void 0) {
|
|
@@ -2998,12 +3155,165 @@ function registerMetricsCommand(program, _version = "0.0.0", projectRoot = proce
|
|
|
2998
3155
|
limit: opts.limit,
|
|
2999
3156
|
compare: compareIds,
|
|
3000
3157
|
tagBaseline: opts.tagBaseline,
|
|
3001
|
-
analysis: opts.analysis
|
|
3158
|
+
analysis: opts.analysis,
|
|
3159
|
+
sprint: opts.sprint,
|
|
3160
|
+
story: opts.story,
|
|
3161
|
+
taskType: opts.taskType,
|
|
3162
|
+
since: opts.since,
|
|
3163
|
+
aggregate: opts.aggregate
|
|
3002
3164
|
});
|
|
3003
3165
|
process.exitCode = exitCode;
|
|
3004
3166
|
});
|
|
3005
3167
|
}
|
|
3006
3168
|
|
|
3169
|
+
//#endregion
|
|
3170
|
+
//#region src/cli/commands/migrate.ts
|
|
3171
|
+
/**
|
|
3172
|
+
* Open the SQLite database at `dbPath` (read-only) and return a snapshot of
|
|
3173
|
+
* the story_metrics rows. Returns an empty snapshot if the file does not
|
|
3174
|
+
* exist or the table is missing.
|
|
3175
|
+
*/
|
|
3176
|
+
function readSqliteSnapshot(dbPath) {
|
|
3177
|
+
let db = null;
|
|
3178
|
+
try {
|
|
3179
|
+
db = new Database(dbPath, { readonly: true });
|
|
3180
|
+
} catch {
|
|
3181
|
+
return { storyMetrics: [] };
|
|
3182
|
+
}
|
|
3183
|
+
try {
|
|
3184
|
+
const rows = db.prepare(`SELECT story_key, result, completed_at, created_at,
|
|
3185
|
+
wall_clock_seconds, input_tokens, output_tokens,
|
|
3186
|
+
cost_usd, review_cycles
|
|
3187
|
+
FROM story_metrics`).all();
|
|
3188
|
+
return { storyMetrics: rows };
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3191
|
+
process.stderr.write(`Warning: could not read story_metrics from SQLite: ${msg}\n`);
|
|
3192
|
+
return { storyMetrics: [] };
|
|
3193
|
+
} finally {
|
|
3194
|
+
db.close();
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
const BATCH_SIZE = 100;
|
|
3198
|
+
/**
|
|
3199
|
+
* Upsert story_metrics rows into the Dolt `metrics` table.
|
|
3200
|
+
* When `dryRun` is `true`, no queries are executed.
|
|
3201
|
+
*/
|
|
3202
|
+
async function migrateDataToDolt(client, rows, dryRun) {
|
|
3203
|
+
let metricsWritten = 0;
|
|
3204
|
+
let skipped = 0;
|
|
3205
|
+
const valid = [];
|
|
3206
|
+
for (const row of rows) {
|
|
3207
|
+
if (!row.story_key) {
|
|
3208
|
+
skipped++;
|
|
3209
|
+
continue;
|
|
3210
|
+
}
|
|
3211
|
+
const recordedAt = row.completed_at ?? row.created_at;
|
|
3212
|
+
if (!recordedAt) {
|
|
3213
|
+
skipped++;
|
|
3214
|
+
continue;
|
|
3215
|
+
}
|
|
3216
|
+
valid.push(row);
|
|
3217
|
+
}
|
|
3218
|
+
if (dryRun) return {
|
|
3219
|
+
metricsWritten: valid.length,
|
|
3220
|
+
skipped
|
|
3221
|
+
};
|
|
3222
|
+
for (let i = 0; i < valid.length; i += BATCH_SIZE) {
|
|
3223
|
+
const batch = valid.slice(i, i + BATCH_SIZE);
|
|
3224
|
+
const placeholderRow = "(?,?,?,?,?,?,?,?,?,?,?,?)";
|
|
3225
|
+
const placeholders = batch.map(() => placeholderRow).join(", ");
|
|
3226
|
+
const sql = `INSERT INTO metrics (story_key, task_type, recorded_at, model, tokens_in, tokens_out, cache_read_tokens, cost_usd, wall_clock_ms, review_cycles, stall_count, result) VALUES ${placeholders} ON DUPLICATE KEY UPDATE cost_usd = VALUES(cost_usd), wall_clock_ms = VALUES(wall_clock_ms), result = VALUES(result)`;
|
|
3227
|
+
const params = [];
|
|
3228
|
+
for (const row of batch) params.push(row.story_key, "pipeline-run", row.completed_at ?? row.created_at, null, row.input_tokens ?? 0, row.output_tokens ?? 0, 0, row.cost_usd ?? 0, Math.round((row.wall_clock_seconds ?? 0) * 1e3), row.review_cycles ?? 0, 0, row.result);
|
|
3229
|
+
await client.query(sql, params);
|
|
3230
|
+
metricsWritten += batch.length;
|
|
3231
|
+
}
|
|
3232
|
+
return {
|
|
3233
|
+
metricsWritten,
|
|
3234
|
+
skipped
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
function registerMigrateCommand(program) {
|
|
3238
|
+
program.command("migrate").description("Migrate historical SQLite data into Dolt").option("--dry-run", "Show counts without writing any data", false).option("--output-format <format>", "Output format: text or json", "text").option("--project-root <path>", "Project root directory (defaults to cwd)", process.cwd()).action(async (options) => {
|
|
3239
|
+
const projectRoot = await resolveMainRepoRoot(options.projectRoot ?? process.cwd());
|
|
3240
|
+
const statePath = join$1(projectRoot, ".substrate", "state");
|
|
3241
|
+
const doltStatePath = join$1(statePath, ".dolt");
|
|
3242
|
+
const doltNotInitializedMsg = "Dolt not initialized. Run 'substrate init --dolt' first.";
|
|
3243
|
+
try {
|
|
3244
|
+
await checkDoltInstalled();
|
|
3245
|
+
} catch (err) {
|
|
3246
|
+
if (err instanceof DoltNotInstalled) {
|
|
3247
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
3248
|
+
error: "ERR_DOLT_NOT_INITIALIZED",
|
|
3249
|
+
message: doltNotInitializedMsg
|
|
3250
|
+
}));
|
|
3251
|
+
else process.stderr.write(doltNotInitializedMsg + "\n");
|
|
3252
|
+
process.exitCode = 1;
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
process.stderr.write(`Unexpected error checking Dolt: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
3256
|
+
process.exitCode = 2;
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
if (!existsSync$1(doltStatePath)) {
|
|
3260
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
3261
|
+
error: "ERR_DOLT_NOT_INITIALIZED",
|
|
3262
|
+
message: doltNotInitializedMsg
|
|
3263
|
+
}));
|
|
3264
|
+
else process.stderr.write(doltNotInitializedMsg + "\n");
|
|
3265
|
+
process.exitCode = 1;
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
const dbPath = join$1(projectRoot, ".substrate", "substrate.db");
|
|
3269
|
+
const snapshot = readSqliteSnapshot(dbPath);
|
|
3270
|
+
if (snapshot.storyMetrics.length === 0) {
|
|
3271
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
3272
|
+
migrated: false,
|
|
3273
|
+
reason: "no-sqlite-data"
|
|
3274
|
+
}));
|
|
3275
|
+
else console.log("No SQLite data found — nothing to migrate");
|
|
3276
|
+
return;
|
|
3277
|
+
}
|
|
3278
|
+
const repoPath = statePath;
|
|
3279
|
+
const client = createDoltClient({ repoPath });
|
|
3280
|
+
try {
|
|
3281
|
+
await client.connect();
|
|
3282
|
+
if (options.dryRun) {
|
|
3283
|
+
const result$1 = await migrateDataToDolt(client, snapshot.storyMetrics, true);
|
|
3284
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
3285
|
+
migrated: false,
|
|
3286
|
+
dryRun: true,
|
|
3287
|
+
counts: { metrics: result$1.metricsWritten }
|
|
3288
|
+
}));
|
|
3289
|
+
else console.log(`Would migrate ${result$1.metricsWritten} story metrics (dry run — no changes written)`);
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
const result = await migrateDataToDolt(client, snapshot.storyMetrics, false);
|
|
3293
|
+
if (result.metricsWritten > 0) try {
|
|
3294
|
+
await client.exec("add .");
|
|
3295
|
+
await client.exec("commit -m \"Migrate historical data from SQLite\"");
|
|
3296
|
+
} catch (execErr) {
|
|
3297
|
+
const msg = execErr instanceof Error ? execErr.message : String(execErr);
|
|
3298
|
+
process.stderr.write(`Warning: Dolt commit failed (non-fatal): ${msg}\n`);
|
|
3299
|
+
}
|
|
3300
|
+
if (result.skipped > 0) process.stderr.write(`Warning: Skipped ${result.skipped} row(s) — missing story_key or recorded_at.\n`);
|
|
3301
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
3302
|
+
migrated: true,
|
|
3303
|
+
counts: { metrics: result.metricsWritten },
|
|
3304
|
+
skipped: result.skipped
|
|
3305
|
+
}));
|
|
3306
|
+
else console.log(`Migrated ${result.metricsWritten} story metrics.`);
|
|
3307
|
+
} catch (err) {
|
|
3308
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3309
|
+
process.stderr.write(`Migration failed: ${msg}\n`);
|
|
3310
|
+
process.exitCode = 2;
|
|
3311
|
+
} finally {
|
|
3312
|
+
await client.close();
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3007
3317
|
//#endregion
|
|
3008
3318
|
//#region src/persistence/queries/cost.ts
|
|
3009
3319
|
const stmtCache = new WeakMap();
|
|
@@ -3590,7 +3900,7 @@ var MonitorDatabaseImpl = class {
|
|
|
3590
3900
|
}
|
|
3591
3901
|
_open() {
|
|
3592
3902
|
logger$9.info({ path: this._path }, "Opening monitor database");
|
|
3593
|
-
this._db = new
|
|
3903
|
+
this._db = new Database(this._path);
|
|
3594
3904
|
const walResult = this._db.pragma("journal_mode = WAL");
|
|
3595
3905
|
if (walResult?.[0]?.journal_mode !== "wal") logger$9.warn({ result: walResult?.[0]?.journal_mode }, "Monitor DB: WAL pragma did not confirm wal mode");
|
|
3596
3906
|
this._db.pragma("synchronous = NORMAL");
|
|
@@ -6597,6 +6907,257 @@ function registerRetryEscalatedCommand(program, _version = "0.0.0", projectRoot
|
|
|
6597
6907
|
});
|
|
6598
6908
|
}
|
|
6599
6909
|
|
|
6910
|
+
//#endregion
|
|
6911
|
+
//#region src/cli/commands/contracts.ts
|
|
6912
|
+
function registerContractsCommand(program) {
|
|
6913
|
+
program.command("contracts").description("Show contract declarations and verification status").option("--output-format <format>", "Output format: text or json", "text").action(async (options) => {
|
|
6914
|
+
const dbRoot = await resolveMainRepoRoot(process.cwd());
|
|
6915
|
+
const statePath = join$1(dbRoot, ".substrate", "state");
|
|
6916
|
+
const doltStatePath = join$1(statePath, ".dolt");
|
|
6917
|
+
const storeConfig = existsSync$1(doltStatePath) ? {
|
|
6918
|
+
backend: "dolt",
|
|
6919
|
+
basePath: statePath
|
|
6920
|
+
} : {
|
|
6921
|
+
backend: "file",
|
|
6922
|
+
basePath: statePath
|
|
6923
|
+
};
|
|
6924
|
+
const store = createStateStore(storeConfig);
|
|
6925
|
+
try {
|
|
6926
|
+
await store.initialize();
|
|
6927
|
+
const contracts = await store.queryContracts();
|
|
6928
|
+
if (contracts.length === 0) {
|
|
6929
|
+
console.log("No contracts stored. Run a pipeline to populate contract data.");
|
|
6930
|
+
return;
|
|
6931
|
+
}
|
|
6932
|
+
const storyKeys = [...new Set(contracts.map((c) => c.storyKey))];
|
|
6933
|
+
const verificationMap = new Map();
|
|
6934
|
+
for (const sk of storyKeys) {
|
|
6935
|
+
const verifications = await store.getContractVerification(sk);
|
|
6936
|
+
const contractVerdicts = new Map();
|
|
6937
|
+
for (const v of verifications) contractVerdicts.set(v.contractName, v.verdict);
|
|
6938
|
+
verificationMap.set(sk, contractVerdicts);
|
|
6939
|
+
}
|
|
6940
|
+
const mergedRecords = contracts.map((c) => {
|
|
6941
|
+
const verdicts = verificationMap.get(c.storyKey);
|
|
6942
|
+
const verdict = verdicts?.get(c.contractName);
|
|
6943
|
+
return {
|
|
6944
|
+
storyKey: c.storyKey,
|
|
6945
|
+
contractName: c.contractName,
|
|
6946
|
+
direction: c.direction,
|
|
6947
|
+
schemaPath: c.schemaPath,
|
|
6948
|
+
verificationStatus: verdict === "pass" ? "✓ pass" : verdict === "fail" ? "✗ fail" : "? pending",
|
|
6949
|
+
verdict: verdict ?? "pending"
|
|
6950
|
+
};
|
|
6951
|
+
});
|
|
6952
|
+
if (options.outputFormat === "json") {
|
|
6953
|
+
console.log(JSON.stringify(mergedRecords, null, 2));
|
|
6954
|
+
return;
|
|
6955
|
+
}
|
|
6956
|
+
const headers = [
|
|
6957
|
+
"Story Key",
|
|
6958
|
+
"Contract Name",
|
|
6959
|
+
"Direction",
|
|
6960
|
+
"Schema Path",
|
|
6961
|
+
"Status"
|
|
6962
|
+
];
|
|
6963
|
+
const rows = mergedRecords.map((r) => [
|
|
6964
|
+
r.storyKey,
|
|
6965
|
+
r.contractName,
|
|
6966
|
+
r.direction,
|
|
6967
|
+
r.schemaPath,
|
|
6968
|
+
r.verificationStatus
|
|
6969
|
+
]);
|
|
6970
|
+
const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length)));
|
|
6971
|
+
const formatRow = (cells) => cells.map((c, i) => c.padEnd(colWidths[i])).join(" ");
|
|
6972
|
+
console.log(formatRow(headers));
|
|
6973
|
+
console.log(colWidths.map((w) => "-".repeat(w)).join(" "));
|
|
6974
|
+
for (const row of rows) console.log(formatRow(row));
|
|
6975
|
+
} finally {
|
|
6976
|
+
await store.close();
|
|
6977
|
+
}
|
|
6978
|
+
});
|
|
6979
|
+
}
|
|
6980
|
+
|
|
6981
|
+
//#endregion
|
|
6982
|
+
//#region src/utils/degraded-mode-hint.ts
|
|
6983
|
+
/**
|
|
6984
|
+
* Determine the appropriate degraded-mode hint message based on whether Dolt
|
|
6985
|
+
* is installed and/or initialized at the given state path.
|
|
6986
|
+
*
|
|
6987
|
+
* @param statePath - Absolute path to the substrate state directory
|
|
6988
|
+
* (e.g. `/project/.substrate/state`).
|
|
6989
|
+
*/
|
|
6990
|
+
async function getDegradedModeHint(statePath) {
|
|
6991
|
+
try {
|
|
6992
|
+
await checkDoltInstalled();
|
|
6993
|
+
if (!existsSync$1(join$1(statePath, ".dolt"))) return {
|
|
6994
|
+
hint: "Note: Dolt is installed but not initialized. Run `substrate init --dolt` to enable diff and history features.",
|
|
6995
|
+
doltInstalled: true
|
|
6996
|
+
};
|
|
6997
|
+
return {
|
|
6998
|
+
hint: "Note: Running on file backend. Diff and history require Dolt.",
|
|
6999
|
+
doltInstalled: true
|
|
7000
|
+
};
|
|
7001
|
+
} catch (err) {
|
|
7002
|
+
if (err instanceof DoltNotInstalled) return {
|
|
7003
|
+
hint: "Note: Dolt is not installed. Install it from https://docs.dolthub.com/introduction/installation, then run `substrate init --dolt` to enable diff and history features.",
|
|
7004
|
+
doltInstalled: false
|
|
7005
|
+
};
|
|
7006
|
+
throw err;
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
/**
|
|
7010
|
+
* Emit a degraded-mode hint for the given command.
|
|
7011
|
+
*
|
|
7012
|
+
* - **Text mode**: writes the hint to `process.stderr` (not stdout).
|
|
7013
|
+
* - **JSON mode**: does NOT write to stderr; the caller is responsible for
|
|
7014
|
+
* writing the returned `hint` field to stdout as part of its JSON envelope.
|
|
7015
|
+
*
|
|
7016
|
+
* @param options - Hint options including output format, command name, and
|
|
7017
|
+
* the resolved state directory path.
|
|
7018
|
+
* @returns The hint message and a flag indicating whether Dolt is installed.
|
|
7019
|
+
*/
|
|
7020
|
+
async function emitDegradedModeHint(options) {
|
|
7021
|
+
const { hint, doltInstalled } = await getDegradedModeHint(options.statePath);
|
|
7022
|
+
if (options.outputFormat !== "json") process.stderr.write(`\n${hint}\n`);
|
|
7023
|
+
return {
|
|
7024
|
+
hint,
|
|
7025
|
+
doltInstalled
|
|
7026
|
+
};
|
|
7027
|
+
}
|
|
7028
|
+
|
|
7029
|
+
//#endregion
|
|
7030
|
+
//#region src/cli/commands/diff.ts
|
|
7031
|
+
function registerDiffCommand(program) {
|
|
7032
|
+
program.command("diff [storyKey]").description("Show stat-based diff of database changes for a story or sprint").option("--sprint <sprintId>", "Diff all stories in the specified sprint").option("--output-format <format>", "Output format: text or json", "text").action(async (storyKey, options) => {
|
|
7033
|
+
if (storyKey === void 0 && options.sprint === void 0) {
|
|
7034
|
+
console.error("Error: provide a story key or --sprint <sprintId>");
|
|
7035
|
+
process.exitCode = 1;
|
|
7036
|
+
return;
|
|
7037
|
+
}
|
|
7038
|
+
const dbRoot = await resolveMainRepoRoot(process.cwd());
|
|
7039
|
+
const statePath = join$1(dbRoot, ".substrate", "state");
|
|
7040
|
+
const doltStatePath = join$1(statePath, ".dolt");
|
|
7041
|
+
const storeConfig = existsSync$1(doltStatePath) ? {
|
|
7042
|
+
backend: "dolt",
|
|
7043
|
+
basePath: statePath
|
|
7044
|
+
} : {
|
|
7045
|
+
backend: "file",
|
|
7046
|
+
basePath: statePath
|
|
7047
|
+
};
|
|
7048
|
+
const store = createStateStore(storeConfig);
|
|
7049
|
+
try {
|
|
7050
|
+
await store.initialize();
|
|
7051
|
+
if (store instanceof FileStateStore) {
|
|
7052
|
+
const result = await emitDegradedModeHint({
|
|
7053
|
+
outputFormat: options.outputFormat,
|
|
7054
|
+
command: "diff",
|
|
7055
|
+
statePath
|
|
7056
|
+
});
|
|
7057
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
7058
|
+
backend: "file",
|
|
7059
|
+
hint: result.hint,
|
|
7060
|
+
diff: null
|
|
7061
|
+
}));
|
|
7062
|
+
return;
|
|
7063
|
+
}
|
|
7064
|
+
if (storyKey !== void 0) {
|
|
7065
|
+
const diff = await store.diffStory(storyKey);
|
|
7066
|
+
if (options.outputFormat === "json") {
|
|
7067
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
7068
|
+
return;
|
|
7069
|
+
}
|
|
7070
|
+
console.log(`Diff for story ${storyKey}:`);
|
|
7071
|
+
if (diff.tables.length === 0) console.log(" (no changes)");
|
|
7072
|
+
else for (const t of diff.tables) console.log(` ${t.table}: +${t.added.length} -${t.deleted.length} ~${t.modified.length}`);
|
|
7073
|
+
} else {
|
|
7074
|
+
const stories = await store.queryStories({ sprint: options.sprint });
|
|
7075
|
+
const tableMap = new Map();
|
|
7076
|
+
for (const story of stories) {
|
|
7077
|
+
const diff = await store.diffStory(story.storyKey);
|
|
7078
|
+
for (const t of diff.tables) {
|
|
7079
|
+
const existing = tableMap.get(t.table);
|
|
7080
|
+
if (existing === void 0) tableMap.set(t.table, {
|
|
7081
|
+
table: t.table,
|
|
7082
|
+
added: [...t.added],
|
|
7083
|
+
deleted: [...t.deleted],
|
|
7084
|
+
modified: [...t.modified]
|
|
7085
|
+
});
|
|
7086
|
+
else {
|
|
7087
|
+
existing.added = [...existing.added, ...t.added];
|
|
7088
|
+
existing.deleted = [...existing.deleted, ...t.deleted];
|
|
7089
|
+
existing.modified = [...existing.modified, ...t.modified];
|
|
7090
|
+
}
|
|
7091
|
+
}
|
|
7092
|
+
}
|
|
7093
|
+
const aggregated = Array.from(tableMap.values());
|
|
7094
|
+
if (options.outputFormat === "json") {
|
|
7095
|
+
console.log(JSON.stringify({
|
|
7096
|
+
sprint: options.sprint,
|
|
7097
|
+
tables: aggregated
|
|
7098
|
+
}, null, 2));
|
|
7099
|
+
return;
|
|
7100
|
+
}
|
|
7101
|
+
console.log(`Diff for sprint ${options.sprint}:`);
|
|
7102
|
+
if (aggregated.length === 0) console.log(" (no changes)");
|
|
7103
|
+
else for (const t of aggregated) console.log(` ${t.table}: +${t.added.length} -${t.deleted.length} ~${t.modified.length}`);
|
|
7104
|
+
}
|
|
7105
|
+
} finally {
|
|
7106
|
+
await store.close();
|
|
7107
|
+
}
|
|
7108
|
+
});
|
|
7109
|
+
}
|
|
7110
|
+
|
|
7111
|
+
//#endregion
|
|
7112
|
+
//#region src/cli/commands/history.ts
|
|
7113
|
+
function registerHistoryCommand(program) {
|
|
7114
|
+
program.command("history").description("Show Dolt commit history for the state repository").option("--limit <n>", "Maximum number of commits to show", "20").option("--output-format <format>", "Output format: text or json", "text").action(async (options) => {
|
|
7115
|
+
const limit = parseInt(options.limit, 10);
|
|
7116
|
+
const dbRoot = await resolveMainRepoRoot(process.cwd());
|
|
7117
|
+
const statePath = join$1(dbRoot, ".substrate", "state");
|
|
7118
|
+
const doltStatePath = join$1(statePath, ".dolt");
|
|
7119
|
+
const storeConfig = existsSync$1(doltStatePath) ? {
|
|
7120
|
+
backend: "dolt",
|
|
7121
|
+
basePath: statePath
|
|
7122
|
+
} : {
|
|
7123
|
+
backend: "file",
|
|
7124
|
+
basePath: statePath
|
|
7125
|
+
};
|
|
7126
|
+
const store = createStateStore(storeConfig);
|
|
7127
|
+
try {
|
|
7128
|
+
await store.initialize();
|
|
7129
|
+
if (store instanceof FileStateStore) {
|
|
7130
|
+
const result = await emitDegradedModeHint({
|
|
7131
|
+
outputFormat: options.outputFormat,
|
|
7132
|
+
command: "history",
|
|
7133
|
+
statePath
|
|
7134
|
+
});
|
|
7135
|
+
if (options.outputFormat === "json") console.log(JSON.stringify({
|
|
7136
|
+
backend: "file",
|
|
7137
|
+
hint: result.hint,
|
|
7138
|
+
entries: []
|
|
7139
|
+
}));
|
|
7140
|
+
return;
|
|
7141
|
+
}
|
|
7142
|
+
const entries = await store.getHistory(limit);
|
|
7143
|
+
if (entries.length === 0) {
|
|
7144
|
+
console.log("No history available.");
|
|
7145
|
+
return;
|
|
7146
|
+
}
|
|
7147
|
+
if (options.outputFormat === "json") {
|
|
7148
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
7149
|
+
return;
|
|
7150
|
+
}
|
|
7151
|
+
for (const entry of entries) {
|
|
7152
|
+
const storyKeyCol = (entry.storyKey ?? "-").padEnd(8);
|
|
7153
|
+
console.log(`${entry.hash} ${entry.timestamp} ${storyKeyCol} ${entry.message}`);
|
|
7154
|
+
}
|
|
7155
|
+
} finally {
|
|
7156
|
+
await store.close();
|
|
7157
|
+
}
|
|
7158
|
+
});
|
|
7159
|
+
}
|
|
7160
|
+
|
|
6600
7161
|
//#endregion
|
|
6601
7162
|
//#region src/cli/index.ts
|
|
6602
7163
|
process.setMaxListeners(20);
|
|
@@ -6645,6 +7206,10 @@ async function createProgram() {
|
|
|
6645
7206
|
registerSupervisorCommand(program, version);
|
|
6646
7207
|
registerMetricsCommand(program, version);
|
|
6647
7208
|
registerRetryEscalatedCommand(program, version, process.cwd(), registry);
|
|
7209
|
+
registerContractsCommand(program);
|
|
7210
|
+
registerDiffCommand(program);
|
|
7211
|
+
registerHistoryCommand(program);
|
|
7212
|
+
registerMigrateCommand(program);
|
|
6648
7213
|
registerCostCommand(program, version);
|
|
6649
7214
|
registerMonitorCommand(program, version);
|
|
6650
7215
|
registerMergeCommand(program);
|