@prompt-lint/rules 1.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.
Files changed (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -0
  3. package/dist/convention/filename-naming.d.ts +16 -0
  4. package/dist/convention/filename-naming.d.ts.map +1 -0
  5. package/dist/convention/filename-naming.js +50 -0
  6. package/dist/convention/filename-naming.js.map +1 -0
  7. package/dist/cost/high-token-estimate.d.ts +4 -0
  8. package/dist/cost/high-token-estimate.d.ts.map +1 -0
  9. package/dist/cost/high-token-estimate.js +51 -0
  10. package/dist/cost/high-token-estimate.js.map +1 -0
  11. package/dist/generated/manifest.d.ts +19 -0
  12. package/dist/generated/manifest.d.ts.map +1 -0
  13. package/dist/generated/manifest.js +98 -0
  14. package/dist/generated/manifest.js.map +1 -0
  15. package/dist/helpers/filename.d.ts +19 -0
  16. package/dist/helpers/filename.d.ts.map +1 -0
  17. package/dist/helpers/filename.js +59 -0
  18. package/dist/helpers/filename.js.map +1 -0
  19. package/dist/helpers/index.d.ts +16 -0
  20. package/dist/helpers/index.d.ts.map +1 -0
  21. package/dist/helpers/index.js +16 -0
  22. package/dist/helpers/index.js.map +1 -0
  23. package/dist/helpers/instruction-override.d.ts +28 -0
  24. package/dist/helpers/instruction-override.d.ts.map +1 -0
  25. package/dist/helpers/instruction-override.js +43 -0
  26. package/dist/helpers/instruction-override.js.map +1 -0
  27. package/dist/helpers/pii.d.ts +31 -0
  28. package/dist/helpers/pii.d.ts.map +1 -0
  29. package/dist/helpers/pii.js +36 -0
  30. package/dist/helpers/pii.js.map +1 -0
  31. package/dist/helpers/regex.d.ts +53 -0
  32. package/dist/helpers/regex.d.ts.map +1 -0
  33. package/dist/helpers/regex.js +79 -0
  34. package/dist/helpers/regex.js.map +1 -0
  35. package/dist/helpers/structured-data.d.ts +19 -0
  36. package/dist/helpers/structured-data.d.ts.map +1 -0
  37. package/dist/helpers/structured-data.js +72 -0
  38. package/dist/helpers/structured-data.js.map +1 -0
  39. package/dist/helpers/tokens.d.ts +26 -0
  40. package/dist/helpers/tokens.d.ts.map +1 -0
  41. package/dist/helpers/tokens.js +63 -0
  42. package/dist/helpers/tokens.js.map +1 -0
  43. package/dist/helpers/vague-quantifiers.d.ts +20 -0
  44. package/dist/helpers/vague-quantifiers.d.ts.map +1 -0
  45. package/dist/helpers/vague-quantifiers.js +83 -0
  46. package/dist/helpers/vague-quantifiers.js.map +1 -0
  47. package/dist/helpers/variables.d.ts +36 -0
  48. package/dist/helpers/variables.d.ts.map +1 -0
  49. package/dist/helpers/variables.js +66 -0
  50. package/dist/helpers/variables.js.map +1 -0
  51. package/dist/index.d.ts +62 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +80 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/quality/missing-output-schema.d.ts +15 -0
  56. package/dist/quality/missing-output-schema.d.ts.map +1 -0
  57. package/dist/quality/missing-output-schema.js +39 -0
  58. package/dist/quality/missing-output-schema.js.map +1 -0
  59. package/dist/quality/vague-quantifier-language.d.ts +13 -0
  60. package/dist/quality/vague-quantifier-language.d.ts.map +1 -0
  61. package/dist/quality/vague-quantifier-language.js +34 -0
  62. package/dist/quality/vague-quantifier-language.js.map +1 -0
  63. package/dist/security/instruction-override-pattern.d.ts +17 -0
  64. package/dist/security/instruction-override-pattern.d.ts.map +1 -0
  65. package/dist/security/instruction-override-pattern.js +39 -0
  66. package/dist/security/instruction-override-pattern.js.map +1 -0
  67. package/dist/security/pii-pattern.d.ts +18 -0
  68. package/dist/security/pii-pattern.d.ts.map +1 -0
  69. package/dist/security/pii-pattern.js +56 -0
  70. package/dist/security/pii-pattern.js.map +1 -0
  71. package/dist/structure/missing-description.d.ts +14 -0
  72. package/dist/structure/missing-description.d.ts.map +1 -0
  73. package/dist/structure/missing-description.js +32 -0
  74. package/dist/structure/missing-description.js.map +1 -0
  75. package/dist/structure/missing-model.d.ts +16 -0
  76. package/dist/structure/missing-model.d.ts.map +1 -0
  77. package/dist/structure/missing-model.js +34 -0
  78. package/dist/structure/missing-model.js.map +1 -0
  79. package/dist/structure/undefined-variable.d.ts +15 -0
  80. package/dist/structure/undefined-variable.d.ts.map +1 -0
  81. package/dist/structure/undefined-variable.js +39 -0
  82. package/dist/structure/undefined-variable.js.map +1 -0
  83. package/dist/structure/unused-variable.d.ts +15 -0
  84. package/dist/structure/unused-variable.d.ts.map +1 -0
  85. package/dist/structure/unused-variable.js +38 -0
  86. package/dist/structure/unused-variable.js.map +1 -0
  87. package/dist/test-helpers.d.ts +40 -0
  88. package/dist/test-helpers.d.ts.map +1 -0
  89. package/dist/test-helpers.js +52 -0
  90. package/dist/test-helpers.js.map +1 -0
  91. package/package.json +42 -0
@@ -0,0 +1,36 @@
1
+ import { findAllMatches } from "./regex.js";
2
+ /**
3
+ * Patterns for detecting personally-identifiable information (PII) inside
4
+ * prompt bodies. The rule fires once per **unique kind** of detection
5
+ * (e.g. one `email`, one `phone`) so the configured severity is not
6
+ * multiplied by repetition in tests.
7
+ *
8
+ * Each pattern uses non-greedy matching with unicode-aware boundaries.
9
+ * URLs containing PII (e.g. `mailto:` links) intentionally still match
10
+ * because the rule targets raw payload bytes, not transport.
11
+ *
12
+ * Each pattern is exposed via {@link PII_PATTERNS} so reporters can
13
+ * surface the matched pattern type in the message.
14
+ */
15
+ export const PII_PATTERNS = {
16
+ email: /[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/gu,
17
+ phone: /(?:\+?\d{1,3}[\s.\-]?)?\(?\d{3}\)?[\s.\-]?\d{3}[\s.\-]?\d{4}/g,
18
+ ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
19
+ creditCard: /\b(?:\d[ -]?){13,16}\b/g,
20
+ };
21
+ /**
22
+ * Detect all PII matches in `body`. Returns one entry per pattern kind.
23
+ * Duplicate matches of the same kind are kept so the rule implementation
24
+ * can decide whether to dedupe.
25
+ */
26
+ export function detectPiiMatches(body) {
27
+ const matches = [];
28
+ for (const kind of Object.keys(PII_PATTERNS)) {
29
+ const pattern = PII_PATTERNS[kind];
30
+ for (const m of findAllMatches(pattern, body)) {
31
+ matches.push({ ...m, kind });
32
+ }
33
+ }
34
+ return matches;
35
+ }
36
+ //# sourceMappingURL=pii.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.js","sourceRoot":"","sources":["../../src/helpers/pii.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,KAAK,EAAE,oDAAoD;IAC3D,KAAK,EAAE,+DAA+D;IACtE,GAAG,EAAE,wBAAwB;IAC7B,UAAU,EAAE,yBAAyB;CAC7B,CAAA;AAQV;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,OAAO,GAAe,EAAE,CAAA;IAC9B,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAc,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;QAClC,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9B,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,53 @@
1
+ import type { SourceLocation } from "@prompt-lint/types";
2
+ /**
3
+ * Lightweight regex utilities used by security and quality rules. Each
4
+ * helper is deterministic and rejects multi-line scans to keep the
5
+ * resulting locations accurate.
6
+ *
7
+ * Source-location computation uses the same 1-indexed line/column
8
+ * convention as `@prompt-lint/parser`.
9
+ */
10
+ export interface RegexMatch {
11
+ readonly start: number;
12
+ readonly end: number;
13
+ readonly text: string;
14
+ readonly location: SourceLocation;
15
+ }
16
+ /**
17
+ * Build a fresh global regex from `pattern` and `flags`. This helper
18
+ * guarantees a new `RegExp` instance every time so callers never hold
19
+ * onto a shared `lastIndex` cursor.
20
+ */
21
+ export declare function compileWordPattern(pattern: string, flags?: string): RegExp;
22
+ /**
23
+ * Build a whole-word-aware regex from the provided identifier list. The
24
+ * match is wrapped with `\b` word boundaries using ASCII-only matching
25
+ * (`\b` ASCII implementation) and uses the unicode flag so non-ASCII
26
+ * identifiers can be detected when callers pass them.
27
+ *
28
+ * The pattern is anchored loosely: callers decide whether to wrap the
29
+ * result with `^` or `$`.
30
+ */
31
+ export declare function compileWordRegex(words: readonly string[], flags?: string): RegExp;
32
+ /**
33
+ * Escape a literal string for safe inclusion in a `RegExp` pattern.
34
+ */
35
+ export declare function escapeRegex(value: string): string;
36
+ /**
37
+ * Run a regex against `body` and emit deterministic
38
+ * `{ start, end, text, location }` matches in source order.
39
+ *
40
+ * If the supplied regex does not carry the `g` flag, this helper wraps
41
+ * a clone that does. Use this for every "find all" path so callers do
42
+ * not have to remember the global requirement of `String.matchAll`.
43
+ */
44
+ export declare function findAllMatches(regex: RegExp, body: string): readonly RegexMatch[];
45
+ /**
46
+ * Compute the 1-indexed `SourceLocation` covering `body[start..end)`.
47
+ *
48
+ * If `body` ends with a newline at `end`, the returned `endColumn` is
49
+ * `1` — the start of the next line — to match the LSP convention used
50
+ * by `@prompt-lint/parser`.
51
+ */
52
+ export declare function locationForRange(body: string, start: number, end: number): SourceLocation;
53
+ //# sourceMappingURL=regex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex.d.ts","sourceRoot":"","sources":["../../src/helpers/regex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD;;;;;;;GAOG;AAEH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAO,GAAG,MAAM,CAExE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,SAAO,GAAG,MAAM,CAK/E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,UAAU,EAAE,CAiBjF;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,cAAc,CAgBzF"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Build a fresh global regex from `pattern` and `flags`. This helper
3
+ * guarantees a new `RegExp` instance every time so callers never hold
4
+ * onto a shared `lastIndex` cursor.
5
+ */
6
+ export function compileWordPattern(pattern, flags = "gu") {
7
+ return new RegExp(pattern, flags);
8
+ }
9
+ /**
10
+ * Build a whole-word-aware regex from the provided identifier list. The
11
+ * match is wrapped with `\b` word boundaries using ASCII-only matching
12
+ * (`\b` ASCII implementation) and uses the unicode flag so non-ASCII
13
+ * identifiers can be detected when callers pass them.
14
+ *
15
+ * The pattern is anchored loosely: callers decide whether to wrap the
16
+ * result with `^` or `$`.
17
+ */
18
+ export function compileWordRegex(words, flags = "gu") {
19
+ const sorted = [...words].sort((a, b) => b.length - a.length);
20
+ const escaped = sorted.map(escapeRegex);
21
+ const body = escaped.map((w) => `\\b${w}\\b`).join("|");
22
+ return new RegExp(body, flags);
23
+ }
24
+ /**
25
+ * Escape a literal string for safe inclusion in a `RegExp` pattern.
26
+ */
27
+ export function escapeRegex(value) {
28
+ return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
29
+ }
30
+ /**
31
+ * Run a regex against `body` and emit deterministic
32
+ * `{ start, end, text, location }` matches in source order.
33
+ *
34
+ * If the supplied regex does not carry the `g` flag, this helper wraps
35
+ * a clone that does. Use this for every "find all" path so callers do
36
+ * not have to remember the global requirement of `String.matchAll`.
37
+ */
38
+ export function findAllMatches(regex, body) {
39
+ const flags = regex.flags.includes("g") ? regex.flags : `${regex.flags}g`;
40
+ const pattern = new RegExp(regex.source, flags);
41
+ pattern.lastIndex = 0;
42
+ const out = [];
43
+ for (const match of body.matchAll(pattern)) {
44
+ const text = match[0] ?? "";
45
+ const start = match.index ?? 0;
46
+ const end = start + text.length;
47
+ out.push({
48
+ start,
49
+ end,
50
+ text,
51
+ location: locationForRange(body, start, end),
52
+ });
53
+ }
54
+ return out;
55
+ }
56
+ /**
57
+ * Compute the 1-indexed `SourceLocation` covering `body[start..end)`.
58
+ *
59
+ * If `body` ends with a newline at `end`, the returned `endColumn` is
60
+ * `1` — the start of the next line — to match the LSP convention used
61
+ * by `@prompt-lint/parser`.
62
+ */
63
+ export function locationForRange(body, start, end) {
64
+ const before = body.slice(0, start);
65
+ const beforeLines = before.split("\n");
66
+ const startLine = beforeLines.length;
67
+ const startColumn = (beforeLines[beforeLines.length - 1]?.length ?? 0) + 1;
68
+ const span = body.slice(start, end);
69
+ const spanLines = span.split("\n");
70
+ const endLine = startLine + spanLines.length - 1;
71
+ let endColumn = spanLines[spanLines.length - 1]?.length ?? 0;
72
+ if (span.includes("\n") && endColumn === 0) {
73
+ endColumn = 1;
74
+ }
75
+ if (endColumn === 0)
76
+ endColumn = 1;
77
+ return { line: startLine, column: startColumn, endLine, endColumn };
78
+ }
79
+ //# sourceMappingURL=regex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex.js","sourceRoot":"","sources":["../../src/helpers/regex.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Phrase vocabulary used to detect whether a prompt requests structured
3
+ * output (JSON, YAML, tables, etc.) without a corresponding schema
4
+ * declaration in frontmatter.
5
+ *
6
+ * The check is intentionally permissive: any natural-language signal
7
+ * that the user expects a structured response triggers the rule. Only
8
+ * the rule implementation decides whether the prompt qualifies for a
9
+ * finding — see {@link hasStructuredDataKeyword}.
10
+ */
11
+ export declare const STRUCTURED_DATA_KEYWORDS: readonly string[];
12
+ /**
13
+ * Return `true` if `body` contains any signal that the prompt
14
+ * requests structured output. Matching is case-insensitive and
15
+ * uses ASCII word boundaries; multi-word phrases are matched as a
16
+ * longest-first sequence.
17
+ */
18
+ export declare function hasStructuredDataKeyword(body: string): boolean;
19
+ //# sourceMappingURL=structured-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-data.d.ts","sourceRoot":"","sources":["../../src/helpers/structured-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EA0CpD,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAW9D"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Phrase vocabulary used to detect whether a prompt requests structured
3
+ * output (JSON, YAML, tables, etc.) without a corresponding schema
4
+ * declaration in frontmatter.
5
+ *
6
+ * The check is intentionally permissive: any natural-language signal
7
+ * that the user expects a structured response triggers the rule. Only
8
+ * the rule implementation decides whether the prompt qualifies for a
9
+ * finding — see {@link hasStructuredDataKeyword}.
10
+ */
11
+ export const STRUCTURED_DATA_KEYWORDS = Object.freeze([
12
+ "json",
13
+ "json object",
14
+ "json array",
15
+ "json schema",
16
+ "json-schema",
17
+ "yaml",
18
+ "toml",
19
+ "csv",
20
+ "table",
21
+ "markdown table",
22
+ "structured output",
23
+ "structured response",
24
+ "structured format",
25
+ "structured form",
26
+ "structured data",
27
+ "with fields",
28
+ "as an object",
29
+ "as a json",
30
+ "as json",
31
+ "as a csv",
32
+ "as csv",
33
+ "as an array",
34
+ "as a list",
35
+ "as a table",
36
+ "list of",
37
+ "array of",
38
+ "schema",
39
+ "jsonobject",
40
+ "jsonresponse",
41
+ "respond with json",
42
+ "respond in json",
43
+ "reply with json",
44
+ "reply in json",
45
+ "return json",
46
+ "return as json",
47
+ "return a json",
48
+ "return a yaml",
49
+ "return as yaml",
50
+ "return yaml",
51
+ "fields:",
52
+ "properties:",
53
+ ]);
54
+ /**
55
+ * Return `true` if `body` contains any signal that the prompt
56
+ * requests structured output. Matching is case-insensitive and
57
+ * uses ASCII word boundaries; multi-word phrases are matched as a
58
+ * longest-first sequence.
59
+ */
60
+ export function hasStructuredDataKeyword(body) {
61
+ const lowerBody = body.toLowerCase();
62
+ const sorted = [...STRUCTURED_DATA_KEYWORDS].sort((a, b) => b.length - a.length);
63
+ for (const phrase of sorted) {
64
+ const escaped = phrase.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
65
+ const re = new RegExp(`(?:^|[^a-z0-9])${escaped}(?:[^a-z0-9]|$)`, "u");
66
+ if (re.test(lowerBody)) {
67
+ return true;
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ //# sourceMappingURL=structured-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-data.js","sourceRoot":"","sources":["../../src/helpers/structured-data.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACvE,MAAM;IACN,aAAa;IACb,YAAY;IACZ,aAAa;IACb,aAAa;IACb,MAAM;IACN,MAAM;IACN,KAAK;IACL,OAAO;IACP,gBAAgB;IAChB,mBAAmB;IACnB,qBAAqB;IACrB,mBAAmB;IACnB,iBAAiB;IACjB,iBAAiB;IACjB,aAAa;IACb,cAAc;IACd,WAAW;IACX,SAAS;IACT,UAAU;IACV,QAAQ;IACR,aAAa;IACb,WAAW;IACX,YAAY;IACZ,SAAS;IACT,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,cAAc;IACd,mBAAmB;IACnB,iBAAiB;IACjB,iBAAiB;IACjB,eAAe;IACf,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,eAAe;IACf,gBAAgB;IAChB,aAAa;IACb,SAAS;IACT,aAAa;CACd,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IACpC,MAAM,MAAM,GAAG,CAAC,GAAG,wBAAwB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;IAChF,KAAK,MAAM,MAAM,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;QAC7D,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,kBAAkB,OAAO,iBAAiB,EAAE,GAAG,CAAC,CAAA;QACtE,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Token estimation.
3
+ *
4
+ * `estimateTokens` is intentionally simple: it approximates the count
5
+ * Claude-family models produce for English-heavy natural language. The
6
+ * heuristic splits on whitespace, then applies a per-token ratio that
7
+ * accounts for the fact that whitespace-delimited words frequently split
8
+ * into multiple tokens at subword boundaries.
9
+ *
10
+ * The function is pure, deterministic, and operates on a string that may
11
+ * contain unicode characters. It is designed to be sufficient for V1
12
+ * budget enforcement — not to replace a model-specific tokenizer.
13
+ */
14
+ /**
15
+ * Estimate the number of tokens produced by `body`.
16
+ *
17
+ * Empty or whitespace-only inputs return `0`. Inputs that contain no
18
+ * alphanumeric characters return `1` because the engine still has to
19
+ * transmit at least the placeholder token.
20
+ *
21
+ * @example
22
+ * estimateTokens("Hello, world!") // ~3
23
+ * estimateTokens("The quick brown fox") // ~4
24
+ */
25
+ export declare function estimateTokens(body: string): number;
26
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/helpers/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsCnD"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Token estimation.
3
+ *
4
+ * `estimateTokens` is intentionally simple: it approximates the count
5
+ * Claude-family models produce for English-heavy natural language. The
6
+ * heuristic splits on whitespace, then applies a per-token ratio that
7
+ * accounts for the fact that whitespace-delimited words frequently split
8
+ * into multiple tokens at subword boundaries.
9
+ *
10
+ * The function is pure, deterministic, and operates on a string that may
11
+ * contain unicode characters. It is designed to be sufficient for V1
12
+ * budget enforcement — not to replace a model-specific tokenizer.
13
+ */
14
+ const AVERAGE_CHARS_PER_TOKEN = 4;
15
+ /**
16
+ * Estimate the number of tokens produced by `body`.
17
+ *
18
+ * Empty or whitespace-only inputs return `0`. Inputs that contain no
19
+ * alphanumeric characters return `1` because the engine still has to
20
+ * transmit at least the placeholder token.
21
+ *
22
+ * @example
23
+ * estimateTokens("Hello, world!") // ~3
24
+ * estimateTokens("The quick brown fox") // ~4
25
+ */
26
+ export function estimateTokens(body) {
27
+ if (body.length === 0)
28
+ return 0;
29
+ const trimmed = body.trim();
30
+ if (trimmed.length === 0)
31
+ return 0;
32
+ let hasAlphanumeric = false;
33
+ let letterCount = 0;
34
+ for (let i = 0; i < body.length; i += 1) {
35
+ const code = body.charCodeAt(i);
36
+ if (
37
+ // ASCII digits
38
+ (code >= 0x30 && code <= 0x39) ||
39
+ // ASCII letters
40
+ (code >= 0x41 && code <= 0x5a) ||
41
+ (code >= 0x61 && code <= 0x7a) ||
42
+ // Latin-1 supplement letters
43
+ (code >= 0xc0 && code <= 0xff && code !== 0xd7 && code !== 0xf7) ||
44
+ // Latin Extended A
45
+ (code >= 0x100 && code <= 0x17f) ||
46
+ // Cyrillic block
47
+ (code >= 0x400 && code <= 0x4ff) ||
48
+ // Greek block
49
+ (code >= 0x370 && code <= 0x3ff) ||
50
+ // CJK ideographs (partial — covers BMP core)
51
+ (code >= 0x4e00 && code <= 0x9fff) ||
52
+ // Hiragana / Katakana
53
+ (code >= 0x3040 && code <= 0x30ff)) {
54
+ hasAlphanumeric = true;
55
+ letterCount += 1;
56
+ }
57
+ }
58
+ if (!hasAlphanumeric)
59
+ return 1;
60
+ const approx = Math.max(1, Math.ceil(letterCount / AVERAGE_CHARS_PER_TOKEN));
61
+ return approx;
62
+ }
63
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/helpers/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,uBAAuB,GAAG,CAAC,CAAA;AAEjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAE/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;IAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAElC,IAAI,eAAe,GAAG,KAAK,CAAA;IAC3B,IAAI,WAAW,GAAG,CAAC,CAAA;IACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC/B;QACE,eAAe;QACf,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAC9B,gBAAgB;YAChB,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAC9B,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;YAC9B,6BAA6B;YAC7B,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC;YAChE,mBAAmB;YACnB,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC;YAChC,iBAAiB;YACjB,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC;YAChC,cAAc;YACd,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,CAAC;YAChC,6CAA6C;YAC7C,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC;YAClC,sBAAsB;YACtB,CAAC,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,EAClC,CAAC;YACD,eAAe,GAAG,IAAI,CAAA;YACtB,WAAW,IAAI,CAAC,CAAA;QAClB,CAAC;IACH,CAAC;IAED,IAAI,CAAC,eAAe;QAAE,OAAO,CAAC,CAAA;IAE9B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,uBAAuB,CAAC,CAAC,CAAA;IAC5E,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { RegexMatch } from "./regex.js";
2
+ /**
3
+ * Vague quantifier vocabulary.
4
+ *
5
+ * The list captures the canonical patterns the V1 docs cite ("a few",
6
+ * "various", "etc.", "and so on") plus additional phrases whose
7
+ * cardinality is genuinely ambiguous when authoring prompts. A test
8
+ * corpus Appendix in the README enumerates the exact list.
9
+ */
10
+ export declare const VAGUE_QUANTIFIER_TERMS: readonly string[];
11
+ export interface VagueQuantifierMatch extends RegexMatch {
12
+ readonly term: string;
13
+ }
14
+ /**
15
+ * Scan `body` for vague-quantifier phrasing. The term associated with
16
+ * each match is normalized (lower-case, trimmed) so callers can group
17
+ * duplicates deterministically.
18
+ */
19
+ export declare function findVagueQuantifierMatches(body: string): readonly VagueQuantifierMatch[];
20
+ //# sourceMappingURL=vague-quantifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vague-quantifiers.d.ts","sourceRoot":"","sources":["../../src/helpers/vague-quantifiers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG5C;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAuClD,CAAA;AAqBF,MAAM,WAAW,oBAAqB,SAAQ,UAAU;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,oBAAoB,EAAE,CAUxF"}
@@ -0,0 +1,83 @@
1
+ import { findAllMatches } from "./regex.js";
2
+ /**
3
+ * Vague quantifier vocabulary.
4
+ *
5
+ * The list captures the canonical patterns the V1 docs cite ("a few",
6
+ * "various", "etc.", "and so on") plus additional phrases whose
7
+ * cardinality is genuinely ambiguous when authoring prompts. A test
8
+ * corpus Appendix in the README enumerates the exact list.
9
+ */
10
+ export const VAGUE_QUANTIFIER_TERMS = Object.freeze([
11
+ "a few",
12
+ "a couple of",
13
+ "a couple",
14
+ "various",
15
+ "various kinds of",
16
+ "various types of",
17
+ "various things",
18
+ "several",
19
+ "some",
20
+ "some kind of",
21
+ "some sort of",
22
+ "some number of",
23
+ "many",
24
+ "many things",
25
+ "lots of",
26
+ "lots",
27
+ "dozens of",
28
+ "dozens",
29
+ "a bunch of",
30
+ "a bunch",
31
+ "etc",
32
+ "etc.",
33
+ "and so on",
34
+ "and so forth",
35
+ "and others",
36
+ "and the like",
37
+ "and more",
38
+ "such as",
39
+ "for example",
40
+ "e.g.",
41
+ "i.e.",
42
+ "or something",
43
+ "or something like that",
44
+ "or so",
45
+ "things like that",
46
+ "stuff like that",
47
+ "whatnot",
48
+ "yada yada",
49
+ ]);
50
+ /**
51
+ * Build the regex used to match vague quantifier phrases. Longer terms
52
+ * are matched first to ensure that "various kinds of" wins over
53
+ * "various", and term boundaries use the ASCII word boundary `\b`.
54
+ *
55
+ * Special characters in terms (`.`, `?`, etc.) are escaped before
56
+ * inclusion in the alternation.
57
+ */
58
+ function buildVagueQuantifierRegex() {
59
+ const sorted = [...VAGUE_QUANTIFIER_TERMS].sort((a, b) => b.length - a.length);
60
+ const escaped = sorted.map((term) => {
61
+ const escapedTerm = term.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
62
+ return `\\b${escapedTerm}\\b`;
63
+ });
64
+ return new RegExp(`(?:${escaped.join("|")})`, "giu");
65
+ }
66
+ const VAGUE_QUANTIFIER_REGEX = buildVagueQuantifierRegex();
67
+ /**
68
+ * Scan `body` for vague-quantifier phrasing. The term associated with
69
+ * each match is normalized (lower-case, trimmed) so callers can group
70
+ * duplicates deterministically.
71
+ */
72
+ export function findVagueQuantifierMatches(body) {
73
+ const out = [];
74
+ for (const m of findAllMatches(VAGUE_QUANTIFIER_REGEX, body)) {
75
+ const term = m.text
76
+ .trim()
77
+ .toLowerCase()
78
+ .replace(/[.,;:!?]+$/u, "");
79
+ out.push({ ...m, term });
80
+ }
81
+ return out;
82
+ }
83
+ //# sourceMappingURL=vague-quantifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vague-quantifiers.js","sourceRoot":"","sources":["../../src/helpers/vague-quantifiers.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACrE,OAAO;IACP,aAAa;IACb,UAAU;IACV,SAAS;IACT,kBAAkB;IAClB,kBAAkB;IAClB,gBAAgB;IAChB,SAAS;IACT,MAAM;IACN,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,MAAM;IACN,aAAa;IACb,SAAS;IACT,MAAM;IACN,WAAW;IACX,QAAQ;IACR,YAAY;IACZ,SAAS;IACT,KAAK;IACL,MAAM;IACN,WAAW;IACX,cAAc;IACd,YAAY;IACZ,cAAc;IACd,UAAU;IACV,SAAS;IACT,aAAa;IACb,MAAM;IACN,MAAM;IACN,cAAc;IACd,wBAAwB;IACxB,OAAO;IACP,kBAAkB;IAClB,iBAAiB;IACjB,SAAS;IACT,WAAW;CACZ,CAAC,CAAA;AAEF;;;;;;;GAOG;AACH,SAAS,yBAAyB;IAChC,MAAM,MAAM,GAAG,CAAC,GAAG,sBAAsB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;IAC9E,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAClC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;QAC/D,OAAO,MAAM,WAAW,KAAK,CAAA;IAC/B,CAAC,CAAC,CAAA;IACF,OAAO,IAAI,MAAM,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,sBAAsB,GAAG,yBAAyB,EAAE,CAAA;AAM1D;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,MAAM,GAAG,GAA2B,EAAE,CAAA;IACtC,KAAK,MAAM,CAAC,IAAI,cAAc,CAAC,sBAAsB,EAAE,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI;aAChB,IAAI,EAAE;aACN,WAAW,EAAE;aACb,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;QAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAA;IAC1B,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { PromptFile, SourceLocation } from "@prompt-lint/types";
2
+ /**
3
+ * Variable inspection helpers used by the `structure/unused-variable` and
4
+ * `structure/undefined-variable` rules.
5
+ *
6
+ * The parser already produces `{ name, locations }` groups via
7
+ * `@prompt-lint/parser`. We re-expose the underlying data here in stable
8
+ * helpers so rule implementations do not have to touch the parser types
9
+ * directly.
10
+ */
11
+ export interface VariableReference {
12
+ readonly name: string;
13
+ readonly firstLocation: SourceLocation;
14
+ }
15
+ /**
16
+ * Return the set of variable names declared in the frontmatter `variables`
17
+ * array. Names that do not satisfy the parser's identifier rules are
18
+ * skipped silently — the parser already surfaces a frontmatter error for
19
+ * those, and re-flagging them here would create duplicate findings.
20
+ */
21
+ export declare function collectDeclaredVariables(file: PromptFile): readonly string[];
22
+ /**
23
+ * Return every `{{ var }}` occurrence in the prompt body, in source order,
24
+ * with deduplicated variable names. The first reported location per name
25
+ * is the first one the parser recorded.
26
+ */
27
+ export declare function collectBodyVariables(file: PromptFile): readonly VariableReference[];
28
+ /**
29
+ * Test whether `name` is referenced anywhere in the parsed `PromptFile`.
30
+ */
31
+ export declare function findVariableOccurrences(file: PromptFile, name: string): readonly SourceLocation[];
32
+ /**
33
+ * Convenience: declare-detection with case-sensitive semantics.
34
+ */
35
+ export declare function isVariableMissing(file: PromptFile, name: string): boolean;
36
+ //# sourceMappingURL=variables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../../src/helpers/variables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEpE;;;;;;;;GAQG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAA;CACvC;AAID;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,MAAM,EAAE,CAY5E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,iBAAiB,EAAE,CAiBnF;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,cAAc,EAAE,CAKjG;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGzE"}
@@ -0,0 +1,66 @@
1
+ const FRONTMATTER_VARIABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_.\-]*$/;
2
+ /**
3
+ * Return the set of variable names declared in the frontmatter `variables`
4
+ * array. Names that do not satisfy the parser's identifier rules are
5
+ * skipped silently — the parser already surfaces a frontmatter error for
6
+ * those, and re-flagging them here would create duplicate findings.
7
+ */
8
+ export function collectDeclaredVariables(file) {
9
+ const declared = file.frontmatter.variables;
10
+ if (declared === undefined)
11
+ return [];
12
+ const seen = new Set();
13
+ const out = [];
14
+ for (const name of declared) {
15
+ if (!FRONTMATTER_VARIABLE_NAME.test(name))
16
+ continue;
17
+ if (seen.has(name))
18
+ continue;
19
+ seen.add(name);
20
+ out.push(name);
21
+ }
22
+ return out;
23
+ }
24
+ /**
25
+ * Return every `{{ var }}` occurrence in the prompt body, in source order,
26
+ * with deduplicated variable names. The first reported location per name
27
+ * is the first one the parser recorded.
28
+ */
29
+ export function collectBodyVariables(file) {
30
+ const seen = new Set();
31
+ const out = [];
32
+ for (const variable of file.variables) {
33
+ if (seen.has(variable.name))
34
+ continue;
35
+ seen.add(variable.name);
36
+ const first = variable.locations[0];
37
+ if (first !== undefined) {
38
+ out.push({ name: variable.name, firstLocation: first });
39
+ }
40
+ else {
41
+ out.push({
42
+ name: variable.name,
43
+ firstLocation: { line: 1, column: 1, endLine: 1, endColumn: 1 },
44
+ });
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ /**
50
+ * Test whether `name` is referenced anywhere in the parsed `PromptFile`.
51
+ */
52
+ export function findVariableOccurrences(file, name) {
53
+ for (const variable of file.variables) {
54
+ if (variable.name === name)
55
+ return variable.locations;
56
+ }
57
+ return [];
58
+ }
59
+ /**
60
+ * Convenience: declare-detection with case-sensitive semantics.
61
+ */
62
+ export function isVariableMissing(file, name) {
63
+ const declared = collectDeclaredVariables(file);
64
+ return !declared.includes(name);
65
+ }
66
+ //# sourceMappingURL=variables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variables.js","sourceRoot":"","sources":["../../src/helpers/variables.ts"],"names":[],"mappings":"AAiBA,MAAM,yBAAyB,GAAG,6BAA6B,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAgB;IACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAA;IAC3C,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QACnD,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC5B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACd,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAgB;IACnD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,GAAG,GAAwB,EAAE,CAAA;IACnC,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAQ;QACrC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;QACnC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAA;QACzD,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,aAAa,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;aAChE,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,IAAgB,EAAE,IAAY;IACpE,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,QAAQ,CAAC,SAAS,CAAA;IACvD,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAgB,EAAE,IAAY;IAC9D,MAAM,QAAQ,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAA;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC"}