@far-world-labs/verblets 0.1.7 → 0.3.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 (306) hide show
  1. package/README.md +86 -213
  2. package/dist/index.browser.js +74 -0
  3. package/dist/index.js +548 -0
  4. package/dist/shared-C6kPWghF.js +7806 -0
  5. package/package.json +32 -8
  6. package/.cursor/launch.json +0 -30
  7. package/.cursor/settings.json +0 -20
  8. package/.github/workflows/branch-protection.yml +0 -22
  9. package/.github/workflows/ci.yml +0 -165
  10. package/.husky/pre-commit +0 -4
  11. package/.prettierrc +0 -6
  12. package/.release-it.json +0 -12
  13. package/.vitest.config.examples.js +0 -8
  14. package/.vitest.config.js +0 -8
  15. package/.vscode/launch.json +0 -31
  16. package/AGENTS.md +0 -220
  17. package/DEVELOPING.md +0 -105
  18. package/docker-compose.yml +0 -7
  19. package/eslint.config.js +0 -80
  20. package/scripts/generate-chain/index.js +0 -111
  21. package/scripts/generate-lib/index.js +0 -68
  22. package/scripts/generate-test/index.js +0 -137
  23. package/scripts/generate-verblet/README.md +0 -17
  24. package/scripts/generate-verblet/index.js +0 -110
  25. package/scripts/run.sh +0 -15
  26. package/scripts/runner/index.js +0 -56
  27. package/scripts/simple-editor/README.md +0 -34
  28. package/scripts/simple-editor/index.js +0 -79
  29. package/scripts/summarize-files/index.js +0 -70
  30. package/src/chains/README.md +0 -30
  31. package/src/chains/anonymize/README.md +0 -21
  32. package/src/chains/anonymize/index.examples.js +0 -75
  33. package/src/chains/anonymize/index.js +0 -121
  34. package/src/chains/anonymize/index.spec.js +0 -78
  35. package/src/chains/bulk-central-tendency/index.examples.js +0 -138
  36. package/src/chains/bulk-central-tendency/index.js +0 -91
  37. package/src/chains/bulk-filter/README.md +0 -21
  38. package/src/chains/bulk-filter/index.examples.js +0 -22
  39. package/src/chains/bulk-filter/index.js +0 -58
  40. package/src/chains/bulk-filter/index.spec.js +0 -38
  41. package/src/chains/bulk-find/README.md +0 -16
  42. package/src/chains/bulk-find/index.examples.js +0 -20
  43. package/src/chains/bulk-find/index.js +0 -30
  44. package/src/chains/bulk-find/index.spec.js +0 -26
  45. package/src/chains/bulk-group/README.md +0 -23
  46. package/src/chains/bulk-group/index.examples.js +0 -18
  47. package/src/chains/bulk-group/index.js +0 -34
  48. package/src/chains/bulk-group/index.spec.js +0 -41
  49. package/src/chains/bulk-map/README.md +0 -43
  50. package/src/chains/bulk-map/index.examples.js +0 -17
  51. package/src/chains/bulk-map/index.js +0 -86
  52. package/src/chains/bulk-map/index.spec.js +0 -44
  53. package/src/chains/bulk-reduce/README.md +0 -12
  54. package/src/chains/bulk-reduce/index.examples.js +0 -15
  55. package/src/chains/bulk-reduce/index.js +0 -13
  56. package/src/chains/bulk-reduce/index.spec.js +0 -25
  57. package/src/chains/bulk-score/README.md +0 -16
  58. package/src/chains/bulk-score/bulk-score-result.json +0 -18
  59. package/src/chains/bulk-score/index.examples.js +0 -22
  60. package/src/chains/bulk-score/index.js +0 -133
  61. package/src/chains/bulk-score/index.spec.js +0 -30
  62. package/src/chains/category-samples/README.md +0 -61
  63. package/src/chains/category-samples/index.examples.js +0 -103
  64. package/src/chains/category-samples/index.js +0 -134
  65. package/src/chains/collect-terms/README.md +0 -12
  66. package/src/chains/collect-terms/index.examples.js +0 -16
  67. package/src/chains/collect-terms/index.js +0 -44
  68. package/src/chains/collect-terms/index.spec.js +0 -25
  69. package/src/chains/date/README.md +0 -12
  70. package/src/chains/date/index.examples.js +0 -47
  71. package/src/chains/date/index.js +0 -74
  72. package/src/chains/date/index.spec.js +0 -62
  73. package/src/chains/disambiguate/README.md +0 -22
  74. package/src/chains/disambiguate/disambiguate-meanings-result.json +0 -16
  75. package/src/chains/disambiguate/index.examples.js +0 -18
  76. package/src/chains/disambiguate/index.js +0 -92
  77. package/src/chains/disambiguate/index.spec.js +0 -25
  78. package/src/chains/dismantle/README.md +0 -67
  79. package/src/chains/dismantle/dismantle.examples.js +0 -27
  80. package/src/chains/dismantle/index.examples.js +0 -30
  81. package/src/chains/dismantle/index.js +0 -303
  82. package/src/chains/dismantle/index.spec.js +0 -32
  83. package/src/chains/expect/README.md +0 -171
  84. package/src/chains/expect/index.examples.js +0 -146
  85. package/src/chains/expect/index.js +0 -173
  86. package/src/chains/expect/index.spec.js +0 -324
  87. package/src/chains/filter-ambiguous/README.md +0 -11
  88. package/src/chains/filter-ambiguous/index.examples.js +0 -20
  89. package/src/chains/filter-ambiguous/index.js +0 -49
  90. package/src/chains/filter-ambiguous/index.spec.js +0 -31
  91. package/src/chains/glossary/README.md +0 -19
  92. package/src/chains/glossary/index.examples.js +0 -386
  93. package/src/chains/glossary/index.js +0 -75
  94. package/src/chains/glossary/index.spec.js +0 -19
  95. package/src/chains/intersections/README.md +0 -152
  96. package/src/chains/intersections/index.examples.js +0 -279
  97. package/src/chains/intersections/index.js +0 -366
  98. package/src/chains/intersections/intersection-result.json +0 -38
  99. package/src/chains/list/index.examples.js +0 -68
  100. package/src/chains/list/index.js +0 -214
  101. package/src/chains/list/index.spec.js +0 -67
  102. package/src/chains/list/list-result.json +0 -16
  103. package/src/chains/list/schema.json +0 -24
  104. package/src/chains/llm-logger/README.md +0 -208
  105. package/src/chains/llm-logger/index.js +0 -205
  106. package/src/chains/llm-logger/index.spec.js +0 -330
  107. package/src/chains/questions/index.examples.js +0 -69
  108. package/src/chains/questions/index.js +0 -135
  109. package/src/chains/questions/index.spec.js +0 -29
  110. package/src/chains/scan-js/index.js +0 -116
  111. package/src/chains/set-interval/README.md +0 -81
  112. package/src/chains/set-interval/index.examples.js +0 -36
  113. package/src/chains/set-interval/index.js +0 -131
  114. package/src/chains/set-interval/index.spec.js +0 -70
  115. package/src/chains/socratic/README.md +0 -17
  116. package/src/chains/socratic/index.js +0 -64
  117. package/src/chains/socratic/index.spec.js +0 -24
  118. package/src/chains/sort/index.examples.js +0 -36
  119. package/src/chains/sort/index.js +0 -163
  120. package/src/chains/sort/index.spec.js +0 -112
  121. package/src/chains/sort/sort-result.json +0 -16
  122. package/src/chains/summary-map/README.md +0 -41
  123. package/src/chains/summary-map/index.examples.js +0 -64
  124. package/src/chains/summary-map/index.js +0 -226
  125. package/src/chains/summary-map/index.spec.js +0 -153
  126. package/src/chains/test/index.js +0 -114
  127. package/src/chains/test-advice/index.js +0 -35
  128. package/src/chains/themes/README.md +0 -20
  129. package/src/chains/themes/index.examples.js +0 -17
  130. package/src/chains/themes/index.js +0 -28
  131. package/src/chains/themes/index.spec.js +0 -19
  132. package/src/chains/veiled-variants/index.examples.js +0 -18
  133. package/src/chains/veiled-variants/index.js +0 -107
  134. package/src/chains/veiled-variants/index.spec.js +0 -40
  135. package/src/constants/common.js +0 -7
  136. package/src/constants/messages.js +0 -3
  137. package/src/constants/models.js +0 -183
  138. package/src/index.js +0 -193
  139. package/src/json-schemas/README.md +0 -13
  140. package/src/json-schemas/cars-test.json +0 -11
  141. package/src/json-schemas/index.js +0 -12
  142. package/src/json-schemas/intent.json +0 -38
  143. package/src/json-schemas/schema-dot-org-photograph.json +0 -133
  144. package/src/json-schemas/schema-dot-org-place.json +0 -129
  145. package/src/lib/README.md +0 -26
  146. package/src/lib/any-signal/index.js +0 -28
  147. package/src/lib/bulk-filter/README.md +0 -22
  148. package/src/lib/bulk-filter/index.examples.js +0 -27
  149. package/src/lib/bulk-filter/index.js +0 -63
  150. package/src/lib/bulk-filter/index.spec.js +0 -38
  151. package/src/lib/bulk-find/README.md +0 -18
  152. package/src/lib/bulk-find/index.examples.js +0 -19
  153. package/src/lib/bulk-find/index.js +0 -30
  154. package/src/lib/bulk-find/index.spec.js +0 -41
  155. package/src/lib/chatgpt/index.js +0 -163
  156. package/src/lib/combinations/index.js +0 -30
  157. package/src/lib/combinations/index.spec.js +0 -23
  158. package/src/lib/editor/index.js +0 -31
  159. package/src/lib/functional/index.js +0 -28
  160. package/src/lib/logger-service/index.js +0 -32
  161. package/src/lib/parse-js-parts/index.js +0 -321
  162. package/src/lib/parse-js-parts/index.spec.js +0 -156
  163. package/src/lib/parse-llm-list/README.md +0 -39
  164. package/src/lib/parse-llm-list/index.js +0 -54
  165. package/src/lib/parse-llm-list/index.spec.js +0 -59
  166. package/src/lib/path-aliases/index.js +0 -37
  167. package/src/lib/path-aliases/index.spec.js +0 -64
  168. package/src/lib/pave/index.js +0 -34
  169. package/src/lib/pave/index.spec.js +0 -76
  170. package/src/lib/prompt-cache/index.js +0 -50
  171. package/src/lib/retry/index.js +0 -66
  172. package/src/lib/retry/index.spec.js +0 -86
  173. package/src/lib/ring-buffer/README.md +0 -460
  174. package/src/lib/ring-buffer/index.js +0 -1074
  175. package/src/lib/search-best-first/city-walk.spec.js +0 -37
  176. package/src/lib/search-best-first/index.js +0 -97
  177. package/src/lib/search-best-first/index.spec.js +0 -35
  178. package/src/lib/search-js-files/code-features-property-definitions.json +0 -123
  179. package/src/lib/search-js-files/index.examples.js +0 -22
  180. package/src/lib/search-js-files/index.js +0 -155
  181. package/src/lib/search-js-files/index.spec.js +0 -34
  182. package/src/lib/search-js-files/scan-file.js +0 -242
  183. package/src/lib/shorten-text/index.js +0 -25
  184. package/src/lib/shorten-text/index.spec.js +0 -68
  185. package/src/lib/strip-numeric/index.js +0 -5
  186. package/src/lib/strip-response/index.js +0 -30
  187. package/src/lib/template-replace/index.js +0 -23
  188. package/src/lib/template-replace/index.spec.js +0 -60
  189. package/src/lib/timed-abort-controller/index.js +0 -41
  190. package/src/lib/to-bool/index.js +0 -8
  191. package/src/lib/to-date/index.js +0 -11
  192. package/src/lib/to-enum/index.js +0 -14
  193. package/src/lib/to-number/index.js +0 -12
  194. package/src/lib/to-number-with-units/index.js +0 -51
  195. package/src/lib/transcribe/index.js +0 -78
  196. package/src/prompts/README.md +0 -17
  197. package/src/prompts/as-enum.js +0 -5
  198. package/src/prompts/as-json-schema.js +0 -9
  199. package/src/prompts/as-object-with-schema.js +0 -26
  200. package/src/prompts/as-schema-org-text.js +0 -25
  201. package/src/prompts/as-schema-org-type.js +0 -1
  202. package/src/prompts/blog-post.js +0 -7
  203. package/src/prompts/code-features.js +0 -24
  204. package/src/prompts/constants.js +0 -101
  205. package/src/prompts/features-json-schema.js +0 -27
  206. package/src/prompts/generate-collection.js +0 -26
  207. package/src/prompts/generate-list.js +0 -48
  208. package/src/prompts/generate-questions.js +0 -19
  209. package/src/prompts/index.js +0 -20
  210. package/src/prompts/intent.js +0 -60
  211. package/src/prompts/output-succinct-names.js +0 -3
  212. package/src/prompts/select-from-threshold.js +0 -17
  213. package/src/prompts/sort.js +0 -31
  214. package/src/prompts/style.js +0 -38
  215. package/src/prompts/summarize.js +0 -13
  216. package/src/prompts/token-budget.js +0 -3
  217. package/src/prompts/wrap-list.js +0 -11
  218. package/src/prompts/wrap-variable.js +0 -36
  219. package/src/services/llm-model/global-overrides.spec.js +0 -432
  220. package/src/services/llm-model/index.js +0 -308
  221. package/src/services/llm-model/model.js +0 -21
  222. package/src/services/llm-model/negotiate.spec.js +0 -447
  223. package/src/services/redis/index.js +0 -147
  224. package/src/test/setup.js +0 -20
  225. package/src/verblets/README.md +0 -26
  226. package/src/verblets/auto/index.examples.js +0 -31
  227. package/src/verblets/auto/index.js +0 -28
  228. package/src/verblets/auto/index.spec.js +0 -32
  229. package/src/verblets/bool/README.md +0 -36
  230. package/src/verblets/bool/index.examples.js +0 -80
  231. package/src/verblets/bool/index.js +0 -25
  232. package/src/verblets/bool/index.schema.json +0 -14
  233. package/src/verblets/bool/index.spec.js +0 -33
  234. package/src/verblets/central-tendency/README.md +0 -166
  235. package/src/verblets/central-tendency/central-tendency-result.json +0 -24
  236. package/src/verblets/central-tendency/index.examples.js +0 -196
  237. package/src/verblets/central-tendency/index.js +0 -171
  238. package/src/verblets/central-tendency/index.spec.js +0 -148
  239. package/src/verblets/enum/index.examples.js +0 -30
  240. package/src/verblets/enum/index.js +0 -18
  241. package/src/verblets/enum/index.spec.js +0 -35
  242. package/src/verblets/expect/README.md +0 -64
  243. package/src/verblets/expect/index.examples.js +0 -109
  244. package/src/verblets/expect/index.js +0 -75
  245. package/src/verblets/expect/index.spec.js +0 -127
  246. package/src/verblets/intent/index.examples.js +0 -139
  247. package/src/verblets/intent/index.js +0 -60
  248. package/src/verblets/intent/index.spec.js +0 -31
  249. package/src/verblets/intersection/README.md +0 -16
  250. package/src/verblets/intersection/index.examples.js +0 -89
  251. package/src/verblets/intersection/index.js +0 -84
  252. package/src/verblets/intersection/index.spec.js +0 -60
  253. package/src/verblets/intersection/intersection-result.json +0 -16
  254. package/src/verblets/list-expand/README.md +0 -10
  255. package/src/verblets/list-expand/index.examples.js +0 -14
  256. package/src/verblets/list-expand/index.js +0 -104
  257. package/src/verblets/list-expand/index.spec.js +0 -18
  258. package/src/verblets/list-expand/list-expand-result.json +0 -16
  259. package/src/verblets/list-filter/README.md +0 -22
  260. package/src/verblets/list-filter/index.examples.js +0 -26
  261. package/src/verblets/list-filter/index.js +0 -18
  262. package/src/verblets/list-filter/index.spec.js +0 -19
  263. package/src/verblets/list-find/README.md +0 -11
  264. package/src/verblets/list-find/index.examples.js +0 -15
  265. package/src/verblets/list-find/index.js +0 -17
  266. package/src/verblets/list-find/index.spec.js +0 -19
  267. package/src/verblets/list-group/README.md +0 -16
  268. package/src/verblets/list-group/index.examples.js +0 -16
  269. package/src/verblets/list-group/index.js +0 -112
  270. package/src/verblets/list-group/index.spec.js +0 -35
  271. package/src/verblets/list-group/list-group-result.json +0 -16
  272. package/src/verblets/list-map/README.md +0 -11
  273. package/src/verblets/list-map/index.examples.js +0 -15
  274. package/src/verblets/list-map/index.js +0 -26
  275. package/src/verblets/list-map/index.spec.js +0 -17
  276. package/src/verblets/list-reduce/README.md +0 -10
  277. package/src/verblets/list-reduce/index.examples.js +0 -14
  278. package/src/verblets/list-reduce/index.js +0 -21
  279. package/src/verblets/list-reduce/index.spec.js +0 -27
  280. package/src/verblets/list-reduce/index.spec.jsx +0 -27
  281. package/src/verblets/name/README.md +0 -15
  282. package/src/verblets/name/index.examples.js +0 -28
  283. package/src/verblets/name/index.js +0 -19
  284. package/src/verblets/name/index.spec.js +0 -33
  285. package/src/verblets/name-similar-to/README.md +0 -26
  286. package/src/verblets/name-similar-to/index.examples.js +0 -18
  287. package/src/verblets/name-similar-to/index.js +0 -20
  288. package/src/verblets/name-similar-to/index.spec.js +0 -13
  289. package/src/verblets/number/index.examples.js +0 -199
  290. package/src/verblets/number/index.js +0 -25
  291. package/src/verblets/number/index.spec.js +0 -33
  292. package/src/verblets/number-with-units/index.examples.js +0 -38
  293. package/src/verblets/number-with-units/index.js +0 -84
  294. package/src/verblets/number-with-units/index.spec.js +0 -46
  295. package/src/verblets/number-with-units/number-with-units-result.json +0 -23
  296. package/src/verblets/schema-org/index.examples.js +0 -51
  297. package/src/verblets/schema-org/index.js +0 -37
  298. package/src/verblets/schema-org/index.spec.js +0 -39
  299. package/src/verblets/sentiment/README.md +0 -10
  300. package/src/verblets/sentiment/index.examples.js +0 -20
  301. package/src/verblets/sentiment/index.js +0 -9
  302. package/src/verblets/sentiment/index.spec.js +0 -20
  303. package/src/verblets/to-object/README.md +0 -38
  304. package/src/verblets/to-object/index.examples.js +0 -29
  305. package/src/verblets/to-object/index.js +0 -131
  306. package/src/verblets/to-object/index.spec.js +0 -71
