substrate-ai 0.2.25 → 0.2.26

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CKvf6LgL.js";
2
+ import { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runSolutioningPhase, validateStopAfterFromConflict } from "../run-CEtHPG4I.js";
3
3
  import { createLogger, deepMask } from "../logger-D2fS2ccL.js";
4
4
  import { AdapterRegistry, ConfigError, ConfigIncompatibleFormatError } from "../errors-CswS7Mzg.js";
5
5
  import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "../version-manager-impl-CZ6KF1Ds.js";
@@ -2842,7 +2842,7 @@ async function runSupervisorAction(options, deps = {}) {
2842
2842
  const expDb = expDbWrapper.db;
2843
2843
  const { runRunAction: runPipeline } = await import(
2844
2844
  /* @vite-ignore */
2845
- "../run-bigUnNya.js"
2845
+ "../run-BXKRGSeL.js"
2846
2846
  );
2847
2847
  const runStoryFn = async (opts) => {
2848
2848
  const exitCode = await runPipeline({
package/dist/index.d.ts CHANGED
@@ -172,6 +172,44 @@ interface StoryEscalationEvent {
172
172
  /** Issues list from the final review (may be empty) */
173
173
  issues: EscalationIssue[];
174
174
  }
175
+ /**
176
+ * Emitted when a dev-story agent reported COMPLETE but git diff shows no
177
+ * file changes in the working tree (phantom completion — Story 24-1).
178
+ */
179
+ interface StoryZeroDiffEscalationEvent {
180
+ type: 'story:zero-diff-escalation';
181
+ /** ISO-8601 timestamp generated at emit time */
182
+ ts: string;
183
+ /** Story key (e.g., "10-1") */
184
+ storyKey: string;
185
+ /** Always "zero-diff-on-complete" */
186
+ reason: string;
187
+ }
188
+ /**
189
+ * Emitted when the build verification command exits with a non-zero code
190
+ * or times out, before code-review is dispatched (Story 24-2).
191
+ */
192
+ interface StoryBuildVerificationFailedEvent {
193
+ type: 'story:build-verification-failed';
194
+ /** ISO-8601 timestamp generated at emit time */
195
+ ts: string;
196
+ /** Story key (e.g., "24-2") */
197
+ storyKey: string;
198
+ /** Exit code from the build command (-1 for timeout) */
199
+ exitCode: number;
200
+ /** Combined stdout+stderr output, truncated to 2000 chars */
201
+ output: string;
202
+ }
203
+ /**
204
+ * Emitted when the build verification command exits with code 0 (Story 24-2).
205
+ */
206
+ interface StoryBuildVerificationPassedEvent {
207
+ type: 'story:build-verification-passed';
208
+ /** ISO-8601 timestamp generated at emit time */
209
+ ts: string;
210
+ /** Story key (e.g., "24-2") */
211
+ storyKey: string;
212
+ }
175
213
  /**
176
214
  * Emitted for non-fatal warnings during pipeline execution
177
215
  * (e.g., token ceiling truncation, partial batch failures).
@@ -432,7 +470,7 @@ interface SupervisorExperimentErrorEvent {
432
470
  * }
433
471
  * ```
434
472
  */
435
- type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent; //#endregion
473
+ type PipelineEvent = PipelineStartEvent | PipelineCompleteEvent | StoryPhaseEvent | StoryDoneEvent | StoryEscalationEvent | StoryWarnEvent | StoryLogEvent | PipelineHeartbeatEvent | StoryStallEvent | StoryZeroDiffEscalationEvent | StoryBuildVerificationFailedEvent | StoryBuildVerificationPassedEvent | SupervisorPollEvent | SupervisorKillEvent | SupervisorRestartEvent | SupervisorAbortEvent | SupervisorSummaryEvent | SupervisorAnalysisCompleteEvent | SupervisorAnalysisErrorEvent | SupervisorExperimentStartEvent | SupervisorExperimentSkipEvent | SupervisorExperimentRecommendationsEvent | SupervisorExperimentCompleteEvent | SupervisorExperimentErrorEvent; //#endregion
436
474
  //#region src/core/errors.d.ts
437
475
 
438
476
  /**
@@ -1018,6 +1056,11 @@ interface OrchestratorEvents {
1018
1056
  storyKey: string;
1019
1057
  msg: string;
1020
1058
  };
1059
+ /** Zero-diff detection gate: dev-story reported COMPLETE but git diff is empty (Story 24-1) */
1060
+ 'orchestrator:zero-diff-escalation': {
1061
+ storyKey: string;
1062
+ reason: string;
1063
+ };
1021
1064
  /** Implementation orchestrator has finished all stories */
1022
1065
  'orchestrator:complete': {
1023
1066
  totalStories: number;
@@ -1067,6 +1110,17 @@ interface OrchestratorEvents {
1067
1110
  affected_items: string[];
1068
1111
  }>;
1069
1112
  };
1113
+ /** Build verification command failed with non-zero exit or timeout */
1114
+ 'story:build-verification-failed': {
1115
+ storyKey: string;
1116
+ exitCode: number;
1117
+ /** Build output (stdout+stderr), truncated to 2000 chars */
1118
+ output: string;
1119
+ };
1120
+ /** Build verification command exited with code 0 */
1121
+ 'story:build-verification-passed': {
1122
+ storyKey: string;
1123
+ };
1070
1124
  }
1071
1125
 
1072
1126
  //#endregion
@@ -1,4 +1,4 @@
1
- import { registerRunCommand, runRunAction } from "./run-CKvf6LgL.js";
1
+ import { registerRunCommand, runRunAction } from "./run-CEtHPG4I.js";
2
2
  import "./logger-D2fS2ccL.js";
3
3
  import "./helpers-DljGJnFF.js";
4
4
  import "./decisions-Dq4cAA2L.js";
@@ -677,7 +677,9 @@ const PackManifestSchema = z.object({
677
677
  prompts: z.record(z.string(), z.string()),
678
678
  constraints: z.record(z.string(), z.string()),
679
679
  templates: z.record(z.string(), z.string()),
680
- conflictGroups: z.record(z.string(), z.string()).optional()
680
+ conflictGroups: z.record(z.string(), z.string()).optional(),
681
+ verifyCommand: z.union([z.string(), z.literal(false)]).optional(),
682
+ verifyTimeoutMs: z.number().optional()
681
683
  });
682
684
  const ConstraintSeveritySchema = z.enum([
683
685
  "required",
@@ -2212,6 +2214,69 @@ const PIPELINE_EVENT_METADATA = [
2212
2214
  description: "Error message."
2213
2215
  }
2214
2216
  ]
2217
+ },
2218
+ {
2219
+ type: "story:zero-diff-escalation",
2220
+ description: "Dev-story reported COMPLETE but git diff shows no file changes (phantom completion).",
2221
+ when: "After dev-story succeeds with zero file changes in working tree.",
2222
+ fields: [
2223
+ {
2224
+ name: "ts",
2225
+ type: "string",
2226
+ description: "Timestamp."
2227
+ },
2228
+ {
2229
+ name: "storyKey",
2230
+ type: "string",
2231
+ description: "Story key."
2232
+ },
2233
+ {
2234
+ name: "reason",
2235
+ type: "string",
2236
+ description: "Always \"zero-diff-on-complete\"."
2237
+ }
2238
+ ]
2239
+ },
2240
+ {
2241
+ type: "story:build-verification-failed",
2242
+ description: "Build verification command (default: npm run build) exited with non-zero code or timed out.",
2243
+ when: "After dev-story and zero-diff check pass, but before code-review is dispatched.",
2244
+ fields: [
2245
+ {
2246
+ name: "ts",
2247
+ type: "string",
2248
+ description: "Timestamp."
2249
+ },
2250
+ {
2251
+ name: "storyKey",
2252
+ type: "string",
2253
+ description: "Story key."
2254
+ },
2255
+ {
2256
+ name: "exitCode",
2257
+ type: "number",
2258
+ description: "Exit code from the build command (-1 for timeout)."
2259
+ },
2260
+ {
2261
+ name: "output",
2262
+ type: "string",
2263
+ description: "Combined stdout+stderr from the build command (truncated to 2000 chars)."
2264
+ }
2265
+ ]
2266
+ },
2267
+ {
2268
+ type: "story:build-verification-passed",
2269
+ description: "Build verification command exited with code 0 — compilation clean.",
2270
+ when: "After dev-story completes and build verification command succeeds.",
2271
+ fields: [{
2272
+ name: "ts",
2273
+ type: "string",
2274
+ description: "Timestamp."
2275
+ }, {
2276
+ name: "storyKey",
2277
+ type: "string",
2278
+ description: "Story key."
2279
+ }]
2215
2280
  }
2216
2281
  ];
2217
2282
  /**
@@ -3472,6 +3537,114 @@ var DispatcherImpl = class {
3472
3537
  }
3473
3538
  }
3474
3539
  };
3540
+ /** Default command for the build verification gate */
3541
+ const DEFAULT_VERIFY_COMMAND = "npm run build";
3542
+ /** Default timeout in milliseconds for the build verification gate */
3543
+ const DEFAULT_VERIFY_TIMEOUT_MS = 6e4;
3544
+ /**
3545
+ * Run the build verification gate synchronously.
3546
+ *
3547
+ * Executes the configured verifyCommand (default: "npm run build") in the
3548
+ * project root directory, capturing stdout and stderr. On success (exit 0)
3549
+ * returns { status: 'passed' }. On failure or timeout, returns a structured
3550
+ * result with status, exitCode, output, and reason.
3551
+ *
3552
+ * AC4/5: reads verifyCommand from options (or defaults to 'npm run build').
3553
+ * AC6: if verifyCommand is empty string or false, returns { status: 'skipped' }.
3554
+ * AC8: timeout is configurable via verifyTimeoutMs (default 60 s).
3555
+ */
3556
+ function runBuildVerification(options) {
3557
+ const { verifyCommand, verifyTimeoutMs, projectRoot } = options;
3558
+ const cmd = verifyCommand === void 0 ? DEFAULT_VERIFY_COMMAND : verifyCommand;
3559
+ if (!cmd) return { status: "skipped" };
3560
+ const timeoutMs = verifyTimeoutMs ?? DEFAULT_VERIFY_TIMEOUT_MS;
3561
+ try {
3562
+ const stdout = execSync(cmd, {
3563
+ cwd: projectRoot,
3564
+ timeout: timeoutMs,
3565
+ encoding: "utf-8"
3566
+ });
3567
+ return {
3568
+ status: "passed",
3569
+ exitCode: 0,
3570
+ output: typeof stdout === "string" ? stdout : ""
3571
+ };
3572
+ } catch (err) {
3573
+ if (err != null && typeof err === "object") {
3574
+ const e = err;
3575
+ const isTimeout = e.killed === true;
3576
+ const exitCode = typeof e.status === "number" ? e.status : 1;
3577
+ const rawStdout = e.stdout;
3578
+ const rawStderr = e.stderr;
3579
+ const stdoutStr = typeof rawStdout === "string" ? rawStdout : Buffer.isBuffer(rawStdout) ? rawStdout.toString("utf-8") : "";
3580
+ const stderrStr = typeof rawStderr === "string" ? rawStderr : Buffer.isBuffer(rawStderr) ? rawStderr.toString("utf-8") : "";
3581
+ const combinedOutput = [stdoutStr, stderrStr].filter((s) => s.length > 0).join("\n");
3582
+ if (isTimeout) return {
3583
+ status: "timeout",
3584
+ exitCode: -1,
3585
+ output: combinedOutput,
3586
+ reason: "build-verification-timeout"
3587
+ };
3588
+ return {
3589
+ status: "failed",
3590
+ exitCode,
3591
+ output: combinedOutput,
3592
+ reason: "build-verification-failed"
3593
+ };
3594
+ }
3595
+ return {
3596
+ status: "failed",
3597
+ exitCode: 1,
3598
+ output: String(err),
3599
+ reason: "build-verification-failed"
3600
+ };
3601
+ }
3602
+ }
3603
+ /**
3604
+ * Check git working tree for modified files using `git diff --name-only HEAD`
3605
+ * (unstaged + staged changes to tracked files) and `git diff --cached --name-only`
3606
+ * (staged new files not yet in HEAD). Returns a deduplicated array of file paths.
3607
+ *
3608
+ * Returns an empty array when:
3609
+ * - No files have been modified or staged
3610
+ * - Git commands fail (e.g., not in a git repo, git not installed)
3611
+ *
3612
+ * Used by the zero-diff detection gate (Story 24-1) to catch phantom completions
3613
+ * where a dev-story agent reported COMPLETE but made no actual file changes.
3614
+ *
3615
+ * @param workingDir - Directory to run git commands in (defaults to process.cwd())
3616
+ * @returns Array of changed file paths; empty when nothing changed
3617
+ */
3618
+ function checkGitDiffFiles(workingDir = process.cwd()) {
3619
+ const results = new Set();
3620
+ try {
3621
+ const unstaged = execSync("git diff --name-only HEAD", {
3622
+ cwd: workingDir,
3623
+ encoding: "utf-8",
3624
+ timeout: 5e3,
3625
+ stdio: [
3626
+ "ignore",
3627
+ "pipe",
3628
+ "pipe"
3629
+ ]
3630
+ });
3631
+ unstaged.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).forEach((f) => results.add(f));
3632
+ } catch {}
3633
+ try {
3634
+ const staged = execSync("git diff --cached --name-only", {
3635
+ cwd: workingDir,
3636
+ encoding: "utf-8",
3637
+ timeout: 5e3,
3638
+ stdio: [
3639
+ "ignore",
3640
+ "pipe",
3641
+ "pipe"
3642
+ ]
3643
+ });
3644
+ staged.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).forEach((f) => results.add(f));
3645
+ } catch {}
3646
+ return Array.from(results);
3647
+ }
3475
3648
  /**
3476
3649
  * Create a new Dispatcher instance.
3477
3650
  *
@@ -7027,6 +7200,7 @@ function createImplementationOrchestrator(deps) {
7027
7200
  persistState();
7028
7201
  let devFilesModified = [];
7029
7202
  const batchFileGroups = [];
7203
+ let devStoryWasSuccess = false;
7030
7204
  try {
7031
7205
  let storyContentForAnalysis = "";
7032
7206
  try {
@@ -7135,6 +7309,7 @@ function createImplementationOrchestrator(deps) {
7135
7309
  batchIndex: batch.batchIndex,
7136
7310
  error: batchResult.error
7137
7311
  }, "Batch dev-story reported failure — continuing with partial files");
7312
+ else devStoryWasSuccess = true;
7138
7313
  eventBus.emit("orchestrator:story-phase-complete", {
7139
7314
  storyKey,
7140
7315
  phase: "IN_DEV",
@@ -7163,7 +7338,8 @@ function createImplementationOrchestrator(deps) {
7163
7338
  result: devResult
7164
7339
  });
7165
7340
  persistState();
7166
- if (devResult.result === "failed") logger$20.warn("Dev-story reported failure, proceeding to code review", {
7341
+ if (devResult.result === "success") devStoryWasSuccess = true;
7342
+ else logger$20.warn("Dev-story reported failure, proceeding to code review", {
7167
7343
  storyKey,
7168
7344
  error: devResult.error,
7169
7345
  filesModified: devFilesModified.length
@@ -7187,7 +7363,70 @@ function createImplementationOrchestrator(deps) {
7187
7363
  persistState();
7188
7364
  return;
7189
7365
  }
7366
+ if (devStoryWasSuccess) {
7367
+ const changedFiles = checkGitDiffFiles(projectRoot ?? process.cwd());
7368
+ if (changedFiles.length === 0) {
7369
+ logger$20.warn({ storyKey }, "Zero-diff detected after COMPLETE dev-story — no file changes in git working tree");
7370
+ eventBus.emit("orchestrator:zero-diff-escalation", {
7371
+ storyKey,
7372
+ reason: "zero-diff-on-complete"
7373
+ });
7374
+ endPhase(storyKey, "dev-story");
7375
+ updateStory(storyKey, {
7376
+ phase: "ESCALATED",
7377
+ error: "zero-diff-on-complete",
7378
+ completedAt: new Date().toISOString()
7379
+ });
7380
+ writeStoryMetricsBestEffort(storyKey, "escalated", 0);
7381
+ emitEscalation({
7382
+ storyKey,
7383
+ lastVerdict: "zero-diff-on-complete",
7384
+ reviewCycles: 0,
7385
+ issues: ["dev-story completed with COMPLETE verdict but no file changes detected in git diff"]
7386
+ });
7387
+ persistState();
7388
+ return;
7389
+ }
7390
+ }
7190
7391
  endPhase(storyKey, "dev-story");
7392
+ {
7393
+ const buildVerifyResult = runBuildVerification({
7394
+ verifyCommand: pack.manifest.verifyCommand,
7395
+ verifyTimeoutMs: pack.manifest.verifyTimeoutMs,
7396
+ projectRoot: projectRoot ?? process.cwd()
7397
+ });
7398
+ if (buildVerifyResult.status === "passed") {
7399
+ eventBus.emit("story:build-verification-passed", { storyKey });
7400
+ logger$20.info({ storyKey }, "Build verification passed");
7401
+ } else if (buildVerifyResult.status === "failed" || buildVerifyResult.status === "timeout") {
7402
+ const truncatedOutput = (buildVerifyResult.output ?? "").slice(0, 2e3);
7403
+ const reason = buildVerifyResult.reason ?? "build-verification-failed";
7404
+ eventBus.emit("story:build-verification-failed", {
7405
+ storyKey,
7406
+ exitCode: buildVerifyResult.exitCode ?? 1,
7407
+ output: truncatedOutput
7408
+ });
7409
+ logger$20.warn({
7410
+ storyKey,
7411
+ reason,
7412
+ exitCode: buildVerifyResult.exitCode
7413
+ }, "Build verification failed — escalating story");
7414
+ updateStory(storyKey, {
7415
+ phase: "ESCALATED",
7416
+ error: reason,
7417
+ completedAt: new Date().toISOString()
7418
+ });
7419
+ writeStoryMetricsBestEffort(storyKey, "escalated", 0);
7420
+ emitEscalation({
7421
+ storyKey,
7422
+ lastVerdict: reason,
7423
+ reviewCycles: 0,
7424
+ issues: [truncatedOutput]
7425
+ });
7426
+ persistState();
7427
+ return;
7428
+ }
7429
+ }
7191
7430
  let reviewCycles = 0;
7192
7431
  let keepReviewing = true;
7193
7432
  let timeoutRetried = false;
@@ -12267,6 +12506,30 @@ async function runRunAction(options) {
12267
12506
  child_active: payload.childActive
12268
12507
  });
12269
12508
  });
12509
+ eventBus.on("orchestrator:zero-diff-escalation", (payload) => {
12510
+ ndjsonEmitter.emit({
12511
+ type: "story:zero-diff-escalation",
12512
+ ts: new Date().toISOString(),
12513
+ storyKey: payload.storyKey,
12514
+ reason: payload.reason
12515
+ });
12516
+ });
12517
+ eventBus.on("story:build-verification-passed", (payload) => {
12518
+ ndjsonEmitter.emit({
12519
+ type: "story:build-verification-passed",
12520
+ ts: new Date().toISOString(),
12521
+ storyKey: payload.storyKey
12522
+ });
12523
+ });
12524
+ eventBus.on("story:build-verification-failed", (payload) => {
12525
+ ndjsonEmitter.emit({
12526
+ type: "story:build-verification-failed",
12527
+ ts: new Date().toISOString(),
12528
+ storyKey: payload.storyKey,
12529
+ exitCode: payload.exitCode,
12530
+ output: payload.output
12531
+ });
12532
+ });
12270
12533
  }
12271
12534
  const orchestrator = createImplementationOrchestrator({
12272
12535
  db,
@@ -12721,4 +12984,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
12721
12984
 
12722
12985
  //#endregion
12723
12986
  export { DatabaseWrapper, SUBSTRATE_OWNED_SETTINGS_KEYS, VALID_PHASES, buildPipelineStatusOutput, createContextCompiler, createDispatcher, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, findPackageRoot, formatOutput, formatPhaseCompletionSummary, formatPipelineStatusHuman, formatPipelineSummary, formatTokenTelemetry, getAllDescendantPids, getAutoHealthData, getSubstrateDefaultSettings, registerHealthCommand, registerRunCommand, resolveBmadMethodSrcPath, resolveBmadMethodVersion, resolveMainRepoRoot, runAnalysisPhase, runMigrations, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
12724
- //# sourceMappingURL=run-CKvf6LgL.js.map
12987
+ //# sourceMappingURL=run-CEtHPG4I.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "substrate-ai",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "Substrate — multi-agent orchestration daemon for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",