@nathapp/nax 0.60.0 → 0.60.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +357 -733
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -18654,7 +18654,7 @@ var init_schemas3 = __esm(() => {
18654
18654
  }),
18655
18655
  acceptance: AcceptanceConfigSchema.default({
18656
18656
  enabled: true,
18657
- maxRetries: 2,
18657
+ maxRetries: 3,
18658
18658
  generateTests: true,
18659
18659
  testPath: ".nax-acceptance.test.ts",
18660
18660
  model: "fast",
@@ -27053,7 +27053,8 @@ ${stderr}` };
27053
27053
  testStrategy: ctx.config.acceptance.testStrategy,
27054
27054
  testFramework: ctx.config.acceptance.testFramework,
27055
27055
  adapter: agent ?? undefined,
27056
- ..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {}
27056
+ ..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {},
27057
+ ..."previousFailure" in ctx && ctx.previousFailure ? { previousFailure: ctx.previousFailure } : {}
27057
27058
  });
27058
27059
  await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
27059
27060
  }
@@ -36571,7 +36572,7 @@ var package_default;
36571
36572
  var init_package = __esm(() => {
36572
36573
  package_default = {
36573
36574
  name: "@nathapp/nax",
36574
- version: "0.60.0",
36575
+ version: "0.60.1",
36575
36576
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
36576
36577
  type: "module",
36577
36578
  bin: {
@@ -36651,8 +36652,8 @@ var init_version = __esm(() => {
36651
36652
  NAX_VERSION = package_default.version;
36652
36653
  NAX_COMMIT = (() => {
36653
36654
  try {
36654
- if (/^[0-9a-f]{6,10}$/.test("73c9c082"))
36655
- return "73c9c082";
36655
+ if (/^[0-9a-f]{6,10}$/.test("2b74a9ef"))
36656
+ return "2b74a9ef";
36656
36657
  } catch {}
36657
36658
  try {
36658
36659
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -36968,196 +36969,6 @@ var init_crash_recovery = __esm(() => {
36968
36969
  init_crash_heartbeat();
36969
36970
  });
36970
36971
 
36971
- // src/acceptance/fix-generator.ts
36972
- function findRelatedStories(failedAC, prd) {
36973
- const relatedStoryIds = [];
36974
- for (const story of prd.userStories) {
36975
- for (const ac of story.acceptanceCriteria) {
36976
- if (ac.includes(failedAC)) {
36977
- relatedStoryIds.push(story.id);
36978
- break;
36979
- }
36980
- }
36981
- }
36982
- if (relatedStoryIds.length > 0) {
36983
- return relatedStoryIds;
36984
- }
36985
- const passedStories = prd.userStories.filter((s) => s.status === "passed").map((s) => s.id);
36986
- return passedStories.slice(0, 5);
36987
- }
36988
- function groupACsByRelatedStories(failedACs, prd) {
36989
- const groups = new Map;
36990
- for (const ac of failedACs) {
36991
- const related = findRelatedStories(ac, prd);
36992
- const key = [...related].sort().join(",");
36993
- if (!groups.has(key)) {
36994
- groups.set(key, { acs: [], relatedStories: related });
36995
- }
36996
- groups.get(key)?.acs.push(ac);
36997
- }
36998
- const result = Array.from(groups.values());
36999
- while (result.length > MAX_FIX_STORIES) {
37000
- result.sort((a, b) => a.acs.length - b.acs.length);
37001
- const smallest = result.shift();
37002
- if (!smallest)
37003
- break;
37004
- result[0].acs.push(...smallest.acs);
37005
- for (const s of smallest.relatedStories) {
37006
- if (!result[0].relatedStories.includes(s)) {
37007
- result[0].relatedStories.push(s);
37008
- }
37009
- }
37010
- }
37011
- return result;
37012
- }
37013
- function buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath) {
37014
- const acList = batchedACs.map((ac) => `${ac}: ${acTextMap[ac] || "No description available"}`).join(`
37015
- `);
37016
- const relatedStoriesText = relatedStories.map((id) => {
37017
- const story = prd.userStories.find((s) => s.id === id);
37018
- if (!story)
37019
- return "";
37020
- return `${story.id}: ${story.title}
37021
- ${story.description}`;
37022
- }).filter(Boolean).join(`
37023
-
37024
- `);
37025
- const testFileSection = testFilePath ? `
37026
- ACCEPTANCE TEST FILE: ${testFilePath}
37027
- (Read this file first to understand what each test expects)
37028
- ` : "";
37029
- return `You are a debugging expert. Feature acceptance tests have failed.${testFileSection}
37030
- FAILED ACCEPTANCE CRITERIA (${batchedACs.length} total):
37031
- ${acList}
37032
-
37033
- TEST FAILURE OUTPUT:
37034
- ${testOutput.slice(0, 2000)}
37035
-
37036
- RELATED STORIES (implemented this functionality):
37037
- ${relatedStoriesText}
37038
-
37039
- Your task: Generate a fix description that will make these acceptance tests pass.
37040
-
37041
- Requirements:
37042
- 1. Read the acceptance test file first to understand what each failing test expects
37043
- 2. Identify the root cause based on the test failure output
37044
- 3. Find and fix the relevant implementation code (do NOT modify the test file)
37045
- 4. Write a clear, actionable fix description (2-4 sentences)
37046
- 5. Reference the relevant story IDs if needed
37047
-
37048
- Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
37049
- }
37050
- async function generateFixStories(adapter, options) {
37051
- const { failedACs, testOutput, prd, specContent, modelDef, testFilePath } = options;
37052
- const logger = getLogger();
37053
- const acTextMap = parseACTextFromSpec(specContent);
37054
- const groups = groupACsByRelatedStories(failedACs, prd);
37055
- const fixStories = [];
37056
- for (let i = 0;i < groups.length; i++) {
37057
- const { acs: batchedACs, relatedStories } = groups[i];
37058
- if (relatedStories.length === 0) {
37059
- logger.warn("acceptance", "[WARN] No related stories found for AC group \u2014 skipping", { batchedACs });
37060
- continue;
37061
- }
37062
- logger.info("acceptance", "Generating fix for AC group", { batchedACs });
37063
- const prompt = buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath);
37064
- const relatedStory = prd.userStories.find((s) => relatedStories.includes(s.id) && s.workdir);
37065
- const workdir = relatedStory?.workdir;
37066
- try {
37067
- const fixResult = await adapter.complete(prompt, {
37068
- model: modelDef.model,
37069
- config: options.config,
37070
- featureName: options.prd.feature,
37071
- workdir: options.workdir,
37072
- sessionRole: "fix-gen",
37073
- timeoutMs: options.timeoutMs ?? options.config?.acceptance?.timeoutMs ?? 1800000
37074
- });
37075
- fixStories.push({
37076
- id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
37077
- title: `Fix: ${batchedACs.join(", ")} \u2014 ${(acTextMap[batchedACs[0]] || "").slice(0, 40)}`,
37078
- failedAC: batchedACs[0],
37079
- batchedACs,
37080
- testOutput,
37081
- relatedStories,
37082
- description: typeof fixResult === "string" ? fixResult : fixResult.output,
37083
- testFilePath,
37084
- workdir
37085
- });
37086
- logger.info("acceptance", "[OK] Generated fix story", { storyId: fixStories[fixStories.length - 1].id });
37087
- } catch (error48) {
37088
- logger.warn("acceptance", "[WARN] Error generating fix", {
37089
- batchedACs,
37090
- error: error48.message
37091
- });
37092
- fixStories.push({
37093
- id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
37094
- title: `Fix: ${batchedACs.join(", ")}`,
37095
- failedAC: batchedACs[0],
37096
- batchedACs,
37097
- testOutput,
37098
- relatedStories,
37099
- description: `Fix the implementation to make ${batchedACs.join(", ")} pass. Related stories: ${relatedStories.join(", ")}.`,
37100
- testFilePath,
37101
- workdir
37102
- });
37103
- }
37104
- }
37105
- return fixStories;
37106
- }
37107
- function parseACTextFromSpec(specContent) {
37108
- const map2 = {};
37109
- const lines = specContent.split(`
37110
- `);
37111
- for (const line of lines) {
37112
- const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
37113
- if (acMatch) {
37114
- const id = acMatch[1].toUpperCase();
37115
- const text = acMatch[2].trim();
37116
- map2[id] = text;
37117
- }
37118
- }
37119
- return map2;
37120
- }
37121
- function convertFixStoryToUserStory(fixStory) {
37122
- const batchedACs = fixStory.batchedACs ?? [fixStory.failedAC];
37123
- const acList = batchedACs.join(", ");
37124
- const truncatedOutput = fixStory.testOutput.slice(0, 1000);
37125
- const testFilePath = fixStory.testFilePath ?? resolveAcceptanceTestFile();
37126
- const enrichedDescription = [
37127
- fixStory.description,
37128
- "",
37129
- `ACCEPTANCE TEST FILE: ${testFilePath}`,
37130
- `FAILED ACCEPTANCE CRITERIA: ${acList}`,
37131
- "",
37132
- "TEST FAILURE OUTPUT:",
37133
- truncatedOutput,
37134
- "",
37135
- "Instructions: Read the acceptance test file first to understand what each failing test expects.",
37136
- "Then find the relevant source code and fix the implementation.",
37137
- "Do NOT modify the test file."
37138
- ].join(`
37139
- `);
37140
- return {
37141
- id: fixStory.id,
37142
- title: fixStory.title,
37143
- description: enrichedDescription,
37144
- acceptanceCriteria: batchedACs.map((ac) => `Fix ${ac}`),
37145
- tags: ["fix", "acceptance-failure"],
37146
- dependencies: fixStory.relatedStories,
37147
- status: "pending",
37148
- passes: false,
37149
- escalations: [],
37150
- attempts: 0,
37151
- contextFiles: [],
37152
- workdir: fixStory.workdir
37153
- };
37154
- }
37155
- var MAX_FIX_STORIES = 8;
37156
- var init_fix_generator = __esm(() => {
37157
- init_logger2();
37158
- init_test_path();
37159
- });
37160
-
37161
36972
  // src/acceptance/content-loader.ts
