substrate-ai 0.4.10 → 0.5.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,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DatabaseWrapper, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, TelemetryPersistence, 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-BErnJT9a.js";
2
+ import { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-GqmIa5YW.js";
3
3
  import { createLogger } from "../logger-D2fS2ccL.js";
4
- import { AdapterRegistry } from "../adapter-registry-Cd-7lG5v.js";
4
+ import { AdapterRegistry } from "../adapter-registry-BkUvZSKJ.js";
5
5
  import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DtZW1maj.js";
6
6
  import { ConfigError, createEventBus } from "../helpers-BihqWgVe.js";
7
7
  import { RoutingRecommender } from "../routing-BUE9pIxW.js";
8
- import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-Db8GTbH2.js";
9
- import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-C0_y8DAs.js";
8
+ import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-C6MF2Cax.js";
9
+ import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-CidppHy-.js";
10
10
  import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-C-fdrHF_.js";
11
11
  import "../version-manager-impl-DTlmGvHb.js";
12
12
  import { registerUpgradeCommand } from "../upgrade-C8_VcI8B.js";
@@ -19,9 +19,8 @@ import yaml from "js-yaml";
19
19
  import { createRequire } from "node:module";
20
20
  import * as path$1 from "node:path";
21
21
  import { isAbsolute, join as join$1 } from "node:path";
22
- import Database from "better-sqlite3";
23
- import { access as access$1, readFile as readFile$1 } from "node:fs/promises";
24
22
  import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, writeFileSync as writeFileSync$1 } from "node:fs";
23
+ import { access as access$1, readFile as readFile$1 } from "node:fs/promises";
25
24
  import { createInterface } from "node:readline";
26
25
  import { homedir } from "os";
27
26
  import { randomUUID } from "crypto";
@@ -666,10 +665,12 @@ async function runInitAction(options) {
666
665
  else process.stderr.write(`Error: ${errorMsg}\n`);
667
666
  return INIT_EXIT_ERROR;
668
667
  }
669
- const dbWrapper = new DatabaseWrapper(dbPath);
670
- dbWrapper.open();
671
- runMigrations(dbWrapper.db);
672
- dbWrapper.close();
668
+ const dbAdapter = createDatabaseAdapter({
669
+ backend: "auto",
670
+ basePath: projectRoot
671
+ });
672
+ await initSchema(dbAdapter);
673
+ await dbAdapter.close();
673
674
  await scaffoldClaudeMd(projectRoot);
674
675
  await scaffoldStatuslineScript(projectRoot);
675
676
  await scaffoldClaudeSettings(projectRoot);
