@evalgate/sdk 2.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 (141) hide show
  1. package/CHANGELOG.md +638 -0
  2. package/README.md +398 -0
  3. package/dist/assertions.d.ts +189 -0
  4. package/dist/assertions.js +662 -0
  5. package/dist/batch.d.ts +68 -0
  6. package/dist/batch.js +179 -0
  7. package/dist/cache.d.ts +65 -0
  8. package/dist/cache.js +131 -0
  9. package/dist/cli/api.d.ts +108 -0
  10. package/dist/cli/api.js +132 -0
  11. package/dist/cli/baseline.d.ts +10 -0
  12. package/dist/cli/baseline.js +172 -0
  13. package/dist/cli/check.d.ts +73 -0
  14. package/dist/cli/check.js +355 -0
  15. package/dist/cli/ci-context.d.ts +6 -0
  16. package/dist/cli/ci-context.js +112 -0
  17. package/dist/cli/ci.d.ts +45 -0
  18. package/dist/cli/ci.js +192 -0
  19. package/dist/cli/config.d.ts +30 -0
  20. package/dist/cli/config.js +230 -0
  21. package/dist/cli/constants.d.ts +15 -0
  22. package/dist/cli/constants.js +18 -0
  23. package/dist/cli/diff.d.ts +173 -0
  24. package/dist/cli/diff.js +685 -0
  25. package/dist/cli/discover.d.ts +84 -0
  26. package/dist/cli/discover.js +419 -0
  27. package/dist/cli/doctor.d.ts +88 -0
  28. package/dist/cli/doctor.js +675 -0
  29. package/dist/cli/env.d.ts +21 -0
  30. package/dist/cli/env.js +42 -0
  31. package/dist/cli/explain.d.ts +58 -0
  32. package/dist/cli/explain.js +561 -0
  33. package/dist/cli/formatters/github.d.ts +8 -0
  34. package/dist/cli/formatters/github.js +135 -0
  35. package/dist/cli/formatters/human.d.ts +6 -0
  36. package/dist/cli/formatters/human.js +110 -0
  37. package/dist/cli/formatters/json.d.ts +6 -0
  38. package/dist/cli/formatters/json.js +10 -0
  39. package/dist/cli/formatters/pr-comment.d.ts +12 -0
  40. package/dist/cli/formatters/pr-comment.js +103 -0
  41. package/dist/cli/formatters/types.d.ts +103 -0
  42. package/dist/cli/formatters/types.js +8 -0
  43. package/dist/cli/gate.d.ts +21 -0
  44. package/dist/cli/gate.js +179 -0
  45. package/dist/cli/impact-analysis.d.ts +63 -0
  46. package/dist/cli/impact-analysis.js +252 -0
  47. package/dist/cli/index.d.ts +9 -0
  48. package/dist/cli/index.js +332 -0
  49. package/dist/cli/init.d.ts +16 -0
  50. package/dist/cli/init.js +292 -0
  51. package/dist/cli/manifest.d.ts +103 -0
  52. package/dist/cli/manifest.js +282 -0
  53. package/dist/cli/migrate.d.ts +41 -0
  54. package/dist/cli/migrate.js +349 -0
  55. package/dist/cli/policy-packs.d.ts +23 -0
  56. package/dist/cli/policy-packs.js +89 -0
  57. package/dist/cli/print-config.d.ts +29 -0
  58. package/dist/cli/print-config.js +270 -0
  59. package/dist/cli/profiles.d.ts +28 -0
  60. package/dist/cli/profiles.js +30 -0
  61. package/dist/cli/reason-codes.d.ts +17 -0
  62. package/dist/cli/reason-codes.js +19 -0
  63. package/dist/cli/regression-gate.d.ts +15 -0
  64. package/dist/cli/regression-gate.js +341 -0
  65. package/dist/cli/render/snippet.d.ts +5 -0
  66. package/dist/cli/render/snippet.js +15 -0
  67. package/dist/cli/render/sort.d.ts +10 -0
  68. package/dist/cli/render/sort.js +24 -0
  69. package/dist/cli/report/build-check-report.d.ts +19 -0
  70. package/dist/cli/report/build-check-report.js +132 -0
  71. package/dist/cli/run.d.ts +101 -0
  72. package/dist/cli/run.js +395 -0
  73. package/dist/cli/share.d.ts +17 -0
  74. package/dist/cli/share.js +91 -0
  75. package/dist/cli/upgrade.d.ts +15 -0
  76. package/dist/cli/upgrade.js +492 -0
  77. package/dist/cli/workspace.d.ts +31 -0
  78. package/dist/cli/workspace.js +68 -0
  79. package/dist/client.d.ts +368 -0
  80. package/dist/client.js +893 -0
  81. package/dist/client.request.test.d.ts +1 -0
  82. package/dist/client.request.test.js +232 -0
  83. package/dist/context.d.ts +134 -0
  84. package/dist/context.js +215 -0
  85. package/dist/errors.d.ts +82 -0
  86. package/dist/errors.js +298 -0
  87. package/dist/export.d.ts +195 -0
  88. package/dist/export.js +344 -0
  89. package/dist/index.d.ts +44 -0
  90. package/dist/index.js +153 -0
  91. package/dist/integrations/anthropic.d.ts +91 -0
  92. package/dist/integrations/anthropic.js +163 -0
  93. package/dist/integrations/openai-eval.d.ts +57 -0
  94. package/dist/integrations/openai-eval.js +232 -0
  95. package/dist/integrations/openai.d.ts +92 -0
  96. package/dist/integrations/openai.js +160 -0
  97. package/dist/local.d.ts +39 -0
  98. package/dist/local.js +148 -0
  99. package/dist/logger.d.ts +128 -0
  100. package/dist/logger.js +227 -0
  101. package/dist/matchers/index.d.ts +1 -0
  102. package/dist/matchers/index.js +6 -0
  103. package/dist/matchers/to-pass-gate.d.ts +29 -0
  104. package/dist/matchers/to-pass-gate.js +35 -0
  105. package/dist/pagination.d.ts +74 -0
  106. package/dist/pagination.js +139 -0
  107. package/dist/regression.d.ts +100 -0
  108. package/dist/regression.js +44 -0
  109. package/dist/runtime/adapters/config-to-dsl.d.ts +33 -0
  110. package/dist/runtime/adapters/config-to-dsl.js +400 -0
  111. package/dist/runtime/adapters/testsuite-to-dsl.d.ts +63 -0
  112. package/dist/runtime/adapters/testsuite-to-dsl.js +276 -0
  113. package/dist/runtime/context.d.ts +26 -0
  114. package/dist/runtime/context.js +74 -0
  115. package/dist/runtime/eval.d.ts +46 -0
  116. package/dist/runtime/eval.js +244 -0
  117. package/dist/runtime/execution-mode.d.ts +80 -0
  118. package/dist/runtime/execution-mode.js +357 -0
  119. package/dist/runtime/executor.d.ts +16 -0
  120. package/dist/runtime/executor.js +152 -0
  121. package/dist/runtime/registry.d.ts +78 -0
  122. package/dist/runtime/registry.js +403 -0
  123. package/dist/runtime/run-report.d.ts +200 -0
  124. package/dist/runtime/run-report.js +222 -0
  125. package/dist/runtime/types.d.ts +356 -0
  126. package/dist/runtime/types.js +76 -0
  127. package/dist/snapshot.d.ts +176 -0
  128. package/dist/snapshot.js +322 -0
  129. package/dist/streaming.d.ts +173 -0
  130. package/dist/streaming.js +268 -0
  131. package/dist/testing.d.ts +273 -0
  132. package/dist/testing.js +317 -0
  133. package/dist/types.d.ts +754 -0
  134. package/dist/types.js +54 -0
  135. package/dist/utils/input-hash.d.ts +8 -0
  136. package/dist/utils/input-hash.js +41 -0
  137. package/dist/version.d.ts +7 -0
  138. package/dist/version.js +10 -0
  139. package/dist/workflows.d.ts +389 -0
  140. package/dist/workflows.js +671 -0
  141. package/package.json +117 -0
