@nathapp/nax 0.56.2 → 0.56.4

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 +722 -486
  2. package/package.json +2 -2
package/dist/nax.js CHANGED
@@ -3321,6 +3321,157 @@ var init_bun_deps = __esm(() => {
3321
3321
  spawn = Bun.spawn;
3322
3322
  });
3323
3323
 
3324
+ // src/agents/cost/parse.ts
3325
+ function parseTokenUsage(output) {
3326
+ try {
3327
+ const jsonMatch = output.match(/\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/);
3328
+ if (jsonMatch) {
3329
+ return {
3330
+ inputTokens: Number.parseInt(jsonMatch[1], 10),
3331
+ outputTokens: Number.parseInt(jsonMatch[2], 10),
3332
+ confidence: "exact"
3333
+ };
3334
+ }
3335
+ const lines = output.split(`
3336
+ `);
3337
+ for (const line of lines) {
3338
+ if (line.trim().startsWith("{")) {
3339
+ try {
3340
+ const parsed = JSON.parse(line);
3341
+ if (parsed.usage?.input_tokens && parsed.usage?.output_tokens) {
3342
+ return {
3343
+ inputTokens: parsed.usage.input_tokens,
3344
+ outputTokens: parsed.usage.output_tokens,
3345
+ confidence: "exact"
3346
+ };
3347
+ }
3348
+ } catch {}
3349
+ }
3350
+ }
3351
+ } catch {}
3352
+ const inputMatch = output.match(/\b(?:input|input_tokens)\s*:\s*(\d{2,})|(?:input)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
3353
+ const outputMatch = output.match(/\b(?:output|output_tokens)\s*:\s*(\d{2,})|(?:output)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
3354
+ if (inputMatch && outputMatch) {
3355
+ const inputTokens = Number.parseInt(inputMatch[1] || inputMatch[2], 10);
3356
+ const outputTokens = Number.parseInt(outputMatch[1] || outputMatch[2], 10);
3357
+ if (inputTokens > 1e6 || outputTokens > 1e6) {
3358
+ return null;
3359
+ }
3360
+ return {
3361
+ inputTokens,
3362
+ outputTokens,
3363
+ confidence: "estimated"
3364
+ };
3365
+ }
3366
+ return null;
3367
+ }
3368
+
3369
+ // src/agents/cost/pricing.ts
3370
+ var COST_RATES, MODEL_PRICING;
3371
+ var init_pricing = __esm(() => {
3372
+ COST_RATES = {
3373
+ fast: {
3374
+ inputPer1M: 0.8,
3375
+ outputPer1M: 4
3376
+ },
3377
+ balanced: {
3378
+ inputPer1M: 3,
3379
+ outputPer1M: 15
3380
+ },
3381
+ powerful: {
3382
+ inputPer1M: 15,
3383
+ outputPer1M: 75
3384
+ }
3385
+ };
3386
+ MODEL_PRICING = {
3387
+ sonnet: { input: 3, output: 15 },
3388
+ haiku: { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3389
+ opus: { input: 15, output: 75 },
3390
+ "claude-sonnet-4": { input: 3, output: 15 },
3391
+ "claude-sonnet-4-5": { input: 3, output: 15 },
3392
+ "claude-sonnet-4-6": { input: 3, output: 15 },
3393
+ "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3394
+ "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3395
+ "claude-opus": { input: 15, output: 75 },
3396
+ "claude-opus-4": { input: 15, output: 75 },
3397
+ "claude-opus-4-6": { input: 15, output: 75 },
3398
+ "gpt-4.1": { input: 10, output: 30 },
3399
+ "gpt-4": { input: 30, output: 60 },
3400
+ "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3401
+ "gemini-2.5-pro": { input: 0.075, output: 0.3 },
3402
+ "gemini-2-pro": { input: 0.075, output: 0.3 },
3403
+ codex: { input: 0.02, output: 0.06 },
3404
+ "code-davinci-002": { input: 0.02, output: 0.06 }
3405
+ };
3406
+ });
3407
+
3408
+ // src/agents/cost/calculate.ts
3409
+ function estimateCost(modelTier, inputTokens, outputTokens, customRates) {
3410
+ const rates = customRates ?? COST_RATES[modelTier];
3411
+ const inputCost = inputTokens / 1e6 * rates.inputPer1M;
3412
+ const outputCost = outputTokens / 1e6 * rates.outputPer1M;
3413
+ return inputCost + outputCost;
3414
+ }
3415
+ function estimateCostFromOutput(modelTier, output) {
3416
+ const usage = parseTokenUsage(output);
3417
+ if (!usage) {
3418
+ return null;
3419
+ }
3420
+ const cost = estimateCost(modelTier, usage.inputTokens, usage.outputTokens);
3421
+ return {
3422
+ cost,
3423
+ confidence: usage.confidence
3424
+ };
3425
+ }
3426
+ function estimateCostByDuration(modelTier, durationMs) {
3427
+ const costPerMinute = {
3428
+ fast: 0.01,
3429
+ balanced: 0.05,
3430
+ powerful: 0.15
3431
+ };
3432
+ const minutes = durationMs / 60000;
3433
+ const cost = minutes * costPerMinute[modelTier];
3434
+ return {
3435
+ cost,
3436
+ confidence: "fallback"
3437
+ };
3438
+ }
3439
+ function formatCostWithConfidence(estimate) {
3440
+ const formattedCost = `$${estimate.cost.toFixed(2)}`;
3441
+ switch (estimate.confidence) {
3442
+ case "exact":
3443
+ return formattedCost;
3444
+ case "estimated":
3445
+ return `~${formattedCost}`;
3446
+ case "fallback":
3447
+ return `~${formattedCost} (duration-based)`;
3448
+ }
3449
+ }
3450
+ function estimateCostFromTokenUsage(usage, model) {
3451
+ const pricing = MODEL_PRICING[model];
3452
+ if (!pricing) {
3453
+ const fallbackInputRate = 3 / 1e6;
3454
+ const fallbackOutputRate = 15 / 1e6;
3455
+ const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
3456
+ const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
3457
+ const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
3458
+ const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
3459
+ return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
3460
+ }
3461
+ const inputRate = pricing.input / 1e6;
3462
+ const outputRate = pricing.output / 1e6;
3463
+ const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
3464
+ const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
3465
+ const inputCost = (usage.input_tokens ?? 0) * inputRate;
3466
+ const outputCost = (usage.output_tokens ?? 0) * outputRate;
3467
+ const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
3468
+ const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
3469
+ return inputCost + outputCost + cacheReadCost + cacheCreationCost;
3470
+ }
3471
+ var init_calculate = __esm(() => {
3472
+ init_pricing();
3473
+ });
3474
+
3324
3475
  // src/config/test-strategy.ts
3325
3476
  function resolveTestStrategy(raw) {
3326
3477
  if (!raw)
@@ -3663,158 +3814,18 @@ var init_env = __esm(() => {
3663
3814
  init_logger2();
3664
3815
  ESSENTIAL_VARS = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
3665
3816
  API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
3666
- ALLOWED_PREFIXES = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_", "ANTHROPIC_"];
3667
- });
3668
-
3669
- // src/agents/cost/pricing.ts
3670
- var COST_RATES, MODEL_PRICING;
3671
- var init_pricing = __esm(() => {
3672
- COST_RATES = {
3673
- fast: {
3674
- inputPer1M: 0.8,
3675
- outputPer1M: 4
3676
- },
3677
- balanced: {
3678
- inputPer1M: 3,
3679
- outputPer1M: 15
3680
- },
3681
- powerful: {
3682
- inputPer1M: 15,
3683
- outputPer1M: 75
3684
- }
3685
- };
3686
- MODEL_PRICING = {
3687
- sonnet: { input: 3, output: 15 },
3688
- haiku: { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3689
- opus: { input: 15, output: 75 },
3690
- "claude-sonnet-4": { input: 3, output: 15 },
3691
- "claude-sonnet-4-5": { input: 3, output: 15 },
3692
- "claude-sonnet-4-6": { input: 3, output: 15 },
3693
- "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3694
- "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3695
- "claude-opus": { input: 15, output: 75 },
3696
- "claude-opus-4": { input: 15, output: 75 },
3697
- "claude-opus-4-6": { input: 15, output: 75 },
3698
- "gpt-4.1": { input: 10, output: 30 },
3699
- "gpt-4": { input: 30, output: 60 },
3700
- "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3701
- "gemini-2.5-pro": { input: 0.075, output: 0.3 },
3702
- "gemini-2-pro": { input: 0.075, output: 0.3 },
3703
- codex: { input: 0.02, output: 0.06 },
3704
- "code-davinci-002": { input: 0.02, output: 0.06 }
3705
- };
3706
- });
3707
-
3708
- // src/agents/cost/parse.ts
3709
- function parseTokenUsage(output) {
3710
- try {
3711
- const jsonMatch = output.match(/\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/);
3712
- if (jsonMatch) {
3713
- return {
3714
- inputTokens: Number.parseInt(jsonMatch[1], 10),
3715
- outputTokens: Number.parseInt(jsonMatch[2], 10),
3716
- confidence: "exact"
3717
- };
3718
- }
3719
- const lines = output.split(`
3720
- `);
3721
- for (const line of lines) {
3722
- if (line.trim().startsWith("{")) {
3723
- try {
3724
- const parsed = JSON.parse(line);
3725
- if (parsed.usage?.input_tokens && parsed.usage?.output_tokens) {
3726
- return {
3727
- inputTokens: parsed.usage.input_tokens,
3728
- outputTokens: parsed.usage.output_tokens,
3729
- confidence: "exact"
3730
- };
3731
- }
3732
- } catch {}
3733
- }
3734
- }
3735
- } catch {}
3736
- const inputMatch = output.match(/\b(?:input|input_tokens)\s*:\s*(\d{2,})|(?:input)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
3737
- const outputMatch = output.match(/\b(?:output|output_tokens)\s*:\s*(\d{2,})|(?:output)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
3738
- if (inputMatch && outputMatch) {
3739
- const inputTokens = Number.parseInt(inputMatch[1] || inputMatch[2], 10);
3740
- const outputTokens = Number.parseInt(outputMatch[1] || outputMatch[2], 10);
3741
- if (inputTokens > 1e6 || outputTokens > 1e6) {
3742
- return null;
3743
- }
3744
- return {
3745
- inputTokens,
3746
- outputTokens,
3747
- confidence: "estimated"
3748
- };
3749
- }
3750
- return null;
3751
- }
3752
-
3753
- // src/agents/cost/calculate.ts
3754
- function estimateCost(modelTier, inputTokens, outputTokens, customRates) {
3755
- const rates = customRates ?? COST_RATES[modelTier];
3756
- const inputCost = inputTokens / 1e6 * rates.inputPer1M;
3757
- const outputCost = outputTokens / 1e6 * rates.outputPer1M;
3758
- return inputCost + outputCost;
3759
- }
3760
- function estimateCostFromOutput(modelTier, output) {
3761
- const usage = parseTokenUsage(output);
3762
- if (!usage) {
3763
- return null;
3764
- }
3765
- const cost = estimateCost(modelTier, usage.inputTokens, usage.outputTokens);
3766
- return {
3767
- cost,
3768
- confidence: usage.confidence
3769
- };
3770
- }
3771
- function estimateCostByDuration(modelTier, durationMs) {
3772
- const costPerMinute = {
3773
- fast: 0.01,
3774
- balanced: 0.05,
3775
- powerful: 0.15
3776
- };
3777
- const minutes = durationMs / 60000;
3778
- const cost = minutes * costPerMinute[modelTier];
3779
- return {
3780
- cost,
3781
- confidence: "fallback"
3782
- };
3783
- }
3784
- function formatCostWithConfidence(estimate) {
3785
- const formattedCost = `$${estimate.cost.toFixed(2)}`;
3786
- switch (estimate.confidence) {
3787
- case "exact":
3788
- return formattedCost;
3789
- case "estimated":
3790
- return `~${formattedCost}`;
3791
- case "fallback":
3792
- return `~${formattedCost} (duration-based)`;
3793
- }
3794
- }
3795
- function estimateCostFromTokenUsage(usage, model) {
3796
- const pricing = MODEL_PRICING[model];
3797
- if (!pricing) {
3798
- const fallbackInputRate = 3 / 1e6;
3799
- const fallbackOutputRate = 15 / 1e6;
3800
- const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
3801
- const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
3802
- const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
3803
- const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
3804
- return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
3805
- }
3806
- const inputRate = pricing.input / 1e6;
3807
- const outputRate = pricing.output / 1e6;
3808
- const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
3809
- const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
3810
- const inputCost = (usage.input_tokens ?? 0) * inputRate;
3811
- const outputCost = (usage.output_tokens ?? 0) * outputRate;
3812
- const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
3813
- const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
3814
- return inputCost + outputCost + cacheReadCost + cacheCreationCost;
3815
- }
3816
- var init_calculate = __esm(() => {
3817
- init_pricing();
3817
+ ALLOWED_PREFIXES = [
3818
+ "CLAUDE_",
3819
+ "NAX_",
3820
+ "CLAW_",
3821
+ "TURBO_",
3822
+ "ACPX_",
3823
+ "CODEX_",
3824
+ "GEMINI_",
3825
+ "ANTHROPIC_",
3826
+ "OPENCODE_",
3827
+ "MINIMAX_"
3828
+ ];
3818
3829
  });
3819
3830
 
3820
3831
  // src/agents/cost/index.ts
@@ -18807,7 +18818,16 @@ class ClaudeCodeAdapter {
18807
18818
  }
18808
18819
  }
18809
18820
  async complete(prompt, options) {
18810
- return executeComplete(this.binary, prompt, options);
18821
+ const startTime = Date.now();
18822
+ const output = await executeComplete(this.binary, prompt, options);
18823
+ const durationMs = Date.now() - startTime;
18824
+ const modelTier = options?.modelTier ?? "balanced";
18825
+ const estimate = estimateCostByDuration(modelTier, durationMs);
18826
+ return {
18827
+ output,
18828
+ costUsd: estimate.cost,
18829
+ source: estimate.confidence
18830
+ };
18811
18831
  }
18812
18832
  async plan(options) {
18813
18833
  const pidRegistry = this.getPidRegistry(options.workdir);
@@ -18885,6 +18905,7 @@ var init_adapter = __esm(() => {
18885
18905
  init_timeout_handler();
18886
18906
  init_logger2();
18887
18907
  init_bun_deps();
18908
+ init_calculate();
18888
18909
  init_decompose();
18889
18910
  init_complete();
18890
18911
  init_execution();
@@ -19400,7 +19421,7 @@ async function refineAcceptanceCriteria(criteria, context) {
19400
19421
  const prompt = buildRefinementPrompt(criteria, codebaseContext, { testStrategy, testFramework });
19401
19422
  let response;
19402
19423
  try {
19403
- response = await _refineDeps.adapter.complete(prompt, {
19424
+ const completeResult = await _refineDeps.adapter.complete(prompt, {
19404
19425
  jsonMode: true,
19405
19426
  maxTokens: 4096,
19406
19427
  model: modelDef.model,
@@ -19410,6 +19431,7 @@ async function refineAcceptanceCriteria(criteria, context) {
19410
19431
  workdir,
19411
19432
  sessionRole: "refine"
19412
19433
  });
19434
+ response = typeof completeResult === "string" ? completeResult : completeResult.output;
19413
19435
  } catch (error48) {
19414
19436
  const reason = errorMessage(error48);
19415
19437
  logger.warn("refinement", "adapter.complete() failed, falling back to original criteria", {
@@ -19551,7 +19573,7 @@ Rules:
19551
19573
  - **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join4(options.workdir, ".nax", "features", options.featureName, acceptanceTestFilename(options.language))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).`;
19552
19574
  const prompt = basePrompt;
19553
19575
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
19554
- const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
19576
+ const completeResult = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
19555
19577
  model: options.modelDef.model,
19556
19578
  config: options.config,
19557
19579
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
@@ -19559,6 +19581,7 @@ Rules:
19559
19581
  featureName: options.featureName,
19560
19582
  sessionRole: "acceptance-gen"
19561
19583
  });
19584
+ const rawOutput = typeof completeResult === "string" ? completeResult : completeResult.output;
19562
19585
  let testCode = extractTestCode(rawOutput);
19563
19586
  logger.debug("acceptance", "Received raw output from LLM", {
19564
19587
  hasCode: testCode !== null,
@@ -19695,7 +19718,7 @@ async function generateAcceptanceTests(adapter, options) {
19695
19718
  logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
19696
19719
  const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
19697
19720
  try {
19698
- const output = await adapter.complete(prompt, {
19721
+ const completeResult = await adapter.complete(prompt, {
19699
19722
  model: options.modelDef.model,
19700
19723
  config: options.config,
19701
19724
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
@@ -19703,6 +19726,7 @@ async function generateAcceptanceTests(adapter, options) {
19703
19726
  featureName: options.featureName,
19704
19727
  sessionRole: "acceptance-gen"
19705
19728
  });
19729
+ const output = typeof completeResult === "string" ? completeResult : completeResult.output;
19706
19730
  const testCode = extractTestCode(output);
19707
19731
  if (!testCode) {
19708
19732
  logger.warn("acceptance", "LLM returned non-code output for acceptance tests \u2014 falling back to skeleton", {
@@ -19950,12 +19974,13 @@ async function generateFixStories(adapter, options) {
19950
19974
  const relatedStory = prd.userStories.find((s) => relatedStories.includes(s.id) && s.workdir);
19951
19975
  const workdir = relatedStory?.workdir;
19952
19976
  try {
19953
- const fixDescription = await adapter.complete(prompt, {
19977
+ const fixResult = await adapter.complete(prompt, {
19954
19978
  model: modelDef.model,
19955
19979
  config: options.config,
19956
19980
  featureName: options.prd.feature,
19957
19981
  workdir: options.workdir,
19958
- sessionRole: "fix-gen"
19982
+ sessionRole: "fix-gen",
19983
+ timeoutMs: options.timeoutMs ?? options.config?.acceptance?.timeoutMs ?? 1800000
19959
19984
  });
19960
19985
  fixStories.push({
19961
19986
  id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
@@ -19964,7 +19989,7 @@ async function generateFixStories(adapter, options) {
19964
19989
  batchedACs,
19965
19990
  testOutput,
19966
19991
  relatedStories,
19967
- description: fixDescription,
19992
+ description: typeof fixResult === "string" ? fixResult : fixResult.output,
19968
19993
  testFilePath,
19969
19994
  workdir
19970
19995
  });
@@ -20883,8 +20908,24 @@ class AcpAgentAdapter {
20883
20908
  }
20884
20909
  if (response.exactCostUsd !== undefined) {
20885
20910
  getSafeLogger()?.info("acp-adapter", "complete() cost", { costUsd: response.exactCostUsd, model });
20911
+ return {
20912
+ output: unwrapped,
20913
+ costUsd: response.exactCostUsd,
20914
+ source: "exact"
20915
+ };
20916
+ }
20917
+ if (response.cumulative_token_usage) {
20918
+ return {
20919
+ output: unwrapped,
20920
+ costUsd: estimateCostFromTokenUsage(response.cumulative_token_usage, model),
20921
+ source: "estimated"
20922
+ };
20886
20923
  }
20887
- return unwrapped;
20924
+ return {
20925
+ output: unwrapped,
20926
+ costUsd: 0,
20927
+ source: "fallback"
20928
+ };
20888
20929
  } catch (err) {
20889
20930
  hadError = true;
20890
20931
  throw err;
@@ -21011,13 +21052,14 @@ class AcpAgentAdapter {
21011
21052
  const prompt = buildDecomposePrompt(options);
21012
21053
  let output;
21013
21054
  try {
21014
- output = await this.complete(prompt, {
21055
+ const completeResult = await this.complete(prompt, {
21015
21056
  model,
21016
21057
  jsonMode: true,
21017
21058
  config: options.config,
21018
21059
  workdir: options.workdir,
21019
21060
  sessionRole: "decompose"
21020
21061
  });
21062
+ output = completeResult.output;
21021
21063
  } catch (err) {
21022
21064
  const msg = err instanceof Error ? err.message : String(err);
21023
21065
  throw new Error(`[acp-adapter] decompose() failed: ${msg}`, { cause: err });
@@ -21164,7 +21206,11 @@ class AiderAdapter {
21164
21206
  if (!trimmed) {
21165
21207
  throw new CompleteError("complete() returned empty output");
21166
21208
  }
21167
- return trimmed;
21209
+ return {
21210
+ output: trimmed,
21211
+ costUsd: 0,
21212
+ source: "fallback"
21213
+ };
21168
21214
  }
21169
21215
  async plan(_options) {
21170
21216
  throw new Error("AiderAdapter.plan() not implemented");
@@ -21236,7 +21282,11 @@ class CodexAdapter {
21236
21282
  if (!trimmed) {
21237
21283
  throw new CompleteError("complete() returned empty output");
21238
21284
  }
21239
- return trimmed;
21285
+ return {
21286
+ output: trimmed,
21287
+ costUsd: 0,
21288
+ source: "fallback"
21289
+ };
21240
21290
  }
21241
21291
  async plan(_options) {
21242
21292
  throw new Error("CodexAdapter.plan() not implemented");
@@ -21331,7 +21381,11 @@ class GeminiAdapter {
21331
21381
  if (!trimmed) {
21332
21382
  throw new CompleteError("complete() returned empty output");
21333
21383
  }
21334
- return trimmed;
21384
+ return {
21385
+ output: trimmed,
21386
+ costUsd: 0,
21387
+ source: "fallback"
21388
+ };
21335
21389
  }
21336
21390
  async plan(_options) {
21337
21391
  throw new Error("GeminiAdapter.plan() not implemented");
@@ -21388,7 +21442,11 @@ class OpenCodeAdapter {
21388
21442
  if (!trimmed) {
21389
21443
  throw new CompleteError("complete() returned empty output");
21390
21444
  }
21391
- return trimmed;
21445
+ return {
21446
+ output: trimmed,
21447
+ costUsd: 0,
21448
+ source: "fallback"
21449
+ };
21392
21450
  }
21393
21451
  async plan(_options) {
21394
21452
  throw new Error("OpenCodeAdapter.plan() not implemented");
@@ -21694,7 +21752,7 @@ async function callLlmOnce(adapter, modelTier, prompt, config2, timeoutMs) {
21694
21752
  try {
21695
21753
  const result = await Promise.race([outputPromise, timeoutPromise]);
21696
21754
  clearTimeout(timeoutId);
21697
- return result;
21755
+ return typeof result === "string" ? result : result.output;
21698
21756
  } catch (err) {
21699
21757
  clearTimeout(timeoutId);
21700
21758
  outputPromise.catch(() => {});
@@ -22056,7 +22114,7 @@ var package_default;
22056
22114
  var init_package = __esm(() => {
22057
22115
  package_default = {
22058
22116
  name: "@nathapp/nax",
22059
- version: "0.56.2",
22117
+ version: "0.56.4",
22060
22118
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22061
22119
  type: "module",
22062
22120
  bin: {
@@ -22068,7 +22126,7 @@ var init_package = __esm(() => {
22068
22126
  build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
22069
22127
  typecheck: "bun x tsc --noEmit",
22070
22128
  lint: "bun x biome check src/ bin/",
22071
- "lint:fix": "bun x biome lint --write src/ bin/",
22129
+ "lint:fix": "bun x biome check --write src/ bin/",
22072
22130
  release: "bun scripts/release.ts",
22073
22131
  test: "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
22074
22132
  "test:bail": "bun test test/unit/ --timeout=60000 --bail && bun test test/integration/ --timeout=60000 --bail && bun test test/ui/ --timeout=60000 --bail",
@@ -22135,8 +22193,8 @@ var init_version = __esm(() => {
22135
22193
  NAX_VERSION = package_default.version;
22136
22194
  NAX_COMMIT = (() => {
22137
22195
  try {
22138
- if (/^[0-9a-f]{6,10}$/.test("b590070f"))
22139
- return "b590070f";
22196
+ if (/^[0-9a-f]{6,10}$/.test("17df843d"))
22197
+ return "17df843d";
22140
22198
  } catch {}
22141
22199
  try {
22142
22200
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -22389,6 +22447,7 @@ var DEFAULT_FALLBACK_AGENT = "claude";
22389
22447
  var init_resolvers = () => {};
22390
22448
 
22391
22449
  // src/debate/session.ts
22450
+ import { join as join11 } from "path";
22392
22451
  function resolveDebaterModel(debater, config2) {
22393
22452
  const tier = debater.model ?? "fast";
22394
22453
  if (!config2?.models)
@@ -22418,6 +22477,25 @@ function extractSessionOutput(response) {
22418
22477
  const last = [...messages].reverse().find((m) => m.role === "assistant");
22419
22478
  return last?.content ?? "";
22420
22479
  }
22480
+ function modelTierFromDebater(debater) {
22481
+ if (debater.model === "fast" || debater.model === "balanced" || debater.model === "powerful") {
22482
+ return debater.model;
22483
+ }
22484
+ return "fast";
22485
+ }
22486
+ async function runComplete(adapter, prompt, options, modelTier) {
22487
+ return adapter.complete(prompt, {
22488
+ ...options,
22489
+ modelTier
22490
+ });
22491
+ }
22492
+ function sessionResponseCostUsd(response, modelTier, durationMs) {
22493
+ if (response.exactCostUsd !== undefined) {
22494
+ return response.exactCostUsd;
22495
+ }
22496
+ const estimate = estimateCostByDuration(modelTier, durationMs);
22497
+ return estimate.cost;
22498
+ }
22421
22499
 
22422
22500
  class DebateSession {
22423
22501
  storyId;
@@ -22441,10 +22519,10 @@ class DebateSession {
22441
22519
  const logger = _debateSessionDeps.getSafeLogger();
22442
22520
  const config2 = this.stageConfig;
22443
22521
  const debaters = config2.debaters ?? [];
22444
- const totalCostUsd = 0;
22522
+ let totalCostUsd = 0;
22445
22523
  const resolved = [];
22446
22524
  for (const debater of debaters) {
22447
- const adapter = _debateSessionDeps.getAgent(debater.agent);
22525
+ const adapter = _debateSessionDeps.getAgent(debater.agent, this.config);
22448
22526
  if (!adapter) {
22449
22527
  logger?.warn("debate", `Agent '${debater.agent}' not found \u2014 skipping debater`);
22450
22528
  continue;
@@ -22483,7 +22561,9 @@ class DebateSession {
22483
22561
  reason: "only 1 session created"
22484
22562
  });
22485
22563
  const solo = sessions[0];
22564
+ const soloStart = Date.now();
22486
22565
  const response = await solo.session.prompt(prompt);
22566
+ totalCostUsd += sessionResponseCostUsd(response, modelTierFromDebater(solo.debater), Date.now() - soloStart);
22487
22567
  const output = extractSessionOutput(response);
22488
22568
  logger?.info("debate", "debate:result", {
22489
22569
  storyId: this.storyId,
@@ -22508,16 +22588,27 @@ class DebateSession {
22508
22588
  });
22509
22589
  return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22510
22590
  }
22511
- const proposalSettled = await Promise.allSettled(sessions.map(({ session }) => session.prompt(prompt)));
22591
+ const proposalSettled = await Promise.allSettled(sessions.map(async (entry) => {
22592
+ const startTime = Date.now();
22593
+ const response = await entry.session.prompt(prompt);
22594
+ return {
22595
+ entry,
22596
+ response,
22597
+ output: extractSessionOutput(response),
22598
+ cost: sessionResponseCostUsd(response, modelTierFromDebater(entry.debater), Date.now() - startTime)
22599
+ };
22600
+ }));
22512
22601
  const successfulSessions = [];
22513
22602
  for (let i = 0;i < proposalSettled.length; i++) {
22514
22603
  const r = proposalSettled[i];
22515
22604
  if (r.status === "fulfilled") {
22516
22605
  successfulSessions.push({
22517
- entry: sessions[i],
22518
- output: extractSessionOutput(r.value),
22606
+ entry: r.value.entry,
22607
+ output: r.value.output,
22608
+ cost: r.value.cost,
22519
22609
  originalIndex: i
22520
22610
  });
22611
+ totalCostUsd += r.value.cost;
22521
22612
  }
22522
22613
  }
22523
22614
  if (successfulSessions.length < 2) {
@@ -22540,17 +22631,28 @@ class DebateSession {
22540
22631
  let critiqueOutputs = [];
22541
22632
  if (config2.rounds > 1) {
22542
22633
  const proposalOutputs2 = successfulSessions.map((s) => s.output);
22543
- const critiqueSettled = await Promise.allSettled(successfulSessions.map(({ entry }, successfulIdx) => entry.session.prompt(buildCritiquePrompt(prompt, proposalOutputs2, successfulIdx))));
22544
- critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => extractSessionOutput(r.value));
22634
+ const critiqueSettled = await Promise.allSettled(successfulSessions.map(async ({ entry }, successfulIdx) => {
22635
+ const startTime = Date.now();
22636
+ const response = await entry.session.prompt(buildCritiquePrompt(prompt, proposalOutputs2, successfulIdx));
22637
+ return {
22638
+ output: extractSessionOutput(response),
22639
+ cost: sessionResponseCostUsd(response, modelTierFromDebater(entry.debater), Date.now() - startTime)
22640
+ };
22641
+ }));
22642
+ critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => {
22643
+ totalCostUsd += r.value.cost;
22644
+ return r.value.output;
22645
+ });
22545
22646
  }
22546
22647
  const proposalOutputs = successfulSessions.map((s) => s.output);
22547
22648
  const successfulProposals = successfulSessions.map((s) => ({
22548
22649
  debater: s.entry.debater,
22549
22650
  adapter: s.entry.adapter,
22550
22651
  output: s.output,
22551
- cost: 0
22652
+ cost: s.cost
22552
22653
  }));
22553
22654
  const outcome = await this.resolve(proposalOutputs, critiqueOutputs, successfulProposals);
22655
+ totalCostUsd += outcome.resolverCostUsd;
22554
22656
  const proposals = successfulSessions.map((s) => ({
22555
22657
  debater: s.entry.debater,
22556
22658
  output: s.output
@@ -22558,12 +22660,12 @@ class DebateSession {
22558
22660
  logger?.info("debate", "debate:result", {
22559
22661
  storyId: this.storyId,
22560
22662
  stage: this.stage,
22561
- outcome
22663
+ outcome: outcome.outcome
22562
22664
  });
22563
22665
  return {
22564
22666
  storyId: this.storyId,
22565
22667
  stage: this.stage,
22566
- outcome,
22668
+ outcome: outcome.outcome,
22567
22669
  rounds: config2.rounds,
22568
22670
  debaters: successfulSessions.map((s) => s.entry.debater.agent),
22569
22671
  resolverType: config2.resolver.type,
@@ -22581,7 +22683,7 @@ class DebateSession {
22581
22683
  let totalCostUsd = 0;
22582
22684
  const resolved = [];
22583
22685
  for (const debater of debaters) {
22584
- const adapter = _debateSessionDeps.getAgent(debater.agent);
22686
+ const adapter = _debateSessionDeps.getAgent(debater.agent, this.config);
22585
22687
  if (!adapter) {
22586
22688
  logger?.warn("debate", `Agent '${debater.agent}' not found \u2014 skipping debater`);
22587
22689
  continue;
@@ -22593,7 +22695,7 @@ class DebateSession {
22593
22695
  stage: this.stage,
22594
22696
  debaters: resolved.map((r) => r.debater.agent)
22595
22697
  });
22596
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: resolveDebaterModel(debater, this.config) }).then((output) => ({ debater, adapter, output, cost: 0 }))));
22698
+ const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => runComplete(adapter, prompt, { model: resolveDebaterModel(debater, this.config) }, modelTierFromDebater(debater)).then((result) => ({ debater, adapter, output: result.output, cost: result.costUsd }))));
22597
22699
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22598
22700
  for (const r of proposalSettled) {
22599
22701
  if (r.status === "fulfilled") {
@@ -22641,9 +22743,8 @@ class DebateSession {
22641
22743
  reason: "all debaters failed \u2014 retrying with first adapter"
22642
22744
  });
22643
22745
  try {
22644
- const fallbackOutput = await fallbackAdapter.complete(prompt, {
22645
- model: resolveDebaterModel(fallbackDebater, this.config)
22646
- });
22746
+ const fallbackResult = await runComplete(fallbackAdapter, prompt, { model: resolveDebaterModel(fallbackDebater, this.config) }, modelTierFromDebater(fallbackDebater));
22747
+ totalCostUsd += fallbackResult.costUsd;
22647
22748
  logger?.info("debate", "debate:result", {
22648
22749
  storyId: this.storyId,
22649
22750
  stage: this.stage,
@@ -22656,7 +22757,7 @@ class DebateSession {
22656
22757
  rounds: 1,
22657
22758
  debaters: [fallbackDebater.agent],
22658
22759
  resolverType: config2.resolver.type,
22659
- proposals: [{ debater: fallbackDebater, output: fallbackOutput }],
22760
+ proposals: [{ debater: fallbackDebater, output: fallbackResult.output }],
22660
22761
  totalCostUsd
22661
22762
  };
22662
22763
  } catch {}
@@ -22666,19 +22767,17 @@ class DebateSession {
22666
22767
  let critiqueOutputs = [];
22667
22768
  if (config2.rounds > 1) {
22668
22769
  const proposalOutputs2 = successful.map((p) => p.output);
22669
- const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => adapter.complete(buildCritiquePrompt(prompt, proposalOutputs2, i), {
22670
- model: resolveDebaterModel(debater, this.config)
22671
- })));
22770
+ const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => runComplete(adapter, buildCritiquePrompt(prompt, proposalOutputs2, i), { model: resolveDebaterModel(debater, this.config) }, modelTierFromDebater(debater))));
22672
22771
  for (const r of critiqueSettled) {
22673
22772
  if (r.status === "fulfilled") {
22674
- totalCostUsd += 0;
22773
+ totalCostUsd += r.value.costUsd;
22675
22774
  }
22676
22775
  }
22677
- critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22776
+ critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value.output);
22678
22777
  }
22679
22778
  const proposalOutputs = successful.map((p) => p.output);
22680
22779
  const outcome = await this.resolve(proposalOutputs, critiqueOutputs, successful);
22681
- totalCostUsd += 0;
22780
+ totalCostUsd += outcome.resolverCostUsd;
22682
22781
  const proposals = successful.map((p) => ({
22683
22782
  debater: p.debater,
22684
22783
  output: p.output
@@ -22686,12 +22785,12 @@ class DebateSession {
22686
22785
  logger?.info("debate", "debate:result", {
22687
22786
  storyId: this.storyId,
22688
22787
  stage: this.stage,
22689
- outcome
22788
+ outcome: outcome.outcome
22690
22789
  });
22691
22790
  return {
22692
22791
  storyId: this.storyId,
22693
22792
  stage: this.stage,
22694
- outcome,
22793
+ outcome: outcome.outcome,
22695
22794
  rounds: config2.rounds,
22696
22795
  debaters: successful.map((p) => p.debater.agent),
22697
22796
  resolverType: config2.resolver.type,
@@ -22699,40 +22798,151 @@ class DebateSession {
22699
22798
  totalCostUsd
22700
22799
  };
22701
22800
  }
22801
+ async runPlan(basePrompt, opts) {
22802
+ const logger = _debateSessionDeps.getSafeLogger();
22803
+ const config2 = this.stageConfig;
22804
+ const debaters = config2.debaters ?? [];
22805
+ const totalCostUsd = 0;
22806
+ const resolved = [];
22807
+ for (const debater of debaters) {
22808
+ const adapter = _debateSessionDeps.getAgent(debater.agent, this.config);
22809
+ if (!adapter) {
22810
+ logger?.warn("debate", `Agent '${debater.agent}' not found \u2014 skipping debater`);
22811
+ continue;
22812
+ }
22813
+ resolved.push({ debater, adapter });
22814
+ }
22815
+ logger?.info("debate", "debate:start", {
22816
+ storyId: this.storyId,
22817
+ stage: this.stage,
22818
+ debaters: resolved.map((r) => r.debater.agent)
22819
+ });
22820
+ const planSettled = await Promise.allSettled(resolved.map(async ({ debater, adapter }, i) => {
22821
+ const tempOutputPath = join11(opts.outputDir, `prd-debate-${i}.json`);
22822
+ const debaterPrompt = `${basePrompt}
22823
+
22824
+ Write the PRD JSON directly to this file path: ${tempOutputPath}
22825
+ Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.`;
22826
+ await adapter.plan({
22827
+ prompt: debaterPrompt,
22828
+ workdir: opts.workdir,
22829
+ interactive: false,
22830
+ timeoutSeconds: opts.timeoutSeconds,
22831
+ config: this.config,
22832
+ modelTier: debater.model ?? "balanced",
22833
+ dangerouslySkipPermissions: opts.dangerouslySkipPermissions,
22834
+ maxInteractionTurns: opts.maxInteractionTurns,
22835
+ featureName: opts.feature,
22836
+ sessionRole: "plan"
22837
+ });
22838
+ const output = await _debateSessionDeps.readFile(tempOutputPath);
22839
+ return { debater, adapter, output, cost: 0 };
22840
+ }));
22841
+ const successful = planSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22842
+ for (let i = 0;i < successful.length; i++) {
22843
+ logger?.info("debate", "debate:proposal", {
22844
+ storyId: this.storyId,
22845
+ stage: this.stage,
22846
+ debaterIndex: i,
22847
+ agent: successful[i].debater.agent
22848
+ });
22849
+ }
22850
+ if (successful.length === 0) {
22851
+ logger?.warn("debate", "debate:fallback", {
22852
+ storyId: this.storyId,
22853
+ stage: this.stage,
22854
+ reason: "all plan debaters failed"
22855
+ });
22856
+ return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22857
+ }
22858
+ if (successful.length === 1) {
22859
+ logger?.warn("debate", "debate:fallback", {
22860
+ storyId: this.storyId,
22861
+ stage: this.stage,
22862
+ reason: "only 1 plan debater succeeded \u2014 using as solo"
22863
+ });
22864
+ logger?.info("debate", "debate:result", { storyId: this.storyId, stage: this.stage, outcome: "passed" });
22865
+ return {
22866
+ storyId: this.storyId,
22867
+ stage: this.stage,
22868
+ outcome: "passed",
22869
+ rounds: 1,
22870
+ debaters: [successful[0].debater.agent],
22871
+ resolverType: config2.resolver.type,
22872
+ proposals: [{ debater: successful[0].debater, output: successful[0].output }],
22873
+ output: successful[0].output,
22874
+ totalCostUsd
22875
+ };
22876
+ }
22877
+ const proposalOutputs = successful.map((p) => p.output);
22878
+ const outcome = await this.resolve(proposalOutputs, [], successful);
22879
+ const winningOutput = successful[0].output;
22880
+ const proposals = successful.map((p) => ({ debater: p.debater, output: p.output }));
22881
+ logger?.info("debate", "debate:result", { storyId: this.storyId, stage: this.stage, outcome });
22882
+ return {
22883
+ storyId: this.storyId,
22884
+ stage: this.stage,
22885
+ outcome: outcome.outcome,
22886
+ rounds: 1,
22887
+ debaters: successful.map((p) => p.debater.agent),
22888
+ resolverType: config2.resolver.type,
22889
+ proposals,
22890
+ output: winningOutput,
22891
+ totalCostUsd
22892
+ };
22893
+ }
22702
22894
  async resolve(proposalOutputs, critiqueOutputs, _successful) {
22703
22895
  const resolverConfig = this.stageConfig.resolver;
22704
22896
  if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
22705
- return majorityResolver(proposalOutputs, resolverConfig.type === "majority-fail-open");
22897
+ return {
22898
+ outcome: majorityResolver(proposalOutputs, resolverConfig.type === "majority-fail-open"),
22899
+ resolverCostUsd: 0
22900
+ };
22706
22901
  }
22707
22902
  if (resolverConfig.type === "synthesis") {
22708
22903
  const agentName = resolverConfig.agent ?? RESOLVER_FALLBACK_AGENT;
22709
- const adapter = _debateSessionDeps.getAgent(agentName);
22904
+ const adapter = _debateSessionDeps.getAgent(agentName, this.config);
22710
22905
  if (adapter) {
22711
- await synthesisResolver(proposalOutputs, critiqueOutputs, { adapter });
22906
+ const resolverResult = await synthesisResolver(proposalOutputs, critiqueOutputs, { adapter });
22907
+ return {
22908
+ outcome: "passed",
22909
+ resolverCostUsd: resolverResult.costUsd
22910
+ };
22712
22911
  }
22713
- return "passed";
22912
+ return {
22913
+ outcome: "passed",
22914
+ resolverCostUsd: 0
22915
+ };
22714
22916
  }
22715
22917
  if (resolverConfig.type === "custom") {
22716
- await judgeResolver(proposalOutputs, critiqueOutputs, resolverConfig, {
22717
- getAgent: _debateSessionDeps.getAgent,
22918
+ const resolverResult = await judgeResolver(proposalOutputs, critiqueOutputs, resolverConfig, {
22919
+ getAgent: (name) => _debateSessionDeps.getAgent(name, this.config),
22718
22920
  defaultAgentName: RESOLVER_FALLBACK_AGENT
22719
22921
  });
22720
- return "passed";
22922
+ return {
22923
+ outcome: "passed",
22924
+ resolverCostUsd: resolverResult.costUsd
22925
+ };
22721
22926
  }
22722
- return "passed";
22927
+ return {
22928
+ outcome: "passed",
22929
+ resolverCostUsd: 0
22930
+ };
22723
22931
  }
22724
22932
  }
22725
22933
  var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
22726
22934
  var init_session = __esm(() => {
22727
22935
  init_spawn_client();
22936
+ init_calculate();
22728
22937
  init_registry();
22729
22938
  init_config();
22730
22939
  init_logger2();
22731
22940
  init_resolvers();
22732
22941
  _debateSessionDeps = {
22733
- getAgent,
22942
+ getAgent: (name, config2) => config2 ? createAgentRegistry(config2).getAgent(name) : getAgent(name),
22734
22943
  getSafeLogger,
22735
- createSpawnAcpClient: (cmdStr, cwd) => createSpawnAcpClient(cmdStr, cwd)
22944
+ createSpawnAcpClient: (cmdStr, cwd) => createSpawnAcpClient(cmdStr, cwd),
22945
+ readFile: (path) => Bun.file(path).text()
22736
22946
  };
22737
22947
  });
22738
22948
 
@@ -22927,7 +23137,7 @@ class AutoInteractionPlugin {
22927
23137
  const modelDef = resolveModelForAgent(naxConfig.models, naxConfig.autoMode.defaultAgent, modelTier, naxConfig.autoMode.defaultAgent);
22928
23138
  modelArg = modelDef.model;
22929
23139
  }
22930
- const output = await adapter.complete(prompt, {
23140
+ const result = await adapter.complete(prompt, {
22931
23141
  ...modelArg && { model: modelArg },
22932
23142
  jsonMode: true,
22933
23143
  ...this.config.naxConfig && { config: this.config.naxConfig },
@@ -22935,6 +23145,7 @@ class AutoInteractionPlugin {
22935
23145
  storyId: request.storyId,
22936
23146
  sessionRole: "auto"
22937
23147
  });
23148
+ const output = typeof result === "string" ? result : result.output;
22938
23149
  return this.parseResponse(output);
22939
23150
  }
22940
23151
  buildPrompt(request) {
@@ -26441,13 +26652,14 @@ ${formatFindings(deduped)}`,
26441
26652
  }
26442
26653
  let rawResponse;
26443
26654
  try {
26444
- rawResponse = await agent.complete(prompt, {
26655
+ const completeResult = await agent.complete(prompt, {
26445
26656
  sessionName: `nax-semantic-${story.id}`,
26446
26657
  workdir,
26447
26658
  timeoutMs: semanticConfig.timeoutMs,
26448
26659
  modelTier: semanticConfig.modelTier,
26449
26660
  config: naxConfig
26450
26661
  });
26662
+ rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
26451
26663
  } catch (err) {
26452
26664
  logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
26453
26665
  return {
@@ -26841,7 +27053,7 @@ __export(exports_review, {
26841
27053
  reviewStage: () => reviewStage,
26842
27054
  _reviewDeps: () => _reviewDeps
26843
27055
  });
26844
- import { join as join16 } from "path";
27056
+ import { join as join17 } from "path";
26845
27057
  var reviewStage, _reviewDeps;
26846
27058
  var init_review = __esm(() => {
26847
27059
  init_agents();
@@ -26855,7 +27067,7 @@ var init_review = __esm(() => {
26855
27067
  const logger = getLogger();
26856
27068
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
26857
27069
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
26858
- const effectiveWorkdir = ctx.story.workdir ? join16(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27070
+ const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
26859
27071
  const agentResolver = ctx.agentGetFn ?? getAgent;
26860
27072
  const agentName = effectiveConfig.autoMode?.defaultAgent;
26861
27073
  const modelResolver = (_tier) => agentName ? agentResolver(agentName) ?? null : null;
@@ -26907,7 +27119,7 @@ var init_review = __esm(() => {
26907
27119
  });
26908
27120
 
26909
27121
  // src/pipeline/stages/autofix.ts
26910
- import { join as join17 } from "path";
27122
+ import { join as join18 } from "path";
26911
27123
  async function recheckReview(ctx) {
26912
27124
  const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
26913
27125
  if (!reviewStage2.enabled(ctx))
@@ -26980,7 +27192,7 @@ async function runAgentRectification(ctx) {
26980
27192
  const prompt = buildReviewRectificationPrompt(failedChecks, ctx.story);
26981
27193
  const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
26982
27194
  const modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, modelTier, ctx.config.autoMode.defaultAgent);
26983
- const rectificationWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27195
+ const rectificationWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
26984
27196
  await agent.run({
26985
27197
  prompt,
26986
27198
  workdir: rectificationWorkdir,
@@ -27044,7 +27256,7 @@ var init_autofix = __esm(() => {
27044
27256
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27045
27257
  const lintFixCmd = effectiveConfig.quality.commands.lintFix;
27046
27258
  const formatFixCmd = effectiveConfig.quality.commands.formatFix;
27047
- const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27259
+ const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
27048
27260
  const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
27049
27261
  const hasLintFailure = failedCheckNames.has("lint");
27050
27262
  logger.info("autofix", "Starting autofix", {
@@ -27127,17 +27339,15 @@ var init_autofix = __esm(() => {
27127
27339
  });
27128
27340
 
27129
27341
  // src/execution/progress.ts
27130
- import { mkdirSync as mkdirSync2 } from "fs";
27131
- import { join as join18 } from "path";
27342
+ import { appendFile as appendFile2, mkdir } from "fs/promises";
27343
+ import { join as join19 } from "path";
27132
27344
  async function appendProgress(featureDir, storyId, status, message) {
27133
- mkdirSync2(featureDir, { recursive: true });
27134
- const progressPath = join18(featureDir, "progress.txt");
27345
+ await mkdir(featureDir, { recursive: true });
27346
+ const progressPath = join19(featureDir, "progress.txt");
27135
27347
  const timestamp = new Date().toISOString();
27136
27348
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
27137
27349
  `;
27138
- const file3 = Bun.file(progressPath);
27139
- const existing = await file3.exists() ? await file3.text() : "";
27140
- await Bun.write(progressPath, existing + entry);
27350
+ await appendFile2(progressPath, entry);
27141
27351
  }
27142
27352
  var init_progress = () => {};
27143
27353
 
@@ -27216,7 +27426,7 @@ function estimateTokens(text) {
27216
27426
 
27217
27427
  // src/constitution/loader.ts
27218
27428
  import { existsSync as existsSync19 } from "fs";
27219
- import { join as join19 } from "path";
27429
+ import { join as join20 } from "path";
27220
27430
  function truncateToTokens(text, maxTokens) {
27221
27431
  const maxChars = maxTokens * 3;
27222
27432
  if (text.length <= maxChars) {
@@ -27238,7 +27448,7 @@ async function loadConstitution(projectDir, config2) {
27238
27448
  }
27239
27449
  let combinedContent = "";
27240
27450
  if (!config2.skipGlobal) {
27241
- const globalPath = join19(globalConfigDir(), config2.path);
27451
+ const globalPath = join20(globalConfigDir(), config2.path);
27242
27452
  if (existsSync19(globalPath)) {
27243
27453
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
27244
27454
  const globalFile = Bun.file(validatedPath);
@@ -27248,7 +27458,7 @@ async function loadConstitution(projectDir, config2) {
27248
27458
  }
27249
27459
  }
27250
27460
  }
27251
- const projectPath = join19(projectDir, config2.path);
27461
+ const projectPath = join20(projectDir, config2.path);
27252
27462
  if (existsSync19(projectPath)) {
27253
27463
  const validatedPath = validateFilePath(projectPath, projectDir);
27254
27464
  const projectFile = Bun.file(validatedPath);
@@ -28276,7 +28486,7 @@ var init_helpers = __esm(() => {
28276
28486
  });
28277
28487
 
28278
28488
  // src/pipeline/stages/context.ts
28279
- import { join as join20 } from "path";
28489
+ import { join as join21 } from "path";
28280
28490
  var contextStage;
28281
28491
  var init_context2 = __esm(() => {
28282
28492
  init_helpers();
@@ -28286,7 +28496,7 @@ var init_context2 = __esm(() => {
28286
28496
  enabled: () => true,
28287
28497
  async execute(ctx) {
28288
28498
  const logger = getLogger();
28289
- const packageWorkdir = ctx.story.workdir ? join20(ctx.workdir, ctx.story.workdir) : undefined;
28499
+ const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
28290
28500
  const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
28291
28501
  if (result) {
28292
28502
  ctx.contextMarkdown = result.markdown;
@@ -28420,14 +28630,14 @@ var init_isolation = __esm(() => {
28420
28630
 
28421
28631
  // src/context/greenfield.ts
28422
28632
  import { readdir } from "fs/promises";
28423
- import { join as join21 } from "path";
28633
+ import { join as join22 } from "path";
28424
28634
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
28425
28635
  const results = [];
28426
28636
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
28427
28637
  try {
28428
28638
  const entries = await readdir(dir, { withFileTypes: true });
28429
28639
  for (const entry of entries) {
28430
- const fullPath = join21(dir, entry.name);
28640
+ const fullPath = join22(dir, entry.name);
28431
28641
  if (entry.isDirectory()) {
28432
28642
  if (ignoreDirs.has(entry.name))
28433
28643
  continue;
@@ -28782,13 +28992,13 @@ function parseTestOutput(output, exitCode) {
28782
28992
 
28783
28993
  // src/verification/runners.ts
28784
28994
  import { existsSync as existsSync20 } from "fs";
28785
- import { join as join22 } from "path";
28995
+ import { join as join23 } from "path";
28786
28996
  async function verifyAssets(workingDirectory, expectedFiles) {
28787
28997
  if (!expectedFiles || expectedFiles.length === 0)
28788
28998
  return { success: true, missingFiles: [] };
28789
28999
  const missingFiles = [];
28790
29000
  for (const file3 of expectedFiles) {
28791
- if (!existsSync20(join22(workingDirectory, file3)))
29001
+ if (!existsSync20(join23(workingDirectory, file3)))
28792
29002
  missingFiles.push(file3);
28793
29003
  }
28794
29004
  if (missingFiles.length > 0) {
@@ -29682,13 +29892,13 @@ var exports_loader = {};
29682
29892
  __export(exports_loader, {
29683
29893
  loadOverride: () => loadOverride
29684
29894
  });
29685
- import { join as join23 } from "path";
29895
+ import { join as join24 } from "path";
29686
29896
  async function loadOverride(role, workdir, config2) {
29687
29897
  const overridePath = config2.prompts?.overrides?.[role];
29688
29898
  if (!overridePath) {
29689
29899
  return null;
29690
29900
  }
29691
- const absolutePath = join23(workdir, overridePath);
29901
+ const absolutePath = join24(workdir, overridePath);
29692
29902
  const file3 = Bun.file(absolutePath);
29693
29903
  if (!await file3.exists()) {
29694
29904
  return null;
@@ -30547,11 +30757,11 @@ var init_tdd = __esm(() => {
30547
30757
 
30548
30758
  // src/pipeline/stages/execution.ts
30549
30759
  import { existsSync as existsSync21 } from "fs";
30550
- import { join as join24 } from "path";
30760
+ import { join as join25 } from "path";
30551
30761
  function resolveStoryWorkdir(repoRoot, storyWorkdir) {
30552
30762
  if (!storyWorkdir)
30553
30763
  return repoRoot;
30554
- const resolved = join24(repoRoot, storyWorkdir);
30764
+ const resolved = join25(repoRoot, storyWorkdir);
30555
30765
  if (!existsSync21(resolved)) {
30556
30766
  throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
30557
30767
  }
@@ -31222,7 +31432,7 @@ async function _defaultRunDebate(storyId, stageConfig, prompt) {
31222
31432
  return { output: null, totalCostUsd: 0 };
31223
31433
  }
31224
31434
  const startMs = Date.now();
31225
- const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: debater.model }).then((out) => out)));
31435
+ const proposalSettled = await Promise.allSettled(resolved.map(({ debater, adapter }) => adapter.complete(prompt, { model: debater.model }).then((out) => typeof out === "string" ? out : out.output)));
31226
31436
  const durationMs = Date.now() - startMs;
31227
31437
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
31228
31438
  if (successful.length === 0) {
@@ -32114,7 +32324,7 @@ var init_regression2 = __esm(() => {
32114
32324
  });
32115
32325
 
32116
32326
  // src/pipeline/stages/routing.ts
32117
- import { join as join25 } from "path";
32327
+ import { join as join26 } from "path";
32118
32328
  var routingStage, _routingDeps;
32119
32329
  var init_routing2 = __esm(() => {
32120
32330
  init_registry();
@@ -32151,7 +32361,7 @@ var init_routing2 = __esm(() => {
32151
32361
  }
32152
32362
  const greenfieldDetectionEnabled = effectiveConfig.tdd.greenfieldDetection ?? true;
32153
32363
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
32154
- const greenfieldScanDir = ctx.story.workdir ? join25(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32364
+ const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32155
32365
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
32156
32366
  if (isGreenfield) {
32157
32367
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -32203,7 +32413,7 @@ var init_crash_detector = __esm(() => {
32203
32413
  });
32204
32414
 
32205
32415
  // src/pipeline/stages/verify.ts
32206
- import { join as join26 } from "path";
32416
+ import { join as join27 } from "path";
32207
32417
  function coerceSmartTestRunner(val) {
32208
32418
  if (val === undefined || val === true)
32209
32419
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -32219,7 +32429,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
32219
32429
  }
32220
32430
  async function readPackageName(dir) {
32221
32431
  try {
32222
- const content = await Bun.file(join26(dir, "package.json")).json();
32432
+ const content = await Bun.file(join27(dir, "package.json")).json();
32223
32433
  return typeof content.name === "string" ? content.name : null;
32224
32434
  } catch {
32225
32435
  return null;
@@ -32264,7 +32474,7 @@ var init_verify = __esm(() => {
32264
32474
  return { action: "continue" };
32265
32475
  }
32266
32476
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
32267
- const effectiveWorkdir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32477
+ const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
32268
32478
  let effectiveCommand = testCommand;
32269
32479
  let isFullSuite = true;
32270
32480
  const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
@@ -32481,8 +32691,8 @@ __export(exports_init_context, {
32481
32691
  _initContextDeps: () => _initContextDeps
32482
32692
  });
32483
32693
  import { existsSync as existsSync24 } from "fs";
32484
- import { mkdir } from "fs/promises";
32485
- import { basename as basename3, join as join30 } from "path";
32694
+ import { mkdir as mkdir2 } from "fs/promises";
32695
+ import { basename as basename3, join as join31 } from "path";
32486
32696
  async function findFiles(dir, maxFiles = 200) {
32487
32697
  try {
32488
32698
  const proc = Bun.spawnSync([
@@ -32510,7 +32720,7 @@ async function findFiles(dir, maxFiles = 200) {
32510
32720
  return [];
32511
32721
  }
32512
32722
  async function readPackageManifest(projectRoot) {
32513
- const packageJsonPath = join30(projectRoot, "package.json");
32723
+ const packageJsonPath = join31(projectRoot, "package.json");
32514
32724
  if (!existsSync24(packageJsonPath)) {
32515
32725
  return null;
32516
32726
  }
@@ -32528,7 +32738,7 @@ async function readPackageManifest(projectRoot) {
32528
32738
  }
32529
32739
  }
32530
32740
  async function readReadmeSnippet(projectRoot) {
32531
- const readmePath = join30(projectRoot, "README.md");
32741
+ const readmePath = join31(projectRoot, "README.md");
32532
32742
  if (!existsSync24(readmePath)) {
32533
32743
  return null;
32534
32744
  }
@@ -32546,7 +32756,7 @@ async function detectEntryPoints(projectRoot) {
32546
32756
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
32547
32757
  const found = [];
32548
32758
  for (const candidate of candidates) {
32549
- const path12 = join30(projectRoot, candidate);
32759
+ const path12 = join31(projectRoot, candidate);
32550
32760
  if (existsSync24(path12)) {
32551
32761
  found.push(candidate);
32552
32762
  }
@@ -32557,7 +32767,7 @@ async function detectConfigFiles(projectRoot) {
32557
32767
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
32558
32768
  const found = [];
32559
32769
  for (const candidate of candidates) {
32560
- const path12 = join30(projectRoot, candidate);
32770
+ const path12 = join31(projectRoot, candidate);
32561
32771
  if (existsSync24(path12)) {
32562
32772
  found.push(candidate);
32563
32773
  }
@@ -32718,14 +32928,14 @@ function generatePackageContextTemplate(packagePath) {
32718
32928
  }
32719
32929
  async function initPackage(repoRoot, packagePath, force = false) {
32720
32930
  const logger = getLogger();
32721
- const naxDir = join30(repoRoot, ".nax", "mono", packagePath);
32722
- const contextPath = join30(naxDir, "context.md");
32931
+ const naxDir = join31(repoRoot, ".nax", "mono", packagePath);
32932
+ const contextPath = join31(naxDir, "context.md");
32723
32933
  if (existsSync24(contextPath) && !force) {
32724
32934
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
32725
32935
  return;
32726
32936
  }
32727
32937
  if (!existsSync24(naxDir)) {
32728
- await mkdir(naxDir, { recursive: true });
32938
+ await mkdir2(naxDir, { recursive: true });
32729
32939
  }
32730
32940
  const content = generatePackageContextTemplate(packagePath);
32731
32941
  await Bun.write(contextPath, content);
@@ -32733,14 +32943,14 @@ async function initPackage(repoRoot, packagePath, force = false) {
32733
32943
  }
32734
32944
  async function initContext(projectRoot, options = {}) {
32735
32945
  const logger = getLogger();
32736
- const naxDir = join30(projectRoot, ".nax");
32737
- const contextPath = join30(naxDir, "context.md");
32946
+ const naxDir = join31(projectRoot, ".nax");
32947
+ const contextPath = join31(naxDir, "context.md");
32738
32948
  if (existsSync24(contextPath) && !options.force) {
32739
32949
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
32740
32950
  return;
32741
32951
  }
32742
32952
  if (!existsSync24(naxDir)) {
32743
- await mkdir(naxDir, { recursive: true });
32953
+ await mkdir2(naxDir, { recursive: true });
32744
32954
  }
32745
32955
  const scan = await scanProject(projectRoot);
32746
32956
  let content;
@@ -32764,7 +32974,7 @@ var init_init_context = __esm(() => {
32764
32974
 
32765
32975
  // src/utils/path-security.ts
32766
32976
  import { realpathSync as realpathSync3 } from "fs";
32767
- import { dirname as dirname4, isAbsolute as isAbsolute4, join as join31, normalize as normalize2, resolve as resolve5 } from "path";
32977
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join32, normalize as normalize2, resolve as resolve5 } from "path";
32768
32978
  function safeRealpathForComparison(p) {
32769
32979
  try {
32770
32980
  return realpathSync3(p);
@@ -32773,7 +32983,7 @@ function safeRealpathForComparison(p) {
32773
32983
  if (parent === p)
32774
32984
  return normalize2(p);
32775
32985
  const resolvedParent = safeRealpathForComparison(parent);
32776
- return join31(resolvedParent, p.split("/").pop() ?? "");
32986
+ return join32(resolvedParent, p.split("/").pop() ?? "");
32777
32987
  }
32778
32988
  }
32779
32989
  function validateModulePath(modulePath, allowedRoots) {
@@ -32791,7 +33001,7 @@ function validateModulePath(modulePath, allowedRoots) {
32791
33001
  } else {
32792
33002
  for (let i = 0;i < allowedRoots.length; i++) {
32793
33003
  const originalRoot = resolve5(allowedRoots[i]);
32794
- const absoluteInput = resolve5(join31(originalRoot, modulePath));
33004
+ const absoluteInput = resolve5(join32(originalRoot, modulePath));
32795
33005
  const resolved = safeRealpathForComparison(absoluteInput);
32796
33006
  const resolvedRoot = resolvedRoots[i];
32797
33007
  if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
@@ -33327,19 +33537,19 @@ var init_loader4 = __esm(() => {
33327
33537
  });
33328
33538
 
33329
33539
  // src/hooks/runner.ts
33330
- import { join as join45 } from "path";
33540
+ import { join as join46 } from "path";
33331
33541
  async function loadHooksConfig(projectDir, globalDir) {
33332
33542
  let globalHooks = { hooks: {} };
33333
33543
  let projectHooks = { hooks: {} };
33334
33544
  let skipGlobal = false;
33335
- const projectPath = join45(projectDir, "hooks.json");
33545
+ const projectPath = join46(projectDir, "hooks.json");
33336
33546
  const projectData = await loadJsonFile(projectPath, "hooks");
33337
33547
  if (projectData) {
33338
33548
  projectHooks = projectData;
33339
33549
  skipGlobal = projectData.skipGlobal ?? false;
33340
33550
  }
33341
33551
  if (!skipGlobal && globalDir) {
33342
- const globalPath = join45(globalDir, "hooks.json");
33552
+ const globalPath = join46(globalDir, "hooks.json");
33343
33553
  const globalData = await loadJsonFile(globalPath, "hooks");
33344
33554
  if (globalData) {
33345
33555
  globalHooks = globalData;
@@ -33784,7 +33994,7 @@ __export(exports_acceptance_loop, {
33784
33994
  isStubTestFile: () => isStubTestFile,
33785
33995
  _acceptanceLoopDeps: () => _acceptanceLoopDeps
33786
33996
  });
33787
- import path14, { join as join46 } from "path";
33997
+ import path14, { join as join47 } from "path";
33788
33998
  function isStubTestFile(content) {
33789
33999
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
33790
34000
  }
@@ -33822,7 +34032,8 @@ async function generateAndAddFixStories(ctx, failures, prd) {
33822
34032
  workdir: ctx.workdir,
33823
34033
  modelDef,
33824
34034
  config: ctx.config,
33825
- testFilePath
34035
+ testFilePath,
34036
+ timeoutMs: ctx.config.acceptance?.timeoutMs
33826
34037
  });
33827
34038
  if (fixStories.length === 0) {
33828
34039
  logger?.error("acceptance", "Failed to generate fix stories");
@@ -33846,7 +34057,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
33846
34057
  agent: ctx.config.autoMode.defaultAgent,
33847
34058
  iteration: iterations
33848
34059
  }), ctx.workdir);
33849
- const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join46(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
34060
+ const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join47(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
33850
34061
  const fixContext = {
33851
34062
  config: ctx.config,
33852
34063
  effectiveConfig: fixEffectiveConfig,
@@ -34443,23 +34654,23 @@ var init_headless_formatter = __esm(() => {
34443
34654
  });
34444
34655
 
34445
34656
  // src/pipeline/subscribers/events-writer.ts
34446
- import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
34657
+ import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
34447
34658
  import { homedir as homedir5 } from "os";
34448
- import { basename as basename6, join as join47 } from "path";
34659
+ import { basename as basename6, join as join48 } from "path";
34449
34660
  function wireEventsWriter(bus, feature, runId, workdir) {
34450
34661
  const logger = getSafeLogger();
34451
34662
  const project = basename6(workdir);
34452
- const eventsDir = join47(homedir5(), ".nax", "events", project);
34453
- const eventsFile = join47(eventsDir, "events.jsonl");
34663
+ const eventsDir = join48(homedir5(), ".nax", "events", project);
34664
+ const eventsFile = join48(eventsDir, "events.jsonl");
34454
34665
  let dirReady = false;
34455
34666
  const write = (line) => {
34456
34667
  return (async () => {
34457
34668
  try {
34458
34669
  if (!dirReady) {
34459
- await mkdir2(eventsDir, { recursive: true });
34670
+ await mkdir3(eventsDir, { recursive: true });
34460
34671
  dirReady = true;
34461
34672
  }
34462
- await appendFile2(eventsFile, `${JSON.stringify(line)}
34673
+ await appendFile3(eventsFile, `${JSON.stringify(line)}
34463
34674
  `);
34464
34675
  } catch (err) {
34465
34676
  logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
@@ -34629,25 +34840,25 @@ var init_interaction2 = __esm(() => {
34629
34840
  });
34630
34841
 
34631
34842
  // src/pipeline/subscribers/registry.ts
34632
- import { mkdir as mkdir3, writeFile } from "fs/promises";
34843
+ import { mkdir as mkdir4, writeFile } from "fs/promises";
34633
34844
  import { homedir as homedir6 } from "os";
34634
- import { basename as basename7, join as join48 } from "path";
34845
+ import { basename as basename7, join as join49 } from "path";
34635
34846
  function wireRegistry(bus, feature, runId, workdir) {
34636
34847
  const logger = getSafeLogger();
34637
34848
  const project = basename7(workdir);
34638
- const runDir = join48(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
34639
- const metaFile = join48(runDir, "meta.json");
34849
+ const runDir = join49(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
34850
+ const metaFile = join49(runDir, "meta.json");
34640
34851
  const unsub = bus.on("run:started", (_ev) => {
34641
34852
  return (async () => {
34642
34853
  try {
34643
- await mkdir3(runDir, { recursive: true });
34854
+ await mkdir4(runDir, { recursive: true });
34644
34855
  const meta3 = {
34645
34856
  runId,
34646
34857
  project,
34647
34858
  feature,
34648
34859
  workdir,
34649
- statusPath: join48(workdir, ".nax", "features", feature, "status.json"),
34650
- eventsDir: join48(workdir, ".nax", "features", feature, "runs"),
34860
+ statusPath: join49(workdir, ".nax", "features", feature, "status.json"),
34861
+ eventsDir: join49(workdir, ".nax", "features", feature, "runs"),
34651
34862
  registeredAt: new Date().toISOString()
34652
34863
  };
34653
34864
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -35299,7 +35510,7 @@ var init_pipeline_result_handler = __esm(() => {
35299
35510
  });
35300
35511
 
35301
35512
  // src/execution/iteration-runner.ts
35302
- import { join as join49 } from "path";
35513
+ import { join as join50 } from "path";
35303
35514
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
35304
35515
  const logger = getSafeLogger();
35305
35516
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -35334,7 +35545,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
35334
35545
  }
35335
35546
  }
35336
35547
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
35337
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
35548
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join50(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
35338
35549
  const pipelineContext = {
35339
35550
  config: ctx.config,
35340
35551
  effectiveConfig,
@@ -35592,16 +35803,16 @@ __export(exports_manager, {
35592
35803
  WorktreeManager: () => WorktreeManager
35593
35804
  });
35594
35805
  import { existsSync as existsSync32, symlinkSync } from "fs";
35595
- import { mkdir as mkdir4 } from "fs/promises";
35596
- import { join as join50 } from "path";
35806
+ import { mkdir as mkdir5 } from "fs/promises";
35807
+ import { join as join51 } from "path";
35597
35808
 
35598
35809
  class WorktreeManager {
35599
35810
  async ensureGitExcludes(projectRoot) {
35600
35811
  const logger = getSafeLogger();
35601
- const infoDir = join50(projectRoot, ".git", "info");
35602
- const excludePath = join50(infoDir, "exclude");
35812
+ const infoDir = join51(projectRoot, ".git", "info");
35813
+ const excludePath = join51(infoDir, "exclude");
35603
35814
  try {
35604
- await mkdir4(infoDir, { recursive: true });
35815
+ await mkdir5(infoDir, { recursive: true });
35605
35816
  let existing = "";
35606
35817
  if (existsSync32(excludePath)) {
35607
35818
  existing = await Bun.file(excludePath).text();
@@ -35626,7 +35837,7 @@ ${missing.join(`
35626
35837
  }
35627
35838
  async create(projectRoot, storyId) {
35628
35839
  validateStoryId(storyId);
35629
- const worktreePath = join50(projectRoot, ".nax-wt", storyId);
35840
+ const worktreePath = join51(projectRoot, ".nax-wt", storyId);
35630
35841
  const branchName = `nax/${storyId}`;
35631
35842
  try {
35632
35843
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -35667,9 +35878,9 @@ ${missing.join(`
35667
35878
  }
35668
35879
  throw new Error(`Failed to create worktree: ${String(error48)}`);
35669
35880
  }
35670
- const nodeModulesSource = join50(projectRoot, "node_modules");
35881
+ const nodeModulesSource = join51(projectRoot, "node_modules");
35671
35882
  if (existsSync32(nodeModulesSource)) {
35672
- const nodeModulesTarget = join50(worktreePath, "node_modules");
35883
+ const nodeModulesTarget = join51(worktreePath, "node_modules");
35673
35884
  try {
35674
35885
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
35675
35886
  } catch (error48) {
@@ -35677,9 +35888,9 @@ ${missing.join(`
35677
35888
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
35678
35889
  }
35679
35890
  }
35680
- const envSource = join50(projectRoot, ".env");
35891
+ const envSource = join51(projectRoot, ".env");
35681
35892
  if (existsSync32(envSource)) {
35682
- const envTarget = join50(worktreePath, ".env");
35893
+ const envTarget = join51(worktreePath, ".env");
35683
35894
  try {
35684
35895
  symlinkSync(envSource, envTarget, "file");
35685
35896
  } catch (error48) {
@@ -35690,7 +35901,7 @@ ${missing.join(`
35690
35901
  }
35691
35902
  async remove(projectRoot, storyId) {
35692
35903
  validateStoryId(storyId);
35693
- const worktreePath = join50(projectRoot, ".nax-wt", storyId);
35904
+ const worktreePath = join51(projectRoot, ".nax-wt", storyId);
35694
35905
  const branchName = `nax/${storyId}`;
35695
35906
  try {
35696
35907
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -36599,16 +36810,16 @@ var init_unified_executor = __esm(() => {
36599
36810
  });
36600
36811
 
36601
36812
  // src/project/detector.ts
36602
- import { join as join51 } from "path";
36813
+ import { join as join52 } from "path";
36603
36814
  async function detectLanguage(workdir, pkg) {
36604
36815
  const deps = _detectorDeps;
36605
- if (await deps.fileExists(join51(workdir, "go.mod")))
36816
+ if (await deps.fileExists(join52(workdir, "go.mod")))
36606
36817
  return "go";
36607
- if (await deps.fileExists(join51(workdir, "Cargo.toml")))
36818
+ if (await deps.fileExists(join52(workdir, "Cargo.toml")))
36608
36819
  return "rust";
36609
- if (await deps.fileExists(join51(workdir, "pyproject.toml")))
36820
+ if (await deps.fileExists(join52(workdir, "pyproject.toml")))
36610
36821
  return "python";
36611
- if (await deps.fileExists(join51(workdir, "requirements.txt")))
36822
+ if (await deps.fileExists(join52(workdir, "requirements.txt")))
36612
36823
  return "python";
36613
36824
  if (pkg != null) {
36614
36825
  const allDeps = {
@@ -36668,18 +36879,18 @@ async function detectLintTool(workdir, language) {
36668
36879
  if (language === "python")
36669
36880
  return "ruff";
36670
36881
  const deps = _detectorDeps;
36671
- if (await deps.fileExists(join51(workdir, "biome.json")))
36882
+ if (await deps.fileExists(join52(workdir, "biome.json")))
36672
36883
  return "biome";
36673
- if (await deps.fileExists(join51(workdir, ".eslintrc")))
36884
+ if (await deps.fileExists(join52(workdir, ".eslintrc")))
36674
36885
  return "eslint";
36675
- if (await deps.fileExists(join51(workdir, ".eslintrc.js")))
36886
+ if (await deps.fileExists(join52(workdir, ".eslintrc.js")))
36676
36887
  return "eslint";
36677
- if (await deps.fileExists(join51(workdir, ".eslintrc.json")))
36888
+ if (await deps.fileExists(join52(workdir, ".eslintrc.json")))
36678
36889
  return "eslint";
36679
36890
  return;
36680
36891
  }
36681
36892
  async function detectProjectProfile(workdir, existing) {
36682
- const pkg = await _detectorDeps.readJson(join51(workdir, "package.json"));
36893
+ const pkg = await _detectorDeps.readJson(join52(workdir, "package.json"));
36683
36894
  const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
36684
36895
  const type = existing.type !== undefined ? existing.type : detectType(pkg);
36685
36896
  const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
@@ -36772,7 +36983,7 @@ async function writeStatusFile(filePath, status) {
36772
36983
  var init_status_file = () => {};
36773
36984
 
36774
36985
  // src/execution/status-writer.ts
36775
- import { join as join52 } from "path";
36986
+ import { join as join53 } from "path";
36776
36987
 
36777
36988
  class StatusWriter {
36778
36989
  statusFile;
@@ -36782,6 +36993,7 @@ class StatusWriter {
36782
36993
  _prd = null;
36783
36994
  _currentStory = null;
36784
36995
  _consecutiveWriteFailures = 0;
36996
+ _mutex = Promise.resolve();
36785
36997
  constructor(statusFile, config2, ctx) {
36786
36998
  this.statusFile = statusFile;
36787
36999
  this.costLimit = config2.execution.costLimit === Number.POSITIVE_INFINITY ? null : config2.execution.costLimit;
@@ -36817,6 +37029,11 @@ class StatusWriter {
36817
37029
  async update(totalCost, iterations, overrides = {}) {
36818
37030
  if (!this._prd)
36819
37031
  return;
37032
+ const write = this._doUpdate(totalCost, iterations, overrides);
37033
+ this._mutex = this._mutex.then(() => write).catch(() => write);
37034
+ return this._mutex;
37035
+ }
37036
+ async _doUpdate(totalCost, iterations, overrides) {
36820
37037
  const safeLogger = getSafeLogger();
36821
37038
  try {
36822
37039
  const base = this.getSnapshot(totalCost, iterations);
@@ -36840,21 +37057,24 @@ class StatusWriter {
36840
37057
  if (!this._prd)
36841
37058
  return;
36842
37059
  const safeLogger = getSafeLogger();
36843
- const featureStatusPath = join52(featureDir, "status.json");
36844
- try {
36845
- const base = this.getSnapshot(totalCost, iterations);
36846
- if (!base) {
36847
- throw new Error("Failed to get snapshot");
37060
+ const featureStatusPath = join53(featureDir, "status.json");
37061
+ const write = async () => {
37062
+ try {
37063
+ const base = this.getSnapshot(totalCost, iterations);
37064
+ if (!base)
37065
+ throw new Error("Failed to get snapshot");
37066
+ const state = { ...base, ...overrides };
37067
+ await writeStatusFile(featureStatusPath, buildStatusSnapshot(state));
37068
+ safeLogger?.debug("status-file", "Feature status written", { path: featureStatusPath });
37069
+ } catch (err) {
37070
+ safeLogger?.warn("status-file", "Failed to write feature status file (non-fatal)", {
37071
+ path: featureStatusPath,
37072
+ error: err.message
37073
+ });
36848
37074
  }
36849
- const state = { ...base, ...overrides };
36850
- await writeStatusFile(featureStatusPath, buildStatusSnapshot(state));
36851
- safeLogger?.debug("status-file", "Feature status written", { path: featureStatusPath });
36852
- } catch (err) {
36853
- safeLogger?.warn("status-file", "Failed to write feature status file (non-fatal)", {
36854
- path: featureStatusPath,
36855
- error: err.message
36856
- });
36857
- }
37075
+ };
37076
+ this._mutex = this._mutex.then(write).catch(() => write());
37077
+ return this._mutex;
36858
37078
  }
36859
37079
  }
36860
37080
  var init_status_writer = __esm(() => {
@@ -36953,7 +37173,7 @@ var exports_precheck_runner = {};
36953
37173
  __export(exports_precheck_runner, {
36954
37174
  runPrecheckValidation: () => runPrecheckValidation
36955
37175
  });
36956
- import { mkdirSync as mkdirSync5 } from "fs";
37176
+ import { mkdirSync as mkdirSync4 } from "fs";
36957
37177
  import path17 from "path";
36958
37178
  async function runPrecheckValidation(ctx) {
36959
37179
  const logger = getSafeLogger();
@@ -36968,7 +37188,7 @@ async function runPrecheckValidation(ctx) {
36968
37188
  format: "human"
36969
37189
  });
36970
37190
  if (ctx.logFilePath) {
36971
- mkdirSync5(path17.dirname(ctx.logFilePath), { recursive: true });
37191
+ mkdirSync4(path17.dirname(ctx.logFilePath), { recursive: true });
36972
37192
  const precheckLog = {
36973
37193
  type: "precheck",
36974
37194
  timestamp: new Date().toISOString(),
@@ -37048,7 +37268,7 @@ __export(exports_run_initialization, {
37048
37268
  initializeRun: () => initializeRun,
37049
37269
  _reconcileDeps: () => _reconcileDeps
37050
37270
  });
37051
- import { join as join53 } from "path";
37271
+ import { join as join54 } from "path";
37052
37272
  async function reconcileState(prd, prdPath, workdir, config2) {
37053
37273
  const logger = getSafeLogger();
37054
37274
  let reconciledCount = 0;
@@ -37066,7 +37286,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
37066
37286
  });
37067
37287
  continue;
37068
37288
  }
37069
- const effectiveWorkdir = story.workdir ? join53(workdir, story.workdir) : workdir;
37289
+ const effectiveWorkdir = story.workdir ? join54(workdir, story.workdir) : workdir;
37070
37290
  try {
37071
37291
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
37072
37292
  if (!reviewResult.success) {
@@ -68274,9 +68494,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
68274
68494
 
68275
68495
  // bin/nax.ts
68276
68496
  init_source();
68277
- import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
68497
+ import { existsSync as existsSync34, mkdirSync as mkdirSync5 } from "fs";
68278
68498
  import { homedir as homedir8 } from "os";
68279
- import { join as join55 } from "path";
68499
+ import { join as join56 } from "path";
68280
68500
 
68281
68501
  // node_modules/commander/esm.mjs
68282
68502
  var import__ = __toESM(require_commander(), 1);
@@ -68760,7 +68980,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
68760
68980
  // src/cli/plan.ts
68761
68981
  init_registry();
68762
68982
  import { existsSync as existsSync15 } from "fs";
68763
- import { join as join11 } from "path";
68983
+ import { join as join12 } from "path";
68764
68984
  import { createInterface as createInterface2 } from "readline";
68765
68985
  init_test_strategy();
68766
68986
 
@@ -69488,7 +69708,7 @@ var _planDeps = {
69488
69708
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
69489
69709
  scanCodebase: (workdir) => scanCodebase(workdir),
69490
69710
  getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
69491
- readPackageJson: (workdir) => Bun.file(join11(workdir, "package.json")).json().catch(() => null),
69711
+ readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
69492
69712
  spawnSync: (cmd, opts) => {
69493
69713
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
69494
69714
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -69508,7 +69728,7 @@ var _planDeps = {
69508
69728
  planDecompose: (workdir, config2, opts) => planDecomposeCommand(workdir, config2, opts)
69509
69729
  };
69510
69730
  async function planCommand(workdir, config2, options) {
69511
- const naxDir = join11(workdir, ".nax");
69731
+ const naxDir = join12(workdir, ".nax");
69512
69732
  if (!existsSync15(naxDir)) {
69513
69733
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
69514
69734
  }
@@ -69524,18 +69744,51 @@ async function planCommand(workdir, config2, options) {
69524
69744
  const codebaseContext = buildCodebaseContext2(scan);
69525
69745
  const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
69526
69746
  const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
69527
- const pkgJson = await _planDeps.readPackageJsonAt(join11(workdir, rel, "package.json"));
69747
+ const pkgJson = await _planDeps.readPackageJsonAt(join12(workdir, rel, "package.json"));
69528
69748
  return buildPackageSummary(rel, pkgJson);
69529
69749
  })) : [];
69530
69750
  const projectName = detectProjectName(workdir, pkg);
69531
69751
  const branchName = options.branch ?? `feat/${options.feature}`;
69532
- const outputDir = join11(naxDir, "features", options.feature);
69533
- const outputPath = join11(outputDir, "prd.json");
69752
+ const outputDir = join12(naxDir, "features", options.feature);
69753
+ const outputPath = join12(outputDir, "prd.json");
69534
69754
  await _planDeps.mkdirp(outputDir);
69535
69755
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
69536
69756
  const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
69537
69757
  let rawResponse;
69538
- if (options.auto) {
69758
+ const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69759
+ if (debateEnabled) {
69760
+ const basePrompt = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails, config2?.project);
69761
+ const resolvedPerm = resolvePermissions(config2, "plan");
69762
+ const planStageConfig = config2?.debate?.stages.plan;
69763
+ const debateSession = _planDeps.createDebateSession({
69764
+ storyId: options.feature,
69765
+ stage: "plan",
69766
+ stageConfig: planStageConfig,
69767
+ config: config2
69768
+ });
69769
+ logger?.info("plan", "Starting debate planning session", {
69770
+ debaters: planStageConfig.debaters?.map((d) => d.agent),
69771
+ rounds: planStageConfig.rounds,
69772
+ feature: options.feature
69773
+ });
69774
+ const debateResult = await debateSession.runPlan(basePrompt, {
69775
+ workdir,
69776
+ feature: options.feature,
69777
+ outputDir,
69778
+ timeoutSeconds,
69779
+ dangerouslySkipPermissions: resolvedPerm.skipPermissions,
69780
+ maxInteractionTurns: config2?.agent?.maxInteractionTurns
69781
+ });
69782
+ if (debateResult.outcome !== "failed" && debateResult.output) {
69783
+ rawResponse = debateResult.output;
69784
+ } else {
69785
+ logger?.warn("debate", "All plan debaters failed \u2014 falling back to single agent", {
69786
+ stage: "plan",
69787
+ event: "fallback"
69788
+ });
69789
+ rawResponse = await runInteractivePlan();
69790
+ }
69791
+ } else if (options.auto) {
69539
69792
  const isAcp = config2?.agent?.protocol === "acp";
69540
69793
  const prompt = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
69541
69794
  const adapter = _planDeps.getAgent(agentName, config2);
@@ -69550,39 +69803,38 @@ async function planCommand(workdir, config2, options) {
69550
69803
  autoModel = resolveModelForAgent2(config2.models, defaultAgent, planTier, defaultAgent).model;
69551
69804
  }
69552
69805
  } catch {}
69553
- const runSingleAgentPlan = async () => {
69554
- if (isAcp) {
69555
- logger?.info("plan", "Starting ACP auto planning session", {
69556
- agent: agentName,
69557
- model: autoModel ?? config2?.plan?.model ?? "balanced",
69806
+ if (isAcp) {
69807
+ logger?.info("plan", "Starting ACP auto planning session", {
69808
+ agent: agentName,
69809
+ model: autoModel ?? config2?.plan?.model ?? "balanced",
69810
+ workdir,
69811
+ feature: options.feature,
69812
+ timeoutSeconds
69813
+ });
69814
+ const pidRegistry = new PidRegistry(workdir);
69815
+ try {
69816
+ await adapter.plan({
69817
+ prompt,
69558
69818
  workdir,
69559
- feature: options.feature,
69560
- timeoutSeconds
69819
+ interactive: false,
69820
+ timeoutSeconds,
69821
+ config: config2,
69822
+ modelTier: config2?.plan?.model ?? "balanced",
69823
+ dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69824
+ maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69825
+ featureName: options.feature,
69826
+ pidRegistry,
69827
+ sessionRole: "plan"
69561
69828
  });
69562
- const pidRegistry = new PidRegistry(workdir);
69563
- try {
69564
- await adapter.plan({
69565
- prompt,
69566
- workdir,
69567
- interactive: false,
69568
- timeoutSeconds,
69569
- config: config2,
69570
- modelTier: config2?.plan?.model ?? "balanced",
69571
- dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69572
- maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69573
- featureName: options.feature,
69574
- pidRegistry,
69575
- sessionRole: "plan"
69576
- });
69577
- } finally {
69578
- await pidRegistry.killAll().catch(() => {});
69579
- }
69580
- if (!_planDeps.existsSync(outputPath)) {
69581
- throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69582
- }
69583
- return await _planDeps.readFile(outputPath);
69829
+ } finally {
69830
+ await pidRegistry.killAll().catch(() => {});
69584
69831
  }
69585
- let result = await adapter.complete(prompt, {
69832
+ if (!_planDeps.existsSync(outputPath)) {
69833
+ throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69834
+ }
69835
+ rawResponse = await _planDeps.readFile(outputPath);
69836
+ } else {
69837
+ const completeResult = await adapter.complete(prompt, {
69586
69838
  model: autoModel,
69587
69839
  jsonMode: true,
69588
69840
  workdir,
@@ -69590,37 +69842,19 @@ async function planCommand(workdir, config2, options) {
69590
69842
  featureName: options.feature,
69591
69843
  sessionRole: "plan"
69592
69844
  });
69845
+ let result = typeof completeResult === "string" ? completeResult : completeResult.output;
69593
69846
  try {
69594
69847
  const envelope = JSON.parse(result);
69595
69848
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
69596
69849
  result = envelope.result;
69597
69850
  }
69598
69851
  } catch {}
69599
- return result;
69600
- };
69601
- const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69602
- if (debateEnabled) {
69603
- const planStageConfig = config2?.debate?.stages.plan;
69604
- const debateSession = _planDeps.createDebateSession({
69605
- storyId: options.feature,
69606
- stage: "plan",
69607
- stageConfig: planStageConfig,
69608
- config: config2
69609
- });
69610
- const debateResult = await debateSession.run(prompt);
69611
- if (debateResult.outcome !== "failed" && debateResult.output) {
69612
- rawResponse = debateResult.output;
69613
- } else {
69614
- logger?.warn("debate", "All debaters failed \u2014 falling back to single agent", {
69615
- stage: "debate",
69616
- event: "fallback"
69617
- });
69618
- rawResponse = await runSingleAgentPlan();
69619
- }
69620
- } else {
69621
- rawResponse = await runSingleAgentPlan();
69852
+ rawResponse = result;
69622
69853
  }
69623
69854
  } else {
69855
+ rawResponse = await runInteractivePlan();
69856
+ }
69857
+ async function runInteractivePlan() {
69624
69858
  const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
69625
69859
  const adapter = _planDeps.getAgent(agentName, config2);
69626
69860
  if (!adapter)
@@ -69667,7 +69901,7 @@ async function planCommand(workdir, config2, options) {
69667
69901
  if (!_planDeps.existsSync(outputPath)) {
69668
69902
  throw new Error(`[plan] Agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69669
69903
  }
69670
- rawResponse = await _planDeps.readFile(outputPath);
69904
+ return _planDeps.readFile(outputPath);
69671
69905
  }
69672
69906
  const finalPrd = validatePlanOutput(rawResponse, options.feature, branchName);
69673
69907
  finalPrd.project = projectName;
@@ -69946,7 +70180,7 @@ Return JSON with this exact structure (no markdown, no explanation \u2014 JSON o
69946
70180
  }`;
69947
70181
  }
69948
70182
  async function planDecomposeCommand(workdir, config2, options) {
69949
- const prdPath = join11(workdir, ".nax", "features", options.feature, "prd.json");
70183
+ const prdPath = join12(workdir, ".nax", "features", options.feature, "prd.json");
69950
70184
  if (!_planDeps.existsSync(prdPath)) {
69951
70185
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
69952
70186
  stage: "decompose",
@@ -69990,22 +70224,24 @@ async function planDecomposeCommand(workdir, config2, options) {
69990
70224
  if (debateResult.outcome !== "failed" && debateResult.output) {
69991
70225
  rawResponse = debateResult.output;
69992
70226
  } else {
69993
- rawResponse = await adapter.complete(prompt, {
70227
+ const completeResult = await adapter.complete(prompt, {
69994
70228
  jsonMode: true,
69995
70229
  workdir,
69996
70230
  sessionRole: "decompose",
69997
70231
  featureName: options.feature,
69998
70232
  storyId: options.storyId
69999
70233
  });
70234
+ rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
70000
70235
  }
70001
70236
  } else {
70002
- rawResponse = await adapter.complete(prompt, {
70237
+ const completeResult = await adapter.complete(prompt, {
70003
70238
  jsonMode: true,
70004
70239
  workdir,
70005
70240
  sessionRole: "decompose",
70006
70241
  featureName: options.feature,
70007
70242
  storyId: options.storyId
70008
70243
  });
70244
+ rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
70009
70245
  }
70010
70246
  const jsonMatch = rawResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
70011
70247
  const cleanedResponse = jsonMatch ? jsonMatch[1] : rawResponse;
@@ -70228,13 +70464,13 @@ async function displayModelEfficiency(workdir) {
70228
70464
  // src/cli/status-features.ts
70229
70465
  init_source();
70230
70466
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
70231
- import { join as join14 } from "path";
70467
+ import { join as join15 } from "path";
70232
70468
 
70233
70469
  // src/commands/common.ts
70234
70470
  init_path_security();
70235
70471
  init_errors();
70236
70472
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
70237
- import { join as join12, resolve as resolve4 } from "path";
70473
+ import { join as join13, resolve as resolve4 } from "path";
70238
70474
  function resolveProject(options = {}) {
70239
70475
  const { dir, feature } = options;
70240
70476
  let projectRoot;
@@ -70242,12 +70478,12 @@ function resolveProject(options = {}) {
70242
70478
  let configPath;
70243
70479
  if (dir) {
70244
70480
  projectRoot = realpathSync2(resolve4(dir));
70245
- naxDir = join12(projectRoot, ".nax");
70481
+ naxDir = join13(projectRoot, ".nax");
70246
70482
  if (!existsSync16(naxDir)) {
70247
70483
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
70248
70484
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
70249
70485
  }
70250
- configPath = join12(naxDir, "config.json");
70486
+ configPath = join13(naxDir, "config.json");
70251
70487
  if (!existsSync16(configPath)) {
70252
70488
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
70253
70489
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -70255,22 +70491,22 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
70255
70491
  } else {
70256
70492
  const found = findProjectRoot(process.cwd());
70257
70493
  if (!found) {
70258
- const cwdNaxDir = join12(process.cwd(), ".nax");
70494
+ const cwdNaxDir = join13(process.cwd(), ".nax");
70259
70495
  if (existsSync16(cwdNaxDir)) {
70260
- const cwdConfigPath = join12(cwdNaxDir, "config.json");
70496
+ const cwdConfigPath = join13(cwdNaxDir, "config.json");
70261
70497
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
70262
70498
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
70263
70499
  }
70264
70500
  throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
70265
70501
  }
70266
70502
  projectRoot = found;
70267
- naxDir = join12(projectRoot, ".nax");
70268
- configPath = join12(naxDir, "config.json");
70503
+ naxDir = join13(projectRoot, ".nax");
70504
+ configPath = join13(naxDir, "config.json");
70269
70505
  }
70270
70506
  let featureDir;
70271
70507
  if (feature) {
70272
- const featuresDir = join12(naxDir, "features");
70273
- featureDir = join12(featuresDir, feature);
70508
+ const featuresDir = join13(naxDir, "features");
70509
+ featureDir = join13(featuresDir, feature);
70274
70510
  if (!existsSync16(featureDir)) {
70275
70511
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
70276
70512
  const availableMsg = availableFeatures.length > 0 ? `
@@ -70297,12 +70533,12 @@ function findProjectRoot(startDir) {
70297
70533
  let current = resolve4(startDir);
70298
70534
  let depth = 0;
70299
70535
  while (depth < MAX_DIRECTORY_DEPTH) {
70300
- const naxDir = join12(current, ".nax");
70301
- const configPath = join12(naxDir, "config.json");
70536
+ const naxDir = join13(current, ".nax");
70537
+ const configPath = join13(naxDir, "config.json");
70302
70538
  if (existsSync16(configPath)) {
70303
70539
  return realpathSync2(current);
70304
70540
  }
70305
- const parent = join12(current, "..");
70541
+ const parent = join13(current, "..");
70306
70542
  if (parent === current) {
70307
70543
  break;
70308
70544
  }
@@ -70324,7 +70560,7 @@ function isPidAlive(pid) {
70324
70560
  }
70325
70561
  }
70326
70562
  async function loadStatusFile(featureDir) {
70327
- const statusPath = join14(featureDir, "status.json");
70563
+ const statusPath = join15(featureDir, "status.json");
70328
70564
  if (!existsSync17(statusPath)) {
70329
70565
  return null;
70330
70566
  }
@@ -70336,7 +70572,7 @@ async function loadStatusFile(featureDir) {
70336
70572
  }
70337
70573
  }
70338
70574
  async function loadProjectStatusFile(projectDir) {
70339
- const statusPath = join14(projectDir, ".nax", "status.json");
70575
+ const statusPath = join15(projectDir, ".nax", "status.json");
70340
70576
  if (!existsSync17(statusPath)) {
70341
70577
  return null;
70342
70578
  }
@@ -70348,7 +70584,7 @@ async function loadProjectStatusFile(projectDir) {
70348
70584
  }
70349
70585
  }
70350
70586
  async function getFeatureSummary(featureName, featureDir) {
70351
- const prdPath = join14(featureDir, "prd.json");
70587
+ const prdPath = join15(featureDir, "prd.json");
70352
70588
  if (!existsSync17(prdPath)) {
70353
70589
  return {
70354
70590
  name: featureName,
@@ -70391,7 +70627,7 @@ async function getFeatureSummary(featureName, featureDir) {
70391
70627
  };
70392
70628
  }
70393
70629
  }
70394
- const runsDir = join14(featureDir, "runs");
70630
+ const runsDir = join15(featureDir, "runs");
70395
70631
  if (existsSync17(runsDir)) {
70396
70632
  const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
70397
70633
  if (runs.length > 0) {
@@ -70402,7 +70638,7 @@ async function getFeatureSummary(featureName, featureDir) {
70402
70638
  return summary;
70403
70639
  }
70404
70640
  async function displayAllFeatures(projectDir) {
70405
- const featuresDir = join14(projectDir, ".nax", "features");
70641
+ const featuresDir = join15(projectDir, ".nax", "features");
70406
70642
  if (!existsSync17(featuresDir)) {
70407
70643
  console.log(source_default.dim("No features found."));
70408
70644
  return;
@@ -70443,7 +70679,7 @@ async function displayAllFeatures(projectDir) {
70443
70679
  console.log();
70444
70680
  }
70445
70681
  }
70446
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join14(featuresDir, name))));
70682
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join15(featuresDir, name))));
70447
70683
  console.log(source_default.bold(`\uD83D\uDCCA Features
70448
70684
  `));
70449
70685
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -70469,7 +70705,7 @@ async function displayAllFeatures(projectDir) {
70469
70705
  console.log();
70470
70706
  }
70471
70707
  async function displayFeatureDetails(featureName, featureDir) {
70472
- const prdPath = join14(featureDir, "prd.json");
70708
+ const prdPath = join15(featureDir, "prd.json");
70473
70709
  if (!existsSync17(prdPath)) {
70474
70710
  console.log(source_default.bold(`
70475
70711
  \uD83D\uDCCA ${featureName}
@@ -70591,7 +70827,7 @@ async function displayFeatureStatus(options = {}) {
70591
70827
  init_errors();
70592
70828
  init_logger2();
70593
70829
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
70594
- import { join as join15 } from "path";
70830
+ import { join as join16 } from "path";
70595
70831
  async function parseRunLog(logPath) {
70596
70832
  const logger = getLogger();
70597
70833
  try {
@@ -70607,7 +70843,7 @@ async function parseRunLog(logPath) {
70607
70843
  async function runsListCommand(options) {
70608
70844
  const logger = getLogger();
70609
70845
  const { feature, workdir } = options;
70610
- const runsDir = join15(workdir, ".nax", "features", feature, "runs");
70846
+ const runsDir = join16(workdir, ".nax", "features", feature, "runs");
70611
70847
  if (!existsSync18(runsDir)) {
70612
70848
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
70613
70849
  return;
@@ -70619,7 +70855,7 @@ async function runsListCommand(options) {
70619
70855
  }
70620
70856
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
70621
70857
  for (const file3 of files.sort().reverse()) {
70622
- const logPath = join15(runsDir, file3);
70858
+ const logPath = join16(runsDir, file3);
70623
70859
  const entries = await parseRunLog(logPath);
70624
70860
  const startEvent = entries.find((e) => e.message === "run.start");
70625
70861
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -70645,7 +70881,7 @@ async function runsListCommand(options) {
70645
70881
  async function runsShowCommand(options) {
70646
70882
  const logger = getLogger();
70647
70883
  const { runId, feature, workdir } = options;
70648
- const logPath = join15(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
70884
+ const logPath = join16(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
70649
70885
  if (!existsSync18(logPath)) {
70650
70886
  logger.error("cli", "Run not found", { runId, feature, logPath });
70651
70887
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -70683,8 +70919,8 @@ async function runsShowCommand(options) {
70683
70919
  }
70684
70920
  // src/cli/prompts-main.ts
70685
70921
  init_logger2();
70686
- import { existsSync as existsSync22, mkdirSync as mkdirSync3 } from "fs";
70687
- import { join as join28 } from "path";
70922
+ import { existsSync as existsSync22, mkdirSync as mkdirSync2 } from "fs";
70923
+ import { join as join29 } from "path";
70688
70924
 
70689
70925
  // src/pipeline/index.ts
70690
70926
  init_runner();
@@ -70759,7 +70995,7 @@ function buildFrontmatter(story, ctx, role) {
70759
70995
 
70760
70996
  // src/cli/prompts-tdd.ts
70761
70997
  init_prompts2();
70762
- import { join as join27 } from "path";
70998
+ import { join as join28 } from "path";
70763
70999
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
70764
71000
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
70765
71001
  PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
@@ -70778,7 +71014,7 @@ ${frontmatter}---
70778
71014
 
70779
71015
  ${session.prompt}`;
70780
71016
  if (outputDir) {
70781
- const promptFile = join27(outputDir, `${story.id}.${session.role}.md`);
71017
+ const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
70782
71018
  await Bun.write(promptFile, fullOutput);
70783
71019
  logger.info("cli", "Written TDD prompt file", {
70784
71020
  storyId: story.id,
@@ -70794,7 +71030,7 @@ ${"=".repeat(80)}`);
70794
71030
  }
70795
71031
  }
70796
71032
  if (outputDir && ctx.contextMarkdown) {
70797
- const contextFile = join27(outputDir, `${story.id}.context.md`);
71033
+ const contextFile = join28(outputDir, `${story.id}.context.md`);
70798
71034
  const frontmatter = buildFrontmatter(story, ctx);
70799
71035
  const contextOutput = `---
70800
71036
  ${frontmatter}---
@@ -70808,12 +71044,12 @@ ${ctx.contextMarkdown}`;
70808
71044
  async function promptsCommand(options) {
70809
71045
  const logger = getLogger();
70810
71046
  const { feature, workdir, config: config2, storyId, outputDir } = options;
70811
- const naxDir = join28(workdir, ".nax");
71047
+ const naxDir = join29(workdir, ".nax");
70812
71048
  if (!existsSync22(naxDir)) {
70813
71049
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
70814
71050
  }
70815
- const featureDir = join28(naxDir, "features", feature);
70816
- const prdPath = join28(featureDir, "prd.json");
71051
+ const featureDir = join29(naxDir, "features", feature);
71052
+ const prdPath = join29(featureDir, "prd.json");
70817
71053
  if (!existsSync22(prdPath)) {
70818
71054
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
70819
71055
  }
@@ -70823,7 +71059,7 @@ async function promptsCommand(options) {
70823
71059
  throw new Error(storyId ? `Story "${storyId}" not found in feature "${feature}"` : `No stories found in feature "${feature}"`);
70824
71060
  }
70825
71061
  if (outputDir) {
70826
- mkdirSync3(outputDir, { recursive: true });
71062
+ mkdirSync2(outputDir, { recursive: true });
70827
71063
  }
70828
71064
  logger.info("cli", "Assembling prompts", {
70829
71065
  feature,
@@ -70874,10 +71110,10 @@ ${frontmatter}---
70874
71110
 
70875
71111
  ${ctx.prompt}`;
70876
71112
  if (outputDir) {
70877
- const promptFile = join28(outputDir, `${story.id}.prompt.md`);
71113
+ const promptFile = join29(outputDir, `${story.id}.prompt.md`);
70878
71114
  await Bun.write(promptFile, fullOutput);
70879
71115
  if (ctx.contextMarkdown) {
70880
- const contextFile = join28(outputDir, `${story.id}.context.md`);
71116
+ const contextFile = join29(outputDir, `${story.id}.context.md`);
70881
71117
  const contextOutput = `---
70882
71118
  ${frontmatter}---
70883
71119
 
@@ -70903,8 +71139,8 @@ ${"=".repeat(80)}`);
70903
71139
  return processedStories;
70904
71140
  }
70905
71141
  // src/cli/prompts-init.ts
70906
- import { existsSync as existsSync23, mkdirSync as mkdirSync4 } from "fs";
70907
- import { join as join29 } from "path";
71142
+ import { existsSync as existsSync23, mkdirSync as mkdirSync3 } from "fs";
71143
+ import { join as join30 } from "path";
70908
71144
  var TEMPLATE_ROLES = [
70909
71145
  { file: "test-writer.md", role: "test-writer" },
70910
71146
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -70928,9 +71164,9 @@ var TEMPLATE_HEADER = `<!--
70928
71164
  `;
70929
71165
  async function promptsInitCommand(options) {
70930
71166
  const { workdir, force = false, autoWireConfig = true } = options;
70931
- const templatesDir = join29(workdir, ".nax", "templates");
70932
- mkdirSync4(templatesDir, { recursive: true });
70933
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join29(templatesDir, f)));
71167
+ const templatesDir = join30(workdir, ".nax", "templates");
71168
+ mkdirSync3(templatesDir, { recursive: true });
71169
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join30(templatesDir, f)));
70934
71170
  if (existingFiles.length > 0 && !force) {
70935
71171
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
70936
71172
  Pass --force to overwrite existing templates.`);
@@ -70938,7 +71174,7 @@ async function promptsInitCommand(options) {
70938
71174
  }
70939
71175
  const written = [];
70940
71176
  for (const template of TEMPLATE_ROLES) {
70941
- const filePath = join29(templatesDir, template.file);
71177
+ const filePath = join30(templatesDir, template.file);
70942
71178
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
70943
71179
  const content = TEMPLATE_HEADER + roleBody;
70944
71180
  await Bun.write(filePath, content);
@@ -70954,7 +71190,7 @@ async function promptsInitCommand(options) {
70954
71190
  return written;
70955
71191
  }
70956
71192
  async function autoWirePromptsConfig(workdir) {
70957
- const configPath = join29(workdir, "nax.config.json");
71193
+ const configPath = join30(workdir, "nax.config.json");
70958
71194
  if (!existsSync23(configPath)) {
70959
71195
  const exampleConfig = JSON.stringify({
70960
71196
  prompts: {
@@ -71120,7 +71356,7 @@ init_config();
71120
71356
  init_logger2();
71121
71357
  init_prd();
71122
71358
  import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
71123
- import { join as join34 } from "path";
71359
+ import { join as join35 } from "path";
71124
71360
 
71125
71361
  // src/cli/diagnose-analysis.ts
71126
71362
  function detectFailurePattern(story, prd, status) {
@@ -71319,7 +71555,7 @@ function isProcessAlive2(pid) {
71319
71555
  }
71320
71556
  }
71321
71557
  async function loadStatusFile2(workdir) {
71322
- const statusPath = join34(workdir, ".nax", "status.json");
71558
+ const statusPath = join35(workdir, ".nax", "status.json");
71323
71559
  if (!existsSync25(statusPath))
71324
71560
  return null;
71325
71561
  try {
@@ -71347,7 +71583,7 @@ async function countCommitsSince(workdir, since) {
71347
71583
  }
71348
71584
  }
71349
71585
  async function checkLock(workdir) {
71350
- const lockFile = Bun.file(join34(workdir, "nax.lock"));
71586
+ const lockFile = Bun.file(join35(workdir, "nax.lock"));
71351
71587
  if (!await lockFile.exists())
71352
71588
  return { lockPresent: false };
71353
71589
  try {
@@ -71365,8 +71601,8 @@ async function diagnoseCommand(options = {}) {
71365
71601
  const logger = getLogger();
71366
71602
  const workdir = options.workdir ?? process.cwd();
71367
71603
  const naxSubdir = findProjectDir(workdir);
71368
- let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
71369
- if (!projectDir && existsSync25(join34(workdir, ".nax"))) {
71604
+ let projectDir = naxSubdir ? join35(naxSubdir, "..") : null;
71605
+ if (!projectDir && existsSync25(join35(workdir, ".nax"))) {
71370
71606
  projectDir = workdir;
71371
71607
  }
71372
71608
  if (!projectDir)
@@ -71377,7 +71613,7 @@ async function diagnoseCommand(options = {}) {
71377
71613
  if (status2) {
71378
71614
  feature = status2.run.feature;
71379
71615
  } else {
71380
- const featuresDir = join34(projectDir, ".nax", "features");
71616
+ const featuresDir = join35(projectDir, ".nax", "features");
71381
71617
  if (!existsSync25(featuresDir))
71382
71618
  throw new Error("No features found in project");
71383
71619
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -71387,8 +71623,8 @@ async function diagnoseCommand(options = {}) {
71387
71623
  logger.info("diagnose", "No feature specified, using first found", { feature });
71388
71624
  }
71389
71625
  }
71390
- const featureDir = join34(projectDir, ".nax", "features", feature);
71391
- const prdPath = join34(featureDir, "prd.json");
71626
+ const featureDir = join35(projectDir, ".nax", "features", feature);
71627
+ const prdPath = join35(featureDir, "prd.json");
71392
71628
  if (!existsSync25(prdPath))
71393
71629
  throw new Error(`Feature not found: ${feature}`);
71394
71630
  const prd = await loadPRD(prdPath);
@@ -71431,7 +71667,7 @@ init_interaction();
71431
71667
  init_source();
71432
71668
  init_loader();
71433
71669
  import { existsSync as existsSync26 } from "fs";
71434
- import { join as join35 } from "path";
71670
+ import { join as join36 } from "path";
71435
71671
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
71436
71672
  async function generateCommand(options) {
71437
71673
  const workdir = options.dir ?? process.cwd();
@@ -71474,7 +71710,7 @@ async function generateCommand(options) {
71474
71710
  return;
71475
71711
  }
71476
71712
  if (options.package) {
71477
- const packageDir = join35(workdir, options.package);
71713
+ const packageDir = join36(workdir, options.package);
71478
71714
  if (dryRun) {
71479
71715
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
71480
71716
  }
@@ -71494,8 +71730,8 @@ async function generateCommand(options) {
71494
71730
  process.exit(1);
71495
71731
  return;
71496
71732
  }
71497
- const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, ".nax/context.md");
71498
- const outputDir = options.output ? join35(workdir, options.output) : workdir;
71733
+ const contextPath = options.context ? join36(workdir, options.context) : join36(workdir, ".nax/context.md");
71734
+ const outputDir = options.output ? join36(workdir, options.output) : workdir;
71499
71735
  const autoInject = !options.noAutoInject;
71500
71736
  if (!existsSync26(contextPath)) {
71501
71737
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -71600,7 +71836,7 @@ async function generateCommand(options) {
71600
71836
  // src/cli/config-display.ts
71601
71837
  init_loader();
71602
71838
  import { existsSync as existsSync28 } from "fs";
71603
- import { join as join37 } from "path";
71839
+ import { join as join38 } from "path";
71604
71840
 
71605
71841
  // src/cli/config-descriptions.ts
71606
71842
  var FIELD_DESCRIPTIONS = {
@@ -71838,7 +72074,7 @@ function deepEqual(a, b) {
71838
72074
  init_defaults();
71839
72075
  init_loader();
71840
72076
  import { existsSync as existsSync27 } from "fs";
71841
- import { join as join36 } from "path";
72077
+ import { join as join37 } from "path";
71842
72078
  async function loadConfigFile(path14) {
71843
72079
  if (!existsSync27(path14))
71844
72080
  return null;
@@ -71860,7 +72096,7 @@ async function loadProjectConfig() {
71860
72096
  const projectDir = findProjectDir();
71861
72097
  if (!projectDir)
71862
72098
  return null;
71863
- const projectPath = join36(projectDir, "config.json");
72099
+ const projectPath = join37(projectDir, "config.json");
71864
72100
  return await loadConfigFile(projectPath);
71865
72101
  }
71866
72102
 
@@ -71920,7 +72156,7 @@ async function configCommand(config2, options = {}) {
71920
72156
  function determineConfigSources() {
71921
72157
  const globalPath = globalConfigPath();
71922
72158
  const projectDir = findProjectDir();
71923
- const projectPath = projectDir ? join37(projectDir, "config.json") : null;
72159
+ const projectPath = projectDir ? join38(projectDir, "config.json") : null;
71924
72160
  return {
71925
72161
  global: fileExists(globalPath) ? globalPath : null,
71926
72162
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -72100,24 +72336,24 @@ async function diagnose(options) {
72100
72336
 
72101
72337
  // src/commands/logs.ts
72102
72338
  import { existsSync as existsSync30 } from "fs";
72103
- import { join as join41 } from "path";
72339
+ import { join as join42 } from "path";
72104
72340
 
72105
72341
  // src/commands/logs-formatter.ts
72106
72342
  init_source();
72107
72343
  init_formatter();
72108
72344
  import { readdirSync as readdirSync7 } from "fs";
72109
- import { join as join40 } from "path";
72345
+ import { join as join41 } from "path";
72110
72346
 
72111
72347
  // src/commands/logs-reader.ts
72112
72348
  import { existsSync as existsSync29, readdirSync as readdirSync6 } from "fs";
72113
72349
  import { readdir as readdir3 } from "fs/promises";
72114
- import { join as join39 } from "path";
72350
+ import { join as join40 } from "path";
72115
72351
 
72116
72352
  // src/utils/paths.ts
72117
72353
  import { homedir as homedir4 } from "os";
72118
- import { join as join38 } from "path";
72354
+ import { join as join39 } from "path";
72119
72355
  function getRunsDir() {
72120
- return process.env.NAX_RUNS_DIR ?? join38(homedir4(), ".nax", "runs");
72356
+ return process.env.NAX_RUNS_DIR ?? join39(homedir4(), ".nax", "runs");
72121
72357
  }
72122
72358
 
72123
72359
  // src/commands/logs-reader.ts
@@ -72134,7 +72370,7 @@ async function resolveRunFileFromRegistry(runId) {
72134
72370
  }
72135
72371
  let matched = null;
72136
72372
  for (const entry of entries) {
72137
- const metaPath = join39(runsDir, entry, "meta.json");
72373
+ const metaPath = join40(runsDir, entry, "meta.json");
72138
72374
  try {
72139
72375
  const meta3 = await Bun.file(metaPath).json();
72140
72376
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -72156,14 +72392,14 @@ async function resolveRunFileFromRegistry(runId) {
72156
72392
  return null;
72157
72393
  }
72158
72394
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
72159
- return join39(matched.eventsDir, specificFile ?? files[0]);
72395
+ return join40(matched.eventsDir, specificFile ?? files[0]);
72160
72396
  }
72161
72397
  async function selectRunFile(runsDir) {
72162
72398
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
72163
72399
  if (files.length === 0) {
72164
72400
  return null;
72165
72401
  }
72166
- return join39(runsDir, files[0]);
72402
+ return join40(runsDir, files[0]);
72167
72403
  }
72168
72404
  async function extractRunSummary(filePath) {
72169
72405
  const file3 = Bun.file(filePath);
@@ -72248,7 +72484,7 @@ Runs:
72248
72484
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
72249
72485
  console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
72250
72486
  for (const file3 of files) {
72251
- const filePath = join40(runsDir, file3);
72487
+ const filePath = join41(runsDir, file3);
72252
72488
  const summary = await extractRunSummary(filePath);
72253
72489
  const timestamp = file3.replace(".jsonl", "");
72254
72490
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -72362,7 +72598,7 @@ async function logsCommand(options) {
72362
72598
  return;
72363
72599
  }
72364
72600
  const resolved = resolveProject({ dir: options.dir });
72365
- const naxDir = join41(resolved.projectDir, ".nax");
72601
+ const naxDir = join42(resolved.projectDir, ".nax");
72366
72602
  const configPath = resolved.configPath;
72367
72603
  const configFile = Bun.file(configPath);
72368
72604
  const config2 = await configFile.json();
@@ -72370,8 +72606,8 @@ async function logsCommand(options) {
72370
72606
  if (!featureName) {
72371
72607
  throw new Error("No feature specified in config.json");
72372
72608
  }
72373
- const featureDir = join41(naxDir, "features", featureName);
72374
- const runsDir = join41(featureDir, "runs");
72609
+ const featureDir = join42(naxDir, "features", featureName);
72610
+ const runsDir = join42(featureDir, "runs");
72375
72611
  if (!existsSync30(runsDir)) {
72376
72612
  throw new Error(`No runs directory found for feature: ${featureName}`);
72377
72613
  }
@@ -72396,7 +72632,7 @@ init_config();
72396
72632
  init_prd();
72397
72633
  init_precheck();
72398
72634
  import { existsSync as existsSync31 } from "fs";
72399
- import { join as join42 } from "path";
72635
+ import { join as join43 } from "path";
72400
72636
  async function precheckCommand(options) {
72401
72637
  const resolved = resolveProject({
72402
72638
  dir: options.dir,
@@ -72418,9 +72654,9 @@ async function precheckCommand(options) {
72418
72654
  process.exit(1);
72419
72655
  }
72420
72656
  }
72421
- const naxDir = join42(resolved.projectDir, ".nax");
72422
- const featureDir = join42(naxDir, "features", featureName);
72423
- const prdPath = join42(featureDir, "prd.json");
72657
+ const naxDir = join43(resolved.projectDir, ".nax");
72658
+ const featureDir = join43(naxDir, "features", featureName);
72659
+ const prdPath = join43(featureDir, "prd.json");
72424
72660
  if (!existsSync31(featureDir)) {
72425
72661
  console.error(source_default.red(`Feature not found: ${featureName}`));
72426
72662
  process.exit(1);
@@ -72442,7 +72678,7 @@ async function precheckCommand(options) {
72442
72678
  // src/commands/runs.ts
72443
72679
  init_source();
72444
72680
  import { readdir as readdir4 } from "fs/promises";
72445
- import { join as join43 } from "path";
72681
+ import { join as join44 } from "path";
72446
72682
  var DEFAULT_LIMIT = 20;
72447
72683
  var _runsCmdDeps = {
72448
72684
  getRunsDir
@@ -72497,7 +72733,7 @@ async function runsCommand(options = {}) {
72497
72733
  }
72498
72734
  const rows = [];
72499
72735
  for (const entry of entries) {
72500
- const metaPath = join43(runsDir, entry, "meta.json");
72736
+ const metaPath = join44(runsDir, entry, "meta.json");
72501
72737
  let meta3;
72502
72738
  try {
72503
72739
  meta3 = await Bun.file(metaPath).json();
@@ -72574,7 +72810,7 @@ async function runsCommand(options = {}) {
72574
72810
 
72575
72811
  // src/commands/unlock.ts
72576
72812
  init_source();
72577
- import { join as join44 } from "path";
72813
+ import { join as join45 } from "path";
72578
72814
  function isProcessAlive3(pid) {
72579
72815
  try {
72580
72816
  process.kill(pid, 0);
@@ -72589,7 +72825,7 @@ function formatLockAge(ageMs) {
72589
72825
  }
72590
72826
  async function unlockCommand(options) {
72591
72827
  const workdir = options.dir ?? process.cwd();
72592
- const lockPath = join44(workdir, "nax.lock");
72828
+ const lockPath = join45(workdir, "nax.lock");
72593
72829
  const lockFile = Bun.file(lockPath);
72594
72830
  const exists = await lockFile.exists();
72595
72831
  if (!exists) {
@@ -80393,15 +80629,15 @@ Next: nax generate --package ${options.package}`));
80393
80629
  }
80394
80630
  return;
80395
80631
  }
80396
- const naxDir = join55(workdir, ".nax");
80632
+ const naxDir = join56(workdir, ".nax");
80397
80633
  if (existsSync34(naxDir) && !options.force) {
80398
80634
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
80399
80635
  return;
80400
80636
  }
80401
- mkdirSync6(join55(naxDir, "features"), { recursive: true });
80402
- mkdirSync6(join55(naxDir, "hooks"), { recursive: true });
80403
- await Bun.write(join55(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
80404
- await Bun.write(join55(naxDir, "hooks.json"), JSON.stringify({
80637
+ mkdirSync5(join56(naxDir, "features"), { recursive: true });
80638
+ mkdirSync5(join56(naxDir, "hooks"), { recursive: true });
80639
+ await Bun.write(join56(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
80640
+ await Bun.write(join56(naxDir, "hooks.json"), JSON.stringify({
80405
80641
  hooks: {
80406
80642
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
80407
80643
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -80409,12 +80645,12 @@ Next: nax generate --package ${options.package}`));
80409
80645
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
80410
80646
  }
80411
80647
  }, null, 2));
80412
- await Bun.write(join55(naxDir, ".gitignore"), `# nax temp files
80648
+ await Bun.write(join56(naxDir, ".gitignore"), `# nax temp files
80413
80649
  *.tmp
80414
80650
  .paused.json
80415
80651
  .nax-verifier-verdict.json
80416
80652
  `);
80417
- await Bun.write(join55(naxDir, "context.md"), `# Project Context
80653
+ await Bun.write(join56(naxDir, "context.md"), `# Project Context
80418
80654
 
80419
80655
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
80420
80656
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -80540,8 +80776,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
80540
80776
  console.error(source_default.red("nax not initialized. Run: nax init"));
80541
80777
  process.exit(1);
80542
80778
  }
80543
- const featureDir = join55(naxDir, "features", options.feature);
80544
- const prdPath = join55(featureDir, "prd.json");
80779
+ const featureDir = join56(naxDir, "features", options.feature);
80780
+ const prdPath = join56(featureDir, "prd.json");
80545
80781
  if (options.plan && options.from) {
80546
80782
  if (existsSync34(prdPath) && !options.force) {
80547
80783
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -80563,10 +80799,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
80563
80799
  }
80564
80800
  }
80565
80801
  try {
80566
- const planLogDir = join55(featureDir, "plan");
80567
- mkdirSync6(planLogDir, { recursive: true });
80802
+ const planLogDir = join56(featureDir, "plan");
80803
+ mkdirSync5(planLogDir, { recursive: true });
80568
80804
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80569
- const planLogPath = join55(planLogDir, `${planLogId}.jsonl`);
80805
+ const planLogPath = join56(planLogDir, `${planLogId}.jsonl`);
80570
80806
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
80571
80807
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
80572
80808
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -80610,10 +80846,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
80610
80846
  process.exit(1);
80611
80847
  }
80612
80848
  resetLogger();
80613
- const runsDir = join55(featureDir, "runs");
80614
- mkdirSync6(runsDir, { recursive: true });
80849
+ const runsDir = join56(featureDir, "runs");
80850
+ mkdirSync5(runsDir, { recursive: true });
80615
80851
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80616
- const logFilePath = join55(runsDir, `${runId}.jsonl`);
80852
+ const logFilePath = join56(runsDir, `${runId}.jsonl`);
80617
80853
  const isTTY = process.stdout.isTTY ?? false;
80618
80854
  const headlessFlag = options.headless ?? false;
80619
80855
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -80629,7 +80865,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80629
80865
  config2.autoMode.defaultAgent = options.agent;
80630
80866
  }
80631
80867
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
80632
- const globalNaxDir = join55(homedir8(), ".nax");
80868
+ const globalNaxDir = join56(homedir8(), ".nax");
80633
80869
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
80634
80870
  const eventEmitter = new PipelineEventEmitter;
80635
80871
  let tuiInstance;
@@ -80652,7 +80888,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80652
80888
  } else {
80653
80889
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
80654
80890
  }
80655
- const statusFilePath = join55(workdir, ".nax", "status.json");
80891
+ const statusFilePath = join56(workdir, ".nax", "status.json");
80656
80892
  let parallel;
80657
80893
  if (options.parallel !== undefined) {
80658
80894
  parallel = Number.parseInt(options.parallel, 10);
@@ -80678,7 +80914,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80678
80914
  headless: useHeadless,
80679
80915
  skipPrecheck: options.skipPrecheck ?? false
80680
80916
  });
80681
- const latestSymlink = join55(runsDir, "latest.jsonl");
80917
+ const latestSymlink = join56(runsDir, "latest.jsonl");
80682
80918
  try {
80683
80919
  if (existsSync34(latestSymlink)) {
80684
80920
  Bun.spawnSync(["rm", latestSymlink]);
@@ -80716,9 +80952,9 @@ features.command("create <name>").description("Create a new feature").option("-d
80716
80952
  console.error(source_default.red("nax not initialized. Run: nax init"));
80717
80953
  process.exit(1);
80718
80954
  }
80719
- const featureDir = join55(naxDir, "features", name);
80720
- mkdirSync6(featureDir, { recursive: true });
80721
- await Bun.write(join55(featureDir, "spec.md"), `# Feature: ${name}
80955
+ const featureDir = join56(naxDir, "features", name);
80956
+ mkdirSync5(featureDir, { recursive: true });
80957
+ await Bun.write(join56(featureDir, "spec.md"), `# Feature: ${name}
80722
80958
 
80723
80959
  ## Overview
80724
80960
 
@@ -80751,7 +80987,7 @@ features.command("create <name>").description("Create a new feature").option("-d
80751
80987
 
80752
80988
  <!-- What this feature explicitly does NOT cover. -->
80753
80989
  `);
80754
- await Bun.write(join55(featureDir, "progress.txt"), `# Progress: ${name}
80990
+ await Bun.write(join56(featureDir, "progress.txt"), `# Progress: ${name}
80755
80991
 
80756
80992
  Created: ${new Date().toISOString()}
80757
80993
 
@@ -80777,7 +81013,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
80777
81013
  console.error(source_default.red("nax not initialized."));
80778
81014
  process.exit(1);
80779
81015
  }
80780
- const featuresDir = join55(naxDir, "features");
81016
+ const featuresDir = join56(naxDir, "features");
80781
81017
  if (!existsSync34(featuresDir)) {
80782
81018
  console.log(source_default.dim("No features yet."));
80783
81019
  return;
@@ -80792,7 +81028,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
80792
81028
  Features:
80793
81029
  `));
80794
81030
  for (const name of entries) {
80795
- const prdPath = join55(featuresDir, name, "prd.json");
81031
+ const prdPath = join56(featuresDir, name, "prd.json");
80796
81032
  if (existsSync34(prdPath)) {
80797
81033
  const prd = await loadPRD(prdPath);
80798
81034
  const c = countStories(prd);
@@ -80823,10 +81059,10 @@ Use: nax plan -f <feature> --from <spec>`));
80823
81059
  process.exit(1);
80824
81060
  }
80825
81061
  const config2 = await loadConfig(workdir);
80826
- const featureLogDir = join55(naxDir, "features", options.feature, "plan");
80827
- mkdirSync6(featureLogDir, { recursive: true });
81062
+ const featureLogDir = join56(naxDir, "features", options.feature, "plan");
81063
+ mkdirSync5(featureLogDir, { recursive: true });
80828
81064
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80829
- const planLogPath = join55(featureLogDir, `${planLogId}.jsonl`);
81065
+ const planLogPath = join56(featureLogDir, `${planLogId}.jsonl`);
80830
81066
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
80831
81067
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
80832
81068
  try {
@@ -80877,7 +81113,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
80877
81113
  console.error(source_default.red("nax not initialized. Run: nax init"));
80878
81114
  process.exit(1);
80879
81115
  }
80880
- const featureDir = join55(naxDir, "features", options.feature);
81116
+ const featureDir = join56(naxDir, "features", options.feature);
80881
81117
  if (!existsSync34(featureDir)) {
80882
81118
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
80883
81119
  process.exit(1);
@@ -80893,7 +81129,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
80893
81129
  specPath: options.from,
80894
81130
  reclassify: options.reclassify
80895
81131
  });
80896
- const prdPath = join55(featureDir, "prd.json");
81132
+ const prdPath = join56(featureDir, "prd.json");
80897
81133
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
80898
81134
  const c = countStories(prd);
80899
81135
  console.log(source_default.green(`