@markbrutx/promptbook-core 0.1.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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/annotations.d.ts +56 -0
  4. package/dist/annotations.d.ts.map +1 -0
  5. package/dist/annotations.js +50 -0
  6. package/dist/annotations.js.map +1 -0
  7. package/dist/bundle.d.ts +44 -0
  8. package/dist/bundle.d.ts.map +1 -0
  9. package/dist/bundle.js +135 -0
  10. package/dist/bundle.js.map +1 -0
  11. package/dist/edge/index.js +192 -0
  12. package/dist/edge.d.ts +12 -0
  13. package/dist/edge.d.ts.map +1 -0
  14. package/dist/edge.js +11 -0
  15. package/dist/edge.js.map +1 -0
  16. package/dist/eval/assertions.d.ts +15 -0
  17. package/dist/eval/assertions.d.ts.map +1 -0
  18. package/dist/eval/assertions.js +131 -0
  19. package/dist/eval/assertions.js.map +1 -0
  20. package/dist/eval/evaluate.d.ts +15 -0
  21. package/dist/eval/evaluate.d.ts.map +1 -0
  22. package/dist/eval/evaluate.js +65 -0
  23. package/dist/eval/evaluate.js.map +1 -0
  24. package/dist/eval/load-fixtures.d.ts +12 -0
  25. package/dist/eval/load-fixtures.d.ts.map +1 -0
  26. package/dist/eval/load-fixtures.js +87 -0
  27. package/dist/eval/load-fixtures.js.map +1 -0
  28. package/dist/eval/types.d.ts +123 -0
  29. package/dist/eval/types.d.ts.map +1 -0
  30. package/dist/eval/types.js +2 -0
  31. package/dist/eval/types.js.map +1 -0
  32. package/dist/frontmatter.d.ts +12 -0
  33. package/dist/frontmatter.d.ts.map +1 -0
  34. package/dist/frontmatter.js +22 -0
  35. package/dist/frontmatter.js.map +1 -0
  36. package/dist/fs.d.ts +11 -0
  37. package/dist/fs.d.ts.map +1 -0
  38. package/dist/fs.js +20 -0
  39. package/dist/fs.js.map +1 -0
  40. package/dist/guards.d.ts +6 -0
  41. package/dist/guards.d.ts.map +1 -0
  42. package/dist/guards.js +9 -0
  43. package/dist/guards.js.map +1 -0
  44. package/dist/index.d.ts +22 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +15 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/interpolate.d.ts +11 -0
  49. package/dist/interpolate.d.ts.map +1 -0
  50. package/dist/interpolate.js +25 -0
  51. package/dist/interpolate.js.map +1 -0
  52. package/dist/lint/lint.d.ts +11 -0
  53. package/dist/lint/lint.d.ts.map +1 -0
  54. package/dist/lint/lint.js +30 -0
  55. package/dist/lint/lint.js.map +1 -0
  56. package/dist/lint/references.d.ts +18 -0
  57. package/dist/lint/references.d.ts.map +1 -0
  58. package/dist/lint/references.js +39 -0
  59. package/dist/lint/references.js.map +1 -0
  60. package/dist/lint/rules/banned-tokens.d.ts +13 -0
  61. package/dist/lint/rules/banned-tokens.d.ts.map +1 -0
  62. package/dist/lint/rules/banned-tokens.js +38 -0
  63. package/dist/lint/rules/banned-tokens.js.map +1 -0
  64. package/dist/lint/rules/dangling-reference.d.ts +11 -0
  65. package/dist/lint/rules/dangling-reference.d.ts.map +1 -0
  66. package/dist/lint/rules/dangling-reference.js +37 -0
  67. package/dist/lint/rules/dangling-reference.js.map +1 -0
  68. package/dist/lint/rules/dead-rule.d.ts +21 -0
  69. package/dist/lint/rules/dead-rule.d.ts.map +1 -0
  70. package/dist/lint/rules/dead-rule.js +135 -0
  71. package/dist/lint/rules/dead-rule.js.map +1 -0
  72. package/dist/lint/rules/example-balance.d.ts +19 -0
  73. package/dist/lint/rules/example-balance.d.ts.map +1 -0
  74. package/dist/lint/rules/example-balance.js +57 -0
  75. package/dist/lint/rules/example-balance.js.map +1 -0
  76. package/dist/lint/rules/index.d.ts +28 -0
  77. package/dist/lint/rules/index.d.ts.map +1 -0
  78. package/dist/lint/rules/index.js +30 -0
  79. package/dist/lint/rules/index.js.map +1 -0
  80. package/dist/lint/rules/language-directive-position.d.ts +16 -0
  81. package/dist/lint/rules/language-directive-position.d.ts.map +1 -0
  82. package/dist/lint/rules/language-directive-position.js +42 -0
  83. package/dist/lint/rules/language-directive-position.js.map +1 -0
  84. package/dist/lint/rules/token-budget.d.ts +18 -0
  85. package/dist/lint/rules/token-budget.d.ts.map +1 -0
  86. package/dist/lint/rules/token-budget.js +39 -0
  87. package/dist/lint/rules/token-budget.js.map +1 -0
  88. package/dist/lint/rules/unused-fragment.d.ts +11 -0
  89. package/dist/lint/rules/unused-fragment.d.ts.map +1 -0
  90. package/dist/lint/rules/unused-fragment.js +33 -0
  91. package/dist/lint/rules/unused-fragment.js.map +1 -0
  92. package/dist/lint/types.d.ts +50 -0
  93. package/dist/lint/types.d.ts.map +1 -0
  94. package/dist/lint/types.js +2 -0
  95. package/dist/lint/types.js.map +1 -0
  96. package/dist/load.d.ts +12 -0
  97. package/dist/load.d.ts.map +1 -0
  98. package/dist/load.js +238 -0
  99. package/dist/load.js.map +1 -0
  100. package/dist/paths.d.ts +12 -0
  101. package/dist/paths.d.ts.map +1 -0
  102. package/dist/paths.js +25 -0
  103. package/dist/paths.js.map +1 -0
  104. package/dist/resolve-book.d.ts +15 -0
  105. package/dist/resolve-book.d.ts.map +1 -0
  106. package/dist/resolve-book.js +195 -0
  107. package/dist/resolve-book.js.map +1 -0
  108. package/dist/resolve.d.ts +13 -0
  109. package/dist/resolve.d.ts.map +1 -0
  110. package/dist/resolve.js +17 -0
  111. package/dist/resolve.js.map +1 -0
  112. package/dist/types.d.ts +173 -0
  113. package/dist/types.d.ts.map +1 -0
  114. package/dist/types.js +9 -0
  115. package/dist/types.js.map +1 -0
  116. package/package.json +48 -0
  117. package/src/annotations.ts +100 -0
  118. package/src/bundle.ts +163 -0
  119. package/src/edge.ts +11 -0
  120. package/src/eval/assertions.ts +174 -0
  121. package/src/eval/evaluate.ts +84 -0
  122. package/src/eval/load-fixtures.ts +91 -0
  123. package/src/eval/types.ts +134 -0
  124. package/src/frontmatter.ts +28 -0
  125. package/src/fs.ts +21 -0
  126. package/src/guards.ts +11 -0
  127. package/src/index.ts +84 -0
  128. package/src/interpolate.ts +27 -0
  129. package/src/lint/lint.ts +32 -0
  130. package/src/lint/references.ts +50 -0
  131. package/src/lint/rules/banned-tokens.ts +46 -0
  132. package/src/lint/rules/dangling-reference.ts +43 -0
  133. package/src/lint/rules/dead-rule.ts +147 -0
  134. package/src/lint/rules/example-balance.ts +68 -0
  135. package/src/lint/rules/index.ts +47 -0
  136. package/src/lint/rules/language-directive-position.ts +51 -0
  137. package/src/lint/rules/token-budget.ts +50 -0
  138. package/src/lint/rules/unused-fragment.ts +38 -0
  139. package/src/lint/types.ts +55 -0
  140. package/src/load.ts +282 -0
  141. package/src/paths.ts +27 -0
  142. package/src/resolve-book.ts +237 -0
  143. package/src/resolve.ts +18 -0
  144. package/src/types.ts +191 -0
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ export { ANNOTATION_QUEUE_DIR, ANNOTATION_QUEUE_FILE, parseInbox, serializeAnnotationLine, serializeInbox, } from "./annotations.js";
2
+ export { serializeBook, serializeBookExpression, serializeBookJson } from "./bundle.js";
3
+ export { defaultAssertions } from "./eval/assertions.js";
4
+ export { evaluate } from "./eval/evaluate.js";
5
+ export { loadFixtures } from "./eval/load-fixtures.js";
6
+ export { parseFrontmatter } from "./frontmatter.js";
7
+ export { nodeFs } from "./fs.js";
8
+ export { interpolate } from "./interpolate.js";
9
+ export { lint } from "./lint/lint.js";
10
+ export { iterateReferences } from "./lint/references.js";
11
+ export { bannedTokens, danglingReference, deadRule, defaultRules, estimateTokensByChars, exampleBalance, languageDirectivePosition, tokenBudget, unusedFragment, } from "./lint/rules/index.js";
12
+ export { loadPrompts } from "./load.js";
13
+ export { resolve } from "./resolve.js";
14
+ export { resolveBook } from "./resolve-book.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,UAAU,EACV,uBAAuB,EACvB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAevD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAUzD,OAAO,EACL,YAAY,EAEZ,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,yBAAyB,EACzB,WAAW,EACX,cAAc,GACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Context } from "./types.js";
2
+ /**
3
+ * Substitute `${path}` placeholders in `body` using `context`.
4
+ *
5
+ * - A missing key resolves to an empty string and triggers `onMissing(key)`.
6
+ * Interpolation never throws.
7
+ * - `\${path}` is an escape and renders the literal `${path}`.
8
+ * - Lookup is by flat key, matching the flat {@link Context} shape.
9
+ */
10
+ export declare function interpolate(body: string, context: Context, onMissing: (key: string) => void): string;
11
+ //# sourceMappingURL=interpolate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.d.ts","sourceRoot":"","sources":["../src/interpolate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,CAapG"}
@@ -0,0 +1,25 @@
1
+ /** Matches `${path}` placeholders, capturing an optional leading `\` escape. */
2
+ const VAR_RE = /(\\?)\$\{([^}]+)\}/g;
3
+ /**
4
+ * Substitute `${path}` placeholders in `body` using `context`.
5
+ *
6
+ * - A missing key resolves to an empty string and triggers `onMissing(key)`.
7
+ * Interpolation never throws.
8
+ * - `\${path}` is an escape and renders the literal `${path}`.
9
+ * - Lookup is by flat key, matching the flat {@link Context} shape.
10
+ */
11
+ export function interpolate(body, context, onMissing) {
12
+ return body.replace(VAR_RE, (_match, backslash, expr) => {
13
+ if (backslash) {
14
+ return `\${${expr}}`;
15
+ }
16
+ const key = expr.trim();
17
+ const value = context[key];
18
+ if (value === undefined) {
19
+ onMissing(key);
20
+ return "";
21
+ }
22
+ return String(value);
23
+ });
24
+ }
25
+ //# sourceMappingURL=interpolate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.js","sourceRoot":"","sources":["../src/interpolate.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,MAAM,MAAM,GAAG,qBAAqB,CAAC;AAErC;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAgB,EAAE,SAAgC;IAC1F,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,SAAiB,EAAE,IAAY,EAAE,EAAE;QACtE,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,MAAM,IAAI,GAAG,CAAC;QACvB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,SAAS,CAAC,GAAG,CAAC,CAAC;YACf,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { LintInput, LintReport, LintRule } from "./types.js";
2
+ /**
3
+ * Run lint rules over an input and aggregate the findings.
4
+ *
5
+ * Book-scope rules always run. Resolved-scope rules run only when
6
+ * `input.result` is present, so calling `lint({ book })` lints structure only.
7
+ * The engine has no intelligence of its own: it dispatches by scope and counts
8
+ * severities. All judgement lives in the rules.
9
+ */
10
+ export declare function lint(input: LintInput, rules?: LintRule[]): LintReport;
11
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/lint/lint.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE/E;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,GAAE,QAAQ,EAAmB,GAAG,UAAU,CAoBrF"}
@@ -0,0 +1,30 @@
1
+ import { defaultRules } from "./rules/index.js";
2
+ /**
3
+ * Run lint rules over an input and aggregate the findings.
4
+ *
5
+ * Book-scope rules always run. Resolved-scope rules run only when
6
+ * `input.result` is present, so calling `lint({ book })` lints structure only.
7
+ * The engine has no intelligence of its own: it dispatches by scope and counts
8
+ * severities. All judgement lives in the rules.
9
+ */
10
+ export function lint(input, rules = defaultRules()) {
11
+ const findings = [];
12
+ for (const rule of rules) {
13
+ if (rule.scope === "resolved" && input.result === undefined) {
14
+ continue;
15
+ }
16
+ findings.push(...rule.check(input));
17
+ }
18
+ let errorCount = 0;
19
+ let warningCount = 0;
20
+ for (const finding of findings) {
21
+ if (finding.severity === "error") {
22
+ errorCount += 1;
23
+ }
24
+ else if (finding.severity === "warning") {
25
+ warningCount += 1;
26
+ }
27
+ }
28
+ return { findings, errorCount, warningCount };
29
+ }
30
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/lint/lint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGhD;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAAC,KAAgB,EAAE,KAAK,GAAe,YAAY,EAAE;IACvE,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,KAAK,KAAK,UAAU,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC5D,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,UAAU,IAAI,CAAC,CAAC;QAClB,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC1C,YAAY,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { PromptBook } from "../types.js";
2
+ /** A single mention of a fragment id, with enough context to report it. */
3
+ export interface FragmentReference {
4
+ id: string;
5
+ composition: string;
6
+ /** Where in a composition the fragment id is mentioned. */
7
+ role: "base" | "order" | "add" | "after" | "replace-from" | "replace-to" | "forbid" | "rule-order";
8
+ /** Index of the rule the reference came from, when it came from a rule. */
9
+ ruleIndex?: number;
10
+ }
11
+ /**
12
+ * Yield every place a fragment id is referenced across all compositions: base
13
+ * and explicit order lists, and each rule's add/after/replace/forbid/order.
14
+ * Shared by the `unused-fragment` and `dangling-reference` rules so both see
15
+ * the same reference surface.
16
+ */
17
+ export declare function iterateReferences(book: PromptBook): Generator<FragmentReference>;
18
+ //# sourceMappingURL=references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../../src/lint/references.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,OAAO,GAAG,cAAc,GAAG,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;IACnG,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAiB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC,iBAAiB,CAAC,CA+BjF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Yield every place a fragment id is referenced across all compositions: base
3
+ * and explicit order lists, and each rule's add/after/replace/forbid/order.
4
+ * Shared by the `unused-fragment` and `dangling-reference` rules so both see
5
+ * the same reference surface.
6
+ */
7
+ export function* iterateReferences(book) {
8
+ for (const composition of book.compositions.values()) {
9
+ const composed = composition.name;
10
+ for (const id of composition.base) {
11
+ yield { id, composition: composed, role: "base" };
12
+ }
13
+ if (composition.order) {
14
+ for (const id of composition.order) {
15
+ yield { id, composition: composed, role: "order" };
16
+ }
17
+ }
18
+ for (const rule of composition.rules) {
19
+ const ruleIndex = rule.index;
20
+ for (const id of rule.add ?? []) {
21
+ yield { id, composition: composed, role: "add", ruleIndex };
22
+ }
23
+ if (rule.after !== undefined) {
24
+ yield { id: rule.after, composition: composed, role: "after", ruleIndex };
25
+ }
26
+ for (const [from, to] of Object.entries(rule.replace ?? {})) {
27
+ yield { id: from, composition: composed, role: "replace-from", ruleIndex };
28
+ yield { id: to, composition: composed, role: "replace-to", ruleIndex };
29
+ }
30
+ for (const id of rule.forbid ?? []) {
31
+ yield { id, composition: composed, role: "forbid", ruleIndex };
32
+ }
33
+ for (const id of rule.order ?? []) {
34
+ yield { id, composition: composed, role: "rule-order", ruleIndex };
35
+ }
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"references.js","sourceRoot":"","sources":["../../src/lint/references.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,MAAM,SAAS,CAAC,CAAC,iBAAiB,CAAC,IAAgB;IACjD,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC;QAClC,KAAK,MAAM,EAAE,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACpD,CAAC;QACD,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACtB,KAAK,MAAM,EAAE,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;gBACnC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;YACrD,CAAC;QACH,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,EAAE,CAAC;YACrC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC;YAC7B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC;gBAChC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC9D,CAAC;YACD,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC7B,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;YAC5E,CAAC;YACD,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC5D,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;gBAC3E,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;YACzE,CAAC;YACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;gBACnC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;YACjE,CAAC;YACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;gBAClC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;YACrE,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { LintRule, Severity } from "../types.js";
2
+ export interface BannedTokensOptions {
3
+ /** Substrings or patterns that must not appear in the assembled text. */
4
+ tokens?: (string | RegExp)[];
5
+ severity?: Severity;
6
+ }
7
+ /**
8
+ * `banned-tokens` (resolved): the assembled text must not contain any of the
9
+ * configured substrings or patterns. The default bans the em-dash. Patterns
10
+ * should not use the global flag, since each token is tested once.
11
+ */
12
+ export declare function bannedTokens(options?: BannedTokensOptions): LintRule;
13
+ //# sourceMappingURL=banned-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banned-tokens.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/banned-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,QAAQ,CA4BxE"}
@@ -0,0 +1,38 @@
1
+ // Em-dash: a common stylistic ban, and deliberately generic (not domain-specific).
2
+ const EM_DASH = "\u2014";
3
+ const DEFAULT_BANNED = [EM_DASH];
4
+ /**
5
+ * `banned-tokens` (resolved): the assembled text must not contain any of the
6
+ * configured substrings or patterns. The default bans the em-dash. Patterns
7
+ * should not use the global flag, since each token is tested once.
8
+ */
9
+ export function bannedTokens(options = {}) {
10
+ const tokens = options.tokens ?? DEFAULT_BANNED;
11
+ const severity = options.severity ?? "error";
12
+ return {
13
+ id: "banned-tokens",
14
+ description: "Resolved prompt must not contain banned substrings or patterns.",
15
+ scope: "resolved",
16
+ check(input) {
17
+ const result = input.result;
18
+ if (result === undefined) {
19
+ return [];
20
+ }
21
+ const text = result.text;
22
+ const findings = [];
23
+ for (const token of tokens) {
24
+ const matched = typeof token === "string" ? text.includes(token) : token.test(text);
25
+ if (matched) {
26
+ const label = typeof token === "string" ? JSON.stringify(token) : String(token);
27
+ findings.push({
28
+ ruleId: "banned-tokens",
29
+ severity,
30
+ message: `text contains banned token ${label}`,
31
+ });
32
+ }
33
+ }
34
+ return findings;
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=banned-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banned-tokens.js","sourceRoot":"","sources":["../../../src/lint/rules/banned-tokens.ts"],"names":[],"mappings":"AAQA,mFAAmF;AACnF,MAAM,OAAO,GAAG,QAAQ,CAAC;AACzB,MAAM,cAAc,GAAwB,CAAC,OAAO,CAAC,CAAC;AAEtD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,OAAO,GAAwB,EAAE;IAC5D,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAC;IAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC7C,OAAO;QACL,EAAE,EAAE,eAAe;QACnB,WAAW,EAAE,iEAAiE;QAC9E,KAAK,EAAE,UAAU;QACjB,KAAK,CAAC,KAAK;YACT,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;YACzB,MAAM,QAAQ,GAAkB,EAAE,CAAC;YACnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpF,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;oBAChF,QAAQ,CAAC,IAAI,CAAC;wBACZ,MAAM,EAAE,eAAe;wBACvB,QAAQ;wBACR,OAAO,EAAE,8BAA8B,KAAK,EAAE;qBAC/C,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { LintRule, Severity } from "../types.js";
2
+ export interface DanglingReferenceOptions {
3
+ severity?: Severity;
4
+ }
5
+ /**
6
+ * `dangling-reference` (book): any reference (base, order, or a rule's
7
+ * add/replace/forbid/order/after) that points at a fragment id which does not
8
+ * exist is a hard error — the prompt cannot assemble as written.
9
+ */
10
+ export declare function danglingReference(options?: DanglingReferenceOptions): LintRule;
11
+ //# sourceMappingURL=dangling-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dangling-reference.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/dangling-reference.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,QAAQ,CA8BlF"}
@@ -0,0 +1,37 @@
1
+ import { iterateReferences } from "../references.js";
2
+ /**
3
+ * `dangling-reference` (book): any reference (base, order, or a rule's
4
+ * add/replace/forbid/order/after) that points at a fragment id which does not
5
+ * exist is a hard error — the prompt cannot assemble as written.
6
+ */
7
+ export function danglingReference(options = {}) {
8
+ const severity = options.severity ?? "error";
9
+ return {
10
+ id: "dangling-reference",
11
+ description: "Every referenced fragment id must exist.",
12
+ scope: "book",
13
+ check(input) {
14
+ const findings = [];
15
+ for (const reference of iterateReferences(input.book)) {
16
+ if (input.book.fragments.has(reference.id)) {
17
+ continue;
18
+ }
19
+ const where = reference.ruleIndex !== undefined
20
+ ? `rule #${reference.ruleIndex} (${reference.role})`
21
+ : reference.role;
22
+ const finding = {
23
+ ruleId: "dangling-reference",
24
+ severity,
25
+ message: `composition "${reference.composition}" references unknown fragment "${reference.id}" via ${where}`,
26
+ fragmentId: reference.id,
27
+ };
28
+ if (reference.ruleIndex !== undefined) {
29
+ finding.ruleIndex = reference.ruleIndex;
30
+ }
31
+ findings.push(finding);
32
+ }
33
+ return findings;
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=dangling-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dangling-reference.js","sourceRoot":"","sources":["../../../src/lint/rules/dangling-reference.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAOrD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAA6B,EAAE;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC;IAC7C,OAAO;QACL,EAAE,EAAE,oBAAoB;QACxB,WAAW,EAAE,0CAA0C;QACvD,KAAK,EAAE,MAAM;QACb,KAAK,CAAC,KAAK;YACT,MAAM,QAAQ,GAAkB,EAAE,CAAC;YACnC,KAAK,MAAM,SAAS,IAAI,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtD,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;oBAC3C,SAAS;gBACX,CAAC;gBACD,MAAM,KAAK,GACT,SAAS,CAAC,SAAS,KAAK,SAAS;oBAC/B,CAAC,CAAC,SAAS,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,IAAI,GAAG;oBACpD,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;gBACrB,MAAM,OAAO,GAAgB;oBAC3B,MAAM,EAAE,oBAAoB;oBAC5B,QAAQ;oBACR,OAAO,EAAE,gBAAgB,SAAS,CAAC,WAAW,kCAAkC,SAAS,CAAC,EAAE,SAAS,KAAK,EAAE;oBAC5G,UAAU,EAAE,SAAS,CAAC,EAAE;iBACzB,CAAC;gBACF,IAAI,SAAS,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;oBACtC,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;gBAC1C,CAAC;gBACD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACzB,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { LintRule, Severity } from "../types.js";
2
+ export interface DeadRuleOptions {
3
+ severity?: Severity;
4
+ }
5
+ /**
6
+ * `dead-rule` (book): catch rules that statically cannot do what they say.
7
+ * For each rule it flags a `replace` whose source is never present, an `add` of
8
+ * an already-present id, and an `order` naming an id that can never appear.
9
+ *
10
+ * `when` is taken into account: a prior rule only shapes the present-set a later
11
+ * rule sees if it is *guaranteed to co-fire* — i.e. its `when` is implied by the
12
+ * later rule's `when`. This keeps mutually-exclusive single-axis swaps (e.g.
13
+ * `tone=a|b|c` each replacing the same base fragment) from reading as dead,
14
+ * while still catching a replace whose source was unconditionally consumed by an
15
+ * earlier rule.
16
+ *
17
+ * v0 limitation: still a static check. "A rule that never fires under any
18
+ * context" (which requires enumerating contexts) remains out of scope.
19
+ */
20
+ export declare function deadRule(options?: DeadRuleOptions): LintRule;
21
+ //# sourceMappingURL=dead-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dead-rule.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/dead-rule.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAe,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA6DhE"}
@@ -0,0 +1,135 @@
1
+ import { valuesEqual } from "../../resolve-book.js";
2
+ /**
3
+ * `dead-rule` (book): catch rules that statically cannot do what they say.
4
+ * For each rule it flags a `replace` whose source is never present, an `add` of
5
+ * an already-present id, and an `order` naming an id that can never appear.
6
+ *
7
+ * `when` is taken into account: a prior rule only shapes the present-set a later
8
+ * rule sees if it is *guaranteed to co-fire* — i.e. its `when` is implied by the
9
+ * later rule's `when`. This keeps mutually-exclusive single-axis swaps (e.g.
10
+ * `tone=a|b|c` each replacing the same base fragment) from reading as dead,
11
+ * while still catching a replace whose source was unconditionally consumed by an
12
+ * earlier rule.
13
+ *
14
+ * v0 limitation: still a static check. "A rule that never fires under any
15
+ * context" (which requires enumerating contexts) remains out of scope.
16
+ */
17
+ export function deadRule(options = {}) {
18
+ const severity = options.severity ?? "warning";
19
+ return {
20
+ id: "dead-rule",
21
+ description: "Rules should affect fragments that can be present (static check).",
22
+ scope: "book",
23
+ check(input) {
24
+ const findings = [];
25
+ for (const composition of input.book.compositions.values()) {
26
+ const everPresent = everPresentSet(composition);
27
+ composition.rules.forEach((rule, position) => {
28
+ const present = presentSetFor(composition.base, composition.rules, position, rule.when);
29
+ switch (rule.action) {
30
+ case "add":
31
+ for (const id of rule.add ?? []) {
32
+ if (present.has(id)) {
33
+ findings.push({
34
+ ruleId: "dead-rule",
35
+ severity,
36
+ message: `rule #${rule.index} in "${composition.name}" adds "${id}", which is already present`,
37
+ fragmentId: id,
38
+ ruleIndex: rule.index,
39
+ });
40
+ }
41
+ }
42
+ break;
43
+ case "replace":
44
+ for (const from of Object.keys(rule.replace ?? {})) {
45
+ if (!present.has(from)) {
46
+ findings.push({
47
+ ruleId: "dead-rule",
48
+ severity,
49
+ message: `rule #${rule.index} in "${composition.name}" replaces "${from}", which is never present`,
50
+ fragmentId: from,
51
+ ruleIndex: rule.index,
52
+ });
53
+ }
54
+ }
55
+ break;
56
+ case "order":
57
+ for (const id of rule.order ?? []) {
58
+ if (!everPresent.has(id)) {
59
+ findings.push({
60
+ ruleId: "dead-rule",
61
+ severity,
62
+ message: `rule #${rule.index} in "${composition.name}" orders unknown id "${id}"`,
63
+ fragmentId: id,
64
+ ruleIndex: rule.index,
65
+ });
66
+ }
67
+ }
68
+ break;
69
+ case "forbid":
70
+ // `forbid` does not feed the static present-set in v0.
71
+ break;
72
+ }
73
+ });
74
+ }
75
+ return findings;
76
+ },
77
+ };
78
+ }
79
+ /**
80
+ * The present-set a rule sees: `base` mutated by every earlier rule that is
81
+ * guaranteed to co-fire (its `when` is implied by this rule's `when`).
82
+ */
83
+ function presentSetFor(base, rules, position, currentWhen) {
84
+ const present = new Set(base);
85
+ for (let i = 0; i < position; i++) {
86
+ const prior = rules[i];
87
+ if (!prior || !firesWhenever(prior.when, currentWhen)) {
88
+ continue;
89
+ }
90
+ if (prior.action === "add") {
91
+ for (const id of prior.add ?? []) {
92
+ present.add(id);
93
+ }
94
+ }
95
+ else if (prior.action === "replace") {
96
+ for (const [from, to] of Object.entries(prior.replace ?? {})) {
97
+ if (present.has(from)) {
98
+ present.delete(from);
99
+ present.add(to);
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return present;
105
+ }
106
+ /** Every id that can ever appear: base plus all add ids and all replace targets. */
107
+ function everPresentSet(composition) {
108
+ const present = new Set(composition.base);
109
+ for (const rule of composition.rules) {
110
+ if (rule.action === "add") {
111
+ for (const id of rule.add ?? []) {
112
+ present.add(id);
113
+ }
114
+ }
115
+ else if (rule.action === "replace") {
116
+ for (const to of Object.values(rule.replace ?? {})) {
117
+ present.add(to);
118
+ }
119
+ }
120
+ }
121
+ return present;
122
+ }
123
+ /** True if `prior` fires in every context where `current` fires — i.e. every
124
+ * constraint in `prior` is also required (same value) by `current`. An empty
125
+ * `prior` (unconditional) always co-fires. */
126
+ function firesWhenever(prior, current) {
127
+ for (const [key, value] of Object.entries(prior)) {
128
+ const actual = current[key];
129
+ if (actual === undefined || !valuesEqual(actual, value)) {
130
+ return false;
131
+ }
132
+ }
133
+ return true;
134
+ }
135
+ //# sourceMappingURL=dead-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dead-rule.js","sourceRoot":"","sources":["../../../src/lint/rules/dead-rule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { LintRule, Severity } from "../types.js";
2
+ export interface ExampleBalanceOptions {
3
+ /** Fragment `kind` treated as an example. */
4
+ kind?: string;
5
+ /** Tag prefix that groups examples, e.g. "case:". */
6
+ groupTagPrefix?: string;
7
+ /** Maximum allowed difference between the largest and smallest group. */
8
+ maxSkew?: number;
9
+ severity?: Severity;
10
+ }
11
+ /**
12
+ * `example-balance` (resolved): few-shot examples (fragments of the configured
13
+ * kind) are grouped by their `groupTagPrefix` tag (e.g. `case:positive`). When
14
+ * the largest group exceeds the smallest by more than `maxSkew`, attention is
15
+ * skewed and a finding is raised. Examples without a group tag are ignored, so
16
+ * the rule only compares groups that actually have examples.
17
+ */
18
+ export declare function exampleBalance(options?: ExampleBalanceOptions): LintRule;
19
+ //# sourceMappingURL=example-balance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-balance.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/example-balance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,WAAW,qBAAqB;IACpC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,QAAQ,CAgD5E"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * `example-balance` (resolved): few-shot examples (fragments of the configured
3
+ * kind) are grouped by their `groupTagPrefix` tag (e.g. `case:positive`). When
4
+ * the largest group exceeds the smallest by more than `maxSkew`, attention is
5
+ * skewed and a finding is raised. Examples without a group tag are ignored, so
6
+ * the rule only compares groups that actually have examples.
7
+ */
8
+ export function exampleBalance(options = {}) {
9
+ const kind = options.kind ?? "example";
10
+ const groupTagPrefix = options.groupTagPrefix ?? "case:";
11
+ const maxSkew = options.maxSkew ?? 1;
12
+ const severity = options.severity ?? "warning";
13
+ return {
14
+ id: "example-balance",
15
+ description: `Example groups (kind "${kind}", tag "${groupTagPrefix}*") should differ by at most ${maxSkew}.`,
16
+ scope: "resolved",
17
+ check(input) {
18
+ const result = input.result;
19
+ if (result === undefined) {
20
+ return [];
21
+ }
22
+ const counts = new Map();
23
+ for (const id of result.trace.finalOrder) {
24
+ const fragment = input.book.fragments.get(id);
25
+ if (fragment?.kind !== kind) {
26
+ continue;
27
+ }
28
+ for (const tag of fragment.tags ?? []) {
29
+ if (tag.startsWith(groupTagPrefix)) {
30
+ counts.set(tag, (counts.get(tag) ?? 0) + 1);
31
+ }
32
+ }
33
+ }
34
+ if (counts.size < 2) {
35
+ return [];
36
+ }
37
+ const values = [...counts.values()];
38
+ const max = Math.max(...values);
39
+ const min = Math.min(...values);
40
+ if (max - min <= maxSkew) {
41
+ return [];
42
+ }
43
+ const summary = [...counts.entries()]
44
+ .sort(([a], [b]) => a.localeCompare(b))
45
+ .map(([group, count]) => `${group}=${count}`)
46
+ .join(", ");
47
+ return [
48
+ {
49
+ ruleId: "example-balance",
50
+ severity,
51
+ message: `example groups are imbalanced (${summary}); skew ${max - min} exceeds ${maxSkew}`,
52
+ },
53
+ ];
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=example-balance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"example-balance.js","sourceRoot":"","sources":["../../../src/lint/rules/example-balance.ts"],"names":[],"mappings":"AAYA;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAO,GAA0B,EAAE;IAChE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,SAAS,CAAC;IACvC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,OAAO,CAAC;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;IAC/C,OAAO;QACL,EAAE,EAAE,iBAAiB;QACrB,WAAW,EAAE,yBAAyB,IAAI,WAAW,cAAc,gCAAgC,OAAO,GAAG;QAC7G,KAAK,EAAE,UAAU;QACjB,KAAK,CAAC,KAAK;YACT,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;YAC5B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;YACzC,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;gBACzC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC9C,IAAI,QAAQ,EAAE,IAAI,KAAK,IAAI,EAAE,CAAC;oBAC5B,SAAS;gBACX,CAAC;gBACD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;oBACtC,IAAI,GAAG,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;wBACnC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC9C,CAAC;gBACH,CAAC;YACH,CAAC;YACD,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACpB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;YAChC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;YAChC,IAAI,GAAG,GAAG,GAAG,IAAI,OAAO,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;iBAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;iBACtC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,IAAI,KAAK,EAAE,CAAC;iBAC5C,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,OAAO;gBACL;oBACE,MAAM,EAAE,iBAAiB;oBACzB,QAAQ;oBACR,OAAO,EAAE,kCAAkC,OAAO,WAAW,GAAG,GAAG,GAAG,YAAY,OAAO,EAAE;iBAC5F;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { LintRule } from "../types.js";
2
+ /** Options surfaced through {@link defaultRules}; per-rule factories take more. */
3
+ export interface DefaultRulesOptions {
4
+ /** Override the token-budget ceiling. */
5
+ maxTokens?: number;
6
+ /** Override the banned-tokens list. */
7
+ bannedTokens?: (string | RegExp)[];
8
+ }
9
+ /**
10
+ * The built-in rule set, instantiated with optional overrides. Callers can
11
+ * also build their own array and pass it to `lint` to swap the set entirely.
12
+ */
13
+ export declare function defaultRules(options?: DefaultRulesOptions): LintRule[];
14
+ export type { BannedTokensOptions } from "./banned-tokens.js";
15
+ export { bannedTokens } from "./banned-tokens.js";
16
+ export type { DanglingReferenceOptions } from "./dangling-reference.js";
17
+ export { danglingReference } from "./dangling-reference.js";
18
+ export type { DeadRuleOptions } from "./dead-rule.js";
19
+ export { deadRule } from "./dead-rule.js";
20
+ export type { ExampleBalanceOptions } from "./example-balance.js";
21
+ export { exampleBalance } from "./example-balance.js";
22
+ export type { LanguageDirectivePositionOptions } from "./language-directive-position.js";
23
+ export { languageDirectivePosition } from "./language-directive-position.js";
24
+ export type { TokenBudgetOptions } from "./token-budget.js";
25
+ export { estimateTokensByChars, tokenBudget } from "./token-budget.js";
26
+ export type { UnusedFragmentOptions } from "./unused-fragment.js";
27
+ export { unusedFragment } from "./unused-fragment.js";
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lint/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAS5C,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,YAAY,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CACpC;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,QAAQ,EAAE,CAU1E;AAED,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC;AACzF,OAAO,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAC7E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACvE,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { bannedTokens } from "./banned-tokens.js";
2
+ import { danglingReference } from "./dangling-reference.js";
3
+ import { deadRule } from "./dead-rule.js";
4
+ import { exampleBalance } from "./example-balance.js";
5
+ import { languageDirectivePosition } from "./language-directive-position.js";
6
+ import { tokenBudget } from "./token-budget.js";
7
+ import { unusedFragment } from "./unused-fragment.js";
8
+ /**
9
+ * The built-in rule set, instantiated with optional overrides. Callers can
10
+ * also build their own array and pass it to `lint` to swap the set entirely.
11
+ */
12
+ export function defaultRules(options = {}) {
13
+ return [
14
+ tokenBudget(options.maxTokens !== undefined ? { maxTokens: options.maxTokens } : {}),
15
+ languageDirectivePosition(),
16
+ exampleBalance(),
17
+ bannedTokens(options.bannedTokens !== undefined ? { tokens: options.bannedTokens } : {}),
18
+ unusedFragment(),
19
+ danglingReference(),
20
+ deadRule(),
21
+ ];
22
+ }
23
+ export { bannedTokens } from "./banned-tokens.js";
24
+ export { danglingReference } from "./dangling-reference.js";
25
+ export { deadRule } from "./dead-rule.js";
26
+ export { exampleBalance } from "./example-balance.js";
27
+ export { languageDirectivePosition } from "./language-directive-position.js";
28
+ export { estimateTokensByChars, tokenBudget } from "./token-budget.js";
29
+ export { unusedFragment } from "./unused-fragment.js";
30
+ //# sourceMappingURL=index.js.map