@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,223 @@
1
+ import { join } from "node:path";
2
+ import fg from "fast-glob";
3
+ import { createLyseRule } from "./_rule-module.js";
4
+ import { extractNamesFromSource, extractVueNames, safeReadText, COMPONENT_GLOB, SCAN_IGNORE, fileHasAiMarker, makeAllowlistCheck, } from "./ai-governance-ai-marker-component-present.js";
5
+ const RULE_ID = "ai-governance/feedback-control-present";
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
+ // Feedback control vocabulary — case-insensitive substring match after
17
+ // separator normalisation (kebab/snake stripped before comparison).
18
+ // Names ending in display-counter suffixes or Icon primitives are excluded.
19
+ const FEEDBACK_PATTERNS = [
20
+ "feedback",
21
+ "thumbsup",
22
+ "thumbsdown",
23
+ "rating",
24
+ "vote",
25
+ "helpful",
26
+ ];
27
+ // Exclude icon primitives AND display-only counters/result labels.
28
+ const EXCLUDE_SUFFIX_RE = /(?:icon|count|result|total|tally|text)$/i;
29
+ // Normalise kebab-case and snake_case names before vocabulary matching.
30
+ function normaliseName(name) {
31
+ return name.toLowerCase().replace(/[-_]/g, "");
32
+ }
33
+ export function isFeedbackControlName(name) {
34
+ const lower = normaliseName(name);
35
+ if (EXCLUDE_SUFFIX_RE.test(lower))
36
+ return false;
37
+ return FEEDBACK_PATTERNS.some((p) => lower.includes(p));
38
+ }
39
+ // Categorized bonus: source exposes named reason options (enum object, union
40
+ // type, or options array) containing known negative-reason vocabulary words.
41
+ const REASON_VOCAB = /\b(inaccurate|unhelpful|offensive|tooLong|too_long|harmful|misleading|irrelevant)\b/i;
42
+ const ENUM_OR_TYPE_RE = /(?:type|enum|const)\s+\w*[Rr]eason\w*\s*[=:{<]/;
43
+ const OPTIONS_ARRAY_RE = /\w*[Oo]ption\w*\s*=\s*\[/;
44
+ export function detectCategorizedFeedback(source) {
45
+ if (!REASON_VOCAB.test(source))
46
+ return false;
47
+ return ENUM_OR_TYPE_RE.test(source) || OPTIONS_ARRAY_RE.test(source);
48
+ }
49
+ function deriveNameFromPath(relPath) {
50
+ const parts = relPath.split("/");
51
+ const file = parts[parts.length - 1] ?? "";
52
+ return file.replace(/\.(tsx|jsx|vue)$/, "");
53
+ }
54
+ // Per-file co-location: only count a feedback control found in a FILE that
55
+ // ALSO contains an AI marker in that same file.
56
+ // Accepts an optional pre-computed file list to avoid a second glob in evaluate.
57
+ export function scanForFeedbackControls(repoRoot, files) {
58
+ const found = new Map();
59
+ function record(name, categorized) {
60
+ const key = normaliseName(name);
61
+ const existing = found.get(key);
62
+ if (!existing) {
63
+ found.set(key, { displayName: name, categorized });
64
+ }
65
+ else {
66
+ found.set(key, { displayName: existing.displayName, categorized: existing.categorized || categorized });
67
+ }
68
+ }
69
+ let componentFiles;
70
+ if (files !== undefined) {
71
+ componentFiles = files;
72
+ }
73
+ else {
74
+ componentFiles = [];
75
+ try {
76
+ componentFiles = fg.sync(COMPONENT_GLOB, {
77
+ cwd: repoRoot,
78
+ absolute: false,
79
+ dot: false,
80
+ ignore: SCAN_IGNORE,
81
+ onlyFiles: true,
82
+ unique: true,
83
+ });
84
+ }
85
+ catch {
86
+ // non-fatal
87
+ }
88
+ }
89
+ for (const rel of componentFiles.sort()) {
90
+ const source = safeReadText(join(repoRoot, rel));
91
+ if (!source)
92
+ continue;
93
+ if (!fileHasAiMarker(source, rel))
94
+ continue;
95
+ const baseName = deriveNameFromPath(rel);
96
+ if (isFeedbackControlName(baseName)) {
97
+ record(baseName, detectCategorizedFeedback(source));
98
+ continue;
99
+ }
100
+ const names = rel.endsWith(".vue")
101
+ ? extractVueNames(source)
102
+ : extractNamesFromSource(source);
103
+ for (const name of names) {
104
+ if (isFeedbackControlName(name)) {
105
+ record(name, detectCategorizedFeedback(source));
106
+ }
107
+ }
108
+ }
109
+ const entries = [...found.values()].sort((a, b) => a.displayName.localeCompare(b.displayName));
110
+ const names = entries.map((e) => e.displayName);
111
+ const categorized = entries.some((e) => e.categorized);
112
+ return { names, categorized };
113
+ }
114
+ const evaluate = async (ctx, _files) => {
115
+ const findings = [];
116
+ if (!ctx.repoRoot)
117
+ return { findings, opportunities: 0 };
118
+ if (isAllowlisted(ctx.repoRoot))
119
+ return { findings, opportunities: 0 };
120
+ // Glob once — reuse the file list for both the gate check and the full scan.
121
+ let componentFiles = [];
122
+ try {
123
+ componentFiles = fg.sync(COMPONENT_GLOB, {
124
+ cwd: ctx.repoRoot,
125
+ absolute: false,
126
+ dot: false,
127
+ ignore: SCAN_IGNORE,
128
+ onlyFiles: true,
129
+ unique: true,
130
+ });
131
+ }
132
+ catch {
133
+ // non-fatal
134
+ }
135
+ let anyAiMarker = false;
136
+ for (const rel of componentFiles) {
137
+ const source = safeReadText(join(ctx.repoRoot, rel));
138
+ if (!source)
139
+ continue;
140
+ if (fileHasAiMarker(source, rel)) {
141
+ anyAiMarker = true;
142
+ break;
143
+ }
144
+ }
145
+ if (!anyAiMarker)
146
+ return { findings, opportunities: 0 };
147
+ const { names, categorized } = scanForFeedbackControls(ctx.repoRoot, componentFiles);
148
+ if (names.length > 0) {
149
+ const list = names.join(", ");
150
+ const categorizedNote = categorized
151
+ ? " (categorized reason enum detected)"
152
+ : "";
153
+ findings.push({
154
+ ruleId: RULE_ID,
155
+ axis: "ai-governance",
156
+ severity: "info",
157
+ location: { file: "src/index.ts", line: 1, column: 1 },
158
+ message: `Feedback control${names.length === 1 ? "" : "s"} detected: ${list}${categorizedNote} (HAX G15 / PAIR Feedback)`,
159
+ suggestion: "Feedback control found — ensure it is paired with the AI-marker component on AI-generated surfaces. If not already categorized, expose a reason enum (e.g. inaccurate, unhelpful, offensive) for richer signal.",
160
+ });
161
+ return { findings, opportunities: 1 };
162
+ }
163
+ findings.push({
164
+ ruleId: RULE_ID,
165
+ axis: "ai-governance",
166
+ severity: "warning",
167
+ location: { file: "src/index.ts", line: 1, column: 1 },
168
+ message: "An AI-marker component is present but no feedback control was detected (HAX G15 / PAIR Feedback). Ship a thumbs-up/down, rating, or helpful/unhelpful component so users can signal AI output quality.",
169
+ suggestion: "Add a dedicated feedback component (e.g. AiFeedback, ThumbsUp/ThumbsDown, StarRating, WasThisHelpful) and optionally expose a reason enum (inaccurate, unhelpful, offensive) for categorized feedback.",
170
+ });
171
+ return { findings, opportunities: 1 };
172
+ };
173
+ export const rule = createLyseRule({
174
+ meta: {
175
+ axis: "ai-governance",
176
+ lyseRuleId: RULE_ID,
177
+ defaultSeverity: "warning",
178
+ shortDescription: "Detect a feedback control on AI output",
179
+ fullDescription: "When an AI-marker component is detected in the design system, this rule checks whether a companion feedback control exists co-located in the same file. Detection is per-file: a feedback vocabulary match only earns credit when the same file also contains an AI-marker (component name or JSX tag). Detection is two-phase. Phase 1 — name-based scan: checks exported identifiers and file base names against the feedback vocabulary (case-insensitive substring, separator-normalised): `feedback`, `thumbsup`, `thumbsdown`, `rating`, `vote`, `helpful`. Names ending in `Icon`, `Count`, `Result`, `Total`, `Tally`, or `Text` suffixes (display counters / icon primitives) are excluded. Phase 2 — categorized bonus: for each matched feedback component file, checks whether the source exposes a reason vocabulary word (`inaccurate`, `unhelpful`, `offensive`, `tooLong`, `harmful`, `misleading`, `irrelevant`) alongside an enum object, union type, or options array. Three outcomes: AI-marker present + feedback control co-located → `info` (notes if categorized; HAX G15 / PAIR Feedback cited); AI-marker present + no co-located feedback control → `warning`; no AI-marker anywhere → no finding.",
180
+ helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-feedback-control-present.md",
181
+ rationale: `Why it matters
182
+
183
+ Users interacting with AI-generated content need a structured way to signal output quality. HAX G15 (IBM Human-AI Experience guidelines, Granular feedback) and the Google PAIR Feedback & Control guidebook require that AI-powered interfaces expose a feedback control — thumbs up/down, rating, or helpful/unhelpful — so users can communicate when AI output is wrong, harmful, or unhelpful.
184
+
185
+ Vendor mandates: Microsoft Fluent 2 AI design guidelines mandate a feedback affordance on AI output; Amazon Cloudscape AI components documentation encourages it; Red Hat PatternFly AI component guidance recommends it. Without a dedicated component, teams implement ad-hoc controls with inconsistent UX, missing accessibility attributes, and no shared vocabulary for categorized negative feedback.
186
+
187
+ Categorized feedback (why was it bad?) provides richer model-improvement signal than binary thumbs alone. This rule rewards designs that expose a reason enum (inaccurate, unhelpful, offensive) by noting the categorized bonus in the info message.
188
+
189
+ The rule uses per-file co-location: a feedback control only earns credit when it lives in the same file as an AI-marker component or JSX tag. Generic form-validation components (ValidationFeedback), display counters (VoteCount), or product review widgets (ProductRating) in unrelated files do not falsely count. The rule fires only when at least one AI-marker file exists. A DS with no AI surface is not penalized.`,
190
+ examples: [
191
+ {
192
+ good: "// AiFeedback.tsx — co-located with AI marker\nexport const AILabel = () => null;\nexport const ThumbsUp = () => null;\nexport const ThumbsDown = () => null;",
193
+ bad: "// ValidationFeedback.tsx — form errors, no AI marker\nexport const ValidationFeedback = () => null;\n// AILabel.tsx — separate file, no feedback control",
194
+ },
195
+ {
196
+ good: "// AiFeedback.tsx — exposes categorized reasons alongside AI marker\nexport const AIBadge = () => null;\nexport const AiFeedback = () => null;\nexport const FeedbackReason = { inaccurate: 'inaccurate', unhelpful: 'unhelpful', offensive: 'offensive' } as const;",
197
+ bad: "// AiFeedback.tsx — no reason categories\nexport const AiFeedback = () => null;",
198
+ },
199
+ {
200
+ good: "// StarRating.tsx present alongside AIBadge in the same file",
201
+ bad: "// AIBadge.tsx present but no rating, vote, or helpful component shipped in the same file",
202
+ },
203
+ ],
204
+ allowlist: [
205
+ "repos containing `lyse-disable ai-governance/feedback-control-present` in an adjacent README or `.lyse.yaml` — rule is N/A",
206
+ "repos with no AI-marker component — no AI surface detected, rule emits nothing",
207
+ "files larger than 1 MB — skipped to avoid pathological cases",
208
+ "files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
209
+ ],
210
+ },
211
+ defaultOptions: [],
212
+ create: () => ({ evaluate }),
213
+ });
214
+ export const _internal = {
215
+ isFeedbackControlName,
216
+ detectCategorizedFeedback,
217
+ isAllowlisted,
218
+ scanForFeedbackControls,
219
+ DISABLE_DIRECTIVE,
220
+ ALLOWLIST_CANDIDATES,
221
+ FEEDBACK_PATTERNS,
222
+ };
223
+ //# sourceMappingURL=ai-governance-feedback-control-present.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-governance-feedback-control-present.js","sourceRoot":"","sources":["../../src/rules/ai-governance-feedback-control-present.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { Rule } from "../types.js";
2
+ export interface ControlHit {
3
+ name?: string;
4
+ label?: string;
5
+ }
6
+ export declare function detectPerOutputControls(source: string): ControlHit[];
7
+ export declare function detectGlobalAiToggle(source: string): boolean;
8
+ export declare const rule: Rule;
9
+ export declare const _internal: {
10
+ detectPerOutputControls: typeof detectPerOutputControls;
11
+ detectGlobalAiToggle: typeof detectGlobalAiToggle;
12
+ isAllowlisted: (repoRoot: string) => boolean;
13
+ DISABLE_DIRECTIVE: string;
14
+ ALLOWLIST_CANDIDATES: string[];
15
+ AI_MARKER_NAMES: ReadonlySet<string>;
16
+ };
@@ -0,0 +1,228 @@
1
+ import { join } from "node:path";
2
+ import fg from "fast-glob";
3
+ import { createLyseRule } from "./_rule-module.js";
4
+ import { AI_MARKER_NAMES, safeReadText, extractNamesFromSource, COMPONENT_GLOB, SCAN_IGNORE, fileHasAiMarker, makeAllowlistCheck, } from "./ai-governance-ai-marker-component-present.js";
5
+ const RULE_ID = "ai-governance/human-control-affordances";
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
+ // ────────────────────────────────────────────────────────────────────────────
17
+ // Group 1 — Per-output control affordances
18
+ // ────────────────────────────────────────────────────────────────────────────
19
+ const PER_OUTPUT_NAME_PATTERNS = [
20
+ "regenerate",
21
+ "retry",
22
+ "stopgenerat",
23
+ "editresponse",
24
+ "editoutput",
25
+ "undo",
26
+ "confirm",
27
+ "dismiss",
28
+ "accept",
29
+ "reject",
30
+ ];
31
+ const PER_OUTPUT_LABELS = new Set([
32
+ "regenerate",
33
+ "retry",
34
+ "stop",
35
+ "stop generating",
36
+ "undo",
37
+ "confirm",
38
+ "dismiss",
39
+ "accept",
40
+ "reject",
41
+ ]);
42
+ const BUTTON_OR_CTA_RE = /<(button|Button|a)\b[^>]*>/g;
43
+ function isPerOutputName(name) {
44
+ const lower = name.toLowerCase();
45
+ return PER_OUTPUT_NAME_PATTERNS.some((p) => lower.includes(p));
46
+ }
47
+ function extractButtonLabels(source) {
48
+ const labels = [];
49
+ BUTTON_OR_CTA_RE.lastIndex = 0;
50
+ let m;
51
+ while ((m = BUTTON_OR_CTA_RE.exec(source)) !== null) {
52
+ const rest = source.slice(m.index + m[0].length);
53
+ const close = rest.search(/<\/\s*(button|Button|a)\s*>/);
54
+ const inner = (close === -1 ? rest : rest.slice(0, close))
55
+ .replace(/<[^>]*>/g, " ")
56
+ .trim()
57
+ .replace(/\s+/g, " ");
58
+ if (inner.length > 0 && inner.length < 80)
59
+ labels.push(inner);
60
+ }
61
+ return labels;
62
+ }
63
+ export function detectPerOutputControls(source) {
64
+ const hits = [];
65
+ for (const name of extractNamesFromSource(source)) {
66
+ if (isPerOutputName(name))
67
+ hits.push({ name });
68
+ }
69
+ for (const label of extractButtonLabels(source)) {
70
+ if (PER_OUTPUT_LABELS.has(label.toLowerCase()))
71
+ hits.push({ label });
72
+ }
73
+ return hits;
74
+ }
75
+ // ────────────────────────────────────────────────────────────────────────────
76
+ // Group 2 — Global AI settings / disable toggle
77
+ // ────────────────────────────────────────────────────────────────────────────
78
+ const GLOBAL_NAME_PATTERNS = [
79
+ "aisettings",
80
+ "aipreferences",
81
+ "disableai",
82
+ "aicontrols",
83
+ "aiconfig",
84
+ ];
85
+ const GLOBAL_LABEL_RE = /(?<![a-zA-Z-])label\s*=\s*["'`](Disable AI|AI features|AI settings|Enable AI|AI on|AI off)["'`]/i;
86
+ function isGlobalAiToggleName(name) {
87
+ const lower = name.toLowerCase();
88
+ return GLOBAL_NAME_PATTERNS.some((p) => lower.includes(p));
89
+ }
90
+ export function detectGlobalAiToggle(source) {
91
+ for (const name of extractNamesFromSource(source)) {
92
+ if (isGlobalAiToggleName(name))
93
+ return true;
94
+ }
95
+ return GLOBAL_LABEL_RE.test(source);
96
+ }
97
+ // ────────────────────────────────────────────────────────────────────────────
98
+ // evaluate
99
+ // ────────────────────────────────────────────────────────────────────────────
100
+ const evaluate = async (ctx, _files) => {
101
+ const findings = [];
102
+ if (!ctx.repoRoot) {
103
+ return { findings, opportunities: 0 };
104
+ }
105
+ if (isAllowlisted(ctx.repoRoot)) {
106
+ return { findings, opportunities: 0 };
107
+ }
108
+ let componentFiles = [];
109
+ try {
110
+ componentFiles = fg.sync(COMPONENT_GLOB, {
111
+ cwd: ctx.repoRoot,
112
+ absolute: false,
113
+ dot: false,
114
+ ignore: SCAN_IGNORE,
115
+ onlyFiles: true,
116
+ unique: true,
117
+ });
118
+ }
119
+ catch {
120
+ // non-fatal
121
+ }
122
+ componentFiles.sort();
123
+ let seenMarker = false;
124
+ const allControlHits = [];
125
+ let hasGlobalToggle = false;
126
+ for (const rel of componentFiles) {
127
+ const abs = join(ctx.repoRoot, rel);
128
+ const source = safeReadText(abs);
129
+ if (!source)
130
+ continue;
131
+ const hasMarker = fileHasAiMarker(source, rel);
132
+ if (hasMarker) {
133
+ seenMarker = true;
134
+ const hits = detectPerOutputControls(source);
135
+ allControlHits.push(...hits);
136
+ }
137
+ if (detectGlobalAiToggle(source))
138
+ hasGlobalToggle = true;
139
+ }
140
+ if (!seenMarker) {
141
+ return { findings, opportunities: 0 };
142
+ }
143
+ const opportunities = componentFiles.length;
144
+ if (allControlHits.length > 0) {
145
+ const names = allControlHits
146
+ .map((h) => h.name ?? h.label ?? "")
147
+ .filter(Boolean);
148
+ const list = [...new Set(names)].join(", ");
149
+ const globalNote = hasGlobalToggle
150
+ ? "Global AI toggle: present."
151
+ : "Global AI toggle: not detected.";
152
+ findings.push({
153
+ ruleId: RULE_ID,
154
+ axis: "ai-governance",
155
+ severity: "info",
156
+ location: { file: "src/index.ts", line: 1, column: 1 },
157
+ message: `Per-output human-control affordances detected (HAX G8): ${list}. ${globalNote} (HAX G9)`,
158
+ suggestion: "Human-control affordances found — verify they are accessible and paired consistently with every AI-generated surface.",
159
+ });
160
+ return { findings, opportunities };
161
+ }
162
+ findings.push({
163
+ ruleId: RULE_ID,
164
+ axis: "ai-governance",
165
+ severity: "warning",
166
+ location: { file: "src/index.ts", line: 1, column: 1 },
167
+ message: "AI surface detected but no per-output control affordances found (HAX G8 / HAX G9). " +
168
+ "Ship Regenerate / Stop / Edit / Undo / Confirm / Dismiss / Accept / Reject controls " +
169
+ "so users can correct AI-generated output.",
170
+ suggestion: "Add per-output control components (e.g. RegenerateButton, StopGenerating, EditResponse, UndoAction, ConfirmOutput, DismissResult, AcceptSuggestion, RejectSuggestion) and a global AI settings/disable toggle (e.g. AISettings, AiPreferences, DisableAI).",
171
+ });
172
+ return { findings, opportunities };
173
+ };
174
+ export const rule = createLyseRule({
175
+ meta: {
176
+ axis: "ai-governance",
177
+ lyseRuleId: RULE_ID,
178
+ defaultSeverity: "warning",
179
+ shortDescription: "Detect human-control affordances over AI output",
180
+ fullDescription: "Scans component files (`**/*.{tsx,jsx,vue}`) for two groups of human-control affordances. " +
181
+ "Group 1 — per-output controls: exported component names or button labels matching the correction/dismissal vocabulary " +
182
+ "(Regenerate, Retry, Stop, Edit, Undo, Confirm, Dismiss, Accept, Reject). " +
183
+ "Group 2 — global AI toggle: exported names or toggle labels indicating a settings surface that lets users disable AI " +
184
+ "(AISettings, AiPreferences, DisableAI, or a label 'Disable AI' / 'AI features'). " +
185
+ "Cross-condition: when an AI-marker component is present (per the shared `isAiMarkerName` predicate) but no per-output control is found, emits `warning`; " +
186
+ "when controls are detected, emits `info` listing them and noting global toggle presence. " +
187
+ "Emits nothing when the DS has no AI surface. Guidelines: HAX G8 (efficient correction) / G9 (efficient dismissal).",
188
+ helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-human-control-affordances.md",
189
+ rationale: `Why it matters
190
+
191
+ HAX G8 (efficient correction) and G9 (efficient dismissal) are foundational human-AI interaction guidelines: users must be able to correct, stop, retry, or dismiss AI-generated output without friction. Without corresponding affordances in the design system, consuming teams implement ad-hoc controls that are inconsistent, inaccessible, and miss the correction loop entirely.
192
+
193
+ This rule performs static detection: does the DS export components covering the standard correction/dismissal vocabulary? It does not verify usage site coverage (deferred to Track 4).
194
+
195
+ A DS with no AI-marker component emits nothing and is not penalised.`,
196
+ examples: [
197
+ {
198
+ good: "// src/index.ts\nexport { AIBadge } from './ai-badge';\nexport { RegenerateButton } from './regenerate-button';\nexport { AISettings } from './ai-settings';",
199
+ bad: "// src/index.ts\nexport { AIBadge } from './ai-badge';\n// no correction or dismissal controls exported",
200
+ },
201
+ {
202
+ good: "// AIControls.tsx\nexport function RegenerateButton() { return <button>Regenerate</button>; }\nexport function DismissResult() { return <button>Dismiss</button>; }",
203
+ bad: "// No per-output control components; users have no standardised way to correct AI output",
204
+ },
205
+ {
206
+ good: "// AISettings.tsx\nexport function AISettings() {\n return <Toggle label=\"Disable AI\" />;\n}",
207
+ bad: "// AI surface present but no global disable/settings toggle shipped",
208
+ },
209
+ ],
210
+ allowlist: [
211
+ "repos containing `lyse-disable ai-governance/human-control-affordances` in an adjacent README or `.lyse.yaml` — rule is N/A",
212
+ "repos with no AI-marker component at all — no AI surface detected, rule emits nothing",
213
+ "files larger than 1 MB — skipped to avoid pathological cases",
214
+ "files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
215
+ ],
216
+ },
217
+ defaultOptions: [],
218
+ create: () => ({ evaluate }),
219
+ });
220
+ export const _internal = {
221
+ detectPerOutputControls,
222
+ detectGlobalAiToggle,
223
+ isAllowlisted,
224
+ DISABLE_DIRECTIVE,
225
+ ALLOWLIST_CANDIDATES,
226
+ AI_MARKER_NAMES,
227
+ };
228
+ //# sourceMappingURL=ai-governance-human-control-affordances.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-governance-human-control-affordances.js","sourceRoot":"","sources":["../../src/rules/ai-governance-human-control-affordances.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Rule } from "../types.js";
2
+ export declare function detectGateLanguage(content: string): boolean;
3
+ interface GateDocResult {
4
+ rel: string;
5
+ hasGateLanguage: boolean;
6
+ }
7
+ export declare function discoverGateDoc(repoRoot: string): GateDocResult | null;
8
+ export declare const rule: Rule;
9
+ export declare const _internal: {
10
+ detectGateLanguage: typeof detectGateLanguage;
11
+ discoverGateDoc: typeof discoverGateDoc;
12
+ isAllowlisted: (repoRoot: string) => boolean;
13
+ DISABLE_DIRECTIVE: string;
14
+ ALLOWLIST_CANDIDATES: string[];
15
+ STATIC_CANDIDATES: string[];
16
+ DOC_GLOBS: string[];
17
+ };
18
+ export {};