substrate-ai 0.20.12 → 0.20.13

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.
@@ -4210,6 +4210,12 @@ const RunManifestSchema = z.object({
4210
4210
  run_id: z.string(),
4211
4211
  cli_flags: CliFlagsSchema.transform((v) => v),
4212
4212
  story_scope: z.array(z.string()),
4213
+ run_status: z.enum([
4214
+ "running",
4215
+ "completed",
4216
+ "failed",
4217
+ "stopped"
4218
+ ]).optional(),
4213
4219
  supervisor_pid: z.number().nullable(),
4214
4220
  supervisor_session_id: z.string().nullable(),
4215
4221
  per_story_state: z.record(z.string(), PerStoryStateSchema),
@@ -4219,6 +4225,8 @@ const RunManifestSchema = z.object({
4219
4225
  run_total: 0
4220
4226
  }),
4221
4227
  pending_proposals: z.array(ProposalSchema),
4228
+ stopped_reason: z.string().optional(),
4229
+ stopped_at: z.string().optional(),
4222
4230
  generation: z.number().int().nonnegative(),
4223
4231
  created_at: z.string(),
4224
4232
  updated_at: z.string()
@@ -4560,6 +4568,55 @@ var RunManifest = class RunManifest {
4560
4568
  /**
4561
4569
  * Raw implementation — must only be called from within `_enqueue`.
4562
4570
  */
4571
+ async _patchRunStatusImpl(updates) {
4572
+ let existingData;
4573
+ try {
4574
+ const read = await RunManifest.read(this.runId, this.baseDir, this.doltAdapter);
4575
+ const { generation: _gen, updated_at: _ts,...rest } = read;
4576
+ existingData = rest;
4577
+ } catch {
4578
+ const now = new Date().toISOString();
4579
+ existingData = {
4580
+ run_id: this.runId,
4581
+ cli_flags: {},
4582
+ story_scope: [],
4583
+ supervisor_pid: null,
4584
+ supervisor_session_id: null,
4585
+ per_story_state: {},
4586
+ recovery_history: [],
4587
+ cost_accumulation: {
4588
+ per_story: {},
4589
+ run_total: 0
4590
+ },
4591
+ pending_proposals: [],
4592
+ created_at: now
4593
+ };
4594
+ }
4595
+ const merged = { ...existingData };
4596
+ if (updates.run_status !== void 0) merged.run_status = updates.run_status;
4597
+ if (updates.stopped_reason !== void 0) merged.stopped_reason = updates.stopped_reason;
4598
+ if (updates.stopped_at !== void 0) merged.stopped_at = updates.stopped_at;
4599
+ await this._writeImpl(merged);
4600
+ }
4601
+ /**
4602
+ * Atomically update the run-level status fields in the manifest.
4603
+ *
4604
+ * Reads the current manifest (or creates a minimal default if absent),
4605
+ * merges the provided status updates at the top level, and writes the
4606
+ * result atomically via a single `write()` call.
4607
+ *
4608
+ * Enqueues the operation via `_enqueue` so concurrent calls are serialized
4609
+ * (preserves the single-writer guarantee from Epic 57-1).
4610
+ * Non-fatal: callers MUST wrap in `.catch((err) => logger.warn(...))`.
4611
+ *
4612
+ * @param updates - Top-level status fields to merge (run_status, stopped_reason, stopped_at)
4613
+ */
4614
+ async patchRunStatus(updates) {
4615
+ return this._enqueue(() => this._patchRunStatusImpl(updates));
4616
+ }
4617
+ /**
4618
+ * Raw implementation — must only be called from within `_enqueue`.
4619
+ */
4563
4620
  async _appendRecoveryEntryImpl(entry) {
4564
4621
  let existingData;
4565
4622
  try {
@@ -5477,4 +5534,4 @@ function registerHealthCommand(program, _version = "0.0.0", projectRoot = proces
5477
5534
 
5478
5535
  //#endregion
5479
5536
  export { BMAD_BASELINE_TOKENS_FULL, DEFAULT_STALL_THRESHOLD_SECONDS, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN$1 as STORY_KEY_PATTERN, SUBSTRATE_OWNED_SETTINGS_KEYS, SupervisorLock, VALID_PHASES, WorkGraphRepository, ZERO_FINDING_COUNTS, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter$1 as createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, createStateStore, detectCycles, extractTargetFilesFromStoryContent, findPackageRoot, formatOutput, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, inspectProcessTree, isOrchestratorProcessLine, parseDbTimestampAsUtc, registerHealthCommand, renderFindings, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveGraphPath, resolveMainRepoRoot, resolveRunManifest, rollupFindingCounts, runHealthAction, validateStoryKey };
5480
- //# sourceMappingURL=health-0jqPFBEL.js.map
5537
+ //# sourceMappingURL=health-CxBbduMn.js.map
@@ -1,4 +1,4 @@
1
- import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-0jqPFBEL.js";
1
+ import { DEFAULT_STALL_THRESHOLD_SECONDS, getAllDescendantPids, getAutoHealthData, inspectProcessTree, isOrchestratorProcessLine, registerHealthCommand, runHealthAction } from "./health-CxBbduMn.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./dist-CqtWS9wF.js";
4
4
  import "./decisions-C0pz9Clx.js";
package/dist/index.d.ts CHANGED
@@ -2286,6 +2286,20 @@ interface OrchestratorEvents {
2286
2286
  /** Retry attempt number (always 2 — first retry after initial timeout) */
2287
2287
  attempt: number;
2288
2288
  };
2289
+ /**
2290
+ * Emitted when an existing story artifact's stored source-AC hash differs
2291
+ * from the current source epic's AC hash, or when the artifact carries no
2292
+ * hash at all (legacy artifact). Causes the orchestrator to re-run
2293
+ * create-story instead of reusing the stale artifact.
2294
+ */
2295
+ 'story:ac-source-drift': {
2296
+ /** Story key whose artifact is stale */
2297
+ storyKey: string;
2298
+ /** Hash stored in the artifact's HTML comment (null if absent — legacy artifact) */
2299
+ storedHash: string | null;
2300
+ /** Hash computed from the current source epic's AC section */
2301
+ currentHash: string;
2302
+ };
2289
2303
  }
2290
2304
 
2291
2305
  //#endregion
@@ -1,8 +1,8 @@
1
- import "./health-0jqPFBEL.js";
1
+ import "./health-CxBbduMn.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-CqtWS9wF.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-B6qbsy-F.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-D5VAkItq.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-0jqPFBEL.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltMergeConflict, FileStateStore, FindingsInjector, RunManifest, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, applyConfigToGraph, buildPipelineStatusOutput, createDatabaseAdapter, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectCycles, extractTargetFilesFromStoryContent, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, renderFindings, resolveGraphPath, resolveMainRepoRoot, validateStoryKey } from "./health-CxBbduMn.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
4
  import { ADVISORY_NOTES, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, 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, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-CqtWS9wF.js";
@@ -5676,6 +5676,21 @@ function getTokenCeiling(workflowType, tokenCeilings) {
5676
5676
  //#region src/modules/compiled-workflows/create-story.ts
5677
5677
  const logger$18 = createLogger("compiled-workflows:create-story");
5678
5678
  /**
5679
+ * Compute a hex SHA-256 of the normalized source AC section text.
5680
+ *
5681
+ * Normalization (minimal — avoids spurious regen from editor whitespace noise):
5682
+ * 1. Split on `\n`
5683
+ * 2. Strip trailing whitespace from each line (`.trimEnd()`)
5684
+ * 3. Rejoin with `\n`
5685
+ * 4. Trim the whole result (`.trim()`)
5686
+ *
5687
+ * Pure function: no I/O, no side effects. Safe to call from tests with zero setup.
5688
+ */
5689
+ function hashSourceAcSection(section) {
5690
+ const normalized = section.split("\n").map((line) => line.trimEnd()).join("\n").trim();
5691
+ return createHash("sha256").update(normalized, "utf8").digest("hex");
5692
+ }
5693
+ /**
5679
5694
  * Execute the compiled create-story workflow.
5680
5695
  *
5681
5696
  * Steps:
@@ -5693,7 +5708,7 @@ const logger$18 = createLogger("compiled-workflows:create-story");
5693
5708
  * @returns Promise resolving to CreateStoryResult
5694
5709
  */
5695
5710
  async function runCreateStory(deps, params) {
5696
- const { epicId, storyKey, pipelineRunId } = params;
5711
+ const { epicId, storyKey, pipelineRunId, source_ac_hash } = params;
5697
5712
  logger$18.debug({
5698
5713
  epicId,
5699
5714
  storyKey,
@@ -5764,7 +5779,12 @@ async function runCreateStory(deps, params) {
5764
5779
  name: "story_template",
5765
5780
  content: storyTemplateContent,
5766
5781
  priority: "important"
5767
- }
5782
+ },
5783
+ ...source_ac_hash !== void 0 ? [{
5784
+ name: "source_ac_hash",
5785
+ content: source_ac_hash,
5786
+ priority: "required"
5787
+ }] : []
5768
5788
  ], TOKEN_CEILING);
5769
5789
  logger$18.debug({
5770
5790
  tokenCount,
@@ -11367,6 +11387,10 @@ function createImplementationOrchestrator(deps) {
11367
11387
  let _costWarningEmitted = false;
11368
11388
  let _budgetExhausted = false;
11369
11389
  let _otlpEndpoint;
11390
+ let _shutdownRequested = false;
11391
+ let _inFlightCount = 0;
11392
+ let _drainResolve = null;
11393
+ let _drainPromise = Promise.resolve();
11370
11394
  const verificationStore = new VerificationStore();
11371
11395
  const verificationPipeline = createDefaultVerificationPipeline(toSdlcEventBus(eventBus));
11372
11396
  const _stateStoreCache = new Map();
@@ -12004,6 +12028,7 @@ function createImplementationOrchestrator(deps) {
12004
12028
  startedAt: new Date().toISOString()
12005
12029
  });
12006
12030
  let storyFilePath;
12031
+ let sourceAcHash;
12007
12032
  const artifactsDir = projectRoot ? join$1(projectRoot, "_bmad-output", "implementation-artifacts") : void 0;
12008
12033
  if (artifactsDir && existsSync(artifactsDir)) try {
12009
12034
  const files = readdirSync(artifactsDir);
@@ -12017,22 +12042,52 @@ function createImplementationOrchestrator(deps) {
12017
12042
  reason: validation.reason
12018
12043
  }, `Existing story file for ${storyKey} is invalid (${validation.reason}) — re-creating`);
12019
12044
  else {
12020
- storyFilePath = candidatePath;
12021
- logger$24.info({
12022
- storyKey,
12023
- storyFilePath
12024
- }, "Found existing story file — skipping create-story");
12025
- endPhase(storyKey, "create-story");
12026
- eventBus.emit("orchestrator:story-phase-complete", {
12027
- storyKey,
12028
- phase: "IN_STORY_CREATION",
12029
- result: {
12030
- result: "success",
12031
- story_file: storyFilePath,
12032
- story_key: storyKey
12045
+ let isDrift = false;
12046
+ try {
12047
+ const epicsPath = projectRoot ? findEpicsFile(projectRoot) : void 0;
12048
+ if (epicsPath !== void 0) {
12049
+ const epicContent = readFileSync(epicsPath, "utf-8");
12050
+ const sourceSection = extractStorySection(epicContent, storyKey);
12051
+ if (sourceSection != null) {
12052
+ const currentHash = hashSourceAcSection(sourceSection);
12053
+ sourceAcHash = currentHash;
12054
+ const artifactContent = await readFile$1(candidatePath, "utf-8");
12055
+ const hashMatch = /<!--\s*source-ac-hash:\s*([0-9a-f]{64})\s*-->/.exec(artifactContent);
12056
+ const storedHash = hashMatch?.[1] ?? null;
12057
+ if (storedHash !== currentHash) {
12058
+ isDrift = true;
12059
+ eventBus.emit("story:ac-source-drift", {
12060
+ storyKey,
12061
+ storedHash,
12062
+ currentHash
12063
+ });
12064
+ logger$24.info({
12065
+ storyKey,
12066
+ storedHash,
12067
+ currentHash
12068
+ }, `[orchestrator] story ${storyKey}: source AC hash mismatch, regenerating story artifact`);
12069
+ }
12070
+ }
12033
12071
  }
12034
- });
12035
- await persistState();
12072
+ } catch {}
12073
+ if (!isDrift) {
12074
+ storyFilePath = candidatePath;
12075
+ logger$24.info({
12076
+ storyKey,
12077
+ storyFilePath
12078
+ }, "Found existing story file — skipping create-story");
12079
+ endPhase(storyKey, "create-story");
12080
+ eventBus.emit("orchestrator:story-phase-complete", {
12081
+ storyKey,
12082
+ phase: "IN_STORY_CREATION",
12083
+ result: {
12084
+ result: "success",
12085
+ story_file: storyFilePath,
12086
+ story_key: storyKey
12087
+ }
12088
+ });
12089
+ await persistState();
12090
+ }
12036
12091
  }
12037
12092
  }
12038
12093
  } catch {}
@@ -12069,7 +12124,8 @@ function createImplementationOrchestrator(deps) {
12069
12124
  }, {
12070
12125
  epicId: storyKey.split("-")[0] ?? storyKey,
12071
12126
  storyKey,
12072
- pipelineRunId: config.pipelineRunId
12127
+ pipelineRunId: config.pipelineRunId,
12128
+ source_ac_hash: sourceAcHash
12073
12129
  });
12074
12130
  endPhase(storyKey, "create-story");
12075
12131
  eventBus.emit("orchestrator:story-phase-complete", {
@@ -13898,6 +13954,10 @@ function createImplementationOrchestrator(deps) {
13898
13954
  async function processConflictGroup(group) {
13899
13955
  const completedStoryKeys = [];
13900
13956
  for (const storyKey of group) {
13957
+ if (_shutdownRequested) {
13958
+ logger$24.info({ storyKey }, "shutdown requested — skipping dispatch");
13959
+ return;
13960
+ }
13901
13961
  if (runManifest !== null && runManifest !== void 0) try {
13902
13962
  const manifestData = await runManifest.read();
13903
13963
  const ceiling = manifestData.cli_flags.cost_ceiling;
@@ -13956,11 +14016,15 @@ function createImplementationOrchestrator(deps) {
13956
14016
  const running = new Set();
13957
14017
  function enqueue() {
13958
14018
  if (_budgetExhausted) return;
14019
+ if (_shutdownRequested) return;
13959
14020
  const group = queue.shift();
13960
14021
  if (group === void 0) return;
14022
+ _inFlightCount++;
13961
14023
  const p = processConflictGroup(group).finally(() => {
13962
14024
  running.delete(p);
13963
- while (running.size < maxConcurrency && queue.length > 0) enqueue();
14025
+ _inFlightCount--;
14026
+ if (_inFlightCount === 0 && _shutdownRequested) _drainResolve?.();
14027
+ while (running.size < maxConcurrency && queue.length > 0 && !_shutdownRequested && !_budgetExhausted) enqueue();
13964
14028
  });
13965
14029
  running.add(p);
13966
14030
  if (running.size > _maxConcurrentActual) _maxConcurrentActual = running.size;
@@ -13969,6 +14033,47 @@ function createImplementationOrchestrator(deps) {
13969
14033
  for (let i = 0; i < initial; i++) enqueue();
13970
14034
  while (running.size > 0) await Promise.race(running);
13971
14035
  }
14036
+ /**
14037
+ * Gracefully shut down the pipeline in response to a POSIX signal.
14038
+ *
14039
+ * 1. Sets the in-memory `_shutdownRequested` flag so the dispatch loop stops
14040
+ * scheduling new stories.
14041
+ * 2. Awaits any in-flight dispatches for up to `config.shutdownGracePeriodMs` ms
14042
+ * (default 5000).
14043
+ * 3. Writes `run_status: 'stopped'` and `stopped_reason` to the run manifest.
14044
+ * 4. Best-effort: updates `pipeline_runs.status = 'stopped'` in Dolt and
14045
+ * transitions active wg_stories to 'cancelled'.
14046
+ * 5. Calls `process.exit(130)` for SIGINT or `process.exit(143)` for SIGTERM.
14047
+ */
14048
+ async function shutdownGracefully(reason, signal) {
14049
+ if (_shutdownRequested) return;
14050
+ _shutdownRequested = true;
14051
+ logger$24.info({
14052
+ reason,
14053
+ signal
14054
+ }, "Graceful shutdown initiated");
14055
+ const gracePeriod = config.shutdownGracePeriodMs ?? 5e3;
14056
+ if (_inFlightCount === 0) _drainResolve?.();
14057
+ await Promise.race([_drainPromise, new Promise((resolve$6) => setTimeout(resolve$6, gracePeriod))]);
14058
+ if (runManifest !== null && runManifest !== void 0) await runManifest.patchRunStatus({
14059
+ run_status: "stopped",
14060
+ stopped_reason: reason,
14061
+ stopped_at: new Date().toISOString()
14062
+ }).catch((err) => logger$24.warn({ err }, "patchRunStatus failed during shutdown (best-effort)"));
14063
+ if (config.pipelineRunId !== void 0) try {
14064
+ await updatePipelineRun(db, config.pipelineRunId, { status: "stopped" });
14065
+ } catch (err) {
14066
+ logger$24.warn({ err }, "updatePipelineRun(stopped) failed during shutdown (best-effort)");
14067
+ }
14068
+ for (const [storyKey, storyState] of _stories) {
14069
+ const isActive = storyState.phase === "PENDING" || storyState.phase === "IN_STORY_CREATION" || storyState.phase === "IN_TEST_PLANNING" || storyState.phase === "IN_DEV" || storyState.phase === "IN_REVIEW" || storyState.phase === "NEEDS_FIXES" || storyState.phase === "CHECKPOINT";
14070
+ if (isActive) wgRepo.updateStoryStatus(storyKey, "cancelled").catch((err) => logger$24.warn({
14071
+ err,
14072
+ storyKey
14073
+ }, "updateStoryStatus(cancelled) failed during shutdown (best-effort)"));
14074
+ }
14075
+ process.exit(signal === "SIGINT" ? 130 : 143);
14076
+ }
13972
14077
  async function run(storyKeys) {
13973
14078
  if (_state === "RUNNING" || _state === "PAUSED") {
13974
14079
  logger$24.warn({ state: _state }, "run() called while orchestrator is already running or paused — ignoring");
@@ -13980,6 +14085,20 @@ function createImplementationOrchestrator(deps) {
13980
14085
  }
13981
14086
  _state = "RUNNING";
13982
14087
  _startedAt = new Date().toISOString();
14088
+ _shutdownRequested = false;
14089
+ _inFlightCount = 0;
14090
+ _drainResolve = null;
14091
+ _drainPromise = new Promise((resolve$6) => {
14092
+ _drainResolve = resolve$6;
14093
+ });
14094
+ const sigtermHandler = () => {
14095
+ shutdownGracefully("killed_by_user", "SIGTERM");
14096
+ };
14097
+ const sigintHandler = () => {
14098
+ shutdownGracefully("killed_by_user", "SIGINT");
14099
+ };
14100
+ process.on("SIGTERM", sigtermHandler);
14101
+ process.on("SIGINT", sigintHandler);
13983
14102
  for (const key of storyKeys) {
13984
14103
  const pendingState = {
13985
14104
  phase: "PENDING",
@@ -14264,6 +14383,8 @@ function createImplementationOrchestrator(deps) {
14264
14383
  await persistState();
14265
14384
  return getStatus();
14266
14385
  } finally {
14386
+ process.off("SIGTERM", sigtermHandler);
14387
+ process.off("SIGINT", sigintHandler);
14267
14388
  if (stateStore !== void 0) await stateStore.close().catch((err) => logger$24.warn({ err }, "StateStore.close() failed (best-effort)"));
14268
14389
  if (ingestionServer !== void 0) await ingestionServer.stop().catch((err) => logger$24.warn({ err }, "IngestionServer.stop() failed (best-effort)"));
14269
14390
  }
@@ -43858,4 +43979,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
43858
43979
 
43859
43980
  //#endregion
43860
43981
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict, wireNdjsonEmitter };
43861
- //# sourceMappingURL=run-B6qbsy-F.js.map
43982
+ //# sourceMappingURL=run-D5VAkItq.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.12",
3
+ "version": "0.20.13",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,6 +38,7 @@ a different story from the epic scope. The story key, title, and core scope are
38
38
  5. **Fill out the story template** with:
39
39
  - A clear user story (As a / I want / So that)
40
40
  - Acceptance criteria: preserve the hard-clause text from the Story Definition verbatim. BDD Given/When/Then phrasing is **optional** — permitted for behavior-oriented criteria where it adds clarity, not mandatory. Never let BDD reshape a MUST / MUST NOT / SHALL clause; copy those clauses literally and, if BDD adds clarity, append the Given/When/Then alongside the original clause rather than replacing it. Aim for the same number of ACs as the source — do not condense clauses into fewer items to hit a target count.
41
+ - Immediately after the `## Acceptance Criteria` heading in the rendered story file, emit the line `<!-- source-ac-hash: {{source_ac_hash}} -->` on its own line. When the hash value is empty or blank (the `{{source_ac_hash}}` placeholder resolved to nothing), omit the comment entirely — do not write `<!-- source-ac-hash: -->` or any comment with an empty or missing hash value.
41
42
  - Concrete tasks broken into 2–4 hour subtasks, each tied to specific ACs
42
43
  - Dev Notes with file paths, import patterns, testing requirements
43
44
  6. **Apply the scope cap** — see Scope Cap Guidance below