@nathapp/nax 0.18.3 → 0.18.4

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.
Files changed (30) hide show
  1. package/.claude/rules/01-project-conventions.md +34 -0
  2. package/.claude/rules/02-test-architecture.md +39 -0
  3. package/.claude/rules/03-test-writing.md +58 -0
  4. package/.claude/rules/04-forbidden-patterns.md +29 -0
  5. package/.githooks/pre-commit +13 -0
  6. package/CHANGELOG.md +9 -0
  7. package/CLAUDE.md +45 -122
  8. package/docker-compose.test.yml +1 -3
  9. package/docs/ROADMAP.md +9 -27
  10. package/package.json +1 -1
  11. package/src/config/schemas.ts +2 -0
  12. package/src/config/types.ts +5 -1
  13. package/src/execution/post-verify.ts +30 -12
  14. package/src/pipeline/stages/execution.ts +10 -2
  15. package/src/pipeline/stages/routing.ts +18 -4
  16. package/src/pipeline/stages/verify.ts +8 -1
  17. package/src/routing/strategies/keyword.ts +7 -4
  18. package/src/routing/strategies/llm.ts +40 -4
  19. package/test/{US-002-orchestrator.test.ts → integration/precheck-orchestrator.test.ts} +3 -3
  20. package/test/{execution/post-verify-bug026.test.ts → unit/execution/post-verify-regression.test.ts} +22 -50
  21. package/test/{execution → unit/execution}/post-verify.test.ts +1 -1
  22. package/test/unit/pipeline/routing-partial-override.test.ts +15 -36
  23. package/test/unit/pipeline/verify-smart-runner.test.ts +5 -6
  24. package/test/unit/routing/routing-stability.test.ts +207 -0
  25. package/test/unit/storyid-events.test.ts +20 -32
  26. package/test/unit/verification/smart-runner-config.test.ts +162 -0
  27. package/test/unit/{smart-test-runner.test.ts → verification/smart-runner-discovery.test.ts} +5 -164
  28. package/test/TEST_COVERAGE_US001.md +0 -217
  29. package/test/TEST_COVERAGE_US003.md +0 -84
  30. package/test/TEST_COVERAGE_US005.md +0 -86
