@trazum/core 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/dist/advisories.d.ts +23 -0
  4. package/dist/advisories.d.ts.map +1 -0
  5. package/dist/advisories.js +376 -0
  6. package/dist/advisories.js.map +1 -0
  7. package/dist/aws-sigv4.d.ts +88 -0
  8. package/dist/aws-sigv4.d.ts.map +1 -0
  9. package/dist/aws-sigv4.js +117 -0
  10. package/dist/aws-sigv4.js.map +1 -0
  11. package/dist/baseline.d.ts +171 -0
  12. package/dist/baseline.d.ts.map +1 -0
  13. package/dist/baseline.js +273 -0
  14. package/dist/baseline.js.map +1 -0
  15. package/dist/cache.d.ts +26 -0
  16. package/dist/cache.d.ts.map +1 -0
  17. package/dist/cache.js +28 -0
  18. package/dist/cache.js.map +1 -0
  19. package/dist/changes.d.ts +29 -0
  20. package/dist/changes.d.ts.map +1 -0
  21. package/dist/changes.js +142 -0
  22. package/dist/changes.js.map +1 -0
  23. package/dist/compare.d.ts +65 -0
  24. package/dist/compare.d.ts.map +1 -0
  25. package/dist/compare.js +58 -0
  26. package/dist/compare.js.map +1 -0
  27. package/dist/config-schema.d.ts +118 -0
  28. package/dist/config-schema.d.ts.map +1 -0
  29. package/dist/config-schema.js +315 -0
  30. package/dist/config-schema.js.map +1 -0
  31. package/dist/config.d.ts +47 -0
  32. package/dist/config.d.ts.map +1 -0
  33. package/dist/config.js +112 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/detect.d.ts +70 -0
  36. package/dist/detect.d.ts.map +1 -0
  37. package/dist/detect.js +228 -0
  38. package/dist/detect.js.map +1 -0
  39. package/dist/evaluate.d.ts +98 -0
  40. package/dist/evaluate.d.ts.map +1 -0
  41. package/dist/evaluate.js +110 -0
  42. package/dist/evaluate.js.map +1 -0
  43. package/dist/extract.d.ts +81 -0
  44. package/dist/extract.d.ts.map +1 -0
  45. package/dist/extract.js +280 -0
  46. package/dist/extract.js.map +1 -0
  47. package/dist/gcp-auth.d.ts +58 -0
  48. package/dist/gcp-auth.d.ts.map +1 -0
  49. package/dist/gcp-auth.js +113 -0
  50. package/dist/gcp-auth.js.map +1 -0
  51. package/dist/glob.d.ts +49 -0
  52. package/dist/glob.d.ts.map +1 -0
  53. package/dist/glob.js +154 -0
  54. package/dist/glob.js.map +1 -0
  55. package/dist/host.d.ts +30 -0
  56. package/dist/host.d.ts.map +1 -0
  57. package/dist/host.js +69 -0
  58. package/dist/host.js.map +1 -0
  59. package/dist/i18n/en.d.ts +4 -0
  60. package/dist/i18n/en.d.ts.map +1 -0
  61. package/dist/i18n/en.js +168 -0
  62. package/dist/i18n/en.js.map +1 -0
  63. package/dist/i18n/es.d.ts +4 -0
  64. package/dist/i18n/es.d.ts.map +1 -0
  65. package/dist/i18n/es.js +168 -0
  66. package/dist/i18n/es.js.map +1 -0
  67. package/dist/i18n/index.d.ts +36 -0
  68. package/dist/i18n/index.d.ts.map +1 -0
  69. package/dist/i18n/index.js +50 -0
  70. package/dist/i18n/index.js.map +1 -0
  71. package/dist/i18n/types.d.ts +180 -0
  72. package/dist/i18n/types.d.ts.map +1 -0
  73. package/dist/i18n/types.js +11 -0
  74. package/dist/i18n/types.js.map +1 -0
  75. package/dist/index.d.ts +66 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +69 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/llm.d.ts +226 -0
  80. package/dist/llm.d.ts.map +1 -0
  81. package/dist/llm.js +485 -0
  82. package/dist/llm.js.map +1 -0
  83. package/dist/nearest.d.ts +20 -0
  84. package/dist/nearest.d.ts.map +1 -0
  85. package/dist/nearest.js +54 -0
  86. package/dist/nearest.js.map +1 -0
  87. package/dist/net.d.ts +90 -0
  88. package/dist/net.d.ts.map +1 -0
  89. package/dist/net.js +203 -0
  90. package/dist/net.js.map +1 -0
  91. package/dist/node.d.ts +32 -0
  92. package/dist/node.d.ts.map +1 -0
  93. package/dist/node.js +33 -0
  94. package/dist/node.js.map +1 -0
  95. package/dist/openrouter.d.ts +25 -0
  96. package/dist/openrouter.d.ts.map +1 -0
  97. package/dist/openrouter.js +72 -0
  98. package/dist/openrouter.js.map +1 -0
  99. package/dist/optimize.d.ts +38 -0
  100. package/dist/optimize.d.ts.map +1 -0
  101. package/dist/optimize.js +183 -0
  102. package/dist/optimize.js.map +1 -0
  103. package/dist/otlp.d.ts +91 -0
  104. package/dist/otlp.d.ts.map +1 -0
  105. package/dist/otlp.js +102 -0
  106. package/dist/otlp.js.map +1 -0
  107. package/dist/phrases.d.ts +169 -0
  108. package/dist/phrases.d.ts.map +1 -0
  109. package/dist/phrases.js +939 -0
  110. package/dist/phrases.js.map +1 -0
  111. package/dist/pricing-overlay.d.ts +55 -0
  112. package/dist/pricing-overlay.d.ts.map +1 -0
  113. package/dist/pricing-overlay.js +241 -0
  114. package/dist/pricing-overlay.js.map +1 -0
  115. package/dist/pricing.d.ts +115 -0
  116. package/dist/pricing.d.ts.map +1 -0
  117. package/dist/pricing.js +400 -0
  118. package/dist/pricing.js.map +1 -0
  119. package/dist/profile.d.ts +71 -0
  120. package/dist/profile.d.ts.map +1 -0
  121. package/dist/profile.js +55 -0
  122. package/dist/profile.js.map +1 -0
  123. package/dist/promptfoo.d.ts +58 -0
  124. package/dist/promptfoo.d.ts.map +1 -0
  125. package/dist/promptfoo.js +149 -0
  126. package/dist/promptfoo.js.map +1 -0
  127. package/dist/prune.d.ts +91 -0
  128. package/dist/prune.d.ts.map +1 -0
  129. package/dist/prune.js +110 -0
  130. package/dist/prune.js.map +1 -0
  131. package/dist/reorder.d.ts +82 -0
  132. package/dist/reorder.d.ts.map +1 -0
  133. package/dist/reorder.js +215 -0
  134. package/dist/reorder.js.map +1 -0
  135. package/dist/review.d.ts +54 -0
  136. package/dist/review.d.ts.map +1 -0
  137. package/dist/review.js +131 -0
  138. package/dist/review.js.map +1 -0
  139. package/dist/rules.d.ts +5 -0
  140. package/dist/rules.d.ts.map +1 -0
  141. package/dist/rules.js +279 -0
  142. package/dist/rules.js.map +1 -0
  143. package/dist/savings.d.ts +36 -0
  144. package/dist/savings.d.ts.map +1 -0
  145. package/dist/savings.js +83 -0
  146. package/dist/savings.js.map +1 -0
  147. package/dist/segment.d.ts +8 -0
  148. package/dist/segment.d.ts.map +1 -0
  149. package/dist/segment.js +74 -0
  150. package/dist/segment.js.map +1 -0
  151. package/dist/shared-prefix.d.ts +63 -0
  152. package/dist/shared-prefix.d.ts.map +1 -0
  153. package/dist/shared-prefix.js +151 -0
  154. package/dist/shared-prefix.js.map +1 -0
  155. package/dist/similarity.d.ts +13 -0
  156. package/dist/similarity.d.ts.map +1 -0
  157. package/dist/similarity.js +30 -0
  158. package/dist/similarity.js.map +1 -0
  159. package/dist/structure.d.ts +144 -0
  160. package/dist/structure.d.ts.map +1 -0
  161. package/dist/structure.js +455 -0
  162. package/dist/structure.js.map +1 -0
  163. package/dist/suggest.d.ts +100 -0
  164. package/dist/suggest.d.ts.map +1 -0
  165. package/dist/suggest.js +151 -0
  166. package/dist/suggest.js.map +1 -0
  167. package/dist/tokenizer.d.ts +57 -0
  168. package/dist/tokenizer.d.ts.map +1 -0
  169. package/dist/tokenizer.js +157 -0
  170. package/dist/tokenizer.js.map +1 -0
  171. package/dist/types.d.ts +296 -0
  172. package/dist/types.d.ts.map +1 -0
  173. package/dist/types.js +3 -0
  174. package/dist/types.js.map +1 -0
  175. package/dist/walk.d.ts +40 -0
  176. package/dist/walk.d.ts.map +1 -0
  177. package/dist/walk.js +95 -0
  178. package/dist/walk.js.map +1 -0
  179. package/package.json +56 -0
  180. package/src/advisories.ts +431 -0
  181. package/src/aws-sigv4.ts +174 -0
  182. package/src/baseline.ts +390 -0
  183. package/src/cache.ts +54 -0
  184. package/src/changes.ts +158 -0
  185. package/src/compare.ts +131 -0
  186. package/src/config-schema.ts +451 -0
  187. package/src/config.ts +161 -0
  188. package/src/detect.ts +312 -0
  189. package/src/evaluate.ts +188 -0
  190. package/src/extract.ts +336 -0
  191. package/src/gcp-auth.ts +166 -0
  192. package/src/glob.ts +160 -0
  193. package/src/host.ts +90 -0
  194. package/src/i18n/en.ts +236 -0
  195. package/src/i18n/es.ts +236 -0
  196. package/src/i18n/index.ts +68 -0
  197. package/src/i18n/types.ts +230 -0
  198. package/src/index.ts +228 -0
  199. package/src/llm.ts +708 -0
  200. package/src/nearest.ts +61 -0
  201. package/src/net.ts +233 -0
  202. package/src/node.ts +63 -0
  203. package/src/openrouter.ts +125 -0
  204. package/src/optimize.ts +228 -0
  205. package/src/otlp.ts +179 -0
  206. package/src/phrases.ts +1047 -0
  207. package/src/pricing-overlay.ts +319 -0
  208. package/src/pricing.ts +468 -0
  209. package/src/profile.ts +124 -0
  210. package/src/promptfoo.ts +213 -0
  211. package/src/prune.ts +211 -0
  212. package/src/reorder.ts +307 -0
  213. package/src/review.ts +180 -0
  214. package/src/rules.ts +324 -0
  215. package/src/savings.ts +121 -0
  216. package/src/segment.ts +106 -0
  217. package/src/shared-prefix.ts +198 -0
  218. package/src/similarity.ts +28 -0
  219. package/src/structure.ts +652 -0
  220. package/src/suggest.ts +254 -0
  221. package/src/tokenizer.ts +190 -0
  222. package/src/types.ts +323 -0
  223. package/src/walk.ts +117 -0
