@nathapp/nax 0.56.3 → 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 +664 -446
  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)
@@ -3677,157 +3828,6 @@ var init_env = __esm(() => {
3677
3828
  ];
3678
3829
  });
3679
3830
 
3680
- // src/agents/cost/pricing.ts
3681
- var COST_RATES, MODEL_PRICING;
3682
- var init_pricing = __esm(() => {
3683
- COST_RATES = {
3684
- fast: {
3685
- inputPer1M: 0.8,
3686
- outputPer1M: 4
3687
- },
3688
- balanced: {
3689
- inputPer1M: 3,
3690
- outputPer1M: 15
3691
- },
3692
- powerful: {
3693
- inputPer1M: 15,
3694
- outputPer1M: 75
3695
- }
3696
- };
3697
- MODEL_PRICING = {
3698
- sonnet: { input: 3, output: 15 },
3699
- haiku: { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3700
- opus: { input: 15, output: 75 },
3701
- "claude-sonnet-4": { input: 3, output: 15 },
3702
- "claude-sonnet-4-5": { input: 3, output: 15 },
3703
- "claude-sonnet-4-6": { input: 3, output: 15 },
3704
- "claude-haiku": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3705
- "claude-haiku-4-5": { input: 0.8, output: 4, cacheRead: 0.1, cacheCreation: 1 },
3706
- "claude-opus": { input: 15, output: 75 },
3707
- "claude-opus-4": { input: 15, output: 75 },
3708
- "claude-opus-4-6": { input: 15, output: 75 },
3709
- "gpt-4.1": { input: 10, output: 30 },
3710
- "gpt-4": { input: 30, output: 60 },
3711
- "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
3712
- "gemini-2.5-pro": { input: 0.075, output: 0.3 },
3713
- "gemini-2-pro": { input: 0.075, output: 0.3 },
3714
- codex: { input: 0.02, output: 0.06 },
3715
- "code-davinci-002": { input: 0.02, output: 0.06 }
3716
- };
3717
- });
3718
-
3719
- // src/agents/cost/parse.ts
3720
- function parseTokenUsage(output) {
3721
- try {
3722
- const jsonMatch = output.match(/\{[^}]*"usage"\s*:\s*\{[^}]*"input_tokens"\s*:\s*(\d+)[^}]*"output_tokens"\s*:\s*(\d+)[^}]*\}[^}]*\}/);
3723
- if (jsonMatch) {
3724
- return {
3725
- inputTokens: Number.parseInt(jsonMatch[1], 10),
3726
- outputTokens: Number.parseInt(jsonMatch[2], 10),
3727
- confidence: "exact"
3728
- };
3729
- }
3730
- const lines = output.split(`
3731
- `);
3732
- for (const line of lines) {
3733
- if (line.trim().startsWith("{")) {
3734
- try {
3735
- const parsed = JSON.parse(line);
3736
- if (parsed.usage?.input_tokens && parsed.usage?.output_tokens) {
3737
- return {
3738
- inputTokens: parsed.usage.input_tokens,
3739
- outputTokens: parsed.usage.output_tokens,
3740
- confidence: "exact"
3741
- };
3742
- }
3743
- } catch {}
3744
- }
3745
- }
3746
- } catch {}
3747
- const inputMatch = output.match(/\b(?:input|input_tokens)\s*:\s*(\d{2,})|(?:input)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
3748
- const outputMatch = output.match(/\b(?:output|output_tokens)\s*:\s*(\d{2,})|(?:output)\s+(?:tokens?)\s*:\s*(\d{2,})/i);
3749
- if (inputMatch && outputMatch) {
3750
- const inputTokens = Number.parseInt(inputMatch[1] || inputMatch[2], 10);
3751
- const outputTokens = Number.parseInt(outputMatch[1] || outputMatch[2], 10);
3752
- if (inputTokens > 1e6 || outputTokens > 1e6) {
3753
- return null;
3754
- }
3755
- return {
3756
- inputTokens,
3757
- outputTokens,
3758
- confidence: "estimated"
3759
- };
3760
- }
3761
- return null;
3762
- }
3763
-
3764
- // src/agents/cost/calculate.ts
3765
- function estimateCost(modelTier, inputTokens, outputTokens, customRates) {
3766
- const rates = customRates ?? COST_RATES[modelTier];
3767
- const inputCost = inputTokens / 1e6 * rates.inputPer1M;
3768
- const outputCost = outputTokens / 1e6 * rates.outputPer1M;
3769
- return inputCost + outputCost;
3770
- }
3771
- function estimateCostFromOutput(modelTier, output) {
3772
- const usage = parseTokenUsage(output);
3773
- if (!usage) {
3774
- return null;
3775
- }
3776
- const cost = estimateCost(modelTier, usage.inputTokens, usage.outputTokens);
3777
- return {
3778
- cost,
3779
- confidence: usage.confidence
3780
- };
3781
- }
3782
- function estimateCostByDuration(modelTier, durationMs) {
3783
- const costPerMinute = {
3784
- fast: 0.01,
3785
- balanced: 0.05,
3786
- powerful: 0.15
3787
- };
3788
- const minutes = durationMs / 60000;
3789
- const cost = minutes * costPerMinute[modelTier];
3790
- return {
3791
- cost,
3792
- confidence: "fallback"
3793
- };
3794
- }
3795
- function formatCostWithConfidence(estimate) {
3796
- const formattedCost = `$${estimate.cost.toFixed(2)}`;
3797
- switch (estimate.confidence) {
3798
- case "exact":
3799
- return formattedCost;
3800
- case "estimated":
3801
- return `~${formattedCost}`;
3802
- case "fallback":
3803
- return `~${formattedCost} (duration-based)`;
3804
- }
3805
- }
3806
- function estimateCostFromTokenUsage(usage, model) {
3807
- const pricing = MODEL_PRICING[model];
3808
- if (!pricing) {
3809
- const fallbackInputRate = 3 / 1e6;
3810
- const fallbackOutputRate = 15 / 1e6;
3811
- const inputCost2 = (usage.input_tokens ?? 0) * fallbackInputRate;
3812
- const outputCost2 = (usage.output_tokens ?? 0) * fallbackOutputRate;
3813
- const cacheReadCost2 = (usage.cache_read_input_tokens ?? 0) * (0.5 / 1e6);
3814
- const cacheCreationCost2 = (usage.cache_creation_input_tokens ?? 0) * (2 / 1e6);
3815
- return inputCost2 + outputCost2 + cacheReadCost2 + cacheCreationCost2;
3816
- }
3817
- const inputRate = pricing.input / 1e6;
3818
- const outputRate = pricing.output / 1e6;
3819
- const cacheReadRate = (pricing.cacheRead ?? pricing.input * 0.1) / 1e6;
3820
- const cacheCreationRate = (pricing.cacheCreation ?? pricing.input * 0.33) / 1e6;
3821
- const inputCost = (usage.input_tokens ?? 0) * inputRate;
3822
- const outputCost = (usage.output_tokens ?? 0) * outputRate;
3823
- const cacheReadCost = (usage.cache_read_input_tokens ?? 0) * cacheReadRate;
3824
- const cacheCreationCost = (usage.cache_creation_input_tokens ?? 0) * cacheCreationRate;
3825
- return inputCost + outputCost + cacheReadCost + cacheCreationCost;
3826
- }
3827
- var init_calculate = __esm(() => {
3828
- init_pricing();
3829
- });
3830
-
3831
3831
  // src/agents/cost/index.ts
3832
3832
  var init_cost = __esm(() => {
3833
3833
  init_pricing();
@@ -18818,7 +18818,16 @@ class ClaudeCodeAdapter {
18818
18818
  }
18819
18819
  }
18820
18820
  async complete(prompt, options) {
18821
- 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
+ };
18822
18831
  }
