@lyse-labs/lyse 0.1.0-alpha.2
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 +45 -0
- package/PRIVACY.md +181 -0
- package/README.md +34 -0
- package/SECURITY.md +43 -0
- package/dist/bench/evidence-pack/anti-dummy.d.ts +11 -0
- package/dist/bench/evidence-pack/anti-dummy.js +20 -0
- package/dist/bench/evidence-pack/anti-dummy.js.map +1 -0
- package/dist/bench/evidence-pack/builder.d.ts +14 -0
- package/dist/bench/evidence-pack/builder.js +77 -0
- package/dist/bench/evidence-pack/builder.js.map +1 -0
- package/dist/bench/evidence-pack/histograms.d.ts +2 -0
- package/dist/bench/evidence-pack/histograms.js +131 -0
- package/dist/bench/evidence-pack/histograms.js.map +1 -0
- package/dist/bench/evidence-pack/manifest-detector.d.ts +2 -0
- package/dist/bench/evidence-pack/manifest-detector.js +65 -0
- package/dist/bench/evidence-pack/manifest-detector.js.map +1 -0
- package/dist/bench/evidence-pack/package-json-digest.d.ts +2 -0
- package/dist/bench/evidence-pack/package-json-digest.js +51 -0
- package/dist/bench/evidence-pack/package-json-digest.js.map +1 -0
- package/dist/bench/evidence-pack/sampler.d.ts +2 -0
- package/dist/bench/evidence-pack/sampler.js +140 -0
- package/dist/bench/evidence-pack/sampler.js.map +1 -0
- package/dist/bench/evidence-pack/types.d.ts +127 -0
- package/dist/bench/evidence-pack/types.js +2 -0
- package/dist/bench/evidence-pack/types.js.map +1 -0
- package/dist/bench/evidence-pack/verifier-corpus.d.ts +5 -0
- package/dist/bench/evidence-pack/verifier-corpus.js +13 -0
- package/dist/bench/evidence-pack/verifier-corpus.js.map +1 -0
- package/dist/bench/taxonomy-loader.d.ts +20 -0
- package/dist/bench/taxonomy-loader.js +28 -0
- package/dist/bench/taxonomy-loader.js.map +1 -0
- package/dist/bench/taxonomy.v3.json +20 -0
- package/dist/cli/__tests__/version-migration.test.d.ts +1 -0
- package/dist/cli/__tests__/version-migration.test.js +69 -0
- package/dist/cli/__tests__/version-migration.test.js.map +1 -0
- package/dist/cli/output/__tests__/eslint-style.test.d.ts +1 -0
- package/dist/cli/output/__tests__/eslint-style.test.js +205 -0
- package/dist/cli/output/__tests__/eslint-style.test.js.map +1 -0
- package/dist/cli/output/__tests__/limit.test.d.ts +1 -0
- package/dist/cli/output/__tests__/limit.test.js +65 -0
- package/dist/cli/output/__tests__/limit.test.js.map +1 -0
- package/dist/cli/output/eslint-style.d.ts +18 -0
- package/dist/cli/output/eslint-style.js +42 -0
- package/dist/cli/output/eslint-style.js.map +1 -0
- package/dist/cli/output/limit.d.ts +5 -0
- package/dist/cli/output/limit.js +32 -0
- package/dist/cli/output/limit.js.map +1 -0
- package/dist/cli/output/score-gauge.d.ts +4 -0
- package/dist/cli/output/score-gauge.js +15 -0
- package/dist/cli/output/score-gauge.js.map +1 -0
- package/dist/cli/version-migration.d.ts +18 -0
- package/dist/cli/version-migration.js +49 -0
- package/dist/cli/version-migration.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +856 -0
- package/dist/cli.js.map +1 -0
- package/dist/codemods/diff.d.ts +17 -0
- package/dist/codemods/diff.js +77 -0
- package/dist/codemods/diff.js.map +1 -0
- package/dist/codemods/git-helpers.d.ts +10 -0
- package/dist/codemods/git-helpers.js +75 -0
- package/dist/codemods/git-helpers.js.map +1 -0
- package/dist/codemods/index.d.ts +27 -0
- package/dist/codemods/index.js +40 -0
- package/dist/codemods/index.js.map +1 -0
- package/dist/codemods/naming-component-pascalcase.d.ts +13 -0
- package/dist/codemods/naming-component-pascalcase.js +106 -0
- package/dist/codemods/naming-component-pascalcase.js.map +1 -0
- package/dist/codemods/naming-hook-prefix.d.ts +13 -0
- package/dist/codemods/naming-hook-prefix.js +86 -0
- package/dist/codemods/naming-hook-prefix.js.map +1 -0
- package/dist/codemods/safety.d.ts +50 -0
- package/dist/codemods/safety.js +109 -0
- package/dist/codemods/safety.js.map +1 -0
- package/dist/codemods/safety.test.d.ts +1 -0
- package/dist/codemods/safety.test.js +154 -0
- package/dist/codemods/safety.test.js.map +1 -0
- package/dist/codemods/shadow-native.d.ts +2 -0
- package/dist/codemods/shadow-native.js +103 -0
- package/dist/codemods/shadow-native.js.map +1 -0
- package/dist/codemods/tokens-color.d.ts +2 -0
- package/dist/codemods/tokens-color.js +69 -0
- package/dist/codemods/tokens-color.js.map +1 -0
- package/dist/codemods/tokens-spacing.d.ts +2 -0
- package/dist/codemods/tokens-spacing.js +66 -0
- package/dist/codemods/tokens-spacing.js.map +1 -0
- package/dist/commands/__tests__/email-prompt.test.d.ts +1 -0
- package/dist/commands/__tests__/email-prompt.test.js +126 -0
- package/dist/commands/__tests__/email-prompt.test.js.map +1 -0
- package/dist/commands/__tests__/explain-score.test.d.ts +1 -0
- package/dist/commands/__tests__/explain-score.test.js +88 -0
- package/dist/commands/__tests__/explain-score.test.js.map +1 -0
- package/dist/commands/__tests__/feedback.test.d.ts +1 -0
- package/dist/commands/__tests__/feedback.test.js +186 -0
- package/dist/commands/__tests__/feedback.test.js.map +1 -0
- package/dist/commands/audit-flags.d.ts +49 -0
- package/dist/commands/audit-flags.js +17 -0
- package/dist/commands/audit-flags.js.map +1 -0
- package/dist/commands/audit-pipeline.d.ts +31 -0
- package/dist/commands/audit-pipeline.js +342 -0
- package/dist/commands/audit-pipeline.js.map +1 -0
- package/dist/commands/bench-pack.d.ts +5 -0
- package/dist/commands/bench-pack.js +61 -0
- package/dist/commands/bench-pack.js.map +1 -0
- package/dist/commands/ci-setup.d.ts +9 -0
- package/dist/commands/ci-setup.js +42 -0
- package/dist/commands/ci-setup.js.map +1 -0
- package/dist/commands/email-prompt.d.ts +36 -0
- package/dist/commands/email-prompt.js +160 -0
- package/dist/commands/email-prompt.js.map +1 -0
- package/dist/commands/explain-score.d.ts +35 -0
- package/dist/commands/explain-score.js +137 -0
- package/dist/commands/explain-score.js.map +1 -0
- package/dist/commands/explain.d.ts +6 -0
- package/dist/commands/explain.js +93 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/feedback.d.ts +31 -0
- package/dist/commands/feedback.js +155 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/fix.d.ts +48 -0
- package/dist/commands/fix.js +191 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/init-detect.d.ts +20 -0
- package/dist/commands/init-detect.js +124 -0
- package/dist/commands/init-detect.js.map +1 -0
- package/dist/commands/init-write-agents-md.d.ts +14 -0
- package/dist/commands/init-write-agents-md.js +71 -0
- package/dist/commands/init-write-agents-md.js.map +1 -0
- package/dist/commands/init-write-lyse-md.d.ts +14 -0
- package/dist/commands/init-write-lyse-md.js +253 -0
- package/dist/commands/init-write-lyse-md.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +147 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp-entry.d.ts +33 -0
- package/dist/commands/mcp-entry.js +47 -0
- package/dist/commands/mcp-entry.js.map +1 -0
- package/dist/commands/mcp-setup.d.ts +10 -0
- package/dist/commands/mcp-setup.js +74 -0
- package/dist/commands/mcp-setup.js.map +1 -0
- package/dist/commands/share.d.ts +4 -0
- package/dist/commands/share.js +60 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/telemetry.d.ts +4 -0
- package/dist/commands/telemetry.js +27 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/templates/lyse-workflow.yml.template +30 -0
- package/dist/config/schema.d.ts +158 -0
- package/dist/config/schema.js +136 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/credentials/keychain.d.ts +20 -0
- package/dist/credentials/keychain.js +35 -0
- package/dist/credentials/keychain.js.map +1 -0
- package/dist/credentials/keychain.test.d.ts +1 -0
- package/dist/credentials/keychain.test.js +32 -0
- package/dist/credentials/keychain.test.js.map +1 -0
- package/dist/credentials/paths.d.ts +1 -0
- package/dist/credentials/paths.js +6 -0
- package/dist/credentials/paths.js.map +1 -0
- package/dist/credentials/store.d.ts +17 -0
- package/dist/credentials/store.js +60 -0
- package/dist/credentials/store.js.map +1 -0
- package/dist/credentials/store.test.d.ts +1 -0
- package/dist/credentials/store.test.js +48 -0
- package/dist/credentials/store.test.js.map +1 -0
- package/dist/detection/from-filesystem.d.ts +2 -0
- package/dist/detection/from-filesystem.js +26 -0
- package/dist/detection/from-filesystem.js.map +1 -0
- package/dist/detection/from-git.d.ts +2 -0
- package/dist/detection/from-git.js +53 -0
- package/dist/detection/from-git.js.map +1 -0
- package/dist/detection/from-package-json.d.ts +2 -0
- package/dist/detection/from-package-json.js +194 -0
- package/dist/detection/from-package-json.js.map +1 -0
- package/dist/detection/pre-flight.d.ts +5 -0
- package/dist/detection/pre-flight.js +47 -0
- package/dist/detection/pre-flight.js.map +1 -0
- package/dist/detection/types.d.ts +27 -0
- package/dist/detection/types.js +2 -0
- package/dist/detection/types.js.map +1 -0
- package/dist/entitlement/check.d.ts +7 -0
- package/dist/entitlement/check.js +37 -0
- package/dist/entitlement/check.js.map +1 -0
- package/dist/entitlement/index.d.ts +1 -0
- package/dist/entitlement/index.js +2 -0
- package/dist/entitlement/index.js.map +1 -0
- package/dist/entitlement/keys.d.ts +10 -0
- package/dist/entitlement/keys.js +14 -0
- package/dist/entitlement/keys.js.map +1 -0
- package/dist/history/ndjson-store.d.ts +70 -0
- package/dist/history/ndjson-store.js +102 -0
- package/dist/history/ndjson-store.js.map +1 -0
- package/dist/identity/index.d.ts +1 -0
- package/dist/identity/index.js +2 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/repo-bucket.d.ts +22 -0
- package/dist/identity/repo-bucket.js +78 -0
- package/dist/identity/repo-bucket.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/layer4-stage.d.ts +18 -0
- package/dist/llm/layer4-stage.js +12 -0
- package/dist/llm/layer4-stage.js.map +1 -0
- package/dist/loaders/components.d.ts +26 -0
- package/dist/loaders/components.js +285 -0
- package/dist/loaders/components.js.map +1 -0
- package/dist/loaders/stories.d.ts +2 -0
- package/dist/loaders/stories.js +231 -0
- package/dist/loaders/stories.js.map +1 -0
- package/dist/loaders/token-graph.d.ts +39 -0
- package/dist/loaders/token-graph.js +146 -0
- package/dist/loaders/token-graph.js.map +1 -0
- package/dist/loaders/tokens.d.ts +2 -0
- package/dist/loaders/tokens.js +521 -0
- package/dist/loaders/tokens.js.map +1 -0
- package/dist/mcp/_find-root.d.ts +12 -0
- package/dist/mcp/_find-root.js +28 -0
- package/dist/mcp/_find-root.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +42 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/audit-file.d.ts +25 -0
- package/dist/mcp/tools/audit-file.js +147 -0
- package/dist/mcp/tools/audit-file.js.map +1 -0
- package/dist/mcp/tools/suggest-fix.d.ts +13 -0
- package/dist/mcp/tools/suggest-fix.js +100 -0
- package/dist/mcp/tools/suggest-fix.js.map +1 -0
- package/dist/menu/action-menu.d.ts +6 -0
- package/dist/menu/action-menu.js +15 -0
- package/dist/menu/action-menu.js.map +1 -0
- package/dist/menu/prompts.d.ts +7 -0
- package/dist/menu/prompts.js +30 -0
- package/dist/menu/prompts.js.map +1 -0
- package/dist/menu/repl.d.ts +17 -0
- package/dist/menu/repl.js +77 -0
- package/dist/menu/repl.js.map +1 -0
- package/dist/parsers/css-in-js.d.ts +2 -0
- package/dist/parsers/css-in-js.js +69 -0
- package/dist/parsers/css-in-js.js.map +1 -0
- package/dist/parsers/css.d.ts +2 -0
- package/dist/parsers/css.js +32 -0
- package/dist/parsers/css.js.map +1 -0
- package/dist/parsers/scss-transform.d.ts +25 -0
- package/dist/parsers/scss-transform.js +55 -0
- package/dist/parsers/scss-transform.js.map +1 -0
- package/dist/parsers/tailwind-v4.d.ts +8 -0
- package/dist/parsers/tailwind-v4.js +90 -0
- package/dist/parsers/tailwind-v4.js.map +1 -0
- package/dist/parsers/ts-morph-project.d.ts +16 -0
- package/dist/parsers/ts-morph-project.js +77 -0
- package/dist/parsers/ts-morph-project.js.map +1 -0
- package/dist/parsers/ts.d.ts +2 -0
- package/dist/parsers/ts.js +61 -0
- package/dist/parsers/ts.js.map +1 -0
- package/dist/reliability/__tests__/types.test.d.ts +1 -0
- package/dist/reliability/__tests__/types.test.js +14 -0
- package/dist/reliability/__tests__/types.test.js.map +1 -0
- package/dist/reliability/catalogue/__tests__/promotion.test.d.ts +1 -0
- package/dist/reliability/catalogue/__tests__/promotion.test.js +23 -0
- package/dist/reliability/catalogue/__tests__/promotion.test.js.map +1 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.d.ts +1 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.js +27 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.js.map +1 -0
- package/dist/reliability/catalogue/promotion.d.ts +8 -0
- package/dist/reliability/catalogue/promotion.js +25 -0
- package/dist/reliability/catalogue/promotion.js.map +1 -0
- package/dist/reliability/catalogue/sub-axes.d.ts +3 -0
- package/dist/reliability/catalogue/sub-axes.js +18 -0
- package/dist/reliability/catalogue/sub-axes.js.map +1 -0
- package/dist/reliability/confidence/__tests__/manifest-loader.test.d.ts +1 -0
- package/dist/reliability/confidence/__tests__/manifest-loader.test.js +103 -0
- package/dist/reliability/confidence/__tests__/manifest-loader.test.js.map +1 -0
- package/dist/reliability/confidence/bundled-manifest.d.ts +2 -0
- package/dist/reliability/confidence/bundled-manifest.js +8 -0
- package/dist/reliability/confidence/bundled-manifest.js.map +1 -0
- package/dist/reliability/confidence/index.d.ts +3 -0
- package/dist/reliability/confidence/index.js +3 -0
- package/dist/reliability/confidence/index.js.map +1 -0
- package/dist/reliability/confidence/manifest-loader.d.ts +8 -0
- package/dist/reliability/confidence/manifest-loader.js +53 -0
- package/dist/reliability/confidence/manifest-loader.js.map +1 -0
- package/dist/reliability/feedback/__tests__/interactive.test.d.ts +1 -0
- package/dist/reliability/feedback/__tests__/interactive.test.js +85 -0
- package/dist/reliability/feedback/__tests__/interactive.test.js.map +1 -0
- package/dist/reliability/feedback/__tests__/repo-bucket.test.d.ts +1 -0
- package/dist/reliability/feedback/__tests__/repo-bucket.test.js +18 -0
- package/dist/reliability/feedback/__tests__/repo-bucket.test.js.map +1 -0
- package/dist/reliability/feedback/__tests__/sender.test.d.ts +1 -0
- package/dist/reliability/feedback/__tests__/sender.test.js +101 -0
- package/dist/reliability/feedback/__tests__/sender.test.js.map +1 -0
- package/dist/reliability/feedback/interactive.d.ts +20 -0
- package/dist/reliability/feedback/interactive.js +150 -0
- package/dist/reliability/feedback/interactive.js.map +1 -0
- package/dist/reliability/feedback/repo-bucket.d.ts +2 -0
- package/dist/reliability/feedback/repo-bucket.js +9 -0
- package/dist/reliability/feedback/repo-bucket.js.map +1 -0
- package/dist/reliability/feedback/sender.d.ts +16 -0
- package/dist/reliability/feedback/sender.js +28 -0
- package/dist/reliability/feedback/sender.js.map +1 -0
- package/dist/reliability/index.d.ts +1 -0
- package/dist/reliability/index.js +2 -0
- package/dist/reliability/index.js.map +1 -0
- package/dist/reliability/llm-eval/__tests__/budget.test.d.ts +1 -0
- package/dist/reliability/llm-eval/__tests__/budget.test.js +39 -0
- package/dist/reliability/llm-eval/__tests__/budget.test.js.map +1 -0
- package/dist/reliability/llm-eval/budget.d.ts +14 -0
- package/dist/reliability/llm-eval/budget.js +43 -0
- package/dist/reliability/llm-eval/budget.js.map +1 -0
- package/dist/reliability/score/__tests__/formula-v1.test.d.ts +1 -0
- package/dist/reliability/score/__tests__/formula-v1.test.js +29 -0
- package/dist/reliability/score/__tests__/formula-v1.test.js.map +1 -0
- package/dist/reliability/score/formula-v1.d.ts +13 -0
- package/dist/reliability/score/formula-v1.js +19 -0
- package/dist/reliability/score/formula-v1.js.map +1 -0
- package/dist/reliability/score/version-pin.d.ts +1 -0
- package/dist/reliability/score/version-pin.js +2 -0
- package/dist/reliability/score/version-pin.js.map +1 -0
- package/dist/reliability/score/weight.d.ts +1 -0
- package/dist/reliability/score/weight.js +5 -0
- package/dist/reliability/score/weight.js.map +1 -0
- package/dist/reliability/types.d.ts +39 -0
- package/dist/reliability/types.js +2 -0
- package/dist/reliability/types.js.map +1 -0
- package/dist/reporters/coverage-footer.d.ts +2 -0
- package/dist/reporters/coverage-footer.js +7 -0
- package/dist/reporters/coverage-footer.js.map +1 -0
- package/dist/reporters/json.d.ts +5 -0
- package/dist/reporters/json.js +51 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/markdown.d.ts +7 -0
- package/dist/reporters/markdown.js +35 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/reporters/sarif.d.ts +5 -0
- package/dist/reporters/sarif.js +109 -0
- package/dist/reporters/sarif.js.map +1 -0
- package/dist/reporters/terminal-format.d.ts +34 -0
- package/dist/reporters/terminal-format.js +97 -0
- package/dist/reporters/terminal-format.js.map +1 -0
- package/dist/reporters/terminal.d.ts +4 -0
- package/dist/reporters/terminal.js +201 -0
- package/dist/reporters/terminal.js.map +1 -0
- package/dist/rule-runner.d.ts +8 -0
- package/dist/rule-runner.js +22 -0
- package/dist/rule-runner.js.map +1 -0
- package/dist/rules/_codemod-adapter.d.ts +16 -0
- package/dist/rules/_codemod-adapter.js +49 -0
- package/dist/rules/_codemod-adapter.js.map +1 -0
- package/dist/rules/_exclude.d.ts +11 -0
- package/dist/rules/_exclude.js +17 -0
- package/dist/rules/_exclude.js.map +1 -0
- package/dist/rules/_function-body-analysis.d.ts +82 -0
- package/dist/rules/_function-body-analysis.js +379 -0
- package/dist/rules/_function-body-analysis.js.map +1 -0
- package/dist/rules/_rule-module.d.ts +31 -0
- package/dist/rules/_rule-module.js +32 -0
- package/dist/rules/_rule-module.js.map +1 -0
- package/dist/rules/_skip-context.d.ts +36 -0
- package/dist/rules/_skip-context.js +128 -0
- package/dist/rules/_skip-context.js.map +1 -0
- package/dist/rules/a11y-essentials.d.ts +1 -0
- package/dist/rules/a11y-essentials.js +140 -0
- package/dist/rules/a11y-essentials.js.map +1 -0
- package/dist/rules/ai-surface-agents-md-quality.d.ts +22 -0
- package/dist/rules/ai-surface-agents-md-quality.js +233 -0
- package/dist/rules/ai-surface-agents-md-quality.js.map +1 -0
- package/dist/rules/ai-surface-component-manifest-json.d.ts +19 -0
- package/dist/rules/ai-surface-component-manifest-json.js +232 -0
- package/dist/rules/ai-surface-component-manifest-json.js.map +1 -0
- package/dist/rules/ai-surface-ds-index-exported.d.ts +19 -0
- package/dist/rules/ai-surface-ds-index-exported.js +239 -0
- package/dist/rules/ai-surface-ds-index-exported.js.map +1 -0
- package/dist/rules/components-shadow-native.d.ts +2 -0
- package/dist/rules/components-shadow-native.js +118 -0
- package/dist/rules/components-shadow-native.js.map +1 -0
- package/dist/rules/manifest.d.ts +5 -0
- package/dist/rules/manifest.js +20 -0
- package/dist/rules/manifest.js.map +1 -0
- package/dist/rules/naming-component-pascalcase.d.ts +7 -0
- package/dist/rules/naming-component-pascalcase.js +197 -0
- package/dist/rules/naming-component-pascalcase.js.map +1 -0
- package/dist/rules/naming-hook-prefix.d.ts +6 -0
- package/dist/rules/naming-hook-prefix.js +185 -0
- package/dist/rules/naming-hook-prefix.js.map +1 -0
- package/dist/rules/pack-loader.d.ts +30 -0
- package/dist/rules/pack-loader.js +60 -0
- package/dist/rules/pack-loader.js.map +1 -0
- package/dist/rules/pack-validator.d.ts +8 -0
- package/dist/rules/pack-validator.js +97 -0
- package/dist/rules/pack-validator.js.map +1 -0
- package/dist/rules/registry.d.ts +3 -0
- package/dist/rules/registry.js +28 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/rules/storybook-coverage.d.ts +1 -0
- package/dist/rules/storybook-coverage.js +49 -0
- package/dist/rules/storybook-coverage.js.map +1 -0
- package/dist/rules/templates/_regex-utils.d.ts +5 -0
- package/dist/rules/templates/_regex-utils.js +8 -0
- package/dist/rules/templates/_regex-utils.js.map +1 -0
- package/dist/rules/templates/a11y-jsx-template.d.ts +15 -0
- package/dist/rules/templates/a11y-jsx-template.js +69 -0
- package/dist/rules/templates/a11y-jsx-template.js.map +1 -0
- package/dist/rules/templates/css-property-token-compliance.d.ts +18 -0
- package/dist/rules/templates/css-property-token-compliance.js +56 -0
- package/dist/rules/templates/css-property-token-compliance.js.map +1 -0
- package/dist/rules/templates/import-source-restriction.d.ts +18 -0
- package/dist/rules/templates/import-source-restriction.js +60 -0
- package/dist/rules/templates/import-source-restriction.js.map +1 -0
- package/dist/rules/templates/js-call-token-compliance.d.ts +18 -0
- package/dist/rules/templates/js-call-token-compliance.js +61 -0
- package/dist/rules/templates/js-call-token-compliance.js.map +1 -0
- package/dist/rules/templates/js-prop-token-compliance.d.ts +21 -0
- package/dist/rules/templates/js-prop-token-compliance.js +56 -0
- package/dist/rules/templates/js-prop-token-compliance.js.map +1 -0
- package/dist/rules/templates/naming-convention.d.ts +18 -0
- package/dist/rules/templates/naming-convention.js +76 -0
- package/dist/rules/templates/naming-convention.js.map +1 -0
- package/dist/rules/templates/registry.d.ts +5 -0
- package/dist/rules/templates/registry.js +30 -0
- package/dist/rules/templates/registry.js.map +1 -0
- package/dist/rules/templates/storybook-coverage-template.d.ts +15 -0
- package/dist/rules/templates/storybook-coverage-template.js +58 -0
- package/dist/rules/templates/storybook-coverage-template.js.map +1 -0
- package/dist/rules/templates/tailwind-utility-class-compliance.d.ts +15 -0
- package/dist/rules/templates/tailwind-utility-class-compliance.js +70 -0
- package/dist/rules/templates/tailwind-utility-class-compliance.js.map +1 -0
- package/dist/rules/templates/types.d.ts +16 -0
- package/dist/rules/templates/types.js +2 -0
- package/dist/rules/templates/types.js.map +1 -0
- package/dist/rules/tokens-description-coverage.d.ts +15 -0
- package/dist/rules/tokens-description-coverage.js +193 -0
- package/dist/rules/tokens-description-coverage.js.map +1 -0
- package/dist/rules/tokens-dtcg-conformance.d.ts +47 -0
- package/dist/rules/tokens-dtcg-conformance.js +363 -0
- package/dist/rules/tokens-dtcg-conformance.js.map +1 -0
- package/dist/rules/tokens-no-hardcoded-color.d.ts +18 -0
- package/dist/rules/tokens-no-hardcoded-color.js +436 -0
- package/dist/rules/tokens-no-hardcoded-color.js.map +1 -0
- package/dist/rules/tokens-no-hardcoded-spacing.d.ts +8 -0
- package/dist/rules/tokens-no-hardcoded-spacing.js +193 -0
- package/dist/rules/tokens-no-hardcoded-spacing.js.map +1 -0
- package/dist/scorer.d.ts +38 -0
- package/dist/scorer.js +126 -0
- package/dist/scorer.js.map +1 -0
- package/dist/share/clipboard.d.ts +26 -0
- package/dist/share/clipboard.js +76 -0
- package/dist/share/clipboard.js.map +1 -0
- package/dist/suppression/__tests__/inline.test.d.ts +1 -0
- package/dist/suppression/__tests__/inline.test.js +25 -0
- package/dist/suppression/__tests__/inline.test.js.map +1 -0
- package/dist/suppression/__tests__/lyseignore.test.d.ts +1 -0
- package/dist/suppression/__tests__/lyseignore.test.js +32 -0
- package/dist/suppression/__tests__/lyseignore.test.js.map +1 -0
- package/dist/suppression/defaults.d.ts +1 -0
- package/dist/suppression/defaults.js +14 -0
- package/dist/suppression/defaults.js.map +1 -0
- package/dist/suppression/inline.d.ts +7 -0
- package/dist/suppression/inline.js +32 -0
- package/dist/suppression/inline.js.map +1 -0
- package/dist/suppression/lyseignore.d.ts +2 -0
- package/dist/suppression/lyseignore.js +22 -0
- package/dist/suppression/lyseignore.js.map +1 -0
- package/dist/telemetry/consent.d.ts +48 -0
- package/dist/telemetry/consent.js +139 -0
- package/dist/telemetry/consent.js.map +1 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +3 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/local-log.d.ts +64 -0
- package/dist/telemetry/local-log.js +123 -0
- package/dist/telemetry/local-log.js.map +1 -0
- package/dist/tokens/dtcg-model.d.ts +53 -0
- package/dist/tokens/dtcg-model.js +18 -0
- package/dist/tokens/dtcg-model.js.map +1 -0
- package/dist/tokens/normalizer.d.ts +17 -0
- package/dist/tokens/normalizer.js +360 -0
- package/dist/tokens/normalizer.js.map +1 -0
- package/dist/types.d.ts +300 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/util/git.d.ts +6 -0
- package/dist/util/git.js +40 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/git.test.d.ts +5 -0
- package/dist/util/git.test.js +42 -0
- package/dist/util/git.test.js.map +1 -0
- package/dist/util/gitignore.d.ts +9 -0
- package/dist/util/gitignore.js +39 -0
- package/dist/util/gitignore.js.map +1 -0
- package/dist/util/hash-deps.d.ts +6 -0
- package/dist/util/hash-deps.js +24 -0
- package/dist/util/hash-deps.js.map +1 -0
- package/dist/util/spinner.d.ts +41 -0
- package/dist/util/spinner.js +126 -0
- package/dist/util/spinner.js.map +1 -0
- package/dist/util/with-spinner.d.ts +11 -0
- package/dist/util/with-spinner.js +19 -0
- package/dist/util/with-spinner.js.map +1 -0
- package/dist/walker.d.ts +14 -0
- package/dist/walker.js +91 -0
- package/dist/walker.js.map +1 -0
- package/package.json +83 -0
- package/rules-manifest.json +289 -0
- package/schemas/v1/lyse-config.json +82 -0
- package/schemas/v1/lyse-event.json +68 -0
- package/schemas/v1/lyse-license.json +19 -0
- package/schemas/v1/lyse-llm-payload.json +53 -0
- package/schemas/v1/lyse-llm-response.json +45 -0
- package/schemas/v1/lyse-result.json +98 -0
- package/schemas/v1/lyse-rules.json +42 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Linter } from "eslint";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
4
|
+
// eslint-plugin-jsx-a11y ships no TypeScript declarations and is a CJS package.
|
|
5
|
+
// We load it via createRequire (safe ESM→CJS bridge) to avoid import assertion
|
|
6
|
+
// syntax that esbuild / vitest's transform pipeline rejects.
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- eslint-plugin-jsx-a11y ships no type declarations
|
|
9
|
+
const jsxA11yPlugin = require("eslint-plugin-jsx-a11y");
|
|
10
|
+
// @typescript-eslint/parser unlocks TSX-with-TS-syntax linting (interfaces,
|
|
11
|
+
// generics, `as` casts). Without it, espree fails on the majority of real
|
|
12
|
+
// React+TS files and the a11y axis emits zero findings. See #167.
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- @typescript-eslint/parser exports a Parser interface; cast for Linter.Config compatibility
|
|
14
|
+
const tsParser = require("@typescript-eslint/parser");
|
|
15
|
+
const linter = new Linter({ configType: "flat" });
|
|
16
|
+
const A11Y_RULES = [
|
|
17
|
+
"jsx-a11y/alt-text",
|
|
18
|
+
"jsx-a11y/anchor-has-content",
|
|
19
|
+
"jsx-a11y/label-has-associated-control",
|
|
20
|
+
"jsx-a11y/role-has-required-aria-props",
|
|
21
|
+
"jsx-a11y/aria-role",
|
|
22
|
+
];
|
|
23
|
+
// Flat config: only register the 5 rules we care about, all as "warn".
|
|
24
|
+
// Cast to `never` because eslint-plugin-jsx-a11y lacks TS declarations and
|
|
25
|
+
// the inferred `any` shape doesn't match Linter.Config[] exactly.
|
|
26
|
+
// Two-entry flat config: @typescript-eslint/parser for .ts/.tsx (handles TS
|
|
27
|
+
// syntax + JSX), espree default for .js/.jsx (cheaper, sufficient when no TS
|
|
28
|
+
// features are present). Closes #167. The TS parser also handles plain JSX
|
|
29
|
+
// cleanly, so this also acts as a safety net if file extensions lie.
|
|
30
|
+
const flatConfig = [
|
|
31
|
+
{
|
|
32
|
+
files: ["**/*.{ts,tsx}"],
|
|
33
|
+
languageOptions: {
|
|
34
|
+
parser: tsParser,
|
|
35
|
+
parserOptions: {
|
|
36
|
+
ecmaVersion: 2022,
|
|
37
|
+
sourceType: "module",
|
|
38
|
+
ecmaFeatures: { jsx: true },
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
plugins: { "jsx-a11y": jsxA11yPlugin },
|
|
42
|
+
rules: Object.fromEntries(A11Y_RULES.map((r) => [r, "warn"])),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
files: ["**/*.{js,jsx}"],
|
|
46
|
+
languageOptions: {
|
|
47
|
+
parserOptions: {
|
|
48
|
+
ecmaVersion: 2022,
|
|
49
|
+
sourceType: "module",
|
|
50
|
+
ecmaFeatures: { jsx: true },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
plugins: { "jsx-a11y": jsxA11yPlugin },
|
|
54
|
+
rules: Object.fromEntries(A11Y_RULES.map((r) => [r, "warn"])),
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
const evaluate = async (_ctx, files) => {
|
|
58
|
+
const findings = [];
|
|
59
|
+
const parseErrors = [];
|
|
60
|
+
let opportunities = 0;
|
|
61
|
+
for (const f of files.ts) {
|
|
62
|
+
// If the upstream SWC parser already failed (`f.ast === null`), the pipeline
|
|
63
|
+
// surfaced this on stderr via `parseErrorCount`. Don't re-report it here as
|
|
64
|
+
// `coverage.parseErrors` (avoid I1 double-counting), but still attempt ESLint
|
|
65
|
+
// analysis — espree handles plain JSX even when SWC's `tsx: false` mode
|
|
66
|
+
// rejects it (e.g. `.jsx` files).
|
|
67
|
+
const swcFailed = f.ast === null;
|
|
68
|
+
// ESLint flat API surfaces parse failures as a single `fatal: true` message.
|
|
69
|
+
// `.tsx` files route through `@typescript-eslint/parser` (#167), `.jsx` keeps
|
|
70
|
+
// espree. Files that still fail to parse (real syntax errors, unsupported
|
|
71
|
+
// features) are excluded from `opportunities` and reported via
|
|
72
|
+
// `meta.coverage.parseErrors` (#155) — otherwise the score would be 100/100
|
|
73
|
+
// on N/0 analyzed, a credibility hole.
|
|
74
|
+
let messages;
|
|
75
|
+
try {
|
|
76
|
+
messages = linter.verify(f.source, flatConfig, { filename: f.path });
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
if (!swcFailed) {
|
|
80
|
+
parseErrors.push({
|
|
81
|
+
file: f.path,
|
|
82
|
+
reason: e instanceof Error ? e.message : "parser threw non-Error value",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const fatal = messages.find((m) => m.fatal === true);
|
|
88
|
+
if (fatal) {
|
|
89
|
+
if (!swcFailed) {
|
|
90
|
+
parseErrors.push({
|
|
91
|
+
file: f.path,
|
|
92
|
+
reason: fatal.message,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// Opportunity heuristic: count JSX elements that COULD have a11y issues.
|
|
98
|
+
// Counted only AFTER successful parse so the score's denominator reflects
|
|
99
|
+
// what was actually analyzed.
|
|
100
|
+
opportunities += (f.source.match(/<(img|a|label|button|input|select|textarea)\b/g) ?? []).length;
|
|
101
|
+
for (const m of messages) {
|
|
102
|
+
if (!m.ruleId || !A11Y_RULES.includes(m.ruleId))
|
|
103
|
+
continue;
|
|
104
|
+
findings.push({
|
|
105
|
+
ruleId: "a11y/essentials",
|
|
106
|
+
axis: "a11y",
|
|
107
|
+
severity: m.severity === 2 ? "error" : "warning",
|
|
108
|
+
location: { file: f.path, line: m.line ?? 0, column: m.column ?? 0 },
|
|
109
|
+
message: `[${m.ruleId}] ${m.message}`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const result = { findings, opportunities };
|
|
114
|
+
if (parseErrors.length > 0)
|
|
115
|
+
result.parseErrors = parseErrors;
|
|
116
|
+
return result;
|
|
117
|
+
};
|
|
118
|
+
export const rule = createLyseRule({
|
|
119
|
+
meta: {
|
|
120
|
+
axis: "a11y",
|
|
121
|
+
lyseRuleId: "a11y/essentials",
|
|
122
|
+
defaultSeverity: "warning",
|
|
123
|
+
shortDescription: "Essential accessibility checks (jsx-a11y subset)",
|
|
124
|
+
fullDescription: "Wraps the canonical `eslint-plugin-jsx-a11y` rules: `alt-text`, `anchor-has-content`, `label-has-associated-control`, `role-has-required-aria-props`, `aria-role`. Surface-level accessibility failures that AI agents most frequently introduce.",
|
|
125
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/a11y-essentials.md",
|
|
126
|
+
rationale: `Why it matters
|
|
127
|
+
|
|
128
|
+
Missing alt text, empty anchors, unlabeled inputs, and invalid ARIA are the most common a11y regressions in AI-generated UI. They block screen-reader users and violate WCAG 2.1 SC 1.1.1, 2.4.4, 1.3.1, 4.1.2.
|
|
129
|
+
|
|
130
|
+
Lyse depends on the canonical \`eslint-plugin-jsx-a11y\` rather than re-porting these rules — the upstream impl is battle-tested across millions of repos.`,
|
|
131
|
+
examples: [
|
|
132
|
+
{ good: '<img src="/logo.png" alt="Lyse logo" />', bad: '<img src="/logo.png" />' },
|
|
133
|
+
{ good: "<label htmlFor=\"email\">Email</label><input id=\"email\" />", bad: "<label>Email</label><input />" },
|
|
134
|
+
],
|
|
135
|
+
allowlist: ['jsx-a11y allowlists per-rule (e.g., decorative `alt=""` for purely-presentational images)'],
|
|
136
|
+
},
|
|
137
|
+
defaultOptions: [],
|
|
138
|
+
create: () => ({ evaluate }),
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=a11y-essentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-essentials.js","sourceRoot":"","sources":["../../src/rules/a11y-essentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
interface FencedBlock {
|
|
3
|
+
firstLine: string;
|
|
4
|
+
startLine: number;
|
|
5
|
+
}
|
|
6
|
+
declare function extractFencedBlocks(content: string): FencedBlock[];
|
|
7
|
+
declare function startsWithRunnable(commandLine: string): boolean;
|
|
8
|
+
interface QualityChecks {
|
|
9
|
+
hasRunnableCodeBlock: boolean;
|
|
10
|
+
referencesExitCodes: boolean;
|
|
11
|
+
referencesToolchainConfig: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare function evaluateQuality(content: string, presentConfigs: Set<string>): QualityChecks;
|
|
14
|
+
export declare const rule: Rule;
|
|
15
|
+
export declare const _internal: {
|
|
16
|
+
extractFencedBlocks: typeof extractFencedBlocks;
|
|
17
|
+
startsWithRunnable: typeof startsWithRunnable;
|
|
18
|
+
evaluateQuality: typeof evaluateQuality;
|
|
19
|
+
EXIT_CODE_PATTERN: RegExp;
|
|
20
|
+
RUNNABLE_PREFIXES: string[];
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
4
|
+
const RULE_ID = "ai-surface/agents-md-quality";
|
|
5
|
+
const MAX_FILE_BYTES = 500_000;
|
|
6
|
+
const CANDIDATE_PATHS = [
|
|
7
|
+
"AGENTS.md",
|
|
8
|
+
".github/AGENTS.md",
|
|
9
|
+
"docs/AGENTS.md",
|
|
10
|
+
];
|
|
11
|
+
const RUNNABLE_PREFIXES = [
|
|
12
|
+
"pnpm",
|
|
13
|
+
"npm",
|
|
14
|
+
"yarn",
|
|
15
|
+
"bun",
|
|
16
|
+
"python",
|
|
17
|
+
"python3",
|
|
18
|
+
"cargo",
|
|
19
|
+
"make",
|
|
20
|
+
"bash",
|
|
21
|
+
"sh",
|
|
22
|
+
"node",
|
|
23
|
+
"tsx",
|
|
24
|
+
"deno",
|
|
25
|
+
"npx",
|
|
26
|
+
"go",
|
|
27
|
+
"rake",
|
|
28
|
+
"uv",
|
|
29
|
+
"poetry",
|
|
30
|
+
"ruff",
|
|
31
|
+
"ruby",
|
|
32
|
+
];
|
|
33
|
+
const TOOLCHAIN_CONFIG_FILES = [
|
|
34
|
+
"package.json",
|
|
35
|
+
"tsconfig.json",
|
|
36
|
+
"pyproject.toml",
|
|
37
|
+
"Cargo.toml",
|
|
38
|
+
"Makefile",
|
|
39
|
+
".lyse.yaml",
|
|
40
|
+
".lyse.yml",
|
|
41
|
+
"lyse.config.ts",
|
|
42
|
+
"lyse.config.js",
|
|
43
|
+
"go.mod",
|
|
44
|
+
"Gemfile",
|
|
45
|
+
"pom.xml",
|
|
46
|
+
"build.gradle",
|
|
47
|
+
"deno.json",
|
|
48
|
+
"deno.jsonc",
|
|
49
|
+
"biome.json",
|
|
50
|
+
"eslint.config.js",
|
|
51
|
+
"vitest.config.ts",
|
|
52
|
+
];
|
|
53
|
+
const EXIT_CODE_PATTERN = /exit\s+code|exit code\s+\d+|exits?\s+(with\s+)?\d+|status\s+code|return code/i;
|
|
54
|
+
function readMarkdownIfSmall(absPath) {
|
|
55
|
+
try {
|
|
56
|
+
const stat = statSync(absPath);
|
|
57
|
+
if (!stat.isFile())
|
|
58
|
+
return null;
|
|
59
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
60
|
+
return null;
|
|
61
|
+
return readFileSync(absPath, "utf8");
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function extractFencedBlocks(content) {
|
|
68
|
+
const blocks = [];
|
|
69
|
+
const lines = content.split("\n");
|
|
70
|
+
let inFence = false;
|
|
71
|
+
let fenceMarker = "";
|
|
72
|
+
let firstLine = "";
|
|
73
|
+
let startLine = 0;
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const line = lines[i] ?? "";
|
|
76
|
+
const trimmed = line.trimStart();
|
|
77
|
+
if (!inFence) {
|
|
78
|
+
const m = trimmed.match(/^(`{3,}|~{3,})/);
|
|
79
|
+
if (m) {
|
|
80
|
+
inFence = true;
|
|
81
|
+
fenceMarker = m[1] ?? "";
|
|
82
|
+
firstLine = "";
|
|
83
|
+
startLine = i + 1;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (trimmed.startsWith(fenceMarker)) {
|
|
88
|
+
if (firstLine !== "")
|
|
89
|
+
blocks.push({ firstLine, startLine });
|
|
90
|
+
inFence = false;
|
|
91
|
+
fenceMarker = "";
|
|
92
|
+
firstLine = "";
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (firstLine === "" && line.trim().length > 0) {
|
|
96
|
+
firstLine = line.trim();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return blocks;
|
|
101
|
+
}
|
|
102
|
+
function startsWithRunnable(commandLine) {
|
|
103
|
+
const cleaned = commandLine.replace(/^[$#>]\s*/, "").trim();
|
|
104
|
+
if (cleaned.length === 0)
|
|
105
|
+
return false;
|
|
106
|
+
const firstToken = cleaned.split(/\s+/, 1)[0] ?? "";
|
|
107
|
+
if (RUNNABLE_PREFIXES.includes(firstToken))
|
|
108
|
+
return true;
|
|
109
|
+
// Allow path-prefixed binaries like ./scripts/run.sh
|
|
110
|
+
if (/^\.\/[\w./-]+$/.test(firstToken))
|
|
111
|
+
return true;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
function evaluateQuality(content, presentConfigs) {
|
|
115
|
+
const blocks = extractFencedBlocks(content);
|
|
116
|
+
const hasRunnableCodeBlock = blocks.some((b) => startsWithRunnable(b.firstLine));
|
|
117
|
+
const referencesExitCodes = EXIT_CODE_PATTERN.test(content);
|
|
118
|
+
let referencesToolchainConfig = false;
|
|
119
|
+
for (const cfg of presentConfigs) {
|
|
120
|
+
if (content.includes(cfg)) {
|
|
121
|
+
referencesToolchainConfig = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { hasRunnableCodeBlock, referencesExitCodes, referencesToolchainConfig };
|
|
126
|
+
}
|
|
127
|
+
function discoverPresentConfigs(repoRoot) {
|
|
128
|
+
const found = new Set();
|
|
129
|
+
for (const cfg of TOOLCHAIN_CONFIG_FILES) {
|
|
130
|
+
if (existsSync(join(repoRoot, cfg)))
|
|
131
|
+
found.add(cfg);
|
|
132
|
+
}
|
|
133
|
+
return found;
|
|
134
|
+
}
|
|
135
|
+
const evaluate = async (ctx, _files) => {
|
|
136
|
+
const findings = [];
|
|
137
|
+
if (!ctx.repoRoot) {
|
|
138
|
+
return { findings, opportunities: 0 };
|
|
139
|
+
}
|
|
140
|
+
const presentConfigs = discoverPresentConfigs(ctx.repoRoot);
|
|
141
|
+
const foundFiles = [];
|
|
142
|
+
for (const candidate of CANDIDATE_PATHS) {
|
|
143
|
+
const abs = join(ctx.repoRoot, candidate);
|
|
144
|
+
const content = readMarkdownIfSmall(abs);
|
|
145
|
+
if (content !== null) {
|
|
146
|
+
foundFiles.push({ rel: relative(ctx.repoRoot, abs) || candidate, content });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (foundFiles.length === 0) {
|
|
150
|
+
findings.push({
|
|
151
|
+
ruleId: RULE_ID,
|
|
152
|
+
axis: "ai-surface",
|
|
153
|
+
severity: "info",
|
|
154
|
+
location: { file: "AGENTS.md", line: 1, column: 1 },
|
|
155
|
+
message: "No AGENTS.md found — coding agents have no command-first instructions",
|
|
156
|
+
suggestion: "create AGENTS.md at repo root with command-first sections (runnable shell commands + expected exit codes)",
|
|
157
|
+
});
|
|
158
|
+
return { findings, opportunities: 1 };
|
|
159
|
+
}
|
|
160
|
+
let opportunities = 0;
|
|
161
|
+
for (const { rel, content } of foundFiles) {
|
|
162
|
+
opportunities += 3;
|
|
163
|
+
const quality = evaluateQuality(content, presentConfigs);
|
|
164
|
+
if (!quality.hasRunnableCodeBlock) {
|
|
165
|
+
findings.push({
|
|
166
|
+
ruleId: RULE_ID,
|
|
167
|
+
axis: "ai-surface",
|
|
168
|
+
severity: "warning",
|
|
169
|
+
location: { file: rel, line: 1, column: 1 },
|
|
170
|
+
message: "AGENTS.md has no fenced code block starting with a runnable shell command (pnpm/npm/python/...)",
|
|
171
|
+
suggestion: "add a section with a fenced code block whose first line is a real command an agent can run",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (!quality.referencesExitCodes) {
|
|
175
|
+
findings.push({
|
|
176
|
+
ruleId: RULE_ID,
|
|
177
|
+
axis: "ai-surface",
|
|
178
|
+
severity: "warning",
|
|
179
|
+
location: { file: rel, line: 1, column: 1 },
|
|
180
|
+
message: "AGENTS.md does not reference exit codes — agents cannot verify command success programmatically",
|
|
181
|
+
suggestion: "document expected exit codes (e.g., `exit code 0 = clean`) for the commands you list",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
if (!quality.referencesToolchainConfig) {
|
|
185
|
+
findings.push({
|
|
186
|
+
ruleId: RULE_ID,
|
|
187
|
+
axis: "ai-surface",
|
|
188
|
+
severity: "warning",
|
|
189
|
+
location: { file: rel, line: 1, column: 1 },
|
|
190
|
+
message: "AGENTS.md does not mention any toolchain config file present in the repo (package.json, tsconfig.json, ...)",
|
|
191
|
+
suggestion: "reference the project's actual config files so agents know which toolchain is in scope",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { findings, opportunities };
|
|
196
|
+
};
|
|
197
|
+
export const rule = createLyseRule({
|
|
198
|
+
meta: {
|
|
199
|
+
axis: "ai-surface",
|
|
200
|
+
lyseRuleId: RULE_ID,
|
|
201
|
+
defaultSeverity: "warning",
|
|
202
|
+
shortDescription: "AGENTS.md should be command-first and machine-actionable",
|
|
203
|
+
fullDescription: "Reads `AGENTS.md` at the repo root (with `.github/AGENTS.md` and `docs/AGENTS.md` fallbacks) and verifies three quality signals: (1) at least one fenced code block whose first line is a runnable shell command (pnpm/npm/yarn/bun/python/cargo/make/bash/sh/node/tsx/deno/...), (2) at least one mention of exit codes / status codes / return codes, (3) at least one reference to a toolchain config file the repo actually has (package.json, tsconfig.json, pyproject.toml, Makefile, .lyse.yaml, etc.). Each failing signal emits one warning; absence of AGENTS.md emits one info finding.",
|
|
204
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-surface-agents-md-quality.md",
|
|
205
|
+
rationale: `Why it matters
|
|
206
|
+
|
|
207
|
+
Coding agents (Claude Code, Cursor, Copilot Workspace, etc.) consume AGENTS.md as their bootstrap context. Research from the kodustech/agent-readiness study (Stream 2) shows that command-first AGENTS.md sections — containing runnable commands and explicit exit-code semantics — shift agent task success by 35–55%.
|
|
208
|
+
|
|
209
|
+
Mere presence is not enough. A prose-only AGENTS.md that explains the project in English without listing a single \`pnpm test\` block leaves the agent guessing at the toolchain. Worse, per Gloaguen et al. (2026), long unstructured context files *reduce* task success and *increase* token cost by 20%.
|
|
210
|
+
|
|
211
|
+
The rule enforces the cheapest, highest-leverage discipline: at least one runnable command, at least one explicit exit-code expectation, and at least one reference to the toolchain config the agent will encounter.`,
|
|
212
|
+
examples: [
|
|
213
|
+
{
|
|
214
|
+
good: "## Build\\n\\n\\`\\`\\`bash\\npnpm install && pnpm test\\n\\`\\`\\`\\n\\nExit code 0 means clean. Uses package.json.",
|
|
215
|
+
bad: "# Welcome\\n\\nThis repo is a TypeScript project. Please read the README before contributing.",
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
allowlist: [
|
|
219
|
+
"files larger than 500 KB — skipped to avoid pathological cases",
|
|
220
|
+
"repos with no AGENTS.md anywhere — emit a single info finding, not a warning",
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
defaultOptions: [],
|
|
224
|
+
create: () => ({ evaluate }),
|
|
225
|
+
});
|
|
226
|
+
export const _internal = {
|
|
227
|
+
extractFencedBlocks,
|
|
228
|
+
startsWithRunnable,
|
|
229
|
+
evaluateQuality,
|
|
230
|
+
EXIT_CODE_PATTERN,
|
|
231
|
+
RUNNABLE_PREFIXES,
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=ai-surface-agents-md-quality.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-surface-agents-md-quality.js","sourceRoot":"","sources":["../../src/rules/ai-surface-agents-md-quality.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
interface ValidationOutcome {
|
|
3
|
+
warnings: string[];
|
|
4
|
+
hasValidComponents: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare function isShadcnComponentsJson(data: unknown): boolean;
|
|
7
|
+
declare function validateEntry(entry: unknown): {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
reason?: string;
|
|
10
|
+
};
|
|
11
|
+
declare function validateManifest(data: unknown): ValidationOutcome;
|
|
12
|
+
export declare const rule: Rule;
|
|
13
|
+
export declare const _internal: {
|
|
14
|
+
validateManifest: typeof validateManifest;
|
|
15
|
+
validateEntry: typeof validateEntry;
|
|
16
|
+
isShadcnComponentsJson: typeof isShadcnComponentsJson;
|
|
17
|
+
CANDIDATE_PATTERNS: string[];
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
import { isPathExcluded } from "./_exclude.js";
|
|
5
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
6
|
+
const RULE_ID = "ai-surface/component-manifest-json";
|
|
7
|
+
const MAX_FILE_BYTES = 2_000_000;
|
|
8
|
+
const CANDIDATE_PATTERNS = [
|
|
9
|
+
"components.json",
|
|
10
|
+
"lyse.components.json",
|
|
11
|
+
"apps/*/components.json",
|
|
12
|
+
"apps/*/lyse.components.json",
|
|
13
|
+
"packages/*/components.json",
|
|
14
|
+
"packages/*/lyse.components.json",
|
|
15
|
+
];
|
|
16
|
+
function discoverManifests(ctx) {
|
|
17
|
+
if (!ctx.repoRoot)
|
|
18
|
+
return [];
|
|
19
|
+
let entries = [];
|
|
20
|
+
try {
|
|
21
|
+
entries = fg.sync(CANDIDATE_PATTERNS, {
|
|
22
|
+
cwd: ctx.repoRoot,
|
|
23
|
+
absolute: false,
|
|
24
|
+
dot: false,
|
|
25
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.git/**"],
|
|
26
|
+
followSymbolicLinks: false,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const out = new Set();
|
|
33
|
+
for (const rel of entries) {
|
|
34
|
+
if (isPathExcluded(rel, ctx.excludePaths))
|
|
35
|
+
continue;
|
|
36
|
+
out.add(rel);
|
|
37
|
+
}
|
|
38
|
+
return Array.from(out).sort();
|
|
39
|
+
}
|
|
40
|
+
function readJsonIfSmall(absPath) {
|
|
41
|
+
try {
|
|
42
|
+
const stat = statSync(absPath);
|
|
43
|
+
if (!stat.isFile())
|
|
44
|
+
return { data: null, parseError: null };
|
|
45
|
+
if (stat.size > MAX_FILE_BYTES)
|
|
46
|
+
return { data: null, parseError: "file too large" };
|
|
47
|
+
const raw = readFileSync(absPath, "utf8");
|
|
48
|
+
if (raw.trim().length === 0)
|
|
49
|
+
return { data: null, parseError: "empty file" };
|
|
50
|
+
return { data: JSON.parse(raw), parseError: null };
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return { data: null, parseError: e instanceof Error ? e.message : "unknown parse error" };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isShadcnComponentsJson(data) {
|
|
57
|
+
if (typeof data !== "object" || data === null)
|
|
58
|
+
return false;
|
|
59
|
+
const obj = data;
|
|
60
|
+
// shadcn/ui components.json has $schema or aliases, not a `components` list
|
|
61
|
+
if (typeof obj.$schema === "string" && obj.$schema.includes("shadcn"))
|
|
62
|
+
return true;
|
|
63
|
+
if (typeof obj.aliases === "object" && obj.aliases !== null && !("components" in obj))
|
|
64
|
+
return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
function validateEntry(entry) {
|
|
68
|
+
if (typeof entry === "string") {
|
|
69
|
+
return entry.trim().length > 0 ? { ok: true } : { ok: false, reason: "empty string entry" };
|
|
70
|
+
}
|
|
71
|
+
if (typeof entry !== "object" || entry === null) {
|
|
72
|
+
return { ok: false, reason: "entry is not an object" };
|
|
73
|
+
}
|
|
74
|
+
const e = entry;
|
|
75
|
+
if (typeof e.name !== "string" || e.name.trim().length === 0) {
|
|
76
|
+
return { ok: false, reason: "entry missing `name`" };
|
|
77
|
+
}
|
|
78
|
+
const hasSource = (typeof e.sourceFile === "string" && e.sourceFile.trim().length > 0) ||
|
|
79
|
+
(typeof e.import === "string" && e.import.trim().length > 0);
|
|
80
|
+
if (!hasSource) {
|
|
81
|
+
return { ok: false, reason: `entry "${e.name}" missing \`sourceFile\` or \`import\`` };
|
|
82
|
+
}
|
|
83
|
+
return { ok: true };
|
|
84
|
+
}
|
|
85
|
+
function validateManifest(data) {
|
|
86
|
+
const warnings = [];
|
|
87
|
+
if (typeof data !== "object" || data === null) {
|
|
88
|
+
return { warnings: ["manifest root is not a JSON object"], hasValidComponents: false };
|
|
89
|
+
}
|
|
90
|
+
const shape = data;
|
|
91
|
+
if (!("components" in shape)) {
|
|
92
|
+
return { warnings: ["manifest has no `components` array or object"], hasValidComponents: false };
|
|
93
|
+
}
|
|
94
|
+
const components = shape.components;
|
|
95
|
+
if (Array.isArray(components)) {
|
|
96
|
+
if (components.length === 0) {
|
|
97
|
+
warnings.push("`components` array is empty");
|
|
98
|
+
return { warnings, hasValidComponents: false };
|
|
99
|
+
}
|
|
100
|
+
for (let i = 0; i < components.length; i++) {
|
|
101
|
+
const result = validateEntry(components[i]);
|
|
102
|
+
if (!result.ok)
|
|
103
|
+
warnings.push(`components[${i}]: ${result.reason ?? "invalid entry"}`);
|
|
104
|
+
}
|
|
105
|
+
return { warnings, hasValidComponents: warnings.length === 0 };
|
|
106
|
+
}
|
|
107
|
+
if (typeof components === "object" && components !== null) {
|
|
108
|
+
const entries = Object.entries(components);
|
|
109
|
+
if (entries.length === 0) {
|
|
110
|
+
warnings.push("`components` object is empty");
|
|
111
|
+
return { warnings, hasValidComponents: false };
|
|
112
|
+
}
|
|
113
|
+
for (const [name, entry] of entries) {
|
|
114
|
+
if (typeof entry === "object" && entry !== null) {
|
|
115
|
+
const e = entry;
|
|
116
|
+
const hasSource = (typeof e.sourceFile === "string" && e.sourceFile.trim().length > 0) ||
|
|
117
|
+
(typeof e.import === "string" && e.import.trim().length > 0);
|
|
118
|
+
if (!hasSource)
|
|
119
|
+
warnings.push(`components.${name} missing \`sourceFile\` or \`import\``);
|
|
120
|
+
}
|
|
121
|
+
else if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
122
|
+
warnings.push(`components.${name} has invalid value`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { warnings, hasValidComponents: warnings.length === 0 };
|
|
126
|
+
}
|
|
127
|
+
return { warnings: ["`components` must be an array or object"], hasValidComponents: false };
|
|
128
|
+
}
|
|
129
|
+
const evaluate = async (ctx, _files) => {
|
|
130
|
+
const findings = [];
|
|
131
|
+
if (!ctx.repoRoot) {
|
|
132
|
+
return { findings, opportunities: 0 };
|
|
133
|
+
}
|
|
134
|
+
const relFiles = discoverManifests(ctx);
|
|
135
|
+
// Filter out shadcn-style components.json (config file, not a manifest).
|
|
136
|
+
const lyseStyle = [];
|
|
137
|
+
let shadcnDetected = false;
|
|
138
|
+
for (const rel of relFiles) {
|
|
139
|
+
const abs = join(ctx.repoRoot, rel);
|
|
140
|
+
const { data } = readJsonIfSmall(abs);
|
|
141
|
+
if (data !== null && isShadcnComponentsJson(data)) {
|
|
142
|
+
shadcnDetected = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
lyseStyle.push(rel);
|
|
146
|
+
}
|
|
147
|
+
if (lyseStyle.length === 0) {
|
|
148
|
+
const suggestion = shadcnDetected
|
|
149
|
+
? "found a shadcn/ui components.json but no lyse-style component manifest; add `lyse.components.json` listing { name, sourceFile } entries for MCP cost reduction"
|
|
150
|
+
: "create a `components.json` or `lyse.components.json` listing your components ({ name, sourceFile }) so MCP servers can serve component lookups at ~5× lower cost than reading source files";
|
|
151
|
+
findings.push({
|
|
152
|
+
ruleId: RULE_ID,
|
|
153
|
+
axis: "ai-surface",
|
|
154
|
+
severity: "info",
|
|
155
|
+
location: { file: "components.json", line: 1, column: 1 },
|
|
156
|
+
message: "No component manifest JSON found — MCP tools fall back to source-file reads",
|
|
157
|
+
suggestion,
|
|
158
|
+
});
|
|
159
|
+
return { findings, opportunities: 1 };
|
|
160
|
+
}
|
|
161
|
+
let opportunities = 0;
|
|
162
|
+
for (const rel of lyseStyle) {
|
|
163
|
+
opportunities += 1;
|
|
164
|
+
const abs = join(ctx.repoRoot, rel);
|
|
165
|
+
const { data, parseError } = readJsonIfSmall(abs);
|
|
166
|
+
const relPath = relative(ctx.repoRoot, abs) || rel;
|
|
167
|
+
if (data === null) {
|
|
168
|
+
findings.push({
|
|
169
|
+
ruleId: RULE_ID,
|
|
170
|
+
axis: "ai-surface",
|
|
171
|
+
severity: "warning",
|
|
172
|
+
location: { file: relPath, line: 1, column: 1 },
|
|
173
|
+
message: `Component manifest is not valid JSON${parseError ? ` (${parseError})` : ""}`,
|
|
174
|
+
suggestion: "ensure the manifest is parseable JSON",
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const outcome = validateManifest(data);
|
|
179
|
+
for (const warn of outcome.warnings) {
|
|
180
|
+
findings.push({
|
|
181
|
+
ruleId: RULE_ID,
|
|
182
|
+
axis: "ai-surface",
|
|
183
|
+
severity: "warning",
|
|
184
|
+
location: { file: relPath, line: 1, column: 1 },
|
|
185
|
+
message: warn,
|
|
186
|
+
suggestion: "each entry should be { name: string, sourceFile: string } (or { name, import } for shadcn-style)",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { findings, opportunities };
|
|
191
|
+
};
|
|
192
|
+
export const rule = createLyseRule({
|
|
193
|
+
meta: {
|
|
194
|
+
axis: "ai-surface",
|
|
195
|
+
lyseRuleId: RULE_ID,
|
|
196
|
+
defaultSeverity: "info",
|
|
197
|
+
shortDescription: "Component manifest JSON for MCP cost reduction",
|
|
198
|
+
fullDescription: "Looks for a component manifest at the repo root (`components.json`, `lyse.components.json`) or in a monorepo (`apps/*/components.json`, `packages/*/components.json`). When found, validates the file is parseable JSON, has a top-level `components` array or object, and each entry has `{ name, sourceFile }` or `{ name, import }`. Absence emits an info finding; malformed manifests emit warnings. shadcn/ui-style `components.json` (the CLI config file, not a manifest) is detected and not counted as a lyse manifest.",
|
|
199
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/ai-surface-component-manifest-json.md",
|
|
200
|
+
rationale: `Why it matters
|
|
201
|
+
|
|
202
|
+
The Indeed MCP cost study (Stream 1) and the broader manifest-pattern literature (Stream 2) show that a static component manifest reduces MCP tool cost by ~5× compared to reading the underlying source files at lookup time. Agents calling \`lyse_components\` or equivalent get a deterministic, low-token list of components instead of paging through tsx files.
|
|
203
|
+
|
|
204
|
+
The manifest is also the source of truth for component discovery in code-connect and MCP-driven design-to-code workflows — without it, tools must heuristically scan exports or rely on conventions that break across monorepos.
|
|
205
|
+
|
|
206
|
+
Severity is intentionally informational for absence (the absence is a missed optimisation, not a bug). Malformed manifests are warnings because they will silently break consumers.`,
|
|
207
|
+
examples: [
|
|
208
|
+
{
|
|
209
|
+
good: '{ "components": [ { "name": "Button", "sourceFile": "packages/ui/src/button.tsx" } ] }',
|
|
210
|
+
bad: '{ "stuff": [] }',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
good: '{ "components": { "Button": { "import": "@acme/ui" } } }',
|
|
214
|
+
bad: '{ "components": [ { "sourceFile": "x.tsx" } ] }',
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
allowlist: [
|
|
218
|
+
"shadcn/ui `components.json` (the CLI config file) — detected via `$schema` or `aliases` and skipped",
|
|
219
|
+
"files larger than 2 MB — skipped to avoid pathological cases",
|
|
220
|
+
"files matching `ctx.excludePaths` config",
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
defaultOptions: [],
|
|
224
|
+
create: () => ({ evaluate }),
|
|
225
|
+
});
|
|
226
|
+
export const _internal = {
|
|
227
|
+
validateManifest,
|
|
228
|
+
validateEntry,
|
|
229
|
+
isShadcnComponentsJson,
|
|
230
|
+
CANDIDATE_PATTERNS,
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=ai-surface-component-manifest-json.js.map
|