agentic-qe 3.7.6 → 3.7.8

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 (171) hide show
  1. package/.claude/helpers/statusline-v3.cjs +13 -1
  2. package/.claude/skills/skills-manifest.json +1 -1
  3. package/CHANGELOG.md +39 -0
  4. package/README.md +36 -0
  5. package/dist/cli/bundle.js +2052 -183
  6. package/dist/coordination/complexity-composition/index.d.ts +2 -0
  7. package/dist/coordination/complexity-composition/index.d.ts.map +1 -0
  8. package/dist/coordination/complexity-composition/index.js +2 -0
  9. package/dist/coordination/complexity-composition/index.js.map +1 -0
  10. package/dist/coordination/complexity-composition/team-composer.d.ts +72 -0
  11. package/dist/coordination/complexity-composition/team-composer.d.ts.map +1 -0
  12. package/dist/coordination/complexity-composition/team-composer.js +221 -0
  13. package/dist/coordination/complexity-composition/team-composer.js.map +1 -0
  14. package/dist/coordination/consensus/consensus-engine.d.ts +10 -1
  15. package/dist/coordination/consensus/consensus-engine.d.ts.map +1 -1
  16. package/dist/coordination/consensus/consensus-engine.js +31 -1
  17. package/dist/coordination/consensus/consensus-engine.js.map +1 -1
  18. package/dist/coordination/consensus/index.d.ts +1 -0
  19. package/dist/coordination/consensus/index.d.ts.map +1 -1
  20. package/dist/coordination/consensus/index.js +4 -0
  21. package/dist/coordination/consensus/index.js.map +1 -1
  22. package/dist/coordination/consensus/interfaces.d.ts +5 -0
  23. package/dist/coordination/consensus/interfaces.d.ts.map +1 -1
  24. package/dist/coordination/consensus/interfaces.js +1 -0
  25. package/dist/coordination/consensus/interfaces.js.map +1 -1
  26. package/dist/coordination/consensus/sycophancy-scorer.d.ts +62 -0
  27. package/dist/coordination/consensus/sycophancy-scorer.d.ts.map +1 -0
  28. package/dist/coordination/consensus/sycophancy-scorer.js +200 -0
  29. package/dist/coordination/consensus/sycophancy-scorer.js.map +1 -0
  30. package/dist/coordination/fleet-tiers/tier-selector.d.ts +20 -0
  31. package/dist/coordination/fleet-tiers/tier-selector.d.ts.map +1 -1
  32. package/dist/coordination/fleet-tiers/tier-selector.js +31 -0
  33. package/dist/coordination/fleet-tiers/tier-selector.js.map +1 -1
  34. package/dist/coordination/fleet-tiers/types.d.ts +2 -0
  35. package/dist/coordination/fleet-tiers/types.d.ts.map +1 -1
  36. package/dist/domains/test-execution/services/e2e/adaptive-locator-service.d.ts +71 -0
  37. package/dist/domains/test-execution/services/e2e/adaptive-locator-service.d.ts.map +1 -0
  38. package/dist/domains/test-execution/services/e2e/adaptive-locator-service.js +456 -0
  39. package/dist/domains/test-execution/services/e2e/adaptive-locator-service.js.map +1 -0
  40. package/dist/domains/test-execution/services/e2e/adaptive-locator-types.d.ts +81 -0
  41. package/dist/domains/test-execution/services/e2e/adaptive-locator-types.d.ts.map +1 -0
  42. package/dist/domains/test-execution/services/e2e/adaptive-locator-types.js +20 -0
  43. package/dist/domains/test-execution/services/e2e/adaptive-locator-types.js.map +1 -0
  44. package/dist/domains/test-execution/services/e2e/browser-orchestrator.d.ts +19 -0
  45. package/dist/domains/test-execution/services/e2e/browser-orchestrator.d.ts.map +1 -1
  46. package/dist/domains/test-execution/services/e2e/browser-orchestrator.js +82 -0
  47. package/dist/domains/test-execution/services/e2e/browser-orchestrator.js.map +1 -1
  48. package/dist/domains/test-execution/services/e2e/index.d.ts +2 -0
  49. package/dist/domains/test-execution/services/e2e/index.d.ts.map +1 -1
  50. package/dist/domains/test-execution/services/e2e/index.js +5 -0
  51. package/dist/domains/test-execution/services/e2e/index.js.map +1 -1
  52. package/dist/domains/test-execution/services/e2e/step-executors.d.ts +6 -0
  53. package/dist/domains/test-execution/services/e2e/step-executors.d.ts.map +1 -1
  54. package/dist/domains/test-execution/services/e2e/step-executors.js +17 -2
  55. package/dist/domains/test-execution/services/e2e/step-executors.js.map +1 -1
  56. package/dist/domains/test-execution/services/e2e/types.d.ts +18 -1
  57. package/dist/domains/test-execution/services/e2e/types.d.ts.map +1 -1
  58. package/dist/domains/test-execution/services/e2e/types.js.map +1 -1
  59. package/dist/domains/test-generation/blind-review/blind-review-orchestrator.d.ts +65 -0
  60. package/dist/domains/test-generation/blind-review/blind-review-orchestrator.d.ts.map +1 -0
  61. package/dist/domains/test-generation/blind-review/blind-review-orchestrator.js +189 -0
  62. package/dist/domains/test-generation/blind-review/blind-review-orchestrator.js.map +1 -0
  63. package/dist/domains/test-generation/blind-review/index.d.ts +2 -0
  64. package/dist/domains/test-generation/blind-review/index.d.ts.map +1 -0
  65. package/dist/domains/test-generation/blind-review/index.js +2 -0
  66. package/dist/domains/test-generation/blind-review/index.js.map +1 -0
  67. package/dist/domains/test-generation/gates/index.d.ts +2 -0
  68. package/dist/domains/test-generation/gates/index.d.ts.map +1 -0
  69. package/dist/domains/test-generation/gates/index.js +2 -0
  70. package/dist/domains/test-generation/gates/index.js.map +1 -0
  71. package/dist/domains/test-generation/gates/test-quality-gate.d.ts +85 -0
  72. package/dist/domains/test-generation/gates/test-quality-gate.d.ts.map +1 -0
  73. package/dist/domains/test-generation/gates/test-quality-gate.js +320 -0
  74. package/dist/domains/test-generation/gates/test-quality-gate.js.map +1 -0
  75. package/dist/domains/test-generation/interfaces.d.ts +3 -0
  76. package/dist/domains/test-generation/interfaces.d.ts.map +1 -1
  77. package/dist/domains/test-generation/pattern-injection/edge-case-injector.d.ts +68 -0
  78. package/dist/domains/test-generation/pattern-injection/edge-case-injector.d.ts.map +1 -0
  79. package/dist/domains/test-generation/pattern-injection/edge-case-injector.js +225 -0
  80. package/dist/domains/test-generation/pattern-injection/edge-case-injector.js.map +1 -0
  81. package/dist/domains/test-generation/pattern-injection/index.d.ts +2 -0
  82. package/dist/domains/test-generation/pattern-injection/index.d.ts.map +1 -0
  83. package/dist/domains/test-generation/pattern-injection/index.js +2 -0
  84. package/dist/domains/test-generation/pattern-injection/index.js.map +1 -0
  85. package/dist/domains/test-generation/services/test-generator.d.ts +6 -0
  86. package/dist/domains/test-generation/services/test-generator.d.ts.map +1 -1
  87. package/dist/domains/test-generation/services/test-generator.js +29 -0
  88. package/dist/domains/test-generation/services/test-generator.js.map +1 -1
  89. package/dist/integrations/agentic-flow/reasoning-bank/experience-replay.d.ts +8 -2
  90. package/dist/integrations/agentic-flow/reasoning-bank/experience-replay.d.ts.map +1 -1
  91. package/dist/integrations/agentic-flow/reasoning-bank/experience-replay.js +62 -38
  92. package/dist/integrations/agentic-flow/reasoning-bank/experience-replay.js.map +1 -1
  93. package/dist/integrations/browser/client-factory.d.ts +6 -1
  94. package/dist/integrations/browser/client-factory.d.ts.map +1 -1
  95. package/dist/integrations/browser/client-factory.js +37 -2
  96. package/dist/integrations/browser/client-factory.js.map +1 -1
  97. package/dist/integrations/browser/index.d.ts +5 -1
  98. package/dist/integrations/browser/index.d.ts.map +1 -1
  99. package/dist/integrations/browser/index.js +8 -1
  100. package/dist/integrations/browser/index.js.map +1 -1
  101. package/dist/integrations/browser/page-pool-types.d.ts +70 -0
  102. package/dist/integrations/browser/page-pool-types.d.ts.map +1 -0
  103. package/dist/integrations/browser/page-pool-types.js +19 -0
  104. package/dist/integrations/browser/page-pool-types.js.map +1 -0
  105. package/dist/integrations/browser/page-pool.d.ts +79 -0
  106. package/dist/integrations/browser/page-pool.d.ts.map +1 -0
  107. package/dist/integrations/browser/page-pool.js +288 -0
  108. package/dist/integrations/browser/page-pool.js.map +1 -0
  109. package/dist/integrations/browser/resource-blocking.d.ts +47 -0
  110. package/dist/integrations/browser/resource-blocking.d.ts.map +1 -0
  111. package/dist/integrations/browser/resource-blocking.js +195 -0
  112. package/dist/integrations/browser/resource-blocking.js.map +1 -0
  113. package/dist/integrations/browser/stealth/index.d.ts +8 -0
  114. package/dist/integrations/browser/stealth/index.d.ts.map +1 -0
  115. package/dist/integrations/browser/stealth/index.js +7 -0
  116. package/dist/integrations/browser/stealth/index.js.map +1 -0
  117. package/dist/integrations/browser/stealth/stealth-client.d.ts +51 -0
  118. package/dist/integrations/browser/stealth/stealth-client.d.ts.map +1 -0
  119. package/dist/integrations/browser/stealth/stealth-client.js +359 -0
  120. package/dist/integrations/browser/stealth/stealth-client.js.map +1 -0
  121. package/dist/integrations/browser/stealth/stealth-types.d.ts +35 -0
  122. package/dist/integrations/browser/stealth/stealth-types.d.ts.map +1 -0
  123. package/dist/integrations/browser/stealth/stealth-types.js +17 -0
  124. package/dist/integrations/browser/stealth/stealth-types.js.map +1 -0
  125. package/dist/integrations/browser/types.d.ts +13 -10
  126. package/dist/integrations/browser/types.d.ts.map +1 -1
  127. package/dist/integrations/browser/types.js.map +1 -1
  128. package/dist/learning/experience-capture.d.ts +12 -2
  129. package/dist/learning/experience-capture.d.ts.map +1 -1
  130. package/dist/learning/experience-capture.js +28 -35
  131. package/dist/learning/experience-capture.js.map +1 -1
  132. package/dist/learning/experience-consolidation.d.ts +74 -0
  133. package/dist/learning/experience-consolidation.d.ts.map +1 -0
  134. package/dist/learning/experience-consolidation.js +403 -0
  135. package/dist/learning/experience-consolidation.js.map +1 -0
  136. package/dist/mcp/bundle.js +3050 -341
  137. package/dist/routing/calibration/ema-calibrator.d.ts +93 -0
  138. package/dist/routing/calibration/ema-calibrator.d.ts.map +1 -0
  139. package/dist/routing/calibration/ema-calibrator.js +140 -0
  140. package/dist/routing/calibration/ema-calibrator.js.map +1 -0
  141. package/dist/routing/calibration/index.d.ts +2 -0
  142. package/dist/routing/calibration/index.d.ts.map +1 -0
  143. package/dist/routing/calibration/index.js +2 -0
  144. package/dist/routing/calibration/index.js.map +1 -0
  145. package/dist/routing/escalation/auto-escalation-tracker.d.ts +62 -0
  146. package/dist/routing/escalation/auto-escalation-tracker.d.ts.map +1 -0
  147. package/dist/routing/escalation/auto-escalation-tracker.js +116 -0
  148. package/dist/routing/escalation/auto-escalation-tracker.js.map +1 -0
  149. package/dist/routing/escalation/index.d.ts +2 -0
  150. package/dist/routing/escalation/index.d.ts.map +1 -0
  151. package/dist/routing/escalation/index.js +2 -0
  152. package/dist/routing/escalation/index.js.map +1 -0
  153. package/dist/routing/index.d.ts +4 -0
  154. package/dist/routing/index.d.ts.map +1 -1
  155. package/dist/routing/index.js +4 -0
  156. package/dist/routing/index.js.map +1 -1
  157. package/dist/routing/routing-config.d.ts +4 -0
  158. package/dist/routing/routing-config.d.ts.map +1 -1
  159. package/dist/routing/routing-config.js +2 -0
  160. package/dist/routing/routing-config.js.map +1 -1
  161. package/dist/routing/routing-feedback.d.ts +35 -2
  162. package/dist/routing/routing-feedback.d.ts.map +1 -1
  163. package/dist/routing/routing-feedback.js +97 -3
  164. package/dist/routing/routing-feedback.js.map +1 -1
  165. package/dist/routing/types.d.ts +2 -0
  166. package/dist/routing/types.d.ts.map +1 -1
  167. package/dist/routing/types.js.map +1 -1
  168. package/dist/workers/workers/learning-consolidation.d.ts.map +1 -1
  169. package/dist/workers/workers/learning-consolidation.js +32 -0
  170. package/dist/workers/workers/learning-consolidation.js.map +1 -1
  171. package/package.json +1 -1
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Blind Review Orchestrator for Test Generation
3
+ * Inspired by loki-mode blind review pattern
4
+ *
5
+ * Runs N independent test generation passes in parallel with varied
6
+ * temperatures, then deduplicates using Jaccard similarity on tokenized
7
+ * assertions to maximize test diversity.
8
+ */
9
+ import { ok, err } from '../../../shared/types/index.js';
10
+ const DEFAULT_CONFIG = {
11
+ reviewerCount: 3,
12
+ deduplicationThreshold: 0.8,
13
+ timeoutMs: 30000,
14
+ varyTemperatures: true,
15
+ temperatures: [0.2, 0.5, 0.8],
16
+ };
17
+ // ============================================================================
18
+ // Tokenization & Jaccard Similarity
19
+ // ============================================================================
20
+ /**
21
+ * Tokenize code into a set of meaningful tokens.
22
+ * Strips whitespace, splits on non-alphanumeric boundaries,
23
+ * and filters tokens shorter than 3 characters.
24
+ */
25
+ export function tokenize(code) {
26
+ return new Set(code
27
+ .replace(/\s+/g, ' ')
28
+ .split(/[^a-zA-Z0-9_]+/)
29
+ .filter((t) => t.length > 2));
30
+ }
31
+ /**
32
+ * Compute Jaccard similarity between two token sets.
33
+ * Returns 0 if both sets are empty.
34
+ */
35
+ export function jaccardSimilarity(a, b) {
36
+ if (a.size === 0 && b.size === 0)
37
+ return 0;
38
+ let intersectionSize = 0;
39
+ const smaller = a.size <= b.size ? a : b;
40
+ const larger = a.size <= b.size ? b : a;
41
+ for (const token of smaller) {
42
+ if (larger.has(token)) {
43
+ intersectionSize++;
44
+ }
45
+ }
46
+ const unionSize = a.size + b.size - intersectionSize;
47
+ return unionSize === 0 ? 0 : intersectionSize / unionSize;
48
+ }
49
+ /**
50
+ * Deduplicate tests using Jaccard similarity on tokenized test code.
51
+ * Groups tests by target file, then within each group keeps the
52
+ * test with the most assertions from each similarity cluster.
53
+ */
54
+ export function deduplicateTests(tests, threshold) {
55
+ if (tests.length === 0)
56
+ return [];
57
+ // Group by source file
58
+ const bySource = new Map();
59
+ for (const test of tests) {
60
+ const key = test.sourceFile || '__ungrouped__';
61
+ const group = bySource.get(key) || [];
62
+ group.push(test);
63
+ bySource.set(key, group);
64
+ }
65
+ const deduplicated = [];
66
+ for (const group of bySource.values()) {
67
+ const tokenSets = group.map((t) => tokenize(t.testCode));
68
+ const used = new Set();
69
+ for (let i = 0; i < group.length; i++) {
70
+ if (used.has(i))
71
+ continue;
72
+ // Start a cluster with this test
73
+ const cluster = [i];
74
+ used.add(i);
75
+ for (let j = i + 1; j < group.length; j++) {
76
+ if (used.has(j))
77
+ continue;
78
+ const similarity = jaccardSimilarity(tokenSets[i], tokenSets[j]);
79
+ if (similarity >= threshold) {
80
+ cluster.push(j);
81
+ used.add(j);
82
+ }
83
+ }
84
+ // Keep the test with the most assertions from the cluster
85
+ let best = cluster[0];
86
+ for (const idx of cluster) {
87
+ if (group[idx].assertions > group[best].assertions) {
88
+ best = idx;
89
+ }
90
+ }
91
+ deduplicated.push(group[best]);
92
+ }
93
+ }
94
+ return deduplicated;
95
+ }
96
+ // ============================================================================
97
+ // Orchestrator
98
+ // ============================================================================
99
+ /**
100
+ * Wraps a promise with a timeout. Rejects if the promise does not
101
+ * settle within the given number of milliseconds.
102
+ */
103
+ function withTimeout(promise, ms) {
104
+ return new Promise((resolve, reject) => {
105
+ const timer = setTimeout(() => reject(new Error(`Reviewer timed out after ${ms}ms`)), ms);
106
+ promise.then((value) => {
107
+ clearTimeout(timer);
108
+ resolve(value);
109
+ }, (reason) => {
110
+ clearTimeout(timer);
111
+ reject(reason);
112
+ });
113
+ });
114
+ }
115
+ export class BlindReviewOrchestrator {
116
+ service;
117
+ constructor(service) {
118
+ this.service = service;
119
+ }
120
+ /**
121
+ * Run blind review: N independent generation passes in parallel,
122
+ * then deduplicate and merge results.
123
+ */
124
+ async generateWithBlindReview(request, config) {
125
+ const cfg = { ...DEFAULT_CONFIG, ...config };
126
+ if (cfg.reviewerCount < 1) {
127
+ return err(new Error('reviewerCount must be at least 1'));
128
+ }
129
+ // Launch N parallel reviewers with varied temperatures
130
+ const reviewerPromises = Array.from({ length: cfg.reviewerCount }, (_, i) => {
131
+ const reviewerId = `reviewer-${i}`;
132
+ const start = Date.now();
133
+ // Vary the request per reviewer: each gets a different temperature
134
+ // to encourage diverse test generation outputs
135
+ const reviewerRequest = { ...request };
136
+ if (cfg.varyTemperatures && cfg.temperatures.length > 0) {
137
+ const temperature = cfg.temperatures[i % cfg.temperatures.length];
138
+ // Attach temperature hint as pattern metadata that the generator can use
139
+ reviewerRequest.patterns = [
140
+ ...(request.patterns ?? []),
141
+ `__blind_review_temperature:${temperature}`,
142
+ ];
143
+ }
144
+ const genPromise = this.service
145
+ .generateTests(reviewerRequest)
146
+ .then((result) => {
147
+ const durationMs = Date.now() - start;
148
+ if (result.success) {
149
+ return {
150
+ reviewerId,
151
+ tests: result.value.tests,
152
+ durationMs,
153
+ };
154
+ }
155
+ return { reviewerId, tests: [], durationMs };
156
+ });
157
+ return withTimeout(genPromise, cfg.timeoutMs).catch(() => ({
158
+ reviewerId,
159
+ tests: [],
160
+ durationMs: Date.now() - start,
161
+ }));
162
+ });
163
+ const reviewerOutputs = await Promise.allSettled(reviewerPromises);
164
+ // Collect fulfilled results
165
+ const outputs = reviewerOutputs
166
+ .filter((r) => r.status === 'fulfilled')
167
+ .map((r) => r.value);
168
+ // Gather all tests
169
+ const allTests = outputs.flatMap((o) => o.tests);
170
+ if (allTests.length === 0) {
171
+ return err(new Error('All reviewers failed to generate tests'));
172
+ }
173
+ // Deduplicate
174
+ const mergedTests = deduplicateTests(allTests, cfg.deduplicationThreshold);
175
+ const totalGenerated = allTests.length;
176
+ const afterDedup = mergedTests.length;
177
+ const uniquenessScore = totalGenerated === 0 ? 0 : afterDedup / totalGenerated;
178
+ return ok({
179
+ mergedTests,
180
+ reviewerOutputs: outputs,
181
+ stats: {
182
+ totalGenerated,
183
+ afterDedup,
184
+ uniquenessScore,
185
+ },
186
+ });
187
+ }
188
+ }
189
+ //# sourceMappingURL=blind-review-orchestrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blind-review-orchestrator.js","sourceRoot":"","sources":["../../../../src/domains/test-generation/blind-review/blind-review-orchestrator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { BlindReviewOrchestrator, tokenize, jaccardSimilarity, deduplicateTests, type BlindReviewConfig, type BlindReviewResult, type BlindReviewStats, type ReviewerOutput, } from './blind-review-orchestrator.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/domains/test-generation/blind-review/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { BlindReviewOrchestrator, tokenize, jaccardSimilarity, deduplicateTests, } from './blind-review-orchestrator.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/domains/test-generation/blind-review/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,QAAQ,EACR,iBAAiB,EACjB,gBAAgB,GAKjB,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { TestQualityGate, type TestQualityGateResult, type TestQualityIssue, type TestQualityIssueType, type TestQualityGateConfig, } from './test-quality-gate.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/domains/test-generation/gates/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,GAC3B,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { TestQualityGate, } from './test-quality-gate.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/domains/test-generation/gates/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAKhB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Test Quality Gate - Mock Detector & Mutation Detector
3
+ * Inspired by loki-mode Gates 8 & 9
4
+ *
5
+ * Validates generated test code quality by detecting:
6
+ * 1. No source imports - test never imports from the source file
7
+ * 2. Tautological assertions - expect(true).toBe(true), expect(x).toBe(x)
8
+ * 3. Empty test bodies - it('...', () => {}) with no assertions
9
+ * 4. Mirrored assertions - expected values copy-pasted from source literals
10
+ *
11
+ * All detection is regex-based, no LLM calls.
12
+ */
13
+ export type TestQualityIssueType = 'no-source-import' | 'tautological-assertion' | 'empty-test-body' | 'mirrored-assertion';
14
+ export interface TestQualityIssue {
15
+ type: TestQualityIssueType;
16
+ severity: 'error' | 'warning';
17
+ line?: number;
18
+ description: string;
19
+ suggestion: string;
20
+ }
21
+ export interface TestQualityGateResult {
22
+ passed: boolean;
23
+ issues: TestQualityIssue[];
24
+ score: number;
25
+ }
26
+ export interface TestQualityGateConfig {
27
+ /** Check for missing source imports (default: true) */
28
+ checkSourceImports: boolean;
29
+ /** Check for tautological assertions (default: true) */
30
+ checkTautologicalAssertions: boolean;
31
+ /** Check for empty test bodies (default: true) */
32
+ checkEmptyTestBodies: boolean;
33
+ /** Check for mirrored assertion values (default: true) */
34
+ checkMirroredAssertions: boolean;
35
+ /** Minimum score to pass (default: 60) */
36
+ minPassScore: number;
37
+ }
38
+ export declare class TestQualityGate {
39
+ private readonly config;
40
+ constructor(config?: Partial<TestQualityGateConfig>);
41
+ /**
42
+ * Validate generated test code quality.
43
+ *
44
+ * @param testCode - The generated test source code
45
+ * @param sourceFilePath - Path to the source file under test
46
+ * @param sourceCode - Optional source code content for mirrored assertion check
47
+ * @returns Gate result with pass/fail, issues, and score
48
+ */
49
+ validate(testCode: string, sourceFilePath: string, sourceCode?: string): TestQualityGateResult;
50
+ /**
51
+ * Check whether the test code imports from the source file.
52
+ * If no import/require statement references the source file basename, flag an error.
53
+ */
54
+ private detectMissingSourceImports;
55
+ /**
56
+ * Detect tautological assertions where the expected and actual values are identical.
57
+ * Examples: expect(true).toBe(true), expect(x).toBe(x), expect('a').toEqual('a')
58
+ */
59
+ private detectTautologicalAssertions;
60
+ /**
61
+ * Detect empty test bodies - test/it blocks with no assertions or meaningful code.
62
+ * Matches: it('...', () => {}), test('...', () => { /* comment * / })
63
+ */
64
+ private detectEmptyTestBodies;
65
+ /**
66
+ * Detect mirrored assertions - expected values that appear to be copy-pasted
67
+ * from source code literals rather than independently computed.
68
+ */
69
+ private detectMirroredAssertions;
70
+ /**
71
+ * Extract non-trivial string and number literals from source code.
72
+ * Trivial values (true, false, null, undefined, 0, 1, '', "") are excluded.
73
+ */
74
+ private extractNonTrivialLiterals;
75
+ /**
76
+ * Check if an assertion expected value matches a source literal.
77
+ */
78
+ private literalMatches;
79
+ /**
80
+ * Calculate quality score from issues.
81
+ * Start at 100, subtract per issue (error: -20, warning: -5), clamp to 0.
82
+ */
83
+ private calculateScore;
84
+ }
85
+ //# sourceMappingURL=test-quality-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-quality-gate.d.ts","sourceRoot":"","sources":["../../../../src/domains/test-generation/gates/test-quality-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,wBAAwB,GACxB,iBAAiB,GACjB,oBAAoB,CAAC;AAEzB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;IAC3B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,uDAAuD;IACvD,kBAAkB,EAAE,OAAO,CAAC;IAC5B,wDAAwD;IACxD,2BAA2B,EAAE,OAAO,CAAC;IACrC,kDAAkD;IAClD,oBAAoB,EAAE,OAAO,CAAC;IAC9B,0DAA0D;IAC1D,uBAAuB,EAAE,OAAO,CAAC;IACjC,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;CACtB;AAcD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;gBAEnC,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC;IAInD;;;;;;;OAOG;IACH,QAAQ,CACN,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,UAAU,CAAC,EAAE,MAAM,GAClB,qBAAqB;IA6BxB;;;OAGG;IACH,OAAO,CAAC,0BAA0B;IAkDlC;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IA2FpC;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAyC7B;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA0ChC;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IA8BjC;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;;OAGG;IACH,OAAO,CAAC,cAAc;CAavB"}
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Test Quality Gate - Mock Detector & Mutation Detector
3
+ * Inspired by loki-mode Gates 8 & 9
4
+ *
5
+ * Validates generated test code quality by detecting:
6
+ * 1. No source imports - test never imports from the source file
7
+ * 2. Tautological assertions - expect(true).toBe(true), expect(x).toBe(x)
8
+ * 3. Empty test bodies - it('...', () => {}) with no assertions
9
+ * 4. Mirrored assertions - expected values copy-pasted from source literals
10
+ *
11
+ * All detection is regex-based, no LLM calls.
12
+ */
13
+ const DEFAULT_CONFIG = {
14
+ checkSourceImports: true,
15
+ checkTautologicalAssertions: true,
16
+ checkEmptyTestBodies: true,
17
+ checkMirroredAssertions: true,
18
+ minPassScore: 60,
19
+ };
20
+ // ============================================================================
21
+ // TestQualityGate
22
+ // ============================================================================
23
+ export class TestQualityGate {
24
+ config;
25
+ constructor(config) {
26
+ this.config = { ...DEFAULT_CONFIG, ...config };
27
+ }
28
+ /**
29
+ * Validate generated test code quality.
30
+ *
31
+ * @param testCode - The generated test source code
32
+ * @param sourceFilePath - Path to the source file under test
33
+ * @param sourceCode - Optional source code content for mirrored assertion check
34
+ * @returns Gate result with pass/fail, issues, and score
35
+ */
36
+ validate(testCode, sourceFilePath, sourceCode) {
37
+ const issues = [];
38
+ if (this.config.checkSourceImports) {
39
+ issues.push(...this.detectMissingSourceImports(testCode, sourceFilePath));
40
+ }
41
+ if (this.config.checkTautologicalAssertions) {
42
+ issues.push(...this.detectTautologicalAssertions(testCode));
43
+ }
44
+ if (this.config.checkEmptyTestBodies) {
45
+ issues.push(...this.detectEmptyTestBodies(testCode));
46
+ }
47
+ if (this.config.checkMirroredAssertions && sourceCode) {
48
+ issues.push(...this.detectMirroredAssertions(testCode, sourceCode));
49
+ }
50
+ const score = this.calculateScore(issues);
51
+ const passed = score >= this.config.minPassScore;
52
+ return { passed, issues, score };
53
+ }
54
+ // ============================================================================
55
+ // Detection Methods
56
+ // ============================================================================
57
+ /**
58
+ * Check whether the test code imports from the source file.
59
+ * If no import/require statement references the source file basename, flag an error.
60
+ */
61
+ detectMissingSourceImports(testCode, sourceFilePath) {
62
+ // Extract basename without extension for matching
63
+ const parts = sourceFilePath.replace(/\\/g, '/').split('/');
64
+ const fileName = parts[parts.length - 1];
65
+ const baseName = fileName.replace(/\.(ts|js|tsx|jsx|mts|mjs|py)$/, '');
66
+ // Collect all import/require paths from the test code
67
+ const importPaths = [];
68
+ // ES import: import ... from '...'
69
+ const esImportRegex = /(?:import|from)\s+['"]([^'"]+)['"]/g;
70
+ let match;
71
+ while ((match = esImportRegex.exec(testCode)) !== null) {
72
+ importPaths.push(match[1]);
73
+ }
74
+ // CommonJS require: require('...')
75
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
76
+ while ((match = requireRegex.exec(testCode)) !== null) {
77
+ importPaths.push(match[1]);
78
+ }
79
+ // Check if any import path references the source file
80
+ const hasSourceImport = importPaths.some((importPath) => {
81
+ const importBaseName = importPath
82
+ .replace(/\\/g, '/')
83
+ .split('/')
84
+ .pop()
85
+ ?.replace(/\.(ts|js|tsx|jsx|mts|mjs)$/, '')
86
+ ?.replace(/\.js$/, '');
87
+ return importBaseName === baseName;
88
+ });
89
+ if (!hasSourceImport && importPaths.length >= 0) {
90
+ return [
91
+ {
92
+ type: 'no-source-import',
93
+ severity: 'error',
94
+ description: `Test does not import from source file "${baseName}". Tests that never reference the source under test are likely mock-only or dead code.`,
95
+ suggestion: `Add an import statement that references the source module, e.g.: import { ... } from './${baseName}.js'`,
96
+ },
97
+ ];
98
+ }
99
+ return [];
100
+ }
101
+ /**
102
+ * Detect tautological assertions where the expected and actual values are identical.
103
+ * Examples: expect(true).toBe(true), expect(x).toBe(x), expect('a').toEqual('a')
104
+ */
105
+ detectTautologicalAssertions(testCode) {
106
+ const issues = [];
107
+ const lines = testCode.split('\n');
108
+ const matchers = ['toBe', 'toEqual', 'toStrictEqual'];
109
+ const matcherPattern = matchers.join('|');
110
+ // Pattern 1: Literal boolean/null/undefined on both sides
111
+ // expect(true).toBe(true), expect(false).toEqual(false), etc.
112
+ const literalPattern = new RegExp(`expect\\s*\\(\\s*(true|false|null|undefined)\\s*\\)\\s*\\.\\s*(?:${matcherPattern})\\s*\\(\\s*\\1\\s*\\)`);
113
+ // Pattern 2: Same numeric literal on both sides
114
+ // expect(1).toBe(1), expect(42).toEqual(42)
115
+ const numericPattern = new RegExp(`expect\\s*\\(\\s*(\\d+(?:\\.\\d+)?)\\s*\\)\\s*\\.\\s*(?:${matcherPattern})\\s*\\(\\s*\\1\\s*\\)`);
116
+ // Pattern 3: Same string literal on both sides (single or double quotes)
117
+ // expect('hello').toBe('hello'), expect("foo").toEqual("foo")
118
+ const singleQuotePattern = new RegExp(`expect\\s*\\(\\s*'([^']*)'\\s*\\)\\s*\\.\\s*(?:${matcherPattern})\\s*\\(\\s*'\\1'\\s*\\)`);
119
+ const doubleQuotePattern = new RegExp(`expect\\s*\\(\\s*"([^"]*)"\\s*\\)\\s*\\.\\s*(?:${matcherPattern})\\s*\\(\\s*"\\1"\\s*\\)`);
120
+ // Pattern 4: Same identifier on both sides
121
+ // expect(x).toBe(x), expect(result).toEqual(result)
122
+ const identifierPattern = new RegExp(`expect\\s*\\(\\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*\\)\\s*\\.\\s*(?:${matcherPattern})\\s*\\(\\s*\\1\\s*\\)`);
123
+ for (let i = 0; i < lines.length; i++) {
124
+ const line = lines[i];
125
+ const lineNum = i + 1;
126
+ if (literalPattern.test(line)) {
127
+ const literalMatch = line.match(literalPattern);
128
+ issues.push({
129
+ type: 'tautological-assertion',
130
+ severity: 'error',
131
+ line: lineNum,
132
+ description: `Tautological assertion: expect(${literalMatch?.[1]}) always equals itself.`,
133
+ suggestion: 'Replace with a meaningful assertion that tests actual behavior, e.g.: expect(myFunction()).toBe(expectedValue)',
134
+ });
135
+ }
136
+ else if (numericPattern.test(line)) {
137
+ const numMatch = line.match(numericPattern);
138
+ issues.push({
139
+ type: 'tautological-assertion',
140
+ severity: 'error',
141
+ line: lineNum,
142
+ description: `Tautological assertion: expect(${numMatch?.[1]}) always equals itself.`,
143
+ suggestion: 'Replace with a meaningful assertion that tests actual behavior.',
144
+ });
145
+ }
146
+ else if (singleQuotePattern.test(line)) {
147
+ const strMatch = line.match(singleQuotePattern);
148
+ issues.push({
149
+ type: 'tautological-assertion',
150
+ severity: 'error',
151
+ line: lineNum,
152
+ description: `Tautological assertion: expect('${strMatch?.[1]}') always equals itself.`,
153
+ suggestion: 'Replace with a meaningful assertion that tests actual behavior.',
154
+ });
155
+ }
156
+ else if (doubleQuotePattern.test(line)) {
157
+ const strMatch = line.match(doubleQuotePattern);
158
+ issues.push({
159
+ type: 'tautological-assertion',
160
+ severity: 'error',
161
+ line: lineNum,
162
+ description: `Tautological assertion: expect("${strMatch?.[1]}") always equals itself.`,
163
+ suggestion: 'Replace with a meaningful assertion that tests actual behavior.',
164
+ });
165
+ }
166
+ else if (identifierPattern.test(line)) {
167
+ const idMatch = line.match(identifierPattern);
168
+ // Avoid false positives with common non-tautological patterns
169
+ // like expect(result).toBe(result) where result is a computed value
170
+ // We flag it anyway since same-variable assertions are always suspicious
171
+ issues.push({
172
+ type: 'tautological-assertion',
173
+ severity: 'error',
174
+ line: lineNum,
175
+ description: `Tautological assertion: expect(${idMatch?.[1]}).toBe(${idMatch?.[1]}) compares a value to itself.`,
176
+ suggestion: 'Compute expected value independently from the actual value.',
177
+ });
178
+ }
179
+ }
180
+ return issues;
181
+ }
182
+ /**
183
+ * Detect empty test bodies - test/it blocks with no assertions or meaningful code.
184
+ * Matches: it('...', () => {}), test('...', () => { /* comment * / })
185
+ */
186
+ detectEmptyTestBodies(testCode) {
187
+ const issues = [];
188
+ const lines = testCode.split('\n');
189
+ // Match it(...) or test(...) blocks with empty or comment-only bodies
190
+ // We look for patterns like:
191
+ // it('desc', () => {})
192
+ // it('desc', () => { })
193
+ // it('desc', () => { /* comment */ })
194
+ // it('desc', function() {})
195
+ // test('desc', () => {})
196
+ //
197
+ // Two-phase approach to avoid ReDoS: first match the block structure,
198
+ // then check if the body contains only whitespace and comments.
199
+ const testBlockRegex = /(?:it|test)\s*\(\s*(?:'[^']*'|"[^"]*"|`[^`]*`)\s*,\s*(?:async\s+)?(?:\(\)\s*=>|function\s*\(\))\s*\{([^}]*)\}\s*\)/;
200
+ const isEmptyOrCommentOnly = (body) => {
201
+ // Strip block comments, then line comments, then check if only whitespace remains
202
+ const stripped = body.replace(/\/\*[^*]*\*\//g, '').replace(/\/\/[^\n]*/g, '');
203
+ return stripped.trim().length === 0;
204
+ };
205
+ for (let i = 0; i < lines.length; i++) {
206
+ // Only check lines that start a test block
207
+ if (!/(?:it|test)\s*\(/.test(lines[i]))
208
+ continue;
209
+ // Build a multi-line window to catch bodies that span 1-3 lines
210
+ const window = lines.slice(i, i + 4).join(' ');
211
+ const match = testBlockRegex.exec(window);
212
+ if (match && isEmptyOrCommentOnly(match[1])) {
213
+ issues.push({
214
+ type: 'empty-test-body',
215
+ severity: 'error',
216
+ line: i + 1,
217
+ description: 'Empty test body: this test has no assertions or meaningful code.',
218
+ suggestion: 'Add assertions that verify the expected behavior of the code under test.',
219
+ });
220
+ }
221
+ }
222
+ return issues;
223
+ }
224
+ /**
225
+ * Detect mirrored assertions - expected values that appear to be copy-pasted
226
+ * from source code literals rather than independently computed.
227
+ */
228
+ detectMirroredAssertions(testCode, sourceCode) {
229
+ const issues = [];
230
+ // Extract non-trivial literals from source code
231
+ const sourceLiterals = this.extractNonTrivialLiterals(sourceCode);
232
+ if (sourceLiterals.length === 0)
233
+ return issues;
234
+ const lines = testCode.split('\n');
235
+ const matcherPattern = /\.(?:toBe|toEqual|toStrictEqual)\s*\(\s*(.+?)\s*\)/;
236
+ for (let i = 0; i < lines.length; i++) {
237
+ const line = lines[i];
238
+ const assertionMatch = line.match(matcherPattern);
239
+ if (!assertionMatch)
240
+ continue;
241
+ const expectedValue = assertionMatch[1].trim();
242
+ // Check if the expected value matches a source literal
243
+ for (const literal of sourceLiterals) {
244
+ if (this.literalMatches(expectedValue, literal)) {
245
+ issues.push({
246
+ type: 'mirrored-assertion',
247
+ severity: 'warning',
248
+ line: i + 1,
249
+ description: `Assertion expected value "${expectedValue}" mirrors a literal from the source code. This may indicate the test was generated by copying source values rather than computing expected results independently.`,
250
+ suggestion: 'Verify the expected value is derived from requirements, not copied from the implementation.',
251
+ });
252
+ break; // One warning per line is enough
253
+ }
254
+ }
255
+ }
256
+ return issues;
257
+ }
258
+ // ============================================================================
259
+ // Utility Methods
260
+ // ============================================================================
261
+ /**
262
+ * Extract non-trivial string and number literals from source code.
263
+ * Trivial values (true, false, null, undefined, 0, 1, '', "") are excluded.
264
+ */
265
+ extractNonTrivialLiterals(sourceCode) {
266
+ const literals = new Set();
267
+ const trivialValues = new Set([
268
+ 'true', 'false', 'null', 'undefined',
269
+ '0', '1', '-1', '""', "''", '``',
270
+ ]);
271
+ // Extract string literals (single and double quoted)
272
+ const stringRegex = /(?:=|return|:)\s*(['"])(.+?)\1/g;
273
+ let match;
274
+ while ((match = stringRegex.exec(sourceCode)) !== null) {
275
+ const value = match[2];
276
+ if (value.length >= 2 && !trivialValues.has(value)) {
277
+ literals.add(`'${value}'`);
278
+ literals.add(`"${value}"`);
279
+ }
280
+ }
281
+ // Extract number literals (non-trivial: > 1 or decimals)
282
+ const numberRegex = /(?:=|return|:)\s*(\d+(?:\.\d+)?)\b/g;
283
+ while ((match = numberRegex.exec(sourceCode)) !== null) {
284
+ const value = match[1];
285
+ if (!trivialValues.has(value)) {
286
+ literals.add(value);
287
+ }
288
+ }
289
+ return Array.from(literals);
290
+ }
291
+ /**
292
+ * Check if an assertion expected value matches a source literal.
293
+ */
294
+ literalMatches(expectedValue, sourceLiteral) {
295
+ // Direct match
296
+ if (expectedValue === sourceLiteral)
297
+ return true;
298
+ // Strip quotes for comparison
299
+ const stripped = expectedValue.replace(/^['"`]|['"`]$/g, '');
300
+ const sourceStripped = sourceLiteral.replace(/^['"`]|['"`]$/g, '');
301
+ return stripped === sourceStripped && stripped.length >= 2;
302
+ }
303
+ /**
304
+ * Calculate quality score from issues.
305
+ * Start at 100, subtract per issue (error: -20, warning: -5), clamp to 0.
306
+ */
307
+ calculateScore(issues) {
308
+ let score = 100;
309
+ for (const issue of issues) {
310
+ if (issue.severity === 'error') {
311
+ score -= 20;
312
+ }
313
+ else {
314
+ score -= 5;
315
+ }
316
+ }
317
+ return Math.max(0, score);
318
+ }
319
+ }
320
+ //# sourceMappingURL=test-quality-gate.js.map