@nathapp/nax 0.45.0 → 0.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/nax.ts +7 -6
  3. package/dist/nax.js +340 -202
  4. package/package.json +1 -1
  5. package/src/acceptance/generator.ts +1 -1
  6. package/src/acceptance/types.ts +2 -0
  7. package/src/agents/acp/adapter.ts +34 -6
  8. package/src/agents/acp/cost.ts +5 -75
  9. package/src/agents/acp/index.ts +0 -2
  10. package/src/agents/acp/parser.ts +57 -104
  11. package/src/agents/acp/spawn-client.ts +13 -2
  12. package/src/agents/{claude.ts → claude/adapter.ts} +15 -12
  13. package/src/agents/{claude-complete.ts → claude/complete.ts} +3 -3
  14. package/src/agents/claude/cost.ts +16 -0
  15. package/src/agents/{claude-execution.ts → claude/execution.ts} +17 -6
  16. package/src/agents/claude/index.ts +3 -0
  17. package/src/agents/{claude-interactive.ts → claude/interactive.ts} +4 -4
  18. package/src/agents/{claude-plan.ts → claude/plan.ts} +12 -9
  19. package/src/agents/cost/calculate.ts +154 -0
  20. package/src/agents/cost/index.ts +10 -0
  21. package/src/agents/cost/parse.ts +97 -0
  22. package/src/agents/cost/pricing.ts +59 -0
  23. package/src/agents/cost/types.ts +45 -0
  24. package/src/agents/index.ts +6 -4
  25. package/src/agents/registry.ts +5 -5
  26. package/src/agents/{claude-decompose.ts → shared/decompose.ts} +2 -2
  27. package/src/agents/{model-resolution.ts → shared/model-resolution.ts} +2 -2
  28. package/src/agents/{types-extended.ts → shared/types-extended.ts} +4 -4
  29. package/src/agents/{validation.ts → shared/validation.ts} +2 -2
  30. package/src/agents/{version-detection.ts → shared/version-detection.ts} +3 -3
  31. package/src/agents/types.ts +11 -4
  32. package/src/cli/agents.ts +1 -1
  33. package/src/cli/init.ts +15 -1
  34. package/src/pipeline/stages/acceptance-setup.ts +1 -0
  35. package/src/pipeline/stages/acceptance.ts +5 -8
  36. package/src/pipeline/stages/regression.ts +2 -0
  37. package/src/pipeline/stages/verify.ts +5 -10
  38. package/src/precheck/checks-agents.ts +1 -1
  39. package/src/precheck/checks-git.ts +28 -2
  40. package/src/precheck/checks-warnings.ts +30 -2
  41. package/src/precheck/checks.ts +1 -0
  42. package/src/precheck/index.ts +2 -0
  43. package/src/utils/log-test-output.ts +25 -0
  44. package/src/agents/cost.ts +0 -268
  45. /package/src/agents/{adapters/aider.ts → aider/adapter.ts} +0 -0
  46. /package/src/agents/{adapters/codex.ts → codex/adapter.ts} +0 -0
  47. /package/src/agents/{adapters/gemini.ts → gemini/adapter.ts} +0 -0
  48. /package/src/agents/{adapters/opencode.ts → opencode/adapter.ts} +0 -0
package/dist/nax.js CHANGED
@@ -3240,60 +3240,6 @@ async function withProcessTimeout(proc, timeoutMs, opts) {
3240
3240
  return { exitCode, timedOut };
3241
3241
  }
3242
3242
 
3243
- // src/agents/types.ts
3244
- var CompleteError;
3245
- var init_types2 = __esm(() => {
3246
- CompleteError = class CompleteError extends Error {
3247
- exitCode;
3248
- constructor(message, exitCode) {
3249
- super(message);
3250
- this.exitCode = exitCode;
3251
- this.name = "CompleteError";
3252
- }
3253
- };
3254
- });
3255
-
3256
- // src/agents/claude-complete.ts
3257
- async function executeComplete(binary, prompt, options) {
3258
- const cmd = [binary, "-p", prompt];
3259
- if (options?.model) {
3260
- cmd.push("--model", options.model);
3261
- }
3262
- if (options?.jsonMode) {
3263
- cmd.push("--output-format", "json");
3264
- }
3265
- const { skipPermissions } = resolvePermissions(options?.config, "complete");
3266
- if (skipPermissions) {
3267
- cmd.push("--dangerously-skip-permissions");
3268
- }
3269
- const spawnOpts = { stdout: "pipe", stderr: "pipe" };
3270
- if (options?.workdir)
3271
- spawnOpts.cwd = options.workdir;
3272
- const proc = _completeDeps.spawn(cmd, spawnOpts);
3273
- const exitCode = await proc.exited;
3274
- const stdout = await new Response(proc.stdout).text();
3275
- const stderr = await new Response(proc.stderr).text();
3276
- const trimmed = stdout.trim();
3277
- if (exitCode !== 0) {
3278
- const errorDetails = stderr.trim() || trimmed;
3279
- const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
3280
- throw new CompleteError(errorMessage, exitCode);
3281
- }
3282
- if (!trimmed) {
3283
- throw new CompleteError("complete() returned empty output");
3284
- }
3285
- return trimmed;
3286
- }
3287
- var _completeDeps;
3288
- var init_claude_complete = __esm(() => {
3289
- init_types2();
3290
- _completeDeps = {
3291
- spawn(cmd, opts) {
3292
- return Bun.spawn(cmd, opts);
3293
- }
3294
- };
3295
- });
3296
-
3297
3243
  // src/config/test-strategy.ts
3298
3244
  function resolveTestStrategy(raw) {
3299
3245
  if (!raw)
@@ -3343,7 +3289,7 @@ var init_test_strategy = __esm(() => {
3343
3289
  ];
3344
3290
  });
3345
3291
 
