@nathapp/nax 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/ROADMAP.md +2 -0
- package/nax/config.json +2 -2
- package/nax/features/verify-v2/prd.json +79 -0
- package/nax/features/verify-v2/progress.txt +3 -0
- package/package.json +1 -1
- package/src/config/defaults.ts +2 -1
- package/src/config/schemas.ts +2 -0
- package/src/config/types.ts +4 -0
- package/src/execution/lifecycle/index.ts +1 -0
- package/src/execution/lifecycle/run-completion.ts +29 -0
- package/src/execution/lifecycle/run-regression.ts +301 -0
- package/src/execution/pipeline-result-handler.ts +0 -1
- package/src/execution/post-verify.ts +31 -194
- package/src/execution/runner.ts +1 -0
- package/src/pipeline/stages/verify.ts +26 -22
- package/src/verification/smart-runner.ts +52 -0
- package/test/integration/rectification-flow.test.ts +3 -3
- package/test/integration/review-config-commands.test.ts +1 -1
- package/test/integration/verify-stage.test.ts +9 -0
- package/test/unit/config/defaults.test.ts +69 -0
- package/test/unit/config/regression-gate-schema.test.ts +159 -0
- package/test/unit/execution/lifecycle/run-completion.test.ts +239 -0
- package/test/unit/execution/lifecycle/run-regression.test.ts +418 -0
- package/test/unit/execution/post-verify-regression.test.ts +31 -84
- package/test/unit/execution/post-verify.test.ts +28 -48
- package/test/unit/pipeline/stages/verify.test.ts +266 -0
- package/test/unit/pipeline/verify-smart-runner.test.ts +1 -0
package/docs/ROADMAP.md
CHANGED
|
@@ -224,6 +224,8 @@
|
|
|
224
224
|
- [x] **BUG-032:** Routing stage overrides escalated `modelTier` with complexity-derived tier. `src/pipeline/stages/routing.ts:43` always runs `complexityToModelTier(routing.complexity, config)` even when `story.routing.modelTier` was explicitly set by `handleTierEscalation()`. BUG-026 was escalated to `balanced` (logged in iteration header), but `Task classified` shows `modelTier=fast` because `complexityToModelTier("simple", config)` → `"fast"`. Related to BUG-013 (escalation routing not applied) which was marked fixed, but the fix in `applyCachedRouting()` in `pipeline-result-handler.ts:295-310` runs **after** the routing stage — too late. **Location:** `src/pipeline/stages/routing.ts:43`. **Fix:** When `story.routing.modelTier` is explicitly set (by escalation), skip `complexityToModelTier()` and use the cached tier directly. Only derive from complexity when `story.routing.modelTier` is absent.
|
|
225
225
|
- [x] **BUG-033:** LLM routing has no retry on timeout — single attempt with hardcoded 15s default. All 5 LLM routing attempts in the v0.18.3 run timed out at 15s, forcing keyword fallback every time. `src/routing/strategies/llm.ts:63` reads `llmConfig?.timeoutMs ?? 15000` but there's no retry logic — one timeout = immediate fallback. **Location:** `src/routing/strategies/llm.ts:callLlm()`. **Fix:** Add `routing.llm.retries` config (default: 1) with backoff. Also surface `routing.llm.timeoutMs` in `nax config --explain` and consider raising default to 30s for batch routing which processes multiple stories.
|
|
226
226
|
|
|
227
|
+
- [ ] **BUG-037:** Test output summary (verify stage) captures precheck boilerplate instead of actual `bun test` failure. **Symptom:** Logs show successful prechecks (Head) instead of failed tests (Tail). **Fix:** Change `Test output preview` log to tail the last 20 lines of output instead of heading the first 10.
|
|
228
|
+
- [ ] **BUG-038:** `smart-runner` over-matching when global defaults change. **Symptom:** Changing `DEFAULT_CONFIG` matches broad integration tests that fail due to environment/precheck side effects, obscuring targeted results. **Fix:** Refine path mapping to prioritize direct unit tests and exclude known heavy integration tests from default smart-runner matches unless explicitly relevant.
|
|
227
229
|
### Features
|
|
228
230
|
- [x] ~~`nax unlock` command~~
|
|
229
231
|
- [x] ~~Constitution file support~~
|
package/nax/config.json
CHANGED
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"maxIterations": 6,
|
|
60
60
|
"iterationDelayMs": 2000,
|
|
61
61
|
"costLimit": 8.0,
|
|
62
|
-
"sessionTimeoutSeconds":
|
|
62
|
+
"sessionTimeoutSeconds": 7200,
|
|
63
63
|
"verificationTimeoutSeconds": 300,
|
|
64
64
|
"maxStoriesPerFeature": 15,
|
|
65
65
|
"rectification": {
|
|
@@ -147,4 +147,4 @@
|
|
|
147
147
|
"scopeToStory": true
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": "nax",
|
|
3
|
+
"branchName": "feat/v0.20.0-verify-v2",
|
|
4
|
+
"feature": "verify-v2",
|
|
5
|
+
"userStories": [
|
|
6
|
+
{
|
|
7
|
+
"id": "US-001",
|
|
8
|
+
"title": "Remove test from review defaults",
|
|
9
|
+
"description": "Change review.checks default from ['typecheck', 'lint', 'test'] to ['typecheck', 'lint'] in src/config/defaults.ts. The test check in review duplicates the pipeline verify stage. Keep 'test' as a valid enum value in the schema for backwards compatibility but remove it from the default config. Update any tests that assert on the default review checks array.",
|
|
10
|
+
"complexity": "simple",
|
|
11
|
+
"status": "passed",
|
|
12
|
+
"attempts": 0,
|
|
13
|
+
"priorErrors": [
|
|
14
|
+
"Attempt 1 failed with model tier: balanced: Stage requested escalation to higher tier"
|
|
15
|
+
],
|
|
16
|
+
"priorFailures": [
|
|
17
|
+
{
|
|
18
|
+
"attempt": 1,
|
|
19
|
+
"modelTier": "balanced",
|
|
20
|
+
"stage": "escalation",
|
|
21
|
+
"summary": "Failed with tier balanced, escalating to next tier",
|
|
22
|
+
"timestamp": "2026-03-05T11:14:42.773Z"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"escalations": [],
|
|
26
|
+
"dependencies": [],
|
|
27
|
+
"tags": [],
|
|
28
|
+
"acceptanceCriteria": [],
|
|
29
|
+
"storyPoints": 1,
|
|
30
|
+
"routing": {
|
|
31
|
+
"complexity": "simple",
|
|
32
|
+
"modelTier": "powerful",
|
|
33
|
+
"testStrategy": "test-after",
|
|
34
|
+
"reasoning": "override: simple config default change, tests already exist"
|
|
35
|
+
},
|
|
36
|
+
"passes": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "US-002",
|
|
40
|
+
"title": "Remove post-verify scoped duplicate",
|
|
41
|
+
"description": "In src/execution/post-verify.ts, remove the scoped verification logic (getChangedTestFiles + runVerification for scoped tests) from runPostAgentVerification(). The pipeline verify stage already runs Smart Test Runner scoped tests. post-verify should ONLY run the regression gate (full suite) and handle failure revert with StructuredFailure. Remove getChangedTestFiles() and scopeTestCommand() helper functions. Remove the scoped rectification loop call. Update the function signature to no longer need storyGitRef. Update all tests for post-verify accordingly.",
|
|
42
|
+
"complexity": "medium",
|
|
43
|
+
"status": "passed",
|
|
44
|
+
"attempts": 0,
|
|
45
|
+
"priorErrors": [],
|
|
46
|
+
"priorFailures": [],
|
|
47
|
+
"escalations": [],
|
|
48
|
+
"dependencies": [],
|
|
49
|
+
"tags": [],
|
|
50
|
+
"acceptanceCriteria": [],
|
|
51
|
+
"storyPoints": 1,
|
|
52
|
+
"passes": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "US-003",
|
|
56
|
+
"title": "Deferred regression gate",
|
|
57
|
+
"description": "Create new src/execution/lifecycle/run-regression.ts that implements a deferred regression gate. Instead of running the full test suite after every story, run it once after all stories complete. Steps: (1) Add 'mode' field to RegressionGateConfigSchema with values 'deferred' | 'per-story' | 'disabled' (default: 'deferred'). (2) Add 'maxRectificationAttempts' field (default: 2). (3) In run-regression.ts: run full suite once, parse failures, use reverse Smart Test Runner mapping (test file -> source file -> responsible story via git log), attempt targeted rectification per responsible story, re-run full suite to confirm. (4) Call deferred regression from run-completion.ts before final metrics, only when mode is 'deferred'. (5) When mode is 'deferred', skip the per-story regression gate in post-verify.ts. (6) Add reverseMapTestToSource() to smart-runner.ts. (7) Handle edge cases: partial completion (only check passed stories), overlapping file changes (try last story first), unmapped tests (warn and mark all passed stories for re-verification).",
|
|
58
|
+
"complexity": "complex",
|
|
59
|
+
"status": "passed",
|
|
60
|
+
"attempts": 0,
|
|
61
|
+
"priorErrors": [],
|
|
62
|
+
"priorFailures": [],
|
|
63
|
+
"escalations": [],
|
|
64
|
+
"dependencies": [],
|
|
65
|
+
"tags": [],
|
|
66
|
+
"acceptanceCriteria": [],
|
|
67
|
+
"storyPoints": 1,
|
|
68
|
+
"routing": {
|
|
69
|
+
"complexity": "complex",
|
|
70
|
+
"modelTier": "balanced",
|
|
71
|
+
"testStrategy": "three-session-tdd-lite",
|
|
72
|
+
"reasoning": "override: complex new file + schema changes, needs powerful model"
|
|
73
|
+
},
|
|
74
|
+
"failureCategory": "session-failure",
|
|
75
|
+
"passes": true
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
"updatedAt": "2026-03-05T11:58:46.858Z"
|
|
79
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
[2026-03-05T07:05:07.935Z] US-002 — PASSED — Remove post-verify scoped duplicate — Cost: $0.1170
|
|
2
|
+
[2026-03-05T08:08:59.656Z] US-003 — FAILED — Deferred regression gate — Execution failed
|
|
3
|
+
[2026-03-05T08:13:50.586Z] US-001 — FAILED — Remove test from review defaults — Execution failed
|
package/package.json
CHANGED
package/src/config/defaults.ts
CHANGED
|
@@ -67,6 +67,7 @@ export const DEFAULT_CONFIG: NaxConfig = {
|
|
|
67
67
|
enabled: true,
|
|
68
68
|
timeoutSeconds: 120,
|
|
69
69
|
acceptOnTimeout: true,
|
|
70
|
+
maxRectificationAttempts: 2,
|
|
70
71
|
},
|
|
71
72
|
contextProviderTokenBudget: 2000,
|
|
72
73
|
smartTestRunner: true,
|
|
@@ -113,7 +114,7 @@ export const DEFAULT_CONFIG: NaxConfig = {
|
|
|
113
114
|
},
|
|
114
115
|
review: {
|
|
115
116
|
enabled: true,
|
|
116
|
-
checks: ["typecheck", "lint"
|
|
117
|
+
checks: ["typecheck", "lint"],
|
|
117
118
|
commands: {},
|
|
118
119
|
},
|
|
119
120
|
plan: {
|
package/src/config/schemas.ts
CHANGED
|
@@ -63,6 +63,8 @@ const RegressionGateConfigSchema = z.object({
|
|
|
63
63
|
enabled: z.boolean().default(true),
|
|
64
64
|
timeoutSeconds: z.number().int().min(10).max(600).default(120),
|
|
65
65
|
acceptOnTimeout: z.boolean().default(true),
|
|
66
|
+
mode: z.enum(["deferred", "per-story", "disabled"]).default("deferred"),
|
|
67
|
+
maxRectificationAttempts: z.number().int().min(1).default(2),
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
const SmartTestRunnerConfigSchema = z.object({
|
package/src/config/types.ts
CHANGED
|
@@ -78,6 +78,10 @@ export interface RegressionGateConfig {
|
|
|
78
78
|
timeoutSeconds: number;
|
|
79
79
|
/** Accept timeout as pass instead of failing (BUG-026, default: true) */
|
|
80
80
|
acceptOnTimeout?: boolean;
|
|
81
|
+
/** Mode of regression gate: 'deferred' (run once after all stories), 'per-story' (run after each story), 'disabled' (default: 'deferred') */
|
|
82
|
+
mode?: "deferred" | "per-story" | "disabled";
|
|
83
|
+
/** Max rectification attempts for deferred regression gate (default: 2) */
|
|
84
|
+
maxRectificationAttempts?: number;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
/** Smart test runner configuration (STR-007) */
|
|
@@ -9,3 +9,4 @@ export { handleParallelCompletion, type ParallelCompletionOptions } from "./para
|
|
|
9
9
|
export { handleRunCompletion, type RunCompletionOptions, type RunCompletionResult } from "./run-completion";
|
|
10
10
|
export { cleanupRun, type RunCleanupOptions } from "./run-cleanup";
|
|
11
11
|
export { setupRun, type RunSetupOptions, type RunSetupResult } from "./run-setup";
|
|
12
|
+
export { runDeferredRegression, type DeferredRegressionOptions, type DeferredRegressionResult } from "./run-regression";
|
|
@@ -2,17 +2,28 @@
|
|
|
2
2
|
* Run Completion — Final Metrics and Status Updates
|
|
3
3
|
*
|
|
4
4
|
* Handles the final steps after sequential execution completes:
|
|
5
|
+
* - Run deferred regression gate (if configured)
|
|
5
6
|
* - Save run metrics
|
|
6
7
|
* - Log completion summary with per-story metrics
|
|
7
8
|
* - Update final status
|
|
8
9
|
*/
|
|
9
10
|
|
|
11
|
+
import type { NaxConfig } from "../../config";
|
|
10
12
|
import { getSafeLogger } from "../../logger";
|
|
11
13
|
import type { StoryMetrics } from "../../metrics";
|
|
12
14
|
import { saveRunMetrics } from "../../metrics";
|
|
13
15
|
import { countStories, isComplete, isStalled } from "../../prd";
|
|
14
16
|
import type { PRD } from "../../prd";
|
|
15
17
|
import type { StatusWriter } from "../status-writer";
|
|
18
|
+
import { runDeferredRegression } from "./run-regression";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Injectable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
|
|
22
|
+
* @internal - test use only.
|
|
23
|
+
*/
|
|
24
|
+
export const _runCompletionDeps = {
|
|
25
|
+
runDeferredRegression,
|
|
26
|
+
};
|
|
16
27
|
|
|
17
28
|
export interface RunCompletionOptions {
|
|
18
29
|
runId: string;
|
|
@@ -26,6 +37,7 @@ export interface RunCompletionOptions {
|
|
|
26
37
|
startTime: number;
|
|
27
38
|
workdir: string;
|
|
28
39
|
statusWriter: StatusWriter;
|
|
40
|
+
config: NaxConfig;
|
|
29
41
|
}
|
|
30
42
|
|
|
31
43
|
export interface RunCompletionResult {
|
|
@@ -57,8 +69,25 @@ export async function handleRunCompletion(options: RunCompletionOptions): Promis
|
|
|
57
69
|
startTime,
|
|
58
70
|
workdir,
|
|
59
71
|
statusWriter,
|
|
72
|
+
config,
|
|
60
73
|
} = options;
|
|
61
74
|
|
|
75
|
+
// Run deferred regression gate before final metrics
|
|
76
|
+
const regressionMode = config.execution.regressionGate?.mode;
|
|
77
|
+
if (regressionMode === "deferred" && config.quality.commands.test) {
|
|
78
|
+
const regressionResult = await _runCompletionDeps.runDeferredRegression({
|
|
79
|
+
config,
|
|
80
|
+
prd,
|
|
81
|
+
workdir,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
logger?.info("regression", "Deferred regression gate completed", {
|
|
85
|
+
success: regressionResult.success,
|
|
86
|
+
failedTests: regressionResult.failedTests,
|
|
87
|
+
affectedStories: regressionResult.affectedStories,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
const durationMs = Date.now() - startTime;
|
|
63
92
|
const runCompletedAt = new Date().toISOString();
|
|
64
93
|
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deferred Regression Gate
|
|
3
|
+
*
|
|
4
|
+
* Runs full test suite once after all stories complete, then attempts
|
|
5
|
+
* targeted rectification per responsible story. Handles edge cases:
|
|
6
|
+
* - Partial completion: only check stories marked passed
|
|
7
|
+
* - Overlapping file changes: try last modified story first
|
|
8
|
+
* - Unmapped tests: warn and mark all passed stories for re-verification
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { NaxConfig } from "../../config";
|
|
12
|
+
import { getSafeLogger } from "../../logger";
|
|
13
|
+
import type { PRD, UserStory } from "../../prd";
|
|
14
|
+
import { countStories } from "../../prd";
|
|
15
|
+
import { hasCommitsForStory } from "../../utils/git";
|
|
16
|
+
import { parseBunTestOutput } from "../../verification";
|
|
17
|
+
import { reverseMapTestToSource } from "../../verification/smart-runner";
|
|
18
|
+
import { runRectificationLoop } from "../post-verify-rectification";
|
|
19
|
+
import { runVerification } from "../verification";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Injectable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
|
|
23
|
+
* @internal - test use only.
|
|
24
|
+
*/
|
|
25
|
+
export const _regressionDeps = {
|
|
26
|
+
runVerification,
|
|
27
|
+
runRectificationLoop,
|
|
28
|
+
parseBunTestOutput,
|
|
29
|
+
reverseMapTestToSource,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface DeferredRegressionOptions {
|
|
33
|
+
config: NaxConfig;
|
|
34
|
+
prd: PRD;
|
|
35
|
+
workdir: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DeferredRegressionResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
failedTests: number;
|
|
41
|
+
passedTests: number;
|
|
42
|
+
rectificationAttempts: number;
|
|
43
|
+
affectedStories: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Map a test file to the story responsible for it via git log.
|
|
48
|
+
*
|
|
49
|
+
* Searches recent commits for story IDs in the format US-NNN.
|
|
50
|
+
* Returns the first matching story ID, or undefined if not found.
|
|
51
|
+
*/
|
|
52
|
+
async function findResponsibleStory(
|
|
53
|
+
testFile: string,
|
|
54
|
+
workdir: string,
|
|
55
|
+
passedStories: UserStory[],
|
|
56
|
+
): Promise<UserStory | undefined> {
|
|
57
|
+
const logger = getSafeLogger();
|
|
58
|
+
|
|
59
|
+
// Try each passed story in reverse order (most recent first)
|
|
60
|
+
for (let i = passedStories.length - 1; i >= 0; i--) {
|
|
61
|
+
const story = passedStories[i];
|
|
62
|
+
const hasCommits = await hasCommitsForStory(workdir, story.id, 50);
|
|
63
|
+
if (hasCommits) {
|
|
64
|
+
logger?.info("regression", `Mapped test to story ${story.id}`, { testFile });
|
|
65
|
+
return story;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Run deferred regression gate after all stories complete.
|
|
74
|
+
*
|
|
75
|
+
* Steps:
|
|
76
|
+
* 1. Run full test suite
|
|
77
|
+
* 2. If failures, reverse-map test files to source files to stories
|
|
78
|
+
* 3. For each affected story, attempt targeted rectification
|
|
79
|
+
* 4. Re-run full suite to confirm fixes
|
|
80
|
+
* 5. Return results with affected story list
|
|
81
|
+
*/
|
|
82
|
+
export async function runDeferredRegression(options: DeferredRegressionOptions): Promise<DeferredRegressionResult> {
|
|
83
|
+
const logger = getSafeLogger();
|
|
84
|
+
const { config, prd, workdir } = options;
|
|
85
|
+
|
|
86
|
+
// Check if regression gate is deferred
|
|
87
|
+
const regressionMode = config.execution.regressionGate?.mode ?? "deferred";
|
|
88
|
+
if (regressionMode === "disabled") {
|
|
89
|
+
logger?.info("regression", "Deferred regression gate disabled");
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
failedTests: 0,
|
|
93
|
+
passedTests: 0,
|
|
94
|
+
rectificationAttempts: 0,
|
|
95
|
+
affectedStories: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (regressionMode !== "deferred") {
|
|
100
|
+
logger?.info("regression", "Regression gate mode is not deferred, skipping");
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
failedTests: 0,
|
|
104
|
+
passedTests: 0,
|
|
105
|
+
rectificationAttempts: 0,
|
|
106
|
+
affectedStories: [],
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const testCommand = config.quality.commands.test ?? "bun test";
|
|
111
|
+
const timeoutSeconds = config.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
112
|
+
const maxRectificationAttempts = config.execution.regressionGate?.maxRectificationAttempts ?? 2;
|
|
113
|
+
|
|
114
|
+
// Only check stories that have been marked as passed
|
|
115
|
+
const counts = countStories(prd);
|
|
116
|
+
const passedStories = prd.userStories.filter((s) => s.status === "passed");
|
|
117
|
+
|
|
118
|
+
if (passedStories.length === 0) {
|
|
119
|
+
logger?.info("regression", "No passed stories to verify (partial completion)");
|
|
120
|
+
return {
|
|
121
|
+
success: true,
|
|
122
|
+
failedTests: 0,
|
|
123
|
+
passedTests: 0,
|
|
124
|
+
rectificationAttempts: 0,
|
|
125
|
+
affectedStories: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
logger?.info("regression", "Running deferred full-suite regression gate", {
|
|
130
|
+
totalStories: counts.total,
|
|
131
|
+
passedStories: passedStories.length,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Step 1: Run full test suite
|
|
135
|
+
const fullSuiteResult = await _regressionDeps.runVerification({
|
|
136
|
+
workingDirectory: workdir,
|
|
137
|
+
command: testCommand,
|
|
138
|
+
timeoutSeconds,
|
|
139
|
+
forceExit: config.quality.forceExit,
|
|
140
|
+
detectOpenHandles: config.quality.detectOpenHandles,
|
|
141
|
+
detectOpenHandlesRetries: config.quality.detectOpenHandlesRetries,
|
|
142
|
+
timeoutRetryCount: 0,
|
|
143
|
+
gracePeriodMs: config.quality.gracePeriodMs,
|
|
144
|
+
drainTimeoutMs: config.quality.drainTimeoutMs,
|
|
145
|
+
shell: config.quality.shell,
|
|
146
|
+
stripEnvVars: config.quality.stripEnvVars,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (fullSuiteResult.success) {
|
|
150
|
+
logger?.info("regression", "Full suite passed");
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
failedTests: 0,
|
|
154
|
+
passedTests: fullSuiteResult.passCount ?? 0,
|
|
155
|
+
rectificationAttempts: 0,
|
|
156
|
+
affectedStories: [],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle timeout
|
|
161
|
+
const acceptOnTimeout = config.execution.regressionGate?.acceptOnTimeout ?? true;
|
|
162
|
+
if (fullSuiteResult.status === "TIMEOUT" && acceptOnTimeout) {
|
|
163
|
+
logger?.warn("regression", "Full-suite regression gate timed out (accepted as pass)");
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
failedTests: 0,
|
|
167
|
+
passedTests: 0,
|
|
168
|
+
rectificationAttempts: 0,
|
|
169
|
+
affectedStories: [],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!fullSuiteResult.output) {
|
|
174
|
+
logger?.error("regression", "Full suite failed with no output");
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
failedTests: fullSuiteResult.failCount ?? 0,
|
|
178
|
+
passedTests: fullSuiteResult.passCount ?? 0,
|
|
179
|
+
rectificationAttempts: 0,
|
|
180
|
+
affectedStories: [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Step 2: Parse failures and map to source files to stories
|
|
185
|
+
const testSummary = _regressionDeps.parseBunTestOutput(fullSuiteResult.output);
|
|
186
|
+
const affectedStories = new Set<string>();
|
|
187
|
+
const affectedStoriesObjs = new Map<string, UserStory>();
|
|
188
|
+
|
|
189
|
+
logger?.warn("regression", "Regression detected", {
|
|
190
|
+
failedTests: testSummary.failed,
|
|
191
|
+
passedTests: testSummary.passed,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Extract test file paths from failures
|
|
195
|
+
const testFilesInFailures = new Set<string>();
|
|
196
|
+
for (const failure of testSummary.failures) {
|
|
197
|
+
if (failure.file) {
|
|
198
|
+
testFilesInFailures.add(failure.file);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (testFilesInFailures.size === 0) {
|
|
203
|
+
logger?.warn("regression", "No test files found in failures (unmapped)");
|
|
204
|
+
// Mark all passed stories for re-verification
|
|
205
|
+
for (const story of passedStories) {
|
|
206
|
+
affectedStories.add(story.id);
|
|
207
|
+
affectedStoriesObjs.set(story.id, story);
|
|
208
|
+
}
|
|
209
|
+
} else {
|
|
210
|
+
// Map test files to source files to stories
|
|
211
|
+
const testFilesArray = Array.from(testFilesInFailures);
|
|
212
|
+
const sourceFilesArray = _regressionDeps.reverseMapTestToSource(testFilesArray, workdir);
|
|
213
|
+
|
|
214
|
+
logger?.info("regression", "Mapped test files to source files", {
|
|
215
|
+
testFiles: testFilesArray.length,
|
|
216
|
+
sourceFiles: sourceFilesArray.length,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
for (const testFile of testFilesArray) {
|
|
220
|
+
const responsibleStory = await findResponsibleStory(testFile, workdir, passedStories);
|
|
221
|
+
if (responsibleStory) {
|
|
222
|
+
affectedStories.add(responsibleStory.id);
|
|
223
|
+
affectedStoriesObjs.set(responsibleStory.id, responsibleStory);
|
|
224
|
+
} else {
|
|
225
|
+
logger?.warn("regression", "Could not map test file to story", { testFile });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (affectedStories.size === 0) {
|
|
231
|
+
logger?.warn("regression", "No stories could be mapped to failures");
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
failedTests: testSummary.failed,
|
|
235
|
+
passedTests: testSummary.passed,
|
|
236
|
+
rectificationAttempts: 0,
|
|
237
|
+
affectedStories: Array.from(affectedStories),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Step 3: Attempt rectification per story
|
|
242
|
+
let rectificationAttempts = 0;
|
|
243
|
+
const affectedStoriesList = Array.from(affectedStoriesObjs.values());
|
|
244
|
+
|
|
245
|
+
for (const story of affectedStoriesList) {
|
|
246
|
+
for (let attempt = 0; attempt < maxRectificationAttempts; attempt++) {
|
|
247
|
+
rectificationAttempts++;
|
|
248
|
+
|
|
249
|
+
logger?.info("regression", `Rectifying story ${story.id} (attempt ${attempt + 1}/${maxRectificationAttempts})`);
|
|
250
|
+
|
|
251
|
+
const fixed = await _regressionDeps.runRectificationLoop({
|
|
252
|
+
config,
|
|
253
|
+
workdir,
|
|
254
|
+
story,
|
|
255
|
+
testCommand,
|
|
256
|
+
timeoutSeconds,
|
|
257
|
+
testOutput: fullSuiteResult.output,
|
|
258
|
+
promptPrefix: `# DEFERRED REGRESSION: Full-Suite Failures\n\nYour story ${story.id} broke tests in the full suite. Fix these regressions.`,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (fixed) {
|
|
262
|
+
logger?.info("regression", `Story ${story.id} rectified successfully`);
|
|
263
|
+
break; // Move to next story
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Step 4: Re-run full suite to confirm
|
|
269
|
+
logger?.info("regression", "Re-running full suite after rectification");
|
|
270
|
+
const retryResult = await _regressionDeps.runVerification({
|
|
271
|
+
workingDirectory: workdir,
|
|
272
|
+
command: testCommand,
|
|
273
|
+
timeoutSeconds,
|
|
274
|
+
forceExit: config.quality.forceExit,
|
|
275
|
+
detectOpenHandles: config.quality.detectOpenHandles,
|
|
276
|
+
detectOpenHandlesRetries: config.quality.detectOpenHandlesRetries,
|
|
277
|
+
timeoutRetryCount: 0,
|
|
278
|
+
gracePeriodMs: config.quality.gracePeriodMs,
|
|
279
|
+
drainTimeoutMs: config.quality.drainTimeoutMs,
|
|
280
|
+
shell: config.quality.shell,
|
|
281
|
+
stripEnvVars: config.quality.stripEnvVars,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const success = retryResult.success || (retryResult.status === "TIMEOUT" && acceptOnTimeout);
|
|
285
|
+
|
|
286
|
+
if (success) {
|
|
287
|
+
logger?.info("regression", "Deferred regression gate passed after rectification");
|
|
288
|
+
} else {
|
|
289
|
+
logger?.warn("regression", "Deferred regression gate still failing after rectification", {
|
|
290
|
+
remainingFailures: retryResult.failCount,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
success,
|
|
296
|
+
failedTests: retryResult.failCount ?? 0,
|
|
297
|
+
passedTests: retryResult.passCount ?? 0,
|
|
298
|
+
rectificationAttempts,
|
|
299
|
+
affectedStories: Array.from(affectedStories),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
@@ -83,7 +83,6 @@ export async function handlePipelineSuccess(
|
|
|
83
83
|
storiesToExecute: ctx.storiesToExecute,
|
|
84
84
|
allStoryMetrics: ctx.allStoryMetrics,
|
|
85
85
|
timeoutRetryCountMap: ctx.timeoutRetryCountMap,
|
|
86
|
-
storyGitRef: ctx.storyGitRef ?? undefined,
|
|
87
86
|
});
|
|
88
87
|
const verificationPassed = verifyResult.passed;
|
|
89
88
|
prd = verifyResult.prd;
|