substrate-ai 0.20.91 → 0.20.93

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.
@@ -4,7 +4,6 @@ import { resolveMainRepoRoot, resolveRunManifest } from "./manifest-read-CSmDEOW
4
4
  import { createRequire } from "module";
5
5
  import { dirname, join } from "path";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
- import { spawnSync } from "node:child_process";
8
7
  import { join as join$1 } from "node:path";
9
8
  import { readFile, writeFile } from "node:fs/promises";
10
9
  import { existsSync as existsSync$1 } from "fs";
@@ -403,13 +402,6 @@ function formatPipelineSummary(run, tokenSummary, decisionsCount, storiesCount,
403
402
 
404
403
  //#endregion
405
404
  //#region src/modules/state/file-store.ts
406
- /**
407
- * In-memory / file-backed StateStore implementation.
408
- *
409
- * Suitable for CI environments and testing where orchestrator state is
410
- * ephemeral. Use DoltStateStore for branch-per-story isolation and versioned
411
- * history in production.
412
- */
413
405
  var FileStateStore = class {
414
406
  _basePath;
415
407
  _stories = new Map();
@@ -423,9 +415,6 @@ var FileStateStore = class {
423
415
  }
424
416
  async initialize() {}
425
417
  async close() {}
426
- async getStoryState(storyKey) {
427
- return this._stories.get(storyKey);
428
- }
429
418
  async setStoryState(storyKey, state) {
430
419
  this._stories.set(storyKey, {
431
420
  ...state,
@@ -451,23 +440,6 @@ var FileStateStore = class {
451
440
  };
452
441
  this._metrics.push(record);
453
442
  }
454
- async queryMetrics(filter) {
455
- const storyKey = filter.storyKey ?? filter.story_key;
456
- const taskType = filter.taskType ?? filter.task_type;
457
- return this._metrics.filter((m) => {
458
- if (storyKey !== void 0 && m.storyKey !== storyKey) return false;
459
- if (taskType !== void 0 && m.taskType !== taskType) return false;
460
- if (filter.sprint !== void 0 && m.sprint !== filter.sprint) return false;
461
- if (filter.dateFrom !== void 0 && m.recordedAt !== void 0 && m.recordedAt < filter.dateFrom) return false;
462
- if (filter.dateTo !== void 0 && m.recordedAt !== void 0 && m.recordedAt > filter.dateTo) return false;
463
- if (filter.since !== void 0 && m.recordedAt !== void 0 && m.recordedAt < filter.since) return false;
464
- return true;
465
- });
466
- }
467
- /**
468
- * Persist an arbitrary key-value metric for a run.
469
- * Stored in memory AND written to `{basePath}/kv-metrics.json` when basePath is set.
470
- */
471
443
  async setMetric(runId, key, value) {
472
444
  let runMap = this._kvMetrics.get(runId);
473
445
  if (runMap === void 0) {
@@ -477,10 +449,6 @@ var FileStateStore = class {
477
449
  runMap.set(key, value);
478
450
  if (this._basePath !== void 0) await this._flushKvMetrics();
479
451
  }
480
- /**
481
- * Retrieve a previously stored key-value metric for a run.
482
- * Reads from in-memory cache, falling back to the JSON file when basePath is set.
483
- */
484
452
  async getMetric(runId, key) {
485
453
  const inMemory = this._kvMetrics.get(runId)?.get(key);
486
454
  if (inMemory !== void 0) return inMemory;
@@ -492,7 +460,6 @@ var FileStateStore = class {
492
460
  } catch {}
493
461
  return void 0;
494
462
  }
495
- /** Serialize the in-memory kv metrics map to JSON on disk. */
496
463
  async _flushKvMetrics() {
497
464
  if (this._basePath === void 0) return;
498
465
  const serialized = {};
@@ -503,9 +470,6 @@ var FileStateStore = class {
503
470
  const filePath = join$1(this._basePath, "kv-metrics.json");
504
471
  await writeFile(filePath, JSON.stringify(serialized, null, 2), "utf-8");
505
472
  }
506
- async getContracts(storyKey) {
507
- return this._contracts.get(storyKey) ?? [];
508
- }
509
473
  async setContracts(storyKey, contracts) {
510
474
  this._contracts.set(storyKey, contracts.map((c) => ({ ...c })));
511
475
  }
@@ -527,18 +491,9 @@ var FileStateStore = class {
527
491
  await writeFile(filePath, JSON.stringify(serialized, null, 2), "utf-8");
528
492
  }
529
493
  }
530
- async getContractVerification(storyKey) {
531
- return this._contractVerifications.get(storyKey) ?? [];
532
- }
533
494
  async branchForStory(_storyKey) {}
534
495
  async mergeStory(_storyKey) {}
535
496
  async rollbackStory(_storyKey) {}
536
- async diffStory(storyKey) {
537
- return {
538
- storyKey,
539
- tables: []
540
- };
541
- }
542
497
  async getHistory(_limit) {
543
498
  return [];
544
499
  }
@@ -589,27 +544,16 @@ const STORY_KEY_PATTERN = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)?$/;
589
544
  function assertValidStoryKey(storyKey) {
590
545
  if (!STORY_KEY_PATTERN.test(storyKey)) throw new DoltQueryError("assertValidStoryKey", `Invalid story key: '${storyKey}'. Must match pattern <key> or <epic>-<story> (e.g. "E6", "10-1", "1-1a", "NEW-26").`);
591
546
  }
592
- /**
593
- * Dolt-backed implementation of the StateStore interface.
594
- *
595
- * Constructor accepts a deps object for DI: `{ repoPath, client }`.
596
- * Call `initialize()` before any CRUD operations.
597
- */
598
- var DoltStateStore = class DoltStateStore {
547
+ var DoltStateStore = class {
599
548
  _repoPath;
600
549
  _client;
601
550
  _storyBranches = new Map();
551
+ /** In-memory KV store for per-run arbitrary metrics. Not persisted to Dolt. */
552
+ _kvMetrics = new Map();
602
553
  constructor(options) {
603
554
  this._repoPath = options.repoPath;
604
555
  this._client = options.client;
605
556
  }
606
- /**
607
- * Return the branch name for a story if one has been created via branchForStory(),
608
- * or undefined to use the default (main) branch.
609
- */
610
- _branchFor(storyKey) {
611
- return this._storyBranches.get(storyKey);
612
- }
613
557
  async initialize() {
614
558
  await this._client.connect();
615
559
  await this._runMigrations();
@@ -619,61 +563,16 @@ var DoltStateStore = class DoltStateStore {
619
563
  async close() {
620
564
  await this._client.close();
621
565
  }
566
+ /**
567
+ * Idempotent runtime migration: add `repo_map_symbols.dependencies` JSON column
568
+ * if the table exists and the column doesn't. Skips silently when the table
569
+ * is absent (fresh DB or non-Dolt env).
570
+ */
622
571
  async _runMigrations() {
623
- const ddl = [
624
- `CREATE TABLE IF NOT EXISTS stories (
625
- story_key VARCHAR(100) NOT NULL,
626
- phase VARCHAR(30) NOT NULL DEFAULT 'PENDING',
627
- review_cycles INT NOT NULL DEFAULT 0,
628
- last_verdict VARCHAR(64) NULL,
629
- error TEXT NULL,
630
- started_at VARCHAR(64) NULL,
631
- completed_at VARCHAR(64) NULL,
632
- sprint VARCHAR(50) NULL,
633
- PRIMARY KEY (story_key)
634
- )`,
635
- `CREATE TABLE IF NOT EXISTS metrics (
636
- id BIGINT NOT NULL AUTO_INCREMENT,
637
- story_key VARCHAR(100) NOT NULL,
638
- task_type VARCHAR(100) NOT NULL,
639
- model VARCHAR(100) NULL,
640
- tokens_in BIGINT NULL,
641
- tokens_out BIGINT NULL,
642
- cache_read_tokens BIGINT NULL,
643
- cost_usd DOUBLE NULL,
644
- wall_clock_ms BIGINT NULL,
645
- review_cycles INT NULL,
646
- stall_count INT NULL,
647
- result VARCHAR(30) NULL,
648
- recorded_at VARCHAR(64) NULL,
649
- sprint VARCHAR(50) NULL,
650
- PRIMARY KEY (id)
651
- )`,
652
- `CREATE TABLE IF NOT EXISTS contracts (
653
- story_key VARCHAR(100) NOT NULL,
654
- contract_name VARCHAR(200) NOT NULL,
655
- direction VARCHAR(20) NOT NULL,
656
- schema_path VARCHAR(500) NULL,
657
- transport VARCHAR(200) NULL,
658
- PRIMARY KEY (story_key, contract_name, direction)
659
- )`,
660
- `CREATE TABLE IF NOT EXISTS review_verdicts (
661
- id BIGINT NOT NULL AUTO_INCREMENT,
662
- story_key VARCHAR(100) NOT NULL,
663
- task_type VARCHAR(100) NOT NULL,
664
- verdict VARCHAR(64) NOT NULL,
665
- issues_count INT NULL,
666
- notes TEXT NULL,
667
- timestamp VARCHAR(64) NULL,
668
- PRIMARY KEY (id)
669
- )`
670
- ];
671
- for (const sql of ddl) await this._client.query(sql);
672
572
  try {
673
573
  const colRows = await this._client.query(`SHOW COLUMNS FROM repo_map_symbols LIKE 'dependencies'`);
674
574
  if (colRows.length === 0) {
675
575
  await this._client.query(`ALTER TABLE repo_map_symbols ADD COLUMN dependencies JSON`);
676
- await this._client.query(`INSERT IGNORE INTO _schema_version (version, description) VALUES (6, 'Add dependencies JSON column to repo_map_symbols (Epic 28-3)')`);
677
576
  log.info({
678
577
  component: "dolt-state",
679
578
  migration: "v5-to-v6",
@@ -705,233 +604,6 @@ var DoltStateStore = class DoltStateStore {
705
604
  log.warn({ detail }, "Dolt flush failed (non-fatal)");
706
605
  }
707
606
  }
708
- async getStoryState(storyKey) {
709
- const rows = await this._client.query("SELECT * FROM stories WHERE story_key = ?", [storyKey]);
710
- if (rows.length === 0) return void 0;
711
- return this._rowToStory(rows[0]);
712
- }
713
- async setStoryState(storyKey, state) {
714
- const branch = this._branchFor(storyKey);
715
- const sql = `REPLACE INTO stories
716
- (story_key, phase, review_cycles, last_verdict, error, started_at, completed_at, sprint)
717
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
718
- await this._client.query(sql, [
719
- storyKey,
720
- state.phase,
721
- state.reviewCycles,
722
- state.lastVerdict ?? null,
723
- state.error ?? null,
724
- state.startedAt ?? null,
725
- state.completedAt ?? null,
726
- state.sprint ?? null
727
- ], branch);
728
- }
729
- async queryStories(filter) {
730
- const conditions = [];
731
- const params = [];
732
- if (filter.phase !== void 0) {
733
- const phases = Array.isArray(filter.phase) ? filter.phase : [filter.phase];
734
- const placeholders = phases.map(() => "?").join(", ");
735
- conditions.push(`phase IN (${placeholders})`);
736
- params.push(...phases);
737
- }
738
- if (filter.sprint !== void 0) {
739
- conditions.push("sprint = ?");
740
- params.push(filter.sprint);
741
- }
742
- if (filter.storyKey !== void 0) {
743
- conditions.push("story_key = ?");
744
- params.push(filter.storyKey);
745
- }
746
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
747
- const sql = `SELECT * FROM stories ${where} ORDER BY story_key`;
748
- const rows = await this._client.query(sql, params);
749
- return rows.map((r) => this._rowToStory(r));
750
- }
751
- _rowToStory(row) {
752
- return {
753
- storyKey: row.story_key,
754
- phase: row.phase,
755
- reviewCycles: Number(row.review_cycles),
756
- lastVerdict: row.last_verdict ?? void 0,
757
- error: row.error ?? void 0,
758
- startedAt: row.started_at ?? void 0,
759
- completedAt: row.completed_at ?? void 0,
760
- sprint: row.sprint ?? void 0
761
- };
762
- }
763
- async recordMetric(metric) {
764
- const branch = this._branchFor(metric.storyKey);
765
- const recordedAt = metric.recordedAt ?? metric.timestamp ?? new Date().toISOString();
766
- const sql = `INSERT INTO metrics
767
- (story_key, task_type, model, tokens_in, tokens_out, cache_read_tokens,
768
- cost_usd, wall_clock_ms, review_cycles, stall_count, result, recorded_at, sprint)
769
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
770
- await this._client.query(sql, [
771
- metric.storyKey,
772
- metric.taskType,
773
- metric.model ?? null,
774
- metric.tokensIn ?? null,
775
- metric.tokensOut ?? null,
776
- metric.cacheReadTokens ?? null,
777
- metric.costUsd ?? null,
778
- metric.wallClockMs ?? null,
779
- metric.reviewCycles ?? null,
780
- metric.stallCount ?? null,
781
- metric.result ?? null,
782
- recordedAt,
783
- metric.sprint ?? null
784
- ], branch);
785
- }
786
- async queryMetrics(filter) {
787
- const conditions = [];
788
- const params = [];
789
- const storyKey = filter.storyKey ?? filter.story_key;
790
- const taskType = filter.taskType ?? filter.task_type;
791
- if (storyKey !== void 0) {
792
- conditions.push("story_key = ?");
793
- params.push(storyKey);
794
- }
795
- if (taskType !== void 0) {
796
- conditions.push("task_type = ?");
797
- params.push(taskType);
798
- }
799
- if (filter.sprint !== void 0) {
800
- conditions.push("sprint = ?");
801
- params.push(filter.sprint);
802
- }
803
- if (filter.dateFrom !== void 0) {
804
- conditions.push("recorded_at >= ?");
805
- params.push(filter.dateFrom);
806
- }
807
- if (filter.dateTo !== void 0) {
808
- conditions.push("recorded_at <= ?");
809
- params.push(filter.dateTo);
810
- }
811
- if (filter.since !== void 0) {
812
- conditions.push("recorded_at >= ?");
813
- params.push(filter.since);
814
- }
815
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
816
- if (filter.aggregate) {
817
- const sql$1 = `SELECT task_type,
818
- AVG(cost_usd) AS avg_cost_usd,
819
- SUM(tokens_in) AS sum_tokens_in,
820
- SUM(tokens_out) AS sum_tokens_out,
821
- COUNT(*) AS count
822
- FROM metrics ${where} GROUP BY task_type ORDER BY task_type`;
823
- const aggRows = await this._client.query(sql$1, params);
824
- return aggRows.map((r) => this._aggregateRowToMetric(r));
825
- }
826
- const sql = `SELECT * FROM metrics ${where} ORDER BY id`;
827
- const rows = await this._client.query(sql, params);
828
- return rows.map((r) => this._rowToMetric(r));
829
- }
830
- _aggregateRowToMetric(row) {
831
- return {
832
- storyKey: "",
833
- taskType: row.task_type,
834
- costUsd: row.avg_cost_usd ?? void 0,
835
- tokensIn: row.sum_tokens_in ?? void 0,
836
- tokensOut: row.sum_tokens_out ?? void 0,
837
- count: row.count,
838
- result: "aggregate"
839
- };
840
- }
841
- _rowToMetric(row) {
842
- return {
843
- storyKey: row.story_key,
844
- taskType: row.task_type,
845
- model: row.model ?? void 0,
846
- tokensIn: row.tokens_in ?? void 0,
847
- tokensOut: row.tokens_out ?? void 0,
848
- cacheReadTokens: row.cache_read_tokens ?? void 0,
849
- costUsd: row.cost_usd ?? void 0,
850
- wallClockMs: row.wall_clock_ms ?? void 0,
851
- reviewCycles: row.review_cycles ?? void 0,
852
- stallCount: row.stall_count ?? void 0,
853
- result: row.result ?? void 0,
854
- recordedAt: row.recorded_at ?? void 0,
855
- sprint: row.sprint ?? void 0,
856
- timestamp: row.timestamp ?? row.recorded_at ?? void 0
857
- };
858
- }
859
- async getContracts(storyKey) {
860
- const rows = await this._client.query("SELECT * FROM contracts WHERE story_key = ? ORDER BY contract_name", [storyKey]);
861
- return rows.map((r) => this._rowToContract(r));
862
- }
863
- async setContracts(storyKey, contracts) {
864
- const branch = this._branchFor(storyKey);
865
- await this._client.query("DELETE FROM contracts WHERE story_key = ?", [storyKey], branch);
866
- for (const c of contracts) await this._client.query(`INSERT INTO contracts (story_key, contract_name, direction, schema_path, transport)
867
- VALUES (?, ?, ?, ?, ?)`, [
868
- c.storyKey,
869
- c.contractName,
870
- c.direction,
871
- c.schemaPath,
872
- c.transport ?? null
873
- ], branch);
874
- }
875
- _rowToContract(row) {
876
- return {
877
- storyKey: row.story_key,
878
- contractName: row.contract_name,
879
- direction: row.direction,
880
- schemaPath: row.schema_path,
881
- transport: row.transport ?? void 0
882
- };
883
- }
884
- async queryContracts(filter) {
885
- const conditions = [];
886
- const params = [];
887
- if (filter?.storyKey !== void 0) {
888
- conditions.push("story_key = ?");
889
- params.push(filter.storyKey);
890
- }
891
- if (filter?.direction !== void 0) {
892
- conditions.push("direction = ?");
893
- params.push(filter.direction);
894
- }
895
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
896
- const sql = `SELECT * FROM contracts ${where} ORDER BY story_key, contract_name`;
897
- const rows = await this._client.query(sql, params);
898
- return rows.map((r) => this._rowToContract(r));
899
- }
900
- async setContractVerification(storyKey, results) {
901
- const branch = this._branchFor(storyKey);
902
- await this._client.query(`DELETE FROM review_verdicts WHERE story_key = ? AND task_type = 'contract-verification'`, [storyKey], branch);
903
- const failCount = results.filter((r) => r.verdict === "fail").length;
904
- for (const r of results) await this._client.query(`INSERT INTO review_verdicts (story_key, task_type, verdict, issues_count, notes, timestamp)
905
- VALUES (?, 'contract-verification', ?, ?, ?, ?)`, [
906
- storyKey,
907
- r.verdict,
908
- failCount,
909
- JSON.stringify({
910
- contractName: r.contractName,
911
- mismatchDescription: r.mismatchDescription
912
- }),
913
- r.verifiedAt
914
- ], branch);
915
- }
916
- async getContractVerification(storyKey) {
917
- const rows = await this._client.query(`SELECT * FROM review_verdicts WHERE story_key = ? AND task_type = 'contract-verification' ORDER BY timestamp DESC`, [storyKey]);
918
- return rows.map((row) => {
919
- let contractName = "";
920
- let mismatchDescription;
921
- if (row.notes !== null) try {
922
- const parsed = JSON.parse(row.notes);
923
- if (typeof parsed.contractName === "string") contractName = parsed.contractName;
924
- if (typeof parsed.mismatchDescription === "string") mismatchDescription = parsed.mismatchDescription;
925
- } catch {}
926
- return {
927
- storyKey: row.story_key,
928
- contractName,
929
- verdict: row.verdict,
930
- ...mismatchDescription !== void 0 ? { mismatchDescription } : {},
931
- verifiedAt: row.timestamp ?? new Date().toISOString()
932
- };
933
- });
934
- }
935
607
  async branchForStory(storyKey) {
936
608
  assertValidStoryKey(storyKey);
937
609
  const branchName = `story/${storyKey}`;
@@ -963,18 +635,13 @@ var DoltStateStore = class DoltStateStore {
963
635
  const mergeRows = await this._client.query(`CALL DOLT_MERGE('${branchName}')`, [], "main");
964
636
  const mergeResult = mergeRows[0];
965
637
  if (mergeResult && (mergeResult.conflicts ?? 0) > 0) {
966
- let table = "stories";
638
+ let table = "unknown";
967
639
  let rowKey = "unknown";
968
640
  let ourValue;
969
641
  let theirValue;
970
642
  try {
971
- const conflictRows = await this._client.query(`SELECT * FROM dolt_conflicts_stories LIMIT 1`, [], "main");
972
- if (conflictRows.length > 0) {
973
- const row = conflictRows[0];
974
- rowKey = String(row["base_story_key"] ?? row["our_story_key"] ?? "unknown");
975
- ourValue = JSON.stringify(row["our_status"] ?? row);
976
- theirValue = JSON.stringify(row["their_status"] ?? row);
977
- }
643
+ const conflictRows = await this._client.query(`SELECT table_name FROM dolt_conflicts LIMIT 1`, [], "main");
644
+ if (conflictRows.length > 0) table = String(conflictRows[0]["table_name"] ?? "unknown");
978
645
  } catch {}
979
646
  this._storyBranches.delete(storyKey);
980
647
  throw new DoltMergeConflictError(table, [rowKey], {
@@ -1018,112 +685,6 @@ var DoltStateStore = class DoltStateStore {
1018
685
  this._storyBranches.delete(storyKey);
1019
686
  }
1020
687
  }
1021
- /**
1022
- * Tables queried by diffStory(). Each table is checked for row-level changes
1023
- * via SELECT * FROM DOLT_DIFF('main', branchName, tableName).
1024
- */
1025
- static DIFF_TABLES = [
1026
- "stories",
1027
- "contracts",
1028
- "metrics",
1029
- "dispatch_log",
1030
- "build_results",
1031
- "review_verdicts"
1032
- ];
1033
- async diffStory(storyKey) {
1034
- assertValidStoryKey(storyKey);
1035
- const branchName = this._storyBranches.get(storyKey);
1036
- if (branchName === void 0) return this._diffMergedStory(storyKey);
1037
- try {
1038
- await this._client.query(`CALL DOLT_ADD('-A')`, [], branchName);
1039
- await this._client.query(`CALL DOLT_COMMIT('-m', 'Story ${storyKey}: pre-diff snapshot', '--allow-empty')`, [], branchName);
1040
- } catch {}
1041
- return this._diffRange("main", branchName, storyKey);
1042
- }
1043
- /**
1044
- * Diff a merged story by finding its merge commit in the Dolt log.
1045
- * Queries the `dolt_log` system table for commits referencing the story,
1046
- * then diffs `<hash>~1` vs `<hash>` for row-level changes.
1047
- */
1048
- async _diffMergedStory(storyKey) {
1049
- try {
1050
- const rows = await this._client.query(`SELECT commit_hash FROM dolt_log WHERE message LIKE ? LIMIT 1`, [`%${storyKey}%`]);
1051
- if (rows.length === 0) return {
1052
- storyKey,
1053
- tables: []
1054
- };
1055
- const hash = String(rows[0].commit_hash);
1056
- if (!hash) return {
1057
- storyKey,
1058
- tables: []
1059
- };
1060
- return this._diffRange(`${hash}~1`, hash, storyKey);
1061
- } catch {
1062
- return {
1063
- storyKey,
1064
- tables: []
1065
- };
1066
- }
1067
- }
1068
- /**
1069
- * Compute row-level diffs between two Dolt revisions (branches or commit hashes)
1070
- * across all tracked tables.
1071
- */
1072
- async _diffRange(fromRef, toRef, storyKey) {
1073
- const tableDiffs = [];
1074
- for (const table of DoltStateStore.DIFF_TABLES) try {
1075
- const rows = await this._client.query(`SELECT * FROM DOLT_DIFF('${fromRef}', '${toRef}', '${table}')`, [], "main");
1076
- if (rows.length === 0) continue;
1077
- const added = [];
1078
- const modified = [];
1079
- const deleted = [];
1080
- for (const row of rows) {
1081
- const diffType = row["diff_type"];
1082
- const rowKey = this._extractRowKey(row);
1083
- const before = this._extractPrefixedFields(row, "before_");
1084
- const after = this._extractPrefixedFields(row, "after_");
1085
- const diffRow = {
1086
- rowKey,
1087
- ...before !== void 0 && { before },
1088
- ...after !== void 0 && { after }
1089
- };
1090
- if (diffType === "added") added.push(diffRow);
1091
- else if (diffType === "modified") modified.push(diffRow);
1092
- else if (diffType === "removed") deleted.push(diffRow);
1093
- }
1094
- if (added.length > 0 || modified.length > 0 || deleted.length > 0) tableDiffs.push({
1095
- table,
1096
- added,
1097
- modified,
1098
- deleted
1099
- });
1100
- } catch {}
1101
- return {
1102
- storyKey,
1103
- tables: tableDiffs
1104
- };
1105
- }
1106
- /**
1107
- * Extract a human-readable row key from a DOLT_DIFF result row.
1108
- * Tries after_ fields first (for added/modified rows), then before_ fields
1109
- * (for removed rows). Skips commit_hash pseudo-columns.
1110
- */
1111
- _extractRowKey(row) {
1112
- for (const prefix of ["after_", "before_"]) for (const [key, val] of Object.entries(row)) if (key.startsWith(prefix) && !key.endsWith("_commit_hash") && val !== null && val !== void 0) return String(val);
1113
- return "unknown";
1114
- }
1115
- /**
1116
- * Extract all fields with a given prefix from a DOLT_DIFF result row,
1117
- * stripping the prefix from the key names. Returns undefined if no matching
1118
- * fields are found.
1119
- */
1120
- _extractPrefixedFields(row, prefix) {
1121
- const result = {};
1122
- for (const [key, val] of Object.entries(row)) if (key.startsWith(prefix)) result[key.slice(prefix.length)] = val;
1123
- return Object.keys(result).length > 0 ? result : void 0;
1124
- }
1125
- /** In-memory KV store for per-run arbitrary metrics. Not persisted to Dolt. */
1126
- _kvMetrics = new Map();
1127
688
  async setMetric(runId, key, value) {
1128
689
  let runMap = this._kvMetrics.get(runId);
1129
690
  if (runMap === void 0) {
@@ -1165,65 +726,29 @@ var DoltStateStore = class DoltStateStore {
1165
726
 
1166
727
  //#endregion
1167
728
  //#region src/modules/state/index.ts
1168
- const logger$1 = createLogger("state:factory");
1169
729
  /**
1170
- * Synchronously check whether Dolt is available and a Dolt repo exists at the
1171
- * canonical state path under `basePath`.
1172
- *
1173
- * @param basePath - Project root to check (e.g. `process.cwd()`).
1174
- * @returns `{ available: true, reason: '...' }` when both probes pass,
1175
- * `{ available: false, reason: '...' }` otherwise.
730
+ * Create a StateStore for orchestrator use. Returns a FileStateStore the
731
+ * Dolt backend is no longer routed through this factory (use
732
+ * `createDoltOperatorReader` for operator-side Dolt reads).
1176
733
  */
1177
- function detectDoltAvailableSync(basePath) {
1178
- const result = spawnSync("dolt", ["version"], { stdio: "ignore" });
1179
- const binaryFound = result.error == null && result.status === 0;
1180
- if (!binaryFound) return {
1181
- available: false,
1182
- reason: "dolt binary not found on PATH"
1183
- };
1184
- const stateDoltDir = join$1(basePath, ".substrate", "state", ".dolt");
1185
- const repoExists = existsSync(stateDoltDir);
1186
- if (!repoExists) return {
1187
- available: false,
1188
- reason: `Dolt repo not initialised at ${stateDoltDir}`
1189
- };
1190
- return {
1191
- available: true,
1192
- reason: "dolt binary found and repo initialised"
1193
- };
734
+ function createStateStore(config = {}) {
735
+ return new FileStateStore({ basePath: config.basePath });
1194
736
  }
1195
737
  /**
1196
- * Create a StateStore backed by the specified backend.
738
+ * Create a DoltOperatorReader for CLI operator commands (history, routing,
739
+ * metrics, health). Constructs a DoltClient against `<basePath>/.dolt/` and
740
+ * exposes only the read-side surface meaningful for operators.
1197
741
  *
1198
- * @param config - Optional configuration. Defaults to `{ backend: 'auto' }`.
1199
- * @returns A StateStore instance. Call `initialize()` before use.
742
+ * Caller is responsible for calling `initialize()` before use and `close()`
743
+ * when done.
1200
744
  */
1201
- function createStateStore(config = {}) {
1202
- const backend = config.backend ?? "auto";
1203
- if (backend === "dolt") {
1204
- const repoPath = config.basePath ?? process.cwd();
1205
- const client = new DoltClient({ repoPath });
1206
- return new DoltStateStore({
1207
- repoPath,
1208
- client
1209
- });
1210
- }
1211
- if (backend === "auto") {
1212
- const repoPath = config.basePath ?? process.cwd();
1213
- const detection = detectDoltAvailableSync(repoPath);
1214
- if (detection.available) {
1215
- logger$1.debug(`Dolt detected, using DoltStateStore (state path: ${join$1(repoPath, ".substrate", "state")})`);
1216
- const client = new DoltClient({ repoPath });
1217
- return new DoltStateStore({
1218
- repoPath,
1219
- client
1220
- });
1221
- } else {
1222
- logger$1.debug(`Dolt not found, using FileStateStore (reason: ${detection.reason})`);
1223
- return new FileStateStore({ basePath: config.basePath });
1224
- }
1225
- }
1226
- return new FileStateStore({ basePath: config.basePath });
745
+ function createDoltOperatorReader(config) {
746
+ const repoPath = config.basePath;
747
+ const client = new DoltClient({ repoPath });
748
+ return new DoltStateStore({
749
+ repoPath,
750
+ client
751
+ });
1227
752
  }
1228
753
 
1229
754
  //#endregion
@@ -1269,8 +794,8 @@ function inspectProcessTree(opts) {
1269
794
  timeout: 5e3
1270
795
  });
1271
796
  else {
1272
- const { execFileSync: execFileSync$1 } = __require("node:child_process");
1273
- psOutput = execFileSync$1("ps", ["-eo", "pid,ppid,stat,command"], {
797
+ const { execFileSync } = __require("node:child_process");
798
+ psOutput = execFileSync("ps", ["-eo", "pid,ppid,stat,command"], {
1274
799
  encoding: "utf-8",
1275
800
  timeout: 5e3
1276
801
  });
@@ -1331,8 +856,8 @@ function getAllDescendantPids(rootPids, execFileSyncOverride) {
1331
856
  timeout: 5e3
1332
857
  });
1333
858
  else {
1334
- const { execFileSync: execFileSync$1 } = __require("node:child_process");
1335
- psOutput = execFileSync$1("ps", ["-eo", "pid,ppid"], {
859
+ const { execFileSync } = __require("node:child_process");
860
+ psOutput = execFileSync("ps", ["-eo", "pid,ppid"], {
1336
861
  encoding: "utf-8",
1337
862
  timeout: 5e3
1338
863
  });
@@ -1410,12 +935,12 @@ function buildHealthStoryCountsFromManifest(perStoryState) {
1410
935
  * (missing DB, missing run, terminal run status). Throws only on unexpected errors.
1411
936
  */
1412
937
  async function getAutoHealthData(options) {
1413
- const { runId, projectRoot, stateStore, stateStoreConfig } = options;
938
+ const { runId, projectRoot, doltReader, doltReaderConfig } = options;
1414
939
  const dbRoot = await resolveMainRepoRoot(projectRoot);
1415
940
  const dbPath = join(dbRoot, ".substrate", "substrate.db");
1416
941
  let doltStateInfo;
1417
- if (stateStoreConfig?.backend === "dolt" && stateStore) {
1418
- const repoPath = stateStoreConfig.basePath ?? projectRoot;
942
+ if (doltReader && doltReaderConfig?.basePath !== void 0) {
943
+ const repoPath = doltReaderConfig.basePath;
1419
944
  const doltDirPath = join(repoPath, ".dolt");
1420
945
  const initialized = existsSync(doltDirPath);
1421
946
  let responsive = false;
@@ -1423,7 +948,7 @@ async function getAutoHealthData(options) {
1423
948
  let branches;
1424
949
  let currentBranch;
1425
950
  try {
1426
- await stateStore.getHistory(1);
951
+ await doltReader.getHistory(1);
1427
952
  responsive = true;
1428
953
  try {
1429
954
  const { execFile: ef } = await import("node:child_process");
@@ -1673,24 +1198,18 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
1673
1198
  program.command("health").description("Check pipeline health: process status, stall detection, and verdict").option("--run-id <id>", "Pipeline run ID to query (defaults to latest)").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").action(async (opts) => {
1674
1199
  const outputFormat = opts.outputFormat === "json" ? "json" : "human";
1675
1200
  const root = opts.projectRoot;
1676
- let stateStore;
1677
- let stateStoreConfig;
1201
+ let doltReader;
1202
+ let doltReaderConfig;
1678
1203
  const doltStatePath = join(root, ".substrate", "state", ".dolt");
1679
1204
  if (existsSync(doltStatePath)) {
1680
1205
  const basePath = join(root, ".substrate", "state");
1681
- stateStoreConfig = {
1682
- backend: "dolt",
1683
- basePath
1684
- };
1206
+ doltReaderConfig = { basePath };
1685
1207
  try {
1686
- stateStore = createStateStore({
1687
- backend: "dolt",
1688
- basePath
1689
- });
1690
- await stateStore.initialize();
1208
+ doltReader = createDoltOperatorReader({ basePath });
1209
+ await doltReader.initialize();
1691
1210
  } catch {
1692
- stateStore = void 0;
1693
- stateStoreConfig = void 0;
1211
+ doltReader = void 0;
1212
+ doltReaderConfig = void 0;
1694
1213
  }
1695
1214
  }
1696
1215
  try {
@@ -1698,18 +1217,18 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
1698
1217
  outputFormat,
1699
1218
  runId: opts.runId,
1700
1219
  projectRoot: root,
1701
- stateStore,
1702
- stateStoreConfig
1220
+ ...doltReader !== void 0 && { doltReader },
1221
+ ...doltReaderConfig !== void 0 && { doltReaderConfig }
1703
1222
  });
1704
1223
  process.exitCode = exitCode;
1705
1224
  } finally {
1706
1225
  try {
1707
- await stateStore?.close();
1226
+ await doltReader?.close();
1708
1227
  } catch {}
1709
1228
  }
1710
1229
  });
1711
1230
  }
1712
1231
 
1713
1232
  //#endregion
1714
- export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, runHealthAction, validateStoryKey };
1715
- //# sourceMappingURL=health-C91xmseT.js.map
1233
+ export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDoltOperatorReader, createStateStore, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, runHealthAction, validateStoryKey };
1234
+ //# sourceMappingURL=health-dWYa13k4.js.map