@@ -1017,16 +1018,19 @@ async function runResumeAction(options) {
1017
1018
  const packPath = join(projectRoot, "packs", packName);
1018
1019
  const dbRoot = await resolveMainRepoRoot(projectRoot);
1019
1020
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
1020
- if (!existsSync(dbPath)) {
1021
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
1022
+ if (!existsSync(dbPath) && !existsSync(doltDir)) {
1021
1023
  const errorMsg = `Decision store not initialized. Run 'substrate init' first.`;
1022
1024
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
1023
1025
  else process.stderr.write(`Error: ${errorMsg}\n`);
1024
1026
  return 1;
1025
1027
  }
1026
- const dbWrapper = new DatabaseWrapper(dbPath);
1028
+ const adapter = createDatabaseAdapter({
1029
+ backend: "auto",
1030
+ basePath: dbRoot
1031
+ });
1027
1032
  try {
1028
- dbWrapper.open();
1029
- const db = dbWrapper.db;
1033
+ await initSchema(adapter);
1030
1034
  const packLoader = createPackLoader();
1031
1035
  let pack;
1032
1036
  try {
@@ -1039,8 +1043,10 @@ async function runResumeAction(options) {
1039
1043
  return 1;
1040
1044
  }
1041
1045
  let run;
1042
- if (specifiedRunId !== void 0 && specifiedRunId !== "") run = db.prepare("SELECT * FROM pipeline_runs WHERE id = ?").get(specifiedRunId);
1043
- else run = getLatestRun(db);
1046
+ if (specifiedRunId !== void 0 && specifiedRunId !== "") {
1047
+ const rows = await adapter.query("SELECT * FROM pipeline_runs WHERE id = ?", [specifiedRunId]);
1048
+ run = rows[0];
1049
+ } else run = await getLatestRun(adapter);
1044
1050
  if (run === void 0) {
1045
1051
  const errorMsg = specifiedRunId !== void 0 ? `Pipeline run '${specifiedRunId}' not found.` : "No pipeline runs found. Run `substrate run --from analysis` first.";
1046
1052
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -1050,7 +1056,7 @@ async function runResumeAction(options) {
1050
1056
  const runId = run.id;
1051
1057
  if (outputFormat === "human") process.stdout.write(`Resuming pipeline run: ${runId}\n`);
1052
1058
  const phaseOrchestrator = createPhaseOrchestrator({
1053
- db,
1059
+ db: adapter,
1054
1060
  pack
1055
1061
  });
1056
1062
  const runStatus = await phaseOrchestrator.resumeRun(runId);
@@ -1092,18 +1098,19 @@ async function runResumeAction(options) {
1092
1098
  return 1;
1093
1099
  } finally {
1094
1100
  try {
1095
- dbWrapper.close();
1101
+ await adapter.close();
1096
1102
  } catch {}
1097
1103
  }
1098
1104
  }
1099
1105
  async function runFullPipelineFromPhase(options) {
1100
1106
  const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, existingRunId, projectRoot, registry: injectedRegistry } = options;
1101
1107
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
1102
- const dbWrapper = new DatabaseWrapper(dbPath);
1108
+ const adapter = createDatabaseAdapter({
1109
+ backend: "auto",
1110
+ basePath: projectRoot
1111
+ });
1103
1112
  try {
1104
- dbWrapper.open();
1105
- runMigrations(dbWrapper.db);
1106
- const db = dbWrapper.db;
1113
+ await initSchema(adapter);
1107
1114
  const packLoader = createPackLoader();
1108
1115
  let pack;
1109
1116
  try {
@@ -1116,20 +1123,20 @@ async function runFullPipelineFromPhase(options) {
1116
1123
  return 1;
1117
1124
  }
1118
1125
  const eventBus = createEventBus();
1119
- const contextCompiler = createContextCompiler({ db });
1126
+ const contextCompiler = createContextCompiler({ db: adapter });
1120
1127
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
1121
1128
  const dispatcher = createDispatcher({
1122
1129
  eventBus,
1123
1130
  adapterRegistry: injectedRegistry
1124
1131
  });
1125
1132
  const phaseDeps = {
1126
- db,
1133
+ db: adapter,
1127
1134
  pack,
1128
1135
  contextCompiler,
1129
1136
  dispatcher
1130
1137
  };
1131
1138
  const phaseOrchestrator = createPhaseOrchestrator({
1132
- db,
1139
+ db: adapter,
1133
1140
  pack
1134
1141
  });
1135
1142
  const startedAt = Date.now();
@@ -1153,7 +1160,7 @@ async function runFullPipelineFromPhase(options) {
1153
1160
  });
1154
1161
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
1155
1162
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
1156
- addTokenUsage(db, runId, {
1163
+ await addTokenUsage(adapter, runId, {
1157
1164
  phase: "analysis",
1158
1165
  agent: "claude-code",
1159
1166
  input_tokens: result.tokenUsage.input,
@@ -1162,7 +1169,7 @@ async function runFullPipelineFromPhase(options) {
1162
1169
  });
1163
1170
  }
1164
1171
  if (result.result === "failed") {
1165
- updatePipelineRun(db, runId, { status: "failed" });
1172
+ await updatePipelineRun(adapter, runId, { status: "failed" });
1166
1173
  const errorMsg = `Analysis phase failed: ${result.error ?? "unknown error"}`;
1167
1174
  if (outputFormat === "human") process.stderr.write(`Error: ${errorMsg}\n`);
1168
1175
  else process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -1173,7 +1180,7 @@ async function runFullPipelineFromPhase(options) {
1173
1180
  const result = await runPlanningPhase(phaseDeps, { runId });
1174
1181
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
1175
1182
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
1176
- addTokenUsage(db, runId, {
1183
+ await addTokenUsage(adapter, runId, {
1177
1184
  phase: "planning",
1178
1185
  agent: "claude-code",
1179
1186
  input_tokens: result.tokenUsage.input,
@@ -1182,7 +1189,7 @@ async function runFullPipelineFromPhase(options) {
1182
1189
  });
1183
1190
  }
1184
1191
  if (result.result === "failed") {
1185
- updatePipelineRun(db, runId, { status: "failed" });
1192
+ await updatePipelineRun(adapter, runId, { status: "failed" });
1186
1193
  const errorMsg = `Planning phase failed: ${result.error ?? "unknown error"}`;
1187
1194
  if (outputFormat === "human") process.stderr.write(`Error: ${errorMsg}\n`);
1188
1195
  else process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -1193,7 +1200,7 @@ async function runFullPipelineFromPhase(options) {
1193
1200
  const result = await runSolutioningPhase(phaseDeps, { runId });
1194
1201
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
1195
1202
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
1196
- addTokenUsage(db, runId, {
1203
+ await addTokenUsage(adapter, runId, {
1197
1204
  phase: "solutioning",
1198
1205
  agent: "claude-code",
1199
1206
  input_tokens: result.tokenUsage.input,
@@ -1202,7 +1209,7 @@ async function runFullPipelineFromPhase(options) {
1202
1209
  });
1203
1210
  }
1204
1211
  if (result.result === "failed") {
1205
- updatePipelineRun(db, runId, { status: "failed" });
1212
+ await updatePipelineRun(adapter, runId, { status: "failed" });
1206
1213
  const errorMsg = `Solutioning phase failed: ${result.error ?? "unknown error"}`;
1207
1214
  if (outputFormat === "human") process.stderr.write(`Error: ${errorMsg}\n`);
1208
1215
  else process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -1222,9 +1229,9 @@ async function runFullPipelineFromPhase(options) {
1222
1229
  }
1223
1230
  } catch {}
1224
1231
  const ingestionServer = telemetryEnabled ? new IngestionServer({ port: telemetryPort }) : void 0;
1225
- const telemetryPersistence = telemetryEnabled ? new TelemetryPersistence(db) : void 0;
1232
+ const telemetryPersistence = telemetryEnabled ? new AdapterTelemetryPersistence(adapter) : void 0;
1226
1233
  const orchestrator = createImplementationOrchestrator({
1227
- db,
1234
+ db: adapter,
1228
1235
  pack,
1229
1236
  contextCompiler,
1230
1237
  dispatcher,
@@ -1244,19 +1251,21 @@ async function runFullPipelineFromPhase(options) {
1244
1251
  if (result?.tokenUsage !== void 0) {
1245
1252
  const { input, output } = result.tokenUsage;
1246
1253
  const costUsd = (input * 3 + output * 15) / 1e6;
1247
- addTokenUsage(db, runId, {
1254
+ addTokenUsage(adapter, runId, {
1248
1255
  phase: payload.phase,
1249
1256
  agent: "claude-code",
1250
1257
  input_tokens: input,
1251
1258
  output_tokens: output,
1252
1259
  cost_usd: costUsd
1260
+ }).catch((err) => {
1261
+ logger$16.warn({ err }, "Failed to record token usage");
1253
1262
  });
1254
1263
  }
1255
1264
  } catch (err) {
1256
1265
  logger$16.warn({ err }, "Failed to record token usage");
1257
1266
  }
1258
1267
  });
1259
- const storyKeys = resolveStoryKeys(db, projectRoot, { pipelineRunId: runId });
1268
+ const storyKeys = await resolveStoryKeys(adapter, projectRoot, { pipelineRunId: runId });
1260
1269
  if (storyKeys.length === 0 && outputFormat === "human") process.stdout.write("[IMPLEMENTATION] No stories found for this run. Check solutioning phase output.\n");
1261
1270
  await orchestrator.run(storyKeys);
1262
1271
  if (outputFormat === "human") process.stdout.write("[IMPLEMENTATION] Complete\n");
@@ -1264,8 +1273,9 @@ async function runFullPipelineFromPhase(options) {
1264
1273
  if (stopAfter !== void 0 && currentPhase === stopAfter) {
1265
1274
  const gate = createStopAfterGate(stopAfter);
1266
1275
  if (gate.shouldHalt()) {
1267
- const decisionsCount$1 = db.prepare(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`).get(runId)?.cnt ?? 0;
1268
- updatePipelineRun(db, runId, { status: "stopped" });
1276
+ const countRows = await adapter.query(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`, [runId]);
1277
+ const decisionsCount$1 = countRows[0]?.cnt ?? 0;
1278
+ await updatePipelineRun(adapter, runId, { status: "stopped" });
1269
1279
  const phaseStartedAt = new Date(startedAt).toISOString();
1270
1280
  const phaseCompletedAt = new Date().toISOString();
1271
1281
  const summary = formatPhaseCompletionSummary({
@@ -1291,11 +1301,14 @@ async function runFullPipelineFromPhase(options) {
1291
1301
  }
1292
1302
  }
1293
1303
  }
1294
- const tokenSummary = getTokenUsageSummary(db, runId);
1304
+ const tokenSummary = await getTokenUsageSummary(adapter, runId);
1295
1305
  const durationMs = Date.now() - startedAt;
1296
- const decisionsCount = db.prepare(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`).get(runId)?.cnt ?? 0;
1297
- const storiesCount = db.prepare(`SELECT COUNT(*) as cnt FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`).get(runId)?.cnt ?? 0;
1298
- const finalRun = db.prepare("SELECT * FROM pipeline_runs WHERE id = ?").get(runId);
1306
+ const decRows = await adapter.query(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`, [runId]);
1307
+ const decisionsCount = decRows[0]?.cnt ?? 0;
1308
+ const storyRows = await adapter.query(`SELECT COUNT(*) as cnt FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`, [runId]);
1309
+ const storiesCount = storyRows[0]?.cnt ?? 0;
1310
+ const finalRunRows = await adapter.query("SELECT * FROM pipeline_runs WHERE id = ?", [runId]);
1311
+ const finalRun = finalRunRows[0];
1299
1312
  if (outputFormat === "json") {
1300
1313
  const statusOutput = buildPipelineStatusOutput(finalRun ?? { id: runId }, tokenSummary, decisionsCount, storiesCount);
1301
1314
  process.stdout.write(formatOutput(statusOutput, "json", true) + "\n");
@@ -1312,7 +1325,7 @@ async function runFullPipelineFromPhase(options) {
1312
1325
  return 1;
1313
1326
  } finally {
1314
1327
  try {
1315
- dbWrapper.close();
1328
+ await adapter.close();
1316
1329
  } catch {}
1317
1330
  }
1318
1331
  }
@@ -1364,28 +1377,33 @@ async function runStatusAction(options) {
1364
1377
  }
1365
1378
  const dbRoot = await resolveMainRepoRoot(projectRoot);
1366
1379
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
1367
- if (!existsSync(dbPath)) {
1380
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
1381
+ if (!existsSync(dbPath) && !existsSync(doltDir)) {
1368
1382
  const errorMsg = `Decision store not initialized. Run 'substrate init' first.`;
1369
1383
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
1370
1384
  else process.stderr.write(`Error: ${errorMsg}\n`);
1371
1385
  return 1;
1372
1386
  }
1373
- const dbWrapper = new DatabaseWrapper(dbPath);
1387
+ const adapter = createDatabaseAdapter({
1388
+ backend: "auto",
1389
+ basePath: dbRoot
1390
+ });
1374
1391
  try {
1375
- dbWrapper.open();
1376
- const db = dbWrapper.db;
1392
+ await initSchema(adapter);
1377
1393
  let run;
1378
- if (runId !== void 0 && runId !== "") run = db.prepare("SELECT * FROM pipeline_runs WHERE id = ?").get(runId);
1379
- else run = getLatestRun(db);
1394
+ if (runId !== void 0 && runId !== "") run = await getPipelineRunById(adapter, runId);
1395
+ else run = await getLatestRun(adapter);
1380
1396
  if (run === void 0) {
1381
1397
  const errorMsg = runId !== void 0 ? `Pipeline run '${runId}' not found.` : "No pipeline runs found. Run `substrate run` first.";
1382
1398
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
1383
1399
  else process.stderr.write(`Error: ${errorMsg}\n`);
1384
1400
  return 1;
1385
1401
  }
1386
- const tokenSummary = getTokenUsageSummary(db, run.id);
1387
- const decisionsCount = db.prepare(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`).get(run.id)?.cnt ?? 0;
1388
- const storiesCount = db.prepare(`SELECT COUNT(*) as cnt FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`).get(run.id)?.cnt ?? 0;
1402
+ const tokenSummary = await getTokenUsageSummary(adapter, run.id);
1403
+ const decisionsCountRows = await adapter.query(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`, [run.id]);
1404
+ const decisionsCount = decisionsCountRows[0]?.cnt ?? 0;
1405
+ const storiesCountRows = await adapter.query(`SELECT COUNT(*) as cnt FROM requirements WHERE pipeline_run_id = ? AND source = 'solutioning-phase'`, [run.id]);
1406
+ const storiesCount = storiesCountRows[0]?.cnt ?? 0;
1389
1407
  let storeStories = [];
1390
1408
  if (stateStore) try {
1391
1409
  storeStories = await stateStore.queryStories({});
@@ -1394,7 +1412,7 @@ async function runStatusAction(options) {
1394
1412
  }
1395
1413
  if (outputFormat === "json") {
1396
1414
  const statusOutput = buildPipelineStatusOutput(run, tokenSummary, decisionsCount, storiesCount);
1397
- const storyMetricsRows = getStoryMetricsForRun(db, run.id);
1415
+ const storyMetricsRows = await getStoryMetricsForRun(adapter, run.id);
1398
1416
  const storyMetricsV2 = storyMetricsRows.map((row) => {
1399
1417
  const phaseBreakdown = {};
1400
1418
  try {
@@ -1497,7 +1515,7 @@ async function runStatusAction(options) {
1497
1515
  return 1;
1498
1516
  } finally {
1499
1517
  try {
1500
- dbWrapper.close();
1518
+ await adapter.close();
1501
1519
  } catch {}
1502
1520
  }
1503
1521
  }
@@ -1542,14 +1560,20 @@ function registerStatusCommand(program, _version = "0.0.0", projectRoot = proces
1542
1560
  * Throws if the parent run is not found or not completed.
1543
1561
  * Returns the new run's ID on success.
1544
1562
  */
1545
- function createAmendmentRun(db, input) {
1546
- const parentRun = db.prepare("SELECT id, status FROM pipeline_runs WHERE id = ?").get(input.parentRunId);
1563
+ async function createAmendmentRun(adapter, input) {
1564
+ const rows = await adapter.query("SELECT id, status FROM pipeline_runs WHERE id = ?", [input.parentRunId]);
1565
+ const parentRun = rows[0];
1547
1566
  if (!parentRun) throw new Error(`Parent run not found: ${input.parentRunId}`);
1548
1567
  if (parentRun.status !== "completed") throw new Error(`Parent run is not completed (status: ${parentRun.status}). Only completed runs can be amended.`);
1549
- db.prepare(`
1550
- INSERT INTO pipeline_runs (id, methodology, current_phase, status, config_json, parent_run_id, created_at, updated_at)
1551
- VALUES (?, ?, NULL, 'running', ?, ?, datetime('now'), datetime('now'))
1552
- `).run(input.id, input.methodology, input.configJson ?? null, input.parentRunId);
1568
+ await adapter.query(`INSERT INTO pipeline_runs (id, methodology, current_phase, status, config_json, parent_run_id, created_at, updated_at)
1569
+ VALUES (?, ?, NULL, 'running', ?, ?, ?, ?)`, [
1570
+ input.id,
1571
+ input.methodology,
1572
+ input.configJson ?? null,
1573
+ input.parentRunId,
1574
+ new Date().toISOString(),
1575
+ new Date().toISOString()
1576
+ ]);
1553
1577
  return input.id;
1554
1578
  }
1555
1579
  /**
@@ -1558,13 +1582,10 @@ function createAmendmentRun(db, input) {
1558
1582
  * Returns decisions WHERE superseded_by IS NULL for the specified run,
1559
1583
  * ordered by created_at ASC.
1560
1584
  */
1561
- function loadParentRunDecisions(db, parentRunId) {
1562
- const stmt = db.prepare(`
1563
- SELECT * FROM decisions
1564
- WHERE pipeline_run_id = ? AND superseded_by IS NULL
1565
- ORDER BY created_at ASC
1566
- `);
1567
- return stmt.all(parentRunId);
1585
+ async function loadParentRunDecisions(adapter, parentRunId) {
1586
+ return adapter.query(`SELECT * FROM decisions
1587
+ WHERE pipeline_run_id = ? AND superseded_by IS NULL
1588
+ ORDER BY created_at ASC`, [parentRunId]);
1568
1589
  }
1569
1590
  /**
1570
1591
  * Mark a decision as superseded by another decision.
@@ -1576,15 +1597,19 @@ function loadParentRunDecisions(db, parentRunId) {
1576
1597
  *
1577
1598
  * On success, updates the original decision's superseded_by field.
1578
1599
  */
1579
- function supersedeDecision(db, originalDecisionId, supersedingDecisionId) {
1580
- const original = db.prepare("SELECT id, superseded_by FROM decisions WHERE id = ?").get(originalDecisionId);
1600
+ async function supersedeDecision(adapter, originalDecisionId, supersedingDecisionId) {
1601
+ const origRows = await adapter.query("SELECT id, superseded_by FROM decisions WHERE id = ?", [originalDecisionId]);
1602
+ const original = origRows[0];
1581
1603
  if (!original) throw new Error(`Decision not found: ${originalDecisionId}`);
1582
- const superseding = db.prepare("SELECT id FROM decisions WHERE id = ?").get(supersedingDecisionId);
1604
+ const superRows = await adapter.query("SELECT id FROM decisions WHERE id = ?", [supersedingDecisionId]);
1605
+ const superseding = superRows[0];
1583
1606
  if (!superseding) throw new Error(`Superseding decision not found: ${supersedingDecisionId}`);
1584
1607
  if (original.superseded_by !== null) throw new Error(`Decision ${originalDecisionId} is already superseded`);
1585
- db.prepare(`
1586
- UPDATE decisions SET superseded_by = ?, updated_at = datetime('now') WHERE id = ?
1587
- `).run(supersedingDecisionId, originalDecisionId);
1608
+ await adapter.query("UPDATE decisions SET superseded_by = ?, updated_at = ? WHERE id = ?", [
1609
+ supersedingDecisionId,
1610
+ new Date().toISOString(),
1611
+ originalDecisionId
1612
+ ]);
1588
1613
  }
1589
1614
  /**
1590
1615
  * Get all active (non-superseded) decisions, with optional filtering.
@@ -1593,7 +1618,7 @@ function supersedeDecision(db, originalDecisionId, supersedingDecisionId) {
1593
1618
  * If no filter is provided, returns all active decisions across all runs.
1594
1619
  * Results are ordered by created_at ASC.
1595
1620
  */
1596
- function getActiveDecisions(db, filter) {
1621
+ async function getActiveDecisions(adapter, filter) {
1597
1622
  const conditions = ["superseded_by IS NULL"];
1598
1623
  const values = [];
1599
1624
  if (filter?.pipeline_run_id !== void 0) {
@@ -1609,25 +1634,22 @@ function getActiveDecisions(db, filter) {
1609
1634
  values.push(filter.category);
1610
1635
  }
1611
1636
  if (filter?.key !== void 0) {
1612
- conditions.push("key = ?");
1637
+ conditions.push("`key` = ?");
1613
1638
  values.push(filter.key);
1614
1639
  }
1615
1640
  const where = `WHERE ${conditions.join(" AND ")}`;
1616
- const stmt = db.prepare(`SELECT * FROM decisions ${where} ORDER BY created_at ASC`);
1617
- return stmt.all(...values);
1641
+ return adapter.query(`SELECT * FROM decisions ${where} ORDER BY created_at ASC`, values);
1618
1642
  }
1619
1643
  /**
1620
1644
  * Get the most recently created pipeline run with status = 'completed'.
1621
1645
  * Returns undefined if no completed run exists.
1622
1646
  */
1623
- function getLatestCompletedRun(db) {
1624
- const stmt = db.prepare(`
1625
- SELECT * FROM pipeline_runs
1626
- WHERE status = 'completed'
1627
- ORDER BY created_at DESC, rowid DESC
1628
- LIMIT 1
1629
- `);
1630
- return stmt.get();
1647
+ async function getLatestCompletedRun(adapter) {
1648
+ const rows = await adapter.query(`SELECT * FROM pipeline_runs
1649
+ WHERE status = 'completed'
1650
+ ORDER BY created_at DESC, id DESC
1651
+ LIMIT 1`);
1652
+ return rows[0];
1631
1653
  }
1632
1654
 
1633
1655
  //#endregion
@@ -1676,13 +1698,13 @@ function buildContextBlock(decisions, phaseName, framingConcept) {
1676
1698
  *
1677
1699
  * Throws if parentRunId is not found (delegates to loadParentRunDecisions()).
1678
1700
  *
1679
- * @param db - better-sqlite3 Database instance
1701
+ * @param db - DatabaseAdapter instance
1680
1702
  * @param parentRunId - ID of the completed parent pipeline run
1681
1703
  * @param options - Optional configuration (phaseFilter, framingConcept)
1682
1704
  * @returns AmendmentContextHandler instance
1683
1705
  */
1684
- function createAmendmentContextHandler(db, parentRunId, options) {
1685
- const allDecisions = loadParentRunDecisions(db, parentRunId);
1706
+ async function createAmendmentContextHandler(db, parentRunId, options) {
1707
+ const allDecisions = await loadParentRunDecisions(db, parentRunId);
1686
1708
  const parentDecisions = options?.phaseFilter && options.phaseFilter.length > 0 ? allDecisions.filter((d) => options.phaseFilter.includes(d.phase)) : allDecisions;
1687
1709
  const framingConcept = options?.framingConcept;
1688
1710
  const supersessionLog = [];
@@ -1948,8 +1970,8 @@ const logger$14 = createLogger("amend-cmd");
1948
1970
  * Errors in individual supersession calls are logged as warnings but do not
1949
1971
  * fail the phase (AC7: atomic with phase completion, non-blocking on error).
1950
1972
  */
1951
- function runPostPhaseSupersessionDetection(db, amendmentRunId, currentPhase, handler) {
1952
- const newDecisions = getActiveDecisions(db, {
1973
+ async function runPostPhaseSupersessionDetection(adapter, amendmentRunId, currentPhase, handler) {
1974
+ const newDecisions = await getActiveDecisions(adapter, {
1953
1975
  pipeline_run_id: amendmentRunId,
1954
1976
  phase: currentPhase
1955
1977
  });
@@ -1957,7 +1979,7 @@ function runPostPhaseSupersessionDetection(db, amendmentRunId, currentPhase, han
1957
1979
  for (const newDec of newDecisions) {
1958
1980
  const parentMatch = parentDecisions.find((p) => p.phase === newDec.phase && p.category === newDec.category && p.key === newDec.key);
1959
1981
  if (parentMatch) try {
1960
- supersedeDecision(db, parentMatch.id, newDec.id);
1982
+ await supersedeDecision(adapter, parentMatch.id, newDec.id);
1961
1983
  handler.logSupersession({
1962
1984
  originalDecisionId: parentMatch.id,
1963
1985
  supersedingDecisionId: newDec.id,
@@ -2010,19 +2032,21 @@ async function runAmendAction(options) {
2010
2032
  const dbDir = join(dbRoot, ".substrate");
2011
2033
  const dbPath = join(dbDir, "substrate.db");
2012
2034
  const packPath = join(projectRoot, "packs", packName);
2013
- if (!existsSync(dbPath)) {
2035
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
2036
+ if (!existsSync(dbPath) && !existsSync(doltDir)) {
2014
2037
  process.stderr.write(`Error: Decision store not initialized. Run 'substrate init' first.\n`);
2015
2038
  return 1;
2016
2039
  }
2017
- const dbWrapper = new DatabaseWrapper(dbPath);
2040
+ const adapter = createDatabaseAdapter({
2041
+ backend: "auto",
2042
+ basePath: dbRoot
2043
+ });
2018
2044
  try {
2019
- dbWrapper.open();
2020
- runMigrations(dbWrapper.db);
2021
- const db = dbWrapper.db;
2045
+ await initSchema(adapter);
2022
2046
  let parentRunId;
2023
2047
  if (specifiedRunId !== void 0 && specifiedRunId !== "") parentRunId = specifiedRunId;
2024
2048
  else {
2025
- const latestCompleted = getLatestCompletedRun(db);
2049
+ const latestCompleted = await getLatestCompletedRun(adapter);
2026
2050
  if (latestCompleted === void 0) {
2027
2051
  process.stderr.write("No completed pipeline run found. Run 'substrate run' first.\n");
2028
2052
  return 1;
@@ -2037,7 +2061,7 @@ async function runAmendAction(options) {
2037
2061
  methodology = pack$1.manifest.name;
2038
2062
  } catch {}
2039
2063
  try {
2040
- createAmendmentRun(db, {
2064
+ await createAmendmentRun(adapter, {
2041
2065
  id: amendmentRunId,
2042
2066
  parentRunId,
2043
2067
  methodology,
@@ -2052,7 +2076,7 @@ async function runAmendAction(options) {
2052
2076
  process.stderr.write(`Error: ${msg}\n`);
2053
2077
  return 1;
2054
2078
  }
2055
- const handler = createAmendmentContextHandler(db, parentRunId, { framingConcept: concept });
2079
+ const handler = await createAmendmentContextHandler(adapter, parentRunId, { framingConcept: concept });
2056
2080
  const packLoader = createPackLoader();
2057
2081
  let pack;
2058
2082
  try {
@@ -2063,14 +2087,14 @@ async function runAmendAction(options) {
2063
2087
  return 1;
2064
2088
  }
2065
2089
  const eventBus = createEventBus();
2066
- const contextCompiler = createContextCompiler({ db });
2090
+ const contextCompiler = createContextCompiler({ db: adapter });
2067
2091
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
2068
2092
  const dispatcher = createDispatcher({
2069
2093
  eventBus,
2070
2094
  adapterRegistry: injectedRegistry
2071
2095
  });
2072
2096
  const phaseDeps = {
2073
- db,
2097
+ db: adapter,
2074
2098
  pack,
2075
2099
  contextCompiler,
2076
2100
  dispatcher
@@ -2085,8 +2109,8 @@ async function runAmendAction(options) {
2085
2109
  if (startIdx > 0) {
2086
2110
  const phasesToCopy = phaseOrder.slice(0, startIdx);
2087
2111
  for (const phase of phasesToCopy) {
2088
- const parentDecisions$1 = getDecisionsByPhaseForRun(db, parentRunId, phase);
2089
- for (const d of parentDecisions$1) createDecision(db, {
2112
+ const parentDecisions$1 = await getDecisionsByPhaseForRun(adapter, parentRunId, phase);
2113
+ for (const d of parentDecisions$1) await createDecision(adapter, {
2090
2114
  pipeline_run_id: amendmentRunId,
2091
2115
  phase: d.phase,
2092
2116
  category: d.category,
@@ -2115,7 +2139,7 @@ async function runAmendAction(options) {
2115
2139
  });
2116
2140
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
2117
2141
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
2118
- addTokenUsage(db, amendmentRunId, {
2142
+ await addTokenUsage(adapter, amendmentRunId, {
2119
2143
  phase: "analysis",
2120
2144
  agent: "claude-code",
2121
2145
  input_tokens: result.tokenUsage.input,
@@ -2124,11 +2148,11 @@ async function runAmendAction(options) {
2124
2148
  });
2125
2149
  }
2126
2150
  if (result.result === "failed") {
2127
- updatePipelineRun(db, amendmentRunId, { status: "failed" });
2151
+ await updatePipelineRun(adapter, amendmentRunId, { status: "failed" });
2128
2152
  process.stderr.write(`Error: Analysis phase failed: ${result.error ?? "unknown error"}${result.details ? ` — ${result.details}` : ""}\n`);
2129
2153
  return 1;
2130
2154
  }
2131
- runPostPhaseSupersessionDetection(db, amendmentRunId, "analysis", handler);
2155
+ await runPostPhaseSupersessionDetection(adapter, amendmentRunId, "analysis", handler);
2132
2156
  process.stdout.write(`[AMENDMENT:ANALYSIS] Complete\n`);
2133
2157
  } else if (currentPhase === "planning") {
2134
2158
  const result = await runPlanningPhase(phaseDeps, {
@@ -2137,7 +2161,7 @@ async function runAmendAction(options) {
2137
2161
  });
2138
2162
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
2139
2163
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
2140
- addTokenUsage(db, amendmentRunId, {
2164
+ await addTokenUsage(adapter, amendmentRunId, {
2141
2165
  phase: "planning",
2142
2166
  agent: "claude-code",
2143
2167
  input_tokens: result.tokenUsage.input,
@@ -2146,11 +2170,11 @@ async function runAmendAction(options) {
2146
2170
  });
2147
2171
  }
2148
2172
  if (result.result === "failed") {
2149
- updatePipelineRun(db, amendmentRunId, { status: "failed" });
2173
+ await updatePipelineRun(adapter, amendmentRunId, { status: "failed" });
2150
2174
  process.stderr.write(`Error: Planning phase failed: ${result.error ?? "unknown error"}${result.details ? ` — ${result.details}` : ""}\n`);
2151
2175
  return 1;
2152
2176
  }
2153
- runPostPhaseSupersessionDetection(db, amendmentRunId, "planning", handler);
2177
+ await runPostPhaseSupersessionDetection(adapter, amendmentRunId, "planning", handler);
2154
2178
  process.stdout.write(`[AMENDMENT:PLANNING] Complete\n`);
2155
2179
  } else if (currentPhase === "solutioning") {
2156
2180
  const result = await runSolutioningPhase(phaseDeps, {
@@ -2159,7 +2183,7 @@ async function runAmendAction(options) {
2159
2183
  });
2160
2184
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
2161
2185
  const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
2162
- addTokenUsage(db, amendmentRunId, {
2186
+ await addTokenUsage(adapter, amendmentRunId, {
2163
2187
  phase: "solutioning",
2164
2188
  agent: "claude-code",
2165
2189
  input_tokens: result.tokenUsage.input,
@@ -2168,18 +2192,19 @@ async function runAmendAction(options) {
2168
2192
  });
2169
2193
  }
2170
2194
  if (result.result === "failed") {
2171
- updatePipelineRun(db, amendmentRunId, { status: "failed" });
2195
+ await updatePipelineRun(adapter, amendmentRunId, { status: "failed" });
2172
2196
  process.stderr.write(`Error: Solutioning phase failed: ${result.error ?? "unknown error"}${result.details ? ` — ${result.details}` : ""}\n`);
2173
2197
  return 1;
2174
2198
  }
2175
- runPostPhaseSupersessionDetection(db, amendmentRunId, "solutioning", handler);
2199
+ await runPostPhaseSupersessionDetection(adapter, amendmentRunId, "solutioning", handler);
2176
2200
  process.stdout.write(`[AMENDMENT:SOLUTIONING] Complete\n`);
2177
2201
  } else if (currentPhase === "implementation") process.stdout.write(`[AMENDMENT:IMPLEMENTATION] Context injected (${amendmentContext.length} chars)\n`);
2178
2202
  if (stopAfter !== void 0 && currentPhase === stopAfter) {
2179
2203
  const gate = createStopAfterGate(stopAfter);
2180
2204
  if (gate.shouldHalt()) {
2181
- const decisionsCount = db.prepare(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`).get(amendmentRunId)?.cnt ?? 0;
2182
- updatePipelineRun(db, amendmentRunId, { status: "stopped" });
2205
+ const decisionsCountRows = await adapter.query(`SELECT COUNT(*) as cnt FROM decisions WHERE pipeline_run_id = ?`, [amendmentRunId]);
2206
+ const decisionsCount = decisionsCountRows[0]?.cnt ?? 0;
2207
+ await updatePipelineRun(adapter, amendmentRunId, { status: "stopped" });
2183
2208
  const phaseStartedAt = new Date(startedAt).toISOString();
2184
2209
  const phaseCompletedAt = new Date().toISOString();
2185
2210
  const summary = formatPhaseCompletionSummary({
@@ -2196,8 +2221,8 @@ async function runAmendAction(options) {
2196
2221
  }
2197
2222
  }
2198
2223
  }
2199
- if (!stopped) updatePipelineRun(db, amendmentRunId, { status: "completed" });
2200
- const amendmentDecisions = getActiveDecisions(db, { pipeline_run_id: amendmentRunId });
2224
+ if (!stopped) await updatePipelineRun(adapter, amendmentRunId, { status: "completed" });
2225
+ const amendmentDecisions = await getActiveDecisions(adapter, { pipeline_run_id: amendmentRunId });
2201
2226
  const parentDecisions = handler.getParentDecisions();
2202
2227
  const supersessionLog = handler.getSupersessionLog();
2203
2228
  const supersededDecisionIds = new Set(supersessionLog.map((s) => s.originalDecisionId));
@@ -2226,7 +2251,7 @@ async function runAmendAction(options) {
2226
2251
  return 1;
2227
2252
  } finally {
2228
2253
  try {
2229
- dbWrapper.close();
2254
+ await adapter.close();
2230
2255
  } catch {}
2231
2256
  }
2232
2257
  }
@@ -2257,20 +2282,23 @@ function defaultSupervisorDeps() {
2257
2282
  resumePipeline: runResumeAction,
2258
2283
  sleep: (ms) => new Promise((resolve$2) => setTimeout(resolve$2, ms)),
2259
2284
  incrementRestarts: (() => {
2260
- let cachedDbWrapper = null;
2285
+ let cachedAdapter = null;
2261
2286
  return async (runId, projectRoot) => {
2262
2287
  try {
2263
- if (cachedDbWrapper === null) {
2288
+ if (cachedAdapter === null) {
2264
2289
  const dbRoot = await resolveMainRepoRoot(projectRoot);
2265
- const dbPath = join(dbRoot, ".substrate", "substrate.db");
2266
- cachedDbWrapper = new DatabaseWrapper(dbPath);
2290
+ cachedAdapter = createDatabaseAdapter({
2291
+ backend: "auto",
2292
+ basePath: dbRoot
2293
+ });
2294
+ await initSchema(cachedAdapter);
2267
2295
  }
2268
- incrementRunRestarts(cachedDbWrapper.db, runId);
2296
+ await incrementRunRestarts(cachedAdapter, runId);
2269
2297
  } catch {
2270
2298
  try {
2271
- cachedDbWrapper?.close();
2299
+ await cachedAdapter?.close();
2272
2300
  } catch {}
2273
- cachedDbWrapper = null;
2301
+ cachedAdapter = null;
2274
2302
  }
2275
2303
  };
2276
2304
  })(),
@@ -2278,15 +2306,19 @@ function defaultSupervisorDeps() {
2278
2306
  try {
2279
2307
  const dbRoot = await resolveMainRepoRoot(projectRoot);
2280
2308
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
2281
- if (!existsSync(dbPath)) return {
2309
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
2310
+ if (!existsSync(dbPath) && !existsSync(doltDir)) return {
2282
2311
  input: 0,
2283
2312
  output: 0,
2284
2313
  cost_usd: 0
2285
2314
  };
2286
- const dbWrapper = new DatabaseWrapper(dbPath);
2315
+ const tsAdapter = createDatabaseAdapter({
2316
+ backend: "auto",
2317
+ basePath: dbRoot
2318
+ });
2287
2319
  try {
2288
- dbWrapper.open();
2289
- const agg = aggregateTokenUsageForRun(dbWrapper.db, runId);
2320
+ await initSchema(tsAdapter);
2321
+ const agg = await aggregateTokenUsageForRun(tsAdapter, runId);
2290
2322
  return {
2291
2323
  input: agg.input,
2292
2324
  output: agg.output,
@@ -2294,7 +2326,7 @@ function defaultSupervisorDeps() {
2294
2326
  };
2295
2327
  } finally {
2296
2328
  try {
2297
- dbWrapper.close();
2329
+ await tsAdapter.close();
2298
2330
  } catch {}
2299
2331
  }
2300
2332
  } catch {
@@ -2312,7 +2344,7 @@ function defaultSupervisorDeps() {
2312
2344
  if (cached === null) {
2313
2345
  const { AdapterRegistry: AR } = await import(
2314
2346
  /* @vite-ignore */
2315
- "../adapter-registry-X5X81xdJ.js"
2347
+ "../adapter-registry-BRQXdPnB.js"
2316
2348
  );
2317
2349
  cached = new AR();
2318
2350
  await cached.discoverAndRegister();
@@ -2324,14 +2356,17 @@ function defaultSupervisorDeps() {
2324
2356
  try {
2325
2357
  const dbRoot = await resolveMainRepoRoot(opts.projectRoot);
2326
2358
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
2327
- if (!existsSync(dbPath)) return;
2328
- const dbWrapper = new DatabaseWrapper(dbPath);
2359
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
2360
+ if (!existsSync(dbPath) && !existsSync(doltDir)) return;
2361
+ const sfAdapter = createDatabaseAdapter({
2362
+ backend: "auto",
2363
+ basePath: dbRoot
2364
+ });
2329
2365
  try {
2330
- dbWrapper.open();
2331
- const db = dbWrapper.db;
2366
+ await initSchema(sfAdapter);
2332
2367
  const activeStories = Object.entries(opts.storyDetails).filter(([, s]) => s.phase !== "PENDING" && s.phase !== "COMPLETE" && s.phase !== "ESCALATED");
2333
2368
  const now = Date.now();
2334
- for (const [storyKey, storyState] of activeStories) createDecision(db, {
2369
+ for (const [storyKey, storyState] of activeStories) await createDecision(sfAdapter, {
2335
2370
  pipeline_run_id: opts.runId ?? null,
2336
2371
  phase: "supervisor",
2337
2372
  category: OPERATIONAL_FINDING,
@@ -2346,7 +2381,7 @@ function defaultSupervisorDeps() {
2346
2381
  });
2347
2382
  } finally {
2348
2383
  try {
2349
- dbWrapper.close();
2384
+ await sfAdapter.close();
2350
2385
  } catch {}
2351
2386
  }
2352
2387
  } catch {}
@@ -2358,13 +2393,16 @@ function defaultSupervisorDeps() {
2358
2393
  try {
2359
2394
  const dbRoot = await resolveMainRepoRoot(opts.projectRoot);
2360
2395
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
2361
- if (!existsSync(dbPath)) return;
2362
- const dbWrapper = new DatabaseWrapper(dbPath);
2396
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
2397
+ if (!existsSync(dbPath) && !existsSync(doltDir)) return;
2398
+ const rsAdapter = createDatabaseAdapter({
2399
+ backend: "auto",
2400
+ basePath: dbRoot
2401
+ });
2363
2402
  try {
2364
- dbWrapper.open();
2365
- const db = dbWrapper.db;
2366
- const tokenAgg = aggregateTokenUsageForRun(db, opts.runId);
2367
- createDecision(db, {
2403
+ await initSchema(rsAdapter);
2404
+ const tokenAgg = await aggregateTokenUsageForRun(rsAdapter, opts.runId);
2405
+ await createDecision(rsAdapter, {
2368
2406
  pipeline_run_id: opts.runId,
2369
2407
  phase: "supervisor",
2370
2408
  category: OPERATIONAL_FINDING,
@@ -2382,24 +2420,26 @@ function defaultSupervisorDeps() {
2382
2420
  });
2383
2421
  } finally {
2384
2422
  try {
2385
- dbWrapper.close();
2423
+ await rsAdapter.close();
2386
2424
  } catch {}
2387
2425
  }
2388
2426
  } catch {}
2389
2427
  },
2390
2428
  runAnalysis: async (runId, projectRoot) => {
2391
2429
  const dbPath = join(projectRoot, ".substrate", "substrate.db");
2392
- if (!existsSync(dbPath)) return;
2393
- const dbWrapper = new DatabaseWrapper(dbPath);
2430
+ const doltDir = join(projectRoot, ".substrate", "state", ".dolt");
2431
+ if (!existsSync(dbPath) && !existsSync(doltDir)) return;
2432
+ const raAdapter = createDatabaseAdapter({
2433
+ backend: "auto",
2434
+ basePath: projectRoot
2435
+ });
2394
2436
  try {
2395
- dbWrapper.open();
2396
- runMigrations(dbWrapper.db);
2397
- const db = dbWrapper.db;
2398
- const run = getRunMetrics(db, runId);
2437
+ await initSchema(raAdapter);
2438
+ const run = await getRunMetrics(raAdapter, runId);
2399
2439
  if (!run) return;
2400
- const stories = getStoryMetricsForRun(db, runId);
2401
- const baseline = getBaselineRunMetrics(db);
2402
- const baselineStories = baseline && baseline.run_id !== runId ? getStoryMetricsForRun(db, baseline.run_id) : [];
2440
+ const stories = await getStoryMetricsForRun(raAdapter, runId);
2441
+ const baseline = await getBaselineRunMetrics(raAdapter);
2442
+ const baselineStories = baseline && baseline.run_id !== runId ? await getStoryMetricsForRun(raAdapter, baseline.run_id) : [];
2403
2443
  const analysisPath = "../../modules/supervisor/analysis.js";
2404
2444
  const { generateAnalysisReport, writeAnalysisReport } = await import(
2405
2445
  /* @vite-ignore */
@@ -2409,7 +2449,7 @@ function defaultSupervisorDeps() {
2409
2449
  writeAnalysisReport(report, projectRoot);
2410
2450
  } catch {} finally {
2411
2451
  try {
2412
- dbWrapper.close();
2452
+ await raAdapter.close();
2413
2453
  } catch {}
2414
2454
  }
2415
2455
  }
@@ -2695,21 +2735,21 @@ async function runSupervisorAction(options, deps = {}) {
2695
2735
  try {
2696
2736
  const { createExperimenter } = await import(
2697
2737
  /* @vite-ignore */
2698
- "../experimenter-CvxtqzXz.js"
2738
+ "../experimenter-CoR0k66d.js"
2699
2739
  );
2700
2740
  const { getLatestRun: getLatest } = await import(
2701
2741
  /* @vite-ignore */
2702
- "../decisions-D7Ao_KcL.js"
2742
+ "../decisions-BxYj_a1X.js"
2703
2743
  );
2704
- const dbPath = join(projectRoot, ".substrate", "substrate.db");
2705
- const expDbWrapper = new DatabaseWrapper(dbPath);
2744
+ const expAdapter = createDatabaseAdapter({
2745
+ backend: "auto",
2746
+ basePath: projectRoot
2747
+ });
2706
2748
  try {
2707
- expDbWrapper.open();
2708
- runMigrations(expDbWrapper.db);
2709
- const expDb = expDbWrapper.db;
2749
+ await initSchema(expAdapter);
2710
2750
  const { runRunAction: runPipeline } = await import(
2711
2751
  /* @vite-ignore */
2712
- "../run-BbdWeKiB.js"
2752
+ "../run-C-yCMYlt.js"
2713
2753
  );
2714
2754
  const runStoryFn = async (opts) => {
2715
2755
  const exitCode = await runPipeline({
@@ -2719,7 +2759,7 @@ async function runSupervisorAction(options, deps = {}) {
2719
2759
  outputFormat: "json",
2720
2760
  projectRoot: opts.projectRoot
2721
2761
  });
2722
- const latestRun = getLatest(expDb);
2762
+ const latestRun = await getLatest(expAdapter);
2723
2763
  const newRunId = latestRun?.id ?? `experiment-${Date.now()}`;
2724
2764
  return {
2725
2765
  runId: newRunId,
@@ -2735,7 +2775,7 @@ async function runSupervisorAction(options, deps = {}) {
2735
2775
  runStory: runStoryFn,
2736
2776
  log: (msg) => log(msg)
2737
2777
  });
2738
- const results = await experimenter.runExperiments(expDb, recommendations, health.run_id);
2778
+ const results = await experimenter.runExperiments(expAdapter, recommendations, health.run_id);
2739
2779
  const improved = results.filter((r) => r.verdict === "IMPROVED").length;
2740
2780
  const mixed = results.filter((r) => r.verdict === "MIXED").length;
2741
2781
  const regressed = results.filter((r) => r.verdict === "REGRESSED").length;
@@ -2749,7 +2789,7 @@ async function runSupervisorAction(options, deps = {}) {
2749
2789
  });
2750
2790
  } finally {
2751
2791
  try {
2752
- expDbWrapper.close();
2792
+ await expAdapter.close();
2753
2793
  } catch {}
2754
2794
  }
2755
2795
  } catch (expErr) {
@@ -2957,11 +2997,17 @@ function registerSupervisorCommand(program, _version = "0.0.0", projectRoot = pr
2957
2997
  //#endregion
2958
2998
  //#region src/cli/commands/metrics.ts
2959
2999
  const logger$13 = createLogger("metrics-cmd");
2960
- async function openTelemetryDb(dbPath) {
2961
- if (!existsSync(dbPath)) return null;
3000
+ async function openTelemetryAdapter(basePath) {
2962
3001
  try {
2963
- const db = new Database(dbPath, { readonly: true });
2964
- return db;
3002
+ const adapter = createDatabaseAdapter({
3003
+ backend: "auto",
3004
+ basePath
3005
+ });
3006
+ const persistence = new AdapterTelemetryPersistence(adapter);
3007
+ return {
3008
+ persistence,
3009
+ close: () => adapter.close()
3010
+ };
2965
3011
  } catch {
2966
3012
  return null;
2967
3013
  }
@@ -3048,11 +3094,10 @@ async function runMetricsAction(options) {
3048
3094
  }
3049
3095
  if (hasTelemetryMode) {
3050
3096
  const dbRoot$1 = await resolveMainRepoRoot(projectRoot);
3051
- const dbPath$1 = join(dbRoot$1, ".substrate", "substrate.db");
3052
3097
  const doltStatePath = join(dbRoot$1, ".substrate", "state", ".dolt");
3053
3098
  const doltExists = existsSync(doltStatePath);
3054
- if (!doltExists && !existsSync(dbPath$1)) {
3055
- const msg = "No telemetry data yet — run a pipeline with `telemetry.enabled: true`";
3099
+ if (!doltExists) {
3100
+ const msg = "No telemetry data yet — run a pipeline with Dolt initialized and `telemetry.enabled: true`";
3056
3101
  if (turns !== void 0 || consumers !== void 0) {
3057
3102
  process.stderr.write(`Error: ${msg}\n`);
3058
3103
  return 1;
@@ -3061,8 +3106,8 @@ async function runMetricsAction(options) {
3061
3106
  else process.stdout.write(msg + "\n");
3062
3107
  return 0;
3063
3108
  }
3064
- const sqliteDb = await openTelemetryDb(dbPath$1);
3065
- if (sqliteDb === null) {
3109
+ const telemetryHandle = await openTelemetryAdapter(dbRoot$1);
3110
+ if (telemetryHandle === null) {
3066
3111
  const msg = "No telemetry data yet — run a pipeline with `telemetry.enabled: true`";
3067
3112
  if (turns !== void 0 || consumers !== void 0) {
3068
3113
  process.stderr.write(`Error: ${msg}\n`);
@@ -3072,8 +3117,8 @@ async function runMetricsAction(options) {
3072
3117
  else process.stdout.write(msg + "\n");
3073
3118
  return 0;
3074
3119
  }
3120
+ const telemetryPersistence = telemetryHandle.persistence;
3075
3121
  try {
3076
- const telemetryPersistence = new TelemetryPersistence(sqliteDb);
3077
3122
  if (efficiency === true) {
3078
3123
  const scores = await telemetryPersistence.getEfficiencyScores(20);
3079
3124
  if (outputFormat === "json") process.stdout.write(formatOutput({ efficiency: rowsToEfficiencyScore(scores) }, "json", true) + "\n");
@@ -3163,7 +3208,7 @@ async function runMetricsAction(options) {
3163
3208
  }
3164
3209
  } finally {
3165
3210
  try {
3166
- sqliteDb.close();
3211
+ await telemetryHandle.close();
3167
3212
  } catch {}
3168
3213
  }
3169
3214
  }
@@ -3249,36 +3294,37 @@ async function runMetricsAction(options) {
3249
3294
  }
3250
3295
  }
3251
3296
  const dbRoot = await resolveMainRepoRoot(projectRoot);
3252
- const dbPath = join(dbRoot, ".substrate", "substrate.db");
3253
- if (!existsSync(dbPath)) {
3297
+ const doltStateDir = join(dbRoot, ".substrate", "state", ".dolt");
3298
+ if (!existsSync(doltStateDir)) {
3254
3299
  if (outputFormat === "json") process.stdout.write(formatOutput({
3255
3300
  runs: [],
3256
- message: "No metrics yet — no pipeline database found."
3301
+ message: "No metrics yet — no pipeline database found. Initialize Dolt with `substrate init`."
3257
3302
  }, "json", true) + "\n");
3258
- else process.stdout.write("No metrics yet — no pipeline database found.\n");
3303
+ else process.stdout.write("No metrics yet — no pipeline database found. Initialize Dolt with `substrate init`.\n");
3259
3304
  return 0;
3260
3305
  }
3261
- const dbWrapper = new DatabaseWrapper(dbPath);
3306
+ const adapter = createDatabaseAdapter({
3307
+ backend: "auto",
3308
+ basePath: dbRoot
3309
+ });
3262
3310
  try {
3263
- dbWrapper.open();
3264
- runMigrations(dbWrapper.db);
3265
- const db = dbWrapper.db;
3311
+ await initSchema(adapter);
3266
3312
  if (tagBaseline !== void 0) {
3267
- const row = getRunMetrics(db, tagBaseline);
3313
+ const row = await getRunMetrics(adapter, tagBaseline);
3268
3314
  if (!row) {
3269
3315
  const msg = `Run '${tagBaseline}' not found in run_metrics.`;
3270
3316
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
3271
3317
  else process.stderr.write(`Error: ${msg}\n`);
3272
3318
  return 1;
3273
3319
  }
3274
- tagRunAsBaseline(db, tagBaseline);
3320
+ await tagRunAsBaseline(adapter, tagBaseline);
3275
3321
  if (outputFormat === "json") process.stdout.write(formatOutput({ tagged_baseline: tagBaseline }, "json", true) + "\n");
3276
3322
  else process.stdout.write(`Baseline tagged: ${tagBaseline}\n`);
3277
3323
  return 0;
3278
3324
  }
3279
3325
  if (compare !== void 0) {
3280
3326
  const [idA, idB] = compare;
3281
- const delta = compareRunMetrics(db, idA, idB);
3327
+ const delta = await compareRunMetrics(adapter, idA, idB);
3282
3328
  if (delta === null) {
3283
3329
  const msg = `One or both run IDs not found in metrics: ${idA}, ${idB}`;
3284
3330
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
@@ -3298,7 +3344,7 @@ async function runMetricsAction(options) {
3298
3344
  }
3299
3345
  return 0;
3300
3346
  }
3301
- const runs = listRunMetrics(db, limit);
3347
+ const runs = await listRunMetrics(adapter, limit);
3302
3348
  let doltMetrics;
3303
3349
  const doltStatePath = join(dbRoot, ".substrate", "state", ".dolt");
3304
3350
  const hasDoltFilters = sprint !== void 0 || story !== void 0 || taskType !== void 0 || since !== void 0 || aggregate === true;
@@ -3319,7 +3365,7 @@ async function runMetricsAction(options) {
3319
3365
  } catch (doltErr) {
3320
3366
  logger$13.warn({ err: doltErr }, "StateStore query failed — falling back to SQLite metrics only");
3321
3367
  }
3322
- const storyMetricDecisions = getDecisionsByCategory(db, STORY_METRICS);
3368
+ const storyMetricDecisions = await getDecisionsByCategory(adapter, STORY_METRICS);
3323
3369
  const storyMetrics = storyMetricDecisions.map((d) => {
3324
3370
  const colonIdx = d.key.indexOf(":");
3325
3371
  const storyKey = colonIdx !== -1 ? d.key.slice(0, colonIdx) : d.key;
@@ -3464,7 +3510,7 @@ async function runMetricsAction(options) {
3464
3510
  return 1;
3465
3511
  } finally {
3466
3512
  try {
3467
- dbWrapper.close();
3513
+ await adapter.close();
3468
3514
  } catch {}
3469
3515
  }
3470
3516
  }
@@ -3514,30 +3560,20 @@ function registerMetricsCommand(program, _version = "0.0.0", projectRoot = proce
3514
3560
  //#endregion
3515
3561
  //#region src/cli/commands/migrate.ts
3516
3562
  /**
3517
- * Open the SQLite database at `dbPath` (read-only) and return a snapshot of
3518
- * the story_metrics rows. Returns an empty snapshot if the file does not
3519
- * exist or the table is missing.
3563
+ * Reads the SQLite snapshot for migration.
3564
+ *
3565
+ * NOTE (Epic 29): SQLite support has been removed from Substrate.
3566
+ *
3567
+ * If you need to migrate historical SQLite data, downgrade to a pre-Epic-29
3568
+ * version of Substrate (v0.4.x or earlier), run `substrate migrate`, then
3569
+ * upgrade. The Dolt database will retain the migrated data across upgrades.
3570
+ *
3571
+ * This function now always returns an empty snapshot.
3520
3572
  */
3521
- function readSqliteSnapshot(dbPath) {
3522
- let db = null;
3523
- try {
3524
- db = new Database(dbPath, { readonly: true });
3525
- } catch {
3526
- return { storyMetrics: [] };
3527
- }
3528
- try {
3529
- const rows = db.prepare(`SELECT story_key, result, completed_at, created_at,
3530
- wall_clock_seconds, input_tokens, output_tokens,
3531
- cost_usd, review_cycles
3532
- FROM story_metrics`).all();
3533
- return { storyMetrics: rows };
3534
- } catch (err) {
3535
- const msg = err instanceof Error ? err.message : String(err);
3536
- process.stderr.write(`Warning: could not read story_metrics from SQLite: ${msg}\n`);
3537
- return { storyMetrics: [] };
3538
- } finally {
3539
- db.close();
3540
- }
3573
+ async function readSqliteSnapshot(dbPath) {
3574
+ const { existsSync: fileExists } = await import("node:fs");
3575
+ if (fileExists(dbPath)) process.stderr.write(`Warning: Legacy SQLite database found at ${dbPath} but SQLite support has been\nremoved in Epic 29. To migrate historical data, downgrade to Substrate v0.4.x,\nrun 'substrate migrate', then upgrade back to this version.\n`);
3576
+ return { storyMetrics: [] };
3541
3577
  }
3542
3578
  const BATCH_SIZE = 100;
3543
3579
  /**
@@ -3611,7 +3647,7 @@ function registerMigrateCommand(program) {
3611
3647
  return;
3612
3648
  }
3613
3649
  const dbPath = join$1(projectRoot, ".substrate", "substrate.db");
3614
- const snapshot = readSqliteSnapshot(dbPath);
3650
+ const snapshot = await readSqliteSnapshot(dbPath);
3615
3651
  if (snapshot.storyMetrics.length === 0) {
3616
3652
  if (options.outputFormat === "json") console.log(JSON.stringify({
3617
3653
  migrated: false,
@@ -3665,25 +3701,14 @@ function registerMigrateCommand(program) {
3665
3701
 
3666
3702
  //#endregion
3667
3703
  //#region src/persistence/queries/cost.ts
3668
- const stmtCache = new WeakMap();
3669
- function getCache(db) {
3670
- let cache = stmtCache.get(db);
3671
- if (!cache) {
3672
- cache = {};
3673
- stmtCache.set(db, cache);
3674
- }
3675
- return cache;
3676
- }
3677
3704
  /**
3678
3705
  * Return aggregated cost totals for a session (AC2).
3679
3706
  *
3680
3707
  * Returns a SessionCostSummary with subscription/API breakdown and savings.
3681
3708
  * Uses idx_cost_entries_session_task index.
3682
3709
  */
3683
- function getSessionCostSummary(db, sessionId) {
3684
- const cache = getCache(db);
3685
- if (!cache.getSessionCostSummaryTotals) cache.getSessionCostSummaryTotals = db.prepare(`
3686
- SELECT
3710
+ async function getSessionCostSummary(adapter, sessionId) {
3711
+ const totalsRows = await adapter.query(`SELECT
3687
3712
  COALESCE(SUM(estimated_cost), 0) AS total_cost_usd,
3688
3713
  COALESCE(SUM(CASE WHEN billing_mode = 'subscription' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS subscription_cost_usd,
3689
3714
  COALESCE(SUM(CASE WHEN billing_mode = 'api' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS api_cost_usd,
@@ -3693,11 +3718,9 @@ function getSessionCostSummary(db, sessionId) {
3693
3718
  SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_task_count,
3694
3719
  MIN(timestamp) AS earliest_recorded_at
3695
3720
  FROM cost_entries
3696
- WHERE session_id = @sessionId
3697
- `);
3698
- const totalsRow = cache.getSessionCostSummaryTotals.get({ sessionId });
3699
- if (!cache.getSessionCostSummaryAgents) cache.getSessionCostSummaryAgents = db.prepare(`
3700
- SELECT
3721
+ WHERE session_id = ?`, [sessionId]);
3722
+ const totalsRow = totalsRows[0];
3723
+ const agentRows = await adapter.query(`SELECT
3701
3724
  agent,
3702
3725
  COUNT(*) AS task_count,
3703
3726
  COALESCE(SUM(estimated_cost), 0) AS cost_usd,
@@ -3705,11 +3728,9 @@ function getSessionCostSummary(db, sessionId) {
3705
3728
  SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_tasks,
3706
3729
  SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_tasks
3707
3730
  FROM cost_entries
3708
- WHERE session_id = @sessionId
3731
+ WHERE session_id = ?
3709
3732
  GROUP BY agent
3710
- ORDER BY cost_usd DESC
3711
- `);
3712
- const agentRows = cache.getSessionCostSummaryAgents.all({ sessionId });
3733
+ ORDER BY cost_usd DESC`, [sessionId]);
3713
3734
  const perAgentBreakdown = agentRows.map((row) => ({
3714
3735
  agent: row.agent,
3715
3736
  task_count: row.task_count,
@@ -3746,69 +3767,31 @@ function getSessionCostSummary(db, sessionId) {
3746
3767
  *
3747
3768
  * Uses idx_cost_entries_session_task index.
3748
3769
  */
3749
- function getSessionCostSummaryFiltered(db, sessionId, includePlanning) {
3750
- const cache = getCache(db);
3751
- let totalsRow;
3752
- let agentRows;
3753
- if (!includePlanning) {
3754
- if (!cache.getSessionCostSummaryFilteredTotals) cache.getSessionCostSummaryFilteredTotals = db.prepare(`
3755
- SELECT
3756
- COALESCE(SUM(estimated_cost), 0) AS total_cost_usd,
3757
- COALESCE(SUM(CASE WHEN billing_mode = 'subscription' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS subscription_cost_usd,
3758
- COALESCE(SUM(CASE WHEN billing_mode = 'api' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS api_cost_usd,
3759
- COALESCE(SUM(savings_usd), 0) AS savings_usd,
3760
- COUNT(*) AS task_count,
3761
- SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_task_count,
3762
- SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_task_count,
3763
- MIN(timestamp) AS earliest_recorded_at
3764
- FROM cost_entries
3765
- WHERE session_id = @sessionId AND category != 'planning'
3766
- `);
3767
- if (!cache.getSessionCostSummaryFilteredAgents) cache.getSessionCostSummaryFilteredAgents = db.prepare(`
3768
- SELECT
3769
- agent,
3770
- COUNT(*) AS task_count,
3771
- COALESCE(SUM(estimated_cost), 0) AS cost_usd,
3772
- COALESCE(SUM(savings_usd), 0) AS savings_usd,
3773
- SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_tasks,
3774
- SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_tasks
3775
- FROM cost_entries
3776
- WHERE session_id = @sessionId AND category != 'planning'
3777
- GROUP BY agent
3778
- ORDER BY cost_usd DESC
3779
- `);
3780
- totalsRow = cache.getSessionCostSummaryFilteredTotals.get({ sessionId });
3781
- agentRows = cache.getSessionCostSummaryFilteredAgents.all({ sessionId });
3782
- } else {
3783
- if (!cache.getSessionCostSummaryUnfilteredTotals) cache.getSessionCostSummaryUnfilteredTotals = db.prepare(`
3784
- SELECT
3785
- COALESCE(SUM(estimated_cost), 0) AS total_cost_usd,
3786
- COALESCE(SUM(CASE WHEN billing_mode = 'subscription' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS subscription_cost_usd,
3787
- COALESCE(SUM(CASE WHEN billing_mode = 'api' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS api_cost_usd,
3788
- COALESCE(SUM(savings_usd), 0) AS savings_usd,
3789
- COUNT(*) AS task_count,
3790
- SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_task_count,
3791
- SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_task_count,
3792
- MIN(timestamp) AS earliest_recorded_at
3793
- FROM cost_entries
3794
- WHERE session_id = @sessionId
3795
- `);
3796
- if (!cache.getSessionCostSummaryUnfilteredAgents) cache.getSessionCostSummaryUnfilteredAgents = db.prepare(`
3797
- SELECT
3798
- agent,
3799
- COUNT(*) AS task_count,
3800
- COALESCE(SUM(estimated_cost), 0) AS cost_usd,
3801
- COALESCE(SUM(savings_usd), 0) AS savings_usd,
3802
- SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_tasks,
3803
- SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_tasks
3804
- FROM cost_entries
3805
- WHERE session_id = @sessionId
3806
- GROUP BY agent
3807
- ORDER BY cost_usd DESC
3808
- `);
3809
- totalsRow = cache.getSessionCostSummaryUnfilteredTotals.get({ sessionId });
3810
- agentRows = cache.getSessionCostSummaryUnfilteredAgents.all({ sessionId });
3811
- }
3770
+ async function getSessionCostSummaryFiltered(adapter, sessionId, includePlanning) {
3771
+ const categoryFilter = includePlanning ? "" : "AND category != 'planning'";
3772
+ const totalsRows = await adapter.query(`SELECT
3773
+ COALESCE(SUM(estimated_cost), 0) AS total_cost_usd,
3774
+ COALESCE(SUM(CASE WHEN billing_mode = 'subscription' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS subscription_cost_usd,
3775
+ COALESCE(SUM(CASE WHEN billing_mode = 'api' THEN COALESCE(estimated_cost, 0) ELSE 0 END), 0) AS api_cost_usd,
3776
+ COALESCE(SUM(savings_usd), 0) AS savings_usd,
3777
+ COUNT(*) AS task_count,
3778
+ SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_task_count,
3779
+ SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_task_count,
3780
+ MIN(timestamp) AS earliest_recorded_at
3781
+ FROM cost_entries
3782
+ WHERE session_id = ? ${categoryFilter}`, [sessionId]);
3783
+ const totalsRow = totalsRows[0];
3784
+ const agentRows = await adapter.query(`SELECT
3785
+ agent,
3786
+ COUNT(*) AS task_count,
3787
+ COALESCE(SUM(estimated_cost), 0) AS cost_usd,
3788
+ COALESCE(SUM(savings_usd), 0) AS savings_usd,
3789
+ SUM(CASE WHEN billing_mode = 'subscription' THEN 1 ELSE 0 END) AS subscription_tasks,
3790
+ SUM(CASE WHEN billing_mode = 'api' THEN 1 ELSE 0 END) AS api_tasks
3791
+ FROM cost_entries
3792
+ WHERE session_id = ? ${categoryFilter}
3793
+ GROUP BY agent
3794
+ ORDER BY cost_usd DESC`, [sessionId]);
3812
3795
  const perAgentBreakdown = agentRows.map((row) => ({
3813
3796
  agent: row.agent,
3814
3797
  task_count: row.task_count,
@@ -3843,13 +3826,11 @@ function getSessionCostSummaryFiltered(db, sessionId, includePlanning) {
3843
3826
  *
3844
3827
  * Uses idx_cost_entries_session_task index.
3845
3828
  */
3846
- function getAllCostEntriesFiltered(db, sessionId, includePlanning) {
3829
+ async function getAllCostEntriesFiltered(adapter, sessionId, includePlanning) {
3847
3830
  const categoryFilter = includePlanning ? "" : "AND category != 'planning'";
3848
- const rows = db.prepare(`
3849
- SELECT * FROM cost_entries
3850
- WHERE session_id = @sessionId ${categoryFilter}
3851
- ORDER BY timestamp DESC
3852
- `).all({ sessionId });
3831
+ const rows = await adapter.query(`SELECT * FROM cost_entries
3832
+ WHERE session_id = ? ${categoryFilter}
3833
+ ORDER BY timestamp DESC`, [sessionId]);
3853
3834
  return rows.map((row) => ({
3854
3835
  id: row.id,
3855
3836
  session_id: row.session_id,
@@ -3873,20 +3854,16 @@ function getAllCostEntriesFiltered(db, sessionId, includePlanning) {
3873
3854
  *
3874
3855
  * Uses idx_cost_category index.
3875
3856
  */
3876
- function getPlanningCostTotal(db, sessionId) {
3877
- const cache = getCache(db);
3878
- if (!cache.getPlanningCostTotal) cache.getPlanningCostTotal = db.prepare(`
3879
- SELECT COALESCE(SUM(estimated_cost), 0) AS planning_cost
3880
- FROM cost_entries
3881
- WHERE session_id = @sessionId AND category = 'planning'
3882
- `);
3883
- const row = cache.getPlanningCostTotal.get({ sessionId });
3884
- return row.planning_cost;
3857
+ async function getPlanningCostTotal(adapter, sessionId) {
3858
+ const rows = await adapter.query(`SELECT COALESCE(SUM(estimated_cost), 0) AS planning_cost
3859
+ FROM cost_entries
3860
+ WHERE session_id = ? AND category = 'planning'`, [sessionId]);
3861
+ return rows[0]?.planning_cost ?? 0;
3885
3862
  }
3886
3863
 
3887
3864
  //#endregion
3888
3865
  //#region src/cli/commands/cost.ts
3889
- function getLatestSessionId(_db) {
3866
+ function getLatestSessionId(_adapter) {
3890
3867
  return null;
3891
3868
  }
3892
3869
  const logger$12 = createLogger("cost-cmd");
@@ -4061,7 +4038,8 @@ function formatCostCsv(summary, taskEntries) {
4061
4038
  async function runCostAction(options) {
4062
4039
  const { sessionId: explicitSessionId, outputFormat, byTask, byAgent, byBilling, includePlanning, projectRoot, version = "0.0.0" } = options;
4063
4040
  const dbPath = join(projectRoot, ".substrate", "substrate.db");
4064
- if (!existsSync(dbPath)) {
4041
+ const doltDir = join(projectRoot, ".substrate", "state", ".dolt");
4042
+ if (!existsSync(dbPath) && !existsSync(doltDir)) {
4065
4043
  process.stderr.write(`Error: No Substrate database found at ${dbPath}. Run 'substrate init' first.\n`);
4066
4044
  return COST_EXIT_ERROR;
4067
4045
  }
@@ -4074,18 +4052,15 @@ async function runCostAction(options) {
4074
4052
  process.stderr.write(`Error: Invalid output format '${outputFormat}'. Valid formats: ${validFormats.join(", ")}\n`);
4075
4053
  return COST_EXIT_ERROR;
4076
4054
  }
4077
- let wrapper = null;
4055
+ let adapter = null;
4078
4056
  try {
4079
- wrapper = new DatabaseWrapper(dbPath);
4080
- wrapper.open();
4081
- const db = wrapper.db;
4082
- if (!db) {
4083
- process.stderr.write("Database connection failed\n");
4084
- return COST_EXIT_ERROR;
4085
- }
4086
- runMigrations(db);
4057
+ adapter = createDatabaseAdapter({
4058
+ backend: "auto",
4059
+ basePath: projectRoot
4060
+ });
4061
+ await initSchema(adapter);
4087
4062
  let sessionId = explicitSessionId ?? null;
4088
- if (!sessionId) sessionId = getLatestSessionId(db);
4063
+ if (!sessionId) sessionId = getLatestSessionId(adapter);
4089
4064
  if (!sessionId) {
4090
4065
  if (outputFormat === "json") {
4091
4066
  const output = buildJsonOutput("substrate cost", {
@@ -4096,8 +4071,8 @@ async function runCostAction(options) {
4096
4071
  } else process.stdout.write("No cost data found\n");
4097
4072
  return COST_EXIT_SUCCESS;
4098
4073
  }
4099
- const summary = includePlanning ? getSessionCostSummary(db, sessionId) : getSessionCostSummaryFiltered(db, sessionId, false);
4100
- const planningCostUsd = includePlanning ? 0 : getPlanningCostTotal(db, sessionId);
4074
+ const summary = includePlanning ? await getSessionCostSummary(adapter, sessionId) : await getSessionCostSummaryFiltered(adapter, sessionId, false);
4075
+ const planningCostUsd = includePlanning ? 0 : await getPlanningCostTotal(adapter, sessionId);
4101
4076
  if (summary.task_count === 0) {
4102
4077
  if (outputFormat === "json") {
4103
4078
  const output = buildJsonOutput("substrate cost", {
@@ -4108,25 +4083,25 @@ async function runCostAction(options) {
4108
4083
  } else process.stdout.write("No cost data found\n");
4109
4084
  return COST_EXIT_SUCCESS;
4110
4085
  }
4111
- const getFilteredEntries = () => getAllCostEntriesFiltered(db, sessionId, includePlanning);
4086
+ const getFilteredEntries = () => getAllCostEntriesFiltered(adapter, sessionId, includePlanning);
4112
4087
  if (outputFormat === "json") {
4113
4088
  const jsonData = {
4114
4089
  session_id: sessionId,
4115
4090
  summary
4116
4091
  };
4117
- if (byTask) jsonData.tasks = getFilteredEntries();
4092
+ if (byTask) jsonData.tasks = await getFilteredEntries();
4118
4093
  if (byAgent) jsonData.agents = summary.per_agent_breakdown;
4119
4094
  const output = buildJsonOutput("substrate cost", jsonData, version);
4120
4095
  process.stdout.write(JSON.stringify(output, null, 2) + "\n");
4121
4096
  return COST_EXIT_SUCCESS;
4122
4097
  }
4123
4098
  if (outputFormat === "csv") {
4124
- const csvOutput = formatCostCsv(summary, byTask ? getFilteredEntries() : void 0);
4099
+ const csvOutput = formatCostCsv(summary, byTask ? await getFilteredEntries() : void 0);
4125
4100
  process.stdout.write(csvOutput + "\n");
4126
4101
  return COST_EXIT_SUCCESS;
4127
4102
  }
4128
4103
  if (byTask) {
4129
- const entries = getFilteredEntries();
4104
+ const entries = await getFilteredEntries();
4130
4105
  process.stdout.write(formatByTaskTable(entries) + "\n");
4131
4106
  } else if (byAgent) process.stdout.write(formatByAgentTable(summary.per_agent_breakdown) + "\n");
4132
4107
  else if (byBilling) process.stdout.write(formatByBillingTable(summary) + "\n");
@@ -4138,8 +4113,8 @@ async function runCostAction(options) {
4138
4113
  logger$12.error({ err }, "runCostAction failed");
4139
4114
  return COST_EXIT_ERROR;
4140
4115
  } finally {
4141
- if (wrapper !== null) try {
4142
- wrapper.close();
4116
+ if (adapter !== null) try {
4117
+ await adapter.close();
4143
4118
  } catch {}
4144
4119
  }
4145
4120
  }
@@ -4167,190 +4142,211 @@ function registerCostCommand(program, version = "0.0.0", projectRoot = process.c
4167
4142
  });
4168
4143
  }
4169
4144
 
4170
- //#endregion
4171
- //#region src/persistence/migrations/001-monitor-schema.ts
4172
- /**
4173
- * Apply the monitor schema to the given database connection.
4174
- * Idempotent — uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS.
4175
- */
4176
- function applyMonitorSchema(db) {
4177
- db.exec(`
4178
- -- Migration versioning
4179
- CREATE TABLE IF NOT EXISTS _schema_version (
4180
- version_id INTEGER PRIMARY KEY,
4181
- applied_at TEXT NOT NULL DEFAULT (datetime('now'))
4182
- );
4183
- INSERT OR IGNORE INTO _schema_version (version_id) VALUES (1);
4184
-
4185
- -- Task-level execution metrics (AC1)
4186
- CREATE TABLE IF NOT EXISTS task_metrics (
4187
- task_id TEXT NOT NULL,
4188
- agent TEXT NOT NULL,
4189
- task_type TEXT NOT NULL,
4190
- outcome TEXT NOT NULL CHECK(outcome IN ('success', 'failure')),
4191
- failure_reason TEXT,
4192
- input_tokens INTEGER NOT NULL DEFAULT 0,
4193
- output_tokens INTEGER NOT NULL DEFAULT 0,
4194
- duration_ms INTEGER NOT NULL DEFAULT 0,
4195
- cost REAL NOT NULL DEFAULT 0.0,
4196
- estimated_cost REAL NOT NULL DEFAULT 0.0,
4197
- billing_mode TEXT NOT NULL DEFAULT 'api',
4198
- retries INTEGER NOT NULL DEFAULT 0,
4199
- recorded_at TEXT NOT NULL DEFAULT (datetime('now')),
4200
- PRIMARY KEY (task_id, recorded_at)
4201
- );
4202
- CREATE INDEX IF NOT EXISTS idx_tm_agent ON task_metrics(agent);
4203
- CREATE INDEX IF NOT EXISTS idx_tm_task_type ON task_metrics(task_type);
4204
- CREATE INDEX IF NOT EXISTS idx_tm_recorded_at ON task_metrics(recorded_at);
4205
- CREATE INDEX IF NOT EXISTS idx_tm_agent_type ON task_metrics(agent, task_type);
4206
-
4207
- -- Aggregate performance stats per (agent, task_type) (for story 8.5)
4208
- CREATE TABLE IF NOT EXISTS performance_aggregates (
4209
- agent TEXT NOT NULL,
4210
- task_type TEXT NOT NULL,
4211
- total_tasks INTEGER NOT NULL DEFAULT 0,
4212
- successful_tasks INTEGER NOT NULL DEFAULT 0,
4213
- failed_tasks INTEGER NOT NULL DEFAULT 0,
4214
- total_input_tokens INTEGER NOT NULL DEFAULT 0,
4215
- total_output_tokens INTEGER NOT NULL DEFAULT 0,
4216
- total_duration_ms INTEGER NOT NULL DEFAULT 0,
4217
- total_cost REAL NOT NULL DEFAULT 0.0,
4218
- total_retries INTEGER NOT NULL DEFAULT 0,
4219
- last_updated TEXT NOT NULL DEFAULT (datetime('now')),
4220
- PRIMARY KEY (agent, task_type)
4221
- );
4222
-
4223
- -- Routing recommendations (for story 8.6)
4224
- CREATE TABLE IF NOT EXISTS routing_recommendations (
4225
- id INTEGER PRIMARY KEY AUTOINCREMENT,
4226
- task_type TEXT NOT NULL,
4227
- current_agent TEXT NOT NULL,
4228
- recommended_agent TEXT NOT NULL,
4229
- reason TEXT,
4230
- confidence REAL NOT NULL DEFAULT 0.0,
4231
- supporting_data TEXT,
4232
- generated_at TEXT NOT NULL DEFAULT (datetime('now')),
4233
- expires_at TEXT
4234
- );
4235
- `);
4236
- }
4237
-
4238
4145
  //#endregion
4239
4146
  //#region src/persistence/monitor-database.ts
4240
4147
  const logger$11 = createLogger("persistence:monitor-db");
4148
+ /**
4149
+ * DatabaseAdapter-backed MonitorDatabase implementation.
4150
+ *
4151
+ * All database operations are executed synchronously by calling the adapter's
4152
+ * sync-compatible query path. The adapter is accepted via constructor injection,
4153
+ * so any SyncAdapter-compatible backend (WASM SQLite, Dolt via sync wrapper) works.
4154
+ *
4155
+ * Schema is applied on construction via _applySchema().
4156
+ */
4241
4157
  var MonitorDatabaseImpl = class {
4242
- _db = null;
4158
+ _adapter;
4159
+ _syncAdapter;
4243
4160
  _path;
4244
- _stmtInsertMetrics;
4245
- _stmtUpsertAggregates;
4246
- constructor(databasePath) {
4247
- this._path = databasePath;
4248
- this._open();
4249
- }
4250
- _open() {
4251
- logger$11.info({ path: this._path }, "Opening monitor database");
4252
- this._db = new Database(this._path);
4253
- const walResult = this._db.pragma("journal_mode = WAL");
4254
- if (walResult?.[0]?.journal_mode !== "wal") logger$11.warn({ result: walResult?.[0]?.journal_mode }, "Monitor DB: WAL pragma did not confirm wal mode");
4255
- this._db.pragma("synchronous = NORMAL");
4256
- this._db.pragma("busy_timeout = 5000");
4257
- this._db.pragma("foreign_keys = ON");
4258
- applyMonitorSchema(this._db);
4259
- this._stmtInsertMetrics = this._db.prepare(`
4260
- INSERT OR IGNORE INTO task_metrics (
4261
- task_id, agent, task_type, outcome, failure_reason,
4262
- input_tokens, output_tokens, duration_ms, cost, estimated_cost,
4263
- billing_mode, recorded_at
4264
- ) VALUES (
4265
- @taskId, @agent, @taskType, @outcome, @failureReason,
4266
- @inputTokens, @outputTokens, @durationMs, @cost, @estimatedCost,
4267
- @billingMode, @recordedAt
4161
+ constructor(databasePathOrAdapter) {
4162
+ if (typeof databasePathOrAdapter === "string") throw new Error("MonitorDatabaseImpl: string path constructor is no longer supported (Epic 29 SQLite removal). Use createWasmSqliteAdapter() and pass the adapter directly: new MonitorDatabaseImpl(await createWasmSqliteAdapter())");
4163
+ else {
4164
+ this._path = "<adapter>";
4165
+ this._adapter = databasePathOrAdapter;
4166
+ }
4167
+ this._syncAdapter = isSyncAdapter(this._adapter) ? this._adapter : null;
4168
+ if (this._syncAdapter === null) throw new Error("MonitorDatabaseImpl: adapter must implement SyncAdapter (querySync/execSync). Use createWasmSqliteAdapter() from src/persistence/wasm-sqlite-adapter.ts.");
4169
+ this._applySchemaSync();
4170
+ logger$11.info({ path: this._path }, "Monitor database ready");
4171
+ }
4172
+ _applySchemaSync() {
4173
+ if (this._syncAdapter === null) return;
4174
+ this._syncAdapter.execSync(`
4175
+ CREATE TABLE IF NOT EXISTS _schema_version (
4176
+ version_id INTEGER PRIMARY KEY,
4177
+ applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
4268
4178
  )
4269
4179
  `);
4270
- this._stmtUpsertAggregates = this._db.prepare(`
4271
- INSERT INTO performance_aggregates (
4272
- agent, task_type, total_tasks, successful_tasks, failed_tasks,
4273
- total_input_tokens, total_output_tokens, total_duration_ms, total_cost, total_retries, last_updated
4274
- ) VALUES (
4275
- @agent, @taskType, @totalTasks, @successfulTasks, @failedTasks,
4276
- @inputTokens, @outputTokens, @durationMs, @cost, @retries, @lastUpdated
4180
+ const existing = this._syncAdapter.querySync("SELECT version_id FROM _schema_version WHERE version_id = 1");
4181
+ if (existing.length === 0) this._syncAdapter.querySync("INSERT INTO _schema_version (version_id) VALUES (1)");
4182
+ this._syncAdapter.execSync(`
4183
+ CREATE TABLE IF NOT EXISTS task_metrics (
4184
+ task_id VARCHAR(255) NOT NULL,
4185
+ agent VARCHAR(128) NOT NULL,
4186
+ task_type VARCHAR(128) NOT NULL,
4187
+ outcome VARCHAR(16) NOT NULL CHECK(outcome IN ('success', 'failure')),
4188
+ failure_reason TEXT,
4189
+ input_tokens INTEGER NOT NULL DEFAULT 0,
4190
+ output_tokens INTEGER NOT NULL DEFAULT 0,
4191
+ duration_ms INTEGER NOT NULL DEFAULT 0,
4192
+ cost DOUBLE NOT NULL DEFAULT 0.0,
4193
+ estimated_cost DOUBLE NOT NULL DEFAULT 0.0,
4194
+ billing_mode VARCHAR(32) NOT NULL DEFAULT 'api',
4195
+ retries INTEGER NOT NULL DEFAULT 0,
4196
+ recorded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
4197
+ PRIMARY KEY (task_id, recorded_at)
4198
+ )
4199
+ `);
4200
+ this._syncAdapter.execSync(`CREATE INDEX IF NOT EXISTS idx_tm_agent ON task_metrics(agent)`);
4201
+ this._syncAdapter.execSync(`CREATE INDEX IF NOT EXISTS idx_tm_task_type ON task_metrics(task_type)`);
4202
+ this._syncAdapter.execSync(`CREATE INDEX IF NOT EXISTS idx_tm_recorded_at ON task_metrics(recorded_at)`);
4203
+ this._syncAdapter.execSync(`CREATE INDEX IF NOT EXISTS idx_tm_agent_type ON task_metrics(agent, task_type)`);
4204
+ this._syncAdapter.execSync(`
4205
+ CREATE TABLE IF NOT EXISTS performance_aggregates (
4206
+ agent VARCHAR(255) NOT NULL,
4207
+ task_type VARCHAR(255) NOT NULL,
4208
+ total_tasks INTEGER NOT NULL DEFAULT 0,
4209
+ successful_tasks INTEGER NOT NULL DEFAULT 0,
4210
+ failed_tasks INTEGER NOT NULL DEFAULT 0,
4211
+ total_input_tokens INTEGER NOT NULL DEFAULT 0,
4212
+ total_output_tokens INTEGER NOT NULL DEFAULT 0,
4213
+ total_duration_ms INTEGER NOT NULL DEFAULT 0,
4214
+ total_cost DOUBLE NOT NULL DEFAULT 0.0,
4215
+ total_retries INTEGER NOT NULL DEFAULT 0,
4216
+ last_updated DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
4217
+ PRIMARY KEY (agent, task_type)
4218
+ )
4219
+ `);
4220
+ this._syncAdapter.execSync(`
4221
+ CREATE TABLE IF NOT EXISTS routing_recommendations (
4222
+ id INTEGER PRIMARY KEY AUTO_INCREMENT,
4223
+ task_type VARCHAR(128) NOT NULL,
4224
+ current_agent VARCHAR(128) NOT NULL,
4225
+ recommended_agent VARCHAR(128) NOT NULL,
4226
+ reason TEXT,
4227
+ confidence DOUBLE NOT NULL DEFAULT 0.0,
4228
+ supporting_data TEXT,
4229
+ generated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
4230
+ expires_at TEXT
4277
4231
  )
4278
- ON CONFLICT(agent, task_type) DO UPDATE SET
4279
- total_tasks = total_tasks + @totalTasks,
4280
- successful_tasks = successful_tasks + @successfulTasks,
4281
- failed_tasks = failed_tasks + @failedTasks,
4282
- total_input_tokens = total_input_tokens + @inputTokens,
4283
- total_output_tokens = total_output_tokens + @outputTokens,
4284
- total_duration_ms = total_duration_ms + @durationMs,
4285
- total_cost = total_cost + @cost,
4286
- total_retries = total_retries + @retries,
4287
- last_updated = @lastUpdated
4288
4232
  `);
4289
- logger$11.info({ path: this._path }, "Monitor database ready");
4290
4233
  }
4291
4234
  _assertOpen() {
4292
- if (this._db === null) throw new Error("MonitorDatabase: connection is closed");
4293
- return this._db;
4235
+ if (this._syncAdapter === null || this._adapter === null) throw new Error("MonitorDatabase: connection is closed");
4236
+ return this._syncAdapter;
4237
+ }
4238
+ /**
4239
+ * Execute a query synchronously and return results.
4240
+ * Uses the SyncAdapter interface for guaranteed synchronous execution.
4241
+ */
4242
+ _querySync(sql, params) {
4243
+ const adapter = this._assertOpen();
4244
+ return adapter.querySync(sql, params);
4245
+ }
4246
+ /**
4247
+ * Execute a mutation (INSERT/UPDATE/DELETE) synchronously.
4248
+ * Uses the SyncAdapter interface for guaranteed synchronous execution.
4249
+ */
4250
+ _mutateSync(sql, params) {
4251
+ const adapter = this._assertOpen();
4252
+ adapter.querySync(sql, params);
4294
4253
  }
4295
4254
  insertTaskMetrics(row) {
4296
- this._assertOpen();
4297
- this._stmtInsertMetrics.run({
4298
- taskId: row.taskId,
4299
- agent: row.agent,
4300
- taskType: row.taskType,
4301
- outcome: row.outcome,
4302
- failureReason: row.failureReason ?? null,
4303
- inputTokens: row.inputTokens,
4304
- outputTokens: row.outputTokens,
4305
- durationMs: row.durationMs,
4306
- cost: row.cost,
4307
- estimatedCost: row.estimatedCost,
4308
- billingMode: row.billingMode,
4309
- recordedAt: row.recordedAt
4310
- });
4255
+ const dup = this._querySync("SELECT task_id FROM task_metrics WHERE task_id = ? AND recorded_at = ?", [row.taskId, row.recordedAt]);
4256
+ if (dup.length > 0) return;
4257
+ this._mutateSync(`INSERT INTO task_metrics (
4258
+ task_id, agent, task_type, outcome, failure_reason,
4259
+ input_tokens, output_tokens, duration_ms, cost, estimated_cost,
4260
+ billing_mode, recorded_at
4261
+ ) VALUES (
4262
+ ?, ?, ?, ?, ?,
4263
+ ?, ?, ?, ?, ?,
4264
+ ?, ?
4265
+ )`, [
4266
+ row.taskId,
4267
+ row.agent,
4268
+ row.taskType,
4269
+ row.outcome,
4270
+ row.failureReason ?? null,
4271
+ row.inputTokens,
4272
+ row.outputTokens,
4273
+ row.durationMs,
4274
+ row.cost,
4275
+ row.estimatedCost,
4276
+ row.billingMode,
4277
+ row.recordedAt
4278
+ ]);
4311
4279
  }
4312
4280
  updateAggregates(agent, taskType, delta) {
4313
- this._assertOpen();
4314
- this._stmtUpsertAggregates.run({
4281
+ const now = new Date().toISOString();
4282
+ const successfulTasks = delta.outcome === "success" ? 1 : 0;
4283
+ const failedTasks = delta.outcome === "failure" ? 1 : 0;
4284
+ const retries = delta.retries ?? 0;
4285
+ const existing = this._querySync(`SELECT agent FROM performance_aggregates WHERE agent = ? AND task_type = ?`, [agent, taskType]);
4286
+ if (existing.length === 0) this._mutateSync(`INSERT INTO performance_aggregates (
4287
+ agent, task_type, total_tasks, successful_tasks, failed_tasks,
4288
+ total_input_tokens, total_output_tokens, total_duration_ms, total_cost, total_retries, last_updated
4289
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4315
4290
  agent,
4316
4291
  taskType,
4317
- totalTasks: 1,
4318
- successfulTasks: delta.outcome === "success" ? 1 : 0,
4319
- failedTasks: delta.outcome === "failure" ? 1 : 0,
4320
- inputTokens: delta.inputTokens,
4321
- outputTokens: delta.outputTokens,
4322
- durationMs: delta.durationMs,
4323
- cost: delta.cost,
4324
- retries: delta.retries ?? 0,
4325
- lastUpdated: new Date().toISOString()
4326
- });
4292
+ 1,
4293
+ successfulTasks,
4294
+ failedTasks,
4295
+ delta.inputTokens,
4296
+ delta.outputTokens,
4297
+ delta.durationMs,
4298
+ delta.cost,
4299
+ retries,
4300
+ now
4301
+ ]);
4302
+ else this._mutateSync(`UPDATE performance_aggregates SET
4303
+ total_tasks = total_tasks + 1,
4304
+ successful_tasks = successful_tasks + ?,
4305
+ failed_tasks = failed_tasks + ?,
4306
+ total_input_tokens = total_input_tokens + ?,
4307
+ total_output_tokens = total_output_tokens + ?,
4308
+ total_duration_ms = total_duration_ms + ?,
4309
+ total_cost = total_cost + ?,
4310
+ total_retries = total_retries + ?,
4311
+ last_updated = ?
4312
+ WHERE agent = ? AND task_type = ?`, [
4313
+ successfulTasks,
4314
+ failedTasks,
4315
+ delta.inputTokens,
4316
+ delta.outputTokens,
4317
+ delta.durationMs,
4318
+ delta.cost,
4319
+ retries,
4320
+ now,
4321
+ agent,
4322
+ taskType
4323
+ ]);
4327
4324
  }
4328
4325
  updatePerformanceAggregates(agent, taskType, delta) {
4329
4326
  this.updateAggregates(agent, taskType, delta);
4330
4327
  }
4331
4328
  getAggregates(filter) {
4332
- const db = this._assertOpen();
4333
4329
  let sql = `
4334
4330
  SELECT agent, task_type, total_tasks, successful_tasks, failed_tasks,
4335
4331
  total_input_tokens, total_output_tokens, total_duration_ms, total_cost, last_updated
4336
4332
  FROM performance_aggregates
4337
4333
  `;
4338
4334
  const conditions = [];
4339
- const params = {};
4335
+ const params = [];
4340
4336
  if (filter?.agent) {
4341
- conditions.push("agent = @agent");
4342
- params.agent = filter.agent;
4337
+ conditions.push("agent = ?");
4338
+ params.push(filter.agent);
4343
4339
  }
4344
4340
  if (filter?.taskType) {
4345
- conditions.push("task_type = @taskType");
4346
- params.taskType = filter.taskType;
4341
+ conditions.push("task_type = ?");
4342
+ params.push(filter.taskType);
4347
4343
  }
4348
4344
  if (filter?.sinceDate) {
4349
- conditions.push("last_updated >= @sinceDate");
4350
- params.sinceDate = filter.sinceDate;
4345
+ conditions.push("last_updated >= ?");
4346
+ params.push(filter.sinceDate);
4351
4347
  }
4352
4348
  if (conditions.length > 0) sql += " WHERE " + conditions.join(" AND ");
4353
- const rows = db.prepare(sql).all(params);
4349
+ const rows = this._querySync(sql, params);
4354
4350
  return rows.map((r) => ({
4355
4351
  agent: r.agent,
4356
4352
  taskType: r.task_type,
@@ -4365,9 +4361,7 @@ var MonitorDatabaseImpl = class {
4365
4361
  }));
4366
4362
  }
4367
4363
  getAgentPerformance(agent) {
4368
- const db = this._assertOpen();
4369
- const row = db.prepare(`
4370
- SELECT
4364
+ const rows = this._querySync(`SELECT
4371
4365
  SUM(total_tasks) AS total_tasks,
4372
4366
  SUM(successful_tasks) AS successful_tasks,
4373
4367
  SUM(failed_tasks) AS failed_tasks,
@@ -4378,8 +4372,8 @@ var MonitorDatabaseImpl = class {
4378
4372
  SUM(total_retries) AS total_retries,
4379
4373
  MAX(last_updated) AS last_updated
4380
4374
  FROM performance_aggregates
4381
- WHERE agent = @agent
4382
- `).get({ agent });
4375
+ WHERE agent = ?`, [agent]);
4376
+ const row = rows[0];
4383
4377
  if (row == null || row.total_tasks == null || row.total_tasks === 0) return null;
4384
4378
  const totalTasks = row.total_tasks;
4385
4379
  const successfulTasks = row.successful_tasks ?? 0;
@@ -4402,9 +4396,7 @@ var MonitorDatabaseImpl = class {
4402
4396
  };
4403
4397
  }
4404
4398
  getTaskTypeBreakdown(taskType) {
4405
- const db = this._assertOpen();
4406
- const rows = db.prepare(`
4407
- SELECT
4399
+ const rows = this._querySync(`SELECT
4408
4400
  agent,
4409
4401
  total_tasks,
4410
4402
  successful_tasks,
@@ -4415,9 +4407,8 @@ var MonitorDatabaseImpl = class {
4415
4407
  total_cost,
4416
4408
  last_updated
4417
4409
  FROM performance_aggregates
4418
- WHERE task_type = @taskType
4419
- ORDER BY (CAST(successful_tasks AS REAL) / NULLIF(total_tasks, 0)) DESC
4420
- `).all({ taskType });
4410
+ WHERE task_type = ?
4411
+ ORDER BY (CAST(successful_tasks AS DOUBLE) / NULLIF(total_tasks, 0)) DESC`, [taskType]);
4421
4412
  if (rows.length === 0) return null;
4422
4413
  return {
4423
4414
  task_type: taskType,
@@ -4432,80 +4423,81 @@ var MonitorDatabaseImpl = class {
4432
4423
  };
4433
4424
  }
4434
4425
  pruneOldData(retentionDays) {
4435
- const db = this._assertOpen();
4436
4426
  const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
4437
- const result = db.prepare("DELETE FROM task_metrics WHERE recorded_at < @cutoff").run({ cutoff });
4427
+ const countRows = this._querySync(`SELECT COUNT(*) AS cnt FROM task_metrics WHERE recorded_at < ?`, [cutoff]);
4428
+ const count = countRows[0]?.cnt ?? 0;
4429
+ this._mutateSync(`DELETE FROM task_metrics WHERE recorded_at < ?`, [cutoff]);
4438
4430
  logger$11.info({
4439
4431
  cutoff,
4440
- deleted: result.changes
4432
+ deleted: count
4441
4433
  }, "Pruned old task_metrics rows");
4442
- return result.changes;
4434
+ return count;
4443
4435
  }
4444
4436
  rebuildAggregates() {
4445
- const db = this._assertOpen();
4446
- db.exec("BEGIN IMMEDIATE");
4437
+ const adapter = this._assertOpen();
4447
4438
  try {
4448
- db.exec(`
4449
- DELETE FROM performance_aggregates;
4450
-
4451
- INSERT INTO performance_aggregates (
4452
- agent, task_type,
4453
- total_tasks, successful_tasks, failed_tasks,
4454
- total_input_tokens, total_output_tokens, total_duration_ms, total_cost, total_retries,
4455
- last_updated
4456
- )
4457
- SELECT
4439
+ adapter.execSync(`BEGIN`);
4440
+ this._mutateSync(`DELETE FROM performance_aggregates`);
4441
+ const rows = this._querySync(`SELECT
4458
4442
  agent,
4459
4443
  task_type,
4460
- COUNT(*) AS total_tasks,
4461
- SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END) AS successful_tasks,
4462
- SUM(CASE WHEN outcome = 'failure' THEN 1 ELSE 0 END) AS failed_tasks,
4463
- SUM(input_tokens) AS total_input_tokens,
4464
- SUM(output_tokens) AS total_output_tokens,
4465
- SUM(duration_ms) AS total_duration_ms,
4466
- SUM(cost) AS total_cost,
4467
- COALESCE(SUM(retries), 0) AS total_retries,
4468
- datetime('now') AS last_updated
4444
+ COUNT(*) AS total_tasks,
4445
+ SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END) AS successful_tasks,
4446
+ SUM(CASE WHEN outcome = 'failure' THEN 1 ELSE 0 END) AS failed_tasks,
4447
+ SUM(input_tokens) AS total_input_tokens,
4448
+ SUM(output_tokens) AS total_output_tokens,
4449
+ SUM(duration_ms) AS total_duration_ms,
4450
+ SUM(cost) AS total_cost,
4451
+ COALESCE(SUM(retries), 0) AS total_retries
4469
4452
  FROM task_metrics
4470
- GROUP BY agent, task_type;
4471
- `);
4472
- db.exec("COMMIT");
4453
+ GROUP BY agent, task_type`);
4454
+ const now = new Date().toISOString();
4455
+ for (const r of rows) this._mutateSync(`INSERT INTO performance_aggregates (
4456
+ agent, task_type,
4457
+ total_tasks, successful_tasks, failed_tasks,
4458
+ total_input_tokens, total_output_tokens, total_duration_ms, total_cost, total_retries,
4459
+ last_updated
4460
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
4461
+ r.agent,
4462
+ r.task_type,
4463
+ r.total_tasks,
4464
+ r.successful_tasks,
4465
+ r.failed_tasks,
4466
+ r.total_input_tokens,
4467
+ r.total_output_tokens,
4468
+ r.total_duration_ms,
4469
+ r.total_cost,
4470
+ r.total_retries,
4471
+ now
4472
+ ]);
4473
+ adapter.execSync(`COMMIT`);
4474
+ logger$11.info("Rebuilt performance_aggregates from task_metrics");
4473
4475
  } catch (err) {
4474
- db.exec("ROLLBACK");
4476
+ try {
4477
+ adapter.execSync(`ROLLBACK`);
4478
+ } catch {}
4475
4479
  throw err;
4476
4480
  }
4477
- logger$11.info("Rebuilt performance_aggregates from task_metrics");
4478
4481
  }
4479
4482
  resetAllData() {
4480
- const db = this._assertOpen();
4481
- db.exec("DELETE FROM task_metrics");
4482
- db.exec("DELETE FROM performance_aggregates");
4483
+ this._mutateSync(`DELETE FROM task_metrics`);
4484
+ this._mutateSync(`DELETE FROM performance_aggregates`);
4483
4485
  logger$11.info({ path: this._path }, "Monitor data reset — all rows deleted");
4484
4486
  }
4485
4487
  getTaskMetricsDateRange() {
4486
- const db = this._assertOpen();
4487
- const row = db.prepare(`
4488
- SELECT MIN(recorded_at) AS earliest, MAX(recorded_at) AS latest
4489
- FROM task_metrics
4490
- `).get();
4488
+ const rows = this._querySync(`SELECT MIN(recorded_at) AS earliest, MAX(recorded_at) AS latest FROM task_metrics`);
4491
4489
  return {
4492
- earliest: row?.earliest ?? null,
4493
- latest: row?.latest ?? null
4490
+ earliest: rows[0]?.earliest ?? null,
4491
+ latest: rows[0]?.latest ?? null
4494
4492
  };
4495
4493
  }
4496
4494
  close() {
4497
- if (this._db === null) return;
4498
- this._db.close();
4499
- this._db = null;
4495
+ if (this._adapter === null) return;
4496
+ this._adapter.close();
4497
+ this._adapter = null;
4498
+ this._syncAdapter = null;
4500
4499
  logger$11.info({ path: this._path }, "Monitor database closed");
4501
4500
  }
4502
- /**
4503
- * Access the raw underlying database for testing purposes only.
4504
- * @internal
4505
- */
4506
- get rawDb() {
4507
- return this._assertOpen();
4508
- }
4509
4501
  };
4510
4502
 
4511
4503
  //#endregion
@@ -6901,22 +6893,25 @@ const logger$4 = createLogger("export-cmd");
6901
6893
  */
6902
6894
  async function runExportAction(options) {
6903
6895
  const { runId, outputDir, projectRoot, outputFormat } = options;
6904
- let dbWrapper;
6896
+ let adapter;
6905
6897
  try {
6906
6898
  const dbRoot = await resolveMainRepoRoot(projectRoot);
6907
6899
  const dbPath = join$1(dbRoot, ".substrate", "substrate.db");
6908
- if (!existsSync$1(dbPath)) {
6900
+ const doltDir = join$1(dbRoot, ".substrate", "state", ".dolt");
6901
+ if (!existsSync$1(dbPath) && !existsSync$1(doltDir)) {
6909
6902
  const errorMsg = `Decision store not initialized. Run 'substrate init' first.`;
6910
6903
  if (outputFormat === "json") process.stdout.write(JSON.stringify({ error: errorMsg }) + "\n");
6911
6904
  else process.stderr.write(`Error: ${errorMsg}\n`);
6912
6905
  return 1;
6913
6906
  }
6914
- dbWrapper = new DatabaseWrapper(dbPath);
6915
- dbWrapper.open();
6916
- const db = dbWrapper.db;
6907
+ adapter = createDatabaseAdapter({
6908
+ backend: "auto",
6909
+ basePath: dbRoot
6910
+ });
6911
+ await initSchema(adapter);
6917
6912
  let run;
6918
- if (runId !== void 0 && runId !== "") run = db.prepare("SELECT * FROM pipeline_runs WHERE id = ?").get(runId);
6919
- else run = getLatestRun(db);
6913
+ if (runId !== void 0 && runId !== "") run = await getPipelineRunById(adapter, runId);
6914
+ else run = await getLatestRun(adapter);
6920
6915
  if (run === void 0) {
6921
6916
  const errorMsg = runId !== void 0 ? `Pipeline run '${runId}' not found.` : "No pipeline runs found. Run `substrate run` first.";
6922
6917
  if (outputFormat === "json") process.stdout.write(JSON.stringify({ error: errorMsg }) + "\n");
@@ -6928,7 +6923,7 @@ async function runExportAction(options) {
6928
6923
  if (!existsSync$1(resolvedOutputDir)) mkdirSync$1(resolvedOutputDir, { recursive: true });
6929
6924
  const filesWritten = [];
6930
6925
  const phasesExported = [];
6931
- const analysisDecisions = getDecisionsByPhaseForRun(db, activeRunId, "analysis");
6926
+ const analysisDecisions = await getDecisionsByPhaseForRun(adapter, activeRunId, "analysis");
6932
6927
  if (analysisDecisions.length > 0) {
6933
6928
  const content = renderProductBrief(analysisDecisions);
6934
6929
  if (content !== "") {
@@ -6939,9 +6934,9 @@ async function runExportAction(options) {
6939
6934
  if (outputFormat === "human") process.stdout.write(` Written: ${filePath}\n`);
6940
6935
  }
6941
6936
  }
6942
- const planningDecisions = getDecisionsByPhaseForRun(db, activeRunId, "planning");
6937
+ const planningDecisions = await getDecisionsByPhaseForRun(adapter, activeRunId, "planning");
6943
6938
  if (planningDecisions.length > 0) {
6944
- const requirements = listRequirements(db).filter((r) => r.pipeline_run_id === activeRunId);
6939
+ const requirements = (await listRequirements(adapter)).filter((r) => r.pipeline_run_id === activeRunId);
6945
6940
  const content = renderPrd(planningDecisions, requirements);
6946
6941
  if (content !== "") {
6947
6942
  const filePath = join$1(resolvedOutputDir, "prd.md");
@@ -6951,7 +6946,7 @@ async function runExportAction(options) {
6951
6946
  if (outputFormat === "human") process.stdout.write(` Written: ${filePath}\n`);
6952
6947
  }
6953
6948
  }
6954
- const solutioningDecisions = getDecisionsByPhaseForRun(db, activeRunId, "solutioning");
6949
+ const solutioningDecisions = await getDecisionsByPhaseForRun(adapter, activeRunId, "solutioning");
6955
6950
  if (solutioningDecisions.length > 0) {
6956
6951
  const archContent = renderArchitecture(solutioningDecisions);
6957
6952
  if (archContent !== "") {
@@ -6978,7 +6973,7 @@ async function runExportAction(options) {
6978
6973
  if (outputFormat === "human") process.stdout.write(` Written: ${filePath}\n`);
6979
6974
  }
6980
6975
  }
6981
- const operationalDecisions = getDecisionsByCategory(db, OPERATIONAL_FINDING);
6976
+ const operationalDecisions = await getDecisionsByCategory(adapter, OPERATIONAL_FINDING);
6982
6977
  if (operationalDecisions.length > 0) {
6983
6978
  const operationalContent = renderOperationalFindings(operationalDecisions);
6984
6979
  if (operationalContent !== "") {
@@ -6989,7 +6984,7 @@ async function runExportAction(options) {
6989
6984
  if (outputFormat === "human") process.stdout.write(` Written: ${filePath}\n`);
6990
6985
  }
6991
6986
  }
6992
- const experimentDecisions = getDecisionsByCategory(db, EXPERIMENT_RESULT);
6987
+ const experimentDecisions = await getDecisionsByCategory(adapter, EXPERIMENT_RESULT);
6993
6988
  if (experimentDecisions.length > 0) {
6994
6989
  const experimentsContent = renderExperiments(experimentDecisions);
6995
6990
  if (experimentsContent !== "") {
@@ -7024,8 +7019,8 @@ async function runExportAction(options) {
7024
7019
  logger$4.error({ err }, "export action failed");
7025
7020
  return 1;
7026
7021
  } finally {
7027
- if (dbWrapper !== void 0) try {
7028
- dbWrapper.close();
7022
+ if (adapter !== void 0) try {
7023
+ await adapter.close();
7029
7024
  } catch {}
7030
7025
  }
7031
7026
  }
@@ -7056,11 +7051,11 @@ function registerExportCommand(program, _version = "0.0.0", projectRoot = proces
7056
7051
  * - When `runId` is omitted, the runId of the last (most recently created)
7057
7052
  * escalation-diagnosis decision is used as the default (AC1 defaulting).
7058
7053
  *
7059
- * @param db The SQLite database connection
7060
- * @param runId Optional run ID to scope the query
7054
+ * @param adapter The database adapter
7055
+ * @param runId Optional run ID to scope the query
7061
7056
  */
7062
- function getRetryableEscalations(db, runId) {
7063
- const decisions = getDecisionsByCategory(db, ESCALATION_DIAGNOSIS);
7057
+ async function getRetryableEscalations(adapter, runId) {
7058
+ const decisions = await getDecisionsByCategory(adapter, ESCALATION_DIAGNOSIS);
7064
7059
  const result = {
7065
7060
  retryable: [],
7066
7061
  skipped: []
@@ -7113,18 +7108,20 @@ async function runRetryEscalatedAction(options) {
7113
7108
  const { runId, dryRun, outputFormat, projectRoot, concurrency, pack: packName, registry: injectedRegistry } = options;
7114
7109
  const dbRoot = await resolveMainRepoRoot(projectRoot);
7115
7110
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
7116
- if (!existsSync(dbPath)) {
7111
+ const doltDir = join(dbRoot, ".substrate", "state", ".dolt");
7112
+ if (!existsSync(dbPath) && !existsSync(doltDir)) {
7117
7113
  const errorMsg = `Decision store not initialized. Run 'substrate init' first.`;
7118
7114
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
7119
7115
  else process.stderr.write(`Error: ${errorMsg}\n`);
7120
7116
  return 1;
7121
7117
  }
7122
- const dbWrapper = new DatabaseWrapper(dbPath);
7118
+ const adapter = createDatabaseAdapter({
7119
+ backend: "auto",
7120
+ basePath: dbRoot
7121
+ });
7123
7122
  try {
7124
- dbWrapper.open();
7125
- runMigrations(dbWrapper.db);
7126
- const db = dbWrapper.db;
7127
- const { retryable, skipped } = getRetryableEscalations(db, runId);
7123
+ await initSchema(adapter);
7124
+ const { retryable, skipped } = await getRetryableEscalations(adapter, runId);
7128
7125
  if (retryable.length === 0) {
7129
7126
  if (outputFormat === "json") process.stdout.write(formatOutput({
7130
7127
  retryKeys: [],
@@ -7162,7 +7159,7 @@ async function runRetryEscalatedAction(options) {
7162
7159
  process.stdout.write(`Retrying: ${count} ${count === 1 ? "story" : "stories"} — ${retryable.join(", ")}\n`);
7163
7160
  for (const s of skipped) process.stdout.write(`Skipping: ${s.key} (${s.reason})\n`);
7164
7161
  }
7165
- const pipelineRun = createPipelineRun(db, {
7162
+ const pipelineRun = await createPipelineRun(adapter, {
7166
7163
  methodology: pack.manifest.name,
7167
7164
  start_phase: "implementation",
7168
7165
  config_json: JSON.stringify({
@@ -7172,14 +7169,14 @@ async function runRetryEscalatedAction(options) {
7172
7169
  })
7173
7170
  });
7174
7171
  const eventBus = createEventBus();
7175
- const contextCompiler = createContextCompiler({ db });
7172
+ const contextCompiler = createContextCompiler({ db: adapter });
7176
7173
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
7177
7174
  const dispatcher = createDispatcher({
7178
7175
  eventBus,
7179
7176
  adapterRegistry: injectedRegistry
7180
7177
  });
7181
7178
  const orchestrator = createImplementationOrchestrator({
7182
- db,
7179
+ db: adapter,
7183
7180
  pack,
7184
7181
  contextCompiler,
7185
7182
  dispatcher,
@@ -7197,12 +7194,14 @@ async function runRetryEscalatedAction(options) {
7197
7194
  if (result?.tokenUsage !== void 0) {
7198
7195
  const { input, output } = result.tokenUsage;
7199
7196
  const costUsd = (input * 3 + output * 15) / 1e6;
7200
- addTokenUsage(db, pipelineRun.id, {
7197
+ addTokenUsage(adapter, pipelineRun.id, {
7201
7198
  phase: payload.phase,
7202
7199
  agent: "claude-code",
7203
7200
  input_tokens: input,
7204
7201
  output_tokens: output,
7205
7202
  cost_usd: costUsd
7203
+ }).catch((err) => {
7204
+ logger$3.warn({ err }, "Failed to record token usage");
7206
7205
  });
7207
7206
  }
7208
7207
  } catch (err) {
@@ -7232,7 +7231,7 @@ async function runRetryEscalatedAction(options) {
7232
7231
  return 1;
7233
7232
  } finally {
7234
7233
  try {
7235
- dbWrapper.close();
7234
+ await adapter.close();
7236
7235
  } catch {}
7237
7236
  }
7238
7237
  }