substrate-ai 0.19.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -4,7 +4,7 @@ import { createLogger } from "../logger-KeHncl-f.js";
4
4
  import { createEventBus } from "../helpers-CElYrONe.js";
5
5
  import { AdapterRegistry, BudgetConfigSchema, CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, ConfigError, CostTrackerConfigSchema, DEFAULT_CONFIG, DoltClient, DoltNotInstalled, EXPERIMENT_RESULT, GlobalSettingsSchema, IngestionServer, MonitorDatabaseImpl, OPERATIONAL_FINDING, PartialGlobalSettingsSchema, PartialProviderConfigSchema, ProvidersSchema, RoutingRecommender, STORY_METRICS, TelemetryConfigSchema, addTokenUsage, aggregateTokenUsageForRun, checkDoltInstalled, compareRunMetrics, createAmendmentRun, createConfigSystem, createDecision, createDoltClient, createPipelineRun, getActiveDecisions, getAllCostEntriesFiltered, getBaselineRunMetrics, getDecisionsByCategory, getDecisionsByPhaseForRun, getLatestCompletedRun, getLatestRun, getPipelineRunById, getPlanningCostTotal, getRetryableEscalations, getRunMetrics, getSessionCostSummary, getSessionCostSummaryFiltered, getStoryMetricsForRun, getTokenUsageSummary, incrementRunRestarts, initSchema, initializeDolt, listRequirements, listRunMetrics, loadParentRunDecisions, supersedeDecision, tagRunAsBaseline, updatePipelineRun } from "../dist-Bm0qSZer.js";
6
6
  import "../adapter-registry-DXLMTmfD.js";
