@nathapp/nax 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nax.js CHANGED
@@ -2552,22 +2552,9 @@ var require_commander = __commonJS((exports) => {
2552
2552
  exports.InvalidOptionArgumentError = InvalidArgumentError;
2553
2553
  });
2554
2554
 
2555
- // src/agents/types.ts
2556
- var CompleteError;
2557
- var init_types = __esm(() => {
2558
- CompleteError = class CompleteError extends Error {
2559
- exitCode;
2560
- constructor(message, exitCode) {
2561
- super(message);
2562
- this.exitCode = exitCode;
2563
- this.name = "CompleteError";
2564
- }
2565
- };
2566
- });
2567
-
2568
2555
  // src/logging/types.ts
2569
2556
  var EMOJI;
2570
- var init_types2 = __esm(() => {
2557
+ var init_types = __esm(() => {
2571
2558
  EMOJI = {
2572
2559
  success: "\u2713",
2573
2560
  failure: "\u2717",
@@ -2820,13 +2807,13 @@ function createNoopChalk() {
2820
2807
  }
2821
2808
  var init_formatter = __esm(() => {
2822
2809
  init_source();
2823
- init_types2();
2810
+ init_types();
2824
2811
  });
2825
2812
 
2826
2813
  // src/logging/index.ts
2827
2814
  var init_logging = __esm(() => {
2828
2815
  init_formatter();
2829
- init_types2();
2816
+ init_types();
2830
2817
  });
2831
2818
 
2832
2819
  // src/logger/formatters.ts
@@ -3037,6 +3024,636 @@ var init_logger2 = __esm(() => {
3037
3024
  init_formatters();
3038
3025
  });
3039
3026
 
3027
+ // src/acceptance/generator.ts
3028
+ function parseAcceptanceCriteria(specContent) {
3029
+ const criteria = [];
3030
+ const lines = specContent.split(`
3031
+ `);
3032
+ for (let i = 0;i < lines.length; i++) {
3033
+ const line = lines[i];
3034
+ const lineNumber = i + 1;
3035
+ const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
3036
+ if (acMatch) {
3037
+ const id = acMatch[1].toUpperCase();
3038
+ const text = acMatch[2].trim();
3039
+ criteria.push({
3040
+ id,
3041
+ text,
3042
+ lineNumber
3043
+ });
3044
+ }
3045
+ }
3046
+ return criteria;
3047
+ }
3048
+ function buildAcceptanceTestPrompt(criteria, featureName, codebaseContext) {
3049
+ const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join(`
3050
+ `);
3051
+ return `You are a test engineer. Generate acceptance tests for the "${featureName}" feature based on the acceptance criteria below.
3052
+
3053
+ CODEBASE CONTEXT:
3054
+ ${codebaseContext}
3055
+
3056
+ ACCEPTANCE CRITERIA:
3057
+ ${criteriaList}
3058
+
3059
+ Generate a complete acceptance.test.ts file using bun:test framework. Follow these rules:
3060
+
3061
+ 1. **One test per AC**: Each acceptance criterion maps to exactly one test
3062
+ 2. **Test observable behavior only**: No implementation details, only user-facing behavior
3063
+ 3. **Independent tests**: No shared state between tests
3064
+ 4. **Integration-level**: Tests should be runnable without mocking (use real implementations)
3065
+ 5. **Clear test names**: Use format "AC-N: <description>" for test names
3066
+ 6. **Async where needed**: Use async/await for operations that may be asynchronous
3067
+
3068
+ Use this structure:
3069
+
3070
+ \`\`\`typescript
3071
+ import { describe, test, expect } from "bun:test";
3072
+
3073
+ describe("${featureName} - Acceptance Tests", () => {
3074
+ test("AC-1: <description>", async () => {
3075
+ // Test implementation
3076
+ });
3077
+
3078
+ test("AC-2: <description>", async () => {
3079
+ // Test implementation
3080
+ });
3081
+ });
3082
+ \`\`\`
3083
+
3084
+ **Important**:
3085
+ - Import the feature code being tested
3086
+ - Set up any necessary test fixtures
3087
+ - Use expect() assertions to verify behavior
3088
+ - Clean up resources if needed (close connections, delete temp files)
3089
+
3090
+ Respond with ONLY the TypeScript test code (no markdown code fences, no explanation).`;
3091
+ }
3092
+ async function generateAcceptanceTests(adapter, options) {
3093
+ const logger = getLogger();
3094
+ const criteria = parseAcceptanceCriteria(options.specContent);
3095
+ if (criteria.length === 0) {
3096
+ logger.warn("acceptance", "\u26A0 No acceptance criteria found in spec.md");
3097
+ return {
3098
+ testCode: generateSkeletonTests(options.featureName, []),
3099
+ criteria: []
3100
+ };
3101
+ }
3102
+ logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
3103
+ const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
3104
+ try {
3105
+ const skipPerms = options.config.quality?.dangerouslySkipPermissions ?? true;
3106
+ const permArgs = skipPerms ? ["--dangerously-skip-permissions"] : [];
3107
+ const cmd = [adapter.binary, "--model", options.modelDef.model, ...permArgs, "-p", prompt];
3108
+ const proc = Bun.spawn(cmd, {
3109
+ cwd: options.workdir,
3110
+ stdout: "pipe",
3111
+ stderr: "pipe",
3112
+ env: {
3113
+ ...process.env,
3114
+ ...options.modelDef.env || {}
3115
+ }
3116
+ });
3117
+ const exitCode = await proc.exited;
3118
+ const stdout = await new Response(proc.stdout).text();
3119
+ const stderr = await new Response(proc.stderr).text();
3120
+ if (exitCode !== 0) {
3121
+ logger.warn("acceptance", "\u26A0 Agent test generation failed", { stderr });
3122
+ return {
3123
+ testCode: generateSkeletonTests(options.featureName, criteria),
3124
+ criteria
3125
+ };
3126
+ }
3127
+ const testCode = extractTestCode(stdout);
3128
+ return {
3129
+ testCode,
3130
+ criteria
3131
+ };
3132
+ } catch (error) {
3133
+ logger.warn("acceptance", "\u26A0 Agent test generation error", { error: error.message });
3134
+ return {
3135
+ testCode: generateSkeletonTests(options.featureName, criteria),
3136
+ criteria
3137
+ };
3138
+ }
3139
+ }
3140
+ function extractTestCode(output) {
3141
+ const fenceMatch = output.match(/```(?:typescript|ts)?\s*([\s\S]*?)\s*```/);
3142
+ if (fenceMatch) {
3143
+ return fenceMatch[1].trim();
3144
+ }
3145
+ const importMatch = output.match(/import\s+{[\s\S]+/);
3146
+ if (importMatch) {
3147
+ return importMatch[0].trim();
3148
+ }
3149
+ return output.trim();
3150
+ }
3151
+ function generateSkeletonTests(featureName, criteria) {
3152
+ const tests = criteria.map((ac) => {
3153
+ return ` test("${ac.id}: ${ac.text}", async () => {
3154
+ // TODO: Implement acceptance test for ${ac.id}
3155
+ // ${ac.text}
3156
+ expect(true).toBe(false); // Replace with actual test
3157
+ });`;
3158
+ }).join(`
3159
+
3160
+ `);
3161
+ return `import { describe, test, expect } from "bun:test";
3162
+
3163
+ describe("${featureName} - Acceptance Tests", () => {
3164
+ ${tests || " // No acceptance criteria found"}
3165
+ });
3166
+ `;
3167
+ }
3168
+ var init_generator = __esm(() => {
3169
+ init_logger2();
3170
+ });
3171
+
3172
+ // src/acceptance/fix-generator.ts
3173
+ function findRelatedStories(failedAC, prd) {
3174
+ const relatedStoryIds = [];
3175
+ for (const story of prd.userStories) {
3176
+ for (const ac of story.acceptanceCriteria) {
3177
+ if (ac.includes(failedAC)) {
3178
+ relatedStoryIds.push(story.id);
3179
+ break;
3180
+ }
3181
+ }
3182
+ }
3183
+ if (relatedStoryIds.length > 0) {
3184
+ return relatedStoryIds;
3185
+ }
3186
+ const passedStories = prd.userStories.filter((s) => s.status === "passed").map((s) => s.id);
3187
+ return passedStories.slice(0, 5);
3188
+ }
3189
+ function buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd) {
3190
+ const relatedStoriesText = relatedStories.map((id) => {
3191
+ const story = prd.userStories.find((s) => s.id === id);
3192
+ if (!story)
3193
+ return "";
3194
+ return `${story.id}: ${story.title}
3195
+ ${story.description}`;
3196
+ }).filter(Boolean).join(`
3197
+
3198
+ `);
3199
+ return `You are a debugging expert. A feature acceptance test has failed.
3200
+
3201
+ FAILED ACCEPTANCE CRITERION:
3202
+ ${failedAC}: ${acText}
3203
+
3204
+ TEST FAILURE OUTPUT:
3205
+ ${testOutput}
3206
+
3207
+ RELATED STORIES (implemented this functionality):
3208
+ ${relatedStoriesText}
3209
+
3210
+ Your task: Generate a fix story description that will make the acceptance test pass.
3211
+
3212
+ Requirements:
3213
+ 1. Analyze the test failure to understand the root cause
3214
+ 2. Identify what needs to change in the code
3215
+ 3. Write a clear, actionable fix description (2-4 sentences)
3216
+ 4. Focus on the specific issue, not general improvements
3217
+ 5. Reference the relevant story IDs if needed
3218
+
3219
+ Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
3220
+ }
3221
+ async function generateFixStories(adapter, options) {
3222
+ const { failedACs, testOutput, prd, specContent, workdir, modelDef } = options;
3223
+ const fixStories = [];
3224
+ const acTextMap = parseACTextFromSpec(specContent);
3225
+ const logger = getLogger();
3226
+ for (let i = 0;i < failedACs.length; i++) {
3227
+ const failedAC = failedACs[i];
3228
+ const acText = acTextMap[failedAC] || "No description available";
3229
+ logger.info("acceptance", "Generating fix for failed AC", { failedAC });
3230
+ const relatedStories = findRelatedStories(failedAC, prd);
3231
+ if (relatedStories.length === 0) {
3232
+ logger.warn("acceptance", "\u26A0 No related stories found for failed AC \u2014 skipping", { failedAC });
3233
+ continue;
3234
+ }
3235
+ const prompt = buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd);
3236
+ try {
3237
+ const skipPerms = options.config.quality?.dangerouslySkipPermissions ?? true;
3238
+ const permArgs = skipPerms ? ["--dangerously-skip-permissions"] : [];
3239
+ const cmd = [adapter.binary, "--model", modelDef.model, ...permArgs, "-p", prompt];
3240
+ const proc = Bun.spawn(cmd, {
3241
+ cwd: workdir,
3242
+ stdout: "pipe",
3243
+ stderr: "pipe",
3244
+ env: {
3245
+ ...process.env,
3246
+ ...modelDef.env || {}
3247
+ }
3248
+ });
3249
+ const exitCode = await proc.exited;
3250
+ const stdout = await new Response(proc.stdout).text();
3251
+ const stderr = await new Response(proc.stderr).text();
3252
+ if (exitCode !== 0) {
3253
+ logger.warn("acceptance", "\u26A0 Agent fix generation failed", { failedAC, stderr });
3254
+ fixStories.push({
3255
+ id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
3256
+ title: `Fix: ${failedAC}`,
3257
+ failedAC,
3258
+ testOutput,
3259
+ relatedStories,
3260
+ description: `Fix the implementation to make ${failedAC} pass. Related stories: ${relatedStories.join(", ")}.`
3261
+ });
3262
+ continue;
3263
+ }
3264
+ const fixDescription = stdout.trim();
3265
+ fixStories.push({
3266
+ id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
3267
+ title: `Fix: ${failedAC} \u2014 ${acText.slice(0, 50)}`,
3268
+ failedAC,
3269
+ testOutput,
3270
+ relatedStories,
3271
+ description: fixDescription
3272
+ });
3273
+ logger.info("acceptance", "\u2713 Generated fix story", { storyId: fixStories[fixStories.length - 1].id });
3274
+ } catch (error) {
3275
+ logger.warn("acceptance", "\u26A0 Error generating fix", {
3276
+ failedAC,
3277
+ error: error.message
3278
+ });
3279
+ fixStories.push({
3280
+ id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
3281
+ title: `Fix: ${failedAC}`,
3282
+ failedAC,
3283
+ testOutput,
3284
+ relatedStories,
3285
+ description: `Fix the implementation to make ${failedAC} pass. Related stories: ${relatedStories.join(", ")}.`
3286
+ });
3287
+ }
3288
+ }
3289
+ return fixStories;
3290
+ }
3291
+ function parseACTextFromSpec(specContent) {
3292
+ const map = {};
3293
+ const lines = specContent.split(`
3294
+ `);
3295
+ for (const line of lines) {
3296
+ const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
3297
+ if (acMatch) {
3298
+ const id = acMatch[1].toUpperCase();
3299
+ const text = acMatch[2].trim();
3300
+ map[id] = text;
3301
+ }
3302
+ }
3303
+ return map;
3304
+ }
3305
+ function convertFixStoryToUserStory(fixStory) {
3306
+ return {
3307
+ id: fixStory.id,
3308
+ title: fixStory.title,
3309
+ description: fixStory.description,
3310
+ acceptanceCriteria: [`Fix ${fixStory.failedAC}`],
3311
+ tags: ["fix", "acceptance-failure"],
3312
+ dependencies: fixStory.relatedStories,
3313
+ status: "pending",
3314
+ passes: false,
3315
+ escalations: [],
3316
+ attempts: 0,
3317
+ contextFiles: []
3318
+ };
3319
+ }
3320
+ var init_fix_generator = __esm(() => {
3321
+ init_logger2();
3322
+ });
3323
+
3324
+ // src/acceptance/index.ts
3325
+ var init_acceptance = __esm(() => {
3326
+ init_generator();
3327
+ init_fix_generator();
3328
+ });
3329
+
3330
+ // src/agents/types.ts
3331
+ var CompleteError;
3332
+ var init_types2 = __esm(() => {
3333
+ CompleteError = class CompleteError extends Error {
3334
+ exitCode;
3335
+ constructor(message, exitCode) {
3336
+ super(message);
3337
+ this.exitCode = exitCode;
3338
+ this.name = "CompleteError";
3339
+ }
3340
+ };
3341
+ });
3342
+
3343
+ // src/agents/adapters/aider.ts
3344
+ class AiderAdapter {
3345
+ name = "aider";
3346
+ displayName = "Aider";
3347
+ binary = "aider";
3348
+ capabilities = {
3349
+ supportedTiers: ["balanced"],
3350
+ maxContextTokens: 20000,
3351
+ features: new Set(["refactor"])
3352
+ };
3353
+ async isInstalled() {
3354
+ const path = _aiderCompleteDeps.which("aider");
3355
+ return path !== null;
3356
+ }
3357
+ buildCommand(options) {
3358
+ return ["aider", "--message", options.prompt, "--yes"];
3359
+ }
3360
+ async run(options) {
3361
+ const cmd = this.buildCommand(options);
3362
+ const startTime = Date.now();
3363
+ const proc = _aiderCompleteDeps.spawn(cmd, {
3364
+ stdout: "pipe",
3365
+ stderr: "inherit"
3366
+ });
3367
+ const exitCode = await proc.exited;
3368
+ const stdout = await new Response(proc.stdout).text();
3369
+ const durationMs = Date.now() - startTime;
3370
+ return {
3371
+ success: exitCode === 0,
3372
+ exitCode,
3373
+ output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS),
3374
+ rateLimited: false,
3375
+ durationMs,
3376
+ estimatedCost: 0,
3377
+ pid: proc.pid
3378
+ };
3379
+ }
3380
+ async complete(prompt, options) {
3381
+ const cmd = ["aider", "--message", prompt, "--yes"];
3382
+ if (options?.model) {
3383
+ cmd.push("--model", options.model);
3384
+ }
3385
+ const proc = _aiderCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
3386
+ const exitCode = await proc.exited;
3387
+ const stdout = await new Response(proc.stdout).text();
3388
+ const stderr = await new Response(proc.stderr).text();
3389
+ const trimmed = stdout.trim();
3390
+ if (exitCode !== 0) {
3391
+ const errorDetails = stderr.trim() || trimmed;
3392
+ const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
3393
+ throw new CompleteError(errorMessage, exitCode);
3394
+ }
3395
+ if (!trimmed) {
3396
+ throw new CompleteError("complete() returned empty output");
3397
+ }
3398
+ return trimmed;
3399
+ }
3400
+ async plan(_options) {
3401
+ throw new Error("AiderAdapter.plan() not implemented");
3402
+ }
3403
+ async decompose(_options) {
3404
+ throw new Error("AiderAdapter.decompose() not implemented");
3405
+ }
3406
+ }
3407
+ var _aiderCompleteDeps, MAX_AGENT_OUTPUT_CHARS = 5000;
3408
+ var init_aider = __esm(() => {
3409
+ init_types2();
3410
+ _aiderCompleteDeps = {
3411
+ which(name) {
3412
+ return Bun.which(name);
3413
+ },
3414
+ spawn(cmd, opts) {
3415
+ return Bun.spawn(cmd, opts);
3416
+ }
3417
+ };
3418
+ });
3419
+
3420
+ // src/agents/adapters/codex.ts
3421
+ class CodexAdapter {
3422
+ name = "codex";
3423
+ displayName = "Codex";
3424
+ binary = "codex";
3425
+ capabilities = {
3426
+ supportedTiers: ["fast", "balanced"],
3427
+ maxContextTokens: 8000,
3428
+ features: new Set(["tdd", "refactor"])
3429
+ };
3430
+ async isInstalled() {
3431
+ const path = _codexRunDeps.which("codex");
3432
+ return path !== null;
3433
+ }
3434
+ buildCommand(options) {
3435
+ return ["codex", "-q", "--prompt", options.prompt];
3436
+ }
3437
+ async run(options) {
3438
+ const cmd = this.buildCommand(options);
3439
+ const startTime = Date.now();
3440
+ const proc = _codexRunDeps.spawn(cmd, {
3441
+ cwd: options.workdir,
3442
+ stdout: "pipe",
3443
+ stderr: "inherit"
3444
+ });
3445
+ const exitCode = await proc.exited;
3446
+ const stdout = await new Response(proc.stdout).text();
3447
+ const durationMs = Date.now() - startTime;
3448
+ return {
3449
+ success: exitCode === 0,
3450
+ exitCode,
3451
+ output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS2),
3452
+ rateLimited: false,
3453
+ durationMs,
3454
+ estimatedCost: 0,
3455
+ pid: proc.pid
3456
+ };
3457
+ }
3458
+ async complete(prompt, _options) {
3459
+ const cmd = ["codex", "-q", "--prompt", prompt];
3460
+ const proc = _codexCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
3461
+ const exitCode = await proc.exited;
3462
+ const stdout = await new Response(proc.stdout).text();
3463
+ const stderr = await new Response(proc.stderr).text();
3464
+ const trimmed = stdout.trim();
3465
+ if (exitCode !== 0) {
3466
+ const errorDetails = stderr.trim() || trimmed;
3467
+ const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
3468
+ throw new CompleteError(errorMessage, exitCode);
3469
+ }
3470
+ if (!trimmed) {
3471
+ throw new CompleteError("complete() returned empty output");
3472
+ }
3473
+ return trimmed;
3474
+ }
3475
+ async plan(_options) {
3476
+ throw new Error("CodexAdapter.plan() not implemented");
3477
+ }
3478
+ async decompose(_options) {
3479
+ throw new Error("CodexAdapter.decompose() not implemented");
3480
+ }
3481
+ }
3482
+ var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS2 = 5000;
3483
+ var init_codex = __esm(() => {
3484
+ init_types2();
3485
+ _codexRunDeps = {
3486
+ which(name) {
3487
+ return Bun.which(name);
3488
+ },
3489
+ spawn(cmd, opts) {
3490
+ return Bun.spawn(cmd, opts);
3491
+ }
3492
+ };
3493
+ _codexCompleteDeps = {
3494
+ spawn(cmd, opts) {
3495
+ return Bun.spawn(cmd, opts);
3496
+ }
3497
+ };
3498
+ });
3499
+
3500
+ // src/agents/adapters/gemini.ts
3501
+ class GeminiAdapter {
3502
+ name = "gemini";
3503
+ displayName = "Gemini CLI";
3504
+ binary = "gemini";
3505
+ capabilities = {
3506
+ supportedTiers: ["fast", "balanced", "powerful"],
3507
+ maxContextTokens: 1e6,
3508
+ features: new Set(["tdd", "review", "refactor"])
3509
+ };
3510
+ async isInstalled() {
3511
+ const path = _geminiRunDeps.which("gemini");
3512
+ if (path === null) {
3513
+ return false;
3514
+ }
3515
+ try {
3516
+ const proc = _geminiRunDeps.spawn(["gemini", "--version"], {
3517
+ stdout: "pipe",
3518
+ stderr: "pipe"
3519
+ });
3520
+ const exitCode = await proc.exited;
3521
+ if (exitCode !== 0) {
3522
+ return false;
3523
+ }
3524
+ const stdout = await new Response(proc.stdout).text();
3525
+ const lowerOut = stdout.toLowerCase();
3526
+ if (lowerOut.includes("not logged in")) {
3527
+ return false;
3528
+ }
3529
+ return true;
3530
+ } catch {
3531
+ return false;
3532
+ }
3533
+ }
3534
+ buildCommand(options) {
3535
+ return ["gemini", "-p", options.prompt];
3536
+ }
3537
+ async run(options) {
3538
+ const cmd = this.buildCommand(options);
3539
+ const startTime = Date.now();
3540
+ const proc = _geminiRunDeps.spawn(cmd, {
3541
+ cwd: options.workdir,
3542
+ stdout: "pipe",
3543
+ stderr: "inherit"
3544
+ });
3545
+ const exitCode = await proc.exited;
3546
+ const stdout = await new Response(proc.stdout).text();
3547
+ const durationMs = Date.now() - startTime;
3548
+ return {
3549
+ success: exitCode === 0,
3550
+ exitCode,
3551
+ output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS3),
3552
+ rateLimited: false,
3553
+ durationMs,
3554
+ estimatedCost: 0,
3555
+ pid: proc.pid
3556
+ };
3557
+ }
3558
+ async complete(prompt, _options) {
3559
+ const cmd = ["gemini", "-p", prompt];
3560
+ const proc = _geminiCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
3561
+ const exitCode = await proc.exited;
3562
+ const stdout = await new Response(proc.stdout).text();
3563
+ const stderr = await new Response(proc.stderr).text();
3564
+ const trimmed = stdout.trim();
3565
+ if (exitCode !== 0) {
3566
+ const errorDetails = stderr.trim() || trimmed;
3567
+ const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
3568
+ throw new CompleteError(errorMessage, exitCode);
3569
+ }
3570
+ if (!trimmed) {
3571
+ throw new CompleteError("complete() returned empty output");
3572
+ }
3573
+ return trimmed;
3574
+ }
3575
+ async plan(_options) {
3576
+ throw new Error("GeminiAdapter.plan() not implemented");
3577
+ }
3578
+ async decompose(_options) {
3579
+ throw new Error("GeminiAdapter.decompose() not implemented");
3580
+ }
3581
+ }
3582
+ var _geminiRunDeps, _geminiCompleteDeps, MAX_AGENT_OUTPUT_CHARS3 = 5000;
3583
+ var init_gemini = __esm(() => {
3584
+ init_types2();
3585
+ _geminiRunDeps = {
3586
+ which(name) {
3587
+ return Bun.which(name);
3588
+ },
3589
+ spawn(cmd, opts) {
3590
+ return Bun.spawn(cmd, opts);
3591
+ }
3592
+ };
3593
+ _geminiCompleteDeps = {
3594
+ spawn(cmd, opts) {
3595
+ return Bun.spawn(cmd, opts);
3596
+ }
3597
+ };
3598
+ });
3599
+
3600
+ // src/agents/adapters/opencode.ts
3601
+ class OpenCodeAdapter {
3602
+ name = "opencode";
3603
+ displayName = "OpenCode";
3604
+ binary = "opencode";
3605
+ capabilities = {
3606
+ supportedTiers: ["fast", "balanced"],
3607
+ maxContextTokens: 8000,
3608
+ features: new Set(["tdd", "refactor"])
3609
+ };
3610
+ async isInstalled() {
3611
+ const path = _opencodeCompleteDeps.which("opencode");
3612
+ return path !== null;
3613
+ }
3614
+ buildCommand(_options) {
3615
+ throw new Error("OpenCodeAdapter.buildCommand() not implemented");
3616
+ }
3617
+ async run(_options) {
3618
+ throw new Error("OpenCodeAdapter.run() not implemented");
3619
+ }
3620
+ async complete(prompt, _options) {
3621
+ const cmd = ["opencode", "--prompt", prompt];
3622
+ const proc = _opencodeCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
3623
+ const exitCode = await proc.exited;
3624
+ const stdout = await new Response(proc.stdout).text();
3625
+ const stderr = await new Response(proc.stderr).text();
3626
+ const trimmed = stdout.trim();
3627
+ if (exitCode !== 0) {
3628
+ const errorDetails = stderr.trim() || trimmed;
3629
+ const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
3630
+ throw new CompleteError(errorMessage, exitCode);
3631
+ }
3632
+ if (!trimmed) {
3633
+ throw new CompleteError("complete() returned empty output");
3634
+ }
3635
+ return trimmed;
3636
+ }
3637
+ async plan(_options) {
3638
+ throw new Error("OpenCodeAdapter.plan() not implemented");
3639
+ }
3640
+ async decompose(_options) {
3641
+ throw new Error("OpenCodeAdapter.decompose() not implemented");
3642
+ }
3643
+ }
3644
+ var _opencodeCompleteDeps;
3645
+ var init_opencode = __esm(() => {
3646
+ init_types2();
3647
+ _opencodeCompleteDeps = {
3648
+ which(name) {
3649
+ return Bun.which(name);
3650
+ },
3651
+ spawn(cmd, opts) {
3652
+ return Bun.spawn(cmd, opts);
3653
+ }
3654
+ };
3655
+ });
3656
+
3040
3657
  // src/execution/pid-registry.ts
3041
3658
  import { existsSync } from "fs";
3042
3659
 
@@ -18032,7 +18649,7 @@ class ClaudeCodeAdapter {
18032
18649
  return {
18033
18650
  success: exitCode === 0 && !timedOut,
18034
18651
  exitCode: actualExitCode,
18035
- output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS),
18652
+ output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS4),
18036
18653
  stderr: stderr.slice(-MAX_AGENT_STDERR_CHARS),
18037
18654
  rateLimited,
18038
18655
  durationMs,
@@ -18168,13 +18785,13 @@ class ClaudeCodeAdapter {
18168
18785
  };
18169
18786
  }
18170
18787
  }
18171
- var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_PERIOD_MS = 5000, _completeDeps, _decomposeDeps, _runOnceDeps;
18788
+ var MAX_AGENT_OUTPUT_CHARS4 = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_PERIOD_MS = 5000, _completeDeps, _decomposeDeps, _runOnceDeps;
18172
18789
  var init_claude = __esm(() => {
18173
18790
  init_pid_registry();
18174
18791
  init_logger2();
18175
18792
  init_claude_plan();
18176
18793
  init_cost();
18177
- init_types();
18794
+ init_types2();
18178
18795
  _completeDeps = {
18179
18796
  spawn(cmd, opts) {
18180
18797
  return Bun.spawn(cmd, opts);
@@ -18192,90 +18809,28 @@ var init_claude = __esm(() => {
18192
18809
  };
18193
18810
  });
18194
18811
 
18195
- // src/agents/adapters/codex.ts
18196
- class CodexAdapter {
18197
- name = "codex";
18198
- displayName = "Codex";
18199
- binary = "codex";
18200
- capabilities = {
18201
- supportedTiers: ["fast", "balanced"],
18202
- maxContextTokens: 8000,
18203
- features: new Set(["tdd", "refactor"])
18204
- };
18205
- async isInstalled() {
18206
- const path = _codexRunDeps.which("codex");
18207
- return path !== null;
18208
- }
18209
- buildCommand(options) {
18210
- return ["codex", "-q", "--prompt", options.prompt];
18211
- }
18212
- async run(options) {
18213
- const cmd = this.buildCommand(options);
18214
- const startTime = Date.now();
18215
- const proc = _codexRunDeps.spawn(cmd, {
18216
- cwd: options.workdir,
18217
- stdout: "pipe",
18218
- stderr: "inherit"
18219
- });
18220
- const exitCode = await proc.exited;
18221
- const stdout = await new Response(proc.stdout).text();
18222
- const durationMs = Date.now() - startTime;
18223
- return {
18224
- success: exitCode === 0,
18225
- exitCode,
18226
- output: stdout.slice(-MAX_AGENT_OUTPUT_CHARS2),
18227
- rateLimited: false,
18228
- durationMs,
18229
- estimatedCost: 0,
18230
- pid: proc.pid
18231
- };
18232
- }
18233
- async complete(prompt, _options) {
18234
- const cmd = ["codex", "-q", "--prompt", prompt];
18235
- const proc = _codexCompleteDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
18236
- const exitCode = await proc.exited;
18237
- const stdout = await new Response(proc.stdout).text();
18238
- const stderr = await new Response(proc.stderr).text();
18239
- const trimmed = stdout.trim();
18240
- if (exitCode !== 0) {
18241
- const errorDetails = stderr.trim() || trimmed;
18242
- const errorMessage = errorDetails || `complete() failed with exit code ${exitCode}`;
18243
- throw new CompleteError(errorMessage, exitCode);
18244
- }
18245
- if (!trimmed) {
18246
- throw new CompleteError("complete() returned empty output");
18247
- }
18248
- return trimmed;
18249
- }
18250
- async plan(_options) {
18251
- throw new Error("CodexAdapter.plan() not implemented");
18252
- }
18253
- async decompose(_options) {
18254
- throw new Error("CodexAdapter.decompose() not implemented");
18255
- }
18256
- }
18257
- var _codexRunDeps, _codexCompleteDeps, MAX_AGENT_OUTPUT_CHARS2 = 5000;
18258
- var init_codex = __esm(() => {
18259
- init_types();
18260
- _codexRunDeps = {
18261
- which(name) {
18262
- return Bun.which(name);
18263
- },
18264
- spawn(cmd, opts) {
18265
- return Bun.spawn(cmd, opts);
18266
- }
18267
- };
18268
- _codexCompleteDeps = {
18269
- spawn(cmd, opts) {
18270
- return Bun.spawn(cmd, opts);
18271
- }
18272
- };
18273
- });
18274
-
18275
18812
  // src/agents/registry.ts
18813
+ var exports_registry = {};
18814
+ __export(exports_registry, {
18815
+ getInstalledAgents: () => getInstalledAgents,
18816
+ getAllAgentNames: () => getAllAgentNames,
18817
+ getAgent: () => getAgent,
18818
+ checkAgentHealth: () => checkAgentHealth,
18819
+ ALL_AGENTS: () => ALL_AGENTS
18820
+ });
18821
+ function getAllAgentNames() {
18822
+ return ALL_AGENTS.map((a) => a.name);
18823
+ }
18276
18824
  function getAgent(name) {
18277
18825
  return ALL_AGENTS.find((a) => a.name === name);
18278
18826
  }
18827
+ async function getInstalledAgents() {
18828
+ const results = await Promise.all(ALL_AGENTS.map(async (agent) => ({
18829
+ agent,
18830
+ installed: await agent.isInstalled()
18831
+ })));
18832
+ return results.filter((r) => r.installed).map((r) => r.agent);
18833
+ }
18279
18834
  async function checkAgentHealth() {
18280
18835
  return Promise.all(ALL_AGENTS.map(async (agent) => ({
18281
18836
  name: agent.name,
@@ -18285,330 +18840,20 @@ async function checkAgentHealth() {
18285
18840
  }
18286
18841
  var ALL_AGENTS;
18287
18842
  var init_registry = __esm(() => {
18843
+ init_aider();
18288
18844
  init_codex();
18845
+ init_gemini();
18846
+ init_opencode();
18289
18847
  init_claude();
18290
18848
  ALL_AGENTS = [
18291
18849
  new ClaudeCodeAdapter,
18292
- new CodexAdapter
18850
+ new CodexAdapter,
18851
+ new OpenCodeAdapter,
18852
+ new GeminiAdapter,
18853
+ new AiderAdapter
18293
18854
  ];
18294
18855
  });
18295
18856
 
18296
- // src/agents/validation.ts
18297
- function validateAgentForTier(agent, tier) {
18298
- return agent.capabilities.supportedTiers.includes(tier);
18299
- }
18300
-
18301
- // src/agents/index.ts
18302
- var init_agents = __esm(() => {
18303
- init_types();
18304
- init_claude();
18305
- init_registry();
18306
- init_cost();
18307
- });
18308
-
18309
- // src/acceptance/generator.ts
18310
- function parseAcceptanceCriteria(specContent) {
18311
- const criteria = [];
18312
- const lines = specContent.split(`
18313
- `);
18314
- for (let i = 0;i < lines.length; i++) {
18315
- const line = lines[i];
18316
- const lineNumber = i + 1;
18317
- const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
18318
- if (acMatch) {
18319
- const id = acMatch[1].toUpperCase();
18320
- const text = acMatch[2].trim();
18321
- criteria.push({
18322
- id,
18323
- text,
18324
- lineNumber
18325
- });
18326
- }
18327
- }
18328
- return criteria;
18329
- }
18330
- function buildAcceptanceTestPrompt(criteria, featureName, codebaseContext) {
18331
- const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join(`
18332
- `);
18333
- return `You are a test engineer. Generate acceptance tests for the "${featureName}" feature based on the acceptance criteria below.
18334
-
18335
- CODEBASE CONTEXT:
18336
- ${codebaseContext}
18337
-
18338
- ACCEPTANCE CRITERIA:
18339
- ${criteriaList}
18340
-
18341
- Generate a complete acceptance.test.ts file using bun:test framework. Follow these rules:
18342
-
18343
- 1. **One test per AC**: Each acceptance criterion maps to exactly one test
18344
- 2. **Test observable behavior only**: No implementation details, only user-facing behavior
18345
- 3. **Independent tests**: No shared state between tests
18346
- 4. **Integration-level**: Tests should be runnable without mocking (use real implementations)
18347
- 5. **Clear test names**: Use format "AC-N: <description>" for test names
18348
- 6. **Async where needed**: Use async/await for operations that may be asynchronous
18349
-
18350
- Use this structure:
18351
-
18352
- \`\`\`typescript
18353
- import { describe, test, expect } from "bun:test";
18354
-
18355
- describe("${featureName} - Acceptance Tests", () => {
18356
- test("AC-1: <description>", async () => {
18357
- // Test implementation
18358
- });
18359
-
18360
- test("AC-2: <description>", async () => {
18361
- // Test implementation
18362
- });
18363
- });
18364
- \`\`\`
18365
-
18366
- **Important**:
18367
- - Import the feature code being tested
18368
- - Set up any necessary test fixtures
18369
- - Use expect() assertions to verify behavior
18370
- - Clean up resources if needed (close connections, delete temp files)
18371
-
18372
- Respond with ONLY the TypeScript test code (no markdown code fences, no explanation).`;
18373
- }
18374
- async function generateAcceptanceTests(adapter, options) {
18375
- const logger = getLogger();
18376
- const criteria = parseAcceptanceCriteria(options.specContent);
18377
- if (criteria.length === 0) {
18378
- logger.warn("acceptance", "\u26A0 No acceptance criteria found in spec.md");
18379
- return {
18380
- testCode: generateSkeletonTests(options.featureName, []),
18381
- criteria: []
18382
- };
18383
- }
18384
- logger.info("acceptance", "Found acceptance criteria", { count: criteria.length });
18385
- const prompt = buildAcceptanceTestPrompt(criteria, options.featureName, options.codebaseContext);
18386
- try {
18387
- const skipPerms = options.config.quality?.dangerouslySkipPermissions ?? true;
18388
- const permArgs = skipPerms ? ["--dangerously-skip-permissions"] : [];
18389
- const cmd = [adapter.binary, "--model", options.modelDef.model, ...permArgs, "-p", prompt];
18390
- const proc = Bun.spawn(cmd, {
18391
- cwd: options.workdir,
18392
- stdout: "pipe",
18393
- stderr: "pipe",
18394
- env: {
18395
- ...process.env,
18396
- ...options.modelDef.env || {}
18397
- }
18398
- });
18399
- const exitCode = await proc.exited;
18400
- const stdout = await new Response(proc.stdout).text();
18401
- const stderr = await new Response(proc.stderr).text();
18402
- if (exitCode !== 0) {
18403
- logger.warn("acceptance", "\u26A0 Agent test generation failed", { stderr });
18404
- return {
18405
- testCode: generateSkeletonTests(options.featureName, criteria),
18406
- criteria
18407
- };
18408
- }
18409
- const testCode = extractTestCode(stdout);
18410
- return {
18411
- testCode,
18412
- criteria
18413
- };
18414
- } catch (error48) {
18415
- logger.warn("acceptance", "\u26A0 Agent test generation error", { error: error48.message });
18416
- return {
18417
- testCode: generateSkeletonTests(options.featureName, criteria),
18418
- criteria
18419
- };
18420
- }
18421
- }
18422
- function extractTestCode(output) {
18423
- const fenceMatch = output.match(/```(?:typescript|ts)?\s*([\s\S]*?)\s*```/);
18424
- if (fenceMatch) {
18425
- return fenceMatch[1].trim();
18426
- }
18427
- const importMatch = output.match(/import\s+{[\s\S]+/);
18428
- if (importMatch) {
18429
- return importMatch[0].trim();
18430
- }
18431
- return output.trim();
18432
- }
18433
- function generateSkeletonTests(featureName, criteria) {
18434
- const tests = criteria.map((ac) => {
18435
- return ` test("${ac.id}: ${ac.text}", async () => {
18436
- // TODO: Implement acceptance test for ${ac.id}
18437
- // ${ac.text}
18438
- expect(true).toBe(false); // Replace with actual test
18439
- });`;
18440
- }).join(`
18441
-
18442
- `);
18443
- return `import { describe, test, expect } from "bun:test";
18444
-
18445
- describe("${featureName} - Acceptance Tests", () => {
18446
- ${tests || " // No acceptance criteria found"}
18447
- });
18448
- `;
18449
- }
18450
- var init_generator = __esm(() => {
18451
- init_logger2();
18452
- });
18453
-
18454
- // src/acceptance/fix-generator.ts
18455
- function findRelatedStories(failedAC, prd) {
18456
- const relatedStoryIds = [];
18457
- for (const story of prd.userStories) {
18458
- for (const ac of story.acceptanceCriteria) {
18459
- if (ac.includes(failedAC)) {
18460
- relatedStoryIds.push(story.id);
18461
- break;
18462
- }
18463
- }
18464
- }
18465
- if (relatedStoryIds.length > 0) {
18466
- return relatedStoryIds;
18467
- }
18468
- const passedStories = prd.userStories.filter((s) => s.status === "passed").map((s) => s.id);
18469
- return passedStories.slice(0, 5);
18470
- }
18471
- function buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd) {
18472
- const relatedStoriesText = relatedStories.map((id) => {
18473
- const story = prd.userStories.find((s) => s.id === id);
18474
- if (!story)
18475
- return "";
18476
- return `${story.id}: ${story.title}
18477
- ${story.description}`;
18478
- }).filter(Boolean).join(`
18479
-
18480
- `);
18481
- return `You are a debugging expert. A feature acceptance test has failed.
18482
-
18483
- FAILED ACCEPTANCE CRITERION:
18484
- ${failedAC}: ${acText}
18485
-
18486
- TEST FAILURE OUTPUT:
18487
- ${testOutput}
18488
-
18489
- RELATED STORIES (implemented this functionality):
18490
- ${relatedStoriesText}
18491
-
18492
- Your task: Generate a fix story description that will make the acceptance test pass.
18493
-
18494
- Requirements:
18495
- 1. Analyze the test failure to understand the root cause
18496
- 2. Identify what needs to change in the code
18497
- 3. Write a clear, actionable fix description (2-4 sentences)
18498
- 4. Focus on the specific issue, not general improvements
18499
- 5. Reference the relevant story IDs if needed
18500
-
18501
- Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
18502
- }
18503
- async function generateFixStories(adapter, options) {
18504
- const { failedACs, testOutput, prd, specContent, workdir, modelDef } = options;
18505
- const fixStories = [];
18506
- const acTextMap = parseACTextFromSpec(specContent);
18507
- const logger = getLogger();
18508
- for (let i = 0;i < failedACs.length; i++) {
18509
- const failedAC = failedACs[i];
18510
- const acText = acTextMap[failedAC] || "No description available";
18511
- logger.info("acceptance", "Generating fix for failed AC", { failedAC });
18512
- const relatedStories = findRelatedStories(failedAC, prd);
18513
- if (relatedStories.length === 0) {
18514
- logger.warn("acceptance", "\u26A0 No related stories found for failed AC \u2014 skipping", { failedAC });
18515
- continue;
18516
- }
18517
- const prompt = buildFixPrompt(failedAC, acText, testOutput, relatedStories, prd);
18518
- try {
18519
- const skipPerms = options.config.quality?.dangerouslySkipPermissions ?? true;
18520
- const permArgs = skipPerms ? ["--dangerously-skip-permissions"] : [];
18521
- const cmd = [adapter.binary, "--model", modelDef.model, ...permArgs, "-p", prompt];
18522
- const proc = Bun.spawn(cmd, {
18523
- cwd: workdir,
18524
- stdout: "pipe",
18525
- stderr: "pipe",
18526
- env: {
18527
- ...process.env,
18528
- ...modelDef.env || {}
18529
- }
18530
- });
18531
- const exitCode = await proc.exited;
18532
- const stdout = await new Response(proc.stdout).text();
18533
- const stderr = await new Response(proc.stderr).text();
18534
- if (exitCode !== 0) {
18535
- logger.warn("acceptance", "\u26A0 Agent fix generation failed", { failedAC, stderr });
18536
- fixStories.push({
18537
- id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
18538
- title: `Fix: ${failedAC}`,
18539
- failedAC,
18540
- testOutput,
18541
- relatedStories,
18542
- description: `Fix the implementation to make ${failedAC} pass. Related stories: ${relatedStories.join(", ")}.`
18543
- });
18544
- continue;
18545
- }
18546
- const fixDescription = stdout.trim();
18547
- fixStories.push({
18548
- id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
18549
- title: `Fix: ${failedAC} \u2014 ${acText.slice(0, 50)}`,
18550
- failedAC,
18551
- testOutput,
18552
- relatedStories,
18553
- description: fixDescription
18554
- });
18555
- logger.info("acceptance", "\u2713 Generated fix story", { storyId: fixStories[fixStories.length - 1].id });
18556
- } catch (error48) {
18557
- logger.warn("acceptance", "\u26A0 Error generating fix", {
18558
- failedAC,
18559
- error: error48.message
18560
- });
18561
- fixStories.push({
18562
- id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
18563
- title: `Fix: ${failedAC}`,
18564
- failedAC,
18565
- testOutput,
18566
- relatedStories,
18567
- description: `Fix the implementation to make ${failedAC} pass. Related stories: ${relatedStories.join(", ")}.`
18568
- });
18569
- }
18570
- }
18571
- return fixStories;
18572
- }
18573
- function parseACTextFromSpec(specContent) {
18574
- const map2 = {};
18575
- const lines = specContent.split(`
18576
- `);
18577
- for (const line of lines) {
18578
- const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
18579
- if (acMatch) {
18580
- const id = acMatch[1].toUpperCase();
18581
- const text = acMatch[2].trim();
18582
- map2[id] = text;
18583
- }
18584
- }
18585
- return map2;
18586
- }
18587
- function convertFixStoryToUserStory(fixStory) {
18588
- return {
18589
- id: fixStory.id,
18590
- title: fixStory.title,
18591
- description: fixStory.description,
18592
- acceptanceCriteria: [`Fix ${fixStory.failedAC}`],
18593
- tags: ["fix", "acceptance-failure"],
18594
- dependencies: fixStory.relatedStories,
18595
- status: "pending",
18596
- passes: false,
18597
- escalations: [],
18598
- attempts: 0,
18599
- contextFiles: []
18600
- };
18601
- }
18602
- var init_fix_generator = __esm(() => {
18603
- init_logger2();
18604
- });
18605
-
18606
- // src/acceptance/index.ts
18607
- var init_acceptance = __esm(() => {
18608
- init_generator();
18609
- init_fix_generator();
18610
- });
18611
-
18612
18857
  // src/decompose/apply.ts
18613
18858
  function applyDecomposition(prd, result) {
18614
18859
  const { subStories } = result;
@@ -20412,7 +20657,7 @@ var package_default;
20412
20657
  var init_package = __esm(() => {
20413
20658
  package_default = {
20414
20659
  name: "@nathapp/nax",
20415
- version: "0.35.0",
20660
+ version: "0.36.0",
20416
20661
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
20417
20662
  type: "module",
20418
20663
  bin: {
@@ -20473,8 +20718,8 @@ var init_version = __esm(() => {
20473
20718
  NAX_VERSION = package_default.version;
20474
20719
  NAX_COMMIT = (() => {
20475
20720
  try {
20476
- if (/^[0-9a-f]{6,10}$/.test("a5e44ea"))
20477
- return "a5e44ea";
20721
+ if (/^[0-9a-f]{6,10}$/.test("78b52b0"))
20722
+ return "78b52b0";
20478
20723
  } catch {}
20479
20724
  try {
20480
20725
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -23778,6 +24023,68 @@ ${pluginMarkdown}` : pluginMarkdown;
23778
24023
  };
23779
24024
  });
23780
24025
 
24026
+ // src/agents/validation.ts
24027
+ function validateAgentForTier(agent, tier) {
24028
+ return agent.capabilities.supportedTiers.includes(tier);
24029
+ }
24030
+
24031
+ // src/agents/version-detection.ts
24032
+ async function getAgentVersion(binaryName) {
24033
+ try {
24034
+ const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
24035
+ stdout: "pipe",
24036
+ stderr: "pipe"
24037
+ });
24038
+ const exitCode = await proc.exited;
24039
+ if (exitCode !== 0) {
24040
+ return null;
24041
+ }
24042
+ const stdout = await new Response(proc.stdout).text();
24043
+ const versionLine = stdout.trim().split(`
24044
+ `)[0];
24045
+ const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
24046
+ if (versionMatch) {
24047
+ return versionMatch[0];
24048
+ }
24049
+ return versionLine || null;
24050
+ } catch {
24051
+ return null;
24052
+ }
24053
+ }
24054
+ async function getAgentVersions() {
24055
+ const agents = await getInstalledAgents();
24056
+ const agentsByName = new Map(agents.map((a) => [a.name, a]));
24057
+ const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
24058
+ const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
24059
+ const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
24060
+ return {
24061
+ name: agent.name,
24062
+ displayName: agent.displayName,
24063
+ version: version2,
24064
+ installed: agentsByName.has(agent.name)
24065
+ };
24066
+ }));
24067
+ return versions2;
24068
+ }
24069
+ var _versionDetectionDeps;
24070
+ var init_version_detection = __esm(() => {
24071
+ init_registry();
24072
+ _versionDetectionDeps = {
24073
+ spawn(cmd, opts) {
24074
+ return Bun.spawn(cmd, opts);
24075
+ }
24076
+ };
24077
+ });
24078
+
24079
+ // src/agents/index.ts
24080
+ var init_agents = __esm(() => {
24081
+ init_types2();
24082
+ init_claude();
24083
+ init_registry();
24084
+ init_cost();
24085
+ init_version_detection();
24086
+ });
24087
+
23781
24088
  // src/tdd/isolation.ts
