substrate-ai 0.1.22 → 0.1.23

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 { AdapterRegistry, ConfigError, ConfigIncompatibleFormatError, DatabaseWrapper, MonitorDatabaseImpl, ParseError, RecommendationEngine, TaskGraphFileSchema, ValidationError, computeChangedKeys, createConfigWatcher, createDatabaseService, createEventBus, createGitWorktreeManager, createLogger, createMonitorAgent, createMonitorDatabase, createRoutingEngine, createTaskGraphEngine, createTuiApp, createWorkerPoolManager, deepMask, detectCycle, getAllTasks, getLatestSessionId, getLogByEvent, getSession, getSessionLog, getTaskLog, isTuiCapable, parseGraphFile, printNonTtyWarning, queryLogFiltered, runMigrations, validateDependencies, validateGraph } from "../app-Bltq6BEm.js";
2
+ import { AdapterRegistry, ConfigError, ConfigIncompatibleFormatError, DatabaseWrapper, MonitorDatabaseImpl, ParseError, RecommendationEngine, TaskGraphFileSchema, ValidationError, computeChangedKeys, createConfigWatcher, createDatabaseService, createEventBus, createGitWorktreeManager, createLogger, createMonitorAgent, createMonitorDatabase, createRoutingEngine, createTaskGraphEngine, createTuiApp, createWorkerPoolManager, deepMask, detectCycle, getAllTasks, getLatestSessionId, getLogByEvent, getSession, getSessionLog, getTaskLog, isTuiCapable, parseGraphFile, printNonTtyWarning, queryLogFiltered, runMigrations, validateDependencies, validateGraph } from "../app-CY3MaJtP.js";
3
3
  import { CURRENT_CONFIG_FORMAT_VERSION, CURRENT_TASK_GRAPH_VERSION, PartialSubstrateConfigSchema, SUPPORTED_CONFIG_FORMAT_VERSIONS, SubstrateConfigSchema } from "../config-schema-C9tTMcm1.js";
4
4
  import { defaultConfigMigrator } from "../version-manager-impl-O25ieEjS.js";
5
5
  import { registerUpgradeCommand } from "../upgrade-CHhsJc_q.js";
