@nathapp/nax 0.45.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/bin/nax.ts +7 -6
  3. package/dist/nax.js +181 -124
  4. package/package.json +1 -1
  5. package/src/agents/acp/adapter.ts +34 -6
  6. package/src/agents/acp/index.ts +0 -2
  7. package/src/agents/acp/parser.ts +57 -104
  8. package/src/agents/acp/spawn-client.ts +2 -1
  9. package/src/agents/{claude.ts → claude/adapter.ts} +15 -12
  10. package/src/agents/{claude-complete.ts → claude/complete.ts} +3 -3
  11. package/src/agents/{cost.ts → claude/cost.ts} +1 -1
  12. package/src/agents/{claude-execution.ts → claude/execution.ts} +5 -5
  13. package/src/agents/claude/index.ts +3 -0
  14. package/src/agents/{claude-interactive.ts → claude/interactive.ts} +4 -4
  15. package/src/agents/{claude-plan.ts → claude/plan.ts} +12 -9
  16. package/src/agents/index.ts +5 -5
  17. package/src/agents/registry.ts +5 -5
  18. package/src/agents/{claude-decompose.ts → shared/decompose.ts} +2 -2
  19. package/src/agents/{model-resolution.ts → shared/model-resolution.ts} +2 -2
  20. package/src/agents/{types-extended.ts → shared/types-extended.ts} +4 -4
  21. package/src/agents/{validation.ts → shared/validation.ts} +2 -2
  22. package/src/agents/{version-detection.ts → shared/version-detection.ts} +3 -3
  23. package/src/agents/types.ts +8 -4
  24. package/src/cli/agents.ts +1 -1
  25. package/src/pipeline/stages/acceptance.ts +5 -8
  26. package/src/pipeline/stages/regression.ts +2 -0
  27. package/src/pipeline/stages/verify.ts +5 -10
  28. package/src/precheck/checks-agents.ts +1 -1
  29. package/src/utils/log-test-output.ts +25 -0
  30. /package/src/agents/{adapters/aider.ts → aider/adapter.ts} +0 -0
  31. /package/src/agents/{adapters/codex.ts → codex/adapter.ts} +0 -0
  32. /package/src/agents/{adapters/gemini.ts → gemini/adapter.ts} +0 -0
  33. /package/src/agents/{adapters/opencode.ts → opencode/adapter.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.46.0] - 2026-03-16
9
+
10
+ ### Fixed
11
+ - **ACP cost metric:** Cost was always `$0` for ACP sessions. `parseAcpxJsonOutput` now handles JSON-RPC envelope format (acpx v0.3+): extracts text from `agent_message_chunk`, captures exact USD cost from `usage_update` (`cost.amount`), and reads camelCase token breakdown (`inputTokens`, `outputTokens`, `cachedReadTokens`, `cachedWriteTokens`) from `result.usage`.
12
+ - **ACP `complete()` cost:** Now logs exact cost via `getSafeLogger()` — previously had zero cost tracking.
13
+ - **`run()` cost:** Prefers exact `cost.amount` from acpx over token-based estimation; falls back to `estimateCostFromTokenUsage` when unavailable.
14
+
15
+ ### Refactored
16
+ - **`src/agents/` folder restructure:** Each adapter now lives in its own subfolder for consistency.
17
+ - `claude/` — Claude Code adapter (adapter, execution, complete, interactive, plan, cost)
18
+ - `acp/` — ACP protocol adapter (unchanged internals)
19
+ - `aider/`, `codex/`, `gemini/`, `opencode/` — per-adapter subfolders
20
+ - `shared/` — cross-adapter utilities: `decompose` (extracted from both claude + acp), `model-resolution`, `validation`, `version-detection`, `types-extended`
21
+ - **Dead code removal:** `streamJsonRpcEvents` (exported but never called), stale `estimateCostFromTokenUsage` re-export from `acp/index.ts`.
22
+
23
+ ### Docs
24
+ - **ARCHITECTURE.md §1:** Updated `src/agents/` tree.
25
+ - **ARCHITECTURE.md §16:** New section — agent adapter folder conventions, `shared/` rules, ACP cost alignment.
26
+
8
27
  ## [0.43.0] - 2026-03-16
9
28
 
