@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.
- package/LICENSE +0 -5
- package/dist/cli.js +67 -11
- package/dist/cli.js.map +1 -1
- package/dist/commands/__tests__/add-ci-gate.test.d.ts +1 -0
- package/dist/commands/__tests__/add-ci-gate.test.js +149 -0
- package/dist/commands/__tests__/add-ci-gate.test.js.map +1 -0
- package/dist/commands/add-ci-gate.d.ts +26 -0
- package/dist/commands/add-ci-gate.js +429 -0
- package/dist/commands/add-ci-gate.js.map +1 -0
- package/dist/commands/audit-pipeline.js +1 -1
- package/dist/commands/audit-pipeline.js.map +1 -1
- package/dist/commands/fix.d.ts +8 -8
- package/dist/commands/init.d.ts +3 -3
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp-setup.js +1 -1
- package/dist/commands/mcp-setup.js.map +1 -1
- package/dist/config/schema.d.ts +14 -14
- package/dist/llm/__tests__/layer4-stage.test.d.ts +1 -0
- package/dist/llm/__tests__/layer4-stage.test.js +157 -0
- package/dist/llm/__tests__/layer4-stage.test.js.map +1 -0
- package/dist/llm/__tests__/validator.test.d.ts +1 -0
- package/dist/llm/__tests__/validator.test.js +156 -0
- package/dist/llm/__tests__/validator.test.js.map +1 -0
- package/dist/llm/connectors/__tests__/anthropic-adapter.test.d.ts +1 -0
- package/dist/llm/connectors/__tests__/anthropic-adapter.test.js +99 -0
- package/dist/llm/connectors/__tests__/anthropic-adapter.test.js.map +1 -0
- package/dist/llm/connectors/__tests__/cache.test.d.ts +1 -0
- package/dist/llm/connectors/__tests__/cache.test.js +50 -0
- package/dist/llm/connectors/__tests__/cache.test.js.map +1 -0
- package/dist/llm/connectors/__tests__/noop-adapter.test.d.ts +1 -0
- package/dist/llm/connectors/__tests__/noop-adapter.test.js +21 -0
- package/dist/llm/connectors/__tests__/noop-adapter.test.js.map +1 -0
- package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.d.ts +1 -0
- package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.js +82 -0
- package/dist/llm/connectors/__tests__/openai-compatible-adapter.test.js.map +1 -0
- package/dist/llm/connectors/__tests__/resolver.test.d.ts +1 -0
- package/dist/llm/connectors/__tests__/resolver.test.js +140 -0
- package/dist/llm/connectors/__tests__/resolver.test.js.map +1 -0
- package/dist/llm/connectors/anthropic-adapter.d.ts +12 -0
- package/dist/llm/connectors/anthropic-adapter.js +57 -0
- package/dist/llm/connectors/anthropic-adapter.js.map +1 -0
- package/dist/llm/connectors/cache.d.ts +15 -0
- package/dist/llm/connectors/cache.js +57 -0
- package/dist/llm/connectors/cache.js.map +1 -0
- package/dist/llm/connectors/noop-adapter.d.ts +4 -0
- package/dist/llm/connectors/noop-adapter.js +12 -0
- package/dist/llm/connectors/noop-adapter.js.map +1 -0
- package/dist/llm/connectors/openai-compatible-adapter.d.ts +13 -0
- package/dist/llm/connectors/openai-compatible-adapter.js +47 -0
- package/dist/llm/connectors/openai-compatible-adapter.js.map +1 -0
- package/dist/llm/connectors/resolver.d.ts +9 -0
- package/dist/llm/connectors/resolver.js +162 -0
- package/dist/llm/connectors/resolver.js.map +1 -0
- package/dist/llm/connectors/types.d.ts +25 -0
- package/dist/llm/connectors/types.js +13 -0
- package/dist/llm/connectors/types.js.map +1 -0
- package/dist/llm/layer4-stage.d.ts +8 -6
- package/dist/llm/layer4-stage.js +145 -9
- package/dist/llm/layer4-stage.js.map +1 -1
- package/dist/llm/rubric-stub.d.ts +8 -0
- package/dist/llm/rubric-stub.js +4 -0
- package/dist/llm/rubric-stub.js.map +1 -0
- package/dist/llm/validator.d.ts +18 -0
- package/dist/llm/validator.js +81 -0
- package/dist/llm/validator.js.map +1 -0
- package/dist/parsers/ai-tokens.d.ts +10 -0
- package/dist/parsers/ai-tokens.js +156 -0
- package/dist/parsers/ai-tokens.js.map +1 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.js +13 -3
- package/dist/reliability/catalogue/__tests__/sub-axes.test.js.map +1 -1
- package/dist/reliability/catalogue/sub-axes.js +16 -0
- package/dist/reliability/catalogue/sub-axes.js.map +1 -1
- package/dist/reliability/types.d.ts +1 -1
- package/dist/rule-runner.js +1 -1
- package/dist/rule-runner.js.map +1 -1
- package/dist/rules/ai-governance-ai-content-live-region.d.ts +11 -0
- package/dist/rules/ai-governance-ai-content-live-region.js +304 -0
- package/dist/rules/ai-governance-ai-content-live-region.js.map +1 -0
- package/dist/rules/ai-governance-ai-loading-error-states.d.ts +9 -0
- package/dist/rules/ai-governance-ai-loading-error-states.js +214 -0
- package/dist/rules/ai-governance-ai-loading-error-states.js.map +1 -0
- package/dist/rules/ai-governance-ai-marker-anti-patterns.d.ts +15 -0
- package/dist/rules/ai-governance-ai-marker-anti-patterns.js +178 -0
- package/dist/rules/ai-governance-ai-marker-anti-patterns.js.map +1 -0
- package/dist/rules/ai-governance-ai-marker-component-present.d.ts +28 -0
- package/dist/rules/ai-governance-ai-marker-component-present.js +320 -0
- package/dist/rules/ai-governance-ai-marker-component-present.js.map +1 -0
- package/dist/rules/ai-governance-ai-token-requires-marker.d.ts +17 -0
- package/dist/rules/ai-governance-ai-token-requires-marker.js +206 -0
- package/dist/rules/ai-governance-ai-token-requires-marker.js.map +1 -0
- package/dist/rules/ai-governance-ai-tokens-reserved.d.ts +8 -0
- package/dist/rules/ai-governance-ai-tokens-reserved.js +85 -0
- package/dist/rules/ai-governance-ai-tokens-reserved.js.map +1 -0
- package/dist/rules/ai-governance-disclaimer-present.d.ts +18 -0
- package/dist/rules/ai-governance-disclaimer-present.js +210 -0
- package/dist/rules/ai-governance-disclaimer-present.js.map +1 -0
- package/dist/rules/ai-governance-explainability-affordance.d.ts +14 -0
- package/dist/rules/ai-governance-explainability-affordance.js +196 -0
- package/dist/rules/ai-governance-explainability-affordance.js.map +1 -0
- package/dist/rules/ai-governance-feedback-control-present.d.ts +19 -0
- package/dist/rules/ai-governance-feedback-control-present.js +223 -0
- package/dist/rules/ai-governance-feedback-control-present.js.map +1 -0
- package/dist/rules/ai-governance-human-control-affordances.d.ts +16 -0
- package/dist/rules/ai-governance-human-control-affordances.js +228 -0
- package/dist/rules/ai-governance-human-control-affordances.js.map +1 -0
- package/dist/rules/ai-governance-value-gate-doc-present.d.ts +18 -0
- package/dist/rules/ai-governance-value-gate-doc-present.js +206 -0
- package/dist/rules/ai-governance-value-gate-doc-present.js.map +1 -0
- package/dist/rules/ai-surface-agent-instruction-files.d.ts +33 -0
- package/dist/rules/ai-surface-agent-instruction-files.js +310 -0
- package/dist/rules/ai-surface-agent-instruction-files.js.map +1 -0
- package/dist/rules/ai-surface-llms-txt-structure.d.ts +18 -0
- package/dist/rules/ai-surface-llms-txt-structure.js +200 -0
- package/dist/rules/ai-surface-llms-txt-structure.js.map +1 -0
- package/dist/rules/ai-surface-mcp-config-present.d.ts +23 -0
- package/dist/rules/ai-surface-mcp-config-present.js +193 -0
- package/dist/rules/ai-surface-mcp-config-present.js.map +1 -0
- package/dist/rules/ai-surface-shadcn-registry-valid.d.ts +22 -0
- package/dist/rules/ai-surface-shadcn-registry-valid.js +247 -0
- package/dist/rules/ai-surface-shadcn-registry-valid.js.map +1 -0
- package/dist/rules/components-contracts-strictness.d.ts +48 -0
- package/dist/rules/components-contracts-strictness.js +372 -0
- package/dist/rules/components-contracts-strictness.js.map +1 -0
- package/dist/rules/registry.js +32 -0
- package/dist/rules/registry.js.map +1 -1
- package/dist/rules/tokens-dtcg-conformance.d.ts +55 -19
- package/dist/rules/tokens-dtcg-conformance.js +320 -82
- package/dist/rules/tokens-dtcg-conformance.js.map +1 -1
- package/dist/scorer.js +4 -3
- package/dist/scorer.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js.map +1 -1
- package/package.json +41 -16
- package/rules-manifest.json +431 -6
- package/schemas/v1/lyse-rules.json +1 -1
- package/dist/commands/ci-setup.d.ts +0 -9
- package/dist/commands/ci-setup.js +0 -42
- package/dist/commands/ci-setup.js.map +0 -1
- package/dist/commands/templates/lyse-workflow.yml.template +0 -30
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
2
|
+
import { detectReservedAiTokens } from "../parsers/ai-tokens.js";
|
|
3
|
+
import { makeAllowlistCheck } from "./ai-governance-ai-marker-component-present.js";
|
|
4
|
+
const RULE_ID = "ai-governance/ai-tokens-reserved";
|
|
5
|
+
const MAX_FINDING_NAMES = 20;
|
|
6
|
+
const DISABLE_DIRECTIVE = `lyse-disable ${RULE_ID}`;
|
|
7
|
+
const ALLOWLIST_CANDIDATES = [
|
|
8
|
+
"README.md",
|
|
9
|
+
"README",
|
|
10
|
+
"README.mdx",
|
|
11
|
+
"readme.md",
|
|
12
|
+
".lyse.yaml",
|
|
13
|
+
".lyse.yml",
|
|
14
|
+
];
|
|
15
|
+
const isAllowlisted = makeAllowlistCheck(DISABLE_DIRECTIVE);
|
|
16
|
+
const evaluate = async (ctx, _files) => {
|
|
17
|
+
const findings = [];
|
|
18
|
+
if (!ctx.repoRoot) {
|
|
19
|
+
return { findings, opportunities: 0 };
|
|
20
|
+
}
|
|
21
|
+
if (isAllowlisted(ctx.repoRoot)) {
|
|
22
|
+
return { findings, opportunities: 0 };
|
|
23
|
+
}
|
|
24
|
+
const names = detectReservedAiTokens(ctx.repoRoot);
|
|
25
|
+
if (names.length === 0) {
|
|
26
|
+
return { findings, opportunities: 0 };
|
|
27
|
+
}
|
|
28
|
+
const shown = names.slice(0, MAX_FINDING_NAMES);
|
|
29
|
+
const more = names.length - shown.length;
|
|
30
|
+
const list = shown.join(", ") + (more > 0 ? `, +${more} more` : "");
|
|
31
|
+
findings.push({
|
|
32
|
+
ruleId: RULE_ID,
|
|
33
|
+
axis: "ai-governance",
|
|
34
|
+
severity: "info",
|
|
35
|
+
location: { file: "tokens.json", line: 1, column: 1 },
|
|
36
|
+
message: `Detected ${names.length} reserved AI-marker design token${names.length === 1 ? "" : "s"}: ${list}`,
|
|
37
|
+
suggestion: "inventory only — reserved AI tokens are recognised vocabularies (Carbon dragon-fruit / *-ai-*, Polaris magic, Workday Canvas *-ai-*); the composite rule `ai-governance/ai-token-requires-marker` will gate downstream usage",
|
|
38
|
+
});
|
|
39
|
+
return { findings, opportunities: 1 };
|
|
40
|
+
};
|
|
41
|
+
export const rule = createLyseRule({
|
|
42
|
+
meta: {
|
|
43
|
+
axis: "ai-governance",
|
|
44
|
+
lyseRuleId: RULE_ID,
|
|
45
|
+
defaultSeverity: "info",
|
|
46
|
+
shortDescription: "Inventory reserved AI-marker design tokens",
|
|
47
|
+
fullDescription: "Scans token sources (`tokens.json`, `tokens/**/*.json`, `*.tokens.json`, and `**/*.css` `--*` custom properties) for token names that match reserved AI-marker vocabularies: Carbon (`dragon-fruit`, `*-ai-*` color tokens), Shopify Polaris (`--p-color-*-magic*`, `magic-*`), Workday Canvas (`*-ai-*` segment), and the generic leading/trailing `ai` segment. Matching is segment-anchored (split by `-`, `_`, `.`, `/`) so `rain`, `paint`, `mainColor`, `captain`, `detail` do not trigger. Emits one info finding listing up to 20 matched names when reserved tokens are present; emits nothing when none are. Severity is informational — a DS with no AI surface is not penalized here; the gating rule lives in Track 3.3 (`ai-governance/ai-token-requires-marker`).",
|
|
48
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-tokens-reserved.md",
|
|
49
|
+
rationale: `Why it matters
|
|
50
|
+
|
|
51
|
+
AI-marker tokens are how a design system tells consumers — humans and agents — that a UI region was produced by AI. Carbon's \`dragon-fruit\` AI gradient, Polaris's \`magic\` namespace, and Workday Canvas's \`*-ai-*\` color tokens are the three established vocabularies; a fourth generic \`ai\` segment covers the long tail.
|
|
52
|
+
|
|
53
|
+
Track 3 of the Lyse roadmap (Face B — AI-Governance) treats these tokens as the foundation for the composite gating rule \`ai-governance/ai-token-requires-marker\` (Track 3.3), which enforces that AI-produced surfaces actually wear the AI marker. This rule is the inventory step: detect which reserved tokens the repo declares so 3.3 has a deterministic set to check against. It carries no penalty — a DS with zero AI tokens (no AI surface) emits no finding and is not down-scored. The teeth live in 3.3.
|
|
54
|
+
|
|
55
|
+
The shared parser \`detectReservedAiTokens(repoRoot)\` is exported from \`packages/core/src/parsers/ai-tokens.ts\` so 3.3 reuses the exact same detection set.`,
|
|
56
|
+
examples: [
|
|
57
|
+
{
|
|
58
|
+
good: '/* tokens.json */ { "color": { "primary": "#0070f3" } }',
|
|
59
|
+
bad: '/* tokens.json */ { "color": { "ai": { "primary": "#0070f3" } } }',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
good: ":root { --color-primary: #0070f3; }",
|
|
63
|
+
bad: ":root { --p-color-bg-magic: #f4f0fd; }",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
good: '/* tokens.json */ { "gradient": { "sunrise": "..." } }',
|
|
67
|
+
bad: '/* tokens.json */ { "gradient": { "dragon-fruit": "..." } }',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
allowlist: [
|
|
71
|
+
"repos containing `lyse-disable ai-governance/ai-tokens-reserved` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
72
|
+
"token files larger than 2 MB — skipped to avoid pathological cases",
|
|
73
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
defaultOptions: [],
|
|
77
|
+
create: () => ({ evaluate }),
|
|
78
|
+
});
|
|
79
|
+
export const _internal = {
|
|
80
|
+
isAllowlisted,
|
|
81
|
+
DISABLE_DIRECTIVE,
|
|
82
|
+
ALLOWLIST_CANDIDATES,
|
|
83
|
+
MAX_FINDING_NAMES,
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=ai-governance-ai-tokens-reserved.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-governance-ai-tokens-reserved.js","sourceRoot":"","sources":["../../src/rules/ai-governance-ai-tokens-reserved.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
interface DisclaimerResult {
|
|
3
|
+
found: boolean;
|
|
4
|
+
isGitLabExact: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function detectDisclaimer(source: string): DisclaimerResult;
|
|
7
|
+
export declare function detectAiMarkerInSource(source: string): boolean;
|
|
8
|
+
export declare const rule: Rule;
|
|
9
|
+
export declare const _internal: {
|
|
10
|
+
detectDisclaimer: typeof detectDisclaimer;
|
|
11
|
+
detectAiMarkerInSource: typeof detectAiMarkerInSource;
|
|
12
|
+
isAllowlisted: (repoRoot: string) => boolean;
|
|
13
|
+
AI_MARKER_NAMES: ReadonlySet<string>;
|
|
14
|
+
DISABLE_DIRECTIVE: string;
|
|
15
|
+
ALLOWLIST_CANDIDATES: string[];
|
|
16
|
+
GITLAB_EXACT_DISCLAIMER: string;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
4
|
+
import { AI_MARKER_NAMES, isAiMarkerName, safeReadText, COMPONENT_GLOB, SCAN_IGNORE, makeAllowlistCheck, } from "./ai-governance-ai-marker-component-present.js";
|
|
5
|
+
const RULE_ID = "ai-governance/disclaimer-present";
|
|
6
|
+
const DISABLE_DIRECTIVE = `lyse-disable ${RULE_ID}`;
|
|
7
|
+
// WHY separate constant: the exact GitLab Pajamas canonical wording is the
|
|
8
|
+
// highest-confidence signal that a team has adopted the industry-standard
|
|
9
|
+
// disclaimer. Verify against Pajamas AI experience guidelines before updating;
|
|
10
|
+
// embed verbatim so substring match is unambiguous.
|
|
11
|
+
const GITLAB_EXACT_DISCLAIMER = "AI-generated content may be inaccurate. Always check important information.";
|
|
12
|
+
// Disclaimer text phrases — word-boundary anchored, case-insensitive.
|
|
13
|
+
// "ai generated" (space variant) covered by the first two patterns.
|
|
14
|
+
const DISCLAIMER_PHRASES = [
|
|
15
|
+
/\bgenerated\s+by\s+ai\b/i,
|
|
16
|
+
/\bai[- ]?generated\b/i,
|
|
17
|
+
/\bmay\s+be\s+inaccurate\b/i,
|
|
18
|
+
/\bcheck\s+important\s+info\b/i,
|
|
19
|
+
/\bpowered\s+by\s+ai\b/i,
|
|
20
|
+
];
|
|
21
|
+
// WHY AI prefix check: generic legal/cookie/privacy disclaimers co-located
|
|
22
|
+
// with an AI marker must not earn info credit — only AI-specific disclaimer
|
|
23
|
+
// components count. Exact `Disclaimer` (standalone) is always AI-disclaimer
|
|
24
|
+
// context; prefixed names must carry an AI-domain qualifier.
|
|
25
|
+
function hasDisclaimerTagName(name) {
|
|
26
|
+
const lower = name.toLowerCase();
|
|
27
|
+
if (!lower.includes("disclaimer"))
|
|
28
|
+
return false;
|
|
29
|
+
if (lower === "disclaimer")
|
|
30
|
+
return true;
|
|
31
|
+
return (lower.startsWith("ai") ||
|
|
32
|
+
lower.startsWith("genai") ||
|
|
33
|
+
lower.startsWith("generative") ||
|
|
34
|
+
lower.startsWith("llm"));
|
|
35
|
+
}
|
|
36
|
+
const ALLOWLIST_CANDIDATES = [
|
|
37
|
+
"README.md",
|
|
38
|
+
"README",
|
|
39
|
+
"README.mdx",
|
|
40
|
+
"readme.md",
|
|
41
|
+
".lyse.yaml",
|
|
42
|
+
".lyse.yml",
|
|
43
|
+
];
|
|
44
|
+
const isAllowlisted = makeAllowlistCheck(DISABLE_DIRECTIVE);
|
|
45
|
+
export function detectDisclaimer(source) {
|
|
46
|
+
if (source.includes(GITLAB_EXACT_DISCLAIMER)) {
|
|
47
|
+
return { found: true, isGitLabExact: true };
|
|
48
|
+
}
|
|
49
|
+
for (const re of DISCLAIMER_PHRASES) {
|
|
50
|
+
if (re.test(source))
|
|
51
|
+
return { found: true, isGitLabExact: false };
|
|
52
|
+
}
|
|
53
|
+
for (const m of source.matchAll(/<\s*([A-Za-z][\w.]*)/g)) {
|
|
54
|
+
if (m[1] && hasDisclaimerTagName(m[1])) {
|
|
55
|
+
return { found: true, isGitLabExact: false };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { found: false, isGitLabExact: false };
|
|
59
|
+
}
|
|
60
|
+
export function detectAiMarkerInSource(source) {
|
|
61
|
+
for (const m of source.matchAll(/<\s*([A-Za-z][\w.-]*)/g)) {
|
|
62
|
+
if (m[1] && isAiMarkerName(m[1]))
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
const MAX_LISTED_FILES = 20;
|
|
68
|
+
const evaluate = async (ctx, _files) => {
|
|
69
|
+
const findings = [];
|
|
70
|
+
if (!ctx.repoRoot)
|
|
71
|
+
return { findings, opportunities: 0 };
|
|
72
|
+
if (isAllowlisted(ctx.repoRoot))
|
|
73
|
+
return { findings, opportunities: 0 };
|
|
74
|
+
let componentFiles = [];
|
|
75
|
+
try {
|
|
76
|
+
componentFiles = fg
|
|
77
|
+
.sync(COMPONENT_GLOB, {
|
|
78
|
+
cwd: ctx.repoRoot,
|
|
79
|
+
absolute: false,
|
|
80
|
+
dot: false,
|
|
81
|
+
ignore: SCAN_IGNORE,
|
|
82
|
+
onlyFiles: true,
|
|
83
|
+
unique: true,
|
|
84
|
+
})
|
|
85
|
+
.sort();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { findings, opportunities: 0 };
|
|
89
|
+
}
|
|
90
|
+
if (componentFiles.length === 0)
|
|
91
|
+
return { findings, opportunities: 0 };
|
|
92
|
+
let anyAiSurface = false;
|
|
93
|
+
const missingDisclaimerFiles = [];
|
|
94
|
+
for (const rel of componentFiles) {
|
|
95
|
+
const abs = join(ctx.repoRoot, rel);
|
|
96
|
+
const source = safeReadText(abs);
|
|
97
|
+
if (!source)
|
|
98
|
+
continue;
|
|
99
|
+
const hasMarker = detectAiMarkerInSource(source);
|
|
100
|
+
const disclaimer = detectDisclaimer(source);
|
|
101
|
+
if (!hasMarker)
|
|
102
|
+
continue;
|
|
103
|
+
anyAiSurface = true;
|
|
104
|
+
if (!disclaimer.found) {
|
|
105
|
+
missingDisclaimerFiles.push(rel);
|
|
106
|
+
}
|
|
107
|
+
if (disclaimer.found) {
|
|
108
|
+
const gitlabNote = disclaimer.isGitLabExact
|
|
109
|
+
? " Matches the GitLab Pajamas canonical disclaimer wording (highest confidence)."
|
|
110
|
+
: "";
|
|
111
|
+
const baseFinding = {
|
|
112
|
+
ruleId: RULE_ID,
|
|
113
|
+
axis: "ai-governance",
|
|
114
|
+
severity: "info",
|
|
115
|
+
location: { file: rel, line: 1, column: 1 },
|
|
116
|
+
message: `AI disclaimer detected in ${rel}.${gitlabNote} Capability framing present (HAX G1/G2).`,
|
|
117
|
+
};
|
|
118
|
+
if (!disclaimer.isGitLabExact) {
|
|
119
|
+
baseFinding.suggestion =
|
|
120
|
+
`Consider adopting the GitLab Pajamas canonical wording for highest-confidence scoring: ` +
|
|
121
|
+
`"AI-generated content may be inaccurate. Always check important information."`;
|
|
122
|
+
}
|
|
123
|
+
findings.push(baseFinding);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!anyAiSurface)
|
|
127
|
+
return { findings: [], opportunities: 0 };
|
|
128
|
+
if (missingDisclaimerFiles.length > 0) {
|
|
129
|
+
const listed = missingDisclaimerFiles.slice(0, MAX_LISTED_FILES);
|
|
130
|
+
const overflow = missingDisclaimerFiles.length - listed.length;
|
|
131
|
+
const fileList = listed.join(", ") + (overflow > 0 ? `, +${overflow} more` : "");
|
|
132
|
+
findings.unshift({
|
|
133
|
+
ruleId: RULE_ID,
|
|
134
|
+
axis: "ai-governance",
|
|
135
|
+
severity: "warning",
|
|
136
|
+
location: { file: missingDisclaimerFiles[0] ?? "src/index.ts", line: 1, column: 1 },
|
|
137
|
+
message: `${missingDisclaimerFiles.length} file${missingDisclaimerFiles.length === 1 ? "" : "s"} contain an AI-marker component but no disclaimer text or disclaimer component (HAX G1/G2 — GitLab Pajamas): ${fileList}.`,
|
|
138
|
+
suggestion: `Render a visible disclaimer alongside AI-generated content. ` +
|
|
139
|
+
`GitLab Pajamas canonical wording: "AI-generated content may be inaccurate. Always check important information."`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return { findings, opportunities: componentFiles.length };
|
|
143
|
+
};
|
|
144
|
+
export const rule = createLyseRule({
|
|
145
|
+
meta: {
|
|
146
|
+
axis: "ai-governance",
|
|
147
|
+
lyseRuleId: RULE_ID,
|
|
148
|
+
defaultSeverity: "warning",
|
|
149
|
+
shortDescription: "Detect AI disclaimer near AI-generated output",
|
|
150
|
+
fullDescription: "Scans component files (`**/*.{tsx,jsx,vue}`) for a co-located AI disclaimer — " +
|
|
151
|
+
"either disclaimer text (`Generated by AI`, `AI-generated`, `May be inaccurate`, " +
|
|
152
|
+
"`Check important info`, `Powered by AI`, case-insensitive word-boundary) or a " +
|
|
153
|
+
"`*Disclaimer*` / `*AIDisclaimer*` component tag. " +
|
|
154
|
+
"Highest-confidence signal: the GitLab Pajamas canonical disclaimer wording matched verbatim. " +
|
|
155
|
+
"Cross-condition: if an AI-marker component (per AI_MARKER_NAMES) is present but no disclaimer is found, " +
|
|
156
|
+
"emits `warning`; if a disclaimer is detected, emits `info` (noting the GitLab canonical match when applicable). " +
|
|
157
|
+
"Emits nothing when no AI surface is present.",
|
|
158
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-disclaimer-present.md",
|
|
159
|
+
rationale: `Why it matters
|
|
160
|
+
|
|
161
|
+
AI systems make errors. Capability-framing guidelines (HAX G1/G2) require that AI-generated surfaces carry a visible disclaimer so users understand the system's limits and know to verify critical information.
|
|
162
|
+
|
|
163
|
+
GitLab Pajamas formalises this with a canonical disclaimer string: "AI-generated content may be inaccurate. Always check important information." When the exact Pajamas wording is used, Lyse flags it as highest-confidence.
|
|
164
|
+
|
|
165
|
+
This rule enforces the pairing: an AI-marker component is the visual signal that content is AI-generated; a disclaimer is the capability-framing signal that the content may err. Having the marker without the disclaimer leaves users without the context they need to make safe decisions.
|
|
166
|
+
|
|
167
|
+
The rule emits \`warning\` (action required) when a marker is present but no disclaimer is found, and \`info\` (inventory) when a disclaimer is detected — flagging whether it matches the GitLab canonical wording.`,
|
|
168
|
+
examples: [
|
|
169
|
+
{
|
|
170
|
+
good: "// AISummary.tsx — marker + disclaimer present\n" +
|
|
171
|
+
"<AILabel />\n" +
|
|
172
|
+
"<p>{summary}</p>\n" +
|
|
173
|
+
'<p className="disclaimer">Generated by AI. May be inaccurate.</p>',
|
|
174
|
+
bad: "// AISummary.tsx — marker present, no disclaimer\n" +
|
|
175
|
+
"<AILabel />\n" +
|
|
176
|
+
"<p>{summary}</p>",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
good: "// GitLab canonical wording — highest-confidence pass\n" +
|
|
180
|
+
'<AIDisclaimer message="AI-generated content may be inaccurate. Always check important information." />',
|
|
181
|
+
bad: "// AI marker but disclaimer is buried in a tooltip the user may never see\n" +
|
|
182
|
+
"<AIBadge /><Tooltip>AI content</Tooltip>",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
good: "// Vue SFC — disclaimer component rendered alongside output\n" +
|
|
186
|
+
"<template><AIOutput :text /><AiDisclaimer /></template>",
|
|
187
|
+
bad: "// Vue SFC — AI token reserved in class, no disclaimer rendered\n" +
|
|
188
|
+
'<template><div class="ai-output">{{ text }}</div></template>',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
allowlist: [
|
|
192
|
+
"repos containing `lyse-disable ai-governance/disclaimer-present` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
193
|
+
"repos with no AI-marker component AND no disclaimer text — no AI surface detected, rule emits nothing",
|
|
194
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
195
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
defaultOptions: [],
|
|
199
|
+
create: () => ({ evaluate }),
|
|
200
|
+
});
|
|
201
|
+
export const _internal = {
|
|
202
|
+
detectDisclaimer,
|
|
203
|
+
detectAiMarkerInSource,
|
|
204
|
+
isAllowlisted,
|
|
205
|
+
AI_MARKER_NAMES,
|
|
206
|
+
DISABLE_DIRECTIVE,
|
|
207
|
+
ALLOWLIST_CANDIDATES,
|
|
208
|
+
GITLAB_EXACT_DISCLAIMER,
|
|
209
|
+
};
|
|
210
|
+
//# sourceMappingURL=ai-governance-disclaimer-present.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-governance-disclaimer-present.js","sourceRoot":"","sources":["../../src/rules/ai-governance-disclaimer-present.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
export declare function isExplainabilityAffordanceName(name: string): boolean;
|
|
3
|
+
export declare function isMarkerWithPopover(name: string, source: string): boolean;
|
|
4
|
+
export declare function scanForExplainabilityAffordances(repoRoot: string, files?: string[]): string[];
|
|
5
|
+
export declare const rule: Rule;
|
|
6
|
+
export declare const _internal: {
|
|
7
|
+
isAllowlisted: (repoRoot: string) => boolean;
|
|
8
|
+
isExplainabilityAffordanceName: typeof isExplainabilityAffordanceName;
|
|
9
|
+
isMarkerWithPopover: typeof isMarkerWithPopover;
|
|
10
|
+
scanForExplainabilityAffordances: typeof scanForExplainabilityAffordances;
|
|
11
|
+
AFFORDANCE_PATTERNS: string[];
|
|
12
|
+
DISABLE_DIRECTIVE: string;
|
|
13
|
+
ALLOWLIST_CANDIDATES: string[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import fg from "fast-glob";
|
|
3
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
4
|
+
import { isAiMarkerName, extractNamesFromSource, extractVueNames, safeReadText, COMPONENT_GLOB, SCAN_IGNORE, fileHasAiMarker, deriveComponentNameFromPath, scanForMarkerComponents, makeAllowlistCheck, } from "./ai-governance-ai-marker-component-present.js";
|
|
5
|
+
const RULE_ID = "ai-governance/explainability-affordance";
|
|
6
|
+
const DISABLE_DIRECTIVE = `lyse-disable ${RULE_ID}`;
|
|
7
|
+
const ALLOWLIST_CANDIDATES = [
|
|
8
|
+
"README.md",
|
|
9
|
+
"README",
|
|
10
|
+
"README.mdx",
|
|
11
|
+
"readme.md",
|
|
12
|
+
".lyse.yaml",
|
|
13
|
+
".lyse.yml",
|
|
14
|
+
];
|
|
15
|
+
// Name-based affordance detection patterns (case-insensitive substring match).
|
|
16
|
+
const AFFORDANCE_PATTERNS = [
|
|
17
|
+
"explain",
|
|
18
|
+
"explainability",
|
|
19
|
+
"whythis",
|
|
20
|
+
"citation",
|
|
21
|
+
"sources",
|
|
22
|
+
"confidence",
|
|
23
|
+
"provenance",
|
|
24
|
+
];
|
|
25
|
+
// ARIA attributes indicating a popover/tooltip carrying explanation content.
|
|
26
|
+
const ARIA_POPOVER_RE = /\baria-describedby\s*=|role\s*=\s*["'](?:dialog|tooltip)["']/i;
|
|
27
|
+
const isAllowlisted = makeAllowlistCheck(DISABLE_DIRECTIVE);
|
|
28
|
+
export function isExplainabilityAffordanceName(name) {
|
|
29
|
+
const lower = name.toLowerCase();
|
|
30
|
+
return AFFORDANCE_PATTERNS.some((p) => lower.includes(p));
|
|
31
|
+
}
|
|
32
|
+
export function isMarkerWithPopover(name, source) {
|
|
33
|
+
if (!isAiMarkerName(name))
|
|
34
|
+
return false;
|
|
35
|
+
return ARIA_POPOVER_RE.test(source);
|
|
36
|
+
}
|
|
37
|
+
// Accepts an optional pre-computed file list to avoid a second glob in evaluate.
|
|
38
|
+
export function scanForExplainabilityAffordances(repoRoot, files) {
|
|
39
|
+
const found = [];
|
|
40
|
+
let componentFiles;
|
|
41
|
+
if (files !== undefined) {
|
|
42
|
+
componentFiles = files;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
componentFiles = [];
|
|
46
|
+
try {
|
|
47
|
+
componentFiles = fg.sync(COMPONENT_GLOB, {
|
|
48
|
+
cwd: repoRoot,
|
|
49
|
+
absolute: false,
|
|
50
|
+
dot: false,
|
|
51
|
+
ignore: SCAN_IGNORE,
|
|
52
|
+
onlyFiles: true,
|
|
53
|
+
unique: true,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// non-fatal
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const rel of componentFiles) {
|
|
61
|
+
const baseName = deriveComponentNameFromPath(rel);
|
|
62
|
+
const source = safeReadText(join(repoRoot, rel));
|
|
63
|
+
if (!source)
|
|
64
|
+
continue;
|
|
65
|
+
const hasMarker = fileHasAiMarker(source, rel) || isAiMarkerName(baseName);
|
|
66
|
+
if (!hasMarker)
|
|
67
|
+
continue;
|
|
68
|
+
// File contains an AI marker — now check for co-located affordance names.
|
|
69
|
+
const names = rel.endsWith(".vue")
|
|
70
|
+
? extractVueNames(source)
|
|
71
|
+
: extractNamesFromSource(source);
|
|
72
|
+
// Also check the file name itself (e.g. AIConfidenceDisplay.tsx where the
|
|
73
|
+
// file is both a marker AND an affordance by name).
|
|
74
|
+
if (isExplainabilityAffordanceName(baseName)) {
|
|
75
|
+
found.push(baseName);
|
|
76
|
+
}
|
|
77
|
+
for (const name of names) {
|
|
78
|
+
if (isExplainabilityAffordanceName(name)) {
|
|
79
|
+
found.push(name);
|
|
80
|
+
}
|
|
81
|
+
else if (isMarkerWithPopover(name, source)) {
|
|
82
|
+
found.push(`${name}[popover]`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const deduped = [];
|
|
88
|
+
for (const name of found) {
|
|
89
|
+
const key = name.toLowerCase();
|
|
90
|
+
if (!seen.has(key)) {
|
|
91
|
+
seen.add(key);
|
|
92
|
+
deduped.push(name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return deduped.sort();
|
|
96
|
+
}
|
|
97
|
+
const evaluate = async (ctx, _files) => {
|
|
98
|
+
const findings = [];
|
|
99
|
+
if (!ctx.repoRoot) {
|
|
100
|
+
return { findings, opportunities: 0 };
|
|
101
|
+
}
|
|
102
|
+
if (isAllowlisted(ctx.repoRoot)) {
|
|
103
|
+
return { findings, opportunities: 0 };
|
|
104
|
+
}
|
|
105
|
+
// Glob once — reuse file list for the marker gate check and affordance scan.
|
|
106
|
+
let componentFiles = [];
|
|
107
|
+
try {
|
|
108
|
+
componentFiles = fg.sync(COMPONENT_GLOB, {
|
|
109
|
+
cwd: ctx.repoRoot,
|
|
110
|
+
absolute: false,
|
|
111
|
+
dot: false,
|
|
112
|
+
ignore: SCAN_IGNORE,
|
|
113
|
+
onlyFiles: true,
|
|
114
|
+
unique: true,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// non-fatal
|
|
119
|
+
}
|
|
120
|
+
if (scanForMarkerComponents(ctx.repoRoot).length === 0) {
|
|
121
|
+
return { findings, opportunities: 0 };
|
|
122
|
+
}
|
|
123
|
+
const affordances = scanForExplainabilityAffordances(ctx.repoRoot, componentFiles);
|
|
124
|
+
if (affordances.length > 0) {
|
|
125
|
+
const list = affordances.join(", ");
|
|
126
|
+
findings.push({
|
|
127
|
+
ruleId: RULE_ID,
|
|
128
|
+
axis: "ai-governance",
|
|
129
|
+
severity: "info",
|
|
130
|
+
location: { file: "src/index.ts", line: 1, column: 1 },
|
|
131
|
+
message: `Explainability affordance${affordances.length === 1 ? "" : "s"} detected: ${list} (HAX G11 / PAIR Explainability)`,
|
|
132
|
+
suggestion: "Explainability affordance found — ensure it is paired with the AI-marker component and surfaces rationale or confidence information to users.",
|
|
133
|
+
});
|
|
134
|
+
return { findings, opportunities: 1 };
|
|
135
|
+
}
|
|
136
|
+
findings.push({
|
|
137
|
+
ruleId: RULE_ID,
|
|
138
|
+
axis: "ai-governance",
|
|
139
|
+
severity: "warning",
|
|
140
|
+
location: { file: "src/index.ts", line: 1, column: 1 },
|
|
141
|
+
message: "An AI-marker component is present but no explainability affordance was detected (HAX G11 / PAIR Explainability). Consider shipping a companion Explain/Citation/Confidence component or binding the marker to a popover/tooltip with aria-describedby or role=\"dialog\".",
|
|
142
|
+
suggestion: "Add a dedicated explainability component (e.g. ExplainPopover, CitationList, ConfidenceDisplay, WhyThis) or attach aria-describedby / role=\"tooltip\" to the AI-marker to surface rationale for AI-generated content.",
|
|
143
|
+
});
|
|
144
|
+
return { findings, opportunities: 1 };
|
|
145
|
+
};
|
|
146
|
+
export const rule = createLyseRule({
|
|
147
|
+
meta: {
|
|
148
|
+
axis: "ai-governance",
|
|
149
|
+
lyseRuleId: RULE_ID,
|
|
150
|
+
defaultSeverity: "warning",
|
|
151
|
+
shortDescription: "Detect an explainability affordance paired with AI-marker components",
|
|
152
|
+
fullDescription: "When an AI-marker component is detected in the design system (per the shared `isAiMarkerName` predicate exported by `ai-governance/ai-marker-component-present`), this rule checks whether a companion explainability affordance exists. Detection is name-based: any exported identifier or component file whose name contains `Explain`, `Explainability`, `WhyThis`, `Citation`, `Sources`, `Confidence`, or `Provenance` (case-insensitive) qualifies. A marker component that opens a popover or tooltip carrying explanation content (`aria-describedby` / `role=\"dialog\"` / `role=\"tooltip\"`) also satisfies the rule. Emits `info` when an affordance is found; emits `warning` when an AI-marker exists but no affordance is detected. Emits nothing when no AI-marker is present (no AI surface). Guidelines: HAX G11 (Explain AI decisions) / PAIR Explainability. The behavioral slice — verifying that an indicator appears wherever AI output is rendered — is deferred to Track 4.",
|
|
153
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-explainability-affordance.md",
|
|
154
|
+
rationale: `Why it matters
|
|
155
|
+
|
|
156
|
+
Users interacting with AI-generated content have a right to understand why a particular output was produced. HAX G11 (Google PAIR / IBM Human-AI Experience guidelines) requires that AI-powered interfaces expose an explanation pathway — a popover, citation list, confidence meter, or similar affordance — so users can evaluate trustworthiness and take informed action.
|
|
157
|
+
|
|
158
|
+
Without this affordance, consumers of the design system have no standard component to reach for when building explainable AI interfaces, leading to ad-hoc implementations with inconsistent UX and missing accessibility attributes.
|
|
159
|
+
|
|
160
|
+
This rule checks only the static "affordance present" slice (Track 3.5): does the DS export a component whose name signals explainability intent, or does an AI-marker component carry the appropriate ARIA binding to open an explanation panel? The behavioral slice — ensuring such an indicator appears wherever AI output is rendered in a consuming application — requires semantic location detection and is out of scope here; it will be addressed in Track 4.
|
|
161
|
+
|
|
162
|
+
A DS with no AI-marker component at all has no AI surface and is not penalised.`,
|
|
163
|
+
examples: [
|
|
164
|
+
{
|
|
165
|
+
good: "// src/index.ts\nexport { AILabel } from './ai-label';\nexport { ExplainPopover } from './explain-popover';",
|
|
166
|
+
bad: "// src/index.ts\nexport { AILabel } from './ai-label';\n// no Explain/Citation/Confidence component exported",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
good: '// AILabel.tsx — marker bound to explanation panel\n<button aria-describedby="explain-panel">AI</button>\n<div id="explain-panel" role="dialog">Why: …</div>',
|
|
170
|
+
bad: "// AILabel.tsx — marker with no explanation binding\n<span className=\"ai-badge\">AI</span>",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
good: "// src/index.ts\nexport { ConfidenceDisplay } from './confidence-display';\nexport { CitationList } from './citation-list';",
|
|
174
|
+
bad: "// AI tokens and AIBadge exported, but no explainability affordance component present",
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
allowlist: [
|
|
178
|
+
"repos containing `lyse-disable ai-governance/explainability-affordance` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
179
|
+
"repos with no AI-marker component — no AI surface detected, rule emits nothing",
|
|
180
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
181
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
defaultOptions: [],
|
|
185
|
+
create: () => ({ evaluate }),
|
|
186
|
+
});
|
|
187
|
+
export const _internal = {
|
|
188
|
+
isAllowlisted,
|
|
189
|
+
isExplainabilityAffordanceName,
|
|
190
|
+
isMarkerWithPopover,
|
|
191
|
+
scanForExplainabilityAffordances,
|
|
192
|
+
AFFORDANCE_PATTERNS,
|
|
193
|
+
DISABLE_DIRECTIVE,
|
|
194
|
+
ALLOWLIST_CANDIDATES,
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=ai-governance-explainability-affordance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-governance-explainability-affordance.js","sourceRoot":"","sources":["../../src/rules/ai-governance-explainability-affordance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
export declare function isFeedbackControlName(name: string): boolean;
|
|
3
|
+
export declare function detectCategorizedFeedback(source: string): boolean;
|
|
4
|
+
interface FeedbackScan {
|
|
5
|
+
names: string[];
|
|
6
|
+
categorized: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function scanForFeedbackControls(repoRoot: string, files?: string[]): FeedbackScan;
|
|
9
|
+
export declare const rule: Rule;
|
|
10
|
+
export declare const _internal: {
|
|
11
|
+
isFeedbackControlName: typeof isFeedbackControlName;
|
|
12
|
+
detectCategorizedFeedback: typeof detectCategorizedFeedback;
|
|
13
|
+
isAllowlisted: (repoRoot: string) => boolean;
|
|
14
|
+
scanForFeedbackControls: typeof scanForFeedbackControls;
|
|
15
|
+
DISABLE_DIRECTIVE: string;
|
|
16
|
+
ALLOWLIST_CANDIDATES: string[];
|
|
17
|
+
FEEDBACK_PATTERNS: readonly ["feedback", "thumbsup", "thumbsdown", "rating", "vote", "helpful"];
|
|
18
|
+
};
|
|
19
|
+
export {};
|