18823
18832
  async plan(options) {
18824
18833
  const pidRegistry = this.getPidRegistry(options.workdir);
@@ -18896,6 +18905,7 @@ var init_adapter = __esm(() => {
18896
18905
  init_timeout_handler();
18897
18906
  init_logger2();
18898
18907
  init_bun_deps();
18908
+ init_calculate();
18899
18909
  init_decompose();
18900
18910
  init_complete();
18901
18911
  init_execution();
@@ -19411,7 +19421,7 @@ async function refineAcceptanceCriteria(criteria, context) {
19411
19421
  const prompt = buildRefinementPrompt(criteria, codebaseContext, { testStrategy, testFramework });
19412
19422
  let response;
19413
19423
  try {
19414
- response = await _refineDeps.adapter.complete(prompt, {
19424
+ const completeResult = await _refineDeps.adapter.complete(prompt, {
19415
19425
  jsonMode: true,
19416
19426
  maxTokens: 4096,
19417
19427
  model: modelDef.model,
@@ -19421,6 +19431,7 @@ async function refineAcceptanceCriteria(criteria, context) {
19421
19431
  workdir,
19422
19432
  sessionRole: "refine"
19423
19433
  });
19434
+ response = typeof completeResult === "string" ? completeResult : completeResult.output;
19424
19435
  } catch (error48) {
19425
19436
  const reason = errorMessage(error48);
19426
19437
  logger.warn("refinement", "adapter.complete() failed, falling back to original criteria", {
@@ -19562,7 +19573,7 @@ Rules:
19562
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).`;
19563
19574
  const prompt = basePrompt;
19564
19575
  logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
19565
- const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
19576
+ const completeResult = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
19566
19577
  model: options.modelDef.model,
19567
19578
  config: options.config,
19568
19579
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
@@ -19570,6 +19581,7 @@ Rules:
19570
19581
  featureName: options.featureName,
19571
19582
  sessionRole: "acceptance-gen"
19572
19583
  });
19584
+ const rawOutput = typeof completeResult === "string" ? completeResult : completeResult.output;
19573
19585
  let testCode = extractTestCode(rawOutput);
19574
19586
  logger.debug("acceptance", "Received raw output from LLM", {
19575
19587
  hasCode: testCode !== null,
@@ -19706,7 +19718,7 @@ async function generateAcceptanceTests(adapter, options) {
19706
19718
  logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
19707
19719
  const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
19708
19720
  try {
19709
- const output = await adapter.complete(prompt, {
19721
+ const completeResult = await adapter.complete(prompt, {
19710
19722
  model: options.modelDef.model,
19711
19723
  config: options.config,
19712
19724
  timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
@@ -19714,6 +19726,7 @@ async function generateAcceptanceTests(adapter, options) {
19714
19726
  featureName: options.featureName,
19715
19727
  sessionRole: "acceptance-gen"
19716
19728
  });
19729
+ const output = typeof completeResult === "string" ? completeResult : completeResult.output;
19717
19730
  const testCode = extractTestCode(output);
19718
19731
  if (!testCode) {
19719
19732
  logger.warn("acceptance", "LLM returned non-code output for acceptance tests \u2014 falling back to skeleton", {
@@ -19961,12 +19974,13 @@ async function generateFixStories(adapter, options) {
19961
19974
  const relatedStory = prd.userStories.find((s) => relatedStories.includes(s.id) && s.workdir);
19962
19975
  const workdir = relatedStory?.workdir;
19963
19976
  try {
19964
- const fixDescription = await adapter.complete(prompt, {
19977
+ const fixResult = await adapter.complete(prompt, {
19965
19978
  model: modelDef.model,
19966
19979
  config: options.config,
19967
19980
  featureName: options.prd.feature,
19968
19981
  workdir: options.workdir,
19969
- sessionRole: "fix-gen"
19982
+ sessionRole: "fix-gen",
19983
+ timeoutMs: options.timeoutMs ?? options.config?.acceptance?.timeoutMs ?? 1800000
19970
19984
  });
19971
19985
  fixStories.push({
19972
19986
  id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
@@ -19975,7 +19989,7 @@ async function generateFixStories(adapter, options) {
19975
19989
  batchedACs,
19976
19990
  testOutput,
19977
19991
  relatedStories,
19978
- description: fixDescription,
19992
+ description: typeof fixResult === "string" ? fixResult : fixResult.output,
19979
19993
  testFilePath,
19980
19994
  workdir
19981
19995
  });
@@ -20894,8 +20908,24 @@ class AcpAgentAdapter {
20894
20908
  }
20895
20909
  if (response.exactCostUsd !== undefined) {
20896
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
+ };
20897
20923
  }
20898
- return unwrapped;
20924
+ return {
20925
+ output: unwrapped,
20926
+ costUsd: 0,
20927
+ source: "fallback"
20928
+ };
20899
20929
  } catch (err) {
20900
20930
  hadError = true;
20901
20931
  throw err;
@@ -21022,13 +21052,14 @@ class AcpAgentAdapter {
21022
21052
  const prompt = buildDecomposePrompt(options);
21023
21053
  let output;
21024
21054
  try {
21025
- output = await this.complete(prompt, {
21055
+ const completeResult = await this.complete(prompt, {
21026
21056
  model,
21027
21057
  jsonMode: true,
21028
21058
  config: options.config,
21029
21059
  workdir: options.workdir,
21030
21060
  sessionRole: "decompose"
21031
21061
  });
21062
+ output = completeResult.output;
21032
21063
  } catch (err) {
21033
21064
  const msg = err instanceof Error ? err.message : String(err);
21034
21065
  throw new Error(`[acp-adapter] decompose() failed: ${msg}`, { cause: err });
@@ -21175,7 +21206,11 @@ class AiderAdapter {
21175
21206
  if (!trimmed) {
21176
21207
  throw new CompleteError("complete() returned empty output");
21177
21208
  }
21178
- return trimmed;
21209
+ return {
21210
+ output: trimmed,
21211
+ costUsd: 0,
21212
+ source: "fallback"
21213
+ };
21179
21214
  }
21180
21215
  async plan(_options) {
21181
21216
  throw new Error("AiderAdapter.plan() not implemented");
@@ -21247,7 +21282,11 @@ class CodexAdapter {
21247
21282
  if (!trimmed) {
21248
21283
  throw new CompleteError("complete() returned empty output");
21249
21284
  }
21250
- return trimmed;
21285
+ return {
21286
+ output: trimmed,
21287
+ costUsd: 0,
21288
+ source: "fallback"
21289
+ };
21251
21290
  }
21252
21291
  async plan(_options) {
21253
21292
  throw new Error("CodexAdapter.plan() not implemented");
@@ -21342,7 +21381,11 @@ class GeminiAdapter {
21342
21381
  if (!trimmed) {
21343
21382
  throw new CompleteError("complete() returned empty output");
21344
21383
  }
21345
- return trimmed;
21384
+ return {
21385
+ output: trimmed,
21386
+ costUsd: 0,
21387
+ source: "fallback"
21388
+ };
21346
21389
  }
21347
21390
  async plan(_options) {
21348
21391
  throw new Error("GeminiAdapter.plan() not implemented");
@@ -21399,7 +21442,11 @@ class OpenCodeAdapter {
21399
21442
  if (!trimmed) {
21400
21443
  throw new CompleteError("complete() returned empty output");
21401
21444
  }
21402
- return trimmed;
21445
+ return {
21446
+ output: trimmed,
21447
+ costUsd: 0,
21448
+ source: "fallback"
21449
+ };
21403
21450
  }
21404
21451
  async plan(_options) {
21405
21452
  throw new Error("OpenCodeAdapter.plan() not implemented");
@@ -21705,7 +21752,7 @@ async function callLlmOnce(adapter, modelTier, prompt, config2, timeoutMs) {
21705
21752
  try {
21706
21753
  const result = await Promise.race([outputPromise, timeoutPromise]);
21707
21754
  clearTimeout(timeoutId);
21708
- return result;
21755
+ return typeof result === "string" ? result : result.output;
21709
21756
  } catch (err) {
21710
21757
  clearTimeout(timeoutId);
21711
21758
  outputPromise.catch(() => {});
@@ -22067,7 +22114,7 @@ var package_default;
22067
22114
  var init_package = __esm(() => {
22068
22115
  package_default = {
22069
22116
  name: "@nathapp/nax",
22070
- version: "0.56.3",
22117
+ version: "0.56.4",
22071
22118
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22072
22119
  type: "module",
22073
22120
  bin: {
@@ -22079,7 +22126,7 @@ var init_package = __esm(() => {
22079
22126
  build: 'bun build bin/nax.ts --outdir dist --target bun --define "GIT_COMMIT=\\"$(git rev-parse --short HEAD)\\""',
22080
22127
  typecheck: "bun x tsc --noEmit",
22081
22128
  lint: "bun x biome check src/ bin/",
22082
- "lint:fix": "bun x biome lint --write src/ bin/",
22129
+ "lint:fix": "bun x biome check --write src/ bin/",
22083
22130
  release: "bun scripts/release.ts",
22084
22131
  test: "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
22085
22132
  "test:bail": "bun test test/unit/ --timeout=60000 --bail && bun test test/integration/ --timeout=60000 --bail && bun test test/ui/ --timeout=60000 --bail",
@@ -22146,8 +22193,8 @@ var init_version = __esm(() => {
22146
22193
  NAX_VERSION = package_default.version;
22147
22194
  NAX_COMMIT = (() => {
22148
22195
  try {
22149
- if (/^[0-9a-f]{6,10}$/.test("52dcc35f"))
22150
- return "52dcc35f";
22196
+ if (/^[0-9a-f]{6,10}$/.test("17df843d"))
22197
+ return "17df843d";
22151
22198
  } catch {}
22152
22199
  try {
22153
22200
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -22400,6 +22447,7 @@ var DEFAULT_FALLBACK_AGENT = "claude";
22400
22447
  var init_resolvers = () => {};
22401
22448
 
22402
22449
  // src/debate/session.ts
22450
+ import { join as join11 } from "path";
22403
22451
  function resolveDebaterModel(debater, config2) {
22404
22452
  const tier = debater.model ?? "fast";
22405
22453
  if (!config2?.models)
@@ -22429,6 +22477,25 @@ function extractSessionOutput(response) {
22429
22477
  const last = [...messages].reverse().find((m) => m.role === "assistant");
22430
22478
  return last?.content ?? "";
22431
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
+ }
22432
22499
 
22433
22500
  class DebateSession {
22434
22501
  storyId;
@@ -22452,10 +22519,10 @@ class DebateSession {
22452
22519
  const logger = _debateSessionDeps.getSafeLogger();
22453
22520
  const config2 = this.stageConfig;
22454
22521
  const debaters = config2.debaters ?? [];
22455
- const totalCostUsd = 0;
22522
+ let totalCostUsd = 0;
22456
22523
  const resolved = [];
22457
22524
  for (const debater of debaters) {
22458
- const adapter = _debateSessionDeps.getAgent(debater.agent);
22525
+ const adapter = _debateSessionDeps.getAgent(debater.agent, this.config);
22459
22526
  if (!adapter) {
22460
22527
  logger?.warn("debate", `Agent '${debater.agent}' not found \u2014 skipping debater`);
22461
22528
  continue;
@@ -22494,7 +22561,9 @@ class DebateSession {
22494
22561
  reason: "only 1 session created"
22495
22562
  });
22496
22563
  const solo = sessions[0];
22564
+ const soloStart = Date.now();
22497
22565
  const response = await solo.session.prompt(prompt);
22566
+ totalCostUsd += sessionResponseCostUsd(response, modelTierFromDebater(solo.debater), Date.now() - soloStart);
22498
22567
  const output = extractSessionOutput(response);
22499
22568
  logger?.info("debate", "debate:result", {
22500
22569
  storyId: this.storyId,
@@ -22519,16 +22588,27 @@ class DebateSession {
22519
22588
  });
22520
22589
  return buildFailedResult(this.storyId, this.stage, config2, totalCostUsd);
22521
22590
  }
22522
- 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
+ }));
22523
22601
  const successfulSessions = [];
22524
22602
  for (let i = 0;i < proposalSettled.length; i++) {
22525
22603
  const r = proposalSettled[i];
22526
22604
  if (r.status === "fulfilled") {
22527
22605
  successfulSessions.push({
22528
- entry: sessions[i],
22529
- output: extractSessionOutput(r.value),
22606
+ entry: r.value.entry,
22607
+ output: r.value.output,
22608
+ cost: r.value.cost,
22530
22609
  originalIndex: i
22531
22610
  });
22611
+ totalCostUsd += r.value.cost;
22532
22612
  }
22533
22613
  }
22534
22614
  if (successfulSessions.length < 2) {
@@ -22551,17 +22631,28 @@ class DebateSession {
22551
22631
  let critiqueOutputs = [];
22552
22632
  if (config2.rounds > 1) {
22553
22633
  const proposalOutputs2 = successfulSessions.map((s) => s.output);
22554
- const critiqueSettled = await Promise.allSettled(successfulSessions.map(({ entry }, successfulIdx) => entry.session.prompt(buildCritiquePrompt(prompt, proposalOutputs2, successfulIdx))));
22555
- 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
+ });
22556
22646
  }
22557
22647
  const proposalOutputs = successfulSessions.map((s) => s.output);
22558
22648
  const successfulProposals = successfulSessions.map((s) => ({
22559
22649
  debater: s.entry.debater,
22560
22650
  adapter: s.entry.adapter,
22561
22651
  output: s.output,
22562
- cost: 0
22652
+ cost: s.cost
22563
22653
  }));
22564
22654
  const outcome = await this.resolve(proposalOutputs, critiqueOutputs, successfulProposals);
22655
+ totalCostUsd += outcome.resolverCostUsd;
22565
22656
  const proposals = successfulSessions.map((s) => ({
22566
22657
  debater: s.entry.debater,
22567
22658
  output: s.output
@@ -22569,12 +22660,12 @@ class DebateSession {
22569
22660
  logger?.info("debate", "debate:result", {
22570
22661
  storyId: this.storyId,
22571
22662
  stage: this.stage,
22572
- outcome
22663
+ outcome: outcome.outcome
22573
22664
  });
22574
22665
  return {
22575
22666
  storyId: this.storyId,
22576
22667
  stage: this.stage,
22577
- outcome,
22668
+ outcome: outcome.outcome,
22578
22669
  rounds: config2.rounds,
22579
22670
  debaters: successfulSessions.map((s) => s.entry.debater.agent),
22580
22671
  resolverType: config2.resolver.type,
@@ -22592,7 +22683,7 @@ class DebateSession {
22592
22683
  let totalCostUsd = 0;
22593
22684
  const resolved = [];
22594
22685
  for (const debater of debaters) {
22595
- const adapter = _debateSessionDeps.getAgent(debater.agent);
22686
+ const adapter = _debateSessionDeps.getAgent(debater.agent, this.config);
22596
22687
  if (!adapter) {
22597
22688
  logger?.warn("debate", `Agent '${debater.agent}' not found \u2014 skipping debater`);
22598
22689
  continue;
@@ -22604,7 +22695,7 @@ class DebateSession {
22604
22695
  stage: this.stage,
22605
22696
  debaters: resolved.map((r) => r.debater.agent)
22606
22697
  });
22607
- 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 }))));
22608
22699
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22609
22700
  for (const r of proposalSettled) {
22610
22701
  if (r.status === "fulfilled") {
@@ -22652,9 +22743,8 @@ class DebateSession {
22652
22743
  reason: "all debaters failed \u2014 retrying with first adapter"
22653
22744
  });
22654
22745
  try {
22655
- const fallbackOutput = await fallbackAdapter.complete(prompt, {
22656
- model: resolveDebaterModel(fallbackDebater, this.config)
22657
- });
22746
+ const fallbackResult = await runComplete(fallbackAdapter, prompt, { model: resolveDebaterModel(fallbackDebater, this.config) }, modelTierFromDebater(fallbackDebater));
22747
+ totalCostUsd += fallbackResult.costUsd;
22658
22748
  logger?.info("debate", "debate:result", {
22659
22749
  storyId: this.storyId,
22660
22750
  stage: this.stage,
@@ -22667,7 +22757,7 @@ class DebateSession {
22667
22757
  rounds: 1,
22668
22758
  debaters: [fallbackDebater.agent],
22669
22759
  resolverType: config2.resolver.type,
22670
- proposals: [{ debater: fallbackDebater, output: fallbackOutput }],
22760
+ proposals: [{ debater: fallbackDebater, output: fallbackResult.output }],
22671
22761
  totalCostUsd
22672
22762
  };
22673
22763
  } catch {}
@@ -22677,19 +22767,17 @@ class DebateSession {
22677
22767
  let critiqueOutputs = [];
22678
22768
  if (config2.rounds > 1) {
22679
22769
  const proposalOutputs2 = successful.map((p) => p.output);
22680
- const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => adapter.complete(buildCritiquePrompt(prompt, proposalOutputs2, i), {
22681
- model: resolveDebaterModel(debater, this.config)
22682
- })));
22770
+ const critiqueSettled = await Promise.allSettled(successful.map(({ debater, adapter }, i) => runComplete(adapter, buildCritiquePrompt(prompt, proposalOutputs2, i), { model: resolveDebaterModel(debater, this.config) }, modelTierFromDebater(debater))));
22683
22771
  for (const r of critiqueSettled) {
22684
22772
  if (r.status === "fulfilled") {
22685
- totalCostUsd += 0;
22773
+ totalCostUsd += r.value.costUsd;
22686
22774
  }
22687
22775
  }
22688
- critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
22776
+ critiqueOutputs = critiqueSettled.filter((r) => r.status === "fulfilled").map((r) => r.value.output);
22689
22777
  }
22690
22778
  const proposalOutputs = successful.map((p) => p.output);
22691
22779
  const outcome = await this.resolve(proposalOutputs, critiqueOutputs, successful);
22692
- totalCostUsd += 0;
22780
+ totalCostUsd += outcome.resolverCostUsd;
22693
22781
  const proposals = successful.map((p) => ({
22694
22782
  debater: p.debater,
22695
22783
  output: p.output
@@ -22697,12 +22785,12 @@ class DebateSession {
22697
22785
  logger?.info("debate", "debate:result", {
22698
22786
  storyId: this.storyId,
22699
22787
  stage: this.stage,
22700
- outcome
22788
+ outcome: outcome.outcome
22701
22789
  });
22702
22790
  return {
22703
22791
  storyId: this.storyId,
22704
22792
  stage: this.stage,
22705
- outcome,
22793
+ outcome: outcome.outcome,
22706
22794
  rounds: config2.rounds,
22707
22795
  debaters: successful.map((p) => p.debater.agent),
22708
22796
  resolverType: config2.resolver.type,
@@ -22710,40 +22798,151 @@ class DebateSession {
22710
22798
  totalCostUsd
22711
22799
  };
22712
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
+ }
22713
22894
  async resolve(proposalOutputs, critiqueOutputs, _successful) {
22714
22895
  const resolverConfig = this.stageConfig.resolver;
22715
22896
  if (resolverConfig.type === "majority-fail-closed" || resolverConfig.type === "majority-fail-open") {
22716
- return majorityResolver(proposalOutputs, resolverConfig.type === "majority-fail-open");
22897
+ return {
22898
+ outcome: majorityResolver(proposalOutputs, resolverConfig.type === "majority-fail-open"),
22899
+ resolverCostUsd: 0
22900
+ };
22717
22901
  }
22718
22902
  if (resolverConfig.type === "synthesis") {
22719
22903
  const agentName = resolverConfig.agent ?? RESOLVER_FALLBACK_AGENT;
22720
- const adapter = _debateSessionDeps.getAgent(agentName);
22904
+ const adapter = _debateSessionDeps.getAgent(agentName, this.config);
22721
22905
  if (adapter) {
22722
- await synthesisResolver(proposalOutputs, critiqueOutputs, { adapter });
22906
+ const resolverResult = await synthesisResolver(proposalOutputs, critiqueOutputs, { adapter });
22907
+ return {
22908
+ outcome: "passed",
22909
+ resolverCostUsd: resolverResult.costUsd
22910
+ };
22723
22911
  }
22724
- return "passed";
22912
+ return {
22913
+ outcome: "passed",
22914
+ resolverCostUsd: 0
22915
+ };
22725
22916
  }
22726
22917
  if (resolverConfig.type === "custom") {
22727
- await judgeResolver(proposalOutputs, critiqueOutputs, resolverConfig, {
22728
- getAgent: _debateSessionDeps.getAgent,
22918
+ const resolverResult = await judgeResolver(proposalOutputs, critiqueOutputs, resolverConfig, {
22919
+ getAgent: (name) => _debateSessionDeps.getAgent(name, this.config),
22729
22920
  defaultAgentName: RESOLVER_FALLBACK_AGENT
22730
22921
  });
22731
- return "passed";
22922
+ return {
22923
+ outcome: "passed",
22924
+ resolverCostUsd: resolverResult.costUsd
22925
+ };
22732
22926
  }
22733
- return "passed";
22927
+ return {
22928
+ outcome: "passed",
22929
+ resolverCostUsd: 0
22930
+ };
22734
22931
  }
22735
22932
  }
22736
22933
  var RESOLVER_FALLBACK_AGENT = "synthesis", _debateSessionDeps;
22737
22934
  var init_session = __esm(() => {
22738
22935
  init_spawn_client();
22936
+ init_calculate();
22739
22937
  init_registry();
22740
22938
  init_config();
22741
22939
  init_logger2();
22742
22940
  init_resolvers();
22743
22941
  _debateSessionDeps = {
22744
- getAgent,
22942
+ getAgent: (name, config2) => config2 ? createAgentRegistry(config2).getAgent(name) : getAgent(name),
22745
22943
  getSafeLogger,
22746
- createSpawnAcpClient: (cmdStr, cwd) => createSpawnAcpClient(cmdStr, cwd)
22944
+ createSpawnAcpClient: (cmdStr, cwd) => createSpawnAcpClient(cmdStr, cwd),
22945
+ readFile: (path) => Bun.file(path).text()
22747
22946
  };
22748
22947
  });
22749
22948
 
@@ -22938,7 +23137,7 @@ class AutoInteractionPlugin {
22938
23137
  const modelDef = resolveModelForAgent(naxConfig.models, naxConfig.autoMode.defaultAgent, modelTier, naxConfig.autoMode.defaultAgent);
22939
23138
  modelArg = modelDef.model;
22940
23139
  }
22941
- const output = await adapter.complete(prompt, {
23140
+ const result = await adapter.complete(prompt, {
22942
23141
  ...modelArg && { model: modelArg },
22943
23142
  jsonMode: true,
22944
23143
  ...this.config.naxConfig && { config: this.config.naxConfig },
@@ -22946,6 +23145,7 @@ class AutoInteractionPlugin {
22946
23145
  storyId: request.storyId,
22947
23146
  sessionRole: "auto"
22948
23147
  });
23148
+ const output = typeof result === "string" ? result : result.output;
22949
23149
  return this.parseResponse(output);
22950
23150
  }
22951
23151
  buildPrompt(request) {
@@ -26452,13 +26652,14 @@ ${formatFindings(deduped)}`,
26452
26652
  }
26453
26653
  let rawResponse;
26454
26654
  try {
26455
- rawResponse = await agent.complete(prompt, {
26655
+ const completeResult = await agent.complete(prompt, {
26456
26656
  sessionName: `nax-semantic-${story.id}`,
26457
26657
  workdir,
26458
26658
  timeoutMs: semanticConfig.timeoutMs,
26459
26659
  modelTier: semanticConfig.modelTier,
26460
26660
  config: naxConfig
26461
26661
  });
26662
+ rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
26462
26663
  } catch (err) {
26463
26664
  logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
26464
26665
  return {
@@ -26852,7 +27053,7 @@ __export(exports_review, {
26852
27053
  reviewStage: () => reviewStage,
26853
27054
  _reviewDeps: () => _reviewDeps
26854
27055
  });
26855
- import { join as join16 } from "path";
27056
+ import { join as join17 } from "path";
26856
27057
  var reviewStage, _reviewDeps;
26857
27058
  var init_review = __esm(() => {
26858
27059
  init_agents();
@@ -26866,7 +27067,7 @@ var init_review = __esm(() => {
26866
27067
  const logger = getLogger();
26867
27068
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
26868
27069
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
26869
- 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;
26870
27071
  const agentResolver = ctx.agentGetFn ?? getAgent;
26871
27072
  const agentName = effectiveConfig.autoMode?.defaultAgent;
26872
27073
  const modelResolver = (_tier) => agentName ? agentResolver(agentName) ?? null : null;
@@ -26918,7 +27119,7 @@ var init_review = __esm(() => {
26918
27119
  });
26919
27120
 
26920
27121
  // src/pipeline/stages/autofix.ts
26921
- import { join as join17 } from "path";
27122
+ import { join as join18 } from "path";
26922
27123
  async function recheckReview(ctx) {
26923
27124
  const { reviewStage: reviewStage2 } = await Promise.resolve().then(() => (init_review(), exports_review));
26924
27125
  if (!reviewStage2.enabled(ctx))
@@ -26991,7 +27192,7 @@ async function runAgentRectification(ctx) {
26991
27192
  const prompt = buildReviewRectificationPrompt(failedChecks, ctx.story);
26992
27193
  const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
26993
27194
  const modelDef = resolveModelForAgent(ctx.config.models, ctx.routing.agent ?? ctx.config.autoMode.defaultAgent, modelTier, ctx.config.autoMode.defaultAgent);
26994
- 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;
26995
27196
  await agent.run({
26996
27197
  prompt,
26997
27198
  workdir: rectificationWorkdir,
@@ -27055,7 +27256,7 @@ var init_autofix = __esm(() => {
27055
27256
  const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
27056
27257
  const lintFixCmd = effectiveConfig.quality.commands.lintFix;
27057
27258
  const formatFixCmd = effectiveConfig.quality.commands.formatFix;
27058
- 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;
27059
27260
  const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
27060
27261
  const hasLintFailure = failedCheckNames.has("lint");
27061
27262
  logger.info("autofix", "Starting autofix", {
@@ -27139,10 +27340,10 @@ var init_autofix = __esm(() => {
27139
27340
 
27140
27341
  // src/execution/progress.ts
27141
27342
  import { appendFile as appendFile2, mkdir } from "fs/promises";
27142
- import { join as join18 } from "path";
27343
+ import { join as join19 } from "path";
27143
27344
  async function appendProgress(featureDir, storyId, status, message) {
27144
27345
  await mkdir(featureDir, { recursive: true });
27145
- const progressPath = join18(featureDir, "progress.txt");
27346
+ const progressPath = join19(featureDir, "progress.txt");
27146
27347
  const timestamp = new Date().toISOString();
27147
27348
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
27148
27349
  `;
@@ -27225,7 +27426,7 @@ function estimateTokens(text) {
27225
27426
 
27226
27427
  // src/constitution/loader.ts
27227
27428
  import { existsSync as existsSync19 } from "fs";
27228
- import { join as join19 } from "path";
27429
+ import { join as join20 } from "path";
27229
27430
  function truncateToTokens(text, maxTokens) {
27230
27431
  const maxChars = maxTokens * 3;
27231
27432
  if (text.length <= maxChars) {
@@ -27247,7 +27448,7 @@ async function loadConstitution(projectDir, config2) {
27247
27448
  }
27248
27449
  let combinedContent = "";
27249
27450
  if (!config2.skipGlobal) {
27250
- const globalPath = join19(globalConfigDir(), config2.path);
27451
+ const globalPath = join20(globalConfigDir(), config2.path);
27251
27452
  if (existsSync19(globalPath)) {
27252
27453
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
27253
27454
  const globalFile = Bun.file(validatedPath);
@@ -27257,7 +27458,7 @@ async function loadConstitution(projectDir, config2) {
27257
27458
  }
27258
27459
  }
27259
27460
  }
27260
- const projectPath = join19(projectDir, config2.path);
27461
+ const projectPath = join20(projectDir, config2.path);
27261
27462
  if (existsSync19(projectPath)) {
27262
27463
  const validatedPath = validateFilePath(projectPath, projectDir);
27263
27464
  const projectFile = Bun.file(validatedPath);
@@ -28285,7 +28486,7 @@ var init_helpers = __esm(() => {
28285
28486
  });
28286
28487
 
28287
28488
  // src/pipeline/stages/context.ts
28288
- import { join as join20 } from "path";
28489
+ import { join as join21 } from "path";
28289
28490
  var contextStage;
28290
28491
  var init_context2 = __esm(() => {
28291
28492
  init_helpers();
@@ -28295,7 +28496,7 @@ var init_context2 = __esm(() => {
28295
28496
  enabled: () => true,
28296
28497
  async execute(ctx) {
28297
28498
  const logger = getLogger();
28298
- 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;
28299
28500
  const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
28300
28501
  if (result) {
28301
28502
  ctx.contextMarkdown = result.markdown;
@@ -28429,14 +28630,14 @@ var init_isolation = __esm(() => {
28429
28630
 
28430
28631
  // src/context/greenfield.ts
28431
28632
  import { readdir } from "fs/promises";
28432
- import { join as join21 } from "path";
28633
+ import { join as join22 } from "path";
28433
28634
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
28434
28635
  const results = [];
28435
28636
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
28436
28637
  try {
28437
28638
  const entries = await readdir(dir, { withFileTypes: true });
28438
28639
  for (const entry of entries) {
28439
- const fullPath = join21(dir, entry.name);
28640
+ const fullPath = join22(dir, entry.name);
28440
28641
  if (entry.isDirectory()) {
28441
28642
  if (ignoreDirs.has(entry.name))
28442
28643
  continue;
@@ -28791,13 +28992,13 @@ function parseTestOutput(output, exitCode) {
28791
28992
 
28792
28993
  // src/verification/runners.ts
28793
28994
  import { existsSync as existsSync20 } from "fs";
28794
- import { join as join22 } from "path";
28995
+ import { join as join23 } from "path";
28795
28996
  async function verifyAssets(workingDirectory, expectedFiles) {
28796
28997
  if (!expectedFiles || expectedFiles.length === 0)
28797
28998
  return { success: true, missingFiles: [] };
28798
28999
  const missingFiles = [];
28799
29000
  for (const file3 of expectedFiles) {
28800
- if (!existsSync20(join22(workingDirectory, file3)))
29001
+ if (!existsSync20(join23(workingDirectory, file3)))
28801
29002
  missingFiles.push(file3);
28802
29003
  }
28803
29004
  if (missingFiles.length > 0) {
@@ -29691,13 +29892,13 @@ var exports_loader = {};
29691
29892
  __export(exports_loader, {
29692
29893
  loadOverride: () => loadOverride
29693
29894
  });
29694
- import { join as join23 } from "path";
29895
+ import { join as join24 } from "path";
29695
29896
  async function loadOverride(role, workdir, config2) {
29696
29897
  const overridePath = config2.prompts?.overrides?.[role];
29697
29898
  if (!overridePath) {
29698
29899
  return null;
29699
29900
  }
29700
- const absolutePath = join23(workdir, overridePath);
29901
+ const absolutePath = join24(workdir, overridePath);
29701
29902
  const file3 = Bun.file(absolutePath);
29702
29903
  if (!await file3.exists()) {
29703
29904
  return null;
@@ -30556,11 +30757,11 @@ var init_tdd = __esm(() => {
30556
30757
 
30557
30758
  // src/pipeline/stages/execution.ts
30558
30759
  import { existsSync as existsSync21 } from "fs";
30559
- import { join as join24 } from "path";
30760
+ import { join as join25 } from "path";
30560
30761
  function resolveStoryWorkdir(repoRoot, storyWorkdir) {
30561
30762
  if (!storyWorkdir)
30562
30763
  return repoRoot;
30563
- const resolved = join24(repoRoot, storyWorkdir);
30764
+ const resolved = join25(repoRoot, storyWorkdir);
30564
30765
  if (!existsSync21(resolved)) {
30565
30766
  throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
30566
30767
  }
@@ -31231,7 +31432,7 @@ async function _defaultRunDebate(storyId, stageConfig, prompt) {
31231
31432
  return { output: null, totalCostUsd: 0 };
31232
31433
  }
31233
31434
  const startMs = Date.now();
31234
- 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)));
31235
31436
  const durationMs = Date.now() - startMs;
31236
31437
  const successful = proposalSettled.filter((r) => r.status === "fulfilled").map((r) => r.value);
31237
31438
  if (successful.length === 0) {
@@ -32123,7 +32324,7 @@ var init_regression2 = __esm(() => {
32123
32324
  });
32124
32325
 
32125
32326
  // src/pipeline/stages/routing.ts
32126
- import { join as join25 } from "path";
32327
+ import { join as join26 } from "path";
32127
32328
  var routingStage, _routingDeps;
32128
32329
  var init_routing2 = __esm(() => {
32129
32330
  init_registry();
@@ -32160,7 +32361,7 @@ var init_routing2 = __esm(() => {
32160
32361
  }
32161
32362
  const greenfieldDetectionEnabled = effectiveConfig.tdd.greenfieldDetection ?? true;
32162
32363
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
32163
- 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;
32164
32365
  const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
32165
32366
  if (isGreenfield) {
32166
32367
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
@@ -32212,7 +32413,7 @@ var init_crash_detector = __esm(() => {
32212
32413
  });
32213
32414
 
32214
32415
  // src/pipeline/stages/verify.ts
32215
- import { join as join26 } from "path";
32416
+ import { join as join27 } from "path";
32216
32417
  function coerceSmartTestRunner(val) {
32217
32418
  if (val === undefined || val === true)
32218
32419
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -32228,7 +32429,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
32228
32429
  }
32229
32430
  async function readPackageName(dir) {
32230
32431
  try {
32231
- const content = await Bun.file(join26(dir, "package.json")).json();
32432
+ const content = await Bun.file(join27(dir, "package.json")).json();
32232
32433
  return typeof content.name === "string" ? content.name : null;
32233
32434
  } catch {
32234
32435
  return null;
@@ -32273,7 +32474,7 @@ var init_verify = __esm(() => {
32273
32474
  return { action: "continue" };
32274
32475
  }
32275
32476
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
32276
- 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;
32277
32478
  let effectiveCommand = testCommand;
32278
32479
  let isFullSuite = true;
32279
32480
  const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
@@ -32491,7 +32692,7 @@ __export(exports_init_context, {
32491
32692
  });
32492
32693
  import { existsSync as existsSync24 } from "fs";
32493
32694
  import { mkdir as mkdir2 } from "fs/promises";
32494
- import { basename as basename3, join as join30 } from "path";
32695
+ import { basename as basename3, join as join31 } from "path";
32495
32696
  async function findFiles(dir, maxFiles = 200) {
32496
32697
  try {
32497
32698
  const proc = Bun.spawnSync([
@@ -32519,7 +32720,7 @@ async function findFiles(dir, maxFiles = 200) {
32519
32720
  return [];
32520
32721
  }
32521
32722
  async function readPackageManifest(projectRoot) {
32522
- const packageJsonPath = join30(projectRoot, "package.json");
32723
+ const packageJsonPath = join31(projectRoot, "package.json");
32523
32724
  if (!existsSync24(packageJsonPath)) {
32524
32725
  return null;
32525
32726
  }
@@ -32537,7 +32738,7 @@ async function readPackageManifest(projectRoot) {
32537
32738
  }
32538
32739
  }
32539
32740
  async function readReadmeSnippet(projectRoot) {
32540
- const readmePath = join30(projectRoot, "README.md");
32741
+ const readmePath = join31(projectRoot, "README.md");
32541
32742
  if (!existsSync24(readmePath)) {
32542
32743
  return null;
32543
32744
  }
@@ -32555,7 +32756,7 @@ async function detectEntryPoints(projectRoot) {
32555
32756
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
32556
32757
  const found = [];
32557
32758
  for (const candidate of candidates) {
32558
- const path12 = join30(projectRoot, candidate);
32759
+ const path12 = join31(projectRoot, candidate);
32559
32760
  if (existsSync24(path12)) {
32560
32761
  found.push(candidate);
32561
32762
  }
@@ -32566,7 +32767,7 @@ async function detectConfigFiles(projectRoot) {
32566
32767
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
32567
32768
  const found = [];
32568
32769
  for (const candidate of candidates) {
32569
- const path12 = join30(projectRoot, candidate);
32770
+ const path12 = join31(projectRoot, candidate);
32570
32771
  if (existsSync24(path12)) {
32571
32772
  found.push(candidate);
32572
32773
  }
@@ -32727,8 +32928,8 @@ function generatePackageContextTemplate(packagePath) {
32727
32928
  }
32728
32929
  async function initPackage(repoRoot, packagePath, force = false) {
32729
32930
  const logger = getLogger();
32730
- const naxDir = join30(repoRoot, ".nax", "mono", packagePath);
32731
- const contextPath = join30(naxDir, "context.md");
32931
+ const naxDir = join31(repoRoot, ".nax", "mono", packagePath);
32932
+ const contextPath = join31(naxDir, "context.md");
32732
32933
  if (existsSync24(contextPath) && !force) {
32733
32934
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
32734
32935
  return;
@@ -32742,8 +32943,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
32742
32943
  }
32743
32944
  async function initContext(projectRoot, options = {}) {
32744
32945
  const logger = getLogger();
32745
- const naxDir = join30(projectRoot, ".nax");
32746
- const contextPath = join30(naxDir, "context.md");
32946
+ const naxDir = join31(projectRoot, ".nax");
32947
+ const contextPath = join31(naxDir, "context.md");
32747
32948
  if (existsSync24(contextPath) && !options.force) {
32748
32949
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
32749
32950
  return;
@@ -32773,7 +32974,7 @@ var init_init_context = __esm(() => {
32773
32974
 
32774
32975
  // src/utils/path-security.ts
32775
32976
  import { realpathSync as realpathSync3 } from "fs";
32776
- 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";
32777
32978
  function safeRealpathForComparison(p) {
32778
32979
  try {
32779
32980
  return realpathSync3(p);
@@ -32782,7 +32983,7 @@ function safeRealpathForComparison(p) {
32782
32983
  if (parent === p)
32783
32984
  return normalize2(p);
32784
32985
  const resolvedParent = safeRealpathForComparison(parent);
32785
- return join31(resolvedParent, p.split("/").pop() ?? "");
32986
+ return join32(resolvedParent, p.split("/").pop() ?? "");
32786
32987
  }
32787
32988
  }
32788
32989
  function validateModulePath(modulePath, allowedRoots) {
@@ -32800,7 +33001,7 @@ function validateModulePath(modulePath, allowedRoots) {
32800
33001
  } else {
32801
33002
  for (let i = 0;i < allowedRoots.length; i++) {
32802
33003
  const originalRoot = resolve5(allowedRoots[i]);
32803
- const absoluteInput = resolve5(join31(originalRoot, modulePath));
33004
+ const absoluteInput = resolve5(join32(originalRoot, modulePath));
32804
33005
  const resolved = safeRealpathForComparison(absoluteInput);
32805
33006
  const resolvedRoot = resolvedRoots[i];
32806
33007
  if (resolved.startsWith(`${resolvedRoot}/`) || resolved === resolvedRoot) {
@@ -33336,19 +33537,19 @@ var init_loader4 = __esm(() => {
33336
33537
  });
33337
33538
 
33338
33539
  // src/hooks/runner.ts
33339
- import { join as join45 } from "path";
33540
+ import { join as join46 } from "path";
33340
33541
  async function loadHooksConfig(projectDir, globalDir) {
33341
33542
  let globalHooks = { hooks: {} };
33342
33543
  let projectHooks = { hooks: {} };
33343
33544
  let skipGlobal = false;
33344
- const projectPath = join45(projectDir, "hooks.json");
33545
+ const projectPath = join46(projectDir, "hooks.json");
33345
33546
  const projectData = await loadJsonFile(projectPath, "hooks");
33346
33547
  if (projectData) {
33347
33548
  projectHooks = projectData;
33348
33549
  skipGlobal = projectData.skipGlobal ?? false;
33349
33550
  }
33350
33551
  if (!skipGlobal && globalDir) {
33351
- const globalPath = join45(globalDir, "hooks.json");
33552
+ const globalPath = join46(globalDir, "hooks.json");
33352
33553
  const globalData = await loadJsonFile(globalPath, "hooks");
33353
33554
  if (globalData) {
33354
33555
  globalHooks = globalData;
@@ -33793,7 +33994,7 @@ __export(exports_acceptance_loop, {
33793
33994
  isStubTestFile: () => isStubTestFile,
33794
33995
  _acceptanceLoopDeps: () => _acceptanceLoopDeps
33795
33996
  });
33796
- import path14, { join as join46 } from "path";
33997
+ import path14, { join as join47 } from "path";
33797
33998
  function isStubTestFile(content) {
33798
33999
  return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
33799
34000
  }
@@ -33831,7 +34032,8 @@ async function generateAndAddFixStories(ctx, failures, prd) {
33831
34032
  workdir: ctx.workdir,
33832
34033
  modelDef,
33833
34034
  config: ctx.config,
33834
- testFilePath
34035
+ testFilePath,
34036
+ timeoutMs: ctx.config.acceptance?.timeoutMs
33835
34037
  });
33836
34038
  if (fixStories.length === 0) {
33837
34039
  logger?.error("acceptance", "Failed to generate fix stories");
@@ -33855,7 +34057,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
33855
34057
  agent: ctx.config.autoMode.defaultAgent,
33856
34058
  iteration: iterations
33857
34059
  }), ctx.workdir);
33858
- 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;
33859
34061
  const fixContext = {
33860
34062
  config: ctx.config,
33861
34063
  effectiveConfig: fixEffectiveConfig,
@@ -34454,12 +34656,12 @@ var init_headless_formatter = __esm(() => {
34454
34656
  // src/pipeline/subscribers/events-writer.ts
34455
34657
  import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
34456
34658
  import { homedir as homedir5 } from "os";
34457
- import { basename as basename6, join as join47 } from "path";
34659
+ import { basename as basename6, join as join48 } from "path";
34458
34660
  function wireEventsWriter(bus, feature, runId, workdir) {
34459
34661
  const logger = getSafeLogger();
34460
34662
  const project = basename6(workdir);
34461
- const eventsDir = join47(homedir5(), ".nax", "events", project);
34462
- const eventsFile = join47(eventsDir, "events.jsonl");
34663
+ const eventsDir = join48(homedir5(), ".nax", "events", project);
34664
+ const eventsFile = join48(eventsDir, "events.jsonl");
34463
34665
  let dirReady = false;
34464
34666
  const write = (line) => {
34465
34667
  return (async () => {
@@ -34640,12 +34842,12 @@ var init_interaction2 = __esm(() => {
34640
34842
  // src/pipeline/subscribers/registry.ts
34641
34843
  import { mkdir as mkdir4, writeFile } from "fs/promises";
34642
34844
  import { homedir as homedir6 } from "os";
34643
- import { basename as basename7, join as join48 } from "path";
34845
+ import { basename as basename7, join as join49 } from "path";
34644
34846
  function wireRegistry(bus, feature, runId, workdir) {
34645
34847
  const logger = getSafeLogger();
34646
34848
  const project = basename7(workdir);
34647
- const runDir = join48(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
34648
- const metaFile = join48(runDir, "meta.json");
34849
+ const runDir = join49(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
34850
+ const metaFile = join49(runDir, "meta.json");
34649
34851
  const unsub = bus.on("run:started", (_ev) => {
34650
34852
  return (async () => {
34651
34853
  try {
@@ -34655,8 +34857,8 @@ function wireRegistry(bus, feature, runId, workdir) {
34655
34857
  project,
34656
34858
  feature,
34657
34859
  workdir,
34658
- statusPath: join48(workdir, ".nax", "features", feature, "status.json"),
34659
- eventsDir: join48(workdir, ".nax", "features", feature, "runs"),
34860
+ statusPath: join49(workdir, ".nax", "features", feature, "status.json"),
34861
+ eventsDir: join49(workdir, ".nax", "features", feature, "runs"),
34660
34862
  registeredAt: new Date().toISOString()
34661
34863
  };
34662
34864
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -35308,7 +35510,7 @@ var init_pipeline_result_handler = __esm(() => {
35308
35510
  });
35309
35511
 
35310
35512
  // src/execution/iteration-runner.ts
35311
- import { join as join49 } from "path";
35513
+ import { join as join50 } from "path";
35312
35514
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
35313
35515
  const logger = getSafeLogger();
35314
35516
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
@@ -35343,7 +35545,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
35343
35545
  }
35344
35546
  }
35345
35547
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
35346
- 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;
35347
35549
  const pipelineContext = {
35348
35550
  config: ctx.config,
35349
35551
  effectiveConfig,
@@ -35602,13 +35804,13 @@ __export(exports_manager, {
35602
35804
  });
35603
35805
  import { existsSync as existsSync32, symlinkSync } from "fs";
35604
35806
  import { mkdir as mkdir5 } from "fs/promises";
35605
- import { join as join50 } from "path";
35807
+ import { join as join51 } from "path";
35606
35808
 
35607
35809
  class WorktreeManager {
35608
35810
  async ensureGitExcludes(projectRoot) {
35609
35811
  const logger = getSafeLogger();
35610
- const infoDir = join50(projectRoot, ".git", "info");
35611
- const excludePath = join50(infoDir, "exclude");
35812
+ const infoDir = join51(projectRoot, ".git", "info");
35813
+ const excludePath = join51(infoDir, "exclude");
35612
35814
  try {
35613
35815
  await mkdir5(infoDir, { recursive: true });
35614
35816
  let existing = "";
@@ -35635,7 +35837,7 @@ ${missing.join(`
35635
35837
  }
35636
35838
  async create(projectRoot, storyId) {
35637
35839
  validateStoryId(storyId);
35638
- const worktreePath = join50(projectRoot, ".nax-wt", storyId);
35840
+ const worktreePath = join51(projectRoot, ".nax-wt", storyId);
35639
35841
  const branchName = `nax/${storyId}`;
35640
35842
  try {
35641
35843
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -35676,9 +35878,9 @@ ${missing.join(`
35676
35878
  }
35677
35879
  throw new Error(`Failed to create worktree: ${String(error48)}`);
35678
35880
  }
35679
- const nodeModulesSource = join50(projectRoot, "node_modules");
35881
+ const nodeModulesSource = join51(projectRoot, "node_modules");
35680
35882
  if (existsSync32(nodeModulesSource)) {
35681
- const nodeModulesTarget = join50(worktreePath, "node_modules");
35883
+ const nodeModulesTarget = join51(worktreePath, "node_modules");
35682
35884
  try {
35683
35885
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
35684
35886
  } catch (error48) {
@@ -35686,9 +35888,9 @@ ${missing.join(`
35686
35888
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
35687
35889
  }
35688
35890
  }
35689
- const envSource = join50(projectRoot, ".env");
35891
+ const envSource = join51(projectRoot, ".env");
35690
35892
  if (existsSync32(envSource)) {
35691
- const envTarget = join50(worktreePath, ".env");
35893
+ const envTarget = join51(worktreePath, ".env");
35692
35894
  try {
35693
35895
  symlinkSync(envSource, envTarget, "file");
35694
35896
  } catch (error48) {
@@ -35699,7 +35901,7 @@ ${missing.join(`
35699
35901
  }
35700
35902
  async remove(projectRoot, storyId) {
35701
35903
  validateStoryId(storyId);
35702
- const worktreePath = join50(projectRoot, ".nax-wt", storyId);
35904
+ const worktreePath = join51(projectRoot, ".nax-wt", storyId);
35703
35905
  const branchName = `nax/${storyId}`;
35704
35906
  try {
35705
35907
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -36608,16 +36810,16 @@ var init_unified_executor = __esm(() => {
36608
36810
  });
36609
36811
 
36610
36812
  // src/project/detector.ts
36611
- import { join as join51 } from "path";
36813
+ import { join as join52 } from "path";
36612
36814
  async function detectLanguage(workdir, pkg) {
36613
36815
  const deps = _detectorDeps;
36614
- if (await deps.fileExists(join51(workdir, "go.mod")))
36816
+ if (await deps.fileExists(join52(workdir, "go.mod")))
36615
36817
  return "go";
36616
- if (await deps.fileExists(join51(workdir, "Cargo.toml")))
36818
+ if (await deps.fileExists(join52(workdir, "Cargo.toml")))
36617
36819
  return "rust";
36618
- if (await deps.fileExists(join51(workdir, "pyproject.toml")))
36820
+ if (await deps.fileExists(join52(workdir, "pyproject.toml")))
36619
36821
  return "python";
36620
- if (await deps.fileExists(join51(workdir, "requirements.txt")))
36822
+ if (await deps.fileExists(join52(workdir, "requirements.txt")))
36621
36823
  return "python";
36622
36824
  if (pkg != null) {
36623
36825
  const allDeps = {
@@ -36677,18 +36879,18 @@ async function detectLintTool(workdir, language) {
36677
36879
  if (language === "python")
36678
36880
  return "ruff";
36679
36881
  const deps = _detectorDeps;
36680
- if (await deps.fileExists(join51(workdir, "biome.json")))
36882
+ if (await deps.fileExists(join52(workdir, "biome.json")))
36681
36883
  return "biome";
36682
- if (await deps.fileExists(join51(workdir, ".eslintrc")))
36884
+ if (await deps.fileExists(join52(workdir, ".eslintrc")))
36683
36885
  return "eslint";
36684
- if (await deps.fileExists(join51(workdir, ".eslintrc.js")))
36886
+ if (await deps.fileExists(join52(workdir, ".eslintrc.js")))
36685
36887
  return "eslint";
36686
- if (await deps.fileExists(join51(workdir, ".eslintrc.json")))
36888
+ if (await deps.fileExists(join52(workdir, ".eslintrc.json")))
36687
36889
  return "eslint";
36688
36890
  return;
36689
36891
  }
36690
36892
  async function detectProjectProfile(workdir, existing) {
36691
- const pkg = await _detectorDeps.readJson(join51(workdir, "package.json"));
36893
+ const pkg = await _detectorDeps.readJson(join52(workdir, "package.json"));
36692
36894
  const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
36693
36895
  const type = existing.type !== undefined ? existing.type : detectType(pkg);
36694
36896
  const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
@@ -36781,7 +36983,7 @@ async function writeStatusFile(filePath, status) {
36781
36983
  var init_status_file = () => {};
36782
36984
 
36783
36985
  // src/execution/status-writer.ts
36784
- import { join as join52 } from "path";
36986
+ import { join as join53 } from "path";
36785
36987
 
36786
36988
  class StatusWriter {
36787
36989
  statusFile;
@@ -36855,7 +37057,7 @@ class StatusWriter {
36855
37057
  if (!this._prd)
36856
37058
  return;
36857
37059
  const safeLogger = getSafeLogger();
36858
- const featureStatusPath = join52(featureDir, "status.json");
37060
+ const featureStatusPath = join53(featureDir, "status.json");
36859
37061
  const write = async () => {
36860
37062
  try {
36861
37063
  const base = this.getSnapshot(totalCost, iterations);
@@ -37066,7 +37268,7 @@ __export(exports_run_initialization, {
37066
37268
  initializeRun: () => initializeRun,
37067
37269
  _reconcileDeps: () => _reconcileDeps
37068
37270
  });
37069
- import { join as join53 } from "path";
37271
+ import { join as join54 } from "path";
37070
37272
  async function reconcileState(prd, prdPath, workdir, config2) {
37071
37273
  const logger = getSafeLogger();
37072
37274
  let reconciledCount = 0;
@@ -37084,7 +37286,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
37084
37286
  });
37085
37287
  continue;
37086
37288
  }
37087
- const effectiveWorkdir = story.workdir ? join53(workdir, story.workdir) : workdir;
37289
+ const effectiveWorkdir = story.workdir ? join54(workdir, story.workdir) : workdir;
37088
37290
  try {
37089
37291
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
37090
37292
  if (!reviewResult.success) {
@@ -68294,7 +68496,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
68294
68496
  init_source();
68295
68497
  import { existsSync as existsSync34, mkdirSync as mkdirSync5 } from "fs";
68296
68498
  import { homedir as homedir8 } from "os";
68297
- import { join as join55 } from "path";
68499
+ import { join as join56 } from "path";
68298
68500
 
68299
68501
  // node_modules/commander/esm.mjs
68300
68502
  var import__ = __toESM(require_commander(), 1);
@@ -68778,7 +68980,7 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
68778
68980
  // src/cli/plan.ts
68779
68981
  init_registry();
68780
68982
  import { existsSync as existsSync15 } from "fs";
68781
- import { join as join11 } from "path";
68983
+ import { join as join12 } from "path";
68782
68984
  import { createInterface as createInterface2 } from "readline";
68783
68985
  init_test_strategy();
68784
68986
 
@@ -69506,7 +69708,7 @@ var _planDeps = {
69506
69708
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
69507
69709
  scanCodebase: (workdir) => scanCodebase(workdir),
69508
69710
  getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
69509
- readPackageJson: (workdir) => Bun.file(join11(workdir, "package.json")).json().catch(() => null),
69711
+ readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
69510
69712
  spawnSync: (cmd, opts) => {
69511
69713
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
69512
69714
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -69526,7 +69728,7 @@ var _planDeps = {
69526
69728
  planDecompose: (workdir, config2, opts) => planDecomposeCommand(workdir, config2, opts)
69527
69729
  };
69528
69730
  async function planCommand(workdir, config2, options) {
69529
- const naxDir = join11(workdir, ".nax");
69731
+ const naxDir = join12(workdir, ".nax");
69530
69732
  if (!existsSync15(naxDir)) {
69531
69733
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
69532
69734
  }
@@ -69542,18 +69744,51 @@ async function planCommand(workdir, config2, options) {
69542
69744
  const codebaseContext = buildCodebaseContext2(scan);
69543
69745
  const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
69544
69746
  const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
69545
- const pkgJson = await _planDeps.readPackageJsonAt(join11(workdir, rel, "package.json"));
69747
+ const pkgJson = await _planDeps.readPackageJsonAt(join12(workdir, rel, "package.json"));
69546
69748
  return buildPackageSummary(rel, pkgJson);
69547
69749
  })) : [];
69548
69750
  const projectName = detectProjectName(workdir, pkg);
69549
69751
  const branchName = options.branch ?? `feat/${options.feature}`;
69550
- const outputDir = join11(naxDir, "features", options.feature);
69551
- const outputPath = join11(outputDir, "prd.json");
69752
+ const outputDir = join12(naxDir, "features", options.feature);
69753
+ const outputPath = join12(outputDir, "prd.json");
69552
69754
  await _planDeps.mkdirp(outputDir);
69553
69755
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
69554
69756
  const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
69555
69757
  let rawResponse;
69556
- 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) {
69557
69792
  const isAcp = config2?.agent?.protocol === "acp";
69558
69793
  const prompt = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
69559
69794
  const adapter = _planDeps.getAgent(agentName, config2);
@@ -69568,39 +69803,38 @@ async function planCommand(workdir, config2, options) {
69568
69803
  autoModel = resolveModelForAgent2(config2.models, defaultAgent, planTier, defaultAgent).model;
69569
69804
  }
69570
69805
  } catch {}
69571
- const runSingleAgentPlan = async () => {
69572
- if (isAcp) {
69573
- logger?.info("plan", "Starting ACP auto planning session", {
69574
- agent: agentName,
69575
- 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,
69576
69818
  workdir,
69577
- feature: options.feature,
69578
- 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"
69579
69828
  });
69580
- const pidRegistry = new PidRegistry(workdir);
69581
- try {
69582
- await adapter.plan({
69583
- prompt,
69584
- workdir,
69585
- interactive: false,
69586
- timeoutSeconds,
69587
- config: config2,
69588
- modelTier: config2?.plan?.model ?? "balanced",
69589
- dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
69590
- maxInteractionTurns: config2?.agent?.maxInteractionTurns,
69591
- featureName: options.feature,
69592
- pidRegistry,
69593
- sessionRole: "plan"
69594
- });
69595
- } finally {
69596
- await pidRegistry.killAll().catch(() => {});
69597
- }
69598
- if (!_planDeps.existsSync(outputPath)) {
69599
- throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69600
- }
69601
- return await _planDeps.readFile(outputPath);
69829
+ } finally {
69830
+ await pidRegistry.killAll().catch(() => {});
69831
+ }
69832
+ if (!_planDeps.existsSync(outputPath)) {
69833
+ throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69602
69834
  }
69603
- let result = await adapter.complete(prompt, {
69835
+ rawResponse = await _planDeps.readFile(outputPath);
69836
+ } else {
69837
+ const completeResult = await adapter.complete(prompt, {
69604
69838
  model: autoModel,
69605
69839
  jsonMode: true,
69606
69840
  workdir,
@@ -69608,37 +69842,19 @@ async function planCommand(workdir, config2, options) {
69608
69842
  featureName: options.feature,
69609
69843
  sessionRole: "plan"
69610
69844
  });
69845
+ let result = typeof completeResult === "string" ? completeResult : completeResult.output;
69611
69846
  try {
69612
69847
  const envelope = JSON.parse(result);
69613
69848
  if (envelope?.type === "result" && typeof envelope?.result === "string") {
69614
69849
  result = envelope.result;
69615
69850
  }
69616
69851
  } catch {}
69617
- return result;
69618
- };
69619
- const debateEnabled = config2?.debate?.enabled && config2?.debate?.stages?.plan?.enabled;
69620
- if (debateEnabled) {
69621
- const planStageConfig = config2?.debate?.stages.plan;
69622
- const debateSession = _planDeps.createDebateSession({
69623
- storyId: options.feature,
69624
- stage: "plan",
69625
- stageConfig: planStageConfig,
69626
- config: config2
69627
- });
69628
- const debateResult = await debateSession.run(prompt);
69629
- if (debateResult.outcome !== "failed" && debateResult.output) {
69630
- rawResponse = debateResult.output;
69631
- } else {
69632
- logger?.warn("debate", "All debaters failed \u2014 falling back to single agent", {
69633
- stage: "debate",
69634
- event: "fallback"
69635
- });
69636
- rawResponse = await runSingleAgentPlan();
69637
- }
69638
- } else {
69639
- rawResponse = await runSingleAgentPlan();
69852
+ rawResponse = result;
69640
69853
  }
69641
69854
  } else {
69855
+ rawResponse = await runInteractivePlan();
69856
+ }
69857
+ async function runInteractivePlan() {
69642
69858
  const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
69643
69859
  const adapter = _planDeps.getAgent(agentName, config2);
69644
69860
  if (!adapter)
@@ -69685,7 +69901,7 @@ async function planCommand(workdir, config2, options) {
69685
69901
  if (!_planDeps.existsSync(outputPath)) {
69686
69902
  throw new Error(`[plan] Agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
69687
69903
  }
69688
- rawResponse = await _planDeps.readFile(outputPath);
69904
+ return _planDeps.readFile(outputPath);
69689
69905
  }
69690
69906
  const finalPrd = validatePlanOutput(rawResponse, options.feature, branchName);
69691
69907
  finalPrd.project = projectName;
@@ -69964,7 +70180,7 @@ Return JSON with this exact structure (no markdown, no explanation \u2014 JSON o
69964
70180
  }`;
69965
70181
  }
69966
70182
  async function planDecomposeCommand(workdir, config2, options) {
69967
- const prdPath = join11(workdir, ".nax", "features", options.feature, "prd.json");
70183
+ const prdPath = join12(workdir, ".nax", "features", options.feature, "prd.json");
69968
70184
  if (!_planDeps.existsSync(prdPath)) {
69969
70185
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
69970
70186
  stage: "decompose",
@@ -70008,22 +70224,24 @@ async function planDecomposeCommand(workdir, config2, options) {
70008
70224
  if (debateResult.outcome !== "failed" && debateResult.output) {
70009
70225
  rawResponse = debateResult.output;
70010
70226
  } else {
70011
- rawResponse = await adapter.complete(prompt, {
70227
+ const completeResult = await adapter.complete(prompt, {
70012
70228
  jsonMode: true,
70013
70229
  workdir,
70014
70230
  sessionRole: "decompose",
70015
70231
  featureName: options.feature,
70016
70232
  storyId: options.storyId
70017
70233
  });
70234
+ rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
70018
70235
  }
70019
70236
  } else {
70020
- rawResponse = await adapter.complete(prompt, {
70237
+ const completeResult = await adapter.complete(prompt, {
70021
70238
  jsonMode: true,
70022
70239
  workdir,
70023
70240
  sessionRole: "decompose",
70024
70241
  featureName: options.feature,
70025
70242
  storyId: options.storyId
70026
70243
  });
70244
+ rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
70027
70245
  }
70028
70246
  const jsonMatch = rawResponse.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
70029
70247
  const cleanedResponse = jsonMatch ? jsonMatch[1] : rawResponse;
@@ -70246,13 +70464,13 @@ async function displayModelEfficiency(workdir) {
70246
70464
  // src/cli/status-features.ts
70247
70465
  init_source();
70248
70466
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
70249
- import { join as join14 } from "path";
70467
+ import { join as join15 } from "path";
70250
70468
 
70251
70469
  // src/commands/common.ts
70252
70470
  init_path_security();
70253
70471
  init_errors();
70254
70472
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
70255
- import { join as join12, resolve as resolve4 } from "path";
70473
+ import { join as join13, resolve as resolve4 } from "path";
70256
70474
  function resolveProject(options = {}) {
70257
70475
  const { dir, feature } = options;
70258
70476
  let projectRoot;
@@ -70260,12 +70478,12 @@ function resolveProject(options = {}) {
70260
70478
  let configPath;
70261
70479
  if (dir) {
70262
70480
  projectRoot = realpathSync2(resolve4(dir));
70263
- naxDir = join12(projectRoot, ".nax");
70481
+ naxDir = join13(projectRoot, ".nax");
70264
70482
  if (!existsSync16(naxDir)) {
70265
70483
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
70266
70484
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
70267
70485
  }
70268
- configPath = join12(naxDir, "config.json");
70486
+ configPath = join13(naxDir, "config.json");
70269
70487
  if (!existsSync16(configPath)) {
70270
70488
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
70271
70489
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -70273,22 +70491,22 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
70273
70491
  } else {
70274
70492
  const found = findProjectRoot(process.cwd());
70275
70493
  if (!found) {
70276
- const cwdNaxDir = join12(process.cwd(), ".nax");
70494
+ const cwdNaxDir = join13(process.cwd(), ".nax");
70277
70495
  if (existsSync16(cwdNaxDir)) {
70278
- const cwdConfigPath = join12(cwdNaxDir, "config.json");
70496
+ const cwdConfigPath = join13(cwdNaxDir, "config.json");
70279
70497
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
70280
70498
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
70281
70499
  }
70282
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() });
70283
70501
  }
70284
70502
  projectRoot = found;
70285
- naxDir = join12(projectRoot, ".nax");
70286
- configPath = join12(naxDir, "config.json");
70503
+ naxDir = join13(projectRoot, ".nax");
70504
+ configPath = join13(naxDir, "config.json");
70287
70505
  }
70288
70506
  let featureDir;
70289
70507
  if (feature) {
70290
- const featuresDir = join12(naxDir, "features");
70291
- featureDir = join12(featuresDir, feature);
70508
+ const featuresDir = join13(naxDir, "features");
70509
+ featureDir = join13(featuresDir, feature);
70292
70510
  if (!existsSync16(featureDir)) {
70293
70511
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
70294
70512
  const availableMsg = availableFeatures.length > 0 ? `
@@ -70315,12 +70533,12 @@ function findProjectRoot(startDir) {
70315
70533
  let current = resolve4(startDir);
70316
70534
  let depth = 0;
70317
70535
  while (depth < MAX_DIRECTORY_DEPTH) {
70318
- const naxDir = join12(current, ".nax");
70319
- const configPath = join12(naxDir, "config.json");
70536
+ const naxDir = join13(current, ".nax");
70537
+ const configPath = join13(naxDir, "config.json");
70320
70538
  if (existsSync16(configPath)) {
70321
70539
  return realpathSync2(current);
70322
70540
  }
70323
- const parent = join12(current, "..");
70541
+ const parent = join13(current, "..");
70324
70542
  if (parent === current) {
70325
70543
  break;
70326
70544
  }
@@ -70342,7 +70560,7 @@ function isPidAlive(pid) {
70342
70560
  }
70343
70561
  }
70344
70562
  async function loadStatusFile(featureDir) {
70345
- const statusPath = join14(featureDir, "status.json");
70563
+ const statusPath = join15(featureDir, "status.json");
70346
70564
  if (!existsSync17(statusPath)) {
70347
70565
  return null;
70348
70566
  }
@@ -70354,7 +70572,7 @@ async function loadStatusFile(featureDir) {
70354
70572
  }
70355
70573
  }
70356
70574
  async function loadProjectStatusFile(projectDir) {
70357
- const statusPath = join14(projectDir, ".nax", "status.json");
70575
+ const statusPath = join15(projectDir, ".nax", "status.json");
70358
70576
  if (!existsSync17(statusPath)) {
70359
70577
  return null;
70360
70578
  }
@@ -70366,7 +70584,7 @@ async function loadProjectStatusFile(projectDir) {
70366
70584
  }
70367
70585
  }
70368
70586
  async function getFeatureSummary(featureName, featureDir) {
70369
- const prdPath = join14(featureDir, "prd.json");
70587
+ const prdPath = join15(featureDir, "prd.json");
70370
70588
  if (!existsSync17(prdPath)) {
70371
70589
  return {
70372
70590
  name: featureName,
@@ -70409,7 +70627,7 @@ async function getFeatureSummary(featureName, featureDir) {
70409
70627
  };
70410
70628
  }
70411
70629
  }
70412
- const runsDir = join14(featureDir, "runs");
70630
+ const runsDir = join15(featureDir, "runs");
70413
70631
  if (existsSync17(runsDir)) {
70414
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();
70415
70633
  if (runs.length > 0) {
@@ -70420,7 +70638,7 @@ async function getFeatureSummary(featureName, featureDir) {
70420
70638
  return summary;
70421
70639
  }
70422
70640
  async function displayAllFeatures(projectDir) {
70423
- const featuresDir = join14(projectDir, ".nax", "features");
70641
+ const featuresDir = join15(projectDir, ".nax", "features");
70424
70642
  if (!existsSync17(featuresDir)) {
70425
70643
  console.log(source_default.dim("No features found."));
70426
70644
  return;
@@ -70461,7 +70679,7 @@ async function displayAllFeatures(projectDir) {
70461
70679
  console.log();
70462
70680
  }
70463
70681
  }
70464
- 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))));
70465
70683
  console.log(source_default.bold(`\uD83D\uDCCA Features
70466
70684
  `));
70467
70685
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -70487,7 +70705,7 @@ async function displayAllFeatures(projectDir) {
70487
70705
  console.log();
70488
70706
  }
70489
70707
  async function displayFeatureDetails(featureName, featureDir) {
70490
- const prdPath = join14(featureDir, "prd.json");
70708
+ const prdPath = join15(featureDir, "prd.json");
70491
70709
  if (!existsSync17(prdPath)) {
70492
70710
  console.log(source_default.bold(`
70493
70711
  \uD83D\uDCCA ${featureName}
@@ -70609,7 +70827,7 @@ async function displayFeatureStatus(options = {}) {
70609
70827
  init_errors();
70610
70828
  init_logger2();
70611
70829
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
70612
- import { join as join15 } from "path";
70830
+ import { join as join16 } from "path";
70613
70831
  async function parseRunLog(logPath) {
70614
70832
  const logger = getLogger();
70615
70833
  try {
@@ -70625,7 +70843,7 @@ async function parseRunLog(logPath) {
70625
70843
  async function runsListCommand(options) {
70626
70844
  const logger = getLogger();
70627
70845
  const { feature, workdir } = options;
70628
- const runsDir = join15(workdir, ".nax", "features", feature, "runs");
70846
+ const runsDir = join16(workdir, ".nax", "features", feature, "runs");
70629
70847
  if (!existsSync18(runsDir)) {
70630
70848
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
70631
70849
  return;
@@ -70637,7 +70855,7 @@ async function runsListCommand(options) {
70637
70855
  }
70638
70856
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
70639
70857
  for (const file3 of files.sort().reverse()) {
70640
- const logPath = join15(runsDir, file3);
70858
+ const logPath = join16(runsDir, file3);
70641
70859
  const entries = await parseRunLog(logPath);
70642
70860
  const startEvent = entries.find((e) => e.message === "run.start");
70643
70861
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -70663,7 +70881,7 @@ async function runsListCommand(options) {
70663
70881
  async function runsShowCommand(options) {
70664
70882
  const logger = getLogger();
70665
70883
  const { runId, feature, workdir } = options;
70666
- const logPath = join15(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
70884
+ const logPath = join16(workdir, ".nax", "features", feature, "runs", `${runId}.jsonl`);
70667
70885
  if (!existsSync18(logPath)) {
70668
70886
  logger.error("cli", "Run not found", { runId, feature, logPath });
70669
70887
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -70702,7 +70920,7 @@ async function runsShowCommand(options) {
70702
70920
  // src/cli/prompts-main.ts
70703
70921
  init_logger2();
70704
70922
  import { existsSync as existsSync22, mkdirSync as mkdirSync2 } from "fs";
70705
- import { join as join28 } from "path";
70923
+ import { join as join29 } from "path";
70706
70924
 
70707
70925
  // src/pipeline/index.ts
70708
70926
  init_runner();
@@ -70777,7 +70995,7 @@ function buildFrontmatter(story, ctx, role) {
70777
70995
 
70778
70996
  // src/cli/prompts-tdd.ts
70779
70997
  init_prompts2();
70780
- import { join as join27 } from "path";
70998
+ import { join as join28 } from "path";
70781
70999
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
70782
71000
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
70783
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(),
@@ -70796,7 +71014,7 @@ ${frontmatter}---
70796
71014
 
70797
71015
  ${session.prompt}`;
70798
71016
  if (outputDir) {
70799
- const promptFile = join27(outputDir, `${story.id}.${session.role}.md`);
71017
+ const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
70800
71018
  await Bun.write(promptFile, fullOutput);
70801
71019
  logger.info("cli", "Written TDD prompt file", {
70802
71020
  storyId: story.id,
@@ -70812,7 +71030,7 @@ ${"=".repeat(80)}`);
70812
71030
  }
70813
71031
  }
70814
71032
  if (outputDir && ctx.contextMarkdown) {
70815
- const contextFile = join27(outputDir, `${story.id}.context.md`);
71033
+ const contextFile = join28(outputDir, `${story.id}.context.md`);
70816
71034
  const frontmatter = buildFrontmatter(story, ctx);
70817
71035
  const contextOutput = `---
70818
71036
  ${frontmatter}---
@@ -70826,12 +71044,12 @@ ${ctx.contextMarkdown}`;
70826
71044
  async function promptsCommand(options) {
70827
71045
  const logger = getLogger();
70828
71046
  const { feature, workdir, config: config2, storyId, outputDir } = options;
70829
- const naxDir = join28(workdir, ".nax");
71047
+ const naxDir = join29(workdir, ".nax");
70830
71048
  if (!existsSync22(naxDir)) {
70831
71049
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
70832
71050
  }
70833
- const featureDir = join28(naxDir, "features", feature);
70834
- const prdPath = join28(featureDir, "prd.json");
71051
+ const featureDir = join29(naxDir, "features", feature);
71052
+ const prdPath = join29(featureDir, "prd.json");
70835
71053
  if (!existsSync22(prdPath)) {
70836
71054
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
70837
71055
  }
@@ -70892,10 +71110,10 @@ ${frontmatter}---
70892
71110
 
70893
71111
  ${ctx.prompt}`;
70894
71112
  if (outputDir) {
70895
- const promptFile = join28(outputDir, `${story.id}.prompt.md`);
71113
+ const promptFile = join29(outputDir, `${story.id}.prompt.md`);
70896
71114
  await Bun.write(promptFile, fullOutput);
70897
71115
  if (ctx.contextMarkdown) {
70898
- const contextFile = join28(outputDir, `${story.id}.context.md`);
71116
+ const contextFile = join29(outputDir, `${story.id}.context.md`);
70899
71117
  const contextOutput = `---
70900
71118
  ${frontmatter}---
70901
71119
 
@@ -70922,7 +71140,7 @@ ${"=".repeat(80)}`);
70922
71140
  }
70923
71141
  // src/cli/prompts-init.ts
70924
71142
  import { existsSync as existsSync23, mkdirSync as mkdirSync3 } from "fs";
70925
- import { join as join29 } from "path";
71143
+ import { join as join30 } from "path";
70926
71144
  var TEMPLATE_ROLES = [
70927
71145
  { file: "test-writer.md", role: "test-writer" },
70928
71146
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -70946,9 +71164,9 @@ var TEMPLATE_HEADER = `<!--
70946
71164
  `;
70947
71165
  async function promptsInitCommand(options) {
70948
71166
  const { workdir, force = false, autoWireConfig = true } = options;
70949
- const templatesDir = join29(workdir, ".nax", "templates");
71167
+ const templatesDir = join30(workdir, ".nax", "templates");
70950
71168
  mkdirSync3(templatesDir, { recursive: true });
70951
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join29(templatesDir, f)));
71169
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync23(join30(templatesDir, f)));
70952
71170
  if (existingFiles.length > 0 && !force) {
70953
71171
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
70954
71172
  Pass --force to overwrite existing templates.`);
@@ -70956,7 +71174,7 @@ async function promptsInitCommand(options) {
70956
71174
  }
70957
71175
  const written = [];
70958
71176
  for (const template of TEMPLATE_ROLES) {
70959
- const filePath = join29(templatesDir, template.file);
71177
+ const filePath = join30(templatesDir, template.file);
70960
71178
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
70961
71179
  const content = TEMPLATE_HEADER + roleBody;
70962
71180
  await Bun.write(filePath, content);
@@ -70972,7 +71190,7 @@ async function promptsInitCommand(options) {
70972
71190
  return written;
70973
71191
  }
70974
71192
  async function autoWirePromptsConfig(workdir) {
70975
- const configPath = join29(workdir, "nax.config.json");
71193
+ const configPath = join30(workdir, "nax.config.json");
70976
71194
  if (!existsSync23(configPath)) {
70977
71195
  const exampleConfig = JSON.stringify({
70978
71196
  prompts: {
@@ -71138,7 +71356,7 @@ init_config();
71138
71356
  init_logger2();
71139
71357
  init_prd();
71140
71358
  import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
71141
- import { join as join34 } from "path";
71359
+ import { join as join35 } from "path";
71142
71360
 
71143
71361
  // src/cli/diagnose-analysis.ts
71144
71362
  function detectFailurePattern(story, prd, status) {
@@ -71337,7 +71555,7 @@ function isProcessAlive2(pid) {
71337
71555
  }
71338
71556
  }
71339
71557
  async function loadStatusFile2(workdir) {
71340
- const statusPath = join34(workdir, ".nax", "status.json");
71558
+ const statusPath = join35(workdir, ".nax", "status.json");
71341
71559
  if (!existsSync25(statusPath))
71342
71560
  return null;
71343
71561
  try {
@@ -71365,7 +71583,7 @@ async function countCommitsSince(workdir, since) {
71365
71583
  }
71366
71584
  }
71367
71585
  async function checkLock(workdir) {
71368
- const lockFile = Bun.file(join34(workdir, "nax.lock"));
71586
+ const lockFile = Bun.file(join35(workdir, "nax.lock"));
71369
71587
  if (!await lockFile.exists())
71370
71588
  return { lockPresent: false };
71371
71589
  try {
@@ -71383,8 +71601,8 @@ async function diagnoseCommand(options = {}) {
71383
71601
  const logger = getLogger();
71384
71602
  const workdir = options.workdir ?? process.cwd();
71385
71603
  const naxSubdir = findProjectDir(workdir);
71386
- let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
71387
- if (!projectDir && existsSync25(join34(workdir, ".nax"))) {
71604
+ let projectDir = naxSubdir ? join35(naxSubdir, "..") : null;
71605
+ if (!projectDir && existsSync25(join35(workdir, ".nax"))) {
71388
71606
  projectDir = workdir;
71389
71607
  }
71390
71608
  if (!projectDir)
@@ -71395,7 +71613,7 @@ async function diagnoseCommand(options = {}) {
71395
71613
  if (status2) {
71396
71614
  feature = status2.run.feature;
71397
71615
  } else {
71398
- const featuresDir = join34(projectDir, ".nax", "features");
71616
+ const featuresDir = join35(projectDir, ".nax", "features");
71399
71617
  if (!existsSync25(featuresDir))
71400
71618
  throw new Error("No features found in project");
71401
71619
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -71405,8 +71623,8 @@ async function diagnoseCommand(options = {}) {
71405
71623
  logger.info("diagnose", "No feature specified, using first found", { feature });
71406
71624
  }
71407
71625
  }
71408
- const featureDir = join34(projectDir, ".nax", "features", feature);
71409
- const prdPath = join34(featureDir, "prd.json");
71626
+ const featureDir = join35(projectDir, ".nax", "features", feature);
71627
+ const prdPath = join35(featureDir, "prd.json");
71410
71628
  if (!existsSync25(prdPath))
71411
71629
  throw new Error(`Feature not found: ${feature}`);
71412
71630
  const prd = await loadPRD(prdPath);
@@ -71449,7 +71667,7 @@ init_interaction();
71449
71667
  init_source();
71450
71668
  init_loader();
71451
71669
  import { existsSync as existsSync26 } from "fs";
71452
- import { join as join35 } from "path";
71670
+ import { join as join36 } from "path";
71453
71671
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
71454
71672
  async function generateCommand(options) {
71455
71673
  const workdir = options.dir ?? process.cwd();
@@ -71492,7 +71710,7 @@ async function generateCommand(options) {
71492
71710
  return;
71493
71711
  }
71494
71712
  if (options.package) {
71495
- const packageDir = join35(workdir, options.package);
71713
+ const packageDir = join36(workdir, options.package);
71496
71714
  if (dryRun) {
71497
71715
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
71498
71716
  }
@@ -71512,8 +71730,8 @@ async function generateCommand(options) {
71512
71730
  process.exit(1);
71513
71731
  return;
71514
71732
  }
71515
- const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, ".nax/context.md");
71516
- 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;
71517
71735
  const autoInject = !options.noAutoInject;
71518
71736
  if (!existsSync26(contextPath)) {
71519
71737
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -71618,7 +71836,7 @@ async function generateCommand(options) {
71618
71836
  // src/cli/config-display.ts
71619
71837
  init_loader();
71620
71838
  import { existsSync as existsSync28 } from "fs";
71621
- import { join as join37 } from "path";
71839
+ import { join as join38 } from "path";
71622
71840
 
71623
71841
  // src/cli/config-descriptions.ts
71624
71842
  var FIELD_DESCRIPTIONS = {
@@ -71856,7 +72074,7 @@ function deepEqual(a, b) {
71856
72074
  init_defaults();
71857
72075
  init_loader();
71858
72076
  import { existsSync as existsSync27 } from "fs";
71859
- import { join as join36 } from "path";
72077
+ import { join as join37 } from "path";
71860
72078
  async function loadConfigFile(path14) {
71861
72079
  if (!existsSync27(path14))
71862
72080
  return null;
@@ -71878,7 +72096,7 @@ async function loadProjectConfig() {
71878
72096
  const projectDir = findProjectDir();
71879
72097
  if (!projectDir)
71880
72098
  return null;
71881
- const projectPath = join36(projectDir, "config.json");
72099
+ const projectPath = join37(projectDir, "config.json");
71882
72100
  return await loadConfigFile(projectPath);
71883
72101
  }
71884
72102
 
@@ -71938,7 +72156,7 @@ async function configCommand(config2, options = {}) {
71938
72156
  function determineConfigSources() {
71939
72157
  const globalPath = globalConfigPath();
71940
72158
  const projectDir = findProjectDir();
71941
- const projectPath = projectDir ? join37(projectDir, "config.json") : null;
72159
+ const projectPath = projectDir ? join38(projectDir, "config.json") : null;
71942
72160
  return {
71943
72161
  global: fileExists(globalPath) ? globalPath : null,
71944
72162
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -72118,24 +72336,24 @@ async function diagnose(options) {
72118
72336
 
72119
72337
  // src/commands/logs.ts
72120
72338
  import { existsSync as existsSync30 } from "fs";
72121
- import { join as join41 } from "path";
72339
+ import { join as join42 } from "path";
72122
72340
 
72123
72341
  // src/commands/logs-formatter.ts
72124
72342
  init_source();
72125
72343
  init_formatter();
72126
72344
  import { readdirSync as readdirSync7 } from "fs";
72127
- import { join as join40 } from "path";
72345
+ import { join as join41 } from "path";
72128
72346
 
72129
72347
  // src/commands/logs-reader.ts
72130
72348
  import { existsSync as existsSync29, readdirSync as readdirSync6 } from "fs";
72131
72349
  import { readdir as readdir3 } from "fs/promises";
72132
- import { join as join39 } from "path";
72350
+ import { join as join40 } from "path";
72133
72351
 
72134
72352
  // src/utils/paths.ts
72135
72353
  import { homedir as homedir4 } from "os";
72136
- import { join as join38 } from "path";
72354
+ import { join as join39 } from "path";
72137
72355
  function getRunsDir() {
72138
- return process.env.NAX_RUNS_DIR ?? join38(homedir4(), ".nax", "runs");
72356
+ return process.env.NAX_RUNS_DIR ?? join39(homedir4(), ".nax", "runs");
72139
72357
  }
72140
72358
 
72141
72359
  // src/commands/logs-reader.ts
@@ -72152,7 +72370,7 @@ async function resolveRunFileFromRegistry(runId) {
72152
72370
  }
72153
72371
  let matched = null;
72154
72372
  for (const entry of entries) {
72155
- const metaPath = join39(runsDir, entry, "meta.json");
72373
+ const metaPath = join40(runsDir, entry, "meta.json");
72156
72374
  try {
72157
72375
  const meta3 = await Bun.file(metaPath).json();
72158
72376
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -72174,14 +72392,14 @@ async function resolveRunFileFromRegistry(runId) {
72174
72392
  return null;
72175
72393
  }
72176
72394
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
72177
- return join39(matched.eventsDir, specificFile ?? files[0]);
72395
+ return join40(matched.eventsDir, specificFile ?? files[0]);
72178
72396
  }
72179
72397
  async function selectRunFile(runsDir) {
72180
72398
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
72181
72399
  if (files.length === 0) {
72182
72400
  return null;
72183
72401
  }
72184
- return join39(runsDir, files[0]);
72402
+ return join40(runsDir, files[0]);
72185
72403
  }
72186
72404
  async function extractRunSummary(filePath) {
72187
72405
  const file3 = Bun.file(filePath);
@@ -72266,7 +72484,7 @@ Runs:
72266
72484
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
72267
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"));
72268
72486
  for (const file3 of files) {
72269
- const filePath = join40(runsDir, file3);
72487
+ const filePath = join41(runsDir, file3);
72270
72488
  const summary = await extractRunSummary(filePath);
72271
72489
  const timestamp = file3.replace(".jsonl", "");
72272
72490
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -72380,7 +72598,7 @@ async function logsCommand(options) {
72380
72598
  return;
72381
72599
  }
72382
72600
  const resolved = resolveProject({ dir: options.dir });
72383
- const naxDir = join41(resolved.projectDir, ".nax");
72601
+ const naxDir = join42(resolved.projectDir, ".nax");
72384
72602
  const configPath = resolved.configPath;
72385
72603
  const configFile = Bun.file(configPath);
72386
72604
  const config2 = await configFile.json();
@@ -72388,8 +72606,8 @@ async function logsCommand(options) {
72388
72606
  if (!featureName) {
72389
72607
  throw new Error("No feature specified in config.json");
72390
72608
  }
72391
- const featureDir = join41(naxDir, "features", featureName);
72392
- const runsDir = join41(featureDir, "runs");
72609
+ const featureDir = join42(naxDir, "features", featureName);
72610
+ const runsDir = join42(featureDir, "runs");
72393
72611
  if (!existsSync30(runsDir)) {
72394
72612
  throw new Error(`No runs directory found for feature: ${featureName}`);
72395
72613
  }
@@ -72414,7 +72632,7 @@ init_config();
72414
72632
  init_prd();
72415
72633
  init_precheck();
72416
72634
  import { existsSync as existsSync31 } from "fs";
72417
- import { join as join42 } from "path";
72635
+ import { join as join43 } from "path";
72418
72636
  async function precheckCommand(options) {
72419
72637
  const resolved = resolveProject({
72420
72638
  dir: options.dir,
@@ -72436,9 +72654,9 @@ async function precheckCommand(options) {
72436
72654
  process.exit(1);
72437
72655
  }
72438
72656
  }
72439
- const naxDir = join42(resolved.projectDir, ".nax");
72440
- const featureDir = join42(naxDir, "features", featureName);
72441
- 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");
72442
72660
  if (!existsSync31(featureDir)) {
72443
72661
  console.error(source_default.red(`Feature not found: ${featureName}`));
72444
72662
  process.exit(1);
@@ -72460,7 +72678,7 @@ async function precheckCommand(options) {
72460
72678
  // src/commands/runs.ts
72461
72679
  init_source();
72462
72680
  import { readdir as readdir4 } from "fs/promises";
72463
- import { join as join43 } from "path";
72681
+ import { join as join44 } from "path";
72464
72682
  var DEFAULT_LIMIT = 20;
72465
72683
  var _runsCmdDeps = {
72466
72684
  getRunsDir
@@ -72515,7 +72733,7 @@ async function runsCommand(options = {}) {
72515
72733
  }
72516
72734
  const rows = [];
72517
72735
  for (const entry of entries) {
72518
- const metaPath = join43(runsDir, entry, "meta.json");
72736
+ const metaPath = join44(runsDir, entry, "meta.json");
72519
72737
  let meta3;
72520
72738
  try {
72521
72739
  meta3 = await Bun.file(metaPath).json();
@@ -72592,7 +72810,7 @@ async function runsCommand(options = {}) {
72592
72810
 
72593
72811
  // src/commands/unlock.ts
72594
72812
  init_source();
72595
- import { join as join44 } from "path";
72813
+ import { join as join45 } from "path";
72596
72814
  function isProcessAlive3(pid) {
72597
72815
  try {
72598
72816
  process.kill(pid, 0);
@@ -72607,7 +72825,7 @@ function formatLockAge(ageMs) {
72607
72825
  }
72608
72826
  async function unlockCommand(options) {
72609
72827
  const workdir = options.dir ?? process.cwd();
72610
- const lockPath = join44(workdir, "nax.lock");
72828
+ const lockPath = join45(workdir, "nax.lock");
72611
72829
  const lockFile = Bun.file(lockPath);
72612
72830
  const exists = await lockFile.exists();
72613
72831
  if (!exists) {
@@ -80411,15 +80629,15 @@ Next: nax generate --package ${options.package}`));
80411
80629
  }
80412
80630
  return;
80413
80631
  }
80414
- const naxDir = join55(workdir, ".nax");
80632
+ const naxDir = join56(workdir, ".nax");
80415
80633
  if (existsSync34(naxDir) && !options.force) {
80416
80634
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
80417
80635
  return;
80418
80636
  }
80419
- mkdirSync5(join55(naxDir, "features"), { recursive: true });
80420
- mkdirSync5(join55(naxDir, "hooks"), { recursive: true });
80421
- await Bun.write(join55(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
80422
- 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({
80423
80641
  hooks: {
80424
80642
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
80425
80643
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -80427,12 +80645,12 @@ Next: nax generate --package ${options.package}`));
80427
80645
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
80428
80646
  }
80429
80647
  }, null, 2));
80430
- await Bun.write(join55(naxDir, ".gitignore"), `# nax temp files
80648
+ await Bun.write(join56(naxDir, ".gitignore"), `# nax temp files
80431
80649
  *.tmp
80432
80650
  .paused.json
80433
80651
  .nax-verifier-verdict.json
80434
80652
  `);
80435
- await Bun.write(join55(naxDir, "context.md"), `# Project Context
80653
+ await Bun.write(join56(naxDir, "context.md"), `# Project Context
80436
80654
 
80437
80655
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
80438
80656
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -80558,8 +80776,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
80558
80776
  console.error(source_default.red("nax not initialized. Run: nax init"));
80559
80777
  process.exit(1);
80560
80778
  }
80561
- const featureDir = join55(naxDir, "features", options.feature);
80562
- const prdPath = join55(featureDir, "prd.json");
80779
+ const featureDir = join56(naxDir, "features", options.feature);
80780
+ const prdPath = join56(featureDir, "prd.json");
80563
80781
  if (options.plan && options.from) {
80564
80782
  if (existsSync34(prdPath) && !options.force) {
80565
80783
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -80581,10 +80799,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
80581
80799
  }
80582
80800
  }
80583
80801
  try {
80584
- const planLogDir = join55(featureDir, "plan");
80802
+ const planLogDir = join56(featureDir, "plan");
80585
80803
  mkdirSync5(planLogDir, { recursive: true });
80586
80804
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80587
- const planLogPath = join55(planLogDir, `${planLogId}.jsonl`);
80805
+ const planLogPath = join56(planLogDir, `${planLogId}.jsonl`);
80588
80806
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
80589
80807
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
80590
80808
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -80628,10 +80846,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
80628
80846
  process.exit(1);
80629
80847
  }
80630
80848
  resetLogger();
80631
- const runsDir = join55(featureDir, "runs");
80849
+ const runsDir = join56(featureDir, "runs");
80632
80850
  mkdirSync5(runsDir, { recursive: true });
80633
80851
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80634
- const logFilePath = join55(runsDir, `${runId}.jsonl`);
80852
+ const logFilePath = join56(runsDir, `${runId}.jsonl`);
80635
80853
  const isTTY = process.stdout.isTTY ?? false;
80636
80854
  const headlessFlag = options.headless ?? false;
80637
80855
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -80647,7 +80865,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80647
80865
  config2.autoMode.defaultAgent = options.agent;
80648
80866
  }
80649
80867
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
80650
- const globalNaxDir = join55(homedir8(), ".nax");
80868
+ const globalNaxDir = join56(homedir8(), ".nax");
80651
80869
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
80652
80870
  const eventEmitter = new PipelineEventEmitter;
80653
80871
  let tuiInstance;
@@ -80670,7 +80888,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80670
80888
  } else {
80671
80889
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
80672
80890
  }
80673
- const statusFilePath = join55(workdir, ".nax", "status.json");
80891
+ const statusFilePath = join56(workdir, ".nax", "status.json");
80674
80892
  let parallel;
80675
80893
  if (options.parallel !== undefined) {
80676
80894
  parallel = Number.parseInt(options.parallel, 10);
@@ -80696,7 +80914,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
80696
80914
  headless: useHeadless,
80697
80915
  skipPrecheck: options.skipPrecheck ?? false
80698
80916
  });
80699
- const latestSymlink = join55(runsDir, "latest.jsonl");
80917
+ const latestSymlink = join56(runsDir, "latest.jsonl");
80700
80918
  try {
80701
80919
  if (existsSync34(latestSymlink)) {
80702
80920
  Bun.spawnSync(["rm", latestSymlink]);
@@ -80734,9 +80952,9 @@ features.command("create <name>").description("Create a new feature").option("-d
80734
80952
  console.error(source_default.red("nax not initialized. Run: nax init"));
80735
80953
  process.exit(1);
80736
80954
  }
80737
- const featureDir = join55(naxDir, "features", name);
80955
+ const featureDir = join56(naxDir, "features", name);
80738
80956
  mkdirSync5(featureDir, { recursive: true });
80739
- await Bun.write(join55(featureDir, "spec.md"), `# Feature: ${name}
80957
+ await Bun.write(join56(featureDir, "spec.md"), `# Feature: ${name}
80740
80958
 
80741
80959
  ## Overview
80742
80960
 
@@ -80769,7 +80987,7 @@ features.command("create <name>").description("Create a new feature").option("-d
80769
80987
 
80770
80988
  <!-- What this feature explicitly does NOT cover. -->
80771
80989
  `);
80772
- await Bun.write(join55(featureDir, "progress.txt"), `# Progress: ${name}
80990
+ await Bun.write(join56(featureDir, "progress.txt"), `# Progress: ${name}
80773
80991
 
80774
80992
  Created: ${new Date().toISOString()}
80775
80993
 
@@ -80795,7 +81013,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
80795
81013
  console.error(source_default.red("nax not initialized."));
80796
81014
  process.exit(1);
80797
81015
  }
80798
- const featuresDir = join55(naxDir, "features");
81016
+ const featuresDir = join56(naxDir, "features");
80799
81017
  if (!existsSync34(featuresDir)) {
80800
81018
  console.log(source_default.dim("No features yet."));
80801
81019
  return;
@@ -80810,7 +81028,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
80810
81028
  Features:
80811
81029
  `));
80812
81030
  for (const name of entries) {
80813
- const prdPath = join55(featuresDir, name, "prd.json");
81031
+ const prdPath = join56(featuresDir, name, "prd.json");
80814
81032
  if (existsSync34(prdPath)) {
80815
81033
  const prd = await loadPRD(prdPath);
80816
81034
  const c = countStories(prd);
@@ -80841,10 +81059,10 @@ Use: nax plan -f <feature> --from <spec>`));
80841
81059
  process.exit(1);
80842
81060
  }
80843
81061
  const config2 = await loadConfig(workdir);
80844
- const featureLogDir = join55(naxDir, "features", options.feature, "plan");
81062
+ const featureLogDir = join56(naxDir, "features", options.feature, "plan");
80845
81063
  mkdirSync5(featureLogDir, { recursive: true });
80846
81064
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
80847
- const planLogPath = join55(featureLogDir, `${planLogId}.jsonl`);
81065
+ const planLogPath = join56(featureLogDir, `${planLogId}.jsonl`);
80848
81066
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
80849
81067
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
80850
81068
  try {
@@ -80895,7 +81113,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
80895
81113
  console.error(source_default.red("nax not initialized. Run: nax init"));
80896
81114
  process.exit(1);
80897
81115
  }
80898
- const featureDir = join55(naxDir, "features", options.feature);
81116
+ const featureDir = join56(naxDir, "features", options.feature);
80899
81117
  if (!existsSync34(featureDir)) {
80900
81118
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
80901
81119
  process.exit(1);
@@ -80911,7 +81129,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
80911
81129
  specPath: options.from,
80912
81130
  reclassify: options.reclassify
80913
81131
  });
80914
- const prdPath = join55(featureDir, "prd.json");
81132
+ const prdPath = join56(featureDir, "prd.json");
80915
81133
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
80916
81134
  const c = countStories(prd);
80917
81135
  console.log(source_default.green(`