37162
36973
  async function loadAcceptanceTestContent(pathsOrFallback) {
37163
36974
  if (!pathsOrFallback)
@@ -37179,13 +36990,6 @@ async function loadAcceptanceTestContent(pathsOrFallback) {
37179
36990
  return [];
37180
36991
  }
37181
36992
 
37182
- // src/acceptance/index.ts
37183
- var init_acceptance4 = __esm(() => {
37184
- init_refinement();
37185
- init_generator();
37186
- init_fix_generator();
37187
- });
37188
-
37189
36993
  // src/acceptance/fix-diagnosis.ts
37190
36994
  function parseImportStatements(content) {
37191
36995
  const importRegex = /import\s+(?:{[^}]+}|[^;]+)\s+from\s+["']([^"']+)["']/g;
@@ -37235,6 +37039,13 @@ ${f.content}
37235
37039
  SEMANTIC VERDICTS:
37236
37040
  ${lines.join(`
37237
37041
  `)}
37042
+ `;
37043
+ }
37044
+ let previousFailureSection = "";
37045
+ if (options.previousFailure && options.previousFailure.length > 0) {
37046
+ previousFailureSection = `
37047
+ PREVIOUS FIX ATTEMPTS:
37048
+ ${options.previousFailure}
37238
37049
  `;
37239
37050
  }
37240
37051
  return `You are a debugging expert. An acceptance test has failed.
@@ -37251,7 +37062,7 @@ ${options.testFileContent}
37251
37062
 
37252
37063
  SOURCE FILES (auto-detected from imports, up to ${MAX_FILE_LINES} lines each):
37253
37064
  ${sourceFilesSection}
37254
- ${verdictSection}
37065
+ ${verdictSection}${previousFailureSection}
37255
37066
  Respond with ONLY a JSON object in this exact format (no markdown, no extra text):
37256
37067
  {
37257
37068
  "verdict": "source_bug" | "test_bug" | "both",
@@ -37277,7 +37088,8 @@ async function diagnoseAcceptanceFailure(agent, options) {
37277
37088
  testOutput,
37278
37089
  testFileContent,
37279
37090
  sourceFiles: validSourceFiles,
37280
- semanticVerdicts: options.semanticVerdicts
37091
+ semanticVerdicts: options.semanticVerdicts,
37092
+ previousFailure: options.previousFailure
37281
37093
  });
37282
37094
  try {
37283
37095
  const timeoutSeconds = (config2.acceptance?.timeoutMs ?? 120000) / 1000;
@@ -37362,7 +37174,7 @@ async function executeSourceFix(agent, options) {
37362
37174
  if (!agent) {
37363
37175
  throw new Error("[fix-executor] agent is required");
37364
37176
  }
37365
- const { testOutput, testFileContent, diagnosis, config: config2, workdir, featureName, storyId, acceptanceTestPath } = options;
37177
+ const { config: config2, workdir, featureName, storyId } = options;
37366
37178
  const modelDef = resolveModelForAgent(config2.models, config2.autoMode.defaultAgent, config2.acceptance.fix.fixModel, config2.autoMode.defaultAgent);
37367
37179
  const sessionName = buildSessionName(workdir, featureName, storyId, "source-fix");
37368
37180
  const prompt = buildSourceFixPrompt(options);
@@ -37386,23 +37198,77 @@ async function executeSourceFix(agent, options) {
37386
37198
  cost: result.estimatedCost
37387
37199
  };
37388
37200
  }
37201
+ function buildTestFixPrompt(options) {
37202
+ const { testOutput, diagnosis, acceptanceTestPath, testFileContent, failedACs, previousFailure } = options;
37203
+ let prompt = `ACCEPTANCE TEST BUG \u2014 surgical fix required.
37204
+
37205
+ `;
37206
+ prompt += `FAILING ACS: ${failedACs.join(", ")}
37207
+
37208
+ `;
37209
+ prompt += `TEST OUTPUT:
37210
+ ${testOutput}
37211
+
37212
+ `;
37213
+ if (diagnosis.reasoning) {
37214
+ prompt += `DIAGNOSIS:
37215
+ ${diagnosis.reasoning}
37216
+
37217
+ `;
37218
+ }
37219
+ if (previousFailure && previousFailure.length > 0) {
37220
+ prompt += `PREVIOUS FAILED ATTEMPTS:
37221
+ ${previousFailure}
37222
+
37223
+ `;
37224
+ }
37225
+ prompt += `ACCEPTANCE TEST FILE: ${acceptanceTestPath}
37226
+
37227
+ `;
37228
+ prompt += `\`\`\`typescript
37229
+ ${testFileContent}
37230
+ \`\`\`
37231
+
37232
+ `;
37233
+ prompt += "Fix ONLY the failing test assertions for the ACs listed above. ";
37234
+ prompt += "Do NOT modify passing tests. Do NOT modify source code. ";
37235
+ prompt += "Edit the test file in place.";
37236
+ return prompt;
37237
+ }
37238
+ async function executeTestFix(agent, options) {
37239
+ if (!agent) {
37240
+ throw new Error("[fix-executor] agent is required");
37241
+ }
37242
+ const { config: config2, workdir, featureName, storyId } = options;
37243
+ const modelDef = resolveModelForAgent(config2.models, config2.autoMode.defaultAgent, config2.acceptance.fix.fixModel, config2.autoMode.defaultAgent);
37244
+ const sessionName = buildSessionName(workdir, featureName, storyId, "test-fix");
37245
+ const prompt = buildTestFixPrompt(options);
37246
+ const timeoutSeconds = config2.execution?.sessionTimeoutSeconds ?? 3600;
37247
+ const runOptions = {
37248
+ prompt,
37249
+ workdir,
37250
+ modelTier: undefined,
37251
+ modelDef,
37252
+ timeoutSeconds,
37253
+ sessionRole: "test-fix",
37254
+ acpSessionName: sessionName,
37255
+ featureName,
37256
+ storyId,
37257
+ config: config2,
37258
+ pipelineStage: "acceptance"
37259
+ };
37260
+ const result = await agent.run(runOptions);
37261
+ return {
37262
+ success: result.success,
37263
+ cost: result.estimatedCost
37264
+ };
37265
+ }
37389
37266
  var init_fix_executor = __esm(() => {
37390
37267
  init_adapter();
37391
37268
  });
37392
37269
 
37393
- // src/execution/lifecycle/acceptance-loop.ts
37394
- var exports_acceptance_loop = {};
37395
- __export(exports_acceptance_loop, {
37396
- runFixRouting: () => runFixRouting,
37397
- runAcceptanceLoop: () => runAcceptanceLoop,
37398
- regenerateAcceptanceTest: () => regenerateAcceptanceTest,
37399
- loadAcceptanceTestContent: () => loadAcceptanceTestContent2,
37400
- isTestLevelFailure: () => isTestLevelFailure,
37401
- isStubTestFile: () => isStubTestFile,
37402
- _regenerateDeps: () => _regenerateDeps,
37403
- _acceptanceLoopDeps: () => _acceptanceLoopDeps
37404
- });
37405
- import path15, { join as join43 } from "path";
37270
+ // src/execution/lifecycle/acceptance-helpers.ts
37271
+ import path15 from "path";
37406
37272
  function isStubTestFile(content) {
37407
37273
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
37408
37274
  }
@@ -37449,73 +37315,7 @@ async function loadAcceptanceTestContent2(featureDir, testPaths, configuredTestP
37449
37315
  function buildResult(success2, prd, totalCost, iterations, storiesCompleted, prdDirty, failedACs, retries) {
37450
37316
  return { success: success2, prd, totalCost, iterations, storiesCompleted, prdDirty, failedACs, retries };
37451
37317
  }
37452
- async function generateAndAddFixStories(ctx, failures, prd) {
37453
- const logger = getSafeLogger();
37454
- const agent = (ctx.agentGetFn ?? _acceptanceLoopDeps.getAgent)(ctx.config.autoMode.defaultAgent);
37455
- if (!agent) {
37456
- logger?.error("acceptance", "Agent not found, cannot generate fix stories");
37457
- return null;
37458
- }
37459
- const modelDef = resolveModelForAgent(ctx.config.models, ctx.config.autoMode.defaultAgent, ctx.config.analyze.model, ctx.config.autoMode.defaultAgent);
37460
- const testFilePath = ctx.featureDir ? resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language) : undefined;
37461
- const fixStories = await generateFixStories(agent, {
37462
- failedACs: failures.failedACs,
37463
- testOutput: failures.testOutput,
37464
- prd,
37465
- specContent: await loadSpecContent(ctx.featureDir),
37466
- workdir: ctx.workdir,
37467
- modelDef,
37468
- config: ctx.config,
37469
- testFilePath,
37470
- timeoutMs: ctx.config.acceptance?.timeoutMs
37471
- });
37472
- if (fixStories.length === 0) {
37473
- logger?.error("acceptance", "Failed to generate fix stories");
37474
- return null;
37475
- }
37476
- logger?.info("acceptance", `Generated ${fixStories.length} fix stories`);
37477
- for (const fixStory of fixStories) {
37478
- const userStory = convertFixStoryToUserStory(fixStory);
37479
- prd.userStories.push(userStory);
37480
- logger?.debug("acceptance", `Fix story added: ${userStory.id}: ${userStory.title}`);
37481
- }
37482
- return fixStories;
37483
- }
37484
- async function executeFixStory(ctx, story, prd, iterations) {
37485
- const logger = getSafeLogger();
37486
- const routing = await resolveRouting(story, ctx.config, ctx.pluginRegistry);
37487
- logger?.info("acceptance", `Starting fix story: ${story.id}`, { storyId: story.id, storyTitle: story.title });
37488
- await fireHook(ctx.hooks, "on-story-start", hookCtx(ctx.feature, {
37489
- storyId: story.id,
37490
- model: routing.modelTier,
37491
- agent: ctx.config.autoMode.defaultAgent,
37492
- iteration: iterations
37493
- }), ctx.workdir);
37494
- const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join43(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
37495
- const fixContext = {
37496
- config: fixEffectiveConfig,
37497
- rootConfig: ctx.config,
37498
- prd,
37499
- story,
37500
- stories: [story],
37501
- routing,
37502
- projectDir: ctx.workdir,
37503
- workdir: story.workdir ? join43(ctx.workdir, story.workdir) : ctx.workdir,
37504
- featureDir: ctx.featureDir,
37505
- hooks: ctx.hooks,
37506
- plugins: ctx.pluginRegistry,
37507
- storyStartTime: new Date().toISOString(),
37508
- agentGetFn: ctx.agentGetFn
37509
- };
37510
- const result = await runPipeline(defaultPipeline, fixContext, ctx.eventEmitter);
37511
- logger?.info("acceptance", `Fix story ${story.id} ${result.success ? "passed" : "failed"}`);
37512
- return {
37513
- success: result.success,
37514
- cost: result.context.agentResult?.estimatedCost || 0,
37515
- metrics: result.context.storyMetrics
37516
- };
37517
- }
37518
- async function regenerateAcceptanceTest(testPath, acceptanceContext) {
37318
+ async function regenerateAcceptanceTest(testPath, acceptanceContext, previousFailure) {
37519
37319
  const logger = getSafeLogger();
37520
37320
  const bakPath = `${testPath}.bak`;
37521
37321
  const content = await Bun.file(testPath).text();
@@ -37559,7 +37359,8 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
37559
37359
  }
37560
37360
  const contextForSetup = {
37561
37361
  ...acceptanceContext,
37562
- ...implementationContext ? { implementationContext } : {}
37362
+ ...implementationContext ? { implementationContext } : {},
37363
+ ...previousFailure ? { previousFailure } : {}
37563
37364
  };
37564
37365
  await _regenerateDeps.acceptanceSetupExecute(contextForSetup);
37565
37366
  if (!await Bun.file(testPath).exists()) {
@@ -37569,300 +37370,179 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
37569
37370
  logger?.info("acceptance", "Acceptance test regenerated successfully");
37570
37371
  return true;
37571
37372
  }
37572
- async function runFixRouting(options) {
37373
+ var _regenerateDeps;
37374
+ var init_acceptance_helpers = __esm(() => {
37375
+ init_logger2();
37376
+ _regenerateDeps = {
37377
+ spawnGitDiff: async (workdir, gitRef) => {
37378
+ const proc = Bun.spawn(["git", "diff", "--name-only", gitRef], {
37379
+ cwd: workdir,
37380
+ stdout: "pipe",
37381
+ stderr: "pipe"
37382
+ });
37383
+ const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
37384
+ return stdout.trim();
37385
+ },
37386
+ readFile: async (filePath) => Bun.file(filePath).text(),
37387
+ acceptanceSetupExecute: async (ctx) => {
37388
+ const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
37389
+ await acceptanceSetupStage2.execute(ctx);
37390
+ }
37391
+ };
37392
+ });
37393
+
37394
+ // src/execution/lifecycle/acceptance-fix.ts
37395
+ async function resolveAcceptanceDiagnosis(opts) {
37573
37396
  const logger = getSafeLogger();
37574
- const { ctx, failures, acceptanceContext } = options;
37575
- const prd = options.prd ?? ctx.prd;
37576
- const semanticVerdicts = options.semanticVerdicts;
37577
- if (semanticVerdicts && semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
37578
- const verdictCount = semanticVerdicts.length;
37579
- const storyId2 = acceptanceContext?.story?.id ?? prd?.userStories?.[0]?.id ?? "unknown";
37580
- logger?.info("acceptance", "All semantic verdicts passed \u2014 routing to test regeneration", {
37581
- storyId: storyId2,
37582
- verdictCount
37397
+ const { agent, failures, totalACs, strategy, semanticVerdicts, diagnosisOpts, previousFailure } = opts;
37398
+ const storyId = diagnosisOpts.storyId;
37399
+ if (strategy === "implement-only") {
37400
+ logger?.info("acceptance.diagnosis", "Fast path: implement-only strategy \u2192 source_bug", { storyId });
37401
+ return {
37402
+ verdict: "source_bug",
37403
+ reasoning: "implement-only strategy \u2014 skipping diagnosis",
37404
+ confidence: 1
37405
+ };
37406
+ }
37407
+ if (semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
37408
+ logger?.info("acceptance.diagnosis", "Fast path: all semantic verdicts passed \u2192 test_bug", {
37409
+ storyId,
37410
+ verdictCount: semanticVerdicts.length
37583
37411
  });
37584
- if (!ctx.featureDir || !acceptanceContext) {
37585
- logger?.warn("acceptance", "Cannot regenerate test \u2014 featureDir or acceptanceContext missing", { storyId: storyId2 });
37586
- return {
37587
- fixed: false,
37588
- cost: 0,
37589
- prdDirty: false,
37590
- verdict: "test_bug",
37591
- confidence: 1,
37592
- reasoning: "Semantic review confirmed all ACs are implemented \u2014 acceptance test failure is a test generation issue"
37593
- };
37594
- }
37595
- const regenOutcome = await _acceptanceLoopDeps.executeTestRegen(ctx, acceptanceContext);
37596
- logger?.info("acceptance.test-regen", "Test regeneration completed", { storyId: storyId2, outcome: regenOutcome });
37597
- if (regenOutcome === "passed") {
37598
- return { fixed: true, cost: 0, prdDirty: true };
37599
- }
37600
37412
  return {
37601
- fixed: false,
37602
- cost: 0,
37603
- prdDirty: regenOutcome !== "no_test_file",
37604
37413
  verdict: "test_bug",
37605
- confidence: 1,
37606
- reasoning: "Semantic review confirmed all ACs are implemented \u2014 acceptance test failure is a test generation issue"
37414
+ reasoning: `Semantic review confirmed all ${semanticVerdicts.length} ACs are implemented \u2014 failure is a test generation issue`,
37415
+ confidence: 1
37607
37416
  };
37608
37417
  }
37418
+ if (isTestLevelFailure(failures.failedACs, totalACs)) {
37419
+ logger?.info("acceptance.diagnosis", "Fast path: test-level failure heuristic \u2192 test_bug", {
37420
+ storyId,
37421
+ failedCount: failures.failedACs.length,
37422
+ totalACs
37423
+ });
37424
+ return {
37425
+ verdict: "test_bug",
37426
+ reasoning: `Test-level failure: ${failures.failedACs.length}/${totalACs} ACs failed (>80% threshold or AC-ERROR sentinel)`,
37427
+ confidence: 0.9
37428
+ };
37429
+ }
37430
+ return await diagnoseAcceptanceFailure(agent, {
37431
+ ...diagnosisOpts,
37432
+ semanticVerdicts,
37433
+ previousFailure
37434
+ });
37435
+ }
37436
+ async function applyFix(opts) {
37437
+ const logger = getSafeLogger();
37438
+ const { ctx, failures, diagnosis, previousFailure } = opts;
37439
+ const storyId = ctx.prd.userStories[0]?.id ?? "unknown";
37609
37440
  const agentName = ctx.config.autoMode.defaultAgent;
37610
- const agent = (ctx.agentGetFn ?? _acceptanceLoopDeps.getAgent)(agentName);
37611
- const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
37612
- const fixMaxRetries = ctx.config.acceptance.fix?.maxRetries ?? 2;
37441
+ const agent = (ctx.agentGetFn ?? _applyFixDeps.getAgent)(agentName);
37442
+ if (!agent) {
37443
+ logger?.error("acceptance.applyFix", "Agent not found", { storyId, agentName });
37444
+ return { cost: 0 };
37445
+ }
37613
37446
  const testPaths = ctx.acceptanceTestPaths;
37614
- let testEntries;
37447
+ let testFileContent = "";
37448
+ let acceptanceTestPath = "";
37615
37449
  if (testPaths && testPaths.length > 0) {
37616
37450
  const pathStrings = testPaths.map((p) => typeof p === "string" ? p : p.testPath);
37617
37451
  const moduleEntries = await loadAcceptanceTestContent(pathStrings);
37618
- testEntries = moduleEntries.map((e) => ({ content: e.content, path: e.testPath }));
37619
- } else {
37620
- const fallbackPath = ctx.featureDir ? resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language) : undefined;
37621
- const moduleEntries = await loadAcceptanceTestContent(fallbackPath);
37622
- testEntries = moduleEntries.map((e) => ({ content: e.content, path: e.testPath }));
37623
- }
37624
- const primaryEntry = testEntries[0] ?? { content: "", path: "" };
37625
- const testFileContent = primaryEntry.content;
37626
- const acceptanceTestPath = primaryEntry.path;
37627
- const firstStory = prd?.userStories?.[0];
37628
- const storyId = firstStory?.id ?? "unknown";
37629
- if (failures.failedACs.length === 0) {
37630
- return { fixed: true, cost: 0, prdDirty: false };
37631
- }
37632
- if (strategy === "implement-only") {
37633
- logger?.info("acceptance", "Strategy is implement-only \u2014 executing source fix directly");
37634
- if (!agent) {
37635
- logger?.error("acceptance", "Agent not found for fix routing");
37636
- return { fixed: false, cost: 0, prdDirty: false };
37637
- }
37638
- let fixAttempts = 0;
37639
- while (fixAttempts < fixMaxRetries) {
37640
- fixAttempts++;
37641
- logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
37642
- const defaultDiagnosis = {
37643
- verdict: "source_bug",
37644
- reasoning: "implement-only strategy \u2014 skipping diagnosis",
37645
- confidence: 1
37646
- };
37647
- const fixResult = await executeSourceFix(agent, {
37648
- testOutput: failures.testOutput,
37649
- testFileContent,
37650
- diagnosis: defaultDiagnosis,
37651
- config: ctx.config,
37652
- workdir: ctx.workdir,
37653
- featureName: ctx.feature,
37654
- storyId,
37655
- acceptanceTestPath
37656
- });
37657
- logger?.info("acceptance.source-fix", "Source fix completed", {
37658
- success: fixResult.success,
37659
- cost: fixResult.cost,
37660
- attempt: fixAttempts
37661
- });
37662
- if (fixResult.success) {
37663
- return { fixed: true, cost: fixResult.cost, prdDirty: false };
37664
- }
37665
- logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
37666
- attempt: fixAttempts,
37667
- maxRetries: fixMaxRetries,
37668
- cost: fixResult.cost,
37669
- willRetry: fixAttempts < fixMaxRetries
37670
- });
37671
- if (fixAttempts >= fixMaxRetries) {
37672
- logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
37673
- break;
37674
- }
37452
+ if (moduleEntries.length > 0) {
37453
+ testFileContent = moduleEntries[0].content;
37454
+ acceptanceTestPath = moduleEntries[0].testPath;
37675
37455
  }
37676
- return { fixed: false, cost: 0, prdDirty: false };
37677
- }
37678
- logger?.info("acceptance", "Strategy is diagnose-first \u2014 running diagnosis");
37679
- const diagnosis = await diagnoseAcceptanceFailure(agent, {
37680
- testOutput: failures.testOutput,
37681
- testFileContent,
37682
- config: ctx.config,
37683
- workdir: ctx.workdir,
37684
- featureName: ctx.feature,
37685
- storyId,
37686
- semanticVerdicts: options.semanticVerdicts
37687
- });
37688
- const diagnosisCost = diagnosis.cost ?? 0;
37689
- logger?.info("acceptance.diagnosis", "Diagnosis complete", {
37690
- verdict: diagnosis.verdict,
37691
- confidence: diagnosis.confidence,
37692
- reasoning: diagnosis.reasoning
37693
- });
37694
- if (diagnosis.verdict === "source_bug") {
37695
- logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
37696
- if (!agent) {
37697
- logger?.error("acceptance", "Agent not found for source fix execution");
37698
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37699
- }
37700
- let fixAttempts = 0;
37701
- while (fixAttempts < fixMaxRetries) {
37702
- fixAttempts++;
37703
- logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
37704
- const fixResult = await executeSourceFix(agent, {
37705
- testOutput: failures.testOutput,
37706
- testFileContent,
37707
- diagnosis,
37708
- config: ctx.config,
37709
- workdir: ctx.workdir,
37710
- featureName: ctx.feature,
37711
- storyId,
37712
- acceptanceTestPath
37713
- });
37714
- logger?.info("acceptance.source-fix", "Source fix completed", {
37715
- success: fixResult.success,
37716
- cost: fixResult.cost,
37717
- attempt: fixAttempts
37718
- });
37719
- if (fixResult.success) {
37720
- return { fixed: true, cost: fixResult.cost + diagnosisCost, prdDirty: false };
37721
- }
37722
- logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
37723
- attempt: fixAttempts,
37724
- maxRetries: fixMaxRetries,
37725
- cost: fixResult.cost,
37726
- willRetry: fixAttempts < fixMaxRetries
37727
- });
37728
- if (fixAttempts >= fixMaxRetries) {
37729
- logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
37730
- break;
37731
- }
37456
+ } else if (ctx.featureDir) {
37457
+ const fallbackPath = resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language);
37458
+ const moduleEntries = await loadAcceptanceTestContent(fallbackPath);
37459
+ if (moduleEntries.length > 0) {
37460
+ testFileContent = moduleEntries[0].content;
37461
+ acceptanceTestPath = moduleEntries[0].testPath;
37732
37462
  }
37733
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37734
37463
  }
37735
- if (diagnosis.verdict === "test_bug") {
37736
- logger?.info("acceptance", "Diagnosis: test_bug \u2014 regenerating acceptance test");
37737
- if (!ctx.featureDir) {
37738
- logger?.error("acceptance", "Cannot regenerate test without featureDir");
37739
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37740
- }
37741
- const testPath = await findExistingAcceptanceTestPath({
37742
- acceptanceTestPaths: ctx.acceptanceTestPaths,
37743
- featureDir: ctx.featureDir,
37744
- testPathConfig: ctx.config.acceptance.testPath,
37745
- language: ctx.config.project?.language
37464
+ let totalCost = 0;
37465
+ if (diagnosis.verdict === "source_bug" || diagnosis.verdict === "both") {
37466
+ logger?.info("acceptance.applyFix", "Applying source fix", { storyId, verdict: diagnosis.verdict });
37467
+ const sourceResult = await _applyFixDeps.executeSourceFix(agent, {
37468
+ testOutput: failures.testOutput,
37469
+ testFileContent,
37470
+ diagnosis,
37471
+ config: ctx.config,
37472
+ workdir: ctx.workdir,
37473
+ featureName: ctx.feature,
37474
+ storyId,
37475
+ acceptanceTestPath
37746
37476
  });
37747
- if (!testPath) {
37748
- logger?.error("acceptance", "Acceptance test file not found for regeneration", {
37749
- candidates: resolveAcceptanceTestCandidates({
37750
- acceptanceTestPaths: ctx.acceptanceTestPaths,
37751
- featureDir: ctx.featureDir,
37752
- testPathConfig: ctx.config.acceptance.testPath,
37753
- language: ctx.config.project?.language
37754
- })
37755
- });
37756
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37757
- }
37758
- const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
37759
- logger?.info("acceptance.test-regen", "Test regeneration completed", {
37760
- outcome: regenerated ? "success" : "failure"
37477
+ totalCost += sourceResult.cost;
37478
+ logger?.info("acceptance.source-fix", "Source fix completed", {
37479
+ storyId,
37480
+ success: sourceResult.success,
37481
+ cost: sourceResult.cost
37761
37482
  });
37762
- if (!regenerated) {
37763
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37764
- }
37765
- const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
37766
- const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
37767
- if (acceptanceResult.action === "continue") {
37768
- logger?.info("acceptance", "Acceptance passed after test regeneration");
37769
- return { fixed: true, cost: diagnosisCost, prdDirty: true };
37770
- }
37771
- logger?.warn("acceptance", "Acceptance still failing after test regeneration");
37772
- return { fixed: false, cost: diagnosisCost, prdDirty: true };
37773
37483
  }
37774
- if (diagnosis.verdict === "both") {
37775
- logger?.info("acceptance", "Diagnosis: both \u2014 executing source fix then regenerating test if needed");
37776
- if (!agent) {
37777
- logger?.error("acceptance", "Agent not found for source fix execution");
37778
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37779
- }
37780
- let sourceFixSuccess = false;
37781
- let sourceFixCost = 0;
37782
- let fixAttempts = 0;
37783
- while (fixAttempts < fixMaxRetries && !sourceFixSuccess) {
37784
- fixAttempts++;
37785
- logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
37786
- const fixResult = await executeSourceFix(agent, {
37787
- testOutput: failures.testOutput,
37788
- testFileContent,
37789
- diagnosis,
37790
- config: ctx.config,
37791
- workdir: ctx.workdir,
37792
- featureName: ctx.feature,
37793
- storyId,
37794
- acceptanceTestPath
37795
- });
37796
- logger?.info("acceptance.source-fix", "Source fix completed", {
37797
- success: fixResult.success,
37798
- cost: fixResult.cost,
37799
- attempt: fixAttempts
37800
- });
37801
- sourceFixSuccess = fixResult.success;
37802
- sourceFixCost += fixResult.cost;
37803
- if (fixResult.success) {
37804
- break;
37805
- }
37806
- logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
37807
- attempt: fixAttempts,
37808
- maxRetries: fixMaxRetries,
37809
- cost: fixResult.cost,
37810
- willRetry: fixAttempts < fixMaxRetries
37811
- });
37812
- if (fixAttempts >= fixMaxRetries) {
37813
- logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
37814
- break;
37815
- }
37816
- }
37817
- if (!sourceFixSuccess) {
37818
- return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
37819
- }
37820
- logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
37821
- const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
37822
- const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
37823
- if (acceptanceResult.action === "continue") {
37824
- logger?.info("acceptance", "Acceptance passed after source fix");
37825
- return { fixed: true, cost: sourceFixCost + diagnosisCost, prdDirty: false };
37826
- }
37827
- logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
37828
- if (!ctx.featureDir) {
37829
- logger?.error("acceptance", "Cannot regenerate test without featureDir");
37830
- return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
37831
- }
37832
- const testPath = await findExistingAcceptanceTestPath({
37833
- acceptanceTestPaths: ctx.acceptanceTestPaths,
37834
- featureDir: ctx.featureDir,
37835
- testPathConfig: ctx.config.acceptance.testPath,
37836
- language: ctx.config.project?.language
37484
+ if (diagnosis.verdict === "test_bug" || diagnosis.verdict === "both") {
37485
+ logger?.info("acceptance.applyFix", "Applying test fix", { storyId, verdict: diagnosis.verdict });
37486
+ const testResult = await _applyFixDeps.executeTestFix(agent, {
37487
+ testOutput: failures.testOutput,
37488
+ testFileContent,
37489
+ failedACs: failures.failedACs,
37490
+ diagnosis,
37491
+ config: ctx.config,
37492
+ workdir: ctx.workdir,
37493
+ featureName: ctx.feature,
37494
+ storyId,
37495
+ acceptanceTestPath,
37496
+ previousFailure
37837
37497
  });
37838
- if (!testPath) {
37839
- logger?.error("acceptance", "Acceptance test file not found for regeneration", {
37840
- candidates: resolveAcceptanceTestCandidates({
37841
- acceptanceTestPaths: ctx.acceptanceTestPaths,
37842
- featureDir: ctx.featureDir,
37843
- testPathConfig: ctx.config.acceptance.testPath,
37844
- language: ctx.config.project?.language
37845
- })
37846
- });
37847
- return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
37848
- }
37849
- const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
37850
- logger?.info("acceptance.test-regen", "Test regeneration completed", {
37851
- outcome: regenerated ? "success" : "failure"
37498
+ totalCost += testResult.cost;
37499
+ logger?.info("acceptance.test-fix", "Test fix completed", {
37500
+ storyId,
37501
+ success: testResult.success,
37502
+ cost: testResult.cost
37852
37503
  });
37853
- return { fixed: regenerated, cost: sourceFixCost + diagnosisCost, prdDirty: regenerated };
37854
37504
  }
37855
- return { fixed: false, cost: diagnosisCost, prdDirty: false };
37505
+ return { cost: totalCost };
37856
37506
  }
37507
+ var _applyFixDeps;
37508
+ var init_acceptance_fix = __esm(() => {
37509
+ init_fix_diagnosis();
37510
+ init_fix_executor();
37511
+ init_test_path();
37512
+ init_registry();
37513
+ init_logger2();
37514
+ init_acceptance_helpers();
37515
+ _applyFixDeps = {
37516
+ getAgent,
37517
+ executeSourceFix,
37518
+ executeTestFix
37519
+ };
37520
+ });
37521
+
37522
+ // src/execution/lifecycle/acceptance-loop.ts
37523
+ var exports_acceptance_loop = {};
37524
+ __export(exports_acceptance_loop, {
37525
+ runAcceptanceLoop: () => runAcceptanceLoop,
37526
+ regenerateAcceptanceTest: () => regenerateAcceptanceTest,
37527
+ loadSpecContent: () => loadSpecContent,
37528
+ loadAcceptanceTestContent: () => loadAcceptanceTestContent2,
37529
+ isTestLevelFailure: () => isTestLevelFailure,
37530
+ isStubTestFile: () => isStubTestFile,
37531
+ buildResult: () => buildResult,
37532
+ _regenerateDeps: () => _regenerateDeps,
37533
+ _acceptanceLoopDeps: () => _acceptanceLoopDeps
37534
+ });
37857
37535
  async function runAcceptanceLoop(ctx) {
37858
37536
  const logger = getSafeLogger();
37859
37537
  const maxRetries = ctx.config.acceptance.maxRetries;
37860
37538
  let acceptanceRetries = 0;
37861
- let prd = ctx.prd;
37539
+ let stubRegenCount = 0;
37540
+ let previousFailure = "";
37541
+ const prd = ctx.prd;
37862
37542
  let totalCost = ctx.totalCost;
37863
- let iterations = ctx.iterations;
37864
- let storiesCompleted = ctx.storiesCompleted;
37865
- let prdDirty = false;
37543
+ const iterations = ctx.iterations;
37544
+ const storiesCompleted = ctx.storiesCompleted;
37545
+ const prdDirty = false;
37866
37546
  logger?.info("acceptance", "All stories complete, running acceptance validation");
37867
37547
  while (acceptanceRetries < maxRetries) {
37868
37548
  const firstStory = prd.userStories[0];
@@ -37899,18 +37579,16 @@ async function runAcceptanceLoop(ctx) {
37899
37579
  const failures = acceptanceContext.acceptanceFailures;
37900
37580
  if (!failures || failures.failedACs.length === 0) {
37901
37581
  logger?.error("acceptance", "Acceptance tests failed but no specific failures detected");
37902
- logger?.warn("acceptance", "Manual intervention required");
37903
37582
  await fireHook(ctx.hooks, "on-pause", hookCtx(ctx.feature, { reason: "Acceptance tests failed (no failures detected)", cost: totalCost }), ctx.workdir);
37904
37583
  return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
37905
37584
  }
37906
37585
  acceptanceRetries++;
37907
37586
  logger?.warn("acceptance", `Acceptance retry ${acceptanceRetries}/${maxRetries}`, {
37587
+ storyId: firstStory?.id,
37908
37588
  failedACs: failures.failedACs
37909
37589
  });
37910
37590
  if (acceptanceRetries >= maxRetries) {
37911
- logger?.error("acceptance", "Max acceptance retries reached");
37912
- logger?.warn("acceptance", "Manual intervention required");
37913
- logger?.debug("acceptance", 'Run: nax accept --override AC-N "reason" to skip specific ACs');
37591
+ logger?.error("acceptance", "Max acceptance retries reached", { storyId: firstStory?.id });
37914
37592
  await fireHook(ctx.hooks, "on-pause", hookCtx(ctx.feature, {
37915
37593
  reason: `Acceptance validation failed after ${maxRetries} retries: ${failures.failedACs.join(", ")}`,
37916
37594
  cost: totalCost
@@ -37924,141 +37602,87 @@ async function runAcceptanceLoop(ctx) {
37924
37602
  testPathConfig: ctx.config.acceptance.testPath,
37925
37603
  language: ctx.config.project?.language
37926
37604
  });
37927
- if (existingStubPath) {
37928
- const testContent = await Bun.file(existingStubPath).text();
37929
- if (isStubTestFile(testContent)) {
37930
- logger?.warn("acceptance", "Stub tests detected \u2014 re-generating acceptance tests", {
37931
- testPath: existingStubPath
37605
+ if (existingStubPath && isStubTestFile(await Bun.file(existingStubPath).text())) {
37606
+ if (stubRegenCount >= MAX_STUB_REGENS) {
37607
+ logger?.error("acceptance", "Acceptance test generator cannot produce real tests \u2014 giving up", {
37608
+ storyId: firstStory?.id,
37609
+ stubRegenCount
37932
37610
  });
37933
- const { unlink: unlink3 } = await import("fs/promises");
37934
- await unlink3(existingStubPath);
37935
- const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
37936
- await acceptanceSetupStage2.execute(acceptanceContext);
37937
- const newContent = await Bun.file(existingStubPath).text();
37938
- if (isStubTestFile(newContent)) {
37939
- logger?.error("acceptance", "Acceptance test generation failed after retry \u2014 manual implementation required");
37940
- return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
37941
- }
37942
- continue;
37943
- }
37944
- }
37945
- }
37946
- const totalACs = prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria).length;
37947
- if (ctx.featureDir && isTestLevelFailure(failures.failedACs, totalACs)) {
37948
- logger?.warn("acceptance", `Test-level failure detected (${failures.failedACs.length}/${totalACs} ACs failed) \u2014 regenerating acceptance test`);
37949
- const testPath = await findExistingAcceptanceTestPath({
37950
- acceptanceTestPaths: ctx.acceptanceTestPaths,
37951
- featureDir: ctx.featureDir,
37952
- testPathConfig: ctx.config.acceptance.testPath,
37953
- language: ctx.config.project?.language
37954
- });
37955
- if (testPath) {
37956
- const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
37957
- if (!regenerated) {
37958
37611
  return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
37959
37612
  }
37613
+ stubRegenCount++;
37614
+ logger?.warn("acceptance", "Stub test detected \u2014 full regen", {
37615
+ storyId: firstStory?.id,
37616
+ attempt: stubRegenCount,
37617
+ maxStubRegens: MAX_STUB_REGENS
37618
+ });
37619
+ await regenerateAcceptanceTest(existingStubPath, acceptanceContext);
37960
37620
  continue;
37961
37621
  }
37962
37622
  }
37963
- const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
37964
- if (strategy === "diagnose-first" || strategy === "implement-only") {
37965
- logger?.info("acceptance", `Running fix routing with strategy: ${strategy}`);
37966
- const semanticVerdicts = ctx.featureDir ? await _acceptanceLoopDeps.loadSemanticVerdicts(ctx.featureDir) : [];
37967
- const fixResult = await runFixRouting({
37968
- ctx,
37969
- failures,
37970
- prd,
37971
- acceptanceContext,
37972
- semanticVerdicts
37973
- });
37974
- totalCost += fixResult.cost;
37975
- if (fixResult.fixed) {
37976
- logger?.info("acceptance", "Fix succeeded \u2014 re-running acceptance tests...");
37977
- continue;
37978
- }
37979
- logger?.error("acceptance", "Fix routing failed to resolve acceptance failures");
37980
- return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
37981
- }
37982
- logger?.info("acceptance", "Generating fix stories...");
37983
- const fixStories = await generateAndAddFixStories(ctx, failures, prd);
37984
- if (!fixStories) {
37623
+ const semanticVerdicts = ctx.featureDir ? await _acceptanceLoopDeps.loadSemanticVerdicts(ctx.featureDir) : [];
37624
+ const totalACs = prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria).length;
37625
+ const agentName = ctx.config.autoMode.defaultAgent;
37626
+ const agent = (ctx.agentGetFn ?? _acceptanceLoopDeps.getAgent)(agentName);
37627
+ if (!agent) {
37628
+ logger?.error("acceptance", "Agent not found for diagnosis", { storyId: firstStory?.id, agentName });
37985
37629
  return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
37986
37630
  }
37987
- await savePRD(prd, ctx.prdPath);
37988
- prdDirty = true;
37989
- logger?.info("acceptance", "Running fix stories...");
37990
- for (const fixStory of fixStories) {
37991
- const userStory = prd.userStories.find((s) => s.id === fixStory.id);
37992
- if (!userStory || userStory.status !== "pending")
37993
- continue;
37994
- iterations++;
37995
- const result = await executeFixStory(ctx, userStory, prd, iterations);
37996
- prd = await loadPRD(ctx.prdPath);
37997
- if (result.success) {
37998
- storiesCompleted++;
37999
- totalCost += result.cost;
38000
- if (result.metrics)
38001
- ctx.allStoryMetrics.push(...result.metrics);
38002
- }
38003
- await savePRD(prd, ctx.prdPath);
38004
- prdDirty = true;
38005
- }
38006
- logger?.info("acceptance", "Re-running acceptance tests...");
37631
+ const testEntries = ctx.acceptanceTestPaths ? await loadAcceptanceTestContent(ctx.acceptanceTestPaths.map((p) => p.testPath)) : [];
37632
+ const testFileContent = testEntries[0]?.content ?? "";
37633
+ const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
37634
+ const diagnosis = await resolveAcceptanceDiagnosis({
37635
+ agent,
37636
+ failures,
37637
+ totalACs,
37638
+ strategy,
37639
+ semanticVerdicts,
37640
+ diagnosisOpts: {
37641
+ testOutput: failures.testOutput,
37642
+ testFileContent,
37643
+ config: ctx.config,
37644
+ workdir: ctx.workdir,
37645
+ featureName: ctx.feature,
37646
+ storyId: firstStory?.id
37647
+ },
37648
+ previousFailure
37649
+ });
37650
+ logger?.info("acceptance.diagnosis", "Diagnosis resolved", {
37651
+ storyId: firstStory?.id,
37652
+ verdict: diagnosis.verdict,
37653
+ confidence: diagnosis.confidence,
37654
+ attempt: acceptanceRetries
37655
+ });
37656
+ const fixResult = await applyFix({
37657
+ ctx,
37658
+ failures,
37659
+ diagnosis,
37660
+ previousFailure
37661
+ });
37662
+ totalCost += fixResult.cost;
37663
+ previousFailure += `
37664
+ ---
37665
+ Attempt ${acceptanceRetries}/${maxRetries}: verdict=${diagnosis.verdict}, confidence=${diagnosis.confidence}
37666
+ Reasoning: ${diagnosis.reasoning}
37667
+ Failed ACs: ${failures.failedACs.join(", ")}
37668
+ `;
38007
37669
  }
38008
37670
  return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
38009
37671
  }
38010
- var _acceptanceLoopDeps, _regenerateDeps;
37672
+ var _acceptanceLoopDeps, MAX_STUB_REGENS = 2;
38011
37673
  var init_acceptance_loop = __esm(() => {
38012
- init_acceptance4();
38013
- init_fix_diagnosis();
38014
- init_fix_executor();
38015
37674
  init_semantic_verdict();
38016
37675
  init_test_path();
38017
37676
  init_registry();
38018
- init_config();
38019
- init_loader();
38020
37677
  init_hooks();
38021
37678
  init_logger2();
38022
- init_runner();
38023
- init_stages();
38024
- init_prd();
38025
- init_routing();
38026
37679
  init_helpers();
37680
+ init_acceptance_fix();
37681
+ init_acceptance_helpers();
37682
+ init_acceptance_helpers();
38027
37683
  _acceptanceLoopDeps = {
38028
37684
  getAgent,
38029
- loadSemanticVerdicts,
38030
- executeTestRegen: async (ctx, acceptanceContext) => {
38031
- const testPath = await findExistingAcceptanceTestPath({
38032
- acceptanceTestPaths: ctx.acceptanceTestPaths,
38033
- featureDir: ctx.featureDir,
38034
- testPathConfig: ctx.config.acceptance.testPath,
38035
- language: ctx.config.project?.language
38036
- });
38037
- if (!testPath)
38038
- return "no_test_file";
38039
- const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
38040
- if (!regenerated)
38041
- return "failed";
38042
- const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
38043
- const result = await acceptanceStage2.execute(acceptanceContext);
38044
- return result.action === "continue" ? "passed" : "failed";
38045
- }
38046
- };
38047
- _regenerateDeps = {
38048
- spawnGitDiff: async (workdir, gitRef) => {
38049
- const proc = Bun.spawn(["git", "diff", "--name-only", gitRef], {
38050
- cwd: workdir,
38051
- stdout: "pipe",
38052
- stderr: "pipe"
38053
- });
38054
- const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
38055
- return stdout.trim();
38056
- },
38057
- readFile: async (filePath) => Bun.file(filePath).text(),
38058
- acceptanceSetupExecute: async (ctx) => {
38059
- const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
38060
- await acceptanceSetupStage2.execute(ctx);
38061
- }
37685
+ loadSemanticVerdicts
38062
37686
  };
38063
37687
  });
38064
37688
 
@@ -38542,12 +38166,12 @@ var init_headless_formatter = __esm(() => {
38542
38166
  // src/pipeline/subscribers/events-writer.ts
38543
38167
  import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
38544
38168
  import { homedir as homedir5 } from "os";
38545
- import { basename as basename6, join as join44 } from "path";
38169
+ import { basename as basename6, join as join43 } from "path";
38546
38170
  function wireEventsWriter(bus, feature, runId, workdir) {
38547
38171
  const logger = getSafeLogger();
38548
38172
  const project = basename6(workdir);
38549
- const eventsDir = join44(homedir5(), ".nax", "events", project);
38550
- const eventsFile = join44(eventsDir, "events.jsonl");
38173
+ const eventsDir = join43(homedir5(), ".nax", "events", project);
38174
+ const eventsFile = join43(eventsDir, "events.jsonl");
38551
38175
  let dirReady = false;
38552
38176
  const write = (line) => {
38553
38177
  return (async () => {
@@ -38728,12 +38352,12 @@ var init_interaction2 = __esm(() => {
38728
38352
  // src/pipeline/subscribers/registry.ts
38729
38353
  import { mkdir as mkdir4, writeFile } from "fs/promises";
38730
38354
  import { homedir as homedir6 } from "os";
38731
- import { basename as basename7, join as join45 } from "path";
38355
+ import { basename as basename7, join as join44 } from "path";
38732
38356
  function wireRegistry(bus, feature, runId, workdir) {
38733
38357
  const logger = getSafeLogger();
38734
38358
  const project = basename7(workdir);
38735
- const runDir = join45(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
38736
- const metaFile = join45(runDir, "meta.json");
38359
+ const runDir = join44(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
38360
+ const metaFile = join44(runDir, "meta.json");
38737
38361
  const unsub = bus.on("run:started", (_ev) => {
38738
38362
  return (async () => {
38739
38363
  try {
@@ -38743,8 +38367,8 @@ function wireRegistry(bus, feature, runId, workdir) {
38743
38367
  project,
38744
38368
  feature,
38745
38369
  workdir,
38746
- statusPath: join45(workdir, ".nax", "features", feature, "status.json"),
38747
- eventsDir: join45(workdir, ".nax", "features", feature, "runs"),
38370
+ statusPath: join44(workdir, ".nax", "features", feature, "status.json"),
38371
+ eventsDir: join44(workdir, ".nax", "features", feature, "runs"),
38748
38372
  registeredAt: new Date().toISOString()
38749
38373
  };
38750
38374
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -39401,7 +39025,7 @@ var init_pipeline_result_handler = __esm(() => {
39401
39025
  });
39402
39026
 
39403
39027
  // src/execution/iteration-runner.ts
39404
- import { join as join46 } from "path";
39028
+ import { join as join45 } from "path";
39405
39029
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
39406
39030
  const logger = getSafeLogger();
39407
39031
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -39436,7 +39060,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
39436
39060
  }
39437
39061
  }
39438
39062
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
39439
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
39063
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join45(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
39440
39064
  const pipelineContext = {
39441
39065
  config: effectiveConfig,
39442
39066
  rootConfig: ctx.config,
@@ -39445,7 +39069,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
39445
39069
  stories: storiesToExecute,
39446
39070
  routing,
39447
39071
  projectDir: ctx.workdir,
39448
- workdir: story.workdir ? join46(ctx.workdir, story.workdir) : ctx.workdir,
39072
+ workdir: story.workdir ? join45(ctx.workdir, story.workdir) : ctx.workdir,
39449
39073
  prdPath: ctx.prdPath,
39450
39074
  featureDir: ctx.featureDir,
39451
39075
  hooks: ctx.hooks,
@@ -39601,7 +39225,7 @@ __export(exports_parallel_worker, {
39601
39225
  executeStoryInWorktree: () => executeStoryInWorktree,
39602
39226
  executeParallelBatch: () => executeParallelBatch
39603
39227
  });
39604
- import { join as join47 } from "path";
39228
+ import { join as join46 } from "path";
39605
39229
  async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
39606
39230
  const logger = getSafeLogger();
39607
39231
  try {
@@ -39621,7 +39245,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
39621
39245
  story,
39622
39246
  stories: [story],
39623
39247
  projectDir: context.projectDir,
39624
- workdir: story.workdir ? join47(worktreePath, story.workdir) : worktreePath,
39248
+ workdir: story.workdir ? join46(worktreePath, story.workdir) : worktreePath,
39625
39249
  routing,
39626
39250
  storyGitRef: storyGitRef ?? undefined
39627
39251
  };
@@ -39710,13 +39334,13 @@ __export(exports_manager, {
39710
39334
  });
39711
39335
  import { existsSync as existsSync29, symlinkSync } from "fs";
39712
39336
  import { mkdir as mkdir5 } from "fs/promises";
39713
- import { join as join48 } from "path";
39337
+ import { join as join47 } from "path";
39714
39338
 
39715
39339
  class WorktreeManager {
39716
39340
  async ensureGitExcludes(projectRoot) {
39717
39341
  const logger = getSafeLogger();
39718
- const infoDir = join48(projectRoot, ".git", "info");
39719
- const excludePath = join48(infoDir, "exclude");
39342
+ const infoDir = join47(projectRoot, ".git", "info");
39343
+ const excludePath = join47(infoDir, "exclude");
39720
39344
  try {
39721
39345
  await mkdir5(infoDir, { recursive: true });
39722
39346
  let existing = "";
@@ -39743,7 +39367,7 @@ ${missing.join(`
39743
39367
  }
39744
39368
  async create(projectRoot, storyId) {
39745
39369
  validateStoryId(storyId);
39746
- const worktreePath = join48(projectRoot, ".nax-wt", storyId);
39370
+ const worktreePath = join47(projectRoot, ".nax-wt", storyId);
39747
39371
  const branchName = `nax/${storyId}`;
39748
39372
  try {
39749
39373
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -39784,9 +39408,9 @@ ${missing.join(`
39784
39408
  }
39785
39409
  throw new Error(`Failed to create worktree: ${String(error48)}`);
39786
39410
  }
39787
- const nodeModulesSource = join48(projectRoot, "node_modules");
39411
+ const nodeModulesSource = join47(projectRoot, "node_modules");
39788
39412
  if (existsSync29(nodeModulesSource)) {
39789
- const nodeModulesTarget = join48(worktreePath, "node_modules");
39413
+ const nodeModulesTarget = join47(worktreePath, "node_modules");
39790
39414
  try {
39791
39415
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
39792
39416
  } catch (error48) {
@@ -39794,9 +39418,9 @@ ${missing.join(`
39794
39418
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
39795
39419
  }
39796
39420
  }
39797
- const envSource = join48(projectRoot, ".env");
39421
+ const envSource = join47(projectRoot, ".env");
39798
39422
  if (existsSync29(envSource)) {
39799
- const envTarget = join48(worktreePath, ".env");
39423
+ const envTarget = join47(worktreePath, ".env");
39800
39424
  try {
39801
39425
  symlinkSync(envSource, envTarget, "file");
39802
39426
  } catch (error48) {
@@ -39807,7 +39431,7 @@ ${missing.join(`
39807
39431
  }
39808
39432
  async remove(projectRoot, storyId) {
39809
39433
  validateStoryId(storyId);
39810
- const worktreePath = join48(projectRoot, ".nax-wt", storyId);
39434
+ const worktreePath = join47(projectRoot, ".nax-wt", storyId);
39811
39435
  const branchName = `nax/${storyId}`;
39812
39436
  try {
39813
39437
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -40749,16 +40373,16 @@ var init_unified_executor = __esm(() => {
40749
40373
  });
40750
40374
 
40751
40375
  // src/project/detector.ts
40752
- import { join as join49 } from "path";
40376
+ import { join as join48 } from "path";
40753
40377
  async function detectLanguage(workdir, pkg) {
40754
40378
  const deps = _detectorDeps;
40755
- if (await deps.fileExists(join49(workdir, "go.mod")))
40379
+ if (await deps.fileExists(join48(workdir, "go.mod")))
40756
40380
  return "go";
40757
- if (await deps.fileExists(join49(workdir, "Cargo.toml")))
40381
+ if (await deps.fileExists(join48(workdir, "Cargo.toml")))
40758
40382
  return "rust";
40759
- if (await deps.fileExists(join49(workdir, "pyproject.toml")))
40383
+ if (await deps.fileExists(join48(workdir, "pyproject.toml")))
40760
40384
  return "python";
40761
- if (await deps.fileExists(join49(workdir, "requirements.txt")))
40385
+ if (await deps.fileExists(join48(workdir, "requirements.txt")))
40762
40386
  return "python";
40763
40387
  if (pkg != null) {
40764
40388
  const allDeps = {
@@ -40818,18 +40442,18 @@ async function detectLintTool(workdir, language) {
40818
40442
  if (language === "python")
40819
40443
  return "ruff";
40820
40444
  const deps = _detectorDeps;
40821
- if (await deps.fileExists(join49(workdir, "biome.json")))
40445
+ if (await deps.fileExists(join48(workdir, "biome.json")))
40822
40446
  return "biome";
40823
- if (await deps.fileExists(join49(workdir, ".eslintrc")))
40447
+ if (await deps.fileExists(join48(workdir, ".eslintrc")))
40824
40448
  return "eslint";
40825
- if (await deps.fileExists(join49(workdir, ".eslintrc.js")))
40449
+ if (await deps.fileExists(join48(workdir, ".eslintrc.js")))
40826
40450
  return "eslint";
40827
- if (await deps.fileExists(join49(workdir, ".eslintrc.json")))
40451
+ if (await deps.fileExists(join48(workdir, ".eslintrc.json")))
40828
40452
  return "eslint";
40829
40453
  return;
40830
40454
  }
40831
40455
  async function detectProjectProfile(workdir, existing) {
40832
- const pkg = await _detectorDeps.readJson(join49(workdir, "package.json"));
40456
+ const pkg = await _detectorDeps.readJson(join48(workdir, "package.json"));
40833
40457
  const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
40834
40458
  const type = existing.type !== undefined ? existing.type : detectType(pkg);
40835
40459
  const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
@@ -40925,7 +40549,7 @@ async function writeStatusFile(filePath, status) {
40925
40549
  var init_status_file = () => {};
40926
40550
 
40927
40551
  // src/execution/status-writer.ts
40928
- import { join as join50 } from "path";
40552
+ import { join as join49 } from "path";
40929
40553
 
40930
40554
  class StatusWriter {
40931
40555
  statusFile;
@@ -41039,7 +40663,7 @@ class StatusWriter {
41039
40663
  if (!this._prd)
41040
40664
  return;
41041
40665
  const safeLogger = getSafeLogger();
41042
- const featureStatusPath = join50(featureDir, "status.json");
40666
+ const featureStatusPath = join49(featureDir, "status.json");
41043
40667
  const write = async () => {
41044
40668
  try {
41045
40669
  const base = this.getSnapshot(totalCost, iterations);
@@ -41250,7 +40874,7 @@ __export(exports_run_initialization, {
41250
40874
  initializeRun: () => initializeRun,
41251
40875
  _reconcileDeps: () => _reconcileDeps
41252
40876
  });
41253
- import { join as join51 } from "path";
40877
+ import { join as join50 } from "path";
41254
40878
  async function reconcileState(prd, prdPath, workdir, config2) {
41255
40879
  const logger = getSafeLogger();
41256
40880
  let reconciledCount = 0;
@@ -41268,7 +40892,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
41268
40892
  });
41269
40893
  continue;
41270
40894
  }
41271
- const effectiveWorkdir = story.workdir ? join51(workdir, story.workdir) : workdir;
40895
+ const effectiveWorkdir = story.workdir ? join50(workdir, story.workdir) : workdir;
41272
40896
  try {
41273
40897
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
41274
40898
  if (!reviewResult.success) {
@@ -72483,7 +72107,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
72483
72107
  init_source();
72484
72108
  import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
72485
72109
  import { homedir as homedir8 } from "os";
72486
- import { join as join53 } from "path";
72110
+ import { join as join52 } from "path";
72487
72111
 
72488
72112
  // node_modules/commander/esm.mjs
72489
72113
  var import__ = __toESM(require_commander(), 1);
@@ -84515,15 +84139,15 @@ Next: nax generate --package ${options.package}`));
84515
84139
  }
84516
84140
  return;
84517
84141
  }
84518
- const naxDir = join53(workdir, ".nax");
84142
+ const naxDir = join52(workdir, ".nax");
84519
84143
  if (existsSync31(naxDir) && !options.force) {
84520
84144
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
84521
84145
  return;
84522
84146
  }
84523
- mkdirSync7(join53(naxDir, "features"), { recursive: true });
84524
- mkdirSync7(join53(naxDir, "hooks"), { recursive: true });
84525
- await Bun.write(join53(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
84526
- await Bun.write(join53(naxDir, "hooks.json"), JSON.stringify({
84147
+ mkdirSync7(join52(naxDir, "features"), { recursive: true });
84148
+ mkdirSync7(join52(naxDir, "hooks"), { recursive: true });
84149
+ await Bun.write(join52(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
84150
+ await Bun.write(join52(naxDir, "hooks.json"), JSON.stringify({
84527
84151
  hooks: {
84528
84152
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
84529
84153
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -84531,12 +84155,12 @@ Next: nax generate --package ${options.package}`));
84531
84155
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
84532
84156
  }
84533
84157
  }, null, 2));
84534
- await Bun.write(join53(naxDir, ".gitignore"), `# nax temp files
84158
+ await Bun.write(join52(naxDir, ".gitignore"), `# nax temp files
84535
84159
  *.tmp
84536
84160
  .paused.json
84537
84161
  .nax-verifier-verdict.json
84538
84162
  `);
84539
- await Bun.write(join53(naxDir, "context.md"), `# Project Context
84163
+ await Bun.write(join52(naxDir, "context.md"), `# Project Context
84540
84164
 
84541
84165
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
84542
84166
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -84666,8 +84290,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
84666
84290
  console.error(source_default.red("nax not initialized. Run: nax init"));
84667
84291
  process.exit(1);
84668
84292
  }
84669
- const featureDir = join53(naxDir, "features", options.feature);
84670
- const prdPath = join53(featureDir, "prd.json");
84293
+ const featureDir = join52(naxDir, "features", options.feature);
84294
+ const prdPath = join52(featureDir, "prd.json");
84671
84295
  if (options.plan && options.from) {
84672
84296
  if (existsSync31(prdPath) && !options.force) {
84673
84297
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -84689,10 +84313,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
84689
84313
  }
84690
84314
  }
84691
84315
  try {
84692
- const planLogDir = join53(featureDir, "plan");
84316
+ const planLogDir = join52(featureDir, "plan");
84693
84317
  mkdirSync7(planLogDir, { recursive: true });
84694
84318
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
84695
- const planLogPath = join53(planLogDir, `${planLogId}.jsonl`);
84319
+ const planLogPath = join52(planLogDir, `${planLogId}.jsonl`);
84696
84320
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
84697
84321
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
84698
84322
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -84736,10 +84360,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
84736
84360
  process.exit(1);
84737
84361
  }
84738
84362
  resetLogger();
84739
- const runsDir = join53(featureDir, "runs");
84363
+ const runsDir = join52(featureDir, "runs");
84740
84364
  mkdirSync7(runsDir, { recursive: true });
84741
84365
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
84742
- const logFilePath = join53(runsDir, `${runId}.jsonl`);
84366
+ const logFilePath = join52(runsDir, `${runId}.jsonl`);
84743
84367
  const isTTY = process.stdout.isTTY ?? false;
84744
84368
  const headlessFlag = options.headless ?? false;
84745
84369
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -84755,7 +84379,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
84755
84379
  config2.autoMode.defaultAgent = options.agent;
84756
84380
  }
84757
84381
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
84758
- const globalNaxDir = join53(homedir8(), ".nax");
84382
+ const globalNaxDir = join52(homedir8(), ".nax");
84759
84383
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
84760
84384
  const eventEmitter = new PipelineEventEmitter;
84761
84385
  let tuiInstance;
@@ -84778,7 +84402,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
84778
84402
  } else {
84779
84403
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
84780
84404
  }
84781
- const statusFilePath = join53(workdir, ".nax", "status.json");
84405
+ const statusFilePath = join52(workdir, ".nax", "status.json");
84782
84406
  let parallel;
84783
84407
  if (options.parallel !== undefined) {
84784
84408
  parallel = Number.parseInt(options.parallel, 10);
@@ -84804,7 +84428,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
84804
84428
  headless: useHeadless,
84805
84429
  skipPrecheck: options.skipPrecheck ?? false
84806
84430
  });
84807
- const latestSymlink = join53(runsDir, "latest.jsonl");
84431
+ const latestSymlink = join52(runsDir, "latest.jsonl");
84808
84432
  try {
84809
84433
  if (existsSync31(latestSymlink)) {
84810
84434
  Bun.spawnSync(["rm", latestSymlink]);
@@ -84842,9 +84466,9 @@ features.command("create <name>").description("Create a new feature").option("-d
84842
84466
  console.error(source_default.red("nax not initialized. Run: nax init"));
84843
84467
  process.exit(1);
84844
84468
  }
84845
- const featureDir = join53(naxDir, "features", name);
84469
+ const featureDir = join52(naxDir, "features", name);
84846
84470
  mkdirSync7(featureDir, { recursive: true });
84847
- await Bun.write(join53(featureDir, "spec.md"), `# Feature: ${name}
84471
+ await Bun.write(join52(featureDir, "spec.md"), `# Feature: ${name}
84848
84472
 
84849
84473
  ## Overview
84850
84474
 
@@ -84877,7 +84501,7 @@ features.command("create <name>").description("Create a new feature").option("-d
84877
84501
 
84878
84502
  <!-- What this feature explicitly does NOT cover. -->
84879
84503
  `);
84880
- await Bun.write(join53(featureDir, "progress.txt"), `# Progress: ${name}
84504
+ await Bun.write(join52(featureDir, "progress.txt"), `# Progress: ${name}
84881
84505
 
84882
84506
  Created: ${new Date().toISOString()}
84883
84507
 
@@ -84903,7 +84527,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
84903
84527
  console.error(source_default.red("nax not initialized."));
84904
84528
  process.exit(1);
84905
84529
  }
84906
- const featuresDir = join53(naxDir, "features");
84530
+ const featuresDir = join52(naxDir, "features");
84907
84531
  if (!existsSync31(featuresDir)) {
84908
84532
  console.log(source_default.dim("No features yet."));
84909
84533
  return;
@@ -84918,7 +84542,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
84918
84542
  Features:
84919
84543
  `));
84920
84544
  for (const name of entries) {
84921
- const prdPath = join53(featuresDir, name, "prd.json");
84545
+ const prdPath = join52(featuresDir, name, "prd.json");
84922
84546
  if (existsSync31(prdPath)) {
84923
84547
  const prd = await loadPRD(prdPath);
84924
84548
  const c = countStories(prd);
@@ -84953,10 +84577,10 @@ Use: nax plan -f <feature> --from <spec>`));
84953
84577
  cliOverrides.profile = options.profile;
84954
84578
  }
84955
84579
  const config2 = await loadConfig(workdir, cliOverrides);
84956
- const featureLogDir = join53(naxDir, "features", options.feature, "plan");
84580
+ const featureLogDir = join52(naxDir, "features", options.feature, "plan");
84957
84581
  mkdirSync7(featureLogDir, { recursive: true });
84958
84582
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
84959
- const planLogPath = join53(featureLogDir, `${planLogId}.jsonl`);
84583
+ const planLogPath = join52(featureLogDir, `${planLogId}.jsonl`);
84960
84584
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
84961
84585
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
84962
84586
  try {