@far-world-labs/verblets 0.1.1 → 0.1.2

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 (300) hide show
  1. package/.cursor/launch.json +30 -0
  2. package/.cursor/settings.json +20 -0
  3. package/.github/workflows/branch-protection.yml +22 -0
  4. package/.github/workflows/ci.yml +117 -0
  5. package/.prettierrc +6 -0
  6. package/.release-it.json +4 -1
  7. package/.vscode/launch.json +31 -0
  8. package/AGENTS.md +220 -0
  9. package/DEVELOPING.md +105 -0
  10. package/README.md +671 -0
  11. package/eslint.config.js +80 -0
  12. package/package.json +28 -16
  13. package/scripts/generate-test/index.js +29 -3
  14. package/scripts/runner/index.js +26 -0
  15. package/scripts/simple-editor/index.js +29 -18
  16. package/scripts/summarize-files/index.js +28 -4
  17. package/src/chains/README.md +30 -0
  18. package/src/chains/anonymize/README.md +21 -0
  19. package/src/chains/anonymize/index.examples.js +75 -0
  20. package/src/chains/anonymize/index.js +121 -0
  21. package/src/chains/anonymize/index.spec.js +78 -0
  22. package/src/chains/bulk-central-tendency/index.examples.js +138 -0
  23. package/src/chains/bulk-central-tendency/index.js +91 -0
  24. package/src/chains/bulk-filter/README.md +21 -0
  25. package/src/chains/bulk-filter/index.examples.js +22 -0
  26. package/src/chains/bulk-filter/index.js +58 -0
  27. package/src/chains/bulk-filter/index.spec.js +38 -0
  28. package/src/chains/bulk-find/README.md +16 -0
  29. package/src/chains/bulk-find/index.examples.js +20 -0
  30. package/src/chains/bulk-find/index.js +30 -0
  31. package/src/chains/bulk-find/index.spec.js +26 -0
  32. package/src/chains/bulk-group/README.md +23 -0
  33. package/src/chains/bulk-group/index.examples.js +18 -0
  34. package/src/chains/bulk-group/index.js +34 -0
  35. package/src/chains/bulk-group/index.spec.js +41 -0
  36. package/src/chains/bulk-map/README.md +43 -0
  37. package/src/chains/bulk-map/index.examples.js +17 -0
  38. package/src/chains/bulk-map/index.js +86 -0
  39. package/src/chains/bulk-map/index.spec.js +44 -0
  40. package/src/chains/bulk-reduce/README.md +12 -0
  41. package/src/chains/bulk-reduce/index.examples.js +15 -0
  42. package/src/chains/bulk-reduce/index.js +13 -0
  43. package/src/chains/bulk-reduce/index.spec.js +25 -0
  44. package/src/chains/bulk-score/README.md +16 -0
  45. package/src/chains/bulk-score/bulk-score-result.json +18 -0
  46. package/src/chains/bulk-score/index.examples.js +22 -0
  47. package/src/chains/bulk-score/index.js +133 -0
  48. package/src/chains/bulk-score/index.spec.js +30 -0
  49. package/src/chains/category-samples/README.md +61 -0
  50. package/src/chains/category-samples/index.examples.js +103 -0
  51. package/src/chains/category-samples/index.js +134 -0
  52. package/src/chains/collect-terms/README.md +12 -0
  53. package/src/chains/collect-terms/index.examples.js +16 -0
  54. package/src/chains/collect-terms/index.js +44 -0
  55. package/src/chains/collect-terms/index.spec.js +25 -0
  56. package/src/chains/date/README.md +12 -0
  57. package/src/chains/date/index.examples.js +47 -0
  58. package/src/chains/date/index.js +74 -0
  59. package/src/chains/date/index.spec.js +62 -0
  60. package/src/chains/disambiguate/README.md +22 -0
  61. package/src/chains/disambiguate/disambiguate-meanings-result.json +16 -0
  62. package/src/chains/disambiguate/index.examples.js +18 -0
  63. package/src/chains/disambiguate/index.js +92 -0
  64. package/src/chains/disambiguate/index.spec.js +25 -0
  65. package/src/chains/dismantle/README.md +67 -0
  66. package/src/chains/dismantle/dismantle.examples.js +27 -0
  67. package/src/chains/dismantle/index.js +6 -17
  68. package/src/chains/dismantle/index.spec.js +1 -2
  69. package/src/chains/expect/README.md +171 -0
  70. package/src/chains/expect/index.examples.js +146 -0
  71. package/src/chains/expect/index.js +173 -0
  72. package/src/chains/expect/index.spec.js +324 -0
  73. package/src/chains/filter-ambiguous/README.md +11 -0
  74. package/src/chains/filter-ambiguous/index.examples.js +20 -0
  75. package/src/chains/filter-ambiguous/index.js +49 -0
  76. package/src/chains/filter-ambiguous/index.spec.js +31 -0
  77. package/src/chains/glossary/README.md +19 -0
  78. package/src/chains/glossary/index.examples.js +386 -0
  79. package/src/chains/glossary/index.js +75 -0
  80. package/src/chains/glossary/index.spec.js +19 -0
  81. package/src/chains/intersections/README.md +152 -0
  82. package/src/chains/intersections/index.examples.js +279 -0
  83. package/src/chains/intersections/index.js +366 -0
  84. package/src/chains/intersections/intersection-result.json +38 -0
  85. package/src/chains/list/index.examples.js +12 -16
  86. package/src/chains/list/index.js +106 -53
  87. package/src/chains/list/index.spec.js +3 -9
  88. package/src/chains/list/list-result.json +16 -0
  89. package/src/chains/llm-logger/README.md +208 -0
  90. package/src/chains/llm-logger/index.js +205 -0
  91. package/src/chains/llm-logger/index.spec.js +330 -0
  92. package/src/chains/questions/index.examples.js +2 -1
  93. package/src/chains/questions/index.js +14 -15
  94. package/src/chains/scan-js/index.js +6 -9
  95. package/src/chains/set-interval/README.md +81 -0
  96. package/src/chains/set-interval/index.examples.js +36 -0
  97. package/src/chains/set-interval/index.js +131 -0
  98. package/src/chains/set-interval/index.spec.js +70 -0
  99. package/src/chains/socratic/README.md +17 -0
  100. package/src/chains/socratic/index.js +64 -0
  101. package/src/chains/socratic/index.spec.js +24 -0
  102. package/src/chains/sort/index.examples.js +3 -7
  103. package/src/chains/sort/index.js +65 -15
  104. package/src/chains/sort/index.spec.js +5 -8
  105. package/src/chains/sort/sort-result.json +16 -0
  106. package/src/chains/summary-map/README.md +9 -1
  107. package/src/chains/summary-map/index.examples.js +9 -2
  108. package/src/chains/summary-map/index.js +43 -25
  109. package/src/chains/summary-map/index.spec.js +78 -3
  110. package/src/chains/test/index.js +9 -13
  111. package/src/chains/test-advice/index.js +4 -5
  112. package/src/chains/themes/README.md +20 -0
  113. package/src/chains/themes/index.examples.js +17 -0
  114. package/src/chains/themes/index.js +28 -0
  115. package/src/chains/themes/index.spec.js +19 -0
  116. package/src/chains/veiled-variants/index.examples.js +18 -0
  117. package/src/chains/veiled-variants/index.js +107 -0
  118. package/src/chains/veiled-variants/index.spec.js +40 -0
  119. package/src/constants/common.js +0 -2
  120. package/src/constants/models.js +172 -0
  121. package/src/index.js +178 -18
  122. package/src/json-schemas/README.md +13 -0
  123. package/src/json-schemas/index.js +8 -14
  124. package/src/json-schemas/schema-dot-org-photograph.json +11 -5
  125. package/src/json-schemas/schema-dot-org-place.json +78 -5
  126. package/src/lib/README.md +26 -0
  127. package/src/lib/bulk-filter/README.md +22 -0
  128. package/src/lib/bulk-filter/index.examples.js +27 -0
  129. package/src/lib/bulk-filter/index.js +63 -0
  130. package/src/lib/bulk-filter/index.spec.js +38 -0
  131. package/src/lib/bulk-find/README.md +18 -0
  132. package/src/lib/bulk-find/index.examples.js +19 -0
  133. package/src/lib/bulk-find/index.js +30 -0
  134. package/src/lib/bulk-find/index.spec.js +41 -0
  135. package/src/lib/chatgpt/index.js +63 -43
  136. package/src/lib/combinations/index.js +30 -0
  137. package/src/lib/combinations/index.spec.js +23 -0
  138. package/src/lib/functional/index.js +28 -0
  139. package/src/lib/logger-service/index.js +32 -0
  140. package/src/lib/parse-js-parts/index.js +9 -21
  141. package/src/lib/parse-llm-list/README.md +39 -0
  142. package/src/lib/parse-llm-list/index.js +54 -0
  143. package/src/lib/parse-llm-list/index.spec.js +59 -0
  144. package/src/lib/path-aliases/index.js +1 -3
  145. package/src/lib/path-aliases/index.spec.js +2 -8
  146. package/src/lib/pave/index.js +4 -4
  147. package/src/lib/pave/index.spec.js +6 -3
  148. package/src/lib/prompt-cache/index.js +14 -10
  149. package/src/lib/retry/index.js +11 -8
  150. package/src/lib/ring-buffer/README.md +460 -0
  151. package/src/lib/ring-buffer/index.js +1074 -0
  152. package/src/lib/search-best-first/city-walk.spec.js +37 -0
  153. package/src/lib/search-best-first/index.js +42 -11
  154. package/src/lib/search-best-first/index.spec.js +35 -0
  155. package/src/lib/search-js-files/index.js +21 -41
  156. package/src/lib/search-js-files/scan-file.js +10 -21
  157. package/src/lib/shorten-text/index.js +2 -7
  158. package/src/lib/shorten-text/index.spec.js +3 -3
  159. package/src/lib/strip-response/index.js +2 -7
  160. package/src/lib/template-replace/index.js +23 -0
  161. package/src/lib/template-replace/index.spec.js +60 -0
  162. package/src/lib/to-date/index.js +11 -0
  163. package/src/lib/to-number/index.js +1 -1
  164. package/src/lib/transcribe/index.js +4 -4
  165. package/src/prompts/README.md +3 -1
  166. package/src/prompts/as-object-with-schema.js +3 -8
  167. package/src/prompts/as-schema-org-text.js +10 -2
  168. package/src/prompts/code-features.js +1 -5
  169. package/src/prompts/constants.js +27 -27
  170. package/src/prompts/generate-collection.js +1 -1
  171. package/src/prompts/intent.js +11 -16
  172. package/src/prompts/select-from-threshold.js +1 -2
  173. package/src/prompts/sort.js +4 -8
  174. package/src/prompts/style.js +4 -7
  175. package/src/prompts/wrap-list.js +1 -4
  176. package/src/services/llm-model/global-overrides.spec.js +432 -0
  177. package/src/services/llm-model/index.js +234 -40
  178. package/src/services/llm-model/model.js +2 -2
  179. package/src/services/llm-model/negotiate.spec.js +447 -0
  180. package/src/services/redis/index.js +70 -7
  181. package/src/test/setup.js +20 -0
  182. package/src/verblets/README.md +26 -0
  183. package/src/verblets/auto/index.examples.js +12 -9
  184. package/src/verblets/auto/index.js +10 -10
  185. package/src/verblets/auto/index.spec.js +4 -6
  186. package/src/verblets/bool/README.md +36 -0
  187. package/src/verblets/bool/index.examples.js +53 -1
  188. package/src/verblets/bool/index.js +6 -9
  189. package/src/verblets/bool/index.spec.js +1 -3
  190. package/src/verblets/central-tendency/README.md +166 -0
  191. package/src/verblets/central-tendency/central-tendency-result.json +24 -0
  192. package/src/verblets/central-tendency/index.examples.js +196 -0
  193. package/src/verblets/central-tendency/index.js +171 -0
  194. package/src/verblets/central-tendency/index.spec.js +148 -0
  195. package/src/verblets/enum/index.examples.js +1 -4
  196. package/src/verblets/enum/index.js +7 -4
  197. package/src/verblets/expect/README.md +64 -0
  198. package/src/verblets/expect/index.examples.js +109 -0
  199. package/src/verblets/expect/index.js +75 -0
  200. package/src/verblets/expect/index.spec.js +127 -0
  201. package/src/verblets/intent/index.examples.js +84 -1
  202. package/src/verblets/intent/index.js +56 -68
  203. package/src/verblets/intersection/README.md +16 -0
  204. package/src/verblets/intersection/index.examples.js +89 -0
  205. package/src/verblets/intersection/index.js +84 -0
  206. package/src/verblets/intersection/index.spec.js +60 -0
  207. package/src/verblets/intersection/intersection-result.json +16 -0
  208. package/src/verblets/list-expand/README.md +10 -0
  209. package/src/verblets/list-expand/index.examples.js +14 -0
  210. package/src/verblets/list-expand/index.js +104 -0
  211. package/src/verblets/list-expand/index.spec.js +18 -0
  212. package/src/verblets/list-expand/list-expand-result.json +16 -0
  213. package/src/verblets/list-filter/README.md +22 -0
  214. package/src/verblets/list-filter/index.examples.js +26 -0
  215. package/src/verblets/list-filter/index.js +18 -0
  216. package/src/verblets/list-filter/index.spec.js +19 -0
  217. package/src/verblets/list-find/README.md +11 -0
  218. package/src/verblets/list-find/index.examples.js +15 -0
  219. package/src/verblets/list-find/index.js +17 -0
  220. package/src/verblets/list-find/index.spec.js +19 -0
  221. package/src/verblets/list-group/README.md +16 -0
  222. package/src/verblets/list-group/index.examples.js +16 -0
  223. package/src/verblets/list-group/index.js +112 -0
  224. package/src/verblets/list-group/index.spec.js +35 -0
  225. package/src/verblets/list-group/list-group-result.json +16 -0
  226. package/src/verblets/list-map/README.md +11 -0
  227. package/src/verblets/list-map/index.examples.js +15 -0
  228. package/src/verblets/list-map/index.js +26 -0
  229. package/src/verblets/list-map/index.spec.js +17 -0
  230. package/src/verblets/list-reduce/README.md +10 -0
  231. package/src/verblets/list-reduce/index.examples.js +14 -0
  232. package/src/verblets/list-reduce/index.js +21 -0
  233. package/src/verblets/list-reduce/index.spec.js +27 -0
  234. package/src/verblets/list-reduce/index.spec.jsx +27 -0
  235. package/src/verblets/name/README.md +15 -0
  236. package/src/verblets/name/index.examples.js +28 -0
  237. package/src/verblets/name/index.js +19 -0
  238. package/src/verblets/name/index.spec.js +33 -0
  239. package/src/verblets/name-similar-to/README.md +26 -0
  240. package/src/verblets/name-similar-to/index.examples.js +18 -0
  241. package/src/verblets/name-similar-to/index.js +20 -0
  242. package/src/verblets/name-similar-to/index.spec.js +13 -0
  243. package/src/verblets/number/index.examples.js +173 -7
  244. package/src/verblets/number/index.js +5 -2
  245. package/src/verblets/number/index.spec.js +1 -3
  246. package/src/verblets/number-with-units/index.examples.js +5 -1
  247. package/src/verblets/number-with-units/index.js +74 -9
  248. package/src/verblets/number-with-units/number-with-units-result.json +23 -0
  249. package/src/verblets/schema-org/index.examples.js +2 -7
  250. package/src/verblets/schema-org/index.js +32 -3
  251. package/src/verblets/sentiment/README.md +10 -0
  252. package/src/verblets/sentiment/index.examples.js +20 -0
  253. package/src/verblets/sentiment/index.js +9 -0
  254. package/src/verblets/sentiment/index.spec.js +20 -0
  255. package/src/verblets/to-object/index.js +10 -15
  256. package/src/verblets/to-object/index.spec.js +1 -4
  257. package/.eslintrc.json +0 -42
  258. package/docs/README.md +0 -41
  259. package/docs/babel.config.js +0 -3
  260. package/docs/blog/2019-05-28-first-blog-post.md +0 -12
  261. package/docs/blog/2019-05-29-long-blog-post.md +0 -44
  262. package/docs/blog/2021-08-01-mdx-blog-post.mdx +0 -20
  263. package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  264. package/docs/blog/2021-08-26-welcome/index.md +0 -25
  265. package/docs/blog/authors.yml +0 -17
  266. package/docs/docs/api/bool.md +0 -74
  267. package/docs/docs/api/search.md +0 -51
  268. package/docs/docs/intro.md +0 -47
  269. package/docs/docs/tutorial-basics/_category_.json +0 -8
  270. package/docs/docs/tutorial-basics/congratulations.md +0 -23
  271. package/docs/docs/tutorial-basics/create-a-blog-post.md +0 -34
  272. package/docs/docs/tutorial-basics/create-a-document.md +0 -57
  273. package/docs/docs/tutorial-basics/create-a-page.md +0 -43
  274. package/docs/docs/tutorial-basics/deploy-your-site.md +0 -31
  275. package/docs/docs/tutorial-basics/markdown-features.mdx +0 -152
  276. package/docs/docs/tutorial-extras/_category_.json +0 -7
  277. package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
  278. package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
  279. package/docs/docs/tutorial-extras/manage-docs-versions.md +0 -55
  280. package/docs/docs/tutorial-extras/translate-your-site.md +0 -88
  281. package/docs/docusaurus.config.js +0 -120
  282. package/docs/package.json +0 -44
  283. package/docs/sidebars.js +0 -31
  284. package/docs/src/components/HomepageFeatures/index.js +0 -61
  285. package/docs/src/components/HomepageFeatures/styles.module.css +0 -11
  286. package/docs/src/css/custom.css +0 -30
  287. package/docs/src/pages/index.js +0 -43
  288. package/docs/src/pages/index.module.css +0 -23
  289. package/docs/src/pages/markdown-page.md +0 -7
  290. package/docs/static/.nojekyll +0 -0
  291. package/docs/static/img/docusaurus-social-card.jpg +0 -0
  292. package/docs/static/img/docusaurus.png +0 -0
  293. package/docs/static/img/favicon.ico +0 -0
  294. package/docs/static/img/logo.svg +0 -1
  295. package/docs/static/img/undraw_docusaurus_mountain.svg +0 -171
  296. package/docs/static/img/undraw_docusaurus_react.svg +0 -170
  297. package/docs/static/img/undraw_docusaurus_tree.svg +0 -40
  298. package/src/constants/openai.js +0 -65
  299. /package/{.vite.config.examples.js → .vitest.config.examples.js} +0 -0
  300. /package/{.vite.config.js → .vitest.config.js} +0 -0
@@ -0,0 +1,324 @@
1
+ import { describe, expect as vitestExpect, it, vi, beforeEach, afterEach } from 'vitest';
2
+ import aiExpectSimple, { expect as aiExpect } from './index.js';
3
+ import { longTestTimeout } from '../../constants/common.js';
4
+
5
+ // Mock the chatgpt function to avoid actual API calls
6
+ vi.mock('../../lib/chatgpt/index.js', () => ({
7
+ default: vi.fn().mockImplementation((prompt) => {
8
+ // Handle exact equality checks
9
+ if (prompt.includes('Does the actual value strictly equal the expected value?')) {
10
+ if (prompt.includes('Actual: "hello"') && prompt.includes('Expected: "hello"')) {
11
+ return 'True';
12
+ }
13
+ if (prompt.includes('Actual: "goodbye"') && prompt.includes('Expected: "hello"')) {
14
+ return 'False';
15
+ }
16
+ if (prompt.includes('Actual: "hello"') && prompt.includes('Expected: "goodbye"')) {
17
+ return 'False';
18
+ }
19
+ }
20
+
21
+ // Handle constraint-based validations (format: "Given this constraint:")
22
+ if (prompt.includes('Given this constraint:')) {
23
+ if (prompt.includes('Is this a greeting?') && prompt.includes('Hello world!')) {
24
+ return 'True';
25
+ }
26
+
27
+ if (prompt.includes('Is this text professional and grammatically correct?')) {
28
+ if (prompt.includes('well-written, professional email')) {
29
+ return 'True';
30
+ }
31
+ }
32
+
33
+ if (prompt.includes('Does this person data look realistic?')) {
34
+ if (prompt.includes('John Doe') && prompt.includes('"age": 30')) {
35
+ return 'True';
36
+ }
37
+ }
38
+
39
+ if (prompt.includes('Is this recommendation specific and actionable?')) {
40
+ if (prompt.includes('Increase marketing budget by 20%')) {
41
+ return 'True';
42
+ }
43
+ }
44
+
45
+ if (prompt.includes('Does this profile represent an experienced software developer')) {
46
+ if (prompt.includes('Alice Johnson') && prompt.includes('JavaScript')) {
47
+ return 'True';
48
+ }
49
+ }
50
+
51
+ if (prompt.includes('Is this story opening engaging')) {
52
+ if (prompt.includes('Once upon a time')) {
53
+ return 'True';
54
+ }
55
+ }
56
+ }
57
+
58
+ // Default to False for unmatched cases
59
+ return 'False';
60
+ }),
61
+ }));
62
+
63
+ describe('expect chain', () => {
64
+ let originalEnv;
65
+
66
+ beforeEach(() => {
67
+ originalEnv = process.env.LLM_EXPECT_MODE;
68
+ });
69
+
70
+ afterEach(() => {
71
+ if (originalEnv !== undefined) {
72
+ process.env.LLM_EXPECT_MODE = originalEnv;
73
+ } else {
74
+ delete process.env.LLM_EXPECT_MODE;
75
+ }
76
+ });
77
+
78
+ describe('Enhanced API', () => {
79
+ it(
80
+ 'should return structured results in none mode',
81
+ async () => {
82
+ process.env.LLM_EXPECT_MODE = 'none';
83
+
84
+ const [passed, details] = await aiExpect('hello', 'hello');
85
+
86
+ vitestExpect(passed).toBe(true);
87
+ vitestExpect(details).toHaveProperty('passed', true);
88
+ vitestExpect(details).toHaveProperty('advice');
89
+ vitestExpect(details).toHaveProperty('file');
90
+ vitestExpect(details).toHaveProperty('line');
91
+ },
92
+ longTestTimeout
93
+ );
94
+
95
+ it(
96
+ 'should handle failed assertions in none mode',
97
+ async () => {
98
+ process.env.LLM_EXPECT_MODE = 'none';
99
+
100
+ const [passed, details] = await aiExpect('hello', 'goodbye');
101
+
102
+ vitestExpect(passed).toBe(false);
103
+ vitestExpect(details.passed).toBe(false);
104
+ vitestExpect(details).toHaveProperty('advice');
105
+ vitestExpect(details).toHaveProperty('file');
106
+ vitestExpect(details).toHaveProperty('line');
107
+ },
108
+ longTestTimeout
109
+ );
110
+
111
+ it(
112
+ 'should throw errors in error mode',
113
+ async () => {
114
+ process.env.LLM_EXPECT_MODE = 'error';
115
+
116
+ await vitestExpect(async () => {
117
+ await aiExpect('hello', 'goodbye');
118
+ }).rejects.toThrow('LLM Assertion Failed');
119
+ },
120
+ longTestTimeout
121
+ );
122
+
123
+ it(
124
+ 'should log in info mode',
125
+ async () => {
126
+ process.env.LLM_EXPECT_MODE = 'info';
127
+ const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {});
128
+
129
+ const [passed] = await aiExpect('hello', 'goodbye');
130
+
131
+ vitestExpect(passed).toBe(false);
132
+ vitestExpect(consoleSpy).toHaveBeenCalledWith(
133
+ vitestExpect.stringContaining('LLM Assertion Failed')
134
+ );
135
+
136
+ consoleSpy.mockRestore();
137
+ },
138
+ longTestTimeout
139
+ );
140
+
141
+ it(
142
+ 'should handle constraint-based validation',
143
+ async () => {
144
+ process.env.LLM_EXPECT_MODE = 'none';
145
+
146
+ const [passed, details] = await aiExpect('Hello world!', undefined, 'Is this a greeting?');
147
+
148
+ vitestExpect(passed).toBe(true);
149
+ vitestExpect(details.passed).toBe(true);
150
+ },
151
+ longTestTimeout
152
+ );
153
+
154
+ it(
155
+ 'should validate content quality',
156
+ async () => {
157
+ const [passed, details] = await aiExpect(
158
+ 'This is a well-written, professional email with proper grammar and clear intent.',
159
+ undefined,
160
+ 'Is this text professional and grammatically correct?'
161
+ );
162
+
163
+ vitestExpect(passed).toBe(true);
164
+ vitestExpect(details).toHaveProperty('file');
165
+ vitestExpect(details).toHaveProperty('line');
166
+ },
167
+ longTestTimeout
168
+ );
169
+
170
+ it(
171
+ 'should validate data structures',
172
+ async () => {
173
+ const [passed] = await aiExpect(
174
+ { name: 'John Doe', age: 30, city: 'New York' },
175
+ undefined,
176
+ 'Does this person data look realistic?'
177
+ );
178
+
179
+ vitestExpect(passed).toBe(true);
180
+ },
181
+ longTestTimeout
182
+ );
183
+
184
+ it(
185
+ 'should handle business logic validation',
186
+ async () => {
187
+ const [passed] = await aiExpect(
188
+ 'Increase marketing budget by 20% for Q4 to boost holiday sales',
189
+ undefined,
190
+ 'Is this recommendation specific and actionable?'
191
+ );
192
+
193
+ vitestExpect(passed).toBe(true);
194
+ },
195
+ longTestTimeout
196
+ );
197
+
198
+ it('should throw error when neither expected nor constraint provided', async () => {
199
+ await vitestExpect(async () => {
200
+ await aiExpect('test value');
201
+ }).rejects.toThrow('Either expected value or constraint must be provided');
202
+ });
203
+ });
204
+
205
+ describe('Simple API (backward compatibility)', () => {
206
+ it(
207
+ 'should pass for exact equality',
208
+ async () => {
209
+ const result = await aiExpectSimple('hello', 'hello');
210
+ vitestExpect(result).toBe(true);
211
+ },
212
+ longTestTimeout
213
+ );
214
+
215
+ it(
216
+ 'should pass for constraint-based validation',
217
+ async () => {
218
+ const result = await aiExpectSimple('Hello world!', undefined, 'Is this a greeting?');
219
+ vitestExpect(result).toBe(true);
220
+ },
221
+ longTestTimeout
222
+ );
223
+
224
+ it(
225
+ 'should fail for non-matching values',
226
+ async () => {
227
+ const result = await aiExpectSimple('goodbye', 'hello');
228
+ vitestExpect(result).toBe(false);
229
+ },
230
+ longTestTimeout
231
+ );
232
+
233
+ it(
234
+ 'should validate content quality',
235
+ async () => {
236
+ const result = await aiExpectSimple(
237
+ 'This is a well-written, professional email with proper grammar.',
238
+ undefined,
239
+ 'Is this text professional and grammatically correct?'
240
+ );
241
+ vitestExpect(result).toBe(true);
242
+ },
243
+ longTestTimeout
244
+ );
245
+ });
246
+
247
+ describe('Environment variable handling', () => {
248
+ it(
249
+ 'should default to none mode when env var is not set',
250
+ async () => {
251
+ delete process.env.LLM_EXPECT_MODE;
252
+
253
+ const [passed] = await aiExpect('hello', 'goodbye');
254
+ vitestExpect(passed).toBe(false);
255
+ // Should not throw in none mode
256
+ },
257
+ longTestTimeout
258
+ );
259
+
260
+ it(
261
+ 'should handle invalid env var values',
262
+ async () => {
263
+ process.env.LLM_EXPECT_MODE = 'invalid';
264
+
265
+ const [passed] = await aiExpect('hello', 'goodbye');
266
+ vitestExpect(passed).toBe(false);
267
+ // Should default to none mode and not throw
268
+ },
269
+ longTestTimeout
270
+ );
271
+ });
272
+
273
+ describe('Advanced features', () => {
274
+ it(
275
+ 'should provide file and line information',
276
+ async () => {
277
+ const [, details] = await aiExpect('hello', 'hello');
278
+
279
+ vitestExpect(details.file).toBeDefined();
280
+ vitestExpect(details.line).toBeTypeOf('number');
281
+ vitestExpect(details.line).toBeGreaterThan(0);
282
+ },
283
+ longTestTimeout
284
+ );
285
+
286
+ it(
287
+ 'should handle complex object comparisons',
288
+ async () => {
289
+ const userProfile = {
290
+ name: 'Alice Johnson',
291
+ skills: ['JavaScript', 'Python', 'React'],
292
+ experience: '5 years',
293
+ level: 'Senior Developer',
294
+ };
295
+
296
+ const [passed] = await aiExpect(
297
+ userProfile,
298
+ undefined,
299
+ 'Does this profile represent an experienced software developer with modern skills?'
300
+ );
301
+
302
+ vitestExpect(passed).toBe(true);
303
+ },
304
+ longTestTimeout
305
+ );
306
+
307
+ it(
308
+ 'should validate creative content',
309
+ async () => {
310
+ const storyOpening =
311
+ 'Once upon a time, in a land far away, there lived a brave knight who embarked on a quest to save the kingdom from an ancient curse.';
312
+
313
+ const [passed] = await aiExpect(
314
+ storyOpening,
315
+ undefined,
316
+ 'Is this story opening engaging and sets up a clear adventure narrative?'
317
+ );
318
+
319
+ vitestExpect(passed).toBe(true);
320
+ },
321
+ longTestTimeout
322
+ );
323
+ });
324
+ });
@@ -0,0 +1,11 @@
1
+ # filter-ambiguous
2
+
3
+ Identify ambiguous words or phrases in text. Sentences are first scored for overall uncertainty and the highest scoring ones are examined for unclear terms. Each term is scored in context so you can zero in on the most confusing language.
4
+
5
+ ```javascript
6
+ import filterAmbiguous from './index.js';
7
+
8
+ const text = `I saw her duck\nThe magician made a little girl disappear\nHe fed her dog food`;
9
+ const result = await filterAmbiguous(text, { topN: 3 });
10
+ // => [{ term: 'duck', sentence: 'I saw her duck', score: 9 }, ...]
11
+ ```
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { longTestTimeout } from '../../constants/common.js';
3
+ import filterAmbiguous from './index.js';
4
+
5
+ describe('filterAmbiguous examples', () => {
6
+ it(
7
+ 'highlights unclear language',
8
+ async () => {
9
+ const text = `I saw her duck\nThe magician made a little girl disappear\nHe fed her dog food`;
10
+ const result = await filterAmbiguous(text, { topN: 2 });
11
+ expect(result.length).toBeGreaterThan(0);
12
+ result.forEach((r) => {
13
+ expect(r).toHaveProperty('term');
14
+ expect(r).toHaveProperty('sentence');
15
+ expect(typeof r.score).toBe('number');
16
+ });
17
+ },
18
+ longTestTimeout
19
+ );
20
+ });
@@ -0,0 +1,49 @@
1
+ import list from '../list/index.js';
2
+ import bulkScore from '../bulk-score/index.js';
3
+
4
+ export default async function filterAmbiguous(text, config = {}) {
5
+ const { topN = 10, chunkSize = 5, llm, ...options } = config;
6
+ if (!text) return [];
7
+ const sentences = text
8
+ .split('\n')
9
+ .map((s) => s.trim())
10
+ .filter(Boolean);
11
+ if (sentences.length === 0) return [];
12
+
13
+ const { scores: sentenceScores } = await bulkScore(
14
+ sentences,
15
+ 'How ambiguous or easily misinterpreted is this sentence?',
16
+ { chunkSize, llm, ...options }
17
+ );
18
+
19
+ const rankedSentences = sentences
20
+ .map((s, i) => ({ sentence: s, score: sentenceScores[i] ?? 0 }))
21
+ .sort((a, b) => b.score - a.score)
22
+ .slice(0, topN);
23
+
24
+ const termPairs = [];
25
+ for (const { sentence } of rankedSentences) {
26
+ // eslint-disable-next-line no-await-in-loop
27
+ const terms = await list('Ambiguous words or short phrases', {
28
+ attachments: { text: sentence },
29
+ targetNewItemsCount: 5,
30
+ llm,
31
+ ...options,
32
+ });
33
+ terms.forEach((term) => {
34
+ termPairs.push({ term, sentence });
35
+ });
36
+ }
37
+
38
+ if (termPairs.length === 0) return [];
39
+
40
+ const { scores } = await bulkScore(
41
+ termPairs.map((p) => `${p.term} | ${p.sentence}`),
42
+ 'Score how ambiguous the term is within the sentence.',
43
+ { chunkSize, llm, ...options }
44
+ );
45
+
46
+ const scored = termPairs.map((p, i) => ({ ...p, score: scores[i] }));
47
+ scored.sort((a, b) => (b.score || 0) - (a.score || 0));
48
+ return scored.slice(0, topN);
49
+ }
@@ -0,0 +1,31 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import filterAmbiguous from './index.js';
3
+ import bulkScore from '../bulk-score/index.js';
4
+ import list from '../list/index.js';
5
+
6
+ vi.mock('../bulk-score/index.js', () => ({
7
+ default: vi.fn(),
8
+ }));
9
+
10
+ vi.mock('../list/index.js', () => ({
11
+ default: vi.fn(),
12
+ }));
13
+
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+ });
17
+
18
+ describe('filterAmbiguous chain', () => {
19
+ it('returns scored ambiguous terms', async () => {
20
+ bulkScore
21
+ .mockResolvedValueOnce({ scores: [1, 9], reference: [] })
22
+ .mockResolvedValueOnce({ scores: [8, 3], reference: [] });
23
+ list.mockResolvedValueOnce(['alpha', 'beta']).mockResolvedValueOnce([]);
24
+
25
+ const result = await filterAmbiguous('s1\ns2', { topN: 1 });
26
+
27
+ expect(result).toStrictEqual([{ term: 'alpha', sentence: 's2', score: 8 }]);
28
+ expect(bulkScore).toHaveBeenCalled();
29
+ expect(list).toHaveBeenCalled();
30
+ });
31
+ });
@@ -0,0 +1,19 @@
1
+ # glossary
2
+
3
+ Identify difficult or technical terms in any text so you can explain them later.
4
+ The chain breaks long passages into paragraphs and uses the `bulk-map` retry
5
+ utility to collect candidate terms from each chunk. Any failed paragraphs are
6
+ automatically retried. Finally the terms are ranked by importance using `sort`.
7
+
8
+ ```javascript
9
+ import glossary from './index.js';
10
+
11
+ const blog = `The chef explained how umami develops through the Maillard reaction alongside sous-vide techniques.`;
12
+
13
+ const terms = await glossary(blog, { maxTerms: 3 });
14
+ console.log(terms);
15
+ // => ['Maillard reaction', 'sous-vide', 'umami']
16
+ ```
17
+
18
+ This is handy when you want to add a quick glossary sidebar to a dense article
19
+ so everyday readers aren't left guessing what key terms mean.