@nathapp/nax 0.48.4 → 0.49.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.
package/dist/nax.js CHANGED
@@ -18717,9 +18717,8 @@ ${criteriaList}
18717
18717
 
18718
18718
  ${strategyInstructions}Generate a complete acceptance.test.ts file using bun:test framework. Each AC maps to exactly one test named "AC-N: <description>".
18719
18719
 
18720
- Use this structure:
18720
+ Structure example (do NOT wrap in markdown fences \u2014 output raw TypeScript only):
18721
18721
 
18722
- \`\`\`typescript
18723
18722
  import { describe, test, expect } from "bun:test";
18724
18723
 
18725
18724
  describe("${options.featureName} - Acceptance Tests", () => {
@@ -18727,11 +18726,11 @@ describe("${options.featureName} - Acceptance Tests", () => {
18727
18726
  // Test implementation
18728
18727
  });
18729
18728
  });
18730
- \`\`\`
18731
18729
 
18732
- Respond with ONLY the TypeScript test code (no markdown code fences, no explanation).`;
18730
+ IMPORTANT: Output raw TypeScript code only. Do NOT use markdown code fences (\`\`\`typescript or \`\`\`). Start directly with the import statement.`;
18733
18731
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
18734
- const testCode = await _generatorPRDDeps.adapter.complete(prompt, { config: options.config });
18732
+ const rawOutput = await _generatorPRDDeps.adapter.complete(prompt, { config: options.config });
18733
+ const testCode = extractTestCode(rawOutput);
18735
18734
  const refinedJsonContent = JSON.stringify(refinedCriteria.map((c, i) => ({
18736
18735
  acId: `AC-${i + 1}`,
18737
18736
  original: c.original,
@@ -20697,17 +20696,49 @@ var init_json_file = __esm(() => {
20697
20696
 
20698
20697
  // src/config/merge.ts
20699
20698
  function mergePackageConfig(root, packageOverride) {
20700
- const packageCommands = packageOverride.quality?.commands;
20701
- if (!packageCommands) {
20699
+ const hasAnyMergeableField = packageOverride.execution !== undefined || packageOverride.review !== undefined || packageOverride.acceptance !== undefined || packageOverride.quality !== undefined || packageOverride.context !== undefined;
20700
+ if (!hasAnyMergeableField) {
20702
20701
  return root;
20703
20702
  }
20704
20703
  return {
20705
20704
  ...root,
20705
+ execution: {
20706
+ ...root.execution,
20707
+ ...packageOverride.execution,
20708
+ smartTestRunner: packageOverride.execution?.smartTestRunner ?? root.execution.smartTestRunner,
20709
+ regressionGate: {
20710
+ ...root.execution.regressionGate,
20711
+ ...packageOverride.execution?.regressionGate
20712
+ },
20713
+ verificationTimeoutSeconds: packageOverride.execution?.verificationTimeoutSeconds ?? root.execution.verificationTimeoutSeconds
20714
+ },
20715
+ review: {
20716
+ ...root.review,
20717
+ ...packageOverride.review,
20718
+ commands: {
20719
+ ...root.review.commands,
20720
+ ...packageOverride.review?.commands
20721
+ }
20722
+ },
20723
+ acceptance: {
20724
+ ...root.acceptance,
20725
+ ...packageOverride.acceptance
20726
+ },
20706
20727
  quality: {
20707
20728
  ...root.quality,
20729
+ requireTests: packageOverride.quality?.requireTests ?? root.quality.requireTests,
20730
+ requireTypecheck: packageOverride.quality?.requireTypecheck ?? root.quality.requireTypecheck,
20731
+ requireLint: packageOverride.quality?.requireLint ?? root.quality.requireLint,
20708
20732
  commands: {
20709
20733
  ...root.quality.commands,
20710
- ...packageCommands
20734
+ ...packageOverride.quality?.commands
20735
+ }
20736
+ },
20737
+ context: {
20738
+ ...root.context,
20739
+ testCoverage: {
20740
+ ...root.context.testCoverage,
20741
+ ...packageOverride.context?.testCoverage
20711
20742
  }
20712
20743
  }
20713
20744
  };
@@ -22210,7 +22241,7 @@ var package_default;
22210
22241
  var init_package = __esm(() => {
22211
22242
  package_default = {
22212
22243
  name: "@nathapp/nax",
22213
- version: "0.48.4",
22244
+ version: "0.49.0",
22214
22245
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22215
22246
  type: "module",
22216
22247
  bin: {
@@ -22283,8 +22314,8 @@ var init_version = __esm(() => {
22283
22314
  NAX_VERSION = package_default.version;
22284
22315
  NAX_COMMIT = (() => {
22285
22316
  try {
22286
- if (/^[0-9a-f]{6,10}$/.test("59e08c0"))
22287
- return "59e08c0";
22317
+ if (/^[0-9a-f]{6,10}$/.test("6a5bc7a"))
22318
+ return "6a5bc7a";
22288
22319
  } catch {}
22289
22320
  try {
22290
22321
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23933,7 +23964,8 @@ var init_acceptance2 = __esm(() => {
23933
23964
  acceptanceStage = {
23934
23965
  name: "acceptance",
23935
23966
  enabled(ctx) {
23936
- if (!ctx.config.acceptance.enabled) {
23967
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
23968
+ if (!effectiveConfig.acceptance.enabled) {
23937
23969
  return false;
23938
23970
  }
23939
23971
  if (!areAllStoriesComplete(ctx)) {
@@ -23943,12 +23975,13 @@ var init_acceptance2 = __esm(() => {
23943
23975
  },
23944
23976
  async execute(ctx) {
23945
23977
  const logger = getLogger();
23978
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
23946
23979
  logger.info("acceptance", "Running acceptance tests");
23947
23980
  if (!ctx.featureDir) {
23948
23981
  logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
23949
23982
  return { action: "continue" };
23950
23983
  }
23951
- const testPath = path4.join(ctx.featureDir, ctx.config.acceptance.testPath);
23984
+ const testPath = path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath);
23952
23985
  const testFile = Bun.file(testPath);
23953
23986
  const exists = await testFile.exists();
23954
23987
  if (!exists) {
@@ -24577,12 +24610,13 @@ var init_review = __esm(() => {
24577
24610
  init_orchestrator();
24578
24611
  reviewStage = {
24579
24612
  name: "review",
24580
- enabled: (ctx) => ctx.config.review.enabled,
24613
+ enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
24581
24614
  async execute(ctx) {
24582
24615
  const logger = getLogger();
24616
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24583
24617
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
24584
24618
  const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
24585
- const result = await reviewOrchestrator.review(ctx.config.review, effectiveWorkdir, ctx.config.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
24619
+ const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
24586
24620
  ctx.reviewResult = result.builtIn;
24587
24621
  if (!result.success) {
24588
24622
  const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
@@ -24590,8 +24624,8 @@ var init_review = __esm(() => {
24590
24624
  ctx.reviewFindings = allFindings;
24591
24625
  }
24592
24626
  if (result.pluginFailed) {
24593
- if (ctx.interaction && isTriggerEnabled("security-review", ctx.config)) {
24594
- const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
24627
+ if (ctx.interaction && isTriggerEnabled("security-review", effectiveConfig)) {
24628
+ const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, effectiveConfig, ctx.interaction);
24595
24629
  if (!shouldContinue) {
24596
24630
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
24597
24631
  return { action: "fail", reason: `Review failed: ${result.failureReason}` };
@@ -24602,11 +24636,11 @@ var init_review = __esm(() => {
24602
24636
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
24603
24637
  return { action: "fail", reason: `Review failed: ${result.failureReason}` };
24604
24638
  }
24605
- logger.warn("review", "Review failed (built-in checks) \u2014 escalating for retry", {
24639
+ logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
24606
24640
  reason: result.failureReason,
24607
24641
  storyId: ctx.story.id
24608
24642
  });
24609
- return { action: "escalate", reason: `Review failed: ${result.failureReason}` };
24643
+ return { action: "continue" };
24610
24644
  }
24611
24645
  logger.info("review", "Review passed", {
24612
24646
  durationMs: result.builtIn.totalDurationMs,
@@ -24621,6 +24655,7 @@ var init_review = __esm(() => {
24621
24655
  });
24622
24656
 
24623
24657
  // src/pipeline/stages/autofix.ts
24658
+ import { join as join18 } from "path";
24624
24659
  async function runCommand(cmd, cwd) {
24625
24660
  const parts = cmd.split(/\s+/);
24626
24661
  const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
@@ -24663,7 +24698,8 @@ Commit your fixes when done.`;
24663
24698
  }
24664
24699
  async function runAgentRectification(ctx) {
24665
24700
  const logger = getLogger();
24666
- const maxAttempts = ctx.config.quality.autofix?.maxAttempts ?? 2;
24701
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24702
+ const maxAttempts = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
24667
24703
  const failedChecks = collectFailedChecks(ctx);
24668
24704
  if (failedChecks.length === 0) {
24669
24705
  logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
@@ -24720,6 +24756,7 @@ var autofixStage, _autofixDeps;
24720
24756
  var init_autofix = __esm(() => {
24721
24757
  init_agents();
24722
24758
  init_config();
24759
+ init_loader2();
24723
24760
  init_logger2();
24724
24761
  init_event_bus();
24725
24762
  autofixStage = {
@@ -24729,7 +24766,7 @@ var init_autofix = __esm(() => {
24729
24766
  return false;
24730
24767
  if (ctx.reviewResult.success)
24731
24768
  return false;
24732
- const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
24769
+ const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
24733
24770
  return autofixEnabled;
24734
24771
  },
24735
24772
  skipReason(ctx) {
@@ -24743,12 +24780,14 @@ var init_autofix = __esm(() => {
24743
24780
  if (!reviewResult || reviewResult.success) {
24744
24781
  return { action: "continue" };
24745
24782
  }
24746
- const lintFixCmd = ctx.config.quality.commands.lintFix;
24747
- const formatFixCmd = ctx.config.quality.commands.formatFix;
24783
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24784
+ const lintFixCmd = effectiveConfig.quality.commands.lintFix;
24785
+ const formatFixCmd = effectiveConfig.quality.commands.formatFix;
24786
+ const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
24748
24787
  if (lintFixCmd || formatFixCmd) {
24749
24788
  if (lintFixCmd) {
24750
24789
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
24751
- const lintResult = await _autofixDeps.runCommand(lintFixCmd, ctx.workdir);
24790
+ const lintResult = await _autofixDeps.runCommand(lintFixCmd, effectiveWorkdir);
24752
24791
  logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id });
24753
24792
  if (lintResult.exitCode !== 0) {
24754
24793
  logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
@@ -24759,7 +24798,7 @@ var init_autofix = __esm(() => {
24759
24798
  }
24760
24799
  if (formatFixCmd) {
24761
24800
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
24762
- const fmtResult = await _autofixDeps.runCommand(formatFixCmd, ctx.workdir);
24801
+ const fmtResult = await _autofixDeps.runCommand(formatFixCmd, effectiveWorkdir);
24763
24802
  logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, { storyId: ctx.story.id });
24764
24803
  if (fmtResult.exitCode !== 0) {
24765
24804
  logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
@@ -24788,15 +24827,15 @@ var init_autofix = __esm(() => {
24788
24827
  return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
24789
24828
  }
24790
24829
  };
24791
- _autofixDeps = { runCommand, recheckReview, runAgentRectification };
24830
+ _autofixDeps = { runCommand, recheckReview, runAgentRectification, loadConfigForWorkdir };
24792
24831
  });
24793
24832
 
24794
24833
  // src/execution/progress.ts
24795
24834
  import { mkdirSync as mkdirSync2 } from "fs";
24796
- import { join as join18 } from "path";
24835
+ import { join as join19 } from "path";
24797
24836
  async function appendProgress(featureDir, storyId, status, message) {
24798
24837
  mkdirSync2(featureDir, { recursive: true });
24799
- const progressPath = join18(featureDir, "progress.txt");
24838
+ const progressPath = join19(featureDir, "progress.txt");
24800
24839
  const timestamp = new Date().toISOString();
24801
24840
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
24802
24841
  `;
@@ -24880,7 +24919,7 @@ function estimateTokens(text) {
24880
24919
 
24881
24920
  // src/constitution/loader.ts
24882
24921
  import { existsSync as existsSync15 } from "fs";
24883
- import { join as join19 } from "path";
24922
+ import { join as join20 } from "path";
24884
24923
  function truncateToTokens(text, maxTokens) {
24885
24924
  const maxChars = maxTokens * 3;
24886
24925
  if (text.length <= maxChars) {
@@ -24902,7 +24941,7 @@ async function loadConstitution(projectDir, config2) {
24902
24941
  }
24903
24942
  let combinedContent = "";
24904
24943
  if (!config2.skipGlobal) {
24905
- const globalPath = join19(globalConfigDir(), config2.path);
24944
+ const globalPath = join20(globalConfigDir(), config2.path);
24906
24945
  if (existsSync15(globalPath)) {
24907
24946
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
24908
24947
  const globalFile = Bun.file(validatedPath);
@@ -24912,7 +24951,7 @@ async function loadConstitution(projectDir, config2) {
24912
24951
  }
24913
24952
  }
24914
24953
  }
24915
- const projectPath = join19(projectDir, config2.path);
24954
+ const projectPath = join20(projectDir, config2.path);
24916
24955
  if (existsSync15(projectPath)) {
24917
24956
  const validatedPath = validateFilePath(projectPath, projectDir);
24918
24957
  const projectFile = Bun.file(validatedPath);
@@ -25884,7 +25923,7 @@ var init_helpers = __esm(() => {
25884
25923
  });
25885
25924
 
25886
25925
  // src/pipeline/stages/context.ts
25887
- import { join as join20 } from "path";
25926
+ import { join as join21 } from "path";
25888
25927
  var contextStage;
25889
25928
  var init_context2 = __esm(() => {
25890
25929
  init_helpers();
@@ -25894,7 +25933,7 @@ var init_context2 = __esm(() => {
25894
25933
  enabled: () => true,
25895
25934
  async execute(ctx) {
25896
25935
  const logger = getLogger();
25897
- const packageWorkdir = ctx.story.workdir ? join20(ctx.workdir, ctx.story.workdir) : undefined;
25936
+ const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
25898
25937
  const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
25899
25938
  if (result) {
25900
25939
  ctx.contextMarkdown = result.markdown;
@@ -26026,14 +26065,14 @@ var init_isolation = __esm(() => {
26026
26065
 
26027
26066
  // src/context/greenfield.ts
26028
26067
  import { readdir } from "fs/promises";
26029
- import { join as join21 } from "path";
26068
+ import { join as join22 } from "path";
26030
26069
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
26031
26070
  const results = [];
26032
26071
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
26033
26072
  try {
26034
26073
  const entries = await readdir(dir, { withFileTypes: true });
26035
26074
  for (const entry of entries) {
26036
- const fullPath = join21(dir, entry.name);
26075
+ const fullPath = join22(dir, entry.name);
26037
26076
  if (entry.isDirectory()) {
26038
26077
  if (ignoreDirs.has(entry.name))
26039
26078
  continue;
@@ -26445,13 +26484,13 @@ function parseTestOutput(output, exitCode) {
26445
26484
 
26446
26485
  // src/verification/runners.ts
26447
26486
  import { existsSync as existsSync16 } from "fs";
26448
- import { join as join22 } from "path";
26487
+ import { join as join23 } from "path";
26449
26488
  async function verifyAssets(workingDirectory, expectedFiles) {
26450
26489
  if (!expectedFiles || expectedFiles.length === 0)
26451
26490
  return { success: true, missingFiles: [] };
26452
26491
  const missingFiles = [];
26453
26492
  for (const file2 of expectedFiles) {
26454
- if (!existsSync16(join22(workingDirectory, file2)))
26493
+ if (!existsSync16(join23(workingDirectory, file2)))
26455
26494
  missingFiles.push(file2);
26456
26495
  }
26457
26496
  if (missingFiles.length > 0) {
@@ -27136,13 +27175,13 @@ var exports_loader = {};
27136
27175
  __export(exports_loader, {
27137
27176
  loadOverride: () => loadOverride
27138
27177
  });
27139
- import { join as join23 } from "path";
27178
+ import { join as join24 } from "path";
27140
27179
  async function loadOverride(role, workdir, config2) {
27141
27180
  const overridePath = config2.prompts?.overrides?.[role];
27142
27181
  if (!overridePath) {
27143
27182
  return null;
27144
27183
  }
27145
- const absolutePath = join23(workdir, overridePath);
27184
+ const absolutePath = join24(workdir, overridePath);
27146
27185
  const file2 = Bun.file(absolutePath);
27147
27186
  if (!await file2.exists()) {
27148
27187
  return null;
@@ -27964,11 +28003,11 @@ var init_tdd = __esm(() => {
27964
28003
 
27965
28004
  // src/pipeline/stages/execution.ts
27966
28005
  import { existsSync as existsSync17 } from "fs";
27967
- import { join as join24 } from "path";
28006
+ import { join as join25 } from "path";
27968
28007
  function resolveStoryWorkdir(repoRoot, storyWorkdir) {
27969
28008
  if (!storyWorkdir)
27970
28009
  return repoRoot;
27971
- const resolved = join24(repoRoot, storyWorkdir);
28010
+ const resolved = join25(repoRoot, storyWorkdir);
27972
28011
  if (!existsSync17(resolved)) {
27973
28012
  throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
27974
28013
  }
@@ -27998,6 +28037,9 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
27998
28037
  if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
27999
28038
  return { action: "escalate" };
28000
28039
  }
28040
+ if (failureCategory === "greenfield-no-tests") {
28041
+ return { action: "escalate" };
28042
+ }
28001
28043
  return {
28002
28044
  action: "pause",
28003
28045
  reason: reviewReason || "Three-session TDD requires review"
@@ -28438,13 +28480,14 @@ var init_prompt = __esm(() => {
28438
28480
  async execute(ctx) {
28439
28481
  const logger = getLogger();
28440
28482
  const isBatch = ctx.stories.length > 1;
28483
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28441
28484
  let prompt;
28442
28485
  if (isBatch) {
28443
- 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);
28486
+ 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);
28444
28487
  prompt = await builder.build();
28445
28488
  } else {
28446
28489
  const role = "tdd-simple";
28447
- 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);
28490
+ 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);
28448
28491
  prompt = await builder.build();
28449
28492
  }
28450
28493
  ctx.prompt = prompt;
@@ -28788,13 +28831,14 @@ var init_rectify = __esm(() => {
28788
28831
  attempt: rectifyAttempt,
28789
28832
  testOutput
28790
28833
  });
28791
- const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
28834
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28835
+ const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
28792
28836
  const fixed = await _rectifyDeps.runRectificationLoop({
28793
28837
  config: ctx.config,
28794
28838
  workdir: ctx.workdir,
28795
28839
  story: ctx.story,
28796
28840
  testCommand,
28797
- timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
28841
+ timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
28798
28842
  testOutput
28799
28843
  });
28800
28844
  pipelineEventBus.emit({
@@ -29322,31 +29366,34 @@ var init_regression2 = __esm(() => {
29322
29366
  regressionStage = {
29323
29367
  name: "regression",
29324
29368
  enabled(ctx) {
29325
- const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29369
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29370
+ const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29326
29371
  if (mode !== "per-story")
29327
29372
  return false;
29328
29373
  if (ctx.verifyResult && !ctx.verifyResult.success)
29329
29374
  return false;
29330
- const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
29375
+ const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
29331
29376
  return gateEnabled;
29332
29377
  },
29333
29378
  skipReason(ctx) {
29334
- const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29379
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29380
+ const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29335
29381
  if (mode !== "per-story")
29336
29382
  return `not needed (regression mode is '${mode}', not 'per-story')`;
29337
29383
  return "disabled (regression gate not enabled in config)";
29338
29384
  },
29339
29385
  async execute(ctx) {
29340
29386
  const logger = getLogger();
29341
- const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
29342
- const timeoutSeconds = ctx.config.execution.regressionGate?.timeoutSeconds ?? 120;
29387
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29388
+ const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
29389
+ const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
29343
29390
  logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
29344
29391
  const verifyCtx = {
29345
29392
  workdir: ctx.workdir,
29346
29393
  testCommand,
29347
29394
  timeoutSeconds,
29348
29395
  storyId: ctx.story.id,
29349
- acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
29396
+ acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
29350
29397
  config: ctx.config
29351
29398
  };
29352
29399
  const result = await _regressionStageDeps.verifyRegression(verifyCtx);
@@ -29381,7 +29428,7 @@ var init_regression2 = __esm(() => {
29381
29428
  });
29382
29429
 
29383
29430
  // src/pipeline/stages/routing.ts
29384
- import { join as join25 } from "path";
29431
+ import { join as join26 } from "path";
29385
29432
  async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
29386
29433
  const naxDecompose = config2.decompose;
29387
29434
  const builderConfig = {
@@ -29455,7 +29502,7 @@ var init_routing2 = __esm(() => {
29455
29502
  }
29456
29503
  const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
29457
29504
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
29458
- const greenfieldScanDir = ctx.story.workdir ? join25(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29505
+ const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29459
29506
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
29460
29507
  if (isGreenfield) {
29461
29508
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -29552,7 +29599,7 @@ var init_crash_detector = __esm(() => {
29552
29599
  });
29553
29600
 
29554
29601
  // src/pipeline/stages/verify.ts
29555
- import { join as join26 } from "path";
29602
+ import { join as join27 } from "path";
29556
29603
  function coerceSmartTestRunner(val) {
29557
29604
  if (val === undefined || val === true)
29558
29605
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -29568,7 +29615,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
29568
29615
  }
29569
29616
  async function readPackageName(dir) {
29570
29617
  try {
29571
- const content = await Bun.file(join26(dir, "package.json")).json();
29618
+ const content = await Bun.file(join27(dir, "package.json")).json();
29572
29619
  return typeof content.name === "string" ? content.name : null;
29573
29620
  } catch {
29574
29621
  return null;
@@ -29585,7 +29632,6 @@ async function resolvePackageTemplate(template, packageDir) {
29585
29632
  }
29586
29633
  var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
29587
29634
  var init_verify = __esm(() => {
29588
- init_loader2();
29589
29635
  init_logger2();
29590
29636
  init_crash_detector();
29591
29637
  init_runners();
@@ -29602,7 +29648,7 @@ var init_verify = __esm(() => {
29602
29648
  skipReason: () => "not needed (full-suite gate already passed)",
29603
29649
  async execute(ctx) {
29604
29650
  const logger = getLogger();
29605
- const effectiveConfig = ctx.story.workdir ? await _verifyDeps.loadConfigForWorkdir(join26(ctx.workdir, "nax", "config.json"), ctx.story.workdir) : ctx.config;
29651
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29606
29652
  if (!effectiveConfig.quality.requireTests) {
29607
29653
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
29608
29654
  return { action: "continue" };
@@ -29614,11 +29660,11 @@ var init_verify = __esm(() => {
29614
29660
  return { action: "continue" };
29615
29661
  }
29616
29662
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
29617
- const effectiveWorkdir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29663
+ const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29618
29664
  let effectiveCommand = testCommand;
29619
29665
  let isFullSuite = true;
29620
- const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
29621
- const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29666
+ const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
29667
+ const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29622
29668
  let resolvedTestScopedTemplate = testScopedTemplate;
29623
29669
  if (testScopedTemplate && ctx.story.workdir) {
29624
29670
  const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
@@ -29677,8 +29723,8 @@ var init_verify = __esm(() => {
29677
29723
  const result = await _verifyDeps.regression({
29678
29724
  workdir: effectiveWorkdir,
29679
29725
  command: effectiveCommand,
29680
- timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
29681
- acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
29726
+ timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
29727
+ acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
29682
29728
  });
29683
29729
  ctx.verifyResult = {
29684
29730
  success: result.success,
@@ -29695,7 +29741,7 @@ var init_verify = __esm(() => {
29695
29741
  };
29696
29742
  if (!result.success) {
29697
29743
  if (result.status === "TIMEOUT") {
29698
- const timeout = ctx.config.execution.verificationTimeoutSeconds;
29744
+ const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
29699
29745
  logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
29700
29746
  exitCode: result.status,
29701
29747
  storyId: ctx.story.id,
@@ -29710,10 +29756,13 @@ var init_verify = __esm(() => {
29710
29756
  if (result.status !== "TIMEOUT") {
29711
29757
  logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
29712
29758
  }
29713
- return {
29714
- action: "escalate",
29715
- reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${ctx.config.execution.verificationTimeoutSeconds}s (not a code failure)` : `Tests failed (exit code ${result.status ?? "non-zero"})`
29716
- };
29759
+ if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
29760
+ return {
29761
+ action: "escalate",
29762
+ 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"})`
29763
+ };
29764
+ }
29765
+ return { action: "continue" };
29717
29766
  }
29718
29767
  logger.info("verify", "Tests passed", { storyId: ctx.story.id });
29719
29768
  return { action: "continue" };
@@ -29721,7 +29770,6 @@ var init_verify = __esm(() => {
29721
29770
  };
29722
29771
  _verifyDeps = {
29723
29772
  regression,
29724
- loadConfigForWorkdir,
29725
29773
  readPackageName
29726
29774
  };
29727
29775
  });
@@ -29809,7 +29857,7 @@ __export(exports_init_context, {
29809
29857
  });
29810
29858
  import { existsSync as existsSync20 } from "fs";
29811
29859
  import { mkdir } from "fs/promises";
29812
- import { basename as basename2, join as join30 } from "path";
29860
+ import { basename as basename2, join as join31 } from "path";
29813
29861
  async function findFiles(dir, maxFiles = 200) {
29814
29862
  try {
29815
29863
  const proc = Bun.spawnSync([
@@ -29837,7 +29885,7 @@ async function findFiles(dir, maxFiles = 200) {
29837
29885
  return [];
29838
29886
  }
29839
29887
  async function readPackageManifest(projectRoot) {
29840
- const packageJsonPath = join30(projectRoot, "package.json");
29888
+ const packageJsonPath = join31(projectRoot, "package.json");
29841
29889
  if (!existsSync20(packageJsonPath)) {
29842
29890
  return null;
29843
29891
  }
@@ -29855,7 +29903,7 @@ async function readPackageManifest(projectRoot) {
29855
29903
  }
29856
29904
  }
29857
29905
  async function readReadmeSnippet(projectRoot) {
29858
- const readmePath = join30(projectRoot, "README.md");
29906
+ const readmePath = join31(projectRoot, "README.md");
29859
29907
  if (!existsSync20(readmePath)) {
29860
29908
  return null;
29861
29909
  }
@@ -29873,7 +29921,7 @@ async function detectEntryPoints(projectRoot) {
29873
29921
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
29874
29922
  const found = [];
29875
29923
  for (const candidate of candidates) {
29876
- const path12 = join30(projectRoot, candidate);
29924
+ const path12 = join31(projectRoot, candidate);
29877
29925
  if (existsSync20(path12)) {
29878
29926
  found.push(candidate);
29879
29927
  }
@@ -29884,7 +29932,7 @@ async function detectConfigFiles(projectRoot) {
29884
29932
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
29885
29933
  const found = [];
29886
29934
  for (const candidate of candidates) {
29887
- const path12 = join30(projectRoot, candidate);
29935
+ const path12 = join31(projectRoot, candidate);
29888
29936
  if (existsSync20(path12)) {
29889
29937
  found.push(candidate);
29890
29938
  }
@@ -30045,9 +30093,9 @@ function generatePackageContextTemplate(packagePath) {
30045
30093
  }
30046
30094
  async function initPackage(repoRoot, packagePath, force = false) {
30047
30095
  const logger = getLogger();
30048
- const packageDir = join30(repoRoot, packagePath);
30049
- const naxDir = join30(packageDir, "nax");
30050
- const contextPath = join30(naxDir, "context.md");
30096
+ const packageDir = join31(repoRoot, packagePath);
30097
+ const naxDir = join31(packageDir, "nax");
30098
+ const contextPath = join31(naxDir, "context.md");
30051
30099
  if (existsSync20(contextPath) && !force) {
30052
30100
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
30053
30101
  return;
@@ -30061,8 +30109,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
30061
30109
  }
30062
30110
  async function initContext(projectRoot, options = {}) {
30063
30111
  const logger = getLogger();
30064
- const naxDir = join30(projectRoot, "nax");
30065
- const contextPath = join30(naxDir, "context.md");
30112
+ const naxDir = join31(projectRoot, "nax");
30113
+ const contextPath = join31(naxDir, "context.md");
30066
30114
  if (existsSync20(contextPath) && !options.force) {
30067
30115
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
30068
30116
  return;
@@ -31370,19 +31418,19 @@ var init_precheck = __esm(() => {
31370
31418
  });
31371
31419
 
31372
31420
  // src/hooks/runner.ts
31373
- import { join as join43 } from "path";
31421
+ import { join as join44 } from "path";
31374
31422
  async function loadHooksConfig(projectDir, globalDir) {
31375
31423
  let globalHooks = { hooks: {} };
31376
31424
  let projectHooks = { hooks: {} };
31377
31425
  let skipGlobal = false;
31378
- const projectPath = join43(projectDir, "hooks.json");
31426
+ const projectPath = join44(projectDir, "hooks.json");
31379
31427
  const projectData = await loadJsonFile(projectPath, "hooks");
31380
31428
  if (projectData) {
31381
31429
  projectHooks = projectData;
31382
31430
  skipGlobal = projectData.skipGlobal ?? false;
31383
31431
  }
31384
31432
  if (!skipGlobal && globalDir) {
31385
- const globalPath = join43(globalDir, "hooks.json");
31433
+ const globalPath = join44(globalDir, "hooks.json");
31386
31434
  const globalData = await loadJsonFile(globalPath, "hooks");
31387
31435
  if (globalData) {
31388
31436
  globalHooks = globalData;
@@ -31873,6 +31921,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
31873
31921
  }), ctx.workdir);
31874
31922
  const fixContext = {
31875
31923
  config: ctx.config,
31924
+ effectiveConfig: ctx.config,
31876
31925
  prd,
31877
31926
  story,
31878
31927
  stories: [story],
@@ -31906,6 +31955,7 @@ async function runAcceptanceLoop(ctx) {
31906
31955
  const firstStory = prd.userStories[0];
31907
31956
  const acceptanceContext = {
31908
31957
  config: ctx.config,
31958
+ effectiveConfig: ctx.config,
31909
31959
  prd,
31910
31960
  story: firstStory,
31911
31961
  stories: [firstStory],
@@ -32414,12 +32464,12 @@ __export(exports_manager, {
32414
32464
  WorktreeManager: () => WorktreeManager
32415
32465
  });
32416
32466
  import { existsSync as existsSync32, symlinkSync } from "fs";
32417
- import { join as join44 } from "path";
32467
+ import { join as join45 } from "path";
32418
32468
 
32419
32469
  class WorktreeManager {
32420
32470
  async create(projectRoot, storyId) {
32421
32471
  validateStoryId(storyId);
32422
- const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32472
+ const worktreePath = join45(projectRoot, ".nax-wt", storyId);
32423
32473
  const branchName = `nax/${storyId}`;
32424
32474
  try {
32425
32475
  const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
@@ -32444,9 +32494,9 @@ class WorktreeManager {
32444
32494
  }
32445
32495
  throw new Error(`Failed to create worktree: ${String(error48)}`);
32446
32496
  }
32447
- const nodeModulesSource = join44(projectRoot, "node_modules");
32497
+ const nodeModulesSource = join45(projectRoot, "node_modules");
32448
32498
  if (existsSync32(nodeModulesSource)) {
32449
- const nodeModulesTarget = join44(worktreePath, "node_modules");
32499
+ const nodeModulesTarget = join45(worktreePath, "node_modules");
32450
32500
  try {
32451
32501
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
32452
32502
  } catch (error48) {
@@ -32454,9 +32504,9 @@ class WorktreeManager {
32454
32504
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
32455
32505
  }
32456
32506
  }
32457
- const envSource = join44(projectRoot, ".env");
32507
+ const envSource = join45(projectRoot, ".env");
32458
32508
  if (existsSync32(envSource)) {
32459
- const envTarget = join44(worktreePath, ".env");
32509
+ const envTarget = join45(worktreePath, ".env");
32460
32510
  try {
32461
32511
  symlinkSync(envSource, envTarget, "file");
32462
32512
  } catch (error48) {
@@ -32467,7 +32517,7 @@ class WorktreeManager {
32467
32517
  }
32468
32518
  async remove(projectRoot, storyId) {
32469
32519
  validateStoryId(storyId);
32470
- const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32520
+ const worktreePath = join45(projectRoot, ".nax-wt", storyId);
32471
32521
  const branchName = `nax/${storyId}`;
32472
32522
  try {
32473
32523
  const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -32777,6 +32827,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
32777
32827
  try {
32778
32828
  const pipelineContext = {
32779
32829
  ...context,
32830
+ effectiveConfig: context.effectiveConfig ?? context.config,
32780
32831
  story,
32781
32832
  stories: [story],
32782
32833
  workdir: worktreePath,
@@ -32857,7 +32908,7 @@ var init_parallel_worker = __esm(() => {
32857
32908
 
32858
32909
  // src/execution/parallel-coordinator.ts
32859
32910
  import os3 from "os";
32860
- import { join as join45 } from "path";
32911
+ import { join as join46 } from "path";
32861
32912
  function groupStoriesByDependencies(stories) {
32862
32913
  const batches = [];
32863
32914
  const processed = new Set;
@@ -32926,6 +32977,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32926
32977
  });
32927
32978
  const baseContext = {
32928
32979
  config: config2,
32980
+ effectiveConfig: config2,
32929
32981
  prd: currentPrd,
32930
32982
  featureDir,
32931
32983
  hooks,
@@ -32935,7 +32987,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32935
32987
  };
32936
32988
  const worktreePaths = new Map;
32937
32989
  for (const story of batch) {
32938
- const worktreePath = join45(projectRoot, ".nax-wt", story.id);
32990
+ const worktreePath = join46(projectRoot, ".nax-wt", story.id);
32939
32991
  try {
32940
32992
  await worktreeManager.create(projectRoot, story.id);
32941
32993
  worktreePaths.set(story.id, worktreePath);
@@ -32984,7 +33036,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32984
33036
  });
32985
33037
  logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
32986
33038
  storyId: mergeResult.storyId,
32987
- worktreePath: join45(projectRoot, ".nax-wt", mergeResult.storyId)
33039
+ worktreePath: join46(projectRoot, ".nax-wt", mergeResult.storyId)
32988
33040
  });
32989
33041
  }
32990
33042
  }
@@ -33063,6 +33115,7 @@ async function rectifyConflictedStory(options) {
33063
33115
  const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
33064
33116
  const pipelineContext = {
33065
33117
  config: config2,
33118
+ effectiveConfig: config2,
33066
33119
  prd,
33067
33120
  story,
33068
33121
  stories: [story],
@@ -33443,12 +33496,12 @@ var init_parallel_executor = __esm(() => {
33443
33496
  // src/pipeline/subscribers/events-writer.ts
33444
33497
  import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
33445
33498
  import { homedir as homedir7 } from "os";
33446
- import { basename as basename5, join as join46 } from "path";
33499
+ import { basename as basename5, join as join47 } from "path";
33447
33500
  function wireEventsWriter(bus, feature, runId, workdir) {
33448
33501
  const logger = getSafeLogger();
33449
33502
  const project = basename5(workdir);
33450
- const eventsDir = join46(homedir7(), ".nax", "events", project);
33451
- const eventsFile = join46(eventsDir, "events.jsonl");
33503
+ const eventsDir = join47(homedir7(), ".nax", "events", project);
33504
+ const eventsFile = join47(eventsDir, "events.jsonl");
33452
33505
  let dirReady = false;
33453
33506
  const write = (line) => {
33454
33507
  (async () => {
@@ -33608,12 +33661,12 @@ var init_interaction2 = __esm(() => {
33608
33661
  // src/pipeline/subscribers/registry.ts
33609
33662
  import { mkdir as mkdir3, writeFile } from "fs/promises";
33610
33663
  import { homedir as homedir8 } from "os";
33611
- import { basename as basename6, join as join47 } from "path";
33664
+ import { basename as basename6, join as join48 } from "path";
33612
33665
  function wireRegistry(bus, feature, runId, workdir) {
33613
33666
  const logger = getSafeLogger();
33614
33667
  const project = basename6(workdir);
33615
- const runDir = join47(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33616
- const metaFile = join47(runDir, "meta.json");
33668
+ const runDir = join48(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33669
+ const metaFile = join48(runDir, "meta.json");
33617
33670
  const unsub = bus.on("run:started", (_ev) => {
33618
33671
  (async () => {
33619
33672
  try {
@@ -33623,8 +33676,8 @@ function wireRegistry(bus, feature, runId, workdir) {
33623
33676
  project,
33624
33677
  feature,
33625
33678
  workdir,
33626
- statusPath: join47(workdir, "nax", "features", feature, "status.json"),
33627
- eventsDir: join47(workdir, "nax", "features", feature, "runs"),
33679
+ statusPath: join48(workdir, "nax", "features", feature, "status.json"),
33680
+ eventsDir: join48(workdir, "nax", "features", feature, "runs"),
33628
33681
  registeredAt: new Date().toISOString()
33629
33682
  };
33630
33683
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -34253,6 +34306,7 @@ var init_pipeline_result_handler = __esm(() => {
34253
34306
  });
34254
34307
 
34255
34308
  // src/execution/iteration-runner.ts
34309
+ import { join as join49 } from "path";
34256
34310
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
34257
34311
  const logger = getSafeLogger();
34258
34312
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -34278,8 +34332,10 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34278
34332
  const storyStartTime = Date.now();
34279
34333
  const storyGitRef = await captureGitRef(ctx.workdir);
34280
34334
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
34335
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, "nax", "config.json"), story.workdir) : ctx.config;
34281
34336
  const pipelineContext = {
34282
34337
  config: ctx.config,
34338
+ effectiveConfig,
34283
34339
  prd,
34284
34340
  story,
34285
34341
  stories: storiesToExecute,
@@ -34350,13 +34406,18 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34350
34406
  reason: pipelineResult.reason
34351
34407
  };
34352
34408
  }
34409
+ var _iterationRunnerDeps;
34353
34410
  var init_iteration_runner = __esm(() => {
34411
+ init_loader2();
34354
34412
  init_logger2();
34355
34413
  init_runner();
34356
34414
  init_stages();
34357
34415
  init_git();
34358
34416
  init_dry_run();
34359
34417
  init_pipeline_result_handler();
34418
+ _iterationRunnerDeps = {
34419
+ loadConfigForWorkdir
34420
+ };
34360
34421
  });
34361
34422
 
34362
34423
  // src/execution/executor-types.ts
@@ -34449,6 +34510,7 @@ async function executeSequential(ctx, initialPrd) {
34449
34510
  logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
34450
34511
  const preRunCtx = {
34451
34512
  config: ctx.config,
34513
+ effectiveConfig: ctx.config,
34452
34514
  prd,
34453
34515
  workdir: ctx.workdir,
34454
34516
  featureDir: ctx.featureDir,
@@ -34620,7 +34682,7 @@ async function writeStatusFile(filePath, status) {
34620
34682
  var init_status_file = () => {};
34621
34683
 
34622
34684
  // src/execution/status-writer.ts
34623
- import { join as join48 } from "path";
34685
+ import { join as join50 } from "path";
34624
34686
 
34625
34687
  class StatusWriter {
34626
34688
  statusFile;
@@ -34688,7 +34750,7 @@ class StatusWriter {
34688
34750
  if (!this._prd)
34689
34751
  return;
34690
34752
  const safeLogger = getSafeLogger();
34691
- const featureStatusPath = join48(featureDir, "status.json");
34753
+ const featureStatusPath = join50(featureDir, "status.json");
34692
34754
  try {
34693
34755
  const base = this.getSnapshot(totalCost, iterations);
34694
34756
  if (!base) {
@@ -66017,7 +66079,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
66017
66079
  init_source();
66018
66080
  import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
66019
66081
  import { homedir as homedir10 } from "os";
66020
- import { join as join49 } from "path";
66082
+ import { join as join51 } from "path";
66021
66083
 
66022
66084
  // node_modules/commander/esm.mjs
66023
66085
  var import__ = __toESM(require_commander(), 1);
@@ -68086,7 +68148,7 @@ async function runsShowCommand(options) {
68086
68148
  // src/cli/prompts-main.ts
68087
68149
  init_logger2();
68088
68150
  import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
68089
- import { join as join28 } from "path";
68151
+ import { join as join29 } from "path";
68090
68152
 
68091
68153
  // src/pipeline/index.ts
68092
68154
  init_runner();
@@ -68122,7 +68184,7 @@ init_prd();
68122
68184
 
68123
68185
  // src/cli/prompts-tdd.ts
68124
68186
  init_prompts2();
68125
- import { join as join27 } from "path";
68187
+ import { join as join28 } from "path";
68126
68188
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
68127
68189
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
68128
68190
  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(),
@@ -68141,7 +68203,7 @@ ${frontmatter}---
68141
68203
 
68142
68204
  ${session.prompt}`;
68143
68205
  if (outputDir) {
68144
- const promptFile = join27(outputDir, `${story.id}.${session.role}.md`);
68206
+ const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
68145
68207
  await Bun.write(promptFile, fullOutput);
68146
68208
  logger.info("cli", "Written TDD prompt file", {
68147
68209
  storyId: story.id,
@@ -68157,7 +68219,7 @@ ${"=".repeat(80)}`);
68157
68219
  }
68158
68220
  }
68159
68221
  if (outputDir && ctx.contextMarkdown) {
68160
- const contextFile = join27(outputDir, `${story.id}.context.md`);
68222
+ const contextFile = join28(outputDir, `${story.id}.context.md`);
68161
68223
  const frontmatter = buildFrontmatter(story, ctx);
68162
68224
  const contextOutput = `---
68163
68225
  ${frontmatter}---
@@ -68171,12 +68233,12 @@ ${ctx.contextMarkdown}`;
68171
68233
  async function promptsCommand(options) {
68172
68234
  const logger = getLogger();
68173
68235
  const { feature, workdir, config: config2, storyId, outputDir } = options;
68174
- const naxDir = join28(workdir, "nax");
68236
+ const naxDir = join29(workdir, "nax");
68175
68237
  if (!existsSync18(naxDir)) {
68176
68238
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
68177
68239
  }
68178
- const featureDir = join28(naxDir, "features", feature);
68179
- const prdPath = join28(featureDir, "prd.json");
68240
+ const featureDir = join29(naxDir, "features", feature);
68241
+ const prdPath = join29(featureDir, "prd.json");
68180
68242
  if (!existsSync18(prdPath)) {
68181
68243
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
68182
68244
  }
@@ -68198,6 +68260,7 @@ async function promptsCommand(options) {
68198
68260
  for (const story of stories) {
68199
68261
  const ctx = {
68200
68262
  config: config2,
68263
+ effectiveConfig: config2,
68201
68264
  prd,
68202
68265
  story,
68203
68266
  stories: [story],
@@ -68236,10 +68299,10 @@ ${frontmatter}---
68236
68299
 
68237
68300
  ${ctx.prompt}`;
68238
68301
  if (outputDir) {
68239
- const promptFile = join28(outputDir, `${story.id}.prompt.md`);
68302
+ const promptFile = join29(outputDir, `${story.id}.prompt.md`);
68240
68303
  await Bun.write(promptFile, fullOutput);
68241
68304
  if (ctx.contextMarkdown) {
68242
- const contextFile = join28(outputDir, `${story.id}.context.md`);
68305
+ const contextFile = join29(outputDir, `${story.id}.context.md`);
68243
68306
  const contextOutput = `---
68244
68307
  ${frontmatter}---
68245
68308
 
@@ -68303,7 +68366,7 @@ function buildFrontmatter(story, ctx, role) {
68303
68366
  }
68304
68367
  // src/cli/prompts-init.ts
68305
68368
  import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
68306
- import { join as join29 } from "path";
68369
+ import { join as join30 } from "path";
68307
68370
  var TEMPLATE_ROLES = [
68308
68371
  { file: "test-writer.md", role: "test-writer" },
68309
68372
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -68327,9 +68390,9 @@ var TEMPLATE_HEADER = `<!--
68327
68390
  `;
68328
68391
  async function promptsInitCommand(options) {
68329
68392
  const { workdir, force = false, autoWireConfig = true } = options;
68330
- const templatesDir = join29(workdir, "nax", "templates");
68393
+ const templatesDir = join30(workdir, "nax", "templates");
68331
68394
  mkdirSync4(templatesDir, { recursive: true });
68332
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join29(templatesDir, f)));
68395
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join30(templatesDir, f)));
68333
68396
  if (existingFiles.length > 0 && !force) {
68334
68397
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
68335
68398
  Pass --force to overwrite existing templates.`);
@@ -68337,7 +68400,7 @@ async function promptsInitCommand(options) {
68337
68400
  }
68338
68401
  const written = [];
68339
68402
  for (const template of TEMPLATE_ROLES) {
68340
- const filePath = join29(templatesDir, template.file);
68403
+ const filePath = join30(templatesDir, template.file);
68341
68404
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
68342
68405
  const content = TEMPLATE_HEADER + roleBody;
68343
68406
  await Bun.write(filePath, content);
@@ -68353,7 +68416,7 @@ async function promptsInitCommand(options) {
68353
68416
  return written;
68354
68417
  }
68355
68418
  async function autoWirePromptsConfig(workdir) {
68356
- const configPath = join29(workdir, "nax.config.json");
68419
+ const configPath = join30(workdir, "nax.config.json");
68357
68420
  if (!existsSync19(configPath)) {
68358
68421
  const exampleConfig = JSON.stringify({
68359
68422
  prompts: {
@@ -68518,7 +68581,7 @@ init_config();
68518
68581
  init_logger2();
68519
68582
  init_prd();
68520
68583
  import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
68521
- import { join as join33 } from "path";
68584
+ import { join as join34 } from "path";
68522
68585
 
68523
68586
  // src/cli/diagnose-analysis.ts
68524
68587
  function detectFailurePattern(story, prd, status) {
@@ -68717,7 +68780,7 @@ function isProcessAlive2(pid) {
68717
68780
  }
68718
68781
  }
68719
68782
  async function loadStatusFile2(workdir) {
68720
- const statusPath = join33(workdir, "nax", "status.json");
68783
+ const statusPath = join34(workdir, "nax", "status.json");
68721
68784
  if (!existsSync21(statusPath))
68722
68785
  return null;
68723
68786
  try {
@@ -68745,7 +68808,7 @@ async function countCommitsSince(workdir, since) {
68745
68808
  }
68746
68809
  }
68747
68810
  async function checkLock(workdir) {
68748
- const lockFile = Bun.file(join33(workdir, "nax.lock"));
68811
+ const lockFile = Bun.file(join34(workdir, "nax.lock"));
68749
68812
  if (!await lockFile.exists())
68750
68813
  return { lockPresent: false };
68751
68814
  try {
@@ -68763,8 +68826,8 @@ async function diagnoseCommand(options = {}) {
68763
68826
  const logger = getLogger();
68764
68827
  const workdir = options.workdir ?? process.cwd();
68765
68828
  const naxSubdir = findProjectDir(workdir);
68766
- let projectDir = naxSubdir ? join33(naxSubdir, "..") : null;
68767
- if (!projectDir && existsSync21(join33(workdir, "nax"))) {
68829
+ let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
68830
+ if (!projectDir && existsSync21(join34(workdir, "nax"))) {
68768
68831
  projectDir = workdir;
68769
68832
  }
68770
68833
  if (!projectDir)
@@ -68775,7 +68838,7 @@ async function diagnoseCommand(options = {}) {
68775
68838
  if (status2) {
68776
68839
  feature = status2.run.feature;
68777
68840
  } else {
68778
- const featuresDir = join33(projectDir, "nax", "features");
68841
+ const featuresDir = join34(projectDir, "nax", "features");
68779
68842
  if (!existsSync21(featuresDir))
68780
68843
  throw new Error("No features found in project");
68781
68844
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -68785,8 +68848,8 @@ async function diagnoseCommand(options = {}) {
68785
68848
  logger.info("diagnose", "No feature specified, using first found", { feature });
68786
68849
  }
68787
68850
  }
68788
- const featureDir = join33(projectDir, "nax", "features", feature);
68789
- const prdPath = join33(featureDir, "prd.json");
68851
+ const featureDir = join34(projectDir, "nax", "features", feature);
68852
+ const prdPath = join34(featureDir, "prd.json");
68790
68853
  if (!existsSync21(prdPath))
68791
68854
  throw new Error(`Feature not found: ${feature}`);
68792
68855
  const prd = await loadPRD(prdPath);
@@ -68829,7 +68892,7 @@ init_interaction();
68829
68892
  init_source();
68830
68893
  init_loader2();
68831
68894
  import { existsSync as existsSync22 } from "fs";
68832
- import { join as join34 } from "path";
68895
+ import { join as join35 } from "path";
68833
68896
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68834
68897
  async function generateCommand(options) {
68835
68898
  const workdir = process.cwd();
@@ -68872,7 +68935,7 @@ async function generateCommand(options) {
68872
68935
  return;
68873
68936
  }
68874
68937
  if (options.package) {
68875
- const packageDir = join34(workdir, options.package);
68938
+ const packageDir = join35(workdir, options.package);
68876
68939
  if (dryRun) {
68877
68940
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
68878
68941
  }
@@ -68892,8 +68955,8 @@ async function generateCommand(options) {
68892
68955
  process.exit(1);
68893
68956
  return;
68894
68957
  }
68895
- const contextPath = options.context ? join34(workdir, options.context) : join34(workdir, "nax/context.md");
68896
- const outputDir = options.output ? join34(workdir, options.output) : workdir;
68958
+ const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, "nax/context.md");
68959
+ const outputDir = options.output ? join35(workdir, options.output) : workdir;
68897
68960
  const autoInject = !options.noAutoInject;
68898
68961
  if (!existsSync22(contextPath)) {
68899
68962
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -68998,7 +69061,7 @@ async function generateCommand(options) {
68998
69061
  // src/cli/config-display.ts
68999
69062
  init_loader2();
69000
69063
  import { existsSync as existsSync24 } from "fs";
69001
- import { join as join36 } from "path";
69064
+ import { join as join37 } from "path";
69002
69065
 
69003
69066
  // src/cli/config-descriptions.ts
69004
69067
  var FIELD_DESCRIPTIONS = {
@@ -69207,7 +69270,7 @@ function deepEqual(a, b) {
69207
69270
  init_defaults();
69208
69271
  init_loader2();
69209
69272
  import { existsSync as existsSync23 } from "fs";
69210
- import { join as join35 } from "path";
69273
+ import { join as join36 } from "path";
69211
69274
  async function loadConfigFile(path14) {
69212
69275
  if (!existsSync23(path14))
69213
69276
  return null;
@@ -69229,7 +69292,7 @@ async function loadProjectConfig() {
69229
69292
  const projectDir = findProjectDir();
69230
69293
  if (!projectDir)
69231
69294
  return null;
69232
- const projectPath = join35(projectDir, "config.json");
69295
+ const projectPath = join36(projectDir, "config.json");
69233
69296
  return await loadConfigFile(projectPath);
69234
69297
  }
69235
69298
 
@@ -69289,7 +69352,7 @@ async function configCommand(config2, options = {}) {
69289
69352
  function determineConfigSources() {
69290
69353
  const globalPath = globalConfigPath();
69291
69354
  const projectDir = findProjectDir();
69292
- const projectPath = projectDir ? join36(projectDir, "config.json") : null;
69355
+ const projectPath = projectDir ? join37(projectDir, "config.json") : null;
69293
69356
  return {
69294
69357
  global: fileExists(globalPath) ? globalPath : null,
69295
69358
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -69469,21 +69532,21 @@ async function diagnose(options) {
69469
69532
 
69470
69533
  // src/commands/logs.ts
69471
69534
  import { existsSync as existsSync26 } from "fs";
69472
- import { join as join39 } from "path";
69535
+ import { join as join40 } from "path";
69473
69536
 
69474
69537
  // src/commands/logs-formatter.ts
69475
69538
  init_source();
69476
69539
  init_formatter();
69477
69540
  import { readdirSync as readdirSync7 } from "fs";
69478
- import { join as join38 } from "path";
69541
+ import { join as join39 } from "path";
69479
69542
 
69480
69543
  // src/commands/logs-reader.ts
69481
69544
  import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
69482
69545
  import { readdir as readdir3 } from "fs/promises";
69483
69546
  import { homedir as homedir5 } from "os";
69484
- import { join as join37 } from "path";
69547
+ import { join as join38 } from "path";
69485
69548
  var _deps7 = {
69486
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join37(homedir5(), ".nax", "runs")
69549
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
69487
69550
  };
69488
69551
  async function resolveRunFileFromRegistry(runId) {
69489
69552
  const runsDir = _deps7.getRunsDir();
@@ -69495,7 +69558,7 @@ async function resolveRunFileFromRegistry(runId) {
69495
69558
  }
69496
69559
  let matched = null;
69497
69560
  for (const entry of entries) {
69498
- const metaPath = join37(runsDir, entry, "meta.json");
69561
+ const metaPath = join38(runsDir, entry, "meta.json");
69499
69562
  try {
69500
69563
  const meta3 = await Bun.file(metaPath).json();
69501
69564
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -69517,14 +69580,14 @@ async function resolveRunFileFromRegistry(runId) {
69517
69580
  return null;
69518
69581
  }
69519
69582
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
69520
- return join37(matched.eventsDir, specificFile ?? files[0]);
69583
+ return join38(matched.eventsDir, specificFile ?? files[0]);
69521
69584
  }
69522
69585
  async function selectRunFile(runsDir) {
69523
69586
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
69524
69587
  if (files.length === 0) {
69525
69588
  return null;
69526
69589
  }
69527
- return join37(runsDir, files[0]);
69590
+ return join38(runsDir, files[0]);
69528
69591
  }
69529
69592
  async function extractRunSummary(filePath) {
69530
69593
  const file2 = Bun.file(filePath);
@@ -69609,7 +69672,7 @@ Runs:
69609
69672
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
69610
69673
  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"));
69611
69674
  for (const file2 of files) {
69612
- const filePath = join38(runsDir, file2);
69675
+ const filePath = join39(runsDir, file2);
69613
69676
  const summary = await extractRunSummary(filePath);
69614
69677
  const timestamp = file2.replace(".jsonl", "");
69615
69678
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -69734,7 +69797,7 @@ async function logsCommand(options) {
69734
69797
  return;
69735
69798
  }
69736
69799
  const resolved = resolveProject({ dir: options.dir });
69737
- const naxDir = join39(resolved.projectDir, "nax");
69800
+ const naxDir = join40(resolved.projectDir, "nax");
69738
69801
  const configPath = resolved.configPath;
69739
69802
  const configFile = Bun.file(configPath);
69740
69803
  const config2 = await configFile.json();
@@ -69742,8 +69805,8 @@ async function logsCommand(options) {
69742
69805
  if (!featureName) {
69743
69806
  throw new Error("No feature specified in config.json");
69744
69807
  }
69745
- const featureDir = join39(naxDir, "features", featureName);
69746
- const runsDir = join39(featureDir, "runs");
69808
+ const featureDir = join40(naxDir, "features", featureName);
69809
+ const runsDir = join40(featureDir, "runs");
69747
69810
  if (!existsSync26(runsDir)) {
69748
69811
  throw new Error(`No runs directory found for feature: ${featureName}`);
69749
69812
  }
@@ -69768,7 +69831,7 @@ init_config();
69768
69831
  init_prd();
69769
69832
  init_precheck();
69770
69833
  import { existsSync as existsSync31 } from "fs";
69771
- import { join as join40 } from "path";
69834
+ import { join as join41 } from "path";
69772
69835
  async function precheckCommand(options) {
69773
69836
  const resolved = resolveProject({
69774
69837
  dir: options.dir,
@@ -69784,9 +69847,9 @@ async function precheckCommand(options) {
69784
69847
  process.exit(1);
69785
69848
  }
69786
69849
  }
69787
- const naxDir = join40(resolved.projectDir, "nax");
69788
- const featureDir = join40(naxDir, "features", featureName);
69789
- const prdPath = join40(featureDir, "prd.json");
69850
+ const naxDir = join41(resolved.projectDir, "nax");
69851
+ const featureDir = join41(naxDir, "features", featureName);
69852
+ const prdPath = join41(featureDir, "prd.json");
69790
69853
  if (!existsSync31(featureDir)) {
69791
69854
  console.error(source_default.red(`Feature not found: ${featureName}`));
69792
69855
  process.exit(1);
@@ -69810,10 +69873,10 @@ async function precheckCommand(options) {
69810
69873
  init_source();
69811
69874
  import { readdir as readdir4 } from "fs/promises";
69812
69875
  import { homedir as homedir6 } from "os";
69813
- import { join as join41 } from "path";
69876
+ import { join as join42 } from "path";
69814
69877
  var DEFAULT_LIMIT = 20;
69815
69878
  var _deps9 = {
69816
- getRunsDir: () => join41(homedir6(), ".nax", "runs")
69879
+ getRunsDir: () => join42(homedir6(), ".nax", "runs")
69817
69880
  };
69818
69881
  function formatDuration3(ms) {
69819
69882
  if (ms <= 0)
@@ -69865,7 +69928,7 @@ async function runsCommand(options = {}) {
69865
69928
  }
69866
69929
  const rows = [];
69867
69930
  for (const entry of entries) {
69868
- const metaPath = join41(runsDir, entry, "meta.json");
69931
+ const metaPath = join42(runsDir, entry, "meta.json");
69869
69932
  let meta3;
69870
69933
  try {
69871
69934
  meta3 = await Bun.file(metaPath).json();
@@ -69942,7 +70005,7 @@ async function runsCommand(options = {}) {
69942
70005
 
69943
70006
  // src/commands/unlock.ts
69944
70007
  init_source();
69945
- import { join as join42 } from "path";
70008
+ import { join as join43 } from "path";
69946
70009
  function isProcessAlive3(pid) {
69947
70010
  try {
69948
70011
  process.kill(pid, 0);
@@ -69957,7 +70020,7 @@ function formatLockAge(ageMs) {
69957
70020
  }
69958
70021
  async function unlockCommand(options) {
69959
70022
  const workdir = options.dir ?? process.cwd();
69960
- const lockPath = join42(workdir, "nax.lock");
70023
+ const lockPath = join43(workdir, "nax.lock");
69961
70024
  const lockFile = Bun.file(lockPath);
69962
70025
  const exists = await lockFile.exists();
69963
70026
  if (!exists) {
@@ -77792,15 +77855,15 @@ Next: nax generate --package ${options.package}`));
77792
77855
  }
77793
77856
  return;
77794
77857
  }
77795
- const naxDir = join49(workdir, "nax");
77858
+ const naxDir = join51(workdir, "nax");
77796
77859
  if (existsSync34(naxDir) && !options.force) {
77797
77860
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
77798
77861
  return;
77799
77862
  }
77800
- mkdirSync6(join49(naxDir, "features"), { recursive: true });
77801
- mkdirSync6(join49(naxDir, "hooks"), { recursive: true });
77802
- await Bun.write(join49(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77803
- await Bun.write(join49(naxDir, "hooks.json"), JSON.stringify({
77863
+ mkdirSync6(join51(naxDir, "features"), { recursive: true });
77864
+ mkdirSync6(join51(naxDir, "hooks"), { recursive: true });
77865
+ await Bun.write(join51(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77866
+ await Bun.write(join51(naxDir, "hooks.json"), JSON.stringify({
77804
77867
  hooks: {
77805
77868
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
77806
77869
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -77808,12 +77871,12 @@ Next: nax generate --package ${options.package}`));
77808
77871
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
77809
77872
  }
77810
77873
  }, null, 2));
77811
- await Bun.write(join49(naxDir, ".gitignore"), `# nax temp files
77874
+ await Bun.write(join51(naxDir, ".gitignore"), `# nax temp files
77812
77875
  *.tmp
77813
77876
  .paused.json
77814
77877
  .nax-verifier-verdict.json
77815
77878
  `);
77816
- await Bun.write(join49(naxDir, "context.md"), `# Project Context
77879
+ await Bun.write(join51(naxDir, "context.md"), `# Project Context
77817
77880
 
77818
77881
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
77819
77882
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -77939,8 +78002,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
77939
78002
  console.error(source_default.red("nax not initialized. Run: nax init"));
77940
78003
  process.exit(1);
77941
78004
  }
77942
- const featureDir = join49(naxDir, "features", options.feature);
77943
- const prdPath = join49(featureDir, "prd.json");
78005
+ const featureDir = join51(naxDir, "features", options.feature);
78006
+ const prdPath = join51(featureDir, "prd.json");
77944
78007
  if (options.plan && options.from) {
77945
78008
  if (existsSync34(prdPath) && !options.force) {
77946
78009
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -77962,10 +78025,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77962
78025
  }
77963
78026
  }
77964
78027
  try {
77965
- const planLogDir = join49(featureDir, "plan");
78028
+ const planLogDir = join51(featureDir, "plan");
77966
78029
  mkdirSync6(planLogDir, { recursive: true });
77967
78030
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77968
- const planLogPath = join49(planLogDir, `${planLogId}.jsonl`);
78031
+ const planLogPath = join51(planLogDir, `${planLogId}.jsonl`);
77969
78032
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77970
78033
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77971
78034
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -78003,10 +78066,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
78003
78066
  process.exit(1);
78004
78067
  }
78005
78068
  resetLogger();
78006
- const runsDir = join49(featureDir, "runs");
78069
+ const runsDir = join51(featureDir, "runs");
78007
78070
  mkdirSync6(runsDir, { recursive: true });
78008
78071
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78009
- const logFilePath = join49(runsDir, `${runId}.jsonl`);
78072
+ const logFilePath = join51(runsDir, `${runId}.jsonl`);
78010
78073
  const isTTY = process.stdout.isTTY ?? false;
78011
78074
  const headlessFlag = options.headless ?? false;
78012
78075
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -78022,7 +78085,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78022
78085
  config2.autoMode.defaultAgent = options.agent;
78023
78086
  }
78024
78087
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
78025
- const globalNaxDir = join49(homedir10(), ".nax");
78088
+ const globalNaxDir = join51(homedir10(), ".nax");
78026
78089
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
78027
78090
  const eventEmitter = new PipelineEventEmitter;
78028
78091
  let tuiInstance;
@@ -78045,7 +78108,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78045
78108
  } else {
78046
78109
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
78047
78110
  }
78048
- const statusFilePath = join49(workdir, "nax", "status.json");
78111
+ const statusFilePath = join51(workdir, "nax", "status.json");
78049
78112
  let parallel;
78050
78113
  if (options.parallel !== undefined) {
78051
78114
  parallel = Number.parseInt(options.parallel, 10);
@@ -78071,7 +78134,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78071
78134
  headless: useHeadless,
78072
78135
  skipPrecheck: options.skipPrecheck ?? false
78073
78136
  });
78074
- const latestSymlink = join49(runsDir, "latest.jsonl");
78137
+ const latestSymlink = join51(runsDir, "latest.jsonl");
78075
78138
  try {
78076
78139
  if (existsSync34(latestSymlink)) {
78077
78140
  Bun.spawnSync(["rm", latestSymlink]);
@@ -78109,9 +78172,9 @@ features.command("create <name>").description("Create a new feature").option("-d
78109
78172
  console.error(source_default.red("nax not initialized. Run: nax init"));
78110
78173
  process.exit(1);
78111
78174
  }
78112
- const featureDir = join49(naxDir, "features", name);
78175
+ const featureDir = join51(naxDir, "features", name);
78113
78176
  mkdirSync6(featureDir, { recursive: true });
78114
- await Bun.write(join49(featureDir, "spec.md"), `# Feature: ${name}
78177
+ await Bun.write(join51(featureDir, "spec.md"), `# Feature: ${name}
78115
78178
 
78116
78179
  ## Overview
78117
78180
 
@@ -78119,7 +78182,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78119
78182
 
78120
78183
  ## Acceptance Criteria
78121
78184
  `);
78122
- await Bun.write(join49(featureDir, "plan.md"), `# Plan: ${name}
78185
+ await Bun.write(join51(featureDir, "plan.md"), `# Plan: ${name}
78123
78186
 
78124
78187
  ## Architecture
78125
78188
 
@@ -78127,7 +78190,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78127
78190
 
78128
78191
  ## Dependencies
78129
78192
  `);
78130
- await Bun.write(join49(featureDir, "tasks.md"), `# Tasks: ${name}
78193
+ await Bun.write(join51(featureDir, "tasks.md"), `# Tasks: ${name}
78131
78194
 
78132
78195
  ## US-001: [Title]
78133
78196
 
@@ -78136,7 +78199,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78136
78199
  ### Acceptance Criteria
78137
78200
  - [ ] Criterion 1
78138
78201
  `);
78139
- await Bun.write(join49(featureDir, "progress.txt"), `# Progress: ${name}
78202
+ await Bun.write(join51(featureDir, "progress.txt"), `# Progress: ${name}
78140
78203
 
78141
78204
  Created: ${new Date().toISOString()}
78142
78205
 
@@ -78164,7 +78227,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78164
78227
  console.error(source_default.red("nax not initialized."));
78165
78228
  process.exit(1);
78166
78229
  }
78167
- const featuresDir = join49(naxDir, "features");
78230
+ const featuresDir = join51(naxDir, "features");
78168
78231
  if (!existsSync34(featuresDir)) {
78169
78232
  console.log(source_default.dim("No features yet."));
78170
78233
  return;
@@ -78179,7 +78242,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78179
78242
  Features:
78180
78243
  `));
78181
78244
  for (const name of entries) {
78182
- const prdPath = join49(featuresDir, name, "prd.json");
78245
+ const prdPath = join51(featuresDir, name, "prd.json");
78183
78246
  if (existsSync34(prdPath)) {
78184
78247
  const prd = await loadPRD(prdPath);
78185
78248
  const c = countStories(prd);
@@ -78210,10 +78273,10 @@ Use: nax plan -f <feature> --from <spec>`));
78210
78273
  process.exit(1);
78211
78274
  }
78212
78275
  const config2 = await loadConfig(workdir);
78213
- const featureLogDir = join49(naxDir, "features", options.feature, "plan");
78276
+ const featureLogDir = join51(naxDir, "features", options.feature, "plan");
78214
78277
  mkdirSync6(featureLogDir, { recursive: true });
78215
78278
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78216
- const planLogPath = join49(featureLogDir, `${planLogId}.jsonl`);
78279
+ const planLogPath = join51(featureLogDir, `${planLogId}.jsonl`);
78217
78280
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
78218
78281
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
78219
78282
  try {
@@ -78250,7 +78313,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78250
78313
  console.error(source_default.red("nax not initialized. Run: nax init"));
78251
78314
  process.exit(1);
78252
78315
  }
78253
- const featureDir = join49(naxDir, "features", options.feature);
78316
+ const featureDir = join51(naxDir, "features", options.feature);
78254
78317
  if (!existsSync34(featureDir)) {
78255
78318
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
78256
78319
  process.exit(1);
@@ -78266,7 +78329,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78266
78329
  specPath: options.from,
78267
78330
  reclassify: options.reclassify
78268
78331
  });
78269
- const prdPath = join49(featureDir, "prd.json");
78332
+ const prdPath = join51(featureDir, "prd.json");
78270
78333
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
78271
78334
  const c = countStories(prd);
78272
78335
  console.log(source_default.green(`