@nathapp/nax 0.60.0 → 0.60.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/dist/nax.js +357 -733
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -18654,7 +18654,7 @@ var init_schemas3 = __esm(() => {
|
|
|
18654
18654
|
}),
|
|
18655
18655
|
acceptance: AcceptanceConfigSchema.default({
|
|
18656
18656
|
enabled: true,
|
|
18657
|
-
maxRetries:
|
|
18657
|
+
maxRetries: 3,
|
|
18658
18658
|
generateTests: true,
|
|
18659
18659
|
testPath: ".nax-acceptance.test.ts",
|
|
18660
18660
|
model: "fast",
|
|
@@ -27053,7 +27053,8 @@ ${stderr}` };
|
|
|
27053
27053
|
testStrategy: ctx.config.acceptance.testStrategy,
|
|
27054
27054
|
testFramework: ctx.config.acceptance.testFramework,
|
|
27055
27055
|
adapter: agent ?? undefined,
|
|
27056
|
-
..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {}
|
|
27056
|
+
..."implementationContext" in ctx && ctx.implementationContext ? { implementationContext: ctx.implementationContext } : {},
|
|
27057
|
+
..."previousFailure" in ctx && ctx.previousFailure ? { previousFailure: ctx.previousFailure } : {}
|
|
27057
27058
|
});
|
|
27058
27059
|
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
27059
27060
|
}
|
|
@@ -36571,7 +36572,7 @@ var package_default;
|
|
|
36571
36572
|
var init_package = __esm(() => {
|
|
36572
36573
|
package_default = {
|
|
36573
36574
|
name: "@nathapp/nax",
|
|
36574
|
-
version: "0.60.
|
|
36575
|
+
version: "0.60.1",
|
|
36575
36576
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
36576
36577
|
type: "module",
|
|
36577
36578
|
bin: {
|
|
@@ -36651,8 +36652,8 @@ var init_version = __esm(() => {
|
|
|
36651
36652
|
NAX_VERSION = package_default.version;
|
|
36652
36653
|
NAX_COMMIT = (() => {
|
|
36653
36654
|
try {
|
|
36654
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
36655
|
-
return "
|
|
36655
|
+
if (/^[0-9a-f]{6,10}$/.test("2b74a9ef"))
|
|
36656
|
+
return "2b74a9ef";
|
|
36656
36657
|
} catch {}
|
|
36657
36658
|
try {
|
|
36658
36659
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -36968,196 +36969,6 @@ var init_crash_recovery = __esm(() => {
|
|
|
36968
36969
|
init_crash_heartbeat();
|
|
36969
36970
|
});
|
|
36970
36971
|
|
|
36971
|
-
// src/acceptance/fix-generator.ts
|
|
36972
|
-
function findRelatedStories(failedAC, prd) {
|
|
36973
|
-
const relatedStoryIds = [];
|
|
36974
|
-
for (const story of prd.userStories) {
|
|
36975
|
-
for (const ac of story.acceptanceCriteria) {
|
|
36976
|
-
if (ac.includes(failedAC)) {
|
|
36977
|
-
relatedStoryIds.push(story.id);
|
|
36978
|
-
break;
|
|
36979
|
-
}
|
|
36980
|
-
}
|
|
36981
|
-
}
|
|
36982
|
-
if (relatedStoryIds.length > 0) {
|
|
36983
|
-
return relatedStoryIds;
|
|
36984
|
-
}
|
|
36985
|
-
const passedStories = prd.userStories.filter((s) => s.status === "passed").map((s) => s.id);
|
|
36986
|
-
return passedStories.slice(0, 5);
|
|
36987
|
-
}
|
|
36988
|
-
function groupACsByRelatedStories(failedACs, prd) {
|
|
36989
|
-
const groups = new Map;
|
|
36990
|
-
for (const ac of failedACs) {
|
|
36991
|
-
const related = findRelatedStories(ac, prd);
|
|
36992
|
-
const key = [...related].sort().join(",");
|
|
36993
|
-
if (!groups.has(key)) {
|
|
36994
|
-
groups.set(key, { acs: [], relatedStories: related });
|
|
36995
|
-
}
|
|
36996
|
-
groups.get(key)?.acs.push(ac);
|
|
36997
|
-
}
|
|
36998
|
-
const result = Array.from(groups.values());
|
|
36999
|
-
while (result.length > MAX_FIX_STORIES) {
|
|
37000
|
-
result.sort((a, b) => a.acs.length - b.acs.length);
|
|
37001
|
-
const smallest = result.shift();
|
|
37002
|
-
if (!smallest)
|
|
37003
|
-
break;
|
|
37004
|
-
result[0].acs.push(...smallest.acs);
|
|
37005
|
-
for (const s of smallest.relatedStories) {
|
|
37006
|
-
if (!result[0].relatedStories.includes(s)) {
|
|
37007
|
-
result[0].relatedStories.push(s);
|
|
37008
|
-
}
|
|
37009
|
-
}
|
|
37010
|
-
}
|
|
37011
|
-
return result;
|
|
37012
|
-
}
|
|
37013
|
-
function buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath) {
|
|
37014
|
-
const acList = batchedACs.map((ac) => `${ac}: ${acTextMap[ac] || "No description available"}`).join(`
|
|
37015
|
-
`);
|
|
37016
|
-
const relatedStoriesText = relatedStories.map((id) => {
|
|
37017
|
-
const story = prd.userStories.find((s) => s.id === id);
|
|
37018
|
-
if (!story)
|
|
37019
|
-
return "";
|
|
37020
|
-
return `${story.id}: ${story.title}
|
|
37021
|
-
${story.description}`;
|
|
37022
|
-
}).filter(Boolean).join(`
|
|
37023
|
-
|
|
37024
|
-
`);
|
|
37025
|
-
const testFileSection = testFilePath ? `
|
|
37026
|
-
ACCEPTANCE TEST FILE: ${testFilePath}
|
|
37027
|
-
(Read this file first to understand what each test expects)
|
|
37028
|
-
` : "";
|
|
37029
|
-
return `You are a debugging expert. Feature acceptance tests have failed.${testFileSection}
|
|
37030
|
-
FAILED ACCEPTANCE CRITERIA (${batchedACs.length} total):
|
|
37031
|
-
${acList}
|
|
37032
|
-
|
|
37033
|
-
TEST FAILURE OUTPUT:
|
|
37034
|
-
${testOutput.slice(0, 2000)}
|
|
37035
|
-
|
|
37036
|
-
RELATED STORIES (implemented this functionality):
|
|
37037
|
-
${relatedStoriesText}
|
|
37038
|
-
|
|
37039
|
-
Your task: Generate a fix description that will make these acceptance tests pass.
|
|
37040
|
-
|
|
37041
|
-
Requirements:
|
|
37042
|
-
1. Read the acceptance test file first to understand what each failing test expects
|
|
37043
|
-
2. Identify the root cause based on the test failure output
|
|
37044
|
-
3. Find and fix the relevant implementation code (do NOT modify the test file)
|
|
37045
|
-
4. Write a clear, actionable fix description (2-4 sentences)
|
|
37046
|
-
5. Reference the relevant story IDs if needed
|
|
37047
|
-
|
|
37048
|
-
Respond with ONLY the fix description (no JSON, no markdown, just the description text).`;
|
|
37049
|
-
}
|
|
37050
|
-
async function generateFixStories(adapter, options) {
|
|
37051
|
-
const { failedACs, testOutput, prd, specContent, modelDef, testFilePath } = options;
|
|
37052
|
-
const logger = getLogger();
|
|
37053
|
-
const acTextMap = parseACTextFromSpec(specContent);
|
|
37054
|
-
const groups = groupACsByRelatedStories(failedACs, prd);
|
|
37055
|
-
const fixStories = [];
|
|
37056
|
-
for (let i = 0;i < groups.length; i++) {
|
|
37057
|
-
const { acs: batchedACs, relatedStories } = groups[i];
|
|
37058
|
-
if (relatedStories.length === 0) {
|
|
37059
|
-
logger.warn("acceptance", "[WARN] No related stories found for AC group \u2014 skipping", { batchedACs });
|
|
37060
|
-
continue;
|
|
37061
|
-
}
|
|
37062
|
-
logger.info("acceptance", "Generating fix for AC group", { batchedACs });
|
|
37063
|
-
const prompt = buildFixPrompt(batchedACs, acTextMap, testOutput, relatedStories, prd, testFilePath);
|
|
37064
|
-
const relatedStory = prd.userStories.find((s) => relatedStories.includes(s.id) && s.workdir);
|
|
37065
|
-
const workdir = relatedStory?.workdir;
|
|
37066
|
-
try {
|
|
37067
|
-
const fixResult = await adapter.complete(prompt, {
|
|
37068
|
-
model: modelDef.model,
|
|
37069
|
-
config: options.config,
|
|
37070
|
-
featureName: options.prd.feature,
|
|
37071
|
-
workdir: options.workdir,
|
|
37072
|
-
sessionRole: "fix-gen",
|
|
37073
|
-
timeoutMs: options.timeoutMs ?? options.config?.acceptance?.timeoutMs ?? 1800000
|
|
37074
|
-
});
|
|
37075
|
-
fixStories.push({
|
|
37076
|
-
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
37077
|
-
title: `Fix: ${batchedACs.join(", ")} \u2014 ${(acTextMap[batchedACs[0]] || "").slice(0, 40)}`,
|
|
37078
|
-
failedAC: batchedACs[0],
|
|
37079
|
-
batchedACs,
|
|
37080
|
-
testOutput,
|
|
37081
|
-
relatedStories,
|
|
37082
|
-
description: typeof fixResult === "string" ? fixResult : fixResult.output,
|
|
37083
|
-
testFilePath,
|
|
37084
|
-
workdir
|
|
37085
|
-
});
|
|
37086
|
-
logger.info("acceptance", "[OK] Generated fix story", { storyId: fixStories[fixStories.length - 1].id });
|
|
37087
|
-
} catch (error48) {
|
|
37088
|
-
logger.warn("acceptance", "[WARN] Error generating fix", {
|
|
37089
|
-
batchedACs,
|
|
37090
|
-
error: error48.message
|
|
37091
|
-
});
|
|
37092
|
-
fixStories.push({
|
|
37093
|
-
id: `US-FIX-${String(i + 1).padStart(3, "0")}`,
|
|
37094
|
-
title: `Fix: ${batchedACs.join(", ")}`,
|
|
37095
|
-
failedAC: batchedACs[0],
|
|
37096
|
-
batchedACs,
|
|
37097
|
-
testOutput,
|
|
37098
|
-
relatedStories,
|
|
37099
|
-
description: `Fix the implementation to make ${batchedACs.join(", ")} pass. Related stories: ${relatedStories.join(", ")}.`,
|
|
37100
|
-
testFilePath,
|
|
37101
|
-
workdir
|
|
37102
|
-
});
|
|
37103
|
-
}
|
|
37104
|
-
}
|
|
37105
|
-
return fixStories;
|
|
37106
|
-
}
|
|
37107
|
-
function parseACTextFromSpec(specContent) {
|
|
37108
|
-
const map2 = {};
|
|
37109
|
-
const lines = specContent.split(`
|
|
37110
|
-
`);
|
|
37111
|
-
for (const line of lines) {
|
|
37112
|
-
const acMatch = line.match(/^\s*-?\s*(?:\[.\])?\s*(AC-\d+):\s*(.+)$/i);
|
|
37113
|
-
if (acMatch) {
|
|
37114
|
-
const id = acMatch[1].toUpperCase();
|
|
37115
|
-
const text = acMatch[2].trim();
|
|
37116
|
-
map2[id] = text;
|
|
37117
|
-
}
|
|
37118
|
-
}
|
|
37119
|
-
return map2;
|
|
37120
|
-
}
|
|
37121
|
-
function convertFixStoryToUserStory(fixStory) {
|
|
37122
|
-
const batchedACs = fixStory.batchedACs ?? [fixStory.failedAC];
|
|
37123
|
-
const acList = batchedACs.join(", ");
|
|
37124
|
-
const truncatedOutput = fixStory.testOutput.slice(0, 1000);
|
|
37125
|
-
const testFilePath = fixStory.testFilePath ?? resolveAcceptanceTestFile();
|
|
37126
|
-
const enrichedDescription = [
|
|
37127
|
-
fixStory.description,
|
|
37128
|
-
"",
|
|
37129
|
-
`ACCEPTANCE TEST FILE: ${testFilePath}`,
|
|
37130
|
-
`FAILED ACCEPTANCE CRITERIA: ${acList}`,
|
|
37131
|
-
"",
|
|
37132
|
-
"TEST FAILURE OUTPUT:",
|
|
37133
|
-
truncatedOutput,
|
|
37134
|
-
"",
|
|
37135
|
-
"Instructions: Read the acceptance test file first to understand what each failing test expects.",
|
|
37136
|
-
"Then find the relevant source code and fix the implementation.",
|
|
37137
|
-
"Do NOT modify the test file."
|
|
37138
|
-
].join(`
|
|
37139
|
-
`);
|
|
37140
|
-
return {
|
|
37141
|
-
id: fixStory.id,
|
|
37142
|
-
title: fixStory.title,
|
|
37143
|
-
description: enrichedDescription,
|
|
37144
|
-
acceptanceCriteria: batchedACs.map((ac) => `Fix ${ac}`),
|
|
37145
|
-
tags: ["fix", "acceptance-failure"],
|
|
37146
|
-
dependencies: fixStory.relatedStories,
|
|
37147
|
-
status: "pending",
|
|
37148
|
-
passes: false,
|
|
37149
|
-
escalations: [],
|
|
37150
|
-
attempts: 0,
|
|
37151
|
-
contextFiles: [],
|
|
37152
|
-
workdir: fixStory.workdir
|
|
37153
|
-
};
|
|
37154
|
-
}
|
|
37155
|
-
var MAX_FIX_STORIES = 8;
|
|
37156
|
-
var init_fix_generator = __esm(() => {
|
|
37157
|
-
init_logger2();
|
|
37158
|
-
init_test_path();
|
|
37159
|
-
});
|
|
37160
|
-
|
|
37161
36972
|
// src/acceptance/content-loader.ts
|
|
37162
36973
|
async function loadAcceptanceTestContent(pathsOrFallback) {
|
|
37163
36974
|
if (!pathsOrFallback)
|
|
@@ -37179,13 +36990,6 @@ async function loadAcceptanceTestContent(pathsOrFallback) {
|
|
|
37179
36990
|
return [];
|
|
37180
36991
|
}
|
|
37181
36992
|
|
|
37182
|
-
// src/acceptance/index.ts
|
|
37183
|
-
var init_acceptance4 = __esm(() => {
|
|
37184
|
-
init_refinement();
|
|
37185
|
-
init_generator();
|
|
37186
|
-
init_fix_generator();
|
|
37187
|
-
});
|
|
37188
|
-
|
|
37189
36993
|
// src/acceptance/fix-diagnosis.ts
|
|
37190
36994
|
function parseImportStatements(content) {
|
|
37191
36995
|
const importRegex = /import\s+(?:{[^}]+}|[^;]+)\s+from\s+["']([^"']+)["']/g;
|
|
@@ -37235,6 +37039,13 @@ ${f.content}
|
|
|
37235
37039
|
SEMANTIC VERDICTS:
|
|
37236
37040
|
${lines.join(`
|
|
37237
37041
|
`)}
|
|
37042
|
+
`;
|
|
37043
|
+
}
|
|
37044
|
+
let previousFailureSection = "";
|
|
37045
|
+
if (options.previousFailure && options.previousFailure.length > 0) {
|
|
37046
|
+
previousFailureSection = `
|
|
37047
|
+
PREVIOUS FIX ATTEMPTS:
|
|
37048
|
+
${options.previousFailure}
|
|
37238
37049
|
`;
|
|
37239
37050
|
}
|
|
37240
37051
|
return `You are a debugging expert. An acceptance test has failed.
|
|
@@ -37251,7 +37062,7 @@ ${options.testFileContent}
|
|
|
37251
37062
|
|
|
37252
37063
|
SOURCE FILES (auto-detected from imports, up to ${MAX_FILE_LINES} lines each):
|
|
37253
37064
|
${sourceFilesSection}
|
|
37254
|
-
${verdictSection}
|
|
37065
|
+
${verdictSection}${previousFailureSection}
|
|
37255
37066
|
Respond with ONLY a JSON object in this exact format (no markdown, no extra text):
|
|
37256
37067
|
{
|
|
37257
37068
|
"verdict": "source_bug" | "test_bug" | "both",
|
|
@@ -37277,7 +37088,8 @@ async function diagnoseAcceptanceFailure(agent, options) {
|
|
|
37277
37088
|
testOutput,
|
|
37278
37089
|
testFileContent,
|
|
37279
37090
|
sourceFiles: validSourceFiles,
|
|
37280
|
-
semanticVerdicts: options.semanticVerdicts
|
|
37091
|
+
semanticVerdicts: options.semanticVerdicts,
|
|
37092
|
+
previousFailure: options.previousFailure
|
|
37281
37093
|
});
|
|
37282
37094
|
try {
|
|
37283
37095
|
const timeoutSeconds = (config2.acceptance?.timeoutMs ?? 120000) / 1000;
|
|
@@ -37362,7 +37174,7 @@ async function executeSourceFix(agent, options) {
|
|
|
37362
37174
|
if (!agent) {
|
|
37363
37175
|
throw new Error("[fix-executor] agent is required");
|
|
37364
37176
|
}
|
|
37365
|
-
const {
|
|
37177
|
+
const { config: config2, workdir, featureName, storyId } = options;
|
|
37366
37178
|
const modelDef = resolveModelForAgent(config2.models, config2.autoMode.defaultAgent, config2.acceptance.fix.fixModel, config2.autoMode.defaultAgent);
|
|
37367
37179
|
const sessionName = buildSessionName(workdir, featureName, storyId, "source-fix");
|
|
37368
37180
|
const prompt = buildSourceFixPrompt(options);
|
|
@@ -37386,23 +37198,77 @@ async function executeSourceFix(agent, options) {
|
|
|
37386
37198
|
cost: result.estimatedCost
|
|
37387
37199
|
};
|
|
37388
37200
|
}
|
|
37201
|
+
function buildTestFixPrompt(options) {
|
|
37202
|
+
const { testOutput, diagnosis, acceptanceTestPath, testFileContent, failedACs, previousFailure } = options;
|
|
37203
|
+
let prompt = `ACCEPTANCE TEST BUG \u2014 surgical fix required.
|
|
37204
|
+
|
|
37205
|
+
`;
|
|
37206
|
+
prompt += `FAILING ACS: ${failedACs.join(", ")}
|
|
37207
|
+
|
|
37208
|
+
`;
|
|
37209
|
+
prompt += `TEST OUTPUT:
|
|
37210
|
+
${testOutput}
|
|
37211
|
+
|
|
37212
|
+
`;
|
|
37213
|
+
if (diagnosis.reasoning) {
|
|
37214
|
+
prompt += `DIAGNOSIS:
|
|
37215
|
+
${diagnosis.reasoning}
|
|
37216
|
+
|
|
37217
|
+
`;
|
|
37218
|
+
}
|
|
37219
|
+
if (previousFailure && previousFailure.length > 0) {
|
|
37220
|
+
prompt += `PREVIOUS FAILED ATTEMPTS:
|
|
37221
|
+
${previousFailure}
|
|
37222
|
+
|
|
37223
|
+
`;
|
|
37224
|
+
}
|
|
37225
|
+
prompt += `ACCEPTANCE TEST FILE: ${acceptanceTestPath}
|
|
37226
|
+
|
|
37227
|
+
`;
|
|
37228
|
+
prompt += `\`\`\`typescript
|
|
37229
|
+
${testFileContent}
|
|
37230
|
+
\`\`\`
|
|
37231
|
+
|
|
37232
|
+
`;
|
|
37233
|
+
prompt += "Fix ONLY the failing test assertions for the ACs listed above. ";
|
|
37234
|
+
prompt += "Do NOT modify passing tests. Do NOT modify source code. ";
|
|
37235
|
+
prompt += "Edit the test file in place.";
|
|
37236
|
+
return prompt;
|
|
37237
|
+
}
|
|
37238
|
+
async function executeTestFix(agent, options) {
|
|
37239
|
+
if (!agent) {
|
|
37240
|
+
throw new Error("[fix-executor] agent is required");
|
|
37241
|
+
}
|
|
37242
|
+
const { config: config2, workdir, featureName, storyId } = options;
|
|
37243
|
+
const modelDef = resolveModelForAgent(config2.models, config2.autoMode.defaultAgent, config2.acceptance.fix.fixModel, config2.autoMode.defaultAgent);
|
|
37244
|
+
const sessionName = buildSessionName(workdir, featureName, storyId, "test-fix");
|
|
37245
|
+
const prompt = buildTestFixPrompt(options);
|
|
37246
|
+
const timeoutSeconds = config2.execution?.sessionTimeoutSeconds ?? 3600;
|
|
37247
|
+
const runOptions = {
|
|
37248
|
+
prompt,
|
|
37249
|
+
workdir,
|
|
37250
|
+
modelTier: undefined,
|
|
37251
|
+
modelDef,
|
|
37252
|
+
timeoutSeconds,
|
|
37253
|
+
sessionRole: "test-fix",
|
|
37254
|
+
acpSessionName: sessionName,
|
|
37255
|
+
featureName,
|
|
37256
|
+
storyId,
|
|
37257
|
+
config: config2,
|
|
37258
|
+
pipelineStage: "acceptance"
|
|
37259
|
+
};
|
|
37260
|
+
const result = await agent.run(runOptions);
|
|
37261
|
+
return {
|
|
37262
|
+
success: result.success,
|
|
37263
|
+
cost: result.estimatedCost
|
|
37264
|
+
};
|
|
37265
|
+
}
|
|
37389
37266
|
var init_fix_executor = __esm(() => {
|
|
37390
37267
|
init_adapter();
|
|
37391
37268
|
});
|
|
37392
37269
|
|
|
37393
|
-
// src/execution/lifecycle/acceptance-
|
|
37394
|
-
|
|
37395
|
-
__export(exports_acceptance_loop, {
|
|
37396
|
-
runFixRouting: () => runFixRouting,
|
|
37397
|
-
runAcceptanceLoop: () => runAcceptanceLoop,
|
|
37398
|
-
regenerateAcceptanceTest: () => regenerateAcceptanceTest,
|
|
37399
|
-
loadAcceptanceTestContent: () => loadAcceptanceTestContent2,
|
|
37400
|
-
isTestLevelFailure: () => isTestLevelFailure,
|
|
37401
|
-
isStubTestFile: () => isStubTestFile,
|
|
37402
|
-
_regenerateDeps: () => _regenerateDeps,
|
|
37403
|
-
_acceptanceLoopDeps: () => _acceptanceLoopDeps
|
|
37404
|
-
});
|
|
37405
|
-
import path15, { join as join43 } from "path";
|
|
37270
|
+
// src/execution/lifecycle/acceptance-helpers.ts
|
|
37271
|
+
import path15 from "path";
|
|
37406
37272
|
function isStubTestFile(content) {
|
|
37407
37273
|
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
37408
37274
|
}
|
|
@@ -37449,73 +37315,7 @@ async function loadAcceptanceTestContent2(featureDir, testPaths, configuredTestP
|
|
|
37449
37315
|
function buildResult(success2, prd, totalCost, iterations, storiesCompleted, prdDirty, failedACs, retries) {
|
|
37450
37316
|
return { success: success2, prd, totalCost, iterations, storiesCompleted, prdDirty, failedACs, retries };
|
|
37451
37317
|
}
|
|
37452
|
-
async function
|
|
37453
|
-
const logger = getSafeLogger();
|
|
37454
|
-
const agent = (ctx.agentGetFn ?? _acceptanceLoopDeps.getAgent)(ctx.config.autoMode.defaultAgent);
|
|
37455
|
-
if (!agent) {
|
|
37456
|
-
logger?.error("acceptance", "Agent not found, cannot generate fix stories");
|
|
37457
|
-
return null;
|
|
37458
|
-
}
|
|
37459
|
-
const modelDef = resolveModelForAgent(ctx.config.models, ctx.config.autoMode.defaultAgent, ctx.config.analyze.model, ctx.config.autoMode.defaultAgent);
|
|
37460
|
-
const testFilePath = ctx.featureDir ? resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language) : undefined;
|
|
37461
|
-
const fixStories = await generateFixStories(agent, {
|
|
37462
|
-
failedACs: failures.failedACs,
|
|
37463
|
-
testOutput: failures.testOutput,
|
|
37464
|
-
prd,
|
|
37465
|
-
specContent: await loadSpecContent(ctx.featureDir),
|
|
37466
|
-
workdir: ctx.workdir,
|
|
37467
|
-
modelDef,
|
|
37468
|
-
config: ctx.config,
|
|
37469
|
-
testFilePath,
|
|
37470
|
-
timeoutMs: ctx.config.acceptance?.timeoutMs
|
|
37471
|
-
});
|
|
37472
|
-
if (fixStories.length === 0) {
|
|
37473
|
-
logger?.error("acceptance", "Failed to generate fix stories");
|
|
37474
|
-
return null;
|
|
37475
|
-
}
|
|
37476
|
-
logger?.info("acceptance", `Generated ${fixStories.length} fix stories`);
|
|
37477
|
-
for (const fixStory of fixStories) {
|
|
37478
|
-
const userStory = convertFixStoryToUserStory(fixStory);
|
|
37479
|
-
prd.userStories.push(userStory);
|
|
37480
|
-
logger?.debug("acceptance", `Fix story added: ${userStory.id}: ${userStory.title}`);
|
|
37481
|
-
}
|
|
37482
|
-
return fixStories;
|
|
37483
|
-
}
|
|
37484
|
-
async function executeFixStory(ctx, story, prd, iterations) {
|
|
37485
|
-
const logger = getSafeLogger();
|
|
37486
|
-
const routing = await resolveRouting(story, ctx.config, ctx.pluginRegistry);
|
|
37487
|
-
logger?.info("acceptance", `Starting fix story: ${story.id}`, { storyId: story.id, storyTitle: story.title });
|
|
37488
|
-
await fireHook(ctx.hooks, "on-story-start", hookCtx(ctx.feature, {
|
|
37489
|
-
storyId: story.id,
|
|
37490
|
-
model: routing.modelTier,
|
|
37491
|
-
agent: ctx.config.autoMode.defaultAgent,
|
|
37492
|
-
iteration: iterations
|
|
37493
|
-
}), ctx.workdir);
|
|
37494
|
-
const fixEffectiveConfig = story.workdir ? await loadConfigForWorkdir(join43(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
37495
|
-
const fixContext = {
|
|
37496
|
-
config: fixEffectiveConfig,
|
|
37497
|
-
rootConfig: ctx.config,
|
|
37498
|
-
prd,
|
|
37499
|
-
story,
|
|
37500
|
-
stories: [story],
|
|
37501
|
-
routing,
|
|
37502
|
-
projectDir: ctx.workdir,
|
|
37503
|
-
workdir: story.workdir ? join43(ctx.workdir, story.workdir) : ctx.workdir,
|
|
37504
|
-
featureDir: ctx.featureDir,
|
|
37505
|
-
hooks: ctx.hooks,
|
|
37506
|
-
plugins: ctx.pluginRegistry,
|
|
37507
|
-
storyStartTime: new Date().toISOString(),
|
|
37508
|
-
agentGetFn: ctx.agentGetFn
|
|
37509
|
-
};
|
|
37510
|
-
const result = await runPipeline(defaultPipeline, fixContext, ctx.eventEmitter);
|
|
37511
|
-
logger?.info("acceptance", `Fix story ${story.id} ${result.success ? "passed" : "failed"}`);
|
|
37512
|
-
return {
|
|
37513
|
-
success: result.success,
|
|
37514
|
-
cost: result.context.agentResult?.estimatedCost || 0,
|
|
37515
|
-
metrics: result.context.storyMetrics
|
|
37516
|
-
};
|
|
37517
|
-
}
|
|
37518
|
-
async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
37318
|
+
async function regenerateAcceptanceTest(testPath, acceptanceContext, previousFailure) {
|
|
37519
37319
|
const logger = getSafeLogger();
|
|
37520
37320
|
const bakPath = `${testPath}.bak`;
|
|
37521
37321
|
const content = await Bun.file(testPath).text();
|
|
@@ -37559,7 +37359,8 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
37559
37359
|
}
|
|
37560
37360
|
const contextForSetup = {
|
|
37561
37361
|
...acceptanceContext,
|
|
37562
|
-
...implementationContext ? { implementationContext } : {}
|
|
37362
|
+
...implementationContext ? { implementationContext } : {},
|
|
37363
|
+
...previousFailure ? { previousFailure } : {}
|
|
37563
37364
|
};
|
|
37564
37365
|
await _regenerateDeps.acceptanceSetupExecute(contextForSetup);
|
|
37565
37366
|
if (!await Bun.file(testPath).exists()) {
|
|
@@ -37569,300 +37370,179 @@ async function regenerateAcceptanceTest(testPath, acceptanceContext) {
|
|
|
37569
37370
|
logger?.info("acceptance", "Acceptance test regenerated successfully");
|
|
37570
37371
|
return true;
|
|
37571
37372
|
}
|
|
37572
|
-
|
|
37373
|
+
var _regenerateDeps;
|
|
37374
|
+
var init_acceptance_helpers = __esm(() => {
|
|
37375
|
+
init_logger2();
|
|
37376
|
+
_regenerateDeps = {
|
|
37377
|
+
spawnGitDiff: async (workdir, gitRef) => {
|
|
37378
|
+
const proc = Bun.spawn(["git", "diff", "--name-only", gitRef], {
|
|
37379
|
+
cwd: workdir,
|
|
37380
|
+
stdout: "pipe",
|
|
37381
|
+
stderr: "pipe"
|
|
37382
|
+
});
|
|
37383
|
+
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
37384
|
+
return stdout.trim();
|
|
37385
|
+
},
|
|
37386
|
+
readFile: async (filePath) => Bun.file(filePath).text(),
|
|
37387
|
+
acceptanceSetupExecute: async (ctx) => {
|
|
37388
|
+
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
37389
|
+
await acceptanceSetupStage2.execute(ctx);
|
|
37390
|
+
}
|
|
37391
|
+
};
|
|
37392
|
+
});
|
|
37393
|
+
|
|
37394
|
+
// src/execution/lifecycle/acceptance-fix.ts
|
|
37395
|
+
async function resolveAcceptanceDiagnosis(opts) {
|
|
37573
37396
|
const logger = getSafeLogger();
|
|
37574
|
-
const {
|
|
37575
|
-
const
|
|
37576
|
-
|
|
37577
|
-
|
|
37578
|
-
|
|
37579
|
-
|
|
37580
|
-
|
|
37581
|
-
|
|
37582
|
-
|
|
37397
|
+
const { agent, failures, totalACs, strategy, semanticVerdicts, diagnosisOpts, previousFailure } = opts;
|
|
37398
|
+
const storyId = diagnosisOpts.storyId;
|
|
37399
|
+
if (strategy === "implement-only") {
|
|
37400
|
+
logger?.info("acceptance.diagnosis", "Fast path: implement-only strategy \u2192 source_bug", { storyId });
|
|
37401
|
+
return {
|
|
37402
|
+
verdict: "source_bug",
|
|
37403
|
+
reasoning: "implement-only strategy \u2014 skipping diagnosis",
|
|
37404
|
+
confidence: 1
|
|
37405
|
+
};
|
|
37406
|
+
}
|
|
37407
|
+
if (semanticVerdicts.length > 0 && semanticVerdicts.every((v) => v.passed)) {
|
|
37408
|
+
logger?.info("acceptance.diagnosis", "Fast path: all semantic verdicts passed \u2192 test_bug", {
|
|
37409
|
+
storyId,
|
|
37410
|
+
verdictCount: semanticVerdicts.length
|
|
37583
37411
|
});
|
|
37584
|
-
if (!ctx.featureDir || !acceptanceContext) {
|
|
37585
|
-
logger?.warn("acceptance", "Cannot regenerate test \u2014 featureDir or acceptanceContext missing", { storyId: storyId2 });
|
|
37586
|
-
return {
|
|
37587
|
-
fixed: false,
|
|
37588
|
-
cost: 0,
|
|
37589
|
-
prdDirty: false,
|
|
37590
|
-
verdict: "test_bug",
|
|
37591
|
-
confidence: 1,
|
|
37592
|
-
reasoning: "Semantic review confirmed all ACs are implemented \u2014 acceptance test failure is a test generation issue"
|
|
37593
|
-
};
|
|
37594
|
-
}
|
|
37595
|
-
const regenOutcome = await _acceptanceLoopDeps.executeTestRegen(ctx, acceptanceContext);
|
|
37596
|
-
logger?.info("acceptance.test-regen", "Test regeneration completed", { storyId: storyId2, outcome: regenOutcome });
|
|
37597
|
-
if (regenOutcome === "passed") {
|
|
37598
|
-
return { fixed: true, cost: 0, prdDirty: true };
|
|
37599
|
-
}
|
|
37600
37412
|
return {
|
|
37601
|
-
fixed: false,
|
|
37602
|
-
cost: 0,
|
|
37603
|
-
prdDirty: regenOutcome !== "no_test_file",
|
|
37604
37413
|
verdict: "test_bug",
|
|
37605
|
-
|
|
37606
|
-
|
|
37414
|
+
reasoning: `Semantic review confirmed all ${semanticVerdicts.length} ACs are implemented \u2014 failure is a test generation issue`,
|
|
37415
|
+
confidence: 1
|
|
37607
37416
|
};
|
|
37608
37417
|
}
|
|
37418
|
+
if (isTestLevelFailure(failures.failedACs, totalACs)) {
|
|
37419
|
+
logger?.info("acceptance.diagnosis", "Fast path: test-level failure heuristic \u2192 test_bug", {
|
|
37420
|
+
storyId,
|
|
37421
|
+
failedCount: failures.failedACs.length,
|
|
37422
|
+
totalACs
|
|
37423
|
+
});
|
|
37424
|
+
return {
|
|
37425
|
+
verdict: "test_bug",
|
|
37426
|
+
reasoning: `Test-level failure: ${failures.failedACs.length}/${totalACs} ACs failed (>80% threshold or AC-ERROR sentinel)`,
|
|
37427
|
+
confidence: 0.9
|
|
37428
|
+
};
|
|
37429
|
+
}
|
|
37430
|
+
return await diagnoseAcceptanceFailure(agent, {
|
|
37431
|
+
...diagnosisOpts,
|
|
37432
|
+
semanticVerdicts,
|
|
37433
|
+
previousFailure
|
|
37434
|
+
});
|
|
37435
|
+
}
|
|
37436
|
+
async function applyFix(opts) {
|
|
37437
|
+
const logger = getSafeLogger();
|
|
37438
|
+
const { ctx, failures, diagnosis, previousFailure } = opts;
|
|
37439
|
+
const storyId = ctx.prd.userStories[0]?.id ?? "unknown";
|
|
37609
37440
|
const agentName = ctx.config.autoMode.defaultAgent;
|
|
37610
|
-
const agent = (ctx.agentGetFn ??
|
|
37611
|
-
|
|
37612
|
-
|
|
37441
|
+
const agent = (ctx.agentGetFn ?? _applyFixDeps.getAgent)(agentName);
|
|
37442
|
+
if (!agent) {
|
|
37443
|
+
logger?.error("acceptance.applyFix", "Agent not found", { storyId, agentName });
|
|
37444
|
+
return { cost: 0 };
|
|
37445
|
+
}
|
|
37613
37446
|
const testPaths = ctx.acceptanceTestPaths;
|
|
37614
|
-
let
|
|
37447
|
+
let testFileContent = "";
|
|
37448
|
+
let acceptanceTestPath = "";
|
|
37615
37449
|
if (testPaths && testPaths.length > 0) {
|
|
37616
37450
|
const pathStrings = testPaths.map((p) => typeof p === "string" ? p : p.testPath);
|
|
37617
37451
|
const moduleEntries = await loadAcceptanceTestContent(pathStrings);
|
|
37618
|
-
|
|
37619
|
-
|
|
37620
|
-
|
|
37621
|
-
const moduleEntries = await loadAcceptanceTestContent(fallbackPath);
|
|
37622
|
-
testEntries = moduleEntries.map((e) => ({ content: e.content, path: e.testPath }));
|
|
37623
|
-
}
|
|
37624
|
-
const primaryEntry = testEntries[0] ?? { content: "", path: "" };
|
|
37625
|
-
const testFileContent = primaryEntry.content;
|
|
37626
|
-
const acceptanceTestPath = primaryEntry.path;
|
|
37627
|
-
const firstStory = prd?.userStories?.[0];
|
|
37628
|
-
const storyId = firstStory?.id ?? "unknown";
|
|
37629
|
-
if (failures.failedACs.length === 0) {
|
|
37630
|
-
return { fixed: true, cost: 0, prdDirty: false };
|
|
37631
|
-
}
|
|
37632
|
-
if (strategy === "implement-only") {
|
|
37633
|
-
logger?.info("acceptance", "Strategy is implement-only \u2014 executing source fix directly");
|
|
37634
|
-
if (!agent) {
|
|
37635
|
-
logger?.error("acceptance", "Agent not found for fix routing");
|
|
37636
|
-
return { fixed: false, cost: 0, prdDirty: false };
|
|
37637
|
-
}
|
|
37638
|
-
let fixAttempts = 0;
|
|
37639
|
-
while (fixAttempts < fixMaxRetries) {
|
|
37640
|
-
fixAttempts++;
|
|
37641
|
-
logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
|
|
37642
|
-
const defaultDiagnosis = {
|
|
37643
|
-
verdict: "source_bug",
|
|
37644
|
-
reasoning: "implement-only strategy \u2014 skipping diagnosis",
|
|
37645
|
-
confidence: 1
|
|
37646
|
-
};
|
|
37647
|
-
const fixResult = await executeSourceFix(agent, {
|
|
37648
|
-
testOutput: failures.testOutput,
|
|
37649
|
-
testFileContent,
|
|
37650
|
-
diagnosis: defaultDiagnosis,
|
|
37651
|
-
config: ctx.config,
|
|
37652
|
-
workdir: ctx.workdir,
|
|
37653
|
-
featureName: ctx.feature,
|
|
37654
|
-
storyId,
|
|
37655
|
-
acceptanceTestPath
|
|
37656
|
-
});
|
|
37657
|
-
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
37658
|
-
success: fixResult.success,
|
|
37659
|
-
cost: fixResult.cost,
|
|
37660
|
-
attempt: fixAttempts
|
|
37661
|
-
});
|
|
37662
|
-
if (fixResult.success) {
|
|
37663
|
-
return { fixed: true, cost: fixResult.cost, prdDirty: false };
|
|
37664
|
-
}
|
|
37665
|
-
logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
|
|
37666
|
-
attempt: fixAttempts,
|
|
37667
|
-
maxRetries: fixMaxRetries,
|
|
37668
|
-
cost: fixResult.cost,
|
|
37669
|
-
willRetry: fixAttempts < fixMaxRetries
|
|
37670
|
-
});
|
|
37671
|
-
if (fixAttempts >= fixMaxRetries) {
|
|
37672
|
-
logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
|
|
37673
|
-
break;
|
|
37674
|
-
}
|
|
37452
|
+
if (moduleEntries.length > 0) {
|
|
37453
|
+
testFileContent = moduleEntries[0].content;
|
|
37454
|
+
acceptanceTestPath = moduleEntries[0].testPath;
|
|
37675
37455
|
}
|
|
37676
|
-
|
|
37677
|
-
|
|
37678
|
-
|
|
37679
|
-
|
|
37680
|
-
|
|
37681
|
-
|
|
37682
|
-
config: ctx.config,
|
|
37683
|
-
workdir: ctx.workdir,
|
|
37684
|
-
featureName: ctx.feature,
|
|
37685
|
-
storyId,
|
|
37686
|
-
semanticVerdicts: options.semanticVerdicts
|
|
37687
|
-
});
|
|
37688
|
-
const diagnosisCost = diagnosis.cost ?? 0;
|
|
37689
|
-
logger?.info("acceptance.diagnosis", "Diagnosis complete", {
|
|
37690
|
-
verdict: diagnosis.verdict,
|
|
37691
|
-
confidence: diagnosis.confidence,
|
|
37692
|
-
reasoning: diagnosis.reasoning
|
|
37693
|
-
});
|
|
37694
|
-
if (diagnosis.verdict === "source_bug") {
|
|
37695
|
-
logger?.info("acceptance", "Diagnosis: source_bug \u2014 executing source fix");
|
|
37696
|
-
if (!agent) {
|
|
37697
|
-
logger?.error("acceptance", "Agent not found for source fix execution");
|
|
37698
|
-
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
37699
|
-
}
|
|
37700
|
-
let fixAttempts = 0;
|
|
37701
|
-
while (fixAttempts < fixMaxRetries) {
|
|
37702
|
-
fixAttempts++;
|
|
37703
|
-
logger?.info("acceptance", `Source fix attempt ${fixAttempts}/${fixMaxRetries}`);
|
|
37704
|
-
const fixResult = await executeSourceFix(agent, {
|
|
37705
|
-
testOutput: failures.testOutput,
|
|
37706
|
-
testFileContent,
|
|
37707
|
-
diagnosis,
|
|
37708
|
-
config: ctx.config,
|
|
37709
|
-
workdir: ctx.workdir,
|
|
37710
|
-
featureName: ctx.feature,
|
|
37711
|
-
storyId,
|
|
37712
|
-
acceptanceTestPath
|
|
37713
|
-
});
|
|
37714
|
-
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
37715
|
-
success: fixResult.success,
|
|
37716
|
-
cost: fixResult.cost,
|
|
37717
|
-
attempt: fixAttempts
|
|
37718
|
-
});
|
|
37719
|
-
if (fixResult.success) {
|
|
37720
|
-
return { fixed: true, cost: fixResult.cost + diagnosisCost, prdDirty: false };
|
|
37721
|
-
}
|
|
37722
|
-
logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
|
|
37723
|
-
attempt: fixAttempts,
|
|
37724
|
-
maxRetries: fixMaxRetries,
|
|
37725
|
-
cost: fixResult.cost,
|
|
37726
|
-
willRetry: fixAttempts < fixMaxRetries
|
|
37727
|
-
});
|
|
37728
|
-
if (fixAttempts >= fixMaxRetries) {
|
|
37729
|
-
logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
|
|
37730
|
-
break;
|
|
37731
|
-
}
|
|
37456
|
+
} else if (ctx.featureDir) {
|
|
37457
|
+
const fallbackPath = resolveAcceptanceFeatureTestPath(ctx.featureDir, ctx.config.acceptance.testPath, ctx.config.project?.language);
|
|
37458
|
+
const moduleEntries = await loadAcceptanceTestContent(fallbackPath);
|
|
37459
|
+
if (moduleEntries.length > 0) {
|
|
37460
|
+
testFileContent = moduleEntries[0].content;
|
|
37461
|
+
acceptanceTestPath = moduleEntries[0].testPath;
|
|
37732
37462
|
}
|
|
37733
|
-
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
37734
37463
|
}
|
|
37735
|
-
|
|
37736
|
-
|
|
37737
|
-
|
|
37738
|
-
|
|
37739
|
-
|
|
37740
|
-
|
|
37741
|
-
|
|
37742
|
-
|
|
37743
|
-
|
|
37744
|
-
|
|
37745
|
-
|
|
37464
|
+
let totalCost = 0;
|
|
37465
|
+
if (diagnosis.verdict === "source_bug" || diagnosis.verdict === "both") {
|
|
37466
|
+
logger?.info("acceptance.applyFix", "Applying source fix", { storyId, verdict: diagnosis.verdict });
|
|
37467
|
+
const sourceResult = await _applyFixDeps.executeSourceFix(agent, {
|
|
37468
|
+
testOutput: failures.testOutput,
|
|
37469
|
+
testFileContent,
|
|
37470
|
+
diagnosis,
|
|
37471
|
+
config: ctx.config,
|
|
37472
|
+
workdir: ctx.workdir,
|
|
37473
|
+
featureName: ctx.feature,
|
|
37474
|
+
storyId,
|
|
37475
|
+
acceptanceTestPath
|
|
37746
37476
|
});
|
|
37747
|
-
|
|
37748
|
-
|
|
37749
|
-
|
|
37750
|
-
|
|
37751
|
-
|
|
37752
|
-
testPathConfig: ctx.config.acceptance.testPath,
|
|
37753
|
-
language: ctx.config.project?.language
|
|
37754
|
-
})
|
|
37755
|
-
});
|
|
37756
|
-
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
37757
|
-
}
|
|
37758
|
-
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
37759
|
-
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
37760
|
-
outcome: regenerated ? "success" : "failure"
|
|
37477
|
+
totalCost += sourceResult.cost;
|
|
37478
|
+
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
37479
|
+
storyId,
|
|
37480
|
+
success: sourceResult.success,
|
|
37481
|
+
cost: sourceResult.cost
|
|
37761
37482
|
});
|
|
37762
|
-
if (!regenerated) {
|
|
37763
|
-
return { fixed: false, cost: diagnosisCost, prdDirty: false };
|
|
37764
|
-
}
|
|
37765
|
-
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
37766
|
-
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
37767
|
-
if (acceptanceResult.action === "continue") {
|
|
37768
|
-
logger?.info("acceptance", "Acceptance passed after test regeneration");
|
|
37769
|
-
return { fixed: true, cost: diagnosisCost, prdDirty: true };
|
|
37770
|
-
}
|
|
37771
|
-
logger?.warn("acceptance", "Acceptance still failing after test regeneration");
|
|
37772
|
-
return { fixed: false, cost: diagnosisCost, prdDirty: true };
|
|
37773
37483
|
}
|
|
37774
|
-
if (diagnosis.verdict === "both") {
|
|
37775
|
-
logger?.info("acceptance", "
|
|
37776
|
-
|
|
37777
|
-
|
|
37778
|
-
|
|
37779
|
-
|
|
37780
|
-
|
|
37781
|
-
|
|
37782
|
-
|
|
37783
|
-
|
|
37784
|
-
|
|
37785
|
-
|
|
37786
|
-
|
|
37787
|
-
testOutput: failures.testOutput,
|
|
37788
|
-
testFileContent,
|
|
37789
|
-
diagnosis,
|
|
37790
|
-
config: ctx.config,
|
|
37791
|
-
workdir: ctx.workdir,
|
|
37792
|
-
featureName: ctx.feature,
|
|
37793
|
-
storyId,
|
|
37794
|
-
acceptanceTestPath
|
|
37795
|
-
});
|
|
37796
|
-
logger?.info("acceptance.source-fix", "Source fix completed", {
|
|
37797
|
-
success: fixResult.success,
|
|
37798
|
-
cost: fixResult.cost,
|
|
37799
|
-
attempt: fixAttempts
|
|
37800
|
-
});
|
|
37801
|
-
sourceFixSuccess = fixResult.success;
|
|
37802
|
-
sourceFixCost += fixResult.cost;
|
|
37803
|
-
if (fixResult.success) {
|
|
37804
|
-
break;
|
|
37805
|
-
}
|
|
37806
|
-
logger?.warn("acceptance.source-fix", "Source fix attempt failed", {
|
|
37807
|
-
attempt: fixAttempts,
|
|
37808
|
-
maxRetries: fixMaxRetries,
|
|
37809
|
-
cost: fixResult.cost,
|
|
37810
|
-
willRetry: fixAttempts < fixMaxRetries
|
|
37811
|
-
});
|
|
37812
|
-
if (fixAttempts >= fixMaxRetries) {
|
|
37813
|
-
logger?.error("acceptance", `Source fix failed after ${fixMaxRetries} attempts`);
|
|
37814
|
-
break;
|
|
37815
|
-
}
|
|
37816
|
-
}
|
|
37817
|
-
if (!sourceFixSuccess) {
|
|
37818
|
-
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
37819
|
-
}
|
|
37820
|
-
logger?.info("acceptance", "Source fix succeeded \u2014 re-running acceptance to verify");
|
|
37821
|
-
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
37822
|
-
const acceptanceResult = await acceptanceStage2.execute(acceptanceContext);
|
|
37823
|
-
if (acceptanceResult.action === "continue") {
|
|
37824
|
-
logger?.info("acceptance", "Acceptance passed after source fix");
|
|
37825
|
-
return { fixed: true, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
37826
|
-
}
|
|
37827
|
-
logger?.info("acceptance", "Acceptance still failing after source fix \u2014 regenerating test");
|
|
37828
|
-
if (!ctx.featureDir) {
|
|
37829
|
-
logger?.error("acceptance", "Cannot regenerate test without featureDir");
|
|
37830
|
-
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
37831
|
-
}
|
|
37832
|
-
const testPath = await findExistingAcceptanceTestPath({
|
|
37833
|
-
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
37834
|
-
featureDir: ctx.featureDir,
|
|
37835
|
-
testPathConfig: ctx.config.acceptance.testPath,
|
|
37836
|
-
language: ctx.config.project?.language
|
|
37484
|
+
if (diagnosis.verdict === "test_bug" || diagnosis.verdict === "both") {
|
|
37485
|
+
logger?.info("acceptance.applyFix", "Applying test fix", { storyId, verdict: diagnosis.verdict });
|
|
37486
|
+
const testResult = await _applyFixDeps.executeTestFix(agent, {
|
|
37487
|
+
testOutput: failures.testOutput,
|
|
37488
|
+
testFileContent,
|
|
37489
|
+
failedACs: failures.failedACs,
|
|
37490
|
+
diagnosis,
|
|
37491
|
+
config: ctx.config,
|
|
37492
|
+
workdir: ctx.workdir,
|
|
37493
|
+
featureName: ctx.feature,
|
|
37494
|
+
storyId,
|
|
37495
|
+
acceptanceTestPath,
|
|
37496
|
+
previousFailure
|
|
37837
37497
|
});
|
|
37838
|
-
|
|
37839
|
-
|
|
37840
|
-
|
|
37841
|
-
|
|
37842
|
-
|
|
37843
|
-
testPathConfig: ctx.config.acceptance.testPath,
|
|
37844
|
-
language: ctx.config.project?.language
|
|
37845
|
-
})
|
|
37846
|
-
});
|
|
37847
|
-
return { fixed: false, cost: sourceFixCost + diagnosisCost, prdDirty: false };
|
|
37848
|
-
}
|
|
37849
|
-
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
37850
|
-
logger?.info("acceptance.test-regen", "Test regeneration completed", {
|
|
37851
|
-
outcome: regenerated ? "success" : "failure"
|
|
37498
|
+
totalCost += testResult.cost;
|
|
37499
|
+
logger?.info("acceptance.test-fix", "Test fix completed", {
|
|
37500
|
+
storyId,
|
|
37501
|
+
success: testResult.success,
|
|
37502
|
+
cost: testResult.cost
|
|
37852
37503
|
});
|
|
37853
|
-
return { fixed: regenerated, cost: sourceFixCost + diagnosisCost, prdDirty: regenerated };
|
|
37854
37504
|
}
|
|
37855
|
-
return {
|
|
37505
|
+
return { cost: totalCost };
|
|
37856
37506
|
}
|
|
37507
|
+
var _applyFixDeps;
|
|
37508
|
+
var init_acceptance_fix = __esm(() => {
|
|
37509
|
+
init_fix_diagnosis();
|
|
37510
|
+
init_fix_executor();
|
|
37511
|
+
init_test_path();
|
|
37512
|
+
init_registry();
|
|
37513
|
+
init_logger2();
|
|
37514
|
+
init_acceptance_helpers();
|
|
37515
|
+
_applyFixDeps = {
|
|
37516
|
+
getAgent,
|
|
37517
|
+
executeSourceFix,
|
|
37518
|
+
executeTestFix
|
|
37519
|
+
};
|
|
37520
|
+
});
|
|
37521
|
+
|
|
37522
|
+
// src/execution/lifecycle/acceptance-loop.ts
|
|
37523
|
+
var exports_acceptance_loop = {};
|
|
37524
|
+
__export(exports_acceptance_loop, {
|
|
37525
|
+
runAcceptanceLoop: () => runAcceptanceLoop,
|
|
37526
|
+
regenerateAcceptanceTest: () => regenerateAcceptanceTest,
|
|
37527
|
+
loadSpecContent: () => loadSpecContent,
|
|
37528
|
+
loadAcceptanceTestContent: () => loadAcceptanceTestContent2,
|
|
37529
|
+
isTestLevelFailure: () => isTestLevelFailure,
|
|
37530
|
+
isStubTestFile: () => isStubTestFile,
|
|
37531
|
+
buildResult: () => buildResult,
|
|
37532
|
+
_regenerateDeps: () => _regenerateDeps,
|
|
37533
|
+
_acceptanceLoopDeps: () => _acceptanceLoopDeps
|
|
37534
|
+
});
|
|
37857
37535
|
async function runAcceptanceLoop(ctx) {
|
|
37858
37536
|
const logger = getSafeLogger();
|
|
37859
37537
|
const maxRetries = ctx.config.acceptance.maxRetries;
|
|
37860
37538
|
let acceptanceRetries = 0;
|
|
37861
|
-
let
|
|
37539
|
+
let stubRegenCount = 0;
|
|
37540
|
+
let previousFailure = "";
|
|
37541
|
+
const prd = ctx.prd;
|
|
37862
37542
|
let totalCost = ctx.totalCost;
|
|
37863
|
-
|
|
37864
|
-
|
|
37865
|
-
|
|
37543
|
+
const iterations = ctx.iterations;
|
|
37544
|
+
const storiesCompleted = ctx.storiesCompleted;
|
|
37545
|
+
const prdDirty = false;
|
|
37866
37546
|
logger?.info("acceptance", "All stories complete, running acceptance validation");
|
|
37867
37547
|
while (acceptanceRetries < maxRetries) {
|
|
37868
37548
|
const firstStory = prd.userStories[0];
|
|
@@ -37899,18 +37579,16 @@ async function runAcceptanceLoop(ctx) {
|
|
|
37899
37579
|
const failures = acceptanceContext.acceptanceFailures;
|
|
37900
37580
|
if (!failures || failures.failedACs.length === 0) {
|
|
37901
37581
|
logger?.error("acceptance", "Acceptance tests failed but no specific failures detected");
|
|
37902
|
-
logger?.warn("acceptance", "Manual intervention required");
|
|
37903
37582
|
await fireHook(ctx.hooks, "on-pause", hookCtx(ctx.feature, { reason: "Acceptance tests failed (no failures detected)", cost: totalCost }), ctx.workdir);
|
|
37904
37583
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
37905
37584
|
}
|
|
37906
37585
|
acceptanceRetries++;
|
|
37907
37586
|
logger?.warn("acceptance", `Acceptance retry ${acceptanceRetries}/${maxRetries}`, {
|
|
37587
|
+
storyId: firstStory?.id,
|
|
37908
37588
|
failedACs: failures.failedACs
|
|
37909
37589
|
});
|
|
37910
37590
|
if (acceptanceRetries >= maxRetries) {
|
|
37911
|
-
logger?.error("acceptance", "Max acceptance retries reached");
|
|
37912
|
-
logger?.warn("acceptance", "Manual intervention required");
|
|
37913
|
-
logger?.debug("acceptance", 'Run: nax accept --override AC-N "reason" to skip specific ACs');
|
|
37591
|
+
logger?.error("acceptance", "Max acceptance retries reached", { storyId: firstStory?.id });
|
|
37914
37592
|
await fireHook(ctx.hooks, "on-pause", hookCtx(ctx.feature, {
|
|
37915
37593
|
reason: `Acceptance validation failed after ${maxRetries} retries: ${failures.failedACs.join(", ")}`,
|
|
37916
37594
|
cost: totalCost
|
|
@@ -37924,141 +37602,87 @@ async function runAcceptanceLoop(ctx) {
|
|
|
37924
37602
|
testPathConfig: ctx.config.acceptance.testPath,
|
|
37925
37603
|
language: ctx.config.project?.language
|
|
37926
37604
|
});
|
|
37927
|
-
if (existingStubPath) {
|
|
37928
|
-
|
|
37929
|
-
|
|
37930
|
-
|
|
37931
|
-
|
|
37605
|
+
if (existingStubPath && isStubTestFile(await Bun.file(existingStubPath).text())) {
|
|
37606
|
+
if (stubRegenCount >= MAX_STUB_REGENS) {
|
|
37607
|
+
logger?.error("acceptance", "Acceptance test generator cannot produce real tests \u2014 giving up", {
|
|
37608
|
+
storyId: firstStory?.id,
|
|
37609
|
+
stubRegenCount
|
|
37932
37610
|
});
|
|
37933
|
-
const { unlink: unlink3 } = await import("fs/promises");
|
|
37934
|
-
await unlink3(existingStubPath);
|
|
37935
|
-
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
37936
|
-
await acceptanceSetupStage2.execute(acceptanceContext);
|
|
37937
|
-
const newContent = await Bun.file(existingStubPath).text();
|
|
37938
|
-
if (isStubTestFile(newContent)) {
|
|
37939
|
-
logger?.error("acceptance", "Acceptance test generation failed after retry \u2014 manual implementation required");
|
|
37940
|
-
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
|
|
37941
|
-
}
|
|
37942
|
-
continue;
|
|
37943
|
-
}
|
|
37944
|
-
}
|
|
37945
|
-
}
|
|
37946
|
-
const totalACs = prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria).length;
|
|
37947
|
-
if (ctx.featureDir && isTestLevelFailure(failures.failedACs, totalACs)) {
|
|
37948
|
-
logger?.warn("acceptance", `Test-level failure detected (${failures.failedACs.length}/${totalACs} ACs failed) \u2014 regenerating acceptance test`);
|
|
37949
|
-
const testPath = await findExistingAcceptanceTestPath({
|
|
37950
|
-
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
37951
|
-
featureDir: ctx.featureDir,
|
|
37952
|
-
testPathConfig: ctx.config.acceptance.testPath,
|
|
37953
|
-
language: ctx.config.project?.language
|
|
37954
|
-
});
|
|
37955
|
-
if (testPath) {
|
|
37956
|
-
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
37957
|
-
if (!regenerated) {
|
|
37958
37611
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
|
|
37959
37612
|
}
|
|
37613
|
+
stubRegenCount++;
|
|
37614
|
+
logger?.warn("acceptance", "Stub test detected \u2014 full regen", {
|
|
37615
|
+
storyId: firstStory?.id,
|
|
37616
|
+
attempt: stubRegenCount,
|
|
37617
|
+
maxStubRegens: MAX_STUB_REGENS
|
|
37618
|
+
});
|
|
37619
|
+
await regenerateAcceptanceTest(existingStubPath, acceptanceContext);
|
|
37960
37620
|
continue;
|
|
37961
37621
|
}
|
|
37962
37622
|
}
|
|
37963
|
-
const
|
|
37964
|
-
|
|
37965
|
-
|
|
37966
|
-
|
|
37967
|
-
|
|
37968
|
-
|
|
37969
|
-
failures,
|
|
37970
|
-
prd,
|
|
37971
|
-
acceptanceContext,
|
|
37972
|
-
semanticVerdicts
|
|
37973
|
-
});
|
|
37974
|
-
totalCost += fixResult.cost;
|
|
37975
|
-
if (fixResult.fixed) {
|
|
37976
|
-
logger?.info("acceptance", "Fix succeeded \u2014 re-running acceptance tests...");
|
|
37977
|
-
continue;
|
|
37978
|
-
}
|
|
37979
|
-
logger?.error("acceptance", "Fix routing failed to resolve acceptance failures");
|
|
37980
|
-
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
|
|
37981
|
-
}
|
|
37982
|
-
logger?.info("acceptance", "Generating fix stories...");
|
|
37983
|
-
const fixStories = await generateAndAddFixStories(ctx, failures, prd);
|
|
37984
|
-
if (!fixStories) {
|
|
37623
|
+
const semanticVerdicts = ctx.featureDir ? await _acceptanceLoopDeps.loadSemanticVerdicts(ctx.featureDir) : [];
|
|
37624
|
+
const totalACs = prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria).length;
|
|
37625
|
+
const agentName = ctx.config.autoMode.defaultAgent;
|
|
37626
|
+
const agent = (ctx.agentGetFn ?? _acceptanceLoopDeps.getAgent)(agentName);
|
|
37627
|
+
if (!agent) {
|
|
37628
|
+
logger?.error("acceptance", "Agent not found for diagnosis", { storyId: firstStory?.id, agentName });
|
|
37985
37629
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty, failures.failedACs, acceptanceRetries);
|
|
37986
37630
|
}
|
|
37987
|
-
await
|
|
37988
|
-
|
|
37989
|
-
|
|
37990
|
-
|
|
37991
|
-
|
|
37992
|
-
|
|
37993
|
-
|
|
37994
|
-
|
|
37995
|
-
|
|
37996
|
-
|
|
37997
|
-
|
|
37998
|
-
|
|
37999
|
-
|
|
38000
|
-
|
|
38001
|
-
|
|
38002
|
-
|
|
38003
|
-
|
|
38004
|
-
|
|
38005
|
-
}
|
|
38006
|
-
logger?.info("acceptance", "
|
|
37631
|
+
const testEntries = ctx.acceptanceTestPaths ? await loadAcceptanceTestContent(ctx.acceptanceTestPaths.map((p) => p.testPath)) : [];
|
|
37632
|
+
const testFileContent = testEntries[0]?.content ?? "";
|
|
37633
|
+
const strategy = ctx.config.acceptance.fix?.strategy ?? "diagnose-first";
|
|
37634
|
+
const diagnosis = await resolveAcceptanceDiagnosis({
|
|
37635
|
+
agent,
|
|
37636
|
+
failures,
|
|
37637
|
+
totalACs,
|
|
37638
|
+
strategy,
|
|
37639
|
+
semanticVerdicts,
|
|
37640
|
+
diagnosisOpts: {
|
|
37641
|
+
testOutput: failures.testOutput,
|
|
37642
|
+
testFileContent,
|
|
37643
|
+
config: ctx.config,
|
|
37644
|
+
workdir: ctx.workdir,
|
|
37645
|
+
featureName: ctx.feature,
|
|
37646
|
+
storyId: firstStory?.id
|
|
37647
|
+
},
|
|
37648
|
+
previousFailure
|
|
37649
|
+
});
|
|
37650
|
+
logger?.info("acceptance.diagnosis", "Diagnosis resolved", {
|
|
37651
|
+
storyId: firstStory?.id,
|
|
37652
|
+
verdict: diagnosis.verdict,
|
|
37653
|
+
confidence: diagnosis.confidence,
|
|
37654
|
+
attempt: acceptanceRetries
|
|
37655
|
+
});
|
|
37656
|
+
const fixResult = await applyFix({
|
|
37657
|
+
ctx,
|
|
37658
|
+
failures,
|
|
37659
|
+
diagnosis,
|
|
37660
|
+
previousFailure
|
|
37661
|
+
});
|
|
37662
|
+
totalCost += fixResult.cost;
|
|
37663
|
+
previousFailure += `
|
|
37664
|
+
---
|
|
37665
|
+
Attempt ${acceptanceRetries}/${maxRetries}: verdict=${diagnosis.verdict}, confidence=${diagnosis.confidence}
|
|
37666
|
+
Reasoning: ${diagnosis.reasoning}
|
|
37667
|
+
Failed ACs: ${failures.failedACs.join(", ")}
|
|
37668
|
+
`;
|
|
38007
37669
|
}
|
|
38008
37670
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
38009
37671
|
}
|
|
38010
|
-
var _acceptanceLoopDeps,
|
|
37672
|
+
var _acceptanceLoopDeps, MAX_STUB_REGENS = 2;
|
|
38011
37673
|
var init_acceptance_loop = __esm(() => {
|
|
38012
|
-
init_acceptance4();
|
|
38013
|
-
init_fix_diagnosis();
|
|
38014
|
-
init_fix_executor();
|
|
38015
37674
|
init_semantic_verdict();
|
|
38016
37675
|
init_test_path();
|
|
38017
37676
|
init_registry();
|
|
38018
|
-
init_config();
|
|
38019
|
-
init_loader();
|
|
38020
37677
|
init_hooks();
|
|
38021
37678
|
init_logger2();
|
|
38022
|
-
init_runner();
|
|
38023
|
-
init_stages();
|
|
38024
|
-
init_prd();
|
|
38025
|
-
init_routing();
|
|
38026
37679
|
init_helpers();
|
|
37680
|
+
init_acceptance_fix();
|
|
37681
|
+
init_acceptance_helpers();
|
|
37682
|
+
init_acceptance_helpers();
|
|
38027
37683
|
_acceptanceLoopDeps = {
|
|
38028
37684
|
getAgent,
|
|
38029
|
-
loadSemanticVerdicts
|
|
38030
|
-
executeTestRegen: async (ctx, acceptanceContext) => {
|
|
38031
|
-
const testPath = await findExistingAcceptanceTestPath({
|
|
38032
|
-
acceptanceTestPaths: ctx.acceptanceTestPaths,
|
|
38033
|
-
featureDir: ctx.featureDir,
|
|
38034
|
-
testPathConfig: ctx.config.acceptance.testPath,
|
|
38035
|
-
language: ctx.config.project?.language
|
|
38036
|
-
});
|
|
38037
|
-
if (!testPath)
|
|
38038
|
-
return "no_test_file";
|
|
38039
|
-
const regenerated = await regenerateAcceptanceTest(testPath, acceptanceContext);
|
|
38040
|
-
if (!regenerated)
|
|
38041
|
-
return "failed";
|
|
38042
|
-
const { acceptanceStage: acceptanceStage2 } = await Promise.resolve().then(() => (init_acceptance(), exports_acceptance));
|
|
38043
|
-
const result = await acceptanceStage2.execute(acceptanceContext);
|
|
38044
|
-
return result.action === "continue" ? "passed" : "failed";
|
|
38045
|
-
}
|
|
38046
|
-
};
|
|
38047
|
-
_regenerateDeps = {
|
|
38048
|
-
spawnGitDiff: async (workdir, gitRef) => {
|
|
38049
|
-
const proc = Bun.spawn(["git", "diff", "--name-only", gitRef], {
|
|
38050
|
-
cwd: workdir,
|
|
38051
|
-
stdout: "pipe",
|
|
38052
|
-
stderr: "pipe"
|
|
38053
|
-
});
|
|
38054
|
-
const [, stdout] = await Promise.all([proc.exited, new Response(proc.stdout).text()]);
|
|
38055
|
-
return stdout.trim();
|
|
38056
|
-
},
|
|
38057
|
-
readFile: async (filePath) => Bun.file(filePath).text(),
|
|
38058
|
-
acceptanceSetupExecute: async (ctx) => {
|
|
38059
|
-
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
38060
|
-
await acceptanceSetupStage2.execute(ctx);
|
|
38061
|
-
}
|
|
37685
|
+
loadSemanticVerdicts
|
|
38062
37686
|
};
|
|
38063
37687
|
});
|
|
38064
37688
|
|
|
@@ -38542,12 +38166,12 @@ var init_headless_formatter = __esm(() => {
|
|
|
38542
38166
|
// src/pipeline/subscribers/events-writer.ts
|
|
38543
38167
|
import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
|
|
38544
38168
|
import { homedir as homedir5 } from "os";
|
|
38545
|
-
import { basename as basename6, join as
|
|
38169
|
+
import { basename as basename6, join as join43 } from "path";
|
|
38546
38170
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
38547
38171
|
const logger = getSafeLogger();
|
|
38548
38172
|
const project = basename6(workdir);
|
|
38549
|
-
const eventsDir =
|
|
38550
|
-
const eventsFile =
|
|
38173
|
+
const eventsDir = join43(homedir5(), ".nax", "events", project);
|
|
38174
|
+
const eventsFile = join43(eventsDir, "events.jsonl");
|
|
38551
38175
|
let dirReady = false;
|
|
38552
38176
|
const write = (line) => {
|
|
38553
38177
|
return (async () => {
|
|
@@ -38728,12 +38352,12 @@ var init_interaction2 = __esm(() => {
|
|
|
38728
38352
|
// src/pipeline/subscribers/registry.ts
|
|
38729
38353
|
import { mkdir as mkdir4, writeFile } from "fs/promises";
|
|
38730
38354
|
import { homedir as homedir6 } from "os";
|
|
38731
|
-
import { basename as basename7, join as
|
|
38355
|
+
import { basename as basename7, join as join44 } from "path";
|
|
38732
38356
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
38733
38357
|
const logger = getSafeLogger();
|
|
38734
38358
|
const project = basename7(workdir);
|
|
38735
|
-
const runDir =
|
|
38736
|
-
const metaFile =
|
|
38359
|
+
const runDir = join44(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
38360
|
+
const metaFile = join44(runDir, "meta.json");
|
|
38737
38361
|
const unsub = bus.on("run:started", (_ev) => {
|
|
38738
38362
|
return (async () => {
|
|
38739
38363
|
try {
|
|
@@ -38743,8 +38367,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
38743
38367
|
project,
|
|
38744
38368
|
feature,
|
|
38745
38369
|
workdir,
|
|
38746
|
-
statusPath:
|
|
38747
|
-
eventsDir:
|
|
38370
|
+
statusPath: join44(workdir, ".nax", "features", feature, "status.json"),
|
|
38371
|
+
eventsDir: join44(workdir, ".nax", "features", feature, "runs"),
|
|
38748
38372
|
registeredAt: new Date().toISOString()
|
|
38749
38373
|
};
|
|
38750
38374
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -39401,7 +39025,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
39401
39025
|
});
|
|
39402
39026
|
|
|
39403
39027
|
// src/execution/iteration-runner.ts
|
|
39404
|
-
import { join as
|
|
39028
|
+
import { join as join45 } from "path";
|
|
39405
39029
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
39406
39030
|
const logger = getSafeLogger();
|
|
39407
39031
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -39436,7 +39060,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
39436
39060
|
}
|
|
39437
39061
|
}
|
|
39438
39062
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
39439
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
39063
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join45(ctx.workdir, ".nax", "config.json"), story.workdir) : ctx.config;
|
|
39440
39064
|
const pipelineContext = {
|
|
39441
39065
|
config: effectiveConfig,
|
|
39442
39066
|
rootConfig: ctx.config,
|
|
@@ -39445,7 +39069,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
39445
39069
|
stories: storiesToExecute,
|
|
39446
39070
|
routing,
|
|
39447
39071
|
projectDir: ctx.workdir,
|
|
39448
|
-
workdir: story.workdir ?
|
|
39072
|
+
workdir: story.workdir ? join45(ctx.workdir, story.workdir) : ctx.workdir,
|
|
39449
39073
|
prdPath: ctx.prdPath,
|
|
39450
39074
|
featureDir: ctx.featureDir,
|
|
39451
39075
|
hooks: ctx.hooks,
|
|
@@ -39601,7 +39225,7 @@ __export(exports_parallel_worker, {
|
|
|
39601
39225
|
executeStoryInWorktree: () => executeStoryInWorktree,
|
|
39602
39226
|
executeParallelBatch: () => executeParallelBatch
|
|
39603
39227
|
});
|
|
39604
|
-
import { join as
|
|
39228
|
+
import { join as join46 } from "path";
|
|
39605
39229
|
async function executeStoryInWorktree(story, worktreePath, context, routing, eventEmitter) {
|
|
39606
39230
|
const logger = getSafeLogger();
|
|
39607
39231
|
try {
|
|
@@ -39621,7 +39245,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
39621
39245
|
story,
|
|
39622
39246
|
stories: [story],
|
|
39623
39247
|
projectDir: context.projectDir,
|
|
39624
|
-
workdir: story.workdir ?
|
|
39248
|
+
workdir: story.workdir ? join46(worktreePath, story.workdir) : worktreePath,
|
|
39625
39249
|
routing,
|
|
39626
39250
|
storyGitRef: storyGitRef ?? undefined
|
|
39627
39251
|
};
|
|
@@ -39710,13 +39334,13 @@ __export(exports_manager, {
|
|
|
39710
39334
|
});
|
|
39711
39335
|
import { existsSync as existsSync29, symlinkSync } from "fs";
|
|
39712
39336
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
39713
|
-
import { join as
|
|
39337
|
+
import { join as join47 } from "path";
|
|
39714
39338
|
|
|
39715
39339
|
class WorktreeManager {
|
|
39716
39340
|
async ensureGitExcludes(projectRoot) {
|
|
39717
39341
|
const logger = getSafeLogger();
|
|
39718
|
-
const infoDir =
|
|
39719
|
-
const excludePath =
|
|
39342
|
+
const infoDir = join47(projectRoot, ".git", "info");
|
|
39343
|
+
const excludePath = join47(infoDir, "exclude");
|
|
39720
39344
|
try {
|
|
39721
39345
|
await mkdir5(infoDir, { recursive: true });
|
|
39722
39346
|
let existing = "";
|
|
@@ -39743,7 +39367,7 @@ ${missing.join(`
|
|
|
39743
39367
|
}
|
|
39744
39368
|
async create(projectRoot, storyId) {
|
|
39745
39369
|
validateStoryId(storyId);
|
|
39746
|
-
const worktreePath =
|
|
39370
|
+
const worktreePath = join47(projectRoot, ".nax-wt", storyId);
|
|
39747
39371
|
const branchName = `nax/${storyId}`;
|
|
39748
39372
|
try {
|
|
39749
39373
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -39784,9 +39408,9 @@ ${missing.join(`
|
|
|
39784
39408
|
}
|
|
39785
39409
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
39786
39410
|
}
|
|
39787
|
-
const nodeModulesSource =
|
|
39411
|
+
const nodeModulesSource = join47(projectRoot, "node_modules");
|
|
39788
39412
|
if (existsSync29(nodeModulesSource)) {
|
|
39789
|
-
const nodeModulesTarget =
|
|
39413
|
+
const nodeModulesTarget = join47(worktreePath, "node_modules");
|
|
39790
39414
|
try {
|
|
39791
39415
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
39792
39416
|
} catch (error48) {
|
|
@@ -39794,9 +39418,9 @@ ${missing.join(`
|
|
|
39794
39418
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
39795
39419
|
}
|
|
39796
39420
|
}
|
|
39797
|
-
const envSource =
|
|
39421
|
+
const envSource = join47(projectRoot, ".env");
|
|
39798
39422
|
if (existsSync29(envSource)) {
|
|
39799
|
-
const envTarget =
|
|
39423
|
+
const envTarget = join47(worktreePath, ".env");
|
|
39800
39424
|
try {
|
|
39801
39425
|
symlinkSync(envSource, envTarget, "file");
|
|
39802
39426
|
} catch (error48) {
|
|
@@ -39807,7 +39431,7 @@ ${missing.join(`
|
|
|
39807
39431
|
}
|
|
39808
39432
|
async remove(projectRoot, storyId) {
|
|
39809
39433
|
validateStoryId(storyId);
|
|
39810
|
-
const worktreePath =
|
|
39434
|
+
const worktreePath = join47(projectRoot, ".nax-wt", storyId);
|
|
39811
39435
|
const branchName = `nax/${storyId}`;
|
|
39812
39436
|
try {
|
|
39813
39437
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -40749,16 +40373,16 @@ var init_unified_executor = __esm(() => {
|
|
|
40749
40373
|
});
|
|
40750
40374
|
|
|
40751
40375
|
// src/project/detector.ts
|
|
40752
|
-
import { join as
|
|
40376
|
+
import { join as join48 } from "path";
|
|
40753
40377
|
async function detectLanguage(workdir, pkg) {
|
|
40754
40378
|
const deps = _detectorDeps;
|
|
40755
|
-
if (await deps.fileExists(
|
|
40379
|
+
if (await deps.fileExists(join48(workdir, "go.mod")))
|
|
40756
40380
|
return "go";
|
|
40757
|
-
if (await deps.fileExists(
|
|
40381
|
+
if (await deps.fileExists(join48(workdir, "Cargo.toml")))
|
|
40758
40382
|
return "rust";
|
|
40759
|
-
if (await deps.fileExists(
|
|
40383
|
+
if (await deps.fileExists(join48(workdir, "pyproject.toml")))
|
|
40760
40384
|
return "python";
|
|
40761
|
-
if (await deps.fileExists(
|
|
40385
|
+
if (await deps.fileExists(join48(workdir, "requirements.txt")))
|
|
40762
40386
|
return "python";
|
|
40763
40387
|
if (pkg != null) {
|
|
40764
40388
|
const allDeps = {
|
|
@@ -40818,18 +40442,18 @@ async function detectLintTool(workdir, language) {
|
|
|
40818
40442
|
if (language === "python")
|
|
40819
40443
|
return "ruff";
|
|
40820
40444
|
const deps = _detectorDeps;
|
|
40821
|
-
if (await deps.fileExists(
|
|
40445
|
+
if (await deps.fileExists(join48(workdir, "biome.json")))
|
|
40822
40446
|
return "biome";
|
|
40823
|
-
if (await deps.fileExists(
|
|
40447
|
+
if (await deps.fileExists(join48(workdir, ".eslintrc")))
|
|
40824
40448
|
return "eslint";
|
|
40825
|
-
if (await deps.fileExists(
|
|
40449
|
+
if (await deps.fileExists(join48(workdir, ".eslintrc.js")))
|
|
40826
40450
|
return "eslint";
|
|
40827
|
-
if (await deps.fileExists(
|
|
40451
|
+
if (await deps.fileExists(join48(workdir, ".eslintrc.json")))
|
|
40828
40452
|
return "eslint";
|
|
40829
40453
|
return;
|
|
40830
40454
|
}
|
|
40831
40455
|
async function detectProjectProfile(workdir, existing) {
|
|
40832
|
-
const pkg = await _detectorDeps.readJson(
|
|
40456
|
+
const pkg = await _detectorDeps.readJson(join48(workdir, "package.json"));
|
|
40833
40457
|
const language = existing.language !== undefined ? existing.language : await detectLanguage(workdir, pkg);
|
|
40834
40458
|
const type = existing.type !== undefined ? existing.type : detectType(pkg);
|
|
40835
40459
|
const testFramework = existing.testFramework !== undefined ? existing.testFramework : await detectTestFramework(workdir, language, pkg);
|
|
@@ -40925,7 +40549,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
40925
40549
|
var init_status_file = () => {};
|
|
40926
40550
|
|
|
40927
40551
|
// src/execution/status-writer.ts
|
|
40928
|
-
import { join as
|
|
40552
|
+
import { join as join49 } from "path";
|
|
40929
40553
|
|
|
40930
40554
|
class StatusWriter {
|
|
40931
40555
|
statusFile;
|
|
@@ -41039,7 +40663,7 @@ class StatusWriter {
|
|
|
41039
40663
|
if (!this._prd)
|
|
41040
40664
|
return;
|
|
41041
40665
|
const safeLogger = getSafeLogger();
|
|
41042
|
-
const featureStatusPath =
|
|
40666
|
+
const featureStatusPath = join49(featureDir, "status.json");
|
|
41043
40667
|
const write = async () => {
|
|
41044
40668
|
try {
|
|
41045
40669
|
const base = this.getSnapshot(totalCost, iterations);
|
|
@@ -41250,7 +40874,7 @@ __export(exports_run_initialization, {
|
|
|
41250
40874
|
initializeRun: () => initializeRun,
|
|
41251
40875
|
_reconcileDeps: () => _reconcileDeps
|
|
41252
40876
|
});
|
|
41253
|
-
import { join as
|
|
40877
|
+
import { join as join50 } from "path";
|
|
41254
40878
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
41255
40879
|
const logger = getSafeLogger();
|
|
41256
40880
|
let reconciledCount = 0;
|
|
@@ -41268,7 +40892,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
41268
40892
|
});
|
|
41269
40893
|
continue;
|
|
41270
40894
|
}
|
|
41271
|
-
const effectiveWorkdir = story.workdir ?
|
|
40895
|
+
const effectiveWorkdir = story.workdir ? join50(workdir, story.workdir) : workdir;
|
|
41272
40896
|
try {
|
|
41273
40897
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
41274
40898
|
if (!reviewResult.success) {
|
|
@@ -72483,7 +72107,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
72483
72107
|
init_source();
|
|
72484
72108
|
import { existsSync as existsSync31, mkdirSync as mkdirSync7 } from "fs";
|
|
72485
72109
|
import { homedir as homedir8 } from "os";
|
|
72486
|
-
import { join as
|
|
72110
|
+
import { join as join52 } from "path";
|
|
72487
72111
|
|
|
72488
72112
|
// node_modules/commander/esm.mjs
|
|
72489
72113
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -84515,15 +84139,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
84515
84139
|
}
|
|
84516
84140
|
return;
|
|
84517
84141
|
}
|
|
84518
|
-
const naxDir =
|
|
84142
|
+
const naxDir = join52(workdir, ".nax");
|
|
84519
84143
|
if (existsSync31(naxDir) && !options.force) {
|
|
84520
84144
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
84521
84145
|
return;
|
|
84522
84146
|
}
|
|
84523
|
-
mkdirSync7(
|
|
84524
|
-
mkdirSync7(
|
|
84525
|
-
await Bun.write(
|
|
84526
|
-
await Bun.write(
|
|
84147
|
+
mkdirSync7(join52(naxDir, "features"), { recursive: true });
|
|
84148
|
+
mkdirSync7(join52(naxDir, "hooks"), { recursive: true });
|
|
84149
|
+
await Bun.write(join52(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
84150
|
+
await Bun.write(join52(naxDir, "hooks.json"), JSON.stringify({
|
|
84527
84151
|
hooks: {
|
|
84528
84152
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
84529
84153
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -84531,12 +84155,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
84531
84155
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
84532
84156
|
}
|
|
84533
84157
|
}, null, 2));
|
|
84534
|
-
await Bun.write(
|
|
84158
|
+
await Bun.write(join52(naxDir, ".gitignore"), `# nax temp files
|
|
84535
84159
|
*.tmp
|
|
84536
84160
|
.paused.json
|
|
84537
84161
|
.nax-verifier-verdict.json
|
|
84538
84162
|
`);
|
|
84539
|
-
await Bun.write(
|
|
84163
|
+
await Bun.write(join52(naxDir, "context.md"), `# Project Context
|
|
84540
84164
|
|
|
84541
84165
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
84542
84166
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -84666,8 +84290,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
84666
84290
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
84667
84291
|
process.exit(1);
|
|
84668
84292
|
}
|
|
84669
|
-
const featureDir =
|
|
84670
|
-
const prdPath =
|
|
84293
|
+
const featureDir = join52(naxDir, "features", options.feature);
|
|
84294
|
+
const prdPath = join52(featureDir, "prd.json");
|
|
84671
84295
|
if (options.plan && options.from) {
|
|
84672
84296
|
if (existsSync31(prdPath) && !options.force) {
|
|
84673
84297
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -84689,10 +84313,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
84689
84313
|
}
|
|
84690
84314
|
}
|
|
84691
84315
|
try {
|
|
84692
|
-
const planLogDir =
|
|
84316
|
+
const planLogDir = join52(featureDir, "plan");
|
|
84693
84317
|
mkdirSync7(planLogDir, { recursive: true });
|
|
84694
84318
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
84695
|
-
const planLogPath =
|
|
84319
|
+
const planLogPath = join52(planLogDir, `${planLogId}.jsonl`);
|
|
84696
84320
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
84697
84321
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
84698
84322
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -84736,10 +84360,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
84736
84360
|
process.exit(1);
|
|
84737
84361
|
}
|
|
84738
84362
|
resetLogger();
|
|
84739
|
-
const runsDir =
|
|
84363
|
+
const runsDir = join52(featureDir, "runs");
|
|
84740
84364
|
mkdirSync7(runsDir, { recursive: true });
|
|
84741
84365
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
84742
|
-
const logFilePath =
|
|
84366
|
+
const logFilePath = join52(runsDir, `${runId}.jsonl`);
|
|
84743
84367
|
const isTTY = process.stdout.isTTY ?? false;
|
|
84744
84368
|
const headlessFlag = options.headless ?? false;
|
|
84745
84369
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -84755,7 +84379,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
84755
84379
|
config2.autoMode.defaultAgent = options.agent;
|
|
84756
84380
|
}
|
|
84757
84381
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
84758
|
-
const globalNaxDir =
|
|
84382
|
+
const globalNaxDir = join52(homedir8(), ".nax");
|
|
84759
84383
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
84760
84384
|
const eventEmitter = new PipelineEventEmitter;
|
|
84761
84385
|
let tuiInstance;
|
|
@@ -84778,7 +84402,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
84778
84402
|
} else {
|
|
84779
84403
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
84780
84404
|
}
|
|
84781
|
-
const statusFilePath =
|
|
84405
|
+
const statusFilePath = join52(workdir, ".nax", "status.json");
|
|
84782
84406
|
let parallel;
|
|
84783
84407
|
if (options.parallel !== undefined) {
|
|
84784
84408
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -84804,7 +84428,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
84804
84428
|
headless: useHeadless,
|
|
84805
84429
|
skipPrecheck: options.skipPrecheck ?? false
|
|
84806
84430
|
});
|
|
84807
|
-
const latestSymlink =
|
|
84431
|
+
const latestSymlink = join52(runsDir, "latest.jsonl");
|
|
84808
84432
|
try {
|
|
84809
84433
|
if (existsSync31(latestSymlink)) {
|
|
84810
84434
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -84842,9 +84466,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
84842
84466
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
84843
84467
|
process.exit(1);
|
|
84844
84468
|
}
|
|
84845
|
-
const featureDir =
|
|
84469
|
+
const featureDir = join52(naxDir, "features", name);
|
|
84846
84470
|
mkdirSync7(featureDir, { recursive: true });
|
|
84847
|
-
await Bun.write(
|
|
84471
|
+
await Bun.write(join52(featureDir, "spec.md"), `# Feature: ${name}
|
|
84848
84472
|
|
|
84849
84473
|
## Overview
|
|
84850
84474
|
|
|
@@ -84877,7 +84501,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
84877
84501
|
|
|
84878
84502
|
<!-- What this feature explicitly does NOT cover. -->
|
|
84879
84503
|
`);
|
|
84880
|
-
await Bun.write(
|
|
84504
|
+
await Bun.write(join52(featureDir, "progress.txt"), `# Progress: ${name}
|
|
84881
84505
|
|
|
84882
84506
|
Created: ${new Date().toISOString()}
|
|
84883
84507
|
|
|
@@ -84903,7 +84527,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
84903
84527
|
console.error(source_default.red("nax not initialized."));
|
|
84904
84528
|
process.exit(1);
|
|
84905
84529
|
}
|
|
84906
|
-
const featuresDir =
|
|
84530
|
+
const featuresDir = join52(naxDir, "features");
|
|
84907
84531
|
if (!existsSync31(featuresDir)) {
|
|
84908
84532
|
console.log(source_default.dim("No features yet."));
|
|
84909
84533
|
return;
|
|
@@ -84918,7 +84542,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
84918
84542
|
Features:
|
|
84919
84543
|
`));
|
|
84920
84544
|
for (const name of entries) {
|
|
84921
|
-
const prdPath =
|
|
84545
|
+
const prdPath = join52(featuresDir, name, "prd.json");
|
|
84922
84546
|
if (existsSync31(prdPath)) {
|
|
84923
84547
|
const prd = await loadPRD(prdPath);
|
|
84924
84548
|
const c = countStories(prd);
|
|
@@ -84953,10 +84577,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
84953
84577
|
cliOverrides.profile = options.profile;
|
|
84954
84578
|
}
|
|
84955
84579
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
84956
|
-
const featureLogDir =
|
|
84580
|
+
const featureLogDir = join52(naxDir, "features", options.feature, "plan");
|
|
84957
84581
|
mkdirSync7(featureLogDir, { recursive: true });
|
|
84958
84582
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
84959
|
-
const planLogPath =
|
|
84583
|
+
const planLogPath = join52(featureLogDir, `${planLogId}.jsonl`);
|
|
84960
84584
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
84961
84585
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
84962
84586
|
try {
|