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.
- package/dist/cli/index.js +83 -351
- package/dist/cli/templates/claude-md-substrate-section.md +1 -2
- package/dist/{health-KPgV5-4u.js → health-CgY9akax.js} +1 -1
- package/dist/{health-C91xmseT.js → health-dWYa13k4.js} +48 -529
- package/dist/{run-DZz1qqAp.js → run-BXRwQRst.js} +2 -2
- package/dist/{run-Ba-2-qk3.js → run-DPQ4DT5Q.js} +2 -2
- package/package.json +1 -1
|
@@ -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 = "
|
|
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
|
|
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
|
-
*
|
|
1171
|
-
*
|
|
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
|
|
1178
|
-
|
|
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
|
|
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
|
-
*
|
|
1199
|
-
*
|
|
742
|
+
* Caller is responsible for calling `initialize()` before use and `close()`
|
|
743
|
+
* when done.
|
|
1200
744
|
*/
|
|
1201
|
-
function
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
|
1273
|
-
psOutput = execFileSync
|
|
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
|
|
1335
|
-
psOutput = execFileSync
|
|
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,
|
|
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 (
|
|
1418
|
-
const repoPath =
|
|
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
|
|
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
|
|
1677
|
-
let
|
|
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
|
-
|
|
1682
|
-
backend: "dolt",
|
|
1683
|
-
basePath
|
|
1684
|
-
};
|
|
1206
|
+
doltReaderConfig = { basePath };
|
|
1685
1207
|
try {
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
basePath
|
|
1689
|
-
});
|
|
1690
|
-
await stateStore.initialize();
|
|
1208
|
+
doltReader = createDoltOperatorReader({ basePath });
|
|
1209
|
+
await doltReader.initialize();
|
|
1691
1210
|
} catch {
|
|
1692
|
-
|
|
1693
|
-
|
|
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
|
-
|
|
1702
|
-
|
|
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
|
|
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-
|
|
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
|