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