3346
- // src/agents/claude-decompose.ts
3292
+ // src/agents/shared/decompose.ts
3347
3293
  function buildDecomposePrompt(options) {
3348
3294
  return `You are a requirements analyst. Break down the following feature specification into user stories and classify each story's complexity.
3349
3295
 
@@ -3454,11 +3400,104 @@ function coerceComplexity(value) {
3454
3400
  }
3455
3401
  return "medium";
3456
3402
  }
3457
- var init_claude_decompose = __esm(() => {
3403
+ var init_decompose = __esm(() => {
3458
3404
  init_test_strategy();
3459
3405
  });
3460
3406
 
3461
- // src/agents/cost.ts
3407
+ // src/agents/types.ts
3408
+ var CompleteError;
3409
+ var init_types2 = __esm(() => {
3410
+ CompleteError = class CompleteError extends Error {
3411
+ exitCode;
3412
+ constructor(message, exitCode) {
3413
+ super(message);
3414
+ this.exitCode = exitCode;
3415
+ this.name = "CompleteError";
3416
+ }
3417
+ };
3418
+ });
3419
+
3420
+ // src/agents/claude/complete.ts
3421
+ async function executeComplete(binary, prompt, options) {
3422
+ const cmd = [binary, "-p", prompt];
3423
+ if (options?.model) {
3424
+ cmd.push("--model", options.model);
3425
+ }
3426
+ if (options?.jsonMode) {
3427
+ cmd.push("--output-format", "json");
3428
+ }
3429
+ const { skipPermissions } = resolvePermissions(options?.config, "complete");
3430
+ if (skipPermissions) {
3431
+ cmd.push("--dangerously-skip-permissions");
3432
+ }
3433
+ const spawnOpts = { stdout: "pipe", stderr: "pipe" };
3434
+ if (options?.workdir)
3435
+ spawnOpts.cwd = options.workdir;
3436
+ const proc = _completeDeps.spawn(cmd, spawnOpts);
3437
+ const exitCode = await proc.exited;
3438
+ const stdout = await new Response(proc.stdout).text();
3439
+ const stderr = await new Response(proc.stderr).text();
3440
+ const trimmed = stdout.trim();
3441
+ if (exitCode !== 0) {
3442
+ const errorDetails = stderr.trim() || trimmed;
3443
+ const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
3444
+ throw new CompleteError(errorMessage, exitCode);
3445
+ }
3446
+ if (!trimmed) {
3447
+ throw new CompleteError("complete() returned empty output");
3448
+ }
3449
+ return trimmed;
3450
+ }
3451
+ var _completeDeps;
3452
+ var init_complete = __esm(() => {
3453
+ init_types2();
3454
+ _completeDeps = {
3455
+ spawn(cmd, opts) {
3456
+ return Bun.spawn(cmd, opts);
3457
+ }
3458
+ };
3459
+ });
3460
+
3461
+ // src/agents/cost/pricing.ts
3462
+ var COST_RATES, MODEL_PRICING;
3463
+ var init_pricing = __esm(() => {
3464
+ COST_RATES = {
3465
+ fast: {
3466
+ inputPer1M: 0.8,
3467
+ outputPer1M: 4
3468
+ },
3469
+ balanced: {
3470
+ inputPer1M: 3,
3471
+ outputPer1M: 15
3472
+ },
3473
+ powerful: {
3474
+ inputPer1M: 15,
3475
+ outputPer1M: 75
3476
+ }
3477
+ };
3478
+ MODEL_PRICING = {
3479
+ sonnet: { input: 3, output: 15 },
3480
+ haiku: { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3481
+ opus: { input: 15, output: 75 },
3482
+ "claude-sonnet-4": { input: 3, output: 15 },
3483
+ "claude-sonnet-4-5": { input: 3, output: 15 },
3484
+ "claude-sonnet-4-6": { input: 3, output: 15 },
3485
+ "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3486
+ "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3487
+ "claude-opus": { input: 15, output: 75 },
3488
+ "claude-opus-4": { input: 15, output: 75 },
3489
+ "claude-opus-4-6": { input: 15, output: 75 },
3490
+ "gpt-4.1": { input: 10, output: 30 },
3491
+ "gpt-4": { input: 30, output: 60 },
3492
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3493
+ "gemini-2.5-pro": { input: 0.075, output: 0.3 },
3494
+ "gemini-2-pro": { input: 0.075, output: 0.3 },
3495
+ codex: { input: 0.02, output: 0.06 },
3496
+ "code-davinci-002": { input: 0.02, output: 0.06 }
3497
+ };
3498
+ });
3499
+
3500
+ // src/agents/cost/parse.ts
3462
3501
  function parseTokenUsage(output) {
3463
3502
  try {
3464
3503
  const jsonMatch = output.match(/\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/);
@@ -3502,6 +3541,8 @@ function parseTokenUsage(output) {
3502
3541
  }
3503
3542
  return null;
3504
3543
  }
3544
+
3545
+ // src/agents/cost/calculate.ts
3505
3546
  function estimateCost(modelTier, inputTokens, outputTokens, customRates) {
3506
3547
  const rates = customRates ?? COST_RATES[modelTier];
3507
3548
  const inputCost = inputTokens / 1e6 * rates.inputPer1M;
@@ -3543,25 +3584,45 @@ function formatCostWithConfidence(estimate) {
3543
3584
  return `~${formattedCost} (duration-based)`;
3544
3585
  }
3545
3586
  }
3546
- var COST_RATES;
3587
+ function estimateCostFromTokenUsage(usage, model) {
3588
+ const pricing = MODEL_PRICING[model];
3589
+ if (!pricing) {
3590
+ const fallbackInputRate = 3 / 1e6;
3591
+ const fallbackOutputRate = 15 / 1e6;
3592
+ const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
3593
+ const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
3594
+ const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
3595
+ const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
3596
+ return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
3597
+ }
3598
+ const inputRate = pricing.input / 1e6;
3599
+ const outputRate = pricing.output / 1e6;
3600
+ const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
3601
+ const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
3602
+ const inputCost = (usage.input_tokens ?? 0) * inputRate;
3603
+ const outputCost = (usage.output_tokens ?? 0) * outputRate;
3604
+ const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
3605
+ const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
3606
+ return inputCost + outputCost + cacheReadCost + cacheCreationCost;
3607
+ }
3608
+ var init_calculate = __esm(() => {
3609
+ init_pricing();
3610
+ });
3611
+
3612
+ // src/agents/cost/index.ts
3547
3613
  var init_cost = __esm(() => {
3548
- COST_RATES = {
3549
- fast: {
3550
- inputPer1M: 0.8,
3551
- outputPer1M: 4
3552
- },
3553
- balanced: {
3554
- inputPer1M: 3,
3555
- outputPer1M: 15
3556
- },
3557
- powerful: {
3558
- inputPer1M: 15,
3559
- outputPer1M: 75
3560
- }
3561
- };
3614
+ init_pricing();
3615
+ init_calculate();
3562
3616
  });
3563
3617
 
3564
- // src/agents/claude-execution.ts
3618
+ // src/agents/claude/cost.ts
3619
+ var init_cost2 = __esm(() => {
3620
+ init_cost();
3621
+ });
3622
+
3623
+ // src/agents/claude/execution.ts
3624
+ import { homedir } from "os";
3625
+ import { isAbsolute } from "path";
3565
3626
  function buildCommand(binary, options) {
3566
3627
  const model = options.modelDef.model;
3567
3628
  const { skipPermissions } = resolvePermissions(options.config, options.pipelineStage ?? "run");
@@ -3570,12 +3631,19 @@ function buildCommand(binary, options) {
3570
3631
  }
3571
3632
  function buildAllowedEnv(options) {
3572
3633
  const allowed = {};
3573
- const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3634
+ const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3574
3635
  for (const varName of essentialVars) {
3575
3636
  if (process.env[varName]) {
3576
3637
  allowed[varName] = process.env[varName];
3577
3638
  }
3578
3639
  }
3640
+ const rawHome = process.env.HOME ?? "";
3641
+ const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
3642
+ if (rawHome !== safeHome) {
3643
+ const logger = getLogger();
3644
+ logger.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
3645
+ }
3646
+ allowed.HOME = safeHome;
3579
3647
  const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
3580
3648
  for (const varName of apiKeyVars) {
3581
3649
  if (process.env[varName]) {
@@ -3663,9 +3731,9 @@ async function executeOnce(binary, options, pidRegistry) {
3663
3731
  };
3664
3732
  }
3665
3733
  var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_PERIOD_MS = 5000, _runOnceDeps;
3666
- var init_claude_execution = __esm(() => {
3734
+ var init_execution = __esm(() => {
3667
3735
  init_logger2();
3668
- init_cost();
3736
+ init_cost2();
3669
3737
  _runOnceDeps = {
3670
3738
  killProc(proc, signal) {
3671
3739
  proc.kill(signal);
@@ -3676,7 +3744,7 @@ var init_claude_execution = __esm(() => {
3676
3744
  };
3677
3745
  });
3678
3746
 
3679
- // src/agents/claude-interactive.ts
3747
+ // src/agents/claude/interactive.ts
3680
3748
  function runInteractiveMode(binary, options, pidRegistry) {
3681
3749
  const model = options.modelDef.model;
3682
3750
  const cmd = [binary, "--model", model, options.prompt];
@@ -3715,9 +3783,9 @@ function runInteractiveMode(binary, options, pidRegistry) {
3715
3783
  pid: proc.pid
3716
3784
  };
3717
3785
  }
3718
- var init_claude_interactive = __esm(() => {
3786
+ var init_interactive = __esm(() => {
3719
3787
  init_logger2();
3720
- init_claude_execution();
3788
+ init_execution();
3721
3789
  });
3722
3790
 
3723
3791
  // src/config/schema-types.ts
@@ -18139,7 +18207,7 @@ var init_schema = __esm(() => {
18139
18207
  init_defaults();
18140
18208
  });
18141
18209
 
18142
- // src/agents/model-resolution.ts
18210
+ // src/agents/shared/model-resolution.ts
18143
18211
  var exports_model_resolution = {};
18144
18212
  __export(exports_model_resolution, {
18145
18213
  resolveBalancedModelDef: () => resolveBalancedModelDef
@@ -18160,7 +18228,7 @@ var init_model_resolution = __esm(() => {
18160
18228
  init_schema();
18161
18229
  });
18162
18230
 
18163
- // src/agents/claude-plan.ts
18231
+ // src/agents/claude/plan.ts
18164
18232
  import { mkdtempSync, rmSync } from "fs";
18165
18233
  import { tmpdir } from "os";
18166
18234
  import { join } from "path";
@@ -18279,12 +18347,12 @@ async function runPlan(binary, options, pidRegistry, buildAllowedEnv2) {
18279
18347
  }
18280
18348
  }
18281
18349
  }
18282
- var init_claude_plan = __esm(() => {
18350
+ var init_plan = __esm(() => {
18283
18351
  init_logger2();
18284
18352
  init_model_resolution();
18285
18353
  });
18286
18354
 
18287
- // src/agents/claude.ts
18355
+ // src/agents/claude/adapter.ts
18288
18356
  class ClaudeCodeAdapter {
18289
18357
  name = "claude";
18290
18358
  displayName = "Claude Code";
@@ -18435,14 +18503,14 @@ class ClaudeCodeAdapter {
18435
18503
  }
18436
18504
  }
18437
18505
  var _decomposeDeps, _claudeAdapterDeps;
18438
- var init_claude = __esm(() => {
18506
+ var init_adapter = __esm(() => {
18439
18507
  init_pid_registry();
18440
18508
  init_logger2();
18441
- init_claude_complete();
18442
- init_claude_decompose();
18443
- init_claude_execution();
18444
- init_claude_interactive();
18445
- init_claude_plan();
18509
+ init_decompose();
18510
+ init_complete();
18511
+ init_execution();
18512
+ init_interactive();
18513
+ init_plan();
18446
18514
  _decomposeDeps = {
18447
18515
  spawn(cmd, opts) {
18448
18516
  return Bun.spawn(cmd, opts);
@@ -18453,6 +18521,12 @@ var init_claude = __esm(() => {
18453
18521
  };
18454
18522
  });
18455
18523
 
18524
+ // src/agents/claude/index.ts
18525
+ var init_claude = __esm(() => {
18526
+ init_adapter();
18527
+ init_execution();
18528
+ });
18529
+
18456
18530
  // src/utils/errors.ts
18457
18531
  function errorMessage(err) {
18458
18532
  return err instanceof Error ? err.message : String(err);
@@ -18665,7 +18739,7 @@ Respond with ONLY the TypeScript test code (no markdown code fences, no explanat
18665
18739
  testable: c.testable,
18666
18740
  storyId: c.storyId
18667
18741
  })), null, 2);
18668
- await _generatorPRDDeps.writeFile(join2(options.workdir, "acceptance-refined.json"), refinedJsonContent);
18742
+ await _generatorPRDDeps.writeFile(join2(options.featureDir, "acceptance-refined.json"), refinedJsonContent);
18669
18743
  return { testCode, criteria };
18670
18744
  }
18671
18745
  function buildStrategyInstructions(strategy, framework) {
@@ -18977,11 +19051,40 @@ function parseAcpxJsonOutput(rawOutput) {
18977
19051
  `).filter((l) => l.trim());
