@nathapp/nax 0.58.5 → 0.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +1402 -887
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -3527,6 +3527,8 @@ async function writePromptAudit(entry) {
3527
3527
  let baseDir;
3528
3528
  if (entry.auditDir) {
3529
3529
  baseDir = isAbsolute(entry.auditDir) ? entry.auditDir : join(entry.workdir, entry.auditDir);
3530
+ } else if (entry.projectDir) {
3531
+ baseDir = join(entry.projectDir, ".nax", "prompt-audit");
3530
3532
  } else {
3531
3533
  const wtMarker = `${sep}.nax-wt${sep}`;
3532
3534
  const wtIdx = entry.workdir.indexOf(wtMarker);
@@ -18267,7 +18269,16 @@ var init_schemas3 = __esm(() => {
18267
18269
  modelTier: ModelTierSchema.default("balanced"),
18268
18270
  rules: exports_external.array(exports_external.string()).default([]),
18269
18271
  timeoutMs: exports_external.number().int().positive().default(600000),
18270
- excludePatterns: exports_external.array(exports_external.string()).default([":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"])
18272
+ excludePatterns: exports_external.array(exports_external.string()).default([
18273
+ ":!test/",
18274
+ ":!tests/",
18275
+ ":!*_test.go",
18276
+ ":!*.test.ts",
18277
+ ":!*.spec.ts",
18278
+ ":!**/__tests__/",
18279
+ ":!.nax/",
18280
+ ":!.nax-pids"
18281
+ ])
18271
18282
  });
18272
18283
  ReviewDialogueConfigSchema = exports_external.object({
18273
18284
  enabled: exports_external.boolean().default(false),
@@ -18612,7 +18623,16 @@ var init_schemas3 = __esm(() => {
18612
18623
  modelTier: "balanced",
18613
18624
  rules: [],
18614
18625
  timeoutMs: 600000,
18615
- excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
18626
+ excludePatterns: [
18627
+ ":!test/",
18628
+ ":!tests/",
18629
+ ":!*_test.go",
18630
+ ":!*.test.ts",
18631
+ ":!*.spec.ts",
18632
+ ":!**/__tests__/",
18633
+ ":!.nax/",
18634
+ ":!.nax-pids"
18635
+ ]
18616
18636
  },
18617
18637
  dialogue: {
18618
18638
  enabled: false,
@@ -19186,6 +19206,7 @@ class AcpAgentAdapter {
19186
19206
  prompt: currentPrompt,
19187
19207
  sessionName,
19188
19208
  workdir: options.workdir,
19209
+ projectDir: options.projectDir,
19189
19210
  auditDir: _runAuditConfig.agent.promptAudit.dir,
19190
19211
  storyId: options.storyId,
19191
19212
  featureName: options.featureName,
@@ -19239,11 +19260,15 @@ class AcpAgentAdapter {
19239
19260
  }
19240
19261
  runState.succeeded = !timedOut && lastResponse?.stopReason === "end_turn";
19241
19262
  } finally {
19263
+ const isSessionBroken = !runState.succeeded && lastResponse?.stopReason === "error";
19242
19264
  if (runState.succeeded && !options.keepSessionOpen) {
19243
19265
  await closeAcpSession(session);
19244
19266
  if (options.featureName && options.storyId) {
19245
19267
  await clearAcpSession(options.workdir, options.featureName, options.storyId, options.sessionRole);
19246
19268
  }
19269
+ } else if (isSessionBroken) {
19270
+ getSafeLogger()?.debug("acp-adapter", "Closing broken session for retry", { sessionName });
19271
+ await closeAcpSession(session);
19247
19272
  } else if (!runState.succeeded) {
19248
19273
  getSafeLogger()?.info("acp-adapter", "Keeping session open for retry", { sessionName });
19249
19274
  } else {
@@ -19492,7 +19517,7 @@ class AcpAgentAdapter {
19492
19517
  this.markUnavailable(this.name);
19493
19518
  throw new Error("[acp-adapter] plan() returned empty spec content");
19494
19519
  }
19495
- return { specContent };
19520
+ return { specContent, costUsd: result.estimatedCost };
19496
19521
  }
19497
19522
  async decompose(options) {
19498
19523
  const model = options.modelDef?.model;
@@ -19521,6 +19546,9 @@ class AcpAgentAdapter {
19521
19546
  }
19522
19547
  return { stories };
19523
19548
  }
19549
+ clearUnavailableAgents() {
19550
+ this._unavailableAgents.clear();
19551
+ }
19524
19552
  markUnavailable(agentName) {
19525
19553
  this._unavailableAgents.add(agentName);
19526
19554
  }
@@ -20089,6 +20117,75 @@ var init_interactive = __esm(() => {
20089
20117
  init_execution();
20090
20118
  });
20091
20119
 
20120
+ // src/config/path-security.ts
20121
+ import { existsSync as existsSync2, lstatSync, realpathSync } from "fs";
20122
+ import { basename, isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
20123
+ function validateDirectory(dirPath, baseDir) {
20124
+ const resolved = resolve2(dirPath);
20125
+ if (!existsSync2(resolved)) {
20126
+ throw new Error(`Directory does not exist: ${dirPath}`);
20127
+ }
20128
+ let realPath;
20129
+ try {
20130
+ realPath = realpathSync(resolved);
20131
+ } catch (error48) {
20132
+ throw new Error(`Failed to resolve path: ${dirPath} (${error48.message})`);
20133
+ }
20134
+ try {
20135
+ const stats = lstatSync(realPath);
20136
+ if (!stats.isDirectory()) {
20137
+ throw new Error(`Not a directory: ${dirPath}`);
20138
+ }
20139
+ } catch (error48) {
20140
+ throw new Error(`Failed to stat path: ${dirPath} (${error48.message})`);
20141
+ }
20142
+ if (baseDir) {
20143
+ const resolvedBase = resolve2(baseDir);
20144
+ const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
20145
+ if (!isWithinDirectory(realPath, realBase)) {
20146
+ throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
20147
+ }
20148
+ }
20149
+ return realPath;
20150
+ }
20151
+ function isWithinDirectory(targetPath, basePath) {
20152
+ const normalizedTarget = normalize(targetPath);
20153
+ const normalizedBase = normalize(basePath);
20154
+ if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
20155
+ return false;
20156
+ }
20157
+ const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
20158
+ const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
20159
+ return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
20160
+ }
20161
+ function validateFilePath(filePath, baseDir) {
20162
+ const resolved = resolve2(filePath);
20163
+ let realPath;
20164
+ try {
20165
+ if (!existsSync2(resolved)) {
20166
+ const parent = resolve2(resolved, "..");
20167
+ if (existsSync2(parent)) {
20168
+ const realParent = realpathSync(parent);
20169
+ realPath = resolve2(realParent, basename(resolved));
20170
+ } else {
20171
+ realPath = resolved;
20172
+ }
20173
+ } else {
20174
+ realPath = realpathSync(resolved);
20175
+ }
20176
+ } catch (error48) {
20177
+ throw new Error(`Failed to resolve path: ${filePath} (${error48.message})`);
20178
+ }
20179
+ const resolvedBase = resolve2(baseDir);
20180
+ const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
20181
+ if (!isWithinDirectory(realPath, realBase)) {
20182
+ throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
20183
+ }
20184
+ return realPath;
20185
+ }
20186
+ var MAX_DIRECTORY_DEPTH = 10;
20187
+ var init_path_security = () => {};
20188
+
20092
20189
  // src/agents/shared/model-resolution.ts
20093
20190
  var exports_model_resolution = {};
20094
20191
  __export(exports_model_resolution, {
@@ -20114,7 +20211,7 @@ var init_model_resolution = __esm(() => {
20114
20211
  // src/agents/claude/plan.ts
20115
20212
  import { mkdtempSync, rmSync } from "fs";
20116
20213
  import { tmpdir } from "os";
20117
- import { join as join3 } from "path";
20214
+ import { join as join3, resolve as resolve3 } from "path";
20118
20215
  function buildPlanCommand(binary, options) {
20119
20216
  const cmd = [binary, "--permission-mode", "plan"];
20120
20217
  let modelDef = options.modelDef;
@@ -20134,17 +20231,12 @@ function buildPlanCommand(binary, options) {
20134
20231
 
20135
20232
  ${options.prompt}`;
20136
20233
  }
20137
- if (options.inputFile) {
20138
- try {
20139
- const inputContent = __require("fs").readFileSync(__require("path").resolve(options.workdir, options.inputFile), "utf-8");
20140
- fullPrompt = `${fullPrompt}
20234
+ if (options.resolvedInputContent) {
20235
+ fullPrompt = `${fullPrompt}
20141
20236
 
20142
20237
  ## Input Requirements
20143
20238
 
20144
- ${inputContent}`;
20145
- } catch (error48) {
20146
- throw new Error(`Failed to read input file ${options.inputFile}: ${error48.message}`);
20147
- }
20239
+ ${options.resolvedInputContent}`;
20148
20240
  }
20149
20241
  if (!options.interactive) {
20150
20242
  cmd.push("-p", fullPrompt);
@@ -20155,7 +20247,13 @@ ${inputContent}`;
20155
20247
  }
20156
20248
  async function runPlan(binary, options, pidRegistry) {
20157
20249
  const { resolveBalancedModelDef: resolveBalancedModelDef2 } = await Promise.resolve().then(() => (init_model_resolution(), exports_model_resolution));
20158
- const cmd = buildPlanCommand(binary, options);
20250
+ let resolvedOptions = options;
20251
+ if (options.inputFile) {
20252
+ const inputPath = validateFilePath(resolve3(options.workdir, options.inputFile), options.workdir);
20253
+ const resolvedInputContent = await Bun.file(inputPath).text();
20254
+ resolvedOptions = { ...options, resolvedInputContent };
20255
+ }
20256
+ const cmd = buildPlanCommand(binary, resolvedOptions);
20159
20257
  let modelDef = options.modelDef;
20160
20258
  if (!modelDef) {
20161
20259
  if (!options.config) {
@@ -20224,6 +20322,7 @@ async function runPlan(binary, options, pidRegistry) {
20224
20322
  }
20225
20323
  }
20226
20324
  var init_plan = __esm(() => {
20325
+ init_path_security();
20227
20326
  init_timeout_handler();
20228
20327
  init_logger2();
20229
20328
  init_env();
@@ -20376,8 +20475,8 @@ class ClaudeCodeAdapter {
20376
20475
  let stdoutTimeoutId;
20377
20476
  const stdout = await Promise.race([
20378
20477
  new Response(proc.stdout).text(),
20379
- new Promise((resolve2) => {
20380
- stdoutTimeoutId = setTimeout(() => resolve2(""), 5000);
20478
+ new Promise((resolve4) => {
20479
+ stdoutTimeoutId = setTimeout(() => resolve4(""), 5000);
20381
20480
  })
20382
20481
  ]);
20383
20482
  clearTimeout(stdoutTimeoutId);
@@ -20725,7 +20824,12 @@ function createAgentRegistry(config2) {
20725
20824
  installed: await agent.isInstalled()
20726
20825
  })));
20727
20826
  }
20728
- return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol };
20827
+ function resetStoryState() {
20828
+ for (const adapter of acpCache.values()) {
20829
+ adapter.clearUnavailableAgents();
20830
+ }
20831
+ }
20832
+ return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol, resetStoryState };
20729
20833
  }
20730
20834
  var ALL_AGENTS;
20731
20835
  var init_registry = __esm(() => {
@@ -20745,75 +20849,6 @@ var init_registry = __esm(() => {
20745
20849
  ];
20746
20850
  });
20747
20851
 
20748
- // src/config/path-security.ts
20749
- import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
20750
- import { basename, isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
20751
- function validateDirectory(dirPath, baseDir) {
20752
- const resolved = resolve2(dirPath);
20753
- if (!existsSync3(resolved)) {
20754
- throw new Error(`Directory does not exist: ${dirPath}`);
20755
- }
20756
- let realPath;
20757
- try {
20758
- realPath = realpathSync(resolved);
20759
- } catch (error48) {
20760
- throw new Error(`Failed to resolve path: ${dirPath} (${error48.message})`);
20761
- }
20762
- try {
20763
- const stats = lstatSync(realPath);
20764
- if (!stats.isDirectory()) {
20765
- throw new Error(`Not a directory: ${dirPath}`);
20766
- }
20767
- } catch (error48) {
20768
- throw new Error(`Failed to stat path: ${dirPath} (${error48.message})`);
20769
- }
20770
- if (baseDir) {
20771
- const resolvedBase = resolve2(baseDir);
20772
- const realBase = existsSync3(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
20773
- if (!isWithinDirectory(realPath, realBase)) {
20774
- throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
20775
- }
20776
- }
20777
- return realPath;
20778
- }
20779
- function isWithinDirectory(targetPath, basePath) {
20780
- const normalizedTarget = normalize(targetPath);
20781
- const normalizedBase = normalize(basePath);
20782
- if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
20783
- return false;
20784
- }
20785
- const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
20786
- const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
20787
- return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
20788
- }
20789
- function validateFilePath(filePath, baseDir) {
20790
- const resolved = resolve2(filePath);
20791
- let realPath;
20792
- try {
20793
- if (!existsSync3(resolved)) {
20794
- const parent = resolve2(resolved, "..");
20795
- if (existsSync3(parent)) {
20796
- const realParent = realpathSync(parent);
20797
- realPath = resolve2(realParent, basename(resolved));
20798
- } else {
20799
- realPath = resolved;
20800
- }
20801
- } else {
20802
- realPath = realpathSync(resolved);
20803
- }
20804
- } catch (error48) {
20805
- throw new Error(`Failed to resolve path: ${filePath} (${error48.message})`);
20806
- }
20807
- const resolvedBase = resolve2(baseDir);
20808
- const realBase = existsSync3(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
20809
- if (!isWithinDirectory(realPath, realBase)) {
20810
- throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
20811
- }
20812
- return realPath;
20813
- }
20814
- var MAX_DIRECTORY_DEPTH = 10;
20815
- var init_path_security = () => {};
20816
-
20817
20852
  // src/utils/json-file.ts
20818
20853
  import { existsSync as existsSync6 } from "fs";
20819
20854
  async function loadJsonFile(path, context = "json-file") {
@@ -21008,7 +21043,7 @@ function isPlainObject2(value) {
21008
21043
 
21009
21044
  // src/config/paths.ts
21010
21045
  import { homedir as homedir2 } from "os";
21011
- import { join as join7, resolve as resolve3 } from "path";
21046
+ import { join as join7, resolve as resolve4 } from "path";
21012
21047
  function globalConfigDir() {
21013
21048
  const override = process.env[GLOBAL_CONFIG_DIR_ENV];
21014
21049
  if (override)
@@ -21016,7 +21051,7 @@ function globalConfigDir() {
21016
21051
  return join7(homedir2(), ".nax");
21017
21052
  }
21018
21053
  function projectConfigDir(projectRoot) {
21019
- return join7(resolve3(projectRoot), PROJECT_NAX_DIR);
21054
+ return join7(resolve4(projectRoot), PROJECT_NAX_DIR);
21020
21055
  }
21021
21056
  var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", PROJECT_NAX_DIR = ".nax";
21022
21057
  var init_paths = () => {};
@@ -21170,12 +21205,12 @@ var init_profile = __esm(() => {
21170
21205
 
21171
21206
  // src/config/loader.ts
21172
21207
  import { existsSync as existsSync7 } from "fs";
21173
- import { basename as basename2, dirname as dirname3, join as join9, resolve as resolve4 } from "path";
21208
+ import { basename as basename2, dirname as dirname2, join as join9, resolve as resolve5 } from "path";
21174
21209
  function globalConfigPath() {
21175
21210
  return join9(globalConfigDir(), "config.json");
21176
21211
  }
21177
21212
  function findProjectDir(startDir = process.cwd()) {
21178
- let dir = resolve4(startDir);
21213
+ let dir = resolve5(startDir);
21179
21214
  let depth = 0;
21180
21215
  while (depth < MAX_DIRECTORY_DEPTH) {
21181
21216
  const candidate = join9(dir, PROJECT_NAX_DIR);
@@ -21226,7 +21261,7 @@ function applyBatchModeCompat(conf) {
21226
21261
  async function loadConfig(startDir, cliOverrides) {
21227
21262
  let rawConfig = structuredClone(DEFAULT_CONFIG);
21228
21263
  const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
21229
- const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname3(startDir) : startDir : process.cwd();
21264
+ const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname2(startDir) : startDir : process.cwd();
21230
21265
  const profileName = await resolveProfileName(cliOverrides ?? {}, process.env, projectRoot);
21231
21266
  const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
21232
21267
  if (globalConfRaw) {
@@ -21269,8 +21304,8 @@ ${errors3.join(`
21269
21304
  }
21270
21305
  async function loadConfigForWorkdir(rootConfigPath, packageDir) {
21271
21306
  const logger = getLogger();
21272
- const resolvedRootConfigPath = resolve4(rootConfigPath);
21273
- const rootNaxDir = dirname3(resolvedRootConfigPath);
21307
+ const resolvedRootConfigPath = resolve5(rootConfigPath);
21308
+ const rootNaxDir = dirname2(resolvedRootConfigPath);
21274
21309
  let rootConfigPromise = _rootConfigCache.get(resolvedRootConfigPath);
21275
21310
  if (!rootConfigPromise) {
21276
21311
  rootConfigPromise = loadConfig(rootNaxDir);
@@ -21281,7 +21316,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
21281
21316
  logger.debug("config", "No packageDir \u2014 using root config");
21282
21317
  return rootConfig;
21283
21318
  }
21284
- const repoRoot = dirname3(rootNaxDir);
21319
+ const repoRoot = dirname2(rootNaxDir);
21285
21320
  const packageConfigPath = join9(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
21286
21321
  const packageOverride = await loadJsonFile(packageConfigPath, "config");
21287
21322
  if (!packageOverride) {
@@ -21508,25 +21543,89 @@ function pipelineStageForDebate(stage) {
21508
21543
  }
21509
21544
  }
21510
21545
  function resolveModelDefForDebater(debater, tier, config2) {
21511
- if (debater.model && !isTierLabel(debater.model)) {
21512
- return resolveModel(debater.model);
21546
+ const modelOverride = debater.model;
21547
+ let effectiveTier = tier;
21548
+ if (modelOverride) {
21549
+ const aliasedTier = MODEL_SHORTHAND_TIERS[modelOverride.toLowerCase()];
21550
+ if (aliasedTier) {
21551
+ effectiveTier = aliasedTier;
21552
+ } else if (!isTierLabel(modelOverride)) {
21553
+ return resolveModel(modelOverride);
21554
+ }
21513
21555
  }
21514
21556
  const configModels = config2?.models ?? DEFAULT_CONFIG.models;
21515
21557
  const configDefaultAgent = config2?.autoMode?.defaultAgent ?? DEFAULT_CONFIG.autoMode.defaultAgent;
21516
21558
  try {
21517
- return resolveModelForAgent(configModels, debater.agent, tier, configDefaultAgent);
21559
+ return resolveModelForAgent(configModels, debater.agent, effectiveTier, configDefaultAgent);
21518
21560
  } catch {}
21519
21561
  try {
21520
- return resolveModelForAgent(DEFAULT_CONFIG.models, DEFAULT_CONFIG.autoMode.defaultAgent, tier, DEFAULT_CONFIG.autoMode.defaultAgent);
21562
+ return resolveModelForAgent(DEFAULT_CONFIG.models, DEFAULT_CONFIG.autoMode.defaultAgent, effectiveTier, DEFAULT_CONFIG.autoMode.defaultAgent);
21521
21563
  } catch {
21522
21564
  return resolveModelForAgent(configModels, debater.agent, "fast", configDefaultAgent);
21523
21565
  }
21524
21566
  }
21525
- async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName) {
21567
+ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, config2, storyId, timeoutMs, workdir, featureName, reviewerSession, resolverContext) {
21526
21568
  const resolverConfig = stageConfig.resolver;
21527
21569
  const logger = _debateSessionDeps.getSafeLogger();
21570
+ if (reviewerSession && resolverContext) {
21571
+ try {
21572
+ const debateCtx = {
21573
+ resolverType: resolverConfig.type
21574
+ };
21575
+ if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
21576
+ const failOpen = resolverConfig.type === "majority-fail-open";
21577
+ const rawOutcome = majorityResolver(proposalOutputs, failOpen);
21578
+ let passCount = 0;
21579
+ let failCount = 0;
21580
+ for (const proposal of proposalOutputs) {
21581
+ try {
21582
+ const stripped = proposal.trim().replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
21583
+ const parsed = JSON.parse(stripped);
21584
+ if (typeof parsed.passed === "boolean" && parsed.passed)
21585
+ passCount++;
21586
+ else if (failOpen)
21587
+ passCount++;
21588
+ else
21589
+ failCount++;
21590
+ } catch {
21591
+ if (failOpen)
21592
+ passCount++;
21593
+ else
21594
+ failCount++;
21595
+ }
21596
+ }
21597
+ debateCtx.majorityVote = { passed: rawOutcome === "passed", passCount, failCount };
21598
+ }
21599
+ const story = {
21600
+ id: resolverContext.story.id,
21601
+ title: resolverContext.story.title,
21602
+ description: "",
21603
+ acceptanceCriteria: resolverContext.story.acceptanceCriteria
21604
+ };
21605
+ let dialogueResult;
21606
+ if (resolverContext.isReReview) {
21607
+ dialogueResult = await reviewerSession.reReviewDebate(resolverContext.labeledProposals, critiqueOutputs, resolverContext.diff, debateCtx);
21608
+ } else {
21609
+ dialogueResult = await reviewerSession.resolveDebate(resolverContext.labeledProposals, critiqueOutputs, resolverContext.diff, story, resolverContext.semanticConfig, debateCtx);
21610
+ }
21611
+ const outcome = dialogueResult.checkResult.success ? "passed" : "failed";
21612
+ return {
21613
+ outcome,
21614
+ resolverCostUsd: dialogueResult.cost ?? 0,
21615
+ dialogueResult
21616
+ };
21617
+ } catch (err) {
21618
+ logger?.warn("debate", "ReviewerSession.resolveDebate() failed \u2014 falling back to stateless resolver", {
21619
+ storyId,
21620
+ error: err instanceof Error ? err.message : String(err)
21621
+ });
21622
+ }
21623
+ }
21624
+ if (reviewerSession && !resolverContext) {
21625
+ logger?.warn("debate", "ReviewerSession provided but resolverContext is undefined \u2014 falling back to stateless resolver", { storyId });
21626
+ }
21528
21627
  if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
21529
- if (workdir !== undefined) {
21628
+ if (workdir !== undefined && !reviewerSession) {
21530
21629
  logger?.warn("debate", "majority resolver does not support implementer session resumption \u2014 switch to synthesis or custom resolver for context-aware semantic review");
21531
21630
  }
21532
21631
  return {
@@ -21552,7 +21651,8 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
21552
21651
  });
21553
21652
  return {
21554
21653
  outcome: "passed",
21555
- resolverCostUsd: resolverResult.costUsd
21654
+ resolverCostUsd: resolverResult.costUsd,
21655
+ output: resolverResult.output
21556
21656
  };
21557
21657
  }
21558
21658
  return { outcome: "passed", resolverCostUsd: 0 };
@@ -21573,12 +21673,13 @@ async function resolveOutcome(proposalOutputs, critiqueOutputs, stageConfig, con
21573
21673
  });
21574
21674
  return {
21575
21675
  outcome: "passed",
21576
- resolverCostUsd: resolverResult.costUsd
21676
+ resolverCostUsd: resolverResult.costUsd,
21677
+ output: resolverResult.output
21577
21678
  };
21578
21679
  }
21579
21680
  return { outcome: "passed", resolverCostUsd: 0 };
21580
21681
  }
21581
- var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
21682
+ var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps, MODEL_SHORTHAND_TIERS;
21582
21683
  var init_session_helpers = __esm(() => {
21583
21684
  init_adapter();
21584
21685
  init_registry();
@@ -21590,6 +21691,11 @@ var init_session_helpers = __esm(() => {
21590
21691
  getSafeLogger,
21591
21692
  readFile: (path) => Bun.file(path).text()
21592
21693
  };
21694
+ MODEL_SHORTHAND_TIERS = {
21695
+ haiku: "fast",
21696
+ sonnet: "balanced",
21697
+ opus: "powerful"
21698
+ };
21593
21699
  });
21594
21700
 
21595
21701
  // src/debate/concurrency.ts
@@ -21775,7 +21881,11 @@ async function runStateful(ctx, prompt) {
21775
21881
  critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value.output);
21776
21882
  }
21777
21883
  const proposalOutputs = successfulProposals.map((s) => s.output);
21778
- const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName);
21884
+ const fullResolverContext = ctx.resolverContextInput ? {
21885
+ ...ctx.resolverContextInput,
21886
+ labeledProposals: successfulProposals.map((s) => ({ debater: s.debater.agent, output: s.output }))
21887
+ } : undefined;
21888
+ const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
21779
21889
  totalCostUsd += outcome.resolverCostUsd;
21780
21890
  const proposals = successfulProposals.map((s) => ({
21781
21891
  debater: s.debater,
@@ -21802,6 +21912,50 @@ var init_session_stateful = __esm(() => {
21802
21912
  });
21803
21913
 
21804
21914
  // src/debate/session-hybrid.ts
21915
+ async function runRebuttalLoop(ctx, proposals, originalPrompt, sessionRolePrefix) {
21916
+ const logger = _debateSessionDeps.getSafeLogger();
21917
+ const config2 = ctx.stageConfig;
21918
+ const rebuttals = [];
21919
+ let costUsd = 0;
21920
+ const proposalList = proposals.map((s) => ({ debater: s.debater, output: s.output }));
21921
+ try {
21922
+ for (let round = 1;round <= config2.rounds; round++) {
21923
+ const priorRebuttals = rebuttals.filter((r) => r.round < round).map((r) => r.output);
21924
+ for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
21925
+ const proposal = proposals[debaterIdx];
21926
+ const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
21927
+ logger?.info("debate:rebuttal-start", "debate:rebuttal-start", {
21928
+ storyId: ctx.storyId,
21929
+ round,
21930
+ debaterIndex: debaterIdx
21931
+ });
21932
+ const rebuttalPrompt = buildRebuttalContext(originalPrompt, proposalList, priorRebuttals, debaterIdx);
21933
+ try {
21934
+ const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
21935
+ costUsd += turnResult.cost;
21936
+ rebuttals.push({ debater: proposal.debater, round, output: turnResult.output });
21937
+ } catch (err) {
21938
+ logger?.warn("debate", "debate:rebuttal-failed", {
21939
+ storyId: ctx.storyId,
21940
+ round,
21941
+ debaterIndex: debaterIdx,
21942
+ error: err instanceof Error ? err.message : String(err)
21943
+ });
21944
+ }
21945
+ }
21946
+ }
21947
+ } finally {
21948
+ for (let debaterIdx = 0;debaterIdx < proposals.length; debaterIdx++) {
21949
+ const proposal = proposals[debaterIdx];
21950
+ const sessionRole = `${sessionRolePrefix}-${debaterIdx}`;
21951
+ try {
21952
+ const closeCost = await closeStatefulSession(ctx, proposal.adapter, proposal.debater, sessionRole);
21953
+ costUsd += closeCost;
21954
+ } catch {}
21955
+ }
21956
+ }
21957
+ return { rebuttals, costUsd };
21958
+ }
21805
21959
  async function runHybrid(ctx, prompt) {
21806
21960
  const logger = _debateSessionDeps.getSafeLogger();
21807
21961
  const config2 = ctx.stageConfig;
@@ -21870,45 +22024,14 @@ async function runHybrid(ctx, prompt) {
21870
22024
  }
21871
22025
  const proposalOutputs = successfulProposals.map((s) => s.output);
21872
22026
  const proposalList = successfulProposals.map((s) => ({ debater: s.debater, output: s.output }));
21873
- const rebuttals = [];
21874
- try {
21875
- for (let round = 1;round <= config2.rounds; round++) {
21876
- const priorRebuttals = rebuttals.filter((r) => r.round < round).map((r) => r.output);
21877
- for (let debaterIdx = 0;debaterIdx < successfulProposals.length; debaterIdx++) {
21878
- const proposal = successfulProposals[debaterIdx];
21879
- const sessionRole = `debate-hybrid-${debaterIdx}`;
21880
- logger?.info("debate:rebuttal-start", "debate:rebuttal-start", {
21881
- storyId: ctx.storyId,
21882
- round,
21883
- debaterIndex: debaterIdx
21884
- });
21885
- const rebuttalPrompt = buildRebuttalContext(prompt, proposalList, priorRebuttals, debaterIdx);
21886
- try {
21887
- const turnResult = await runStatefulTurn(ctx, proposal.adapter, proposal.debater, rebuttalPrompt, sessionRole, true);
21888
- totalCostUsd += turnResult.cost;
21889
- rebuttals.push({ debater: proposal.debater, round, output: turnResult.output });
21890
- } catch (err) {
21891
- logger?.warn("debate", "debate:rebuttal-failed", {
21892
- storyId: ctx.storyId,
21893
- round,
21894
- debaterIndex: debaterIdx,
21895
- error: err instanceof Error ? err.message : String(err)
21896
- });
21897
- }
21898
- }
21899
- }
21900
- } finally {
21901
- for (let debaterIdx = 0;debaterIdx < successfulProposals.length; debaterIdx++) {
21902
- const proposal = successfulProposals[debaterIdx];
21903
- const sessionRole = `debate-hybrid-${debaterIdx}`;
21904
- try {
21905
- const closeCost = await closeStatefulSession(ctx, proposal.adapter, proposal.debater, sessionRole);
21906
- totalCostUsd += closeCost;
21907
- } catch {}
21908
- }
21909
- }
22027
+ const { rebuttals, costUsd: rebuttalCost } = await runRebuttalLoop(ctx, successfulProposals, prompt, "debate-hybrid");
22028
+ totalCostUsd += rebuttalCost;
21910
22029
  const critiqueOutputs = rebuttals.map((r) => r.output);
21911
- const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000);
22030
+ const fullResolverContext = ctx.resolverContextInput ? {
22031
+ ...ctx.resolverContextInput,
22032
+ labeledProposals: successfulProposals.map((s) => ({ debater: s.debater.agent, output: s.output }))
22033
+ } : undefined;
22034
+ const resolveResult = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutSeconds * 1000, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
21912
22035
  totalCostUsd += resolveResult.resolverCostUsd;
21913
22036
  return {
21914
22037
  storyId: ctx.storyId,
@@ -22051,7 +22174,11 @@ async function runOneShot(ctx, prompt) {
22051
22174
  critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value.output);
22052
22175
  }
22053
22176
  const proposalOutputs = successful.map((p) => p.output);
22054
- const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutMs);
22177
+ const fullResolverContext = ctx.resolverContextInput ? {
22178
+ ...ctx.resolverContextInput,
22179
+ labeledProposals: successful.map((p) => ({ debater: p.debater.agent, output: p.output }))
22180
+ } : undefined;
22181
+ const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, ctx.timeoutMs, ctx.workdir, ctx.featureName, ctx.reviewerSession, fullResolverContext);
22055
22182
  totalCostUsd += outcome.resolverCostUsd;
22056
22183
  const proposals = successful.map((p) => ({
22057
22184
  debater: p.debater,
@@ -22083,7 +22210,7 @@ async function runPlan2(ctx, basePrompt, opts) {
22083
22210
  const logger = _debateSessionDeps.getSafeLogger();
22084
22211
  const config2 = ctx.stageConfig;
22085
22212
  const debaters = config2.debaters ?? [];
22086
- const totalCostUsd = 0;
22213
+ let totalCostUsd = 0;
22087
22214
  const resolved = [];
22088
22215
  for (const debater of debaters) {
22089
22216
  const adapter = _debateSessionDeps.getAgent(debater.agent, ctx.config);
@@ -22106,13 +22233,16 @@ async function runPlan2(ctx, basePrompt, opts) {
22106
22233
 
22107
22234
  Write the PRD JSON directly to this file path: ${tempOutputPath}
22108
22235
  Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.`;
22109
- await adapter.plan({
22236
+ const modelTier = modelTierFromDebater(debater);
22237
+ const modelDef = resolveModelDefForDebater(debater, modelTier, ctx.config);
22238
+ const planResult = await adapter.plan({
22110
22239
  prompt: debaterPrompt,
22111
22240
  workdir: opts.workdir,
22112
22241
  interactive: false,
22113
22242
  timeoutSeconds: opts.timeoutSeconds,
22114
22243
  config: ctx.config,
22115
- modelTier: debater.model ?? "balanced",
22244
+ modelTier,
22245
+ modelDef,
22116
22246
  dangerouslySkipPermissions: opts.dangerouslySkipPermissions,
22117
22247
  maxInteractionTurns: opts.maxInteractionTurns,
22118
22248
  featureName: opts.feature,
@@ -22120,13 +22250,14 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
22120
22250
  sessionRole: `plan-${i}`
22121
22251
  });
22122
22252
  const output = await _debateSessionDeps.readFile(tempOutputPath);
22123
- return { debater, adapter, output, cost: 0 };
22253
+ return { debater, adapter, output, cost: planResult.costUsd ?? 0 };
22124
22254
  }), concurrencyLimit);
22125
22255
  const successful = [];
22126
22256
  for (let i = 0;i < settled.length; i++) {
22127
22257
  const res = settled[i];
22128
22258
  if (res.status === "fulfilled") {
22129
22259
  successful.push(res.value);
22260
+ totalCostUsd += res.value.cost;
22130
22261
  } else {
22131
22262
  const { debater } = resolved[i];
22132
22263
  logger?.warn("debate", "debate:debater-failed", {
@@ -22178,9 +22309,30 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
22178
22309
  };
22179
22310
  }
22180
22311
  const proposalOutputs = successful.map((p) => p.output);
22312
+ const mode = ctx.stageConfig.mode ?? "panel";
22313
+ const sessionMode = ctx.stageConfig.sessionMode ?? "one-shot";
22314
+ let critiqueOutputs = [];
22315
+ let rebuttalList;
22316
+ if (mode === "hybrid" && sessionMode === "stateful") {
22317
+ const hybridCtx = {
22318
+ storyId: ctx.storyId,
22319
+ stage: ctx.stage,
22320
+ stageConfig: ctx.stageConfig,
22321
+ config: ctx.config,
22322
+ workdir: opts.workdir,
22323
+ featureName: opts.feature,
22324
+ timeoutSeconds: opts.timeoutSeconds ?? 600
22325
+ };
22326
+ const { rebuttals, costUsd } = await runRebuttalLoop(hybridCtx, successful, basePrompt, "plan-hybrid");
22327
+ critiqueOutputs = rebuttals.map((r) => r.output);
22328
+ rebuttalList = rebuttals;
22329
+ totalCostUsd += costUsd;
22330
+ } else if (mode === "hybrid") {
22331
+ logger?.warn("debate", "hybrid mode requires sessionMode: stateful for plan \u2014 running as panel");
22332
+ }
22181
22333
  const resolverTimeoutMs = (ctx.stageConfig.timeoutSeconds ?? 600) * 1000;
22182
- const outcome = await resolveOutcome(proposalOutputs, [], ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs);
22183
- const winningOutput = successful[0].output;
22334
+ const outcome = await resolveOutcome(proposalOutputs, critiqueOutputs, ctx.stageConfig, ctx.config, ctx.storyId, resolverTimeoutMs, opts.workdir, opts.feature);
22335
+ const winningOutput = outcome.output ?? successful[0].output;
22184
22336
  const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
22185
22337
  logger?.info("debate", "debate:result", {
22186
22338
  storyId: ctx.storyId,
@@ -22191,16 +22343,18 @@ Do NOT output the JSON to the conversation. Write the file, then reply with a br
22191
22343
  storyId: ctx.storyId,
22192
22344
  stage: ctx.stage,
22193
22345
  outcome: outcome.outcome,
22194
- rounds: 1,
22346
+ rounds: rebuttalList ? config2.rounds : 1,
22195
22347
  debaters: successful.map((p) => p.debater.agent),
22196
22348
  resolverType: config2.resolver.type,
22197
22349
  proposals,
22350
+ rebuttals: rebuttalList,
22198
22351
  output: winningOutput,
22199
22352
  totalCostUsd
22200
22353
  };
22201
22354
  }
22202
22355
  var init_session_plan = __esm(() => {
22203
22356
  init_session_helpers();
22357
+ init_session_hybrid();
22204
22358
  });
22205
22359
 
22206
22360
  // src/debate/session.ts
@@ -22212,6 +22366,8 @@ class DebateSession {
22212
22366
  workdir;
22213
22367
  featureName;
22214
22368
  timeoutSeconds;
22369
+ reviewerSession;
22370
+ resolverContextInput;
22215
22371
  get timeoutMs() {
22216
22372
  return this.timeoutSeconds * 1000;
22217
22373
  }
@@ -22223,6 +22379,8 @@ class DebateSession {
22223
22379
  this.workdir = opts.workdir ?? process.cwd();
22224
22380
  this.featureName = opts.featureName ?? opts.stage;
22225
22381
  this.timeoutSeconds = opts.timeoutSeconds ?? opts.stageConfig.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
22382
+ this.reviewerSession = opts.reviewerSession;
22383
+ this.resolverContextInput = opts.resolverContextInput;
22226
22384
  }
22227
22385
  async run(prompt) {
22228
22386
  const sessionMode = this.stageConfig.sessionMode ?? "one-shot";
@@ -22236,7 +22394,9 @@ class DebateSession {
22236
22394
  config: this.config,
22237
22395
  workdir: this.workdir,
22238
22396
  featureName: this.featureName,
22239
- timeoutSeconds: this.timeoutSeconds
22397
+ timeoutSeconds: this.timeoutSeconds,
22398
+ reviewerSession: this.reviewerSession,
22399
+ resolverContextInput: this.resolverContextInput
22240
22400
  }, prompt);
22241
22401
  }
22242
22402
  const logger = _debateSessionDeps.getSafeLogger();
@@ -22246,7 +22406,11 @@ class DebateSession {
22246
22406
  stage: this.stage,
22247
22407
  stageConfig: this.stageConfig,
22248
22408
  config: this.config,
22249
- timeoutMs: this.timeoutMs
22409
+ timeoutMs: this.timeoutMs,
22410
+ workdir: this.workdir,
22411
+ featureName: this.featureName,
22412
+ reviewerSession: this.reviewerSession,
22413
+ resolverContextInput: this.resolverContextInput
22250
22414
  }, prompt);
22251
22415
  }
22252
22416
  if (sessionMode === "stateful") {
@@ -22257,7 +22421,9 @@ class DebateSession {
22257
22421
  config: this.config,
22258
22422
  workdir: this.workdir,
22259
22423
  featureName: this.featureName,
22260
- timeoutSeconds: this.timeoutSeconds
22424
+ timeoutSeconds: this.timeoutSeconds,
22425
+ reviewerSession: this.reviewerSession,
22426
+ resolverContextInput: this.resolverContextInput
22261
22427
  }, prompt);
22262
22428
  }
22263
22429
  return runOneShot({
@@ -22265,7 +22431,11 @@ class DebateSession {
22265
22431
  stage: this.stage,
22266
22432
  stageConfig: this.stageConfig,
22267
22433
  config: this.config,
22268
- timeoutMs: this.timeoutMs
22434
+ timeoutMs: this.timeoutMs,
22435
+ workdir: this.workdir,
22436
+ featureName: this.featureName,
22437
+ reviewerSession: this.reviewerSession,
22438
+ resolverContextInput: this.resolverContextInput
22269
22439
  }, prompt);
22270
22440
  }
22271
22441
  async runPlan(basePrompt, opts) {
@@ -22600,30 +22770,41 @@ class CLIInteractionPlugin {
22600
22770
  }
22601
22771
  async send(request) {
22602
22772
  this.pendingRequests.set(request.id, request);
22603
- console.log(`
22604
- ${"=".repeat(80)}`);
22605
- console.log(`[INTERACTION] ${request.stage.toUpperCase()} \u2014 ${request.type.toUpperCase()}`);
22606
- console.log("=".repeat(80));
22607
- console.log(`
22773
+ process.stdout.write(`
22774
+ ${"=".repeat(80)}
22775
+ `);
22776
+ process.stdout.write(`[INTERACTION] ${request.stage.toUpperCase()} \u2014 ${request.type.toUpperCase()}
22777
+ `);
22778
+ process.stdout.write(`${"=".repeat(80)}
22779
+ `);
22780
+ process.stdout.write(`
22608
22781
  ${request.summary}
22782
+
22609
22783
  `);
22610
22784
  if (request.detail) {
22611
- console.log(request.detail);
22612
- console.log("");
22785
+ process.stdout.write(`${request.detail}
22786
+ `);
22787
+ process.stdout.write(`
22788
+ `);
22613
22789
  }
22614
22790
  if (request.options && request.options.length > 0) {
22615
- console.log("Options:");
22791
+ process.stdout.write(`Options:
22792
+ `);
22616
22793
  for (const opt of request.options) {
22617
22794
  const desc = opt.description ? ` \u2014 ${opt.description}` : "";
22618
- console.log(` [${opt.key}] ${opt.label}${desc}`);
22795
+ process.stdout.write(` [${opt.key}] ${opt.label}${desc}
22796
+ `);
22619
22797
  }
22620
- console.log("");
22798
+ process.stdout.write(`
22799
+ `);
22621
22800
  }
22622
22801
  if (request.timeout) {
22623
22802
  const timeoutSec = Math.floor(request.timeout / 1000);
22624
- console.log(`[Timeout: ${timeoutSec}s | Fallback: ${request.fallback}]`);
22803
+ process.stdout.write(`[Timeout: ${timeoutSec}s | Fallback: ${request.fallback}]
22804
+ `);
22625
22805
  }
22626
- console.log(`${"=".repeat(80)}
22806
+ process.stdout.write(`${"=".repeat(80)}
22807
+
22627
22808
  `);
22628
22809
  }
22629
22810
  async receive(requestId, timeout = 60000) {
@@ -22645,9 +22826,9 @@ ${request.summary}
22645
22826
  if (!this.rl) {
22646
22827
  throw new Error("CLI plugin not initialized");
22647
22828
  }
22648
- const timeoutPromise = new Promise((resolve5) => {
22829
+ const timeoutPromise = new Promise((resolve6) => {
22649
22830
  setTimeout(() => {
22650
- resolve5({
22831
+ resolve6({
22651
22832
  requestId: request.id,
22652
22833
  action: "skip",
22653
22834
  respondedBy: "timeout",
@@ -22799,9 +22980,9 @@ ${request.summary}
22799
22980
  if (!this.rl) {
22800
22981
  throw new Error("CLI plugin not initialized");
22801
22982
  }
22802
- return new Promise((resolve5) => {
22983
+ return new Promise((resolve6) => {
22803
22984
  this.rl?.question(prompt, (answer) => {
22804
- resolve5(answer);
22985
+ resolve6(answer);
22805
22986
  });
22806
22987
  });
22807
22988
  }
@@ -23233,7 +23414,7 @@ class WebhookInteractionPlugin {
23233
23414
  this.pendingResponses.delete(requestId);
23234
23415
  return early;
23235
23416
  }
23236
- return new Promise((resolve5) => {
23417
+ return new Promise((resolve6) => {
23237
23418
  const existingCallback = this.receiveCallbacks.get(requestId);
23238
23419
  if (existingCallback) {
23239
23420
  this.clearReceiveTimer(requestId);
@@ -23247,7 +23428,7 @@ class WebhookInteractionPlugin {
23247
23428
  const timer = setTimeout(() => {
23248
23429
  this.clearReceiveTimer(requestId);
23249
23430
  this.receiveCallbacks.delete(requestId);
23250
- resolve5({
23431
+ resolve6({
23251
23432
  requestId,
23252
23433
  action: "skip",
23253
23434
  respondedBy: "timeout",
@@ -23258,7 +23439,7 @@ class WebhookInteractionPlugin {
23258
23439
  this.receiveCallbacks.set(requestId, (response) => {
23259
23440
  this.clearReceiveTimer(requestId);
23260
23441
  this.receiveCallbacks.delete(requestId);
23261
- resolve5(response);
23442
+ resolve6(response);
23262
23443
  });
23263
23444
  });
23264
23445
  }
@@ -25203,6 +25384,7 @@ async function runPipeline(stages, context, eventEmitter) {
25203
25384
  const logger = getLogger();
25204
25385
  const retryCountMap = new Map;
25205
25386
  let i = 0;
25387
+ let stageCostAccum = 0;
25206
25388
  while (i < stages.length) {
25207
25389
  const stage = stages[i];
25208
25390
  if (!stage.enabled(context)) {
@@ -25221,27 +25403,58 @@ async function runPipeline(stages, context, eventEmitter) {
25221
25403
  reason: `Stage "${stage.name}" threw error: ${errorMessage(error48)}`
25222
25404
  };
25223
25405
  eventEmitter?.emit("stage:exit", stage.name, failResult);
25224
- return { success: false, finalAction: "fail", reason: failResult.reason, stoppedAtStage: stage.name, context };
25406
+ return {
25407
+ success: false,
25408
+ finalAction: "fail",
25409
+ reason: failResult.reason,
25410
+ stoppedAtStage: stage.name,
25411
+ context,
25412
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25413
+ };
25225
25414
  }
25415
+ if (result.cost)
25416
+ stageCostAccum += result.cost;
25226
25417
  eventEmitter?.emit("stage:exit", stage.name, result);
25227
25418
  switch (result.action) {
25228
25419
  case "continue":
25229
25420
  i++;
25230
25421
  continue;
25231
25422
  case "skip":
25232
- return { success: false, finalAction: "skip", reason: result.reason, stoppedAtStage: stage.name, context };
25423
+ return {
25424
+ success: false,
25425
+ finalAction: "skip",
25426
+ reason: result.reason,
25427
+ stoppedAtStage: stage.name,
25428
+ context,
25429
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25430
+ };
25233
25431
  case "fail":
25234
- return { success: false, finalAction: "fail", reason: result.reason, stoppedAtStage: stage.name, context };
25432
+ return {
25433
+ success: false,
25434
+ finalAction: "fail",
25435
+ reason: result.reason,
25436
+ stoppedAtStage: stage.name,
25437
+ context,
25438
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25439
+ };
25235
25440
  case "escalate":
25236
25441
  return {
25237
25442
  success: false,
25238
25443
  finalAction: "escalate",
25239
25444
  reason: result.reason ?? "Stage requested escalation to higher tier",
25240
25445
  stoppedAtStage: stage.name,
25241
- context
25446
+ context,
25447
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25242
25448
  };
25243
25449
  case "pause":
25244
- return { success: false, finalAction: "pause", reason: result.reason, stoppedAtStage: stage.name, context };
25450
+ return {
25451
+ success: false,
25452
+ finalAction: "pause",
25453
+ reason: result.reason,
25454
+ stoppedAtStage: stage.name,
25455
+ context,
25456
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25457
+ };
25245
25458
  case "retry": {
25246
25459
  const retries = (retryCountMap.get(result.fromStage) ?? 0) + 1;
25247
25460
  if (retries > MAX_STAGE_RETRIES) {
@@ -25251,7 +25464,8 @@ async function runPipeline(stages, context, eventEmitter) {
25251
25464
  finalAction: "fail",
25252
25465
  reason: `Stage "${stage.name}" exceeded max retries (${MAX_STAGE_RETRIES}) for "${result.fromStage}"`,
25253
25466
  stoppedAtStage: stage.name,
25254
- context
25467
+ context,
25468
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25255
25469
  };
25256
25470
  }
25257
25471
  retryCountMap.set(result.fromStage, retries);
@@ -25263,7 +25477,8 @@ async function runPipeline(stages, context, eventEmitter) {
25263
25477
  finalAction: "escalate",
25264
25478
  reason: `Retry target stage "${result.fromStage}" not found`,
25265
25479
  stoppedAtStage: stage.name,
25266
- context
25480
+ context,
25481
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25267
25482
  };
25268
25483
  }
25269
25484
  logger.debug("pipeline", `Retrying from stage "${result.fromStage}" (attempt ${retries}/${MAX_STAGE_RETRIES})`);
@@ -25276,7 +25491,12 @@ async function runPipeline(stages, context, eventEmitter) {
25276
25491
  }
25277
25492
  }
25278
25493
  }
25279
- return { success: true, finalAction: "complete", context };
25494
+ return {
25495
+ success: true,
25496
+ finalAction: "complete",
25497
+ context,
25498
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25499
+ };
25280
25500
  }
25281
25501
  var MAX_STAGE_RETRIES = 5;
25282
25502
  var init_runner = __esm(() => {
@@ -25814,8 +26034,7 @@ var init_acceptance = __esm(() => {
25814
26034
  acceptanceStage = {
25815
26035
  name: "acceptance",
25816
26036
  enabled(ctx) {
25817
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
25818
- if (!effectiveConfig.acceptance.enabled) {
26037
+ if (!ctx.config.acceptance.enabled) {
25819
26038
  return false;
25820
26039
  }
25821
26040
  if (!areAllStoriesComplete(ctx)) {
@@ -25825,7 +26044,6 @@ var init_acceptance = __esm(() => {
25825
26044
  },
25826
26045
  async execute(ctx) {
25827
26046
  const logger = getLogger();
25828
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
25829
26047
  logger.info("acceptance", "Running acceptance tests", { storyId: ctx.story.id });
25830
26048
  if (!ctx.featureDir) {
25831
26049
  logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests", { storyId: ctx.story.id });
@@ -25833,7 +26051,7 @@ var init_acceptance = __esm(() => {
25833
26051
  }
25834
26052
  const testGroups = ctx.acceptanceTestPaths ?? [
25835
26053
  {
25836
- testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir, effectiveConfig.acceptance.testPath, effectiveConfig.project?.language),
26054
+ testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language),
25837
26055
  packageDir: ctx.workdir
25838
26056
  }
25839
26057
  ];
@@ -25848,7 +26066,7 @@ var init_acceptance = __esm(() => {
25848
26066
  logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { storyId: ctx.story.id, testPath });
25849
26067
  continue;
25850
26068
  }
25851
- const testCmdParts = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
26069
+ const testCmdParts = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
25852
26070
  logger.info("acceptance", "Running acceptance command", {
25853
26071
  storyId: ctx.story.id,
25854
26072
  cmd: testCmdParts.join(" "),
@@ -25943,12 +26161,12 @@ function buildRefinementPrompt(criteria, codebaseContext, options) {
25943
26161
  `);
25944
26162
  const strategySection = buildStrategySection(options);
25945
26163
  const refinedExample = buildRefinedExample(options?.testStrategy);
26164
+ const codebaseSection = codebaseContext ? `CODEBASE CONTEXT:
26165
+ ${codebaseContext}
26166
+ ` : "";
25946
26167
  const core2 = `You are an acceptance criteria refinement assistant. Your task is to convert raw acceptance criteria into concrete, machine-verifiable assertions.
25947
26168
 
25948
- CODEBASE CONTEXT:
25949
- ${codebaseContext}
25950
- ${strategySection}
25951
- ACCEPTANCE CRITERIA TO REFINE:
26169
+ ${codebaseSection}${strategySection}ACCEPTANCE CRITERIA TO REFINE:
25952
26170
  ${criteriaList}
25953
26171
 
25954
26172
  For each criterion, produce a refined version that is concrete and automatically testable where possible.
@@ -26201,12 +26419,11 @@ ${stderr}` };
26201
26419
  if (!ctx.featureDir) {
26202
26420
  return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
26203
26421
  }
26204
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
26205
- const language = effectiveConfig.project?.language;
26206
- const testPathConfig = (ctx.effectiveConfig ?? ctx.config).acceptance.testPath;
26422
+ const language = ctx.config.project?.language;
26423
+ const testPathConfig = ctx.config.acceptance.testPath;
26207
26424
  const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
26208
- const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria);
26209
- const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-"));
26425
+ const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed").flatMap((s) => s.acceptanceCriteria);
26426
+ const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed");
26210
26427
  const workdirGroups = new Map;
26211
26428
  for (const story of nonFixStories) {
26212
26429
  const wd = story.workdir ?? "";
@@ -26262,7 +26479,7 @@ ${stderr}` };
26262
26479
  }
26263
26480
  if (shouldGenerate) {
26264
26481
  totalCriteria = allCriteria.length;
26265
- const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.config.autoMode.defaultAgent);
26482
+ const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
26266
26483
  let allRefinedCriteria;
26267
26484
  if (ctx.config.acceptance.refinement) {
26268
26485
  const maxConcurrency = ctx.config.acceptance.refinementConcurrency ?? 3;
@@ -26306,7 +26523,7 @@ ${stderr}` };
26306
26523
  const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
26307
26524
  let modelDef;
26308
26525
  try {
26309
- modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, ctx.config.acceptance.model ?? "fast", ctx.config.autoMode.defaultAgent);
26526
+ modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.config.acceptance.model ?? "fast", ctx.rootConfig.autoMode.defaultAgent);
26310
26527
  } catch {
26311
26528
  const tier = ctx.config.acceptance.model ?? "fast";
26312
26529
  modelDef = { provider: "anthropic", model: tier };
@@ -26342,7 +26559,7 @@ ${stderr}` };
26342
26559
  }
26343
26560
  let redFailCount = 0;
26344
26561
  for (const { testPath, packageDir } of testPaths) {
26345
- const runCmd = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
26562
+ const runCmd = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
26346
26563
  getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
26347
26564
  cmd: runCmd.join(" "),
26348
26565
  packageDir
@@ -26365,59 +26582,6 @@ ${stderr}` };
26365
26582
  };
26366
26583
  });
26367
26584
 
26368
- // src/agents/claude/index.ts
26369
- var init_claude = __esm(() => {
26370
- init_adapter3();
26371
- init_execution();
26372
- });
26373
-
26374
- // src/agents/shared/validation.ts
26375
- function validateAgentForTier(agent, tier) {
26376
- return agent.capabilities.supportedTiers.includes(tier);
26377
- }
26378
- function validateAgentFeature(agent, feature) {
26379
- return agent.capabilities.features.has(feature);
26380
- }
26381
- function describeAgentCapabilities(agent) {
26382
- const tiers = agent.capabilities.supportedTiers.join(",");
26383
- const features = Array.from(agent.capabilities.features).join(",");
26384
- const maxTokens = agent.capabilities.maxContextTokens;
26385
- return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
26386
- }
26387
-
26388
- // src/agents/index.ts
26389
- var exports_agents = {};
26390
- __export(exports_agents, {
26391
- validateAgentForTier: () => validateAgentForTier,
26392
- validateAgentFeature: () => validateAgentFeature,
26393
- parseTokenUsage: () => parseTokenUsage,
26394
- getInstalledAgents: () => getInstalledAgents,
26395
- getAllAgentNames: () => getAllAgentNames,
26396
- getAgentVersions: () => getAgentVersions,
26397
- getAgentVersion: () => getAgentVersion,
26398
- getAgent: () => getAgent,
26399
- formatCostWithConfidence: () => formatCostWithConfidence,
26400
- estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
26401
- estimateCostFromOutput: () => estimateCostFromOutput,
26402
- estimateCostByDuration: () => estimateCostByDuration,
26403
- estimateCost: () => estimateCost,
26404
- describeAgentCapabilities: () => describeAgentCapabilities,
26405
- checkAgentHealth: () => checkAgentHealth,
26406
- MODEL_PRICING: () => MODEL_PRICING,
26407
- CompleteError: () => CompleteError,
26408
- ClaudeCodeAdapter: () => ClaudeCodeAdapter,
26409
- COST_RATES: () => COST_RATES,
26410
- AllAgentsUnavailableError: () => AllAgentsUnavailableError
26411
- });
26412
- var init_agents = __esm(() => {
26413
- init_types2();
26414
- init_claude();
26415
- init_registry();
26416
- init_cost();
26417
- init_version_detection();
26418
- init_errors();
26419
- });
26420
-
26421
26585
  // src/quality/runner.ts
26422
26586
  var {spawn: spawn2 } = globalThis.Bun;
26423
26587
  async function runQualityCommand(opts) {
@@ -26720,7 +26884,60 @@ Do NOT add new features \u2014 only fix the identified issues.
26720
26884
  Commit your fixes when done.${scopeConstraint}`;
26721
26885
  }
26722
26886
 
26723
- // src/review/dialogue.ts
26887
+ // src/agents/claude/index.ts
26888
+ var init_claude = __esm(() => {
26889
+ init_adapter3();
26890
+ init_execution();
26891
+ });
26892
+
26893
+ // src/agents/shared/validation.ts
26894
+ function validateAgentForTier(agent, tier) {
26895
+ return agent.capabilities.supportedTiers.includes(tier);
26896
+ }
26897
+ function validateAgentFeature(agent, feature) {
26898
+ return agent.capabilities.features.has(feature);
26899
+ }
26900
+ function describeAgentCapabilities(agent) {
26901
+ const tiers = agent.capabilities.supportedTiers.join(",");
26902
+ const features = Array.from(agent.capabilities.features).join(",");
26903
+ const maxTokens = agent.capabilities.maxContextTokens;
26904
+ return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
26905
+ }
26906
+
26907
+ // src/agents/index.ts
26908
+ var exports_agents = {};
26909
+ __export(exports_agents, {
26910
+ validateAgentForTier: () => validateAgentForTier,
26911
+ validateAgentFeature: () => validateAgentFeature,
26912
+ parseTokenUsage: () => parseTokenUsage,
26913
+ getInstalledAgents: () => getInstalledAgents,
26914
+ getAllAgentNames: () => getAllAgentNames,
26915
+ getAgentVersions: () => getAgentVersions,
26916
+ getAgentVersion: () => getAgentVersion,
26917
+ getAgent: () => getAgent,
26918
+ formatCostWithConfidence: () => formatCostWithConfidence,
26919
+ estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
26920
+ estimateCostFromOutput: () => estimateCostFromOutput,
26921
+ estimateCostByDuration: () => estimateCostByDuration,
26922
+ estimateCost: () => estimateCost,
26923
+ describeAgentCapabilities: () => describeAgentCapabilities,
26924
+ checkAgentHealth: () => checkAgentHealth,
26925
+ MODEL_PRICING: () => MODEL_PRICING,
26926
+ CompleteError: () => CompleteError,
26927
+ ClaudeCodeAdapter: () => ClaudeCodeAdapter,
26928
+ COST_RATES: () => COST_RATES,
26929
+ AllAgentsUnavailableError: () => AllAgentsUnavailableError
26930
+ });
26931
+ var init_agents = __esm(() => {
26932
+ init_types2();
26933
+ init_claude();
26934
+ init_registry();
26935
+ init_cost();
26936
+ init_version_detection();
26937
+ init_errors();
26938
+ });
26939
+
26940
+ // src/review/dialogue-prompts.ts
26724
26941
  function buildReviewPrompt(diff, story, _semanticConfig) {
26725
26942
  const criteria = story.acceptanceCriteria.map((c) => `- ${c}`).join(`
26726
26943
  `);
@@ -26754,6 +26971,98 @@ function buildReReviewPrompt(updatedDiff, previousFindings) {
26754
26971
  ].join(`
26755
26972
  `);
26756
26973
  }
26974
+ function buildProposalsSection(proposals) {
26975
+ return proposals.map((p) => `### ${p.debater}
26976
+ ${p.output}`).join(`
26977
+
26978
+ `);
26979
+ }
26980
+ function buildCritiquesSection(critiques) {
26981
+ if (critiques.length === 0)
26982
+ return "";
26983
+ return `
26984
+
26985
+ ## Critiques
26986
+ ${critiques.map((c, i) => `### Critique ${i + 1}
26987
+ ${c}`).join(`
26988
+
26989
+ `)}`;
26990
+ }
26991
+ function buildVoteTallyLine(ctx) {
26992
+ if (!ctx.majorityVote)
26993
+ return "";
26994
+ const { passCount, failCount } = ctx.majorityVote;
26995
+ const failOpenNote = ctx.resolverType === "majority-fail-open" ? " (unparseable proposals count as pass)" : " (unparseable proposals count as fail)";
26996
+ return `
26997
+
26998
+ The preliminary majority vote is: **${passCount} passed, ${failCount} failed**${failOpenNote}. Verify the failing findings with tools before giving your authoritative verdict.`;
26999
+ }
27000
+ function buildResolverFraming(ctx) {
27001
+ switch (ctx.resolverType) {
27002
+ case "majority-fail-closed":
27003
+ case "majority-fail-open":
27004
+ return "You are the authoritative reviewer resolving a debate. A preliminary vote was taken \u2014 see tally below. Verify disputed findings using tools (READ files, GREP for usage) and give your final verdict.";
27005
+ case "synthesis":
27006
+ return "You are a synthesis reviewer. Synthesize the debater proposals into a single, coherent, tool-verified verdict. Use READ and GREP to verify claims before ruling.";
27007
+ case "custom":
27008
+ return "You are the judge. Evaluate the debater proposals independently. Verify claims with tools (READ, GREP) and give your final authoritative verdict.";
27009
+ default:
27010
+ return "You are the reviewer. Evaluate the debater proposals and give your final authoritative verdict.";
27011
+ }
27012
+ }
27013
+ function buildDebateResolverPrompt(proposals, critiques, diff, story, _semanticConfig, resolverContext) {
27014
+ const criteria = story.acceptanceCriteria.map((c) => `- ${c}`).join(`
27015
+ `);
27016
+ const framing = buildResolverFraming(resolverContext);
27017
+ const voteTally = buildVoteTallyLine(resolverContext);
27018
+ const proposalsSection = buildProposalsSection(proposals);
27019
+ const critiquesSection = buildCritiquesSection(critiques);
27020
+ return [
27021
+ framing,
27022
+ "",
27023
+ `## Story ${story.id}: ${story.title}`,
27024
+ "",
27025
+ "## Acceptance Criteria",
27026
+ criteria,
27027
+ "",
27028
+ "## Debater Proposals",
27029
+ proposalsSection,
27030
+ critiquesSection,
27031
+ "",
27032
+ "## Diff",
27033
+ diff,
27034
+ voteTally,
27035
+ "",
27036
+ "Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string } }"
27037
+ ].filter((line) => line !== undefined).join(`
27038
+ `);
27039
+ }
27040
+ function buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFindings, resolverContext) {
27041
+ const framing = buildResolverFraming(resolverContext);
27042
+ const findingsList = previousFindings.length > 0 ? previousFindings.map((f) => `- ${f.ruleId}: ${f.message}`).join(`
27043
+ `) : "(none)";
27044
+ const proposalsSection = buildProposalsSection(proposals);
27045
+ const critiquesSection = buildCritiquesSection(critiques);
27046
+ return [
27047
+ `${framing} This is a re-review after implementer changes.`,
27048
+ "",
27049
+ "## Previous Findings",
27050
+ findingsList,
27051
+ "",
27052
+ "## Updated Debater Proposals",
27053
+ proposalsSection,
27054
+ critiquesSection,
27055
+ "",
27056
+ "## Updated Diff",
27057
+ updatedDiff,
27058
+ "",
27059
+ "Respond with JSON: { passed: boolean, findings: [...], findingReasoning: { [id]: string }, deltaSummary: string }",
27060
+ "deltaSummary should describe which previous findings are resolved vs still present."
27061
+ ].filter((line) => line !== undefined).join(`
27062
+ `);
27063
+ }
27064
+
27065
+ // src/review/dialogue.ts
26757
27066
  function extractDeltaSummary(rawOutput, previousFindings, newFindings) {
26758
27067
  try {
26759
27068
  const parsed = JSON.parse(rawOutput);
@@ -26825,6 +27134,7 @@ function createReviewerSession(agent, storyId, workdir, featureName, _config) {
26825
27134
  let lastCheckResult = null;
26826
27135
  let lastStory = null;
26827
27136
  let lastSemanticConfig = null;
27137
+ let lastWasDebateResolve = false;
26828
27138
  const sessionState = {
26829
27139
  generation: 1,
26830
27140
  pendingCompactionContext: null
@@ -26883,10 +27193,12 @@ ${prompt}`,
26883
27193
  history.push({ role: "implementer", content: prompt });
26884
27194
  history.push({ role: "reviewer", content: result.output });
26885
27195
  const parsed = parseReviewResponse(result.output);
26886
- lastCheckResult = parsed;
27196
+ const reviewResult = { ...parsed, cost: result.estimatedCost ?? 0 };
27197
+ lastCheckResult = reviewResult;
26887
27198
  lastStory = story;
26888
27199
  lastSemanticConfig = semanticConfig;
26889
- return parsed;
27200
+ lastWasDebateResolve = false;
27201
+ return reviewResult;
26890
27202
  },
26891
27203
  async reReview(updatedDiff) {
26892
27204
  if (!active) {
@@ -26920,7 +27232,7 @@ ${prompt}`,
26920
27232
  history.push({ role: "reviewer", content: result.output });
26921
27233
  const parsed = parseReviewResponse(result.output);
26922
27234
  const deltaSummary = extractDeltaSummary(result.output, previousFindings, parsed.checkResult.findings);
26923
- const dialogueResult = { ...parsed, deltaSummary };
27235
+ const dialogueResult = { ...parsed, deltaSummary, cost: result.estimatedCost ?? 0 };
26924
27236
  lastCheckResult = dialogueResult;
26925
27237
  const maxMessages = _config.review?.dialogue?.maxDialogueMessages ?? 20;
26926
27238
  if (history.length > maxMessages) {
@@ -26955,6 +27267,76 @@ ${prompt}`,
26955
27267
  history.push({ role: "reviewer", content: result.output });
26956
27268
  return result.output;
26957
27269
  },
27270
+ async resolveDebate(proposals, critiques, diff, story, semanticConfig, resolverContext) {
27271
+ if (!active) {
27272
+ throw new NaxError(`[dialogue] ReviewerSession for story ${storyId} has been destroyed`, "REVIEWER_SESSION_DESTROYED", { stage: "review", storyId, featureName });
27273
+ }
27274
+ const prompt = buildDebateResolverPrompt(proposals, critiques, diff, story, semanticConfig, resolverContext);
27275
+ const { modelTier, modelDef, timeoutSeconds } = resolveRunParams(semanticConfig);
27276
+ const { effectivePrompt, acpSessionName } = buildEffectiveRunArgs(prompt);
27277
+ const result = await agent.run({
27278
+ prompt: effectivePrompt,
27279
+ workdir,
27280
+ modelTier,
27281
+ modelDef,
27282
+ timeoutSeconds,
27283
+ sessionRole: "reviewer",
27284
+ keepSessionOpen: true,
27285
+ pipelineStage: "review",
27286
+ config: _config,
27287
+ storyId,
27288
+ featureName,
27289
+ acpSessionName
27290
+ });
27291
+ history.push({ role: "implementer", content: prompt });
27292
+ history.push({ role: "reviewer", content: result.output });
27293
+ const parsed = parseReviewResponse(result.output);
27294
+ const reviewResult = { ...parsed, cost: result.estimatedCost ?? 0 };
27295
+ lastCheckResult = reviewResult;
27296
+ lastStory = story;
27297
+ lastSemanticConfig = semanticConfig;
27298
+ lastWasDebateResolve = true;
27299
+ return reviewResult;
27300
+ },
27301
+ async reReviewDebate(proposals, critiques, updatedDiff, resolverContext) {
27302
+ if (!active) {
27303
+ throw new NaxError(`[dialogue] ReviewerSession for story ${storyId} has been destroyed`, "REVIEWER_SESSION_DESTROYED", { stage: "review", storyId, featureName });
27304
+ }
27305
+ if (!lastCheckResult || !lastSemanticConfig || !lastWasDebateResolve) {
27306
+ throw new NaxError(`[dialogue] reReviewDebate() called before any resolveDebate() on story ${storyId}`, "NO_REVIEW_RESULT", { stage: "review", storyId });
27307
+ }
27308
+ const previousFindings = lastCheckResult.checkResult.findings;
27309
+ const prompt = buildDebateReReviewPrompt(proposals, critiques, updatedDiff, previousFindings, resolverContext);
27310
+ const { modelTier, modelDef, timeoutSeconds } = resolveRunParams(lastSemanticConfig);
27311
+ const { effectivePrompt, acpSessionName } = buildEffectiveRunArgs(prompt);
27312
+ const result = await agent.run({
27313
+ prompt: effectivePrompt,
27314
+ workdir,
27315
+ modelTier,
27316
+ modelDef,
27317
+ timeoutSeconds,
27318
+ sessionRole: "reviewer",
27319
+ keepSessionOpen: true,
27320
+ pipelineStage: "review",
27321
+ config: _config,
27322
+ storyId,
27323
+ featureName,
27324
+ acpSessionName
27325
+ });
27326
+ history.push({ role: "implementer", content: prompt });
27327
+ history.push({ role: "reviewer", content: result.output });
27328
+ const parsed = parseReviewResponse(result.output);
27329
+ const deltaSummary = extractDeltaSummary(result.output, previousFindings, parsed.checkResult.findings);
27330
+ const dialogueResult = { ...parsed, deltaSummary, cost: result.estimatedCost ?? 0 };
27331
+ lastCheckResult = dialogueResult;
27332
+ const maxMessages = _config.review?.dialogue?.maxDialogueMessages ?? 20;
27333
+ if (history.length > maxMessages) {
27334
+ const compactedSummary = compactHistory(history);
27335
+ sessionState.generation++;
27336
+ sessionState.pendingCompactionContext = compactedSummary;
27337
+ }
27338
+ return dialogueResult;
27339
+ },
26958
27340
  getVerdict() {
26959
27341
  if (!lastCheckResult || !lastStory) {
26960
27342
  throw new NaxError(`[dialogue] getVerdict() called before any review() on story ${storyId}`, "NO_REVIEW_RESULT", { stage: "review", storyId });
@@ -27190,10 +27572,8 @@ var init_language_commands = __esm(() => {
27190
27572
  // src/review/semantic.ts
27191
27573
  var {spawn: spawn3 } = globalThis.Bun;
27192
27574
  async function collectDiff(workdir, storyGitRef, excludePatterns) {
27193
- const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`];
27194
- if (excludePatterns.length > 0) {
27195
- cmd.push("--", ".", ...excludePatterns);
27196
- }
27575
+ const merged = [...new Set([...excludePatterns, ...ALWAYS_EXCLUDED])];
27576
+ const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`, "--", ".", ...merged];
27197
27577
  const proc = _semanticDeps.spawn({
27198
27578
  cmd,
27199
27579
  cwd: workdir,
@@ -27353,7 +27733,7 @@ function toReviewFindings(findings) {
27353
27733
  source: "semantic-review"
27354
27734
  }));
27355
27735
  }
27356
- async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig, featureName) {
27736
+ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig, featureName, resolverSession) {
27357
27737
  const startTime = Date.now();
27358
27738
  const logger = getSafeLogger();
27359
27739
  if (featureName === undefined) {
@@ -27423,6 +27803,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
27423
27803
  const reviewDebateEnabled = naxConfig?.debate?.enabled && naxConfig?.debate?.stages?.review?.enabled;
27424
27804
  if (reviewDebateEnabled) {
27425
27805
  const reviewStageConfig = naxConfig?.debate?.stages.review;
27806
+ const isReReview = resolverSession !== undefined && resolverSession.history.length > 0;
27426
27807
  const debateSession = _semanticDeps.createDebateSession({
27427
27808
  storyId: story.id,
27428
27809
  stage: "review",
@@ -27430,25 +27811,69 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
27430
27811
  config: naxConfig ?? DEFAULT_CONFIG,
27431
27812
  workdir,
27432
27813
  featureName,
27433
- timeoutSeconds: naxConfig?.execution?.sessionTimeoutSeconds
27814
+ timeoutSeconds: naxConfig?.execution?.sessionTimeoutSeconds,
27815
+ reviewerSession: resolverSession,
27816
+ resolverContextInput: resolverSession ? {
27817
+ diff,
27818
+ story: { id: story.id, title: story.title, acceptanceCriteria: story.acceptanceCriteria },
27819
+ semanticConfig,
27820
+ resolverType: reviewStageConfig.resolver.type,
27821
+ isReReview
27822
+ } : undefined
27434
27823
  });
27824
+ const historyLenBefore = resolverSession?.history.length ?? 0;
27435
27825
  const debateResult = await debateSession.run(prompt);
27436
- let passCount = 0;
27437
- let failCount = 0;
27826
+ const debateCost = debateResult.totalCostUsd ?? 0;
27827
+ const sessionUsed = resolverSession && resolverSession.history.length > historyLenBefore;
27828
+ if (sessionUsed) {
27829
+ const durationMs3 = Date.now() - startTime;
27830
+ try {
27831
+ const verdict = resolverSession.getVerdict();
27832
+ const findings = verdict.findings ?? [];
27833
+ if (!verdict.passed && findings.length > 0) {
27834
+ logger?.warn("review", `Semantic review failed (debate+dialogue): ${findings.length} findings`, {
27835
+ storyId: story.id,
27836
+ durationMs: durationMs3
27837
+ });
27838
+ return {
27839
+ check: "semantic",
27840
+ success: false,
27841
+ command: "",
27842
+ exitCode: 1,
27843
+ output: `Semantic review failed:
27844
+
27845
+ ${findings.map((f) => `${f.ruleId}: ${f.message}`).join(`
27846
+ `)}`,
27847
+ durationMs: durationMs3,
27848
+ findings,
27849
+ cost: debateCost
27850
+ };
27851
+ }
27852
+ const label = verdict.passed ? "Semantic review passed (debate+dialogue)" : "Semantic review passed (debate+dialogue, all findings non-blocking)";
27853
+ logger?.info("review", label, { storyId: story.id, durationMs: durationMs3 });
27854
+ return {
27855
+ check: "semantic",
27856
+ success: true,
27857
+ command: "",
27858
+ exitCode: 0,
27859
+ output: label,
27860
+ durationMs: durationMs3,
27861
+ cost: debateCost
27862
+ };
27863
+ } catch {
27864
+ logger?.warn("review", "getVerdict() failed after debate+dialogue \u2014 falling back to stateless verdict", {
27865
+ storyId: story.id
27866
+ });
27867
+ }
27868
+ }
27869
+ const resolverPassed = debateResult.outcome === "passed";
27438
27870
  const allFindings = [];
27439
27871
  for (const p of debateResult.proposals) {
27440
27872
  const parsed2 = parseLLMResponse(p.output);
27441
27873
  if (parsed2) {
27442
- if (parsed2.passed)
27443
- passCount++;
27444
- else
27445
- failCount++;
27446
27874
  allFindings.push(...parsed2.findings);
27447
- } else {
27448
- failCount++;
27449
27875
  }
27450
27876
  }
27451
- const majorityPassed = passCount > failCount;
27452
27877
  const seen = new Set;
27453
27878
  const deduped = [];
27454
27879
  for (const f of allFindings) {
@@ -27460,7 +27885,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
27460
27885
  }
27461
27886
  const debateBlocking = deduped.filter((f) => isBlockingSeverity(f.severity));
27462
27887
  const durationMs2 = Date.now() - startTime;
27463
- if (!majorityPassed) {
27888
+ if (!resolverPassed) {
27464
27889
  if (debateBlocking.length > 0) {
27465
27890
  logger?.warn("review", `Semantic review failed (debate): ${debateBlocking.length} findings`, {
27466
27891
  storyId: story.id,
@@ -27475,7 +27900,8 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
27475
27900
 
27476
27901
  ${formatFindings(debateBlocking)}`,
27477
27902
  durationMs: durationMs2,
27478
- findings: toReviewFindings(debateBlocking)
27903
+ findings: toReviewFindings(debateBlocking),
27904
+ cost: debateCost
27479
27905
  };
27480
27906
  }
27481
27907
  logger?.info("review", "Semantic review passed (debate, all findings non-blocking)", {
@@ -27488,7 +27914,8 @@ ${formatFindings(debateBlocking)}`,
27488
27914
  command: "",
27489
27915
  exitCode: 0,
27490
27916
  output: "Semantic review passed (debate, all findings were unverifiable or informational)",
27491
- durationMs: durationMs2
27917
+ durationMs: durationMs2,
27918
+ cost: debateCost
27492
27919
  };
27493
27920
  }
27494
27921
  logger?.info("review", "Semantic review passed (debate)", { storyId: story.id, durationMs: durationMs2 });
@@ -27498,7 +27925,8 @@ ${formatFindings(debateBlocking)}`,
27498
27925
  command: "",
27499
27926
  exitCode: 0,
27500
27927
  output: "Semantic review passed",
27501
- durationMs: durationMs2
27928
+ durationMs: durationMs2,
27929
+ cost: debateCost
27502
27930
  };
27503
27931
  }
27504
27932
  const implementerSidecarKey = `${story.id}:implementer`;
@@ -27517,6 +27945,7 @@ ${formatFindings(debateBlocking)}`,
27517
27945
  }
27518
27946
  } catch {}
27519
27947
  let rawResponse;
27948
+ let llmCost = 0;
27520
27949
  try {
27521
27950
  let runErr;
27522
27951
  let runSucceeded = false;
@@ -27533,6 +27962,7 @@ ${formatFindings(debateBlocking)}`,
27533
27962
  config: naxConfig ?? DEFAULT_CONFIG
27534
27963
  });
27535
27964
  runOutput = runResult.output;
27965
+ llmCost = runResult.estimatedCost ?? 0;
27536
27966
  runSucceeded = true;
27537
27967
  } catch (err) {
27538
27968
  runErr = err;
@@ -27548,6 +27978,7 @@ ${formatFindings(debateBlocking)}`,
27548
27978
  config: naxConfig ?? DEFAULT_CONFIG
27549
27979
  });
27550
27980
  rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
27981
+ llmCost = typeof completeResult === "string" ? 0 : completeResult.costUsd ?? 0;
27551
27982
  }
27552
27983
  } catch (err) {
27553
27984
  logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
@@ -27573,7 +28004,8 @@ ${formatFindings(debateBlocking)}`,
27573
28004
  command: "",
27574
28005
  exitCode: 1,
27575
28006
  output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
27576
- durationMs: Date.now() - startTime
28007
+ durationMs: Date.now() - startTime,
28008
+ cost: llmCost
27577
28009
  };
27578
28010
  }
27579
28011
  logger?.warn("semantic", "LLM returned invalid JSON \u2014 fail-open", { rawResponse: rawResponse.slice(0, 200) });
@@ -27583,7 +28015,8 @@ ${formatFindings(debateBlocking)}`,
27583
28015
  command: "",
27584
28016
  exitCode: 0,
27585
28017
  output: "semantic review: could not parse LLM response (fail-open)",
27586
- durationMs: Date.now() - startTime
28018
+ durationMs: Date.now() - startTime,
28019
+ cost: llmCost
27587
28020
  };
27588
28021
  }
27589
28022
  const blockingFindings = parsed.findings.filter((f) => isBlockingSeverity(f.severity));
@@ -27620,7 +28053,8 @@ ${formatFindings(blockingFindings)}`;
27620
28053
  exitCode: 1,
27621
28054
  output,
27622
28055
  durationMs: durationMs2,
27623
- findings: toReviewFindings(blockingFindings)
28056
+ findings: toReviewFindings(blockingFindings),
28057
+ cost: llmCost
27624
28058
  };
27625
28059
  }
27626
28060
  if (!parsed.passed && blockingFindings.length === 0) {
@@ -27632,7 +28066,8 @@ ${formatFindings(blockingFindings)}`;
27632
28066
  command: "",
27633
28067
  exitCode: 0,
27634
28068
  output: "Semantic review passed (all findings were unverifiable or informational)",
27635
- durationMs: durationMs2
28069
+ durationMs: durationMs2,
28070
+ cost: llmCost
27636
28071
  };
27637
28072
  }
27638
28073
  const durationMs = Date.now() - startTime;
@@ -27645,10 +28080,11 @@ ${formatFindings(blockingFindings)}`;
27645
28080
  command: "",
27646
28081
  exitCode: parsed.passed ? 0 : 1,
27647
28082
  output: parsed.passed ? "Semantic review passed" : "Semantic review failed (no findings)",
27648
- durationMs
28083
+ durationMs,
28084
+ cost: llmCost
27649
28085
  };
27650
28086
  }
27651
- var _semanticDeps, DIFF_CAP_BYTES = 51200;
28087
+ var _semanticDeps, DIFF_CAP_BYTES = 51200, ALWAYS_EXCLUDED;
27652
28088
  var init_semantic = __esm(() => {
27653
28089
  init_adapter();
27654
28090
  init_config();
@@ -27662,6 +28098,7 @@ var init_semantic = __esm(() => {
27662
28098
  createDebateSession: (opts) => new DebateSession(opts),
27663
28099
  readAcpSession
27664
28100
  };
28101
+ ALWAYS_EXCLUDED = [":!.nax/", ":!.nax-pids"];
27665
28102
  });
27666
28103
 
27667
28104
  // src/review/runner.ts
@@ -27746,7 +28183,7 @@ async function getUncommittedFilesImpl(workdir) {
27746
28183
  return [];
27747
28184
  }
27748
28185
  }
27749
- async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName) {
28186
+ async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName, resolverSession) {
27750
28187
  const startTime = Date.now();
27751
28188
  const logger = getSafeLogger();
27752
28189
  const checks3 = [];
@@ -27803,10 +28240,19 @@ Stage and commit these files before running review.`
27803
28240
  modelTier: "balanced",
27804
28241
  rules: [],
27805
28242
  timeoutMs: 600000,
27806
- excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
28243
+ excludePatterns: [
28244
+ ":!test/",
28245
+ ":!tests/",
28246
+ ":!*_test.go",
28247
+ ":!*.test.ts",
28248
+ ":!*.spec.ts",
28249
+ ":!**/__tests__/",
28250
+ ":!.nax/",
28251
+ ":!.nax-pids"
28252
+ ]
27807
28253
  };
27808
28254
  const runSemantic = _reviewSemanticDeps.runSemanticReview;
27809
- const result2 = featureName !== undefined ? await runSemantic(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig, featureName) : await runSemantic(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig);
28255
+ const result2 = await runSemantic(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig, featureName, resolverSession);
27810
28256
  checks3.push(result2);
27811
28257
  if (!result2.success && !firstFailure) {
27812
28258
  firstFailure = `${checkName} failed`;
@@ -27888,9 +28334,9 @@ async function getChangedFiles(workdir, baseRef) {
27888
28334
  }
27889
28335
 
27890
28336
  class ReviewOrchestrator {
27891
- async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig, retrySkipChecks, featureName) {
28337
+ async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig, retrySkipChecks, featureName, resolverSession) {
27892
28338
  const logger = getSafeLogger();
27893
- const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName);
28339
+ const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig, retrySkipChecks, featureName, resolverSession);
27894
28340
  if (!builtIn.success) {
27895
28341
  return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
27896
28342
  }
@@ -27951,6 +28397,21 @@ class ReviewOrchestrator {
27951
28397
  }
27952
28398
  return { builtIn, success: true, pluginFailed: false };
27953
28399
  }
28400
+ reviewFromContext(ctx) {
28401
+ const retrySkipChecks = ctx.retrySkipChecks;
28402
+ ctx.retrySkipChecks = undefined;
28403
+ const agentResolver = ctx.agentGetFn ?? undefined;
28404
+ const agentName = ctx.rootConfig.autoMode?.defaultAgent;
28405
+ const modelResolver = agentName ? (_tier) => agentResolver ? agentResolver(agentName) ?? null : null : undefined;
28406
+ const reviewDebateEnabled = ctx.rootConfig?.debate?.enabled && ctx.rootConfig?.debate?.stages?.review?.enabled;
28407
+ const resolverSession = reviewDebateEnabled ? ctx.reviewerSession : undefined;
28408
+ return this.review(ctx.config.review, ctx.workdir, ctx.config.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir, ctx.config.quality?.commands, ctx.story.id, {
28409
+ id: ctx.story.id,
28410
+ title: ctx.story.title,
28411
+ description: ctx.story.description,
28412
+ acceptanceCriteria: ctx.story.acceptanceCriteria
28413
+ }, modelResolver, ctx.config, retrySkipChecks, ctx.prd.feature, resolverSession);
28414
+ }
27954
28415
  }
27955
28416
  var _orchestratorDeps, reviewOrchestrator;
27956
28417
  var init_orchestrator = __esm(() => {
@@ -27966,7 +28427,6 @@ __export(exports_review, {
27966
28427
  reviewStage: () => reviewStage,
27967
28428
  _reviewDeps: () => _reviewDeps
27968
28429
  });
27969
- import { join as join17 } from "path";
27970
28430
  var reviewStage, _reviewDeps;
27971
28431
  var init_review = __esm(() => {
27972
28432
  init_agents();
@@ -27976,19 +28436,15 @@ var init_review = __esm(() => {
27976
28436
  init_orchestrator();
27977
28437
  reviewStage = {
27978
28438
  name: "review",
27979
- enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
28439
+ enabled: (ctx) => ctx.config.review.enabled,
27980
28440
  async execute(ctx) {
27981
28441
  const logger = getLogger();
27982
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27983
- const dialogueEnabled = effectiveConfig.review?.dialogue?.enabled ?? false;
28442
+ const reviewDebateEnabled = ctx.rootConfig?.debate?.enabled && ctx.rootConfig?.debate?.stages?.review?.enabled;
28443
+ const dialogueEnabled = ctx.config.review?.dialogue?.enabled ?? false;
27984
28444
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
27985
- const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27986
28445
  const agentResolver = ctx.agentGetFn ?? getAgent;
27987
- const agentName = effectiveConfig.autoMode?.defaultAgent;
27988
- const modelResolver = (_tier) => agentName ? agentResolver(agentName) ?? null : null;
27989
- const retrySkipChecks = ctx.retrySkipChecks;
27990
- ctx.retrySkipChecks = undefined;
27991
- if (dialogueEnabled && ctx.reviewerSession) {
28446
+ const agentName = ctx.rootConfig.autoMode?.defaultAgent;
28447
+ if (dialogueEnabled && !reviewDebateEnabled && ctx.reviewerSession) {
27992
28448
  try {
27993
28449
  const diff = ctx.storyGitRef ?? "";
27994
28450
  const reReviewResult = await ctx.reviewerSession.reReview(diff);
@@ -28025,57 +28481,56 @@ var init_review = __esm(() => {
28025
28481
  }
28026
28482
  if (dialogueEnabled && !ctx.reviewerSession) {
28027
28483
  const agent = agentName ? agentResolver(agentName) ?? null : null;
28028
- ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id, effectiveWorkdir, ctx.prd.feature ?? "", ctx.config);
28029
- const semanticConfig = effectiveConfig.review?.semantic;
28030
- if (semanticConfig && agent) {
28031
- try {
28032
- const diff = ctx.storyGitRef ?? "";
28033
- const story = {
28034
- id: ctx.story.id,
28035
- title: ctx.story.title,
28036
- description: ctx.story.description,
28037
- acceptanceCriteria: ctx.story.acceptanceCriteria
28038
- };
28039
- const sessionResult = await ctx.reviewerSession.review(diff, story, semanticConfig);
28040
- const passed = sessionResult.checkResult.success;
28041
- ctx.reviewResult = {
28042
- success: passed,
28043
- checks: passed ? [] : [
28044
- {
28045
- check: "semantic",
28046
- success: false,
28047
- command: "reviewer-session-review",
28048
- exitCode: 1,
28049
- output: sessionResult.checkResult.findings.map((f) => f.message).join(`
28484
+ ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id, ctx.workdir, ctx.prd.feature ?? "", ctx.config);
28485
+ if (!reviewDebateEnabled) {
28486
+ const semanticConfig = ctx.config.review?.semantic;
28487
+ if (semanticConfig && agent) {
28488
+ try {
28489
+ const diff = ctx.storyGitRef ?? "";
28490
+ const story = {
28491
+ id: ctx.story.id,
28492
+ title: ctx.story.title,
28493
+ description: ctx.story.description,
28494
+ acceptanceCriteria: ctx.story.acceptanceCriteria
28495
+ };
28496
+ const sessionResult = await ctx.reviewerSession.review(diff, story, semanticConfig);
28497
+ const passed = sessionResult.checkResult.success;
28498
+ ctx.reviewResult = {
28499
+ success: passed,
28500
+ checks: passed ? [] : [
28501
+ {
28502
+ check: "semantic",
28503
+ success: false,
28504
+ command: "reviewer-session-review",
28505
+ exitCode: 1,
28506
+ output: sessionResult.checkResult.findings.map((f) => f.message).join(`
28050
28507
  `),
28051
- durationMs: 0,
28052
- findings: sessionResult.checkResult.findings
28053
- }
28054
- ],
28055
- totalDurationMs: 0
28056
- };
28057
- if (passed) {
28058
- logger.info("review", "Review passed (dialogue session)", { storyId: ctx.story.id });
28059
- } else {
28060
- logger.warn("review", "Review failed (dialogue session) \u2014 handing off to autofix", {
28508
+ durationMs: 0,
28509
+ findings: sessionResult.checkResult.findings
28510
+ }
28511
+ ],
28512
+ totalDurationMs: 0
28513
+ };
28514
+ const dialogueCost = sessionResult.cost ?? 0;
28515
+ if (passed) {
28516
+ logger.info("review", "Review passed (dialogue session)", { storyId: ctx.story.id });
28517
+ } else {
28518
+ logger.warn("review", "Review failed (dialogue session) \u2014 handing off to autofix", {
28519
+ storyId: ctx.story.id
28520
+ });
28521
+ }
28522
+ return { action: "continue", cost: dialogueCost || undefined };
28523
+ } catch (err) {
28524
+ logger.warn("review", "ReviewerSession.review() failed \u2014 falling back to one-shot review", {
28061
28525
  storyId: ctx.story.id
28062
28526
  });
28063
28527
  }
28064
- return { action: "continue" };
28065
- } catch (err) {
28066
- logger.warn("review", "ReviewerSession.review() failed \u2014 falling back to one-shot review", {
28067
- storyId: ctx.story.id
28068
- });
28069
28528
  }
28070
28529
  }
28071
28530
  }
28072
- const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir, effectiveConfig.quality?.commands, ctx.story.id, {
28073
- id: ctx.story.id,
28074
- title: ctx.story.title,
28075
- description: ctx.story.description,
28076
- acceptanceCriteria: ctx.story.acceptanceCriteria
28077
- }, modelResolver, ctx.config, retrySkipChecks, ctx.prd.feature);
28531
+ const result = await reviewOrchestrator.reviewFromContext(ctx);
28078
28532
  ctx.reviewResult = result.builtIn;
28533
+ const reviewCost = (result.builtIn.checks ?? []).reduce((sum, c) => sum + (c.cost ?? 0), 0) || undefined;
28079
28534
  if (!result.success) {
28080
28535
  const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
28081
28536
  const semanticFindings = (result.builtIn.checks ?? []).filter((c) => c.check === "semantic" && !c.success && c.findings?.length).flatMap((c) => c.findings ?? []);
@@ -28084,29 +28539,29 @@ var init_review = __esm(() => {
28084
28539
  ctx.reviewFindings = allFindings;
28085
28540
  }
28086
28541
  if (result.pluginFailed) {
28087
- if (ctx.interaction && isTriggerEnabled("security-review", effectiveConfig)) {
28088
- const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, effectiveConfig, ctx.interaction);
28542
+ if (ctx.interaction && isTriggerEnabled("security-review", ctx.config)) {
28543
+ const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
28089
28544
  if (!shouldContinue) {
28090
28545
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
28091
- return { action: "fail", reason: `Review failed: ${result.failureReason}` };
28546
+ return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
28092
28547
  }
28093
28548
  logger.warn("review", "Security-review trigger escalated \u2014 retrying story", { storyId: ctx.story.id });
28094
- return { action: "escalate", reason: `Review failed: ${result.failureReason}` };
28549
+ return { action: "escalate", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
28095
28550
  }
28096
28551
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
28097
- return { action: "fail", reason: `Review failed: ${result.failureReason}` };
28552
+ return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
28098
28553
  }
28099
28554
  logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
28100
28555
  reason: result.failureReason,
28101
28556
  storyId: ctx.story.id
28102
28557
  });
28103
- return { action: "continue" };
28558
+ return { action: "continue", cost: reviewCost };
28104
28559
  }
28105
28560
  logger.info("review", "Review passed", {
28106
28561
  durationMs: result.builtIn.totalDurationMs,
28107
28562
  storyId: ctx.story.id
28108
28563
  });
28109
- return { action: "continue" };
28564
+ return { action: "continue", cost: reviewCost };
28110
28565
  }
28111
28566
  };
28112
28567
  _reviewDeps = {
@@ -28116,7 +28571,6 @@ var init_review = __esm(() => {
28116
28571
  });
28117
28572
 
28118
28573
  // src/pipeline/stages/autofix.ts
28119
- import { join as join18 } from "path";
28120
28574
  async function recheckReview(ctx) {
28121
28575
  const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
28122
28576
  if (!reviewStage2.enabled(ctx))
@@ -28154,16 +28608,15 @@ Your previous fix attempt (attempt ${attempt}) did not resolve the quality error
28154
28608
  }
28155
28609
  async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir) {
28156
28610
  const logger = getLogger();
28157
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28158
- const maxPerCycle = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
28159
- const maxTotal = effectiveConfig.quality.autofix?.maxTotalAttempts ?? 10;
28160
- const rethinkAtAttempt = effectiveConfig.quality.autofix?.rethinkAtAttempt ?? 2;
28161
- const urgencyAtAttempt = effectiveConfig.quality.autofix?.urgencyAtAttempt ?? 3;
28611
+ const maxPerCycle = ctx.config.quality.autofix?.maxAttempts ?? 2;
28612
+ const maxTotal = ctx.config.quality.autofix?.maxTotalAttempts ?? 10;
28613
+ const rethinkAtAttempt = ctx.config.quality.autofix?.rethinkAtAttempt ?? 2;
28614
+ const urgencyAtAttempt = ctx.config.quality.autofix?.urgencyAtAttempt ?? 3;
28162
28615
  const consumed = ctx.autofixAttempt ?? 0;
28163
28616
  const failedChecks = collectFailedChecks(ctx);
28164
28617
  if (failedChecks.length === 0) {
28165
28618
  logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
28166
- return false;
28619
+ return { succeeded: false, cost: 0 };
28167
28620
  }
28168
28621
  if (consumed >= maxTotal) {
28169
28622
  logger.warn("autofix", "Global autofix budget exhausted \u2014 escalating", {
@@ -28171,16 +28624,17 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28171
28624
  totalAttempts: consumed,
28172
28625
  maxTotalAttempts: maxTotal
28173
28626
  });
28174
- return false;
28627
+ return { succeeded: false, cost: 0 };
28175
28628
  }
28176
28629
  const remainingBudget = maxTotal - consumed;
28177
28630
  const maxAttempts = Math.min(maxPerCycle, remainingBudget);
28178
- const agentGetFn = ctx.agentGetFn ?? _autofixDeps.getAgent;
28631
+ const agentGetFn = ctx.agentGetFn ?? ((name) => _autofixDeps.getAgent(name, ctx.rootConfig));
28179
28632
  const loopState = {
28180
28633
  attempt: 0,
28181
28634
  failedChecks
28182
28635
  };
28183
- return runSharedRectificationLoop({
28636
+ let autofixCostAccum = 0;
28637
+ const succeeded = await runSharedRectificationLoop({
28184
28638
  stage: "autofix",
28185
28639
  storyId: ctx.story.id,
28186
28640
  maxAttempts,
@@ -28207,29 +28661,30 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28207
28661
  },
28208
28662
  runAttempt: async (attempt, prompt) => {
28209
28663
  ctx.autofixAttempt = consumed + attempt;
28210
- const agent = agentGetFn(ctx.config.autoMode.defaultAgent);
28664
+ const agent = agentGetFn(ctx.rootConfig.autoMode.defaultAgent);
28211
28665
  if (!agent) {
28212
28666
  logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
28213
28667
  throw new Error("AUTOFIX_AGENT_NOT_FOUND");
28214
28668
  }
28215
- const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
28216
- const modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, modelTier, ctx.config.autoMode.defaultAgent);
28217
- const rectificationWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
28669
+ const modelTier = ctx.story.routing?.modelTier ?? ctx.rootConfig.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
28670
+ const modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, modelTier, ctx.rootConfig.autoMode.defaultAgent);
28218
28671
  const result = await agent.run({
28219
28672
  prompt,
28220
- workdir: rectificationWorkdir,
28673
+ workdir: ctx.workdir,
28221
28674
  modelTier,
28222
28675
  modelDef,
28223
28676
  timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
28224
28677
  dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
28225
28678
  pipelineStage: "rectification",
28226
28679
  config: ctx.config,
28680
+ projectDir: ctx.projectDir,
28227
28681
  maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
28228
28682
  storyId: ctx.story.id,
28229
28683
  sessionRole: "implementer"
28230
28684
  });
28685
+ autofixCostAccum += result.estimatedCost ?? 0;
28231
28686
  if (ctx.reviewerSession && result.output) {
28232
- const maxClarifications = effectiveConfig.review?.dialogue?.maxClarificationsPerAttempt ?? 3;
28687
+ const maxClarifications = ctx.config.review?.dialogue?.maxClarificationsPerAttempt ?? 3;
28233
28688
  let clarifyCount = 0;
28234
28689
  const clarifyRegex = new RegExp(CLARIFY_REGEX.source, `${CLARIFY_REGEX.flags}g`);
28235
28690
  let match;
@@ -28312,10 +28767,11 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28312
28767
  }
28313
28768
  throw error48;
28314
28769
  });
28770
+ return { succeeded, cost: autofixCostAccum };
28315
28771
  }
28316
28772
  var CLARIFY_REGEX, autofixStage, _autofixDeps;
28317
28773
  var init_autofix = __esm(() => {
28318
- init_agents();
28774
+ init_registry();
28319
28775
  init_config();
28320
28776
  init_loader();
28321
28777
  init_logger2();
@@ -28329,7 +28785,7 @@ var init_autofix = __esm(() => {
28329
28785
  return false;
28330
28786
  if (ctx.reviewResult.success)
28331
28787
  return false;
28332
- const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
28788
+ const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
28333
28789
  return autofixEnabled;
28334
28790
  },
28335
28791
  skipReason(ctx) {
@@ -28343,16 +28799,14 @@ var init_autofix = __esm(() => {
28343
28799
  if (!reviewResult || reviewResult.success) {
28344
28800
  return { action: "continue" };
28345
28801
  }
28346
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28347
- const lintFixCmd = effectiveConfig.quality.commands.lintFix ?? effectiveConfig.review.commands.lintFix;
28348
- const formatFixCmd = effectiveConfig.quality.commands.formatFix ?? effectiveConfig.review.commands.formatFix;
28349
- const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
28802
+ const lintFixCmd = ctx.config.quality.commands.lintFix ?? ctx.config.review.commands.lintFix;
28803
+ const formatFixCmd = ctx.config.quality.commands.formatFix ?? ctx.config.review.commands.formatFix;
28350
28804
  const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
28351
28805
  const hasLintFailure = failedCheckNames.has("lint");
28352
28806
  logger.info("autofix", "Starting autofix", {
28353
28807
  storyId: ctx.story.id,
28354
28808
  failedChecks: [...failedCheckNames],
28355
- workdir: effectiveWorkdir
28809
+ workdir: ctx.workdir
28356
28810
  });
28357
28811
  if (hasLintFailure && (lintFixCmd || formatFixCmd)) {
28358
28812
  if (lintFixCmd) {
@@ -28360,7 +28814,7 @@ var init_autofix = __esm(() => {
28360
28814
  const lintResult = await _autofixDeps.runQualityCommand({
28361
28815
  commandName: "lintFix",
28362
28816
  command: lintFixCmd,
28363
- workdir: effectiveWorkdir,
28817
+ workdir: ctx.workdir,
28364
28818
  storyId: ctx.story.id
28365
28819
  });
28366
28820
  logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id, command: lintFixCmd });
@@ -28376,7 +28830,7 @@ var init_autofix = __esm(() => {
28376
28830
  const fmtResult = await _autofixDeps.runQualityCommand({
28377
28831
  commandName: "formatFix",
28378
28832
  command: formatFixCmd,
28379
- workdir: effectiveWorkdir,
28833
+ workdir: ctx.workdir,
28380
28834
  storyId: ctx.story.id
28381
28835
  });
28382
28836
  logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, {
@@ -28393,6 +28847,14 @@ var init_autofix = __esm(() => {
28393
28847
  const recheckPassed = await _autofixDeps.recheckReview(ctx);
28394
28848
  pipelineEventBus.emit({ type: "autofix:completed", storyId: ctx.story.id, fixed: recheckPassed });
28395
28849
  if (recheckPassed) {
28850
+ const passedChecks = (ctx.reviewResult?.checks ?? []).filter((c) => c.success).map((c) => c.check);
28851
+ if (passedChecks.length > 0) {
28852
+ ctx.retrySkipChecks = new Set(passedChecks);
28853
+ logger.debug("autofix", "Skipping already-passed checks on retry", {
28854
+ storyId: ctx.story.id,
28855
+ skippedChecks: passedChecks
28856
+ });
28857
+ }
28396
28858
  logger.info("autofix", "Mechanical autofix succeeded \u2014 retrying review", { storyId: ctx.story.id });
28397
28859
  return { action: "retry", fromStage: "review" };
28398
28860
  }
@@ -28400,7 +28862,7 @@ var init_autofix = __esm(() => {
28400
28862
  storyId: ctx.story.id
28401
28863
  });
28402
28864
  }
28403
- const agentFixed = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir);
28865
+ const { succeeded: agentFixed, cost: agentCost } = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, ctx.workdir);
28404
28866
  if (agentFixed) {
28405
28867
  if (ctx.reviewResult)
28406
28868
  ctx.reviewResult = { ...ctx.reviewResult, success: true };
@@ -28413,14 +28875,18 @@ var init_autofix = __esm(() => {
28413
28875
  });
28414
28876
  }
28415
28877
  logger.info("autofix", "Agent rectification succeeded \u2014 retrying review", { storyId: ctx.story.id });
28416
- return { action: "retry", fromStage: "review" };
28878
+ return { action: "retry", fromStage: "review", cost: agentCost };
28417
28879
  }
28418
28880
  logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
28419
- return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
28881
+ return {
28882
+ action: "escalate",
28883
+ reason: "Autofix exhausted: review still failing after fix attempts",
28884
+ cost: agentCost
28885
+ };
28420
28886
  }
28421
28887
  };
28422
28888
  _autofixDeps = {
28423
- getAgent,
28889
+ getAgent: (name, config2) => createAgentRegistry(config2).getAgent(name),
28424
28890
  runQualityCommand,
28425
28891
  recheckReview,
28426
28892
  runAgentRectification: (ctx, lintFixCmd, formatFixCmd, effectiveWorkdir) => runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir),
@@ -28486,10 +28952,10 @@ var init_semantic_verdict = __esm(() => {
28486
28952
 
28487
28953
  // src/execution/progress.ts
28488
28954
  import { appendFile as appendFile2, mkdir } from "fs/promises";
28489
- import { join as join19 } from "path";
28955
+ import { join as join17 } from "path";
28490
28956
  async function appendProgress(featureDir, storyId, status, message) {
28491
28957
  await mkdir(featureDir, { recursive: true });
28492
- const progressPath = join19(featureDir, "progress.txt");
28958
+ const progressPath = join17(featureDir, "progress.txt");
28493
28959
  const timestamp = new Date().toISOString();
28494
28960
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
28495
28961
  `;
@@ -28514,7 +28980,7 @@ var init_completion = __esm(() => {
28514
28980
  const logger = getLogger();
28515
28981
  const isBatch = ctx.stories.length > 1;
28516
28982
  const sessionCost = ctx.agentResult?.estimatedCost || 0;
28517
- const prdPath = ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json`;
28983
+ const prdPath = ctx.prdPath ?? (ctx.featureDir ? `${ctx.featureDir}/prd.json` : `${ctx.workdir}/nax/features/unknown/prd.json`);
28518
28984
  const storyStartTime = ctx.storyStartTime || new Date().toISOString();
28519
28985
  if (isBatch) {
28520
28986
  ctx.storyMetrics = collectBatchMetrics(ctx, storyStartTime);
@@ -28565,7 +29031,7 @@ var init_completion = __esm(() => {
28565
29031
  };
28566
29032
  await _completionDeps.persistSemanticVerdict(ctx.featureDir, ctx.story.id, verdict);
28567
29033
  }
28568
- await savePRD(ctx.prd, prdPath);
29034
+ await _completionDeps.savePRD(ctx.prd, prdPath);
28569
29035
  const updatedCounts = countStories(ctx.prd);
28570
29036
  logger.info("completion", "Progress update", {
28571
29037
  storyId: ctx.story.id,
@@ -28584,7 +29050,8 @@ var init_completion = __esm(() => {
28584
29050
  };
28585
29051
  _completionDeps = {
28586
29052
  checkReviewGate,
28587
- persistSemanticVerdict
29053
+ persistSemanticVerdict,
29054
+ savePRD
28588
29055
  };
28589
29056
  });
28590
29057
 
@@ -28595,7 +29062,7 @@ function estimateTokens(text) {
28595
29062
 
28596
29063
  // src/constitution/loader.ts
28597
29064
  import { existsSync as existsSync17 } from "fs";
28598
- import { join as join20 } from "path";
29065
+ import { join as join18 } from "path";
28599
29066
  function truncateToTokens(text, maxTokens) {
28600
29067
  const maxChars = maxTokens * 3;
28601
29068
  if (text.length <= maxChars) {
@@ -28617,7 +29084,7 @@ async function loadConstitution(projectDir, config2) {
28617
29084
  }
28618
29085
  let combinedContent = "";
28619
29086
  if (!config2.skipGlobal) {
28620
- const globalPath = join20(globalConfigDir(), config2.path);
29087
+ const globalPath = join18(globalConfigDir(), config2.path);
28621
29088
  if (existsSync17(globalPath)) {
28622
29089
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
28623
29090
  const globalFile = Bun.file(validatedPath);
@@ -28627,7 +29094,7 @@ async function loadConstitution(projectDir, config2) {
28627
29094
  }
28628
29095
  }
28629
29096
  }
28630
- const projectPath = join20(projectDir, config2.path);
29097
+ const projectPath = join18(projectDir, config2.path);
28631
29098
  if (existsSync17(projectPath)) {
28632
29099
  const validatedPath = validateFilePath(projectPath, projectDir);
28633
29100
  const projectFile = Bun.file(validatedPath);
@@ -28675,7 +29142,7 @@ var init_constitution = __esm(() => {
28675
29142
  });
28676
29143
 
28677
29144
  // src/pipeline/stages/constitution.ts
28678
- import { dirname as dirname4 } from "path";
29145
+ import { dirname as dirname3 } from "path";
28679
29146
  var constitutionStage;
28680
29147
  var init_constitution2 = __esm(() => {
28681
29148
  init_constitution();
@@ -28685,7 +29152,7 @@ var init_constitution2 = __esm(() => {
28685
29152
  enabled: (ctx) => ctx.config.constitution.enabled,
28686
29153
  async execute(ctx) {
28687
29154
  const logger = getLogger();
28688
- const ngentDir = ctx.featureDir ? dirname4(dirname4(ctx.featureDir)) : `${ctx.workdir}/nax`;
29155
+ const ngentDir = ctx.featureDir ? dirname3(dirname3(ctx.featureDir)) : `${ctx.workdir}/nax`;
28689
29156
  const result = await loadConstitution(ngentDir, ctx.config.constitution);
28690
29157
  if (result) {
28691
29158
  ctx.constitution = result;
@@ -28850,6 +29317,27 @@ function createStoryContext(story, priority) {
28850
29317
  return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
28851
29318
  }
28852
29319
  function createDependencyContext(story, priority) {
29320
+ const content = isCompletedDependency(story) ? formatCompletedDependency(story) : formatFullDependency(story);
29321
+ return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
29322
+ }
29323
+ function isCompletedDependency(story) {
29324
+ return story.status === "passed" || story.status === "decomposed" || story.status === "skipped";
29325
+ }
29326
+ function formatCompletedDependency(story) {
29327
+ const header = `## ${story.id} (${story.status}): ${story.title}`;
29328
+ if (story.diffSummary) {
29329
+ return `${header}
29330
+
29331
+ **Changes made:**
29332
+ \`\`\`
29333
+ ${story.diffSummary}
29334
+ \`\`\``;
29335
+ }
29336
+ return `${header}
29337
+
29338
+ Status: ${story.status} (no diff summary available)`;
29339
+ }
29340
+ function formatFullDependency(story) {
28853
29341
  let content = formatStoryAsText(story);
28854
29342
  if (story.diffSummary) {
28855
29343
  content += `
@@ -28859,7 +29347,7 @@ function createDependencyContext(story, priority) {
28859
29347
  ${story.diffSummary}
28860
29348
  \`\`\``;
28861
29349
  }
28862
- return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
29350
+ return content;
28863
29351
  }
28864
29352
  function createErrorContext(errorMessage2, priority) {
28865
29353
  return { type: "error", content: errorMessage2, priority, tokens: estimateTokens(errorMessage2) };
@@ -29535,7 +30023,8 @@ ${pkgContent.trim()}`;
29535
30023
  if (built.elements.length === 0 && !packageSection) {
29536
30024
  return;
29537
30025
  }
29538
- const baseMarkdown = built.elements.length > 0 ? formatContextAsMarkdown(built) : "";
30026
+ const elementsForMarkdown = built.elements.filter((e) => e.type !== "story");
30027
+ const baseMarkdown = elementsForMarkdown.length > 0 ? formatContextAsMarkdown({ ...built, elements: elementsForMarkdown }) : "";
29539
30028
  const markdown = packageSection ? `${baseMarkdown}${packageSection}` : baseMarkdown;
29540
30029
  return { markdown, builtContext: built };
29541
30030
  } catch (error48) {
@@ -29546,6 +30035,10 @@ ${pkgContent.trim()}`;
29546
30035
  return;
29547
30036
  }
29548
30037
  }
30038
+ function buildStoryContextFullFromCtx(ctx) {
30039
+ const packageWorkdir = ctx.story.workdir ? ctx.workdir : undefined;
30040
+ return buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
30041
+ }
29549
30042
  function getAllReadyStories(prd) {
29550
30043
  const storyIds = new Set(prd.userStories.map((s) => s.id));
29551
30044
  const completedIds = new Set(prd.userStories.filter((s) => s.passes || s.status === "skipped").map((s) => s.id));
@@ -29656,7 +30149,6 @@ var init_helpers = __esm(() => {
29656
30149
  });
29657
30150
 
29658
30151
  // src/pipeline/stages/context.ts
29659
- import { join as join21 } from "path";
29660
30152
  var contextStage;
29661
30153
  var init_context2 = __esm(() => {
29662
30154
  init_helpers();
@@ -29666,8 +30158,7 @@ var init_context2 = __esm(() => {
29666
30158
  enabled: () => true,
29667
30159
  async execute(ctx) {
29668
30160
  const logger = getLogger();
29669
- const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
29670
- const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
30161
+ const result = await buildStoryContextFullFromCtx(ctx);
29671
30162
  if (result) {
29672
30163
  ctx.contextMarkdown = result.markdown;
29673
30164
  ctx.builtContext = result.builtContext;
@@ -29800,14 +30291,14 @@ var init_isolation = __esm(() => {
29800
30291
 
29801
30292
  // src/context/greenfield.ts
29802
30293
  import { readdir } from "fs/promises";
29803
- import { join as join22 } from "path";
30294
+ import { join as join19 } from "path";
29804
30295
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
29805
30296
  const results = [];
29806
30297
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
29807
30298
  try {
29808
30299
  const entries = await readdir(dir, { withFileTypes: true });
29809
30300
  for (const entry of entries) {
29810
- const fullPath = join22(dir, entry.name);
30301
+ const fullPath = join19(dir, entry.name);
29811
30302
  if (entry.isDirectory()) {
29812
30303
  if (ignoreDirs.has(entry.name))
29813
30304
  continue;
@@ -29873,10 +30364,10 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
29873
30364
  const timeoutMs = timeoutSeconds * 1000;
29874
30365
  let timedOut = false;
29875
30366
  const timer = { id: undefined };
29876
- const timeoutPromise = new Promise((resolve7) => {
30367
+ const timeoutPromise = new Promise((resolve8) => {
29877
30368
  timer.id = setTimeout(() => {
29878
30369
  timedOut = true;
29879
- resolve7();
30370
+ resolve8();
29880
30371
  }, timeoutMs);
29881
30372
  });
29882
30373
  const processPromise = proc.exited;
@@ -29885,7 +30376,12 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
29885
30376
  if (timedOut) {
29886
30377
  const pid = proc.pid;
29887
30378
  killProcessGroup(pid, "SIGTERM");
29888
- await Bun.sleep(gracePeriodMs);
30379
+ await Promise.race([
30380
+ proc.exited,
30381
+ new Promise((resolve8) => {
30382
+ setTimeout(resolve8, gracePeriodMs);
30383
+ })
30384
+ ]);
29889
30385
  killProcessGroup(pid, "SIGKILL");
29890
30386
  const [out, err] = await Promise.all([
29891
30387
  raceWithDeadline(stdoutPromise, drainTimeoutMs),
@@ -30147,13 +30643,13 @@ function parseTestOutput(output, exitCode) {
30147
30643
 
30148
30644
  // src/verification/runners.ts
30149
30645
  import { existsSync as existsSync18 } from "fs";
30150
- import { join as join23 } from "path";
30646
+ import { join as join20 } from "path";
30151
30647
  async function verifyAssets(workingDirectory, expectedFiles) {
30152
30648
  if (!expectedFiles || expectedFiles.length === 0)
30153
30649
  return { success: true, missingFiles: [] };
30154
30650
  const missingFiles = [];
30155
30651
  for (const file3 of expectedFiles) {
30156
- if (!existsSync18(join23(workingDirectory, file3)))
30652
+ if (!existsSync18(join20(workingDirectory, file3)))
30157
30653
  missingFiles.push(file3);
30158
30654
  }
30159
30655
  if (missingFiles.length > 0) {
@@ -30478,10 +30974,10 @@ var init_prompts = __esm(() => {
30478
30974
  });
30479
30975
 
30480
30976
  // src/tdd/rectification-gate.ts
30481
- async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
30977
+ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir) {
30482
30978
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
30483
30979
  if (!rectificationEnabled)
30484
- return false;
30980
+ return { passed: false, cost: 0 };
30485
30981
  const rectificationConfig = config2.execution.rectification;
30486
30982
  const testCmd = config2.quality?.commands?.test ?? "bun test";
30487
30983
  const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
@@ -30496,7 +30992,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
30496
30992
  if (!fullSuitePassed && fullSuiteResult.output) {
30497
30993
  const testSummary = _rectificationGateDeps.parseBunTestOutput(fullSuiteResult.output);
30498
30994
  if (testSummary.failed > 0) {
30499
- return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
30995
+ return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
30500
30996
  }
30501
30997
  if (testSummary.passed > 0) {
30502
30998
  logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
@@ -30504,7 +31000,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
30504
31000
  exitCode: fullSuiteResult.exitCode,
30505
31001
  passedTests: testSummary.passed
30506
31002
  });
30507
- return true;
31003
+ return { passed: true, cost: 0 };
30508
31004
  }
30509
31005
  logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
30510
31006
  storyId: story.id,
@@ -30512,19 +31008,19 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
30512
31008
  outputLength: fullSuiteResult.output.length,
30513
31009
  outputTail: fullSuiteResult.output.slice(-200)
30514
31010
  });
30515
- return false;
31011
+ return { passed: false, cost: 0 };
30516
31012
  }
30517
31013
  if (fullSuitePassed) {
30518
31014
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
30519
- return true;
31015
+ return { passed: true, cost: 0 };
30520
31016
  }
30521
31017
  logger.warn("tdd", "Full suite gate execution failed (no output)", {
30522
31018
  storyId: story.id,
30523
31019
  exitCode: fullSuiteResult.exitCode
30524
31020
  });
30525
- return false;
31021
+ return { passed: false, cost: 0 };
30526
31022
  }
30527
- async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
31023
+ async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir) {
30528
31024
  const rectificationState = {
30529
31025
  attempt: 0,
30530
31026
  initialFailures: testSummary.failed,
@@ -30544,6 +31040,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30544
31040
  ...rectificationState,
30545
31041
  isolationPassed: true
30546
31042
  };
31043
+ let gateCostAccum = 0;
30547
31044
  const fixed = await runSharedRectificationLoop({
30548
31045
  stage: "tdd",
30549
31046
  storyId: story.id,
@@ -30575,6 +31072,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30575
31072
  dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
30576
31073
  pipelineStage: "rectification",
30577
31074
  config: config2,
31075
+ projectDir,
30578
31076
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
30579
31077
  featureName,
30580
31078
  storyId: story.id,
@@ -30585,6 +31083,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30585
31083
  if (!rectifyResult.success && rectifyResult.pid) {
30586
31084
  await cleanupProcessTree(rectifyResult.pid);
30587
31085
  }
31086
+ gateCostAccum += rectifyResult.estimatedCost ?? 0;
30588
31087
  if (rectifyResult.success) {
30589
31088
  logger.info("tdd", "Rectification agent session complete", {
30590
31089
  storyId: story.id,
@@ -30645,7 +31144,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30645
31144
  }
30646
31145
  });
30647
31146
  if (fixed) {
30648
- return true;
31147
+ return { passed: true, cost: gateCostAccum };
30649
31148
  }
30650
31149
  const finalFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
30651
31150
  cwd: workdir
@@ -30657,10 +31156,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30657
31156
  attempts: rectificationState.attempt,
30658
31157
  remainingFailures: rectificationState.currentFailures
30659
31158
  });
30660
- return false;
31159
+ return { passed: false, cost: gateCostAccum };
30661
31160
  }
30662
31161
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
30663
- return true;
31162
+ return { passed: true, cost: gateCostAccum };
30664
31163
  }
30665
31164
  var _rectificationGateDeps;
30666
31165
  var init_rectification_gate = __esm(() => {
@@ -31064,36 +31563,10 @@ Set \`approved: false\` when ANY of these conditions are true:
31064
31563
  - Critical acceptance criteria are not met
31065
31564
  - Code quality is poor (security issues, severe bugs, etc.)
31066
31565
 
31067
- **Full JSON schema example** (fill in all fields with real values):
31566
+ **JSON schema** (fill in all fields with real values):
31068
31567
 
31069
31568
  \`\`\`json
31070
- {
31071
- "version": 1,
31072
- "approved": true,
31073
- "tests": {
31074
- "allPassing": true,
31075
- "passCount": 42,
31076
- "failCount": 0
31077
- },
31078
- "testModifications": {
31079
- "detected": false,
31080
- "files": [],
31081
- "legitimate": true,
31082
- "reasoning": "No test files were modified by the implementer"
31083
- },
31084
- "acceptanceCriteria": {
31085
- "allMet": true,
31086
- "criteria": [
31087
- { "criterion": "Example criterion", "met": true }
31088
- ]
31089
- },
31090
- "quality": {
31091
- "rating": "good",
31092
- "issues": []
31093
- },
31094
- "fixes": [],
31095
- "reasoning": "All tests pass, implementation is clean, all acceptance criteria are met."
31096
- }
31569
+ {"version":1,"approved":true,"tests":{"allPassing":true,"passCount":42,"failCount":0},"testModifications":{"detected":false,"files":[],"legitimate":true,"reasoning":"..."},"acceptanceCriteria":{"allMet":true,"criteria":[{"criterion":"...","met":true}]},"quality":{"rating":"good","issues":[]},"fixes":[],"reasoning":"..."}
31097
31570
  \`\`\`
31098
31571
 
31099
31572
  **Field notes:**
@@ -31110,13 +31583,13 @@ var exports_loader = {};
31110
31583
  __export(exports_loader, {
31111
31584
  loadOverride: () => loadOverride
31112
31585
  });
31113
- import { join as join24 } from "path";
31586
+ import { join as join21 } from "path";
31114
31587
  async function loadOverride(role, workdir, config2) {
31115
31588
  const overridePath = config2.prompts?.overrides?.[role];
31116
31589
  if (!overridePath) {
31117
31590
  return null;
31118
31591
  }
31119
- const absolutePath = join24(workdir, overridePath);
31592
+ const absolutePath = join21(workdir, overridePath);
31120
31593
  const file3 = Bun.file(absolutePath);
31121
31594
  if (!await file3.exists()) {
31122
31595
  return null;
@@ -31326,7 +31799,7 @@ async function rollbackToRef(workdir, ref) {
31326
31799
  }
31327
31800
  logger.info("tdd", "Successfully rolled back git changes", { ref });
31328
31801
  }
31329
- async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge) {
31802
+ async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge, projectDir) {
31330
31803
  const startTime = Date.now();
31331
31804
  let prompt;
31332
31805
  if (_sessionRunnerDeps.buildPrompt) {
@@ -31356,6 +31829,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
31356
31829
  dangerouslySkipPermissions: resolvePermissions(config2, "run").skipPermissions,
31357
31830
  pipelineStage: "run",
31358
31831
  config: config2,
31832
+ projectDir,
31359
31833
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
31360
31834
  featureName,
31361
31835
  storyId: story.id,
@@ -31727,7 +32201,8 @@ async function runThreeSessionTdd(options) {
31727
32201
  dryRun = false,
31728
32202
  lite = false,
31729
32203
  _recursionDepth = 0,
31730
- interactionChain
32204
+ interactionChain,
32205
+ projectDir
31731
32206
  } = options;
31732
32207
  const logger = getLogger();
31733
32208
  const MAX_RECURSION_DEPTH = 2;
@@ -31786,7 +32261,7 @@ async function runThreeSessionTdd(options) {
31786
32261
  let session1;
31787
32262
  if (!isRetry) {
31788
32263
  const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
31789
- session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
32264
+ session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }), projectDir);
31790
32265
  sessions.push(session1);
31791
32266
  }
31792
32267
  if (session1 && !session1.success) {
@@ -31848,7 +32323,7 @@ async function runThreeSessionTdd(options) {
31848
32323
  });
31849
32324
  const session2Ref = await captureGitRef(workdir) ?? "HEAD";
31850
32325
  const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
31851
- const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }));
32326
+ const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution, featureName, buildInteractionBridge(interactionChain, { featureName, storyId: story.id, stage: "execution" }), projectDir);
31852
32327
  sessions.push(session2);
31853
32328
  if (!session2.success) {
31854
32329
  needsHumanReview = true;
@@ -31864,10 +32339,10 @@ async function runThreeSessionTdd(options) {
31864
32339
  lite
31865
32340
  };
31866
32341
  }
31867
- const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
32342
+ const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir);
31868
32343
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
31869
32344
  const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
31870
- const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
32345
+ const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName, undefined, projectDir);
31871
32346
  sessions.push(session3);
31872
32347
  const verdict = await readVerdict(workdir);
31873
32348
  await cleanupVerdict(workdir);
@@ -31927,7 +32402,7 @@ async function runThreeSessionTdd(options) {
31927
32402
  needsHumanReview = false;
31928
32403
  }
31929
32404
  }
31930
- const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0);
32405
+ const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0) + fullSuiteGateCost;
31931
32406
  logger.info("tdd", allSuccessful ? "[OK] Three-session TDD complete" : "[WARN] Three-session TDD needs review", {
31932
32407
  storyId: story.id,
31933
32408
  success: allSuccessful,
@@ -31963,6 +32438,22 @@ async function runThreeSessionTdd(options) {
31963
32438
  fullSuiteGatePassed
31964
32439
  };
31965
32440
  }
32441
+ function runThreeSessionTddFromCtx(ctx, opts) {
32442
+ return runThreeSessionTdd({
32443
+ agent: opts.agent,
32444
+ story: ctx.story,
32445
+ config: ctx.config,
32446
+ workdir: ctx.workdir,
32447
+ modelTier: ctx.routing.modelTier,
32448
+ featureName: ctx.prd.feature,
32449
+ contextMarkdown: ctx.contextMarkdown,
32450
+ constitution: ctx.constitution?.content,
32451
+ dryRun: opts.dryRun ?? false,
32452
+ lite: opts.lite ?? false,
32453
+ interactionChain: ctx.interaction,
32454
+ projectDir: ctx.projectDir
32455
+ });
32456
+ }
31966
32457
  var init_orchestrator2 = __esm(() => {
31967
32458
  init_config();
31968
32459
  init_greenfield();
@@ -31985,17 +32476,6 @@ var init_tdd = __esm(() => {
31985
32476
  });
31986
32477
 
31987
32478
  // src/pipeline/stages/execution.ts
31988
- import { existsSync as existsSync19 } from "fs";
31989
- import { join as join25 } from "path";
31990
- function resolveStoryWorkdir(repoRoot, storyWorkdir) {
31991
- if (!storyWorkdir)
31992
- return repoRoot;
31993
- const resolved = join25(repoRoot, storyWorkdir);
31994
- if (!existsSync19(resolved)) {
31995
- throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
31996
- }
31997
- return resolved;
31998
- }
31999
32479
  function isAmbiguousOutput(output) {
32000
32480
  if (!output)
32001
32481
  return false;
@@ -32042,11 +32522,11 @@ var init_execution2 = __esm(() => {
32042
32522
  enabled: () => true,
32043
32523
  async execute(ctx) {
32044
32524
  const logger = getLogger();
32045
- const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.config.autoMode.defaultAgent);
32525
+ const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
32046
32526
  if (!agent) {
32047
32527
  return {
32048
32528
  action: "fail",
32049
- reason: `Agent "${ctx.config.autoMode.defaultAgent}" not found`
32529
+ reason: `Agent "${ctx.rootConfig.autoMode.defaultAgent}" not found`
32050
32530
  };
32051
32531
  }
32052
32532
  const isTddStrategy = ctx.routing.testStrategy === "three-session-tdd" || ctx.routing.testStrategy === "three-session-tdd-lite";
@@ -32056,20 +32536,7 @@ var init_execution2 = __esm(() => {
32056
32536
  storyId: ctx.story.id,
32057
32537
  lite: isLiteMode
32058
32538
  });
32059
- const effectiveWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
32060
- const tddResult = await runThreeSessionTdd({
32061
- agent,
32062
- story: ctx.story,
32063
- config: ctx.config,
32064
- workdir: effectiveWorkdir,
32065
- modelTier: ctx.routing.modelTier,
32066
- featureName: ctx.prd.feature,
32067
- contextMarkdown: ctx.contextMarkdown,
32068
- constitution: ctx.constitution?.content,
32069
- dryRun: false,
32070
- lite: isLiteMode,
32071
- interactionChain: ctx.interaction
32072
- });
32539
+ const tddResult = await runThreeSessionTddFromCtx(ctx, { agent, dryRun: false, lite: isLiteMode });
32073
32540
  ctx.agentResult = {
32074
32541
  success: tddResult.success,
32075
32542
  estimatedCost: tddResult.totalCost,
@@ -32132,17 +32599,17 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
32132
32599
  supportedTiers: agent.capabilities.supportedTiers
32133
32600
  });
32134
32601
  }
32135
- const storyWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
32136
32602
  const keepSessionOpen = !!(ctx.config.review?.enabled === true || ctx.config.execution.rectification?.enabled === true);
32137
32603
  const result = await agent.run({
32138
32604
  prompt: ctx.prompt,
32139
- workdir: storyWorkdir,
32605
+ workdir: ctx.workdir,
32140
32606
  modelTier: ctx.routing.modelTier,
32141
- modelDef: resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, ctx.routing.modelTier, ctx.config.autoMode.defaultAgent),
32607
+ modelDef: resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.routing.modelTier, ctx.rootConfig.autoMode.defaultAgent),
32142
32608
  timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
32143
32609
  dangerouslySkipPermissions: resolvePermissions(ctx.config, "run").skipPermissions,
32144
32610
  pipelineStage: "run",
32145
32611
  config: ctx.config,
32612
+ projectDir: ctx.projectDir,
32146
32613
  maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
32147
32614
  pidRegistry: ctx.pidRegistry,
32148
32615
  featureName: ctx.prd.feature,
@@ -32156,7 +32623,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
32156
32623
  })
32157
32624
  });
32158
32625
  ctx.agentResult = result;
32159
- await autoCommitIfDirty(storyWorkdir, "execution", "single-session", ctx.story.id);
32626
+ await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
32160
32627
  const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
32161
32628
  if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
32162
32629
  const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
@@ -32197,8 +32664,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
32197
32664
  detectMergeConflict,
32198
32665
  checkMergeConflict,
32199
32666
  isAmbiguousOutput,
32200
- checkStoryAmbiguity,
32201
- resolveStoryWorkdir
32667
+ checkStoryAmbiguity
32202
32668
  };
32203
32669
  });
32204
32670
 
@@ -32475,17 +32941,16 @@ var init_prompt = __esm(() => {
32475
32941
  async execute(ctx) {
32476
32942
  const logger = getLogger();
32477
32943
  const isBatch = ctx.stories.length > 1;
32478
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
32479
32944
  const acceptanceEntries = await _loadAcceptanceEntries(ctx, logger);
32480
32945
  let prompt;
32481
32946
  if (isBatch) {
32482
- const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test).hermeticConfig(effectiveConfig.quality?.testing);
32947
+ const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).hermeticConfig(ctx.config.quality?.testing);
32483
32948
  if (acceptanceEntries.length > 0)
32484
32949
  builder.acceptanceContext(acceptanceEntries);
32485
32950
  prompt = await builder.build();
32486
32951
  } else {
32487
32952
  const role = ctx.routing.testStrategy === "no-test" ? "no-test" : "tdd-simple";
32488
- const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test).hermeticConfig(effectiveConfig.quality?.testing).noTestJustification(ctx.story.routing?.noTestJustification);
32953
+ const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).hermeticConfig(ctx.config.quality?.testing).noTestJustification(ctx.story.routing?.noTestJustification);
32489
32954
  if (acceptanceEntries.length > 0)
32490
32955
  builder.acceptanceContext(acceptanceEntries);
32491
32956
  prompt = await builder.build();
@@ -32718,7 +33183,18 @@ async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
32718
33183
  return { output, totalCostUsd };
32719
33184
  }
32720
33185
  async function runRectificationLoop2(opts) {
32721
- const { config: config2, workdir, story, testCommand, timeoutSeconds, testOutput, promptPrefix, featureName, agentGetFn } = opts;
33186
+ const {
33187
+ config: config2,
33188
+ workdir,
33189
+ story,
33190
+ testCommand,
33191
+ timeoutSeconds,
33192
+ testOutput,
33193
+ promptPrefix,
33194
+ featureName,
33195
+ agentGetFn,
33196
+ projectDir
33197
+ } = opts;
32722
33198
  const logger = getSafeLogger();
32723
33199
  const rectificationConfig = config2.execution.rectification;
32724
33200
  const testSummary = parseBunTestOutput(testOutput);
@@ -32729,7 +33205,8 @@ async function runRectificationLoop2(opts) {
32729
33205
  currentFailures: testSummary.failed,
32730
33206
  lastExitCode: 1
32731
33207
  };
32732
- return runSharedRectificationLoop({
33208
+ let costAccum = 0;
33209
+ const succeeded = await runSharedRectificationLoop({
32733
33210
  stage: "rectification",
32734
33211
  storyId: story.id,
32735
33212
  maxAttempts: rectificationConfig.maxRetries,
@@ -32810,11 +33287,13 @@ ${rectificationPrompt}`;
32810
33287
  dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
32811
33288
  pipelineStage: "rectification",
32812
33289
  config: config2,
33290
+ projectDir,
32813
33291
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
32814
33292
  featureName,
32815
33293
  storyId: story.id,
32816
33294
  sessionRole: "implementer"
32817
33295
  });
33296
+ costAccum += agentResult.estimatedCost ?? 0;
32818
33297
  if (agentResult.success) {
32819
33298
  logger?.info("rectification", `Agent ${label} session complete`, {
32820
33299
  storyId: story.id,
@@ -32934,11 +33413,13 @@ ${escalationPrompt}`;
32934
33413
  dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
32935
33414
  pipelineStage: "rectification",
32936
33415
  config: config2,
33416
+ projectDir,
32937
33417
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
32938
33418
  featureName,
32939
33419
  storyId: story.id,
32940
33420
  sessionRole: "implementer"
32941
33421
  });
33422
+ costAccum += escalationRunResult.estimatedCost ?? 0;
32942
33423
  logger?.info("rectification", "escalated rectification attempt cost", {
32943
33424
  storyId: story.id,
32944
33425
  escalatedTier,
@@ -32975,6 +33456,21 @@ ${escalationPrompt}`;
32975
33456
  }
32976
33457
  throw error48;
32977
33458
  });
33459
+ return { succeeded, cost: costAccum };
33460
+ }
33461
+ function runRectificationLoopFromCtx(ctx, opts) {
33462
+ return runRectificationLoop2({
33463
+ config: ctx.config,
33464
+ workdir: ctx.workdir,
33465
+ story: ctx.story,
33466
+ testCommand: opts.testCommand,
33467
+ timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
33468
+ testOutput: opts.testOutput,
33469
+ promptPrefix: opts.promptPrefix,
33470
+ featureName: ctx.prd.feature,
33471
+ agentGetFn: ctx.agentGetFn,
33472
+ projectDir: ctx.projectDir
33473
+ });
32978
33474
  }
32979
33475
  var _rectificationDeps;
32980
33476
  var init_rectification_loop = __esm(() => {
@@ -33036,36 +33532,28 @@ var init_rectify = __esm(() => {
33036
33532
  attempt: rectifyAttempt,
33037
33533
  testOutput
33038
33534
  });
33039
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
33040
- const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
33041
- const fixed = await _rectifyDeps.runRectificationLoop({
33042
- config: ctx.config,
33043
- workdir: ctx.workdir,
33044
- story: ctx.story,
33045
- testCommand,
33046
- timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
33047
- testOutput,
33048
- agentGetFn: ctx.agentGetFn
33049
- });
33535
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
33536
+ const { succeeded, cost } = await _rectifyDeps.runRectificationLoop(ctx, { testCommand, testOutput });
33050
33537
  pipelineEventBus.emit({
33051
33538
  type: "rectify:completed",
33052
33539
  storyId: ctx.story.id,
33053
33540
  attempt: rectifyAttempt,
33054
- fixed
33541
+ fixed: succeeded
33055
33542
  });
33056
- if (fixed) {
33543
+ if (succeeded) {
33057
33544
  logger.info("rectify", "Rectification succeeded \u2014 retrying verify", { storyId: ctx.story.id });
33058
33545
  ctx.verifyResult = undefined;
33059
- return { action: "retry", fromStage: "verify" };
33546
+ return { action: "retry", fromStage: "verify", cost };
33060
33547
  }
33061
33548
  logger.warn("rectify", "Rectification exhausted \u2014 escalating", { storyId: ctx.story.id });
33062
33549
  return {
33063
33550
  action: "escalate",
33064
- reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`
33551
+ reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`,
33552
+ cost
33065
33553
  };
33066
33554
  }
33067
33555
  };
33068
- _rectifyDeps = { runRectificationLoop: runRectificationLoop2 };
33556
+ _rectifyDeps = { runRectificationLoop: runRectificationLoopFromCtx };
33069
33557
  });
33070
33558
 
33071
33559
  // src/verification/orchestrator-types.ts
@@ -33172,16 +33660,16 @@ class AcceptanceStrategy {
33172
33660
  }, timeoutMs);
33173
33661
  const exitCode = await Promise.race([
33174
33662
  proc.exited,
33175
- new Promise((resolve7) => setTimeout(() => resolve7(124), timeoutMs + 6000))
33663
+ new Promise((resolve8) => setTimeout(() => resolve8(124), timeoutMs + 6000))
33176
33664
  ]);
33177
33665
  clearTimeout(timeoutId);
33178
33666
  const stdout = await Promise.race([
33179
33667
  new Response(proc.stdout).text(),
33180
- new Promise((resolve7) => setTimeout(() => resolve7(""), 3000))
33668
+ new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
33181
33669
  ]);
33182
33670
  const stderr = await Promise.race([
33183
33671
  new Response(proc.stderr).text(),
33184
- new Promise((resolve7) => setTimeout(() => resolve7(""), 3000))
33672
+ new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
33185
33673
  ]);
33186
33674
  const durationMs = Date.now() - start;
33187
33675
  if (timedOut || exitCode === 124) {
@@ -33591,34 +34079,31 @@ var init_regression2 = __esm(() => {
33591
34079
  regressionStage = {
33592
34080
  name: "regression",
33593
34081
  enabled(ctx) {
33594
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
33595
- const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
34082
+ const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
33596
34083
  if (mode !== "per-story")
33597
34084
  return false;
33598
34085
  if (ctx.verifyResult && !ctx.verifyResult.success)
33599
34086
  return false;
33600
- const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
34087
+ const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
33601
34088
  return gateEnabled;
33602
34089
  },
33603
34090
  skipReason(ctx) {
33604
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
33605
- const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
34091
+ const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
33606
34092
  if (mode !== "per-story")
33607
34093
  return `not needed (regression mode is '${mode}', not 'per-story')`;
33608
34094
  return "disabled (regression gate not enabled in config)";
33609
34095
  },
33610
34096
  async execute(ctx) {
33611
34097
  const logger = getLogger();
33612
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
33613
- const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
33614
- const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
34098
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
34099
+ const timeoutSeconds = ctx.config.execution.regressionGate?.timeoutSeconds ?? 120;
33615
34100
  logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
33616
34101
  const verifyCtx = {
33617
34102
  workdir: ctx.workdir,
33618
34103
  testCommand,
33619
34104
  timeoutSeconds,
33620
34105
  storyId: ctx.story.id,
33621
- acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
34106
+ acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
33622
34107
  config: ctx.config
33623
34108
  };
33624
34109
  const result = await _regressionStageDeps.verifyRegression(verifyCtx);
@@ -34225,7 +34710,6 @@ var init_routing = __esm(() => {
34225
34710
  });
34226
34711
 
34227
34712
  // src/pipeline/stages/routing.ts
34228
- import { join as join26 } from "path";
34229
34713
  var routingStage, _routingDeps;
34230
34714
  var init_routing2 = __esm(() => {
34231
34715
  init_greenfield();
@@ -34238,13 +34722,12 @@ var init_routing2 = __esm(() => {
34238
34722
  enabled: () => true,
34239
34723
  async execute(ctx) {
34240
34724
  const logger = getLogger();
34241
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
34242
- const agentName = effectiveConfig.execution?.agent ?? "claude";
34725
+ const agentName = ctx.config.execution?.agent ?? "claude";
34243
34726
  const adapter = ctx.agentGetFn ? ctx.agentGetFn(agentName) : undefined;
34244
34727
  if (ctx.story.id === ctx.stories[0]?.id) {
34245
34728
  _routingDeps.clearCache();
34246
34729
  }
34247
- const decision = await _routingDeps.resolveRouting(ctx.story, effectiveConfig, ctx.plugins, adapter);
34730
+ const decision = await _routingDeps.resolveRouting(ctx.story, ctx.config, ctx.plugins, adapter);
34248
34731
  const TIER_RANK = { fast: 0, balanced: 1, powerful: 2 };
34249
34732
  const derivedTier = decision.modelTier;
34250
34733
  const previousTier = ctx.story.routing?.modelTier;
@@ -34262,9 +34745,9 @@ var init_routing2 = __esm(() => {
34262
34745
  if (ctx.prdPath) {
34263
34746
  await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
34264
34747
  }
34265
- const greenfieldDetectionEnabled = effectiveConfig.tdd.greenfieldDetection ?? true;
34748
+ const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
34266
34749
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
34267
- const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
34750
+ const greenfieldScanDir = ctx.workdir;
34268
34751
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
34269
34752
  if (isGreenfield) {
34270
34753
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -34315,7 +34798,7 @@ var init_crash_detector = __esm(() => {
34315
34798
  });
34316
34799
 
34317
34800
  // src/pipeline/stages/verify.ts
34318
- import { join as join27 } from "path";
34801
+ import { join as join22 } from "path";
34319
34802
  function coerceSmartTestRunner(val) {
34320
34803
  if (val === undefined || val === true)
34321
34804
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -34331,7 +34814,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
34331
34814
  }
34332
34815
  async function readPackageName(dir) {
34333
34816
  try {
34334
- const content = await Bun.file(join27(dir, "package.json")).json();
34817
+ const content = await Bun.file(join22(dir, "package.json")).json();
34335
34818
  return typeof content.name === "string" ? content.name : null;
34336
34819
  } catch {
34337
34820
  return null;
@@ -34364,26 +34847,24 @@ var init_verify = __esm(() => {
34364
34847
  skipReason: () => "not needed (full-suite gate already passed)",
34365
34848
  async execute(ctx) {
34366
34849
  const logger = getLogger();
34367
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
34368
- if (!effectiveConfig.quality.requireTests) {
34850
+ if (!ctx.config.quality.requireTests) {
34369
34851
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
34370
34852
  return { action: "continue" };
34371
34853
  }
34372
- const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test;
34373
- const testScopedTemplate = effectiveConfig.quality.commands.testScoped;
34854
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test;
34855
+ const testScopedTemplate = ctx.config.quality.commands.testScoped;
34374
34856
  if (!testCommand) {
34375
34857
  logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
34376
34858
  return { action: "continue" };
34377
34859
  }
34378
34860
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
34379
- const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
34380
34861
  let effectiveCommand = testCommand;
34381
34862
  let isFullSuite = true;
34382
- const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
34383
- const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
34863
+ const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
34864
+ const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
34384
34865
  let resolvedTestScopedTemplate = testScopedTemplate;
34385
34866
  if (testScopedTemplate && ctx.story.workdir) {
34386
- const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
34867
+ const resolved = await resolvePackageTemplate(testScopedTemplate, ctx.workdir);
34387
34868
  resolvedTestScopedTemplate = resolved ?? undefined;
34388
34869
  }
34389
34870
  const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
@@ -34402,8 +34883,8 @@ var init_verify = __esm(() => {
34402
34883
  });
34403
34884
  }
34404
34885
  } else if (smartRunnerConfig.enabled) {
34405
- const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(effectiveWorkdir, ctx.storyGitRef, ctx.story.workdir);
34406
- const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, effectiveWorkdir);
34886
+ const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(ctx.workdir, ctx.storyGitRef, ctx.story.workdir);
34887
+ const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, ctx.workdir);
34407
34888
  if (pass1Files.length > 0) {
34408
34889
  logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
34409
34890
  storyId: ctx.story.id
@@ -34411,7 +34892,7 @@ var init_verify = __esm(() => {
34411
34892
  effectiveCommand = buildScopedCommand2(pass1Files, testCommand, resolvedTestScopedTemplate);
34412
34893
  isFullSuite = false;
34413
34894
  } else if (smartRunnerConfig.fallback === "import-grep") {
34414
- const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, effectiveWorkdir, smartRunnerConfig.testFilePatterns);
34895
+ const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, ctx.workdir, smartRunnerConfig.testFilePatterns);
34415
34896
  if (pass2Files.length > 0) {
34416
34897
  logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
34417
34898
  storyId: ctx.story.id
@@ -34437,10 +34918,10 @@ var init_verify = __esm(() => {
34437
34918
  command: effectiveCommand
34438
34919
  });
34439
34920
  const result = await _verifyDeps.regression({
34440
- workdir: effectiveWorkdir,
34921
+ workdir: ctx.workdir,
34441
34922
  command: effectiveCommand,
34442
- timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
34443
- acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
34923
+ timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
34924
+ acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
34444
34925
  });
34445
34926
  ctx.verifyResult = {
34446
34927
  success: result.success,
@@ -34457,7 +34938,7 @@ var init_verify = __esm(() => {
34457
34938
  };
34458
34939
  if (!result.success) {
34459
34940
  if (result.status === "TIMEOUT") {
34460
- const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
34941
+ const timeout = ctx.config.execution.verificationTimeoutSeconds;
34461
34942
  logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
34462
34943
  exitCode: result.status,
34463
34944
  storyId: ctx.story.id,
@@ -34475,7 +34956,7 @@ var init_verify = __esm(() => {
34475
34956
  if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
34476
34957
  return {
34477
34958
  action: "escalate",
34478
- reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${effectiveConfig.execution.verificationTimeoutSeconds}s (not a code failure)` : `Tests failed with runtime crash (exit code ${result.status ?? "non-zero"})`
34959
+ reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${ctx.config.execution.verificationTimeoutSeconds}s (not a code failure)` : `Tests failed with runtime crash (exit code ${result.status ?? "non-zero"})`
34479
34960
  };
34480
34961
  }
34481
34962
  return { action: "continue" };
@@ -34593,9 +35074,9 @@ __export(exports_init_context, {
34593
35074
  generateContextTemplate: () => generateContextTemplate,
34594
35075
  _initContextDeps: () => _initContextDeps
34595
35076
  });
34596
- import { existsSync as existsSync22 } from "fs";
35077
+ import { existsSync as existsSync21 } from "fs";
34597
35078
  import { mkdir as mkdir2 } from "fs/promises";
34598
- import { basename as basename4, join as join31 } from "path";
35079
+ import { basename as basename3, join as join26 } from "path";
34599
35080
  async function findFiles(dir, maxFiles = 200) {
34600
35081
  try {
34601
35082
  const proc = Bun.spawnSync([
@@ -34623,8 +35104,8 @@ async function findFiles(dir, maxFiles = 200) {
34623
35104
  return [];
34624
35105
  }
34625
35106
  async function readPackageManifest(projectRoot) {
34626
- const packageJsonPath = join31(projectRoot, "package.json");
34627
- if (!existsSync22(packageJsonPath)) {
35107
+ const packageJsonPath = join26(projectRoot, "package.json");
35108
+ if (!existsSync21(packageJsonPath)) {
34628
35109
  return null;
34629
35110
  }
34630
35111
  try {
@@ -34641,8 +35122,8 @@ async function readPackageManifest(projectRoot) {
34641
35122
  }
34642
35123
  }
34643
35124
  async function readReadmeSnippet(projectRoot) {
34644
- const readmePath = join31(projectRoot, "README.md");
34645
- if (!existsSync22(readmePath)) {
35125
+ const readmePath = join26(projectRoot, "README.md");
35126
+ if (!existsSync21(readmePath)) {
34646
35127
  return null;
34647
35128
  }
34648
35129
  try {
@@ -34659,8 +35140,8 @@ async function detectEntryPoints(projectRoot) {
34659
35140
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
34660
35141
  const found = [];
34661
35142
  for (const candidate of candidates) {
34662
- const path13 = join31(projectRoot, candidate);
34663
- if (existsSync22(path13)) {
35143
+ const path13 = join26(projectRoot, candidate);
35144
+ if (existsSync21(path13)) {
34664
35145
  found.push(candidate);
34665
35146
  }
34666
35147
  }
@@ -34670,8 +35151,8 @@ async function detectConfigFiles(projectRoot) {
34670
35151
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
34671
35152
  const found = [];
34672
35153
  for (const candidate of candidates) {
34673
- const path13 = join31(projectRoot, candidate);
34674
- if (existsSync22(path13)) {
35154
+ const path13 = join26(projectRoot, candidate);
35155
+ if (existsSync21(path13)) {
34675
35156
  found.push(candidate);
34676
35157
  }
34677
35158
  }
@@ -34683,7 +35164,7 @@ async function scanProject(projectRoot) {
34683
35164
  const readmeSnippet = await readReadmeSnippet(projectRoot);
34684
35165
  const entryPoints = await detectEntryPoints(projectRoot);
34685
35166
  const configFiles = await detectConfigFiles(projectRoot);
34686
- const projectName = packageManifest?.name || basename4(projectRoot);
35167
+ const projectName = packageManifest?.name || basename3(projectRoot);
34687
35168
  return {
34688
35169
  projectName,
34689
35170
  fileTree,
@@ -34831,13 +35312,13 @@ function generatePackageContextTemplate(packagePath) {
34831
35312
  }
34832
35313
  async function initPackage(repoRoot, packagePath, force = false) {
34833
35314
  const logger = getLogger();
34834
- const naxDir = join31(repoRoot, ".nax", "mono", packagePath);
34835
- const contextPath = join31(naxDir, "context.md");
34836
- if (existsSync22(contextPath) && !force) {
35315
+ const naxDir = join26(repoRoot, ".nax", "mono", packagePath);
35316
+ const contextPath = join26(naxDir, "context.md");
35317
+ if (existsSync21(contextPath) && !force) {
34837
35318
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
34838
35319
  return;
34839
35320
  }
34840
- if (!existsSync22(naxDir)) {
35321
+ if (!existsSync21(naxDir)) {
34841
35322
  await mkdir2(naxDir, { recursive: true });
34842
35323
  }
34843
35324
  const content = generatePackageContextTemplate(packagePath);
@@ -34846,13 +35327,13 @@ async function initPackage(repoRoot, packagePath, force = false) {
34846
35327
  }
34847
35328
  async function initContext(projectRoot, options = {}) {
34848
35329
  const logger = getLogger();
34849
- const naxDir = join31(projectRoot, ".nax");
34850
- const contextPath = join31(naxDir, "context.md");
34851
- if (existsSync22(contextPath) && !options.force) {
35330
+ const naxDir = join26(projectRoot, ".nax");
35331
+ const contextPath = join26(naxDir, "context.md");
35332
+ if (existsSync21(contextPath) && !options.force) {
34852
35333
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
34853
35334
  return;
34854
35335
  }
34855
- if (!existsSync22(naxDir)) {
35336
+ if (!existsSync21(naxDir)) {
34856
35337
  await mkdir2(naxDir, { recursive: true });
34857
35338
  }
34858
35339
  const scan = await scanProject(projectRoot);
@@ -34877,23 +35358,23 @@ var init_init_context = __esm(() => {
34877
35358
 
34878
35359
  // src/utils/path-security.ts
34879
35360
  import { realpathSync as realpathSync3 } from "fs";
34880
- import { dirname as dirname5, isAbsolute as isAbsolute5, join as join32, normalize as normalize2, resolve as resolve7 } from "path";
35361
+ import { dirname as dirname4, isAbsolute as isAbsolute5, join as join27, normalize as normalize2, resolve as resolve8 } from "path";
34881
35362
  function safeRealpathForComparison(p) {
34882
35363
  try {
34883
35364
  return realpathSync3(p);
34884
35365
  } catch {
34885
- const parent = dirname5(p);
35366
+ const parent = dirname4(p);
34886
35367
  if (parent === p)
34887
35368
  return normalize2(p);
34888
35369
  const resolvedParent = safeRealpathForComparison(parent);
34889
- return join32(resolvedParent, p.split("/").pop() ?? "");
35370
+ return join27(resolvedParent, p.split("/").pop() ?? "");
34890
35371
  }
34891
35372
  }
34892
35373
  function validateModulePath(modulePath, allowedRoots) {
34893
35374
  if (!modulePath) {
34894
35375
  return { valid: false, error: "Module path is empty" };
34895
35376
  }
34896
- const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve7(r)));
35377
+ const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve8(r)));
34897
35378
  if (isAbsolute5(modulePath)) {
34898
35379
  const normalized = normalize2(modulePath);
34899
35380
  const resolved = safeRealpathForComparison(normalized);
@@ -34903,8 +35384,8 @@ function validateModulePath(modulePath, allowedRoots) {
34903
35384
  }
34904
35385
  } else {
34905
35386
  for (let i = 0;i < allowedRoots.length; i++) {
34906
- const originalRoot = resolve7(allowedRoots[i]);
34907
- const absoluteInput = resolve7(join32(originalRoot, modulePath));
35387
+ const originalRoot = resolve8(allowedRoots[i]);
35388
+ const absoluteInput = resolve8(join27(originalRoot, modulePath));
34908
35389
  const resolved = safeRealpathForComparison(absoluteInput);
34909
35390
  const resolvedRoot = resolvedRoots[i];
34910
35391
  if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
@@ -35265,11 +35746,11 @@ function getSafeLogger6() {
35265
35746
  return getSafeLogger();
35266
35747
  }
35267
35748
  function extractPluginName(pluginPath) {
35268
- const basename6 = path13.basename(pluginPath);
35269
- if (basename6 === "index.ts" || basename6 === "index.js" || basename6 === "index.mjs") {
35749
+ const basename5 = path13.basename(pluginPath);
35750
+ if (basename5 === "index.ts" || basename5 === "index.js" || basename5 === "index.mjs") {
35270
35751
  return path13.basename(path13.dirname(pluginPath));
35271
35752
  }
35272
- return basename6.replace(/\.(ts|js|mjs)$/, "");
35753
+ return basename5.replace(/\.(ts|js|mjs)$/, "");
35273
35754
  }
35274
35755
  async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
35275
35756
  const loadedPlugins = [];
@@ -35430,7 +35911,7 @@ async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots =
35430
35911
  return null;
35431
35912
  }
35432
35913
  }
35433
- var _pluginErrorSink = (...args) => console.error(...args);
35914
+ var _pluginErrorSink = () => {};
35434
35915
  var init_loader4 = __esm(() => {
35435
35916
  init_logger2();
35436
35917
  init_path_security2();
@@ -35440,19 +35921,19 @@ var init_loader4 = __esm(() => {
35440
35921
  });
35441
35922
 
35442
35923
  // src/hooks/runner.ts
35443
- import { join as join47 } from "path";
35924
+ import { join as join42 } from "path";
35444
35925
  async function loadHooksConfig(projectDir, globalDir) {
35445
35926
  let globalHooks = { hooks: {} };
35446
35927
  let projectHooks = { hooks: {} };
35447
35928
  let skipGlobal = false;
35448
- const projectPath = join47(projectDir, "hooks.json");
35929
+ const projectPath = join42(projectDir, "hooks.json");
35449
35930
  const projectData = await loadJsonFile(projectPath, "hooks");
35450
35931
  if (projectData) {
35451
35932
  projectHooks = projectData;
35452
35933
  skipGlobal = projectData.skipGlobal ?? false;
35453
35934
  }
35454
35935
  if (!skipGlobal && globalDir) {
35455
- const globalPath = join47(globalDir, "hooks.json");
35936
+ const globalPath = join42(globalDir, "hooks.json");
35456
35937
  const globalData = await loadJsonFile(globalPath, "hooks");
35457
35938
  if (globalData) {
35458
35939
  globalHooks = globalData;
@@ -35652,7 +36133,7 @@ var package_default;
35652
36133
  var init_package = __esm(() => {
35653
36134
  package_default = {
35654
36135
  name: "@nathapp/nax",
35655
- version: "0.58.5",
36136
+ version: "0.59.1",
35656
36137
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
35657
36138
  type: "module",
35658
36139
  bin: {
@@ -35732,8 +36213,8 @@ var init_version = __esm(() => {
35732
36213
  NAX_VERSION = package_default.version;
35733
36214
  NAX_COMMIT = (() => {
35734
36215
  try {
35735
- if (/^[0-9a-f]{6,10}$/.test("374b714e"))
35736
- return "374b714e";
36216
+ if (/^[0-9a-f]{6,10}$/.test("b8492d03"))
36217
+ return "b8492d03";
35737
36218
  } catch {}
35738
36219
  try {
35739
36220
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -36029,18 +36510,18 @@ var init_crash_signals = __esm(() => {
36029
36510
 
36030
36511
  // src/execution/crash-recovery.ts
36031
36512
  function installCrashHandlers(ctx) {
36032
- if (handlersInstalled) {
36033
- return () => {};
36513
+ if (activeCleanup) {
36514
+ activeCleanup();
36034
36515
  }
36035
36516
  const cleanup = installSignalHandlers(ctx);
36036
- handlersInstalled = true;
36037
- return () => {
36517
+ activeCleanup = () => {
36038
36518
  cleanup();
36039
36519
  stopHeartbeat();
36040
- handlersInstalled = false;
36520
+ activeCleanup = null;
36041
36521
  };
36522
+ return activeCleanup;
36042
36523
  }
36043
- var handlersInstalled = false;
36524
+ var activeCleanup = null;
36044
36525
  var init_crash_recovery = __esm(() => {
36045
36526
  init_crash_heartbeat();
36046
36527
  init_crash_signals();
@@ -36361,12 +36842,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
36361
36842
  semanticVerdicts: options.semanticVerdicts
36362
36843
  });
36363
36844
  try {
36845
+ const timeoutSeconds = (config2.acceptance?.timeoutMs ?? 120000) / 1000;
36364
36846
  const result = await agent.run({
36365
36847
  prompt,
36366
36848
  workdir,
36367
36849
  modelTier: undefined,
36368
36850
  modelDef,
36369
- timeoutSeconds: 300,
36851
+ timeoutSeconds,
36370
36852
  sessionRole: "diagnose",
36371
36853
  acpSessionName: sessionName,
36372
36854
  featureName,
@@ -36375,12 +36857,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
36375
36857
  });
36376
36858
  const diagnosis = parseDiagnosisResult(result.output);
36377
36859
  if (diagnosis) {
36378
- return diagnosis;
36860
+ return { ...diagnosis, cost: result.estimatedCost ?? 0 };
36379
36861
  }
36380
36862
  return {
36381
36863
  verdict: "source_bug",
36382
36864
  reasoning: "diagnosis failed \u2014 falling back to source fix",
36383
- confidence: 0
36865
+ confidence: 0,
36866
+ cost: result.estimatedCost ?? 0
36384
36867
  };
36385
36868
  } catch {
36386
36869
  return {
@@ -36492,7 +36975,7 @@ __export(exports_acceptance_loop, {
36492
36975
  _regenerateDeps: () => _regenerateDeps,
36493
36976
  _acceptanceLoopDeps: () => _acceptanceLoopDeps
36494
36977
  });
36495
- import path15, { join as join48 } from "path";
36978
+ import path15, { join as join43 } from "path";
36496
36979
  function isStubTestFile(content) {
36497
36980
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
36498
36981
  }
@@ -36581,15 +37064,16 @@ async function executeFixStory(ctx, story, prd, iterations) {
36581
37064
  agent: ctx.config.autoMode.defaultAgent,
36582
37065
  iteration: iterations
36583
37066
  }), ctx.workdir);
36584
- const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join48(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
37067
+ const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join43(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
36585
37068
  const fixContext = {
36586
- config: ctx.config,
36587
- effectiveConfig: fixEffectiveConfig,
37069
+ config: fixEffectiveConfig,
37070
+ rootConfig: ctx.config,
36588
37071
  prd,
36589
37072
  story,
36590
37073
  stories: [story],
36591
37074
  routing,
36592
- workdir: ctx.workdir,
37075
+ projectDir: ctx.workdir,
37076
+ workdir: story.workdir ? join43(ctx.workdir, story.workdir) : ctx.workdir,
36593
37077
  featureDir: ctx.featureDir,
36594
37078
  hooks: ctx.hooks,
36595
37079
  plugins: ctx.pluginRegistry,
@@ -36768,6 +37252,7 @@ async function runFixRouting(options) {
36768
37252
  storyId,
36769
37253
  semanticVerdicts: options.semanticVerdicts
36770
37254
  });
37255
+ const diagnosisCost = diagnosis.cost ?? 0;
36771
37256
  logger?.info("acceptance.diagnosis", "Diagnosis complete", {
36772
37257
  verdict: diagnosis.verdict,
36773
37258
  confidence: diagnosis.confidence,
@@ -36777,7 +37262,7 @@ async function runFixRouting(options) {
36777
37262
  logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
36778
37263
  if (!agent) {
36779
37264
  logger?.error("acceptance", "Agent not found for source fix execution");
36780
- return { fixed: false, cost: 0, prdDirty: false };
37265
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36781
37266
  }
36782
37267
  let fixAttempts = 0;
36783
37268
  while (fixAttempts < fixMaxRetries) {
@@ -36799,7 +37284,7 @@ async function runFixRouting(options) {
36799
37284
  attempt: fixAttempts
36800
37285
  });
36801
37286
  if (fixResult.success) {
36802
- return { fixed: true, cost: fixResult.cost, prdDirty: false };
37287
+ return { fixed: true, cost: fixResult.cost + diagnosisCost, prdDirty: false };
36803
37288
  }
36804
37289
  logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
36805
37290
  attempt: fixAttempts,
@@ -36812,13 +37297,13 @@ async function runFixRouting(options) {
36812
37297
  break;
36813
37298
  }
36814
37299
  }
36815
- return { fixed: false, cost: 0, prdDirty: false };
37300
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36816
37301
  }
36817
37302
  if (diagnosis.verdict === "test_bug") {
36818
37303
  logger?.info("acceptance", "Diagnosis: test_bug \u2014 regenerating acceptance test");
36819
37304
  if (!ctx.featureDir) {
36820
37305
  logger?.error("acceptance", "Cannot regenerate test without featureDir");
36821
- return { fixed: false, cost: 0, prdDirty: false };
37306
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36822
37307
  }
36823
37308
  const testPath = await findExistingAcceptanceTestPath({
36824
37309
  acceptanceTestPaths: ctx.acceptanceTestPaths,
@@ -36835,29 +37320,29 @@ async function runFixRouting(options) {
36835
37320
  language: ctx.config.project?.language
36836
37321
  })
36837
37322
  });
36838
- return { fixed: false, cost: 0, prdDirty: false };
37323
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36839
37324
  }
36840
37325
  const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
36841
37326
  logger?.info("acceptance.test-regen", "Test regeneration completed", {
36842
37327
  outcome: regenerated ? "success" : "failure"
36843
37328
  });
36844
37329
  if (!regenerated) {
36845
- return { fixed: false, cost: 0, prdDirty: false };
37330
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36846
37331
  }
36847
37332
  const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
36848
37333
  const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
36849
37334
  if (acceptanceResult.action === "continue") {
36850
37335
  logger?.info("acceptance", "Acceptance passed after test regeneration");
36851
- return { fixed: true, cost: 0, prdDirty: true };
37336
+ return { fixed: true, cost: diagnosisCost, prdDirty: true };
36852
37337
  }
36853
37338
  logger?.warn("acceptance", "Acceptance still failing after test regeneration");
36854
- return { fixed: false, cost: 0, prdDirty: true };
37339
+ return { fixed: false, cost: diagnosisCost, prdDirty: true };
36855
37340
  }
36856
37341
  if (diagnosis.verdict === "both") {
36857
37342
  logger?.info("acceptance", "Diagnosis: both \u2014 executing source fix then regenerating test if needed");
36858
37343
  if (!agent) {
36859
37344
  logger?.error("acceptance", "Agent not found for source fix execution");
36860
- return { fixed: false, cost: 0, prdDirty: false };
37345
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36861
37346
  }
36862
37347
  let sourceFixSuccess = false;
36863
37348
  let sourceFixCost = 0;
@@ -36897,19 +37382,19 @@ async function runFixRouting(options) {
36897
37382
  }
36898
37383
  }
36899
37384
  if (!sourceFixSuccess) {
36900
- return { fixed: false, cost: sourceFixCost, prdDirty: false };
37385
+ return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36901
37386
  }
36902
37387
  logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
36903
37388
  const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
36904
37389
  const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
36905
37390
  if (acceptanceResult.action === "continue") {
36906
37391
  logger?.info("acceptance", "Acceptance passed after source fix");
36907
- return { fixed: true, cost: sourceFixCost, prdDirty: false };
37392
+ return { fixed: true, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36908
37393
  }
36909
37394
  logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
36910
37395
  if (!ctx.featureDir) {
36911
37396
  logger?.error("acceptance", "Cannot regenerate test without featureDir");
36912
- return { fixed: false, cost: sourceFixCost, prdDirty: false };
37397
+ return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36913
37398
  }
36914
37399
  const testPath = await findExistingAcceptanceTestPath({
36915
37400
  acceptanceTestPaths: ctx.acceptanceTestPaths,
@@ -36926,15 +37411,15 @@ async function runFixRouting(options) {
36926
37411
  language: ctx.config.project?.language
36927
37412
  })
36928
37413
  });
36929
- return { fixed: false, cost: sourceFixCost, prdDirty: false };
37414
+ return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36930
37415
  }
36931
37416
  const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
36932
37417
  logger?.info("acceptance.test-regen", "Test regeneration completed", {
36933
37418
  outcome: regenerated ? "success" : "failure"
36934
37419
  });
36935
- return { fixed: regenerated, cost: sourceFixCost, prdDirty: regenerated };
37420
+ return { fixed: regenerated, cost: sourceFixCost + diagnosisCost, prdDirty: regenerated };
36936
37421
  }
36937
- return { fixed: false, cost: 0, prdDirty: false };
37422
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36938
37423
  }
36939
37424
  async function runAcceptanceLoop(ctx) {
36940
37425
  const logger = getSafeLogger();
@@ -36950,7 +37435,7 @@ async function runAcceptanceLoop(ctx) {
36950
37435
  const firstStory = prd.userStories[0];
36951
37436
  const acceptanceContext = {
36952
37437
  config: ctx.config,
36953
- effectiveConfig: ctx.config,
37438
+ rootConfig: ctx.config,
36954
37439
  prd,
36955
37440
  story: firstStory,
36956
37441
  stories: [firstStory],
@@ -36960,6 +37445,7 @@ async function runAcceptanceLoop(ctx) {
36960
37445
  testStrategy: "test-after",
36961
37446
  reasoning: "Acceptance validation"
36962
37447
  },
37448
+ projectDir: ctx.workdir,
36963
37449
  workdir: ctx.workdir,
36964
37450
  featureDir: ctx.featureDir,
36965
37451
  hooks: ctx.hooks,
@@ -37185,6 +37671,20 @@ async function runDeferredRegression(options) {
37185
37671
  const testCommand = config2.quality.commands.test ?? "bun test";
37186
37672
  const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
37187
37673
  const maxRectificationAttempts = config2.execution.regressionGate?.maxRectificationAttempts ?? 2;
37674
+ const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
37675
+ const verifyOpts = {
37676
+ workdir,
37677
+ command: testCommand,
37678
+ timeoutSeconds,
37679
+ forceExit: config2.quality.forceExit,
37680
+ detectOpenHandles: config2.quality.detectOpenHandles,
37681
+ detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
37682
+ timeoutRetryCount: 0,
37683
+ gracePeriodMs: config2.quality.gracePeriodMs,
37684
+ drainTimeoutMs: config2.quality.drainTimeoutMs,
37685
+ shell: config2.quality.shell,
37686
+ stripEnvVars: config2.quality.stripEnvVars
37687
+ };
37188
37688
  const counts = countStories(prd);
37189
37689
  const passedStories = prd.userStories.filter((s) => s.status === "passed");
37190
37690
  if (passedStories.length === 0) {
@@ -37202,19 +37702,7 @@ async function runDeferredRegression(options) {
37202
37702
  totalStories: counts.total,
37203
37703
  passedStories: passedStories.length
37204
37704
  });
37205
- const fullSuiteResult = await _regressionDeps.runVerification({
37206
- workdir,
37207
- command: testCommand,
37208
- timeoutSeconds,
37209
- forceExit: config2.quality.forceExit,
37210
- detectOpenHandles: config2.quality.detectOpenHandles,
37211
- detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
37212
- timeoutRetryCount: 0,
37213
- gracePeriodMs: config2.quality.gracePeriodMs,
37214
- drainTimeoutMs: config2.quality.drainTimeoutMs,
37215
- shell: config2.quality.shell,
37216
- stripEnvVars: config2.quality.stripEnvVars
37217
- });
37705
+ const fullSuiteResult = await _regressionDeps.runVerification(verifyOpts);
37218
37706
  if (fullSuiteResult.success) {
37219
37707
  logger?.info("regression", "Full suite passed");
37220
37708
  return {
@@ -37226,7 +37714,6 @@ async function runDeferredRegression(options) {
37226
37714
  affectedStories: []
37227
37715
  };
37228
37716
  }
37229
- const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
37230
37717
  if (fullSuiteResult.status === "TIMEOUT" && acceptOnTimeout) {
37231
37718
  logger?.warn("regression", "Full-suite regression gate timed out (accepted as pass)");
37232
37719
  return {
@@ -37308,6 +37795,8 @@ async function runDeferredRegression(options) {
37308
37795
  };
37309
37796
  }
37310
37797
  let rectificationAttempts = 0;
37798
+ let storiesRectified = 0;
37799
+ let currentTestOutput = fullSuiteResult.output;
37311
37800
  const affectedStoriesList = Array.from(affectedStoriesObjs.values());
37312
37801
  for (const story of affectedStoriesList) {
37313
37802
  for (let attempt = 0;attempt < maxRectificationAttempts; attempt++) {
@@ -37319,32 +37808,51 @@ async function runDeferredRegression(options) {
37319
37808
  story,
37320
37809
  testCommand,
37321
37810
  timeoutSeconds,
37322
- testOutput: fullSuiteResult.output,
37811
+ testOutput: currentTestOutput,
37323
37812
  promptPrefix: `# DEFERRED REGRESSION: Full-Suite Failures
37324
37813
 
37325
37814
  Your story ${story.id} broke tests in the full suite. Fix these regressions.`,
37326
37815
  agentGetFn
37327
37816
  });
37328
37817
  if (fixed) {
37818
+ storiesRectified++;
37329
37819
  logger?.info("regression", `Story ${story.id} rectified successfully`);
37820
+ logger?.info("regression", "Re-running full suite after story rectification", {
37821
+ storyId: story.id,
37822
+ storiesRectified,
37823
+ storiesRemaining: affectedStoriesList.length - storiesRectified
37824
+ });
37825
+ const midResult = await _regressionDeps.runVerification(verifyOpts);
37826
+ const midSuccess = midResult.success || midResult.status === "TIMEOUT" && acceptOnTimeout;
37827
+ if (midSuccess) {
37828
+ logger?.info("regression", "Full suite passed after story rectification \u2014 early exit", {
37829
+ storyId: story.id,
37830
+ storiesRectified,
37831
+ storiesSkipped: affectedStoriesList.length - storiesRectified,
37832
+ passCount: midResult.passCount ?? 0
37833
+ });
37834
+ return {
37835
+ success: true,
37836
+ failedTests: testFilesInFailures.size,
37837
+ failedTestFiles: Array.from(testFilesInFailures),
37838
+ passedTests: midResult.passCount ?? 0,
37839
+ rectificationAttempts,
37840
+ affectedStories: Array.from(affectedStories)
37841
+ };
37842
+ }
37843
+ logger?.warn("regression", "Full suite still failing after story rectification \u2014 continuing", {
37844
+ storyId: story.id,
37845
+ failCount: midResult.failCount ?? 0,
37846
+ passCount: midResult.passCount ?? 0
37847
+ });
37848
+ if (midResult.output)
37849
+ currentTestOutput = midResult.output;
37330
37850
  break;
37331
37851
  }
37332
37852
  }
37333
37853
  }
37334
37854
  logger?.info("regression", "Re-running full suite after rectification");
37335
- const retryResult = await _regressionDeps.runVerification({
37336
- workdir,
37337
- command: testCommand,
37338
- timeoutSeconds,
37339
- forceExit: config2.quality.forceExit,
37340
- detectOpenHandles: config2.quality.detectOpenHandles,
37341
- detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
37342
- timeoutRetryCount: 0,
37343
- gracePeriodMs: config2.quality.gracePeriodMs,
37344
- drainTimeoutMs: config2.quality.drainTimeoutMs,
37345
- shell: config2.quality.shell,
37346
- stripEnvVars: config2.quality.stripEnvVars
37347
- });
37855
+ const retryResult = await _regressionDeps.runVerification(verifyOpts);
37348
37856
  const success2 = retryResult.success || retryResult.status === "TIMEOUT" && acceptOnTimeout;
37349
37857
  if (success2) {
37350
37858
  logger?.info("regression", "Deferred regression gate passed after rectification");
@@ -37601,12 +38109,12 @@ var init_headless_formatter = __esm(() => {
37601
38109
  // src/pipeline/subscribers/events-writer.ts
37602
38110
  import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
37603
38111
  import { homedir as homedir5 } from "os";
37604
- import { basename as basename7, join as join49 } from "path";
38112
+ import { basename as basename6, join as join44 } from "path";
37605
38113
  function wireEventsWriter(bus, feature, runId, workdir) {
37606
38114
  const logger = getSafeLogger();
37607
- const project = basename7(workdir);
37608
- const eventsDir = join49(homedir5(), ".nax", "events", project);
37609
- const eventsFile = join49(eventsDir, "events.jsonl");
38115
+ const project = basename6(workdir);
38116
+ const eventsDir = join44(homedir5(), ".nax", "events", project);
38117
+ const eventsFile = join44(eventsDir, "events.jsonl");
37610
38118
  let dirReady = false;
37611
38119
  const write = (line) => {
37612
38120
  return (async () => {
@@ -37787,12 +38295,12 @@ var init_interaction2 = __esm(() => {
37787
38295
  // src/pipeline/subscribers/registry.ts
37788
38296
  import { mkdir as mkdir4, writeFile } from "fs/promises";
37789
38297
  import { homedir as homedir6 } from "os";
37790
- import { basename as basename8, join as join50 } from "path";
38298
+ import { basename as basename7, join as join45 } from "path";
37791
38299
  function wireRegistry(bus, feature, runId, workdir) {
37792
38300
  const logger = getSafeLogger();
37793
- const project = basename8(workdir);
37794
- const runDir = join50(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
37795
- const metaFile = join50(runDir, "meta.json");
38301
+ const project = basename7(workdir);
38302
+ const runDir = join45(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
38303
+ const metaFile = join45(runDir, "meta.json");
37796
38304
  const unsub = bus.on("run:started", (_ev) => {
37797
38305
  return (async () => {
37798
38306
  try {
@@ -37802,8 +38310,8 @@ function wireRegistry(bus, feature, runId, workdir) {
37802
38310
  project,
37803
38311
  feature,
37804
38312
  workdir,
37805
- statusPath: join50(workdir, ".nax", "features", feature, "status.json"),
37806
- eventsDir: join50(workdir, ".nax", "features", feature, "runs"),
38313
+ statusPath: join45(workdir, ".nax", "features", feature, "status.json"),
38314
+ eventsDir: join45(workdir, ".nax", "features", feature, "runs"),
37807
38315
  registeredAt: new Date().toISOString()
37808
38316
  };
37809
38317
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -38328,7 +38836,7 @@ function filterOutputFiles(files) {
38328
38836
  }
38329
38837
  async function handlePipelineSuccess(ctx, pipelineResult) {
38330
38838
  const logger = getSafeLogger();
38331
- const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
38839
+ const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
38332
38840
  const prd = ctx.prd;
38333
38841
  if (pipelineResult.context.storyMetrics) {
38334
38842
  ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
@@ -38376,7 +38884,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
38376
38884
  const logger = getSafeLogger();
38377
38885
  let prd = ctx.prd;
38378
38886
  let prdDirty = false;
38379
- const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
38887
+ const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
38380
38888
  switch (pipelineResult.finalAction) {
38381
38889
  case "pause":
38382
38890
  markStoryPaused(prd, ctx.story.id);
@@ -38455,7 +38963,7 @@ var init_pipeline_result_handler = __esm(() => {
38455
38963
  });
38456
38964
 
38457
38965
  // src/execution/iteration-runner.ts
38458
- import { join as join51 } from "path";
38966
+ import { join as join46 } from "path";
38459
38967
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
38460
38968
  const logger = getSafeLogger();
38461
38969
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -38490,15 +38998,16 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
38490
38998
  }
38491
38999
  }
38492
39000
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
38493
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join51(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
39001
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
38494
39002
  const pipelineContext = {
38495
- config: ctx.config,
38496
- effectiveConfig,
39003
+ config: effectiveConfig,
39004
+ rootConfig: ctx.config,
38497
39005
  prd,
38498
39006
  story,
38499
39007
  stories: storiesToExecute,
38500
39008
  routing,
38501
- workdir: ctx.workdir,
39009
+ projectDir: ctx.workdir,
39010
+ workdir: story.workdir ? join46(ctx.workdir, story.workdir) : ctx.workdir,
38502
39011
  prdPath: ctx.prdPath,
38503
39012
  featureDir: ctx.featureDir,
38504
39013
  hooks: ctx.hooks,
@@ -38523,13 +39032,6 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
38523
39032
  await ctx.statusWriter.update(totalCost, iterations);
38524
39033
  const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
38525
39034
  const currentPrd = pipelineResult.context.prd;
38526
- pipelineContext.agentResult = undefined;
38527
- pipelineContext.prompt = undefined;
38528
- pipelineContext.contextMarkdown = undefined;
38529
- pipelineContext.builtContext = undefined;
38530
- pipelineContext.verifyResult = undefined;
38531
- pipelineContext.reviewResult = undefined;
38532
- pipelineContext.constitution = undefined;
38533
39035
  const handlerCtx = {
38534
39036
  config: ctx.config,
38535
39037
  prd: currentPrd,
@@ -38552,26 +39054,36 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
38552
39054
  storyStartTime,
38553
39055
  statusWriter: ctx.statusWriter
38554
39056
  };
39057
+ let iterResult;
38555
39058
  if (pipelineResult.success) {
38556
- const r2 = await handlePipelineSuccess(handlerCtx, pipelineResult);
38557
- return {
38558
- prd: r2.prd,
38559
- storiesCompletedDelta: r2.storiesCompletedDelta,
38560
- costDelta: r2.costDelta,
38561
- prdDirty: r2.prdDirty,
39059
+ const r = await handlePipelineSuccess(handlerCtx, pipelineResult);
39060
+ iterResult = {
39061
+ prd: r.prd,
39062
+ storiesCompletedDelta: r.storiesCompletedDelta,
39063
+ costDelta: r.costDelta,
39064
+ prdDirty: r.prdDirty,
38562
39065
  finalAction: pipelineResult.finalAction
38563
39066
  };
39067
+ } else {
39068
+ const r = await handlePipelineFailure(handlerCtx, pipelineResult);
39069
+ iterResult = {
39070
+ prd: r.prd,
39071
+ storiesCompletedDelta: 0,
39072
+ costDelta: r.costDelta,
39073
+ prdDirty: r.prdDirty,
39074
+ finalAction: pipelineResult.finalAction,
39075
+ reason: pipelineResult.reason,
39076
+ subStoryCount: pipelineResult.subStoryCount
39077
+ };
38564
39078
  }
38565
- const r = await handlePipelineFailure(handlerCtx, pipelineResult);
38566
- return {
38567
- prd: r.prd,
38568
- storiesCompletedDelta: 0,
38569
- costDelta: r.costDelta,
38570
- prdDirty: r.prdDirty,
38571
- finalAction: pipelineResult.finalAction,
38572
- reason: pipelineResult.reason,
38573
- subStoryCount: pipelineResult.subStoryCount
38574
- };
39079
+ pipelineContext.agentResult = undefined;
39080
+ pipelineContext.prompt = undefined;
39081
+ pipelineContext.contextMarkdown = undefined;
39082
+ pipelineContext.builtContext = undefined;
39083
+ pipelineContext.verifyResult = undefined;
39084
+ pipelineContext.reviewResult = undefined;
39085
+ pipelineContext.constitution = undefined;
39086
+ return iterResult;
38575
39087
  }
38576
39088
  var _iterationRunnerDeps;
38577
39089
  var init_iteration_runner = __esm(() => {
@@ -38651,6 +39163,7 @@ __export(exports_parallel_worker, {
38651
39163
  executeStoryInWorktree: () => executeStoryInWorktree,
38652
39164
  executeParallelBatch: () => executeParallelBatch
38653
39165
  });
39166
+ import { join as join47 } from "path";
38654
39167
  async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
38655
39168
  const logger = getSafeLogger();
38656
39169
  try {
@@ -38665,10 +39178,12 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
38665
39178
  }
38666
39179
  const pipelineContext = {
38667
39180
  ...context,
38668
- effectiveConfig: context.effectiveConfig ?? context.config,
39181
+ config: context.config,
39182
+ rootConfig: context.rootConfig,
38669
39183
  story,
38670
39184
  stories: [story],
38671
- workdir: worktreePath,
39185
+ projectDir: context.projectDir,
39186
+ workdir: story.workdir ? join47(worktreePath, story.workdir) : worktreePath,
38672
39187
  routing,
38673
39188
  storyGitRef: storyGitRef ?? undefined
38674
39189
  };
@@ -38713,7 +39228,7 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
38713
39228
  }
38714
39229
  const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
38715
39230
  const storyConfig = storyEffectiveConfigs?.get(story.id);
38716
- const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
39231
+ const storyContext = storyConfig ? { ...context, config: storyConfig } : context;
38717
39232
  const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
38718
39233
  results.totalCost += result.cost;
38719
39234
  results.storyCosts.set(story.id, result.cost);
@@ -38755,19 +39270,19 @@ __export(exports_manager, {
38755
39270
  _managerDeps: () => _managerDeps,
38756
39271
  WorktreeManager: () => WorktreeManager
38757
39272
  });
38758
- import { existsSync as existsSync30, symlinkSync } from "fs";
39273
+ import { existsSync as existsSync29, symlinkSync } from "fs";
38759
39274
  import { mkdir as mkdir5 } from "fs/promises";
38760
- import { join as join52 } from "path";
39275
+ import { join as join48 } from "path";
38761
39276
 
38762
39277
  class WorktreeManager {
38763
39278
  async ensureGitExcludes(projectRoot) {
38764
39279
  const logger = getSafeLogger();
38765
- const infoDir = join52(projectRoot, ".git", "info");
38766
- const excludePath = join52(infoDir, "exclude");
39280
+ const infoDir = join48(projectRoot, ".git", "info");
39281
+ const excludePath = join48(infoDir, "exclude");
38767
39282
  try {
38768
39283
  await mkdir5(infoDir, { recursive: true });
38769
39284
  let existing = "";
38770
- if (existsSync30(excludePath)) {
39285
+ if (existsSync29(excludePath)) {
38771
39286
  existing = await Bun.file(excludePath).text();
38772
39287
  }
38773
39288
  const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -38790,7 +39305,7 @@ ${missing.join(`
38790
39305
  }
38791
39306
  async create(projectRoot, storyId) {
38792
39307
  validateStoryId(storyId);
38793
- const worktreePath = join52(projectRoot, ".nax-wt", storyId);
39308
+ const worktreePath = join48(projectRoot, ".nax-wt", storyId);
38794
39309
  const branchName = `nax/${storyId}`;
38795
39310
  try {
38796
39311
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -38831,9 +39346,9 @@ ${missing.join(`
38831
39346
  }
38832
39347
  throw new Error(`Failed to create worktree: ${String(error48)}`);
38833
39348
  }
38834
- const nodeModulesSource = join52(projectRoot, "node_modules");
38835
- if (existsSync30(nodeModulesSource)) {
38836
- const nodeModulesTarget = join52(worktreePath, "node_modules");
39349
+ const nodeModulesSource = join48(projectRoot, "node_modules");
39350
+ if (existsSync29(nodeModulesSource)) {
39351
+ const nodeModulesTarget = join48(worktreePath, "node_modules");
38837
39352
  try {
38838
39353
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
38839
39354
  } catch (error48) {
@@ -38841,9 +39356,9 @@ ${missing.join(`
38841
39356
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
38842
39357
  }
38843
39358
  }
38844
- const envSource = join52(projectRoot, ".env");
38845
- if (existsSync30(envSource)) {
38846
- const envTarget = join52(worktreePath, ".env");
39359
+ const envSource = join48(projectRoot, ".env");
39360
+ if (existsSync29(envSource)) {
39361
+ const envTarget = join48(worktreePath, ".env");
38847
39362
  try {
38848
39363
  symlinkSync(envSource, envTarget, "file");
38849
39364
  } catch (error48) {
@@ -38854,7 +39369,7 @@ ${missing.join(`
38854
39369
  }
38855
39370
  async remove(projectRoot, storyId) {
38856
39371
  validateStoryId(storyId);
38857
- const worktreePath = join52(projectRoot, ".nax-wt", storyId);
39372
+ const worktreePath = join48(projectRoot, ".nax-wt", storyId);
38858
39373
  const branchName = `nax/${storyId}`;
38859
39374
  try {
38860
39375
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -39214,10 +39729,11 @@ async function rectifyConflictedStory(options) {
39214
39729
  const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
39215
39730
  const pipelineContext = {
39216
39731
  config: config2,
39217
- effectiveConfig: config2,
39732
+ rootConfig: config2,
39218
39733
  prd,
39219
39734
  story,
39220
39735
  stories: [story],
39736
+ projectDir: workdir,
39221
39737
  workdir: worktreePath,
39222
39738
  featureDir: undefined,
39223
39739
  hooks,
@@ -39454,8 +39970,9 @@ async function executeUnified(ctx, initialPrd) {
39454
39970
  logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
39455
39971
  preRunCtx = {
39456
39972
  config: ctx.config,
39457
- effectiveConfig: ctx.config,
39973
+ rootConfig: ctx.config,
39458
39974
  prd,
39975
+ projectDir: ctx.workdir,
39459
39976
  workdir: ctx.workdir,
39460
39977
  featureDir: ctx.featureDir,
39461
39978
  story: prd.userStories[0],
@@ -39500,6 +40017,7 @@ async function executeUnified(ctx, initialPrd) {
39500
40017
  const readyStories = getAllReadyStories(prd);
39501
40018
  const batch = _unifiedExecutorDeps.selectIndependentBatch(readyStories, ctx.parallelCount);
39502
40019
  if (batch.length > 1) {
40020
+ ctx.onBeforeStory?.();
39503
40021
  for (const story of batch) {
39504
40022
  pipelineEventBus.emit({
39505
40023
  type: "story:started",
@@ -39525,8 +40043,9 @@ async function executeUnified(ctx, initialPrd) {
39525
40043
  maxConcurrency: ctx.parallelCount,
39526
40044
  pipelineContext: {
39527
40045
  config: ctx.config,
39528
- effectiveConfig: ctx.config,
40046
+ rootConfig: ctx.config,
39529
40047
  prd,
40048
+ projectDir: ctx.workdir,
39530
40049
  hooks: ctx.hooks,
39531
40050
  featureDir: ctx.featureDir,
39532
40051
  agentGetFn: ctx.agentGetFn,
@@ -39640,6 +40159,7 @@ async function executeUnified(ctx, initialPrd) {
39640
40159
  }
39641
40160
  pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
39642
40161
  }
40162
+ ctx.onBeforeStory?.();
39643
40163
  pipelineEventBus.emit({
39644
40164
  type: "story:started",
39645
40165
  storyId: singleStory.id,
@@ -39702,6 +40222,7 @@ async function executeUnified(ctx, initialPrd) {
39702
40222
  }
39703
40223
  pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
39704
40224
  }
40225
+ ctx.onBeforeStory?.();
39705
40226
  pipelineEventBus.emit({
39706
40227
  type: "story:started",
39707
40228
  storyId: selection.story.id,
@@ -39790,16 +40311,16 @@ var init_unified_executor = __esm(() => {
39790
40311
  });
39791
40312
 
39792
40313
  // src/project/detector.ts
39793
- import { join as join53 } from "path";
40314
+ import { join as join49 } from "path";
39794
40315
  async function detectLanguage(workdir, pkg) {
39795
40316
  const deps = _detectorDeps;
39796
- if (await deps.fileExists(join53(workdir, "go.mod")))
40317
+ if (await deps.fileExists(join49(workdir, "go.mod")))
39797
40318
  return "go";
39798
- if (await deps.fileExists(join53(workdir, "Cargo.toml")))
40319
+ if (await deps.fileExists(join49(workdir, "Cargo.toml")))
39799
40320
  return "rust";
39800
- if (await deps.fileExists(join53(workdir, "pyproject.toml")))
40321
+ if (await deps.fileExists(join49(workdir, "pyproject.toml")))
39801
40322
  return "python";
39802
- if (await deps.fileExists(join53(workdir, "requirements.txt")))
40323
+ if (await deps.fileExists(join49(workdir, "requirements.txt")))
39803
40324
  return "python";
39804
40325
  if (pkg != null) {
39805
40326
  const allDeps = {
@@ -39859,18 +40380,18 @@ async function detectLintTool(workdir, language) {
39859
40380
  if (language === "python")
39860
40381
  return "ruff";
39861
40382
  const deps = _detectorDeps;
39862
- if (await deps.fileExists(join53(workdir, "biome.json")))
40383
+ if (await deps.fileExists(join49(workdir, "biome.json")))
39863
40384
  return "biome";
39864
- if (await deps.fileExists(join53(workdir, ".eslintrc")))
40385
+ if (await deps.fileExists(join49(workdir, ".eslintrc")))
39865
40386
  return "eslint";
39866
- if (await deps.fileExists(join53(workdir, ".eslintrc.js")))
40387
+ if (await deps.fileExists(join49(workdir, ".eslintrc.js")))
39867
40388
  return "eslint";
39868
- if (await deps.fileExists(join53(workdir, ".eslintrc.json")))
40389
+ if (await deps.fileExists(join49(workdir, ".eslintrc.json")))
39869
40390
  return "eslint";
39870
40391
  return;
39871
40392
  }
39872
40393
  async function detectProjectProfile(workdir, existing) {
39873
- const pkg = await _detectorDeps.readJson(join53(workdir, "package.json"));
40394
+ const pkg = await _detectorDeps.readJson(join49(workdir, "package.json"));
39874
40395
  const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
39875
40396
  const type = existing.type !== undefined ? existing.type : detectType(pkg);
39876
40397
  const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
@@ -39907,7 +40428,7 @@ var init_project = __esm(() => {
39907
40428
 
39908
40429
  // src/execution/status-file.ts
39909
40430
  import { rename, unlink as unlink3 } from "fs/promises";
39910
- import { resolve as resolve9 } from "path";
40431
+ import { resolve as resolve10 } from "path";
39911
40432
  function countProgress(prd) {
39912
40433
  const stories = prd.userStories;
39913
40434
  const passed = stories.filter((s) => s.status === "passed").length;
@@ -39952,7 +40473,7 @@ function buildStatusSnapshot(state) {
39952
40473
  return snapshot;
39953
40474
  }
39954
40475
  async function writeStatusFile(filePath, status) {
39955
- const resolvedPath = resolve9(filePath);
40476
+ const resolvedPath = resolve10(filePath);
39956
40477
  if (filePath.includes("../") || filePath.includes("..\\")) {
39957
40478
  throw new Error("Invalid status file path: path traversal detected");
39958
40479
  }
@@ -39966,7 +40487,7 @@ async function writeStatusFile(filePath, status) {
39966
40487
  var init_status_file = () => {};
39967
40488
 
39968
40489
  // src/execution/status-writer.ts
39969
- import { join as join54 } from "path";
40490
+ import { join as join50 } from "path";
39970
40491
 
39971
40492
  class StatusWriter {
39972
40493
  statusFile;
@@ -40080,7 +40601,7 @@ class StatusWriter {
40080
40601
  if (!this._prd)
40081
40602
  return;
40082
40603
  const safeLogger = getSafeLogger();
40083
- const featureStatusPath = join54(featureDir, "status.json");
40604
+ const featureStatusPath = join50(featureDir, "status.json");
40084
40605
  const write = async () => {
40085
40606
  try {
40086
40607
  const base = this.getSnapshot(totalCost, iterations);
@@ -40291,7 +40812,7 @@ __export(exports_run_initialization, {
40291
40812
  initializeRun: () => initializeRun,
40292
40813
  _reconcileDeps: () => _reconcileDeps
40293
40814
  });
40294
- import { join as join55 } from "path";
40815
+ import { join as join51 } from "path";
40295
40816
  async function reconcileState(prd, prdPath, workdir, config2) {
40296
40817
  const logger = getSafeLogger();
40297
40818
  let reconciledCount = 0;
@@ -40309,7 +40830,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
40309
40830
  });
40310
40831
  continue;
40311
40832
  }
40312
- const effectiveWorkdir = story.workdir ? join55(workdir, story.workdir) : workdir;
40833
+ const effectiveWorkdir = story.workdir ? join51(workdir, story.workdir) : workdir;
40313
40834
  try {
40314
40835
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
40315
40836
  if (!reviewResult.success) {
@@ -40373,7 +40894,7 @@ function validateStoryCount(counts, config2) {
40373
40894
  }
40374
40895
  function logActiveProtocol(config2) {
40375
40896
  const logger = getSafeLogger();
40376
- const protocol = config2.agent?.protocol ?? "cli";
40897
+ const protocol = config2.agent?.protocol;
40377
40898
  logger?.info("run-initialization", `Agent protocol: ${protocol}`, { protocol });
40378
40899
  }
40379
40900
  async function initializeRun(ctx) {
@@ -41035,14 +41556,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41035
41556
  prevActScopeDepth !== actScopeDepth - 1 && console.error("You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one. ");
41036
41557
  actScopeDepth = prevActScopeDepth;
41037
41558
  }
41038
- function recursivelyFlushAsyncActWork(returnValue, resolve10, reject) {
41559
+ function recursivelyFlushAsyncActWork(returnValue, resolve11, reject) {
41039
41560
  var queue = ReactSharedInternals.actQueue;
41040
41561
  if (queue !== null)
41041
41562
  if (queue.length !== 0)
41042
41563
  try {
41043
41564
  flushActQueue(queue);
41044
41565
  enqueueTask(function() {
41045
- return recursivelyFlushAsyncActWork(returnValue, resolve10, reject);
41566
+ return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
41046
41567
  });
41047
41568
  return;
41048
41569
  } catch (error48) {
@@ -41050,7 +41571,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41050
41571
  }
41051
41572
  else
41052
41573
  ReactSharedInternals.actQueue = null;
41053
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve10(returnValue);
41574
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve11(returnValue);
41054
41575
  }
41055
41576
  function flushActQueue(queue) {
41056
41577
  if (!isFlushing) {
@@ -41226,14 +41747,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41226
41747
  didAwaitActCall || didWarnNoAwaitAct || (didWarnNoAwaitAct = true, console.error("You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"));
41227
41748
  });
41228
41749
  return {
41229
- then: function(resolve10, reject) {
41750
+ then: function(resolve11, reject) {
41230
41751
  didAwaitActCall = true;
41231
41752
  thenable.then(function(returnValue) {
41232
41753
  popActScope(prevActQueue, prevActScopeDepth);
41233
41754
  if (prevActScopeDepth === 0) {
41234
41755
  try {
41235
41756
  flushActQueue(queue), enqueueTask(function() {
41236
- return recursivelyFlushAsyncActWork(returnValue, resolve10, reject);
41757
+ return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
41237
41758
  });
41238
41759
  } catch (error$0) {
41239
41760
  ReactSharedInternals.thrownErrors.push(error$0);
@@ -41244,7 +41765,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41244
41765
  reject(_thrownError);
41245
41766
  }
41246
41767
  } else
41247
- resolve10(returnValue);
41768
+ resolve11(returnValue);
41248
41769
  }, function(error48) {
41249
41770
  popActScope(prevActQueue, prevActScopeDepth);
41250
41771
  0 < ReactSharedInternals.thrownErrors.length ? (error48 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(error48)) : reject(error48);
@@ -41260,11 +41781,11 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41260
41781
  if (0 < ReactSharedInternals.thrownErrors.length)
41261
41782
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
41262
41783
  return {
41263
- then: function(resolve10, reject) {
41784
+ then: function(resolve11, reject) {
41264
41785
  didAwaitActCall = true;
41265
41786
  prevActScopeDepth === 0 ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
41266
- return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve10, reject);
41267
- })) : resolve10(returnValue$jscomp$0);
41787
+ return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve11, reject);
41788
+ })) : resolve11(returnValue$jscomp$0);
41268
41789
  }
41269
41790
  };
41270
41791
  };
@@ -44106,8 +44627,8 @@ It can also happen if the client has a browser extension installed which messes
44106
44627
  currentEntangledActionThenable = {
44107
44628
  status: "pending",
44108
44629
  value: undefined,
44109
- then: function(resolve10) {
44110
- entangledListeners.push(resolve10);
44630
+ then: function(resolve11) {
44631
+ entangledListeners.push(resolve11);
44111
44632
  }
44112
44633
  };
44113
44634
  }
@@ -44131,8 +44652,8 @@ It can also happen if the client has a browser extension installed which messes
44131
44652
  status: "pending",
44132
44653
  value: null,
44133
44654
  reason: null,
44134
- then: function(resolve10) {
44135
- listeners.push(resolve10);
44655
+ then: function(resolve11) {
44656
+ listeners.push(resolve11);
44136
44657
  }
44137
44658
  };
44138
44659
  thenable.then(function() {
@@ -71522,9 +72043,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
71522
72043
 
71523
72044
  // bin/nax.ts
71524
72045
  init_source();
71525
- import { existsSync as existsSync32, mkdirSync as mkdirSync7 } from "fs";
72046
+ import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
71526
72047
  import { homedir as homedir8 } from "os";
71527
- import { join as join57 } from "path";
72048
+ import { join as join53 } from "path";
71528
72049
 
71529
72050
  // node_modules/commander/esm.mjs
71530
72051
  var import__ = __toESM(require_commander(), 1);
@@ -71550,15 +72071,15 @@ import { join as join11 } from "path";
71550
72071
  import { createInterface as createInterface2 } from "readline";
71551
72072
 
71552
72073
  // src/analyze/scanner.ts
71553
- import { existsSync as existsSync2, readdirSync } from "fs";
72074
+ import { existsSync as existsSync3, readdirSync } from "fs";
71554
72075
  import { join as join4 } from "path";
71555
72076
  async function scanCodebase(workdir) {
71556
72077
  const srcPath = join4(workdir, "src");
71557
72078
  const packageJsonPath = join4(workdir, "package.json");
71558
- const fileTree = existsSync2(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
72079
+ const fileTree = existsSync3(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
71559
72080
  let dependencies = {};
71560
72081
  let devDependencies = {};
71561
- if (existsSync2(packageJsonPath)) {
72082
+ if (existsSync3(packageJsonPath)) {
71562
72083
  try {
71563
72084
  const pkg = await Bun.file(packageJsonPath).json();
71564
72085
  dependencies = pkg.dependencies || {};
@@ -71619,16 +72140,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
71619
72140
  } else {
71620
72141
  patterns.push("Test framework: likely bun:test (no framework dependency)");
71621
72142
  }
71622
- if (existsSync2(join4(workdir, "test"))) {
72143
+ if (existsSync3(join4(workdir, "test"))) {
71623
72144
  patterns.push("Test directory: test/");
71624
72145
  }
71625
- if (existsSync2(join4(workdir, "__tests__"))) {
72146
+ if (existsSync3(join4(workdir, "__tests__"))) {
71626
72147
  patterns.push("Test directory: __tests__/");
71627
72148
  }
71628
- if (existsSync2(join4(workdir, "tests"))) {
72149
+ if (existsSync3(join4(workdir, "tests"))) {
71629
72150
  patterns.push("Test directory: tests/");
71630
72151
  }
71631
- const hasTestFiles = existsSync2(join4(workdir, "test")) || existsSync2(join4(workdir, "src"));
72152
+ const hasTestFiles = existsSync3(join4(workdir, "test")) || existsSync3(join4(workdir, "src"));
71632
72153
  if (hasTestFiles) {
71633
72154
  patterns.push("Test files: *.test.ts, *.spec.ts");
71634
72155
  }
@@ -71640,7 +72161,7 @@ init_test_strategy();
71640
72161
 
71641
72162
  // src/context/generator.ts
71642
72163
  init_path_security();
71643
- import { existsSync as existsSync5, readFileSync } from "fs";
72164
+ import { existsSync as existsSync5 } from "fs";
71644
72165
  import { join as join6, relative } from "path";
71645
72166
 
71646
72167
  // src/context/injector.ts
@@ -71983,7 +72504,6 @@ var windsurfGenerator = {
71983
72504
  // src/context/generator.ts
71984
72505
  var _generatorDeps = {
71985
72506
  existsSync: (p) => existsSync5(p),
71986
- readFileSync: (p, enc) => readFileSync(p, enc),
71987
72507
  readTextFile: (p) => Bun.file(p).text(),
71988
72508
  writeFile: (p, content) => Bun.write(p, content),
71989
72509
  buildProjectMetadata
@@ -72085,47 +72605,41 @@ async function discoverWorkspacePackages(repoRoot) {
72085
72605
  }
72086
72606
  }
72087
72607
  const turboPath = join6(repoRoot, "turbo.json");
72088
- if (_generatorDeps.existsSync(turboPath)) {
72089
- try {
72090
- const turbo = JSON.parse(_generatorDeps.readFileSync(turboPath, "utf-8"));
72091
- if (Array.isArray(turbo.packages)) {
72092
- await resolveGlobs(turbo.packages);
72093
- }
72094
- } catch {}
72095
- }
72608
+ try {
72609
+ const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
72610
+ if (Array.isArray(turbo.packages)) {
72611
+ await resolveGlobs(turbo.packages);
72612
+ }
72613
+ } catch {}
72096
72614
  const pkgPath = join6(repoRoot, "package.json");
72097
- if (_generatorDeps.existsSync(pkgPath)) {
72098
- try {
72099
- const pkg = JSON.parse(_generatorDeps.readFileSync(pkgPath, "utf-8"));
72100
- const ws = pkg.workspaces;
72101
- const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
72102
- if (patterns.length > 0)
72103
- await resolveGlobs(patterns);
72104
- } catch {}
72105
- }
72615
+ try {
72616
+ const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
72617
+ const ws = pkg.workspaces;
72618
+ const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
72619
+ if (patterns.length > 0)
72620
+ await resolveGlobs(patterns);
72621
+ } catch {}
72106
72622
  const pnpmPath = join6(repoRoot, "pnpm-workspace.yaml");
72107
- if (_generatorDeps.existsSync(pnpmPath)) {
72108
- try {
72109
- const raw = _generatorDeps.readFileSync(pnpmPath, "utf-8");
72110
- const lines = raw.split(`
72623
+ try {
72624
+ const raw = await _generatorDeps.readTextFile(pnpmPath);
72625
+ const lines = raw.split(`
72111
72626
  `);
72112
- let inPackages = false;
72113
- const patterns = [];
72114
- for (const line of lines) {
72115
- if (/^packages\s*:/.test(line)) {
72116
- inPackages = true;
72117
- continue;
72118
- }
72119
- if (inPackages && /^\s+-\s+/.test(line)) {
72120
- patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
72121
- } else if (inPackages && !/^\s/.test(line)) {
72122
- break;
72123
- }
72627
+ let inPackages = false;
72628
+ const patterns = [];
72629
+ for (const line of lines) {
72630
+ if (/^packages\s*:/.test(line)) {
72631
+ inPackages = true;
72632
+ continue;
72124
72633
  }
72125
- if (patterns.length > 0)
72126
- await resolveGlobs(patterns);
72127
- } catch {}
72128
- }
72634
+ if (inPackages && /^\s+-\s+/.test(line)) {
72635
+ patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
72636
+ } else if (inPackages && !/^\s/.test(line)) {
72637
+ break;
72638
+ }
72639
+ }
72640
+ if (patterns.length > 0)
72641
+ await resolveGlobs(patterns);
72642
+ } catch {}
72129
72643
  return results.sort();
72130
72644
  }
72131
72645
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
@@ -72657,13 +73171,13 @@ function createCliInteractionBridge() {
72657
73171
  process.stdout.write(`
72658
73172
  \uD83E\uDD16 Agent: ${text}
72659
73173
  You: `);
72660
- return new Promise((resolve5) => {
73174
+ return new Promise((resolve6) => {
72661
73175
  const rl = createInterface2({ input: process.stdin, terminal: false });
72662
73176
  rl.once("line", (line) => {
72663
73177
  rl.close();
72664
- resolve5(line.trim());
73178
+ resolve6(line.trim());
72665
73179
  });
72666
- rl.once("close", () => resolve5(""));
73180
+ rl.once("close", () => resolve6(""));
72667
73181
  });
72668
73182
  }
72669
73183
  };
@@ -73168,20 +73682,20 @@ async function displayModelEfficiency(workdir) {
73168
73682
  // src/cli/status-features.ts
73169
73683
  init_source();
73170
73684
  import { existsSync as existsSync15, readdirSync as readdirSync4 } from "fs";
73171
- import { join as join14, resolve as resolve6 } from "path";
73685
+ import { join as join14, resolve as resolve7 } from "path";
73172
73686
 
73173
73687
  // src/commands/common.ts
73174
73688
  init_path_security();
73175
73689
  init_errors();
73176
73690
  import { existsSync as existsSync14, readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
73177
- import { join as join12, resolve as resolve5 } from "path";
73691
+ import { join as join12, resolve as resolve6 } from "path";
73178
73692
  function resolveProject(options = {}) {
73179
73693
  const { dir, feature } = options;
73180
73694
  let projectRoot;
73181
73695
  let naxDir;
73182
73696
  let configPath;
73183
73697
  if (dir) {
73184
- projectRoot = realpathSync2(resolve5(dir));
73698
+ projectRoot = realpathSync2(resolve6(dir));
73185
73699
  naxDir = join12(projectRoot, ".nax");
73186
73700
  if (!existsSync14(naxDir)) {
73187
73701
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
@@ -73234,7 +73748,7 @@ No features found in this project.`;
73234
73748
  };
73235
73749
  }
73236
73750
  function findProjectRoot(startDir) {
73237
- let current = resolve5(startDir);
73751
+ let current = resolve6(startDir);
73238
73752
  let depth = 0;
73239
73753
  while (depth < MAX_DIRECTORY_DEPTH) {
73240
73754
  const naxDir = join12(current, ".nax");
@@ -73551,7 +74065,7 @@ async function displayFeatureStatus(options = {}) {
73551
74065
  if (options.feature) {
73552
74066
  let featureDir;
73553
74067
  if (options.dir) {
73554
- featureDir = join14(resolve6(options.dir), ".nax", "features", options.feature);
74068
+ featureDir = join14(resolve7(options.dir), ".nax", "features", options.feature);
73555
74069
  } else {
73556
74070
  const resolved = resolveProject({ feature: options.feature });
73557
74071
  if (!resolved.featureDir) {
@@ -73661,8 +74175,8 @@ async function runsShowCommand(options) {
73661
74175
  }
73662
74176
  // src/cli/prompts-main.ts
73663
74177
  init_logger2();
73664
- import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
73665
- import { join as join29 } from "path";
74178
+ import { existsSync as existsSync19, mkdirSync as mkdirSync3 } from "fs";
74179
+ import { join as join24 } from "path";
73666
74180
 
73667
74181
  // src/pipeline/index.ts
73668
74182
  init_runner();
@@ -73737,7 +74251,7 @@ function buildFrontmatter(story, ctx, role) {
73737
74251
 
73738
74252
  // src/cli/prompts-tdd.ts
73739
74253
  init_prompts2();
73740
- import { join as join28 } from "path";
74254
+ import { join as join23 } from "path";
73741
74255
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
73742
74256
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
73743
74257
  PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
@@ -73756,7 +74270,7 @@ ${frontmatter}---
73756
74270
 
73757
74271
  ${session.prompt}`;
73758
74272
  if (outputDir) {
73759
- const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
74273
+ const promptFile = join23(outputDir, `${story.id}.${session.role}.md`);
73760
74274
  await Bun.write(promptFile, fullOutput);
73761
74275
  logger.info("cli", "Written TDD prompt file", {
73762
74276
  storyId: story.id,
@@ -73772,7 +74286,7 @@ ${"=".repeat(80)}`);
73772
74286
  }
73773
74287
  }
73774
74288
  if (outputDir && ctx.contextMarkdown) {
73775
- const contextFile = join28(outputDir, `${story.id}.context.md`);
74289
+ const contextFile = join23(outputDir, `${story.id}.context.md`);
73776
74290
  const frontmatter = buildFrontmatter(story, ctx);
73777
74291
  const contextOutput = `---
73778
74292
  ${frontmatter}---
@@ -73786,13 +74300,13 @@ ${ctx.contextMarkdown}`;
73786
74300
  async function promptsCommand(options) {
73787
74301
  const logger = getLogger();
73788
74302
  const { feature, workdir, config: config2, storyId, outputDir } = options;
73789
- const naxDir = join29(workdir, ".nax");
73790
- if (!existsSync20(naxDir)) {
74303
+ const naxDir = join24(workdir, ".nax");
74304
+ if (!existsSync19(naxDir)) {
73791
74305
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
73792
74306
  }
73793
- const featureDir = join29(naxDir, "features", feature);
73794
- const prdPath = join29(featureDir, "prd.json");
73795
- if (!existsSync20(prdPath)) {
74307
+ const featureDir = join24(naxDir, "features", feature);
74308
+ const prdPath = join24(featureDir, "prd.json");
74309
+ if (!existsSync19(prdPath)) {
73796
74310
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
73797
74311
  }
73798
74312
  const prd = await loadPRD(prdPath);
@@ -73813,7 +74327,7 @@ async function promptsCommand(options) {
73813
74327
  for (const story of stories) {
73814
74328
  const ctx = {
73815
74329
  config: config2,
73816
- effectiveConfig: config2,
74330
+ rootConfig: config2,
73817
74331
  prd,
73818
74332
  story,
73819
74333
  stories: [story],
@@ -73823,6 +74337,7 @@ async function promptsCommand(options) {
73823
74337
  testStrategy: "test-after",
73824
74338
  reasoning: "Placeholder routing"
73825
74339
  },
74340
+ projectDir: workdir,
73826
74341
  workdir,
73827
74342
  featureDir,
73828
74343
  hooks: { hooks: {} }
@@ -73852,10 +74367,10 @@ ${frontmatter}---
73852
74367
 
73853
74368
  ${ctx.prompt}`;
73854
74369
  if (outputDir) {
73855
- const promptFile = join29(outputDir, `${story.id}.prompt.md`);
74370
+ const promptFile = join24(outputDir, `${story.id}.prompt.md`);
73856
74371
  await Bun.write(promptFile, fullOutput);
73857
74372
  if (ctx.contextMarkdown) {
73858
- const contextFile = join29(outputDir, `${story.id}.context.md`);
74373
+ const contextFile = join24(outputDir, `${story.id}.context.md`);
73859
74374
  const contextOutput = `---
73860
74375
  ${frontmatter}---
73861
74376
 
@@ -73881,8 +74396,8 @@ ${"=".repeat(80)}`);
73881
74396
  return processedStories;
73882
74397
  }
73883
74398
  // src/cli/prompts-init.ts
73884
- import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
73885
- import { join as join30 } from "path";
74399
+ import { existsSync as existsSync20, mkdirSync as mkdirSync4 } from "fs";
74400
+ import { join as join25 } from "path";
73886
74401
  var TEMPLATE_ROLES = [
73887
74402
  { file: "test-writer.md", role: "test-writer" },
73888
74403
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -73906,9 +74421,9 @@ var TEMPLATE_HEADER = `<!--
73906
74421
  `;
73907
74422
  async function promptsInitCommand(options) {
73908
74423
  const { workdir, force = false, autoWireConfig = true } = options;
73909
- const templatesDir = join30(workdir, ".nax", "templates");
74424
+ const templatesDir = join25(workdir, ".nax", "templates");
73910
74425
  mkdirSync4(templatesDir, { recursive: true });
73911
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join30(templatesDir, f)));
74426
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync20(join25(templatesDir, f)));
73912
74427
  if (existingFiles.length > 0 && !force) {
73913
74428
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
73914
74429
  Pass --force to overwrite existing templates.`);
@@ -73916,7 +74431,7 @@ async function promptsInitCommand(options) {
73916
74431
  }
73917
74432
  const written = [];
73918
74433
  for (const template of TEMPLATE_ROLES) {
73919
- const filePath = join30(templatesDir, template.file);
74434
+ const filePath = join25(templatesDir, template.file);
73920
74435
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
73921
74436
  const content = TEMPLATE_HEADER + roleBody;
73922
74437
  await Bun.write(filePath, content);
@@ -73932,8 +74447,8 @@ async function promptsInitCommand(options) {
73932
74447
  return written;
73933
74448
  }
73934
74449
  async function autoWirePromptsConfig(workdir) {
73935
- const configPath = join30(workdir, "nax.config.json");
73936
- if (!existsSync21(configPath)) {
74450
+ const configPath = join25(workdir, "nax.config.json");
74451
+ if (!existsSync20(configPath)) {
73937
74452
  const exampleConfig = JSON.stringify({
73938
74453
  prompts: {
73939
74454
  overrides: {
@@ -74097,8 +74612,8 @@ function pad(str, width) {
74097
74612
  init_config();
74098
74613
  init_logger2();
74099
74614
  init_prd();
74100
- import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
74101
- import { join as join35 } from "path";
74615
+ import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
74616
+ import { join as join30 } from "path";
74102
74617
 
74103
74618
  // src/cli/diagnose-analysis.ts
74104
74619
  function detectFailurePattern(story, prd, status) {
@@ -74297,8 +74812,8 @@ function isProcessAlive2(pid) {
74297
74812
  }
74298
74813
  }
74299
74814
  async function loadStatusFile2(workdir) {
74300
- const statusPath = join35(workdir, ".nax", "status.json");
74301
- if (!existsSync23(statusPath))
74815
+ const statusPath = join30(workdir, ".nax", "status.json");
74816
+ if (!existsSync22(statusPath))
74302
74817
  return null;
74303
74818
  try {
74304
74819
  return await Bun.file(statusPath).json();
@@ -74325,7 +74840,7 @@ async function countCommitsSince(workdir, since) {
74325
74840
  }
74326
74841
  }
74327
74842
  async function checkLock(workdir) {
74328
- const lockFile = Bun.file(join35(workdir, "nax.lock"));
74843
+ const lockFile = Bun.file(join30(workdir, "nax.lock"));
74329
74844
  if (!await lockFile.exists())
74330
74845
  return { lockPresent: false };
74331
74846
  try {
@@ -74343,8 +74858,8 @@ async function diagnoseCommand(options = {}) {
74343
74858
  const logger = getLogger();
74344
74859
  const workdir = options.workdir ?? process.cwd();
74345
74860
  const naxSubdir = findProjectDir(workdir);
74346
- let projectDir = naxSubdir ? join35(naxSubdir, "..") : null;
74347
- if (!projectDir && existsSync23(join35(workdir, ".nax"))) {
74861
+ let projectDir = naxSubdir ? join30(naxSubdir, "..") : null;
74862
+ if (!projectDir && existsSync22(join30(workdir, ".nax"))) {
74348
74863
  projectDir = workdir;
74349
74864
  }
74350
74865
  if (!projectDir)
@@ -74355,8 +74870,8 @@ async function diagnoseCommand(options = {}) {
74355
74870
  if (status2) {
74356
74871
  feature = status2.run.feature;
74357
74872
  } else {
74358
- const featuresDir = join35(projectDir, ".nax", "features");
74359
- if (!existsSync23(featuresDir))
74873
+ const featuresDir = join30(projectDir, ".nax", "features");
74874
+ if (!existsSync22(featuresDir))
74360
74875
  throw new Error("No features found in project");
74361
74876
  const features = readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
74362
74877
  if (features.length === 0)
@@ -74365,9 +74880,9 @@ async function diagnoseCommand(options = {}) {
74365
74880
  logger.info("diagnose", "No feature specified, using first found", { feature });
74366
74881
  }
74367
74882
  }
74368
- const featureDir = join35(projectDir, ".nax", "features", feature);
74369
- const prdPath = join35(featureDir, "prd.json");
74370
- if (!existsSync23(prdPath))
74883
+ const featureDir = join30(projectDir, ".nax", "features", feature);
74884
+ const prdPath = join30(featureDir, "prd.json");
74885
+ if (!existsSync22(prdPath))
74371
74886
  throw new Error(`Feature not found: ${feature}`);
74372
74887
  const prd = await loadPRD(prdPath);
74373
74888
  const status = await loadStatusFile2(projectDir);
@@ -74408,8 +74923,8 @@ init_interaction();
74408
74923
  // src/cli/generate.ts
74409
74924
  init_source();
74410
74925
  init_loader();
74411
- import { existsSync as existsSync24 } from "fs";
74412
- import { join as join36 } from "path";
74926
+ import { existsSync as existsSync23 } from "fs";
74927
+ import { join as join31 } from "path";
74413
74928
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
74414
74929
  async function generateCommand(options) {
74415
74930
  const workdir = options.dir ?? process.cwd();
@@ -74452,7 +74967,7 @@ async function generateCommand(options) {
74452
74967
  return;
74453
74968
  }
74454
74969
  if (options.package) {
74455
- const packageDir = join36(workdir, options.package);
74970
+ const packageDir = join31(workdir, options.package);
74456
74971
  if (dryRun) {
74457
74972
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
74458
74973
  }
@@ -74472,10 +74987,10 @@ async function generateCommand(options) {
74472
74987
  process.exit(1);
74473
74988
  return;
74474
74989
  }
74475
- const contextPath = options.context ? join36(workdir, options.context) : join36(workdir, ".nax/context.md");
74476
- const outputDir = options.output ? join36(workdir, options.output) : workdir;
74990
+ const contextPath = options.context ? join31(workdir, options.context) : join31(workdir, ".nax/context.md");
74991
+ const outputDir = options.output ? join31(workdir, options.output) : workdir;
74477
74992
  const autoInject = !options.noAutoInject;
74478
- if (!existsSync24(contextPath)) {
74993
+ if (!existsSync23(contextPath)) {
74479
74994
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
74480
74995
  console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
74481
74996
  process.exit(1);
@@ -74577,8 +75092,8 @@ async function generateCommand(options) {
74577
75092
  }
74578
75093
  // src/cli/config-display.ts
74579
75094
  init_loader();
74580
- import { existsSync as existsSync26 } from "fs";
74581
- import { join as join38 } from "path";
75095
+ import { existsSync as existsSync25 } from "fs";
75096
+ import { join as join33 } from "path";
74582
75097
 
74583
75098
  // src/cli/config-descriptions.ts
74584
75099
  var FIELD_DESCRIPTIONS = {
@@ -74817,10 +75332,10 @@ function deepEqual(a, b) {
74817
75332
  // src/cli/config-get.ts
74818
75333
  init_defaults();
74819
75334
  init_loader();
74820
- import { existsSync as existsSync25 } from "fs";
74821
- import { join as join37 } from "path";
75335
+ import { existsSync as existsSync24 } from "fs";
75336
+ import { join as join32 } from "path";
74822
75337
  async function loadConfigFile(path15) {
74823
- if (!existsSync25(path15))
75338
+ if (!existsSync24(path15))
74824
75339
  return null;
74825
75340
  try {
74826
75341
  return await Bun.file(path15).json();
@@ -74840,7 +75355,7 @@ async function loadProjectConfig() {
74840
75355
  const projectDir = findProjectDir();
74841
75356
  if (!projectDir)
74842
75357
  return null;
74843
- const projectPath = join37(projectDir, "config.json");
75358
+ const projectPath = join32(projectDir, "config.json");
74844
75359
  return await loadConfigFile(projectPath);
74845
75360
  }
74846
75361
 
@@ -74900,14 +75415,14 @@ async function configCommand(config2, options = {}) {
74900
75415
  function determineConfigSources() {
74901
75416
  const globalPath = globalConfigPath();
74902
75417
  const projectDir = findProjectDir();
74903
- const projectPath = projectDir ? join38(projectDir, "config.json") : null;
75418
+ const projectPath = projectDir ? join33(projectDir, "config.json") : null;
74904
75419
  return {
74905
75420
  global: fileExists(globalPath) ? globalPath : null,
74906
75421
  project: projectPath && fileExists(projectPath) ? projectPath : null
74907
75422
  };
74908
75423
  }
74909
75424
  function fileExists(path15) {
74910
- return existsSync26(path15);
75425
+ return existsSync25(path15);
74911
75426
  }
74912
75427
  function displayConfigWithDescriptions(obj, path15, sources, indent = 0) {
74913
75428
  const indentStr = " ".repeat(indent);
@@ -75049,15 +75564,15 @@ init_paths();
75049
75564
  init_profile();
75050
75565
  import { mkdirSync as mkdirSync5 } from "fs";
75051
75566
  import { readdirSync as readdirSync7 } from "fs";
75052
- import { join as join39 } from "path";
75567
+ import { join as join34 } from "path";
75053
75568
  var _profileCLIDeps = {
75054
75569
  env: process.env
75055
75570
  };
75056
75571
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
75057
75572
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
75058
75573
  async function profileListCommand(startDir) {
75059
- const globalProfilesDir = join39(globalConfigDir(), "profiles");
75060
- const projectProfilesDir = join39(projectConfigDir(startDir), "profiles");
75574
+ const globalProfilesDir = join34(globalConfigDir(), "profiles");
75575
+ const projectProfilesDir = join34(projectConfigDir(startDir), "profiles");
75061
75576
  const globalProfiles = scanProfileDir(globalProfilesDir);
75062
75577
  const projectProfiles = scanProfileDir(projectProfilesDir);
75063
75578
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -75116,7 +75631,7 @@ function maskProfileValues(obj) {
75116
75631
  return result;
75117
75632
  }
75118
75633
  async function profileUseCommand(profileName, startDir) {
75119
- const configPath = join39(projectConfigDir(startDir), "config.json");
75634
+ const configPath = join34(projectConfigDir(startDir), "config.json");
75120
75635
  const configFile = Bun.file(configPath);
75121
75636
  let existing = {};
75122
75637
  if (await configFile.exists()) {
@@ -75135,8 +75650,8 @@ async function profileCurrentCommand(startDir) {
75135
75650
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
75136
75651
  }
75137
75652
  async function profileCreateCommand(profileName, startDir) {
75138
- const profilesDir = join39(projectConfigDir(startDir), "profiles");
75139
- const profilePath = join39(profilesDir, `${profileName}.json`);
75653
+ const profilesDir = join34(projectConfigDir(startDir), "profiles");
75654
+ const profilePath = join34(profilesDir, `${profileName}.json`);
75140
75655
  const profileFile = Bun.file(profilePath);
75141
75656
  if (await profileFile.exists()) {
75142
75657
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -75201,25 +75716,25 @@ async function diagnose(options) {
75201
75716
  }
75202
75717
 
75203
75718
  // src/commands/logs.ts
75204
- import { existsSync as existsSync28 } from "fs";
75205
- import { join as join43 } from "path";
75719
+ import { existsSync as existsSync27 } from "fs";
75720
+ import { join as join38 } from "path";
75206
75721
 
75207
75722
  // src/commands/logs-formatter.ts
75208
75723
  init_source();
75209
75724
  init_formatter();
75210
75725
  import { readdirSync as readdirSync9 } from "fs";
75211
- import { join as join42 } from "path";
75726
+ import { join as join37 } from "path";
75212
75727
 
75213
75728
  // src/commands/logs-reader.ts
75214
- import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
75729
+ import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
75215
75730
  import { readdir as readdir3 } from "fs/promises";
75216
- import { join as join41 } from "path";
75731
+ import { join as join36 } from "path";
75217
75732
 
75218
75733
  // src/utils/paths.ts
75219
75734
  import { homedir as homedir4 } from "os";
75220
- import { join as join40 } from "path";
75735
+ import { join as join35 } from "path";
75221
75736
  function getRunsDir() {
75222
- return process.env.NAX_RUNS_DIR ?? join40(homedir4(), ".nax", "runs");
75737
+ return process.env.NAX_RUNS_DIR ?? join35(homedir4(), ".nax", "runs");
75223
75738
  }
75224
75739
 
75225
75740
  // src/commands/logs-reader.ts
@@ -75236,7 +75751,7 @@ async function resolveRunFileFromRegistry(runId) {
75236
75751
  }
75237
75752
  let matched = null;
75238
75753
  for (const entry of entries) {
75239
- const metaPath = join41(runsDir, entry, "meta.json");
75754
+ const metaPath = join36(runsDir, entry, "meta.json");
75240
75755
  try {
75241
75756
  const meta3 = await Bun.file(metaPath).json();
75242
75757
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -75248,7 +75763,7 @@ async function resolveRunFileFromRegistry(runId) {
75248
75763
  if (!matched) {
75249
75764
  throw new Error(`Run not found in registry: ${runId}`);
75250
75765
  }
75251
- if (!existsSync27(matched.eventsDir)) {
75766
+ if (!existsSync26(matched.eventsDir)) {
75252
75767
  console.log(`Log directory unavailable for run: ${runId}`);
75253
75768
  return null;
75254
75769
  }
@@ -75258,14 +75773,14 @@ async function resolveRunFileFromRegistry(runId) {
75258
75773
  return null;
75259
75774
  }
75260
75775
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
75261
- return join41(matched.eventsDir, specificFile ?? files[0]);
75776
+ return join36(matched.eventsDir, specificFile ?? files[0]);
75262
75777
  }
75263
75778
  async function selectRunFile(runsDir) {
75264
75779
  const files = readdirSync8(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
75265
75780
  if (files.length === 0) {
75266
75781
  return null;
75267
75782
  }
75268
- return join41(runsDir, files[0]);
75783
+ return join36(runsDir, files[0]);
75269
75784
  }
75270
75785
  async function extractRunSummary(filePath) {
75271
75786
  const file3 = Bun.file(filePath);
@@ -75350,7 +75865,7 @@ Runs:
75350
75865
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
75351
75866
  console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
75352
75867
  for (const file3 of files) {
75353
- const filePath = join42(runsDir, file3);
75868
+ const filePath = join37(runsDir, file3);
75354
75869
  const summary = await extractRunSummary(filePath);
75355
75870
  const timestamp = file3.replace(".jsonl", "");
75356
75871
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -75464,7 +75979,7 @@ async function logsCommand(options) {
75464
75979
  return;
75465
75980
  }
75466
75981
  const resolved = resolveProject({ dir: options.dir });
75467
- const naxDir = join43(resolved.projectDir, ".nax");
75982
+ const naxDir = join38(resolved.projectDir, ".nax");
75468
75983
  const configPath = resolved.configPath;
75469
75984
  const configFile = Bun.file(configPath);
75470
75985
  const config2 = await configFile.json();
@@ -75472,9 +75987,9 @@ async function logsCommand(options) {
75472
75987
  if (!featureName) {
75473
75988
  throw new Error("No feature specified in config.json");
75474
75989
  }
75475
- const featureDir = join43(naxDir, "features", featureName);
75476
- const runsDir = join43(featureDir, "runs");
75477
- if (!existsSync28(runsDir)) {
75990
+ const featureDir = join38(naxDir, "features", featureName);
75991
+ const runsDir = join38(featureDir, "runs");
75992
+ if (!existsSync27(runsDir)) {
75478
75993
  throw new Error(`No runs directory found for feature: ${featureName}`);
75479
75994
  }
75480
75995
  if (options.list) {
@@ -75497,8 +76012,8 @@ init_source();
75497
76012
  init_config();
75498
76013
  init_prd();
75499
76014
  init_precheck();
75500
- import { existsSync as existsSync29 } from "fs";
75501
- import { join as join44 } from "path";
76015
+ import { existsSync as existsSync28 } from "fs";
76016
+ import { join as join39 } from "path";
75502
76017
  async function precheckCommand(options) {
75503
76018
  const resolved = resolveProject({
75504
76019
  dir: options.dir,
@@ -75520,14 +76035,14 @@ async function precheckCommand(options) {
75520
76035
  process.exit(1);
75521
76036
  }
75522
76037
  }
75523
- const naxDir = join44(resolved.projectDir, ".nax");
75524
- const featureDir = join44(naxDir, "features", featureName);
75525
- const prdPath = join44(featureDir, "prd.json");
75526
- if (!existsSync29(featureDir)) {
76038
+ const naxDir = join39(resolved.projectDir, ".nax");
76039
+ const featureDir = join39(naxDir, "features", featureName);
76040
+ const prdPath = join39(featureDir, "prd.json");
76041
+ if (!existsSync28(featureDir)) {
75527
76042
  console.error(source_default.red(`Feature not found: ${featureName}`));
75528
76043
  process.exit(1);
75529
76044
  }
75530
- if (!existsSync29(prdPath)) {
76045
+ if (!existsSync28(prdPath)) {
75531
76046
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
75532
76047
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
75533
76048
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -75544,7 +76059,7 @@ async function precheckCommand(options) {
75544
76059
  // src/commands/runs.ts
75545
76060
  init_source();
75546
76061
  import { readdir as readdir4 } from "fs/promises";
75547
- import { join as join45 } from "path";
76062
+ import { join as join40 } from "path";
75548
76063
  var DEFAULT_LIMIT = 20;
75549
76064
  var _runsCmdDeps = {
75550
76065
  getRunsDir
@@ -75599,7 +76114,7 @@ async function runsCommand(options = {}) {
75599
76114
  }
75600
76115
  const rows = [];
75601
76116
  for (const entry of entries) {
75602
- const metaPath = join45(runsDir, entry, "meta.json");
76117
+ const metaPath = join40(runsDir, entry, "meta.json");
75603
76118
  let meta3;
75604
76119
  try {
75605
76120
  meta3 = await Bun.file(metaPath).json();
@@ -75676,7 +76191,7 @@ async function runsCommand(options = {}) {
75676
76191
 
75677
76192
  // src/commands/unlock.ts
75678
76193
  init_source();
75679
- import { join as join46 } from "path";
76194
+ import { join as join41 } from "path";
75680
76195
  function isProcessAlive3(pid) {
75681
76196
  try {
75682
76197
  process.kill(pid, 0);
@@ -75691,7 +76206,7 @@ function formatLockAge(ageMs) {
75691
76206
  }
75692
76207
  async function unlockCommand(options) {
75693
76208
  const workdir = options.dir ?? process.cwd();
75694
- const lockPath = join46(workdir, "nax.lock");
76209
+ const lockPath = join41(workdir, "nax.lock");
75695
76210
  const lockFile = Bun.file(lockPath);
75696
76211
  const exists = await lockFile.exists();
75697
76212
  if (!exists) {
@@ -75767,11 +76282,9 @@ async function runCompletionPhase(options) {
75767
76282
  const regressionAlreadyPassed = postRunStatus?.regression?.status === "passed";
75768
76283
  if (acceptanceAlreadyPassed && regressionAlreadyPassed) {
75769
76284
  logger?.info("execution", "Post-run phases already passed \u2014 skipping acceptance and regression");
75770
- console.info("Post-run phases already passed \u2014 skipping acceptance and regression");
75771
76285
  } else {
75772
76286
  if (acceptanceAlreadyPassed) {
75773
76287
  logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
75774
- console.info("Acceptance already passed \u2014 skipping acceptance phase");
75775
76288
  } else if (options.config.acceptance.enabled && isComplete(options.prd)) {
75776
76289
  options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
75777
76290
  const acceptanceResult = await _runnerCompletionDeps.runAcceptanceLoop({
@@ -75970,6 +76483,7 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
75970
76483
  startTime: options.startTime,
75971
76484
  parallelCount: options.parallel,
75972
76485
  agentGetFn: options.agentGetFn,
76486
+ onBeforeStory: options.onBeforeStory,
75973
76487
  pidRegistry: options.pidRegistry,
75974
76488
  interactionChain: options.interactionChain,
75975
76489
  batchPlan
@@ -76094,6 +76608,7 @@ async function run(options) {
76094
76608
  headless,
76095
76609
  parallel,
76096
76610
  agentGetFn,
76611
+ onBeforeStory: () => registry2.resetStoryState(),
76097
76612
  pidRegistry,
76098
76613
  interactionChain
76099
76614
  }, prd, pluginRegistry);
@@ -81677,8 +82192,8 @@ class Ink {
81677
82192
  }
81678
82193
  }
81679
82194
  async waitUntilExit() {
81680
- this.exitPromise ||= new Promise((resolve10, reject2) => {
81681
- this.resolveExitPromise = resolve10;
82195
+ this.exitPromise ||= new Promise((resolve11, reject2) => {
82196
+ this.resolveExitPromise = resolve11;
81682
82197
  this.rejectExitPromise = reject2;
81683
82198
  });
81684
82199
  if (!this.beforeExitHandler) {
@@ -83491,7 +84006,7 @@ async function promptForConfirmation(question) {
83491
84006
  if (!process.stdin.isTTY) {
83492
84007
  return true;
83493
84008
  }
83494
- return new Promise((resolve10) => {
84009
+ return new Promise((resolve11) => {
83495
84010
  process.stdout.write(source_default.bold(`${question} [Y/n] `));
83496
84011
  process.stdin.setRawMode(true);
83497
84012
  process.stdin.resume();
@@ -83504,9 +84019,9 @@ async function promptForConfirmation(question) {
83504
84019
  process.stdout.write(`
83505
84020
  `);
83506
84021
  if (answer === "n") {
83507
- resolve10(false);
84022
+ resolve11(false);
83508
84023
  } else {
83509
- resolve10(true);
84024
+ resolve11(true);
83510
84025
  }
83511
84026
  };
83512
84027
  process.stdin.on("data", handler);
@@ -83535,15 +84050,15 @@ Next: nax generate --package ${options.package}`));
83535
84050
  }
83536
84051
  return;
83537
84052
  }
83538
- const naxDir = join57(workdir, ".nax");
83539
- if (existsSync32(naxDir) && !options.force) {
84053
+ const naxDir = join53(workdir, ".nax");
84054
+ if (existsSync31(naxDir) && !options.force) {
83540
84055
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
83541
84056
  return;
83542
84057
  }
83543
- mkdirSync7(join57(naxDir, "features"), { recursive: true });
83544
- mkdirSync7(join57(naxDir, "hooks"), { recursive: true });
83545
- await Bun.write(join57(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
83546
- await Bun.write(join57(naxDir, "hooks.json"), JSON.stringify({
84058
+ mkdirSync7(join53(naxDir, "features"), { recursive: true });
84059
+ mkdirSync7(join53(naxDir, "hooks"), { recursive: true });
84060
+ await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
84061
+ await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
83547
84062
  hooks: {
83548
84063
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
83549
84064
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -83551,12 +84066,12 @@ Next: nax generate --package ${options.package}`));
83551
84066
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
83552
84067
  }
83553
84068
  }, null, 2));
83554
- await Bun.write(join57(naxDir, ".gitignore"), `# nax temp files
84069
+ await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
83555
84070
  *.tmp
83556
84071
  .paused.json
83557
84072
  .nax-verifier-verdict.json
83558
84073
  `);
83559
- await Bun.write(join57(naxDir, "context.md"), `# Project Context
84074
+ await Bun.write(join53(naxDir, "context.md"), `# Project Context
83560
84075
 
83561
84076
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
83562
84077
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -83653,7 +84168,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
83653
84168
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
83654
84169
  process.exit(1);
83655
84170
  }
83656
- if (options.from && !existsSync32(options.from)) {
84171
+ if (options.from && !existsSync31(options.from)) {
83657
84172
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
83658
84173
  process.exit(1);
83659
84174
  }
@@ -83686,10 +84201,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
83686
84201
  console.error(source_default.red("nax not initialized. Run: nax init"));
83687
84202
  process.exit(1);
83688
84203
  }
83689
- const featureDir = join57(naxDir, "features", options.feature);
83690
- const prdPath = join57(featureDir, "prd.json");
84204
+ const featureDir = join53(naxDir, "features", options.feature);
84205
+ const prdPath = join53(featureDir, "prd.json");
83691
84206
  if (options.plan && options.from) {
83692
- if (existsSync32(prdPath) && !options.force) {
84207
+ if (existsSync31(prdPath) && !options.force) {
83693
84208
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
83694
84209
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
83695
84210
  process.exit(1);
@@ -83709,10 +84224,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
83709
84224
  }
83710
84225
  }
83711
84226
  try {
83712
- const planLogDir = join57(featureDir, "plan");
84227
+ const planLogDir = join53(featureDir, "plan");
83713
84228
  mkdirSync7(planLogDir, { recursive: true });
83714
84229
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
83715
- const planLogPath = join57(planLogDir, `${planLogId}.jsonl`);
84230
+ const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
83716
84231
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
83717
84232
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
83718
84233
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -83751,15 +84266,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
83751
84266
  process.exit(1);
83752
84267
  }
83753
84268
  }
83754
- if (!existsSync32(prdPath)) {
84269
+ if (!existsSync31(prdPath)) {
83755
84270
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
83756
84271
  process.exit(1);
83757
84272
  }
83758
84273
  resetLogger();
83759
- const runsDir = join57(featureDir, "runs");
84274
+ const runsDir = join53(featureDir, "runs");
83760
84275
  mkdirSync7(runsDir, { recursive: true });
83761
84276
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
83762
- const logFilePath = join57(runsDir, `${runId}.jsonl`);
84277
+ const logFilePath = join53(runsDir, `${runId}.jsonl`);
83763
84278
  const isTTY = process.stdout.isTTY ?? false;
83764
84279
  const headlessFlag = options.headless ?? false;
83765
84280
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -83775,7 +84290,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
83775
84290
  config2.autoMode.defaultAgent = options.agent;
83776
84291
  }
83777
84292
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
83778
- const globalNaxDir = join57(homedir8(), ".nax");
84293
+ const globalNaxDir = join53(homedir8(), ".nax");
83779
84294
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
83780
84295
  const eventEmitter = new PipelineEventEmitter;
83781
84296
  let tuiInstance;
@@ -83798,7 +84313,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
83798
84313
  } else {
83799
84314
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
83800
84315
  }
83801
- const statusFilePath = join57(workdir, ".nax", "status.json");
84316
+ const statusFilePath = join53(workdir, ".nax", "status.json");
83802
84317
  let parallel;
83803
84318
  if (options.parallel !== undefined) {
83804
84319
  parallel = Number.parseInt(options.parallel, 10);
@@ -83824,9 +84339,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
83824
84339
  headless: useHeadless,
83825
84340
  skipPrecheck: options.skipPrecheck ?? false
83826
84341
  });
83827
- const latestSymlink = join57(runsDir, "latest.jsonl");
84342
+ const latestSymlink = join53(runsDir, "latest.jsonl");
83828
84343
  try {
83829
- if (existsSync32(latestSymlink)) {
84344
+ if (existsSync31(latestSymlink)) {
83830
84345
  Bun.spawnSync(["rm", latestSymlink]);
83831
84346
  }
83832
84347
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -83862,9 +84377,9 @@ features.command("create <name>").description("Create a new feature").option("-d
83862
84377
  console.error(source_default.red("nax not initialized. Run: nax init"));
83863
84378
  process.exit(1);
83864
84379
  }
83865
- const featureDir = join57(naxDir, "features", name);
84380
+ const featureDir = join53(naxDir, "features", name);
83866
84381
  mkdirSync7(featureDir, { recursive: true });
83867
- await Bun.write(join57(featureDir, "spec.md"), `# Feature: ${name}
84382
+ await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
83868
84383
 
83869
84384
  ## Overview
83870
84385
 
@@ -83897,7 +84412,7 @@ features.command("create <name>").description("Create a new feature").option("-d
83897
84412
 
83898
84413
  <!-- What this feature explicitly does NOT cover. -->
83899
84414
  `);
83900
- await Bun.write(join57(featureDir, "progress.txt"), `# Progress: ${name}
84415
+ await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
83901
84416
 
83902
84417
  Created: ${new Date().toISOString()}
83903
84418
 
@@ -83923,8 +84438,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
83923
84438
  console.error(source_default.red("nax not initialized."));
83924
84439
  process.exit(1);
83925
84440
  }
83926
- const featuresDir = join57(naxDir, "features");
83927
- if (!existsSync32(featuresDir)) {
84441
+ const featuresDir = join53(naxDir, "features");
84442
+ if (!existsSync31(featuresDir)) {
83928
84443
  console.log(source_default.dim("No features yet."));
83929
84444
  return;
83930
84445
  }
@@ -83938,8 +84453,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
83938
84453
  Features:
83939
84454
  `));
83940
84455
  for (const name of entries) {
83941
- const prdPath = join57(featuresDir, name, "prd.json");
83942
- if (existsSync32(prdPath)) {
84456
+ const prdPath = join53(featuresDir, name, "prd.json");
84457
+ if (existsSync31(prdPath)) {
83943
84458
  const prd = await loadPRD(prdPath);
83944
84459
  const c = countStories(prd);
83945
84460
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -83973,10 +84488,10 @@ Use: nax plan -f <feature> --from <spec>`));
83973
84488
  cliOverrides.profile = options.profile;
83974
84489
  }
83975
84490
  const config2 = await loadConfig(workdir, cliOverrides);
83976
- const featureLogDir = join57(naxDir, "features", options.feature, "plan");
84491
+ const featureLogDir = join53(naxDir, "features", options.feature, "plan");
83977
84492
  mkdirSync7(featureLogDir, { recursive: true });
83978
84493
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
83979
- const planLogPath = join57(featureLogDir, `${planLogId}.jsonl`);
84494
+ const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
83980
84495
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
83981
84496
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
83982
84497
  try {