23782
24089
  function isTestFile(filePath) {
23783
24090
  return TEST_PATTERNS.some((pattern) => pattern.test(filePath));
@@ -23935,7 +24242,38 @@ async function hasCommitsForStory(workdir, storyId, maxCommits = 20) {
23935
24242
  function detectMergeConflict(output) {
23936
24243
  return output.includes("CONFLICT") || output.includes("conflict");
23937
24244
  }
24245
+ async function autoCommitIfDirty(workdir, stage, role, storyId) {
24246
+ const logger = getSafeLogger();
24247
+ try {
24248
+ const statusProc = Bun.spawn(["git", "status", "--porcelain"], {
24249
+ cwd: workdir,
24250
+ stdout: "pipe",
24251
+ stderr: "pipe"
24252
+ });
24253
+ const statusOutput = await new Response(statusProc.stdout).text();
24254
+ await statusProc.exited;
24255
+ if (!statusOutput.trim())
24256
+ return;
24257
+ logger?.warn(stage, `Agent did not commit after ${role} session \u2014 auto-committing`, {
24258
+ role,
24259
+ storyId,
24260
+ dirtyFiles: statusOutput.trim().split(`
24261
+ `).length
24262
+ });
24263
+ const addProc = Bun.spawn(["git", "add", "-A"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
24264
+ await addProc.exited;
24265
+ const commitProc = Bun.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
24266
+ cwd: workdir,
24267
+ stdout: "pipe",
24268
+ stderr: "pipe"
24269
+ });
24270
+ await commitProc.exited;
24271
+ } catch {}
24272
+ }
23938
24273
  var GIT_TIMEOUT_MS = 1e4;
24274
+ var init_git = __esm(() => {
24275
+ init_logger2();
24276
+ });
23939
24277
  // src/verification/executor.ts
23940
24278
  async function drainWithDeadline(proc, deadlineMs) {
23941
24279
  const EMPTY = Symbol("timeout");
@@ -24512,7 +24850,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
24512
24850
  exitCode: rectifyResult.exitCode
24513
24851
  });
24514
24852
  }
