@lyse-labs/lyse 0.1.0-alpha.2 → 0.2.0-alpha.1

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 (140) hide show
  1. package/LICENSE +0 -5
  2. package/dist/cli.js +67 -11
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/__tests__/add-ci-gate.test.d.ts +1 -0
  5. package/dist/commands/__tests__/add-ci-gate.test.js +149 -0
  6. package/dist/commands/__tests__/add-ci-gate.test.js.map +1 -0
  7. package/dist/commands/add-ci-gate.d.ts +26 -0
  8. package/dist/commands/add-ci-gate.js +429 -0
  9. package/dist/commands/add-ci-gate.js.map +1 -0
  10. package/dist/commands/audit-pipeline.js +1 -1
  11. package/dist/commands/audit-pipeline.js.map +1 -1
  12. package/dist/commands/fix.d.ts +8 -8
  13. package/dist/commands/init.d.ts +3 -3
  14. package/dist/commands/init.js +1 -1
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/mcp-setup.js +1 -1
  17. package/dist/commands/mcp-setup.js.map +1 -1
  18. package/dist/config/schema.d.ts +14 -14
  19. package/dist/llm/__tests__/layer4-stage.test.d.ts +1 -0
  20. package/dist/llm/__tests__/layer4-stage.test.js +157 -0
  21. package/dist/llm/__tests__/layer4-stage.test.js.map +1 -0
  22. package/dist/llm/__tests__/validator.test.d.ts +1 -0
  23. package/dist/llm/__tests__/validator.test.js +156 -0
  24. package/dist/llm/__tests__/validator.test.js.map +1 -0
  25. package/dist/llm/connectors/__tests__/anthropic-adapter.test.d.ts +1 -0
  26. package/dist/llm/connectors/__tests__/anthropic-adapter.test.js +99 -0
  27. package/dist/llm/connectors/__tests__/anthropic-adapter.test.js.map +1 -0
  28. package/dist/llm/connectors/__tests__/cache.test.d.ts +1 -0
  29. package/dist/llm/connectors/__tests__/cache.test.js +50 -0
  30. package/dist/llm/connectors/__tests__/cache.test.js.map +1 -0
  31. package/dist/llm/connectors/__tests__/noop-adapter.test.d.ts +1 -0
  32. package/dist/llm/connectors/__tests__/noop-adapter.test.js +21 -0
  33. package/dist/llm/connectors/__tests__/noop-adapter.test.js.map +1 -0
  34. package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.d.ts +1 -0
  35. package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.js +82 -0
  36. package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.js.map +1 -0
  37. package/dist/llm/connectors/__tests__/resolver.test.d.ts +1 -0
  38. package/dist/llm/connectors/__tests__/resolver.test.js +140 -0
  39. package/dist/llm/connectors/__tests__/resolver.test.js.map +1 -0
  40. package/dist/llm/connectors/anthropic-adapter.d.ts +12 -0
  41. package/dist/llm/connectors/anthropic-adapter.js +57 -0
  42. package/dist/llm/connectors/anthropic-adapter.js.map +1 -0
  43. package/dist/llm/connectors/cache.d.ts +15 -0
  44. package/dist/llm/connectors/cache.js +57 -0
  45. package/dist/llm/connectors/cache.js.map +1 -0
  46. package/dist/llm/connectors/noop-adapter.d.ts +4 -0
  47. package/dist/llm/connectors/noop-adapter.js +12 -0
  48. package/dist/llm/connectors/noop-adapter.js.map +1 -0
  49. package/dist/llm/connectors/openai-compatible-adapter.d.ts +13 -0
  50. package/dist/llm/connectors/openai-compatible-adapter.js +47 -0
  51. package/dist/llm/connectors/openai-compatible-adapter.js.map +1 -0
  52. package/dist/llm/connectors/resolver.d.ts +9 -0
  53. package/dist/llm/connectors/resolver.js +162 -0
  54. package/dist/llm/connectors/resolver.js.map +1 -0
  55. package/dist/llm/connectors/types.d.ts +25 -0
  56. package/dist/llm/connectors/types.js +13 -0
  57. package/dist/llm/connectors/types.js.map +1 -0
  58. package/dist/llm/layer4-stage.d.ts +8 -6
  59. package/dist/llm/layer4-stage.js +145 -9
  60. package/dist/llm/layer4-stage.js.map +1 -1
  61. package/dist/llm/rubric-stub.d.ts +8 -0
  62. package/dist/llm/rubric-stub.js +4 -0
  63. package/dist/llm/rubric-stub.js.map +1 -0
  64. package/dist/llm/validator.d.ts +18 -0
  65. package/dist/llm/validator.js +81 -0
  66. package/dist/llm/validator.js.map +1 -0
  67. package/dist/parsers/ai-tokens.d.ts +10 -0
  68. package/dist/parsers/ai-tokens.js +156 -0
  69. package/dist/parsers/ai-tokens.js.map +1 -0
  70. package/dist/reliability/catalogue/__tests__/sub-axes.test.js +13 -3
  71. package/dist/reliability/catalogue/__tests__/sub-axes.test.js.map +1 -1
  72. package/dist/reliability/catalogue/sub-axes.js +16 -0
  73. package/dist/reliability/catalogue/sub-axes.js.map +1 -1
  74. package/dist/reliability/types.d.ts +1 -1
  75. package/dist/rule-runner.js +1 -1
  76. package/dist/rule-runner.js.map +1 -1
  77. package/dist/rules/ai-governance-ai-content-live-region.d.ts +11 -0
  78. package/dist/rules/ai-governance-ai-content-live-region.js +304 -0
  79. package/dist/rules/ai-governance-ai-content-live-region.js.map +1 -0
  80. package/dist/rules/ai-governance-ai-loading-error-states.d.ts +9 -0
  81. package/dist/rules/ai-governance-ai-loading-error-states.js +214 -0
  82. package/dist/rules/ai-governance-ai-loading-error-states.js.map +1 -0
  83. package/dist/rules/ai-governance-ai-marker-anti-patterns.d.ts +15 -0
  84. package/dist/rules/ai-governance-ai-marker-anti-patterns.js +178 -0
  85. package/dist/rules/ai-governance-ai-marker-anti-patterns.js.map +1 -0
  86. package/dist/rules/ai-governance-ai-marker-component-present.d.ts +28 -0
  87. package/dist/rules/ai-governance-ai-marker-component-present.js +320 -0
  88. package/dist/rules/ai-governance-ai-marker-component-present.js.map +1 -0
  89. package/dist/rules/ai-governance-ai-token-requires-marker.d.ts +17 -0
  90. package/dist/rules/ai-governance-ai-token-requires-marker.js +206 -0
  91. package/dist/rules/ai-governance-ai-token-requires-marker.js.map +1 -0
  92. package/dist/rules/ai-governance-ai-tokens-reserved.d.ts +8 -0
  93. package/dist/rules/ai-governance-ai-tokens-reserved.js +85 -0
  94. package/dist/rules/ai-governance-ai-tokens-reserved.js.map +1 -0
  95. package/dist/rules/ai-governance-disclaimer-present.d.ts +18 -0
  96. package/dist/rules/ai-governance-disclaimer-present.js +210 -0
  97. package/dist/rules/ai-governance-disclaimer-present.js.map +1 -0
  98. package/dist/rules/ai-governance-explainability-affordance.d.ts +14 -0
  99. package/dist/rules/ai-governance-explainability-affordance.js +196 -0
  100. package/dist/rules/ai-governance-explainability-affordance.js.map +1 -0
  101. package/dist/rules/ai-governance-feedback-control-present.d.ts +19 -0
  102. package/dist/rules/ai-governance-feedback-control-present.js +223 -0
  103. package/dist/rules/ai-governance-feedback-control-present.js.map +1 -0
  104. package/dist/rules/ai-governance-human-control-affordances.d.ts +16 -0
  105. package/dist/rules/ai-governance-human-control-affordances.js +228 -0
  106. package/dist/rules/ai-governance-human-control-affordances.js.map +1 -0
  107. package/dist/rules/ai-governance-value-gate-doc-present.d.ts +18 -0
  108. package/dist/rules/ai-governance-value-gate-doc-present.js +206 -0
  109. package/dist/rules/ai-governance-value-gate-doc-present.js.map +1 -0
  110. package/dist/rules/ai-surface-agent-instruction-files.d.ts +33 -0
  111. package/dist/rules/ai-surface-agent-instruction-files.js +310 -0
  112. package/dist/rules/ai-surface-agent-instruction-files.js.map +1 -0
  113. package/dist/rules/ai-surface-llms-txt-structure.d.ts +18 -0
  114. package/dist/rules/ai-surface-llms-txt-structure.js +200 -0
  115. package/dist/rules/ai-surface-llms-txt-structure.js.map +1 -0
  116. package/dist/rules/ai-surface-mcp-config-present.d.ts +23 -0
  117. package/dist/rules/ai-surface-mcp-config-present.js +193 -0
  118. package/dist/rules/ai-surface-mcp-config-present.js.map +1 -0
  119. package/dist/rules/ai-surface-shadcn-registry-valid.d.ts +22 -0
  120. package/dist/rules/ai-surface-shadcn-registry-valid.js +247 -0
  121. package/dist/rules/ai-surface-shadcn-registry-valid.js.map +1 -0
  122. package/dist/rules/components-contracts-strictness.d.ts +48 -0
  123. package/dist/rules/components-contracts-strictness.js +372 -0
  124. package/dist/rules/components-contracts-strictness.js.map +1 -0
  125. package/dist/rules/registry.js +32 -0
  126. package/dist/rules/registry.js.map +1 -1
  127. package/dist/rules/tokens-dtcg-conformance.d.ts +55 -19
  128. package/dist/rules/tokens-dtcg-conformance.js +320 -82
  129. package/dist/rules/tokens-dtcg-conformance.js.map +1 -1
  130. package/dist/scorer.js +4 -3
  131. package/dist/scorer.js.map +1 -1
  132. package/dist/types.d.ts +2 -2
  133. package/dist/types.js.map +1 -1
  134. package/package.json +41 -16
  135. package/rules-manifest.json +431 -6
  136. package/schemas/v1/lyse-rules.json +1 -1
  137. package/dist/commands/ci-setup.d.ts +0 -9
  138. package/dist/commands/ci-setup.js +0 -42
  139. package/dist/commands/ci-setup.js.map +0 -1
  140. package/dist/commands/templates/lyse-workflow.yml.template +0 -30