7
- import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-C3MnOuBe.js";
7
+ import { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-Byzy10gG.js";
8
8
  import "../errors-BSpu7pIv.js";
9
9
  import "../routing-CcBOCuC9.js";
10
10
  import "../decisions-C0pz9Clx.js";
@@ -1135,6 +1135,17 @@ const TokenCeilingsSchema = z.object({
1135
1135
  "test-plan": z.number().int().positive("test-plan token ceiling must be a positive integer").optional(),
1136
1136
  "test-expansion": z.number().int().positive("test-expansion token ceiling must be a positive integer").optional()
1137
1137
  });
1138
+ /**
1139
+ * Per-task-type dispatch timeout overrides (milliseconds).
1140
+ * Keys match task types in DEFAULT_TIMEOUTS. Values override defaults.
1141
+ */
1142
+ const DispatchTimeoutsSchema = z.object({
1143
+ "create-story": z.number().int().positive().optional(),
1144
+ "dev-story": z.number().int().positive().optional(),
1145
+ "code-review": z.number().int().positive().optional(),
1146
+ "minor-fixes": z.number().int().positive().optional(),
1147
+ "major-rework": z.number().int().positive().optional()
1148
+ });
1138
1149
  const SubstrateConfigSchema = z.object({
1139
1150
  config_format_version: z.enum(["1"]),
1140
1151
  task_graph_version: z.enum(["1"]).optional(),
@@ -1143,6 +1154,7 @@ const SubstrateConfigSchema = z.object({
1143
1154
  cost_tracker: CostTrackerConfigSchema.optional(),
1144
1155
  budget: BudgetConfigSchema.optional(),
1145
1156
  token_ceilings: TokenCeilingsSchema.optional(),
1157
+ dispatch_timeouts: DispatchTimeoutsSchema.optional(),
1146
1158
  telemetry: TelemetryConfigSchema.optional()
1147
1159
  }).strict();
1148
1160
  const PartialSubstrateConfigSchema = z.object({
@@ -1157,6 +1169,7 @@ const PartialSubstrateConfigSchema = z.object({
1157
1169
  cost_tracker: CostTrackerConfigSchema.partial().optional(),
1158
1170
  budget: BudgetConfigSchema.partial().optional(),
1159
1171
  token_ceilings: TokenCeilingsSchema.optional(),
1172
+ dispatch_timeouts: DispatchTimeoutsSchema.optional(),
1160
1173
  telemetry: TelemetryConfigSchema.partial().optional()
1161
1174
  }).strict();
1162
1175
 
@@ -4529,7 +4542,7 @@ async function runSupervisorAction(options, deps = {}) {
4529
4542
  await initSchema(expAdapter);
4530
4543
  const { runRunAction: runPipeline } = await import(
4531
4544
  /* @vite-ignore */
4532
- "../run-C8jeEtC6.js"
4545
+ "../run-CzEyqOom.js"
4533
4546
  );
4534
4547
  const runStoryFn = async (opts) => {
4535
4548
  const exitCode = await runPipeline({
package/dist/index.d.ts CHANGED
@@ -1417,6 +1417,7 @@ declare function withRetry<T>(fn: () => Promise<T>, maxRetries?: number, baseDel
1417
1417
  //#endregion
1418
1418
  //#region src/modules/config/config-schema.d.ts
1419
1419
  //# sourceMappingURL=helpers.d.ts.map
1420
+
1420
1421
  declare const SubstrateConfigSchema: z.ZodObject<{
1421
1422
  config_format_version: z.ZodEnum<{
1422
1423
  1: "1";
@@ -1514,6 +1515,13 @@ declare const SubstrateConfigSchema: z.ZodObject<{
1514
1515
  'test-plan': z.ZodOptional<z.ZodNumber>;
1515
1516
  'test-expansion': z.ZodOptional<z.ZodNumber>;
1516
1517
  }, z.core.$strip>>;
1518
+ dispatch_timeouts: z.ZodOptional<z.ZodObject<{
1519
+ 'create-story': z.ZodOptional<z.ZodNumber>;
1520
+ 'dev-story': z.ZodOptional<z.ZodNumber>;
1521
+ 'code-review': z.ZodOptional<z.ZodNumber>;
1522
+ 'minor-fixes': z.ZodOptional<z.ZodNumber>;
1523
+ 'major-rework': z.ZodOptional<z.ZodNumber>;
1524
+ }, z.core.$strip>>;
1517
1525
  telemetry: z.ZodOptional<z.ZodObject<{
1518
1526
  enabled: z.ZodDefault<z.ZodBoolean>;
1519
1527
  port: z.ZodDefault<z.ZodNumber>;
@@ -6671,6 +6671,43 @@ function defaultFailResult(error, tokenUsage) {
6671
6671
  };
6672
6672
  }
6673
6673
  /**
6674
+ * Count test cases (`it(`, `test(`, `it.each(`, `test.each(`) in modified
6675
+ * test files. Returns a structured summary the reviewer can use as ground
6676
+ * truth instead of manually estimating test counts from code inspection.
6677
+ */
6678
+ async function countTestMetrics(filesModified, cwd) {
6679
+ if (!filesModified || filesModified.length === 0) return "";
6680
+ const testFiles = filesModified.filter((f$1) => f$1.includes(".test.") || f$1.includes(".spec.") || f$1.includes("__tests__"));
6681
+ if (testFiles.length === 0) return "";
6682
+ const results = [];
6683
+ let totalCount = 0;
6684
+ for (const file of testFiles) try {
6685
+ const out = execSync(`grep -cE "^\\s*(it|test|it\\.each|test\\.each)\\s*\\(" "${file}" 2>/dev/null || echo 0`, {
6686
+ cwd,
6687
+ encoding: "utf-8",
6688
+ timeout: 5e3
6689
+ }).trim();
6690
+ const count = parseInt(out, 10) || 0;
6691
+ if (count > 0) {
6692
+ results.push({
6693
+ file: file.split("/").pop(),
6694
+ count
6695
+ });
6696
+ totalCount += count;
6697
+ }
6698
+ } catch {}
6699
+ if (totalCount === 0) return "";
6700
+ const lines = [
6701
+ `VERIFIED TEST COUNT (automated — use as ground truth):`,
6702
+ `Total test cases: ${totalCount} across ${results.length} test file(s)`,
6703
+ ...results.map((r) => ` ${r.file}: ${r.count} test(s)`),
6704
+ "",
6705
+ "IMPORTANT: Use this verified count when evaluating AC test coverage thresholds.",
6706
+ "Do NOT manually estimate test counts — use the numbers above."
6707
+ ];
6708
+ return lines.join("\n");
6709
+ }
6710
+ /**
6674
6711
  * Execute the compiled code-review workflow.
6675
6712
  *
6676
6713
  * Steps:
@@ -6808,6 +6845,8 @@ async function runCodeReview(deps, params) {
6808
6845
  }, "Injecting prior findings into code-review prompt");
6809
6846
  }
6810
6847
  } catch {}
6848
+ const testMetricsContent = await countTestMetrics(filesModified, cwd);
6849
+ if (testMetricsContent) logger$12.debug({ storyKey }, "Injecting verified test-count metrics into code-review context");
6811
6850
  const sections = [
6812
6851
  {
6813
6852
  name: "story_content",
@@ -6819,6 +6858,11 @@ async function runCodeReview(deps, params) {
6819
6858
  content: gitDiffContent,
6820
6859
  priority: "important"
6821
6860
  },
6861
+ {
6862
+ name: "test_metrics",
6863
+ content: testMetricsContent,
6864
+ priority: "important"
6865
+ },
6822
6866
  {
6823
6867
  name: "previous_findings",
6824
6868
  content: previousFindingsContent,
@@ -9715,6 +9759,52 @@ function extractExpectedStoryTitle(shardContent, storyKey) {
9715
9759
  }
9716
9760
  return null;
9717
9761
  }
9762
+ /**
9763
+ * Check whether a story's expected NEW files already exist in the working tree,
9764
+ * indicating the story was implicitly implemented by adjacent stories.
9765
+ *
9766
+ * Parses the consolidated epics document for the story's "Files likely touched"
9767
+ * section and checks for files marked as "(new)". If all expected new files
9768
+ * already exist, the story is considered implicitly covered.
9769
+ *
9770
+ * Returns `true` if the story appears already covered, `false` otherwise.
9771
+ */
9772
+ function isImplicitlyCovered(storyKey, projectRoot) {
9773
+ const planningDir = join$1(projectRoot, "_bmad-output", "planning-artifacts");
9774
+ if (!existsSync(planningDir)) return false;
9775
+ let epicsPath;
9776
+ try {
9777
+ const entries = readdirSync(planningDir, { encoding: "utf-8" });
9778
+ const match$1 = entries.find((e) => /^epics[-.].*\.md$/i.test(e) && !/^epic-\d+/.test(e));
9779
+ if (match$1) epicsPath = join$1(planningDir, match$1);
9780
+ } catch {
9781
+ return false;
9782
+ }
9783
+ if (!epicsPath) return false;
9784
+ let content;
9785
+ try {
9786
+ content = readFileSync(epicsPath, "utf-8");
9787
+ } catch {
9788
+ return false;
9789
+ }
9790
+ const escapedKey = storyKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9791
+ const storyHeading = new RegExp(`^###\\s+Story\\s+${escapedKey}[:\\s]`, "m");
9792
+ const headingMatch = storyHeading.exec(content);
9793
+ if (!headingMatch) return false;
9794
+ const sectionStart = headingMatch.index;
9795
+ const nextHeading = content.indexOf("\n### Story ", sectionStart + 1);
9796
+ const section = nextHeading > 0 ? content.slice(sectionStart, nextHeading) : content.slice(sectionStart);
9797
+ const filesIdx = section.indexOf("Files likely touched:");
9798
+ if (filesIdx < 0) return false;
9799
+ const filesBlock = section.slice(filesIdx);
9800
+ const newFilePattern = /^-\s*`([^`]+)`\s*\(new\)/gm;
9801
+ const expectedNewFiles = [];
9802
+ let fm;
9803
+ while ((fm = newFilePattern.exec(filesBlock)) !== null) if (fm[1]) expectedNewFiles.push(fm[1]);
9804
+ if (expectedNewFiles.length === 0) return false;
9805
+ const existCount = expectedNewFiles.filter((f$1) => existsSync(join$1(projectRoot, f$1))).length;
9806
+ return existCount === expectedNewFiles.length;
9807
+ }
9718
9808
  const TITLE_OVERLAP_WARNING_THRESHOLD = .3;
9719
9809
  /**
9720
9810
  * Map a StoryPhase to the corresponding WgStoryStatus for wg_stories writes.
@@ -10318,6 +10408,25 @@ function createImplementationOrchestrator(deps) {
10318
10408
  }
10319
10409
  }
10320
10410
  } catch {}
10411
+ if (storyFilePath === void 0 && projectRoot && isImplicitlyCovered(storyKey, projectRoot)) {
10412
+ logger$21.info({ storyKey }, `Story ${storyKey} appears implicitly covered — all expected new files already exist. Skipping create-story.`);
10413
+ endPhase(storyKey, "create-story");
10414
+ eventBus.emit("orchestrator:story-phase-complete", {
10415
+ storyKey,
10416
+ phase: "IN_STORY_CREATION",
10417
+ result: {
10418
+ result: "success",
10419
+ story_key: storyKey,
10420
+ implicitlyCovered: true
10421
+ }
10422
+ });
10423
+ updateStory(storyKey, {
10424
+ phase: "COMPLETE",
10425
+ completedAt: new Date().toISOString()
10426
+ });
10427
+ await persistState();
10428
+ return;
10429
+ }
10321
10430
  if (storyFilePath === void 0) try {
10322
10431
  incrementDispatches(storyKey);
10323
10432
  const createResult = await runCreateStory({
@@ -39338,6 +39447,7 @@ async function runRunAction(options) {
39338
39447
  });
39339
39448
  } catch {}
39340
39449
  let tokenCeilings;
39450
+ let dispatchTimeouts;
39341
39451
  let telemetryEnabled = false;
39342
39452
  let telemetryPort = 4318;
39343
39453
  try {
@@ -39345,6 +39455,10 @@ async function runRunAction(options) {
39345
39455
  await configSystem.load();
39346
39456
  const cfg = configSystem.getConfig();
39347
39457
  tokenCeilings = cfg.token_ceilings;
39458
+ if (cfg.dispatch_timeouts) {
39459
+ dispatchTimeouts = Object.fromEntries(Object.entries(cfg.dispatch_timeouts).filter(([, v]) => v !== void 0));
39460
+ logger.info({ dispatchTimeouts }, "Loaded dispatch timeout overrides from config");
39461
+ }
39348
39462
  if (cfg.telemetry?.enabled === true) {
39349
39463
  telemetryEnabled = true;
39350
39464
  telemetryPort = cfg.telemetry.port ?? 4318;
@@ -39631,7 +39745,10 @@ async function runRunAction(options) {
39631
39745
  const dispatcher = createDispatcher({
39632
39746
  eventBus,
39633
39747
  adapterRegistry: injectedRegistry,
39634
- config: { routingResolver }
39748
+ config: {
39749
+ routingResolver,
39750
+ ...dispatchTimeouts ? { defaultTimeouts: dispatchTimeouts } : {}
39751
+ }
39635
39752
  });
39636
39753
  eventBus.on("orchestrator:story-phase-complete", (payload) => {
39637
39754
  try {
@@ -40498,4 +40615,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
40498
40615
 
40499
40616
  //#endregion
40500
40617
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
40501
- //# sourceMappingURL=run-C3MnOuBe.js.map
40618
+ //# sourceMappingURL=run-Byzy10gG.js.map
@@ -2,7 +2,7 @@ import "./health-Cx2ZhRNT.js";
2
2
  import "./logger-KeHncl-f.js";
3
3
  import "./helpers-CElYrONe.js";
4
4
  import "./dist-Bm0qSZer.js";
5
- import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-C3MnOuBe.js";
5
+ import { normalizeGraphSummaryToStatus, registerRunCommand, runRunAction } from "./run-Byzy10gG.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.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",