substrate-ai 0.20.17 → 0.20.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, 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, getRunningPipelineRuns, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-CqtWS9wF.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { 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, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-xQLHet2Y.js";
7
+ import { 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, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-2nI3qh0-.js";
8
8
  import "../errors-1uLGqnvr.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -5198,7 +5198,7 @@ async function runSupervisorAction(options, deps = {}) {
5198
5198
  await initSchema(expAdapter);
5199
5199
  const { runRunAction: runPipeline } = await import(
5200
5200
  /* @vite-ignore */
5201
- "../run-CZW8qqr5.js"
5201
+ "../run-BNCfGm5V.js"
5202
5202
  );
5203
5203
  const runStoryFn = async (opts) => {
5204
5204
  const exitCode = await runPipeline({
@@ -5737,6 +5737,19 @@ async function runCreateStory(deps, params) {
5737
5737
  }
5738
5738
  const implementationDecisions = await getImplementationDecisions(deps, pipelineRunId);
5739
5739
  const epicShardContent = getEpicShard(implementationDecisions, epicId, deps.projectRoot, storyKey);
5740
+ let effectiveSourceAcHash = source_ac_hash;
5741
+ if (epicShardContent.length > 0) {
5742
+ const storySection = extractStorySection(epicShardContent, storyKey);
5743
+ if (storySection !== null) {
5744
+ const computedHash = hashSourceAcSection(storySection);
5745
+ if (source_ac_hash !== void 0 && source_ac_hash !== computedHash) logger$18.debug({
5746
+ storyKey,
5747
+ suppliedHash: source_ac_hash,
5748
+ computedHash
5749
+ }, "Orchestrator-supplied source_ac_hash differs from epic_shard content hash — using computed (Story 58-18)");
5750
+ effectiveSourceAcHash = computedHash;
5751
+ }
5752
+ }
5740
5753
  const prevDevNotesContent = getPrevDevNotes(implementationDecisions, epicId);
5741
5754
  let storyDefinitionContent = "";
5742
5755
  try {
@@ -5780,9 +5793,9 @@ async function runCreateStory(deps, params) {
5780
5793
  content: storyTemplateContent,
5781
5794
  priority: "important"
5782
5795
  },
5783
- ...source_ac_hash !== void 0 ? [{
5796
+ ...effectiveSourceAcHash !== void 0 ? [{
5784
5797
  name: "source_ac_hash",
5785
- content: source_ac_hash,
5798
+ content: effectiveSourceAcHash,
5786
5799
  priority: "required"
5787
5800
  }] : []
5788
5801
  ], TOKEN_CEILING);
@@ -5968,27 +5981,24 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
5968
5981
  }
5969
5982
  const epicShard = decisions.find((d) => d.category === "epic-shard" && d.key === epicId);
5970
5983
  const shardContent = epicShard?.value;
5971
- if (shardContent) {
5972
- if (storyKey) {
5973
- const storySection = extractStorySection(shardContent, storyKey);
5974
- if (storySection) {
5975
- logger$18.debug({
5976
- epicId,
5977
- storyKey
5978
- }, "Extracted per-story section from epic shard (pre-37-0 fallback)");
5979
- return storySection;
5980
- }
5984
+ if (shardContent && storyKey) {
5985
+ const storySection = extractStorySection(shardContent, storyKey);
5986
+ if (storySection) {
5981
5987
  logger$18.debug({
5982
5988
  epicId,
5983
5989
  storyKey
5984
- }, "No matching story section found using full epic shard");
5990
+ }, "Extracted per-story section from epic shard (pre-37-0 fallback)");
5991
+ return storySection;
5985
5992
  }
5986
- return shardContent;
5993
+ logger$18.info({
5994
+ epicId,
5995
+ storyKey
5996
+ }, "Story section absent in decisions-store shard — attempting file-based fallback before returning stale shard");
5987
5997
  }
5988
5998
  if (projectRoot) {
5989
5999
  const fallback = readEpicShardFromFile(projectRoot, epicId);
5990
6000
  if (fallback) {
5991
- logger$18.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
6001
+ logger$18.info({ epicId }, "Using file-based fallback for epic shard");
5992
6002
  if (storyKey) {
5993
6003
  const storySection = extractStorySection(fallback, storyKey);
5994
6004
  if (storySection) {
@@ -6002,6 +6012,7 @@ function getEpicShard(decisions, epicId, projectRoot, storyKey) {
6002
6012
  return fallback;
6003
6013
  }
6004
6014
  }
6015
+ if (shardContent) return shardContent;
6005
6016
  return "";
6006
6017
  } catch (err) {
6007
6018
  logger$18.warn({
@@ -8776,6 +8787,13 @@ async function seedEpicShards(db, projectRoot) {
8776
8787
  for (const shard of shards) {
8777
8788
  const subsections = parseStorySubsections(shard.epicId, shard.content);
8778
8789
  for (const subsection of subsections) {
8790
+ if (subsection.content.length > MAX_EPIC_SHARD_CHARS) logger$11.warn({
8791
+ epicId: shard.epicId,
8792
+ storyKey: subsection.key,
8793
+ originalLength: subsection.content.length,
8794
+ truncatedLength: MAX_EPIC_SHARD_CHARS,
8795
+ droppedChars: subsection.content.length - MAX_EPIC_SHARD_CHARS
8796
+ }, `Epic shard for ${subsection.key} exceeded ${MAX_EPIC_SHARD_CHARS}-char cap and was truncated; tail content lost from decisions store. Consider splitting the story or raising MAX_EPIC_SHARD_CHARS.`);
8779
8797
  await createDecision(db, {
8780
8798
  pipeline_run_id: null,
8781
8799
  phase: "implementation",
@@ -8897,25 +8915,46 @@ function parseEpicShards(content) {
8897
8915
  * Parse an epic section's content into per-story subsections.
8898
8916
  *
8899
8917
  * Matches story headings using three patterns:
8900
- * - Markdown headings: #{2,6} Story \d+-\d+ (e.g., ### Story 37-1: Title)
8901
- * - Bold: **Story \d+-\d+** (e.g., **Story 37-1**)
8902
- * - Bare key: \d+-\d+:\s (e.g., 37-1: Title — must start at line start)
8918
+ * - Markdown headings: #{2,6} Story \d+[-._ ]\d+ (e.g., ### Story 37-1: Title or ### Story 1.1)
8919
+ * - Bold: **Story \d+[-._ ]\d+** (e.g., **Story 37-1**)
8920
+ * - Bare key: \d+[-._ ]\d+:\s (e.g., 37-1: Title — must start at line start)
8903
8921
  *
8904
8922
  * Each subsection spans from its heading to the next matching heading or EOF.
8905
8923
  *
8924
+ * Story 58-17: separator normalization. The original regex required `\d+-\d+`
8925
+ * (dash-only). Strata uses `### Story 1.1` (dot-separated) per its BMAD-template
8926
+ * convention. Without separator-agnostic parsing, every Story 1.X heading was
8927
+ * silently invisible to this parser, the matches.length === 0 fallback path
8928
+ * fired, the entire epic was stored as ONE per-epic decision (key=epicId)
8929
+ * truncated at 12K chars. All stories past the truncation point (1.6, 1.8,
8930
+ * 1.9+ in strata's case) were lost from the decisions store, which is the
8931
+ * actual root cause of strata obs_2026-04-20_001 — create-story received
8932
+ * empty input for those stories and hallucinated specs from domain priors.
8933
+ *
8934
+ * Epic 58-5 already made `extractStorySection` separator-agnostic for the
8935
+ * same reason; this matches that precedent at the seed-time parser.
8936
+ *
8937
+ * Captured storyKey is normalized to canonical dash-form (`1.1` → `1-1`) so
8938
+ * decision keys are consistent regardless of the source heading style — a
8939
+ * `--stories 1-9` CLI invocation finds the shard whether the epic used dot,
8940
+ * dash, underscore, or space separators.
8941
+ *
8906
8942
  * AC3: If no story headings are found, returns a single per-epic fallback entry
8907
8943
  * keyed by epicId — preserving backward-compatible behaviour for unstructured epics.
8908
8944
  */
8909
8945
  function parseStorySubsections(epicId, epicContent) {
8910
- const storyPattern = /(?:^#{2,6}\s+Story\s+(\d+-\d+)|^\*\*Story\s+(\d+-\d+)\*\*|^(\d+-\d+):\s)/gim;
8946
+ const storyPattern = /(?:^#{2,6}\s+Story\s+(\d+[-._ ]\d+)|^\*\*Story\s+(\d+[-._ ]\d+)\*\*|^(\d+[-._ ]\d+):\s)/gim;
8911
8947
  const matches = [];
8912
8948
  let match$2;
8913
8949
  while ((match$2 = storyPattern.exec(epicContent)) !== null) {
8914
- const storyKey = match$2[1] ?? match$2[2] ?? match$2[3];
8915
- if (storyKey !== void 0) matches.push({
8916
- storyKey,
8917
- startIdx: match$2.index
8918
- });
8950
+ const rawKey = match$2[1] ?? match$2[2] ?? match$2[3];
8951
+ if (rawKey !== void 0) {
8952
+ const storyKey = rawKey.replace(/[._ ]/g, "-");
8953
+ matches.push({
8954
+ storyKey,
8955
+ startIdx: match$2.index
8956
+ });
8957
+ }
8919
8958
  }
8920
8959
  if (matches.length === 0) return [{
8921
8960
  key: epicId,
@@ -43291,11 +43330,11 @@ async function runRunAction(options) {
43291
43330
  });
43292
43331
  process.on("SIGINT", () => {
43293
43332
  ingestionServer.stop();
43294
- process.exit(130);
43333
+ setTimeout(() => process.exit(130), 6e3).unref();
43295
43334
  });
43296
43335
  process.on("SIGTERM", () => {
43297
43336
  ingestionServer.stop();
43298
- process.exit(143);
43337
+ setTimeout(() => process.exit(143), 6e3).unref();
43299
43338
  });
43300
43339
  }
43301
43340
  if (telemetryPersistence !== void 0) {
@@ -43825,11 +43864,11 @@ async function runFullPipeline(options) {
43825
43864
  });
43826
43865
  process.on("SIGINT", () => {
43827
43866
  fpIngestionServer.stop();
43828
- process.exit(130);
43867
+ setTimeout(() => process.exit(130), 6e3).unref();
43829
43868
  });
43830
43869
  process.on("SIGTERM", () => {
43831
43870
  fpIngestionServer.stop();
43832
- process.exit(143);
43871
+ setTimeout(() => process.exit(143), 6e3).unref();
43833
43872
  });
43834
43873
  }
43835
43874
  const fpTelemetryPersistence = fullTelemetryEnabled ? new AdapterTelemetryPersistence(adapter) : void 0;
@@ -44052,4 +44091,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
44052
44091
 
44053
44092
  //#endregion
44054
44093
  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 };
44055
- //# sourceMappingURL=run-xQLHet2Y.js.map
44094
+ //# sourceMappingURL=run-2nI3qh0-.js.map
@@ -2,7 +2,7 @@ import "./health-ZGa9E0D2.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-xQLHet2Y.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, resolveMaxReviewCycles, runRunAction, wireNdjsonEmitter } from "./run-2nI3qh0-.js";
6
6
  import "./routing-CcBOCuC9.js";
7
7
  import "./decisions-C0pz9Clx.js";
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.20.17",
3
+ "version": "0.20.19",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,24 @@ Using the context above, write a complete, implementation-ready story file for s
29
29
  Use the title, description, and acceptance criteria from the Story Definition — do NOT substitute
30
30
  a different story from the epic scope. The story key, title, and core scope are non-negotiable.
31
31
 
32
+ ## Input Validation (fail-loud)
33
+
34
+ Before anything else, verify the input contains the source Acceptance Criteria for story `{{story_key}}`. Scan `Epic Scope` and `Story Definition` for BOTH:
35
+
36
+ - A heading matching `Story {{story_key}}` (separators: `-`, `.`, `_`, space).
37
+ - An AC-bearing block within that section (`## Acceptance Criteria`, `### Acceptance Criteria`, `**Acceptance Criteria:**`, etc.).
38
+
39
+ If either is missing — shard truncated, context about other stories only — **do not infer, guess, or hallucinate an AC from the story key or domain priors**. A prior substrate session recorded a shape-specific drift exactly here: no source AC for a "graph builder" story → the agent invented a LanceDB+class-based spec, contradicting the author's explicit "plain JSON adjacency list" directive, purely from a trained pattern.
40
+
41
+ Instead, emit immediately per the Output Contract below:
42
+
43
+ ```yaml
44
+ result: failure
45
+ error: source-ac-content-missing
46
+ ```
47
+
48
+ Do NOT write a partial story file. Do NOT paraphrase surrounding context. Do NOT dispatch Write. The orchestrator treats this as terminal — the correct outcome when the input pipeline has degraded.
49
+
32
50
  ## Instructions
33
51
 
34
52
  1. **Use the Story Definition as your primary input** — it specifies exactly what this story builds. The epic scope provides surrounding context only.