@@ -389,7 +389,7 @@ function listTemplates() {
389
389
 
390
390
  //#endregion
391
391
  //#region src/cli/commands/init.ts
392
- const logger$32 = createLogger("init");
392
+ const logger$34 = createLogger("init");
393
393
  /**
394
394
  * Detect whether the CLI was invoked via `npx substrate`.
395
395
  * When true, prefix suggested commands with `npx `.
@@ -573,7 +573,7 @@ async function runInit(options = {}) {
573
573
  discoveryReport = await registry.discoverAndRegister();
574
574
  } catch (err) {
575
575
  const message = err instanceof Error ? err.message : String(err);
576
- logger$32.error({ err }, "Adapter discovery failed");
576
+ logger$34.error({ err }, "Adapter discovery failed");
577
577
  process.stderr.write(` Error: adapter discovery failed — ${message}\n`);
578
578
  return INIT_EXIT_ERROR;
579
579
  }
@@ -611,7 +611,7 @@ async function runInit(options = {}) {
611
611
  await writeFile(routingPolicyPath, routingHeader + yaml.dump(routingPolicy), "utf-8");
612
612
  } catch (err) {
613
613
  const message = err instanceof Error ? err.message : String(err);
614
- logger$32.error({ err }, "Failed to write config files");
614
+ logger$34.error({ err }, "Failed to write config files");
615
615
  process.stderr.write(` Error: failed to write configuration — ${message}\n`);
616
616
  return INIT_EXIT_ERROR;
617
617
  }
@@ -686,7 +686,7 @@ function formatUnsupportedVersionError(formatType, version, supported) {
686
686
 
687
687
  //#endregion
688
688
  //#region src/modules/config/config-system-impl.ts
689
- const logger$31 = createLogger("config");
689
+ const logger$33 = createLogger("config");
690
690
  function deepMerge(base, override) {
691
691
  const result = { ...base };
692
692
  for (const [key, val] of Object.entries(override)) if (val !== null && val !== void 0 && typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) result[key] = deepMerge(result[key], val);
@@ -731,7 +731,7 @@ function readEnvOverrides() {
731
731
  }
732
732
  const parsed = PartialSubstrateConfigSchema.safeParse(overrides);
733
733
  if (!parsed.success) {
734
- logger$31.warn({ errors: parsed.error.issues }, "Invalid environment variable overrides ignored");
734
+ logger$33.warn({ errors: parsed.error.issues }, "Invalid environment variable overrides ignored");
735
735
  return {};
736
736
  }
737
737
  return parsed.data;
@@ -795,7 +795,7 @@ var ConfigSystemImpl = class {
795
795
  throw new ConfigError(`Configuration validation failed:\n${issues}`, { issues: result.error.issues });
796
796
  }
797
797
  this._config = result.data;
798
- logger$31.debug("Configuration loaded successfully");
798
+ logger$33.debug("Configuration loaded successfully");
799
799
  }
800
800
  getConfig() {
801
801
  if (this._config === null) throw new ConfigError("Configuration has not been loaded. Call load() before getConfig().", {});
@@ -858,7 +858,7 @@ var ConfigSystemImpl = class {
858
858
  if (version !== void 0 && typeof version === "string" && !isVersionSupported(version, SUPPORTED_CONFIG_FORMAT_VERSIONS)) if (defaultConfigMigrator.canMigrate(version, CURRENT_CONFIG_FORMAT_VERSION)) {
859
859
  const migrationOutput = defaultConfigMigrator.migrate(rawObj, version, CURRENT_CONFIG_FORMAT_VERSION, filePath);
860
860
  if (migrationOutput.result.success) {
861
- logger$31.info({
861
+ logger$33.info({
862
862
  from: version,
863
863
  to: CURRENT_CONFIG_FORMAT_VERSION,
864
864
  backup: migrationOutput.result.backupPath
@@ -901,7 +901,7 @@ function createConfigSystem(options = {}) {
901
901
 
902
902
  //#endregion
903
903
  //#region src/cli/commands/config.ts
904
- const logger$30 = createLogger("config-cmd");
904
+ const logger$32 = createLogger("config-cmd");
905
905
  const CONFIG_EXIT_SUCCESS = 0;
906
906
  const CONFIG_EXIT_ERROR = 1;
907
907
  const CONFIG_EXIT_INVALID = 2;
@@ -927,7 +927,7 @@ async function runConfigShow(opts = {}) {
927
927
  return CONFIG_EXIT_INVALID;
928
928
  }
929
929
  const message = err instanceof Error ? err.message : String(err);
930
- logger$30.error({ err }, "Failed to load configuration");
930
+ logger$32.error({ err }, "Failed to load configuration");
931
931
  process.stderr.write(` Error loading configuration: ${message}\n`);
932
932
  return CONFIG_EXIT_ERROR;
933
933
  }
@@ -1001,7 +1001,7 @@ async function runConfigExport(opts = {}) {
1001
1001
  return CONFIG_EXIT_INVALID;
1002
1002
  }
1003
1003
  const message = err instanceof Error ? err.message : String(err);
1004
- logger$30.error({ err }, "Failed to load configuration");
1004
+ logger$32.error({ err }, "Failed to load configuration");
1005
1005
  process.stderr.write(`Error loading configuration: ${message}\n`);
1006
1006
  return CONFIG_EXIT_ERROR;
1007
1007
  }
@@ -1155,7 +1155,7 @@ function registerConfigCommand(program, _version) {
1155
1155
 
1156
1156
  //#endregion
1157
1157
  //#region src/cli/commands/merge.ts
1158
- const logger$29 = createLogger("merge-cmd");
1158
+ const logger$31 = createLogger("merge-cmd");
1159
1159
  const MERGE_EXIT_SUCCESS = 0;
1160
1160
  const MERGE_EXIT_CONFLICT = 1;
1161
1161
  const MERGE_EXIT_ERROR = 2;
@@ -1193,7 +1193,7 @@ async function mergeTask(taskId, targetBranch, projectRoot) {
1193
1193
  projectRoot
1194
1194
  });
1195
1195
  try {
1196
- logger$29.info({
1196
+ logger$31.info({
1197
1197
  taskId,
1198
1198
  targetBranch
1199
1199
  }, "Running conflict detection...");
@@ -1215,7 +1215,7 @@ async function mergeTask(taskId, targetBranch, projectRoot) {
1215
1215
  } catch (err) {
1216
1216
  const message = err instanceof Error ? err.message : String(err);
1217
1217
  console.error(`Error merging task "${taskId}": ${message}`);
1218
- logger$29.error({
1218
+ logger$31.error({
1219
1219
  taskId,
1220
1220
  err
1221
1221
  }, "merge --task failed");
@@ -1269,7 +1269,7 @@ async function mergeAll(targetBranch, projectRoot, taskIds) {
1269
1269
  error: message
1270
1270
  });
1271
1271
  console.log(` Error for task "${taskId}": ${message}`);
1272
- logger$29.error({
1272
+ logger$31.error({
1273
1273
  taskId,
1274
1274
  err
1275
1275
  }, "merge --all: task failed");
@@ -1322,7 +1322,7 @@ function registerMergeCommand(program, projectRoot = process.cwd()) {
1322
1322
 
1323
1323
  //#endregion
1324
1324
  //#region src/cli/commands/worktrees.ts
1325
- const logger$28 = createLogger("worktrees-cmd");
1325
+ const logger$30 = createLogger("worktrees-cmd");
1326
1326
  const WORKTREES_EXIT_SUCCESS = 0;
1327
1327
  const WORKTREES_EXIT_ERROR = 1;
1328
1328
  /** Valid task statuses for filtering */
@@ -1449,7 +1449,7 @@ async function listWorktreesAction(options) {
1449
1449
  try {
1450
1450
  worktreeInfos = await manager.listWorktrees();
1451
1451
  } catch (err) {
1452
- logger$28.error({ err }, "Failed to list worktrees");
1452
+ logger$30.error({ err }, "Failed to list worktrees");
1453
1453
  const message = err instanceof Error ? err.message : String(err);
1454
1454
  process.stderr.write(`Error listing worktrees: ${message}\n`);
1455
1455
  return WORKTREES_EXIT_ERROR;
@@ -1476,7 +1476,7 @@ async function listWorktreesAction(options) {
1476
1476
  } catch (err) {
1477
1477
  const message = err instanceof Error ? err.message : String(err);
1478
1478
  process.stderr.write(`Error: ${message}\n`);
1479
- logger$28.error({ err }, "listWorktreesAction failed");
1479
+ logger$30.error({ err }, "listWorktreesAction failed");
1480
1480
  return WORKTREES_EXIT_ERROR;
1481
1481
  }
1482
1482
  }
@@ -1738,7 +1738,7 @@ function getPlanningCostTotal(db, sessionId) {
1738
1738
 
1739
1739
  //#endregion
1740
1740
  //#region src/cli/commands/cost.ts
1741
- const logger$27 = createLogger("cost-cmd");
1741
+ const logger$29 = createLogger("cost-cmd");
1742
1742
  const COST_EXIT_SUCCESS = 0;
1743
1743
  const COST_EXIT_ERROR = 1;
1744
1744
  /**
@@ -1984,7 +1984,7 @@ async function runCostAction(options) {
1984
1984
  } catch (err) {
1985
1985
  const message = err instanceof Error ? err.message : String(err);
1986
1986
  process.stderr.write(`Error: ${message}\n`);
1987
- logger$27.error({ err }, "runCostAction failed");
1987
+ logger$29.error({ err }, "runCostAction failed");
1988
1988
  return COST_EXIT_ERROR;
1989
1989
  } finally {
1990
1990
  if (wrapper !== null) try {
@@ -2046,7 +2046,7 @@ function emitStatusSnapshot(snapshot) {
2046
2046
 
2047
2047
  //#endregion
2048
2048
  //#region src/recovery/crash-recovery.ts
2049
- const logger$26 = createLogger("crash-recovery");
2049
+ const logger$28 = createLogger("crash-recovery");
2050
2050
  var CrashRecoveryManager = class {
2051
2051
  db;
2052
2052
  gitWorktreeManager;
@@ -2099,7 +2099,7 @@ var CrashRecoveryManager = class {
2099
2099
  });
2100
2100
  }
2101
2101
  if (this.gitWorktreeManager !== void 0) this.cleanupOrphanedWorktrees().catch((err) => {
2102
- logger$26.warn({ err }, "Worktree cleanup failed during recovery (non-fatal)");
2102
+ logger$28.warn({ err }, "Worktree cleanup failed during recovery (non-fatal)");
2103
2103
  });
2104
2104
  let newlyReady = 0;
2105
2105
  if (sessionId !== void 0) {
@@ -2109,7 +2109,7 @@ var CrashRecoveryManager = class {
2109
2109
  const row = db.prepare("SELECT COUNT(*) as count FROM ready_tasks").get();
2110
2110
  newlyReady = row.count;
2111
2111
  }
2112
- logger$26.info({
2112
+ logger$28.info({
2113
2113
  event: "recovery:complete",
2114
2114
  recovered,
2115
2115
  failed,
@@ -2131,10 +2131,10 @@ var CrashRecoveryManager = class {
2131
2131
  if (this.gitWorktreeManager === void 0) return 0;
2132
2132
  try {
2133
2133
  const count = await this.gitWorktreeManager.cleanupAllWorktrees();
2134
- logger$26.info({ count }, "Cleaned up orphaned worktrees");
2134
+ logger$28.info({ count }, "Cleaned up orphaned worktrees");
2135
2135
  return count;
2136
2136
  } catch (err) {
2137
- logger$26.warn({ err }, "Failed to clean up orphaned worktrees — continuing");
2137
+ logger$28.warn({ err }, "Failed to clean up orphaned worktrees — continuing");
2138
2138
  return 0;
2139
2139
  }
2140
2140
  }
@@ -2217,7 +2217,7 @@ function setupGracefulShutdown(options) {
2217
2217
 
2218
2218
  //#endregion
2219
2219
  //#region src/cli/commands/start.ts
2220
- const logger$25 = createLogger("start-cmd");
2220
+ const logger$27 = createLogger("start-cmd");
2221
2221
  const START_EXIT_SUCCESS = 0;
2222
2222
  const START_EXIT_ERROR = 1;
2223
2223
  const START_EXIT_USAGE_ERROR = 2;
@@ -2326,7 +2326,7 @@ async function runStartAction(options) {
2326
2326
  let configWatcher$1 = null;
2327
2327
  const configFilePath = join(projectRoot, "substrate.config.yaml");
2328
2328
  if (noWatchConfig) {
2329
- logger$25.info("Config hot-reload disabled (--no-watch-config).");
2329
+ logger$27.info("Config hot-reload disabled (--no-watch-config).");
2330
2330
  process.stdout.write("Config hot-reload disabled (--no-watch-config).\n");
2331
2331
  } else {
2332
2332
  let currentHotConfig = config;
@@ -2341,7 +2341,7 @@ async function runStartAction(options) {
2341
2341
  const changedKeys = computeChangedKeys(previousConfig, newConfig);
2342
2342
  currentHotConfig = newConfig;
2343
2343
  const n = changedKeys.length;
2344
- logger$25.info({
2344
+ logger$27.info({
2345
2345
  changedKeys,
2346
2346
  configPath: configFilePath
2347
2347
  }, `Config reloaded: ${n} setting(s) changed`);
@@ -2353,7 +2353,7 @@ async function runStartAction(options) {
2353
2353
  });
2354
2354
  },
2355
2355
  onError: (err) => {
2356
- logger$25.error({
2356
+ logger$27.error({
2357
2357
  err,
2358
2358
  configPath: configFilePath
2359
2359
  }, `Config reload failed: ${err.message}. Continuing with previous config.`);
@@ -2366,7 +2366,7 @@ async function runStartAction(options) {
2366
2366
  let cleanupShutdown = null;
2367
2367
  if (resolvedGraphFile === null) if (interruptedSession !== void 0) {
2368
2368
  process.stdout.write(`Resuming interrupted session ${interruptedSession.id}\n`);
2369
- logger$25.info({ sessionId: interruptedSession.id }, "session:resumed");
2369
+ logger$27.info({ sessionId: interruptedSession.id }, "session:resumed");
2370
2370
  const recovery = new CrashRecoveryManager({
2371
2371
  db: databaseService.db,
2372
2372
  gitWorktreeManager
@@ -2490,7 +2490,7 @@ async function runStartAction(options) {
2490
2490
  } catch (err) {
2491
2491
  const message = err instanceof Error ? err.message : String(err);
2492
2492
  process.stderr.write(`Error: ${message}\n`);
2493
- logger$25.error({ err }, "runStartAction failed");
2493
+ logger$27.error({ err }, "runStartAction failed");
2494
2494
  return START_EXIT_ERROR;
2495
2495
  } finally {
2496
2496
  try {
@@ -2648,7 +2648,7 @@ function renderTaskGraph(snapshot, tasks) {
2648
2648
 
2649
2649
  //#endregion
2650
2650
  //#region src/cli/commands/status.ts
2651
- const logger$24 = createLogger("status-cmd");
2651
+ const logger$26 = createLogger("status-cmd");
2652
2652
  const STATUS_EXIT_SUCCESS = 0;
2653
2653
  const STATUS_EXIT_ERROR = 1;
2654
2654
  const STATUS_EXIT_NOT_FOUND = 2;
@@ -2801,7 +2801,7 @@ async function runStatusAction(options) {
2801
2801
  } catch (err) {
2802
2802
  const message = err instanceof Error ? err.message : String(err);
2803
2803
  process.stderr.write(`Error: ${message}\n`);
2804
- logger$24.error({ err }, "runStatusAction failed");
2804
+ logger$26.error({ err }, "runStatusAction failed");
2805
2805
  return STATUS_EXIT_ERROR;
2806
2806
  } finally {
2807
2807
  if (wrapper !== null) try {
@@ -2834,7 +2834,7 @@ function registerStatusCommand(program, _version = "0.0.0", projectRoot = proces
2834
2834
 
2835
2835
  //#endregion
2836
2836
  //#region src/cli/commands/pause.ts
2837
- const logger$23 = createLogger("pause-cmd");
2837
+ const logger$25 = createLogger("pause-cmd");
2838
2838
  const PAUSE_EXIT_SUCCESS = 0;
2839
2839
  const PAUSE_EXIT_ERROR = 1;
2840
2840
  const PAUSE_EXIT_USAGE_ERROR = 2;
@@ -2903,7 +2903,7 @@ async function runPauseAction(options) {
2903
2903
  } catch (err) {
2904
2904
  const message = err instanceof Error ? err.message : String(err);
2905
2905
  process.stderr.write(`Error: ${message}\n`);
2906
- logger$23.error({ err }, "runPauseAction failed");
2906
+ logger$25.error({ err }, "runPauseAction failed");
2907
2907
  return PAUSE_EXIT_ERROR;
2908
2908
  } finally {
2909
2909
  if (wrapper !== null) try {
@@ -2933,7 +2933,7 @@ function registerPauseCommand(program, version = "0.0.0", projectRoot = process.
2933
2933
 
2934
2934
  //#endregion
2935
2935
  //#region src/cli/commands/resume.ts
2936
- const logger$22 = createLogger("resume-cmd");
2936
+ const logger$24 = createLogger("resume-cmd");
2937
2937
  const RESUME_EXIT_SUCCESS = 0;
2938
2938
  const RESUME_EXIT_ERROR = 1;
2939
2939
  const RESUME_EXIT_USAGE_ERROR = 2;
@@ -3018,7 +3018,7 @@ async function runResumeAction(options) {
3018
3018
  } catch (err) {
3019
3019
  const message = err instanceof Error ? err.message : String(err);
3020
3020
  process.stderr.write(`Error: ${message}\n`);
3021
- logger$22.error({ err }, "runResumeAction failed");
3021
+ logger$24.error({ err }, "runResumeAction failed");
3022
3022
  return RESUME_EXIT_ERROR;
3023
3023
  } finally {
3024
3024
  if (wrapper !== null) try {
@@ -3051,7 +3051,7 @@ function registerResumeCommand(program, version = "0.0.0", projectRoot = process
3051
3051
 
3052
3052
  //#endregion
3053
3053
  //#region src/cli/commands/cancel.ts
3054
- const logger$21 = createLogger("cancel-cmd");
3054
+ const logger$23 = createLogger("cancel-cmd");
3055
3055
  const CANCEL_EXIT_SUCCESS = 0;
3056
3056
  const CANCEL_EXIT_ERROR = 1;
3057
3057
  const CANCEL_EXIT_USAGE_ERROR = 2;
@@ -3148,7 +3148,7 @@ async function runCancelAction(options) {
3148
3148
  } catch (err) {
3149
3149
  const message = err instanceof Error ? err.message : String(err);
3150
3150
  process.stderr.write(`Error: ${message}\n`);
3151
- logger$21.error({ err }, "runCancelAction failed");
3151
+ logger$23.error({ err }, "runCancelAction failed");
3152
3152
  return CANCEL_EXIT_ERROR;
3153
3153
  } finally {
3154
3154
  if (wrapper !== null) try {
@@ -3263,7 +3263,7 @@ function renderFailedTasksJson(tasks) {
3263
3263
 
3264
3264
  //#endregion
3265
3265
  //#region src/cli/commands/retry.ts
3266
- const logger$20 = createLogger("retry-cmd");
3266
+ const logger$22 = createLogger("retry-cmd");
3267
3267
  const RETRY_EXIT_SUCCESS = 0;
3268
3268
  const RETRY_EXIT_PARTIAL_FAILURE = 1;
3269
3269
  const RETRY_EXIT_USAGE_ERROR = 2;
@@ -3368,7 +3368,7 @@ async function runRetryAction(options) {
3368
3368
  } catch (err) {
3369
3369
  const message = err instanceof Error ? err.message : String(err);
3370
3370
  process.stderr.write(`Error: ${message}\n`);
3371
- logger$20.error({ err }, "runRetryAction failed");
3371
+ logger$22.error({ err }, "runRetryAction failed");
3372
3372
  return RETRY_EXIT_USAGE_ERROR;
3373
3373
  } finally {
3374
3374
  if (wrapper !== null) try {
@@ -3497,11 +3497,11 @@ async function runFollowMode(opts) {
3497
3497
  });
3498
3498
  });
3499
3499
  const sigintHandler = () => {
3500
- logger$20.info("SIGINT received — initiating graceful shutdown");
3500
+ logger$22.info("SIGINT received — initiating graceful shutdown");
3501
3501
  taskGraphEngine.cancelAll();
3502
3502
  };
3503
3503
  const sigtermHandler = () => {
3504
- logger$20.info("SIGTERM received — initiating graceful shutdown");
3504
+ logger$22.info("SIGTERM received — initiating graceful shutdown");
3505
3505
  taskGraphEngine.cancelAll();
3506
3506
  };
3507
3507
  process.once("SIGINT", sigintHandler);
@@ -3514,7 +3514,7 @@ async function runFollowMode(opts) {
3514
3514
  } catch (err) {
3515
3515
  const message = err instanceof Error ? err.message : String(err);
3516
3516
  process.stderr.write(`Error: ${message}\n`);
3517
- logger$20.error({ err }, "runFollowMode failed");
3517
+ logger$22.error({ err }, "runFollowMode failed");
3518
3518
  return RETRY_EXIT_USAGE_ERROR;
3519
3519
  } finally {
3520
3520
  try {
@@ -3974,7 +3974,7 @@ function buildMultiAgentInstructionsSection(agentCount) {
3974
3974
 
3975
3975
  //#endregion
3976
3976
  //#region src/modules/plan-generator/plan-generator.ts
3977
- const logger$19 = createLogger("plan-generator");
3977
+ const logger$21 = createLogger("plan-generator");
3978
3978
  /**
3979
3979
  * Wrapper around execFile that immediately closes stdin on the child process.
3980
3980
  * Some CLI tools (e.g. Claude Code) wait for stdin to close before processing
@@ -4151,7 +4151,7 @@ var PlanGenerator = class {
4151
4151
  else {
4152
4152
  const slugified = dep.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 64);
4153
4153
  if (taskKeys.has(slugified)) resolvedDeps.push(slugified);
4154
- else logger$19.warn({
4154
+ else logger$21.warn({
4155
4155
  taskKey,
4156
4156
  dep
4157
4157
  }, `depends_on reference '${dep}' not found in task keys; removing`);
@@ -4898,7 +4898,7 @@ function getLatestPlanVersion(db, planId) {
4898
4898
 
4899
4899
  //#endregion
4900
4900
  //#region src/modules/plan-generator/plan-refiner.ts
4901
- const logger$18 = createLogger("plan-refiner");
4901
+ const logger$20 = createLogger("plan-refiner");
4902
4902
  var PlanRefiner = class {
4903
4903
  db;
4904
4904
  planGenerator;
@@ -4941,7 +4941,7 @@ var PlanRefiner = class {
4941
4941
  newFeedback: feedback,
4942
4942
  availableAgents: this.availableAgents
4943
4943
  });
4944
- logger$18.info({
4944
+ logger$20.info({
4945
4945
  planId,
4946
4946
  currentVersion,
4947
4947
  feedbackRounds: feedbackHistory.length
@@ -4988,7 +4988,7 @@ var PlanRefiner = class {
4988
4988
  newVersion,
4989
4989
  taskCount
4990
4990
  });
4991
- logger$18.info({
4991
+ logger$20.info({
4992
4992
  planId,
4993
4993
  newVersion,
4994
4994
  taskCount
@@ -5070,7 +5070,7 @@ function normalizeForDiff(value) {
5070
5070
 
5071
5071
  //#endregion
5072
5072
  //#region src/cli/commands/plan-refine.ts
5073
- const logger$17 = createLogger("plan-refine-cmd");
5073
+ const logger$19 = createLogger("plan-refine-cmd");
5074
5074
  const REFINE_EXIT_SUCCESS = 0;
5075
5075
  const REFINE_EXIT_ERROR = 1;
5076
5076
  const REFINE_EXIT_USAGE_ERROR = 2;
@@ -5112,7 +5112,7 @@ async function runPlanRefineAction(options) {
5112
5112
  let result;
5113
5113
  try {
5114
5114
  result = await refiner.refine(planId, feedback, (event, payload) => {
5115
- logger$17.info({
5115
+ logger$19.info({
5116
5116
  event,
5117
5117
  payload
5118
5118
  }, "Plan refinement event");
@@ -5155,7 +5155,7 @@ async function runPlanRefineAction(options) {
5155
5155
  } catch (err) {
5156
5156
  const message = err instanceof Error ? err.message : String(err);
5157
5157
  process.stderr.write(`Error: ${message}\n`);
5158
- logger$17.error({ err }, "runPlanRefineAction failed");
5158
+ logger$19.error({ err }, "runPlanRefineAction failed");
5159
5159
  return REFINE_EXIT_ERROR;
5160
5160
  } finally {
5161
5161
  dbWrapper.close();
@@ -5180,7 +5180,7 @@ function registerPlanRefineCommand(planCmd, _version = "0.0.0", projectRoot = pr
5180
5180
 
5181
5181
  //#endregion
5182
5182
  //#region src/cli/commands/plan-diff.ts
5183
- const logger$16 = createLogger("plan-diff-cmd");
5183
+ const logger$18 = createLogger("plan-diff-cmd");
5184
5184
  const DIFF_EXIT_SUCCESS = 0;
5185
5185
  const DIFF_EXIT_ERROR = 1;
5186
5186
  const DIFF_EXIT_NOT_FOUND = 2;
@@ -5223,7 +5223,7 @@ async function runPlanDiffAction(options) {
5223
5223
  } catch (err) {
5224
5224
  const message = err instanceof Error ? err.message : String(err);
5225
5225
  process.stderr.write(`Error: ${message}\n`);
5226
- logger$16.error({ err }, "runPlanDiffAction failed");
5226
+ logger$18.error({ err }, "runPlanDiffAction failed");
5227
5227
  return DIFF_EXIT_ERROR;
5228
5228
  } finally {
5229
5229
  dbWrapper.close();
@@ -5271,7 +5271,7 @@ function registerPlanDiffCommand(planCmd, _version = "0.0.0", projectRoot = proc
5271
5271
 
5272
5272
  //#endregion
5273
5273
  //#region src/cli/commands/plan-rollback.ts
5274
- const logger$15 = createLogger("plan-rollback-cmd");
5274
+ const logger$17 = createLogger("plan-rollback-cmd");
5275
5275
  const ROLLBACK_EXIT_SUCCESS = 0;
5276
5276
  const ROLLBACK_EXIT_ERROR = 1;
5277
5277
  const ROLLBACK_EXIT_USAGE_ERROR = 2;
@@ -5319,7 +5319,7 @@ async function runPlanRollbackAction(options, onEvent) {
5319
5319
  toVersion,
5320
5320
  newVersion
5321
5321
  });
5322
- logger$15.info({
5322
+ logger$17.info({
5323
5323
  planId,
5324
5324
  fromVersion,
5325
5325
  toVersion,
@@ -5360,7 +5360,7 @@ async function runPlanRollbackAction(options, onEvent) {
5360
5360
  } catch (err) {
5361
5361
  const message = err instanceof Error ? err.message : String(err);
5362
5362
  process.stderr.write(`Error: ${message}\n`);
5363
- logger$15.error({ err }, "runPlanRollbackAction failed");
5363
+ logger$17.error({ err }, "runPlanRollbackAction failed");
5364
5364
  return ROLLBACK_EXIT_ERROR;
5365
5365
  } finally {
5366
5366
  dbWrapper.close();
@@ -5554,7 +5554,7 @@ function validatePlan(raw, adapterRegistry, options) {
5554
5554
 
5555
5555
  //#endregion
5556
5556
  //#region src/cli/commands/plan.ts
5557
- const logger$14 = createLogger("plan-cmd");
5557
+ const logger$16 = createLogger("plan-cmd");
5558
5558
  const PLAN_EXIT_SUCCESS = 0;
5559
5559
  const PLAN_EXIT_ERROR = 1;
5560
5560
  const PLAN_EXIT_USAGE_ERROR = 2;
@@ -5698,7 +5698,7 @@ async function runPlanReviewAction(options) {
5698
5698
  }
5699
5699
  const message = err instanceof Error ? err.message : String(err);
5700
5700
  process.stderr.write(`Error: ${message}\n`);
5701
- logger$14.error({ err }, "runPlanReviewAction failed");
5701
+ logger$16.error({ err }, "runPlanReviewAction failed");
5702
5702
  return PLAN_EXIT_ERROR;
5703
5703
  }
5704
5704
  if (dryRun) {
@@ -5724,7 +5724,7 @@ async function runPlanReviewAction(options) {
5724
5724
  if (ext.endsWith(".yaml") || ext.endsWith(".yml")) taskGraph = load(planYaml);
5725
5725
  else taskGraph = JSON.parse(planYaml);
5726
5726
  } catch {
5727
- logger$14.warn("Could not read generated plan file for DB storage");
5727
+ logger$16.warn("Could not read generated plan file for DB storage");
5728
5728
  }
5729
5729
  if (outputFormat === "json") {
5730
5730
  const envelope = {
@@ -7181,7 +7181,7 @@ function truncateToTokens(text, maxTokens) {
7181
7181
 
7182
7182
  //#endregion
7183
7183
  //#region src/modules/context-compiler/context-compiler-impl.ts
7184
- const logger$13 = createLogger("context-compiler");
7184
+ const logger$15 = createLogger("context-compiler");
7185
7185
  /**
7186
7186
  * Fraction of the original token budget that must remain (after required +
7187
7187
  * important sections) before an optional section is included.
@@ -7273,7 +7273,7 @@ var ContextCompilerImpl = class {
7273
7273
  includedParts.push(truncated);
7274
7274
  remainingBudget -= truncatedTokens;
7275
7275
  anyTruncated = true;
7276
- logger$13.warn({
7276
+ logger$15.warn({
7277
7277
  section: section.name,
7278
7278
  originalTokens: tokens,
7279
7279
  budgetTokens: truncatedTokens
@@ -7287,7 +7287,7 @@ var ContextCompilerImpl = class {
7287
7287
  });
7288
7288
  } else {
7289
7289
  anyTruncated = true;
7290
- logger$13.warn({
7290
+ logger$15.warn({
7291
7291
  section: section.name,
7292
7292
  tokens
7293
7293
  }, "Context compiler: omitted \"important\" section — no budget remaining");
@@ -7314,7 +7314,7 @@ var ContextCompilerImpl = class {
7314
7314
  } else {
7315
7315
  if (tokens > 0) {
7316
7316
  anyTruncated = true;
7317
- logger$13.warn({
7317
+ logger$15.warn({
7318
7318
  section: section.name,
7319
7319
  tokens,
7320
7320
  budgetFractionRemaining: budgetFractionRemaining.toFixed(2)
@@ -7416,6 +7416,8 @@ const DEFAULT_TIMEOUTS = {
7416
7416
  "code-review": 9e5,
7417
7417
  "minor-fixes": 6e5,
7418
7418
  "major-rework": 9e5,
7419
+ "readiness-check": 6e5,
7420
+ "elicitation": 9e5,
7419
7421
  "analysis-vision": 18e4,
7420
7422
  "analysis-scope": 18e4,
7421
7423
  "planning-classification": 18e4,
@@ -7580,7 +7582,7 @@ function parseYamlResult(yamlText, schema) {
7580
7582
 
7581
7583
  //#endregion
7582
7584
  //#region src/modules/agent-dispatch/dispatcher-impl.ts
7583
- const logger$12 = createLogger("agent-dispatch");
7585
+ const logger$14 = createLogger("agent-dispatch");
7584
7586
  const SHUTDOWN_GRACE_MS = 1e4;
7585
7587
  const SHUTDOWN_MAX_WAIT_MS = 3e4;
7586
7588
  const CHARS_PER_TOKEN = 4;
@@ -7649,7 +7651,7 @@ var DispatcherImpl = class {
7649
7651
  resolve: typedResolve,
7650
7652
  reject
7651
7653
  });
7652
- logger$12.debug({
7654
+ logger$14.debug({
7653
7655
  id,
7654
7656
  queueLength: this._queue.length
7655
7657
  }, "Dispatch queued");
@@ -7679,7 +7681,7 @@ var DispatcherImpl = class {
7679
7681
  }
7680
7682
  async shutdown() {
7681
7683
  this._shuttingDown = true;
7682
- logger$12.info({
7684
+ logger$14.info({
7683
7685
  running: this._running.size,
7684
7686
  queued: this._queue.length
7685
7687
  }, "Dispatcher shutting down");
@@ -7712,13 +7714,13 @@ var DispatcherImpl = class {
7712
7714
  }
7713
7715
  }, 50);
7714
7716
  });
7715
- logger$12.info("Dispatcher shutdown complete");
7717
+ logger$14.info("Dispatcher shutdown complete");
7716
7718
  }
7717
7719
  async _startDispatch(id, request, resolve$2) {
7718
7720
  const { prompt, agent, taskType, timeout, outputSchema, workingDirectory, model, maxTurns } = request;
7719
7721
  const adapter = this._adapterRegistry.get(agent);
7720
7722
  if (adapter === void 0) {
7721
- logger$12.warn({
7723
+ logger$14.warn({
7722
7724
  id,
7723
7725
  agent
7724
7726
  }, "No adapter found for agent");
@@ -7762,7 +7764,7 @@ var DispatcherImpl = class {
7762
7764
  });
7763
7765
  const startedAt = Date.now();
7764
7766
  proc.on("error", (err) => {
7765
- logger$12.error({
7767
+ logger$14.error({
7766
7768
  id,
7767
7769
  binary: cmd.binary,
7768
7770
  error: err.message
@@ -7770,7 +7772,7 @@ var DispatcherImpl = class {
7770
7772
  });
7771
7773
  if (proc.stdin !== null) {
7772
7774
  proc.stdin.on("error", (err) => {
7773
- if (err.code !== "EPIPE") logger$12.warn({
7775
+ if (err.code !== "EPIPE") logger$14.warn({
7774
7776
  id,
7775
7777
  error: err.message
7776
7778
  }, "stdin write error");
@@ -7812,7 +7814,7 @@ var DispatcherImpl = class {
7812
7814
  agent,
7813
7815
  taskType
7814
7816
  });
7815
- logger$12.debug({
7817
+ logger$14.debug({
7816
7818
  id,
7817
7819
  agent,
7818
7820
  taskType,
@@ -7829,7 +7831,7 @@ var DispatcherImpl = class {
7829
7831
  dispatchId: id,
7830
7832
  timeoutMs
7831
7833
  });
7832
- logger$12.warn({
7834
+ logger$14.warn({
7833
7835
  id,
7834
7836
  agent,
7835
7837
  taskType,
@@ -7883,7 +7885,7 @@ var DispatcherImpl = class {
7883
7885
  exitCode: code,
7884
7886
  output: stdout
7885
7887
  });
7886
- logger$12.debug({
7888
+ logger$14.debug({
7887
7889
  id,
7888
7890
  agent,
7889
7891
  taskType,
@@ -7909,7 +7911,7 @@ var DispatcherImpl = class {
7909
7911
  error: stderr || `Process exited with code ${String(code)}`,
7910
7912
  exitCode: code
7911
7913
  });
7912
- logger$12.debug({
7914
+ logger$14.debug({
7913
7915
  id,
7914
7916
  agent,
7915
7917
  taskType,
@@ -7961,7 +7963,7 @@ var DispatcherImpl = class {
7961
7963
  const next = this._queue.shift();
7962
7964
  if (next === void 0) return;
7963
7965
  next.handle.status = "running";
7964
- logger$12.debug({
7966
+ logger$14.debug({
7965
7967
  id: next.id,
7966
7968
  queueLength: this._queue.length
7967
7969
  }, "Dequeued dispatch");
@@ -8320,9 +8322,120 @@ function getTokenUsageSummary(db, runId) {
8320
8322
  return stmt.all(runId);
8321
8323
  }
8322
8324
 
8325
+ //#endregion
8326
+ //#region src/persistence/queries/metrics.ts
8327
+ /**
8328
+ * Write or update run-level metrics.
8329
+ */
8330
+ function writeRunMetrics(db, input) {
8331
+ const stmt = db.prepare(`
8332
+ INSERT OR REPLACE INTO run_metrics (
8333
+ run_id, methodology, status, started_at, completed_at,
8334
+ wall_clock_seconds, total_input_tokens, total_output_tokens, total_cost_usd,
8335
+ stories_attempted, stories_succeeded, stories_failed, stories_escalated,
8336
+ total_review_cycles, total_dispatches, concurrency_setting, max_concurrent_actual, restarts,
8337
+ is_baseline
8338
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8339
+ `);
8340
+ stmt.run(input.run_id, input.methodology, input.status, input.started_at, input.completed_at ?? null, input.wall_clock_seconds ?? 0, input.total_input_tokens ?? 0, input.total_output_tokens ?? 0, input.total_cost_usd ?? 0, input.stories_attempted ?? 0, input.stories_succeeded ?? 0, input.stories_failed ?? 0, input.stories_escalated ?? 0, input.total_review_cycles ?? 0, input.total_dispatches ?? 0, input.concurrency_setting ?? 1, input.max_concurrent_actual ?? 1, input.restarts ?? 0, input.is_baseline ?? 0);
8341
+ }
8342
+ /**
8343
+ * Get run metrics for a specific run.
8344
+ */
8345
+ function getRunMetrics(db, runId) {
8346
+ return db.prepare("SELECT * FROM run_metrics WHERE run_id = ?").get(runId);
8347
+ }
8348
+ /**
8349
+ * List the most recent N run metrics rows, newest first.
8350
+ */
8351
+ function listRunMetrics(db, limit = 10) {
8352
+ return db.prepare("SELECT * FROM run_metrics ORDER BY started_at DESC LIMIT ?").all(limit);
8353
+ }
8354
+ /**
8355
+ * Tag a run as the baseline (clears any existing baseline first).
8356
+ */
8357
+ function tagRunAsBaseline(db, runId) {
8358
+ db.transaction(() => {
8359
+ db.prepare("UPDATE run_metrics SET is_baseline = 0").run();
8360
+ db.prepare("UPDATE run_metrics SET is_baseline = 1 WHERE run_id = ?").run(runId);
8361
+ })();
8362
+ }
8363
+ /**
8364
+ * Write or update story-level metrics.
8365
+ */
8366
+ function writeStoryMetrics(db, input) {
8367
+ const stmt = db.prepare(`
8368
+ INSERT INTO story_metrics (
8369
+ run_id, story_key, result, phase_durations_json, started_at, completed_at,
8370
+ wall_clock_seconds, input_tokens, output_tokens, cost_usd,
8371
+ review_cycles, dispatches
8372
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8373
+ ON CONFLICT(run_id, story_key) DO UPDATE SET
8374
+ result = excluded.result,
8375
+ phase_durations_json = excluded.phase_durations_json,
8376
+ started_at = COALESCE(excluded.started_at, story_metrics.started_at),
8377
+ completed_at = excluded.completed_at,
8378
+ wall_clock_seconds = excluded.wall_clock_seconds,
8379
+ input_tokens = excluded.input_tokens,
8380
+ output_tokens = excluded.output_tokens,
8381
+ cost_usd = excluded.cost_usd,
8382
+ review_cycles = excluded.review_cycles,
8383
+ dispatches = excluded.dispatches
8384
+ `);
8385
+ stmt.run(input.run_id, input.story_key, input.result, input.phase_durations_json ?? null, input.started_at ?? null, input.completed_at ?? null, input.wall_clock_seconds ?? 0, input.input_tokens ?? 0, input.output_tokens ?? 0, input.cost_usd ?? 0, input.review_cycles ?? 0, input.dispatches ?? 0);
8386
+ }
8387
+ /**
8388
+ * Compare two runs and return percentage deltas for key numeric fields.
8389
+ * Positive deltas mean run B is larger/longer than run A.
8390
+ * Returns null if either run does not exist.
8391
+ */
8392
+ function compareRunMetrics(db, runIdA, runIdB) {
8393
+ const a = getRunMetrics(db, runIdA);
8394
+ const b = getRunMetrics(db, runIdB);
8395
+ if (!a || !b) return null;
8396
+ const pct = (base, diff) => base === 0 ? 0 : Math.round(diff / base * 100 * 10) / 10;
8397
+ const inputDelta = b.total_input_tokens - a.total_input_tokens;
8398
+ const outputDelta = b.total_output_tokens - a.total_output_tokens;
8399
+ const clockDelta = (b.wall_clock_seconds ?? 0) - (a.wall_clock_seconds ?? 0);
8400
+ const cycleDelta = b.total_review_cycles - a.total_review_cycles;
8401
+ const costDelta = (b.total_cost_usd ?? 0) - (a.total_cost_usd ?? 0);
8402
+ return {
8403
+ run_id_a: runIdA,
8404
+ run_id_b: runIdB,
8405
+ token_input_delta: inputDelta,
8406
+ token_output_delta: outputDelta,
8407
+ token_input_pct: pct(a.total_input_tokens, inputDelta),
8408
+ token_output_pct: pct(a.total_output_tokens, outputDelta),
8409
+ wall_clock_delta_seconds: clockDelta,
8410
+ wall_clock_pct: pct(a.wall_clock_seconds ?? 0, clockDelta),
8411
+ review_cycles_delta: cycleDelta,
8412
+ review_cycles_pct: pct(a.total_review_cycles, cycleDelta),
8413
+ cost_delta: costDelta,
8414
+ cost_pct: pct(a.total_cost_usd ?? 0, costDelta)
8415
+ };
8416
+ }
8417
+ /**
8418
+ * Aggregate token usage from the token_usage table for a pipeline run.
8419
+ */
8420
+ function aggregateTokenUsageForRun(db, runId) {
8421
+ const row = db.prepare(`
8422
+ SELECT
8423
+ COALESCE(SUM(input_tokens), 0) as input,
8424
+ COALESCE(SUM(output_tokens), 0) as output,
8425
+ COALESCE(SUM(cost_usd), 0) as cost
8426
+ FROM token_usage
8427
+ WHERE pipeline_run_id = ?
8428
+ `).get(runId);
8429
+ return row ?? {
8430
+ input: 0,
8431
+ output: 0,
8432
+ cost: 0
8433
+ };
8434
+ }
8435
+
8323
8436
  //#endregion
8324
8437
  //#region src/modules/compiled-workflows/prompt-assembler.ts
8325
- const logger$11 = createLogger("compiled-workflows:prompt-assembler");
8438
+ const logger$13 = createLogger("compiled-workflows:prompt-assembler");
8326
8439
  /**
8327
8440
  * Assemble a final prompt from a template and sections map.
8328
8441
  *
@@ -8347,7 +8460,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
8347
8460
  tokenCount,
8348
8461
  truncated: false
8349
8462
  };
8350
- logger$11.warn({
8463
+ logger$13.warn({
8351
8464
  tokenCount,
8352
8465
  ceiling: tokenCeiling
8353
8466
  }, "Prompt exceeds token ceiling — truncating optional sections");
@@ -8363,10 +8476,10 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
8363
8476
  const targetSectionTokens = Math.max(0, currentSectionTokens - overBy);
8364
8477
  if (targetSectionTokens === 0) {
8365
8478
  contentMap[section.name] = "";
8366
- logger$11.warn({ sectionName: section.name }, "Section eliminated to fit token budget");
8479
+ logger$13.warn({ sectionName: section.name }, "Section eliminated to fit token budget");
8367
8480
  } else {
8368
8481
  contentMap[section.name] = truncateToTokens(section.content, targetSectionTokens);
8369
- logger$11.warn({
8482
+ logger$13.warn({
8370
8483
  sectionName: section.name,
8371
8484
  targetSectionTokens
8372
8485
  }, "Section truncated to fit token budget");
@@ -8377,7 +8490,7 @@ function assemblePrompt(template, sections, tokenCeiling = 2200) {
8377
8490
  }
8378
8491
  if (tokenCount <= tokenCeiling) break;
8379
8492
  }
8380
- if (tokenCount > tokenCeiling) logger$11.warn({
8493
+ if (tokenCount > tokenCeiling) logger$13.warn({
8381
8494
  tokenCount,
8382
8495
  ceiling: tokenCeiling
8383
8496
  }, "Required sections alone exceed token ceiling — returning over-budget prompt");
@@ -8518,7 +8631,7 @@ const CodeReviewResultSchema = z.object({
8518
8631
 
8519
8632
  //#endregion
8520
8633
  //#region src/modules/compiled-workflows/create-story.ts
8521
- const logger$10 = createLogger("compiled-workflows:create-story");
8634
+ const logger$12 = createLogger("compiled-workflows:create-story");
8522
8635
  /**
8523
8636
  * Hard ceiling for the assembled create-story prompt.
8524
8637
  */
@@ -8542,7 +8655,7 @@ const TOKEN_CEILING$2 = 3e3;
8542
8655
  */
8543
8656
  async function runCreateStory(deps, params) {
8544
8657
  const { epicId, storyKey, pipelineRunId } = params;
8545
- logger$10.debug({
8658
+ logger$12.debug({
8546
8659
  epicId,
8547
8660
  storyKey,
8548
8661
  pipelineRunId
@@ -8552,7 +8665,7 @@ async function runCreateStory(deps, params) {
8552
8665
  template = await deps.pack.getPrompt("create-story");
8553
8666
  } catch (err) {
8554
8667
  const error = err instanceof Error ? err.message : String(err);
8555
- logger$10.error({ error }, "Failed to retrieve create-story prompt template");
8668
+ logger$12.error({ error }, "Failed to retrieve create-story prompt template");
8556
8669
  return {
8557
8670
  result: "failed",
8558
8671
  error: `Failed to retrieve prompt template: ${error}`,
@@ -8594,7 +8707,7 @@ async function runCreateStory(deps, params) {
8594
8707
  priority: "important"
8595
8708
  }
8596
8709
  ], TOKEN_CEILING$2);
8597
- logger$10.debug({
8710
+ logger$12.debug({
8598
8711
  tokenCount,
8599
8712
  truncated,
8600
8713
  tokenCeiling: TOKEN_CEILING$2
@@ -8611,7 +8724,7 @@ async function runCreateStory(deps, params) {
8611
8724
  dispatchResult = await handle.result;
8612
8725
  } catch (err) {
8613
8726
  const error = err instanceof Error ? err.message : String(err);
8614
- logger$10.error({
8727
+ logger$12.error({
8615
8728
  epicId,
8616
8729
  storyKey,
8617
8730
  error
@@ -8632,7 +8745,7 @@ async function runCreateStory(deps, params) {
8632
8745
  if (dispatchResult.status === "failed") {
8633
8746
  const errorMsg = dispatchResult.parseError ?? `Dispatch failed with exit code ${dispatchResult.exitCode}`;
8634
8747
  const stderrDetail = dispatchResult.output ? ` Output: ${dispatchResult.output}` : "";
8635
- logger$10.warn({
8748
+ logger$12.warn({
8636
8749
  epicId,
8637
8750
  storyKey,
8638
8751
  exitCode: dispatchResult.exitCode
@@ -8644,7 +8757,7 @@ async function runCreateStory(deps, params) {
8644
8757
  };
8645
8758
  }
8646
8759
  if (dispatchResult.status === "timeout") {
8647
- logger$10.warn({
8760
+ logger$12.warn({
8648
8761
  epicId,
8649
8762
  storyKey
8650
8763
  }, "Create-story dispatch timed out");
@@ -8657,7 +8770,7 @@ async function runCreateStory(deps, params) {
8657
8770
  if (dispatchResult.parsed === null) {
8658
8771
  const details = dispatchResult.parseError ?? "No YAML block found in output";
8659
8772
  const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
8660
- logger$10.warn({
8773
+ logger$12.warn({
8661
8774
  epicId,
8662
8775
  storyKey,
8663
8776
  details,
@@ -8673,7 +8786,7 @@ async function runCreateStory(deps, params) {
8673
8786
  const parseResult = CreateStoryResultSchema.safeParse(dispatchResult.parsed);
8674
8787
  if (!parseResult.success) {
8675
8788
  const details = parseResult.error.message;
8676
- logger$10.warn({
8789
+ logger$12.warn({
8677
8790
  epicId,
8678
8791
  storyKey,
8679
8792
  details
@@ -8686,7 +8799,7 @@ async function runCreateStory(deps, params) {
8686
8799
  };
8687
8800
  }
8688
8801
  const parsed = parseResult.data;
8689
- logger$10.info({
8802
+ logger$12.info({
8690
8803
  epicId,
8691
8804
  storyKey,
8692
8805
  storyFile: parsed.story_file,
@@ -8708,7 +8821,7 @@ function getImplementationDecisions(deps) {
8708
8821
  try {
8709
8822
  return getDecisionsByPhase(deps.db, "implementation");
8710
8823
  } catch (err) {
8711
- logger$10.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve implementation decisions");
8824
+ logger$12.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve implementation decisions");
8712
8825
  return [];
8713
8826
  }
8714
8827
  }
@@ -8724,13 +8837,13 @@ function getEpicShard(decisions, epicId, projectRoot) {
8724
8837
  if (projectRoot) {
8725
8838
  const fallback = readEpicShardFromFile(projectRoot, epicId);
8726
8839
  if (fallback) {
8727
- logger$10.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
8840
+ logger$12.info({ epicId }, "Using file-based fallback for epic shard (decisions table empty)");
8728
8841
  return fallback;
8729
8842
  }
8730
8843
  }
8731
8844
  return "";
8732
8845
  } catch (err) {
8733
- logger$10.warn({
8846
+ logger$12.warn({
8734
8847
  epicId,
8735
8848
  error: err instanceof Error ? err.message : String(err)
8736
8849
  }, "Failed to retrieve epic shard");
@@ -8747,7 +8860,7 @@ function getPrevDevNotes(decisions, epicId) {
8747
8860
  if (devNotes.length === 0) return "";
8748
8861
  return devNotes[devNotes.length - 1].value;
8749
8862
  } catch (err) {
8750
- logger$10.warn({
8863
+ logger$12.warn({
8751
8864
  epicId,
8752
8865
  error: err instanceof Error ? err.message : String(err)
8753
8866
  }, "Failed to retrieve prev dev notes");
@@ -8767,13 +8880,13 @@ function getArchConstraints$1(deps) {
8767
8880
  if (deps.projectRoot) {
8768
8881
  const fallback = readArchConstraintsFromFile(deps.projectRoot);
8769
8882
  if (fallback) {
8770
- logger$10.info("Using file-based fallback for architecture constraints (decisions table empty)");
8883
+ logger$12.info("Using file-based fallback for architecture constraints (decisions table empty)");
8771
8884
  return fallback;
8772
8885
  }
8773
8886
  }
8774
8887
  return "";
8775
8888
  } catch (err) {
8776
- logger$10.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
8889
+ logger$12.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
8777
8890
  return "";
8778
8891
  }
8779
8892
  }
@@ -8793,7 +8906,7 @@ function readEpicShardFromFile(projectRoot, epicId) {
8793
8906
  const match = pattern.exec(content);
8794
8907
  return match ? match[0].trim() : "";
8795
8908
  } catch (err) {
8796
- logger$10.warn({
8909
+ logger$12.warn({
8797
8910
  epicId,
8798
8911
  error: err instanceof Error ? err.message : String(err)
8799
8912
  }, "File-based epic shard fallback failed");
@@ -8816,7 +8929,7 @@ function readArchConstraintsFromFile(projectRoot) {
8816
8929
  const content = readFileSync$1(archPath, "utf-8");
8817
8930
  return content.slice(0, 1500);
8818
8931
  } catch (err) {
8819
- logger$10.warn({ error: err instanceof Error ? err.message : String(err) }, "File-based architecture fallback failed");
8932
+ logger$12.warn({ error: err instanceof Error ? err.message : String(err) }, "File-based architecture fallback failed");
8820
8933
  return "";
8821
8934
  }
8822
8935
  }
@@ -8829,14 +8942,14 @@ async function getStoryTemplate(deps) {
8829
8942
  try {
8830
8943
  return await deps.pack.getTemplate("story");
8831
8944
  } catch (err) {
8832
- logger$10.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve story template from pack");
8945
+ logger$12.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve story template from pack");
8833
8946
  return "";
8834
8947
  }
8835
8948
  }
8836
8949
 
8837
8950
  //#endregion
8838
8951
  //#region src/modules/compiled-workflows/git-helpers.ts
8839
- const logger$9 = createLogger("compiled-workflows:git-helpers");
8952
+ const logger$11 = createLogger("compiled-workflows:git-helpers");
8840
8953
  /**
8841
8954
  * Capture the full git diff for HEAD (working tree vs current commit).
8842
8955
  *
@@ -8960,7 +9073,7 @@ async function runGitCommand(args, cwd, logLabel) {
8960
9073
  stderr += chunk.toString("utf-8");
8961
9074
  });
8962
9075
  proc.on("error", (err) => {
8963
- logger$9.warn({
9076
+ logger$11.warn({
8964
9077
  label: logLabel,
8965
9078
  cwd,
8966
9079
  error: err.message
@@ -8969,7 +9082,7 @@ async function runGitCommand(args, cwd, logLabel) {
8969
9082
  });
8970
9083
  proc.on("close", (code) => {
8971
9084
  if (code !== 0) {
8972
- logger$9.warn({
9085
+ logger$11.warn({
8973
9086
  label: logLabel,
8974
9087
  cwd,
8975
9088
  code,
@@ -8985,7 +9098,7 @@ async function runGitCommand(args, cwd, logLabel) {
8985
9098
 
8986
9099
  //#endregion
8987
9100
  //#region src/modules/compiled-workflows/dev-story.ts
8988
- const logger$8 = createLogger("compiled-workflows:dev-story");
9101
+ const logger$10 = createLogger("compiled-workflows:dev-story");
8989
9102
  /** Hard token ceiling for the assembled dev-story prompt */
8990
9103
  const TOKEN_CEILING$1 = 24e3;
8991
9104
  /** Default timeout for dev-story dispatches in milliseconds (30 min) */
@@ -9007,7 +9120,7 @@ const DEFAULT_VITEST_PATTERNS = `## Test Patterns (defaults)
9007
9120
  */
9008
9121
  async function runDevStory(deps, params) {
9009
9122
  const { storyKey, storyFilePath, taskScope, priorFiles } = params;
9010
- logger$8.info({
9123
+ logger$10.info({
9011
9124
  storyKey,
9012
9125
  storyFilePath
9013
9126
  }, "Starting compiled dev-story workflow");
@@ -9049,10 +9162,10 @@ async function runDevStory(deps, params) {
9049
9162
  let template;
9050
9163
  try {
9051
9164
  template = await deps.pack.getPrompt("dev-story");
9052
- logger$8.debug({ storyKey }, "Retrieved dev-story prompt template from pack");
9165
+ logger$10.debug({ storyKey }, "Retrieved dev-story prompt template from pack");
9053
9166
  } catch (err) {
9054
9167
  const error = err instanceof Error ? err.message : String(err);
9055
- logger$8.error({
9168
+ logger$10.error({
9056
9169
  storyKey,
9057
9170
  error
9058
9171
  }, "Failed to retrieve dev-story prompt template");
@@ -9063,14 +9176,14 @@ async function runDevStory(deps, params) {
9063
9176
  storyContent = await readFile$2(storyFilePath, "utf-8");
9064
9177
  } catch (err) {
9065
9178
  if (err.code === "ENOENT") {
9066
- logger$8.error({
9179
+ logger$10.error({
9067
9180
  storyKey,
9068
9181
  storyFilePath
9069
9182
  }, "Story file not found");
9070
9183
  return makeFailureResult("story_file_not_found");
9071
9184
  }
9072
9185
  const error = err instanceof Error ? err.message : String(err);
9073
- logger$8.error({
9186
+ logger$10.error({
9074
9187
  storyKey,
9075
9188
  storyFilePath,
9076
9189
  error
@@ -9078,7 +9191,7 @@ async function runDevStory(deps, params) {
9078
9191
  return makeFailureResult(`story_file_read_error: ${error}`);
9079
9192
  }
9080
9193
  if (storyContent.trim().length === 0) {
9081
- logger$8.error({
9194
+ logger$10.error({
9082
9195
  storyKey,
9083
9196
  storyFilePath
9084
9197
  }, "Story file is empty");
@@ -9090,17 +9203,17 @@ async function runDevStory(deps, params) {
9090
9203
  const testPatternDecisions = solutioningDecisions.filter((d) => d.category === "test-patterns");
9091
9204
  if (testPatternDecisions.length > 0) {
9092
9205
  testPatternsContent = "## Test Patterns\n" + testPatternDecisions.map((d) => `- ${d.key}: ${d.value}`).join("\n");
9093
- logger$8.debug({
9206
+ logger$10.debug({
9094
9207
  storyKey,
9095
9208
  count: testPatternDecisions.length
9096
9209
  }, "Loaded test patterns from decision store");
9097
9210
  } else {
9098
9211
  testPatternsContent = DEFAULT_VITEST_PATTERNS;
9099
- logger$8.debug({ storyKey }, "No test-pattern decisions found — using default Vitest patterns");
9212
+ logger$10.debug({ storyKey }, "No test-pattern decisions found — using default Vitest patterns");
9100
9213
  }
9101
9214
  } catch (err) {
9102
9215
  const error = err instanceof Error ? err.message : String(err);
9103
- logger$8.warn({
9216
+ logger$10.warn({
9104
9217
  storyKey,
9105
9218
  error
9106
9219
  }, "Failed to load test patterns — using defaults");
@@ -9143,7 +9256,7 @@ async function runDevStory(deps, params) {
9143
9256
  }
9144
9257
  ];
9145
9258
  const { prompt, tokenCount, truncated } = assemblePrompt(template, sections, TOKEN_CEILING$1);
9146
- logger$8.info({
9259
+ logger$10.info({
9147
9260
  storyKey,
9148
9261
  tokenCount,
9149
9262
  ceiling: TOKEN_CEILING$1,
@@ -9162,7 +9275,7 @@ async function runDevStory(deps, params) {
9162
9275
  dispatchResult = await handle.result;
9163
9276
  } catch (err) {
9164
9277
  const error = err instanceof Error ? err.message : String(err);
9165
- logger$8.error({
9278
+ logger$10.error({
9166
9279
  storyKey,
9167
9280
  error
9168
9281
  }, "Dispatch threw an unexpected error");
@@ -9173,11 +9286,11 @@ async function runDevStory(deps, params) {
9173
9286
  output: dispatchResult.tokenEstimate.output
9174
9287
  };
9175
9288
  if (dispatchResult.status === "timeout") {
9176
- logger$8.error({
9289
+ logger$10.error({
9177
9290
  storyKey,
9178
9291
  durationMs: dispatchResult.durationMs
9179
9292
  }, "Dev-story dispatch timed out");
9180
- if (dispatchResult.output.length > 0) logger$8.info({
9293
+ if (dispatchResult.output.length > 0) logger$10.info({
9181
9294
  storyKey,
9182
9295
  partialOutput: dispatchResult.output.slice(0, 500)
9183
9296
  }, "Partial output before timeout");
@@ -9187,12 +9300,12 @@ async function runDevStory(deps, params) {
9187
9300
  };
9188
9301
  }
9189
9302
  if (dispatchResult.status === "failed" || dispatchResult.exitCode !== 0) {
9190
- logger$8.error({
9303
+ logger$10.error({
9191
9304
  storyKey,
9192
9305
  exitCode: dispatchResult.exitCode,
9193
9306
  status: dispatchResult.status
9194
9307
  }, "Dev-story dispatch failed");
9195
- if (dispatchResult.output.length > 0) logger$8.info({
9308
+ if (dispatchResult.output.length > 0) logger$10.info({
9196
9309
  storyKey,
9197
9310
  partialOutput: dispatchResult.output.slice(0, 500)
9198
9311
  }, "Partial output from failed dispatch");
@@ -9204,7 +9317,7 @@ async function runDevStory(deps, params) {
9204
9317
  if (dispatchResult.parseError !== null || dispatchResult.parsed === null) {
9205
9318
  const details = dispatchResult.parseError ?? "parsed result was null";
9206
9319
  const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
9207
- logger$8.error({
9320
+ logger$10.error({
9208
9321
  storyKey,
9209
9322
  parseError: details,
9210
9323
  rawOutputSnippet: rawSnippet
@@ -9212,12 +9325,12 @@ async function runDevStory(deps, params) {
9212
9325
  let filesModified = [];
9213
9326
  try {
9214
9327
  filesModified = await getGitChangedFiles(deps.projectRoot ?? process.cwd());
9215
- if (filesModified.length > 0) logger$8.info({
9328
+ if (filesModified.length > 0) logger$10.info({
9216
9329
  storyKey,
9217
9330
  fileCount: filesModified.length
9218
9331
  }, "Recovered files_modified from git status (YAML fallback)");
9219
9332
  } catch (err) {
9220
- logger$8.warn({
9333
+ logger$10.warn({
9221
9334
  storyKey,
9222
9335
  error: err instanceof Error ? err.message : String(err)
9223
9336
  }, "Failed to recover files_modified from git");
@@ -9234,7 +9347,7 @@ async function runDevStory(deps, params) {
9234
9347
  };
9235
9348
  }
9236
9349
  const parsed = dispatchResult.parsed;
9237
- logger$8.info({
9350
+ logger$10.info({
9238
9351
  storyKey,
9239
9352
  result: parsed.result,
9240
9353
  acMet: parsed.ac_met.length
@@ -9373,7 +9486,7 @@ function extractFilesInScope(storyContent) {
9373
9486
 
9374
9487
  //#endregion
9375
9488
  //#region src/modules/compiled-workflows/code-review.ts
9376
- const logger$7 = createLogger("compiled-workflows:code-review");
9489
+ const logger$9 = createLogger("compiled-workflows:code-review");
9377
9490
  /**
9378
9491
  * Hard token ceiling for the assembled code-review prompt (50,000 tokens).
9379
9492
  * Quality reviews require seeing actual code diffs, not just file names.
@@ -9413,7 +9526,7 @@ function defaultFailResult(error, tokenUsage) {
9413
9526
  async function runCodeReview(deps, params) {
9414
9527
  const { storyKey, storyFilePath, workingDirectory, pipelineRunId, filesModified, previousIssues } = params;
9415
9528
  const cwd = workingDirectory ?? process.cwd();
9416
- logger$7.debug({
9529
+ logger$9.debug({
9417
9530
  storyKey,
9418
9531
  storyFilePath,
9419
9532
  cwd,
@@ -9424,7 +9537,7 @@ async function runCodeReview(deps, params) {
9424
9537
  template = await deps.pack.getPrompt("code-review");
9425
9538
  } catch (err) {
9426
9539
  const error = err instanceof Error ? err.message : String(err);
9427
- logger$7.error({ error }, "Failed to retrieve code-review prompt template");
9540
+ logger$9.error({ error }, "Failed to retrieve code-review prompt template");
9428
9541
  return defaultFailResult(`Failed to retrieve prompt template: ${error}`, {
9429
9542
  input: 0,
9430
9543
  output: 0
@@ -9435,7 +9548,7 @@ async function runCodeReview(deps, params) {
9435
9548
  storyContent = await readFile$2(storyFilePath, "utf-8");
9436
9549
  } catch (err) {
9437
9550
  const error = err instanceof Error ? err.message : String(err);
9438
- logger$7.error({
9551
+ logger$9.error({
9439
9552
  storyFilePath,
9440
9553
  error
9441
9554
  }, "Failed to read story file");
@@ -9455,12 +9568,12 @@ async function runCodeReview(deps, params) {
9455
9568
  const scopedTotal = nonDiffTokens + countTokens(scopedDiff);
9456
9569
  if (scopedTotal <= TOKEN_CEILING) {
9457
9570
  gitDiffContent = scopedDiff;
9458
- logger$7.debug({
9571
+ logger$9.debug({
9459
9572
  fileCount: filesModified.length,
9460
9573
  tokenCount: scopedTotal
9461
9574
  }, "Using scoped file diff");
9462
9575
  } else {
9463
- logger$7.warn({
9576
+ logger$9.warn({
9464
9577
  estimatedTotal: scopedTotal,
9465
9578
  ceiling: TOKEN_CEILING,
9466
9579
  fileCount: filesModified.length
@@ -9474,7 +9587,7 @@ async function runCodeReview(deps, params) {
9474
9587
  const fullTotal = nonDiffTokens + countTokens(fullDiff);
9475
9588
  if (fullTotal <= TOKEN_CEILING) gitDiffContent = fullDiff;
9476
9589
  else {
9477
- logger$7.warn({
9590
+ logger$9.warn({
9478
9591
  estimatedTotal: fullTotal,
9479
9592
  ceiling: TOKEN_CEILING
9480
9593
  }, "Full git diff would exceed token ceiling — using stat-only summary");
@@ -9512,11 +9625,11 @@ async function runCodeReview(deps, params) {
9512
9625
  }
9513
9626
  ];
9514
9627
  const assembleResult = assemblePrompt(template, sections, TOKEN_CEILING);
9515
- if (assembleResult.truncated) logger$7.warn({
9628
+ if (assembleResult.truncated) logger$9.warn({
9516
9629
  storyKey,
9517
9630
  tokenCount: assembleResult.tokenCount
9518
9631
  }, "Code-review prompt truncated to fit token ceiling");
9519
- logger$7.debug({
9632
+ logger$9.debug({
9520
9633
  storyKey,
9521
9634
  tokenCount: assembleResult.tokenCount,
9522
9635
  truncated: assembleResult.truncated
@@ -9534,7 +9647,7 @@ async function runCodeReview(deps, params) {
9534
9647
  dispatchResult = await handle.result;
9535
9648
  } catch (err) {
9536
9649
  const error = err instanceof Error ? err.message : String(err);
9537
- logger$7.error({
9650
+ logger$9.error({
9538
9651
  storyKey,
9539
9652
  error
9540
9653
  }, "Code-review dispatch threw unexpected error");
@@ -9550,7 +9663,7 @@ async function runCodeReview(deps, params) {
9550
9663
  const rawOutput = dispatchResult.output ?? void 0;
9551
9664
  if (dispatchResult.status === "failed") {
9552
9665
  const errorMsg = `Dispatch status: failed. Exit code: ${dispatchResult.exitCode}. ${dispatchResult.parseError ?? ""} ${dispatchResult.output ? `Stderr: ${dispatchResult.output}` : ""}`.trim();
9553
- logger$7.warn({
9666
+ logger$9.warn({
9554
9667
  storyKey,
9555
9668
  exitCode: dispatchResult.exitCode
9556
9669
  }, "Code-review dispatch failed");
@@ -9560,7 +9673,7 @@ async function runCodeReview(deps, params) {
9560
9673
  };
9561
9674
  }
9562
9675
  if (dispatchResult.status === "timeout") {
9563
- logger$7.warn({ storyKey }, "Code-review dispatch timed out");
9676
+ logger$9.warn({ storyKey }, "Code-review dispatch timed out");
9564
9677
  return {
9565
9678
  ...defaultFailResult("Dispatch status: timeout. The agent did not complete within the allowed time.", tokenUsage),
9566
9679
  rawOutput
@@ -9568,7 +9681,7 @@ async function runCodeReview(deps, params) {
9568
9681
  }
9569
9682
  if (dispatchResult.parsed === null) {
9570
9683
  const details = dispatchResult.parseError ?? "No YAML block found in output";
9571
- logger$7.warn({
9684
+ logger$9.warn({
9572
9685
  storyKey,
9573
9686
  details
9574
9687
  }, "Code-review output schema validation failed");
@@ -9585,7 +9698,7 @@ async function runCodeReview(deps, params) {
9585
9698
  const parseResult = CodeReviewResultSchema.safeParse(dispatchResult.parsed);
9586
9699
  if (!parseResult.success) {
9587
9700
  const details = parseResult.error.message;
9588
- logger$7.warn({
9701
+ logger$9.warn({
9589
9702
  storyKey,
9590
9703
  details
9591
9704
  }, "Code-review output failed schema validation");
@@ -9600,13 +9713,13 @@ async function runCodeReview(deps, params) {
9600
9713
  };
9601
9714
  }
9602
9715
  const parsed = parseResult.data;
9603
- if (parsed.agentVerdict !== parsed.verdict) logger$7.info({
9716
+ if (parsed.agentVerdict !== parsed.verdict) logger$9.info({
9604
9717
  storyKey,
9605
9718
  agentVerdict: parsed.agentVerdict,
9606
9719
  pipelineVerdict: parsed.verdict,
9607
9720
  issues: parsed.issues
9608
9721
  }, "Pipeline overrode agent verdict based on issue severities");
9609
- logger$7.info({
9722
+ logger$9.info({
9610
9723
  storyKey,
9611
9724
  verdict: parsed.verdict,
9612
9725
  issues: parsed.issues
@@ -9631,7 +9744,7 @@ function getArchConstraints(deps) {
9631
9744
  if (constraints.length === 0) return "";
9632
9745
  return constraints.map((d) => `${d.key}: ${d.value}`).join("\n");
9633
9746
  } catch (err) {
9634
- logger$7.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
9747
+ logger$9.warn({ error: err instanceof Error ? err.message : String(err) }, "Failed to retrieve architecture constraints");
9635
9748
  return "";
9636
9749
  }
9637
9750
  }
@@ -9963,7 +10076,7 @@ function detectConflictGroups(storyKeys, config) {
9963
10076
 
9964
10077
  //#endregion
9965
10078
  //#region src/modules/implementation-orchestrator/seed-methodology-context.ts
9966
- const logger$6 = createLogger("implementation-orchestrator:seed");
10079
+ const logger$8 = createLogger("implementation-orchestrator:seed");
9967
10080
  /** Max chars for the architecture summary seeded into decisions */
9968
10081
  const MAX_ARCH_CHARS = 6e3;
9969
10082
  /** Max chars per epic shard */
@@ -9997,12 +10110,12 @@ function seedMethodologyContext(db, projectRoot) {
9997
10110
  const testCount = seedTestPatterns(db, projectRoot);
9998
10111
  if (testCount === -1) result.skippedCategories.push("test-patterns");
9999
10112
  else result.decisionsCreated += testCount;
10000
- logger$6.info({
10113
+ logger$8.info({
10001
10114
  decisionsCreated: result.decisionsCreated,
10002
10115
  skippedCategories: result.skippedCategories
10003
10116
  }, "Methodology context seeding complete");
10004
10117
  } catch (err) {
10005
- logger$6.warn({ error: err instanceof Error ? err.message : String(err) }, "Methodology context seeding failed (non-fatal)");
10118
+ logger$8.warn({ error: err instanceof Error ? err.message : String(err) }, "Methodology context seeding failed (non-fatal)");
10006
10119
  }
10007
10120
  return result;
10008
10121
  }
@@ -10046,7 +10159,7 @@ function seedArchitecture(db, projectRoot) {
10046
10159
  });
10047
10160
  count = 1;
10048
10161
  }
10049
- logger$6.debug({ count }, "Seeded architecture decisions");
10162
+ logger$8.debug({ count }, "Seeded architecture decisions");
10050
10163
  return count;
10051
10164
  }
10052
10165
  /**
@@ -10074,7 +10187,7 @@ function seedEpicShards(db, projectRoot) {
10074
10187
  });
10075
10188
  count++;
10076
10189
  }
10077
- logger$6.debug({ count }, "Seeded epic shard decisions");
10190
+ logger$8.debug({ count }, "Seeded epic shard decisions");
10078
10191
  return count;
10079
10192
  }
10080
10193
  /**
@@ -10095,7 +10208,7 @@ function seedTestPatterns(db, projectRoot) {
10095
10208
  value: patterns.slice(0, MAX_TEST_PATTERNS_CHARS),
10096
10209
  rationale: "Detected from project configuration at orchestrator startup"
10097
10210
  });
10098
- logger$6.debug("Seeded test patterns decision");
10211
+ logger$8.debug("Seeded test patterns decision");
10099
10212
  return 1;
10100
10213
  }
10101
10214
  /**
@@ -10287,7 +10400,7 @@ function createPauseGate() {
10287
10400
  */
10288
10401
  function createImplementationOrchestrator(deps) {
10289
10402
  const { db, pack, contextCompiler, dispatcher, eventBus, config, projectRoot } = deps;
10290
- const logger$33 = createLogger("implementation-orchestrator");
10403
+ const logger$35 = createLogger("implementation-orchestrator");
10291
10404
  let _state = "IDLE";
10292
10405
  let _startedAt;
10293
10406
  let _completedAt;
@@ -10299,6 +10412,56 @@ function createImplementationOrchestrator(deps) {
10299
10412
  let _heartbeatTimer = null;
10300
10413
  const HEARTBEAT_INTERVAL_MS = 3e4;
10301
10414
  const WATCHDOG_TIMEOUT_MS = 6e5;
10415
+ const _phaseStartMs = new Map();
10416
+ const _phaseEndMs = new Map();
10417
+ const _storyDispatches = new Map();
10418
+ function startPhase(storyKey, phase) {
10419
+ if (!_phaseStartMs.has(storyKey)) _phaseStartMs.set(storyKey, new Map());
10420
+ _phaseStartMs.get(storyKey).set(phase, Date.now());
10421
+ }
10422
+ function endPhase(storyKey, phase) {
10423
+ if (!_phaseEndMs.has(storyKey)) _phaseEndMs.set(storyKey, new Map());
10424
+ _phaseEndMs.get(storyKey).set(phase, Date.now());
10425
+ }
10426
+ function incrementDispatches(storyKey) {
10427
+ _storyDispatches.set(storyKey, (_storyDispatches.get(storyKey) ?? 0) + 1);
10428
+ }
10429
+ function buildPhaseDurationsJson(storyKey) {
10430
+ const starts = _phaseStartMs.get(storyKey);
10431
+ const ends = _phaseEndMs.get(storyKey);
10432
+ if (!starts || starts.size === 0) return "{}";
10433
+ const durations = {};
10434
+ for (const [phase, startMs] of starts) {
10435
+ const endMs = ends?.get(phase) ?? Date.now();
10436
+ durations[phase] = Math.round((endMs - startMs) / 1e3);
10437
+ }
10438
+ return JSON.stringify(durations);
10439
+ }
10440
+ function writeStoryMetricsBestEffort(storyKey, result, reviewCycles) {
10441
+ if (config.pipelineRunId === void 0) return;
10442
+ try {
10443
+ const storyState = _stories.get(storyKey);
10444
+ const startedAt = storyState?.startedAt;
10445
+ const completedAt = storyState?.completedAt ?? new Date().toISOString();
10446
+ const wallClockSeconds = startedAt ? Math.round((new Date(completedAt).getTime() - new Date(startedAt).getTime()) / 1e3) : 0;
10447
+ writeStoryMetrics(db, {
10448
+ run_id: config.pipelineRunId,
10449
+ story_key: storyKey,
10450
+ result,
10451
+ phase_durations_json: buildPhaseDurationsJson(storyKey),
10452
+ started_at: startedAt,
10453
+ completed_at: completedAt,
10454
+ wall_clock_seconds: wallClockSeconds,
10455
+ review_cycles: reviewCycles,
10456
+ dispatches: _storyDispatches.get(storyKey) ?? 0
10457
+ });
10458
+ } catch (err) {
10459
+ logger$35.warn({
10460
+ err,
10461
+ storyKey
10462
+ }, "Failed to write story metrics (best-effort)");
10463
+ }
10464
+ }
10302
10465
  function getStatus() {
10303
10466
  const stories = {};
10304
10467
  for (const [key, s] of _stories) stories[key] = { ...s };
@@ -10328,7 +10491,7 @@ function createImplementationOrchestrator(deps) {
10328
10491
  token_usage_json: serialized
10329
10492
  });
10330
10493
  } catch (err) {
10331
- logger$33.warn("Failed to persist orchestrator state", { err });
10494
+ logger$35.warn("Failed to persist orchestrator state", { err });
10332
10495
  }
10333
10496
  }
10334
10497
  function recordProgress() {
@@ -10353,7 +10516,7 @@ function createImplementationOrchestrator(deps) {
10353
10516
  const elapsed = Date.now() - _lastProgressTs;
10354
10517
  if (elapsed >= WATCHDOG_TIMEOUT_MS) {
10355
10518
  for (const [key, s] of _stories) if (s.phase !== "PENDING" && s.phase !== "COMPLETE" && s.phase !== "ESCALATED") {
10356
- logger$33.warn({
10519
+ logger$35.warn({
10357
10520
  storyKey: key,
10358
10521
  phase: s.phase,
10359
10522
  elapsedMs: elapsed
@@ -10389,9 +10552,10 @@ function createImplementationOrchestrator(deps) {
10389
10552
  * exhausted retries the story is ESCALATED.
10390
10553
  */
10391
10554
  async function processStory(storyKey) {
10392
- logger$33.info("Processing story", { storyKey });
10555
+ logger$35.info("Processing story", { storyKey });
10393
10556
  await waitIfPaused();
10394
10557
  if (_state !== "RUNNING") return;
10558
+ startPhase(storyKey, "create-story");
10395
10559
  updateStory(storyKey, {
10396
10560
  phase: "IN_STORY_CREATION",
10397
10561
  startedAt: new Date().toISOString()
@@ -10403,7 +10567,7 @@ function createImplementationOrchestrator(deps) {
10403
10567
  const match = files.find((f) => f.startsWith(`${storyKey}-`) && f.endsWith(".md"));
10404
10568
  if (match) {
10405
10569
  storyFilePath = join$1(artifactsDir, match);
10406
- logger$33.info({
10570
+ logger$35.info({
10407
10571
  storyKey,
10408
10572
  storyFilePath
10409
10573
  }, "Found existing story file — skipping create-story");
@@ -10420,6 +10584,7 @@ function createImplementationOrchestrator(deps) {
10420
10584
  }
10421
10585
  } catch {}
10422
10586
  if (storyFilePath === void 0) try {
10587
+ incrementDispatches(storyKey);
10423
10588
  const createResult = await runCreateStory({
10424
10589
  db,
10425
10590
  pack,
@@ -10431,6 +10596,7 @@ function createImplementationOrchestrator(deps) {
10431
10596
  storyKey,
10432
10597
  pipelineRunId: config.pipelineRunId
10433
10598
  });
10599
+ endPhase(storyKey, "create-story");
10434
10600
  eventBus.emit("orchestrator:story-phase-complete", {
10435
10601
  storyKey,
10436
10602
  phase: "IN_STORY_CREATION",
@@ -10444,6 +10610,7 @@ function createImplementationOrchestrator(deps) {
10444
10610
  error: errMsg,
10445
10611
  completedAt: new Date().toISOString()
10446
10612
  });
10613
+ writeStoryMetricsBestEffort(storyKey, "failed", 0);
10447
10614
  eventBus.emit("orchestrator:story-escalated", {
10448
10615
  storyKey,
10449
10616
  lastVerdict: "create-story-failed",
@@ -10460,6 +10627,7 @@ function createImplementationOrchestrator(deps) {
10460
10627
  error: errMsg,
10461
10628
  completedAt: new Date().toISOString()
10462
10629
  });
10630
+ writeStoryMetricsBestEffort(storyKey, "failed", 0);
10463
10631
  eventBus.emit("orchestrator:story-escalated", {
10464
10632
  storyKey,
10465
10633
  lastVerdict: "create-story-no-file",
@@ -10472,11 +10640,13 @@ function createImplementationOrchestrator(deps) {
10472
10640
  storyFilePath = createResult.story_file;
10473
10641
  } catch (err) {
10474
10642
  const errMsg = err instanceof Error ? err.message : String(err);
10643
+ endPhase(storyKey, "create-story");
10475
10644
  updateStory(storyKey, {
10476
10645
  phase: "ESCALATED",
10477
10646
  error: errMsg,
10478
10647
  completedAt: new Date().toISOString()
10479
10648
  });
10649
+ writeStoryMetricsBestEffort(storyKey, "failed", 0);
10480
10650
  eventBus.emit("orchestrator:story-escalated", {
10481
10651
  storyKey,
10482
10652
  lastVerdict: "create-story-exception",
@@ -10488,6 +10658,7 @@ function createImplementationOrchestrator(deps) {
10488
10658
  }
10489
10659
  await waitIfPaused();
10490
10660
  if (_state !== "RUNNING") return;
10661
+ startPhase(storyKey, "dev-story");
10491
10662
  updateStory(storyKey, { phase: "IN_DEV" });
10492
10663
  persistState();
10493
10664
  let devFilesModified = [];
@@ -10497,7 +10668,7 @@ function createImplementationOrchestrator(deps) {
10497
10668
  try {
10498
10669
  storyContentForAnalysis = await readFile$2(storyFilePath ?? "", "utf-8");
10499
10670
  } catch (err) {
10500
- logger$33.error({
10671
+ logger$35.error({
10501
10672
  storyKey,
10502
10673
  storyFilePath,
10503
10674
  error: err instanceof Error ? err.message : String(err)
@@ -10505,7 +10676,7 @@ function createImplementationOrchestrator(deps) {
10505
10676
  }
10506
10677
  const analysis = analyzeStoryComplexity(storyContentForAnalysis);
10507
10678
  const batches = planTaskBatches(analysis);
10508
- logger$33.info({
10679
+ logger$35.info({
10509
10680
  storyKey,
10510
10681
  estimatedScope: analysis.estimatedScope,
10511
10682
  batchCount: batches.length,
@@ -10523,12 +10694,13 @@ function createImplementationOrchestrator(deps) {
10523
10694
  if (_state !== "RUNNING") break;
10524
10695
  const taskScope = batch.taskIds.map((id, i) => `T${id}: ${batch.taskTitles[i] ?? ""}`).join("\n");
10525
10696
  const priorFiles = allFilesModified.size > 0 ? Array.from(allFilesModified) : void 0;
10526
- logger$33.info({
10697
+ logger$35.info({
10527
10698
  storyKey,
10528
10699
  batchIndex: batch.batchIndex,
10529
10700
  taskCount: batch.taskIds.length
10530
10701
  }, "Dispatching dev-story batch");
10531
10702
  const batchStartMs = Date.now();
10703
+ incrementDispatches(storyKey);
10532
10704
  let batchResult;
10533
10705
  try {
10534
10706
  batchResult = await runDevStory({
@@ -10546,7 +10718,7 @@ function createImplementationOrchestrator(deps) {
10546
10718
  });
10547
10719
  } catch (batchErr) {
10548
10720
  const errMsg = batchErr instanceof Error ? batchErr.message : String(batchErr);
10549
- logger$33.warn({
10721
+ logger$35.warn({
10550
10722
  storyKey,
10551
10723
  batchIndex: batch.batchIndex,
10552
10724
  error: errMsg
@@ -10566,7 +10738,7 @@ function createImplementationOrchestrator(deps) {
10566
10738
  filesModified: batchFilesModified,
10567
10739
  result: batchResult.result === "success" ? "success" : "failed"
10568
10740
  };
10569
- logger$33.info(batchMetrics, "Batch dev-story metrics");
10741
+ logger$35.info(batchMetrics, "Batch dev-story metrics");
10570
10742
  for (const f of batchFilesModified) allFilesModified.add(f);
10571
10743
  if (batchFilesModified.length > 0) batchFileGroups.push({
10572
10744
  batchIndex: batch.batchIndex,
@@ -10588,13 +10760,13 @@ function createImplementationOrchestrator(deps) {
10588
10760
  })
10589
10761
  });
10590
10762
  } catch (tokenErr) {
10591
- logger$33.warn({
10763
+ logger$35.warn({
10592
10764
  storyKey,
10593
10765
  batchIndex: batch.batchIndex,
10594
10766
  err: tokenErr
10595
10767
  }, "Failed to record batch token usage");
10596
10768
  }
10597
- if (batchResult.result === "failed") logger$33.warn({
10769
+ if (batchResult.result === "failed") logger$35.warn({
10598
10770
  storyKey,
10599
10771
  batchIndex: batch.batchIndex,
10600
10772
  error: batchResult.error
@@ -10608,6 +10780,7 @@ function createImplementationOrchestrator(deps) {
10608
10780
  }
10609
10781
  devFilesModified = Array.from(allFilesModified);
10610
10782
  } else {
10783
+ incrementDispatches(storyKey);
10611
10784
  const devResult = await runDevStory({
10612
10785
  db,
10613
10786
  pack,
@@ -10626,7 +10799,7 @@ function createImplementationOrchestrator(deps) {
10626
10799
  result: devResult
10627
10800
  });
10628
10801
  persistState();
10629
- if (devResult.result === "failed") logger$33.warn("Dev-story reported failure, proceeding to code review", {
10802
+ if (devResult.result === "failed") logger$35.warn("Dev-story reported failure, proceeding to code review", {
10630
10803
  storyKey,
10631
10804
  error: devResult.error,
10632
10805
  filesModified: devFilesModified.length
@@ -10634,11 +10807,13 @@ function createImplementationOrchestrator(deps) {
10634
10807
  }
10635
10808
  } catch (err) {
10636
10809
  const errMsg = err instanceof Error ? err.message : String(err);
10810
+ endPhase(storyKey, "dev-story");
10637
10811
  updateStory(storyKey, {
10638
10812
  phase: "ESCALATED",
10639
10813
  error: errMsg,
10640
10814
  completedAt: new Date().toISOString()
10641
10815
  });
10816
+ writeStoryMetricsBestEffort(storyKey, "failed", 0);
10642
10817
  eventBus.emit("orchestrator:story-escalated", {
10643
10818
  storyKey,
10644
10819
  lastVerdict: "dev-story-exception",
@@ -10648,6 +10823,7 @@ function createImplementationOrchestrator(deps) {
10648
10823
  persistState();
10649
10824
  return;
10650
10825
  }
10826
+ endPhase(storyKey, "dev-story");
10651
10827
  let reviewCycles = 0;
10652
10828
  let keepReviewing = true;
10653
10829
  let timeoutRetried = false;
@@ -10655,6 +10831,7 @@ function createImplementationOrchestrator(deps) {
10655
10831
  while (keepReviewing) {
10656
10832
  await waitIfPaused();
10657
10833
  if (_state !== "RUNNING") return;
10834
+ if (reviewCycles === 0) startPhase(storyKey, "code-review");
10658
10835
  updateStory(storyKey, {
10659
10836
  phase: "IN_REVIEW",
10660
10837
  reviewCycles
@@ -10679,11 +10856,12 @@ function createImplementationOrchestrator(deps) {
10679
10856
  "NEEDS_MAJOR_REWORK": 2
10680
10857
  };
10681
10858
  for (const group of batchFileGroups) {
10682
- logger$33.info({
10859
+ logger$35.info({
10683
10860
  storyKey,
10684
10861
  batchIndex: group.batchIndex,
10685
10862
  fileCount: group.files.length
10686
10863
  }, "Running batched code review");
10864
+ incrementDispatches(storyKey);
10687
10865
  const batchReview = await runCodeReview({
10688
10866
  db,
10689
10867
  pack,
@@ -10715,30 +10893,33 @@ function createImplementationOrchestrator(deps) {
10715
10893
  rawOutput: lastRawOutput,
10716
10894
  tokenUsage: aggregateTokens
10717
10895
  };
10718
- logger$33.info({
10896
+ logger$35.info({
10719
10897
  storyKey,
10720
10898
  batchCount: batchFileGroups.length,
10721
10899
  verdict: worstVerdict,
10722
10900
  issues: allIssues.length
10723
10901
  }, "Batched code review complete — aggregate result");
10724
- } else reviewResult = await runCodeReview({
10725
- db,
10726
- pack,
10727
- contextCompiler,
10728
- dispatcher,
10729
- projectRoot
10730
- }, {
10731
- storyKey,
10732
- storyFilePath: storyFilePath ?? "",
10733
- workingDirectory: projectRoot,
10734
- pipelineRunId: config.pipelineRunId,
10735
- filesModified: devFilesModified,
10736
- ...previousIssueList.length > 0 ? { previousIssues: previousIssueList } : {}
10737
- });
10902
+ } else {
10903
+ incrementDispatches(storyKey);
10904
+ reviewResult = await runCodeReview({
10905
+ db,
10906
+ pack,
10907
+ contextCompiler,
10908
+ dispatcher,
10909
+ projectRoot
10910
+ }, {
10911
+ storyKey,
10912
+ storyFilePath: storyFilePath ?? "",
10913
+ workingDirectory: projectRoot,
10914
+ pipelineRunId: config.pipelineRunId,
10915
+ filesModified: devFilesModified,
10916
+ ...previousIssueList.length > 0 ? { previousIssues: previousIssueList } : {}
10917
+ });
10918
+ }
10738
10919
  const isPhantomReview = reviewResult.verdict !== "SHIP_IT" && (reviewResult.issue_list === void 0 || reviewResult.issue_list.length === 0) && reviewResult.error !== void 0;
10739
10920
  if (isPhantomReview && !timeoutRetried) {
10740
10921
  timeoutRetried = true;
10741
- logger$33.warn({
10922
+ logger$35.warn({
10742
10923
  storyKey,
10743
10924
  reviewCycles,
10744
10925
  error: reviewResult.error
@@ -10748,7 +10929,7 @@ function createImplementationOrchestrator(deps) {
10748
10929
  verdict = reviewResult.verdict;
10749
10930
  issueList = reviewResult.issue_list ?? [];
10750
10931
  if (verdict === "NEEDS_MAJOR_REWORK" && reviewCycles > 0 && previousIssueList.length > 0 && issueList.length < previousIssueList.length) {
10751
- logger$33.info({
10932
+ logger$35.info({
10752
10933
  storyKey,
10753
10934
  originalVerdict: verdict,
10754
10935
  issuesBefore: previousIssueList.length,
@@ -10784,7 +10965,7 @@ function createImplementationOrchestrator(deps) {
10784
10965
  if (_decomposition !== void 0) parts.push(`decomposed: ${_decomposition.batchCount} batches`);
10785
10966
  parts.push(`${fileCount} files`);
10786
10967
  parts.push(`${totalTokensK} tokens`);
10787
- logger$33.info({
10968
+ logger$35.info({
10788
10969
  storyKey,
10789
10970
  verdict,
10790
10971
  agentVerdict: reviewResult.agentVerdict
@@ -10792,11 +10973,13 @@ function createImplementationOrchestrator(deps) {
10792
10973
  }
10793
10974
  } catch (err) {
10794
10975
  const errMsg = err instanceof Error ? err.message : String(err);
10976
+ endPhase(storyKey, "code-review");
10795
10977
  updateStory(storyKey, {
10796
10978
  phase: "ESCALATED",
10797
10979
  error: errMsg,
10798
10980
  completedAt: new Date().toISOString()
10799
10981
  });
10982
+ writeStoryMetricsBestEffort(storyKey, "failed", reviewCycles);
10800
10983
  eventBus.emit("orchestrator:story-escalated", {
10801
10984
  storyKey,
10802
10985
  lastVerdict: "code-review-exception",
@@ -10807,10 +10990,12 @@ function createImplementationOrchestrator(deps) {
10807
10990
  return;
10808
10991
  }
10809
10992
  if (verdict === "SHIP_IT") {
10993
+ endPhase(storyKey, "code-review");
10810
10994
  updateStory(storyKey, {
10811
10995
  phase: "COMPLETE",
10812
10996
  completedAt: new Date().toISOString()
10813
10997
  });
10998
+ writeStoryMetricsBestEffort(storyKey, "success", reviewCycles + 1);
10814
10999
  eventBus.emit("orchestrator:story-complete", {
10815
11000
  storyKey,
10816
11001
  reviewCycles
@@ -10822,11 +11007,13 @@ function createImplementationOrchestrator(deps) {
10822
11007
  if (reviewCycles >= config.maxReviewCycles - 1) {
10823
11008
  const finalReviewCycles = reviewCycles + 1;
10824
11009
  if (verdict !== "NEEDS_MINOR_FIXES") {
11010
+ endPhase(storyKey, "code-review");
10825
11011
  updateStory(storyKey, {
10826
11012
  phase: "ESCALATED",
10827
11013
  reviewCycles: finalReviewCycles,
10828
11014
  completedAt: new Date().toISOString()
10829
11015
  });
11016
+ writeStoryMetricsBestEffort(storyKey, "escalated", finalReviewCycles);
10830
11017
  eventBus.emit("orchestrator:story-escalated", {
10831
11018
  storyKey,
10832
11019
  lastVerdict: verdict,
@@ -10836,7 +11023,7 @@ function createImplementationOrchestrator(deps) {
10836
11023
  persistState();
10837
11024
  return;
10838
11025
  }
10839
- logger$33.info({
11026
+ logger$35.info({
10840
11027
  storyKey,
10841
11028
  reviewCycles: finalReviewCycles,
10842
11029
  issueCount: issueList.length
@@ -10886,7 +11073,7 @@ function createImplementationOrchestrator(deps) {
10886
11073
  fixPrompt = assembled.prompt;
10887
11074
  } catch {
10888
11075
  fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, minor fixes needed`;
10889
- logger$33.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
11076
+ logger$35.warn("Failed to assemble auto-approve fix prompt, using fallback", { storyKey });
10890
11077
  }
10891
11078
  const handle = dispatcher.dispatch({
10892
11079
  prompt: fixPrompt,
@@ -10903,18 +11090,20 @@ function createImplementationOrchestrator(deps) {
10903
11090
  output: fixResult.tokenEstimate.output
10904
11091
  } : void 0 }
10905
11092
  });
10906
- if (fixResult.status === "timeout") logger$33.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
11093
+ if (fixResult.status === "timeout") logger$35.warn("Auto-approve fix timed out — approving anyway (issues were minor)", { storyKey });
10907
11094
  } catch (err) {
10908
- logger$33.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
11095
+ logger$35.warn("Auto-approve fix dispatch failed — approving anyway (issues were minor)", {
10909
11096
  storyKey,
10910
11097
  err
10911
11098
  });
10912
11099
  }
11100
+ endPhase(storyKey, "code-review");
10913
11101
  updateStory(storyKey, {
10914
11102
  phase: "COMPLETE",
10915
11103
  reviewCycles: finalReviewCycles,
10916
11104
  completedAt: new Date().toISOString()
10917
11105
  });
11106
+ writeStoryMetricsBestEffort(storyKey, "success", finalReviewCycles);
10918
11107
  eventBus.emit("orchestrator:story-complete", {
10919
11108
  storyKey,
10920
11109
  reviewCycles: finalReviewCycles
@@ -10975,11 +11164,12 @@ function createImplementationOrchestrator(deps) {
10975
11164
  fixPrompt = assembled.prompt;
10976
11165
  } catch {
10977
11166
  fixPrompt = `Fix story ${storyKey}: verdict=${verdict}, taskType=${taskType}`;
10978
- logger$33.warn("Failed to assemble fix prompt, using fallback", {
11167
+ logger$35.warn("Failed to assemble fix prompt, using fallback", {
10979
11168
  storyKey,
10980
11169
  taskType
10981
11170
  });
10982
11171
  }
11172
+ incrementDispatches(storyKey);
10983
11173
  const handle = dispatcher.dispatch({
10984
11174
  prompt: fixPrompt,
10985
11175
  agent: "claude-code",
@@ -10997,15 +11187,17 @@ function createImplementationOrchestrator(deps) {
10997
11187
  } : void 0 }
10998
11188
  });
10999
11189
  if (fixResult.status === "timeout") {
11000
- logger$33.warn("Fix dispatch timed out — escalating story", {
11190
+ logger$35.warn("Fix dispatch timed out — escalating story", {
11001
11191
  storyKey,
11002
11192
  taskType
11003
11193
  });
11194
+ endPhase(storyKey, "code-review");
11004
11195
  updateStory(storyKey, {
11005
11196
  phase: "ESCALATED",
11006
11197
  error: `fix-dispatch-timeout (${taskType})`,
11007
11198
  completedAt: new Date().toISOString()
11008
11199
  });
11200
+ writeStoryMetricsBestEffort(storyKey, "escalated", reviewCycles + 1);
11009
11201
  eventBus.emit("orchestrator:story-escalated", {
11010
11202
  storyKey,
11011
11203
  lastVerdict: verdict,
@@ -11015,13 +11207,13 @@ function createImplementationOrchestrator(deps) {
11015
11207
  persistState();
11016
11208
  return;
11017
11209
  }
11018
- if (fixResult.status === "failed") logger$33.warn("Fix dispatch failed", {
11210
+ if (fixResult.status === "failed") logger$35.warn("Fix dispatch failed", {
11019
11211
  storyKey,
11020
11212
  taskType,
11021
11213
  exitCode: fixResult.exitCode
11022
11214
  });
11023
11215
  } catch (err) {
11024
- logger$33.warn("Fix dispatch failed, continuing to next review", {
11216
+ logger$35.warn("Fix dispatch failed, continuing to next review", {
11025
11217
  storyKey,
11026
11218
  taskType,
11027
11219
  err
@@ -11074,11 +11266,11 @@ function createImplementationOrchestrator(deps) {
11074
11266
  }
11075
11267
  async function run(storyKeys) {
11076
11268
  if (_state === "RUNNING" || _state === "PAUSED") {
11077
- logger$33.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
11269
+ logger$35.warn("run() called while orchestrator is already running or paused — ignoring", { state: _state });
11078
11270
  return getStatus();
11079
11271
  }
11080
11272
  if (_state === "COMPLETE") {
11081
- logger$33.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
11273
+ logger$35.warn("run() called on a COMPLETE orchestrator — ignoring", { state: _state });
11082
11274
  return getStatus();
11083
11275
  }
11084
11276
  _state = "RUNNING";
@@ -11096,13 +11288,13 @@ function createImplementationOrchestrator(deps) {
11096
11288
  startHeartbeat();
11097
11289
  if (projectRoot !== void 0) {
11098
11290
  const seedResult = seedMethodologyContext(db, projectRoot);
11099
- if (seedResult.decisionsCreated > 0) logger$33.info({
11291
+ if (seedResult.decisionsCreated > 0) logger$35.info({
11100
11292
  decisionsCreated: seedResult.decisionsCreated,
11101
11293
  skippedCategories: seedResult.skippedCategories
11102
11294
  }, "Methodology context seeded from planning artifacts");
11103
11295
  }
11104
11296
  const groups = detectConflictGroups(storyKeys);
11105
- logger$33.info("Orchestrator starting", {
11297
+ logger$35.info("Orchestrator starting", {
11106
11298
  storyCount: storyKeys.length,
11107
11299
  groupCount: groups.length,
11108
11300
  maxConcurrency: config.maxConcurrency
@@ -11114,7 +11306,7 @@ function createImplementationOrchestrator(deps) {
11114
11306
  _state = "FAILED";
11115
11307
  _completedAt = new Date().toISOString();
11116
11308
  persistState();
11117
- logger$33.error("Orchestrator failed with unhandled error", { err });
11309
+ logger$35.error("Orchestrator failed with unhandled error", { err });
11118
11310
  return getStatus();
11119
11311
  }
11120
11312
  stopHeartbeat();
@@ -11141,7 +11333,7 @@ function createImplementationOrchestrator(deps) {
11141
11333
  _pauseGate = createPauseGate();
11142
11334
  _state = "PAUSED";
11143
11335
  eventBus.emit("orchestrator:paused", {});
11144
- logger$33.info("Orchestrator paused");
11336
+ logger$35.info("Orchestrator paused");
11145
11337
  }
11146
11338
  function resume() {
11147
11339
  if (_state !== "PAUSED") return;
@@ -11152,7 +11344,7 @@ function createImplementationOrchestrator(deps) {
11152
11344
  }
11153
11345
  _state = "RUNNING";
11154
11346
  eventBus.emit("orchestrator:resumed", {});
11155
- logger$33.info("Orchestrator resumed");
11347
+ logger$35.info("Orchestrator resumed");
11156
11348
  }
11157
11349
  return {
11158
11350
  run,
@@ -11330,6 +11522,31 @@ function createPlanningPhaseDefinition() {
11330
11522
  };
11331
11523
  }
11332
11524
  /**
11525
+ * Create the UX Design phase definition.
11526
+ *
11527
+ * Entry gates: 'prd' artifact from planning must exist
11528
+ * Exit gates: 'ux-design' artifact must exist for this run
11529
+ *
11530
+ * This phase is inserted between planning and solutioning when UX design is
11531
+ * enabled in the pack manifest (`uxDesign: true`).
11532
+ */
11533
+ function createUxDesignPhaseDefinition() {
11534
+ return {
11535
+ name: "ux-design",
11536
+ description: "Design the user experience: personas, core experience vision, design system, visual foundation, user journeys, and accessibility guidelines.",
11537
+ entryGates: [createArtifactExistsGate("planning", "prd")],
11538
+ exitGates: [createArtifactExistsGate("ux-design", "ux-design")],
11539
+ onEnter: async (_db, runId) => {
11540
+ logPhase(`UX Design phase starting for run ${runId}`);
11541
+ },
11542
+ onExit: async (db, runId) => {
11543
+ const artifact = getArtifactByTypeForRun(db, runId, "ux-design", "ux-design");
11544
+ if (artifact === void 0) logPhase(`UX Design phase exit WARNING: ux-design artifact not found for run ${runId}`);
11545
+ else logPhase(`UX Design phase completed for run ${runId} — ux-design artifact registered: ${artifact.id}`);
11546
+ }
11547
+ };
11548
+ }
11549
+ /**
11333
11550
  * Create the Solutioning phase definition.
11334
11551
  *
11335
11552
  * Entry gates: 'prd' artifact from planning must exist
@@ -11383,15 +11600,19 @@ function createImplementationPhaseDefinition() {
11383
11600
  };
11384
11601
  }
11385
11602
  /**
11386
- * Return all four built-in phase definitions in execution order.
11603
+ * Return the built-in phase definitions in execution order.
11604
+ *
11605
+ * When `uxDesignEnabled` is true, the `ux-design` phase is inserted between
11606
+ * `planning` and `solutioning`, with its own entry/exit gates.
11607
+ *
11608
+ * @param config - Optional configuration for conditional phase inclusion
11387
11609
  */
11388
- function createBuiltInPhases() {
11389
- return [
11390
- createAnalysisPhaseDefinition(),
11391
- createPlanningPhaseDefinition(),
11392
- createSolutioningPhaseDefinition(),
11393
- createImplementationPhaseDefinition()
11394
- ];
11610
+ function createBuiltInPhases(config) {
11611
+ const phases = [createAnalysisPhaseDefinition(), createPlanningPhaseDefinition()];
11612
+ if (config?.uxDesignEnabled === true) phases.push(createUxDesignPhaseDefinition());
11613
+ phases.push(createSolutioningPhaseDefinition());
11614
+ phases.push(createImplementationPhaseDefinition());
11615
+ return phases;
11395
11616
  }
11396
11617
 
11397
11618
  //#endregion
@@ -11454,7 +11675,8 @@ var PhaseOrchestratorImpl = class {
11454
11675
  this._db = deps.db;
11455
11676
  this._pack = deps.pack;
11456
11677
  this._qualityGates = deps.qualityGates;
11457
- this._phases = createBuiltInPhases();
11678
+ const uxDesignEnabled = this._pack.manifest.uxDesign === true;
11679
+ this._phases = createBuiltInPhases({ uxDesignEnabled });
11458
11680
  const builtInNames = new Set(this._phases.map((p) => p.name));
11459
11681
  const packPhases = this._pack.getPhases();
11460
11682
  for (const packPhase of packPhases) if (!builtInNames.has(packPhase.name)) this._phases.push({
@@ -11610,12 +11832,44 @@ var PhaseOrchestratorImpl = class {
11610
11832
  getPhases() {
11611
11833
  return [...this._phases];
11612
11834
  }
11835
+ markPhaseFailed(runId, phase, reason) {
11836
+ updatePipelineRun(this._db, runId, { status: "failed" });
11837
+ const run = getPipelineRunById(this._db, runId);
11838
+ if (!run) return;
11839
+ const config = parseConfigJson(run.config_json);
11840
+ const history = config.phaseHistory;
11841
+ const currentEntry = history.find((h) => h.phase === phase && !h.completedAt);
11842
+ if (currentEntry) {
11843
+ currentEntry.completedAt = new Date().toISOString();
11844
+ currentEntry.gateResults = [{
11845
+ gate: "sub-phase-execution",
11846
+ passed: false,
11847
+ error: reason
11848
+ }];
11849
+ } else history.push({
11850
+ phase,
11851
+ startedAt: new Date().toISOString(),
11852
+ completedAt: new Date().toISOString(),
11853
+ gateResults: [{
11854
+ gate: "sub-phase-execution",
11855
+ passed: false,
11856
+ error: reason
11857
+ }]
11858
+ });
11859
+ const newConfigJson = JSON.stringify({
11860
+ ...config,
11861
+ phaseHistory: history
11862
+ });
11863
+ updatePipelineRunConfig(this._db, runId, newConfigJson);
11864
+ }
11613
11865
  };
11614
11866
  /**
11615
11867
  * Create a new PhaseOrchestrator with the given dependencies.
11616
11868
  *
11617
- * The orchestrator is pre-loaded with the four built-in phases
11618
- * (analysis, planning, solutioning, implementation).
11869
+ * The orchestrator is pre-loaded with the built-in phases
11870
+ * (analysis, planning, [ux-design,] solutioning, implementation).
11871
+ * The optional ux-design phase is inserted between planning and solutioning
11872
+ * when `pack.manifest.uxDesign === true`.
11619
11873
  */
11620
11874
  function createPhaseOrchestrator(deps) {
11621
11875
  return new PhaseOrchestratorImpl(deps);
@@ -11698,334 +11952,479 @@ function summarizeDecisions(decisions, maxChars) {
11698
11952
  }
11699
11953
 
11700
11954
  //#endregion
11701
- //#region src/modules/phase-orchestrator/step-runner.ts
11702
- const logger$5 = createLogger("step-runner");
11955
+ //#region src/modules/phase-orchestrator/schemas/critique-output.ts
11703
11956
  /**
11704
- * Format an array of decision records into a markdown section for injection.
11705
- *
11706
- * @param decisions - Decision records from the store
11707
- * @param sectionTitle - Title for the markdown section
11708
- * @returns Formatted markdown string
11957
+ * A single issue identified by the critique agent.
11709
11958
  */
11710
- function formatDecisionsForInjection(decisions, sectionTitle) {
11711
- if (decisions.length === 0) return "";
11712
- const parts = [];
11713
- if (sectionTitle) parts.push(`## ${sectionTitle}`);
11714
- for (const d of decisions) {
11715
- const rationale = d.rationale ? ` (${d.rationale})` : "";
11959
+ const CritiqueIssueSchema = z.object({
11960
+ severity: z.enum([
11961
+ "blocker",
11962
+ "major",
11963
+ "minor"
11964
+ ]),
11965
+ category: z.string().min(1),
11966
+ description: z.string().min(5),
11967
+ suggestion: z.string().min(5)
11968
+ });
11969
+ /**
11970
+ * Full output schema for critique agent responses.
11971
+ *
11972
+ * The critique agent must emit a YAML block matching this schema.
11973
+ * - `pass` means the artifact meets quality standards with no blocking issues.
11974
+ * - `needs_work` means one or more issues must be addressed before the artifact
11975
+ * can be considered complete.
11976
+ */
11977
+ const CritiqueOutputSchema = z.object({
11978
+ verdict: z.enum(["pass", "needs_work"]),
11979
+ issue_count: z.number().int().min(0),
11980
+ issues: z.array(CritiqueIssueSchema).default([])
11981
+ });
11982
+
11983
+ //#endregion
11984
+ //#region src/modules/phase-orchestrator/critique-loop.ts
11985
+ const logger$7 = createLogger("critique-loop");
11986
+ /**
11987
+ * Maps a phase name to the critique prompt template name.
11988
+ * Falls back to `critique-${phase}` for unknown phases.
11989
+ */
11990
+ function getCritiquePromptName(phase) {
11991
+ const mapping = {
11992
+ analysis: "critique-analysis",
11993
+ planning: "critique-planning",
11994
+ solutioning: "critique-architecture",
11995
+ architecture: "critique-architecture",
11996
+ stories: "critique-stories"
11997
+ };
11998
+ return mapping[phase] ?? `critique-${phase}`;
11999
+ }
12000
+ /**
12001
+ * Execute a critique-and-refine loop on a phase artifact.
12002
+ *
12003
+ * Dispatches a critique agent, checks the verdict, and if the artifact
12004
+ * needs work, dispatches a refinement agent and repeats up to maxIterations.
12005
+ * After each critique, stores the result in the decision store under category 'critique'.
12006
+ *
12007
+ * @param artifact - The artifact content (raw text/YAML/markdown) to critique
12008
+ * @param phaseId - Phase name used to select the critique prompt template
12009
+ * @param runId - Pipeline run ID for decision store scoping
12010
+ * @param phase - Phase name for decision store persistence
12011
+ * @param deps - Shared phase dependencies (db, pack, dispatcher)
12012
+ * @param options - Critique loop configuration options
12013
+ * @returns CritiqueLoopResult with verdict, token costs, and timing
12014
+ */
12015
+ async function runCritiqueLoop(artifact, phaseId, runId, phase, deps, options = {}) {
12016
+ const { maxIterations = 2, projectContext = "", phaseContext = "" } = options;
12017
+ const startMs = Date.now();
12018
+ const critiqueTokens = {
12019
+ input: 0,
12020
+ output: 0
12021
+ };
12022
+ const refinementTokens = {
12023
+ input: 0,
12024
+ output: 0
12025
+ };
12026
+ let iterations = 0;
12027
+ let currentArtifact = artifact;
12028
+ let lastCritiqueOutput = null;
12029
+ const critiquePromptName = getCritiquePromptName(phaseId);
12030
+ for (let i = 0; i < maxIterations; i++) {
12031
+ iterations = i + 1;
12032
+ let critiquePrompt;
11716
12033
  try {
11717
- const parsed = JSON.parse(d.value);
11718
- if (Array.isArray(parsed)) {
11719
- parts.push(`### ${d.key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}`);
11720
- for (const item of parsed) parts.push(`- ${String(item)}`);
11721
- } else if (typeof parsed === "object" && parsed !== null) parts.push(`- **${d.key}**: ${JSON.stringify(parsed)}${rationale}`);
11722
- else parts.push(`- **${d.key}**: ${String(parsed)}${rationale}`);
11723
- } catch {
11724
- parts.push(`- **${d.key}**: ${d.value}${rationale}`);
12034
+ const critiqueTemplate = await deps.pack.getPrompt(critiquePromptName);
12035
+ critiquePrompt = critiqueTemplate.replace("{{artifact_content}}", currentArtifact).replace("{{project_context}}", projectContext);
12036
+ } catch (err) {
12037
+ const message = err instanceof Error ? err.message : String(err);
12038
+ logger$7.warn({
12039
+ phaseId,
12040
+ promptName: critiquePromptName,
12041
+ err: message
12042
+ }, "Critique loop: failed to load critique prompt template — skipping critique");
12043
+ return {
12044
+ verdict: "pass",
12045
+ iterations,
12046
+ remainingIssues: [],
12047
+ critiqueTokens,
12048
+ refinementTokens,
12049
+ totalMs: Date.now() - startMs,
12050
+ error: `Failed to load critique prompt '${critiquePromptName}': ${message}`
12051
+ };
12052
+ }
12053
+ let critiqueOutput;
12054
+ try {
12055
+ const handle = deps.dispatcher.dispatch({
12056
+ prompt: critiquePrompt,
12057
+ agent: "claude-code",
12058
+ taskType: "critique",
12059
+ outputSchema: CritiqueOutputSchema
12060
+ });
12061
+ const result = await handle.result;
12062
+ critiqueTokens.input += result.tokenEstimate.input;
12063
+ critiqueTokens.output += result.tokenEstimate.output;
12064
+ if (result.status !== "completed" || result.parsed === null) {
12065
+ const errMsg = result.parseError ?? `Critique dispatch ended with status '${result.status}'`;
12066
+ logger$7.warn({
12067
+ phaseId,
12068
+ iteration: i + 1,
12069
+ err: errMsg
12070
+ }, "Critique loop: critique dispatch failed — treating as pass to avoid blocking pipeline");
12071
+ return {
12072
+ verdict: "pass",
12073
+ iterations,
12074
+ remainingIssues: [],
12075
+ critiqueTokens,
12076
+ refinementTokens,
12077
+ totalMs: Date.now() - startMs,
12078
+ error: errMsg
12079
+ };
12080
+ }
12081
+ critiqueOutput = result.parsed;
12082
+ lastCritiqueOutput = critiqueOutput;
12083
+ } catch (err) {
12084
+ const message = err instanceof Error ? err.message : String(err);
12085
+ logger$7.warn({
12086
+ phaseId,
12087
+ iteration: i + 1,
12088
+ err: message
12089
+ }, "Critique loop: critique dispatch threw — treating as pass to avoid blocking pipeline");
12090
+ return {
12091
+ verdict: "pass",
12092
+ iterations,
12093
+ remainingIssues: [],
12094
+ critiqueTokens,
12095
+ refinementTokens,
12096
+ totalMs: Date.now() - startMs,
12097
+ error: message
12098
+ };
12099
+ }
12100
+ try {
12101
+ upsertDecision(deps.db, {
12102
+ pipeline_run_id: runId,
12103
+ phase,
12104
+ category: "critique",
12105
+ key: `${phaseId}-iteration-${i + 1}-verdict`,
12106
+ value: critiqueOutput.verdict,
12107
+ rationale: `Critique loop iteration ${i + 1} of ${maxIterations}`
12108
+ });
12109
+ upsertDecision(deps.db, {
12110
+ pipeline_run_id: runId,
12111
+ phase,
12112
+ category: "critique",
12113
+ key: `${phaseId}-iteration-${i + 1}-issue_count`,
12114
+ value: String(critiqueOutput.issue_count)
12115
+ });
12116
+ if (critiqueOutput.issues.length > 0) upsertDecision(deps.db, {
12117
+ pipeline_run_id: runId,
12118
+ phase,
12119
+ category: "critique",
12120
+ key: `${phaseId}-iteration-${i + 1}-issues`,
12121
+ value: JSON.stringify(critiqueOutput.issues)
12122
+ });
12123
+ } catch (err) {
12124
+ const message = err instanceof Error ? err.message : String(err);
12125
+ logger$7.warn({
12126
+ phaseId,
12127
+ iteration: i + 1,
12128
+ err: message
12129
+ }, "Critique loop: failed to store critique decision — continuing");
12130
+ }
12131
+ if (critiqueOutput.verdict === "pass") {
12132
+ logger$7.info({
12133
+ phaseId,
12134
+ iteration: i + 1
12135
+ }, "Critique loop: artifact passed critique — loop complete");
12136
+ return {
12137
+ verdict: "pass",
12138
+ iterations,
12139
+ remainingIssues: [],
12140
+ critiqueTokens,
12141
+ refinementTokens,
12142
+ totalMs: Date.now() - startMs
12143
+ };
12144
+ }
12145
+ logger$7.info({
12146
+ phaseId,
12147
+ iteration: i + 1,
12148
+ issueCount: critiqueOutput.issue_count
12149
+ }, "Critique loop: artifact needs work — dispatching refinement");
12150
+ if (i < maxIterations - 1) {
12151
+ let refinePrompt;
12152
+ try {
12153
+ const refineTemplate = await deps.pack.getPrompt("refine-artifact");
12154
+ const issuesText = critiqueOutput.issues.map((issue) => `- [${issue.severity}] ${issue.category}: ${issue.description}\n Suggestion: ${issue.suggestion}`).join("\n");
12155
+ refinePrompt = refineTemplate.replace("{{original_artifact}}", currentArtifact).replace("{{critique_issues}}", issuesText).replace("{{phase_context}}", phaseContext);
12156
+ } catch (err) {
12157
+ const message = err instanceof Error ? err.message : String(err);
12158
+ logger$7.warn({
12159
+ phaseId,
12160
+ iteration: i + 1,
12161
+ err: message
12162
+ }, "Critique loop: failed to load refinement prompt — stopping loop");
12163
+ break;
12164
+ }
12165
+ try {
12166
+ const refineHandle = deps.dispatcher.dispatch({
12167
+ prompt: refinePrompt,
12168
+ agent: "claude-code",
12169
+ taskType: "critique",
12170
+ outputSchema: void 0
12171
+ });
12172
+ const refineResult = await refineHandle.result;
12173
+ refinementTokens.input += refineResult.tokenEstimate.input;
12174
+ refinementTokens.output += refineResult.tokenEstimate.output;
12175
+ if (refineResult.status === "completed" && refineResult.output) {
12176
+ const originalLength = currentArtifact.length;
12177
+ const refinedLength = refineResult.output.length;
12178
+ const delta = refinedLength - originalLength;
12179
+ logger$7.info({
12180
+ phaseId,
12181
+ iteration: i + 1,
12182
+ originalLength,
12183
+ refinedLength,
12184
+ delta
12185
+ }, "Critique loop: refinement complete");
12186
+ currentArtifact = refineResult.output;
12187
+ } else {
12188
+ logger$7.warn({
12189
+ phaseId,
12190
+ iteration: i + 1,
12191
+ status: refineResult.status
12192
+ }, "Critique loop: refinement dispatch failed — stopping loop");
12193
+ break;
12194
+ }
12195
+ } catch (err) {
12196
+ const message = err instanceof Error ? err.message : String(err);
12197
+ logger$7.warn({
12198
+ phaseId,
12199
+ iteration: i + 1,
12200
+ err: message
12201
+ }, "Critique loop: refinement dispatch threw — stopping loop");
12202
+ break;
12203
+ }
11725
12204
  }
11726
12205
  }
11727
- return parts.join("\n");
12206
+ const remainingIssues = lastCritiqueOutput?.issues ?? [];
12207
+ if (remainingIssues.length > 0) {
12208
+ logger$7.warn({
12209
+ phaseId,
12210
+ maxIterations,
12211
+ issueCount: remainingIssues.length
12212
+ }, "Critique loop: max iterations reached with unresolved issues");
12213
+ for (const issue of remainingIssues) logger$7.warn({
12214
+ phaseId,
12215
+ severity: issue.severity,
12216
+ category: issue.category,
12217
+ description: issue.description
12218
+ }, `Critique loop: unresolved issue — ${issue.severity}: ${issue.description}`);
12219
+ }
12220
+ return {
12221
+ verdict: "needs_work",
12222
+ iterations,
12223
+ remainingIssues,
12224
+ critiqueTokens,
12225
+ refinementTokens,
12226
+ totalMs: Date.now() - startMs
12227
+ };
11728
12228
  }
12229
+
12230
+ //#endregion
12231
+ //#region src/modules/phase-orchestrator/elicitation-selector.ts
12232
+ const logger$6 = createLogger("elicitation-selector");
11729
12233
  /**
11730
- * Resolve a single context reference to a string value.
12234
+ * Affinity scores (0.0–1.0) for each category per content type.
11731
12235
  *
11732
- * @param ref - The context reference to resolve
11733
- * @param deps - Phase dependencies (for DB access)
11734
- * @param runId - Pipeline run ID
11735
- * @param params - Runtime parameters map
11736
- * @param stepOutputs - Map of step name → raw parsed output from prior steps
11737
- * @returns Resolved string value
11738
- */
11739
- function resolveContext(ref, deps, runId, params, stepOutputs) {
11740
- const { source } = ref;
11741
- if (source.startsWith("param:")) {
11742
- const key = source.slice(6);
11743
- return params[key] ?? "";
12236
+ * Higher score more likely to be selected for that content type.
12237
+ * Based on the method-to-phase affinity matrix from the Dev Notes.
12238
+ */
12239
+ const CATEGORY_AFFINITY = {
12240
+ brief: {
12241
+ core: 1,
12242
+ collaboration: .9,
12243
+ creative: .8,
12244
+ research: .5,
12245
+ risk: .4,
12246
+ advanced: .3,
12247
+ technical: .2,
12248
+ competitive: .2,
12249
+ learning: .1,
12250
+ philosophical: .1,
12251
+ retrospective: .1
12252
+ },
12253
+ prd: {
12254
+ risk: 1,
12255
+ core: .9,
12256
+ research: .8,
12257
+ collaboration: .6,
12258
+ creative: .4,
12259
+ advanced: .3,
12260
+ technical: .2,
12261
+ competitive: .2,
12262
+ learning: .1,
12263
+ philosophical: .1,
12264
+ retrospective: .1
12265
+ },
12266
+ architecture: {
12267
+ technical: 1,
12268
+ competitive: .9,
12269
+ risk: .8,
12270
+ core: .5,
12271
+ advanced: .5,
12272
+ research: .4,
12273
+ collaboration: .3,
12274
+ creative: .2,
12275
+ learning: .1,
12276
+ philosophical: .1,
12277
+ retrospective: .1
12278
+ },
12279
+ stories: {
12280
+ collaboration: 1,
12281
+ risk: .9,
12282
+ core: .5,
12283
+ research: .4,
12284
+ creative: .3,
12285
+ technical: .3,
12286
+ advanced: .2,
12287
+ competitive: .2,
12288
+ learning: .1,
12289
+ philosophical: .1,
12290
+ retrospective: .1
12291
+ },
12292
+ "ux-design": {
12293
+ creative: 1,
12294
+ collaboration: .9,
12295
+ research: .8,
12296
+ core: .5,
12297
+ risk: .4,
12298
+ advanced: .3,
12299
+ competitive: .3,
12300
+ technical: .2,
12301
+ learning: .1,
12302
+ philosophical: .1,
12303
+ retrospective: .1
11744
12304
  }
11745
- if (source.startsWith("decision:")) {
11746
- const path$1 = source.slice(9);
11747
- const [phase, category] = path$1.split(".");
11748
- if (!phase || !category) return "";
11749
- const decisions = getDecisionsByPhaseForRun(deps.db, runId, phase);
11750
- const filtered = decisions.filter((d) => d.category === category);
11751
- return formatDecisionsForInjection(filtered.map((d) => ({
11752
- key: d.key,
11753
- value: d.value,
11754
- rationale: d.rationale ?? null
11755
- })), category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
12305
+ };
12306
+ const RECENCY_PENALTY_FACTOR = .2;
12307
+ /**
12308
+ * Parse a CSV string (with header) into an array of ElicitationMethod objects.
12309
+ *
12310
+ * Expected CSV format (columns): num,category,method_name,description,output_pattern
12311
+ *
12312
+ * @param csvContent - Raw CSV file content including header row
12313
+ * @returns Array of parsed elicitation methods
12314
+ */
12315
+ function parseMethodsCsv(csvContent) {
12316
+ const lines = csvContent.trim().split("\n");
12317
+ if (lines.length < 2) return [];
12318
+ const methods = [];
12319
+ for (let i = 1; i < lines.length; i++) {
12320
+ const line = lines[i].trim();
12321
+ if (!line) continue;
12322
+ const firstComma = line.indexOf(",");
12323
+ const secondComma = line.indexOf(",", firstComma + 1);
12324
+ const thirdComma = line.indexOf(",", secondComma + 1);
12325
+ const lastComma = line.lastIndexOf(",");
12326
+ if (firstComma < 0 || secondComma < 0 || thirdComma < 0 || lastComma <= thirdComma) continue;
12327
+ const category = line.slice(firstComma + 1, secondComma);
12328
+ const name = line.slice(secondComma + 1, thirdComma);
12329
+ const description = line.slice(thirdComma + 1, lastComma);
12330
+ const output_pattern = line.slice(lastComma + 1);
12331
+ if (!category || !name || !description || !output_pattern) continue;
12332
+ methods.push({
12333
+ name,
12334
+ category,
12335
+ description,
12336
+ output_pattern
12337
+ });
11756
12338
  }
11757
- if (source.startsWith("step:")) {
11758
- const stepName = source.slice(5);
11759
- const output = stepOutputs.get(stepName);
11760
- if (!output) return "";
11761
- const parts = [];
11762
- for (const [key, value] of Object.entries(output)) {
11763
- if (key === "result") continue;
11764
- if (Array.isArray(value)) {
11765
- parts.push(`### ${key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}`);
11766
- for (const item of value) if (typeof item === "object" && item !== null) parts.push(`- ${JSON.stringify(item)}`);
11767
- else parts.push(`- ${String(item)}`);
11768
- } else if (typeof value === "object" && value !== null) parts.push(`- **${key}**: ${JSON.stringify(value)}`);
11769
- else parts.push(`- **${key}**: ${String(value)}`);
11770
- }
11771
- return parts.join("\n");
11772
- }
11773
- return "";
12339
+ return methods;
11774
12340
  }
11775
12341
  /**
11776
- * Execute a sequence of steps, accumulating context and persisting results.
12342
+ * Load elicitation methods from the pack data directory.
11777
12343
  *
11778
- * Halts on the first step that fails. Each step's output is available to
11779
- * subsequent steps via the stepOutputs map and decision store.
12344
+ * Reads from packs/bmad/data/elicitation-methods.csv relative to process.cwd().
12345
+ * Returns an empty array if the file cannot be read.
11780
12346
  *
11781
- * @param steps - Ordered list of step definitions to execute
11782
- * @param deps - Shared phase dependencies
11783
- * @param runId - Pipeline run ID
11784
- * @param phase - Phase name (for decision store persistence)
11785
- * @param params - Runtime parameters map (concept, product_brief, etc.)
11786
- * @returns Aggregated multi-step result
12347
+ * @returns Array of all available elicitation methods
11787
12348
  */
11788
- async function runSteps(steps, deps, runId, phase, params) {
11789
- const stepResults = [];
11790
- const stepOutputs = new Map();
11791
- let totalInput = 0;
11792
- let totalOutput = 0;
11793
- for (const step of steps) try {
11794
- const template = await deps.pack.getPrompt(step.name);
11795
- let prompt = template;
11796
- for (const ref of step.context) {
11797
- const value = resolveContext(ref, deps, runId, params, stepOutputs);
11798
- prompt = prompt.replace(`{{${ref.placeholder}}}`, value);
11799
- }
11800
- const allDecisions = getDecisionsByPhaseForRun(deps.db, runId, phase);
11801
- const budgetTokens = calculateDynamicBudget(4e3, allDecisions.length);
11802
- let estimatedTokens = Math.ceil(prompt.length / 4);
11803
- if (estimatedTokens > budgetTokens) {
11804
- const decisionRefs = step.context.filter((ref) => ref.source.startsWith("decision:"));
11805
- if (decisionRefs.length > 0) {
11806
- logger$5.warn({
11807
- step: step.name,
11808
- estimatedTokens,
11809
- budgetTokens
11810
- }, "Prompt exceeds budget — attempting decision summarization");
11811
- let summarizedPrompt = template;
11812
- for (const ref of step.context) {
11813
- let value;
11814
- if (ref.source.startsWith("decision:")) {
11815
- const path$1 = ref.source.slice(9);
11816
- const [decPhase, decCategory] = path$1.split(".");
11817
- if (decPhase && decCategory) {
11818
- const decisions = getDecisionsByPhaseForRun(deps.db, runId, decPhase);
11819
- const filtered = decisions.filter((d) => d.category === decCategory);
11820
- const budgetChars = budgetTokens * 4;
11821
- const availableChars = Math.max(200, Math.floor(budgetChars / decisionRefs.length));
11822
- value = summarizeDecisions(filtered.map((d) => ({
11823
- key: d.key,
11824
- value: d.value,
11825
- category: d.category
11826
- })), availableChars);
11827
- } else value = resolveContext(ref, deps, runId, params, stepOutputs);
11828
- } else value = resolveContext(ref, deps, runId, params, stepOutputs);
11829
- summarizedPrompt = summarizedPrompt.replace(`{{${ref.placeholder}}}`, value);
11830
- }
11831
- prompt = summarizedPrompt;
11832
- estimatedTokens = Math.ceil(prompt.length / 4);
11833
- if (estimatedTokens <= budgetTokens) logger$5.info({
11834
- step: step.name,
11835
- estimatedTokens,
11836
- budgetTokens
11837
- }, "Decision summarization brought prompt within budget");
11838
- }
11839
- if (estimatedTokens > budgetTokens) {
11840
- const errorMsg = `Step '${step.name}' prompt exceeds token budget after summarization: ${estimatedTokens} tokens (max ${budgetTokens})`;
11841
- stepResults.push({
11842
- name: step.name,
11843
- success: false,
11844
- parsed: null,
11845
- error: errorMsg,
11846
- tokenUsage: {
11847
- input: 0,
11848
- output: 0
11849
- }
11850
- });
11851
- return {
11852
- success: false,
11853
- steps: stepResults,
11854
- tokenUsage: {
11855
- input: totalInput,
11856
- output: totalOutput
11857
- },
11858
- error: errorMsg
11859
- };
11860
- }
11861
- }
11862
- const handle = deps.dispatcher.dispatch({
11863
- prompt,
11864
- agent: "claude-code",
11865
- taskType: step.taskType,
11866
- outputSchema: step.outputSchema
11867
- });
11868
- const dispatchResult = await handle.result;
11869
- const tokenUsage = {
11870
- input: dispatchResult.tokenEstimate.input,
11871
- output: dispatchResult.tokenEstimate.output
11872
- };
11873
- totalInput += tokenUsage.input;
11874
- totalOutput += tokenUsage.output;
11875
- if (dispatchResult.status === "timeout") {
11876
- const errorMsg = `Step '${step.name}' timed out after ${dispatchResult.durationMs}ms`;
11877
- stepResults.push({
11878
- name: step.name,
11879
- success: false,
11880
- parsed: null,
11881
- error: errorMsg,
11882
- tokenUsage
11883
- });
11884
- return {
11885
- success: false,
11886
- steps: stepResults,
11887
- tokenUsage: {
11888
- input: totalInput,
11889
- output: totalOutput
11890
- },
11891
- error: errorMsg
11892
- };
11893
- }
11894
- if (dispatchResult.status === "failed") {
11895
- const errorMsg = `Step '${step.name}' dispatch failed: ${dispatchResult.parseError ?? dispatchResult.output}`;
11896
- stepResults.push({
11897
- name: step.name,
11898
- success: false,
11899
- parsed: null,
11900
- error: errorMsg,
11901
- tokenUsage
11902
- });
11903
- return {
11904
- success: false,
11905
- steps: stepResults,
11906
- tokenUsage: {
11907
- input: totalInput,
11908
- output: totalOutput
11909
- },
11910
- error: errorMsg
11911
- };
11912
- }
11913
- if (dispatchResult.parsed === null || dispatchResult.parseError !== null) {
11914
- const errorMsg = `Step '${step.name}' schema validation failed: ${dispatchResult.parseError ?? "No parsed output"}`;
11915
- stepResults.push({
11916
- name: step.name,
11917
- success: false,
11918
- parsed: null,
11919
- error: errorMsg,
11920
- tokenUsage
11921
- });
11922
- return {
11923
- success: false,
11924
- steps: stepResults,
11925
- tokenUsage: {
11926
- input: totalInput,
11927
- output: totalOutput
11928
- },
11929
- error: errorMsg
11930
- };
11931
- }
11932
- const parsed = dispatchResult.parsed;
11933
- if (parsed.result === "failed") {
11934
- const errorMsg = `Step '${step.name}' agent reported failure`;
11935
- stepResults.push({
11936
- name: step.name,
11937
- success: false,
11938
- parsed: null,
11939
- error: errorMsg,
11940
- tokenUsage
11941
- });
11942
- return {
11943
- success: false,
11944
- steps: stepResults,
11945
- tokenUsage: {
11946
- input: totalInput,
11947
- output: totalOutput
11948
- },
11949
- error: errorMsg
11950
- };
11951
- }
11952
- stepOutputs.set(step.name, parsed);
11953
- for (const mapping of step.persist) {
11954
- const fieldValue = parsed[mapping.field];
11955
- if (fieldValue === void 0) continue;
11956
- if (mapping.key === "array" && Array.isArray(fieldValue)) for (const [index, item] of fieldValue.entries()) upsertDecision(deps.db, {
11957
- pipeline_run_id: runId,
11958
- phase,
11959
- category: mapping.category,
11960
- key: `${step.name}-${index}`,
11961
- value: typeof item === "object" ? JSON.stringify(item) : String(item)
11962
- });
11963
- else if (typeof fieldValue === "object" && fieldValue !== null) upsertDecision(deps.db, {
11964
- pipeline_run_id: runId,
11965
- phase,
11966
- category: mapping.category,
11967
- key: mapping.key,
11968
- value: JSON.stringify(fieldValue)
11969
- });
11970
- else upsertDecision(deps.db, {
11971
- pipeline_run_id: runId,
11972
- phase,
11973
- category: mapping.category,
11974
- key: mapping.key,
11975
- value: String(fieldValue)
11976
- });
11977
- }
11978
- let artifactId;
11979
- if (step.registerArtifact) {
11980
- const artifact = registerArtifact(deps.db, {
11981
- pipeline_run_id: runId,
11982
- phase,
11983
- type: step.registerArtifact.type,
11984
- path: step.registerArtifact.path,
11985
- summary: step.registerArtifact.summarize(parsed)
11986
- });
11987
- artifactId = artifact.id;
11988
- }
11989
- const stepResult = {
11990
- name: step.name,
11991
- success: true,
11992
- parsed,
11993
- error: null,
11994
- tokenUsage
11995
- };
11996
- if (artifactId !== void 0) stepResult.artifactId = artifactId;
11997
- stepResults.push(stepResult);
12349
+ function loadElicitationMethods() {
12350
+ const csvPath = join(process.cwd(), "packs", "bmad", "data", "elicitation-methods.csv");
12351
+ try {
12352
+ const content = readFileSync(csvPath, "utf-8");
12353
+ const methods = parseMethodsCsv(content);
12354
+ logger$6.debug({ count: methods.length }, "Loaded elicitation methods");
12355
+ return methods;
11998
12356
  } catch (err) {
11999
- const message = err instanceof Error ? err.message : String(err);
12000
- const errorMsg = `Step '${step.name}' unexpected error: ${message}`;
12001
- stepResults.push({
12002
- name: step.name,
12003
- success: false,
12004
- parsed: null,
12005
- error: errorMsg,
12006
- tokenUsage: {
12007
- input: 0,
12008
- output: 0
12009
- }
12010
- });
12357
+ logger$6.warn({
12358
+ csvPath,
12359
+ err
12360
+ }, "Failed to load elicitation methods CSV");
12361
+ return [];
12362
+ }
12363
+ }
12364
+ /**
12365
+ * Select 1–2 elicitation methods appropriate for the given context.
12366
+ *
12367
+ * Selection algorithm:
12368
+ * 1. Score each method: categoryAffinity × recencyFactor × riskBoost × complexityBoost
12369
+ * 2. Sort descending by score
12370
+ * 3. Return top 1–2 methods (always at least 1 if methods are available)
12371
+ *
12372
+ * Methods used in previous rounds (listed in `usedMethods`) are deprioritized
12373
+ * via `RECENCY_PENALTY_FACTOR` to encourage category rotation.
12374
+ *
12375
+ * @param context - Elicitation context describing the artifact and domain
12376
+ * @param usedMethods - Names of methods already used in this pipeline run
12377
+ * @param methods - Optional pre-loaded method list (defaults to loadElicitationMethods())
12378
+ * @returns Array of 0–2 selected ElicitationMethod objects
12379
+ */
12380
+ function selectMethods(context, usedMethods, methods) {
12381
+ const allMethods = methods ?? loadElicitationMethods();
12382
+ if (allMethods.length === 0) return [];
12383
+ const affinity = CATEGORY_AFFINITY[context.content_type] ?? {};
12384
+ const usedSet = new Set(usedMethods);
12385
+ const complexityScore = context.complexity_score ?? .5;
12386
+ const riskLevel = context.risk_level ?? "medium";
12387
+ const scored = allMethods.map((method) => {
12388
+ const categoryScore = affinity[method.category] ?? .3;
12389
+ const recencyFactor = usedSet.has(method.name) ? RECENCY_PENALTY_FACTOR : 1;
12390
+ const riskBoost = riskLevel === "high" && method.category === "risk" ? 1.3 : 1;
12391
+ const complexityBoost = complexityScore > .7 && (method.category === "technical" || method.category === "advanced") ? 1.2 : 1;
12392
+ const score = categoryScore * recencyFactor * riskBoost * complexityBoost;
12011
12393
  return {
12012
- success: false,
12013
- steps: stepResults,
12014
- tokenUsage: {
12015
- input: totalInput,
12016
- output: totalOutput
12017
- },
12018
- error: errorMsg
12394
+ method,
12395
+ score
12019
12396
  };
12397
+ });
12398
+ scored.sort((a, b) => {
12399
+ if (b.score !== a.score) return b.score - a.score;
12400
+ return a.method.name.localeCompare(b.method.name);
12401
+ });
12402
+ return scored.slice(0, 2).map((s) => s.method);
12403
+ }
12404
+ /**
12405
+ * Derive an ElicitationContext content_type from a phase name and step name.
12406
+ *
12407
+ * Mapping:
12408
+ * - analysis phase → 'brief'
12409
+ * - planning phase → 'prd'
12410
+ * - solutioning + arch step → 'architecture'
12411
+ * - solutioning + story/epic step → 'stories'
12412
+ * - ux-design phase → 'ux-design'
12413
+ * - default → 'brief'
12414
+ *
12415
+ * @param phase - Pipeline phase name
12416
+ * @param stepName - Step name within the phase
12417
+ * @returns Content type for method selection
12418
+ */
12419
+ function deriveContentType(phase, stepName) {
12420
+ if (phase === "analysis") return "brief";
12421
+ if (phase === "planning") return "prd";
12422
+ if (phase === "ux-design") return "ux-design";
12423
+ if (phase === "solutioning") {
12424
+ if (stepName.includes("arch")) return "architecture";
12425
+ if (stepName.includes("stor") || stepName.includes("epic")) return "stories";
12020
12426
  }
12021
- return {
12022
- success: true,
12023
- steps: stepResults,
12024
- tokenUsage: {
12025
- input: totalInput,
12026
- output: totalOutput
12027
- }
12028
- };
12427
+ return "brief";
12029
12428
  }
12030
12429
 
12031
12430
  //#endregion
@@ -12201,6 +12600,502 @@ const EpicDesignOutputSchema = z.object({
12201
12600
  fr_coverage: z.array(z.string()).default([])
12202
12601
  })).min(1).optional()
12203
12602
  });
12603
+ /**
12604
+ * Step 1 output: UX Discovery + Core Experience.
12605
+ * Covers user personas, core experience goals, and emotional response targets.
12606
+ * Content fields are optional to allow `{result: 'failed'}` without Zod rejection.
12607
+ */
12608
+ const UxDiscoveryOutputSchema = z.object({
12609
+ result: z.enum(["success", "failed"]),
12610
+ target_personas: z.array(z.string().min(1)).optional(),
12611
+ core_experience: z.string().optional(),
12612
+ emotional_goals: z.array(z.string().min(1)).optional(),
12613
+ inspiration_references: z.array(z.string()).default([])
12614
+ });
12615
+ /**
12616
+ * Step 2 output: Design System + Visual Foundation.
12617
+ * Covers design system approach, visual language, and design directions.
12618
+ * Content fields are optional to allow `{result: 'failed'}` without Zod rejection.
12619
+ */
12620
+ const UxDesignSystemOutputSchema = z.object({
12621
+ result: z.enum(["success", "failed"]),
12622
+ design_system: z.string().optional(),
12623
+ visual_foundation: z.string().optional(),
12624
+ design_principles: z.array(z.string().min(1)).optional(),
12625
+ color_and_typography: z.string().optional()
12626
+ });
12627
+ /**
12628
+ * Step 3 output: User Journeys + Component Strategy + Accessibility.
12629
+ * Covers user flows, component architecture, UX patterns, and a11y guidelines.
12630
+ * Content fields are optional to allow `{result: 'failed'}` without Zod rejection.
12631
+ */
12632
+ const UxJourneysOutputSchema = z.object({
12633
+ result: z.enum(["success", "failed"]),
12634
+ user_journeys: z.array(z.string().min(1)).optional(),
12635
+ component_strategy: z.string().optional(),
12636
+ ux_patterns: z.array(z.string()).default([]),
12637
+ accessibility_guidelines: z.array(z.string()).default([])
12638
+ });
12639
+ /**
12640
+ * Zod schema for the YAML output emitted by an elicitation sub-agent.
12641
+ * The agent returns structured insights from applying an elicitation method.
12642
+ */
12643
+ const ElicitationOutputSchema = z.object({
12644
+ result: z.enum(["success", "failed"]),
12645
+ insights: z.string()
12646
+ });
12647
+
12648
+ //#endregion
12649
+ //#region src/modules/phase-orchestrator/step-runner.ts
12650
+ const logger$5 = createLogger("step-runner");
12651
+ /**
12652
+ * Format an array of decision records into a markdown section for injection.
12653
+ *
12654
+ * @param decisions - Decision records from the store
12655
+ * @param sectionTitle - Title for the markdown section
12656
+ * @returns Formatted markdown string
12657
+ */
12658
+ function formatDecisionsForInjection(decisions, sectionTitle) {
12659
+ if (decisions.length === 0) return "";
12660
+ const parts = [];
12661
+ if (sectionTitle) parts.push(`## ${sectionTitle}`);
12662
+ for (const d of decisions) {
12663
+ const rationale = d.rationale ? ` (${d.rationale})` : "";
12664
+ try {
12665
+ const parsed = JSON.parse(d.value);
12666
+ if (Array.isArray(parsed)) {
12667
+ parts.push(`### ${d.key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}`);
12668
+ for (const item of parsed) parts.push(`- ${String(item)}`);
12669
+ } else if (typeof parsed === "object" && parsed !== null) parts.push(`- **${d.key}**: ${JSON.stringify(parsed)}${rationale}`);
12670
+ else parts.push(`- **${d.key}**: ${String(parsed)}${rationale}`);
12671
+ } catch {
12672
+ parts.push(`- **${d.key}**: ${d.value}${rationale}`);
12673
+ }
12674
+ }
12675
+ return parts.join("\n");
12676
+ }
12677
+ /**
12678
+ * Resolve a single context reference to a string value.
12679
+ *
12680
+ * @param ref - The context reference to resolve
12681
+ * @param deps - Phase dependencies (for DB access)
12682
+ * @param runId - Pipeline run ID
12683
+ * @param params - Runtime parameters map
12684
+ * @param stepOutputs - Map of step name → raw parsed output from prior steps
12685
+ * @returns Resolved string value
12686
+ */
12687
+ function resolveContext(ref, deps, runId, params, stepOutputs) {
12688
+ const { source } = ref;
12689
+ if (source.startsWith("param:")) {
12690
+ const key = source.slice(6);
12691
+ return params[key] ?? "";
12692
+ }
12693
+ if (source.startsWith("decision:")) {
12694
+ const path$1 = source.slice(9);
12695
+ const [phase, category] = path$1.split(".");
12696
+ if (!phase || !category) return "";
12697
+ const decisions = getDecisionsByPhaseForRun(deps.db, runId, phase);
12698
+ const filtered = decisions.filter((d) => d.category === category);
12699
+ return formatDecisionsForInjection(filtered.map((d) => ({
12700
+ key: d.key,
12701
+ value: d.value,
12702
+ rationale: d.rationale ?? null
12703
+ })), category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
12704
+ }
12705
+ if (source.startsWith("step:")) {
12706
+ const stepName = source.slice(5);
12707
+ const output = stepOutputs.get(stepName);
12708
+ if (!output) return "";
12709
+ const parts = [];
12710
+ for (const [key, value] of Object.entries(output)) {
12711
+ if (key === "result") continue;
12712
+ if (Array.isArray(value)) {
12713
+ parts.push(`### ${key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())}`);
12714
+ for (const item of value) if (typeof item === "object" && item !== null) parts.push(`- ${JSON.stringify(item)}`);
12715
+ else parts.push(`- ${String(item)}`);
12716
+ } else if (typeof value === "object" && value !== null) parts.push(`- **${key}**: ${JSON.stringify(value)}`);
12717
+ else parts.push(`- **${key}**: ${String(value)}`);
12718
+ }
12719
+ return parts.join("\n");
12720
+ }
12721
+ return "";
12722
+ }
12723
+ /**
12724
+ * Execute a sequence of steps, accumulating context and persisting results.
12725
+ *
12726
+ * Halts on the first step that fails. Each step's output is available to
12727
+ * subsequent steps via the stepOutputs map and decision store.
12728
+ *
12729
+ * @param steps - Ordered list of step definitions to execute
12730
+ * @param deps - Shared phase dependencies
12731
+ * @param runId - Pipeline run ID
12732
+ * @param phase - Phase name (for decision store persistence)
12733
+ * @param params - Runtime parameters map (concept, product_brief, etc.)
12734
+ * @returns Aggregated multi-step result
12735
+ */
12736
+ async function runSteps(steps, deps, runId, phase, params) {
12737
+ const stepResults = [];
12738
+ const stepOutputs = new Map();
12739
+ let totalInput = 0;
12740
+ let totalOutput = 0;
12741
+ let totalElicitationInput = 0;
12742
+ let totalElicitationOutput = 0;
12743
+ const usedElicitationMethods = [];
12744
+ for (const step of steps) try {
12745
+ const template = await deps.pack.getPrompt(step.name);
12746
+ let prompt = template;
12747
+ for (const ref of step.context) {
12748
+ const value = resolveContext(ref, deps, runId, params, stepOutputs);
12749
+ prompt = prompt.replace(`{{${ref.placeholder}}}`, value);
12750
+ }
12751
+ const allDecisions = getDecisionsByPhaseForRun(deps.db, runId, phase);
12752
+ const budgetTokens = calculateDynamicBudget(4e3, allDecisions.length);
12753
+ let estimatedTokens = Math.ceil(prompt.length / 4);
12754
+ if (estimatedTokens > budgetTokens) {
12755
+ const decisionRefs = step.context.filter((ref) => ref.source.startsWith("decision:"));
12756
+ if (decisionRefs.length > 0) {
12757
+ logger$5.warn({
12758
+ step: step.name,
12759
+ estimatedTokens,
12760
+ budgetTokens
12761
+ }, "Prompt exceeds budget — attempting decision summarization");
12762
+ let summarizedPrompt = template;
12763
+ for (const ref of step.context) {
12764
+ let value;
12765
+ if (ref.source.startsWith("decision:")) {
12766
+ const path$1 = ref.source.slice(9);
12767
+ const [decPhase, decCategory] = path$1.split(".");
12768
+ if (decPhase && decCategory) {
12769
+ const decisions = getDecisionsByPhaseForRun(deps.db, runId, decPhase);
12770
+ const filtered = decisions.filter((d) => d.category === decCategory);
12771
+ const budgetChars = budgetTokens * 4;
12772
+ const availableChars = Math.max(200, Math.floor(budgetChars / decisionRefs.length));
12773
+ value = summarizeDecisions(filtered.map((d) => ({
12774
+ key: d.key,
12775
+ value: d.value,
12776
+ category: d.category
12777
+ })), availableChars);
12778
+ } else value = resolveContext(ref, deps, runId, params, stepOutputs);
12779
+ } else value = resolveContext(ref, deps, runId, params, stepOutputs);
12780
+ summarizedPrompt = summarizedPrompt.replace(`{{${ref.placeholder}}}`, value);
12781
+ }
12782
+ prompt = summarizedPrompt;
12783
+ estimatedTokens = Math.ceil(prompt.length / 4);
12784
+ if (estimatedTokens <= budgetTokens) logger$5.info({
12785
+ step: step.name,
12786
+ estimatedTokens,
12787
+ budgetTokens
12788
+ }, "Decision summarization brought prompt within budget");
12789
+ }
12790
+ if (estimatedTokens > budgetTokens) {
12791
+ const errorMsg = `Step '${step.name}' prompt exceeds token budget after summarization: ${estimatedTokens} tokens (max ${budgetTokens})`;
12792
+ stepResults.push({
12793
+ name: step.name,
12794
+ success: false,
12795
+ parsed: null,
12796
+ error: errorMsg,
12797
+ tokenUsage: {
12798
+ input: 0,
12799
+ output: 0
12800
+ }
12801
+ });
12802
+ return {
12803
+ success: false,
12804
+ steps: stepResults,
12805
+ tokenUsage: {
12806
+ input: totalInput,
12807
+ output: totalOutput
12808
+ },
12809
+ elicitationTokenUsage: {
12810
+ input: totalElicitationInput,
12811
+ output: totalElicitationOutput
12812
+ },
12813
+ error: errorMsg
12814
+ };
12815
+ }
12816
+ }
12817
+ const handle = deps.dispatcher.dispatch({
12818
+ prompt,
12819
+ agent: "claude-code",
12820
+ taskType: step.taskType,
12821
+ outputSchema: step.outputSchema
12822
+ });
12823
+ const dispatchResult = await handle.result;
12824
+ const tokenUsage = {
12825
+ input: dispatchResult.tokenEstimate.input,
12826
+ output: dispatchResult.tokenEstimate.output
12827
+ };
12828
+ totalInput += tokenUsage.input;
12829
+ totalOutput += tokenUsage.output;
12830
+ if (dispatchResult.status === "timeout") {
12831
+ const errorMsg = `Step '${step.name}' timed out after ${dispatchResult.durationMs}ms`;
12832
+ stepResults.push({
12833
+ name: step.name,
12834
+ success: false,
12835
+ parsed: null,
12836
+ error: errorMsg,
12837
+ tokenUsage
12838
+ });
12839
+ return {
12840
+ success: false,
12841
+ steps: stepResults,
12842
+ tokenUsage: {
12843
+ input: totalInput,
12844
+ output: totalOutput
12845
+ },
12846
+ elicitationTokenUsage: {
12847
+ input: totalElicitationInput,
12848
+ output: totalElicitationOutput
12849
+ },
12850
+ error: errorMsg
12851
+ };
12852
+ }
12853
+ if (dispatchResult.status === "failed") {
12854
+ const errorMsg = `Step '${step.name}' dispatch failed: ${dispatchResult.parseError ?? dispatchResult.output}`;
12855
+ stepResults.push({
12856
+ name: step.name,
12857
+ success: false,
12858
+ parsed: null,
12859
+ error: errorMsg,
12860
+ tokenUsage
12861
+ });
12862
+ return {
12863
+ success: false,
12864
+ steps: stepResults,
12865
+ tokenUsage: {
12866
+ input: totalInput,
12867
+ output: totalOutput
12868
+ },
12869
+ elicitationTokenUsage: {
12870
+ input: totalElicitationInput,
12871
+ output: totalElicitationOutput
12872
+ },
12873
+ error: errorMsg
12874
+ };
12875
+ }
12876
+ if (dispatchResult.parsed === null || dispatchResult.parseError !== null) {
12877
+ const errorMsg = `Step '${step.name}' schema validation failed: ${dispatchResult.parseError ?? "No parsed output"}`;
12878
+ stepResults.push({
12879
+ name: step.name,
12880
+ success: false,
12881
+ parsed: null,
12882
+ error: errorMsg,
12883
+ tokenUsage
12884
+ });
12885
+ return {
12886
+ success: false,
12887
+ steps: stepResults,
12888
+ tokenUsage: {
12889
+ input: totalInput,
12890
+ output: totalOutput
12891
+ },
12892
+ elicitationTokenUsage: {
12893
+ input: totalElicitationInput,
12894
+ output: totalElicitationOutput
12895
+ },
12896
+ error: errorMsg
12897
+ };
12898
+ }
12899
+ const parsed = dispatchResult.parsed;
12900
+ if (parsed.result === "failed") {
12901
+ const errorMsg = `Step '${step.name}' agent reported failure`;
12902
+ stepResults.push({
12903
+ name: step.name,
12904
+ success: false,
12905
+ parsed: null,
12906
+ error: errorMsg,
12907
+ tokenUsage
12908
+ });
12909
+ return {
12910
+ success: false,
12911
+ steps: stepResults,
12912
+ tokenUsage: {
12913
+ input: totalInput,
12914
+ output: totalOutput
12915
+ },
12916
+ elicitationTokenUsage: {
12917
+ input: totalElicitationInput,
12918
+ output: totalElicitationOutput
12919
+ },
12920
+ error: errorMsg
12921
+ };
12922
+ }
12923
+ stepOutputs.set(step.name, parsed);
12924
+ for (const mapping of step.persist) {
12925
+ const fieldValue = parsed[mapping.field];
12926
+ if (fieldValue === void 0) continue;
12927
+ if (mapping.key === "array" && Array.isArray(fieldValue)) for (const [index, item] of fieldValue.entries()) upsertDecision(deps.db, {
12928
+ pipeline_run_id: runId,
12929
+ phase,
12930
+ category: mapping.category,
12931
+ key: `${step.name}-${index}`,
12932
+ value: typeof item === "object" ? JSON.stringify(item) : String(item)
12933
+ });
12934
+ else if (typeof fieldValue === "object" && fieldValue !== null) upsertDecision(deps.db, {
12935
+ pipeline_run_id: runId,
12936
+ phase,
12937
+ category: mapping.category,
12938
+ key: mapping.key,
12939
+ value: JSON.stringify(fieldValue)
12940
+ });
12941
+ else upsertDecision(deps.db, {
12942
+ pipeline_run_id: runId,
12943
+ phase,
12944
+ category: mapping.category,
12945
+ key: mapping.key,
12946
+ value: String(fieldValue)
12947
+ });
12948
+ }
12949
+ let artifactId;
12950
+ if (step.registerArtifact) {
12951
+ const artifact = registerArtifact(deps.db, {
12952
+ pipeline_run_id: runId,
12953
+ phase,
12954
+ type: step.registerArtifact.type,
12955
+ path: step.registerArtifact.path,
12956
+ summary: step.registerArtifact.summarize(parsed)
12957
+ });
12958
+ artifactId = artifact.id;
12959
+ }
12960
+ if (step.critique === true) try {
12961
+ const artifactContent = JSON.stringify(parsed, null, 2);
12962
+ const critiqueResult = await runCritiqueLoop(artifactContent, phase, runId, phase, deps);
12963
+ totalInput += critiqueResult.critiqueTokens.input + critiqueResult.refinementTokens.input;
12964
+ totalOutput += critiqueResult.critiqueTokens.output + critiqueResult.refinementTokens.output;
12965
+ logger$5.info({
12966
+ step: step.name,
12967
+ verdict: critiqueResult.verdict,
12968
+ iterations: critiqueResult.iterations,
12969
+ totalMs: critiqueResult.totalMs
12970
+ }, "Step critique loop complete");
12971
+ } catch (critiqueErr) {
12972
+ const critiqueMsg = critiqueErr instanceof Error ? critiqueErr.message : String(critiqueErr);
12973
+ logger$5.warn({
12974
+ step: step.name,
12975
+ err: critiqueMsg
12976
+ }, "Step critique loop threw an error — continuing without critique");
12977
+ }
12978
+ let stepElicitationTokens;
12979
+ if (step.elicitate === true) try {
12980
+ const contentType = deriveContentType(phase, step.name);
12981
+ const selectedMethods = selectMethods({ content_type: contentType }, usedElicitationMethods);
12982
+ if (selectedMethods.length > 0) {
12983
+ logger$5.info({
12984
+ step: step.name,
12985
+ methods: selectedMethods.map((m) => m.name),
12986
+ contentType
12987
+ }, "Running automated elicitation");
12988
+ const elicitationTemplate = await deps.pack.getPrompt("elicitation-apply");
12989
+ const artifactContent = JSON.stringify(parsed, null, 2);
12990
+ let elicitInput = 0;
12991
+ let elicitOutput = 0;
12992
+ let roundIndex = 0;
12993
+ for (const method of selectedMethods) {
12994
+ roundIndex++;
12995
+ const elicitPrompt = elicitationTemplate.replace(/\{\{method_name\}\}/g, method.name).replace(/\{\{method_description\}\}/g, method.description).replace(/\{\{output_pattern\}\}/g, method.output_pattern).replace(/\{\{artifact_content\}\}/g, artifactContent);
12996
+ const elicitHandle = deps.dispatcher.dispatch({
12997
+ prompt: elicitPrompt,
12998
+ agent: "claude-code",
12999
+ taskType: "elicitation",
13000
+ outputSchema: ElicitationOutputSchema
13001
+ });
13002
+ const elicitResult = await elicitHandle.result;
13003
+ elicitInput += elicitResult.tokenEstimate.input;
13004
+ elicitOutput += elicitResult.tokenEstimate.output;
13005
+ if (elicitResult.status === "completed" && elicitResult.parsed !== null) {
13006
+ const elicitParsed = elicitResult.parsed;
13007
+ if (elicitParsed.result === "success" && elicitParsed.insights) {
13008
+ upsertDecision(deps.db, {
13009
+ pipeline_run_id: runId,
13010
+ phase,
13011
+ category: "elicitation",
13012
+ key: `${phase}-round-${roundIndex}-method`,
13013
+ value: method.name
13014
+ });
13015
+ upsertDecision(deps.db, {
13016
+ pipeline_run_id: runId,
13017
+ phase,
13018
+ category: "elicitation",
13019
+ key: `${phase}-round-${roundIndex}-insights`,
13020
+ value: elicitParsed.insights
13021
+ });
13022
+ logger$5.info({
13023
+ step: step.name,
13024
+ method: method.name,
13025
+ roundIndex
13026
+ }, "Elicitation insights stored in decision store");
13027
+ }
13028
+ } else logger$5.warn({
13029
+ step: step.name,
13030
+ method: method.name,
13031
+ status: elicitResult.status
13032
+ }, "Elicitation dispatch did not produce valid output — skipping");
13033
+ usedElicitationMethods.push(method.name);
13034
+ }
13035
+ stepElicitationTokens = {
13036
+ input: elicitInput,
13037
+ output: elicitOutput
13038
+ };
13039
+ totalElicitationInput += elicitInput;
13040
+ totalElicitationOutput += elicitOutput;
13041
+ }
13042
+ } catch (elicitErr) {
13043
+ const elicitMsg = elicitErr instanceof Error ? elicitErr.message : String(elicitErr);
13044
+ logger$5.warn({
13045
+ step: step.name,
13046
+ err: elicitMsg
13047
+ }, "Step elicitation threw an error — continuing without elicitation");
13048
+ }
13049
+ const stepResult = {
13050
+ name: step.name,
13051
+ success: true,
13052
+ parsed,
13053
+ error: null,
13054
+ tokenUsage
13055
+ };
13056
+ if (artifactId !== void 0) stepResult.artifactId = artifactId;
13057
+ if (stepElicitationTokens !== void 0) stepResult.elicitationTokenUsage = stepElicitationTokens;
13058
+ stepResults.push(stepResult);
13059
+ } catch (err) {
13060
+ const message = err instanceof Error ? err.message : String(err);
13061
+ const errorMsg = `Step '${step.name}' unexpected error: ${message}`;
13062
+ stepResults.push({
13063
+ name: step.name,
13064
+ success: false,
13065
+ parsed: null,
13066
+ error: errorMsg,
13067
+ tokenUsage: {
13068
+ input: 0,
13069
+ output: 0
13070
+ }
13071
+ });
13072
+ return {
13073
+ success: false,
13074
+ steps: stepResults,
13075
+ tokenUsage: {
13076
+ input: totalInput,
13077
+ output: totalOutput
13078
+ },
13079
+ elicitationTokenUsage: {
13080
+ input: totalElicitationInput,
13081
+ output: totalElicitationOutput
13082
+ },
13083
+ error: errorMsg
13084
+ };
13085
+ }
13086
+ return {
13087
+ success: true,
13088
+ steps: stepResults,
13089
+ tokenUsage: {
13090
+ input: totalInput,
13091
+ output: totalOutput
13092
+ },
13093
+ elicitationTokenUsage: {
13094
+ input: totalElicitationInput,
13095
+ output: totalElicitationOutput
13096
+ }
13097
+ };
13098
+ }
12204
13099
 
12205
13100
  //#endregion
12206
13101
  //#region src/modules/phase-orchestrator/phases/analysis.ts
@@ -12877,64 +13772,43 @@ async function runPlanningPhase(deps, params) {
12877
13772
  }
12878
13773
 
12879
13774
  //#endregion
12880
- //#region src/modules/quality-gates/gate-impl.ts
13775
+ //#region src/modules/phase-orchestrator/schemas/readiness-output.ts
12881
13776
  /**
12882
- * Concrete implementation of QualityGate.
13777
+ * A single finding identified by the readiness check agent.
12883
13778
  */
12884
- var QualityGateImpl = class {
12885
- config;
12886
- _retryCount = 0;
12887
- constructor(config) {
12888
- this.config = config;
12889
- }
12890
- get name() {
12891
- return this.config.name;
12892
- }
12893
- /**
12894
- * Evaluate the given output against this gate's evaluator function.
12895
- *
12896
- * Flow:
12897
- * - Run evaluator
12898
- * - If pass → return `{ action: 'proceed', result: output }`
12899
- * - If fail and retries remain → increment counter, return `{ action: 'retry', ... }`
12900
- * - If fail and no retries remain → return `{ action: 'warn', ... }`
12901
- */
12902
- evaluate(output) {
12903
- const evaluation = this.config.evaluator(output);
12904
- if (evaluation.pass) return {
12905
- action: "proceed",
12906
- issues: evaluation.issues,
12907
- retriesRemaining: this.config.maxRetries - this._retryCount,
12908
- result: output
12909
- };
12910
- const retriesRemaining = this.config.maxRetries - this._retryCount;
12911
- if (retriesRemaining > 0) {
12912
- this._retryCount += 1;
12913
- return {
12914
- action: "retry",
12915
- issues: evaluation.issues,
12916
- retriesRemaining: retriesRemaining - 1
12917
- };
12918
- }
12919
- return {
12920
- action: "warn",
12921
- issues: evaluation.issues,
12922
- retriesRemaining: 0
12923
- };
12924
- }
12925
- /**
12926
- * Reset the retry counter to 0, allowing re-use of this gate.
12927
- */
12928
- reset() {
12929
- this._retryCount = 0;
12930
- }
12931
- };
13779
+ const ReadinessFindingSchema = z.object({
13780
+ category: z.enum([
13781
+ "fr_coverage",
13782
+ "architecture_compliance",
13783
+ "story_quality",
13784
+ "ux_alignment",
13785
+ "dependency_validity"
13786
+ ]),
13787
+ severity: z.enum([
13788
+ "blocker",
13789
+ "major",
13790
+ "minor"
13791
+ ]),
13792
+ description: z.string().min(1),
13793
+ affected_items: z.array(z.string()).default([])
13794
+ });
12932
13795
  /**
12933
- * Factory function to create a QualityGate instance from config.
13796
+ * Full output schema for the readiness check agent.
13797
+ *
13798
+ * The agent must emit a YAML block matching this schema.
13799
+ * - READY: all FRs covered, no blockers, acceptable quality — pipeline may proceed
13800
+ * - NEEDS_WORK: gaps identified but fixable via targeted story regeneration
13801
+ * - NOT_READY: critical failures that cannot be auto-remediated
12934
13802
  */
12935
- function createQualityGate(config) {
12936
- return new QualityGateImpl(config);
12937
- }
13803
+ const ReadinessOutputSchema = z.object({
13804
+ verdict: z.enum([
13805
+ "READY",
13806
+ "NEEDS_WORK",
13807
+ "NOT_READY"
13808
+ ]),
13809
+ coverage_score: z.number().min(0).max(100),
13810
+ findings: z.array(ReadinessFindingSchema).default([])
13811
+ });
12938
13812
 
12939
13813
  //#endregion
12940
13814
  //#region src/modules/phase-orchestrator/phases/solutioning.ts
@@ -12956,6 +13830,12 @@ const STORY_REQUIREMENTS_PLACEHOLDER = "{{requirements}}";
12956
13830
  const STORY_ARCHITECTURE_PLACEHOLDER = "{{architecture_decisions}}";
12957
13831
  /** Gap analysis placeholder used in retry prompt */
12958
13832
  const GAP_ANALYSIS_PLACEHOLDER = "{{gap_analysis}}";
13833
+ /** Placeholders in readiness-check prompt template */
13834
+ const READINESS_FR_PLACEHOLDER = "{{functional_requirements}}";
13835
+ const READINESS_NFR_PLACEHOLDER = "{{non_functional_requirements}}";
13836
+ const READINESS_ARCH_PLACEHOLDER = STORY_ARCHITECTURE_PLACEHOLDER;
13837
+ const READINESS_STORIES_PLACEHOLDER = "{{stories}}";
13838
+ const READINESS_UX_PLACEHOLDER = "{{ux_decisions}}";
12959
13839
  /**
12960
13840
  * Format functional and non-functional requirements from the planning phase
12961
13841
  * into a compact text block suitable for prompt injection.
@@ -13227,73 +14107,146 @@ async function runStoryGeneration(deps, params, gapAnalysis) {
13227
14107
  };
13228
14108
  }
13229
14109
  /**
13230
- * Run the readiness check using a QualityGate to verify FR-to-story traceability.
13231
- *
13232
- * For each functional requirement from the planning phase, checks if at least
13233
- * one solutioning-phase story references it (simple substring keyword match).
13234
- *
13235
- * @param deps - Shared phase dependencies
13236
- * @param runId - Pipeline run ID to scope the query
13237
- * @returns Object with `passed` boolean and optional `gaps` array (uncovered FRs)
14110
+ * Format functional requirements from pre-fetched planning phase decisions for prompt injection.
14111
+ * Accepts pre-fetched planning decisions to avoid duplicate DB queries (shared with formatNFRsForReadiness).
13238
14112
  */
13239
- async function runReadinessCheck(deps, runId) {
13240
- const { db } = deps;
13241
- const planningDecisions = getDecisionsByPhaseForRun(db, runId, "planning");
14113
+ function formatFRsForReadiness(planningDecisions) {
13242
14114
  const frDecisions = planningDecisions.filter((d) => d.category === "functional-requirements");
13243
- const functionalRequirements = [];
13244
- for (const d of frDecisions) try {
14115
+ if (frDecisions.length === 0) return "(No functional requirements found)";
14116
+ const lines = [];
14117
+ for (const [i, d] of frDecisions.entries()) try {
13245
14118
  const fr = JSON.parse(d.value);
13246
- if (fr.description) functionalRequirements.push(fr.description);
14119
+ lines.push(`- [${d.key ?? `FR-${i}`}] [${fr.priority?.toUpperCase() ?? "MUST"}] ${fr.description}`);
14120
+ } catch {
14121
+ lines.push(`- [${d.key ?? `FR-${i}`}] ${d.value}`);
14122
+ }
14123
+ return lines.join("\n");
14124
+ }
14125
+ /**
14126
+ * Format non-functional requirements from pre-fetched planning phase decisions for prompt injection.
14127
+ * Accepts pre-fetched planning decisions to avoid duplicate DB queries (shared with formatFRsForReadiness).
14128
+ */
14129
+ function formatNFRsForReadiness(planningDecisions) {
14130
+ const nfrDecisions = planningDecisions.filter((d) => d.category === "non-functional-requirements");
14131
+ if (nfrDecisions.length === 0) return "(No non-functional requirements found)";
14132
+ const lines = [];
14133
+ for (const d of nfrDecisions) try {
14134
+ const nfr = JSON.parse(d.value);
14135
+ lines.push(`- [${nfr.category?.toUpperCase() ?? "NFR"}] ${nfr.description}`);
13247
14136
  } catch {
13248
- functionalRequirements.push(d.value);
14137
+ lines.push(`- ${d.value}`);
13249
14138
  }
14139
+ return lines.join("\n");
14140
+ }
14141
+ /**
14142
+ * Format all stories from solutioning phase for prompt injection.
14143
+ */
14144
+ function formatStoriesForReadiness(db, runId) {
13250
14145
  const solutioningDecisions = getDecisionsByPhaseForRun(db, runId, "solutioning");
13251
14146
  const storyDecisions = solutioningDecisions.filter((d) => d.category === "stories");
13252
- const stories = [];
14147
+ if (storyDecisions.length === 0) return "(No stories found)";
14148
+ const lines = [];
13253
14149
  for (const d of storyDecisions) try {
13254
14150
  const story = JSON.parse(d.value);
13255
- stories.push({
13256
- description: [story.title ?? "", story.description ?? ""].join(" "),
13257
- ac: story.ac ?? []
13258
- });
14151
+ lines.push(`### Story ${story.key ?? d.key}: ${story.title ?? "(untitled)"}`);
14152
+ lines.push(`**Priority**: ${story.priority ?? "must"}`);
14153
+ lines.push(`**Description**: ${story.description ?? ""}`);
14154
+ const acList = story.acceptance_criteria ?? story.ac;
14155
+ if (acList && acList.length > 0) {
14156
+ lines.push("**Acceptance Criteria**:");
14157
+ for (const ac of acList) lines.push(` - ${ac}`);
14158
+ }
14159
+ lines.push("");
13259
14160
  } catch {
13260
- stories.push({
13261
- description: d.value,
13262
- ac: []
13263
- });
14161
+ lines.push(`### Story ${d.key}: ${d.value}`);
14162
+ lines.push("");
14163
+ }
14164
+ return lines.join("\n");
14165
+ }
14166
+ /**
14167
+ * Format UX decisions from the UX design phase (if any) for prompt injection.
14168
+ */
14169
+ function formatUxDecisionsForReadiness(db, runId) {
14170
+ const uxDecisions = getDecisionsByPhaseForRun(db, runId, "ux-design");
14171
+ if (uxDecisions.length === 0) return "";
14172
+ const lines = ["### UX Design Decisions"];
14173
+ for (const d of uxDecisions) lines.push(`- **${d.key}** [${d.category}]: ${d.value}`);
14174
+ return lines.join("\n");
14175
+ }
14176
+ /**
14177
+ * Run the adversarial readiness check by dispatching a sub-agent.
14178
+ *
14179
+ * Assembles comprehensive context (FRs, NFRs, architecture decisions, stories,
14180
+ * optional UX decisions) and dispatches a readiness-check agent to perform a
14181
+ * proper adversarial review — replacing the old QualityGate keyword matcher.
14182
+ *
14183
+ * @param deps - Shared phase dependencies
14184
+ * @param runId - Pipeline run ID to scope the query
14185
+ * @returns Readiness check result with verdict, findings, and coverage score
14186
+ */
14187
+ async function runReadinessCheck(deps, runId) {
14188
+ const { db, pack, dispatcher } = deps;
14189
+ const zeroTokenUsage = {
14190
+ input: 0,
14191
+ output: 0
14192
+ };
14193
+ let template;
14194
+ try {
14195
+ template = await pack.getPrompt("readiness-check");
14196
+ } catch {
14197
+ return {
14198
+ verdict: "error",
14199
+ error: "readiness-check prompt template not found in methodology pack",
14200
+ tokenUsage: zeroTokenUsage
14201
+ };
13264
14202
  }
13265
- const coverageData = {
13266
- functionalRequirements,
13267
- stories
14203
+ const planningDecisions = getDecisionsByPhaseForRun(db, runId, "planning");
14204
+ const formattedFRs = formatFRsForReadiness(planningDecisions);
14205
+ const formattedNFRs = formatNFRsForReadiness(planningDecisions);
14206
+ const formattedArchitecture = formatArchitectureDecisions(db, runId);
14207
+ const formattedStories = formatStoriesForReadiness(db, runId);
14208
+ const formattedUx = formatUxDecisionsForReadiness(db, runId);
14209
+ let prompt = template.replace(READINESS_FR_PLACEHOLDER, formattedFRs).replace(READINESS_NFR_PLACEHOLDER, formattedNFRs).replace(READINESS_ARCH_PLACEHOLDER, formattedArchitecture).replace(READINESS_STORIES_PLACEHOLDER, formattedStories);
14210
+ if (formattedUx) prompt = prompt.replace(READINESS_UX_PLACEHOLDER, formattedUx);
14211
+ else prompt = prompt.replace(READINESS_UX_PLACEHOLDER, "");
14212
+ const handle = dispatcher.dispatch({
14213
+ prompt,
14214
+ agent: "claude-code",
14215
+ taskType: "readiness-check",
14216
+ outputSchema: ReadinessOutputSchema
14217
+ });
14218
+ const dispatchResult = await handle.result;
14219
+ const tokenEstimate = dispatchResult.tokenEstimate;
14220
+ const tokenUsage = {
14221
+ input: tokenEstimate.input,
14222
+ output: tokenEstimate.output
14223
+ };
14224
+ logger$4.info({
14225
+ runId,
14226
+ durationMs: dispatchResult.durationMs,
14227
+ tokens: tokenEstimate
14228
+ }, "Readiness check dispatch completed");
14229
+ if (dispatchResult.status === "timeout") return {
14230
+ verdict: "error",
14231
+ error: `Readiness check agent timed out after ${dispatchResult.durationMs}ms`,
14232
+ tokenUsage
14233
+ };
14234
+ if (dispatchResult.status === "failed") return {
14235
+ verdict: "error",
14236
+ error: `Readiness check dispatch failed: ${dispatchResult.parseError ?? dispatchResult.output}`,
14237
+ tokenUsage
14238
+ };
14239
+ if (dispatchResult.parsed === null || dispatchResult.parseError !== null) return {
14240
+ verdict: "error",
14241
+ error: `Readiness check schema validation failed: ${dispatchResult.parseError ?? "No parsed output"}`,
14242
+ tokenUsage
13268
14243
  };
13269
- const gate = createQualityGate({
13270
- name: "solutioning-readiness",
13271
- maxRetries: 0,
13272
- evaluator: (output) => {
13273
- const data = output;
13274
- const gaps$1 = [];
13275
- for (const fr of data.functionalRequirements) {
13276
- const frLower = fr.toLowerCase();
13277
- const frKeywords = frLower.split(/\s+/).filter((w) => w.length > 4);
13278
- const covered = data.stories.some((story) => {
13279
- const storyText = [story.description, ...story.ac].join(" ").toLowerCase();
13280
- return frKeywords.some((kw) => storyText.includes(kw)) || storyText.includes(frLower);
13281
- });
13282
- if (!covered) gaps$1.push(fr);
13283
- }
13284
- return {
13285
- pass: gaps$1.length === 0,
13286
- issues: gaps$1.map((g) => `Uncovered FR: ${g}`),
13287
- severity: gaps$1.length === 0 ? "info" : "error"
13288
- };
13289
- }
13290
- });
13291
- const gateResult = gate.evaluate(coverageData);
13292
- if (gateResult.action === "proceed") return { passed: true };
13293
- const gaps = gateResult.issues.map((issue) => issue.replace(/^Uncovered FR: /, ""));
14244
+ const parsed = dispatchResult.parsed;
13294
14245
  return {
13295
- passed: false,
13296
- gaps
14246
+ verdict: parsed.verdict,
14247
+ findings: parsed.findings ?? [],
14248
+ coverageScore: parsed.coverage_score,
14249
+ tokenUsage
13297
14250
  };
13298
14251
  }
13299
14252
  /**
@@ -13579,65 +14532,228 @@ async function runSolutioningPhase(deps, params) {
13579
14532
  }
13580
14533
  };
13581
14534
  const readinessResult = await runReadinessCheck(deps, params.runId);
13582
- if (!readinessResult.passed) {
13583
- const gaps = readinessResult.gaps ?? [];
13584
- const gapAnalysis = [
13585
- "## Gap Analysis: Uncovered Functional Requirements",
13586
- "The following functional requirements are not covered by any generated story:",
13587
- ...gaps.map((g) => `- ${g}`),
13588
- "",
13589
- "Please generate additional stories to cover these requirements."
13590
- ].join("\n");
13591
- const retryResult = await runStoryGeneration(deps, params, gapAnalysis);
13592
- totalInput += retryResult.tokenUsage.input;
13593
- totalOutput += retryResult.tokenUsage.output;
13594
- if ("error" in retryResult) return {
14535
+ totalInput += readinessResult.tokenUsage.input;
14536
+ totalOutput += readinessResult.tokenUsage.output;
14537
+ if (readinessResult.verdict === "error") {
14538
+ logger$4.error({
14539
+ runId: params.runId,
14540
+ error: readinessResult.error
14541
+ }, "Readiness check agent failed");
14542
+ return {
13595
14543
  result: "failed",
13596
- error: "story_generation_retry_failed",
13597
- details: retryResult.error,
14544
+ error: "readiness_check_error",
14545
+ details: readinessResult.error,
13598
14546
  readiness_passed: false,
13599
- gaps,
13600
14547
  artifact_ids: [archResult.artifactId, storyResult.artifactId],
13601
14548
  tokenUsage: {
13602
14549
  input: totalInput,
13603
14550
  output: totalOutput
13604
14551
  }
13605
14552
  };
13606
- const retryReadiness = await runReadinessCheck(deps, params.runId);
13607
- if (!retryReadiness.passed) return {
14553
+ }
14554
+ logger$4.info({
14555
+ runId: params.runId,
14556
+ verdict: readinessResult.verdict,
14557
+ coverageScore: readinessResult.coverageScore,
14558
+ findingCount: readinessResult.findings.length
14559
+ }, "Readiness check verdict received");
14560
+ if (readinessResult.verdict === "NOT_READY") {
14561
+ const blockers = readinessResult.findings.filter((f) => f.severity === "blocker");
14562
+ const majorFindings = readinessResult.findings.filter((f) => f.severity === "major");
14563
+ for (const [i, finding] of readinessResult.findings.entries()) upsertDecision(deps.db, {
14564
+ pipeline_run_id: params.runId,
14565
+ phase: "solutioning",
14566
+ category: "readiness-findings",
14567
+ key: `finding-${i + 1}`,
14568
+ value: JSON.stringify(finding)
14569
+ });
14570
+ logger$4.error({
14571
+ runId: params.runId,
14572
+ verdict: "NOT_READY",
14573
+ coverageScore: readinessResult.coverageScore,
14574
+ blockers: blockers.length,
14575
+ major: majorFindings.length,
14576
+ findings: readinessResult.findings
14577
+ }, "Readiness check returned NOT_READY — solutioning phase failed");
14578
+ if (deps.eventBus) {
14579
+ deps.eventBus.emit("solutioning:readiness-check", {
14580
+ runId: params.runId,
14581
+ verdict: "NOT_READY",
14582
+ coverageScore: readinessResult.coverageScore,
14583
+ findingCount: readinessResult.findings.length,
14584
+ blockerCount: blockers.length
14585
+ });
14586
+ deps.eventBus.emit("solutioning:readiness-failed", {
14587
+ runId: params.runId,
14588
+ verdict: "NOT_READY",
14589
+ coverageScore: readinessResult.coverageScore,
14590
+ findings: readinessResult.findings.map((f) => ({
14591
+ category: f.category,
14592
+ severity: f.severity,
14593
+ description: f.description,
14594
+ affected_items: f.affected_items
14595
+ }))
14596
+ });
14597
+ }
14598
+ return {
13608
14599
  result: "failed",
13609
- error: "readiness_check_failed",
13610
- details: "Readiness check failed after maximum retries",
14600
+ error: "readiness_not_ready",
14601
+ details: `Readiness check returned NOT_READY: ${blockers.length} blockers, coverage score ${readinessResult.coverageScore}%`,
13611
14602
  readiness_passed: false,
13612
- gaps: retryReadiness.gaps ?? gaps,
13613
- artifact_ids: [
13614
- archResult.artifactId,
13615
- storyResult.artifactId,
13616
- retryResult.artifactId
13617
- ],
14603
+ gaps: readinessResult.findings.filter((f) => f.category === "fr_coverage").map((f) => f.description),
14604
+ artifact_ids: [archResult.artifactId, storyResult.artifactId],
13618
14605
  tokenUsage: {
13619
14606
  input: totalInput,
13620
14607
  output: totalOutput
13621
14608
  }
13622
14609
  };
13623
- const retryStories = retryResult.epics.reduce((sum, epic) => sum + epic.stories.length, 0);
13624
- return {
13625
- result: "success",
13626
- architecture_decisions: archResult.decisions.length,
13627
- epics: retryResult.epics.length,
13628
- stories: retryStories,
13629
- readiness_passed: true,
13630
- artifact_ids: [
13631
- archResult.artifactId,
13632
- storyResult.artifactId,
13633
- retryResult.artifactId
13634
- ],
13635
- tokenUsage: {
13636
- input: totalInput,
13637
- output: totalOutput
14610
+ }
14611
+ if (readinessResult.verdict === "NEEDS_WORK") {
14612
+ const blockers = readinessResult.findings.filter((f) => f.severity === "blocker");
14613
+ if (blockers.length > 0) {
14614
+ for (const [i, finding] of readinessResult.findings.entries()) upsertDecision(deps.db, {
14615
+ pipeline_run_id: params.runId,
14616
+ phase: "solutioning",
14617
+ category: "readiness-findings",
14618
+ key: `finding-${i + 1}`,
14619
+ value: JSON.stringify(finding)
14620
+ });
14621
+ if (deps.eventBus) deps.eventBus.emit("solutioning:readiness-check", {
14622
+ runId: params.runId,
14623
+ verdict: "NEEDS_WORK",
14624
+ coverageScore: readinessResult.coverageScore,
14625
+ findingCount: readinessResult.findings.length,
14626
+ blockerCount: blockers.length
14627
+ });
14628
+ const gapAnalysis = [
14629
+ "## Gap Analysis: Readiness Check Blocker Findings",
14630
+ "The readiness check identified the following blocker issues that must be addressed:",
14631
+ "",
14632
+ ...blockers.map((f) => [`### [${f.category.toUpperCase()}] ${f.description}`, f.affected_items.length > 0 ? `Affected: ${f.affected_items.join(", ")}` : ""].filter(Boolean).join("\n")),
14633
+ "",
14634
+ "Please generate additional or revised stories to specifically address each blocker above."
14635
+ ].join("\n");
14636
+ logger$4.info({
14637
+ runId: params.runId,
14638
+ blockerCount: blockers.length
14639
+ }, "Readiness NEEDS_WORK with blockers — retrying story generation with gap analysis");
14640
+ const retryResult = await runStoryGeneration(deps, params, gapAnalysis);
14641
+ totalInput += retryResult.tokenUsage.input;
14642
+ totalOutput += retryResult.tokenUsage.output;
14643
+ if ("error" in retryResult) return {
14644
+ result: "failed",
14645
+ error: "story_generation_retry_failed",
14646
+ details: retryResult.error,
14647
+ readiness_passed: false,
14648
+ gaps: blockers.map((f) => f.description),
14649
+ artifact_ids: [archResult.artifactId, storyResult.artifactId],
14650
+ tokenUsage: {
14651
+ input: totalInput,
14652
+ output: totalOutput
14653
+ }
14654
+ };
14655
+ const retryReadiness = await runReadinessCheck(deps, params.runId);
14656
+ totalInput += retryReadiness.tokenUsage.input;
14657
+ totalOutput += retryReadiness.tokenUsage.output;
14658
+ if (retryReadiness.verdict === "error") return {
14659
+ result: "failed",
14660
+ error: "readiness_check_error",
14661
+ details: retryReadiness.error,
14662
+ readiness_passed: false,
14663
+ artifact_ids: [
14664
+ archResult.artifactId,
14665
+ storyResult.artifactId,
14666
+ retryResult.artifactId
14667
+ ],
14668
+ tokenUsage: {
14669
+ input: totalInput,
14670
+ output: totalOutput
14671
+ }
14672
+ };
14673
+ if (retryReadiness.verdict === "NOT_READY" || retryReadiness.verdict === "NEEDS_WORK") {
14674
+ const retryBlockers = retryReadiness.findings.filter((f) => f.severity === "blocker");
14675
+ logger$4.error({
14676
+ runId: params.runId,
14677
+ verdict: retryReadiness.verdict,
14678
+ retryBlockers: retryBlockers.length
14679
+ }, "Readiness check failed after maximum retries");
14680
+ return {
14681
+ result: "failed",
14682
+ error: "readiness_check_failed",
14683
+ details: `Readiness check failed after maximum retries: verdict=${retryReadiness.verdict}, coverage=${retryReadiness.coverageScore}%`,
14684
+ readiness_passed: false,
14685
+ gaps: retryReadiness.findings.filter((f) => f.category === "fr_coverage").map((f) => f.description),
14686
+ artifact_ids: [
14687
+ archResult.artifactId,
14688
+ storyResult.artifactId,
14689
+ retryResult.artifactId
14690
+ ],
14691
+ tokenUsage: {
14692
+ input: totalInput,
14693
+ output: totalOutput
14694
+ }
14695
+ };
13638
14696
  }
13639
- };
14697
+ const retryStories = retryResult.epics.reduce((sum, epic) => sum + epic.stories.length, 0);
14698
+ const minorFindings$1 = retryReadiness.findings.filter((f) => f.severity === "minor");
14699
+ if (minorFindings$1.length > 0) logger$4.warn({
14700
+ runId: params.runId,
14701
+ minorFindings: minorFindings$1
14702
+ }, "Readiness READY with minor findings after retry");
14703
+ if (deps.eventBus) deps.eventBus.emit("solutioning:readiness-check", {
14704
+ runId: params.runId,
14705
+ verdict: "READY",
14706
+ coverageScore: retryReadiness.coverageScore,
14707
+ findingCount: retryReadiness.findings.length,
14708
+ blockerCount: 0
14709
+ });
14710
+ return {
14711
+ result: "success",
14712
+ architecture_decisions: archResult.decisions.length,
14713
+ epics: retryResult.epics.length,
14714
+ stories: retryStories,
14715
+ readiness_passed: true,
14716
+ artifact_ids: [
14717
+ archResult.artifactId,
14718
+ storyResult.artifactId,
14719
+ retryResult.artifactId
14720
+ ],
14721
+ tokenUsage: {
14722
+ input: totalInput,
14723
+ output: totalOutput
14724
+ }
14725
+ };
14726
+ }
14727
+ const majorFindings = readinessResult.findings.filter((f) => f.severity === "major");
14728
+ logger$4.warn({
14729
+ runId: params.runId,
14730
+ majorCount: majorFindings.length,
14731
+ findings: readinessResult.findings
14732
+ }, "Readiness NEEDS_WORK (no blockers) — proceeding with warnings");
14733
+ if (deps.eventBus) deps.eventBus.emit("solutioning:readiness-check", {
14734
+ runId: params.runId,
14735
+ verdict: "NEEDS_WORK",
14736
+ coverageScore: readinessResult.coverageScore,
14737
+ findingCount: readinessResult.findings.length,
14738
+ blockerCount: 0
14739
+ });
14740
+ }
14741
+ const minorFindings = readinessResult.findings.filter((f) => f.severity === "minor");
14742
+ if (minorFindings.length > 0) {
14743
+ const verdictLabel = readinessResult.verdict === "READY" ? "READY" : "NEEDS_WORK (no blockers)";
14744
+ logger$4.warn({
14745
+ runId: params.runId,
14746
+ verdict: readinessResult.verdict,
14747
+ minorFindings
14748
+ }, `Readiness ${verdictLabel} with minor findings — proceeding`);
13640
14749
  }
14750
+ if (readinessResult.verdict === "READY" && deps.eventBus) deps.eventBus.emit("solutioning:readiness-check", {
14751
+ runId: params.runId,
14752
+ verdict: "READY",
14753
+ coverageScore: readinessResult.coverageScore,
14754
+ findingCount: readinessResult.findings.length,
14755
+ blockerCount: 0
14756
+ });
13641
14757
  const totalStories = storyResult.epics.reduce((sum, epic) => sum + epic.stories.length, 0);
13642
14758
  return {
13643
14759
  result: "success",
@@ -13664,6 +14780,222 @@ async function runSolutioningPhase(deps, params) {
13664
14780
  }
13665
14781
  }
13666
14782
 
14783
+ //#endregion
14784
+ //#region src/modules/phase-orchestrator/phases/ux-design.ts
14785
+ /**
14786
+ * Build step definitions for 3-step UX design decomposition.
14787
+ *
14788
+ * Step 1: Discovery + Core Experience
14789
+ * - Injects product brief and requirements context
14790
+ * - Produces: target_personas, core_experience, emotional_goals, inspiration_references
14791
+ *
14792
+ * Step 2: Design System + Visual Foundation
14793
+ * - Injects product brief, requirements, and Step 1 discoveries
14794
+ * - Produces: design_system, visual_foundation, design_principles, color_and_typography
14795
+ *
14796
+ * Step 3: User Journeys + Components + Accessibility
14797
+ * - Injects product brief, requirements, Step 1 discoveries, Step 2 design system
14798
+ * - Produces: user_journeys, component_strategy, ux_patterns, accessibility_guidelines
14799
+ * - Registers 'ux-design' artifact
14800
+ */
14801
+ function buildUxDesignSteps() {
14802
+ return [
14803
+ {
14804
+ name: "ux-step-1-discovery",
14805
+ taskType: "ux-discovery",
14806
+ outputSchema: UxDiscoveryOutputSchema,
14807
+ context: [{
14808
+ placeholder: "product_brief",
14809
+ source: "decision:analysis.product-brief"
14810
+ }, {
14811
+ placeholder: "requirements",
14812
+ source: "decision:planning.functional-requirements"
14813
+ }],
14814
+ persist: [
14815
+ {
14816
+ field: "target_personas",
14817
+ category: "ux-design",
14818
+ key: "target_personas"
14819
+ },
14820
+ {
14821
+ field: "core_experience",
14822
+ category: "ux-design",
14823
+ key: "core_experience"
14824
+ },
14825
+ {
14826
+ field: "emotional_goals",
14827
+ category: "ux-design",
14828
+ key: "emotional_goals"
14829
+ },
14830
+ {
14831
+ field: "inspiration_references",
14832
+ category: "ux-design",
14833
+ key: "inspiration_references"
14834
+ }
14835
+ ],
14836
+ elicitate: true,
14837
+ elicitationMethods: ["User Persona Focus Group", "SCAMPER"]
14838
+ },
14839
+ {
14840
+ name: "ux-step-2-design-system",
14841
+ taskType: "ux-design-system",
14842
+ outputSchema: UxDesignSystemOutputSchema,
14843
+ context: [
14844
+ {
14845
+ placeholder: "product_brief",
14846
+ source: "decision:analysis.product-brief"
14847
+ },
14848
+ {
14849
+ placeholder: "requirements",
14850
+ source: "decision:planning.functional-requirements"
14851
+ },
14852
+ {
14853
+ placeholder: "ux_discovery",
14854
+ source: "step:ux-step-1-discovery"
14855
+ }
14856
+ ],
14857
+ persist: [
14858
+ {
14859
+ field: "design_system",
14860
+ category: "ux-design",
14861
+ key: "design_system"
14862
+ },
14863
+ {
14864
+ field: "visual_foundation",
14865
+ category: "ux-design",
14866
+ key: "visual_foundation"
14867
+ },
14868
+ {
14869
+ field: "design_principles",
14870
+ category: "ux-design",
14871
+ key: "design_principles"
14872
+ },
14873
+ {
14874
+ field: "color_and_typography",
14875
+ category: "ux-design",
14876
+ key: "color_and_typography"
14877
+ }
14878
+ ],
14879
+ elicitate: true,
14880
+ elicitationMethods: ["SCAMPER", "Design Thinking"]
14881
+ },
14882
+ {
14883
+ name: "ux-step-3-journeys",
14884
+ taskType: "ux-journeys",
14885
+ outputSchema: UxJourneysOutputSchema,
14886
+ context: [
14887
+ {
14888
+ placeholder: "product_brief",
14889
+ source: "decision:analysis.product-brief"
14890
+ },
14891
+ {
14892
+ placeholder: "requirements",
14893
+ source: "decision:planning.functional-requirements"
14894
+ },
14895
+ {
14896
+ placeholder: "ux_discovery",
14897
+ source: "step:ux-step-1-discovery"
14898
+ },
14899
+ {
14900
+ placeholder: "design_system",
14901
+ source: "step:ux-step-2-design-system"
14902
+ }
14903
+ ],
14904
+ persist: [
14905
+ {
14906
+ field: "user_journeys",
14907
+ category: "ux-design",
14908
+ key: "user_journeys"
14909
+ },
14910
+ {
14911
+ field: "component_strategy",
14912
+ category: "ux-design",
14913
+ key: "component_strategy"
14914
+ },
14915
+ {
14916
+ field: "ux_patterns",
14917
+ category: "ux-design",
14918
+ key: "ux_patterns"
14919
+ },
14920
+ {
14921
+ field: "accessibility_guidelines",
14922
+ category: "ux-design",
14923
+ key: "accessibility_guidelines"
14924
+ }
14925
+ ],
14926
+ registerArtifact: {
14927
+ type: "ux-design",
14928
+ path: "decision-store://ux-design/ux-design",
14929
+ summarize: (parsed) => {
14930
+ const journeys = Array.isArray(parsed.user_journeys) ? parsed.user_journeys : void 0;
14931
+ const patterns = Array.isArray(parsed.ux_patterns) ? parsed.ux_patterns : void 0;
14932
+ const count = (journeys?.length ?? 0) + (patterns?.length ?? 0);
14933
+ return count > 0 ? `${count} UX decisions captured (journeys + patterns)` : "UX design complete";
14934
+ }
14935
+ },
14936
+ critique: true
14937
+ }
14938
+ ];
14939
+ }
14940
+ /**
14941
+ * Execute the UX design phase of the BMAD pipeline.
14942
+ *
14943
+ * Runs 3 sequential steps covering discovery, design system, and user journeys.
14944
+ * Each step builds on prior step decisions via the decision store.
14945
+ *
14946
+ * On success, a 'ux-design' artifact is registered and UX decisions are
14947
+ * available to the architecture phase via `decision:ux-design.*`.
14948
+ *
14949
+ * @param deps - Shared phase dependencies (db, pack, contextCompiler, dispatcher)
14950
+ * @param params - Phase parameters (runId)
14951
+ * @returns UxDesignResult with success/failure status and token usage
14952
+ */
14953
+ async function runUxDesignPhase(deps, params) {
14954
+ const { runId } = params;
14955
+ const zeroTokenUsage = {
14956
+ input: 0,
14957
+ output: 0
14958
+ };
14959
+ try {
14960
+ const steps = buildUxDesignSteps();
14961
+ const result = await runSteps(steps, deps, runId, "ux-design", {});
14962
+ if (!result.success) return {
14963
+ result: "failed",
14964
+ error: result.error ?? "ux_design_multi_step_failed",
14965
+ details: result.error ?? "UX design multi-step execution failed",
14966
+ tokenUsage: result.tokenUsage
14967
+ };
14968
+ const lastStep = result.steps[result.steps.length - 1];
14969
+ const artifactId = lastStep?.artifactId;
14970
+ if (!artifactId) {
14971
+ const artifact = registerArtifact(deps.db, {
14972
+ pipeline_run_id: runId,
14973
+ phase: "ux-design",
14974
+ type: "ux-design",
14975
+ path: "decision-store://ux-design/ux-design",
14976
+ summary: "UX design phase completed"
14977
+ });
14978
+ return {
14979
+ result: "success",
14980
+ artifact_id: artifact.id,
14981
+ tokenUsage: result.tokenUsage
14982
+ };
14983
+ }
14984
+ return {
14985
+ result: "success",
14986
+ artifact_id: artifactId,
14987
+ tokenUsage: result.tokenUsage
14988
+ };
14989
+ } catch (err) {
14990
+ const message = err instanceof Error ? err.message : String(err);
14991
+ return {
14992
+ result: "failed",
14993
+ error: message,
14994
+ tokenUsage: zeroTokenUsage
14995
+ };
14996
+ }
14997
+ }
14998
+
13667
14999
  //#endregion
13668
15000
  //#region src/modules/stop-after/types.ts
13669
15001
  /**
@@ -14659,7 +15991,7 @@ function mapInternalPhaseToEventPhase(internalPhase) {
14659
15991
  }
14660
15992
  }
14661
15993
  async function runAutoRun(options) {
14662
- const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag } = options;
15994
+ const { pack: packName, from: startPhase, stopAfter, concept: conceptArg, conceptFile, stories: storiesArg, concurrency, outputFormat, projectRoot, events: eventsFlag, verbose: verboseFlag, tui: tuiFlag, skipUx } = options;
14663
15995
  if (startPhase !== void 0 && !VALID_PHASES.includes(startPhase)) {
14664
15996
  const errorMsg = `Invalid phase '${startPhase}'. Valid phases: ${VALID_PHASES.join(", ")}`;
14665
15997
  if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
@@ -14713,7 +16045,9 @@ async function runAutoRun(options) {
14713
16045
  concept,
14714
16046
  concurrency,
14715
16047
  outputFormat,
14716
- projectRoot
16048
+ projectRoot,
16049
+ ...eventsFlag === true ? { events: true } : {},
16050
+ ...skipUx === true ? { skipUx: true } : {}
14717
16051
  });
14718
16052
  let storyKeys = [];
14719
16053
  if (storiesArg !== void 0 && storiesArg !== "") {
@@ -15071,6 +16405,29 @@ async function runAutoRun(options) {
15071
16405
  else if (s.phase === "ESCALATED") if (s.error !== void 0) failedKeys.push(key);
15072
16406
  else escalatedKeys.push(key);
15073
16407
  else failedKeys.push(key);
16408
+ try {
16409
+ const runEndMs = Date.now();
16410
+ const runStartMs = new Date(pipelineRun.created_at).getTime();
16411
+ const tokenAgg = aggregateTokenUsageForRun(db, pipelineRun.id);
16412
+ writeRunMetrics(db, {
16413
+ run_id: pipelineRun.id,
16414
+ methodology: pack.manifest.name,
16415
+ status: failedKeys.length > 0 ? "failed" : "completed",
16416
+ started_at: pipelineRun.created_at,
16417
+ completed_at: new Date().toISOString(),
16418
+ wall_clock_seconds: Math.round((runEndMs - runStartMs) / 1e3),
16419
+ total_input_tokens: tokenAgg.input,
16420
+ total_output_tokens: tokenAgg.output,
16421
+ total_cost_usd: tokenAgg.cost,
16422
+ stories_attempted: storyKeys.length,
16423
+ stories_succeeded: succeededKeys.length,
16424
+ stories_failed: failedKeys.length,
16425
+ stories_escalated: escalatedKeys.length,
16426
+ concurrency_setting: concurrency
16427
+ });
16428
+ } catch (metricsErr) {
16429
+ logger$3.warn({ err: metricsErr }, "Failed to write run metrics (best-effort)");
16430
+ }
15074
16431
  if (progressRenderer !== void 0) progressRenderer.render({
15075
16432
  type: "pipeline:complete",
15076
16433
  ts: new Date().toISOString(),
@@ -15123,7 +16480,7 @@ async function runAutoRun(options) {
15123
16480
  }
15124
16481
  }
15125
16482
  async function runFullPipeline(options) {
15126
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot } = options;
16483
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx } = options;
15127
16484
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
15128
16485
  const dbWrapper = new DatabaseWrapper(dbPath);
15129
16486
  try {
@@ -15163,9 +16520,16 @@ async function runFullPipeline(options) {
15163
16520
  contextCompiler,
15164
16521
  dispatcher
15165
16522
  };
16523
+ const packForOrchestrator = skipUx === true && pack.manifest.uxDesign === true ? {
16524
+ ...pack,
16525
+ manifest: {
16526
+ ...pack.manifest,
16527
+ uxDesign: false
16528
+ }
16529
+ } : pack;
15166
16530
  const phaseOrchestrator = createPhaseOrchestrator({
15167
16531
  db,
15168
- pack
16532
+ pack: packForOrchestrator
15169
16533
  });
15170
16534
  const startedAt = Date.now();
15171
16535
  const runId = await phaseOrchestrator.startRun(concept ?? "", startPhase);
@@ -15173,7 +16537,14 @@ async function runFullPipeline(options) {
15173
16537
  process.stdout.write(`Starting full pipeline from phase: ${startPhase}\n`);
15174
16538
  process.stdout.write(`Pipeline run ID: ${runId}\n`);
15175
16539
  }
15176
- const phaseOrder = [
16540
+ const uxEnabled = packForOrchestrator.manifest.uxDesign === true;
16541
+ const phaseOrder = uxEnabled ? [
16542
+ "analysis",
16543
+ "planning",
16544
+ "ux-design",
16545
+ "solutioning",
16546
+ "implementation"
16547
+ ] : [
15177
16548
  "analysis",
15178
16549
  "planning",
15179
16550
  "solutioning",
@@ -15232,6 +16603,29 @@ async function runFullPipeline(options) {
15232
16603
  process.stdout.write(`[PLANNING] Complete — ${result.requirements_count ?? 0} requirements, ${result.user_stories_count ?? 0} user stories\n`);
15233
16604
  process.stdout.write(` Tokens: ${result.tokenUsage.input.toLocaleString()} input / ${result.tokenUsage.output.toLocaleString()} output\n`);
15234
16605
  }
16606
+ } else if (currentPhase === "ux-design") {
16607
+ const result = await runUxDesignPhase(phaseDeps, { runId });
16608
+ if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
16609
+ const costUsd = (result.tokenUsage.input * 3 + result.tokenUsage.output * 15) / 1e6;
16610
+ addTokenUsage(db, runId, {
16611
+ phase: "ux-design",
16612
+ agent: "claude-code",
16613
+ input_tokens: result.tokenUsage.input,
16614
+ output_tokens: result.tokenUsage.output,
16615
+ cost_usd: costUsd
16616
+ });
16617
+ }
16618
+ if (result.result === "failed") {
16619
+ updatePipelineRun(db, runId, { status: "failed" });
16620
+ const errorMsg = `UX design phase failed: ${result.error ?? "unknown error"}${result.details ? ` — ${result.details}` : ""}`;
16621
+ if (outputFormat === "human") process.stderr.write(`Error: ${errorMsg}\n`);
16622
+ else process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
16623
+ return 1;
16624
+ }
16625
+ if (outputFormat === "human") {
16626
+ process.stdout.write(`[UX-DESIGN] Complete — UX design artifact registered (artifact: ${result.artifact_id ?? "n/a"})\n`);
16627
+ process.stdout.write(` Tokens: ${result.tokenUsage.input.toLocaleString()} input / ${result.tokenUsage.output.toLocaleString()} output\n`);
16628
+ }
15235
16629
  } else if (currentPhase === "solutioning") {
15236
16630
  const result = await runSolutioningPhase(phaseDeps, { runId });
15237
16631
  if (result.tokenUsage.input > 0 || result.tokenUsage.output > 0) {
@@ -15245,8 +16639,17 @@ async function runFullPipeline(options) {
15245
16639
  });
15246
16640
  }
15247
16641
  if (result.result === "failed") {
15248
- updatePipelineRun(db, runId, { status: "failed" });
15249
16642
  const errorMsg = `Solutioning phase failed: ${result.error ?? "unknown error"}${result.details ? ` — ${result.details}` : ""}`;
16643
+ phaseOrchestrator.markPhaseFailed(runId, "solutioning", errorMsg);
16644
+ if (eventsFlag === true) {
16645
+ const ndjsonEmitter = createEventEmitter(process.stdout);
16646
+ ndjsonEmitter.emit({
16647
+ type: "story:warn",
16648
+ ts: new Date().toISOString(),
16649
+ key: "solutioning",
16650
+ msg: errorMsg
16651
+ });
16652
+ }
15250
16653
  if (outputFormat === "human") process.stderr.write(`Error: ${errorMsg}\n`);
15251
16654
  else process.stdout.write(formatOutput(null, "json", false, errorMsg) + "\n");
15252
16655
  return 1;
@@ -16430,6 +17833,90 @@ async function runAmendCommand(options) {
16430
17833
  } catch {}
16431
17834
  }
16432
17835
  }
17836
+ async function runAutoMetrics(options) {
17837
+ const { outputFormat, projectRoot, limit = 10, compare, tagBaseline } = options;
17838
+ const dbRoot = await resolveMainRepoRoot(projectRoot);
17839
+ const dbPath = join(dbRoot, ".substrate", "substrate.db");
17840
+ if (!existsSync(dbPath)) {
17841
+ if (outputFormat === "json") process.stdout.write(formatOutput({
17842
+ runs: [],
17843
+ message: "No metrics yet — no pipeline database found."
17844
+ }, "json", true) + "\n");
17845
+ else process.stdout.write("No metrics yet — no pipeline database found.\n");
17846
+ return 0;
17847
+ }
17848
+ const dbWrapper = new DatabaseWrapper(dbPath);
17849
+ try {
17850
+ dbWrapper.open();
17851
+ runMigrations(dbWrapper.db);
17852
+ const db = dbWrapper.db;
17853
+ if (tagBaseline !== void 0) {
17854
+ const row = getRunMetrics(db, tagBaseline);
17855
+ if (!row) {
17856
+ const msg = `Run '${tagBaseline}' not found in run_metrics.`;
17857
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
17858
+ else process.stderr.write(`Error: ${msg}\n`);
17859
+ return 1;
17860
+ }
17861
+ tagRunAsBaseline(db, tagBaseline);
17862
+ if (outputFormat === "json") process.stdout.write(formatOutput({ tagged_baseline: tagBaseline }, "json", true) + "\n");
17863
+ else process.stdout.write(`Baseline tagged: ${tagBaseline}\n`);
17864
+ return 0;
17865
+ }
17866
+ if (compare !== void 0) {
17867
+ const [idA, idB] = compare;
17868
+ const delta = compareRunMetrics(db, idA, idB);
17869
+ if (delta === null) {
17870
+ const msg = `One or both run IDs not found in metrics: ${idA}, ${idB}`;
17871
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
17872
+ else process.stderr.write(`Error: ${msg}\n`);
17873
+ return 1;
17874
+ }
17875
+ if (outputFormat === "json") process.stdout.write(formatOutput(delta, "json", true) + "\n");
17876
+ else {
17877
+ const sign = (n) => n > 0 ? "+" : "";
17878
+ process.stdout.write(`\nMetrics Comparison: ${idA.slice(0, 8)} vs ${idB.slice(0, 8)}\n`);
17879
+ process.stdout.write(` Input tokens: ${sign(delta.token_input_delta)}${delta.token_input_delta.toLocaleString()} (${sign(delta.token_input_pct)}${delta.token_input_pct}%)\n`);
17880
+ process.stdout.write(` Output tokens: ${sign(delta.token_output_delta)}${delta.token_output_delta.toLocaleString()} (${sign(delta.token_output_pct)}${delta.token_output_pct}%)\n`);
17881
+ process.stdout.write(` Wall clock: ${sign(delta.wall_clock_delta_seconds)}${delta.wall_clock_delta_seconds}s (${sign(delta.wall_clock_pct)}${delta.wall_clock_pct}%)\n`);
17882
+ process.stdout.write(` Review cycles: ${sign(delta.review_cycles_delta)}${delta.review_cycles_delta} (${sign(delta.review_cycles_pct)}${delta.review_cycles_pct}%)\n`);
17883
+ process.stdout.write(` Cost USD: ${sign(delta.cost_delta)}$${Math.abs(delta.cost_delta).toFixed(4)} (${sign(delta.cost_pct)}${delta.cost_pct}%)\n`);
17884
+ }
17885
+ return 0;
17886
+ }
17887
+ const runs = listRunMetrics(db, limit);
17888
+ if (outputFormat === "json") process.stdout.write(formatOutput({ runs }, "json", true) + "\n");
17889
+ else {
17890
+ if (runs.length === 0) {
17891
+ process.stdout.write("No run metrics recorded yet. Run `substrate auto run` to generate metrics.\n");
17892
+ return 0;
17893
+ }
17894
+ process.stdout.write(`\nPipeline Run Metrics (last ${runs.length} runs)\n`);
17895
+ process.stdout.write("─".repeat(80) + "\n");
17896
+ for (const run of runs) {
17897
+ const isBaseline = run.is_baseline ? " [BASELINE]" : "";
17898
+ process.stdout.write(`\nRun: ${run.run_id}${isBaseline}\n`);
17899
+ process.stdout.write(` Status: ${run.status} | Methodology: ${run.methodology}\n`);
17900
+ process.stdout.write(` Started: ${run.started_at}\n`);
17901
+ if (run.completed_at) process.stdout.write(` Completed: ${run.completed_at} (${run.wall_clock_seconds}s)\n`);
17902
+ process.stdout.write(` Stories: attempted=${run.stories_attempted} succeeded=${run.stories_succeeded} failed=${run.stories_failed} escalated=${run.stories_escalated}\n`);
17903
+ process.stdout.write(` Tokens: ${(run.total_input_tokens ?? 0).toLocaleString()} in / ${(run.total_output_tokens ?? 0).toLocaleString()} out $${(run.total_cost_usd ?? 0).toFixed(4)}\n`);
17904
+ process.stdout.write(` Cycles: ${run.total_review_cycles} | Dispatches: ${run.total_dispatches} | Concurrency: ${run.concurrency_setting}\n`);
17905
+ }
17906
+ }
17907
+ return 0;
17908
+ } catch (err) {
17909
+ const msg = err instanceof Error ? err.message : String(err);
17910
+ if (outputFormat === "json") process.stdout.write(formatOutput(null, "json", false, msg) + "\n");
17911
+ else process.stderr.write(`Error: ${msg}\n`);
17912
+ logger$3.error({ err }, "auto metrics failed");
17913
+ return 1;
17914
+ } finally {
17915
+ try {
17916
+ dbWrapper.close();
17917
+ } catch {}
17918
+ }
17919
+ }
16433
17920
  /**
16434
17921
  * Register the `substrate auto` command group with the CLI program.
16435
17922
  *
@@ -16451,7 +17938,7 @@ function registerAutoCommand(program, _version = "0.0.0", projectRoot = process.
16451
17938
  });
16452
17939
  process.exitCode = exitCode;
16453
17940
  });
16454
- auto.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").action(async (opts) => {
17941
+ auto.command("run").description("Run the autonomous pipeline (use --from to start from a specific phase)").option("--pack <name>", "Methodology pack name", "bmad").option("--from <phase>", "Start from this phase: analysis, planning, solutioning, implementation").option("--stop-after <phase>", "Stop pipeline after this phase completes").option("--concept <text>", "Inline concept text (required when --from analysis)").option("--concept-file <path>", "Path to a file containing the concept text").option("--stories <keys>", "Comma-separated story keys (e.g., 10-1,10-2)").option("--concurrency <n>", "Maximum parallel conflict groups", (v) => parseInt(v, 10), 3).option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--events", "Emit structured NDJSON events on stdout for programmatic consumption").option("--verbose", "Show detailed pino log output").option("--help-agent", "Print a machine-optimized prompt fragment for AI agents and exit").option("--tui", "Show TUI dashboard").option("--skip-ux", "Skip the UX design phase even if enabled in the pack manifest").action(async (opts) => {
16455
17942
  if (opts.helpAgent) {
16456
17943
  process.exitCode = await runHelpAgent();
16457
17944
  return;
@@ -16480,7 +17967,8 @@ function registerAutoCommand(program, _version = "0.0.0", projectRoot = process.
16480
17967
  projectRoot: opts.projectRoot,
16481
17968
  events: opts.events,
16482
17969
  verbose: opts.verbose,
16483
- tui: opts.tui
17970
+ tui: opts.tui,
17971
+ skipUx: opts.skipUx
16484
17972
  });
16485
17973
  process.exitCode = exitCode;
16486
17974
  });
@@ -16539,6 +18027,22 @@ function registerAutoCommand(program, _version = "0.0.0", projectRoot = process.
16539
18027
  });
16540
18028
  process.exitCode = exitCode;
16541
18029
  });
18030
+ auto.command("metrics").description("Show historical pipeline run metrics and cross-run comparison").option("--project-root <path>", "Project root directory", projectRoot).option("--output-format <format>", "Output format: human (default) or json", "human").option("--limit <n>", "Number of runs to show (default: 10)", (v) => parseInt(v, 10), 10).option("--compare <run-id-a,run-id-b>", "Compare two runs side-by-side (comma-separated IDs, e.g. abc123,def456)").option("--tag-baseline <run-id>", "Mark a run as the performance baseline").action(async (opts) => {
18031
+ const outputFormat = opts.outputFormat === "json" ? "json" : "human";
18032
+ let compareIds;
18033
+ if (opts.compare !== void 0) {
18034
+ const parts = opts.compare.split(",").map((s) => s.trim());
18035
+ if (parts.length === 2 && parts[0] && parts[1]) compareIds = [parts[0], parts[1]];
18036
+ }
18037
+ const exitCode = await runAutoMetrics({
18038
+ outputFormat,
18039
+ projectRoot: opts.projectRoot,
18040
+ limit: opts.limit,
18041
+ compare: compareIds,
18042
+ tagBaseline: opts.tagBaseline
18043
+ });
18044
+ process.exitCode = exitCode;
18045
+ });
16542
18046
  }
16543
18047
 
16544
18048
  //#endregion