10
29
  ### Added
package/bin/nax.ts CHANGED
@@ -366,10 +366,11 @@ program
366
366
  }
367
367
 
368
368
  try {
369
- // Initialize plan logger before calling planCommand — writes to features/<feature>/plan-<ts>.jsonl
370
- mkdirSync(featureDir, { recursive: true });
369
+ // Initialize plan logger before calling planCommand — writes to features/<feature>/plan/<ts>.jsonl
370
+ const planLogDir = join(featureDir, "plan");
371
+ mkdirSync(planLogDir, { recursive: true });
371
372
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
372
- const planLogPath = join(featureDir, `plan-${planLogId}.jsonl`);
373
+ const planLogPath = join(planLogDir, `${planLogId}.jsonl`);
373
374
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
374
375
  console.log(chalk.dim(` [Plan log: ${planLogPath}]`));
375
376
 
@@ -688,11 +689,11 @@ program
688
689
  // Load config
689
690
  const config = await loadConfig(workdir);
690
691
 
691
- // Initialize logger — writes to nax/features/<feature>/plan-<timestamp>.jsonl
692
- const featureLogDir = join(naxDir, "features", options.feature);
692
+ // Initialize logger — writes to nax/features/<feature>/plan/<timestamp>.jsonl
693
+ const featureLogDir = join(naxDir, "features", options.feature, "plan");
693
694
  mkdirSync(featureLogDir, { recursive: true });
694
695
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
695
- const planLogPath = join(featureLogDir, `plan-${planLogId}.jsonl`);
696
+ const planLogPath = join(featureLogDir, `${planLogId}.jsonl`);
696
697
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
697
698
  console.log(chalk.dim(` [Plan log: ${planLogPath}]`));
698
699
 
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,65 @@ 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/claude/cost.ts
3462
3462
  function parseTokenUsage(output) {
3463
3463
  try {
3464
3464
  const jsonMatch = output.match(/\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/);
@@ -3561,7 +3561,7 @@ var init_cost = __esm(() => {
3561
3561
  };
3562
3562
  });
3563
3563
 
3564
- // src/agents/claude-execution.ts
3564
+ // src/agents/claude/execution.ts
3565
3565
  function buildCommand(binary, options) {
3566
3566
  const model = options.modelDef.model;
3567
3567
  const { skipPermissions } = resolvePermissions(options.config, options.pipelineStage ?? "run");
@@ -3663,7 +3663,7 @@ async function executeOnce(binary, options, pidRegistry) {
3663
3663
  };
3664
3664
  }
3665
3665
  var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_PERIOD_MS = 5000, _runOnceDeps;
3666
- var init_claude_execution = __esm(() => {
3666
+ var init_execution = __esm(() => {
3667
3667
  init_logger2();
3668
3668
  init_cost();
3669
3669
  _runOnceDeps = {
@@ -3676,7 +3676,7 @@ var init_claude_execution = __esm(() => {
3676
3676
  };
3677
3677
  });
3678
3678
 
3679
- // src/agents/claude-interactive.ts
3679
+ // src/agents/claude/interactive.ts
3680
3680
  function runInteractiveMode(binary, options, pidRegistry) {
3681
3681
  const model = options.modelDef.model;
3682
3682
  const cmd = [binary, "--model", model, options.prompt];
@@ -3715,9 +3715,9 @@ function runInteractiveMode(binary, options, pidRegistry) {
3715
3715
  pid: proc.pid
3716
3716
  };
3717
3717
  }
3718
- var init_claude_interactive = __esm(() => {
3718
+ var init_interactive = __esm(() => {
3719
3719
  init_logger2();
3720
- init_claude_execution();
3720
+ init_execution();
3721
3721
  });
3722
3722
 
3723
3723
  // src/config/schema-types.ts
@@ -18139,7 +18139,7 @@ var init_schema = __esm(() => {
18139
18139
  init_defaults();
18140
18140
  });
18141
18141
 
18142
- // src/agents/model-resolution.ts
18142
+ // src/agents/shared/model-resolution.ts
18143
18143
  var exports_model_resolution = {};
18144
18144
  __export(exports_model_resolution, {
18145
18145
  resolveBalancedModelDef: () => resolveBalancedModelDef
@@ -18160,7 +18160,7 @@ var init_model_resolution = __esm(() => {
18160
18160
  init_schema();
18161
18161
  });
18162
18162
 
18163
- // src/agents/claude-plan.ts
18163
+ // src/agents/claude/plan.ts
18164
18164
  import { mkdtempSync, rmSync } from "fs";
18165
18165
  import { tmpdir } from "os";
18166
18166
  import { join } from "path";
@@ -18279,12 +18279,12 @@ async function runPlan(binary, options, pidRegistry, buildAllowedEnv2) {
18279
18279
  }
18280
18280
  }
18281
18281
  }
18282
- var init_claude_plan = __esm(() => {
18282
+ var init_plan = __esm(() => {
18283
18283
  init_logger2();
18284
18284
  init_model_resolution();
18285
18285
  });
18286
18286
 
18287
- // src/agents/claude.ts
18287
+ // src/agents/claude/adapter.ts
18288
18288
  class ClaudeCodeAdapter {
18289
18289
  name = "claude";
18290
18290
  displayName = "Claude Code";
@@ -18435,14 +18435,14 @@ class ClaudeCodeAdapter {
18435
18435
  }
18436
18436
  }
18437
18437
  var _decomposeDeps, _claudeAdapterDeps;
18438
- var init_claude = __esm(() => {
18438
+ var init_adapter = __esm(() => {
18439
18439
  init_pid_registry();
18440
18440
  init_logger2();
18441
- init_claude_complete();
18442
- init_claude_decompose();
18443
- init_claude_execution();
18444
- init_claude_interactive();
18445
- init_claude_plan();
18441
+ init_decompose();
18442
+ init_complete();
18443
+ init_execution();
18444
+ init_interactive();
18445
+ init_plan();
18446
18446
  _decomposeDeps = {
18447
18447
  spawn(cmd, opts) {
18448
18448
  return Bun.spawn(cmd, opts);
@@ -18453,6 +18453,12 @@ var init_claude = __esm(() => {
18453
18453
  };
18454
18454
  });
18455
18455
 
18456
+ // src/agents/claude/index.ts
18457
+ var init_claude = __esm(() => {
18458
+ init_adapter();
18459
+ init_execution();
18460
+ });
18461
+
18456
18462
  // src/utils/errors.ts
18457
18463
  function errorMessage(err) {
18458
18464
  return err instanceof Error ? err.message : String(err);
@@ -18977,11 +18983,40 @@ function parseAcpxJsonOutput(rawOutput) {
18977
18983
  `).filter((l) => l.trim());
18978
18984
  let text = "";
18979
18985
  let tokenUsage;
18986
+ let exactCostUsd;
18980
18987
  let stopReason;
18981
18988
  let error48;
18982
18989
  for (const line of lines) {
18983
18990
  try {
18984
18991
  const event = JSON.parse(line);
18992
+ if (event.jsonrpc === "2.0") {
18993
+ if (event.method === "session/update" && event.params?.update) {
18994
+ const update = event.params.update;
18995
+ if (update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && update.content.text) {
18996
+ text += update.content.text;
18997
+ }
18998
+ if (update.sessionUpdate === "usage_update" && typeof update.cost?.amount === "number") {
18999
+ exactCostUsd = update.cost.amount;
19000
+ }
19001
+ }
19002
+ if (event.id !== undefined && event.result && typeof event.result === "object") {
19003
+ const result = event.result;
19004
+ if (result.stopReason)
19005
+ stopReason = result.stopReason;
19006
+ if (result.stop_reason)
19007
+ stopReason = result.stop_reason;
19008
+ if (result.usage && typeof result.usage === "object") {
19009
+ const u = result.usage;
19010
+ tokenUsage = {
19011
+ input_tokens: u.inputTokens ?? u.input_tokens ?? 0,
19012
+ output_tokens: u.outputTokens ?? u.output_tokens ?? 0,
19013
+ cache_read_input_tokens: u.cachedReadTokens ?? u.cache_read_input_tokens ?? 0,
19014
+ cache_creation_input_tokens: u.cachedWriteTokens ?? u.cache_creation_input_tokens ?? 0
19015
+ };
19016
+ }
19017
+ }
19018
+ continue;
19019
+ }
18985
19020
  if (event.content && typeof event.content === "string")
18986
19021
  text += event.content;
18987
19022
  if (event.text && typeof event.text === "string")
@@ -19008,7 +19043,7 @@ function parseAcpxJsonOutput(rawOutput) {
19008
19043
  text = line;
19009
19044
  }
19010
19045
  }
19011
- return { text: text.trim(), tokenUsage, stopReason, error: error48 };
19046
+ return { text: text.trim(), tokenUsage, exactCostUsd, stopReason, error: error48 };
19012
19047
  }
19013
19048
 
19014
19049
  // src/agents/acp/spawn-client.ts
@@ -19107,8 +19142,9 @@ class SpawnAcpSession {
19107
19142
  const parsed = parseAcpxJsonOutput(stdout);
19108
19143
  return {
19109
19144
  messages: [{ role: "assistant", content: parsed.text || "" }],
19110
- stopReason: "end_turn",
19111
- cumulative_token_usage: parsed.tokenUsage
19145
+ stopReason: parsed.stopReason ?? "end_turn",
19146
+ cumulative_token_usage: parsed.tokenUsage,
19147
+ exactCostUsd: parsed.exactCostUsd
19112
19148
  };
19113
19149
  } catch (err) {
19114
19150
  getSafeLogger()?.warn("acp-adapter", "Failed to parse session prompt response", {
@@ -19569,7 +19605,13 @@ class AcpAgentAdapter {
19569
19605
  let lastResponse = null;
19570
19606
  let timedOut = false;
19571
19607
  const runState = { succeeded: false };
19572
- const totalTokenUsage = { input_tokens: 0, output_tokens: 0 };
19608
+ const totalTokenUsage = {
19609
+ input_tokens: 0,
19610
+ output_tokens: 0,
19611
+ cache_read_input_tokens: 0,
19612
+ cache_creation_input_tokens: 0
19613
+ };
19614
+ let totalExactCostUsd;
19573
19615
  try {
19574
19616
  let currentPrompt = options.prompt;
19575
19617
  let turnCount = 0;
@@ -19588,6 +19630,11 @@ class AcpAgentAdapter {
19588
19630
  if (lastResponse.cumulative_token_usage) {
19589
19631
  totalTokenUsage.input_tokens += lastResponse.cumulative_token_usage.input_tokens ?? 0;
19590
19632
  totalTokenUsage.output_tokens += lastResponse.cumulative_token_usage.output_tokens ?? 0;
19633
+ totalTokenUsage.cache_read_input_tokens += lastResponse.cumulative_token_usage.cache_read_input_tokens ?? 0;
19634
+ totalTokenUsage.cache_creation_input_tokens += lastResponse.cumulative_token_usage.cache_creation_input_tokens ?? 0;
19635
+ }
19636
+ if (lastResponse.exactCostUsd !== undefined) {
19637
+ totalExactCostUsd = (totalExactCostUsd ?? 0) + lastResponse.exactCostUsd;
19591
19638
  }
19592
19639
  const outputText = extractOutput(lastResponse);
19593
19640
  const question = extractQuestion(outputText);
@@ -19634,7 +19681,7 @@ class AcpAgentAdapter {
19634
19681
  }
19635
19682
  const success2 = lastResponse?.stopReason === "end_turn";
19636
19683
  const output = extractOutput(lastResponse);
19637
- const estimatedCost = totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0;
19684
+ const estimatedCost = totalExactCostUsd ?? (totalTokenUsage.input_tokens > 0 || totalTokenUsage.output_tokens > 0 ? estimateCostFromTokenUsage(totalTokenUsage, options.modelDef.model) : 0);
19638
19685
  return {
19639
19686
  success: success2,
19640
19687
  exitCode: success2 ? 0 : 1,
@@ -19684,6 +19731,12 @@ class AcpAgentAdapter {
19684
19731
  if (!unwrapped) {
19685
19732
  throw new CompleteError("complete() returned empty output");
19686
19733
  }
19734
+ if (response.exactCostUsd !== undefined) {
19735
+ getSafeLogger()?.info("acp-adapter", "complete() cost", {
19736
+ costUsd: response.exactCostUsd,
19737
+ model
19738
+ });
19739
+ }
19687
19740
  return unwrapped;
19688
19741
  } catch (err) {
19689
19742
  const error48 = err instanceof Error ? err : new Error(String(err));
@@ -19770,9 +19823,9 @@ class AcpAgentAdapter {
19770
19823
  }
19771
19824
  }
19772
19825
  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(() => {
19826
+ var init_adapter2 = __esm(() => {
19774
19827
  init_logger2();
19775
- init_claude_decompose();
19828
+ init_decompose();
19776
19829
  init_spawn_client();
19777
19830
  init_types2();
19778
19831
  init_cost2();
@@ -19817,7 +19870,7 @@ var init_adapter = __esm(() => {
19817
19870
  MAX_SESSION_AGE_MS = 2 * 60 * 60 * 1000;
19818
19871
  });
19819
19872
 
19820
- // src/agents/adapters/aider.ts
19873
+ // src/agents/aider/adapter.ts
19821
19874
  class AiderAdapter {
19822
19875
  name = "aider";
19823
19876
  displayName = "Aider";
@@ -19882,7 +19935,7 @@ class AiderAdapter {
19882
19935
  }
19883
19936
  }
19884
19937
  var _aiderCompleteDeps, MAX_AGENT_OUTPUT_CHARS3 = 5000;
19885
- var init_aider = __esm(() => {
19938
+ var init_adapter3 = __esm(() => {
19886
19939
  init_types2();
19887
19940
  _aiderCompleteDeps = {
19888
19941
  which(name) {
@@ -19894,7 +19947,7 @@ var init_aider = __esm(() => {
19894
19947
  };
19895
19948
  });
19896
19949
 
19897
- // src/agents/adapters/codex.ts
19950
+ // src/agents/codex/adapter.ts
19898
19951
  class CodexAdapter {
19899
19952
  name = "codex";
19900
19953
  displayName = "Codex";
@@ -19957,7 +20010,7 @@ class CodexAdapter {
19957
20010
  }
19958
20011
  }
19959
20012
  var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS4 = 5000;
19960
- var init_codex = __esm(() => {
20013
+ var init_adapter4 = __esm(() => {
19961
20014
  init_types2();
19962
20015
  _codexRunDeps = {
19963
20016
  which(name) {
@@ -19974,7 +20027,7 @@ var init_codex = __esm(() => {
19974
20027
  };
19975
20028
  });
19976
20029
 
19977
- // src/agents/adapters/gemini.ts
20030
+ // src/agents/gemini/adapter.ts
19978
20031
  class GeminiAdapter {
19979
20032
  name = "gemini";
19980
20033
  displayName = "Gemini CLI";
@@ -20057,7 +20110,7 @@ class GeminiAdapter {
20057
20110
  }
20058
20111
  }
20059
20112
  var _geminiRunDeps, _geminiCompleteDeps, MAX_AGENT_OUTPUT_CHARS5 = 5000;
20060
- var init_gemini = __esm(() => {
20113
+ var init_adapter5 = __esm(() => {
20061
20114
  init_types2();
20062
20115
  _geminiRunDeps = {
20063
20116
  which(name) {
@@ -20074,7 +20127,7 @@ var init_gemini = __esm(() => {
20074
20127
  };
20075
20128
  });
20076
20129
 
20077
- // src/agents/adapters/opencode.ts
20130
+ // src/agents/opencode/adapter.ts
20078
20131
  class OpenCodeAdapter {
20079
20132
  name = "opencode";
20080
20133
  displayName = "OpenCode";
@@ -20119,7 +20172,7 @@ class OpenCodeAdapter {
20119
20172
  }
20120
20173
  }
20121
20174
  var _opencodeCompleteDeps;
20122
- var init_opencode = __esm(() => {
20175
+ var init_adapter6 = __esm(() => {
20123
20176
  init_types2();
20124
20177
  _opencodeCompleteDeps = {
20125
20178
  which(name) {
@@ -20211,12 +20264,12 @@ function createAgentRegistry(config2) {
20211
20264
  var ALL_AGENTS;
20212
20265
  var init_registry = __esm(() => {
20213
20266
  init_logger2();
20267
+ init_adapter2();
20268
+ init_adapter3();
20214
20269
  init_adapter();
20215
- init_aider();
20216
- init_codex();
20217
- init_gemini();
20218
- init_opencode();
20219
- init_claude();
20270
+ init_adapter4();
20271
+ init_adapter5();
20272
+ init_adapter6();
20220
20273
  ALL_AGENTS = [
20221
20274
  new ClaudeCodeAdapter,
20222
20275
  new CodexAdapter,
@@ -22085,7 +22138,7 @@ var package_default;
22085
22138
  var init_package = __esm(() => {
22086
22139
  package_default = {
22087
22140
  name: "@nathapp/nax",
22088
- version: "0.45.0",
22141
+ version: "0.46.0",
22089
22142
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22090
22143
  type: "module",
22091
22144
  bin: {
@@ -22158,8 +22211,8 @@ var init_version = __esm(() => {
22158
22211
  NAX_VERSION = package_default.version;
22159
22212
  NAX_COMMIT = (() => {
22160
22213
  try {
22161
- if (/^[0-9a-f]{6,10}$/.test("d6bdccb"))
22162
- return "d6bdccb";
22214
+ if (/^[0-9a-f]{6,10}$/.test("6a485b9"))
22215
+ return "6a485b9";
22163
22216
  } catch {}
22164
22217
  try {
22165
22218
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23759,6 +23812,20 @@ var init_runner = __esm(() => {
23759
23812
  init_logger2();
23760
23813
  });
23761
23814
 
23815
+ // src/utils/log-test-output.ts
23816
+ function logTestOutput(logger, stage, output, opts = {}) {
23817
+ if (!logger || !output)
23818
+ return;
23819
+ const tailLines = opts.tailLines ?? 20;
23820
+ const lines = output.split(`
23821
+ `).slice(-tailLines).join(`
23822
+ `);
23823
+ logger.debug(stage, "Test output (tail)", {
23824
+ ...opts.storyId !== undefined && { storyId: opts.storyId },
23825
+ output: lines
23826
+ });
23827
+ }
23828
+
23762
23829
  // src/pipeline/stages/acceptance.ts
23763
23830
  var exports_acceptance = {};
23764
23831
  __export(exports_acceptance, {
@@ -23842,10 +23909,8 @@ ${stderr}`;
23842
23909
  return { action: "continue" };
23843
23910
  }
23844
23911
  if (failedACs.length === 0 && exitCode !== 0) {
23845
- logger.error("acceptance", "Tests errored with no AC failures parsed", {
23846
- exitCode,
23847
- output
23848
- });
23912
+ logger.error("acceptance", "Tests errored with no AC failures parsed", { exitCode });
23913
+ logTestOutput(logger, "acceptance", output);
23849
23914
  ctx.acceptanceFailures = {
23850
23915
  failedACs: ["AC-ERROR"],
23851
23916
  testOutput: output
@@ -23863,10 +23928,8 @@ ${stderr}`;
23863
23928
  overrides: overriddenFailures.map((acId) => ({ acId, reason: overrides[acId] }))
23864
23929
  });
23865
23930
  }
23866
- logger.error("acceptance", "Acceptance tests failed", {
23867
- failedACs: actualFailures,
23868
- output
23869
- });
23931
+ logger.error("acceptance", "Acceptance tests failed", { failedACs: actualFailures });
23932
+ logTestOutput(logger, "acceptance", output);
23870
23933
  ctx.acceptanceFailures = {
23871
23934
  failedACs: actualFailures,
23872
23935
  testOutput: output
@@ -25615,7 +25678,7 @@ ${pluginMarkdown}` : pluginMarkdown;
25615
25678
  };
25616
25679
  });
25617
25680
 
25618
- // src/agents/validation.ts
25681
+ // src/agents/shared/validation.ts
25619
25682
  function validateAgentForTier(agent, tier) {
25620
25683
  return agent.capabilities.supportedTiers.includes(tier);
25621
25684
  }
@@ -25629,7 +25692,7 @@ function describeAgentCapabilities(agent) {
25629
25692
  return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
25630
25693
  }
25631
25694
 
25632
- // src/agents/version-detection.ts
25695
+ // src/agents/shared/version-detection.ts
25633
25696
  async function getAgentVersion(binaryName) {
25634
25697
  try {
25635
25698
  const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
@@ -27742,7 +27805,7 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
27742
27805
  };
27743
27806
  }
27744
27807
  var executionStage, _executionDeps;
27745
- var init_execution = __esm(() => {
27808
+ var init_execution2 = __esm(() => {
27746
27809
  init_agents();
27747
27810
  init_config();
27748
27811
  init_triggers();
@@ -29097,6 +29160,7 @@ var init_regression2 = __esm(() => {
29097
29160
  storyId: ctx.story.id,
29098
29161
  failCount: result.failCount
29099
29162
  });
29163
+ logTestOutput(logger, "regression", result.rawOutput, { storyId: ctx.story.id });
29100
29164
  pipelineEventBus.emit({
29101
29165
  type: "regression:detected",
29102
29166
  storyId: ctx.story.id,
@@ -29395,16 +29459,8 @@ var init_verify = __esm(() => {
29395
29459
  storyId: ctx.story.id
29396
29460
  });
29397
29461
  }
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
- }
29462
+ if (result.status !== "TIMEOUT") {
29463
+ logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
29408
29464
  }
29409
29465
  return {
29410
29466
  action: "escalate",
@@ -29449,7 +29505,7 @@ var init_stages = __esm(() => {
29449
29505
  init_completion();
29450
29506
  init_constitution2();
29451
29507
  init_context2();
29452
- init_execution();
29508
+ init_execution2();
29453
29509
  init_optimizer2();
29454
29510
  init_prompt();
29455
29511
  init_queue_check();
@@ -29464,7 +29520,7 @@ var init_stages = __esm(() => {
29464
29520
  init_context2();
29465
29521
  init_prompt();
29466
29522
  init_optimizer2();
29467
- init_execution();
29523
+ init_execution2();
29468
29524
  init_verify();
29469
29525
  init_rectify();
29470
29526
  init_review();
@@ -34454,7 +34510,7 @@ async function setupRun(options) {
34454
34510
  } else {
34455
34511
  logger?.warn("precheck", "Precheck validations skipped (--skip-precheck)");
34456
34512
  }
34457
- const { sweepStaleFeatureSessions: sweepStaleFeatureSessions2 } = await Promise.resolve().then(() => (init_adapter(), exports_adapter));
34513
+ const { sweepStaleFeatureSessions: sweepStaleFeatureSessions2 } = await Promise.resolve().then(() => (init_adapter2(), exports_adapter));
34458
34514
  await sweepStaleFeatureSessions2(workdir, feature).catch(() => {});
34459
34515
  const lockAcquired = await acquireLock(workdir);
34460
34516
  if (!lockAcquired) {
@@ -69088,7 +69144,7 @@ async function unlockCommand(options) {
69088
69144
  init_config();
69089
69145
 
69090
69146
  // src/execution/runner.ts
69091
- init_adapter();
69147
+ init_adapter2();
69092
69148
  init_registry();
69093
69149
  init_hooks();
69094
69150
  init_logger2();
@@ -77039,9 +77095,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77039
77095
  }
77040
77096
  }
77041
77097
  try {
77042
- mkdirSync6(featureDir, { recursive: true });
77098
+ const planLogDir = join43(featureDir, "plan");
77099
+ mkdirSync6(planLogDir, { recursive: true });
77043
77100
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77044
- const planLogPath = join43(featureDir, `plan-${planLogId}.jsonl`);
77101
+ const planLogPath = join43(planLogDir, `${planLogId}.jsonl`);
77045
77102
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77046
77103
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77047
77104
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -77286,10 +77343,10 @@ Use: nax plan -f <feature> --from <spec>`));
77286
77343
  process.exit(1);
77287
77344
  }
77288
77345
  const config2 = await loadConfig(workdir);
77289
- const featureLogDir = join43(naxDir, "features", options.feature);
77346
+ const featureLogDir = join43(naxDir, "features", options.feature, "plan");
77290
77347
  mkdirSync6(featureLogDir, { recursive: true });
77291
77348
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77292
- const planLogPath = join43(featureLogDir, `plan-${planLogId}.jsonl`);
77349
+ const planLogPath = join43(featureLogDir, `${planLogId}.jsonl`);
77293
77350
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77294
77351
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77295
77352
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {