@far-world-labs/verblets 0.1.1 → 0.1.3

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 +120 -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 +254 -0
  11. package/eslint.config.js +80 -0
  12. package/package.json +29 -17
  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 +8 -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 +44 -47
  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 +26 -9
  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 +16 -22
  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 +95 -7
  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,173 @@
1
+ import chatgpt from '../../lib/chatgpt/index.js';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Get the calling file and line number from stack trace
7
+ */
8
+ function getCallerInfo() {
9
+ const { stack } = new Error();
10
+ const lines = stack.split('\n');
11
+
12
+ // Find the first line that's not this file
13
+ for (let i = 2; i < lines.length; i++) {
14
+ const line = lines[i];
15
+ if (line.includes('at ') && !line.includes('expect')) {
16
+ const match = line.match(/at .* \((.+):(\d+):\d+\)/);
17
+ if (match) {
18
+ return { file: match[1], line: parseInt(match[2]) };
19
+ }
20
+ // Handle cases without parentheses
21
+ const simpleMatch = line.match(/at (.+):(\d+):\d+/);
22
+ if (simpleMatch) {
23
+ return { file: simpleMatch[1], line: parseInt(simpleMatch[2]) };
24
+ }
25
+ }
26
+ }
27
+ return { file: 'unknown', line: 0 };
28
+ }
29
+
30
+ /**
31
+ * Read code context around the assertion
32
+ */
33
+ function getCodeContext(filePath, lineNumber) {
34
+ try {
35
+ if (!fs.existsSync(filePath)) return null;
36
+
37
+ const content = fs.readFileSync(filePath, 'utf8');
38
+ const lines = content.split('\n');
39
+
40
+ // Get 400 lines before and 100 lines after
41
+ const start = Math.max(0, lineNumber - 400);
42
+ const end = Math.min(lines.length, lineNumber + 100);
43
+
44
+ return {
45
+ lines: lines.slice(start, end),
46
+ startLine: start + 1,
47
+ assertionLine: lineNumber,
48
+ };
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Generate intelligent advice for failed assertions
56
+ */
57
+ async function generateAdvice(actual, expected, constraint, codeContext) {
58
+ const contextInfo = codeContext
59
+ ? `
60
+ Code context around assertion (line ${codeContext.assertionLine}):
61
+ \`\`\`
62
+ ${codeContext.lines.join('\n')}
63
+ \`\`\`
64
+ `
65
+ : '';
66
+
67
+ const prompt = `You are a debugging assistant helping with a failed LLM assertion.
68
+
69
+ ASSERTION DETAILS:
70
+ - Actual value: ${JSON.stringify(actual, null, 2)}
71
+ - Expected value: ${expected ? JSON.stringify(expected, null, 2) : 'N/A'}
72
+ - Constraint: ${constraint || 'N/A'}
73
+
74
+ ${contextInfo}
75
+
76
+ Provide structured debugging advice in this format:
77
+
78
+ ISSUE: [Brief description of why the assertion failed]
79
+ FIX: [Specific actionable steps to resolve the issue]
80
+ CONTEXT: [Additional context about the problem and potential root causes]
81
+
82
+ Keep your response concise but actionable. Focus on practical solutions.`;
83
+
84
+ try {
85
+ return await chatgpt(prompt);
86
+ } catch {
87
+ return 'Unable to generate debugging advice due to LLM error.';
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Enhanced LLM expectation with debugging features
93
+ */
94
+ export async function expect(actual, expected, constraint) {
95
+ const mode = process.env.LLM_EXPECT_MODE || 'none';
96
+ const callerInfo = getCallerInfo();
97
+
98
+ // Build the assertion prompt
99
+ let prompt;
100
+ if (constraint && expected === undefined) {
101
+ // Constraint-only mode
102
+ prompt = `Given this constraint: "${constraint}"
103
+
104
+ Actual value: ${JSON.stringify(actual, null, 2)}
105
+
106
+ Does the actual value satisfy the constraint? Answer only "True" or "False".`;
107
+ } else if (constraint && expected !== undefined) {
108
+ // Both expected and constraint provided - use constraint
109
+ prompt = `Given this constraint: "${constraint}"
110
+
111
+ Actual value: ${JSON.stringify(actual, null, 2)}
112
+ Expected value: ${JSON.stringify(expected, null, 2)}
113
+
114
+ Does the actual value satisfy the constraint? Answer only "True" or "False".`;
115
+ } else if (expected !== undefined) {
116
+ // Expected value only
117
+ prompt = `Does the actual value strictly equal the expected value?
118
+
119
+ Actual: ${JSON.stringify(actual, null, 2)}
120
+ Expected: ${JSON.stringify(expected, null, 2)}
121
+
122
+ Answer only "True" or "False".`;
123
+ } else {
124
+ throw new Error('Either expected value or constraint must be provided');
125
+ }
126
+
127
+ try {
128
+ const response = await chatgpt(prompt);
129
+ const passed = response.trim().toLowerCase() === 'true';
130
+
131
+ // Prepare result structure
132
+ const result = {
133
+ passed,
134
+ advice: null,
135
+ file: callerInfo.file,
136
+ line: callerInfo.line,
137
+ };
138
+
139
+ // Handle failure cases based on mode
140
+ if (!passed) {
141
+ if (mode === 'info' || mode === 'error') {
142
+ const codeContext = getCodeContext(callerInfo.file, callerInfo.line);
143
+ result.advice = await generateAdvice(actual, expected, constraint, codeContext);
144
+
145
+ const message = `LLM Assertion Failed at ${path.basename(callerInfo.file)}:${
146
+ callerInfo.line
147
+ }
148
+ ${result.advice}`;
149
+
150
+ if (mode === 'error') {
151
+ throw new Error(message);
152
+ } else if (mode === 'info') {
153
+ console.info(message);
154
+ }
155
+ }
156
+ }
157
+
158
+ return [passed, result];
159
+ } catch (error) {
160
+ if (error.message.includes('LLM Assertion Failed')) {
161
+ throw error; // Re-throw our custom errors
162
+ }
163
+ throw new Error(`LLM expectation failed due to error: ${error.message}`);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Simple LLM expectation (backward compatibility)
169
+ */
170
+ export default async function expectSimple(actual, expected, constraint) {
171
+ const [passed] = await expect(actual, expected, constraint);
172
+ return passed;
173
+ }
@@ -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.