@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
package/dist/cli.js
ADDED
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, renderUsage, runCommand, runMain } from "citty";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { access } from "node:fs/promises";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { loadTokens } from "./loaders/tokens.js";
|
|
8
|
+
import { buildComponentInventory } from "./loaders/components.js";
|
|
9
|
+
import { renderJson } from "./reporters/json.js";
|
|
10
|
+
import { renderSarif } from "./reporters/sarif.js";
|
|
11
|
+
import { renderTerminal } from "./reporters/terminal.js";
|
|
12
|
+
import { formatCoverageFooter } from "./reporters/coverage-footer.js";
|
|
13
|
+
import { renderAgentsMd } from "./reporters/markdown.js";
|
|
14
|
+
import { renderEslintStyle, fromLegacyFinding } from "./cli/output/eslint-style.js";
|
|
15
|
+
import { renderScoreGauge } from "./cli/output/score-gauge.js";
|
|
16
|
+
import { resolveLimit } from "./cli/output/limit.js";
|
|
17
|
+
import { CURRENT_SCORING_VERSION } from "./reliability/score/version-pin.js";
|
|
18
|
+
import { persistCurrentVersion, readMigrationWarning } from "./cli/version-migration.js";
|
|
19
|
+
import { VERSION } from "./index.js";
|
|
20
|
+
import { RULES_VERSION } from "./rules/manifest.js";
|
|
21
|
+
import { checkEntitlement } from "./entitlement/index.js";
|
|
22
|
+
import { computeRepoBucket, BUCKET_SALT } from "./identity/index.js";
|
|
23
|
+
import { startMcpServer } from "./mcp/server.js";
|
|
24
|
+
import { runFix } from "./commands/fix.js";
|
|
25
|
+
import { runExplain } from "./commands/explain.js";
|
|
26
|
+
import { runExplainScore } from "./commands/explain-score.js";
|
|
27
|
+
import { feedbackMissed } from "./commands/feedback.js";
|
|
28
|
+
import { auditDirectory, RefuseToRunError } from "./commands/audit-pipeline.js";
|
|
29
|
+
import { runShare } from "./commands/share.js";
|
|
30
|
+
import { runInit } from "./commands/init.js";
|
|
31
|
+
import { maybePromptForEmail, syncPendingEmail } from "./commands/email-prompt.js";
|
|
32
|
+
import { runMcpSetup } from "./commands/mcp-setup.js";
|
|
33
|
+
import { appendAuditEvent, appendCommandInvokedEvent } from "./history/ndjson-store.js";
|
|
34
|
+
import { ensureGitignoreEntry } from "./util/gitignore.js";
|
|
35
|
+
import { withSpinner } from "./util/with-spinner.js";
|
|
36
|
+
import { showActionMenu } from "./menu/action-menu.js";
|
|
37
|
+
import { runRepl, withExitGuard } from "./menu/repl.js";
|
|
38
|
+
import { countAutoFixable, buildClassifyContext, populateConfidence } from "./codemods/safety.js";
|
|
39
|
+
import { isInteractive, confirm } from "./menu/prompts.js";
|
|
40
|
+
import { detectFromFilesystem } from "./detection/from-filesystem.js";
|
|
41
|
+
import { logAuditStarted, logAuditCompleted, logFindingDiscovered, generateId, ensureConsentDecision, } from "./telemetry/index.js";
|
|
42
|
+
import { runTelemetryOn, runTelemetryOff, runTelemetryStatus } from "./commands/telemetry.js";
|
|
43
|
+
import { runBenchPack } from "./commands/bench-pack.js";
|
|
44
|
+
function computeTerminalOpts(args, isTTY, fileCount, durationMs, cwd, hasTokenRegistry, findingsLimit) {
|
|
45
|
+
const mode = args.verbose ? "verbose" : args.quiet ? "quiet" : "default";
|
|
46
|
+
const noColorEnv = typeof process.env["NO_COLOR"] === "string" && process.env["NO_COLOR"] !== "";
|
|
47
|
+
const color = isTTY && !args["no-color"] && !noColorEnv;
|
|
48
|
+
const unicode = (isTTY && process.platform !== "win32") || !!process.env["WT_SESSION"] || !!process.env["TERM_PROGRAM"];
|
|
49
|
+
const width = Math.min(process.stdout.columns ?? 80, 100);
|
|
50
|
+
return {
|
|
51
|
+
mode, color, unicode, width, outDir: args.output, fileCount, durationMs, cwd, hasTokenRegistry,
|
|
52
|
+
...(findingsLimit !== undefined ? { findingsLimit } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// ESLint-style text renderer — problems first, score gauge in the footer.
|
|
57
|
+
// Spec § 9 + T31: defaults to ESLint-style; --format=legacy restores the older
|
|
58
|
+
// gauge-first terminal layout from reporters/terminal.ts.
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
function renderEslintStyleAudit(result, limit) {
|
|
61
|
+
const eslintFindings = result.findings.map(fromLegacyFinding);
|
|
62
|
+
const experimental = eslintFindings.filter((f) => f.confidence === "low").length;
|
|
63
|
+
const counted = eslintFindings.length - experimental;
|
|
64
|
+
const sections = [];
|
|
65
|
+
const findingsBlock = renderEslintStyle({
|
|
66
|
+
findings: eslintFindings,
|
|
67
|
+
counted,
|
|
68
|
+
experimental,
|
|
69
|
+
...(limit !== undefined ? { limit } : {}),
|
|
70
|
+
});
|
|
71
|
+
if (findingsBlock)
|
|
72
|
+
sections.push(findingsBlock);
|
|
73
|
+
const gauge = renderScoreGauge(result.finalScore, CURRENT_SCORING_VERSION, counted, experimental, { toolVersion: result.toolVersion });
|
|
74
|
+
sections.push(gauge);
|
|
75
|
+
if (result.meta?.coverage) {
|
|
76
|
+
sections.push(formatCoverageFooter(result.meta.coverage));
|
|
77
|
+
}
|
|
78
|
+
return sections.join("\n\n");
|
|
79
|
+
}
|
|
80
|
+
async function runAudit(repoRoot, flags) {
|
|
81
|
+
const pipeline = await auditDirectory(repoRoot, flags);
|
|
82
|
+
const hasTokenRegistry = !!(pipeline.config.designSystem?.componentsModule);
|
|
83
|
+
// Populate `Finding.confidence` once, here, so every downstream consumer
|
|
84
|
+
// (score gauge experimental counter, ESLint-style EXP tag, JSON/SARIF
|
|
85
|
+
// reporters, telemetry) sees the same classification. The pipeline emits
|
|
86
|
+
// findings without confidence because the classification needs repo-wide
|
|
87
|
+
// context (tokens, components, repoRoot) that's only assembled here.
|
|
88
|
+
const ctx = buildClassifyContext(pipeline.result.findings, pipeline.tokens, pipeline.config, repoRoot);
|
|
89
|
+
const result = populateConfidence(pipeline.result, ctx);
|
|
90
|
+
return {
|
|
91
|
+
result,
|
|
92
|
+
tokens: pipeline.tokens,
|
|
93
|
+
config: pipeline.config,
|
|
94
|
+
componentInventory: pipeline.componentInventory,
|
|
95
|
+
fileCount: pipeline.fileCount,
|
|
96
|
+
hasTokenRegistry,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Global flags helpers
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
function applyGlobalFlags(args) {
|
|
103
|
+
if (args.yes === true)
|
|
104
|
+
process.env.LYSE_YES = "1";
|
|
105
|
+
if (args["no-prompt"] === true)
|
|
106
|
+
process.env.LYSE_NO_PROMPT = "1";
|
|
107
|
+
if (args["no-color"] === true)
|
|
108
|
+
process.env.NO_COLOR = "1";
|
|
109
|
+
if (args.quiet === true)
|
|
110
|
+
process.env.LYSE_QUIET = "1";
|
|
111
|
+
// --config <path> overrides .lyse.yaml discovery (Spec § 8).
|
|
112
|
+
// audit-pipeline.ts reads LYSE_CONFIG_PATH before falling back to discovery.
|
|
113
|
+
if (typeof args.config === "string" && args.config) {
|
|
114
|
+
process.env.LYSE_CONFIG_PATH = resolve(args.config);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Global flags shared across every subcommand so they are parsed and applied
|
|
118
|
+
// even when citty routes directly to the subcommand's run() without executing
|
|
119
|
+
// the parent command's run().
|
|
120
|
+
const GLOBAL_FLAGS = {
|
|
121
|
+
yes: { type: "boolean", description: "Accept all defaults (no prompts)" },
|
|
122
|
+
"no-prompt": { type: "boolean", description: "Refuse prompts; error on missing input" },
|
|
123
|
+
"no-color": { type: "boolean", description: "Disable ANSI color output" },
|
|
124
|
+
quiet: { type: "boolean", description: "Suppress informational output" },
|
|
125
|
+
config: { type: "string", description: "Path to a config file (overrides .lyse.yaml discovery)" },
|
|
126
|
+
};
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Subcommands
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
const auditCommand = defineCommand({
|
|
131
|
+
meta: { name: "audit", description: "Audit a repository's design system" },
|
|
132
|
+
args: {
|
|
133
|
+
root: { type: "positional", required: false, default: ".", description: "repository root (defaults to current working directory)" },
|
|
134
|
+
output: { type: "string", description: "output directory (default: stdout)" },
|
|
135
|
+
format: { type: "string", description: "json | text | eslint | legacy | sarif (default: text → ESLint-style for tty, json otherwise)" },
|
|
136
|
+
"include-timestamps": { type: "boolean", default: false, description: "include timestamp in JSON output (breaks determinism)" },
|
|
137
|
+
quiet: { type: "boolean", default: false, description: "suppress all stdout except score" },
|
|
138
|
+
verbose: { type: "boolean", default: false, description: "show all findings (default: top 5)" },
|
|
139
|
+
"no-color": { type: "boolean", default: false, description: "disable ANSI color output" },
|
|
140
|
+
limit: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "Max findings to render in text/eslint/legacy output (default: 10). Use `all` or `0` to show every finding. Ignored by --format=json|sarif.",
|
|
143
|
+
},
|
|
144
|
+
threshold: { type: "string", description: "fail (exit 1) if final score < threshold", default: "0" },
|
|
145
|
+
"static-only": {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
description: "Skip Layer 4 LLM augmentation; report static-only score (~30% coverage)",
|
|
148
|
+
},
|
|
149
|
+
"cost-cap-usd": {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "Abort if projected LLM cost exceeds this amount (default: $5 local, $1 CI)",
|
|
152
|
+
},
|
|
153
|
+
"no-cache": {
|
|
154
|
+
type: "boolean",
|
|
155
|
+
description: "Ignore the LLM cache; force a fresh LLM call",
|
|
156
|
+
},
|
|
157
|
+
"llm-provider": {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Override the LLM provider (anthropic | openai | openai-compat | ollama)",
|
|
160
|
+
},
|
|
161
|
+
"llm-model": {
|
|
162
|
+
type: "string",
|
|
163
|
+
description: "Override the LLM model",
|
|
164
|
+
},
|
|
165
|
+
dim: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "Focus the LLM audit on a single axis (tokens, a11y, components, stories, ai-surface).",
|
|
168
|
+
},
|
|
169
|
+
interactive: {
|
|
170
|
+
type: "boolean",
|
|
171
|
+
default: false,
|
|
172
|
+
description: "after audit, prompt for each finding (y/n/?/s/q) and optionally send verdicts to /v1/feedback (requires `lyse telemetry on`)",
|
|
173
|
+
},
|
|
174
|
+
"no-telemetry": {
|
|
175
|
+
type: "boolean",
|
|
176
|
+
default: false,
|
|
177
|
+
description: "Force-disable telemetry for this single run only (does not change persisted consent)",
|
|
178
|
+
},
|
|
179
|
+
yes: GLOBAL_FLAGS.yes,
|
|
180
|
+
"no-prompt": GLOBAL_FLAGS["no-prompt"],
|
|
181
|
+
},
|
|
182
|
+
async run({ args }) {
|
|
183
|
+
applyGlobalFlags(args);
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
const repoRoot = resolve(args.root);
|
|
186
|
+
// T40: one-time warning when migrating from an alpha release. Printed to
|
|
187
|
+
// stderr so it doesn't pollute stdout-captured JSON/SARIF output, and
|
|
188
|
+
// suppressed under --quiet for CI noise hygiene.
|
|
189
|
+
{
|
|
190
|
+
const mig = readMigrationWarning({ currentVersion: VERSION });
|
|
191
|
+
if (mig.warning && args.quiet !== true) {
|
|
192
|
+
process.stderr.write(mig.warning);
|
|
193
|
+
}
|
|
194
|
+
persistCurrentVersion({ currentVersion: VERSION });
|
|
195
|
+
}
|
|
196
|
+
const entitlement = await checkEntitlement("audit");
|
|
197
|
+
if (!entitlement.allowed) {
|
|
198
|
+
console.error(`Feature 'audit' not available on plan '${entitlement.plan}': ${entitlement.reason}`);
|
|
199
|
+
process.exit(2);
|
|
200
|
+
}
|
|
201
|
+
// Resolve telemetry consent (may prompt on first run). Per ADR 0012,
|
|
202
|
+
// never emit on the run that triggered the prompt.
|
|
203
|
+
const consent = await ensureConsentDecision();
|
|
204
|
+
const telemetryActive = consent.accepted && !consent.justAsked && args["no-telemetry"] !== true;
|
|
205
|
+
// Build telemetry context early (only if opt-in and not just asked)
|
|
206
|
+
let telemetryCtx = null;
|
|
207
|
+
if (telemetryActive) {
|
|
208
|
+
const repoBucket = computeRepoBucket(repoRoot);
|
|
209
|
+
if (repoBucket) {
|
|
210
|
+
telemetryCtx = {
|
|
211
|
+
repoRoot,
|
|
212
|
+
sessionId: generateId(),
|
|
213
|
+
repoBucket,
|
|
214
|
+
sdkVersion: VERSION,
|
|
215
|
+
rulesVersion: RULES_VERSION,
|
|
216
|
+
salt: BUCKET_SALT,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Build flag overrides from CLI args.
|
|
221
|
+
const auditFlags = {
|
|
222
|
+
...(args["static-only"] === true ? { staticOnly: true } : {}),
|
|
223
|
+
...(typeof args["cost-cap-usd"] === "string" && args["cost-cap-usd"]
|
|
224
|
+
? { costCapUsd: parseFloat(args["cost-cap-usd"]) }
|
|
225
|
+
: {}),
|
|
226
|
+
...(args["no-cache"] === true ? { noCache: true } : {}),
|
|
227
|
+
...(typeof args["llm-provider"] === "string" && args["llm-provider"]
|
|
228
|
+
? { llmProvider: args["llm-provider"] }
|
|
229
|
+
: {}),
|
|
230
|
+
...(typeof args["llm-model"] === "string" && args["llm-model"]
|
|
231
|
+
? { llmModel: args["llm-model"] }
|
|
232
|
+
: {}),
|
|
233
|
+
...(typeof args["dim"] === "string" && args["dim"]
|
|
234
|
+
? { llmDimension: args["dim"].trim().toLowerCase() }
|
|
235
|
+
: {}),
|
|
236
|
+
};
|
|
237
|
+
// Issue #97 — visual feedback. Compute spinner enablement BEFORE running
|
|
238
|
+
// the audit so we can surface phase progress. The format default mirrors
|
|
239
|
+
// the post-audit logic below (TTY → "text", else "json") so we suppress
|
|
240
|
+
// the spinner whenever stdout receives machine-readable output.
|
|
241
|
+
const isTTYForSpinner = process.stdout.isTTY ?? false;
|
|
242
|
+
const formatForSpinner = args.format ?? (isTTYForSpinner ? "text" : "json");
|
|
243
|
+
const isQuiet = args.quiet === true;
|
|
244
|
+
const isMachineFormatForSpinner = formatForSpinner === "json" || formatForSpinner === "sarif";
|
|
245
|
+
let result, fileCount, hasTokenRegistry;
|
|
246
|
+
let tokens, config;
|
|
247
|
+
try {
|
|
248
|
+
({ result, fileCount, hasTokenRegistry, tokens, config } = await withSpinner({
|
|
249
|
+
isTTY: isTTYForSpinner,
|
|
250
|
+
quiet: isQuiet,
|
|
251
|
+
machineFormat: isMachineFormatForSpinner,
|
|
252
|
+
startLabel: "Discovering files…",
|
|
253
|
+
successLabel: (r) => {
|
|
254
|
+
const elapsedSec = Math.round((Date.now() - startTime) / 100) / 10;
|
|
255
|
+
return (`Audit complete · ${r.result.findings.length} findings · ` +
|
|
256
|
+
`score ${r.result.finalScore}/100 · tier ${r.result.tier} · ${elapsedSec}s`);
|
|
257
|
+
},
|
|
258
|
+
failLabel: (m) => `Audit failed: ${m}`,
|
|
259
|
+
}, async (spinner) => {
|
|
260
|
+
const flagsWithProgress = { ...auditFlags, progress: spinner };
|
|
261
|
+
return runAudit(repoRoot, flagsWithProgress);
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
if (err instanceof RefuseToRunError) {
|
|
266
|
+
console.error(`[lyse] ${err.message}`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
throw err;
|
|
270
|
+
}
|
|
271
|
+
// Emit telemetry events (all after runAudit so stack is known; semantics refined in V0.2)
|
|
272
|
+
if (telemetryCtx) {
|
|
273
|
+
const stack = {};
|
|
274
|
+
if (result.stack[0])
|
|
275
|
+
stack.framework = result.stack[0];
|
|
276
|
+
if (result.stack[1])
|
|
277
|
+
stack.ds_detected = result.stack[1];
|
|
278
|
+
logAuditStarted(telemetryCtx, stack);
|
|
279
|
+
logAuditCompleted(telemetryCtx, Date.now() - startTime, result);
|
|
280
|
+
for (const f of result.findings)
|
|
281
|
+
logFindingDiscovered(telemetryCtx, f);
|
|
282
|
+
}
|
|
283
|
+
// Ensure .lyse/ is in .gitignore before writing history (idempotent guard against untracked-dirty)
|
|
284
|
+
await ensureGitignoreEntry(repoRoot, ".lyse/");
|
|
285
|
+
// Append audit event to history (for delta display)
|
|
286
|
+
const tokensScore = result.axes.find((a) => a.axis === "tokens")?.score;
|
|
287
|
+
const a11yScore = result.axes.find((a) => a.axis === "a11y")?.score;
|
|
288
|
+
const componentsScore = result.axes.find((a) => a.axis === "components")?.score;
|
|
289
|
+
const storiesScore = result.axes.find((a) => a.axis === "stories")?.score;
|
|
290
|
+
await appendAuditEvent(repoRoot, {
|
|
291
|
+
score: typeof result.finalScore === "number" ? result.finalScore : 0,
|
|
292
|
+
axes: {
|
|
293
|
+
tokens: typeof tokensScore === "number" ? tokensScore : null,
|
|
294
|
+
a11y: typeof a11yScore === "number" ? a11yScore : null,
|
|
295
|
+
components: typeof componentsScore === "number" ? componentsScore : null,
|
|
296
|
+
stories: typeof storiesScore === "number" ? storiesScore : null,
|
|
297
|
+
},
|
|
298
|
+
findings_count: result.findings.length,
|
|
299
|
+
}, null);
|
|
300
|
+
const isTTY = process.stdout.isTTY ?? false;
|
|
301
|
+
const format = args.format ?? (isTTY ? "text" : "json");
|
|
302
|
+
// Resolve --limit for text/eslint/legacy output. JSON/SARIF intentionally
|
|
303
|
+
// ignore the flag (machine consumers want the full report, always). When
|
|
304
|
+
// the user doesn't pass --limit, the per-format default differs:
|
|
305
|
+
// legacy → undefined, so terminal.ts falls back to its historical
|
|
306
|
+
// top-5 / verbose=all behavior;
|
|
307
|
+
// text/eslint → null (unlimited) — eslint-style already lists findings
|
|
308
|
+
// as a flat block; users see every finding by default and
|
|
309
|
+
// pass --limit=N to truncate.
|
|
310
|
+
let textFindingsLimit;
|
|
311
|
+
try {
|
|
312
|
+
textFindingsLimit = resolveLimit(args, format === "legacy" ? undefined : null);
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
console.error(`[lyse] ${err.message}`);
|
|
316
|
+
process.exit(64); // EX_USAGE
|
|
317
|
+
}
|
|
318
|
+
if (format === "sarif") {
|
|
319
|
+
const sarifContent = renderSarif(result, { includeTimestamp: !!args["include-timestamps"] });
|
|
320
|
+
if (args.output) {
|
|
321
|
+
const outDir = resolve(args.output);
|
|
322
|
+
mkdirSync(outDir, { recursive: true });
|
|
323
|
+
writeFileSync(join(outDir, "lyse.sarif"), sarifContent);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
process.stdout.write(sarifContent);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
const jsonContent = renderJson(result, { includeTimestamp: !!args["include-timestamps"] });
|
|
331
|
+
const isTextFormat = format === "text" || format === "eslint" || format === "legacy";
|
|
332
|
+
const renderTextForStdout = async () => {
|
|
333
|
+
if (format === "legacy") {
|
|
334
|
+
const opts = computeTerminalOpts(args, isTTY, fileCount, Date.now() - startTime, repoRoot, hasTokenRegistry, textFindingsLimit);
|
|
335
|
+
return (await renderTerminal(result, opts)) + "\n";
|
|
336
|
+
}
|
|
337
|
+
return renderEslintStyleAudit(result, textFindingsLimit) + "\n";
|
|
338
|
+
};
|
|
339
|
+
if (args.output) {
|
|
340
|
+
// File mode — write files; only emit text to stdout if format=text|eslint|legacy.
|
|
341
|
+
const outDir = resolve(args.output);
|
|
342
|
+
mkdirSync(outDir, { recursive: true });
|
|
343
|
+
writeFileSync(join(outDir, "lyse.json"), jsonContent);
|
|
344
|
+
if (isTextFormat) {
|
|
345
|
+
process.stdout.write(await renderTextForStdout());
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
// Stdout mode — print exactly one format.
|
|
350
|
+
if (format === "json") {
|
|
351
|
+
process.stdout.write(jsonContent);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
process.stdout.write(await renderTextForStdout());
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
// Post-audit action menu (spec § 6.9 + § 9) + interactive feedback (T23).
|
|
360
|
+
// Skip when: --quiet, non-TTY / CI, --format=json|sarif (machine output),
|
|
361
|
+
// --no-prompt (refuse prompts), --yes (accept defaults — auto-skip prompts).
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
const isMachineFormat = format === "json" || format === "sarif";
|
|
364
|
+
const promptsAllowed = !args.quiet &&
|
|
365
|
+
!isMachineFormat &&
|
|
366
|
+
isInteractive() &&
|
|
367
|
+
args["no-prompt"] !== true &&
|
|
368
|
+
args.yes !== true;
|
|
369
|
+
const wantsFeedback = args.interactive === true && promptsAllowed && result.findings.length > 0;
|
|
370
|
+
// Once-per-machine opt-in email capture for release & security updates.
|
|
371
|
+
// Short-circuits on --yes / CI / non-TTY / LYSE_NO_EMAIL_PROMPT=1 / when
|
|
372
|
+
// ~/.lyse/profile.json already records a decision. `syncPendingEmail`
|
|
373
|
+
// runs always (incl. non-TTY) to retry any captured-but-undelivered email.
|
|
374
|
+
if (promptsAllowed) {
|
|
375
|
+
await maybePromptForEmail({ yes: args.yes === true });
|
|
376
|
+
}
|
|
377
|
+
await syncPendingEmail();
|
|
378
|
+
if (promptsAllowed && !wantsFeedback) {
|
|
379
|
+
// Standard action menu path (no --interactive, or no findings).
|
|
380
|
+
const autoFixableCount = countAutoFixable(result.findings, tokens, config, repoRoot);
|
|
381
|
+
const fsDetect = await detectFromFilesystem(repoRoot);
|
|
382
|
+
const detectedIDE = !!(fsDetect.cursor.value || fsDetect.claudeCode.value);
|
|
383
|
+
const choice = await showActionMenu({ autoFixableCount, detectedIDE });
|
|
384
|
+
if (choice === "fix") {
|
|
385
|
+
await runFix({ cwd: repoRoot, autoApprove: args.yes === true });
|
|
386
|
+
}
|
|
387
|
+
else if (choice === "mcp-setup") {
|
|
388
|
+
await runMcpSetup({ cwd: repoRoot, autoApprove: args.yes === true });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else if (wantsFeedback) {
|
|
392
|
+
// --interactive mode: skip the action menu and go straight to per-finding
|
|
393
|
+
// feedback prompts. Consent is governed by ADR 0012 (~/.lyse/consent.json);
|
|
394
|
+
// the prior banner-based ack has been retired.
|
|
395
|
+
const { runInteractiveFeedback } = await import("./reliability/feedback/interactive.js");
|
|
396
|
+
await runInteractiveFeedback({ findings: result.findings, repoRoot });
|
|
397
|
+
}
|
|
398
|
+
// Exit code logic — sysexits.h-style
|
|
399
|
+
const threshold = parseInt(String(args.threshold ?? "0"), 10);
|
|
400
|
+
if (Number.isNaN(threshold)) {
|
|
401
|
+
console.error(`Invalid --threshold value: ${args.threshold}`);
|
|
402
|
+
process.exit(64); // EX_USAGE
|
|
403
|
+
}
|
|
404
|
+
// Emit command_invoked metric (opt-in, only when consent has been accepted)
|
|
405
|
+
const didFail = typeof result.finalScore === "number" && result.finalScore < threshold;
|
|
406
|
+
await appendCommandInvokedEvent(repoRoot, "audit", didFail ? "error" : "success", Date.now() - startTime);
|
|
407
|
+
if (didFail) {
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
// Implicit exit 0 (success)
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
// Shared handler for agents command and agents-md alias
|
|
414
|
+
// Design note: `lyse agents` writes to stdout by default; users redirect to
|
|
415
|
+
// AGENTS.md themselves (shell handles overwrites). When --output <path> is
|
|
416
|
+
// provided, WE write the file and must prompt before clobbering an existing
|
|
417
|
+
// one. --yes bypasses the prompt; --no-prompt errors out cleanly in CI.
|
|
418
|
+
async function agentsHandler({ args }) {
|
|
419
|
+
applyGlobalFlags(args);
|
|
420
|
+
const entitlement = await checkEntitlement("agents");
|
|
421
|
+
if (!entitlement.allowed) {
|
|
422
|
+
console.error(`Feature 'agents' not available on plan '${entitlement.plan}': ${entitlement.reason}`);
|
|
423
|
+
process.exit(2);
|
|
424
|
+
}
|
|
425
|
+
const repoRoot = resolve(args.root ?? ".");
|
|
426
|
+
const agentFlags = {
|
|
427
|
+
...(args["static-only"] === true ? { staticOnly: true } : {}),
|
|
428
|
+
};
|
|
429
|
+
const { result, tokens, componentInventory } = await runAudit(repoRoot, agentFlags);
|
|
430
|
+
const namespaces = [];
|
|
431
|
+
if (tokens) {
|
|
432
|
+
if (tokens.colors.size > 0)
|
|
433
|
+
namespaces.push("color/*");
|
|
434
|
+
if (tokens.spacing.size > 0)
|
|
435
|
+
namespaces.push("spacing/*");
|
|
436
|
+
}
|
|
437
|
+
const md = renderAgentsMd(result, {
|
|
438
|
+
tokenNamespaces: namespaces,
|
|
439
|
+
components: componentInventory.map((c) => c.name),
|
|
440
|
+
});
|
|
441
|
+
if (args.output) {
|
|
442
|
+
const outputPath = resolve(args.output);
|
|
443
|
+
let fileExists = false;
|
|
444
|
+
try {
|
|
445
|
+
await access(outputPath);
|
|
446
|
+
fileExists = true;
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// File doesn't exist — safe to write
|
|
450
|
+
}
|
|
451
|
+
if (fileExists) {
|
|
452
|
+
// --yes bypasses prompt; --no-prompt errors when can't prompt interactively
|
|
453
|
+
if (process.env.LYSE_YES === "1") {
|
|
454
|
+
// Auto-approved — overwrite silently
|
|
455
|
+
}
|
|
456
|
+
else if (process.env.LYSE_NO_PROMPT === "1") {
|
|
457
|
+
console.error(`${outputPath} exists. Use --yes to overwrite.`);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
const ok = await confirm(`${outputPath} exists. Overwrite?`, false);
|
|
462
|
+
if (!ok) {
|
|
463
|
+
console.log("Aborted (existing file preserved).");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
writeFileSync(outputPath, md);
|
|
469
|
+
console.log(`Wrote ${outputPath}`);
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
process.stdout.write(md);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const agentsCommand = defineCommand({
|
|
476
|
+
meta: { name: "agents", description: "Generate AGENTS.md from the project at <path>" },
|
|
477
|
+
args: {
|
|
478
|
+
root: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
479
|
+
output: { type: "string", description: "write to file (default: stdout)" },
|
|
480
|
+
"static-only": {
|
|
481
|
+
type: "boolean",
|
|
482
|
+
description: "Skip Layer 4 LLM augmentation; use static-only findings",
|
|
483
|
+
},
|
|
484
|
+
...GLOBAL_FLAGS,
|
|
485
|
+
},
|
|
486
|
+
run: agentsHandler,
|
|
487
|
+
});
|
|
488
|
+
const agentsMdCommand = defineCommand({
|
|
489
|
+
meta: { name: "agents-md", description: "DEPRECATED: use `lyse agents`" },
|
|
490
|
+
args: {
|
|
491
|
+
root: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
492
|
+
output: { type: "string", description: "write to file (default: stdout)" },
|
|
493
|
+
"static-only": {
|
|
494
|
+
type: "boolean",
|
|
495
|
+
description: "Skip Layer 4 LLM augmentation; use static-only findings",
|
|
496
|
+
},
|
|
497
|
+
...GLOBAL_FLAGS,
|
|
498
|
+
},
|
|
499
|
+
async run({ args }) {
|
|
500
|
+
process.stderr.write("WARNING: `lyse agents-md` is deprecated. Use `lyse agents` instead. (Alias removed in v0.2.)\n");
|
|
501
|
+
await agentsHandler({ args });
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
const versionCommand = defineCommand({
|
|
505
|
+
meta: { name: "version", description: "Print tool, rules, and schema versions" },
|
|
506
|
+
args: { ...GLOBAL_FLAGS },
|
|
507
|
+
async run({ args }) {
|
|
508
|
+
applyGlobalFlags(args);
|
|
509
|
+
process.stdout.write([
|
|
510
|
+
`lyse ${VERSION}`,
|
|
511
|
+
`rules ${RULES_VERSION}`,
|
|
512
|
+
`schema-versions: result=2, event=1.0.0, config=1.0.0, license=1.0.0, rules=1.0.0`,
|
|
513
|
+
"",
|
|
514
|
+
].join("\n"));
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
// ---------------------------------------------------------------------------
|
|
518
|
+
// explain subcommand
|
|
519
|
+
// ---------------------------------------------------------------------------
|
|
520
|
+
const explainCommand = defineCommand({
|
|
521
|
+
meta: { name: "explain", description: "Show rationale for a rule, or the score breakdown (--score)" },
|
|
522
|
+
args: {
|
|
523
|
+
ruleId: { type: "positional", required: false, description: "rule id, e.g. tokens/no-hardcoded-color (omit with --score)" },
|
|
524
|
+
score: { type: "boolean", default: false, description: "Show a Lighthouse-style score breakdown of the current repo's Health Score" },
|
|
525
|
+
"static-only": { type: "boolean", default: false, description: "(with --score) skip the LLM augmentation step" },
|
|
526
|
+
format: { type: "string", default: "text", description: "text | md (default: text)" },
|
|
527
|
+
...GLOBAL_FLAGS,
|
|
528
|
+
},
|
|
529
|
+
async run({ args }) {
|
|
530
|
+
applyGlobalFlags(args);
|
|
531
|
+
if (args.score === true) {
|
|
532
|
+
await runExplainScore({
|
|
533
|
+
cwd: process.cwd(),
|
|
534
|
+
...(args["static-only"] === true ? { staticOnly: true } : {}),
|
|
535
|
+
});
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (typeof args.ruleId !== "string" || args.ruleId.length === 0) {
|
|
539
|
+
process.stderr.write("Usage: lyse explain <ruleId>\n or: lyse explain --score [--static-only]\n");
|
|
540
|
+
process.exitCode = 64;
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
await runExplain({ cwd: process.cwd(), ruleId: args.ruleId, format: args.format });
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
async function requireMcpServerEntitlement() {
|
|
547
|
+
const entitlement = await checkEntitlement("mcp_server");
|
|
548
|
+
if (!entitlement.allowed) {
|
|
549
|
+
console.error(`Feature 'mcp_server' not available on plan '${entitlement.plan}': ${entitlement.reason}`);
|
|
550
|
+
process.exit(2);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
const mcpCommand = defineCommand({
|
|
554
|
+
meta: { name: "mcp", description: "MCP server for AI agents" },
|
|
555
|
+
subCommands: {
|
|
556
|
+
setup: defineCommand({
|
|
557
|
+
meta: { name: "setup", description: "Configure your IDE's MCP file" },
|
|
558
|
+
args: {
|
|
559
|
+
path: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
560
|
+
target: { type: "string", description: "cursor | claude-code | both" },
|
|
561
|
+
dev: { type: "boolean", description: "Force absolute-path entry (auto-detected when running from a local build)." },
|
|
562
|
+
...GLOBAL_FLAGS,
|
|
563
|
+
},
|
|
564
|
+
async run({ args }) {
|
|
565
|
+
applyGlobalFlags(args);
|
|
566
|
+
const { resolve } = await import("node:path");
|
|
567
|
+
const yes = args.yes === true;
|
|
568
|
+
const opts = {
|
|
569
|
+
cwd: resolve(String(args.path ?? ".")),
|
|
570
|
+
autoApprove: yes,
|
|
571
|
+
};
|
|
572
|
+
if (typeof args.target === "string") {
|
|
573
|
+
opts.target = args.target;
|
|
574
|
+
}
|
|
575
|
+
if (args.dev === true) {
|
|
576
|
+
opts.dev = true;
|
|
577
|
+
}
|
|
578
|
+
const isQuiet = args.quiet === true;
|
|
579
|
+
await withSpinner({
|
|
580
|
+
quiet: isQuiet,
|
|
581
|
+
startLabel: "Writing MCP config…",
|
|
582
|
+
successLabel: () => "MCP configured",
|
|
583
|
+
failLabel: (m) => `MCP setup failed: ${m}`,
|
|
584
|
+
}, async () => runMcpSetup(opts));
|
|
585
|
+
},
|
|
586
|
+
}),
|
|
587
|
+
serve: defineCommand({
|
|
588
|
+
meta: {
|
|
589
|
+
name: "serve",
|
|
590
|
+
description: "Start the MCP stdio server (this is what `.mcp.json` / `.cursor/mcp.json` invoke).",
|
|
591
|
+
},
|
|
592
|
+
async run() {
|
|
593
|
+
await requireMcpServerEntitlement();
|
|
594
|
+
await startMcpServer();
|
|
595
|
+
},
|
|
596
|
+
}),
|
|
597
|
+
},
|
|
598
|
+
async run() {
|
|
599
|
+
await requireMcpServerEntitlement();
|
|
600
|
+
await startMcpServer();
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
const fixCommand = defineCommand({
|
|
604
|
+
meta: { name: "fix", description: "Auto-fix design system violations with confidence gates and safety guards" },
|
|
605
|
+
args: {
|
|
606
|
+
path: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
607
|
+
"dry-run": { type: "boolean", default: false, description: "preview changes without writing or committing" },
|
|
608
|
+
interactive: { type: "boolean", default: false, description: "enable interactive prompts" },
|
|
609
|
+
confidence: { type: "string", default: "high", description: "confidence floor: high | medium | low" },
|
|
610
|
+
rule: { type: "string", description: "limit fixes to a specific rule ID" },
|
|
611
|
+
"force-on-dirty": { type: "boolean", default: false, description: "allow running on a dirty working tree" },
|
|
612
|
+
"verify-with-tests": { type: "boolean", default: false, description: "run tests after each rule batch; revert on failure" },
|
|
613
|
+
branch: { type: "string", description: "override the branch name (useful for tests)" },
|
|
614
|
+
...GLOBAL_FLAGS,
|
|
615
|
+
},
|
|
616
|
+
async run({ args }) {
|
|
617
|
+
applyGlobalFlags(args);
|
|
618
|
+
const entitlement = await checkEntitlement("fix");
|
|
619
|
+
if (!entitlement.allowed) {
|
|
620
|
+
console.error(`Feature 'fix' not available on plan '${entitlement.plan}': ${entitlement.reason}`);
|
|
621
|
+
process.exit(2);
|
|
622
|
+
}
|
|
623
|
+
const cwd = resolve(args.path ?? ".");
|
|
624
|
+
const opts = {
|
|
625
|
+
cwd,
|
|
626
|
+
dryRun: args["dry-run"],
|
|
627
|
+
interactive: args.interactive,
|
|
628
|
+
confidence: (args.confidence ?? "high"),
|
|
629
|
+
rule: args.rule,
|
|
630
|
+
forceOnDirty: args["force-on-dirty"],
|
|
631
|
+
verifyWithTests: args["verify-with-tests"],
|
|
632
|
+
branch: args.branch,
|
|
633
|
+
};
|
|
634
|
+
const isQuiet = args.quiet === true;
|
|
635
|
+
const result = await withSpinner({
|
|
636
|
+
quiet: isQuiet,
|
|
637
|
+
startLabel: "Discovering files…",
|
|
638
|
+
successLabel: () => "Fix complete",
|
|
639
|
+
failLabel: (m) => `Fix failed: ${m}`,
|
|
640
|
+
}, async () => runFix(opts));
|
|
641
|
+
console.log(`✓ Branch: ${result.branch}`);
|
|
642
|
+
for (const r of result.ruleResults) {
|
|
643
|
+
const testStatus = r.testsPassed === false ? " (tests failed, reverted)" : r.testsPassed === true ? " (tests passed)" : "";
|
|
644
|
+
console.log(`✓ ${r.ruleId}: ${r.count} fixes${testStatus}`);
|
|
645
|
+
if (r.warnings && r.warnings.length > 0) {
|
|
646
|
+
for (const w of r.warnings) {
|
|
647
|
+
process.stderr.write(` ⚠ ${w}\n`);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (result.skipped.medium > 0 || result.skipped.low > 0) {
|
|
652
|
+
console.log(` Skipped: ${result.skipped.medium} medium-confidence, ${result.skipped.low} low-confidence findings`);
|
|
653
|
+
console.log(` Use --confidence=medium or --interactive to review.`);
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
const shareCommand = defineCommand({
|
|
658
|
+
meta: { name: "share", description: "Audit + copy Markdown summary to clipboard" },
|
|
659
|
+
args: {
|
|
660
|
+
path: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
661
|
+
...GLOBAL_FLAGS,
|
|
662
|
+
},
|
|
663
|
+
async run({ args }) {
|
|
664
|
+
applyGlobalFlags(args);
|
|
665
|
+
const entitlement = await checkEntitlement("share");
|
|
666
|
+
if (!entitlement.allowed) {
|
|
667
|
+
console.error(`Feature 'share' not available on plan '${entitlement.plan}': ${entitlement.reason}`);
|
|
668
|
+
process.exit(2);
|
|
669
|
+
}
|
|
670
|
+
const cwd = resolve(args.path ?? ".");
|
|
671
|
+
await runShare(cwd, { quiet: args.quiet === true });
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
const initCommand = defineCommand({
|
|
675
|
+
meta: { name: "init", description: "Interactive wizard for first-time setup" },
|
|
676
|
+
args: {
|
|
677
|
+
path: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
678
|
+
"first-run": { type: "boolean", description: "mark as first run (used by npm create lyse)" },
|
|
679
|
+
...GLOBAL_FLAGS,
|
|
680
|
+
},
|
|
681
|
+
async run({ args }) {
|
|
682
|
+
applyGlobalFlags(args);
|
|
683
|
+
const { resolve } = await import("node:path");
|
|
684
|
+
const isQuiet = args.quiet === true;
|
|
685
|
+
await withSpinner({
|
|
686
|
+
quiet: isQuiet,
|
|
687
|
+
startLabel: "Detecting framework…",
|
|
688
|
+
successLabel: () => "Initialized .lyse.yaml + AGENTS.md",
|
|
689
|
+
failLabel: (m) => `Init failed: ${m}`,
|
|
690
|
+
}, async () => runInit({
|
|
691
|
+
cwd: resolve(args.path ?? "."),
|
|
692
|
+
firstRun: args["first-run"],
|
|
693
|
+
yes: args.yes === true,
|
|
694
|
+
}));
|
|
695
|
+
},
|
|
696
|
+
});
|
|
697
|
+
const feedbackCommand = defineCommand({
|
|
698
|
+
meta: {
|
|
699
|
+
name: "feedback",
|
|
700
|
+
description: "Send feedback on missed findings to the hand-label queue (requires `lyse telemetry on`)",
|
|
701
|
+
},
|
|
702
|
+
args: {
|
|
703
|
+
missed: { type: "string", description: "<file>:<line> the auditor missed", required: true },
|
|
704
|
+
"sub-axis": { type: "string", description: "Sub-axis ID you expected to catch this (e.g. tokens.color)" },
|
|
705
|
+
...GLOBAL_FLAGS,
|
|
706
|
+
},
|
|
707
|
+
async run({ args }) {
|
|
708
|
+
applyGlobalFlags(args);
|
|
709
|
+
const cwd = resolve(process.cwd());
|
|
710
|
+
const autoConfirm = args.yes === true;
|
|
711
|
+
const subAxisId = typeof args["sub-axis"] === "string" ? args["sub-axis"] : undefined;
|
|
712
|
+
const fbArgs = {
|
|
713
|
+
cwd,
|
|
714
|
+
missed: args.missed,
|
|
715
|
+
autoConfirm,
|
|
716
|
+
};
|
|
717
|
+
if (subAxisId !== undefined)
|
|
718
|
+
fbArgs.subAxisId = subAxisId;
|
|
719
|
+
const r = await feedbackMissed(fbArgs);
|
|
720
|
+
if (!r.ok)
|
|
721
|
+
process.exitCode = 1;
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
const telemetryCommand = defineCommand({
|
|
725
|
+
meta: {
|
|
726
|
+
name: "telemetry",
|
|
727
|
+
description: "Manage anonymous telemetry consent (see PRIVACY.md)",
|
|
728
|
+
},
|
|
729
|
+
subCommands: {
|
|
730
|
+
on: defineCommand({
|
|
731
|
+
meta: { name: "on", description: "Enable anonymous telemetry" },
|
|
732
|
+
run() {
|
|
733
|
+
runTelemetryOn();
|
|
734
|
+
},
|
|
735
|
+
}),
|
|
736
|
+
off: defineCommand({
|
|
737
|
+
meta: { name: "off", description: "Disable anonymous telemetry" },
|
|
738
|
+
run() {
|
|
739
|
+
runTelemetryOff();
|
|
740
|
+
},
|
|
741
|
+
}),
|
|
742
|
+
status: defineCommand({
|
|
743
|
+
meta: { name: "status", description: "Show current telemetry consent state" },
|
|
744
|
+
run() {
|
|
745
|
+
runTelemetryStatus();
|
|
746
|
+
},
|
|
747
|
+
}),
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
const benchPackCommand = defineCommand({
|
|
751
|
+
meta: { name: "bench-pack", description: "Emit a deterministic evidence pack (JSON) for submission to the public benchmark" },
|
|
752
|
+
args: {
|
|
753
|
+
path: { type: "positional", required: false, default: ".", description: "repository root" },
|
|
754
|
+
output: { type: "string", default: "evidence-pack.json", description: "output JSON path" },
|
|
755
|
+
...GLOBAL_FLAGS,
|
|
756
|
+
},
|
|
757
|
+
async run({ args }) {
|
|
758
|
+
applyGlobalFlags(args);
|
|
759
|
+
const isQuiet = args.quiet === true;
|
|
760
|
+
await withSpinner({
|
|
761
|
+
quiet: isQuiet,
|
|
762
|
+
startLabel: "Packing benchmark…",
|
|
763
|
+
successLabel: () => "Bench pack written",
|
|
764
|
+
failLabel: (m) => `Bench pack failed: ${m}`,
|
|
765
|
+
}, async () => runBenchPack({ cwd: resolve(args.path ?? "."), output: args.output }));
|
|
766
|
+
},
|
|
767
|
+
});
|
|
768
|
+
// ---------------------------------------------------------------------------
|
|
769
|
+
// Root-level REPL dispatch — when `lyse` is invoked without a subcommand on a
|
|
770
|
+
// TTY, an interactive menu lets the user pick an action (audit, fix, mcp-setup,
|
|
771
|
+
// explain, bench-pack, telemetry) and loops back to itself after each run. On
|
|
772
|
+
// non-TTY (CI, pipe) or when `--no-menu` / `LYSE_NO_MENU=1` is set, the menu is
|
|
773
|
+
// skipped and the standard help text is printed instead.
|
|
774
|
+
// ---------------------------------------------------------------------------
|
|
775
|
+
async function dispatchReplAction(action, ctx) {
|
|
776
|
+
switch (action) {
|
|
777
|
+
case "audit":
|
|
778
|
+
await withExitGuard(() => runCommand(auditCommand, { rawArgs: [ctx.cwd] }));
|
|
779
|
+
return;
|
|
780
|
+
case "fix":
|
|
781
|
+
await withExitGuard(() => runCommand(fixCommand, { rawArgs: [ctx.cwd] }));
|
|
782
|
+
return;
|
|
783
|
+
case "mcp-setup":
|
|
784
|
+
await withExitGuard(() => runCommand(mcpCommand, { rawArgs: ["setup", ctx.cwd] }));
|
|
785
|
+
return;
|
|
786
|
+
case "explain": {
|
|
787
|
+
const r = await prompts({
|
|
788
|
+
type: "text",
|
|
789
|
+
name: "v",
|
|
790
|
+
message: "Rule ID (e.g. tokens/no-hardcoded-color, blank to cancel):",
|
|
791
|
+
});
|
|
792
|
+
const ruleId = typeof r.v === "string" ? r.v.trim() : "";
|
|
793
|
+
if (!ruleId)
|
|
794
|
+
return;
|
|
795
|
+
await withExitGuard(() => runCommand(explainCommand, { rawArgs: [ruleId] }));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
case "bench-pack":
|
|
799
|
+
await withExitGuard(() => runCommand(benchPackCommand, { rawArgs: [ctx.cwd] }));
|
|
800
|
+
return;
|
|
801
|
+
case "telemetry": {
|
|
802
|
+
const r = await prompts({
|
|
803
|
+
type: "select",
|
|
804
|
+
name: "v",
|
|
805
|
+
message: "Telemetry:",
|
|
806
|
+
choices: [
|
|
807
|
+
{ title: "Status — show current consent", value: "status" },
|
|
808
|
+
{ title: "On — opt in to anonymous telemetry", value: "on" },
|
|
809
|
+
{ title: "Off — opt out", value: "off" },
|
|
810
|
+
{ title: "Back", value: "back" },
|
|
811
|
+
],
|
|
812
|
+
});
|
|
813
|
+
if (!r.v || r.v === "back")
|
|
814
|
+
return;
|
|
815
|
+
await withExitGuard(() => runCommand(telemetryCommand, { rawArgs: [r.v] }));
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
case "exit":
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const main = defineCommand({
|
|
823
|
+
meta: { name: "lyse", version: VERSION, description: "Audit your design system" },
|
|
824
|
+
args: {
|
|
825
|
+
yes: { type: "boolean", description: "Accept all defaults (no prompts)" },
|
|
826
|
+
"no-prompt": { type: "boolean", description: "Refuse prompts; error on missing input" },
|
|
827
|
+
"no-color": { type: "boolean", description: "Disable ANSI color output" },
|
|
828
|
+
quiet: { type: "boolean", description: "Suppress informational output" },
|
|
829
|
+
"no-menu": { type: "boolean", description: "Skip the interactive menu (print help instead)" },
|
|
830
|
+
},
|
|
831
|
+
subCommands: { init: initCommand, audit: auditCommand, fix: fixCommand, share: shareCommand, agents: agentsCommand, "agents-md": agentsMdCommand, "bench-pack": benchPackCommand, version: versionCommand, explain: explainCommand, mcp: mcpCommand, feedback: feedbackCommand, telemetry: telemetryCommand },
|
|
832
|
+
async run({ args, cmd, rawArgs }) {
|
|
833
|
+
applyGlobalFlags(args);
|
|
834
|
+
// citty calls parent.run() AFTER the matched subcommand finishes — so
|
|
835
|
+
// detect when a subcommand was invoked (first non-flag in rawArgs) and
|
|
836
|
+
// bow out cleanly. Otherwise our help / REPL would print AFTER the
|
|
837
|
+
// subcommand's stdout (breaking audit's JSON / SARIF / mcp-serve output).
|
|
838
|
+
const subCommands = cmd.subCommands;
|
|
839
|
+
if (subCommands) {
|
|
840
|
+
const firstPositional = rawArgs.find((a) => !a.startsWith("-"));
|
|
841
|
+
if (firstPositional && firstPositional in subCommands)
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const noMenu = args["no-menu"] === true || process.env.LYSE_NO_MENU === "1";
|
|
845
|
+
if (noMenu || !isInteractive()) {
|
|
846
|
+
// Use renderUsage + process.stdout.write rather than citty's showUsage
|
|
847
|
+
// because the latter routes through consola, which silently drops output
|
|
848
|
+
// when CI=true is set in the env (regression-proofs CI/test environments).
|
|
849
|
+
process.stdout.write((await renderUsage(cmd)) + "\n");
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
await runRepl({ cwd: process.cwd(), quiet: args.quiet === true, version: VERSION }, dispatchReplAction);
|
|
853
|
+
},
|
|
854
|
+
});
|
|
855
|
+
runMain(main);
|
|
856
|
+
//# sourceMappingURL=cli.js.map
|