substrate-ai 0.7.1 → 0.8.0

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";
@@ -13269,7 +13269,8 @@ function wgStatusForPhase(phase) {
13269
13269
  case "IN_TEST_PLANNING":
13270
13270
  case "IN_DEV":
13271
13271
  case "IN_REVIEW":
13272
- case "NEEDS_FIXES": return "in_progress";
13272
+ case "NEEDS_FIXES":
13273
+ case "CHECKPOINT": return "in_progress";
13273
13274
  case "COMPLETE": return "complete";
13274
13275
  case "ESCALATED": return "escalated";
13275
13276
  }
@@ -13358,6 +13359,7 @@ function createImplementationOrchestrator(deps) {
13358
13359
  let _contractMismatches;
13359
13360
  let _otlpEndpoint;
13360
13361
  const _stateStoreCache = new Map();
13362
+ const _checkpoints = new Map();
13361
13363
  const MEMORY_PRESSURE_BACKOFF_MS = [
13362
13364
  3e4,
13363
13365
  6e4,
@@ -13568,7 +13570,8 @@ function createImplementationOrchestrator(deps) {
13568
13570
  lastVerdict: record.lastVerdict,
13569
13571
  error: record.error,
13570
13572
  startedAt: record.startedAt,
13571
- completedAt: record.completedAt
13573
+ completedAt: record.completedAt,
13574
+ checkpointFilesCount: record.checkpointFilesCount
13572
13575
  };
13573
13576
  for (const [key, s] of _stories) stories[key] = { ...s };
13574
13577
  const status = {
@@ -13641,7 +13644,8 @@ function createImplementationOrchestrator(deps) {
13641
13644
  error: state.error,
13642
13645
  startedAt: state.startedAt,
13643
13646
  completedAt: state.completedAt,
13644
- sprint: config.sprint
13647
+ sprint: config.sprint,
13648
+ checkpointFilesCount: state.checkpointFilesCount
13645
13649
  };
13646
13650
  await stateStore.setStoryState(storyKey, record);
13647
13651
  } catch (err) {
@@ -14244,7 +14248,187 @@ function createImplementationOrchestrator(deps) {
14244
14248
  result: devResult
14245
14249
  });
14246
14250
  await persistState();
14247
- if (devResult.result === "success") devStoryWasSuccess = true;
14251
+ let checkpointHandled = false;
14252
+ if (devResult.result === "failed" && devResult.error?.startsWith("dispatch_timeout")) {
14253
+ endPhase(storyKey, "dev-story");
14254
+ const timeoutFiles = checkGitDiffFiles(projectRoot ?? process.cwd());
14255
+ if (timeoutFiles.length === 0) {
14256
+ logger$25.warn({ storyKey }, "Dev-story timeout with zero modified files — escalating immediately (no checkpoint)");
14257
+ updateStory(storyKey, {
14258
+ phase: "ESCALATED",
14259
+ error: "timeout-no-files",
14260
+ completedAt: new Date().toISOString()
14261
+ });
14262
+ await writeStoryMetricsBestEffort(storyKey, "escalated", 0);
14263
+ await emitEscalation({
14264
+ storyKey,
14265
+ lastVerdict: "timeout-no-files",
14266
+ reviewCycles: 0,
14267
+ issues: ["dev-story timed out with no partial files — nothing to checkpoint"]
14268
+ });
14269
+ await persistState();
14270
+ return;
14271
+ }
14272
+ logger$25.info({
14273
+ storyKey,
14274
+ filesCount: timeoutFiles.length
14275
+ }, "Dev-story timeout with partial files — capturing checkpoint");
14276
+ let gitDiff = "";
14277
+ try {
14278
+ gitDiff = execSync(`git diff HEAD -- ${timeoutFiles.map((f) => `"${f}"`).join(" ")}`, {
14279
+ cwd: projectRoot ?? process.cwd(),
14280
+ encoding: "utf-8",
14281
+ timeout: 1e4,
14282
+ stdio: [
14283
+ "ignore",
14284
+ "pipe",
14285
+ "pipe"
14286
+ ]
14287
+ }).trim();
14288
+ } catch (diffErr) {
14289
+ logger$25.warn({
14290
+ storyKey,
14291
+ error: diffErr instanceof Error ? diffErr.message : String(diffErr)
14292
+ }, "Failed to capture git diff for checkpoint — proceeding with empty diff");
14293
+ }
14294
+ _checkpoints.set(storyKey, {
14295
+ filesModified: timeoutFiles,
14296
+ gitDiff,
14297
+ partialOutput: devResult.error ?? ""
14298
+ });
14299
+ updateStory(storyKey, {
14300
+ phase: "CHECKPOINT",
14301
+ checkpointFilesCount: timeoutFiles.length
14302
+ });
14303
+ const diffSizeBytes = Buffer.byteLength(gitDiff, "utf-8");
14304
+ eventBus.emit("story:checkpoint-saved", {
14305
+ storyKey,
14306
+ filesCount: timeoutFiles.length,
14307
+ diffSizeBytes
14308
+ });
14309
+ if (stateStore !== void 0) stateStore.recordMetric({
14310
+ storyKey,
14311
+ taskType: "dev-story",
14312
+ result: "timeout",
14313
+ recordedAt: new Date().toISOString(),
14314
+ sprint: config.sprint
14315
+ }).catch((storeErr) => {
14316
+ logger$25.warn({
14317
+ err: storeErr,
14318
+ storyKey
14319
+ }, "Failed to record timeout metric to StateStore (best-effort)");
14320
+ });
14321
+ await persistState();
14322
+ eventBus.emit("story:checkpoint-retry", {
14323
+ storyKey,
14324
+ filesCount: timeoutFiles.length,
14325
+ attempt: 2
14326
+ });
14327
+ const checkpointData = _checkpoints.get(storyKey);
14328
+ let checkpointRetryPrompt;
14329
+ let checkpointRetryMaxTurns;
14330
+ try {
14331
+ const devStoryTemplate = await pack.getPrompt("dev-story");
14332
+ const storyContent = await readFile$1(storyFilePath ?? "", "utf-8");
14333
+ const complexity = computeStoryComplexity(storyContent);
14334
+ checkpointRetryMaxTurns = resolveDevStoryMaxTurns(complexity.complexityScore);
14335
+ logComplexityResult(storyKey, complexity, checkpointRetryMaxTurns);
14336
+ let archConstraints = "";
14337
+ try {
14338
+ const decisions = await getDecisionsByPhase(db, "solutioning");
14339
+ const constraints = decisions.filter((d) => d.category === "architecture");
14340
+ archConstraints = constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
14341
+ } catch {}
14342
+ const checkpointContext = [
14343
+ "Your prior attempt timed out. Here is the work you completed:",
14344
+ "",
14345
+ `Files modified (${checkpointData.filesModified.length}):`,
14346
+ ...checkpointData.filesModified.map((f) => `- ${f}`),
14347
+ "",
14348
+ "```diff",
14349
+ checkpointData.gitDiff || "(no diff available)",
14350
+ "```",
14351
+ "",
14352
+ "Continue from where you left off. Do not redo completed work."
14353
+ ].join("\n");
14354
+ const sections = [
14355
+ {
14356
+ name: "story_content",
14357
+ content: storyContent,
14358
+ priority: "required"
14359
+ },
14360
+ {
14361
+ name: "checkpoint_context",
14362
+ content: checkpointContext,
14363
+ priority: "required"
14364
+ },
14365
+ {
14366
+ name: "arch_constraints",
14367
+ content: archConstraints,
14368
+ priority: "optional"
14369
+ }
14370
+ ];
14371
+ const assembled = assemblePrompt(devStoryTemplate, sections, 24e3);
14372
+ checkpointRetryPrompt = assembled.prompt;
14373
+ } catch {
14374
+ checkpointRetryPrompt = `Continue story ${storyKey} from checkpoint. Your prior attempt timed out. Do not redo completed work.`;
14375
+ logger$25.warn({ storyKey }, "Failed to assemble checkpoint retry prompt — using fallback");
14376
+ }
14377
+ logger$25.info({
14378
+ storyKey,
14379
+ filesCount: checkpointData.filesModified.length
14380
+ }, "Dispatching checkpoint retry for timed-out story");
14381
+ incrementDispatches(storyKey);
14382
+ updateStory(storyKey, { phase: "IN_DEV" });
14383
+ startPhase(storyKey, "dev-story-retry");
14384
+ const checkpointRetryHandle = dispatcher.dispatch({
14385
+ prompt: checkpointRetryPrompt,
14386
+ agent: "claude-code",
14387
+ taskType: "dev-story",
14388
+ outputSchema: DevStoryResultSchema,
14389
+ ...checkpointRetryMaxTurns !== void 0 ? { maxTurns: checkpointRetryMaxTurns } : {},
14390
+ ...projectRoot !== void 0 ? { workingDirectory: projectRoot } : {},
14391
+ ..._otlpEndpoint !== void 0 ? { otlpEndpoint: _otlpEndpoint } : {},
14392
+ ...config.perStoryContextCeilings?.[storyKey] !== void 0 ? { maxContextTokens: config.perStoryContextCeilings[storyKey] } : {},
14393
+ storyKey
14394
+ });
14395
+ const checkpointRetryResult = await checkpointRetryHandle.result;
14396
+ endPhase(storyKey, "dev-story-retry");
14397
+ eventBus.emit("orchestrator:story-phase-complete", {
14398
+ storyKey,
14399
+ phase: "IN_DEV",
14400
+ result: { tokenUsage: checkpointRetryResult.tokenEstimate ? {
14401
+ input: checkpointRetryResult.tokenEstimate.input,
14402
+ output: checkpointRetryResult.tokenEstimate.output
14403
+ } : void 0 }
14404
+ });
14405
+ if (checkpointRetryResult.status === "timeout") {
14406
+ logger$25.warn({ storyKey }, "Checkpoint retry dispatch timed out — escalating story");
14407
+ updateStory(storyKey, {
14408
+ phase: "ESCALATED",
14409
+ error: "checkpoint-retry-timeout",
14410
+ completedAt: new Date().toISOString()
14411
+ });
14412
+ await writeStoryMetricsBestEffort(storyKey, "escalated", 0);
14413
+ await emitEscalation({
14414
+ storyKey,
14415
+ lastVerdict: "checkpoint-retry-timeout",
14416
+ reviewCycles: 0,
14417
+ issues: ["checkpoint retry timed out — no infinite retry loop"]
14418
+ });
14419
+ await persistState();
14420
+ return;
14421
+ }
14422
+ const retryParsed = checkpointRetryResult.parsed;
14423
+ devFilesModified = retryParsed?.files_modified ?? checkGitDiffFiles(projectRoot ?? process.cwd());
14424
+ if (checkpointRetryResult.status === "completed" && retryParsed?.result === "success") devStoryWasSuccess = true;
14425
+ else logger$25.warn({
14426
+ storyKey,
14427
+ status: checkpointRetryResult.status
14428
+ }, "Checkpoint retry completed with failure — proceeding to code review");
14429
+ checkpointHandled = true;
14430
+ }
14431
+ if (!checkpointHandled) if (devResult.result === "success") devStoryWasSuccess = true;
14248
14432
  else logger$25.warn({
14249
14433
  storyKey,
14250
14434
  error: devResult.error,
@@ -19794,6 +19978,189 @@ function mapInternalPhaseToEventPhase(internalPhase) {
19794
19978
  default: return null;
19795
19979
  }
19796
19980
  }
19981
+ /**
19982
+ * Wire all NDJSON event subscriptions from the event bus to the emitter.
19983
+ * Shared helper called from both the implementation-only path and the full
19984
+ * pipeline path (AC2: shared event subscription logic).
19985
+ */
19986
+ function wireNdjsonEmitter(eventBus, ndjsonEmitter) {
19987
+ eventBus.on("orchestrator:story-phase-start", (payload) => {
19988
+ const phase = mapInternalPhaseToEventPhase(payload.phase);
19989
+ if (phase !== null) ndjsonEmitter.emit({
19990
+ type: "story:phase",
19991
+ ts: new Date().toISOString(),
19992
+ key: payload.storyKey,
19993
+ phase,
19994
+ status: "in_progress"
19995
+ });
19996
+ });
19997
+ eventBus.on("orchestrator:story-phase-complete", (payload) => {
19998
+ const phase = mapInternalPhaseToEventPhase(payload.phase);
19999
+ if (phase !== null) {
20000
+ const result = payload.result;
20001
+ ndjsonEmitter.emit({
20002
+ type: "story:phase",
20003
+ ts: new Date().toISOString(),
20004
+ key: payload.storyKey,
20005
+ phase,
20006
+ status: "complete",
20007
+ ...phase === "code-review" && result?.verdict !== void 0 ? { verdict: result.verdict } : {},
20008
+ ...phase === "create-story" && result?.story_file !== void 0 ? { file: result.story_file } : {}
20009
+ });
20010
+ }
20011
+ });
20012
+ eventBus.on("routing:model-selected", (payload) => {
20013
+ ndjsonEmitter.emit({
20014
+ type: "routing:model-selected",
20015
+ ts: new Date().toISOString(),
20016
+ dispatch_id: payload.dispatchId,
20017
+ task_type: payload.taskType,
20018
+ phase: payload.phase,
20019
+ model: payload.model,
20020
+ source: payload.source
20021
+ });
20022
+ });
20023
+ eventBus.on("orchestrator:story-complete", (payload) => {
20024
+ ndjsonEmitter.emit({
20025
+ type: "story:done",
20026
+ ts: new Date().toISOString(),
20027
+ key: payload.storyKey,
20028
+ result: "success",
20029
+ review_cycles: payload.reviewCycles
20030
+ });
20031
+ });
20032
+ eventBus.on("orchestrator:story-escalated", (payload) => {
20033
+ const rawIssues = Array.isArray(payload.issues) ? payload.issues : [];
20034
+ const issues = rawIssues.map((issue) => {
20035
+ const iss = issue;
20036
+ return {
20037
+ severity: iss.severity ?? "unknown",
20038
+ file: iss.file ?? "",
20039
+ desc: iss.desc ?? iss.description ?? ""
20040
+ };
20041
+ });
20042
+ ndjsonEmitter.emit({
20043
+ type: "story:escalation",
20044
+ ts: new Date().toISOString(),
20045
+ key: payload.storyKey,
20046
+ reason: payload.lastVerdict ?? "escalated",
20047
+ cycles: payload.reviewCycles ?? 0,
20048
+ issues,
20049
+ ...payload.diagnosis !== void 0 ? { diagnosis: payload.diagnosis } : {}
20050
+ });
20051
+ });
20052
+ eventBus.on("orchestrator:story-warn", (payload) => {
20053
+ ndjsonEmitter.emit({
20054
+ type: "story:warn",
20055
+ ts: new Date().toISOString(),
20056
+ key: payload.storyKey,
20057
+ msg: payload.msg
20058
+ });
20059
+ });
20060
+ eventBus.on("orchestrator:heartbeat", (payload) => {
20061
+ ndjsonEmitter.emit({
20062
+ type: "pipeline:heartbeat",
20063
+ ts: new Date().toISOString(),
20064
+ run_id: payload.runId,
20065
+ active_dispatches: payload.activeDispatches,
20066
+ completed_dispatches: payload.completedDispatches,
20067
+ queued_dispatches: payload.queuedDispatches
20068
+ });
20069
+ });
20070
+ eventBus.on("orchestrator:stall", (payload) => {
20071
+ ndjsonEmitter.emit({
20072
+ type: "story:stall",
20073
+ ts: new Date().toISOString(),
20074
+ run_id: payload.runId,
20075
+ story_key: payload.storyKey,
20076
+ phase: payload.phase,
20077
+ elapsed_ms: payload.elapsedMs,
20078
+ child_pids: payload.childPids,
20079
+ child_active: payload.childActive
20080
+ });
20081
+ });
20082
+ eventBus.on("orchestrator:zero-diff-escalation", (payload) => {
20083
+ ndjsonEmitter.emit({
20084
+ type: "story:zero-diff-escalation",
20085
+ ts: new Date().toISOString(),
20086
+ storyKey: payload.storyKey,
20087
+ reason: payload.reason
20088
+ });
20089
+ });
20090
+ eventBus.on("story:build-verification-passed", (payload) => {
20091
+ ndjsonEmitter.emit({
20092
+ type: "story:build-verification-passed",
20093
+ ts: new Date().toISOString(),
20094
+ storyKey: payload.storyKey
20095
+ });
20096
+ });
20097
+ eventBus.on("story:build-verification-failed", (payload) => {
20098
+ ndjsonEmitter.emit({
20099
+ type: "story:build-verification-failed",
20100
+ ts: new Date().toISOString(),
20101
+ storyKey: payload.storyKey,
20102
+ exitCode: payload.exitCode,
20103
+ output: payload.output
20104
+ });
20105
+ });
20106
+ eventBus.on("story:interface-change-warning", (payload) => {
20107
+ ndjsonEmitter.emit({
20108
+ type: "story:interface-change-warning",
20109
+ ts: new Date().toISOString(),
20110
+ storyKey: payload.storyKey,
20111
+ modifiedInterfaces: payload.modifiedInterfaces,
20112
+ potentiallyAffectedTests: payload.potentiallyAffectedTests
20113
+ });
20114
+ });
20115
+ eventBus.on("story:metrics", (payload) => {
20116
+ ndjsonEmitter.emit({
20117
+ type: "story:metrics",
20118
+ ts: new Date().toISOString(),
20119
+ storyKey: payload.storyKey,
20120
+ wallClockMs: payload.wallClockMs,
20121
+ phaseBreakdown: payload.phaseBreakdown,
20122
+ tokens: payload.tokens,
20123
+ reviewCycles: payload.reviewCycles,
20124
+ dispatches: payload.dispatches
20125
+ });
20126
+ });
20127
+ eventBus.on("pipeline:pre-flight-failure", (payload) => {
20128
+ ndjsonEmitter.emit({
20129
+ type: "pipeline:pre-flight-failure",
20130
+ ts: new Date().toISOString(),
20131
+ exitCode: payload.exitCode,
20132
+ output: payload.output + "\nTip: Use --skip-preflight to bypass, or check your build command in .substrate/project-profile.yaml"
20133
+ });
20134
+ });
20135
+ eventBus.on("pipeline:contract-mismatch", (payload) => {
20136
+ ndjsonEmitter.emit({
20137
+ type: "pipeline:contract-mismatch",
20138
+ ts: new Date().toISOString(),
20139
+ exporter: payload.exporter,
20140
+ importer: payload.importer,
20141
+ contractName: payload.contractName,
20142
+ mismatchDescription: payload.mismatchDescription
20143
+ });
20144
+ });
20145
+ eventBus.on("pipeline:contract-verification-summary", (payload) => {
20146
+ ndjsonEmitter.emit({
20147
+ type: "pipeline:contract-verification-summary",
20148
+ ts: new Date().toISOString(),
20149
+ verified: payload.verified,
20150
+ stalePruned: payload.stalePruned,
20151
+ mismatches: payload.mismatches,
20152
+ verdict: payload.verdict
20153
+ });
20154
+ });
20155
+ eventBus.on("pipeline:profile-stale", (payload) => {
20156
+ ndjsonEmitter.emit({
20157
+ type: "pipeline:profile-stale",
20158
+ ts: new Date().toISOString(),
20159
+ message: payload.message,
20160
+ indicators: payload.indicators
20161
+ });
20162
+ });
20163
+ }
19797
20164
  async function runRunAction(options) {
19798
20165
  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
20166
  if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
@@ -19884,7 +20251,25 @@ async function runRunAction(options) {
19884
20251
  }
19885
20252
  }
19886
20253
  let effectiveStartPhase = startPhase;
19887
- if (effectiveStartPhase === void 0) {
20254
+ if (effectiveStartPhase === void 0) if (parsedStoryKeys.length > 0) {
20255
+ const artifactsDir = join(projectRoot, "_bmad-output", "implementation-artifacts");
20256
+ if (existsSync(artifactsDir)) {
20257
+ let files;
20258
+ try {
20259
+ const result = readdirSync(artifactsDir, { encoding: "utf-8" });
20260
+ if (Array.isArray(result)) files = result;
20261
+ } catch {}
20262
+ if (files !== void 0) for (const key of parsedStoryKeys) {
20263
+ const found = files.some((f) => f.startsWith(`${key}-`) && f.endsWith(".md"));
20264
+ if (!found) {
20265
+ const errorMsg = `Story file not found for key: ${key}`;
20266
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
20267
+ else process.stderr.write(`Error: ${errorMsg}\n`);
20268
+ return 1;
20269
+ }
20270
+ }
20271
+ }
20272
+ } else {
19888
20273
  mkdirSync(dbDir, { recursive: true });
19889
20274
  try {
19890
20275
  const detectAdapter = createDatabaseAdapter({
@@ -20019,6 +20404,24 @@ async function runRunAction(options) {
20019
20404
  ...parsedStoryKeys.length > 0 ? { explicitStories: parsedStoryKeys } : {}
20020
20405
  })
20021
20406
  });
20407
+ const runIdFilePath = join(dbDir, "current-run-id");
20408
+ try {
20409
+ writeFileSync(runIdFilePath, pipelineRun.id, "utf-8");
20410
+ const cleanupRunIdFile = () => {
20411
+ try {
20412
+ unlinkSync(runIdFilePath);
20413
+ } catch {}
20414
+ };
20415
+ process.on("exit", cleanupRunIdFile);
20416
+ process.once("SIGTERM", () => {
20417
+ cleanupRunIdFile();
20418
+ process.exit(0);
20419
+ });
20420
+ process.once("SIGINT", () => {
20421
+ cleanupRunIdFile();
20422
+ process.exit(130);
20423
+ });
20424
+ } catch {}
20022
20425
  const eventBus = createEventBus();
20023
20426
  const contextCompiler = createContextCompiler({ db: adapter });
20024
20427
  if (!injectedRegistry) throw new Error("AdapterRegistry is required — must be initialized at CLI startup");
@@ -20292,182 +20695,7 @@ async function runRunAction(options) {
20292
20695
  stories: storyKeys,
20293
20696
  concurrency
20294
20697
  });
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
- });
20698
+ wireNdjsonEmitter(eventBus, ndjsonEmitter);
20471
20699
  }
20472
20700
  const ingestionServer = telemetryEnabled ? new IngestionServer({ port: telemetryPort }) : void 0;
20473
20701
  if (ingestionServer !== void 0) {
@@ -20688,6 +20916,24 @@ async function runFullPipeline(options) {
20688
20916
  });
20689
20917
  const startedAt = Date.now();
20690
20918
  const runId = await phaseOrchestrator.startRun(concept ?? "", startPhase);
20919
+ const runIdFilePath = join(dbDir, "current-run-id");
20920
+ try {
20921
+ writeFileSync(runIdFilePath, runId, "utf-8");
20922
+ const cleanupRunIdFile = () => {
20923
+ try {
20924
+ unlinkSync(runIdFilePath);
20925
+ } catch {}
20926
+ };
20927
+ process.on("exit", cleanupRunIdFile);
20928
+ process.once("SIGTERM", () => {
20929
+ cleanupRunIdFile();
20930
+ process.exit(0);
20931
+ });
20932
+ process.once("SIGINT", () => {
20933
+ cleanupRunIdFile();
20934
+ process.exit(130);
20935
+ });
20936
+ } catch {}
20691
20937
  if (explicitStories !== void 0 && explicitStories.length > 0 || options.epic !== void 0) {
20692
20938
  const existingRun = (await adapter.query("SELECT config_json FROM pipeline_runs WHERE id = ?", [runId]))[0];
20693
20939
  const existing = JSON.parse(existingRun?.config_json ?? "{}");
@@ -20702,14 +20948,34 @@ async function runFullPipeline(options) {
20702
20948
  process.stdout.write(`Starting full pipeline from phase: ${startPhase}\n`);
20703
20949
  process.stdout.write(`Pipeline run ID: ${runId}\n`);
20704
20950
  }
20951
+ let fullPipelineNdjsonEmitter;
20952
+ if (eventsFlag === true) {
20953
+ fullPipelineNdjsonEmitter = createEventEmitter(process.stdout);
20954
+ fullPipelineNdjsonEmitter.emit({
20955
+ type: "pipeline:start",
20956
+ ts: new Date().toISOString(),
20957
+ run_id: runId,
20958
+ stories: explicitStories ?? [],
20959
+ concurrency
20960
+ });
20961
+ wireNdjsonEmitter(eventBus, fullPipelineNdjsonEmitter);
20962
+ }
20705
20963
  const phaseOrder = [];
20706
20964
  if (effectiveResearch) phaseOrder.push("research");
20707
20965
  phaseOrder.push("analysis", "planning");
20708
20966
  if (effectiveUxDesign) phaseOrder.push("ux-design");
20709
20967
  phaseOrder.push("solutioning", "implementation");
20710
20968
  const startIdx = phaseOrder.indexOf(startPhase);
20969
+ const fpSucceededKeys = [];
20970
+ const fpFailedKeys = [];
20971
+ const fpEscalatedKeys = [];
20711
20972
  for (let i = startIdx; i < phaseOrder.length; i++) {
20712
20973
  const currentPhase = phaseOrder[i];
20974
+ if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
20975
+ type: "pipeline:phase-start",
20976
+ ts: new Date().toISOString(),
20977
+ phase: currentPhase
20978
+ });
20713
20979
  if (outputFormat === "human") process.stdout.write(`\n[${currentPhase.toUpperCase()}] Starting...\n`);
20714
20980
  if (currentPhase === "analysis") {
20715
20981
  const result = await runAnalysisPhase(phaseDeps, {
@@ -20910,9 +21176,18 @@ async function runFullPipeline(options) {
20910
21176
  });
20911
21177
  if (storyKeys.length === 0 && outputFormat === "human") process.stdout.write("[IMPLEMENTATION] No stories found. Run solutioning first or pass --stories.\n");
20912
21178
  if (outputFormat === "human") process.stdout.write(`[IMPLEMENTATION] Starting ${storyKeys.length} stories with concurrency=${concurrency}\n`);
20913
- await orchestrator.run(storyKeys);
21179
+ const implStatus = await orchestrator.run(storyKeys);
20914
21180
  if (outputFormat === "human") process.stdout.write("[IMPLEMENTATION] Complete\n");
21181
+ for (const [key, s] of Object.entries(implStatus.stories)) if (s.phase === "COMPLETE") fpSucceededKeys.push(key);
21182
+ else if (s.phase === "ESCALATED") if (s.error !== void 0) fpFailedKeys.push(key);
21183
+ else fpEscalatedKeys.push(key);
21184
+ else fpFailedKeys.push(key);
20915
21185
  }
21186
+ if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
21187
+ type: "pipeline:phase-complete",
21188
+ ts: new Date().toISOString(),
21189
+ phase: currentPhase
21190
+ });
20916
21191
  if (stopAfter !== void 0 && currentPhase === stopAfter) {
20917
21192
  const gate = createStopAfterGate(stopAfter);
20918
21193
  if (gate.shouldHalt()) {
@@ -20961,6 +21236,13 @@ async function runFullPipeline(options) {
20961
21236
  process.stdout.write("\n");
20962
21237
  process.stdout.write(formatTokenTelemetry(tokenSummary, BMAD_BASELINE_TOKENS_FULL) + "\n");
20963
21238
  }
21239
+ if (fullPipelineNdjsonEmitter !== void 0) fullPipelineNdjsonEmitter.emit({
21240
+ type: "pipeline:complete",
21241
+ ts: new Date().toISOString(),
21242
+ succeeded: fpSucceededKeys,
21243
+ failed: fpFailedKeys,
21244
+ escalated: fpEscalatedKeys
21245
+ });
20964
21246
  return 0;
20965
21247
  } catch (err) {
20966
21248
  const msg = err instanceof Error ? err.message : String(err);
@@ -21020,4 +21302,4 @@ function registerRunCommand(program, _version = "0.0.0", projectRoot = process.c
21020
21302
 
21021
21303
  //#endregion
21022
21304
  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
21305
+ //# sourceMappingURL=run-ohzPz3r1.js.map