@trazum/core 1.86.0 → 2.0.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.
package/dist/receipt.js CHANGED
@@ -1,26 +1,45 @@
1
1
  import { PROVIDER_REVIEWED, modelFrom } from './pricing.js';
2
2
  import { UNLABELLED } from './usage.js';
3
3
  /**
4
- * The fields that leave this machine, listed once, in one place.
4
+ * The fields that leave this machine, keyed by the interface they describe.
5
5
  *
6
- * The redaction guard reads this array rather than a copy of it, so a field
7
- * added to `ReceiptLine` and not added here fails the guard. A whitelist
8
- * maintained in two places is a whitelist with a hole in it.
6
+ * `Record<keyof ReceiptLine, true>` is what holds the two together: a field
7
+ * added to `ReceiptLine` and not added here is a **compile error**, and so is
8
+ * an entry here that `ReceiptLine` does not have. A whitelist maintained in two
9
+ * places is a whitelist with a hole in it.
10
+ *
11
+ * **This used to say the redaction guard held that, and it did not.** The guard
12
+ * reads the emitted lines and checks every key it finds against this list,
13
+ * which catches an emitter that emits an unlisted field — a real property, kept
14
+ * below. It cannot catch a field added to the *type*: a plant added
15
+ * `operator?: string` to `ReceiptLine`, emitted it on no line the fixture
16
+ * built, and all twelve checks passed. A field populated in one branch is
17
+ * exactly the shape a leak takes, and the published whitelist customers read to
18
+ * see what leaves their machine would have been wrong while its own guard said
19
+ * otherwise.
20
+ */
21
+ const RECEIPT_LINE_FIELD_MAP = {
22
+ label: true,
23
+ model: true,
24
+ calls: true,
25
+ inputTokens: true,
26
+ cacheReadTokens: true,
27
+ cacheWriteTokens: true,
28
+ cacheWrite5mTokens: true,
29
+ cacheWrite1hTokens: true,
30
+ outputTokens: true,
31
+ usd: true,
32
+ money: true,
33
+ pricing: true,
34
+ };
35
+ /**
36
+ * The same list, published.
37
+ *
38
+ * Derived from the map rather than written again, because writing it twice is
39
+ * the fault this exists to prevent. Key order is insertion order, so the array
40
+ * a consumer reads is unchanged.
9
41
  */
