@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/dist/detect.js ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * How each provider announces itself in source.
3
+ *
4
+ * Data rather than branches, so adding a provider is a row. Matched as plain
5
+ * substrings on a lowercased copy — no regex, because this runs over files a
6
+ * contributor supplies and the pattern-goes-quadratic lesson has been paid for
7
+ * three times this week already.
8
+ *
9
+ * A quoted package name will also match the same string appearing in a prompt's
10
+ * own text. That is accepted rather than fixed: an import is the *weakest* kind
11
+ * of evidence here, so a model literal or a base URL overrides it, and the
12
+ * alternative — enumerating every import spelling in two languages — is how the
13
+ * single-quoted `require` form came to be missing.
14
+ */
15
+ const SIGNATURES = [
16
+ {
17
+ provider: 'anthropic',
18
+ imports: ['@anthropic-ai/sdk', 'anthropic-sdk', 'from anthropic', 'import anthropic'],
19
+ urls: ['api.anthropic.com'],
20
+ },
21
+ {
22
+ provider: 'openai',
23
+ // The quoted package name covers `from 'openai'`, `require("openai")` and
24
+ // every other JS spelling without enumerating them — which is how the
25
+ // single-quoted `require` form got missed. The bare Python forms need their
26
+ // own entries because Python does not quote its imports.
27
+ imports: ["'openai'", '"openai"', 'from openai', 'import openai'],
28
+ urls: ['api.openai.com'],
29
+ },
30
+ {
31
+ provider: 'google',
32
+ imports: [
33
+ '@google/genai',
34
+ '@google/generative-ai',
35
+ 'google.generativeai',
36
+ 'from google import genai',
37
+ ],
38
+ urls: ['generativelanguage.googleapis.com', 'aiplatform.googleapis.com'],
39
+ },
40
+ {
41
+ provider: 'moonshot',
42
+ imports: ['moonshot'],
43
+ urls: ['api.moonshot.cn', 'api.moonshot.ai'],
44
+ },
45
+ {
46
+ provider: 'deepseek',
47
+ imports: ['deepseek'],
48
+ urls: ['api.deepseek.com'],
49
+ },
50
+ { provider: 'xai', imports: ['@ai-sdk/xai', 'xai-sdk'], urls: ['api.x.ai'] },
51
+ { provider: 'mistral', imports: ['@mistralai/mistralai', 'from mistralai'], urls: ['api.mistral.ai'] },
52
+ ];
53
+ /**
54
+ * 1-based line lookup, built once per file.
55
+ *
56
+ * The obvious version counts newlines from zero on every call, which is
57
+ * quadratic in the number of matches — 36 seconds on a file repeating a model
58
+ * id. That is the third time this week the same shape has appeared, and the
59
+ * second time I have written it after fixing it in `extract.ts`; the difference
60
+ * here is that matches arrive out of order across the signature loop, so a
61
+ * forward-only counter does not work and this is a binary search instead.
62
+ */
63
+ function lineIndex(source) {
64
+ const newlines = [];
65
+ for (let i = 0; i < source.length; i++)
66
+ if (source[i] === '\n')
67
+ newlines.push(i);
68
+ return (offset) => {
69
+ let low = 0;
70
+ let high = newlines.length;
71
+ while (low < high) {
72
+ const mid = (low + high) >> 1;
73
+ if (newlines[mid] < offset)
74
+ low = mid + 1;
75
+ else
76
+ high = mid;
77
+ }
78
+ return low + 1;
79
+ };
80
+ }
81
+ /**
82
+ * Model names written as string literals next to a `model` key.
83
+ *
84
+ * Bounded deliberately: `model` then up to a few characters of punctuation and
85
+ * whitespace, then a quoted value. A looser search would match the word "model"
86
+ * in a prompt's own text and price the prompt against something it merely
87
+ * mentions.
88
+ */
89
+ function modelLiterals(source, known, lineAt) {
90
+ const found = [];
91
+ const haystack = source.toLowerCase();
92
+ for (const model of known) {
93
+ let from = 0;
94
+ for (;;) {
95
+ const at = haystack.indexOf(model.id.toLowerCase(), from);
96
+ if (at === -1)
97
+ break;
98
+ from = at + model.id.length;
99
+ // It has to be a quoted value: a model id in prose is a mention, not a
100
+ // call. Checking the character either side is enough and costs nothing.
101
+ const before = source[at - 1];
102
+ const after = source[at + model.id.length];
103
+ const quoted = (before === "'" || before === '"' || before === '`') &&
104
+ (after === "'" || after === '"' || after === '`');
105
+ if (!quoted)
106
+ continue;
107
+ found.push({
108
+ kind: 'model-literal',
109
+ detail: model.id,
110
+ line: lineAt(at),
111
+ provider: model.provider,
112
+ model: model.id,
113
+ });
114
+ }
115
+ }
116
+ return found;
117
+ }
118
+ /** `model=gpt-5` on a `trazum:prompt` marker line. */
119
+ function markerModels(source, known, lineAt) {
120
+ const found = [];
121
+ let from = 0;
122
+ for (;;) {
123
+ const at = source.indexOf('trazum:prompt', from);
124
+ if (at === -1)
125
+ break;
126
+ from = at + 13;
127
+ const lineEnd = source.indexOf('\n', at);
128
+ const rest = source.slice(from, lineEnd === -1 ? source.length : lineEnd);
129
+ const key = rest.indexOf('model=');
130
+ if (key === -1)
131
+ continue;
132
+ const value = rest.slice(key + 6).trim().split(/[\s>]/)[0] ?? '';
133
+ const model = known.find((m) => m.id === value);
134
+ if (!model)
135
+ continue;
136
+ found.push({
137
+ kind: 'marker',
138
+ detail: `model=${value}`,
139
+ line: lineAt(at),
140
+ provider: model.provider,
141
+ model: model.id,
142
+ });
143
+ }
144
+ return found;
145
+ }
146
+ /**
147
+ * Works out which provider a source file talks to, and says why.
148
+ *
149
+ * Returns `provider: null` both when nothing was found and when the file named
150
+ * more than one — the caller cannot act on either, and `conflicts` distinguishes
151
+ * them for the reader.
152
+ */
153
+ export function detectFromSource(source, options = {}) {
154
+ const known = options.models ?? [];
155
+ const haystack = source.toLowerCase();
156
+ const lineAt = lineIndex(source);
157
+ const evidence = [
158
+ // Strongest first: the author naming a model beats the code naming one,
159
+ // which beats the code naming only a provider.
160
+ ...markerModels(source, known, lineAt),
161
+ ...modelLiterals(source, known, lineAt),
162
+ ];
163
+ for (const signature of SIGNATURES) {
164
+ for (const needle of signature.imports) {
165
+ const at = haystack.indexOf(needle.toLowerCase());
166
+ if (at === -1)
167
+ continue;
168
+ evidence.push({
169
+ kind: 'sdk-import',
170
+ detail: needle,
171
+ line: lineAt(at),
172
+ provider: signature.provider,
173
+ });
174
+ break;
175
+ }
176
+ for (const url of signature.urls) {
177
+ const at = haystack.indexOf(url);
178
+ if (at === -1)
179
+ continue;
180
+ evidence.push({
181
+ kind: 'base-url',
182
+ detail: url,
183
+ line: lineAt(at),
184
+ provider: signature.provider,
185
+ });
186
+ break;
187
+ }
188
+ }
189
+ if (evidence.length === 0) {
190
+ return { provider: null, model: null, evidence: [], conflicts: [] };
191
+ }
192
+ // A stronger kind overrides a weaker one; only equals can disagree.
193
+ //
194
+ // Getting this wrong the first time broke the most common non-OpenAI setup
195
+ // there is. Moonshot, DeepSeek, xAI, Groq and Together all ship an
196
+ // OpenAI-compatible endpoint, so their documented usage is the OpenAI SDK
197
+ // pointed at a different `base_url`. Treating that as a contradiction —
198
+ // "this file says both openai and deepseek" — would refuse to price a
199
+ // perfectly ordinary DeepSeek client. The base URL is the specific fact and
200
+ // the SDK import is the generic one, so the URL wins rather than ties.
201
+ //
202
+ // Likewise a `model=` on the marker is the author stating the answer, which
203
+ // cannot be contradicted by an import they wrote for another reason.
204
+ const RANK = {
205
+ marker: 3,
206
+ 'model-literal': 2,
207
+ 'base-url': 1,
208
+ 'sdk-import': 0,
209
+ };
210
+ const ranked = [...evidence].sort((a, b) => RANK[b.kind] - RANK[a.kind]);
211
+ const best = ranked[0];
212
+ const topRank = RANK[best.kind];
213
+ // Only evidence of the same strength can conflict. Anything weaker is
214
+ // context, not contradiction.
215
+ const conflicts = ranked.filter((e) => RANK[e.kind] === topRank && e.provider !== undefined && e.provider !== best.provider);
216
+ if (conflicts.length > 0) {
217
+ return { provider: null, model: null, evidence: ranked, conflicts };
218
+ }
219
+ return {
220
+ provider: best.provider ?? null,
221
+ // A model only comes from something that named one, and only from the
222
+ // winning provider: an import tells us who, never which of their models.
223
+ model: ranked.find((e) => e.model !== undefined && e.provider === best.provider)?.model ?? null,
224
+ evidence: ranked,
225
+ conflicts: [],
226
+ };
227
+ }
228
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,GAAmE;IACjF;QACE,QAAQ,EAAE,WAAW;QACrB,OAAO,EAAE,CAAC,mBAAmB,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,CAAC;QACrF,IAAI,EAAE,CAAC,mBAAmB,CAAC;KAC5B;IACD;QACE,QAAQ,EAAE,QAAQ;QAClB,0EAA0E;QAC1E,sEAAsE;QACtE,4EAA4E;QAC5E,yDAAyD;QACzD,OAAO,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,eAAe,CAAC;QACjE,IAAI,EAAE,CAAC,gBAAgB,CAAC;KACzB;IACD;QACE,QAAQ,EAAE,QAAQ;QAClB,OAAO,EAAE;YACP,eAAe;YACf,uBAAuB;YACvB,qBAAqB;YACrB,0BAA0B;SAC3B;QACD,IAAI,EAAE,CAAC,mCAAmC,EAAE,2BAA2B,CAAC;KACzE;IACD;QACE,QAAQ,EAAE,UAAU;QACpB,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,IAAI,EAAE,CAAC,iBAAiB,EAAE,iBAAiB,CAAC;KAC7C;IACD;QACE,QAAQ,EAAE,UAAU;QACpB,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,IAAI,EAAE,CAAC,kBAAkB,CAAC;KAC3B;IACD,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,aAAa,EAAE,SAAS,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE;IAC5E,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,sBAAsB,EAAE,gBAAgB,CAAC,EAAE,IAAI,EAAE,CAAC,gBAAgB,CAAC,EAAE;CACvG,CAAC;AAEF;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,MAAc;IAC/B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEjF,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,IAAI,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC3B,OAAO,GAAG,GAAG,IAAI,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,QAAQ,CAAC,GAAG,CAAE,GAAG,MAAM;gBAAE,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;;gBACtC,IAAI,GAAG,GAAG,CAAC;QAClB,CAAC;QACD,OAAO,GAAG,GAAG,CAAC,CAAC;IACjB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CACpB,MAAc,EACd,KAAqB,EACrB,MAAkC;IAElC,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,SAAS,CAAC;YACR,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,IAAI,CAAC,CAAC;YAC1D,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,MAAM;YACrB,IAAI,GAAG,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC;YAE5B,uEAAuE;YACvE,wEAAwE;YACxE,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;YAC3C,MAAM,MAAM,GACV,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,CAAC;gBACpD,CAAC,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,CAAC;YACpD,IAAI,CAAC,MAAM;gBAAE,SAAS;YAEtB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,KAAK,CAAC,EAAE;gBAChB,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,KAAK,EAAE,KAAK,CAAC,EAAE;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sDAAsD;AACtD,SAAS,YAAY,CACnB,MAAc,EACd,KAAqB,EACrB,MAAkC;IAElC,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,SAAS,CAAC;QACR,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;QACjD,IAAI,EAAE,KAAK,CAAC,CAAC;YAAE,MAAM;QACrB,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC;QAEf,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,SAAS;QAEzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACjE,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;QAChD,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,SAAS,KAAK,EAAE;YACxB,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,KAAK,EAAE,KAAK,CAAC,EAAE;SAChB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAOD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,OAAO,GAAkB,EAAE;IAC1E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IAEjC,MAAM,QAAQ,GAAe;QAC3B,wEAAwE;QACxE,+CAA+C;QAC/C,GAAG,YAAY,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;QACtC,GAAG,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;KACxC,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACvC,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;YAClD,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,SAAS;YACxB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBAChB,QAAQ,EAAE,SAAS,CAAC,QAAQ;aAC7B,CAAC,CAAC;YACH,MAAM;QACR,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC;YACjC,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,EAAE,KAAK,CAAC,CAAC;gBAAE,SAAS;YACxB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,GAAG;gBACX,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;gBAChB,QAAQ,EAAE,SAAS,CAAC,QAAQ;aAC7B,CAAC,CAAC;YACH,MAAM;QACR,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACtE,CAAC;IAED,oEAAoE;IACpE,EAAE;IACF,2EAA2E;IAC3E,mEAAmE;IACnE,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,4EAA4E;IAC5E,uEAAuE;IACvE,EAAE;IACF,4EAA4E;IAC5E,qEAAqE;IACrE,MAAM,IAAI,GAAiC;QACzC,MAAM,EAAE,CAAC;QACT,eAAe,EAAE,CAAC;QAClB,UAAU,EAAE,CAAC;QACb,YAAY,EAAE,CAAC;KAChB,CAAC;IACF,MAAM,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;IACxB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhC,sEAAsE;IACtE,8BAA8B;IAC9B,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAC7B,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAC5F,CAAC;IAEF,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IACtE,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,IAAI;QAC/B,sEAAsE;QACtE,yEAAyE;QACzE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,IAAI;QAC/F,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,EAAE;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,98 @@
1
+ import type { LlmProvider } from './types.js';
2
+ /**
3
+ * Golden-set evaluation.
4
+ *
5
+ * Everything else Trazum reports is arithmetic: tokens, prices, multiplication.
6
+ * This is the one question arithmetic cannot answer — does the shorter prompt
7
+ * still do the job? — and the README has been answering it with a caveat
8
+ * ("the aggressive level can change nuance; read the diff") because a rules
9
+ * engine genuinely cannot know.
10
+ *
11
+ * The trap here is comparing the two prompts' outputs and calling the
12
+ * difference a regression. A model asked the same question twice does not
13
+ * answer identically, so "the optimised prompt diverged on 3 of 10 cases" is
14
+ * meaningless on its own — it might be better than the original manages
15
+ * against itself.
16
+ *
17
+ * So the original is run twice per case first, and that self-agreement is the
18
+ * yardstick. The optimised prompt is judged against the model's own variance,
19
+ * not against an imaginary determinism it never had. It costs a third call per
20
+ * case and it is the only reason the number means anything.
21
+ */
22
+ export interface EvalCase {
23
+ /** The input this case feeds the prompt. */
24
+ input: string;
25
+ /** The original prompt's two answers, used to measure its own variance. */
26
+ baseline: [string, string];
27
+ /** The optimised prompt's answer. */
28
+ optimized: string;
29
+ /** How closely the original agreed with itself (0-1). */
30
+ selfSimilarity: number;
31
+ /** How closely the optimised answer matched the original's first (0-1). */
32
+ crossSimilarity: number;
33
+ }
34
+ export type EvalVerdict = 'indistinguishable' | 'within-noise' | 'diverges' | 'inconclusive';
35
+ export interface EvalReport {
36
+ provider: string;
37
+ model: string;
38
+ cases: EvalCase[];
39
+ /** Mean agreement of the original prompt with itself. The yardstick. */
40
+ selfAgreement: number;
41
+ /** Mean agreement between the original and the optimised prompt. */
42
+ crossAgreement: number;
43
+ verdict: EvalVerdict;
44
+ /** Total provider calls made, so the cost is never a surprise. */
45
+ callsMade: number;
46
+ }
47
+ export interface EvaluateOptions {
48
+ /**
49
+ * How many cases to run at once. Kept low by default: this hammers someone
50
+ * else's endpoint, and a rate limit tripped halfway through wastes every
51
+ * call already paid for.
52
+ */
53
+ concurrency?: number;
54
+ }
55
+ /**
56
+ * Builds the prompt for one case.
57
+ *
58
+ * A template gets its first placeholder filled; anything else gets the input
59
+ * appended. Substituting is the honest reading of a prompt written with
60
+ * `{{query}}` — appending would test a prompt nobody runs.
61
+ */
62
+ export declare function fillPrompt(prompt: string, input: string): string;
63
+ /** Agreement between two answers, 0-1. */
64
+ /**
65
+ * How closely two answers agree, 0 to 1.
66
+ *
67
+ * Exported because `prune.ts` measures the same thing and must measure it the
68
+ * same way. It was a private function here first, and the copy that appeared in
69
+ * `prune.ts` was a bag-of-words F1 while this is Jaccard over normalised text —
70
+ * two different numbers under one name, with a comment in the copy claiming they
71
+ * were the same measure. Sharing the function is what makes that comment true.
72
+ */
73
+ export declare function agreement(a: string, b: string): number;
74
+ /**
75
+ * Turns the two agreement figures into a verdict.
76
+ *
77
+ * The comparison is always relative. An optimised prompt agreeing with the
78
+ * original 0.85 of the time looks alarming until you see the original agrees
79
+ * with itself 0.86 — at which point the optimisation changed nothing the model
80
+ * was not already doing on its own.
81
+ *
82
+ * `inconclusive` exists because a model that is wildly inconsistent with itself
83
+ * cannot be used to judge anything. Reporting a confident verdict off that
84
+ * would be worse than admitting the test does not work here.
85
+ */
86
+ export declare function verdictFor(selfAgreement: number, crossAgreement: number): EvalVerdict;
87
+ /** Runs `tasks` with a bounded number in flight, preserving order. */
88
+ export declare function pooled<T>(tasks: Array<() => Promise<T>>, limit: number): Promise<T[]>;
89
+ /**
90
+ * Runs both prompt versions over a set of inputs and reports whether the
91
+ * optimisation changed the answers.
92
+ *
93
+ * Costs **three provider calls per case**: the original twice, the optimised
94
+ * once. The doubled original is what makes the result interpretable, and
95
+ * `callsMade` reports the total so the bill is never a surprise.
96
+ */
97
+ export declare function evaluate(originalPrompt: string, optimizedPrompt: string, inputs: readonly string[], provider: LlmProvider, options?: EvaluateOptions): Promise<EvalReport>;
98
+ //# sourceMappingURL=evaluate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,WAAW,QAAQ;IACvB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,cAAc,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,WAAW,GAAG,mBAAmB,GAAG,cAAc,GAAG,UAAU,GAAG,cAAc,CAAC;AAE7F,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,wEAAwE;IACxE,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,WAAW,CAAC;IACrB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,0CAA0C;AAC1C;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtD;AAKD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,WAAW,CAOrF;AAED,sEAAsE;AACtE,wBAAsB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAc3F;AAED;;;;;;;GAOG;AACH,wBAAsB,QAAQ,CAC5B,cAAc,EAAE,MAAM,EACtB,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,QAAQ,EAAE,WAAW,EACrB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,UAAU,CAAC,CAsCrB"}
@@ -0,0 +1,110 @@
1
+ import { segment } from './segment.js';
2
+ import { jaccard, normalizeForCompare } from './similarity.js';
3
+ /**
4
+ * Builds the prompt for one case.
5
+ *
6
+ * A template gets its first placeholder filled; anything else gets the input
7
+ * appended. Substituting is the honest reading of a prompt written with
8
+ * `{{query}}` — appending would test a prompt nobody runs.
9
+ */
10
+ export function fillPrompt(prompt, input) {
11
+ const placeholder = segment(prompt).find((s) => s.kind === 'protected' && s.protection === 'placeholder');
12
+ if (!placeholder)
13
+ return `${prompt.trimEnd()}\n\n${input}`;
14
+ return prompt.replace(placeholder.text, input);
15
+ }
16
+ /** Agreement between two answers, 0-1. */
17
+ /**
18
+ * How closely two answers agree, 0 to 1.
19
+ *
20
+ * Exported because `prune.ts` measures the same thing and must measure it the
21
+ * same way. It was a private function here first, and the copy that appeared in
22
+ * `prune.ts` was a bag-of-words F1 while this is Jaccard over normalised text —
23
+ * two different numbers under one name, with a comment in the copy claiming they
24
+ * were the same measure. Sharing the function is what makes that comment true.
25
+ */
26
+ export function agreement(a, b) {
27
+ const left = normalizeForCompare(a);
28
+ const right = normalizeForCompare(b);
29
+ if (left === right)
30
+ return 1;
31
+ return jaccard(left, right);
32
+ }
33
+ const mean = (values) => values.length === 0 ? 0 : values.reduce((sum, v) => sum + v, 0) / values.length;
34
+ /**
35
+ * Turns the two agreement figures into a verdict.
36
+ *
37
+ * The comparison is always relative. An optimised prompt agreeing with the
38
+ * original 0.85 of the time looks alarming until you see the original agrees
39
+ * with itself 0.86 — at which point the optimisation changed nothing the model
40
+ * was not already doing on its own.
41
+ *
42
+ * `inconclusive` exists because a model that is wildly inconsistent with itself
43
+ * cannot be used to judge anything. Reporting a confident verdict off that
44
+ * would be worse than admitting the test does not work here.
45
+ */
46
+ export function verdictFor(selfAgreement, crossAgreement) {
47
+ if (crossAgreement >= 0.999)
48
+ return 'indistinguishable';
49
+ if (selfAgreement < 0.5)
50
+ return 'inconclusive';
51
+ // Within a small margin of the model's own noise floor, the difference is
52
+ // not attributable to the prompt.
53
+ if (crossAgreement >= selfAgreement - 0.05)
54
+ return 'within-noise';
55
+ return 'diverges';
56
+ }
57
+ /** Runs `tasks` with a bounded number in flight, preserving order. */
58
+ export async function pooled(tasks, limit) {
59
+ const results = new Array(tasks.length);
60
+ let next = 0;
61
+ const workers = Array.from({ length: Math.min(limit, tasks.length) }, async () => {
62
+ for (;;) {
63
+ const index = next++;
64
+ if (index >= tasks.length)
65
+ return;
66
+ results[index] = await tasks[index]();
67
+ }
68
+ });
69
+ await Promise.all(workers);
70
+ return results;
71
+ }
72
+ /**
73
+ * Runs both prompt versions over a set of inputs and reports whether the
74
+ * optimisation changed the answers.
75
+ *
76
+ * Costs **three provider calls per case**: the original twice, the optimised
77
+ * once. The doubled original is what makes the result interpretable, and
78
+ * `callsMade` reports the total so the bill is never a surprise.
79
+ */
80
+ export async function evaluate(originalPrompt, optimizedPrompt, inputs, provider, options = {}) {
81
+ const concurrency = Math.max(1, options.concurrency ?? 3);
82
+ const run = (prompt, input) => provider.complete({ system: fillPrompt(prompt, input), user: input });
83
+ const cases = await pooled(inputs.map((input) => async () => {
84
+ // Sequential within a case: the two baseline runs exist to measure the
85
+ // model's variance, and issuing them together invites a provider to
86
+ // serve one from a cache and report a variance of zero.
87
+ const baselineA = await run(originalPrompt, input);
88
+ const baselineB = await run(originalPrompt, input);
89
+ const optimized = await run(optimizedPrompt, input);
90
+ return {
91
+ input,
92
+ baseline: [baselineA, baselineB],
93
+ optimized,
94
+ selfSimilarity: agreement(baselineA, baselineB),
95
+ crossSimilarity: agreement(baselineA, optimized),
96
+ };
97
+ }), concurrency);
98
+ const selfAgreement = mean(cases.map((c) => c.selfSimilarity));
99
+ const crossAgreement = mean(cases.map((c) => c.crossSimilarity));
100
+ return {
101
+ provider: provider.name,
102
+ model: provider.model,
103
+ cases,
104
+ selfAgreement,
105
+ crossAgreement,
106
+ verdict: verdictFor(selfAgreement, crossAgreement),
107
+ callsMade: cases.length * 3,
108
+ };
109
+ }
110
+ //# sourceMappingURL=evaluate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Prompts embedded in source files.
3
+ *
4
+ * `check` and `diff` read `.txt`, `.md`, `.prompt` and `.tmpl`. Real prompts live
5
+ * in TypeScript template literals, Python triple-quoted strings and YAML blocks,
6
+ * so adopting Trazum has meant first refactoring them out into standalone files —
7
+ * a change to somebody's application as the price of admission.
8
+ *
9
+ * **It reads a marker, it does not guess.** `// trazum:prompt` on the line before
10
+ * the literal, and then the literal by delimiter matching. Guessing which string
11
+ * in a file is a prompt is a heuristic, and a heuristic inside a tool used as a
12
+ * CI gate produces failures on strings nobody meant to govern. A marker is one
13
+ * line of noise in exchange for never being wrong about what it picked up.
14
+ *
15
+ * **Interpolation already works and is not a special case.** `${x}` inside a
16
+ * template literal is exactly the placeholder shape `segment.ts` protects, so an
17
+ * embedded prompt gets the same cache-prefix analysis, the same protection from
18
+ * the rules, and the same `--reorder` treatment as a `{{x}}` template. Nothing
19
+ * here has to know about it.
20
+ *
21
+ * **The honest limit:** a prompt assembled from concatenated pieces cannot be
22
+ * read this way. `` `You are ${role}.` + rules.join('\n') `` is a prompt whose
23
+ * text does not exist until it runs. This module declines it and says so rather
24
+ * than governing the half it can see — a budget enforced against a fragment is a
25
+ * green build for a prompt nobody measured.
26
+ *
27
+ * Scanned character by character rather than with a regex. The module it most
28
+ * resembles shipped two quadratic patterns this week, and delimiter matching over
29
+ * untrusted source is exactly the shape that goes wrong.
30
+ */
31
+ export interface ExtractedPrompt {
32
+ /** Name from `trazum:prompt <name>`, or undefined when the marker was bare. */
33
+ name?: string;
34
+ /** 1-based line of the marker comment, which is what an error should cite. */
35
+ line: number;
36
+ /** The prompt text, delimiters removed and escapes resolved. */
37
+ text: string;
38
+ /** The delimiter it was written with, so a writer could put it back. */
39
+ quote: '`' | '"' | "'" | '"""' | "'''";
40
+ /** Offsets of the text within the source, exclusive of the delimiters. */
41
+ start: number;
42
+ end: number;
43
+ }
44
+ export interface DeclinedPrompt {
45
+ line: number;
46
+ /**
47
+ * Why this marker produced nothing.
48
+ *
49
+ * `concatenated` is the interesting one: the text exists only at runtime.
50
+ * `no-literal` means the marker was not followed by a string at all.
51
+ * `unterminated` means the file ends inside the literal, which is a syntax
52
+ * error in the source rather than a Trazum problem, but worth naming.
53
+ */
54
+ reason: 'concatenated' | 'no-literal' | 'unterminated';
55
+ detail: string;
56
+ }
57
+ export interface ExtractionResult {
58
+ prompts: ExtractedPrompt[];
59
+ declined: DeclinedPrompt[];
60
+ }
61
+ /**
62
+ * Finds every marked prompt in a source file.
63
+ *
64
+ * Returns what it found and what it refused, because a marker that produced
65
+ * nothing is the case the author most needs to hear about: they asked for the
66
+ * prompt to be governed and it is not being governed.
67
+ */
68
+ export declare function extractPrompts(source: string): ExtractionResult;
69
+ /**
70
+ * A stable identifier for an embedded prompt, for budgets and reports.
71
+ *
72
+ * `src/prompts.ts#support` when the marker was named, `src/prompts.ts:12` when it
73
+ * was not. Both are path-prefixed so the glob patterns in `trazum.config.json`
74
+ * cover embedded prompts without learning a new syntax — `src/**` matches either.
75
+ */
76
+ export declare function promptId(path: string, prompt: ExtractedPrompt): string;
77
+ /** Whether a file is worth opening for extraction at all. */
78
+ export declare const SOURCE_EXTENSIONS: string[];
79
+ /** Cheap pre-filter: a source file with no marker has nothing to extract. */
80
+ export declare const hasMarker: (source: string) => boolean;
81
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,KAAK,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,KAAK,GAAG,KAAK,CAAC;IACvC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,cAAc,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAkJD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAuF/D;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,CAEtE;AAED,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,UAgB7B,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,SAAS,WAAY,MAAM,KAAG,OAA+B,CAAC"}