24515
- await autoCommitIfDirty(workdir, "rectification", story.id, logger);
24853
+ await autoCommitIfDirty(workdir, "tdd", "rectification", story.id);
24516
24854
  const rectifyIsolation = lite ? undefined : await verifyImplementerIsolation(workdir, rectifyBeforeRef);
24517
24855
  if (rectifyIsolation && !rectifyIsolation.passed) {
24518
24856
  logger.error("tdd", "Rectification violated isolation", {
@@ -24557,35 +24895,9 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
24557
24895
  logger.info("tdd", "Full suite gate passed", { storyId: story.id });
24558
24896
  return true;
24559
24897
  }
24560
- async function autoCommitIfDirty(workdir, role, storyId, logger) {
24561
- try {
24562
- const statusProc = Bun.spawn(["git", "status", "--porcelain"], {
24563
- cwd: workdir,
24564
- stdout: "pipe",
24565
- stderr: "pipe"
24566
- });
24567
- const statusOutput = await new Response(statusProc.stdout).text();
24568
- await statusProc.exited;
24569
- if (!statusOutput.trim())
24570
- return;
24571
- logger.warn("tdd", `Agent did not commit after ${role} session \u2014 auto-committing`, {
24572
- role,
24573
- storyId,
24574
- dirtyFiles: statusOutput.trim().split(`
24575
- `).length
24576
- });
24577
- const addProc = Bun.spawn(["git", "add", "-A"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
24578
- await addProc.exited;
24579
- const commitProc = Bun.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
24580
- cwd: workdir,
24581
- stdout: "pipe",
24582
- stderr: "pipe"
24583
- });
24584
- await commitProc.exited;
24585
- } catch {}
24586
- }
24587
24898
  var init_rectification_gate = __esm(() => {
24588
24899
  init_config();
24900
+ init_git();
24589
24901
  init_verification();
24590
24902
  init_cleanup();
24591
24903
  init_isolation();
@@ -24939,7 +25251,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
24939
25251
  exitCode: result.exitCode
24940
25252
  });
24941
25253
  }
24942
- await autoCommitIfDirty2(workdir, role, story.id);
25254
+ await autoCommitIfDirty(workdir, "tdd", role, story.id);
24943
25255
  let isolation;
24944
25256
  if (!skipIsolation) {
24945
25257
  if (role === "test-writer") {
@@ -24986,42 +25298,11 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
24986
25298
  estimatedCost: result.estimatedCost
24987
25299
  };
24988
25300
  }
24989
- async function autoCommitIfDirty2(workdir, role, storyId) {
24990
- const logger = getLogger();
24991
- try {
24992
- const statusProc = Bun.spawn(["git", "status", "--porcelain"], {
24993
- cwd: workdir,
24994
- stdout: "pipe",
24995
- stderr: "pipe"
24996
- });
24997
- const statusOutput = await new Response(statusProc.stdout).text();
24998
- await statusProc.exited;
24999
- if (!statusOutput.trim())
25000
- return;
25001
- logger.warn("tdd", `Agent did not commit after ${role} session \u2014 auto-committing`, {
25002
- role,
25003
- storyId,
25004
- dirtyFiles: statusOutput.trim().split(`
25005
- `).length
25006
- });
25007
- const addProc = Bun.spawn(["git", "add", "-A"], {
25008
- cwd: workdir,
25009
- stdout: "pipe",
25010
- stderr: "pipe"
25011
- });
25012
- await addProc.exited;
25013
- const commitProc = Bun.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
25014
- cwd: workdir,
25015
- stdout: "pipe",
25016
- stderr: "pipe"
25017
- });
25018
- await commitProc.exited;
25019
- } catch {}
25020
- }
25021
25301
  var init_session_runner = __esm(() => {
25022
25302
  init_config();
25023
25303
  init_logger2();
25024
25304
  init_prompts2();
25305
+ init_git();
25025
25306
  init_cleanup();
25026
25307
  init_isolation();
25027
25308
  });