18978
19052
  let text = "";
18979
19053
  let tokenUsage;
19054
+ let exactCostUsd;
18980
19055
  let stopReason;
18981
19056
  let error48;
18982
19057
  for (const line of lines) {
18983
19058
  try {
18984
19059
  const event = JSON.parse(line);
19060
+ if (event.jsonrpc === "2.0") {
19061
+ if (event.method === "session/update" && event.params?.update) {
19062
+ const update = event.params.update;
19063
+ if (update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && update.content.text) {
19064
+ text += update.content.text;
19065
+ }
19066
+ if (update.sessionUpdate === "usage_update" && typeof update.cost?.amount === "number") {
19067
+ exactCostUsd = update.cost.amount;
19068
+ }
19069
+ }
19070
+ if (event.id !== undefined && event.result && typeof event.result === "object") {
19071
+ const result = event.result;
19072
+ if (result.stopReason)
19073
+ stopReason = result.stopReason;
19074
+ if (result.stop_reason)
19075
+ stopReason = result.stop_reason;
19076
+ if (result.usage && typeof result.usage === "object") {
19077
+ const u = result.usage;
19078
+ tokenUsage = {
19079
+ input_tokens: u.inputTokens ?? u.input_tokens ?? 0,
19080
+ output_tokens: u.outputTokens ?? u.output_tokens ?? 0,
19081
+ cache_read_input_tokens: u.cachedReadTokens ?? u.cache_read_input_tokens ?? 0,
19082
+ cache_creation_input_tokens: u.cachedWriteTokens ?? u.cache_creation_input_tokens ?? 0
19083
+ };
19084
+ }
19085
+ }
19086
+ continue;
19087
+ }
18985
19088
  if (event.content && typeof event.content === "string")
18986
19089
  text += event.content;
18987
19090
  if (event.text && typeof event.text === "string")
@@ -19008,17 +19111,25 @@ function parseAcpxJsonOutput(rawOutput) {
19008
19111
  text = line;
19009
19112
  }
19010
19113
  }
19011
- return { text: text.trim(), tokenUsage, stopReason, error: error48 };
19114
+ return { text: text.trim(), tokenUsage, exactCostUsd, stopReason, error: error48 };
19012
19115
  }
