@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/bin/nax.ts +18 -9
- package/dist/nax.js +1064 -560
- package/package.json +1 -1
- package/src/agents/adapters/aider.ts +135 -0
- package/src/agents/adapters/gemini.ts +177 -0
- package/src/agents/adapters/opencode.ts +106 -0
- package/src/agents/index.ts +2 -0
- package/src/agents/registry.ts +6 -2
- package/src/agents/version-detection.ts +109 -0
- package/src/cli/agents.ts +87 -0
- package/src/cli/config.ts +28 -14
- package/src/cli/generate.ts +1 -1
- package/src/cli/index.ts +1 -0
- package/src/context/generator.ts +4 -0
- package/src/context/generators/codex.ts +28 -0
- package/src/context/generators/gemini.ts +28 -0
- package/src/context/types.ts +1 -1
- package/src/pipeline/stages/execution.ts +2 -39
- package/src/pipeline/stages/routing.ts +8 -2
- package/src/precheck/checks-agents.ts +63 -0
- package/src/precheck/checks.ts +3 -0
- package/src/precheck/index.ts +2 -0
- package/src/tdd/rectification-gate.ts +2 -46
- package/src/tdd/session-runner.ts +2 -49
- package/src/tdd/verdict.ts +135 -8
- package/src/utils/git.ts +49 -0
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
|
|
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
|
-
|
|
2810
|
+
init_types();
|
|
2824
2811
|
});
|
|
2825
2812
|
|
|
2826
2813
|
// src/logging/index.ts
|
|
2827
2814
|
var init_logging = __esm(() => {
|
|
2828
2815
|
init_formatter();
|
|
2829
|
-
|
|
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(-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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("
|
|
20477
|
-
return "
|
|
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
|
|
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
|
|
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 (
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
64928
|
-
"autoMode.fallbackOrder":
|
|
64929
|
-
"autoMode.complexityRouting": "Model tier
|
|
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":
|
|
64937
|
-
"autoMode.escalation.escalateEntireBatch": "
|
|
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
|
|
65247
|
-
const
|
|
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
|
|
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
|
-
|
|
65751
|
-
|
|
65752
|
-
|
|
65753
|
-
|
|
65754
|
-
|
|
65755
|
-
|
|
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
|
-
|
|
65765
|
-
|
|
65766
|
-
|
|
65767
|
-
|
|
65768
|
-
|
|
65769
|
-
|
|
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("
|
|
73843
|
-
|
|
73844
|
-
|
|
73845
|
-
|
|
73846
|
-
|
|
73847
|
-
|
|
73848
|
-
|
|
73849
|
-
|
|
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 {
|