@@ -25077,6 +25358,95 @@ function isValidVerdict(obj) {
25077
25358
  return false;
25078
25359
  return true;
25079
25360
  }
25361
+ function coerceVerdict(obj) {
25362
+ try {
25363
+ const verdictStr = String(obj.verdict ?? "").toUpperCase();
25364
+ const approved = verdictStr === "PASS" || verdictStr === "APPROVED" || obj.approved === true;
25365
+ let passCount = 0;
25366
+ let failCount = 0;
25367
+ let allPassing = approved;
25368
+ const summary = obj.verification_summary;
25369
+ if (summary?.test_results && typeof summary.test_results === "string") {
25370
+ const match = summary.test_results.match(/(\d+)\/(\d+)/);
25371
+ if (match) {
25372
+ passCount = Number.parseInt(match[1], 10);
25373
+ const total = Number.parseInt(match[2], 10);
25374
+ failCount = total - passCount;
25375
+ allPassing = failCount === 0;
25376
+ }
25377
+ }
25378
+ if (obj.tests && typeof obj.tests === "object") {
25379
+ const t = obj.tests;
25380
+ if (typeof t.passCount === "number")
25381
+ passCount = t.passCount;
25382
+ if (typeof t.failCount === "number")
25383
+ failCount = t.failCount;
25384
+ if (typeof t.allPassing === "boolean")
25385
+ allPassing = t.allPassing;
25386
+ }
25387
+ const criteria = [];
25388
+ let allMet = approved;
25389
+ const acReview = obj.acceptance_criteria_review;
25390
+ if (acReview) {
25391
+ for (const [key, val] of Object.entries(acReview)) {
25392
+ if (key.startsWith("criterion") && val && typeof val === "object") {
25393
+ const c = val;
25394
+ const met = String(c.status ?? "").toUpperCase() === "SATISFIED" || c.met === true;
25395
+ criteria.push({
25396
+ criterion: String(c.name ?? c.criterion ?? key),
25397
+ met,
25398
+ note: c.evidence ? String(c.evidence).slice(0, 200) : undefined
25399
+ });
25400
+ if (!met)
25401
+ allMet = false;
25402
+ }
25403
+ }
25404
+ }
25405
+ if (obj.acceptanceCriteria && typeof obj.acceptanceCriteria === "object") {
25406
+ const ac = obj.acceptanceCriteria;
25407
+ if (typeof ac.allMet === "boolean")
25408
+ allMet = ac.allMet;
25409
+ if (Array.isArray(ac.criteria)) {
25410
+ for (const c of ac.criteria) {
25411
+ if (c && typeof c === "object") {
25412
+ criteria.push(c);
25413
+ }
25414
+ }
25415
+ }
25416
+ }
25417
+ if (criteria.length === 0 && summary?.acceptance_criteria && typeof summary.acceptance_criteria === "string") {
25418
+ const acMatch = summary.acceptance_criteria.match(/(\d+)\/(\d+)/);
25419
+ if (acMatch) {
25420
+ const met = Number.parseInt(acMatch[1], 10);
25421
+ const total = Number.parseInt(acMatch[2], 10);
25422
+ allMet = met === total;
25423
+ }
25424
+ }
25425
+ let rating = "acceptable";
25426
+ const qualityStr = summary?.code_quality ? String(summary.code_quality).toLowerCase() : obj.quality && typeof obj.quality === "object" ? String(obj.quality.rating ?? "acceptable").toLowerCase() : "acceptable";
25427
+ if (qualityStr === "high" || qualityStr === "good")
25428
+ rating = "good";
25429
+ else if (qualityStr === "low" || qualityStr === "poor")
25430
+ rating = "poor";
25431
+ return {
25432
+ version: 1,
25433
+ approved,
25434
+ tests: { allPassing, passCount, failCount },
25435
+ testModifications: {
25436
+ detected: false,
25437
+ files: [],
25438
+ legitimate: true,
25439
+ reasoning: "Not assessed in free-form verdict"
25440
+ },
25441
+ acceptanceCriteria: { allMet, criteria },
25442
+ quality: { rating, issues: [] },
25443
+ fixes: Array.isArray(obj.fixes) ? obj.fixes : [],
25444
+ reasoning: typeof obj.reasoning === "string" ? obj.reasoning : typeof obj.overall_status === "string" ? obj.overall_status : summary?.overall_status ? String(summary.overall_status) : `Coerced from free-form verdict: ${verdictStr}`
25445
+ };
25446
+ } catch {
25447
+ return null;
25448
+ }
25449
+ }
25080
25450
  async function readVerdict(workdir) {
25081
25451
  const logger = getLogger();
25082
25452
  const verdictPath = path8.join(workdir, VERDICT_FILE);
@@ -25096,14 +25466,26 @@ async function readVerdict(workdir) {
25096
25466
  });