@@ -0,0 +1,662 @@
1
+ "use strict";
2
+ /**
3
+ * Enhanced Assertion Library
4
+ * Tier 1.3: Pre-Built Assertion Library with 20+ built-in assertions
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { expect } from '@ai-eval-platform/sdk';
9
+ *
10
+ * const output = "Hello, how can I help you today?";
11
+ *
12
+ * expect(output).toContainKeywords(['help', 'today']);
13
+ * expect(output).toHaveSentiment('positive');
14
+ * expect(output).toMatchPattern(/help/i);
15
+ * expect(output).toHaveLength({ min: 10, max: 100 });
16
+ * ```
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.Expectation = exports.AssertionError = void 0;
20
+ exports.expect = expect;
21
+ exports.runAssertions = runAssertions;
22
+ exports.containsKeywords = containsKeywords;
23
+ exports.matchesPattern = matchesPattern;
24
+ exports.hasLength = hasLength;
25
+ exports.containsJSON = containsJSON;
26
+ exports.notContainsPII = notContainsPII;
27
+ exports.hasSentiment = hasSentiment;
28
+ exports.similarTo = similarTo;
29
+ exports.withinRange = withinRange;
30
+ exports.isValidEmail = isValidEmail;
31
+ exports.isValidURL = isValidURL;
32
+ exports.hasNoHallucinations = hasNoHallucinations;
33
+ exports.matchesSchema = matchesSchema;
34
+ exports.hasReadabilityScore = hasReadabilityScore;
35
+ exports.containsLanguage = containsLanguage;
36
+ exports.hasFactualAccuracy = hasFactualAccuracy;
37
+ exports.respondedWithinTime = respondedWithinTime;
38
+ exports.hasNoToxicity = hasNoToxicity;
39
+ exports.followsInstructions = followsInstructions;
40
+ exports.containsAllRequiredFields = containsAllRequiredFields;
41
+ exports.hasValidCodeSyntax = hasValidCodeSyntax;
42
+ class AssertionError extends Error {
43
+ constructor(message, expected, actual) {
44
+ super(message);
45
+ this.expected = expected;
46
+ this.actual = actual;
47
+ this.name = "AssertionError";
48
+ Object.setPrototypeOf(this, AssertionError.prototype);
49
+ }
50
+ }
51
+ exports.AssertionError = AssertionError;
52
+ /**
53
+ * Fluent assertion builder
54
+ */
55
+ class Expectation {
56
+ constructor(value) {
57
+ this.value = value;
58
+ }
59
+ /**
60
+ * Assert value equals expected
61
+ * @example expect(output).toEqual("Hello")
62
+ */
63
+ toEqual(expected, message) {
64
+ const passed = JSON.stringify(this.value) === JSON.stringify(expected);
65
+ return {
66
+ name: "toEqual",
67
+ passed,
68
+ expected,
69
+ actual: this.value,
70
+ message: message ||
71
+ (passed
72
+ ? "Values are equal"
73
+ : `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(this.value)}`),
74
+ };
75
+ }
76
+ /**
77
+ * Assert value contains substring
78
+ * @example expect(output).toContain("help")
79
+ */
80
+ toContain(substring, message) {
81
+ const text = String(this.value);
82
+ const passed = text.includes(substring);
83
+ return {
84
+ name: "toContain",
85
+ passed,
86
+ expected: substring,
87
+ actual: text,
88
+ message: message ||
89
+ (passed
90
+ ? `Text contains "${substring}"`
91
+ : `Text does not contain "${substring}"`),
92
+ };
93
+ }
94
+ /**
95
+ * Assert value contains all keywords
96
+ * @example expect(output).toContainKeywords(['help', 'support'])
97
+ */
98
+ toContainKeywords(keywords, message) {
99
+ const text = String(this.value).toLowerCase();
100
+ const missingKeywords = keywords.filter((k) => !text.includes(k.toLowerCase()));
101
+ const passed = missingKeywords.length === 0;
102
+ return {
103
+ name: "toContainKeywords",
104
+ passed,
105
+ expected: keywords,
106
+ actual: text,
107
+ message: message ||
108
+ (passed
109
+ ? `Contains all keywords`
110
+ : `Missing keywords: ${missingKeywords.join(", ")}`),
111
+ };
112
+ }
113
+ /**
114
+ * Assert value does not contain substring
115
+ * @example expect(output).toNotContain("error")
116
+ */
117
+ toNotContain(substring, message) {
118
+ const text = String(this.value);
119
+ const passed = !text.includes(substring);
120
+ return {
121
+ name: "toNotContain",
122
+ passed,
123
+ expected: `not containing "${substring}"`,
124
+ actual: text,
125
+ message: message ||
126
+ (passed
127
+ ? `Text does not contain "${substring}"`
128
+ : `Text contains "${substring}"`),
129
+ };
130
+ }
131
+ /**
132
+ * Assert value does not contain PII (emails, phone numbers, SSN)
133
+ * @example expect(output).toNotContainPII()
134
+ */
135
+ toNotContainPII(message) {
136
+ const text = String(this.value);
137
+ const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/;
138
+ const phonePattern = /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/;
139
+ const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/;
140
+ const foundPII = [];
141
+ if (emailPattern.test(text))
142
+ foundPII.push("email");
143
+ if (phonePattern.test(text))
144
+ foundPII.push("phone number");
145
+ if (ssnPattern.test(text))
146
+ foundPII.push("SSN");
147
+ const passed = foundPII.length === 0;
148
+ return {
149
+ name: "toNotContainPII",
150
+ passed,
151
+ expected: "no PII",
152
+ actual: foundPII.length > 0 ? `Found: ${foundPII.join(", ")}` : "no PII",
153
+ message: message ||
154
+ (passed ? "No PII detected" : `PII detected: ${foundPII.join(", ")}`),
155
+ };
156
+ }
157
+ /**
158
+ * Assert value matches regular expression
159
+ * @example expect(output).toMatchPattern(/\d{3}-\d{3}-\d{4}/)
160
+ */
161
+ toMatchPattern(pattern, message) {
162
+ const text = String(this.value);
163
+ const passed = pattern.test(text);
164
+ return {
165
+ name: "toMatchPattern",
166
+ passed,
167
+ expected: pattern.toString(),
168
+ actual: text,
169
+ message: message ||
170
+ (passed
171
+ ? `Matches pattern ${pattern}`
172
+ : `Does not match pattern ${pattern}`),
173
+ };
174
+ }
175
+ /**
176
+ * Assert value is valid JSON
177
+ * @example expect(output).toBeValidJSON()
178
+ */
179
+ toBeValidJSON(message) {
180
+ let passed = false;
181
+ let parsedJson = null;
182
+ try {
183
+ parsedJson = JSON.parse(String(this.value));
184
+ passed = true;
185
+ }
186
+ catch (_e) {
187
+ passed = false;
188
+ }
189
+ return {
190
+ name: "toBeValidJSON",
191
+ passed,
192
+ expected: "valid JSON",
193
+ actual: passed ? parsedJson : this.value,
194
+ message: message || (passed ? "Valid JSON" : "Invalid JSON"),
195
+ };
196
+ }
197
+ /**
198
+ * Assert JSON matches schema
199
+ * @example expect(output).toMatchJSON({ status: 'success' })
200
+ */
201
+ toMatchJSON(schema, message) {
202
+ let passed = false;
203
+ let parsedJson = null;
204
+ try {
205
+ parsedJson = JSON.parse(String(this.value));
206
+ const requiredKeys = Object.keys(schema);
207
+ const actualKeys = Object.keys(parsedJson);
208
+ passed = requiredKeys.every((key) => actualKeys.includes(key));
209
+ }
210
+ catch (_e) {
211
+ passed = false;
212
+ }
213
+ return {
214
+ name: "toMatchJSON",
215
+ passed,
216
+ expected: schema,
217
+ actual: parsedJson,
218
+ message: message ||
219
+ (passed ? "JSON matches schema" : "JSON does not match schema"),
220
+ };
221
+ }
222
+ /**
223
+ * Assert value has expected sentiment
224
+ * @example expect(output).toHaveSentiment('positive')
225
+ */
226
+ toHaveSentiment(expected, message) {
227
+ const text = String(this.value).toLowerCase();
228
+ const positiveWords = [
229
+ "good",
230
+ "great",
231
+ "excellent",
232
+ "amazing",
233
+ "wonderful",
234
+ "fantastic",
235
+ "love",
236
+ "best",
237
+ "happy",
238
+ "helpful",
239
+ ];
240
+ const negativeWords = [
241
+ "bad",
242
+ "terrible",
243
+ "awful",
244
+ "horrible",
245
+ "worst",
246
+ "hate",
247
+ "poor",
248
+ "disappointing",
249
+ "sad",
250
+ "useless",
251
+ ];
252
+ const positiveCount = positiveWords.filter((w) => text.includes(w)).length;
253
+ const negativeCount = negativeWords.filter((w) => text.includes(w)).length;
254
+ let actual;
255
+ if (positiveCount > negativeCount)
256
+ actual = "positive";
257
+ else if (negativeCount > positiveCount)
258
+ actual = "negative";
259
+ else
260
+ actual = "neutral";
261
+ const passed = actual === expected;
262
+ return {
263
+ name: "toHaveSentiment",
264
+ passed,
265
+ expected,
266
+ actual,
267
+ message: message ||
268
+ (passed
269
+ ? `Sentiment is ${expected}`
270
+ : `Expected ${expected}, got ${actual}`),
271
+ };
272
+ }
273
+ /**
274
+ * Assert string length is within range
275
+ * @example expect(output).toHaveLength({ min: 10, max: 100 })
276
+ */
277
+ toHaveLength(range, message) {
278
+ const length = String(this.value).length;
279
+ const passed = (range.min === undefined || length >= range.min) &&
280
+ (range.max === undefined || length <= range.max);
281
+ return {
282
+ name: "toHaveLength",
283
+ passed,
284
+ expected: range,
285
+ actual: length,
286
+ message: message ||
287
+ (passed
288
+ ? `Length ${length} is within range`
289
+ : `Length ${length} not in range`),
290
+ };
291
+ }
292
+ /**
293
+ * Assert no hallucinations (all ground truth facts present)
294
+ * @example expect(output).toNotHallucinate(['fact1', 'fact2'])
295
+ */
296
+ toNotHallucinate(groundTruth, message) {
297
+ const text = String(this.value).toLowerCase();
298
+ const missingFacts = groundTruth.filter((fact) => !text.includes(fact.toLowerCase()));
299
+ const passed = missingFacts.length === 0;
300
+ return {
301
+ name: "toNotHallucinate",
302
+ passed,
303
+ expected: "all ground truth facts",
304
+ actual: missingFacts.length > 0
305
+ ? `Missing: ${missingFacts.join(", ")}`
306
+ : "all facts present",
307
+ message: message ||
308
+ (passed
309
+ ? "No hallucinations detected"
310
+ : `Missing facts: ${missingFacts.join(", ")}`),
311
+ };
312
+ }
313
+ /**
314
+ * Assert response latency is within limit
315
+ * @example expect(durationMs).toBeFasterThan(1000)
316
+ */
317
+ toBeFasterThan(maxMs, message) {
318
+ const duration = Number(this.value);
319
+ const passed = duration <= maxMs;
320
+ return {
321
+ name: "toBeFasterThan",
322
+ passed,
323
+ expected: `<= ${maxMs}ms`,
324
+ actual: `${duration}ms`,
325
+ message: message ||
326
+ (passed
327
+ ? `${duration}ms within limit`
328
+ : `${duration}ms exceeds ${maxMs}ms`),
329
+ };
330
+ }
331
+ /**
332
+ * Assert value is truthy
333
+ * @example expect(result).toBeTruthy()
334
+ */
335
+ toBeTruthy(message) {
336
+ const passed = Boolean(this.value);
337
+ return {
338
+ name: "toBeTruthy",
339
+ passed,
340
+ expected: "truthy value",
341
+ actual: this.value,
342
+ message: message || (passed ? "Value is truthy" : "Value is falsy"),
343
+ };
344
+ }
345
+ /**
346
+ * Assert value is falsy
347
+ * @example expect(error).toBeFalsy()
348
+ */
349
+ toBeFalsy(message) {
350
+ const passed = !this.value;
351
+ return {
352
+ name: "toBeFalsy",
353
+ passed,
354
+ expected: "falsy value",
355
+ actual: this.value,
356
+ message: message || (passed ? "Value is falsy" : "Value is truthy"),
357
+ };
358
+ }
359
+ /**
360
+ * Assert value is greater than expected
361
+ * @example expect(score).toBeGreaterThan(0.8)
362
+ */
363
+ toBeGreaterThan(expected, message) {
364
+ const value = Number(this.value);
365
+ const passed = value > expected;
366
+ return {
367
+ name: "toBeGreaterThan",
368
+ passed,
369
+ expected: `> ${expected}`,
370
+ actual: value,
371
+ message: message ||
372
+ (passed ? `${value} > ${expected}` : `${value} <= ${expected}`),
373
+ };
374
+ }
375
+ /**
376
+ * Assert value is less than expected
377
+ * @example expect(errorRate).toBeLessThan(0.05)
378
+ */
379
+ toBeLessThan(expected, message) {
380
+ const value = Number(this.value);
381
+ const passed = value < expected;
382
+ return {
383
+ name: "toBeLessThan",
384
+ passed,
385
+ expected: `< ${expected}`,
386
+ actual: value,
387
+ message: message ||
388
+ (passed ? `${value} < ${expected}` : `${value} >= ${expected}`),
389
+ };
390
+ }
391
+ /**
392
+ * Assert value is between min and max
393
+ * @example expect(score).toBeBetween(0, 1)
394
+ */
395
+ toBeBetween(min, max, message) {
396
+ const value = Number(this.value);
397
+ const passed = value >= min && value <= max;
398
+ return {
399
+ name: "toBeBetween",
400
+ passed,
401
+ expected: `between ${min} and ${max}`,
402
+ actual: value,
403
+ message: message ||
404
+ (passed ? `${value} is within range` : `${value} is outside range`),
405
+ };
406
+ }
407
+ /**
408
+ * Assert value contains code block
409
+ * @example expect(output).toContainCode()
410
+ */
411
+ toContainCode(message) {
412
+ const text = String(this.value);
413
+ const hasCodeBlock = /```[\s\S]*?```/.test(text) || /<code>[\s\S]*?<\/code>/.test(text);
414
+ return {
415
+ name: "toContainCode",
416
+ passed: hasCodeBlock,
417
+ expected: "code block",
418
+ actual: text,
419
+ message: message ||
420
+ (hasCodeBlock ? "Contains code block" : "No code block found"),
421
+ };
422
+ }
423
+ /**
424
+ * Assert value is professional tone (no profanity)
425
+ * @example expect(output).toBeProfessional()
426
+ */
427
+ toBeProfessional(message) {
428
+ const text = String(this.value).toLowerCase();
429
+ const profanity = ["damn", "hell", "shit", "fuck", "ass", "bitch", "crap"];
430
+ const foundProfanity = profanity.filter((word) => text.includes(word));
431
+ const passed = foundProfanity.length === 0;
432
+ return {
433
+ name: "toBeProfessional",
434
+ passed,
435
+ expected: "professional tone",
436
+ actual: foundProfanity.length > 0
437
+ ? `Found: ${foundProfanity.join(", ")}`
438
+ : "professional",
439
+ message: message ||
440
+ (passed
441
+ ? "Professional tone"
442
+ : `Unprofessional language: ${foundProfanity.join(", ")}`),
443
+ };
444
+ }
445
+ /**
446
+ * Assert value has proper grammar (basic checks)
447
+ * @example expect(output).toHaveProperGrammar()
448
+ */
449
+ toHaveProperGrammar(message) {
450
+ const text = String(this.value);
451
+ const issues = [];
452
+ // Check for double spaces
453
+ if (/ {2,}/.test(text))
454
+ issues.push("double spaces");
455
+ // Check for missing periods at end
456
+ if (text.length > 10 && !/[.!?]$/.test(text.trim()))
457
+ issues.push("missing ending punctuation");
458
+ // Check for lowercase sentence starts
459
+ if (/\.\s+[a-z]/.test(text))
460
+ issues.push("lowercase after period");
461
+ const passed = issues.length === 0;
462
+ return {
463
+ name: "toHaveProperGrammar",
464
+ passed,
465
+ expected: "proper grammar",
466
+ actual: issues.length > 0 ? `Issues: ${issues.join(", ")}` : "proper grammar",
467
+ message: message ||
468
+ (passed ? "Proper grammar" : `Grammar issues: ${issues.join(", ")}`),
469
+ };
470
+ }
471
+ }
472
+ exports.Expectation = Expectation;
473
+ /**
474
+ * Create an expectation for fluent assertions
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * const output = "Hello, how can I help you?";
479
+ *
480
+ * expect(output).toContain("help");
481
+ * expect(output).toHaveSentiment('positive');
482
+ * expect(output).toHaveLength({ min: 10, max: 100 });
483
+ * ```
484
+ */
485
+ function expect(value) {
486
+ return new Expectation(value);
487
+ }
488
+ /**
489
+ * Run multiple assertions and collect results
490
+ *
491
+ * @example
492
+ * ```typescript
493
+ * const results = runAssertions([
494
+ * () => expect(output).toContain("help"),
495
+ * () => expect(output).toHaveSentiment('positive'),
496
+ * () => expect(output).toHaveLength({ min: 10 })
497
+ * ]);
498
+ *
499
+ * const allPassed = results.every(r => r.passed);
500
+ * ```
501
+ */
502
+ function runAssertions(assertions) {
503
+ return assertions.map((assertion) => {
504
+ try {
505
+ return assertion();
506
+ }
507
+ catch (error) {
508
+ return {
509
+ name: "unknown",
510
+ passed: false,
511
+ expected: null,
512
+ actual: null,
513
+ message: error instanceof Error ? error.message : "Unknown error",
514
+ };
515
+ }
516
+ });
517
+ }
518
+ // Standalone assertion functions
519
+ function containsKeywords(text, keywords) {
520
+ return keywords.every((keyword) => text.toLowerCase().includes(keyword.toLowerCase()));
521
+ }
522
+ function matchesPattern(text, pattern) {
523
+ return pattern.test(text);
524
+ }
525
+ function hasLength(text, range) {
526
+ const length = text.length;
527
+ if (range.min !== undefined && length < range.min)
528
+ return false;
529
+ if (range.max !== undefined && length > range.max)
530
+ return false;
531
+ return true;
532
+ }
533
+ function containsJSON(text) {
534
+ try {
535
+ JSON.parse(text);
536
+ return true;
537
+ }
538
+ catch {
539
+ return false;
540
+ }
541
+ }
542
+ function notContainsPII(text) {
543
+ // Simple PII detection patterns
544
+ const piiPatterns = [
545
+ /\b\d{3}-\d{2}-\d{4}\b/, // SSN
546
+ /\b\d{3}\.\d{3}\.\d{4}\b/, // SSN with dots
547
+ /\b\d{10}\b/, // Phone number
548
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, // Email
549
+ /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/, // IP address
550
+ ];
551
+ return !piiPatterns.some((pattern) => pattern.test(text));
552
+ }
553
+ function hasSentiment(text, expected) {
554
+ // This is a simplified implementation
555
+ const positiveWords = ["good", "great", "excellent", "awesome"];
556
+ const negativeWords = ["bad", "terrible", "awful", "poor"];
557
+ const words = text.toLowerCase().split(/\s+/);
558
+ const positiveCount = words.filter((word) => positiveWords.includes(word)).length;
559
+ const negativeCount = words.filter((word) => negativeWords.includes(word)).length;
560
+ if (expected === "positive")
561
+ return positiveCount > negativeCount;
562
+ if (expected === "negative")
563
+ return negativeCount > positiveCount;
564
+ return positiveCount === negativeCount; // neutral
565
+ }
566
+ function similarTo(text1, text2, threshold = 0.8) {
567
+ // Simple similarity check - in a real app, you'd use a proper string similarity algorithm
568
+ const words1 = new Set(text1.toLowerCase().split(/\s+/));
569
+ const words2 = new Set(text2.toLowerCase().split(/\s+/));
570
+ const intersection = new Set([...words1].filter((word) => words2.has(word)));
571
+ const union = new Set([...words1, ...words2]);
572
+ return intersection.size / union.size >= threshold;
573
+ }
574
+ function withinRange(value, min, max) {
575
+ return value >= min && value <= max;
576
+ }
577
+ function isValidEmail(email) {
578
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
579
+ }
580
+ function isValidURL(url) {
581
+ try {
582
+ new URL(url);
583
+ return true;
584
+ }
585
+ catch {
586
+ return false;
587
+ }
588
+ }
589
+ function hasNoHallucinations(text, groundTruth) {
590
+ // This is a simplified implementation
591
+ return groundTruth.every((truth) => text.includes(truth));
592
+ }
593
+ function matchesSchema(value, schema) {
594
+ // This is a simplified implementation
595
+ if (typeof value !== "object" || value === null)
596
+ return false;
597
+ return Object.keys(schema).every((key) => key in value);
598
+ }
599
+ function hasReadabilityScore(text, minScore) {
600
+ // This is a simplified implementation
601
+ const words = text.split(/\s+/).length;
602
+ const sentences = text.split(/[.!?]+/).length;
603
+ const score = 206.835 - 1.015 * (words / sentences) - 84.6 * (syllables(text) / words);
604
+ return score >= minScore;
605
+ }
606
+ function syllables(word) {
607
+ // Simple syllable counter
608
+ word = word.toLowerCase();
609
+ if (word.length <= 3)
610
+ return 1;
611
+ return word
612
+ .replace(/[^aeiouy]+/g, " ")
613
+ .trim()
614
+ .split(/\s+/).length;
615
+ }
616
+ function containsLanguage(text, language) {
617
+ // This is a simplified implementation
618
+ // In a real app, you'd use a language detection library
619
+ const languageKeywords = {
620
+ en: ["the", "and", "you", "that", "was", "for", "are", "with"],
621
+ es: ["el", "la", "los", "las", "de", "que", "y", "en"],
622
+ fr: ["le", "la", "les", "de", "et", "à", "un", "une"],
623
+ };
624
+ const keywords = languageKeywords[language.toLowerCase()] || [];
625
+ return keywords.some((keyword) => text.toLowerCase().includes(keyword));
626
+ }
627
+ function hasFactualAccuracy(text, facts) {
628
+ // This is a simplified implementation
629
+ return facts.every((fact) => text.includes(fact));
630
+ }
631
+ function respondedWithinTime(startTime, maxMs) {
632
+ return Date.now() - startTime <= maxMs;
633
+ }
634
+ function hasNoToxicity(text) {
635
+ // This is a simplified implementation
636
+ const toxicWords = ["hate", "stupid", "idiot", "dumb"];
637
+ return !toxicWords.some((word) => text.toLowerCase().includes(word));
638
+ }
639
+ function followsInstructions(text, instructions) {
640
+ return instructions.every((instruction) => {
641
+ if (instruction.startsWith("!")) {
642
+ return !text.includes(instruction.slice(1));
643
+ }
644
+ return text.includes(instruction);
645
+ });
646
+ }
647
+ function containsAllRequiredFields(obj, requiredFields) {
648
+ return requiredFields.every((field) => obj && typeof obj === "object" && field in obj);
649
+ }
650
+ function hasValidCodeSyntax(code, language) {
651
+ // This is a simplified implementation
652
+ // In a real app, you'd use a proper parser for each language
653
+ try {
654
+ if (language === "json")
655
+ JSON.parse(code);
656
+ // Add more language validations as needed
657
+ return true;
658
+ }
659
+ catch {
660
+ return false;
661
+ }
662
+ }