substrate-ai 0.7.1 → 0.8.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.
@@ -1,4 +1,4 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, DoltClient, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, initSchema, inspectProcessTree, parseDbTimestampAsUtc, resolveMainRepoRoot, validateStoryKey } from "./health-Dnx-FGva.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, DoltClient, DoltMergeConflict, FileStateStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, WorkGraphRepository, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, initSchema, inspectProcessTree, parseDbTimestampAsUtc, resolveMainRepoRoot, validateStoryKey } from "./health-C-VRJruD.js";
2
2
  import { createLogger, deepMask } from "./logger-D2fS2ccL.js";
3
3
  import { CURRENT_CONFIG_FORMAT_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema, defaultConfigMigrator } from "./config-migrator-CtGelIsG.js";
4
4
  import { ConfigError, ConfigIncompatibleFormatError, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CpMs8VZX.js";
@@ -7,7 +7,7 @@ import { addTokenUsage, createDecision, createPipelineRun, createRequirement, ge
7
7
  import { ADVISORY_NOTES, ESCALATION_DIAGNOSIS, OPERATIONAL_FINDING, STORY_METRICS, STORY_OUTCOME, TEST_EXPANSION_FINDING, TEST_PLAN, aggregateTokenUsageForRun, aggregateTokenUsageForStory, getStoryMetricsForRun, writeRunMetrics, writeStoryMetrics } from "./operational-BdcdmDqS.js";
8
8
  import { dirname, join, resolve } from "path";
9
9
  import { access, mkdir, readFile, readdir, stat, writeFile } from "fs/promises";
10
- import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "fs";
11
11
  import yaml from "js-yaml";
12
12
  import { z } from "zod";
13
13
  import { execFile, execSync, spawn } from "node:child_process";
@@ -1588,6 +1588,34 @@ const PIPELINE_EVENT_METADATA = [
1588
1588
  }
1589
1589
  ]
1590
1590
  },
