substrate-ai 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ import { AdapterRegistry } from "./dist-CLvAwmT7.js";
2
+ import "./adapter-registry-DXLMTmfD.js";
3
+
4
+ export { AdapterRegistry };
package/dist/cli/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, detectCycles, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot } from "../health-BR5GD5Ge.js";
2
+ import { FileStateStore, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, createDatabaseAdapter, createStateStore, detectCycles, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot } from "../health-DswaC1q5.js";
3
3
  import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
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-DKG5lyUw.js";
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, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-zWACyS3w.js";
8
- import "../errors-CQKOiaYV.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, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CUMPhuVq.js";
8
+ import "../errors-D1LU8CZ9.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
11
11
  import "../version-manager-impl-BmOWu8ml.js";
12
- import { registerUpgradeCommand } from "../upgrade-CBE6JaxX.js";
12
+ import { registerUpgradeCommand } from "../upgrade-DT0I_-1E.js";
13
13
  import { Command } from "commander";
14
14
  import { fileURLToPath } from "url";
15
15
  import { dirname, join, resolve } from "path";
@@ -3025,7 +3025,7 @@ async function runStatusAction(options) {
3025
3025
  if (run === void 0) run = await getLatestRun(adapter);
3026
3026
  }
3027
3027
  if (run === void 0) {
3028
- const { inspectProcessTree } = await import("../health-DXNHn938.js");
3028
+ const { inspectProcessTree } = await import("../health-GEDGgGan.js");
3029
3029
  const substrateDirPath = join(projectRoot, ".substrate");
3030
3030
  const processInfo = inspectProcessTree({
3031
3031
  projectRoot,
@@ -3910,7 +3910,7 @@ function defaultSupervisorDeps() {
3910
3910
  if (cached === null) {
3911
3911
  const { AdapterRegistry: AR } = await import(
3912
3912
  /* @vite-ignore */
3913
- "../adapter-registry-B19pDsb2.js"
3913
+ "../adapter-registry-DbLuI3IA.js"
3914
3914
  );
3915
3915
  cached = new AR();
3916
3916
  await cached.discoverAndRegister();
@@ -4104,6 +4104,10 @@ async function handleStallRecovery(health, state, config, deps, io) {
4104
4104
  const orchestratorIdle = health.process.child_pids.length === 0 && health.stories.active > 0;
4105
4105
  const effectiveThreshold = inReviewPhase && !orchestratorIdle ? stallThreshold * 2 : stallThreshold;
4106
4106
  if (health.staleness_seconds < effectiveThreshold) return null;
4107
+ if (state.runId !== void 0 && health.run_id !== null && health.run_id !== state.runId) {
4108
+ log("Supervisor skipping kill — active pipeline belongs to a different session");
4109
+ return null;
4110
+ }
4107
4111
  const directPids = [...health.process.orchestrator_pid !== null ? [health.process.orchestrator_pid] : [], ...health.process.child_pids];
4108
4112
  const descendantPids = getAllDescendants(directPids);
4109
4113
  const directPidSet = new Set(directPids);
@@ -4341,11 +4345,11 @@ async function runSupervisorAction(options, deps = {}) {
4341
4345
  try {
4342
4346
  const { createExperimenter } = await import(
4343
4347
  /* @vite-ignore */
4344
- "../experimenter-DAIK4Ljm.js"
4348
+ "../experimenter-D0k2wT3I.js"
4345
4349
  );
4346
4350
  const { getLatestRun: getLatest } = await import(
4347
4351
  /* @vite-ignore */
4348
- "../decisions-0VsbM-3Z.js"
4352
+ "../decisions-CGNEausW.js"
4349
4353
  );
4350
4354
  const expAdapter = createDatabaseAdapter({
4351
4355
  backend: "auto",
@@ -4355,7 +4359,7 @@ async function runSupervisorAction(options, deps = {}) {
4355
4359
  await initSchema(expAdapter);
4356
4360
  const { runRunAction: runPipeline } = await import(
4357
4361
  /* @vite-ignore */
4358
- "../run-C3HrjTtt.js"
4362
+ "../run-CZo7hpsh.js"
4359
4363
  );
4360
4364
  const runStoryFn = async (opts) => {
4361
4365
  const exitCode = await runPipeline({
@@ -4871,7 +4875,7 @@ async function runMetricsAction(options) {
4871
4875
  const routingConfigPath = join(dbDir, "routing.yml");
4872
4876
  let routingConfig = null;
4873
4877
  if (existsSync$1(routingConfigPath)) try {
4874
- const { loadModelRoutingConfig } = await import("../routing-h9IhuF0i.js");
4878
+ const { loadModelRoutingConfig } = await import("../routing-B1aoIz7L.js");
4875
4879
  routingConfig = loadModelRoutingConfig(routingConfigPath);
4876
4880
  } catch {}
4877
4881
  if (routingConfig === null) routingConfig = {
@@ -8621,8 +8625,8 @@ async function createProgram() {
8621
8625
  /** Fire-and-forget startup version check (story 8.3, AC3/AC5) */
8622
8626
  function checkForUpdatesInBackground(currentVersion) {
8623
8627
  if (process.env.SUBSTRATE_NO_UPDATE_CHECK === "1") return;
8624
- import("../upgrade-D0JTv4uQ.js").then(async () => {
8625
- const { createVersionManager } = await import("../version-manager-impl-CxCB4SPB.js");
8628
+ import("../upgrade-DPdh5w4p.js").then(async () => {
8629
+ const { createVersionManager } = await import("../version-manager-impl-BHnUB2tl.js");
8626
8630
  const vm = createVersionManager();
8627
8631
  const result = await vm.checkForUpdates();
8628
8632
  if (result.updateAvailable) {
@@ -1,4 +1,4 @@
1
- import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-DKG5lyUw.js";
1
+ import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, listRequirements, registerArtifact, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./dist-CLvAwmT7.js";
2
2
  import "./decisions-C0pz9Clx.js";
3
3
 
4
4
  export { getLatestRun };
@@ -1871,11 +1871,19 @@ var InMemoryDatabaseAdapter = class {
1871
1871
 
1872
1872
  //#endregion
1873
1873
  //#region packages/core/dist/persistence/adapter.js
1874
- function isDoltAvailable(basePath) {
1874
+ function isDoltBinaryAvailable() {
1875
1875
  const result = spawnSync("dolt", ["version"], { stdio: "ignore" });
1876
- if (result.error != null || result.status !== 0) return false;
1876
+ return result.error == null && result.status === 0;
1877
+ }
1878
+ function isDoltAvailable(basePath) {
1877
1879
  const stateDoltDir = join$1(basePath, ".substrate", "state", ".dolt");
1878
- return existsSync(stateDoltDir);
1880
+ if (!existsSync(stateDoltDir)) return false;
1881
+ if (isDoltBinaryAvailable()) return true;
1882
+ console.warn("[persistence:adapter] Dolt directory found but dolt binary unavailable — retrying once...");
1883
+ spawnSync("sleep", ["1"], { stdio: "ignore" });
1884
+ if (isDoltBinaryAvailable()) return true;
1885
+ console.warn("[persistence:adapter] Dolt still unavailable after retry — falling back to InMemoryDatabaseAdapter. Telemetry and cost data will NOT persist.");
1886
+ return false;
1879
1887
  }
1880
1888
  function createDatabaseAdapter(config = { backend: "auto" }, doltClientFactory) {
1881
1889
  const backend = config.backend ?? "auto";
@@ -10228,4 +10236,4 @@ async function callLLM(params) {
10228
10236
 
10229
10237
  //#endregion
10230
10238
  export { ADVISORY_NOTES, AdapterRegistry, AdtError, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, Categorizer, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, ConsumerAnalyzer, CostTrackerConfigSchema, DEFAULT_CONFIG, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, DoltNotInstalled, DoltQueryError, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, GeminiCLIAdapter, GlobalSettingsSchema, IngestionServer, LogTurnAnalyzer, ModelRoutingConfigSchema, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProviderPolicySchema, ProvidersSchema, Recommender, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TASK_TYPE_PHASE_MAP, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryConfigSchema, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, VersionManagerImpl, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, callLLM, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDatabaseAdapter as createDatabaseAdapter$1, createDecision, createDoltClient, createExperimenter, createPipelineRun, createRequirement, createVersionManager, detectInterfaceChanges, determineVerdict, getActiveDecisions, getAllCostEntriesFiltered, getArtifactByTypeForRun, getArtifactsByRun, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getModelTier, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRequirements, listRunMetrics, loadModelRoutingConfig, loadParentRunDecisions, registerArtifact, resolvePromptFile, supersedeDecision, tagRunAsBaseline, updateDecision, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics };
10231
- //# sourceMappingURL=dist-DKG5lyUw.js.map
10239
+ //# sourceMappingURL=dist-CLvAwmT7.js.map
@@ -1,4 +1,4 @@
1
- import { AdtError } from "./dist-DKG5lyUw.js";
1
+ import { AdtError } from "./dist-CLvAwmT7.js";
2
2
 
3
3
  //#region src/core/errors.ts
4
4
  /** Error thrown when task configuration is invalid */
@@ -71,4 +71,4 @@ var TaskGraphIncompatibleFormatError = class extends AdtError {
71
71
 
72
72
  //#endregion
73
73
  export { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError };
74
- //# sourceMappingURL=errors-CQKOiaYV.js.map
74
+ //# sourceMappingURL=errors-D1LU8CZ9.js.map
@@ -1,3 +1,3 @@
1
- import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-DKG5lyUw.js";
1
+ import { buildAuditLogEntry, buildBranchName, buildModificationDirective, buildPRBody, buildWorktreePath, createExperimenter, determineVerdict, resolvePromptFile } from "./dist-CLvAwmT7.js";
2
2
 
3
3
  export { createExperimenter };
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from "./logger-KeHncl-f.js";
2
- import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-DKG5lyUw.js";
2
+ import { DoltClient, DoltQueryError, createDatabaseAdapter$1 as createDatabaseAdapter, getLatestRun, getPipelineRunById, initSchema } from "./dist-CLvAwmT7.js";
3
3
  import { createRequire } from "module";
4
4
  import { dirname, join } from "path";
5
5
  import { existsSync, readFileSync } from "node:fs";
@@ -1930,4 +1930,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
1930
1930
 
1931
1931
  //#endregion
1932
1932
  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, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createStateStore, detectCycles, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runHealthAction, validateStoryKey };
1933
- //# sourceMappingURL=health-BR5GD5Ge.js.map
1933
+ //# sourceMappingURL=health-DswaC1q5.js.map
@@ -1,6 +1,6 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-BR5GD5Ge.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-DswaC1q5.js";
2
2
  import "./logger-KeHncl-f.js";
3
- import "./dist-DKG5lyUw.js";
3
+ import "./dist-CLvAwmT7.js";
4
4
  import "./decisions-C0pz9Clx.js";
5
5
 
6
6
  export { inspectProcessTree };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { childLogger, createLogger, logger } from "./logger-KeHncl-f.js";
2
2
  import { assertDefined, createEventBus, createTuiApp, deepClone, formatDuration, generateId, isPlainObject, isTuiCapable, printNonTtyWarning, sleep, withRetry } from "./helpers-CElYrONe.js";
3
- import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-DKG5lyUw.js";
3
+ import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-CLvAwmT7.js";
4
4
  import "./adapter-registry-DXLMTmfD.js";
5
- import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-CQKOiaYV.js";
5
+ import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-D1LU8CZ9.js";
6
6
 
7
7
  //#region src/core/di.ts
8
8
  /**
@@ -1,4 +1,4 @@
1
- import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-DKG5lyUw.js";
1
+ import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./dist-CLvAwmT7.js";
2
2
  import "./routing-CcBOCuC9.js";
3
3
 
4
4
  export { loadModelRoutingConfig };
@@ -1,7 +1,7 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, resolveMainRepoRoot, validateStoryKey } from "./health-BR5GD5Ge.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, resolveMainRepoRoot, validateStoryKey } from "./health-DswaC1q5.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
- import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getPipelineRunById, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-DKG5lyUw.js";
4
+ import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, createConfigSystem, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getPipelineRunById, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-CLvAwmT7.js";
5
5
  import { basename, dirname, extname, join } from "path";
6
6
  import { access, readFile, readdir, stat } from "fs/promises";
7
7
  import { EventEmitter } from "node:events";
@@ -10893,11 +10893,23 @@ function createImplementationOrchestrator(deps) {
10893
10893
  checkpointHandled = true;
10894
10894
  }
10895
10895
  if (!checkpointHandled) if (devResult.result === "success") devStoryWasSuccess = true;
10896
- else logger$21.warn({
10897
- storyKey,
10898
- error: devResult.error,
10899
- filesModified: devFilesModified.length
10900
- }, "Dev-story reported failure, proceeding to code review");
10896
+ else {
10897
+ logger$21.warn({
10898
+ storyKey,
10899
+ error: devResult.error,
10900
+ filesModified: devFilesModified.length
10901
+ }, "Dev-story reported failure, proceeding to code review");
10902
+ if (!devResult.error?.startsWith("dispatch_timeout")) {
10903
+ logger$21.warn({
10904
+ storyKey,
10905
+ error: devResult.error
10906
+ }, "Agent process failure (non-timeout) — story will proceed to code review with partial work");
10907
+ eventBus.emit("orchestrator:story-warn", {
10908
+ storyKey,
10909
+ msg: "agent process failure (non-timeout)"
10910
+ });
10911
+ }
10912
+ }
10901
10913
  }
10902
10914
  } catch (err) {
10903
10915
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -10974,9 +10986,12 @@ function createImplementationOrchestrator(deps) {
10974
10986
  if (buildVerifyResult.status === "passed") {
10975
10987
  const resolvedRootForTsc = projectRoot ?? process.cwd();
10976
10988
  const tscBin = join$1(resolvedRootForTsc, "node_modules", ".bin", "tsc");
10977
- const hasTsc = existsSync(tscBin) && existsSync(join$1(resolvedRootForTsc, "tsconfig.json"));
10989
+ const typecheckConfig = join$1(resolvedRootForTsc, "tsconfig.typecheck.json");
10990
+ const defaultConfig = join$1(resolvedRootForTsc, "tsconfig.json");
10991
+ const tscConfigFlag = existsSync(typecheckConfig) ? ` -p ${typecheckConfig}` : "";
10992
+ const hasTsc = existsSync(tscBin) && (existsSync(typecheckConfig) || existsSync(defaultConfig));
10978
10993
  if (hasTsc) try {
10979
- execSync(`"${tscBin}" --noEmit`, {
10994
+ execSync(`"${tscBin}" --noEmit${tscConfigFlag}`, {
10980
10995
  cwd: resolvedRootForTsc,
10981
10996
  timeout: 12e4,
10982
10997
  encoding: "utf-8",
@@ -16735,15 +16750,34 @@ function createSdlcDevStoryHandler(options) {
16735
16750
  };
16736
16751
  try {
16737
16752
  const workflowResult = await options.runDevStory(options.deps, devStoryParams);
16738
- if (workflowResult.result === "success") outcome = {
16739
- status: "SUCCESS",
16740
- contextUpdates: {
16741
- filesModified: workflowResult.files_modified,
16742
- acMet: workflowResult.ac_met,
16743
- devStoryFilesModified: workflowResult.files_modified
16753
+ if (workflowResult.result === "success") {
16754
+ if (options.buildVerifier) {
16755
+ const projectRoot = context.getString("projectRoot", "");
16756
+ if (projectRoot) {
16757
+ const buildResult = options.buildVerifier(projectRoot);
16758
+ if (buildResult.status === "failed" || buildResult.status === "timeout") {
16759
+ outcome = {
16760
+ status: "FAILURE",
16761
+ failureReason: `build verification failed after dev-story: ${buildResult.output?.slice(0, 500) ?? "no output"}`,
16762
+ contextUpdates: {
16763
+ filesModified: workflowResult.files_modified,
16764
+ devStoryFilesModified: workflowResult.files_modified,
16765
+ devStoryAcFailures: ["build-verification"]
16766
+ }
16767
+ };
16768
+ return outcome;
16769
+ }
16770
+ }
16744
16771
  }
16745
- };
16746
- else {
16772
+ outcome = {
16773
+ status: "SUCCESS",
16774
+ contextUpdates: {
16775
+ filesModified: workflowResult.files_modified,
16776
+ acMet: workflowResult.ac_met,
16777
+ devStoryFilesModified: workflowResult.files_modified
16778
+ }
16779
+ };
16780
+ } else {
16747
16781
  const failureReason = workflowResult.error ?? (workflowResult.ac_failures.length > 0 ? `dev-story failed ACs: ${workflowResult.ac_failures.join(", ")}` : "dev-story workflow failed");
16748
16782
  outcome = {
16749
16783
  status: "FAILURE",
@@ -16973,12 +17007,12 @@ function createSdlcEventBridge(opts) {
16973
17007
  });
16974
17008
  };
16975
17009
  const onGoalGateUnsatisfied = (data) => {
16976
- const { nodeId } = data;
16977
- if (nodeId === "dev_story") sdlcBus.emit("orchestrator:story-escalated", {
17010
+ const evt = data;
17011
+ if (evt.nodeId === "dev_story") sdlcBus.emit("orchestrator:story-escalated", {
16978
17012
  storyKey,
16979
- lastVerdict: "NEEDS_MAJOR_REWORK",
16980
- reviewCycles: devStoryRetries,
16981
- issues: []
17013
+ lastVerdict: evt.lastVerdict ?? "NEEDS_MAJOR_REWORK",
17014
+ reviewCycles: evt.reviewCycles ?? devStoryRetries,
17015
+ issues: evt.issues ?? []
16982
17016
  });
16983
17017
  };
16984
17018
  graphEvents.on("graph:node-started", onNodeStarted);
@@ -17041,6 +17075,10 @@ function createGraphOrchestrator(config) {
17041
17075
  sdlcBus: config.eventBus,
17042
17076
  graphEvents: factoryBus
17043
17077
  }) : void 0;
17078
+ let escalated = false;
17079
+ factoryBus.on("graph:goal-gate-unsatisfied", () => {
17080
+ escalated = true;
17081
+ });
17044
17082
  let result;
17045
17083
  try {
17046
17084
  result = await config.executor.run(config.graph, {
@@ -17053,7 +17091,7 @@ function createGraphOrchestrator(config) {
17053
17091
  } catch (err) {
17054
17092
  const errMsg = err instanceof Error ? err.message : String(err);
17055
17093
  summary.stories[storyKey] = {
17056
- outcome: "FAILED",
17094
+ outcome: escalated ? "ESCALATED" : "FAILED",
17057
17095
  error: errMsg
17058
17096
  };
17059
17097
  summary.f++;
@@ -17064,6 +17102,9 @@ function createGraphOrchestrator(config) {
17064
17102
  if (result.status === "SUCCESS") {
17065
17103
  summary.stories[storyKey] = { outcome: "SUCCESS" };
17066
17104
  summary.s++;
17105
+ } else if (escalated) {
17106
+ summary.stories[storyKey] = { outcome: "ESCALATED" };
17107
+ summary.f++;
17067
17108
  } else {
17068
17109
  summary.stories[storyKey] = { outcome: "FAILED" };
17069
17110
  summary.f++;
@@ -22121,6 +22162,10 @@ var RunStateManager = class {
22121
22162
  */
22122
22163
  function createConvergenceController() {
22123
22164
  const outcomes = new Map();
22165
+ /** Returns true only when id is non-empty AND exists in graph.nodes. */
22166
+ function isValidTarget(id, graph) {
22167
+ return id !== "" && graph.nodes.has(id);
22168
+ }
22124
22169
  return {
22125
22170
  recordOutcome(nodeId, status) {
22126
22171
  outcomes.set(nodeId, status);
@@ -22136,9 +22181,386 @@ function createConvergenceController() {
22136
22181
  satisfied: failingNodes.length === 0,
22137
22182
  failingNodes
22138
22183
  };
22184
+ },
22185
+ checkGoalGates(graph, runId, eventBus) {
22186
+ const failedGates = [];
22187
+ for (const [id, node] of graph.nodes) {
22188
+ if (!node.goalGate) continue;
22189
+ const status = outcomes.get(id);
22190
+ const satisfied = status === "SUCCESS" || status === "PARTIAL_SUCCESS";
22191
+ eventBus?.emit("graph:goal-gate-checked", {
22192
+ runId,
22193
+ nodeId: id,
22194
+ satisfied
22195
+ });
22196
+ if (!satisfied) failedGates.push(id);
22197
+ }
22198
+ return {
22199
+ satisfied: failedGates.length === 0,
22200
+ failedGates
22201
+ };
22202
+ },
22203
+ resolveRetryTarget(failedNode, graph) {
22204
+ const candidates = [
22205
+ failedNode.retryTarget,
22206
+ failedNode.fallbackRetryTarget,
22207
+ graph.retryTarget,
22208
+ graph.fallbackRetryTarget
22209
+ ];
22210
+ for (const candidate of candidates) if (isValidTarget(candidate, graph)) return candidate;
22211
+ return null;
22212
+ }
22213
+ };
22214
+ }
22215
+
22216
+ //#endregion
22217
+ //#region packages/factory/dist/convergence/budget.js
22218
+ const DEFAULT_BACKOFF = {
22219
+ initialDelay: 200,
22220
+ factor: 2,
22221
+ maxDelay: 6e4,
22222
+ jitterFactor: .5
22223
+ };
22224
+ /**
22225
+ * Compute the delay (in milliseconds) before the next retry attempt.
22226
+ *
22227
+ * Formula (before jitter):
22228
+ * `baseDelay = initialDelay * factor^attemptIndex`
22229
+ * `cappedDelay = Math.min(baseDelay, maxDelay)`
22230
+ *
22231
+ * Jitter:
22232
+ * `jitter = (Math.random() * 2 - 1) * jitterFactor * cappedDelay`
22233
+ * `delay = Math.max(0, Math.round(cappedDelay + jitter))`
22234
+ *
22235
+ * Passing `{ jitterFactor: 0 }` disables jitter for deterministic tests.
22236
+ *
22237
+ * @param attemptIndex - Zero-based index of the current attempt (0 = first retry).
22238
+ * @param options - Optional overrides for the backoff parameters.
22239
+ */
22240
+ function computeBackoffDelay(attemptIndex, options) {
22241
+ const { initialDelay, factor, maxDelay, jitterFactor } = {
22242
+ ...DEFAULT_BACKOFF,
22243
+ ...options
22244
+ };
22245
+ const baseDelay = initialDelay * Math.pow(factor, attemptIndex);
22246
+ const cappedDelay = Math.min(baseDelay, maxDelay);
22247
+ const jitter = (Math.random() * 2 - 1) * jitterFactor * cappedDelay;
22248
+ return Math.max(0, Math.round(cappedDelay + jitter));
22249
+ }
22250
+ /**
22251
+ * Determine whether a pipeline is permitted to dispatch the next node.
22252
+ *
22253
+ * **Unlimited mode:** When `cap === 0` the function returns `{ allowed: true }`
22254
+ * immediately, regardless of `accumulatedCost`. This matches the
22255
+ * `FactoryConfigSchema` default of `budget_cap_usd: 0` which means "no limit".
22256
+ *
22257
+ * **Strict greater-than boundary:** Enforcement triggers only when
22258
+ * `accumulatedCost > cap`. A cost that exactly equals the cap is still allowed,
22259
+ * consistent with the PRD wording "halts *before* dispatching further nodes
22260
+ * when accumulated cost **exceeds** the cap."
22261
+ *
22262
+ * @param accumulatedCost - Total cost (USD) spent so far during this pipeline run.
22263
+ * @param cap - Maximum allowed cost (USD). `0` disables enforcement.
22264
+ * @returns `{ allowed: true }` when the pipeline may continue, or
22265
+ * `{ allowed: false, reason: '...' }` when the budget is exhausted.
22266
+ */
22267
+ function checkPipelineBudget(accumulatedCost, cap) {
22268
+ if (cap === 0) return { allowed: true };
22269
+ if (accumulatedCost > cap) return {
22270
+ allowed: false,
22271
+ reason: `pipeline budget exhausted: $${accumulatedCost.toFixed(2)} > $${cap.toFixed(2)}`
22272
+ };
22273
+ return { allowed: true };
22274
+ }
22275
+ /**
22276
+ * Tracks accumulated cost for a single pipeline run and enforces a configurable
22277
+ * spending cap via `checkPipelineBudget`.
22278
+ *
22279
+ * **Lifecycle:** Create one instance per pipeline run. Story 45-8 will call
22280
+ * `addCost()` after each node dispatch completes and `checkBudget()` before
22281
+ * the next dispatch. Call `reset()` between pipeline runs or in tests to clear
22282
+ * accumulated state.
22283
+ */
22284
+ var PipelineBudgetManager = class {
22285
+ totalCost = 0;
22286
+ /**
22287
+ * Add `amount` USD to the running total for this pipeline run.
22288
+ *
22289
+ * @param amount - Cost (USD) for the just-completed node dispatch.
22290
+ */
22291
+ addCost(amount) {
22292
+ this.totalCost += amount;
22293
+ }
22294
+ /**
22295
+ * Return the total cost (USD) accumulated so far during this pipeline run.
22296
+ */
22297
+ getTotalCost() {
22298
+ return this.totalCost;
22299
+ }
22300
+ /**
22301
+ * Reset the accumulated cost to zero.
22302
+ * Useful for test isolation and future pipeline reuse scenarios.
22303
+ */
22304
+ reset() {
22305
+ this.totalCost = 0;
22306
+ }
22307
+ /**
22308
+ * Determine whether the pipeline may dispatch the next node, delegating to
22309
+ * `checkPipelineBudget` with the current accumulated cost.
22310
+ *
22311
+ * @param cap - Maximum allowed cost (USD). `0` disables enforcement.
22312
+ */
22313
+ checkBudget(cap) {
22314
+ return checkPipelineBudget(this.totalCost, cap);
22315
+ }
22316
+ };
22317
+ /**
22318
+ * Determine whether a pipeline session is permitted to dispatch the next node
22319
+ * based on wall-clock elapsed time.
22320
+ *
22321
+ * **Unlimited mode:** When `capMs === 0` the function returns `{ allowed: true }`
22322
+ * immediately, regardless of `elapsedMs`. This matches the `FactoryConfigSchema`
22323
+ * default of `wall_clock_cap_seconds: 0` which means "no limit".
22324
+ *
22325
+ * **Strict greater-than boundary:** Enforcement triggers only when
22326
+ * `elapsedMs > capMs`. An elapsed time that exactly equals the cap is still
22327
+ * allowed, consistent with the PRD wording "halts *before* dispatching further
22328
+ * nodes when elapsed time **exceeds** the cap."
22329
+ *
22330
+ * @param elapsedMs - Milliseconds elapsed since the pipeline session started.
22331
+ * @param capMs - Maximum allowed elapsed time in milliseconds. `0` disables
22332
+ * enforcement (unlimited mode).
22333
+ * @returns `{ allowed: true }` when the session may continue, or
22334
+ * `{ allowed: false, reason: 'wall clock budget exhausted' }` when the
22335
+ * cap has been exceeded.
22336
+ */
22337
+ function checkSessionBudget(elapsedMs, capMs) {
22338
+ if (capMs === 0) return { allowed: true };
22339
+ if (elapsedMs > capMs) return {
22340
+ allowed: false,
22341
+ reason: "wall clock budget exhausted"
22342
+ };
22343
+ return { allowed: true };
22344
+ }
22345
+ /**
22346
+ * Tracks wall-clock elapsed time for a single pipeline session and enforces a
22347
+ * configurable time cap via `checkSessionBudget`.
22348
+ *
22349
+ * **Lifecycle:** Create one instance per pipeline run, constructed at pipeline
22350
+ * launch. Story 45-8 will call `checkBudget()` before each node dispatch as
22351
+ * the highest-priority budget check (before `PipelineBudgetManager`).
22352
+ * Call `reset()` between pipeline runs or in tests for
22353
+ * isolation.
22354
+ *
22355
+ * **Cap 0 means unlimited:** A `capSeconds` value of `0` passed to `checkBudget`
22356
+ * disables all wall-clock enforcement and always returns `{ allowed: true }`.
22357
+ */
22358
+ var SessionBudgetManager = class {
22359
+ startTime;
22360
+ constructor() {
22361
+ this.startTime = Date.now();
22362
+ }
22363
+ /**
22364
+ * Return the number of milliseconds elapsed since this manager was constructed
22365
+ * (or since the last `reset()` call). Always returns a non-negative number.
22366
+ */
22367
+ getElapsedMs() {
22368
+ return Date.now() - this.startTime;
22369
+ }
22370
+ /**
22371
+ * Reset the session start timestamp to the current time. Subsequent calls to
22372
+ * `getElapsedMs()` will measure from this new baseline. Useful for test
22373
+ * isolation and future pipeline reuse scenarios.
22374
+ */
22375
+ reset() {
22376
+ this.startTime = Date.now();
22377
+ }
22378
+ /**
22379
+ * Determine whether the pipeline session may dispatch the next node, delegating
22380
+ * to `checkSessionBudget` with the current elapsed time converted from seconds
22381
+ * to milliseconds.
22382
+ *
22383
+ * @param capSeconds - Maximum allowed elapsed time in **seconds** (as stored in
22384
+ * `FactoryConfig.wall_clock_cap_seconds`). A value of `0`
22385
+ * disables enforcement.
22386
+ */
22387
+ checkBudget(capSeconds) {
22388
+ return checkSessionBudget(this.getElapsedMs(), capSeconds * 1e3);
22389
+ }
22390
+ };
22391
+
22392
+ //#endregion
22393
+ //#region packages/factory/dist/convergence/plateau.js
22394
+ /**
22395
+ * Plateau detection for the convergence loop.
22396
+ * Story 45-6: provides pure plateau detection primitives — no I/O, no side effects.
22397
+ *
22398
+ * Algorithm: Track the last N satisfaction scores (N = `window`, default 3).
22399
+ * If max−min of the window falls strictly below threshold, declare plateau.
22400
+ *
22401
+ * Consumed by:
22402
+ * - Story 45-8 (convergence controller integration)
22403
+ */
22404
+ const DEFAULT_WINDOW = 3;
22405
+ const DEFAULT_THRESHOLD = .05;
22406
+ /**
22407
+ * Create a new PlateauDetector with the given options.
22408
+ *
22409
+ * **Defaults:** `window=3`, `threshold=0.05` — matching `FactoryConfigSchema.plateau_window`
22410
+ * and `FactoryConfigSchema.plateau_threshold`. Story 45-8 will read these values from
22411
+ * `FactoryConfig` and pass them in.
22412
+ *
22413
+ * **Insufficient-data guard:** `isPlateaued()` always returns `false` when fewer than
22414
+ * `window` scores have been recorded. A plateau can only be declared once the window is full.
22415
+ *
22416
+ * @param options - Optional configuration for window size and threshold.
22417
+ */
22418
+ function createPlateauDetector(options) {
22419
+ const window = options?.window ?? DEFAULT_WINDOW;
22420
+ const threshold = options?.threshold ?? DEFAULT_THRESHOLD;
22421
+ let scores = [];
22422
+ return {
22423
+ recordScore(_iteration, score) {
22424
+ scores.push(score);
22425
+ scores = scores.slice(-window);
22426
+ },
22427
+ isPlateaued() {
22428
+ if (scores.length < window) return false;
22429
+ const delta = Math.max(...scores) - Math.min(...scores);
22430
+ return delta < threshold;
22431
+ },
22432
+ getWindow() {
22433
+ return window;
22434
+ },
22435
+ getScores() {
22436
+ return [...scores];
22139
22437
  }
22140
22438
  };
22141
22439
  }
22440
+ /**
22441
+ * Check whether the detector has reached a plateau and, if so, emit the
22442
+ * `convergence:plateau-detected` event on the provided event bus.
22443
+ *
22444
+ * This mirrors the `checkGoalGates()` pattern:
22445
+ * - Pure detection is isolated in `PlateauDetector` (no side effects).
22446
+ * - Event emission is isolated here in this wrapper.
22447
+ * - Callers may omit `eventBus` for pure check behavior (no event is emitted).
22448
+ *
22449
+ * @param detector - A `PlateauDetector` instance.
22450
+ * @param context - Run/node identifiers and an optional event bus.
22451
+ * @returns `{ plateaued: true, scores }` with event emitted when plateaued;
22452
+ * `{ plateaued: false, scores }` with no event emitted otherwise.
22453
+ */
22454
+ function checkPlateauAndEmit(detector, context) {
22455
+ const { runId, nodeId, eventBus } = context;
22456
+ const scores = detector.getScores();
22457
+ if (detector.isPlateaued()) {
22458
+ eventBus?.emit("convergence:plateau-detected", {
22459
+ runId,
22460
+ nodeId,
22461
+ scores,
22462
+ window: detector.getWindow()
22463
+ });
22464
+ return {
22465
+ plateaued: true,
22466
+ scores
22467
+ };
22468
+ }
22469
+ return {
22470
+ plateaued: false,
22471
+ scores
22472
+ };
22473
+ }
22474
+
22475
+ //#endregion
22476
+ //#region packages/factory/dist/convergence/remediation.js
22477
+ /**
22478
+ * Remediation context injection for the convergence loop.
22479
+ * Story 45-7: builds structured remediation context from failure data and
22480
+ * injects it into a retried node's IGraphContext.
22481
+ *
22482
+ * Architecture reference: Section 6.5 — Remediation Context fields
22483
+ *
22484
+ * Pure functions (`formatScenarioDiff`, `deriveFixScope`, `buildRemediationContext`)
22485
+ * have no I/O and no side effects.
22486
+ * Only `injectRemediationContext` mutates state (the IGraphContext).
22487
+ *
22488
+ * Consumed by:
22489
+ * - Story 45-8 (convergence controller integration with executor)
22490
+ * - CodergenBackend handlers (via `getRemediationContext`)
22491
+ */
22492
+ /**
22493
+ * The agreed key under which remediation context is stored in `IGraphContext`.
22494
+ * Namespaced under `convergence.` to avoid collision with user-defined context keys.
22495
+ * Story 45-8 writes this key; CodergenBackend handlers read it via `getRemediationContext()`.
22496
+ */
22497
+ const REMEDIATION_CONTEXT_KEY = "convergence.remediation";
22498
+ /**
22499
+ * Formats a human-readable diff of failed scenarios from a ScenarioRunResult.
22500
+ *
22501
+ * This is a pure formatting function with no side effects. For each failed
22502
+ * scenario it produces a line `"- {name}: {stderr || stdout || '(no output)'}"`,
22503
+ * preferring stderr (most useful for debugging), falling back to stdout
22504
+ * (some tools write errors to stdout), then to the literal `'(no output)'`.
22505
+ *
22506
+ * Returns `"All scenarios passed"` when there are no failures.
22507
+ */
22508
+ function formatScenarioDiff(results) {
22509
+ const failed = results.scenarios.filter((s$1) => s$1.status === "fail");
22510
+ if (failed.length === 0) return "All scenarios passed";
22511
+ const lines = failed.map((s$1) => {
22512
+ const output = s$1.stderr || s$1.stdout || "(no output)";
22513
+ return `- ${s$1.name}: ${output}`;
22514
+ });
22515
+ return lines.join("\n");
22516
+ }
22517
+ /**
22518
+ * Derives a focused fix instruction string from failed scenarios.
22519
+ *
22520
+ * This function produces human-readable fix instructions for the retried agent.
22521
+ * Returns `"Fix {n} failing scenario{s}: {name1}, {name2}, ..."` when there are
22522
+ * failures, or `""` when all scenarios pass.
22523
+ *
22524
+ * Pluralization: singular "scenario" when n === 1, plural "scenarios" otherwise.
22525
+ */
22526
+ function deriveFixScope(results) {
22527
+ const failed = results.scenarios.filter((s$1) => s$1.status === "fail");
22528
+ if (failed.length === 0) return "";
22529
+ const n$1 = failed.length;
22530
+ const plural = n$1 === 1 ? "scenario" : "scenarios";
22531
+ const names = failed.map((s$1) => s$1.name).join(", ");
22532
+ return `Fix ${n$1} failing ${plural}: ${names}`;
22533
+ }
22534
+ /**
22535
+ * Builds a complete `RemediationContext` from the provided parameters.
22536
+ *
22537
+ * `scenarioResults` is optional — first-iteration retries may not have scenario
22538
+ * data yet. When omitted, `scenarioDiff` defaults to
22539
+ * `"No scenario results available"` and `fixScope` defaults to `""`.
22540
+ *
22541
+ * Stores `satisfactionScoreHistory` as a defensive copy (`[...params.satisfactionScoreHistory]`)
22542
+ * so external mutation of the caller's array does not corrupt the stored history.
22543
+ */
22544
+ function buildRemediationContext(params) {
22545
+ const scenarioDiff = params.scenarioResults ? formatScenarioDiff(params.scenarioResults) : "No scenario results available";
22546
+ const fixScope = params.scenarioResults ? deriveFixScope(params.scenarioResults) : "";
22547
+ return {
22548
+ previousFailureReason: params.previousFailureReason,
22549
+ scenarioDiff,
22550
+ iterationCount: params.iterationCount,
22551
+ satisfactionScoreHistory: [...params.satisfactionScoreHistory],
22552
+ fixScope
22553
+ };
22554
+ }
22555
+ /**
22556
+ * Injects a `RemediationContext` into an `IGraphContext` under `REMEDIATION_CONTEXT_KEY`.
22557
+ *
22558
+ * Called by the executor's retry loop before dispatching to the retried node —
22559
+ * story 45-8 wires this call into the graph executor.
22560
+ */
22561
+ function injectRemediationContext(context, remediation) {
22562
+ context.set(REMEDIATION_CONTEXT_KEY, remediation);
22563
+ }
22142
22564
 
22143
22565
  //#endregion
22144
22566
  //#region packages/factory/dist/graph/executor.js
@@ -22179,17 +22601,6 @@ function normalizeOutcomeStatus(raw) {
22179
22601
  };
22180
22602
  }
22181
22603
  /**
22182
- * Compute exponential backoff delay with ±50% jitter.
22183
- *
22184
- * @param attempt - Zero-indexed attempt number (0 = first retry, 1 = second, etc.)
22185
- * @returns Delay in milliseconds, floored at 0 and capped at 60,000ms
22186
- */
22187
- function computeBackoffDelay(attempt) {
22188
- const rawDelay = Math.min(200 * Math.pow(2, attempt), 6e4);
22189
- const jitter = rawDelay * .5 * (2 * Math.random() - 1);
22190
- return Math.max(0, rawDelay + jitter);
22191
- }
22192
- /**
22193
22604
  * Dispatch a node handler with exponential backoff retry on FAIL outcomes.
22194
22605
  *
22195
22606
  * Emits `graph:node-retried` before each retry attempt.
@@ -22248,6 +22659,13 @@ function createGraphExecutor() {
22248
22659
  const checkpointManager = new CheckpointManager();
22249
22660
  const checkpointFilePath = path.join(config.logsRoot, "checkpoint.json");
22250
22661
  const controller = createConvergenceController();
22662
+ const sessionManager = new SessionBudgetManager();
22663
+ const pipelineManager = new PipelineBudgetManager();
22664
+ const plateauDetector = createPlateauDetector({
22665
+ ...config.plateauWindow !== void 0 ? { window: config.plateauWindow } : {},
22666
+ ...config.plateauThreshold !== void 0 ? { threshold: config.plateauThreshold } : {}
22667
+ });
22668
+ let convergenceIteration = 0;
22251
22669
  let completedNodes = [];
22252
22670
  let nodeRetries = {};
22253
22671
  let context = new GraphContext();
@@ -22287,23 +22705,64 @@ function createGraphExecutor() {
22287
22705
  }
22288
22706
  } else currentNode = graph.startNode();
22289
22707
  while (true) {
22708
+ const sessionResult = sessionManager.checkBudget((config.wallClockCapMs ?? 0) / 1e3);
22709
+ if (!sessionResult.allowed) {
22710
+ config.eventBus?.emit("convergence:budget-exhausted", {
22711
+ runId: config.runId,
22712
+ level: "session",
22713
+ reason: sessionResult.reason
22714
+ });
22715
+ return {
22716
+ status: "FAIL",
22717
+ failureReason: `Session budget exceeded: ${sessionResult.reason}`
22718
+ };
22719
+ }
22720
+ const pipelineResult = pipelineManager.checkBudget(config.pipelineBudgetCapUsd ?? 0);
22721
+ if (!pipelineResult.allowed) {
22722
+ config.eventBus?.emit("convergence:budget-exhausted", {
22723
+ runId: config.runId,
22724
+ level: "pipeline",
22725
+ reason: pipelineResult.reason
22726
+ });
22727
+ return {
22728
+ status: "FAIL",
22729
+ failureReason: `Pipeline budget exceeded: ${pipelineResult.reason}`
22730
+ };
22731
+ }
22290
22732
  const exitNode = graph.exitNode();
22291
22733
  if (currentNode.id === exitNode.id) {
22292
- const gateResult = controller.evaluateGates(graph);
22734
+ const gateResult = controller.checkGoalGates(graph, config.runId, config.eventBus);
22293
22735
  if (!gateResult.satisfied) {
22294
- const failingNodeId = gateResult.failingNodes[0];
22736
+ const failingNodeId = gateResult.failedGates[0];
22295
22737
  const failingGateNode = graph.nodes.get(failingNodeId);
22296
- const retryTarget = failingGateNode?.retryTarget || failingGateNode?.fallbackRetryTarget || graph.retryTarget || graph.fallbackRetryTarget;
22297
- if (retryTarget) {
22298
- const retryNode = graph.nodes.get(retryTarget);
22299
- if (!retryNode) throw new Error(`Retry target node "${retryTarget}" not found in graph`);
22300
- currentNode = retryNode;
22301
- continue;
22302
- }
22303
- return {
22738
+ const retryTargetId = failingGateNode ? controller.resolveRetryTarget(failingGateNode, graph) : null;
22739
+ if (!retryTargetId) return {
22304
22740
  status: "FAIL",
22305
22741
  failureReason: "Goal gate failed: no retry target"
22306
22742
  };
22743
+ const retryNode = graph.nodes.get(retryTargetId);
22744
+ if (!retryNode) throw new Error(`Retry target node "${retryTargetId}" not found in graph`);
22745
+ convergenceIteration++;
22746
+ const satisfactionScore = context.getNumber("satisfaction_score", 0);
22747
+ plateauDetector.recordScore(convergenceIteration, satisfactionScore);
22748
+ const plateauResult = checkPlateauAndEmit(plateauDetector, {
22749
+ runId: config.runId,
22750
+ nodeId: retryTargetId,
22751
+ ...config.eventBus ? { eventBus: config.eventBus } : {}
22752
+ });
22753
+ if (plateauResult.plateaued) return {
22754
+ status: "FAIL",
22755
+ failureReason: `Convergence plateau detected after ${convergenceIteration} iteration(s): scores plateaued at [${plateauResult.scores.join(", ")}]`
22756
+ };
22757
+ const remediation = buildRemediationContext({
22758
+ previousFailureReason: `Goal gate unsatisfied: ${gateResult.failedGates.join(", ")}`,
22759
+ iterationCount: convergenceIteration,
22760
+ satisfactionScoreHistory: plateauResult.scores
22761
+ });
22762
+ injectRemediationContext(context, remediation);
22763
+ skipCycleCheck = true;
22764
+ currentNode = retryNode;
22765
+ continue;
22307
22766
  }
22308
22767
  return { status: "SUCCESS" };
22309
22768
  }
@@ -22400,6 +22859,8 @@ function createGraphExecutor() {
22400
22859
  controller.recordOutcome(nodeToDispatch.id, controllerStatus);
22401
22860
  }
22402
22861
  if (outcome.contextUpdates) for (const [key, value] of Object.entries(outcome.contextUpdates)) context.set(key, value);
22862
+ const nodeCost = context.getNumber("factory.lastNodeCostUsd", 0);
22863
+ if (nodeCost > 0) pipelineManager.addCost(nodeCost);
22403
22864
  if (!skipCompletedPush) completedNodes.push(currentNode.id);
22404
22865
  skipCompletedPush = false;
22405
22866
  await checkpointManager.save(config.logsRoot, {
@@ -22414,7 +22875,7 @@ function createGraphExecutor() {
22414
22875
  checkpointPath: checkpointFilePath
22415
22876
  });
22416
22877
  if (outcome.status === "FAIL") {
22417
- const retryTarget = currentNode.retryTarget || currentNode.fallbackRetryTarget || graph.retryTarget || graph.fallbackRetryTarget;
22878
+ const retryTarget = controller.resolveRetryTarget(currentNode, graph);
22418
22879
  if (retryTarget) {
22419
22880
  const retryNode = graph.nodes.get(retryTarget);
22420
22881
  if (!retryNode) throw new Error(`Retry target node "${retryTarget}" not found in graph`);
@@ -27810,13 +28271,19 @@ function registerFactoryCommand(program) {
27810
28271
  const logsRoot = path.join(projectDir, ".substrate", "runs", runId);
27811
28272
  const stateManager = new RunStateManager({ runDir: logsRoot });
27812
28273
  await stateManager.initRun(dotSource);
28274
+ /** wallClockCapMs: FactoryConfig.wall_clock_cap_seconds × 1000 (story 45-10) */
28275
+ const factoryConfig = await loadFactoryConfig(projectDir, opts.config);
27813
28276
  const executor = createGraphExecutor();
27814
28277
  await executor.run(graph, {
27815
28278
  runId,
27816
28279
  logsRoot,
27817
28280
  handlerRegistry: createDefaultRegistry(),
27818
28281
  eventBus,
27819
- dotSource
28282
+ dotSource,
28283
+ wallClockCapMs: (factoryConfig.factory?.wall_clock_cap_seconds ?? 0) * 1e3,
28284
+ pipelineBudgetCapUsd: factoryConfig.factory?.budget_cap_usd ?? 0,
28285
+ plateauWindow: factoryConfig.factory?.plateau_window ?? 3,
28286
+ plateauThreshold: factoryConfig.factory?.plateau_threshold ?? .05
27820
28287
  });
27821
28288
  } catch (err) {
27822
28289
  const msg = err instanceof Error ? err.message : String(err);
@@ -28691,7 +29158,12 @@ async function runRunAction(options) {
28691
29158
  devStoryOptions: {
28692
29159
  deps: workflowDeps,
28693
29160
  eventBus: sdlcEventBus,
28694
- runDevStory
29161
+ runDevStory,
29162
+ buildVerifier: (root) => runBuildVerification({
29163
+ verifyCommand: pack.manifest.verifyCommand,
29164
+ verifyTimeoutMs: pack.manifest.verifyTimeoutMs,
29165
+ projectRoot: root
29166
+ })
28695
29167
  },
28696
29168
  codeReviewOptions: {
28697
29169
  deps: workflowDeps,
@@ -29293,5 +29765,5 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
29293
29765
  }
29294
29766
 
29295
29767
  //#endregion
29296
- 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, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
29297
- //# sourceMappingURL=run-zWACyS3w.js.map
29768
+ 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, normalizeGraphSummaryToStatus, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
29769
+ //# sourceMappingURL=run-CUMPhuVq.js.map
@@ -0,0 +1,9 @@
1
+ import "./health-DswaC1q5.js";
2
+ import "./logger-KeHncl-f.js";
3
+ import "./helpers-CElYrONe.js";
4
+ import "./dist-CLvAwmT7.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-CUMPhuVq.js";
6
+ import "./routing-CcBOCuC9.js";
7
+ import "./decisions-C0pz9Clx.js";
8
+
9
+ export { runRunAction };
package/dist/schema.sql CHANGED
@@ -274,6 +274,11 @@ CREATE TABLE IF NOT EXISTS story_dependencies (
274
274
  PRIMARY KEY (story_key, depends_on)
275
275
  );
276
276
 
277
+ -- Migration: add created_at to story_dependencies (added in v0.12.0)
278
+ SET @_sd_created_at_exists = (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'story_dependencies' AND COLUMN_NAME = 'created_at');
279
+ SET @_sql = IF(@_sd_created_at_exists = 0, 'ALTER TABLE story_dependencies ADD COLUMN created_at DATETIME DEFAULT NULL', 'SELECT 1');
280
+ PREPARE _add_col FROM @_sql; EXECUTE _add_col; DEALLOCATE PREPARE _add_col;
281
+
277
282
  -- ---------------------------------------------------------------------------
278
283
  -- ready_stories view (Epic 31-1)
279
284
  -- ---------------------------------------------------------------------------
@@ -1,5 +1,5 @@
1
- import "./dist-DKG5lyUw.js";
1
+ import "./dist-CLvAwmT7.js";
2
2
  import "./version-manager-impl-BmOWu8ml.js";
3
- import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-CBE6JaxX.js";
3
+ import { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand } from "./upgrade-DT0I_-1E.js";
4
4
 
5
5
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
@@ -1,4 +1,4 @@
1
- import { createVersionManager } from "./dist-DKG5lyUw.js";
1
+ import { createVersionManager } from "./dist-CLvAwmT7.js";
2
2
  import { execSync, spawn } from "child_process";
3
3
  import * as readline from "readline";
4
4
 
@@ -123,4 +123,4 @@ function registerUpgradeCommand(program) {
123
123
 
124
124
  //#endregion
125
125
  export { isGlobalInstall, registerUpgradeCommand, runUpgradeCommand };
126
- //# sourceMappingURL=upgrade-CBE6JaxX.js.map
126
+ //# sourceMappingURL=upgrade-DT0I_-1E.js.map
@@ -1,4 +1,4 @@
1
- import { VersionManagerImpl, createVersionManager } from "./dist-DKG5lyUw.js";
1
+ import { VersionManagerImpl, createVersionManager } from "./dist-CLvAwmT7.js";
2
2
  import "./version-manager-impl-BmOWu8ml.js";
3
3
 
4
4
  export { createVersionManager };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,4 +0,0 @@
1
- import { AdapterRegistry } from "./dist-DKG5lyUw.js";
2
- import "./adapter-registry-DXLMTmfD.js";
3
-
4
- export { AdapterRegistry };
@@ -1,9 +0,0 @@
1
- import "./health-BR5GD5Ge.js";
2
- import "./logger-KeHncl-f.js";
3
- import "./helpers-CElYrONe.js";
4
- import "./dist-DKG5lyUw.js";
5
- import { registerRunCommand, runRunAction } from "./run-zWACyS3w.js";
6
- import "./routing-CcBOCuC9.js";
7
- import "./decisions-C0pz9Clx.js";
8
-
9
- export { runRunAction };