@nathapp/nax 0.54.1 → 0.54.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +309 -199
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -3580,6 +3580,44 @@ var init_complete = __esm(() => {
3580
3580
  };
3581
3581
  });
3582
3582
 
3583
+ // src/agents/shared/env.ts
3584
+ import { homedir } from "os";
3585
+ import { isAbsolute } from "path";
3586
+ function buildAllowedEnv(options) {
3587
+ const allowed = {};
3588
+ for (const varName of ESSENTIAL_VARS) {
3589
+ if (process.env[varName])
3590
+ allowed[varName] = process.env[varName];
3591
+ }
3592
+ const rawHome = process.env.HOME ?? "";
3593
+ const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
3594
+ if (rawHome !== safeHome) {
3595
+ getSafeLogger()?.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
3596
+ }
3597
+ allowed.HOME = safeHome;
3598
+ for (const varName of API_KEY_VARS) {
3599
+ if (process.env[varName])
3600
+ allowed[varName] = process.env[varName];
3601
+ }
3602
+ for (const [key, value] of Object.entries(process.env)) {
3603
+ if (ALLOWED_PREFIXES.some((prefix) => key.startsWith(prefix))) {
3604
+ allowed[key] = value;
3605
+ }
3606
+ }
3607
+ if (options?.modelEnv)
3608
+ Object.assign(allowed, options.modelEnv);
3609
+ if (options?.env)
3610
+ Object.assign(allowed, options.env);
3611
+ return allowed;
3612
+ }
3613
+ var ESSENTIAL_VARS, API_KEY_VARS, ALLOWED_PREFIXES;
3614
+ var init_env = __esm(() => {
3615
+ init_logger2();
3616
+ ESSENTIAL_VARS = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3617
+ API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
3618
+ ALLOWED_PREFIXES = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_", "ANTHROPIC_"];
3619
+ });
3620
+
3583
3621
  // src/agents/cost/pricing.ts
3584
3622
  var COST_RATES, MODEL_PRICING;
