@nathapp/nax 0.58.5 → 0.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +811 -694
  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);
@@ -19186,6 +19188,7 @@ class AcpAgentAdapter {
19186
19188
  prompt: currentPrompt,
19187
19189
  sessionName,
19188
19190
  workdir: options.workdir,
19191
+ projectDir: options.projectDir,
19189
19192
  auditDir: _runAuditConfig.agent.promptAudit.dir,
19190
19193
  storyId: options.storyId,
19191
19194
  featureName: options.featureName,
@@ -19521,6 +19524,9 @@ class AcpAgentAdapter {
19521
19524
  }
19522
19525
  return { stories };
19523
19526
  }
19527
+ clearUnavailableAgents() {
19528
+ this._unavailableAgents.clear();
19529
+ }
19524
19530
  markUnavailable(agentName) {
19525
19531
  this._unavailableAgents.add(agentName);
19526
19532
  }
@@ -20089,6 +20095,75 @@ var init_interactive = __esm(() => {
20089
20095
  init_execution();
20090
20096
  });
20091
20097
 
20098
+ // src/config/path-security.ts
20099
+ import { existsSync as existsSync2, lstatSync, realpathSync } from "fs";
20100
+ import { basename, isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
20101
+ function validateDirectory(dirPath, baseDir) {
20102
+ const resolved = resolve2(dirPath);
20103
+ if (!existsSync2(resolved)) {
20104
+ throw new Error(`Directory does not exist: ${dirPath}`);
20105
+ }
20106
+ let realPath;
20107
+ try {
20108
+ realPath = realpathSync(resolved);
20109
+ } catch (error48) {
20110
+ throw new Error(`Failed to resolve path: ${dirPath} (${error48.message})`);
20111
+ }
20112
+ try {
20113
+ const stats = lstatSync(realPath);
20114
+ if (!stats.isDirectory()) {
20115
+ throw new Error(`Not a directory: ${dirPath}`);
20116
+ }
20117
+ } catch (error48) {
20118
+ throw new Error(`Failed to stat path: ${dirPath} (${error48.message})`);
20119
+ }
20120
+ if (baseDir) {
20121
+ const resolvedBase = resolve2(baseDir);
20122
+ const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
20123
+ if (!isWithinDirectory(realPath, realBase)) {
20124
+ throw new Error(`Path is outside allowed directory: ${dirPath} (resolved to ${realPath}, base: ${realBase})`);
20125
+ }
20126
+ }
20127
+ return realPath;
20128
+ }
20129
+ function isWithinDirectory(targetPath, basePath) {
20130
+ const normalizedTarget = normalize(targetPath);
20131
+ const normalizedBase = normalize(basePath);
20132
+ if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
20133
+ return false;
20134
+ }
20135
+ const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
20136
+ const targetWithSlash = normalizedTarget.endsWith("/") ? normalizedTarget : `${normalizedTarget}/`;
20137
+ return targetWithSlash.startsWith(baseWithSlash) || normalizedTarget === normalizedBase;
20138
+ }
20139
+ function validateFilePath(filePath, baseDir) {
20140
+ const resolved = resolve2(filePath);
20141
+ let realPath;
20142
+ try {
20143
+ if (!existsSync2(resolved)) {
20144
+ const parent = resolve2(resolved, "..");
20145
+ if (existsSync2(parent)) {
20146
+ const realParent = realpathSync(parent);
20147
+ realPath = resolve2(realParent, basename(resolved));
20148
+ } else {
20149
+ realPath = resolved;
20150
+ }
20151
+ } else {
20152
+ realPath = realpathSync(resolved);
20153
+ }
20154
+ } catch (error48) {
20155
+ throw new Error(`Failed to resolve path: ${filePath} (${error48.message})`);
20156
+ }
20157
+ const resolvedBase = resolve2(baseDir);
20158
+ const realBase = existsSync2(resolvedBase) ? realpathSync(resolvedBase) : resolvedBase;
20159
+ if (!isWithinDirectory(realPath, realBase)) {
20160
+ throw new Error(`Path is outside allowed directory: ${filePath} (resolved to ${realPath}, base: ${realBase})`);
20161
+ }
20162
+ return realPath;
20163
+ }
20164
+ var MAX_DIRECTORY_DEPTH = 10;
20165
+ var init_path_security = () => {};
20166
+
20092
20167
  // src/agents/shared/model-resolution.ts
20093
20168
  var exports_model_resolution = {};