25097
25467
  return null;
25098
25468
  }
25099
- if (!isValidVerdict(parsed)) {
25100
- logger.warn("tdd", "Verifier verdict file missing required fields \u2014 ignoring", {
25101
- path: verdictPath,
25102
- content: JSON.stringify(parsed).slice(0, 500)
25103
- });
25104
- return null;
25469
+ if (isValidVerdict(parsed)) {
25470
+ return parsed;
25471
+ }
25472
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
25473
+ const coerced = coerceVerdict(parsed);
25474
+ if (coerced) {
25475
+ logger.info("tdd", "Coerced free-form verdict to structured format", {
25476
+ path: verdictPath,
25477
+ approved: coerced.approved,
25478
+ passCount: coerced.tests.passCount,
25479
+ failCount: coerced.tests.failCount
25480
+ });
25481
+ return coerced;
25482
+ }
25105
25483
  }
25106
- return parsed;
25484
+ logger.warn("tdd", "Verifier verdict file missing required fields and coercion failed \u2014 ignoring", {
25485
+ path: verdictPath,
25486
+ content: JSON.stringify(parsed).slice(0, 500)
25487
+ });
25488
+ return null;
25107
25489
  } catch (err) {
25108
25490
  logger.warn("tdd", "Failed to read verifier verdict file \u2014 ignoring", {
25109
25491
  path: verdictPath,
@@ -25424,6 +25806,7 @@ var init_orchestrator2 = __esm(() => {
25424
25806
  init_config();
25425
25807
  init_greenfield();
25426
25808
  init_logger2();
25809
+ init_git();
25427
25810
  init_verification();
25428
25811
  init_rectification_gate();
25429
25812
  init_session_runner();
@@ -25469,34 +25852,6 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
25469
25852
  reason: reviewReason || "Three-session TDD requires review"
25470
25853
  };
25471
25854
  }
25472
- async function autoCommitIfDirty3(workdir, role, storyId) {
25473
- try {
25474
- const statusProc = Bun.spawn(["git", "status", "--porcelain"], {
25475
- cwd: workdir,
25476
- stdout: "pipe",
25477
- stderr: "pipe"
25478
- });
25479
- const statusOutput = await new Response(statusProc.stdout).text();
25480
- await statusProc.exited;
25481
- if (!statusOutput.trim())
25482
- return;
25483
- const logger = getLogger();
25484
- logger.warn("execution", `Agent did not commit after ${role} session \u2014 auto-committing`, {
25485
- role,
25486
- storyId,
25487
- dirtyFiles: statusOutput.trim().split(`
25488
- `).length
25489
- });
25490
- const addProc = Bun.spawn(["git", "add", "-A"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
25491
- await addProc.exited;
25492
- const commitProc = Bun.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
25493
- cwd: workdir,
25494
- stdout: "pipe",
25495
- stderr: "pipe"
25496
- });
25497
- await commitProc.exited;
25498
- } catch {}
25499
- }
25500
25855
  var executionStage, _executionDeps;
25501
25856
  var init_execution = __esm(() => {
25502
25857
  init_agents();
@@ -25504,6 +25859,7 @@ var init_execution = __esm(() => {
25504
25859
  init_triggers();
25505
25860
  init_logger2();
25506
25861
  init_tdd();
25862
+ init_git();
25507
25863
  executionStage = {
25508
25864
  name: "execution",
25509
25865
  enabled: () => true,
@@ -25578,7 +25934,7 @@ var init_execution = __esm(() => {
25578
25934
  dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions
25579
25935
  });
25580
25936
  ctx.agentResult = result;
25581
- await autoCommitIfDirty3(ctx.workdir, "single-session", ctx.story.id);
25937
+ await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
25582
25938
  const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
25583
25939
  if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
25584
25940
  const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
@@ -26616,6 +26972,7 @@ function reverseMapTestToSource(testFiles, workdir) {
26616
26972
  }
26617
26973
  var _smartRunnerDeps;
26618
26974
  var init_smart_runner = __esm(() => {
26975
+ init_git();
26619
26976
  _smartRunnerDeps = {
26620
26977
  getChangedSourceFiles,
26621
26978
  mapSourceToTests,
@@ -26851,6 +27208,7 @@ async function runDecompose(story, prd, config2, _workdir) {
26851
27208
  }
26852
27209
  var routingStage, _routingDeps;
26853
27210
  var init_routing2 = __esm(() => {
27211
+ init_registry();
26854
27212
  init_greenfield();
26855
27213
  init_builder2();
26856
27214
  init_triggers();
@@ -26863,6 +27221,8 @@ var init_routing2 = __esm(() => {
26863
27221
  enabled: () => true,
26864
27222
  async execute(ctx) {
26865
27223
  const logger = getLogger();
27224
+ const agentName = ctx.config.execution?.agent ?? "claude";
27225
+ const adapter = _routingDeps.getAgent(agentName);
26866
27226
  const hasExistingRouting = ctx.story.routing !== undefined;
26867
27227
  const hasContentHash = ctx.story.routing?.contentHash !== undefined;
26868
27228
  let currentHash;
@@ -26874,7 +27234,7 @@ var init_routing2 = __esm(() => {
26874
27234
  const isCacheHit = hasExistingRouting && (!hasContentHash || hashMatch);
26875
27235
  let routing;
26876
27236
  if (isCacheHit) {
26877
- routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
27237
+ routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config, adapter }, ctx.workdir, ctx.plugins);
26878
27238
  if (ctx.story.routing?.complexity)
26879
27239
  routing.complexity = ctx.story.routing.complexity;
26880
27240
  if (!hasContentHash && ctx.story.routing?.testStrategy)
@@ -26885,7 +27245,7 @@ var init_routing2 = __esm(() => {
26885
27245
  routing.modelTier = _routingDeps.complexityToModelTier(routing.complexity, ctx.config);
26886
27246
  }
26887
27247
  } else {
26888
- routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
27248
+ routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config, adapter }, ctx.workdir, ctx.plugins);
26889
27249
  currentHash = currentHash ?? _routingDeps.computeStoryContentHash(ctx.story);
26890
27250
  ctx.story.routing = {
26891
27251
  ...ctx.story.routing ?? {},
@@ -26974,7 +27334,8 @@ var init_routing2 = __esm(() => {
26974
27334
  computeStoryContentHash,
26975
27335
  applyDecomposition,
26976
27336
  runDecompose,
26977
- checkStoryOversized
27337
+ checkStoryOversized,
27338
+ getAgent
26978
27339
  };
26979
27340
  });
26980
27341
 
@@ -28065,10 +28426,55 @@ async function checkPromptOverrideFiles(config2, workdir) {
28065
28426
  }
28066
28427
  var init_checks_warnings = () => {};
28067
28428
 
28429
+ // src/precheck/checks-agents.ts
28430
+ async function checkMultiAgentHealth() {
28431
+ try {
28432
+ const versions2 = await getAgentVersions();
28433
+ const installed = versions2.filter((v) => v.installed);
28434
+ const notInstalled = versions2.filter((v) => !v.installed);
28435
+ const lines = [];
28436
+ if (installed.length > 0) {
28437
+ lines.push(`Installed agents (${installed.length}):`);
28438
+ for (const agent of installed) {
28439
+ const versionStr = agent.version ? ` v${agent.version}` : " (version unknown)";
28440
+ lines.push(` \u2022 ${agent.displayName}${versionStr}`);
28441
+ }
28442
+ } else {
28443
+ lines.push("No additional agents detected (using default configured agent)");
28444
+ }
28445
+ if (notInstalled.length > 0) {
28446
+ lines.push(`
28447
+ Available but not installed (${notInstalled.length}):`);
28448
+ for (const agent of notInstalled) {
28449
+ lines.push(` \u2022 ${agent.displayName}`);
28450
+ }
28451
+ }
28452
+ const message = lines.join(`
28453
+ `);
28454
+ return {
28455
+ name: "multi-agent-health",
28456
+ tier: "warning",
28457
+ passed: true,
28458
+ message
28459
+ };
28460
+ } catch (error48) {
28461
+ return {
28462
+ name: "multi-agent-health",
28463
+ tier: "warning",
28464
+ passed: true,
28465
+ message: `Agent detection: ${error48 instanceof Error ? error48.message : "Unknown error"}`
28466
+ };
28467
+ }
28468
+ }
28469
+ var init_checks_agents = __esm(() => {
28470
+ init_version_detection();
28471
+ });
28472
+
28068
28473
  // src/precheck/checks.ts
28069
28474
  var init_checks3 = __esm(() => {
28070
28475
  init_checks_blockers();
28071
28476
  init_checks_warnings();
28477
+ init_checks_agents();
28072
28478
  });
28073
28479
 
28074
28480
  // src/precheck/story-size-gate.ts
@@ -28214,7 +28620,8 @@ async function runPrecheck(config2, prd, options) {
28214
28620
  () => checkPendingStories(prd),
28215
28621
  () => checkOptionalCommands(config2, workdir),
28216
28622
  () => checkGitignoreCoversNax(workdir),
28217
- () => checkPromptOverrideFiles(config2, workdir)
28623
+ () => checkPromptOverrideFiles(config2, workdir),
28624
+ () => checkMultiAgentHealth()
28218
28625
  ];
28219
28626
  for (const checkFn of tier2Checks) {
28220
28627
  const result = await checkFn();
@@ -29364,6 +29771,7 @@ var init_run_initialization = __esm(() => {
29364
29771
  init_errors3();
29365
29772
  init_logger2();
29366
29773
  init_prd();
29774
+ init_git();
29367
29775
  });
29368
29776
 
29369
29777
  // src/execution/lifecycle/run-setup.ts
@@ -30947,6 +31355,7 @@ var init_iteration_runner = __esm(() => {
30947
31355
  init_logger2();
30948
31356
  init_runner();
30949
31357
  init_stages();
31358
+ init_git();
30950
31359
  init_dry_run();
30951
31360
  init_pipeline_result_handler();
30952
31361
  });
@@ -31515,6 +31924,7 @@ var _regressionDeps;
31515
31924
  var init_run_regression = __esm(() => {
31516
31925
  init_logger2();
31517
31926
  init_prd();
31927
+ init_git();
31518
31928
  init_verification();
31519
31929
  init_rectification_loop();
31520
31930
  init_runners();
@@ -62594,9 +63004,6 @@ var {
62594
63004
  Help
62595
63005
  } = import__.default;
62596
63006
 
62597
- // bin/nax.ts
62598
- init_agents();
62599
-
62600
63007
  // src/cli/analyze.ts
62601
63008
  init_acceptance();
62602
63009
  init_registry();
@@ -64717,6 +65124,25 @@ var claudeGenerator = {
64717
65124
  generate: generateClaudeConfig
64718
65125
  };
64719
65126
 
65127
+ // src/context/generators/codex.ts
65128
+ function generateCodexConfig(context) {
65129
+ const header = `# Codex Instructions
65130
+
65131
+ This file is auto-generated from \`nax/context.md\`.
65132
+ DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
65133
+
65134
+ ---
65135
+
65136
+ `;
65137
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
65138
+ return header + metaSection + context.markdown;
65139
+ }
65140
+ var codexGenerator = {
65141
+ name: "codex",
65142
+ outputFile: "codex.md",
65143
+ generate: generateCodexConfig
65144
+ };
65145
+
64720
65146
  // src/context/generators/cursor.ts
64721
65147
  function generateCursorRules(context) {
64722
65148
  const header = `# Project Rules
@@ -64736,6 +65162,25 @@ var cursorGenerator = {
64736
65162
  generate: generateCursorRules
64737
65163
  };
64738
65164
 
65165
+ // src/context/generators/gemini.ts
65166
+ function generateGeminiConfig(context) {
65167
+ const header = `# Gemini CLI Context
65168
+
65169
+ This file is auto-generated from \`nax/context.md\`.
65170
+ DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
65171
+
65172
+ ---
65173
+
65174
+ `;
65175
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
65176
+ return header + metaSection + context.markdown;
65177
+ }
65178
+ var geminiGenerator = {
65179
+ name: "gemini",
65180
+ outputFile: "GEMINI.md",
65181
+ generate: generateGeminiConfig
65182
+ };
65183
+
64739
65184
  // src/context/generators/opencode.ts
64740
65185
  function generateOpencodeConfig(context) {
64741
65186
  const header = `# Agent Instructions
@@ -64779,10 +65224,12 @@ var windsurfGenerator = {
64779
65224
  // src/context/generator.ts
64780
65225
  var GENERATORS = {
64781
65226
  claude: claudeGenerator,
65227
+ codex: codexGenerator,
64782
65228
  opencode: opencodeGenerator,
64783
65229
  cursor: cursorGenerator,
64784
65230
  windsurf: windsurfGenerator,
64785
- aider: aiderGenerator
65231
+ aider: aiderGenerator,
65232
+ gemini: geminiGenerator
64786
65233
  };
64787
65234
  async function loadContextContent(options, config2) {
64788
65235
  if (!existsSync18(options.contextPath)) {
@@ -64834,7 +65281,7 @@ async function generateAll(options, config2) {
64834
65281
  }
64835
65282
 
64836
65283
  // src/cli/generate.ts
64837
- var VALID_AGENTS = ["claude", "opencode", "cursor", "windsurf", "aider"];
65284
+ var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
64838
65285
  async function generateCommand(options) {
64839
65286
  const workdir = process.cwd();
64840
65287
  const contextPath = options.context ? join24(workdir, options.context) : join24(workdir, "nax/context.md");
@@ -64922,19 +65369,19 @@ var FIELD_DESCRIPTIONS = {
64922
65369
  "models.fast": "Fast model for lightweight tasks (e.g., haiku)",
64923
65370
  "models.balanced": "Balanced model for general coding (e.g., sonnet)",
64924
65371
  "models.powerful": "Powerful model for complex tasks (e.g., opus)",
64925
- autoMode: "Auto mode configuration for agent orchestration",
65372
+ autoMode: "Auto mode configuration for agent orchestration. Enables multi-agent routing with model tier selection per task complexity and escalation on failures.",
64926
65373
  "autoMode.enabled": "Enable automatic agent selection and escalation",
64927
- "autoMode.defaultAgent": "Default agent to use (e.g., claude, codex)",
64928
- "autoMode.fallbackOrder": "Fallback order when agent is rate-limited",
64929
- "autoMode.complexityRouting": "Model tier per complexity level",
64930
- "autoMode.complexityRouting.simple": "Model tier for simple tasks",
64931
- "autoMode.complexityRouting.medium": "Model tier for medium tasks",
64932
- "autoMode.complexityRouting.complex": "Model tier for complex tasks",
64933
- "autoMode.complexityRouting.expert": "Model tier for expert tasks",
64934
- "autoMode.escalation": "Escalation settings for failed stories",
65374
+ "autoMode.defaultAgent": "Default agent to use when no specific agent is requested. Examples: 'claude' (Claude Code), 'codex' (GitHub Copilot), 'opencode' (OpenCode). The agent handles the main coding tasks.",
65375
+ "autoMode.fallbackOrder": 'Fallback order for agent selection when the primary agent is rate-limited, unavailable, or fails. Tries each agent in sequence until one succeeds. Example: ["claude", "codex", "opencode"] means try Claude first, then Copilot, then OpenCode.',
65376
+ "autoMode.complexityRouting": "Model tier routing rules mapped to story complexity levels. Determines which model (fast/balanced/powerful) to use based on task complexity: simple \u2192 fast, medium \u2192 balanced, complex \u2192 powerful, expert \u2192 powerful.",
65377
+ "autoMode.complexityRouting.simple": "Model tier for simple tasks (low complexity, straightforward changes)",
65378
+ "autoMode.complexityRouting.medium": "Model tier for medium tasks (moderate complexity, multi-file changes)",
65379
+ "autoMode.complexityRouting.complex": "Model tier for complex tasks (high complexity, architectural decisions)",
65380
+ "autoMode.complexityRouting.expert": "Model tier for expert tasks (highest complexity, novel problems, design patterns)",
65381
+ "autoMode.escalation": "Escalation settings for failed stories. When a story fails after max attempts at current tier, escalate to the next tier in tierOrder. Enables progressive use of more powerful models.",
64935
65382
  "autoMode.escalation.enabled": "Enable tier escalation on failure",
64936
- "autoMode.escalation.tierOrder": "Ordered tier escalation with per-tier attempt budgets",
64937
- "autoMode.escalation.escalateEntireBatch": "Escalate all stories in batch when one fails",
65383
+ "autoMode.escalation.tierOrder": 'Ordered tier escalation chain with per-tier attempt budgets. Format: [{"tier": "fast", "attempts": 2}, {"tier": "balanced", "attempts": 2}, {"tier": "powerful", "attempts": 1}]. Allows each tier to attempt fixes before escalating to the next.',
65384
+ "autoMode.escalation.escalateEntireBatch": "When enabled, escalate all stories in a batch if one fails. When disabled, only the failing story escalates (allows parallel attempts at different tiers).",
64938
65385
  routing: "Model routing strategy configuration",
64939
65386
  "routing.strategy": "Routing strategy: keyword | llm | manual | adaptive | custom",
64940
65387
  "routing.customStrategyPath": "Path to custom routing strategy (if strategy=custom)",
@@ -65243,8 +65690,11 @@ function displayConfigWithDescriptions(obj, path13, sources, indent = 0) {
65243
65690
  const currentPathStr = currentPath.join(".");
65244
65691
  const description = FIELD_DESCRIPTIONS[currentPathStr];
65245
65692
  if (description) {
65246
- const isPromptsSubSection = currentPathStr.startsWith("prompts.");
65247
- const comment = isPromptsSubSection ? `${currentPathStr}: ${description}` : description;
65693
+ const pathParts = currentPathStr.split(".");
65694
+ const isDirectSubsection = pathParts.length === 2;
65695
+ const isKeySection = ["prompts", "autoMode", "models", "routing"].includes(pathParts[0]);
65696
+ const shouldIncludePath = isKeySection && isDirectSubsection;
65697
+ const comment = shouldIncludePath ? `${currentPathStr}: ${description}` : description;
65248
65698
  console.log(`${indentStr}# ${comment}`);
65249
65699
  }
65250
65700
  if (value !== null && typeof value === "object" && !Array.isArray(value)) {
@@ -65312,6 +65762,55 @@ function formatValueForTable(value) {
65312
65762
  }
65313
65763
  return String(value);
65314
65764
  }
65765
+ // src/cli/agents.ts
65766
+ init_registry();
65767
+ init_version_detection();
65768
+ async function agentsListCommand(config2, _workdir) {
65769
+ const agentVersions = await Promise.all(ALL_AGENTS.map(async (agent) => ({
65770
+ name: agent.name,
65771
+ displayName: agent.displayName,
65772
+ binary: agent.binary,
65773
+ version: await getAgentVersion(agent.binary),
65774
+ installed: await agent.isInstalled(),
65775
+ capabilities: agent.capabilities,
65776
+ isDefault: config2.autoMode.defaultAgent === agent.name
65777
+ })));
65778
+ const rows = agentVersions.map((info) => {
65779
+ const status = info.installed ? "installed" : "unavailable";
65780
+ const versionStr = info.version || "-";
65781
+ const defaultMarker = info.isDefault ? " (default)" : "";
65782
+ return {
65783
+ name: info.displayName + defaultMarker,
65784
+ status,
65785
+ version: versionStr,
65786
+ binary: info.binary,
65787
+ tiers: info.capabilities.supportedTiers.join(", ")
65788
+ };
65789
+ });
65790
+ if (rows.length === 0) {
65791
+ console.log("No agents available.");
65792
+ return;
65793
+ }
65794
+ const widths = {
65795
+ name: Math.max(5, ...rows.map((r) => r.name.length)),
65796
+ status: Math.max(6, ...rows.map((r) => r.status.length)),
65797
+ version: Math.max(7, ...rows.map((r) => r.version.length)),
65798
+ binary: Math.max(6, ...rows.map((r) => r.binary.length)),
65799
+ tiers: Math.max(5, ...rows.map((r) => r.tiers.length))
65800
+ };
65801
+ console.log(`
65802
+ Available Agents:
65803
+ `);
65804
+ console.log(`${pad2("Agent", widths.name)} ${pad2("Status", widths.status)} ${pad2("Version", widths.version)} ${pad2("Binary", widths.binary)} ${pad2("Tiers", widths.tiers)}`);
65805
+ console.log(`${"-".repeat(widths.name)} ${"-".repeat(widths.status)} ${"-".repeat(widths.version)} ${"-".repeat(widths.binary)} ${"-".repeat(widths.tiers)}`);
65806
+ for (const row of rows) {
65807
+ console.log(`${pad2(row.name, widths.name)} ${pad2(row.status, widths.status)} ${pad2(row.version, widths.version)} ${pad2(row.binary, widths.binary)} ${pad2(row.tiers, widths.tiers)}`);
65808
+ }
65809
+ console.log();
65810
+ }
65811
+ function pad2(str, width) {
65812
+ return str.padEnd(width);
65813
+ }
65315
65814
  // src/commands/diagnose.ts
65316
65815
  async function diagnose(options) {
65317
65816
  await diagnoseCommand(options);
@@ -65688,7 +66187,7 @@ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
65688
66187
  function visibleLength(str) {
65689
66188
  return str.replace(ANSI_RE, "").length;
65690
66189
  }
65691
- function pad2(str, width) {
66190
+ function pad3(str, width) {
65692
66191
  const padding = Math.max(0, width - visibleLength(str));
65693
66192
  return str + " ".repeat(padding);
65694
66193
  }
@@ -65747,12 +66246,12 @@ async function runsCommand(options = {}) {
65747
66246
  date: 11
65748
66247
  };
65749
66248
  const header = [
65750
- pad2(source_default.bold("RUN ID"), COL.runId),
65751
- pad2(source_default.bold("PROJECT"), COL.project),
65752
- pad2(source_default.bold("FEATURE"), COL.feature),
65753
- pad2(source_default.bold("STATUS"), COL.status),
65754
- pad2(source_default.bold("STORIES"), COL.stories),
65755
- pad2(source_default.bold("DURATION"), COL.duration),
66249
+ pad3(source_default.bold("RUN ID"), COL.runId),
66250
+ pad3(source_default.bold("PROJECT"), COL.project),
66251
+ pad3(source_default.bold("FEATURE"), COL.feature),
66252
+ pad3(source_default.bold("STATUS"), COL.status),
66253
+ pad3(source_default.bold("STORIES"), COL.stories),
66254
+ pad3(source_default.bold("DURATION"), COL.duration),
65756
66255
  source_default.bold("DATE")
65757
66256
  ].join(" ");
65758
66257
  console.log();
@@ -65761,12 +66260,12 @@ async function runsCommand(options = {}) {
65761
66260
  for (const row of displayed) {
65762
66261
  const colored = colorStatus(row.status);
65763
66262
  const line = [
65764
- pad2(row.runId, COL.runId),
65765
- pad2(row.project, COL.project),
65766
- pad2(row.feature, COL.feature),
65767
- pad2(colored, COL.status + (colored.length - visibleLength(colored))),
65768
- pad2(`${row.passed}/${row.total}`, COL.stories),
65769
- pad2(formatDuration3(row.durationMs), COL.duration),
66263
+ pad3(row.runId, COL.runId),
66264
+ pad3(row.project, COL.project),
66265
+ pad3(row.feature, COL.feature),
66266
+ pad3(colored, COL.status + (colored.length - visibleLength(colored))),
66267
+ pad3(`${row.passed}/${row.total}`, COL.stories),
66268
+ pad3(formatDuration3(row.durationMs), COL.duration),
65770
66269
  formatDate(row.registeredAt)
65771
66270
  ].join(" ");
65772
66271
  console.log(line);
@@ -73839,16 +74338,21 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
73839
74338
  process.exit(1);
73840
74339
  }
73841
74340
  });
73842
- program2.command("agents").description("Check available coding agents").action(async () => {
73843
- const health = await checkAgentHealth();
73844
- console.log(source_default.bold(`
73845
- Coding Agents:
73846
- `));
73847
- for (const agent of health) {
73848
- const status = agent.installed ? source_default.green("\u2705 installed") : source_default.red("\u274C not found");
73849
- console.log(` ${agent.displayName.padEnd(15)} ${status}`);
74341
+ program2.command("agents").description("List available coding agents with status and capabilities").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
74342
+ let workdir;
74343
+ try {
74344
+ workdir = validateDirectory(options.dir);
74345
+ } catch (err) {
74346
+ console.error(source_default.red(`Invalid directory: ${err.message}`));
74347
+ process.exit(1);
74348
+ }
74349
+ try {
74350
+ const config2 = await loadConfig(workdir);
74351
+ await agentsListCommand(config2, workdir);
74352
+ } catch (err) {
74353
+ console.error(source_default.red(`Error: ${err.message}`));
74354
+ process.exit(1);
73850
74355
  }
73851
- console.log();
73852
74356
  });
73853
74357
  program2.command("config").description("Display effective merged configuration").option("--explain", "Show detailed field descriptions", false).option("--diff", "Show only fields where project overrides global", false).action(async (options) => {
73854
74358
  try {