@nathapp/nax 0.50.2 → 0.50.3
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 +191 -181
- package/package.json +1 -1
- package/src/acceptance/generator.ts +57 -65
- package/src/acceptance/types.ts +3 -0
- package/src/cli/config-descriptions.ts +1 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/runtime-types.ts +2 -0
- package/src/config/schemas.ts +1 -0
- package/src/execution/lifecycle/acceptance-loop.ts +29 -0
- package/src/pipeline/stages/acceptance-setup.ts +4 -0
package/dist/nax.js
CHANGED
|
@@ -17874,7 +17874,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17874
17874
|
refinement: exports_external.boolean().default(true),
|
|
17875
17875
|
redGate: exports_external.boolean().default(true),
|
|
17876
17876
|
testStrategy: exports_external.enum(["unit", "component", "cli", "e2e", "snapshot"]).optional(),
|
|
17877
|
-
testFramework: exports_external.string().min(1, "acceptance.testFramework must be non-empty").optional()
|
|
17877
|
+
testFramework: exports_external.string().min(1, "acceptance.testFramework must be non-empty").optional(),
|
|
17878
|
+
timeoutMs: exports_external.number().int().min(30000).max(3600000).default(1800000)
|
|
17878
17879
|
});
|
|
17879
17880
|
TestCoverageConfigSchema = exports_external.object({
|
|
17880
17881
|
enabled: exports_external.boolean().default(true),
|
|
@@ -18163,7 +18164,8 @@ var init_defaults = __esm(() => {
|
|
|
18163
18164
|
testPath: "acceptance.test.ts",
|
|
18164
18165
|
model: "fast",
|
|
18165
18166
|
refinement: true,
|
|
18166
|
-
redGate: true
|
|
18167
|
+
redGate: true,
|
|
18168
|
+
timeoutMs: 1800000
|
|
18167
18169
|
},
|
|
18168
18170
|
context: {
|
|
18169
18171
|
fileInjection: "disabled",
|
|
@@ -18725,32 +18727,48 @@ async function generateFromPRD(_stories, refinedCriteria, options) {
|
|
|
18725
18727
|
}
|
|
18726
18728
|
const criteriaList = refinedCriteria.map((c, i) => `AC-${i + 1}: ${c.refined}`).join(`
|
|
18727
18729
|
`);
|
|
18728
|
-
const
|
|
18729
|
-
|
|
18730
|
+
const frameworkOverrideLine = options.testFramework ? `
|
|
18731
|
+
[FRAMEWORK OVERRIDE: Use ${options.testFramework} as the test framework regardless of what you detect.]` : "";
|
|
18732
|
+
const basePrompt = `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${options.featureName}" feature.
|
|
18730
18733
|
|
|
18731
|
-
|
|
18732
|
-
${options.codebaseContext}
|
|
18734
|
+
## Step 1: Understand and Classify the Acceptance Criteria
|
|
18733
18735
|
|
|
18734
|
-
|
|
18736
|
+
Read each AC below and classify its verification type:
|
|
18737
|
+
- **file-check**: Verify by reading source files (e.g. "no @nestjs/jwt imports", "file exists", "module registered", "uses registerAs pattern")
|
|
18738
|
+
- **runtime-check**: Load and invoke code directly, assert on return values or behavior
|
|
18739
|
+
- **integration-check**: Requires a running service (e.g. HTTP endpoint returns 200, 11th request returns 429, database query succeeds)
|
|
18740
|
+
|
|
18741
|
+
ACCEPTANCE CRITERIA:
|
|
18735
18742
|
${criteriaList}
|
|
18736
18743
|
|
|
18737
|
-
|
|
18744
|
+
## Step 2: Explore the Project
|
|
18738
18745
|
|
|
18739
|
-
|
|
18746
|
+
Before writing any tests, examine the project to understand:
|
|
18747
|
+
1. **Language and test framework** \u2014 check dependency manifests (package.json, go.mod, Gemfile, pyproject.toml, Cargo.toml, build.gradle, etc.) to identify the language and test runner
|
|
18748
|
+
2. **Existing test patterns** \u2014 read 1-2 existing test files to understand import style, describe/test/it conventions, and available helpers
|
|
18749
|
+
3. **Project structure** \u2014 identify relevant source directories to determine correct import or load paths
|
|
18740
18750
|
|
|
18741
|
-
|
|
18751
|
+
${frameworkOverrideLine}
|
|
18742
18752
|
|
|
18743
|
-
|
|
18744
|
-
test("AC-1: <description>", async () => {
|
|
18745
|
-
// Test implementation
|
|
18746
|
-
});
|
|
18747
|
-
});
|
|
18753
|
+
## Step 3: Generate the Acceptance Test File
|
|
18748
18754
|
|
|
18749
|
-
|
|
18755
|
+
Write the complete acceptance test file using the framework identified in Step 2.
|
|
18756
|
+
|
|
18757
|
+
Rules:
|
|
18758
|
+
- **One test per AC**, named exactly "AC-N: <description>"
|
|
18759
|
+
- **file-check ACs** \u2192 read source files using the language's standard file I/O, assert with string or regex checks. Do not start the application.
|
|
18760
|
+
- **runtime-check ACs** \u2192 load or import the module directly and invoke it, assert on the return value or observable side effects
|
|
18761
|
+
- **integration-check ACs** \u2192 use the language's HTTP client or existing test helpers; add a clear setup block (beforeAll/setup/TestMain/etc.) explaining what must be running
|
|
18762
|
+
- **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
18763
|
+
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18764
|
+
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration`;
|
|
18765
|
+
const prompt = basePrompt;
|
|
18750
18766
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
18751
|
-
const rawOutput = await _generatorPRDDeps.adapter.complete(prompt, {
|
|
18767
|
+
const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
18752
18768
|
model: options.modelDef.model,
|
|
18753
|
-
config: options.config
|
|
18769
|
+
config: options.config,
|
|
18770
|
+
timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
|
|
18771
|
+
workdir: options.workdir
|
|
18754
18772
|
});
|
|
18755
18773
|
const testCode = extractTestCode(rawOutput);
|
|
18756
18774
|
if (!testCode) {
|
|
@@ -18774,40 +18792,6 @@ IMPORTANT: Output raw TypeScript code only. Do NOT use markdown code fences (\`\
|
|
|
18774
18792
|
await _generatorPRDDeps.writeFile(join2(options.featureDir, "acceptance-refined.json"), refinedJsonContent);
|
|
18775
18793
|
return { testCode, criteria };
|
|
18776
18794
|
}
|
|
18777
|
-
function buildStrategyInstructions(strategy, framework) {
|
|
18778
|
-
switch (strategy) {
|
|
18779
|
-
case "component": {
|
|
18780
|
-
const fw = framework ?? "ink-testing-library";
|
|
18781
|
-
if (fw === "react") {
|
|
18782
|
-
return `TEST STRATEGY: component (react)
|
|
18783
|
-
Import render and screen from @testing-library/react. Render the component and use screen.getByText to assert on output.
|
|
18784
|
-
|
|
18785
|
-
`;
|
|
18786
|
-
}
|
|
18787
|
-
return `TEST STRATEGY: component (ink-testing-library)
|
|
18788
|
-
Import render from ink-testing-library. Render the component and use lastFrame() to assert on output.
|
|
18789
|
-
|
|
18790
|
-
`;
|
|
18791
|
-
}
|
|
18792
|
-
case "cli":
|
|
18793
|
-
return `TEST STRATEGY: cli
|
|
18794
|
-
Use Bun.spawn to run the binary. Read stdout and assert on the text output.
|
|
18795
|
-
|
|
18796
|
-
`;
|
|
18797
|
-
case "e2e":
|
|
18798
|
-
return `TEST STRATEGY: e2e
|
|
18799
|
-
Use fetch() against http://localhost to call the running service. Assert on response body using response.text() or response.json().
|
|
18800
|
-
|
|
18801
|
-
`;
|
|
18802
|
-
case "snapshot":
|
|
18803
|
-
return `TEST STRATEGY: snapshot
|
|
18804
|
-
Render the component and use toMatchSnapshot() to capture and compare snapshots.
|
|
18805
|
-
|
|
18806
|
-
`;
|
|
18807
|
-
default:
|
|
18808
|
-
return "";
|
|
18809
|
-
}
|
|
18810
|
-
}
|
|
18811
18795
|
function parseAcceptanceCriteria(specContent) {
|
|
18812
18796
|
const criteria = [];
|
|
18813
18797
|
const lines = specContent.split(`
|
|
@@ -18831,46 +18815,38 @@ function parseAcceptanceCriteria(specContent) {
|
|
|
18831
18815
|
function buildAcceptanceTestPrompt(criteria, featureName, codebaseContext) {
|
|
18832
18816
|
const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join(`
|
|
18833
18817
|
`);
|
|
18834
|
-
return `You are a test engineer.
|
|
18818
|
+
return `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${featureName}" feature.
|
|
18835
18819
|
|
|
18836
|
-
|
|
18837
|
-
|
|
18820
|
+
## Step 1: Understand and Classify the Acceptance Criteria
|
|
18821
|
+
|
|
18822
|
+
Read each AC below and classify its verification type:
|
|
18823
|
+
- **file-check**: Verify by reading source files (e.g. "no @nestjs/jwt imports", "file exists", "module registered", "uses registerAs pattern")
|
|
18824
|
+
- **runtime-check**: Load and invoke code directly, assert on return values or behavior
|
|
18825
|
+
- **integration-check**: Requires a running service (e.g. HTTP endpoint returns 200, 11th request returns 429, database query succeeds)
|
|
18838
18826
|
|
|
18839
18827
|
ACCEPTANCE CRITERIA:
|
|
18840
18828
|
${criteriaList}
|
|
18841
18829
|
|
|
18842
|
-
|
|
18843
|
-
|
|
18844
|
-
1. **One test per AC**: Each acceptance criterion maps to exactly one test
|
|
18845
|
-
2. **Test observable behavior only**: No implementation details, only user-facing behavior
|
|
18846
|
-
3. **Independent tests**: No shared state between tests
|
|
18847
|
-
4. **Real-implementation**: Tests should use real implementations without mocking (test observable behavior, not internal units)
|
|
18848
|
-
5. **Clear test names**: Use format "AC-N: <description>" for test names
|
|
18849
|
-
6. **Async where needed**: Use async/await for operations that may be asynchronous
|
|
18830
|
+
## Step 2: Explore the Project
|
|
18850
18831
|
|
|
18851
|
-
|
|
18832
|
+
Before writing any tests, examine the project to understand:
|
|
18833
|
+
1. **Language and test framework** \u2014 check dependency manifests (package.json, go.mod, Gemfile, pyproject.toml, Cargo.toml, build.gradle, etc.) to identify the language and test runner
|
|
18834
|
+
2. **Existing test patterns** \u2014 read 1-2 existing test files to understand import style, describe/test/it conventions, and available helpers
|
|
18835
|
+
3. **Project structure** \u2014 identify relevant source directories to determine correct import or load paths
|
|
18852
18836
|
|
|
18853
|
-
\`\`\`typescript
|
|
18854
|
-
import { describe, test, expect } from "bun:test";
|
|
18855
|
-
|
|
18856
|
-
describe("${featureName} - Acceptance Tests", () => {
|
|
18857
|
-
test("AC-1: <description>", async () => {
|
|
18858
|
-
// Test implementation
|
|
18859
|
-
});
|
|
18860
18837
|
|
|
18861
|
-
|
|
18862
|
-
// Test implementation
|
|
18863
|
-
});
|
|
18864
|
-
});
|
|
18865
|
-
\`\`\`
|
|
18838
|
+
## Step 3: Generate the Acceptance Test File
|
|
18866
18839
|
|
|
18867
|
-
|
|
18868
|
-
- Import the feature code being tested
|
|
18869
|
-
- Set up any necessary test fixtures
|
|
18870
|
-
- Use expect() assertions to verify behavior
|
|
18871
|
-
- Clean up resources if needed (close connections, delete temp files)
|
|
18840
|
+
Write the complete acceptance test file using the framework identified in Step 2.
|
|
18872
18841
|
|
|
18873
|
-
|
|
18842
|
+
Rules:
|
|
18843
|
+
- **One test per AC**, named exactly "AC-N: <description>"
|
|
18844
|
+
- **file-check ACs** \u2192 read source files using the language's standard file I/O, assert with string or regex checks. Do not start the application.
|
|
18845
|
+
- **runtime-check ACs** \u2192 load or import the module directly and invoke it, assert on the return value or observable side effects
|
|
18846
|
+
- **integration-check ACs** \u2192 use the language's HTTP client or existing test helpers; add a clear setup block (beforeAll/setup/TestMain/etc.) explaining what must be running
|
|
18847
|
+
- **NEVER use placeholder assertions** \u2014 no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
18848
|
+
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18849
|
+
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration`;
|
|
18874
18850
|
}
|
|
18875
18851
|
async function generateAcceptanceTests(adapter, options) {
|
|
18876
18852
|
const logger = getLogger();
|
|
@@ -18887,7 +18863,9 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18887
18863
|
try {
|
|
18888
18864
|
const output = await adapter.complete(prompt, {
|
|
18889
18865
|
model: options.modelDef.model,
|
|
18890
|
-
config: options.config
|
|
18866
|
+
config: options.config,
|
|
18867
|
+
timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
|
|
18868
|
+
workdir: options.workdir
|
|
18891
18869
|
});
|
|
18892
18870
|
const testCode = extractTestCode(output);
|
|
18893
18871
|
if (!testCode) {
|
|
@@ -22351,7 +22329,7 @@ var package_default;
|
|
|
22351
22329
|
var init_package = __esm(() => {
|
|
22352
22330
|
package_default = {
|
|
22353
22331
|
name: "@nathapp/nax",
|
|
22354
|
-
version: "0.50.
|
|
22332
|
+
version: "0.50.3",
|
|
22355
22333
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22356
22334
|
type: "module",
|
|
22357
22335
|
bin: {
|
|
@@ -22425,8 +22403,8 @@ var init_version = __esm(() => {
|
|
|
22425
22403
|
NAX_VERSION = package_default.version;
|
|
22426
22404
|
NAX_COMMIT = (() => {
|
|
22427
22405
|
try {
|
|
22428
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22429
|
-
return "
|
|
22406
|
+
if (/^[0-9a-f]{6,10}$/.test("684b48b"))
|
|
22407
|
+
return "684b48b";
|
|
22430
22408
|
} catch {}
|
|
22431
22409
|
try {
|
|
22432
22410
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -24180,7 +24158,105 @@ ${stderr}`;
|
|
|
24180
24158
|
};
|
|
24181
24159
|
});
|
|
24182
24160
|
|
|
24161
|
+
// src/agents/shared/validation.ts
|
|
24162
|
+
function validateAgentForTier(agent, tier) {
|
|
24163
|
+
return agent.capabilities.supportedTiers.includes(tier);
|
|
24164
|
+
}
|
|
24165
|
+
function validateAgentFeature(agent, feature) {
|
|
24166
|
+
return agent.capabilities.features.has(feature);
|
|
24167
|
+
}
|
|
24168
|
+
function describeAgentCapabilities(agent) {
|
|
24169
|
+
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
24170
|
+
const features = Array.from(agent.capabilities.features).join(",");
|
|
24171
|
+
const maxTokens = agent.capabilities.maxContextTokens;
|
|
24172
|
+
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
24173
|
+
}
|
|
24174
|
+
|
|
24175
|
+
// src/agents/shared/version-detection.ts
|
|
24176
|
+
async function getAgentVersion(binaryName) {
|
|
24177
|
+
try {
|
|
24178
|
+
const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
|
|
24179
|
+
stdout: "pipe",
|
|
24180
|
+
stderr: "pipe"
|
|
24181
|
+
});
|
|
24182
|
+
const exitCode = await proc.exited;
|
|
24183
|
+
if (exitCode !== 0) {
|
|
24184
|
+
return null;
|
|
24185
|
+
}
|
|
24186
|
+
const stdout = await new Response(proc.stdout).text();
|
|
24187
|
+
const versionLine = stdout.trim().split(`
|
|
24188
|
+
`)[0];
|
|
24189
|
+
const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
|
|
24190
|
+
if (versionMatch) {
|
|
24191
|
+
return versionMatch[0];
|
|
24192
|
+
}
|
|
24193
|
+
return versionLine || null;
|
|
24194
|
+
} catch {
|
|
24195
|
+
return null;
|
|
24196
|
+
}
|
|
24197
|
+
}
|
|
24198
|
+
async function getAgentVersions() {
|
|
24199
|
+
const agents = await getInstalledAgents();
|
|
24200
|
+
const agentsByName = new Map(agents.map((a) => [a.name, a]));
|
|
24201
|
+
const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
24202
|
+
const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
|
|
24203
|
+
const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
|
|
24204
|
+
return {
|
|
24205
|
+
name: agent.name,
|
|
24206
|
+
displayName: agent.displayName,
|
|
24207
|
+
version: version2,
|
|
24208
|
+
installed: agentsByName.has(agent.name)
|
|
24209
|
+
};
|
|
24210
|
+
}));
|
|
24211
|
+
return versions2;
|
|
24212
|
+
}
|
|
24213
|
+
var _versionDetectionDeps;
|
|
24214
|
+
var init_version_detection = __esm(() => {
|
|
24215
|
+
init_registry();
|
|
24216
|
+
_versionDetectionDeps = {
|
|
24217
|
+
spawn(cmd, opts) {
|
|
24218
|
+
return Bun.spawn(cmd, opts);
|
|
24219
|
+
}
|
|
24220
|
+
};
|
|
24221
|
+
});
|
|
24222
|
+
|
|
24223
|
+
// src/agents/index.ts
|
|
24224
|
+
var exports_agents = {};
|
|
24225
|
+
__export(exports_agents, {
|
|
24226
|
+
validateAgentForTier: () => validateAgentForTier,
|
|
24227
|
+
validateAgentFeature: () => validateAgentFeature,
|
|
24228
|
+
parseTokenUsage: () => parseTokenUsage,
|
|
24229
|
+
getInstalledAgents: () => getInstalledAgents,
|
|
24230
|
+
getAllAgentNames: () => getAllAgentNames,
|
|
24231
|
+
getAgentVersions: () => getAgentVersions,
|
|
24232
|
+
getAgentVersion: () => getAgentVersion,
|
|
24233
|
+
getAgent: () => getAgent,
|
|
24234
|
+
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
24235
|
+
estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
|
|
24236
|
+
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
24237
|
+
estimateCostByDuration: () => estimateCostByDuration,
|
|
24238
|
+
estimateCost: () => estimateCost,
|
|
24239
|
+
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
24240
|
+
checkAgentHealth: () => checkAgentHealth,
|
|
24241
|
+
MODEL_PRICING: () => MODEL_PRICING,
|
|
24242
|
+
CompleteError: () => CompleteError,
|
|
24243
|
+
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
24244
|
+
COST_RATES: () => COST_RATES
|
|
24245
|
+
});
|
|
24246
|
+
var init_agents = __esm(() => {
|
|
24247
|
+
init_types2();
|
|
24248
|
+
init_claude();
|
|
24249
|
+
init_registry();
|
|
24250
|
+
init_cost();
|
|
24251
|
+
init_version_detection();
|
|
24252
|
+
});
|
|
24253
|
+
|
|
24183
24254
|
// src/pipeline/stages/acceptance-setup.ts
|
|
24255
|
+
var exports_acceptance_setup = {};
|
|
24256
|
+
__export(exports_acceptance_setup, {
|
|
24257
|
+
acceptanceSetupStage: () => acceptanceSetupStage,
|
|
24258
|
+
_acceptanceSetupDeps: () => _acceptanceSetupDeps
|
|
24259
|
+
});
|
|
24184
24260
|
import path5 from "path";
|
|
24185
24261
|
var _acceptanceSetupDeps, acceptanceSetupStage;
|
|
24186
24262
|
var init_acceptance_setup = __esm(() => {
|
|
@@ -24232,6 +24308,8 @@ ${stderr}` };
|
|
|
24232
24308
|
if (!fileExists) {
|
|
24233
24309
|
const allCriteria = ctx.prd.userStories.flatMap((s) => s.acceptanceCriteria);
|
|
24234
24310
|
totalCriteria = allCriteria.length;
|
|
24311
|
+
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
24312
|
+
const agent = (ctx.agentGetFn ?? getAgent2)(ctx.config.autoMode.defaultAgent);
|
|
24235
24313
|
let refinedCriteria;
|
|
24236
24314
|
if (ctx.config.acceptance.refinement) {
|
|
24237
24315
|
refinedCriteria = await _acceptanceSetupDeps.refine(allCriteria, {
|
|
@@ -24259,7 +24337,8 @@ ${stderr}` };
|
|
|
24259
24337
|
modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
|
|
24260
24338
|
config: ctx.config,
|
|
24261
24339
|
testStrategy: ctx.config.acceptance.testStrategy,
|
|
24262
|
-
testFramework: ctx.config.acceptance.testFramework
|
|
24340
|
+
testFramework: ctx.config.acceptance.testFramework,
|
|
24341
|
+
adapter: agent ?? undefined
|
|
24263
24342
|
});
|
|
24264
24343
|
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
24265
24344
|
}
|
|
@@ -24281,99 +24360,6 @@ ${stderr}` };
|
|
|
24281
24360
|
};
|
|
24282
24361
|
});
|
|
24283
24362
|
|
|
24284
|
-
// src/agents/shared/validation.ts
|
|
24285
|
-
function validateAgentForTier(agent, tier) {
|
|
24286
|
-
return agent.capabilities.supportedTiers.includes(tier);
|
|
24287
|
-
}
|
|
24288
|
-
function validateAgentFeature(agent, feature) {
|
|
24289
|
-
return agent.capabilities.features.has(feature);
|
|
24290
|
-
}
|
|
24291
|
-
function describeAgentCapabilities(agent) {
|
|
24292
|
-
const tiers = agent.capabilities.supportedTiers.join(",");
|
|
24293
|
-
const features = Array.from(agent.capabilities.features).join(",");
|
|
24294
|
-
const maxTokens = agent.capabilities.maxContextTokens;
|
|
24295
|
-
return `${agent.name}: tiers=[${tiers}], maxTokens=${maxTokens}, features=[${features}]`;
|
|
24296
|
-
}
|
|
24297
|
-
|
|
24298
|
-
// src/agents/shared/version-detection.ts
|
|
24299
|
-
async function getAgentVersion(binaryName) {
|
|
24300
|
-
try {
|
|
24301
|
-
const proc = _versionDetectionDeps.spawn([binaryName, "--version"], {
|
|
24302
|
-
stdout: "pipe",
|
|
24303
|
-
stderr: "pipe"
|
|
24304
|
-
});
|
|
24305
|
-
const exitCode = await proc.exited;
|
|
24306
|
-
if (exitCode !== 0) {
|
|
24307
|
-
return null;
|
|
24308
|
-
}
|
|
24309
|
-
const stdout = await new Response(proc.stdout).text();
|
|
24310
|
-
const versionLine = stdout.trim().split(`
|
|
24311
|
-
`)[0];
|
|
24312
|
-
const versionMatch = versionLine.match(/v?(\d+\.\d+(?:\.\d+)?(?:[-+][\w.]+)?)/);
|
|
24313
|
-
if (versionMatch) {
|
|
24314
|
-
return versionMatch[0];
|
|
24315
|
-
}
|
|
24316
|
-
return versionLine || null;
|
|
24317
|
-
} catch {
|
|
24318
|
-
return null;
|
|
24319
|
-
}
|
|
24320
|
-
}
|
|
24321
|
-
async function getAgentVersions() {
|
|
24322
|
-
const agents = await getInstalledAgents();
|
|
24323
|
-
const agentsByName = new Map(agents.map((a) => [a.name, a]));
|
|
24324
|
-
const { ALL_AGENTS: ALL_AGENTS2 } = await Promise.resolve().then(() => (init_registry(), exports_registry));
|
|
24325
|
-
const versions2 = await Promise.all(ALL_AGENTS2.map(async (agent) => {
|
|
24326
|
-
const version2 = agentsByName.has(agent.name) ? await getAgentVersion(agent.binary) : null;
|
|
24327
|
-
return {
|
|
24328
|
-
name: agent.name,
|
|
24329
|
-
displayName: agent.displayName,
|
|
24330
|
-
version: version2,
|
|
24331
|
-
installed: agentsByName.has(agent.name)
|
|
24332
|
-
};
|
|
24333
|
-
}));
|
|
24334
|
-
return versions2;
|
|
24335
|
-
}
|
|
24336
|
-
var _versionDetectionDeps;
|
|
24337
|
-
var init_version_detection = __esm(() => {
|
|
24338
|
-
init_registry();
|
|
24339
|
-
_versionDetectionDeps = {
|
|
24340
|
-
spawn(cmd, opts) {
|
|
24341
|
-
return Bun.spawn(cmd, opts);
|
|
24342
|
-
}
|
|
24343
|
-
};
|
|
24344
|
-
});
|
|
24345
|
-
|
|
24346
|
-
// src/agents/index.ts
|
|
24347
|
-
var exports_agents = {};
|
|
24348
|
-
__export(exports_agents, {
|
|
24349
|
-
validateAgentForTier: () => validateAgentForTier,
|
|
24350
|
-
validateAgentFeature: () => validateAgentFeature,
|
|
24351
|
-
parseTokenUsage: () => parseTokenUsage,
|
|
24352
|
-
getInstalledAgents: () => getInstalledAgents,
|
|
24353
|
-
getAllAgentNames: () => getAllAgentNames,
|
|
24354
|
-
getAgentVersions: () => getAgentVersions,
|
|
24355
|
-
getAgentVersion: () => getAgentVersion,
|
|
24356
|
-
getAgent: () => getAgent,
|
|
24357
|
-
formatCostWithConfidence: () => formatCostWithConfidence,
|
|
24358
|
-
estimateCostFromTokenUsage: () => estimateCostFromTokenUsage,
|
|
24359
|
-
estimateCostFromOutput: () => estimateCostFromOutput,
|
|
24360
|
-
estimateCostByDuration: () => estimateCostByDuration,
|
|
24361
|
-
estimateCost: () => estimateCost,
|
|
24362
|
-
describeAgentCapabilities: () => describeAgentCapabilities,
|
|
24363
|
-
checkAgentHealth: () => checkAgentHealth,
|
|
24364
|
-
MODEL_PRICING: () => MODEL_PRICING,
|
|
24365
|
-
CompleteError: () => CompleteError,
|
|
24366
|
-
ClaudeCodeAdapter: () => ClaudeCodeAdapter,
|
|
24367
|
-
COST_RATES: () => COST_RATES
|
|
24368
|
-
});
|
|
24369
|
-
var init_agents = __esm(() => {
|
|
24370
|
-
init_types2();
|
|
24371
|
-
init_claude();
|
|
24372
|
-
init_registry();
|
|
24373
|
-
init_cost();
|
|
24374
|
-
init_version_detection();
|
|
24375
|
-
});
|
|
24376
|
-
|
|
24377
24363
|
// src/pipeline/event-bus.ts
|
|
24378
24364
|
class PipelineEventBus {
|
|
24379
24365
|
subscribers = new Map;
|
|
@@ -32201,9 +32187,13 @@ var init_crash_recovery = __esm(() => {
|
|
|
32201
32187
|
// src/execution/lifecycle/acceptance-loop.ts
|
|
32202
32188
|
var exports_acceptance_loop = {};
|
|
32203
32189
|
__export(exports_acceptance_loop, {
|
|
32204
|
-
runAcceptanceLoop: () => runAcceptanceLoop
|
|
32190
|
+
runAcceptanceLoop: () => runAcceptanceLoop,
|
|
32191
|
+
isStubTestFile: () => isStubTestFile
|
|
32205
32192
|
});
|
|
32206
32193
|
import path14 from "path";
|
|
32194
|
+
function isStubTestFile(content) {
|
|
32195
|
+
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
32196
|
+
}
|
|
32207
32197
|
async function loadSpecContent(featureDir) {
|
|
32208
32198
|
if (!featureDir)
|
|
32209
32199
|
return "";
|
|
@@ -32337,6 +32327,25 @@ async function runAcceptanceLoop(ctx) {
|
|
|
32337
32327
|
}), ctx.workdir);
|
|
32338
32328
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32339
32329
|
}
|
|
32330
|
+
if (ctx.featureDir) {
|
|
32331
|
+
const testPath = path14.join(ctx.featureDir, "acceptance.test.ts");
|
|
32332
|
+
const testFile = Bun.file(testPath);
|
|
32333
|
+
if (await testFile.exists()) {
|
|
32334
|
+
const testContent = await testFile.text();
|
|
32335
|
+
if (isStubTestFile(testContent)) {
|
|
32336
|
+
logger?.warn("acceptance", "Stub tests detected \u2014 re-generating acceptance tests");
|
|
32337
|
+
const { unlink: unlink3 } = await import("fs/promises");
|
|
32338
|
+
await unlink3(testPath);
|
|
32339
|
+
const { acceptanceSetupStage: acceptanceSetupStage2 } = await Promise.resolve().then(() => (init_acceptance_setup(), exports_acceptance_setup));
|
|
32340
|
+
await acceptanceSetupStage2.execute(acceptanceContext);
|
|
32341
|
+
const newContent = await Bun.file(testPath).text();
|
|
32342
|
+
if (isStubTestFile(newContent)) {
|
|
32343
|
+
logger?.error("acceptance", "Acceptance test generation failed after retry \u2014 manual implementation required");
|
|
32344
|
+
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
32345
|
+
}
|
|
32346
|
+
}
|
|
32347
|
+
}
|
|
32348
|
+
}
|
|
32340
32349
|
logger?.info("acceptance", "Generating fix stories...");
|
|
32341
32350
|
const fixStories = await generateAndAddFixStories(ctx, failures, prd);
|
|
32342
32351
|
if (!fixStories) {
|
|
@@ -69630,6 +69639,7 @@ var FIELD_DESCRIPTIONS = {
|
|
|
69630
69639
|
"acceptance.maxRetries": "Max retry loops for fix stories",
|
|
69631
69640
|
"acceptance.generateTests": "Generate acceptance tests during analyze",
|
|
69632
69641
|
"acceptance.testPath": "Path to acceptance test file (relative to feature dir)",
|
|
69642
|
+
"acceptance.timeoutMs": "Timeout for acceptance test generation in milliseconds (default: 1800000 = 30 min)",
|
|
69633
69643
|
context: "Context injection configuration",
|
|
69634
69644
|
"context.fileInjection": "Mode: 'disabled' (default, MCP-aware agents pull context on-demand) | 'keyword' (legacy git-grep injection for non-MCP agents). Set context.fileInjection in config.",
|
|
69635
69645
|
"context.testCoverage": "Test coverage context settings",
|
package/package.json
CHANGED
|
@@ -82,35 +82,53 @@ export async function generateFromPRD(
|
|
|
82
82
|
|
|
83
83
|
const criteriaList = refinedCriteria.map((c, i) => `AC-${i + 1}: ${c.refined}`).join("\n");
|
|
84
84
|
|
|
85
|
-
const
|
|
85
|
+
const frameworkOverrideLine = options.testFramework
|
|
86
|
+
? `\n[FRAMEWORK OVERRIDE: Use ${options.testFramework} as the test framework regardless of what you detect.]`
|
|
87
|
+
: "";
|
|
86
88
|
|
|
87
|
-
const
|
|
89
|
+
const basePrompt = `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${options.featureName}" feature.
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
${options.codebaseContext}
|
|
91
|
+
## Step 1: Understand and Classify the Acceptance Criteria
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
Read each AC below and classify its verification type:
|
|
94
|
+
- **file-check**: Verify by reading source files (e.g. "no @nestjs/jwt imports", "file exists", "module registered", "uses registerAs pattern")
|
|
95
|
+
- **runtime-check**: Load and invoke code directly, assert on return values or behavior
|
|
96
|
+
- **integration-check**: Requires a running service (e.g. HTTP endpoint returns 200, 11th request returns 429, database query succeeds)
|
|
97
|
+
|
|
98
|
+
ACCEPTANCE CRITERIA:
|
|
93
99
|
${criteriaList}
|
|
94
100
|
|
|
95
|
-
|
|
101
|
+
## Step 2: Explore the Project
|
|
96
102
|
|
|
97
|
-
|
|
103
|
+
Before writing any tests, examine the project to understand:
|
|
104
|
+
1. **Language and test framework** — check dependency manifests (package.json, go.mod, Gemfile, pyproject.toml, Cargo.toml, build.gradle, etc.) to identify the language and test runner
|
|
105
|
+
2. **Existing test patterns** — read 1-2 existing test files to understand import style, describe/test/it conventions, and available helpers
|
|
106
|
+
3. **Project structure** — identify relevant source directories to determine correct import or load paths
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
${frameworkOverrideLine}
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
});
|
|
105
|
-
});
|
|
110
|
+
## Step 3: Generate the Acceptance Test File
|
|
111
|
+
|
|
112
|
+
Write the complete acceptance test file using the framework identified in Step 2.
|
|
106
113
|
|
|
107
|
-
|
|
114
|
+
Rules:
|
|
115
|
+
- **One test per AC**, named exactly "AC-N: <description>"
|
|
116
|
+
- **file-check ACs** → read source files using the language's standard file I/O, assert with string or regex checks. Do not start the application.
|
|
117
|
+
- **runtime-check ACs** → load or import the module directly and invoke it, assert on the return value or observable side effects
|
|
118
|
+
- **integration-check ACs** → use the language's HTTP client or existing test helpers; add a clear setup block (beforeAll/setup/TestMain/etc.) explaining what must be running
|
|
119
|
+
- **NEVER use placeholder assertions** — no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
120
|
+
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
121
|
+
- Output raw code only — no markdown fences, start directly with the language's import or package declaration`;
|
|
122
|
+
|
|
123
|
+
const prompt = basePrompt;
|
|
108
124
|
|
|
109
125
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
110
126
|
|
|
111
|
-
const rawOutput = await _generatorPRDDeps.adapter.complete(prompt, {
|
|
127
|
+
const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
112
128
|
model: options.modelDef.model,
|
|
113
129
|
config: options.config,
|
|
130
|
+
timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
|
|
131
|
+
workdir: options.workdir,
|
|
114
132
|
});
|
|
115
133
|
const testCode = extractTestCode(rawOutput);
|
|
116
134
|
|
|
@@ -143,26 +161,6 @@ IMPORTANT: Output raw TypeScript code only. Do NOT use markdown code fences (\`\
|
|
|
143
161
|
return { testCode, criteria };
|
|
144
162
|
}
|
|
145
163
|
|
|
146
|
-
function buildStrategyInstructions(strategy?: string, framework?: string): string {
|
|
147
|
-
switch (strategy) {
|
|
148
|
-
case "component": {
|
|
149
|
-
const fw = framework ?? "ink-testing-library";
|
|
150
|
-
if (fw === "react") {
|
|
151
|
-
return "TEST STRATEGY: component (react)\nImport render and screen from @testing-library/react. Render the component and use screen.getByText to assert on output.\n\n";
|
|
152
|
-
}
|
|
153
|
-
return "TEST STRATEGY: component (ink-testing-library)\nImport render from ink-testing-library. Render the component and use lastFrame() to assert on output.\n\n";
|
|
154
|
-
}
|
|
155
|
-
case "cli":
|
|
156
|
-
return "TEST STRATEGY: cli\nUse Bun.spawn to run the binary. Read stdout and assert on the text output.\n\n";
|
|
157
|
-
case "e2e":
|
|
158
|
-
return "TEST STRATEGY: e2e\nUse fetch() against http://localhost to call the running service. Assert on response body using response.text() or response.json().\n\n";
|
|
159
|
-
case "snapshot":
|
|
160
|
-
return "TEST STRATEGY: snapshot\nRender the component and use toMatchSnapshot() to capture and compare snapshots.\n\n";
|
|
161
|
-
default:
|
|
162
|
-
return "";
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
164
|
export function parseAcceptanceCriteria(specContent: string): AcceptanceCriterion[] {
|
|
167
165
|
const criteria: AcceptanceCriterion[] = [];
|
|
168
166
|
const lines = specContent.split("\n");
|
|
@@ -218,46 +216,38 @@ export function buildAcceptanceTestPrompt(
|
|
|
218
216
|
): string {
|
|
219
217
|
const criteriaList = criteria.map((ac) => `${ac.id}: ${ac.text}`).join("\n");
|
|
220
218
|
|
|
221
|
-
return `You are a test engineer.
|
|
219
|
+
return `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${featureName}" feature.
|
|
220
|
+
|
|
221
|
+
## Step 1: Understand and Classify the Acceptance Criteria
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
Read each AC below and classify its verification type:
|
|
224
|
+
- **file-check**: Verify by reading source files (e.g. "no @nestjs/jwt imports", "file exists", "module registered", "uses registerAs pattern")
|
|
225
|
+
- **runtime-check**: Load and invoke code directly, assert on return values or behavior
|
|
226
|
+
- **integration-check**: Requires a running service (e.g. HTTP endpoint returns 200, 11th request returns 429, database query succeeds)
|
|
225
227
|
|
|
226
228
|
ACCEPTANCE CRITERIA:
|
|
227
229
|
${criteriaList}
|
|
228
230
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
1. **One test per AC**: Each acceptance criterion maps to exactly one test
|
|
232
|
-
2. **Test observable behavior only**: No implementation details, only user-facing behavior
|
|
233
|
-
3. **Independent tests**: No shared state between tests
|
|
234
|
-
4. **Real-implementation**: Tests should use real implementations without mocking (test observable behavior, not internal units)
|
|
235
|
-
5. **Clear test names**: Use format "AC-N: <description>" for test names
|
|
236
|
-
6. **Async where needed**: Use async/await for operations that may be asynchronous
|
|
237
|
-
|
|
238
|
-
Use this structure:
|
|
231
|
+
## Step 2: Explore the Project
|
|
239
232
|
|
|
240
|
-
|
|
241
|
-
|
|
233
|
+
Before writing any tests, examine the project to understand:
|
|
234
|
+
1. **Language and test framework** — check dependency manifests (package.json, go.mod, Gemfile, pyproject.toml, Cargo.toml, build.gradle, etc.) to identify the language and test runner
|
|
235
|
+
2. **Existing test patterns** — read 1-2 existing test files to understand import style, describe/test/it conventions, and available helpers
|
|
236
|
+
3. **Project structure** — identify relevant source directories to determine correct import or load paths
|
|
242
237
|
|
|
243
|
-
describe("${featureName} - Acceptance Tests", () => {
|
|
244
|
-
test("AC-1: <description>", async () => {
|
|
245
|
-
// Test implementation
|
|
246
|
-
});
|
|
247
238
|
|
|
248
|
-
|
|
249
|
-
// Test implementation
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
\`\`\`
|
|
239
|
+
## Step 3: Generate the Acceptance Test File
|
|
253
240
|
|
|
254
|
-
|
|
255
|
-
- Import the feature code being tested
|
|
256
|
-
- Set up any necessary test fixtures
|
|
257
|
-
- Use expect() assertions to verify behavior
|
|
258
|
-
- Clean up resources if needed (close connections, delete temp files)
|
|
241
|
+
Write the complete acceptance test file using the framework identified in Step 2.
|
|
259
242
|
|
|
260
|
-
|
|
243
|
+
Rules:
|
|
244
|
+
- **One test per AC**, named exactly "AC-N: <description>"
|
|
245
|
+
- **file-check ACs** → read source files using the language's standard file I/O, assert with string or regex checks. Do not start the application.
|
|
246
|
+
- **runtime-check ACs** → load or import the module directly and invoke it, assert on the return value or observable side effects
|
|
247
|
+
- **integration-check ACs** → use the language's HTTP client or existing test helpers; add a clear setup block (beforeAll/setup/TestMain/etc.) explaining what must be running
|
|
248
|
+
- **NEVER use placeholder assertions** — no always-passing or always-failing stubs, no TODO comments as the only content, no empty test bodies
|
|
249
|
+
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
250
|
+
- Output raw code only — no markdown fences, start directly with the language's import or package declaration`;
|
|
261
251
|
}
|
|
262
252
|
|
|
263
253
|
/**
|
|
@@ -313,6 +303,8 @@ export async function generateAcceptanceTests(
|
|
|
313
303
|
const output = await adapter.complete(prompt, {
|
|
314
304
|
model: options.modelDef.model,
|
|
315
305
|
config: options.config,
|
|
306
|
+
timeoutMs: options.config?.acceptance?.timeoutMs ?? 1800000,
|
|
307
|
+
workdir: options.workdir,
|
|
316
308
|
});
|
|
317
309
|
|
|
318
310
|
// Extract test code from output
|
package/src/acceptance/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Types for generating acceptance tests from spec.md acceptance criteria.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { AgentAdapter } from "../agents/types";
|
|
7
8
|
import type { AcceptanceTestStrategy, ModelDef, ModelTier, NaxConfig } from "../config/schema";
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -94,6 +95,8 @@ export interface GenerateFromPRDOptions {
|
|
|
94
95
|
testStrategy?: AcceptanceTestStrategy;
|
|
95
96
|
/** Test framework for component/snapshot strategies (e.g. 'ink-testing-library', 'react') */
|
|
96
97
|
testFramework?: string;
|
|
98
|
+
/** Agent adapter to use for test generation — overrides _generatorPRDDeps.adapter */
|
|
99
|
+
adapter?: AgentAdapter;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
export interface GenerateAcceptanceTestsOptions {
|
|
@@ -141,6 +141,7 @@ export const FIELD_DESCRIPTIONS: Record<string, string> = {
|
|
|
141
141
|
"acceptance.maxRetries": "Max retry loops for fix stories",
|
|
142
142
|
"acceptance.generateTests": "Generate acceptance tests during analyze",
|
|
143
143
|
"acceptance.testPath": "Path to acceptance test file (relative to feature dir)",
|
|
144
|
+
"acceptance.timeoutMs": "Timeout for acceptance test generation in milliseconds (default: 1800000 = 30 min)",
|
|
144
145
|
|
|
145
146
|
// Context
|
|
146
147
|
context: "Context injection configuration",
|
package/src/config/defaults.ts
CHANGED
|
@@ -262,6 +262,8 @@ export interface AcceptanceConfig {
|
|
|
262
262
|
testStrategy?: AcceptanceTestStrategy;
|
|
263
263
|
/** Test framework for acceptance tests (default: auto-detect) */
|
|
264
264
|
testFramework?: string;
|
|
265
|
+
/** Timeout for acceptance test generation in milliseconds (default: 1800000 = 30 min) */
|
|
266
|
+
timeoutMs: number;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
/** Optimizer config (v0.10) */
|
package/src/config/schemas.ts
CHANGED
|
@@ -257,6 +257,7 @@ export const AcceptanceConfigSchema = z.object({
|
|
|
257
257
|
redGate: z.boolean().default(true),
|
|
258
258
|
testStrategy: z.enum(["unit", "component", "cli", "e2e", "snapshot"]).optional(),
|
|
259
259
|
testFramework: z.string().min(1, "acceptance.testFramework must be non-empty").optional(),
|
|
260
|
+
timeoutMs: z.number().int().min(30000).max(3600000).default(1800000),
|
|
260
261
|
});
|
|
261
262
|
|
|
262
263
|
const TestCoverageConfigSchema = z.object({
|
|
@@ -55,6 +55,11 @@ export interface AcceptanceLoopResult {
|
|
|
55
55
|
prdDirty: boolean;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function isStubTestFile(content: string): boolean {
|
|
59
|
+
// Detect skeleton stubs: expect(true).toBe(false) or expect(true).toBe(true) in test bodies
|
|
60
|
+
return /expect\s*\(\s*true\s*\)\s*\.\s*toBe\s*\(\s*(?:false|true)\s*\)/.test(content);
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
/** Load spec.md content for AC text */
|
|
59
64
|
async function loadSpecContent(featureDir?: string): Promise<string> {
|
|
60
65
|
if (!featureDir) return "";
|
|
@@ -243,6 +248,30 @@ export async function runAcceptanceLoop(ctx: AcceptanceLoopContext): Promise<Acc
|
|
|
243
248
|
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
244
249
|
}
|
|
245
250
|
|
|
251
|
+
// Check for stub test file before generating fix stories
|
|
252
|
+
if (ctx.featureDir) {
|
|
253
|
+
const testPath = path.join(ctx.featureDir, "acceptance.test.ts");
|
|
254
|
+
const testFile = Bun.file(testPath);
|
|
255
|
+
if (await testFile.exists()) {
|
|
256
|
+
const testContent = await testFile.text();
|
|
257
|
+
if (isStubTestFile(testContent)) {
|
|
258
|
+
logger?.warn("acceptance", "Stub tests detected — re-generating acceptance tests");
|
|
259
|
+
const { unlink } = await import("node:fs/promises");
|
|
260
|
+
await unlink(testPath);
|
|
261
|
+
const { acceptanceSetupStage } = await import("../../pipeline/stages/acceptance-setup");
|
|
262
|
+
await acceptanceSetupStage.execute(acceptanceContext);
|
|
263
|
+
const newContent = await Bun.file(testPath).text();
|
|
264
|
+
if (isStubTestFile(newContent)) {
|
|
265
|
+
logger?.error(
|
|
266
|
+
"acceptance",
|
|
267
|
+
"Acceptance test generation failed after retry — manual implementation required",
|
|
268
|
+
);
|
|
269
|
+
return buildResult(false, prd, totalCost, iterations, storiesCompleted, prdDirty);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
246
275
|
// Generate and add fix stories
|
|
247
276
|
logger?.info("acceptance", "Generating fix stories...");
|
|
248
277
|
const fixStories = await generateAndAddFixStories(ctx, failures, prd);
|
|
@@ -82,6 +82,9 @@ export const acceptanceSetupStage: PipelineStage = {
|
|
|
82
82
|
const allCriteria: string[] = ctx.prd.userStories.flatMap((s) => s.acceptanceCriteria);
|
|
83
83
|
totalCriteria = allCriteria.length;
|
|
84
84
|
|
|
85
|
+
const { getAgent } = await import("../../agents");
|
|
86
|
+
const agent = (ctx.agentGetFn ?? getAgent)(ctx.config.autoMode.defaultAgent);
|
|
87
|
+
|
|
85
88
|
let refinedCriteria: RefinedCriterion[];
|
|
86
89
|
|
|
87
90
|
if (ctx.config.acceptance.refinement) {
|
|
@@ -113,6 +116,7 @@ export const acceptanceSetupStage: PipelineStage = {
|
|
|
113
116
|
config: ctx.config,
|
|
114
117
|
testStrategy: ctx.config.acceptance.testStrategy,
|
|
115
118
|
testFramework: ctx.config.acceptance.testFramework,
|
|
119
|
+
adapter: agent ?? undefined,
|
|
116
120
|
});
|
|
117
121
|
|
|
118
122
|
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|