@@ -1,279 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import intersections from './index.js';
3
- import { expect as aiExpect } from '../expect/index.js';
4
- import { longTestTimeout } from '../../constants/common.js';
5
-
6
- describe('intersections chain examples', () => {
7
- it(
8
- 'analyzes technology categories comprehensively',
9
- async () => {
10
- const result = await intersections(['software', 'hardware', 'networking']);
11
-
12
- // Basic validation
13
- expect(typeof result).toBe('object');
14
- expect(result).not.toBeNull();
15
-
16
- // Should have meaningful intersections
17
- expect(Object.keys(result).length).toBeGreaterThan(0);
18
-
19
- // Validate structure of each intersection
20
- // eslint-disable-next-line no-unused-vars
21
- for (const [_comboKey, intersection] of Object.entries(result)) {
22
- expect(intersection.combination).toBeDefined();
23
- expect(intersection.description).toBeDefined();
24
- expect(intersection.elements).toBeDefined();
25
- expect(Array.isArray(intersection.combination)).toBe(true);
26
- expect(Array.isArray(intersection.elements)).toBe(true);
27
- expect(typeof intersection.description).toBe('string');
28
- expect(intersection.combination.length).toBeGreaterThanOrEqual(2);
29
- }
30
-
31
- // AI validation of technology intersections
32
- const [hasValidTechIntersections] = await aiExpect(
33
- result,
34
- undefined,
35
- 'Should contain meaningful intersections between technology categories with relevant examples'
36
- );
37
- expect(hasValidTechIntersections).toBe(true);
38
- },
39
- longTestTimeout
40
- );
41
-
42
- it(
43
- 'handles diverse categories with meaningful intersections',
44
- async () => {
45
- const result = await intersections(['art', 'science']);
46
-
47
- // Basic validation
48
- expect(typeof result).toBe('object');
49
- expect(result).not.toBeNull();
50
-
51
- // Should have at least one intersection for these broad categories
52
- if (Object.keys(result).length > 0) {
53
- const firstIntersection = Object.values(result)[0];
54
- expect(Array.isArray(firstIntersection.elements)).toBe(true);
55
- expect(typeof firstIntersection.description).toBe('string');
56
- expect(Array.isArray(firstIntersection.combination)).toBe(true);
57
- expect(firstIntersection.combination).toContain('art');
58
- expect(firstIntersection.combination).toContain('science');
59
-
60
- // AI validation of meaningful intersections
61
- const [hasMeaningfulIntersections] = await aiExpect(
62
- result,
63
- undefined,
64
- 'Should contain meaningful intersections between art and science with relevant examples'
65
- );
66
- expect(hasMeaningfulIntersections).toBe(true);
67
- }
68
- },
69
- longTestTimeout
70
- );
71
-
72
- it(
73
- 'validates schema compliance and structure quality',
74
- async () => {
75
- const result = await intersections(['music', 'mathematics']);
76
-
77
- // Schema validation - should be an object
78
- expect(typeof result).toBe('object');
79
- expect(result).not.toBeNull();
80
- expect(Array.isArray(result)).toBe(false);
81
-
82
- // If we have results, validate strict schema compliance
83
- if (Object.keys(result).length > 0) {
84
- for (const [comboKey, intersection] of Object.entries(result)) {
85
- // Key format validation (should be "category + category")
86
- expect(comboKey).toMatch(/^.+ \+ .+$/);
87
-
88
- // Required properties
89
- expect(intersection).toHaveProperty('combination');
90
- expect(intersection).toHaveProperty('description');
91
- expect(intersection).toHaveProperty('elements');
92
-
93
- // Type validation
94
- expect(Array.isArray(intersection.combination)).toBe(true);
95
- expect(typeof intersection.description).toBe('string');
96
- expect(Array.isArray(intersection.elements)).toBe(true);
97
-
98
- // Content validation
99
- expect(intersection.combination.length).toBeGreaterThanOrEqual(2);
100
- expect(intersection.description.length).toBeGreaterThan(0);
101
- expect(intersection.combination).toContain('music');
102
- expect(intersection.combination).toContain('mathematics');
103
- }
104
-
105
- // AI validation of music-math intersections
106
- const [hasValidMusicMathIntersections] = await aiExpect(
107
- Object.values(result)[0].elements,
108
- undefined,
109
- 'Should contain examples that genuinely belong to both music and mathematics'
110
- );
111
- expect(hasValidMusicMathIntersections).toBe(true);
112
- }
113
- },
114
- longTestTimeout
115
- );
116
-
117
- it(
118
- 'handles complex multi-category intersections',
119
- async () => {
120
- const result = await intersections(['biology', 'chemistry', 'physics'], {
121
- maxSize: 3,
122
- minSize: 2,
123
- });
124
-
125
- // Basic validation
126
- expect(typeof result).toBe('object');
127
- expect(result).not.toBeNull();
128
-
129
- // Should handle multiple intersection types
130
- if (Object.keys(result).length > 0) {
131
- let hasTwoWayIntersection = false;
132
- let _hasThreeWayIntersection = false;
133
-
134
- // eslint-disable-next-line no-unused-vars
135
- for (const [_comboKey, intersection] of Object.entries(result)) {
136
- const comboSize = intersection.combination.length;
137
-
138
- if (comboSize === 2) hasTwoWayIntersection = true;
139
- // eslint-disable-next-line no-unused-vars
140
- if (comboSize === 3) _hasThreeWayIntersection = true;
141
-
142
- // Validate structure
143
- expect(Array.isArray(intersection.combination)).toBe(true);
144
- expect(typeof intersection.description).toBe('string');
145
- expect(Array.isArray(intersection.elements)).toBe(true);
146
-
147
- // All combinations should be from our input categories
148
- for (const category of intersection.combination) {
149
- expect(['biology', 'chemistry', 'physics']).toContain(category);
150
- }
151
- }
152
-
153
- // Should have at least two-way intersections for science categories
154
- expect(hasTwoWayIntersection).toBe(true);
155
-
156
- // AI validation of scientific intersections
157
- const [hasValidScienceIntersections] = await aiExpect(
158
- result,
159
- undefined,
160
- 'Should contain meaningful intersections between scientific disciplines with relevant examples'
161
- );
162
- expect(hasValidScienceIntersections).toBe(true);
163
- }
164
- },
165
- longTestTimeout
166
- );
167
-
168
- it(
169
- 'handles single category appropriately',
170
- async () => {
171
- const result = await intersections(['photography']);
172
-
173
- // Should return empty object since intersections require multiple items
174
- expect(Object.keys(result).length).toBe(0);
175
-
176
- // Validate single category handling
177
- const [isEmptyObject] = await aiExpect(
178
- result,
179
- undefined,
180
- 'Should be an empty object since intersections require multiple categories'
181
- );
182
- expect(isEmptyObject).toBe(true);
183
- },
184
- longTestTimeout
185
- );
186
-
187
- it(
188
- 'produces consistent and logical results with quality validation',
189
- async () => {
190
- const result = await intersections(['literature', 'psychology'], {
191
- goodnessScore: 8, // Higher quality threshold
192
- });
193
-
194
- // Basic validation - should be an object
195
- expect(typeof result).toBe('object');
196
- expect(result).not.toBeNull();
197
-
198
- // If we have results, validate high-quality structure
199
- if (Object.keys(result).length > 0) {
200
- // eslint-disable-next-line no-unused-vars
201
- for (const [_comboKey, intersection] of Object.entries(result)) {
202
- // Basic structure checks
203
- expect(intersection.combination).toBeDefined();
204
- expect(intersection.description).toBeDefined();
205
- expect(intersection.elements).toBeDefined();
206
- expect(Array.isArray(intersection.combination)).toBe(true);
207
- expect(Array.isArray(intersection.elements)).toBe(true);
208
- expect(typeof intersection.description).toBe('string');
209
-
210
- // Quality checks - should have meaningful content
211
- expect(intersection.description.length).toBeGreaterThan(10);
212
- expect(intersection.elements.length).toBeGreaterThan(0);
213
- }
214
-
215
- // AI validation of literature-psychology intersections
216
- const [hasQualityIntersections] = await aiExpect(
217
- result,
218
- undefined,
219
- 'Should contain high-quality intersections between literature and psychology with meaningful examples'
220
- );
221
- expect(hasQualityIntersections).toBe(true);
222
- }
223
- },
224
- longTestTimeout
225
- );
226
-
227
- it(
228
- 'handles edge cases gracefully',
229
- async () => {
230
- const result = await intersections([]);
231
-
232
- // Validate empty input handling
233
- const [isEmptyForEmptyInput] = await aiExpect(
234
- result,
235
- undefined,
236
- 'Should be an empty object for empty input'
237
- );
238
- expect(isEmptyForEmptyInput).toBe(true);
239
-
240
- // Validate empty result structure
241
- expect(Object.keys(result).length).toBe(0);
242
- },
243
- longTestTimeout
244
- );
245
-
246
- it(
247
- 'validates custom instructions and configuration',
248
- async () => {
249
- const customInstructions = 'Focus on practical applications and real-world examples';
250
- const result = await intersections(['engineering', 'design'], {
251
- instructions: customInstructions,
252
- batchSize: 2,
253
- });
254
-
255
- // Basic validation
256
- expect(typeof result).toBe('object');
257
- expect(result).not.toBeNull();
258
-
259
- // If we have results, validate they follow custom instructions
260
- if (Object.keys(result).length > 0) {
261
- const firstIntersection = Object.values(result)[0];
262
-
263
- // Structure validation
264
- expect(Array.isArray(firstIntersection.combination)).toBe(true);
265
- expect(typeof firstIntersection.description).toBe('string');
266
- expect(Array.isArray(firstIntersection.elements)).toBe(true);
267
-
268
- // AI validation that results follow custom instructions
269
- const [followsInstructions] = await aiExpect(
270
- firstIntersection,
271
- undefined,
272
- 'Should contain practical applications and real-world examples as requested in custom instructions'
273
- );
274
- expect(followsInstructions).toBe(true);
275
- }
276
- },
277
- longTestTimeout
278
- );
279
- });
@@ -1,366 +0,0 @@
1
- import intersection from '../../verblets/intersection/index.js';
2
- import { rangeCombinations } from '../../lib/combinations/index.js';
3
- import chatGPT from '../../lib/chatgpt/index.js';
4
- import wrapVariable from '../../prompts/wrap-variable.js';
5
- import pkg from 'lodash';
6
- const { shuffle } = pkg;
7
- import number from '../../verblets/number/index.js';
8
- import { constants as promptConstants } from '../../prompts/index.js';
9
- import fs from 'node:fs/promises';
10
- import path from 'node:path';
11
- import { fileURLToPath } from 'node:url';
12
-
13
- // Get the directory of this module
14
- const __filename = fileURLToPath(import.meta.url);
15
- const __dirname = path.dirname(__filename);
16
-
17
- const {
18
- onlyJSONStringArray,
19
- asNumber,
20
- strictFormat,
21
- contentIsQuestion,
22
- explainAndSeparate,
23
- explainAndSeparatePrimitive,
24
- } = promptConstants;
25
-
26
- /**
27
- * Load the JSON schema for intersection results
28
- * @returns {Promise<Object>} JSON schema for validation
29
- */
30
- async function getIntersectionSchema() {
31
- const schemaPath = path.join(__dirname, 'intersection-result.json');
32
- return JSON.parse(await fs.readFile(schemaPath, 'utf8'));
33
- }
34
-
35
- /**
36
- * Prompt for listing elements that belong to all categories
37
- */
38
- const ELEMENTS_PROMPT = (categories, instructions) => {
39
- const basePrompt = `${contentIsQuestion} List specific examples, instances, or elements that belong to all of: ${categories.join(
40
- ', '
41
- )}.`;
42
-
43
- const instructionsText = instructions ? `\n\nAdditional guidance: ${instructions}` : '';
44
-
45
- return `${basePrompt}${instructionsText}
46
-
47
- ${strictFormat} ${onlyJSONStringArray}`;
48
- };
49
-
50
- /**
51
- * Prompt for scoring intersection results
52
- */
53
- const SCORING_PROMPT = (
54
- combo,
55
- description,
56
- elements
57
- ) => `${contentIsQuestion} Rate this intersection result on a scale of 1-10:
58
-
59
- Combination: ${combo.join(' + ')}
60
- Description: ${description}
61
- Elements: ${elements.join(', ')}
62
-
63
- Criteria: Correctly lists specific examples, instances, or elements that belong to ALL categories simultaneously, with meaningful description.
64
-
65
- ${explainAndSeparate} ${explainAndSeparatePrimitive}
66
-
67
- ${asNumber}`;
68
-
69
- /**
70
- * Prompt for generating exhaustive elements using examples
71
- */
72
- const EXHAUSTIVE_PROMPT = (examplePrompts, combo, instructions) => {
73
- const basePrompt = `Here are examples of well-enumerated intersections:
74
-
75
- ${examplePrompts}
76
-
77
- ${contentIsQuestion} For the combination: ${combo.join(' + ')}
78
-
79
- Provide an exhaustive list of specific examples, instances, or elements that belong to ALL of these categories simultaneously. Be as comprehensive as possible, following the pattern of the examples above.`;
80
-
81
- const instructionsText = instructions ? `\n\nAdditional guidance: ${instructions}` : '';
82
-
83
- return `${basePrompt}${instructionsText}
84
-
85
- ${wrapVariable(combo.join(' | '), { tag: 'combination' })}
86
-
87
- ${strictFormat} ${onlyJSONStringArray}`;
88
- };
89
-
90
- /**
91
- * Parse elements from LLM response (now expects JSON array)
92
- */
93
- const parseElements = (elementsText) => {
94
- try {
95
- const parsed = JSON.parse(elementsText.trim());
96
- return Array.isArray(parsed) ? parsed.filter(Boolean) : [];
97
- } catch {
98
- // Fallback to old parsing method if JSON parsing fails
99
- return elementsText
100
- .split('\n')
101
- .map((line) => line.replace(/^[-*•]\s*/, '').trim())
102
- .filter(Boolean);
103
- }
104
- };
105
-
106
- /**
107
- * Process a single combination to get elements, description, and score
108
- */
109
- const processCombo = async (combo, instructions, llm) => {
110
- const comboKey = combo.join(' + ');
111
-
112
- // Process elements, description, and scoring in parallel
113
- const [elements, intersectionItems] = await Promise.all([
114
- chatGPT(ELEMENTS_PROMPT(combo, instructions), { modelOptions: llm }),
115
- intersection(combo, { instructions, llm }),
116
- ]);
117
-
118
- const elementList = parseElements(elements);
119
- const description = Array.isArray(intersectionItems)
120
- ? intersectionItems.join(', ')
121
- : String(intersectionItems);
122
- const score = await number(SCORING_PROMPT(combo, description, elementList), { llm });
123
-
124
- return {
125
- combo,
126
- comboKey,
127
- description,
128
- elementList,
129
- score,
130
- };
131
- };
132
-
133
- /**
134
- * Process remaining combination using top examples as patterns
135
- */
136
- const processRemainingCombo = async (combo, instructions, examplePrompts, llm) => {
137
- const comboKey = combo.join(' + ');
138
-
139
- const [exhaustiveElements, intersectionItems] = await Promise.all([
140
- chatGPT(EXHAUSTIVE_PROMPT(examplePrompts, combo, instructions), { modelOptions: llm }),
141
- intersection(combo, { instructions, llm }),
142
- ]);
143
-
144
- const exhaustiveList = parseElements(exhaustiveElements);
145
- const description = Array.isArray(intersectionItems)
146
- ? intersectionItems.join(', ')
147
- : String(intersectionItems);
148
-
149
- return {
150
- key: comboKey,
151
- intersection: {
152
- combination: combo,
153
- description,
154
- elements: exhaustiveList,
155
- },
156
- };
157
- };
158
-
159
- /**
160
- * Find intersections for all combinations of items with consistent, exhaustive results
161
- *
162
- * @param {Array} items - Array of items to find intersections between
163
- * @param {Object} options - Configuration options
164
- * @param {string} options.instructions - Custom instructions for intersection finding
165
- * @param {number} options.minSize - Minimum combination size (default: 2)
166
- * @param {number} options.maxSize - Maximum combination size (default: items.length)
167
- * @param {number} options.batchSize - Number of combinations to process in parallel (default: 5)
168
- * @param {number} options.goodnessScore - Minimum score threshold for good examples (default: 7)
169
- * @param {string|Object} options.llm - LLM model to use (default: 'fastGoodCheap' with extended timeout)
170
- * @param {boolean} options.useSchemaValidation - Whether to validate results with JSON schema (default: false)
171
- * @returns {Object} Results with combinations, elements, and exhaustive intersections
172
- * @throws {Error} When no combinations score above the goodness threshold
173
- */
174
- export default async function intersections(items, options = {}) {
175
- if (!Array.isArray(items) || items.length < 2) {
176
- return {};
177
- }
178
-
179
- const {
180
- instructions,
181
- minSize = 2,
182
- maxSize = items.length,
183
- batchSize = 5,
184
- goodnessScore = 7,
185
- llm = { modelName: 'fastGoodCheap', requestTimeout: 120_000 }, // Extended timeout for complex operations
186
- useSchemaValidation = false, // Disabled by default due to OpenAI API limitations with patternProperties
187
- } = options;
188
-
189
- // Note: Schema validation is disabled by default because OpenAI's structured output
190
- // doesn't handle patternProperties well. The function returns a dynamic object
191
- // where keys are combination strings (e.g., "art + science") and values are
192
- // intersection objects with combination, description, and elements properties.
193
-
194
- // Step 1: Generate and shuffle combinations
195
- const allCombinations = rangeCombinations(items, minSize, maxSize);
196
- const combinations = shuffle(allCombinations);
197
-
198
- if (combinations.length === 0) {
199
- return {};
200
- }
201
-
202
- // Step 2: Find first 3 high-quality intersections in parallel
203
- const topExamples = [];
204
-
205
- for (let i = 0; i < combinations.length && topExamples.length < 3; i += batchSize) {
206
- const batch = combinations.slice(i, i + batchSize);
207
- const results = await Promise.all(batch.map((combo) => processCombo(combo, instructions, llm)));
208
-
209
- for (const result of results) {
210
- if (result.score > goodnessScore && topExamples.length < 3) {
211
- topExamples.push({
212
- key: result.comboKey,
213
- intersection: {
214
- combination: result.combo,
215
- description: result.description,
216
- elements: result.elementList,
217
- },
218
- });
219
- }
220
- }
221
- }
222
-
223
- // If no good examples found, lower the threshold and try again
224
- if (topExamples.length === 0) {
225
- const lowerThreshold = Math.max(1, goodnessScore - 3);
226
- for (let i = 0; i < Math.min(combinations.length, 10) && topExamples.length < 3; i++) {
227
- const result = await processCombo(combinations[i], instructions, llm);
228
- if (result.score > lowerThreshold) {
229
- topExamples.push({
230
- key: result.comboKey,
231
- intersection: {
232
- combination: result.combo,
233
- description: result.description,
234
- elements: result.elementList,
235
- },
236
- });
237
- }
238
- }
239
- }
240
-
241
- // If still no examples, return empty object
242
- if (topExamples.length === 0) {
243
- return {};
244
- }
245
-
246
- // Step 3: Use top examples to generate all intersections in parallel
247
- const examplePrompts = topExamples
248
- .map(
249
- ({ key, intersection }) =>
250
- `${key}: ${intersection.description}\nElements: ${intersection.elements
251
- .slice(0, 5)
252
- .join(', ')}`
253
- )
254
- .join('\n\n');
255
-
256
- const remainingCombinations = allCombinations.filter((combo) => {
257
- const comboKey = combo.join(' + ');
258
- return !topExamples.find((ex) => ex.key === comboKey); // Skip already processed examples
259
- });
260
-
261
- // Process remaining combinations in parallel
262
- const remainingResults = await Promise.all(
263
- remainingCombinations.map((combo) =>
264
- processRemainingCombo(combo, instructions, examplePrompts, llm)
265
- )
266
- );
267
-
268
- // Combine all results
269
- const finalIntersections = {};
270
-
271
- // Add top examples
272
- for (const example of topExamples) {
273
- finalIntersections[example.key] = example.intersection;
274
- }
275
-
276
- // Add remaining results
277
- for (const result of remainingResults) {
278
- finalIntersections[result.key] = result.intersection;
279
- }
280
-
281
- // Step 4: Validate results with JSON schema if enabled
282
- if (useSchemaValidation && Object.keys(finalIntersections).length > 0) {
283
- const validated = await validateIntersectionResults(finalIntersections, llm);
284
- // Extract the intersections from the validated structure and return flat
285
- return validated.intersections || finalIntersections;
286
- }
287
-
288
- return finalIntersections;
289
- }
290
-
291
- /**
292
- * Create model options with JSON schema validation
293
- * @param {string|Object} llm - LLM model to use
294
- * @param {string} schemaName - Name for the JSON schema
295
- * @returns {Promise<Object>} Model options with schema validation
296
- */
297
- async function createModelOptions(llm = 'fastGoodCheap', schemaName = 'intersection_result') {
298
- const schema = await getIntersectionSchema();
299
-
300
- const responseFormat = {
301
- type: 'json_schema',
302
- json_schema: {
303
- name: schemaName,
304
- schema,
305
- },
306
- };
307
-
308
- if (typeof llm === 'string') {
309
- return {
310
- modelName: llm,
311
- response_format: responseFormat,
312
- };
313
- } else {
314
- return {
315
- ...llm,
316
- response_format: responseFormat,
317
- };
318
- }
319
- }
320
-
321
- /**
322
- * Validate and structure final results using JSON schema
323
- * @param {Object} intersections - Raw intersection results
324
- * @param {string|Object} llm - LLM model to use
325
- * @returns {Promise<Object>} Schema-validated intersection results
326
- */
327
- async function validateIntersectionResults(intersections, llm = 'fastGoodCheap') {
328
- if (!intersections || Object.keys(intersections).length === 0) {
329
- return { intersections: {} };
330
- }
331
-
332
- const prompt = `Validate and structure these intersection results according to the required schema:
333
-
334
- ${JSON.stringify(intersections, null, 2)}
335
-
336
- Ensure each intersection has:
337
- - combination: array of category names
338
- - description: clear explanation of the intersection
339
- - elements: array of specific examples that belong to ALL categories
340
-
341
- Return the properly structured JSON object with an "intersections" property containing the results.`;
342
-
343
- try {
344
- const modelOptions = await createModelOptions(llm, 'intersection_result');
345
- const response = await chatGPT(prompt, { modelOptions });
346
- const parsed = typeof response === 'string' ? JSON.parse(response) : response;
347
-
348
- // Extract intersections from the object structure
349
- const resultIntersections = parsed?.intersections || parsed;
350
-
351
- // Validate that the result is an object
352
- if (
353
- typeof resultIntersections !== 'object' ||
354
- resultIntersections === null ||
355
- Array.isArray(resultIntersections)
356
- ) {
357
- console.warn('Schema validation failed: invalid structure, returning original results');
358
- return { intersections };
359
- }
360
-
361
- return { intersections: resultIntersections };
362
- } catch (error) {
363
- console.warn('Schema validation failed, returning original results:', error.message);
364
- return { intersections };
365
- }
366
- }
@@ -1,38 +0,0 @@
1
- {
2
- "$schema": "http://json-schema.org/draft-07/schema#",
3
- "type": "object",
4
- "description": "Intersection results between categories",
5
- "properties": {
6
- "intersections": {
7
- "type": "object",
8
- "description": "Map of intersection results keyed by combination identifier",
9
- "additionalProperties": {
10
- "type": "object",
11
- "properties": {
12
- "combination": {
13
- "type": "array",
14
- "items": {
15
- "type": "string"
16
- },
17
- "description": "Array of category names that form this intersection"
18
- },
19
- "description": {
20
- "type": "string",
21
- "description": "Clear explanation of what this intersection represents"
22
- },
23
- "elements": {
24
- "type": "array",
25
- "items": {
26
- "type": "string"
27
- },
28
- "description": "Specific examples that belong to ALL categories in the combination"
29
- }
30
- },
31
- "required": ["combination", "description", "elements"],
32
- "additionalProperties": false
33
- }
34
- }
35
- },
36
- "required": ["intersections"],
37
- "additionalProperties": false
38
- }