substrate-ai 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, createTelemetryAdvisor, detectCycles, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-IDOmPys1.js";
2
+ import { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, createTelemetryAdvisor, detectCycles, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-GwwiCYXX.js";
3
3
  import { createLogger } from "../logger-D2fS2ccL.js";
4
4
  import { AdapterRegistry } from "../adapter-registry-D2zdMwVu.js";
5
5
  import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema } from "../config-migrator-DtZW1maj.js";
6
6
  import { ConfigError, createEventBus } from "../helpers-BihqWgVe.js";
7
- import { RoutingRecommender } from "../routing-BUE9pIxW.js";
7
+ import { RoutingRecommender } from "../routing-CobBiKeV.js";
8
8
  import { addTokenUsage, createDecision, createPipelineRun, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getTokenUsageSummary, listRequirements, updatePipelineRun } from "../decisions-C6MF2Cax.js";
9
9
  import { ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, OPERATIONAL_FINDING, STORY_METRICS, aggregateTokenUsageForRun, compareRunMetrics, getBaselineRunMetrics, getRunMetrics, getStoryMetricsForRun, incrementRunRestarts, listRunMetrics, tagRunAsBaseline } from "../operational-BRpT8MYF.js";
10
10
  import { abortMerge, createWorktree, getConflictingFiles, getMergedFiles, getOrphanedWorktrees, performMerge, removeBranch, removeWorktree, simulateMerge, verifyGitVersion } from "../git-utils-C-fdrHF_.js";
@@ -432,8 +432,8 @@ async function detectMonorepoProfile(rootDir) {
432
432
  return { project: {
433
433
  type: "monorepo",
434
434
  tool: "turborepo",
435
- buildCommand: "turbo build",
436
- testCommand: "turbo test",
435
+ buildCommand: "npx turbo build",
436
+ testCommand: "npx turbo test",
437
437
  packages
438
438
  } };
439
439
  }
@@ -912,6 +912,22 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
912
912
  const TaskToolCommandGenerator = resolveExport(taskToolMod, "TaskToolCommandGenerator");
913
913
  const manifestMod = _require(join(installerLibPath, "core", "manifest-generator.js"));
914
914
  const ManifestGenerator = resolveExport(manifestMod, "ManifestGenerator");
915
+ const pathUtilsMod = _require(join(installerLibPath, "ide", "shared", "path-utils.js"));
916
+ const pathUtils = { toDashPath: pathUtilsMod.toDashPath ?? pathUtilsMod.default?.toDashPath };
917
+ const writeDashFallback = async (baseDir, artifacts, acceptTypes) => {
918
+ let written = 0;
919
+ for (const artifact of artifacts) {
920
+ if (!acceptTypes.includes(artifact.type)) continue;
921
+ const content = artifact.content;
922
+ if (!content || !artifact.relativePath) continue;
923
+ const flatName = pathUtils.toDashPath(artifact.relativePath);
924
+ const dest = join(baseDir, flatName);
925
+ mkdirSync(dirname(dest), { recursive: true });
926
+ writeFileSync(dest, content, "utf-8");
927
+ written++;
928
+ }
929
+ return written;
930
+ };
915
931
  const nonCoreModules = scanBmadModules(bmadDir);
916
932
  const allModules = ["core", ...nonCoreModules];
917
933
  try {
@@ -925,13 +941,13 @@ async function scaffoldClaudeCommands(projectRoot, outputFormat) {
925
941
  clearBmadCommandFiles(commandsDir);
926
942
  const agentGen = new AgentCommandGenerator("_bmad");
927
943
  const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, nonCoreModules);
928
- const agentCount = await agentGen.writeDashArtifacts(commandsDir, agentArtifacts);
944
+ const agentCount = typeof agentGen.writeDashArtifacts === "function" ? await agentGen.writeDashArtifacts(commandsDir, agentArtifacts) : await writeDashFallback(commandsDir, agentArtifacts, ["agent-launcher"]);
929
945
  const workflowGen = new WorkflowCommandGenerator("_bmad");
930
946
  const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
931
- const workflowCount = await workflowGen.writeDashArtifacts(commandsDir, workflowArtifacts);
947
+ const workflowCount = typeof workflowGen.writeDashArtifacts === "function" ? await workflowGen.writeDashArtifacts(commandsDir, workflowArtifacts) : await writeDashFallback(commandsDir, workflowArtifacts, ["workflow-command", "workflow-launcher"]);
932
948
  const taskToolGen = new TaskToolCommandGenerator("_bmad");
933
949
  const { artifacts: taskToolArtifacts } = await taskToolGen.collectTaskToolArtifacts(bmadDir);
934
- const taskToolCount = await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts);
950
+ const taskToolCount = typeof taskToolGen.writeDashArtifacts === "function" ? await taskToolGen.writeDashArtifacts(commandsDir, taskToolArtifacts) : await writeDashFallback(commandsDir, taskToolArtifacts, ["task", "tool"]);
935
951
  const total = agentCount + workflowCount + taskToolCount;
936
952
  if (outputFormat !== "json") process.stdout.write(`Generated ${String(total)} Claude Code commands (${String(agentCount)} agents, ${String(workflowCount)} workflows, ${String(taskToolCount)} tasks/tools)\n`);
937
953
  logger$18.info({
@@ -1541,7 +1557,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
1541
1557
  }
1542
1558
  }
1543
1559
  async function runResumeAction(options) {
1544
- const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, events: eventsFlag, registry } = options;
1560
+ const { runId: specifiedRunId, stopAfter, outputFormat, projectRoot, concurrency, pack: packName, events: eventsFlag, registry, maxReviewCycles = 2 } = options;
1545
1561
  if (stopAfter !== void 0 && !VALID_PHASES.includes(stopAfter)) {
1546
1562
  const errorMsg = `Invalid phase: "${stopAfter}". Valid phases: ${VALID_PHASES.join(", ")}`;
1547
1563
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -1638,7 +1654,7 @@ async function runResumeAction(options) {
1638
1654
  }
1639
1655
  }