1591
+ {
1592
+ type: "pipeline:phase-start",
1593
+ description: "A pipeline phase has started during full pipeline execution.",
1594
+ when: "When --from is used and a phase begins (analysis, planning, solutioning, implementation).",
1595
+ fields: [{
1596
+ name: "ts",
1597
+ type: "string",
1598
+ description: "Timestamp."
1599
+ }, {
1600
+ name: "phase",
1601
+ type: "string",
1602
+ description: "Phase name (e.g., analysis, implementation)."
1603
+ }]
1604
+ },
1605
+ {
1606
+ type: "pipeline:phase-complete",
1607
+ description: "A pipeline phase has completed during full pipeline execution.",
1608
+ when: "When --from is used and a phase finishes successfully.",
1609
+ fields: [{
1610
+ name: "ts",
1611
+ type: "string",
1612
+ description: "Timestamp."
1613
+ }, {
1614
+ name: "phase",
1615
+ type: "string",
1616
+ description: "Phase name (e.g., analysis, implementation)."
1617
+ }]
1618
+ },
1591
1619
  {
1592
1620
  type: "pipeline:pre-flight-failure",
1593
1621
  description: "Pre-flight build check failed before any story was dispatched. Pipeline aborts immediately.",
@@ -13269,7 +13297,8 @@ function wgStatusForPhase(phase) {
13269
13297
  case "IN_TEST_PLANNING":
13270
13298
  case "IN_DEV":
13271
13299
  case "IN_REVIEW":
13272
- case "NEEDS_FIXES": return "in_progress";
13300
+ case "NEEDS_FIXES":
13301
+ case "CHECKPOINT": return "in_progress";
13273
13302
  case "COMPLETE": return "complete";
13274
13303
  case "ESCALATED": return "escalated";
13275
13304
  }
@@ -13358,6 +13387,7 @@ function createImplementationOrchestrator(deps) {
13358
13387
  let _contractMismatches;
13359
13388
  let _otlpEndpoint;
13360
13389
  const _stateStoreCache = new Map();
13390
+ const _checkpoints = new Map();
13361
13391
  const MEMORY_PRESSURE_BACKOFF_MS = [
13362
13392
  3e4,
13363
13393
  6e4,
@@ -13568,7 +13598,8 @@ function createImplementationOrchestrator(deps) {
13568
13598
  lastVerdict: record.lastVerdict,
13569
13599
  error: record.error,
13570
13600
  startedAt: record.startedAt,
13571
- completedAt: record.completedAt
13601
+ completedAt: record.completedAt,
13602
+ checkpointFilesCount: record.checkpointFilesCount
13572
13603
  };
13573
13604
  for (const [key, s] of _stories) stories[key] = { ...s };
13574
13605
  const status = {
@@ -13641,7 +13672,8 @@ function createImplementationOrchestrator(deps) {
13641
13672
  error: state.error,
13642
13673
  startedAt: state.startedAt,
13643
13674
  completedAt: state.completedAt,
13644
- sprint: config.sprint
13675
+ sprint: config.sprint,
13676
+ checkpointFilesCount: state.checkpointFilesCount
13645
13677
  };
13646
13678
  await stateStore.setStoryState(storyKey, record);
13647
13679
  } catch (err) {
@@ -14244,7 +14276,187 @@ function createImplementationOrchestrator(deps) {
14244
14276
  result: devResult
14245
14277
  });
14246
14278
  await persistState();
14247
- if (devResult.result === "success") devStoryWasSuccess = true;
14279
+ let checkpointHandled = false;
14280
+ if (devResult.result === "failed" && devResult.error?.startsWith("dispatch_timeout")) {
14281
+ endPhase(storyKey, "dev-story");
14282
+ const timeoutFiles = checkGitDiffFiles(projectRoot ?? process.cwd());
14283
+ if (timeoutFiles.length === 0) {
14284
+ logger$25.warn({ storyKey }, "Dev-story timeout with zero modified files — escalating immediately (no checkpoint)");
14285
+ updateStory(storyKey, {
14286
+ phase: "ESCALATED",
14287
+ error: "timeout-no-files",
14288
+ completedAt: new Date().toISOString()
14289
+ });
14290
+ await writeStoryMetricsBestEffort(storyKey, "escalated", 0);
14291
+ await emitEscalation({
14292
+ storyKey,
14293
+ lastVerdict: "timeout-no-files",
14294
+ reviewCycles: 0,
14295
+ issues: ["dev-story timed out with no partial files — nothing to checkpoint"]
14296
+ });
14297
+ await persistState();
14298
+ return;
14299
+ }
14300
+ logger$25.info({
14301
+ storyKey,
14302
+ filesCount: timeoutFiles.length
14303
+ }, "Dev-story timeout with partial files — capturing checkpoint");
14304
+ let gitDiff = "";
14305
+ try {
14306
+ gitDiff = execSync(`git diff HEAD -- ${timeoutFiles.map((f) => `"${f}"`).join(" ")}`, {
14307
+ cwd: projectRoot ?? process.cwd(),
14308
+ encoding: "utf-8",
14309
+ timeout: 1e4,
14310
+ stdio: [
14311
+ "ignore",
14312
+ "pipe",
14313
+ "pipe"
14314
+ ]
14315
+ }).trim();
14316
+ } catch (diffErr) {
14317
+ logger$25.warn({
14318
+ storyKey,
14319
+ error: diffErr instanceof Error ? diffErr.message : String(diffErr)
14320
+ }, "Failed to capture git diff for checkpoint — proceeding with empty diff");
14321
+ }
14322
+ _checkpoints.set(storyKey, {
14323
+ filesModified: timeoutFiles,
14324
+ gitDiff,
14325
+ partialOutput: devResult.error ?? ""
14326
+ });
14327
+ updateStory(storyKey, {
14328
+ phase: "CHECKPOINT",
14329
+ checkpointFilesCount: timeoutFiles.length
14330
+ });
14331
+ const diffSizeBytes = Buffer.byteLength(gitDiff, "utf-8");
14332
+ eventBus.emit("story:checkpoint-saved", {
14333
+ storyKey,
14334
+ filesCount: timeoutFiles.length,
14335
+ diffSizeBytes
14336
+ });
14337
+ if (stateStore !== void 0) stateStore.recordMetric({
14338
+ storyKey,
14339
+ taskType: "dev-story",
14340
+ result: "timeout",
14341
+ recordedAt: new Date().toISOString(),
14342
+ sprint: config.sprint
14343
+ }).catch((storeErr) => {
14344
+ logger$25.warn({
14345
+ err: storeErr,
14346
+ storyKey
14347
+ }, "Failed to record timeout metric to StateStore (best-effort)");
14348
+ });
14349
+ await persistState();
14350
+ eventBus.emit("story:checkpoint-retry", {
14351
+ storyKey,
14352
+ filesCount: timeoutFiles.length,
14353
+ attempt: 2
14354
+ });
14355
+ const checkpointData = _checkpoints.get(storyKey);
14356
+ let checkpointRetryPrompt;
14357
+ let checkpointRetryMaxTurns;
14358
+ try {
14359
+ const devStoryTemplate = await pack.getPrompt("dev-story");
14360
+ const storyContent = await readFile$1(storyFilePath ?? "", "utf-8");
14361
+ const complexity = computeStoryComplexity(storyContent);
14362
+ checkpointRetryMaxTurns = resolveDevStoryMaxTurns(complexity.complexityScore);
14363
+ logComplexityResult(storyKey, complexity, checkpointRetryMaxTurns);
14364
+ let archConstraints = "";
14365
+ try {
14366
+ const decisions = await getDecisionsByPhase(db, "solutioning");
14367
+ const constraints = decisions.filter((d) => d.category === "architecture");
14368
+ archConstraints = constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
14369
+ } catch {}
14370
+ const checkpointContext = [
14371
+ "Your prior attempt timed out. Here is the work you completed:",
14372
+ "",
14373
+ `Files modified (${checkpointData.filesModified.length}):`,
14374
+ ...checkpointData.filesModified.map((f) => `- ${f}`),
14375
+ "",
14376
+ "```diff",
14377
+ checkpointData.gitDiff || "(no diff available)",
14378
+ "```",
14379
+ "",
14380
+ "Continue from where you left off. Do not redo completed work."
14381
+ ].join("\n");
14382
+ const sections = [
14383
+ {
14384
+ name: "story_content",
14385
+ content: storyContent,
14386
+ priority: "required"
14387
+ },
14388
+ {
14389
+ name: "checkpoint_context",
14390
+ content: checkpointContext,
14391
+ priority: "required"
14392
+ },
14393
+ {
14394
+ name: "arch_constraints",
14395
+ content: archConstraints,
14396
+ priority: "optional"
14397
+ }
14398
+ ];
14399
+ const assembled = assemblePrompt(devStoryTemplate, sections, 24e3);
14400
+ checkpointRetryPrompt = assembled.prompt;
14401
+ } catch {
14402
+ checkpointRetryPrompt = `Continue story ${storyKey} from checkpoint. Your prior attempt timed out. Do not redo completed work.`;
14403
+ logger$25.warn({ storyKey }, "Failed to assemble checkpoint retry prompt — using fallback");
14404
+ }
14405
+ logger$25.info({
14406
+ storyKey,
14407
+ filesCount: checkpointData.filesModified.length
14408
+ }, "Dispatching checkpoint retry for timed-out story");
14409
+ incrementDispatches(storyKey);
14410
+ updateStory(storyKey, { phase: "IN_DEV" });
14411
+ startPhase(storyKey, "dev-story-retry");
14412
+ const checkpointRetryHandle = dispatcher.dispatch({
14413
+ prompt: checkpointRetryPrompt,
14414
+ agent: "claude-code",
14415
+ taskType: "dev-story",
14416
+ outputSchema: DevStoryResultSchema,
14417
+ ...checkpointRetryMaxTurns !== void 0 ? { maxTurns: checkpointRetryMaxTurns } : {},
14418
+ ...projectRoot !== void 0 ? { workingDirectory: projectRoot } : {},
14419
+ ..._otlpEndpoint !== void 0 ? { otlpEndpoint: _otlpEndpoint } : {},
14420
+ ...config.perStoryContextCeilings?.[storyKey] !== void 0 ? { maxContextTokens: config.perStoryContextCeilings[storyKey] } : {},
14421
+ storyKey
14422
+ });
14423
+ const checkpointRetryResult = await checkpointRetryHandle.result;
14424
+ endPhase(storyKey, "dev-story-retry");
14425
+ eventBus.emit("orchestrator:story-phase-complete", {
14426
+ storyKey,
14427
+ phase: "IN_DEV",
14428
+ result: { tokenUsage: checkpointRetryResult.tokenEstimate ? {
14429
+ input: checkpointRetryResult.tokenEstimate.input,
14430
+ output: checkpointRetryResult.tokenEstimate.output
14431
+ } : void 0 }
14432
+ });
14433
+ if (checkpointRetryResult.status === "timeout") {
14434
+ logger$25.warn({ storyKey }, "Checkpoint retry dispatch timed out — escalating story");
14435
+ updateStory(storyKey, {
14436
+ phase: "ESCALATED",
14437
+ error: "checkpoint-retry-timeout",
14438
+ completedAt: new Date().toISOString()
14439
+ });
14440
+ await writeStoryMetricsBestEffort(storyKey, "escalated", 0);
14441
+ await emitEscalation({
14442
+ storyKey,
14443
+ lastVerdict: "checkpoint-retry-timeout",
14444
+ reviewCycles: 0,
14445
+ issues: ["checkpoint retry timed out — no infinite retry loop"]
14446
+ });
14447
+ await persistState();
14448
+ return;
14449
+ }
14450
+ const retryParsed = checkpointRetryResult.parsed;
14451
+ devFilesModified = retryParsed?.files_modified ?? checkGitDiffFiles(projectRoot ?? process.cwd());
14452
+ if (checkpointRetryResult.status === "completed" && retryParsed?.result === "success") devStoryWasSuccess = true;
14453
+ else logger$25.warn({
14454
+ storyKey,
14455
+ status: checkpointRetryResult.status
14456
+ }, "Checkpoint retry completed with failure — proceeding to code review");
14457
+ checkpointHandled = true;
14458
+ }
14459
+ if (!checkpointHandled) if (devResult.result === "success") devStoryWasSuccess = true;
14248
14460
  else logger$25.warn({
14249
14461
  storyKey,
14250
14462
  error: devResult.error,
@@ -19794,6 +20006,189 @@ function mapInternalPhaseToEventPhase(internalPhase) {
19794
20006
  default: return null;
19795
20007
  }
19796
20008
  }
20009
+ /**
20010
+ * Wire all NDJSON event subscriptions from the event bus to the emitter.
20011
+ * Shared helper called from both the implementation-only path and the full
20012
+ * pipeline path (AC2: shared event subscription logic).
20013
+ */
20014
+ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
20015
+ eventBus.on("orchestrator:story-phase-start", (payload) => {
20016
+ const phase = mapInternalPhaseToEventPhase(payload.phase);
20017
+ if (phase !== null) ndjsonEmitter.emit({
20018
+ type: "story:phase",
20019
+ ts: new Date().toISOString(),
20020
+ key: payload.storyKey,
20021
+ phase,
20022
+ status: "in_progress"
20023
+ });
20024
+ });
20025
+ eventBus.on("orchestrator:story-phase-complete", (payload) => {
20026
+ const phase = mapInternalPhaseToEventPhase(payload.phase);
20027
+ if (phase !== null) {
20028
+ const result = payload.result;
20029
+ ndjsonEmitter.emit({
20030
+ type: "story:phase",
20031
+ ts: new Date().toISOString(),
20032
+ key: payload.storyKey,
20033
+ phase,
20034
+ status: "complete",
20035
+ ...phase === "code-review" && result?.verdict !== void 0 ? { verdict: result.verdict } : {},
20036
+ ...phase === "create-story" && result?.story_file !== void 0 ? { file: result.story_file } : {}
20037
+ });
20038
+ }
20039
+ });
20040
+ eventBus.on("routing:model-selected", (payload) => {
20041
+ ndjsonEmitter.emit({
20042
+ type: "routing:model-selected",
20043
+ ts: new Date().toISOString(),
20044
+ dispatch_id: payload.dispatchId,
20045
+ task_type: payload.taskType,
20046
+ phase: payload.phase,
20047
+ model: payload.model,
20048
+ source: payload.source
20049
+ });
20050
+ });
20051
+ eventBus.on("orchestrator:story-complete", (payload) => {
20052
+ ndjsonEmitter.emit({
20053
+ type: "story:done",
20054
+ ts: new Date().toISOString(),
20055
+ key: payload.storyKey,
20056
+ result: "success",
20057
+ review_cycles: payload.reviewCycles
20058
+ });
20059
+ });
20060
+ eventBus.on("orchestrator:story-escalated", (payload) => {
20061
+ const rawIssues = Array.isArray(payload.issues) ? payload.issues : [];
20062
+ const issues = rawIssues.map((issue) => {
20063
+ const iss = issue;
20064
+ return {
20065
+ severity: iss.severity ?? "unknown",
20066
+ file: iss.file ?? "",
20067
+ desc: iss.desc ?? iss.description ?? ""
20068
+ };
20069
+ });
20070
+ ndjsonEmitter.emit({
20071
+ type: "story:escalation",
20072
+ ts: new Date().toISOString(),
20073
+ key: payload.storyKey,
20074
+ reason: payload.lastVerdict ?? "escalated",
20075
+ cycles: payload.reviewCycles ?? 0,
20076
+ issues,
20077
+ ...payload.diagnosis !== void 0 ? { diagnosis: payload.diagnosis } : {}
20078
+ });
20079
+ });
20080
+ eventBus.on("orchestrator:story-warn", (payload) => {
20081
+ ndjsonEmitter.emit({
20082
+ type: "story:warn",
20083
+ ts: new Date().toISOString(),
20084
+ key: payload.storyKey,
20085
+ msg: payload.msg
20086
+ });
20087
+ });
20088
+ eventBus.on("orchestrator:heartbeat", (payload) => {
20089
+ ndjsonEmitter.emit({
20090
+ type: "pipeline:heartbeat",
20091
+ ts: new Date().toISOString(),
20092
+ run_id: payload.runId,
20093
+ active_dispatches: payload.activeDispatches,
20094
+ completed_dispatches: payload.completedDispatches,
20095
+ queued_dispatches: payload.queuedDispatches
20096
+ });
20097
+ });
20098
+ eventBus.on("orchestrator:stall", (payload) => {
20099
+ ndjsonEmitter.emit({
20100
+ type: "story:stall",
20101
+ ts: new Date().toISOString(),
20102
+ run_id: payload.runId,
20103
+ story_key: payload.storyKey,
20104
+ phase: payload.phase,
20105
+ elapsed_ms: payload.elapsedMs,
20106
+ child_pids: payload.childPids,
20107
+ child_active: payload.childActive
20108
+ });
20109
+ });
20110
+ eventBus.on("orchestrator:zero-diff-escalation", (payload) => {
20111
+ ndjsonEmitter.emit({
20112
+ type: "story:zero-diff-escalation",
20113
+ ts: new Date().toISOString(),
20114
+ storyKey: payload.storyKey,
20115
+ reason: payload.reason
20116
+ });
20117
+ });
20118
+ eventBus.on("story:build-verification-passed", (payload) => {
20119
+ ndjsonEmitter.emit({
20120
+ type: "story:build-verification-passed",
20121
+ ts: new Date().toISOString(),
20122
+ storyKey: payload.storyKey
20123
+ });
20124
+ });
20125
+ eventBus.on("story:build-verification-failed", (payload) => {
20126
+ ndjsonEmitter.emit({
20127
+ type: "story:build-verification-failed",
20128
+ ts: new Date().toISOString(),
20129
+ storyKey: payload.storyKey,
20130
+ exitCode: payload.exitCode,
20131
+ output: payload.output
20132
+ });
20133
+ });
20134
+ eventBus.on("story:interface-change-warning", (payload) => {
20135
+ ndjsonEmitter.emit({
20136
+ type: "story:interface-change-warning",
20137
+ ts: new Date().toISOString(),
20138
+ storyKey: payload.storyKey,
20139
+ modifiedInterfaces: payload.modifiedInterfaces,
20140
+ potentiallyAffectedTests: payload.potentiallyAffectedTests
20141
+ });
20142
+ });
20143
+ eventBus.on("story:metrics", (payload) => {
20144
+ ndjsonEmitter.emit({
20145
+ type: "story:metrics",
20146
+ ts: new Date().toISOString(),
20147
+ storyKey: payload.storyKey,
20148
+ wallClockMs: payload.wallClockMs,
20149
+ phaseBreakdown: payload.phaseBreakdown,
20150
+ tokens: payload.tokens,
20151
+ reviewCycles: payload.reviewCycles,
20152
+ dispatches: payload.dispatches
20153
+ });
20154
+ });
20155
+ eventBus.on("pipeline:pre-flight-failure", (payload) => {
20156
+ ndjsonEmitter.emit({
20157
+ type: "pipeline:pre-flight-failure",
20158
+ ts: new Date().toISOString(),
20159
+ exitCode: payload.exitCode,
20160
+ output: payload.output + "\nTip: Use --skip-preflight to bypass, or check your build command in .substrate/project-profile.yaml"
20161
+ });
20162
+ });
20163
+ eventBus.on("pipeline:contract-mismatch", (payload) => {
20164
+ ndjsonEmitter.emit({
20165
+ type: "pipeline:contract-mismatch",
20166
+ ts: new Date().toISOString(),
20167
+ exporter: payload.exporter,
20168
+ importer: payload.importer,
20169
+ contractName: payload.contractName,
20170
+ mismatchDescription: payload.mismatchDescription
20171
+ });
20172
+ });
20173
+ eventBus.on("pipeline:contract-verification-summary", (payload) => {
20174
+ ndjsonEmitter.emit({
20175
+ type: "pipeline:contract-verification-summary",
20176
+ ts: new Date().toISOString(),
20177
+ verified: payload.verified,
20178
+ stalePruned: payload.stalePruned,
20179
+ mismatches: payload.mismatches,
20180
+ verdict: payload.verdict
20181
+ });
20182
+ });
20183
+ eventBus.on("pipeline:profile-stale", (payload) => {
20184
+ ndjsonEmitter.emit({
20185
+ type: "pipeline:profile-stale",
20186
+ ts: new Date().toISOString(),
20187
+ message: payload.message,
20188
+ indicators: payload.indicators
20189
+ });
20190
+ });
20191
+ }
19797
20192
  async function runRunAction(options) {
19798
20193
  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;
19799
20194
  if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
@@ -19884,7 +20279,20 @@ async function runRunAction(options) {
19884
20279
  }
19885
20280
  }
19886
20281
  let effectiveStartPhase = startPhase;
19887
- if (effectiveStartPhase === void 0) {
20282
+ if (effectiveStartPhase === void 0) if (parsedStoryKeys.length > 0) {
20283
+ const artifactsDir = join(projectRoot, "_bmad-output", "implementation-artifacts");
20284
+ if (existsSync(artifactsDir)) {
20285
+ let files;
20286
+ try {
20287
+ const result = readdirSync(artifactsDir, { encoding: "utf-8" });
20288
+ if (Array.isArray(result)) files = result;
20289
+ } catch {}
20290
+ if (files !== void 0) {
20291
+ const missing = parsedStoryKeys.filter((key) => !files.some((f) => f.startsWith(`${key}-`) && f.endsWith(".md")));
20292
+ if (missing.length > 0) logger.info({ missing }, `Story files not found for ${missing.length} key(s) — create-story phase will generate them`);
20293
+ }
20294
+ }
20295
+ } else {
19888
20296
  mkdirSync(dbDir, { recursive: true });
19889
20297
  try {
19890
20298
  const detectAdapter = createDatabaseAdapter({
@@ -20019,6 +20427,24 @@ async function runRunAction(options) {
20019
20427
  ...parsedStoryKeys.length > 0 ? { explicitStories: parsedStoryKeys } : {}
20020
20428
  })
20021
20429
  });
20430
+ const runIdFilePath = join(dbDir, "current-run-id");
20431
+ try {
20432
+ writeFileSync(runIdFilePath, pipelineRun.id, "utf-8");
20433
+ const cleanupRunIdFile = () => {
20434
+ try {
20435
+ unlinkSync(runIdFilePath);
20436
+ } catch {}
20437
+ };
20438
+ process.on("exit", cleanupRunIdFile);
20439
+ process.once("SIGTERM", () => {
20440
+ cleanupRunIdFile();
20441
+ process.exit(0);
20442
+ });
20443
+ process.once("SIGINT", () => {
20444
+ cleanupRunIdFile();
20445
+ process.exit(130);
20446
+ });
20447
+ } catch {}
20022
20448
  const eventBus = createEventBus();
20023
20449
  const contextCompiler = createContextCompiler({ db: adapter });
20024
20450
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
@@ -20292,182 +20718,7 @@ async function runRunAction(options) {
20292
20718
  stories: storyKeys,
20293
20719
  concurrency
20294
20720
  });
20295
- eventBus.on("orchestrator:story-phase-start", (payload) => {
20296
- const phase = mapInternalPhaseToEventPhase(payload.phase);
20297
- if (phase !== null) ndjsonEmitter.emit({
20298
- type: "story:phase",
20299
- ts: new Date().toISOString(),
20300
- key: payload.storyKey,
20301
- phase,
20302
- status: "in_progress"
20303
- });
20304
- });
20305
- eventBus.on("orchestrator:story-phase-complete", (payload) => {
20306
- const phase = mapInternalPhaseToEventPhase(payload.phase);
20307
- if (phase !== null) {
20308
- const result = payload.result;
20309
- ndjsonEmitter.emit({
20310
- type: "story:phase",
20311
- ts: new Date().toISOString(),
20312
- key: payload.storyKey,
20313
- phase,
20314
- status: "complete",
20315
- ...phase === "code-review" && result?.verdict !== void 0 ? { verdict: result.verdict } : {},
20316
- ...phase === "create-story" && result?.story_file !== void 0 ? { file: result.story_file } : {}
20317
- });
20318
- }
20319
- });
20320
- eventBus.on("routing:model-selected", (payload) => {
20321
- ndjsonEmitter.emit({
20322
- type: "routing:model-selected",
20323
- ts: new Date().toISOString(),
20324
- dispatch_id: payload.dispatchId,
20325
- task_type: payload.taskType,
20326
- phase: payload.phase,
20327
- model: payload.model,
20328
- source: payload.source
20329
- });
20330
- });
20331
- eventBus.on("orchestrator:story-complete", (payload) => {
20332
- ndjsonEmitter.emit({
20333
- type: "story:done",
20334
- ts: new Date().toISOString(),
20335
- key: payload.storyKey,
20336
- result: "success",
20337
- review_cycles: payload.reviewCycles
20338
- });
20339
- });
20340
- eventBus.on("orchestrator:story-escalated", (payload) => {
20341
- const rawIssues = Array.isArray(payload.issues) ? payload.issues : [];
20342
- const issues = rawIssues.map((issue) => {
20343
- const iss = issue;
20344
- return {
20345
- severity: iss.severity ?? "unknown",
20346
- file: iss.file ?? "",
20347
- desc: iss.desc ?? iss.description ?? ""
20348
- };
20349
- });
20350
- ndjsonEmitter.emit({
20351
- type: "story:escalation",
20352
- ts: new Date().toISOString(),
20353
- key: payload.storyKey,
20354
- reason: payload.lastVerdict ?? "escalated",
20355
- cycles: payload.reviewCycles ?? 0,
20356
- issues,
20357
- ...payload.diagnosis !== void 0 ? { diagnosis: payload.diagnosis } : {}
20358
- });
20359
- });
20360
- eventBus.on("orchestrator:story-warn", (payload) => {
20361
- ndjsonEmitter.emit({
20362
- type: "story:warn",
20363
- ts: new Date().toISOString(),
20364
- key: payload.storyKey,
20365
- msg: payload.msg
20366
- });
20367
- });
20368
- eventBus.on("orchestrator:heartbeat", (payload) => {
20369
- ndjsonEmitter.emit({
20370
- type: "pipeline:heartbeat",
20371
- ts: new Date().toISOString(),
20372
- run_id: payload.runId,
20373
- active_dispatches: payload.activeDispatches,
20374
- completed_dispatches: payload.completedDispatches,
20375
- queued_dispatches: payload.queuedDispatches
20376
- });
20377
- });
20378
- eventBus.on("orchestrator:stall", (payload) => {
20379
- ndjsonEmitter.emit({
20380
- type: "story:stall",
20381
- ts: new Date().toISOString(),
20382
- run_id: payload.runId,
20383
- story_key: payload.storyKey,
20384
- phase: payload.phase,
20385
- elapsed_ms: payload.elapsedMs,
20386
- child_pids: payload.childPids,
20387
- child_active: payload.childActive
20388
- });
20389
- });
20390
- eventBus.on("orchestrator:zero-diff-escalation", (payload) => {
20391
- ndjsonEmitter.emit({
20392
- type: "story:zero-diff-escalation",
20393
- ts: new Date().toISOString(),
20394
- storyKey: payload.storyKey,
20395
- reason: payload.reason
20396
- });
20397
- });
20398
- eventBus.on("story:build-verification-passed", (payload) => {
20399
- ndjsonEmitter.emit({
20400
- type: "story:build-verification-passed",
20401
- ts: new Date().toISOString(),
20402
- storyKey: payload.storyKey
20403
- });
20404
- });
20405
- eventBus.on("story:build-verification-failed", (payload) => {
20406
- ndjsonEmitter.emit({
20407
- type: "story:build-verification-failed",
20408
- ts: new Date().toISOString(),
20409
- storyKey: payload.storyKey,
20410
- exitCode: payload.exitCode,
20411
- output: payload.output
20412
- });
20413
- });
20414
- eventBus.on("story:interface-change-warning", (payload) => {
20415
- ndjsonEmitter.emit({
20416
- type: "story:interface-change-warning",
20417
- ts: new Date().toISOString(),
20418
- storyKey: payload.storyKey,
20419
- modifiedInterfaces: payload.modifiedInterfaces,
20420
- potentiallyAffectedTests: payload.potentiallyAffectedTests
20421
- });
20422
- });
20423
- eventBus.on("story:metrics", (payload) => {
20424
- ndjsonEmitter.emit({
20425
- type: "story:metrics",
20426
- ts: new Date().toISOString(),
20427
- storyKey: payload.storyKey,
20428
- wallClockMs: payload.wallClockMs,
20429
- phaseBreakdown: payload.phaseBreakdown,
20430
- tokens: payload.tokens,
20431
- reviewCycles: payload.reviewCycles,
20432
- dispatches: payload.dispatches
20433
- });
20434
- });
20435
- eventBus.on("pipeline:pre-flight-failure", (payload) => {
20436
- ndjsonEmitter.emit({
20437
- type: "pipeline:pre-flight-failure",
20438
- ts: new Date().toISOString(),
20439
- exitCode: payload.exitCode,
20440
- output: payload.output + "\nTip: Use --skip-preflight to bypass, or check your build command in .substrate/project-profile.yaml"
20441
- });
20442
- });
20443
- eventBus.on("pipeline:contract-mismatch", (payload) => {
20444
- ndjsonEmitter.emit({
20445
- type: "pipeline:contract-mismatch",
20446
- ts: new Date().toISOString(),
20447
- exporter: payload.exporter,
20448
- importer: payload.importer,
20449
- contractName: payload.contractName,
20450
- mismatchDescription: payload.mismatchDescription
20451
- });
20452
- });
20453
- eventBus.on("pipeline:contract-verification-summary", (payload) => {
20454
- ndjsonEmitter.emit({
20455
- type: "pipeline:contract-verification-summary",
20456
- ts: new Date().toISOString(),
20457
- verified: payload.verified,
20458
- stalePruned: payload.stalePruned,
20459
- mismatches: payload.mismatches,
20460
- verdict: payload.verdict
20461
- });
20462
- });
20463
- eventBus.on("pipeline:profile-stale", (payload) => {
20464
- ndjsonEmitter.emit({
20465
- type: "pipeline:profile-stale",
20466
- ts: new Date().toISOString(),
20467
- message: payload.message,
20468
- indicators: payload.indicators
20469
- });
20470
- });
20721
+ wireNdjsonEmitter(eventBus, ndjsonEmitter);
20471
20722
  }
20472
20723
  const ingestionServer = telemetryEnabled ? new IngestionServer({ port: telemetryPort }) : void 0;
20473
20724
  if (ingestionServer !== void 0) {
@@ -20688,6 +20939,24 @@ async function runFullPipeline(options) {
20688
20939
  });
20689
20940
  const startedAt = Date.now();
20690
20941
  const runId = await phaseOrchestrator.startRun(concept ?? "", startPhase);
20942
+ const runIdFilePath = join(dbDir, "current-run-id");
20943
+ try {
20944
+ writeFileSync(runIdFilePath, runId, "utf-8");
20945
+ const cleanupRunIdFile = () => {
20946
+ try {
20947
+ unlinkSync(runIdFilePath);
20948
+ } catch {}
20949
+ };
20950
+ process.on("exit", cleanupRunIdFile);
20951
+ process.once("SIGTERM", () => {
20952
+ cleanupRunIdFile();
20953
+ process.exit(0);
20954
+ });
20955
+ process.once("SIGINT", () => {
20956
+ cleanupRunIdFile();
20957
+ process.exit(130);
20958
+ });
20959
+ } catch {}
20691
20960
  if (explicitStories !== void 0 && explicitStories.length > 0 || options.epic !== void 0) {
20692
20961
  const existingRun = (await adapter.query("SELECT config_json FROM pipeline_runs WHERE id = ?", [runId]))[0];
20693
20962
  const existing = JSON.parse(existingRun?.config_json ?? "{}");
@@ -20702,14 +20971,34 @@ async function runFullPipeline(options) {
20702
20971
  process.stdout.write(`Starting full pipeline from phase: ${startPhase}\n`);
20703
20972
  process.stdout.write(`Pipeline run ID: ${runId}\n`);
20704
20973
  }
20974
+ let fullPipelineNdjsonEmitter;
20975
+ if (eventsFlag === true) {
20976
+ fullPipelineNdjsonEmitter = createEventEmitter(process.stdout);
20977
+ fullPipelineNdjsonEmitter.emit({
20978
+ type: "pipeline:start",
20979
+ ts: new Date().toISOString(),
20980
+ run_id: runId,
20981
+ stories: explicitStories ?? [],
20982
+ concurrency
20983
+ });
20984
+ wireNdjsonEmitter(eventBus, fullPipelineNdjsonEmitter);
20985
+ }
20705
20986
  const phaseOrder = [];
20706
20987
  if (effectiveResearch) phaseOrder.push("research");
20707
20988
  phaseOrder.push("analysis", "planning");
20708
20989
  if (effectiveUxDesign) phaseOrder.push("ux-design");
20709
20990
  phaseOrder.push("solutioning", "implementation");
20710
20991
  const startIdx = phaseOrder.indexOf(startPhase);
20992
+ const fpSucceededKeys = [];
20993
+ const fpFailedKeys = [];
20994
+ const fpEscalatedKeys = [];
20711
20995
  for (let i = startIdx; i < phaseOrder.length; i++) {
20712
20996
  const currentPhase = phaseOrder[i];
20997
+ if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
20998
+ type: "pipeline:phase-start",
20999
+ ts: new Date().toISOString(),
21000
+ phase: currentPhase
21001
+ });
20713
21002
  if (outputFormat === "human") process.stdout.write(`\n[${currentPhase.toUpperCase()}] Starting...\n`);
20714
21003
  if (currentPhase === "analysis") {
20715
21004
  const result = await runAnalysisPhase(phaseDeps, {
@@ -20910,9 +21199,18 @@ async function runFullPipeline(options) {
20910
21199
  });
20911
21200
  if (storyKeys.length === 0 && outputFormat === "human") process.stdout.write("[IMPLEMENTATION] No stories found. Run solutioning first or pass --stories.\n");
20912
21201
  if (outputFormat === "human") process.stdout.write(`[IMPLEMENTATION] Starting ${storyKeys.length} stories with concurrency=${concurrency}\n`);
20913
- await orchestrator.run(storyKeys);
21202
+ const implStatus = await orchestrator.run(storyKeys);
20914
21203
  if (outputFormat === "human") process.stdout.write("[IMPLEMENTATION] Complete\n");
21204
+ for (const [key, s] of Object.entries(implStatus.stories)) if (s.phase === "COMPLETE") fpSucceededKeys.push(key);
21205
+ else if (s.phase === "ESCALATED") if (s.error !== void 0) fpFailedKeys.push(key);
21206
+ else fpEscalatedKeys.push(key);
21207
+ else fpFailedKeys.push(key);
20915
21208
  }
21209
+ if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
21210
+ type: "pipeline:phase-complete",
21211
+ ts: new Date().toISOString(),
21212
+ phase: currentPhase
21213
+ });
20916
21214
  if (stopAfter !== void 0 && currentPhase === stopAfter) {
20917
21215
  const gate = createStopAfterGate(stopAfter);
20918
21216
  if (gate.shouldHalt()) {
@@ -20961,6 +21259,13 @@ async function runFullPipeline(options) {
20961
21259
  process.stdout.write("\n");
20962
21260
  process.stdout.write(formatTokenTelemetry(tokenSummary, BMAD_BASELINE_TOKENS_FULL) + "\n");
20963
21261
  }
21262
+ if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
21263
+ type: "pipeline:complete",
21264
+ ts: new Date().toISOString(),
21265
+ succeeded: fpSucceededKeys,
21266
+ failed: fpFailedKeys,
21267
+ escalated: fpEscalatedKeys
21268
+ });
20964
21269
  return 0;
20965
21270
  } catch (err) {
20966
21271
  const msg = err instanceof Error ? err.message : String(err);
@@ -21020,4 +21325,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
21020
21325
 
21021
21326
  //#endregion
21022
21327
  export { AdapterTelemetryPersistence, AppError, DEFAULT_CONFIG, DEFAULT_ROUTING_POLICY, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, GitClient, GrammarLoader, IngestionServer, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createConfigSystem, createContextCompiler, createDispatcher, createEventEmitter, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, formatPhaseCompletionSummary, registerRunCommand, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runRunAction, runSolutioningPhase, validateStopAfterFromConflict };
21023
- //# sourceMappingURL=run-BW8_vcTi.js.map
21328
+ //# sourceMappingURL=run-VMOBJZ7f.js.map