10
- export const RECEIPT_LINE_FIELDS = Object.freeze([
11
- 'label',
12
- 'model',
13
- 'calls',
14
- 'inputTokens',
15
- 'cacheReadTokens',
16
- 'cacheWriteTokens',
17
- 'cacheWrite5mTokens',
18
- 'cacheWrite1hTokens',
19
- 'outputTokens',
20
- 'usd',
21
- 'money',
22
- 'pricing',
23
- ]);
42
+ export const RECEIPT_LINE_FIELDS = Object.freeze(Object.keys(RECEIPT_LINE_FIELD_MAP));
24
43
  const lineFrom = (label, model, breakdown, catalogue) => {
25
44
  /*
26
45
  * Every slice reaching here is priced: `byLabelAndModel` holds only what the
@@ -1 +1 @@
1
- {"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,OAAO,EAAE,MAAM,YAAY,CAAC;AA6C1D,wEAAwE;AACxE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAqE/C;AAED,gDAAgD;AAChD,wBAAgB,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAEhD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAE5D"}
1
+ {"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,OAAO,EAAE,MAAM,YAAY,CAAC;AA2I1D,wEAAwE;AACxE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAqE/C;AAED,gDAAgD;AAChD,wBAAgB,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAEhD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAE5D"}
package/dist/segment.js CHANGED
@@ -2,8 +2,102 @@
2
2
  const PATTERNS = [
3
3
  // Fenced code blocks with ``` or ~~~ (unclosed blocks run to the end).
4
4
  { kind: 'fenced-code', regex: /(?:```|~~~)[\s\S]*?(?:```|~~~|$)/g },
5
- // Indented blocks of 4+ spaces are left to the rules: they are ambiguous in markdown.
5
+ /*
6
+ Indented code blocks, which this file used to leave to the rules on the
7
+ grounds that they are *"ambiguous in markdown"*. The ambiguity is real. The
8
+ consequence of acting on it was not proportionate to it.
9
+
10
+ **What the rules did to four lines of indented code**, at the aggressive
11
+ level, every one measured rather than imagined:
12
+
13
+ const label = "please keep"; -> Const label = " keep";
14
+ def run(x): -> Def run(x):
15
+ SELECT … WHERE note = 'please refund'; -> … = ' refund';
16
+ {"reason": "please cancel"} -> {"reason": " cancel"}
17
+
18
+ Three separate failures at once. The indentation goes, which by itself
19
+ makes the Python a syntax error. Keywords get sentence-capitalised, which
20
+ makes the rest of them syntax errors. And **string literals are edited** —
21
+ that SQL now matches a different value and that payload carries a different
22
+ reason. The report says tokens were saved.
23
+
24
+ The blank line before the block is what makes this checkable rather than a
25
+ guess: CommonMark says an indented code block cannot interrupt a paragraph,
26
+ so a run of indented lines after a blank line is code by the specification
27
+ rather than by anybody's judgement here.
28
+
29
+ **What it still gets wrong, stated rather than discovered later.** Content
30
+ indented four or more spaces inside a deeply nested list is continuation,
31
+ not code, and this protects it. That costs unsaved tokens in a rare shape.
32
+ Not protecting code costs a broken prompt in a common one, and those are not
33
+ the same kind of wrong.
34
+
35
+ Every quantifier is bounded or consumes a whole line exactly once, for the
36
+ reason the email mask below now carries in full.
37
+ */
38
+ {
39
+ kind: 'indented-code',
40
+ /*
41
+ **The lookbehind requires real content before the blank line, and the
42
+ fuzzer is why.** Its first version asked only for a blank line, which a
43
+ document *beginning* with one satisfies — and `optimize` ends with a
44
+ `.trim()` on the whole reassembled string, outside every mask, which then
45
+ removes the leading newlines and the first line's indentation. The block
46
+ stopped being a block, the second pass no longer protected it, and 54 of
47
+ 1,500 corpus inputs failed the idempotence property.
48
+
49
+ The trim is fixed at its source now — `optimize` trims the *masked* string,
50
+ where a protected span is a placeholder nothing can edit — so the mask can
51
+ also claim a block at the very start of a document. It has to: at the
52
+ aggressive level a rule can delete the sentence before the blank line, and
53
+ a block that was protected on the first pass has to still be protected on
54
+ the second or the output never settles.
55
+ */
56
+ regex: /(?<=\S[ \t]*\n[ \t]*\n)(?:(?: {4,}|\t)[^\n]*(?:\n|$))+/g,
57
+ },
6
58
  { kind: 'url', regex: /\b(?:https?|ftp):\/\/[^\s<>"')\]]+/g, trimTrailing: /[.,;:!?]+$/ },
59
+ /*
60
+ Email addresses, for the reason at the top of this file.
61
+
62
+ **Five of ten realistic addresses came out corrupted before this existed.**
63
+ `please@example.com` became `@example.com`, and so did `thanks@`,
64
+ `basically@`, `essentially.ops@` and `very.important@`: the politeness,
65
+ filler and intensifier rules match the local part as ordinary prose and cut
66
+ it out. What is left is not a wrong address, it is not an address — and the
67
+ report says tokens were saved.
68
+
69
+ That is the failure this module's own first paragraph names. A code block, a
70
+ URL and a placeholder were on the list; the thing every support prompt in
71
+ the world carries was not. `support@` and `no-reply@` survived, and
72
+ `por.favor@ejemplo.es` survived only because the Spanish politeness entry is
73
+ written with a space — luck, not design, which is what made it invisible.
74
+
75
+ After the URL pattern so a `mailto:` link is claimed as a URL first, and
76
+ trailing punctuation is trimmed for the same reason it is there: the full
77
+ stop in "write to a@b.com." belongs to the sentence.
78
+ */
79
+ {
80
+ kind: 'email',
81
+ /*
82
+ **Every quantifier here is bounded, and the first version of this was not.**
83
+ The local part accepts `.`, so on a long dotted run with no `@` in it —
84
+ a dotted run behind a scheme, which is a fixture in `security.test.js`
85
+ for exactly this reason — an unbounded `+` matches the whole run from every starting
86
+ position and then fails. Quadratic: **897ms on 40,008 characters**, and
87
+ `optimize` runs this alongside every other pattern, which took the total
88
+ past the 5-second cliff detector on a CI runner while passing on a faster
89
+ machine. That test's own note had already recorded the lesson from the
90
+ last time — *"quadratic until the label quantifiers were bounded"* — and
91
+ this pattern arrived a week later without them.
92
+
93
+ The bounds are RFC 5321's, not invented: 64 octets for a local part, 63
94
+ for a domain label. The TLD's 24 is generous against the longest real one
95
+ (`.travelersinsurance`, 19). Bounded, the same input takes **8ms**, and no
96
+ address in the corpus is lost.
97
+ */
98
+ regex: /\b[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9-]{1,63}(?:\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,24}/g,
99
+ trimTrailing: /[.,;:!?]+$/,
100
+ },
7
101
  // Template placeholders: {{var}}, {var}, ${var}, {% tag %}, <<VAR>>, %(var)s
8
102
  {
9
103
  kind: 'placeholder',
@@ -1 +1 @@
1
- {"version":3,"file":"segment.js","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"segment.js","sourceRoot":"","sources":["../src/segment.ts"],"names":[],"mappings":"AAqBA,gEAAgE;AAChE,MAAM,QAAQ,GAAiB;IAC7B,uEAAuE;IACvE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,mCAAmC,EAAE;IACnE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAgCE;IACF;QACE,IAAI,EAAE,eAAe;QACrB;;;;;;;;;;;;;;;UAeE;QACF,KAAK,EAAE,yDAAyD;KACjE;IACD,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,qCAAqC,EAAE,YAAY,EAAE,YAAY,EAAE;IACzF;;;;;;;;;;;;;;;;;;;MAmBE;IACF;QACE,IAAI,EAAE,OAAO;QACb;;;;;;;;;;;;;;;;UAgBE;QACF,KAAK,EAAE,uFAAuF;QAC9F,YAAY,EAAE,YAAY;KAC3B;IACD,6EAA6E;IAC7E;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EACH,0GAA0G;KAC7G;IACD,qBAAqB;IACrB,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAAE;IAC5C,oDAAoD;IACpD,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,yCAAyC,EAAE;CACtE,CAAC;AAQF,wEAAwE;AACxE,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC;;;;;;;;;;;;;;MAcE;IACF,MAAM,IAAI,GAAY,EAAE,CAAC;IAEzB,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,QAAQ,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC7D,MAAM,WAAW,GAAG,CAAC,KAAa,EAAE,GAAW,EAAqB,EAAE,CACpE,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,IAAI,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAEnE,2DAA2D;QAC3D,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAyB,CAAC;QAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACpC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,EAAE,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS;YACX,CAAC;YACD,IAAI,YAAY;gBAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;YAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YACnC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;YACtB,MAAM,GAAG,GAAG,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;YACnC,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YACpC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;gBACtB,sEAAsE;gBACtE,0DAA0D;gBAC1D,EAAE,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC5C,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,8DAA8D;IAC9D,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAEvC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE,KAAK,CAAC,IAAI;YACtB,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC;SACzC,CAAC,CAAC;QACH,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;IAClB,CAAC;IACD,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,IAAI,CAAC,QAAmB;IACtC,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,QAAmB;IAChD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC3E,CAAC"}
@@ -67,6 +67,33 @@ export interface ExampleAnalysis {
67
67
  /** Tokens held by examples that repeat an earlier one. */
68
68
  redundantTokens: number;
69
69
  }
70
+ /**
71
+ * **A shape this deliberately does not detect, and the reason is the point.**
72
+ *
73
+ * Inline mappings are everywhere — `1. "package never arrived" -> shipping`,
74
+ * `- "Acme raised a round" => Acme` — and on the probes that found the two
75
+ * faults above they are the remaining silent case. They are not added, because
76
+ * the only thing separating a demonstration from an instruction here is
77
+ * judgement: `if the ticket is about delivery -> use the shipping category` is
78
+ * the same line shape and is a rule, not an example.
79
+ *
80
+ * A detector that got that wrong would not merely report a wrong number.
81
+ * `prune` *removes* examples, so a rule misread as an example becomes a
82
+ * proposal to delete an instruction — and it would arrive with the same
83
+ * confidence as a correct one.
84
+ *
85
+ * The tempting narrowing is to require the left side to be quoted. It was
86
+ * considered and refused: both probes are quoted **because the author of the
87
+ * probes wrote them that way**, and a threshold fitted to one's own fixture is
88
+ * a threshold that has been measured against nothing. What would settle it is
89
+ * real prompts using the shape, not a rule invented here.
90
+ *
91
+ * Until then the honest behaviour is the one `prune` now has: say what was
92
+ * looked for, so a prompt this cannot read reports that it was not seen rather
93
+ * than that it does not exist. That sentence used to read "this prompt has
94
+ * fewer than two few-shot examples", which was a confident false statement
95
+ * about a prompt with four of them.
96
+ */
70
97
  /** Splits the prompt into labelled example blocks. */
71
98
  export declare function findExamples(prompt: string, count: TokenCounter): ExampleBlock[];
72
99
  /**
@@ -1 +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"}
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;AA6JD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CA6BlE;AAsHD;;;;;;;;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;AA0HD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,sDAAsD;AACtD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,YAAY,EAAE,CAchF;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;AAuJD;;;;;;;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"}
package/dist/structure.js CHANGED
@@ -9,6 +9,27 @@ import { jaccard, normalizeForCompare } from './similarity.js';
9
9
  * merely mentions a language, which is most translation prompts.
10
10
  */
11
11
  const RESPOND = String.raw `(?:respond|reply|answer|write|speak|output|responde|contesta|responder|escribe|redacta)`;
12
+ /**
13
+ * How a prompt refers to the reader having produced their message.
14
+ *
15
+ * `used|wrote|speaks` missed on a tense: *"respond in the same language the
16
+ * user writes in"* — present, which is how a standing instruction is normally
17
+ * written — was invisible, so a prompt pinning English and mirroring the user
18
+ * in the same breath reported no conflict at all.
19
+ */
20
+ const SPOKE = String.raw `(?:used?|uses|wrote|writes|writing|speaks|spoke|sent|asked)`;
21
+ /** Nouns that mean *the thing the model produces*, as opposed to any other text. */
22
+ const THE_ANSWER = String.raw `(?:answers?|responses?|repl(?:y|ies)|explanations?|respuestas?)`;
23
+ /**
24
+ * A numeric ceiling on length, without the unit.
25
+ *
26
+ * Kept separate because it is only ever safe next to an anchor: *"the title
27
+ * field takes at most five words"* is the same phrase about a form field, and
28
+ * reading it as an instruction about the answer is how a widening turns a
29
+ * detector into a nuisance.
30
+ */
31
+ const REQUIRE_SHORT = String.raw `keep\s+(?:it|them|${THE_ANSWER})\s+(?:short|brief|concise)`;
32
+ const AT_MOST = String.raw `(?:at\s+most|no\s+more\s+than|under|within|máximo\s+de?)\s+(?:one|two|three|four|five|\d+)\s+(?:sentences?|words?|frases?|palabras?)`;
12
33
  const AXES = [
13
34
  {
14
35
  axis: 'response-language',
@@ -23,7 +44,7 @@ const AXES = [
23
44
  // The qualifier slot ("own", "native", "preferred") is what makes
24
45
  // this survive real prompts: "the customer's own language" is at
25
46
  // least as common as the bare form.
26
- String.raw `\bin\s+the\s+(?:same\s+)?language\s+(?:of|as|the\s+\w+\s+(?:used|wrote|speaks))|\bin\s+(?:the\s+(?:user|customer|client)'?s?|their)\s+(?:own\s+|native\s+|preferred\s+)?language\b|\bmatch\s+the\s+language\b|\ben\s+(?:el\s+)?(?:mismo\s+)?idioma\s+(?:del?\s+|en\s+que\s+)?(?:usuario|cliente|consulta|mensaje|escrib)`, 'i'),
47
+ String.raw `\bin\s+the\s+(?:same\s+)?language\s+(?:of|as|the\s+\w+\s+(?:used|wrote|speaks))|\bin\s+(?:the\s+(?:user|customer|client)'?s?|their)\s+(?:own\s+|native\s+|preferred\s+)?language\b|\bmatch\s+the\s+language\b|\b${RESPOND}\b[^.!?\n]{0,40}?\bin\s+(?:the\s+(?:same\s+)?|whatever\s+|whichever\s+)language\s+(?:of|as|that\s+|the\s+\w+\s+)?\w*\s*${SPOKE}|\ben\s+(?:el\s+)?(?:mismo\s+)?idioma\s+(?:del?\s+|en\s+que\s+)?(?:usuario|cliente|consulta|mensaje|escrib)`, 'i'),
27
48
  ],
28
49
  ],
29
50
  },
@@ -49,11 +70,11 @@ const AXES = [
49
70
  values: [
50
71
  [
51
72
  'length-brief',
52
- new RegExp(String.raw `\b(?:be\s+(?:brief|concise|succinct|terse)|keep\s+it\s+(?:short|brief)|as\s+short\s+as\s+possible|in\s+(?:one|a\s+single)\s+sentence|s[ée]\s+(?:breve|conciso)|de\s+forma\s+(?:breve|concisa)|brevemente)\b`, 'i'),
73
+ new RegExp(String.raw `\b(?:be\s+(?:brief|concise|succinct|terse)|keep\s+it\s+(?:short|brief)|as\s+short\s+as\s+possible|${REQUIRE_SHORT}|\b${RESPOND}\b[^.!?\n]{0,40}?${AT_MOST}|in\s+(?:one|a\s+single)\s+sentence|s[ée]\s+(?:breve|conciso)|de\s+forma\s+(?:breve|concisa)|brevemente)\b`, 'i'),
53
74
  ],
54
75
  [
55
76
  'length-detailed',
56
- new RegExp(String.raw `\b(?:be\s+(?:detailed|comprehensive|thorough|exhaustive)|in\s+(?:great\s+)?depth|as\s+much\s+detail\s+as\s+possible|s[ée]\s+(?:exhaustivo|detallado)|de\s+forma\s+(?:detallada|exhaustiva)|con\s+todo\s+detalle|detalladamente)\b`, 'i'),
77
+ new RegExp(String.raw `\b(?:be\s+(?:detailed|comprehensive|thorough|exhaustive)|in\s+(?:great\s+)?depth|(?:detailed|comprehensive|thorough|exhaustive|in-depth)[^.!?\n]{0,30}?\b${THE_ANSWER}\b|as\s+much\s+detail\s+as\s+possible|s[ée]\s+(?:exhaustivo|detallado)|de\s+forma\s+(?:detallada|exhaustiva)|con\s+todo\s+detalle|detalladamente)\b`, 'i'),
57
78
  ],
58
79
  ],
59
80
  },
@@ -62,11 +83,11 @@ const AXES = [
62
83
  values: [
63
84
  [
64
85
  'reasoning-shown',
65
- new RegExp(String.raw `\b(?:explain\s+your\s+(?:reasoning|answer|thinking)|show\s+your\s+(?:work|reasoning|thinking)|think\s+step[\s-]by[\s-]step|justify\s+your\s+answer|explica\s+tu\s+razonamiento|razona\s+paso\s+a\s+paso|justifica\s+tu\s+respuesta)\b`, 'i'),
86
+ new RegExp(String.raw `\b(?:explain\s+your\s+(?:reasoning|answer|thinking)|show\s+your\s+(?:work|reasoning|thinking)|show\s+your\s+chain\s+of\s+thought|walk\s+(?:me\s+)?through\s+your\s+(?:reasoning|thinking|logic)|think\s+step[\s-]by[\s-]step|justify\s+your\s+answer|explica\s+tu\s+razonamiento|razona\s+paso\s+a\s+paso|justifica\s+tu\s+respuesta)\b`, 'i'),
66
87
  ],
67
88
  [
68
89
  'reasoning-hidden',
69
- new RegExp(String.raw `\b(?:no\s+(?:explanation|commentary|preamble)|without\s+(?:explanation|commentary)|do\s+not\s+explain|don'?t\s+explain|no\s+expliques|sin\s+(?:explicaciones|comentarios)|sin\s+preámbulo)\b`, 'i'),
90
+ new RegExp(String.raw `\b(?:no\s+(?:explanation|commentary|preamble)|without\s+(?:explanation|commentary|justification|reasoning)|no\s+justification|do\s+not\s+explain|don'?t\s+explain|no\s+expliques|sin\s+(?:explicaciones|comentarios)|sin\s+preámbulo)\b`, 'i'),
70
91
  ],
71
92
  ],
72
93
  },
@@ -168,10 +189,79 @@ const LABEL_SUFFIX = String.raw `[ \t]{0,4}\d{0,6}[ \t]{0,4}[:.)]`;
168
189
  * indistinguishable from ordinary prose, and guessing would make the advisory
169
190
  * fire on prompts containing no examples at all.
170
191
  */
192
+ /**
193
+ * The label on the side that **asks**, in the wordings prompts actually use.
194
+ *
195
+ * **This list was three entries long and it made six analyses blind.** A
196
+ * support prompt labelled `Customer:` / `Agent:` — the single most common
197
+ * shape there is — found zero examples, so `prune` had nothing to cut,
198
+ * `profile` reported the examples as costing nothing, and the redundant-example
199
+ * advisory never fired. Measured across fourteen labellings real prompts use,
200
+ * nine found nothing: `Customer`, `Human`, `Question`, `Client`, `Prompt`,
201
+ * `Query`, `Cliente`, `Pregunta`, `REQUEST`. `Human:` is Anthropic's own
202
+ * historical convention and `Q:` worked while `Question:` did not, which is
203
+ * the arbitrariness that gives the fault away.
204
+ *
205
+ * The failure was silent, which is what made it survive: an unrecognised label
206
+ * produces no examples, and no examples produces no finding — the same shape
207
+ * as a gate that passes because it is checking nothing.
208
+ *
209
+ * Only openers belong here. `Answer:` and `Assistant:` close a turn, and
210
+ * splitting on those would cut every example between its question and its
211
+ * answer and then compare the halves.
212
+ */
213
+ const ASKER_LABELS = [
214
+ 'user',
215
+ 'usuario',
216
+ 'human',
217
+ 'humano',
218
+ 'customer',
219
+ 'cliente',
220
+ 'client',
221
+ 'q',
222
+ 'question',
223
+ 'pregunta',
224
+ 'query',
225
+ 'consulta',
226
+ 'request',
227
+ 'petición',
228
+ 'peticion',
229
+ 'prompt',
230
+ ];
231
+ /**
232
+ * The label on the side that **answers**.
233
+ *
234
+ * Never a tier: these appear inside an example rather than opening one. They
235
+ * are here so `EXAMPLE_FIELD_LINE` can be built from the same two lists the
236
+ * splitter uses, which is what stops the pair drifting apart again — the field
237
+ * detector already knew `question` and `answer` while the splitter did not,
238
+ * and that disagreement is precisely how `Q:` came to work and `Question:` not.
239
+ */
240
+ const ANSWERER_LABELS = [
241
+ 'assistant',
242
+ 'asistente',
243
+ 'a',
244
+ 'answer',
245
+ 'respuesta',
246
+ 'output',
247
+ 'salida',
248
+ 'response',
249
+ 'reply',
250
+ 'agent',
251
+ 'agente',
252
+ 'support',
253
+ 'soporte',
254
+ 'bot',
255
+ 'result',
256
+ 'resultado',
257
+ ];
258
+ /** Labels that name an example as a whole, rather than one side of it. */
259
+ const BLOCK_LABELS = ['example', 'ejemplo', 'input', 'entrada'];
260
+ const anyOf = (labels) => labels.join('|');
171
261
  const EXAMPLE_HEADER_TIERS = [
172
262
  new RegExp(`${LABEL_PREFIX}(?:example|ejemplo)${LABEL_SUFFIX}`, 'i'),
173
263
  new RegExp(`${LABEL_PREFIX}(?:input|entrada)${LABEL_SUFFIX}`, 'i'),
174
- new RegExp(`${LABEL_PREFIX}(?:user|usuario|q)${LABEL_SUFFIX}`, 'i'),
264
+ new RegExp(`${LABEL_PREFIX}(?:${anyOf(ASKER_LABELS)})${LABEL_SUFFIX}`, 'i'),
175
265
  ];
176
266
  /**
177
267
  * A labelled field inside a few-shot example — `Input:`, `Output:`, `A:`…
@@ -182,7 +272,7 @@ const EXAMPLE_HEADER_TIERS = [
182
272
  * those lines leaves an example with no output at all — worse than the
183
273
  * repetition it removed.
184
274
  */
185
- export const EXAMPLE_FIELD_LINE = new RegExp(`${LABEL_PREFIX}(?:input|output|entrada|salida|example|ejemplo|user|usuario|assistant|asistente|q|a|question|answer|pregunta|respuesta)${LABEL_SUFFIX}`, 'i');
275
+ export const EXAMPLE_FIELD_LINE = new RegExp(`${LABEL_PREFIX}(?:${anyOf([...BLOCK_LABELS, ...ASKER_LABELS, ...ANSWERER_LABELS])})${LABEL_SUFFIX}`, 'i');
186
276
  /**
187
277
  * Similarity at which two examples are treated as repeating each other.
188
278
  *
@@ -201,6 +291,30 @@ export const EXAMPLE_FIELD_LINE = new RegExp(`${LABEL_PREFIX}(?:input|output|ent
201
291
  * "arrived fast" teach the same thing needs a model, not a word-set overlap;
202
292
  * that belongs to the optional LLM pass, not here.
203
293
  */
294
+ /**
295
+ * How alike two examples must be before one is called redundant.
296
+ *
297
+ * **Do not lower this hoping to catch paraphrases: it was measured and it does
298
+ * not work.** Four support examples whose answers were *"Let me look that up
299
+ * for you. Could you share your order number?"*, *"I can check that for you.
300
+ * What's your order number?"*, *"Happy to help. May I have your order
301
+ * number?"* and *"Of course. What is your order number?"* are one example
302
+ * wearing four coats — any reader says so instantly. Their word overlap is
303
+ * 0.29–0.43, and comparing only the answers makes it **worse**, 0.21–0.39,
304
+ * because the shared content is three words (`your`, `order`, `number`) while
305
+ * the openers are all different. There is no threshold that separates those
306
+ * from genuinely different examples: at 0.4 this fires on every prompt whose
307
+ * examples share a domain vocabulary.
308
+ *
309
+ * That similarity is a semantic judgement, and this repository has a place for
310
+ * those — `trazum semantic`, which says what it costs and asks first. What
311
+ * word overlap can honestly find is a paragraph somebody pasted twice and
312
+ * edited lightly, which is what 0.7 is for.
313
+ *
314
+ * So the deterministic half reports **what the examples cost**, which is a
315
+ * fact, and leaves *whether one should go* to `prune`, which answers it by
316
+ * running the cases rather than by counting words.
317
+ */
204
318
  const EXAMPLE_SIMILARITY = 0.7;
205
319
  /** Splits the prompt on one header style. */
206
320
  function splitOnHeader(lines, header) {
@@ -245,9 +359,67 @@ function trimToExample(block) {
245
359
  }
246
360
  return kept.join('\n\n').trim();
247
361
  }
362
+ /**
363
+ * Examples delimited by an `<example>` tag rather than by a label.
364
+ *
365
+ * **This is Anthropic's own documented convention for few-shot prompting**, and
366
+ * the product whose headline model is Claude could not see it: a code-review
367
+ * prompt wrapping three demonstrations in `<example>` found zero examples, and
368
+ * the same six analyses went quiet that `Customer:` had silenced. A shape a
369
+ * provider tells people to use is not an edge case.
370
+ *
371
+ * Tried before the label tiers because it is the least ambiguous thing in this
372
+ * file. A label is a word that might be prose; `<example>` is a delimiter, so
373
+ * there is no threshold and no judgement — either the tag is there or it is not.
374
+ *
375
+ * Only `example` and `examples`, which are what the documentation names.
376
+ * `<sample>` and `<demonstration>` appear in the wild and are not added: the
377
+ * list would then be a guess about what people might write, and this splitter
378
+ * has already been burnt once by a vocabulary somebody thought was wide enough.
379
+ * A tag arriving here should arrive with a prompt that uses it.
380
+ *
381
+ * A tag inside a fenced block is documentation about the tag rather than a use
382
+ * of it — a prompt explaining how to write examples is not a prompt containing
383
+ * them — so fenced regions are removed before matching.
384
+ */
385
+ function xmlExamples(prompt) {
386
+ const outsideFences = prompt.replace(/^([ \t]{0,3})(`{3,}|~{3,})[\s\S]*?^\1\2[ \t]*$/gm, '');
387
+ const tag = /<example(?:\s[^>]*)?>([\s\S]*?)<\/example>/gi;
388
+ return [...outsideFences.matchAll(tag)].map(([, body]) => (body ?? '').trim()).filter(Boolean);
389
+ }
390
+ /**
391
+ * **A shape this deliberately does not detect, and the reason is the point.**
392
+ *
393
+ * Inline mappings are everywhere — `1. "package never arrived" -> shipping`,
394
+ * `- "Acme raised a round" => Acme` — and on the probes that found the two
395
+ * faults above they are the remaining silent case. They are not added, because
396
+ * the only thing separating a demonstration from an instruction here is
397
+ * judgement: `if the ticket is about delivery -> use the shipping category` is
398
+ * the same line shape and is a rule, not an example.
399
+ *
400
+ * A detector that got that wrong would not merely report a wrong number.
401
+ * `prune` *removes* examples, so a rule misread as an example becomes a
402
+ * proposal to delete an instruction — and it would arrive with the same
403
+ * confidence as a correct one.
404
+ *
405
+ * The tempting narrowing is to require the left side to be quoted. It was
406
+ * considered and refused: both probes are quoted **because the author of the
407
+ * probes wrote them that way**, and a threshold fitted to one's own fixture is
408
+ * a threshold that has been measured against nothing. What would settle it is
409
+ * real prompts using the shape, not a rule invented here.
410
+ *
411
+ * Until then the honest behaviour is the one `prune` now has: say what was
412
+ * looked for, so a prompt this cannot read reports that it was not seen rather
413
+ * than that it does not exist. That sentence used to read "this prompt has
414
+ * fewer than two few-shot examples", which was a confident false statement
415
+ * about a prompt with four of them.
416
+ */
248
417
  /** Splits the prompt into labelled example blocks. */
249
418
  export function findExamples(prompt, count) {
250
419
  const lines = prompt.split('\n');
420
+ const tagged = xmlExamples(prompt);
421
+ if (tagged.length >= 2)
422
+ return tagged.map((text) => ({ text, tokens: count(text) }));
251
423
  for (const header of EXAMPLE_HEADER_TIERS) {
252
424
  const blocks = splitOnHeader(lines, header);
253
425
  if (blocks.length >= 2) {
@@ -306,6 +478,102 @@ function fencedBlocks(prompt) {
306
478
  * prompts worth checking. Only keys at nesting depth 1 count, so a nested
307
479
  * field name cannot be mistaken for a top-level one.
308
480
  */
481
+ /**
482
+ * Fence languages this reads a schema out of.
483
+ *
484
+ * `ts` and `typescript` are new; the rest were already here and three of them
485
+ * did not work. See `unquotedKeys`.
486
+ */
487
+ const SCHEMA_LANGS = /^(?:json|jsonc|json5|yaml|yml|ts|typescript)$/;
488
+ /**
489
+ * Keys written without quotes, which is most of them.
490
+ *
491
+ * **The filter above already admitted `yaml`, `yml` and `json5`, and the
492
+ * extractor could not read any of them.** It only accepted a quoted string
493
+ * followed by a colon, which is JSON and nothing else — so a prompt showing its
494
+ * schema as YAML matched a language this module claims to support, produced
495
+ * zero keys, and reported no restatement however thoroughly the prose walked
496
+ * the fields. Not a missing feature: a promise made in one expression and
497
+ * broken by the next.
498
+ *
499
+ * `ts` is added at the same time because it is the same omission wearing a
500
+ * different label — `interface Ticket { category: string }` is how a
501
+ * TypeScript codebase writes the structured output it wants back, and it was
502
+ * not on the list at all.
503
+ *
504
+ * Two shapes, because YAML has no braces:
505
+ *
506
+ * - **Braced** (`json5`, `jsonc`, `ts`): an identifier at depth 1 followed by
507
+ * `:` or `?:`. Depth is what keeps a nested object's fields out.
508
+ * - **Indented** (`yaml`, `yml`): an identifier at the block's own outermost
509
+ * indent, followed by `:`. Anything indented past it belongs to a parent key
510
+ * and is not top-level.
511
+ *
512
+ * Deliberately not applied to a fence with **no** language. An unlabelled block
513
+ * is as likely to be a shell session or a log line as a schema, and
514
+ * `Note: something` reads as a key to any rule this simple. The quoted form
515
+ * stays the only thing read there, exactly as before.
516
+ */
517
+ function unquotedKeys(body, lang) {
518
+ if (!SCHEMA_LANGS.test(lang))
519
+ return [];
520
+ if (/^(?:yaml|yml)$/.test(lang)) {
521
+ const lines = body
522
+ .split('\n')
523
+ .filter((line) => line.trim() !== '' && !/^\s*#/.test(line));
524
+ const outermost = Math.min(...lines.map((line) => line.length - line.trimStart().length));
525
+ if (!Number.isFinite(outermost))
526
+ return [];
527
+ return [
528
+ ...new Set(lines
529
+ .filter((line) => line.length - line.trimStart().length === outermost)
530
+ .map((line) => /^\s*([A-Za-z_][\w-]*)\s*:/.exec(line))
531
+ .filter((found) => found !== null)
532
+ .map((found) => found[1])),
533
+ ];
534
+ }
535
+ /*
536
+ Character by character rather than line by line, and that was found by
537
+ running it: `interface Ticket { category: string; urgency: number }` is one
538
+ line, so a line-oriented walk sees depth 0 where it starts and reads nothing
539
+ at all. Single-line objects are the common case in a `ts` fence and in the
540
+ JSON5 people paste, not the exception.
541
+ */
542
+ const keys = [];
543
+ let depth = 0;
544
+ let candidate = '';
545
+ for (let i = 0; i < body.length; i++) {
546
+ const ch = body[i];
547
+ if (ch === '{' || ch === '[') {
548
+ depth++;
549
+ candidate = '';
550
+ continue;
551
+ }
552
+ if (ch === '}' || ch === ']') {
553
+ depth--;
554
+ candidate = '';
555
+ continue;
556
+ }
557
+ if (ch === ',' || ch === ';' || ch === '\n') {
558
+ candidate = '';
559
+ continue;
560
+ }
561
+ if (/[A-Za-z0-9_-]/.test(ch)) {
562
+ candidate += ch;
563
+ continue;
564
+ }
565
+ if (ch === ':' || (ch === '?' && body[i + 1] === ':')) {
566
+ if (depth === 1 && /^[A-Za-z_][\w-]*$/.test(candidate))
567
+ keys.push(candidate);
568
+ candidate = '';
569
+ continue;
570
+ }
571
+ // Whitespace between a name and its colon is fine; anything else is not a key.
572
+ if (!/\s/.test(ch))
573
+ candidate = '';
574
+ }
575
+ return [...new Set(keys)];
576
+ }
309
577
  function topLevelKeys(body) {
310
578
  const keys = [];
311
579
  let depth = 0;
@@ -360,8 +628,8 @@ export function findRestatedFormat(prompt, count) {
360
628
  const blocks = fencedBlocks(prompt);
361
629
  const keys = [
362
630
  ...new Set(blocks
363
- .filter((b) => b.lang === '' || /json|jsonc|json5|yaml|yml/.test(b.lang))
364
- .flatMap((b) => topLevelKeys(b.body))),
631
+ .filter((b) => b.lang === '' || SCHEMA_LANGS.test(b.lang))
632
+ .flatMap((b) => [...topLevelKeys(b.body), ...unquotedKeys(b.body, b.lang)])),
365
633
  ].filter((k) => k.length >= 3);
366
634
  if (keys.length < RESTATED_KEY_MINIMUM)
367
635
  return null;