@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
package/rules-manifest.json
CHANGED
|
@@ -64,18 +64,18 @@
|
|
|
64
64
|
"id": "tokens/dtcg-conformance",
|
|
65
65
|
"axis": "tokens",
|
|
66
66
|
"defaultSeverity": "warning",
|
|
67
|
-
"shortDescription": "DTCG
|
|
68
|
-
"fullDescription": "Validates token JSON files (`*.tokens.json`, files under `tokens/**`) against the W3C Design Tokens Community Group
|
|
67
|
+
"shortDescription": "Strict W3C DTCG validation for token JSON files",
|
|
68
|
+
"fullDescription": "Validates token JSON files (`*.tokens.json`, files under `tokens/**`) against the W3C Design Tokens Community Group draft. Per-leaf checks: every token must declare `$value` and SHOULD declare `$type`; alias references `{group.name}` must resolve; type-specific values are validated (color = CSS color, dimension = number+unit, fontWeight integer 1-1000 or named, duration = number+unit, cubicBezier = 4-number array or named easing, number = finite number, fontFamily = non-empty string|array). Composite tokens (shadow, typography, border, transition, gradient) are shape-checked.",
|
|
69
69
|
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/tokens-dtcg-conformance.md",
|
|
70
|
-
"rationale": "Why it matters\n\nDTCG conformance is the contract between design and code. Non-conformant token files don't survive round-trips through Style Dictionary, Tokens Studio, or Figma Tokens plugins — they silently corrupt theming and break dark-mode propagation.\n\nThe most common drift modes are: tokens with `$value` but no `$type` (Style Dictionary can't infer the right transform), aliases that point to renamed paths after a refactor, and composite shadow tokens with legacy string shapes that no longer parse.",
|
|
70
|
+
"rationale": "Why it matters\n\nDTCG conformance is the contract between design and code. Non-conformant token files don't survive round-trips through Style Dictionary, Tokens Studio, or Figma Tokens plugins — they silently corrupt theming and break dark-mode propagation.\n\nThe most common drift modes are: tokens with `$value` but no `$type` (Style Dictionary can't infer the right transform), aliases that point to renamed paths after a refactor, type-claimed but malformed values (`$type: \"color\"` with `$value: \"blu\"`, `$type: \"dimension\"` with `$value: \"16\"` — no unit), and composite shadow tokens with legacy string shapes that no longer parse.",
|
|
71
71
|
"examples": [
|
|
72
72
|
{
|
|
73
73
|
"good": "{ \"color\": { \"brand\": { \"$value\": \"#2563eb\", \"$type\": \"color\" } } }",
|
|
74
74
|
"bad": "{ \"color\": { \"brand\": { \"$value\": \"#2563eb\" } } }"
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
|
-
"good": "{ \"
|
|
78
|
-
"bad": "{ \"
|
|
77
|
+
"good": "{ \"spacing\": { \"sm\": { \"$value\": \"8px\", \"$type\": \"dimension\" } } }",
|
|
78
|
+
"bad": "{ \"spacing\": { \"sm\": { \"$value\": \"8\", \"$type\": \"dimension\" } } } (no unit)"
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
"good": "{ \"semantic\": { \"primary\": { \"$value\": \"{color.brand}\", \"$type\": \"color\" } } } (when color.brand exists)",
|
|
@@ -85,7 +85,8 @@
|
|
|
85
85
|
"allowlist": [
|
|
86
86
|
"files matching `*.tokens.json` heuristic but containing only $-prefixed metadata (no $value anywhere) — skipped, not flagged",
|
|
87
87
|
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
88
|
-
"files matching `ctx.excludePaths` config"
|
|
88
|
+
"files matching `ctx.excludePaths` config",
|
|
89
|
+
"tokens declaring `$extensions.lyse.disable: [\"tokens/dtcg-conformance\"]` — skipped per the standard DTCG extension mechanism"
|
|
89
90
|
]
|
|
90
91
|
},
|
|
91
92
|
{
|
|
@@ -130,6 +131,31 @@
|
|
|
130
131
|
"files matching `designSystem.excludePaths` config"
|
|
131
132
|
]
|
|
132
133
|
},
|
|
134
|
+
{
|
|
135
|
+
"id": "components/contracts-strictness",
|
|
136
|
+
"axis": "components",
|
|
137
|
+
"defaultSeverity": "warning",
|
|
138
|
+
"shortDescription": "Component prop contracts must be strictly typed and .d.ts shipped",
|
|
139
|
+
"fullDescription": "Scans exported PascalCase components in .tsx/.jsx files for lax TypeScript prop contracts that hinder AI-agent code generation: props typed `any` or `unknown` (error), and variant-like props (name matching variant/size/intent/color/tone/appearance/kind) typed plain `string` instead of a string-literal union (warning). Also checks each publishable package.json for a `types` or `typings` field and that the referenced file exists post-build (warning when missing).",
|
|
140
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/components-contracts-strictness.md",
|
|
141
|
+
"rationale": "Why it matters\n\nAI coding agents and IDE tooling rely on TypeScript prop signatures to suggest valid usages. A prop typed `any` or `unknown` is a black hole — the agent has nothing to constrain its output and falls back to guesses. A variant prop typed plain `string` (`variant: string`) is just as bad: the agent has no way to know the component accepts only `\"primary\" | \"secondary\" | \"ghost\"` and will happily suggest `variant=\"huge\"`.\n\nShipping a `.d.ts` (declared via `package.json` `types` / `typings`) is the same problem at the package boundary: without a declaration file, downstream consumers and agents fall back to untyped any-mode and lose every guarantee the source code put in.\n\nThe rule errors on `any` / `unknown` because those are silent footguns. It warns on variant-string and missing `.d.ts` because there are legitimate (if narrow) reasons to leave them, and because the auto-fix path differs from the unsafe types.",
|
|
142
|
+
"examples": [
|
|
143
|
+
{
|
|
144
|
+
"good": "type ButtonVariant = \"primary\" | \"secondary\" | \"ghost\";\ninterface ButtonProps { variant: ButtonVariant; size: \"sm\" | \"md\" | \"lg\"; }\nexport function Button(props: ButtonProps) { return <button />; }",
|
|
145
|
+
"bad": "interface ButtonProps { variant: string; size: any; data: unknown; }\nexport function Button(props: ButtonProps) { return <button />; }"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"good": "{ \"name\": \"@acme/ui\", \"main\": \"./dist/index.js\", \"types\": \"./dist/index.d.ts\" }",
|
|
149
|
+
"bad": "{ \"name\": \"@acme/ui\", \"main\": \"./dist/index.js\" }"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"allowlist": [
|
|
153
|
+
"framework-allowed props: `children`, `ref`, `key`, `as`, `asChild` (rest-spread `...rest` and ref-forwarded types are skipped)",
|
|
154
|
+
"private packages (`\"private\": true`) and non-publishable package.json files (no `name`/`main`/`module`/`exports`/`types`/`typings`)",
|
|
155
|
+
"test files (.test.tsx, .spec.tsx)",
|
|
156
|
+
"inline `// lyse-disable-next-line components/contracts-strictness` directive (handled by the global suppression engine)"
|
|
157
|
+
]
|
|
158
|
+
},
|
|
133
159
|
{
|
|
134
160
|
"id": "naming/component-pascalcase",
|
|
135
161
|
"axis": "components",
|
|
@@ -284,6 +310,405 @@
|
|
|
284
310
|
"repos with no `componentsModule` configured or auto-detected — rule is N/A",
|
|
285
311
|
"indexes using `export * from ...` — accepted without counting named exports"
|
|
286
312
|
]
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"id": "ai-surface/mcp-config-present",
|
|
316
|
+
"axis": "ai-surface",
|
|
317
|
+
"defaultSeverity": "warning",
|
|
318
|
+
"shortDescription": "Design system should declare at least one MCP server",
|
|
319
|
+
"fullDescription": "Looks for an MCP (Model Context Protocol) configuration file at the repo root: `.mcp.json` (Claude Code convention), `.cursor/mcp.json` (Cursor convention), or `claude_desktop_config.json`. When found, validates the file is parseable JSON, has a top-level `mcpServers` object with at least one entry, and each server entry has a non-empty string key and a `command` (string); `args` (array) is optional. Absence emits one warning (DS not yet AI-agent-accessible). Malformed JSON, missing/empty `mcpServers`, or invalid server entries emit errors.",
|
|
320
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-surface-mcp-config-present.md",
|
|
321
|
+
"rationale": "Why it matters\n\nThe Model Context Protocol (MCP) is the de-facto standard for letting coding agents (Claude Code, Cursor, Claude Desktop) call tools that surface a design system's components, tokens, and docs at lookup time. A design system without an MCP server declaration leaves agents to scrape README and source files heuristically — the cost-vs-accuracy regression documented in Stream 1 of the AI-Consumable track.\n\nThe signal is binary and cheap to enforce: either the repo declares at least one valid `mcpServers` entry or it doesn't. A warning (not info) reflects the strategic importance of AI-Consumable readiness for Track 2 design systems: shipping a stable MCP surface is now table-stakes.\n\nSeverity escalates to error when a config file is present but malformed — a broken `.mcp.json` silently breaks every agent that tries to connect, which is worse than no config at all.",
|
|
322
|
+
"examples": [
|
|
323
|
+
{
|
|
324
|
+
"good": "{ \"mcpServers\": { \"lyse\": { \"command\": \"npx\", \"args\": [\"@lyse-labs/lyse\", \"mcp\"] } } }",
|
|
325
|
+
"bad": "{ \"mcpServers\": {} }"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"good": "{ \"mcpServers\": { \"design-system\": { \"command\": \"node\", \"args\": [\"./mcp-server.js\"] } } }",
|
|
329
|
+
"bad": "{ \"servers\": [] }"
|
|
330
|
+
}
|
|
331
|
+
],
|
|
332
|
+
"allowlist": [
|
|
333
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
334
|
+
"repos containing `// lyse-disable ai-surface/mcp-config-present` in an adjacent README — rule is N/A"
|
|
335
|
+
]
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"id": "ai-surface/llms-txt-structure",
|
|
339
|
+
"axis": "ai-surface",
|
|
340
|
+
"defaultSeverity": "warning",
|
|
341
|
+
"shortDescription": "llms.txt at repo root must follow the llmstxt.org structure",
|
|
342
|
+
"fullDescription": "Detects whether a design-system repository ships an `llms.txt` file at the repo root and validates the file's structure against the llmstxt.org specification: a single `# <title>` H1, a `> <summary>` blockquote, and at least one `## <section>` heading whose list items follow `- [<title>](<url>): <description>`. Absence emits a warning. A present-but-malformed `llms.txt` emits one error per structural issue. The optional companion file `llms-full.txt` is detected as a bonus signal but is not required.",
|
|
343
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-surface-llms-txt-structure.md",
|
|
344
|
+
"rationale": "Why it matters\n\n`llms.txt` (llmstxt.org, Tantum 2024) is the emerging convention for handing AI agents a token-cheap, curated map of a project. For a design system this is the AI-Consumable surface: a single discoverable entry that lists the canonical Quickstart, API reference, component index, and policy docs without forcing the agent to crawl the whole repo.\n\nAbsence is a missed opportunity, not a bug — agents fall back to scanning the README and source tree, which is slower and more expensive. Structural errors (missing H1, missing summary, malformed link rows) are scored as errors because consumers (cursor, claude code, custom agents) parse the file on the assumption it follows the spec, and silent malformations break the contract.\n\nThe companion `llms-full.txt` — a single-file inlining of every linked document — is a strong bonus signal but is not enforced as a hard requirement.",
|
|
345
|
+
"examples": [
|
|
346
|
+
{
|
|
347
|
+
"good": "# Acme DS\n\n> A token-first React design system.\n\n## Docs\n\n- [Quickstart](https://acme.dev/quickstart): Get started in 3 minutes.\n- [API reference](https://acme.dev/api): Full method index.",
|
|
348
|
+
"bad": "Welcome to Acme DS. We ship Buttons and Cards.\n\n- random link list"
|
|
349
|
+
}
|
|
350
|
+
],
|
|
351
|
+
"allowlist": [
|
|
352
|
+
"files larger than 1 MB at `llms.txt` — skipped to avoid pathological cases",
|
|
353
|
+
"repos whose README at the root contains the directive `lyse-disable ai-surface/llms-txt-structure` — rule is N/A"
|
|
354
|
+
]
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"id": "ai-surface/shadcn-registry-valid",
|
|
358
|
+
"axis": "ai-surface",
|
|
359
|
+
"defaultSeverity": "warning",
|
|
360
|
+
"shortDescription": "shadcn-style registry.json must be present and valid",
|
|
361
|
+
"fullDescription": "Detects whether the design system ships a shadcn-style component registry — `registry.json` at the repo root, `public/registry.json` (Next.js-hosted), or per-component `registry/*.json` files. Validates the minimal shadcn shape: each item must have `name` (string), `type` (e.g. \"registry:ui\") and a non-empty `files` array of `{ path, content?, type? }`. Optional fields (`dependencies`, `registryDependencies`, `tailwind`, `cssVars`) are not validated. When `components.json` exists but no registry is shipped, emits a warning for the missed AI-Consumable surface; malformed JSON or missing required fields are errors.",
|
|
362
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-surface-shadcn-registry-valid.md",
|
|
363
|
+
"rationale": "Why it matters\n\nA valid shadcn-style `registry.json` is the single most reliable signal that a design system is AI-Consumable today. The shadcn CLI uses it to install components into downstream apps; coding agents (Cursor, Claude, GPT) increasingly look for it to discover and pull components instead of scraping source files.\n\nDetection is conservative — we look for the canonical locations only (`registry.json`, `public/registry.json`, `registry/*.json`) and validate only the three required fields per the public shadcn schema. Optional fields like `tailwind` and `cssVars` vary across versions and are not part of the validity contract.\n\nA repo declaring `components.json` (the shadcn CLI marker) but no registry is a strong indicator the team is on the shadcn path but hasn't published the consumable surface — that's a warning, not an error. Malformed JSON or items missing `name`/`type`/`files` will silently break the shadcn CLI and any agent consumer, so they are errors.",
|
|
364
|
+
"examples": [
|
|
365
|
+
{
|
|
366
|
+
"good": "{ \"name\": \"button\", \"type\": \"registry:ui\", \"files\": [{ \"path\": \"ui/button.tsx\" }] }",
|
|
367
|
+
"bad": "{ \"name\": \"button\", \"type\": \"registry:ui\" }"
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"good": "{ \"items\": [{ \"name\": \"button\", \"type\": \"registry:ui\", \"files\": [{ \"path\": \"ui/button.tsx\" }] }] }",
|
|
371
|
+
"bad": "{ \"items\": [{ \"name\": \"button\" }] }"
|
|
372
|
+
}
|
|
373
|
+
],
|
|
374
|
+
"allowlist": [
|
|
375
|
+
"repos with no `components.json` AND no `registry.json` — rule is N/A",
|
|
376
|
+
"files larger than 4 MB — skipped to avoid pathological cases",
|
|
377
|
+
"files matching `ctx.excludePaths` config",
|
|
378
|
+
"disable in `.lyse.yaml`: `rules: { ai-surface/shadcn-registry-valid: off }`"
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"id": "ai-surface/agent-instruction-files",
|
|
383
|
+
"axis": "ai-surface",
|
|
384
|
+
"defaultSeverity": "warning",
|
|
385
|
+
"shortDescription": "Repo should ship Cursor rules or Claude skills with valid frontmatter",
|
|
386
|
+
"fullDescription": "Scans for agent instruction bundles at the repo root: `.cursor/rules/*.mdc` (Cursor rules) and `.claude/skills/*/SKILL.md` (Anthropic Claude skills). When neither is present, emits a single warning — the repo gives coding agents no project-specific guidance signal. When found, each file is parsed for YAML frontmatter and validated: Cursor rules must declare `description` and `globs`; Claude skills must declare `name` (kebab-case) and `description` (≤200 chars). Files larger than 5 KB raise a token-budget warning (they cost agents context on every load). Malformed frontmatter and missing required keys raise errors; oversize, non-kebab `name`, and overlong descriptions raise warnings.",
|
|
387
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-surface-agent-instruction-files.md",
|
|
388
|
+
"rationale": "Why it matters\n\nCursor rules and Claude skills are the contract surface for two of the most-used coding agents in 2026. Without at least one of these bundles, the agent has no project-specific guidance beyond `AGENTS.md` or `CLAUDE.md` — and even those don't carry the same auto-attach semantics as Cursor's `globs` field or the same auto-load semantics as Claude's skill manifest.\n\nBeyond presence, the *frontmatter* matters. Cursor uses `globs` to decide which rule fires for which file edit; missing `globs` silently disables the rule. Claude skills are loaded by their `name` + `description` pair (the description is the agent's \"tool selection\" prompt); a missing description means the skill is invisible to the agent's decision loop.\n\nToken budget is the third signal. The Anthropic agent skills documentation (Oct 2026) and the Cursor rules documentation both recommend keeping individual files small — long instruction files crowd out the actual context the agent needs to read, and inflate per-call cost. The 5 KB heuristic is the same threshold the Claude skill examples use.",
|
|
389
|
+
"examples": [
|
|
390
|
+
{
|
|
391
|
+
"good": "---\\ndescription: TypeScript style guide for this monorepo\\nglobs: [\"src/**/*.ts\", \"src/**/*.tsx\"]\\nalwaysApply: false\\n---\\n\\n# TypeScript style\\n\\nUse strict mode. Prefer type aliases over interfaces.",
|
|
392
|
+
"bad": "---\\n# missing required `description` and `globs`\\n---\\n\\n# TypeScript style\\n\\nUse strict mode."
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"good": "---\\nname: pr-checklist\\ndescription: Generates a PR checklist from the diff (≤200 chars)\\nversion: 1.0.0\\n---\\n\\n# PR checklist skill\\n\\nProcedural instructions for the agent.",
|
|
396
|
+
"bad": "---\\nname: PR_Checklist\\n# missing `description`; `name` is not kebab-case\\n---\\n\\n# PR checklist"
|
|
397
|
+
}
|
|
398
|
+
],
|
|
399
|
+
"allowlist": [
|
|
400
|
+
"files larger than 500 KB — skipped to avoid pathological cases (and counted as an oversize warning)",
|
|
401
|
+
"files matching `ctx.excludePaths` config",
|
|
402
|
+
"repos that ship only AGENTS.md/CLAUDE.md but neither Cursor rules nor Claude skills — emit one warning (not error) to nudge adoption"
|
|
403
|
+
]
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"id": "ai-governance/ai-tokens-reserved",
|
|
407
|
+
"axis": "ai-governance",
|
|
408
|
+
"defaultSeverity": "info",
|
|
409
|
+
"shortDescription": "Inventory reserved AI-marker design tokens",
|
|
410
|
+
"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`).",
|
|
411
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-tokens-reserved.md",
|
|
412
|
+
"rationale": "Why it matters\n\nAI-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.\n\nTrack 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.\n\nThe shared parser `detectReservedAiTokens(repoRoot)` is exported from `packages/core/src/parsers/ai-tokens.ts` so 3.3 reuses the exact same detection set.",
|
|
413
|
+
"examples": [
|
|
414
|
+
{
|
|
415
|
+
"good": "/* tokens.json */ { \"color\": { \"primary\": \"#0070f3\" } }",
|
|
416
|
+
"bad": "/* tokens.json */ { \"color\": { \"ai\": { \"primary\": \"#0070f3\" } } }"
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
"good": ":root { --color-primary: #0070f3; }",
|
|
420
|
+
"bad": ":root { --p-color-bg-magic: #f4f0fd; }"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
"good": "/* tokens.json */ { \"gradient\": { \"sunrise\": \"...\" } }",
|
|
424
|
+
"bad": "/* tokens.json */ { \"gradient\": { \"dragon-fruit\": \"...\" } }"
|
|
425
|
+
}
|
|
426
|
+
],
|
|
427
|
+
"allowlist": [
|
|
428
|
+
"repos containing `lyse-disable ai-governance/ai-tokens-reserved` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
429
|
+
"token files larger than 2 MB — skipped to avoid pathological cases",
|
|
430
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
431
|
+
]
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
"id": "ai-governance/ai-marker-component-present",
|
|
435
|
+
"axis": "ai-governance",
|
|
436
|
+
"defaultSeverity": "warning",
|
|
437
|
+
"shortDescription": "Detect AI-marker component in the DS export surface",
|
|
438
|
+
"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.",
|
|
439
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-marker-component-present.md",
|
|
440
|
+
"rationale": "Why it matters\n\nAI-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.\n\nThe 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.\n\nThis 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.\n\nThe exported `AI_MARKER_NAMES` constant is shared with sibling rules (Track 3.3 / 3.5) to ensure a single vocabulary source of truth.",
|
|
441
|
+
"examples": [
|
|
442
|
+
{
|
|
443
|
+
"good": "// src/index.ts\nexport { AILabel } from './ai-label';\nexport { Button } from './button';",
|
|
444
|
+
"bad": "// src/index.ts — no AI-marker component exported\nexport { Button } from './button';"
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
"good": "// AILabel.tsx — component file named with the marker vocabulary",
|
|
448
|
+
"bad": "// tokens.json has `color.ai.primary` but no AILabel/AIBadge component exists"
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
"good": "// Polaris-style: magic-icon.tsx component file detected",
|
|
452
|
+
"bad": "// Reserved tokens present, no marker component shipped"
|
|
453
|
+
}
|
|
454
|
+
],
|
|
455
|
+
"allowlist": [
|
|
456
|
+
"repos containing `lyse-disable ai-governance/ai-marker-component-present` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
457
|
+
"repos with no reserved AI tokens AND no marker component — no AI surface detected, rule emits nothing",
|
|
458
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
459
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
460
|
+
]
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
"id": "ai-governance/explainability-affordance",
|
|
464
|
+
"axis": "ai-governance",
|
|
465
|
+
"defaultSeverity": "warning",
|
|
466
|
+
"shortDescription": "Detect an explainability affordance paired with AI-marker components",
|
|
467
|
+
"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.",
|
|
468
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-explainability-affordance.md",
|
|
469
|
+
"rationale": "Why it matters\n\nUsers 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.\n\nWithout 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.\n\nThis 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.\n\nA DS with no AI-marker component at all has no AI surface and is not penalised.",
|
|
470
|
+
"examples": [
|
|
471
|
+
{
|
|
472
|
+
"good": "// src/index.ts\nexport { AILabel } from './ai-label';\nexport { ExplainPopover } from './explain-popover';",
|
|
473
|
+
"bad": "// src/index.ts\nexport { AILabel } from './ai-label';\n// no Explain/Citation/Confidence component exported"
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
"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>",
|
|
477
|
+
"bad": "// AILabel.tsx — marker with no explanation binding\n<span className=\"ai-badge\">AI</span>"
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
"good": "// src/index.ts\nexport { ConfidenceDisplay } from './confidence-display';\nexport { CitationList } from './citation-list';",
|
|
481
|
+
"bad": "// AI tokens and AIBadge exported, but no explainability affordance component present"
|
|
482
|
+
}
|
|
483
|
+
],
|
|
484
|
+
"allowlist": [
|
|
485
|
+
"repos containing `lyse-disable ai-governance/explainability-affordance` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
486
|
+
"repos with no AI-marker component — no AI surface detected, rule emits nothing",
|
|
487
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
488
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
"id": "ai-governance/ai-token-requires-marker",
|
|
493
|
+
"axis": "ai-governance",
|
|
494
|
+
"defaultSeverity": "error",
|
|
495
|
+
"shortDescription": "AI token usage requires a co-located AI-marker (Carbon mandatory composite)",
|
|
496
|
+
"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`).",
|
|
497
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-token-requires-marker.md",
|
|
498
|
+
"rationale": "Why it matters\n\nIBM 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.\n\nLyse 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.\n\nThe 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.",
|
|
499
|
+
"examples": [
|
|
500
|
+
{
|
|
501
|
+
"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);",
|
|
502
|
+
"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);"
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"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>",
|
|
506
|
+
"bad": "// AnswerCard.tsx — magic token applied, no marker whatsoever\n<div style={{ background: 'var(--p-color-bg-magic)' }}>\n {aiAnswer}\n</div>"
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"good": "// Component.tsx — no AI tokens, no AI-marker needed\nconst Card = () => <div style={{ color: 'var(--color-primary)' }}>{content}</div>;",
|
|
510
|
+
"bad": "// AIAssistant.vue — uses --ai-surface token, missing data-ai or AIBadge\n<template><div :style=\"{ background: 'var(--ai-surface)' }\">{{ answer }}</div></template>"
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
"allowlist": [
|
|
514
|
+
"component files larger than 1 MB — skipped",
|
|
515
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
|
|
516
|
+
"repos with no reserved AI tokens anywhere — rule is a no-op (zero findings, zero opportunities)",
|
|
517
|
+
"findings where token reference is ambiguous (dot-path heuristic only) — emitted as LOW confidence and suppressed by default"
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
"id": "ai-governance/ai-loading-error-states",
|
|
522
|
+
"axis": "ai-governance",
|
|
523
|
+
"defaultSeverity": "warning",
|
|
524
|
+
"shortDescription": "Named AI loading state with paired text + AI-specific error state present",
|
|
525
|
+
"fullDescription": "Scans component files (`**/*.{tsx,jsx,vue}`) for (a) a named AI loading state that carries paired visible or accessible text — not a bare spinner — and (b) an AI-specific error state component. Recognised loading vocabulary: `*Generating*`, `*Thinking*`, `*AILoading*`, `*StreamingIndicator*`, `*AIStatus*`, `*LoadingState*`. A bare generic spinner (`Spinner`, `LoadingSpinner`) without an AI-named wrapper and without a `loadingText` prop or visible status string does NOT satisfy the requirement. Recognised error vocabulary: `*AIError*`, `*GenerationError*`, `*AIFailure*`, `*GenerationFailed*`, `*AITimeout*`, or any name combining an AI keyword (`ai`, `generation`, `genai`, `llm`, `generative`) with an error keyword (`error`, `failure`, `failed`, `timeout`). Emits `warning` for each absent state type when an AI marker surface is detected; emits `info` when both are present; emits nothing when no AI surface is detected. Recovery-flow detection (retry orchestration, post-error behavior) is out of scope — tracked in Track 4 (#16).",
|
|
526
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-loading-error-states.md",
|
|
527
|
+
"rationale": "Why it matters\n\nAI-generating surfaces have two failure modes invisible to generic DS rules: (1) a loading state that gives no context — users see a spinner but don't know if the model is generating, stuck, or done — and (2) a generic error boundary that offers no AI-specific message, leaving users without guidance when a generation fails.\n\nAWS Cloudscape's generative-AI patterns mandate that every AI loading state carry named, visible text (e.g. \"Generating response…\") so users understand what the system is doing and can judge when to wait vs. cancel. A bare, unlabelled spinner violates this requirement.\n\nAn AI-specific error component is equally critical: it must communicate that the AI operation failed (not a generic network error) and ideally suggest next steps — though the recovery-flow logic itself is deferred to Track 4.\n\nThis rule detects the static presence of both states. It cross-conditions: if an AI marker is found but either state is absent, a warning is emitted; if both are present, an info finding confirms the DS is provisioned for AI-state handling.",
|
|
528
|
+
"examples": [
|
|
529
|
+
{
|
|
530
|
+
"good": "// Generating.tsx\nexport const Generating = () => (\n <div role=\"status\" aria-live=\"polite\">\n <Spinner /> Generating response…\n </div>\n);",
|
|
531
|
+
"bad": "// LoadingSpinner.tsx — bare spinner, no AI name, no paired text\nexport const LoadingSpinner = () => <svg className=\"spin\" />;"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"good": "// AILoading.tsx — loadingText prop satisfies paired-text requirement\nexport function AILoading({ loadingText }: { loadingText: string }) {\n return <div><Spinner /><span>{loadingText}</span></div>;\n}",
|
|
535
|
+
"bad": "// No AI-named loading state at all — only generic Spinner exported"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
"good": "// AIError.tsx\nexport const AIError = ({ message }: { message: string }) => (\n <div role=\"alert\">\n <strong>Generation failed</strong>\n <p>{message}</p>\n </div>\n);",
|
|
539
|
+
"bad": "// ErrorBoundary.tsx — generic, gives no AI context\nexport class ErrorBoundary extends React.Component {\n render() { return this.props.children; }\n}"
|
|
540
|
+
}
|
|
541
|
+
],
|
|
542
|
+
"allowlist": [
|
|
543
|
+
"repos containing `lyse-disable ai-governance/ai-loading-error-states` in an adjacent README, README.md, README.mdx, .lyse.yaml, or .lyse.yml — rule is N/A",
|
|
544
|
+
"repos with no AI marker surface detected (no AILabel, AIBadge, magic-* etc.) — no AI surface, rule emits nothing",
|
|
545
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
546
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`",
|
|
547
|
+
"recovery-flow detection (retry orchestration, post-error navigation) — explicitly deferred to Track 4 (#16)"
|
|
548
|
+
]
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"id": "ai-governance/human-control-affordances",
|
|
552
|
+
"axis": "ai-governance",
|
|
553
|
+
"defaultSeverity": "warning",
|
|
554
|
+
"shortDescription": "Detect human-control affordances over AI output",
|
|
555
|
+
"fullDescription": "Scans component files (`**/*.{tsx,jsx,vue}`) for two groups of human-control affordances. Group 1 — per-output controls: exported component names or button labels matching the correction/dismissal vocabulary (Regenerate, Retry, Stop, Edit, Undo, Confirm, Dismiss, Accept, Reject). Group 2 — global AI toggle: exported names or toggle labels indicating a settings surface that lets users disable AI (AISettings, AiPreferences, DisableAI, or a label 'Disable AI' / 'AI features'). Cross-condition: when an AI-marker component is present (per the shared `isAiMarkerName` predicate) but no per-output control is found, emits `warning`; when controls are detected, emits `info` listing them and noting global toggle presence. Emits nothing when the DS has no AI surface. Guidelines: HAX G8 (efficient correction) / G9 (efficient dismissal).",
|
|
556
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-human-control-affordances.md",
|
|
557
|
+
"rationale": "Why it matters\n\nHAX 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.\n\nThis 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).\n\nA DS with no AI-marker component emits nothing and is not penalised.",
|
|
558
|
+
"examples": [
|
|
559
|
+
{
|
|
560
|
+
"good": "// src/index.ts\nexport { AIBadge } from './ai-badge';\nexport { RegenerateButton } from './regenerate-button';\nexport { AISettings } from './ai-settings';",
|
|
561
|
+
"bad": "// src/index.ts\nexport { AIBadge } from './ai-badge';\n// no correction or dismissal controls exported"
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
"good": "// AIControls.tsx\nexport function RegenerateButton() { return <button>Regenerate</button>; }\nexport function DismissResult() { return <button>Dismiss</button>; }",
|
|
565
|
+
"bad": "// No per-output control components; users have no standardised way to correct AI output"
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
"good": "// AISettings.tsx\nexport function AISettings() {\n return <Toggle label=\"Disable AI\" />;\n}",
|
|
569
|
+
"bad": "// AI surface present but no global disable/settings toggle shipped"
|
|
570
|
+
}
|
|
571
|
+
],
|
|
572
|
+
"allowlist": [
|
|
573
|
+
"repos containing `lyse-disable ai-governance/human-control-affordances` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
574
|
+
"repos with no AI-marker component at all — no AI surface detected, rule emits nothing",
|
|
575
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
576
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
577
|
+
]
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
"id": "ai-governance/ai-marker-anti-patterns",
|
|
581
|
+
"axis": "ai-governance",
|
|
582
|
+
"defaultSeverity": "warning",
|
|
583
|
+
"shortDescription": "Lint AI-marker anti-patterns in component source files",
|
|
584
|
+
"fullDescription": "Scans component files (`**/*.{tsx,jsx,vue}`) for two forbidden AI-marking anti-patterns. Anti-pattern A: a sparkle signal (`✨` literal, `Sparkle`/`Sparkles`/`SparkleIcon` import, or `icon=\"sparkle*\"` prop) used as the sole AI marker with no accompanying text label or AI-marker component — SAP Fiori XAI forbids icon-only marking because it fails accessibility and is ambiguous. Anti-pattern B: the standalone, case-sensitive token `AI` used as the primary action label of a `<button>`, `<Button>`, or `<a>` element — GitLab Pajamas forbids this because `AI` describes the technology, not the user action. Both detectors are pure source heuristics with no runtime or behavioral analysis. Emits `warning` for each occurrence. A sparkle accompanied by a text AI-marker or AI-marker component (`AILabel`, `AIBadge`, magic-* etc.) is not flagged. `AI` appearing only in body or descriptor text (headings, paragraphs) is not flagged.",
|
|
585
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-marker-anti-patterns.md",
|
|
586
|
+
"rationale": "Why it matters\n\nIcon-only AI marking (a lone sparkle ✨) silently fails users who rely on assistive technology: a screen reader announces an image description or nothing at all — not \"AI-generated.\" SAP Fiori XAI made this explicit: sparkle MUST be paired with a text label or dedicated AI-marker component. Lyse enforces the same constraint statically.\n\nLabelling a CTA button \"AI\" is a UX anti-pattern: \"AI\" is a noun describing technology, not an action verb. GitLab Pajamas explicitly forbids it, recommending \"Summarize,\" \"Explain,\" or \"Generate\" instead — words that tell the user what will happen, not what engine does it. Lyse catches this in source before it ships.\n\nBoth findings are warnings (not errors) because the fix is mechanical and low-risk.",
|
|
587
|
+
"examples": [
|
|
588
|
+
{
|
|
589
|
+
"good": "<span><AILabel>AI</AILabel> <Sparkles aria-hidden/></span>",
|
|
590
|
+
"bad": "<span>✨ {generatedText}</span>"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
"good": "<button>Summarize</button>",
|
|
594
|
+
"bad": "<button>AI</button>"
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
"good": "<button>Draft reply</button>",
|
|
598
|
+
"bad": "<button>Ask AI</button>"
|
|
599
|
+
}
|
|
600
|
+
],
|
|
601
|
+
"allowlist": [
|
|
602
|
+
"repos containing `lyse-disable ai-governance/ai-marker-anti-patterns` in an adjacent README or `.lyse.yaml` — rule is N/A for the entire repo",
|
|
603
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
604
|
+
"files under `node_modules/`, `dist/`, `build/`",
|
|
605
|
+
"sparkle accompanied by a text AI-marker (`AILabel`, `AIBadge`, magic-*, GenAI*, etc.) — not flagged",
|
|
606
|
+
"`AI` in non-CTA elements (headings, paragraphs, spans) — not flagged"
|
|
607
|
+
]
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
"id": "ai-governance/ai-content-live-region",
|
|
611
|
+
"axis": "ai-governance",
|
|
612
|
+
"defaultSeverity": "warning",
|
|
613
|
+
"shortDescription": "Detect ARIA live region on AI output / streaming components",
|
|
614
|
+
"fullDescription": "Globs `**/*.{tsx,jsx,vue}` and runs two per-file detectors: one that identifies AI-output/streaming surfaces (AI_MARKER_NAMES, *AIResponse*, *ChatMessage*, isStreaming, isGenerating props), another that detects live-region attributes (aria-live=\"polite|assertive\", role=\"status\", role=\"alert\", PatternFly isLiveRegion). Cross-condition: AI surface present but no live region → `warning`; AI surface inside a live region → `info` naming the mechanism; no AI/streaming surface → no finding.",
|
|
615
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-ai-content-live-region.md",
|
|
616
|
+
"rationale": "Why it matters\n\nScreen-reader users rely on ARIA live regions to hear dynamically updated content. When an AI model streams a response token-by-token, the DOM updates silently unless the container carries aria-live=\"polite\", role=\"status\", role=\"alert\", or an equivalent framework mechanism. Without a live region, visually impaired users miss the entire AI response — a failure of both accessibility and AI-governance (the system produces output that some users cannot perceive).\n\nWAI-ARIA specifies that aria-live=\"polite\" announces changes after the user is idle (appropriate for non-urgent AI output); aria-live=\"assertive\" / role=\"alert\" interrupt immediately (use only for errors). PatternFly provides isLiveRegion as a prop on several container components to avoid hand-rolling the attribute.\n\nThis rule checks the static presence of a live-region mechanism in the same file as an AI-output or streaming component — it does not verify runtime behaviour or dynamic DOM updates.",
|
|
617
|
+
"examples": [
|
|
618
|
+
{
|
|
619
|
+
"good": "// aria-live wraps AI output (React)\nexport function AiAnswer({ content }: { content: string }) {\n return (\n <div aria-live=\"polite\">\n <AILabel>AI</AILabel>\n <p>{content}</p>\n </div>\n );\n}",
|
|
620
|
+
"bad": "// AI output with no live region\nexport function AiAnswer({ content }: { content: string }) {\n return (\n <div className=\"response\">\n <ChatAIResponse content={content} />\n </div>\n );\n}"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
"good": "// PatternFly isLiveRegion\n<TextContent isLiveRegion>{streamedContent}</TextContent>",
|
|
624
|
+
"bad": "// isStreaming prop but no live region wrapper\n<OutputBlock isGenerating={generating} />"
|
|
625
|
+
}
|
|
626
|
+
],
|
|
627
|
+
"allowlist": [
|
|
628
|
+
"repos containing `lyse-disable ai-governance/ai-content-live-region` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
629
|
+
"repos with no AI-output or streaming component — no AI surface detected, rule emits nothing",
|
|
630
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
631
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
632
|
+
]
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
"id": "ai-governance/disclaimer-present",
|
|
636
|
+
"axis": "ai-governance",
|
|
637
|
+
"defaultSeverity": "warning",
|
|
638
|
+
"shortDescription": "Detect AI disclaimer near AI-generated output",
|
|
639
|
+
"fullDescription": "Scans component files (`**/*.{tsx,jsx,vue}`) for a co-located AI disclaimer — either disclaimer text (`Generated by AI`, `AI-generated`, `May be inaccurate`, `Check important info`, `Powered by AI`, case-insensitive word-boundary) or a `*Disclaimer*` / `*AIDisclaimer*` component tag. Highest-confidence signal: the GitLab Pajamas canonical disclaimer wording matched verbatim. Cross-condition: if an AI-marker component (per AI_MARKER_NAMES) is present but no disclaimer is found, emits `warning`; if a disclaimer is detected, emits `info` (noting the GitLab canonical match when applicable). Emits nothing when no AI surface is present.",
|
|
640
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-disclaimer-present.md",
|
|
641
|
+
"rationale": "Why it matters\n\nAI 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.\n\nGitLab 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.\n\nThis 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.\n\nThe 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.",
|
|
642
|
+
"examples": [
|
|
643
|
+
{
|
|
644
|
+
"good": "// AISummary.tsx — marker + disclaimer present\n<AILabel />\n<p>{summary}</p>\n<p className=\"disclaimer\">Generated by AI. May be inaccurate.</p>",
|
|
645
|
+
"bad": "// AISummary.tsx — marker present, no disclaimer\n<AILabel />\n<p>{summary}</p>"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
"good": "// GitLab canonical wording — highest-confidence pass\n<AIDisclaimer message=\"AI-generated content may be inaccurate. Always check important information.\" />",
|
|
649
|
+
"bad": "// AI marker but disclaimer is buried in a tooltip the user may never see\n<AIBadge /><Tooltip>AI content</Tooltip>"
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
"good": "// Vue SFC — disclaimer component rendered alongside output\n<template><AIOutput :text /><AiDisclaimer /></template>",
|
|
653
|
+
"bad": "// Vue SFC — AI token reserved in class, no disclaimer rendered\n<template><div class=\"ai-output\">{{ text }}</div></template>"
|
|
654
|
+
}
|
|
655
|
+
],
|
|
656
|
+
"allowlist": [
|
|
657
|
+
"repos containing `lyse-disable ai-governance/disclaimer-present` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
658
|
+
"repos with no AI-marker component AND no disclaimer text — no AI surface detected, rule emits nothing",
|
|
659
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
660
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
661
|
+
]
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
"id": "ai-governance/feedback-control-present",
|
|
665
|
+
"axis": "ai-governance",
|
|
666
|
+
"defaultSeverity": "warning",
|
|
667
|
+
"shortDescription": "Detect a feedback control on AI output",
|
|
668
|
+
"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.",
|
|
669
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-feedback-control-present.md",
|
|
670
|
+
"rationale": "Why it matters\n\nUsers 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.\n\nVendor 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.\n\nCategorized 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.\n\nThe 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.",
|
|
671
|
+
"examples": [
|
|
672
|
+
{
|
|
673
|
+
"good": "// AiFeedback.tsx — co-located with AI marker\nexport const AILabel = () => null;\nexport const ThumbsUp = () => null;\nexport const ThumbsDown = () => null;",
|
|
674
|
+
"bad": "// ValidationFeedback.tsx — form errors, no AI marker\nexport const ValidationFeedback = () => null;\n// AILabel.tsx — separate file, no feedback control"
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
"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;",
|
|
678
|
+
"bad": "// AiFeedback.tsx — no reason categories\nexport const AiFeedback = () => null;"
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
"good": "// StarRating.tsx present alongside AIBadge in the same file",
|
|
682
|
+
"bad": "// AIBadge.tsx present but no rating, vote, or helpful component shipped in the same file"
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
"allowlist": [
|
|
686
|
+
"repos containing `lyse-disable ai-governance/feedback-control-present` in an adjacent README or `.lyse.yaml` — rule is N/A",
|
|
687
|
+
"repos with no AI-marker component — no AI surface detected, rule emits nothing",
|
|
688
|
+
"files larger than 1 MB — skipped to avoid pathological cases",
|
|
689
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
690
|
+
]
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
"id": "ai-governance/value-gate-doc-present",
|
|
694
|
+
"axis": "ai-governance",
|
|
695
|
+
"defaultSeverity": "warning",
|
|
696
|
+
"shortDescription": "AI value-gate governance doc must be present when an AI surface exists",
|
|
697
|
+
"fullDescription": "When a design system ships an AI surface — detected by an AI-marker component (via the `AI_MARKER_NAMES` vocabulary from Track 3.2) or reserved AI-marker design tokens (Track 3.1, `detectReservedAiTokens`) — this rule checks that a governance value-gate doc exists and contains structured go/no-go decision language. Candidate locations scanned: `AI_GOVERNANCE.md` at repo root, `docs/ai-value-gate.md`, `docs/ai-governance.md`, `docs/ai-readiness.md`, `docs/ai-checklist.md`, `.lyse/ai-value-gate.md`, and `AI_GOVERNANCE.md` anywhere in the repo tree. A doc is considered valid if it contains at least one of: \"is AI needed\", \"value gate\", \"go/no-go\", \"should this be AI\", \"is AI the right tool\", \"ai-readiness\", \"why AI?\", or \"deterministic rule ... instead of AI\". Emits `warning` when AI surface exists but no doc found, or doc found but lacks gate language. Emits `info` when a valid value-gate doc is present. Emits nothing when the DS has no AI surface.",
|
|
698
|
+
"helpUri": "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-governance-value-gate-doc-present.md",
|
|
699
|
+
"rationale": "Why it matters\n\nBefore shipping AI-powered UI, design systems need a structured answer to \"is AI actually needed here?\" — a go/no-go gate. ServiceNow's internal \"10-Q\" process documents exactly this: a fixed checklist of questions (is AI the right tool? can a deterministic rule solve this? what is the fallback?) that must be answered before any AI feature lands.\n\nWithout this gate doc, individual teams ship AI features ad-hoc, often when a rule-based solution would have been faster, cheaper, and more auditable. The governance debt accumulates: inconsistent AI use, unclear fallback paths, and no audit trail.\n\nThis rule does NOT enforce the answers — only that the DS ships the document, ensuring the gate process is defined and discoverable. Teams that use Lyse for AI governance get a measurable signal: \"this DS has defined its AI decision process.\"\n\nThe cross-condition logic — checking for an AI surface first via `scanForMarkerComponents` and `detectReservedAiTokens` — ensures DSs without any AI surface are not penalised. The rule is additive: it rewards good governance without creating false positives for projects that have not yet adopted AI features.",
|
|
700
|
+
"examples": [
|
|
701
|
+
{
|
|
702
|
+
"good": "# AI Value Gate\n\n## Is AI needed?\n- [ ] Can a deterministic rule solve this instead?\n- [ ] Does ML outperform a rule on this specific input distribution?\n- [ ] What is the fallback if the model is unavailable?\n\nGo/no-go: answer all three before shipping.",
|
|
703
|
+
"bad": "# AI Guidelines\n\nUse `AILabel` on all AI-generated content surfaces.\nFollow the design tokens from the `ai` namespace."
|
|
704
|
+
}
|
|
705
|
+
],
|
|
706
|
+
"allowlist": [
|
|
707
|
+
"repos containing `lyse-disable ai-governance/value-gate-doc-present` in README, README.md, README.mdx, readme.md, .lyse.yaml, or .lyse.yml — rule is N/A",
|
|
708
|
+
"DSs with no AI surface (no AI-marker component and no reserved AI tokens) — rule emits nothing",
|
|
709
|
+
"doc files larger than 500 KB — skipped to avoid pathological cases",
|
|
710
|
+
"files under `node_modules/`, `dist/`, `build/`, `.git/`, `.next/`, `out/`, `coverage/`"
|
|
711
|
+
]
|
|
287
712
|
}
|
|
288
713
|
]
|
|
289
714
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"additionalProperties": false,
|
|
17
17
|
"properties": {
|
|
18
18
|
"id": { "type": "string" },
|
|
19
|
-
"axis": { "type": "string", "enum": ["tokens", "a11y", "components", "stories", "ai-surface"] },
|
|
19
|
+
"axis": { "type": "string", "enum": ["tokens", "a11y", "components", "stories", "ai-surface", "ai-governance"] },
|
|
20
20
|
"defaultSeverity": { "type": "string", "enum": ["error", "warning", "info"] },
|
|
21
21
|
"shortDescription": { "type": "string" },
|
|
22
22
|
"fullDescription": { "type": "string" },
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, access } from "node:fs/promises";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { detectFromGit } from "../detection/from-git.js";
|
|
5
|
-
import { confirm } from "../menu/prompts.js";
|
|
6
|
-
import { appendCiSetupCompletedEvent } from "../history/ndjson-store.js";
|
|
7
|
-
export async function runCiSetup(opt) {
|
|
8
|
-
const git = await detectFromGit(opt.cwd);
|
|
9
|
-
if (!git.github.value) {
|
|
10
|
-
throw new Error("No GitHub remote detected. `lyse ci setup` requires a GitHub repo.");
|
|
11
|
-
}
|
|
12
|
-
const wfPath = join(opt.cwd, ".github/workflows/lyse.yml");
|
|
13
|
-
try {
|
|
14
|
-
await access(wfPath);
|
|
15
|
-
if (!opt.autoApprove) {
|
|
16
|
-
const overwrite = await confirm(`.github/workflows/lyse.yml exists. Overwrite?`, false);
|
|
17
|
-
if (!overwrite) {
|
|
18
|
-
console.log("Aborted (existing file preserved).");
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
// File does not exist — proceed to create
|
|
25
|
-
}
|
|
26
|
-
// Read template — file is co-located via package.json files[]
|
|
27
|
-
const templatePath = join(dirname(fileURLToPath(import.meta.url)), "templates/lyse-workflow.yml.template");
|
|
28
|
-
const template = await readFile(templatePath, "utf8");
|
|
29
|
-
const rendered = template
|
|
30
|
-
.replaceAll("{{ default_branch }}", git.git.value?.defaultBranch ?? "main")
|
|
31
|
-
.replaceAll("{{ threshold }}", String(opt.threshold ?? 70))
|
|
32
|
-
.replaceAll("{{ post_pr_comment }}", String(opt.postPrComment ?? true))
|
|
33
|
-
.replaceAll("{{ upload_sarif }}", String(opt.uploadSarif ?? true))
|
|
34
|
-
.replaceAll("{{ publish_bench }}", String(opt.publishBench ?? false));
|
|
35
|
-
await mkdir(join(opt.cwd, ".github/workflows"), { recursive: true });
|
|
36
|
-
await writeFile(wfPath, rendered);
|
|
37
|
-
console.log(`✓ Created ${wfPath}`);
|
|
38
|
-
console.log(` Don't forget: git add .github/workflows/lyse.yml && commit && push`);
|
|
39
|
-
// Emit telemetry event (opt-in only)
|
|
40
|
-
await appendCiSetupCompletedEvent(opt.cwd, opt.threshold ?? 70);
|
|
41
|
-
}
|
|
42
|
-
//# sourceMappingURL=ci-setup.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ci-setup.js","sourceRoot":"","sources":["../../src/commands/ci-setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAWzE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAmB;IAClD,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,4BAA4B,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,+CAA+C,EAAE,KAAK,CAAC,CAAC;YACxF,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;IAED,8DAA8D;IAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,sCAAsC,CAAC,CAAC;IAC3G,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAEtD,MAAM,QAAQ,GAAG,QAAQ;SACtB,UAAU,CAAC,sBAAsB,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,IAAI,MAAM,CAAC;SAC1E,UAAU,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;SAC1D,UAAU,CAAC,uBAAuB,EAAE,MAAM,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC;SACtE,UAAU,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC;SACjE,UAAU,CAAC,qBAAqB,EAAE,MAAM,CAAC,GAAG,CAAC,YAAY,IAAI,KAAK,CAAC,CAAC,CAAC;IAExE,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,mBAAmB,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAElC,OAAO,CAAC,GAAG,CAAC,aAAa,MAAM,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,sEAAsE,CAAC,CAAC;IAEpF,qCAAqC;IACrC,MAAM,2BAA2B,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;AAClE,CAAC"}
|