@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
@@ -0,0 +1,151 @@
1
+ import { normalizeForCompare } from './similarity.js';
2
+ import { estimateTokens } from './tokenizer.js';
3
+ /**
4
+ * The waste no single prompt can see.
5
+ *
6
+ * Every other analysis in this package reads one prompt. This one reads a
7
+ * workspace, because there is a kind of caching loss that only exists *between*
8
+ * prompts: forty prompts assembled from the same system preamble, byte-identical
9
+ * except that one has a trailing space, another reordered two bullets, and a
10
+ * third says "You are an assistant" where the rest say "You are an Assistant".
11
+ *
12
+ * Prompt caching is a byte-for-byte prefix match. Those forty prompts therefore
13
+ * occupy forty separate cache entries and share nothing. Unify the preamble and
14
+ * they occupy one. No amount of analysis of any one of those files can find that
15
+ * — each is individually fine — which is what makes this worth a module rather
16
+ * than another advisory.
17
+ *
18
+ * **It reports no dollar figure, and that is a finding rather than a gap.**
19
+ *
20
+ * The saving lives entirely in the cache hit rate: a prompt called ten times a
21
+ * month never warms an entry of its own and always reads a shared one that
22
+ * somebody else keeps hot. But `cacheHitRate` is an *input* to Trazum's cost
23
+ * model, not an output — `advisories.ts` takes it from `--cache-hit-rate` and
24
+ * applies the same value to every prompt. Under that model, splitting one prefix
25
+ * into forty changes nothing, because the model has no term for how many distinct
26
+ * cache entries exist.
27
+ *
28
+ * Pricing this would mean inventing a distribution of calls across the group,
29
+ * which is the one thing here that only the operator knows. So the report gives
30
+ * what it can establish — which prompts, how far the common prefix runs, and what
31
+ * stops it being byte-identical — and refuses the number. Naming a mechanism
32
+ * accurately beats attaching a figure to it that came from nowhere.
33
+ */
34
+ /** Blocks are blank-line separated, the same unit `reorder.ts` moves. */
35
+ function blocksOf(text) {
36
+ return text.split(/\n[ \t]*\n/);
37
+ }
38
+ /**
39
+ * Whitespace collapsed, and nothing else touched.
40
+ *
41
+ * Used to separate the two kinds of drift, because they are different amounts of
42
+ * work to fix. A prefix that differs only here is a formatter away from sharing a
43
+ * cache entry; one that differs in wording needs somebody to decide which wording
44
+ * is right.
45
+ */
46
+ function collapseWhitespace(text) {
47
+ return text.replace(/\s+/g, ' ').trim();
48
+ }
49
+ /**
50
+ * The prefix length below which unifying buys nothing, and `Infinity` when there
51
+ * is no answer.
52
+ *
53
+ * Lives here rather than in the CLI because it is the gate on this whole report
54
+ * and a gate with no test is decoration. It was a private function in the CLI
55
+ * first; a mutation run deleting the `unknown` branch survived, which is what a
56
+ * function nothing can reach looks like from the outside.
57
+ *
58
+ * `unknown` and a missing minimum both yield `Infinity`, so nothing is reported.
59
+ * That is the direction to fail: telling somebody to unify a preamble across
60
+ * twelve files to enable caching their provider may not offer spends their
61
+ * afternoon, and unlike a wrong number on a report nothing later corrects it.
62
+ */
63
+ export function cacheableMinimum(model) {
64
+ if (!model)
65
+ return Number.POSITIVE_INFINITY;
66
+ if (model.caching === 'none' || model.caching === 'unknown')
67
+ return Number.POSITIVE_INFINITY;
68
+ return model.cacheMinTokens ?? Number.POSITIVE_INFINITY;
69
+ }
70
+ /**
71
+ * Groups of prompts that could share a cache prefix and do not.
72
+ *
73
+ * Grouped by their **first** block, which is not a shortcut. Caching matches from
74
+ * the beginning of the request, so two prompts whose opening paragraphs differ
75
+ * share nothing no matter how identical the rest is — a group keyed on anything
76
+ * later would name prompts that cannot be made to share a prefix without
77
+ * reordering them, which is a different and far more dangerous change.
78
+ */
79
+ export function sharedPrefixes(prompts, options = {}) {
80
+ const counter = options.countTokens ?? estimateTokens;
81
+ const minTokens = options.minTokens ?? 0;
82
+ const byOpening = new Map();
83
+ for (const prompt of prompts) {
84
+ const blocks = blocksOf(prompt.text);
85
+ const opening = normalizeForCompare(blocks[0] ?? '');
86
+ // An empty opening is not a match, it is an absence. Grouping on it would
87
+ // put every blank-led prompt in one group and report a prefix of nothing.
88
+ if (opening === '')
89
+ continue;
90
+ const group = byOpening.get(opening);
91
+ if (group)
92
+ group.push(prompt);
93
+ else
94
+ byOpening.set(opening, [prompt]);
95
+ }
96
+ const found = [];
97
+ for (const group of byOpening.values()) {
98
+ if (group.length < 2)
99
+ continue;
100
+ const blockLists = group.map((prompt) => blocksOf(prompt.text));
101
+ const first = blockLists[0];
102
+ if (!first)
103
+ continue;
104
+ // How far the agreement runs, comparing normalised blocks position by
105
+ // position. Stops at the first block any member disagrees on, because a
106
+ // prefix is contiguous by definition.
107
+ let shared = 0;
108
+ for (let index = 0; index < first.length; index++) {
109
+ const target = normalizeForCompare(first[index] ?? '');
110
+ if (target === '')
111
+ break;
112
+ const all = blockLists.every((blocks) => normalizeForCompare(blocks[index] ?? '') === target);
113
+ if (!all)
114
+ break;
115
+ shared = index + 1;
116
+ }
117
+ if (shared === 0)
118
+ continue;
119
+ /**
120
+ * The raw prefixes, joined the way they were written.
121
+ *
122
+ * `\n\n` rather than the original separator: the separator is itself part of
123
+ * what may differ, and reconstructing each prompt's exact bytes is not needed
124
+ * to answer whether they agree.
125
+ */
126
+ const rawPrefixes = blockLists.map((blocks) => blocks.slice(0, shared).join('\n\n'));
127
+ const sample = rawPrefixes[0] ?? '';
128
+ // Already byte-identical: these prompts share a cache entry today and there
129
+ // is nothing to recover. Reporting them would be noise that teaches people
130
+ // to stop reading this section.
131
+ if (rawPrefixes.every((prefix) => prefix === sample))
132
+ continue;
133
+ const collapsed = rawPrefixes.map(collapseWhitespace);
134
+ const flat = collapseWhitespace(sample);
135
+ const drift = collapsed.every((prefix) => prefix === flat) ? 'whitespace' : 'wording';
136
+ const tokens = counter(sample);
137
+ if (tokens < minTokens)
138
+ continue;
139
+ found.push({
140
+ paths: group.map((prompt) => prompt.path),
141
+ blocks: shared,
142
+ tokens,
143
+ drift,
144
+ sample,
145
+ });
146
+ }
147
+ // Largest prefix first: it is the group where unifying recovers most, and the
148
+ // ordering is a total one so the output does not depend on Map iteration.
149
+ return found.sort((a, b) => b.tokens - a.tokens || (a.paths[0] ?? '').localeCompare(b.paths[0] ?? ''));
150
+ }
151
+ //# sourceMappingURL=shared-prefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-prefix.js","sourceRoot":"","sources":["../src/shared-prefix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Text-similarity helpers shared by the duplicate rules and the structural
3
+ * analysis.
4
+ *
5
+ * Both need to answer "are these two chunks saying the same thing?", and both
6
+ * need to answer it the same way — a rule that removes a near-duplicate and an
7
+ * advisory that reports one should never disagree about what "near" means.
8
+ */
9
+ /** Normalises a line or paragraph for duplicate comparison. */
10
+ export declare function normalizeForCompare(text: string): string;
11
+ /** Jaccard similarity over word sets. */
12
+ export declare function jaccard(a: string, b: string): number;
13
+ //# sourceMappingURL=similarity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../src/similarity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxD;AAED,yCAAyC;AACzC,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAOpD"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Text-similarity helpers shared by the duplicate rules and the structural
3
+ * analysis.
4
+ *
5
+ * Both need to answer "are these two chunks saying the same thing?", and both
6
+ * need to answer it the same way — a rule that removes a near-duplicate and an
7
+ * advisory that reports one should never disagree about what "near" means.
8
+ */
9
+ /** Normalises a line or paragraph for duplicate comparison. */
10
+ export function normalizeForCompare(text) {
11
+ return text
12
+ .toLowerCase()
13
+ .normalize('NFD')
14
+ .replace(/\p{Diacritic}/gu, '')
15
+ .replace(/[^\p{L}\p{N}]+/gu, ' ')
16
+ .trim();
17
+ }
18
+ /** Jaccard similarity over word sets. */
19
+ export function jaccard(a, b) {
20
+ const setA = new Set(a.split(' ').filter(Boolean));
21
+ const setB = new Set(b.split(' ').filter(Boolean));
22
+ if (setA.size === 0 || setB.size === 0)
23
+ return 0;
24
+ let intersection = 0;
25
+ for (const word of setA)
26
+ if (setB.has(word))
27
+ intersection++;
28
+ return intersection / (setA.size + setB.size - intersection);
29
+ }
30
+ //# sourceMappingURL=similarity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity.js","sourceRoot":"","sources":["../src/similarity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,WAAW,EAAE;SACb,SAAS,CAAC,KAAK,CAAC;SAChB,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;SAC9B,OAAO,CAAC,kBAAkB,EAAE,GAAG,CAAC;SAChC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,OAAO,CAAC,CAAS,EAAE,CAAS;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACjD,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,IAAI,IAAI,IAAI;QAAE,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,YAAY,EAAE,CAAC;IAC5D,OAAO,YAAY,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,144 @@
1
+ import type { ContradictionAxisId, ContradictionValueId } from './i18n/types.js';
2
+ import type { TokenCounter } from './types.js';
3
+ /**
4
+ * Structural analysis.
5
+ *
6
+ * Every rule in `rules.ts` matches a phrase. The waste this module looks for
7
+ * is a relationship between two places in the prompt — an instruction that
8
+ * contradicts another, an example that teaches what an earlier one already
9
+ * taught. No dictionary can see either, because neither is wrong on its own.
10
+ *
11
+ * Everything here is advisory. A contradiction has a right answer only the
12
+ * author knows, and an example that looks redundant may be demonstrating a
13
+ * boundary case on purpose. Trazum points; it does not cut.
14
+ */
15
+ /**
16
+ * Sourced from the i18n contract rather than declared here, so adding an axis
17
+ * fails to compile until every catalogue can name it.
18
+ */
19
+ export type ContradictionAxis = ContradictionAxisId;
20
+ export interface ContradictionSide {
21
+ /** Which end of the axis this instruction sits on. */
22
+ value: ContradictionValueId;
23
+ /** The sentence it was found in, trimmed for display. */
24
+ snippet: string;
25
+ }
26
+ export interface Contradiction {
27
+ axis: ContradictionAxis;
28
+ a: ContradictionSide;
29
+ b: ContradictionSide;
30
+ }
31
+ /**
32
+ * Finds instructions that pull in opposite directions on the same axis.
33
+ *
34
+ * Only the first occurrence of each value is kept, and an axis reports at most
35
+ * one contradiction: a prompt that says "be concise" four times and "be
36
+ * thorough" once has one problem, not four.
37
+ *
38
+ * Two matches inside the same sentence are ignored. "Be concise but complete"
39
+ * is a deliberate trade-off an author wrote on purpose; flagging it would
40
+ * train people to ignore the advisory.
41
+ */
42
+ export declare function findContradictions(prompt: string): Contradiction[];
43
+ /**
44
+ * A labelled field inside a few-shot example — `Input:`, `Output:`, `A:`…
45
+ *
46
+ * Exported because the duplicate-line rule needs it: two examples that share
47
+ * an output line are demonstrating that two different inputs map to the *same*
48
+ * answer, which is often the whole point of including both. Deduplicating
49
+ * those lines leaves an example with no output at all — worse than the
50
+ * repetition it removed.
51
+ */
52
+ export declare const EXAMPLE_FIELD_LINE: RegExp;
53
+ export interface ExampleBlock {
54
+ text: string;
55
+ tokens: number;
56
+ }
57
+ export interface RedundantExample {
58
+ /** Index of the earlier example that already teaches this. */
59
+ duplicateOf: number;
60
+ index: number;
61
+ similarity: number;
62
+ tokens: number;
63
+ }
64
+ export interface ExampleAnalysis {
65
+ examples: ExampleBlock[];
66
+ redundant: RedundantExample[];
67
+ /** Tokens held by examples that repeat an earlier one. */
68
+ redundantTokens: number;
69
+ }
70
+ /** Splits the prompt into labelled example blocks. */
71
+ export declare function findExamples(prompt: string, count: TokenCounter): ExampleBlock[];
72
+ /**
73
+ * Reports examples that repeat what an earlier example already demonstrates.
74
+ *
75
+ * Each example is compared against every earlier one, and blamed on the first
76
+ * match: in a run of three near-identical examples the second and third are
77
+ * both reported against the first, rather than chaining.
78
+ */
79
+ export declare function analyzeExamples(prompt: string, count: TokenCounter): ExampleAnalysis;
80
+ /**
81
+ * A prompt that shows its output schema in a code block, and then describes
82
+ * the same fields again in prose, is paying for the schema twice.
83
+ *
84
+ * The block is the version that survives: it is unambiguous, and the
85
+ * protection pass already guarantees Trazum will not touch it. The prose
86
+ * restatement is what can go — but only a human can tell a restatement from a
87
+ * clarification that happens to name the same fields, so this reports and does
88
+ * not cut.
89
+ */
90
+ export interface RestatedFormat {
91
+ /** Top-level keys found in the fenced schema. */
92
+ keys: string[];
93
+ /** Those keys that are also named in the prose outside the block. */
94
+ restatedKeys: string[];
95
+ /** Tokens held by the prose sentences that restate them. */
96
+ restatedTokens: number;
97
+ }
98
+ /**
99
+ * Finds an output schema that the prose repeats.
100
+ *
101
+ * The threshold is deliberately blunt: naming one or two fields in prose is
102
+ * ordinary ("set `escalate` to true when the customer asks for a human"), and
103
+ * flagging that would make the advisory noise. Three or more, and the prose is
104
+ * walking the schema.
105
+ */
106
+ export declare function findRestatedFormat(prompt: string, count: TokenCounter): RestatedFormat | null;
107
+ /**
108
+ * A schema shown in the prompt that the request could carry as a parameter.
109
+ *
110
+ * Every provider worth naming now accepts a response schema alongside the
111
+ * message — `output_config.format`, `response_format`, `responseSchema`,
112
+ * whatever it is called this quarter. A prompt that spells the same shape out in
113
+ * a fenced block pays for it in input tokens on **every call**, and gets a
114
+ * weaker guarantee for the money: prose asks the model to comply, a parameter
115
+ * makes the decoder comply.
116
+ *
117
+ * So this is the rare finding that is not a trade-off. Moving a schema out is
118
+ * cheaper *and* stricter. What stops it being a rule is that Trazum cannot make
119
+ * the change: it edits prompts, and this is a change to the call around the
120
+ * prompt. It reports, names the tokens, and leaves the edit to whoever owns the
121
+ * client code.
122
+ *
123
+ * **The one way this could do harm, and what prevents it.** A fenced JSON block
124
+ * in a prompt is one of two completely different things. `Output format: {...}`
125
+ * is a contract and moving it is free. `Input: {...}` inside a few-shot example
126
+ * is *data the prompt needs*, and moving it breaks the prompt. Nothing here
127
+ * guesses which: a block counts only when a phrase from `OUTPUT_CUES_BY_LANGUAGE`
128
+ * appears in the text immediately before it. A schema with no such phrase is
129
+ * left alone, and a prompt in a language those dictionaries do not cover raises
130
+ * nothing at all — a false negative, stated as one, rather than an English cue
131
+ * matched inside Japanese prose and called a saving.
132
+ */
133
+ export interface MovableSchema {
134
+ /** Fenced blocks that an output cue introduces. */
135
+ blocks: number;
136
+ /** Top-level keys across all of them, deduplicated. */
137
+ keys: string[];
138
+ /** Tokens the blocks hold, fences included, since all of it leaves the prompt. */
139
+ tokens: number;
140
+ /** The cue that identified the first block, so the report can quote it. */
141
+ cue: string;
142
+ }
143
+ export declare function findMovableSchema(prompt: string, count: TokenCounter): MovableSchema | null;
144
+ //# sourceMappingURL=structure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACjF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;;;;;;GAWG;AAMH;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,KAAK,EAAE,oBAAoB,CAAC;IAC5B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,CAAC,EAAE,iBAAiB,CAAC;IACrB,CAAC,EAAE,iBAAiB,CAAC;CACtB;AAoID;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CA6BlE;AA6CD;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,QAG9B,CAAC;AAEF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;CACzB;AAqED,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,EAAE,CAWhF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,eAAe,CAyBpF;AAMD;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,qEAAqE;IACrE,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,CAAC;CACxB;AAmED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,cAAc,GAAG,IAAI,CAuC7F;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;CACb;AAqBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,aAAa,GAAG,IAAI,CA4C3F"}