@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,386 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import glossary from './index.js';
3
+ import aiExpect from '../../verblets/expect/index.js';
4
+ import { longTestTimeout } from '../../constants/common.js';
5
+
6
+ describe('glossary examples', () => {
7
+ // Set environment mode to 'none' for all tests to avoid throwing
8
+ const originalMode = process.env.LLM_EXPECT_MODE;
9
+
10
+ beforeAll(() => {
11
+ process.env.LLM_EXPECT_MODE = 'none';
12
+ });
13
+
14
+ afterAll(() => {
15
+ if (originalMode !== undefined) {
16
+ process.env.LLM_EXPECT_MODE = originalMode;
17
+ } else {
18
+ delete process.env.LLM_EXPECT_MODE;
19
+ }
20
+ });
21
+
22
+ it(
23
+ 'extracts terms from a science paragraph',
24
+ async () => {
25
+ const text = `The chef explained how umami develops through the Maillard reaction alongside sous-vide techniques.`;
26
+ const result = await glossary(text, { maxTerms: 2 });
27
+
28
+ expect(result.length).toBeGreaterThan(0);
29
+ expect(Array.isArray(result)).toBe(true);
30
+
31
+ // LLM assertion to validate that extracted terms are technical/complex
32
+ const areTermsTechnical = await aiExpect(
33
+ `From the text "${text}", these terms were extracted: ${result.join(', ')}`
34
+ ).toSatisfy(
35
+ 'Are these terms technical or complex enough that a casual reader might need definitions?',
36
+ {
37
+ message: `Expected extracted terms to be technical/complex, but got: ${result.join(
38
+ ', '
39
+ )}`,
40
+ }
41
+ );
42
+ expect(
43
+ areTermsTechnical,
44
+ `Expected extracted terms to be technical/complex, but got: ${result.join(', ')}`
45
+ ).toBe(true);
46
+
47
+ // LLM assertion to validate terms are relevant to the source text
48
+ const areTermsRelevant = await aiExpect(
49
+ `Original text: "${text}" | Extracted terms: ${result.join(', ')}`
50
+ ).toSatisfy(
51
+ 'Are all these extracted terms actually present or directly related to the original text?',
52
+ {
53
+ message: `Expected terms to be relevant to source text, but extracted: ${result.join(
54
+ ', '
55
+ )} from: "${text}"`,
56
+ }
57
+ );
58
+ expect(
59
+ areTermsRelevant,
60
+ `Expected terms to be relevant to source text, but extracted: ${result.join(
61
+ ', '
62
+ )} from: "${text}"`
63
+ ).toBe(true);
64
+
65
+ // LLM assertion to validate term selection quality
66
+ const isGoodSelection = await aiExpect(
67
+ `For a cooking/culinary text, these terms were selected for a glossary: ${result.join(
68
+ ', '
69
+ )}`
70
+ ).toSatisfy(
71
+ 'Are these appropriate terms for a culinary glossary that would help readers understand cooking concepts?',
72
+ { message: `Expected appropriate culinary terms, but got: ${result.join(', ')}` }
73
+ );
74
+ expect(
75
+ isGoodSelection,
76
+ `Expected appropriate culinary terms, but got: ${result.join(', ')}`
77
+ ).toBe(true);
78
+ },
79
+ longTestTimeout
80
+ );
81
+
82
+ it(
83
+ 'handles technical documentation with multiple complex terms',
84
+ async () => {
85
+ const sourceText = `Our microservice architecture uses an API gateway for routing. Authentication is handled via OAuth 2.0 and JWT tokens. We also employ load balancing to distribute traffic.`;
86
+ const result = await glossary(sourceText, { maxTerms: 5 });
87
+ expect(result).toBeDefined();
88
+ expect(Array.isArray(result)).toBe(true);
89
+ expect(result.length).toBeGreaterThan(0);
90
+ // Check that the extracted terms cover the key technical concepts in the source text.
91
+ const keyConcepts = ['microservice', 'API gateway', 'OAuth', 'JWT', 'load balancing'];
92
+ const coverage = keyConcepts.every((concept) =>
93
+ result.some((term) => term.toLowerCase().includes(concept.toLowerCase()))
94
+ );
95
+ expect(coverage).toBe(true);
96
+ },
97
+ longTestTimeout
98
+ );
99
+
100
+ it(
101
+ 'filters out common words and focuses on specialized terms',
102
+ async () => {
103
+ const text = `The quantum entanglement phenomenon occurs when particles become interconnected,
104
+ demonstrating superposition states that challenge classical physics understanding.
105
+ This behavior is fundamental to quantum computing applications.`;
106
+
107
+ const result = await glossary(text, { maxTerms: 4 });
108
+
109
+ expect(result.length).toBeGreaterThan(0);
110
+
111
+ // LLM assertion to ensure no common words are included
112
+ const noCommonWords = await aiExpect(
113
+ `These terms were selected for a glossary: ${result.join(', ')}`
114
+ ).toSatisfy(
115
+ 'Are all of these terms specialized/technical rather than common everyday words?',
116
+ {
117
+ message: `Expected only specialized/technical terms, but some may be common words: ${result.join(
118
+ ', '
119
+ )}`,
120
+ }
121
+ );
122
+ expect(
123
+ noCommonWords,
124
+ `Expected only specialized/technical terms, but some may be common words: ${result.join(
125
+ ', '
126
+ )}`
127
+ ).toBe(true);
128
+
129
+ // LLM assertion for scientific accuracy
130
+ const scientificallyAccurate = await aiExpect(
131
+ `From a quantum physics text, these terms were extracted: ${result.join(', ')}`
132
+ ).toSatisfy(
133
+ 'Are these reasonable terms that relate to quantum physics or scientific concepts? They should be legitimate scientific terminology, even if not all are highly technical quantum physics terms.',
134
+ {
135
+ message: `Expected reasonable scientific terms related to quantum physics, but got: ${result.join(
136
+ ', '
137
+ )}. Terms should be legitimate scientific concepts.`,
138
+ }
139
+ );
140
+ expect(
141
+ scientificallyAccurate,
142
+ `Expected reasonable scientific terms related to quantum physics, but got: ${result.join(
143
+ ', '
144
+ )}. Terms should be legitimate scientific concepts.`
145
+ ).toBe(true);
146
+
147
+ // LLM assertion for educational value
148
+ const educationalValue = await aiExpect(
149
+ `For someone learning about quantum physics, these glossary terms were provided: ${result.join(
150
+ ', '
151
+ )}`
152
+ ).toSatisfy(
153
+ 'Would defining these terms be helpful for understanding the quantum physics concepts in the text? Focus on whether they contribute to comprehension rather than requiring perfect technical precision.',
154
+ {
155
+ message: `Expected terms that would help with understanding quantum physics concepts, but got: ${result.join(
156
+ ', '
157
+ )}`,
158
+ }
159
+ );
160
+ expect(
161
+ educationalValue,
162
+ `Expected terms that would help with understanding quantum physics concepts, but got: ${result.join(
163
+ ', '
164
+ )}`
165
+ ).toBe(true);
166
+ },
167
+ longTestTimeout
168
+ );
169
+
170
+ it(
171
+ 'handles empty or simple text appropriately',
172
+ async () => {
173
+ const simpleText = `The cat sat on the mat. It was a sunny day.`;
174
+ const result = await glossary(simpleText, { maxTerms: 3 });
175
+
176
+ // Should return empty array or very few terms for simple text
177
+ expect(Array.isArray(result)).toBe(true);
178
+
179
+ if (result.length > 0) {
180
+ // If any terms are returned, they should still be reasonable
181
+ const reasonableForSimpleText = await aiExpect(
182
+ `From simple text "${simpleText}", these terms were extracted: ${result.join(', ')}`
183
+ ).toSatisfy(
184
+ 'If any terms were extracted from this simple text, are they reasonable choices?',
185
+ {
186
+ message: `Expected reasonable terms for simple text, but extracted: ${result.join(
187
+ ', '
188
+ )} from: "${simpleText}"`,
189
+ }
190
+ );
191
+ expect(
192
+ reasonableForSimpleText,
193
+ `Expected reasonable terms for simple text, but extracted: ${result.join(
194
+ ', '
195
+ )} from: "${simpleText}"`
196
+ ).toBe(true);
197
+ } else {
198
+ // LLM assertion that empty result is appropriate for simple text
199
+ const appropriatelyEmpty = await aiExpect(
200
+ `For the simple text "${simpleText}", no glossary terms were extracted`
201
+ ).toSatisfy(
202
+ 'Is it appropriate to extract no glossary terms from this simple, everyday text?',
203
+ { message: `Expected empty result to be appropriate for simple text: "${simpleText}"` }
204
+ );
205
+ expect(
206
+ appropriatelyEmpty,
207
+ `Expected empty result to be appropriate for simple text: "${simpleText}"`
208
+ ).toBe(true);
209
+ }
210
+ },
211
+ longTestTimeout
212
+ );
213
+
214
+ it(
215
+ 'extracts operative philosophical terms for conceptual clarity',
216
+ async () => {
217
+ const philosophyText = `Heidegger's concept of Dasein fundamentally challenges the Cartesian subject-object distinction
218
+ through his analysis of Being-in-the-world. The phenomenological reduction, as developed by Husserl,
219
+ brackets the natural attitude to reveal the intentional structure of consciousness. This hermeneutic circle
220
+ demonstrates how our pre-understanding shapes interpretation, while the ontological difference between
221
+ beings and Being itself remains concealed in everyday thrownness. Gadamer's fusion of horizons extends
222
+ this analysis to show how tradition and prejudice constitute the productive ground of understanding.`;
223
+
224
+ const result = await glossary(philosophyText, { maxTerms: 8 });
225
+
226
+ expect(result.length).toBeGreaterThan(0);
227
+ expect(result.length).toBeLessThanOrEqual(8);
228
+
229
+ // LLM assertion for philosophical term appropriateness - abstract concepts
230
+ const arePhilosophicalConcepts = await aiExpect(
231
+ `From philosophical text, these terms were extracted: ${result.join(', ')}`
232
+ ).toSatisfy(
233
+ 'Are these terms that would genuinely benefit from precise definition to avoid misunderstanding or ambiguity in philosophical discourse?'
234
+ );
235
+ expect(arePhilosophicalConcepts).toBe(true);
236
+
237
+ // LLM assertion for conceptual precision - terms that need clarification
238
+ const needDefinition = await aiExpect(
239
+ `These philosophical terms: ${result.join(', ')}`
240
+ ).toSatisfy(
241
+ 'Are these terms that would genuinely benefit from precise definition to avoid misunderstanding or ambiguity in philosophical discourse?',
242
+ {
243
+ message: `Expected terms needing precise definition for clarity, but got: ${result.join(
244
+ ', '
245
+ )}`,
246
+ }
247
+ );
248
+ expect(
249
+ needDefinition,
250
+ `Expected terms needing precise definition for clarity, but got: ${result.join(', ')}`
251
+ ).toBe(true);
252
+
253
+ // LLM assertion for operative significance - terms central to the argument
254
+ const operativeTerms = await aiExpect(
255
+ `In the context of phenomenology and hermeneutics, these terms were selected: ${result.join(
256
+ ', '
257
+ )}`
258
+ ).toSatisfy(
259
+ 'Are these terms operatively significant - meaning they carry specific technical weight and are central to understanding the philosophical arguments being made?'
260
+ );
261
+ expect(operativeTerms).toBe(true);
262
+
263
+ // LLM assertion for avoiding common words - focus on technical terminology
264
+ const avoidCommonPhilosophical = await aiExpect(
265
+ `These terms from philosophical text: ${result.join(', ')}`
266
+ ).toSatisfy(
267
+ 'Are these specialized philosophical terms rather than common words that happen to appear in philosophical contexts?'
268
+ );
269
+ expect(avoidCommonPhilosophical).toBe(true);
270
+ },
271
+ longTestTimeout
272
+ );
273
+
274
+ it(
275
+ 'extracts operative tech terms across multiple categories',
276
+ async () => {
277
+ const techText = `The team implemented Domain-Driven Design using the Repository pattern and CQRS architecture.
278
+ Martin Fowler's Strangler Fig pattern helped migrate the legacy system while Kent Beck's Test-Driven Development
279
+ approach ensured code quality. We used Docker containers orchestrated by Kubernetes, with Redis for caching
280
+ and PostgreSQL for persistence. The CI/CD pipeline leveraged Jenkins and implemented the Blue-Green deployment
281
+ strategy. Uncle Bob's Clean Architecture principles guided the service boundaries, while Eric Evans'
282
+ Bounded Context concept helped define microservice boundaries. The team followed Scrum methodology with
283
+ pair programming sessions.`;
284
+
285
+ const result = await glossary(techText, {
286
+ maxTerms: 12,
287
+ sortBy:
288
+ 'importance for understanding the content, prioritizing concrete tools and technologies (Docker, Kubernetes, Redis, PostgreSQL, Jenkins) equally with architectural patterns and methodologies',
289
+ });
290
+
291
+ expect(result.length).toBeGreaterThan(0);
292
+ expect(result.length).toBeLessThanOrEqual(12);
293
+
294
+ // LLM assertion for technical tools identification
295
+ const includesTools = await aiExpect(
296
+ `From tech text, these terms were extracted: ${result.join(', ')}`
297
+ ).toSatisfy(
298
+ 'Do these terms include technical tools, technologies, or software systems (like Docker, Kubernetes, Redis, PostgreSQL, Jenkins)?',
299
+ {
300
+ message: `Expected to include technical tools/technologies, but got: ${result.join(
301
+ ', '
302
+ )}`,
303
+ }
304
+ );
305
+ expect(
306
+ includesTools,
307
+ `Expected to include technical tools/technologies, but got: ${result.join(', ')}`
308
+ ).toBe(true);
309
+
310
+ // LLM assertion for design patterns identification
311
+ const includesPatterns = await aiExpect(
312
+ `These terms from software development: ${result.join(', ')}`
313
+ ).toSatisfy(
314
+ 'Do these terms include software design patterns, architectural patterns, or development patterns (like Repository, CQRS, Strangler Fig, Blue-Green)?',
315
+ {
316
+ message: `Expected to include design/architectural patterns, but got: ${result.join(
317
+ ', '
318
+ )}`,
319
+ }
320
+ );
321
+ expect(
322
+ includesPatterns,
323
+ `Expected to include design/architectural patterns, but got: ${result.join(', ')}`
324
+ ).toBe(true);
325
+
326
+ // LLM assertion for methodologies and practices
327
+ const includesMethodologies = await aiExpect(
328
+ `From development context, these terms: ${result.join(', ')}`
329
+ ).toSatisfy(
330
+ 'Do these terms include software development methodologies, practices, or approaches (like Domain-Driven Design, Test-Driven Development, Clean Architecture, Scrum)?',
331
+ {
332
+ message: `Expected to include development methodologies/practices, but got: ${result.join(
333
+ ', '
334
+ )}`,
335
+ }
336
+ );
337
+ expect(
338
+ includesMethodologies,
339
+ `Expected to include development methodologies/practices, but got: ${result.join(', ')}`
340
+ ).toBe(true);
341
+
342
+ // LLM assertion for key people/thought leaders (optional but valuable)
343
+ const mayIncludePeople = await aiExpect(`These tech terms: ${result.join(', ')}`).toSatisfy(
344
+ 'Do these terms appropriately focus on concepts, tools, and patterns rather than just including person names, while potentially including key thought leaders if they are central to understanding specific methodologies?',
345
+ {
346
+ message: `Expected focus on concepts/tools over person names, but got: ${result.join(
347
+ ', '
348
+ )}`,
349
+ }
350
+ );
351
+ expect(
352
+ mayIncludePeople,
353
+ `Expected focus on concepts/tools over person names, but got: ${result.join(', ')}`
354
+ ).toBe(true);
355
+
356
+ // LLM assertion for operative significance in tech context
357
+ const operativeTechTerms = await aiExpect(
358
+ `In a software development context, these terms: ${result.join(', ')}`
359
+ ).toSatisfy(
360
+ 'Are these terms operatively significant for understanding the technical architecture, development practices, and implementation decisions being described?',
361
+ { message: `Expected operatively significant tech terms, but got: ${result.join(', ')}` }
362
+ );
363
+ expect(
364
+ operativeTechTerms,
365
+ `Expected operatively significant tech terms, but got: ${result.join(', ')}`
366
+ ).toBe(true);
367
+
368
+ // LLM assertion for practical utility - terms that need definition
369
+ const practicalUtility = await aiExpect(
370
+ `For someone learning about software architecture and development, these terms: ${result.join(
371
+ ', '
372
+ )}`
373
+ ).toSatisfy(
374
+ 'Are these terms that would genuinely benefit from clear definitions to understand modern software development practices and architectural decisions?',
375
+ {
376
+ message: `Expected terms needing definition for learning, but got: ${result.join(', ')}`,
377
+ }
378
+ );
379
+ expect(
380
+ practicalUtility,
381
+ `Expected terms needing definition for learning, but got: ${result.join(', ')}`
382
+ ).toBe(true);
383
+ },
384
+ longTestTimeout
385
+ );
386
+ });
@@ -0,0 +1,75 @@
1
+ import nlp from 'compromise';
2
+ import sort from '../sort/index.js';
3
+ import { bulkMapRetry } from '../bulk-map/index.js';
4
+ import { constants as promptConstants } from '../../prompts/index.js';
5
+ import parseLLMList from '../../lib/parse-llm-list/index.js';
6
+
7
+ const { onlyJSONStringArrayPerLine } = promptConstants;
8
+
9
+ /**
10
+ * Extract uncommon or technical terms from text that would benefit from definition.
11
+ *
12
+ * @param {string} text - source text
13
+ * @param {object} [options]
14
+ * @param {number} [options.maxTerms=10] - maximum terms to return
15
+ * @param {number} [options.batchSize=3] - number of sentences per batch
16
+ * @param {number} [options.overlap=1] - number of overlapping sentences between batches
17
+ * @param {number} [options.chunkSize=1] - number of text chunks to process in parallel
18
+ * @param {string} [options.sortBy='importance for understanding the content'] - sorting criteria
19
+ * @returns {Promise<string[]>} list of important terms, sorted by relevance
20
+ */
21
+ export default async function glossary(
22
+ text,
23
+ {
24
+ maxTerms = 10,
25
+ batchSize = 3,
26
+ overlap = 1,
27
+ chunkSize = 1,
28
+ sortBy = 'importance for understanding the content',
29
+ } = {}
30
+ ) {
31
+ if (!text || !text.trim()) return [];
32
+
33
+ // Parse sentences using compromise
34
+ const doc = nlp(text);
35
+ const sentences = doc.sentences().out('array');
36
+
37
+ if (sentences.length === 0) return [];
38
+
39
+ // Create batches of sentences with overlap
40
+ const textChunks = [];
41
+ for (let i = 0; i < sentences.length; i += batchSize - overlap) {
42
+ const batch = sentences.slice(i, i + batchSize);
43
+ if (batch.length > 0) {
44
+ textChunks.push(batch.join(' '));
45
+ }
46
+ }
47
+
48
+ const instructions = `For each text chunk, extract specialized terms that would benefit from definition in a glossary.
49
+
50
+ Focus on terms that:
51
+ - Are technical, academic, or domain-specific
52
+ - Would be unfamiliar to a general audience
53
+ - Carry precise meaning in their field
54
+ - Are essential for understanding the content
55
+
56
+ ${onlyJSONStringArrayPerLine}`;
57
+
58
+ const mapped = await bulkMapRetry(textChunks, instructions, { chunkSize });
59
+
60
+ const termSet = new Set();
61
+ mapped.forEach((line) => {
62
+ const terms = parseLLMList(line);
63
+ terms.forEach((term) => {
64
+ termSet.add(term);
65
+ });
66
+ });
67
+
68
+ const terms = Array.from(termSet);
69
+ if (terms.length === 0) return [];
70
+
71
+ // Sort by importance for understanding the content
72
+ const sorted = await sort(terms, sortBy);
73
+
74
+ return sorted.slice(0, maxTerms);
75
+ }
@@ -0,0 +1,19 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import glossary from './index.js';
3
+
4
+ vi.mock('../bulk-map/index.js', () => ({
5
+ bulkMapRetry: vi.fn(() => Promise.resolve(['qubits, entanglement', 'decoherence, qubits'])),
6
+ default: vi.fn(),
7
+ }));
8
+
9
+ vi.mock('../sort/index.js', () => ({
10
+ default: vi.fn((list, _criteria, _config) => Promise.resolve(list)),
11
+ }));
12
+
13
+ describe('glossary', () => {
14
+ it('collects unique terms', async () => {
15
+ const text = 'para1\n\npara2';
16
+ const result = await glossary(text, { maxTerms: 5 });
17
+ expect(result).toStrictEqual(['qubits', 'entanglement', 'decoherence']);
18
+ });
19
+ });
@@ -0,0 +1,152 @@
1
+ # Intersections Chain
2
+
3
+ The intersections chain finds comprehensive intersections for all combinations of items, ensuring consistent and exhaustive results through example-driven improvement with built-in quality validation.
4
+
5
+ ## Real-World Example: Planning a Perfect Weekend
6
+
7
+ Imagine you're planning activities that work for different groups of people. Let's find intersections between "outdoor enthusiasts", "food lovers", and "budget-conscious friends":
8
+
9
+ ```javascript
10
+ import intersections from './index.js';
11
+
12
+ const result = await intersections([
13
+ 'outdoor enthusiasts',
14
+ 'food lovers',
15
+ 'budget-conscious friends'
16
+ ]);
17
+
18
+ console.log(result);
19
+ ```
20
+
21
+ **Sample Output:**
22
+ ```javascript
23
+ {
24
+ "outdoor enthusiasts + food lovers": {
25
+ combination: ["outdoor enthusiasts", "food lovers"],
26
+ description: "Activities that combine outdoor adventure with culinary experiences",
27
+ elements: [
28
+ "camping with gourmet cooking",
29
+ "food truck festivals in parks",
30
+ "farmers market visits",
31
+ "outdoor barbecue competitions",
32
+ "hiking to scenic picnic spots",
33
+ "beach cookouts",
34
+ "food foraging tours"
35
+ ]
36
+ },
37
+ "outdoor enthusiasts + budget-conscious friends": {
38
+ combination: ["outdoor enthusiasts", "budget-conscious friends"],
39
+ description: "Affordable outdoor activities that don't break the bank",
40
+ elements: [
41
+ "free hiking trails",
42
+ "public beach visits",
43
+ "city park activities",
44
+ "free outdoor concerts",
45
+ "community garden volunteering",
46
+ "geocaching adventures",
47
+ "sunset watching spots"
48
+ ]
49
+ },
50
+ "food lovers + budget-conscious friends": {
51
+ combination: ["food lovers", "budget-conscious friends"],
52
+ description: "Delicious food experiences that are wallet-friendly",
53
+ elements: [
54
+ "happy hour specials",
55
+ "food truck meals",
56
+ "potluck dinner parties",
57
+ "cooking classes at community centers",
58
+ "ethnic food markets",
59
+ "restaurant week deals",
60
+ "home cooking challenges"
61
+ ]
62
+ },
63
+ "outdoor enthusiasts + food lovers + budget-conscious friends": {
64
+ combination: ["outdoor enthusiasts", "food lovers", "budget-conscious friends"],
65
+ description: "Perfect activities that satisfy adventure, food, and budget needs",
66
+ elements: [
67
+ "picnic in free parks with homemade food",
68
+ "food truck festivals in public spaces",
69
+ "community garden potlucks",
70
+ "beach barbecues with shared costs",
71
+ "hiking with packed gourmet sandwiches",
72
+ "free outdoor farmers markets"
73
+ ]
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## How it Works
79
+
80
+ 1. **Generate Combinations** - Creates all possible combinations of the input items (including single items)
81
+ 2. **Shuffle & Score** - Randomly orders combinations and uses AI to score quality (1-10 scale)
82
+ 3. **Find Best Examples** - Identifies the first 3 combinations that score above the threshold
83
+ 4. **Validate Examples** - Ensures the top examples are high-quality intersections
84
+ 5. **Improve All Results** - Uses the best examples as patterns to enhance all intersection results
85
+ 6. **Error Handling** - Throws an error if no combinations meet the quality threshold
86
+
87
+ This approach finds good examples organically, uses them to improve the quality of all results, and validates quality at key checkpoints.
88
+
89
+ ## API Reference
90
+
91
+ ### `intersections(items, options)`
92
+
93
+ **Parameters:**
94
+ - `items` (Array): Array of items to find intersections between
95
+ - `options` (Object, optional): Configuration options
96
+ - `instructions` (string): Custom instructions for intersection finding
97
+ - `minSize` (number, default: 1): Minimum combination size
98
+ - `maxSize` (number, default: items.length): Maximum combination size
99
+ - `batchSize` (number, default: 5): Number of combinations to process in parallel
100
+ - `goodnessScore` (number, default: 7): Minimum score threshold for good examples (1-10 scale)
101
+
102
+ **Returns:** Object with intersection results
103
+
104
+ **Throws:** Error when no combinations score above the goodness threshold
105
+
106
+ ### Usage Examples
107
+
108
+ ```javascript
109
+ // Basic usage
110
+ const results = await intersections(['cats', 'dogs', 'birds']);
111
+
112
+ // Custom configuration
113
+ const results = await intersections(['hiking', 'photography', 'meditation'], {
114
+ instructions: 'Focus on peaceful, mindful activities',
115
+ batchSize: 10, // Process more combinations in parallel
116
+ goodnessScore: 8, // Higher quality threshold
117
+ minSize: 2, // Only combinations of 2+ items
118
+ maxSize: 3 // Maximum 3 items per combination
119
+ });
120
+
121
+ // Error handling
122
+ try {
123
+ const results = await intersections(['incompatible', 'categories'], {
124
+ goodnessScore: 9 // Very high threshold
125
+ });
126
+ } catch (error) {
127
+ console.log(error.message);
128
+ // "No intersections found with score above 9. Consider lowering the goodnessScore threshold or improving input quality."
129
+ }
130
+ ```
131
+
132
+ ## Configuration Tips
133
+
134
+ - **Lower `goodnessScore`** (5-6) for more permissive quality standards
135
+ - **Higher `goodnessScore`** (8-9) for stricter quality requirements
136
+ - **Increase `batchSize`** (10-15) for faster processing with good API limits
137
+ - **Decrease `batchSize`** (2-3) to be gentler on API rate limits
138
+ - **Use `instructions`** to guide the AI toward specific types of intersections
139
+
140
+ ## Quality Assurance
141
+
142
+ The chain includes built-in quality validation:
143
+
144
+ - **Example Validation**: Ensures the top 3 examples have meaningful descriptions, good element counts, and represent quality intersections
145
+ - **Error Handling**: Throws descriptive errors when quality thresholds aren't met
146
+ - **Parallel Processing**: Efficiently processes combinations in configurable batches
147
+
148
+ The system learns from its best results and applies those patterns to enhance the quality of all intersections, moving away from predefined categorization methods.
149
+
150
+ ## Usage
151
+
152
+ ```javascript