1640
1656
  async function runFullPipelineFromPhase(options) {
1641
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, events: eventsFlag, existingRunId, projectRoot, registry: injectedRegistry, stories: explicitStories } = options;
1657
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, events: eventsFlag, existingRunId, projectRoot, registry: injectedRegistry, stories: explicitStories, maxReviewCycles = 2 } = options;
1642
1658
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
1643
1659
  const adapter = createDatabaseAdapter({
1644
1660
  backend: "auto",
@@ -1775,7 +1791,7 @@ async function runFullPipelineFromPhase(options) {
1775
1791
  eventBus,
1776
1792
  config: {
1777
1793
  maxConcurrency: concurrency,
1778
- maxReviewCycles: 2,
1794
+ maxReviewCycles,
1779
1795
  pipelineRunId: runId,
1780
1796
  enableHeartbeat: eventsFlag === true
1781
1797
  },
@@ -1955,7 +1971,7 @@ async function runFullPipelineFromPhase(options) {
1955
1971
  }
1956
1972
  }
1957
1973
  function registerResumeCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
1958
- program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").action(async (opts) => {
1974
+ program.command("resume").description("Resume a previously interrupted pipeline run").option("--run-id <id>", "Pipeline run ID to resume (defaults to latest)").option("--pack <name>", "Methodology pack name", "bmad").option("--stop-after <phase>", "Stop pipeline after this phase completes (overrides saved state)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).action(async (opts) => {
1959
1975
  const outputFormat = opts.outputFormat === "json" ? "json" : "human";
1960
1976
  const exitCode = await runResumeAction({
1961
1977
  runId: opts.runId,
@@ -1965,6 +1981,7 @@ function registerResumeCommand(program, _version = "0.0.0", projectRoot = proces
1965
1981
  concurrency: opts.concurrency,
1966
1982
  pack: opts.pack,
1967
1983
  events: opts.events,
1984
+ maxReviewCycles: opts.maxReviewCycles,
1968
1985
  registry
1969
1986
  });
1970
1987
  process.exitCode = exitCode;
@@ -2058,7 +2075,7 @@ async function runStatusAction(options) {
2058
2075
  if (runId !== void 0 && runId !== "") run = await getPipelineRunById(adapter, runId);
2059
2076
  else run = await getLatestRun(adapter);
2060
2077
  if (run === void 0) {
2061
- const errorMsg = runId !== void 0 ? `Pipeline run '${runId}' not found.` : "No pipeline runs found. Run `substrate run` first.";
2078
+ const errorMsg = runId !== void 0 ? `Pipeline run '${runId}' not found.` : "No pipeline runs found. Run `substrate run --events` to start a pipeline first.";
2062
2079
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
2063
2080
  else process.stderr.write(`Error: ${errorMsg}\n`);
2064
2081
  return 1;
@@ -3469,7 +3486,7 @@ async function runSupervisorAction(options, deps = {}) {
3469
3486
  await initSchema(expAdapter);
3470
3487
  const { runRunAction: runPipeline } = await import(
3471
3488
  /* @vite-ignore */
3472
- "../run-DTOsG7PJ.js"
3489
+ "../run-B5-a932y.js"
3473
3490
  );
3474
3491
  const runStoryFn = async (opts) => {
3475
3492
  const exitCode = await runPipeline({
@@ -3985,7 +4002,7 @@ async function runMetricsAction(options) {
3985
4002
  const routingConfigPath = join(dbDir, "routing.yml");
3986
4003
  let routingConfig = null;
3987
4004
  if (existsSync(routingConfigPath)) try {
3988
- const { loadModelRoutingConfig } = await import("../routing-DbR9FPmj.js");
4005
+ const { loadModelRoutingConfig } = await import("../routing-CpsRPjLE.js");
3989
4006
  routingConfig = loadModelRoutingConfig(routingConfigPath);
3990
4007
  } catch {}
3991
4008
  if (routingConfig === null) routingConfig = {
package/dist/index.d.ts CHANGED
@@ -514,6 +514,23 @@ interface SupervisorExperimentErrorEvent {
514
514
  /** Error message describing why the experiment failed */
515
515
  error: string;
516
516
  }
517
+ /**
518
+ * Emitted after all stories complete when the `.substrate/project-profile.yaml`
519
+ * may be outdated relative to the actual project structure (e.g., profile says
520
+ * `type: single` but a `turbo.json` now exists, or new language markers appeared).
521
+ *
522
+ * Non-blocking warning — the pipeline has already finished. The user should
523
+ * re-run `substrate init --force` to regenerate the profile.
524
+ */
525
+ interface PipelineProfileStaleEvent {
526
+ type: 'pipeline:profile-stale';
527
+ /** ISO-8601 timestamp generated at emit time */
528
+ ts: string;
529
+ /** Human-readable message describing the staleness indicators found */
530
+ message: string;
531
+ /** List of staleness indicators detected (e.g., "turbo.json exists but profile says type: single") */
532
+ indicators: string[];
533
+ }
517
534
  /**
518
535
  * Emitted when post-sprint contract verification finds a mismatch between
519
536
  * declared export/import contracts (Story 25-6).
@@ -581,7 +598,7 @@ interface RoutingModelSelectedEvent {
581
598
  * }
582
599
  * ```
583
600
  */
584
- type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineContractMismatchEvent | PipelineContractVerificationSummaryEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent | RoutingModelSelectedEvent; //#endregion
601
+ type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | PipelinePreFlightFailureEvent | PipelineProfileStaleEvent | PipelineContractMismatchEvent | PipelineContractVerificationSummaryEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | StoryInterfaceChangeWarningEvent | StoryMetricsEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent | RoutingModelSelectedEvent; //#endregion
585
602
  //#region src/core/errors.d.ts
586
603
 
587
604
  /**
@@ -1264,6 +1281,13 @@ interface OrchestratorEvents {
1264
1281
  /** Build output (stdout+stderr), truncated to 2000 chars */
1265
1282
  output: string;
1266
1283
  };
1284
+ /** Project profile may be outdated relative to the actual project structure */
1285
+ 'pipeline:profile-stale': {
1286
+ /** Human-readable message describing the staleness indicators found */
1287
+ message: string;
1288
+ /** List of staleness indicators detected */
1289
+ indicators: string[];
1290
+ };
1267
1291
  /** Contract verification found a mismatch between declared export/import contracts */
1268
1292
  'pipeline:contract-mismatch': {
1269
1293
  /** Story key that declared the export for this contract */
@@ -263,7 +263,7 @@ var RoutingResolver = class RoutingResolver {
263
263
  return new RoutingResolver(config, logger$1);
264
264
  } catch (err) {
265
265
  if (err instanceof RoutingConfigError && err.code === "CONFIG_NOT_FOUND") {
266
- logger$1.warn({
266
+ logger$1.debug({
267
267
  configPath: filePath,
268
268
  component: "routing",
269
269
  reason: "config not found"
@@ -829,4 +829,4 @@ var RoutingTuner = class {
829
829
 
830
830
  //#endregion
831
831
  export { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig };
832
- //# sourceMappingURL=routing-BUE9pIxW.js.map
832
+ //# sourceMappingURL=routing-CobBiKeV.js.map
@@ -1,4 +1,4 @@
1
1
  import "./logger-D2fS2ccL.js";
2
- import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./routing-BUE9pIxW.js";
2
+ import { ModelRoutingConfigSchema, ProviderPolicySchema, RoutingConfigError, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, TASK_TYPE_PHASE_MAP, getModelTier, loadModelRoutingConfig } from "./routing-CobBiKeV.js";
3
3
 
4
4
  export { loadModelRoutingConfig };
@@ -1,8 +1,8 @@
1
- import { registerRunCommand, runRunAction } from "./run-IDOmPys1.js";
1
+ import { registerRunCommand, runRunAction } from "./run-GwwiCYXX.js";
2
2
  import "./logger-D2fS2ccL.js";
3
3
  import "./config-migrator-DtZW1maj.js";
4
4
  import "./helpers-BihqWgVe.js";
5
- import "./routing-BUE9pIxW.js";
5
+ import "./routing-CobBiKeV.js";
6
6
  import "./decisions-C6MF2Cax.js";
7
7
  import "./operational-BRpT8MYF.js";
8
8
 
@@ -1,7 +1,7 @@
1
1
  import { createLogger, deepMask } from "./logger-D2fS2ccL.js";
2
2
  import { CURRENT_CONFIG_FORMAT_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "./config-migrator-DtZW1maj.js";
3
3
  import { ConfigError, ConfigIncompatibleFormatError, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-BihqWgVe.js";
4
- import { RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, loadModelRoutingConfig } from "./routing-BUE9pIxW.js";
4
+ import { RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, loadModelRoutingConfig } from "./routing-CobBiKeV.js";
5
5
  import { addTokenUsage, createDecision, createPipelineRun, createRequirement, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunningPipelineRuns, getTokenUsageSummary, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision } from "./decisions-C6MF2Cax.js";
6
6
  import { ADVISORY_NOTES, ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-BRpT8MYF.js";
7
7
  import { createRequire } from "module";
@@ -325,7 +325,8 @@ var InMemoryDatabaseAdapter = class {
325
325
  if (!m$1) return [];
326
326
  return [this._evalSelectExprs(m$1[1].trim())];
327
327
  }
328
- const m = /SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/is.exec(sql);
328
+ const stripped = sql.replace(/\s+ORDER\s+BY\s+.+?(?=\s+LIMIT\s|\s*$)/is, "").replace(/\s+LIMIT\s+\d+\s*$/is, "");
329
+ const m = /SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/is.exec(stripped);
329
330
  if (!m) return [];
330
331
  const colsStr = m[1].trim();
331
332
  const tableName = m[2];
@@ -334,6 +335,7 @@ var InMemoryDatabaseAdapter = class {
334
335
  let rows = table.map((r) => ({ ...r }));
335
336
  if (whereStr) rows = rows.filter((row) => this._matchWhere(whereStr.trim(), row));
336
337
  if (colsStr === "*") return rows;
338
+ if (/\b(?:SUM|COALESCE|COUNT|AVG|MIN|MAX)\s*\(/i.test(colsStr)) return [this._evalAggregate(colsStr, rows)];
337
339
  return rows.map((row) => this._projectCols(colsStr, row));
338
340
  }
339
341
  _update(sql) {
@@ -393,12 +395,22 @@ var InMemoryDatabaseAdapter = class {
393
395
  if (row[notNullM[1]] === null || row[notNullM[1]] === void 0) return false;
394
396
  continue;
395
397
  }
398
+ const likeM = /^(\w+)\s+LIKE\s+'(.*)'$/is.exec(trimmed);
399
+ if (likeM) {
400
+ const colVal = row[likeM[1]];
401
+ if (colVal === null || colVal === void 0) return false;
402
+ const pattern = likeM[2].replace(/''/g, "'");
403
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === "%" || ch === "_" ? ch : "\\" + ch);
404
+ const regex = new RegExp("^" + escaped.replace(/%/g, ".*").replace(/_/g, ".") + "$", "s");
405
+ if (!regex.test(String(colVal))) return false;
406
+ continue;
407
+ }
396
408
  }
397
409
  return true;
398
410
  }
399
411
  _projectCols(colsStr, row) {
400
412
  const result = {};
401
- const cols = colsStr.split(",").map((c) => c.trim());
413
+ const cols = this._splitTopLevelCommas(colsStr);
402
414
  for (const col of cols) {
403
415
  const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
404
416
  if (aliasM) result[aliasM[2]] = this._evalExprAgainstRow(aliasM[1].trim(), row);
@@ -408,7 +420,7 @@ var InMemoryDatabaseAdapter = class {
408
420
  }
409
421
  _evalSelectExprs(exprs) {
410
422
  const result = {};
411
- const parts = exprs.split(",").map((p) => p.trim());
423
+ const parts = this._splitTopLevelCommas(exprs);
412
424
  for (const part of parts) {
413
425
  const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(part);
414
426
  if (aliasM) result[aliasM[2]] = this._evalLiteral(aliasM[1].trim());
@@ -431,6 +443,87 @@ var InMemoryDatabaseAdapter = class {
431
443
  return literal;
432
444
  }
433
445
  /**
446
+ * Split a string by commas that are NOT inside parentheses.
447
+ * E.g. "COALESCE(SUM(x), 0) as a, y" → ["COALESCE(SUM(x), 0) as a", "y"]
448
+ */
449
+ _splitTopLevelCommas(str) {
450
+ const parts = [];
451
+ let current = "";
452
+ let depth = 0;
453
+ let inStr = false;
454
+ for (let i = 0; i < str.length; i++) {
455
+ const ch = str[i];
456
+ if (ch === "'" && !inStr) {
457
+ inStr = true;
458
+ current += ch;
459
+ } else if (ch === "'" && inStr) if (str[i + 1] === "'") {
460
+ current += "''";
461
+ i++;
462
+ } else {
463
+ inStr = false;
464
+ current += ch;
465
+ }
466
+ else if (!inStr && ch === "(") {
467
+ depth++;
468
+ current += ch;
469
+ } else if (!inStr && ch === ")") {
470
+ depth--;
471
+ current += ch;
472
+ } else if (!inStr && ch === "," && depth === 0) {
473
+ parts.push(current.trim());
474
+ current = "";
475
+ } else current += ch;
476
+ }
477
+ if (current.trim() !== "") parts.push(current.trim());
478
+ return parts;
479
+ }
480
+ /**
481
+ * Evaluate aggregate SELECT expressions (SUM, COALESCE, COUNT) across
482
+ * a set of filtered rows, returning a single result row.
483
+ */
484
+ _evalAggregate(colsStr, rows) {
485
+ const result = {};
486
+ const cols = this._splitTopLevelCommas(colsStr);
487
+ for (const col of cols) {
488
+ const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
489
+ const expr = aliasM ? aliasM[1].trim() : col.trim();
490
+ const alias = aliasM ? aliasM[2] : col.trim();
491
+ result[alias] = this._evalAggregateExpr(expr, rows);
492
+ }
493
+ return result;
494
+ }
495
+ /**
496
+ * Evaluate a single aggregate expression against a set of rows.
497
+ * Supports: SUM(col), COALESCE(expr, default), COUNT(*).
498
+ */
499
+ _evalAggregateExpr(expr, rows) {
500
+ const trimmed = expr.trim();
501
+ const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
502
+ if (coalesceM) {
503
+ const args = this._splitTopLevelCommas(coalesceM[1]);
504
+ for (const arg of args) {
505
+ const val = this._evalAggregateExpr(arg.trim(), rows);
506
+ if (val !== null && val !== void 0) return val;
507
+ }
508
+ return null;
509
+ }
510
+ const sumM = /^SUM\((\w+)\)$/i.exec(trimmed);
511
+ if (sumM) {
512
+ const col = sumM[1];
513
+ if (rows.length === 0) return null;
514
+ let total = 0;
515
+ for (const row of rows) total += Number(row[col] ?? 0);
516
+ return total;
517
+ }
518
+ if (/^COUNT\(\*\)$/i.test(trimmed)) return rows.length;
519
+ const countM = /^COUNT\((\w+)\)$/i.exec(trimmed);
520
+ if (countM) {
521
+ const col = countM[1];
522
+ return rows.filter((r) => r[col] !== null && r[col] !== void 0).length;
523
+ }
524
+ return this._evalLiteral(trimmed);
525
+ }
526
+ /**
434
527
  * Parse a comma-separated list of SQL literal values.
435
528
  * Handles: NULL, numbers, single-quoted strings.
436
529
  * Simple split by comma (assumes no commas inside string values).
@@ -1829,8 +1922,8 @@ function resolveBmadMethodVersion(fromDir = __dirname) {
1829
1922
  const BMAD_BASELINE_TOKENS_FULL = 56800;
1830
1923
  /** BMAD baseline token total for create+dev+review comparison */
1831
1924
  const BMAD_BASELINE_TOKENS = 23800;
1832
- /** Story key pattern: <epic>-<story> e.g. "10-1" */
1833
- const STORY_KEY_PATTERN$1 = /^\d+-\d+$/;
1925
+ /** Story key pattern: <epic>-<story> e.g. "10-1", "1-1a", "NEW-26" */
1926
+ const STORY_KEY_PATTERN$1 = /^[A-Za-z0-9]+-[A-Za-z0-9]+$/;
1834
1927
  /**
1835
1928
  * Top-level keys in .claude/settings.json that substrate owns.
1836
1929
  * On init, these are set/updated unconditionally.
@@ -3441,6 +3534,28 @@ const PIPELINE_EVENT_METADATA = [
3441
3534
  description: "Overall verification result."
3442
3535
  }
3443
3536
  ]
3537
+ },
3538
+ {
3539
+ type: "pipeline:profile-stale",
3540
+ description: "Project profile may be outdated. Non-blocking warning — run `substrate init --force` to re-detect.",
3541
+ when: "After all stories complete, before pipeline:complete. Emitted when staleness indicators are found.",
3542
+ fields: [
3543
+ {
3544
+ name: "ts",
3545
+ type: "string",
3546
+ description: "Timestamp."
3547
+ },
3548
+ {
3549
+ name: "message",
3550
+ type: "string",
3551
+ description: "Human-readable staleness warning message."
3552
+ },
3553
+ {
3554
+ name: "indicators",
3555
+ type: "string[]",
3556
+ description: "List of staleness indicators (e.g., \"turbo.json exists but profile says type: single\")."
3557
+ }
3558
+ ]
3444
3559
  }
3445
3560
  ];
3446
3561
  /**
@@ -6338,7 +6453,8 @@ var DispatcherShuttingDownError = class extends Error {
6338
6453
  const YAML_ANCHOR_KEYS = [
6339
6454
  "result:",
6340
6455
  "verdict:",
6341
- "story_file:"
6456
+ "story_file:",
6457
+ "expansion_priority:"
6342
6458
  ];
6343
6459
  /**
6344
6460
  * Extract the YAML result block from sub-agent output.
@@ -7086,7 +7202,7 @@ function detectPackageManager(projectRoot) {
7086
7202
  if (existsSync$1(join$1(projectRoot, "turbo.json"))) return {
7087
7203
  packageManager: "none",
7088
7204
  lockfile: "turbo.json",
7089
- command: "turbo build"
7205
+ command: "npx turbo build"
7090
7206
  };
7091
7207
  const nodeCandidates = [
7092
7208
  {
@@ -7188,6 +7304,16 @@ function runBuildVerification(options) {
7188
7304
  output: combinedOutput,
7189
7305
  reason: "build-verification-timeout"
7190
7306
  };
7307
+ const missingScriptPattern = /Missing script[:\s]|No script found|Command "build" not found/i;
7308
+ if (missingScriptPattern.test(combinedOutput)) {
7309
+ logger$23.warn("Build script not found — skipping pre-flight (greenfield repo)");
7310
+ return {
7311
+ status: "skipped",
7312
+ exitCode,
7313
+ output: combinedOutput,
7314
+ reason: "build-script-not-found"
7315
+ };
7316
+ }
7191
7317
  return {
7192
7318
  status: "failed",
7193
7319
  exitCode,
@@ -7665,12 +7791,12 @@ var FileStateStore = class {
7665
7791
  //#region src/modules/state/dolt-store.ts
7666
7792
  const log = createLogger("modules:state:dolt");
7667
7793
  /**
7668
- * Validate that a story key matches the expected pattern (e.g. "26-7").
7794
+ * Validate that a story key matches the expected pattern (e.g. "26-7", "1-1a", "NEW-26").
7669
7795
  * Prevents SQL injection via string-interpolated identifiers.
7670
7796
  */
7671
- const STORY_KEY_PATTERN = /^[0-9]+-[0-9]+$/;
7797
+ const STORY_KEY_PATTERN = /^[A-Za-z0-9]+-[A-Za-z0-9]+$/;
7672
7798
  function assertValidStoryKey(storyKey) {
7673
- if (!STORY_KEY_PATTERN.test(storyKey)) throw new DoltQueryError("assertValidStoryKey", `Invalid story key: '${storyKey}'. Must match pattern <number>-<number>.`);
7799
+ if (!STORY_KEY_PATTERN.test(storyKey)) throw new DoltQueryError("assertValidStoryKey", `Invalid story key: '${storyKey}'. Must match pattern <segment>-<segment> (e.g. "10-1", "1-1a", "NEW-26").`);
7674
7800
  }
7675
7801
  /**
7676
7802
  * Dolt-backed implementation of the StateStore interface.
@@ -9095,9 +9221,17 @@ function readEpicShardFromFile(projectRoot, epicId) {
9095
9221
  if (!epicsPath) return "";
9096
9222
  const content = readFileSync$1(epicsPath, "utf-8");
9097
9223
  const epicNum = epicId.replace(/^epic-/i, "");
9098
- const pattern = new RegExp(`^#{2,4}\\s+(?:Epic\\s+)?${epicNum}[.:\\s].*?(?=\\n#{2,4}\\s|$)`, "ms");
9099
- const match$1 = pattern.exec(content);
9100
- return match$1 ? match$1[0].trim() : "";
9224
+ const headingPattern = new RegExp(`^(#{2,4})\\s+(?:Epic\\s+)?${epicNum}[.:\\s]`, "m");
9225
+ const headingMatch = headingPattern.exec(content);
9226
+ if (!headingMatch) return "";
9227
+ const startIdx = headingMatch.index;
9228
+ const headingLevel = headingMatch[1].length;
9229
+ const hashes = "#".repeat(headingLevel);
9230
+ const endPattern = new RegExp(`\\n${hashes}\\s`, "g");
9231
+ endPattern.lastIndex = startIdx + headingMatch[0].length;
9232
+ const endMatch = endPattern.exec(content);
9233
+ const endIdx = endMatch ? endMatch.index : content.length;
9234
+ return content.slice(startIdx, endIdx).trim();
9101
9235
  } catch (err) {
9102
9236
  logger$20.warn({
9103
9237
  epicId,
@@ -9356,9 +9490,9 @@ async function getProjectFindings(db) {
9356
9490
  sections.push("**Prior escalations:**");
9357
9491
  for (const d of diagnoses.slice(-3)) try {
9358
9492
  const val = JSON.parse(d.value);
9359
- sections.push(`- ${d.key.split(":")[0]}: ${val.recommendedAction} — ${val.rationale}`);
9493
+ sections.push(`- ${(d.key ?? "").split(":")[0]}: ${val.recommendedAction} — ${val.rationale}`);
9360
9494
  } catch {
9361
- sections.push(`- ${d.key}: escalated`);
9495
+ sections.push(`- ${d.key ?? "unknown"}: escalated`);
9362
9496
  }
9363
9497
  }
9364
9498
  const highCycleStories = metrics.filter((m) => {
@@ -9373,16 +9507,16 @@ async function getProjectFindings(db) {
9373
9507
  sections.push("**Stories with high review cycles:**");
9374
9508
  for (const m of highCycleStories) try {
9375
9509
  const val = JSON.parse(m.value);
9376
- sections.push(`- ${m.key.split(":")[0]}: ${val.review_cycles} cycles`);
9510
+ sections.push(`- ${(m.key ?? "").split(":")[0]}: ${val.review_cycles} cycles`);
9377
9511
  } catch {}
9378
9512
  }
9379
- const stalls = operational.filter((o) => o.key.startsWith("stall:"));
9513
+ const stalls = operational.filter((o) => o.key?.startsWith("stall:"));
9380
9514
  if (stalls.length > 0) sections.push(`**Prior stalls:** ${stalls.length} stall event(s) recorded`);
9381
9515
  if (advisoryNotes.length > 0) {
9382
9516
  sections.push("**Advisory notes from prior reviews (LGTM_WITH_NOTES):**");
9383
9517
  for (const n of advisoryNotes.slice(-3)) try {
9384
9518
  const val = JSON.parse(n.value);
9385
- const storyId = n.key.split(":")[0];
9519
+ const storyId = (n.key ?? "").split(":")[0];
9386
9520
  if (typeof val.notes === "string" && val.notes.length > 0) sections.push(`- ${storyId}: ${val.notes}`);
9387
9521
  } catch {
9388
9522
  sections.push(`- ${n.key}: advisory notes available`);
@@ -12377,7 +12511,7 @@ function detectInterfaceChanges(options) {
12377
12511
  for (const name of allNames) {
12378
12512
  let grepOutput = "";
12379
12513
  try {
12380
- grepOutput = execSync(`grep -r --include="*.test.ts" --include="*.spec.ts" -l "${name}" .`, {
12514
+ grepOutput = execSync(`grep -r --include="*.test.ts" --include="*.spec.ts" --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=dist --exclude-dir=.next --exclude-dir=coverage -l "${name}" .`, {
12381
12515
  cwd: projectRoot,
12382
12516
  encoding: "utf-8",
12383
12517
  timeout: 1e4,
@@ -15870,6 +16004,9 @@ function createTelemetryAdvisor(deps) {
15870
16004
 
15871
16005
  //#endregion
15872
16006
  //#region src/modules/implementation-orchestrator/orchestrator-impl.ts
16007
+ function estimateDispatchCost(input, output) {
16008
+ return (input * 3 + output * 15) / 1e6;
16009
+ }
15873
16010
  function createPauseGate() {
15874
16011
  let resolve$2;
15875
16012
  const promise = new Promise((res) => {
@@ -15902,6 +16039,69 @@ function buildTargetedFilesContent(issueList) {
15902
16039
  return lines.join("\n");
15903
16040
  }
15904
16041
  /**
16042
+ * Normalize a title string into a set of meaningful words for comparison.
16043
+ * Strips punctuation, lowercases, and filters out very short words (<=2 chars)
16044
+ * and common stop words to focus on content-bearing terms.
16045
+ */
16046
+ function titleToWordSet(title) {
16047
+ const stopWords = new Set([
16048
+ "the",
16049
+ "and",
16050
+ "for",
16051
+ "with",
16052
+ "from",
16053
+ "into",
16054
+ "that",
16055
+ "this",
16056
+ "via"
16057
+ ]);
16058
+ return new Set(title.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").split(/[\s-]+/).filter((w) => w.length > 2 && !stopWords.has(w)));
16059
+ }
16060
+ /**
16061
+ * Compute the word overlap ratio between two titles.
16062
+ * Returns a value between 0 and 1, where 1 means all words in the smaller set
16063
+ * are present in the larger set.
16064
+ *
16065
+ * Uses the smaller set as the denominator so that a generated title that is a
16066
+ * reasonable subset or superset of the expected title still scores well.
16067
+ */
16068
+ function computeTitleOverlap(titleA, titleB) {
16069
+ const wordsA = titleToWordSet(titleA);
16070
+ const wordsB = titleToWordSet(titleB);
16071
+ if (wordsA.size === 0 || wordsB.size === 0) return 0;
16072
+ let shared = 0;
16073
+ for (const w of wordsA) if (wordsB.has(w)) shared++;
16074
+ const denominator = Math.min(wordsA.size, wordsB.size);
16075
+ return shared / denominator;
16076
+ }
16077
+ /**
16078
+ * Extract the expected story title from the epic shard content.
16079
+ *
16080
+ * Looks for patterns like:
16081
+ * - "### Story 37-1: Turborepo monorepo scaffold"
16082
+ * - "Story 37-1: Turborepo monorepo scaffold"
16083
+ * - "**37-1**: Turborepo monorepo scaffold"
16084
+ * - "37-1: Turborepo monorepo scaffold"
16085
+ *
16086
+ * Returns the title portion after the story key, or null if no match.
16087
+ */
16088
+ function extractExpectedStoryTitle(shardContent, storyKey) {
16089
+ if (!shardContent || !storyKey) return null;
16090
+ const escaped = storyKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16091
+ const patterns = [
16092
+ new RegExp(`^#{2,4}\\s+Story\\s+${escaped}[:\\s]+\\s*(.+)$`, "mi"),
16093
+ new RegExp(`^Story\\s+${escaped}[:\\s]+\\s*(.+)$`, "mi"),
16094
+ new RegExp(`^\\*\\*${escaped}\\*\\*[:\\s]+\\s*(.+)$`, "mi"),
16095
+ new RegExp(`^${escaped}[:\\s]+\\s*(.+)$`, "mi")
16096
+ ];
16097
+ for (const pattern of patterns) {
16098
+ const match$1 = pattern.exec(shardContent);
16099
+ if (match$1?.[1]) return match$1[1].replace(/\*+$/, "").trim();
16100
+ }
16101
+ return null;
16102
+ }
16103
+ const TITLE_OVERLAP_WARNING_THRESHOLD = .3;
16104
+ /**
15905
16105
  * Map a StoryPhase to the corresponding WgStoryStatus for wg_stories writes.
15906
16106
  * Returns null for PENDING (no write needed).
15907
16107
  */
@@ -15918,6 +16118,55 @@ function wgStatusForPhase(phase) {
15918
16118
  }
15919
16119
  }
15920
16120
  /**
16121
+ * Check whether `.substrate/project-profile.yaml` is stale relative to
16122
+ * the actual project structure.
16123
+ *
16124
+ * Returns an array of human-readable indicator strings. An empty array
16125
+ * means the profile appears current (or doesn't exist).
16126
+ *
16127
+ * Staleness indicators checked:
16128
+ * - Profile says `type: single` but `turbo.json` exists (should be monorepo)
16129
+ * - Profile has no Go language but `go.mod` exists
16130
+ * - Profile has no Python language but `pyproject.toml` exists
16131
+ * - Profile has no Rust language but `Cargo.toml` exists
16132
+ */
16133
+ function checkProfileStaleness(projectRoot) {
16134
+ const profilePath = join$1(projectRoot, ".substrate", "project-profile.yaml");
16135
+ if (!existsSync$1(profilePath)) return [];
16136
+ let profile;
16137
+ try {
16138
+ const raw = readFileSync$1(profilePath, "utf-8");
16139
+ profile = yaml.load(raw) ?? {};
16140
+ } catch {
16141
+ return [];
16142
+ }
16143
+ const project = profile.project;
16144
+ if (project === void 0) return [];
16145
+ const indicators = [];
16146
+ const declaredLanguages = new Set();
16147
+ if (typeof project.language === "string") declaredLanguages.add(project.language);
16148
+ if (Array.isArray(project.packages)) {
16149
+ for (const pkg of project.packages) if (typeof pkg.language === "string") declaredLanguages.add(pkg.language);
16150
+ }
16151
+ if (project.type === "single" && existsSync$1(join$1(projectRoot, "turbo.json"))) indicators.push("turbo.json exists but profile says type: single (should be monorepo)");
16152
+ const languageMarkers = [
16153
+ {
16154
+ file: "go.mod",
16155
+ language: "go"
16156
+ },
16157
+ {
16158
+ file: "pyproject.toml",
16159
+ language: "python"
16160
+ },
16161
+ {
16162
+ file: "Cargo.toml",
16163
+ language: "rust"
16164
+ }
16165
+ ];
16166
+ for (const marker of languageMarkers) if (existsSync$1(join$1(projectRoot, marker.file)) && !declaredLanguages.has(marker.language)) indicators.push(`${marker.file} exists but profile does not declare ${marker.language}`);
16167
+ return indicators;
16168
+ }
16169
+ /**
15921
16170
  * Factory function that creates an ImplementationOrchestrator instance.
15922
16171
  *
15923
16172
  * @param deps - Injected dependencies (db, pack, contextCompiler, dispatcher,
@@ -16274,8 +16523,7 @@ function createImplementationOrchestrator(deps) {
16274
16523
  for (const s of _stories.values()) if (s.phase === "COMPLETE" || s.phase === "ESCALATED") completed++;
16275
16524
  else if (s.phase === "PENDING") queued++;
16276
16525
  else active++;
16277
- const timeSinceProgress = Date.now() - _lastProgressTs;
16278
- if (timeSinceProgress >= HEARTBEAT_INTERVAL_MS) eventBus.emit("orchestrator:heartbeat", {
16526
+ eventBus.emit("orchestrator:heartbeat", {
16279
16527
  runId: config.pipelineRunId ?? "",
16280
16528
  activeDispatches: active,
16281
16529
  completedDispatches: completed,
@@ -16474,7 +16722,7 @@ function createImplementationOrchestrator(deps) {
16474
16722
  agent: "create-story",
16475
16723
  input_tokens: createResult.tokenUsage.input,
16476
16724
  output_tokens: createResult.tokenUsage.output,
16477
- cost_usd: 0,
16725
+ cost_usd: estimateDispatchCost(createResult.tokenUsage.input, createResult.tokenUsage.output),
16478
16726
  metadata: JSON.stringify({ storyKey })
16479
16727
  });
16480
16728
  } catch (tokenErr) {
@@ -16519,6 +16767,46 @@ function createImplementationOrchestrator(deps) {
16519
16767
  return;
16520
16768
  }
16521
16769
  storyFilePath = createResult.story_file;
16770
+ if (createResult.story_title) try {
16771
+ const epicId = storyKey.split("-")[0] ?? storyKey;
16772
+ const implDecisions = await getDecisionsByPhase(db, "implementation");
16773
+ let shardContent;
16774
+ const perStoryShard = implDecisions.find((d) => d.category === "epic-shard" && d.key === storyKey);
16775
+ if (perStoryShard?.value) shardContent = perStoryShard.value;
16776
+ else {
16777
+ const epicShard = implDecisions.find((d) => d.category === "epic-shard" && d.key === epicId);
16778
+ if (epicShard?.value) shardContent = extractStorySection(epicShard.value, storyKey) ?? epicShard.value;
16779
+ }
16780
+ if (shardContent) {
16781
+ const expectedTitle = extractExpectedStoryTitle(shardContent, storyKey);
16782
+ if (expectedTitle) {
16783
+ const overlap = computeTitleOverlap(expectedTitle, createResult.story_title);
16784
+ if (overlap < TITLE_OVERLAP_WARNING_THRESHOLD) {
16785
+ const msg = `Story title mismatch: expected "${expectedTitle}" but got "${createResult.story_title}" (word overlap: ${Math.round(overlap * 100)}%). This may indicate the create-story agent received truncated context.`;
16786
+ logger$27.warn({
16787
+ storyKey,
16788
+ expectedTitle,
16789
+ generatedTitle: createResult.story_title,
16790
+ overlap
16791
+ }, msg);
16792
+ eventBus.emit("orchestrator:story-warn", {
16793
+ storyKey,
16794
+ msg
16795
+ });
16796
+ } else logger$27.debug({
16797
+ storyKey,
16798
+ expectedTitle,
16799
+ generatedTitle: createResult.story_title,
16800
+ overlap
16801
+ }, "Story title validation passed");
16802
+ }
16803
+ }
16804
+ } catch (titleValidationErr) {
16805
+ logger$27.debug({
16806
+ storyKey,
16807
+ err: titleValidationErr
16808
+ }, "Story title validation skipped due to error");
16809
+ }
16522
16810
  } catch (err) {
16523
16811
  const errMsg = err instanceof Error ? err.message : String(err);
16524
16812
  endPhase(storyKey, "create-story");
@@ -16612,7 +16900,7 @@ function createImplementationOrchestrator(deps) {
16612
16900
  agent: "test-plan",
16613
16901
  input_tokens: testPlanTokenUsage.input,
16614
16902
  output_tokens: testPlanTokenUsage.output,
16615
- cost_usd: 0,
16903
+ cost_usd: estimateDispatchCost(testPlanTokenUsage.input, testPlanTokenUsage.output),
16616
16904
  metadata: JSON.stringify({ storyKey })
16617
16905
  });
16618
16906
  } catch (tokenErr) {
@@ -16727,7 +17015,7 @@ function createImplementationOrchestrator(deps) {
16727
17015
  agent: `batch-${batch.batchIndex}`,
16728
17016
  input_tokens: batchResult.tokenUsage.input,
16729
17017
  output_tokens: batchResult.tokenUsage.output,
16730
- cost_usd: 0,
17018
+ cost_usd: estimateDispatchCost(batchResult.tokenUsage.input, batchResult.tokenUsage.output),
16731
17019
  metadata: JSON.stringify({
16732
17020
  storyKey,
16733
17021
  batchIndex: batch.batchIndex,
@@ -16783,7 +17071,7 @@ function createImplementationOrchestrator(deps) {
16783
17071
  agent: "dev-story",
16784
17072
  input_tokens: devResult.tokenUsage.input,
16785
17073
  output_tokens: devResult.tokenUsage.output,
16786
- cost_usd: 0,
17074
+ cost_usd: estimateDispatchCost(devResult.tokenUsage.input, devResult.tokenUsage.output),
16787
17075
  metadata: JSON.stringify({ storyKey })
16788
17076
  });
16789
17077
  } catch (tokenErr) {
@@ -17019,7 +17307,7 @@ function createImplementationOrchestrator(deps) {
17019
17307
  agent: useBatchedReview ? "code-review-batched" : "code-review",
17020
17308
  input_tokens: reviewResult.tokenUsage.input,
17021
17309
  output_tokens: reviewResult.tokenUsage.output,
17022
- cost_usd: 0,
17310
+ cost_usd: estimateDispatchCost(reviewResult.tokenUsage.input, reviewResult.tokenUsage.output),
17023
17311
  metadata: JSON.stringify({
17024
17312
  storyKey,
17025
17313
  reviewCycle: reviewCycles
@@ -17041,6 +17329,28 @@ function createImplementationOrchestrator(deps) {
17041
17329
  }, "Phantom review detected (0 issues + error) — retrying review once");
17042
17330
  continue;
17043
17331
  }
17332
+ if (isPhantomReview && timeoutRetried) {
17333
+ logger$27.warn({
17334
+ storyKey,
17335
+ reviewCycles,
17336
+ error: reviewResult.error
17337
+ }, "Consecutive review timeouts detected (original + retry both failed) — escalating immediately");
17338
+ endPhase(storyKey, "code-review");
17339
+ updateStory(storyKey, {
17340
+ phase: "ESCALATED",
17341
+ error: "consecutive-review-timeouts",
17342
+ completedAt: new Date().toISOString()
17343
+ });
17344
+ await writeStoryMetricsBestEffort(storyKey, "escalated", reviewCycles + 1);
17345
+ await emitEscalation({
17346
+ storyKey,
17347
+ lastVerdict: "consecutive-review-timeouts",
17348
+ reviewCycles: reviewCycles + 1,
17349
+ issues: ["Review dispatch failed twice consecutively (original + phantom-retry). Likely resource-constrained or diff too large for reviewer."]
17350
+ });
17351
+ await persistState();
17352
+ return;
17353
+ }
17044
17354
  verdict = reviewResult.verdict;
17045
17355
  issueList = reviewResult.issue_list ?? [];
17046
17356
  if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
@@ -17387,6 +17697,20 @@ function createImplementationOrchestrator(deps) {
17387
17697
  const constraints = decisions.filter((d) => d.category === "architecture");
17388
17698
  archConstraints = constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
17389
17699
  } catch {}
17700
+ let gitDiffContent = "";
17701
+ try {
17702
+ const diffFiles = checkGitDiffFiles(projectRoot ?? process.cwd());
17703
+ if (diffFiles.length > 0) gitDiffContent = execSync(`git diff HEAD -- ${diffFiles.map((f) => `"${f}"`).join(" ")}`, {
17704
+ cwd: projectRoot ?? process.cwd(),
17705
+ encoding: "utf-8",
17706
+ timeout: 1e4,
17707
+ stdio: [
17708
+ "ignore",
17709
+ "pipe",
17710
+ "pipe"
17711
+ ]
17712
+ }).trim();
17713
+ } catch {}
17390
17714
  const sections = isMajorRework ? [
17391
17715
  {
17392
17716
  name: "story_content",
@@ -17405,7 +17729,7 @@ function createImplementationOrchestrator(deps) {
17405
17729
  },
17406
17730
  {
17407
17731
  name: "git_diff",
17408
- content: "",
17732
+ content: gitDiffContent,
17409
17733
  priority: "optional"
17410
17734
  }
17411
17735
  ] : (() => {
@@ -17836,6 +18160,19 @@ function createImplementationOrchestrator(deps) {
17836
18160
  } catch (err) {
17837
18161
  logger$27.error({ err }, "Post-sprint contract verification threw an error — skipping");
17838
18162
  }
18163
+ if (projectRoot !== void 0) try {
18164
+ const indicators = checkProfileStaleness(projectRoot);
18165
+ if (indicators.length > 0) {
18166
+ const message = "Project profile may be outdated — consider running `substrate init --force` to re-detect";
18167
+ eventBus.emit("pipeline:profile-stale", {
18168
+ message,
18169
+ indicators
18170
+ });
18171
+ logger$27.warn({ indicators }, message);
18172
+ }
18173
+ } catch (err) {
18174
+ logger$27.debug({ err }, "Profile staleness check failed (best-effort)");
18175
+ }
17839
18176
  let completed = 0;
17840
18177
  let escalated = 0;
17841
18178
  let failed = 0;
@@ -17966,14 +18303,14 @@ async function resolveStoryKeys(db, projectRoot, opts) {
17966
18303
  function parseStoryKeysFromEpics(content) {
17967
18304
  if (content.length === 0) return [];
17968
18305
  const keys = new Set();
17969
- const explicitKeyPattern = /\*\*Story key:\*\*\s*`?(\d+-\d+)(?:-[^`\s]*)?`?/g;
18306
+ const explicitKeyPattern = /\*\*Story key:\*\*\s*`?([A-Za-z0-9]+-[A-Za-z0-9]+)(?:-[^`\s]*)?`?/g;
17970
18307
  let match$1;
17971
18308
  while ((match$1 = explicitKeyPattern.exec(content)) !== null) if (match$1[1] !== void 0) keys.add(match$1[1]);
17972
- const headingPattern = /^###\s+Story\s+(\d+)[.\-](\d+)/gm;
18309
+ const headingPattern = /^###\s+Story\s+([A-Za-z0-9]+)[.\-]([A-Za-z0-9]+)/gm;
17973
18310
  while ((match$1 = headingPattern.exec(content)) !== null) if (match$1[1] !== void 0 && match$1[2] !== void 0) keys.add(`${match$1[1]}-${match$1[2]}`);
17974
- const inlineStoryPattern = /Story\s+(\d+)-(\d+)[:\s]/g;
18311
+ const inlineStoryPattern = /Story\s+([A-Za-z0-9]+)-([A-Za-z0-9]+)[:\s]/g;
17975
18312
  while ((match$1 = inlineStoryPattern.exec(content)) !== null) if (match$1[1] !== void 0 && match$1[2] !== void 0) keys.add(`${match$1[1]}-${match$1[2]}`);
17976
- const filePathPattern = /_bmad-output\/implementation-artifacts\/(\d+-\d+)-/g;
18313
+ const filePathPattern = /_bmad-output\/implementation-artifacts\/([A-Za-z0-9]+-[A-Za-z0-9]+)-/g;
17977
18314
  while ((match$1 = filePathPattern.exec(content)) !== null) if (match$1[1] !== void 0) keys.add(match$1[1]);
17978
18315
  return sortStoryKeys(Array.from(keys));
17979
18316
  }
@@ -18019,6 +18356,12 @@ function discoverPendingStoryKeys(projectRoot, epicNumber) {
18019
18356
  allKeys = sortStoryKeys([...new Set(allKeys)]);
18020
18357
  }
18021
18358
  }
18359
+ const sprintKeys = parseStoryKeysFromSprintStatus(projectRoot);
18360
+ if (sprintKeys.length > 0) {
18361
+ const merged = new Set(allKeys);
18362
+ for (const k of sprintKeys) merged.add(k);
18363
+ allKeys = sortStoryKeys([...merged]);
18364
+ }
18022
18365
  if (allKeys.length === 0) return [];
18023
18366
  const existingKeys = collectExistingStoryKeys(projectRoot);
18024
18367
  return allKeys.filter((k) => !existingKeys.has(k));
@@ -18069,7 +18412,7 @@ function collectExistingStoryKeys(projectRoot) {
18069
18412
  } catch {
18070
18413
  return existing;
18071
18414
  }
18072
- const filePattern = /^(\d+-\d+)-/;
18415
+ const filePattern = /^([A-Za-z0-9]+-[A-Za-z0-9]+)-/;
18073
18416
  for (const entry of entries) {
18074
18417
  if (!entry.endsWith(".md")) continue;
18075
18418
  const m = filePattern.exec(entry);
@@ -18078,6 +18421,33 @@ function collectExistingStoryKeys(projectRoot) {
18078
18421
  return existing;
18079
18422
  }
18080
18423
  /**
18424
+ * Parse story keys from sprint-status.yaml.
18425
+ * Reads the development_status map and extracts keys that match the
18426
+ * alphanumeric story key pattern (e.g., 1-1a, NEW-26, E5-accessibility).
18427
+ * Filters out epic status entries (epic-N) and retrospective entries.
18428
+ */
18429
+ function parseStoryKeysFromSprintStatus(projectRoot) {
18430
+ const candidates = [join$1(projectRoot, "_bmad-output", "implementation-artifacts", "sprint-status.yaml"), join$1(projectRoot, "_bmad-output", "sprint-status.yaml")];
18431
+ const statusPath = candidates.find((p) => existsSync$1(p));
18432
+ if (!statusPath) return [];
18433
+ try {
18434
+ const content = readFileSync$1(statusPath, "utf-8");
18435
+ const keys = [];
18436
+ const linePattern = /^\s{2}([A-Za-z0-9]+-[A-Za-z0-9]+(?:-[A-Za-z0-9-]*)?)\s*:/gm;
18437
+ let match$1;
18438
+ while ((match$1 = linePattern.exec(content)) !== null) {
18439
+ const fullKey = match$1[1];
18440
+ if (/^epic-\d+$/.test(fullKey)) continue;
18441
+ if (fullKey.includes("retrospective")) continue;
18442
+ const segments = fullKey.split("-");
18443
+ if (segments.length >= 2) keys.push(`${segments[0]}-${segments[1]}`);
18444
+ }
18445
+ return [...new Set(keys)];
18446
+ } catch {
18447
+ return [];
18448
+ }
18449
+ }
18450
+ /**
18081
18451
  * Collect story keys already completed in previous pipeline runs.
18082
18452
  * Scans pipeline_runs with status='completed' and extracts story keys
18083
18453
  * with phase='COMPLETE' from their token_usage_json state.
@@ -18096,16 +18466,26 @@ async function getCompletedStoryKeys(db) {
18096
18466
  return completed;
18097
18467
  }
18098
18468
  /**
18099
- * Sort story keys numerically by epic number (primary) then story number (secondary).
18100
- * E.g. ["10-1", "1-2", "2-1"] ["1-2", "2-1", "10-1"]
18469
+ * Sort story keys: numeric keys first (by epic then story number),
18470
+ * then alphabetic-prefix keys (NEW-*, E-*) sorted lexicographically.
18471
+ * E.g. ["10-1", "1-2a", "1-2", "NEW-26", "E5-acc"] → ["1-2", "1-2a", "10-1", "E5-acc", "NEW-26"]
18101
18472
  */
18102
18473
  function sortStoryKeys(keys) {
18103
18474
  return keys.slice().sort((a, b) => {
18104
- const [ae, as_] = a.split("-").map(Number);
18105
- const [be, bs] = b.split("-").map(Number);
18106
- const epicDiff = (ae ?? 0) - (be ?? 0);
18107
- if (epicDiff !== 0) return epicDiff;
18108
- return (as_ ?? 0) - (bs ?? 0);
18475
+ const aParts = a.split("-");
18476
+ const bParts = b.split("-");
18477
+ const aNum = Number(aParts[0]);
18478
+ const bNum = Number(bParts[0]);
18479
+ if (!isNaN(aNum) && !isNaN(bNum)) {
18480
+ if (aNum !== bNum) return aNum - bNum;
18481
+ const aStory = Number(aParts[1]);
18482
+ const bStory = Number(bParts[1]);
18483
+ if (!isNaN(aStory) && !isNaN(bStory) && aStory !== bStory) return aStory - bStory;
18484
+ return (aParts[1] ?? "").localeCompare(bParts[1] ?? "");
18485
+ }
18486
+ if (!isNaN(aNum)) return -1;
18487
+ if (!isNaN(bNum)) return 1;
18488
+ return a.localeCompare(b);
18109
18489
  });
18110
18490
  }
18111
18491
 
@@ -22114,7 +22494,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
22114
22494
  }
22115
22495
  }
22116
22496
  async function runRunAction(options) {
22117
- const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, epic: epicNumber, dryRun, registry: injectedRegistry } = options;
22497
+ const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, epic: epicNumber, dryRun, maxReviewCycles = 2, registry: injectedRegistry } = options;
22118
22498
  if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
22119
22499
  const errorMsg = `Invalid phase '${startPhase}'. Valid phases: ${VALID_PHASES.join(", ")}`;
22120
22500
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -22196,7 +22576,7 @@ async function runRunAction(options) {
22196
22576
  if (storiesArg !== void 0 && storiesArg !== "") {
22197
22577
  parsedStoryKeys = storiesArg.split(",").map((k) => k.trim()).filter((k) => k.length > 0);
22198
22578
  for (const key of parsedStoryKeys) if (!validateStoryKey(key)) {
22199
- const errorMsg = `Story key '${key}' is not a valid format. Expected: <epic>-<story> (e.g., 10-1)`;
22579
+ const errorMsg = `Story key '${key}' is not a valid format. Expected: <epic>-<story> (e.g., 10-1, 1-1a, NEW-26)`;
22200
22580
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
22201
22581
  else process.stderr.write(`Error: ${errorMsg}\n`);
22202
22582
  return 1;
@@ -22252,7 +22632,8 @@ async function runRunAction(options) {
22252
22632
  ...telemetryEnabled ? {
22253
22633
  telemetryEnabled: true,
22254
22634
  telemetryPort
22255
- } : {}
22635
+ } : {},
22636
+ maxReviewCycles
22256
22637
  });
22257
22638
  let storyKeys = [...parsedStoryKeys];
22258
22639
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
@@ -22754,7 +23135,7 @@ async function runRunAction(options) {
22754
23135
  type: "pipeline:pre-flight-failure",
22755
23136
  ts: new Date().toISOString(),
22756
23137
  exitCode: payload.exitCode,
22757
- output: payload.output
23138
+ output: payload.output + "\nTip: Use --skip-preflight to bypass, or check your build command in .substrate/project-profile.yaml"
22758
23139
  });
22759
23140
  });
22760
23141
  eventBus.on("pipeline:contract-mismatch", (payload) => {
@@ -22777,6 +23158,14 @@ async function runRunAction(options) {
22777
23158
  verdict: payload.verdict
22778
23159
  });
22779
23160
  });
23161
+ eventBus.on("pipeline:profile-stale", (payload) => {
23162
+ ndjsonEmitter.emit({
23163
+ type: "pipeline:profile-stale",
23164
+ ts: new Date().toISOString(),
23165
+ message: payload.message,
23166
+ indicators: payload.indicators
23167
+ });
23168
+ });
22780
23169
  }
22781
23170
  const ingestionServer = telemetryEnabled ? new IngestionServer({ port: telemetryPort }) : void 0;
22782
23171
  if (telemetryPersistence !== void 0) {
@@ -22809,7 +23198,7 @@ async function runRunAction(options) {
22809
23198
  eventBus,
22810
23199
  config: {
22811
23200
  maxConcurrency: concurrency,
22812
- maxReviewCycles: 2,
23201
+ maxReviewCycles,
22813
23202
  pipelineRunId: pipelineRun.id,
22814
23203
  enableHeartbeat: eventsFlag === true,
22815
23204
  skipPreflight: skipPreflight === true
@@ -22928,7 +23317,7 @@ async function runRunAction(options) {
22928
23317
  }
22929
23318
  }
22930
23319
  async function runFullPipeline(options) {
22931
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort } = options;
23320
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, maxReviewCycles = 2, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort } = options;
22932
23321
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
22933
23322
  const adapter = createDatabaseAdapter({
22934
23323
  backend: "auto",
@@ -23148,7 +23537,7 @@ async function runFullPipeline(options) {
23148
23537
  eventBus,
23149
23538
  config: {
23150
23539
  maxConcurrency: concurrency,
23151
- maxReviewCycles: 2,
23540
+ maxReviewCycles,
23152
23541
  pipelineRunId: runId,
23153
23542
  skipPreflight: skipPreflight === true
23154
23543
  },
@@ -23257,7 +23646,7 @@ async function runFullPipeline(options) {
23257
23646
  }
23258
23647
  }
23259
23648
  function registerRunCommand(program, _version = "0.0.0", projectRoot = process.cwd(), registry) {
23260
- program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").action(async (opts) => {
23649
+ program.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--epic <n>", "Scope story discovery to a single epic number (e.g., 27)", (v) => parseInt(v, 10)).option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").option("--research", "Enable the research phase even if not set in the pack manifest").option("--skip-research", "Skip the research phase even if enabled in the pack manifest").option("--skip-preflight", "Skip the pre-flight build check (escape hatch for known-broken projects)").option("--max-review-cycles <n>", "Maximum review cycles per story (default: 2)", (v) => parseInt(v, 10), 2).option("--dry-run", "Preview routing and repo-map injection without dispatching (Story 28-9)").action(async (opts) => {
23261
23650
  if (opts.helpAgent) {
23262
23651
  process.exitCode = await runHelpAgent();
23263
23652
  return;
@@ -23292,6 +23681,7 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
23292
23681
  research: opts.research,
23293
23682
  skipResearch: opts.skipResearch,
23294
23683
  skipPreflight: opts.skipPreflight,
23684
+ maxReviewCycles: opts.maxReviewCycles,
23295
23685
  dryRun: opts.dryRun,
23296
23686
  registry
23297
23687
  });
@@ -23301,4 +23691,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
23301
23691
 
23302
23692
  //#endregion
23303
23693
  export { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltClient, DoltNotInstalled, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, FileStateStore, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SUBSTRATE_OWNED_SETTINGS_KEYS, SymbolParser, VALID_PHASES, WorkGraphRepository, buildPipelineStatusOutput, checkDoltInstalled, createConfigSystem, createContextCompiler, createDatabaseAdapter, createDispatcher, createDoltClient, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStateStore, createStopAfterGate, createTelemetryAdvisor, detectCycles, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, initSchema, initializeDolt, isSyncAdapter, parseDbTimestampAsUtc, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
23304
- //# sourceMappingURL=run-IDOmPys1.js.map
23694
+ //# sourceMappingURL=run-GwwiCYXX.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",