3585
3623
  var init_pricing = __esm(() => {
@@ -3743,49 +3781,12 @@ var init_cost2 = __esm(() => {
3743
3781
  });
3744
3782
 
3745
3783
  // src/agents/claude/execution.ts
3746
- import { homedir } from "os";
3747
- import { isAbsolute } from "path";
3748
3784
  function buildCommand(binary, options) {
3749
3785
  const model = options.modelDef.model;
3750
3786
  const { skipPermissions } = resolvePermissions(options.config, options.pipelineStage ?? "run");
3751
3787
  const permArgs = skipPermissions ? ["--dangerously-skip-permissions"] : [];
3752
3788
  return [binary, "--model", model, ...permArgs, "-p", options.prompt];
3753
3789
  }
3754
- function buildAllowedEnv(options) {
3755
- const allowed = {};
3756
- const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3757
- for (const varName of essentialVars) {
3758
- if (process.env[varName]) {
3759
- allowed[varName] = process.env[varName];
3760
- }
3761
- }
3762
- const rawHome = process.env.HOME ?? "";
3763
- const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
3764
- if (rawHome !== safeHome) {
3765
- const logger = getLogger();
3766
- logger.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
3767
- }
3768
- allowed.HOME = safeHome;
3769
- const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
3770
- for (const varName of apiKeyVars) {
3771
- if (process.env[varName]) {
3772
- allowed[varName] = process.env[varName];
3773
- }
3774
- }
3775
- const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_"];
3776
- for (const [key, value] of Object.entries(process.env)) {
3777
- if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
3778
- allowed[key] = value;
3779
- }
3780
- }
3781
- if (options.modelDef.env) {
3782
- Object.assign(allowed, options.modelDef.env);
3783
- }
3784
- if (options.env) {
3785
- Object.assign(allowed, options.env);
3786
- }
3787
- return allowed;
3788
- }
3789
3790
  async function executeOnce(binary, options, pidRegistry) {
3790
3791
  const cmd = _runOnceDeps.buildCmd(binary, options);
3791
3792
  const startTime = Date.now();
@@ -3803,7 +3804,7 @@ async function executeOnce(binary, options, pidRegistry) {
3803
3804
  cwd: options.workdir,
3804
3805
  stdout: "pipe",
3805
3806
  stderr: "inherit",
3806
- env: buildAllowedEnv(options)
3807
+ env: buildAllowedEnv({ env: options.env, modelEnv: options.modelDef.env })
3807
3808
  });
3808
3809
  const processPid = proc.pid;
3809
3810
  await pidRegistry.register(processPid);
@@ -3866,7 +3867,9 @@ var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_
3866
3867
  var init_execution = __esm(() => {
3867
3868
  init_logger2();
3868
3869
  init_bun_deps();
3870
+ init_env();
3869
3871
  init_cost2();
3872
+ init_env();
3870
3873
  _runOnceDeps = {
3871
3874
  killProc(proc, signal) {
3872
3875
  proc.kill(signal);
@@ -17978,7 +17981,8 @@ var init_schemas3 = __esm(() => {
17978
17981
  });
17979
17982
  SemanticReviewConfigSchema = exports_external.object({
17980
17983
  modelTier: ModelTierSchema.default("balanced"),
17981
- rules: exports_external.array(exports_external.string()).default([])
17984
+ rules: exports_external.array(exports_external.string()).default([]),
17985
+ timeoutMs: exports_external.number().int().positive().default(600000)
17982
17986
  });
17983
17987
  ReviewConfigSchema = exports_external.object({
17984
17988
  enabled: exports_external.boolean(),
@@ -18273,7 +18277,8 @@ var init_defaults = __esm(() => {
18273
18277
  pluginMode: "per-story",
18274
18278
  semantic: {
18275
18279
  modelTier: "balanced",
18276
- rules: []
18280
+ rules: [],
18281
+ timeoutMs: 600000
18277
18282
  }
18278
18283
  },
18279
18284
  plan: {
@@ -18859,13 +18864,13 @@ function skeletonImportLine(testFramework) {
18859
18864
  function acceptanceTestFilename(language) {
18860
18865
  switch (language?.toLowerCase()) {
18861
18866
  case "go":
18862
- return "acceptance_test.go";
18867
+ return ".nax-acceptance_test.go";
18863
18868
  case "python":
18864
- return "test_acceptance.py";
18869
+ return ".nax-acceptance.test.py";
18865
18870
  case "rust":
18866
- return "tests/acceptance.rs";
18871
+ return ".nax-acceptance.rs";
18867
18872
  default:
18868
- return "acceptance.test.ts";
18873
+ return ".nax-acceptance.test.ts";
18869
18874
  }
18870
18875
  }
18871
18876
  function buildAcceptanceRunCommand(testPath, testFramework, commandOverride) {
@@ -18936,7 +18941,7 @@ Rules:
18936
18941
  - Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
18937
18942
  - **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
18938
18943
  - Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
18939
- - **Path anchor (CRITICAL)**: This test file will be saved at \`<repo-root>/.nax/features/${options.featureName}/${acceptanceTestFilename(options.language)}\` and will ALWAYS run from the repo root. The repo root is exactly 4 \`../\` levels above \`__dirname\`: \`join(__dirname, '..', '..', '..', '..')\`. For monorepo projects, navigate into packages from root (e.g. \`join(root, 'apps/api/src')\`).`;
18944
+ - **Path anchor (CRITICAL)**: This test file lives at \`<package-root>/${acceptanceTestFilename(options.language)}\` and runs from the package root. Import from package sources using relative paths like \`./src/...\`. No deep \`../../../../\` traversal needed.`;
18940
18945
  const prompt = basePrompt;
18941
18946
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
18942
18947
  const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
@@ -18947,7 +18952,7 @@ Rules:
18947
18952
  });
18948
18953
  let testCode = extractTestCode(rawOutput);
18949
18954
  if (!testCode) {
18950
- const targetPath = join2(options.featureDir, "acceptance.test.ts");
18955
+ const targetPath = join2(options.featureDir, acceptanceTestFilename(options.language));
18951
18956
  try {
18952
18957
  const existing = await Bun.file(targetPath).text();
18953
18958
  const recovered = extractTestCode(existing);
@@ -19471,37 +19476,6 @@ function parseAcpxJsonOutput(rawOutput) {
19471
19476
  }
19472
19477
 
19473
19478
  // src/agents/acp/spawn-client.ts
19474
- import { homedir as homedir2 } from "os";
19475
- import { isAbsolute as isAbsolute2 } from "path";
19476
- function buildAllowedEnv2(extraEnv) {
19477
- const allowed = {};
19478
- const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
19479
- for (const varName of essentialVars) {
19480
- if (process.env[varName])
19481
- allowed[varName] = process.env[varName];
19482
- }
19483
- const rawHome = process.env.HOME ?? "";
19484
- const safeHome = rawHome && isAbsolute2(rawHome) ? rawHome : homedir2();
19485
- if (rawHome !== safeHome) {
19486
- getSafeLogger()?.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
19487
- }
19488
- allowed.HOME = safeHome;
19489
- const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
19490
- for (const varName of apiKeyVars) {
19491
- if (process.env[varName])
19492
- allowed[varName] = process.env[varName];
19493
- }
19494
- const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_"];
19495
- for (const [key, value] of Object.entries(process.env)) {
19496
- if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
19497
- allowed[key] = value;
19498
- }
19499
- }
19500
- if (extraEnv)
19501
- Object.assign(allowed, extraEnv);
19502
- return allowed;
19503
- }
19504
-
19505
19479
  class SpawnAcpSession {
19506
19480
  agentName;
19507
19481
  sessionName;
@@ -19591,7 +19565,7 @@ class SpawnAcpSession {
19591
19565
  await this.pidRegistry?.unregister(processPid);
19592
19566
  }
19593
19567
  }
19594
- async close() {
19568
+ async close(options) {
19595
19569
  if (this.activeProc) {
19596
19570
  try {
19597
19571
  this.activeProc.kill(15);
@@ -19610,6 +19584,14 @@ class SpawnAcpSession {
19610
19584
  stderr: stderr.slice(0, 200)
19611
19585
  });
19612
19586
  }
19587
+ if (options?.forceTerminate) {
19588
+ try {
19589
+ const stopProc = _spawnClientDeps.spawn(["acpx", this.agentName, "stop"], { stdout: "pipe", stderr: "pipe" });
19590
+ await stopProc.exited;
19591
+ } catch (err) {
19592
+ getSafeLogger()?.debug("acp-adapter", "acpx stop failed (swallowed)", { cause: String(err) });
19593
+ }
19594
+ }
19613
19595
  }
19614
19596
  async cancelActivePrompt() {
19615
19597
  if (this.activeProc) {
@@ -19643,7 +19625,7 @@ class SpawnAcpClient {
19643
19625
  this.agentName = lastToken;
19644
19626
  this.cwd = cwd || process.cwd();
19645
19627
  this.timeoutSeconds = timeoutSeconds || 1800;
19646
- this.env = buildAllowedEnv2();
19628
+ this.env = buildAllowedEnv();
19647
19629
  this.pidRegistry = pidRegistry;
19648
19630
  }
19649
19631
  async start() {}
@@ -19695,6 +19677,7 @@ var _spawnClientDeps;
19695
19677
  var init_spawn_client = __esm(() => {
19696
19678
  init_logger2();
19697
19679
  init_bun_deps();
19680
+ init_env();
19698
19681
  _spawnClientDeps = {
19699
19682
  spawn: typedSpawn
19700
19683
  };
@@ -20116,6 +20099,7 @@ class AcpAgentAdapter {
20116
20099
  const client = _acpAdapterDeps.createClient(cmdStr, workdir);
20117
20100
  await client.start();
20118
20101
  let session = null;
20102
+ let hadError = false;
20119
20103
  try {
20120
20104
  session = await client.createSession({
20121
20105
  agentName: this.name,
@@ -20157,6 +20141,7 @@ class AcpAgentAdapter {
20157
20141
  }
20158
20142
  return unwrapped;
20159
20143
  } catch (err) {
20144
+ hadError = true;
20160
20145
  const error48 = err instanceof Error ? err : new Error(String(err));
20161
20146
  lastError = error48;
20162
20147
  const shouldRetry = isRateLimitError(error48) && attempt < MAX_RATE_LIMIT_RETRIES - 1;
@@ -20170,7 +20155,7 @@ class AcpAgentAdapter {
20170
20155
  await _acpAdapterDeps.sleep(backoffMs);
20171
20156
  } finally {
20172
20157
  if (session) {
20173
- await session.close().catch(() => {});
20158
+ await session.close({ forceTerminate: hadError }).catch(() => {});
20174
20159
  }
20175
20160
  await client.close().catch(() => {});
20176
20161
  }
@@ -20978,7 +20963,7 @@ function isPlainObject2(value) {
20978
20963
 
20979
20964
  // src/config/path-security.ts
20980
20965
  import { existsSync as existsSync4, lstatSync, realpathSync } from "fs";
20981
- import { isAbsolute as isAbsolute3, normalize, resolve } from "path";
20966
+ import { isAbsolute as isAbsolute2, normalize, resolve } from "path";
20982
20967
  function validateDirectory(dirPath, baseDir) {
20983
20968
  const resolved = resolve(dirPath);
20984
20969
  if (!existsSync4(resolved)) {
@@ -21010,7 +20995,7 @@ function validateDirectory(dirPath, baseDir) {
21010
20995
  function isWithinDirectory(targetPath, basePath) {
21011
20996
  const normalizedTarget = normalize(targetPath);
21012
20997
  const normalizedBase = normalize(basePath);
21013
- if (!isAbsolute3(normalizedTarget) || !isAbsolute3(normalizedBase)) {
20998
+ if (!isAbsolute2(normalizedTarget) || !isAbsolute2(normalizedBase)) {
21014
20999
  return false;
21015
21000
  }
21016
21001
  const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
@@ -21046,10 +21031,10 @@ var MAX_DIRECTORY_DEPTH = 10;
21046
21031
  var init_path_security = () => {};
21047
21032
 
21048
21033
  // src/config/paths.ts
21049
- import { homedir as homedir3 } from "os";
21034
+ import { homedir as homedir2 } from "os";
21050
21035
  import { join as join5, resolve as resolve2 } from "path";
21051
21036
  function globalConfigDir() {
21052
- return join5(homedir3(), ".nax");
21037
+ return join5(homedir2(), ".nax");
21053
21038
  }
21054
21039
  var PROJECT_NAX_DIR = ".nax";
21055
21040
  var init_paths = () => {};
@@ -22320,7 +22305,7 @@ var package_default;
22320
22305
  var init_package = __esm(() => {
22321
22306
  package_default = {
22322
22307
  name: "@nathapp/nax",
22323
- version: "0.54.1",
22308
+ version: "0.54.2",
22324
22309
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22325
22310
  type: "module",
22326
22311
  bin: {
@@ -22397,8 +22382,8 @@ var init_version = __esm(() => {
22397
22382
  NAX_VERSION = package_default.version;
22398
22383
  NAX_COMMIT = (() => {
22399
22384
  try {
22400
- if (/^[0-9a-f]{6,10}$/.test("5282117"))
22401
- return "5282117";
22385
+ if (/^[0-9a-f]{6,10}$/.test("18dd8fc"))
22386
+ return "18dd8fc";
22402
22387
  } catch {}
22403
22388
  try {
22404
22389
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -24085,53 +24070,44 @@ var init_acceptance2 = __esm(() => {
24085
24070
  logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
24086
24071
  return { action: "continue" };
24087
24072
  }
24088
- const testPath = path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath);
24089
- const testFile = Bun.file(testPath);
24090
- const exists = await testFile.exists();
24091
- if (!exists) {
24092
- logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", {
24093
- testPath
24073
+ const testGroups = ctx.acceptanceTestPaths ?? [
24074
+ {
24075
+ testPath: path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath),
24076
+ packageDir: ctx.workdir
24077
+ }
24078
+ ];
24079
+ const allFailedACs = [];
24080
+ const allOutputParts = [];
24081
+ let anyError = false;
24082
+ let errorExitCode = 0;
24083
+ for (const { testPath, packageDir } of testGroups) {
24084
+ const testFile = Bun.file(testPath);
24085
+ const exists = await testFile.exists();
24086
+ if (!exists) {
24087
+ logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { testPath });
24088
+ continue;
24089
+ }
24090
+ const testCmdParts = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
24091
+ logger.info("acceptance", "Running acceptance command", {
24092
+ cmd: testCmdParts.join(" "),
24093
+ packageDir
24094
24094
  });
24095
- return { action: "continue" };
24096
- }
24097
- const testCmdParts = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
24098
- logger.info("acceptance", "Running acceptance command", { cmd: testCmdParts.join(" ") });
24099
- const proc = Bun.spawn(testCmdParts, {
24100
- cwd: ctx.workdir,
24101
- stdout: "pipe",
24102
- stderr: "pipe"
24103
- });
24104
- const [exitCode, stdout, stderr] = await Promise.all([
24105
- proc.exited,
24106
- new Response(proc.stdout).text(),
24107
- new Response(proc.stderr).text()
24108
- ]);
24109
- const output = `${stdout}
24095
+ const proc = Bun.spawn(testCmdParts, {
24096
+ cwd: packageDir,
24097
+ stdout: "pipe",
24098
+ stderr: "pipe"
24099
+ });
24100
+ const [exitCode, stdout, stderr] = await Promise.all([
24101
+ proc.exited,
24102
+ new Response(proc.stdout).text(),
24103
+ new Response(proc.stderr).text()
24104
+ ]);
24105
+ const output = `${stdout}
24110
24106
  ${stderr}`;
24111
- const failedACs = parseTestFailures(output);
24112
- const overrides = ctx.prd.acceptanceOverrides || {};
24113
- const actualFailures = failedACs.filter((acId) => !overrides[acId]);
24114
- if (actualFailures.length === 0 && exitCode === 0) {
24115
- logger.info("acceptance", "All acceptance tests passed");
24116
- return { action: "continue" };
24117
- }
24118
- if (failedACs.length > 0 && actualFailures.length === 0) {
24119
- logger.info("acceptance", "All failed ACs are overridden \u2014 treating as pass");
24120
- return { action: "continue" };
24121
- }
24122
- if (failedACs.length === 0 && exitCode !== 0) {
24123
- logger.error("acceptance", "Tests errored with no AC failures parsed", { exitCode });
24124
- logTestOutput(logger, "acceptance", output);
24125
- ctx.acceptanceFailures = {
24126
- failedACs: ["AC-ERROR"],
24127
- testOutput: output
24128
- };
24129
- return {
24130
- action: "fail",
24131
- reason: `Acceptance tests errored (exit code ${exitCode}): syntax error, import failure, or unhandled exception`
24132
- };
24133
- }
24134
- if (actualFailures.length > 0) {
24107
+ allOutputParts.push(output);
24108
+ const failedACs = parseTestFailures(output);
24109
+ const overrides = ctx.prd.acceptanceOverrides ?? {};
24110
+ const actualFailures = failedACs.filter((acId) => !overrides[acId]);
24135
24111
  const overriddenFailures = failedACs.filter((acId) => overrides[acId]);
24136
24112
  if (overriddenFailures.length > 0) {
24137
24113
  logger.warn("acceptance", "Skipped failures (overridden)", {
@@ -24139,19 +24115,52 @@ ${stderr}`;
24139
24115
  overrides: overriddenFailures.map((acId) => ({ acId, reason: overrides[acId] }))
24140
24116
  });
24141
24117
  }
24142
- logger.error("acceptance", "Acceptance tests failed", { failedACs: actualFailures });
24143
- logTestOutput(logger, "acceptance", output);
24144
- ctx.acceptanceFailures = {
24145
- failedACs: actualFailures,
24146
- testOutput: output
24147
- };
24118
+ if (failedACs.length === 0 && exitCode !== 0) {
24119
+ logger.error("acceptance", "Tests errored with no AC failures parsed", {
24120
+ exitCode,
24121
+ packageDir
24122
+ });
24123
+ logTestOutput(logger, "acceptance", output);
24124
+ anyError = true;
24125
+ errorExitCode = exitCode;
24126
+ allFailedACs.push("AC-ERROR");
24127
+ continue;
24128
+ }
24129
+ for (const acId of actualFailures) {
24130
+ if (!allFailedACs.includes(acId)) {
24131
+ allFailedACs.push(acId);
24132
+ }
24133
+ }
24134
+ if (actualFailures.length > 0) {
24135
+ logger.error("acceptance", "Acceptance tests failed", {
24136
+ failedACs: actualFailures,
24137
+ packageDir
24138
+ });
24139
+ logTestOutput(logger, "acceptance", output);
24140
+ } else if (exitCode === 0) {
24141
+ logger.info("acceptance", "Package acceptance tests passed", { packageDir });
24142
+ }
24143
+ }
24144
+ const combinedOutput = allOutputParts.join(`
24145
+ `);
24146
+ if (allFailedACs.length === 0) {
24147
+ logger.info("acceptance", "All acceptance tests passed");
24148
+ return { action: "continue" };
24149
+ }
24150
+ ctx.acceptanceFailures = {
24151
+ failedACs: allFailedACs,
24152
+ testOutput: combinedOutput
24153
+ };
24154
+ if (anyError) {
24148
24155
  return {
24149
24156
  action: "fail",
24150
- reason: `Acceptance tests failed: ${actualFailures.join(", ")}`
24157
+ reason: `Acceptance tests errored (exit code ${errorExitCode}): syntax error, import failure, or unhandled exception`
24151
24158
  };
24152
24159
  }
24153
- logger.info("acceptance", "All acceptance tests passed");
24154
- return { action: "continue" };
24160
+ return {
24161
+ action: "fail",
24162
+ reason: `Acceptance tests failed: ${allFailedACs.join(", ")}`
24163
+ };
24155
24164
  }
24156
24165
  };
24157
24166
  });
@@ -24333,57 +24342,103 @@ ${stderr}` };
24333
24342
  return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
24334
24343
  }
24335
24344
  const language = (ctx.effectiveConfig ?? ctx.config).project?.language;
24336
- const testPath = path5.join(ctx.featureDir, acceptanceTestFilename(language));
24337
24345
  const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
24338
24346
  const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria);
24347
+ const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-"));
24348
+ const workdirGroups = new Map;
24349
+ for (const story of nonFixStories) {
24350
+ const wd = story.workdir ?? "";
24351
+ if (!workdirGroups.has(wd)) {
24352
+ workdirGroups.set(wd, { stories: [], criteria: [] });
24353
+ }
24354
+ const group = workdirGroups.get(wd);
24355
+ if (group) {
24356
+ group.stories.push(story);
24357
+ group.criteria.push(...story.acceptanceCriteria);
24358
+ }
24359
+ }
24360
+ if (workdirGroups.size === 0) {
24361
+ workdirGroups.set("", { stories: [], criteria: [] });
24362
+ }
24363
+ const testPaths = [];
24364
+ for (const [workdir] of workdirGroups) {
24365
+ const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
24366
+ const testPath = path5.join(packageDir, acceptanceTestFilename(language));
24367
+ testPaths.push({ testPath, packageDir });
24368
+ }
24339
24369
  let totalCriteria = 0;
24340
24370
  let testableCount = 0;
24341
- const fileExists = await _acceptanceSetupDeps.fileExists(testPath);
24342
- let shouldGenerate = !fileExists;
24343
- if (fileExists) {
24371
+ const existsResults = await Promise.all(testPaths.map(({ testPath }) => _acceptanceSetupDeps.fileExists(testPath)));
24372
+ const anyFileMissing = existsResults.some((exists) => !exists);
24373
+ let shouldGenerate = anyFileMissing;
24374
+ if (!anyFileMissing) {
24344
24375
  const fingerprint = computeACFingerprint(allCriteria);
24345
24376
  const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
24377
+ getSafeLogger()?.debug("acceptance-setup", "Fingerprint check", {
24378
+ currentFingerprint: fingerprint,
24379
+ storedFingerprint: meta3?.acFingerprint ?? "none",
24380
+ match: meta3?.acFingerprint === fingerprint
24381
+ });
24346
24382
  if (!meta3 || meta3.acFingerprint !== fingerprint) {
24347
- await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
24348
- await _acceptanceSetupDeps.deleteFile(testPath);
24383
+ getSafeLogger()?.info("acceptance-setup", "ACs changed \u2014 regenerating acceptance tests", {
24384
+ reason: !meta3 ? "no meta file" : "fingerprint mismatch"
24385
+ });
24386
+ for (const { testPath } of testPaths) {
24387
+ if (await _acceptanceSetupDeps.fileExists(testPath)) {
24388
+ await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
24389
+ await _acceptanceSetupDeps.deleteFile(testPath);
24390
+ }
24391
+ }
24349
24392
  shouldGenerate = true;
24393
+ } else {
24394
+ getSafeLogger()?.info("acceptance-setup", "Reusing existing acceptance tests (fingerprint match)");
24350
24395
  }
24351
24396
  }
24352
24397
  if (shouldGenerate) {
24353
24398
  totalCriteria = allCriteria.length;
24354
24399
  const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
24355
24400
  const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.config.autoMode.defaultAgent);
24356
- let refinedCriteria;
24401
+ let allRefinedCriteria;
24357
24402
  if (ctx.config.acceptance.refinement) {
24358
- refinedCriteria = await _acceptanceSetupDeps.refine(allCriteria, {
24359
- storyId: ctx.prd.userStories[0]?.id ?? "US-001",
24360
- codebaseContext: "",
24361
- config: ctx.config,
24362
- testStrategy: ctx.config.acceptance.testStrategy,
24363
- testFramework: ctx.config.acceptance.testFramework
24364
- });
24403
+ allRefinedCriteria = [];
24404
+ for (const story of nonFixStories) {
24405
+ const storyRefined = await _acceptanceSetupDeps.refine(story.acceptanceCriteria, {
24406
+ storyId: story.id,
24407
+ codebaseContext: "",
24408
+ config: ctx.config,
24409
+ testStrategy: ctx.config.acceptance.testStrategy,
24410
+ testFramework: ctx.config.acceptance.testFramework
24411
+ });
24412
+ allRefinedCriteria = allRefinedCriteria.concat(storyRefined);
24413
+ }
24365
24414
  } else {
24366
- refinedCriteria = allCriteria.map((c) => ({
24415
+ allRefinedCriteria = nonFixStories.flatMap((story) => story.acceptanceCriteria.map((c) => ({
24367
24416
  original: c,
24368
24417
  refined: c,
24369
24418
  testable: true,
24370
- storyId: ctx.prd.userStories[0]?.id ?? "US-001"
24371
- }));
24419
+ storyId: story.id
24420
+ })));
24421
+ }
24422
+ testableCount = allRefinedCriteria.filter((r) => r.testable).length;
24423
+ for (const [workdir, group] of workdirGroups) {
24424
+ const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
24425
+ const testPath = path5.join(packageDir, acceptanceTestFilename(language));
24426
+ const groupStoryIds = new Set(group.stories.map((s) => s.id));
24427
+ const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
24428
+ const result = await _acceptanceSetupDeps.generate(group.stories, groupRefined, {
24429
+ featureName: ctx.prd.feature,
24430
+ workdir: packageDir,
24431
+ featureDir: ctx.featureDir,
24432
+ codebaseContext: "",
24433
+ modelTier: ctx.config.acceptance.model ?? "fast",
24434
+ modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
24435
+ config: ctx.config,
24436
+ testStrategy: ctx.config.acceptance.testStrategy,
24437
+ testFramework: ctx.config.acceptance.testFramework,
24438
+ adapter: agent ?? undefined
24439
+ });
24440
+ await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
24372
24441
  }
24373
- testableCount = refinedCriteria.filter((r) => r.testable).length;
24374
- const result = await _acceptanceSetupDeps.generate(ctx.prd.userStories, refinedCriteria, {
24375
- featureName: ctx.prd.feature,
24376
- workdir: ctx.workdir,
24377
- featureDir: ctx.featureDir,
24378
- codebaseContext: "",
24379
- modelTier: ctx.config.acceptance.model ?? "fast",
24380
- modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
24381
- config: ctx.config,
24382
- testStrategy: ctx.config.acceptance.testStrategy,
24383
- testFramework: ctx.config.acceptance.testFramework,
24384
- adapter: agent ?? undefined
24385
- });
24386
- await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
24387
24442
  const fingerprint = computeACFingerprint(allCriteria);
24388
24443
  await _acceptanceSetupDeps.writeMeta(metaPath, {
24389
24444
  generatedAt: new Date().toISOString(),
@@ -24393,22 +24448,32 @@ ${stderr}` };
24393
24448
  generator: "nax"
24394
24449
  });
24395
24450
  }
24451
+ ctx.acceptanceTestPaths = testPaths;
24396
24452
  if (ctx.config.acceptance.redGate === false) {
24397
24453
  ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
24398
24454
  return { action: "continue" };
24399
24455
  }
24400
24456
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
24401
- const runCmd = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
24402
- getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", { cmd: runCmd.join(" ") });
24403
- const { exitCode } = await _acceptanceSetupDeps.runTest(testPath, ctx.workdir, runCmd);
24404
- if (exitCode === 0) {
24457
+ let redFailCount = 0;
24458
+ for (const { testPath, packageDir } of testPaths) {
24459
+ const runCmd = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
24460
+ getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
24461
+ cmd: runCmd.join(" "),
24462
+ packageDir
24463
+ });
24464
+ const { exitCode } = await _acceptanceSetupDeps.runTest(testPath, packageDir, runCmd);
24465
+ if (exitCode !== 0) {
24466
+ redFailCount++;
24467
+ }
24468
+ }
24469
+ if (redFailCount === 0) {
24405
24470
  ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
24406
24471
  return {
24407
24472
  action: "skip",
24408
24473
  reason: "[acceptance-setup] Acceptance tests already pass \u2014 they are not testing new behavior. Skipping acceptance gate."
24409
24474
  };
24410
24475
  }
24411
- ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 1 };
24476
+ ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount };
24412
24477
  return { action: "continue" };
24413
24478
  }
24414
24479
  };
@@ -24736,7 +24801,11 @@ If the implementation looks correct, respond with { "passed": true, "findings":
24736
24801
  }
24737
24802
  function parseLLMResponse(raw) {
24738
24803
  try {
24739
- const parsed = JSON.parse(raw);
24804
+ let cleaned = raw.trim();
24805
+ const fenceMatch = cleaned.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/);
24806
+ if (fenceMatch)
24807
+ cleaned = fenceMatch[1].trim();
24808
+ const parsed = JSON.parse(cleaned);
24740
24809
  if (typeof parsed !== "object" || parsed === null)
24741
24810
  return null;
24742
24811
  const obj = parsed;
@@ -24784,6 +24853,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
24784
24853
  durationMs: Date.now() - startTime
24785
24854
  };
24786
24855
  }
24856
+ logger?.info("review", "Running semantic check", { storyId: story.id, modelTier: semanticConfig.modelTier });
24787
24857
  const rawDiff = await collectDiff(workdir, storyGitRef);
24788
24858
  const diff = truncateDiff(rawDiff);
24789
24859
  const agent = modelResolver(semanticConfig.modelTier);
@@ -24803,7 +24873,11 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
24803
24873
  const prompt = buildPrompt(story, semanticConfig, diff);
24804
24874
  let rawResponse;
24805
24875
  try {
24806
- rawResponse = await agent.complete(prompt);
24876
+ rawResponse = await agent.complete(prompt, {
24877
+ sessionName: `nax-semantic-${story.id}`,
24878
+ workdir,
24879
+ timeoutMs: semanticConfig.timeoutMs
24880
+ });
24807
24881
  } catch (err) {
24808
24882
  logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
24809
24883
  return {
@@ -24828,6 +24902,11 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
24828
24902
  };
24829
24903
  }
24830
24904
  if (!parsed.passed && parsed.findings.length > 0) {
24905
+ const durationMs2 = Date.now() - startTime;
24906
+ logger?.warn("review", `Semantic review failed: ${parsed.findings.length} findings`, {
24907
+ storyId: story.id,
24908
+ durationMs: durationMs2
24909
+ });
24831
24910
  const output = `Semantic review failed:
24832
24911
 
24833
24912
  ${formatFindings(parsed.findings)}`;
@@ -24837,17 +24916,21 @@ ${formatFindings(parsed.findings)}`;
24837
24916
  command: "",
24838
24917
  exitCode: 1,
24839
24918
  output,
24840
- durationMs: Date.now() - startTime,
24919
+ durationMs: durationMs2,
24841
24920
  findings: toReviewFindings(parsed.findings)
24842
24921
  };
24843
24922
  }
24923
+ const durationMs = Date.now() - startTime;
24924
+ if (parsed.passed) {
24925
+ logger?.info("review", "Semantic review passed", { storyId: story.id, durationMs });
24926
+ }
24844
24927
  return {
24845
24928
  check: "semantic",
24846
24929
  success: parsed.passed,
24847
24930
  command: "",
24848
24931
  exitCode: parsed.passed ? 0 : 1,
24849
24932
  output: parsed.passed ? "Semantic review passed" : "Semantic review failed (no findings)",
24850
- durationMs: Date.now() - startTime
24933
+ durationMs
24851
24934
  };
24852
24935
  }
24853
24936
  var _semanticDeps, DIFF_CAP_BYTES = 12288, DEFAULT_RULES;
@@ -25030,7 +25113,8 @@ async function runReview(config2, workdir, executionConfig, qualityCommands, sto
25030
25113
  /nax\/features\/[^/]+\/acceptance-refined\.json$/,
25031
25114
  /\.nax-verifier-verdict\.json$/,
25032
25115
  /\.nax-pids$/,
25033
- /\.nax-wt\//
25116
+ /\.nax-wt\//,
25117
+ /\.nax-acceptance[^/]*$/
25034
25118
  ];
25035
25119
  const uncommittedFiles = allUncommittedFiles.filter((f) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(f)));
25036
25120
  if (uncommittedFiles.length > 0) {
@@ -25055,7 +25139,11 @@ Stage and commit these files before running review.`
25055
25139
  description: story?.description ?? "",
25056
25140
  acceptanceCriteria: story?.acceptanceCriteria ?? []
25057
25141
  };
25058
- const semanticCfg = config2.semantic ?? { modelTier: "balanced", rules: [] };
25142
+ const semanticCfg = config2.semantic ?? {
25143
+ modelTier: "balanced",
25144
+ rules: [],
25145
+ timeoutMs: 600000
25146
+ };
25059
25147
  const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null));
25060
25148
  checks3.push(result2);
25061
25149
  if (!result2.success && !firstFailure) {
@@ -31090,7 +31178,7 @@ var init_init_context = __esm(() => {
31090
31178
 
31091
31179
  // src/utils/path-security.ts
31092
31180
  import { realpathSync as realpathSync3 } from "fs";
31093
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join31, normalize as normalize2, resolve as resolve5 } from "path";
31181
+ import { dirname as dirname4, isAbsolute as isAbsolute3, join as join31, normalize as normalize2, resolve as resolve5 } from "path";
31094
31182
  function safeRealpathForComparison(p) {
31095
31183
  try {
31096
31184
  return realpathSync3(p);
@@ -31107,7 +31195,7 @@ function validateModulePath(modulePath, allowedRoots) {
31107
31195
  return { valid: false, error: "Module path is empty" };
31108
31196
  }
31109
31197
  const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve5(r)));
31110
- if (isAbsolute4(modulePath)) {
31198
+ if (isAbsolute3(modulePath)) {
31111
31199
  const normalized = normalize2(modulePath);
31112
31200
  const resolved = safeRealpathForComparison(normalized);
31113
31201
  const isWithin = resolvedRoots.some((root) => resolved.startsWith(`${root}/`) || resolved === root);
@@ -31735,7 +31823,8 @@ var init_checks_git = __esm(() => {
31735
31823
  /^.{2} \.nax\/features\/[^/]+\/acceptance-refined\.json$/,
31736
31824
  /^.{2} \.nax-verifier-verdict\.json$/,
31737
31825
  /^.{2} \.nax-pids$/,
31738
- /^.{2} \.nax-wt\//
31826
+ /^.{2} \.nax-wt\//,
31827
+ /^.{2} .*\.nax-acceptance[^/]*$/
31739
31828
  ];
31740
31829
  });
31741
31830
 
@@ -31948,7 +32037,7 @@ var init_checks_blockers = __esm(() => {
31948
32037
 
31949
32038
  // src/precheck/checks-warnings.ts
31950
32039
  import { existsSync as existsSync30 } from "fs";
31951
- import { isAbsolute as isAbsolute6 } from "path";
32040
+ import { isAbsolute as isAbsolute5 } from "path";
31952
32041
  async function checkClaudeMdExists(workdir) {
31953
32042
  const claudeMdPath = `${workdir}/CLAUDE.md`;
31954
32043
  const passed = existsSync30(claudeMdPath);
@@ -32048,7 +32137,8 @@ async function checkGitignoreCoversNax(workdir) {
32048
32137
  ".nax/metrics.json",
32049
32138
  ".nax/features/*/status.json",
32050
32139
  ".nax-pids",
32051
- ".nax-wt/"
32140
+ ".nax-wt/",
32141
+ "**/.nax-acceptance*"
32052
32142
  ];
32053
32143
  const missing = patterns.filter((pattern) => !content.includes(pattern));
32054
32144
  const passed = missing.length === 0;
@@ -32080,7 +32170,7 @@ async function checkPromptOverrideFiles(config2, workdir) {
32080
32170
  }
32081
32171
  async function checkHomeEnvValid() {
32082
32172
  const home = process.env.HOME ?? "";
32083
- const passed = home !== "" && isAbsolute6(home);
32173
+ const passed = home !== "" && isAbsolute5(home);
32084
32174
  return {
32085
32175
  name: "home-env-valid",
32086
32176
  tier: "warning",
@@ -32183,6 +32273,24 @@ async function checkLanguageTools(profile, workdir) {
32183
32273
  message: `Missing ${language} tools: ${missing.join(", ")}. ${toolConfig.installHint}`
32184
32274
  };
32185
32275
  }
32276
+ function checkBuildCommandInReviewChecks(config2) {
32277
+ const hasBuildCmd = !!(config2.review?.commands?.build || config2.quality?.commands?.build);
32278
+ const buildInChecks = config2.review?.checks?.includes("build") ?? false;
32279
+ if (hasBuildCmd && !buildInChecks) {
32280
+ return {
32281
+ name: "build-command-in-review-checks",
32282
+ tier: "warning",
32283
+ passed: false,
32284
+ message: 'A build command is configured but "build" is not in review.checks \u2014 the build step will never run. Add "build" to review.checks to enable it.'
32285
+ };
32286
+ }
32287
+ return {
32288
+ name: "build-command-in-review-checks",
32289
+ tier: "warning",
32290
+ passed: true,
32291
+ message: "build command check OK"
32292
+ };
32293
+ }
32186
32294
  var _languageToolsDeps;
32187
32295
  var init_checks_warnings = __esm(() => {
32188
32296
  _languageToolsDeps = {
@@ -32374,7 +32482,8 @@ function getEnvironmentWarnings(config2, workdir) {
32374
32482
  () => checkHomeEnvValid(),
32375
32483
  () => checkPromptOverrideFiles(config2, workdir),
32376
32484
  () => checkLanguageTools(config2.project, workdir),
32377
- () => checkMultiAgentHealth()
32485
+ () => checkMultiAgentHealth(),
32486
+ () => Promise.resolve(checkBuildCommandInReviewChecks(config2))
32378
32487
  ];
32379
32488
  }
32380
32489
  function getProjectBlockers(prd) {
@@ -34711,12 +34820,12 @@ var init_parallel_executor = __esm(() => {
34711
34820
 
34712
34821
  // src/pipeline/subscribers/events-writer.ts
34713
34822
  import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
34714
- import { homedir as homedir6 } from "os";
34823
+ import { homedir as homedir5 } from "os";
34715
34824
  import { basename as basename5, join as join49 } from "path";
34716
34825
  function wireEventsWriter(bus, feature, runId, workdir) {
34717
34826
  const logger = getSafeLogger();
34718
34827
  const project = basename5(workdir);
34719
- const eventsDir = join49(homedir6(), ".nax", "events", project);
34828
+ const eventsDir = join49(homedir5(), ".nax", "events", project);
34720
34829
  const eventsFile = join49(eventsDir, "events.jsonl");
34721
34830
  let dirReady = false;
34722
34831
  const write = (line) => {
@@ -34890,12 +34999,12 @@ var init_interaction2 = __esm(() => {
34890
34999
 
34891
35000
  // src/pipeline/subscribers/registry.ts
34892
35001
  import { mkdir as mkdir3, writeFile } from "fs/promises";
34893
- import { homedir as homedir7 } from "os";
35002
+ import { homedir as homedir6 } from "os";
34894
35003
  import { basename as basename6, join as join50 } from "path";
34895
35004
  function wireRegistry(bus, feature, runId, workdir) {
34896
35005
  const logger = getSafeLogger();
34897
35006
  const project = basename6(workdir);
34898
- const runDir = join50(homedir7(), ".nax", "runs", `${project}-${feature}-${runId}`);
35007
+ const runDir = join50(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
34899
35008
  const metaFile = join50(runDir, "meta.json");
34900
35009
  const unsub = bus.on("run:started", (_ev) => {
34901
35010
  (async () => {
@@ -35759,7 +35868,8 @@ async function executeSequential(ctx, initialPrd) {
35759
35868
  story: prd.userStories[0],
35760
35869
  stories: prd.userStories,
35761
35870
  routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
35762
- hooks: ctx.hooks
35871
+ hooks: ctx.hooks,
35872
+ agentGetFn: ctx.agentGetFn
35763
35873
  };
35764
35874
  await runPipeline(preRunPipeline, preRunCtx, ctx.eventEmitter);
35765
35875
  while (iterations < ctx.config.execution.maxIterations) {
@@ -67558,7 +67668,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
67558
67668
  // bin/nax.ts
67559
67669
  init_source();
67560
67670
  import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
67561
- import { homedir as homedir9 } from "os";
67671
+ import { homedir as homedir8 } from "os";
67562
67672
  import { join as join56 } from "path";
67563
67673
 
67564
67674
  // node_modules/commander/esm.mjs
@@ -71101,10 +71211,10 @@ import { readdir as readdir3 } from "fs/promises";
71101
71211
  import { join as join39 } from "path";
71102
71212
 
71103
71213
  // src/utils/paths.ts
71104
- import { homedir as homedir5 } from "os";
71214
+ import { homedir as homedir4 } from "os";
71105
71215
  import { join as join38 } from "path";
71106
71216
  function getRunsDir() {
71107
- return process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs");
71217
+ return process.env.NAX_RUNS_DIR ?? join38(homedir4(), ".nax", "runs");
71108
71218
  }
71109
71219
 
71110
71220
  // src/commands/logs-reader.ts
@@ -79668,7 +79778,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
79668
79778
  config2.autoMode.defaultAgent = options.agent;
79669
79779
  }
79670
79780
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
79671
- const globalNaxDir = join56(homedir9(), ".nax");
79781
+ const globalNaxDir = join56(homedir8(), ".nax");
79672
79782
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
79673
79783
  const eventEmitter = new PipelineEventEmitter;
79674
79784
  let tuiInstance;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.54.1",
3
+ "version": "0.54.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {