@nathapp/nax 0.48.3 → 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.3",
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("fa27043"))
22287
- return "fa27043";
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) {
@@ -24397,8 +24430,23 @@ async function runReview(config2, workdir, executionConfig) {
24397
24430
  const checks3 = [];
24398
24431
  let firstFailure;
24399
24432
  const allUncommittedFiles = await _deps4.getUncommittedFiles(workdir);
24400
- const NAX_RUNTIME_FILES = new Set(["nax/status.json", ".nax-verifier-verdict.json"]);
24401
- const uncommittedFiles = allUncommittedFiles.filter((f) => !NAX_RUNTIME_FILES.has(f) && !f.match(/^nax\/features\/.+\/prd\.json$/));
24433
+ const NAX_RUNTIME_PATTERNS = [
24434
+ /nax\.lock$/,
24435
+ /nax\/metrics\.json$/,
24436
+ /nax\/status\.json$/,
24437
+ /nax\/features\/[^/]+\/status\.json$/,
24438
+ /nax\/features\/[^/]+\/prd\.json$/,
24439
+ /nax\/features\/[^/]+\/runs\//,
24440
+ /nax\/features\/[^/]+\/plan\//,
24441
+ /nax\/features\/[^/]+\/acp-sessions\.json$/,
24442
+ /nax\/features\/[^/]+\/interactions\//,
24443
+ /nax\/features\/[^/]+\/progress\.txt$/,
24444
+ /nax\/features\/[^/]+\/acceptance-refined\.json$/,
24445
+ /\.nax-verifier-verdict\.json$/,
24446
+ /\.nax-pids$/,
24447
+ /\.nax-wt\//
24448
+ ];
24449
+ const uncommittedFiles = allUncommittedFiles.filter((f) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(f)));
24402
24450
  if (uncommittedFiles.length > 0) {
24403
24451
  const fileList = uncommittedFiles.join(", ");
24404
24452
  logger?.warn("review", `Uncommitted changes detected before review: ${fileList}`);
@@ -24562,12 +24610,13 @@ var init_review = __esm(() => {
24562
24610
  init_orchestrator();
24563
24611
  reviewStage = {
24564
24612
  name: "review",
24565
- enabled: (ctx) => ctx.config.review.enabled,
24613
+ enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
24566
24614
  async execute(ctx) {
24567
24615
  const logger = getLogger();
24616
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24568
24617
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
24569
24618
  const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
24570
- 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);
24571
24620
  ctx.reviewResult = result.builtIn;
24572
24621
  if (!result.success) {
24573
24622
  const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
@@ -24575,8 +24624,8 @@ var init_review = __esm(() => {
24575
24624
  ctx.reviewFindings = allFindings;
24576
24625
  }
24577
24626
  if (result.pluginFailed) {
24578
- if (ctx.interaction && isTriggerEnabled("security-review", ctx.config)) {
24579
- 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);
24580
24629
  if (!shouldContinue) {
24581
24630
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
24582
24631
  return { action: "fail", reason: `Review failed: ${result.failureReason}` };
@@ -24587,11 +24636,11 @@ var init_review = __esm(() => {
24587
24636
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
24588
24637
  return { action: "fail", reason: `Review failed: ${result.failureReason}` };
24589
24638
  }
24590
- 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", {
24591
24640
  reason: result.failureReason,
24592
24641
  storyId: ctx.story.id
24593
24642
  });
24594
- return { action: "escalate", reason: `Review failed: ${result.failureReason}` };
24643
+ return { action: "continue" };
24595
24644
  }
24596
24645
  logger.info("review", "Review passed", {
24597
24646
  durationMs: result.builtIn.totalDurationMs,
@@ -24606,6 +24655,7 @@ var init_review = __esm(() => {
24606
24655
  });
24607
24656
 
24608
24657
  // src/pipeline/stages/autofix.ts
24658
+ import { join as join18 } from "path";
24609
24659
  async function runCommand(cmd, cwd) {
24610
24660
  const parts = cmd.split(/\s+/);
24611
24661
  const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
@@ -24648,7 +24698,8 @@ Commit your fixes when done.`;
24648
24698
  }
24649
24699
  async function runAgentRectification(ctx) {
24650
24700
  const logger = getLogger();
24651
- const maxAttempts = ctx.config.quality.autofix?.maxAttempts ?? 2;
24701
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24702
+ const maxAttempts = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
24652
24703
  const failedChecks = collectFailedChecks(ctx);
24653
24704
  if (failedChecks.length === 0) {
24654
24705
  logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
@@ -24705,6 +24756,7 @@ var autofixStage, _autofixDeps;
24705
24756
  var init_autofix = __esm(() => {
24706
24757
  init_agents();
24707
24758
  init_config();
24759
+ init_loader2();
24708
24760
  init_logger2();
24709
24761
  init_event_bus();
24710
24762
  autofixStage = {
@@ -24714,7 +24766,7 @@ var init_autofix = __esm(() => {
24714
24766
  return false;
24715
24767
  if (ctx.reviewResult.success)
24716
24768
  return false;
24717
- const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
24769
+ const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
24718
24770
  return autofixEnabled;
24719
24771
  },
24720
24772
  skipReason(ctx) {
@@ -24728,12 +24780,14 @@ var init_autofix = __esm(() => {
24728
24780
  if (!reviewResult || reviewResult.success) {
24729
24781
  return { action: "continue" };
24730
24782
  }
24731
- const lintFixCmd = ctx.config.quality.commands.lintFix;
24732
- 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;
24733
24787
  if (lintFixCmd || formatFixCmd) {
24734
24788
  if (lintFixCmd) {
24735
24789
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
24736
- const lintResult = await _autofixDeps.runCommand(lintFixCmd, ctx.workdir);
24790
+ const lintResult = await _autofixDeps.runCommand(lintFixCmd, effectiveWorkdir);
24737
24791
  logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id });
24738
24792
  if (lintResult.exitCode !== 0) {
24739
24793
  logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
@@ -24744,7 +24798,7 @@ var init_autofix = __esm(() => {
24744
24798
  }
24745
24799
  if (formatFixCmd) {
24746
24800
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
24747
- const fmtResult = await _autofixDeps.runCommand(formatFixCmd, ctx.workdir);
24801
+ const fmtResult = await _autofixDeps.runCommand(formatFixCmd, effectiveWorkdir);
24748
24802
  logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, { storyId: ctx.story.id });
24749
24803
  if (fmtResult.exitCode !== 0) {
24750
24804
  logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
@@ -24773,15 +24827,15 @@ var init_autofix = __esm(() => {
24773
24827
  return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
24774
24828
  }
24775
24829
  };
24776
- _autofixDeps = { runCommand, recheckReview, runAgentRectification };
24830
+ _autofixDeps = { runCommand, recheckReview, runAgentRectification, loadConfigForWorkdir };
24777
24831
  });
24778
24832
 
24779
24833
  // src/execution/progress.ts
24780
24834
  import { mkdirSync as mkdirSync2 } from "fs";
24781
- import { join as join18 } from "path";
24835
+ import { join as join19 } from "path";
24782
24836
  async function appendProgress(featureDir, storyId, status, message) {
24783
24837
  mkdirSync2(featureDir, { recursive: true });
24784
- const progressPath = join18(featureDir, "progress.txt");
24838
+ const progressPath = join19(featureDir, "progress.txt");
24785
24839
  const timestamp = new Date().toISOString();
24786
24840
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
24787
24841
  `;
@@ -24865,7 +24919,7 @@ function estimateTokens(text) {
24865
24919
 
24866
24920
  // src/constitution/loader.ts
24867
24921
  import { existsSync as existsSync15 } from "fs";
24868
- import { join as join19 } from "path";
24922
+ import { join as join20 } from "path";
24869
24923
  function truncateToTokens(text, maxTokens) {
24870
24924
  const maxChars = maxTokens * 3;
24871
24925
  if (text.length <= maxChars) {
@@ -24887,7 +24941,7 @@ async function loadConstitution(projectDir, config2) {
24887
24941
  }
24888
24942
  let combinedContent = "";
24889
24943
  if (!config2.skipGlobal) {
24890
- const globalPath = join19(globalConfigDir(), config2.path);
24944
+ const globalPath = join20(globalConfigDir(), config2.path);
24891
24945
  if (existsSync15(globalPath)) {
24892
24946
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
24893
24947
  const globalFile = Bun.file(validatedPath);
@@ -24897,7 +24951,7 @@ async function loadConstitution(projectDir, config2) {
24897
24951
  }
24898
24952
  }
24899
24953
  }
24900
- const projectPath = join19(projectDir, config2.path);
24954
+ const projectPath = join20(projectDir, config2.path);
24901
24955
  if (existsSync15(projectPath)) {
24902
24956
  const validatedPath = validateFilePath(projectPath, projectDir);
24903
24957
  const projectFile = Bun.file(validatedPath);
@@ -25869,7 +25923,7 @@ var init_helpers = __esm(() => {
25869
25923
  });
25870
25924
 
25871
25925
  // src/pipeline/stages/context.ts
25872
- import { join as join20 } from "path";
25926
+ import { join as join21 } from "path";
25873
25927
  var contextStage;
25874
25928
  var init_context2 = __esm(() => {
25875
25929
  init_helpers();
@@ -25879,7 +25933,7 @@ var init_context2 = __esm(() => {
25879
25933
  enabled: () => true,
25880
25934
  async execute(ctx) {
25881
25935
  const logger = getLogger();
25882
- 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;
25883
25937
  const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
25884
25938
  if (result) {
25885
25939
  ctx.contextMarkdown = result.markdown;
@@ -26011,14 +26065,14 @@ var init_isolation = __esm(() => {
26011
26065
 
26012
26066
  // src/context/greenfield.ts
26013
26067
  import { readdir } from "fs/promises";
26014
- import { join as join21 } from "path";
26068
+ import { join as join22 } from "path";
26015
26069
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
26016
26070
  const results = [];
26017
26071
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
26018
26072
  try {
26019
26073
  const entries = await readdir(dir, { withFileTypes: true });
26020
26074
  for (const entry of entries) {
26021
- const fullPath = join21(dir, entry.name);
26075
+ const fullPath = join22(dir, entry.name);
26022
26076
  if (entry.isDirectory()) {
26023
26077
  if (ignoreDirs.has(entry.name))
26024
26078
  continue;
@@ -26430,13 +26484,13 @@ function parseTestOutput(output, exitCode) {
26430
26484
 
26431
26485
  // src/verification/runners.ts
26432
26486
  import { existsSync as existsSync16 } from "fs";
26433
- import { join as join22 } from "path";
26487
+ import { join as join23 } from "path";
26434
26488
  async function verifyAssets(workingDirectory, expectedFiles) {
26435
26489
  if (!expectedFiles || expectedFiles.length === 0)
26436
26490
  return { success: true, missingFiles: [] };
26437
26491
  const missingFiles = [];
26438
26492
  for (const file2 of expectedFiles) {
26439
- if (!existsSync16(join22(workingDirectory, file2)))
26493
+ if (!existsSync16(join23(workingDirectory, file2)))
26440
26494
  missingFiles.push(file2);
26441
26495
  }
26442
26496
  if (missingFiles.length > 0) {
@@ -27121,13 +27175,13 @@ var exports_loader = {};
27121
27175
  __export(exports_loader, {
27122
27176
  loadOverride: () => loadOverride
27123
27177
  });
27124
- import { join as join23 } from "path";
27178
+ import { join as join24 } from "path";
27125
27179
  async function loadOverride(role, workdir, config2) {
27126
27180
  const overridePath = config2.prompts?.overrides?.[role];
27127
27181
  if (!overridePath) {
27128
27182
  return null;
27129
27183
  }
27130
- const absolutePath = join23(workdir, overridePath);
27184
+ const absolutePath = join24(workdir, overridePath);
27131
27185
  const file2 = Bun.file(absolutePath);
27132
27186
  if (!await file2.exists()) {
27133
27187
  return null;
@@ -27949,11 +28003,11 @@ var init_tdd = __esm(() => {
27949
28003
 
27950
28004
  // src/pipeline/stages/execution.ts
27951
28005
  import { existsSync as existsSync17 } from "fs";
27952
- import { join as join24 } from "path";
28006
+ import { join as join25 } from "path";
27953
28007
  function resolveStoryWorkdir(repoRoot, storyWorkdir) {
27954
28008
  if (!storyWorkdir)
27955
28009
  return repoRoot;
27956
- const resolved = join24(repoRoot, storyWorkdir);
28010
+ const resolved = join25(repoRoot, storyWorkdir);
27957
28011
  if (!existsSync17(resolved)) {
27958
28012
  throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
27959
28013
  }
@@ -27983,6 +28037,9 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
27983
28037
  if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
27984
28038
  return { action: "escalate" };
27985
28039
  }
28040
+ if (failureCategory === "greenfield-no-tests") {
28041
+ return { action: "escalate" };
28042
+ }
27986
28043
  return {
27987
28044
  action: "pause",
27988
28045
  reason: reviewReason || "Three-session TDD requires review"
@@ -28423,13 +28480,14 @@ var init_prompt = __esm(() => {
28423
28480
  async execute(ctx) {
28424
28481
  const logger = getLogger();
28425
28482
  const isBatch = ctx.stories.length > 1;
28483
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28426
28484
  let prompt;
28427
28485
  if (isBatch) {
28428
- 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);
28429
28487
  prompt = await builder.build();
28430
28488
  } else {
28431
28489
  const role = "tdd-simple";
28432
- 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);
28433
28491
  prompt = await builder.build();
28434
28492
  }
28435
28493
  ctx.prompt = prompt;
@@ -28773,13 +28831,14 @@ var init_rectify = __esm(() => {
28773
28831
  attempt: rectifyAttempt,
28774
28832
  testOutput
28775
28833
  });
28776
- 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";
28777
28836
  const fixed = await _rectifyDeps.runRectificationLoop({
28778
28837
  config: ctx.config,
28779
28838
  workdir: ctx.workdir,
28780
28839
  story: ctx.story,
28781
28840
  testCommand,
28782
- timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
28841
+ timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
28783
28842
  testOutput
28784
28843
  });
28785
28844
  pipelineEventBus.emit({
@@ -29307,31 +29366,34 @@ var init_regression2 = __esm(() => {
29307
29366
  regressionStage = {
29308
29367
  name: "regression",
29309
29368
  enabled(ctx) {
29310
- const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29369
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29370
+ const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29311
29371
  if (mode !== "per-story")
29312
29372
  return false;
29313
29373
  if (ctx.verifyResult && !ctx.verifyResult.success)
29314
29374
  return false;
29315
- const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
29375
+ const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
29316
29376
  return gateEnabled;
29317
29377
  },
29318
29378
  skipReason(ctx) {
29319
- const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29379
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29380
+ const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29320
29381
  if (mode !== "per-story")
29321
29382
  return `not needed (regression mode is '${mode}', not 'per-story')`;
29322
29383
  return "disabled (regression gate not enabled in config)";
29323
29384
  },
29324
29385
  async execute(ctx) {
29325
29386
  const logger = getLogger();
29326
- const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
29327
- 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;
29328
29390
  logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
29329
29391
  const verifyCtx = {
29330
29392
  workdir: ctx.workdir,
29331
29393
  testCommand,
29332
29394
  timeoutSeconds,
29333
29395
  storyId: ctx.story.id,
29334
- acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
29396
+ acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
29335
29397
  config: ctx.config
29336
29398
  };
29337
29399
  const result = await _regressionStageDeps.verifyRegression(verifyCtx);
@@ -29366,7 +29428,7 @@ var init_regression2 = __esm(() => {
29366
29428
  });
29367
29429
 
29368
29430
  // src/pipeline/stages/routing.ts
29369
- import { join as join25 } from "path";
29431
+ import { join as join26 } from "path";
29370
29432
  async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
29371
29433
  const naxDecompose = config2.decompose;
29372
29434
  const builderConfig = {
@@ -29440,7 +29502,7 @@ var init_routing2 = __esm(() => {
29440
29502
  }
29441
29503
  const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
29442
29504
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
29443
- 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;
29444
29506
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
29445
29507
  if (isGreenfield) {
29446
29508
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -29537,7 +29599,7 @@ var init_crash_detector = __esm(() => {
29537
29599
  });
29538
29600
 
29539
29601
  // src/pipeline/stages/verify.ts
29540
- import { join as join26 } from "path";
29602
+ import { join as join27 } from "path";
29541
29603
  function coerceSmartTestRunner(val) {
29542
29604
  if (val === undefined || val === true)
29543
29605
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -29551,13 +29613,30 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
29551
29613
  }
29552
29614
  return _smartRunnerDeps.buildSmartTestCommand(testFiles, baseCommand);
29553
29615
  }
29616
+ async function readPackageName(dir) {
29617
+ try {
29618
+ const content = await Bun.file(join27(dir, "package.json")).json();
29619
+ return typeof content.name === "string" ? content.name : null;
29620
+ } catch {
29621
+ return null;
29622
+ }
29623
+ }
29624
+ async function resolvePackageTemplate(template, packageDir) {
29625
+ if (!template.includes("{{package}}"))
29626
+ return template;
29627
+ const name = await _verifyDeps.readPackageName(packageDir);
29628
+ if (name === null) {
29629
+ return null;
29630
+ }
29631
+ return template.replaceAll("{{package}}", name);
29632
+ }
29554
29633
  var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
29555
29634
  var init_verify = __esm(() => {
29556
- init_loader2();
29557
29635
  init_logger2();
29558
29636
  init_crash_detector();
29559
29637
  init_runners();
29560
29638
  init_smart_runner();
29639
+ init_scoped();
29561
29640
  DEFAULT_SMART_RUNNER_CONFIG2 = {
29562
29641
  enabled: true,
29563
29642
  testFilePatterns: ["test/**/*.test.ts"],
@@ -29569,7 +29648,7 @@ var init_verify = __esm(() => {
29569
29648
  skipReason: () => "not needed (full-suite gate already passed)",
29570
29649
  async execute(ctx) {
29571
29650
  const logger = getLogger();
29572
- 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;
29573
29652
  if (!effectiveConfig.quality.requireTests) {
29574
29653
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
29575
29654
  return { action: "continue" };
@@ -29581,19 +29660,39 @@ var init_verify = __esm(() => {
29581
29660
  return { action: "continue" };
29582
29661
  }
29583
29662
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
29584
- 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;
29585
29664
  let effectiveCommand = testCommand;
29586
29665
  let isFullSuite = true;
29587
- const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
29588
- const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29589
- if (smartRunnerConfig.enabled) {
29666
+ const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
29667
+ const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29668
+ let resolvedTestScopedTemplate = testScopedTemplate;
29669
+ if (testScopedTemplate && ctx.story.workdir) {
29670
+ const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
29671
+ resolvedTestScopedTemplate = resolved ?? undefined;
29672
+ }
29673
+ const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
29674
+ if (isMonorepoOrchestrator) {
29675
+ if (resolvedTestScopedTemplate && ctx.story.workdir) {
29676
+ effectiveCommand = resolvedTestScopedTemplate;
29677
+ isFullSuite = false;
29678
+ logger.info("verify", "Monorepo orchestrator \u2014 using testScoped template", {
29679
+ storyId: ctx.story.id,
29680
+ command: effectiveCommand
29681
+ });
29682
+ } else {
29683
+ logger.info("verify", "Monorepo orchestrator \u2014 running full suite (no package context)", {
29684
+ storyId: ctx.story.id,
29685
+ command: effectiveCommand
29686
+ });
29687
+ }
29688
+ } else if (smartRunnerConfig.enabled) {
29590
29689
  const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(effectiveWorkdir, ctx.storyGitRef, ctx.story.workdir);
29591
29690
  const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, effectiveWorkdir);
29592
29691
  if (pass1Files.length > 0) {
29593
29692
  logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
29594
29693
  storyId: ctx.story.id
29595
29694
  });
29596
- effectiveCommand = buildScopedCommand2(pass1Files, testCommand, testScopedTemplate);
29695
+ effectiveCommand = buildScopedCommand2(pass1Files, testCommand, resolvedTestScopedTemplate);
29597
29696
  isFullSuite = false;
29598
29697
  } else if (smartRunnerConfig.fallback === "import-grep") {
29599
29698
  const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, effectiveWorkdir, smartRunnerConfig.testFilePatterns);
@@ -29601,7 +29700,7 @@ var init_verify = __esm(() => {
29601
29700
  logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
29602
29701
  storyId: ctx.story.id
29603
29702
  });
29604
- effectiveCommand = buildScopedCommand2(pass2Files, testCommand, testScopedTemplate);
29703
+ effectiveCommand = buildScopedCommand2(pass2Files, testCommand, resolvedTestScopedTemplate);
29605
29704
  isFullSuite = false;
29606
29705
  }
29607
29706
  }
@@ -29624,8 +29723,8 @@ var init_verify = __esm(() => {
29624
29723
  const result = await _verifyDeps.regression({
29625
29724
  workdir: effectiveWorkdir,
29626
29725
  command: effectiveCommand,
29627
- timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
29628
- acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
29726
+ timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
29727
+ acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
29629
29728
  });
29630
29729
  ctx.verifyResult = {
29631
29730
  success: result.success,
@@ -29642,7 +29741,7 @@ var init_verify = __esm(() => {
29642
29741
  };
29643
29742
  if (!result.success) {
29644
29743
  if (result.status === "TIMEOUT") {
29645
- const timeout = ctx.config.execution.verificationTimeoutSeconds;
29744
+ const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
29646
29745
  logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
29647
29746
  exitCode: result.status,
29648
29747
  storyId: ctx.story.id,
@@ -29657,10 +29756,13 @@ var init_verify = __esm(() => {
29657
29756
  if (result.status !== "TIMEOUT") {
29658
29757
  logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
29659
29758
  }
29660
- return {
29661
- action: "escalate",
29662
- 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"})`
29663
- };
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" };
29664
29766
  }
29665
29767
  logger.info("verify", "Tests passed", { storyId: ctx.story.id });
29666
29768
  return { action: "continue" };
@@ -29668,7 +29770,7 @@ var init_verify = __esm(() => {
29668
29770
  };
29669
29771
  _verifyDeps = {
29670
29772
  regression,
29671
- loadConfigForWorkdir
29773
+ readPackageName
29672
29774
  };
29673
29775
  });
29674
29776
 
@@ -29755,7 +29857,7 @@ __export(exports_init_context, {
29755
29857
  });
29756
29858
  import { existsSync as existsSync20 } from "fs";
29757
29859
  import { mkdir } from "fs/promises";
29758
- import { basename, join as join30 } from "path";
29860
+ import { basename as basename2, join as join31 } from "path";
29759
29861
  async function findFiles(dir, maxFiles = 200) {
29760
29862
  try {
29761
29863
  const proc = Bun.spawnSync([
@@ -29783,7 +29885,7 @@ async function findFiles(dir, maxFiles = 200) {
29783
29885
  return [];
29784
29886
  }
29785
29887
  async function readPackageManifest(projectRoot) {
29786
- const packageJsonPath = join30(projectRoot, "package.json");
29888
+ const packageJsonPath = join31(projectRoot, "package.json");
29787
29889
  if (!existsSync20(packageJsonPath)) {
29788
29890
  return null;
29789
29891
  }
@@ -29801,7 +29903,7 @@ async function readPackageManifest(projectRoot) {
29801
29903
  }
29802
29904
  }
29803
29905
  async function readReadmeSnippet(projectRoot) {
29804
- const readmePath = join30(projectRoot, "README.md");
29906
+ const readmePath = join31(projectRoot, "README.md");
29805
29907
  if (!existsSync20(readmePath)) {
29806
29908
  return null;
29807
29909
  }
@@ -29819,7 +29921,7 @@ async function detectEntryPoints(projectRoot) {
29819
29921
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
29820
29922
  const found = [];
29821
29923
  for (const candidate of candidates) {
29822
- const path12 = join30(projectRoot, candidate);
29924
+ const path12 = join31(projectRoot, candidate);
29823
29925
  if (existsSync20(path12)) {
29824
29926
  found.push(candidate);
29825
29927
  }
@@ -29830,7 +29932,7 @@ async function detectConfigFiles(projectRoot) {
29830
29932
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
29831
29933
  const found = [];
29832
29934
  for (const candidate of candidates) {
29833
- const path12 = join30(projectRoot, candidate);
29935
+ const path12 = join31(projectRoot, candidate);
29834
29936
  if (existsSync20(path12)) {
29835
29937
  found.push(candidate);
29836
29938
  }
@@ -29843,7 +29945,7 @@ async function scanProject(projectRoot) {
29843
29945
  const readmeSnippet = await readReadmeSnippet(projectRoot);
29844
29946
  const entryPoints = await detectEntryPoints(projectRoot);
29845
29947
  const configFiles = await detectConfigFiles(projectRoot);
29846
- const projectName = packageManifest?.name || basename(projectRoot);
29948
+ const projectName = packageManifest?.name || basename2(projectRoot);
29847
29949
  return {
29848
29950
  projectName,
29849
29951
  fileTree,
@@ -29991,9 +30093,9 @@ function generatePackageContextTemplate(packagePath) {
29991
30093
  }
29992
30094
  async function initPackage(repoRoot, packagePath, force = false) {
29993
30095
  const logger = getLogger();
29994
- const packageDir = join30(repoRoot, packagePath);
29995
- const naxDir = join30(packageDir, "nax");
29996
- 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");
29997
30099
  if (existsSync20(contextPath) && !force) {
29998
30100
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
29999
30101
  return;
@@ -30007,8 +30109,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
30007
30109
  }
30008
30110
  async function initContext(projectRoot, options = {}) {
30009
30111
  const logger = getLogger();
30010
- const naxDir = join30(projectRoot, "nax");
30011
- const contextPath = join30(naxDir, "context.md");
30112
+ const naxDir = join31(projectRoot, "nax");
30113
+ const contextPath = join31(naxDir, "context.md");
30012
30114
  if (existsSync20(contextPath) && !options.force) {
30013
30115
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
30014
30116
  return;
@@ -30352,11 +30454,11 @@ function getSafeLogger6() {
30352
30454
  return getSafeLogger();
30353
30455
  }
30354
30456
  function extractPluginName(pluginPath) {
30355
- const basename3 = path12.basename(pluginPath);
30356
- if (basename3 === "index.ts" || basename3 === "index.js" || basename3 === "index.mjs") {
30457
+ const basename4 = path12.basename(pluginPath);
30458
+ if (basename4 === "index.ts" || basename4 === "index.js" || basename4 === "index.mjs") {
30357
30459
  return path12.basename(path12.dirname(pluginPath));
30358
30460
  }
30359
- return basename3.replace(/\.(ts|js|mjs)$/, "");
30461
+ return basename4.replace(/\.(ts|js|mjs)$/, "");
30360
30462
  }
30361
30463
  async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
30362
30464
  const loadedPlugins = [];
@@ -31316,19 +31418,19 @@ var init_precheck = __esm(() => {
31316
31418
  });
31317
31419
 
31318
31420
  // src/hooks/runner.ts
31319
- import { join as join43 } from "path";
31421
+ import { join as join44 } from "path";
31320
31422
  async function loadHooksConfig(projectDir, globalDir) {
31321
31423
  let globalHooks = { hooks: {} };
31322
31424
  let projectHooks = { hooks: {} };
31323
31425
  let skipGlobal = false;
31324
- const projectPath = join43(projectDir, "hooks.json");
31426
+ const projectPath = join44(projectDir, "hooks.json");
31325
31427
  const projectData = await loadJsonFile(projectPath, "hooks");
31326
31428
  if (projectData) {
31327
31429
  projectHooks = projectData;
31328
31430
  skipGlobal = projectData.skipGlobal ?? false;
31329
31431
  }
31330
31432
  if (!skipGlobal && globalDir) {
31331
- const globalPath = join43(globalDir, "hooks.json");
31433
+ const globalPath = join44(globalDir, "hooks.json");
31332
31434
  const globalData = await loadJsonFile(globalPath, "hooks");
31333
31435
  if (globalData) {
31334
31436
  globalHooks = globalData;
@@ -31819,6 +31921,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
31819
31921
  }), ctx.workdir);
31820
31922
  const fixContext = {
31821
31923
  config: ctx.config,
31924
+ effectiveConfig: ctx.config,
31822
31925
  prd,
31823
31926
  story,
31824
31927
  stories: [story],
@@ -31852,6 +31955,7 @@ async function runAcceptanceLoop(ctx) {
31852
31955
  const firstStory = prd.userStories[0];
31853
31956
  const acceptanceContext = {
31854
31957
  config: ctx.config,
31958
+ effectiveConfig: ctx.config,
31855
31959
  prd,
31856
31960
  story: firstStory,
31857
31961
  stories: [firstStory],
@@ -32360,12 +32464,12 @@ __export(exports_manager, {
32360
32464
  WorktreeManager: () => WorktreeManager
32361
32465
  });
32362
32466
  import { existsSync as existsSync32, symlinkSync } from "fs";
32363
- import { join as join44 } from "path";
32467
+ import { join as join45 } from "path";
32364
32468
 
32365
32469
  class WorktreeManager {
32366
32470
  async create(projectRoot, storyId) {
32367
32471
  validateStoryId(storyId);
32368
- const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32472
+ const worktreePath = join45(projectRoot, ".nax-wt", storyId);
32369
32473
  const branchName = `nax/${storyId}`;
32370
32474
  try {
32371
32475
  const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
@@ -32390,9 +32494,9 @@ class WorktreeManager {
32390
32494
  }
32391
32495
  throw new Error(`Failed to create worktree: ${String(error48)}`);
32392
32496
  }
32393
- const nodeModulesSource = join44(projectRoot, "node_modules");
32497
+ const nodeModulesSource = join45(projectRoot, "node_modules");
32394
32498
  if (existsSync32(nodeModulesSource)) {
32395
- const nodeModulesTarget = join44(worktreePath, "node_modules");
32499
+ const nodeModulesTarget = join45(worktreePath, "node_modules");
32396
32500
  try {
32397
32501
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
32398
32502
  } catch (error48) {
@@ -32400,9 +32504,9 @@ class WorktreeManager {
32400
32504
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
32401
32505
  }
32402
32506
  }
32403
- const envSource = join44(projectRoot, ".env");
32507
+ const envSource = join45(projectRoot, ".env");
32404
32508
  if (existsSync32(envSource)) {
32405
- const envTarget = join44(worktreePath, ".env");
32509
+ const envTarget = join45(worktreePath, ".env");
32406
32510
  try {
32407
32511
  symlinkSync(envSource, envTarget, "file");
32408
32512
  } catch (error48) {
@@ -32413,7 +32517,7 @@ class WorktreeManager {
32413
32517
  }
32414
32518
  async remove(projectRoot, storyId) {
32415
32519
  validateStoryId(storyId);
32416
- const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32520
+ const worktreePath = join45(projectRoot, ".nax-wt", storyId);
32417
32521
  const branchName = `nax/${storyId}`;
32418
32522
  try {
32419
32523
  const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -32723,6 +32827,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
32723
32827
  try {
32724
32828
  const pipelineContext = {
32725
32829
  ...context,
32830
+ effectiveConfig: context.effectiveConfig ?? context.config,
32726
32831
  story,
32727
32832
  stories: [story],
32728
32833
  workdir: worktreePath,
@@ -32803,7 +32908,7 @@ var init_parallel_worker = __esm(() => {
32803
32908
 
32804
32909
  // src/execution/parallel-coordinator.ts
32805
32910
  import os3 from "os";
32806
- import { join as join45 } from "path";
32911
+ import { join as join46 } from "path";
32807
32912
  function groupStoriesByDependencies(stories) {
32808
32913
  const batches = [];
32809
32914
  const processed = new Set;
@@ -32872,6 +32977,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32872
32977
  });
32873
32978
  const baseContext = {
32874
32979
  config: config2,
32980
+ effectiveConfig: config2,
32875
32981
  prd: currentPrd,
32876
32982
  featureDir,
32877
32983
  hooks,
@@ -32881,7 +32987,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32881
32987
  };
32882
32988
  const worktreePaths = new Map;
32883
32989
  for (const story of batch) {
32884
- const worktreePath = join45(projectRoot, ".nax-wt", story.id);
32990
+ const worktreePath = join46(projectRoot, ".nax-wt", story.id);
32885
32991
  try {
32886
32992
  await worktreeManager.create(projectRoot, story.id);
32887
32993
  worktreePaths.set(story.id, worktreePath);
@@ -32930,7 +33036,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32930
33036
  });
32931
33037
  logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
32932
33038
  storyId: mergeResult.storyId,
32933
- worktreePath: join45(projectRoot, ".nax-wt", mergeResult.storyId)
33039
+ worktreePath: join46(projectRoot, ".nax-wt", mergeResult.storyId)
32934
33040
  });
32935
33041
  }
32936
33042
  }
@@ -33009,6 +33115,7 @@ async function rectifyConflictedStory(options) {
33009
33115
  const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
33010
33116
  const pipelineContext = {
33011
33117
  config: config2,
33118
+ effectiveConfig: config2,
33012
33119
  prd,
33013
33120
  story,
33014
33121
  stories: [story],
@@ -33389,12 +33496,12 @@ var init_parallel_executor = __esm(() => {
33389
33496
  // src/pipeline/subscribers/events-writer.ts
33390
33497
  import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
33391
33498
  import { homedir as homedir7 } from "os";
33392
- import { basename as basename4, join as join46 } from "path";
33499
+ import { basename as basename5, join as join47 } from "path";
33393
33500
  function wireEventsWriter(bus, feature, runId, workdir) {
33394
33501
  const logger = getSafeLogger();
33395
- const project = basename4(workdir);
33396
- const eventsDir = join46(homedir7(), ".nax", "events", project);
33397
- const eventsFile = join46(eventsDir, "events.jsonl");
33502
+ const project = basename5(workdir);
33503
+ const eventsDir = join47(homedir7(), ".nax", "events", project);
33504
+ const eventsFile = join47(eventsDir, "events.jsonl");
33398
33505
  let dirReady = false;
33399
33506
  const write = (line) => {
33400
33507
  (async () => {
@@ -33554,12 +33661,12 @@ var init_interaction2 = __esm(() => {
33554
33661
  // src/pipeline/subscribers/registry.ts
33555
33662
  import { mkdir as mkdir3, writeFile } from "fs/promises";
33556
33663
  import { homedir as homedir8 } from "os";
33557
- import { basename as basename5, join as join47 } from "path";
33664
+ import { basename as basename6, join as join48 } from "path";
33558
33665
  function wireRegistry(bus, feature, runId, workdir) {
33559
33666
  const logger = getSafeLogger();
33560
- const project = basename5(workdir);
33561
- const runDir = join47(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33562
- const metaFile = join47(runDir, "meta.json");
33667
+ const project = basename6(workdir);
33668
+ const runDir = join48(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33669
+ const metaFile = join48(runDir, "meta.json");
33563
33670
  const unsub = bus.on("run:started", (_ev) => {
33564
33671
  (async () => {
33565
33672
  try {
@@ -33569,8 +33676,8 @@ function wireRegistry(bus, feature, runId, workdir) {
33569
33676
  project,
33570
33677
  feature,
33571
33678
  workdir,
33572
- statusPath: join47(workdir, "nax", "features", feature, "status.json"),
33573
- eventsDir: join47(workdir, "nax", "features", feature, "runs"),
33679
+ statusPath: join48(workdir, "nax", "features", feature, "status.json"),
33680
+ eventsDir: join48(workdir, "nax", "features", feature, "runs"),
33574
33681
  registeredAt: new Date().toISOString()
33575
33682
  };
33576
33683
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -34199,6 +34306,7 @@ var init_pipeline_result_handler = __esm(() => {
34199
34306
  });
34200
34307
 
34201
34308
  // src/execution/iteration-runner.ts
34309
+ import { join as join49 } from "path";
34202
34310
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
34203
34311
  const logger = getSafeLogger();
34204
34312
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -34224,8 +34332,10 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34224
34332
  const storyStartTime = Date.now();
34225
34333
  const storyGitRef = await captureGitRef(ctx.workdir);
34226
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;
34227
34336
  const pipelineContext = {
34228
34337
  config: ctx.config,
34338
+ effectiveConfig,
34229
34339
  prd,
34230
34340
  story,
34231
34341
  stories: storiesToExecute,
@@ -34296,13 +34406,18 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34296
34406
  reason: pipelineResult.reason
34297
34407
  };
34298
34408
  }
34409
+ var _iterationRunnerDeps;
34299
34410
  var init_iteration_runner = __esm(() => {
34411
+ init_loader2();
34300
34412
  init_logger2();
34301
34413
  init_runner();
34302
34414
  init_stages();
34303
34415
  init_git();
34304
34416
  init_dry_run();
34305
34417
  init_pipeline_result_handler();
34418
+ _iterationRunnerDeps = {
34419
+ loadConfigForWorkdir
34420
+ };
34306
34421
  });
34307
34422
 
34308
34423
  // src/execution/executor-types.ts
@@ -34395,6 +34510,7 @@ async function executeSequential(ctx, initialPrd) {
34395
34510
  logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
34396
34511
  const preRunCtx = {
34397
34512
  config: ctx.config,
34513
+ effectiveConfig: ctx.config,
34398
34514
  prd,
34399
34515
  workdir: ctx.workdir,
34400
34516
  featureDir: ctx.featureDir,
@@ -34566,7 +34682,7 @@ async function writeStatusFile(filePath, status) {
34566
34682
  var init_status_file = () => {};
34567
34683
 
34568
34684
  // src/execution/status-writer.ts
34569
- import { join as join48 } from "path";
34685
+ import { join as join50 } from "path";
34570
34686
 
34571
34687
  class StatusWriter {
34572
34688
  statusFile;
@@ -34634,7 +34750,7 @@ class StatusWriter {
34634
34750
  if (!this._prd)
34635
34751
  return;
34636
34752
  const safeLogger = getSafeLogger();
34637
- const featureStatusPath = join48(featureDir, "status.json");
34753
+ const featureStatusPath = join50(featureDir, "status.json");
34638
34754
  try {
34639
34755
  const base = this.getSnapshot(totalCost, iterations);
34640
34756
  if (!base) {
@@ -65963,7 +66079,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
65963
66079
  init_source();
65964
66080
  import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
65965
66081
  import { homedir as homedir10 } from "os";
65966
- import { join as join49 } from "path";
66082
+ import { join as join51 } from "path";
65967
66083
 
65968
66084
  // node_modules/commander/esm.mjs
65969
66085
  var import__ = __toESM(require_commander(), 1);
@@ -68032,7 +68148,7 @@ async function runsShowCommand(options) {
68032
68148
  // src/cli/prompts-main.ts
68033
68149
  init_logger2();
68034
68150
  import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
68035
- import { join as join28 } from "path";
68151
+ import { join as join29 } from "path";
68036
68152
 
68037
68153
  // src/pipeline/index.ts
68038
68154
  init_runner();
@@ -68068,7 +68184,7 @@ init_prd();
68068
68184
 
68069
68185
  // src/cli/prompts-tdd.ts
68070
68186
  init_prompts2();
68071
- import { join as join27 } from "path";
68187
+ import { join as join28 } from "path";
68072
68188
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
68073
68189
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
68074
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(),
@@ -68087,7 +68203,7 @@ ${frontmatter}---
68087
68203
 
68088
68204
  ${session.prompt}`;
68089
68205
  if (outputDir) {
68090
- const promptFile = join27(outputDir, `${story.id}.${session.role}.md`);
68206
+ const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
68091
68207
  await Bun.write(promptFile, fullOutput);
68092
68208
  logger.info("cli", "Written TDD prompt file", {
68093
68209
  storyId: story.id,
@@ -68103,7 +68219,7 @@ ${"=".repeat(80)}`);
68103
68219
  }
68104
68220
  }
68105
68221
  if (outputDir && ctx.contextMarkdown) {
68106
- const contextFile = join27(outputDir, `${story.id}.context.md`);
68222
+ const contextFile = join28(outputDir, `${story.id}.context.md`);
68107
68223
  const frontmatter = buildFrontmatter(story, ctx);
68108
68224
  const contextOutput = `---
68109
68225
  ${frontmatter}---
@@ -68117,12 +68233,12 @@ ${ctx.contextMarkdown}`;
68117
68233
  async function promptsCommand(options) {
68118
68234
  const logger = getLogger();
68119
68235
  const { feature, workdir, config: config2, storyId, outputDir } = options;
68120
- const naxDir = join28(workdir, "nax");
68236
+ const naxDir = join29(workdir, "nax");
68121
68237
  if (!existsSync18(naxDir)) {
68122
68238
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
68123
68239
  }
68124
- const featureDir = join28(naxDir, "features", feature);
68125
- const prdPath = join28(featureDir, "prd.json");
68240
+ const featureDir = join29(naxDir, "features", feature);
68241
+ const prdPath = join29(featureDir, "prd.json");
68126
68242
  if (!existsSync18(prdPath)) {
68127
68243
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
68128
68244
  }
@@ -68144,6 +68260,7 @@ async function promptsCommand(options) {
68144
68260
  for (const story of stories) {
68145
68261
  const ctx = {
68146
68262
  config: config2,
68263
+ effectiveConfig: config2,
68147
68264
  prd,
68148
68265
  story,
68149
68266
  stories: [story],
@@ -68182,10 +68299,10 @@ ${frontmatter}---
68182
68299
 
68183
68300
  ${ctx.prompt}`;
68184
68301
  if (outputDir) {
68185
- const promptFile = join28(outputDir, `${story.id}.prompt.md`);
68302
+ const promptFile = join29(outputDir, `${story.id}.prompt.md`);
68186
68303
  await Bun.write(promptFile, fullOutput);
68187
68304
  if (ctx.contextMarkdown) {
68188
- const contextFile = join28(outputDir, `${story.id}.context.md`);
68305
+ const contextFile = join29(outputDir, `${story.id}.context.md`);
68189
68306
  const contextOutput = `---
68190
68307
  ${frontmatter}---
68191
68308
 
@@ -68249,7 +68366,7 @@ function buildFrontmatter(story, ctx, role) {
68249
68366
  }
68250
68367
  // src/cli/prompts-init.ts
68251
68368
  import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
68252
- import { join as join29 } from "path";
68369
+ import { join as join30 } from "path";
68253
68370
  var TEMPLATE_ROLES = [
68254
68371
  { file: "test-writer.md", role: "test-writer" },
68255
68372
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -68273,9 +68390,9 @@ var TEMPLATE_HEADER = `<!--
68273
68390
  `;
68274
68391
  async function promptsInitCommand(options) {
68275
68392
  const { workdir, force = false, autoWireConfig = true } = options;
68276
- const templatesDir = join29(workdir, "nax", "templates");
68393
+ const templatesDir = join30(workdir, "nax", "templates");
68277
68394
  mkdirSync4(templatesDir, { recursive: true });
68278
- 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)));
68279
68396
  if (existingFiles.length > 0 && !force) {
68280
68397
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
68281
68398
  Pass --force to overwrite existing templates.`);
@@ -68283,7 +68400,7 @@ async function promptsInitCommand(options) {
68283
68400
  }
68284
68401
  const written = [];
68285
68402
  for (const template of TEMPLATE_ROLES) {
68286
- const filePath = join29(templatesDir, template.file);
68403
+ const filePath = join30(templatesDir, template.file);
68287
68404
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
68288
68405
  const content = TEMPLATE_HEADER + roleBody;
68289
68406
  await Bun.write(filePath, content);
@@ -68299,7 +68416,7 @@ async function promptsInitCommand(options) {
68299
68416
  return written;
68300
68417
  }
68301
68418
  async function autoWirePromptsConfig(workdir) {
68302
- const configPath = join29(workdir, "nax.config.json");
68419
+ const configPath = join30(workdir, "nax.config.json");
68303
68420
  if (!existsSync19(configPath)) {
68304
68421
  const exampleConfig = JSON.stringify({
68305
68422
  prompts: {
@@ -68464,7 +68581,7 @@ init_config();
68464
68581
  init_logger2();
68465
68582
  init_prd();
68466
68583
  import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
68467
- import { join as join33 } from "path";
68584
+ import { join as join34 } from "path";
68468
68585
 
68469
68586
  // src/cli/diagnose-analysis.ts
68470
68587
  function detectFailurePattern(story, prd, status) {
@@ -68663,7 +68780,7 @@ function isProcessAlive2(pid) {
68663
68780
  }
68664
68781
  }
68665
68782
  async function loadStatusFile2(workdir) {
68666
- const statusPath = join33(workdir, "nax", "status.json");
68783
+ const statusPath = join34(workdir, "nax", "status.json");
68667
68784
  if (!existsSync21(statusPath))
68668
68785
  return null;
68669
68786
  try {
@@ -68691,7 +68808,7 @@ async function countCommitsSince(workdir, since) {
68691
68808
  }
68692
68809
  }
68693
68810
  async function checkLock(workdir) {
68694
- const lockFile = Bun.file(join33(workdir, "nax.lock"));
68811
+ const lockFile = Bun.file(join34(workdir, "nax.lock"));
68695
68812
  if (!await lockFile.exists())
68696
68813
  return { lockPresent: false };
68697
68814
  try {
@@ -68709,8 +68826,8 @@ async function diagnoseCommand(options = {}) {
68709
68826
  const logger = getLogger();
68710
68827
  const workdir = options.workdir ?? process.cwd();
68711
68828
  const naxSubdir = findProjectDir(workdir);
68712
- let projectDir = naxSubdir ? join33(naxSubdir, "..") : null;
68713
- if (!projectDir && existsSync21(join33(workdir, "nax"))) {
68829
+ let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
68830
+ if (!projectDir && existsSync21(join34(workdir, "nax"))) {
68714
68831
  projectDir = workdir;
68715
68832
  }
68716
68833
  if (!projectDir)
@@ -68721,7 +68838,7 @@ async function diagnoseCommand(options = {}) {
68721
68838
  if (status2) {
68722
68839
  feature = status2.run.feature;
68723
68840
  } else {
68724
- const featuresDir = join33(projectDir, "nax", "features");
68841
+ const featuresDir = join34(projectDir, "nax", "features");
68725
68842
  if (!existsSync21(featuresDir))
68726
68843
  throw new Error("No features found in project");
68727
68844
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -68731,8 +68848,8 @@ async function diagnoseCommand(options = {}) {
68731
68848
  logger.info("diagnose", "No feature specified, using first found", { feature });
68732
68849
  }
68733
68850
  }
68734
- const featureDir = join33(projectDir, "nax", "features", feature);
68735
- const prdPath = join33(featureDir, "prd.json");
68851
+ const featureDir = join34(projectDir, "nax", "features", feature);
68852
+ const prdPath = join34(featureDir, "prd.json");
68736
68853
  if (!existsSync21(prdPath))
68737
68854
  throw new Error(`Feature not found: ${feature}`);
68738
68855
  const prd = await loadPRD(prdPath);
@@ -68775,7 +68892,7 @@ init_interaction();
68775
68892
  init_source();
68776
68893
  init_loader2();
68777
68894
  import { existsSync as existsSync22 } from "fs";
68778
- import { join as join34 } from "path";
68895
+ import { join as join35 } from "path";
68779
68896
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68780
68897
  async function generateCommand(options) {
68781
68898
  const workdir = process.cwd();
@@ -68818,7 +68935,7 @@ async function generateCommand(options) {
68818
68935
  return;
68819
68936
  }
68820
68937
  if (options.package) {
68821
- const packageDir = join34(workdir, options.package);
68938
+ const packageDir = join35(workdir, options.package);
68822
68939
  if (dryRun) {
68823
68940
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
68824
68941
  }
@@ -68838,8 +68955,8 @@ async function generateCommand(options) {
68838
68955
  process.exit(1);
68839
68956
  return;
68840
68957
  }
68841
- const contextPath = options.context ? join34(workdir, options.context) : join34(workdir, "nax/context.md");
68842
- 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;
68843
68960
  const autoInject = !options.noAutoInject;
68844
68961
  if (!existsSync22(contextPath)) {
68845
68962
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -68944,7 +69061,7 @@ async function generateCommand(options) {
68944
69061
  // src/cli/config-display.ts
68945
69062
  init_loader2();
68946
69063
  import { existsSync as existsSync24 } from "fs";
68947
- import { join as join36 } from "path";
69064
+ import { join as join37 } from "path";
68948
69065
 
68949
69066
  // src/cli/config-descriptions.ts
68950
69067
  var FIELD_DESCRIPTIONS = {
@@ -69153,7 +69270,7 @@ function deepEqual(a, b) {
69153
69270
  init_defaults();
69154
69271
  init_loader2();
69155
69272
  import { existsSync as existsSync23 } from "fs";
69156
- import { join as join35 } from "path";
69273
+ import { join as join36 } from "path";
69157
69274
  async function loadConfigFile(path14) {
69158
69275
  if (!existsSync23(path14))
69159
69276
  return null;
@@ -69175,7 +69292,7 @@ async function loadProjectConfig() {
69175
69292
  const projectDir = findProjectDir();
69176
69293
  if (!projectDir)
69177
69294
  return null;
69178
- const projectPath = join35(projectDir, "config.json");
69295
+ const projectPath = join36(projectDir, "config.json");
69179
69296
  return await loadConfigFile(projectPath);
69180
69297
  }
69181
69298
 
@@ -69235,7 +69352,7 @@ async function configCommand(config2, options = {}) {
69235
69352
  function determineConfigSources() {
69236
69353
  const globalPath = globalConfigPath();
69237
69354
  const projectDir = findProjectDir();
69238
- const projectPath = projectDir ? join36(projectDir, "config.json") : null;
69355
+ const projectPath = projectDir ? join37(projectDir, "config.json") : null;
69239
69356
  return {
69240
69357
  global: fileExists(globalPath) ? globalPath : null,
69241
69358
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -69415,21 +69532,21 @@ async function diagnose(options) {
69415
69532
 
69416
69533
  // src/commands/logs.ts
69417
69534
  import { existsSync as existsSync26 } from "fs";
69418
- import { join as join39 } from "path";
69535
+ import { join as join40 } from "path";
69419
69536
 
69420
69537
  // src/commands/logs-formatter.ts
69421
69538
  init_source();
69422
69539
  init_formatter();
69423
69540
  import { readdirSync as readdirSync7 } from "fs";
69424
- import { join as join38 } from "path";
69541
+ import { join as join39 } from "path";
69425
69542
 
69426
69543
  // src/commands/logs-reader.ts
69427
69544
  import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
69428
69545
  import { readdir as readdir3 } from "fs/promises";
69429
69546
  import { homedir as homedir5 } from "os";
69430
- import { join as join37 } from "path";
69547
+ import { join as join38 } from "path";
69431
69548
  var _deps7 = {
69432
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join37(homedir5(), ".nax", "runs")
69549
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
69433
69550
  };
69434
69551
  async function resolveRunFileFromRegistry(runId) {
69435
69552
  const runsDir = _deps7.getRunsDir();
@@ -69441,7 +69558,7 @@ async function resolveRunFileFromRegistry(runId) {
69441
69558
  }
69442
69559
  let matched = null;
69443
69560
  for (const entry of entries) {
69444
- const metaPath = join37(runsDir, entry, "meta.json");
69561
+ const metaPath = join38(runsDir, entry, "meta.json");
69445
69562
  try {
69446
69563
  const meta3 = await Bun.file(metaPath).json();
69447
69564
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -69463,14 +69580,14 @@ async function resolveRunFileFromRegistry(runId) {
69463
69580
  return null;
69464
69581
  }
69465
69582
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
69466
- return join37(matched.eventsDir, specificFile ?? files[0]);
69583
+ return join38(matched.eventsDir, specificFile ?? files[0]);
69467
69584
  }
69468
69585
  async function selectRunFile(runsDir) {
69469
69586
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
69470
69587
  if (files.length === 0) {
69471
69588
  return null;
69472
69589
  }
69473
- return join37(runsDir, files[0]);
69590
+ return join38(runsDir, files[0]);
69474
69591
  }
69475
69592
  async function extractRunSummary(filePath) {
69476
69593
  const file2 = Bun.file(filePath);
@@ -69555,7 +69672,7 @@ Runs:
69555
69672
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
69556
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"));
69557
69674
  for (const file2 of files) {
69558
- const filePath = join38(runsDir, file2);
69675
+ const filePath = join39(runsDir, file2);
69559
69676
  const summary = await extractRunSummary(filePath);
69560
69677
  const timestamp = file2.replace(".jsonl", "");
69561
69678
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -69680,7 +69797,7 @@ async function logsCommand(options) {
69680
69797
  return;
69681
69798
  }
69682
69799
  const resolved = resolveProject({ dir: options.dir });
69683
- const naxDir = join39(resolved.projectDir, "nax");
69800
+ const naxDir = join40(resolved.projectDir, "nax");
69684
69801
  const configPath = resolved.configPath;
69685
69802
  const configFile = Bun.file(configPath);
69686
69803
  const config2 = await configFile.json();
@@ -69688,8 +69805,8 @@ async function logsCommand(options) {
69688
69805
  if (!featureName) {
69689
69806
  throw new Error("No feature specified in config.json");
69690
69807
  }
69691
- const featureDir = join39(naxDir, "features", featureName);
69692
- const runsDir = join39(featureDir, "runs");
69808
+ const featureDir = join40(naxDir, "features", featureName);
69809
+ const runsDir = join40(featureDir, "runs");
69693
69810
  if (!existsSync26(runsDir)) {
69694
69811
  throw new Error(`No runs directory found for feature: ${featureName}`);
69695
69812
  }
@@ -69714,7 +69831,7 @@ init_config();
69714
69831
  init_prd();
69715
69832
  init_precheck();
69716
69833
  import { existsSync as existsSync31 } from "fs";
69717
- import { join as join40 } from "path";
69834
+ import { join as join41 } from "path";
69718
69835
  async function precheckCommand(options) {
69719
69836
  const resolved = resolveProject({
69720
69837
  dir: options.dir,
@@ -69730,9 +69847,9 @@ async function precheckCommand(options) {
69730
69847
  process.exit(1);
69731
69848
  }
69732
69849
  }
69733
- const naxDir = join40(resolved.projectDir, "nax");
69734
- const featureDir = join40(naxDir, "features", featureName);
69735
- 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");
69736
69853
  if (!existsSync31(featureDir)) {
69737
69854
  console.error(source_default.red(`Feature not found: ${featureName}`));
69738
69855
  process.exit(1);
@@ -69756,10 +69873,10 @@ async function precheckCommand(options) {
69756
69873
  init_source();
69757
69874
  import { readdir as readdir4 } from "fs/promises";
69758
69875
  import { homedir as homedir6 } from "os";
69759
- import { join as join41 } from "path";
69876
+ import { join as join42 } from "path";
69760
69877
  var DEFAULT_LIMIT = 20;
69761
69878
  var _deps9 = {
69762
- getRunsDir: () => join41(homedir6(), ".nax", "runs")
69879
+ getRunsDir: () => join42(homedir6(), ".nax", "runs")
69763
69880
  };
69764
69881
  function formatDuration3(ms) {
69765
69882
  if (ms <= 0)
@@ -69811,7 +69928,7 @@ async function runsCommand(options = {}) {
69811
69928
  }
69812
69929
  const rows = [];
69813
69930
  for (const entry of entries) {
69814
- const metaPath = join41(runsDir, entry, "meta.json");
69931
+ const metaPath = join42(runsDir, entry, "meta.json");
69815
69932
  let meta3;
69816
69933
  try {
69817
69934
  meta3 = await Bun.file(metaPath).json();
@@ -69888,7 +70005,7 @@ async function runsCommand(options = {}) {
69888
70005
 
69889
70006
  // src/commands/unlock.ts
69890
70007
  init_source();
69891
- import { join as join42 } from "path";
70008
+ import { join as join43 } from "path";
69892
70009
  function isProcessAlive3(pid) {
69893
70010
  try {
69894
70011
  process.kill(pid, 0);
@@ -69903,7 +70020,7 @@ function formatLockAge(ageMs) {
69903
70020
  }
69904
70021
  async function unlockCommand(options) {
69905
70022
  const workdir = options.dir ?? process.cwd();
69906
- const lockPath = join42(workdir, "nax.lock");
70023
+ const lockPath = join43(workdir, "nax.lock");
69907
70024
  const lockFile = Bun.file(lockPath);
69908
70025
  const exists = await lockFile.exists();
69909
70026
  if (!exists) {
@@ -77738,15 +77855,15 @@ Next: nax generate --package ${options.package}`));
77738
77855
  }
77739
77856
  return;
77740
77857
  }
77741
- const naxDir = join49(workdir, "nax");
77858
+ const naxDir = join51(workdir, "nax");
77742
77859
  if (existsSync34(naxDir) && !options.force) {
77743
77860
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
77744
77861
  return;
77745
77862
  }
77746
- mkdirSync6(join49(naxDir, "features"), { recursive: true });
77747
- mkdirSync6(join49(naxDir, "hooks"), { recursive: true });
77748
- await Bun.write(join49(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77749
- 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({
77750
77867
  hooks: {
77751
77868
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
77752
77869
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -77754,12 +77871,12 @@ Next: nax generate --package ${options.package}`));
77754
77871
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
77755
77872
  }
77756
77873
  }, null, 2));
77757
- await Bun.write(join49(naxDir, ".gitignore"), `# nax temp files
77874
+ await Bun.write(join51(naxDir, ".gitignore"), `# nax temp files
77758
77875
  *.tmp
77759
77876
  .paused.json
77760
77877
  .nax-verifier-verdict.json
77761
77878
  `);
77762
- await Bun.write(join49(naxDir, "context.md"), `# Project Context
77879
+ await Bun.write(join51(naxDir, "context.md"), `# Project Context
77763
77880
 
77764
77881
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
77765
77882
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -77885,8 +78002,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
77885
78002
  console.error(source_default.red("nax not initialized. Run: nax init"));
77886
78003
  process.exit(1);
77887
78004
  }
77888
- const featureDir = join49(naxDir, "features", options.feature);
77889
- const prdPath = join49(featureDir, "prd.json");
78005
+ const featureDir = join51(naxDir, "features", options.feature);
78006
+ const prdPath = join51(featureDir, "prd.json");
77890
78007
  if (options.plan && options.from) {
77891
78008
  if (existsSync34(prdPath) && !options.force) {
77892
78009
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -77908,10 +78025,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77908
78025
  }
77909
78026
  }
77910
78027
  try {
77911
- const planLogDir = join49(featureDir, "plan");
78028
+ const planLogDir = join51(featureDir, "plan");
77912
78029
  mkdirSync6(planLogDir, { recursive: true });
77913
78030
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77914
- const planLogPath = join49(planLogDir, `${planLogId}.jsonl`);
78031
+ const planLogPath = join51(planLogDir, `${planLogId}.jsonl`);
77915
78032
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77916
78033
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77917
78034
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -77949,10 +78066,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77949
78066
  process.exit(1);
77950
78067
  }
77951
78068
  resetLogger();
77952
- const runsDir = join49(featureDir, "runs");
78069
+ const runsDir = join51(featureDir, "runs");
77953
78070
  mkdirSync6(runsDir, { recursive: true });
77954
78071
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77955
- const logFilePath = join49(runsDir, `${runId}.jsonl`);
78072
+ const logFilePath = join51(runsDir, `${runId}.jsonl`);
77956
78073
  const isTTY = process.stdout.isTTY ?? false;
77957
78074
  const headlessFlag = options.headless ?? false;
77958
78075
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -77968,7 +78085,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77968
78085
  config2.autoMode.defaultAgent = options.agent;
77969
78086
  }
77970
78087
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
77971
- const globalNaxDir = join49(homedir10(), ".nax");
78088
+ const globalNaxDir = join51(homedir10(), ".nax");
77972
78089
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
77973
78090
  const eventEmitter = new PipelineEventEmitter;
77974
78091
  let tuiInstance;
@@ -77991,7 +78108,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77991
78108
  } else {
77992
78109
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
77993
78110
  }
77994
- const statusFilePath = join49(workdir, "nax", "status.json");
78111
+ const statusFilePath = join51(workdir, "nax", "status.json");
77995
78112
  let parallel;
77996
78113
  if (options.parallel !== undefined) {
77997
78114
  parallel = Number.parseInt(options.parallel, 10);
@@ -78017,7 +78134,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78017
78134
  headless: useHeadless,
78018
78135
  skipPrecheck: options.skipPrecheck ?? false
78019
78136
  });
78020
- const latestSymlink = join49(runsDir, "latest.jsonl");
78137
+ const latestSymlink = join51(runsDir, "latest.jsonl");
78021
78138
  try {
78022
78139
  if (existsSync34(latestSymlink)) {
78023
78140
  Bun.spawnSync(["rm", latestSymlink]);
@@ -78055,9 +78172,9 @@ features.command("create <name>").description("Create a new feature").option("-d
78055
78172
  console.error(source_default.red("nax not initialized. Run: nax init"));
78056
78173
  process.exit(1);
78057
78174
  }
78058
- const featureDir = join49(naxDir, "features", name);
78175
+ const featureDir = join51(naxDir, "features", name);
78059
78176
  mkdirSync6(featureDir, { recursive: true });
78060
- await Bun.write(join49(featureDir, "spec.md"), `# Feature: ${name}
78177
+ await Bun.write(join51(featureDir, "spec.md"), `# Feature: ${name}
78061
78178
 
78062
78179
  ## Overview
78063
78180
 
@@ -78065,7 +78182,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78065
78182
 
78066
78183
  ## Acceptance Criteria
78067
78184
  `);
78068
- await Bun.write(join49(featureDir, "plan.md"), `# Plan: ${name}
78185
+ await Bun.write(join51(featureDir, "plan.md"), `# Plan: ${name}
78069
78186
 
78070
78187
  ## Architecture
78071
78188
 
@@ -78073,7 +78190,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78073
78190
 
78074
78191
  ## Dependencies
78075
78192
  `);
78076
- await Bun.write(join49(featureDir, "tasks.md"), `# Tasks: ${name}
78193
+ await Bun.write(join51(featureDir, "tasks.md"), `# Tasks: ${name}
78077
78194
 
78078
78195
  ## US-001: [Title]
78079
78196
 
@@ -78082,7 +78199,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78082
78199
  ### Acceptance Criteria
78083
78200
  - [ ] Criterion 1
78084
78201
  `);
78085
- await Bun.write(join49(featureDir, "progress.txt"), `# Progress: ${name}
78202
+ await Bun.write(join51(featureDir, "progress.txt"), `# Progress: ${name}
78086
78203
 
78087
78204
  Created: ${new Date().toISOString()}
78088
78205
 
@@ -78110,7 +78227,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78110
78227
  console.error(source_default.red("nax not initialized."));
78111
78228
  process.exit(1);
78112
78229
  }
78113
- const featuresDir = join49(naxDir, "features");
78230
+ const featuresDir = join51(naxDir, "features");
78114
78231
  if (!existsSync34(featuresDir)) {
78115
78232
  console.log(source_default.dim("No features yet."));
78116
78233
  return;
@@ -78125,7 +78242,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78125
78242
  Features:
78126
78243
  `));
78127
78244
  for (const name of entries) {
78128
- const prdPath = join49(featuresDir, name, "prd.json");
78245
+ const prdPath = join51(featuresDir, name, "prd.json");
78129
78246
  if (existsSync34(prdPath)) {
78130
78247
  const prd = await loadPRD(prdPath);
78131
78248
  const c = countStories(prd);
@@ -78156,10 +78273,10 @@ Use: nax plan -f <feature> --from <spec>`));
78156
78273
  process.exit(1);
78157
78274
  }
78158
78275
  const config2 = await loadConfig(workdir);
78159
- const featureLogDir = join49(naxDir, "features", options.feature, "plan");
78276
+ const featureLogDir = join51(naxDir, "features", options.feature, "plan");
78160
78277
  mkdirSync6(featureLogDir, { recursive: true });
78161
78278
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78162
- const planLogPath = join49(featureLogDir, `${planLogId}.jsonl`);
78279
+ const planLogPath = join51(featureLogDir, `${planLogId}.jsonl`);
78163
78280
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
78164
78281
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
78165
78282
  try {
@@ -78196,7 +78313,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78196
78313
  console.error(source_default.red("nax not initialized. Run: nax init"));
78197
78314
  process.exit(1);
78198
78315
  }
78199
- const featureDir = join49(naxDir, "features", options.feature);
78316
+ const featureDir = join51(naxDir, "features", options.feature);
78200
78317
  if (!existsSync34(featureDir)) {
78201
78318
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
78202
78319
  process.exit(1);
@@ -78212,7 +78329,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78212
78329
  specPath: options.from,
78213
78330
  reclassify: options.reclassify
78214
78331
  });
78215
- const prdPath = join49(featureDir, "prd.json");
78332
+ const prdPath = join51(featureDir, "prd.json");
78216
78333
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
78217
78334
  const c = countStories(prd);
78218
78335
  console.log(source_default.green(`