@nathapp/nax 0.35.0 → 0.36.1
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 +1283 -662
- 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/claude-decompose.ts +3 -3
- 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/constitution.ts +0 -92
- package/src/cli/generate.ts +1 -1
- package/src/cli/index.ts +1 -0
- package/src/constitution/generator.ts +0 -33
- package/src/constitution/index.ts +2 -1
- package/src/constitution/loader.ts +1 -13
- package/src/context/builder.ts +1 -2
- package/src/context/elements.ts +1 -12
- 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/index.ts +2 -1
- package/src/context/test-scanner.ts +1 -1
- package/src/context/types.ts +1 -1
- package/src/interaction/chain.ts +17 -1
- package/src/pipeline/stages/execution.ts +25 -40
- 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/prompts/builder.ts +13 -6
- package/src/prompts/sections/conventions.ts +5 -7
- package/src/prompts/sections/isolation.ts +7 -7
- package/src/prompts/sections/role-task.ts +64 -64
- package/src/review/orchestrator.ts +11 -1
- package/src/routing/strategies/llm-prompts.ts +1 -1
- package/src/routing/strategies/llm.ts +3 -3
- package/src/tdd/index.ts +2 -3
- package/src/tdd/isolation.ts +0 -13
- package/src/tdd/orchestrator.ts +5 -0
- package/src/tdd/prompts.ts +1 -231
- package/src/tdd/rectification-gate.ts +2 -46
- package/src/tdd/session-runner.ts +4 -49
- package/src/tdd/verdict.ts +154 -9
- package/src/utils/git.ts +49 -0
- package/src/verification/parser.ts +0 -10
- package/src/verification/rectification-loop.ts +2 -51
- package/src/worktree/dispatcher.ts +0 -59
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
|
|
|
@@ -3297,7 +3914,7 @@ ${output.slice(0, 500)}`);
|
|
|
3297
3914
|
acceptanceCriteria: Array.isArray(record.acceptanceCriteria) ? record.acceptanceCriteria : ["Implementation complete"],
|
|
3298
3915
|
tags: Array.isArray(record.tags) ? record.tags : [],
|
|
3299
3916
|
dependencies: Array.isArray(record.dependencies) ? record.dependencies : [],
|
|
3300
|
-
complexity:
|
|
3917
|
+
complexity: coerceComplexity(record.complexity),
|
|
3301
3918
|
contextFiles: Array.isArray(record.contextFiles) ? record.contextFiles : Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
|
|
3302
3919
|
relevantFiles: Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
|
|
3303
3920
|
reasoning: String(record.reasoning || "No reasoning provided"),
|
|
@@ -3311,7 +3928,7 @@ ${output.slice(0, 500)}`);
|
|
|
3311
3928
|
}
|
|
3312
3929
|
return stories;
|
|
3313
3930
|
}
|
|
3314
|
-
function
|
|
3931
|
+
function coerceComplexity(value) {
|
|
3315
3932
|
if (value === "simple" || value === "medium" || value === "complex" || value === "expert") {
|
|
3316
3933
|
return value;
|
|
3317
3934
|
}
|
|
@@ -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;
|
|
@@ -19226,7 +19471,7 @@ Your complexity classification will determine the execution strategy:
|
|
|
19226
19471
|
Respond with ONLY this JSON (no markdown, no explanation):
|
|
19227
19472
|
{"complexity":"simple|medium|complex|expert","modelTier":"fast|balanced|powerful","reasoning":"<one line>"}`;
|
|
19228
19473
|
}
|
|
19229
|
-
function
|
|
19474
|
+
function buildBatchRoutingPrompt(stories, config2) {
|
|
19230
19475
|
const storyBlocks = stories.map((story, idx) => {
|
|
19231
19476
|
const criteria = story.acceptanceCriteria.map((c, i) => ` ${i + 1}. ${c}`).join(`
|
|
19232
19477
|
`);
|
|
@@ -19406,7 +19651,7 @@ async function routeBatch(stories, context) {
|
|
|
19406
19651
|
throw new Error("No agent adapter available for batch routing (AA-003)");
|
|
19407
19652
|
}
|
|
19408
19653
|
const modelTier = llmConfig.model ?? "fast";
|
|
19409
|
-
const prompt =
|
|
19654
|
+
const prompt = buildBatchRoutingPrompt(stories, config2);
|
|
19410
19655
|
try {
|
|
19411
19656
|
const output = await callLlm(adapter, modelTier, prompt, config2);
|
|
19412
19657
|
const decisions = parseBatchResponse(output, stories, config2);
|
|
@@ -19828,7 +20073,7 @@ var init_routing = __esm(() => {
|
|
|
19828
20073
|
});
|
|
19829
20074
|
|
|
19830
20075
|
// src/decompose/validators/complexity.ts
|
|
19831
|
-
function
|
|
20076
|
+
function validateComplexity(substories, maxComplexity) {
|
|
19832
20077
|
const errors3 = [];
|
|
19833
20078
|
const warnings = [];
|
|
19834
20079
|
const maxOrder = COMPLEXITY_ORDER[maxComplexity];
|
|
@@ -20140,7 +20385,7 @@ function runAllValidators(originalStory, substories, existingStories, config2) {
|
|
|
20140
20385
|
const results = [
|
|
20141
20386
|
validateOverlap(substories, existingStories),
|
|
20142
20387
|
validateCoverage(originalStory, substories),
|
|
20143
|
-
|
|
20388
|
+
validateComplexity(substories, maxComplexity),
|
|
20144
20389
|
validateDependencies(substories, existingIds)
|
|
20145
20390
|
];
|
|
20146
20391
|
const errors3 = results.flatMap((r) => r.errors);
|
|
@@ -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.1",
|
|
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("b241bab"))
|
|
20722
|
+
return "b241bab";
|
|
20478
20723
|
} catch {}
|
|
20479
20724
|
try {
|
|
20480
20725
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -20847,6 +21092,12 @@ class InteractionChain {
|
|
|
20847
21092
|
async prompt(request) {
|
|
20848
21093
|
await this.send(request);
|
|
20849
21094
|
const response = await this.receive(request.id, request.timeout);
|
|
21095
|
+
if (response.action === "choose" && response.value && request.options) {
|
|
21096
|
+
const matched = request.options.find((o) => o.key === response.value);
|
|
21097
|
+
if (matched) {
|
|
21098
|
+
return { ...response, action: matched.key };
|
|
21099
|
+
}
|
|
21100
|
+
}
|
|
20850
21101
|
return response;
|
|
20851
21102
|
}
|
|
20852
21103
|
async cancel(requestId) {
|
|
@@ -22436,9 +22687,17 @@ class ReviewOrchestrator {
|
|
|
22436
22687
|
const changedFiles = await getChangedFiles(workdir);
|
|
22437
22688
|
const pluginResults = [];
|
|
22438
22689
|
for (const reviewer of reviewers) {
|
|
22439
|
-
logger?.info("review", `Running plugin reviewer: ${reviewer.name}
|
|
22690
|
+
logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
|
|
22691
|
+
changedFiles: changedFiles.length
|
|
22692
|
+
});
|
|
22440
22693
|
try {
|
|
22441
22694
|
const result = await reviewer.check(workdir, changedFiles);
|
|
22695
|
+
logger?.info("review", `Plugin reviewer result: ${reviewer.name}`, {
|
|
22696
|
+
passed: result.passed,
|
|
22697
|
+
exitCode: result.exitCode,
|
|
22698
|
+
output: result.output?.slice(0, 500),
|
|
22699
|
+
findings: result.findings?.length ?? 0
|
|
22700
|
+
});
|
|
22442
22701
|
pluginResults.push({
|
|
22443
22702
|
name: reviewer.name,
|
|
22444
22703
|
passed: result.passed,
|
|
@@ -22457,6 +22716,7 @@ class ReviewOrchestrator {
|
|
|
22457
22716
|
}
|
|
22458
22717
|
} catch (error48) {
|
|
22459
22718
|
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
22719
|
+
logger?.warn("review", `Plugin reviewer threw error: ${reviewer.name}`, { error: errorMsg });
|
|
22460
22720
|
pluginResults.push({ name: reviewer.name, passed: false, output: "", error: errorMsg });
|
|
22461
22721
|
builtIn.pluginReviewers = pluginResults;
|
|
22462
22722
|
return {
|
|
@@ -22714,12 +22974,14 @@ var init_completion = __esm(() => {
|
|
|
22714
22974
|
};
|
|
22715
22975
|
});
|
|
22716
22976
|
|
|
22977
|
+
// src/optimizer/types.ts
|
|
22978
|
+
function estimateTokens(text) {
|
|
22979
|
+
return Math.ceil(text.length / 4);
|
|
22980
|
+
}
|
|
22981
|
+
|
|
22717
22982
|
// src/constitution/loader.ts
|
|
22718
22983
|
import { existsSync as existsSync13 } from "fs";
|
|
22719
22984
|
import { join as join14 } from "path";
|
|
22720
|
-
function estimateTokens(text) {
|
|
22721
|
-
return Math.ceil(text.length / 3);
|
|
22722
|
-
}
|
|
22723
22985
|
function truncateToTokens(text, maxTokens) {
|
|
22724
22986
|
const maxChars = maxTokens * 3;
|
|
22725
22987
|
if (text.length <= maxChars) {
|
|
@@ -22969,32 +23231,29 @@ var init_auto_detect = __esm(() => {
|
|
|
22969
23231
|
});
|
|
22970
23232
|
|
|
22971
23233
|
// src/context/elements.ts
|
|
22972
|
-
function estimateTokens2(text) {
|
|
22973
|
-
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
22974
|
-
}
|
|
22975
23234
|
function createStoryContext(story, priority) {
|
|
22976
23235
|
const content = formatStoryAsText(story);
|
|
22977
|
-
return { type: "story", storyId: story.id, content, priority, tokens:
|
|
23236
|
+
return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
22978
23237
|
}
|
|
22979
23238
|
function createDependencyContext(story, priority) {
|
|
22980
23239
|
const content = formatStoryAsText(story);
|
|
22981
|
-
return { type: "dependency", storyId: story.id, content, priority, tokens:
|
|
23240
|
+
return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
|
|
22982
23241
|
}
|
|
22983
23242
|
function createErrorContext(errorMessage, priority) {
|
|
22984
|
-
return { type: "error", content: errorMessage, priority, tokens:
|
|
23243
|
+
return { type: "error", content: errorMessage, priority, tokens: estimateTokens(errorMessage) };
|
|
22985
23244
|
}
|
|
22986
23245
|
function createProgressContext(progressText, priority) {
|
|
22987
|
-
return { type: "progress", content: progressText, priority, tokens:
|
|
23246
|
+
return { type: "progress", content: progressText, priority, tokens: estimateTokens(progressText) };
|
|
22988
23247
|
}
|
|
22989
23248
|
function createFileContext(filePath, content, priority) {
|
|
22990
|
-
return { type: "file", filePath, content, priority, tokens:
|
|
23249
|
+
return { type: "file", filePath, content, priority, tokens: estimateTokens(content) };
|
|
22991
23250
|
}
|
|
22992
23251
|
function createTestCoverageContext(content, tokens, priority) {
|
|
22993
23252
|
return { type: "test-coverage", content, priority, tokens };
|
|
22994
23253
|
}
|
|
22995
23254
|
function createPriorFailuresContext(failures, priority) {
|
|
22996
23255
|
const content = formatPriorFailures(failures);
|
|
22997
|
-
return { type: "prior-failures", content, priority, tokens:
|
|
23256
|
+
return { type: "prior-failures", content, priority, tokens: estimateTokens(content) };
|
|
22998
23257
|
}
|
|
22999
23258
|
function formatPriorFailures(failures) {
|
|
23000
23259
|
if (!failures || failures.length === 0) {
|
|
@@ -23065,7 +23324,6 @@ function formatStoryAsText(story) {
|
|
|
23065
23324
|
return parts.join(`
|
|
23066
23325
|
`);
|
|
23067
23326
|
}
|
|
23068
|
-
var CHARS_PER_TOKEN = 3;
|
|
23069
23327
|
var init_elements = __esm(() => {
|
|
23070
23328
|
init_logger2();
|
|
23071
23329
|
});
|
|
@@ -23222,7 +23480,7 @@ function truncateToTokenBudget(files, maxTokens, preferredDetail) {
|
|
|
23222
23480
|
for (let i = startIndex;i < detailLevels.length; i++) {
|
|
23223
23481
|
const detail = detailLevels[i];
|
|
23224
23482
|
const summary = formatTestSummary(files, detail);
|
|
23225
|
-
const tokens =
|
|
23483
|
+
const tokens = estimateTokens(summary);
|
|
23226
23484
|
if (tokens <= maxTokens) {
|
|
23227
23485
|
return { summary, detail, truncated: i !== startIndex };
|
|
23228
23486
|
}
|
|
@@ -23232,7 +23490,7 @@ function truncateToTokenBudget(files, maxTokens, preferredDetail) {
|
|
|
23232
23490
|
truncatedFiles = truncatedFiles.slice(0, truncatedFiles.length - 1);
|
|
23233
23491
|
const summary = `${formatTestSummary(truncatedFiles, "names-only")}
|
|
23234
23492
|
... and ${files.length - truncatedFiles.length} more test files`;
|
|
23235
|
-
if (
|
|
23493
|
+
if (estimateTokens(summary) <= maxTokens) {
|
|
23236
23494
|
return { summary, detail: "names-only", truncated: true };
|
|
23237
23495
|
}
|
|
23238
23496
|
}
|
|
@@ -23255,13 +23513,12 @@ async function generateTestCoverageSummary(options) {
|
|
|
23255
23513
|
}
|
|
23256
23514
|
const totalTests = files.reduce((sum, f) => sum + f.testCount, 0);
|
|
23257
23515
|
const { summary } = truncateToTokenBudget(files, maxTokens, detail);
|
|
23258
|
-
const tokens =
|
|
23516
|
+
const tokens = estimateTokens(summary);
|
|
23259
23517
|
return { files, totalTests, summary, tokens };
|
|
23260
23518
|
}
|
|
23261
23519
|
var COMMON_TEST_DIRS;
|
|
23262
23520
|
var init_test_scanner = __esm(() => {
|
|
23263
23521
|
init_logger2();
|
|
23264
|
-
init_builder3();
|
|
23265
23522
|
COMMON_TEST_DIRS = ["test", "tests", "__tests__", "src/__tests__", "spec"];
|
|
23266
23523
|
});
|
|
23267
23524
|
|
|
@@ -23778,6 +24035,68 @@ ${pluginMarkdown}` : pluginMarkdown;
|
|
|
23778
24035
|
};
|
|
23779
24036
|
});
|
|
23780
24037
|
|
|
24038
|
+
// src/agents/validation.ts
|
|
24039
|
+
function validateAgentForTier(agent, tier) {
|
|
24040
|
+
return agent.capabilities.supportedTiers.includes(tier);
|
|
24041
|
+
}
|
|
24042
|
+
|
|
24043
|
+
// src/agents/version-detection.ts
|
|
24044
|
+
async function getAgentVersion(binaryName) {
|
|
24045
|
+
try {
|
|
24046
|
+
const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
|
|
24047
|
+
stdout: "pipe",
|
|
24048
|
+
stderr: "pipe"
|
|
24049
|
+
});
|
|
24050
|
+
const exitCode = await proc.exited;
|
|
24051
|
+
if (exitCode !== 0) {
|
|
24052
|
+
return null;
|
|
24053
|
+
}
|
|
24054
|
+
const stdout = await new Response(proc.stdout).text();
|
|
24055
|
+
const versionLine = stdout.trim().split(`
|
|
24056
|
+
`)[0];
|
|
24057
|
+
const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
|
|
24058
|
+
if (versionMatch) {
|
|
24059
|
+
return versionMatch[0];
|
|
24060
|
+
}
|
|
24061
|
+
return versionLine || null;
|
|
24062
|
+
} catch {
|
|
24063
|
+
return null;
|
|
24064
|
+
}
|
|
24065
|
+
}
|
|
24066
|
+
async function getAgentVersions() {
|
|
24067
|
+
const agents = await getInstalledAgents();
|
|
24068
|
+
const agentsByName = new Map(agents.map((a) => [a.name, a]));
|
|
24069
|
+
const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
24070
|
+
const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
|
|
24071
|
+
const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
|
|
24072
|
+
return {
|
|
24073
|
+
name: agent.name,
|
|
24074
|
+
displayName: agent.displayName,
|
|
24075
|
+
version: version2,
|
|
24076
|
+
installed: agentsByName.has(agent.name)
|
|
24077
|
+
};
|
|
24078
|
+
}));
|
|
24079
|
+
return versions2;
|
|
24080
|
+
}
|
|
24081
|
+
var _versionDetectionDeps;
|
|
24082
|
+
var init_version_detection = __esm(() => {
|
|
24083
|
+
init_registry();
|
|
24084
|
+
_versionDetectionDeps = {
|
|
24085
|
+
spawn(cmd, opts) {
|
|
24086
|
+
return Bun.spawn(cmd, opts);
|
|
24087
|
+
}
|
|
24088
|
+
};
|
|
24089
|
+
});
|
|
24090
|
+
|
|
24091
|
+
// src/agents/index.ts
|
|
24092
|
+
var init_agents = __esm(() => {
|
|
24093
|
+
init_types2();
|
|
24094
|
+
init_claude();
|
|
24095
|
+
init_registry();
|
|
24096
|
+
init_cost();
|
|
24097
|
+
init_version_detection();
|
|
24098
|
+
});
|
|
24099
|
+
|
|
23781
24100
|
// src/tdd/isolation.ts
|
|
23782
24101
|
function isTestFile(filePath) {
|
|
23783
24102
|
return TEST_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
@@ -23935,7 +24254,38 @@ async function hasCommitsForStory(workdir, storyId, maxCommits = 20) {
|
|
|
23935
24254
|
function detectMergeConflict(output) {
|
|
23936
24255
|
return output.includes("CONFLICT") || output.includes("conflict");
|
|
23937
24256
|
}
|
|
24257
|
+
async function autoCommitIfDirty(workdir, stage, role, storyId) {
|
|
24258
|
+
const logger = getSafeLogger();
|
|
24259
|
+
try {
|
|
24260
|
+
const statusProc = Bun.spawn(["git", "status", "--porcelain"], {
|
|
24261
|
+
cwd: workdir,
|
|
24262
|
+
stdout: "pipe",
|
|
24263
|
+
stderr: "pipe"
|
|
24264
|
+
});
|
|
24265
|
+
const statusOutput = await new Response(statusProc.stdout).text();
|
|
24266
|
+
await statusProc.exited;
|
|
24267
|
+
if (!statusOutput.trim())
|
|
24268
|
+
return;
|
|
24269
|
+
logger?.warn(stage, `Agent did not commit after ${role} session \u2014 auto-committing`, {
|
|
24270
|
+
role,
|
|
24271
|
+
storyId,
|
|
24272
|
+
dirtyFiles: statusOutput.trim().split(`
|
|
24273
|
+
`).length
|
|
24274
|
+
});
|
|
24275
|
+
const addProc = Bun.spawn(["git", "add", "-A"], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
24276
|
+
await addProc.exited;
|
|
24277
|
+
const commitProc = Bun.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
|
|
24278
|
+
cwd: workdir,
|
|
24279
|
+
stdout: "pipe",
|
|
24280
|
+
stderr: "pipe"
|
|
24281
|
+
});
|
|
24282
|
+
await commitProc.exited;
|
|
24283
|
+
} catch {}
|
|
24284
|
+
}
|
|
23938
24285
|
var GIT_TIMEOUT_MS = 1e4;
|
|
24286
|
+
var init_git = __esm(() => {
|
|
24287
|
+
init_logger2();
|
|
24288
|
+
});
|
|
23939
24289
|
// src/verification/executor.ts
|
|
23940
24290
|
async function drainWithDeadline(proc, deadlineMs) {
|
|
23941
24291
|
const EMPTY = Symbol("timeout");
|
|
@@ -24420,7 +24770,7 @@ var init_cleanup = __esm(() => {
|
|
|
24420
24770
|
});
|
|
24421
24771
|
|
|
24422
24772
|
// src/tdd/prompts.ts
|
|
24423
|
-
function buildImplementerRectificationPrompt(failures, story,
|
|
24773
|
+
function buildImplementerRectificationPrompt(failures, story, _contextMarkdown, config2) {
|
|
24424
24774
|
return createRectificationPrompt(failures, story, config2);
|
|
24425
24775
|
}
|
|
24426
24776
|
var init_prompts = __esm(() => {
|
|
@@ -24512,7 +24862,7 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
24512
24862
|
exitCode: rectifyResult.exitCode
|
|
24513
24863
|
});
|
|
24514
24864
|
}
|
|
24515
|
-
await autoCommitIfDirty(workdir, "rectification", story.id
|
|
24865
|
+
await autoCommitIfDirty(workdir, "tdd", "rectification", story.id);
|
|
24516
24866
|
const rectifyIsolation = lite ? undefined : await verifyImplementerIsolation(workdir, rectifyBeforeRef);
|
|
24517
24867
|
if (rectifyIsolation && !rectifyIsolation.passed) {
|
|
24518
24868
|
logger.error("tdd", "Rectification violated isolation", {
|
|
@@ -24557,35 +24907,9 @@ async function runRectificationLoop(story, config2, workdir, agent, implementerT
|
|
|
24557
24907
|
logger.info("tdd", "Full suite gate passed", { storyId: story.id });
|
|
24558
24908
|
return true;
|
|
24559
24909
|
}
|
|
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
24910
|
var init_rectification_gate = __esm(() => {
|
|
24588
24911
|
init_config();
|
|
24912
|
+
init_git();
|
|
24589
24913
|
init_verification();
|
|
24590
24914
|
init_cleanup();
|
|
24591
24915
|
init_isolation();
|
|
@@ -24596,9 +24920,9 @@ var init_rectification_gate = __esm(() => {
|
|
|
24596
24920
|
function buildConventionsSection() {
|
|
24597
24921
|
return `# Conventions
|
|
24598
24922
|
|
|
24599
|
-
|
|
24923
|
+
Follow existing code patterns and conventions. Write idiomatic, maintainable code.
|
|
24600
24924
|
|
|
24601
|
-
|
|
24925
|
+
Commit your changes when done using conventional commit format (e.g. \`feat:\`, \`fix:\`, \`test:\`).`;
|
|
24602
24926
|
}
|
|
24603
24927
|
|
|
24604
24928
|
// src/prompts/sections/isolation.ts
|
|
@@ -24607,29 +24931,39 @@ function buildIsolationSection(roleOrMode, mode) {
|
|
|
24607
24931
|
return buildIsolationSection("test-writer", roleOrMode);
|
|
24608
24932
|
}
|
|
24609
24933
|
const role = roleOrMode;
|
|
24610
|
-
const header =
|
|
24611
|
-
|
|
24612
|
-
`;
|
|
24934
|
+
const header = "# Isolation Rules";
|
|
24613
24935
|
const footer = `
|
|
24614
24936
|
|
|
24615
24937
|
${TEST_FILTER_RULE}`;
|
|
24616
24938
|
if (role === "test-writer") {
|
|
24617
24939
|
const m = mode ?? "strict";
|
|
24618
24940
|
if (m === "strict") {
|
|
24619
|
-
return `${header}
|
|
24941
|
+
return `${header}
|
|
24942
|
+
|
|
24943
|
+
isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
|
|
24620
24944
|
}
|
|
24621
|
-
return `${header}
|
|
24945
|
+
return `${header}
|
|
24946
|
+
|
|
24947
|
+
isolation scope: Create test files in test/. MAY read src/ files and MAY import from src/ to ensure correct types/interfaces. May create minimal stubs in src/ if needed to make imports work, but do NOT implement real logic.${footer}`;
|
|
24622
24948
|
}
|
|
24623
24949
|
if (role === "implementer") {
|
|
24624
|
-
return `${header}
|
|
24950
|
+
return `${header}
|
|
24951
|
+
|
|
24952
|
+
isolation scope: Implement source code in src/ to make tests pass. Do not modify test files. Run tests frequently to track progress.${footer}`;
|
|
24625
24953
|
}
|
|
24626
24954
|
if (role === "verifier") {
|
|
24627
|
-
return `${header}
|
|
24955
|
+
return `${header}
|
|
24956
|
+
|
|
24957
|
+
isolation scope: Read-only inspection. Review all test results, implementation code, and acceptance criteria compliance. You MAY write a verdict file (.nax-verifier-verdict.json) and apply legitimate fixes if needed.${footer}`;
|
|
24628
24958
|
}
|
|
24629
24959
|
if (role === "single-session") {
|
|
24630
|
-
return `${header}
|
|
24960
|
+
return `${header}
|
|
24961
|
+
|
|
24962
|
+
isolation scope: Create test files in test/ directory, then implement source code in src/ to make tests pass. Both directories are in scope for this session.${footer}`;
|
|
24631
24963
|
}
|
|
24632
|
-
return `${header}
|
|
24964
|
+
return `${header}
|
|
24965
|
+
|
|
24966
|
+
isolation scope: You may modify both src/ and test/ files. Write failing tests FIRST, then implement to make them pass.`;
|
|
24633
24967
|
}
|
|
24634
24968
|
var TEST_FILTER_RULE;
|
|
24635
24969
|
var init_isolation2 = __esm(() => {
|
|
@@ -24647,76 +24981,76 @@ function buildRoleTaskSection(roleOrVariant, variant) {
|
|
|
24647
24981
|
if (v === "standard") {
|
|
24648
24982
|
return `# Role: Implementer
|
|
24649
24983
|
|
|
24650
|
-
|
|
24984
|
+
Your task: make failing tests pass.
|
|
24651
24985
|
|
|
24652
|
-
|
|
24653
|
-
|
|
24654
|
-
|
|
24655
|
-
|
|
24656
|
-
|
|
24657
|
-
|
|
24986
|
+
Instructions:
|
|
24987
|
+
- Implement source code in src/ to make tests pass
|
|
24988
|
+
- Do NOT modify test files
|
|
24989
|
+
- Run tests frequently to track progress
|
|
24990
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
24991
|
+
- Goal: all tests green, all changes committed`;
|
|
24658
24992
|
}
|
|
24659
24993
|
return `# Role: Implementer (Lite)
|
|
24660
24994
|
|
|
24661
|
-
|
|
24995
|
+
Your task: Write tests AND implement the feature in a single session.
|
|
24662
24996
|
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24997
|
+
Instructions:
|
|
24998
|
+
- Write tests first (test/ directory), then implement (src/ directory)
|
|
24999
|
+
- All tests must pass by the end
|
|
25000
|
+
- Use Bun test (describe/test/expect)
|
|
25001
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25002
|
+
- Goal: all tests green, all criteria met, all changes committed`;
|
|
24669
25003
|
}
|
|
24670
25004
|
if (role === "test-writer") {
|
|
24671
25005
|
return `# Role: Test-Writer
|
|
24672
25006
|
|
|
24673
|
-
|
|
25007
|
+
Your task: Write comprehensive failing tests for the feature.
|
|
24674
25008
|
|
|
24675
|
-
|
|
24676
|
-
|
|
24677
|
-
|
|
24678
|
-
|
|
24679
|
-
|
|
24680
|
-
|
|
24681
|
-
|
|
25009
|
+
Instructions:
|
|
25010
|
+
- Create test files in test/ directory that cover acceptance criteria
|
|
25011
|
+
- Tests must fail initially (RED phase) \u2014 the feature is not yet implemented
|
|
25012
|
+
- Use Bun test (describe/test/expect)
|
|
25013
|
+
- Write clear test names that document expected behavior
|
|
25014
|
+
- Focus on behavior, not implementation details
|
|
25015
|
+
- Goal: comprehensive test suite ready for implementation`;
|
|
24682
25016
|
}
|
|
24683
25017
|
if (role === "verifier") {
|
|
24684
25018
|
return `# Role: Verifier
|
|
24685
25019
|
|
|
24686
|
-
|
|
25020
|
+
Your task: Review and verify the implementation against acceptance criteria.
|
|
24687
25021
|
|
|
24688
|
-
|
|
24689
|
-
|
|
24690
|
-
|
|
24691
|
-
|
|
24692
|
-
|
|
24693
|
-
|
|
24694
|
-
|
|
25022
|
+
Instructions:
|
|
25023
|
+
- Review all test results \u2014 verify tests pass
|
|
25024
|
+
- Check that implementation meets all acceptance criteria
|
|
25025
|
+
- Inspect code quality, error handling, and edge cases
|
|
25026
|
+
- Verify test modifications (if any) are legitimate fixes
|
|
25027
|
+
- Write a detailed verdict with reasoning
|
|
25028
|
+
- Goal: provide comprehensive verification and quality assurance`;
|
|
24695
25029
|
}
|
|
24696
25030
|
if (role === "single-session") {
|
|
24697
25031
|
return `# Role: Single-Session
|
|
24698
25032
|
|
|
24699
|
-
|
|
25033
|
+
Your task: Write tests AND implement the feature in a single focused session.
|
|
24700
25034
|
|
|
24701
|
-
|
|
24702
|
-
|
|
24703
|
-
|
|
24704
|
-
|
|
24705
|
-
|
|
24706
|
-
|
|
24707
|
-
|
|
25035
|
+
Instructions:
|
|
25036
|
+
- Phase 1: Write comprehensive tests (test/ directory)
|
|
25037
|
+
- Phase 2: Implement to make all tests pass (src/ directory)
|
|
25038
|
+
- Use Bun test (describe/test/expect)
|
|
25039
|
+
- Run tests frequently throughout implementation
|
|
25040
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25041
|
+
- Goal: all tests passing, all changes committed, full story complete`;
|
|
24708
25042
|
}
|
|
24709
25043
|
return `# Role: TDD-Simple
|
|
24710
25044
|
|
|
24711
|
-
|
|
25045
|
+
Your task: Write failing tests FIRST, then implement to make them pass.
|
|
24712
25046
|
|
|
24713
|
-
|
|
24714
|
-
|
|
24715
|
-
|
|
24716
|
-
|
|
24717
|
-
|
|
24718
|
-
|
|
24719
|
-
|
|
25047
|
+
Instructions:
|
|
25048
|
+
- RED phase: Write failing tests FIRST for the acceptance criteria
|
|
25049
|
+
- RED phase: Run the tests to confirm they fail
|
|
25050
|
+
- GREEN phase: Implement the minimum code to make tests pass
|
|
25051
|
+
- REFACTOR phase: Refactor while keeping tests green
|
|
25052
|
+
- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
|
|
25053
|
+
- Goal: all tests passing, feature complete, all changes committed`;
|
|
24720
25054
|
}
|
|
24721
25055
|
|
|
24722
25056
|
// src/prompts/sections/story.ts
|
|
@@ -24734,6 +25068,69 @@ ${story.description}
|
|
|
24734
25068
|
${criteria}`;
|
|
24735
25069
|
}
|
|
24736
25070
|
|
|
25071
|
+
// src/prompts/sections/verdict.ts
|
|
25072
|
+
function buildVerdictSection(story) {
|
|
25073
|
+
return `# Verdict Instructions
|
|
25074
|
+
|
|
25075
|
+
## Write Verdict File
|
|
25076
|
+
|
|
25077
|
+
After completing your verification, you **MUST** write a verdict file at the **project root**:
|
|
25078
|
+
|
|
25079
|
+
**File:** \`.nax-verifier-verdict.json\`
|
|
25080
|
+
|
|
25081
|
+
Set \`approved: true\` when ALL of these conditions are met:
|
|
25082
|
+
- All tests pass
|
|
25083
|
+
- Implementation is clean and follows conventions
|
|
25084
|
+
- All acceptance criteria met
|
|
25085
|
+
- Any test modifications by implementer are legitimate fixes
|
|
25086
|
+
|
|
25087
|
+
Set \`approved: false\` when ANY of these conditions are true:
|
|
25088
|
+
- Tests are failing and you cannot fix them
|
|
25089
|
+
- The implementer loosened test assertions to mask bugs
|
|
25090
|
+
- Critical acceptance criteria are not met
|
|
25091
|
+
- Code quality is poor (security issues, severe bugs, etc.)
|
|
25092
|
+
|
|
25093
|
+
**Full JSON schema example** (fill in all fields with real values):
|
|
25094
|
+
|
|
25095
|
+
\`\`\`json
|
|
25096
|
+
{
|
|
25097
|
+
"version": 1,
|
|
25098
|
+
"approved": true,
|
|
25099
|
+
"tests": {
|
|
25100
|
+
"allPassing": true,
|
|
25101
|
+
"passCount": 42,
|
|
25102
|
+
"failCount": 0
|
|
25103
|
+
},
|
|
25104
|
+
"testModifications": {
|
|
25105
|
+
"detected": false,
|
|
25106
|
+
"files": [],
|
|
25107
|
+
"legitimate": true,
|
|
25108
|
+
"reasoning": "No test files were modified by the implementer"
|
|
25109
|
+
},
|
|
25110
|
+
"acceptanceCriteria": {
|
|
25111
|
+
"allMet": true,
|
|
25112
|
+
"criteria": [
|
|
25113
|
+
{ "criterion": "Example criterion", "met": true }
|
|
25114
|
+
]
|
|
25115
|
+
},
|
|
25116
|
+
"quality": {
|
|
25117
|
+
"rating": "good",
|
|
25118
|
+
"issues": []
|
|
25119
|
+
},
|
|
25120
|
+
"fixes": [],
|
|
25121
|
+
"reasoning": "All tests pass, implementation is clean, all acceptance criteria are met."
|
|
25122
|
+
}
|
|
25123
|
+
\`\`\`
|
|
25124
|
+
|
|
25125
|
+
**Field notes:**
|
|
25126
|
+
- \`quality.rating\` must be one of: \`"good"\`, \`"acceptable"\`, \`"poor"\`
|
|
25127
|
+
- \`testModifications.files\` \u2014 list any test files the implementer changed
|
|
25128
|
+
- \`fixes\` \u2014 list any fixes you applied yourself during this verification session
|
|
25129
|
+
- \`reasoning\` \u2014 brief summary of your overall assessment
|
|
25130
|
+
|
|
25131
|
+
When done, commit any fixes with message: "fix: verify and adjust ${story.title}"`;
|
|
25132
|
+
}
|
|
25133
|
+
|
|
24737
25134
|
// src/prompts/loader.ts
|
|
24738
25135
|
var exports_loader = {};
|
|
24739
25136
|
__export(exports_loader, {
|
|
@@ -24809,6 +25206,9 @@ ${this._constitution}`);
|
|
|
24809
25206
|
if (this._story) {
|
|
24810
25207
|
sections.push(buildStorySection(this._story));
|
|
24811
25208
|
}
|
|
25209
|
+
if (this._role === "verifier" && this._story) {
|
|
25210
|
+
sections.push(buildVerdictSection(this._story));
|
|
25211
|
+
}
|
|
24812
25212
|
const isolation = this._options.isolation;
|
|
24813
25213
|
sections.push(buildIsolationSection(this._role, isolation));
|
|
24814
25214
|
if (this._contextMd) {
|
|
@@ -24897,7 +25297,7 @@ async function rollbackToRef(workdir, ref) {
|
|
|
24897
25297
|
}
|
|
24898
25298
|
logger.info("tdd", "Successfully rolled back git changes", { ref });
|
|
24899
25299
|
}
|
|
24900
|
-
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false) {
|
|
25300
|
+
async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution) {
|
|
24901
25301
|
const startTime = Date.now();
|
|
24902
25302
|
let prompt;
|
|
24903
25303
|
switch (role) {
|
|
@@ -24905,7 +25305,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
24905
25305
|
prompt = await PromptBuilder.for("test-writer", { isolation: lite ? "lite" : "strict" }).withLoader(workdir, config2).story(story).context(contextMarkdown).build();
|
|
24906
25306
|
break;
|
|
24907
25307
|
case "implementer":
|
|
24908
|
-
prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" }).withLoader(workdir, config2).story(story).context(contextMarkdown).build();
|
|
25308
|
+
prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" }).withLoader(workdir, config2).story(story).context(contextMarkdown).constitution(constitution).build();
|
|
24909
25309
|
break;
|
|
24910
25310
|
case "verifier":
|
|
24911
25311
|
prompt = await PromptBuilder.for("verifier").withLoader(workdir, config2).story(story).context(contextMarkdown).build();
|
|
@@ -24939,7 +25339,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
24939
25339
|
exitCode: result.exitCode
|
|
24940
25340
|
});
|
|
24941
25341
|
}
|
|
24942
|
-
await
|
|
25342
|
+
await autoCommitIfDirty(workdir, "tdd", role, story.id);
|
|
24943
25343
|
let isolation;
|
|
24944
25344
|
if (!skipIsolation) {
|
|
24945
25345
|
if (role === "test-writer") {
|
|
@@ -24986,42 +25386,11 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
|
|
|
24986
25386
|
estimatedCost: result.estimatedCost
|
|
24987
25387
|
};
|
|
24988
25388
|
}
|
|
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
25389
|
var init_session_runner = __esm(() => {
|
|
25022
25390
|
init_config();
|
|
25023
25391
|
init_logger2();
|
|
25024
25392
|
init_prompts2();
|
|
25393
|
+
init_git();
|
|
25025
25394
|
init_cleanup();
|
|
25026
25395
|
init_isolation();
|
|
25027
25396
|
});
|
|
@@ -25077,6 +25446,95 @@ function isValidVerdict(obj) {
|
|
|
25077
25446
|
return false;
|
|
25078
25447
|
return true;
|
|
25079
25448
|
}
|
|
25449
|
+
function coerceVerdict(obj) {
|
|
25450
|
+
try {
|
|
25451
|
+
const verdictStr = String(obj.verdict ?? "").toUpperCase();
|
|
25452
|
+
const approved = verdictStr === "PASS" || verdictStr === "APPROVED" || verdictStr.startsWith("VERIFIED") || verdictStr.includes("ALL ACCEPTANCE CRITERIA MET") || obj.approved === true;
|
|
25453
|
+
let passCount = 0;
|
|
25454
|
+
let failCount = 0;
|
|
25455
|
+
let allPassing = approved;
|
|
25456
|
+
const summary = obj.verification_summary;
|
|
25457
|
+
if (summary?.test_results && typeof summary.test_results === "string") {
|
|
25458
|
+
const match = summary.test_results.match(/(\d+)\/(\d+)/);
|
|
25459
|
+
if (match) {
|
|
25460
|
+
passCount = Number.parseInt(match[1], 10);
|
|
25461
|
+
const total = Number.parseInt(match[2], 10);
|
|
25462
|
+
failCount = total - passCount;
|
|
25463
|
+
allPassing = failCount === 0;
|
|
25464
|
+
}
|
|
25465
|
+
}
|
|
25466
|
+
if (obj.tests && typeof obj.tests === "object") {
|
|
25467
|
+
const t = obj.tests;
|
|
25468
|
+
if (typeof t.passCount === "number")
|
|
25469
|
+
passCount = t.passCount;
|
|
25470
|
+
if (typeof t.failCount === "number")
|
|
25471
|
+
failCount = t.failCount;
|
|
25472
|
+
if (typeof t.allPassing === "boolean")
|
|
25473
|
+
allPassing = t.allPassing;
|
|
25474
|
+
}
|
|
25475
|
+
const criteria = [];
|
|
25476
|
+
let allMet = approved;
|
|
25477
|
+
const acReview = obj.acceptance_criteria_review;
|
|
25478
|
+
if (acReview) {
|
|
25479
|
+
for (const [key, val] of Object.entries(acReview)) {
|
|
25480
|
+
if (key.startsWith("criterion") && val && typeof val === "object") {
|
|
25481
|
+
const c = val;
|
|
25482
|
+
const met = String(c.status ?? "").toUpperCase() === "SATISFIED" || c.met === true;
|
|
25483
|
+
criteria.push({
|
|
25484
|
+
criterion: String(c.name ?? c.criterion ?? key),
|
|
25485
|
+
met,
|
|
25486
|
+
note: c.evidence ? String(c.evidence).slice(0, 200) : undefined
|
|
25487
|
+
});
|
|
25488
|
+
if (!met)
|
|
25489
|
+
allMet = false;
|
|
25490
|
+
}
|
|
25491
|
+
}
|
|
25492
|
+
}
|
|
25493
|
+
if (obj.acceptanceCriteria && typeof obj.acceptanceCriteria === "object") {
|
|
25494
|
+
const ac = obj.acceptanceCriteria;
|
|
25495
|
+
if (typeof ac.allMet === "boolean")
|
|
25496
|
+
allMet = ac.allMet;
|
|
25497
|
+
if (Array.isArray(ac.criteria)) {
|
|
25498
|
+
for (const c of ac.criteria) {
|
|
25499
|
+
if (c && typeof c === "object") {
|
|
25500
|
+
criteria.push(c);
|
|
25501
|
+
}
|
|
25502
|
+
}
|
|
25503
|
+
}
|
|
25504
|
+
}
|
|
25505
|
+
if (criteria.length === 0 && summary?.acceptance_criteria && typeof summary.acceptance_criteria === "string") {
|
|
25506
|
+
const acMatch = summary.acceptance_criteria.match(/(\d+)\/(\d+)/);
|
|
25507
|
+
if (acMatch) {
|
|
25508
|
+
const met = Number.parseInt(acMatch[1], 10);
|
|
25509
|
+
const total = Number.parseInt(acMatch[2], 10);
|
|
25510
|
+
allMet = met === total;
|
|
25511
|
+
}
|
|
25512
|
+
}
|
|
25513
|
+
let rating = "acceptable";
|
|
25514
|
+
const qualityStr = summary?.code_quality ? String(summary.code_quality).toLowerCase() : obj.quality && typeof obj.quality === "object" ? String(obj.quality.rating ?? "acceptable").toLowerCase() : "acceptable";
|
|
25515
|
+
if (qualityStr === "high" || qualityStr === "good")
|
|
25516
|
+
rating = "good";
|
|
25517
|
+
else if (qualityStr === "low" || qualityStr === "poor")
|
|
25518
|
+
rating = "poor";
|
|
25519
|
+
return {
|
|
25520
|
+
version: 1,
|
|
25521
|
+
approved,
|
|
25522
|
+
tests: { allPassing, passCount, failCount },
|
|
25523
|
+
testModifications: {
|
|
25524
|
+
detected: false,
|
|
25525
|
+
files: [],
|
|
25526
|
+
legitimate: true,
|
|
25527
|
+
reasoning: "Not assessed in free-form verdict"
|
|
25528
|
+
},
|
|
25529
|
+
acceptanceCriteria: { allMet, criteria },
|
|
25530
|
+
quality: { rating, issues: [] },
|
|
25531
|
+
fixes: Array.isArray(obj.fixes) ? obj.fixes : [],
|
|
25532
|
+
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}`
|
|
25533
|
+
};
|
|
25534
|
+
} catch {
|
|
25535
|
+
return null;
|
|
25536
|
+
}
|
|
25537
|
+
}
|
|
25080
25538
|
async function readVerdict(workdir) {
|
|
25081
25539
|
const logger = getLogger();
|
|
25082
25540
|
const verdictPath = path8.join(workdir, VERDICT_FILE);
|
|
@@ -25086,24 +25544,47 @@ async function readVerdict(workdir) {
|
|
|
25086
25544
|
if (!exists) {
|
|
25087
25545
|
return null;
|
|
25088
25546
|
}
|
|
25089
|
-
let
|
|
25547
|
+
let rawText;
|
|
25090
25548
|
try {
|
|
25091
|
-
|
|
25092
|
-
} catch (
|
|
25093
|
-
logger.warn("tdd", "
|
|
25549
|
+
rawText = await file2.text();
|
|
25550
|
+
} catch (readErr) {
|
|
25551
|
+
logger.warn("tdd", "Failed to read verifier verdict file", {
|
|
25094
25552
|
path: verdictPath,
|
|
25095
|
-
error: String(
|
|
25553
|
+
error: String(readErr)
|
|
25096
25554
|
});
|
|
25097
25555
|
return null;
|
|
25098
25556
|
}
|
|
25099
|
-
|
|
25100
|
-
|
|
25557
|
+
let parsed;
|
|
25558
|
+
try {
|
|
25559
|
+
parsed = JSON.parse(rawText);
|
|
25560
|
+
} catch (parseErr) {
|
|
25561
|
+
logger.warn("tdd", "Verifier verdict file is not valid JSON \u2014 ignoring", {
|
|
25101
25562
|
path: verdictPath,
|
|
25102
|
-
|
|
25563
|
+
error: String(parseErr),
|
|
25564
|
+
rawContent: rawText.slice(0, 1000)
|
|
25103
25565
|
});
|
|
25104
25566
|
return null;
|
|
25105
25567
|
}
|
|
25106
|
-
|
|
25568
|
+
if (isValidVerdict(parsed)) {
|
|
25569
|
+
return parsed;
|
|
25570
|
+
}
|
|
25571
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
25572
|
+
const coerced = coerceVerdict(parsed);
|
|
25573
|
+
if (coerced) {
|
|
25574
|
+
logger.info("tdd", "Coerced free-form verdict to structured format", {
|
|
25575
|
+
path: verdictPath,
|
|
25576
|
+
approved: coerced.approved,
|
|
25577
|
+
passCount: coerced.tests.passCount,
|
|
25578
|
+
failCount: coerced.tests.failCount
|
|
25579
|
+
});
|
|
25580
|
+
return coerced;
|
|
25581
|
+
}
|
|
25582
|
+
}
|
|
25583
|
+
logger.warn("tdd", "Verifier verdict file missing required fields and coercion failed \u2014 ignoring", {
|
|
25584
|
+
path: verdictPath,
|
|
25585
|
+
content: JSON.stringify(parsed).slice(0, 500)
|
|
25586
|
+
});
|
|
25587
|
+
return null;
|
|
25107
25588
|
} catch (err) {
|
|
25108
25589
|
logger.warn("tdd", "Failed to read verifier verdict file \u2014 ignoring", {
|
|
25109
25590
|
path: verdictPath,
|
|
@@ -25182,6 +25663,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25182
25663
|
workdir,
|
|
25183
25664
|
modelTier,
|
|
25184
25665
|
contextMarkdown,
|
|
25666
|
+
constitution,
|
|
25185
25667
|
dryRun = false,
|
|
25186
25668
|
lite = false,
|
|
25187
25669
|
_recursionDepth = 0
|
|
@@ -25243,7 +25725,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25243
25725
|
let session1;
|
|
25244
25726
|
if (!isRetry) {
|
|
25245
25727
|
const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
|
|
25246
|
-
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite);
|
|
25728
|
+
session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution);
|
|
25247
25729
|
sessions.push(session1);
|
|
25248
25730
|
}
|
|
25249
25731
|
if (session1 && !session1.success) {
|
|
@@ -25305,7 +25787,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25305
25787
|
});
|
|
25306
25788
|
const session2Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
25307
25789
|
const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
|
|
25308
|
-
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite);
|
|
25790
|
+
const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution);
|
|
25309
25791
|
sessions.push(session2);
|
|
25310
25792
|
if (!session2.success) {
|
|
25311
25793
|
needsHumanReview = true;
|
|
@@ -25324,7 +25806,7 @@ async function runThreeSessionTdd(options) {
|
|
|
25324
25806
|
const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger);
|
|
25325
25807
|
const session3Ref = await captureGitRef(workdir) ?? "HEAD";
|
|
25326
25808
|
const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
|
|
25327
|
-
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false);
|
|
25809
|
+
const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution);
|
|
25328
25810
|
sessions.push(session3);
|
|
25329
25811
|
const verdict = await readVerdict(workdir);
|
|
25330
25812
|
await cleanupVerdict(workdir);
|
|
@@ -25424,6 +25906,7 @@ var init_orchestrator2 = __esm(() => {
|
|
|
25424
25906
|
init_config();
|
|
25425
25907
|
init_greenfield();
|
|
25426
25908
|
init_logger2();
|
|
25909
|
+
init_git();
|
|
25427
25910
|
init_verification();
|
|
25428
25911
|
init_rectification_gate();
|
|
25429
25912
|
init_session_runner();
|
|
@@ -25469,34 +25952,6 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
|
|
|
25469
25952
|
reason: reviewReason || "Three-session TDD requires review"
|
|
25470
25953
|
};
|
|
25471
25954
|
}
|
|
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
25955
|
var executionStage, _executionDeps;
|
|
25501
25956
|
var init_execution = __esm(() => {
|
|
25502
25957
|
init_agents();
|
|
@@ -25504,6 +25959,7 @@ var init_execution = __esm(() => {
|
|
|
25504
25959
|
init_triggers();
|
|
25505
25960
|
init_logger2();
|
|
25506
25961
|
init_tdd();
|
|
25962
|
+
init_git();
|
|
25507
25963
|
executionStage = {
|
|
25508
25964
|
name: "execution",
|
|
25509
25965
|
enabled: () => true,
|
|
@@ -25530,6 +25986,7 @@ var init_execution = __esm(() => {
|
|
|
25530
25986
|
workdir: ctx.workdir,
|
|
25531
25987
|
modelTier: ctx.routing.modelTier,
|
|
25532
25988
|
contextMarkdown: ctx.contextMarkdown,
|
|
25989
|
+
constitution: ctx.constitution?.content,
|
|
25533
25990
|
dryRun: false,
|
|
25534
25991
|
lite: isLiteMode
|
|
25535
25992
|
});
|
|
@@ -25553,6 +26010,28 @@ var init_execution = __esm(() => {
|
|
|
25553
26010
|
lite: tddResult.lite,
|
|
25554
26011
|
failureCategory: tddResult.failureCategory
|
|
25555
26012
|
});
|
|
26013
|
+
if (ctx.interaction) {
|
|
26014
|
+
try {
|
|
26015
|
+
await ctx.interaction.send({
|
|
26016
|
+
id: `human-review-${ctx.story.id}-${Date.now()}`,
|
|
26017
|
+
type: "notify",
|
|
26018
|
+
featureName: ctx.featureDir ? ctx.featureDir.split("/").pop() ?? "unknown" : "unknown",
|
|
26019
|
+
storyId: ctx.story.id,
|
|
26020
|
+
stage: "execution",
|
|
26021
|
+
summary: `\u26A0\uFE0F Human review needed: ${ctx.story.id}`,
|
|
26022
|
+
detail: `Story: ${ctx.story.title}
|
|
26023
|
+
Reason: ${tddResult.reviewReason ?? "No reason provided"}
|
|
26024
|
+
Category: ${tddResult.failureCategory ?? "unknown"}`,
|
|
26025
|
+
fallback: "continue",
|
|
26026
|
+
createdAt: Date.now()
|
|
26027
|
+
});
|
|
26028
|
+
} catch (notifyErr) {
|
|
26029
|
+
logger.warn("execution", "Failed to send human review notification", {
|
|
26030
|
+
storyId: ctx.story.id,
|
|
26031
|
+
error: String(notifyErr)
|
|
26032
|
+
});
|
|
26033
|
+
}
|
|
26034
|
+
}
|
|
25556
26035
|
}
|
|
25557
26036
|
return routeTddFailure(tddResult.failureCategory, isLiteMode, ctx, tddResult.reviewReason);
|
|
25558
26037
|
}
|
|
@@ -25578,7 +26057,7 @@ var init_execution = __esm(() => {
|
|
|
25578
26057
|
dangerouslySkipPermissions: ctx.config.execution.dangerouslySkipPermissions
|
|
25579
26058
|
});
|
|
25580
26059
|
ctx.agentResult = result;
|
|
25581
|
-
await
|
|
26060
|
+
await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
|
|
25582
26061
|
const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
|
|
25583
26062
|
if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
|
|
25584
26063
|
const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
|
|
@@ -25623,16 +26102,11 @@ var init_execution = __esm(() => {
|
|
|
25623
26102
|
};
|
|
25624
26103
|
});
|
|
25625
26104
|
|
|
25626
|
-
// src/optimizer/types.ts
|
|
25627
|
-
function estimateTokens4(text) {
|
|
25628
|
-
return Math.ceil(text.length / 4);
|
|
25629
|
-
}
|
|
25630
|
-
|
|
25631
26105
|
// src/optimizer/noop.optimizer.ts
|
|
25632
26106
|
class NoopOptimizer {
|
|
25633
26107
|
name = "noop";
|
|
25634
26108
|
async optimize(input) {
|
|
25635
|
-
const tokens =
|
|
26109
|
+
const tokens = estimateTokens(input.prompt);
|
|
25636
26110
|
return {
|
|
25637
26111
|
prompt: input.prompt,
|
|
25638
26112
|
originalTokens: tokens,
|
|
@@ -25648,7 +26122,7 @@ var init_noop_optimizer = () => {};
|
|
|
25648
26122
|
class RuleBasedOptimizer {
|
|
25649
26123
|
name = "rule-based";
|
|
25650
26124
|
async optimize(input) {
|
|
25651
|
-
const originalTokens =
|
|
26125
|
+
const originalTokens = estimateTokens(input.prompt);
|
|
25652
26126
|
const appliedRules = [];
|
|
25653
26127
|
let optimized = input.prompt;
|
|
25654
26128
|
const config2 = {
|
|
@@ -25677,13 +26151,13 @@ class RuleBasedOptimizer {
|
|
|
25677
26151
|
}
|
|
25678
26152
|
}
|
|
25679
26153
|
if (config2.maxPromptTokens) {
|
|
25680
|
-
const currentTokens =
|
|
26154
|
+
const currentTokens = estimateTokens(optimized);
|
|
25681
26155
|
if (currentTokens > config2.maxPromptTokens) {
|
|
25682
26156
|
optimized = this.trimToMaxTokens(optimized, config2.maxPromptTokens);
|
|
25683
26157
|
appliedRules.push("maxPromptTokens");
|
|
25684
26158
|
}
|
|
25685
26159
|
}
|
|
25686
|
-
const optimizedTokens =
|
|
26160
|
+
const optimizedTokens = estimateTokens(optimized);
|
|
25687
26161
|
const savings = originalTokens > 0 ? (originalTokens - optimizedTokens) / originalTokens : 0;
|
|
25688
26162
|
return {
|
|
25689
26163
|
prompt: optimized,
|
|
@@ -25726,7 +26200,7 @@ ${newContextSection}`);
|
|
|
25726
26200
|
return prompt;
|
|
25727
26201
|
}
|
|
25728
26202
|
trimToMaxTokens(prompt, maxTokens) {
|
|
25729
|
-
const currentTokens =
|
|
26203
|
+
const currentTokens = estimateTokens(prompt);
|
|
25730
26204
|
if (currentTokens <= maxTokens) {
|
|
25731
26205
|
return prompt;
|
|
25732
26206
|
}
|
|
@@ -25865,7 +26339,7 @@ var init_optimizer2 = __esm(() => {
|
|
|
25865
26339
|
});
|
|
25866
26340
|
|
|
25867
26341
|
// src/execution/prompts.ts
|
|
25868
|
-
function
|
|
26342
|
+
function buildBatchPrompt(stories, contextMarkdown, constitution) {
|
|
25869
26343
|
const storyPrompts = stories.map((story, idx) => {
|
|
25870
26344
|
return `## Story ${idx + 1}: ${story.id} \u2014 ${story.title}
|
|
25871
26345
|
|
|
@@ -25923,7 +26397,7 @@ var init_prompt = __esm(() => {
|
|
|
25923
26397
|
const isBatch = ctx.stories.length > 1;
|
|
25924
26398
|
let prompt;
|
|
25925
26399
|
if (isBatch) {
|
|
25926
|
-
prompt =
|
|
26400
|
+
prompt = buildBatchPrompt(ctx.stories, ctx.contextMarkdown, ctx.constitution);
|
|
25927
26401
|
} else {
|
|
25928
26402
|
const role = ctx.routing.testStrategy === "tdd-simple" ? "tdd-simple" : "single-session";
|
|
25929
26403
|
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content);
|
|
@@ -26211,7 +26685,6 @@ ${rectificationPrompt}`;
|
|
|
26211
26685
|
var init_rectification_loop = __esm(() => {
|
|
26212
26686
|
init_agents();
|
|
26213
26687
|
init_config();
|
|
26214
|
-
init_progress();
|
|
26215
26688
|
init_test_output_parser();
|
|
26216
26689
|
init_logger2();
|
|
26217
26690
|
init_prd();
|
|
@@ -26616,6 +27089,7 @@ function reverseMapTestToSource(testFiles, workdir) {
|
|
|
26616
27089
|
}
|
|
26617
27090
|
var _smartRunnerDeps;
|
|
26618
27091
|
var init_smart_runner = __esm(() => {
|
|
27092
|
+
init_git();
|
|
26619
27093
|
_smartRunnerDeps = {
|
|
26620
27094
|
getChangedSourceFiles,
|
|
26621
27095
|
mapSourceToTests,
|
|
@@ -26851,6 +27325,7 @@ async function runDecompose(story, prd, config2, _workdir) {
|
|
|
26851
27325
|
}
|
|
26852
27326
|
var routingStage, _routingDeps;
|
|
26853
27327
|
var init_routing2 = __esm(() => {
|
|
27328
|
+
init_registry();
|
|
26854
27329
|
init_greenfield();
|
|
26855
27330
|
init_builder2();
|
|
26856
27331
|
init_triggers();
|
|
@@ -26863,6 +27338,8 @@ var init_routing2 = __esm(() => {
|
|
|
26863
27338
|
enabled: () => true,
|
|
26864
27339
|
async execute(ctx) {
|
|
26865
27340
|
const logger = getLogger();
|
|
27341
|
+
const agentName = ctx.config.execution?.agent ?? "claude";
|
|
27342
|
+
const adapter = _routingDeps.getAgent(agentName);
|
|
26866
27343
|
const hasExistingRouting = ctx.story.routing !== undefined;
|
|
26867
27344
|
const hasContentHash = ctx.story.routing?.contentHash !== undefined;
|
|
26868
27345
|
let currentHash;
|
|
@@ -26874,7 +27351,7 @@ var init_routing2 = __esm(() => {
|
|
|
26874
27351
|
const isCacheHit = hasExistingRouting && (!hasContentHash || hashMatch);
|
|
26875
27352
|
let routing;
|
|
26876
27353
|
if (isCacheHit) {
|
|
26877
|
-
routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
|
|
27354
|
+
routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config, adapter }, ctx.workdir, ctx.plugins);
|
|
26878
27355
|
if (ctx.story.routing?.complexity)
|
|
26879
27356
|
routing.complexity = ctx.story.routing.complexity;
|
|
26880
27357
|
if (!hasContentHash && ctx.story.routing?.testStrategy)
|
|
@@ -26885,7 +27362,7 @@ var init_routing2 = __esm(() => {
|
|
|
26885
27362
|
routing.modelTier = _routingDeps.complexityToModelTier(routing.complexity, ctx.config);
|
|
26886
27363
|
}
|
|
26887
27364
|
} else {
|
|
26888
|
-
routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
|
|
27365
|
+
routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config, adapter }, ctx.workdir, ctx.plugins);
|
|
26889
27366
|
currentHash = currentHash ?? _routingDeps.computeStoryContentHash(ctx.story);
|
|
26890
27367
|
ctx.story.routing = {
|
|
26891
27368
|
...ctx.story.routing ?? {},
|
|
@@ -26974,7 +27451,8 @@ var init_routing2 = __esm(() => {
|
|
|
26974
27451
|
computeStoryContentHash,
|
|
26975
27452
|
applyDecomposition,
|
|
26976
27453
|
runDecompose,
|
|
26977
|
-
checkStoryOversized
|
|
27454
|
+
checkStoryOversized,
|
|
27455
|
+
getAgent
|
|
26978
27456
|
};
|
|
26979
27457
|
});
|
|
26980
27458
|
|
|
@@ -28065,10 +28543,55 @@ async function checkPromptOverrideFiles(config2, workdir) {
|
|
|
28065
28543
|
}
|
|
28066
28544
|
var init_checks_warnings = () => {};
|
|
28067
28545
|
|
|
28546
|
+
// src/precheck/checks-agents.ts
|
|
28547
|
+
async function checkMultiAgentHealth() {
|
|
28548
|
+
try {
|
|
28549
|
+
const versions2 = await getAgentVersions();
|
|
28550
|
+
const installed = versions2.filter((v) => v.installed);
|
|
28551
|
+
const notInstalled = versions2.filter((v) => !v.installed);
|
|
28552
|
+
const lines = [];
|
|
28553
|
+
if (installed.length > 0) {
|
|
28554
|
+
lines.push(`Installed agents (${installed.length}):`);
|
|
28555
|
+
for (const agent of installed) {
|
|
28556
|
+
const versionStr = agent.version ? ` v${agent.version}` : " (version unknown)";
|
|
28557
|
+
lines.push(` \u2022 ${agent.displayName}${versionStr}`);
|
|
28558
|
+
}
|
|
28559
|
+
} else {
|
|
28560
|
+
lines.push("No additional agents detected (using default configured agent)");
|
|
28561
|
+
}
|
|
28562
|
+
if (notInstalled.length > 0) {
|
|
28563
|
+
lines.push(`
|
|
28564
|
+
Available but not installed (${notInstalled.length}):`);
|
|
28565
|
+
for (const agent of notInstalled) {
|
|
28566
|
+
lines.push(` \u2022 ${agent.displayName}`);
|
|
28567
|
+
}
|
|
28568
|
+
}
|
|
28569
|
+
const message = lines.join(`
|
|
28570
|
+
`);
|
|
28571
|
+
return {
|
|
28572
|
+
name: "multi-agent-health",
|
|
28573
|
+
tier: "warning",
|
|
28574
|
+
passed: true,
|
|
28575
|
+
message
|
|
28576
|
+
};
|
|
28577
|
+
} catch (error48) {
|
|
28578
|
+
return {
|
|
28579
|
+
name: "multi-agent-health",
|
|
28580
|
+
tier: "warning",
|
|
28581
|
+
passed: true,
|
|
28582
|
+
message: `Agent detection: ${error48 instanceof Error ? error48.message : "Unknown error"}`
|
|
28583
|
+
};
|
|
28584
|
+
}
|
|
28585
|
+
}
|
|
28586
|
+
var init_checks_agents = __esm(() => {
|
|
28587
|
+
init_version_detection();
|
|
28588
|
+
});
|
|
28589
|
+
|
|
28068
28590
|
// src/precheck/checks.ts
|
|
28069
28591
|
var init_checks3 = __esm(() => {
|
|
28070
28592
|
init_checks_blockers();
|
|
28071
28593
|
init_checks_warnings();
|
|
28594
|
+
init_checks_agents();
|
|
28072
28595
|
});
|
|
28073
28596
|
|
|
28074
28597
|
// src/precheck/story-size-gate.ts
|
|
@@ -28214,7 +28737,8 @@ async function runPrecheck(config2, prd, options) {
|
|
|
28214
28737
|
() => checkPendingStories(prd),
|
|
28215
28738
|
() => checkOptionalCommands(config2, workdir),
|
|
28216
28739
|
() => checkGitignoreCoversNax(workdir),
|
|
28217
|
-
() => checkPromptOverrideFiles(config2, workdir)
|
|
28740
|
+
() => checkPromptOverrideFiles(config2, workdir),
|
|
28741
|
+
() => checkMultiAgentHealth()
|
|
28218
28742
|
];
|
|
28219
28743
|
for (const checkFn of tier2Checks) {
|
|
28220
28744
|
const result = await checkFn();
|
|
@@ -29364,6 +29888,7 @@ var init_run_initialization = __esm(() => {
|
|
|
29364
29888
|
init_errors3();
|
|
29365
29889
|
init_logger2();
|
|
29366
29890
|
init_prd();
|
|
29891
|
+
init_git();
|
|
29367
29892
|
});
|
|
29368
29893
|
|
|
29369
29894
|
// src/execution/lifecycle/run-setup.ts
|
|
@@ -30947,6 +31472,7 @@ var init_iteration_runner = __esm(() => {
|
|
|
30947
31472
|
init_logger2();
|
|
30948
31473
|
init_runner();
|
|
30949
31474
|
init_stages();
|
|
31475
|
+
init_git();
|
|
30950
31476
|
init_dry_run();
|
|
30951
31477
|
init_pipeline_result_handler();
|
|
30952
31478
|
});
|
|
@@ -31515,6 +32041,7 @@ var _regressionDeps;
|
|
|
31515
32041
|
var init_run_regression = __esm(() => {
|
|
31516
32042
|
init_logger2();
|
|
31517
32043
|
init_prd();
|
|
32044
|
+
init_git();
|
|
31518
32045
|
init_verification();
|
|
31519
32046
|
init_rectification_loop();
|
|
31520
32047
|
init_runners();
|
|
@@ -62594,9 +63121,6 @@ var {
|
|
|
62594
63121
|
Help
|
|
62595
63122
|
} = import__.default;
|
|
62596
63123
|
|
|
62597
|
-
// bin/nax.ts
|
|
62598
|
-
init_agents();
|
|
62599
|
-
|
|
62600
63124
|
// src/cli/analyze.ts
|
|
62601
63125
|
init_acceptance();
|
|
62602
63126
|
init_registry();
|
|
@@ -64717,6 +65241,25 @@ var claudeGenerator = {
|
|
|
64717
65241
|
generate: generateClaudeConfig
|
|
64718
65242
|
};
|
|
64719
65243
|
|
|
65244
|
+
// src/context/generators/codex.ts
|
|
65245
|
+
function generateCodexConfig(context) {
|
|
65246
|
+
const header = `# Codex Instructions
|
|
65247
|
+
|
|
65248
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
65249
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
65250
|
+
|
|
65251
|
+
---
|
|
65252
|
+
|
|
65253
|
+
`;
|
|
65254
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
65255
|
+
return header + metaSection + context.markdown;
|
|
65256
|
+
}
|
|
65257
|
+
var codexGenerator = {
|
|
65258
|
+
name: "codex",
|
|
65259
|
+
outputFile: "codex.md",
|
|
65260
|
+
generate: generateCodexConfig
|
|
65261
|
+
};
|
|
65262
|
+
|
|
64720
65263
|
// src/context/generators/cursor.ts
|
|
64721
65264
|
function generateCursorRules(context) {
|
|
64722
65265
|
const header = `# Project Rules
|
|
@@ -64736,6 +65279,25 @@ var cursorGenerator = {
|
|
|
64736
65279
|
generate: generateCursorRules
|
|
64737
65280
|
};
|
|
64738
65281
|
|
|
65282
|
+
// src/context/generators/gemini.ts
|
|
65283
|
+
function generateGeminiConfig(context) {
|
|
65284
|
+
const header = `# Gemini CLI Context
|
|
65285
|
+
|
|
65286
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
65287
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
65288
|
+
|
|
65289
|
+
---
|
|
65290
|
+
|
|
65291
|
+
`;
|
|
65292
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
65293
|
+
return header + metaSection + context.markdown;
|
|
65294
|
+
}
|
|
65295
|
+
var geminiGenerator = {
|
|
65296
|
+
name: "gemini",
|
|
65297
|
+
outputFile: "GEMINI.md",
|
|
65298
|
+
generate: generateGeminiConfig
|
|
65299
|
+
};
|
|
65300
|
+
|
|
64739
65301
|
// src/context/generators/opencode.ts
|
|
64740
65302
|
function generateOpencodeConfig(context) {
|
|
64741
65303
|
const header = `# Agent Instructions
|
|
@@ -64779,10 +65341,12 @@ var windsurfGenerator = {
|
|
|
64779
65341
|
// src/context/generator.ts
|
|
64780
65342
|
var GENERATORS = {
|
|
64781
65343
|
claude: claudeGenerator,
|
|
65344
|
+
codex: codexGenerator,
|
|
64782
65345
|
opencode: opencodeGenerator,
|
|
64783
65346
|
cursor: cursorGenerator,
|
|
64784
65347
|
windsurf: windsurfGenerator,
|
|
64785
|
-
aider: aiderGenerator
|
|
65348
|
+
aider: aiderGenerator,
|
|
65349
|
+
gemini: geminiGenerator
|
|
64786
65350
|
};
|
|
64787
65351
|
async function loadContextContent(options, config2) {
|
|
64788
65352
|
if (!existsSync18(options.contextPath)) {
|
|
@@ -64834,7 +65398,7 @@ async function generateAll(options, config2) {
|
|
|
64834
65398
|
}
|
|
64835
65399
|
|
|
64836
65400
|
// src/cli/generate.ts
|
|
64837
|
-
var VALID_AGENTS = ["claude", "opencode", "cursor", "windsurf", "aider"];
|
|
65401
|
+
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
64838
65402
|
async function generateCommand(options) {
|
|
64839
65403
|
const workdir = process.cwd();
|
|
64840
65404
|
const contextPath = options.context ? join24(workdir, options.context) : join24(workdir, "nax/context.md");
|
|
@@ -64922,19 +65486,19 @@ var FIELD_DESCRIPTIONS = {
|
|
|
64922
65486
|
"models.fast": "Fast model for lightweight tasks (e.g., haiku)",
|
|
64923
65487
|
"models.balanced": "Balanced model for general coding (e.g., sonnet)",
|
|
64924
65488
|
"models.powerful": "Powerful model for complex tasks (e.g., opus)",
|
|
64925
|
-
autoMode: "Auto mode configuration for agent orchestration",
|
|
65489
|
+
autoMode: "Auto mode configuration for agent orchestration. Enables multi-agent routing with model tier selection per task complexity and escalation on failures.",
|
|
64926
65490
|
"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",
|
|
65491
|
+
"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.",
|
|
65492
|
+
"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.',
|
|
65493
|
+
"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.",
|
|
65494
|
+
"autoMode.complexityRouting.simple": "Model tier for simple tasks (low complexity, straightforward changes)",
|
|
65495
|
+
"autoMode.complexityRouting.medium": "Model tier for medium tasks (moderate complexity, multi-file changes)",
|
|
65496
|
+
"autoMode.complexityRouting.complex": "Model tier for complex tasks (high complexity, architectural decisions)",
|
|
65497
|
+
"autoMode.complexityRouting.expert": "Model tier for expert tasks (highest complexity, novel problems, design patterns)",
|
|
65498
|
+
"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
65499
|
"autoMode.escalation.enabled": "Enable tier escalation on failure",
|
|
64936
|
-
"autoMode.escalation.tierOrder":
|
|
64937
|
-
"autoMode.escalation.escalateEntireBatch": "
|
|
65500
|
+
"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.',
|
|
65501
|
+
"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
65502
|
routing: "Model routing strategy configuration",
|
|
64939
65503
|
"routing.strategy": "Routing strategy: keyword | llm | manual | adaptive | custom",
|
|
64940
65504
|
"routing.customStrategyPath": "Path to custom routing strategy (if strategy=custom)",
|
|
@@ -65243,8 +65807,11 @@ function displayConfigWithDescriptions(obj, path13, sources, indent = 0) {
|
|
|
65243
65807
|
const currentPathStr = currentPath.join(".");
|
|
65244
65808
|
const description = FIELD_DESCRIPTIONS[currentPathStr];
|
|
65245
65809
|
if (description) {
|
|
65246
|
-
const
|
|
65247
|
-
const
|
|
65810
|
+
const pathParts = currentPathStr.split(".");
|
|
65811
|
+
const isDirectSubsection = pathParts.length === 2;
|
|
65812
|
+
const isKeySection = ["prompts", "autoMode", "models", "routing"].includes(pathParts[0]);
|
|
65813
|
+
const shouldIncludePath = isKeySection && isDirectSubsection;
|
|
65814
|
+
const comment = shouldIncludePath ? `${currentPathStr}: ${description}` : description;
|
|
65248
65815
|
console.log(`${indentStr}# ${comment}`);
|
|
65249
65816
|
}
|
|
65250
65817
|
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
@@ -65312,6 +65879,55 @@ function formatValueForTable(value) {
|
|
|
65312
65879
|
}
|
|
65313
65880
|
return String(value);
|
|
65314
65881
|
}
|
|
65882
|
+
// src/cli/agents.ts
|
|
65883
|
+
init_registry();
|
|
65884
|
+
init_version_detection();
|
|
65885
|
+
async function agentsListCommand(config2, _workdir) {
|
|
65886
|
+
const agentVersions = await Promise.all(ALL_AGENTS.map(async (agent) => ({
|
|
65887
|
+
name: agent.name,
|
|
65888
|
+
displayName: agent.displayName,
|
|
65889
|
+
binary: agent.binary,
|
|
65890
|
+
version: await getAgentVersion(agent.binary),
|
|
65891
|
+
installed: await agent.isInstalled(),
|
|
65892
|
+
capabilities: agent.capabilities,
|
|
65893
|
+
isDefault: config2.autoMode.defaultAgent === agent.name
|
|
65894
|
+
})));
|
|
65895
|
+
const rows = agentVersions.map((info) => {
|
|
65896
|
+
const status = info.installed ? "installed" : "unavailable";
|
|
65897
|
+
const versionStr = info.version || "-";
|
|
65898
|
+
const defaultMarker = info.isDefault ? " (default)" : "";
|
|
65899
|
+
return {
|
|
65900
|
+
name: info.displayName + defaultMarker,
|
|
65901
|
+
status,
|
|
65902
|
+
version: versionStr,
|
|
65903
|
+
binary: info.binary,
|
|
65904
|
+
tiers: info.capabilities.supportedTiers.join(", ")
|
|
65905
|
+
};
|
|
65906
|
+
});
|
|
65907
|
+
if (rows.length === 0) {
|
|
65908
|
+
console.log("No agents available.");
|
|
65909
|
+
return;
|
|
65910
|
+
}
|
|
65911
|
+
const widths = {
|
|
65912
|
+
name: Math.max(5, ...rows.map((r) => r.name.length)),
|
|
65913
|
+
status: Math.max(6, ...rows.map((r) => r.status.length)),
|
|
65914
|
+
version: Math.max(7, ...rows.map((r) => r.version.length)),
|
|
65915
|
+
binary: Math.max(6, ...rows.map((r) => r.binary.length)),
|
|
65916
|
+
tiers: Math.max(5, ...rows.map((r) => r.tiers.length))
|
|
65917
|
+
};
|
|
65918
|
+
console.log(`
|
|
65919
|
+
Available Agents:
|
|
65920
|
+
`);
|
|
65921
|
+
console.log(`${pad2("Agent", widths.name)} ${pad2("Status", widths.status)} ${pad2("Version", widths.version)} ${pad2("Binary", widths.binary)} ${pad2("Tiers", widths.tiers)}`);
|
|
65922
|
+
console.log(`${"-".repeat(widths.name)} ${"-".repeat(widths.status)} ${"-".repeat(widths.version)} ${"-".repeat(widths.binary)} ${"-".repeat(widths.tiers)}`);
|
|
65923
|
+
for (const row of rows) {
|
|
65924
|
+
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)}`);
|
|
65925
|
+
}
|
|
65926
|
+
console.log();
|
|
65927
|
+
}
|
|
65928
|
+
function pad2(str, width) {
|
|
65929
|
+
return str.padEnd(width);
|
|
65930
|
+
}
|
|
65315
65931
|
// src/commands/diagnose.ts
|
|
65316
65932
|
async function diagnose(options) {
|
|
65317
65933
|
await diagnoseCommand(options);
|
|
@@ -65688,7 +66304,7 @@ var ANSI_RE = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
|
65688
66304
|
function visibleLength(str) {
|
|
65689
66305
|
return str.replace(ANSI_RE, "").length;
|
|
65690
66306
|
}
|
|
65691
|
-
function
|
|
66307
|
+
function pad3(str, width) {
|
|
65692
66308
|
const padding = Math.max(0, width - visibleLength(str));
|
|
65693
66309
|
return str + " ".repeat(padding);
|
|
65694
66310
|
}
|
|
@@ -65747,12 +66363,12 @@ async function runsCommand(options = {}) {
|
|
|
65747
66363
|
date: 11
|
|
65748
66364
|
};
|
|
65749
66365
|
const header = [
|
|
65750
|
-
|
|
65751
|
-
|
|
65752
|
-
|
|
65753
|
-
|
|
65754
|
-
|
|
65755
|
-
|
|
66366
|
+
pad3(source_default.bold("RUN ID"), COL.runId),
|
|
66367
|
+
pad3(source_default.bold("PROJECT"), COL.project),
|
|
66368
|
+
pad3(source_default.bold("FEATURE"), COL.feature),
|
|
66369
|
+
pad3(source_default.bold("STATUS"), COL.status),
|
|
66370
|
+
pad3(source_default.bold("STORIES"), COL.stories),
|
|
66371
|
+
pad3(source_default.bold("DURATION"), COL.duration),
|
|
65756
66372
|
source_default.bold("DATE")
|
|
65757
66373
|
].join(" ");
|
|
65758
66374
|
console.log();
|
|
@@ -65761,12 +66377,12 @@ async function runsCommand(options = {}) {
|
|
|
65761
66377
|
for (const row of displayed) {
|
|
65762
66378
|
const colored = colorStatus(row.status);
|
|
65763
66379
|
const line = [
|
|
65764
|
-
|
|
65765
|
-
|
|
65766
|
-
|
|
65767
|
-
|
|
65768
|
-
|
|
65769
|
-
|
|
66380
|
+
pad3(row.runId, COL.runId),
|
|
66381
|
+
pad3(row.project, COL.project),
|
|
66382
|
+
pad3(row.feature, COL.feature),
|
|
66383
|
+
pad3(colored, COL.status + (colored.length - visibleLength(colored))),
|
|
66384
|
+
pad3(`${row.passed}/${row.total}`, COL.stories),
|
|
66385
|
+
pad3(formatDuration3(row.durationMs), COL.duration),
|
|
65770
66386
|
formatDate(row.registeredAt)
|
|
65771
66387
|
].join(" ");
|
|
65772
66388
|
console.log(line);
|
|
@@ -73839,16 +74455,21 @@ program2.command("analyze").description("Parse spec.md into prd.json via agent d
|
|
|
73839
74455
|
process.exit(1);
|
|
73840
74456
|
}
|
|
73841
74457
|
});
|
|
73842
|
-
program2.command("agents").description("
|
|
73843
|
-
|
|
73844
|
-
|
|
73845
|
-
|
|
73846
|
-
|
|
73847
|
-
|
|
73848
|
-
|
|
73849
|
-
|
|
74458
|
+
program2.command("agents").description("List available coding agents with status and capabilities").option("-d, --dir <path>", "Project directory", process.cwd()).action(async (options) => {
|
|
74459
|
+
let workdir;
|
|
74460
|
+
try {
|
|
74461
|
+
workdir = validateDirectory(options.dir);
|
|
74462
|
+
} catch (err) {
|
|
74463
|
+
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
74464
|
+
process.exit(1);
|
|
74465
|
+
}
|
|
74466
|
+
try {
|
|
74467
|
+
const config2 = await loadConfig(workdir);
|
|
74468
|
+
await agentsListCommand(config2, workdir);
|
|
74469
|
+
} catch (err) {
|
|
74470
|
+
console.error(source_default.red(`Error: ${err.message}`));
|
|
74471
|
+
process.exit(1);
|
|
73850
74472
|
}
|
|
73851
|
-
console.log();
|
|
73852
74473
|
});
|
|
73853
74474
|
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
74475
|
try {
|