@nathapp/nax 0.56.5 → 0.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +463 -15
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -17991,7 +17991,13 @@ var init_defaults = __esm(() => {
|
|
|
17991
17991
|
model: "fast",
|
|
17992
17992
|
refinement: true,
|
|
17993
17993
|
redGate: true,
|
|
17994
|
-
timeoutMs: 1800000
|
|
17994
|
+
timeoutMs: 1800000,
|
|
17995
|
+
fix: {
|
|
17996
|
+
diagnoseModel: "fast",
|
|
17997
|
+
fixModel: "balanced",
|
|
17998
|
+
strategy: "diagnose-first",
|
|
17999
|
+
maxRetries: 2
|
|
18000
|
+
}
|
|
17995
18001
|
},
|
|
17996
18002
|
context: {
|
|
17997
18003
|
fileInjection: "disabled",
|
|
@@ -18086,7 +18092,7 @@ function isLegacyFlatModels(val) {
|
|
|
18086
18092
|
}
|
|
18087
18093
|
return false;
|
|
18088
18094
|
}
|
|
18089
|
-
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, DebaterSchema, toObject = (val) => val === undefined || val === null ? {} : val, RESOLVER_TYPES, makeResolverSchema = (defaultType) => exports_external.preprocess(toObject, exports_external.object({
|
|
18095
|
+
var TokenPricingSchema, ModelDefSchema, ModelEntrySchema, PerAgentModelMapSchema, ModelMapSchema, ModelTierSchema, TierConfigSchema, AutoModeConfigSchema, RectificationConfigSchema, RegressionGateConfigSchema, SmartTestRunnerConfigSchema, SMART_TEST_RUNNER_DEFAULT, smartTestRunnerFieldSchema, ExecutionConfigSchema, QualityConfigSchema, TddConfigSchema, ConstitutionConfigSchema, AnalyzeConfigSchema, SemanticReviewConfigSchema, ReviewConfigSchema, PlanConfigSchema, AcceptanceFixConfigSchema, AcceptanceConfigSchema, TestCoverageConfigSchema, ContextAutoDetectConfigSchema, ContextConfigSchema, LlmRoutingConfigSchema, RoutingConfigSchema, OptimizerConfigSchema, PluginConfigEntrySchema, HooksConfigSchema, InteractionConfigSchema, StorySizeGateConfigSchema, AgentConfigSchema, PrecheckConfigSchema, PromptsConfigSchema, ProjectProfileSchema, VALID_AGENT_TYPES, GenerateConfigSchema, DebaterSchema, toObject = (val) => val === undefined || val === null ? {} : val, RESOLVER_TYPES, makeResolverSchema = (defaultType) => exports_external.preprocess(toObject, exports_external.object({
|
|
18090
18096
|
type: exports_external.enum(RESOLVER_TYPES).default(defaultType),
|
|
18091
18097
|
agent: exports_external.string().min(1).optional(),
|
|
18092
18098
|
tieBreaker: exports_external.string().min(1).optional(),
|
|
@@ -18299,6 +18305,12 @@ var init_schemas3 = __esm(() => {
|
|
|
18299
18305
|
model: ModelTierSchema,
|
|
18300
18306
|
outputPath: exports_external.string().min(1, "plan.outputPath must be non-empty")
|
|
18301
18307
|
});
|
|
18308
|
+
AcceptanceFixConfigSchema = exports_external.object({
|
|
18309
|
+
diagnoseModel: exports_external.string().min(1, "acceptance.fix.diagnoseModel must be non-empty"),
|
|
18310
|
+
fixModel: exports_external.string().min(1, "acceptance.fix.fixModel must be non-empty"),
|
|
18311
|
+
strategy: exports_external.enum(["diagnose-first", "implement-only"]),
|
|
18312
|
+
maxRetries: exports_external.number().int().nonnegative()
|
|
18313
|
+
});
|
|
18302
18314
|
AcceptanceConfigSchema = exports_external.object({
|
|
18303
18315
|
enabled: exports_external.boolean(),
|
|
18304
18316
|
maxRetries: exports_external.number().int().nonnegative(),
|
|
@@ -18310,7 +18322,13 @@ var init_schemas3 = __esm(() => {
|
|
|
18310
18322
|
redGate: exports_external.boolean().default(true),
|
|
18311
18323
|
testStrategy: exports_external.enum(["unit", "component", "cli", "e2e", "snapshot"]).optional(),
|
|
18312
18324
|
testFramework: exports_external.string().min(1, "acceptance.testFramework must be non-empty").optional(),
|
|
18313
|
-
timeoutMs: exports_external.number().int().min(30000).max(3600000).default(1800000)
|
|
18325
|
+
timeoutMs: exports_external.number().int().min(30000).max(3600000).default(1800000),
|
|
18326
|
+
fix: AcceptanceFixConfigSchema.optional().default({
|
|
18327
|
+
diagnoseModel: "fast",
|
|
18328
|
+
fixModel: "balanced",
|
|
18329
|
+
strategy: "diagnose-first",
|
|
18330
|
+
maxRetries: 2
|
|
18331
|
+
})
|
|
18314
18332
|
});
|
|
18315
18333
|
TestCoverageConfigSchema = exports_external.object({
|
|
18316
18334
|
enabled: exports_external.boolean().default(true),
|
|
@@ -18982,7 +19000,8 @@ class AcpAgentAdapter {
|
|
|
18982
19000
|
const tryOneAgent = async (agentName) => {
|
|
18983
19001
|
const model = await resolveModel2(agentName);
|
|
18984
19002
|
const cmdStr = `acpx --model ${model} ${agentName}`;
|
|
18985
|
-
const
|
|
19003
|
+
const timeoutSeconds = Math.ceil(timeoutMs / 1000);
|
|
19004
|
+
const client = _acpAdapterDeps.createClient(cmdStr, workdir, timeoutSeconds);
|
|
18986
19005
|
await client.start();
|
|
18987
19006
|
let session = null;
|
|
18988
19007
|
let hadError = false;
|
|
@@ -20909,7 +20928,8 @@ async function refineAcceptanceCriteria(criteria, context) {
|
|
|
20909
20928
|
featureName,
|
|
20910
20929
|
storyId,
|
|
20911
20930
|
workdir,
|
|
20912
|
-
sessionRole: "refine"
|
|
20931
|
+
sessionRole: "refine",
|
|
20932
|
+
timeoutMs: config2.acceptance?.timeoutMs ?? 120000
|
|
20913
20933
|
});
|
|
20914
20934
|
response = typeof completeResult === "string" ? completeResult : completeResult.output;
|
|
20915
20935
|
} catch (error48) {
|
|
@@ -20958,6 +20978,7 @@ var init_refinement = __esm(() => {
|
|
|
20958
20978
|
// src/acceptance/generator.ts
|
|
20959
20979
|
var exports_generator = {};
|
|
20960
20980
|
__export(exports_generator, {
|
|
20981
|
+
resolveAcceptanceTestFile: () => resolveAcceptanceTestFile,
|
|
20961
20982
|
parseAcceptanceCriteria: () => parseAcceptanceCriteria,
|
|
20962
20983
|
generateSkeletonTests: () => generateSkeletonTests,
|
|
20963
20984
|
generateFromPRD: () => generateFromPRD,
|
|
@@ -20993,6 +21014,9 @@ function acceptanceTestFilename(language) {
|
|
|
20993
21014
|
return ".nax-acceptance.test.ts";
|
|
20994
21015
|
}
|
|
20995
21016
|
}
|
|
21017
|
+
function resolveAcceptanceTestFile(language, testPathConfig) {
|
|
21018
|
+
return testPathConfig ?? acceptanceTestFilename(language);
|
|
21019
|
+
}
|
|
20996
21020
|
function buildAcceptanceRunCommand(testPath, testFramework, commandOverride) {
|
|
20997
21021
|
if (commandOverride) {
|
|
20998
21022
|
const resolved = commandOverride.replace(/\{\{files\}\}/g, testPath).replace(/\{\{file\}\}/g, testPath).replace(/\{\{FILE\}\}/g, testPath);
|
|
@@ -21061,7 +21085,7 @@ Rules:
|
|
|
21061
21085
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
21062
21086
|
- **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
|
|
21063
21087
|
- **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
|
|
21064
|
-
- **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join5(options.workdir, ".nax", "features", options.featureName,
|
|
21088
|
+
- **Path anchor (CRITICAL)**: Write the test file to this exact path: \`${join5(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile(options.language, options.config?.acceptance?.testPath))}\`. Import from package sources using relative paths like \`../../../src/...\` (3 levels up from \`.nax/features/<name>/\` to the package root).`;
|
|
21065
21089
|
const prompt = basePrompt;
|
|
21066
21090
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
21067
21091
|
const completeResult = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
@@ -21080,7 +21104,7 @@ Rules:
|
|
|
21080
21104
|
outputPreview: rawOutput.slice(0, 300)
|
|
21081
21105
|
});
|
|
21082
21106
|
if (!testCode) {
|
|
21083
|
-
const targetPath = join5(options.workdir, ".nax", "features", options.featureName,
|
|
21107
|
+
const targetPath = join5(options.workdir, ".nax", "features", options.featureName, resolveAcceptanceTestFile(options.language, options.config?.acceptance?.testPath));
|
|
21084
21108
|
let recoveryFailed = false;
|
|
21085
21109
|
logger.debug("acceptance", "BUG-076 recovery: checking for agent-written file", { targetPath });
|
|
21086
21110
|
try {
|
|
@@ -22132,7 +22156,7 @@ var package_default;
|
|
|
22132
22156
|
var init_package = __esm(() => {
|
|
22133
22157
|
package_default = {
|
|
22134
22158
|
name: "@nathapp/nax",
|
|
22135
|
-
version: "0.
|
|
22159
|
+
version: "0.57.0",
|
|
22136
22160
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22137
22161
|
type: "module",
|
|
22138
22162
|
bin: {
|
|
@@ -22211,8 +22235,8 @@ var init_version = __esm(() => {
|
|
|
22211
22235
|
NAX_VERSION = package_default.version;
|
|
22212
22236
|
NAX_COMMIT = (() => {
|
|
22213
22237
|
try {
|
|
22214
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22215
|
-
return "
|
|
22238
|
+
if (/^[0-9a-f]{6,10}$/.test("478df448"))
|
|
22239
|
+
return "478df448";
|
|
22216
22240
|
} catch {}
|
|
22217
22241
|
try {
|
|
22218
22242
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -25977,6 +26001,7 @@ ${stderr}` };
|
|
|
25977
26001
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
25978
26002
|
}
|
|
25979
26003
|
const language = (ctx.effectiveConfig ?? ctx.config).project?.language;
|
|
26004
|
+
const testPathConfig = (ctx.effectiveConfig ?? ctx.config).acceptance.testPath;
|
|
25980
26005
|
const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
|
|
25981
26006
|
const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria);
|
|
25982
26007
|
const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-"));
|
|
@@ -25999,7 +26024,7 @@ ${stderr}` };
|
|
|
25999
26024
|
const testPaths = [];
|
|
26000
26025
|
for (const [workdir] of workdirGroups) {
|
|
26001
26026
|
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
26002
|
-
const testPath = path5.join(packageDir, ".nax", "features", featureName,
|
|
26027
|
+
const testPath = path5.join(packageDir, ".nax", "features", featureName, resolveAcceptanceTestFile(language, testPathConfig));
|
|
26003
26028
|
testPaths.push({ testPath, packageDir });
|
|
26004
26029
|
}
|
|
26005
26030
|
let totalCriteria = 0;
|
|
@@ -26062,7 +26087,7 @@ ${stderr}` };
|
|
|
26062
26087
|
testableCount = allRefinedCriteria.filter((r) => r.testable).length;
|
|
26063
26088
|
for (const [workdir, group] of workdirGroups) {
|
|
26064
26089
|
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
26065
|
-
const testPath = path5.join(packageDir,
|
|
26090
|
+
const testPath = path5.join(packageDir, resolveAcceptanceTestFile(language, testPathConfig));
|
|
26066
26091
|
const groupStoryIds = new Set(group.stories.map((s) => s.id));
|
|
26067
26092
|
const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
|
|
26068
26093
|
const result = await _acceptanceSetupDeps.generate(group.stories, groupRefined, {
|
|
@@ -34087,6 +34112,214 @@ var init_crash_recovery = __esm(() => {
|
|
|
34087
34112
|
init_crash_heartbeat();
|
|
34088
34113
|
});
|
|
34089
34114
|
|
|
34115
|
+
// src/acceptance/fix-diagnosis.ts
|
|
34116
|
+
function parseImportStatements(content) {
|
|
34117
|
+
const importRegex = /import\s+(?:{[^}]+}|[^;]+)\s+from\s+["']([^"']+)["']/g;
|
|
34118
|
+
const imports = [];
|
|
34119
|
+
const regexMatch = content.matchAll(importRegex);
|
|
34120
|
+
for (const match of regexMatch) {
|
|
34121
|
+
imports.push(match[1]);
|
|
34122
|
+
}
|
|
34123
|
+
return imports;
|
|
34124
|
+
}
|
|
34125
|
+
function resolveImportPaths(imports, workdir) {
|
|
34126
|
+
const resolved = [];
|
|
34127
|
+
for (const imp of imports) {
|
|
34128
|
+
if (imp.startsWith(".")) {
|
|
34129
|
+
resolved.push(imp);
|
|
34130
|
+
}
|
|
34131
|
+
}
|
|
34132
|
+
return resolved.slice(0, MAX_SOURCE_FILES);
|
|
34133
|
+
}
|
|
34134
|
+
async function readSourceFileContent(filePath, workdir) {
|
|
34135
|
+
try {
|
|
34136
|
+
const fullPath = `${workdir}/${filePath}`;
|
|
34137
|
+
const file3 = await Bun.file(fullPath).text();
|
|
34138
|
+
const lines = file3.split(`
|
|
34139
|
+
`).slice(0, MAX_FILE_LINES);
|
|
34140
|
+
return { path: filePath, content: lines.join(`
|
|
34141
|
+
`) };
|
|
34142
|
+
} catch {
|
|
34143
|
+
return null;
|
|
34144
|
+
}
|
|
34145
|
+
}
|
|
34146
|
+
function buildDiagnosisPrompt(options) {
|
|
34147
|
+
const truncatedOutput = options.testOutput.slice(0, MAX_TEST_OUTPUT_CHARS);
|
|
34148
|
+
const sourceFilesSection = options.sourceFiles.length > 0 ? options.sourceFiles.map((f) => `FILE: ${f.path}
|
|
34149
|
+
\`\`\`
|
|
34150
|
+
${f.content}
|
|
34151
|
+
\`\`\``).join(`
|
|
34152
|
+
|
|
34153
|
+
`) : "(No source files could be resolved from imports)";
|
|
34154
|
+
return `You are a debugging expert. An acceptance test has failed.
|
|
34155
|
+
|
|
34156
|
+
TASK: Diagnose whether the failure is due to a bug in the SOURCE CODE or a bug in the TEST CODE.
|
|
34157
|
+
|
|
34158
|
+
FAILING TEST OUTPUT:
|
|
34159
|
+
${truncatedOutput}
|
|
34160
|
+
|
|
34161
|
+
ACCEPTANCE TEST FILE CONTENT:
|
|
34162
|
+
\`\`\`typescript
|
|
34163
|
+
${options.testFileContent}
|
|
34164
|
+
\`\`\`
|
|
34165
|
+
|
|
34166
|
+
SOURCE FILES (auto-detected from imports, up to ${MAX_FILE_LINES} lines each):
|
|
34167
|
+
${sourceFilesSection}
|
|
34168
|
+
|
|
34169
|
+
Respond with ONLY a JSON object in this exact format (no markdown, no extra text):
|
|
34170
|
+
{
|
|
34171
|
+
"verdict": "source_bug" | "test_bug" | "both",
|
|
34172
|
+
"reasoning": "Your analysis explaining why this is a source_bug, test_bug, or both",
|
|
34173
|
+
"confidence": 0.0-1.0,
|
|
34174
|
+
"testIssues": ["Issue in test code if any"],
|
|
34175
|
+
"sourceIssues": ["Issue in source code if any"]
|
|
34176
|
+
}`;
|
|
34177
|
+
}
|
|
34178
|
+
async function diagnoseAcceptanceFailure(agent, options) {
|
|
34179
|
+
if (!agent) {
|
|
34180
|
+
throw new Error("[diagnosis] Agent adapter is required");
|
|
34181
|
+
}
|
|
34182
|
+
const { testOutput, testFileContent, config: config2, workdir, featureName, storyId } = options;
|
|
34183
|
+
const sessionName = buildSessionName(workdir, featureName, storyId, "diagnose");
|
|
34184
|
+
const diagnoseModelTier = config2.acceptance.fix.diagnoseModel;
|
|
34185
|
+
const modelDef = resolveModelForAgent(config2.models, config2.autoMode.defaultAgent, diagnoseModelTier, config2.autoMode.defaultAgent);
|
|
34186
|
+
const imports = parseImportStatements(testFileContent);
|
|
34187
|
+
const relativeImports = resolveImportPaths(imports, workdir);
|
|
34188
|
+
const sourceFiles = await Promise.all(relativeImports.map((imp) => readSourceFileContent(imp, workdir)));
|
|
34189
|
+
const validSourceFiles = sourceFiles.filter((f) => f !== null);
|
|
34190
|
+
const prompt = buildDiagnosisPrompt({
|
|
34191
|
+
testOutput,
|
|
34192
|
+
testFileContent,
|
|
34193
|
+
sourceFiles: validSourceFiles
|
|
34194
|
+
});
|
|
34195
|
+
try {
|
|
34196
|
+
const result = await agent.run({
|
|
34197
|
+
prompt,
|
|
34198
|
+
workdir,
|
|
34199
|
+
modelTier: undefined,
|
|
34200
|
+
modelDef,
|
|
34201
|
+
timeoutSeconds: 300,
|
|
34202
|
+
sessionRole: "diagnose",
|
|
34203
|
+
acpSessionName: sessionName,
|
|
34204
|
+
featureName,
|
|
34205
|
+
storyId,
|
|
34206
|
+
config: config2
|
|
34207
|
+
});
|
|
34208
|
+
const diagnosis = parseDiagnosisResult(result.output);
|
|
34209
|
+
if (diagnosis) {
|
|
34210
|
+
return diagnosis;
|
|
34211
|
+
}
|
|
34212
|
+
return {
|
|
34213
|
+
verdict: "source_bug",
|
|
34214
|
+
reasoning: "diagnosis failed \u2014 falling back to source fix",
|
|
34215
|
+
confidence: 0
|
|
34216
|
+
};
|
|
34217
|
+
} catch (err) {
|
|
34218
|
+
return {
|
|
34219
|
+
verdict: "source_bug",
|
|
34220
|
+
reasoning: "diagnosis failed \u2014 falling back to source fix",
|
|
34221
|
+
confidence: 0
|
|
34222
|
+
};
|
|
34223
|
+
}
|
|
34224
|
+
}
|
|
34225
|
+
function parseDiagnosisResult(output) {
|
|
34226
|
+
if (!output || output.trim() === "") {
|
|
34227
|
+
return null;
|
|
34228
|
+
}
|
|
34229
|
+
try {
|
|
34230
|
+
const cleaned = output.trim();
|
|
34231
|
+
let jsonStr = cleaned;
|
|
34232
|
+
const firstBrace = cleaned.indexOf("{");
|
|
34233
|
+
const lastBrace = cleaned.lastIndexOf("}");
|
|
34234
|
+
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
|
|
34235
|
+
jsonStr = cleaned.slice(firstBrace, lastBrace + 1);
|
|
34236
|
+
}
|
|
34237
|
+
const parsed = JSON.parse(jsonStr);
|
|
34238
|
+
if (typeof parsed.verdict === "string" && typeof parsed.reasoning === "string" && typeof parsed.confidence === "number") {
|
|
34239
|
+
return {
|
|
34240
|
+
verdict: parsed.verdict,
|
|
34241
|
+
reasoning: parsed.reasoning,
|
|
34242
|
+
confidence: parsed.confidence,
|
|
34243
|
+
testIssues: parsed.testIssues,
|
|
34244
|
+
sourceIssues: parsed.sourceIssues
|
|
34245
|
+
};
|
|
34246
|
+
}
|
|
34247
|
+
return null;
|
|
34248
|
+
} catch {
|
|
34249
|
+
return null;
|
|
34250
|
+
}
|
|
34251
|
+
}
|
|
34252
|
+
var MAX_SOURCE_FILES = 5, MAX_FILE_LINES = 500, MAX_TEST_OUTPUT_CHARS = 2000;
|
|
34253
|
+
var init_fix_diagnosis = __esm(() => {
|
|
34254
|
+
init_adapter();
|
|
34255
|
+
});
|
|
34256
|
+
|
|
34257
|
+
// src/acceptance/fix-executor.ts
|
|
34258
|
+
function buildSourceFixPrompt(options) {
|
|
34259
|
+
const { testOutput, diagnosis, acceptanceTestPath } = options;
|
|
34260
|
+
let prompt = `ACCEPTANCE TEST FAILURE:
|
|
34261
|
+
${testOutput}
|
|
34262
|
+
|
|
34263
|
+
`;
|
|
34264
|
+
if (diagnosis.reasoning) {
|
|
34265
|
+
prompt += `DIAGNOSIS:
|
|
34266
|
+
${diagnosis.reasoning}
|
|
34267
|
+
|
|
34268
|
+
`;
|
|
34269
|
+
}
|
|
34270
|
+
prompt += `ACCEPTANCE TEST FILE: ${acceptanceTestPath}
|
|
34271
|
+
|
|
34272
|
+
`;
|
|
34273
|
+
prompt += "Fix the source implementation. Do NOT modify the test file.";
|
|
34274
|
+
return prompt;
|
|
34275
|
+
}
|
|
34276
|
+
async function executeSourceFix(agent, options) {
|
|
34277
|
+
if (!agent) {
|
|
34278
|
+
throw new Error("[fix-executor] agent is required");
|
|
34279
|
+
}
|
|
34280
|
+
const { testOutput, testFileContent, diagnosis, config: config2, workdir, featureName, storyId, acceptanceTestPath } = options;
|
|
34281
|
+
const modelDef = resolveModelForAgent(config2.models, config2.autoMode.defaultAgent, config2.acceptance.fix.fixModel, config2.autoMode.defaultAgent);
|
|
34282
|
+
const sessionName = buildSessionName(workdir, featureName, storyId, "source-fix");
|
|
34283
|
+
const prompt = buildSourceFixPrompt(options);
|
|
34284
|
+
const timeoutSeconds = config2.execution?.sessionTimeoutSeconds ?? 3600;
|
|
34285
|
+
const runOptions = {
|
|
34286
|
+
prompt,
|
|
34287
|
+
workdir,
|
|
34288
|
+
modelTier: undefined,
|
|
34289
|
+
modelDef,
|
|
34290
|
+
timeoutSeconds,
|
|
34291
|
+
sessionRole: "source-fix",
|
|
34292
|
+
acpSessionName: sessionName,
|
|
34293
|
+
featureName,
|
|
34294
|
+
storyId,
|
|
34295
|
+
config: config2,
|
|
34296
|
+
pipelineStage: "acceptance"
|
|
34297
|
+
};
|
|
34298
|
+
const result = await agent.run(runOptions);
|
|
34299
|
+
let success2 = result.success;
|
|
34300
|
+
try {
|
|
34301
|
+
const verifyProc = _fixExecutorDeps.spawn(["bun", "test", acceptanceTestPath], {
|
|
34302
|
+
cwd: workdir,
|
|
34303
|
+
stdout: "pipe",
|
|
34304
|
+
stderr: "pipe"
|
|
34305
|
+
});
|
|
34306
|
+
const exitCode = await verifyProc.exited;
|
|
34307
|
+
success2 = exitCode === 0;
|
|
34308
|
+
} catch {
|
|
34309
|
+
success2 = result.success;
|
|
34310
|
+
}
|
|
34311
|
+
return {
|
|
34312
|
+
success: success2,
|
|
34313
|
+
cost: result.estimatedCost
|
|
34314
|
+
};
|
|
34315
|
+
}
|
|
34316
|
+
var _fixExecutorDeps;
|
|
34317
|
+
var init_fix_executor = __esm(() => {
|
|
34318
|
+
init_adapter();
|
|
34319
|
+
init_bun_deps();
|
|
34320
|
+
_fixExecutorDeps = { spawn };
|
|
34321
|
+
});
|
|
34322
|
+
|
|
34090
34323
|
// src/execution/lifecycle/acceptance-loop.ts
|
|
34091
34324
|
var exports_acceptance_loop = {};
|
|
34092
34325
|
__export(exports_acceptance_loop, {
|
|
@@ -34113,6 +34346,14 @@ async function loadSpecContent(featureDir) {
|
|
|
34113
34346
|
const specFile = Bun.file(specPath);
|
|
34114
34347
|
return await specFile.exists() ? await specFile.text() : "";
|
|
34115
34348
|
}
|
|
34349
|
+
async function loadAcceptanceTestContent(featureDir) {
|
|
34350
|
+
if (!featureDir)
|
|
34351
|
+
return { content: "", path: "" };
|
|
34352
|
+
const testPath = path14.join(featureDir, "acceptance.test.ts");
|
|
34353
|
+
const testFile = Bun.file(testPath);
|
|
34354
|
+
const content = await testFile.exists() ? await testFile.text() : "";
|
|
34355
|
+
return { content, path: testPath };
|
|
34356
|
+
}
|
|
34116
34357
|
function buildResult(success2, prd, totalCost, iterations, storiesCompleted, prdDirty) {
|
|
34117
34358
|
return { success: success2, prd, totalCost, iterations, storiesCompleted, prdDirty };
|
|
34118
34359
|
}
|
|
@@ -34198,6 +34439,191 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
34198
34439
|
logger?.info("acceptance", "Acceptance test regenerated successfully");
|
|
34199
34440
|
return true;
|
|
34200
34441
|
}
|
|
34442
|
+
async function runFixRouting(options) {
|
|
34443
|
+
const logger = getSafeLogger();
|
|
34444
|
+
const { ctx, failures, prd, acceptanceContext } = options;
|
|
34445
|
+
const agentName = ctx.config.autoMode.defaultAgent;
|
|
34446
|
+
const agent = (ctx.agentGetFn ?? _acceptanceLoopDeps.getAgent)(agentName);
|
|
34447
|
+
if (!agent) {
|
|
34448
|
+
logger?.error("acceptance", "Agent not found for fix routing");
|
|
34449
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34450
|
+
}
|
|
34451
|
+
const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
|
|
34452
|
+
const fixMaxRetries = ctx.config.acceptance.fix?.maxRetries ?? 2;
|
|
34453
|
+
const { content: testFileContent, path: acceptanceTestPath } = await loadAcceptanceTestContent(ctx.featureDir);
|
|
34454
|
+
const firstStory = prd.userStories[0];
|
|
34455
|
+
const storyId = firstStory?.id ?? "unknown";
|
|
34456
|
+
if (strategy === "implement-only") {
|
|
34457
|
+
logger?.info("acceptance", "Strategy is implement-only \u2014 executing source fix directly");
|
|
34458
|
+
let fixAttempts = 0;
|
|
34459
|
+
while (fixAttempts < fixMaxRetries) {
|
|
34460
|
+
fixAttempts++;
|
|
34461
|
+
logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
|
|
34462
|
+
const defaultDiagnosis = {
|
|
34463
|
+
verdict: "source_bug",
|
|
34464
|
+
reasoning: "implement-only strategy \u2014 skipping diagnosis",
|
|
34465
|
+
confidence: 1
|
|
34466
|
+
};
|
|
34467
|
+
const fixResult = await executeSourceFix(agent, {
|
|
34468
|
+
testOutput: failures.testOutput,
|
|
34469
|
+
testFileContent,
|
|
34470
|
+
diagnosis: defaultDiagnosis,
|
|
34471
|
+
config: ctx.config,
|
|
34472
|
+
workdir: ctx.workdir,
|
|
34473
|
+
featureName: ctx.feature,
|
|
34474
|
+
storyId,
|
|
34475
|
+
acceptanceTestPath
|
|
34476
|
+
});
|
|
34477
|
+
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
34478
|
+
success: fixResult.success,
|
|
34479
|
+
cost: fixResult.cost,
|
|
34480
|
+
attempt: fixAttempts
|
|
34481
|
+
});
|
|
34482
|
+
if (fixResult.success) {
|
|
34483
|
+
return { fixed: true, cost: fixResult.cost, prdDirty: false };
|
|
34484
|
+
}
|
|
34485
|
+
if (fixAttempts >= fixMaxRetries) {
|
|
34486
|
+
logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
|
|
34487
|
+
break;
|
|
34488
|
+
}
|
|
34489
|
+
}
|
|
34490
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34491
|
+
}
|
|
34492
|
+
logger?.info("acceptance", "Strategy is diagnose-first \u2014 running diagnosis");
|
|
34493
|
+
const diagnosis = await diagnoseAcceptanceFailure(agent, {
|
|
34494
|
+
testOutput: failures.testOutput,
|
|
34495
|
+
testFileContent,
|
|
34496
|
+
config: ctx.config,
|
|
34497
|
+
workdir: ctx.workdir,
|
|
34498
|
+
featureName: ctx.feature,
|
|
34499
|
+
storyId
|
|
34500
|
+
});
|
|
34501
|
+
logger?.info("acceptance.diagnosis", "Diagnosis complete", {
|
|
34502
|
+
verdict: diagnosis.verdict,
|
|
34503
|
+
confidence: diagnosis.confidence,
|
|
34504
|
+
reasoning: diagnosis.reasoning
|
|
34505
|
+
});
|
|
34506
|
+
if (diagnosis.verdict === "source_bug") {
|
|
34507
|
+
logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
|
|
34508
|
+
let fixAttempts = 0;
|
|
34509
|
+
while (fixAttempts < fixMaxRetries) {
|
|
34510
|
+
fixAttempts++;
|
|
34511
|
+
logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
|
|
34512
|
+
const fixResult = await executeSourceFix(agent, {
|
|
34513
|
+
testOutput: failures.testOutput,
|
|
34514
|
+
testFileContent,
|
|
34515
|
+
diagnosis,
|
|
34516
|
+
config: ctx.config,
|
|
34517
|
+
workdir: ctx.workdir,
|
|
34518
|
+
featureName: ctx.feature,
|
|
34519
|
+
storyId,
|
|
34520
|
+
acceptanceTestPath
|
|
34521
|
+
});
|
|
34522
|
+
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
34523
|
+
success: fixResult.success,
|
|
34524
|
+
cost: fixResult.cost,
|
|
34525
|
+
attempt: fixAttempts
|
|
34526
|
+
});
|
|
34527
|
+
if (fixResult.success) {
|
|
34528
|
+
return { fixed: true, cost: fixResult.cost, prdDirty: false };
|
|
34529
|
+
}
|
|
34530
|
+
if (fixAttempts >= fixMaxRetries) {
|
|
34531
|
+
logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
|
|
34532
|
+
break;
|
|
34533
|
+
}
|
|
34534
|
+
}
|
|
34535
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34536
|
+
}
|
|
34537
|
+
if (diagnosis.verdict === "test_bug") {
|
|
34538
|
+
logger?.info("acceptance", "Diagnosis: test_bug \u2014 regenerating acceptance test");
|
|
34539
|
+
if (!ctx.featureDir) {
|
|
34540
|
+
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
34541
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34542
|
+
}
|
|
34543
|
+
const testPath = path14.join(ctx.featureDir, "acceptance.test.ts");
|
|
34544
|
+
const testFile = Bun.file(testPath);
|
|
34545
|
+
if (!await testFile.exists()) {
|
|
34546
|
+
logger?.error("acceptance", "Acceptance test file not found for regeneration");
|
|
34547
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34548
|
+
}
|
|
34549
|
+
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
34550
|
+
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
34551
|
+
outcome: regenerated ? "success" : "failure"
|
|
34552
|
+
});
|
|
34553
|
+
if (!regenerated) {
|
|
34554
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34555
|
+
}
|
|
34556
|
+
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance2(), exports_acceptance));
|
|
34557
|
+
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
34558
|
+
if (acceptanceResult.action === "continue") {
|
|
34559
|
+
logger?.info("acceptance", "Acceptance passed after test regeneration");
|
|
34560
|
+
return { fixed: true, cost: 0, prdDirty: true };
|
|
34561
|
+
}
|
|
34562
|
+
logger?.warn("acceptance", "Acceptance still failing after test regeneration");
|
|
34563
|
+
return { fixed: false, cost: 0, prdDirty: true };
|
|
34564
|
+
}
|
|
34565
|
+
if (diagnosis.verdict === "both") {
|
|
34566
|
+
logger?.info("acceptance", "Diagnosis: both \u2014 executing source fix then regenerating test if needed");
|
|
34567
|
+
let sourceFixSuccess = false;
|
|
34568
|
+
let sourceFixCost = 0;
|
|
34569
|
+
let fixAttempts = 0;
|
|
34570
|
+
while (fixAttempts < fixMaxRetries && !sourceFixSuccess) {
|
|
34571
|
+
fixAttempts++;
|
|
34572
|
+
logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
|
|
34573
|
+
const fixResult = await executeSourceFix(agent, {
|
|
34574
|
+
testOutput: failures.testOutput,
|
|
34575
|
+
testFileContent,
|
|
34576
|
+
diagnosis,
|
|
34577
|
+
config: ctx.config,
|
|
34578
|
+
workdir: ctx.workdir,
|
|
34579
|
+
featureName: ctx.feature,
|
|
34580
|
+
storyId,
|
|
34581
|
+
acceptanceTestPath
|
|
34582
|
+
});
|
|
34583
|
+
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
34584
|
+
success: fixResult.success,
|
|
34585
|
+
cost: fixResult.cost,
|
|
34586
|
+
attempt: fixAttempts
|
|
34587
|
+
});
|
|
34588
|
+
sourceFixSuccess = fixResult.success;
|
|
34589
|
+
sourceFixCost += fixResult.cost;
|
|
34590
|
+
if (fixResult.success) {
|
|
34591
|
+
break;
|
|
34592
|
+
}
|
|
34593
|
+
if (fixAttempts >= fixMaxRetries) {
|
|
34594
|
+
logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
|
|
34595
|
+
break;
|
|
34596
|
+
}
|
|
34597
|
+
}
|
|
34598
|
+
if (!sourceFixSuccess) {
|
|
34599
|
+
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
34600
|
+
}
|
|
34601
|
+
logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
|
|
34602
|
+
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance2(), exports_acceptance));
|
|
34603
|
+
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
34604
|
+
if (acceptanceResult.action === "continue") {
|
|
34605
|
+
logger?.info("acceptance", "Acceptance passed after source fix");
|
|
34606
|
+
return { fixed: true, cost: sourceFixCost, prdDirty: false };
|
|
34607
|
+
}
|
|
34608
|
+
logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
|
|
34609
|
+
if (!ctx.featureDir) {
|
|
34610
|
+
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
34611
|
+
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
34612
|
+
}
|
|
34613
|
+
const testPath = path14.join(ctx.featureDir, "acceptance.test.ts");
|
|
34614
|
+
const testFile = Bun.file(testPath);
|
|
34615
|
+
if (!await testFile.exists()) {
|
|
34616
|
+
logger?.error("acceptance", "Acceptance test file not found for regeneration");
|
|
34617
|
+
return { fixed: false, cost: sourceFixCost, prdDirty: false };
|
|
34618
|
+
}
|
|
34619
|
+
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
34620
|
+
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
34621
|
+
outcome: regenerated ? "success" : "failure"
|
|
34622
|
+
});
|
|
34623
|
+
return { fixed: regenerated, cost: sourceFixCost, prdDirty: regenerated };
|
|
34624
|
+
}
|
|
34625
|
+
return { fixed: false, cost: 0, prdDirty: false };
|
|
34626
|
+
}
|
|
34201
34627
|
async function runAcceptanceLoop(ctx) {
|
|
34202
34628
|
const logger = getSafeLogger();
|
|
34203
34629
|
const maxRetries = ctx.config.acceptance.maxRetries;
|
|
@@ -34292,6 +34718,23 @@ async function runAcceptanceLoop(ctx) {
|
|
|
34292
34718
|
continue;
|
|
34293
34719
|
}
|
|
34294
34720
|
}
|
|
34721
|
+
const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
|
|
34722
|
+
if (strategy === "diagnose-first" || strategy === "implement-only") {
|
|
34723
|
+
logger?.info("acceptance", `Running fix routing with strategy: ${strategy}`);
|
|
34724
|
+
const fixResult = await runFixRouting({
|
|
34725
|
+
ctx,
|
|
34726
|
+
failures,
|
|
34727
|
+
prd,
|
|
34728
|
+
acceptanceContext
|
|
34729
|
+
});
|
|
34730
|
+
totalCost += fixResult.cost;
|
|
34731
|
+
if (fixResult.fixed) {
|
|
34732
|
+
logger?.info("acceptance", "Fix succeeded \u2014 re-running acceptance tests...");
|
|
34733
|
+
continue;
|
|
34734
|
+
}
|
|
34735
|
+
logger?.error("acceptance", "Fix routing failed to resolve acceptance failures");
|
|
34736
|
+
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
34737
|
+
}
|
|
34295
34738
|
logger?.info("acceptance", "Generating fix stories...");
|
|
34296
34739
|
const fixStories = await generateAndAddFixStories(ctx, failures, prd);
|
|
34297
34740
|
if (!fixStories) {
|
|
@@ -34323,6 +34766,8 @@ async function runAcceptanceLoop(ctx) {
|
|
|
34323
34766
|
var _acceptanceLoopDeps;
|
|
34324
34767
|
var init_acceptance_loop = __esm(() => {
|
|
34325
34768
|
init_acceptance();
|
|
34769
|
+
init_fix_diagnosis();
|
|
34770
|
+
init_fix_executor();
|
|
34326
34771
|
init_registry();
|
|
34327
34772
|
init_config();
|
|
34328
34773
|
init_loader();
|
|
@@ -69944,7 +70389,8 @@ async function planCommand(workdir, config2, options) {
|
|
|
69944
70389
|
workdir,
|
|
69945
70390
|
config: config2,
|
|
69946
70391
|
featureName: options.feature,
|
|
69947
|
-
sessionRole: "plan"
|
|
70392
|
+
sessionRole: "plan",
|
|
70393
|
+
timeoutMs: (config2?.execution?.sessionTimeoutSeconds ?? 3600) * 1000
|
|
69948
70394
|
});
|
|
69949
70395
|
let result = typeof completeResult === "string" ? completeResult : completeResult.output;
|
|
69950
70396
|
try {
|
|
@@ -70337,7 +70783,8 @@ async function planDecomposeCommand(workdir, config2, options) {
|
|
|
70337
70783
|
workdir,
|
|
70338
70784
|
sessionRole: "decompose",
|
|
70339
70785
|
featureName: options.feature,
|
|
70340
|
-
storyId: options.storyId
|
|
70786
|
+
storyId: options.storyId,
|
|
70787
|
+
timeoutMs: (config2?.execution?.sessionTimeoutSeconds ?? 3600) * 1000
|
|
70341
70788
|
});
|
|
70342
70789
|
rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
|
|
70343
70790
|
}
|
|
@@ -70347,7 +70794,8 @@ async function planDecomposeCommand(workdir, config2, options) {
|
|
|
70347
70794
|
workdir,
|
|
70348
70795
|
sessionRole: "decompose",
|
|
70349
70796
|
featureName: options.feature,
|
|
70350
|
-
storyId: options.storyId
|
|
70797
|
+
storyId: options.storyId,
|
|
70798
|
+
timeoutMs: (config2?.execution?.sessionTimeoutSeconds ?? 3600) * 1000
|
|
70351
70799
|
});
|
|
70352
70800
|
rawResponse = typeof completeResult === "string" ? completeResult : completeResult.output;
|
|
70353
70801
|
}
|