outcome-cli 1.0.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.
Files changed (113) hide show
  1. package/README.md +261 -0
  2. package/package.json +95 -0
  3. package/src/agents/README.md +139 -0
  4. package/src/agents/adapters/anthropic.adapter.ts +166 -0
  5. package/src/agents/adapters/dalle.adapter.ts +145 -0
  6. package/src/agents/adapters/gemini.adapter.ts +134 -0
  7. package/src/agents/adapters/imagen.adapter.ts +106 -0
  8. package/src/agents/adapters/nano-banana.adapter.ts +129 -0
  9. package/src/agents/adapters/openai.adapter.ts +165 -0
  10. package/src/agents/adapters/veo.adapter.ts +130 -0
  11. package/src/agents/agent.schema.property.test.ts +379 -0
  12. package/src/agents/agent.schema.test.ts +148 -0
  13. package/src/agents/agent.schema.ts +263 -0
  14. package/src/agents/index.ts +60 -0
  15. package/src/agents/registered-agent.schema.ts +356 -0
  16. package/src/agents/registry.ts +97 -0
  17. package/src/agents/tournament-configs.property.test.ts +266 -0
  18. package/src/cli/README.md +145 -0
  19. package/src/cli/commands/define.ts +79 -0
  20. package/src/cli/commands/list.ts +46 -0
  21. package/src/cli/commands/logs.ts +83 -0
  22. package/src/cli/commands/run.ts +416 -0
  23. package/src/cli/commands/verify.ts +110 -0
  24. package/src/cli/index.ts +81 -0
  25. package/src/config/README.md +128 -0
  26. package/src/config/env.ts +262 -0
  27. package/src/config/index.ts +19 -0
  28. package/src/eval/README.md +318 -0
  29. package/src/eval/ai-judge.test.ts +435 -0
  30. package/src/eval/ai-judge.ts +368 -0
  31. package/src/eval/code-validators.ts +414 -0
  32. package/src/eval/evaluateOutcome.property.test.ts +1174 -0
  33. package/src/eval/evaluateOutcome.ts +591 -0
  34. package/src/eval/immigration-validators.ts +122 -0
  35. package/src/eval/index.ts +90 -0
  36. package/src/eval/judge-cache.ts +402 -0
  37. package/src/eval/tournament-validators.property.test.ts +439 -0
  38. package/src/eval/validators.property.test.ts +1118 -0
  39. package/src/eval/validators.ts +1199 -0
  40. package/src/eval/weighted-scorer.ts +285 -0
  41. package/src/index.ts +17 -0
  42. package/src/league/README.md +188 -0
  43. package/src/league/health-check.ts +353 -0
  44. package/src/league/index.ts +93 -0
  45. package/src/league/killAgent.ts +151 -0
  46. package/src/league/league.test.ts +1151 -0
  47. package/src/league/runLeague.ts +843 -0
  48. package/src/league/scoreAgent.ts +175 -0
  49. package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
  50. package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
  51. package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
  52. package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
  53. package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
  54. package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
  55. package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
  56. package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
  57. package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
  58. package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
  59. package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
  60. package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
  61. package/src/modules/omnibridge/api/.gitkeep +1 -0
  62. package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
  63. package/src/modules/omnibridge/auth/.gitkeep +1 -0
  64. package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
  65. package/src/modules/omnibridge/auth/session-vault.ts +577 -0
  66. package/src/modules/omnibridge/core/.gitkeep +1 -0
  67. package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
  68. package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
  69. package/src/modules/omnibridge/core/types.ts +610 -0
  70. package/src/modules/omnibridge/execution/.gitkeep +1 -0
  71. package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
  72. package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
  73. package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
  74. package/src/modules/omnibridge/index.ts +212 -0
  75. package/src/modules/omnibridge/omnibridge.ts +510 -0
  76. package/src/modules/omnibridge/verification/.gitkeep +1 -0
  77. package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
  78. package/src/outcomes/README.md +75 -0
  79. package/src/outcomes/acquire-pilot-customer.ts +297 -0
  80. package/src/outcomes/code-delivery-outcomes.ts +89 -0
  81. package/src/outcomes/code-outcomes.ts +256 -0
  82. package/src/outcomes/code_review_battle.test.ts +135 -0
  83. package/src/outcomes/code_review_battle.ts +135 -0
  84. package/src/outcomes/cold_email_battle.ts +97 -0
  85. package/src/outcomes/content_creation_battle.ts +160 -0
  86. package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
  87. package/src/outcomes/index.ts +107 -0
  88. package/src/outcomes/lead_gen_battle.test.ts +113 -0
  89. package/src/outcomes/lead_gen_battle.ts +99 -0
  90. package/src/outcomes/outcome.schema.property.test.ts +229 -0
  91. package/src/outcomes/outcome.schema.ts +187 -0
  92. package/src/outcomes/qualified_sales_interest.ts +118 -0
  93. package/src/outcomes/swarm_planner.property.test.ts +370 -0
  94. package/src/outcomes/swarm_planner.ts +96 -0
  95. package/src/outcomes/web_extraction.ts +234 -0
  96. package/src/runtime/README.md +220 -0
  97. package/src/runtime/agentRunner.test.ts +341 -0
  98. package/src/runtime/agentRunner.ts +746 -0
  99. package/src/runtime/claudeAdapter.ts +232 -0
  100. package/src/runtime/costTracker.ts +123 -0
  101. package/src/runtime/index.ts +34 -0
  102. package/src/runtime/modelAdapter.property.test.ts +305 -0
  103. package/src/runtime/modelAdapter.ts +144 -0
  104. package/src/runtime/openaiAdapter.ts +235 -0
  105. package/src/utils/README.md +122 -0
  106. package/src/utils/command-runner.ts +134 -0
  107. package/src/utils/cost-guard.ts +379 -0
  108. package/src/utils/errors.test.ts +290 -0
  109. package/src/utils/errors.ts +442 -0
  110. package/src/utils/index.ts +37 -0
  111. package/src/utils/logger.test.ts +361 -0
  112. package/src/utils/logger.ts +419 -0
  113. package/src/utils/output-parsers.ts +216 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * Property-based tests for Tournament Seed Bounty Validators