20094
20169
  __export(exports_model_resolution, {
@@ -20114,7 +20189,7 @@ var init_model_resolution = __esm(() => {
20114
20189
  // src/agents/claude/plan.ts
20115
20190
  import { mkdtempSync, rmSync } from "fs";
20116
20191
  import { tmpdir } from "os";
20117
- import { join as join3 } from "path";
20192
+ import { join as join3, resolve as resolve3 } from "path";
20118
20193
  function buildPlanCommand(binary, options) {
20119
20194
  const cmd = [binary, "--permission-mode", "plan"];
20120
20195
  let modelDef = options.modelDef;
@@ -20134,17 +20209,12 @@ function buildPlanCommand(binary, options) {
20134
20209
 
20135
20210
  ${options.prompt}`;
20136
20211
  }
20137
- if (options.inputFile) {
20138
- try {
20139
- const inputContent = __require("fs").readFileSync(__require("path").resolve(options.workdir, options.inputFile), "utf-8");
20140
- fullPrompt = `${fullPrompt}
20212
+ if (options.resolvedInputContent) {
20213
+ fullPrompt = `${fullPrompt}
20141
20214
 
20142
20215
  ## Input Requirements
20143
20216
 
20144
- ${inputContent}`;
20145
- } catch (error48) {
20146
- throw new Error(`Failed to read input file ${options.inputFile}: ${error48.message}`);
20147
- }
20217
+ ${options.resolvedInputContent}`;
20148
20218
  }
20149
20219
  if (!options.interactive) {
20150
20220
  cmd.push("-p", fullPrompt);
@@ -20155,7 +20225,13 @@ ${inputContent}`;
20155
20225
  }
20156
20226
  async function runPlan(binary, options, pidRegistry) {
20157
20227
  const { resolveBalancedModelDef: resolveBalancedModelDef2 } = await Promise.resolve().then(() => (init_model_resolution(), exports_model_resolution));
20158
- const cmd = buildPlanCommand(binary, options);
20228
+ let resolvedOptions = options;
20229
+ if (options.inputFile) {
20230
+ const inputPath = validateFilePath(resolve3(options.workdir, options.inputFile), options.workdir);
20231
+ const resolvedInputContent = await Bun.file(inputPath).text();
20232
+ resolvedOptions = { ...options, resolvedInputContent };
20233
+ }
20234
+ const cmd = buildPlanCommand(binary, resolvedOptions);
20159
20235
  let modelDef = options.modelDef;
20160
20236
  if (!modelDef) {
20161
20237
  if (!options.config) {
@@ -20224,6 +20300,7 @@ async function runPlan(binary, options, pidRegistry) {
20224
20300
  }
20225
20301
  }
20226
20302
  var init_plan = __esm(() => {
20303
+ init_path_security();
20227
20304
  init_timeout_handler();
20228
20305
  init_logger2();
20229
20306
  init_env();
@@ -20376,8 +20453,8 @@ class ClaudeCodeAdapter {
20376
20453
  let stdoutTimeoutId;
20377
20454
  const stdout = await Promise.race([
20378
20455
  new Response(proc.stdout).text(),
20379
- new Promise((resolve2) => {
20380
- stdoutTimeoutId = setTimeout(() => resolve2(""), 5000);
20456
+ new Promise((resolve4) => {
20457
+ stdoutTimeoutId = setTimeout(() => resolve4(""), 5000);
20381
20458
  })
20382
20459
  ]);
20383
20460
  clearTimeout(stdoutTimeoutId);
@@ -20725,7 +20802,12 @@ function createAgentRegistry(config2) {
20725
20802
  installed: await agent.isInstalled()
20726
20803
  })));
20727
20804
  }
20728
- return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol };
20805
+ function resetStoryState() {
20806
+ for (const adapter of acpCache.values()) {
20807
+ adapter.clearUnavailableAgents();
20808
+ }
20809
+ }
20810
+ return { getAgent: getAgent2, getInstalledAgents: getInstalledAgents2, checkAgentHealth: checkAgentHealth2, protocol, resetStoryState };
20729
20811
  }
20730
20812
  var ALL_AGENTS;
20731
20813
  var init_registry = __esm(() => {
@@ -20745,75 +20827,6 @@ var init_registry = __esm(() => {
20745
20827
  ];
20746
20828
  });
20747
20829
 
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
20830
  // src/utils/json-file.ts
20818
20831
  import { existsSync as existsSync6 } from "fs";
20819
20832
  async function loadJsonFile(path, context = "json-file") {
@@ -21008,7 +21021,7 @@ function isPlainObject2(value) {
21008
21021
 
21009
21022
  // src/config/paths.ts
21010
21023
  import { homedir as homedir2 } from "os";
21011
- import { join as join7, resolve as resolve3 } from "path";
21024
+ import { join as join7, resolve as resolve4 } from "path";
21012
21025
  function globalConfigDir() {
21013
21026
  const override = process.env[GLOBAL_CONFIG_DIR_ENV];
21014
21027
  if (override)
@@ -21016,7 +21029,7 @@ function globalConfigDir() {
21016
21029
  return join7(homedir2(), ".nax");
21017
21030
  }
21018
21031
  function projectConfigDir(projectRoot) {
21019
- return join7(resolve3(projectRoot), PROJECT_NAX_DIR);
21032
+ return join7(resolve4(projectRoot), PROJECT_NAX_DIR);
21020
21033
  }
21021
21034
  var GLOBAL_CONFIG_DIR_ENV = "NAX_GLOBAL_CONFIG_DIR", PROJECT_NAX_DIR = ".nax";
21022
21035
  var init_paths = () => {};
@@ -21170,12 +21183,12 @@ var init_profile = __esm(() => {
21170
21183
 
21171
21184
  // src/config/loader.ts
21172
21185
  import { existsSync as existsSync7 } from "fs";
21173
- import { basename as basename2, dirname as dirname3, join as join9, resolve as resolve4 } from "path";
21186
+ import { basename as basename2, dirname as dirname2, join as join9, resolve as resolve5 } from "path";
21174
21187
  function globalConfigPath() {
21175
21188
  return join9(globalConfigDir(), "config.json");
21176
21189
  }
21177
21190
  function findProjectDir(startDir = process.cwd()) {
21178
- let dir = resolve4(startDir);
21191
+ let dir = resolve5(startDir);
21179
21192
  let depth = 0;
21180
21193
  while (depth < MAX_DIRECTORY_DEPTH) {
21181
21194
  const candidate = join9(dir, PROJECT_NAX_DIR);
@@ -21226,7 +21239,7 @@ function applyBatchModeCompat(conf) {
21226
21239
  async function loadConfig(startDir, cliOverrides) {
21227
21240
  let rawConfig = structuredClone(DEFAULT_CONFIG);
21228
21241
  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();
21242
+ const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname2(startDir) : startDir : process.cwd();
21230
21243
  const profileName = await resolveProfileName(cliOverrides ?? {}, process.env, projectRoot);
21231
21244
  const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
21232
21245
  if (globalConfRaw) {
@@ -21269,8 +21282,8 @@ ${errors3.join(`
21269
21282
  }
21270
21283
  async function loadConfigForWorkdir(rootConfigPath, packageDir) {
21271
21284
  const logger = getLogger();
21272
- const resolvedRootConfigPath = resolve4(rootConfigPath);
21273
- const rootNaxDir = dirname3(resolvedRootConfigPath);
21285
+ const resolvedRootConfigPath = resolve5(rootConfigPath);
21286
+ const rootNaxDir = dirname2(resolvedRootConfigPath);
21274
21287
  let rootConfigPromise = _rootConfigCache.get(resolvedRootConfigPath);
21275
21288
  if (!rootConfigPromise) {
21276
21289
  rootConfigPromise = loadConfig(rootNaxDir);
@@ -21281,7 +21294,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
21281
21294
  logger.debug("config", "No packageDir \u2014 using root config");
21282
21295
  return rootConfig;
21283
21296
  }
21284
- const repoRoot = dirname3(rootNaxDir);
21297
+ const repoRoot = dirname2(rootNaxDir);
21285
21298
  const packageConfigPath = join9(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
21286
21299
  const packageOverride = await loadJsonFile(packageConfigPath, "config");
21287
21300
  if (!packageOverride) {
@@ -22645,9 +22658,9 @@ ${request.summary}
22645
22658
  if (!this.rl) {
22646
22659
  throw new Error("CLI plugin not initialized");
22647
22660
  }
22648
- const timeoutPromise = new Promise((resolve5) => {
22661
+ const timeoutPromise = new Promise((resolve6) => {
22649
22662
  setTimeout(() => {
22650
- resolve5({
22663
+ resolve6({
22651
22664
  requestId: request.id,
22652
22665
  action: "skip",
22653
22666
  respondedBy: "timeout",
@@ -22799,9 +22812,9 @@ ${request.summary}
22799
22812
  if (!this.rl) {
22800
22813
  throw new Error("CLI plugin not initialized");
22801
22814
  }
22802
- return new Promise((resolve5) => {
22815
+ return new Promise((resolve6) => {
22803
22816
  this.rl?.question(prompt, (answer) => {
22804
- resolve5(answer);
22817
+ resolve6(answer);
22805
22818
  });
22806
22819
  });
22807
22820
  }
@@ -23233,7 +23246,7 @@ class WebhookInteractionPlugin {
23233
23246
  this.pendingResponses.delete(requestId);
23234
23247
  return early;
23235
23248
  }
23236
- return new Promise((resolve5) => {
23249
+ return new Promise((resolve6) => {
23237
23250
  const existingCallback = this.receiveCallbacks.get(requestId);
23238
23251
  if (existingCallback) {
23239
23252
  this.clearReceiveTimer(requestId);
@@ -23247,7 +23260,7 @@ class WebhookInteractionPlugin {
23247
23260
  const timer = setTimeout(() => {
23248
23261
  this.clearReceiveTimer(requestId);
23249
23262
  this.receiveCallbacks.delete(requestId);
23250
- resolve5({
23263
+ resolve6({
23251
23264
  requestId,
23252
23265
  action: "skip",
23253
23266
  respondedBy: "timeout",
@@ -23258,7 +23271,7 @@ class WebhookInteractionPlugin {
23258
23271
  this.receiveCallbacks.set(requestId, (response) => {
23259
23272
  this.clearReceiveTimer(requestId);
23260
23273
  this.receiveCallbacks.delete(requestId);
23261
- resolve5(response);
23274
+ resolve6(response);
23262
23275
  });
23263
23276
  });
23264
23277
  }
@@ -25203,6 +25216,7 @@ async function runPipeline(stages, context, eventEmitter) {
25203
25216
  const logger = getLogger();
25204
25217
  const retryCountMap = new Map;
25205
25218
  let i = 0;
25219
+ let stageCostAccum = 0;
25206
25220
  while (i < stages.length) {
25207
25221
  const stage = stages[i];
25208
25222
  if (!stage.enabled(context)) {
@@ -25221,27 +25235,58 @@ async function runPipeline(stages, context, eventEmitter) {
25221
25235
  reason: `Stage "${stage.name}" threw error: ${errorMessage(error48)}`
25222
25236
  };
25223
25237
  eventEmitter?.emit("stage:exit", stage.name, failResult);
25224
- return { success: false, finalAction: "fail", reason: failResult.reason, stoppedAtStage: stage.name, context };
25238
+ return {
25239
+ success: false,
25240
+ finalAction: "fail",
25241
+ reason: failResult.reason,
25242
+ stoppedAtStage: stage.name,
25243
+ context,
25244
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25245
+ };
25225
25246
  }
25247
+ if (result.cost)
25248
+ stageCostAccum += result.cost;
25226
25249
  eventEmitter?.emit("stage:exit", stage.name, result);
25227
25250
  switch (result.action) {
25228
25251
  case "continue":
25229
25252
  i++;
25230
25253
  continue;
25231
25254
  case "skip":
25232
- return { success: false, finalAction: "skip", reason: result.reason, stoppedAtStage: stage.name, context };
25255
+ return {
25256
+ success: false,
25257
+ finalAction: "skip",
25258
+ reason: result.reason,
25259
+ stoppedAtStage: stage.name,
25260
+ context,
25261
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25262
+ };
25233
25263
  case "fail":
25234
- return { success: false, finalAction: "fail", reason: result.reason, stoppedAtStage: stage.name, context };
25264
+ return {
25265
+ success: false,
25266
+ finalAction: "fail",
25267
+ reason: result.reason,
25268
+ stoppedAtStage: stage.name,
25269
+ context,
25270
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25271
+ };
25235
25272
  case "escalate":
25236
25273
  return {
25237
25274
  success: false,
25238
25275
  finalAction: "escalate",
25239
25276
  reason: result.reason ?? "Stage requested escalation to higher tier",
25240
25277
  stoppedAtStage: stage.name,
25241
- context
25278
+ context,
25279
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25242
25280
  };
25243
25281
  case "pause":
25244
- return { success: false, finalAction: "pause", reason: result.reason, stoppedAtStage: stage.name, context };
25282
+ return {
25283
+ success: false,
25284
+ finalAction: "pause",
25285
+ reason: result.reason,
25286
+ stoppedAtStage: stage.name,
25287
+ context,
25288
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25289
+ };
25245
25290
  case "retry": {
25246
25291
  const retries = (retryCountMap.get(result.fromStage) ?? 0) + 1;
25247
25292
  if (retries > MAX_STAGE_RETRIES) {
@@ -25251,7 +25296,8 @@ async function runPipeline(stages, context, eventEmitter) {
25251
25296
  finalAction: "fail",
25252
25297
  reason: `Stage "${stage.name}" exceeded max retries (${MAX_STAGE_RETRIES}) for "${result.fromStage}"`,
25253
25298
  stoppedAtStage: stage.name,
25254
- context
25299
+ context,
25300
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25255
25301
  };
25256
25302
  }
25257
25303
  retryCountMap.set(result.fromStage, retries);
@@ -25263,7 +25309,8 @@ async function runPipeline(stages, context, eventEmitter) {
25263
25309
  finalAction: "escalate",
25264
25310
  reason: `Retry target stage "${result.fromStage}" not found`,
25265
25311
  stoppedAtStage: stage.name,
25266
- context
25312
+ context,
25313
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25267
25314
  };
25268
25315
  }
25269
25316
  logger.debug("pipeline", `Retrying from stage "${result.fromStage}" (attempt ${retries}/${MAX_STAGE_RETRIES})`);
@@ -25276,7 +25323,12 @@ async function runPipeline(stages, context, eventEmitter) {
25276
25323
  }
25277
25324
  }
25278
25325
  }
25279
- return { success: true, finalAction: "complete", context };
25326
+ return {
25327
+ success: true,
25328
+ finalAction: "complete",
25329
+ context,
25330
+ stageCost: stageCostAccum > 0 ? stageCostAccum : undefined
25331
+ };
25280
25332
  }
25281
25333
  var MAX_STAGE_RETRIES = 5;
25282
25334
  var init_runner = __esm(() => {
@@ -25814,8 +25866,7 @@ var init_acceptance = __esm(() => {
25814
25866
  acceptanceStage = {
25815
25867
  name: "acceptance",
25816
25868
  enabled(ctx) {
25817
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
25818
- if (!effectiveConfig.acceptance.enabled) {
25869
+ if (!ctx.config.acceptance.enabled) {
25819
25870
  return false;
25820
25871
  }
25821
25872
  if (!areAllStoriesComplete(ctx)) {
@@ -25825,7 +25876,6 @@ var init_acceptance = __esm(() => {
25825
25876
  },
25826
25877
  async execute(ctx) {
25827
25878
  const logger = getLogger();
25828
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
25829
25879
  logger.info("acceptance", "Running acceptance tests", { storyId: ctx.story.id });
25830
25880
  if (!ctx.featureDir) {
25831
25881
  logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests", { storyId: ctx.story.id });
@@ -25833,7 +25883,7 @@ var init_acceptance = __esm(() => {
25833
25883
  }
25834
25884
  const testGroups = ctx.acceptanceTestPaths ?? [
25835
25885
  {
25836
- testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir, effectiveConfig.acceptance.testPath, effectiveConfig.project?.language),
25886
+ testPath: resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language),
25837
25887
  packageDir: ctx.workdir
25838
25888
  }
25839
25889
  ];
@@ -25848,7 +25898,7 @@ var init_acceptance = __esm(() => {
25848
25898
  logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { storyId: ctx.story.id, testPath });
25849
25899
  continue;
25850
25900
  }
25851
- const testCmdParts = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
25901
+ const testCmdParts = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
25852
25902
  logger.info("acceptance", "Running acceptance command", {
25853
25903
  storyId: ctx.story.id,
25854
25904
  cmd: testCmdParts.join(" "),
@@ -25943,12 +25993,12 @@ function buildRefinementPrompt(criteria, codebaseContext, options) {
25943
25993
  `);
25944
25994
  const strategySection = buildStrategySection(options);
25945
25995
  const refinedExample = buildRefinedExample(options?.testStrategy);
25996
+ const codebaseSection = codebaseContext ? `CODEBASE CONTEXT:
25997
+ ${codebaseContext}
25998
+ ` : "";
25946
25999
  const core2 = `You are an acceptance criteria refinement assistant. Your task is to convert raw acceptance criteria into concrete, machine-verifiable assertions.
25947
26000
 
25948
- CODEBASE CONTEXT:
25949
- ${codebaseContext}
25950
- ${strategySection}
25951
- ACCEPTANCE CRITERIA TO REFINE:
26001
+ ${codebaseSection}${strategySection}ACCEPTANCE CRITERIA TO REFINE:
25952
26002
  ${criteriaList}
25953
26003
 
25954
26004
  For each criterion, produce a refined version that is concrete and automatically testable where possible.
@@ -26201,12 +26251,11 @@ ${stderr}` };
26201
26251
  if (!ctx.featureDir) {
26202
26252
  return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
26203
26253
  }
26204
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
26205
- const language = effectiveConfig.project?.language;
26206
- const testPathConfig = (ctx.effectiveConfig ?? ctx.config).acceptance.testPath;
26254
+ const language = ctx.config.project?.language;
26255
+ const testPathConfig = ctx.config.acceptance.testPath;
26207
26256
  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-"));
26257
+ const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed").flatMap((s) => s.acceptanceCriteria);
26258
+ const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-") && s.status !== "decomposed");
26210
26259
  const workdirGroups = new Map;
26211
26260
  for (const story of nonFixStories) {
26212
26261
  const wd = story.workdir ?? "";
@@ -26262,7 +26311,7 @@ ${stderr}` };
26262
26311
  }
26263
26312
  if (shouldGenerate) {
26264
26313
  totalCriteria = allCriteria.length;
26265
- const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.config.autoMode.defaultAgent);
26314
+ const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
26266
26315
  let allRefinedCriteria;
26267
26316
  if (ctx.config.acceptance.refinement) {
26268
26317
  const maxConcurrency = ctx.config.acceptance.refinementConcurrency ?? 3;
@@ -26306,7 +26355,7 @@ ${stderr}` };
26306
26355
  const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
26307
26356
  let modelDef;
26308
26357
  try {
26309
- modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, ctx.config.acceptance.model ?? "fast", ctx.config.autoMode.defaultAgent);
26358
+ modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.config.acceptance.model ?? "fast", ctx.rootConfig.autoMode.defaultAgent);
26310
26359
  } catch {
26311
26360
  const tier = ctx.config.acceptance.model ?? "fast";
26312
26361
  modelDef = { provider: "anthropic", model: tier };
@@ -26342,7 +26391,7 @@ ${stderr}` };
26342
26391
  }
26343
26392
  let redFailCount = 0;
26344
26393
  for (const { testPath, packageDir } of testPaths) {
26345
- const runCmd = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
26394
+ const runCmd = buildAcceptanceRunCommand(testPath, ctx.config.project?.testFramework, ctx.config.acceptance.command);
26346
26395
  getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
26347
26396
  cmd: runCmd.join(" "),
26348
26397
  packageDir
@@ -26883,10 +26932,11 @@ ${prompt}`,
26883
26932
  history.push({ role: "implementer", content: prompt });
26884
26933
  history.push({ role: "reviewer", content: result.output });
26885
26934
  const parsed = parseReviewResponse(result.output);
26886
- lastCheckResult = parsed;
26935
+ const reviewResult = { ...parsed, cost: result.estimatedCost ?? 0 };
26936
+ lastCheckResult = reviewResult;
26887
26937
  lastStory = story;
26888
26938
  lastSemanticConfig = semanticConfig;
26889
- return parsed;
26939
+ return reviewResult;
26890
26940
  },
26891
26941
  async reReview(updatedDiff) {
26892
26942
  if (!active) {
@@ -26920,7 +26970,7 @@ ${prompt}`,
26920
26970
  history.push({ role: "reviewer", content: result.output });
26921
26971
  const parsed = parseReviewResponse(result.output);
26922
26972
  const deltaSummary = extractDeltaSummary(result.output, previousFindings, parsed.checkResult.findings);
26923
- const dialogueResult = { ...parsed, deltaSummary };
26973
+ const dialogueResult = { ...parsed, deltaSummary, cost: result.estimatedCost ?? 0 };
26924
26974
  lastCheckResult = dialogueResult;
26925
26975
  const maxMessages = _config.review?.dialogue?.maxDialogueMessages ?? 20;
26926
26976
  if (history.length > maxMessages) {
@@ -27433,6 +27483,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
27433
27483
  timeoutSeconds: naxConfig?.execution?.sessionTimeoutSeconds
27434
27484
  });
27435
27485
  const debateResult = await debateSession.run(prompt);
27486
+ const debateCost = debateResult.totalCostUsd ?? 0;
27436
27487
  let passCount = 0;
27437
27488
  let failCount = 0;
27438
27489
  const allFindings = [];
@@ -27475,7 +27526,8 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
27475
27526
 
27476
27527
  ${formatFindings(debateBlocking)}`,
27477
27528
  durationMs: durationMs2,
27478
- findings: toReviewFindings(debateBlocking)
27529
+ findings: toReviewFindings(debateBlocking),
27530
+ cost: debateCost
27479
27531
  };
27480
27532
  }
27481
27533
  logger?.info("review", "Semantic review passed (debate, all findings non-blocking)", {
@@ -27488,7 +27540,8 @@ ${formatFindings(debateBlocking)}`,
27488
27540
  command: "",
27489
27541
  exitCode: 0,
27490
27542
  output: "Semantic review passed (debate, all findings were unverifiable or informational)",
27491
- durationMs: durationMs2
27543
+ durationMs: durationMs2,
27544
+ cost: debateCost
27492
27545
  };
27493
27546
  }
27494
27547
  logger?.info("review", "Semantic review passed (debate)", { storyId: story.id, durationMs: durationMs2 });
@@ -27498,7 +27551,8 @@ ${formatFindings(debateBlocking)}`,
27498
27551
  command: "",
27499
27552
  exitCode: 0,
27500
27553
  output: "Semantic review passed",
27501
- durationMs: durationMs2
27554
+ durationMs: durationMs2,
27555
+ cost: debateCost
27502
27556
  };
27503
27557
  }
27504
27558
  const implementerSidecarKey = `${story.id}:implementer`;
@@ -27517,6 +27571,7 @@ ${formatFindings(debateBlocking)}`,
27517
27571
  }
27518
27572
  } catch {}
27519
27573
  let rawResponse;
27574
+ let llmCost = 0;
27520
27575
  try {
27521
27576
  let runErr;
27522
27577
  let runSucceeded = false;
@@ -27533,6 +27588,7 @@ ${formatFindings(debateBlocking)}`,
27533
27588
  config: naxConfig ?? DEFAULT_CONFIG
27534
27589
  });
27535
27590
  runOutput = runResult.output;
27591
+ llmCost = runResult.estimatedCost ?? 0;
27536
27592
  runSucceeded = true;
27537
27593
  } catch (err) {
27538
27594
  runErr = err;
@@ -27548,6 +27604,7 @@ ${formatFindings(debateBlocking)}`,
27548
27604
  config: naxConfig ?? DEFAULT_CONFIG
27549
27605
  });
27550
27606
  rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
27607
+ llmCost = typeof completeResult === "string" ? 0 : completeResult.costUsd ?? 0;
27551
27608
  }
27552
27609
  } catch (err) {
27553
27610
  logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
@@ -27573,7 +27630,8 @@ ${formatFindings(debateBlocking)}`,
27573
27630
  command: "",
27574
27631
  exitCode: 1,
27575
27632
  output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
27576
- durationMs: Date.now() - startTime
27633
+ durationMs: Date.now() - startTime,
27634
+ cost: llmCost
27577
27635
  };
27578
27636
  }
27579
27637
  logger?.warn("semantic", "LLM returned invalid JSON \u2014 fail-open", { rawResponse: rawResponse.slice(0, 200) });
@@ -27583,7 +27641,8 @@ ${formatFindings(debateBlocking)}`,
27583
27641
  command: "",
27584
27642
  exitCode: 0,
27585
27643
  output: "semantic review: could not parse LLM response (fail-open)",
27586
- durationMs: Date.now() - startTime
27644
+ durationMs: Date.now() - startTime,
27645
+ cost: llmCost
27587
27646
  };
27588
27647
  }
27589
27648
  const blockingFindings = parsed.findings.filter((f) => isBlockingSeverity(f.severity));
@@ -27620,7 +27679,8 @@ ${formatFindings(blockingFindings)}`;
27620
27679
  exitCode: 1,
27621
27680
  output,
27622
27681
  durationMs: durationMs2,
27623
- findings: toReviewFindings(blockingFindings)
27682
+ findings: toReviewFindings(blockingFindings),
27683
+ cost: llmCost
27624
27684
  };
27625
27685
  }
27626
27686
  if (!parsed.passed && blockingFindings.length === 0) {
@@ -27632,7 +27692,8 @@ ${formatFindings(blockingFindings)}`;
27632
27692
  command: "",
27633
27693
  exitCode: 0,
27634
27694
  output: "Semantic review passed (all findings were unverifiable or informational)",
27635
- durationMs: durationMs2
27695
+ durationMs: durationMs2,
27696
+ cost: llmCost
27636
27697
  };
27637
27698
  }
27638
27699
  const durationMs = Date.now() - startTime;
@@ -27645,7 +27706,8 @@ ${formatFindings(blockingFindings)}`;
27645
27706
  command: "",
27646
27707
  exitCode: parsed.passed ? 0 : 1,
27647
27708
  output: parsed.passed ? "Semantic review passed" : "Semantic review failed (no findings)",
27648
- durationMs
27709
+ durationMs,
27710
+ cost: llmCost
27649
27711
  };
27650
27712
  }
27651
27713
  var _semanticDeps, DIFF_CAP_BYTES = 51200;
@@ -27951,6 +28013,19 @@ class ReviewOrchestrator {
27951
28013
  }
27952
28014
  return { builtIn, success: true, pluginFailed: false };
27953
28015
  }
28016
+ reviewFromContext(ctx) {
28017
+ const retrySkipChecks = ctx.retrySkipChecks;
28018
+ ctx.retrySkipChecks = undefined;
28019
+ const agentResolver = ctx.agentGetFn ?? undefined;
28020
+ const agentName = ctx.rootConfig.autoMode?.defaultAgent;
28021
+ const modelResolver = agentName ? (_tier) => agentResolver ? agentResolver(agentName) ?? null : null : undefined;
28022
+ 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, {
28023
+ id: ctx.story.id,
28024
+ title: ctx.story.title,
28025
+ description: ctx.story.description,
28026
+ acceptanceCriteria: ctx.story.acceptanceCriteria
28027
+ }, modelResolver, ctx.config, retrySkipChecks, ctx.prd.feature);
28028
+ }
27954
28029
  }
27955
28030
  var _orchestratorDeps, reviewOrchestrator;
27956
28031
  var init_orchestrator = __esm(() => {
@@ -27966,7 +28041,6 @@ __export(exports_review, {
27966
28041
  reviewStage: () => reviewStage,
27967
28042
  _reviewDeps: () => _reviewDeps
27968
28043
  });
27969
- import { join as join17 } from "path";
27970
28044
  var reviewStage, _reviewDeps;
27971
28045
  var init_review = __esm(() => {
27972
28046
  init_agents();
@@ -27976,18 +28050,13 @@ var init_review = __esm(() => {
27976
28050
  init_orchestrator();
27977
28051
  reviewStage = {
27978
28052
  name: "review",
27979
- enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
28053
+ enabled: (ctx) => ctx.config.review.enabled,
27980
28054
  async execute(ctx) {
27981
28055
  const logger = getLogger();
27982
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27983
- const dialogueEnabled = effectiveConfig.review?.dialogue?.enabled ?? false;
28056
+ const dialogueEnabled = ctx.config.review?.dialogue?.enabled ?? false;
27984
28057
  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
28058
  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;
28059
+ const agentName = ctx.rootConfig.autoMode?.defaultAgent;
27991
28060
  if (dialogueEnabled && ctx.reviewerSession) {
27992
28061
  try {
27993
28062
  const diff = ctx.storyGitRef ?? "";
@@ -28025,8 +28094,8 @@ var init_review = __esm(() => {
28025
28094
  }
28026
28095
  if (dialogueEnabled && !ctx.reviewerSession) {
28027
28096
  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;
28097
+ ctx.reviewerSession = _reviewDeps.createReviewerSession(agent ?? null, ctx.story.id, ctx.workdir, ctx.prd.feature ?? "", ctx.config);
28098
+ const semanticConfig = ctx.config.review?.semantic;
28030
28099
  if (semanticConfig && agent) {
28031
28100
  try {
28032
28101
  const diff = ctx.storyGitRef ?? "";
@@ -28054,6 +28123,7 @@ var init_review = __esm(() => {
28054
28123
  ],
28055
28124
  totalDurationMs: 0
28056
28125
  };
28126
+ const dialogueCost = sessionResult.cost ?? 0;
28057
28127
  if (passed) {
28058
28128
  logger.info("review", "Review passed (dialogue session)", { storyId: ctx.story.id });
28059
28129
  } else {
@@ -28061,7 +28131,7 @@ var init_review = __esm(() => {
28061
28131
  storyId: ctx.story.id
28062
28132
  });
28063
28133
  }
28064
- return { action: "continue" };
28134
+ return { action: "continue", cost: dialogueCost || undefined };
28065
28135
  } catch (err) {
28066
28136
  logger.warn("review", "ReviewerSession.review() failed \u2014 falling back to one-shot review", {
28067
28137
  storyId: ctx.story.id
@@ -28069,13 +28139,9 @@ var init_review = __esm(() => {
28069
28139
  }
28070
28140
  }
28071
28141
  }
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);
28142
+ const result = await reviewOrchestrator.reviewFromContext(ctx);
28078
28143
  ctx.reviewResult = result.builtIn;
28144
+ const reviewCost = (result.builtIn.checks ?? []).reduce((sum, c) => sum + (c.cost ?? 0), 0) || undefined;
28079
28145
  if (!result.success) {
28080
28146
  const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
28081
28147
  const semanticFindings = (result.builtIn.checks ?? []).filter((c) => c.check === "semantic" && !c.success && c.findings?.length).flatMap((c) => c.findings ?? []);
@@ -28084,29 +28150,29 @@ var init_review = __esm(() => {
28084
28150
  ctx.reviewFindings = allFindings;
28085
28151
  }
28086
28152
  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);
28153
+ if (ctx.interaction && isTriggerEnabled("security-review", ctx.config)) {
28154
+ const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
28089
28155
  if (!shouldContinue) {
28090
28156
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
28091
- return { action: "fail", reason: `Review failed: ${result.failureReason}` };
28157
+ return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
28092
28158
  }
28093
28159
  logger.warn("review", "Security-review trigger escalated \u2014 retrying story", { storyId: ctx.story.id });
28094
- return { action: "escalate", reason: `Review failed: ${result.failureReason}` };
28160
+ return { action: "escalate", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
28095
28161
  }
28096
28162
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
28097
- return { action: "fail", reason: `Review failed: ${result.failureReason}` };
28163
+ return { action: "fail", reason: `Review failed: ${result.failureReason}`, cost: reviewCost };
28098
28164
  }
28099
28165
  logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
28100
28166
  reason: result.failureReason,
28101
28167
  storyId: ctx.story.id
28102
28168
  });
28103
- return { action: "continue" };
28169
+ return { action: "continue", cost: reviewCost };
28104
28170
  }
28105
28171
  logger.info("review", "Review passed", {
28106
28172
  durationMs: result.builtIn.totalDurationMs,
28107
28173
  storyId: ctx.story.id
28108
28174
  });
28109
- return { action: "continue" };
28175
+ return { action: "continue", cost: reviewCost };
28110
28176
  }
28111
28177
  };
28112
28178
  _reviewDeps = {
@@ -28116,7 +28182,6 @@ var init_review = __esm(() => {
28116
28182
  });
28117
28183
 
28118
28184
  // src/pipeline/stages/autofix.ts
28119
- import { join as join18 } from "path";
28120
28185
  async function recheckReview(ctx) {
28121
28186
  const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
28122
28187
  if (!reviewStage2.enabled(ctx))
@@ -28154,16 +28219,15 @@ Your previous fix attempt (attempt ${attempt}) did not resolve the quality error
28154
28219
  }
28155
28220
  async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir) {
28156
28221
  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;
28222
+ const maxPerCycle = ctx.config.quality.autofix?.maxAttempts ?? 2;
28223
+ const maxTotal = ctx.config.quality.autofix?.maxTotalAttempts ?? 10;
28224
+ const rethinkAtAttempt = ctx.config.quality.autofix?.rethinkAtAttempt ?? 2;
28225
+ const urgencyAtAttempt = ctx.config.quality.autofix?.urgencyAtAttempt ?? 3;
28162
28226
  const consumed = ctx.autofixAttempt ?? 0;
28163
28227
  const failedChecks = collectFailedChecks(ctx);
28164
28228
  if (failedChecks.length === 0) {
28165
28229
  logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
28166
- return false;
28230
+ return { succeeded: false, cost: 0 };
28167
28231
  }
28168
28232
  if (consumed >= maxTotal) {
28169
28233
  logger.warn("autofix", "Global autofix budget exhausted \u2014 escalating", {
@@ -28171,7 +28235,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28171
28235
  totalAttempts: consumed,
28172
28236
  maxTotalAttempts: maxTotal
28173
28237
  });
28174
- return false;
28238
+ return { succeeded: false, cost: 0 };
28175
28239
  }
28176
28240
  const remainingBudget = maxTotal - consumed;
28177
28241
  const maxAttempts = Math.min(maxPerCycle, remainingBudget);
@@ -28180,7 +28244,8 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28180
28244
  attempt: 0,
28181
28245
  failedChecks
28182
28246
  };
28183
- return runSharedRectificationLoop({
28247
+ let autofixCostAccum = 0;
28248
+ const succeeded = await runSharedRectificationLoop({
28184
28249
  stage: "autofix",
28185
28250
  storyId: ctx.story.id,
28186
28251
  maxAttempts,
@@ -28207,29 +28272,30 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28207
28272
  },
28208
28273
  runAttempt: async (attempt, prompt) => {
28209
28274
  ctx.autofixAttempt = consumed + attempt;
28210
- const agent = agentGetFn(ctx.config.autoMode.defaultAgent);
28275
+ const agent = agentGetFn(ctx.rootConfig.autoMode.defaultAgent);
28211
28276
  if (!agent) {
28212
28277
  logger.error("autofix", "Agent not found \u2014 cannot run agent rectification", { storyId: ctx.story.id });
28213
28278
  throw new Error("AUTOFIX_AGENT_NOT_FOUND");
28214
28279
  }
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;
28280
+ const modelTier = ctx.story.routing?.modelTier ?? ctx.rootConfig.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
28281
+ const modelDef = resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, modelTier, ctx.rootConfig.autoMode.defaultAgent);
28218
28282
  const result = await agent.run({
28219
28283
  prompt,
28220
- workdir: rectificationWorkdir,
28284
+ workdir: ctx.workdir,
28221
28285
  modelTier,
28222
28286
  modelDef,
28223
28287
  timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
28224
28288
  dangerouslySkipPermissions: resolvePermissions(ctx.config, "rectification").skipPermissions,
28225
28289
  pipelineStage: "rectification",
28226
28290
  config: ctx.config,
28291
+ projectDir: ctx.projectDir,
28227
28292
  maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
28228
28293
  storyId: ctx.story.id,
28229
28294
  sessionRole: "implementer"
28230
28295
  });
28296
+ autofixCostAccum += result.estimatedCost ?? 0;
28231
28297
  if (ctx.reviewerSession && result.output) {
28232
- const maxClarifications = effectiveConfig.review?.dialogue?.maxClarificationsPerAttempt ?? 3;
28298
+ const maxClarifications = ctx.config.review?.dialogue?.maxClarificationsPerAttempt ?? 3;
28233
28299
  let clarifyCount = 0;
28234
28300
  const clarifyRegex = new RegExp(CLARIFY_REGEX.source, `${CLARIFY_REGEX.flags}g`);
28235
28301
  let match;
@@ -28312,6 +28378,7 @@ async function runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWor
28312
28378
  }
28313
28379
  throw error48;
28314
28380
  });
28381
+ return { succeeded, cost: autofixCostAccum };
28315
28382
  }
28316
28383
  var CLARIFY_REGEX, autofixStage, _autofixDeps;
28317
28384
  var init_autofix = __esm(() => {
@@ -28329,7 +28396,7 @@ var init_autofix = __esm(() => {
28329
28396
  return false;
28330
28397
  if (ctx.reviewResult.success)
28331
28398
  return false;
28332
- const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
28399
+ const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
28333
28400
  return autofixEnabled;
28334
28401
  },
28335
28402
  skipReason(ctx) {
@@ -28343,16 +28410,14 @@ var init_autofix = __esm(() => {
28343
28410
  if (!reviewResult || reviewResult.success) {
28344
28411
  return { action: "continue" };
28345
28412
  }
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;
28413
+ const lintFixCmd = ctx.config.quality.commands.lintFix ?? ctx.config.review.commands.lintFix;
28414
+ const formatFixCmd = ctx.config.quality.commands.formatFix ?? ctx.config.review.commands.formatFix;
28350
28415
  const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
28351
28416
  const hasLintFailure = failedCheckNames.has("lint");
28352
28417
  logger.info("autofix", "Starting autofix", {
28353
28418
  storyId: ctx.story.id,
28354
28419
  failedChecks: [...failedCheckNames],
28355
- workdir: effectiveWorkdir
28420
+ workdir: ctx.workdir
28356
28421
  });
28357
28422
  if (hasLintFailure && (lintFixCmd || formatFixCmd)) {
28358
28423
  if (lintFixCmd) {
@@ -28360,7 +28425,7 @@ var init_autofix = __esm(() => {
28360
28425
  const lintResult = await _autofixDeps.runQualityCommand({
28361
28426
  commandName: "lintFix",
28362
28427
  command: lintFixCmd,
28363
- workdir: effectiveWorkdir,
28428
+ workdir: ctx.workdir,
28364
28429
  storyId: ctx.story.id
28365
28430
  });
28366
28431
  logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id, command: lintFixCmd });
@@ -28376,7 +28441,7 @@ var init_autofix = __esm(() => {
28376
28441
  const fmtResult = await _autofixDeps.runQualityCommand({
28377
28442
  commandName: "formatFix",
28378
28443
  command: formatFixCmd,
28379
- workdir: effectiveWorkdir,
28444
+ workdir: ctx.workdir,
28380
28445
  storyId: ctx.story.id
28381
28446
  });
28382
28447
  logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, {
@@ -28400,7 +28465,7 @@ var init_autofix = __esm(() => {
28400
28465
  storyId: ctx.story.id
28401
28466
  });
28402
28467
  }
28403
- const agentFixed = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, effectiveWorkdir);
28468
+ const { succeeded: agentFixed, cost: agentCost } = await _autofixDeps.runAgentRectification(ctx, lintFixCmd, formatFixCmd, ctx.workdir);
28404
28469
  if (agentFixed) {
28405
28470
  if (ctx.reviewResult)
28406
28471
  ctx.reviewResult = { ...ctx.reviewResult, success: true };
@@ -28413,10 +28478,14 @@ var init_autofix = __esm(() => {
28413
28478
  });
28414
28479
  }
28415
28480
  logger.info("autofix", "Agent rectification succeeded \u2014 retrying review", { storyId: ctx.story.id });
28416
- return { action: "retry", fromStage: "review" };
28481
+ return { action: "retry", fromStage: "review", cost: agentCost };
28417
28482
  }
28418
28483
  logger.warn("autofix", "Autofix exhausted \u2014 escalating", { storyId: ctx.story.id });
28419
- return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
28484
+ return {
28485
+ action: "escalate",
28486
+ reason: "Autofix exhausted: review still failing after fix attempts",
28487
+ cost: agentCost
28488
+ };
28420
28489
  }
28421
28490
  };
28422
28491
  _autofixDeps = {
@@ -28486,10 +28555,10 @@ var init_semantic_verdict = __esm(() => {
28486
28555
 
28487
28556
  // src/execution/progress.ts
28488
28557
  import { appendFile as appendFile2, mkdir } from "fs/promises";
28489
- import { join as join19 } from "path";
28558
+ import { join as join17 } from "path";
28490
28559
  async function appendProgress(featureDir, storyId, status, message) {
28491
28560
  await mkdir(featureDir, { recursive: true });
28492
- const progressPath = join19(featureDir, "progress.txt");
28561
+ const progressPath = join17(featureDir, "progress.txt");
28493
28562
  const timestamp = new Date().toISOString();
28494
28563
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
28495
28564
  `;
@@ -28595,7 +28664,7 @@ function estimateTokens(text) {
28595
28664
 
28596
28665
  // src/constitution/loader.ts
28597
28666
  import { existsSync as existsSync17 } from "fs";
28598
- import { join as join20 } from "path";
28667
+ import { join as join18 } from "path";
28599
28668
  function truncateToTokens(text, maxTokens) {
28600
28669
  const maxChars = maxTokens * 3;
28601
28670
  if (text.length <= maxChars) {
@@ -28617,7 +28686,7 @@ async function loadConstitution(projectDir, config2) {
28617
28686
  }
28618
28687
  let combinedContent = "";
28619
28688
  if (!config2.skipGlobal) {
28620
- const globalPath = join20(globalConfigDir(), config2.path);
28689
+ const globalPath = join18(globalConfigDir(), config2.path);
28621
28690
  if (existsSync17(globalPath)) {
28622
28691
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
28623
28692
  const globalFile = Bun.file(validatedPath);
@@ -28627,7 +28696,7 @@ async function loadConstitution(projectDir, config2) {
28627
28696
  }
28628
28697
  }
28629
28698
  }
28630
- const projectPath = join20(projectDir, config2.path);
28699
+ const projectPath = join18(projectDir, config2.path);
28631
28700
  if (existsSync17(projectPath)) {
28632
28701
  const validatedPath = validateFilePath(projectPath, projectDir);
28633
28702
  const projectFile = Bun.file(validatedPath);
@@ -28675,7 +28744,7 @@ var init_constitution = __esm(() => {
28675
28744
  });
28676
28745
 
28677
28746
  // src/pipeline/stages/constitution.ts
28678
- import { dirname as dirname4 } from "path";
28747
+ import { dirname as dirname3 } from "path";
28679
28748
  var constitutionStage;
28680
28749
  var init_constitution2 = __esm(() => {
28681
28750
  init_constitution();
@@ -28685,7 +28754,7 @@ var init_constitution2 = __esm(() => {
28685
28754
  enabled: (ctx) => ctx.config.constitution.enabled,
28686
28755
  async execute(ctx) {
28687
28756
  const logger = getLogger();
28688
- const ngentDir = ctx.featureDir ? dirname4(dirname4(ctx.featureDir)) : `${ctx.workdir}/nax`;
28757
+ const ngentDir = ctx.featureDir ? dirname3(dirname3(ctx.featureDir)) : `${ctx.workdir}/nax`;
28689
28758
  const result = await loadConstitution(ngentDir, ctx.config.constitution);
28690
28759
  if (result) {
28691
28760
  ctx.constitution = result;
@@ -28850,6 +28919,27 @@ function createStoryContext(story, priority) {
28850
28919
  return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
28851
28920
  }
28852
28921
  function createDependencyContext(story, priority) {
28922
+ const content = isCompletedDependency(story) ? formatCompletedDependency(story) : formatFullDependency(story);
28923
+ return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
28924
+ }
28925
+ function isCompletedDependency(story) {
28926
+ return story.status === "passed" || story.status === "decomposed" || story.status === "skipped";
28927
+ }
28928
+ function formatCompletedDependency(story) {
28929
+ const header = `## ${story.id} (${story.status}): ${story.title}`;
28930
+ if (story.diffSummary) {
28931
+ return `${header}
28932
+
28933
+ **Changes made:**
28934
+ \`\`\`
28935
+ ${story.diffSummary}
28936
+ \`\`\``;
28937
+ }
28938
+ return `${header}
28939
+
28940
+ Status: ${story.status} (no diff summary available)`;
28941
+ }
28942
+ function formatFullDependency(story) {
28853
28943
  let content = formatStoryAsText(story);
28854
28944
  if (story.diffSummary) {
28855
28945
  content += `
@@ -28859,7 +28949,7 @@ function createDependencyContext(story, priority) {
28859
28949
  ${story.diffSummary}
28860
28950
  \`\`\``;
28861
28951
  }
28862
- return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
28952
+ return content;
28863
28953
  }
28864
28954
  function createErrorContext(errorMessage2, priority) {
28865
28955
  return { type: "error", content: errorMessage2, priority, tokens: estimateTokens(errorMessage2) };
@@ -29535,7 +29625,8 @@ ${pkgContent.trim()}`;
29535
29625
  if (built.elements.length === 0 && !packageSection) {
29536
29626
  return;
29537
29627
  }
29538
- const baseMarkdown = built.elements.length > 0 ? formatContextAsMarkdown(built) : "";
29628
+ const elementsForMarkdown = built.elements.filter((e) => e.type !== "story");
29629
+ const baseMarkdown = elementsForMarkdown.length > 0 ? formatContextAsMarkdown({ ...built, elements: elementsForMarkdown }) : "";
29539
29630
  const markdown = packageSection ? `${baseMarkdown}${packageSection}` : baseMarkdown;
29540
29631
  return { markdown, builtContext: built };
29541
29632
  } catch (error48) {
@@ -29546,6 +29637,10 @@ ${pkgContent.trim()}`;
29546
29637
  return;
29547
29638
  }
29548
29639
  }
29640
+ function buildStoryContextFullFromCtx(ctx) {
29641
+ const packageWorkdir = ctx.story.workdir ? ctx.workdir : undefined;
29642
+ return buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
29643
+ }
29549
29644
  function getAllReadyStories(prd) {
29550
29645
  const storyIds = new Set(prd.userStories.map((s) => s.id));
29551
29646
  const completedIds = new Set(prd.userStories.filter((s) => s.passes || s.status === "skipped").map((s) => s.id));
@@ -29656,7 +29751,6 @@ var init_helpers = __esm(() => {
29656
29751
  });
29657
29752
 
29658
29753
  // src/pipeline/stages/context.ts
29659
- import { join as join21 } from "path";
29660
29754
  var contextStage;
29661
29755
  var init_context2 = __esm(() => {
29662
29756
  init_helpers();
@@ -29666,8 +29760,7 @@ var init_context2 = __esm(() => {
29666
29760
  enabled: () => true,
29667
29761
  async execute(ctx) {
29668
29762
  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);
29763
+ const result = await buildStoryContextFullFromCtx(ctx);
29671
29764
  if (result) {
29672
29765
  ctx.contextMarkdown = result.markdown;
29673
29766
  ctx.builtContext = result.builtContext;
@@ -29800,14 +29893,14 @@ var init_isolation = __esm(() => {
29800
29893
 
29801
29894
  // src/context/greenfield.ts
29802
29895
  import { readdir } from "fs/promises";
29803
- import { join as join22 } from "path";
29896
+ import { join as join19 } from "path";
29804
29897
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
29805
29898
  const results = [];
29806
29899
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
29807
29900
  try {
29808
29901
  const entries = await readdir(dir, { withFileTypes: true });
29809
29902
  for (const entry of entries) {
29810
- const fullPath = join22(dir, entry.name);
29903
+ const fullPath = join19(dir, entry.name);
29811
29904
  if (entry.isDirectory()) {
29812
29905
  if (ignoreDirs.has(entry.name))
29813
29906
  continue;
@@ -29873,10 +29966,10 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
29873
29966
  const timeoutMs = timeoutSeconds * 1000;
29874
29967
  let timedOut = false;
29875
29968
  const timer = { id: undefined };
29876
- const timeoutPromise = new Promise((resolve7) => {
29969
+ const timeoutPromise = new Promise((resolve8) => {
29877
29970
  timer.id = setTimeout(() => {
29878
29971
  timedOut = true;
29879
- resolve7();
29972
+ resolve8();
29880
29973
  }, timeoutMs);
29881
29974
  });
29882
29975
  const processPromise = proc.exited;
@@ -29885,7 +29978,12 @@ async function executeWithTimeout(command, timeoutSeconds, env2, options) {
29885
29978
  if (timedOut) {
29886
29979
  const pid = proc.pid;
29887
29980
  killProcessGroup(pid, "SIGTERM");
29888
- await Bun.sleep(gracePeriodMs);
29981
+ await Promise.race([
29982
+ proc.exited,
29983
+ new Promise((resolve8) => {
29984
+ setTimeout(resolve8, gracePeriodMs);
29985
+ })
29986
+ ]);
29889
29987
  killProcessGroup(pid, "SIGKILL");
29890
29988
  const [out, err] = await Promise.all([
29891
29989
  raceWithDeadline(stdoutPromise, drainTimeoutMs),
@@ -30147,13 +30245,13 @@ function parseTestOutput(output, exitCode) {
30147
30245
 
30148
30246
  // src/verification/runners.ts
30149
30247
  import { existsSync as existsSync18 } from "fs";
30150
- import { join as join23 } from "path";
30248
+ import { join as join20 } from "path";
30151
30249
  async function verifyAssets(workingDirectory, expectedFiles) {
30152
30250
  if (!expectedFiles || expectedFiles.length === 0)
30153
30251
  return { success: true, missingFiles: [] };
30154
30252
  const missingFiles = [];
30155
30253
  for (const file3 of expectedFiles) {
30156
- if (!existsSync18(join23(workingDirectory, file3)))
30254
+ if (!existsSync18(join20(workingDirectory, file3)))
30157
30255
  missingFiles.push(file3);
30158
30256
  }
30159
30257
  if (missingFiles.length > 0) {
@@ -30478,10 +30576,10 @@ var init_prompts = __esm(() => {
30478
30576
  });
30479
30577
 
30480
30578
  // src/tdd/rectification-gate.ts
30481
- async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName) {
30579
+ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir) {
30482
30580
  const rectificationEnabled = config2.execution.rectification?.enabled ?? false;
30483
30581
  if (!rectificationEnabled)
30484
- return false;
30582
+ return { passed: false, cost: 0 };
30485
30583
  const rectificationConfig = config2.execution.rectification;
30486
30584
  const testCmd = config2.quality?.commands?.test ?? "bun test";
30487
30585
  const fullSuiteTimeout = rectificationConfig.fullSuiteTimeoutSeconds;
@@ -30496,7 +30594,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
30496
30594
  if (!fullSuitePassed && fullSuiteResult.output) {
30497
30595
  const testSummary = _rectificationGateDeps.parseBunTestOutput(fullSuiteResult.output);
30498
30596
  if (testSummary.failed > 0) {
30499
- return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName);
30597
+ return await runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir);
30500
30598
  }
30501
30599
  if (testSummary.passed > 0) {
30502
30600
  logger.info("tdd", "Full suite gate passed (non-zero exit, 0 failures, tests detected)", {
@@ -30504,7 +30602,7 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
30504
30602
  exitCode: fullSuiteResult.exitCode,
30505
30603
  passedTests: testSummary.passed
30506
30604
  });
30507
- return true;
30605
+ return { passed: true, cost: 0 };
30508
30606
  }
30509
30607
  logger.warn("tdd", "Full suite gate inconclusive \u2014 no test results parsed from output (possible crash/OOM)", {
30510
30608
  storyId: story.id,
@@ -30512,19 +30610,19 @@ async function runFullSuiteGate(story, config2, workdir, agent, implementerTier,
30512
30610
  outputLength: fullSuiteResult.output.length,
30513
30611
  outputTail: fullSuiteResult.output.slice(-200)
30514
30612
  });
30515
- return false;
30613
+ return { passed: false, cost: 0 };
30516
30614
  }
30517
30615
  if (fullSuitePassed) {
30518
30616
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
30519
- return true;
30617
+ return { passed: true, cost: 0 };
30520
30618
  }
30521
30619
  logger.warn("tdd", "Full suite gate execution failed (no output)", {
30522
30620
  storyId: story.id,
30523
30621
  exitCode: fullSuiteResult.exitCode
30524
30622
  });
30525
- return false;
30623
+ return { passed: false, cost: 0 };
30526
30624
  }
30527
- async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName) {
30625
+ async function runRectificationLoop(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, testSummary, rectificationConfig, testCmd, fullSuiteTimeout, featureName, projectDir) {
30528
30626
  const rectificationState = {
30529
30627
  attempt: 0,
30530
30628
  initialFailures: testSummary.failed,
@@ -30544,6 +30642,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30544
30642
  ...rectificationState,
30545
30643
  isolationPassed: true
30546
30644
  };
30645
+ let gateCostAccum = 0;
30547
30646
  const fixed = await runSharedRectificationLoop({
30548
30647
  stage: "tdd",
30549
30648
  storyId: story.id,
@@ -30575,6 +30674,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30575
30674
  dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
30576
30675
  pipelineStage: "rectification",
30577
30676
  config: config2,
30677
+ projectDir,
30578
30678
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
30579
30679
  featureName,
30580
30680
  storyId: story.id,
@@ -30585,6 +30685,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30585
30685
  if (!rectifyResult.success && rectifyResult.pid) {
30586
30686
  await cleanupProcessTree(rectifyResult.pid);
30587
30687
  }
30688
+ gateCostAccum += rectifyResult.estimatedCost ?? 0;
30588
30689
  if (rectifyResult.success) {
30589
30690
  logger.info("tdd", "Rectification agent session complete", {
30590
30691
  storyId: story.id,
@@ -30645,7 +30746,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30645
30746
  }
30646
30747
  });
30647
30748
  if (fixed) {
30648
- return true;
30749
+ return { passed: true, cost: gateCostAccum };
30649
30750
  }
30650
30751
  const finalFullSuite = await _rectificationGateDeps.executeWithTimeout(testCmd, fullSuiteTimeout, undefined, {
30651
30752
  cwd: workdir
@@ -30657,10 +30758,10 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
30657
30758
  attempts: rectificationState.attempt,
30658
30759
  remainingFailures: rectificationState.currentFailures
30659
30760
  });
30660
- return false;
30761
+ return { passed: false, cost: gateCostAccum };
30661
30762
  }
30662
30763
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
30663
- return true;
30764
+ return { passed: true, cost: gateCostAccum };
30664
30765
  }
30665
30766
  var _rectificationGateDeps;
30666
30767
  var init_rectification_gate = __esm(() => {
@@ -31064,36 +31165,10 @@ Set \`approved: false\` when ANY of these conditions are true:
31064
31165
  - Critical acceptance criteria are not met
31065
31166
  - Code quality is poor (security issues, severe bugs, etc.)
31066
31167
 
31067
- **Full JSON schema example** (fill in all fields with real values):
31168
+ **JSON schema** (fill in all fields with real values):
31068
31169
 
31069
31170
  \`\`\`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
- }
31171
+ {"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
31172
  \`\`\`
31098
31173
 
31099
31174
  **Field notes:**
@@ -31110,13 +31185,13 @@ var exports_loader = {};
31110
31185
  __export(exports_loader, {
31111
31186
  loadOverride: () => loadOverride
31112
31187
  });
31113
- import { join as join24 } from "path";
31188
+ import { join as join21 } from "path";
31114
31189
  async function loadOverride(role, workdir, config2) {
31115
31190
  const overridePath = config2.prompts?.overrides?.[role];
31116
31191
  if (!overridePath) {
31117
31192
  return null;
31118
31193
  }
31119
- const absolutePath = join24(workdir, overridePath);
31194
+ const absolutePath = join21(workdir, overridePath);
31120
31195
  const file3 = Bun.file(absolutePath);
31121
31196
  if (!await file3.exists()) {
31122
31197
  return null;
@@ -31326,7 +31401,7 @@ async function rollbackToRef(workdir, ref) {
31326
31401
  }
31327
31402
  logger.info("tdd", "Successfully rolled back git changes", { ref });
31328
31403
  }
31329
- async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge) {
31404
+ async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution, featureName, interactionBridge, projectDir) {
31330
31405
  const startTime = Date.now();
31331
31406
  let prompt;
31332
31407
  if (_sessionRunnerDeps.buildPrompt) {
@@ -31356,6 +31431,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
31356
31431
  dangerouslySkipPermissions: resolvePermissions(config2, "run").skipPermissions,
31357
31432
  pipelineStage: "run",
31358
31433
  config: config2,
31434
+ projectDir,
31359
31435
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
31360
31436
  featureName,
31361
31437
  storyId: story.id,
@@ -31727,7 +31803,8 @@ async function runThreeSessionTdd(options) {
31727
31803
  dryRun = false,
31728
31804
  lite = false,
31729
31805
  _recursionDepth = 0,
31730
- interactionChain
31806
+ interactionChain,
31807
+ projectDir
31731
31808
  } = options;
31732
31809
  const logger = getLogger();
31733
31810
  const MAX_RECURSION_DEPTH = 2;
@@ -31786,7 +31863,7 @@ async function runThreeSessionTdd(options) {
31786
31863
  let session1;
31787
31864
  if (!isRetry) {
31788
31865
  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" }));
31866
+ 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
31867
  sessions.push(session1);
31791
31868
  }
31792
31869
  if (session1 && !session1.success) {
@@ -31848,7 +31925,7 @@ async function runThreeSessionTdd(options) {
31848
31925
  });
31849
31926
  const session2Ref = await captureGitRef(workdir) ?? "HEAD";
31850
31927
  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" }));
31928
+ 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
31929
  sessions.push(session2);
31853
31930
  if (!session2.success) {
31854
31931
  needsHumanReview = true;
@@ -31864,10 +31941,10 @@ async function runThreeSessionTdd(options) {
31864
31941
  lite
31865
31942
  };
31866
31943
  }
31867
- const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName);
31944
+ const { passed: fullSuiteGatePassed, cost: fullSuiteGateCost } = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger, featureName, projectDir);
31868
31945
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
31869
31946
  const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
31870
- const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName);
31947
+ const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution, featureName, undefined, projectDir);
31871
31948
  sessions.push(session3);
31872
31949
  const verdict = await readVerdict(workdir);
31873
31950
  await cleanupVerdict(workdir);
@@ -31927,7 +32004,7 @@ async function runThreeSessionTdd(options) {
31927
32004
  needsHumanReview = false;
31928
32005
  }
31929
32006
  }
31930
- const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0);
32007
+ const totalCost = sessions.reduce((sum, s) => sum + s.estimatedCost, 0) + fullSuiteGateCost;
31931
32008
  logger.info("tdd", allSuccessful ? "[OK] Three-session TDD complete" : "[WARN] Three-session TDD needs review", {
31932
32009
  storyId: story.id,
31933
32010
  success: allSuccessful,
@@ -31963,6 +32040,22 @@ async function runThreeSessionTdd(options) {
31963
32040
  fullSuiteGatePassed
31964
32041
  };
31965
32042
  }
32043
+ function runThreeSessionTddFromCtx(ctx, opts) {
32044
+ return runThreeSessionTdd({
32045
+ agent: opts.agent,
32046
+ story: ctx.story,
32047
+ config: ctx.config,
32048
+ workdir: ctx.workdir,
32049
+ modelTier: ctx.routing.modelTier,
32050
+ featureName: ctx.prd.feature,
32051
+ contextMarkdown: ctx.contextMarkdown,
32052
+ constitution: ctx.constitution?.content,
32053
+ dryRun: opts.dryRun ?? false,
32054
+ lite: opts.lite ?? false,
32055
+ interactionChain: ctx.interaction,
32056
+ projectDir: ctx.projectDir
32057
+ });
32058
+ }
31966
32059
  var init_orchestrator2 = __esm(() => {
31967
32060
  init_config();
31968
32061
  init_greenfield();
@@ -31985,17 +32078,6 @@ var init_tdd = __esm(() => {
31985
32078
  });
31986
32079
 
31987
32080
  // 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
32081
  function isAmbiguousOutput(output) {
32000
32082
  if (!output)
32001
32083
  return false;
@@ -32042,11 +32124,11 @@ var init_execution2 = __esm(() => {
32042
32124
  enabled: () => true,
32043
32125
  async execute(ctx) {
32044
32126
  const logger = getLogger();
32045
- const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.config.autoMode.defaultAgent);
32127
+ const agent = (ctx.agentGetFn ?? _executionDeps.getAgent)(ctx.rootConfig.autoMode.defaultAgent);
32046
32128
  if (!agent) {
32047
32129
  return {
32048
32130
  action: "fail",
32049
- reason: `Agent "${ctx.config.autoMode.defaultAgent}" not found`
32131
+ reason: `Agent "${ctx.rootConfig.autoMode.defaultAgent}" not found`
32050
32132
  };
32051
32133
  }
32052
32134
  const isTddStrategy = ctx.routing.testStrategy === "three-session-tdd" || ctx.routing.testStrategy === "three-session-tdd-lite";
@@ -32056,20 +32138,7 @@ var init_execution2 = __esm(() => {
32056
32138
  storyId: ctx.story.id,
32057
32139
  lite: isLiteMode
32058
32140
  });
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
- });
32141
+ const tddResult = await runThreeSessionTddFromCtx(ctx, { agent, dryRun: false, lite: isLiteMode });
32073
32142
  ctx.agentResult = {
32074
32143
  success: tddResult.success,
32075
32144
  estimatedCost: tddResult.totalCost,
@@ -32132,17 +32201,17 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
32132
32201
  supportedTiers: agent.capabilities.supportedTiers
32133
32202
  });
32134
32203
  }
32135
- const storyWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
32136
32204
  const keepSessionOpen = !!(ctx.config.review?.enabled === true || ctx.config.execution.rectification?.enabled === true);
32137
32205
  const result = await agent.run({
32138
32206
  prompt: ctx.prompt,
32139
- workdir: storyWorkdir,
32207
+ workdir: ctx.workdir,
32140
32208
  modelTier: ctx.routing.modelTier,
32141
- modelDef: resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, ctx.routing.modelTier, ctx.config.autoMode.defaultAgent),
32209
+ modelDef: resolveModelForAgent(ctx.rootConfig.models, ctx.routing.agent ?? ctx.rootConfig.autoMode.defaultAgent, ctx.routing.modelTier, ctx.rootConfig.autoMode.defaultAgent),
32142
32210
  timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
32143
32211
  dangerouslySkipPermissions: resolvePermissions(ctx.config, "run").skipPermissions,
32144
32212
  pipelineStage: "run",
32145
32213
  config: ctx.config,
32214
+ projectDir: ctx.projectDir,
32146
32215
  maxInteractionTurns: ctx.config.agent?.maxInteractionTurns,
32147
32216
  pidRegistry: ctx.pidRegistry,
32148
32217
  featureName: ctx.prd.feature,
@@ -32156,7 +32225,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
32156
32225
  })
32157
32226
  });
32158
32227
  ctx.agentResult = result;
32159
- await autoCommitIfDirty(storyWorkdir, "execution", "single-session", ctx.story.id);
32228
+ await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
32160
32229
  const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
32161
32230
  if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
32162
32231
  const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
@@ -32197,8 +32266,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
32197
32266
  detectMergeConflict,
32198
32267
  checkMergeConflict,
32199
32268
  isAmbiguousOutput,
32200
- checkStoryAmbiguity,
32201
- resolveStoryWorkdir
32269
+ checkStoryAmbiguity
32202
32270
  };
32203
32271
  });
32204
32272
 
@@ -32475,17 +32543,16 @@ var init_prompt = __esm(() => {
32475
32543
  async execute(ctx) {
32476
32544
  const logger = getLogger();
32477
32545
  const isBatch = ctx.stories.length > 1;
32478
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
32479
32546
  const acceptanceEntries = await _loadAcceptanceEntries(ctx, logger);
32480
32547
  let prompt;
32481
32548
  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);
32549
+ 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
32550
  if (acceptanceEntries.length > 0)
32484
32551
  builder.acceptanceContext(acceptanceEntries);
32485
32552
  prompt = await builder.build();
32486
32553
  } else {
32487
32554
  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);
32555
+ 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
32556
  if (acceptanceEntries.length > 0)
32490
32557
  builder.acceptanceContext(acceptanceEntries);
32491
32558
  prompt = await builder.build();
@@ -32718,7 +32785,18 @@ async function _defaultRunDebate(storyId, stageConfig, prompt, config2) {
32718
32785
  return { output, totalCostUsd };
32719
32786
  }
32720
32787
  async function runRectificationLoop2(opts) {
32721
- const { config: config2, workdir, story, testCommand, timeoutSeconds, testOutput, promptPrefix, featureName, agentGetFn } = opts;
32788
+ const {
32789
+ config: config2,
32790
+ workdir,
32791
+ story,
32792
+ testCommand,
32793
+ timeoutSeconds,
32794
+ testOutput,
32795
+ promptPrefix,
32796
+ featureName,
32797
+ agentGetFn,
32798
+ projectDir
32799
+ } = opts;
32722
32800
  const logger = getSafeLogger();
32723
32801
  const rectificationConfig = config2.execution.rectification;
32724
32802
  const testSummary = parseBunTestOutput(testOutput);
@@ -32729,7 +32807,8 @@ async function runRectificationLoop2(opts) {
32729
32807
  currentFailures: testSummary.failed,
32730
32808
  lastExitCode: 1
32731
32809
  };
32732
- return runSharedRectificationLoop({
32810
+ let costAccum = 0;
32811
+ const succeeded = await runSharedRectificationLoop({
32733
32812
  stage: "rectification",
32734
32813
  storyId: story.id,
32735
32814
  maxAttempts: rectificationConfig.maxRetries,
@@ -32810,11 +32889,13 @@ ${rectificationPrompt}`;
32810
32889
  dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
32811
32890
  pipelineStage: "rectification",
32812
32891
  config: config2,
32892
+ projectDir,
32813
32893
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
32814
32894
  featureName,
32815
32895
  storyId: story.id,
32816
32896
  sessionRole: "implementer"
32817
32897
  });
32898
+ costAccum += agentResult.estimatedCost ?? 0;
32818
32899
  if (agentResult.success) {
32819
32900
  logger?.info("rectification", `Agent ${label} session complete`, {
32820
32901
  storyId: story.id,
@@ -32934,11 +33015,13 @@ ${escalationPrompt}`;
32934
33015
  dangerouslySkipPermissions: resolvePermissions(config2, "rectification").skipPermissions,
32935
33016
  pipelineStage: "rectification",
32936
33017
  config: config2,
33018
+ projectDir,
32937
33019
  maxInteractionTurns: config2.agent?.maxInteractionTurns,
32938
33020
  featureName,
32939
33021
  storyId: story.id,
32940
33022
  sessionRole: "implementer"
32941
33023
  });
33024
+ costAccum += escalationRunResult.estimatedCost ?? 0;
32942
33025
  logger?.info("rectification", "escalated rectification attempt cost", {
32943
33026
  storyId: story.id,
32944
33027
  escalatedTier,
@@ -32975,6 +33058,21 @@ ${escalationPrompt}`;
32975
33058
  }
32976
33059
  throw error48;
32977
33060
  });
33061
+ return { succeeded, cost: costAccum };
33062
+ }
33063
+ function runRectificationLoopFromCtx(ctx, opts) {
33064
+ return runRectificationLoop2({
33065
+ config: ctx.config,
33066
+ workdir: ctx.workdir,
33067
+ story: ctx.story,
33068
+ testCommand: opts.testCommand,
33069
+ timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
33070
+ testOutput: opts.testOutput,
33071
+ promptPrefix: opts.promptPrefix,
33072
+ featureName: ctx.prd.feature,
33073
+ agentGetFn: ctx.agentGetFn,
33074
+ projectDir: ctx.projectDir
33075
+ });
32978
33076
  }
32979
33077
  var _rectificationDeps;
32980
33078
  var init_rectification_loop = __esm(() => {
@@ -33036,36 +33134,28 @@ var init_rectify = __esm(() => {
33036
33134
  attempt: rectifyAttempt,
33037
33135
  testOutput
33038
33136
  });
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
- });
33137
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
33138
+ const { succeeded, cost } = await _rectifyDeps.runRectificationLoop(ctx, { testCommand, testOutput });
33050
33139
  pipelineEventBus.emit({
33051
33140
  type: "rectify:completed",
33052
33141
  storyId: ctx.story.id,
33053
33142
  attempt: rectifyAttempt,
33054
- fixed
33143
+ fixed: succeeded
33055
33144
  });
33056
- if (fixed) {
33145
+ if (succeeded) {
33057
33146
  logger.info("rectify", "Rectification succeeded \u2014 retrying verify", { storyId: ctx.story.id });
33058
33147
  ctx.verifyResult = undefined;
33059
- return { action: "retry", fromStage: "verify" };
33148
+ return { action: "retry", fromStage: "verify", cost };
33060
33149
  }
33061
33150
  logger.warn("rectify", "Rectification exhausted \u2014 escalating", { storyId: ctx.story.id });
33062
33151
  return {
33063
33152
  action: "escalate",
33064
- reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`
33153
+ reason: `Rectification exhausted after ${maxRetries} attempts (${verifyResult.failCount} test failures)`,
33154
+ cost
33065
33155
  };
33066
33156
  }
33067
33157
  };
33068
- _rectifyDeps = { runRectificationLoop: runRectificationLoop2 };
33158
+ _rectifyDeps = { runRectificationLoop: runRectificationLoopFromCtx };
33069
33159
  });
33070
33160
 
33071
33161
  // src/verification/orchestrator-types.ts
@@ -33172,16 +33262,16 @@ class AcceptanceStrategy {
33172
33262
  }, timeoutMs);
33173
33263
  const exitCode = await Promise.race([
33174
33264
  proc.exited,
33175
- new Promise((resolve7) => setTimeout(() => resolve7(124), timeoutMs + 6000))
33265
+ new Promise((resolve8) => setTimeout(() => resolve8(124), timeoutMs + 6000))
33176
33266
  ]);
33177
33267
  clearTimeout(timeoutId);
33178
33268
  const stdout = await Promise.race([
33179
33269
  new Response(proc.stdout).text(),
33180
- new Promise((resolve7) => setTimeout(() => resolve7(""), 3000))
33270
+ new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
33181
33271
  ]);
33182
33272
  const stderr = await Promise.race([
33183
33273
  new Response(proc.stderr).text(),
33184
- new Promise((resolve7) => setTimeout(() => resolve7(""), 3000))
33274
+ new Promise((resolve8) => setTimeout(() => resolve8(""), 3000))
33185
33275
  ]);
33186
33276
  const durationMs = Date.now() - start;
33187
33277
  if (timedOut || exitCode === 124) {
@@ -33591,34 +33681,31 @@ var init_regression2 = __esm(() => {
33591
33681
  regressionStage = {
33592
33682
  name: "regression",
33593
33683
  enabled(ctx) {
33594
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
33595
- const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
33684
+ const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
33596
33685
  if (mode !== "per-story")
33597
33686
  return false;
33598
33687
  if (ctx.verifyResult && !ctx.verifyResult.success)
33599
33688
  return false;
33600
- const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
33689
+ const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
33601
33690
  return gateEnabled;
33602
33691
  },
33603
33692
  skipReason(ctx) {
33604
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
33605
- const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
33693
+ const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
33606
33694
  if (mode !== "per-story")
33607
33695
  return `not needed (regression mode is '${mode}', not 'per-story')`;
33608
33696
  return "disabled (regression gate not enabled in config)";
33609
33697
  },
33610
33698
  async execute(ctx) {
33611
33699
  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;
33700
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
33701
+ const timeoutSeconds = ctx.config.execution.regressionGate?.timeoutSeconds ?? 120;
33615
33702
  logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
33616
33703
  const verifyCtx = {
33617
33704
  workdir: ctx.workdir,
33618
33705
  testCommand,
33619
33706
  timeoutSeconds,
33620
33707
  storyId: ctx.story.id,
33621
- acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
33708
+ acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
33622
33709
  config: ctx.config
33623
33710
  };
33624
33711
  const result = await _regressionStageDeps.verifyRegression(verifyCtx);
@@ -34225,7 +34312,6 @@ var init_routing = __esm(() => {
34225
34312
  });
34226
34313
 
34227
34314
  // src/pipeline/stages/routing.ts
34228
- import { join as join26 } from "path";
34229
34315
  var routingStage, _routingDeps;
34230
34316
  var init_routing2 = __esm(() => {
34231
34317
  init_greenfield();
@@ -34238,13 +34324,12 @@ var init_routing2 = __esm(() => {
34238
34324
  enabled: () => true,
34239
34325
  async execute(ctx) {
34240
34326
  const logger = getLogger();
34241
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
34242
- const agentName = effectiveConfig.execution?.agent ?? "claude";
34327
+ const agentName = ctx.config.execution?.agent ?? "claude";
34243
34328
  const adapter = ctx.agentGetFn ? ctx.agentGetFn(agentName) : undefined;
34244
34329
  if (ctx.story.id === ctx.stories[0]?.id) {
34245
34330
  _routingDeps.clearCache();
34246
34331
  }
34247
- const decision = await _routingDeps.resolveRouting(ctx.story, effectiveConfig, ctx.plugins, adapter);
34332
+ const decision = await _routingDeps.resolveRouting(ctx.story, ctx.config, ctx.plugins, adapter);
34248
34333
  const TIER_RANK = { fast: 0, balanced: 1, powerful: 2 };
34249
34334
  const derivedTier = decision.modelTier;
34250
34335
  const previousTier = ctx.story.routing?.modelTier;
@@ -34262,9 +34347,9 @@ var init_routing2 = __esm(() => {
34262
34347
  if (ctx.prdPath) {
34263
34348
  await _routingDeps.savePRD(ctx.prd, ctx.prdPath);
34264
34349
  }
34265
- const greenfieldDetectionEnabled = effectiveConfig.tdd.greenfieldDetection ?? true;
34350
+ const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
34266
34351
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
34267
- const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
34352
+ const greenfieldScanDir = ctx.workdir;
34268
34353
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
34269
34354
  if (isGreenfield) {
34270
34355
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -34315,7 +34400,7 @@ var init_crash_detector = __esm(() => {
34315
34400
  });
34316
34401
 
34317
34402
  // src/pipeline/stages/verify.ts
34318
- import { join as join27 } from "path";
34403
+ import { join as join22 } from "path";
34319
34404
  function coerceSmartTestRunner(val) {
34320
34405
  if (val === undefined || val === true)
34321
34406
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -34331,7 +34416,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
34331
34416
  }
34332
34417
  async function readPackageName(dir) {
34333
34418
  try {
34334
- const content = await Bun.file(join27(dir, "package.json")).json();
34419
+ const content = await Bun.file(join22(dir, "package.json")).json();
34335
34420
  return typeof content.name === "string" ? content.name : null;
34336
34421
  } catch {
34337
34422
  return null;
@@ -34364,26 +34449,24 @@ var init_verify = __esm(() => {
34364
34449
  skipReason: () => "not needed (full-suite gate already passed)",
34365
34450
  async execute(ctx) {
34366
34451
  const logger = getLogger();
34367
- const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
34368
- if (!effectiveConfig.quality.requireTests) {
34452
+ if (!ctx.config.quality.requireTests) {
34369
34453
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
34370
34454
  return { action: "continue" };
34371
34455
  }
34372
- const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test;
34373
- const testScopedTemplate = effectiveConfig.quality.commands.testScoped;
34456
+ const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test;
34457
+ const testScopedTemplate = ctx.config.quality.commands.testScoped;
34374
34458
  if (!testCommand) {
34375
34459
  logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
34376
34460
  return { action: "continue" };
34377
34461
  }
34378
34462
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
34379
- const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
34380
34463
  let effectiveCommand = testCommand;
34381
34464
  let isFullSuite = true;
34382
- const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
34383
- const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
34465
+ const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
34466
+ const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
34384
34467
  let resolvedTestScopedTemplate = testScopedTemplate;
34385
34468
  if (testScopedTemplate && ctx.story.workdir) {
34386
- const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
34469
+ const resolved = await resolvePackageTemplate(testScopedTemplate, ctx.workdir);
34387
34470
  resolvedTestScopedTemplate = resolved ?? undefined;
34388
34471
  }
34389
34472
  const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
@@ -34402,8 +34485,8 @@ var init_verify = __esm(() => {
34402
34485
  });
34403
34486
  }
34404
34487
  } else if (smartRunnerConfig.enabled) {
34405
- const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(effectiveWorkdir, ctx.storyGitRef, ctx.story.workdir);
34406
- const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, effectiveWorkdir);
34488
+ const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(ctx.workdir, ctx.storyGitRef, ctx.story.workdir);
34489
+ const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, ctx.workdir);
34407
34490
  if (pass1Files.length > 0) {
34408
34491
  logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
34409
34492
  storyId: ctx.story.id
@@ -34411,7 +34494,7 @@ var init_verify = __esm(() => {
34411
34494
  effectiveCommand = buildScopedCommand2(pass1Files, testCommand, resolvedTestScopedTemplate);
34412
34495
  isFullSuite = false;
34413
34496
  } else if (smartRunnerConfig.fallback === "import-grep") {
34414
- const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, effectiveWorkdir, smartRunnerConfig.testFilePatterns);
34497
+ const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, ctx.workdir, smartRunnerConfig.testFilePatterns);
34415
34498
  if (pass2Files.length > 0) {
34416
34499
  logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
34417
34500
  storyId: ctx.story.id
@@ -34437,10 +34520,10 @@ var init_verify = __esm(() => {
34437
34520
  command: effectiveCommand
34438
34521
  });
34439
34522
  const result = await _verifyDeps.regression({
34440
- workdir: effectiveWorkdir,
34523
+ workdir: ctx.workdir,
34441
34524
  command: effectiveCommand,
34442
- timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
34443
- acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
34525
+ timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
34526
+ acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
34444
34527
  });
34445
34528
  ctx.verifyResult = {
34446
34529
  success: result.success,
@@ -34457,7 +34540,7 @@ var init_verify = __esm(() => {
34457
34540
  };
34458
34541
  if (!result.success) {
34459
34542
  if (result.status === "TIMEOUT") {
34460
- const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
34543
+ const timeout = ctx.config.execution.verificationTimeoutSeconds;
34461
34544
  logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
34462
34545
  exitCode: result.status,
34463
34546
  storyId: ctx.story.id,
@@ -34475,7 +34558,7 @@ var init_verify = __esm(() => {
34475
34558
  if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
34476
34559
  return {
34477
34560
  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"})`
34561
+ 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
34562
  };
34480
34563
  }
34481
34564
  return { action: "continue" };
@@ -34593,9 +34676,9 @@ __export(exports_init_context, {
34593
34676
  generateContextTemplate: () => generateContextTemplate,
34594
34677
  _initContextDeps: () => _initContextDeps
34595
34678
  });
34596
- import { existsSync as existsSync22 } from "fs";
34679
+ import { existsSync as existsSync21 } from "fs";
34597
34680
  import { mkdir as mkdir2 } from "fs/promises";
34598
- import { basename as basename4, join as join31 } from "path";
34681
+ import { basename as basename3, join as join26 } from "path";
34599
34682
  async function findFiles(dir, maxFiles = 200) {
34600
34683
  try {
34601
34684
  const proc = Bun.spawnSync([
@@ -34623,8 +34706,8 @@ async function findFiles(dir, maxFiles = 200) {
34623
34706
  return [];
34624
34707
  }
34625
34708
  async function readPackageManifest(projectRoot) {
34626
- const packageJsonPath = join31(projectRoot, "package.json");
34627
- if (!existsSync22(packageJsonPath)) {
34709
+ const packageJsonPath = join26(projectRoot, "package.json");
34710
+ if (!existsSync21(packageJsonPath)) {
34628
34711
  return null;
34629
34712
  }
34630
34713
  try {
@@ -34641,8 +34724,8 @@ async function readPackageManifest(projectRoot) {
34641
34724
  }
34642
34725
  }
34643
34726
  async function readReadmeSnippet(projectRoot) {
34644
- const readmePath = join31(projectRoot, "README.md");
34645
- if (!existsSync22(readmePath)) {
34727
+ const readmePath = join26(projectRoot, "README.md");
34728
+ if (!existsSync21(readmePath)) {
34646
34729
  return null;
34647
34730
  }
34648
34731
  try {
@@ -34659,8 +34742,8 @@ async function detectEntryPoints(projectRoot) {
34659
34742
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
34660
34743
  const found = [];
34661
34744
  for (const candidate of candidates) {
34662
- const path13 = join31(projectRoot, candidate);
34663
- if (existsSync22(path13)) {
34745
+ const path13 = join26(projectRoot, candidate);
34746
+ if (existsSync21(path13)) {
34664
34747
  found.push(candidate);
34665
34748
  }
34666
34749
  }
@@ -34670,8 +34753,8 @@ async function detectConfigFiles(projectRoot) {
34670
34753
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
34671
34754
  const found = [];
34672
34755
  for (const candidate of candidates) {
34673
- const path13 = join31(projectRoot, candidate);
34674
- if (existsSync22(path13)) {
34756
+ const path13 = join26(projectRoot, candidate);
34757
+ if (existsSync21(path13)) {
34675
34758
  found.push(candidate);
34676
34759
  }
34677
34760
  }
@@ -34683,7 +34766,7 @@ async function scanProject(projectRoot) {
34683
34766
  const readmeSnippet = await readReadmeSnippet(projectRoot);
34684
34767
  const entryPoints = await detectEntryPoints(projectRoot);
34685
34768
  const configFiles = await detectConfigFiles(projectRoot);
34686
- const projectName = packageManifest?.name || basename4(projectRoot);
34769
+ const projectName = packageManifest?.name || basename3(projectRoot);
34687
34770
  return {
34688
34771
  projectName,
34689
34772
  fileTree,
@@ -34831,13 +34914,13 @@ function generatePackageContextTemplate(packagePath) {
34831
34914
  }
34832
34915
  async function initPackage(repoRoot, packagePath, force = false) {
34833
34916
  const logger = getLogger();
34834
- const naxDir = join31(repoRoot, ".nax", "mono", packagePath);
34835
- const contextPath = join31(naxDir, "context.md");
34836
- if (existsSync22(contextPath) && !force) {
34917
+ const naxDir = join26(repoRoot, ".nax", "mono", packagePath);
34918
+ const contextPath = join26(naxDir, "context.md");
34919
+ if (existsSync21(contextPath) && !force) {
34837
34920
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
34838
34921
  return;
34839
34922
  }
34840
- if (!existsSync22(naxDir)) {
34923
+ if (!existsSync21(naxDir)) {
34841
34924
  await mkdir2(naxDir, { recursive: true });
34842
34925
  }
34843
34926
  const content = generatePackageContextTemplate(packagePath);
@@ -34846,13 +34929,13 @@ async function initPackage(repoRoot, packagePath, force = false) {
34846
34929
  }
34847
34930
  async function initContext(projectRoot, options = {}) {
34848
34931
  const logger = getLogger();
34849
- const naxDir = join31(projectRoot, ".nax");
34850
- const contextPath = join31(naxDir, "context.md");
34851
- if (existsSync22(contextPath) && !options.force) {
34932
+ const naxDir = join26(projectRoot, ".nax");
34933
+ const contextPath = join26(naxDir, "context.md");
34934
+ if (existsSync21(contextPath) && !options.force) {
34852
34935
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
34853
34936
  return;
34854
34937
  }
34855
- if (!existsSync22(naxDir)) {
34938
+ if (!existsSync21(naxDir)) {
34856
34939
  await mkdir2(naxDir, { recursive: true });
34857
34940
  }
34858
34941
  const scan = await scanProject(projectRoot);
@@ -34877,23 +34960,23 @@ var init_init_context = __esm(() => {
34877
34960
 
34878
34961
  // src/utils/path-security.ts
34879
34962
  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";
34963
+ import { dirname as dirname4, isAbsolute as isAbsolute5, join as join27, normalize as normalize2, resolve as resolve8 } from "path";
34881
34964
  function safeRealpathForComparison(p) {
34882
34965
  try {
34883
34966
  return realpathSync3(p);
34884
34967
  } catch {
34885
- const parent = dirname5(p);
34968
+ const parent = dirname4(p);
34886
34969
  if (parent === p)
34887
34970
  return normalize2(p);
34888
34971
  const resolvedParent = safeRealpathForComparison(parent);
34889
- return join32(resolvedParent, p.split("/").pop() ?? "");
34972
+ return join27(resolvedParent, p.split("/").pop() ?? "");
34890
34973
  }
34891
34974
  }
34892
34975
  function validateModulePath(modulePath, allowedRoots) {
34893
34976
  if (!modulePath) {
34894
34977
  return { valid: false, error: "Module path is empty" };
34895
34978
  }
34896
- const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve7(r)));
34979
+ const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve8(r)));
34897
34980
  if (isAbsolute5(modulePath)) {
34898
34981
  const normalized = normalize2(modulePath);
34899
34982
  const resolved = safeRealpathForComparison(normalized);
@@ -34903,8 +34986,8 @@ function validateModulePath(modulePath, allowedRoots) {
34903
34986
  }
34904
34987
  } else {
34905
34988
  for (let i = 0;i < allowedRoots.length; i++) {
34906
- const originalRoot = resolve7(allowedRoots[i]);
34907
- const absoluteInput = resolve7(join32(originalRoot, modulePath));
34989
+ const originalRoot = resolve8(allowedRoots[i]);
34990
+ const absoluteInput = resolve8(join27(originalRoot, modulePath));
34908
34991
  const resolved = safeRealpathForComparison(absoluteInput);
34909
34992
  const resolvedRoot = resolvedRoots[i];
34910
34993
  if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
@@ -35265,11 +35348,11 @@ function getSafeLogger6() {
35265
35348
  return getSafeLogger();
35266
35349
  }
35267
35350
  function extractPluginName(pluginPath) {
35268
- const basename6 = path13.basename(pluginPath);
35269
- if (basename6 === "index.ts" || basename6 === "index.js" || basename6 === "index.mjs") {
35351
+ const basename5 = path13.basename(pluginPath);
35352
+ if (basename5 === "index.ts" || basename5 === "index.js" || basename5 === "index.mjs") {
35270
35353
  return path13.basename(path13.dirname(pluginPath));
35271
35354
  }
35272
- return basename6.replace(/\.(ts|js|mjs)$/, "");
35355
+ return basename5.replace(/\.(ts|js|mjs)$/, "");
35273
35356
  }
35274
35357
  async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
35275
35358
  const loadedPlugins = [];
@@ -35430,7 +35513,7 @@ async function loadAndValidatePlugin(initialModulePath, config2, allowedRoots =
35430
35513
  return null;
35431
35514
  }
35432
35515
  }
35433
- var _pluginErrorSink = (...args) => console.error(...args);
35516
+ var _pluginErrorSink = () => {};
35434
35517
  var init_loader4 = __esm(() => {
35435
35518
  init_logger2();
35436
35519
  init_path_security2();
@@ -35440,19 +35523,19 @@ var init_loader4 = __esm(() => {
35440
35523
  });
35441
35524
 
35442
35525
  // src/hooks/runner.ts
35443
- import { join as join47 } from "path";
35526
+ import { join as join42 } from "path";
35444
35527
  async function loadHooksConfig(projectDir, globalDir) {
35445
35528
  let globalHooks = { hooks: {} };
35446
35529
  let projectHooks = { hooks: {} };
35447
35530
  let skipGlobal = false;
35448
- const projectPath = join47(projectDir, "hooks.json");
35531
+ const projectPath = join42(projectDir, "hooks.json");
35449
35532
  const projectData = await loadJsonFile(projectPath, "hooks");
35450
35533
  if (projectData) {
35451
35534
  projectHooks = projectData;
35452
35535
  skipGlobal = projectData.skipGlobal ?? false;
35453
35536
  }
35454
35537
  if (!skipGlobal && globalDir) {
35455
- const globalPath = join47(globalDir, "hooks.json");
35538
+ const globalPath = join42(globalDir, "hooks.json");
35456
35539
  const globalData = await loadJsonFile(globalPath, "hooks");
35457
35540
  if (globalData) {
35458
35541
  globalHooks = globalData;
@@ -35652,7 +35735,7 @@ var package_default;
35652
35735
  var init_package = __esm(() => {
35653
35736
  package_default = {
35654
35737
  name: "@nathapp/nax",
35655
- version: "0.58.5",
35738
+ version: "0.59.0",
35656
35739
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
35657
35740
  type: "module",
35658
35741
  bin: {
@@ -35732,8 +35815,8 @@ var init_version = __esm(() => {
35732
35815
  NAX_VERSION = package_default.version;
35733
35816
  NAX_COMMIT = (() => {
35734
35817
  try {
35735
- if (/^[0-9a-f]{6,10}$/.test("374b714e"))
35736
- return "374b714e";
35818
+ if (/^[0-9a-f]{6,10}$/.test("13990b5b"))
35819
+ return "13990b5b";
35737
35820
  } catch {}
35738
35821
  try {
35739
35822
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -36029,18 +36112,18 @@ var init_crash_signals = __esm(() => {
36029
36112
 
36030
36113
  // src/execution/crash-recovery.ts
36031
36114
  function installCrashHandlers(ctx) {
36032
- if (handlersInstalled) {
36033
- return () => {};
36115
+ if (activeCleanup) {
36116
+ activeCleanup();
36034
36117
  }
36035
36118
  const cleanup = installSignalHandlers(ctx);
36036
- handlersInstalled = true;
36037
- return () => {
36119
+ activeCleanup = () => {
36038
36120
  cleanup();
36039
36121
  stopHeartbeat();
36040
- handlersInstalled = false;
36122
+ activeCleanup = null;
36041
36123
  };
36124
+ return activeCleanup;
36042
36125
  }
36043
- var handlersInstalled = false;
36126
+ var activeCleanup = null;
36044
36127
  var init_crash_recovery = __esm(() => {
36045
36128
  init_crash_heartbeat();
36046
36129
  init_crash_signals();
@@ -36361,12 +36444,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
36361
36444
  semanticVerdicts: options.semanticVerdicts
36362
36445
  });
36363
36446
  try {
36447
+ const timeoutSeconds = (config2.acceptance?.timeoutMs ?? 120000) / 1000;
36364
36448
  const result = await agent.run({
36365
36449
  prompt,
36366
36450
  workdir,
36367
36451
  modelTier: undefined,
36368
36452
  modelDef,
36369
- timeoutSeconds: 300,
36453
+ timeoutSeconds,
36370
36454
  sessionRole: "diagnose",
36371
36455
  acpSessionName: sessionName,
36372
36456
  featureName,
@@ -36375,12 +36459,13 @@ async function diagnoseAcceptanceFailure(agent, options) {
36375
36459
  });
36376
36460
  const diagnosis = parseDiagnosisResult(result.output);
36377
36461
  if (diagnosis) {
36378
- return diagnosis;
36462
+ return { ...diagnosis, cost: result.estimatedCost ?? 0 };
36379
36463
  }
36380
36464
  return {
36381
36465
  verdict: "source_bug",
36382
36466
  reasoning: "diagnosis failed \u2014 falling back to source fix",
36383
- confidence: 0
36467
+ confidence: 0,
36468
+ cost: result.estimatedCost ?? 0
36384
36469
  };
36385
36470
  } catch {
36386
36471
  return {
@@ -36492,7 +36577,7 @@ __export(exports_acceptance_loop, {
36492
36577
  _regenerateDeps: () => _regenerateDeps,
36493
36578
  _acceptanceLoopDeps: () => _acceptanceLoopDeps
36494
36579
  });
36495
- import path15, { join as join48 } from "path";
36580
+ import path15, { join as join43 } from "path";
36496
36581
  function isStubTestFile(content) {
36497
36582
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
36498
36583
  }
@@ -36581,15 +36666,16 @@ async function executeFixStory(ctx, story, prd, iterations) {
36581
36666
  agent: ctx.config.autoMode.defaultAgent,
36582
36667
  iteration: iterations
36583
36668
  }), ctx.workdir);
36584
- const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join48(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
36669
+ const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join43(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
36585
36670
  const fixContext = {
36586
- config: ctx.config,
36587
- effectiveConfig: fixEffectiveConfig,
36671
+ config: fixEffectiveConfig,
36672
+ rootConfig: ctx.config,
36588
36673
  prd,
36589
36674
  story,
36590
36675
  stories: [story],
36591
36676
  routing,
36592
- workdir: ctx.workdir,
36677
+ projectDir: ctx.workdir,
36678
+ workdir: story.workdir ? join43(ctx.workdir, story.workdir) : ctx.workdir,
36593
36679
  featureDir: ctx.featureDir,
36594
36680
  hooks: ctx.hooks,
36595
36681
  plugins: ctx.pluginRegistry,
@@ -36768,6 +36854,7 @@ async function runFixRouting(options) {
36768
36854
  storyId,
36769
36855
  semanticVerdicts: options.semanticVerdicts
36770
36856
  });
36857
+ const diagnosisCost = diagnosis.cost ?? 0;
36771
36858
  logger?.info("acceptance.diagnosis", "Diagnosis complete", {
36772
36859
  verdict: diagnosis.verdict,
36773
36860
  confidence: diagnosis.confidence,
@@ -36777,7 +36864,7 @@ async function runFixRouting(options) {
36777
36864
  logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
36778
36865
  if (!agent) {
36779
36866
  logger?.error("acceptance", "Agent not found for source fix execution");
36780
- return { fixed: false, cost: 0, prdDirty: false };
36867
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36781
36868
  }
36782
36869
  let fixAttempts = 0;
36783
36870
  while (fixAttempts < fixMaxRetries) {
@@ -36799,7 +36886,7 @@ async function runFixRouting(options) {
36799
36886
  attempt: fixAttempts
36800
36887
  });
36801
36888
  if (fixResult.success) {
36802
- return { fixed: true, cost: fixResult.cost, prdDirty: false };
36889
+ return { fixed: true, cost: fixResult.cost + diagnosisCost, prdDirty: false };
36803
36890
  }
36804
36891
  logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
36805
36892
  attempt: fixAttempts,
@@ -36812,13 +36899,13 @@ async function runFixRouting(options) {
36812
36899
  break;
36813
36900
  }
36814
36901
  }
36815
- return { fixed: false, cost: 0, prdDirty: false };
36902
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36816
36903
  }
36817
36904
  if (diagnosis.verdict === "test_bug") {
36818
36905
  logger?.info("acceptance", "Diagnosis: test_bug \u2014 regenerating acceptance test");
36819
36906
  if (!ctx.featureDir) {
36820
36907
  logger?.error("acceptance", "Cannot regenerate test without featureDir");
36821
- return { fixed: false, cost: 0, prdDirty: false };
36908
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36822
36909
  }
36823
36910
  const testPath = await findExistingAcceptanceTestPath({
36824
36911
  acceptanceTestPaths: ctx.acceptanceTestPaths,
@@ -36835,29 +36922,29 @@ async function runFixRouting(options) {
36835
36922
  language: ctx.config.project?.language
36836
36923
  })
36837
36924
  });
36838
- return { fixed: false, cost: 0, prdDirty: false };
36925
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36839
36926
  }
36840
36927
  const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
36841
36928
  logger?.info("acceptance.test-regen", "Test regeneration completed", {
36842
36929
  outcome: regenerated ? "success" : "failure"
36843
36930
  });
36844
36931
  if (!regenerated) {
36845
- return { fixed: false, cost: 0, prdDirty: false };
36932
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36846
36933
  }
36847
36934
  const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
36848
36935
  const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
36849
36936
  if (acceptanceResult.action === "continue") {
36850
36937
  logger?.info("acceptance", "Acceptance passed after test regeneration");
36851
- return { fixed: true, cost: 0, prdDirty: true };
36938
+ return { fixed: true, cost: diagnosisCost, prdDirty: true };
36852
36939
  }
36853
36940
  logger?.warn("acceptance", "Acceptance still failing after test regeneration");
36854
- return { fixed: false, cost: 0, prdDirty: true };
36941
+ return { fixed: false, cost: diagnosisCost, prdDirty: true };
36855
36942
  }
36856
36943
  if (diagnosis.verdict === "both") {
36857
36944
  logger?.info("acceptance", "Diagnosis: both \u2014 executing source fix then regenerating test if needed");
36858
36945
  if (!agent) {
36859
36946
  logger?.error("acceptance", "Agent not found for source fix execution");
36860
- return { fixed: false, cost: 0, prdDirty: false };
36947
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36861
36948
  }
36862
36949
  let sourceFixSuccess = false;
36863
36950
  let sourceFixCost = 0;
@@ -36897,19 +36984,19 @@ async function runFixRouting(options) {
36897
36984
  }
36898
36985
  }
36899
36986
  if (!sourceFixSuccess) {
36900
- return { fixed: false, cost: sourceFixCost, prdDirty: false };
36987
+ return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36901
36988
  }
36902
36989
  logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
36903
36990
  const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
36904
36991
  const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
36905
36992
  if (acceptanceResult.action === "continue") {
36906
36993
  logger?.info("acceptance", "Acceptance passed after source fix");
36907
- return { fixed: true, cost: sourceFixCost, prdDirty: false };
36994
+ return { fixed: true, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36908
36995
  }
36909
36996
  logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
36910
36997
  if (!ctx.featureDir) {
36911
36998
  logger?.error("acceptance", "Cannot regenerate test without featureDir");
36912
- return { fixed: false, cost: sourceFixCost, prdDirty: false };
36999
+ return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36913
37000
  }
36914
37001
  const testPath = await findExistingAcceptanceTestPath({
36915
37002
  acceptanceTestPaths: ctx.acceptanceTestPaths,
@@ -36926,15 +37013,15 @@ async function runFixRouting(options) {
36926
37013
  language: ctx.config.project?.language
36927
37014
  })
36928
37015
  });
36929
- return { fixed: false, cost: sourceFixCost, prdDirty: false };
37016
+ return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
36930
37017
  }
36931
37018
  const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
36932
37019
  logger?.info("acceptance.test-regen", "Test regeneration completed", {
36933
37020
  outcome: regenerated ? "success" : "failure"
36934
37021
  });
36935
- return { fixed: regenerated, cost: sourceFixCost, prdDirty: regenerated };
37022
+ return { fixed: regenerated, cost: sourceFixCost + diagnosisCost, prdDirty: regenerated };
36936
37023
  }
36937
- return { fixed: false, cost: 0, prdDirty: false };
37024
+ return { fixed: false, cost: diagnosisCost, prdDirty: false };
36938
37025
  }
36939
37026
  async function runAcceptanceLoop(ctx) {
36940
37027
  const logger = getSafeLogger();
@@ -36950,7 +37037,7 @@ async function runAcceptanceLoop(ctx) {
36950
37037
  const firstStory = prd.userStories[0];
36951
37038
  const acceptanceContext = {
36952
37039
  config: ctx.config,
36953
- effectiveConfig: ctx.config,
37040
+ rootConfig: ctx.config,
36954
37041
  prd,
36955
37042
  story: firstStory,
36956
37043
  stories: [firstStory],
@@ -36960,6 +37047,7 @@ async function runAcceptanceLoop(ctx) {
36960
37047
  testStrategy: "test-after",
36961
37048
  reasoning: "Acceptance validation"
36962
37049
  },
37050
+ projectDir: ctx.workdir,
36963
37051
  workdir: ctx.workdir,
36964
37052
  featureDir: ctx.featureDir,
36965
37053
  hooks: ctx.hooks,
@@ -37185,6 +37273,20 @@ async function runDeferredRegression(options) {
37185
37273
  const testCommand = config2.quality.commands.test ?? "bun test";
37186
37274
  const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
37187
37275
  const maxRectificationAttempts = config2.execution.regressionGate?.maxRectificationAttempts ?? 2;
37276
+ const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
37277
+ const verifyOpts = {
37278
+ workdir,
37279
+ command: testCommand,
37280
+ timeoutSeconds,
37281
+ forceExit: config2.quality.forceExit,
37282
+ detectOpenHandles: config2.quality.detectOpenHandles,
37283
+ detectOpenHandlesRetries: config2.quality.detectOpenHandlesRetries,
37284
+ timeoutRetryCount: 0,
37285
+ gracePeriodMs: config2.quality.gracePeriodMs,
37286
+ drainTimeoutMs: config2.quality.drainTimeoutMs,
37287
+ shell: config2.quality.shell,
37288
+ stripEnvVars: config2.quality.stripEnvVars
37289
+ };
37188
37290
  const counts = countStories(prd);
37189
37291
  const passedStories = prd.userStories.filter((s) => s.status === "passed");
37190
37292
  if (passedStories.length === 0) {
@@ -37202,19 +37304,7 @@ async function runDeferredRegression(options) {
37202
37304
  totalStories: counts.total,
37203
37305
  passedStories: passedStories.length
37204
37306
  });
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
- });
37307
+ const fullSuiteResult = await _regressionDeps.runVerification(verifyOpts);
37218
37308
  if (fullSuiteResult.success) {
37219
37309
  logger?.info("regression", "Full suite passed");
37220
37310
  return {
@@ -37226,7 +37316,6 @@ async function runDeferredRegression(options) {
37226
37316
  affectedStories: []
37227
37317
  };
37228
37318
  }
37229
- const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
37230
37319
  if (fullSuiteResult.status === "TIMEOUT" && acceptOnTimeout) {
37231
37320
  logger?.warn("regression", "Full-suite regression gate timed out (accepted as pass)");
37232
37321
  return {
@@ -37308,6 +37397,8 @@ async function runDeferredRegression(options) {
37308
37397
  };
37309
37398
  }
37310
37399
  let rectificationAttempts = 0;
37400
+ let storiesRectified = 0;
37401
+ let currentTestOutput = fullSuiteResult.output;
37311
37402
  const affectedStoriesList = Array.from(affectedStoriesObjs.values());
37312
37403
  for (const story of affectedStoriesList) {
37313
37404
  for (let attempt = 0;attempt < maxRectificationAttempts; attempt++) {
@@ -37319,32 +37410,51 @@ async function runDeferredRegression(options) {
37319
37410
  story,
37320
37411
  testCommand,
37321
37412
  timeoutSeconds,
37322
- testOutput: fullSuiteResult.output,
37413
+ testOutput: currentTestOutput,
37323
37414
  promptPrefix: `# DEFERRED REGRESSION: Full-Suite Failures
37324
37415
 
37325
37416
  Your story ${story.id} broke tests in the full suite. Fix these regressions.`,
37326
37417
  agentGetFn
37327
37418
  });
37328
37419
  if (fixed) {
37420
+ storiesRectified++;
37329
37421
  logger?.info("regression", `Story ${story.id} rectified successfully`);
37422
+ logger?.info("regression", "Re-running full suite after story rectification", {
37423
+ storyId: story.id,
37424
+ storiesRectified,
37425
+ storiesRemaining: affectedStoriesList.length - storiesRectified
37426
+ });
37427
+ const midResult = await _regressionDeps.runVerification(verifyOpts);
37428
+ const midSuccess = midResult.success || midResult.status === "TIMEOUT" && acceptOnTimeout;
37429
+ if (midSuccess) {
37430
+ logger?.info("regression", "Full suite passed after story rectification \u2014 early exit", {
37431
+ storyId: story.id,
37432
+ storiesRectified,
37433
+ storiesSkipped: affectedStoriesList.length - storiesRectified,
37434
+ passCount: midResult.passCount ?? 0
37435
+ });
37436
+ return {
37437
+ success: true,
37438
+ failedTests: testFilesInFailures.size,
37439
+ failedTestFiles: Array.from(testFilesInFailures),
37440
+ passedTests: midResult.passCount ?? 0,
37441
+ rectificationAttempts,
37442
+ affectedStories: Array.from(affectedStories)
37443
+ };
37444
+ }
37445
+ logger?.warn("regression", "Full suite still failing after story rectification \u2014 continuing", {
37446
+ storyId: story.id,
37447
+ failCount: midResult.failCount ?? 0,
37448
+ passCount: midResult.passCount ?? 0
37449
+ });
37450
+ if (midResult.output)
37451
+ currentTestOutput = midResult.output;
37330
37452
  break;
37331
37453
  }
37332
37454
  }
37333
37455
  }
37334
37456
  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
- });
37457
+ const retryResult = await _regressionDeps.runVerification(verifyOpts);
37348
37458
  const success2 = retryResult.success || retryResult.status === "TIMEOUT" && acceptOnTimeout;
37349
37459
  if (success2) {
37350
37460
  logger?.info("regression", "Deferred regression gate passed after rectification");
@@ -37601,12 +37711,12 @@ var init_headless_formatter = __esm(() => {
37601
37711
  // src/pipeline/subscribers/events-writer.ts
37602
37712
  import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
37603
37713
  import { homedir as homedir5 } from "os";
37604
- import { basename as basename7, join as join49 } from "path";
37714
+ import { basename as basename6, join as join44 } from "path";
37605
37715
  function wireEventsWriter(bus, feature, runId, workdir) {
37606
37716
  const logger = getSafeLogger();
37607
- const project = basename7(workdir);
37608
- const eventsDir = join49(homedir5(), ".nax", "events", project);
37609
- const eventsFile = join49(eventsDir, "events.jsonl");
37717
+ const project = basename6(workdir);
37718
+ const eventsDir = join44(homedir5(), ".nax", "events", project);
37719
+ const eventsFile = join44(eventsDir, "events.jsonl");
37610
37720
  let dirReady = false;
37611
37721
  const write = (line) => {
37612
37722
  return (async () => {
@@ -37787,12 +37897,12 @@ var init_interaction2 = __esm(() => {
37787
37897
  // src/pipeline/subscribers/registry.ts
37788
37898
  import { mkdir as mkdir4, writeFile } from "fs/promises";
37789
37899
  import { homedir as homedir6 } from "os";
37790
- import { basename as basename8, join as join50 } from "path";
37900
+ import { basename as basename7, join as join45 } from "path";
37791
37901
  function wireRegistry(bus, feature, runId, workdir) {
37792
37902
  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");
37903
+ const project = basename7(workdir);
37904
+ const runDir = join45(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
37905
+ const metaFile = join45(runDir, "meta.json");
37796
37906
  const unsub = bus.on("run:started", (_ev) => {
37797
37907
  return (async () => {
37798
37908
  try {
@@ -37802,8 +37912,8 @@ function wireRegistry(bus, feature, runId, workdir) {
37802
37912
  project,
37803
37913
  feature,
37804
37914
  workdir,
37805
- statusPath: join50(workdir, ".nax", "features", feature, "status.json"),
37806
- eventsDir: join50(workdir, ".nax", "features", feature, "runs"),
37915
+ statusPath: join45(workdir, ".nax", "features", feature, "status.json"),
37916
+ eventsDir: join45(workdir, ".nax", "features", feature, "runs"),
37807
37917
  registeredAt: new Date().toISOString()
37808
37918
  };
37809
37919
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -38328,7 +38438,7 @@ function filterOutputFiles(files) {
38328
38438
  }
38329
38439
  async function handlePipelineSuccess(ctx, pipelineResult) {
38330
38440
  const logger = getSafeLogger();
38331
- const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
38441
+ const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
38332
38442
  const prd = ctx.prd;
38333
38443
  if (pipelineResult.context.storyMetrics) {
38334
38444
  ctx.allStoryMetrics.push(...pipelineResult.context.storyMetrics);
@@ -38376,7 +38486,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
38376
38486
  const logger = getSafeLogger();
38377
38487
  let prd = ctx.prd;
38378
38488
  let prdDirty = false;
38379
- const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
38489
+ const costDelta = (pipelineResult.context.agentResult?.estimatedCost ?? 0) + (pipelineResult.stageCost ?? 0);
38380
38490
  switch (pipelineResult.finalAction) {
38381
38491
  case "pause":
38382
38492
  markStoryPaused(prd, ctx.story.id);
@@ -38455,7 +38565,7 @@ var init_pipeline_result_handler = __esm(() => {
38455
38565
  });
38456
38566
 
38457
38567
  // src/execution/iteration-runner.ts
38458
- import { join as join51 } from "path";
38568
+ import { join as join46 } from "path";
38459
38569
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
38460
38570
  const logger = getSafeLogger();
38461
38571
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -38490,15 +38600,16 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
38490
38600
  }
38491
38601
  }
38492
38602
  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;
38603
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
38494
38604
  const pipelineContext = {
38495
- config: ctx.config,
38496
- effectiveConfig,
38605
+ config: effectiveConfig,
38606
+ rootConfig: ctx.config,
38497
38607
  prd,
38498
38608
  story,
38499
38609
  stories: storiesToExecute,
38500
38610
  routing,
38501
- workdir: ctx.workdir,
38611
+ projectDir: ctx.workdir,
38612
+ workdir: story.workdir ? join46(ctx.workdir, story.workdir) : ctx.workdir,
38502
38613
  prdPath: ctx.prdPath,
38503
38614
  featureDir: ctx.featureDir,
38504
38615
  hooks: ctx.hooks,
@@ -38523,13 +38634,6 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
38523
38634
  await ctx.statusWriter.update(totalCost, iterations);
38524
38635
  const pipelineResult = await runPipeline(defaultPipeline, pipelineContext, ctx.eventEmitter);
38525
38636
  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
38637
  const handlerCtx = {
38534
38638
  config: ctx.config,
38535
38639
  prd: currentPrd,
@@ -38552,26 +38656,36 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
38552
38656
  storyStartTime,
38553
38657
  statusWriter: ctx.statusWriter
38554
38658
  };
38659
+ let iterResult;
38555
38660
  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,
38661
+ const r = await handlePipelineSuccess(handlerCtx, pipelineResult);
38662
+ iterResult = {
38663
+ prd: r.prd,
38664
+ storiesCompletedDelta: r.storiesCompletedDelta,
38665
+ costDelta: r.costDelta,
38666
+ prdDirty: r.prdDirty,
38562
38667
  finalAction: pipelineResult.finalAction
38563
38668
  };
38669
+ } else {
38670
+ const r = await handlePipelineFailure(handlerCtx, pipelineResult);
38671
+ iterResult = {
38672
+ prd: r.prd,
38673
+ storiesCompletedDelta: 0,
38674
+ costDelta: r.costDelta,
38675
+ prdDirty: r.prdDirty,
38676
+ finalAction: pipelineResult.finalAction,
38677
+ reason: pipelineResult.reason,
38678
+ subStoryCount: pipelineResult.subStoryCount
38679
+ };
38564
38680
  }
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
- };
38681
+ pipelineContext.agentResult = undefined;
38682
+ pipelineContext.prompt = undefined;
38683
+ pipelineContext.contextMarkdown = undefined;
38684
+ pipelineContext.builtContext = undefined;
38685
+ pipelineContext.verifyResult = undefined;
38686
+ pipelineContext.reviewResult = undefined;
38687
+ pipelineContext.constitution = undefined;
38688
+ return iterResult;
38575
38689
  }
38576
38690
  var _iterationRunnerDeps;
38577
38691
  var init_iteration_runner = __esm(() => {
@@ -38651,6 +38765,7 @@ __export(exports_parallel_worker, {
38651
38765
  executeStoryInWorktree: () => executeStoryInWorktree,
38652
38766
  executeParallelBatch: () => executeParallelBatch
38653
38767
  });
38768
+ import { join as join47 } from "path";
38654
38769
  async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
38655
38770
  const logger = getSafeLogger();
38656
38771
  try {
@@ -38665,10 +38780,12 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
38665
38780
  }
38666
38781
  const pipelineContext = {
38667
38782
  ...context,
38668
- effectiveConfig: context.effectiveConfig ?? context.config,
38783
+ config: context.config,
38784
+ rootConfig: context.rootConfig,
38669
38785
  story,
38670
38786
  stories: [story],
38671
- workdir: worktreePath,
38787
+ projectDir: context.projectDir,
38788
+ workdir: story.workdir ? join47(worktreePath, story.workdir) : worktreePath,
38672
38789
  routing,
38673
38790
  storyGitRef: storyGitRef ?? undefined
38674
38791
  };
@@ -38713,7 +38830,7 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
38713
38830
  }
38714
38831
  const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
38715
38832
  const storyConfig = storyEffectiveConfigs?.get(story.id);
38716
- const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
38833
+ const storyContext = storyConfig ? { ...context, config: storyConfig } : context;
38717
38834
  const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
38718
38835
  results.totalCost += result.cost;
38719
38836
  results.storyCosts.set(story.id, result.cost);
@@ -38755,19 +38872,19 @@ __export(exports_manager, {
38755
38872
  _managerDeps: () => _managerDeps,
38756
38873
  WorktreeManager: () => WorktreeManager
38757
38874
  });
38758
- import { existsSync as existsSync30, symlinkSync } from "fs";
38875
+ import { existsSync as existsSync29, symlinkSync } from "fs";
38759
38876
  import { mkdir as mkdir5 } from "fs/promises";
38760
- import { join as join52 } from "path";
38877
+ import { join as join48 } from "path";
38761
38878
 
38762
38879
  class WorktreeManager {
38763
38880
  async ensureGitExcludes(projectRoot) {
38764
38881
  const logger = getSafeLogger();
38765
- const infoDir = join52(projectRoot, ".git", "info");
38766
- const excludePath = join52(infoDir, "exclude");
38882
+ const infoDir = join48(projectRoot, ".git", "info");
38883
+ const excludePath = join48(infoDir, "exclude");
38767
38884
  try {
38768
38885
  await mkdir5(infoDir, { recursive: true });
38769
38886
  let existing = "";
38770
- if (existsSync30(excludePath)) {
38887
+ if (existsSync29(excludePath)) {
38771
38888
  existing = await Bun.file(excludePath).text();
38772
38889
  }
38773
38890
  const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -38790,7 +38907,7 @@ ${missing.join(`
38790
38907
  }
38791
38908
  async create(projectRoot, storyId) {
38792
38909
  validateStoryId(storyId);
38793
- const worktreePath = join52(projectRoot, ".nax-wt", storyId);
38910
+ const worktreePath = join48(projectRoot, ".nax-wt", storyId);
38794
38911
  const branchName = `nax/${storyId}`;
38795
38912
  try {
38796
38913
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -38831,9 +38948,9 @@ ${missing.join(`
38831
38948
  }
38832
38949
  throw new Error(`Failed to create worktree: ${String(error48)}`);
38833
38950
  }
38834
- const nodeModulesSource = join52(projectRoot, "node_modules");
38835
- if (existsSync30(nodeModulesSource)) {
38836
- const nodeModulesTarget = join52(worktreePath, "node_modules");
38951
+ const nodeModulesSource = join48(projectRoot, "node_modules");
38952
+ if (existsSync29(nodeModulesSource)) {
38953
+ const nodeModulesTarget = join48(worktreePath, "node_modules");
38837
38954
  try {
38838
38955
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
38839
38956
  } catch (error48) {
@@ -38841,9 +38958,9 @@ ${missing.join(`
38841
38958
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
38842
38959
  }
38843
38960
  }
38844
- const envSource = join52(projectRoot, ".env");
38845
- if (existsSync30(envSource)) {
38846
- const envTarget = join52(worktreePath, ".env");
38961
+ const envSource = join48(projectRoot, ".env");
38962
+ if (existsSync29(envSource)) {
38963
+ const envTarget = join48(worktreePath, ".env");
38847
38964
  try {
38848
38965
  symlinkSync(envSource, envTarget, "file");
38849
38966
  } catch (error48) {
@@ -38854,7 +38971,7 @@ ${missing.join(`
38854
38971
  }
38855
38972
  async remove(projectRoot, storyId) {
38856
38973
  validateStoryId(storyId);
38857
- const worktreePath = join52(projectRoot, ".nax-wt", storyId);
38974
+ const worktreePath = join48(projectRoot, ".nax-wt", storyId);
38858
38975
  const branchName = `nax/${storyId}`;
38859
38976
  try {
38860
38977
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -39214,10 +39331,11 @@ async function rectifyConflictedStory(options) {
39214
39331
  const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
39215
39332
  const pipelineContext = {
39216
39333
  config: config2,
39217
- effectiveConfig: config2,
39334
+ rootConfig: config2,
39218
39335
  prd,
39219
39336
  story,
39220
39337
  stories: [story],
39338
+ projectDir: workdir,
39221
39339
  workdir: worktreePath,
39222
39340
  featureDir: undefined,
39223
39341
  hooks,
@@ -39454,8 +39572,9 @@ async function executeUnified(ctx, initialPrd) {
39454
39572
  logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
39455
39573
  preRunCtx = {
39456
39574
  config: ctx.config,
39457
- effectiveConfig: ctx.config,
39575
+ rootConfig: ctx.config,
39458
39576
  prd,
39577
+ projectDir: ctx.workdir,
39459
39578
  workdir: ctx.workdir,
39460
39579
  featureDir: ctx.featureDir,
39461
39580
  story: prd.userStories[0],
@@ -39500,6 +39619,7 @@ async function executeUnified(ctx, initialPrd) {
39500
39619
  const readyStories = getAllReadyStories(prd);
39501
39620
  const batch = _unifiedExecutorDeps.selectIndependentBatch(readyStories, ctx.parallelCount);
39502
39621
  if (batch.length > 1) {
39622
+ ctx.onBeforeStory?.();
39503
39623
  for (const story of batch) {
39504
39624
  pipelineEventBus.emit({
39505
39625
  type: "story:started",
@@ -39525,8 +39645,9 @@ async function executeUnified(ctx, initialPrd) {
39525
39645
  maxConcurrency: ctx.parallelCount,
39526
39646
  pipelineContext: {
39527
39647
  config: ctx.config,
39528
- effectiveConfig: ctx.config,
39648
+ rootConfig: ctx.config,
39529
39649
  prd,
39650
+ projectDir: ctx.workdir,
39530
39651
  hooks: ctx.hooks,
39531
39652
  featureDir: ctx.featureDir,
39532
39653
  agentGetFn: ctx.agentGetFn,
@@ -39640,6 +39761,7 @@ async function executeUnified(ctx, initialPrd) {
39640
39761
  }
39641
39762
  pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
39642
39763
  }
39764
+ ctx.onBeforeStory?.();
39643
39765
  pipelineEventBus.emit({
39644
39766
  type: "story:started",
39645
39767
  storyId: singleStory.id,
@@ -39702,6 +39824,7 @@ async function executeUnified(ctx, initialPrd) {
39702
39824
  }
39703
39825
  pipelineEventBus.emit({ type: "run:resumed", feature: ctx.feature });
39704
39826
  }
39827
+ ctx.onBeforeStory?.();
39705
39828
  pipelineEventBus.emit({
39706
39829
  type: "story:started",
39707
39830
  storyId: selection.story.id,
@@ -39790,16 +39913,16 @@ var init_unified_executor = __esm(() => {
39790
39913
  });
39791
39914
 
39792
39915
  // src/project/detector.ts
39793
- import { join as join53 } from "path";
39916
+ import { join as join49 } from "path";
39794
39917
  async function detectLanguage(workdir, pkg) {
39795
39918
  const deps = _detectorDeps;
39796
- if (await deps.fileExists(join53(workdir, "go.mod")))
39919
+ if (await deps.fileExists(join49(workdir, "go.mod")))
39797
39920
  return "go";
39798
- if (await deps.fileExists(join53(workdir, "Cargo.toml")))
39921
+ if (await deps.fileExists(join49(workdir, "Cargo.toml")))
39799
39922
  return "rust";
39800
- if (await deps.fileExists(join53(workdir, "pyproject.toml")))
39923
+ if (await deps.fileExists(join49(workdir, "pyproject.toml")))
39801
39924
  return "python";
39802
- if (await deps.fileExists(join53(workdir, "requirements.txt")))
39925
+ if (await deps.fileExists(join49(workdir, "requirements.txt")))
39803
39926
  return "python";
39804
39927
  if (pkg != null) {
39805
39928
  const allDeps = {
@@ -39859,18 +39982,18 @@ async function detectLintTool(workdir, language) {
39859
39982
  if (language === "python")
39860
39983
  return "ruff";
39861
39984
  const deps = _detectorDeps;
39862
- if (await deps.fileExists(join53(workdir, "biome.json")))
39985
+ if (await deps.fileExists(join49(workdir, "biome.json")))
39863
39986
  return "biome";
39864
- if (await deps.fileExists(join53(workdir, ".eslintrc")))
39987
+ if (await deps.fileExists(join49(workdir, ".eslintrc")))
39865
39988
  return "eslint";
39866
- if (await deps.fileExists(join53(workdir, ".eslintrc.js")))
39989
+ if (await deps.fileExists(join49(workdir, ".eslintrc.js")))
39867
39990
  return "eslint";
39868
- if (await deps.fileExists(join53(workdir, ".eslintrc.json")))
39991
+ if (await deps.fileExists(join49(workdir, ".eslintrc.json")))
39869
39992
  return "eslint";
39870
39993
  return;
39871
39994
  }
39872
39995
  async function detectProjectProfile(workdir, existing) {
39873
- const pkg = await _detectorDeps.readJson(join53(workdir, "package.json"));
39996
+ const pkg = await _detectorDeps.readJson(join49(workdir, "package.json"));
39874
39997
  const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
39875
39998
  const type = existing.type !== undefined ? existing.type : detectType(pkg);
39876
39999
  const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
@@ -39907,7 +40030,7 @@ var init_project = __esm(() => {
39907
40030
 
39908
40031
  // src/execution/status-file.ts
39909
40032
  import { rename, unlink as unlink3 } from "fs/promises";
39910
- import { resolve as resolve9 } from "path";
40033
+ import { resolve as resolve10 } from "path";
39911
40034
  function countProgress(prd) {
39912
40035
  const stories = prd.userStories;
39913
40036
  const passed = stories.filter((s) => s.status === "passed").length;
@@ -39952,7 +40075,7 @@ function buildStatusSnapshot(state) {
39952
40075
  return snapshot;
39953
40076
  }
39954
40077
  async function writeStatusFile(filePath, status) {
39955
- const resolvedPath = resolve9(filePath);
40078
+ const resolvedPath = resolve10(filePath);
39956
40079
  if (filePath.includes("../") || filePath.includes("..\\")) {
39957
40080
  throw new Error("Invalid status file path: path traversal detected");
39958
40081
  }
@@ -39966,7 +40089,7 @@ async function writeStatusFile(filePath, status) {
39966
40089
  var init_status_file = () => {};
39967
40090
 
39968
40091
  // src/execution/status-writer.ts
39969
- import { join as join54 } from "path";
40092
+ import { join as join50 } from "path";
39970
40093
 
39971
40094
  class StatusWriter {
39972
40095
  statusFile;
@@ -40080,7 +40203,7 @@ class StatusWriter {
40080
40203
  if (!this._prd)
40081
40204
  return;
40082
40205
  const safeLogger = getSafeLogger();
40083
- const featureStatusPath = join54(featureDir, "status.json");
40206
+ const featureStatusPath = join50(featureDir, "status.json");
40084
40207
  const write = async () => {
40085
40208
  try {
40086
40209
  const base = this.getSnapshot(totalCost, iterations);
@@ -40291,7 +40414,7 @@ __export(exports_run_initialization, {
40291
40414
  initializeRun: () => initializeRun,
40292
40415
  _reconcileDeps: () => _reconcileDeps
40293
40416
  });
40294
- import { join as join55 } from "path";
40417
+ import { join as join51 } from "path";
40295
40418
  async function reconcileState(prd, prdPath, workdir, config2) {
40296
40419
  const logger = getSafeLogger();
40297
40420
  let reconciledCount = 0;
@@ -40309,7 +40432,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
40309
40432
  });
40310
40433
  continue;
40311
40434
  }
40312
- const effectiveWorkdir = story.workdir ? join55(workdir, story.workdir) : workdir;
40435
+ const effectiveWorkdir = story.workdir ? join51(workdir, story.workdir) : workdir;
40313
40436
  try {
40314
40437
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
40315
40438
  if (!reviewResult.success) {
@@ -41035,14 +41158,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41035
41158
  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
41159
  actScopeDepth = prevActScopeDepth;
41037
41160
  }
41038
- function recursivelyFlushAsyncActWork(returnValue, resolve10, reject) {
41161
+ function recursivelyFlushAsyncActWork(returnValue, resolve11, reject) {
41039
41162
  var queue = ReactSharedInternals.actQueue;
41040
41163
  if (queue !== null)
41041
41164
  if (queue.length !== 0)
41042
41165
  try {
41043
41166
  flushActQueue(queue);
41044
41167
  enqueueTask(function() {
41045
- return recursivelyFlushAsyncActWork(returnValue, resolve10, reject);
41168
+ return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
41046
41169
  });
41047
41170
  return;
41048
41171
  } catch (error48) {
@@ -41050,7 +41173,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41050
41173
  }
41051
41174
  else
41052
41175
  ReactSharedInternals.actQueue = null;
41053
- 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve10(returnValue);
41176
+ 0 < ReactSharedInternals.thrownErrors.length ? (queue = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(queue)) : resolve11(returnValue);
41054
41177
  }
41055
41178
  function flushActQueue(queue) {
41056
41179
  if (!isFlushing) {
@@ -41226,14 +41349,14 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41226
41349
  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
41350
  });
41228
41351
  return {
41229
- then: function(resolve10, reject) {
41352
+ then: function(resolve11, reject) {
41230
41353
  didAwaitActCall = true;
41231
41354
  thenable.then(function(returnValue) {
41232
41355
  popActScope(prevActQueue, prevActScopeDepth);
41233
41356
  if (prevActScopeDepth === 0) {
41234
41357
  try {
41235
41358
  flushActQueue(queue), enqueueTask(function() {
41236
- return recursivelyFlushAsyncActWork(returnValue, resolve10, reject);
41359
+ return recursivelyFlushAsyncActWork(returnValue, resolve11, reject);
41237
41360
  });
41238
41361
  } catch (error$0) {
41239
41362
  ReactSharedInternals.thrownErrors.push(error$0);
@@ -41244,7 +41367,7 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41244
41367
  reject(_thrownError);
41245
41368
  }
41246
41369
  } else
41247
- resolve10(returnValue);
41370
+ resolve11(returnValue);
41248
41371
  }, function(error48) {
41249
41372
  popActScope(prevActQueue, prevActScopeDepth);
41250
41373
  0 < ReactSharedInternals.thrownErrors.length ? (error48 = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, reject(error48)) : reject(error48);
@@ -41260,11 +41383,11 @@ See https://react.dev/link/invalid-hook-call for tips about how to debug and fix
41260
41383
  if (0 < ReactSharedInternals.thrownErrors.length)
41261
41384
  throw callback = aggregateErrors(ReactSharedInternals.thrownErrors), ReactSharedInternals.thrownErrors.length = 0, callback;
41262
41385
  return {
41263
- then: function(resolve10, reject) {
41386
+ then: function(resolve11, reject) {
41264
41387
  didAwaitActCall = true;
41265
41388
  prevActScopeDepth === 0 ? (ReactSharedInternals.actQueue = queue, enqueueTask(function() {
41266
- return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve10, reject);
41267
- })) : resolve10(returnValue$jscomp$0);
41389
+ return recursivelyFlushAsyncActWork(returnValue$jscomp$0, resolve11, reject);
41390
+ })) : resolve11(returnValue$jscomp$0);
41268
41391
  }
41269
41392
  };
41270
41393
  };
@@ -44106,8 +44229,8 @@ It can also happen if the client has a browser extension installed which messes
44106
44229
  currentEntangledActionThenable = {
44107
44230
  status: "pending",
44108
44231
  value: undefined,
44109
- then: function(resolve10) {
44110
- entangledListeners.push(resolve10);
44232
+ then: function(resolve11) {
44233
+ entangledListeners.push(resolve11);
44111
44234
  }
44112
44235
  };
44113
44236
  }
@@ -44131,8 +44254,8 @@ It can also happen if the client has a browser extension installed which messes
44131
44254
  status: "pending",
44132
44255
  value: null,
44133
44256
  reason: null,
44134
- then: function(resolve10) {
44135
- listeners.push(resolve10);
44257
+ then: function(resolve11) {
44258
+ listeners.push(resolve11);
44136
44259
  }
44137
44260
  };
44138
44261
  thenable.then(function() {
@@ -71522,9 +71645,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
71522
71645
 
71523
71646
  // bin/nax.ts
71524
71647
  init_source();
71525
- import { existsSync as existsSync32, mkdirSync as mkdirSync7 } from "fs";
71648
+ import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
71526
71649
  import { homedir as homedir8 } from "os";
71527
- import { join as join57 } from "path";
71650
+ import { join as join53 } from "path";
71528
71651
 
71529
71652
  // node_modules/commander/esm.mjs
71530
71653
  var import__ = __toESM(require_commander(), 1);
@@ -71550,15 +71673,15 @@ import { join as join11 } from "path";
71550
71673
  import { createInterface as createInterface2 } from "readline";
71551
71674
 
71552
71675
  // src/analyze/scanner.ts
71553
- import { existsSync as existsSync2, readdirSync } from "fs";
71676
+ import { existsSync as existsSync3, readdirSync } from "fs";
71554
71677
  import { join as join4 } from "path";
71555
71678
  async function scanCodebase(workdir) {
71556
71679
  const srcPath = join4(workdir, "src");
71557
71680
  const packageJsonPath = join4(workdir, "package.json");
71558
- const fileTree = existsSync2(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
71681
+ const fileTree = existsSync3(srcPath) ? await generateFileTree(srcPath, 3) : "No src/ directory";
71559
71682
  let dependencies = {};
71560
71683
  let devDependencies = {};
71561
- if (existsSync2(packageJsonPath)) {
71684
+ if (existsSync3(packageJsonPath)) {
71562
71685
  try {
71563
71686
  const pkg = await Bun.file(packageJsonPath).json();
71564
71687
  dependencies = pkg.dependencies || {};
@@ -71619,16 +71742,16 @@ function detectTestPatterns(workdir, dependencies, devDependencies) {
71619
71742
  } else {
71620
71743
  patterns.push("Test framework: likely bun:test (no framework dependency)");
71621
71744
  }
71622
- if (existsSync2(join4(workdir, "test"))) {
71745
+ if (existsSync3(join4(workdir, "test"))) {
71623
71746
  patterns.push("Test directory: test/");
71624
71747
  }
71625
- if (existsSync2(join4(workdir, "__tests__"))) {
71748
+ if (existsSync3(join4(workdir, "__tests__"))) {
71626
71749
  patterns.push("Test directory: __tests__/");
71627
71750
  }
71628
- if (existsSync2(join4(workdir, "tests"))) {
71751
+ if (existsSync3(join4(workdir, "tests"))) {
71629
71752
  patterns.push("Test directory: tests/");
71630
71753
  }
71631
- const hasTestFiles = existsSync2(join4(workdir, "test")) || existsSync2(join4(workdir, "src"));
71754
+ const hasTestFiles = existsSync3(join4(workdir, "test")) || existsSync3(join4(workdir, "src"));
71632
71755
  if (hasTestFiles) {
71633
71756
  patterns.push("Test files: *.test.ts, *.spec.ts");
71634
71757
  }
@@ -71640,7 +71763,7 @@ init_test_strategy();
71640
71763
 
71641
71764
  // src/context/generator.ts
71642
71765
  init_path_security();
71643
- import { existsSync as existsSync5, readFileSync } from "fs";
71766
+ import { existsSync as existsSync5 } from "fs";
71644
71767
  import { join as join6, relative } from "path";
71645
71768
 
71646
71769
  // src/context/injector.ts
@@ -71983,7 +72106,6 @@ var windsurfGenerator = {
71983
72106
  // src/context/generator.ts
71984
72107
  var _generatorDeps = {
71985
72108
  existsSync: (p) => existsSync5(p),
71986
- readFileSync: (p, enc) => readFileSync(p, enc),
71987
72109
  readTextFile: (p) => Bun.file(p).text(),
71988
72110
  writeFile: (p, content) => Bun.write(p, content),
71989
72111
  buildProjectMetadata
@@ -72085,47 +72207,41 @@ async function discoverWorkspacePackages(repoRoot) {
72085
72207
  }
72086
72208
  }
72087
72209
  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
- }
72210
+ try {
72211
+ const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
72212
+ if (Array.isArray(turbo.packages)) {
72213
+ await resolveGlobs(turbo.packages);
72214
+ }
72215
+ } catch {}
72096
72216
  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
- }
72217
+ try {
72218
+ const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
72219
+ const ws = pkg.workspaces;
72220
+ const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
72221
+ if (patterns.length > 0)
72222
+ await resolveGlobs(patterns);
72223
+ } catch {}
72106
72224
  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(`
72225
+ try {
72226
+ const raw = await _generatorDeps.readTextFile(pnpmPath);
72227
+ const lines = raw.split(`
72111
72228
  `);
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
- }
72229
+ let inPackages = false;
72230
+ const patterns = [];
72231
+ for (const line of lines) {
72232
+ if (/^packages\s*:/.test(line)) {
72233
+ inPackages = true;
72234
+ continue;
72124
72235
  }
72125
- if (patterns.length > 0)
72126
- await resolveGlobs(patterns);
72127
- } catch {}
72128
- }
72236
+ if (inPackages && /^\s+-\s+/.test(line)) {
72237
+ patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
72238
+ } else if (inPackages && !/^\s/.test(line)) {
72239
+ break;
72240
+ }
72241
+ }
72242
+ if (patterns.length > 0)
72243
+ await resolveGlobs(patterns);
72244
+ } catch {}
72129
72245
  return results.sort();
72130
72246
  }
72131
72247
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
@@ -72657,13 +72773,13 @@ function createCliInteractionBridge() {
72657
72773
  process.stdout.write(`
72658
72774
  \uD83E\uDD16 Agent: ${text}
72659
72775
  You: `);
72660
- return new Promise((resolve5) => {
72776
+ return new Promise((resolve6) => {
72661
72777
  const rl = createInterface2({ input: process.stdin, terminal: false });
72662
72778
  rl.once("line", (line) => {
72663
72779
  rl.close();
72664
- resolve5(line.trim());
72780
+ resolve6(line.trim());
72665
72781
  });
72666
- rl.once("close", () => resolve5(""));
72782
+ rl.once("close", () => resolve6(""));
72667
72783
  });
72668
72784
  }
72669
72785
  };
@@ -73168,20 +73284,20 @@ async function displayModelEfficiency(workdir) {
73168
73284
  // src/cli/status-features.ts
73169
73285
  init_source();
73170
73286
  import { existsSync as existsSync15, readdirSync as readdirSync4 } from "fs";
73171
- import { join as join14, resolve as resolve6 } from "path";
73287
+ import { join as join14, resolve as resolve7 } from "path";
73172
73288
 
73173
73289
  // src/commands/common.ts
73174
73290
  init_path_security();
73175
73291
  init_errors();
73176
73292
  import { existsSync as existsSync14, readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
73177
- import { join as join12, resolve as resolve5 } from "path";
73293
+ import { join as join12, resolve as resolve6 } from "path";
73178
73294
  function resolveProject(options = {}) {
73179
73295
  const { dir, feature } = options;
73180
73296
  let projectRoot;
73181
73297
  let naxDir;
73182
73298
  let configPath;
73183
73299
  if (dir) {
73184
- projectRoot = realpathSync2(resolve5(dir));
73300
+ projectRoot = realpathSync2(resolve6(dir));
73185
73301
  naxDir = join12(projectRoot, ".nax");
73186
73302
  if (!existsSync14(naxDir)) {
73187
73303
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
@@ -73234,7 +73350,7 @@ No features found in this project.`;
73234
73350
  };
73235
73351
  }
73236
73352
  function findProjectRoot(startDir) {
73237
- let current = resolve5(startDir);
73353
+ let current = resolve6(startDir);
73238
73354
  let depth = 0;
73239
73355
  while (depth < MAX_DIRECTORY_DEPTH) {
73240
73356
  const naxDir = join12(current, ".nax");
@@ -73551,7 +73667,7 @@ async function displayFeatureStatus(options = {}) {
73551
73667
  if (options.feature) {
73552
73668
  let featureDir;
73553
73669
  if (options.dir) {
73554
- featureDir = join14(resolve6(options.dir), ".nax", "features", options.feature);
73670
+ featureDir = join14(resolve7(options.dir), ".nax", "features", options.feature);
73555
73671
  } else {
73556
73672
  const resolved = resolveProject({ feature: options.feature });
73557
73673
  if (!resolved.featureDir) {
@@ -73661,8 +73777,8 @@ async function runsShowCommand(options) {
73661
73777
  }
73662
73778
  // src/cli/prompts-main.ts
73663
73779
  init_logger2();
73664
- import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
73665
- import { join as join29 } from "path";
73780
+ import { existsSync as existsSync19, mkdirSync as mkdirSync3 } from "fs";
73781
+ import { join as join24 } from "path";
73666
73782
 
73667
73783
  // src/pipeline/index.ts
73668
73784
  init_runner();
@@ -73737,7 +73853,7 @@ function buildFrontmatter(story, ctx, role) {
73737
73853
 
73738
73854
  // src/cli/prompts-tdd.ts
73739
73855
  init_prompts2();
73740
- import { join as join28 } from "path";
73856
+ import { join as join23 } from "path";
73741
73857
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
73742
73858
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
73743
73859
  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 +73872,7 @@ ${frontmatter}---
73756
73872
 
73757
73873
  ${session.prompt}`;
73758
73874
  if (outputDir) {
73759
- const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
73875
+ const promptFile = join23(outputDir, `${story.id}.${session.role}.md`);
73760
73876
  await Bun.write(promptFile, fullOutput);
73761
73877
  logger.info("cli", "Written TDD prompt file", {
73762
73878
  storyId: story.id,
@@ -73772,7 +73888,7 @@ ${"=".repeat(80)}`);
73772
73888
  }
73773
73889
  }
73774
73890
  if (outputDir && ctx.contextMarkdown) {
73775
- const contextFile = join28(outputDir, `${story.id}.context.md`);
73891
+ const contextFile = join23(outputDir, `${story.id}.context.md`);
73776
73892
  const frontmatter = buildFrontmatter(story, ctx);
73777
73893
  const contextOutput = `---
73778
73894
  ${frontmatter}---
@@ -73786,13 +73902,13 @@ ${ctx.contextMarkdown}`;
73786
73902
  async function promptsCommand(options) {
73787
73903
  const logger = getLogger();
73788
73904
  const { feature, workdir, config: config2, storyId, outputDir } = options;
73789
- const naxDir = join29(workdir, ".nax");
73790
- if (!existsSync20(naxDir)) {
73905
+ const naxDir = join24(workdir, ".nax");
73906
+ if (!existsSync19(naxDir)) {
73791
73907
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
73792
73908
  }
73793
- const featureDir = join29(naxDir, "features", feature);
73794
- const prdPath = join29(featureDir, "prd.json");
73795
- if (!existsSync20(prdPath)) {
73909
+ const featureDir = join24(naxDir, "features", feature);
73910
+ const prdPath = join24(featureDir, "prd.json");
73911
+ if (!existsSync19(prdPath)) {
73796
73912
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
73797
73913
  }
73798
73914
  const prd = await loadPRD(prdPath);
@@ -73813,7 +73929,7 @@ async function promptsCommand(options) {
73813
73929
  for (const story of stories) {
73814
73930
  const ctx = {
73815
73931
  config: config2,
73816
- effectiveConfig: config2,
73932
+ rootConfig: config2,
73817
73933
  prd,
73818
73934
  story,
73819
73935
  stories: [story],
@@ -73823,6 +73939,7 @@ async function promptsCommand(options) {
73823
73939
  testStrategy: "test-after",
73824
73940
  reasoning: "Placeholder routing"
73825
73941
  },
73942
+ projectDir: workdir,
73826
73943
  workdir,
73827
73944
  featureDir,
73828
73945
  hooks: { hooks: {} }
@@ -73852,10 +73969,10 @@ ${frontmatter}---
73852
73969
 
73853
73970
  ${ctx.prompt}`;
73854
73971
  if (outputDir) {
73855
- const promptFile = join29(outputDir, `${story.id}.prompt.md`);
73972
+ const promptFile = join24(outputDir, `${story.id}.prompt.md`);
73856
73973
  await Bun.write(promptFile, fullOutput);
73857
73974
  if (ctx.contextMarkdown) {
73858
- const contextFile = join29(outputDir, `${story.id}.context.md`);
73975
+ const contextFile = join24(outputDir, `${story.id}.context.md`);
73859
73976
  const contextOutput = `---
73860
73977
  ${frontmatter}---
73861
73978
 
@@ -73881,8 +73998,8 @@ ${"=".repeat(80)}`);
73881
73998
  return processedStories;
73882
73999
  }
73883
74000
  // src/cli/prompts-init.ts
73884
- import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
73885
- import { join as join30 } from "path";
74001
+ import { existsSync as existsSync20, mkdirSync as mkdirSync4 } from "fs";
74002
+ import { join as join25 } from "path";
73886
74003
  var TEMPLATE_ROLES = [
73887
74004
  { file: "test-writer.md", role: "test-writer" },
73888
74005
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -73906,9 +74023,9 @@ var TEMPLATE_HEADER = `<!--
73906
74023
  `;
73907
74024
  async function promptsInitCommand(options) {
73908
74025
  const { workdir, force = false, autoWireConfig = true } = options;
73909
- const templatesDir = join30(workdir, ".nax", "templates");
74026
+ const templatesDir = join25(workdir, ".nax", "templates");
73910
74027
  mkdirSync4(templatesDir, { recursive: true });
73911
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join30(templatesDir, f)));
74028
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync20(join25(templatesDir, f)));
73912
74029
  if (existingFiles.length > 0 && !force) {
73913
74030
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
73914
74031
  Pass --force to overwrite existing templates.`);
@@ -73916,7 +74033,7 @@ async function promptsInitCommand(options) {
73916
74033
  }
73917
74034
  const written = [];
73918
74035
  for (const template of TEMPLATE_ROLES) {
73919
- const filePath = join30(templatesDir, template.file);
74036
+ const filePath = join25(templatesDir, template.file);
73920
74037
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
73921
74038
  const content = TEMPLATE_HEADER + roleBody;
73922
74039
  await Bun.write(filePath, content);
@@ -73932,8 +74049,8 @@ async function promptsInitCommand(options) {
73932
74049
  return written;
73933
74050
  }
73934
74051
  async function autoWirePromptsConfig(workdir) {
73935
- const configPath = join30(workdir, "nax.config.json");
73936
- if (!existsSync21(configPath)) {
74052
+ const configPath = join25(workdir, "nax.config.json");
74053
+ if (!existsSync20(configPath)) {
73937
74054
  const exampleConfig = JSON.stringify({
73938
74055
  prompts: {
73939
74056
  overrides: {
@@ -74097,8 +74214,8 @@ function pad(str, width) {
74097
74214
  init_config();
74098
74215
  init_logger2();
74099
74216
  init_prd();
74100
- import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
74101
- import { join as join35 } from "path";
74217
+ import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
74218
+ import { join as join30 } from "path";
74102
74219
 
74103
74220
  // src/cli/diagnose-analysis.ts
74104
74221
  function detectFailurePattern(story, prd, status) {
@@ -74297,8 +74414,8 @@ function isProcessAlive2(pid) {
74297
74414
  }
74298
74415
  }
74299
74416
  async function loadStatusFile2(workdir) {
74300
- const statusPath = join35(workdir, ".nax", "status.json");
74301
- if (!existsSync23(statusPath))
74417
+ const statusPath = join30(workdir, ".nax", "status.json");
74418
+ if (!existsSync22(statusPath))
74302
74419
  return null;
74303
74420
  try {
74304
74421
  return await Bun.file(statusPath).json();
@@ -74325,7 +74442,7 @@ async function countCommitsSince(workdir, since) {
74325
74442
  }
74326
74443
  }
74327
74444
  async function checkLock(workdir) {
74328
- const lockFile = Bun.file(join35(workdir, "nax.lock"));
74445
+ const lockFile = Bun.file(join30(workdir, "nax.lock"));
74329
74446
  if (!await lockFile.exists())
74330
74447
  return { lockPresent: false };
74331
74448
  try {
@@ -74343,8 +74460,8 @@ async function diagnoseCommand(options = {}) {
74343
74460
  const logger = getLogger();
74344
74461
  const workdir = options.workdir ?? process.cwd();
74345
74462
  const naxSubdir = findProjectDir(workdir);
74346
- let projectDir = naxSubdir ? join35(naxSubdir, "..") : null;
74347
- if (!projectDir && existsSync23(join35(workdir, ".nax"))) {
74463
+ let projectDir = naxSubdir ? join30(naxSubdir, "..") : null;
74464
+ if (!projectDir && existsSync22(join30(workdir, ".nax"))) {
74348
74465
  projectDir = workdir;
74349
74466
  }
74350
74467
  if (!projectDir)
@@ -74355,8 +74472,8 @@ async function diagnoseCommand(options = {}) {
74355
74472
  if (status2) {
74356
74473
  feature = status2.run.feature;
74357
74474
  } else {
74358
- const featuresDir = join35(projectDir, ".nax", "features");
74359
- if (!existsSync23(featuresDir))
74475
+ const featuresDir = join30(projectDir, ".nax", "features");
74476
+ if (!existsSync22(featuresDir))
74360
74477
  throw new Error("No features found in project");
74361
74478
  const features = readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
74362
74479
  if (features.length === 0)
@@ -74365,9 +74482,9 @@ async function diagnoseCommand(options = {}) {
74365
74482
  logger.info("diagnose", "No feature specified, using first found", { feature });
74366
74483
  }
74367
74484
  }
74368
- const featureDir = join35(projectDir, ".nax", "features", feature);
74369
- const prdPath = join35(featureDir, "prd.json");
74370
- if (!existsSync23(prdPath))
74485
+ const featureDir = join30(projectDir, ".nax", "features", feature);
74486
+ const prdPath = join30(featureDir, "prd.json");
74487
+ if (!existsSync22(prdPath))
74371
74488
  throw new Error(`Feature not found: ${feature}`);
74372
74489
  const prd = await loadPRD(prdPath);
74373
74490
  const status = await loadStatusFile2(projectDir);
@@ -74408,8 +74525,8 @@ init_interaction();
74408
74525
  // src/cli/generate.ts
74409
74526
  init_source();
74410
74527
  init_loader();
74411
- import { existsSync as existsSync24 } from "fs";
74412
- import { join as join36 } from "path";
74528
+ import { existsSync as existsSync23 } from "fs";
74529
+ import { join as join31 } from "path";
74413
74530
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
74414
74531
  async function generateCommand(options) {
74415
74532
  const workdir = options.dir ?? process.cwd();
@@ -74452,7 +74569,7 @@ async function generateCommand(options) {
74452
74569
  return;
74453
74570
  }
74454
74571
  if (options.package) {
74455
- const packageDir = join36(workdir, options.package);
74572
+ const packageDir = join31(workdir, options.package);
74456
74573
  if (dryRun) {
74457
74574
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
74458
74575
  }
@@ -74472,10 +74589,10 @@ async function generateCommand(options) {
74472
74589
  process.exit(1);
74473
74590
  return;
74474
74591
  }
74475
- const contextPath = options.context ? join36(workdir, options.context) : join36(workdir, ".nax/context.md");
74476
- const outputDir = options.output ? join36(workdir, options.output) : workdir;
74592
+ const contextPath = options.context ? join31(workdir, options.context) : join31(workdir, ".nax/context.md");
74593
+ const outputDir = options.output ? join31(workdir, options.output) : workdir;
74477
74594
  const autoInject = !options.noAutoInject;
74478
- if (!existsSync24(contextPath)) {
74595
+ if (!existsSync23(contextPath)) {
74479
74596
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
74480
74597
  console.error(source_default.yellow(" Create .nax/context.md first, or run `nax init` to scaffold it."));
74481
74598
  process.exit(1);
@@ -74577,8 +74694,8 @@ async function generateCommand(options) {
74577
74694
  }
74578
74695
  // src/cli/config-display.ts
74579
74696
  init_loader();
74580
- import { existsSync as existsSync26 } from "fs";
74581
- import { join as join38 } from "path";
74697
+ import { existsSync as existsSync25 } from "fs";
74698
+ import { join as join33 } from "path";
74582
74699
 
74583
74700
  // src/cli/config-descriptions.ts
74584
74701
  var FIELD_DESCRIPTIONS = {
@@ -74817,10 +74934,10 @@ function deepEqual(a, b) {
74817
74934
  // src/cli/config-get.ts
74818
74935
  init_defaults();
74819
74936
  init_loader();
74820
- import { existsSync as existsSync25 } from "fs";
74821
- import { join as join37 } from "path";
74937
+ import { existsSync as existsSync24 } from "fs";
74938
+ import { join as join32 } from "path";
74822
74939
  async function loadConfigFile(path15) {
74823
- if (!existsSync25(path15))
74940
+ if (!existsSync24(path15))
74824
74941
  return null;
74825
74942
  try {
74826
74943
  return await Bun.file(path15).json();
@@ -74840,7 +74957,7 @@ async function loadProjectConfig() {
74840
74957
  const projectDir = findProjectDir();
74841
74958
  if (!projectDir)
74842
74959
  return null;
74843
- const projectPath = join37(projectDir, "config.json");
74960
+ const projectPath = join32(projectDir, "config.json");
74844
74961
  return await loadConfigFile(projectPath);
74845
74962
  }
74846
74963
 
@@ -74900,14 +75017,14 @@ async function configCommand(config2, options = {}) {
74900
75017
  function determineConfigSources() {
74901
75018
  const globalPath = globalConfigPath();
74902
75019
  const projectDir = findProjectDir();
74903
- const projectPath = projectDir ? join38(projectDir, "config.json") : null;
75020
+ const projectPath = projectDir ? join33(projectDir, "config.json") : null;
74904
75021
  return {
74905
75022
  global: fileExists(globalPath) ? globalPath : null,
74906
75023
  project: projectPath && fileExists(projectPath) ? projectPath : null
74907
75024
  };
74908
75025
  }
74909
75026
  function fileExists(path15) {
74910
- return existsSync26(path15);
75027
+ return existsSync25(path15);
74911
75028
  }
74912
75029
  function displayConfigWithDescriptions(obj, path15, sources, indent = 0) {
74913
75030
  const indentStr = " ".repeat(indent);
@@ -75049,15 +75166,15 @@ init_paths();
75049
75166
  init_profile();
75050
75167
  import { mkdirSync as mkdirSync5 } from "fs";
75051
75168
  import { readdirSync as readdirSync7 } from "fs";
75052
- import { join as join39 } from "path";
75169
+ import { join as join34 } from "path";
75053
75170
  var _profileCLIDeps = {
75054
75171
  env: process.env
75055
75172
  };
75056
75173
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
75057
75174
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
75058
75175
  async function profileListCommand(startDir) {
75059
- const globalProfilesDir = join39(globalConfigDir(), "profiles");
75060
- const projectProfilesDir = join39(projectConfigDir(startDir), "profiles");
75176
+ const globalProfilesDir = join34(globalConfigDir(), "profiles");
75177
+ const projectProfilesDir = join34(projectConfigDir(startDir), "profiles");
75061
75178
  const globalProfiles = scanProfileDir(globalProfilesDir);
75062
75179
  const projectProfiles = scanProfileDir(projectProfilesDir);
75063
75180
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -75116,7 +75233,7 @@ function maskProfileValues(obj) {
75116
75233
  return result;
75117
75234
  }
75118
75235
  async function profileUseCommand(profileName, startDir) {
75119
- const configPath = join39(projectConfigDir(startDir), "config.json");
75236
+ const configPath = join34(projectConfigDir(startDir), "config.json");
75120
75237
  const configFile = Bun.file(configPath);
75121
75238
  let existing = {};
75122
75239
  if (await configFile.exists()) {
@@ -75135,8 +75252,8 @@ async function profileCurrentCommand(startDir) {
75135
75252
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
75136
75253
  }
75137
75254
  async function profileCreateCommand(profileName, startDir) {
75138
- const profilesDir = join39(projectConfigDir(startDir), "profiles");
75139
- const profilePath = join39(profilesDir, `${profileName}.json`);
75255
+ const profilesDir = join34(projectConfigDir(startDir), "profiles");
75256
+ const profilePath = join34(profilesDir, `${profileName}.json`);
75140
75257
  const profileFile = Bun.file(profilePath);
75141
75258
  if (await profileFile.exists()) {
75142
75259
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -75201,25 +75318,25 @@ async function diagnose(options) {
75201
75318
  }
75202
75319
 
75203
75320
  // src/commands/logs.ts
75204
- import { existsSync as existsSync28 } from "fs";
75205
- import { join as join43 } from "path";
75321
+ import { existsSync as existsSync27 } from "fs";
75322
+ import { join as join38 } from "path";
75206
75323
 
75207
75324
  // src/commands/logs-formatter.ts
75208
75325
  init_source();
75209
75326
  init_formatter();
75210
75327
  import { readdirSync as readdirSync9 } from "fs";
75211
- import { join as join42 } from "path";
75328
+ import { join as join37 } from "path";
75212
75329
 
75213
75330
  // src/commands/logs-reader.ts
75214
- import { existsSync as existsSync27, readdirSync as readdirSync8 } from "fs";
75331
+ import { existsSync as existsSync26, readdirSync as readdirSync8 } from "fs";
75215
75332
  import { readdir as readdir3 } from "fs/promises";
75216
- import { join as join41 } from "path";
75333
+ import { join as join36 } from "path";
75217
75334
 
75218
75335
  // src/utils/paths.ts
75219
75336
  import { homedir as homedir4 } from "os";
75220
- import { join as join40 } from "path";
75337
+ import { join as join35 } from "path";
75221
75338
  function getRunsDir() {
75222
- return process.env.NAX_RUNS_DIR ?? join40(homedir4(), ".nax", "runs");
75339
+ return process.env.NAX_RUNS_DIR ?? join35(homedir4(), ".nax", "runs");
75223
75340
  }
75224
75341
 
75225
75342
  // src/commands/logs-reader.ts
@@ -75236,7 +75353,7 @@ async function resolveRunFileFromRegistry(runId) {
75236
75353
  }
75237
75354
  let matched = null;
75238
75355
  for (const entry of entries) {
75239
- const metaPath = join41(runsDir, entry, "meta.json");
75356
+ const metaPath = join36(runsDir, entry, "meta.json");
75240
75357
  try {
75241
75358
  const meta3 = await Bun.file(metaPath).json();
75242
75359
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -75248,7 +75365,7 @@ async function resolveRunFileFromRegistry(runId) {
75248
75365
  if (!matched) {
75249
75366
  throw new Error(`Run not found in registry: ${runId}`);
75250
75367
  }
75251
- if (!existsSync27(matched.eventsDir)) {
75368
+ if (!existsSync26(matched.eventsDir)) {
75252
75369
  console.log(`Log directory unavailable for run: ${runId}`);
75253
75370
  return null;
75254
75371
  }
@@ -75258,14 +75375,14 @@ async function resolveRunFileFromRegistry(runId) {
75258
75375
  return null;
75259
75376
  }
75260
75377
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
75261
- return join41(matched.eventsDir, specificFile ?? files[0]);
75378
+ return join36(matched.eventsDir, specificFile ?? files[0]);
75262
75379
  }
75263
75380
  async function selectRunFile(runsDir) {
75264
75381
  const files = readdirSync8(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
75265
75382
  if (files.length === 0) {
75266
75383
  return null;
75267
75384
  }
75268
- return join41(runsDir, files[0]);
75385
+ return join36(runsDir, files[0]);
75269
75386
  }
75270
75387
  async function extractRunSummary(filePath) {
75271
75388
  const file3 = Bun.file(filePath);
@@ -75350,7 +75467,7 @@ Runs:
75350
75467
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
75351
75468
  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
75469
  for (const file3 of files) {
75353
- const filePath = join42(runsDir, file3);
75470
+ const filePath = join37(runsDir, file3);
75354
75471
  const summary = await extractRunSummary(filePath);
75355
75472
  const timestamp = file3.replace(".jsonl", "");
75356
75473
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -75464,7 +75581,7 @@ async function logsCommand(options) {
75464
75581
  return;
75465
75582
  }
75466
75583
  const resolved = resolveProject({ dir: options.dir });
75467
- const naxDir = join43(resolved.projectDir, ".nax");
75584
+ const naxDir = join38(resolved.projectDir, ".nax");
75468
75585
  const configPath = resolved.configPath;
75469
75586
  const configFile = Bun.file(configPath);
75470
75587
  const config2 = await configFile.json();
@@ -75472,9 +75589,9 @@ async function logsCommand(options) {
75472
75589
  if (!featureName) {
75473
75590
  throw new Error("No feature specified in config.json");
75474
75591
  }
75475
- const featureDir = join43(naxDir, "features", featureName);
75476
- const runsDir = join43(featureDir, "runs");
75477
- if (!existsSync28(runsDir)) {
75592
+ const featureDir = join38(naxDir, "features", featureName);
75593
+ const runsDir = join38(featureDir, "runs");
75594
+ if (!existsSync27(runsDir)) {
75478
75595
  throw new Error(`No runs directory found for feature: ${featureName}`);
75479
75596
  }
75480
75597
  if (options.list) {
@@ -75497,8 +75614,8 @@ init_source();
75497
75614
  init_config();
75498
75615
  init_prd();
75499
75616
  init_precheck();
75500
- import { existsSync as existsSync29 } from "fs";
75501
- import { join as join44 } from "path";
75617
+ import { existsSync as existsSync28 } from "fs";
75618
+ import { join as join39 } from "path";
75502
75619
  async function precheckCommand(options) {
75503
75620
  const resolved = resolveProject({
75504
75621
  dir: options.dir,
@@ -75520,14 +75637,14 @@ async function precheckCommand(options) {
75520
75637
  process.exit(1);
75521
75638
  }
75522
75639
  }
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)) {
75640
+ const naxDir = join39(resolved.projectDir, ".nax");
75641
+ const featureDir = join39(naxDir, "features", featureName);
75642
+ const prdPath = join39(featureDir, "prd.json");
75643
+ if (!existsSync28(featureDir)) {
75527
75644
  console.error(source_default.red(`Feature not found: ${featureName}`));
75528
75645
  process.exit(1);
75529
75646
  }
75530
- if (!existsSync29(prdPath)) {
75647
+ if (!existsSync28(prdPath)) {
75531
75648
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
75532
75649
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
75533
75650
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -75544,7 +75661,7 @@ async function precheckCommand(options) {
75544
75661
  // src/commands/runs.ts
75545
75662
  init_source();
75546
75663
  import { readdir as readdir4 } from "fs/promises";
75547
- import { join as join45 } from "path";
75664
+ import { join as join40 } from "path";
75548
75665
  var DEFAULT_LIMIT = 20;
75549
75666
  var _runsCmdDeps = {
75550
75667
  getRunsDir
@@ -75599,7 +75716,7 @@ async function runsCommand(options = {}) {
75599
75716
  }
75600
75717
  const rows = [];
75601
75718
  for (const entry of entries) {
75602
- const metaPath = join45(runsDir, entry, "meta.json");
75719
+ const metaPath = join40(runsDir, entry, "meta.json");
75603
75720
  let meta3;
75604
75721
  try {
75605
75722
  meta3 = await Bun.file(metaPath).json();
@@ -75676,7 +75793,7 @@ async function runsCommand(options = {}) {
75676
75793
 
75677
75794
  // src/commands/unlock.ts
75678
75795
  init_source();
75679
- import { join as join46 } from "path";
75796
+ import { join as join41 } from "path";
75680
75797
  function isProcessAlive3(pid) {
75681
75798
  try {
75682
75799
  process.kill(pid, 0);
@@ -75691,7 +75808,7 @@ function formatLockAge(ageMs) {
75691
75808
  }
75692
75809
  async function unlockCommand(options) {
75693
75810
  const workdir = options.dir ?? process.cwd();
75694
- const lockPath = join46(workdir, "nax.lock");
75811
+ const lockPath = join41(workdir, "nax.lock");
75695
75812
  const lockFile = Bun.file(lockPath);
75696
75813
  const exists = await lockFile.exists();
75697
75814
  if (!exists) {
@@ -75767,11 +75884,9 @@ async function runCompletionPhase(options) {
75767
75884
  const regressionAlreadyPassed = postRunStatus?.regression?.status === "passed";
75768
75885
  if (acceptanceAlreadyPassed && regressionAlreadyPassed) {
75769
75886
  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
75887
  } else {
75772
75888
  if (acceptanceAlreadyPassed) {
75773
75889
  logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
75774
- console.info("Acceptance already passed \u2014 skipping acceptance phase");
75775
75890
  } else if (options.config.acceptance.enabled && isComplete(options.prd)) {
75776
75891
  options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
75777
75892
  const acceptanceResult = await _runnerCompletionDeps.runAcceptanceLoop({
@@ -75970,6 +76085,7 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
75970
76085
  startTime: options.startTime,
75971
76086
  parallelCount: options.parallel,
75972
76087
  agentGetFn: options.agentGetFn,
76088
+ onBeforeStory: options.onBeforeStory,
75973
76089
  pidRegistry: options.pidRegistry,
75974
76090
  interactionChain: options.interactionChain,
75975
76091
  batchPlan
@@ -76094,6 +76210,7 @@ async function run(options) {
76094
76210
  headless,
76095
76211
  parallel,
76096
76212
  agentGetFn,
76213
+ onBeforeStory: () => registry2.resetStoryState(),
76097
76214
  pidRegistry,
76098
76215
  interactionChain
76099
76216
  }, prd, pluginRegistry);
@@ -81677,8 +81794,8 @@ class Ink {
81677
81794
  }
81678
81795
  }
81679
81796
  async waitUntilExit() {
81680
- this.exitPromise ||= new Promise((resolve10, reject2) => {
81681
- this.resolveExitPromise = resolve10;
81797
+ this.exitPromise ||= new Promise((resolve11, reject2) => {
81798
+ this.resolveExitPromise = resolve11;
81682
81799
  this.rejectExitPromise = reject2;
81683
81800
  });
81684
81801
  if (!this.beforeExitHandler) {
@@ -83491,7 +83608,7 @@ async function promptForConfirmation(question) {
83491
83608
  if (!process.stdin.isTTY) {
83492
83609
  return true;
83493
83610
  }
83494
- return new Promise((resolve10) => {
83611
+ return new Promise((resolve11) => {
83495
83612
  process.stdout.write(source_default.bold(`${question} [Y/n] `));
83496
83613
  process.stdin.setRawMode(true);
83497
83614
  process.stdin.resume();
@@ -83504,9 +83621,9 @@ async function promptForConfirmation(question) {
83504
83621
  process.stdout.write(`
83505
83622
  `);
83506
83623
  if (answer === "n") {
83507
- resolve10(false);
83624
+ resolve11(false);
83508
83625
  } else {
83509
- resolve10(true);
83626
+ resolve11(true);
83510
83627
  }
83511
83628
  };
83512
83629
  process.stdin.on("data", handler);
@@ -83535,15 +83652,15 @@ Next: nax generate --package ${options.package}`));
83535
83652
  }
83536
83653
  return;
83537
83654
  }
83538
- const naxDir = join57(workdir, ".nax");
83539
- if (existsSync32(naxDir) && !options.force) {
83655
+ const naxDir = join53(workdir, ".nax");
83656
+ if (existsSync31(naxDir) && !options.force) {
83540
83657
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
83541
83658
  return;
83542
83659
  }
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({
83660
+ mkdirSync7(join53(naxDir, "features"), { recursive: true });
83661
+ mkdirSync7(join53(naxDir, "hooks"), { recursive: true });
83662
+ await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
83663
+ await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
83547
83664
  hooks: {
83548
83665
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
83549
83666
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -83551,12 +83668,12 @@ Next: nax generate --package ${options.package}`));
83551
83668
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
83552
83669
  }
83553
83670
  }, null, 2));
83554
- await Bun.write(join57(naxDir, ".gitignore"), `# nax temp files
83671
+ await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
83555
83672
  *.tmp
83556
83673
  .paused.json
83557
83674
  .nax-verifier-verdict.json
83558
83675
  `);
83559
- await Bun.write(join57(naxDir, "context.md"), `# Project Context
83676
+ await Bun.write(join53(naxDir, "context.md"), `# Project Context
83560
83677
 
83561
83678
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
83562
83679
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -83653,7 +83770,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
83653
83770
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
83654
83771
  process.exit(1);
83655
83772
  }
83656
- if (options.from && !existsSync32(options.from)) {
83773
+ if (options.from && !existsSync31(options.from)) {
83657
83774
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
83658
83775
  process.exit(1);
83659
83776
  }
@@ -83686,10 +83803,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
83686
83803
  console.error(source_default.red("nax not initialized. Run: nax init"));
83687
83804
  process.exit(1);
83688
83805
  }
83689
- const featureDir = join57(naxDir, "features", options.feature);
83690
- const prdPath = join57(featureDir, "prd.json");
83806
+ const featureDir = join53(naxDir, "features", options.feature);
83807
+ const prdPath = join53(featureDir, "prd.json");
83691
83808
  if (options.plan && options.from) {
83692
- if (existsSync32(prdPath) && !options.force) {
83809
+ if (existsSync31(prdPath) && !options.force) {
83693
83810
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
83694
83811
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
83695
83812
  process.exit(1);
@@ -83709,10 +83826,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
83709
83826
  }
83710
83827
  }
83711
83828
  try {
83712
- const planLogDir = join57(featureDir, "plan");
83829
+ const planLogDir = join53(featureDir, "plan");
83713
83830
  mkdirSync7(planLogDir, { recursive: true });
83714
83831
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
83715
- const planLogPath = join57(planLogDir, `${planLogId}.jsonl`);
83832
+ const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
83716
83833
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
83717
83834
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
83718
83835
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -83751,15 +83868,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
83751
83868
  process.exit(1);
83752
83869
  }
83753
83870
  }
83754
- if (!existsSync32(prdPath)) {
83871
+ if (!existsSync31(prdPath)) {
83755
83872
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
83756
83873
  process.exit(1);
83757
83874
  }
83758
83875
  resetLogger();
83759
- const runsDir = join57(featureDir, "runs");
83876
+ const runsDir = join53(featureDir, "runs");
83760
83877
  mkdirSync7(runsDir, { recursive: true });
83761
83878
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
83762
- const logFilePath = join57(runsDir, `${runId}.jsonl`);
83879
+ const logFilePath = join53(runsDir, `${runId}.jsonl`);
83763
83880
  const isTTY = process.stdout.isTTY ?? false;
83764
83881
  const headlessFlag = options.headless ?? false;
83765
83882
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -83775,7 +83892,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
83775
83892
  config2.autoMode.defaultAgent = options.agent;
83776
83893
  }
83777
83894
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
83778
- const globalNaxDir = join57(homedir8(), ".nax");
83895
+ const globalNaxDir = join53(homedir8(), ".nax");
83779
83896
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
83780
83897
  const eventEmitter = new PipelineEventEmitter;
83781
83898
  let tuiInstance;
@@ -83798,7 +83915,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
83798
83915
  } else {
83799
83916
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
83800
83917
  }
83801
- const statusFilePath = join57(workdir, ".nax", "status.json");
83918
+ const statusFilePath = join53(workdir, ".nax", "status.json");
83802
83919
  let parallel;
83803
83920
  if (options.parallel !== undefined) {
83804
83921
  parallel = Number.parseInt(options.parallel, 10);
@@ -83824,9 +83941,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
83824
83941
  headless: useHeadless,
83825
83942
  skipPrecheck: options.skipPrecheck ?? false
83826
83943
  });
83827
- const latestSymlink = join57(runsDir, "latest.jsonl");
83944
+ const latestSymlink = join53(runsDir, "latest.jsonl");
83828
83945
  try {
83829
- if (existsSync32(latestSymlink)) {
83946
+ if (existsSync31(latestSymlink)) {
83830
83947
  Bun.spawnSync(["rm", latestSymlink]);
83831
83948
  }
83832
83949
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -83862,9 +83979,9 @@ features.command("create <name>").description("Create a new feature").option("-d
83862
83979
  console.error(source_default.red("nax not initialized. Run: nax init"));
83863
83980
  process.exit(1);
83864
83981
  }
83865
- const featureDir = join57(naxDir, "features", name);
83982
+ const featureDir = join53(naxDir, "features", name);
83866
83983
  mkdirSync7(featureDir, { recursive: true });
83867
- await Bun.write(join57(featureDir, "spec.md"), `# Feature: ${name}
83984
+ await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
83868
83985
 
83869
83986
  ## Overview
83870
83987
 
@@ -83897,7 +84014,7 @@ features.command("create <name>").description("Create a new feature").option("-d
83897
84014
 
83898
84015
  <!-- What this feature explicitly does NOT cover. -->
83899
84016
  `);
83900
- await Bun.write(join57(featureDir, "progress.txt"), `# Progress: ${name}
84017
+ await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
83901
84018
 
83902
84019
  Created: ${new Date().toISOString()}
83903
84020
 
@@ -83923,8 +84040,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
83923
84040
  console.error(source_default.red("nax not initialized."));
83924
84041
  process.exit(1);
83925
84042
  }
83926
- const featuresDir = join57(naxDir, "features");
83927
- if (!existsSync32(featuresDir)) {
84043
+ const featuresDir = join53(naxDir, "features");
84044
+ if (!existsSync31(featuresDir)) {
83928
84045
  console.log(source_default.dim("No features yet."));
83929
84046
  return;
83930
84047
  }
@@ -83938,8 +84055,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
83938
84055
  Features:
83939
84056
  `));
83940
84057
  for (const name of entries) {
83941
- const prdPath = join57(featuresDir, name, "prd.json");
83942
- if (existsSync32(prdPath)) {
84058
+ const prdPath = join53(featuresDir, name, "prd.json");
84059
+ if (existsSync31(prdPath)) {
83943
84060
  const prd = await loadPRD(prdPath);
83944
84061
  const c = countStories(prd);
83945
84062
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -83973,10 +84090,10 @@ Use: nax plan -f <feature> --from <spec>`));
83973
84090
  cliOverrides.profile = options.profile;
83974
84091
  }
83975
84092
  const config2 = await loadConfig(workdir, cliOverrides);
83976
- const featureLogDir = join57(naxDir, "features", options.feature, "plan");
84093
+ const featureLogDir = join53(naxDir, "features", options.feature, "plan");
83977
84094
  mkdirSync7(featureLogDir, { recursive: true });
83978
84095
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
83979
- const planLogPath = join57(featureLogDir, `${planLogId}.jsonl`);
84096
+ const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
83980
84097
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
83981
84098
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
83982
84099
  try {