package/src/phrases.ts ADDED
@@ -0,0 +1,1047 @@
1
+ /**
2
+ * Phrase dictionaries.
3
+ *
4
+ * These lists are DATA, not user interface: they are the vocabulary Trazum
5
+ * looks for inside the prompts it optimises. Real prompts mix languages, so
6
+ * the dictionaries deliberately cover English and Spanish at the same time and
7
+ * a single prompt can trigger entries from both.
8
+ *
9
+ * Adding a language here means adding entries to these lists — it is unrelated
10
+ * to the report language, which lives in `src/i18n/`.
11
+ *
12
+ * Every entry earns its place by preserving meaning. Anything whose removal
13
+ * could change what the prompt asks for belongs in the `aggressive` level, or
14
+ * does not belong here at all.
15
+ */
16
+
17
+ /**
18
+ * Languages the trimming dictionaries below actually cover.
19
+ *
20
+ * Exported because the report has to be able to say so. Until this existed,
21
+ * a French or German prompt came back with `No rule found anything to trim` —
22
+ * which reads as "your prompt is already efficient" and meant "I do not speak
23
+ * your language". Same class of mistake as the one `--reorder` had: the tool
24
+ * knew something it was not telling the reader.
25
+ *
26
+ * `phrases.test.js` asserts this list matches the languages the dictionaries are
27
+ * actually grouped under, so adding a dictionary without listing it — or listing
28
+ * one without the entries — fails rather than misleads.
29
+ */
30
+ export const PHRASE_LANGUAGES: readonly string[] = ['en', 'es', 'fr', 'de', 'pt', 'it', 'nl'];
31
+
32
+ /** Long phrases and their shorter equivalent. */
33
+ export const VERBOSE_PHRASES: ReadonlyArray<readonly [string, string]> = [
34
+ // English
35
+ ['in order to', 'to'],
36
+ ['for the purpose of', 'to'],
37
+ ['due to the fact that', 'because'],
38
+ ['owing to the fact that', 'because'],
39
+ ['in view of the fact that', 'because'],
40
+ ['in the event that', 'if'],
41
+ ['at this point in time', 'now'],
42
+ ['at the present time', 'now'],
43
+ ['in the near future', 'soon'],
44
+ ['a large number of', 'many'],
45
+ ['a great deal of', 'much'],
46
+ ['the majority of', 'most'],
47
+ ['make use of', 'use'],
48
+ ['utilize', 'use'],
49
+ ['in spite of the fact that', 'although'],
50
+ ['despite the fact that', 'although'],
51
+ ['with regard to', 'about'],
52
+ ['with respect to', 'about'],
53
+ ['in relation to', 'about'],
54
+ ['each and every', 'every'],
55
+ ['is able to', 'can'],
56
+ ['are able to', 'can'],
57
+ ['has the ability to', 'can'],
58
+ ['have the ability to', 'can'],
59
+ ['prior to', 'before'],
60
+ ['subsequent to', 'after'],
61
+ ['in a timely manner', 'promptly'],
62
+ ['at all times', 'always'],
63
+ ['on a regular basis', 'regularly'],
64
+ ['a sufficient number of', 'enough'],
65
+ ['in the process of', ''],
66
+
67
+ // Spanish
68
+ ['con el fin de', 'para'],
69
+ ['con el objetivo de', 'para'],
70
+ ['con el propósito de', 'para'],
71
+ ['a fin de', 'para'],
72
+ ['debido al hecho de que', 'porque'],
73
+ ['dado el hecho de que', 'porque'],
74
+ ['por el hecho de que', 'porque'],
75
+ ['en el caso de que', 'si'],
76
+ ['en caso de que', 'si'],
77
+ ['en el momento en que', 'cuando'],
78
+ ['una gran cantidad de', 'muchos'],
79
+ ['un gran número de', 'muchos'],
80
+ ['la mayor parte de', 'la mayoría de'],
81
+ ['hacer uso de', 'usar'],
82
+ ['llevar a cabo', 'realizar'],
83
+ ['en relación con', 'sobre'],
84
+ ['con respecto a', 'sobre'],
85
+ ['a pesar de que', 'aunque'],
86
+ ['así como también', 'y'],
87
+ ['todos y cada uno de los', 'todos los'],
88
+ ['cada uno de los', 'cada'],
89
+ ['en este momento', 'ahora'],
90
+ ['en la actualidad', 'ahora'],
91
+ ['hoy en día', 'hoy'],
92
+ ['de forma regular', 'regularmente'],
93
+ ['en todo momento', 'siempre'],
94
+ ['tiene la capacidad de', 'puede'],
95
+ ['es capaz de', 'puede'],
96
+ ['con anterioridad a', 'antes de'],
97
+ ['con posterioridad a', 'después de'],
98
+ ['un número suficiente de', 'suficientes'],
99
+
100
+ // French
101
+ ['afin de', 'pour'],
102
+ ['dans le but de', 'pour'],
103
+ ['en vue de', 'pour'],
104
+ ['du fait que', 'car'],
105
+ ['étant donné que', 'car'],
106
+ ['dans le cas où', 'si'],
107
+ ["à l'heure actuelle", 'maintenant'],
108
+ ['dans un avenir proche', 'bientôt'],
109
+ ['un grand nombre de', 'beaucoup de'],
110
+ ['la majorité des', 'la plupart des'],
111
+ ['faire usage de', 'utiliser'],
112
+ ['en ce qui concerne', 'sur'],
113
+ ['par rapport à', 'sur'],
114
+ ['est en mesure de', 'peut'],
115
+ ['il est nécessaire de', 'il faut'],
116
+
117
+ // German
118
+ ['um zu können', 'um zu'],
119
+ ['zu dem Zweck', 'um'],
120
+ ['aufgrund der Tatsache, dass', 'weil'],
121
+ ['aus dem Grund, dass', 'weil'],
122
+ ['in dem Fall, dass', 'wenn'],
123
+ ['zum jetzigen Zeitpunkt', 'jetzt'],
124
+ ['in naher Zukunft', 'bald'],
125
+ ['eine große Anzahl von', 'viele'],
126
+ ['die Mehrheit der', 'die meisten'],
127
+ ['Gebrauch machen von', 'nutzen'],
128
+ ['in Bezug auf', 'zu'],
129
+ ['ist in der Lage zu', 'kann'],
130
+ ['es ist notwendig, dass', 'es muss'],
131
+
132
+ // Portuguese
133
+ ['a fim de', 'para'],
134
+ ['com o objetivo de', 'para'],
135
+ ['devido ao fato de que', 'porque'],
136
+ ['no caso de que', 'se'],
137
+ ['neste momento', 'agora'],
138
+ ['num futuro próximo', 'em breve'],
139
+ ['um grande número de', 'muitos'],
140
+ ['a maioria dos', 'a maior parte dos'],
141
+ ['fazer uso de', 'usar'],
142
+ ['no que se refere a', 'sobre'],
143
+ ['em relação a', 'sobre'],
144
+ ['é capaz de', 'pode'],
145
+ ['é necessário que', 'deve'],
146
+
147
+ // Italian
148
+ ['al fine di', 'per'],
149
+ ['con lo scopo di', 'per'],
150
+ ['a causa del fatto che', 'perché'],
151
+ ['nel caso in cui', 'se'],
152
+ ['in questo momento', 'ora'],
153
+ ['nel prossimo futuro', 'presto'],
154
+ ['un gran numero di', 'molti'],
155
+ ['la maggior parte dei', 'la maggioranza dei'],
156
+ ['fare uso di', 'usare'],
157
+ ['per quanto riguarda', 'su'],
158
+ ['in relazione a', 'su'],
159
+ ['è in grado di', 'può'],
160
+ ['è necessario che', 'deve'],
161
+
162
+ // Dutch
163
+ ['met het doel om', 'om'],
164
+ ['vanwege het feit dat', 'omdat'],
165
+ ['in het geval dat', 'als'],
166
+ ['op dit moment', 'nu'],
167
+ ['in de nabije toekomst', 'binnenkort'],
168
+ ['een groot aantal', 'veel'],
169
+ ['de meerderheid van', 'de meeste'],
170
+ ['gebruik maken van', 'gebruiken'],
171
+ ['met betrekking tot', 'over'],
172
+ ['is in staat om', 'kan'],
173
+ ];
174
+
175
+ /** Courtesy: adds nothing for the model and costs tokens on every call. */
176
+ export const POLITENESS: readonly string[] = [
177
+ // English
178
+ 'please',
179
+ 'thanks in advance',
180
+ 'thank you very much',
181
+ 'thank you',
182
+ 'thanks',
183
+ 'kindly',
184
+ "if you don't mind",
185
+ 'if you would be so kind',
186
+ 'much appreciated',
187
+
188
+ // Spanish
189
+ 'por favor',
190
+ 'muchas gracias',
191
+ 'gracias de antemano',
192
+ 'te lo agradezco',
193
+ 'te agradecería',
194
+ 'si eres tan amable',
195
+ 'si no te importa',
196
+ 'gracias',
197
+
198
+ // French
199
+ "s'il vous plaît",
200
+ "s'il te plaît",
201
+ 'merci beaucoup',
202
+ 'merci d\'avance',
203
+ 'merci',
204
+ 'je vous remercie',
205
+ 'si cela ne vous dérange pas',
206
+
207
+ // German
208
+ 'bitte',
209
+ 'vielen Dank',
210
+ 'danke im Voraus',
211
+ 'danke',
212
+ 'ich danke Ihnen',
213
+ 'wenn es Ihnen nichts ausmacht',
214
+
215
+ // Portuguese
216
+ 'por gentileza',
217
+ 'muito obrigado',
218
+ 'muito obrigada',
219
+ 'obrigado',
220
+ 'obrigada',
221
+ 'agradeço desde já',
222
+
223
+ // Italian
224
+ 'per favore',
225
+ 'per cortesia',
226
+ 'grazie mille',
227
+ 'grazie in anticipo',
228
+ 'grazie',
229
+ 'ti ringrazio',
230
+
231
+ // Dutch
232
+ 'alsjeblieft',
233
+ 'alstublieft',
234
+ 'hartelijk dank',
235
+ 'bedankt',
236
+ 'dank je',
237
+ 'dank u',
238
+ ];
239
+
240
+ /** Filler and throat-clearing with no content. */
241
+ export const FILLER: readonly string[] = [
242
+ // English
243
+ 'basically',
244
+ 'essentially',
245
+ 'as you know',
246
+ 'as you may know',
247
+ 'it is important to note that',
248
+ "it's important to note that",
249
+ 'it is worth noting that',
250
+ "it's worth noting that",
251
+ 'needless to say',
252
+ 'in essence',
253
+ 'at the end of the day',
254
+ 'as a matter of fact',
255
+
256
+ // Spanish
257
+ 'básicamente',
258
+ 'basicamente',
259
+ 'en realidad',
260
+ 'como ya sabes',
261
+ 'como sabrás',
262
+ 'cabe destacar que',
263
+ 'cabe mencionar que',
264
+ 'es importante destacar que',
265
+ 'es importante mencionar que',
266
+ 'vale la pena mencionar que',
267
+ 'por así decirlo',
268
+ 'dicho esto',
269
+
270
+ // French
271
+ 'il est important de noter que',
272
+ 'il convient de noter que',
273
+ 'il faut noter que',
274
+ 'comme vous le savez',
275
+ 'essentiellement',
276
+ 'fondamentalement',
277
+ 'en fait',
278
+
279
+ // German
280
+ 'es ist wichtig zu beachten, dass',
281
+ 'es sei darauf hingewiesen, dass',
282
+ 'wie Sie wissen',
283
+ 'im Grunde',
284
+ 'im Wesentlichen',
285
+ 'eigentlich',
286
+
287
+ // Portuguese
288
+ 'é importante notar que',
289
+ 'vale a pena notar que',
290
+ 'como você sabe',
291
+ 'basicamente',
292
+ 'essencialmente',
293
+
294
+ // Italian
295
+ 'è importante notare che',
296
+ 'vale la pena notare che',
297
+ 'come sai',
298
+ 'fondamentalmente',
299
+ 'essenzialmente',
300
+
301
+ // Dutch
302
+ 'het is belangrijk om op te merken dat',
303
+ 'zoals je weet',
304
+ 'eigenlijk',
305
+ 'in principe',
306
+ ];
307
+
308
+ /** Intensifiers: they rarely change the task and almost always add tokens. */
309
+ /**
310
+ * Words that add emphasis and nothing else, so dropping one cannot change what
311
+ * the prompt asks for.
312
+ *
313
+ * **A quantifier is not an intensifier**, and the distinction is not visible in
314
+ * a dictionary translated word by word. Spanish gets `muy` and deliberately not
315
+ * `mucho`; the first draft of the other five languages did not keep that line
316
+ * and shipped `muito`, `molto` and `heel`, each of which does both jobs:
317
+ *
318
+ * Hai molto tempo per rispondere. → Hai tempo per rispondere.
319
+ *
320
+ * "You have much time" became "you have time". Caught by running the five
321
+ * languages through the rules rather than by reading the list, and pinned by a
322
+ * test that keeps those three words out.
323
+ */
324
+ export const INTENSIFIERS: readonly string[] = [
325
+ // English
326
+ 'very',
327
+ 'really',
328
+ 'extremely',
329
+ 'incredibly',
330
+ 'absolutely',
331
+ 'totally',
332
+ 'quite',
333
+
334
+ // Spanish
335
+ 'muy',
336
+ 'realmente',
337
+ 'sumamente',
338
+ 'extremadamente',
339
+ 'increíblemente',
340
+ 'absolutamente',
341
+ 'totalmente',
342
+
343
+ // French
344
+ 'très',
345
+ 'vraiment',
346
+ 'extrêmement',
347
+ 'absolument',
348
+ 'tout à fait',
349
+
350
+ // German
351
+ 'sehr',
352
+ 'wirklich',
353
+ 'äußerst',
354
+ 'absolut',
355
+ 'völlig',
356
+
357
+ // Portuguese
358
+ 'realmente',
359
+ 'extremamente',
360
+ 'absolutamente',
361
+ 'totalmente',
362
+
363
+ // Italian
364
+ 'davvero',
365
+ 'estremamente',
366
+ 'assolutamente',
367
+ 'totalmente',
368
+
369
+ // Dutch
370
+ 'zeer',
371
+ 'echt',
372
+ 'uiterst',
373
+ 'absoluut',
374
+ ];
375
+
376
+ /** Hedges that weaken the instruction without adding information. */
377
+ export const HEDGES: readonly string[] = [
378
+ // English
379
+ 'i think',
380
+ 'i believe',
381
+ 'it seems that',
382
+ 'in my opinion',
383
+ 'arguably',
384
+
385
+ // Spanish
386
+ 'creo que',
387
+ 'me parece que',
388
+ 'diría que',
389
+ 'en mi opinión',
390
+
391
+ // French
392
+ 'peut-être',
393
+ 'probablement',
394
+ 'en quelque sorte',
395
+ 'je pense que',
396
+
397
+ // German
398
+ 'vielleicht',
399
+ 'wahrscheinlich',
400
+ 'irgendwie',
401
+ 'ich denke, dass',
402
+
403
+ // Portuguese
404
+ 'talvez',
405
+ 'provavelmente',
406
+ 'de certa forma',
407
+ 'eu acho que',
408
+
409
+ // Italian
410
+ 'forse',
411
+ 'probabilmente',
412
+ 'in un certo senso',
413
+ 'penso che',
414
+
415
+ // Dutch
416
+ 'misschien',
417
+ 'waarschijnlijk',
418
+ 'ik denk dat',
419
+ ];
420
+
421
+ /**
422
+ * Self-verification instructions.
423
+ *
424
+ * On current models these trigger over-verification: the model already checks
425
+ * its own work, and asking explicitly fires extra steps paid for in output
426
+ * tokens. Aggressive level because some workflows do want explicit
427
+ * verification.
428
+ *
429
+ * The full forms come first on purpose: removing only the core phrase would
430
+ * leave a dangling fragment such as "Before answering." — worse than leaving
431
+ * the sentence alone.
432
+ *
433
+ * That includes whatever introduces the instruction. "You MUST double-check
434
+ * your answer before responding" reduced to "You must." for exactly this
435
+ * reason: the phrase matched, the subject and modal in front of it did not,
436
+ * and what survived was a sentence that says nothing. Anything that can open
437
+ * one of these instructions belongs in the list, ahead of the bare form.
438
+ */
439
+ export const SELF_CHECK: readonly string[] = [
440
+ // English — openers first, so the whole instruction goes rather than
441
+ // leaving the subject and modal stranded in front of the removal.
442
+ 'you must double-check your answer before responding',
443
+ 'you must double check your answer before responding',
444
+ 'you should double-check your answer before responding',
445
+ 'you must verify your answer before responding',
446
+ 'you should verify your answer before responding',
447
+ 'always double-check your answer before responding',
448
+ 'always verify your answer before responding',
449
+ 'be sure to double-check your answer before responding',
450
+ 'make sure to double-check your answer before responding',
451
+ 'make sure to verify your answer before responding',
452
+ 'you must double-check your answer',
453
+ 'you should double-check your answer',
454
+ 'you must verify your answer',
455
+ 'you should verify your answer',
456
+ 'always double-check your work',
457
+ 'be sure to double-check your work',
458
+ 'make sure to check your work',
459
+
460
+ // English — bare forms
461
+ 'double-check your answer before responding',
462
+ 'double check your answer before responding',
463
+ 'double-check your work before responding',
464
+ 'verify your answer before responding',
465
+ 'double-check your answer',
466
+ 'double check your answer',
467
+ 'double-check your work',
468
+ 'double check your work',
469
+ 'verify your answer',
470
+ 're-verify before responding',
471
+ 'check your work twice',
472
+
473
+ // Spanish — openers first, same reason as above
474
+ 'debes verificar tu respuesta antes de contestar',
475
+ 'debes verificar tu respuesta antes de responder',
476
+ 'debes revisar tu respuesta antes de contestar',
477
+ 'siempre verifica tu respuesta antes de contestar',
478
+ 'asegúrate de verificar tu respuesta antes de contestar',
479
+ 'asegúrate de revisar tu respuesta',
480
+ 'debes verificar tu respuesta',
481
+ 'debes revisar tu respuesta',
482
+
483
+ // Spanish — bare forms
484
+ 'verifica tu respuesta antes de contestar',
485
+ 'verifica tu respuesta antes de responder',
486
+ 'revisa tu respuesta antes de contestar',
487
+ 'revisa tu respuesta antes de responder',
488
+ 'comprueba tu trabajo antes de responder',
489
+ 'verifica tu respuesta',
490
+ 'revisa tu respuesta',
491
+ 'comprueba tu trabajo',
492
+ 'revisa dos veces',
493
+ 'vuelve a comprobarlo',
494
+ 'asegúrate de revisar tu trabajo',
495
+
496
+ // French
497
+ 'vérifiez votre réponse avant de répondre',
498
+ 'relisez votre réponse',
499
+ 'assurez-vous que votre réponse est correcte',
500
+ 'réfléchissez étape par étape avant de répondre',
501
+
502
+ // German
503
+ 'überprüfen Sie Ihre Antwort, bevor Sie antworten',
504
+ 'lesen Sie Ihre Antwort noch einmal',
505
+ 'stellen Sie sicher, dass Ihre Antwort korrekt ist',
506
+ 'denken Sie Schritt für Schritt nach',
507
+
508
+ // Portuguese
509
+ 'verifique sua resposta antes de responder',
510
+ 'releia sua resposta',
511
+ 'certifique-se de que sua resposta está correta',
512
+ 'pense passo a passo antes de responder',
513
+
514
+ // Italian
515
+ 'verifica la tua risposta prima di rispondere',
516
+ 'rileggi la tua risposta',
517
+ 'assicurati che la tua risposta sia corretta',
518
+ 'pensa passo per passo prima di rispondere',
519
+
520
+ // Dutch
521
+ 'controleer je antwoord voordat je antwoordt',
522
+ 'lees je antwoord nog eens',
523
+ 'denk stap voor stap na',
524
+ ];
525
+
526
+ /** Words shouted in capitals that cost fewer tokens in lowercase. */
527
+ export const SHOUTED_WORDS: readonly string[] = [
528
+ // English
529
+ 'MUST',
530
+ 'NEVER',
531
+ 'ALWAYS',
532
+ 'CRITICAL',
533
+ 'IMPORTANT',
534
+ 'REQUIRED',
535
+ 'MANDATORY',
536
+ 'SHOULD',
537
+ 'DO NOT',
538
+
539
+ // Spanish
540
+ 'DEBES',
541
+ 'NUNCA',
542
+ 'SIEMPRE',
543
+ 'OBLIGATORIO',
544
+ 'IMPRESCINDIBLE',
545
+ 'IMPORTANTE',
546
+ 'CRÍTICO',
547
+ ];
548
+
549
+ /** Emphasis prefixes at the start of a line. */
550
+ export const EMPHASIS_PREFIXES: readonly string[] = [
551
+ // English
552
+ 'CRITICAL',
553
+ 'IMPORTANT',
554
+ 'WARNING',
555
+ 'NOTE',
556
+
557
+ // Spanish
558
+ 'ATENCIÓN',
559
+ 'ATENCION',
560
+ 'IMPORTANTE',
561
+ 'CRÍTICO',
562
+ 'CRITICO',
563
+ 'AVISO',
564
+ 'NOTA',
565
+ ];
566
+
567
+ /**
568
+ * Vocabulary suggesting the task needs a more capable model.
569
+ * Multilingual for the same reason as the dictionaries above.
570
+ */
571
+ export const COMPLEX_SIGNALS: readonly string[] = [
572
+ // English
573
+ 'analyze',
574
+ 'reason',
575
+ 'prove',
576
+ 'design',
577
+ 'architecture',
578
+ 'refactor',
579
+ 'debug',
580
+ 'agent',
581
+ 'tool use',
582
+ 'multi-step',
583
+ 'step by step',
584
+ 'strategy',
585
+ 'investigate',
586
+ 'audit',
587
+ 'migrate',
588
+
589
+ // Spanish
590
+ 'analiza',
591
+ 'razona',
592
+ 'demuestra',
593
+ 'diseña',
594
+ 'arquitectura',
595
+ 'refactoriza',
596
+ 'depura',
597
+ 'optimiza',
598
+ 'estrategia',
599
+ 'agente',
600
+ 'herramienta',
601
+ 'paso a paso',
602
+ 'investiga',
603
+ 'audita',
604
+ 'migra',
605
+ ];
606
+
607
+ /** Vocabulary suggesting a cheaper model would do. */
608
+ export const SIMPLE_SIGNALS: readonly string[] = [
609
+ // English
610
+ 'classify',
611
+ 'translate',
612
+ 'extract',
613
+ 'summarize',
614
+ 'label',
615
+ 'sentiment',
616
+ 'format as',
617
+ 'yes or no',
618
+ 'tag the',
619
+
620
+ // Spanish
621
+ 'clasifica',
622
+ 'traduce',
623
+ 'extrae',
624
+ 'resume',
625
+ 'etiqueta',
626
+ 'sentimiento',
627
+ 'formatea',
628
+ 'corrige la ortografía',
629
+ 'sí o no',
630
+ ];
631
+
632
+ /**
633
+ * Phrases that refer *backwards* to something earlier in the prompt, by language.
634
+ *
635
+ * These are what makes reordering unsafe. "Summarise the text above" is correct
636
+ * where it sits and nonsense if moved in front of the text it points at, so a
637
+ * block containing one of these must stay where it is — and so must everything
638
+ * after it, because moving a later block past a pinned one changes their order
639
+ * relative to each other.
640
+ *
641
+ * Deliberately generous. A false positive costs a saving that was available; a
642
+ * false negative silently changes what the prompt asks for, which is the one
643
+ * thing this project will not trade for tokens.
644
+ *
645
+ * **Grouped by language rather than kept as one flat list, because the flat list
646
+ * hid a hole.** It held English and Spanish and was applied to every prompt, so
647
+ * a French, German or Japanese author ran `--reorder` with no protection at all:
648
+ * every refusal this module is built on silently did not apply to them, and
649
+ * "Résumez le texte ci-dessus" was hoisted above the text and reported as a
650
+ * saving. A list per language makes the coverage a thing you can look at, and
651
+ * lets `UNCOVERED_SCRIPTS` below refuse what is still missing rather than
652
+ * pretending.
653
+ *
654
+ * Every language is matched against every prompt. Language detection would be
655
+ * one more thing to get wrong, and the cost of checking a French prompt for
656
+ * German phrases is a missed saving, which is the direction this module errs in
657
+ * anyway.
658
+ */
659
+ export interface BackwardReferenceSet {
660
+ /**
661
+ * Whether a match must sit on a word boundary.
662
+ *
663
+ * True for anything written with spaces, so "aboveboard" does not pin a block.
664
+ * False for Japanese and Chinese, which have no word boundaries at all — the
665
+ * boundary test asks whether the neighbouring character is a letter, and in
666
+ * 上記のテキスト it always is, so a boundary-matched CJK phrase would never
667
+ * fire. A list that cannot match is worse than no list: it reads like cover.
668
+ */
669
+ wordBoundaries: boolean;
670
+ phrases: readonly string[];
671
+ }
672
+
673
+ export const BACKWARD_REFERENCES_BY_LANGUAGE: Readonly<Record<string, BackwardReferenceSet>> = {
674
+ en: {
675
+ wordBoundaries: true,
676
+ phrases: [
677
+ 'above',
678
+ 'below',
679
+ 'the following',
680
+ 'as follows',
681
+ 'previous',
682
+ 'previously',
683
+ 'earlier',
684
+ 'aforementioned',
685
+ 'that said',
686
+ 'given this',
687
+ 'given the above',
688
+ 'based on this',
689
+ 'based on the above',
690
+ 'from the text',
691
+ 'in the text',
692
+ 'the text provided',
693
+ 'the input above',
694
+ 'the message above',
695
+ 'this input',
696
+ 'this message',
697
+ 'this text',
698
+ 'these examples',
699
+ 'the examples above',
700
+ ],
701
+ },
702
+
703
+ es: {
704
+ wordBoundaries: true,
705
+ phrases: [
706
+ 'arriba',
707
+ 'anterior',
708
+ 'anteriormente',
709
+ 'antes',
710
+ 'a continuación',
711
+ 'lo siguiente',
712
+ 'lo anterior',
713
+ 'mencionado',
714
+ 'dicho esto',
715
+ 'segun lo anterior',
716
+ 'según lo anterior',
717
+ 'del texto',
718
+ 'en el texto',
719
+ 'el texto anterior',
720
+ 'el mensaje anterior',
721
+ 'este texto',
722
+ 'este mensaje',
723
+ 'esta entrada',
724
+ 'estos ejemplos',
725
+ ],
726
+ },
727
+
728
+ fr: {
729
+ wordBoundaries: true,
730
+ phrases: [
731
+ 'ci-dessus',
732
+ 'ci-dessous',
733
+ 'précédent',
734
+ 'précédente',
735
+ 'précédemment',
736
+ 'susmentionné',
737
+ 'mentionné',
738
+ 'plus haut',
739
+ 'suivant',
740
+ 'ce qui suit',
741
+ 'cela dit',
742
+ 'à partir de ce',
743
+ 'du texte',
744
+ 'dans le texte',
745
+ 'le texte ci-dessus',
746
+ 'le message ci-dessus',
747
+ 'ce texte',
748
+ 'ce message',
749
+ 'cette entrée',
750
+ 'ces exemples',
751
+ ],
752
+ },
753
+
754
+ de: {
755
+ wordBoundaries: true,
756
+ phrases: [
757
+ 'oben',
758
+ 'unten',
759
+ 'obige',
760
+ 'obigen',
761
+ 'obenstehend',
762
+ 'vorherige',
763
+ 'vorherigen',
764
+ 'vorher',
765
+ 'zuvor',
766
+ 'genannt',
767
+ 'oben genannten',
768
+ 'folgendes',
769
+ 'wie folgt',
770
+ 'aus dem text',
771
+ 'im text',
772
+ 'der obige text',
773
+ 'diese eingabe',
774
+ 'dieser text',
775
+ 'diese nachricht',
776
+ 'diese beispiele',
777
+ ],
778
+ },
779
+
780
+ pt: {
781
+ wordBoundaries: true,
782
+ phrases: [
783
+ 'acima',
784
+ 'abaixo',
785
+ 'anterior',
786
+ 'anteriormente',
787
+ 'antes',
788
+ 'mencionado',
789
+ 'supracitado',
790
+ 'a seguir',
791
+ 'o seguinte',
792
+ 'dito isto',
793
+ 'do texto',
794
+ 'no texto',
795
+ 'o texto acima',
796
+ 'a mensagem acima',
797
+ 'este texto',
798
+ 'esta mensagem',
799
+ 'esta entrada',
800
+ 'estes exemplos',
801
+ ],
802
+ },
803
+
804
+ it: {
805
+ wordBoundaries: true,
806
+ phrases: [
807
+ 'sopra',
808
+ 'sotto',
809
+ 'precedente',
810
+ 'precedentemente',
811
+ 'in precedenza',
812
+ 'suddetto',
813
+ 'menzionato',
814
+ 'quanto segue',
815
+ 'come segue',
816
+ 'detto questo',
817
+ 'dal testo',
818
+ 'nel testo',
819
+ 'il testo sopra',
820
+ 'il messaggio sopra',
821
+ 'questo testo',
822
+ 'questo messaggio',
823
+ 'questi esempi',
824
+ ],
825
+ },
826
+
827
+ nl: {
828
+ wordBoundaries: true,
829
+ phrases: [
830
+ 'hierboven',
831
+ 'hieronder',
832
+ 'bovenstaande',
833
+ 'vorige',
834
+ 'eerder',
835
+ 'genoemde',
836
+ 'het volgende',
837
+ 'als volgt',
838
+ 'uit de tekst',
839
+ 'in de tekst',
840
+ 'deze tekst',
841
+ 'dit bericht',
842
+ 'deze invoer',
843
+ 'deze voorbeelden',
844
+ ],
845
+ },
846
+
847
+ ja: {
848
+ // No word boundaries: see `wordBoundaries` above.
849
+ wordBoundaries: false,
850
+ phrases: [
851
+ '上記',
852
+ '下記',
853
+ '前述',
854
+ '先ほど',
855
+ '以上の',
856
+ '上のテキスト',
857
+ 'このテキスト',
858
+ 'このメッセージ',
859
+ 'この入力',
860
+ 'これらの例',
861
+ '次のとおり',
862
+ ],
863
+ },
864
+
865
+ zh: {
866
+ wordBoundaries: false,
867
+ phrases: [
868
+ '上述',
869
+ '上面',
870
+ '前面',
871
+ '以上',
872
+ '前述',
873
+ '如下',
874
+ '下面',
875
+ '上文',
876
+ '该文本',
877
+ '这段文本',
878
+ '这条消息',
879
+ '这些示例',
880
+ ],
881
+ },
882
+ };
883
+
884
+ /**
885
+ * Every phrase, flattened. Kept for callers that only need the words.
886
+ */
887
+ export const BACKWARD_REFERENCES: readonly string[] = Object.values(
888
+ BACKWARD_REFERENCES_BY_LANGUAGE,
889
+ ).flatMap((set) => set.phrases);
890
+
891
+ /**
892
+ * Scripts this module has no backward-reference phrases for.
893
+ *
894
+ * The point of naming them is that the alternative is what used to happen:
895
+ * rearranging a Russian or Arabic prompt with nothing to stop it, and calling
896
+ * the result a saving. `reorderForCache` refuses when it sees one of these,
897
+ * which turns a silent hazard into a message somebody can act on — and into a
898
+ * short list of pull requests, since adding a language is adding an array.
899
+ *
900
+ * Matched on the script, not the language: the question is not "which language
901
+ * is this" but "is there any chance my phrase lists apply to it".
902
+ */
903
+ export const UNCOVERED_SCRIPTS: ReadonlyArray<{ name: string; pattern: RegExp }> = [
904
+ { name: 'Cyrillic', pattern: /\p{Script=Cyrillic}/u },
905
+ { name: 'Arabic', pattern: /\p{Script=Arabic}/u },
906
+ { name: 'Hebrew', pattern: /\p{Script=Hebrew}/u },
907
+ { name: 'Hangul', pattern: /\p{Script=Hangul}/u },
908
+ { name: 'Devanagari', pattern: /\p{Script=Devanagari}/u },
909
+ { name: 'Thai', pattern: /\p{Script=Thai}/u },
910
+ { name: 'Greek', pattern: /\p{Script=Greek}/u },
911
+ ];
912
+
913
+ /**
914
+ * Phrases that mark a block as the *output contract* rather than as data.
915
+ *
916
+ * Needed because a fenced JSON block in a prompt is one of two completely
917
+ * different things, and confusing them is the one way this analysis could do
918
+ * harm. `Output format: {...}` describes what the model must return, and a
919
+ * provider that accepts a response schema takes it as a request parameter
920
+ * instead — those tokens leave the prompt on every call. `Input: {...}` inside a
921
+ * few-shot example is *data the prompt needs*, and advising anyone to move it
922
+ * would break their prompt.
923
+ *
924
+ * Nothing here guesses. A schema block with no such phrase before it is left
925
+ * alone, because "probably an output format" is not good enough when being wrong
926
+ * costs somebody a working prompt.
927
+ *
928
+ * Seven languages, the same set the rules cover. A prompt in a language absent
929
+ * here raises nothing, which is a false negative and states itself as one — the
930
+ * alternative is matching an English cue inside Japanese prose and calling the
931
+ * result a saving.
932
+ */
933
+ export const OUTPUT_CUES_BY_LANGUAGE: Readonly<Record<string, readonly string[]>> = {
934
+ en: [
935
+ 'output format',
936
+ 'response format',
937
+ 'output schema',
938
+ 'respond with',
939
+ 'reply with',
940
+ 'answer with',
941
+ 'return only',
942
+ 'return a json',
943
+ 'return json',
944
+ 'must return',
945
+ 'your output',
946
+ 'your response must',
947
+ 'format your',
948
+ 'in this format',
949
+ 'following format',
950
+ 'following shape',
951
+ 'json format',
952
+ ],
953
+ es: [
954
+ 'formato de salida',
955
+ 'formato de respuesta',
956
+ 'esquema de salida',
957
+ 'responde con',
958
+ 'responda con',
959
+ 'contesta con',
960
+ 'devuelve solo',
961
+ 'devuelve un json',
962
+ 'devuelve json',
963
+ 'debe devolver',
964
+ 'tu respuesta',
965
+ 'tu salida',
966
+ 'con este formato',
967
+ 'siguiente formato',
968
+ 'formato json',
969
+ ],
970
+ fr: [
971
+ 'format de sortie',
972
+ 'format de réponse',
973
+ 'schéma de sortie',
974
+ 'réponds avec',
975
+ 'répondez avec',
976
+ 'renvoie uniquement',
977
+ 'renvoie un json',
978
+ 'doit renvoyer',
979
+ 'ta réponse',
980
+ 'votre réponse',
981
+ 'ce format',
982
+ 'format suivant',
983
+ 'format json',
984
+ ],
985
+ de: [
986
+ 'ausgabeformat',
987
+ 'antwortformat',
988
+ 'ausgabeschema',
989
+ 'antworte mit',
990
+ 'antworten sie mit',
991
+ 'gib nur',
992
+ 'gib ein json',
993
+ 'muss zurückgeben',
994
+ 'deine antwort',
995
+ 'ihre antwort',
996
+ 'diesem format',
997
+ 'folgendes format',
998
+ 'json-format',
999
+ ],
1000
+ pt: [
1001
+ 'formato de saída',
1002
+ 'formato de resposta',
1003
+ 'esquema de saída',
1004
+ 'responda com',
1005
+ 'responde com',
1006
+ 'retorne apenas',
1007
+ 'retorne um json',
1008
+ 'deve retornar',
1009
+ 'sua resposta',
1010
+ 'sua saída',
1011
+ 'este formato',
1012
+ 'formato seguinte',
1013
+ 'formato json',
1014
+ ],
1015
+ it: [
1016
+ 'formato di output',
1017
+ 'formato di risposta',
1018
+ 'schema di output',
1019
+ 'rispondi con',
1020
+ 'risponda con',
1021
+ 'restituisci solo',
1022
+ 'restituisci un json',
1023
+ 'deve restituire',
1024
+ 'la tua risposta',
1025
+ 'il tuo output',
1026
+ 'questo formato',
1027
+ 'formato seguente',
1028
+ 'formato json',
1029
+ ],
1030
+ nl: [
1031
+ 'uitvoerformaat',
1032
+ 'antwoordformaat',
1033
+ 'uitvoerschema',
1034
+ 'antwoord met',
1035
+ 'geef alleen',
1036
+ 'geef een json',
1037
+ 'moet teruggeven',
1038
+ 'je antwoord',
1039
+ 'uw antwoord',
1040
+ 'dit formaat',
1041
+ 'volgende formaat',
1042
+ 'json-formaat',
1043
+ ],
1044
+ };
1045
+
1046
+ /** Every output cue, flattened. Order is not significant. */
1047
+ export const OUTPUT_CUES: readonly string[] = Object.values(OUTPUT_CUES_BY_LANGUAGE).flat();