19013
19116
 
19014
19117
  // src/agents/acp/spawn-client.ts
19118
+ import { homedir as homedir2 } from "os";
19119
+ import { isAbsolute as isAbsolute2 } from "path";
19015
19120
  function buildAllowedEnv2(extraEnv) {
19016
19121
  const allowed = {};
19017
- const essentialVars = ["PATH", "HOME", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
19122
+ const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
19018
19123
  for (const varName of essentialVars) {
19019
19124
  if (process.env[varName])
19020
19125
  allowed[varName] = process.env[varName];
19021
19126
  }
19127
+ const rawHome = process.env.HOME ?? "";
19128
+ const safeHome = rawHome && isAbsolute2(rawHome) ? rawHome : homedir2();
19129
+ if (rawHome !== safeHome) {
19130
+ getSafeLogger()?.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
19131
+ }
19132
+ allowed.HOME = safeHome;
19022
19133
  const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
19023
19134
  for (const varName of apiKeyVars) {
19024
19135
  if (process.env[varName])
@@ -19107,8 +19218,9 @@ class SpawnAcpSession {
19107
19218
  const parsed = parseAcpxJsonOutput(stdout);
19108
19219
  return {
19109
19220
  messages: [{ role: "assistant", content: parsed.text || "" }],
19110
- stopReason: "end_turn",
19111
- cumulative_token_usage: parsed.tokenUsage
19221
+ stopReason: parsed.stopReason ?? "end_turn",
19222
+ cumulative_token_usage: parsed.tokenUsage,
19223
+ exactCostUsd: parsed.exactCostUsd
19112
19224
  };
19113
19225
  } catch (err) {
19114
19226
  getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
@@ -19234,44 +19346,8 @@ var init_spawn_client = __esm(() => {
19234
19346
  });
19235
19347
 
19236
19348
  // src/agents/acp/cost.ts
19237
- function estimateCostFromTokenUsage(usage, model) {
19238
- const pricing = MODEL_PRICING[model];
19239
- if (!pricing) {
19240
- const fallbackInputRate = 3 / 1e6;
19241
- const fallbackOutputRate = 15 / 1e6;
19242
- const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
19243
- const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
19244
- const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
19245
- const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
19246
- return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
19247
- }
19248
- const inputRate = pricing.input / 1e6;
19249
- const outputRate = pricing.output / 1e6;
19250
- const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
19251
- const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
19252
- const inputCost = (usage.input_tokens ?? 0) * inputRate;
19253
- const outputCost = (usage.output_tokens ?? 0) * outputRate;
19254
- const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
19255
- const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
19256
- return inputCost + outputCost + cacheReadCost + cacheCreationCost;
19257
- }
19258
- var MODEL_PRICING;
19259
- var init_cost2 = __esm(() => {
19260
- MODEL_PRICING = {
19261
- "claude-sonnet-4": { input: 3, output: 15 },
19262
- "claude-sonnet-4-5": { input: 3, output: 15 },
19263
- "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
19264
- "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
19265
- "claude-opus": { input: 15, output: 75 },
19266
- "claude-opus-4": { input: 15, output: 75 },
19267
- "gpt-4.1": { input: 10, output: 30 },
19268
- "gpt-4": { input: 30, output: 60 },
19269
- "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
19270
- "gemini-2.5-pro": { input: 0.075, output: 0.3 },
19271
- "gemini-2-pro": { input: 0.075, output: 0.3 },
19272
- codex: { input: 0.02, output: 0.06 },
19273
- "code-davinci-002": { input: 0.02, output: 0.06 }
19274
- };
19349
+ var init_cost3 = __esm(() => {
19350
+ init_cost();
19275
19351
  });
19276
19352
 
19277
19353
  // src/agents/acp/adapter.ts
@@ -19569,7 +19645,13 @@ class AcpAgentAdapter {
19569
19645
  let lastResponse = null;
19570
19646
  let timedOut = false;
19571
19647
  const runState = { succeeded: false };
19572
- const totalTokenUsage = { input_tokens: 0, output_tokens: 0 };
19648
+ const totalTokenUsage = {
19649
+ input_tokens: 0,
19650
+ output_tokens: 0,
19651
+ cache_read_input_tokens: 0,
19652
+ cache_creation_input_tokens: 0
19653
+ };
19654
+ let totalExactCostUsd;
19573
19655
  try {
19574
19656
  let currentPrompt = options.prompt;
19575
19657
  let turnCount = 0;
@@ -19588,6 +19670,11 @@ class AcpAgentAdapter {
19588
19670
  if (lastResponse.cumulative_token_usage) {
19589
19671
  totalTokenUsage.input_tokens += lastResponse.cumulative_token_usage.input_tokens ?? 0;
19590
19672
  totalTokenUsage.output_tokens += lastResponse.cumulative_token_usage.output_tokens ?? 0;
19673
+ totalTokenUsage.cache_read_input_tokens += lastResponse.cumulative_token_usage.cache_read_input_tokens ?? 0;
19674
+ totalTokenUsage.cache_creation_input_tokens += lastResponse.cumulative_token_usage.cache_creation_input_tokens ?? 0;
19675
+ }
19676
+ if (lastResponse.exactCostUsd !== undefined) {
19677
+ totalExactCostUsd = (totalExactCostUsd ?? 0) + lastResponse.exactCostUsd;
19591
19678
  }
19592
19679
  const outputText = extractOutput(lastResponse);
19593
19680
  const question = extractQuestion(outputText);
@@ -19634,7 +19721,7 @@ class AcpAgentAdapter {
19634
19721
  }
19635
19722
  const success2 = lastResponse?.stopReason === "end_turn";
19636
19723
  const output = extractOutput(lastResponse);
19637
- const estimatedCost = totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0;
19724
+ const estimatedCost = totalExactCostUsd ?? (totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0);
19638
19725
  return {
19639
19726
  success: success2,
19640
19727
  exitCode: success2 ? 0 : 1,
@@ -19684,6 +19771,12 @@ class AcpAgentAdapter {
19684
19771
  if (!unwrapped) {
19685
19772
  throw new CompleteError("complete() returned empty output");
19686
19773
  }
19774
+ if (response.exactCostUsd !== undefined) {
19775
+ getSafeLogger()?.info("acp-adapter", "complete() cost", {
19776
+ costUsd: response.exactCostUsd,
19777
+ model
19778
+ });
19779
+ }
19687
19780
  return unwrapped;
19688
19781
  } catch (err) {
19689
19782
  const error48 = err instanceof Error ? err : new Error(String(err));
@@ -19770,12 +19863,12 @@ class AcpAgentAdapter {
19770
19863
  }
19771
19864
  }
19772
19865
  var MAX_AGENT_OUTPUT_CHARS2 = 5000, MAX_RATE_LIMIT_RETRIES = 3, INTERACTION_TIMEOUT_MS, AGENT_REGISTRY, DEFAULT_ENTRY, _acpAdapterDeps, MAX_SESSION_AGE_MS;
19773
- var init_adapter = __esm(() => {
19866
+ var init_adapter2 = __esm(() => {
19774
19867
  init_logger2();
19775
- init_claude_decompose();
19868
+ init_decompose();
19776
19869
  init_spawn_client();
19777
19870
  init_types2();
19778
- init_cost2();
19871
+ init_cost3();
19779
19872
  INTERACTION_TIMEOUT_MS = 5 * 60 * 1000;
19780
19873
  AGENT_REGISTRY = {
19781
19874
  claude: {
@@ -19817,7 +19910,7 @@ var init_adapter = __esm(() => {
19817
19910
  MAX_SESSION_AGE_MS = 2 * 60 * 60 * 1000;
19818
19911
  });
19819
19912
 
19820
- // src/agents/adapters/aider.ts
19913
+ // src/agents/aider/adapter.ts
19821
19914
  class AiderAdapter {
19822
19915
  name = "aider";
19823
19916
  displayName = "Aider";
@@ -19882,7 +19975,7 @@ class AiderAdapter {
19882
19975
  }
19883
19976
  }
19884
19977
  var _aiderCompleteDeps, MAX_AGENT_OUTPUT_CHARS3 = 5000;
19885
- var init_aider = __esm(() => {
19978
+ var init_adapter3 = __esm(() => {
19886
19979
  init_types2();
19887
19980
  _aiderCompleteDeps = {
19888
19981
  which(name) {
@@ -19894,7 +19987,7 @@ var init_aider = __esm(() => {
19894
19987
  };
19895
19988
  });
19896
19989
 
19897
- // src/agents/adapters/codex.ts
19990
+ // src/agents/codex/adapter.ts
19898
19991
  class CodexAdapter {
19899
19992
  name = "codex";
19900
19993
  displayName = "Codex";
@@ -19957,7 +20050,7 @@ class CodexAdapter {
19957
20050
  }
19958
20051
  }
19959
20052
  var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS4 = 5000;
19960
- var init_codex = __esm(() => {
20053
+ var init_adapter4 = __esm(() => {
19961
20054
  init_types2();
19962
20055
  _codexRunDeps = {
19963
20056
  which(name) {
@@ -19974,7 +20067,7 @@ var init_codex = __esm(() => {
19974
20067
  };
19975
20068
  });
19976
20069
 
19977
- // src/agents/adapters/gemini.ts
20070
+ // src/agents/gemini/adapter.ts
19978
20071
  class GeminiAdapter {
19979
20072
  name = "gemini";
19980
20073
  displayName = "Gemini CLI";
@@ -20057,7 +20150,7 @@ class GeminiAdapter {
20057
20150
  }
20058
20151
  }
20059
20152
  var _geminiRunDeps, _geminiCompleteDeps, MAX_AGENT_OUTPUT_CHARS5 = 5000;
20060
- var init_gemini = __esm(() => {
20153
+ var init_adapter5 = __esm(() => {
20061
20154
  init_types2();
20062
20155
  _geminiRunDeps = {
20063
20156
  which(name) {
@@ -20074,7 +20167,7 @@ var init_gemini = __esm(() => {
20074
20167
  };
20075
20168
  });
20076
20169
 
20077
- // src/agents/adapters/opencode.ts
20170
+ // src/agents/opencode/adapter.ts
20078
20171
  class OpenCodeAdapter {
20079
20172
  name = "opencode";
20080
20173
  displayName = "OpenCode";
@@ -20119,7 +20212,7 @@ class OpenCodeAdapter {
20119
20212
  }
20120
20213
  }
20121
20214
  var _opencodeCompleteDeps;
20122
- var init_opencode = __esm(() => {
20215
+ var init_adapter6 = __esm(() => {
20123
20216
  init_types2();
20124
20217
  _opencodeCompleteDeps = {
20125
20218
  which(name) {
@@ -20211,12 +20304,12 @@ function createAgentRegistry(config2) {
20211
20304
  var ALL_AGENTS;
20212
20305
  var init_registry = __esm(() => {
20213
20306
  init_logger2();
20307
+ init_adapter2();
20308
+ init_adapter3();
20214
20309
  init_adapter();
20215
- init_aider();
20216
- init_codex();
20217
- init_gemini();
20218
- init_opencode();
20219
- init_claude();
20310
+ init_adapter4();
20311
+ init_adapter5();
20312
+ init_adapter6();
20220
20313
  ALL_AGENTS = [
20221
20314
  new ClaudeCodeAdapter,
20222
20315
  new CodexAdapter,
@@ -20374,7 +20467,7 @@ var init_chain = __esm(() => {
20374
20467
 
20375
20468
  // src/utils/path-security.ts
20376
20469
  import { realpathSync } from "fs";
20377
- import { dirname, isAbsolute, join as join5, normalize, resolve } from "path";
20470
+ import { dirname, isAbsolute as isAbsolute3, join as join5, normalize, resolve } from "path";
20378
20471
  function safeRealpath(p) {
20379
20472
  try {
20380
20473
  return realpathSync(p);
@@ -20392,7 +20485,7 @@ function validateModulePath(modulePath, allowedRoots) {
20392
20485
  return { valid: false, error: "Module path is empty" };
20393
20486
  }
20394
20487
  const normalizedRoots = allowedRoots.map((r) => safeRealpath(resolve(r)));
20395
- if (isAbsolute(modulePath)) {
20488
+ if (isAbsolute3(modulePath)) {
20396
20489
  const absoluteTarget = safeRealpath(normalize(modulePath));
20397
20490
  const isWithin = normalizedRoots.some((root) => {
20398
20491
  return absoluteTarget.startsWith(`${root}/`) || absoluteTarget === root;
@@ -20683,7 +20776,7 @@ function isPlainObject2(value) {
20683
20776
 
20684
20777
  // src/config/path-security.ts
20685
20778
  import { existsSync as existsSync4, lstatSync, realpathSync as realpathSync2 } from "fs";
20686
- import { isAbsolute as isAbsolute2, normalize as normalize2, resolve as resolve3 } from "path";
20779
+ import { isAbsolute as isAbsolute4, normalize as normalize2, resolve as resolve3 } from "path";
20687
20780
  function validateDirectory(dirPath, baseDir) {
20688
20781
  const resolved = resolve3(dirPath);
20689
20782
  if (!existsSync4(resolved)) {
@@ -20715,7 +20808,7 @@ function validateDirectory(dirPath, baseDir) {
20715
20808
  function isWithinDirectory(targetPath, basePath) {
20716
20809
  const normalizedTarget = normalize2(targetPath);
20717
20810
  const normalizedBase = normalize2(basePath);
20718
- if (!isAbsolute2(normalizedTarget) || !isAbsolute2(normalizedBase)) {
20811
+ if (!isAbsolute4(normalizedTarget) || !isAbsolute4(normalizedBase)) {
20719
20812
  return false;
20720
20813
  }
20721
20814
  const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
@@ -20751,10 +20844,10 @@ var MAX_DIRECTORY_DEPTH = 10;
20751
20844
  var init_path_security2 = () => {};
20752
20845
 
20753
20846
  // src/config/paths.ts
20754
- import { homedir } from "os";
20847
+ import { homedir as homedir3 } from "os";
20755
20848
  import { join as join6, resolve as resolve4 } from "path";
20756
20849
  function globalConfigDir() {
20757
- return join6(homedir(), ".nax");
20850
+ return join6(homedir3(), ".nax");
20758
20851
  }
20759
20852
  var init_paths = () => {};
20760
20853
 
@@ -22085,7 +22178,7 @@ var package_default;
22085
22178
  var init_package = __esm(() => {
22086
22179
  package_default = {
22087
22180
  name: "@nathapp/nax",
22088
- version: "0.45.0",
22181
+ version: "0.46.1",
22089
22182
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22090
22183
  type: "module",
22091
22184
  bin: {
@@ -22158,8 +22251,8 @@ var init_version = __esm(() => {
22158
22251
  NAX_VERSION = package_default.version;
22159
22252
  NAX_COMMIT = (() => {
22160
22253
  try {
22161
- if (/^[0-9a-f]{6,10}$/.test("d6bdccb"))
22162
- return "d6bdccb";
22254
+ if (/^[0-9a-f]{6,10}$/.test("405c88a"))
22255
+ return "405c88a";
22163
22256
  } catch {}
22164
22257
  try {
22165
22258
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23759,6 +23852,20 @@ var init_runner = __esm(() => {
23759
23852
  init_logger2();
23760
23853
  });
23761
23854
 
23855
+ // src/utils/log-test-output.ts
23856
+ function logTestOutput(logger, stage, output, opts = {}) {
23857
+ if (!logger || !output)
23858
+ return;
23859
+ const tailLines = opts.tailLines ?? 20;
23860
+ const lines = output.split(`
23861
+ `).slice(-tailLines).join(`
23862
+ `);
23863
+ logger.debug(stage, "Test output (tail)", {
23864
+ ...opts.storyId !== undefined && { storyId: opts.storyId },
23865
+ output: lines
23866
+ });
23867
+ }
23868
+
23762
23869
  // src/pipeline/stages/acceptance.ts
23763
23870
  var exports_acceptance = {};
23764
23871
  __export(exports_acceptance, {
@@ -23842,10 +23949,8 @@ ${stderr}`;
23842
23949
  return { action: "continue" };
23843
23950
  }
23844
23951
  if (failedACs.length === 0 && exitCode !== 0) {
23845
- logger.error("acceptance", "Tests errored with no AC failures parsed", {
23846
- exitCode,
23847
- output
23848
- });
23952
+ logger.error("acceptance", "Tests errored with no AC failures parsed", { exitCode });
23953
+ logTestOutput(logger, "acceptance", output);
23849
23954
  ctx.acceptanceFailures = {
23850
23955
  failedACs: ["AC-ERROR"],
23851
23956
  testOutput: output
@@ -23863,10 +23968,8 @@ ${stderr}`;
23863
23968
  overrides: overriddenFailures.map((acId) => ({ acId, reason: overrides[acId] }))
23864
23969
  });
23865
23970
  }
23866
- logger.error("acceptance", "Acceptance tests failed", {
23867
- failedACs: actualFailures,
23868
- output
23869
- });
23971
+ logger.error("acceptance", "Acceptance tests failed", { failedACs: actualFailures });
23972
+ logTestOutput(logger, "acceptance", output);
23870
23973
  ctx.acceptanceFailures = {
23871
23974
  failedACs: actualFailures,
23872
23975
  testOutput: output
@@ -23955,6 +24058,7 @@ ${stderr}` };
23955
24058
  const result = await _acceptanceSetupDeps.generate(ctx.prd.userStories, refinedCriteria, {
23956
24059
  featureName: ctx.prd.feature,
23957
24060
  workdir: ctx.workdir,
24061
+ featureDir: ctx.featureDir,
23958
24062
  codebaseContext: "",
23959
24063
  modelTier: ctx.config.acceptance.model ?? "fast",
23960
24064
  modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
@@ -25615,7 +25719,7 @@ ${pluginMarkdown}` : pluginMarkdown;
25615
25719
  };
25616
25720
  });
25617
25721
 
25618
- // src/agents/validation.ts
25722
+ // src/agents/shared/validation.ts
25619
25723
  function validateAgentForTier(agent, tier) {
25620
25724
  return agent.capabilities.supportedTiers.includes(tier);
25621
25725
  }
@@ -25629,7 +25733,7 @@ function describeAgentCapabilities(agent) {
25629
25733
  return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
25630
25734
  }
25631
25735
 
25632
- // src/agents/version-detection.ts
25736
+ // src/agents/shared/version-detection.ts
25633
25737
  async function getAgentVersion(binaryName) {
25634
25738
  try {
25635
25739
  const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
@@ -25689,11 +25793,13 @@ __export(exports_agents, {
25689
25793
  getAgentVersion: () => getAgentVersion,
25690
25794
  getAgent: () => getAgent,
25691
25795
  formatCostWithConfidence: () => formatCostWithConfidence,
25796
+ estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
25692
25797
  estimateCostFromOutput: () => estimateCostFromOutput,
25693
25798
  estimateCostByDuration: () => estimateCostByDuration,
25694
25799
  estimateCost: () => estimateCost,
25695
25800
  describeAgentCapabilities: () => describeAgentCapabilities,
25696
25801
  checkAgentHealth: () => checkAgentHealth,
25802
+ MODEL_PRICING: () => MODEL_PRICING,
25697
25803
  CompleteError: () => CompleteError,
25698
25804
  ClaudeCodeAdapter: () => ClaudeCodeAdapter,
25699
25805
  COST_RATES: () => COST_RATES
@@ -27742,7 +27848,7 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
27742
27848
  };
27743
27849
  }
27744
27850
  var executionStage, _executionDeps;
27745
- var init_execution = __esm(() => {
27851
+ var init_execution2 = __esm(() => {
27746
27852
  init_agents();
27747
27853
  init_config();
27748
27854
  init_triggers();
@@ -29097,6 +29203,7 @@ var init_regression2 = __esm(() => {
29097
29203
  storyId: ctx.story.id,
29098
29204
  failCount: result.failCount
29099
29205
  });
29206
+ logTestOutput(logger, "regression", result.rawOutput, { storyId: ctx.story.id });
29100
29207
  pipelineEventBus.emit({
29101
29208
  type: "regression:detected",
29102
29209
  storyId: ctx.story.id,
@@ -29395,16 +29502,8 @@ var init_verify = __esm(() => {
29395
29502
  storyId: ctx.story.id
29396
29503
  });
29397
29504
  }
29398
- if (result.output && result.status !== "TIMEOUT") {
29399
- const outputLines = result.output.split(`
29400
- `).slice(-20);
29401
- if (outputLines.length > 0) {
29402
- logger.debug("verify", "Test output preview", {
29403
- storyId: ctx.story.id,
29404
- output: outputLines.join(`
29405
- `)
29406
- });
29407
- }
29505
+ if (result.status !== "TIMEOUT") {
29506
+ logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
29408
29507
  }
29409
29508
  return {
29410
29509
  action: "escalate",
@@ -29449,7 +29548,7 @@ var init_stages = __esm(() => {
29449
29548
  init_completion();
29450
29549
  init_constitution2();
29451
29550
  init_context2();
29452
- init_execution();
29551
+ init_execution2();
29453
29552
  init_optimizer2();
29454
29553
  init_prompt();
29455
29554
  init_queue_check();
@@ -29464,7 +29563,7 @@ var init_stages = __esm(() => {
29464
29563
  init_context2();
29465
29564
  init_prompt();
29466
29565
  init_optimizer2();
29467
- init_execution();
29566
+ init_execution2();
29468
29567
  init_verify();
29469
29568
  init_rectify();
29470
29569
  init_review();
@@ -30013,12 +30112,15 @@ async function checkWorkingTreeClean(workdir) {
30013
30112
  });
30014
30113
  const output = await new Response(proc.stdout).text();
30015
30114
  const exitCode = await proc.exited;
30016
- const passed = exitCode === 0 && output.trim() === "";
30115
+ const lines = output.trim() === "" ? [] : output.split(`
30116
+ `).filter(Boolean);
30117
+ const nonNaxDirtyFiles = lines.filter((line) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(line)));
30118
+ const passed = exitCode === 0 && nonNaxDirtyFiles.length === 0;
30017
30119
  return {
30018
30120
  name: "working-tree-clean",
30019
30121
  tier: "blocker",
30020
30122
  passed,
30021
- message: passed ? "Working tree is clean" : "Uncommitted changes detected"
30123
+ message: passed ? "Working tree is clean" : `Uncommitted changes detected: ${nonNaxDirtyFiles.map((l) => l.slice(3)).join(", ")}`
30022
30124
  };
30023
30125
  }
30024
30126
  async function checkGitUserConfigured(workdir) {
@@ -30043,7 +30145,23 @@ async function checkGitUserConfigured(workdir) {
30043
30145
  message: passed ? "Git user is configured" : !hasName && !hasEmail ? "Git user.name and user.email not configured" : !hasName ? "Git user.name not configured" : "Git user.email not configured"
30044
30146
  };
30045
30147
  }
30046
- var init_checks_git = () => {};
30148
+ var NAX_RUNTIME_PATTERNS;
30149
+ var init_checks_git = __esm(() => {
30150
+ NAX_RUNTIME_PATTERNS = [
30151
+ /^.{2} nax\.lock$/,
30152
+ /^.{2} nax\/metrics\.json$/,
30153
+ /^.{2} nax\/features\/[^/]+\/status\.json$/,
30154
+ /^.{2} nax\/features\/[^/]+\/runs\//,
30155
+ /^.{2} nax\/features\/[^/]+\/plan\//,
30156
+ /^.{2} nax\/features\/[^/]+\/acp-sessions\.json$/,
30157
+ /^.{2} nax\/features\/[^/]+\/interactions\//,
30158
+ /^.{2} nax\/features\/[^/]+\/progress\.txt$/,
30159
+ /^.{2} nax\/features\/[^/]+\/acceptance-refined\.json$/,
30160
+ /^.{2} \.nax-verifier-verdict\.json$/,
30161
+ /^.{2} \.nax-pids$/,
30162
+ /^.{2} \.nax-wt\//
30163
+ ];
30164
+ });
30047
30165
 
30048
30166
  // src/precheck/checks-config.ts
30049
30167
  import { existsSync as existsSync26, statSync as statSync3 } from "fs";
@@ -30304,6 +30422,7 @@ var init_checks_blockers = __esm(() => {
30304
30422
 
30305
30423
  // src/precheck/checks-warnings.ts
30306
30424
  import { existsSync as existsSync28 } from "fs";
30425
+ import { isAbsolute as isAbsolute6 } from "path";
30307
30426
  async function checkClaudeMdExists(workdir) {
30308
30427
  const claudeMdPath = `${workdir}/CLAUDE.md`;
30309
30428
  const passed = existsSync28(claudeMdPath);
@@ -30397,7 +30516,14 @@ async function checkGitignoreCoversNax(workdir) {
30397
30516
  }
30398
30517
  const file2 = Bun.file(gitignorePath);
30399
30518
  const content = await file2.text();
30400
- const patterns = ["nax.lock", "runs/", "test/tmp/"];
30519
+ const patterns = [
30520
+ "nax.lock",
30521
+ "nax/**/runs/",
30522
+ "nax/metrics.json",
30523
+ "nax/features/*/status.json",
30524
+ ".nax-pids",
30525
+ ".nax-wt/"
30526
+ ];
30401
30527
  const missing = patterns.filter((pattern) => !content.includes(pattern));
30402
30528
  const passed = missing.length === 0;
30403
30529
  return {
@@ -30426,6 +30552,16 @@ async function checkPromptOverrideFiles(config2, workdir) {
30426
30552
  }
30427
30553
  return checks3;
30428
30554
  }
30555
+ async function checkHomeEnvValid() {
30556
+ const home = process.env.HOME ?? "";
30557
+ const passed = home !== "" && isAbsolute6(home);
30558
+ return {
30559
+ name: "home-env-valid",
30560
+ tier: "warning",
30561
+ passed,
30562
+ message: passed ? `HOME env is valid: ${home}` : home === "" ? "HOME env is not set \u2014 agent may write files to unexpected locations" : `HOME env is not an absolute path ("${home}") \u2014 may cause literal "~" directories in repo`
30563
+ };
30564
+ }
30429
30565
  var init_checks_warnings = () => {};
30430
30566
 
30431
30567
  // src/precheck/checks-agents.ts
@@ -30606,6 +30742,7 @@ function getEnvironmentWarnings(config2, workdir) {
30606
30742
  () => checkDiskSpace(),
30607
30743
  () => checkOptionalCommands(config2, workdir),
30608
30744
  () => checkGitignoreCoversNax(workdir),
30745
+ () => checkHomeEnvValid(),
30609
30746
  () => checkPromptOverrideFiles(config2, workdir),
30610
30747
  () => checkMultiAgentHealth()
30611
30748
  ];
@@ -32846,12 +32983,12 @@ var init_parallel_executor = __esm(() => {
32846
32983
 
32847
32984
  // src/pipeline/subscribers/events-writer.ts
32848
32985
  import { appendFile as appendFile2, mkdir } from "fs/promises";
32849
- import { homedir as homedir5 } from "os";
32986
+ import { homedir as homedir7 } from "os";
32850
32987
  import { basename as basename3, join as join40 } from "path";
32851
32988
  function wireEventsWriter(bus, feature, runId, workdir) {
32852
32989
  const logger = getSafeLogger();
32853
32990
  const project = basename3(workdir);
32854
- const eventsDir = join40(homedir5(), ".nax", "events", project);
32991
+ const eventsDir = join40(homedir7(), ".nax", "events", project);
32855
32992
  const eventsFile = join40(eventsDir, "events.jsonl");
32856
32993
  let dirReady = false;
32857
32994
  const write = (line) => {
@@ -33011,12 +33148,12 @@ var init_interaction2 = __esm(() => {
33011
33148
 
33012
33149
  // src/pipeline/subscribers/registry.ts
33013
33150
  import { mkdir as mkdir2, writeFile } from "fs/promises";
33014
- import { homedir as homedir6 } from "os";
33151
+ import { homedir as homedir8 } from "os";
33015
33152
  import { basename as basename4, join as join41 } from "path";
33016
33153
  function wireRegistry(bus, feature, runId, workdir) {
33017
33154
  const logger = getSafeLogger();
33018
33155
  const project = basename4(workdir);
33019
- const runDir = join41(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
33156
+ const runDir = join41(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33020
33157
  const metaFile = join41(runDir, "meta.json");
33021
33158
  const unsub = bus.on("run:started", (_ev) => {
33022
33159
  (async () => {
@@ -34454,7 +34591,7 @@ async function setupRun(options) {
34454
34591
  } else {
34455
34592
  logger?.warn("precheck", "Precheck validations skipped (--skip-precheck)");
34456
34593
  }
34457
- const { sweepStaleFeatureSessions: sweepStaleFeatureSessions2 } = await Promise.resolve().then(() => (init_adapter(), exports_adapter));
34594
+ const { sweepStaleFeatureSessions: sweepStaleFeatureSessions2 } = await Promise.resolve().then(() => (init_adapter2(), exports_adapter));
34458
34595
  await sweepStaleFeatureSessions2(workdir, feature).catch(() => {});
34459
34596
  const lockAcquired = await acquireLock(workdir);
34460
34597
  if (!lockAcquired) {
@@ -65416,7 +65553,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
65416
65553
  // bin/nax.ts
65417
65554
  init_source();
65418
65555
  import { existsSync as existsSync32, mkdirSync as mkdirSync6 } from "fs";
65419
- import { homedir as homedir8 } from "os";
65556
+ import { homedir as homedir10 } from "os";
65420
65557
  import { join as join43 } from "path";
65421
65558
 
65422
65559
  // node_modules/commander/esm.mjs
@@ -68572,10 +68709,10 @@ import { join as join32 } from "path";
68572
68709
  // src/commands/logs-reader.ts
68573
68710
  import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
68574
68711
  import { readdir as readdir3 } from "fs/promises";
68575
- import { homedir as homedir3 } from "os";
68712
+ import { homedir as homedir5 } from "os";
68576
68713
  import { join as join31 } from "path";
68577
68714
  var _deps6 = {
68578
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir3(), ".nax", "runs")
68715
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir5(), ".nax", "runs")
68579
68716
  };
68580
68717
  async function resolveRunFileFromRegistry(runId) {
68581
68718
  const runsDir = _deps6.getRunsDir();
@@ -68901,11 +69038,11 @@ async function precheckCommand(options) {
68901
69038
  // src/commands/runs.ts
68902
69039
  init_source();
68903
69040
  import { readdir as readdir4 } from "fs/promises";
68904
- import { homedir as homedir4 } from "os";
69041
+ import { homedir as homedir6 } from "os";
68905
69042
  import { join as join35 } from "path";
68906
69043
  var DEFAULT_LIMIT = 20;
68907
69044
  var _deps8 = {
68908
- getRunsDir: () => join35(homedir4(), ".nax", "runs")
69045
+ getRunsDir: () => join35(homedir6(), ".nax", "runs")
68909
69046
  };
68910
69047
  function formatDuration3(ms) {
68911
69048
  if (ms <= 0)
@@ -69088,7 +69225,7 @@ async function unlockCommand(options) {
69088
69225
  init_config();
69089
69226
 
69090
69227
  // src/execution/runner.ts
69091
- init_adapter();
69228
+ init_adapter2();
69092
69229
  init_registry();
69093
69230
  init_hooks();
69094
69231
  init_logger2();
@@ -77039,9 +77176,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77039
77176
  }
77040
77177
  }
77041
77178
  try {
77042
- mkdirSync6(featureDir, { recursive: true });
77179
+ const planLogDir = join43(featureDir, "plan");
77180
+ mkdirSync6(planLogDir, { recursive: true });
77043
77181
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77044
- const planLogPath = join43(featureDir, `plan-${planLogId}.jsonl`);
77182
+ const planLogPath = join43(planLogDir, `${planLogId}.jsonl`);
77045
77183
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77046
77184
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77047
77185
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -77098,7 +77236,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77098
77236
  config2.autoMode.defaultAgent = options.agent;
77099
77237
  }
77100
77238
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
77101
- const globalNaxDir = join43(homedir8(), ".nax");
77239
+ const globalNaxDir = join43(homedir10(), ".nax");
77102
77240
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
77103
77241
  const eventEmitter = new PipelineEventEmitter;
77104
77242
  let tuiInstance;
@@ -77286,10 +77424,10 @@ Use: nax plan -f <feature> --from <spec>`));
77286
77424
  process.exit(1);
77287
77425
  }
77288
77426
  const config2 = await loadConfig(workdir);
77289
- const featureLogDir = join43(naxDir, "features", options.feature);
77427
+ const featureLogDir = join43(naxDir, "features", options.feature, "plan");
77290
77428
  mkdirSync6(featureLogDir, { recursive: true });
77291
77429
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77292
- const planLogPath = join43(featureLogDir, `plan-${planLogId}.jsonl`);
77430
+ const planLogPath = join43(featureLogDir, `${planLogId}.jsonl`);
77293
77431
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77294
77432
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77295
77433
  try {