@nathapp/nax 0.48.4 → 0.49.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,14 @@ 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, {
18733
+ model: options.modelDef.model,
18734
+ config: options.config
18735
+ });
18736
+ const testCode = extractTestCode(rawOutput);
18735
18737
  const refinedJsonContent = JSON.stringify(refinedCriteria.map((c, i) => ({
18736
18738
  acId: `AC-${i + 1}`,
18737
18739
  original: c.original,
@@ -19171,6 +19173,8 @@ class SpawnAcpSession {
19171
19173
  "acpx",
19172
19174
  "--cwd",
19173
19175
  this.cwd,
19176
+ "--format",
19177
+ "json",
19174
19178
  ...this.permissionMode === "approve-all" ? ["--approve-all"] : [],
19175
19179
  "--model",
19176
19180
  this.model,
@@ -19743,7 +19747,11 @@ class AcpAgentAdapter {
19743
19747
  await client.start();
19744
19748
  let session = null;
19745
19749
  try {
19746
- session = await client.createSession({ agentName: this.name, permissionMode });
19750
+ session = await client.createSession({
19751
+ agentName: this.name,
19752
+ permissionMode,
19753
+ sessionName: _options?.sessionName
19754
+ });
19747
19755
  let timeoutId;
19748
19756
  const timeoutPromise = new Promise((_, reject) => {
19749
19757
  timeoutId = setTimeout(() => reject(new Error(`complete() timed out after ${timeoutMs}ms`)), timeoutMs);
@@ -20697,17 +20705,49 @@ var init_json_file = __esm(() => {
20697
20705
 
20698
20706
  // src/config/merge.ts
20699
20707
  function mergePackageConfig(root, packageOverride) {
20700
- const packageCommands = packageOverride.quality?.commands;
20701
- if (!packageCommands) {
20708
+ const hasAnyMergeableField = packageOverride.execution !== undefined || packageOverride.review !== undefined || packageOverride.acceptance !== undefined || packageOverride.quality !== undefined || packageOverride.context !== undefined;
20709
+ if (!hasAnyMergeableField) {
20702
20710
  return root;
20703
20711
  }
20704
20712
  return {
20705
20713
  ...root,
20714
+ execution: {
20715
+ ...root.execution,
20716
+ ...packageOverride.execution,
20717
+ smartTestRunner: packageOverride.execution?.smartTestRunner ?? root.execution.smartTestRunner,
20718
+ regressionGate: {
20719
+ ...root.execution.regressionGate,
20720
+ ...packageOverride.execution?.regressionGate
20721
+ },
20722
+ verificationTimeoutSeconds: packageOverride.execution?.verificationTimeoutSeconds ?? root.execution.verificationTimeoutSeconds
20723
+ },
20724
+ review: {
20725
+ ...root.review,
20726
+ ...packageOverride.review,
20727
+ commands: {
20728
+ ...root.review.commands,
20729
+ ...packageOverride.review?.commands
20730
+ }
20731
+ },
20732
+ acceptance: {
20733
+ ...root.acceptance,
20734
+ ...packageOverride.acceptance
20735
+ },
20706
20736
  quality: {
20707
20737
  ...root.quality,
20738
+ requireTests: packageOverride.quality?.requireTests ?? root.quality.requireTests,
20739
+ requireTypecheck: packageOverride.quality?.requireTypecheck ?? root.quality.requireTypecheck,
20740
+ requireLint: packageOverride.quality?.requireLint ?? root.quality.requireLint,
20708
20741
  commands: {
20709
20742
  ...root.quality.commands,
20710
- ...packageCommands
20743
+ ...packageOverride.quality?.commands
20744
+ }
20745
+ },
20746
+ context: {
20747
+ ...root.context,
20748
+ testCoverage: {
20749
+ ...root.context.testCoverage,
20750
+ ...packageOverride.context?.testCoverage
20711
20751
  }
20712
20752
  }
20713
20753
  };
@@ -22210,7 +22250,7 @@ var package_default;
22210
22250
  var init_package = __esm(() => {
22211
22251
  package_default = {
22212
22252
  name: "@nathapp/nax",
22213
- version: "0.48.4",
22253
+ version: "0.49.1",
22214
22254
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22215
22255
  type: "module",
22216
22256
  bin: {
@@ -22283,8 +22323,8 @@ var init_version = __esm(() => {
22283
22323
  NAX_VERSION = package_default.version;
22284
22324
  NAX_COMMIT = (() => {
22285
22325
  try {
22286
- if (/^[0-9a-f]{6,10}$/.test("59e08c0"))
22287
- return "59e08c0";
22326
+ if (/^[0-9a-f]{6,10}$/.test("635a552"))
22327
+ return "635a552";
22288
22328
  } catch {}
22289
22329
  try {
22290
22330
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23933,7 +23973,8 @@ var init_acceptance2 = __esm(() => {
23933
23973
  acceptanceStage = {
23934
23974
  name: "acceptance",
23935
23975
  enabled(ctx) {
23936
- if (!ctx.config.acceptance.enabled) {
23976
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
23977
+ if (!effectiveConfig.acceptance.enabled) {
23937
23978
  return false;
23938
23979
  }
23939
23980
  if (!areAllStoriesComplete(ctx)) {
@@ -23943,12 +23984,13 @@ var init_acceptance2 = __esm(() => {
23943
23984
  },
23944
23985
  async execute(ctx) {
23945
23986
  const logger = getLogger();
23987
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
23946
23988
  logger.info("acceptance", "Running acceptance tests");
23947
23989
  if (!ctx.featureDir) {
23948
23990
  logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
23949
23991
  return { action: "continue" };
23950
23992
  }
23951
- const testPath = path4.join(ctx.featureDir, ctx.config.acceptance.testPath);
23993
+ const testPath = path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath);
23952
23994
  const testFile = Bun.file(testPath);
23953
23995
  const exists = await testFile.exists();
23954
23996
  if (!exists) {
@@ -24577,12 +24619,13 @@ var init_review = __esm(() => {
24577
24619
  init_orchestrator();
24578
24620
  reviewStage = {
24579
24621
  name: "review",
24580
- enabled: (ctx) => ctx.config.review.enabled,
24622
+ enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
24581
24623
  async execute(ctx) {
24582
24624
  const logger = getLogger();
24625
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24583
24626
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
24584
24627
  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);
24628
+ const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
24586
24629
  ctx.reviewResult = result.builtIn;
24587
24630
  if (!result.success) {
24588
24631
  const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
@@ -24590,8 +24633,8 @@ var init_review = __esm(() => {
24590
24633
  ctx.reviewFindings = allFindings;
24591
24634
  }
24592
24635
  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);
24636
+ if (ctx.interaction && isTriggerEnabled("security-review", effectiveConfig)) {
24637
+ const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, effectiveConfig, ctx.interaction);
24595
24638
  if (!shouldContinue) {
24596
24639
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
24597
24640
  return { action: "fail", reason: `Review failed: ${result.failureReason}` };
@@ -24602,11 +24645,11 @@ var init_review = __esm(() => {
24602
24645
  logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
24603
24646
  return { action: "fail", reason: `Review failed: ${result.failureReason}` };
24604
24647
  }
24605
- logger.warn("review", "Review failed (built-in checks) \u2014 escalating for retry", {
24648
+ logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
24606
24649
  reason: result.failureReason,
24607
24650
  storyId: ctx.story.id
24608
24651
  });
24609
- return { action: "escalate", reason: `Review failed: ${result.failureReason}` };
24652
+ return { action: "continue" };
24610
24653
  }
24611
24654
  logger.info("review", "Review passed", {
24612
24655
  durationMs: result.builtIn.totalDurationMs,
@@ -24621,6 +24664,7 @@ var init_review = __esm(() => {
24621
24664
  });
24622
24665
 
24623
24666
  // src/pipeline/stages/autofix.ts
24667
+ import { join as join18 } from "path";
24624
24668
  async function runCommand(cmd, cwd) {
24625
24669
  const parts = cmd.split(/\s+/);
24626
24670
  const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
@@ -24663,7 +24707,8 @@ Commit your fixes when done.`;
24663
24707
  }
24664
24708
  async function runAgentRectification(ctx) {
24665
24709
  const logger = getLogger();
24666
- const maxAttempts = ctx.config.quality.autofix?.maxAttempts ?? 2;
24710
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24711
+ const maxAttempts = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
24667
24712
  const failedChecks = collectFailedChecks(ctx);
24668
24713
  if (failedChecks.length === 0) {
24669
24714
  logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
@@ -24720,6 +24765,7 @@ var autofixStage, _autofixDeps;
24720
24765
  var init_autofix = __esm(() => {
24721
24766
  init_agents();
24722
24767
  init_config();
24768
+ init_loader2();
24723
24769
  init_logger2();
24724
24770
  init_event_bus();
24725
24771
  autofixStage = {
@@ -24729,7 +24775,7 @@ var init_autofix = __esm(() => {
24729
24775
  return false;
24730
24776
  if (ctx.reviewResult.success)
24731
24777
  return false;
24732
- const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
24778
+ const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
24733
24779
  return autofixEnabled;
24734
24780
  },
24735
24781
  skipReason(ctx) {
@@ -24743,12 +24789,14 @@ var init_autofix = __esm(() => {
24743
24789
  if (!reviewResult || reviewResult.success) {
24744
24790
  return { action: "continue" };
24745
24791
  }
24746
- const lintFixCmd = ctx.config.quality.commands.lintFix;
24747
- const formatFixCmd = ctx.config.quality.commands.formatFix;
24792
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24793
+ const lintFixCmd = effectiveConfig.quality.commands.lintFix;
24794
+ const formatFixCmd = effectiveConfig.quality.commands.formatFix;
24795
+ const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
24748
24796
  if (lintFixCmd || formatFixCmd) {
24749
24797
  if (lintFixCmd) {
24750
24798
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
24751
- const lintResult = await _autofixDeps.runCommand(lintFixCmd, ctx.workdir);
24799
+ const lintResult = await _autofixDeps.runCommand(lintFixCmd, effectiveWorkdir);
24752
24800
  logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id });
24753
24801
  if (lintResult.exitCode !== 0) {
24754
24802
  logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
@@ -24759,7 +24807,7 @@ var init_autofix = __esm(() => {
24759
24807
  }
24760
24808
  if (formatFixCmd) {
24761
24809
  pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
24762
- const fmtResult = await _autofixDeps.runCommand(formatFixCmd, ctx.workdir);
24810
+ const fmtResult = await _autofixDeps.runCommand(formatFixCmd, effectiveWorkdir);
24763
24811
  logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, { storyId: ctx.story.id });
24764
24812
  if (fmtResult.exitCode !== 0) {
24765
24813
  logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
@@ -24788,15 +24836,15 @@ var init_autofix = __esm(() => {
24788
24836
  return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
24789
24837
  }
24790
24838
  };
24791
- _autofixDeps = { runCommand, recheckReview, runAgentRectification };
24839
+ _autofixDeps = { runCommand, recheckReview, runAgentRectification, loadConfigForWorkdir };
24792
24840
  });
24793
24841
 
24794
24842
  // src/execution/progress.ts
24795
24843
  import { mkdirSync as mkdirSync2 } from "fs";
24796
- import { join as join18 } from "path";
24844
+ import { join as join19 } from "path";
24797
24845
  async function appendProgress(featureDir, storyId, status, message) {
24798
24846
  mkdirSync2(featureDir, { recursive: true });
24799
- const progressPath = join18(featureDir, "progress.txt");
24847
+ const progressPath = join19(featureDir, "progress.txt");
24800
24848
  const timestamp = new Date().toISOString();
24801
24849
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
24802
24850
  `;
@@ -24880,7 +24928,7 @@ function estimateTokens(text) {
24880
24928
 
24881
24929
  // src/constitution/loader.ts
24882
24930
  import { existsSync as existsSync15 } from "fs";
24883
- import { join as join19 } from "path";
24931
+ import { join as join20 } from "path";
24884
24932
  function truncateToTokens(text, maxTokens) {
24885
24933
  const maxChars = maxTokens * 3;
24886
24934
  if (text.length <= maxChars) {
@@ -24902,7 +24950,7 @@ async function loadConstitution(projectDir, config2) {
24902
24950
  }
24903
24951
  let combinedContent = "";
24904
24952
  if (!config2.skipGlobal) {
24905
- const globalPath = join19(globalConfigDir(), config2.path);
24953
+ const globalPath = join20(globalConfigDir(), config2.path);
24906
24954
  if (existsSync15(globalPath)) {
24907
24955
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
24908
24956
  const globalFile = Bun.file(validatedPath);
@@ -24912,7 +24960,7 @@ async function loadConstitution(projectDir, config2) {
24912
24960
  }
24913
24961
  }
24914
24962
  }
24915
- const projectPath = join19(projectDir, config2.path);
24963
+ const projectPath = join20(projectDir, config2.path);
24916
24964
  if (existsSync15(projectPath)) {
24917
24965
  const validatedPath = validateFilePath(projectPath, projectDir);
24918
24966
  const projectFile = Bun.file(validatedPath);
@@ -25884,7 +25932,7 @@ var init_helpers = __esm(() => {
25884
25932
  });
25885
25933
 
25886
25934
  // src/pipeline/stages/context.ts
25887
- import { join as join20 } from "path";
25935
+ import { join as join21 } from "path";
25888
25936
  var contextStage;
25889
25937
  var init_context2 = __esm(() => {
25890
25938
  init_helpers();
@@ -25894,7 +25942,7 @@ var init_context2 = __esm(() => {
25894
25942
  enabled: () => true,
25895
25943
  async execute(ctx) {
25896
25944
  const logger = getLogger();
25897
- const packageWorkdir = ctx.story.workdir ? join20(ctx.workdir, ctx.story.workdir) : undefined;
25945
+ const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
25898
25946
  const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
25899
25947
  if (result) {
25900
25948
  ctx.contextMarkdown = result.markdown;
@@ -26026,14 +26074,14 @@ var init_isolation = __esm(() => {
26026
26074
 
26027
26075
  // src/context/greenfield.ts
26028
26076
  import { readdir } from "fs/promises";
26029
- import { join as join21 } from "path";
26077
+ import { join as join22 } from "path";
26030
26078
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
26031
26079
  const results = [];
26032
26080
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
26033
26081
  try {
26034
26082
  const entries = await readdir(dir, { withFileTypes: true });
26035
26083
  for (const entry of entries) {
26036
- const fullPath = join21(dir, entry.name);
26084
+ const fullPath = join22(dir, entry.name);
26037
26085
  if (entry.isDirectory()) {
26038
26086
  if (ignoreDirs.has(entry.name))
26039
26087
  continue;
@@ -26139,7 +26187,9 @@ async function autoCommitIfDirty(workdir, stage, role, storyId) {
26139
26187
  return gitRoot;
26140
26188
  }
26141
26189
  })();
26142
- if (realWorkdir !== realGitRoot)
26190
+ const isAtRoot = realWorkdir === realGitRoot;
26191
+ const isSubdir = realGitRoot && realWorkdir.startsWith(`${realGitRoot}/`);
26192
+ if (!isAtRoot && !isSubdir)
26143
26193
  return;
26144
26194
  const statusProc = _gitDeps.spawn(["git", "status", "--porcelain"], {
26145
26195
  cwd: workdir,
@@ -26156,7 +26206,8 @@ async function autoCommitIfDirty(workdir, stage, role, storyId) {
26156
26206
  dirtyFiles: statusOutput.trim().split(`
26157
26207
  `).length
26158
26208
  });
26159
- const addProc = _gitDeps.spawn(["git", "add", "-A"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
26209
+ const addArgs = isSubdir ? ["git", "add", "."] : ["git", "add", "-A"];
26210
+ const addProc = _gitDeps.spawn(addArgs, { cwd: workdir, stdout: "pipe", stderr: "pipe" });
26160
26211
  await addProc.exited;
26161
26212
  const commitProc = _gitDeps.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
26162
26213
  cwd: workdir,
@@ -26445,13 +26496,13 @@ function parseTestOutput(output, exitCode) {
26445
26496
 
26446
26497
  // src/verification/runners.ts
26447
26498
  import { existsSync as existsSync16 } from "fs";
26448
- import { join as join22 } from "path";
26499
+ import { join as join23 } from "path";
26449
26500
  async function verifyAssets(workingDirectory, expectedFiles) {
26450
26501
  if (!expectedFiles || expectedFiles.length === 0)
26451
26502
  return { success: true, missingFiles: [] };
26452
26503
  const missingFiles = [];
26453
26504
  for (const file2 of expectedFiles) {
26454
- if (!existsSync16(join22(workingDirectory, file2)))
26505
+ if (!existsSync16(join23(workingDirectory, file2)))
26455
26506
  missingFiles.push(file2);
26456
26507
  }
26457
26508
  if (missingFiles.length > 0) {
@@ -27136,13 +27187,13 @@ var exports_loader = {};
27136
27187
  __export(exports_loader, {
27137
27188
  loadOverride: () => loadOverride
27138
27189
  });
27139
- import { join as join23 } from "path";
27190
+ import { join as join24 } from "path";
27140
27191
  async function loadOverride(role, workdir, config2) {
27141
27192
  const overridePath = config2.prompts?.overrides?.[role];
27142
27193
  if (!overridePath) {
27143
27194
  return null;
27144
27195
  }
27145
- const absolutePath = join23(workdir, overridePath);
27196
+ const absolutePath = join24(workdir, overridePath);
27146
27197
  const file2 = Bun.file(absolutePath);
27147
27198
  if (!await file2.exists()) {
27148
27199
  return null;
@@ -27964,11 +28015,11 @@ var init_tdd = __esm(() => {
27964
28015
 
27965
28016
  // src/pipeline/stages/execution.ts
27966
28017
  import { existsSync as existsSync17 } from "fs";
27967
- import { join as join24 } from "path";
28018
+ import { join as join25 } from "path";
27968
28019
  function resolveStoryWorkdir(repoRoot, storyWorkdir) {
27969
28020
  if (!storyWorkdir)
27970
28021
  return repoRoot;
27971
- const resolved = join24(repoRoot, storyWorkdir);
28022
+ const resolved = join25(repoRoot, storyWorkdir);
27972
28023
  if (!existsSync17(resolved)) {
27973
28024
  throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
27974
28025
  }
@@ -27998,6 +28049,9 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
27998
28049
  if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
27999
28050
  return { action: "escalate" };
28000
28051
  }
28052
+ if (failureCategory === "greenfield-no-tests") {
28053
+ return { action: "escalate" };
28054
+ }
28001
28055
  return {
28002
28056
  action: "pause",
28003
28057
  reason: reviewReason || "Three-session TDD requires review"
@@ -28438,13 +28492,14 @@ var init_prompt = __esm(() => {
28438
28492
  async execute(ctx) {
28439
28493
  const logger = getLogger();
28440
28494
  const isBatch = ctx.stories.length > 1;
28495
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28441
28496
  let prompt;
28442
28497
  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);
28498
+ 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
28499
  prompt = await builder.build();
28445
28500
  } else {
28446
28501
  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);
28502
+ 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
28503
  prompt = await builder.build();
28449
28504
  }
28450
28505
  ctx.prompt = prompt;
@@ -28788,13 +28843,14 @@ var init_rectify = __esm(() => {
28788
28843
  attempt: rectifyAttempt,
28789
28844
  testOutput
28790
28845
  });
28791
- const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test ?? "bun test";
28846
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
28847
+ const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
28792
28848
  const fixed = await _rectifyDeps.runRectificationLoop({
28793
28849
  config: ctx.config,
28794
28850
  workdir: ctx.workdir,
28795
28851
  story: ctx.story,
28796
28852
  testCommand,
28797
- timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
28853
+ timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
28798
28854
  testOutput
28799
28855
  });
28800
28856
  pipelineEventBus.emit({
@@ -29322,31 +29378,34 @@ var init_regression2 = __esm(() => {
29322
29378
  regressionStage = {
29323
29379
  name: "regression",
29324
29380
  enabled(ctx) {
29325
- const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29381
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29382
+ const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29326
29383
  if (mode !== "per-story")
29327
29384
  return false;
29328
29385
  if (ctx.verifyResult && !ctx.verifyResult.success)
29329
29386
  return false;
29330
- const gateEnabled = ctx.config.execution.regressionGate?.enabled ?? true;
29387
+ const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
29331
29388
  return gateEnabled;
29332
29389
  },
29333
29390
  skipReason(ctx) {
29334
- const mode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29391
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29392
+ const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29335
29393
  if (mode !== "per-story")
29336
29394
  return `not needed (regression mode is '${mode}', not 'per-story')`;
29337
29395
  return "disabled (regression gate not enabled in config)";
29338
29396
  },
29339
29397
  async execute(ctx) {
29340
29398
  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;
29399
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29400
+ const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
29401
+ const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
29343
29402
  logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
29344
29403
  const verifyCtx = {
29345
29404
  workdir: ctx.workdir,
29346
29405
  testCommand,
29347
29406
  timeoutSeconds,
29348
29407
  storyId: ctx.story.id,
29349
- acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true,
29408
+ acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
29350
29409
  config: ctx.config
29351
29410
  };
29352
29411
  const result = await _regressionStageDeps.verifyRegression(verifyCtx);
@@ -29381,7 +29440,7 @@ var init_regression2 = __esm(() => {
29381
29440
  });
29382
29441
 
29383
29442
  // src/pipeline/stages/routing.ts
29384
- import { join as join25 } from "path";
29443
+ import { join as join26 } from "path";
29385
29444
  async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
29386
29445
  const naxDecompose = config2.decompose;
29387
29446
  const builderConfig = {
@@ -29393,9 +29452,24 @@ async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
29393
29452
  if (!agent) {
29394
29453
  throw new Error(`[decompose] Agent "${config2.autoMode.defaultAgent}" not found \u2014 cannot decompose`);
29395
29454
  }
29455
+ const decomposeTier = naxDecompose?.model ?? "balanced";
29456
+ let decomposeModel;
29457
+ try {
29458
+ const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
29459
+ const models = config2.models;
29460
+ const entry = models[decomposeTier] ?? models.balanced;
29461
+ if (entry)
29462
+ decomposeModel = resolveModel2(entry).model;
29463
+ } catch {}
29464
+ const storySessionName = `nax-decompose-${story.id.toLowerCase()}`;
29396
29465
  const adapter = {
29397
29466
  async decompose(prompt) {
29398
- return agent.complete(prompt, { jsonMode: true, config: config2 });
29467
+ return agent.complete(prompt, {
29468
+ model: decomposeModel,
29469
+ jsonMode: true,
29470
+ config: config2,
29471
+ sessionName: storySessionName
29472
+ });
29399
29473
  }
29400
29474
  };
29401
29475
  return DecomposeBuilder.for(story).prd(prd).config(builderConfig).decompose(adapter);
@@ -29455,7 +29529,7 @@ var init_routing2 = __esm(() => {
29455
29529
  }
29456
29530
  const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
29457
29531
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
29458
- const greenfieldScanDir = ctx.story.workdir ? join25(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29532
+ const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29459
29533
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
29460
29534
  if (isGreenfield) {
29461
29535
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -29552,7 +29626,7 @@ var init_crash_detector = __esm(() => {
29552
29626
  });
29553
29627
 
29554
29628
  // src/pipeline/stages/verify.ts
29555
- import { join as join26 } from "path";
29629
+ import { join as join27 } from "path";
29556
29630
  function coerceSmartTestRunner(val) {
29557
29631
  if (val === undefined || val === true)
29558
29632
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -29568,7 +29642,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
29568
29642
  }
29569
29643
  async function readPackageName(dir) {
29570
29644
  try {
29571
- const content = await Bun.file(join26(dir, "package.json")).json();
29645
+ const content = await Bun.file(join27(dir, "package.json")).json();
29572
29646
  return typeof content.name === "string" ? content.name : null;
29573
29647
  } catch {
29574
29648
  return null;
@@ -29585,7 +29659,6 @@ async function resolvePackageTemplate(template, packageDir) {
29585
29659
  }
29586
29660
  var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
29587
29661
  var init_verify = __esm(() => {
29588
- init_loader2();
29589
29662
  init_logger2();
29590
29663
  init_crash_detector();
29591
29664
  init_runners();
@@ -29602,7 +29675,7 @@ var init_verify = __esm(() => {
29602
29675
  skipReason: () => "not needed (full-suite gate already passed)",
29603
29676
  async execute(ctx) {
29604
29677
  const logger = getLogger();
29605
- const effectiveConfig = ctx.story.workdir ? await _verifyDeps.loadConfigForWorkdir(join26(ctx.workdir, "nax", "config.json"), ctx.story.workdir) : ctx.config;
29678
+ const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
29606
29679
  if (!effectiveConfig.quality.requireTests) {
29607
29680
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
29608
29681
  return { action: "continue" };
@@ -29614,11 +29687,11 @@ var init_verify = __esm(() => {
29614
29687
  return { action: "continue" };
29615
29688
  }
29616
29689
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
29617
- const effectiveWorkdir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29690
+ const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29618
29691
  let effectiveCommand = testCommand;
29619
29692
  let isFullSuite = true;
29620
- const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
29621
- const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29693
+ const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
29694
+ const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
29622
29695
  let resolvedTestScopedTemplate = testScopedTemplate;
29623
29696
  if (testScopedTemplate && ctx.story.workdir) {
29624
29697
  const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
@@ -29677,8 +29750,8 @@ var init_verify = __esm(() => {
29677
29750
  const result = await _verifyDeps.regression({
29678
29751
  workdir: effectiveWorkdir,
29679
29752
  command: effectiveCommand,
29680
- timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
29681
- acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
29753
+ timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
29754
+ acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
29682
29755
  });
29683
29756
  ctx.verifyResult = {
29684
29757
  success: result.success,
@@ -29695,7 +29768,7 @@ var init_verify = __esm(() => {
29695
29768
  };
29696
29769
  if (!result.success) {
29697
29770
  if (result.status === "TIMEOUT") {
29698
- const timeout = ctx.config.execution.verificationTimeoutSeconds;
29771
+ const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
29699
29772
  logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
29700
29773
  exitCode: result.status,
29701
29774
  storyId: ctx.story.id,
@@ -29710,10 +29783,13 @@ var init_verify = __esm(() => {
29710
29783
  if (result.status !== "TIMEOUT") {
29711
29784
  logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
29712
29785
  }
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
- };
29786
+ if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
29787
+ return {
29788
+ action: "escalate",
29789
+ 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"})`
29790
+ };
29791
+ }
29792
+ return { action: "continue" };
29717
29793
  }
29718
29794
  logger.info("verify", "Tests passed", { storyId: ctx.story.id });
29719
29795
  return { action: "continue" };
@@ -29721,7 +29797,6 @@ var init_verify = __esm(() => {
29721
29797
  };
29722
29798
  _verifyDeps = {
29723
29799
  regression,
29724
- loadConfigForWorkdir,
29725
29800
  readPackageName
29726
29801
  };
29727
29802
  });
@@ -29809,7 +29884,7 @@ __export(exports_init_context, {
29809
29884
  });
29810
29885
  import { existsSync as existsSync20 } from "fs";
29811
29886
  import { mkdir } from "fs/promises";
29812
- import { basename as basename2, join as join30 } from "path";
29887
+ import { basename as basename2, join as join31 } from "path";
29813
29888
  async function findFiles(dir, maxFiles = 200) {
29814
29889
  try {
29815
29890
  const proc = Bun.spawnSync([
@@ -29837,7 +29912,7 @@ async function findFiles(dir, maxFiles = 200) {
29837
29912
  return [];
29838
29913
  }
29839
29914
  async function readPackageManifest(projectRoot) {
29840
- const packageJsonPath = join30(projectRoot, "package.json");
29915
+ const packageJsonPath = join31(projectRoot, "package.json");
29841
29916
  if (!existsSync20(packageJsonPath)) {
29842
29917
  return null;
29843
29918
  }
@@ -29855,7 +29930,7 @@ async function readPackageManifest(projectRoot) {
29855
29930
  }
29856
29931
  }
29857
29932
  async function readReadmeSnippet(projectRoot) {
29858
- const readmePath = join30(projectRoot, "README.md");
29933
+ const readmePath = join31(projectRoot, "README.md");
29859
29934
  if (!existsSync20(readmePath)) {
29860
29935
  return null;
29861
29936
  }
@@ -29873,7 +29948,7 @@ async function detectEntryPoints(projectRoot) {
29873
29948
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
29874
29949
  const found = [];
29875
29950
  for (const candidate of candidates) {
29876
- const path12 = join30(projectRoot, candidate);
29951
+ const path12 = join31(projectRoot, candidate);
29877
29952
  if (existsSync20(path12)) {
29878
29953
  found.push(candidate);
29879
29954
  }
@@ -29884,7 +29959,7 @@ async function detectConfigFiles(projectRoot) {
29884
29959
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
29885
29960
  const found = [];
29886
29961
  for (const candidate of candidates) {
29887
- const path12 = join30(projectRoot, candidate);
29962
+ const path12 = join31(projectRoot, candidate);
29888
29963
  if (existsSync20(path12)) {
29889
29964
  found.push(candidate);
29890
29965
  }
@@ -30045,9 +30120,9 @@ function generatePackageContextTemplate(packagePath) {
30045
30120
  }
30046
30121
  async function initPackage(repoRoot, packagePath, force = false) {
30047
30122
  const logger = getLogger();
30048
- const packageDir = join30(repoRoot, packagePath);
30049
- const naxDir = join30(packageDir, "nax");
30050
- const contextPath = join30(naxDir, "context.md");
30123
+ const packageDir = join31(repoRoot, packagePath);
30124
+ const naxDir = join31(packageDir, "nax");
30125
+ const contextPath = join31(naxDir, "context.md");
30051
30126
  if (existsSync20(contextPath) && !force) {
30052
30127
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
30053
30128
  return;
@@ -30061,8 +30136,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
30061
30136
  }
30062
30137
  async function initContext(projectRoot, options = {}) {
30063
30138
  const logger = getLogger();
30064
- const naxDir = join30(projectRoot, "nax");
30065
- const contextPath = join30(naxDir, "context.md");
30139
+ const naxDir = join31(projectRoot, "nax");
30140
+ const contextPath = join31(naxDir, "context.md");
30066
30141
  if (existsSync20(contextPath) && !options.force) {
30067
30142
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
30068
30143
  return;
@@ -31370,19 +31445,19 @@ var init_precheck = __esm(() => {
31370
31445
  });
31371
31446
 
31372
31447
  // src/hooks/runner.ts
31373
- import { join as join43 } from "path";
31448
+ import { join as join44 } from "path";
31374
31449
  async function loadHooksConfig(projectDir, globalDir) {
31375
31450
  let globalHooks = { hooks: {} };
31376
31451
  let projectHooks = { hooks: {} };
31377
31452
  let skipGlobal = false;
31378
- const projectPath = join43(projectDir, "hooks.json");
31453
+ const projectPath = join44(projectDir, "hooks.json");
31379
31454
  const projectData = await loadJsonFile(projectPath, "hooks");
31380
31455
  if (projectData) {
31381
31456
  projectHooks = projectData;
31382
31457
  skipGlobal = projectData.skipGlobal ?? false;
31383
31458
  }
31384
31459
  if (!skipGlobal && globalDir) {
31385
- const globalPath = join43(globalDir, "hooks.json");
31460
+ const globalPath = join44(globalDir, "hooks.json");
31386
31461
  const globalData = await loadJsonFile(globalPath, "hooks");
31387
31462
  if (globalData) {
31388
31463
  globalHooks = globalData;
@@ -31873,6 +31948,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
31873
31948
  }), ctx.workdir);
31874
31949
  const fixContext = {
31875
31950
  config: ctx.config,
31951
+ effectiveConfig: ctx.config,
31876
31952
  prd,
31877
31953
  story,
31878
31954
  stories: [story],
@@ -31906,6 +31982,7 @@ async function runAcceptanceLoop(ctx) {
31906
31982
  const firstStory = prd.userStories[0];
31907
31983
  const acceptanceContext = {
31908
31984
  config: ctx.config,
31985
+ effectiveConfig: ctx.config,
31909
31986
  prd,
31910
31987
  story: firstStory,
31911
31988
  stories: [firstStory],
@@ -32414,12 +32491,12 @@ __export(exports_manager, {
32414
32491
  WorktreeManager: () => WorktreeManager
32415
32492
  });
32416
32493
  import { existsSync as existsSync32, symlinkSync } from "fs";
32417
- import { join as join44 } from "path";
32494
+ import { join as join45 } from "path";
32418
32495
 
32419
32496
  class WorktreeManager {
32420
32497
  async create(projectRoot, storyId) {
32421
32498
  validateStoryId(storyId);
32422
- const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32499
+ const worktreePath = join45(projectRoot, ".nax-wt", storyId);
32423
32500
  const branchName = `nax/${storyId}`;
32424
32501
  try {
32425
32502
  const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
@@ -32444,9 +32521,9 @@ class WorktreeManager {
32444
32521
  }
32445
32522
  throw new Error(`Failed to create worktree: ${String(error48)}`);
32446
32523
  }
32447
- const nodeModulesSource = join44(projectRoot, "node_modules");
32524
+ const nodeModulesSource = join45(projectRoot, "node_modules");
32448
32525
  if (existsSync32(nodeModulesSource)) {
32449
- const nodeModulesTarget = join44(worktreePath, "node_modules");
32526
+ const nodeModulesTarget = join45(worktreePath, "node_modules");
32450
32527
  try {
32451
32528
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
32452
32529
  } catch (error48) {
@@ -32454,9 +32531,9 @@ class WorktreeManager {
32454
32531
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
32455
32532
  }
32456
32533
  }
32457
- const envSource = join44(projectRoot, ".env");
32534
+ const envSource = join45(projectRoot, ".env");
32458
32535
  if (existsSync32(envSource)) {
32459
- const envTarget = join44(worktreePath, ".env");
32536
+ const envTarget = join45(worktreePath, ".env");
32460
32537
  try {
32461
32538
  symlinkSync(envSource, envTarget, "file");
32462
32539
  } catch (error48) {
@@ -32467,7 +32544,7 @@ class WorktreeManager {
32467
32544
  }
32468
32545
  async remove(projectRoot, storyId) {
32469
32546
  validateStoryId(storyId);
32470
- const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32547
+ const worktreePath = join45(projectRoot, ".nax-wt", storyId);
32471
32548
  const branchName = `nax/${storyId}`;
32472
32549
  try {
32473
32550
  const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -32777,6 +32854,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
32777
32854
  try {
32778
32855
  const pipelineContext = {
32779
32856
  ...context,
32857
+ effectiveConfig: context.effectiveConfig ?? context.config,
32780
32858
  story,
32781
32859
  stories: [story],
32782
32860
  workdir: worktreePath,
@@ -32857,7 +32935,7 @@ var init_parallel_worker = __esm(() => {
32857
32935
 
32858
32936
  // src/execution/parallel-coordinator.ts
32859
32937
  import os3 from "os";
32860
- import { join as join45 } from "path";
32938
+ import { join as join46 } from "path";
32861
32939
  function groupStoriesByDependencies(stories) {
32862
32940
  const batches = [];
32863
32941
  const processed = new Set;
@@ -32926,6 +33004,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32926
33004
  });
32927
33005
  const baseContext = {
32928
33006
  config: config2,
33007
+ effectiveConfig: config2,
32929
33008
  prd: currentPrd,
32930
33009
  featureDir,
32931
33010
  hooks,
@@ -32935,7 +33014,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32935
33014
  };
32936
33015
  const worktreePaths = new Map;
32937
33016
  for (const story of batch) {
32938
- const worktreePath = join45(projectRoot, ".nax-wt", story.id);
33017
+ const worktreePath = join46(projectRoot, ".nax-wt", story.id);
32939
33018
  try {
32940
33019
  await worktreeManager.create(projectRoot, story.id);
32941
33020
  worktreePaths.set(story.id, worktreePath);
@@ -32984,7 +33063,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32984
33063
  });
32985
33064
  logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
32986
33065
  storyId: mergeResult.storyId,
32987
- worktreePath: join45(projectRoot, ".nax-wt", mergeResult.storyId)
33066
+ worktreePath: join46(projectRoot, ".nax-wt", mergeResult.storyId)
32988
33067
  });
32989
33068
  }
32990
33069
  }
@@ -33063,6 +33142,7 @@ async function rectifyConflictedStory(options) {
33063
33142
  const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
33064
33143
  const pipelineContext = {
33065
33144
  config: config2,
33145
+ effectiveConfig: config2,
33066
33146
  prd,
33067
33147
  story,
33068
33148
  stories: [story],
@@ -33443,12 +33523,12 @@ var init_parallel_executor = __esm(() => {
33443
33523
  // src/pipeline/subscribers/events-writer.ts
33444
33524
  import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
33445
33525
  import { homedir as homedir7 } from "os";
33446
- import { basename as basename5, join as join46 } from "path";
33526
+ import { basename as basename5, join as join47 } from "path";
33447
33527
  function wireEventsWriter(bus, feature, runId, workdir) {
33448
33528
  const logger = getSafeLogger();
33449
33529
  const project = basename5(workdir);
33450
- const eventsDir = join46(homedir7(), ".nax", "events", project);
33451
- const eventsFile = join46(eventsDir, "events.jsonl");
33530
+ const eventsDir = join47(homedir7(), ".nax", "events", project);
33531
+ const eventsFile = join47(eventsDir, "events.jsonl");
33452
33532
  let dirReady = false;
33453
33533
  const write = (line) => {
33454
33534
  (async () => {
@@ -33608,12 +33688,12 @@ var init_interaction2 = __esm(() => {
33608
33688
  // src/pipeline/subscribers/registry.ts
33609
33689
  import { mkdir as mkdir3, writeFile } from "fs/promises";
33610
33690
  import { homedir as homedir8 } from "os";
33611
- import { basename as basename6, join as join47 } from "path";
33691
+ import { basename as basename6, join as join48 } from "path";
33612
33692
  function wireRegistry(bus, feature, runId, workdir) {
33613
33693
  const logger = getSafeLogger();
33614
33694
  const project = basename6(workdir);
33615
- const runDir = join47(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33616
- const metaFile = join47(runDir, "meta.json");
33695
+ const runDir = join48(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33696
+ const metaFile = join48(runDir, "meta.json");
33617
33697
  const unsub = bus.on("run:started", (_ev) => {
33618
33698
  (async () => {
33619
33699
  try {
@@ -33623,8 +33703,8 @@ function wireRegistry(bus, feature, runId, workdir) {
33623
33703
  project,
33624
33704
  feature,
33625
33705
  workdir,
33626
- statusPath: join47(workdir, "nax", "features", feature, "status.json"),
33627
- eventsDir: join47(workdir, "nax", "features", feature, "runs"),
33706
+ statusPath: join48(workdir, "nax", "features", feature, "status.json"),
33707
+ eventsDir: join48(workdir, "nax", "features", feature, "runs"),
33628
33708
  registeredAt: new Date().toISOString()
33629
33709
  };
33630
33710
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -34253,6 +34333,7 @@ var init_pipeline_result_handler = __esm(() => {
34253
34333
  });
34254
34334
 
34255
34335
  // src/execution/iteration-runner.ts
34336
+ import { join as join49 } from "path";
34256
34337
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
34257
34338
  const logger = getSafeLogger();
34258
34339
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -34278,8 +34359,10 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34278
34359
  const storyStartTime = Date.now();
34279
34360
  const storyGitRef = await captureGitRef(ctx.workdir);
34280
34361
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
34362
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, "nax", "config.json"), story.workdir) : ctx.config;
34281
34363
  const pipelineContext = {
34282
34364
  config: ctx.config,
34365
+ effectiveConfig,
34283
34366
  prd,
34284
34367
  story,
34285
34368
  stories: storiesToExecute,
@@ -34350,13 +34433,18 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
34350
34433
  reason: pipelineResult.reason
34351
34434
  };
34352
34435
  }
34436
+ var _iterationRunnerDeps;
34353
34437
  var init_iteration_runner = __esm(() => {
34438
+ init_loader2();
34354
34439
  init_logger2();
34355
34440
  init_runner();
34356
34441
  init_stages();
34357
34442
  init_git();
34358
34443
  init_dry_run();
34359
34444
  init_pipeline_result_handler();
34445
+ _iterationRunnerDeps = {
34446
+ loadConfigForWorkdir
34447
+ };
34360
34448
  });
34361
34449
 
34362
34450
  // src/execution/executor-types.ts
@@ -34449,6 +34537,7 @@ async function executeSequential(ctx, initialPrd) {
34449
34537
  logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
34450
34538
  const preRunCtx = {
34451
34539
  config: ctx.config,
34540
+ effectiveConfig: ctx.config,
34452
34541
  prd,
34453
34542
  workdir: ctx.workdir,
34454
34543
  featureDir: ctx.featureDir,
@@ -34620,7 +34709,7 @@ async function writeStatusFile(filePath, status) {
34620
34709
  var init_status_file = () => {};
34621
34710
 
34622
34711
  // src/execution/status-writer.ts
34623
- import { join as join48 } from "path";
34712
+ import { join as join50 } from "path";
34624
34713
 
34625
34714
  class StatusWriter {
34626
34715
  statusFile;
@@ -34688,7 +34777,7 @@ class StatusWriter {
34688
34777
  if (!this._prd)
34689
34778
  return;
34690
34779
  const safeLogger = getSafeLogger();
34691
- const featureStatusPath = join48(featureDir, "status.json");
34780
+ const featureStatusPath = join50(featureDir, "status.json");
34692
34781
  try {
34693
34782
  const base = this.getSnapshot(totalCost, iterations);
34694
34783
  if (!base) {
@@ -66017,7 +66106,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
66017
66106
  init_source();
66018
66107
  import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
66019
66108
  import { homedir as homedir10 } from "os";
66020
- import { join as join49 } from "path";
66109
+ import { join as join51 } from "path";
66021
66110
 
66022
66111
  // node_modules/commander/esm.mjs
66023
66112
  var import__ = __toESM(require_commander(), 1);
@@ -67243,7 +67332,16 @@ async function planCommand(workdir, config2, options) {
67243
67332
  const cliAdapter = _deps2.getAgent(agentName);
67244
67333
  if (!cliAdapter)
67245
67334
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
67246
- rawResponse = await cliAdapter.complete(prompt, { jsonMode: true, workdir, config: config2 });
67335
+ let autoModel;
67336
+ try {
67337
+ const planTier = config2?.plan?.model ?? "balanced";
67338
+ const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
67339
+ const models = config2?.models;
67340
+ const entry = models?.[planTier] ?? models?.balanced;
67341
+ if (entry)
67342
+ autoModel = resolveModel2(entry).model;
67343
+ } catch {}
67344
+ rawResponse = await cliAdapter.complete(prompt, { model: autoModel, jsonMode: true, workdir, config: config2 });
67247
67345
  try {
67248
67346
  const envelope = JSON.parse(rawResponse);
67249
67347
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
@@ -68086,7 +68184,7 @@ async function runsShowCommand(options) {
68086
68184
  // src/cli/prompts-main.ts
68087
68185
  init_logger2();
68088
68186
  import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
68089
- import { join as join28 } from "path";
68187
+ import { join as join29 } from "path";
68090
68188
 
68091
68189
  // src/pipeline/index.ts
68092
68190
  init_runner();
@@ -68122,7 +68220,7 @@ init_prd();
68122
68220
 
68123
68221
  // src/cli/prompts-tdd.ts
68124
68222
  init_prompts2();
68125
- import { join as join27 } from "path";
68223
+ import { join as join28 } from "path";
68126
68224
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
68127
68225
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
68128
68226
  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 +68239,7 @@ ${frontmatter}---
68141
68239
 
68142
68240
  ${session.prompt}`;
68143
68241
  if (outputDir) {
68144
- const promptFile = join27(outputDir, `${story.id}.${session.role}.md`);
68242
+ const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
68145
68243
  await Bun.write(promptFile, fullOutput);
68146
68244
  logger.info("cli", "Written TDD prompt file", {
68147
68245
  storyId: story.id,
@@ -68157,7 +68255,7 @@ ${"=".repeat(80)}`);
68157
68255
  }
68158
68256
  }
68159
68257
  if (outputDir && ctx.contextMarkdown) {
68160
- const contextFile = join27(outputDir, `${story.id}.context.md`);
68258
+ const contextFile = join28(outputDir, `${story.id}.context.md`);
68161
68259
  const frontmatter = buildFrontmatter(story, ctx);
68162
68260
  const contextOutput = `---
68163
68261
  ${frontmatter}---
@@ -68171,12 +68269,12 @@ ${ctx.contextMarkdown}`;
68171
68269
  async function promptsCommand(options) {
68172
68270
  const logger = getLogger();
68173
68271
  const { feature, workdir, config: config2, storyId, outputDir } = options;
68174
- const naxDir = join28(workdir, "nax");
68272
+ const naxDir = join29(workdir, "nax");
68175
68273
  if (!existsSync18(naxDir)) {
68176
68274
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
68177
68275
  }
68178
- const featureDir = join28(naxDir, "features", feature);
68179
- const prdPath = join28(featureDir, "prd.json");
68276
+ const featureDir = join29(naxDir, "features", feature);
68277
+ const prdPath = join29(featureDir, "prd.json");
68180
68278
  if (!existsSync18(prdPath)) {
68181
68279
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
68182
68280
  }
@@ -68198,6 +68296,7 @@ async function promptsCommand(options) {
68198
68296
  for (const story of stories) {
68199
68297
  const ctx = {
68200
68298
  config: config2,
68299
+ effectiveConfig: config2,
68201
68300
  prd,
68202
68301
  story,
68203
68302
  stories: [story],
@@ -68236,10 +68335,10 @@ ${frontmatter}---
68236
68335
 
68237
68336
  ${ctx.prompt}`;
68238
68337
  if (outputDir) {
68239
- const promptFile = join28(outputDir, `${story.id}.prompt.md`);
68338
+ const promptFile = join29(outputDir, `${story.id}.prompt.md`);
68240
68339
  await Bun.write(promptFile, fullOutput);
68241
68340
  if (ctx.contextMarkdown) {
68242
- const contextFile = join28(outputDir, `${story.id}.context.md`);
68341
+ const contextFile = join29(outputDir, `${story.id}.context.md`);
68243
68342
  const contextOutput = `---
68244
68343
  ${frontmatter}---
68245
68344
 
@@ -68303,7 +68402,7 @@ function buildFrontmatter(story, ctx, role) {
68303
68402
  }
68304
68403
  // src/cli/prompts-init.ts
68305
68404
  import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
68306
- import { join as join29 } from "path";
68405
+ import { join as join30 } from "path";
68307
68406
  var TEMPLATE_ROLES = [
68308
68407
  { file: "test-writer.md", role: "test-writer" },
68309
68408
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -68327,9 +68426,9 @@ var TEMPLATE_HEADER = `<!--
68327
68426
  `;
68328
68427
  async function promptsInitCommand(options) {
68329
68428
  const { workdir, force = false, autoWireConfig = true } = options;
68330
- const templatesDir = join29(workdir, "nax", "templates");
68429
+ const templatesDir = join30(workdir, "nax", "templates");
68331
68430
  mkdirSync4(templatesDir, { recursive: true });
68332
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join29(templatesDir, f)));
68431
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join30(templatesDir, f)));
68333
68432
  if (existingFiles.length > 0 && !force) {
68334
68433
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
68335
68434
  Pass --force to overwrite existing templates.`);
@@ -68337,7 +68436,7 @@ async function promptsInitCommand(options) {
68337
68436
  }
68338
68437
  const written = [];
68339
68438
  for (const template of TEMPLATE_ROLES) {
68340
- const filePath = join29(templatesDir, template.file);
68439
+ const filePath = join30(templatesDir, template.file);
68341
68440
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
68342
68441
  const content = TEMPLATE_HEADER + roleBody;
68343
68442
  await Bun.write(filePath, content);
@@ -68353,7 +68452,7 @@ async function promptsInitCommand(options) {
68353
68452
  return written;
68354
68453
  }
68355
68454
  async function autoWirePromptsConfig(workdir) {
68356
- const configPath = join29(workdir, "nax.config.json");
68455
+ const configPath = join30(workdir, "nax.config.json");
68357
68456
  if (!existsSync19(configPath)) {
68358
68457
  const exampleConfig = JSON.stringify({
68359
68458
  prompts: {
@@ -68518,7 +68617,7 @@ init_config();
68518
68617
  init_logger2();
68519
68618
  init_prd();
68520
68619
  import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
68521
- import { join as join33 } from "path";
68620
+ import { join as join34 } from "path";
68522
68621
 
68523
68622
  // src/cli/diagnose-analysis.ts
68524
68623
  function detectFailurePattern(story, prd, status) {
@@ -68717,7 +68816,7 @@ function isProcessAlive2(pid) {
68717
68816
  }
68718
68817
  }
68719
68818
  async function loadStatusFile2(workdir) {
68720
- const statusPath = join33(workdir, "nax", "status.json");
68819
+ const statusPath = join34(workdir, "nax", "status.json");
68721
68820
  if (!existsSync21(statusPath))
68722
68821
  return null;
68723
68822
  try {
@@ -68745,7 +68844,7 @@ async function countCommitsSince(workdir, since) {
68745
68844
  }
68746
68845
  }
68747
68846
  async function checkLock(workdir) {
68748
- const lockFile = Bun.file(join33(workdir, "nax.lock"));
68847
+ const lockFile = Bun.file(join34(workdir, "nax.lock"));
68749
68848
  if (!await lockFile.exists())
68750
68849
  return { lockPresent: false };
68751
68850
  try {
@@ -68763,8 +68862,8 @@ async function diagnoseCommand(options = {}) {
68763
68862
  const logger = getLogger();
68764
68863
  const workdir = options.workdir ?? process.cwd();
68765
68864
  const naxSubdir = findProjectDir(workdir);
68766
- let projectDir = naxSubdir ? join33(naxSubdir, "..") : null;
68767
- if (!projectDir && existsSync21(join33(workdir, "nax"))) {
68865
+ let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
68866
+ if (!projectDir && existsSync21(join34(workdir, "nax"))) {
68768
68867
  projectDir = workdir;
68769
68868
  }
68770
68869
  if (!projectDir)
@@ -68775,7 +68874,7 @@ async function diagnoseCommand(options = {}) {
68775
68874
  if (status2) {
68776
68875
  feature = status2.run.feature;
68777
68876
  } else {
68778
- const featuresDir = join33(projectDir, "nax", "features");
68877
+ const featuresDir = join34(projectDir, "nax", "features");
68779
68878
  if (!existsSync21(featuresDir))
68780
68879
  throw new Error("No features found in project");
68781
68880
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -68785,8 +68884,8 @@ async function diagnoseCommand(options = {}) {
68785
68884
  logger.info("diagnose", "No feature specified, using first found", { feature });
68786
68885
  }
68787
68886
  }
68788
- const featureDir = join33(projectDir, "nax", "features", feature);
68789
- const prdPath = join33(featureDir, "prd.json");
68887
+ const featureDir = join34(projectDir, "nax", "features", feature);
68888
+ const prdPath = join34(featureDir, "prd.json");
68790
68889
  if (!existsSync21(prdPath))
68791
68890
  throw new Error(`Feature not found: ${feature}`);
68792
68891
  const prd = await loadPRD(prdPath);
@@ -68829,7 +68928,7 @@ init_interaction();
68829
68928
  init_source();
68830
68929
  init_loader2();
68831
68930
  import { existsSync as existsSync22 } from "fs";
68832
- import { join as join34 } from "path";
68931
+ import { join as join35 } from "path";
68833
68932
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68834
68933
  async function generateCommand(options) {
68835
68934
  const workdir = process.cwd();
@@ -68872,7 +68971,7 @@ async function generateCommand(options) {
68872
68971
  return;
68873
68972
  }
68874
68973
  if (options.package) {
68875
- const packageDir = join34(workdir, options.package);
68974
+ const packageDir = join35(workdir, options.package);
68876
68975
  if (dryRun) {
68877
68976
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
68878
68977
  }
@@ -68892,8 +68991,8 @@ async function generateCommand(options) {
68892
68991
  process.exit(1);
68893
68992
  return;
68894
68993
  }
68895
- const contextPath = options.context ? join34(workdir, options.context) : join34(workdir, "nax/context.md");
68896
- const outputDir = options.output ? join34(workdir, options.output) : workdir;
68994
+ const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, "nax/context.md");
68995
+ const outputDir = options.output ? join35(workdir, options.output) : workdir;
68897
68996
  const autoInject = !options.noAutoInject;
68898
68997
  if (!existsSync22(contextPath)) {
68899
68998
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -68998,7 +69097,7 @@ async function generateCommand(options) {
68998
69097
  // src/cli/config-display.ts
68999
69098
  init_loader2();
69000
69099
  import { existsSync as existsSync24 } from "fs";
69001
- import { join as join36 } from "path";
69100
+ import { join as join37 } from "path";
69002
69101
 
69003
69102
  // src/cli/config-descriptions.ts
69004
69103
  var FIELD_DESCRIPTIONS = {
@@ -69207,7 +69306,7 @@ function deepEqual(a, b) {
69207
69306
  init_defaults();
69208
69307
  init_loader2();
69209
69308
  import { existsSync as existsSync23 } from "fs";
69210
- import { join as join35 } from "path";
69309
+ import { join as join36 } from "path";
69211
69310
  async function loadConfigFile(path14) {
69212
69311
  if (!existsSync23(path14))
69213
69312
  return null;
@@ -69229,7 +69328,7 @@ async function loadProjectConfig() {
69229
69328
  const projectDir = findProjectDir();
69230
69329
  if (!projectDir)
69231
69330
  return null;
69232
- const projectPath = join35(projectDir, "config.json");
69331
+ const projectPath = join36(projectDir, "config.json");
69233
69332
  return await loadConfigFile(projectPath);
69234
69333
  }
69235
69334
 
@@ -69289,7 +69388,7 @@ async function configCommand(config2, options = {}) {
69289
69388
  function determineConfigSources() {
69290
69389
  const globalPath = globalConfigPath();
69291
69390
  const projectDir = findProjectDir();
69292
- const projectPath = projectDir ? join36(projectDir, "config.json") : null;
69391
+ const projectPath = projectDir ? join37(projectDir, "config.json") : null;
69293
69392
  return {
69294
69393
  global: fileExists(globalPath) ? globalPath : null,
69295
69394
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -69469,21 +69568,21 @@ async function diagnose(options) {
69469
69568
 
69470
69569
  // src/commands/logs.ts
69471
69570
  import { existsSync as existsSync26 } from "fs";
69472
- import { join as join39 } from "path";
69571
+ import { join as join40 } from "path";
69473
69572
 
69474
69573
  // src/commands/logs-formatter.ts
69475
69574
  init_source();
69476
69575
  init_formatter();
69477
69576
  import { readdirSync as readdirSync7 } from "fs";
69478
- import { join as join38 } from "path";
69577
+ import { join as join39 } from "path";
69479
69578
 
69480
69579
  // src/commands/logs-reader.ts
69481
69580
  import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
69482
69581
  import { readdir as readdir3 } from "fs/promises";
69483
69582
  import { homedir as homedir5 } from "os";
69484
- import { join as join37 } from "path";
69583
+ import { join as join38 } from "path";
69485
69584
  var _deps7 = {
69486
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join37(homedir5(), ".nax", "runs")
69585
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
69487
69586
  };
69488
69587
  async function resolveRunFileFromRegistry(runId) {
69489
69588
  const runsDir = _deps7.getRunsDir();
@@ -69495,7 +69594,7 @@ async function resolveRunFileFromRegistry(runId) {
69495
69594
  }
69496
69595
  let matched = null;
69497
69596
  for (const entry of entries) {
69498
- const metaPath = join37(runsDir, entry, "meta.json");
69597
+ const metaPath = join38(runsDir, entry, "meta.json");
69499
69598
  try {
69500
69599
  const meta3 = await Bun.file(metaPath).json();
69501
69600
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -69517,14 +69616,14 @@ async function resolveRunFileFromRegistry(runId) {
69517
69616
  return null;
69518
69617
  }
69519
69618
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
69520
- return join37(matched.eventsDir, specificFile ?? files[0]);
69619
+ return join38(matched.eventsDir, specificFile ?? files[0]);
69521
69620
  }
69522
69621
  async function selectRunFile(runsDir) {
69523
69622
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
69524
69623
  if (files.length === 0) {
69525
69624
  return null;
69526
69625
  }
69527
- return join37(runsDir, files[0]);
69626
+ return join38(runsDir, files[0]);
69528
69627
  }
69529
69628
  async function extractRunSummary(filePath) {
69530
69629
  const file2 = Bun.file(filePath);
@@ -69609,7 +69708,7 @@ Runs:
69609
69708
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
69610
69709
  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
69710
  for (const file2 of files) {
69612
- const filePath = join38(runsDir, file2);
69711
+ const filePath = join39(runsDir, file2);
69613
69712
  const summary = await extractRunSummary(filePath);
69614
69713
  const timestamp = file2.replace(".jsonl", "");
69615
69714
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -69734,7 +69833,7 @@ async function logsCommand(options) {
69734
69833
  return;
69735
69834
  }
69736
69835
  const resolved = resolveProject({ dir: options.dir });
69737
- const naxDir = join39(resolved.projectDir, "nax");
69836
+ const naxDir = join40(resolved.projectDir, "nax");
69738
69837
  const configPath = resolved.configPath;
69739
69838
  const configFile = Bun.file(configPath);
69740
69839
  const config2 = await configFile.json();
@@ -69742,8 +69841,8 @@ async function logsCommand(options) {
69742
69841
  if (!featureName) {
69743
69842
  throw new Error("No feature specified in config.json");
69744
69843
  }
69745
- const featureDir = join39(naxDir, "features", featureName);
69746
- const runsDir = join39(featureDir, "runs");
69844
+ const featureDir = join40(naxDir, "features", featureName);
69845
+ const runsDir = join40(featureDir, "runs");
69747
69846
  if (!existsSync26(runsDir)) {
69748
69847
  throw new Error(`No runs directory found for feature: ${featureName}`);
69749
69848
  }
@@ -69768,7 +69867,7 @@ init_config();
69768
69867
  init_prd();
69769
69868
  init_precheck();
69770
69869
  import { existsSync as existsSync31 } from "fs";
69771
- import { join as join40 } from "path";
69870
+ import { join as join41 } from "path";
69772
69871
  async function precheckCommand(options) {
69773
69872
  const resolved = resolveProject({
69774
69873
  dir: options.dir,
@@ -69784,9 +69883,9 @@ async function precheckCommand(options) {
69784
69883
  process.exit(1);
69785
69884
  }
69786
69885
  }
69787
- const naxDir = join40(resolved.projectDir, "nax");
69788
- const featureDir = join40(naxDir, "features", featureName);
69789
- const prdPath = join40(featureDir, "prd.json");
69886
+ const naxDir = join41(resolved.projectDir, "nax");
69887
+ const featureDir = join41(naxDir, "features", featureName);
69888
+ const prdPath = join41(featureDir, "prd.json");
69790
69889
  if (!existsSync31(featureDir)) {
69791
69890
  console.error(source_default.red(`Feature not found: ${featureName}`));
69792
69891
  process.exit(1);
@@ -69810,10 +69909,10 @@ async function precheckCommand(options) {
69810
69909
  init_source();
69811
69910
  import { readdir as readdir4 } from "fs/promises";
69812
69911
  import { homedir as homedir6 } from "os";
69813
- import { join as join41 } from "path";
69912
+ import { join as join42 } from "path";
69814
69913
  var DEFAULT_LIMIT = 20;
69815
69914
  var _deps9 = {
69816
- getRunsDir: () => join41(homedir6(), ".nax", "runs")
69915
+ getRunsDir: () => join42(homedir6(), ".nax", "runs")
69817
69916
  };
69818
69917
  function formatDuration3(ms) {
69819
69918
  if (ms <= 0)
@@ -69865,7 +69964,7 @@ async function runsCommand(options = {}) {
69865
69964
  }
69866
69965
  const rows = [];
69867
69966
  for (const entry of entries) {
69868
- const metaPath = join41(runsDir, entry, "meta.json");
69967
+ const metaPath = join42(runsDir, entry, "meta.json");
69869
69968
  let meta3;
69870
69969
  try {
69871
69970
  meta3 = await Bun.file(metaPath).json();
@@ -69942,7 +70041,7 @@ async function runsCommand(options = {}) {
69942
70041
 
69943
70042
  // src/commands/unlock.ts
69944
70043
  init_source();
69945
- import { join as join42 } from "path";
70044
+ import { join as join43 } from "path";
69946
70045
  function isProcessAlive3(pid) {
69947
70046
  try {
69948
70047
  process.kill(pid, 0);
@@ -69957,7 +70056,7 @@ function formatLockAge(ageMs) {
69957
70056
  }
69958
70057
  async function unlockCommand(options) {
69959
70058
  const workdir = options.dir ?? process.cwd();
69960
- const lockPath = join42(workdir, "nax.lock");
70059
+ const lockPath = join43(workdir, "nax.lock");
69961
70060
  const lockFile = Bun.file(lockPath);
69962
70061
  const exists = await lockFile.exists();
69963
70062
  if (!exists) {
@@ -77792,15 +77891,15 @@ Next: nax generate --package ${options.package}`));
77792
77891
  }
77793
77892
  return;
77794
77893
  }
77795
- const naxDir = join49(workdir, "nax");
77894
+ const naxDir = join51(workdir, "nax");
77796
77895
  if (existsSync34(naxDir) && !options.force) {
77797
77896
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
77798
77897
  return;
77799
77898
  }
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({
77899
+ mkdirSync6(join51(naxDir, "features"), { recursive: true });
77900
+ mkdirSync6(join51(naxDir, "hooks"), { recursive: true });
77901
+ await Bun.write(join51(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77902
+ await Bun.write(join51(naxDir, "hooks.json"), JSON.stringify({
77804
77903
  hooks: {
77805
77904
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
77806
77905
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -77808,12 +77907,12 @@ Next: nax generate --package ${options.package}`));
77808
77907
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
77809
77908
  }
77810
77909
  }, null, 2));
77811
- await Bun.write(join49(naxDir, ".gitignore"), `# nax temp files
77910
+ await Bun.write(join51(naxDir, ".gitignore"), `# nax temp files
77812
77911
  *.tmp
77813
77912
  .paused.json
77814
77913
  .nax-verifier-verdict.json
77815
77914
  `);
77816
- await Bun.write(join49(naxDir, "context.md"), `# Project Context
77915
+ await Bun.write(join51(naxDir, "context.md"), `# Project Context
77817
77916
 
77818
77917
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
77819
77918
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -77939,8 +78038,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
77939
78038
  console.error(source_default.red("nax not initialized. Run: nax init"));
77940
78039
  process.exit(1);
77941
78040
  }
77942
- const featureDir = join49(naxDir, "features", options.feature);
77943
- const prdPath = join49(featureDir, "prd.json");
78041
+ const featureDir = join51(naxDir, "features", options.feature);
78042
+ const prdPath = join51(featureDir, "prd.json");
77944
78043
  if (options.plan && options.from) {
77945
78044
  if (existsSync34(prdPath) && !options.force) {
77946
78045
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -77962,10 +78061,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77962
78061
  }
77963
78062
  }
77964
78063
  try {
77965
- const planLogDir = join49(featureDir, "plan");
78064
+ const planLogDir = join51(featureDir, "plan");
77966
78065
  mkdirSync6(planLogDir, { recursive: true });
77967
78066
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77968
- const planLogPath = join49(planLogDir, `${planLogId}.jsonl`);
78067
+ const planLogPath = join51(planLogDir, `${planLogId}.jsonl`);
77969
78068
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77970
78069
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77971
78070
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -78003,10 +78102,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
78003
78102
  process.exit(1);
78004
78103
  }
78005
78104
  resetLogger();
78006
- const runsDir = join49(featureDir, "runs");
78105
+ const runsDir = join51(featureDir, "runs");
78007
78106
  mkdirSync6(runsDir, { recursive: true });
78008
78107
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78009
- const logFilePath = join49(runsDir, `${runId}.jsonl`);
78108
+ const logFilePath = join51(runsDir, `${runId}.jsonl`);
78010
78109
  const isTTY = process.stdout.isTTY ?? false;
78011
78110
  const headlessFlag = options.headless ?? false;
78012
78111
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -78022,7 +78121,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78022
78121
  config2.autoMode.defaultAgent = options.agent;
78023
78122
  }
78024
78123
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
78025
- const globalNaxDir = join49(homedir10(), ".nax");
78124
+ const globalNaxDir = join51(homedir10(), ".nax");
78026
78125
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
78027
78126
  const eventEmitter = new PipelineEventEmitter;
78028
78127
  let tuiInstance;
@@ -78045,7 +78144,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78045
78144
  } else {
78046
78145
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
78047
78146
  }
78048
- const statusFilePath = join49(workdir, "nax", "status.json");
78147
+ const statusFilePath = join51(workdir, "nax", "status.json");
78049
78148
  let parallel;
78050
78149
  if (options.parallel !== undefined) {
78051
78150
  parallel = Number.parseInt(options.parallel, 10);
@@ -78071,7 +78170,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
78071
78170
  headless: useHeadless,
78072
78171
  skipPrecheck: options.skipPrecheck ?? false
78073
78172
  });
78074
- const latestSymlink = join49(runsDir, "latest.jsonl");
78173
+ const latestSymlink = join51(runsDir, "latest.jsonl");
78075
78174
  try {
78076
78175
  if (existsSync34(latestSymlink)) {
78077
78176
  Bun.spawnSync(["rm", latestSymlink]);
@@ -78109,9 +78208,9 @@ features.command("create <name>").description("Create a new feature").option("-d
78109
78208
  console.error(source_default.red("nax not initialized. Run: nax init"));
78110
78209
  process.exit(1);
78111
78210
  }
78112
- const featureDir = join49(naxDir, "features", name);
78211
+ const featureDir = join51(naxDir, "features", name);
78113
78212
  mkdirSync6(featureDir, { recursive: true });
78114
- await Bun.write(join49(featureDir, "spec.md"), `# Feature: ${name}
78213
+ await Bun.write(join51(featureDir, "spec.md"), `# Feature: ${name}
78115
78214
 
78116
78215
  ## Overview
78117
78216
 
@@ -78119,7 +78218,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78119
78218
 
78120
78219
  ## Acceptance Criteria
78121
78220
  `);
78122
- await Bun.write(join49(featureDir, "plan.md"), `# Plan: ${name}
78221
+ await Bun.write(join51(featureDir, "plan.md"), `# Plan: ${name}
78123
78222
 
78124
78223
  ## Architecture
78125
78224
 
@@ -78127,7 +78226,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78127
78226
 
78128
78227
  ## Dependencies
78129
78228
  `);
78130
- await Bun.write(join49(featureDir, "tasks.md"), `# Tasks: ${name}
78229
+ await Bun.write(join51(featureDir, "tasks.md"), `# Tasks: ${name}
78131
78230
 
78132
78231
  ## US-001: [Title]
78133
78232
 
@@ -78136,7 +78235,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78136
78235
  ### Acceptance Criteria
78137
78236
  - [ ] Criterion 1
78138
78237
  `);
78139
- await Bun.write(join49(featureDir, "progress.txt"), `# Progress: ${name}
78238
+ await Bun.write(join51(featureDir, "progress.txt"), `# Progress: ${name}
78140
78239
 
78141
78240
  Created: ${new Date().toISOString()}
78142
78241
 
@@ -78164,7 +78263,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78164
78263
  console.error(source_default.red("nax not initialized."));
78165
78264
  process.exit(1);
78166
78265
  }
78167
- const featuresDir = join49(naxDir, "features");
78266
+ const featuresDir = join51(naxDir, "features");
78168
78267
  if (!existsSync34(featuresDir)) {
78169
78268
  console.log(source_default.dim("No features yet."));
78170
78269
  return;
@@ -78179,7 +78278,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78179
78278
  Features:
78180
78279
  `));
78181
78280
  for (const name of entries) {
78182
- const prdPath = join49(featuresDir, name, "prd.json");
78281
+ const prdPath = join51(featuresDir, name, "prd.json");
78183
78282
  if (existsSync34(prdPath)) {
78184
78283
  const prd = await loadPRD(prdPath);
78185
78284
  const c = countStories(prd);
@@ -78210,10 +78309,10 @@ Use: nax plan -f <feature> --from <spec>`));
78210
78309
  process.exit(1);
78211
78310
  }
78212
78311
  const config2 = await loadConfig(workdir);
78213
- const featureLogDir = join49(naxDir, "features", options.feature, "plan");
78312
+ const featureLogDir = join51(naxDir, "features", options.feature, "plan");
78214
78313
  mkdirSync6(featureLogDir, { recursive: true });
78215
78314
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78216
- const planLogPath = join49(featureLogDir, `${planLogId}.jsonl`);
78315
+ const planLogPath = join51(featureLogDir, `${planLogId}.jsonl`);
78217
78316
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
78218
78317
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
78219
78318
  try {
@@ -78250,7 +78349,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78250
78349
  console.error(source_default.red("nax not initialized. Run: nax init"));
78251
78350
  process.exit(1);
78252
78351
  }
78253
- const featureDir = join49(naxDir, "features", options.feature);
78352
+ const featureDir = join51(naxDir, "features", options.feature);
78254
78353
  if (!existsSync34(featureDir)) {
78255
78354
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
78256
78355
  process.exit(1);
@@ -78266,7 +78365,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78266
78365
  specPath: options.from,
78267
78366
  reclassify: options.reclassify
78268
78367
  });
78269
- const prdPath = join49(featureDir, "prd.json");
78368
+ const prdPath = join51(featureDir, "prd.json");
78270
78369
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
78271
78370
  const c = countStories(prd);
78272
78371
  console.log(source_default.green(`