3
+ *
4
+ * Tests the validators used in code review battles and lead generation battles.
5
+ * Each property test validates universal correctness properties across many inputs.
6
+ *
7
+ * @module eval/tournament-validators.property.test
8
+ */
9
+
10
+ import { describe, test, expect } from 'vitest';
11
+ import * as fc from 'fast-check';
12
+ import {
13
+ validateSecurityIssue,
14
+ validatePerformanceIssue,
15
+ validateNoiseFreeness,
16
+ validateComplexityReduction,
17
+ validateLinkedIn,
18
+ validateLeadGenPrecision,
19
+ validateExpertReview,
20
+ type ValidationResult,
21
+ } from './validators.js';
22
+
23
+ // ============================================================================
24
+ // Code Review Battle Property Tests
25
+ // ============================================================================
26
+
27
+ /**
28
+ * Property-based tests for Security Issue Detection
29
+ *
30
+ * **Feature: tournament-seed-bounties, Property 1: Security Issue Detection**
31
+ * **Validates: Requirements 1.4, 3.3**
32
+ *
33
+ * Property 1: Security Issue Detection
34
+ * *For any* code review artifact, the security validator SHALL return valid: true
35
+ * if and only if the artifact contains at least one issue with type "security"
36
+ * and severity "CRITICAL".
37
+ */
38
+ describe('Security Issue Detection - Property Tests', () => {
39
+ // **Feature: tournament-seed-bounties, Property 1: Security Issue Detection**
40
+ test('artifacts with CRITICAL security issues are valid', () => {
41
+ fc.assert(
42
+ fc.property(
43
+ fc.array(fc.record({
44
+ type: fc.constantFrom('security', 'performance', 'style', 'logic'),
45
+ severity: fc.constantFrom('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'),
46
+ description: fc.string(),
47
+ lineNumber: fc.integer({ min: 1, max: 100 })
48
+ })),
49
+ (issues) => {
50
+ // Ensure at least one CRITICAL security issue exists
51
+ const artifactWithSecurity = {
52
+ issues: [
53
+ ...issues,
54
+ {
55
+ type: 'security',
56
+ severity: 'CRITICAL',
57
+ description: 'SQL injection vulnerability',
58
+ lineNumber: 1
59
+ }
60
+ ]
61
+ };
62
+
63
+ const result = validateSecurityIssue(artifactWithSecurity, 'CRITICAL');
64
+ expect(result.valid).toBe(true);
65
+ expect(result.errors).toHaveLength(0);
66
+ }
67
+ ),
68
+ { numRuns: 100 }
69
+ );
70
+ });
71
+
72
+ // **Feature: tournament-seed-bounties, Property 1: Security Issue Detection**
73
+ test('artifacts without CRITICAL security issues are invalid', () => {
74
+ fc.assert(
75
+ fc.property(
76
+ fc.array(fc.record({
77
+ type: fc.constantFrom('performance', 'style', 'logic'), // No security
78
+ severity: fc.constantFrom('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'),
79
+ description: fc.string(),
80
+ lineNumber: fc.integer({ min: 1, max: 100 })
81
+ })),
82
+ (issues) => {
83
+ const artifact = { issues };
84
+ const result = validateSecurityIssue(artifact, 'CRITICAL');
85
+ expect(result.valid).toBe(false);
86
+ expect(result.errors.length).toBeGreaterThan(0);
87
+ }
88
+ ),
89
+ { numRuns: 100 }
90
+ );
91
+ });
92
+
93
+ // **Feature: tournament-seed-bounties, Property 1: Security Issue Detection**
94
+ test('validation is deterministic', () => {
95
+ fc.assert(
96
+ fc.property(
97
+ fc.array(fc.record({
98
+ type: fc.constantFrom('security', 'performance', 'style', 'logic'),
99
+ severity: fc.constantFrom('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'),
100
+ description: fc.string()
101
+ })),
102
+ (issues) => {
103
+ const artifact = { issues };
104
+ const result1 = validateSecurityIssue(artifact, 'CRITICAL');
105
+ const result2 = validateSecurityIssue(artifact, 'CRITICAL');
106
+ expect(result1.valid).toBe(result2.valid);
107
+ expect(result1.errors).toEqual(result2.errors);
108
+ }
109
+ ),
110
+ { numRuns: 100 }
111
+ );
112
+ });
113
+ });
114
+
115
+ /**
116
+ * Property-based tests for Performance Issue Detection
117
+ *
118
+ * **Feature: tournament-seed-bounties, Property 2: Performance Issue Detection**
119
+ * **Validates: Requirements 1.5, 3.4**
120
+ *
121
+ * Property 2: Performance Issue Detection
122
+ * *For any* code review artifact, the performance validator SHALL return valid: true
123
+ * if and only if the artifact contains at least one issue with type "performance".
124
+ */
125
+ describe('Performance Issue Detection - Property Tests', () => {
126
+ // **Feature: tournament-seed-bounties, Property 2: Performance Issue Detection**
127
+ test('artifacts with performance issues are valid', () => {
128
+ fc.assert(
129
+ fc.property(
130
+ fc.array(fc.record({
131
+ type: fc.constantFrom('security', 'performance', 'style', 'logic'),
132
+ severity: fc.constantFrom('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'),
133
+ description: fc.string()
134
+ })),
135
+ (issues) => {
136
+ // Ensure at least one performance issue exists
137
+ const artifactWithPerformance = {
138
+ issues: [
139
+ ...issues,
140
+ {
141
+ type: 'performance',
142
+ severity: 'HIGH',
143
+ description: 'N+1 query detected'
144
+ }
145
+ ]
146
+ };
147
+
148
+ const result = validatePerformanceIssue(artifactWithPerformance);
149
+ expect(result.valid).toBe(true);
150
+ expect(result.errors).toHaveLength(0);
151
+ }
152
+ ),
153
+ { numRuns: 100 }
154
+ );
155
+ });
156
+
157
+ // **Feature: tournament-seed-bounties, Property 2: Performance Issue Detection**
158
+ test('artifacts without performance issues are invalid', () => {
159
+ fc.assert(
160
+ fc.property(
161
+ fc.array(fc.record({
162
+ type: fc.constantFrom('security', 'style', 'logic'), // No performance
163
+ severity: fc.constantFrom('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'),
164
+ description: fc.string()
165
+ })),
166
+ (issues) => {
167
+ const artifact = { issues };
168
+ const result = validatePerformanceIssue(artifact);
169
+ expect(result.valid).toBe(false);
170
+ expect(result.errors.length).toBeGreaterThan(0);
171
+ }
172
+ ),
173
+ { numRuns: 100 }
174
+ );
175
+ });
176
+ });
177
+
178
+ /**
179
+ * Property-based tests for Noise-Free Comment Validation
180
+ *
181
+ * **Feature: tournament-seed-bounties, Property 3: Noise-Free Comment Validation**
182
+ * **Validates: Requirements 1.6, 3.5**
183
+ *
184
+ * Property 3: Noise-Free Comment Validation
185
+ * *For any* code review artifact and source diff, the noise-freeness validator
186
+ * SHALL return valid: true if and only if every comment's lineContent is present
187
+ * in the source diff string.
188
+ */
189
+ describe('Noise-Free Comment Validation - Property Tests', () => {
190
+ // **Feature: tournament-seed-bounties, Property 3: Noise-Free Comment Validation**
191
+ test('comments within source diff are valid', () => {
192
+ fc.assert(
193
+ fc.property(
194
+ fc.array(fc.string({ minLength: 1, maxLength: 100 })),
195
+ fc.array(fc.string({ minLength: 1, maxLength: 50 })),
196
+ (sourceLines, additionalLines) => {
197
+ const sourceDiff = [...sourceLines, ...additionalLines].join('\n');
198
+
199
+ // Create comments that reference lines in the source diff
200
+ const comments = sourceLines.map((line, index) => ({
201
+ lineContent: line,
202
+ comment: `Comment about line ${index}`,
203
+ lineNumber: index + 1
204
+ }));
205
+
206
+ const artifact = { comments };
207
+ const result = validateNoiseFreeness(artifact, sourceDiff);
208
+ expect(result.valid).toBe(true);
209
+ expect(result.errors).toHaveLength(0);
210
+ }
211
+ ),
212
+ { numRuns: 100 }
213
+ );
214
+ });
215
+
216
+ // **Feature: tournament-seed-bounties, Property 3: Noise-Free Comment Validation**
217
+ test('comments outside source diff are invalid', () => {
218
+ fc.assert(
219
+ fc.property(
220
+ fc.string({ minLength: 1, maxLength: 100 }),
221
+ fc.string({ minLength: 1, maxLength: 100 }).filter(s => s !== ''),
222
+ (sourceDiff, outsideLine) => {
223
+ // Ensure outsideLine is not in sourceDiff
224
+ fc.pre(!sourceDiff.includes(outsideLine));
225
+
226
+ const comments = [{
227
+ lineContent: outsideLine,
228
+ comment: 'This comment is outside the diff',
229
+ lineNumber: 1
230
+ }];
231
+
232
+ const artifact = { comments };
233
+ const result = validateNoiseFreeness(artifact, sourceDiff);
234
+ expect(result.valid).toBe(false);
235
+ expect(result.errors.length).toBeGreaterThan(0);
236
+ }
237
+ ),
238
+ { numRuns: 100 }
239
+ );
240
+ });
241
+ });
242
+
243
+ /**
244
+ * Property-based tests for Complexity Reduction Validation
245
+ *
246
+ * **Feature: tournament-seed-bounties, Property 4: Complexity Reduction Validation**
247
+ * **Validates: Requirements 1.7, 3.7**
248
+ *
249
+ * Property 4: Complexity Reduction Validation
250
+ * *For any* code review artifact with a refactor suggestion, the complexity validator
251
+ * SHALL return valid: true if and only if (originalComplexity - suggestedComplexity)
252
+ * >= minReduction.
253
+ */
254
+ describe('Complexity Reduction Validation - Property Tests', () => {
255
+ // **Feature: tournament-seed-bounties, Property 4: Complexity Reduction Validation**
256
+ test('sufficient complexity reduction is valid', () => {
257
+ fc.assert(
258
+ fc.property(
259
+ fc.integer({ min: 5, max: 20 }), // originalComplexity
260
+ fc.integer({ min: 2, max: 10 }), // minReduction
261
+ (originalComplexity, minReduction) => {
262
+ const suggestedComplexity = originalComplexity - minReduction;
263
+
264
+ const artifact = {
265
+ refactorSuggestion: {
266
+ originalComplexity,
267
+ suggestedComplexity,
268
+ description: 'Refactor to reduce complexity'
269
+ }
270
+ };
271
+
272
+ const result = validateComplexityReduction(artifact, minReduction);
273
+ expect(result.valid).toBe(true);
274
+ expect(result.errors).toHaveLength(0);
275
+ }
276
+ ),
277
+ { numRuns: 100 }
278
+ );
279
+ });
280
+
281
+ // **Feature: tournament-seed-bounties, Property 4: Complexity Reduction Validation**
282
+ test('insufficient complexity reduction is invalid', () => {
283
+ fc.assert(
284
+ fc.property(
285
+ fc.integer({ min: 5, max: 20 }), // originalComplexity
286
+ fc.integer({ min: 3, max: 10 }), // minReduction
287
+ (originalComplexity, minReduction) => {
288
+ // Create insufficient reduction (less than minReduction)
289
+ const reduction = Math.max(0, minReduction - 1);
290
+ const suggestedComplexity = originalComplexity - reduction;
291
+
292
+ const artifact = {
293
+ refactorSuggestion: {
294
+ originalComplexity,
295
+ suggestedComplexity,
296
+ description: 'Insufficient refactor'
297
+ }
298
+ };
299
+
300
+ const result = validateComplexityReduction(artifact, minReduction);
301
+ expect(result.valid).toBe(false);
302
+ expect(result.errors.length).toBeGreaterThan(0);
303
+ }
304
+ ),
305
+ { numRuns: 100 }
306
+ );
307
+ });
308
+
309
+ // **Feature: tournament-seed-bounties, Property 4: Complexity Reduction Validation**
310
+ test('missing refactor suggestion is invalid', () => {
311
+ const artifact = {}; // No refactorSuggestion
312
+ const result = validateComplexityReduction(artifact, 2);
313
+ expect(result.valid).toBe(false);
314
+ expect(result.errors.length).toBeGreaterThan(0);
315
+ expect(result.errors[0]).toContain('No refactor suggestion provided');
316
+ });
317
+ });
318
+
319
+ // ============================================================================
320
+ // Lead Generation Battle Property Tests
321
+ // ============================================================================
322
+
323
+ /**
324
+ * Property-based tests for ValidationResult Structure Consistency
325
+ *
326
+ * **Feature: tournament-seed-bounties, Property 7: ValidationResult Structure Consistency**
327
+ * **Validates: Requirements 3.2, 4.2, 1.8, 2.8**
328
+ *
329
+ * Property 7: ValidationResult Structure Consistency
330
+ * *For any* validator function call, the result SHALL have a valid boolean and an
331
+ * errors array where: if valid is true, errors is empty; if valid is false,
332
+ * errors contains at least one descriptive message.
333
+ */
334
+ describe('ValidationResult Structure Consistency - Property Tests', () => {
335
+ // **Feature: tournament-seed-bounties, Property 7: ValidationResult Structure Consistency**
336
+ test('all validators return consistent ValidationResult structure', () => {
337
+ fc.assert(
338
+ fc.property(
339
+ fc.string(),
340
+ (linkedIn) => {
341
+ const result = validateLinkedIn(linkedIn);
342
+
343
+ // Must have valid boolean and errors array
344
+ expect(typeof result.valid).toBe('boolean');
345
+ expect(Array.isArray(result.errors)).toBe(true);
346
+
347
+ // If valid, errors should be empty; if invalid, errors should have content
348
+ if (result.valid) {
349
+ expect(result.errors).toHaveLength(0);
350
+ } else {
351
+ expect(result.errors.length).toBeGreaterThan(0);
352
+ expect(result.errors.every(error => typeof error === 'string')).toBe(true);
353
+ }
354
+ }
355
+ ),
356
+ { numRuns: 100 }
357
+ );
358
+ });
359
+
360
+ // **Feature: tournament-seed-bounties, Property 7: ValidationResult Structure Consistency**
361
+ test('composite validators maintain structure consistency', () => {
362
+ fc.assert(
363
+ fc.property(
364
+ fc.record({
365
+ email: fc.string(),
366
+ companySize: fc.integer({ min: 0, max: 10000 }),
367
+ role: fc.string(),
368
+ linkedIn: fc.string()
369
+ }),
370
+ (artifact) => {
371
+ const result = validateLeadGenPrecision(artifact);
372
+
373
+ // Must have valid boolean and errors array
374
+ expect(typeof result.valid).toBe('boolean');
375
+ expect(Array.isArray(result.errors)).toBe(true);
376
+
377
+ // If valid, errors should be empty; if invalid, errors should have content
378
+ if (result.valid) {
379
+ expect(result.errors).toHaveLength(0);
380
+ } else {
381
+ expect(result.errors.length).toBeGreaterThan(0);
382
+ expect(result.errors.every(error => typeof error === 'string')).toBe(true);
383
+ }
384
+ }
385
+ ),
386
+ { numRuns: 100 }
387
+ );
388
+ });
389
+ });
390
+
391
+ // ============================================================================
392
+ // Integration Property Tests
393
+ // ============================================================================
394
+
395
+ /**
396
+ * Property-based tests for Expert Review Composite Validation
397
+ *
398
+ * Tests the validateExpertReview function which combines all code review validations.
399
+ */
400
+ describe('Expert Review Composite Validation - Property Tests', () => {
401
+ test('expert review combines all validation errors', () => {
402
+ fc.assert(
403
+ fc.property(
404
+ fc.array(fc.record({
405
+ type: fc.constantFrom('style', 'logic'), // No security or performance
406
+ severity: fc.constantFrom('HIGH', 'MEDIUM', 'LOW'), // No CRITICAL
407
+ description: fc.string()
408
+ })),
409
+ fc.array(fc.record({
410
+ lineContent: fc.string(),
411
+ comment: fc.string(),
412
+ lineNumber: fc.integer({ min: 1, max: 100 })
413
+ })),
414
+ fc.string(),
415
+ (issues, comments, sourceDiff) => {
416
+ // Create artifact that will fail multiple validations
417
+ const artifact = {
418
+ issues,
419
+ comments,
420
+ // No refactorSuggestion
421
+ };
422
+
423
+ const result = validateExpertReview(artifact, sourceDiff);
424
+
425
+ // Should be invalid and have multiple errors
426
+ expect(result.valid).toBe(false);
427
+ expect(result.errors.length).toBeGreaterThan(0);
428
+
429
+ // Should contain specific error messages
430
+ const errorText = result.errors.join(' ');
431
+ expect(errorText).toContain('security');
432
+ expect(errorText).toContain('performance');
433
+ expect(errorText).toContain('refactor');
434
+ }
435
+ ),
436
+ { numRuns: 100 }
437
+ );
438
+ });
439
+ });