substrate-ai 0.15.1 → 0.16.1

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
@@ -4,7 +4,7 @@ import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, EXPERIMENT_RESULT, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRequirements, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-CLvAwmT7.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, listGraphRuns, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CRP9jiCz.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-C7m0gTBv.js";
8
8
  import "../errors-D1LU8CZ9.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -4359,7 +4359,7 @@ async function runSupervisorAction(options, deps = {}) {
4359
4359
  await initSchema(expAdapter);
4360
4360
  const { runRunAction: runPipeline } = await import(
4361
4361
  /* @vite-ignore */
4362
- "../run-BPSzSeU8.js"
4362
+ "../run-Cfm9wW8m.js"
4363
4363
  );
4364
4364
  const runStoryFn = async (opts) => {
4365
4365
  const exitCode = await runPipeline({
@@ -4998,6 +4998,19 @@ async function runMetricsAction(options) {
4998
4998
  const execAt = String(r.executed_at).slice(0, 19);
4999
4999
  process.stdout.write(` ${String(r.iteration).padStart(3)} ${scoreStr.padStart(7)} ${passesStr.padStart(7)} ${passedTotal.padStart(13)} ${execAt.padEnd(20)}\n`);
5000
5000
  }
5001
+ try {
5002
+ const twinRuns = await getTwinRunsForRun(adapter, resolvedRunId);
5003
+ if (twinRuns.length > 0) {
5004
+ process.stdout.write("\nTwins:\n");
5005
+ for (const twin of twinRuns) {
5006
+ const ports = twin.ports.map((p) => `${p.host}:${p.container}`).join(", ");
5007
+ const stoppedAt = twin.stopped_at ?? "still running";
5008
+ process.stdout.write(` ${twin.twin_name} [${twin.status}] ports: ${ports || "none"} started: ${twin.started_at} stopped: ${stoppedAt} health failures: ${twin.health_failure_count}\n`);
5009
+ }
5010
+ }
5011
+ } catch (err) {
5012
+ logger$10.debug({ err }, "getTwinRunsForRun failed — twin_runs table may not exist yet");
5013
+ }
5001
5014
  }
5002
5015
  return 0;
5003
5016
  }
@@ -7,13 +7,15 @@ import { access, readFile, readdir, stat } from "fs/promises";
7
7
  import { EventEmitter } from "node:events";
8
8
  import yaml from "js-yaml";
9
9
  import * as actualFS from "node:fs";
10
- import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { execFile, execSync, spawn } from "node:child_process";
12
- import path, { dirname as dirname$1, extname as extname$1, join as join$1, posix, win32 } from "node:path";
12
+ import path, { dirname as dirname$1, extname as extname$1, join as join$1, posix, resolve as resolve$1, win32 } from "node:path";
13
+ import { tmpdir } from "node:os";
13
14
  import { createHash, randomUUID } from "node:crypto";
14
15
  import { z } from "zod";
15
- import { lstat, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, readlink, realpath, stat as stat$1, writeFile as writeFile$1 } from "node:fs/promises";
16
+ import { access as access$1, lstat, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, readlink, realpath, stat as stat$1, unlink, writeFile as writeFile$1 } from "node:fs/promises";
16
17
  import { existsSync as existsSync$1, lstatSync, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdir as readdir$2, readdirSync as readdirSync$1, readlinkSync, realpathSync, unlinkSync, writeFileSync as writeFileSync$1 } from "fs";
18
+ import { createRequire } from "node:module";
17
19
  import { fileURLToPath } from "node:url";
18
20
  import { createHash as createHash$1 } from "crypto";
19
21
  import { spawn as spawn$1 } from "child_process";
@@ -17035,12 +17037,26 @@ const __dirname = dirname$1(fileURLToPath(import.meta.url));
17035
17037
  /**
17036
17038
  * Returns the absolute path to the bundled SDLC pipeline DOT file.
17037
17039
  *
17038
- * Exported for use by the CLI composition root (story 43-10) — preferred over
17039
- * ad-hoc path construction with createRequire because `__dirname` is already
17040
- * resolved here relative to the installed package.
17040
+ * Resolution order:
17041
+ * 1. Relative to __dirname (works in source/unbundled: __dirname = packages/sdlc/src/orchestrator/)
17042
+ * 2. Via createRequire to locate @substrate-ai/sdlc package.json, then graphs/ relative to it
17043
+ * (works when bundled: __dirname points to dist/ but createRequire finds the real package)
17044
+ *
17045
+ * @throws {Error} if the DOT file cannot be found by any method.
17041
17046
  */
17042
17047
  function resolveGraphPath$1() {
17043
- return join$1(__dirname, "../../graphs/sdlc-pipeline.dot");
17048
+ const candidates = [
17049
+ join$1(__dirname, "../../graphs/sdlc-pipeline.dot"),
17050
+ join$1(__dirname, "../graphs/sdlc-pipeline.dot"),
17051
+ join$1(__dirname, "graphs/sdlc-pipeline.dot")
17052
+ ];
17053
+ try {
17054
+ const require$1 = createRequire(import.meta.url);
17055
+ const sdlcPkgPath = require$1.resolve("@substrate-ai/sdlc/package.json");
17056
+ candidates.push(join$1(dirname$1(sdlcPkgPath), "graphs", "sdlc-pipeline.dot"));
17057
+ } catch {}
17058
+ for (const candidate of candidates) if (existsSync(candidate)) return candidate;
17059
+ throw new Error(`Cannot locate sdlc-pipeline.dot. Searched:\n${candidates.map((c) => ` ${c}`).join("\n")}`);
17044
17060
  }
17045
17061
  /** Thrown by `createGraphOrchestrator` when the supplied graph is structurally invalid. */
17046
17062
  var GraphOrchestratorInitError = class extends Error {
@@ -17066,7 +17082,11 @@ function createGraphOrchestrator(config) {
17066
17082
  const initialContext = {
17067
17083
  storyKey,
17068
17084
  projectRoot: config.projectRoot,
17069
- methodologyPack: config.methodologyPack
17085
+ methodologyPack: config.methodologyPack,
17086
+ ...config.pipelineRunId !== void 0 ? {
17087
+ runId: config.pipelineRunId,
17088
+ pipelineRunId: config.pipelineRunId
17089
+ } : {}
17070
17090
  };
17071
17091
  const factoryBus = new EventEmitter();
17072
17092
  const bridge = config.eventBus != null ? createSdlcEventBridge({
@@ -22955,7 +22975,7 @@ function createGraphExecutor() {
22955
22975
  };
22956
22976
  let completedNodes = [];
22957
22977
  let nodeRetries = {};
22958
- let context = new GraphContext();
22978
+ let context = new GraphContext(config.initialContext);
22959
22979
  let step = 0;
22960
22980
  const visitCount = new Map();
22961
22981
  let resumeCompletedSet = null;
@@ -27316,7 +27336,7 @@ var Pattern = class Pattern {
27316
27336
  #isUNC;
27317
27337
  #isAbsolute;
27318
27338
  #followGlobstar = true;
27319
- constructor(patternList, globList, index, platform) {
27339
+ constructor(patternList, globList, index, platform$1) {
27320
27340
  if (!isPatternList(patternList)) throw new TypeError("empty pattern list");
27321
27341
  if (!isGlobList(globList)) throw new TypeError("empty glob list");
27322
27342
  if (globList.length !== patternList.length) throw new TypeError("mismatched pattern list and glob list lengths");
@@ -27325,7 +27345,7 @@ var Pattern = class Pattern {
27325
27345
  this.#patternList = patternList;
27326
27346
  this.#globList = globList;
27327
27347
  this.#index = index;
27328
- this.#platform = platform;
27348
+ this.#platform = platform$1;
27329
27349
  if (this.#index === 0) {
27330
27350
  if (this.isUNC()) {
27331
27351
  const [p0, p1, p2, p3, ...prest] = this.#patternList;
@@ -27472,12 +27492,12 @@ var Ignore = class {
27472
27492
  absoluteChildren;
27473
27493
  platform;
27474
27494
  mmopts;
27475
- constructor(ignored, { nobrace, nocase, noext, noglobstar, platform = defaultPlatform$1 }) {
27495
+ constructor(ignored, { nobrace, nocase, noext, noglobstar, platform: platform$1 = defaultPlatform$1 }) {
27476
27496
  this.relative = [];
27477
27497
  this.absolute = [];
27478
27498
  this.relativeChildren = [];
27479
27499
  this.absoluteChildren = [];
27480
- this.platform = platform;
27500
+ this.platform = platform$1;
27481
27501
  this.mmopts = {
27482
27502
  dot: true,
27483
27503
  nobrace,
@@ -27485,7 +27505,7 @@ var Ignore = class {
27485
27505
  noext,
27486
27506
  noglobstar,
27487
27507
  optimizationLevel: 2,
27488
- platform,
27508
+ platform: platform$1,
27489
27509
  nocomment: true,
27490
27510
  nonegate: true
27491
27511
  };
@@ -28376,14 +28396,24 @@ function getExecutionCommand(filePath) {
28376
28396
  }
28377
28397
  /**
28378
28398
  * Run a single scenario file and return its result.
28399
+ *
28400
+ * @param entry - Scenario entry with name and path.
28401
+ * @param projectRoot - Working directory for scenario execution.
28402
+ * @param env - Optional extra environment variables to inject into the subprocess.
28403
+ * When provided, merged on top of `process.env`.
28379
28404
  */
28380
- function runScenario(entry, projectRoot) {
28405
+ function runScenario(entry, projectRoot, env) {
28381
28406
  return new Promise((resolve$2) => {
28382
28407
  const startTime = Date.now();
28383
28408
  const command = getExecutionCommand(entry.path);
28409
+ const spawnEnv = env != null ? {
28410
+ ...process.env,
28411
+ ...env
28412
+ } : void 0;
28384
28413
  const child = spawn$1(command, [], {
28385
28414
  cwd: projectRoot,
28386
- shell: true
28415
+ shell: true,
28416
+ env: spawnEnv
28387
28417
  });
28388
28418
  let stdoutBuf = "";
28389
28419
  let stderrBuf = "";
@@ -28427,25 +28457,93 @@ function runScenario(entry, projectRoot) {
28427
28457
  });
28428
28458
  }
28429
28459
  /**
28460
+ * Build a ScenarioRunResult where every scenario is marked as failed due to
28461
+ * a twin startup error. No scripts were executed.
28462
+ */
28463
+ function buildStartupFailureResult(scenarioEntries, err) {
28464
+ const scenarios = scenarioEntries.map((entry) => ({
28465
+ name: entry.name,
28466
+ status: "fail",
28467
+ exitCode: -1,
28468
+ stdout: "",
28469
+ stderr: err.message,
28470
+ durationMs: 0
28471
+ }));
28472
+ return {
28473
+ scenarios,
28474
+ summary: {
28475
+ total: scenarios.length,
28476
+ passed: 0,
28477
+ failed: scenarios.length
28478
+ },
28479
+ durationMs: 0
28480
+ };
28481
+ }
28482
+ /**
28430
28483
  * Create a ScenarioRunner that executes all scenarios in a manifest.
28431
28484
  *
28432
- * @param _options - Optional configuration (currently reserved for future use).
28485
+ * @param options - Optional configuration including twin coordinator and timeout.
28433
28486
  */
28434
- function createScenarioRunner(_options) {
28487
+ function createScenarioRunner(options) {
28435
28488
  return { async run(manifest, projectRoot) {
28436
28489
  const startTime = Date.now();
28437
- const scenarios = await Promise.all(manifest.scenarios.map((entry) => runScenario(entry, projectRoot)));
28438
- const passed = scenarios.filter((s$1) => s$1.status === "pass").length;
28439
- const failed = scenarios.filter((s$1) => s$1.status === "fail").length;
28440
- return {
28441
- scenarios,
28442
- summary: {
28443
- total: scenarios.length,
28444
- passed,
28445
- failed
28446
- },
28447
- durationMs: Date.now() - startTime
28448
- };
28490
+ const { twinCoordinator } = options ?? {};
28491
+ const requiresTwins = (manifest.twins?.length ?? 0) > 0 && twinCoordinator != null;
28492
+ if (!requiresTwins) {
28493
+ if (options?.twinHealthMonitor) {
28494
+ const healthStatus = options.twinHealthMonitor.getStatus();
28495
+ const unhealthyNames = Object.entries(healthStatus).filter(([, s$1]) => s$1 === "unhealthy").map(([name]) => name);
28496
+ if (unhealthyNames.length > 0) {
28497
+ const msg = unhealthyNames.map((n$1) => `Twin '${n$1}' is unhealthy`).join("; ");
28498
+ return buildStartupFailureResult(manifest.scenarios, new Error(msg));
28499
+ }
28500
+ }
28501
+ const scenarios = await Promise.all(manifest.scenarios.map((entry) => runScenario(entry, projectRoot)));
28502
+ const passed = scenarios.filter((s$1) => s$1.status === "pass").length;
28503
+ const failed = scenarios.filter((s$1) => s$1.status === "fail").length;
28504
+ return {
28505
+ scenarios,
28506
+ summary: {
28507
+ total: scenarios.length,
28508
+ passed,
28509
+ failed
28510
+ },
28511
+ durationMs: Date.now() - startTime
28512
+ };
28513
+ }
28514
+ let twinEnv;
28515
+ try {
28516
+ twinEnv = await twinCoordinator.startTwins(manifest.twins);
28517
+ } catch (err) {
28518
+ return buildStartupFailureResult(manifest.scenarios, err);
28519
+ }
28520
+ if (options?.twinHealthMonitor) {
28521
+ const healthStatus = options.twinHealthMonitor.getStatus();
28522
+ const unhealthyNames = Object.entries(healthStatus).filter(([, s$1]) => s$1 === "unhealthy").map(([name]) => name);
28523
+ if (unhealthyNames.length > 0) {
28524
+ const msg = unhealthyNames.map((n$1) => `Twin '${n$1}' is unhealthy`).join("; ");
28525
+ try {
28526
+ await twinCoordinator.stopTwins();
28527
+ } catch {}
28528
+ return buildStartupFailureResult(manifest.scenarios, new Error(msg));
28529
+ }
28530
+ }
28531
+ try {
28532
+ const scenarios = await Promise.all(manifest.scenarios.map((entry) => runScenario(entry, projectRoot, twinEnv)));
28533
+ const passed = scenarios.filter((s$1) => s$1.status === "pass").length;
28534
+ const failed = scenarios.filter((s$1) => s$1.status === "fail").length;
28535
+ return {
28536
+ scenarios,
28537
+ summary: {
28538
+ total: scenarios.length,
28539
+ passed,
28540
+ failed
28541
+ },
28542
+ durationMs: Date.now() - startTime
28543
+ };
28544
+ } finally {
28545
+ await twinCoordinator.stopTwins();
28546
+ }
28449
28547
  } };
28450
28548
  }
28451
28549
 
@@ -28488,6 +28586,455 @@ function registerScenariosCommand(program) {
28488
28586
  });
28489
28587
  }
28490
28588
 
28589
+ //#endregion
28590
+ //#region packages/factory/dist/twins/schema.js
28591
+ /**
28592
+ * Validates and transforms a "host:container" port string into a PortMapping object.
28593
+ */
28594
+ const portMappingStringSchema = z.string().regex(/^\d+:\d+$/, "Port mapping must be in \"host:container\" format (e.g., \"5432:5432\")").transform((val) => {
28595
+ const parts = val.split(":");
28596
+ const host = Number(parts[0]);
28597
+ const container = Number(parts[1]);
28598
+ return {
28599
+ host,
28600
+ container
28601
+ };
28602
+ });
28603
+ /**
28604
+ * Validates a healthcheck configuration object.
28605
+ */
28606
+ const twinHealthcheckSchema = z.object({
28607
+ url: z.string().url("Healthcheck URL must be a valid URL"),
28608
+ interval_ms: z.number().int().positive().default(500),
28609
+ timeout_ms: z.number().int().positive().default(1e4)
28610
+ });
28611
+ /**
28612
+ * Validates a full twin definition. Unknown fields are rejected via `.strict()`.
28613
+ */
28614
+ const twinDefinitionSchema = z.object({
28615
+ name: z.string().min(1, "Twin name must not be empty"),
28616
+ image: z.string().min(1, "Twin image must not be empty"),
28617
+ ports: z.array(portMappingStringSchema).default([]),
28618
+ environment: z.record(z.string(), z.string()).default({}),
28619
+ healthcheck: twinHealthcheckSchema.optional()
28620
+ }).strict();
28621
+ const TwinDefinitionSchema = twinDefinitionSchema;
28622
+
28623
+ //#endregion
28624
+ //#region packages/factory/dist/twins/types.js
28625
+ /**
28626
+ * Twin Registry — Type definitions for TwinDefinition and related interfaces.
28627
+ *
28628
+ * Story 47-1.
28629
+ */
28630
+ /**
28631
+ * Thrown when a single twin definition file fails validation or parsing.
28632
+ */
28633
+ var TwinDefinitionError = class extends Error {
28634
+ sourceFile;
28635
+ constructor(message, sourceFile) {
28636
+ super(message);
28637
+ this.sourceFile = sourceFile;
28638
+ this.name = "TwinDefinitionError";
28639
+ }
28640
+ };
28641
+ /**
28642
+ * Thrown when a registry-level constraint is violated (e.g., duplicate twin names).
28643
+ */
28644
+ var TwinRegistryError = class extends Error {
28645
+ constructor(message) {
28646
+ super(message);
28647
+ this.name = "TwinRegistryError";
28648
+ }
28649
+ };
28650
+
28651
+ //#endregion
28652
+ //#region packages/factory/dist/twins/registry.js
28653
+ var TwinRegistry = class {
28654
+ _twins = new Map();
28655
+ /**
28656
+ * Discovers and validates all *.yaml and *.yml twin definition files in the given directory.
28657
+ * Non-recursive — only top-level files are processed.
28658
+ *
28659
+ * @throws {TwinDefinitionError} if a file contains invalid YAML or fails schema validation
28660
+ * @throws {TwinRegistryError} if two files declare the same twin name
28661
+ */
28662
+ async discover(dir) {
28663
+ let entries;
28664
+ try {
28665
+ entries = await readdir$1(dir);
28666
+ } catch (err) {
28667
+ throw new TwinDefinitionError(`Failed to read directory: ${dir} — ${err.message}`);
28668
+ }
28669
+ const yamlFiles = entries.filter((f$1) => f$1.endsWith(".yaml") || f$1.endsWith(".yml"));
28670
+ const perFileErrors = [];
28671
+ for (const filename of yamlFiles) {
28672
+ const filePath = resolve$1(join$1(dir, filename));
28673
+ let raw;
28674
+ try {
28675
+ raw = await readFile$1(filePath, "utf-8");
28676
+ } catch (err) {
28677
+ perFileErrors.push(new TwinDefinitionError(`Failed to read file: ${filePath} — ${err.message}`, filePath));
28678
+ continue;
28679
+ }
28680
+ let parsed;
28681
+ try {
28682
+ parsed = yaml.load(raw);
28683
+ } catch (err) {
28684
+ perFileErrors.push(new TwinDefinitionError(`Twin definition at ${filePath} contains invalid YAML: ${err.message}`, filePath));
28685
+ continue;
28686
+ }
28687
+ const result = TwinDefinitionSchema.safeParse(parsed);
28688
+ if (!result.success) {
28689
+ const firstIssue = result.error.issues[0];
28690
+ const fieldPath = firstIssue?.path?.join(".") ?? "unknown";
28691
+ const fieldMessage = firstIssue?.message ?? result.error.message;
28692
+ let message;
28693
+ const issueCode = firstIssue?.code;
28694
+ if (issueCode === "unrecognized_keys") {
28695
+ const keys = firstIssue.keys ?? [];
28696
+ message = `Twin definition at ${filePath} contains unrecognised field(s): ${keys.join(", ")}`;
28697
+ } else if (issueCode === "unrecognized_key") {
28698
+ const key = firstIssue.key ?? fieldPath;
28699
+ message = `Twin definition at ${filePath} contains unrecognised field(s): ${key}`;
28700
+ } else {
28701
+ const parsedObj = parsed;
28702
+ const isMissingField = parsedObj && typeof parsedObj === "object" && fieldPath !== "unknown" && !(fieldPath in parsedObj);
28703
+ if (isMissingField) message = `Twin definition at ${filePath} is missing required field: ${fieldPath}`;
28704
+ else message = `Twin definition at ${filePath} failed validation — ${fieldPath}: ${fieldMessage}`;
28705
+ }
28706
+ perFileErrors.push(new TwinDefinitionError(message, filePath));
28707
+ continue;
28708
+ }
28709
+ const data = result.data;
28710
+ const twin = {
28711
+ name: data.name,
28712
+ image: data.image,
28713
+ ports: data.ports,
28714
+ environment: data.environment,
28715
+ sourceFile: filePath,
28716
+ ...data.healthcheck !== void 0 && { healthcheck: data.healthcheck }
28717
+ };
28718
+ if (this._twins.has(twin.name)) {
28719
+ const existing = this._twins.get(twin.name);
28720
+ throw new TwinRegistryError(`Duplicate twin name "${twin.name}" found in: ${existing.sourceFile} and ${filePath}`);
28721
+ }
28722
+ this._twins.set(twin.name, twin);
28723
+ }
28724
+ if (perFileErrors.length > 0) throw perFileErrors[0];
28725
+ }
28726
+ /**
28727
+ * Returns all discovered twin definitions.
28728
+ */
28729
+ list() {
28730
+ return Array.from(this._twins.values());
28731
+ }
28732
+ /**
28733
+ * Returns a twin definition by name, or undefined if not found.
28734
+ */
28735
+ get(name) {
28736
+ return this._twins.get(name);
28737
+ }
28738
+ /**
28739
+ * Polls the health endpoint of a twin definition until healthy or timed out.
28740
+ *
28741
+ * @param twin - The twin definition to poll
28742
+ * @param options - Optional overrides (e.g., mock fetch for testing)
28743
+ * @returns HealthPollResult indicating success or timeout
28744
+ */
28745
+ async pollHealth(twin, options) {
28746
+ const { healthcheck } = twin;
28747
+ if (!healthcheck) return {
28748
+ healthy: true,
28749
+ attempts: 0
28750
+ };
28751
+ const fetchFn = options?.fetch ?? globalThis.fetch;
28752
+ const { url, interval_ms = 500, timeout_ms = 1e4 } = healthcheck;
28753
+ const startTime = Date.now();
28754
+ let attempts = 0;
28755
+ while (true) {
28756
+ attempts++;
28757
+ try {
28758
+ const response = await fetchFn(url);
28759
+ if (response.ok) return {
28760
+ healthy: true,
28761
+ attempts
28762
+ };
28763
+ } catch {}
28764
+ const elapsed = Date.now() - startTime;
28765
+ if (elapsed >= timeout_ms) return {
28766
+ healthy: false,
28767
+ error: `Health check timed out after ${timeout_ms}ms`
28768
+ };
28769
+ await new Promise((resolve$2) => setTimeout(resolve$2, interval_ms));
28770
+ if (Date.now() - startTime >= timeout_ms) return {
28771
+ healthy: false,
28772
+ error: `Health check timed out after ${timeout_ms}ms`
28773
+ };
28774
+ }
28775
+ }
28776
+ };
28777
+ /**
28778
+ * Factory function that creates a new TwinRegistry instance.
28779
+ */
28780
+ function createTwinRegistry() {
28781
+ return new TwinRegistry();
28782
+ }
28783
+
28784
+ //#endregion
28785
+ //#region packages/factory/dist/twins/docker-compose.js
28786
+ /**
28787
+ * Thrown when Docker is unavailable or the compose lifecycle fails.
28788
+ */
28789
+ var TwinError = class extends Error {
28790
+ constructor(message) {
28791
+ super(message);
28792
+ this.name = "TwinError";
28793
+ }
28794
+ };
28795
+ /**
28796
+ * Generates a Docker Compose v3.8 YAML string from an array of twin definitions.
28797
+ * Built manually with template strings — no external YAML library required.
28798
+ */
28799
+ function generateComposeYaml(twins) {
28800
+ const lines = [];
28801
+ lines.push("version: '3.8'");
28802
+ lines.push("services:");
28803
+ for (const twin of twins) {
28804
+ lines.push(` ${twin.name}:`);
28805
+ lines.push(` image: ${twin.image}`);
28806
+ if (twin.ports.length > 0) {
28807
+ lines.push(" ports:");
28808
+ for (const port of twin.ports) lines.push(` - "${port.host}:${port.container}"`);
28809
+ }
28810
+ if (twin.environment && Object.keys(twin.environment).length > 0) {
28811
+ lines.push(" environment:");
28812
+ for (const [key, value] of Object.entries(twin.environment)) lines.push(` ${key}: ${value}`);
28813
+ }
28814
+ }
28815
+ return lines.join("\n") + "\n";
28816
+ }
28817
+ /**
28818
+ * Polls a twin's health endpoint until it returns a 2xx response or exhausts attempts.
28819
+ *
28820
+ * Algorithm (from story 47-2 dev notes):
28821
+ * attempts = 0
28822
+ * while attempts < maxAttempts:
28823
+ * try fetch → if ok, return
28824
+ * catch (connection refused) → continue
28825
+ * attempts++
28826
+ * sleep(intervalMs)
28827
+ * throw TwinError
28828
+ */
28829
+ async function pollTwinHealth(twin, maxAttempts, healthIntervalMs) {
28830
+ if (!twin.healthcheck?.url) return;
28831
+ const url = twin.healthcheck.url;
28832
+ const intervalMs = twin.healthcheck.interval_ms ?? healthIntervalMs;
28833
+ let attempts = 0;
28834
+ while (attempts < maxAttempts) {
28835
+ try {
28836
+ const response = await fetch(url);
28837
+ if (response.ok) return;
28838
+ } catch {}
28839
+ attempts++;
28840
+ if (attempts < maxAttempts) await new Promise((resolve$2) => setTimeout(resolve$2, intervalMs));
28841
+ }
28842
+ throw new TwinError(`Twin '${twin.name}' failed health check after ${maxAttempts} attempts`);
28843
+ }
28844
+ /**
28845
+ * Creates a TwinManager that orchestrates Docker Compose for digital twin services.
28846
+ *
28847
+ * The event bus is injected — do NOT import a global singleton or create one internally.
28848
+ *
28849
+ * @param eventBus - Typed event bus for emitting twin lifecycle events
28850
+ * @param options - Optional health check configuration
28851
+ * @returns TwinManager with start() and stop() methods
28852
+ */
28853
+ function createTwinManager(eventBus, options) {
28854
+ const maxHealthAttempts = options?.maxHealthAttempts ?? 30;
28855
+ const healthIntervalMs = options?.healthIntervalMs ?? 1e3;
28856
+ /** Temp directory containing the generated docker-compose.yml. Null when not started. */
28857
+ let composeDir = null;
28858
+ /** Twins that were passed to start() — used by stop() to emit twin:stopped events. */
28859
+ let startedTwins = [];
28860
+ return {
28861
+ async start(twins) {
28862
+ try {
28863
+ execSync("docker info", { stdio: "ignore" });
28864
+ } catch {
28865
+ throw new TwinError("Docker not found — twins require Docker");
28866
+ }
28867
+ const yaml$1 = generateComposeYaml(twins);
28868
+ const dir = join$1(tmpdir(), randomUUID());
28869
+ mkdirSync(dir, { recursive: true });
28870
+ writeFileSync(join$1(dir, "docker-compose.yml"), yaml$1, "utf-8");
28871
+ composeDir = dir;
28872
+ startedTwins = twins;
28873
+ try {
28874
+ execSync("docker compose up -d", {
28875
+ cwd: dir,
28876
+ stdio: "pipe"
28877
+ });
28878
+ } catch (err) {
28879
+ const error = err;
28880
+ const stderr = error.stderr?.toString() ?? "";
28881
+ throw new TwinError(`docker compose up failed: ${stderr}`);
28882
+ }
28883
+ for (const twin of twins) await pollTwinHealth(twin, maxHealthAttempts, healthIntervalMs);
28884
+ for (const twin of twins) eventBus.emit("twin:started", {
28885
+ twinName: twin.name,
28886
+ ports: twin.ports,
28887
+ healthStatus: "healthy"
28888
+ });
28889
+ },
28890
+ async stop() {
28891
+ if (!composeDir) return;
28892
+ const dir = composeDir;
28893
+ try {
28894
+ execSync("docker compose down --remove-orphans", {
28895
+ cwd: dir,
28896
+ stdio: "pipe"
28897
+ });
28898
+ } catch {}
28899
+ rmSync(dir, {
28900
+ recursive: true,
28901
+ force: true
28902
+ });
28903
+ for (const twin of startedTwins) eventBus.emit("twin:stopped", { twinName: twin.name });
28904
+ composeDir = null;
28905
+ startedTwins = [];
28906
+ },
28907
+ getComposeDir() {
28908
+ return composeDir;
28909
+ }
28910
+ };
28911
+ }
28912
+
28913
+ //#endregion
28914
+ //#region packages/factory/dist/twins/templates.js
28915
+ /**
28916
+ * Twin Template Catalog — pre-built twin definition templates for common external services.
28917
+ *
28918
+ * Story 47-4.
28919
+ */
28920
+ const localstackTemplate = {
28921
+ name: "localstack",
28922
+ description: "LocalStack — AWS cloud service emulator (S3, SQS, DynamoDB)",
28923
+ definition: {
28924
+ name: "localstack",
28925
+ image: "localstack/localstack:latest",
28926
+ ports: ["4566:4566"],
28927
+ environment: { SERVICES: "s3,sqs,dynamodb" },
28928
+ healthcheck: {
28929
+ url: "http://localhost:4566/_localstack/health",
28930
+ interval_ms: 500,
28931
+ timeout_ms: 1e4
28932
+ }
28933
+ }
28934
+ };
28935
+ const wiremockTemplate = {
28936
+ name: "wiremock",
28937
+ description: "WireMock — HTTP API mock and stub server",
28938
+ definition: {
28939
+ name: "wiremock",
28940
+ image: "wiremock/wiremock:latest",
28941
+ ports: ["8080:8080"],
28942
+ environment: {},
28943
+ healthcheck: {
28944
+ url: "http://localhost:8080/__admin/health",
28945
+ interval_ms: 500,
28946
+ timeout_ms: 1e4
28947
+ }
28948
+ }
28949
+ };
28950
+ /**
28951
+ * Map of all built-in twin templates, keyed by template name.
28952
+ */
28953
+ const TWIN_TEMPLATES = new Map([[localstackTemplate.name, localstackTemplate], [wiremockTemplate.name, wiremockTemplate]]);
28954
+ /**
28955
+ * Returns all available twin template entries.
28956
+ */
28957
+ function listTwinTemplates() {
28958
+ return Array.from(TWIN_TEMPLATES.values());
28959
+ }
28960
+ /**
28961
+ * Returns the twin template entry for the given name, or `undefined` if not found.
28962
+ */
28963
+ function getTwinTemplate(name) {
28964
+ return TWIN_TEMPLATES.get(name);
28965
+ }
28966
+
28967
+ //#endregion
28968
+ //#region packages/factory/dist/twins/run-state.js
28969
+ /**
28970
+ * Returns the absolute path to the run-state JSON file for the given project.
28971
+ *
28972
+ * @param projectDir - Absolute path to the project root (usually `process.cwd()`)
28973
+ */
28974
+ function runStatePath(projectDir) {
28975
+ return path.join(projectDir, ".substrate", "twins", ".run-state.json");
28976
+ }
28977
+ /**
28978
+ * Reads and JSON-parses the run-state file.
28979
+ *
28980
+ * @returns The parsed `TwinRunState`, or `null` if the file does not exist.
28981
+ * @throws If any I/O error other than ENOENT occurs, or if the JSON is invalid.
28982
+ */
28983
+ async function readRunState(projectDir) {
28984
+ const filePath = runStatePath(projectDir);
28985
+ try {
28986
+ const content = await readFile$1(filePath, "utf-8");
28987
+ return JSON.parse(content);
28988
+ } catch (err) {
28989
+ if (err.code === "ENOENT") return null;
28990
+ throw err;
28991
+ }
28992
+ }
28993
+ /**
28994
+ * Writes the given run state to disk, creating parent directories as needed.
28995
+ *
28996
+ * @param projectDir - Absolute path to the project root
28997
+ * @param state - The `TwinRunState` to persist
28998
+ */
28999
+ async function writeRunState(projectDir, state) {
29000
+ const filePath = runStatePath(projectDir);
29001
+ await mkdir$1(path.dirname(filePath), { recursive: true });
29002
+ await writeFile$1(filePath, JSON.stringify(state, null, 2), "utf-8");
29003
+ }
29004
+ /**
29005
+ * Deletes the run-state file. No-op if the file does not exist.
29006
+ *
29007
+ * @param projectDir - Absolute path to the project root
29008
+ */
29009
+ async function clearRunState(projectDir) {
29010
+ const filePath = runStatePath(projectDir);
29011
+ try {
29012
+ await unlink(filePath);
29013
+ } catch (err) {
29014
+ if (err.code !== "ENOENT") throw err;
29015
+ }
29016
+ }
29017
+
29018
+ //#endregion
29019
+ //#region packages/factory/dist/twins/persistence.js
29020
+ /**
29021
+ * Retrieve all twin run summaries for a given run_id, with health failure counts.
29022
+ *
29023
+ * Uses two portable queries and merges results client-side.
29024
+ *
29025
+ * @returns Array of TwinRunSummary — empty array if no twins found for this run.
29026
+ */
29027
+ async function getTwinRunsForRun(adapter, runId) {
29028
+ const rows = await adapter.query("SELECT * FROM twin_runs WHERE run_id = ?", [runId]);
29029
+ const failureCounts = await adapter.query("SELECT twin_name, COUNT(*) as cnt FROM twin_health_failures WHERE run_id = ? GROUP BY twin_name", [runId]);
29030
+ const failureMap = new Map(failureCounts.map((r) => [r.twin_name, r.cnt]));
29031
+ return rows.map((row) => ({
29032
+ ...row,
29033
+ ports: row.ports_json ? JSON.parse(row.ports_json) : [],
29034
+ health_failure_count: failureMap.get(row.twin_name) ?? 0
29035
+ }));
29036
+ }
29037
+
28491
29038
  //#endregion
28492
29039
  //#region packages/factory/dist/config.js
28493
29040
  /**
@@ -28611,6 +29158,27 @@ async function factorySchema(adapter) {
28611
29158
  )
28612
29159
  `);
28613
29160
  await adapter.exec("CREATE INDEX IF NOT EXISTS idx_scenario_results_run ON scenario_results(run_id)");
29161
+ await adapter.exec(`
29162
+ CREATE TABLE IF NOT EXISTS twin_runs (
29163
+ id VARCHAR(255) PRIMARY KEY,
29164
+ run_id VARCHAR(255),
29165
+ twin_name TEXT NOT NULL,
29166
+ started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
29167
+ stopped_at DATETIME,
29168
+ status VARCHAR(32) NOT NULL DEFAULT 'running',
29169
+ ports_json TEXT
29170
+ )
29171
+ `);
29172
+ await adapter.exec(`
29173
+ CREATE TABLE IF NOT EXISTS twin_health_failures (
29174
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
29175
+ twin_name TEXT NOT NULL,
29176
+ run_id VARCHAR(255),
29177
+ checked_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
29178
+ error_message TEXT NOT NULL
29179
+ )
29180
+ `);
29181
+ await adapter.exec("CREATE INDEX IF NOT EXISTS idx_twin_health_failures_twin ON twin_health_failures(twin_name)");
28614
29182
  }
28615
29183
 
28616
29184
  //#endregion
@@ -28770,6 +29338,160 @@ function registerFactoryCommand(program) {
28770
29338
  else process.stdout.write(`✗ ${passedCount}/${TOTAL_RULE_COUNT} rules passed, ${errors.length} ${errLabel}, ${warnings.length} ${warnLabel}\n`);
28771
29339
  if (errors.length > 0) process.exit(1);
28772
29340
  });
29341
+ const twinsCmd = factoryCmd.command("twins").description("Digital twin template management");
29342
+ twinsCmd.command("templates").description("List available built-in twin templates").action(() => {
29343
+ const templates = listTwinTemplates();
29344
+ for (const t of templates) process.stdout.write(` ${t.name.padEnd(16)} ${t.description}\n`);
29345
+ });
29346
+ twinsCmd.command("init").description("Initialize a twin definition file from a built-in template").requiredOption("--template <name>", "Template name (e.g. localstack, wiremock)").option("--force", "Overwrite existing file if it already exists").action(async (opts) => {
29347
+ try {
29348
+ const entry = getTwinTemplate(opts.template);
29349
+ if (!entry) {
29350
+ const available = listTwinTemplates().map((t) => t.name).join(", ");
29351
+ process.stderr.write(`Error: Unknown template '${opts.template}'. Available: ${available}\n`);
29352
+ process.exit(1);
29353
+ return;
29354
+ }
29355
+ const targetPath = path.join(process.cwd(), ".substrate", "twins", `${opts.template}.yaml`);
29356
+ if (!opts.force) try {
29357
+ await access$1(targetPath);
29358
+ process.stderr.write(`Error: File already exists: ${targetPath} — use --force to overwrite\n`);
29359
+ process.exit(1);
29360
+ return;
29361
+ } catch {}
29362
+ await mkdir$1(path.dirname(targetPath), { recursive: true });
29363
+ const yamlContent = yaml.dump(entry.definition);
29364
+ await writeFile$1(targetPath, yamlContent, "utf-8");
29365
+ process.stdout.write(`Created ${targetPath}\n`);
29366
+ } catch (err) {
29367
+ const msg = err instanceof Error ? err.message : String(err);
29368
+ process.stderr.write(`Error: ${msg}\n`);
29369
+ process.exit(1);
29370
+ }
29371
+ });
29372
+ twinsCmd.command("start").description("Start all discovered twin definitions via Docker Compose").action(async () => {
29373
+ try {
29374
+ const projectDir = process.cwd();
29375
+ const twinsDir = path.join(projectDir, ".substrate", "twins");
29376
+ const registry = createTwinRegistry();
29377
+ try {
29378
+ await registry.discover(twinsDir);
29379
+ } catch (err) {
29380
+ const msg = err instanceof Error ? err.message : String(err);
29381
+ process.stderr.write(`Error: ${msg}\n`);
29382
+ process.exit(1);
29383
+ return;
29384
+ }
29385
+ const twins = registry.list();
29386
+ if (twins.length === 0) {
29387
+ process.stderr.write("No twin definitions found in .substrate/twins/\n");
29388
+ process.exit(1);
29389
+ return;
29390
+ }
29391
+ const eventBus = new TypedEventBusImpl();
29392
+ eventBus.on("twin:started", (e) => {
29393
+ process.stdout.write(` Started: ${e.twinName}\n`);
29394
+ });
29395
+ const manager = createTwinManager(eventBus);
29396
+ try {
29397
+ await manager.start(twins);
29398
+ } catch (err) {
29399
+ const msg = err instanceof Error ? err.message : String(err);
29400
+ process.stderr.write(`Error: ${msg}\n`);
29401
+ process.exit(1);
29402
+ return;
29403
+ }
29404
+ const composeDir = manager.getComposeDir();
29405
+ await writeRunState(projectDir, {
29406
+ composeDir,
29407
+ twinNames: twins.map((t) => t.name),
29408
+ startedAt: new Date().toISOString()
29409
+ });
29410
+ process.stdout.write("\nAll twins started successfully.\n");
29411
+ } catch (err) {
29412
+ const msg = err instanceof Error ? err.message : String(err);
29413
+ process.stderr.write(`Error: ${msg}\n`);
29414
+ process.exit(1);
29415
+ }
29416
+ });
29417
+ twinsCmd.command("stop").description("Stop all running twins").action(async () => {
29418
+ try {
29419
+ const projectDir = process.cwd();
29420
+ const state = await readRunState(projectDir);
29421
+ if (!state) {
29422
+ process.stderr.write("No twins are currently running\n");
29423
+ process.exit(1);
29424
+ return;
29425
+ }
29426
+ try {
29427
+ execSync("docker compose down --remove-orphans", {
29428
+ cwd: state.composeDir,
29429
+ stdio: "pipe"
29430
+ });
29431
+ } catch {}
29432
+ rmSync(state.composeDir, {
29433
+ recursive: true,
29434
+ force: true
29435
+ });
29436
+ await clearRunState(projectDir);
29437
+ process.stdout.write(`Stopped twins: ${state.twinNames.join(", ")}\n`);
29438
+ } catch (err) {
29439
+ const msg = err instanceof Error ? err.message : String(err);
29440
+ process.stderr.write(`Error: ${msg}\n`);
29441
+ process.exit(1);
29442
+ }
29443
+ });
29444
+ twinsCmd.command("status").description("Show status of all discovered twins").action(async () => {
29445
+ try {
29446
+ const projectDir = process.cwd();
29447
+ const state = await readRunState(projectDir);
29448
+ const runningNames = new Set(state?.twinNames ?? []);
29449
+ const registry = createTwinRegistry();
29450
+ let twins = [];
29451
+ try {
29452
+ await registry.discover(path.join(projectDir, ".substrate", "twins"));
29453
+ twins = registry.list();
29454
+ } catch {}
29455
+ if (twins.length === 0) {
29456
+ process.stdout.write("No twin definitions found in .substrate/twins/\n");
29457
+ return;
29458
+ }
29459
+ for (const twin of twins) {
29460
+ const status = runningNames.has(twin.name) ? "running" : "stopped";
29461
+ const portsStr = twin.ports.length > 0 ? twin.ports.map((p) => `${p.host}:${p.container}`).join(", ") : "—";
29462
+ process.stdout.write(` ${twin.name.padEnd(20)} ${status.padEnd(10)} ${portsStr}\n`);
29463
+ }
29464
+ } catch (err) {
29465
+ const msg = err instanceof Error ? err.message : String(err);
29466
+ process.stderr.write(`Error: ${msg}\n`);
29467
+ process.exit(1);
29468
+ }
29469
+ });
29470
+ twinsCmd.command("list").description("List all discovered twin definitions").action(async () => {
29471
+ try {
29472
+ const projectDir = process.cwd();
29473
+ const registry = createTwinRegistry();
29474
+ let twins = [];
29475
+ try {
29476
+ await registry.discover(path.join(projectDir, ".substrate", "twins"));
29477
+ twins = registry.list();
29478
+ } catch {}
29479
+ if (twins.length === 0) {
29480
+ process.stdout.write("No twin definitions found in .substrate/twins/\n");
29481
+ return;
29482
+ }
29483
+ process.stdout.write(" NAME IMAGE PORTS HEALTHCHECK\n");
29484
+ for (const twin of twins) {
29485
+ const ports = twin.ports.length > 0 ? twin.ports.map((p) => `${p.host}:${p.container}`).join(", ") : "—";
29486
+ const healthcheck = twin.healthcheck?.url ?? "—";
29487
+ process.stdout.write(` ${twin.name.padEnd(20)} ${twin.image.padEnd(38)} ${ports.padEnd(16)} ${healthcheck}\n`);
29488
+ }
29489
+ } catch (err) {
29490
+ const msg = err instanceof Error ? err.message : String(err);
29491
+ process.stderr.write(`Error: ${msg}\n`);
29492
+ process.exit(1);
29493
+ }
29494
+ });
28773
29495
  }
28774
29496
 
28775
29497
  //#endregion
@@ -28796,7 +29518,7 @@ function registerFactoryCommand(program) {
28796
29518
  * @returns A HandlerRegistry with all four SDLC handlers registered.
28797
29519
  */
28798
29520
  function buildSdlcHandlerRegistry(deps) {
28799
- const registry = new HandlerRegistry();
29521
+ const registry = createDefaultRegistry();
28800
29522
  registry.register("sdlc.phase", createSdlcPhaseHandler(deps.phaseHandlerDeps));
28801
29523
  registry.register("sdlc.create-story", createSdlcCreateStoryHandler(deps.createStoryOptions));
28802
29524
  registry.register("sdlc.dev-story", createSdlcDevStoryHandler(deps.devStoryOptions));
@@ -30244,5 +30966,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
30244
30966
  }
30245
30967
 
30246
30968
  //#endregion
30247
- export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
30248
- //# sourceMappingURL=run-CRP9jiCz.js.map
30969
+ export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
30970
+ //# sourceMappingURL=run-C7m0gTBv.js.map
@@ -2,7 +2,7 @@ import "./health-DswaC1q5.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-CLvAwmT7.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-CRP9jiCz.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-C7m0gTBv.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.15.1",
3
+ "version": "0.16.1",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "scripts": {
50
50
  "agent-memory:bootstrap": "node scripts/bootstrap-agent-memory.mjs",
51
51
  "build": "tsc --build packages/core packages/sdlc packages/factory && tsdown",
52
- "postbuild": "cp -r src/cli/templates dist/cli/templates && cp src/modules/state/schema.sql dist/schema.sql",
52
+ "postbuild": "cp -r src/cli/templates dist/cli/templates && cp src/modules/state/schema.sql dist/schema.sql && mkdir -p dist/graphs && cp packages/sdlc/graphs/sdlc-pipeline.dot dist/graphs/",
53
53
  "check:circular": "dpdm --no-warning --exit-code circular:1 packages/core/src/index.ts packages/sdlc/src/index.ts packages/factory/src/index.ts",
54
54
  "dev": "tsx watch src/cli/index.ts",
55
55
  "test": "vitest run --coverage",