@@ -85,7 +85,7 @@ export const executionStage: PipelineStage = {
85
85
  const logger = getLogger();
86
86
 
87
87
  // HARD FAILURE: No agent available — cannot proceed without an agent
88
- const agent = getAgent(ctx.config.autoMode.defaultAgent);
88
+ const agent = _executionDeps.getAgent(ctx.config.autoMode.defaultAgent);
89
89
  if (!agent) {
90
90
  return {
91
91
  action: "fail",
@@ -152,7 +152,7 @@ export const executionStage: PipelineStage = {
152
152
  }
153
153
 
154
154
  // Validate agent supports the requested tier
155
- if (!validateAgentForTier(agent, ctx.routing.modelTier)) {
155
+ if (!_executionDeps.validateAgentForTier(agent, ctx.routing.modelTier)) {
156
156
  logger.warn("execution", "Agent tier mismatch", {
157
157
  storyId: ctx.story.id,
158
158
  agentName: agent.name,
@@ -192,3 +192,11 @@ export const executionStage: PipelineStage = {
192
192
  return { action: "continue" };
193
193
  },
194
194
  };
195
+
196
+ /**
197
+ * Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
198
+ */
199
+ export const _executionDeps = {
200
+ getAgent,
201
+ validateAgentForTier,
202
+ };
@@ -35,7 +35,7 @@ export const routingStage: PipelineStage = {
35
35
  let routing: { complexity: string; testStrategy: string; modelTier: string; reasoning?: string };
36
36
  if (ctx.story.routing) {
37
37
  // Use cached complexity/testStrategy/modelTier
38
- routing = await routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
38
+ routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
39
39
  // Override with cached values only when they are actually set
40
40
  if (ctx.story.routing?.complexity) routing.complexity = ctx.story.routing.complexity;
41
41
  if (ctx.story.routing?.testStrategy) routing.testStrategy = ctx.story.routing.testStrategy;
@@ -44,17 +44,20 @@ export const routingStage: PipelineStage = {
44
44
  if (ctx.story.routing?.modelTier) {
45
45
  routing.modelTier = ctx.story.routing.modelTier;
46
46
  } else {
47
- routing.modelTier = complexityToModelTier(routing.complexity as import("../../config").Complexity, ctx.config);
47
+ routing.modelTier = _routingDeps.complexityToModelTier(
48
+ routing.complexity as import("../../config").Complexity,
49
+ ctx.config,
50
+ );
48
51
  }
49
52
  } else {
50
53
  // Fresh classification
51
- routing = await routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
54
+ routing = await _routingDeps.routeStory(ctx.story, { config: ctx.config }, ctx.workdir, ctx.plugins);
52
55
  }
53
56
 
54
57
  // BUG-010: Greenfield detection — force test-after if no test files exist
55
58
  const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
56
59
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
57
- const isGreenfield = await isGreenfieldStory(ctx.story, ctx.workdir);
60
+ const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, ctx.workdir);
58
61
  if (isGreenfield) {
59
62
  logger.info("routing", "Greenfield detected — forcing test-after strategy", {
60
63
  storyId: ctx.story.id,
@@ -84,3 +87,14 @@ export const routingStage: PipelineStage = {
84
87
  return { action: "continue" };
85
88
  },
86
89
  };
90
+
91
+ /**
92
+ * Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
93
+ * Tests can override individual functions without poisoning the module registry.
94
+ */
95
+ export const _routingDeps = {
96
+ routeStory,
97
+ complexityToModelTier,
98
+ isGreenfieldStory,
99
+ clearCache,
100
+ };
@@ -99,7 +99,7 @@ export const verifyStage: PipelineStage = {
99
99
  }
100
100
 
101
101
  // Use unified regression gate (includes 2s wait for agent process cleanup)
102
- const result = await regression({
102
+ const result = await _verifyDeps.regression({
103
103
  workdir: ctx.workdir,
104
104
  command: effectiveCommand,
105
105
  timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
@@ -151,3 +151,10 @@ export const verifyStage: PipelineStage = {
151
151
  return { action: "continue" };
152
152
  },
153
153
  };
154
+
155
+ /**
156
+ * Swappable dependencies for testing (avoids mock.module() which leaks in Bun 1.x).
157
+ */
158
+ export const _verifyDeps = {
159
+ regression,
160
+ };
@@ -71,11 +71,13 @@ const PUBLIC_API_KEYWORDS = [
71
71
  */
72
72
  function classifyComplexity(
73
73
  title: string,
74
- description: string,
74
+ _description: string,
75
75
  acceptanceCriteria: string[],
76
76
  tags: string[] = [],
77
77
  ): Complexity {
78
- const text = [title, description, ...acceptanceCriteria, ...tags].join(" ").toLowerCase();
78
+ // BUG-031: Exclude description it accumulates priorErrors across retries and
79
+ // causes classification drift. Only use stable, immutable story fields.
80
+ const text = [title, ...acceptanceCriteria, ...tags].join(" ").toLowerCase();
79
81
 
80
82
  if (EXPERT_KEYWORDS.some((kw) => text.includes(kw))) {
81
83
  return "expert";
@@ -98,10 +100,11 @@ function classifyComplexity(
98
100
  function determineTestStrategy(
99
101
  complexity: Complexity,
100
102
  title: string,
101
- description: string,
103
+ _description: string,
102
104
  tags: string[] = [],
103
105
  ): TestStrategy {
104
- const text = [title, description, ...tags].join(" ").toLowerCase();
106
+ // BUG-031: Exclude description — only use stable, immutable story fields.
107
+ const text = [title, ...tags].join(" ").toLowerCase();
105
108
 
106
109
  const isSecurityCritical = SECURITY_KEYWORDS.some((kw) => text.includes(kw));
107
110
  const isPublicApi = PUBLIC_API_KEYWORDS.some((kw) => text.includes(kw));
@@ -58,10 +58,7 @@ function evictOldest(): void {
58
58
  * @returns LLM response text
59
59
  * @throws Error on timeout or spawn failure
60
60
  */
61
- async function callLlm(modelTier: string, prompt: string, config: NaxConfig): Promise<string> {
62
- const llmConfig = config.routing.llm;
63
- const timeoutMs = llmConfig?.timeoutMs ?? 15000;
64
-
61
+ async function callLlmOnce(modelTier: string, prompt: string, config: NaxConfig, timeoutMs: number): Promise<string> {
65
62
  // Resolve model tier to actual model identifier
66
63
  const modelEntry = config.models[modelTier];
67
64
  if (!modelEntry) {
@@ -108,6 +105,45 @@ async function callLlm(modelTier: string, prompt: string, config: NaxConfig): Pr
108
105
  }
109
106
  }
110
107
 
108
+ /**
109
+ * Call LLM via claude CLI with timeout and retry (BUG-033).
110
+ *
111
+ * @param modelTier - Model tier to use for routing call
112
+ * @param prompt - Prompt to send to LLM
113
+ * @param config - nax configuration
114
+ * @returns LLM response text
115
+ * @throws Error after all retries exhausted
116
+ */
117
+ async function callLlm(modelTier: string, prompt: string, config: NaxConfig): Promise<string> {
118
+ const llmConfig = config.routing.llm;
119
+ const timeoutMs = llmConfig?.timeoutMs ?? 30000;
120
+ const maxRetries = llmConfig?.retries ?? 1;
121
+ const retryDelayMs = llmConfig?.retryDelayMs ?? 1000;
122
+
123
+ let lastError: Error | undefined;
124
+
125
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
126
+ try {
127
+ return await callLlmOnce(modelTier, prompt, config, timeoutMs);
128
+ } catch (err) {
129
+ lastError = err as Error;
130
+ if (attempt < maxRetries) {
131
+ const logger = getLogger();
132
+ logger.warn(
133
+ "routing",
134
+ `LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryDelayMs}ms`,
135
+ {
136
+ error: lastError.message,
137
+ },
138
+ );
139
+ await Bun.sleep(retryDelayMs);
140
+ }
141
+ }
142
+ }
143
+
144
+ throw lastError ?? new Error("LLM call failed with unknown error");
145
+ }
146
+
111
147
  /**
112
148
  * Route multiple stories in a single batch LLM call.
113
149
  *
@@ -14,9 +14,9 @@ const skipInCI = process.env.CI ? test.skip : test;
14
14
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { tmpdir } from "node:os";
16
16
  import { join } from "node:path";
17
- import type { NaxConfig } from "../src/config";
18
- import type { PRD } from "../src/prd/types";
19
- import { EXIT_CODES, runPrecheck } from "../src/precheck";
17
+ import type { NaxConfig } from "../../src/config";
18
+ import type { PRD } from "../../src/prd/types";
19
+ import { EXIT_CODES, runPrecheck } from "../../src/precheck";
20
20
 
21
21
  // Helper to create a minimal valid git environment
22
22
  async function setupGitRepo(dir: string): Promise<void> {
@@ -15,10 +15,10 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
15
15
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
  import { tmpdir } from "node:os";
18
- import type { NaxConfig } from "../../src/config";
19
- import type { PRD, UserStory } from "../../src/prd/types";
20
- import type { StoryMetrics } from "../../src/metrics";
21
- import type { VerificationResult } from "../../src/verification";
18
+ import type { NaxConfig } from "../../../src/config";
19
+ import type { PRD, UserStory } from "../../../src/prd/types";
20
+ import type { StoryMetrics } from "../../../src/metrics";
21
+ import type { VerificationResult } from "../../../src/verification";
22
22
 
23
23
  // ---------------------------------------------------------------------------
24
24
  // Mock runVerification with call-order-based responses
@@ -41,54 +41,13 @@ const mockRevertStoriesOnFailure = mock(async ({ prd }: { prd: PRD; [k: string]:
41
41
  const mockRunRectificationLoop = mock(async () => false);
42
42
 
43
43
  // ---------------------------------------------------------------------------
44
- // Module mocksmust be top-level (Bun ESM hoisting)
44
+ // Static importsuses _postVerifyDeps pattern (no mock.module() needed)
45
45
  // ---------------------------------------------------------------------------
46
46
 
47
- mock.module("../../src/execution/verification", () => ({
48
- runVerification: mockRunVerification,
49
- parseTestOutput: () => ({ passCount: 5, failCount: 0, isEnvironmentalFailure: false }),
50
- getEnvironmentalEscalationThreshold: () => 3,
51
- }));
52
-
53
- mock.module("../../src/execution/post-verify-rectification", () => ({
54
- revertStoriesOnFailure: mockRevertStoriesOnFailure,
55
- runRectificationLoop: mockRunRectificationLoop,
56
- }));
57
-
58
- mock.module("../../src/prd", () => ({
59
- getExpectedFiles: () => [],
60
- savePRD: mock(async () => {}),
61
- }));
62
-
63
- mock.module("../../src/execution/progress", () => ({
64
- appendProgress: mock(async () => {}),
65
- }));
66
-
67
- mock.module("../../src/execution/escalation", () => ({
68
- getTierConfig: () => undefined,
69
- }));
70
-
71
- mock.module("../../src/verification/parser", () => ({
72
- parseBunTestOutput: () => ({ failed: 0, passed: 5, failures: [] }),
73
- }));
74
-
75
- mock.module("../../src/logger", () => ({
76
- getSafeLogger: () => ({
77
- info: () => {},
78
- warn: () => {},
79
- debug: () => {},
80
- error: () => {},
81
- }),
82
- getLogger: () => ({
83
- info: () => {},
84
- warn: () => {},
85
- debug: () => {},
86
- error: () => {},
87
- }),
88
- }));
89
-
90
- // Dynamic import after mocks
91
- const { runPostAgentVerification } = await import("../../src/execution/post-verify");
47
+ import { _postVerifyDeps, runPostAgentVerification } from "../../../src/execution/post-verify";
48
+
49
+ // ── Capture originals for afterEach restoration ───────────────────────────────
50
+ const _origPostVerifyDeps = { ..._postVerifyDeps };
92
51
 
93
52
  // ---------------------------------------------------------------------------
94
53
  // Fixtures
@@ -283,6 +242,17 @@ let tempDir: string;
283
242
  let storyGitRef: string;
284
243
 
285
244
  beforeEach(() => {
245
+ // Wire _postVerifyDeps to mocks
246
+ _postVerifyDeps.runVerification = mockRunVerification as typeof _postVerifyDeps.runVerification;
247
+ _postVerifyDeps.parseTestOutput = () => ({ passCount: 5, failCount: 0, isEnvironmentalFailure: false }) as any;
248
+ _postVerifyDeps.getEnvironmentalEscalationThreshold = () => 3;
249
+ _postVerifyDeps.revertStoriesOnFailure = mockRevertStoriesOnFailure as typeof _postVerifyDeps.revertStoriesOnFailure;
250
+ _postVerifyDeps.runRectificationLoop = mockRunRectificationLoop as typeof _postVerifyDeps.runRectificationLoop;
251
+ _postVerifyDeps.getExpectedFiles = () => [];
252
+ _postVerifyDeps.savePRD = mock(async () => {}) as typeof _postVerifyDeps.savePRD;
253
+ _postVerifyDeps.appendProgress = mock(async () => {}) as typeof _postVerifyDeps.appendProgress;
254
+ _postVerifyDeps.getTierConfig = () => undefined as any;
255
+ _postVerifyDeps.parseBunTestOutput = () => ({ failed: 0, passed: 5, failures: [] }) as any;
286
256
  mockRunVerification.mockClear();
287
257
  mockRevertStoriesOnFailure.mockClear();
288
258
  mockRunRectificationLoop.mockClear();
@@ -295,6 +265,8 @@ beforeEach(() => {
295
265
  });
296
266
 
297
267
  afterEach(() => {
268
+ Object.assign(_postVerifyDeps, _origPostVerifyDeps);
269
+ mock.restore();
298
270
  rmSync(tempDir, { recursive: true, force: true });
299
271
  });
300
272
 
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { describe, expect, test } from "bun:test";
11
- import type { RegressionGateConfig } from "../../src/config/schema";
11
+ import type { RegressionGateConfig } from "../../../src/config/schema";
12
12
 
13
13
  describe("RegressionGateConfig", () => {
14
14
  test("should have correct default values", () => {
@@ -6,13 +6,14 @@
6
6
  * a fresh classification.
7
7
  */
8
8
 
9
- import { beforeEach, afterEach, describe, expect, mock, test } from "bun:test";
9
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
10
10
  import { initLogger, resetLogger } from "../../../src/logger";
11
- import type { PipelineContext } from "../../../src/pipeline/types";
11
+ import { _routingDeps, routingStage } from "../../../src/pipeline/stages/routing";
12
12
  import type { NaxConfig } from "../../../src/config";
13
+ import type { PipelineContext } from "../../../src/pipeline/types";
13
14
  import type { UserStory } from "../../../src/prd/types";
14
15
 
15
- // ── Module mocks (must be declared before dynamic imports) ────────────────────
16
+ // ── Mock functions ────────────────────────────────────────────────────────────
16
17
 
17
18
  const mockRouteStory = mock(async () => ({
18
19
  complexity: "medium",
@@ -22,26 +23,11 @@ const mockRouteStory = mock(async () => ({
22
23
  }));
23
24
 
24
25
  const mockComplexityToModelTier = mock((_complexity: string, _config: unknown) => "balanced" as const);
26
+ const mockIsGreenfieldStory = mock(async () => false);
25
27
 
26
- mock.module("../../../src/routing", () => ({
27
- routeStory: mockRouteStory,
28
- complexityToModelTier: mockComplexityToModelTier,
29
- }));
30
-
31
- // Greenfield check: return false so it never interferes with test strategy
32
- mock.module("../../../src/context/greenfield", () => ({
33
- isGreenfieldStory: mock(async () => false),
34
- }));
35
-
36
- // LLM batch cache is not relevant here
37
- mock.module("../../../src/routing/strategies/llm", () => ({
38
- clearCache: mock(() => {}),
39
- routeBatch: mock(async () => []),
40
- }));
41
-
42
- // ── Dynamic imports after mocks ───────────────────────────────────────────────
28
+ // ── Capture originals for afterEach restoration ───────────────────────────────
43
29
 
44
- const { routingStage } = await import("../../../src/pipeline/stages/routing");
30
+ const _origDeps = { ..._routingDeps };
45
31
 
46
32
  // ── Fixtures ──────────────────────────────────────────────────────────────────
47
33
 
@@ -58,11 +44,9 @@ function makeStory(routingOverride?: Partial<UserStory["routing"]>): UserStory {
58
44
  tags: [],
59
45
  dependencies: [],
60
46
  };
61
-
62
47
  if (routingOverride !== undefined) {
63
48
  story.routing = routingOverride as UserStory["routing"];
64
49
  }
65
-
66
50
  return story;
67
51
  }
68
52
 
@@ -82,16 +66,22 @@ function makeCtx(story: UserStory): PipelineContext {
82
66
  } as PipelineContext;
83
67
  }
84
68
 
85
- // ── Logger setup ──────────────────────────────────────────────────────────────
69
+ // ── Lifecycle ─────────────────────────────────────────────────────────────────
86
70
 
87
71
  beforeEach(() => {
88
72
  resetLogger();
89
73
  initLogger({ level: "error", useChalk: false });
74
+ _routingDeps.routeStory = mockRouteStory as typeof _routingDeps.routeStory;
75
+ _routingDeps.complexityToModelTier = mockComplexityToModelTier as typeof _routingDeps.complexityToModelTier;
76
+ _routingDeps.isGreenfieldStory = mockIsGreenfieldStory as typeof _routingDeps.isGreenfieldStory;
90
77
  mockRouteStory.mockClear();
91
78
  mockComplexityToModelTier.mockClear();
79
+ mockIsGreenfieldStory.mockClear();
92
80
  });
93
81
 
94
82
  afterEach(() => {
83
+ Object.assign(_routingDeps, _origDeps);
84
+ mock.restore();
95
85
  resetLogger();
96
86
  });
97
87
 
@@ -99,42 +89,31 @@ afterEach(() => {
99
89
 
100
90
  describe("routing stage — partial override (FIX-001)", () => {
101
91
  test("(1) partial override with only testStrategy preserves LLM complexity", async () => {
102
- // Story sets only testStrategy — complexity should come from LLM
103
92
  const story = makeStory({ testStrategy: "test-after", complexity: undefined as any, reasoning: "manual" });
104
93
  const ctx = makeCtx(story);
105
94
 
106
95
  await routingStage.execute(ctx);
107
96
 
108
- // testStrategy is overridden by the story field
109
97
  expect(ctx.routing.testStrategy).toBe("test-after");
110
- // complexity should remain from the LLM result ("medium"), not undefined
111
98
  expect(ctx.routing.complexity).toBe("medium");
112
99
  });
113
100
 
114
101
  test("(2) LLM-classified complexity is preserved when story.routing has no complexity", async () => {
115
- // story.routing is present but complexity is undefined (falsy)
116
102
  const story = makeStory({ testStrategy: "test-after", complexity: undefined as any, reasoning: "" });
117
103
  const ctx = makeCtx(story);
118
104
 
119
105
  await routingStage.execute(ctx);
120
106
 
121
- // LLM returned "medium" — it must not be overwritten with undefined
122
107
  expect(ctx.routing.complexity).toBe("medium");
123
108
  expect(ctx.routing.complexity).not.toBeUndefined();
124
109
  });
125
110
 
126
111
  test("(3) full override works when both complexity and testStrategy are set", async () => {
127
- // Story has explicit values for both fields
128
- const story = makeStory({
129
- complexity: "simple",
130
- testStrategy: "test-after",
131
- reasoning: "manual override",
132
- });
112
+ const story = makeStory({ complexity: "simple", testStrategy: "test-after", reasoning: "manual override" });
133
113
  const ctx = makeCtx(story);
134
114
 
135
115
  await routingStage.execute(ctx);
136
116
 
137
- // Both fields should be overridden from the story
138
117
  expect(ctx.routing.complexity).toBe("simple");
139
118
  expect(ctx.routing.testStrategy).toBe("test-after");
140
119
  });
@@ -23,15 +23,12 @@ import type { PRD, UserStory } from "../../../src/prd/types";
23
23
 
24
24
  const mockRegression = mock(async () => ({ success: true, status: "SUCCESS" as const }));
25
25
 
26
- mock.module("../../../src/verification/gate", () => ({
27
- regression: mockRegression,
28
- }));
26
+ // ---- Static imports — no mock.module() needed (uses _deps pattern) ----------
27
+ import { _verifyDeps, verifyStage } from "../../../src/pipeline/stages/verify";
29
28
 
30
29
  // ---- Capture originals for afterEach restoration ----------------------------
31
30
  const _origDeps = { ..._smartRunnerDeps };
32
-
33
- // ---- Dynamic import after gate mock -----------------------------------------
34
- const { verifyStage } = await import("../../../src/pipeline/stages/verify");
31
+ const _origVerifyDeps = { ..._verifyDeps };
35
32
 
36
33
  // ---- Mock functions ---------------------------------------------------------
37
34
 
@@ -160,6 +157,7 @@ describe("Verify Stage --- Smart Runner Integration", () => {
160
157
  _smartRunnerDeps.mapSourceToTests = mockMapSourceToTests;
161
158
  _smartRunnerDeps.importGrepFallback = mockImportGrepFallback;
162
159
  _smartRunnerDeps.buildSmartTestCommand = mockBuildSmartTestCommand;
160
+ _verifyDeps.regression = mockRegression as typeof _verifyDeps.regression;
163
161
  mockRegression.mockClear();
164
162
  mockGetChangedSourceFiles.mockClear();
165
163
  mockMapSourceToTests.mockClear();
@@ -170,6 +168,7 @@ describe("Verify Stage --- Smart Runner Integration", () => {
170
168
  afterEach(() => {
171
169
  resetLogger();
172
170
  Object.assign(_smartRunnerDeps, _origDeps);
171
+ Object.assign(_verifyDeps, _origVerifyDeps);
173
172
  });
174
173
 
175
174
  describe("AC1: uses scoped test command when smart runner finds test files", () => {