@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.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/convention/filename-naming.d.ts +16 -0
- package/dist/convention/filename-naming.d.ts.map +1 -0
- package/dist/convention/filename-naming.js +50 -0
- package/dist/convention/filename-naming.js.map +1 -0
- package/dist/cost/high-token-estimate.d.ts +4 -0
- package/dist/cost/high-token-estimate.d.ts.map +1 -0
- package/dist/cost/high-token-estimate.js +51 -0
- package/dist/cost/high-token-estimate.js.map +1 -0
- package/dist/generated/manifest.d.ts +19 -0
- package/dist/generated/manifest.d.ts.map +1 -0
- package/dist/generated/manifest.js +98 -0
- package/dist/generated/manifest.js.map +1 -0
- package/dist/helpers/filename.d.ts +19 -0
- package/dist/helpers/filename.d.ts.map +1 -0
- package/dist/helpers/filename.js +59 -0
- package/dist/helpers/filename.js.map +1 -0
- package/dist/helpers/index.d.ts +16 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +16 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/instruction-override.d.ts +28 -0
- package/dist/helpers/instruction-override.d.ts.map +1 -0
- package/dist/helpers/instruction-override.js +43 -0
- package/dist/helpers/instruction-override.js.map +1 -0
- package/dist/helpers/pii.d.ts +31 -0
- package/dist/helpers/pii.d.ts.map +1 -0
- package/dist/helpers/pii.js +36 -0
- package/dist/helpers/pii.js.map +1 -0
- package/dist/helpers/regex.d.ts +53 -0
- package/dist/helpers/regex.d.ts.map +1 -0
- package/dist/helpers/regex.js +79 -0
- package/dist/helpers/regex.js.map +1 -0
- package/dist/helpers/structured-data.d.ts +19 -0
- package/dist/helpers/structured-data.d.ts.map +1 -0
- package/dist/helpers/structured-data.js +72 -0
- package/dist/helpers/structured-data.js.map +1 -0
- package/dist/helpers/tokens.d.ts +26 -0
- package/dist/helpers/tokens.d.ts.map +1 -0
- package/dist/helpers/tokens.js +63 -0
- package/dist/helpers/tokens.js.map +1 -0
- package/dist/helpers/vague-quantifiers.d.ts +20 -0
- package/dist/helpers/vague-quantifiers.d.ts.map +1 -0
- package/dist/helpers/vague-quantifiers.js +83 -0
- package/dist/helpers/vague-quantifiers.js.map +1 -0
- package/dist/helpers/variables.d.ts +36 -0
- package/dist/helpers/variables.d.ts.map +1 -0
- package/dist/helpers/variables.js +66 -0
- package/dist/helpers/variables.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +80 -0
- package/dist/index.js.map +1 -0
- package/dist/quality/missing-output-schema.d.ts +15 -0
- package/dist/quality/missing-output-schema.d.ts.map +1 -0
- package/dist/quality/missing-output-schema.js +39 -0
- package/dist/quality/missing-output-schema.js.map +1 -0
- package/dist/quality/vague-quantifier-language.d.ts +13 -0
- package/dist/quality/vague-quantifier-language.d.ts.map +1 -0
- package/dist/quality/vague-quantifier-language.js +34 -0
- package/dist/quality/vague-quantifier-language.js.map +1 -0
- package/dist/security/instruction-override-pattern.d.ts +17 -0
- package/dist/security/instruction-override-pattern.d.ts.map +1 -0
- package/dist/security/instruction-override-pattern.js +39 -0
- package/dist/security/instruction-override-pattern.js.map +1 -0
- package/dist/security/pii-pattern.d.ts +18 -0
- package/dist/security/pii-pattern.d.ts.map +1 -0
- package/dist/security/pii-pattern.js +56 -0
- package/dist/security/pii-pattern.js.map +1 -0
- package/dist/structure/missing-description.d.ts +14 -0
- package/dist/structure/missing-description.d.ts.map +1 -0
- package/dist/structure/missing-description.js +32 -0
- package/dist/structure/missing-description.js.map +1 -0
- package/dist/structure/missing-model.d.ts +16 -0
- package/dist/structure/missing-model.d.ts.map +1 -0
- package/dist/structure/missing-model.js +34 -0
- package/dist/structure/missing-model.js.map +1 -0
- package/dist/structure/undefined-variable.d.ts +15 -0
- package/dist/structure/undefined-variable.d.ts.map +1 -0
- package/dist/structure/undefined-variable.js +39 -0
- package/dist/structure/undefined-variable.js.map +1 -0
- package/dist/structure/unused-variable.d.ts +15 -0
- package/dist/structure/unused-variable.d.ts.map +1 -0
- package/dist/structure/unused-variable.js +38 -0
- package/dist/structure/unused-variable.js.map +1 -0
- package/dist/test-helpers.d.ts +40 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +52 -0
- package/dist/test-helpers.js.map +1 -0
- 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"}
|