@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.
- package/dist/nax.js +664 -446
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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("
|
|
22150
|
-
return "
|
|
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
|
-
|
|
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((
|
|
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:
|
|
22529
|
-
output:
|
|
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) =>
|
|
22555
|
-
|
|
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:
|
|
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
|
|
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
|
|
22656
|
-
|
|
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:
|
|
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
|
|
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 +=
|
|
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 +=
|
|
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
|
|
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
|
|
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
|
|
22922
|
+
return {
|
|
22923
|
+
outcome: "passed",
|
|
22924
|
+
resolverCostUsd: resolverResult.costUsd
|
|
22925
|
+
};
|
|
22732
22926
|
}
|
|
22733
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 ?
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
30760
|
+
import { join as join25 } from "path";
|
|
30560
30761
|
function resolveStoryWorkdir(repoRoot, storyWorkdir) {
|
|
30561
30762
|
if (!storyWorkdir)
|
|
30562
30763
|
return repoRoot;
|
|
30563
|
-
const resolved =
|
|
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
|
|
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 ?
|
|
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
|
|
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(
|
|
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 ?
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
32731
|
-
const contextPath =
|
|
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 =
|
|
32746
|
-
const contextPath =
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
34462
|
-
const eventsFile =
|
|
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
|
|
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 =
|
|
34648
|
-
const metaFile =
|
|
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:
|
|
34659
|
-
eventsDir:
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
35611
|
-
const excludePath =
|
|
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 =
|
|
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 =
|
|
35881
|
+
const nodeModulesSource = join51(projectRoot, "node_modules");
|
|
35680
35882
|
if (existsSync32(nodeModulesSource)) {
|
|
35681
|
-
const nodeModulesTarget =
|
|
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 =
|
|
35891
|
+
const envSource = join51(projectRoot, ".env");
|
|
35690
35892
|
if (existsSync32(envSource)) {
|
|
35691
|
-
const envTarget =
|
|
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 =
|
|
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
|
|
36813
|
+
import { join as join52 } from "path";
|
|
36612
36814
|
async function detectLanguage(workdir, pkg) {
|
|
36613
36815
|
const deps = _detectorDeps;
|
|
36614
|
-
if (await deps.fileExists(
|
|
36816
|
+
if (await deps.fileExists(join52(workdir, "go.mod")))
|
|
36615
36817
|
return "go";
|
|
36616
|
-
if (await deps.fileExists(
|
|
36818
|
+
if (await deps.fileExists(join52(workdir, "Cargo.toml")))
|
|
36617
36819
|
return "rust";
|
|
36618
|
-
if (await deps.fileExists(
|
|
36820
|
+
if (await deps.fileExists(join52(workdir, "pyproject.toml")))
|
|
36619
36821
|
return "python";
|
|
36620
|
-
if (await deps.fileExists(
|
|
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(
|
|
36882
|
+
if (await deps.fileExists(join52(workdir, "biome.json")))
|
|
36681
36883
|
return "biome";
|
|
36682
|
-
if (await deps.fileExists(
|
|
36884
|
+
if (await deps.fileExists(join52(workdir, ".eslintrc")))
|
|
36683
36885
|
return "eslint";
|
|
36684
|
-
if (await deps.fileExists(
|
|
36886
|
+
if (await deps.fileExists(join52(workdir, ".eslintrc.js")))
|
|
36685
36887
|
return "eslint";
|
|
36686
|
-
if (await deps.fileExists(
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
69551
|
-
const outputPath =
|
|
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
|
-
|
|
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
|
-
|
|
69572
|
-
|
|
69573
|
-
|
|
69574
|
-
|
|
69575
|
-
|
|
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
|
-
|
|
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
|
-
|
|
69581
|
-
|
|
69582
|
-
|
|
69583
|
-
|
|
69584
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
70494
|
+
const cwdNaxDir = join13(process.cwd(), ".nax");
|
|
70277
70495
|
if (existsSync16(cwdNaxDir)) {
|
|
70278
|
-
const cwdConfigPath =
|
|
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 =
|
|
70286
|
-
configPath =
|
|
70503
|
+
naxDir = join13(projectRoot, ".nax");
|
|
70504
|
+
configPath = join13(naxDir, "config.json");
|
|
70287
70505
|
}
|
|
70288
70506
|
let featureDir;
|
|
70289
70507
|
if (feature) {
|
|
70290
|
-
const featuresDir =
|
|
70291
|
-
featureDir =
|
|
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 =
|
|
70319
|
-
const configPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
70834
|
-
const prdPath =
|
|
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 =
|
|
71113
|
+
const promptFile = join29(outputDir, `${story.id}.prompt.md`);
|
|
70896
71114
|
await Bun.write(promptFile, fullOutput);
|
|
70897
71115
|
if (ctx.contextMarkdown) {
|
|
70898
|
-
const contextFile =
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
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 ?
|
|
71387
|
-
if (!projectDir && existsSync25(
|
|
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 =
|
|
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 =
|
|
71409
|
-
const prdPath =
|
|
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
|
|
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 =
|
|
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 ?
|
|
71516
|
-
const outputDir = options.output ?
|
|
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
|
|
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
|
|
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 =
|
|
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 ?
|
|
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
|
|
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
|
|
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
|
|
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
|
|
72354
|
+
import { join as join39 } from "path";
|
|
72137
72355
|
function getRunsDir() {
|
|
72138
|
-
return process.env.NAX_RUNS_DIR ??
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
72392
|
-
const runsDir =
|
|
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
|
|
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 =
|
|
72440
|
-
const featureDir =
|
|
72441
|
-
const prdPath =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
80420
|
-
mkdirSync5(
|
|
80421
|
-
await Bun.write(
|
|
80422
|
-
await Bun.write(
|
|
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(
|
|
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(
|
|
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 =
|
|
80562
|
-
const prdPath =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
80955
|
+
const featureDir = join56(naxDir, "features", name);
|
|
80738
80956
|
mkdirSync5(featureDir, { recursive: true });
|
|
80739
|
-
await Bun.write(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(`
|