@@ -0,0 +1,320 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import fg from "fast-glob";
4
+ import { createLyseRule } from "./_rule-module.js";
5
+ import { detectReservedAiTokens } from "../parsers/ai-tokens.js";
6
+ const RULE_ID = "ai-governance/ai-marker-component-present";
7
+ const MAX_ALLOWLIST_FILE_BYTES = 1_000_000;
8
+ const DISABLE_DIRECTIVE = `lyse-disable ${RULE_ID}`;
9
+ // Exported so sibling rules 3.3 / 3.5 can reuse the same vocabulary without
10
+ // duplicating the set.
11
+ export const AI_MARKER_NAMES = new Set([
12
+ "ailabel",
13
+ "aibadge",
14
+ "aitag",
15
+ "aiindicator",
16
+ "aimarker",
17
+ "aiavatar",
18
+ "genaiavatar",
19
+ "genaibadge",
20
+ "genailabel",
21
+ "genaitag",
22
+ ]);
23
+ // Polaris `magic-*` prefix matched separately (prefix match, not set lookup).
24
+ const MAGIC_PREFIX = "magic-";
25
+ const ALLOWLIST_CANDIDATES = [
26
+ "README.md",
27
+ "README",
28
+ "README.mdx",
29
+ "readme.md",
30
+ ".lyse.yaml",
31
+ ".lyse.yml",
32
+ ];
33
+ const INDEX_CANDIDATES = [
34
+ "src/index.ts",
35
+ "src/index.tsx",
36
+ "index.ts",
37
+ "index.tsx",
38
+ ];
39
+ // Shared across all component-scanning ai-governance rules.
40
+ export const COMPONENT_GLOB = "**/*.{tsx,jsx,vue}";
41
+ export const SCAN_IGNORE = [
42
+ "**/node_modules/**",
43
+ "**/dist/**",
44
+ "**/build/**",
45
+ "**/.git/**",
46
+ "**/.next/**",
47
+ "**/out/**",
48
+ "**/coverage/**",
49
+ ];
50
+ // Keep the module-level alias used internally.
51
+ const IGNORE = SCAN_IGNORE;
52
+ const MAX_FILE_BYTES = 1_000_000;
53
+ const NAMED_EXPORT_RE = /\bexport\s+(?:default\s+)?(?:function\s+([A-Za-z_$][\w$]*)|(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=)/g;
54
+ const NAMED_EXPORT_BLOCK_RE = /\bexport\s*\{([^}]+)\}/g;
55
+ const VUE_COMPONENT_NAME_RE = /(?:name\s*:\s*['"]([A-Za-z_$][\w$]*)['"])/g;
56
+ function isAllowlisted(repoRoot) {
57
+ for (const candidate of ALLOWLIST_CANDIDATES) {
58
+ const abs = join(repoRoot, candidate);
59
+ if (!existsSync(abs))
60
+ continue;
61
+ try {
62
+ const stat = statSync(abs);
63
+ if (!stat.isFile() || stat.size > MAX_ALLOWLIST_FILE_BYTES)
64
+ continue;
65
+ const raw = readFileSync(abs, "utf8");
66
+ if (raw.includes(DISABLE_DIRECTIVE))
67
+ return true;
68
+ }
69
+ catch {
70
+ // unreadable — fall through
71
+ }
72
+ }
73
+ return false;
74
+ }
75
+ export function isAiMarkerName(name) {
76
+ const lower = name.toLowerCase();
77
+ if (AI_MARKER_NAMES.has(lower))
78
+ return true;
79
+ if (lower.startsWith(MAGIC_PREFIX))
80
+ return true;
81
+ if (lower.startsWith("genai"))
82
+ return true;
83
+ if (lower.includes("aimarker"))
84
+ return true;
85
+ if (lower.includes("aiavatar"))
86
+ return true;
87
+ if (lower.includes("aiindicator"))
88
+ return true;
89
+ return false;
90
+ }
91
+ export function safeReadText(absPath) {
92
+ try {
93
+ const stat = statSync(absPath);
94
+ if (!stat.isFile() || stat.size > MAX_FILE_BYTES || stat.size === 0)
95
+ return null;
96
+ return readFileSync(absPath, "utf8");
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ export function extractNamesFromSource(source) {
103
+ const names = [];
104
+ NAMED_EXPORT_RE.lastIndex = 0;
105
+ let m;
106
+ while ((m = NAMED_EXPORT_RE.exec(source)) !== null) {
107
+ const name = m[1] ?? m[2];
108
+ if (name)
109
+ names.push(name);
110
+ }
111
+ NAMED_EXPORT_BLOCK_RE.lastIndex = 0;
112
+ while ((m = NAMED_EXPORT_BLOCK_RE.exec(source)) !== null) {
113
+ const block = m[1] ?? "";
114
+ for (const part of block.split(",")) {
115
+ const tokens = part.trim().split(/\s+as\s+/);
116
+ const surfaceName = tokens.length > 1 ? tokens[1] : tokens[0];
117
+ if (surfaceName?.trim())
118
+ names.push(surfaceName.trim());
119
+ }
120
+ }
121
+ return names;
122
+ }
123
+ export function extractVueNames(source) {
124
+ const names = [];
125
+ VUE_COMPONENT_NAME_RE.lastIndex = 0;
126
+ let m;
127
+ while ((m = VUE_COMPONENT_NAME_RE.exec(source)) !== null) {
128
+ if (m[1])
129
+ names.push(m[1]);
130
+ }
131
+ return names;
132
+ }
133
+ export function deriveComponentNameFromPath(relPath) {
134
+ const parts = relPath.split("/");
135
+ const file = parts[parts.length - 1] ?? "";
136
+ return file.replace(/\.(tsx|jsx|vue)$/, "");
137
+ }
138
+ // Shared per-file AI-marker check used by explainability, feedback-control,
139
+ // human-control-affordances, and ai-loading-error-states rules.
140
+ export function fileHasAiMarker(source, relPath) {
141
+ const names = relPath.endsWith(".vue")
142
+ ? extractVueNames(source)
143
+ : extractNamesFromSource(source);
144
+ if (names.some((n) => isAiMarkerName(n)))
145
+ return true;
146
+ for (const m of source.matchAll(/<\s*([A-Za-z][\w.-]*)/g)) {
147
+ if (m[1] && isAiMarkerName(m[1]))
148
+ return true;
149
+ }
150
+ return false;
151
+ }
152
+ // Factory for the per-rule allowlist check. Each rule passes its own
153
+ // DISABLE_DIRECTIVE so the check reads the same files but looks for the
154
+ // rule-specific disable comment.
155
+ export function makeAllowlistCheck(disableDirective) {
156
+ return function isAllowlistedFor(repoRoot) {
157
+ for (const candidate of ALLOWLIST_CANDIDATES) {
158
+ const abs = join(repoRoot, candidate);
159
+ if (!existsSync(abs))
160
+ continue;
161
+ try {
162
+ const stat = statSync(abs);
163
+ if (!stat.isFile() || stat.size > MAX_ALLOWLIST_FILE_BYTES)
164
+ continue;
165
+ const raw = readFileSync(abs, "utf8");
166
+ if (raw.includes(disableDirective))
167
+ return true;
168
+ }
169
+ catch {
170
+ // unreadable — fall through
171
+ }
172
+ }
173
+ return false;
174
+ };
175
+ }
176
+ export function scanForMarkerComponents(repoRoot) {
177
+ const found = [];
178
+ for (const candidate of INDEX_CANDIDATES) {
179
+ const abs = join(repoRoot, candidate);
180
+ const source = safeReadText(abs);
181
+ if (!source)
182
+ continue;
183
+ for (const name of extractNamesFromSource(source)) {
184
+ if (isAiMarkerName(name))
185
+ found.push(name);
186
+ }
187
+ }
188
+ let componentFiles = [];
189
+ try {
190
+ componentFiles = fg.sync(COMPONENT_GLOB, {
191
+ cwd: repoRoot,
192
+ absolute: false,
193
+ dot: false,
194
+ ignore: IGNORE,
195
+ onlyFiles: true,
196
+ unique: true,
197
+ });
198
+ }
199
+ catch {
200
+ // non-fatal
201
+ }
202
+ for (const rel of componentFiles) {
203
+ const baseName = deriveComponentNameFromPath(rel);
204
+ if (isAiMarkerName(baseName)) {
205
+ found.push(baseName);
206
+ continue;
207
+ }
208
+ const source = safeReadText(join(repoRoot, rel));
209
+ if (!source)
210
+ continue;
211
+ const names = rel.endsWith(".vue")
212
+ ? extractVueNames(source)
213
+ : extractNamesFromSource(source);
214
+ for (const name of names) {
215
+ if (isAiMarkerName(name))
216
+ found.push(name);
217
+ }
218
+ }
219
+ const seen = new Set();
220
+ const deduped = [];
221
+ for (const name of found) {
222
+ const key = name.toLowerCase();
223
+ if (!seen.has(key)) {
224
+ seen.add(key);
225
+ deduped.push(name);
226
+ }
227
+ }
228
+ return deduped.sort();
229
+ }
230
+ const evaluate = async (ctx, _files) => {
231
+ const findings = [];
232
+ if (!ctx.repoRoot) {
233
+ return { findings, opportunities: 0 };
234
+ }
235
+ if (isAllowlisted(ctx.repoRoot)) {
236
+ return { findings, opportunities: 0 };
237
+ }
238
+ const markerComponents = scanForMarkerComponents(ctx.repoRoot);
239
+ const reservedTokens = detectReservedAiTokens(ctx.repoRoot);
240
+ if (markerComponents.length === 0 && reservedTokens.length === 0) {
241
+ return { findings, opportunities: 0 };
242
+ }
243
+ if (markerComponents.length > 0) {
244
+ const list = markerComponents.join(", ");
245
+ findings.push({
246
+ ruleId: RULE_ID,
247
+ axis: "ai-governance",
248
+ severity: "info",
249
+ location: { file: "src/index.ts", line: 1, column: 1 },
250
+ message: `AI-marker component${markerComponents.length === 1 ? "" : "s"} detected: ${list}`,
251
+ suggestion: "AI-marker components found — ensure they are applied consistently on AI-generated surfaces (Track 3.3 will enforce the pairing with reserved tokens)",
252
+ });
253
+ return { findings, opportunities: 1 };
254
+ }
255
+ // Reserved tokens present but no marker component — cross-condition warning
256
+ findings.push({
257
+ ruleId: RULE_ID,
258
+ axis: "ai-governance",
259
+ severity: "warning",
260
+ location: { file: "tokens.json", line: 1, column: 1 },
261
+ message: `Reserved AI tokens are present (${reservedTokens.length} found) but no AI-marker component was detected in the export surface or component files`,
262
+ suggestion: "ship a dedicated AI-marker component (e.g. AILabel, AIBadge, or a magic-prefixed component) so consumers can visually mark AI-generated content",
263
+ });
264
+ return { findings, opportunities: 1 };
265
+ };
266
+ export const rule = createLyseRule({
267
+ meta: {
268
+ axis: "ai-governance",
269
+ lyseRuleId: RULE_ID,
270
+ defaultSeverity: "warning",
271
+ shortDescription: "Detect AI-marker component in the DS export surface",
272
+ fullDescription: "Scans the design system's export surface (`src/index.ts`, `index.ts`, etc.) and component files (`**/*.{tsx,jsx,vue}`) for a dedicated AI-marker component — a label, badge, avatar, or indicator that visually marks AI-generated output. Recognised vocabularies: Carbon `AILabel`, generic `AIBadge` / `AITag` / `AIIndicator` / `AIAvatar`, `GenAI*` variants, `*AIMarker*`, and Polaris `magic-*` components. Emits `info` when a marker component is found; emits `warning` when reserved AI tokens exist (detected by the shared `detectReservedAiTokens` parser) but no marker component is present; emits nothing when the DS has no AI surface at all.",
273
+ helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-marker-component-present.md",
274
+ rationale: `Why it matters
275
+
276
+ AI-marker components are the visual contract between the design system and consumers: they signal "this content was produced by AI." Without a dedicated component, individual teams reinvent ad-hoc markers, breaking consistency and accessibility.
277
+
278
+ The most important case to flag is a DS that ships reserved AI tokens (signaling AI-surface intent) but provides no corresponding component — consumers have no standardised way to mark AI provenance visually.
279
+
280
+ This rule emits \`info\` when a marker component is detected (inventory), and \`warning\` when reserved tokens exist but no marker component is found. A DS with no AI surface emits nothing and is not penalised.
281
+
282
+ The exported \`AI_MARKER_NAMES\` constant is shared with sibling rules (Track 3.3 / 3.5) to ensure a single vocabulary source of truth.`,
283
+ examples: [
284
+ {
285
+ good: "// src/index.ts\nexport { AILabel } from './ai-label';\nexport { Button } from './button';",
286
+ bad: "// src/index.ts — no AI-marker component exported\nexport { Button } from './button';",
287
+ },
288
+ {
289
+ good: "// AILabel.tsx — component file named with the marker vocabulary",
290
+ bad: "// tokens.json has `color.ai.primary` but no AILabel/AIBadge component exists",
291
+ },
292
+ {
293
+ good: "// Polaris-style: magic-icon.tsx component file detected",
294
+ bad: "// Reserved tokens present, no marker component shipped",
295
+ },
296
+ ],
297
+ allowlist: [
298
+ "repos containing `lyse-disable ai-governance/ai-marker-component-present` in an adjacent README or `.lyse.yaml` — rule is N/A",
299
+ "repos with no reserved AI tokens AND no marker component — no AI surface detected, rule emits nothing",
300
+ "files larger than 1 MB — skipped to avoid pathological cases",
301
+ "files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
302
+ ],
303
+ },
304
+ defaultOptions: [],
305
+ create: () => ({ evaluate }),
306
+ });
307
+ export const _internal = {
308
+ isAiMarkerName,
309
+ isAllowlisted,
310
+ scanForMarkerComponents,
311
+ extractNamesFromSource,
312
+ DISABLE_DIRECTIVE,
313
+ ALLOWLIST_CANDIDATES,
314
+ COMPONENT_GLOB,
315
+ SCAN_IGNORE,
316
+ deriveComponentNameFromPath,
317
+ fileHasAiMarker,
318
+ makeAllowlistCheck,
319
+ };
320
+ //# sourceMappingURL=ai-governance-ai-marker-component-present.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-governance-ai-marker-component-present.js","sourceRoot":"","sources":["../../src/rules/ai-governance-ai-marker-component-present.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Rule } from "../types.js";
2
+ interface ComponentAnalysis {
3
+ usesReservedToken: boolean;
4
+ hasAiMarker: boolean;
5
+ confidence: "high" | "low";
6
+ tokenRefs: string[];
7
+ }
8
+ declare function analyseComponent(source: string): ComponentAnalysis;
9
+ export declare const rule: Rule;
10
+ export declare const _internal: {
11
+ analyseComponent: typeof analyseComponent;
12
+ isAllowlisted: (repoRoot: string) => boolean;
13
+ DISABLE_DIRECTIVE: string;
14
+ ALLOWLIST_CANDIDATES: string[];
15
+ AI_MARKER_NAMES: ReadonlySet<string>;
16
+ };
17
+ export {};
@@ -0,0 +1,206 @@
1
+ import { join } from "node:path";
2
+ import fg from "fast-glob";
3
+ import { createLyseRule } from "./_rule-module.js";
4
+ import { detectReservedAiTokens, isReservedTokenName } from "../parsers/ai-tokens.js";
5
+ import { AI_MARKER_NAMES, isAiMarkerName, safeReadText, COMPONENT_GLOB, SCAN_IGNORE, makeAllowlistCheck, } from "./ai-governance-ai-marker-component-present.js";
6
+ const RULE_ID = "ai-governance/ai-token-requires-marker";
7
+ const DISABLE_DIRECTIVE = `lyse-disable ${RULE_ID}`;
8
+ const ALLOWLIST_CANDIDATES = [
9
+ "README.md",
10
+ "README",
11
+ "README.mdx",
12
+ "readme.md",
13
+ ".lyse.yaml",
14
+ ".lyse.yml",
15
+ ];
16
+ const isAllowlisted = makeAllowlistCheck(DISABLE_DIRECTIVE);
17
+ // Matches `var(--token-name)` CSS-in-JS / inline style references.
18
+ const CSS_VAR_RE = /var\(\s*(--[a-zA-Z_][a-zA-Z0-9_-]*)\s*\)/g;
19
+ // Matches bare `--token-name` references (not declarations — no trailing colon).
20
+ // Used in JSX style props such as style={{ color: `--ai-primary` }} or template literals.
21
+ const BARE_CSS_TOKEN_RE = /(?<![a-zA-Z0-9_-])(--[a-zA-Z_][a-zA-Z0-9_-]*)(?!\s*:)(?![a-zA-Z0-9_-])/g;
22
+ // Matches dot-path token references like `color.ai.primary`, `tokens["color"]["ai"]`.
23
+ // Segment-level: at least one segment must be reserved.
24
+ const DOT_TOKEN_RE = /\b([a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*){1,})\b/g;
25
+ // Matches JSX element opening tags: <AILabel, <AiBadge, <magic-icon, etc.
26
+ const JSX_TAG_RE = /<([A-Za-z][A-Za-z0-9_-]*)(?:\s|\/|>)/g;
27
+ // Matches `data-ai` or `data-ai-*` attribute (explicit AI annotation).
28
+ const DATA_AI_ATTR_RE = /\bdata-ai(?:-[a-z][a-z0-9-]*)?\b/;
29
+ function analyseComponent(source) {
30
+ const tokenRefs = [];
31
+ // 1. Detect reserved token usage via var(--...) references.
32
+ CSS_VAR_RE.lastIndex = 0;
33
+ let m;
34
+ while ((m = CSS_VAR_RE.exec(source)) !== null) {
35
+ const name = m[1];
36
+ if (name && isReservedTokenName(name))
37
+ tokenRefs.push(name);
38
+ }
39
+ // 2. Detect bare --token-name references (not declarations).
40
+ BARE_CSS_TOKEN_RE.lastIndex = 0;
41
+ while ((m = BARE_CSS_TOKEN_RE.exec(source)) !== null) {
42
+ const name = m[1];
43
+ if (name && isReservedTokenName(name) && !tokenRefs.includes(name))
44
+ tokenRefs.push(name);
45
+ }
46
+ // 3. Detect dot-path token references in JS/TS expressions.
47
+ DOT_TOKEN_RE.lastIndex = 0;
48
+ while ((m = DOT_TOKEN_RE.exec(source)) !== null) {
49
+ const path = m[1];
50
+ if (path && isReservedTokenName(path) && !tokenRefs.includes(path))
51
+ tokenRefs.push(path);
52
+ }
53
+ const usesReservedToken = tokenRefs.length > 0;
54
+ // 4. Detect AI-marker presence: JSX component tags.
55
+ let markerViaJsx = false;
56
+ JSX_TAG_RE.lastIndex = 0;
57
+ while ((m = JSX_TAG_RE.exec(source)) !== null) {
58
+ const tag = m[1];
59
+ if (tag && isAiMarkerName(tag)) {
60
+ markerViaJsx = true;
61
+ break;
62
+ }
63
+ }
64
+ // 5. Detect data-ai attribute (explicit programmatic annotation).
65
+ const markerViaDataAttr = DATA_AI_ATTR_RE.test(source);
66
+ const hasAiMarker = markerViaJsx || markerViaDataAttr;
67
+ // 6. Classify confidence.
68
+ // HIGH: token usage is unambiguous (var(--...) form) AND marker absence/presence is clear.
69
+ // LOW: only dot-path references present (may be false positives from variable names).
70
+ const tokenIsUnambiguous = (() => {
71
+ CSS_VAR_RE.lastIndex = 0;
72
+ BARE_CSS_TOKEN_RE.lastIndex = 0;
73
+ let hasCssRef = false;
74
+ let mm;
75
+ while ((mm = CSS_VAR_RE.exec(source)) !== null) {
76
+ const name = mm[1];
77
+ if (name && isReservedTokenName(name)) {
78
+ hasCssRef = true;
79
+ break;
80
+ }
81
+ }
82
+ if (!hasCssRef) {
83
+ while ((mm = BARE_CSS_TOKEN_RE.exec(source)) !== null) {
84
+ const name = mm[1];
85
+ if (name && isReservedTokenName(name)) {
86
+ hasCssRef = true;
87
+ break;
88
+ }
89
+ }
90
+ }
91
+ return hasCssRef;
92
+ })();
93
+ let confidence;
94
+ if (!usesReservedToken) {
95
+ confidence = "high";
96
+ }
97
+ else if (tokenIsUnambiguous) {
98
+ confidence = "high";
99
+ }
100
+ else {
101
+ confidence = "low";
102
+ }
103
+ return { usesReservedToken, hasAiMarker, confidence, tokenRefs };
104
+ }
105
+ const evaluate = async (ctx, _files) => {
106
+ const findings = [];
107
+ if (!ctx.repoRoot)
108
+ return { findings, opportunities: 0 };
109
+ if (isAllowlisted(ctx.repoRoot))
110
+ return { findings, opportunities: 0 };
111
+ // Fast-exit: no reserved tokens declared at all → nothing to check.
112
+ const reservedTokens = detectReservedAiTokens(ctx.repoRoot);
113
+ if (reservedTokens.length === 0)
114
+ return { findings, opportunities: 0 };
115
+ let componentFiles = [];
116
+ try {
117
+ componentFiles = fg.sync(COMPONENT_GLOB, {
118
+ cwd: ctx.repoRoot,
119
+ absolute: false,
120
+ dot: false,
121
+ ignore: SCAN_IGNORE,
122
+ onlyFiles: true,
123
+ unique: true,
124
+ });
125
+ }
126
+ catch {
127
+ return { findings, opportunities: 0 };
128
+ }
129
+ let opportunities = 0;
130
+ for (const rel of componentFiles) {
131
+ const source = safeReadText(join(ctx.repoRoot, rel));
132
+ if (source === null)
133
+ continue;
134
+ const analysis = analyseComponent(source);
135
+ if (!analysis.usesReservedToken)
136
+ continue;
137
+ opportunities++;
138
+ if (analysis.hasAiMarker)
139
+ continue;
140
+ if (analysis.confidence === "low")
141
+ continue;
142
+ const tokenList = analysis.tokenRefs.slice(0, 5).join(", ");
143
+ const more = analysis.tokenRefs.length > 5 ? ` +${analysis.tokenRefs.length - 5} more` : "";
144
+ findings.push({
145
+ ruleId: RULE_ID,
146
+ axis: "ai-governance",
147
+ severity: "error",
148
+ location: { file: rel, line: 1, column: 1 },
149
+ message: `Component uses reserved AI token(s) (${tokenList}${more}) but renders no AI-marker`,
150
+ suggestion: "Add an AI-marker component (e.g. AILabel, AIBadge, or a magic-* component) alongside AI-generated content, or annotate the element with `data-ai`.",
151
+ confidence: "high",
152
+ });
153
+ }
154
+ return { findings, opportunities };
155
+ };
156
+ const classifyConfidence = (finding, _ctx) => {
157
+ return finding.confidence ?? "high";
158
+ };
159
+ export const rule = createLyseRule({
160
+ meta: {
161
+ axis: "ai-governance",
162
+ lyseRuleId: RULE_ID,
163
+ defaultSeverity: "error",
164
+ shortDescription: "AI token usage requires a co-located AI-marker (Carbon mandatory composite)",
165
+ fullDescription: "For each component file (`**/*.{tsx,jsx,vue}`): if the file references a reserved AI design token (`var(--ai-*)`, `--p-color-*-magic*`, `color.ai.*`, `dragon-fruit`, etc.) it MUST also render an AI-marker — a JSX element whose name is in the shared `AI_MARKER_NAMES` vocabulary (imported from `ai-governance/ai-marker-component-present`), a `magic-*`-prefixed tag, or an explicit `data-ai` attribute. Token usage without a co-located marker is an `error`. Confidence is HIGH only when token detection is via unambiguous `var(--…)` or bare `--token` references; dot-path heuristic hits are LOW-confidence and suppressed by default. The rule is a no-op when no reserved tokens are declared anywhere in the repo (fast-exit via `detectReservedAiTokens`).",
166
+ helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-token-requires-marker.md",
167
+ rationale: `Why it matters
168
+
169
+ IBM Carbon's AI design system mandates the composite: every AI-generated surface must both (a) consume an AI-marker token for visual styling and (b) render a labelling component (AILabel, AIBadge, etc.) so the provenance is legible to users. Without this pairing, the marker token is applied silently — the UI looks "AI-styled" without any transparency cue, which violates IBM's own guidance and emerging regulatory expectations around AI disclosure.
170
+
171
+ Lyse enforces this as an \`error\` (not \`warning\`) because the composite is binary: either both halves are present (correct) or one is missing (incorrect — always a bug or oversight, not a style preference). The fast-exit on \`detectReservedAiTokens\` means the rule is a zero-cost no-op for DS repos with no AI surface.
172
+
173
+ The marker vocabulary (\`AI_MARKER_NAMES\`) is shared with sibling rule \`ai-governance/ai-marker-component-present\` to keep a single source of truth for what counts as a valid AI marker.`,
174
+ examples: [
175
+ {
176
+ good: "// AICard.tsx — uses var(--ai-gradient) AND renders <AILabel>\nconst AICard = () => (\n <div style={{ background: 'var(--ai-gradient)' }}>\n <AILabel>AI-generated</AILabel>\n {content}\n </div>\n);",
177
+ bad: "// AICard.tsx — uses var(--ai-gradient) but no AI-marker rendered\nconst AICard = () => (\n <div style={{ background: 'var(--ai-gradient)' }}>\n {content}\n </div>\n);",
178
+ },
179
+ {
180
+ good: "// AnswerCard.tsx — data-ai attribute used as explicit annotation\n<div data-ai style={{ background: 'var(--p-color-bg-magic)' }}>\n {aiAnswer}\n</div>",
181
+ bad: "// AnswerCard.tsx — magic token applied, no marker whatsoever\n<div style={{ background: 'var(--p-color-bg-magic)' }}>\n {aiAnswer}\n</div>",
182
+ },
183
+ {
184
+ good: "// Component.tsx — no AI tokens, no AI-marker needed\nconst Card = () => <div style={{ color: 'var(--color-primary)' }}>{content}</div>;",
185
+ bad: "// AIAssistant.vue — uses --ai-surface token, missing data-ai or AIBadge\n<template><div :style=\"{ background: 'var(--ai-surface)' }\">{{ answer }}</div></template>",
186
+ },
187
+ ],
188
+ allowlist: [
189
+ "component files larger than 1 MB — skipped",
190
+ "files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
191
+ "repos with no reserved AI tokens anywhere — rule is a no-op (zero findings, zero opportunities)",
192
+ "findings where token reference is ambiguous (dot-path heuristic only) — emitted as LOW confidence and suppressed by default",
193
+ ],
194
+ },
195
+ defaultOptions: [],
196
+ create: () => ({ evaluate }),
197
+ classifyConfidence,
198
+ });
199
+ export const _internal = {
200
+ analyseComponent,
201
+ isAllowlisted,
202
+ DISABLE_DIRECTIVE,
203
+ ALLOWLIST_CANDIDATES,
204
+ AI_MARKER_NAMES,
205
+ };
206
+ //# sourceMappingURL=ai-governance-ai-token-requires-marker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-governance-ai-token-requires-marker.js","sourceRoot":"","sources":["../../src/rules/ai-governance-ai-token-requires-marker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,WAAW,CAAC;AAU3B,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AACtF,OAAO,EACL,eAAe,EACf,cAAc,EACd,YAAY,EACZ,cAAc,EACd,WAAW,EACX,kBAAkB,GACnB,MAAM,gDAAgD,CAAC;AAExD,MAAM,OAAO,GAAG,wCAAwC,CAAC;AACzD,MAAM,iBAAiB,GAAG,gBAAgB,OAAO,EAAE,CAAC;AACpD,MAAM,oBAAoB,GAAG;IAC3B,WAAW;IACX,QAAQ;IACR,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,WAAW;CACZ,CAAC;AAEF,MAAM,aAAa,GAAG,kBAAkB,CAAC,iBAAiB,CAAC,CAAC;AAE5D,mEAAmE;AACnE,MAAM,UAAU,GAAG,2CAA2C,CAAC;AAE/D,iFAAiF;AACjF,0FAA0F;AAC1F,MAAM,iBAAiB,GAAG,yEAAyE,CAAC;AAEpG,sFAAsF;AACtF,wDAAwD;AACxD,MAAM,YAAY,GAAG,mDAAmD,CAAC;AAEzE,0EAA0E;AAC1E,MAAM,UAAU,GAAG,uCAAuC,CAAC;AAE3D,uEAAuE;AACvE,MAAM,eAAe,GAAG,kCAAkC,CAAC;AAS3D,SAAS,gBAAgB,CAAC,MAAc;IACtC,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,4DAA4D;IAC5D,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC;IACzB,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;IAED,6DAA6D;IAC7D,iBAAiB,CAAC,SAAS,GAAG,CAAC,CAAC;IAChC,OAAO,CAAC,CAAC,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3F,CAAC;IAED,4DAA4D;IAC5D,YAAY,CAAC,SAAS,GAAG,CAAC,CAAC;IAC3B,OAAO,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,iBAAiB,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAE/C,oDAAoD;IACpD,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC;IACzB,OAAO,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACjB,IAAI,GAAG,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/B,YAAY,GAAG,IAAI,CAAC;YACpB,MAAM;QACR,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,MAAM,iBAAiB,GAAG,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEvD,MAAM,WAAW,GAAG,YAAY,IAAI,iBAAiB,CAAC;IAEtD,0BAA0B;IAC1B,8FAA8F;IAC9F,yFAAyF;IACzF,MAAM,kBAAkB,GAAG,CAAC,GAAG,EAAE;QAC/B,UAAU,CAAC,SAAS,GAAG,CAAC,CAAC;QACzB,iBAAiB,CAAC,SAAS,GAAG,CAAC,CAAC;QAChC,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,EAA0B,CAAC;QAC/B,OAAO,CAAC,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/C,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAC,SAAS,GAAG,IAAI,CAAC;gBAAC,MAAM;YAAC,CAAC;QACrE,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,CAAC,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBACtD,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;gBACnB,IAAI,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAC,SAAS,GAAG,IAAI,CAAC;oBAAC,MAAM;gBAAC,CAAC;YACrE,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEL,IAAI,UAA0B,CAAC;IAC/B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,UAAU,GAAG,MAAM,CAAC;IACtB,CAAC;SAAM,IAAI,kBAAkB,EAAE,CAAC;QAC9B,UAAU,GAAG,MAAM,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,QAAQ,GAAG,KAAK,EACpB,GAAgB,EAChB,MAAmB,EACM,EAAE;IAC3B,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG,CAAC,QAAQ;QAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IACzD,IAAI,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IAEvE,oEAAoE;IACpE,MAAM,cAAc,GAAG,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5D,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IAEvE,IAAI,cAAc,GAAa,EAAE,CAAC;IAClC,IAAI,CAAC;QACH,cAAc,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE;YACvC,GAAG,EAAE,GAAG,CAAC,QAAQ;YACjB,QAAQ,EAAE,KAAK;YACf,GAAG,EAAE,KAAK;YACV,MAAM,EAAE,WAAW;YACnB,SAAS,EAAE,IAAI;YACf,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IACxC,CAAC;IAED,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,IAAI;YAAE,SAAS;QAE9B,MAAM,QAAQ,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ,CAAC,iBAAiB;YAAE,SAAS;QAE1C,aAAa,EAAE,CAAC;QAEhB,IAAI,QAAQ,CAAC,WAAW;YAAE,SAAS;QAEnC,IAAI,QAAQ,CAAC,UAAU,KAAK,KAAK;YAAE,SAAS;QAE5C,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAE5F,QAAQ,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,OAAO;YACf,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,OAAO;YACjB,QAAQ,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;YAC3C,OAAO,EAAE,wCAAwC,SAAS,GAAG,IAAI,4BAA4B;YAC7F,UAAU,EACR,oJAAoJ;YACtJ,UAAU,EAAE,MAAM;SACnB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAA4C,CAClE,OAAgB,EAChB,IAAqB,EACT,EAAE;IACd,OAAO,OAAO,CAAC,UAAU,IAAI,MAAM,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,IAAI,GAAS,cAAc,CAAC;IACvC,IAAI,EAAE;QACJ,IAAI,EAAE,eAAe;QACrB,UAAU,EAAE,OAAO;QACnB,eAAe,EAAE,OAAO;QACxB,gBAAgB,EAAE,6EAA6E;QAC/F,eAAe,EACb,gvBAAgvB;QAClvB,OAAO,EACL,kGAAkG;QACpG,SAAS,EAAE;;;;;;6LAM8K;QACzL,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,+MAA+M;gBACrN,GAAG,EAAE,8KAA8K;aACpL;YACD;gBACE,IAAI,EAAE,0JAA0J;gBAChK,GAAG,EAAE,8IAA8I;aACpJ;YACD;gBACE,IAAI,EAAE,0IAA0I;gBAChJ,GAAG,EAAE,uKAAuK;aAC7K;SACF;QACD,SAAS,EAAE;YACT,4CAA4C;YAC5C,wFAAwF;YACxF,iGAAiG;YACjG,6HAA6H;SAC9H;KACF;IACD,cAAc,EAAE,EAAE;IAClB,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC5B,kBAAkB;CACnB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,gBAAgB;IAChB,aAAa;IACb,iBAAiB;IACjB,oBAAoB;IACpB,eAAe;CAChB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare const rule: Rule;
3
+ export declare const _internal: {
4
+ isAllowlisted: (repoRoot: string) => boolean;
5
+ DISABLE_DIRECTIVE: string;
6
+ ALLOWLIST_CANDIDATES: string[];
7
+ MAX_FINDING_NAMES: number;
8
+ };